From 7b47cfc45084db4dc3fc25e4b830df2cc0d509d6 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 20 Aug 2021 15:48:14 +0800 Subject: [PATCH 001/299] merge 17643 --- cmd/bootnode/main.go | 4 +- cmd/ctool/ppos/reward.go | 6 +- cmd/ctool/ppos/slashing.go | 6 +- cmd/ctool/ppos/staking.go | 8 +- cmd/faucet/faucet.go | 12 +- cmd/p2psim/main.go | 8 +- cmd/ppos_tool/main.go | 33 +- cmd/utils/flags.go | 10 +- common/byteutil/byteutil.go | 32 +- common/byteutil/byteutil_test.go | 13 +- common/consensus/evidence.go | 5 +- consensus/bft_mock.go | 15 +- consensus/cbft/cbft.go | 33 +- consensus/cbft/cbft_byzantine_test.go | 13 +- consensus/cbft/cbft_common_util.go | 21 +- consensus/cbft/cbft_test.go | 26 +- consensus/cbft/cbft_test_util.go | 17 +- consensus/cbft/consensus_process.go | 8 +- consensus/cbft/evidence/duplicateEvidence.go | 11 +- .../cbft/evidence/evidence_common_test.go | 7 +- consensus/cbft/evidence/message.go | 12 +- consensus/cbft/evidence_test.go | 16 +- consensus/cbft/network/handler.go | 8 +- consensus/cbft/network/handler_test.go | 18 +- consensus/cbft/network/helper.go | 14 +- consensus/cbft/network/helper_test.go | 24 +- consensus/cbft/network/interface.go | 7 +- consensus/cbft/network/peer.go | 11 +- consensus/cbft/network/peer_test.go | 8 +- consensus/cbft/network/router.go | 13 +- consensus/cbft/network/router_test.go | 13 +- consensus/cbft/prepare_block_test.go | 71 ++- consensus/cbft/prepare_vote_test.go | 56 +- consensus/cbft/sync_msg_test.go | 7 +- consensus/cbft/sync_process_test.go | 10 +- consensus/cbft/types/config.go | 7 +- consensus/cbft/validator/validator.go | 67 ++- consensus/cbft/validator/validator_test.go | 99 ++- consensus/cbft/view_change_test.go | 30 +- consensus/cbft/wal_bridge.go | 5 +- consensus/consensus.go | 9 +- console/console_test.go | 8 +- core/blockchain_reactor.go | 11 +- core/cbfttypes/type.go | 26 +- core/genesis_data.go | 2 +- core/vm/delegate_reward_contract.go | 6 +- core/vm/delegate_reward_contract_test.go | 12 +- core/vm/gov_contract.go | 15 +- core/vm/gov_contract_test.go | 19 +- core/vm/platon_contract_test.go | 65 +- core/vm/slashing_contract.go | 4 +- core/vm/slashing_contract_test.go | 7 +- core/vm/staking_contract.go | 19 +- core/vm/staking_contract_whitebox_test.go | 5 +- core/vm/validator_inner_contract.go | 8 +- crypto/crypto.go | 6 + eth/backend.go | 7 +- eth/handler.go | 5 +- eth/helper_test.go | 5 +- eth/sync.go | 17 +- eth/sync_test.go | 7 +- les/commons.go | 25 +- les/handler.go | 7 +- les/helper_test.go | 7 +- les/peer.go | 10 +- les/protocol.go | 13 +- les/serverpool.go | 122 ++-- miner/worker.go | 2 +- node/api.go | 7 +- node/config.go | 13 +- node/crypto_handler.go | 7 +- node/crypto_handler_test.go | 5 +- node/fake.go | 12 +- p2p/consensus_dialed.go | 13 +- p2p/dial.go | 121 ++-- p2p/dial_test.go | 457 +++++++------- p2p/discover/node.go | 432 ++------------ p2p/discover/node_test.go | 336 ----------- p2p/discover/table.go | 228 ++++--- p2p/discover/table_test.go | 562 ++++++++---------- p2p/discover/table_util_test.go | 167 ++++++ p2p/discover/udp.go | 125 ++-- p2p/discover/udp_test.go | 86 +-- p2p/{enr => enode}/idscheme.go | 127 ++-- p2p/enode/idscheme_test.go | 75 +++ p2p/enode/node.go | 384 ++++++++++++ p2p/enode/node_test.go | 63 ++ p2p/{discover/database.go => enode/nodedb.go} | 161 +++-- .../database_test.go => enode/nodedb_test.go} | 218 ++++--- p2p/enode/urlv4.go | 198 ++++++ p2p/enode/urlv4_test.go | 248 ++++++++ p2p/enr/enr.go | 150 ++--- p2p/enr/enr_test.go | 126 ++-- p2p/enr/entries.go | 26 - p2p/enr/idscheme_test.go | 36 -- p2p/message.go | 7 +- p2p/peer.go | 96 +-- p2p/peer_test.go | 4 +- p2p/protocol.go | 4 +- p2p/protocols/protocol_test.go | 45 +- p2p/rlpx.go | 56 +- p2p/rlpx_test.go | 44 +- p2p/server.go | 464 +++++---------- p2p/server_test.go | 158 ++--- p2p/simulations/adapters/exec.go | 17 +- p2p/simulations/adapters/inproc.go | 28 +- p2p/simulations/adapters/types.go | 20 +- p2p/simulations/examples/ping-pong.go | 7 +- p2p/simulations/http.go | 9 +- p2p/simulations/http_test.go | 17 +- p2p/simulations/mocker.go | 9 +- p2p/simulations/mocker_test.go | 4 +- p2p/simulations/network.go | 65 +- p2p/simulations/network_test.go | 10 +- p2p/simulations/simulation.go | 14 +- p2p/testing/peerpool.go | 13 +- p2p/testing/protocolsession.go | 35 +- p2p/testing/protocoltester.go | 12 +- params/config.go | 9 +- x/gov/gov.go | 21 +- x/gov/gov_db.go | 22 +- x/gov/gov_db_test.go | 32 +- x/gov/gov_snapdb.go | 19 +- x/gov/gov_test.go | 20 +- x/gov/gov_types.go | 13 +- x/gov/proposals.go | 24 +- x/handler/vrf_handler.go | 9 +- x/plugin/base_plugin.go | 9 +- x/plugin/gov_plugin.go | 7 +- x/plugin/gov_plugin_test.go | 17 +- x/plugin/issue1625_patch.go | 5 +- x/plugin/issue1625_patch_test.go | 12 +- x/plugin/issue1654_patch.go | 10 +- x/plugin/platon_plugin_test.go | 82 +-- x/plugin/restricting_plugin.go | 7 +- x/plugin/reward_plugin.go | 31 +- x/plugin/reward_plugin_test.go | 10 +- x/plugin/slashing_plugin.go | 42 +- x/plugin/slashing_plugin_test.go | 26 +- x/plugin/staking_plugin.go | 73 ++- x/plugin/staking_plugin_test.go | 41 +- x/reward/reward_db_key.go | 8 +- x/reward/{rward_test.go => reward_test.go} | 6 +- x/reward/reward_type.go | 16 +- x/staking/staking_db.go | 14 +- x/staking/staking_db_key.go | 12 +- x/staking/staking_types.go | 22 +- x/xutil/calculate.go | 7 +- 148 files changed, 3799 insertions(+), 3581 deletions(-) delete mode 100644 p2p/discover/node_test.go create mode 100644 p2p/discover/table_util_test.go rename p2p/{enr => enode}/idscheme.go (50%) create mode 100644 p2p/enode/idscheme_test.go create mode 100644 p2p/enode/node.go create mode 100644 p2p/enode/node_test.go rename p2p/{discover/database.go => enode/nodedb.go} (68%) rename p2p/{discover/database_test.go => enode/nodedb_test.go} (55%) create mode 100644 p2p/enode/urlv4.go create mode 100644 p2p/enode/urlv4_test.go delete mode 100644 p2p/enr/idscheme_test.go rename x/reward/{rward_test.go => reward_test.go} (91%) diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go index 80c2eefbda..8244895d2f 100644 --- a/cmd/bootnode/main.go +++ b/cmd/bootnode/main.go @@ -24,6 +24,8 @@ import ( "net" "os" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/cmd/utils" "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/log" @@ -85,7 +87,7 @@ func main() { } if *writeAddr { - fmt.Printf("%v\n", discover.PubkeyID(&nodeKey.PublicKey)) + fmt.Printf("%v\n", enode.PubkeyToIDV4(&nodeKey.PublicKey)) os.Exit(0) } diff --git a/cmd/ctool/ppos/reward.go b/cmd/ctool/ppos/reward.go index 6bba3541fd..20907b1239 100644 --- a/cmd/ctool/ppos/reward.go +++ b/cmd/ctool/ppos/reward.go @@ -19,7 +19,7 @@ package ppos import ( "gopkg.in/urfave/cli.v1" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" ) var ( @@ -45,9 +45,9 @@ var ( func getDelegateReward(c *cli.Context) error { nodeIDlist := c.StringSlice(nodeList.Name) - idlist := make([]discover.NodeID, 0) + idlist := make([]enode.IDv0, 0) for _, node := range nodeIDlist { - nodeid, err := discover.HexID(node) + nodeid, err := enode.HexIDv0(node) if err != nil { return err } diff --git a/cmd/ctool/ppos/slashing.go b/cmd/ctool/ppos/slashing.go index fe7f2198d9..b8c5577995 100644 --- a/cmd/ctool/ppos/slashing.go +++ b/cmd/ctool/ppos/slashing.go @@ -19,9 +19,9 @@ package ppos import ( "errors" - "gopkg.in/urfave/cli.v1" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "gopkg.in/urfave/cli.v1" ) var ( @@ -67,7 +67,7 @@ func checkDuplicateSign(c *cli.Context) error { if nodeIDstring == "" { return errors.New("The reported node ID is not set") } - nodeid, err := discover.HexID(nodeIDstring) + nodeid, err := enode.HexIDv0(nodeIDstring) if err != nil { return err } diff --git a/cmd/ctool/ppos/staking.go b/cmd/ctool/ppos/staking.go index 4bc6d5d681..f22325434f 100644 --- a/cmd/ctool/ppos/staking.go +++ b/cmd/ctool/ppos/staking.go @@ -19,11 +19,11 @@ package ppos import ( "errors" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/common" "gopkg.in/urfave/cli.v1" - - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) var ( @@ -156,7 +156,7 @@ func getDelegateInfo(c *cli.Context) error { if nodeIDstring == "" { return errors.New("The verifier's node ID is not set") } - nodeid, err := discover.HexID(nodeIDstring) + nodeid, err := enode.HexIDv0(nodeIDstring) if err != nil { return err } @@ -169,7 +169,7 @@ func getCandidateInfo(c *cli.Context) error { if nodeIDstring == "" { return errors.New("The verifier's node ID is not set") } - nodeid, err := discover.HexID(nodeIDstring) + nodeid, err := enode.HexIDv0(nodeIDstring) if err != nil { return err } diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index e6c521c49d..a980f8049d 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -41,6 +41,10 @@ import ( "sync" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + "golang.org/x/net/websocket" + "github.com/AlayaNetwork/Alaya-Go/accounts" "github.com/AlayaNetwork/Alaya-Go/accounts/keystore" "github.com/AlayaNetwork/Alaya-Go/common" @@ -54,11 +58,9 @@ import ( "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/p2p/discv5" "github.com/AlayaNetwork/Alaya-Go/p2p/nat" "github.com/AlayaNetwork/Alaya-Go/params" - "golang.org/x/net/websocket" ) var ( @@ -260,8 +262,10 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u return nil, err } for _, boot := range enodes { - old, _ := discover.ParseNode(boot.String()) - stack.Server().AddPeer(old) + old, err := enode.ParseV4(boot.String()) + if err != nil { + stack.Server().AddPeer(old) + } } // Attach to the client and retrieve and interesting metadatas api, err := stack.Attach() diff --git a/cmd/p2psim/main.go b/cmd/p2psim/main.go index d7131e3fc1..d3670ad599 100644 --- a/cmd/p2psim/main.go +++ b/cmd/p2psim/main.go @@ -45,13 +45,15 @@ import ( "strings" "text/tabwriter" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + "gopkg.in/urfave/cli.v1" + "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" "github.com/AlayaNetwork/Alaya-Go/rpc" - "gopkg.in/urfave/cli.v1" ) var client *simulations.Client @@ -285,7 +287,7 @@ func createNode(ctx *cli.Context) error { if err != nil { return err } - config.ID = discover.PubkeyID(&privKey.PublicKey) + config.ID = enode.PubkeyToIDV4(&privKey.PublicKey) config.PrivateKey = privKey } if services := ctx.String("services"); services != "" { diff --git a/cmd/ppos_tool/main.go b/cmd/ppos_tool/main.go index 111b7a65a1..668cfa2013 100644 --- a/cmd/ppos_tool/main.go +++ b/cmd/ppos_tool/main.go @@ -27,12 +27,13 @@ import ( "strconv" "strings" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/crypto/bls" "github.com/AlayaNetwork/Alaya-Go/cmd/utils" "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/common/hexutil" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/AlayaNetwork/Alaya-Go/x/restricting" ) @@ -46,7 +47,7 @@ var ( type Ppos_1000 struct { Typ uint16 BenefitAddress common.Address - NodeId discover.NodeID + NodeId enode.IDv0 ExternalId string NodeName string Website string @@ -62,7 +63,7 @@ type Ppos_1000 struct { // editorCandidate type Ppos_1001 struct { BenefitAddress common.Address - NodeId discover.NodeID + NodeId enode.IDv0 RewardPer uint16 ExternalId string NodeName string @@ -72,27 +73,27 @@ type Ppos_1001 struct { // increaseStaking type Ppos_1002 struct { - NodeId discover.NodeID + NodeId enode.IDv0 Typ uint16 Amount *big.Int } // withdrewStaking type Ppos_1003 struct { - NodeId discover.NodeID + NodeId enode.IDv0 } // delegate type Ppos_1004 struct { Typ uint16 - NodeId discover.NodeID + NodeId enode.IDv0 Amount *big.Int } // withdrewDelegation type Ppos_1005 struct { StakingBlockNum uint64 - NodeId discover.NodeID + NodeId enode.IDv0 Amount *big.Int } @@ -105,23 +106,23 @@ type Ppos_1103 struct { type Ppos_1104 struct { StakingBlockNum uint64 DelAddr common.Address - NodeId discover.NodeID + NodeId enode.IDv0 } // getCandidateInfo type Ppos_1105 struct { - NodeId discover.NodeID + NodeId enode.IDv0 } // submitText type Ppos_2000 struct { - Verifier discover.NodeID + Verifier enode.IDv0 PIPID string } // submitVersion type Ppos_2001 struct { - Verifier discover.NodeID + Verifier enode.IDv0 PIPID string NewVersion uint32 EndVotingRounds uint64 @@ -129,7 +130,7 @@ type Ppos_2001 struct { // submitParam type Ppos_2002 struct { - Verifier discover.NodeID + Verifier enode.IDv0 PIPID string Module string Name string @@ -138,7 +139,7 @@ type Ppos_2002 struct { // submitCancel type Ppos_2005 struct { - Verifier discover.NodeID + Verifier enode.IDv0 PIPID string EndVotingRounds uint64 TobeCanceled common.Hash @@ -146,7 +147,7 @@ type Ppos_2005 struct { // vote type Ppos_2003 struct { - Verifier discover.NodeID + Verifier enode.IDv0 ProposalID common.Hash Option uint8 ProgramVersion uint32 @@ -155,7 +156,7 @@ type Ppos_2003 struct { //declareVersion type Ppos_2004 struct { - Verifier discover.NodeID + Verifier enode.IDv0 ProgramVersion uint32 VersionSign common.VersionSign } @@ -225,7 +226,7 @@ type Ppos_5000 struct { type Ppos_5100 struct { Addr common.Address - NodeIDs []discover.NodeID + NodeIDs []enode.IDv0 } type decDataConfig struct { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index b6fa49a3da..b863793669 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -27,6 +27,8 @@ import ( "strings" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" "gopkg.in/urfave/cli.v1" @@ -52,7 +54,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/metrics/influxdb" "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/p2p/nat" "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" "github.com/AlayaNetwork/Alaya-Go/params" @@ -673,9 +674,9 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { return // already set, don't apply defaults. } - cfg.BootstrapNodes = make([]*discover.Node, 0, len(urls)) + cfg.BootstrapNodes = make([]*enode.Node, 0, len(urls)) for _, url := range urls { - node, err := discover.ParseNode(url) + node, err := enode.ParseV4(url) if err != nil { log.Crit("Bootstrap URL invalid", "enode", url, "err", err) } @@ -1205,7 +1206,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { func SetCbft(ctx *cli.Context, cfg *types.OptionsConfig, nodeCfg *node.Config) { if nodeCfg.P2P.PrivateKey != nil { cfg.NodePriKey = nodeCfg.P2P.PrivateKey - cfg.NodeID = discover.PubkeyID(&cfg.NodePriKey.PublicKey) + cfg.Node = enode.NewV4(&cfg.NodePriKey.PublicKey, nil, 0, 0) + cfg.NodeID = cfg.Node.IDv0() } if ctx.GlobalIsSet(CbftBlsPriKeyFileFlag.Name) { diff --git a/common/byteutil/byteutil.go b/common/byteutil/byteutil.go index 94fad75e30..c62c8b59f3 100644 --- a/common/byteutil/byteutil.go +++ b/common/byteutil/byteutil.go @@ -14,17 +14,16 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package byteutil import ( - "encoding/hex" "math/big" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/crypto/bls" "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/AlayaNetwork/Alaya-Go/x/restricting" ) @@ -45,8 +44,8 @@ var Bytes2X_CMD = map[string]interface{}{ "*big.Int": BytesToBigInt, "[]*big.Int": BytesToBigIntArr, - "discover.NodeID": BytesToNodeId, - "[]discover.NodeID": BytesToNodeIdArr, + "enode.IDv0": BytesToNodeId, + "[]enode.IDv0": BytesToNodeIdArr, "common.Hash": BytesToHash, "[]common.Hash": BytesToHashArr, "common.Address": BytesToAddress, @@ -186,27 +185,16 @@ func BytesToBigIntArr(curByte []byte) []*big.Int { return arr } -func BytesToNodeId(curByte []byte) discover.NodeID { - //str := BytesToString(curByte) - //nodeId, _ := discover.HexID(str) - //return nodeId - var nodeId discover.NodeID +func BytesToNodeId(curByte []byte) enode.IDv0 { + var nodeId enode.IDv0 if err := rlp.DecodeBytes(curByte, &nodeId); nil != err { panic("BytesToNodeId:" + err.Error()) } return nodeId } -func BytesToNodeIdArr(curByte []byte) []discover.NodeID { - /*str := BytesToString(curByte) - strArr := strings.Split(str, ":") - var ANodeID []discover.NodeID - for i := 0; i < len(strArr); i++ { - nodeId, _ := discover.HexID(strArr[i]) - ANodeID = append(ANodeID, nodeId) - } - return ANodeID*/ - var nodeIdArr []discover.NodeID +func BytesToNodeIdArr(curByte []byte) []enode.IDv0 { + var nodeIdArr []enode.IDv0 if err := rlp.DecodeBytes(curByte, &nodeIdArr); nil != err { panic("BytesToNodeIdArr:" + err.Error()) } @@ -328,10 +316,6 @@ func BytesToRestrictingPlanArr(curByte []byte) []restricting.RestrictingPlan { return planArr } -func PrintNodeID(nodeID discover.NodeID) string { - return hex.EncodeToString(nodeID.Bytes()[:8]) -} - func Concat(s1 []byte, s2 ...byte) []byte { r := make([]byte, len(s1)+len(s2)) copy(r, s1) diff --git a/common/byteutil/byteutil_test.go b/common/byteutil/byteutil_test.go index f262284767..4a500699d4 100644 --- a/common/byteutil/byteutil_test.go +++ b/common/byteutil/byteutil_test.go @@ -14,19 +14,18 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package byteutil import ( "math/big" "testing" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/x/restricting" "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" - "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/stretchr/testify/assert" @@ -102,19 +101,19 @@ func TestBytesToBigIntArr(t *testing.T) { func TestBytesToNodeId(t *testing.T) { ecdsaKey, _ := crypto.GenerateKey() - nodeID := discover.PubkeyID(&ecdsaKey.PublicKey) + nodeID := enode.PublicKeyToIDv0(&ecdsaKey.PublicKey) data, err := rlp.EncodeToBytes(nodeID) assert.Nil(t, err) dnodeID := BytesToNodeId(data) assert.Equal(t, nodeID, dnodeID) - assert.NotNil(t, PrintNodeID(dnodeID)) + assert.NotNil(t, dnodeID.String()) } func TestBytesToNodeIdArr(t *testing.T) { - nodeIdArr := make([]discover.NodeID, 0, 3) + nodeIdArr := make([]enode.IDv0, 0, 3) for i := 0; i < 3; i++ { ecdsaKey, _ := crypto.GenerateKey() - nodeID := discover.PubkeyID(&ecdsaKey.PublicKey) + nodeID := enode.PublicKeyToIDv0(&ecdsaKey.PublicKey) nodeIdArr = append(nodeIdArr, nodeID) } data, err := rlp.EncodeToBytes(nodeIdArr) diff --git a/common/consensus/evidence.go b/common/consensus/evidence.go index 9b8d6b493f..4bb2d93ebe 100644 --- a/common/consensus/evidence.go +++ b/common/consensus/evidence.go @@ -14,12 +14,11 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package consensus import ( "github.com/AlayaNetwork/Alaya-Go/crypto/bls" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" ) type EvidenceType uint8 @@ -33,7 +32,7 @@ type Evidence interface { ViewNumber() uint64 Hash() []byte //Address() common.NodeAddress - NodeID() discover.NodeID + NodeID() enode.IDv0 BlsPubKey() *bls.PublicKey Validate() error Type() EvidenceType diff --git a/consensus/bft_mock.go b/consensus/bft_mock.go index addb68a01a..9b04f9f971 100644 --- a/consensus/bft_mock.go +++ b/consensus/bft_mock.go @@ -22,6 +22,8 @@ import ( "math/big" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/common/consensus" @@ -32,7 +34,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/core/state" "github.com/AlayaNetwork/Alaya-Go/core/types" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rpc" ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" @@ -144,12 +145,12 @@ func (bm *BftMock) UnmarshalEvidence(data []byte) (consensus.Evidences, error) { return nil, nil } -func (bm *BftMock) NodeID() discover.NodeID { +func (bm *BftMock) Node() *enode.Node { privateKey, err := crypto.GenerateKey() if nil != err { panic(fmt.Sprintf("Failed to generate random NodeId private key: %v", err)) } - return discover.PubkeyID(&privateKey.PublicKey) + return enode.NewV4(&privateKey.PublicKey, nil, 0, 0) } // Author retrieves the Ethereum address of the account that minted the given @@ -278,7 +279,7 @@ func (bm *BftMock) Close() error { } // ConsensusNodes returns the current consensus node address list. -func (bm *BftMock) ConsensusNodes() ([]discover.NodeID, error) { +func (bm *BftMock) ConsensusNodes() ([]enode.ID, error) { return nil, nil } @@ -289,7 +290,7 @@ func (bm *BftMock) ShouldSeal(curTime time.Time) (bool, error) { // OnBlockSignature received a new block signature // Need to verify if the signature is signed by nodeID -func (bm *BftMock) OnBlockSignature(chain ChainReader, nodeID discover.NodeID, sig *cbfttypes.BlockSignature) error { +func (bm *BftMock) OnBlockSignature(chain ChainReader, nodeID enode.IDv0, sig *cbfttypes.BlockSignature) error { return nil } @@ -299,7 +300,7 @@ func (bm *BftMock) OnNewBlock(chain ChainReader, block *types.Block) error { } // OnPong processes the BFT signatures -func (bm *BftMock) OnPong(nodeID discover.NodeID, netLatency int64) error { +func (bm *BftMock) OnPong(nodeID enode.IDv0, netLatency int64) error { return nil } @@ -310,7 +311,7 @@ func (bm *BftMock) OnBlockSynced() { } // CheckConsensusNode is a fake interface, no need to implement. -func (bm *BftMock) CheckConsensusNode(nodeID discover.NodeID) (bool, error) { +func (bm *BftMock) CheckConsensusNode(nodeID enode.IDv0) (bool, error) { return true, nil } diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index a12b53d8b4..22706305d7 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbft import ( @@ -26,6 +25,8 @@ import ( "strings" "sync/atomic" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + mapset "github.com/deckarep/golang-set" "github.com/AlayaNetwork/Alaya-Go/common/hexutil" @@ -59,7 +60,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/rpc" ) @@ -184,7 +184,7 @@ type Cbft struct { //test insertBlockQCHook func(block *types.Block, qc *ctypes.QuorumCert) executeFinishHook func(index uint32) - consensusNodesMock func() ([]discover.NodeID, error) + consensusNodesMock func() ([]enode.ID, error) } // New returns a new CBFT. @@ -261,11 +261,16 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCacheWriter consensus. // Start the handler to process the message. go cbft.network.Start() + if cbft.config.Option.Node == nil { + cbft.config.Option.Node = enode.NewV4(&cbft.nodeServiceContext.NodePriKey().PublicKey, nil, 0, 0) + cbft.config.Option.NodeID = cbft.config.Option.Node.IDv0() + cbft.config.Option.NodePriKey = cbft.nodeServiceContext.NodePriKey() + } if isGenesis() { - cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), cstate.DefaultEpoch, cbft.config.Option.NodeID) + cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), cstate.DefaultEpoch, cbft.config.Option.Node.ID()) cbft.changeView(cstate.DefaultEpoch, cstate.DefaultViewNumber, block, qc, nil) } else { - cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), qc.Epoch, cbft.config.Option.NodeID) + cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), qc.Epoch, cbft.config.Option.Node.ID()) cbft.changeView(qc.Epoch, qc.ViewNumber, block, qc, nil) } @@ -756,9 +761,9 @@ func (cbft *Cbft) OnSeal(block *types.Block, results chan<- *types.Block, stop < return } - me, err := cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.NodeID()) + me, err := cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.Node().ID()) if err != nil { - cbft.log.Warn("Can not got the validator, seal fail", "epoch", cbft.state.Epoch(), "nodeID", cbft.NodeID()) + cbft.log.Warn("Can not got the validator, seal fail", "epoch", cbft.state.Epoch(), "nodeID", cbft.Node().ID()) return } numValidators := cbft.validatorPool.Len(cbft.state.Epoch()) @@ -1092,7 +1097,7 @@ func (cbft *Cbft) Close() error { } // ConsensusNodes returns to the list of consensus nodes. -func (cbft *Cbft) ConsensusNodes() ([]discover.NodeID, error) { +func (cbft *Cbft) ConsensusNodes() ([]enode.ID, error) { if cbft.consensusNodesMock != nil { return cbft.consensusNodesMock() } @@ -1144,14 +1149,14 @@ func (cbft *Cbft) OnShouldSeal(result chan error) { return } currentExecutedBlockNumber := cbft.state.HighestExecutedBlock().NumberU64() - if !cbft.validatorPool.IsValidator(cbft.state.Epoch(), cbft.config.Option.NodeID) { + if !cbft.validatorPool.IsValidator(cbft.state.Epoch(), cbft.config.Option.Node.ID()) { result <- ErrorNotValidator return } numValidators := cbft.validatorPool.Len(cbft.state.Epoch()) currentProposer := cbft.state.ViewNumber() % uint64(numValidators) - validator, err := cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.config.Option.NodeID) + validator, err := cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.config.Option.Node.ID()) if err != nil { cbft.log.Error("Should seal fail", "err", err) result <- err @@ -1218,7 +1223,7 @@ func (cbft *Cbft) CalcNextBlockTime(blockTime time.Time) time.Time { // IsConsensusNode returns whether the current node is a consensus node. func (cbft *Cbft) IsConsensusNode() bool { - return cbft.validatorPool.IsValidator(cbft.state.Epoch(), cbft.config.Option.NodeID) + return cbft.validatorPool.IsValidator(cbft.state.Epoch(), cbft.config.Option.Node.ID()) } // GetBlock returns the block corresponding to the specified number and hash. @@ -1368,7 +1373,7 @@ func (cbft *Cbft) isStart() bool { } func (cbft *Cbft) isCurrentValidator() (*cbfttypes.ValidateNode, error) { - return cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.config.Option.NodeID) + return cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.config.Option.Node.ID()) } func (cbft *Cbft) currentProposer() *cbfttypes.ValidateNode { @@ -1812,8 +1817,8 @@ func (cbft *Cbft) verifyViewChangeQC(viewChangeQC *ctypes.ViewChangeQC) error { } // NodeID returns the ID value of the current node -func (cbft *Cbft) NodeID() discover.NodeID { - return cbft.config.Option.NodeID +func (cbft *Cbft) Node() *enode.Node { + return cbft.config.Option.Node } func (cbft *Cbft) avgRTT() time.Duration { diff --git a/consensus/cbft/cbft_byzantine_test.go b/consensus/cbft/cbft_byzantine_test.go index 6369f64614..c48d5a2705 100644 --- a/consensus/cbft/cbft_byzantine_test.go +++ b/consensus/cbft/cbft_byzantine_test.go @@ -14,12 +14,10 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbft import ( "fmt" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "io/ioutil" "math/big" "os" @@ -27,6 +25,8 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/consensus" "github.com/AlayaNetwork/Alaya-Go/common/hexutil" @@ -47,8 +47,9 @@ import ( "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" - ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/stretchr/testify/assert" + + ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" ) const ( @@ -273,7 +274,7 @@ func TestPB03(t *testing.T) { _, ok = evds[0].(evidence.DuplicatePrepareBlockEvidence) if ok { assert.Equal(t, lockBlock.NumberU64()+1, evds[0].BlockNumber()) - assert.Equal(t, discover.PubkeyID(&nodes[0].engine.config.Option.NodePriKey.PublicKey), evds[0].NodeID()) + assert.Equal(t, enode.PublicKeyToIDv0(&nodes[0].engine.config.Option.NodePriKey.PublicKey), evds[0].NodeID()) assert.Nil(t, evds[0].Validate()) } } @@ -500,7 +501,7 @@ func TestVT02(t *testing.T) { _, ok = evds[0].(evidence.DuplicatePrepareVoteEvidence) if ok { assert.Equal(t, qcBlock.NumberU64()+1, evds[0].BlockNumber()) - assert.Equal(t, discover.PubkeyID(&nodes[0].engine.config.Option.NodePriKey.PublicKey), evds[0].NodeID()) + assert.Equal(t, enode.PublicKeyToIDv0(&nodes[0].engine.config.Option.NodePriKey.PublicKey), evds[0].NodeID()) assert.Nil(t, evds[0].Validate()) } } @@ -613,7 +614,7 @@ func TestVC03(t *testing.T) { _, ok = evds[0].(evidence.DuplicateViewChangeEvidence) if ok { assert.Equal(t, qcBlock.NumberU64()+1, evds[0].BlockNumber()) - assert.Equal(t, discover.PubkeyID(&nodes[0].engine.config.Option.NodePriKey.PublicKey), evds[0].NodeID()) + assert.Equal(t, enode.PublicKeyToIDv0(&nodes[0].engine.config.Option.NodePriKey.PublicKey), evds[0].NodeID()) assert.Nil(t, evds[0].Validate()) } } diff --git a/consensus/cbft/cbft_common_util.go b/consensus/cbft/cbft_common_util.go index d6fe02b92f..ca51ce58b6 100644 --- a/consensus/cbft/cbft_common_util.go +++ b/consensus/cbft/cbft_common_util.go @@ -14,14 +14,18 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbft import ( "crypto/ecdsa" "math/big" + "net" "time" + "github.com/AlayaNetwork/Alaya-Go/node" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/ethdb" "github.com/AlayaNetwork/Alaya-Go/core/rawdb" @@ -43,8 +47,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" "github.com/AlayaNetwork/Alaya-Go/event" - "github.com/AlayaNetwork/Alaya-Go/node" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" ) @@ -118,8 +120,7 @@ func GenerateCbftNode(num int) ([]*ecdsa.PrivateKey, []*bls.SecretKey, []params. pk, sk := GenerateKeys(num) nodes := make([]params.CbftNode, num) for i := 0; i < num; i++ { - - nodes[i].Node = *discover.NewNode(discover.PubkeyID(&pk[i].PublicKey), nil, 0, 0) + nodes[i].Node = enode.NewV4(&pk[i].PublicKey, net.ParseIP("0.0.0.0"), 0, 0) nodes[i].BlsPubKey = *sk[i].GetPublicKey() } @@ -135,9 +136,11 @@ func CreateCBFT(pk *ecdsa.PrivateKey, sk *bls.SecretKey, period uint64, amount u InitialNodes: []params.CbftNode{}, } + pnode := enode.NewV4(&pk.PublicKey, nil, 0, 0) optConfig := &ctypes.OptionsConfig{ NodePriKey: pk, - NodeID: discover.PubkeyID(&pk.PublicKey), + NodeID: pnode.IDv0(), + Node: pnode, BlsPriKey: sk, MaxQueuesLimit: 1000, BlacklistDeadline: 1, @@ -247,12 +250,12 @@ func MockValidator(pk *ecdsa.PrivateKey, sk *bls.SecretKey, nodes []params.CbftN } // NewEngineManager returns a list of EngineManager and NodeID. -func NewEngineManager(cbfts []*TestCBFT) ([]*network.EngineManager, []discover.NodeID) { - nodeids := make([]discover.NodeID, 0) +func NewEngineManager(cbfts []*TestCBFT) ([]*network.EngineManager, []enode.ID) { + nodeids := make([]enode.ID, 0) engines := make([]*network.EngineManager, 0) for _, c := range cbfts { engines = append(engines, c.engine.network) - nodeids = append(nodeids, c.engine.config.Option.NodeID) + nodeids = append(nodeids, c.engine.config.Option.Node.ID()) } return engines, nodeids } diff --git a/consensus/cbft/cbft_test.go b/consensus/cbft/cbft_test.go index 2f11095ac3..b7a337c283 100644 --- a/consensus/cbft/cbft_test.go +++ b/consensus/cbft/cbft_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbft import ( @@ -25,6 +24,8 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/core/rawdb" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" @@ -42,7 +43,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/core/types" cvm "github.com/AlayaNetwork/Alaya-Go/core/vm" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -73,14 +73,14 @@ func TestBls(t *testing.T) { owner := sk[0] nodes := make([]params.CbftNode, num) for i := 0; i < num; i++ { - nodes[i].Node = *discover.NewNode(discover.PubkeyID(&pk[i].PublicKey), nil, 0, 0) + nodes[i].Node = enode.NewV4(&pk[i].PublicKey, nil, 0, 0) nodes[i].BlsPubKey = *sk[i].GetPublicKey() } agency := validator.NewStaticAgency(nodes) cbft := &Cbft{ - validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID), + validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID()), config: ctypes.Config{ Option: &ctypes.OptionsConfig{ BlsPriKey: owner, @@ -99,13 +99,13 @@ func TestPrepareBlockBls(t *testing.T) { pk, sk := GenerateKeys(1) owner := sk[0] node := params.CbftNode{ - Node: *discover.NewNode(discover.PubkeyID(&pk[0].PublicKey), nil, 0, 0), + Node: enode.NewV4(&pk[0].PublicKey, nil, 0, 0), BlsPubKey: *sk[0].GetPublicKey(), } agency := validator.NewStaticAgency([]params.CbftNode{node}) cbft := &Cbft{ - validatorPool: validator.NewValidatorPool(agency, 0, 0, node.Node.ID), + validatorPool: validator.NewValidatorPool(agency, 0, 0, node.Node.ID()), config: ctypes.Config{ Option: &ctypes.OptionsConfig{ BlsPriKey: owner, @@ -163,7 +163,7 @@ func TestAgg(t *testing.T) { pk, sk := GenerateKeys(num) nodes := make([]params.CbftNode, num) for i := 0; i < num; i++ { - nodes[i].Node = *discover.NewNode(discover.PubkeyID(&pk[i].PublicKey), nil, 0, 0) + nodes[i].Node = enode.NewV4(&pk[i].PublicKey, nil, 0, 0) nodes[i].BlsPubKey = *sk[i].GetPublicKey() } @@ -174,7 +174,7 @@ func TestAgg(t *testing.T) { for i := 0; i < num; i++ { cnode[i] = &Cbft{ - validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID), + validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID()), config: ctypes.Config{ Option: &ctypes.OptionsConfig{ BlsPriKey: sk[i], @@ -513,9 +513,9 @@ func testValidatorSwitch(t *testing.T) { func newUpdateValidatorTx(t *testing.T, parent *types.Block, header *types.Header, nodes []params.CbftNode, switchNode params.CbftNode, mineNode *TestCBFT) (*types.Transaction, *types.Receipt, *cstate.StateDB) { type Vd struct { - Index uint `json:"index"` - NodeID discover.NodeID `json:"nodeID"` - BlsPubKey bls.PublicKey `json:"blsPubKey"` + Index uint `json:"index"` + NodeID enode.IDv0 `json:"nodeID"` + BlsPubKey bls.PublicKey `json:"blsPubKey"` } type VdList struct { NodeList []*Vd `json:"validateNode"` @@ -528,13 +528,13 @@ func newUpdateValidatorTx(t *testing.T, parent *types.Block, header *types.Heade for i := 0; i < 3; i++ { vdl.NodeList = append(vdl.NodeList, &Vd{ Index: uint(i), - NodeID: nodes[i].Node.ID, + NodeID: nodes[i].Node.IDv0(), BlsPubKey: nodes[i].BlsPubKey, }) } vdl.NodeList = append(vdl.NodeList, &Vd{ Index: 3, - NodeID: switchNode.Node.ID, + NodeID: switchNode.Node.IDv0(), BlsPubKey: switchNode.BlsPubKey, }) diff --git a/consensus/cbft/cbft_test_util.go b/consensus/cbft/cbft_test_util.go index 74c145d460..6997e60361 100644 --- a/consensus/cbft/cbft_test_util.go +++ b/consensus/cbft/cbft_test_util.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbft import ( @@ -107,7 +106,7 @@ func newTestView(start bool, nodeNumber int) *testView { } func (tv *testView) firstProposer() *Cbft { for _, c := range tv.allCbft { - index, err := c.validatorPool.GetIndexByNodeID(c.state.Epoch(), c.NodeID()) + index, err := c.validatorPool.GetIndexByNodeID(c.state.Epoch(), c.Node().ID()) if err != nil { panic("find proposer node failed") } @@ -125,7 +124,7 @@ func (tv *testView) firstProposerBlsKey() *bls.SecretKey { } func (tv *testView) secondProposer() *Cbft { for _, c := range tv.allCbft { - index, err := c.validatorPool.GetIndexByNodeID(c.state.Epoch(), c.NodeID()) + index, err := c.validatorPool.GetIndexByNodeID(c.state.Epoch(), c.Node().ID()) if err != nil { panic("find proposer node failed") } @@ -143,7 +142,7 @@ func (tv *testView) secondProposerBlsKey() *bls.SecretKey { } func (tv *testView) thirdProposer() *Cbft { for _, c := range tv.allCbft { - index, err := c.validatorPool.GetIndexByNodeID(c.state.Epoch(), c.NodeID()) + index, err := c.validatorPool.GetIndexByNodeID(c.state.Epoch(), c.Node().ID()) if err != nil { panic("find proposer node failed") } @@ -166,7 +165,7 @@ func (tv *testView) currentProposerInfo(cbft *Cbft) (uint32, uint64) { func (tv *testView) currentProposer(cbft *Cbft) *Cbft { currentProposer, _ := tv.currentProposerInfo(cbft) for _, c := range tv.allCbft { - index, err := c.validatorPool.GetIndexByNodeID(c.state.Epoch(), c.NodeID()) + index, err := c.validatorPool.GetIndexByNodeID(c.state.Epoch(), c.Node().ID()) if err != nil { panic("find proposer node failed") } @@ -355,7 +354,7 @@ func mockViewChange(priv *bls.SecretKey, epoch uint64, viewNumber uint64, hash c func mockBlockQC(nodes []*TestCBFT, block *types.Block, blockIndex uint32, qc *ctypes.QuorumCert) *protocols.BlockQuorumCert { votes := make(map[uint32]*protocols.PrepareVote) for _, node := range nodes { - index, err := node.engine.validatorPool.GetIndexByNodeID(node.engine.state.Epoch(), node.engine.NodeID()) + index, err := node.engine.validatorPool.GetIndexByNodeID(node.engine.state.Epoch(), node.engine.Node().ID()) if err != nil { panic("find proposer node failed") } @@ -379,7 +378,7 @@ func mockBlockQCWithNotConsensus(nodes []*TestCBFT, block *types.Block, blockInd func mockBlockQCWithViewNumber(nodes []*TestCBFT, block *types.Block, blockIndex uint32, qc *ctypes.QuorumCert, viewNumber uint64) *protocols.BlockQuorumCert { votes := make(map[uint32]*protocols.PrepareVote) for _, node := range nodes { - index, err := node.engine.validatorPool.GetIndexByNodeID(node.engine.state.Epoch(), node.engine.NodeID()) + index, err := node.engine.validatorPool.GetIndexByNodeID(node.engine.state.Epoch(), node.engine.Node().ID()) if err != nil { panic("find proposer node failed") } @@ -393,7 +392,7 @@ func mockBlockQCWithViewNumber(nodes []*TestCBFT, block *types.Block, blockIndex func mockBlockQCWithEpoch(nodes []*TestCBFT, block *types.Block, blockIndex uint32, qc *ctypes.QuorumCert, epoch uint64) *protocols.BlockQuorumCert { votes := make(map[uint32]*protocols.PrepareVote) for _, node := range nodes { - index, err := node.engine.validatorPool.GetIndexByNodeID(node.engine.state.Epoch(), node.engine.NodeID()) + index, err := node.engine.validatorPool.GetIndexByNodeID(node.engine.state.Epoch(), node.engine.Node().ID()) if err != nil { panic("find proposer node failed") } @@ -456,7 +455,7 @@ func mockPrepareQC(total uint32, votes map[uint32]*protocols.PrepareVote) *ctype func mockViewQC(block *types.Block, nodes []*TestCBFT, qc *ctypes.QuorumCert) *ctypes.ViewChangeQC { votes := make(map[uint32]*protocols.ViewChange) for _, node := range nodes { - index, err := node.engine.validatorPool.GetIndexByNodeID(node.engine.state.Epoch(), node.engine.NodeID()) + index, err := node.engine.validatorPool.GetIndexByNodeID(node.engine.state.Epoch(), node.engine.Node().ID()) if err != nil { panic(err.Error()) } diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index f7c215e33c..19f5ce0916 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -307,7 +307,7 @@ func (cbft *Cbft) insertPrepareQC(qc *ctypes.QuorumCert) { return false } hasExecuted := func() bool { - if cbft.validatorPool.IsValidator(qc.Epoch, cbft.config.Option.NodeID) { + if cbft.validatorPool.IsValidator(qc.Epoch, cbft.config.Option.Node.ID()) { return cbft.state.HadSendPrepareVote().Had(qc.BlockIndex) && linked(qc.BlockNumber) } else if cbft.validatorPool.IsCandidateNode(cbft.config.Option.NodeID) { blockIndex, finish := cbft.state.Executing() @@ -337,7 +337,7 @@ func (cbft *Cbft) onAsyncExecuteStatus(s *executor.BlockExecuteStatus) { if cbft.executeFinishHook != nil { cbft.executeFinishHook(index) } - _, err := cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.config.Option.NodeID) + _, err := cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.config.Option.Node.ID()) if err != nil { cbft.log.Debug("Current node is not validator,no need to sign block", "err", err, "hash", s.Hash, "number", s.Number) return @@ -364,7 +364,7 @@ func (cbft *Cbft) signBlock(hash common.Hash, number uint64, index uint32) error // todo sign vote // parentQC added when sending // Determine if the current consensus node is - node, err := cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.config.Option.NodeID) + node, err := cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.config.Option.Node.ID()) if err != nil { return err } @@ -420,7 +420,7 @@ func (cbft *Cbft) trySendPrepareVote() { p.ParentQC = qc hadSend.Push(p) //Determine if the current consensus node is - node, _ := cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.config.Option.NodeID) + node, _ := cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.config.Option.Node.ID()) cbft.log.Info("Add local prepareVote", "vote", p.String()) cbft.state.AddPrepareVote(uint32(node.Index), p) pending.Pop() diff --git a/consensus/cbft/evidence/duplicateEvidence.go b/consensus/cbft/evidence/duplicateEvidence.go index c5efa21afd..5cef3c98e9 100644 --- a/consensus/cbft/evidence/duplicateEvidence.go +++ b/consensus/cbft/evidence/duplicateEvidence.go @@ -14,17 +14,16 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package evidence import ( "bytes" "fmt" - "github.com/AlayaNetwork/Alaya-Go/crypto/bls" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/common/consensus" + "github.com/AlayaNetwork/Alaya-Go/crypto/bls" "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/rlp" @@ -114,7 +113,7 @@ func (d DuplicatePrepareBlockEvidence) Validate() error { return nil } -func (d DuplicatePrepareBlockEvidence) NodeID() discover.NodeID { +func (d DuplicatePrepareBlockEvidence) NodeID() enode.IDv0 { return d.PrepareA.ValidateNode.NodeID } @@ -209,7 +208,7 @@ func (d DuplicatePrepareVoteEvidence) Validate() error { return nil } -func (d DuplicatePrepareVoteEvidence) NodeID() discover.NodeID { +func (d DuplicatePrepareVoteEvidence) NodeID() enode.IDv0 { return d.VoteA.ValidateNode.NodeID } @@ -301,7 +300,7 @@ func (d DuplicateViewChangeEvidence) Validate() error { return nil } -func (d DuplicateViewChangeEvidence) NodeID() discover.NodeID { +func (d DuplicateViewChangeEvidence) NodeID() enode.IDv0 { return d.ViewA.ValidateNode.NodeID } diff --git a/consensus/cbft/evidence/evidence_common_test.go b/consensus/cbft/evidence/evidence_common_test.go index 449252dde5..ef0e3b08a7 100644 --- a/consensus/cbft/evidence/evidence_common_test.go +++ b/consensus/cbft/evidence/evidence_common_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package evidence import ( @@ -23,12 +22,12 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/crypto/bls" "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" - "github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" "github.com/AlayaNetwork/Alaya-Go/core/types" @@ -73,7 +72,7 @@ func createValidateNode(num int) ([]*cbfttypes.ValidateNode, []*bls.SecretKey) { Index: uint32(i), Address: crypto.PubkeyToNodeAddress(pk[i].PublicKey), PubKey: &pk[i].PublicKey, - NodeID: discover.PubkeyID(&pk[i].PublicKey), + NodeID: enode.PubkeyToIDV4(&pk[i].PublicKey), } nodes[i].BlsPubKey = sk[i].GetPublicKey() diff --git a/consensus/cbft/evidence/message.go b/consensus/cbft/evidence/message.go index 0acd48be42..6b848530a5 100644 --- a/consensus/cbft/evidence/message.go +++ b/consensus/cbft/evidence/message.go @@ -14,12 +14,13 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package evidence import ( "errors" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/rlp" @@ -28,7 +29,6 @@ import ( ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) // Proposed block carrier. @@ -183,15 +183,15 @@ func (ev *EvidenceView) Verify() error { // EvidenceNode mainly used to save node BlsPubKey type EvidenceNode struct { - Index uint32 `json:"index"` - NodeID discover.NodeID `json:"nodeId"` - BlsPubKey *bls.PublicKey `json:"blsPubKey"` + Index uint32 `json:"index"` + NodeID enode.IDv0 `json:"nodeId"` + BlsPubKey *bls.PublicKey `json:"blsPubKey"` } func NewEvidenceNode(node *cbfttypes.ValidateNode) *EvidenceNode { return &EvidenceNode{ Index: node.Index, - NodeID: node.NodeID, + NodeID: enode.PublicKeyToIDv0(node.PubKey), BlsPubKey: node.BlsPubKey, } } diff --git a/consensus/cbft/evidence_test.go b/consensus/cbft/evidence_test.go index b12bc52a2f..ab14a28cdb 100644 --- a/consensus/cbft/evidence_test.go +++ b/consensus/cbft/evidence_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbft import ( @@ -24,13 +23,14 @@ import ( "testing" "time" + "github.com/stretchr/testify/suite" + "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/common/hexutil" "github.com/AlayaNetwork/Alaya-Go/consensus" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/evidence" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/stretchr/testify/suite" ) func TestEvidenceSuite(t *testing.T) { @@ -81,10 +81,10 @@ func (suit *EvidenceTestSuite) TestViewChangeDuplicate() { suit.blockOne.Hash(), suit.blockOne.NumberU64(), suit.view.secondProposerIndex(), suit.blockOneQC.BlockQC) viewChange2 := mockViewChange(suit.view.secondProposerBlsKey(), suit.epoch, suit.oldViewNumber, suit.view.genesisBlock.Hash(), suit.view.genesisBlock.NumberU64(), suit.view.secondProposerIndex(), nil) - if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().NodeID().String(), viewChange1); err != nil { + if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().Node().ID().String(), viewChange1); err != nil { suit.T().Fatal(err.Error()) } - if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().NodeID().String(), viewChange2); err == nil { + if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().Node().ID().String(), viewChange2); err == nil { suit.T().Fatal("fail") } ev := suit.view.firstProposer().Evidences() @@ -168,13 +168,13 @@ func (suit *EvidenceTestSuite) TestPrepareBlockDuplicate() { prepareBlock1 := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.epoch, suit.oldViewNumber+1, 0, suit.view.secondProposerIndex(), block1, qc, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock1); err != nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock1); err != nil { suit.T().Fatal(err.Error()) } // time.Sleep(time.Millisecond * 10) prepareBlock2 := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.epoch, suit.oldViewNumber+1, 0, suit.view.secondProposerIndex(), block2, qc, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock2); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock2); err == nil { suit.T().Error("FAIL") } else { fmt.Println(err.Error()) @@ -284,10 +284,10 @@ func (suit *EvidenceTestSuite) TestPrepareVoteDuplicate() { prepareVote2 := mockPrepareVote(suit.view.firstProposerBlsKey(), suit.epoch, suit.oldViewNumber, 0, suit.view.firstProposerIndex(), block1.Hash(), block1.NumberU64(), nil) - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote1); err != nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote1); err != nil { suit.T().Fatal(err.Error()) } - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote2); err == nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote2); err == nil { suit.T().Fatal("FAIL") } ev := suit.view.secondProposer().Evidences() diff --git a/consensus/cbft/network/handler.go b/consensus/cbft/network/handler.go index b5bdaae1b9..ee63b2cc76 100644 --- a/consensus/cbft/network/handler.go +++ b/consensus/cbft/network/handler.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package network import ( @@ -25,6 +24,8 @@ import ( "strconv" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/hashicorp/golang-lru" "github.com/AlayaNetwork/Alaya-Go/common" @@ -33,7 +34,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) const ( @@ -299,7 +299,7 @@ func (h *EngineManager) Protocols() []p2p.Protocol { NodeInfo: func() interface{} { return h.NodeInfo() }, - PeerInfo: func(id discover.NodeID) interface{} { + PeerInfo: func(id enode.ID) interface{} { if p, err := h.peers.get(fmt.Sprintf("%x", id[:8])); err == nil { return p.Info() } @@ -335,7 +335,7 @@ func (h *EngineManager) Unregister(id string) error { } // ConsensusNodes returns a list of all consensus nodes. -func (h *EngineManager) ConsensusNodes() ([]discover.NodeID, error) { +func (h *EngineManager) ConsensusNodes() ([]enode.ID, error) { return h.engine.ConsensusNodes() } diff --git a/consensus/cbft/network/handler_test.go b/consensus/cbft/network/handler_test.go index 18ea80d183..1e88796c26 100644 --- a/consensus/cbft/network/handler_test.go +++ b/consensus/cbft/network/handler_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package network import ( @@ -28,6 +27,8 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" "github.com/AlayaNetwork/Alaya-Go/common" @@ -40,25 +41,24 @@ import ( "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) // fakeCbft is a fake cbft for testing.It implements all // methods of the Cbft interface. type fakeCbft struct { localPeer *peer // Represents a local peer - consensusNodes []discover.NodeID // All consensus nodes + consensusNodes []enode.ID // All consensus nodes writer p2p.MsgReadWriter // Pipeline for receiving data. peers []*peer // Pre-initialized node for testing. } // Returns the ID of the local node. -func (s *fakeCbft) NodeID() discover.NodeID { - return s.localPeer.Peer.ID() +func (s *fakeCbft) Node() *enode.Node { + return s.localPeer.Peer.Node() } // Return all consensus nodes. -func (s *fakeCbft) ConsensusNodes() ([]discover.NodeID, error) { +func (s *fakeCbft) ConsensusNodes() ([]enode.ID, error) { return s.consensusNodes, nil } @@ -144,10 +144,10 @@ func (s *fakeCbft) BlockExists(blockNumber uint64, blockHash common.Hash) error // Create a new EngineManager. func newHandle(t *testing.T) (*EngineManager, *fakeCbft) { // init local peer and engineManager. - var consensusNodes []discover.NodeID + var consensusNodes []enode.ID var peers []*peer writer, reader := p2p.MsgPipe() - var localID discover.NodeID + var localID enode.ID rand.Read(localID[:]) localPeer := newPeer(1, p2p.NewPeer(localID, "local", nil), reader) @@ -223,7 +223,7 @@ func Test_EngineManager_Handle(t *testing.T) { // protocols := h.Protocols() protocols[0].NodeInfo() - pi := protocols[0].PeerInfo(fake.NodeID()) + pi := protocols[0].PeerInfo(fake.Node().ID()) assert.Nil(t, pi) err := protocols[0].Run(fakePeer.Peer, fakePeer.rw) //err := h.handler(fakePeer.Peer, fakePeer.rw) diff --git a/consensus/cbft/network/helper.go b/consensus/cbft/network/helper.go index 483068241d..b15d956f0d 100644 --- a/consensus/cbft/network/helper.go +++ b/consensus/cbft/network/helper.go @@ -14,25 +14,25 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package network import ( "crypto/rand" "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) // ============================ simulation network ============================ // RandomID returns a list of NodeID by random. -func RandomID() []discover.NodeID { - ids := make([]discover.NodeID, 0) +func RandomID() []enode.ID { + ids := make([]enode.ID, 0) for i := 0; i < 4; i++ { - var id discover.NodeID + var id enode.ID rand.Read(id[:]) ids = append(ids, id) } @@ -43,7 +43,7 @@ func RandomID() []discover.NodeID { // simulate the test environment. // // The number of simulated network nodes is fixed at four. -func EnhanceEngineManager(ids []discover.NodeID, handlers []*EngineManager) { +func EnhanceEngineManager(ids []enode.ID, handlers []*EngineManager) { // node 1 => 1 <--> 2 association. rw1Node1_2, rw2Node1_2 := p2p.MsgPipe() @@ -100,7 +100,7 @@ func SetSendQueueHook(engine *EngineManager, hook func(msg *types.MsgPackage)) { } // FillEngineManager populates the peer for the specified Handle. -func FillEngineManager(ids []discover.NodeID, handler *EngineManager) { +func FillEngineManager(ids []enode.ID, handler *EngineManager) { write, read := p2p.MsgPipe() for _, v := range ids { peer := newPeer(CbftProtocolVersion, p2p.NewPeer(v, v.TerminalString(), nil), write) diff --git a/consensus/cbft/network/helper_test.go b/consensus/cbft/network/helper_test.go index fd86a03619..828cff277a 100644 --- a/consensus/cbft/network/helper_test.go +++ b/consensus/cbft/network/helper_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package network import ( @@ -25,6 +24,8 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" types2 "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/log" @@ -32,7 +33,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/p2p" ) @@ -239,7 +239,7 @@ func newFakePeer(name string, version int, pm *EngineManager, shake bool) (*fake app, net := p2p.MsgPipe() // Generate a random id and create the peer. - var id discover.NodeID + var id enode.ID rand.Read(id[:]) // Create a peer that belonging to cbft. @@ -256,11 +256,11 @@ func newFakePeer(name string, version int, pm *EngineManager, shake bool) (*fake } // Create a new peer for testing, return peer and ID. -func newTestPeer(version int, name string) (*peer, discover.NodeID) { +func newTestPeer(version int, name string) (*peer, enode.ID) { _, net := p2p.MsgPipe() // Generate a random id and create the peer. - var id discover.NodeID + var id enode.ID rand.Read(id[:]) // Create a peer that belonging to cbft. @@ -269,9 +269,9 @@ func newTestPeer(version int, name string) (*peer, discover.NodeID) { return peer, id } -func newLinkedPeer(rw p2p.MsgReadWriter, version int, name string) (*peer, discover.NodeID) { +func newLinkedPeer(rw p2p.MsgReadWriter, version int, name string) (*peer, enode.ID) { // Generate a random id and create the peer. - var id discover.NodeID + var id enode.ID rand.Read(id[:]) // Create a peer that belonging to cbft. @@ -323,15 +323,15 @@ func Test_InitializePeers(t *testing.T) { } type mockCbft struct { - consensusNodes []discover.NodeID - peerID discover.NodeID + consensusNodes []enode.ID + peerID enode.ID } -func (s *mockCbft) NodeID() discover.NodeID { - return s.peerID +func (s *mockCbft) Node() *enode.Node { + return nil } -func (s *mockCbft) ConsensusNodes() ([]discover.NodeID, error) { +func (s *mockCbft) ConsensusNodes() ([]enode.ID, error) { return s.consensusNodes, nil } diff --git a/consensus/cbft/network/interface.go b/consensus/cbft/network/interface.go index 0ab3c3d777..0d13cd675a 100644 --- a/consensus/cbft/network/interface.go +++ b/consensus/cbft/network/interface.go @@ -14,14 +14,13 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package network import ( "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" ) // Cbft defines the network layer to use the relevant interface @@ -29,10 +28,10 @@ import ( type Cbft interface { // Returns the ID value of the current node. - NodeID() discover.NodeID + Node() *enode.Node // Return a list of all consensus nodes. - ConsensusNodes() ([]discover.NodeID, error) + ConsensusNodes() ([]enode.ID, error) // Return configuration information of CBFT consensus. Config() *types.Config diff --git a/consensus/cbft/network/peer.go b/consensus/cbft/network/peer.go index 89c79f5b8d..920fa3ac8f 100644 --- a/consensus/cbft/network/peer.go +++ b/consensus/cbft/network/peer.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package network import ( @@ -28,15 +27,17 @@ import ( "sync" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" + mapset "github.com/deckarep/golang-set" + "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" - mapset "github.com/deckarep/golang-set" ) var ( @@ -474,7 +475,7 @@ func (ps *PeerSet) Len() int { // peersWithConsensus retrieves a list of peers that exist with the PeerSet based // on the incoming consensus node ID array. -func (ps *PeerSet) peersWithConsensus(consensusNodes []discover.NodeID) []*peer { +func (ps *PeerSet) peersWithConsensus(consensusNodes []enode.ID) []*peer { ps.lock.RLock() defer ps.lock.RUnlock() @@ -489,7 +490,7 @@ func (ps *PeerSet) peersWithConsensus(consensusNodes []discover.NodeID) []*peer } // peersWithoutConsensus retrieves a list of peer that does not contain consensus nodes. -func (ps *PeerSet) peersWithoutConsensus(consensusNodes []discover.NodeID) []*peer { +func (ps *PeerSet) peersWithoutConsensus(consensusNodes []enode.ID) []*peer { ps.lock.RLock() defer ps.lock.RUnlock() diff --git a/consensus/cbft/network/peer_test.go b/consensus/cbft/network/peer_test.go index 517ab02dfa..c9fcea4a76 100644 --- a/consensus/cbft/network/peer_test.go +++ b/consensus/cbft/network/peer_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package network import ( @@ -26,6 +25,8 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" @@ -35,7 +36,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) func Test_NewPeer(t *testing.T) { @@ -142,7 +142,7 @@ func Test_PeerSet_Peers(t *testing.T) { // Init the node of peerSet. ps := NewPeerSet() var peers []*peer - var ids []discover.NodeID + var ids []enode.ID for i := 0; i < 11; i++ { p, id := newTestPeer(1, fmt.Sprintf("%d", i)) peers = append(peers, p) @@ -195,7 +195,7 @@ func Test_PeerSet_Peers(t *testing.T) { func Test_Peer_Handshake(t *testing.T) { exec := func(close chan<- struct{}, inStatus, outStatus *protocols.CbftStatusData, wantErr error) { in, out := p2p.MsgPipe() - var id discover.NodeID + var id enode.ID rand.Read(id[:]) me := newPeer(1, p2p.NewPeer(id, "me", nil), in) you := newPeer(1, p2p.NewPeer(id, "you", nil), out) diff --git a/consensus/cbft/network/router.go b/consensus/cbft/network/router.go index edf511562f..5af0344a78 100644 --- a/consensus/cbft/network/router.go +++ b/consensus/cbft/network/router.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - // Package network implements a concrete consensus engines. package network @@ -25,7 +24,7 @@ import ( "reflect" "sync" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" @@ -38,10 +37,10 @@ import ( // the number of nodes selected per broadcast. const DefaultFanOut = 5 -type unregisterFunc func(id string) error // Unregister peer from peerSet. -type getByIDFunc func(id string) (*peer, error) // Get peer based on ID. -type consensusNodesFunc func() ([]discover.NodeID, error) // Get a list of consensus nodes. -type peersFunc func() ([]*peer, error) // Get a list of all neighbor nodes. +type unregisterFunc func(id string) error // Unregister peer from peerSet. +type getByIDFunc func(id string) (*peer, error) // Get peer based on ID. +type consensusNodesFunc func() ([]enode.ID, error) // Get a list of consensus nodes. +type peersFunc func() ([]*peer, error) // Get a list of all neighbor nodes. // Router implements the message protocol of gossip. // @@ -313,7 +312,7 @@ func formatPeers(peers []*peer) string { } // FormatNodes is used to print the information about peerID. -func FormatNodes(ids []discover.NodeID) string { +func FormatNodes(ids []enode.ID) string { var bf bytes.Buffer for idx, id := range ids { bf.WriteString(id.TerminalString()) diff --git a/consensus/cbft/network/router_test.go b/consensus/cbft/network/router_test.go index 33f35b0c24..74d92f7a6b 100644 --- a/consensus/cbft/network/router_test.go +++ b/consensus/cbft/network/router_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package network import ( @@ -26,6 +25,8 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" @@ -34,8 +35,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" - "github.com/stretchr/testify/assert" ) @@ -48,9 +47,9 @@ var ( func newTestRouter(t *testing.T) (*router, *peer) { // Create a peerSet for assistance. ps := NewPeerSet() - var consensusNodes []discover.NodeID + var consensusNodes []enode.ID writer, reader := p2p.MsgPipe() - var localID discover.NodeID + var localID enode.ID rand.Read(localID[:]) localPeer := newPeer(1, p2p.NewPeer(localID, "local", nil), reader) for i := 0; i < testingPeerCount; i++ { @@ -66,7 +65,7 @@ func newTestRouter(t *testing.T) (*router, *peer) { getHook := func(id string) (*peer, error) { return ps.get(id) } - consensusNodesHook := func() ([]discover.NodeID, error) { + consensusNodesHook := func() ([]enode.ID, error) { return consensusNodes, nil } peersHook := func() ([]*peer, error) { @@ -295,7 +294,7 @@ func Test_Router_FormatPeers(t *testing.T) { t.Log(formatPeers(peers)) } -func formatDiscoverNodeIDs(ids []discover.NodeID) string { +func formatDiscoverNodeIDs(ids []enode.ID) string { var bf bytes.Buffer for idx, id := range ids { bf.WriteString(id.TerminalString()) diff --git a/consensus/cbft/prepare_block_test.go b/consensus/cbft/prepare_block_test.go index d8d0f784b3..50cc399a04 100644 --- a/consensus/cbft/prepare_block_test.go +++ b/consensus/cbft/prepare_block_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbft import ( @@ -134,7 +133,7 @@ func (suit *PrepareBlockTestSuite) TestCheckErrPrepareBlock() { }, } for _, testcase := range testcases { - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.firstProposer().NodeID().String(), testcase.data); err == nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.firstProposer().Node().ID().String(), testcase.data); err == nil { suit.T().Errorf("case %s is failed", testcase.name) suit.view.secondProposer().state.ResetView(suit.view.Epoch(), suit.oldViewNumber) // suit.T().Error(err.Error()) @@ -151,7 +150,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockOneWithViewChangePrepareQCAnd prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 0, suit.view.secondProposerIndex(), block2, suit.blockOneQC.BlockQC, viewQC) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err != nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err != nil { suit.T().Fatal(err.Error()) } suit.waitVote() @@ -172,7 +171,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockOneWithViewChangePrepareQCAnd suit.view.secondProposerIndex(), block2, suit.blockOneQC.BlockQC, viewQC) fmt.Println(prepareBlock.BlockNum()) fmt.Println(prepareBlock.Block.ParentHash().String()) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err != nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err != nil { suit.T().Fatal(err.Error()) } suit.waitVote() @@ -243,7 +242,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockOneWithViewChangeErrFirstBloc }, } for _, testcase := range testcases { - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), testcase.data); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), testcase.data); err == nil { suit.T().Errorf("CASE:%s is failed", testcase.name) suit.view.firstProposer().state.ResetView(oldEpoch, suit.oldViewNumber+1) // suit.T().Error(err.Error()) @@ -303,7 +302,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockOneWithViewChangeErrFirstBloc }, } for _, testcase := range testcases { - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), testcase.data); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), testcase.data); err == nil { suit.T().Errorf("CASE:%s is failed", testcase.name) suit.view.firstProposer().state.ResetView(oldEpoch, suit.oldViewNumber+1) // suit.T().Error(err.Error()) @@ -324,7 +323,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockOneWithViewChangeFirstBlockTo viewQC := mockViewQC(block2, suit.view.allNode, block2QC.BlockQC) prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 0, suit.view.secondProposerIndex(), block3, block2QC.BlockQC, viewQC) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err == nil { suit.T().Fatal("FAIL") } else { suit.EqualValues("viewNumber higher than local(local:0, msg:1)", err.Error()) @@ -341,7 +340,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockOneWithViewChangeFirstBlockTo viewQC := mockViewQC(block2, suit.view.allNode, block2QC.BlockQC) prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 0, suit.view.secondProposerIndex(), block2, suit.blockOneQC.BlockQC, viewQC) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err == nil { suit.T().Fatal("FAIL") } else { suit.EqualValues("viewNumber higher than local(local:0, msg:1)", err.Error()) @@ -360,7 +359,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockOneWithViewChangeFirstBlockTo suit.view.firstProposer().state.SetLastViewChangeQC(viewQC) prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 0, suit.view.secondProposerIndex(), block2, suit.blockOneQC.BlockQC, viewQC) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err == nil { suit.T().Fatal("FAIL") } } @@ -375,7 +374,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockOneWithViewChangeFirstBlockNo viewQC := mockViewQC(block2, suit.view.allNode, block2QC.BlockQC) prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 0, suit.view.secondProposerIndex(), block2, suit.blockOneQC.BlockQC, viewQC) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -409,14 +408,14 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockOneWithDifHash() { prepareBlock1 := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 0, suit.view.secondProposerIndex(), block1, qc, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock1); err != nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock1); err != nil { suit.T().Fatal(err.Error()) } suit.waitVote() prepareBlock2 := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 0, suit.view.secondProposerIndex(), block2, qc, nil) fmt.Println(block1.Hash().String(), block2.Hash().String()) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock2); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock2); err == nil { suit.T().Error("FAIL") } else { reg := regexp.MustCompile(`DuplicatePrepareBlockEvidence`) @@ -436,7 +435,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockOneWithLastBlockQC() { _, qc := suit.view.firstProposer().blockTree.FindBlockAndQC(suit.view.firstProposer().state.HighestQCBlock().Hash(), suit.view.firstProposer().state.HighestQCBlock().NumberU64()) prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 0, suit.view.secondProposerIndex(), block11, qc, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err != nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err != nil { suit.T().Fatal(err.Error()) } suit.waitVote() @@ -450,7 +449,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockOneWithNumberIsOne() { errQC := mockErrBlockQC(notConsensusNodes, block1, 0, nil) prepareBlock := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 0, suit.view.firstProposerIndex(), block1, errQC.BlockQC, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err == nil { suit.T().Fatal("fail") } } @@ -463,7 +462,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockWithBlockIndexNotIsZero() { errQC := mockErrBlockQC(notConsensusNodes, block1, 0, nil) prepareBlock := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 1, suit.view.firstProposerIndex(), block1, errQC.BlockQC, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err == nil { suit.T().Fatal("fail") } } @@ -475,7 +474,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockOneWithLastBlockQCNotQC() { block11 := NewBlockWithSign(suit.view.firstProposer().state.HighestQCBlock().Hash(), 11, suit.view.allNode[1]) prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 0, suit.view.secondProposerIndex(), block11, nil, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -492,7 +491,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockOneWithLastBlockQCBlockIndexI suit.view.firstProposer().state.HighestQCBlock().NumberU64()) prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 1, suit.view.secondProposerIndex(), block11, qc, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -513,7 +512,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockOneWithLastBlockQCLead() { prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 0, suit.view.secondProposerIndex(), block11, block10QC.BlockQC, nil) - if err := otherNode.OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err == nil { + if err := otherNode.OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -533,7 +532,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockNotOneWithViewNumberTooLow() block12 := NewBlockWithSign(block11.Hash(), 12, suit.view.allNode[1]) prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 1, suit.view.secondProposerIndex(), block12, block11QC.BlockQC, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -552,7 +551,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockNotOneWithViewNumberTooHigh() block12 := NewBlockWithSign(suit.blockOne.Hash(), 12, suit.view.allNode[1]) prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+2, 1, suit.view.secondProposerIndex(), block12, nil, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -572,7 +571,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockNotOneWithParentQC() { block12 := NewBlockWithSign(block11.Hash(), 12, suit.view.allNode[1]) prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 1, suit.view.secondProposerIndex(), block12, nil, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err != nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err != nil { suit.T().Fatal(err.Error()) } suit.waitVote() @@ -588,14 +587,14 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockNotOneWithParentNotQC() { suit.view.firstProposer().state.HighestQCBlock().NumberU64()) prepareBlock11 := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 0, suit.view.secondProposerIndex(), block11, oldQC, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock11); err != nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock11); err != nil { suit.T().Fatal("FAIL") } suit.waitVote() block12 := NewBlockWithSign(block11.Hash(), 12, suit.view.allNode[1]) prepareBlock12 := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 1, suit.view.secondProposerIndex(), block12, nil, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock12); err != nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock12); err != nil { suit.T().Fatal(err.Error()) } suit.waitVote() @@ -610,7 +609,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockNotOneWithAmountTooMany() { block11 := NewBlockWithSign(suit.view.firstProposer().state.HighestQCBlock().Hash(), 11, suit.view.allNode[1]) prepareBlock := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 10, suit.view.secondProposerIndex(), block11, nil, nil) - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.firstProposer().NodeID().String(), prepareBlock); err == nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.firstProposer().Node().ID().String(), prepareBlock); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -625,13 +624,13 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockNotOneWithBlockNumberRepeat() suit.createEvPool(paths) prepareBlock1 := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 0, suit.view.firstProposerIndex(), suit.blockOne, nil, nil) - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.firstProposer().NodeID().String(), prepareBlock1); err != nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.firstProposer().Node().ID().String(), prepareBlock1); err != nil { suit.T().Fatal(err.Error()) } block2 := NewBlockWithSign(suit.blockOne.Hash(), 1, suit.view.allNode[0]) prepareBlock2 := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 0, suit.view.firstProposerIndex(), block2, nil, nil) - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.firstProposer().NodeID().String(), prepareBlock2); err == nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.firstProposer().Node().ID().String(), prepareBlock2); err == nil { suit.T().Fatal("FAIL") } else { reg := regexp.MustCompile(`DuplicatePrepareBlockEvidence`) @@ -654,7 +653,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockNotOneWithBlockNumberDisconti block12 := NewBlockWithSign(block11.Hash(), 13, suit.view.allNode[1]) prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 1, suit.view.secondProposerIndex(), block12, block11QC.BlockQC, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -675,7 +674,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockNotOneWithBlockHashDiscontinu block12 := NewBlockWithSign(suit.view.genesisBlock.Hash(), 12, suit.view.allNode[1]) prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 1, suit.view.secondProposerIndex(), block12, block11QC.BlockQC, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -695,7 +694,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockNotOneWithBlockIndexErr() { block12 := NewBlockWithSign(block11.Hash(), 12, suit.view.allNode[1]) prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 2, suit.view.secondProposerIndex(), block12, block11QC.BlockQC, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err == nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -708,13 +707,13 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockNotOneWithBlockIndexErr() { func (suit *PrepareBlockTestSuite) TestPrepareBlockNotOneWithBlockIndexRepeat() { prepareBlock1 := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 0, suit.view.firstProposerIndex(), suit.blockOne, nil, nil) - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock1); err != nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock1); err != nil { suit.T().Fatal(err.Error()) } block2 := NewBlockWithSign(suit.blockOne.Hash(), 3, suit.view.allNode[0]) prepareBlock2 := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 0, suit.view.firstProposerIndex(), block2, nil, nil) - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock2); err == nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock2); err == nil { suit.T().Fatal("FAIL") } } @@ -724,10 +723,10 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockNotOneWithBlockIndexRepeat() func (suit *PrepareBlockTestSuite) TestPrepareBlockDup() { prepareBlock1 := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 0, suit.view.firstProposerIndex(), suit.blockOne, nil, nil) - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock1); err != nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock1); err != nil { suit.T().Fatal(err.Error()) } - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock1); err == nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock1); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -740,7 +739,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockBlockIndexTooHigh() { block2 := NewBlockWithSign(suit.blockOne.Hash(), 2, suit.view.allNode[0]) prepareBlock1 := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 1, suit.view.firstProposerIndex(), block2, nil, nil) - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock1); err == nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock1); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -753,7 +752,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockNotOneWithTimeout() { time.Sleep((testPeriod + 200) * time.Millisecond) prepareBlock1 := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 0, suit.view.firstProposerIndex(), suit.blockOne, nil, nil) - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock1); err == nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock1); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -766,7 +765,7 @@ func (suit *PrepareBlockTestSuite) TestPrepareBlockNotOneWithNotConsensus() { notConsensus := mockNotConsensusNode(false, suit.view.nodeParams, 1) prepareBlock1 := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 0, suit.view.firstProposerIndex(), suit.blockOne, nil, nil) - if err := notConsensus[0].engine.OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock1); err != nil { + if err := notConsensus[0].engine.OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock1); err != nil { suit.T().Fatal(err.Error()) } } diff --git a/consensus/cbft/prepare_vote_test.go b/consensus/cbft/prepare_vote_test.go index 484b84aef2..03e55a22c8 100644 --- a/consensus/cbft/prepare_vote_test.go +++ b/consensus/cbft/prepare_vote_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbft import ( @@ -27,10 +26,11 @@ import ( "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" + "github.com/stretchr/testify/suite" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/stretchr/testify/suite" ) func TestPrepareVoteSuite(t *testing.T) { @@ -87,7 +87,7 @@ func (suit *PrepareVoteTestSuite) TestBuildPrepareVote() { block12 := NewBlockWithSign(block11.Hash(), 12, suit.view.allNode[1]) prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 1, suit.view.secondProposerIndex(), block12, nil, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err != nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err != nil { suit.T().Fatal(err.Error()) } suit.waitVote() @@ -108,7 +108,7 @@ func (suit *PrepareVoteTestSuite) TestCheckErrPrepareVote() { _, notConsensusKey := GenerateKeys(1) prepareBlock := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 0, suit.view.firstProposerIndex(), suit.blockOne, nil, nil) - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.firstProposer().NodeID().String(), prepareBlock); err != nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.firstProposer().Node().ID().String(), prepareBlock); err != nil { suit.T().Fatal("FAIL") } testcases := []struct { @@ -138,7 +138,7 @@ func (suit *PrepareVoteTestSuite) TestCheckErrPrepareVote() { }, } for _, testcase := range testcases { - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), testcase.data); err == nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), testcase.data); err == nil { suit.T().Errorf("case %s is failed", testcase.name) } else { fmt.Println(err.Error()) @@ -158,7 +158,7 @@ func (suit *PrepareVoteTestSuite) TestPrepareVoteWithParentIsZeroButNotParentQC( suit.view.secondProposer().state.AddPrepareBlock(prepareBlock) prepareVote := mockPrepareVote(suit.view.firstProposerBlsKey(), epoch, suit.oldViewNumber, 0, suit.view.firstProposerIndex(), suit.blockOne.Hash(), suit.blockOne.NumberU64(), nil) - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote); err != nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote); err != nil { suit.T().Fatal(err.Error()) } suit.Equal(suit.view.firstProposerIndex(), suit.view.secondProposer().state.AllPrepareVoteByIndex(0)[0].ValidatorIndex) @@ -171,12 +171,12 @@ func (suit *PrepareVoteTestSuite) TestPrepareVoteWithParentIsNotZeroButNotParent block2 := NewBlockWithSign(suit.blockOne.Hash(), 2, suit.view.allNode[0]) prepareBlock := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 1, suit.view.firstProposerIndex(), block2, nil, nil) - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err != nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err != nil { suit.T().Fatal(err.Error()) } prepareVote := mockPrepareVote(suit.view.firstProposerBlsKey(), suit.epoch, suit.oldViewNumber, 1, suit.view.firstProposerIndex(), block2.Hash(), block2.NumberU64(), nil) - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote); err == nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -193,12 +193,12 @@ func (suit *PrepareVoteTestSuite) TestPrepareVoteWithParentIsNotZeroAndBlockInde fmt.Println(qc.String()) prepareBlock := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 0, suit.view.secondProposerIndex(), block1, qc, nil) - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock); err != nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock); err != nil { suit.T().Fatal(err.Error()) } prepareVote := mockPrepareVote(suit.view.firstProposerBlsKey(), suit.epoch, suit.oldViewNumber+1, 0, suit.view.firstProposerIndex(), block1.Hash(), block1.NumberU64(), nil) - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote); err == nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -227,7 +227,7 @@ func (suit *PrepareVoteTestSuite) TestPrepareVoteWithBlockNumberIsOneAndErrParen func (suit *PrepareVoteTestSuite) TestPrepareVoteWithNotPrepareBlock() { prepareVote := mockPrepareVote(suit.view.firstProposerBlsKey(), suit.epoch, suit.oldViewNumber, 0, suit.view.firstProposerIndex(), suit.blockOne.Hash(), suit.blockOne.NumberU64(), nil) - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote); err == nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -243,7 +243,7 @@ func (suit *PrepareVoteTestSuite) TestPrepareVoteWithExceedLimit() { _, oldQC := suit.view.firstProposer().blockTree.FindBlockAndQC(h, n) prepareVote := mockPrepareVote(suit.view.secondProposerBlsKey(), suit.epoch, suit.oldViewNumber+1, 10, suit.view.secondProposerIndex(), block11.Hash(), block11.NumberU64(), oldQC) - if err := suit.view.firstProposer().OnPrepareVote(suit.view.secondProposer().NodeID().String(), prepareVote); err == nil { + if err := suit.view.firstProposer().OnPrepareVote(suit.view.secondProposer().Node().ID().String(), prepareVote); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -260,10 +260,10 @@ func (suit *PrepareVoteTestSuite) TestPrepareVoteWithRepeat() { prepareVote := mockPrepareVote(suit.view.firstProposerBlsKey(), suit.epoch, suit.oldViewNumber, 0, suit.view.firstProposerIndex(), suit.blockOne.Hash(), suit.blockOne.NumberU64(), nil) - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote); err != nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote); err != nil { suit.T().Fatal(err.Error()) } - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote); err == nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote); err == nil { suit.T().Fatal("FAIL") } else { suit.Equal(err.Error(), "prepare vote has exist(blockIndex:0, validatorIndex:0)") @@ -287,10 +287,10 @@ func (suit *PrepareVoteTestSuite) TestPrepareVoteDu() { prepareVote2 := mockPrepareVote(suit.view.firstProposerBlsKey(), suit.epoch, suit.oldViewNumber, 0, suit.view.firstProposerIndex(), block1.Hash(), block1.NumberU64(), nil) - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote1); err != nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote1); err != nil { suit.T().Fatal(err.Error()) } - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote2); err == nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote2); err == nil { suit.T().Fatal("FAIL") } else { reg := regexp.MustCompile(`DuplicatePrepareVoteEvidence`) @@ -310,7 +310,7 @@ func (suit *PrepareVoteTestSuite) TestPrepareVoteWithViewNumberTooLow() { suit.view.secondProposer().state.ResetView(suit.epoch, suit.oldViewNumber+1) prepareVote := mockPrepareVote(suit.view.firstProposerBlsKey(), suit.epoch, suit.oldViewNumber, 0, suit.view.firstProposerIndex(), suit.blockOne.Hash(), suit.blockOne.NumberU64(), nil) - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote); err == nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote); err == nil { suit.T().Fatal("FAIL") } else { suit.Equal(err.Error(), "viewNumber too low(local:1, msg:0)") @@ -322,7 +322,7 @@ func (suit *PrepareVoteTestSuite) TestPrepareVoteWithViewNumberTooLow() { func (suit *PrepareVoteTestSuite) TestPrepareVoteWithViewNumberTooHigh() { prepareVote := mockPrepareVote(suit.view.firstProposerBlsKey(), suit.epoch, suit.oldViewNumber+1, 0, suit.view.firstProposerIndex(), suit.blockOne.Hash(), suit.blockOne.NumberU64(), nil) - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote); err == nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote); err == nil { suit.T().Fatal("FAIL") } else { suit.Equal(err.Error(), "viewNumber higher than local(local:0, msg:1)") @@ -335,18 +335,18 @@ func (suit *PrepareVoteTestSuite) TestPrepareVoteWithParentIsNotParentQC() { qc := mockBlockQC(suit.view.allNode, suit.blockOne, 0, nil) prepareBlock1 := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 0, suit.view.firstProposerIndex(), suit.blockOne, nil, nil) - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.firstProposer().NodeID().String(), prepareBlock1); err != nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.firstProposer().Node().ID().String(), prepareBlock1); err != nil { suit.T().Fatal(err.Error()) } block2 := NewBlockWithSign(suit.blockOne.Hash(), 2, suit.view.allNode[0]) prepareBlock2 := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 1, suit.view.firstProposerIndex(), block2, nil, nil) - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock2); err != nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock2); err != nil { suit.T().Fatal("FAIL") } prepareVote := mockPrepareVote(suit.view.firstProposerBlsKey(), suit.epoch, suit.oldViewNumber, 1, suit.view.firstProposerIndex(), block2.Hash(), block2.NumberU64(), qc.BlockQC) - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote); err != nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote); err != nil { suit.T().Fatal(err.Error()) } } @@ -357,19 +357,19 @@ func (suit *PrepareVoteTestSuite) TestPrepareVoteWithParentErrParentQC() { qc := mockBlockQC(suit.view.allNode[0:1], suit.blockOne, 0, nil) prepareBlock1 := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 0, suit.view.firstProposerIndex(), suit.blockOne, nil, nil) - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock1); err != nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock1); err != nil { suit.T().Fatal("FAIL") } block2 := NewBlockWithSign(suit.blockOne.Hash(), 2, suit.view.allNode[0]) prepareBlock2 := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber, 1, suit.view.firstProposerIndex(), block2, nil, nil) - if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock2); err != nil { + if err := suit.view.secondProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock2); err != nil { suit.T().Fatal("FAIL") } prepareVote := mockPrepareVote(suit.view.firstProposerBlsKey(), suit.epoch, suit.oldViewNumber, 1, suit.view.firstProposerIndex(), block2.Hash(), block2.NumberU64(), qc.BlockQC) - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote); err == nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -389,14 +389,14 @@ func (suit *PrepareVoteTestSuite) TestPrepareVoteWithParentQCHasChild() { block12 := NewBlockWithSign(block11.Hash(), 12, suit.view.allNode[1]) prepareBlock12 := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 1, suit.view.secondProposerIndex(), block12, nil, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock12); err != nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock12); err != nil { suit.T().Fatal(err.Error()) } block12QC := mockBlockQC(suit.view.allNode, block12, 1, block11QC.BlockQC) block13 := NewBlockWithSign(block12.Hash(), 13, suit.view.allNode[1]) prepareBlock13 := mockPrepareBlock(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.oldViewNumber+1, 2, suit.view.secondProposerIndex(), block13, nil, nil) - if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().NodeID().String(), prepareBlock13); err != nil { + if err := suit.view.firstProposer().OnPrepareBlock(suit.view.secondProposer().Node().ID().String(), prepareBlock13); err != nil { suit.T().Fatal(err.Error()) } suit.waitVote() @@ -429,7 +429,7 @@ func (suit *PrepareVoteTestSuite) TestPrepareVoteWithTimeout() { suit.view.firstProposerIndex(), suit.blockOne, nil, nil) suit.view.secondProposer().state.AddPrepareBlock(prepareBlock) time.Sleep(time.Millisecond * testPeriod) - if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote); err == nil { + if err := suit.view.secondProposer().OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote); err == nil { suit.T().Fatal("FAIL") } else { fmt.Println(err.Error()) @@ -495,7 +495,7 @@ func (suit *PrepareVoteTestSuite) TestPrepareVoteOfNotConsensus() { prepareBlock := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.epoch, suit.oldViewNumber, 0, suit.view.firstProposerIndex(), suit.blockOne, nil, nil) notConsensus[0].engine.state.AddPrepareBlock(prepareBlock) - if err := notConsensus[0].engine.OnPrepareVote(suit.view.firstProposer().NodeID().String(), prepareVote); err != nil { + if err := notConsensus[0].engine.OnPrepareVote(suit.view.firstProposer().Node().ID().String(), prepareVote); err != nil { suit.T().Error(err.Error()) } } diff --git a/consensus/cbft/sync_msg_test.go b/consensus/cbft/sync_msg_test.go index 7554713b50..1d21688d1d 100644 --- a/consensus/cbft/sync_msg_test.go +++ b/consensus/cbft/sync_msg_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbft import ( @@ -358,7 +357,7 @@ func (suit *SyncMsgTestSuite) TestOnGetQCBlockListDifNumber() { func (suit *SyncMsgTestSuite) TestOnGetPrepareVote() { votes := make([]*protocols.PrepareVote, 0) for _, node := range suit.view.allCbft { - index, err := node.validatorPool.GetIndexByNodeID(suit.epoch, node.config.Option.NodeID) + index, err := node.validatorPool.GetIndexByNodeID(suit.epoch, node.config.Option.Node.ID()) if err != nil { panic(err.Error()) } @@ -392,7 +391,7 @@ func (suit *SyncMsgTestSuite) TestOnPrepareVotes() { suit.view.firstProposer().state.AddPrepareBlock(pb) votes := make([]*protocols.PrepareVote, 0) for _, node := range suit.view.allCbft { - index, err := node.validatorPool.GetIndexByNodeID(suit.epoch, node.config.Option.NodeID) + index, err := node.validatorPool.GetIndexByNodeID(suit.epoch, node.config.Option.Node.ID()) if err != nil { panic(err.Error()) } @@ -417,7 +416,7 @@ func (suit *SyncMsgTestSuite) TestOnPrepareVotesDup() { suit.view.firstProposer().state.AddPrepareBlock(pb) votes := make([]*protocols.PrepareVote, 0) for _, node := range suit.view.allCbft { - index, err := node.validatorPool.GetIndexByNodeID(suit.epoch, node.config.Option.NodeID) + index, err := node.validatorPool.GetIndexByNodeID(suit.epoch, node.config.Option.Node.ID()) if err != nil { panic(err.Error()) } diff --git a/consensus/cbft/sync_process_test.go b/consensus/cbft/sync_process_test.go index 4943fb223d..e1a92e3529 100644 --- a/consensus/cbft/sync_process_test.go +++ b/consensus/cbft/sync_process_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbft import ( @@ -23,9 +22,11 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/stretchr/testify/assert" "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/fetcher" @@ -34,7 +35,6 @@ import ( types2 "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/stretchr/testify/assert" ) func init() { @@ -527,7 +527,7 @@ func TestCbft_MissingViewChangeNodes(t *testing.T) { assert.Nil(t, message) } -func buildSingleCbft() (*Cbft, []discover.NodeID) { +func buildSingleCbft() (*Cbft, []enode.ID) { // Init mock node. pk, sk, cbftnodes := GenerateCbftNode(1) node := MockNode(pk[0], sk[0], cbftnodes, 1000000, 10) @@ -535,7 +535,7 @@ func buildSingleCbft() (*Cbft, []discover.NodeID) { //node.engine.network.Close() // Add a node to the Handler. cNodes := network.RandomID() - node.engine.consensusNodesMock = func() ([]discover.NodeID, error) { + node.engine.consensusNodesMock = func() ([]enode.ID, error) { return cNodes, nil } network.FillEngineManager(cNodes, node.engine.network) diff --git a/consensus/cbft/types/config.go b/consensus/cbft/types/config.go index 2e2737c62d..473a5f41f2 100644 --- a/consensus/cbft/types/config.go +++ b/consensus/cbft/types/config.go @@ -14,20 +14,21 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package types import ( "crypto/ecdsa" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/crypto/bls" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" ) type OptionsConfig struct { NodePriKey *ecdsa.PrivateKey `json:"-"` - NodeID discover.NodeID `json:"nodeID"` + NodeID enode.IDv0 `json:"nodeID"` + Node *enode.Node `json:"-"` BlsPriKey *bls.SecretKey `json:"-"` WalMode bool `json:"walMode"` diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index e306b16df3..2112f3849c 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package validator import ( @@ -23,6 +22,8 @@ import ( "fmt" "sync" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/core/state" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" @@ -38,7 +39,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/crypto/bls" "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -50,18 +50,18 @@ func newValidators(nodes []params.CbftNode, validBlockNumber uint64) *cbfttypes. } for i, node := range nodes { - pubkey, err := node.Node.ID.Pubkey() - if err != nil { - panic(err) + pubkey := node.Node.Pubkey() + if pubkey == nil { + panic("pubkey should not nil") } blsPubKey := node.BlsPubKey - vds.Nodes[node.Node.ID] = &cbfttypes.ValidateNode{ + vds.Nodes[node.Node.ID()] = &cbfttypes.ValidateNode{ Index: uint32(i), Address: crypto.PubkeyToNodeAddress(*pubkey), PubKey: pubkey, - NodeID: node.Node.ID, + NodeID: node.Node.ID(), BlsPubKey: &blsPubKey, } } @@ -104,7 +104,7 @@ func (d *StaticAgency) GetValidator(uint64) (*cbfttypes.Validators, error) { return d.validators, nil } -func (d *StaticAgency) IsCandidateNode(nodeID discover.NodeID) bool { +func (d *StaticAgency) IsCandidateNode(nodeID enode.IDv0) bool { return false } @@ -155,7 +155,7 @@ func (d *MockAgency) GetValidator(blockNumber uint64) (*cbfttypes.Validators, er return d.validators, nil } -func (d *MockAgency) IsCandidateNode(nodeID discover.NodeID) bool { +func (d *MockAgency) IsCandidateNode(nodeID enode.IDv0) bool { return false } @@ -271,11 +271,12 @@ func (ia *InnerAgency) GetValidator(blockNumber uint64) (v *cbfttypes.Validators for _, node := range vds.ValidateNodes { pubkey, _ := node.NodeID.Pubkey() blsPubKey := node.BlsPubKey - validators.Nodes[node.NodeID] = &cbfttypes.ValidateNode{ + id := enode.PubkeyToIDV4(pubkey) + validators.Nodes[id] = &cbfttypes.ValidateNode{ Index: uint32(node.Index), Address: node.Address, PubKey: pubkey, - NodeID: node.NodeID, + NodeID: node.NodeID.ID(), BlsPubKey: &blsPubKey, } } @@ -283,7 +284,7 @@ func (ia *InnerAgency) GetValidator(blockNumber uint64) (v *cbfttypes.Validators return &validators, nil } -func (ia *InnerAgency) IsCandidateNode(nodeID discover.NodeID) bool { +func (ia *InnerAgency) IsCandidateNode(nodeID enode.IDv0) bool { return true } @@ -298,7 +299,7 @@ type ValidatorPool struct { lock sync.RWMutex // Current node's public key - nodeID discover.NodeID + nodeID enode.ID // A block number which validators switch point. switchPoint uint64 @@ -312,7 +313,7 @@ type ValidatorPool struct { } // NewValidatorPool new a validator pool. -func NewValidatorPool(agency consensus.Agency, blockNumber uint64, epoch uint64, nodeID discover.NodeID) *ValidatorPool { +func NewValidatorPool(agency consensus.Agency, blockNumber uint64, epoch uint64, nodeID enode.ID) *ValidatorPool { pool := &ValidatorPool{ agency: agency, nodeID: nodeID, @@ -426,22 +427,22 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *even // in the consensus stages. Also we are not needed // to keep connect with old validators. if isValidatorAfter { - for _, nodeID := range vp.currentValidators.NodeList() { + for nodeID, vnode := range vp.currentValidators.Nodes { if node, _ := vp.prevValidators.FindNodeByID(nodeID); node == nil { - eventMux.Post(cbfttypes.AddValidatorEvent{NodeID: nodeID}) + eventMux.Post(cbfttypes.AddValidatorEvent{Node: enode.NewV4(vnode.PubKey, nil, 0, 0)}) log.Trace("Post AddValidatorEvent", "nodeID", nodeID.String()) } } - for _, nodeID := range vp.prevValidators.NodeList() { + for nodeID, vnode := range vp.prevValidators.Nodes { if node, _ := vp.currentValidators.FindNodeByID(nodeID); node == nil { - eventMux.Post(cbfttypes.RemoveValidatorEvent{NodeID: nodeID}) + eventMux.Post(cbfttypes.RemoveValidatorEvent{Node: enode.NewV4(vnode.PubKey, nil, 0, 0)}) log.Trace("Post RemoveValidatorEvent", "nodeID", nodeID.String()) } } } else { - for _, nodeID := range vp.prevValidators.NodeList() { - eventMux.Post(cbfttypes.RemoveValidatorEvent{NodeID: nodeID}) + for nodeID, vnode := range vp.prevValidators.Nodes { + eventMux.Post(cbfttypes.RemoveValidatorEvent{Node: enode.NewV4(vnode.PubKey, nil, 0, 0)}) log.Trace("Post RemoveValidatorEvent", "nodeID", nodeID.String()) } } @@ -451,8 +452,8 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *even // consensus peers is because we need to keep connecting // with other validators in the consensus stages. if isValidatorAfter { - for _, nodeID := range vp.currentValidators.NodeList() { - eventMux.Post(cbfttypes.AddValidatorEvent{NodeID: nodeID}) + for nodeID, vnode := range vp.currentValidators.Nodes { + eventMux.Post(cbfttypes.AddValidatorEvent{Node: enode.NewV4(vnode.PubKey, nil, 0, 0)}) log.Trace("Post AddValidatorEvent", "nodeID", nodeID.String()) } } @@ -462,13 +463,13 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *even } // GetValidatorByNodeID get the validator by node id. -func (vp *ValidatorPool) GetValidatorByNodeID(epoch uint64, nodeID discover.NodeID) (*cbfttypes.ValidateNode, error) { +func (vp *ValidatorPool) GetValidatorByNodeID(epoch uint64, nodeID enode.ID) (*cbfttypes.ValidateNode, error) { vp.lock.RLock() defer vp.lock.RUnlock() return vp.getValidatorByNodeID(epoch, nodeID) } -func (vp *ValidatorPool) getValidatorByNodeID(epoch uint64, nodeID discover.NodeID) (*cbfttypes.ValidateNode, error) { +func (vp *ValidatorPool) getValidatorByNodeID(epoch uint64, nodeID enode.ID) (*cbfttypes.ValidateNode, error) { if vp.epochToBlockNumber(epoch) <= vp.switchPoint { return vp.prevValidators.FindNodeByID(nodeID) } @@ -506,14 +507,14 @@ func (vp *ValidatorPool) getValidatorByIndex(epoch uint64, index uint32) (*cbftt } // GetNodeIDByIndex get the node id by index. -func (vp *ValidatorPool) GetNodeIDByIndex(epoch uint64, index int) discover.NodeID { +func (vp *ValidatorPool) GetNodeIDByIndex(epoch uint64, index int) enode.ID { vp.lock.RLock() defer vp.lock.RUnlock() return vp.getNodeIDByIndex(epoch, index) } -func (vp *ValidatorPool) getNodeIDByIndex(epoch uint64, index int) discover.NodeID { +func (vp *ValidatorPool) getNodeIDByIndex(epoch uint64, index int) enode.ID { if vp.epochToBlockNumber(epoch) <= vp.switchPoint { return vp.prevValidators.NodeID(index) } @@ -521,14 +522,14 @@ func (vp *ValidatorPool) getNodeIDByIndex(epoch uint64, index int) discover.Node } // GetIndexByNodeID get the index by node id. -func (vp *ValidatorPool) GetIndexByNodeID(epoch uint64, nodeID discover.NodeID) (uint32, error) { +func (vp *ValidatorPool) GetIndexByNodeID(epoch uint64, nodeID enode.ID) (uint32, error) { vp.lock.RLock() defer vp.lock.RUnlock() return vp.getIndexByNodeID(epoch, nodeID) } -func (vp *ValidatorPool) getIndexByNodeID(epoch uint64, nodeID discover.NodeID) (uint32, error) { +func (vp *ValidatorPool) getIndexByNodeID(epoch uint64, nodeID enode.ID) (uint32, error) { if vp.epochToBlockNumber(epoch) <= vp.switchPoint { return vp.prevValidators.Index(nodeID) } @@ -536,14 +537,14 @@ func (vp *ValidatorPool) getIndexByNodeID(epoch uint64, nodeID discover.NodeID) } // ValidatorList get the validator list. -func (vp *ValidatorPool) ValidatorList(epoch uint64) []discover.NodeID { +func (vp *ValidatorPool) ValidatorList(epoch uint64) []enode.ID { vp.lock.RLock() defer vp.lock.RUnlock() return vp.validatorList(epoch) } -func (vp *ValidatorPool) validatorList(epoch uint64) []discover.NodeID { +func (vp *ValidatorPool) validatorList(epoch uint64) []enode.ID { if vp.epochToBlockNumber(epoch) <= vp.switchPoint { return vp.prevValidators.NodeList() } @@ -568,20 +569,20 @@ func (vp *ValidatorPool) VerifyHeader(header *types.Header) error { } // IsValidator check if the node is validator. -func (vp *ValidatorPool) IsValidator(epoch uint64, nodeID discover.NodeID) bool { +func (vp *ValidatorPool) IsValidator(epoch uint64, nodeID enode.ID) bool { vp.lock.RLock() defer vp.lock.RUnlock() return vp.isValidator(epoch, nodeID) } -func (vp *ValidatorPool) isValidator(epoch uint64, nodeID discover.NodeID) bool { +func (vp *ValidatorPool) isValidator(epoch uint64, nodeID enode.ID) bool { _, err := vp.getValidatorByNodeID(epoch, nodeID) return err == nil } // IsCandidateNode check if the node is candidate node. -func (vp *ValidatorPool) IsCandidateNode(nodeID discover.NodeID) bool { +func (vp *ValidatorPool) IsCandidateNode(nodeID enode.IDv0) bool { return vp.agency.IsCandidateNode(nodeID) } diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index 25dfd77298..cbd3c9b0d2 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package validator import ( @@ -28,6 +27,8 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/core/rawdb" "github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" @@ -47,7 +48,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/crypto/bls" "github.com/AlayaNetwork/Alaya-Go/ethdb" "github.com/AlayaNetwork/Alaya-Go/event" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -90,43 +90,42 @@ var ( func newTestNode() []params.CbftNode { nodes := make([]params.CbftNode, 0) - - n0, _ := discover.ParseNode("enode://e74864b27aecf5cbbfcd523da7657f126b0a5330a970c8264140704d280e6737fd8098d0ee4299706b825771f3d7017aa02f662e4e9a48e9112d93bf05fea66d@127.0.0.1:16789") + n0, _ := enode.ParseV4("enode://e74864b27aecf5cbbfcd523da7657f126b0a5330a970c8264140704d280e6737fd8098d0ee4299706b825771f3d7017aa02f662e4e9a48e9112d93bf05fea66d@127.0.0.1:16789") var sec0 bls.SecretKey sec0.SetByCSPRNG() - n1, _ := discover.ParseNode("enode://bf0cd4c95bc3d48cc7999bcf5b3fe6ab9974fd5dabc5253e3e7506c075d0c7a699251caa76672b144be0fc75fe34cee9aaac20753036b0dbd1cb2b3691f26965@127.0.0.1:26789") + n1, _ := enode.ParseV4("enode://bf0cd4c95bc3d48cc7999bcf5b3fe6ab9974fd5dabc5253e3e7506c075d0c7a699251caa76672b144be0fc75fe34cee9aaac20753036b0dbd1cb2b3691f26965@127.0.0.1:26789") var sec1 bls.SecretKey sec1.SetByCSPRNG() - n2, _ := discover.ParseNode("enode://84c59064dd3b2df54204c52d772cf3809bb0ad6be268843e406f473cef61dacc6d4d4546779dbfa1480deddc64016179ecefdf75d837914f69b679a71ad9711a@127.0.0.1:36789") + n2, _ := enode.ParseV4("enode://84c59064dd3b2df54204c52d772cf3809bb0ad6be268843e406f473cef61dacc6d4d4546779dbfa1480deddc64016179ecefdf75d837914f69b679a71ad9711a@127.0.0.1:36789") var sec2 bls.SecretKey sec2.SetByCSPRNG() - n3, _ := discover.ParseNode("enode://a9b7e60fa1290c1013cb862c0693d9e87113e8d4cb87d60452749acd978c9fd3a80b49ab5ce7916a5bbfe0b0a0d7e4cde201bd59acccdf97006990156bfe73a5@127.0.0.1:46789") + n3, _ := enode.ParseV4("enode://a9b7e60fa1290c1013cb862c0693d9e87113e8d4cb87d60452749acd978c9fd3a80b49ab5ce7916a5bbfe0b0a0d7e4cde201bd59acccdf97006990156bfe73a5@127.0.0.1:46789") var sec3 bls.SecretKey sec3.SetByCSPRNG() - nodes = append(nodes, params.CbftNode{Node: *n0, BlsPubKey: *sec0.GetPublicKey()}) - nodes = append(nodes, params.CbftNode{Node: *n1, BlsPubKey: *sec1.GetPublicKey()}) - nodes = append(nodes, params.CbftNode{Node: *n2, BlsPubKey: *sec2.GetPublicKey()}) - nodes = append(nodes, params.CbftNode{Node: *n3, BlsPubKey: *sec3.GetPublicKey()}) + nodes = append(nodes, params.CbftNode{Node: n0, BlsPubKey: *sec0.GetPublicKey()}) + nodes = append(nodes, params.CbftNode{Node: n1, BlsPubKey: *sec1.GetPublicKey()}) + nodes = append(nodes, params.CbftNode{Node: n2, BlsPubKey: *sec2.GetPublicKey()}) + nodes = append(nodes, params.CbftNode{Node: n3, BlsPubKey: *sec3.GetPublicKey()}) return nodes } func newTestNode3() []params.CbftNode { nodes := make([]params.CbftNode, 0) - n0, _ := discover.ParseNode("enode://e74864b27aecf5cbbfcd523da7657f126b0a5330a970c8264140704d280e6737fd8098d0ee4299706b825771f3d7017aa02f662e4e9a48e9112d93bf05fea66d@127.0.0.1:16789") + n0, _ := enode.ParseV4("enode://e74864b27aecf5cbbfcd523da7657f126b0a5330a970c8264140704d280e6737fd8098d0ee4299706b825771f3d7017aa02f662e4e9a48e9112d93bf05fea66d@127.0.0.1:16789") var sec0 bls.SecretKey sec0.SetByCSPRNG() - n1, _ := discover.ParseNode("enode://bf0cd4c95bc3d48cc7999bcf5b3fe6ab9974fd5dabc5253e3e7506c075d0c7a699251caa76672b144be0fc75fe34cee9aaac20753036b0dbd1cb2b3691f26965@127.0.0.1:26789") + n1, _ := enode.ParseV4("enode://bf0cd4c95bc3d48cc7999bcf5b3fe6ab9974fd5dabc5253e3e7506c075d0c7a699251caa76672b144be0fc75fe34cee9aaac20753036b0dbd1cb2b3691f26965@127.0.0.1:26789") var sec1 bls.SecretKey sec1.SetByCSPRNG() - n2, _ := discover.ParseNode("enode://84c59064dd3b2df54204c52d772cf3809bb0ad6be268843e406f473cef61dacc6d4d4546779dbfa1480deddc64016179ecefdf75d837914f69b679a71ad9711a@127.0.0.1:36789") + n2, _ := enode.ParseV4("enode://84c59064dd3b2df54204c52d772cf3809bb0ad6be268843e406f473cef61dacc6d4d4546779dbfa1480deddc64016179ecefdf75d837914f69b679a71ad9711a@127.0.0.1:36789") var sec2 bls.SecretKey sec2.SetByCSPRNG() - nodes = append(nodes, params.CbftNode{Node: *n0, BlsPubKey: *sec0.GetPublicKey()}) - nodes = append(nodes, params.CbftNode{Node: *n1, BlsPubKey: *sec1.GetPublicKey()}) - nodes = append(nodes, params.CbftNode{Node: *n2, BlsPubKey: *sec2.GetPublicKey()}) + nodes = append(nodes, params.CbftNode{Node: n0, BlsPubKey: *sec0.GetPublicKey()}) + nodes = append(nodes, params.CbftNode{Node: n1, BlsPubKey: *sec1.GetPublicKey()}) + nodes = append(nodes, params.CbftNode{Node: n2, BlsPubKey: *sec2.GetPublicKey()}) return nodes } @@ -138,16 +137,16 @@ func TestValidators(t *testing.T) { vds := newValidators(nodes, 0) assert.True(t, len(nodes) == vds.Len()) - assert.Equal(t, vds.NodeID(0), nodes[0].Node.ID) + assert.Equal(t, vds.NodeID(0), nodes[0].Node.ID()) - validator, err := vds.FindNodeByID(nodes[2].Node.ID) + validator, err := vds.FindNodeByID(nodes[2].Node.ID()) assert.True(t, err == nil, "get node idex fail") assert.True(t, validator.Index == 2) - pubkey, _ := nodes[1].Node.ID.Pubkey() + pubkey := nodes[1].Node.Pubkey() addrN1 := crypto.PubkeyToNodeAddress(*pubkey) - validator, err = vds.FindNodeByID(nodes[1].Node.ID) + validator, err = vds.FindNodeByID(nodes[1].Node.ID()) assert.True(t, err == nil, "get node index and address fail") assert.Equal(t, validator.Address, addrN1) assert.Equal(t, validator.Index, uint32(1)) @@ -159,7 +158,7 @@ func TestValidators(t *testing.T) { nl := vds.NodeList() assert.True(t, len(nl) == vds.Len()) - emptyNodeID := discover.NodeID{} + emptyNodeID := enode.ID{} validator, err = vds.FindNodeByID(emptyNodeID) assert.True(t, validator == nil) assert.True(t, err != nil) @@ -182,10 +181,10 @@ func TestValidators(t *testing.T) { assert.False(t, vds.Equal(vds3)) badNodes := make([]params.CbftNode, 0) - badNode, _ := discover.ParseNode("enode://111164b27aecf5cbbfcd523da7657f126b0a5330a970c8264140704d280e6737fd8098d0ee4299706b825771f3d7017aa02f662e4e9a48e9112d93bf05fea66d@127.0.0.1:16789") + badNode, _ := enode.ParseV4("enode://111164b27aecf5cbbfcd523da7657f126b0a5330a970c8264140704d280e6737fd8098d0ee4299706b825771f3d7017aa02f662e4e9a48e9112d93bf05fea66d@127.0.0.1:16789") var sec bls.SecretKey sec.SetByCSPRNG() - badNodes = append(badNodes, params.CbftNode{Node: *badNode, BlsPubKey: *sec.GetPublicKey()}) + badNodes = append(badNodes, params.CbftNode{Node: badNode, BlsPubKey: *sec.GetPublicKey()}) assert.Panics(t, func() { newValidators(badNodes, 0) }) } @@ -323,7 +322,7 @@ func TestInnerAgency(t *testing.T) { assert.True(t, newVds.ValidBlockNumber == 81) id3 := newVds.NodeID(3) - assert.Equal(t, id3, vmVds.ValidateNodes[3].NodeID) + assert.Equal(t, id3, vmVds.ValidateNodes[3].NodeID.ID()) assert.True(t, agency.GetLastNumber(81) == 120) assert.True(t, agency.Sign(nil) == nil) @@ -427,20 +426,20 @@ func TestValidatorPool(t *testing.T) { nodes := newTestNode() agency := newTestInnerAgency(nodes) - validatorPool := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID) + validatorPool := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID()) assert.False(t, validatorPool.ShouldSwitch(0)) assert.True(t, validatorPool.ShouldSwitch(40)) - node, err := validatorPool.GetValidatorByNodeID(0, nodes[0].Node.ID) + node, err := validatorPool.GetValidatorByNodeID(0, nodes[0].Node.ID()) assert.Nil(t, err) - assert.Equal(t, node.NodeID, nodes[0].Node.ID) + assert.Equal(t, node.NodeID, nodes[0].Node.ID()) - _, err = validatorPool.GetValidatorByNodeID(0, discover.NodeID{}) + _, err = validatorPool.GetValidatorByNodeID(0, enode.ID{}) assert.Equal(t, err, errors.New("not found the node")) node, err = validatorPool.GetValidatorByIndex(0, 1) assert.Nil(t, err) - assert.Equal(t, node.NodeID, nodes[1].Node.ID) + assert.Equal(t, node.NodeID, nodes[1].Node.ID()) _, err = validatorPool.GetValidatorByIndex(0, 5) assert.Equal(t, err, errors.New("not found the specified validator")) @@ -455,31 +454,31 @@ func TestValidatorPool(t *testing.T) { assert.Equal(t, err, errors.New("invalid address")) nodeID := validatorPool.GetNodeIDByIndex(0, 4) - assert.Equal(t, nodeID, discover.NodeID{}) + assert.Equal(t, nodeID, enode.ID{}) nodeID = validatorPool.GetNodeIDByIndex(0, 0) - assert.Equal(t, nodeID, nodes[0].Node.ID) + assert.Equal(t, nodeID, nodes[0].Node.ID()) index, err := validatorPool.GetIndexByNodeID(0, nodeID) assert.Nil(t, err) assert.Equal(t, index, uint32(0)) - index, err = validatorPool.GetIndexByNodeID(0, discover.NodeID{}) + index, err = validatorPool.GetIndexByNodeID(0, enode.ID{}) assert.Equal(t, err, errors.New("not found the specified validator")) assert.Equal(t, index, uint32(0xffffffff)) nl := validatorPool.ValidatorList(0) assert.True(t, len(nl) == len(nodes)) - assert.True(t, validatorPool.IsValidator(0, nodes[0].Node.ID)) + assert.True(t, validatorPool.IsValidator(0, nodes[0].Node.ID())) assert.True(t, validatorPool.Len(0) == len(nodes)) - assert.True(t, validatorPool.IsCandidateNode(discover.NodeID{})) + assert.True(t, validatorPool.IsCandidateNode(enode.ZeroIDv0)) eventMux := &event.TypeMux{} validatorPool.Update(80, 1, eventMux) - assert.True(t, validatorPool.IsValidator(0, nodes[0].Node.ID)) - assert.False(t, validatorPool.IsValidator(1, nodes[0].Node.ID)) + assert.True(t, validatorPool.IsValidator(0, nodes[0].Node.ID())) + assert.False(t, validatorPool.IsValidator(1, nodes[0].Node.ID())) } func TestValidatorPoolVerify(t *testing.T) { @@ -487,33 +486,33 @@ func TestValidatorPoolVerify(t *testing.T) { nodes := make([]params.CbftNode, 0) - n0, _ := discover.ParseNode("enode://e74864b27aecf5cbbfcd523da7657f126b0a5330a970c8264140704d280e6737fd8098d0ee4299706b825771f3d7017aa02f662e4e9a48e9112d93bf05fea66d@127.0.0.1:16789") + n0, _ := enode.ParseV4("enode://e74864b27aecf5cbbfcd523da7657f126b0a5330a970c8264140704d280e6737fd8098d0ee4299706b825771f3d7017aa02f662e4e9a48e9112d93bf05fea66d@127.0.0.1:16789") var sec0 bls.SecretKey sec0.SetByCSPRNG() - n1, _ := discover.ParseNode("enode://bf0cd4c95bc3d48cc7999bcf5b3fe6ab9974fd5dabc5253e3e7506c075d0c7a699251caa76672b144be0fc75fe34cee9aaac20753036b0dbd1cb2b3691f26965@127.0.0.1:26789") + n1, _ := enode.ParseV4("enode://bf0cd4c95bc3d48cc7999bcf5b3fe6ab9974fd5dabc5253e3e7506c075d0c7a699251caa76672b144be0fc75fe34cee9aaac20753036b0dbd1cb2b3691f26965@127.0.0.1:26789") var sec1 bls.SecretKey sec1.SetByCSPRNG() - n2, _ := discover.ParseNode("enode://84c59064dd3b2df54204c52d772cf3809bb0ad6be268843e406f473cef61dacc6d4d4546779dbfa1480deddc64016179ecefdf75d837914f69b679a71ad9711a@127.0.0.1:36789") + n2, _ := enode.ParseV4("enode://84c59064dd3b2df54204c52d772cf3809bb0ad6be268843e406f473cef61dacc6d4d4546779dbfa1480deddc64016179ecefdf75d837914f69b679a71ad9711a@127.0.0.1:36789") var sec2 bls.SecretKey sec2.SetByCSPRNG() - n3, _ := discover.ParseNode("enode://a9b7e60fa1290c1013cb862c0693d9e87113e8d4cb87d60452749acd978c9fd3a80b49ab5ce7916a5bbfe0b0a0d7e4cde201bd59acccdf97006990156bfe73a5@127.0.0.1:46789") + n3, _ := enode.ParseV4("enode://a9b7e60fa1290c1013cb862c0693d9e87113e8d4cb87d60452749acd978c9fd3a80b49ab5ce7916a5bbfe0b0a0d7e4cde201bd59acccdf97006990156bfe73a5@127.0.0.1:46789") var sec3 bls.SecretKey sec3.SetByCSPRNG() priKey, _ := crypto.GenerateKey() nodeStr := fmt.Sprintf("enode://%s@127.0.0.1:6666", hex.EncodeToString(crypto.FromECDSAPub(&priKey.PublicKey)[1:])) - n4, _ := discover.ParseNode(nodeStr) + n4, _ := enode.ParseV4(nodeStr) var sec4 bls.SecretKey sec4.SetByCSPRNG() - nodes = append(nodes, params.CbftNode{Node: *n0, BlsPubKey: *sec0.GetPublicKey()}) - nodes = append(nodes, params.CbftNode{Node: *n1, BlsPubKey: *sec1.GetPublicKey()}) - nodes = append(nodes, params.CbftNode{Node: *n2, BlsPubKey: *sec2.GetPublicKey()}) - nodes = append(nodes, params.CbftNode{Node: *n3, BlsPubKey: *sec3.GetPublicKey()}) - nodes = append(nodes, params.CbftNode{Node: *n4, BlsPubKey: *sec4.GetPublicKey()}) + nodes = append(nodes, params.CbftNode{Node: n0, BlsPubKey: *sec0.GetPublicKey()}) + nodes = append(nodes, params.CbftNode{Node: n1, BlsPubKey: *sec1.GetPublicKey()}) + nodes = append(nodes, params.CbftNode{Node: n2, BlsPubKey: *sec2.GetPublicKey()}) + nodes = append(nodes, params.CbftNode{Node: n3, BlsPubKey: *sec3.GetPublicKey()}) + nodes = append(nodes, params.CbftNode{Node: n4, BlsPubKey: *sec4.GetPublicKey()}) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID()) m := "test sig" @@ -580,13 +579,13 @@ func (m *mockAgency) GetValidator(blockNumber uint64) (*cbfttypes.Validators, er }, nil } -func (m *mockAgency) IsCandidateNode(discover.NodeID) bool { return false } +func (m *mockAgency) IsCandidateNode(dv0 enode.IDv0) bool { return false } func (m *mockAgency) OnCommit(block *types.Block) error { return nil } func TestValidatorPoolReset(t *testing.T) { agency := newMockAgency(100) - vp := NewValidatorPool(agency, 0, 0, discover.NodeID{}) + vp := NewValidatorPool(agency, 0, 0, enode.ID{}) vp.Reset(100, 10) assert.Equal(t, vp.switchPoint, uint64(100)) diff --git a/consensus/cbft/view_change_test.go b/consensus/cbft/view_change_test.go index d27e18445d..86b393a818 100644 --- a/consensus/cbft/view_change_test.go +++ b/consensus/cbft/view_change_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbft import ( @@ -25,9 +24,10 @@ import ( "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/evidence" + "github.com/stretchr/testify/suite" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/stretchr/testify/suite" ) func TestViewChangeSuite(t *testing.T) { @@ -156,7 +156,7 @@ func (suit *ViewChangeTestSuite) TestViewChangeCheckErr() { }, } for _, testcase := range testcases { - if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().NodeID().String(), testcase.data); err == nil { + if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().Node().ID().String(), testcase.data); err == nil { suit.T().Errorf("CASE:%s is failefd", testcase.name) } else { fmt.Println(err.Error()) @@ -170,7 +170,7 @@ func (suit *ViewChangeTestSuite) TestViewChangeCheckCorrect() { suit.insertOneBlock() viewChange := mockViewChange(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.view.secondProposer().state.ViewNumber(), suit.blockOne.Hash(), suit.blockOne.NumberU64(), suit.view.secondProposerIndex(), suit.blockOneQC.BlockQC) - if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().NodeID().String(), viewChange); err != nil { + if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().Node().ID().String(), viewChange); err != nil { suit.T().Fatal(err.Error()) } suit.Equal(1, suit.view.firstProposer().state.ViewChangeLen()) @@ -181,7 +181,7 @@ func (suit *ViewChangeTestSuite) TestViewChangeCheckCorrect() { func (suit *ViewChangeTestSuite) TestViewChangeCheckZero() { viewChange := mockViewChange(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.view.secondProposer().state.ViewNumber(), suit.view.genesisBlock.Hash(), suit.view.genesisBlock.NumberU64(), suit.view.secondProposerIndex(), nil) - if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().NodeID().String(), viewChange); err != nil { + if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().Node().ID().String(), viewChange); err != nil { suit.T().Fatal(err.Error()) } suit.Equal(1, suit.view.firstProposer().state.ViewChangeLen()) @@ -197,7 +197,7 @@ func (suit *ViewChangeTestSuite) TestViewChangeCheckZeroPrepareQCNotNil() { errQC := mockErrBlockQC(notConsensusNodes, block, 8, nil) errViewChange := mockViewChange(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.view.secondProposer().state.ViewNumber(), h, 0, suit.view.firstProposerIndex(), errQC.BlockQC) - if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().NodeID().String(), errViewChange); err == nil { + if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().Node().ID().String(), errViewChange); err == nil { suit.T().Fatal("fail") } } @@ -211,7 +211,7 @@ func (suit *ViewChangeTestSuite) TestViewChangeLeadHighestQCBlock() { suit.view.firstProposer().insertQCBlock(block2, block2QC.BlockQC) viewChange := mockViewChange(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.view.firstProposer().state.ViewNumber(), block2.Hash(), block2.NumberU64(), suit.view.firstProposerIndex(), block2QC.BlockQC) - if err := suit.view.secondProposer().OnViewChange(suit.view.firstProposer().NodeID().String(), viewChange); err != nil { + if err := suit.view.secondProposer().OnViewChange(suit.view.firstProposer().Node().ID().String(), viewChange); err != nil { suit.T().Fatal(err.Error()) } suit.Equal(1, suit.view.secondProposer().state.ViewChangeLen()) @@ -226,7 +226,7 @@ func (suit *ViewChangeTestSuite) TestViewChangeBehindHighestQCBlock() { suit.view.secondProposer().insertQCBlock(block2, block2QC.BlockQC) viewChange := mockViewChange(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.view.firstProposer().state.ViewNumber(), suit.blockOne.Hash(), suit.blockOne.NumberU64(), suit.view.firstProposerIndex(), suit.blockOneQC.BlockQC) - if err := suit.view.secondProposer().OnViewChange(suit.view.firstProposer().NodeID().String(), viewChange); err != nil { + if err := suit.view.secondProposer().OnViewChange(suit.view.firstProposer().Node().ID().String(), viewChange); err != nil { suit.T().Fatal(err.Error()) } suit.Equal(1, suit.view.secondProposer().state.ViewChangeLen()) @@ -239,7 +239,7 @@ func (suit *ViewChangeTestSuite) TestViewChangeViewNumberBehind() { suit.view.secondProposer().state.ResetView(1, 2) viewChange := mockViewChange(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.view.firstProposer().state.ViewNumber(), suit.blockOne.Hash(), suit.blockOne.NumberU64(), suit.view.firstProposerIndex(), suit.blockOneQC.BlockQC) - if err := suit.view.secondProposer().OnViewChange(suit.view.firstProposer().NodeID().String(), viewChange); err == nil { + if err := suit.view.secondProposer().OnViewChange(suit.view.firstProposer().Node().ID().String(), viewChange); err == nil { suit.T().Fatal("FAIL") } else if err.Error() != "viewNumber too low(local:2, msg:0)" { suit.T().Fatal(err.Error()) @@ -253,7 +253,7 @@ func (suit *ViewChangeTestSuite) TestViewChangeViewNumberLead() { suit.insertOneBlock() viewChange := mockViewChange(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.view.firstProposer().state.ViewNumber()+1, suit.blockOne.Hash(), suit.blockOne.NumberU64(), suit.view.firstProposerIndex(), suit.blockOneQC.BlockQC) - if err := suit.view.secondProposer().OnViewChange(suit.view.firstProposer().NodeID().String(), viewChange); err == nil { + if err := suit.view.secondProposer().OnViewChange(suit.view.firstProposer().Node().ID().String(), viewChange); err == nil { suit.T().Fatal("FAIL") } else { suit.EqualValues("viewNumber higher than local(local:0, msg:1)", err.Error()) @@ -268,10 +268,10 @@ func (suit *ViewChangeTestSuite) TestCheckCorrectViewChangeRepeat() { viewChange := mockViewChange(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.view.secondProposer().state.ViewNumber(), suit.blockOne.Hash(), suit.blockOne.NumberU64(), suit.view.secondProposerIndex(), suit.blockOneQC.BlockQC) - if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().NodeID().String(), viewChange); err != nil { + if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().Node().ID().String(), viewChange); err != nil { suit.T().Fatal(err.Error()) } - if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().NodeID().String(), viewChange); err != nil { + if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().Node().ID().String(), viewChange); err != nil { suit.T().Fatal(err.Error()) } suit.Equal(1, suit.view.firstProposer().state.ViewChangeLen()) @@ -288,10 +288,10 @@ func (suit *ViewChangeTestSuite) TestViewChangeRepeatWithDifBlock() { suit.blockOne.Hash(), suit.blockOne.NumberU64(), suit.view.secondProposerIndex(), suit.blockOneQC.BlockQC) viewChange2 := mockViewChange(suit.view.secondProposerBlsKey(), suit.view.Epoch(), suit.view.secondProposer().state.ViewNumber(), suit.view.genesisBlock.Hash(), suit.view.genesisBlock.NumberU64(), suit.view.secondProposerIndex(), nil) - if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().NodeID().String(), viewChange1); err != nil { + if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().Node().ID().String(), viewChange1); err != nil { suit.T().Fatal(err.Error()) } - if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().NodeID().String(), viewChange2); err == nil { + if err := suit.view.firstProposer().OnViewChange(suit.view.secondProposer().Node().ID().String(), viewChange2); err == nil { suit.T().Fatal("fail") } else { reg := regexp.MustCompile(`DuplicateViewChangeEvidence`) @@ -309,7 +309,7 @@ func (suit *ViewChangeTestSuite) TestViewChangeNotConsensus() { suit.insertOneBlock() viewChange := mockViewChange(suit.view.firstProposerBlsKey(), suit.view.Epoch(), suit.view.firstProposer().state.ViewNumber(), suit.blockOne.Hash(), suit.blockOne.NumberU64(), suit.view.firstProposerIndex(), suit.blockOneQC.BlockQC) - if err := notConsensusNodes[0].engine.OnViewChange(suit.view.firstProposer().NodeID().String(), viewChange); err != nil { + if err := notConsensusNodes[0].engine.OnViewChange(suit.view.firstProposer().Node().ID().String(), viewChange); err != nil { suit.T().Error(err.Error()) } } diff --git a/consensus/cbft/wal_bridge.go b/consensus/cbft/wal_bridge.go index 7d1c7187c5..f5b408ed83 100644 --- a/consensus/cbft/wal_bridge.go +++ b/consensus/cbft/wal_bridge.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbft import ( @@ -393,7 +392,7 @@ func (cbft *Cbft) recoveryMsg(msg interface{}) error { return err } if should { - node, err := cbft.validatorPool.GetValidatorByNodeID(m.ViewChange.Epoch, cbft.config.Option.NodeID) + node, err := cbft.validatorPool.GetValidatorByNodeID(m.ViewChange.Epoch, cbft.config.Option.Node.ID()) if err != nil { return err } @@ -444,7 +443,7 @@ func (cbft *Cbft) recoveryMsg(msg interface{}) error { } cbft.state.HadSendPrepareVote().Push(m.Vote) - node, _ := cbft.validatorPool.GetValidatorByNodeID(m.Vote.Epoch, cbft.config.Option.NodeID) + node, _ := cbft.validatorPool.GetValidatorByNodeID(m.Vote.Epoch, cbft.config.Option.Node.ID()) cbft.state.AddPrepareVote(uint32(node.Index), m.Vote) } } diff --git a/consensus/consensus.go b/consensus/consensus.go index e2c85821bb..a1fd4d99fa 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -20,12 +20,13 @@ package consensus import ( "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" "github.com/AlayaNetwork/Alaya-Go/core/state" "github.com/AlayaNetwork/Alaya-Go/core/types" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/rpc" ) @@ -161,7 +162,7 @@ type Agency interface { VerifyHeader(header *types.Header, stateDB *state.StateDB) error GetLastNumber(blockNumber uint64) uint64 GetValidator(blockNumber uint64) (*cbfttypes.Validators, error) - IsCandidateNode(nodeID discover.NodeID) bool + IsCandidateNode(nodeID enode.IDv0) bool OnCommit(block *types.Block) error } @@ -173,7 +174,7 @@ type Bft interface { Start(chain ChainReader, blockCacheWriter BlockCacheWriter, pool TxPoolReset, agency Agency) error // Returns the current consensus node address list. - ConsensusNodes() ([]discover.NodeID, error) + ConsensusNodes() ([]enode.ID, error) // Returns whether the current node is out of the block ShouldSeal(curTime time.Time) (bool, error) @@ -195,5 +196,5 @@ type Bft interface { TracingSwitch(flag int8) // NodeID is temporary. - NodeID() discover.NodeID + Node() *enode.Node } diff --git a/console/console_test.go b/console/console_test.go index 02824a708c..b605532c88 100644 --- a/console/console_test.go +++ b/console/console_test.go @@ -26,12 +26,13 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" "github.com/AlayaNetwork/Alaya-Go/core" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/eth" @@ -110,12 +111,13 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester { //} ethConf := ð.DefaultConfig ethConf.Genesis = core.DefaultGrapeGenesisBlock() - n, _ := discover.ParseNode("enode://73f48a69ae73b85c0a578258954936300b305cb063cbd658d680826ebc0d47cedb890f01f15df2f2e510342d16e7bf5aaf3d7be4ba05a3490de0e9663663addc@127.0.0.1:16789") + + n, _ := enode.ParseV4("enode://73f48a69ae73b85c0a578258954936300b305cb063cbd658d680826ebc0d47cedb890f01f15df2f2e510342d16e7bf5aaf3d7be4ba05a3490de0e9663663addc@127.0.0.1:16789") var nodes []params.CbftNode var blsKey bls.SecretKey blsKey.SetByCSPRNG() - nodes = append(nodes, params.CbftNode{Node: *n, BlsPubKey: *blsKey.GetPublicKey()}) + nodes = append(nodes, params.CbftNode{Node: n, BlsPubKey: *blsKey.GetPublicKey()}) ethConf.Genesis.Config.Cbft = ¶ms.CbftConfig{ InitialNodes: nodes, } diff --git a/core/blockchain_reactor.go b/core/blockchain_reactor.go index bf5e3c5d50..e58c0576f8 100644 --- a/core/blockchain_reactor.go +++ b/core/blockchain_reactor.go @@ -25,13 +25,14 @@ import ( "math/big" "sync" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/common" cvm "github.com/AlayaNetwork/Alaya-Go/common/vm" "github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" "github.com/AlayaNetwork/Alaya-Go/core/state" "github.com/AlayaNetwork/Alaya-Go/core/vm" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/x/handler" "github.com/AlayaNetwork/Alaya-Go/x/staking" "github.com/AlayaNetwork/Alaya-Go/x/xutil" @@ -51,7 +52,7 @@ type BlockChainReactor struct { beginRule []int // Order rules for xxPlugins called in BeginBlocker endRule []int // Order rules for xxPlugins called in EndBlocker validatorMode string // mode: static, inner, ppos - NodeId discover.NodeID // The nodeId of current node + NodeId enode.IDv0 // The nodeId of current node exitCh chan chan struct{} // Used to receive an exit signal exitOnce sync.Once chainID *big.Int @@ -184,7 +185,7 @@ func (bcr *BlockChainReactor) SetPrivateKey(privateKey *ecdsa.PrivateKey) { bcr.vh.SetPrivateKey(privateKey) } plugin.SlashInstance().SetPrivateKey(privateKey) - bcr.NodeId = discover.PubkeyID(&privateKey.PublicKey) + bcr.NodeId = enode.PublicKeyToIDv0(&privateKey.PublicKey) } } @@ -195,7 +196,7 @@ func (bcr *BlockChainReactor) SetEndRule(rule []int) { bcr.endRule = rule } -func (bcr *BlockChainReactor) SetWorkerCoinBase(header *types.Header, nodeId discover.NodeID) { +func (bcr *BlockChainReactor) SetWorkerCoinBase(header *types.Header, nodeId enode.IDv0) { /** this things about ppos @@ -393,7 +394,7 @@ func (bcr *BlockChainReactor) GetValidator(blockNumber uint64) (*cbfttypes.Valid return plugin.StakingInstance().GetValidator(blockNumber) } -func (bcr *BlockChainReactor) IsCandidateNode(nodeID discover.NodeID) bool { +func (bcr *BlockChainReactor) IsCandidateNode(nodeID enode.IDv0) bool { return plugin.StakingInstance().IsCandidateNode(nodeID) } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 433d5bedc5..337fdf4877 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbfttypes import ( @@ -23,11 +22,13 @@ import ( "encoding/json" "errors" "fmt" - "github.com/AlayaNetwork/Alaya-Go/common/hexutil" "math" "math/big" "sort" + "github.com/AlayaNetwork/Alaya-Go/common/hexutil" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" @@ -35,7 +36,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/core/types" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) // Block's Signature info @@ -88,11 +88,11 @@ func (ps *ProducerState) Validate(period int) bool { } type AddValidatorEvent struct { - NodeID discover.NodeID + Node *enode.Node } type RemoveValidatorEvent struct { - NodeID discover.NodeID + Node *enode.Node } type UpdateValidatorEvent struct{} @@ -101,11 +101,11 @@ type ValidateNode struct { Index uint32 `json:"index"` Address common.NodeAddress `json:"address"` PubKey *ecdsa.PublicKey `json:"-"` - NodeID discover.NodeID `json:"nodeID"` + NodeID enode.ID `json:"nodeID"` BlsPubKey *bls.PublicKey `json:"blsPubKey"` } -type ValidateNodeMap map[discover.NodeID]*ValidateNode +type ValidateNodeMap map[enode.ID]*ValidateNode type SortedValidatorNode []*ValidateNode @@ -151,8 +151,8 @@ func (vs *Validators) String() string { return string(b) } -func (vs *Validators) NodeList() []discover.NodeID { - nodeList := make([]discover.NodeID, 0) +func (vs *Validators) NodeList() []enode.ID { + nodeList := make([]enode.ID, 0) for id, _ := range vs.Nodes { nodeList = append(nodeList, id) } @@ -190,7 +190,7 @@ func (vs *Validators) NodeListByBitArray(vSet *utils.BitArray) ([]*ValidateNode, return l, nil } -func (vs *Validators) FindNodeByID(id discover.NodeID) (*ValidateNode, error) { +func (vs *Validators) FindNodeByID(id enode.ID) (*ValidateNode, error) { node, ok := vs.Nodes[id] if ok { return node, nil @@ -218,17 +218,17 @@ func (vs *Validators) FindNodeByAddress(addr common.NodeAddress) (*ValidateNode, return nil, errors.New("invalid address") } -func (vs *Validators) NodeID(idx int) discover.NodeID { +func (vs *Validators) NodeID(idx int) enode.ID { if len(vs.sortedNodes) == 0 { vs.sort() } if idx >= vs.sortedNodes.Len() { - return discover.NodeID{} + return enode.ID{} } return vs.sortedNodes[idx].NodeID } -func (vs *Validators) Index(nodeID discover.NodeID) (uint32, error) { +func (vs *Validators) Index(nodeID enode.ID) (uint32, error) { if node, ok := vs.Nodes[nodeID]; ok { return node.Index, nil } diff --git a/core/genesis_data.go b/core/genesis_data.go index e8da8df8e9..6907d9a76c 100644 --- a/core/genesis_data.go +++ b/core/genesis_data.go @@ -75,7 +75,7 @@ func genesisStakingData(prevHash common.Hash, snapdb snapshotdb.BaseDB, g *Genes } base := &staking.CandidateBase{ - NodeId: node.Node.ID, + NodeId: node.Node.IDv0(), BlsPubKey: keyHex, StakingAddress: xcom.CDFAccount(), BenefitAddress: vm.RewardManagerPoolAddr, diff --git a/core/vm/delegate_reward_contract.go b/core/vm/delegate_reward_contract.go index ea8bed0acf..c1d6807f8f 100644 --- a/core/vm/delegate_reward_contract.go +++ b/core/vm/delegate_reward_contract.go @@ -21,6 +21,8 @@ import ( "math/big" "sort" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/x/gov" "github.com/AlayaNetwork/Alaya-Go/x/staking" "github.com/AlayaNetwork/Alaya-Go/x/xcom" @@ -34,8 +36,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" - "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/x/plugin" ) @@ -161,7 +161,7 @@ func (rc *DelegateRewardContract) withdrawDelegateReward() ([]byte, error) { return txResultHandlerWithRes(vm.DelegateRewardPoolAddr, rc.Evm, FuncNameWithdrawDelegateReward, "", TxWithdrawDelegateReward, int(common.NoErr.Code), reward), nil } -func (rc *DelegateRewardContract) getDelegateReward(address common.Address, nodeIDs []discover.NodeID) ([]byte, error) { +func (rc *DelegateRewardContract) getDelegateReward(address common.Address, nodeIDs []enode.IDv0) ([]byte, error) { state := rc.Evm.StateDB blockNum := rc.Evm.BlockNumber diff --git a/core/vm/delegate_reward_contract_test.go b/core/vm/delegate_reward_contract_test.go index 67d363b819..d9f0bc43f5 100644 --- a/core/vm/delegate_reward_contract_test.go +++ b/core/vm/delegate_reward_contract_test.go @@ -18,12 +18,15 @@ package vm import ( "errors" - "github.com/AlayaNetwork/Alaya-Go/x/gov" - "github.com/AlayaNetwork/Alaya-Go/x/xcom" - "github.com/stretchr/testify/assert" "math/big" "testing" + "github.com/stretchr/testify/assert" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/x/gov" + "github.com/AlayaNetwork/Alaya-Go/x/xcom" + "github.com/AlayaNetwork/Alaya-Go/common/vm" "github.com/AlayaNetwork/Alaya-Go/log" @@ -43,7 +46,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/common/mock" "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/x/staking" "github.com/AlayaNetwork/Alaya-Go/x/xutil" ) @@ -60,7 +62,7 @@ func generateStk(rewardPer uint16, delegateTotal *big.Int, blockNumber uint64) ( if nil != err { panic(err) } - nodeID, add := discover.PubkeyID(&privateKey.PublicKey), crypto.PubkeyToAddress(privateKey.PublicKey) + nodeID, add := enode.PublicKeyToIDv0(&privateKey.PublicKey), crypto.PubkeyToAddress(privateKey.PublicKey) canBase.BenefitAddress = add canBase.NodeId = nodeID canBase.StakingBlockNum = 100 diff --git a/core/vm/gov_contract.go b/core/vm/gov_contract.go index 3e329ee38a..6c8cc595ca 100644 --- a/core/vm/gov_contract.go +++ b/core/vm/gov_contract.go @@ -19,12 +19,13 @@ package vm import ( "math/big" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/x/xutil" "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/common/vm" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/x/gov" "github.com/AlayaNetwork/Alaya-Go/x/plugin" @@ -114,7 +115,7 @@ func (gc *GovContract) CheckGasPrice(gasPrice *big.Int, fcode uint16) error { return nil } -func (gc *GovContract) submitText(verifier discover.NodeID, pipID string) ([]byte, error) { +func (gc *GovContract) submitText(verifier enode.IDv0, pipID string) ([]byte, error) { from := gc.Contract.CallerAddress blockNumber := gc.Evm.BlockNumber.Uint64() blockHash := gc.Evm.BlockHash @@ -149,7 +150,7 @@ func (gc *GovContract) submitText(verifier discover.NodeID, pipID string) ([]byt return gc.nonCallHandler("submitText", SubmitText, err) } -func (gc *GovContract) submitVersion(verifier discover.NodeID, pipID string, newVersion uint32, endVotingRounds uint64) ([]byte, error) { +func (gc *GovContract) submitVersion(verifier enode.IDv0, pipID string, newVersion uint32, endVotingRounds uint64) ([]byte, error) { from := gc.Contract.CallerAddress blockNumber := gc.Evm.BlockNumber.Uint64() @@ -191,7 +192,7 @@ func (gc *GovContract) submitVersion(verifier discover.NodeID, pipID string, new return gc.nonCallHandler("submitVersion", SubmitVersion, err) } -func (gc *GovContract) submitCancel(verifier discover.NodeID, pipID string, endVotingRounds uint64, tobeCanceledProposalID common.Hash) ([]byte, error) { +func (gc *GovContract) submitCancel(verifier enode.IDv0, pipID string, endVotingRounds uint64, tobeCanceledProposalID common.Hash) ([]byte, error) { from := gc.Contract.CallerAddress blockNumber := gc.Evm.BlockNumber.Uint64() @@ -232,7 +233,7 @@ func (gc *GovContract) submitCancel(verifier discover.NodeID, pipID string, endV return gc.nonCallHandler("submitCancel", SubmitCancel, err) } -func (gc *GovContract) submitParam(verifier discover.NodeID, pipID string, module, name, newValue string) ([]byte, error) { +func (gc *GovContract) submitParam(verifier enode.IDv0, pipID string, module, name, newValue string) ([]byte, error) { from := gc.Contract.CallerAddress blockNumber := gc.Evm.BlockNumber.Uint64() blockHash := gc.Evm.BlockHash @@ -274,7 +275,7 @@ func (gc *GovContract) submitParam(verifier discover.NodeID, pipID string, modul return gc.nonCallHandler("submitParam", SubmitParam, err) } -func (gc *GovContract) vote(verifier discover.NodeID, proposalID common.Hash, op uint8, programVersion uint32, programVersionSign common.VersionSign) ([]byte, error) { +func (gc *GovContract) vote(verifier enode.IDv0, proposalID common.Hash, op uint8, programVersion uint32, programVersionSign common.VersionSign) ([]byte, error) { from := gc.Contract.CallerAddress blockNumber := gc.Evm.BlockNumber.Uint64() blockHash := gc.Evm.BlockHash @@ -310,7 +311,7 @@ func (gc *GovContract) vote(verifier discover.NodeID, proposalID common.Hash, op return gc.nonCallHandler("vote", Vote, err) } -func (gc *GovContract) declareVersion(activeNode discover.NodeID, programVersion uint32, programVersionSign common.VersionSign) ([]byte, error) { +func (gc *GovContract) declareVersion(activeNode enode.IDv0, programVersion uint32, programVersionSign common.VersionSign) ([]byte, error) { from := gc.Contract.CallerAddress blockNumber := gc.Evm.BlockNumber.Uint64() blockHash := gc.Evm.BlockHash diff --git a/core/vm/gov_contract_test.go b/core/vm/gov_contract_test.go index edcb6b1b82..9edf2c964d 100644 --- a/core/vm/gov_contract_test.go +++ b/core/vm/gov_contract_test.go @@ -22,6 +22,8 @@ import ( "math/big" "testing" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + //"github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/x/xutil" @@ -31,7 +33,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/AlayaNetwork/Alaya-Go/common/mock" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/x/gov" "github.com/AlayaNetwork/Alaya-Go/common" @@ -104,7 +105,7 @@ func buildSubmitTextInput() []byte { return common.MustRlpEncode(input) } -func buildSubmitText(nodeID discover.NodeID, pipID string) []byte { +func buildSubmitText(nodeID enode.IDv0, pipID string) []byte { var input [][]byte input = make([][]byte, 0) input = append(input, common.MustRlpEncode(uint16(2000))) // func type code @@ -114,7 +115,7 @@ func buildSubmitText(nodeID discover.NodeID, pipID string) []byte { return common.MustRlpEncode(input) } -func buildSubmitParam(nodeID discover.NodeID, pipID string, module, name, newValue string) []byte { +func buildSubmitParam(nodeID enode.IDv0, pipID string, module, name, newValue string) []byte { var input [][]byte input = make([][]byte, 0) input = append(input, common.MustRlpEncode(uint16(2002))) // func type code @@ -139,7 +140,7 @@ func buildSubmitVersionInput() []byte { return common.MustRlpEncode(input) } -func buildSubmitVersion(nodeID discover.NodeID, pipID string, newVersion uint32, endVotingRounds uint64) []byte { +func buildSubmitVersion(nodeID enode.IDv0, pipID string, newVersion uint32, endVotingRounds uint64) []byte { var input [][]byte input = make([][]byte, 0) input = append(input, common.MustRlpEncode(uint16(2001))) // func type code @@ -162,7 +163,7 @@ func buildSubmitCancelInput() []byte { return common.MustRlpEncode(input) } -func buildSubmitCancel(nodeID discover.NodeID, pipID string, endVotingRounds uint64, tobeCanceledProposalID common.Hash) []byte { +func buildSubmitCancel(nodeID enode.IDv0, pipID string, endVotingRounds uint64, tobeCanceledProposalID common.Hash) []byte { var input [][]byte input = make([][]byte, 0) input = append(input, common.MustRlpEncode(uint16(2005))) // func type code @@ -209,7 +210,7 @@ func buildDeclareInput() []byte { return common.MustRlpEncode(input) } -func buildDeclare(nodeID discover.NodeID, declaredVersion uint32, sign common.VersionSign) []byte { +func buildDeclare(nodeID enode.IDv0, declaredVersion uint32, sign common.VersionSign) []byte { var input [][]byte input = make([][]byte, 0) input = append(input, common.MustRlpEncode(uint16(2004))) // func type code @@ -371,7 +372,7 @@ func TestGovContract_SubmitText_Proposal_Empty(t *testing.T) { chain := setup(t) defer clear(chain, t) - runGovContract(false, gc, buildSubmitText(discover.ZeroNodeID, "pipid1"), t, gov.ProposerEmpty) + runGovContract(false, gc, buildSubmitText(enode.ZeroIDv0, "pipid1"), t, gov.ProposerEmpty) } func TestGovContract_ListGovernParam(t *testing.T) { @@ -494,7 +495,7 @@ func TestGovContract_voteTwoProposal_punished(t *testing.T) { commit_sndb(chain) prepair_sndb(chain, txHashArr[5]) - punished := make(map[discover.NodeID]struct{}) + punished := make(map[enode.IDv0]struct{}) currentValidatorList, _ := plugin.StakingInstance().ListCurrentValidatorID(chain.CurrentHeader().Hash(), chain.CurrentHeader().Number.Uint64()) // punish last one @@ -1629,7 +1630,7 @@ func runGovContract(callType bool, contract *GovContract, buf []byte, t *testing func Test_ResetVoteOption(t *testing.T) { v := gov.VoteInfo{} v.ProposalID = common.ZeroHash - v.VoteNodeID = discover.NodeID{} + v.VoteNodeID = enode.ZeroIDv0 v.VoteOption = gov.Abstention t.Log(v) diff --git a/core/vm/platon_contract_test.go b/core/vm/platon_contract_test.go index 6e86fd2973..23038acbf1 100644 --- a/core/vm/platon_contract_test.go +++ b/core/vm/platon_contract_test.go @@ -24,6 +24,8 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/common" @@ -34,7 +36,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/AlayaNetwork/Alaya-Go/x/gov" "github.com/AlayaNetwork/Alaya-Go/x/plugin" @@ -51,32 +52,32 @@ func init() { const initGas = 10000000 var ( - nodeIdArr = []discover.NodeID{ - discover.MustHexID("0x362003c50ed3a523cdede37a001803b8f0fed27cb402b3d6127a1a96661ec202318f68f4c76d9b0bfbabfd551a178d4335eaeaa9b7981a4df30dfc8c0bfe3384"), - discover.MustHexID("0xced880d4769331f47af07a8d1b79de1e40c95a37ea1890bb9d3f0da8349e1a7c0ea4cadbb9c5bf185b051061eef8e5eadca251c24e1db1d9faf0fb24cbd06f9a"), - discover.MustHexID("0xda56501a77fc1dfe0399b81f3909061d9a176cb9433fab4d3dfb1a10344c243274e38155e18878c7a0b3fcdd6182000c7784a95e2c4d9e0691ce67798624786e"), - discover.MustHexID("0x89a4409abe1ace8b77c4497c2073a8a2046dbdabb58c8bb58fe73926bbdc572fb848d739b1d2d09dd0796abcc1ed8d9a33bb3ef0a6c2e106e408090df179b041"), - discover.MustHexID("0x65e2ab09161e32e6d07d82adaa416ee6d41d617c52db20e3145a4d1b7d396af38d095c87508ad5bb35df741513bdc4bf12fec215e58450e255f05d194d41d089"), - discover.MustHexID("0x9bfacd628f3adb0f94e8b3968064d5248fa18efa75c680fdffea3af2575406461f3395817dd2a1be07a79bd81ffa00f57ad82286061d4a6caceece048e352380"), - discover.MustHexID("0x1e07d66b56bbc931ddce7cc5b9f55672d7fe4e19897a42f19d4ad7c969435cad652d720401d68f5769e245ec0f4e23362c8b1b062771d614876fdbb875ba9d44"), - discover.MustHexID("0x11a315747ce79cdf3d6aaf87ff2b6897950a20bda281838f922ea9407736fec9029d85f6202fd059a57a9119d05895402e7570948ae759cb093a54c3da9e0a4a"), - discover.MustHexID("0x248af08a775ff63a47a5970e4928bcccd1a8cef984fd4142ea7f89cd13015bdab9ca4a8c5e1070dc00fa81a047542f53ca596f553c4acfb7abe75a8fb5019057"), - discover.MustHexID("0xfd790ff5dc48baccb9418ce5cfac6a10c3646f20a3fe32d9502c4edce3a77fa90bfee0361d8a72093b7994f8cbc28ee537bdda2b634c5966b1a9253d9d270145"), - discover.MustHexID("0x56d243db84a521cb204f582ee84bca7f4af29437dd447a6e36d17f4853888e05343844bd64294b99b835ca7f72ef5b1325ef1c89b0c5c2744154cdadf7c4e9fa"), - discover.MustHexID("0x8796a6fcefd9037d8433e3a959ff8f3c4552a482ce727b00a90bfd1ec365ce2faa33e19aa6a172b5c186b51f5a875b5acd35063171f0d9501a9c8f1c98513825"), - discover.MustHexID("0x547b876036165d66274ce31692165c8acb6f140a65cab0e0e12f1f09d1c7d8d53decf997830919e4f5cacb2df1adfe914c53d22e3ab284730b78f5c63a273b8c"), - discover.MustHexID("0x9fdbeb873bea2557752eabd2c96419b8a700b680716081472601ddf7498f0db9b8a40797b677f2fac541031f742c2bbd110ff264ae3400bf177c456a76a93d42"), - discover.MustHexID("0xc553783799bfef7c34a84b2737f2c77f8f2c5cfedc3fd7af2d944da6ece90aa94cf621e6de5c4495881fbfc9beec655ffb10e39cb4ca9be7768d284409040f32"), - discover.MustHexID("0x75ad2ee8ca77619c3ba0ddcec5dab1375fe4fa90bab9e751caef3996ce082dfed32fe4c137401ee05e501c079b2e4400397b09de14b08b09c9e7f9698e9e4f0a"), - discover.MustHexID("0xdb18af9be2af9dff2347c3d06db4b1bada0598d099a210275251b68fa7b5a863d47fcdd382cc4b3ea01e5b55e9dd0bdbce654133b7f58928ce74629d5e68b974"), - discover.MustHexID("0x472d19e5e9888368c02f24ebbbe0f2132096e7183d213ab65d96b8c03205f88398924af8876f3c615e08aa0f9a26c38911fda26d51c602c8d4f8f3cb866808d7"), - discover.MustHexID("4f1f036e5e18cc812347d5073cbec2a8da7930de323063c39b0d4413a396e088bfa90e8c28174313d8d82e9a14bc0884b13a48fc28e619e44c48a49b4fd9f107"), - discover.MustHexID("f18c596232d637409c6295abb1e720db99ffc12363a1eb8123d6f54af80423a5edd06f91115115a1dca1377e97b9031e2ddb864d34d9b3491d6fa07e8d9b951b"), - discover.MustHexID("7a8f7a28ac1c4eaf98b2be890f372e5abc58ebe6d3aab47aedcb0076e34eb42882e926676ebab327a4ef4e2ea5c4296e9c7bc0991360cb44f52672631012db1b"), - discover.MustHexID("9eeb448babf9e93449e831b91f98d9cbc0c2324fe8c43baac69d090717454f3f930713084713fe3a9f01e4ca59b80a0f2b41dbd6d531f414650bab0363e3691a"), - discover.MustHexID("cc1d7314c15e30dc5587f675eb5f803b1a2d88bfe76cec591cec1ff678bc6abce98f40054325bdcb44fb83174f27d38a54fbce4846af8f027b333868bc5144a4"), - discover.MustHexID("e4d99694be2fc8a53d8c2446f947aec1c7de3ee26f7cd43f4f6f77371f56f11156218dec32b51ddce470e97127624d330bb7a3237ba5f0d87d2d3166faf1035e"), - discover.MustHexID("9c61f59f70296b6d494e7230888e58f19b13c5c6c85562e57e1fe02d0ff872b4957238c73559d017c8770b999891056aa6329dbf628bc19028d8f4d35ec35823"), + nodeIdArr = []enode.IDv0{ + enode.MustHexIDv0("0x362003c50ed3a523cdede37a001803b8f0fed27cb402b3d6127a1a96661ec202318f68f4c76d9b0bfbabfd551a178d4335eaeaa9b7981a4df30dfc8c0bfe3384"), + enode.MustHexIDv0("0xced880d4769331f47af07a8d1b79de1e40c95a37ea1890bb9d3f0da8349e1a7c0ea4cadbb9c5bf185b051061eef8e5eadca251c24e1db1d9faf0fb24cbd06f9a"), + enode.MustHexIDv0("0xda56501a77fc1dfe0399b81f3909061d9a176cb9433fab4d3dfb1a10344c243274e38155e18878c7a0b3fcdd6182000c7784a95e2c4d9e0691ce67798624786e"), + enode.MustHexIDv0("0x89a4409abe1ace8b77c4497c2073a8a2046dbdabb58c8bb58fe73926bbdc572fb848d739b1d2d09dd0796abcc1ed8d9a33bb3ef0a6c2e106e408090df179b041"), + enode.MustHexIDv0("0x65e2ab09161e32e6d07d82adaa416ee6d41d617c52db20e3145a4d1b7d396af38d095c87508ad5bb35df741513bdc4bf12fec215e58450e255f05d194d41d089"), + enode.MustHexIDv0("0x9bfacd628f3adb0f94e8b3968064d5248fa18efa75c680fdffea3af2575406461f3395817dd2a1be07a79bd81ffa00f57ad82286061d4a6caceece048e352380"), + enode.MustHexIDv0("0x1e07d66b56bbc931ddce7cc5b9f55672d7fe4e19897a42f19d4ad7c969435cad652d720401d68f5769e245ec0f4e23362c8b1b062771d614876fdbb875ba9d44"), + enode.MustHexIDv0("0x11a315747ce79cdf3d6aaf87ff2b6897950a20bda281838f922ea9407736fec9029d85f6202fd059a57a9119d05895402e7570948ae759cb093a54c3da9e0a4a"), + enode.MustHexIDv0("0x248af08a775ff63a47a5970e4928bcccd1a8cef984fd4142ea7f89cd13015bdab9ca4a8c5e1070dc00fa81a047542f53ca596f553c4acfb7abe75a8fb5019057"), + enode.MustHexIDv0("0xfd790ff5dc48baccb9418ce5cfac6a10c3646f20a3fe32d9502c4edce3a77fa90bfee0361d8a72093b7994f8cbc28ee537bdda2b634c5966b1a9253d9d270145"), + enode.MustHexIDv0("0x56d243db84a521cb204f582ee84bca7f4af29437dd447a6e36d17f4853888e05343844bd64294b99b835ca7f72ef5b1325ef1c89b0c5c2744154cdadf7c4e9fa"), + enode.MustHexIDv0("0x8796a6fcefd9037d8433e3a959ff8f3c4552a482ce727b00a90bfd1ec365ce2faa33e19aa6a172b5c186b51f5a875b5acd35063171f0d9501a9c8f1c98513825"), + enode.MustHexIDv0("0x547b876036165d66274ce31692165c8acb6f140a65cab0e0e12f1f09d1c7d8d53decf997830919e4f5cacb2df1adfe914c53d22e3ab284730b78f5c63a273b8c"), + enode.MustHexIDv0("0x9fdbeb873bea2557752eabd2c96419b8a700b680716081472601ddf7498f0db9b8a40797b677f2fac541031f742c2bbd110ff264ae3400bf177c456a76a93d42"), + enode.MustHexIDv0("0xc553783799bfef7c34a84b2737f2c77f8f2c5cfedc3fd7af2d944da6ece90aa94cf621e6de5c4495881fbfc9beec655ffb10e39cb4ca9be7768d284409040f32"), + enode.MustHexIDv0("0x75ad2ee8ca77619c3ba0ddcec5dab1375fe4fa90bab9e751caef3996ce082dfed32fe4c137401ee05e501c079b2e4400397b09de14b08b09c9e7f9698e9e4f0a"), + enode.MustHexIDv0("0xdb18af9be2af9dff2347c3d06db4b1bada0598d099a210275251b68fa7b5a863d47fcdd382cc4b3ea01e5b55e9dd0bdbce654133b7f58928ce74629d5e68b974"), + enode.MustHexIDv0("0x472d19e5e9888368c02f24ebbbe0f2132096e7183d213ab65d96b8c03205f88398924af8876f3c615e08aa0f9a26c38911fda26d51c602c8d4f8f3cb866808d7"), + enode.MustHexIDv0("4f1f036e5e18cc812347d5073cbec2a8da7930de323063c39b0d4413a396e088bfa90e8c28174313d8d82e9a14bc0884b13a48fc28e619e44c48a49b4fd9f107"), + enode.MustHexIDv0("f18c596232d637409c6295abb1e720db99ffc12363a1eb8123d6f54af80423a5edd06f91115115a1dca1377e97b9031e2ddb864d34d9b3491d6fa07e8d9b951b"), + enode.MustHexIDv0("7a8f7a28ac1c4eaf98b2be890f372e5abc58ebe6d3aab47aedcb0076e34eb42882e926676ebab327a4ef4e2ea5c4296e9c7bc0991360cb44f52672631012db1b"), + enode.MustHexIDv0("9eeb448babf9e93449e831b91f98d9cbc0c2324fe8c43baac69d090717454f3f930713084713fe3a9f01e4ca59b80a0f2b41dbd6d531f414650bab0363e3691a"), + enode.MustHexIDv0("cc1d7314c15e30dc5587f675eb5f803b1a2d88bfe76cec591cec1ff678bc6abce98f40054325bdcb44fb83174f27d38a54fbce4846af8f027b333868bc5144a4"), + enode.MustHexIDv0("e4d99694be2fc8a53d8c2446f947aec1c7de3ee26f7cd43f4f6f77371f56f11156218dec32b51ddce470e97127624d330bb7a3237ba5f0d87d2d3166faf1035e"), + enode.MustHexIDv0("9c61f59f70296b6d494e7230888e58f19b13c5c6c85562e57e1fe02d0ff872b4957238c73559d017c8770b999891056aa6329dbf628bc19028d8f4d35ec35823"), } addrArr = []common.Address{ @@ -770,19 +771,19 @@ func build_staking_data_more(chain *mock.Chain) { balance = new(big.Int).Add(balance, big.NewInt(int64(weight))) - randBuildFunc := func() (discover.NodeID, common.Address, error) { + randBuildFunc := func() (enode.IDv0, common.Address, error) { privateKey, err := crypto.GenerateKey() if nil != err { fmt.Printf("Failed to generate random NodeId private key: %v", err) - return discover.NodeID{}, common.ZeroAddr, err + return enode.IDv0{}, common.ZeroAddr, err } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) privateKey, err = crypto.GenerateKey() if nil != err { fmt.Printf("Failed to generate random Address private key: %v", err) - return discover.NodeID{}, common.ZeroAddr, err + return enode.IDv0{}, common.ZeroAddr, err } addr := crypto.PubkeyToAddress(privateKey.PublicKey) @@ -790,7 +791,7 @@ func build_staking_data_more(chain *mock.Chain) { return nodeId, addr, nil } - var nodeId discover.NodeID + var nodeId enode.IDv0 var addr common.Address if i < 25 { diff --git a/core/vm/slashing_contract.go b/core/vm/slashing_contract.go index de6d9f3b34..586cb0c4a5 100644 --- a/core/vm/slashing_contract.go +++ b/core/vm/slashing_contract.go @@ -20,7 +20,7 @@ import ( "fmt" "math/big" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/common/consensus" @@ -114,7 +114,7 @@ func (sc *SlashingContract) reportDuplicateSign(dupType uint8, data string) ([]b } // Check if the node has double sign behavior at a certain block height -func (sc *SlashingContract) checkDuplicateSign(dupType uint8, nodeId discover.NodeID, blockNumber uint64) ([]byte, error) { +func (sc *SlashingContract) checkDuplicateSign(dupType uint8, nodeId enode.IDv0, blockNumber uint64) ([]byte, error) { log.Info("checkDuplicateSign exist", "blockNumber", blockNumber, "nodeId", nodeId.TerminalString(), "dupType", dupType) txHash, err := sc.Plugin.CheckDuplicateSign(nodeId, blockNumber, consensus.EvidenceType(dupType), sc.Evm.StateDB) var data string diff --git a/core/vm/slashing_contract_test.go b/core/vm/slashing_contract_test.go index aac8534a85..4755162b19 100644 --- a/core/vm/slashing_contract_test.go +++ b/core/vm/slashing_contract_test.go @@ -22,6 +22,8 @@ import ( "math/big" "testing" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/evidence" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" @@ -29,7 +31,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/common/hexutil" "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/AlayaNetwork/Alaya-Go/x/plugin" "github.com/AlayaNetwork/Alaya-Go/x/staking" @@ -44,7 +45,7 @@ func TestSlashingContract_ReportMutiSign(t *testing.T) { t.Fatal(err) } addr := common.MustBech32ToAddress("atx1r9tx0n00etv5c5smmlctlpg8jas7p78nmnfw8v") - nodeId, err := discover.HexID("51c0559c065400151377d71acd7a17282a7c8abcfefdb11992dcecafde15e100b8e31e1a5e74834a04792d016f166c80b9923423fe280570e8131debf591d483") + nodeId, err := enode.HexIDv0("51c0559c065400151377d71acd7a17282a7c8abcfefdb11992dcecafde15e100b8e31e1a5e74834a04792d016f166c80b9923423fe280570e8131debf591d483") if nil != err { t.Fatal(err) } @@ -173,7 +174,7 @@ func TestSlashingContract_CheckMutiSign(t *testing.T) { var params [][]byte params = make([][]byte, 0) - nodeId, err := discover.HexID("51c0559c065400151377d71acd7a17282a7c8abcfefdb11992dcecafde15e100b8e31e1a5e74834a04792d016f166c80b9923423fe280570e8131debf591d483") + nodeId, err := enode.HexIDv0("51c0559c065400151377d71acd7a17282a7c8abcfefdb11992dcecafde15e100b8e31e1a5e74834a04792d016f166c80b9923423fe280570e8131debf591d483") if nil != err { t.Fatal(err) } diff --git a/core/vm/staking_contract.go b/core/vm/staking_contract.go index 8c484a902f..689646cda0 100644 --- a/core/vm/staking_contract.go +++ b/core/vm/staking_contract.go @@ -22,6 +22,8 @@ import ( "math" "math/big" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/x/reward" "github.com/AlayaNetwork/Alaya-Go/x/xcom" @@ -40,7 +42,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/common/vm" "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/x/plugin" "github.com/AlayaNetwork/Alaya-Go/x/staking" "github.com/AlayaNetwork/Alaya-Go/x/xutil" @@ -117,7 +118,7 @@ func (stkc *StakingContract) FnSigns() map[uint16]interface{} { } } -func (stkc *StakingContract) createStaking(typ uint16, benefitAddress common.Address, nodeId discover.NodeID, +func (stkc *StakingContract) createStaking(typ uint16, benefitAddress common.Address, nodeId enode.IDv0, externalId, nodeName, website, details string, amount *big.Int, rewardPer uint16, programVersion uint32, programVersionSign common.VersionSign, blsPubKey bls.PublicKeyHex, blsProof bls.SchnorrProofHex) ([]byte, error) { @@ -332,7 +333,7 @@ func verifyRewardPer(rewardPer uint16) bool { return rewardPer <= 10000 // 1BP(BasePoint)=0.01% } -func (stkc *StakingContract) editCandidate(benefitAddress *common.Address, nodeId discover.NodeID, rewardPer *uint16, +func (stkc *StakingContract) editCandidate(benefitAddress *common.Address, nodeId enode.IDv0, rewardPer *uint16, externalId, nodeName, website, details *string) ([]byte, error) { txHash := stkc.Evm.StateDB.TxHash() @@ -500,7 +501,7 @@ func (stkc *StakingContract) editCandidate(benefitAddress *common.Address, nodeI "", TxEditorCandidate, common.NoErr) } -func (stkc *StakingContract) increaseStaking(nodeId discover.NodeID, typ uint16, amount *big.Int) ([]byte, error) { +func (stkc *StakingContract) increaseStaking(nodeId enode.IDv0, typ uint16, amount *big.Int) ([]byte, error) { txHash := stkc.Evm.StateDB.TxHash() blockNumber := stkc.Evm.BlockNumber @@ -574,7 +575,7 @@ func (stkc *StakingContract) increaseStaking(nodeId discover.NodeID, typ uint16, "", TxIncreaseStaking, common.NoErr) } -func (stkc *StakingContract) withdrewStaking(nodeId discover.NodeID) ([]byte, error) { +func (stkc *StakingContract) withdrewStaking(nodeId enode.IDv0) ([]byte, error) { txHash := stkc.Evm.StateDB.TxHash() blockNumber := stkc.Evm.BlockNumber @@ -639,7 +640,7 @@ func (stkc *StakingContract) withdrewStaking(nodeId discover.NodeID) ([]byte, er "", TxWithdrewCandidate, common.NoErr) } -func (stkc *StakingContract) delegate(typ uint16, nodeId discover.NodeID, amount *big.Int) ([]byte, error) { +func (stkc *StakingContract) delegate(typ uint16, nodeId enode.IDv0, amount *big.Int) ([]byte, error) { txHash := stkc.Evm.StateDB.TxHash() blockNumber := stkc.Evm.BlockNumber @@ -767,7 +768,7 @@ func (stkc *StakingContract) delegate(typ uint16, nodeId discover.NodeID, amount "", TxDelegate, common.NoErr) } -func (stkc *StakingContract) withdrewDelegation(stakingBlockNum uint64, nodeId discover.NodeID, amount *big.Int) ([]byte, error) { +func (stkc *StakingContract) withdrewDelegation(stakingBlockNum uint64, nodeId enode.IDv0, amount *big.Int) ([]byte, error) { txHash := stkc.Evm.StateDB.TxHash() blockNumber := stkc.Evm.BlockNumber @@ -934,7 +935,7 @@ func (stkc *StakingContract) getRelatedListByDelAddr(addr common.Address) ([]byt } func (stkc *StakingContract) getDelegateInfo(stakingBlockNum uint64, delAddr common.Address, - nodeId discover.NodeID) ([]byte, error) { + nodeId enode.IDv0) ([]byte, error) { blockNumber := stkc.Evm.BlockNumber blockHash := stkc.Evm.BlockHash @@ -957,7 +958,7 @@ func (stkc *StakingContract) getDelegateInfo(stakingBlockNum uint64, delAddr com del, nil), nil } -func (stkc *StakingContract) getCandidateInfo(nodeId discover.NodeID) ([]byte, error) { +func (stkc *StakingContract) getCandidateInfo(nodeId enode.IDv0) ([]byte, error) { blockNumber := stkc.Evm.BlockNumber blockHash := stkc.Evm.BlockHash diff --git a/core/vm/staking_contract_whitebox_test.go b/core/vm/staking_contract_whitebox_test.go index 572626b882..0d62090555 100644 --- a/core/vm/staking_contract_whitebox_test.go +++ b/core/vm/staking_contract_whitebox_test.go @@ -23,11 +23,12 @@ import ( "math/big" "testing" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/stretchr/testify/assert" "github.com/AlayaNetwork/Alaya-Go/x/xcom" @@ -894,7 +895,7 @@ func Test_CreateStake_by_InvalidNodeId(t *testing.T) { // //0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff //0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - nid := discover.MustHexID("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + nid := enode.MustHexIDv0("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") nodeId, _ := rlp.EncodeToBytes(nid) externalId, _ := rlp.EncodeToBytes("xssssddddffffggggg") diff --git a/core/vm/validator_inner_contract.go b/core/vm/validator_inner_contract.go index 858831949f..4a54fc62f8 100644 --- a/core/vm/validator_inner_contract.go +++ b/core/vm/validator_inner_contract.go @@ -20,6 +20,8 @@ import ( "errors" "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "bytes" "encoding/json" @@ -30,7 +32,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -46,8 +47,9 @@ const ( ) type ValidateNode struct { - Index uint `json:"index"` - NodeID discover.NodeID `json:"nodeID"` + Index uint `json:"index"` + //todo do not change + NodeID enode.IDv0 `json:"nodeID"` Address common.NodeAddress `json:"-"` BlsPubKey bls.PublicKey `json:"blsPubKey"` } diff --git a/crypto/crypto.go b/crypto/crypto.go index cbebf1036b..5a4a1b705e 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -233,3 +233,9 @@ func zeroBytes(bytes []byte) { bytes[i] = 0 } } + +const PubkeyBytesBits = 512 + +// PubkeyBytes is a unique identifier for each node. +// The node identifier is a marshaled elliptic curve public key. +type PubkeyBytes [PubkeyBytesBits / 8]byte diff --git a/eth/backend.go b/eth/backend.go index 292fa938d9..b1ccb59018 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -56,7 +56,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/miner" "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/rpc" xplugin "github.com/AlayaNetwork/Alaya-Go/x/plugin" @@ -370,6 +369,8 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { log.Error("Init cbft consensus engine fail", "error", err) return nil, errors.New("Failed to init cbft consensus engine") } + } else { + log.Crit("engin not good") } // Permit the downloader to use the trie cache allowance during fast sync @@ -610,7 +611,7 @@ func (s *Ethereum) Start(srvr *p2p.Server) error { for _, n := range s.chainConfig.Cbft.InitialNodes { // todo: Mock point. if !node.FakeNetEnable { - srvr.AddConsensusPeer(discover.NewNode(n.Node.ID, n.Node.IP, n.Node.UDP, n.Node.TCP)) + srvr.AddConsensusPeer(n.Node) } } } @@ -662,4 +663,4 @@ func handlePlugin(reactor *core.BlockChainReactor) { reactor.SetBeginRule([]int{xcom.StakingRule, xcom.SlashingRule, xcom.CollectDeclareVersionRule, xcom.GovernanceRule}) reactor.SetEndRule([]int{xcom.CollectDeclareVersionRule, xcom.RestrictingRule, xcom.RewardRule, xcom.GovernanceRule, xcom.StakingRule}) -} \ No newline at end of file +} diff --git a/eth/handler.go b/eth/handler.go index 8d46a181f7..de64a115d4 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -28,6 +28,8 @@ import ( "sync/atomic" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/trie" "github.com/syndtr/goleveldb/leveldb/iterator" @@ -43,7 +45,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -151,7 +152,7 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne NodeInfo: func() interface{} { return manager.NodeInfo() }, - PeerInfo: func(id discover.NodeID) interface{} { + PeerInfo: func(id enode.ID) interface{} { if p := manager.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil { return p.Info() } diff --git a/eth/helper_test.go b/eth/helper_test.go index 267935a2a6..a7e6f49119 100644 --- a/eth/helper_test.go +++ b/eth/helper_test.go @@ -27,6 +27,8 @@ import ( "sync" "testing" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/core/rawdb" "github.com/AlayaNetwork/Alaya-Go/consensus" @@ -40,7 +42,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/ethdb" "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" _ "github.com/AlayaNetwork/Alaya-Go/x/xcom" ) @@ -191,7 +192,7 @@ func newTestPeer(name string, version int, pm *ProtocolManager, shake bool) (*te app, net := p2p.MsgPipe() // Start the peer on a new thread - var id discover.NodeID + var id enode.ID rand.Read(id[:]) peer := pm.newPeer(version, p2p.NewPeer(id, name, nil), net) diff --git a/eth/sync.go b/eth/sync.go index 7ed48a92b8..743406b9fb 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -22,11 +22,12 @@ import ( "sync/atomic" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/core/types" "github.com/AlayaNetwork/Alaya-Go/eth/downloader" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) const ( @@ -66,7 +67,7 @@ func (pm *ProtocolManager) syncTransactions(p *peer) { func (pm *ProtocolManager) txsyncLoop() { defer pm.wg.Done() var ( - pending = make(map[discover.NodeID]*txsync) + pending = make(map[enode.ID]*txsync) sending = false // whether a send is active pack = new(txsync) // the pack that is being sent done = make(chan error, 1) // result of the send @@ -215,8 +216,6 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp { return nil // Sync already running. } - log.Debug("what the fuck1") - // Ensure we're at mininum peer count. minPeers := defaultMinSyncPeers if cs.forced { @@ -228,8 +227,6 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp { return nil } - log.Debug("what the fuck2") - // We have enough peers, check TD. peer := cs.pm.peers.BestPeer() if peer == nil { @@ -245,16 +242,12 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp { return nil } - log.Debug("what the fuck3") - mode := downloader.FullSync if currentBlock.NumberU64() > 0 { log.Info("Blockchain not empty, auto disabling fast sync") atomic.StoreUint32(&cs.pm.fastSync, 0) } - log.Debug("what the fuck4") - if atomic.LoadUint32(&cs.pm.fastSync) == 1 { // Fast sync was explicitly requested, and explicitly granted mode = downloader.FastSync @@ -268,14 +261,10 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp { mode = downloader.FastSync } - log.Debug("what the fuck5") - if mode == downloader.FastSync && cs.pm.blockchain.CurrentFastBlock().Number().Cmp(pBn) >= 0 { return nil } - log.Debug("what the fuck", "mode", mode, "diff", diff) - return &chainSyncOp{mode: mode, peer: peer, bn: pBn, head: peerHead, diff: new(big.Int).Sub(pBn, currentBlock.Number())} } diff --git a/eth/sync_test.go b/eth/sync_test.go index 2d242bf7db..e6fe8c3f58 100644 --- a/eth/sync_test.go +++ b/eth/sync_test.go @@ -22,9 +22,10 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/eth/downloader" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) // Tests that fast sync gets disabled as soon as a real block is successfully @@ -46,8 +47,8 @@ func TestFastSyncDisabling(t *testing.T) { // Sync up the two peers io1, io2 := p2p.MsgPipe() - go pmFull.handle(pmFull.newPeer(63, p2p.NewPeer(discover.NodeID{}, "empty", nil), io2)) - go pmEmpty.handle(pmEmpty.newPeer(63, p2p.NewPeer(discover.NodeID{}, "full", nil), io1)) + go pmFull.handle(pmFull.newPeer(63, p2p.NewPeer(enode.ID{}, "empty", nil), io2)) + go pmEmpty.handle(pmEmpty.newPeer(63, p2p.NewPeer(enode.ID{}, "full", nil), io1)) time.Sleep(250 * time.Millisecond) bestPeer := pmEmpty.peers.BestPeer() diff --git a/les/commons.go b/les/commons.go index b9cddb2afc..68f2d479c4 100644 --- a/les/commons.go +++ b/les/commons.go @@ -19,13 +19,14 @@ package les import ( "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/core" "github.com/AlayaNetwork/Alaya-Go/eth" "github.com/AlayaNetwork/Alaya-Go/ethdb" "github.com/AlayaNetwork/Alaya-Go/light" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" ) @@ -41,11 +42,11 @@ type lesCommons struct { // NodeInfo represents a short summary of the Ethereum sub-protocol metadata // known about the host peer. type NodeInfo struct { - Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3, Rinkeby=4) - Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block - Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules - Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block - CHT params.TrustedCheckpoint `json:"cht"` // Trused CHT checkpoint for fast catchup + Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3, Rinkeby=4) + Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block + Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules + Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block + CHT params.TrustedCheckpoint `json:"cht"` // Trused CHT checkpoint for fast catchup } // makeProtocols creates protocol descriptors for the given LES versions. @@ -61,7 +62,7 @@ func (c *lesCommons) makeProtocols(versions []uint) []p2p.Protocol { Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { return c.protocolManager.runPeer(version, p, rw) }, - PeerInfo: func(id discover.NodeID) interface{} { + PeerInfo: func(id enode.ID) interface{} { if p := c.protocolManager.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil { return p.Info() } @@ -94,10 +95,10 @@ func (c *lesCommons) nodeInfo() interface{} { chain := c.protocolManager.blockchain return &NodeInfo{ - Network: c.config.NetworkId, - Genesis: chain.Genesis().Hash(), - Config: chain.Config(), - Head: chain.CurrentHeader().Hash(), - CHT: cht, + Network: c.config.NetworkId, + Genesis: chain.Genesis().Hash(), + Config: chain.Config(), + Head: chain.CurrentHeader().Hash(), + CHT: cht, } } diff --git a/les/handler.go b/les/handler.go index 712b7a8ac8..b37f269b5b 100644 --- a/les/handler.go +++ b/les/handler.go @@ -156,7 +156,7 @@ func NewProtocolManager(chainConfig *params.ChainConfig, indexerConfig *light.In } if lightSync { - manager.downloader = downloader.New( chainDb, snapshotdb.Instance(),nil, manager.eventMux, nil, blockchain, removePeer, nil) + manager.downloader = downloader.New(chainDb, snapshotdb.Instance(), nil, manager.eventMux, nil, blockchain, removePeer, nil) manager.peers.notify((*downloaderPeerNotify)(manager)) manager.fetcher = newLightFetcher(manager) } @@ -214,8 +214,7 @@ func (pm *ProtocolManager) runPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWrit var entry *poolEntry peer := pm.newPeer(int(version), pm.networkId, p, rw) if pm.serverPool != nil { - addr := p.RemoteAddr().(*net.TCPAddr) - entry = pm.serverPool.connect(peer, addr.IP, uint16(addr.Port)) + entry = pm.serverPool.connect(peer, peer.Node()) } peer.poolEntry = entry select { @@ -382,7 +381,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } if p.requestAnnounceType == announceTypeSigned { - if err := req.checkSignature(p.pubKey); err != nil { + if err := req.checkSignature(p.ID()); err != nil { p.Log().Trace("Invalid announcement signature", "err", err) return err } diff --git a/les/helper_test.go b/les/helper_test.go index a255b6e56a..fd7017a9dd 100644 --- a/les/helper_test.go +++ b/les/helper_test.go @@ -26,6 +26,8 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/core/rawdb" "github.com/AlayaNetwork/Alaya-Go/common" @@ -40,7 +42,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/les/flowcontrol" "github.com/AlayaNetwork/Alaya-Go/light" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" _ "github.com/AlayaNetwork/Alaya-Go/x/xcom" ) @@ -217,7 +218,7 @@ func newTestPeer(t *testing.T, name string, version int, pm *ProtocolManager, sh app, net := p2p.MsgPipe() // Generate a random id and create the peer - var id discover.NodeID + var id enode.ID rand.Read(id[:]) peer := pm.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) @@ -253,7 +254,7 @@ func newTestPeerPair(name string, version int, pm, pm2 *ProtocolManager) (*peer, app, net := p2p.MsgPipe() // Generate a random id and create the peer - var id discover.NodeID + var id enode.ID rand.Read(id[:]) peer := pm.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) diff --git a/les/peer.go b/les/peer.go index c9824897ed..130fda7be1 100644 --- a/les/peer.go +++ b/les/peer.go @@ -18,7 +18,6 @@ package les import ( - "crypto/ecdsa" "errors" "fmt" "math/big" @@ -51,7 +50,6 @@ const ( type peer struct { *p2p.Peer - pubKey *ecdsa.PublicKey rw p2p.MsgReadWriter @@ -80,11 +78,9 @@ type peer struct { func newPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { id := p.ID() - pubKey, _ := id.Pubkey() return &peer{ Peer: p, - pubKey: pubKey, rw: rw, version: version, network: network, @@ -104,9 +100,9 @@ func (p *peer) queueSend(f func()) { // Info gathers and returns a collection of metadata known about a peer. func (p *peer) Info() *eth.PeerInfo { return ð.PeerInfo{ - Version: p.version, - BN: new(big.Int).SetUint64(p.headInfo.Number), - Head: fmt.Sprintf("%x", p.Head()), + Version: p.version, + BN: new(big.Int).SetUint64(p.headInfo.Number), + Head: fmt.Sprintf("%x", p.Head()), } } diff --git a/les/protocol.go b/les/protocol.go index ba3ae49b07..673719e64d 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -18,13 +18,13 @@ package les import ( - "bytes" "crypto/ecdsa" - "crypto/elliptic" "errors" "fmt" "io" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/core" "github.com/AlayaNetwork/Alaya-Go/core/rawdb" @@ -146,21 +146,20 @@ func (a *announceData) sign(privKey *ecdsa.PrivateKey) { } // checkSignature verifies if the block announcement has a valid signature by the given pubKey -func (a *announceData) checkSignature(pubKey *ecdsa.PublicKey) error { +func (a *announceData) checkSignature(id enode.ID) error { var sig []byte if err := a.Update.decode().get("sign", &sig); err != nil { return err } rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number}) - recPubkey, err := crypto.Ecrecover(crypto.Keccak256(rlp), sig) + recPubkey, err := crypto.SigToPub(crypto.Keccak256(rlp), sig) if err != nil { return err } - pbytes := elliptic.Marshal(pubKey.Curve, pubKey.X, pubKey.Y) - if bytes.Equal(pbytes, recPubkey) { + if id == enode.PubkeyToIDV4(recPubkey) { return nil } - return errors.New("Wrong signature") + return errors.New("wrong signature") } type blockInfo struct { diff --git a/les/serverpool.go b/les/serverpool.go index 6e4f0ad498..8a1c415fc6 100644 --- a/les/serverpool.go +++ b/les/serverpool.go @@ -18,6 +18,7 @@ package les import ( + "crypto/ecdsa" "fmt" "io" "math" @@ -27,11 +28,13 @@ import ( "sync" "time" + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/common/mclock" "github.com/AlayaNetwork/Alaya-Go/ethdb" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/p2p/discv5" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -90,8 +93,7 @@ const ( // connReq represents a request for peer connection. type connReq struct { p *peer - ip net.IP - port uint16 + node *enode.Node result chan *poolEntry } @@ -122,10 +124,10 @@ type serverPool struct { topic discv5.Topic discSetPeriod chan time.Duration - discNodes chan *discv5.Node + discNodes chan *enode.Node discLookups chan bool - entries map[discover.NodeID]*poolEntry + entries map[enode.ID]*poolEntry timeout, enableRetry chan *poolEntry adjustStats chan poolStatAdjust @@ -145,7 +147,7 @@ func newServerPool(db ethdb.Database, quit chan struct{}, wg *sync.WaitGroup) *s db: db, quit: quit, wg: wg, - entries: make(map[discover.NodeID]*poolEntry), + entries: make(map[enode.ID]*poolEntry), timeout: make(chan *poolEntry, 1), adjustStats: make(chan poolStatAdjust, 100), enableRetry: make(chan *poolEntry, 1), @@ -170,22 +172,38 @@ func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) { if pool.server.DiscV5 != nil { pool.discSetPeriod = make(chan time.Duration, 1) - pool.discNodes = make(chan *discv5.Node, 100) + pool.discNodes = make(chan *enode.Node, 100) pool.discLookups = make(chan bool, 100) - go pool.server.DiscV5.SearchTopic(pool.topic, pool.discSetPeriod, pool.discNodes, pool.discLookups) + go pool.discoverNodes() } pool.checkDial() go pool.eventLoop() } +// 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 // dialed by the server pool recently, the appropriate pool entry is returned. // Otherwise, the connection should be rejected. // Note that whenever a connection has been accepted and a pool entry has been returned, // disconnect should also always be called. -func (pool *serverPool) connect(p *peer, ip net.IP, port uint16) *poolEntry { +func (pool *serverPool) connect(p *peer, node *enode.Node) *poolEntry { log.Debug("Connect new entry", "enode", p.id) - req := &connReq{p: p, ip: ip, port: port, result: make(chan *poolEntry, 1)} + req := &connReq{p: p, node: node, result: make(chan *poolEntry, 1)} select { case pool.connCh <- req: case <-pool.quit: @@ -196,7 +214,7 @@ func (pool *serverPool) connect(p *peer, ip net.IP, port uint16) *poolEntry { // registered should be called after a successful handshake func (pool *serverPool) registered(entry *poolEntry) { - log.Debug("Registered new entry", "enode", entry.id) + log.Debug("Registered new entry", "enode", entry.node.ID()) req := ®isterReq{entry: entry, done: make(chan struct{})} select { case pool.registerCh <- req: @@ -216,7 +234,7 @@ func (pool *serverPool) disconnect(entry *poolEntry) { stopped = true default: } - log.Debug("Disconnected old entry", "enode", entry.id) + 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. @@ -320,7 +338,7 @@ func (pool *serverPool) eventLoop() { } case node := <-pool.discNodes: - entry := pool.findOrNewNode(discover.NodeID(node.ID), node.IP, node.TCP) + entry := pool.findOrNewNode(node) pool.updateCheckDial(entry) case conv := <-pool.discLookups: @@ -341,7 +359,7 @@ func (pool *serverPool) eventLoop() { // Handle peer connection requests. entry := pool.entries[req.p.ID()] if entry == nil { - entry = pool.findOrNewNode(req.p.ID(), req.ip, req.port) + entry = pool.findOrNewNode(req.node) } if entry.state == psConnected || entry.state == psRegistered { req.result <- nil @@ -351,8 +369,8 @@ func (pool *serverPool) eventLoop() { entry.peer = req.p entry.state = psConnected addr := &poolEntryAddress{ - ip: req.ip, - port: req.port, + ip: req.node.IP(), + port: uint16(req.node.TCP()), lastSeen: mclock.Now(), } entry.lastConnected = addr @@ -401,18 +419,18 @@ func (pool *serverPool) eventLoop() { } } -func (pool *serverPool) findOrNewNode(id discover.NodeID, ip net.IP, port uint16) *poolEntry { +func (pool *serverPool) findOrNewNode(node *enode.Node) *poolEntry { now := mclock.Now() - entry := pool.entries[id] + entry := pool.entries[node.ID()] if entry == nil { - log.Debug("Discovered new entry", "id", id) + log.Debug("Discovered new entry", "id", node.ID()) entry = &poolEntry{ - id: id, + node: node, addr: make(map[string]*poolEntryAddress), addrSelect: *newWeightedRandomSelect(), shortRetry: shortRetryCnt, } - pool.entries[id] = entry + pool.entries[node.ID()] = entry // initialize previously unknown peers with good statistics to give a chance to prove themselves entry.connectStats.add(1, initStatsWeight) entry.delayStats.add(0, initStatsWeight) @@ -420,10 +438,7 @@ func (pool *serverPool) findOrNewNode(id discover.NodeID, ip net.IP, port uint16 entry.timeoutStats.add(0, initStatsWeight) } entry.lastDiscovered = now - addr := &poolEntryAddress{ - ip: ip, - port: port, - } + addr := &poolEntryAddress{ip: node.IP(), port: uint16(node.TCP())} if a, ok := entry.addr[addr.strKey()]; ok { addr = a } else { @@ -450,12 +465,12 @@ func (pool *serverPool) loadNodes() { return } for _, e := range list { - log.Debug("Loaded server stats", "id", e.id, "fails", e.lastConnected.fails, + log.Debug("Loaded server stats", "id", e.node.ID(), "fails", e.lastConnected.fails, "conn", fmt.Sprintf("%v/%v", e.connectStats.avg, e.connectStats.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), "timeout", fmt.Sprintf("%v/%v", e.timeoutStats.avg, e.timeoutStats.weight)) - pool.entries[e.id] = e + pool.entries[e.node.ID()] = e pool.knownQueue.setLatest(e) pool.knownSelect.update((*knownEntry)(e)) } @@ -481,7 +496,7 @@ func (pool *serverPool) removeEntry(entry *poolEntry) { pool.newSelect.remove((*discoveredEntry)(entry)) pool.knownSelect.remove((*knownEntry)(entry)) entry.removed = true - delete(pool.entries, entry.id) + delete(pool.entries, entry.node.ID()) } // setRetryDial starts the timer which will enable dialing a certain node again @@ -559,10 +574,10 @@ func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) { pool.newSelected++ } addr := entry.addrSelect.choose().(*poolEntryAddress) - log.Debug("Dialing new peer", "lesaddr", entry.id.String()+"@"+addr.strKey(), "set", len(entry.addr), "known", knownSelected) + log.Debug("Dialing new peer", "lesaddr", entry.node.ID().String()+"@"+addr.strKey(), "set", len(entry.addr), "known", knownSelected) entry.dialed = addr go func() { - pool.server.AddPeer(discover.NewNode(entry.id, addr.ip, addr.port, addr.port)) + pool.server.AddPeer(entry.node) select { case <-pool.quit: case <-time.After(dialTimeout): @@ -580,7 +595,7 @@ func (pool *serverPool) checkDialTimeout(entry *poolEntry) { if entry.state != psDialed { return } - log.Debug("Dial timeout", "lesaddr", entry.id.String()+"@"+entry.dialed.strKey()) + log.Debug("Dial timeout", "lesaddr", entry.node.ID().String()+"@"+entry.dialed.strKey()) entry.state = psNotConnected if entry.knownSelected { pool.knownSelected-- @@ -602,8 +617,9 @@ const ( // poolEntry represents a server node and stores its current state and statistics. type poolEntry struct { peer *peer - id discover.NodeID + pubkey [64]byte // secp256k1 key of the node addr map[string]*poolEntryAddress + node *enode.Node lastConnected, dialed *poolEntryAddress addrSelect weightedRandomSelect @@ -620,23 +636,39 @@ type poolEntry struct { 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 { - return rlp.Encode(w, []interface{}{e.id, e.lastConnected.ip, e.lastConnected.port, e.lastConnected.fails, &e.connectStats, &e.delayStats, &e.responseStats, &e.timeoutStats}) + return rlp.Encode(w, &poolEntryEnc{ + 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 { - var entry struct { - ID discover.NodeID - IP net.IP - Port uint16 - Fails uint - CStat, DStat, RStat, TStat poolStats - } + var entry poolEntryEnc if err := s.Decode(&entry); err != nil { 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()} - e.id = entry.ID + e.node = enode.NewV4(pubkey, entry.IP, int(entry.Port), int(entry.Port)) e.addr = make(map[string]*poolEntryAddress) e.addr[addr.strKey()] = addr e.addrSelect = *newWeightedRandomSelect() @@ -651,6 +683,14 @@ func (e *poolEntry) DecodeRLP(s *rlp.Stream) error { 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 type discoveredEntry poolEntry diff --git a/miner/worker.go b/miner/worker.go index 2070fae282..bd644a91fe 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1013,7 +1013,7 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64, // Only set the coinbase if our consensus engine is running (avoid spurious block rewards) if w.isRunning() { if b, ok := w.engine.(consensus.Bft); ok { - core.GetReactorInstance().SetWorkerCoinBase(header, b.NodeID()) + core.GetReactorInstance().SetWorkerCoinBase(header, b.Node().IDv0()) } } diff --git a/node/api.go b/node/api.go index 776e4656d4..681c2d7a68 100644 --- a/node/api.go +++ b/node/api.go @@ -21,12 +21,13 @@ import ( "fmt" "strings" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/common/hexutil" "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rpc" ) @@ -51,7 +52,7 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) { return false, ErrNodeStopped } // Try to add the url as a static peer and return - node, err := discover.ParseNode(url) + node, err := enode.ParseV4(url) if err != nil { return false, fmt.Errorf("invalid enode: %v", err) } @@ -67,7 +68,7 @@ func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) { return false, ErrNodeStopped } // Try to remove the url as a static peer and return - node, err := discover.ParseNode(url) + node, err := enode.ParseV4(url) if err != nil { return false, fmt.Errorf("invalid enode: %v", err) } diff --git a/node/config.go b/node/config.go index 4f06a13a3e..5c19ed30a5 100644 --- a/node/config.go +++ b/node/config.go @@ -26,6 +26,8 @@ import ( "strings" "sync" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/crypto/bls" "github.com/AlayaNetwork/Alaya-Go/accounts" @@ -35,7 +37,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rpc" ) @@ -401,18 +402,18 @@ func (c *Config) BlsKey() *bls.SecretKey { } // StaticNodes returns a list of node enode URLs configured as static nodes. -func (c *Config) StaticNodes() []*discover.Node { +func (c *Config) StaticNodes() []*enode.Node { return c.parsePersistentNodes(&c.staticNodesWarning, c.ResolvePath(datadirStaticNodes)) } // TrustedNodes returns a list of node enode URLs configured as trusted nodes. -func (c *Config) TrustedNodes() []*discover.Node { +func (c *Config) TrustedNodes() []*enode.Node { return c.parsePersistentNodes(&c.trustedNodesWarning, c.ResolvePath(datadirTrustedNodes)) } // parsePersistentNodes parses a list of discovery node URLs loaded from a .json // file from within the data directory. -func (c *Config) parsePersistentNodes(w *bool, path string) []*discover.Node { +func (c *Config) parsePersistentNodes(w *bool, path string) []*enode.Node { // Short circuit if no node config is present if c.DataDir == "" { return nil @@ -429,12 +430,12 @@ func (c *Config) parsePersistentNodes(w *bool, path string) []*discover.Node { return nil } // Interpret the list as a discovery node array - var nodes []*discover.Node + var nodes []*enode.Node for _, url := range nodelist { if url == "" { continue } - node, err := discover.ParseNode(url) + node, err := enode.ParseV4(url) if err != nil { log.Error(fmt.Sprintf("Node URL %s: %v\n", url, err)) continue diff --git a/node/crypto_handler.go b/node/crypto_handler.go index c530d706c2..e31a6b33cc 100755 --- a/node/crypto_handler.go +++ b/node/crypto_handler.go @@ -21,6 +21,8 @@ import ( "encoding/hex" "sync" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/crypto/sha3" "github.com/AlayaNetwork/Alaya-Go/rlp" @@ -28,7 +30,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) var ( @@ -71,13 +72,13 @@ func (chandler *CryptoHandler) MustSign(data interface{}) []byte { return sig } -func (chandler *CryptoHandler) IsSignedByNodeID(data interface{}, sig []byte, nodeID discover.NodeID) bool { +func (chandler *CryptoHandler) IsSignedByNodeID(data interface{}, sig []byte, nodeID enode.IDv0) bool { pubKey, err := crypto.SigToPub(RlpHash(data).Bytes(), sig) if err != nil { log.Error("Check if the signature is signed by a node", "err", err) return false } - id := discover.PubkeyID(pubKey) + id := enode.PublicKeyToIDv0(pubKey) if id == nodeID { return true diff --git a/node/crypto_handler_test.go b/node/crypto_handler_test.go index d556b0f342..ccbab49073 100755 --- a/node/crypto_handler_test.go +++ b/node/crypto_handler_test.go @@ -19,8 +19,9 @@ package node import ( "testing" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) var ( @@ -28,7 +29,7 @@ var ( priKey = crypto.HexMustToECDSA("8c56e4a0d8bb1f82b94231d535c499fdcbf6e06221acf669d5a964f5bb974903") //nodeID = discover.MustHexID("3e7864716b671c4de0dc2d7fd86215e0dcb8419e66430a770294eb2f37b714a07b6a3493055bb2d733dee9bfcc995e1c8e7885f338a69bf6c28930f3cf341819") - nodeID = discover.MustHexID("3a06953a2d5d45b29167bef58208f1287225bdd2591260af29ae1300aeed362e9b548369dfc1659abbef403c9b3b07a8a194040e966acd6e5b6d55aa2df7c1d8") + nodeID = enode.MustHexIDv0("3a06953a2d5d45b29167bef58208f1287225bdd2591260af29ae1300aeed362e9b548369dfc1659abbef403c9b3b07a8a194040e966acd6e5b6d55aa2df7c1d8") ) func initChandlerHandler() { diff --git a/node/fake.go b/node/fake.go index a563882c30..7c730b4ba2 100644 --- a/node/fake.go +++ b/node/fake.go @@ -19,7 +19,7 @@ package node import ( "crypto/ecdsa" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" ) var FakeNetEnable bool = false @@ -38,9 +38,9 @@ var indexMock = map[int][]int{ } // MockDiscoveryNode returns to a specific network topology. -func MockDiscoveryNode(privateKey *ecdsa.PrivateKey, nodes []*discover.Node) []*discover.Node { - selfNodeID := discover.PubkeyID(&privateKey.PublicKey) - mockNodes := make([]*discover.Node, 0) +func MockDiscoveryNode(privateKey *ecdsa.PrivateKey, nodes []*enode.Node) []*enode.Node { + selfNodeID := enode.PubkeyToIDV4(&privateKey.PublicKey) + mockNodes := make([]*enode.Node, 0) ok, idxs := needAdd(selfNodeID, nodes) for idx, n := range nodes { if idxs == nil { @@ -57,10 +57,10 @@ func MockDiscoveryNode(privateKey *ecdsa.PrivateKey, nodes []*discover.Node) []* } // mock -func needAdd(self discover.NodeID, nodes []*discover.Node) (bool, []int) { +func needAdd(self enode.ID, nodes []*enode.Node) (bool, []int) { selfIndex := -1 for idx, n := range nodes { - if n.ID.TerminalString() == self.TerminalString() { + if n.ID().TerminalString() == self.TerminalString() { selfIndex = idx break } diff --git a/p2p/consensus_dialed.go b/p2p/consensus_dialed.go index e25d8dd473..38e3f088d8 100644 --- a/p2p/consensus_dialed.go +++ b/p2p/consensus_dialed.go @@ -2,9 +2,10 @@ package p2p import ( "fmt" - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "strings" + + "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" ) type dialedTasks struct { @@ -51,12 +52,12 @@ func (tasks *dialedTasks) AddTask(task *dialTask) error { return nil } -func (tasks *dialedTasks) RemoveTask(NodeID discover.NodeID) error { +func (tasks *dialedTasks) RemoveTask(NodeID *enode.Node) error { log.Info("[before remove]Consensus dialed task list before RemoveTask operation", "task queue", tasks.description()) if !tasks.isEmpty() { for i, t := range tasks.queue { - if t.dest.ID == NodeID { + if t.dest.ID() == NodeID.ID() { tasks.queue = append(tasks.queue[:i], tasks.queue[i+1:]...) break } @@ -104,7 +105,7 @@ func (tasks *dialedTasks) pollIndex(index int) *dialTask { // index of task in the queue func (tasks *dialedTasks) index(task *dialTask) int { for i, t := range tasks.queue { - if t.dest.ID == task.dest.ID { + if t.dest.ID() == task.dest.ID() { return i } } @@ -140,7 +141,7 @@ func (tasks *dialedTasks) isEmpty() bool { func (tasks *dialedTasks) description() string { var description []string for _, t := range tasks.queue { - description = append(description, fmt.Sprintf("%x", t.dest.ID[:8])) + description = append(description, fmt.Sprintf("%x", t.dest.ID().TerminalString())) } return strings.Join(description, ",") } diff --git a/p2p/dial.go b/p2p/dial.go index 55d974e227..60c1fe6521 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -18,14 +18,13 @@ package p2p import ( "container/heap" - "crypto/rand" "errors" "fmt" "net" "time" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" ) @@ -47,12 +46,12 @@ const ( maxResolveDelay = time.Hour ) -type removeConsensusPeerFn func(node *discover.Node) +type removeConsensusPeerFn func(node *enode.Node) // NodeDialer is used to connect to nodes in the network, typically by using // an underlying net.Dialer but also using net.Pipe in tests type NodeDialer interface { - Dial(*discover.Node) (net.Conn, error) + Dial(*enode.Node) (net.Conn, error) } // TCPDialer implements the NodeDialer interface by using a net.Dialer to @@ -62,8 +61,8 @@ type TCPDialer struct { } // Dial creates a TCP connection to the node -func (t TCPDialer) Dial(dest *discover.Node) (net.Conn, error) { - addr := &net.TCPAddr{IP: dest.IP, Port: int(dest.TCP)} +func (t TCPDialer) Dial(dest *enode.Node) (net.Conn, error) { + addr := &net.TCPAddr{IP: dest.IP(), Port: dest.TCP()} return t.Dialer.Dial("tcp", addr.String()) } @@ -76,24 +75,24 @@ type dialstate struct { netrestrict *netutil.Netlist lookupRunning bool - dialing map[discover.NodeID]connFlag - lookupBuf []*discover.Node // current discovery lookup results - randomNodes []*discover.Node // filled from Table - static map[discover.NodeID]*dialTask + dialing map[enode.ID]connFlag + lookupBuf []*enode.Node // current discovery lookup results + randomNodes []*enode.Node // filled from Table + static map[enode.ID]*dialTask //consensus map[discover.NodeID]*dialTask consensus *dialedTasks hist *dialHistory - start time.Time // time when the dialer was first used - bootnodes []*discover.Node // default dials when there are no peers + start time.Time // time when the dialer was first used + bootnodes []*enode.Node // default dials when there are no peers } type discoverTable interface { - Self() *discover.Node + Self() *enode.Node Close() - Resolve(target discover.NodeID) *discover.Node - Lookup(target discover.NodeID) []*discover.Node - ReadRandomNodes([]*discover.Node) int + Resolve(*enode.Node) *enode.Node + LookupRandom() []*enode.Node + ReadRandomNodes([]*enode.Node) int } // the dial history remembers recent dials. @@ -101,7 +100,7 @@ type dialHistory []pastDial // pastDial is an entry in the dial history. type pastDial struct { - id discover.NodeID + id enode.ID exp time.Time } @@ -113,7 +112,7 @@ type task interface { // fields cannot be accessed while the task is running. type dialTask struct { flags connFlag - dest *discover.Node + dest *enode.Node lastResolved time.Time resolveDelay time.Duration } @@ -122,7 +121,7 @@ type dialTask struct { // Only one discoverTask is active at any time. // discoverTask.Do performs a random lookup. type discoverTask struct { - results []*discover.Node + results []*enode.Node } // A waitExpireTask is generated if there are no other tasks @@ -131,17 +130,16 @@ type waitExpireTask struct { time.Duration } -func newDialState(static []*discover.Node, bootnodes []*discover.Node, ntab discoverTable, maxdyn int, netrestrict *netutil.Netlist, maxConsensusPeers int) *dialstate { +func newDialState(static []*enode.Node, bootnodes []*enode.Node, ntab discoverTable, maxdyn int, netrestrict *netutil.Netlist, maxConsensusPeers int) *dialstate { s := &dialstate{ maxDynDials: maxdyn, ntab: ntab, netrestrict: netrestrict, - static: make(map[discover.NodeID]*dialTask), - //consensus: make(map[discover.NodeID]*dialTask), + static: make(map[enode.ID]*dialTask), consensus: NewDialedTasks(maxConsensusPeers*2, nil), - dialing: make(map[discover.NodeID]connFlag), - bootnodes: make([]*discover.Node, len(bootnodes)), - randomNodes: make([]*discover.Node, maxdyn/2), + dialing: make(map[enode.ID]connFlag), + bootnodes: make([]*enode.Node, len(bootnodes)), + randomNodes: make([]*enode.Node, maxdyn/2), hist: new(dialHistory), } copy(s.bootnodes, bootnodes) @@ -151,54 +149,54 @@ func newDialState(static []*discover.Node, bootnodes []*discover.Node, ntab disc return s } -func (s *dialstate) addStatic(n *discover.Node) { +func (s *dialstate) addStatic(n *enode.Node) { // This overwrites the task instead of updating an existing // 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 *discover.Node) { +func (s *dialstate) removeStatic(n *enode.Node) { // 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 // can force a server to reconnect with chosen peer immediately. - s.hist.remove(n.ID) + s.hist.remove(n.ID()) } -func (s *dialstate) addConsensus(n *discover.Node) { +func (s *dialstate) addConsensus(n *enode.Node) { log.Warn("dial adding consensus node", "node", n) //s.consensus[n.ID] = &dialTask{flags: consensusDialedConn, dest: n} s.consensus.AddTask(&dialTask{flags: consensusDialedConn, dest: n}) } -func (s *dialstate) removeConsensus(n *discover.Node) { +func (s *dialstate) removeConsensus(n *enode.Node) { //delete(s.consensus, n.ID) - s.consensus.RemoveTask(n.ID) - s.hist.remove(n.ID) + s.consensus.RemoveTask(n) + s.hist.remove(n.ID()) } -func (s *dialstate) removeConsensusFromQueue(n *discover.Node) { +func (s *dialstate) removeConsensusFromQueue(n *enode.Node) { //delete(s.consensus, n.ID) //s.consensus.RemoveTask(n.ID) - s.hist.remove(n.ID) + s.hist.remove(n.ID()) } func (s *dialstate) initRemoveConsensusPeerFn(removeConsensusPeerFn removeConsensusPeerFn) { s.consensus.InitRemoveConsensusPeerFn(removeConsensusPeerFn) } -func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now time.Time) []task { +func (s *dialstate) newTasks(nRunning int, peers map[enode.ID]*Peer, now time.Time) []task { if s.start.IsZero() { s.start = now } var newtasks []task - addDial := func(flag connFlag, n *discover.Node) bool { + addDial := func(flag connFlag, n *enode.Node) bool { if err := s.checkDial(n, peers); err != nil { - log.Trace("Skipping dial candidate", "id", n.ID, "addr", &net.TCPAddr{IP: n.IP, Port: int(n.TCP)}, "err", err) + log.Trace("Skipping dial candidate", "id", n.ID(), "addr", &net.TCPAddr{IP: n.IP(), Port: n.TCP()}, "err", err) return false } - s.dialing[n.ID] = flag + s.dialing[n.ID()] = flag newtasks = append(newtasks, &dialTask{flags: flag, dest: n}) return true } @@ -224,8 +222,8 @@ func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now err := s.checkDial(t.dest, peers) switch err { case errNotWhitelisted, errSelf: - 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) + log.Warn("Removing static dial candidate", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP(), Port: t.dest.TCP()}, "err", err) + delete(s.static, t.dest.ID()) case nil: s.dialing[id] = t.flags newtasks = append(newtasks, t) @@ -238,9 +236,9 @@ func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now switch err { case errNotWhitelisted, errSelf: //delete(s.consensus, t.dest.ID) - s.consensus.RemoveTask(t.dest.ID) + s.consensus.RemoveTask(t.dest) case nil: - s.dialing[t.dest.ID] = t.flags + s.dialing[t.dest.ID()] = t.flags newtasks = append(newtasks, t) } } @@ -302,18 +300,18 @@ var ( errNotWhitelisted = errors.New("not contained in netrestrict whitelist") ) -func (s *dialstate) checkDial(n *discover.Node, peers map[discover.NodeID]*Peer) error { - _, dialing := s.dialing[n.ID] +func (s *dialstate) checkDial(n *enode.Node, peers map[enode.ID]*Peer) error { + _, dialing := s.dialing[n.ID()] switch { case dialing: return errAlreadyDialing - case peers[n.ID] != nil: + case peers[n.ID()] != nil: return errAlreadyConnected - case s.ntab != nil && n.ID == s.ntab.Self().ID: + case s.ntab != nil && n.ID() == s.ntab.Self().ID(): return errSelf - case s.netrestrict != nil && !s.netrestrict.Contains(n.IP): + case s.netrestrict != nil && !s.netrestrict.Contains(n.IP()): return errNotWhitelisted - case s.hist.contains(n.ID): + case s.hist.contains(n.ID()): return errRecentlyDialed } return nil @@ -322,8 +320,8 @@ func (s *dialstate) checkDial(n *discover.Node, peers map[discover.NodeID]*Peer) func (s *dialstate) taskDone(t task, now time.Time) { switch t := t.(type) { case *dialTask: - s.hist.add(t.dest.ID, now.Add(dialHistoryExpiration)) - delete(s.dialing, t.dest.ID) + s.hist.add(t.dest.ID(), now.Add(dialHistoryExpiration)) + delete(s.dialing, t.dest.ID()) case *discoverTask: s.lookupRunning = false s.lookupBuf = append(s.lookupBuf, t.results...) @@ -365,7 +363,7 @@ func (t *dialTask) resolve(srv *Server) bool { if time.Since(t.lastResolved) < t.resolveDelay { return false } - resolved := srv.ntab.Resolve(t.dest.ID) + resolved := srv.ntab.Resolve(t.dest) t.lastResolved = time.Now() if resolved == nil { t.resolveDelay *= 2 @@ -378,7 +376,7 @@ func (t *dialTask) resolve(srv *Server) bool { // The node was found. t.resolveDelay = initialResolveDelay t.dest = resolved - log.Debug("Resolved node", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP, Port: int(t.dest.TCP)}) + log.Debug("Resolved node", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP(), Port: t.dest.TCP()}) return true } @@ -387,7 +385,7 @@ type dialError struct { } // dial performs the actual connection attempt. -func (t *dialTask) dial(srv *Server, dest *discover.Node) error { +func (t *dialTask) dial(srv *Server, dest *enode.Node) error { fd, err := srv.Dialer.Dial(dest) if err != nil { return &dialError{err} @@ -397,7 +395,8 @@ func (t *dialTask) dial(srv *Server, dest *discover.Node) error { } func (t *dialTask) String() string { - return fmt.Sprintf("%v %x %v:%d", t.flags, t.dest.ID[:8], t.dest.IP, t.dest.TCP) + id := t.dest.ID() + return fmt.Sprintf("%v %x %v:%d", t.flags, id[:8], t.dest.IP(), t.dest.TCP()) } func (t *discoverTask) Do(srv *Server) { @@ -409,9 +408,7 @@ func (t *discoverTask) Do(srv *Server) { time.Sleep(next.Sub(now)) } srv.lastLookup = time.Now() - var target discover.NodeID - rand.Read(target[:]) - t.results = srv.ntab.Lookup(target) + t.results = srv.ntab.LookupRandom() } func (t *discoverTask) String() string { @@ -433,11 +430,11 @@ func (t waitExpireTask) String() string { func (h dialHistory) min() pastDial { return h[0] } -func (h *dialHistory) add(id discover.NodeID, exp time.Time) { +func (h *dialHistory) add(id enode.ID, exp time.Time) { heap.Push(h, pastDial{id, exp}) } -func (h *dialHistory) remove(id discover.NodeID) bool { +func (h *dialHistory) remove(id enode.ID) bool { for i, v := range *h { if v.id == id { heap.Remove(h, i) @@ -446,7 +443,7 @@ func (h *dialHistory) remove(id discover.NodeID) bool { } return false } -func (h dialHistory) contains(id discover.NodeID) bool { +func (h dialHistory) contains(id enode.ID) bool { for _, v := range h { if v.id == id { return true diff --git a/p2p/dial_test.go b/p2p/dial_test.go index 09fc9c942b..c2f6ae5089 100644 --- a/p2p/dial_test.go +++ b/p2p/dial_test.go @@ -23,8 +23,11 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/davecgh/go-spew/spew" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" ) @@ -48,10 +51,10 @@ func runDialTest(t *testing.T, test dialtest) { vtime time.Time running int ) - pm := func(ps []*Peer) map[discover.NodeID]*Peer { - m := make(map[discover.NodeID]*Peer) + pm := func(ps []*Peer) map[enode.ID]*Peer { + m := make(map[enode.ID]*Peer) for _, p := range ps { - m[p.rw.id] = p + m[p.ID()] = p } return m } @@ -69,6 +72,7 @@ func runDialTest(t *testing.T, test dialtest) { 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)) } + t.Log("tasks:", spew.Sdump(new)) // Time advances by 16 seconds on every round. vtime = vtime.Add(16 * time.Second) @@ -76,79 +80,79 @@ func runDialTest(t *testing.T, test dialtest) { } } -type fakeTable []*discover.Node +type fakeTable []*enode.Node -func (t fakeTable) Self() *discover.Node { return new(discover.Node) } -func (t fakeTable) Close() {} -func (t fakeTable) Lookup(discover.NodeID) []*discover.Node { return nil } -func (t fakeTable) Resolve(discover.NodeID) *discover.Node { return nil } -func (t fakeTable) ReadRandomNodes(buf []*discover.Node) int { return copy(buf, t) } +func (t fakeTable) Self() *enode.Node { return new(enode.Node) } +func (t fakeTable) Close() {} +func (t fakeTable) LookupRandom() []*enode.Node { return nil } +func (t fakeTable) Resolve(*enode.Node) *enode.Node { return nil } +func (t fakeTable) ReadRandomNodes(buf []*enode.Node) int { return copy(buf, t) } // This test checks that dynamic dials are launched from discovery results func TestDialStateDynDial(t *testing.T) { runDialTest(t, dialtest{ - init: newDialState(nil,nil, fakeTable{}, 5, nil, 75), + init: newDialState(nil, nil, fakeTable{}, 5, nil, 75), rounds: []round{ // A discovery query is launched. { peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, id: uintID(0)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, }, new: []task{&discoverTask{}}, }, // Dynamic dials are launched when it completes. { peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, id: uintID(0)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, }, done: []task{ - &discoverTask{results: []*discover.Node{ - {ID: uintID(2)}, // this one is already connected and not dialed. - {ID: uintID(3)}, - {ID: uintID(4)}, - {ID: uintID(5)}, - {ID: uintID(6)}, // these are not tried because max dyn dials is 5 - {ID: uintID(7)}, // ... + &discoverTask{results: []*enode.Node{ + newNode(uintID(2), nil), // this one is already connected and not dialed. + newNode(uintID(3), nil), + newNode(uintID(4), nil), + newNode(uintID(5), nil), + newNode(uintID(6), nil), // these are not tried because max dyn dials is 5 + newNode(uintID(7), nil), // ... }}, }, new: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, }, }, // 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. { peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, id: uintID(0)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(3)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(4)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(3), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}}, }, done: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, }, }, // No new dial tasks are launched in the this round because // maxDynDials has been reached. { peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, id: uintID(0)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(3)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(4)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(5)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(3), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}}, }, done: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, }, new: []task{ &waitExpireTask{Duration: 14 * time.Second}, @@ -158,14 +162,14 @@ func TestDialStateDynDial(t *testing.T) { // results from last discovery lookup are reused. { peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, id: uintID(0)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(3)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(4)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(5)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(3), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}}, }, new: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(6)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(6), nil)}, }, }, // More peers (3,4) drop off and dial for ID 6 completes. @@ -173,15 +177,15 @@ func TestDialStateDynDial(t *testing.T) { // and a new one is spawned because more candidates are needed. { peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, id: uintID(0)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(5)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}}, }, done: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(6)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(6), nil)}, }, new: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(7)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(7), nil)}, &discoverTask{}, }, }, @@ -190,23 +194,23 @@ func TestDialStateDynDial(t *testing.T) { // no new is started. { peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, id: uintID(0)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(5)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(7)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(7), nil)}}, }, done: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(7)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(7), nil)}, }, }, // Finish the running node discovery with an empty set. A new lookup // should be immediately requested. { peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, id: uintID(0)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(5)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(7)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(7), nil)}}, }, done: []task{ &discoverTask{}, @@ -221,17 +225,17 @@ func TestDialStateDynDial(t *testing.T) { // Tests that bootnodes are dialed if no peers are connectd, but not otherwise. func TestDialStateDynDialBootnode(t *testing.T) { - bootnodes := []*discover.Node{ - {ID: uintID(1)}, - {ID: uintID(2)}, - {ID: uintID(3)}, + bootnodes := []*enode.Node{ + newNode(uintID(1), nil), + newNode(uintID(2), nil), + newNode(uintID(3), nil), } table := fakeTable{ - {ID: uintID(4)}, - {ID: uintID(5)}, - {ID: uintID(6)}, - {ID: uintID(7)}, - {ID: uintID(8)}, + newNode(uintID(4), nil), + newNode(uintID(5), nil), + newNode(uintID(6), nil), + newNode(uintID(7), nil), + newNode(uintID(8), nil), } runDialTest(t, dialtest{ init: newDialState(nil, bootnodes, table, 5, nil, 75), @@ -239,16 +243,16 @@ func TestDialStateDynDialBootnode(t *testing.T) { // 2 dynamic dials attempted, bootnodes pending fallback interval { new: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, &discoverTask{}, }, }, // No dials succeed, bootnodes still pending fallback interval { done: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, }, }, // No dials succeed, bootnodes still pending fallback interval @@ -256,51 +260,51 @@ func TestDialStateDynDialBootnode(t *testing.T) { // No dials succeed, 2 dynamic dials attempted and 1 bootnode too as fallback interval was reached { new: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, }, }, // No dials succeed, 2nd bootnode is attempted { done: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, }, new: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)}, }, }, // No dials succeed, 3rd bootnode is attempted { done: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)}, }, new: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, }, }, // No dials succeed, 1st bootnode is attempted again, expired random nodes retried { done: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, }, new: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, }, }, // Random dial succeeds, no more bootnodes are attempted { peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, id: uintID(4)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}}, }, done: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, }, }, }, @@ -311,79 +315,79 @@ func TestDialStateDynDialFromTable(t *testing.T) { // This table always returns the same random nodes // in the order given below. table := fakeTable{ - {ID: uintID(1)}, - {ID: uintID(2)}, - {ID: uintID(3)}, - {ID: uintID(4)}, - {ID: uintID(5)}, - {ID: uintID(6)}, - {ID: uintID(7)}, - {ID: uintID(8)}, + newNode(uintID(1), nil), + newNode(uintID(2), nil), + newNode(uintID(3), nil), + newNode(uintID(4), nil), + newNode(uintID(5), nil), + newNode(uintID(6), nil), + newNode(uintID(7), nil), + newNode(uintID(8), nil), } runDialTest(t, dialtest{ - init: newDialState(nil,nil, table, 10, nil, 75), + init: newDialState(nil, nil, table, 10, nil, 75), rounds: []round{ // 5 out of 8 of the nodes returned by ReadRandomNodes are dialed. { new: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, &discoverTask{}, }, }, // Dialing nodes 1,2 succeeds. Dials from the lookup are launched. { peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, }, done: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}}, - &discoverTask{results: []*discover.Node{ - {ID: uintID(10)}, - {ID: uintID(11)}, - {ID: uintID(12)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)}, + &discoverTask{results: []*enode.Node{ + newNode(uintID(10), nil), + newNode(uintID(11), nil), + newNode(uintID(12), nil), }}, }, new: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(10)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(11)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(12)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(10), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(11), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(12), nil)}, &discoverTask{}, }, }, // Dialing nodes 3,4,5 fails. The dials from the lookup succeed. { peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(10)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(11)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(12)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(10), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(11), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(12), nil)}}, }, done: []task{ - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(10)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(11)}}, - &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(12)}}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(10), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(11), nil)}, + &dialTask{flags: dynDialedConn, dest: newNode(uintID(12), nil)}, }, }, // Waiting for expiry. No waitExpireTask is launched because the // discovery query is still running. { peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(10)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(11)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(12)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(10), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(11), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(12), nil)}}, }, }, // Nodes 3,4 are not tried again because only the first two @@ -391,36 +395,44 @@ func TestDialStateDynDialFromTable(t *testing.T) { // already connected. { peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(10)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(11)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(12)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(10), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(11), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(12), nil)}}, }, }, }, }) } +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. func TestDialStateNetRestrict(t *testing.T) { // This table always returns the same random nodes // in the order given below. table := fakeTable{ - {ID: uintID(1), IP: net.ParseIP("127.0.0.1")}, - {ID: uintID(2), IP: net.ParseIP("127.0.0.2")}, - {ID: uintID(3), IP: net.ParseIP("127.0.0.3")}, - {ID: uintID(4), IP: net.ParseIP("127.0.0.4")}, - {ID: uintID(5), IP: net.ParseIP("127.0.2.5")}, - {ID: uintID(6), IP: net.ParseIP("127.0.2.6")}, - {ID: uintID(7), IP: net.ParseIP("127.0.2.7")}, - {ID: uintID(8), IP: net.ParseIP("127.0.2.8")}, + newNode(uintID(1), net.ParseIP("127.0.0.1")), + newNode(uintID(2), net.ParseIP("127.0.0.2")), + newNode(uintID(3), net.ParseIP("127.0.0.3")), + newNode(uintID(4), net.ParseIP("127.0.0.4")), + newNode(uintID(5), net.ParseIP("127.0.2.5")), + newNode(uintID(6), net.ParseIP("127.0.2.6")), + newNode(uintID(7), net.ParseIP("127.0.2.7")), + newNode(uintID(8), net.ParseIP("127.0.2.8")), } restrict := new(netutil.Netlist) restrict.Add("127.0.2.0/24") runDialTest(t, dialtest{ - init: newDialState(nil,nil, table, 10, restrict, 75), + init: newDialState(nil, nil, table, 10, restrict, 75), rounds: []round{ { new: []task{ @@ -434,12 +446,12 @@ func TestDialStateNetRestrict(t *testing.T) { // This test checks that static dials are launched. func TestDialStateStaticDial(t *testing.T) { - wantStatic := []*discover.Node{ - {ID: uintID(1)}, - {ID: uintID(2)}, - {ID: uintID(3)}, - {ID: uintID(4)}, - {ID: uintID(5)}, + wantStatic := []*enode.Node{ + newNode(uintID(1), nil), + newNode(uintID(2), nil), + newNode(uintID(3), nil), + newNode(uintID(4), nil), + newNode(uintID(5), nil), } runDialTest(t, dialtest{ @@ -449,40 +461,40 @@ func TestDialStateStaticDial(t *testing.T) { // aren't yet connected. { peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, }, new: []task{ - &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)}}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(4), nil)}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(5), nil)}, }, }, // No new tasks are launched in this round because all static // nodes are either connected or still being dialed. { peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, - {rw: &conn{flags: staticDialedConn, id: uintID(3)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}}, }, done: []task{ - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)}, }, }, // No new dial tasks are launched because all static // nodes are now connected. { peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, - {rw: &conn{flags: staticDialedConn, id: uintID(3)}}, - {rw: &conn{flags: staticDialedConn, id: uintID(4)}}, - {rw: &conn{flags: staticDialedConn, id: uintID(5)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(4), nil)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(5), nil)}}, }, done: []task{ - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(4)}}, - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(5)}}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(4), nil)}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(5), nil)}, }, new: []task{ &waitExpireTask{Duration: 14 * time.Second}, @@ -491,24 +503,24 @@ func TestDialStateStaticDial(t *testing.T) { // Wait a round for dial history to expire, no new tasks should spawn. { peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, - {rw: &conn{flags: staticDialedConn, id: uintID(3)}}, - {rw: &conn{flags: staticDialedConn, id: uintID(4)}}, - {rw: &conn{flags: staticDialedConn, id: uintID(5)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(4), nil)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(5), nil)}}, }, }, // If a static node is dropped, it should be immediately redialed, // irrespective whether it was originally static or dynamic. { peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: staticDialedConn, id: uintID(3)}}, - {rw: &conn{flags: staticDialedConn, id: uintID(5)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(5), nil)}}, }, new: []task{ - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}}, - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(4)}}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(4), nil)}, }, }, }, @@ -517,9 +529,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. func TestDialStaticAfterReset(t *testing.T) { - wantStatic := []*discover.Node{ - {ID: uintID(1)}, - {ID: uintID(2)}, + wantStatic := []*enode.Node{ + newNode(uintID(1), nil), + newNode(uintID(2), nil), } rounds := []round{ @@ -527,19 +539,19 @@ func TestDialStaticAfterReset(t *testing.T) { { peers: nil, new: []task{ - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}}, - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)}, }, }, // No new dial tasks, all peers are connected. { peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, id: uintID(1)}}, - {rw: &conn{flags: staticDialedConn, id: uintID(2)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(2), nil)}}, }, done: []task{ - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}}, - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)}, }, new: []task{ &waitExpireTask{Duration: 30 * time.Second}, @@ -547,7 +559,7 @@ func TestDialStaticAfterReset(t *testing.T) { }, } dTest := dialtest{ - init: newDialState(wantStatic,nil, fakeTable{}, 0, nil, 75), + init: newDialState(wantStatic, nil, fakeTable{}, 0, nil, 75), rounds: rounds, } runDialTest(t, dTest) @@ -561,46 +573,46 @@ func TestDialStaticAfterReset(t *testing.T) { // This test checks that past dials are not retried for some time. func TestDialStateCache(t *testing.T) { - wantStatic := []*discover.Node{ - {ID: uintID(1)}, - {ID: uintID(2)}, - {ID: uintID(3)}, + wantStatic := []*enode.Node{ + newNode(uintID(1), nil), + newNode(uintID(2), nil), + newNode(uintID(3), nil), } runDialTest(t, dialtest{ - init: newDialState(wantStatic,nil, fakeTable{}, 0, nil, 75), + init: newDialState(wantStatic, nil, fakeTable{}, 0, nil, 75), rounds: []round{ // Static dials are launched for the nodes that // aren't yet connected. { peers: nil, new: []task{ - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}}, - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}}, - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)}, }, }, // No new tasks are launched in this round because all static // nodes are either connected or still being dialed. { peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, id: uintID(1)}}, - {rw: &conn{flags: staticDialedConn, id: uintID(2)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: staticDialedConn, node: newNode(uintID(2), nil)}}, }, done: []task{ - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}}, - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)}, }, }, // A salvage task is launched to wait for node 3's history // entry to expire. { peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, }, done: []task{ - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)}, }, new: []task{ &waitExpireTask{Duration: 14 * time.Second}, @@ -609,18 +621,18 @@ func TestDialStateCache(t *testing.T) { // Still waiting for node 3's entry to expire in the cache. { peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, }, }, // The cache entry for node 3 has expired and is retried. { peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, id: uintID(1)}}, - {rw: &conn{flags: dynDialedConn, id: uintID(2)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, + {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, }, new: []task{ - &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}}, + &dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)}, }, }, }, @@ -628,12 +640,12 @@ func TestDialStateCache(t *testing.T) { } func TestDialResolve(t *testing.T) { - resolved := discover.NewNode(uintID(1), net.IP{127, 0, 55, 234}, 3333, 4444) + resolved := newNode(uintID(1), net.IP{127, 0, 55, 234}) table := &resolveMock{answer: resolved} - state := newDialState(nil,nil, table, 0, nil, 75) + state := newDialState(nil, nil, table, 0, nil, 75) // Check that the task is generated with an incomplete ID. - dest := discover.NewNode(uintID(1), nil, 0, 0) + dest := newNode(uintID(1), nil) state.addStatic(dest) tasks := state.newTasks(0, nil, time.Time{}) if !reflect.DeepEqual(tasks, []task{&dialTask{flags: staticDialedConn, dest: dest}}) { @@ -644,7 +656,7 @@ func TestDialResolve(t *testing.T) { config := Config{Dialer: TCPDialer{&net.Dialer{Deadline: time.Now().Add(-5 * time.Minute)}}} srv := &Server{ntab: table, Config: config} tasks[0].Do(srv) - if !reflect.DeepEqual(table.resolveCalls, []discover.NodeID{dest.ID}) { + if !reflect.DeepEqual(table.resolveCalls, []*enode.Node{dest}) { t.Fatalf("wrong resolve calls, got %v", table.resolveCalls) } @@ -672,25 +684,24 @@ next: return true } -func uintID(i uint32) discover.NodeID { - var id discover.NodeID +func uintID(i uint32) enode.ID { + var id enode.ID binary.BigEndian.PutUint32(id[:], i) return id } // implements discoverTable for TestDialResolve type resolveMock struct { - resolveCalls []discover.NodeID - answer *discover.Node + resolveCalls []*enode.Node + answer *enode.Node } -func (t *resolveMock) Resolve(id discover.NodeID) *discover.Node { - t.resolveCalls = append(t.resolveCalls, id) +func (t *resolveMock) Resolve(n *enode.Node) *enode.Node { + t.resolveCalls = append(t.resolveCalls, n) return t.answer } -func (t *resolveMock) Self() *discover.Node { return new(discover.Node) } -func (t *resolveMock) Close() {} -func (t *resolveMock) Bootstrap([]*discover.Node) {} -func (t *resolveMock) Lookup(discover.NodeID) []*discover.Node { return nil } -func (t *resolveMock) ReadRandomNodes(buf []*discover.Node) int { return 0 } +func (t *resolveMock) Self() *enode.Node { return new(enode.Node) } +func (t *resolveMock) Close() {} +func (t *resolveMock) LookupRandom() []*enode.Node { return nil } +func (t *resolveMock) ReadRandomNodes(buf []*enode.Node) int { return 0 } diff --git a/p2p/discover/node.go b/p2p/discover/node.go index 22740593ab..1d41dd8886 100644 --- a/p2p/discover/node.go +++ b/p2p/discover/node.go @@ -18,422 +18,88 @@ package discover import ( "crypto/ecdsa" - "crypto/elliptic" - "encoding/hex" "errors" - "fmt" "math/big" - "math/rand" "net" - "net/url" - "regexp" - "strconv" - "strings" "time" - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/crypto" -) - -const NodeIDBits = 512 + "github.com/AlayaNetwork/Alaya-Go/common/math" + "github.com/AlayaNetwork/Alaya-Go/crypto/secp256k1" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" -var ( - ZeroNodeID = MustHexID(NodeID{}.String()) + "github.com/AlayaNetwork/Alaya-Go/crypto" ) -// Node represents a host on the network. +// node represents a host on the network. // The fields of Node may not be modified. -type Node struct { - IP net.IP // len 4 for IPv4 or 16 for IPv6 - 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 -} - -// NewNode creates a new node. It is mostly meant to be used for -// testing purposes. -func NewNode(id NodeID, ip net.IP, udpPort, tcpPort uint16) *Node { - if ipv4 := ip.To4(); ipv4 != nil { - ip = ipv4 - } - return &Node{ - IP: ip, - UDP: udpPort, - TCP: tcpPort, - ID: id, - sha: crypto.Keccak256Hash(id[:]), - } -} - -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:// -// -// -// 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 16789 -// and UDP discovery port 30301. -// -// enode://@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 { - if ips, err := net.LookupIP(host); err == nil && len(ips) != 0 { - ip = ips[0] - } else { - 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 +type node struct { + enode.Node + addedAt time.Time // time when the node was added to the table } -// 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 -} +type encPubkey [64]byte -// 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 +func encodePubkey(key *ecdsa.PublicKey) encPubkey { + var e encPubkey + math.ReadBits(key.X, e[:len(e)/2]) + math.ReadBits(key.Y, e[len(e)/2:]) + return e } -// 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) +func decodePubkey(e encPubkey) (*ecdsa.PublicKey, error) { + p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)} + half := len(e) / 2 + p.X.SetBytes(e[:half]) + p.Y.SetBytes(e[half:]) + if !p.Curve.IsOnCurve(p.X, p.Y) { + return nil, errors.New("invalid secp256k1 curve point") } - return id + return p, nil } -// 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 +func (e encPubkey) id() enode.ID { + return enode.ID(crypto.Keccak256Hash(e[:])) } -// 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) +// recoverNodeKey computes the public key used to sign the +// given hash from the signature. +func recoverNodeKey(hash, sig []byte) (key encPubkey, err error) { + pubkey, err := secp256k1.RecoverPubkey(hash, sig) if err != nil { - panic(err) + return key, err } - return id + copy(key[:], pubkey[1:]) + return key, nil } -// 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 +func wrapNode(n *enode.Node) *node { + return &node{Node: *n} } -// 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)} - half := len(id) / 2 - p.X.SetBytes(id[:half]) - p.Y.SetBytes(id[half:]) - if !p.Curve.IsOnCurve(p.X, p.Y) { - return nil, errors.New("id is invalid secp256k1 curve point") +func wrapNodes(ns []*enode.Node) []*node { + result := make([]*node, len(ns)) + for i, n := range ns { + result[i] = wrapNode(n) } - return p, nil + return result } -// recoverNodeID computes the public key used to sign the -// given hash from the signature. -func recoverNodeID(hash, sig []byte) (id NodeID, err error) { - pubkey, err := crypto.Ecrecover(hash, sig) - if err != nil { - return id, err - } - if len(pubkey)-1 != len(id) { - return id, fmt.Errorf("recovered pubkey has %d bits, want %d bits", len(pubkey)*8, (len(id)+1)*8) - } - for i := range id { - id[i] = pubkey[i+1] - } - return id, nil +func unwrapNode(n *node) *enode.Node { + return &n.Node } -// 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 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 - } +func unwrapNodes(ns []*node) []*enode.Node { + result := make([]*enode.Node, len(ns)) + for i, n := range ns { + result[i] = unwrapNode(n) } - return 0 + return result } -// table of leading zero counts for bytes [0..255] -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, +func (n *node) addr() *net.UDPAddr { + return &net.UDPAddr{IP: n.IP(), Port: n.UDP()} } -// 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 +func (n *node) String() string { + return n.Node.String() } diff --git a/p2p/discover/node_test.go b/p2p/discover/node_test.go deleted file mode 100644 index 037814d589..0000000000 --- a/p2p/discover/node_test.go +++ /dev/null @@ -1,336 +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 . - -package discover - -import ( - "bytes" - "fmt" - "math/big" - "math/rand" - "net" - "reflect" - "strings" - "testing" - "testing/quick" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/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, 16789) - 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]:16789?discport=52150 - // n1.Incomplete() -> false - // n2: enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439 - // n2.Incomplete() -> true -} - -var parseNodeTests = []struct { - rawurl string - wantError string - wantResult *Node -}{ - { - rawurl: "http://foobar", - wantError: `invalid URL scheme, want "enode"`, - }, - { - rawurl: "enode://01010101@123.124.125.126:3", - wantError: `invalid node ID (wrong length, want 128 hex chars)`, - }, - // Complete nodes with IP address. - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3", - wantError: `invalid IP address`, - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo", - wantError: `invalid port`, - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo", - wantError: `invalid discport in query`, - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{0x7f, 0x0, 0x0, 0x1}, - 52150, - 52150, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.ParseIP("::"), - 52150, - 52150, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), - 52150, - 52150, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{0x7f, 0x0, 0x0, 0x1}, - 22334, - 52150, - ), - }, - // Incomplete nodes with no address. - { - rawurl: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - nil, 0, 0, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - nil, 0, 0, - ), - }, - // Invalid URLs - { - rawurl: "01010101", - wantError: `invalid node ID (wrong length, want 128 hex chars)`, - }, - { - rawurl: "enode://01010101", - wantError: `invalid node ID (wrong length, want 128 hex chars)`, - }, - { - // This test checks that errors from url.Parse are handled. - rawurl: "://foo", - wantError: `parse ://foo: missing protocol scheme`, - }, -} - -func TestParseNode(t *testing.T) { - for _, test := range parseNodeTests { - n, err := ParseNode(test.rawurl) - if test.wantError != "" { - if err == nil { - t.Errorf("test %q:\n got nil error, expected %#q", test.rawurl, test.wantError) - continue - } - //else if err.Error() != test.wantError { - // t.Errorf("test %q:\n got error %#q, expected %#q", test.rawurl, err.Error(), test.wantError) - // continue - //} - } else { - if err != nil { - t.Errorf("test %q:\n unexpected error: %v", test.rawurl, err) - continue - } - if !reflect.DeepEqual(n, test.wantResult) { - t.Errorf("test %q:\n result mismatch:\ngot: %#v, want: %#v", test.rawurl, n, test.wantResult) - } - } - } -} - -func TestNodeString(t *testing.T) { - for i, test := range parseNodeTests { - if test.wantError == "" && strings.HasPrefix(test.rawurl, "enode://") { - str := test.wantResult.String() - if str != test.rawurl { - t.Errorf("test %d: Node.String() mismatch:\ngot: %s\nwant: %s", i, str, test.rawurl) - } - } - } -} - -func TestHexID(t *testing.T) { - 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 := MustHexID("0x000000000000000000000000000000000000000000000000000000000000000000000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") - id2 := MustHexID("000000000000000000000000000000000000000000000000000000000000000000000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") - - if id1 != ref { - t.Errorf("wrong id1\ngot %v\nwant %v", id1[:], ref[:]) - } - if id2 != ref { - t.Errorf("wrong id2\ngot %v\nwant %v", id2[:], ref[:]) - } -} - -func TestNodeID_textEncoding(t *testing.T) { - ref := NodeID{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, - 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 := "01020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364" - - text, err := ref.MarshalText() - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(text, []byte(hex)) { - t.Fatalf("text encoding did not match\nexpected: %s\ngot: %s", hex, text) - } - - id := new(NodeID) - if err := id.UnmarshalText(text); err != nil { - t.Fatal(err) - } - if *id != ref { - t.Fatalf("text decoding did not match\nexpected: %s\ngot: %s", ref, id) - } -} - -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) { - distcmpBig := func(target, a, b common.Hash) int { - tbig := new(big.Int).SetBytes(target[:]) - abig := new(big.Int).SetBytes(a[:]) - bbig := new(big.Int).SetBytes(b[:]) - return new(big.Int).Xor(tbig, abig).Cmp(new(big.Int).Xor(tbig, bbig)) - } - if err := quick.CheckEqual(distcmp, distcmpBig, quickcfg()); err != nil { - t.Error(err) - } -} - -// the random tests is likely to miss the case where they're equal. -func TestNodeID_distcmpEqual(t *testing.T) { - base := common.Hash{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - 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 { - t.Errorf("distcmp(base, x, x) != 0") - } -} - -func TestNodeID_logdist(t *testing.T) { - logdistBig := func(a, b common.Hash) int { - abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:]) - return new(big.Int).Xor(abig, bbig).BitLen() - } - if err := quick.CheckEqual(logdist, logdistBig, quickcfg()); err != nil { - t.Error(err) - } -} - -// the random tests is likely to miss the case where they're equal. -func TestNodeID_logdistEqual(t *testing.T) { - x := common.Hash{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - if 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) -} diff --git a/p2p/discover/table.go b/p2p/discover/table.go index 98686df018..cb6ab973f1 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -23,6 +23,7 @@ package discover import ( + "crypto/ecdsa" crand "crypto/rand" "encoding/binary" "fmt" @@ -32,6 +33,8 @@ import ( "sync" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/log" @@ -65,49 +68,44 @@ const ( type Table struct { mutex sync.Mutex // protects buckets, bucket content, nursery, rand 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 ips netutil.DistinctNetSet - db *nodeDB // database of known nodes + db *enode.DB // database of known nodes refreshReq chan chan struct{} initDone chan struct{} closeReq chan struct{} closed chan struct{} - nodeAddedHook func(*Node) // for testing + nodeAddedHook func(*node) // for testing net transport - self *Node // metadata of the local node + self *node // metadata of the local node } // transport is implemented by the UDP transport. // it is an interface so we can test without opening lots of UDP // sockets and without generating a private key. type transport interface { - ping(NodeID, *net.UDPAddr) error - findnode(toid NodeID, addr *net.UDPAddr, target NodeID) ([]*Node, error) + ping(enode.ID, *net.UDPAddr) error + findnode(toid enode.ID, addr *net.UDPAddr, target encPubkey) ([]*node, error) close() } // bucket contains nodes, ordered by their last activity. the entry // that was most recently active is the first element in entries. type bucket struct { - entries []*Node // live entries, sorted by time of last contact - replacements []*Node // recently seen nodes to be used if revalidation fails + entries []*node // live entries, sorted by time of last contact + replacements []*node // recently seen nodes to be used if revalidation fails ips netutil.DistinctNetSet } -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, nodeDBVersion, ourID) - if err != nil { - return nil, err - } +func newTable(t transport, self *enode.Node, db *enode.DB, bootnodes []*enode.Node) (*Table, error) { tab := &Table{ net: t, db: db, - self: NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)), + self: wrapNode(self), refreshReq: make(chan chan struct{}), initDone: make(chan struct{}), closeReq: make(chan struct{}), @@ -125,10 +123,7 @@ func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string } tab.seedRand() tab.loadSeedNodes() - // 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() return tab, nil } @@ -143,15 +138,13 @@ func (tab *Table) seedRand() { } // Self returns the local node. -// The returned node should not be modified by the caller. -func (tab *Table) Self() *Node { - return tab.self +func (tab *Table) Self() *enode.Node { + return unwrapNode(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) { +// ReadRandomNodes fills the given slice with random nodes from the table. The results +// are guaranteed to be unique for a single invocation, no node will appear twice. +func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) { if !tab.isInitDone() { return 0 } @@ -159,7 +152,7 @@ func (tab *Table) ReadRandomNodes(buf []*Node) (n int) { defer tab.mutex.Unlock() // Find all non-empty buckets and get a fresh slice of their entries. - var buckets [][]*Node + var buckets [][]*node for _, b := range &tab.buckets { if len(b.entries) > 0 { buckets = append(buckets, b.entries) @@ -177,7 +170,7 @@ func (tab *Table) ReadRandomNodes(buf []*Node) (n int) { var i, j int for ; i < len(buf); i, j = i+1, (j+1)%len(buckets) { b := buckets[j] - buf[i] = &(*b[0]) + buf[i] = unwrapNode(b[0]) buckets[j] = b[1:] if len(b) == 1 { buckets = append(buckets[:j], buckets[j+1:]...) @@ -202,20 +195,13 @@ func (tab *Table) Close() { // setFallbackNodes sets the initial points of contact. These nodes // are used to connect to the network if the table is empty and there // are no known nodes in the database. -func (tab *Table) setFallbackNodes(nodes []*Node) error { +func (tab *Table) setFallbackNodes(nodes []*enode.Node) error { for _, n := range nodes { - if err := n.validateComplete(); err != nil { - return fmt.Errorf("bad bootstrap/fallback node %q (%v)", n, err) + if err := n.ValidateComplete(); err != nil { + return fmt.Errorf("bad bootstrap node %q: %v", n, err) } } - 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) - } + tab.nursery = wrapNodes(nodes) return nil } @@ -231,47 +217,48 @@ func (tab *Table) isInitDone() bool { // Resolve searches for a specific node with the given ID. // It returns nil if the node could not be found. -func (tab *Table) Resolve(targetID NodeID) *Node { +func (tab *Table) Resolve(n *enode.Node) *enode.Node { // If the node is present in the local table, no // network interaction is required. - hash := crypto.Keccak256Hash(targetID[:]) + hash := n.ID() tab.mutex.Lock() cl := tab.closest(hash, 1) tab.mutex.Unlock() - if len(cl.entries) > 0 && cl.entries[0].ID == targetID { - return cl.entries[0] + if len(cl.entries) > 0 && cl.entries[0].ID() == hash { + return unwrapNode(cl.entries[0]) } // Otherwise, do a network lookup. - result := tab.Lookup(targetID) + result := tab.lookup(encodePubkey(n.Pubkey()), true) for _, n := range result { - if n.ID == targetID { - return n + if n.ID() == hash { + return unwrapNode(n) } } return nil } -// Lookup performs a network search for nodes close -// to the given target. It approaches the 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(targetID NodeID) []*Node { - return tab.lookup(targetID, true) +// LookupRandom finds random nodes in the network. +func (tab *Table) LookupRandom() []*enode.Node { + var target encPubkey + crand.Read(target[:]) + return unwrapNodes(tab.lookup(target, true)) } -func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node { +// lookup performs a network search for nodes close to the given target. It approaches the +// 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 ( - target = crypto.Keccak256Hash(targetID[:]) - asked = make(map[NodeID]bool) - seen = make(map[NodeID]bool) - reply = make(chan []*Node, alpha) + target = enode.ID(crypto.Keccak256Hash(targetKey[:])) + asked = make(map[enode.ID]bool) + seen = make(map[enode.ID]bool) + reply = make(chan []*node, alpha) pendingQueries = 0 result *nodesByDistance ) // don't query further if we hit ourself. // unlikely to happen often in practice. - asked[tab.self.ID] = true + asked[tab.self.ID()] = true for { tab.mutex.Lock() @@ -293,10 +280,10 @@ func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node { // ask the alpha closest nodes that we haven't asked yet for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ { n := result.entries[i] - if !asked[n.ID] { - asked[n.ID] = true + if !asked[n.ID()] { + asked[n.ID()] = true pendingQueries++ - go tab.findnode(n, targetID, reply) + go tab.findnode(n, targetKey, reply) } } if pendingQueries == 0 { @@ -305,8 +292,8 @@ func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node { } // wait for the next reply for _, n := range <-reply { - if n != nil && !seen[n.ID] { - seen[n.ID] = true + if n != nil && !seen[n.ID()] { + seen[n.ID()] = true result.push(n, bucketSize) } } @@ -315,25 +302,24 @@ func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node { return result.entries } -func (tab *Table) findnode(n *Node, targetID NodeID, reply chan<- []*Node) { - fails := tab.db.findFails(n.ID) - r, err := tab.net.findnode(n.ID, n.addr(), targetID) - if len(r) == 0 { +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) + 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) + 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) + 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 { - log.Debug("Findnode list", "nodeIP", n.addr(), "nodeID", n.ID) tab.add(n) } reply <- r @@ -406,7 +392,6 @@ loop: for _, ch := range waiting { close(ch) } - tab.db.close() close(tab.closed) } @@ -422,7 +407,11 @@ func (tab *Table) doRefresh(done chan struct{}) { tab.loadSeedNodes() // Run self lookup to discover new neighbor nodes. - tab.lookup(tab.self.ID, false) + // We can only do this if we have a secp256k1 identity. + 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 // perform a lookup in the least recently used bucket. We cannot @@ -431,19 +420,19 @@ func (tab *Table) doRefresh(done chan struct{}) { // sha3 preimage that falls into a chosen bucket. // We perform a few lookups with a random target instead. for i := 0; i < 3; i++ { - var target NodeID + var target encPubkey crand.Read(target[:]) tab.lookup(target, false) } } func (tab *Table) loadSeedNodes() { - seeds := tab.db.querySeeds(seedCount, seedMaxAge) + seeds := wrapNodes(tab.db.QuerySeeds(seedCount, seedMaxAge)) seeds = append(seeds, tab.nursery...) for i := range seeds { seed := seeds[i] - age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.lastPongReceived(seed.ID)) }} - log.Trace("Found seed node in database", "id", seed.ID, "addr", seed.addr(), "age", age) + age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.LastPongReceived(seed.ID())) }} + log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age) tab.add(seed) } } @@ -460,28 +449,28 @@ func (tab *Table) doRevalidate(done chan<- struct{}) { } // Ping the selected node and wait for a pong. - err := tab.net.ping(last.ID, last.addr()) + err := tab.net.ping(last.ID(), last.addr()) tab.mutex.Lock() defer tab.mutex.Unlock() b := tab.buckets[bi] if err == nil { // The node responded, move it to the front. - log.Trace("Revalidated node", "b", bi, "id", last.ID) + log.Debug("Revalidated node", "b", bi, "id", last.ID()) b.bump(last) return } // No reply received, pick a replacement or delete the node if there aren't // any replacements. if r := tab.replace(b, last); r != nil { - log.Trace("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 { - log.Trace("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. -func (tab *Table) nodeToRevalidate() (n *Node, bi int) { +func (tab *Table) nodeToRevalidate() (n *node, bi int) { tab.mutex.Lock() defer tab.mutex.Unlock() @@ -512,7 +501,7 @@ func (tab *Table) copyLiveNodes() { for _, b := range &tab.buckets { for _, n := range b.entries { if now.Sub(n.addedAt) >= seedMinTableTime { - tab.db.updateNode(n) + tab.db.UpdateNode(unwrapNode(n)) } } } @@ -520,7 +509,7 @@ func (tab *Table) copyLiveNodes() { // closest returns the n nodes in the table that are closest to the // given id. The caller must hold tab.mutex. -func (tab *Table) closest(target common.Hash, nresults int) *nodesByDistance { +func (tab *Table) closest(target enode.ID, nresults int) *nodesByDistance { // This is a very wasteful way to find the closest nodes but // obviously correct. I believe that tree-based buckets would make // this easier to implement efficiently. @@ -541,8 +530,8 @@ func (tab *Table) len() (n int) { } // bucket returns the bucket for the given node ID hash. -func (tab *Table) bucket(sha common.Hash) *bucket { - d := logdist(tab.self.sha, sha) +func (tab *Table) bucket(id enode.ID) *bucket { + d := enode.LogDist(tab.self.ID(), id) if d <= bucketMinDistance { return tab.buckets[0] } @@ -554,11 +543,14 @@ func (tab *Table) bucket(sha common.Hash) *bucket { // least recently active node in the bucket does not respond to a ping packet. // // The caller must not hold tab.mutex. -func (tab *Table) add(n *Node) { +func (tab *Table) add(n *node) { + if n.ID() == tab.self.ID() { + return + } + tab.mutex.Lock() defer tab.mutex.Unlock() - - b := tab.bucket(n.sha) + 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) @@ -571,7 +563,7 @@ func (tab *Table) add(n *Node) { // table could be filled by just sending ping repeatedly. // // The caller must not hold tab.mutex. -func (tab *Table) addThroughPing(n *Node) { +func (tab *Table) addThroughPing(n *node) { if !tab.isInitDone() { return } @@ -580,15 +572,15 @@ func (tab *Table) addThroughPing(n *Node) { // 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. -func (tab *Table) stuff(nodes []*Node) { +func (tab *Table) stuff(nodes []*node) { tab.mutex.Lock() defer tab.mutex.Unlock() for _, n := range nodes { - if n.ID == tab.self.ID { + if n.ID() == tab.self.ID() { continue // don't add self } - b := tab.bucket(n.sha) + b := tab.bucket(n.ID()) if len(b.entries) < bucketSize { tab.bumpOrAdd(b, n) } @@ -596,11 +588,11 @@ func (tab *Table) stuff(nodes []*Node) { } // delete removes an entry from the node table. It is used to evacuate dead nodes. -func (tab *Table) delete(node *Node) { +func (tab *Table) delete(node *node) { tab.mutex.Lock() defer tab.mutex.Unlock() - tab.deleteInBucket(tab.bucket(node.sha), node) + tab.deleteInBucket(tab.bucket(node.ID()), node) } func (tab *Table) addIP(b *bucket, ip net.IP) bool { @@ -627,27 +619,27 @@ func (tab *Table) removeIP(b *bucket, ip net.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 { - if e.ID == n.ID { + if e.ID() == n.ID() { return // already in list } } - if !tab.addIP(b, n.IP) { + if !tab.addIP(b, n.IP()) { return } - var removed *Node + var removed *node b.replacements, removed = pushNode(b.replacements, n, maxReplacements) 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 // last entry in the bucket. If 'last' isn't the last entry, it has either been replaced // with someone else or became active. -func (tab *Table) replace(b *bucket, last *Node) *Node { - if len(b.entries) == 0 || b.entries[len(b.entries)-1].ID != last.ID { +func (tab *Table) replace(b *bucket, last *node) *node { + if len(b.entries) == 0 || b.entries[len(b.entries)-1].ID() != last.ID() { // Entry has moved, don't replace it. return nil } @@ -659,15 +651,15 @@ func (tab *Table) replace(b *bucket, last *Node) *Node { r := b.replacements[tab.rand.Intn(len(b.replacements))] b.replacements = deleteNode(b.replacements, r) b.entries[len(b.entries)-1] = r - tab.removeIP(b, last.IP) + tab.removeIP(b, last.IP()) return r } // bump moves the given node to the front of the bucket entry 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 { - if b.entries[i].ID == n.ID { + if b.entries[i].ID() == n.ID() { // move it to the front copy(b.entries[1:], b.entries[:i]) b.entries[0] = n @@ -679,11 +671,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 // 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) { return true } - if len(b.entries) >= bucketSize || !tab.addIP(b, n.IP) { + if len(b.entries) >= bucketSize || !tab.addIP(b, n.IP()) { return false } b.entries, _ = pushNode(b.entries, n, bucketSize) @@ -695,13 +687,13 @@ func (tab *Table) bumpOrAdd(b *bucket, n *Node) bool { return true } -func (tab *Table) deleteInBucket(b *bucket, n *Node) { +func (tab *Table) deleteInBucket(b *bucket, n *node) { 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. -func pushNode(list []*Node, n *Node, max int) ([]*Node, *Node) { +func pushNode(list []*node, n *node, max int) ([]*node, *node) { if len(list) < max { list = append(list, nil) } @@ -712,9 +704,9 @@ func pushNode(list []*Node, n *Node, max int) ([]*Node, *Node) { } // deleteNode removes n from list. -func deleteNode(list []*Node, n *Node) []*Node { +func deleteNode(list []*node, n *node) []*node { for i := range list { - if list[i].ID == n.ID { + if list[i].ID() == n.ID() { return append(list[:i], list[i+1:]...) } } @@ -724,14 +716,14 @@ func deleteNode(list []*Node, n *Node) []*Node { // nodesByDistance is a list of nodes, ordered by // distance to target. type nodesByDistance struct { - entries []*Node - target common.Hash + entries []*node + target enode.ID } // 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 { - return distcmp(h.target, h.entries[i].sha, n.sha) > 0 + return enode.DistCmp(h.target, h.entries[i].ID(), n.ID()) > 0 }) if len(h.entries) < maxElems { h.entries = append(h.entries, n) diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go index df741c49c3..2d5aa26182 100644 --- a/p2p/discover/table_test.go +++ b/p2p/discover/table_test.go @@ -20,15 +20,15 @@ import ( "crypto/ecdsa" "fmt" "math/rand" - "sync" - "net" "reflect" "testing" "testing/quick" "time" - "github.com/AlayaNetwork/Alaya-Go/common" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/crypto" ) @@ -49,24 +49,26 @@ func TestTable_pingReplace(t *testing.T) { func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding bool) { transport := newPingRecorder() - tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "", nil) + tab, db := newTestTable(transport) defer tab.Close() + defer db.Close() <-tab.initDone // Fill up the sender's bucket. - pingSender := NewNode(MustHexID("a502af0f59b2aab7746995408c79e9ca312d2793cc997e44fc55eda62f0150bbb8c59a6f9269ba3a081518b62699ee807c7c19c20125ddfccca872608af9e370"), net.IP{}, 99, 99) + pingKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8") + pingSender := wrapNode(enode.NewV4(&pingKey.PublicKey, net.IP{}, 99, 99)) last := fillBucket(tab, pingSender) // Add the sender as if it just pinged us. Revalidate should replace the last node in // its bucket if it is unresponsive. Revalidate again to ensure that - transport.dead[last.ID] = !lastInBucketIsResponding - transport.dead[pingSender.ID] = !newNodeIsResponding + transport.dead[last.ID()] = !lastInBucketIsResponding + transport.dead[pingSender.ID()] = !newNodeIsResponding tab.add(pingSender) tab.doRevalidate(make(chan struct{}, 1)) tab.doRevalidate(make(chan struct{}, 1)) - if !transport.pinged[last.ID] { + if !transport.pinged[last.ID()] { // Oldest node in bucket is pinged to see whether it is still alive. t.Error("table did not ping last node in bucket") } @@ -77,14 +79,14 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding if !lastInBucketIsResponding && !newNodeIsResponding { wantSize-- } - if l := len(tab.bucket(pingSender.sha).entries); l != wantSize { - t.Errorf("wrong bucket size after add: got %d, want %d", l, wantSize) + if l := len(tab.bucket(pingSender.ID()).entries); l != wantSize { + t.Errorf("wrong bucket size after bond: got %d, want %d", l, wantSize) } - if found := contains(tab.bucket(pingSender.sha).entries, last.ID); found != lastInBucketIsResponding { + if found := contains(tab.bucket(pingSender.ID()).entries, last.ID()); found != lastInBucketIsResponding { t.Errorf("last entry found: %t, want: %t", found, lastInBucketIsResponding) } wantNewEntry := newNodeIsResponding && !lastInBucketIsResponding - if found := contains(tab.bucket(pingSender.sha).entries, pingSender.ID); found != wantNewEntry { + if found := contains(tab.bucket(pingSender.ID()).entries, pingSender.ID()); found != wantNewEntry { t.Errorf("new entry found: %t, want: %t", found, wantNewEntry) } } @@ -97,9 +99,9 @@ func TestBucket_bumpNoDuplicates(t *testing.T) { Values: func(args []reflect.Value, rand *rand.Rand) { // generate a random list of nodes. this will be the content of the bucket. n := rand.Intn(bucketSize-1) + 1 - nodes := make([]*Node, n) + nodes := make([]*node, n) for i := range nodes { - nodes[i] = nodeAtDistance(common.Hash{}, 200) + nodes[i] = nodeAtDistance(enode.ID{}, 200, intIP(200)) } args[0] = reflect.ValueOf(nodes) // generate random bump positions. @@ -111,8 +113,8 @@ func TestBucket_bumpNoDuplicates(t *testing.T) { }, } - prop := func(nodes []*Node, bumps []int) (ok bool) { - b := &bucket{entries: make([]*Node, len(nodes))} + prop := func(nodes []*node, bumps []int) (ok bool) { + b := &bucket{entries: make([]*node, len(nodes))} copy(b.entries, nodes) for i, pos := range bumps { b.bump(b.entries[pos]) @@ -134,12 +136,12 @@ func TestBucket_bumpNoDuplicates(t *testing.T) { // This checks that the table-wide IP limit is applied correctly. func TestTable_IPLimit(t *testing.T) { transport := newPingRecorder() - tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "", nil) + tab, db := newTestTable(transport) defer tab.Close() + defer db.Close() for i := 0; i < tableIPLimit+1; i++ { - n := nodeAtDistance(tab.self.sha, i) - n.IP = net.IP{172, 0, 1, byte(i)} + n := nodeAtDistance(tab.self.ID(), i, net.IP{172, 0, 1, byte(i)}) tab.add(n) } if tab.len() > tableIPLimit { @@ -147,16 +149,16 @@ func TestTable_IPLimit(t *testing.T) { } } -// This checks that the table-wide IP limit is applied correctly. +// This checks that the per-bucket IP limit is applied correctly. func TestTable_BucketIPLimit(t *testing.T) { transport := newPingRecorder() - tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "", nil) + tab, db := newTestTable(transport) defer tab.Close() + defer db.Close() d := 3 for i := 0; i < bucketIPLimit+1; i++ { - n := nodeAtDistance(tab.self.sha, d) - n.IP = net.IP{172, 0, 1, byte(i)} + n := nodeAtDistance(tab.self.ID(), d, net.IP{172, 0, 1, byte(i)}) tab.add(n) } if tab.len() > bucketIPLimit { @@ -164,69 +166,18 @@ 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) 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 (t *pingRecorder) close() {} - func TestTable_closest(t *testing.T) { t.Parallel() test := func(test *closeTest) bool { // for any node table, Target and N transport := newPingRecorder() - tab, _ := newTable(transport, test.Self, &net.UDPAddr{}, "", nil) + tab, db := newTestTable(transport) defer tab.Close() + defer db.Close() tab.stuff(test.All) - // check that doClosest(Target, N) returns nodes + // check that closest(Target, N) returns nodes result := tab.closest(test.Target, test.N).entries if hasDuplicates(result) { t.Errorf("result contains duplicates") @@ -252,15 +203,15 @@ func TestTable_closest(t *testing.T) { // check that the result nodes have minimum distance to target. for _, b := range tab.buckets { 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 } - farthestResult := result[len(result)-1].sha - if distcmp(test.Target, n.sha, farthestResult) < 0 { + farthestResult := result[len(result)-1].ID() + if enode.DistCmp(test.Target, n.ID(), farthestResult) < 0 { t.Errorf("table contains node that is closer to target but it's not in result") t.Logf(" Target: %v", test.Target) t.Logf(" Farthest Result: %v", farthestResult) - t.Logf(" ID: %v", n.ID) + t.Logf(" ID: %v", n.ID()) return false } } @@ -277,25 +228,26 @@ func TestTable_ReadRandomNodesGetAll(t *testing.T) { MaxCount: 200, Rand: rand.New(rand.NewSource(time.Now().Unix())), Values: func(args []reflect.Value, rand *rand.Rand) { - args[0] = reflect.ValueOf(make([]*Node, rand.Intn(1000))) + args[0] = reflect.ValueOf(make([]*enode.Node, rand.Intn(1000))) }, } - test := func(buf []*Node) bool { + test := func(buf []*enode.Node) bool { transport := newPingRecorder() - tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "", nil) + tab, db := newTestTable(transport) defer tab.Close() + defer db.Close() <-tab.initDone for i := 0; i < len(buf); i++ { ld := cfg.Rand.Intn(len(tab.buckets)) - tab.stuff([]*Node{nodeAtDistance(tab.self.sha, ld)}) + tab.stuff([]*node{nodeAtDistance(tab.self.ID(), ld, intIP(ld))}) } gotN := tab.ReadRandomNodes(buf) if gotN != tab.len() { t.Errorf("wrong number of nodes, got %d, want %d", gotN, tab.len()) return false } - if hasDuplicates(buf[:gotN]) { + if hasDuplicates(wrapNodes(buf[:gotN])) { t.Errorf("result contains duplicates") return false } @@ -307,41 +259,43 @@ func TestTable_ReadRandomNodesGetAll(t *testing.T) { } type closeTest struct { - Self NodeID - Target common.Hash - All []*Node + Self enode.ID + Target enode.ID + All []*node N int } func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value { t := &closeTest{ - Self: gen(NodeID{}, rand).(NodeID), - Target: gen(common.Hash{}, rand).(common.Hash), + Self: gen(enode.ID{}, rand).(enode.ID), + Target: gen(enode.ID{}, rand).(enode.ID), N: rand.Intn(bucketSize), } - for _, id := range gen([]NodeID{}, rand).([]NodeID) { - t.All = append(t.All, &Node{ID: id}) + for _, id := range gen([]enode.ID{}, rand).([]enode.ID) { + n := enode.SignNull(new(enr.Record), id) + t.All = append(t.All, wrapNode(n)) } return reflect.ValueOf(t) } func TestTable_Lookup(t *testing.T) { - self := nodeAtDistance(common.Hash{}, 0) - tab, _ := newTable(lookupTestnet, self.ID, &net.UDPAddr{}, "", nil) + tab, db := newTestTable(lookupTestnet) defer tab.Close() + defer db.Close() // lookup on empty table returns no nodes - if results := tab.Lookup(lookupTestnet.target); len(results) > 0 { + if results := tab.lookup(lookupTestnet.target, false); 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) - seed := NewNode(lookupTestnet.dists[256][0], net.IP{}, 256, 0) - tab.stuff([]*Node{seed}) + seedKey, _ := decodePubkey(lookupTestnet.dists[256][0]) + seed := wrapNode(enode.NewV4(seedKey, net.IP{}, 0, 256)) + tab.stuff([]*node{seed}) - results := tab.Lookup(lookupTestnet.target) + results := tab.lookup(lookupTestnet.target, true) t.Logf("results:") for _, e := range results { - t.Logf(" ld=%d, %x", logdist(lookupTestnet.targetSha, e.sha), e.sha[:]) + t.Logf(" ld=%d, %x", enode.LogDist(lookupTestnet.targetSha, e.ID()), e.ID().Bytes()) } if len(results) != bucketSize { t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize) @@ -358,235 +312,236 @@ func TestTable_Lookup(t *testing.T) { // This is the test network for the Lookup test. // The nodes were obtained by running testnet.mine with a random NodeID as target. var lookupTestnet = &preminedTestnet{ - target: MustHexID("166aea4f556532c6d34e8b740e5d314af7e9ac0ca79833bd751d6b665f12dfd38ec563c363b32f02aef4a80b44fd3def94612d497b99cb5f17fd24de454927ec"), - 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][]NodeID{ + target: hexEncPubkey("166aea4f556532c6d34e8b740e5d314af7e9ac0ca79833bd751d6b665f12dfd38ec563c363b32f02aef4a80b44fd3def94612d497b99cb5f17fd24de454927ec"), + targetSha: enode.HexID("5c944ee51c5ae9f72a95eccb8aed0374eecb5119d720cbea6813e8e0d6ad9261"), + dists: [257][]encPubkey{ 240: { - MustHexID("2001ad5e3e80c71b952161bc0186731cf5ffe942d24a79230a0555802296238e57ea7a32f5b6f18564eadc1c65389448481f8c9338df0a3dbd18f708cbc2cbcb"), - MustHexID("6ba3f4f57d084b6bf94cc4555b8c657e4a8ac7b7baf23c6874efc21dd1e4f56b7eb2721e07f5242d2f1d8381fc8cae535e860197c69236798ba1ad231b105794"), + hexEncPubkey("2001ad5e3e80c71b952161bc0186731cf5ffe942d24a79230a0555802296238e57ea7a32f5b6f18564eadc1c65389448481f8c9338df0a3dbd18f708cbc2cbcb"), + hexEncPubkey("6ba3f4f57d084b6bf94cc4555b8c657e4a8ac7b7baf23c6874efc21dd1e4f56b7eb2721e07f5242d2f1d8381fc8cae535e860197c69236798ba1ad231b105794"), }, 244: { - MustHexID("696ba1f0a9d55c59246f776600542a9e6432490f0cd78f8bb55a196918df2081a9b521c3c3ba48e465a75c10768807717f8f689b0b4adce00e1c75737552a178"), + hexEncPubkey("696ba1f0a9d55c59246f776600542a9e6432490f0cd78f8bb55a196918df2081a9b521c3c3ba48e465a75c10768807717f8f689b0b4adce00e1c75737552a178"), }, 246: { - MustHexID("d6d32178bdc38416f46ffb8b3ec9e4cb2cfff8d04dd7e4311a70e403cb62b10be1b447311b60b4f9ee221a8131fc2cbd45b96dd80deba68a949d467241facfa8"), - MustHexID("3ea3d04a43a3dfb5ac11cffc2319248cf41b6279659393c2f55b8a0a5fc9d12581a9d97ef5d8ff9b5abf3321a290e8f63a4f785f450dc8a672aba3ba2ff4fdab"), - MustHexID("2fc897f05ae585553e5c014effd3078f84f37f9333afacffb109f00ca8e7a3373de810a3946be971cbccdfd40249f9fe7f322118ea459ac71acca85a1ef8b7f4"), + hexEncPubkey("d6d32178bdc38416f46ffb8b3ec9e4cb2cfff8d04dd7e4311a70e403cb62b10be1b447311b60b4f9ee221a8131fc2cbd45b96dd80deba68a949d467241facfa8"), + hexEncPubkey("3ea3d04a43a3dfb5ac11cffc2319248cf41b6279659393c2f55b8a0a5fc9d12581a9d97ef5d8ff9b5abf3321a290e8f63a4f785f450dc8a672aba3ba2ff4fdab"), + hexEncPubkey("2fc897f05ae585553e5c014effd3078f84f37f9333afacffb109f00ca8e7a3373de810a3946be971cbccdfd40249f9fe7f322118ea459ac71acca85a1ef8b7f4"), }, 247: { - MustHexID("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"), - MustHexID("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"), - MustHexID("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"), - MustHexID("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"), - MustHexID("8b58c6073dd98bbad4e310b97186c8f822d3a5c7d57af40e2136e88e315afd115edb27d2d0685a908cfe5aa49d0debdda6e6e63972691d6bd8c5af2d771dd2a9"), - MustHexID("2cbb718b7dc682da19652e7d9eb4fefaf7b7147d82c1c2b6805edf77b85e29fde9f6da195741467ff2638dc62c8d3e014ea5686693c15ed0080b6de90354c137"), - MustHexID("e84027696d3f12f2de30a9311afea8fbd313c2360daff52bb5fc8c7094d5295758bec3134e4eef24e4cdf377b40da344993284628a7a346eba94f74160998feb"), - MustHexID("f1357a4f04f9d33753a57c0b65ba20a5d8777abbffd04e906014491c9103fb08590e45548d37aa4bd70965e2e81ddba94f31860348df01469eec8c1829200a68"), - MustHexID("4ab0a75941b12892369b4490a1928c8ca52a9ad6d3dffbd1d8c0b907bc200fe74c022d011ec39b64808a39c0ca41f1d3254386c3e7733e7044c44259486461b6"), - MustHexID("d45150a72dc74388773e68e03133a3b5f51447fe91837d566706b3c035ee4b56f160c878c6273394daee7f56cc398985269052f22f75a8057df2fe6172765354"), + hexEncPubkey("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"), + hexEncPubkey("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"), + hexEncPubkey("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"), + hexEncPubkey("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"), + hexEncPubkey("8b58c6073dd98bbad4e310b97186c8f822d3a5c7d57af40e2136e88e315afd115edb27d2d0685a908cfe5aa49d0debdda6e6e63972691d6bd8c5af2d771dd2a9"), + hexEncPubkey("2cbb718b7dc682da19652e7d9eb4fefaf7b7147d82c1c2b6805edf77b85e29fde9f6da195741467ff2638dc62c8d3e014ea5686693c15ed0080b6de90354c137"), + hexEncPubkey("e84027696d3f12f2de30a9311afea8fbd313c2360daff52bb5fc8c7094d5295758bec3134e4eef24e4cdf377b40da344993284628a7a346eba94f74160998feb"), + hexEncPubkey("f1357a4f04f9d33753a57c0b65ba20a5d8777abbffd04e906014491c9103fb08590e45548d37aa4bd70965e2e81ddba94f31860348df01469eec8c1829200a68"), + hexEncPubkey("4ab0a75941b12892369b4490a1928c8ca52a9ad6d3dffbd1d8c0b907bc200fe74c022d011ec39b64808a39c0ca41f1d3254386c3e7733e7044c44259486461b6"), + hexEncPubkey("d45150a72dc74388773e68e03133a3b5f51447fe91837d566706b3c035ee4b56f160c878c6273394daee7f56cc398985269052f22f75a8057df2fe6172765354"), }, 248: { - MustHexID("6aadfce366a189bab08ac84721567483202c86590642ea6d6a14f37ca78d82bdb6509eb7b8b2f6f63c78ae3ae1d8837c89509e41497d719b23ad53dd81574afa"), - MustHexID("a605ecfd6069a4cf4cf7f5840e5bc0ce10d23a3ac59e2aaa70c6afd5637359d2519b4524f56fc2ca180cdbebe54262f720ccaae8c1b28fd553c485675831624d"), - MustHexID("29701451cb9448ca33fc33680b44b840d815be90146eb521641efbffed0859c154e8892d3906eae9934bfacee72cd1d2fa9dd050fd18888eea49da155ab0efd2"), - MustHexID("3ed426322dee7572b08592e1e079f8b6c6b30e10e6243edd144a6a48fdbdb83df73a6e41b1143722cb82604f2203a32758610b5d9544f44a1a7921ba001528c1"), - MustHexID("b2e2a2b7fdd363572a3256e75435fab1da3b16f7891a8bd2015f30995dae665d7eabfd194d87d99d5df628b4bbc7b04e5b492c596422dd8272746c7a1b0b8e4f"), - MustHexID("0c69c9756162c593e85615b814ce57a2a8ca2df6c690b9c4e4602731b61e1531a3bbe3f7114271554427ffabea80ad8f36fa95a49fa77b675ae182c6ccac1728"), - MustHexID("8d28be21d5a97b0876442fa4f5e5387f5bf3faad0b6f13b8607b64d6e448c0991ca28dd7fe2f64eb8eadd7150bff5d5666aa6ed868b84c71311f4ba9a38569dd"), - MustHexID("2c677e1c64b9c9df6359348a7f5f33dc79e22f0177042486d125f8b6ca7f0dc756b1f672aceee5f1746bcff80aaf6f92a8dc0c9fbeb259b3fa0da060de5ab7e8"), - MustHexID("3994880f94a8678f0cd247a43f474a8af375d2a072128da1ad6cae84a244105ff85e94fc7d8496f639468de7ee998908a91c7e33ef7585fff92e984b210941a1"), - MustHexID("b45a9153c08d002a48090d15d61a7c7dad8c2af85d4ff5bd36ce23a9a11e0709bf8d56614c7b193bc028c16cbf7f20dfbcc751328b64a924995d47b41e452422"), - MustHexID("057ab3a9e53c7a84b0f3fc586117a525cdd18e313f52a67bf31798d48078e325abe5cfee3f6c2533230cb37d0549289d692a29dd400e899b8552d4b928f6f907"), - MustHexID("0ddf663d308791eb92e6bd88a2f8cb45e4f4f35bb16708a0e6ff7f1362aa6a73fedd0a1b1557fb3365e38e1b79d6918e2fae2788728b70c9ab6b51a3b94a4338"), - MustHexID("f637e07ff50cc1e3731735841c4798411059f2023abcf3885674f3e8032531b0edca50fd715df6feb489b6177c345374d64f4b07d257a7745de393a107b013a5"), - MustHexID("e24ec7c6eec094f63c7b3239f56d311ec5a3e45bc4e622a1095a65b95eea6fe13e29f3b6b7a2cbfe40906e3989f17ac834c3102dd0cadaaa26e16ee06d782b72"), - MustHexID("b76ea1a6fd6506ef6e3506a4f1f60ed6287fff8114af6141b2ff13e61242331b54082b023cfea5b3083354a4fb3f9eb8be01fb4a518f579e731a5d0707291a6b"), - MustHexID("9b53a37950ca8890ee349b325032d7b672cab7eced178d3060137b24ef6b92a43977922d5bdfb4a3409a2d80128e02f795f9dae6d7d99973ad0e23a2afb8442f"), + hexEncPubkey("6aadfce366a189bab08ac84721567483202c86590642ea6d6a14f37ca78d82bdb6509eb7b8b2f6f63c78ae3ae1d8837c89509e41497d719b23ad53dd81574afa"), + hexEncPubkey("a605ecfd6069a4cf4cf7f5840e5bc0ce10d23a3ac59e2aaa70c6afd5637359d2519b4524f56fc2ca180cdbebe54262f720ccaae8c1b28fd553c485675831624d"), + hexEncPubkey("29701451cb9448ca33fc33680b44b840d815be90146eb521641efbffed0859c154e8892d3906eae9934bfacee72cd1d2fa9dd050fd18888eea49da155ab0efd2"), + hexEncPubkey("3ed426322dee7572b08592e1e079f8b6c6b30e10e6243edd144a6a48fdbdb83df73a6e41b1143722cb82604f2203a32758610b5d9544f44a1a7921ba001528c1"), + hexEncPubkey("b2e2a2b7fdd363572a3256e75435fab1da3b16f7891a8bd2015f30995dae665d7eabfd194d87d99d5df628b4bbc7b04e5b492c596422dd8272746c7a1b0b8e4f"), + hexEncPubkey("0c69c9756162c593e85615b814ce57a2a8ca2df6c690b9c4e4602731b61e1531a3bbe3f7114271554427ffabea80ad8f36fa95a49fa77b675ae182c6ccac1728"), + hexEncPubkey("8d28be21d5a97b0876442fa4f5e5387f5bf3faad0b6f13b8607b64d6e448c0991ca28dd7fe2f64eb8eadd7150bff5d5666aa6ed868b84c71311f4ba9a38569dd"), + hexEncPubkey("2c677e1c64b9c9df6359348a7f5f33dc79e22f0177042486d125f8b6ca7f0dc756b1f672aceee5f1746bcff80aaf6f92a8dc0c9fbeb259b3fa0da060de5ab7e8"), + hexEncPubkey("3994880f94a8678f0cd247a43f474a8af375d2a072128da1ad6cae84a244105ff85e94fc7d8496f639468de7ee998908a91c7e33ef7585fff92e984b210941a1"), + hexEncPubkey("b45a9153c08d002a48090d15d61a7c7dad8c2af85d4ff5bd36ce23a9a11e0709bf8d56614c7b193bc028c16cbf7f20dfbcc751328b64a924995d47b41e452422"), + hexEncPubkey("057ab3a9e53c7a84b0f3fc586117a525cdd18e313f52a67bf31798d48078e325abe5cfee3f6c2533230cb37d0549289d692a29dd400e899b8552d4b928f6f907"), + hexEncPubkey("0ddf663d308791eb92e6bd88a2f8cb45e4f4f35bb16708a0e6ff7f1362aa6a73fedd0a1b1557fb3365e38e1b79d6918e2fae2788728b70c9ab6b51a3b94a4338"), + hexEncPubkey("f637e07ff50cc1e3731735841c4798411059f2023abcf3885674f3e8032531b0edca50fd715df6feb489b6177c345374d64f4b07d257a7745de393a107b013a5"), + hexEncPubkey("e24ec7c6eec094f63c7b3239f56d311ec5a3e45bc4e622a1095a65b95eea6fe13e29f3b6b7a2cbfe40906e3989f17ac834c3102dd0cadaaa26e16ee06d782b72"), + hexEncPubkey("b76ea1a6fd6506ef6e3506a4f1f60ed6287fff8114af6141b2ff13e61242331b54082b023cfea5b3083354a4fb3f9eb8be01fb4a518f579e731a5d0707291a6b"), + hexEncPubkey("9b53a37950ca8890ee349b325032d7b672cab7eced178d3060137b24ef6b92a43977922d5bdfb4a3409a2d80128e02f795f9dae6d7d99973ad0e23a2afb8442f"), }, 249: { - MustHexID("675ae65567c3c72c50c73bc0fd4f61f202ea5f93346ca57b551de3411ccc614fad61cb9035493af47615311b9d44ee7a161972ee4d77c28fe1ec029d01434e6a"), - MustHexID("8eb81408389da88536ae5800392b16ef5109d7ea132c18e9a82928047ecdb502693f6e4a4cdd18b54296caf561db937185731456c456c98bfe7de0baf0eaa495"), - MustHexID("2adba8b1612a541771cb93a726a38a4b88e97b18eced2593eb7daf82f05a5321ca94a72cc780c306ff21e551a932fc2c6d791e4681907b5ceab7f084c3fa2944"), - MustHexID("b1b4bfbda514d9b8f35b1c28961da5d5216fe50548f4066f69af3b7666a3b2e06eac646735e963e5c8f8138a2fb95af15b13b23ff00c6986eccc0efaa8ee6fb4"), - MustHexID("d2139281b289ad0e4d7b4243c4364f5c51aac8b60f4806135de06b12b5b369c9e43a6eb494eab860d115c15c6fbb8c5a1b0e382972e0e460af395b8385363de7"), - MustHexID("4a693df4b8fc5bdc7cec342c3ed2e228d7c5b4ab7321ddaa6cccbeb45b05a9f1d95766b4002e6d4791c2deacb8a667aadea6a700da28a3eea810a30395701bbc"), - MustHexID("ab41611195ec3c62bb8cd762ee19fb182d194fd141f4a66780efbef4b07ce916246c022b841237a3a6b512a93431157edd221e854ed2a259b72e9c5351f44d0c"), - MustHexID("68e8e26099030d10c3c703ae7045c0a48061fb88058d853b3e67880014c449d4311014da99d617d3150a20f1a3da5e34bf0f14f1c51fe4dd9d58afd222823176"), - MustHexID("3fbcacf546fb129cd70fc48de3b593ba99d3c473798bc309292aca280320e0eacc04442c914cad5c4cf6950345ba79b0d51302df88285d4e83ee3fe41339eee7"), - MustHexID("1d4a623659f7c8f80b6c3939596afdf42e78f892f682c768ad36eb7bfba402dbf97aea3a268f3badd8fe7636be216edf3d67ee1e08789ebbc7be625056bd7109"), - MustHexID("a283c474ab09da02bbc96b16317241d0627646fcc427d1fe790b76a7bf1989ced90f92101a973047ae9940c92720dffbac8eff21df8cae468a50f72f9e159417"), - MustHexID("dbf7e5ad7f87c3dfecae65d87c3039e14ed0bdc56caf00ce81931073e2e16719d746295512ff7937a15c3b03603e7c41a4f9df94fcd37bb200dd8f332767e9cb"), - MustHexID("caaa070a26692f64fc77f30d7b5ae980d419b4393a0f442b1c821ef58c0862898b0d22f74a4f8c5d83069493e3ec0b92f17dc1fe6e4cd437c1ec25039e7ce839"), - MustHexID("874cc8d1213beb65c4e0e1de38ef5d8165235893ac74ab5ea937c885eaab25c8d79dad0456e9fd3e9450626cac7e107b004478fb59842f067857f39a47cee695"), - MustHexID("d94193f236105010972f5df1b7818b55846592a0445b9cdc4eaed811b8c4c0f7c27dc8cc9837a4774656d6b34682d6d329d42b6ebb55da1d475c2474dc3dfdf4"), - MustHexID("edd9af6aded4094e9785637c28fccbd3980cbe28e2eb9a411048a23c2ace4bd6b0b7088a7817997b49a3dd05fc6929ca6c7abbb69438dbdabe65e971d2a794b2"), + hexEncPubkey("675ae65567c3c72c50c73bc0fd4f61f202ea5f93346ca57b551de3411ccc614fad61cb9035493af47615311b9d44ee7a161972ee4d77c28fe1ec029d01434e6a"), + hexEncPubkey("8eb81408389da88536ae5800392b16ef5109d7ea132c18e9a82928047ecdb502693f6e4a4cdd18b54296caf561db937185731456c456c98bfe7de0baf0eaa495"), + hexEncPubkey("2adba8b1612a541771cb93a726a38a4b88e97b18eced2593eb7daf82f05a5321ca94a72cc780c306ff21e551a932fc2c6d791e4681907b5ceab7f084c3fa2944"), + hexEncPubkey("b1b4bfbda514d9b8f35b1c28961da5d5216fe50548f4066f69af3b7666a3b2e06eac646735e963e5c8f8138a2fb95af15b13b23ff00c6986eccc0efaa8ee6fb4"), + hexEncPubkey("d2139281b289ad0e4d7b4243c4364f5c51aac8b60f4806135de06b12b5b369c9e43a6eb494eab860d115c15c6fbb8c5a1b0e382972e0e460af395b8385363de7"), + hexEncPubkey("4a693df4b8fc5bdc7cec342c3ed2e228d7c5b4ab7321ddaa6cccbeb45b05a9f1d95766b4002e6d4791c2deacb8a667aadea6a700da28a3eea810a30395701bbc"), + hexEncPubkey("ab41611195ec3c62bb8cd762ee19fb182d194fd141f4a66780efbef4b07ce916246c022b841237a3a6b512a93431157edd221e854ed2a259b72e9c5351f44d0c"), + hexEncPubkey("68e8e26099030d10c3c703ae7045c0a48061fb88058d853b3e67880014c449d4311014da99d617d3150a20f1a3da5e34bf0f14f1c51fe4dd9d58afd222823176"), + hexEncPubkey("3fbcacf546fb129cd70fc48de3b593ba99d3c473798bc309292aca280320e0eacc04442c914cad5c4cf6950345ba79b0d51302df88285d4e83ee3fe41339eee7"), + hexEncPubkey("1d4a623659f7c8f80b6c3939596afdf42e78f892f682c768ad36eb7bfba402dbf97aea3a268f3badd8fe7636be216edf3d67ee1e08789ebbc7be625056bd7109"), + hexEncPubkey("a283c474ab09da02bbc96b16317241d0627646fcc427d1fe790b76a7bf1989ced90f92101a973047ae9940c92720dffbac8eff21df8cae468a50f72f9e159417"), + hexEncPubkey("dbf7e5ad7f87c3dfecae65d87c3039e14ed0bdc56caf00ce81931073e2e16719d746295512ff7937a15c3b03603e7c41a4f9df94fcd37bb200dd8f332767e9cb"), + hexEncPubkey("caaa070a26692f64fc77f30d7b5ae980d419b4393a0f442b1c821ef58c0862898b0d22f74a4f8c5d83069493e3ec0b92f17dc1fe6e4cd437c1ec25039e7ce839"), + hexEncPubkey("874cc8d1213beb65c4e0e1de38ef5d8165235893ac74ab5ea937c885eaab25c8d79dad0456e9fd3e9450626cac7e107b004478fb59842f067857f39a47cee695"), + hexEncPubkey("d94193f236105010972f5df1b7818b55846592a0445b9cdc4eaed811b8c4c0f7c27dc8cc9837a4774656d6b34682d6d329d42b6ebb55da1d475c2474dc3dfdf4"), + hexEncPubkey("edd9af6aded4094e9785637c28fccbd3980cbe28e2eb9a411048a23c2ace4bd6b0b7088a7817997b49a3dd05fc6929ca6c7abbb69438dbdabe65e971d2a794b2"), }, 250: { - MustHexID("53a5bd1215d4ab709ae8fdc2ced50bba320bced78bd9c5dc92947fb402250c914891786db0978c898c058493f86fc68b1c5de8a5cb36336150ac7a88655b6c39"), - MustHexID("b7f79e3ab59f79262623c9ccefc8f01d682323aee56ffbe295437487e9d5acaf556a9c92e1f1c6a9601f2b9eb6b027ae1aeaebac71d61b9b78e88676efd3e1a3"), - MustHexID("d374bf7e8d7ffff69cc00bebff38ef5bc1dcb0a8d51c1a3d70e61ac6b2e2d6617109254b0ac224354dfbf79009fe4239e09020c483cc60c071e00b9238684f30"), - MustHexID("1e1eac1c9add703eb252eb991594f8f5a173255d526a855fab24ae57dc277e055bc3c7a7ae0b45d437c4f47a72d97eb7b126f2ba344ba6c0e14b2c6f27d4b1e6"), - MustHexID("ae28953f63d4bc4e706712a59319c111f5ff8f312584f65d7436b4cd3d14b217b958f8486bad666b4481fe879019fb1f767cf15b3e3e2711efc33b56d460448a"), - MustHexID("934bb1edf9c7a318b82306aca67feb3d6b434421fa275d694f0b4927afd8b1d3935b727fd4ff6e3d012e0c82f1824385174e8c6450ade59c2a43281a4b3446b6"), - MustHexID("9eef3f28f70ce19637519a0916555bf76d26de31312ac656cf9d3e379899ea44e4dd7ffcce923b4f3563f8a00489a34bd6936db0cbb4c959d32c49f017e07d05"), - MustHexID("82200872e8f871c48f1fad13daec6478298099b591bb3dbc4ef6890aa28ebee5860d07d70be62f4c0af85085a90ae8179ee8f937cf37915c67ea73e704b03ee7"), - MustHexID("6c75a5834a08476b7fc37ff3dc2011dc3ea3b36524bad7a6d319b18878fad813c0ba76d1f4555cacd3890c865438c21f0e0aed1f80e0a157e642124c69f43a11"), - MustHexID("995b873742206cb02b736e73a88580c2aacb0bd4a3c97a647b647bcab3f5e03c0e0736520a8b3600da09edf4248991fb01091ec7ff3ec7cdc8a1beae011e7aae"), - MustHexID("c773a056594b5cdef2e850d30891ff0e927c3b1b9c35cd8e8d53a1017001e237468e1ece3ae33d612ca3e6abb0a9169aa352e9dcda358e5af2ad982b577447db"), - MustHexID("2b46a5f6923f475c6be99ec6d134437a6d11f6bb4b4ac6bcd94572fa1092639d1c08aeefcb51f0912f0a060f71d4f38ee4da70ecc16010b05dd4a674aab14c3a"), - MustHexID("af6ab501366debbaa0d22e20e9688f32ef6b3b644440580fd78de4fe0e99e2a16eb5636bbae0d1c259df8ddda77b35b9a35cbc36137473e9c68fbc9d203ba842"), - MustHexID("c9f6f2dd1a941926f03f770695bda289859e85fabaf94baaae20b93e5015dc014ba41150176a36a1884adb52f405194693e63b0c464a6891cc9cc1c80d450326"), - MustHexID("5b116f0751526868a909b61a30b0c5282c37df6925cc03ddea556ef0d0602a9595fd6c14d371f8ed7d45d89918a032dcd22be4342a8793d88fdbeb3ca3d75bd7"), - MustHexID("50f3222fb6b82481c7c813b2172e1daea43e2710a443b9c2a57a12bd160dd37e20f87aa968c82ad639af6972185609d47036c0d93b4b7269b74ebd7073221c10"), + hexEncPubkey("53a5bd1215d4ab709ae8fdc2ced50bba320bced78bd9c5dc92947fb402250c914891786db0978c898c058493f86fc68b1c5de8a5cb36336150ac7a88655b6c39"), + hexEncPubkey("b7f79e3ab59f79262623c9ccefc8f01d682323aee56ffbe295437487e9d5acaf556a9c92e1f1c6a9601f2b9eb6b027ae1aeaebac71d61b9b78e88676efd3e1a3"), + hexEncPubkey("d374bf7e8d7ffff69cc00bebff38ef5bc1dcb0a8d51c1a3d70e61ac6b2e2d6617109254b0ac224354dfbf79009fe4239e09020c483cc60c071e00b9238684f30"), + hexEncPubkey("1e1eac1c9add703eb252eb991594f8f5a173255d526a855fab24ae57dc277e055bc3c7a7ae0b45d437c4f47a72d97eb7b126f2ba344ba6c0e14b2c6f27d4b1e6"), + hexEncPubkey("ae28953f63d4bc4e706712a59319c111f5ff8f312584f65d7436b4cd3d14b217b958f8486bad666b4481fe879019fb1f767cf15b3e3e2711efc33b56d460448a"), + hexEncPubkey("934bb1edf9c7a318b82306aca67feb3d6b434421fa275d694f0b4927afd8b1d3935b727fd4ff6e3d012e0c82f1824385174e8c6450ade59c2a43281a4b3446b6"), + hexEncPubkey("9eef3f28f70ce19637519a0916555bf76d26de31312ac656cf9d3e379899ea44e4dd7ffcce923b4f3563f8a00489a34bd6936db0cbb4c959d32c49f017e07d05"), + hexEncPubkey("82200872e8f871c48f1fad13daec6478298099b591bb3dbc4ef6890aa28ebee5860d07d70be62f4c0af85085a90ae8179ee8f937cf37915c67ea73e704b03ee7"), + hexEncPubkey("6c75a5834a08476b7fc37ff3dc2011dc3ea3b36524bad7a6d319b18878fad813c0ba76d1f4555cacd3890c865438c21f0e0aed1f80e0a157e642124c69f43a11"), + hexEncPubkey("995b873742206cb02b736e73a88580c2aacb0bd4a3c97a647b647bcab3f5e03c0e0736520a8b3600da09edf4248991fb01091ec7ff3ec7cdc8a1beae011e7aae"), + hexEncPubkey("c773a056594b5cdef2e850d30891ff0e927c3b1b9c35cd8e8d53a1017001e237468e1ece3ae33d612ca3e6abb0a9169aa352e9dcda358e5af2ad982b577447db"), + hexEncPubkey("2b46a5f6923f475c6be99ec6d134437a6d11f6bb4b4ac6bcd94572fa1092639d1c08aeefcb51f0912f0a060f71d4f38ee4da70ecc16010b05dd4a674aab14c3a"), + hexEncPubkey("af6ab501366debbaa0d22e20e9688f32ef6b3b644440580fd78de4fe0e99e2a16eb5636bbae0d1c259df8ddda77b35b9a35cbc36137473e9c68fbc9d203ba842"), + hexEncPubkey("c9f6f2dd1a941926f03f770695bda289859e85fabaf94baaae20b93e5015dc014ba41150176a36a1884adb52f405194693e63b0c464a6891cc9cc1c80d450326"), + hexEncPubkey("5b116f0751526868a909b61a30b0c5282c37df6925cc03ddea556ef0d0602a9595fd6c14d371f8ed7d45d89918a032dcd22be4342a8793d88fdbeb3ca3d75bd7"), + hexEncPubkey("50f3222fb6b82481c7c813b2172e1daea43e2710a443b9c2a57a12bd160dd37e20f87aa968c82ad639af6972185609d47036c0d93b4b7269b74ebd7073221c10"), }, 251: { - MustHexID("9b8f702a62d1bee67bedfeb102eca7f37fa1713e310f0d6651cc0c33ea7c5477575289ccd463e5a2574a00a676a1fdce05658ba447bb9d2827f0ba47b947e894"), - MustHexID("b97532eb83054ed054b4abdf413bb30c00e4205545c93521554dbe77faa3cfaa5bd31ef466a107b0b34a71ec97214c0c83919720142cddac93aa7a3e928d4708"), - MustHexID("2f7a5e952bfb67f2f90b8441b5fadc9ee13b1dcde3afeeb3dd64bf937f86663cc5c55d1fa83952b5422763c7df1b7f2794b751c6be316ebc0beb4942e65ab8c1"), - MustHexID("42c7483781727051a0b3660f14faf39e0d33de5e643702ae933837d036508ab856ce7eec8ec89c4929a4901256e5233a3d847d5d4893f91bcf21835a9a880fee"), - MustHexID("873bae27bf1dc854408fba94046a53ab0c965cebe1e4e12290806fc62b88deb1f4a47f9e18f78fc0e7913a0c6e42ac4d0fc3a20cea6bc65f0c8a0ca90b67521e"), - MustHexID("a7e3a370bbd761d413f8d209e85886f68bf73d5c3089b2dc6fa42aab1ecb5162635497eed95dee2417f3c9c74a3e76319625c48ead2e963c7de877cd4551f347"), - MustHexID("528597534776a40df2addaaea15b6ff832ce36b9748a265768368f657e76d58569d9f30dbb91e91cf0ae7efe8f402f17aa0ae15f5c55051ba03ba830287f4c42"), - MustHexID("461d8bd4f13c3c09031fdb84f104ed737a52f630261463ce0bdb5704259bab4b737dda688285b8444dbecaecad7f50f835190b38684ced5e90c54219e5adf1bc"), - MustHexID("6ec50c0be3fd232737090fc0111caaf0bb6b18f72be453428087a11a97fd6b52db0344acbf789a689bd4f5f50f79017ea784f8fd6fe723ad6ae675b9e3b13e21"), - MustHexID("12fc5e2f77a83fdcc727b79d8ae7fe6a516881138d3011847ee136b400fed7cfba1f53fd7a9730253c7aa4f39abeacd04f138417ba7fcb0f36cccc3514e0dab6"), - MustHexID("4fdbe75914ccd0bce02101606a1ccf3657ec963e3b3c20239d5fec87673fe446d649b4f15f1fe1a40e6cfbd446dda2d31d40bb602b1093b8fcd5f139ba0eb46a"), - MustHexID("3753668a0f6281e425ea69b52cb2d17ab97afbe6eb84cf5d25425bc5e53009388857640668fadd7c110721e6047c9697803bd8a6487b43bb343bfa32ebf24039"), - MustHexID("2e81b16346637dec4410fd88e527346145b9c0a849dbf2628049ac7dae016c8f4305649d5659ec77f1e8a0fac0db457b6080547226f06283598e3740ad94849a"), - MustHexID("802c3cc27f91c89213223d758f8d2ecd41135b357b6d698f24d811cdf113033a81c38e0bdff574a5c005b00a8c193dc2531f8c1fa05fa60acf0ab6f2858af09f"), - MustHexID("fcc9a2e1ac3667026ff16192876d1813bb75abdbf39b929a92863012fe8b1d890badea7a0de36274d5c1eb1e8f975785532c50d80fd44b1a4b692f437303393f"), - MustHexID("6d8b3efb461151dd4f6de809b62726f5b89e9b38e9ba1391967f61cde844f7528fecf821b74049207cee5a527096b31f3ad623928cd3ce51d926fa345a6b2951"), + hexEncPubkey("9b8f702a62d1bee67bedfeb102eca7f37fa1713e310f0d6651cc0c33ea7c5477575289ccd463e5a2574a00a676a1fdce05658ba447bb9d2827f0ba47b947e894"), + hexEncPubkey("b97532eb83054ed054b4abdf413bb30c00e4205545c93521554dbe77faa3cfaa5bd31ef466a107b0b34a71ec97214c0c83919720142cddac93aa7a3e928d4708"), + hexEncPubkey("2f7a5e952bfb67f2f90b8441b5fadc9ee13b1dcde3afeeb3dd64bf937f86663cc5c55d1fa83952b5422763c7df1b7f2794b751c6be316ebc0beb4942e65ab8c1"), + hexEncPubkey("42c7483781727051a0b3660f14faf39e0d33de5e643702ae933837d036508ab856ce7eec8ec89c4929a4901256e5233a3d847d5d4893f91bcf21835a9a880fee"), + hexEncPubkey("873bae27bf1dc854408fba94046a53ab0c965cebe1e4e12290806fc62b88deb1f4a47f9e18f78fc0e7913a0c6e42ac4d0fc3a20cea6bc65f0c8a0ca90b67521e"), + hexEncPubkey("a7e3a370bbd761d413f8d209e85886f68bf73d5c3089b2dc6fa42aab1ecb5162635497eed95dee2417f3c9c74a3e76319625c48ead2e963c7de877cd4551f347"), + hexEncPubkey("528597534776a40df2addaaea15b6ff832ce36b9748a265768368f657e76d58569d9f30dbb91e91cf0ae7efe8f402f17aa0ae15f5c55051ba03ba830287f4c42"), + hexEncPubkey("461d8bd4f13c3c09031fdb84f104ed737a52f630261463ce0bdb5704259bab4b737dda688285b8444dbecaecad7f50f835190b38684ced5e90c54219e5adf1bc"), + hexEncPubkey("6ec50c0be3fd232737090fc0111caaf0bb6b18f72be453428087a11a97fd6b52db0344acbf789a689bd4f5f50f79017ea784f8fd6fe723ad6ae675b9e3b13e21"), + hexEncPubkey("12fc5e2f77a83fdcc727b79d8ae7fe6a516881138d3011847ee136b400fed7cfba1f53fd7a9730253c7aa4f39abeacd04f138417ba7fcb0f36cccc3514e0dab6"), + hexEncPubkey("4fdbe75914ccd0bce02101606a1ccf3657ec963e3b3c20239d5fec87673fe446d649b4f15f1fe1a40e6cfbd446dda2d31d40bb602b1093b8fcd5f139ba0eb46a"), + hexEncPubkey("3753668a0f6281e425ea69b52cb2d17ab97afbe6eb84cf5d25425bc5e53009388857640668fadd7c110721e6047c9697803bd8a6487b43bb343bfa32ebf24039"), + hexEncPubkey("2e81b16346637dec4410fd88e527346145b9c0a849dbf2628049ac7dae016c8f4305649d5659ec77f1e8a0fac0db457b6080547226f06283598e3740ad94849a"), + hexEncPubkey("802c3cc27f91c89213223d758f8d2ecd41135b357b6d698f24d811cdf113033a81c38e0bdff574a5c005b00a8c193dc2531f8c1fa05fa60acf0ab6f2858af09f"), + hexEncPubkey("fcc9a2e1ac3667026ff16192876d1813bb75abdbf39b929a92863012fe8b1d890badea7a0de36274d5c1eb1e8f975785532c50d80fd44b1a4b692f437303393f"), + hexEncPubkey("6d8b3efb461151dd4f6de809b62726f5b89e9b38e9ba1391967f61cde844f7528fecf821b74049207cee5a527096b31f3ad623928cd3ce51d926fa345a6b2951"), }, 252: { - MustHexID("f1ae93157cc48c2075dd5868fbf523e79e06caf4b8198f352f6e526680b78ff4227263de92612f7d63472bd09367bb92a636fff16fe46ccf41614f7a72495c2a"), - MustHexID("587f482d111b239c27c0cb89b51dd5d574db8efd8de14a2e6a1400c54d4567e77c65f89c1da52841212080b91604104768350276b6682f2f961cdaf4039581c7"), - MustHexID("e3f88274d35cefdaabdf205afe0e80e936cc982b8e3e47a84ce664c413b29016a4fb4f3a3ebae0a2f79671f8323661ed462bf4390af94c424dc8ace0c301b90f"), - MustHexID("0ddc736077da9a12ba410dc5ea63cbcbe7659dd08596485b2bff3435221f82c10d263efd9af938e128464be64a178b7cd22e19f400d5802f4c9df54bf89f2619"), - MustHexID("784aa34d833c6ce63fcc1279630113c3272e82c4ae8c126c5a52a88ac461b6baeed4244e607b05dc14e5b2f41c70a273c3804dea237f14f7a1e546f6d1309d14"), - MustHexID("f253a2c354ee0e27cfcae786d726753d4ad24be6516b279a936195a487de4a59dbc296accf20463749ff55293263ed8c1b6365eecb248d44e75e9741c0d18205"), - MustHexID("a1910b80357b3ad9b4593e0628922939614dc9056a5fbf477279c8b2c1d0b4b31d89a0c09d0d41f795271d14d3360ef08a3f821e65e7e1f56c07a36afe49c7c5"), - MustHexID("f1168552c2efe541160f0909b0b4a9d6aeedcf595cdf0e9b165c97e3e197471a1ee6320e93389edfba28af6eaf10de98597ad56e7ab1b504ed762451996c3b98"), - MustHexID("b0c8e5d2c8634a7930e1a6fd082e448c6cf9d2d8b7293558b59238815a4df926c286bf297d2049f14e8296a6eb3256af614ec1812c4f2bbe807673b58bf14c8c"), - MustHexID("0fb346076396a38badc342df3679b55bd7f40a609ab103411fe45082c01f12ea016729e95914b2b5540e987ff5c9b133e85862648e7f36abdfd23100d248d234"), - MustHexID("f736e0cc83417feaa280d9483f5d4d72d1b036cd0c6d9cbdeb8ac35ceb2604780de46dddaa32a378474e1d5ccdf79b373331c30c7911ade2ae32f98832e5de1f"), - MustHexID("8b02991457602f42b38b342d3f2259ae4100c354b3843885f7e4e07bd644f64dab94bb7f38a3915f8b7f11d8e3f81c28e07a0078cf79d7397e38a7b7e0c857e2"), - MustHexID("9221d9f04a8a184993d12baa91116692bb685f887671302999d69300ad103eb2d2c75a09d8979404c6dd28f12362f58a1a43619c493d9108fd47588a23ce5824"), - MustHexID("652797801744dada833fff207d67484742eea6835d695925f3e618d71b68ec3c65bdd85b4302b2cdcb835ad3f94fd00d8da07e570b41bc0d2bcf69a8de1b3284"), - MustHexID("d84f06fe64debc4cd0625e36d19b99014b6218375262cc2209202bdbafd7dffcc4e34ce6398e182e02fd8faeed622c3e175545864902dfd3d1ac57647cddf4c6"), - MustHexID("d0ed87b294f38f1d741eb601020eeec30ac16331d05880fe27868f1e454446de367d7457b41c79e202eaf9525b029e4f1d7e17d85a55f83a557c005c68d7328a"), + hexEncPubkey("f1ae93157cc48c2075dd5868fbf523e79e06caf4b8198f352f6e526680b78ff4227263de92612f7d63472bd09367bb92a636fff16fe46ccf41614f7a72495c2a"), + hexEncPubkey("587f482d111b239c27c0cb89b51dd5d574db8efd8de14a2e6a1400c54d4567e77c65f89c1da52841212080b91604104768350276b6682f2f961cdaf4039581c7"), + hexEncPubkey("e3f88274d35cefdaabdf205afe0e80e936cc982b8e3e47a84ce664c413b29016a4fb4f3a3ebae0a2f79671f8323661ed462bf4390af94c424dc8ace0c301b90f"), + hexEncPubkey("0ddc736077da9a12ba410dc5ea63cbcbe7659dd08596485b2bff3435221f82c10d263efd9af938e128464be64a178b7cd22e19f400d5802f4c9df54bf89f2619"), + hexEncPubkey("784aa34d833c6ce63fcc1279630113c3272e82c4ae8c126c5a52a88ac461b6baeed4244e607b05dc14e5b2f41c70a273c3804dea237f14f7a1e546f6d1309d14"), + hexEncPubkey("f253a2c354ee0e27cfcae786d726753d4ad24be6516b279a936195a487de4a59dbc296accf20463749ff55293263ed8c1b6365eecb248d44e75e9741c0d18205"), + hexEncPubkey("a1910b80357b3ad9b4593e0628922939614dc9056a5fbf477279c8b2c1d0b4b31d89a0c09d0d41f795271d14d3360ef08a3f821e65e7e1f56c07a36afe49c7c5"), + hexEncPubkey("f1168552c2efe541160f0909b0b4a9d6aeedcf595cdf0e9b165c97e3e197471a1ee6320e93389edfba28af6eaf10de98597ad56e7ab1b504ed762451996c3b98"), + hexEncPubkey("b0c8e5d2c8634a7930e1a6fd082e448c6cf9d2d8b7293558b59238815a4df926c286bf297d2049f14e8296a6eb3256af614ec1812c4f2bbe807673b58bf14c8c"), + hexEncPubkey("0fb346076396a38badc342df3679b55bd7f40a609ab103411fe45082c01f12ea016729e95914b2b5540e987ff5c9b133e85862648e7f36abdfd23100d248d234"), + hexEncPubkey("f736e0cc83417feaa280d9483f5d4d72d1b036cd0c6d9cbdeb8ac35ceb2604780de46dddaa32a378474e1d5ccdf79b373331c30c7911ade2ae32f98832e5de1f"), + hexEncPubkey("8b02991457602f42b38b342d3f2259ae4100c354b3843885f7e4e07bd644f64dab94bb7f38a3915f8b7f11d8e3f81c28e07a0078cf79d7397e38a7b7e0c857e2"), + hexEncPubkey("9221d9f04a8a184993d12baa91116692bb685f887671302999d69300ad103eb2d2c75a09d8979404c6dd28f12362f58a1a43619c493d9108fd47588a23ce5824"), + hexEncPubkey("652797801744dada833fff207d67484742eea6835d695925f3e618d71b68ec3c65bdd85b4302b2cdcb835ad3f94fd00d8da07e570b41bc0d2bcf69a8de1b3284"), + hexEncPubkey("d84f06fe64debc4cd0625e36d19b99014b6218375262cc2209202bdbafd7dffcc4e34ce6398e182e02fd8faeed622c3e175545864902dfd3d1ac57647cddf4c6"), + hexEncPubkey("d0ed87b294f38f1d741eb601020eeec30ac16331d05880fe27868f1e454446de367d7457b41c79e202eaf9525b029e4f1d7e17d85a55f83a557c005c68d7328a"), }, 253: { - MustHexID("ad4485e386e3cc7c7310366a7c38fb810b8896c0d52e55944bfd320ca294e7912d6c53c0a0cf85e7ce226e92491d60430e86f8f15cda0161ed71893fb4a9e3a1"), - MustHexID("36d0e7e5b7734f98c6183eeeb8ac5130a85e910a925311a19c4941b1290f945d4fc3996b12ef4966960b6fa0fb29b1604f83a0f81bd5fd6398d2e1a22e46af0c"), - MustHexID("7d307d8acb4a561afa23bdf0bd945d35c90245e26345ec3a1f9f7df354222a7cdcb81339c9ed6744526c27a1a0c8d10857e98df942fa433602facac71ac68a31"), - MustHexID("d97bf55f88c83fae36232661af115d66ca600fc4bd6d1fb35ff9bb4dad674c02cf8c8d05f317525b5522250db58bb1ecafb7157392bf5aa61b178c61f098d995"), - MustHexID("7045d678f1f9eb7a4613764d17bd5698796494d0bf977b16f2dbc272b8a0f7858a60805c022fc3d1fe4f31c37e63cdaca0416c0d053ef48a815f8b19121605e0"), - MustHexID("14e1f21418d445748de2a95cd9a8c3b15b506f86a0acabd8af44bb968ce39885b19c8822af61b3dd58a34d1f265baec30e3ae56149dc7d2aa4a538f7319f69c8"), - MustHexID("b9453d78281b66a4eac95a1546017111eaaa5f92a65d0de10b1122940e92b319728a24edf4dec6acc412321b1c95266d39c7b3a5d265c629c3e49a65fb022c09"), - MustHexID("e8a49248419e3824a00d86af422f22f7366e2d4922b304b7169937616a01d9d6fa5abf5cc01061a352dc866f48e1fa2240dbb453d872b1d7be62bdfc1d5e248c"), - MustHexID("bebcff24b52362f30e0589ee573ce2d86f073d58d18e6852a592fa86ceb1a6c9b96d7fb9ec7ed1ed98a51b6743039e780279f6bb49d0a04327ac7a182d9a56f6"), - MustHexID("d0835e5a4291db249b8d2fca9f503049988180c7d247bedaa2cf3a1bad0a76709360a85d4f9a1423b2cbc82bb4d94b47c0cde20afc430224834c49fe312a9ae3"), - MustHexID("6b087fe2a2da5e4f0b0f4777598a4a7fb66bf77dbd5bfc44e8a7eaa432ab585a6e226891f56a7d4f5ed11a7c57b90f1661bba1059590ca4267a35801c2802913"), - MustHexID("d901e5bde52d1a0f4ddf010a686a53974cdae4ebe5c6551b3c37d6b6d635d38d5b0e5f80bc0186a2c7809dbf3a42870dd09643e68d32db896c6da8ba734579e7"), - MustHexID("96419fb80efae4b674402bb969ebaab86c1274f29a83a311e24516d36cdf148fe21754d46c97688cdd7468f24c08b13e4727c29263393638a3b37b99ff60ebca"), - MustHexID("7b9c1889ae916a5d5abcdfb0aaedcc9c6f9eb1c1a4f68d0c2d034fe79ac610ce917c3abc670744150fa891bfcd8ab14fed6983fca964de920aa393fa7b326748"), - MustHexID("7a369b2b8962cc4c65900be046482fbf7c14f98a135bbbae25152c82ad168fb2097b3d1429197cf46d3ce9fdeb64808f908a489cc6019725db040060fdfe5405"), - MustHexID("47bcae48288da5ecc7f5058dfa07cf14d89d06d6e449cb946e237aa6652ea050d9f5a24a65efdc0013ccf232bf88670979eddef249b054f63f38da9d7796dbd8"), + hexEncPubkey("ad4485e386e3cc7c7310366a7c38fb810b8896c0d52e55944bfd320ca294e7912d6c53c0a0cf85e7ce226e92491d60430e86f8f15cda0161ed71893fb4a9e3a1"), + hexEncPubkey("36d0e7e5b7734f98c6183eeeb8ac5130a85e910a925311a19c4941b1290f945d4fc3996b12ef4966960b6fa0fb29b1604f83a0f81bd5fd6398d2e1a22e46af0c"), + hexEncPubkey("7d307d8acb4a561afa23bdf0bd945d35c90245e26345ec3a1f9f7df354222a7cdcb81339c9ed6744526c27a1a0c8d10857e98df942fa433602facac71ac68a31"), + hexEncPubkey("d97bf55f88c83fae36232661af115d66ca600fc4bd6d1fb35ff9bb4dad674c02cf8c8d05f317525b5522250db58bb1ecafb7157392bf5aa61b178c61f098d995"), + hexEncPubkey("7045d678f1f9eb7a4613764d17bd5698796494d0bf977b16f2dbc272b8a0f7858a60805c022fc3d1fe4f31c37e63cdaca0416c0d053ef48a815f8b19121605e0"), + hexEncPubkey("14e1f21418d445748de2a95cd9a8c3b15b506f86a0acabd8af44bb968ce39885b19c8822af61b3dd58a34d1f265baec30e3ae56149dc7d2aa4a538f7319f69c8"), + hexEncPubkey("b9453d78281b66a4eac95a1546017111eaaa5f92a65d0de10b1122940e92b319728a24edf4dec6acc412321b1c95266d39c7b3a5d265c629c3e49a65fb022c09"), + hexEncPubkey("e8a49248419e3824a00d86af422f22f7366e2d4922b304b7169937616a01d9d6fa5abf5cc01061a352dc866f48e1fa2240dbb453d872b1d7be62bdfc1d5e248c"), + hexEncPubkey("bebcff24b52362f30e0589ee573ce2d86f073d58d18e6852a592fa86ceb1a6c9b96d7fb9ec7ed1ed98a51b6743039e780279f6bb49d0a04327ac7a182d9a56f6"), + hexEncPubkey("d0835e5a4291db249b8d2fca9f503049988180c7d247bedaa2cf3a1bad0a76709360a85d4f9a1423b2cbc82bb4d94b47c0cde20afc430224834c49fe312a9ae3"), + hexEncPubkey("6b087fe2a2da5e4f0b0f4777598a4a7fb66bf77dbd5bfc44e8a7eaa432ab585a6e226891f56a7d4f5ed11a7c57b90f1661bba1059590ca4267a35801c2802913"), + hexEncPubkey("d901e5bde52d1a0f4ddf010a686a53974cdae4ebe5c6551b3c37d6b6d635d38d5b0e5f80bc0186a2c7809dbf3a42870dd09643e68d32db896c6da8ba734579e7"), + hexEncPubkey("96419fb80efae4b674402bb969ebaab86c1274f29a83a311e24516d36cdf148fe21754d46c97688cdd7468f24c08b13e4727c29263393638a3b37b99ff60ebca"), + hexEncPubkey("7b9c1889ae916a5d5abcdfb0aaedcc9c6f9eb1c1a4f68d0c2d034fe79ac610ce917c3abc670744150fa891bfcd8ab14fed6983fca964de920aa393fa7b326748"), + hexEncPubkey("7a369b2b8962cc4c65900be046482fbf7c14f98a135bbbae25152c82ad168fb2097b3d1429197cf46d3ce9fdeb64808f908a489cc6019725db040060fdfe5405"), + hexEncPubkey("47bcae48288da5ecc7f5058dfa07cf14d89d06d6e449cb946e237aa6652ea050d9f5a24a65efdc0013ccf232bf88670979eddef249b054f63f38da9d7796dbd8"), }, 254: { - MustHexID("099739d7abc8abd38ecc7a816c521a1168a4dbd359fa7212a5123ab583ffa1cf485a5fed219575d6475dbcdd541638b2d3631a6c7fce7474e7fe3cba1d4d5853"), - MustHexID("c2b01603b088a7182d0cf7ef29fb2b04c70acb320fccf78526bf9472e10c74ee70b3fcfa6f4b11d167bd7d3bc4d936b660f2c9bff934793d97cb21750e7c3d31"), - MustHexID("20e4d8f45f2f863e94b45548c1ef22a11f7d36f263e4f8623761e05a64c4572379b000a52211751e2561b0f14f4fc92dd4130410c8ccc71eb4f0e95a700d4ca9"), - MustHexID("27f4a16cc085e72d86e25c98bd2eca173eaaee7565c78ec5a52e9e12b2211f35de81b5b45e9195de2ebfe29106742c59112b951a04eb7ae48822911fc1f9389e"), - MustHexID("55db5ee7d98e7f0b1c3b9d5be6f2bc619a1b86c3cdd513160ad4dcf267037a5fffad527ac15d50aeb32c59c13d1d4c1e567ebbf4de0d25236130c8361f9aac63"), - MustHexID("883df308b0130fc928a8559fe50667a0fff80493bc09685d18213b2db241a3ad11310ed86b0ef662b3ce21fc3d9aa7f3fc24b8d9afe17c7407e9afd3345ae548"), - MustHexID("c7af968cc9bc8200c3ee1a387405f7563be1dce6710a3439f42ea40657d0eae9d2b3c16c42d779605351fcdece4da637b9804e60ca08cfb89aec32c197beffa6"), - MustHexID("3e66f2b788e3ff1d04106b80597915cd7afa06c405a7ae026556b6e583dca8e05cfbab5039bb9a1b5d06083ffe8de5780b1775550e7218f5e98624bf7af9a0a8"), - MustHexID("4fc7f53764de3337fdaec0a711d35d3a923e72fa65025444d12230b3552ed43d9b2d1ad08ccb11f2d50c58809e6dd74dde910e195294fca3b47ae5a3967cc479"), - MustHexID("bafdfdcf6ccaa989436752fa97c77477b6baa7deb374b16c095492c529eb133e8e2f99e1977012b64767b9d34b2cf6d2048ed489bd822b5139b523f6a423167b"), - MustHexID("7f5d78008a4312fe059104ce80202c82b8915c2eb4411c6b812b16f7642e57c00f2c9425121f5cbac4257fe0b3e81ef5dea97ea2dbaa98f6a8b6fd4d1e5980bb"), - MustHexID("598c37fe78f922751a052f463aeb0cb0bc7f52b7c2a4cf2da72ec0931c7c32175d4165d0f8998f7320e87324ac3311c03f9382a5385c55f0407b7a66b2acd864"), - MustHexID("f758c4136e1c148777a7f3275a76e2db0b2b04066fd738554ec398c1c6cc9fb47e14a3b4c87bd47deaeab3ffd2110514c3855685a374794daff87b605b27ee2e"), - MustHexID("0307bb9e4fd865a49dcf1fe4333d1b944547db650ab580af0b33e53c4fef6c789531110fac801bbcbce21fc4d6f61b6d5b24abdf5b22e3030646d579f6dca9c2"), - MustHexID("82504b6eb49bb2c0f91a7006ce9cefdbaf6df38706198502c2e06601091fc9dc91e4f15db3410d45c6af355bc270b0f268d3dff560f956985c7332d4b10bd1ed"), - MustHexID("b39b5b677b45944ceebe76e76d1f051de2f2a0ec7b0d650da52135743e66a9a5dba45f638258f9a7545d9a790c7fe6d3fdf82c25425c7887323e45d27d06c057"), + hexEncPubkey("099739d7abc8abd38ecc7a816c521a1168a4dbd359fa7212a5123ab583ffa1cf485a5fed219575d6475dbcdd541638b2d3631a6c7fce7474e7fe3cba1d4d5853"), + hexEncPubkey("c2b01603b088a7182d0cf7ef29fb2b04c70acb320fccf78526bf9472e10c74ee70b3fcfa6f4b11d167bd7d3bc4d936b660f2c9bff934793d97cb21750e7c3d31"), + hexEncPubkey("20e4d8f45f2f863e94b45548c1ef22a11f7d36f263e4f8623761e05a64c4572379b000a52211751e2561b0f14f4fc92dd4130410c8ccc71eb4f0e95a700d4ca9"), + hexEncPubkey("27f4a16cc085e72d86e25c98bd2eca173eaaee7565c78ec5a52e9e12b2211f35de81b5b45e9195de2ebfe29106742c59112b951a04eb7ae48822911fc1f9389e"), + hexEncPubkey("55db5ee7d98e7f0b1c3b9d5be6f2bc619a1b86c3cdd513160ad4dcf267037a5fffad527ac15d50aeb32c59c13d1d4c1e567ebbf4de0d25236130c8361f9aac63"), + hexEncPubkey("883df308b0130fc928a8559fe50667a0fff80493bc09685d18213b2db241a3ad11310ed86b0ef662b3ce21fc3d9aa7f3fc24b8d9afe17c7407e9afd3345ae548"), + hexEncPubkey("c7af968cc9bc8200c3ee1a387405f7563be1dce6710a3439f42ea40657d0eae9d2b3c16c42d779605351fcdece4da637b9804e60ca08cfb89aec32c197beffa6"), + hexEncPubkey("3e66f2b788e3ff1d04106b80597915cd7afa06c405a7ae026556b6e583dca8e05cfbab5039bb9a1b5d06083ffe8de5780b1775550e7218f5e98624bf7af9a0a8"), + hexEncPubkey("4fc7f53764de3337fdaec0a711d35d3a923e72fa65025444d12230b3552ed43d9b2d1ad08ccb11f2d50c58809e6dd74dde910e195294fca3b47ae5a3967cc479"), + hexEncPubkey("bafdfdcf6ccaa989436752fa97c77477b6baa7deb374b16c095492c529eb133e8e2f99e1977012b64767b9d34b2cf6d2048ed489bd822b5139b523f6a423167b"), + hexEncPubkey("7f5d78008a4312fe059104ce80202c82b8915c2eb4411c6b812b16f7642e57c00f2c9425121f5cbac4257fe0b3e81ef5dea97ea2dbaa98f6a8b6fd4d1e5980bb"), + hexEncPubkey("598c37fe78f922751a052f463aeb0cb0bc7f52b7c2a4cf2da72ec0931c7c32175d4165d0f8998f7320e87324ac3311c03f9382a5385c55f0407b7a66b2acd864"), + hexEncPubkey("f758c4136e1c148777a7f3275a76e2db0b2b04066fd738554ec398c1c6cc9fb47e14a3b4c87bd47deaeab3ffd2110514c3855685a374794daff87b605b27ee2e"), + hexEncPubkey("0307bb9e4fd865a49dcf1fe4333d1b944547db650ab580af0b33e53c4fef6c789531110fac801bbcbce21fc4d6f61b6d5b24abdf5b22e3030646d579f6dca9c2"), + hexEncPubkey("82504b6eb49bb2c0f91a7006ce9cefdbaf6df38706198502c2e06601091fc9dc91e4f15db3410d45c6af355bc270b0f268d3dff560f956985c7332d4b10bd1ed"), + hexEncPubkey("b39b5b677b45944ceebe76e76d1f051de2f2a0ec7b0d650da52135743e66a9a5dba45f638258f9a7545d9a790c7fe6d3fdf82c25425c7887323e45d27d06c057"), }, 255: { - MustHexID("5c4d58d46e055dd1f093f81ee60a675e1f02f54da6206720adee4dccef9b67a31efc5c2a2949c31a04ee31beadc79aba10da31440a1f9ff2a24093c63c36d784"), - MustHexID("ea72161ffdd4b1e124c7b93b0684805f4c4b58d617ed498b37a145c670dbc2e04976f8785583d9c805ffbf343c31d492d79f841652bbbd01b61ed85640b23495"), - MustHexID("51caa1d93352d47a8e531692a3612adac1e8ac68d0a200d086c1c57ae1e1a91aa285ab242e8c52ef9d7afe374c9485b122ae815f1707b875569d0433c1c3ce85"), - MustHexID("c08397d5751b47bd3da044b908be0fb0e510d3149574dff7aeab33749b023bb171b5769990fe17469dbebc100bc150e798aeda426a2dcc766699a225fddd75c6"), - MustHexID("0222c1c194b749736e593f937fad67ee348ac57287a15c7e42877aa38a9b87732a408bca370f812efd0eedbff13e6d5b854bf3ba1dec431a796ed47f32552b09"), - MustHexID("03d859cd46ef02d9bfad5268461a6955426845eef4126de6be0fa4e8d7e0727ba2385b78f1a883a8239e95ebb814f2af8379632c7d5b100688eebc5841209582"), - MustHexID("64d5004b7e043c39ff0bd10cb20094c287721d5251715884c280a612b494b3e9e1c64ba6f67614994c7d969a0d0c0295d107d53fc225d47c44c4b82852d6f960"), - MustHexID("b0a5eefb2dab6f786670f35bf9641eefe6dd87fd3f1362bcab4aaa792903500ab23d88fae68411372e0813b057535a601d46e454323745a948017f6063a47b1f"), - MustHexID("0cc6df0a3433d448b5684d2a3ffa9d1a825388177a18f44ad0008c7bd7702f1ec0fc38b83506f7de689c3b6ecb552599927e29699eed6bb867ff08f80068b287"), - MustHexID("50772f7b8c03a4e153355fbbf79c8a80cf32af656ff0c7873c99911099d04a0dae0674706c357e0145ad017a0ade65e6052cb1b0d574fcd6f67da3eee0ace66b"), - MustHexID("1ae37829c9ef41f8b508b82259ebac76b1ed900d7a45c08b7970f25d2d48ddd1829e2f11423a18749940b6dab8598c6e416cef0efd47e46e51f29a0bc65b37cd"), - MustHexID("ba973cab31c2af091fc1644a93527d62b2394999e2b6ccbf158dd5ab9796a43d408786f1803ef4e29debfeb62fce2b6caa5ab2b24d1549c822a11c40c2856665"), - MustHexID("bc413ad270dd6ea25bddba78f3298b03b8ba6f8608ac03d06007d4116fa78ef5a0cfe8c80155089382fc7a193243ee5500082660cb5d7793f60f2d7d18650964"), - MustHexID("5a6a9ef07634d9eec3baa87c997b529b92652afa11473dfee41ef7037d5c06e0ddb9fe842364462d79dd31cff8a59a1b8d5bc2b810dea1d4cbbd3beb80ecec83"), - MustHexID("f492c6ee2696d5f682f7f537757e52744c2ae560f1090a07024609e903d334e9e174fc01609c5a229ddbcac36c9d21adaf6457dab38a25bfd44f2f0ee4277998"), - MustHexID("459e4db99298cb0467a90acee6888b08bb857450deac11015cced5104853be5adce5b69c740968bc7f931495d671a70cad9f48546d7cd203357fe9af0e8d2164"), + hexEncPubkey("5c4d58d46e055dd1f093f81ee60a675e1f02f54da6206720adee4dccef9b67a31efc5c2a2949c31a04ee31beadc79aba10da31440a1f9ff2a24093c63c36d784"), + hexEncPubkey("ea72161ffdd4b1e124c7b93b0684805f4c4b58d617ed498b37a145c670dbc2e04976f8785583d9c805ffbf343c31d492d79f841652bbbd01b61ed85640b23495"), + hexEncPubkey("51caa1d93352d47a8e531692a3612adac1e8ac68d0a200d086c1c57ae1e1a91aa285ab242e8c52ef9d7afe374c9485b122ae815f1707b875569d0433c1c3ce85"), + hexEncPubkey("c08397d5751b47bd3da044b908be0fb0e510d3149574dff7aeab33749b023bb171b5769990fe17469dbebc100bc150e798aeda426a2dcc766699a225fddd75c6"), + hexEncPubkey("0222c1c194b749736e593f937fad67ee348ac57287a15c7e42877aa38a9b87732a408bca370f812efd0eedbff13e6d5b854bf3ba1dec431a796ed47f32552b09"), + hexEncPubkey("03d859cd46ef02d9bfad5268461a6955426845eef4126de6be0fa4e8d7e0727ba2385b78f1a883a8239e95ebb814f2af8379632c7d5b100688eebc5841209582"), + hexEncPubkey("64d5004b7e043c39ff0bd10cb20094c287721d5251715884c280a612b494b3e9e1c64ba6f67614994c7d969a0d0c0295d107d53fc225d47c44c4b82852d6f960"), + hexEncPubkey("b0a5eefb2dab6f786670f35bf9641eefe6dd87fd3f1362bcab4aaa792903500ab23d88fae68411372e0813b057535a601d46e454323745a948017f6063a47b1f"), + hexEncPubkey("0cc6df0a3433d448b5684d2a3ffa9d1a825388177a18f44ad0008c7bd7702f1ec0fc38b83506f7de689c3b6ecb552599927e29699eed6bb867ff08f80068b287"), + hexEncPubkey("50772f7b8c03a4e153355fbbf79c8a80cf32af656ff0c7873c99911099d04a0dae0674706c357e0145ad017a0ade65e6052cb1b0d574fcd6f67da3eee0ace66b"), + hexEncPubkey("1ae37829c9ef41f8b508b82259ebac76b1ed900d7a45c08b7970f25d2d48ddd1829e2f11423a18749940b6dab8598c6e416cef0efd47e46e51f29a0bc65b37cd"), + hexEncPubkey("ba973cab31c2af091fc1644a93527d62b2394999e2b6ccbf158dd5ab9796a43d408786f1803ef4e29debfeb62fce2b6caa5ab2b24d1549c822a11c40c2856665"), + hexEncPubkey("bc413ad270dd6ea25bddba78f3298b03b8ba6f8608ac03d06007d4116fa78ef5a0cfe8c80155089382fc7a193243ee5500082660cb5d7793f60f2d7d18650964"), + hexEncPubkey("5a6a9ef07634d9eec3baa87c997b529b92652afa11473dfee41ef7037d5c06e0ddb9fe842364462d79dd31cff8a59a1b8d5bc2b810dea1d4cbbd3beb80ecec83"), + hexEncPubkey("f492c6ee2696d5f682f7f537757e52744c2ae560f1090a07024609e903d334e9e174fc01609c5a229ddbcac36c9d21adaf6457dab38a25bfd44f2f0ee4277998"), + hexEncPubkey("459e4db99298cb0467a90acee6888b08bb857450deac11015cced5104853be5adce5b69c740968bc7f931495d671a70cad9f48546d7cd203357fe9af0e8d2164"), }, 256: { - MustHexID("a8593af8a4aef7b806b5197612017951bac8845a1917ca9a6a15dd6086d608505144990b245785c4cd2d67a295701c7aac2aa18823fb0033987284b019656268"), - MustHexID("d2eebef914928c3aad77fc1b2a495f52d2294acf5edaa7d8a530b540f094b861a68fe8348a46a7c302f08ab609d85912a4968eacfea0740847b29421b4795d9e"), - MustHexID("b14bfcb31495f32b650b63cf7d08492e3e29071fdc73cf2da0da48d4b191a70ba1a65f42ad8c343206101f00f8a48e8db4b08bf3f622c0853e7323b250835b91"), - MustHexID("7feaee0d818c03eb30e4e0bf03ade0f3c21ca38e938a761aa1781cf70bda8cc5cd631a6cc53dd44f1d4a6d3e2dae6513c6c66ee50cb2f0e9ad6f7e319b309fd9"), - MustHexID("4ca3b657b139311db8d583c25dd5963005e46689e1317620496cc64129c7f3e52870820e0ec7941d28809311df6db8a2867bbd4f235b4248af24d7a9c22d1232"), - MustHexID("1181defb1d16851d42dd951d84424d6bd1479137f587fa184d5a8152be6b6b16ed08bcdb2c2ed8539bcde98c80c432875f9f724737c316a2bd385a39d3cab1d8"), - MustHexID("d9dd818769fa0c3ec9f553c759b92476f082817252a04a47dc1777740b1731d280058c66f982812f173a294acf4944a85ba08346e2de153ba3ba41ce8a62cb64"), - MustHexID("bd7c4f8a9e770aa915c771b15e107ca123d838762da0d3ffc53aa6b53e9cd076cffc534ec4d2e4c334c683f1f5ea72e0e123f6c261915ed5b58ac1b59f003d88"), - MustHexID("3dd5739c73649d510456a70e9d6b46a855864a4a3f744e088fd8c8da11b18e4c9b5f2d7da50b1c147b2bae5ca9609ae01f7a3cdea9dce34f80a91d29cd82f918"), - MustHexID("f0d7df1efc439b4bcc0b762118c1cfa99b2a6143a9f4b10e3c9465125f4c9fca4ab88a2504169bbcad65492cf2f50da9dd5d077c39574a944f94d8246529066b"), - MustHexID("dd598b9ba441448e5fb1a6ec6c5f5aa9605bad6e223297c729b1705d11d05f6bfd3d41988b694681ae69bb03b9a08bff4beab5596503d12a39bffb5cd6e94c7c"), - MustHexID("3fce284ac97e567aebae681b15b7a2b6df9d873945536335883e4bbc26460c064370537f323fd1ada828ea43154992d14ac0cec0940a2bd2a3f42ec156d60c83"), - MustHexID("7c8dfa8c1311cb14fb29a8ac11bca23ecc115e56d9fcf7b7ac1db9066aa4eb39f8b1dabf46e192a65be95ebfb4e839b5ab4533fef414921825e996b210dd53bd"), - MustHexID("cafa6934f82120456620573d7f801390ed5e16ed619613a37e409e44ab355ef755e83565a913b48a9466db786f8d4fbd590bfec474c2524d4a2608d4eafd6abd"), - MustHexID("9d16600d0dd310d77045769fed2cb427f32db88cd57d86e49390c2ba8a9698cfa856f775be2013237226e7bf47b248871cf865d23015937d1edeb20db5e3e760"), - MustHexID("17be6b6ba54199b1d80eff866d348ea11d8a4b341d63ad9a6681d3ef8a43853ac564d153eb2a8737f0afc9ab320f6f95c55aa11aaa13bbb1ff422fd16bdf8188"), + hexEncPubkey("a8593af8a4aef7b806b5197612017951bac8845a1917ca9a6a15dd6086d608505144990b245785c4cd2d67a295701c7aac2aa18823fb0033987284b019656268"), + hexEncPubkey("d2eebef914928c3aad77fc1b2a495f52d2294acf5edaa7d8a530b540f094b861a68fe8348a46a7c302f08ab609d85912a4968eacfea0740847b29421b4795d9e"), + hexEncPubkey("b14bfcb31495f32b650b63cf7d08492e3e29071fdc73cf2da0da48d4b191a70ba1a65f42ad8c343206101f00f8a48e8db4b08bf3f622c0853e7323b250835b91"), + hexEncPubkey("7feaee0d818c03eb30e4e0bf03ade0f3c21ca38e938a761aa1781cf70bda8cc5cd631a6cc53dd44f1d4a6d3e2dae6513c6c66ee50cb2f0e9ad6f7e319b309fd9"), + hexEncPubkey("4ca3b657b139311db8d583c25dd5963005e46689e1317620496cc64129c7f3e52870820e0ec7941d28809311df6db8a2867bbd4f235b4248af24d7a9c22d1232"), + hexEncPubkey("1181defb1d16851d42dd951d84424d6bd1479137f587fa184d5a8152be6b6b16ed08bcdb2c2ed8539bcde98c80c432875f9f724737c316a2bd385a39d3cab1d8"), + hexEncPubkey("d9dd818769fa0c3ec9f553c759b92476f082817252a04a47dc1777740b1731d280058c66f982812f173a294acf4944a85ba08346e2de153ba3ba41ce8a62cb64"), + hexEncPubkey("bd7c4f8a9e770aa915c771b15e107ca123d838762da0d3ffc53aa6b53e9cd076cffc534ec4d2e4c334c683f1f5ea72e0e123f6c261915ed5b58ac1b59f003d88"), + hexEncPubkey("3dd5739c73649d510456a70e9d6b46a855864a4a3f744e088fd8c8da11b18e4c9b5f2d7da50b1c147b2bae5ca9609ae01f7a3cdea9dce34f80a91d29cd82f918"), + hexEncPubkey("f0d7df1efc439b4bcc0b762118c1cfa99b2a6143a9f4b10e3c9465125f4c9fca4ab88a2504169bbcad65492cf2f50da9dd5d077c39574a944f94d8246529066b"), + hexEncPubkey("dd598b9ba441448e5fb1a6ec6c5f5aa9605bad6e223297c729b1705d11d05f6bfd3d41988b694681ae69bb03b9a08bff4beab5596503d12a39bffb5cd6e94c7c"), + hexEncPubkey("3fce284ac97e567aebae681b15b7a2b6df9d873945536335883e4bbc26460c064370537f323fd1ada828ea43154992d14ac0cec0940a2bd2a3f42ec156d60c83"), + hexEncPubkey("7c8dfa8c1311cb14fb29a8ac11bca23ecc115e56d9fcf7b7ac1db9066aa4eb39f8b1dabf46e192a65be95ebfb4e839b5ab4533fef414921825e996b210dd53bd"), + hexEncPubkey("cafa6934f82120456620573d7f801390ed5e16ed619613a37e409e44ab355ef755e83565a913b48a9466db786f8d4fbd590bfec474c2524d4a2608d4eafd6abd"), + hexEncPubkey("9d16600d0dd310d77045769fed2cb427f32db88cd57d86e49390c2ba8a9698cfa856f775be2013237226e7bf47b248871cf865d23015937d1edeb20db5e3e760"), + hexEncPubkey("17be6b6ba54199b1d80eff866d348ea11d8a4b341d63ad9a6681d3ef8a43853ac564d153eb2a8737f0afc9ab320f6f95c55aa11aaa13bbb1ff422fd16bdf8188"), }, }, } type preminedTestnet struct { - target NodeID - targetSha common.Hash // sha3(target) - dists [hashBits + 1][]NodeID + target encPubkey + targetSha enode.ID // sha3(target) + dists [hashBits + 1][]encPubkey } -func (tn *preminedTestnet) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node, error) { +func (tn *preminedTestnet) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) { // current log distance is encoded in port number // fmt.Println("findnode query at dist", toaddr.Port) if toaddr.Port == 0 { panic("query to node at distance 0") } - next := uint16(toaddr.Port) - 1 - var result []*Node - for i, id := range tn.dists[toaddr.Port] { - result = append(result, NewNode(id, net.ParseIP("127.0.0.1"), next, uint16(i))) + next := toaddr.Port - 1 + var result []*node + for i, ekey := range tn.dists[toaddr.Port] { + key, _ := decodePubkey(ekey) + node := wrapNode(enode.NewV4(key, net.ParseIP("127.0.0.1"), i, next)) + result = append(result, node) } return result, nil } -func (*preminedTestnet) close() {} -func (*preminedTestnet) waitping(from NodeID) error { return nil } -func (*preminedTestnet) ping(toid NodeID, toaddr *net.UDPAddr) error { return nil } +func (*preminedTestnet) close() {} +func (*preminedTestnet) waitping(from enode.ID) error { return nil } +func (*preminedTestnet) ping(toid enode.ID, toaddr *net.UDPAddr) error { return nil } // mine generates a testnet struct literal with nodes at // various distances to the given target. -func (tn *preminedTestnet) mine(target NodeID) { +func (tn *preminedTestnet) mine(target encPubkey) { tn.target = target - tn.targetSha = crypto.Keccak256Hash(tn.target[:]) + tn.targetSha = tn.target.id() found := 0 for found < bucketSize*10 { k := newkey() - id := PubkeyID(&k.PublicKey) - sha := crypto.Keccak256Hash(id[:]) - ld := logdist(tn.targetSha, sha) + key := encodePubkey(&k.PublicKey) + ld := enode.LogDist(tn.targetSha, key.id()) if len(tn.dists[ld]) < bucketSize { - tn.dists[ld] = append(tn.dists[ld], id) + tn.dists[ld] = append(tn.dists[ld], key) fmt.Println("found ID with ld", ld) found++ } @@ -594,14 +549,14 @@ func (tn *preminedTestnet) mine(target NodeID) { fmt.Println("&preminedTestnet{") fmt.Printf(" target: %#v,\n", tn.target) fmt.Printf(" targetSha: %#v,\n", tn.targetSha) - fmt.Printf(" dists: [%d][]NodeID{\n", len(tn.dists)) + fmt.Printf(" dists: [%d][]encPubkey{\n", len(tn.dists)) for ld, ns := range tn.dists { if len(ns) == 0 { continue } - fmt.Printf(" %d: []NodeID{\n", ld) + fmt.Printf(" %d: []encPubkey{\n", ld) for _, n := range ns { - fmt.Printf(" MustHexID(\"%x\"),\n", n[:]) + fmt.Printf(" hexEncPubkey(\"%x\"),\n", n[:]) } fmt.Println(" },") } @@ -609,40 +564,6 @@ func (tn *preminedTestnet) mine(target NodeID) { 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. // it generates a random value of the given value's type. func gen(typ interface{}, rand *rand.Rand) interface{} { @@ -653,6 +574,13 @@ func gen(typ interface{}, rand *rand.Rand) 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 { key, err := crypto.GenerateKey() if err != nil { diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go new file mode 100644 index 0000000000..71aa493512 --- /dev/null +++ b/p2p/discover/table_util_test.go @@ -0,0 +1,167 @@ +// 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 . + +package discover + +import ( + "crypto/ecdsa" + "encoding/hex" + "fmt" + "math/rand" + "net" + "sync" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" +) + +func newTestTable(t transport) (*Table, *enode.DB) { + var r enr.Record + r.Set(enr.IP{0, 0, 0, 0}) + n := enode.SignNull(&r, enode.ID{}) + db, _ := enode.OpenDB("") + tab, _ := newTable(t, n, 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 +} + +func newPingRecorder() *pingRecorder { + return &pingRecorder{ + dead: make(map[enode.ID]bool), + pinged: make(map[enode.ID]bool), + } +} + +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 +} diff --git a/p2p/discover/udp.go b/p2p/discover/udp.go index a41ee3fa6d..c79ea726b0 100644 --- a/p2p/discover/udp.go +++ b/p2p/discover/udp.go @@ -27,6 +27,8 @@ import ( "reflect" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p/nat" @@ -49,8 +51,9 @@ var ( // Timeouts const ( - respTimeout = 500 * time.Millisecond - expiration = 20 * time.Second + respTimeout = 500 * time.Millisecond + expiration = 20 * time.Second + bondExpiration = 24 * time.Hour ntpFailureThreshold = 32 // Continuous timeouts after which to check NTP ntpWarningCooldown = 10 * time.Minute // Minimum amount of time to pass before repeating NTP warning @@ -94,7 +97,7 @@ type ( // findnode is a query for nodes close to the given target. findnode struct { - Target NodeID // doesn't need to be an actual public key + Target encPubkey Expiration uint64 // Ignore additional fields (for forward compatibility). Rest []rlp.RawValue `rlp:"tail"` @@ -112,7 +115,7 @@ type ( IP net.IP // len 4 for IPv4 or 16 for IPv6 UDP uint16 // for discovery protocol TCP uint16 // for RLPx protocol - ID NodeID + ID encPubkey } rpcEndpoint struct { @@ -130,7 +133,7 @@ func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint { 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 { return nil, errors.New("low port") } @@ -140,17 +143,26 @@ func (t *udp) nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*Node, error) { if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) { return nil, errors.New("not contained in netrestrict whitelist") } - n := NewNode(rn.ID, rn.IP, rn.UDP, rn.TCP) - err := n.validateComplete() + key, err := decodePubkey(rn.ID) + if err != nil { + return nil, err + } + n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP))) + err = n.ValidateComplete() return n, err } -func nodeToRPC(n *Node) rpcNode { - return rpcNode{ID: n.ID, IP: n.IP, UDP: n.UDP, TCP: n.TCP} +func nodeToRPC(n *node) rpcNode { + var key ecdsa.PublicKey + 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 { - handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error + handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error name() string } @@ -188,7 +200,7 @@ type udp struct { // to all the callback functions for that node. type pending struct { // these fields must match in the reply. - from NodeID + from enode.ID ptype byte // time when the request must complete @@ -206,7 +218,7 @@ type pending struct { } type reply struct { - from NodeID + from enode.ID ptype byte data interface{} // loop indicates whether there was @@ -232,7 +244,7 @@ type Config struct { AnnounceAddr *net.UDPAddr // local address announced in the DHT NodeDBPath string // if set, the node database is stored at this filesystem location NetRestrict *netutil.Netlist // network whitelist - Bootnodes []*Node // list of bootstrap nodes + Bootnodes []*enode.Node // list of bootstrap nodes Unhandled chan<- ReadPacket // unhandled packets are sent on this channel } @@ -254,6 +266,16 @@ func ListenUDP(c conn, cfg Config) (*Table, error) { } func newUDP(c conn, cfg Config) (*Table, *udp, error) { + realaddr := c.LocalAddr().(*net.UDPAddr) + if cfg.AnnounceAddr != nil { + realaddr = cfg.AnnounceAddr + } + self := enode.NewV4(&cfg.PrivateKey.PublicKey, realaddr.IP, realaddr.Port, realaddr.Port) + db, err := enode.OpenDB(cfg.NodeDBPath) + if err != nil { + return nil, nil, err + } + udp := &udp{ conn: c, priv: cfg.PrivateKey, @@ -262,13 +284,9 @@ func newUDP(c conn, cfg Config) (*Table, *udp, error) { gotreply: make(chan reply), addpending: make(chan *pending), } - 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) + tab, err := newTable(udp, self, db, cfg.Bootnodes) if err != nil { return nil, nil, err } @@ -282,17 +300,18 @@ func newUDP(c conn, cfg Config) (*Table, *udp, error) { func (t *udp) close() { close(t.closing) t.conn.Close() + t.db.Close() // TODO: wait for the loops to end. } // ping sends a ping message to the given node and waits for a reply. -func (t *udp) ping(toid NodeID, toaddr *net.UDPAddr) error { +func (t *udp) ping(toid enode.ID, 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 NodeID, toaddr *net.UDPAddr, callback func()) <-chan error { +func (t *udp) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) <-chan error { req := &ping{ Version: 4, From: t.ourEndpoint, @@ -317,21 +336,21 @@ func (t *udp) sendPing(toid NodeID, toaddr *net.UDPAddr, callback func()) <-chan return errc } -func (t *udp) waitping(from NodeID) error { +func (t *udp) waitping(from enode.ID) error { return <-t.pending(from, pingPacket, func(interface{}) bool { return true }) } // findnode sends a findnode request to the given node and waits until // the node has sent up to k neighbors. -func (t *udp) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node, error) { +func (t *udp) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) { // If we haven't seen a ping from the destination node for a while, it won't remember // our endpoint proof and reject findnode. Solicit a ping first. - if time.Since(t.db.lastPingReceived(toid)) > nodeDBNodeExpiration { + if time.Since(t.db.LastPingReceived(toid)) > bondExpiration { t.ping(toid, toaddr) t.waitping(toid) } - nodes := make([]*Node, 0, bucketSize) + nodes := make([]*node, 0, bucketSize) nreceived := 0 errc := t.pending(toid, neighborsPacket, func(r interface{}) bool { reply := r.(*neighbors) @@ -356,7 +375,7 @@ func (t *udp) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node // pending adds a reply callback to the pending reply queue. // see the documentation of type pending for a detailed explanation. -func (t *udp) pending(id NodeID, ptype byte, callback func(interface{}) bool) <-chan error { +func (t *udp) pending(id enode.ID, ptype byte, callback func(interface{}) bool) <-chan error { ch := make(chan error, 1) p := &pending{from: id, ptype: ptype, callback: callback, errc: ch} select { @@ -368,7 +387,7 @@ func (t *udp) pending(id NodeID, ptype byte, callback func(interface{}) bool) <- return ch } -func (t *udp) handleReply(from NodeID, ptype byte, req packet) bool { +func (t *udp) handleReply(from enode.ID, ptype byte, req packet) bool { matched := make(chan bool, 1) select { case t.gotreply <- reply{from, ptype, req, matched}: @@ -582,19 +601,20 @@ func (t *udp) handlePacket(from *net.UDPAddr, buf []byte) error { return err } -func decodePacket(buf []byte) (packet, NodeID, []byte, error) { +func decodePacket(buf []byte) (packet, encPubkey, []byte, error) { if len(buf) < headSize+1 { - return nil, NodeID{}, nil, errPacketTooSmall + return nil, encPubkey{}, nil, errPacketTooSmall } hash, sig, sigdata := buf[:macSize], buf[macSize:headSize], buf[headSize:] shouldhash := crypto.Keccak256(buf[macSize:]) if !bytes.Equal(hash, shouldhash) { - return nil, NodeID{}, nil, errBadHash + return nil, encPubkey{}, nil, errBadHash } - fromID, err := recoverNodeID(crypto.Keccak256(buf[headSize:]), sig) + fromKey, err := recoverNodeKey(crypto.Keccak256(buf[headSize:]), sig) if err != nil { - return nil, NodeID{}, hash, err + return nil, fromKey, hash, err } + var req packet switch ptype := sigdata[0]; ptype { case pingPacket: @@ -606,14 +626,14 @@ func decodePacket(buf []byte) (packet, NodeID, []byte, error) { case neighborsPacket: req = new(neighbors) default: - return nil, fromID, hash, fmt.Errorf("unknown type: %d", ptype) + return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype) } s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0) err = s.Decode(req) - return req, fromID, hash, err + return req, fromKey, hash, err } -func (req *ping) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error { +func (req *ping) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error { if expired(req.Expiration) { return errExpired } @@ -622,54 +642,55 @@ func (req *ping) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) er return errData } + key, err := decodePubkey(fromKey) + if err != nil { + return fmt.Errorf("invalid public key: %v", err) + } t.send(from, pongPacket, &pong{ To: makeEndpoint(from, req.From.TCP), ReplyTok: mac, Expiration: uint64(time.Now().Add(expiration).Unix()), Rest: cRest, }) - t.handleReply(fromID, pingPacket, req) - - // Add the node to the table. Before doing so, ensure that we have a recent enough pong - // recorded in the database so their findnode requests will be accepted later. - n := NewNode(fromID, from.IP, uint16(from.Port), req.From.TCP) - if time.Since(t.db.lastPongReceived(fromID)) > nodeDBNodeExpiration { - t.sendPing(fromID, from, func() { t.addThroughPing(n) }) + n := wrapNode(enode.NewV4(key, from.IP, int(req.From.TCP), from.Port)) + t.handleReply(n.ID(), pingPacket, req) + if time.Since(t.db.LastPongReceived(n.ID())) > bondExpiration { + t.sendPing(n.ID(), from, func() { t.addThroughPing(n) }) } else { t.addThroughPing(n) } - t.db.updateLastPingReceived(fromID, time.Now()) + t.db.UpdateLastPingReceived(n.ID(), time.Now()) return nil } func (req *ping) name() string { return "PING/v4" } -func (req *pong) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error { +func (req *pong) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error { if expired(req.Expiration) { return errExpired } - if !reflect.DeepEqual(req.Rest, cRest) { return errData } - + fromID := fromKey.id() if !t.handleReply(fromID, pongPacket, req) { return errUnsolicitedReply } - t.db.updateLastPongReceived(fromID, time.Now()) + t.db.UpdateLastPongReceived(fromID, time.Now()) return nil } func (req *pong) name() string { return "PONG/v4" } -func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error { +func (req *findnode) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error { if expired(req.Expiration) { return errExpired } if !reflect.DeepEqual(req.Rest, cRest) { return errData } - if !t.db.hasBond(fromID) { + fromID := fromKey.id() + if time.Since(t.db.LastPongReceived(fromID)) > bondExpiration { // No endpoint proof pong exists, we don't process the packet. This prevents an // attack vector where the discovery protocol could be used to amplify traffic in a // DDOS attack. A malicious actor would send a findnode request with the IP address @@ -678,7 +699,7 @@ func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte // findnode) to the victim. return errUnknownNode } - target := crypto.Keccak256Hash(req.Target[:]) + target := enode.ID(crypto.Keccak256Hash(req.Target[:])) t.mutex.Lock() closest := t.closest(target, bucketSize).entries t.mutex.Unlock() @@ -691,7 +712,7 @@ func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte // Send neighbors in chunks with at most maxNeighbors per packet // to stay below the 1280 byte limit. 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)) } if len(p.Nodes) == maxNeighbors { @@ -708,14 +729,14 @@ func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte func (req *findnode) name() string { return "FINDNODE/v4" } -func (req *neighbors) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error { +func (req *neighbors) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error { if expired(req.Expiration) { return errExpired } if !reflect.DeepEqual(req.Rest, cRest) { return errData } - if !t.handleReply(fromID, neighborsPacket, req) { + if !t.handleReply(fromKey.id(), neighborsPacket, req) { return errUnsolicitedReply } return nil diff --git a/p2p/discover/udp_test.go b/p2p/discover/udp_test.go index 7c8e1ba044..6f5389d6e3 100644 --- a/p2p/discover/udp_test.go +++ b/p2p/discover/udp_test.go @@ -33,10 +33,13 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + "github.com/davecgh/go-spew/spew" + "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/rlp" - "github.com/davecgh/go-spew/spew" ) func init() { @@ -46,7 +49,7 @@ func init() { // shared test variables var ( futureExp = uint64(time.Now().Add(10 * time.Hour).Unix()) - testTarget = NodeID{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1} + testTarget = encPubkey{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} 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} @@ -136,7 +139,7 @@ func TestUDP_pingTimeout(t *testing.T) { defer test.table.Close() toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} - toid := NodeID{1, 2, 3, 4} + toid := enode.ID{1, 2, 3, 4} if err := test.udp.ping(toid, toaddr); err != errTimeout { t.Error("expected timeout error, got", err) } @@ -220,8 +223,8 @@ func TestUDP_findnodeTimeout(t *testing.T) { defer test.table.Close() toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} - toid := NodeID{1, 2, 3, 4} - target := NodeID{4, 5, 6, 7} + toid := enode.ID{1, 2, 3, 4} + target := encPubkey{4, 5, 6, 7} result, err := test.udp.findnode(toid, toaddr, target) if err != errTimeout { t.Error("expected timeout error, got", err) @@ -238,28 +241,30 @@ func TestUDP_findnode(t *testing.T) { // put a few nodes into the table. their exact // distribution shouldn't matter much, although we need to // take care not to overflow any bucket. - targetHash := crypto.Keccak256Hash(testTarget[:]) - nodes := &nodesByDistance{target: targetHash} + nodes := &nodesByDistance{target: testTarget.id()} for i := 0; i < bucketSize; i++ { - nodes.push(nodeAtDistance(test.table.self.sha, i+2), bucketSize) + key := newkey() + n := wrapNode(enode.NewV4(&key.PublicKey, net.IP{10, 13, 0, 1}, 0, i)) + nodes.push(n, bucketSize) } test.table.stuff(nodes.entries) // ensure there's a bond with the test node, // findnode won't be accepted otherwise. - test.table.db.updateLastPongReceived(PubkeyID(&test.remotekey.PublicKey), time.Now()) + remoteID := encodePubkey(&test.remotekey.PublicKey).id() + test.table.db.UpdateLastPongReceived(remoteID, time.Now()) // check that closest neighbors are returned. test.packetIn(nil, findnodePacket, &findnode{Target: testTarget, Expiration: futureExp, Rest: cRest}) - expected := test.table.closest(targetHash, bucketSize) + expected := test.table.closest(testTarget.id(), bucketSize) - waitNeighbors := func(want []*Node) { + waitNeighbors := func(want []*node) { test.waitPacketOut(func(p *neighbors) { if len(p.Nodes) != len(want) { t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSize) } for i := range p.Nodes { - if p.Nodes[i].ID != want[i].ID { + if p.Nodes[i].ID.id() != want[i].ID() { t.Errorf("result mismatch at %d:\n got: %v\n want: %v", i, p.Nodes[i], expected.entries[i]) } } @@ -273,12 +278,13 @@ func TestUDP_findnodeMultiReply(t *testing.T) { test := newUDPTest(t) defer test.table.Close() - rid := PubkeyID(&test.remotekey.PublicKey) - test.table.db.updateLastPingReceived(rid, time.Now()) + rid := enode.PubkeyToIDV4(&test.remotekey.PublicKey) + test.table.db.UpdateLastPingReceived(rid, time.Now()) // queue a pending findnode request - resultc, errc := make(chan []*Node), make(chan error) + resultc, errc := make(chan []*node), make(chan error) go func() { + rid := encodePubkey(&test.remotekey.PublicKey).id() ns, err := test.udp.findnode(rid, test.remoteaddr, testTarget) if err != nil && len(ns) == 0 { errc <- err @@ -296,11 +302,11 @@ func TestUDP_findnodeMultiReply(t *testing.T) { }) // send the reply as two packets. - list := []*Node{ - MustParseNode("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:16789?discport=30304"), - MustParseNode("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:16789"), - MustParseNode("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:16789?discport=17"), - MustParseNode("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:16789"), + list := []*node{ + wrapNode(enode.MustParseV4("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304")), + wrapNode(enode.MustParseV4("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303")), + wrapNode(enode.MustParseV4("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17")), + wrapNode(enode.MustParseV4("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303")), } rpclist := make([]rpcNode, len(list)) for i := range list { @@ -325,8 +331,8 @@ func TestUDP_findnodeMultiReply(t *testing.T) { func TestUDP_successfulPing(t *testing.T) { test := newUDPTest(t) - added := make(chan *Node, 1) - test.table.nodeAddedHook = func(n *Node) { added <- n } + added := make(chan *node, 1) + test.table.nodeAddedHook = func(n *node) { added <- n } defer test.table.Close() // The remote side sends a ping packet to initiate the exchange. @@ -370,18 +376,18 @@ func TestUDP_successfulPing(t *testing.T) { // pong packet. select { case n := <-added: - rid := PubkeyID(&test.remotekey.PublicKey) - if n.ID != rid { - t.Errorf("node has wrong ID: got %v, want %v", n.ID, rid) + rid := encodePubkey(&test.remotekey.PublicKey).id() + if n.ID() != rid { + t.Errorf("node has wrong ID: got %v, want %v", n.ID(), rid) } - if !n.IP.Equal(test.remoteaddr.IP) { - t.Errorf("node has wrong IP: got %v, want: %v", n.IP, 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) } - if int(n.UDP) != test.remoteaddr.Port { - t.Errorf("node has wrong UDP port: got %v, want: %v", 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) } - if n.TCP != testRemote.TCP { - t.Errorf("node has wrong TCP port: got %v, want: %v", n.TCP, testRemote.TCP) + if n.TCP() != int(testRemote.TCP) { + t.Errorf("node has wrong TCP port: got %v, want: %v", n.TCP(), testRemote.TCP) } case <-time.After(2 * time.Second): t.Errorf("node was not added within 2 seconds") @@ -434,7 +440,7 @@ var testPackets = []struct { { input: "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260add7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396", wantPacket: &findnode{ - Target: MustHexID("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"), + Target: hexEncPubkey("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"), Expiration: 1136239445, Rest: []rlp.RawValue{{0x82, 0x99, 0x99}, {0x83, 0x99, 0x99, 0x99}}, }, @@ -444,25 +450,25 @@ var testPackets = []struct { wantPacket: &neighbors{ Nodes: []rpcNode{ { - ID: MustHexID("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"), + ID: hexEncPubkey("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"), IP: net.ParseIP("99.33.22.55").To4(), UDP: 4444, TCP: 4445, }, { - ID: MustHexID("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"), + ID: hexEncPubkey("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"), IP: net.ParseIP("1.2.3.4").To4(), UDP: 1, TCP: 1, }, { - ID: MustHexID("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"), + ID: hexEncPubkey("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"), IP: net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), UDP: 3333, TCP: 3333, }, { - ID: MustHexID("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"), + ID: hexEncPubkey("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"), IP: net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), UDP: 999, TCP: 1000, @@ -476,14 +482,14 @@ var testPackets = []struct { func TestForwardCompatibility(t *testing.T) { testkey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - wantNodeID := PubkeyID(&testkey.PublicKey) + wantNodeKey := encodePubkey(&testkey.PublicKey) for _, test := range testPackets { input, err := hex.DecodeString(test.input) if err != nil { t.Fatalf("invalid hex: %s", test.input) } - packet, nodeid, _, err := decodePacket(input) + packet, nodekey, _, err := decodePacket(input) if err != nil { t.Errorf("did not accept packet %s\n%v", test.input, err) continue @@ -491,8 +497,8 @@ func TestForwardCompatibility(t *testing.T) { if !reflect.DeepEqual(packet, test.wantPacket) { t.Errorf("got %s\nwant %s", spew.Sdump(packet), spew.Sdump(test.wantPacket)) } - if nodeid != wantNodeID { - t.Errorf("got id %v\nwant id %v", nodeid, wantNodeID) + if nodekey != wantNodeKey { + t.Errorf("got id %v\nwant id %v", nodekey, wantNodeKey) } } } diff --git a/p2p/enr/idscheme.go b/p2p/enode/idscheme.go similarity index 50% rename from p2p/enr/idscheme.go rename to p2p/enode/idscheme.go index f6ea2a8149..d5fa0a0894 100644 --- a/p2p/enr/idscheme.go +++ b/p2p/enode/idscheme.go @@ -14,12 +14,14 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package enr +package enode import ( "crypto/ecdsa" "fmt" - "sync" + "io" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" "github.com/AlayaNetwork/Alaya-Go/common/math" "github.com/AlayaNetwork/Alaya-Go/crypto" @@ -27,44 +29,24 @@ import ( "github.com/AlayaNetwork/Alaya-Go/rlp" ) -// Registry of known identity schemes. -var schemes sync.Map - -// 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 +// List of known secure identity schemes. +var ValidSchemes = enr.SchemeMap{ + "v4": V4ID{}, } -// RegisterIdentityScheme adds an identity scheme to the global registry. -func RegisterIdentityScheme(name string, scheme IdentityScheme) { - if _, loaded := schemes.LoadOrStore(name, scheme); loaded { - panic("identity scheme " + name + " already registered") - } -} - -// FindIdentityScheme resolves name to an identity scheme in the global registry. -func FindIdentityScheme(name string) IdentityScheme { - s, ok := schemes.Load(name) - if !ok { - return nil - } - return s.(IdentityScheme) +var ValidSchemesForTesting = enr.SchemeMap{ + "v4": V4ID{}, + "null": NullID{}, } // v4ID is the "v4" identity scheme. -type v4ID struct{} - -func init() { - RegisterIdentityScheme("v4", v4ID{}) -} +type V4ID struct{} // SignV4 signs a record using the v4 scheme. -func SignV4(r *Record, privkey *ecdsa.PrivateKey) error { +func SignV4(r *enr.Record, privkey *ecdsa.PrivateKey) error { // Copy r to avoid modifying it if signing fails. cpy := *r - cpy.Set(ID("v4")) + cpy.Set(enr.ID("v4")) cpy.Set(Secp256k1(privkey.PublicKey)) h := sha3.NewKeccak256() @@ -74,18 +56,13 @@ func SignV4(r *Record, privkey *ecdsa.PrivateKey) error { return err } sig = sig[:len(sig)-1] // remove v - if err = cpy.SetSig("v4", sig); err == nil { + if err = cpy.SetSig(V4ID{}, sig); err == nil { *r = cpy } return err } -// s256raw is an unparsed secp256k1 public key entry. -type s256raw []byte - -func (s256raw) ENRKey() string { return "secp256k1" } - -func (v4ID) Verify(r *Record, sig []byte) error { +func (V4ID) Verify(r *enr.Record, sig []byte) error { var entry s256raw if err := r.Load(&entry); err != nil { return err @@ -96,12 +73,12 @@ func (v4ID) Verify(r *Record, sig []byte) error { h := sha3.NewKeccak256() rlp.Encode(h, r.AppendElements(nil)) if !crypto.VerifySignature(entry, h.Sum(nil), sig) { - return errInvalidSig + return enr.ErrInvalidSig } return nil } -func (v4ID) NodeAddr(r *Record) []byte { +func (V4ID) NodeAddr(r *enr.Record) []byte { var pubkey Secp256k1 err := r.Load(&pubkey) if err != nil { @@ -112,3 +89,73 @@ func (v4ID) NodeAddr(r *Record) []byte { 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} +} diff --git a/p2p/enode/idscheme_test.go b/p2p/enode/idscheme_test.go new file mode 100644 index 0000000000..fcfe12c043 --- /dev/null +++ b/p2p/enode/idscheme_test.go @@ -0,0 +1,75 @@ +// 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 . + +package enode + +import ( + "bytes" + "crypto/ecdsa" + "encoding/hex" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/rlp" +) + +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) +} diff --git a/p2p/enode/node.go b/p2p/enode/node.go new file mode 100644 index 0000000000..3f5a8c0b31 --- /dev/null +++ b/p2p/enode/node.go @@ -0,0 +1,384 @@ +// 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 . + +package enode + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "encoding/hex" + "errors" + "fmt" + "math/big" + "math/bits" + "math/rand" + "net" + "strings" + + "github.com/AlayaNetwork/Alaya-Go/common/math" + + "github.com/AlayaNetwork/Alaya-Go/crypto" + + "github.com/AlayaNetwork/Alaya-Go/log" + + "github.com/AlayaNetwork/Alaya-Go/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 +} + +// IDv0 returns the node byte of the Pubkey. +func (n *Node) IDv0() IDv0 { + var id IDv0 + pub := n.Pubkey() + if pub == nil { + log.Debug("this pub key is empty") + return id + } + return PublicKeyToIDv0(pub) +} + +// 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 +} + +var ( + ZeroIDv0 = MustHexIDv0(IDv0{}.String()) +) + +type IDv0 crypto.PubkeyBytes + +// Bytes returns a byte slice representation of the NodeID +func (n IDv0) Bytes() []byte { + return n[:] +} + +// 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 (n IDv0) Pubkey() (*ecdsa.PublicKey, error) { + p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)} + half := len(n) / 2 + p.X.SetBytes(n[:half]) + p.Y.SetBytes(n[half:]) + if !p.Curve.IsOnCurve(p.X, p.Y) { + return nil, errors.New("id is invalid secp256k1 curve point") + } + return p, nil +} + +// TerminalString returns a shortened hex string for terminal logging. +func (n IDv0) TerminalString() string { + return hex.EncodeToString(n[:8]) +} + +// PubkeyBytes prints as a long hexadecimal number. +func (n IDv0) String() string { + return fmt.Sprintf("%x", n[:]) +} + +// MarshalText implements the encoding.TextMarshaler interface. +func (n IDv0) MarshalText() ([]byte, error) { + return []byte(hex.EncodeToString(n[:])), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +func (n *IDv0) UnmarshalText(text []byte) error { + id, err := HexIDv0(string(text)) + if err != nil { + return err + } + *n = id + return nil +} + +func (n *IDv0) ID() ID { + pubkey, err := n.Pubkey() + if err != nil { + panic(err) + } + buf := make([]byte, 64) + math.ReadBits(pubkey.X, buf[:32]) + math.ReadBits(pubkey.Y, buf[32:]) + var id ID + copy(id[:], crypto.Keccak256(buf)) + return id +} + +// MustBytesToIDv0 converts a byte slice to a PubkeyBytes. +// It panics if the byte slice is not a valid PubkeyBytes. +func MustBytesToIDv0(b []byte) IDv0 { + id, err := BytesToIDv0(b) + if err != nil { + panic(err) + } + return id +} + +// BytesToIDv0 converts a byte slice to a PubkeyBytes +func BytesToIDv0(b []byte) (IDv0, error) { + var id IDv0 + if len(b) != len(id) { + return id, fmt.Errorf("wrong length, want %d bytes", len(id)) + } + copy(id[:], b) + return id, nil +} + +// PublicKeyToIDv0 returns a marshaled representation of the given public key. +func PublicKeyToIDv0(pub *ecdsa.PublicKey) IDv0 { + var id IDv0 + 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 +} + +// MustHexIDv0 converts a hex string to a NodeID. +// It panics if the string is not a valid NodeID. +func MustHexIDv0(in string) IDv0 { + id, err := HexIDv0(in) + if err != nil { + panic(err) + } + return id +} + +// HexID converts a hex string to a NodeID. +// The string may be prefixed with 0x. +func HexIDv0(in string) (IDv0, error) { + var id IDv0 + 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 +} diff --git a/p2p/enode/node_test.go b/p2p/enode/node_test.go new file mode 100644 index 0000000000..1abd5d70ce --- /dev/null +++ b/p2p/enode/node_test.go @@ -0,0 +1,63 @@ +// 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 . + +package enode + +import ( + "encoding/hex" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/rlp" +) + +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) + } + } +} diff --git a/p2p/discover/database.go b/p2p/enode/nodedb.go similarity index 68% rename from p2p/discover/database.go rename to p2p/enode/nodedb.go index 9c77f527d7..51060498e2 100644 --- a/p2p/discover/database.go +++ b/p2p/enode/nodedb.go @@ -17,38 +17,39 @@ // Contains the node database, storing previously seen nodes and any collected // metadata about them for QoS purposes. -package discover +package enode import ( "bytes" "crypto/rand" "encoding/binary" + "fmt" "os" "sync" "time" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/storage" "github.com/syndtr/goleveldb/leveldb/util" + + "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/rlp" ) var ( - nodeDBNilNodeID = NodeID{} // Special node ID to use as a nil element. + nodeDBNilID = ID{} // Special node ID to use as a nil element. nodeDBNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped. nodeDBCleanupCycle = time.Hour // Time period for running the expiration task. - nodeDBVersion = 5 + nodeDBVersion = 6 ) -// nodeDB stores all nodes we know about. -type nodeDB struct { +// DB is the node database, storing previously seen nodes and any collected metadata about +// them for QoS purposes. +type DB struct { 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 quit chan struct{} // Channel to signal the expiring thread to stop } @@ -64,33 +65,27 @@ var ( 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) { +// OpenDB opens a 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 OpenDB(path string) (*DB, error) { if path == "" { - return newMemoryNodeDB(self) + return newMemoryDB() } - return newPersistentNodeDB(path, version, self) + return newPersistentDB(path) } -// newMemoryNodeDB creates a new in-memory node database without a persistent -// backend. -func newMemoryNodeDB(self NodeID) (*nodeDB, error) { +// newMemoryNodeDB creates a new in-memory node database without a persistent backend. +func newMemoryDB() (*DB, error) { db, err := leveldb.Open(storage.NewMemStorage(), nil) if err != nil { return nil, err } - return &nodeDB{ - lvl: db, - self: self, - quit: make(chan struct{}), - }, nil + return &DB{lvl: db, quit: make(chan struct{})}, nil } // newPersistentNodeDB creates/opens a leveldb backed persistent node database, // also flushing its contents in case of a version mismatch. -func newPersistentNodeDB(path string, version int, self NodeID) (*nodeDB, error) { +func newPersistentDB(path string) (*DB, error) { opts := &opt.Options{OpenFilesCacheCapacity: 5} db, err := leveldb.OpenFile(path, opts) if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted { @@ -102,7 +97,7 @@ func newPersistentNodeDB(path string, version int, self NodeID) (*nodeDB, error) // The nodes contained in the cache correspond to a certain protocol version. // Flush all nodes if the version doesn't match. currentVer := make([]byte, binary.MaxVarintLen64) - currentVer = currentVer[:binary.PutVarint(currentVer, int64(version))] + currentVer = currentVer[:binary.PutVarint(currentVer, int64(nodeDBVersion))] blob, err := db.Get(nodeDBVersionKey, nil) switch err { @@ -120,30 +115,26 @@ func newPersistentNodeDB(path string, version int, self NodeID) (*nodeDB, error) if err = os.RemoveAll(path); err != nil { return nil, err } - return newPersistentNodeDB(path, version, self) + return newPersistentDB(path) } } - return &nodeDB{ - lvl: db, - self: self, - quit: make(chan struct{}), - }, nil + return &DB{lvl: db, quit: make(chan struct{})}, nil } // makeKey generates the leveldb key-blob from a node id and its particular // field of interest. -func makeKey(id NodeID, field string) []byte { - if bytes.Equal(id[:], nodeDBNilNodeID[:]) { +func makeKey(id ID, field string) []byte { + if bytes.Equal(id[:], nodeDBNilID[:]) { return []byte(field) } return append(nodeDBItemPrefix, append(id[:], field...)...) } // splitKey tries to split a database key into a node id and a field part. -func splitKey(key []byte) (id NodeID, field string) { +func splitKey(key []byte) (id ID, field string) { // If the key is not of a node, return it plainly if !bytes.HasPrefix(key, nodeDBItemPrefix) { - return NodeID{}, string(key) + return ID{}, string(key) } // Otherwise split the id and field item := key[len(nodeDBItemPrefix):] @@ -155,7 +146,7 @@ func splitKey(key []byte) (id NodeID, field string) { // fetchInt64 retrieves an integer instance associated with a particular // database key. -func (db *nodeDB) fetchInt64(key []byte) int64 { +func (db *DB) fetchInt64(key []byte) int64 { blob, err := db.lvl.Get(key, nil) if err != nil { return 0 @@ -169,39 +160,43 @@ func (db *nodeDB) fetchInt64(key []byte) int64 { // storeInt64 update a specific database entry to the current time instance as a // unix timestamp. -func (db *nodeDB) storeInt64(key []byte, n int64) error { +func (db *DB) storeInt64(key []byte, n int64) error { blob := make([]byte, binary.MaxVarintLen64) blob = blob[:binary.PutVarint(blob, n)] return db.lvl.Put(key, blob, nil) } -// node retrieves a node with a given id from the database. -func (db *nodeDB) node(id NodeID) *Node { +// 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, nodeDBDiscoverRoot), nil) if err != nil { return nil } + return mustDecodeNode(id[:], blob) +} + +func mustDecodeNode(id, data []byte) *Node { node := new(Node) - if err := rlp.DecodeBytes(blob, node); err != nil { - log.Error("Failed to decode node RLP", "err", err) - return nil + if err := rlp.DecodeBytes(data, &node.r); err != nil { + panic(fmt.Errorf("p2p/enode: can't decode node %x in DB: %v", id, err)) } - node.sha = crypto.Keccak256Hash(node.ID[:]) + // Restore node id cache. + copy(node.id[:], id) return node } -// updateNode inserts - potentially overwriting - a node into the peer database. -func (db *nodeDB) updateNode(node *Node) error { - blob, err := rlp.EncodeToBytes(node) +// UpdateNode inserts - potentially overwriting - a node into the peer database. +func (db *DB) UpdateNode(node *Node) error { + blob, err := rlp.EncodeToBytes(&node.r) if err != nil { return err } - return db.lvl.Put(makeKey(node.ID, nodeDBDiscoverRoot), blob, nil) + return db.lvl.Put(makeKey(node.ID(), nodeDBDiscoverRoot), blob, nil) } -// deleteNode deletes all information/keys associated with a node. -func (db *nodeDB) deleteNode(id NodeID) error { +// 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) for deleter.Next() { if err := db.lvl.Delete(deleter.Key(), nil); err != nil { @@ -220,13 +215,13 @@ func (db *nodeDB) deleteNode(id NodeID) error { // it would require significant overhead to exactly trace the first successful // convergence, it's simpler to "ensure" the correct state when an appropriate // condition occurs (i.e. a successful bonding), and discard further events. -func (db *nodeDB) ensureExpirer() { +func (db *DB) ensureExpirer() { db.runner.Do(func() { go db.expirer() }) } // expirer should be started in a go routine, and is responsible for looping ad // infinitum and dropping stale data from the database. -func (db *nodeDB) expirer() { +func (db *DB) expirer() { tick := time.NewTicker(nodeDBCleanupCycle) defer tick.Stop() for { @@ -243,7 +238,7 @@ func (db *nodeDB) expirer() { // expireNodes iterates over the database and deletes all nodes that have not // been seen (i.e. received a pong from) for some allotted time. -func (db *nodeDB) expireNodes() error { +func (db *DB) expireNodes() error { threshold := time.Now().Add(-nodeDBNodeExpiration) // Find discovered nodes that are older than the allowance @@ -257,60 +252,56 @@ func (db *nodeDB) expireNodes() error { continue } // Skip the node if not expired yet (and not self) - if !bytes.Equal(id[:], db.self[:]) { - if seen := db.lastPongReceived(id); seen.After(threshold) { - continue - } + if seen := db.LastPongReceived(id); seen.After(threshold) { + continue } // Otherwise delete all associated information - db.deleteNode(id) + db.DeleteNode(id) } return nil } -// lastPingReceived retrieves the time of the last ping packet sent by the remote node. -func (db *nodeDB) lastPingReceived(id NodeID) time.Time { +// LastPingReceived retrieves the time of the last ping packet received from +// a remote node. +func (db *DB) LastPingReceived(id ID) time.Time { return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPing)), 0) } -// updateLastPing updates the last time remote node pinged us. -func (db *nodeDB) updateLastPingReceived(id NodeID, instance time.Time) error { +// UpdateLastPingReceived updates the last time we tried contacting a remote node. +func (db *DB) UpdateLastPingReceived(id ID, instance time.Time) error { return db.storeInt64(makeKey(id, nodeDBDiscoverPing), instance.Unix()) } -// lastPongReceived retrieves the time of the last successful pong from remote node. -func (db *nodeDB) lastPongReceived(id NodeID) time.Time { +// LastPongReceived retrieves the time of the last successful pong from remote node. +func (db *DB) LastPongReceived(id ID) time.Time { + // Launch expirer + db.ensureExpirer() return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPong)), 0) } -// hasBond reports whether the given node is considered bonded. -func (db *nodeDB) hasBond(id NodeID) bool { - return time.Since(db.lastPongReceived(id)) < nodeDBNodeExpiration -} - -// updateLastPongReceived updates the last pong time of a node. -func (db *nodeDB) updateLastPongReceived(id NodeID, instance time.Time) error { +// UpdateLastPongReceived updates the last pong time of a node. +func (db *DB) UpdateLastPongReceived(id ID, instance time.Time) error { return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix()) } -// findFails retrieves the number of findnode failures since bonding. -func (db *nodeDB) findFails(id NodeID) int { +// FindFails retrieves the number of findnode failures since bonding. +func (db *DB) FindFails(id ID) int { return int(db.fetchInt64(makeKey(id, nodeDBDiscoverFindFails))) } -// updateFindFails updates the number of findnode failures since bonding. -func (db *nodeDB) updateFindFails(id NodeID, fails int) error { +// UpdateFindFails updates the number of findnode failures since bonding. +func (db *DB) UpdateFindFails(id ID, fails int) error { return db.storeInt64(makeKey(id, nodeDBDiscoverFindFails), int64(fails)) } -// querySeeds retrieves random nodes to be used as potential seed nodes +// QuerySeeds retrieves random nodes to be used as potential seed nodes // for bootstrapping. -func (db *nodeDB) querySeeds(n int, maxAge time.Duration) []*Node { +func (db *DB) QuerySeeds(n int, maxAge time.Duration) []*Node { var ( now = time.Now() nodes = make([]*Node, 0, n) it = db.lvl.NewIterator(nil, nil) - id NodeID + id ID ) defer it.Release() @@ -329,14 +320,11 @@ seek: id[0] = 0 continue seek // iterator exhausted } - if n.ID == db.self { - continue seek - } - if now.Sub(db.lastPongReceived(n.ID)) > maxAge { + if now.Sub(db.LastPongReceived(n.ID())) > maxAge { continue seek } for i := range nodes { - if nodes[i].ID == n.ID { + if nodes[i].ID() == n.ID() { continue seek // duplicate } } @@ -353,18 +341,13 @@ func nextNode(it iterator.Iterator) *Node { if field != nodeDBDiscoverRoot { continue } - 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 mustDecodeNode(id[:], it.Value()) } return nil } // close flushes and closes the database files. -func (db *nodeDB) close() { +func (db *DB) Close() { close(db.quit) db.lvl.Close() } diff --git a/p2p/discover/database_test.go b/p2p/enode/nodedb_test.go similarity index 55% rename from p2p/discover/database_test.go rename to p2p/enode/nodedb_test.go index 3e384a1a9a..a9efb6d43d 100644 --- a/p2p/discover/database_test.go +++ b/p2p/enode/nodedb_test.go @@ -14,10 +14,11 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package discover +package enode import ( "bytes" + "fmt" "io/ioutil" "net" "os" @@ -28,24 +29,21 @@ import ( ) var nodeDBKeyTests = []struct { - id NodeID + id ID field string key []byte }{ { - id: NodeID{}, + id: ID{}, field: "version", key: []byte{0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e}, // field }, { - id: MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + id: HexID("51232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), field: ":discover", - key: []byte{0x6e, 0x3a, // prefix - 0x1d, 0xd9, 0xd6, 0x5c, 0x45, 0x52, 0xb5, 0xeb, // 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, // + key: []byte{ + 0x6e, 0x3a, // prefix + 0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, // node id 0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, // 0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, // 0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, // @@ -54,7 +52,7 @@ var nodeDBKeyTests = []struct { }, } -func TestNodeDBKeys(t *testing.T) { +func TestDBKeys(t *testing.T) { for i, tt := range nodeDBKeyTests { 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) @@ -78,9 +76,9 @@ var nodeDBInt64Tests = []struct { {key: []byte{0x03}, value: 3}, } -func TestNodeDBInt64(t *testing.T) { - db, _ := newNodeDB("", nodeDBVersion, NodeID{}) - defer db.close() +func TestDBInt64(t *testing.T) { + db, _ := OpenDB("") + defer db.Close() tests := nodeDBInt64Tests for i := 0; i < len(tests); i++ { @@ -101,9 +99,9 @@ func TestNodeDBInt64(t *testing.T) { } } -func TestNodeDBFetchStore(t *testing.T) { - node := NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), +func TestDBFetchStore(t *testing.T) { + node := NewV4( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.IP{192, 168, 0, 1}, 16789, 16789, @@ -111,47 +109,47 @@ func TestNodeDBFetchStore(t *testing.T) { inst := time.Now() num := 314 - db, _ := newNodeDB("", nodeDBVersion, NodeID{}) - defer db.close() + db, _ := OpenDB("") + defer db.Close() // Check fetch/store operations on a node ping object - if stored := db.lastPingReceived(node.ID); stored.Unix() != 0 { + if stored := db.LastPingReceived(node.ID()); stored.Unix() != 0 { t.Errorf("ping: non-existing object: %v", stored) } - if err := db.updateLastPingReceived(node.ID, inst); err != nil { + if err := db.UpdateLastPingReceived(node.ID(), inst); err != nil { t.Errorf("ping: failed to update: %v", err) } - if stored := db.lastPingReceived(node.ID); stored.Unix() != inst.Unix() { + if stored := db.LastPingReceived(node.ID()); stored.Unix() != inst.Unix() { t.Errorf("ping: value mismatch: have %v, want %v", stored, inst) } // Check fetch/store operations on a node pong object - if stored := db.lastPongReceived(node.ID); stored.Unix() != 0 { + if stored := db.LastPongReceived(node.ID()); stored.Unix() != 0 { t.Errorf("pong: non-existing object: %v", stored) } - if err := db.updateLastPongReceived(node.ID, inst); err != nil { + if err := db.UpdateLastPongReceived(node.ID(), inst); err != nil { t.Errorf("pong: failed to update: %v", err) } - if stored := db.lastPongReceived(node.ID); stored.Unix() != inst.Unix() { + if stored := db.LastPongReceived(node.ID()); stored.Unix() != inst.Unix() { t.Errorf("pong: value mismatch: have %v, want %v", stored, inst) } // 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) } - 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) } - 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) } // 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) } - if err := db.updateNode(node); err != nil { + if err := db.UpdateNode(node); err != nil { 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") } else if !reflect.DeepEqual(stored, node) { t.Errorf("node: data mismatch: have %v, want %v", stored, node) @@ -165,8 +163,8 @@ var nodeDBSeedQueryNodes = []struct { // This one should not be in the result set because its last // pong time is too far in the past. { - node: NewNode( - MustHexID("0x84d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + node: NewV4( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.IP{127, 0, 0, 3}, 16789, 16789, @@ -176,8 +174,8 @@ var nodeDBSeedQueryNodes = []struct { // This one shouldn't be in in the result set because its // nodeID is the local node's ID. { - node: NewNode( - MustHexID("0x57d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + node: NewV4( + hexPubkey("ff93ff820abacd4351b0f14e47b324bc82ff014c226f3f66a53535734a3c150e7e38ca03ef0964ba55acddc768f5e99cd59dea95ddd4defbab1339c92fa319b2"), net.IP{127, 0, 0, 3}, 16789, 16789, @@ -187,8 +185,8 @@ var nodeDBSeedQueryNodes = []struct { // These should be in the result set. { - node: NewNode( - MustHexID("0x22d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + node: NewV4( + hexPubkey("c2b5eb3f5dde05f815b63777809ee3e7e0cbb20035a6b00ce327191e6eaa8f26a8d461c9112b7ab94698e7361fa19fd647e603e73239002946d76085b6f928d6"), net.IP{127, 0, 0, 1}, 16789, 16789, @@ -196,8 +194,8 @@ var nodeDBSeedQueryNodes = []struct { pong: time.Now().Add(-2 * time.Second), }, { - node: NewNode( - MustHexID("0x44d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + node: NewV4( + hexPubkey("6ca1d400c8ddf8acc94bcb0dd254911ad71a57bed5e0ae5aa205beed59b28c2339908e97990c493499613cff8ecf6c3dc7112a8ead220cdcd00d8847ca3db755"), net.IP{127, 0, 0, 2}, 16789, 16789, @@ -205,56 +203,91 @@ var nodeDBSeedQueryNodes = []struct { pong: time.Now().Add(-3 * time.Second), }, { - node: NewNode( - MustHexID("0xe2d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + node: NewV4( + hexPubkey("234dc63fe4d131212b38236c4c3411288d7bec61cbf7b120ff12c43dc60c96182882f4291d209db66f8a38e986c9c010ff59231a67f9515c7d1668b86b221a47"), net.IP{127, 0, 0, 3}, 16789, 16789, ), 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 TestNodeDBSeedQuery(t *testing.T) { - db, _ := newNodeDB("", nodeDBVersion, nodeDBSeedQueryNodes[1].node.ID) - defer db.close() +func TestDBSeedQuery(t *testing.T) { + // Querying seeds uses seeks an might not find all nodes + // every time when the database is small. Run the test multiple + // 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 for i, seed := range nodeDBSeedQueryNodes { - if err := db.updateNode(seed.node); err != nil { - t.Fatalf("node %d: failed to insert: %v", i, err) + if err := db.UpdateNode(seed.node); err != nil { + return fmt.Errorf("node %d: failed to insert: %v", i, err) } - if err := db.updateLastPongReceived(seed.node.ID, seed.pong); err != nil { - t.Fatalf("node %d: failed to insert bondTime: %v", i, err) + if err := db.UpdateLastPongReceived(seed.node.ID(), seed.pong); err != nil { + return fmt.Errorf("node %d: failed to insert bondTime: %v", i, err) } } // Retrieve the entire batch and check for duplicates - seeds := db.querySeeds(len(nodeDBSeedQueryNodes)*2, time.Hour) - have := make(map[NodeID]struct{}) + seeds := db.QuerySeeds(len(nodeDBSeedQueryNodes)*2, time.Hour) + have := make(map[ID]struct{}) for _, seed := range seeds { - have[seed.ID] = struct{}{} + have[seed.ID()] = struct{}{} } - want := make(map[NodeID]struct{}) - for _, seed := range nodeDBSeedQueryNodes[2:] { - want[seed.node.ID] = struct{}{} + want := make(map[ID]struct{}) + for _, seed := range nodeDBSeedQueryNodes[1:] { + want[seed.node.ID()] = struct{}{} } if len(seeds) != len(want) { - t.Errorf("seed count mismatch: have %v, want %v", len(seeds), len(want)) + return fmt.Errorf("seed count mismatch: have %v, want %v", len(seeds), len(want)) } for id := range have { if _, ok := want[id]; !ok { - t.Errorf("extra seed: %v", id) + return fmt.Errorf("extra seed: %v", id) } } for id := range want { if _, ok := have[id]; !ok { - t.Errorf("missing seed: %v", id) + return fmt.Errorf("missing seed: %v", id) } } + return nil } -func TestNodeDBPersistency(t *testing.T) { +func TestDBPersistency(t *testing.T) { root, err := ioutil.TempDir("", "nodedb-") if err != nil { t.Fatalf("failed to create temporary data folder: %v", err) @@ -267,34 +300,24 @@ func TestNodeDBPersistency(t *testing.T) { ) // Create a persistent database and store some values - db, err := newNodeDB(filepath.Join(root, "database"), nodeDBVersion, NodeID{}) + db, err := OpenDB(filepath.Join(root, "database")) if err != nil { t.Fatalf("failed to create persistent database: %v", err) } if err := db.storeInt64(testKey, testInt); err != nil { t.Fatalf("failed to store value: %v.", err) } - db.close() + db.Close() // Reopen the database and check the value - db, err = newNodeDB(filepath.Join(root, "database"), nodeDBVersion, NodeID{}) + db, err = OpenDB(filepath.Join(root, "database")) if err != nil { t.Fatalf("failed to open persistent database: %v", err) } if val := db.fetchInt64(testKey); val != testInt { t.Fatalf("value mismatch: have %v, want %v", val, testInt) } - db.close() - - // Change the database version and check flush - db, err = newNodeDB(filepath.Join(root, "database"), nodeDBVersion+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() + db.Close() } var nodeDBExpirationNodes = []struct { @@ -303,8 +326,8 @@ var nodeDBExpirationNodes = []struct { exp bool }{ { - node: NewNode( - MustHexID("0x01d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + node: NewV4( + hexPubkey("8d110e2ed4b446d9b5fb50f117e5f37fb7597af455e1dab0e6f045a6eeaa786a6781141659020d38bdc5e698ed3d4d2bafa8b5061810dfa63e8ac038db2e9b67"), net.IP{127, 0, 0, 1}, 16789, 16789, @@ -312,8 +335,8 @@ var nodeDBExpirationNodes = []struct { pong: time.Now().Add(-nodeDBNodeExpiration + time.Minute), exp: false, }, { - node: NewNode( - MustHexID("0x02d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + node: NewV4( + hexPubkey("913a205579c32425b220dfba999d215066e5bdbf900226b11da1907eae5e93eb40616d47412cf819664e9eacbdfcca6b0c6e07e09847a38472d4be46ab0c3672"), net.IP{127, 0, 0, 2}, 16789, 16789, @@ -323,16 +346,16 @@ var nodeDBExpirationNodes = []struct { }, } -func TestNodeDBExpiration(t *testing.T) { - db, _ := newNodeDB("", nodeDBVersion, NodeID{}) - defer db.close() +func TestDBExpiration(t *testing.T) { + db, _ := OpenDB("") + 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 { + if err := db.UpdateNode(seed.node); err != nil { t.Fatalf("node %d: failed to insert: %v", i, err) } - if err := db.updateLastPongReceived(seed.node.ID, seed.pong); err != nil { + if err := db.UpdateLastPongReceived(seed.node.ID(), seed.pong); err != nil { t.Fatalf("node %d: failed to update bondTime: %v", i, err) } } @@ -341,40 +364,9 @@ func TestNodeDBExpiration(t *testing.T) { t.Fatalf("failed to expire nodes: %v", err) } 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) { 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("", nodeDBVersion, 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.updateLastPongReceived(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") - } -} diff --git a/p2p/enode/urlv4.go b/p2p/enode/urlv4.go new file mode 100644 index 0000000000..7066fa0e77 --- /dev/null +++ b/p2p/enode/urlv4.go @@ -0,0 +1,198 @@ +// 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 . + +package enode + +import ( + "crypto/ecdsa" + "encoding/hex" + "errors" + "fmt" + "net" + "net/url" + "regexp" + "strconv" + + "github.com/AlayaNetwork/Alaya-Go/common/math" + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/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:// +// +// +// 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://@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 { + if ips, err := net.LookupIP(host); err == nil && len(ips) != 0 { + ip = ips[0] + } else { + 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)) +} diff --git a/p2p/enode/urlv4_test.go b/p2p/enode/urlv4_test.go new file mode 100644 index 0000000000..7dfcb9e03c --- /dev/null +++ b/p2p/enode/urlv4_test.go @@ -0,0 +1,248 @@ +// 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 . + +package enode + +import ( + "bytes" + "crypto/ecdsa" + "math/big" + "net" + "os" + "reflect" + "strings" + "testing" + "testing/quick" + + "github.com/AlayaNetwork/Alaya-Go/log" +) + +var parseNodeTests = []struct { + rawurl string + wantError string + wantResult *Node +}{ + { + rawurl: "http://foobar", + wantError: `invalid URL scheme, want "enode"`, + }, + { + rawurl: "enode://01010101@123.124.125.126:3", + wantError: `invalid node ID (wrong length, want 128 hex chars)`, + }, + // Complete nodes with IP address. + { + rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3", + wantError: `invalid IP address`, + }, + { + rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo", + wantError: `invalid port`, + }, + { + rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo", + wantError: `invalid discport in query`, + }, + { + rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150", + wantResult: NewV4( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + net.IP{0x7f, 0x0, 0x0, 0x1}, + 52150, + 52150, + ), + }, + { + rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150", + wantResult: NewV4( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + net.ParseIP("::"), + 52150, + 52150, + ), + }, + { + rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150", + wantResult: NewV4( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), + 52150, + 52150, + ), + }, + { + rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334", + wantResult: NewV4( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + net.IP{0x7f, 0x0, 0x0, 0x1}, + 52150, + 22334, + ), + }, + // Incomplete nodes with no address. + { + rawurl: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", + wantResult: NewV4( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + nil, 0, 0, + ), + }, + { + rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", + wantResult: NewV4( + hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + nil, 0, 0, + ), + }, + // Invalid URLs + { + rawurl: "01010101", + wantError: `invalid node ID (wrong length, want 128 hex chars)`, + }, + { + rawurl: "enode://01010101", + wantError: `invalid node ID (wrong length, want 128 hex chars)`, + }, + { + // This test checks that errors from url.Parse are handled. + rawurl: "://foo", + wantError: `parse ://foo: missing protocol scheme`, + }, +} + +func hexPubkey(h string) *ecdsa.PublicKey { + k, err := parsePubkey(h) + if err != nil { + panic(err) + } + return k +} + +func TestParseNode(t *testing.T) { + log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(6), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))) + + for _, test := range parseNodeTests { + n, err := ParseV4(test.rawurl) + if test.wantError != "" { + if err == nil { + t.Errorf("test %q:\n got nil error, expected %#q", test.rawurl, test.wantError) + continue + } else if err.Error() != test.wantError { + t.Errorf("test %q:\n got error %#q, expected %#q", test.rawurl, err.Error(), test.wantError) + continue + } + } else { + if err != nil { + t.Errorf("test %q:\n unexpected error: %v", test.rawurl, err) + continue + } + if !reflect.DeepEqual(n, test.wantResult) { + t.Errorf("test %q:\n result mismatch:\ngot: %#v\nwant: %#v", test.rawurl, n, test.wantResult) + } + } + } +} + +func TestNodeString(t *testing.T) { + for i, test := range parseNodeTests { + if test.wantError == "" && strings.HasPrefix(test.rawurl, "enode://") { + str := test.wantResult.String() + if str != test.rawurl { + t.Errorf("test %d: Node.String() mismatch:\ngot: %s\nwant: %s", i, str, test.rawurl) + } + } + } +} + +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} + id1 := HexID("0x00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") + id2 := HexID("00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") + + if id1 != ref { + t.Errorf("wrong id1\ngot %v\nwant %v", id1[:], ref[:]) + } + if id2 != ref { + t.Errorf("wrong id2\ngot %v\nwant %v", id2[:], ref[:]) + } +} + +func TestID_textEncoding(t *testing.T) { + ref := ID{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, + 0x31, 0x32, + } + hex := "0102030405060708091011121314151617181920212223242526272829303132" + + text, err := ref.MarshalText() + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(text, []byte(hex)) { + t.Fatalf("text encoding did not match\nexpected: %s\ngot: %s", hex, text) + } + + id := new(ID) + if err := id.UnmarshalText(text); err != nil { + t.Fatal(err) + } + if *id != ref { + t.Fatalf("text decoding did not match\nexpected: %s\ngot: %s", ref, id) + } +} + +func TestNodeID_distcmp(t *testing.T) { + distcmpBig := func(target, a, b ID) int { + tbig := new(big.Int).SetBytes(target[:]) + abig := new(big.Int).SetBytes(a[:]) + bbig := new(big.Int).SetBytes(b[:]) + return new(big.Int).Xor(tbig, abig).Cmp(new(big.Int).Xor(tbig, bbig)) + } + if err := quick.CheckEqual(DistCmp, distcmpBig, nil); err != nil { + t.Error(err) + } +} + +// The random tests is likely to miss the case where a and b are equal, +// this test checks it explicitly. +func TestNodeID_distcmpEqual(t *testing.T) { + base := ID{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} + if DistCmp(base, x, x) != 0 { + t.Errorf("DistCmp(base, x, x) != 0") + } +} + +func TestNodeID_logdist(t *testing.T) { + logdistBig := func(a, b ID) int { + abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:]) + return new(big.Int).Xor(abig, bbig).BitLen() + } + if err := quick.CheckEqual(LogDist, logdistBig, nil); err != nil { + t.Error(err) + } +} + +// The random tests is likely to miss the case where a and b are equal, +// this test checks it explicitly. +func TestNodeID_logdistEqual(t *testing.T) { + x := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} + if LogDist(x, x) != 0 { + t.Errorf("LogDist(x, x) != 0") + } +} diff --git a/p2p/enr/enr.go b/p2p/enr/enr.go index daea2b6318..2c24f45c8c 100644 --- a/p2p/enr/enr.go +++ b/p2p/enr/enr.go @@ -40,8 +40,7 @@ import ( const SizeLimit = 300 // maximum encoded size of a node record in bytes var ( - errNoID = errors.New("unknown or unspecified identity scheme") - 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") errDuplicateKey = errors.New("record contains duplicate key") errIncompletePair = errors.New("record contains incomplete k/v pair") @@ -50,6 +49,32 @@ var ( 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. type Record struct { seq uint64 // sequence number @@ -64,11 +89,6 @@ type pair struct { 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. func (r *Record) Seq() uint64 { return r.seq @@ -140,7 +160,7 @@ func (r *Record) invalidate() { // EncodeRLP implements rlp.Encoder. Encoding fails if // the record is unsigned. func (r Record) EncodeRLP(w io.Writer) error { - if !r.Signed() { + if r.signature == nil { return errEncodeUnsigned } _, err := w.Write(r.raw) @@ -149,25 +169,34 @@ func (r Record) EncodeRLP(w io.Writer) error { // DecodeRLP implements rlp.Decoder. Decoding verifies the signature. func (r *Record) DecodeRLP(s *rlp.Stream) error { - raw, err := s.Raw() + dec, raw, err := decodeRecord(s) if err != nil { 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 { - return errTooBig + return dec, raw, errTooBig } // Decode the RLP container. - dec := Record{raw: raw} s = rlp.NewStream(bytes.NewReader(raw), 0) if _, err := s.List(); err != nil { - return err + return dec, raw, err } if err = s.Decode(&dec.signature); err != nil { - return err + return dec, raw, err } if err = s.Decode(&dec.seq); err != nil { - return err + return dec, raw, err } // The rest of the record contains sorted k/v pairs. var prevkey string @@ -177,73 +206,68 @@ func (r *Record) DecodeRLP(s *rlp.Stream) error { if err == rlp.EOL { break } - return err + return dec, raw, err } if err := s.Decode(&kv.v); err != nil { if err == rlp.EOL { - return errIncompletePair + return dec, raw, errIncompletePair } - return err + return dec, raw, err } if i > 0 { if kv.k == prevkey { - return errDuplicateKey + return dec, raw, errDuplicateKey } if kv.k < prevkey { - return errNotSorted + return dec, raw, errNotSorted } } dec.pairs = append(dec.pairs, kv) prevkey = kv.k } - if err := s.ListEnd(); err != nil { - return err - } + return dec, raw, s.ListEnd() +} - _, scheme := dec.idScheme() - if scheme == nil { - return errNoID - } - if err := scheme.Verify(&dec, dec.signature); err != nil { - return err - } - *r = dec - return nil +// 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) } -// NodeAddr returns the node address. The return value will be nil if the record is -// unsigned or uses an unknown identity scheme. -func (r *Record) NodeAddr() []byte { - _, scheme := r.idScheme() - if scheme == nil { - return nil - } - return scheme.NodeAddr(r) +// 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. -func (r *Record) SetSig(idscheme string, sig []byte) error { - // Check that "id" is set and matches the given scheme. This panics because - // inconsitencies here are always implementation bugs in the signing function calling - // this method. - id, s := r.idScheme() - if s == nil { - panic(errNoID) - } - if id != idscheme { - panic(fmt.Errorf("identity scheme mismatch in Sign: record has %s, want %s", id, idscheme)) - } - - // Verify against the scheme. - if err := s.Verify(r, sig); err != nil { - return err - } - raw, err := r.encode(sig) - if err != nil { - return err +// +// 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 } - r.signature, r.raw = sig, raw return nil } @@ -268,11 +292,3 @@ func (r *Record) encode(sig []byte) (raw []byte, err error) { } return raw, nil } - -func (r *Record) idScheme() (string, IdentityScheme) { - var id ID - if err := r.Load(&id); err != nil { - return "", nil - } - return string(id), FindIdentityScheme(string(id)) -} diff --git a/p2p/enr/enr_test.go b/p2p/enr/enr_test.go index 68d5286309..7da9375693 100644 --- a/p2p/enr/enr_test.go +++ b/p2p/enr/enr_test.go @@ -17,21 +17,17 @@ package enr import ( - "encoding/hex" + "bytes" + "encoding/binary" "fmt" "math/rand" "testing" "time" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" -) -var ( - privkey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - pubkey = &privkey.PublicKey + "github.com/AlayaNetwork/Alaya-Go/rlp" ) var rnd = rand.New(rand.NewSource(time.Now().UnixNano())) @@ -86,18 +82,6 @@ func TestGetSetUDP(t *testing.T) { 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 := SignV4(&r, 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) { var r Record ip4 := IP{127, 0, 0, 1} @@ -166,23 +150,20 @@ func TestSortedGetAndSet(t *testing.T) { func TestDirty(t *testing.T) { var r Record - if r.Signed() { - t.Error("Signed returned true for zero record") - } if _, err := rlp.EncodeToBytes(r); err != errEncodeUnsigned { t.Errorf("expected errEncodeUnsigned, got %#v", err) } - require.NoError(t, SignV4(&r, privkey)) - if !r.Signed() { - t.Error("Signed return false for signed record") + require.NoError(t, signTest([]byte{5}, &r)) + if len(r.signature) == 0 { + t.Error("record is not signed") } _, err := rlp.EncodeToBytes(r) assert.NoError(t, err) r.SetSeq(3) - if r.Signed() { - t.Error("Signed returned true for modified record") + if len(r.signature) != 0 { + t.Error("signature still set after modification") } if _, err := rlp.EncodeToBytes(r); err != errEncodeUnsigned { t.Errorf("expected errEncodeUnsigned, got %#v", err) @@ -209,7 +190,7 @@ func TestSignEncodeAndDecode(t *testing.T) { var r Record r.Set(UDP(16789)) r.Set(IP{127, 0, 0, 1}) - require.NoError(t, SignV4(&r, privkey)) + require.NoError(t, signTest([]byte{5}, &r)) blob, err := rlp.EncodeToBytes(r) require.NoError(t, err) @@ -223,48 +204,6 @@ func TestSignEncodeAndDecode(t *testing.T) { 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, SignV4(&r, privkey)) - expected := "a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7" - assert.Equal(t, expected, hex.EncodeToString(r.NodeAddr())) -} - -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 Record - if err := rlp.DecodeBytes(pyRecord, &r); err != nil { - t.Fatalf("can't decode: %v", err) - } - - var ( - wantAddr, _ = hex.DecodeString("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7") - wantSeq = uint64(1) - wantIP = IP{127, 0, 0, 1} - wantUDP = UDP(16789) - ) - 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(IP): &wantIP, new(UDP): &wantUDP} - 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. func TestRecordTooBig(t *testing.T) { var r Record @@ -272,13 +211,13 @@ func TestRecordTooBig(t *testing.T) { // set a big value for random key, expect error r.Set(WithEntry(key, randomString(SizeLimit))) - if err := SignV4(&r, privkey); err != errTooBig { + if err := signTest([]byte{5}, &r); err != errTooBig { t.Fatalf("expected to get errTooBig, got %#v", err) } // set an acceptable value for random key, expect no error r.Set(WithEntry(key, randomString(100))) - require.NoError(t, SignV4(&r, privkey)) + require.NoError(t, signTest([]byte{5}, &r)) } // TestSignEncodeAndDecodeRandom tests encoding/decoding of records containing random key/value pairs. @@ -294,7 +233,7 @@ func TestSignEncodeAndDecodeRandom(t *testing.T) { r.Set(WithEntry(key, &value)) } - require.NoError(t, SignV4(&r, privkey)) + require.NoError(t, signTest([]byte{5}, &r)) _, err := rlp.EncodeToBytes(r) require.NoError(t, err) @@ -307,11 +246,40 @@ func TestSignEncodeAndDecodeRandom(t *testing.T) { } } -func BenchmarkDecode(b *testing.B) { - var r Record - for i := 0; i < b.N; i++ { - rlp.DecodeBytes(pyRecord, &r) +type testSig struct{} + +type testID []byte + +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())) { + return ErrInvalidSig + } + return nil +} + +func (testSig) NodeAddr(r *Record) []byte { + var id []byte + if err := r.Load((*testID)(&id)); err != nil { + return nil } - b.StopTimer() - r.NodeAddr() + return id } diff --git a/p2p/enr/entries.go b/p2p/enr/entries.go index 9035e91882..fc7c36cefd 100644 --- a/p2p/enr/entries.go +++ b/p2p/enr/entries.go @@ -17,12 +17,10 @@ package enr import ( - "crypto/ecdsa" "fmt" "io" "net" - "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -98,30 +96,6 @@ func (v *IP) DecodeRLP(s *rlp.Stream) error { 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. type KeyError struct { Key string diff --git a/p2p/enr/idscheme_test.go b/p2p/enr/idscheme_test.go deleted file mode 100644 index d790e12f14..0000000000 --- a/p2p/enr/idscheme_test.go +++ /dev/null @@ -1,36 +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 . - -package enr - -import ( - "crypto/ecdsa" - "math/big" - "testing" -) - -// 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 Record - if err := SignV4(&r, invalidKey); err == nil { - t.Fatal("expected error from SignV4") - } - if len(r.pairs) > 0 { - t.Fatal("expected empty record, have", r.pairs) - } -} diff --git a/p2p/message.go b/p2p/message.go index e500fd3fa0..fad45b354d 100644 --- a/p2p/message.go +++ b/p2p/message.go @@ -25,8 +25,9 @@ import ( "sync/atomic" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/event" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -253,13 +254,13 @@ type msgEventer struct { MsgReadWriter feed *event.Feed - peerID discover.NodeID + peerID enode.ID Protocol string } // newMsgEventer returns a msgEventer which sends message events to the given // feed -func newMsgEventer(rw MsgReadWriter, feed *event.Feed, peerID discover.NodeID, proto string) *msgEventer { +func newMsgEventer(rw MsgReadWriter, feed *event.Feed, peerID enode.ID, proto string) *msgEventer { return &msgEventer{ MsgReadWriter: rw, feed: feed, diff --git a/p2p/peer.go b/p2p/peer.go index 910dd165b7..ae74488a3b 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -25,10 +25,12 @@ import ( "sync" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/common/mclock" "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -60,7 +62,7 @@ type protoHandshake struct { Name string Caps []Cap ListenPort uint64 - ID discover.NodeID + ID []byte // secp256k1 public key // Ignore additional fields (for forward compatibility). Rest []rlp.RawValue `rlp:"tail"` @@ -90,12 +92,12 @@ const ( // 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 type PeerEvent struct { - Type PeerEventType `json:"type"` - Peer discover.NodeID `json:"peer"` - Error string `json:"error,omitempty"` - Protocol string `json:"protocol,omitempty"` - MsgCode *uint64 `json:"msg_code,omitempty"` - MsgSize *uint32 `json:"msg_size,omitempty"` + Type PeerEventType `json:"type"` + Peer enode.ID `json:"peer"` + Error string `json:"error,omitempty"` + Protocol string `json:"protocol,omitempty"` + MsgCode *uint64 `json:"msg_code,omitempty"` + MsgSize *uint32 `json:"msg_size,omitempty"` } // Peer represents a connected remote node. @@ -114,72 +116,29 @@ type Peer struct { events *event.Feed } -func NewPeerByNodeID(nodeId1 discover.NodeID, nodeId2 discover.NodeID, protos []Protocol) (*Peer, MsgReadWriter, *Peer, MsgReadWriter) { - fd1, fd2 := net.Pipe() - c1 := &conn{fd: fd1, transport: newMockTransport(nodeId1, fd1), id: nodeId1} - c2 := &conn{fd: fd2, transport: newMockTransport(nodeId2, fd2), id: nodeId2} - for _, p := range protos { - c1.caps = append(c1.caps, p.cap()) - c2.caps = append(c2.caps, p.cap()) - } - - peer1 := newPeer(c1, protos) - peer2 := newPeer(c1, protos) - return peer1, c1, peer2, c2 -} - -func NewMockPeerNodeID(nodeId discover.NodeID, protos []Protocol) (func(), MsgReadWriter, *Peer, <-chan error) { - fd1, fd2 := net.Pipe() - c1 := &conn{fd: fd1, transport: newMockTransport(nodeId, fd1), id: nodeId} - c2 := &conn{fd: fd2, transport: newMockTransport(nodeId, fd2)} - for _, p := range protos { - c1.caps = append(c1.caps, p.cap()) - c2.caps = append(c2.caps, p.cap()) - } - - peer := newPeer(c1, protos) - errc := make(chan error, 1) - go func() { - _, err := peer.run() - errc <- err - }() - - closer := func() { c2.close(errors.New("close func called")) } - return closer, c2, peer, errc -} - -func NewMockPeer(protos []Protocol) (func(), MsgWriter, *Peer, <-chan error) { - fd1, fd2 := net.Pipe() - c1 := &conn{fd: fd1, transport: newMockTransport(randomID(), fd1)} - c2 := &conn{fd: fd2, transport: newMockTransport(randomID(), fd2)} - for _, p := range protos { - c1.caps = append(c1.caps, p.cap()) - c2.caps = append(c2.caps, p.cap()) - } - - peer := newPeer(c1, protos) - errc := make(chan error, 1) - go func() { - _, err := peer.run() - errc <- err - }() - - closer := func() { c2.close(errors.New("close func called")) } - return closer, c2, peer, errc -} - // NewPeer returns a peer for testing purposes. -func NewPeer(id discover.NodeID, name string, caps []Cap) *Peer { +func NewPeer(id enode.ID, name string, caps []Cap) *Peer { pipe, _ := net.Pipe() - conn := &conn{fd: pipe, transport: nil, id: id, caps: caps, name: name} + node := enode.SignNull(new(enr.Record), id) + conn := &conn{fd: pipe, transport: nil, node: node, caps: caps, name: name} peer := newPeer(conn, nil) close(peer.closed) // ensures Disconnect doesn't block return peer } // ID returns the node's public key. -func (p *Peer) ID() discover.NodeID { - return p.rw.id +func (p *Peer) ID() enode.ID { + return p.rw.node.ID() +} + +// IDV0 returns the node's public key. +func (p *Peer) IDV0() enode.IDv0 { + return p.rw.node.IDv0() +} + +// 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. @@ -214,7 +173,8 @@ func (p *Peer) Disconnect(reason DiscReason) { // String implements fmt.Stringer. func (p *Peer) String() string { - return fmt.Sprintf("Peer %x %v", p.rw.id[:8], p.RemoteAddr()) + id := p.ID() + return fmt.Sprintf("Peer %x %v", id[:8], p.RemoteAddr()) } // Inbound returns true if the peer is an inbound connection @@ -231,7 +191,7 @@ func newPeer(conn *conn, protocols []Protocol) *Peer { disc: make(chan DiscReason), protoErr: make(chan error, len(protomap)+1), // protocols + pingLoop closed: make(chan struct{}), - log: log.New("id", conn.id, "conn", conn.flags), + log: log.New("id", conn.node.ID(), "conn", conn.flags), } return p } diff --git a/p2p/peer_test.go b/p2p/peer_test.go index a3e1c74fd8..5aa64a32e1 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -45,8 +45,8 @@ var discard = Protocol{ func testPeer(protos []Protocol) (func(), *conn, *Peer, <-chan error) { fd1, fd2 := net.Pipe() - c1 := &conn{fd: fd1, transport: newTestTransport(randomID(), fd1)} - c2 := &conn{fd: fd2, transport: newTestTransport(randomID(), fd2)} + c1 := &conn{fd: fd1, node: newNode(randomID(), nil), transport: newTestTransport(&newkey().PublicKey, fd1)} + c2 := &conn{fd: fd2, node: newNode(randomID(), nil), transport: newTestTransport(&newkey().PublicKey, fd2)} for _, p := range protos { c1.caps = append(c1.caps, p.cap()) c2.caps = append(c2.caps, p.cap()) diff --git a/p2p/protocol.go b/p2p/protocol.go index c2f8977584..fb9d5b94c0 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -19,7 +19,7 @@ package p2p import ( "fmt" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" ) // Protocol represents a P2P subprotocol implementation. @@ -51,7 +51,7 @@ type Protocol struct { // 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, // but returns nil, it is assumed that the protocol handshake is still running. - PeerInfo func(id discover.NodeID) interface{} + PeerInfo func(id enode.ID) interface{} } func (p Protocol) cap() Cap { diff --git a/p2p/protocols/protocol_test.go b/p2p/protocols/protocol_test.go index bbe53981af..91779e5c0d 100644 --- a/p2p/protocols/protocol_test.go +++ b/p2p/protocols/protocol_test.go @@ -23,8 +23,9 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" p2ptest "github.com/AlayaNetwork/Alaya-Go/p2p/testing" ) @@ -36,7 +37,7 @@ type hs0 struct { // message to kill/drop the peer with nodeID type kill struct { - C discover.NodeID + C enode.ID } // message to drop connection @@ -144,7 +145,7 @@ func protocolTester(t *testing.T, pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTes return p2ptest.NewProtocolTester(t, conf.ID, 2, newProtocol(pp)) } -func protoHandshakeExchange(id discover.NodeID, proto *protoHandshake) []p2ptest.Exchange { +func protoHandshakeExchange(id enode.ID, proto *protoHandshake) []p2ptest.Exchange { return []p2ptest.Exchange{ { @@ -172,13 +173,13 @@ func runProtoHandshake(t *testing.T, proto *protoHandshake, errs ...error) { pp := p2ptest.NewTestPeerPool() s := protocolTester(t, pp) // TODO: make this more than one handshake - id := s.IDs[0] - if err := s.TestExchanges(protoHandshakeExchange(id, proto)...); err != nil { + node := s.Nodes[0] + if err := s.TestExchanges(protoHandshakeExchange(node.ID(), proto)...); err != nil { t.Fatal(err) } var disconnects []*p2ptest.Disconnect for i, err := range errs { - disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err}) + disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err}) } if err := s.TestDisconnected(disconnects...); err != nil { t.Fatal(err) @@ -197,7 +198,7 @@ func TestProtoHandshakeSuccess(t *testing.T) { runProtoHandshake(t, &protoHandshake{42, "420"}) } -func moduleHandshakeExchange(id discover.NodeID, resp uint) []p2ptest.Exchange { +func moduleHandshakeExchange(id enode.ID, resp uint) []p2ptest.Exchange { return []p2ptest.Exchange{ { @@ -224,16 +225,16 @@ func moduleHandshakeExchange(id discover.NodeID, resp uint) []p2ptest.Exchange { func runModuleHandshake(t *testing.T, resp uint, errs ...error) { pp := p2ptest.NewTestPeerPool() s := protocolTester(t, pp) - id := s.IDs[0] - if err := s.TestExchanges(protoHandshakeExchange(id, &protoHandshake{42, "420"})...); err != nil { + node := s.Nodes[0] + if err := s.TestExchanges(protoHandshakeExchange(node.ID(), &protoHandshake{42, "420"})...); err != nil { t.Fatal(err) } - if err := s.TestExchanges(moduleHandshakeExchange(id, resp)...); err != nil { + if err := s.TestExchanges(moduleHandshakeExchange(node.ID(), resp)...); err != nil { t.Fatal(err) } var disconnects []*p2ptest.Disconnect for i, err := range errs { - disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err}) + disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err}) } if err := s.TestDisconnected(disconnects...); err != nil { t.Fatal(err) @@ -249,7 +250,7 @@ func TestModuleHandshakeSuccess(t *testing.T) { } // testing complex interactions over multiple peers, relaying, dropping -func testMultiPeerSetup(a, b discover.NodeID) []p2ptest.Exchange { +func testMultiPeerSetup(a, b enode.ID) []p2ptest.Exchange { return []p2ptest.Exchange{ { @@ -305,7 +306,7 @@ func runMultiplePeers(t *testing.T, peer int, errs ...error) { pp := p2ptest.NewTestPeerPool() s := protocolTester(t, pp) - if err := s.TestExchanges(testMultiPeerSetup(s.IDs[0], s.IDs[1])...); err != nil { + if err := s.TestExchanges(testMultiPeerSetup(s.Nodes[0].ID(), s.Nodes[1].ID())...); err != nil { t.Fatal(err) } // after some exchanges of messages, we can test state changes @@ -318,15 +319,15 @@ WAIT: for { select { case <-tick.C: - if pp.Has(s.IDs[0]) { + if pp.Has(s.Nodes[0].ID()) { break WAIT } case <-timeout.C: t.Fatal("timeout") } } - if !pp.Has(s.IDs[1]) { - t.Fatalf("missing peer test-1: %v (%v)", pp, s.IDs) + if !pp.Has(s.Nodes[1].ID()) { + t.Fatalf("missing peer test-1: %v (%v)", pp, s.Nodes) } // peer 0 sends kill request for peer with index @@ -334,8 +335,8 @@ WAIT: Triggers: []p2ptest.Trigger{ { Code: 2, - Msg: &kill{s.IDs[peer]}, - Peer: s.IDs[0], + Msg: &kill{s.Nodes[peer].ID()}, + Peer: s.Nodes[0].ID(), }, }, }) @@ -350,7 +351,7 @@ WAIT: { Code: 3, Msg: &drop{}, - Peer: s.IDs[(peer+1)%2], + Peer: s.Nodes[(peer+1)%2].ID(), }, }, }) @@ -362,14 +363,14 @@ WAIT: // check the actual discconnect errors on the individual peers var disconnects []*p2ptest.Disconnect for i, err := range errs { - disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err}) + disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err}) } if err := s.TestDisconnected(disconnects...); err != nil { t.Fatal(err) } // test if disconnected peers have been removed from peerPool - if pp.Has(s.IDs[peer]) { - t.Fatalf("peer test-%v not dropped: %v (%v)", peer, pp, s.IDs) + if pp.Has(s.Nodes[peer].ID()) { + t.Fatalf("peer test-%v not dropped: %v (%v)", peer, pp, s.Nodes) } } diff --git a/p2p/rlpx.go b/p2p/rlpx.go index 693c26dd91..6788e940cd 100644 --- a/p2p/rlpx.go +++ b/p2p/rlpx.go @@ -35,6 +35,8 @@ import ( "sync" "time" + "github.com/AlayaNetwork/Alaya-Go/common/bitutil" + "github.com/AlayaNetwork/Alaya-Go/log" "github.com/golang/snappy" @@ -42,7 +44,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/crypto/ecies" "github.com/AlayaNetwork/Alaya-Go/crypto/sha3" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -167,7 +168,7 @@ func readProtocolHandshake(rw MsgReader, our *protoHandshake) (*protoHandshake, if err := msg.Decode(&hs); err != nil { return nil, err } - if (hs.ID == discover.NodeID{}) { + if len(hs.ID) != 64 || !bitutil.TestBytes(hs.ID) { return nil, DiscInvalidIdentity } return &hs, nil @@ -177,7 +178,7 @@ func readProtocolHandshake(rw MsgReader, our *protoHandshake) (*protoHandshake, // messages. the protocol handshake is the first authenticated message // and also verifies whether the encryption handshake 'worked' and the // remote side actually provided the right public key. -func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *discover.Node) (discover.NodeID, error) { +func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *ecdsa.PublicKey) (*ecdsa.PublicKey, error) { var ( sec secrets err error @@ -185,23 +186,21 @@ func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *discover.Node) (disco if dial == nil { sec, err = receiverEncHandshake(t.fd, prv) } else { - sec, err = initiatorEncHandshake(t.fd, prv, dial.ID) + sec, err = initiatorEncHandshake(t.fd, prv, dial) } if err != nil { - return discover.NodeID{}, err + return nil, err } t.wmu.Lock() t.rw = newRLPXFrameRW(t.fd, sec) t.wmu.Unlock() - return sec.RemoteID, nil + return sec.Remote.ExportECDSA(), nil } // encHandshake contains the state of the encryption handshake. type encHandshake struct { - initiator bool - remoteID discover.NodeID - - remotePub *ecies.PublicKey // remote-pubk + initiator bool + remote *ecies.PublicKey // remote-pubk initNonce, respNonce []byte // nonce randomPrivKey *ecies.PrivateKey // ecdhe-random remoteRandomPub *ecies.PublicKey // ecdhe-random-pubk @@ -210,7 +209,7 @@ type encHandshake struct { // secrets represents the connection secrets // which are negotiated during the encryption handshake. type secrets struct { - RemoteID discover.NodeID + Remote *ecies.PublicKey AES, MAC []byte EgressMAC, IngressMAC hash.Hash Token []byte @@ -251,9 +250,9 @@ func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) { sharedSecret := crypto.Keccak256(ecdheSecret, crypto.Keccak256(h.respNonce, h.initNonce)) aesSecret := crypto.Keccak256(ecdheSecret, sharedSecret) s := secrets{ - RemoteID: h.remoteID, - AES: aesSecret, - MAC: crypto.Keccak256(ecdheSecret, aesSecret), + Remote: h.remote, + AES: aesSecret, + MAC: crypto.Keccak256(ecdheSecret, aesSecret), } // setup sha3 instances for the MACs @@ -275,15 +274,15 @@ func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) { // staticSharedSecret returns the static shared secret, the result // of key agreement between the local and remote static node key. func (h *encHandshake) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error) { - return ecies.ImportECDSA(prv).GenerateShared(h.remotePub, sskLen, sskLen) + return ecies.ImportECDSA(prv).GenerateShared(h.remote, sskLen, sskLen) } // initiatorEncHandshake negotiates a session token on conn. // it should be called on the dialing side of the connection. // // prv is the local client's private key. -func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remoteID discover.NodeID) (s secrets, err error) { - h := &encHandshake{initiator: true, remoteID: remoteID} +func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s secrets, err error) { + h := &encHandshake{initiator: true, remote: ecies.ImportECDSAPublic(remote)} authMsg, err := h.makeAuthMsg(prv) if err != nil { return s, err @@ -309,14 +308,10 @@ func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remoteID d // makeAuthMsg creates the initiator handshake message. func (h *encHandshake) makeAuthMsg(prv *ecdsa.PrivateKey) (*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. h.initNonce = make([]byte, shaLen) - if _, err := rand.Read(h.initNonce); err != nil { + _, err := rand.Read(h.initNonce) + if err != nil { return nil, err } // Generate random keypair to for ECDH. @@ -386,13 +381,12 @@ func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s secrets, func (h *encHandshake) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error { // Import the remote identity. - h.initNonce = msg.Nonce[:] - h.remoteID = msg.InitiatorPubkey - rpub, err := h.remoteID.Pubkey() + rpub, err := importPublicKey(msg.InitiatorPubkey[:]) if err != nil { - return fmt.Errorf("bad remoteID: %#v", err) + return err } - h.remotePub = ecies.ImportECDSAPublic(rpub) + h.initNonce = msg.Nonce[:] + h.remote = rpub // Generate random keypair for ECDH. // If a private key is already set, use it instead of generating one (for testing). @@ -438,7 +432,7 @@ func (msg *authMsgV4) sealPlain(h *encHandshake) ([]byte, error) { n += copy(buf[n:], msg.InitiatorPubkey[:]) n += copy(buf[n:], msg.Nonce[:]) buf[n] = 0 // token-flag - return ecies.Encrypt(rand.Reader, h.remotePub, buf, nil, nil) + return ecies.Encrypt(rand.Reader, h.remote, buf, nil, nil) } func (msg *authMsgV4) decodePlain(input []byte) { @@ -454,7 +448,7 @@ func (msg *authRespV4) sealPlain(hs *encHandshake) ([]byte, error) { buf := make([]byte, authRespLen) n := copy(buf, msg.RandomPubkey[:]) copy(buf[n:], msg.Nonce[:]) - return ecies.Encrypt(rand.Reader, hs.remotePub, buf, nil, nil) + return ecies.Encrypt(rand.Reader, hs.remote, buf, nil, nil) } func (msg *authRespV4) decodePlain(input []byte) { @@ -477,7 +471,7 @@ func sealEIP8(msg interface{}, h *encHandshake) ([]byte, error) { prefix := make([]byte, 2) binary.BigEndian.PutUint16(prefix, uint16(buf.Len()+eciesOverhead)) - enc, err := ecies.Encrypt(rand.Reader, h.remotePub, buf.Bytes(), nil, prefix) + enc, err := ecies.Encrypt(rand.Reader, h.remote, buf.Bytes(), nil, prefix) return append(prefix, enc...), err } diff --git a/p2p/rlpx_test.go b/p2p/rlpx_test.go index 1b0ed69935..d4641feca3 100644 --- a/p2p/rlpx_test.go +++ b/p2p/rlpx_test.go @@ -18,6 +18,7 @@ package p2p import ( "bytes" + "crypto/ecdsa" "crypto/rand" "errors" "fmt" @@ -31,10 +32,10 @@ import ( "time" "github.com/davecgh/go-spew/spew" + "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/crypto/ecies" "github.com/AlayaNetwork/Alaya-Go/crypto/sha3" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/pipes" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -80,9 +81,9 @@ func TestEncHandshake(t *testing.T) { func testEncHandshake(token []byte) error { type result struct { - side string - id discover.NodeID - err error + side string + pubkey *ecdsa.PublicKey + err error } var ( prv0, _ = crypto.GenerateKey() @@ -97,14 +98,12 @@ func testEncHandshake(token []byte) error { defer func() { output <- r }() defer fd0.Close() - dest := &discover.Node{ID: discover.PubkeyID(&prv1.PublicKey)} - r.id, r.err = c0.doEncHandshake(prv0, dest) + r.pubkey, r.err = c0.doEncHandshake(prv0, &prv1.PublicKey) if r.err != nil { return } - id1 := discover.PubkeyID(&prv1.PublicKey) - if r.id != id1 { - r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.id, id1) + if !reflect.DeepEqual(r.pubkey, &prv1.PublicKey) { + r.err = fmt.Errorf("remote pubkey mismatch: got %v, want: %v", r.pubkey, &prv1.PublicKey) } }() go func() { @@ -112,13 +111,12 @@ func testEncHandshake(token []byte) error { defer func() { output <- r }() defer fd1.Close() - r.id, r.err = c1.doEncHandshake(prv1, nil) + r.pubkey, r.err = c1.doEncHandshake(prv1, nil) if r.err != nil { return } - id0 := discover.PubkeyID(&prv0.PublicKey) - if r.id != id0 { - r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.id, id0) + if !reflect.DeepEqual(r.pubkey, &prv0.PublicKey) { + r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.pubkey, &prv0.PublicKey) } }() @@ -150,12 +148,12 @@ func testEncHandshake(token []byte) error { func TestProtocolHandshake(t *testing.T) { var ( prv0, _ = crypto.GenerateKey() - node0 = &discover.Node{ID: discover.PubkeyID(&prv0.PublicKey), IP: net.IP{1, 2, 3, 4}, TCP: 33} - hs0 = &protoHandshake{Version: 3, ID: node0.ID, Caps: []Cap{{"a", 0}, {"b", 2}}} + pub0 = crypto.FromECDSAPub(&prv0.PublicKey)[1:] + hs0 = &protoHandshake{Version: 3, ID: pub0, Caps: []Cap{{"a", 0}, {"b", 2}}} prv1, _ = crypto.GenerateKey() - node1 = &discover.Node{ID: discover.PubkeyID(&prv1.PublicKey), IP: net.IP{5, 6, 7, 8}, TCP: 44} - hs1 = &protoHandshake{Version: 3, ID: node1.ID, Caps: []Cap{{"c", 1}, {"d", 3}}} + pub1 = crypto.FromECDSAPub(&prv1.PublicKey)[1:] + hs1 = &protoHandshake{Version: 3, ID: pub1, Caps: []Cap{{"c", 1}, {"d", 3}}} wg sync.WaitGroup ) @@ -170,13 +168,13 @@ func TestProtocolHandshake(t *testing.T) { defer wg.Done() defer fd0.Close() rlpx := newRLPX(fd0) - remid, err := rlpx.doEncHandshake(prv0, node1) + rpubkey, err := rlpx.doEncHandshake(prv0, &prv1.PublicKey) if err != nil { t.Errorf("dial side enc handshake failed: %v", err) return } - if remid != node1.ID { - t.Errorf("dial side remote id mismatch: got %v, want %v", remid, node1.ID) + if !reflect.DeepEqual(rpubkey, &prv1.PublicKey) { + t.Errorf("dial side remote pubkey mismatch: got %v, want %v", rpubkey, &prv1.PublicKey) return } @@ -196,13 +194,13 @@ func TestProtocolHandshake(t *testing.T) { defer wg.Done() defer fd1.Close() rlpx := newRLPX(fd1) - remid, err := rlpx.doEncHandshake(prv1, nil) + rpubkey, err := rlpx.doEncHandshake(prv1, nil) if err != nil { t.Errorf("listen side enc handshake failed: %v", err) return } - if remid != node0.ID { - t.Errorf("listen side remote id mismatch: got %v, want %v", remid, node0.ID) + if !reflect.DeepEqual(rpubkey, &prv0.PublicKey) { + t.Errorf("listen side remote pubkey mismatch: got %v, want %v", rpubkey, &prv0.PublicKey) return } diff --git a/p2p/server.go b/p2p/server.go index 265633e6a6..a7c24c4dc9 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -18,22 +18,23 @@ package p2p import ( + "bytes" "crypto/ecdsa" "errors" + "fmt" "math/big" - "math/rand" "net" "sync" "sync/atomic" "time" - "github.com/AlayaNetwork/Alaya-Go/params" + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/common/mclock" "github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" - "github.com/AlayaNetwork/Alaya-Go/crypto/sha3" "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p/discover" @@ -60,166 +61,6 @@ const ( var errServerStopped = errors.New("server stopped") -var AllowNodesMap = map[discover.NodeID]struct{}{ - discover.MustHexID("f7a07f6ff8c282a4a1c1febaf68531bbb557fa262c641d022f249e926e222c9190602b502d2772fa4a2834ffb4bf6298f4c80467178e90728c5a852504f571ad"): struct{}{}, - discover.MustHexID("87aa41f5f3ae96260017f34d836708d5e92319b766b6d475744a01e939c8921715c4fecb7951e9bc9aae7c334ca51c77247aed32e643e49341037996a28dace7"): struct{}{}, - discover.MustHexID("8f1c8333053cad81c76baf4df547eab49d1d1e1f7602b470ed3cd36e14461042731b20904252bff629d1fe60b05c3223653410c72a30fe90ef66ed95e6f4289a"): struct{}{}, - discover.MustHexID("0c2bf9b0d73ebd5728e810deb55a6f656cc44b92a8399aa0898eaa26bad3232707441e381f7a83eb1c0b38bde813bf84e7311b8d9fe75f7c5eea6c219c3103cc"): struct{}{}, - discover.MustHexID("89ca7ccb7fab8e4c8b1b24c747670757b9ef1b3b7631f64e6ea6b469c5936c501fcdcfa7fef2a77521072162c1fc0f8a1663899d31ebb1bc7d00678634ef746c"): struct{}{}, - discover.MustHexID("fff1010bbf1762d13bf13828142c612a7d287f0f1367f8104a78f001145fd788fb44b87e9eac404bc2e880602450405850ff286658781dce130aee981394551d"): struct{}{}, - discover.MustHexID("32b7ca6dec2f4e96187d6dbbed02a224dc91b605302689ac921f069c9bd314d431ffaa4ecd220164513cba63361a78e672ba2c9557e44b76c7e6433ed5fbee94"): struct{}{}, - discover.MustHexID("0eb6b43a9945a062e67b45248084ec4b5da5f22d35a58991c8f508666253fbd1b679b633728f4c3384ee878ca5efca7623786fdf623b4e5288ace830dc237614"): struct{}{}, - discover.MustHexID("a3f400758b678e15d7db188f2448411b8b472cb94da7a712dd7842f03c879478e156fd7a549a782a12de5f5dd9dc979dfd2946f8396b9328dd4fbdead37e49ba"): struct{}{}, - discover.MustHexID("6f5584a27a272099c1c8dd948f24f09b660ca38918e76db0eb3fad3c177b97229775ef61d45341279317bce68cf08fce30473dcfd30a0348049b0289048e29a3"): struct{}{}, - discover.MustHexID("c45a914bba5ad5c735489a100954eb08c49c3f718879e483d0debbe3e83da409cb1f9100ac73a2951bdf38886db419d8648534698cad05ab99c27d6869f62582"): struct{}{}, - discover.MustHexID("680b23be9f9b1fa7684086ebd465bbd5503305738dae58146043632aa553c79c6a22330605a74f03e84379e9706d7644a8cbe5e638a70a58d20e7eb5042ec3ca"): struct{}{}, - discover.MustHexID("4be36c1bda3d630c2448b646f2cb96a44867f558c32b3445f474cc17cb26639f80bf3a5f8bb993261cf7862e3d43e6306eec2a5c8c712f4e1ead3bb126899abd"): struct{}{}, - discover.MustHexID("a138433f1bc37e0c7a2bfb82f9f91f4bfad697524616f0aa08a78fdf1ecad9315cc3f80e8736b9d888e5f605ab49e2404461ea69c34b06697f067b509914be46"): struct{}{}, - discover.MustHexID("00aa2e361168765c0df1ebe981455dd64f4c091adb01e162567101f8f1b5fa31ac960c198406a00fc558ebb8c14843ef72ed47d5931d0605a0e6caab41c8a86e"): struct{}{}, - discover.MustHexID("73686446756b83458b910015775081acaa83d6727f4dcf71b732a827ebd563bc4553e1a3171029899ca276f285d7b63b36609ab8aaa0f138eeee38724d89a15e"): struct{}{}, - discover.MustHexID("656e0ee96374d3f60d2e323a059fb8848490433b4d0cefd216ce47e2e575375bc561745c9f35ecdf080848f5f08c5a4bd7af839be594f4abce7666b7e2a2a2a6"): struct{}{}, - discover.MustHexID("cc77a9f28ae8a89acc48ec969c34fad9ed877d636eccf22725af7bd89b274f702d40073d2dda1b9fbfc572746979a7876c1dca93fc617918586a53747d211d43"): struct{}{}, - discover.MustHexID("3e05f80d931922509c7f65ab647b39efec56c95360fe293413368b8e5db7cfb385ab7f8a77acd6a8c39ae6a0d108e0723cc84c19c8797ecd0af1c5c6e94a541b"): struct{}{}, - discover.MustHexID("853667f088dbad823cfb9fc956ddf343cacf4cffc9d5de1e7227a2261bd2c5df356cb0a914f9404f21cc502d46a9d87bf90c5863286d07ebb0b06fd71f9c1192"): struct{}{}, - discover.MustHexID("b8d128a2b3a8eeb1c3cf4e471b34884c92f60f2d419149e8f93fc5fba18a36d6cae5cae0f07285630b5d43f5a0ba915e548dc4079dbd8a9d93a8f71ee7489804"): struct{}{}, - discover.MustHexID("c0dc97ee57ba202faf012ecb72bf30aebcd2cf7c161d7012017e0320e0db15925c107998bd833d61ec4c2689172d7e34a0371f4511773641e00814c2632b0e66"): struct{}{}, - discover.MustHexID("8f2dc504635ff5b394c3bf7debd885db3f4108772d8e51c26a3860a97d34295ec5c15cffe9fdccd88052408f3a84208718291b077e257898e603ec7b195d3684"): struct{}{}, - discover.MustHexID("1fd9fd7d9c31dad117384c7cc2f223a9e76f7aa81e30f38f030f24212be3fa20ca1f067d878a8ae97deb89b81efbd0542b3880cbd428b4ffae494fcd2c31834b"): struct{}{}, - discover.MustHexID("32b67023a9057c66ff9b9e0f9ca5a23a09e686a707c303695219ef0aa82ca2c7bd965da5cb1262232376584ed4402877fe786b58c3b1adc8fafbb3174b3f24f3"): struct{}{}, - discover.MustHexID("505ef930162c1b0736d7b7a44a52e0e179597bd3a43e13b057734abffb46978311ef10bf16d565aad1ed4f208493b030c173c7bcfbff989d7d4bf2c37925f621"): struct{}{}, - discover.MustHexID("73601a21e758e4052b43e68d9c09f2750dbc82b1736cce6a52258d42a18cde6c006d1eae39d44dda08987362d0ba8f8df43df05b3330281e866207117de25885"): struct{}{}, - discover.MustHexID("ade53a03334eda53f0bed8f3e1572a7d5faf59f40e464b683e9160946a738b0d72b333b93c07b4b75341de633784b4a2d00aacf78ce082949470046a4875fbee"): struct{}{}, - discover.MustHexID("32d622ecfc31deaae46f1249486f334a57476f9db66d930a4629440bdf0db3345d36c29cb8ce5ab58a410685b5b4e5f8648e35dc1a77c510326bc323ee473d78"): struct{}{}, - discover.MustHexID("f01b7b56c23a67376f0b1578feefed152952bbc3326fd2c412432a6f7e95ebe4d8adc231c8f3a4e3ab7d35c05278073766f89c5565a1f5267cd73a07c1d2fb04"): struct{}{}, - discover.MustHexID("370248c1d479da63476c52c742decc6b04b92908cfeccc53bf3a3367971f8de08a270a7e756f4714e99d0d10521426a4270335233ce5ebe8029828f35707c33a"): struct{}{}, - discover.MustHexID("8bc8734315acf2af4c92a458f077f1f8c96f0530fb43510c11361f1d6469631423206ef76cd879ade849ee15fbcaeb042e3721168614b4fad4eecd60a6aa3e94"): struct{}{}, - discover.MustHexID("e504fb1ed169c96d5abb63dced01109becb930349995d0ff0942b6a4b116bfeea6810c1e804ce42e240576036bd21cdca09fdcdd6cc90685badf802956a21931"): struct{}{}, - discover.MustHexID("239a70bf6909fe8f63bdb298799fe81d29c45d9a35952ae0cee48f77d79c678f9e9c4e2ba63c0691c0a5d5adcedc159b361bf0d20d6a3be534adf080e6d2205f"): struct{}{}, - discover.MustHexID("196e461fe3fea40d260bcef18eb0907fe61fb55322d3b446fe2dfa37127e1e6850435352c8a51635a755de9a25acb85a0efda56634e69be49a30d02908faecc7"): struct{}{}, - discover.MustHexID("88d14bc287a42fec8a490b90258ddfbc18e98a32f42f9dbe27a4369b8e64a41e22ca84aba764ef7d1326b5b9a8e7b54d6d10f23dc665ff7e7c40c04a5f0a3703"): struct{}{}, - discover.MustHexID("11daedba2520a87da234f48bdaa4373e536b85367b863c218e6638c558214708470260830c2f17feccf8997187c70c96469bcca9ea0f5b522aae6fadea8e9ddf"): struct{}{}, - discover.MustHexID("5c7c9dd985ddf54ddd865c4c26d0bfc291568f421100eb96fd6c69593bf226a4d18257f00234155d3e8a8b12a24d89525711f58e47e643dc1c198448dbce9dd1"): struct{}{}, - discover.MustHexID("3581081a17b98c9bb1e04fe0bb63d5bd0ce648aa2cf6214109923678b36783bd83be9c00f51fff1c08dd094196a600caff012ffb7bc6337f91cd903a3fd87283"): struct{}{}, - discover.MustHexID("fad2c7f917eb3057d85031eae8bbda52541b527dd1d24a25e7e9b40d7329570a85dc45ec61b189a9cc30047ae906a08dc375558828e1c76dc853ce99b42b91e4"): struct{}{}, - discover.MustHexID("4e44d7995f1b2b01bdbc65e5448f9fe3c7082f46f85fe980bccc3418836cec8ffab248dc0b54c0d8974e1327b8992643ef9db222ec9a44512a092d33b274bc1d"): struct{}{}, - discover.MustHexID("d03f770e38a5e6f6ef760be7bd79662c66a21bb3624525d12018daacadd3815c008b8ceaaf34d0646cbf8a537a00f57b217fca19f782898fd9cd36c70daefbff"): struct{}{}, - discover.MustHexID("14c2b76bd5945f8f77da071e0d46b253adf765551919ef3e7f80885a7a5506bb70e34926c546d27490b5a58cf01cc3f079296b0b97a01daa694dcb94c97548bc"): struct{}{}, - discover.MustHexID("19b8fb478a8502a25e461270122ece3135b15dc0de118264495bae30d39af81fd9134ed95364e6a39c3eebfba57fbffa7961a5158d3dac0f0da0313dac7af024"): struct{}{}, - discover.MustHexID("24b9584429dcb52ef6da575a0246d59158ff04f37118e9ce580f100e9da4a99064db252648f78497cf4b27f53eeaace7ca795ff75734e0e95386a5e3282f5fff"): struct{}{}, - discover.MustHexID("cd57e10afc9426739a5cbd299d1dd2e5da59bdef15e878b7125c23f9bd9d620aaec70e240e25bf1607e787636d58692576972e3d77a956401d22498856dcc57f"): struct{}{}, - discover.MustHexID("db98c59f4fd681bb8907ff0d4c67d1baf75ea364a2d31ef36ee53bc60ec3fa1d8123cf29a7990b028ba6a6bd9197c96217c9982175266816c700222c660c4d30"): struct{}{}, - discover.MustHexID("46e97c2f1774a2e505c8629d4e5d654f7faca48dfe7c23e4ebbbf86655feeae502096da83ab1d6d3cc47da5ff852bcb45490cceda4bbe7055f6d7cd91d5d8640"): struct{}{}, - discover.MustHexID("77861e48684ab9b35b6aafdbbb9028da0ff0bbe669f0485a8f387fa5ae83c2c9d54fabf3c027684b1371cad8f21b37981c053a5ff54eb8db2914f28ece651575"): struct{}{}, - discover.MustHexID("205e875111a1b5765ae07c1660f56f53ee4def882fea68a4db27a4b8f3a0b91a4c02729b0355c546d2c17116683b29aa94035205e354d15cd114f485d0778db1"): struct{}{}, - discover.MustHexID("ae75f869ed4d8bf87936ebeb1ff9bc8591b9dfc65bc59b978119e098f86b11241d6d39ec2f8f47f2087e6ead6902b82f74f272498024f830f6eded0d771f8f8d"): struct{}{}, - discover.MustHexID("9460fce5beea98e4d56c62a920bb041f45e48a5a7b96d12d02a16cbb20863be9c76491127533d9cefa5b4cec48ae6595b7ba347ef7dc8277cfb343eebde4646b"): struct{}{}, - discover.MustHexID("f6cb212b47105dbbb5b42e52492c330ba92b9a40706de88d7f3de02e1fc5507e43bc25db9b474a4b75ad4c2091d79f297e1fe5d3e09f8e4a12aba7c26db33717"): struct{}{}, - discover.MustHexID("2d35a84c4fc677fe2a19c43407d4cd387b0bbf90a5a3511794d7f752012e4090d8e7a0931ed540be41b73badd3c767c5de28195f3062c7aefba951bfd7a5c49e"): struct{}{}, - discover.MustHexID("db2bb5a8ca81f75c2b7551364acf827a86850bae5a0ce47a9eabecb2f5c4a00ccbeec8d31a6d271bc47a8c6ae6ca97a92fceb19ecdbffc8eb35c3666d6acce20"): struct{}{}, - discover.MustHexID("4922ef029b4bd93a1bca252d3ce4ed4d50192179e1c06e3ef452a18fe17d21e8c42eadf7d6aec47de2b90b07befa6c448d82f55599be6141c370dfac0b56f547"): struct{}{}, - discover.MustHexID("1d4b0709c6bc493af2086c3d38daf22ab64a0e992704a571af7479b0b58292d0bbab33552095082e3aa9297a8f338bd2679345e3f02bdefbc68c80993e0800e4"): struct{}{}, - discover.MustHexID("a6bbf49d6f1df8e3de68123802c182719fc2b17d40d655a0233a30f5131f35f58d48c87a462bdc58ec1a9014c340baebb9220d1b7c965c4f45e6cc7ca2ed6e81"): struct{}{}, - discover.MustHexID("e29327568233fa6c18f30f1ba7da3b416ac87b630a4848ac584cbb7044b2cd5b25e358c0d4c515d271c17ea145870db1dffdc278162eed77947ee45ff14d5a70"): struct{}{}, - discover.MustHexID("9839ced934daefca276eb8945c978e97774e9bfe8a101104b3202e3b38229fa4c71a06145366b4b89a135d89002ffd5bbbd12f9da33bc7c20026b290e45fa5c8"): struct{}{}, - discover.MustHexID("e1f6cd14a08821b893d2b0175d5f673ba162102cc795ca6095e276f560cc37e17a7a14dbfde674f43f9915d04edc9d76f7a64fb66e2b9dfada80ae2310621aec"): struct{}{}, - discover.MustHexID("0a72d3cc733ff102dda801c1c407c9febf8a01eb92b89dab101641e5a02b51d6ee7723aa3998519d9cef2d50ab7b18dd547899b9f9697d6757ada2fdfbbff134"): struct{}{}, - discover.MustHexID("88e70a87f6acc8edf3b381c02f3c3317392e458af688920bbfe04e3694979847e25d59fb7fe2c1d3487f1ae5a7876fbcefabe06f722dfa28a83f3ca4853c4254"): struct{}{}, - discover.MustHexID("fc5f7bc80f543b7fb2c3205ab05537b6bdc7248b398bda4ea863f48a13f3a8f7ac6aefa07dec946849cde152743d718b5628ab83fb12759e23572ad62dabcadb"): struct{}{}, - discover.MustHexID("929846fad7ecacfb21fb83ffd6a64e244f3cfc3e5a53b8f25786c2e2ea5327a6abe8c3691817bb35683cc1006fb0a6cd430e00cdfcad0dcb4afdef3e58f3c5cc"): struct{}{}, - discover.MustHexID("f2ec2830850a4e9dd48b358f908e1f22448cf5b0314750363acfd6a531edd2056237d39d182b92891a58e7d9862a43ee143a049167b7700914c41c726fad1399"): struct{}{}, - discover.MustHexID("9fe8313e32a7bc34e009a29b2a172ceded04eed6ce67f7c99680de038794c9168b4731927d45a9b82a04c5919dc768431f1a6bf4ed0424a216aab8b619a7bb7f"): struct{}{}, - discover.MustHexID("511ab9921b1ecf4bd8c76193a1c281f57a03190eae5418a23a7920a8064f89b9022bfae56d7fd2e740c2bc90c07e7aa78f201fa19c8b2b6e0dd15d8c97bec8c6"): struct{}{}, - discover.MustHexID("a98f15ecc908e6ce68b7fe29ea56c8c552f09b658352d0d0fb0fc2f08aa50b186cac9468a2742b9be9ec5d6ed0840d157afa88cb51ed2efacfd51cea26a7aa84"): struct{}{}, - discover.MustHexID("a2340b4acd4f7b743d7e8785e8ff297490b0e333f25cfe31d17df006f7e554553c6dc502f7c9f7b8798ab3ccf74624065a6cc20603842b1015793c0b37de9b15"): struct{}{}, - discover.MustHexID("c0e1be37b517f3bc7a946b2fe6b01caf815f4f6d52fadadd47b98aaf7cb19f974f7d5a7e05180f88442dad9db1b413e045df0d94ccda8eeb6873151f056ca0a1"): struct{}{}, - discover.MustHexID("854c6554239251092bc7de885b586042760a34584a686a4b360e171e7e27fab23ab5d6c8faa2519b1e5af6d9a2009e234b0f6261a0daf4a6987942a72c23783e"): struct{}{}, - discover.MustHexID("168fff55f6a28c3f10802e90cefb4daf58eb9a60c55eaaeda6588b20eece884ab9104da866633929e3c0ea413de2624b361939cf1513e1b67460686b7192f528"): struct{}{}, - discover.MustHexID("eb1a3c276744580b50d478e7910debcc7af7b3807e58ca23b7542a8265d996ebd5037ec9e6ba36ee8528aec0465bf375f32acf3538f75d79a11ab501bfff11b1"): struct{}{}, - discover.MustHexID("2527d6df6123e22e49ee6bad9dd3d579aeebd30893fc17aba8cad48c5414c78b82363aa5bd3fed1e118060300ddff1739077d4d9ec5f0940b10b347deeeb8971"): struct{}{}, - discover.MustHexID("6e409559d6b93b01e330400c8ca56a26ce979fef23edf4460e450975d0755dda5d44bcb4632871bffd2447d1de2b43f7639b8742985ab2cb52d984518c7cfefd"): struct{}{}, - discover.MustHexID("68ca2e833cdb044017d5f2bc35c2c61ecd5b340cac3384b3b33116c7caf951afdfef6fbf2542322de06a485beeb7b0e5c198b819c3dd991958eaa36c148bb356"): struct{}{}, - discover.MustHexID("7e2c09d2a2df357120facbc990568978845326a8ee4253d22d75e4576d8eb5e1cf00a411bc1a768dbe8d5d3368c864d4099fa1c509de8ecb950c71ac4180b204"): struct{}{}, - discover.MustHexID("5f2bf1ada8117f9fca7117c3d402f375d6b97296f2e40cac4eba9f3cf137d1d046468d11da8a670dab6ca468bf0ee770c4ab6246887fd78c85bf244bcecbd255"): struct{}{}, - discover.MustHexID("1d13161894d676fa124655820b92c722e04194ebc82bd2df5487dce684c21af80bd1e95230748d5aa65f2c28176fb231297f0cc5257a49051a7e33f3dd114b93"): struct{}{}, - discover.MustHexID("1dbe057f33d9748e1d396d624f4c2554f67742f18247e6be6c615c56c70a6e18a6604dd887fd1e9ffdf9708486fb76b711cb5d8e66ccc69d2cee09428832aa98"): struct{}{}, - discover.MustHexID("453009617066f547833c3c0bad5c4625546df74759e836ca34442228c564b7e8b100cd3b3b777b982681441da01c5532755dfde09970f2470b145b18e3583628"): struct{}{}, - discover.MustHexID("bf7e30583e0fa2ae326cfeca0fe1a45247c7a8463a23b84bdc33cc4eba41d4b581604cb9da4c4491518cf9c6b1fd31a1ed196b13cb41496555fb92e0a56482c6"): struct{}{}, - discover.MustHexID("d0f648262b54c16a0c4f82d1c2b08b638620537fc26321a47ace94b6691e1fa289fc4e06e1d94113b33ad1c33e3726655448f6f6a8bd1ea1c99a9f84da92c3d9"): struct{}{}, - discover.MustHexID("29c5d9c702b1dbd69bb5ae76aa396b0b4b60fd1f650717c01fe677494f3625ceb14df406624f46dccd5f788f890e4b85e51a529068877e5c5b772ef1c0c93d7f"): struct{}{}, - discover.MustHexID("218b20ee9cd80ea631c771996262a1d4b74b6db5c14321d55f42810a812b128c8c1e3bca0877e19e4ca12db6875d1922f11cfaeb9b2ec78da5157e523c7db8d3"): struct{}{}, - discover.MustHexID("a71e63927d7c96421448a9e79833d3c4a3d9de323b9d09c93a33da4ec2e7239efac29735e5298b21c23f7cfaacdec949060bd34c6afe04fc75b6501504058756"): struct{}{}, - discover.MustHexID("89baa83d0447aaabdf21f784532060bbcd0ca18742c13b09553c233e42225b36b0d52096d8e2a9db18a370a1b01099fa8eda654536c9847105ad8f6431ad5e55"): struct{}{}, - discover.MustHexID("d94b1914a4e187acd6b6038d99a48fea21cd8208d2aff92fdca28be9731772aefb5ef469d9f5b8a2221161be761337fa54f7ea8edb2f675ea22c9f278827fc39"): struct{}{}, - discover.MustHexID("bcb2be51df673ae15820d8ac083ab565c78408f79e372a5858e11fc23f1f711890e1402459fab088bd2beb95729fad5c6dbe681b3ebfc3af143b25afc14fcb59"): struct{}{}, - discover.MustHexID("3a30e0ca53a5ad43e92d6df887468d9f735b756c153cf5bfadbf70f5e3851cecbe0d88f48b1b26a9a9abe7cd2e0ba45758ab710be68b345525a19f6782798ef6"): struct{}{}, - discover.MustHexID("5af7343f5858a9acf635f9381dd58c2727ed495e9b34f899df4086731f4c5d73c27ea8b88529e655a552ab6babe65fc0d2bb578f59aa347db30ae34cf62223cc"): struct{}{}, - discover.MustHexID("cbf8b3b0bd51bc4cd3edb55a5cbd297e7dafcdfa17ffe82d1f4d2d5f1be1782030f6ac26b9f3910337acb8fcc356f79233851a35836f4197b3d4c8038c27cbfa"): struct{}{}, - discover.MustHexID("985f2fd92897063958dd06ba3f511c2c3bc1309cb6a5034a04e2e50e44d24708aa22235f305419a39693068966786201083d6a0ed12ac51dd3bf4ec8bb0cfaa2"): struct{}{}, - discover.MustHexID("38b694dd05452a810b3701ae8478c9b6ea68a1383f9695a7fb466801fd5ba57496f5575b5037cf55bf43f04ce33f34a095add9b3fefd25c3d1f1e7d28531f6f2"): struct{}{}, - discover.MustHexID("c537d6e1393521608e1dea636cf2f0e095d1e73e1b6151bfd0159b563fc9106f9c80118fce0b3d211cdcad2df0359a1596c3983e79bc26b8d19c8325e1869aaa"): struct{}{}, - discover.MustHexID("949e3793be67761380ae1536832cd5d310a555ded62d8f049636c6292815d27340188a679d5bbcf66d48fcbe459c05cf61cf475d168ec059b7e4a5ee22b64ba7"): struct{}{}, - discover.MustHexID("6e78c448dad030abfced12cba9aca8b386031f3b944622d370de8018c4252b6e9144167894da91d2123030a43b0287b5df2636adecb4c9507c44454b60a2140a"): struct{}{}, - discover.MustHexID("a7fe1e01eb794334490bfa5f4b286b6b781d3cd3f7ba02cdb7ff5d6f0322d4dc97449781e61d6a8c67576219dbaa4d854b014acbeceeed6b777618173da47986"): struct{}{}, - discover.MustHexID("0525a2e651701ed46764b4648fe88a64c70ecbf69d6d897adf48f549772d9d67840288a1396f82d6d6fc7b3ca6adf0778995935e445a3e7b682cc3c41ccd83ff"): struct{}{}, - discover.MustHexID("c353b813621ba95ec3382b668c94bc2d3b7c97f205ad21840d5c723f709f237d2f3d63cf10e48185725285eaa3785d3b0f9254e9cb62b031697249f213f7de9c"): struct{}{}, - discover.MustHexID("23d1bee8744c6d63bbd02c2b0a1685b215762dc934d1fe5e3b88432e04edfaaaac78f8e5c52d46adee9707c14380971ccd30a5c4e544f8056f793ac328927939"): struct{}{}, - discover.MustHexID("2dcb6b2368ea69ece2cc71fda719e76ba63e20eaac290df752f4ac88ef7efac9ddaa6c7ed024888546462ca028b269247d05a39eeded148032a5e9288565f7de"): struct{}{}, - discover.MustHexID("0e630f5cc6d9f99ef24fcedc9273ce82b4ad51cbf17eda2b88dba7393abf466b18164a4a60acb4709e78b55f2d29c065f10303038986ce2ea97db73e32918d42"): struct{}{}, - discover.MustHexID("85fd7813f1872885722929c1ee41c7046aa938fc4b158935032d1a14aa129cabc008dc3e1b8aceb5f500b58256867b2d59f0f97d42215b0ec9f5ed2ffc3642ed"): struct{}{}, - discover.MustHexID("c67c30ced9155115d3fe25c61a586a0597362b8c9cf5d63bc64f3ada2b7943e918a6d77f3e8c2e3f998f13262718b547b6cfe51bb5808d0e1e82c6019c223eb7"): struct{}{}, - discover.MustHexID("b5b54f8a5e9f50bc1b87a402458d49c8d94e42eba3cffcbe39e3c9cb21c3a86530eb53a37d277c7771ab5979ec12d3a351e2bd4e082f6497384dd940a3e5e234"): struct{}{}, - discover.MustHexID("6a517f4e7d4227c74ce4310d8dcf581399e05eb5048b4994955f3582512d0c425e49ef3e269e497998460cb68b20246907193607c4850a1f7cb4919d46b559a4"): struct{}{}, - discover.MustHexID("a36aa96b38c65db7a5269b9250b06efccf27eff06f4a7cdf169eac25f60d03f215de5c095a77acc5a288448425f6789347a93a4499dd4dfd33a6c51a4d8bddd0"): struct{}{}, - discover.MustHexID("887db62779c1166038f50a90ae94f226058f7319ceb6375e7c2ed8a40e52063d9363a1edaecebd30f1b6772bab9ebf38f0bb0f1d5bad07a0df47df2400845823"): struct{}{}, - discover.MustHexID("8fbc190c20707087eac8dbcbffa28a489e5a6c555c3b9542caa60307af5f08a2faa5db23b8ede13691d52cad2a1075432f9a754673f34f60e274d66742475a3a"): struct{}{}, - discover.MustHexID("f1efed4e853d00ff3f1be65fd497bc8f0a3d5f66b285069c9190653567e1838ab635b88940d3ce786747af549a1a5bf9b7173e9dc3a3aea9f10363613581a9e0"): struct{}{}, - discover.MustHexID("f26619faa0c072b3af9fe2e47ecdb0b72e28efd7047f65e63d8719a0158450d61acfaf93d253a4dbeb53122ca4cb5fb276ab301690e956320f147e387624a142"): struct{}{}, - discover.MustHexID("25f4caa5654e4b132de6ada9a1b8a6c474536566f481a9dc015c2ff75b1dd79e99c81a420ae5243a68b68eb62b468f2d1c5349eef9f123e6ed4f186efd8d1d09"): struct{}{}, - discover.MustHexID("6eeddc7bea67b08cb2bff68b4c5c6cda0b4234779a21b78b244203acab504b801ca299dcbf5b50c4650e502196b05f4fd6f3582fd0973528fd3047c36ab2198d"): struct{}{}, - discover.MustHexID("0178bdb413b9acc82b80fc1d31ef4e66065708c2ef8758bf5121100049273dd9b41fb16c64bc747d732575b959a45400db757a417ac0d6dffe220ce68827eee5"): struct{}{}, - discover.MustHexID("d839188ce67070dbc2bdd2a93a8a9abacf1ee89432ab18feb8de668bd595312f2483be56197d15e977770f59560cee5c03301a6c3cb90415951d0de26c2bbde8"): struct{}{}, - discover.MustHexID("a55b37d341ec17a34081e45cf7289ca9d6ab1152f78ef911adb4a2e4f2700dd935175b6193183c9bc8dd3bbe352f5b5013cbefe692cb046c70dae551683be27d"): struct{}{}, - discover.MustHexID("b19fc39a15f20ca81342d96cf4ddf46ab871ed1cdf4d592d16d735d4962d3729b691d0f6732e85cabf9a70ab5b57470d18b368afec00dd3110424bca3a7ae468"): struct{}{}, - discover.MustHexID("e2053e04f95afa5c8378677de62212edb972e21b40421786c53de57141853ce870481a80b68a449903479751da114a30d27a568812e178b919bd1e9b2c82f92e"): struct{}{}, - discover.MustHexID("f0f4a28ee2e9d9a4178bb862217928fee242574b7609c625bca6699deeee7af6235a1e3986686e5eff8de6647cb7796a594de2fc07ca1cdcae84b72fc053b588"): struct{}{}, - discover.MustHexID("fe9e15b816920aca3ad387d8ae4bcf5b8ec600c44c339b6b20994f4580580a668369d99d204a31567522f04a7f52db29f0d58234e1ff540d2593ff214c5aadd2"): struct{}{}, - discover.MustHexID("3d4f4dc9968874853803e2614f615583e7fd8839bf231eac3e759e08e452c679d7ed86db76a1778e19a0bdefc62b5973ee6fdfea12c7b35ac3b580f5d05dfaf6"): struct{}{}, - discover.MustHexID("b93710f214ca408ad09e8ec51faf21dd1b3a0fa7c42088ffe27225ac0df80cc5fc6e94e98f914537f6ba1199f4239a0ab0deed00fac34699a7aa54d32a68797d"): struct{}{}, - discover.MustHexID("28c82d6ef53b64cb9830cb9abf933b45d4370039f526b06ed499e53bdee7dd2d020bad5b1f716f351c93dd1b25aaa7de257071daa92ba0834fa0c8d26151d1c3"): struct{}{}, - discover.MustHexID("3015687818ccdeac78b66f2b15dfa4534da10abf7aab713267431114b4ede6fb6bc27f7d0cf8e792c356da6e916d81c80b65205679d2046beb9a69cfe8c374f7"): struct{}{}, - discover.MustHexID("68db72723b00124d63bb8e8e86e2ae23fd2c7d3799ce227a468676a51a52dbab63abdf415575f51ca0992dec20032c82e6bf0f03abed4b03a8ee33653957119b"): struct{}{}, - discover.MustHexID("0cf921182dc578a1eccf55562bc3437343de72f464912e6f296144e9dd82d51937c8aea89364fa0d793052284f1ff22b53464ed424e210d2cc3f711964cfffac"): struct{}{}, - discover.MustHexID("5984b8e581167b7d41cc472f56de19d0503bae3f2153bc9918214eb43cff484eba5ee982eb385eed339ff2b7a64dbb500750f53286618732e246ead228597b72"): struct{}{}, - discover.MustHexID("b70c76572bb4805cddc15aa97c28df7a573d15c43d41494d7a015e5100a9f7046ec9207ab60c1e20e9601f49c11ffe6d4fe2636529ebc1aa75fcf30cb799159d"): struct{}{}, - discover.MustHexID("b6dc4bbf9692b2420b6e6eb5ae87043723d512bfbce0288985dd8fe14fdd443aa2eb04261e3b05e6f419282c77ac24dc53b6b75432a86ef4cc1dc5c17a3a98a6"): struct{}{}, - discover.MustHexID("6887b34c8a76be71ef0c8d743a07ae5154b5550e5e31ec43050b1627c5936ee99bbaedd23d87e2cedc52b02b8a46d717b6f7e34702ae6cc86cf6fe4f6b7596a7"): struct{}{}, - discover.MustHexID("25ffee4be31237ead04e038747b3ba25e3eeb059016e6908518b0cb8ecb7648119d56775da5f1dc23699a13ed996692a644b48cd9b261909ac8bb00be5f700f5"): struct{}{}, - discover.MustHexID("7dc831a1668827507528992d72471a397dc0e8a65ade1a91ba197c76a89ed7336eb05bf098fc11083648ad48b2b0f6d4903cc17be221115316112b81ba72d427"): struct{}{}, - discover.MustHexID("982f73d9983cb4bdd92e94d594242c8ab44b056820d571d019f0b28f4f32a1bc86f7f25242dd911474e4a22780dabce8c7bffd57d459297feac05bba533d9c92"): struct{}{}, - discover.MustHexID("fad7c5a4d4fef670df9c80ece25a80f262088a790c41c0f974faff1f28dffaf50ea505924dc9f4a498b4c21b01c73330cb39103cde54d01007ba623f28da5f72"): struct{}{}, - discover.MustHexID("f5b318918dca88a73dabb71fa270acc3dea0676d6ca8d46966ef72b2c7e09b4874822a691073fb4a80af951ddd5105dc2c380ceaaed6449bc2256aec61a56cc0"): struct{}{}, - discover.MustHexID("bf9e74aae3b4dabdd417c4edb58867fba06233e6a1f7e8013960f56ea60efebfd2f334ae3f822ae42de7de6851de9ce9f188686c766c797e7d9a8a9245eac866"): struct{}{}, - discover.MustHexID("bf2ad47cb3010ae6dc23f0ddeb84fb73abf46d46e98c136ca8864d64bf962b8976391440e03d121beb92cfdd2c587daf65641fc53bdc636fd5c8e267d4bd8a51"): struct{}{}, - discover.MustHexID("3ab721f2f51e2d572d18ef182ae05f54ce01360119f2669788b40f5f1338357345344716a1170874b469b11281c1e3cc8a7f31c88b07527cacb55c8b66ea05a0"): struct{}{}, - discover.MustHexID("86c63b28fedc3364b25c4864dc46e158f3948a87abaccc1fea686a7405ca17ed90a33dd73bdc0a0585d19de5855f3281d06b8f4f594b1cabd97efcc7039522fd"): struct{}{}, - discover.MustHexID("80d92c92a0f97c48edcb6e35d66302a43a54323955a4f2d6ce6d641533e42b7497e9a59c32adee84293152cac35608164325395d4fe0050a737e8d6e1026c8b3"): struct{}{}, - discover.MustHexID("e3aa1da40ffa6625cf2601bc8759f06530c3f31afa23551039b659edceac98e09a37e7e79fca9d8d91fca525b46d5248453bd02ace937973f45fcab32d147f9b"): struct{}{}, - discover.MustHexID("580ca2b64f4ea1ae5f266b62e475b456d168d84debed84185719809c8b0e35f8c03271e097b5a80cfde67c29ce11bd3a76cd95befceab5a050565240250b74e5"): struct{}{}, - discover.MustHexID("7e15e8599518c651dcf57944b0a72dacd84bd4db76b2973eda1d5bce352fd8f518c1b1b843dc30a207143445a008a65341898bf1e45c197e3a4bcaec65f38daa"): struct{}{}, - discover.MustHexID("826f465ab4ed4c74bd811b4507724aeb9f7ce5e3d752c718fa4d464315c494f5720907a9ad046495becb0271e5340ee5db77ff5b060238d4008c3778a3e85aa9"): struct{}{}, - discover.MustHexID("efbd089457980cb916e6cd196591c673720882930f30a2bd0c11c1a86478c47bd719ad565de36f717989c9722ba5aa98534062daa300cb66ef6469cf52e2a955"): struct{}{}, - discover.MustHexID("70c598ebdd53b20482fba31876699193cbbd47b3dc5f0c687fcb5e7062a01b9e0862ecc65e3620634117a971b5cc6e8c0abcc125bc63274456d9d5b0c39ad72f"): struct{}{}, - discover.MustHexID("735f009b6985f0439144d0718cb1901eda97b9401a70ff08f420f3aeace5e226d5af74d24f3dce12beba7116acbabe540671e6bd5e3a5855d12ab8098a94b122"): struct{}{}, - discover.MustHexID("e3c84ebcd351010df4524cad73348483d1f23011eaa76435dfc0486b0182787c4a57e326a648c95cdcc792c92df15f0bdfe6782fd336a262daa64568a2c01dfe"): struct{}{}, - discover.MustHexID("59782ce52eafe81a2b4228457e70a21d6c64158a0682eecb5bd2a7b0c10299bf1f986e6878e89b1d13236a2f6c943745ba1b68bb6edeea80badf83d055b4d949"): struct{}{}, - discover.MustHexID("b6cb09bf53ca3133b932f1a5cb473ef7ddc67925bc4579891c85bce88a564135ecafb9619c9170f9acc5aa304c1e65389437cf06f74c4bae88a580c7e4a5d05e"): struct{}{}, - discover.MustHexID("8717968ef857018ec6205b5354945c5e9e8ff058417f27b03b3af975274a1304d54a280490ed7b4ba9819b3849c1277c380f2f5326555e290d88fbf9e5a6fb05"): struct{}{}, - discover.MustHexID("a376c3950130c665e4d100d7e9c37ab21d04910b9c1d7c59ce1daf8ea2257b0f0c238eedaa63bc029efe37a36fd5322982ab306dd3dd036009e23a8b0e5f3838"): struct{}{}, - discover.MustHexID("b4f3148a9f1a05eedf109774606943f21208f4f296ba0469bb45667a0bc1f5b66993034dfaab88b2f388d9d02c66f95e05bc3af139d1e92ff62aedb4b8c318ea"): struct{}{}, - discover.MustHexID("4b0e852717ff8aeb8d0ef3dd02b3de4f204aaa7988ca365f1c9657a5282e34fe8a1b1ef02cc6b254b7e0f6fca7671229578da874ed5f8da2f917d09500985570"): struct{}{}, - discover.MustHexID("fe5ad9842a74b1006bbde108c715ac25c2f437f44748aebc1f21c99fbcbeb630c719a9035e8d929327be83ffa0aae0de3f8adc190ba4d238d6bad3aa5b7e94a1"): struct{}{}, -} - // Config holds Server options. type Config struct { // This field must be set to a valid secp256k1 private key. @@ -263,7 +104,7 @@ type Config struct { // BootstrapNodes are used to establish connectivity // with the rest of the network. - BootstrapNodes []*discover.Node + BootstrapNodes []*enode.Node // BootstrapNodesV5 are used to establish connectivity // with the rest of the network using the V5 discovery @@ -272,11 +113,11 @@ type Config struct { // Static nodes are used as pre-configured connections which are always // maintained and re-connected on disconnects. - StaticNodes []*discover.Node `json:"-"` + StaticNodes []*enode.Node // Trusted nodes are used as pre-configured connections which are always // allowed to connect, even above the peer limit. - TrustedNodes []*discover.Node + TrustedNodes []*enode.Node // 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 @@ -344,12 +185,12 @@ type Server struct { peerOpDone chan struct{} quit chan struct{} - addstatic chan *discover.Node - removestatic chan *discover.Node - addconsensus chan *discover.Node - removeconsensus chan *discover.Node - addtrusted chan *discover.Node - removetrusted chan *discover.Node + addstatic chan *enode.Node + removestatic chan *enode.Node + addconsensus chan *enode.Node + removeconsensus chan *enode.Node + addtrusted chan *enode.Node + removetrusted chan *enode.Node posthandshake chan *conn addpeer chan *conn delpeer chan peerDrop @@ -361,7 +202,7 @@ type Server struct { consensus bool } -type peerOpFunc func(map[discover.NodeID]*Peer) +type peerOpFunc func(map[enode.ID]*Peer) type peerDrop struct { *Peer @@ -384,16 +225,16 @@ const ( type conn struct { fd net.Conn transport + node *enode.Node flags connFlag - cont chan error // The run loop uses cont to signal errors to SetupConn. - id discover.NodeID // valid after the encryption handshake - caps []Cap // valid after the protocol handshake - name string // valid after the protocol handshake + cont chan error // The run loop uses cont to signal errors to SetupConn. + caps []Cap // valid after the protocol handshake + name string // valid after the protocol handshake } type transport interface { // The two handshakes. - doEncHandshake(prv *ecdsa.PrivateKey, dialDest *discover.Node) (discover.NodeID, error) + doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) // The MsgReadWriter can only be used after the encryption // handshake has completed. The code uses conn.id to track this @@ -407,8 +248,8 @@ type transport interface { func (c *conn) String() string { s := c.flags.String() - if (c.id != discover.NodeID{}) { - s += " " + c.id.String() + if (c.node.ID() != enode.ID{}) { + s += " " + c.node.ID().String() } s += " " + c.fd.RemoteAddr().String() return s @@ -464,7 +305,7 @@ func (srv *Server) Peers() []*Peer { // Note: We'd love to put this function into a variable but // that seems to cause a weird compiler error in some // environments. - case srv.peerOp <- func(peers map[discover.NodeID]*Peer) { + case srv.peerOp <- func(peers map[enode.ID]*Peer) { for _, p := range peers { ps = append(ps, p) } @@ -479,7 +320,7 @@ func (srv *Server) Peers() []*Peer { func (srv *Server) PeerCount() int { var count int select { - case srv.peerOp <- func(ps map[discover.NodeID]*Peer) { count = len(ps) }: + case srv.peerOp <- func(ps map[enode.ID]*Peer) { count = len(ps) }: <-srv.peerOpDone case <-srv.quit: } @@ -489,7 +330,7 @@ func (srv *Server) PeerCount() int { // 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 // attempt to reconnect the peer. -func (srv *Server) AddPeer(node *discover.Node) { +func (srv *Server) AddPeer(node *enode.Node) { select { case srv.addstatic <- node: case <-srv.quit: @@ -497,26 +338,17 @@ func (srv *Server) AddPeer(node *discover.Node) { } // RemovePeer disconnects from the given node -func (srv *Server) RemovePeer(node *discover.Node) { +func (srv *Server) RemovePeer(node *enode.Node) { select { case srv.removestatic <- node: case <-srv.quit: } } -// Determine whether the node is in the whitelist. -func (srv *Server) IsAllowNode(nodeID discover.NodeID) bool { - if srv.ChainID.Cmp(params.AlayaChainConfig.ChainID) == 0 { - _, ok := AllowNodesMap[nodeID] - return ok - } - return true -} - // AddConsensusPeer connects to the given consensus node and maintains the connection until the // server is shut down. If the connection fails for any reason, the server will // attempt to reconnect the peer. -func (srv *Server) AddConsensusPeer(node *discover.Node) { +func (srv *Server) AddConsensusPeer(node *enode.Node) { select { case srv.addconsensus <- node: case <-srv.quit: @@ -524,7 +356,7 @@ func (srv *Server) AddConsensusPeer(node *discover.Node) { } // RemoveConsensusPeer disconnects from the given consensus node -func (srv *Server) RemoveConsensusPeer(node *discover.Node) { +func (srv *Server) RemoveConsensusPeer(node *enode.Node) { select { case srv.removeconsensus <- node: case <-srv.quit: @@ -533,7 +365,7 @@ func (srv *Server) RemoveConsensusPeer(node *discover.Node) { // 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 *discover.Node) { +func (srv *Server) AddTrustedPeer(node *enode.Node) { select { case srv.addtrusted <- node: case <-srv.quit: @@ -541,7 +373,7 @@ func (srv *Server) AddTrustedPeer(node *discover.Node) { } // RemoveTrustedPeer removes the given node from the trusted peer set. -func (srv *Server) RemoveTrustedPeer(node *discover.Node) { +func (srv *Server) RemoveTrustedPeer(node *enode.Node) { select { case srv.removetrusted <- node: case <-srv.quit: @@ -554,36 +386,47 @@ func (srv *Server) SubscribeEvents(ch chan *PeerEvent) event.Subscription { } // Self returns the local node's endpoint information. -func (srv *Server) Self() *discover.Node { +func (srv *Server) Self() *enode.Node { srv.lock.Lock() - defer srv.lock.Unlock() + running, listener, ntab := srv.running, srv.listener, srv.ntab + srv.lock.Unlock() - if !srv.running { - return &discover.Node{IP: net.ParseIP("0.0.0.0")} + if !running { + return enode.NewV4(&srv.PrivateKey.PublicKey, net.ParseIP("0.0.0.0"), 0, 0) } - return srv.makeSelf(srv.listener, srv.ntab) + return srv.makeSelf(listener, ntab) } -func (srv *Server) makeSelf(listener net.Listener, ntab discoverTable) *discover.Node { - // If the server's not running, return an empty node. +func (srv *Server) makeSelf(listener net.Listener, ntab discoverTable) *enode.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), - } + addr := srv.tcpAddr(listener) + return enode.NewV4(&srv.PrivateKey.PublicKey, addr.IP, addr.Port, 0) } // Otherwise return the discovery node. return ntab.Self() } +func (srv *Server) tcpAddr(listener net.Listener) net.TCPAddr { + addr := net.TCPAddr{IP: net.IP{0, 0, 0, 0}} + if listener == nil { + return addr // Inbound connections disabled, use zero address. + } + // Otherwise inject the listener address too. + if a, ok := listener.Addr().(*net.TCPAddr); ok { + addr = *a + } + if srv.NAT != nil { + if ip, err := srv.NAT.ExternalIP(); err == nil { + addr.IP = ip + } + } + if addr.IP.IsUnspecified() { + addr.IP = net.IP{127, 0, 0, 1} + } + return addr +} + // Stop terminates the server and all active peer connections. // It blocks until all active connections have been closed. func (srv *Server) Stop() { @@ -657,12 +500,12 @@ func (srv *Server) Start() (err error) { srv.addpeer = make(chan *conn) srv.delpeer = make(chan peerDrop) srv.posthandshake = make(chan *conn) - srv.addstatic = make(chan *discover.Node) - srv.removestatic = make(chan *discover.Node) - srv.addconsensus = make(chan *discover.Node) - srv.removeconsensus = make(chan *discover.Node) - srv.addtrusted = make(chan *discover.Node) - srv.removetrusted = make(chan *discover.Node) + srv.addstatic = make(chan *enode.Node) + srv.removestatic = make(chan *enode.Node) + srv.addconsensus = make(chan *enode.Node) + srv.removeconsensus = make(chan *enode.Node) + srv.addtrusted = make(chan *enode.Node) + srv.removetrusted = make(chan *enode.Node) srv.peerOp = make(chan peerOpFunc) srv.peerOpDone = make(chan struct{}) @@ -740,7 +583,8 @@ func (srv *Server) Start() (err error) { dialer := newDialState(srv.StaticNodes, srv.BootstrapNodes, srv.ntab, dynPeers, srv.NetRestrict, srv.MaxConsensusPeers) // handshake - srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: discover.PubkeyID(&srv.PrivateKey.PublicKey)} + 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()) } @@ -756,7 +600,6 @@ func (srv *Server) Start() (err error) { srv.loopWG.Add(1) go srv.run(dialer) - srv.running = true return nil } @@ -783,23 +626,23 @@ func (srv *Server) startListening() error { } type dialer interface { - newTasks(running int, peers map[discover.NodeID]*Peer, now time.Time) []task + newTasks(running int, peers map[enode.ID]*Peer, now time.Time) []task taskDone(task, time.Time) - addStatic(*discover.Node) - removeStatic(*discover.Node) - addConsensus(*discover.Node) - removeConsensus(*discover.Node) - removeConsensusFromQueue(*discover.Node) + addStatic(*enode.Node) + removeStatic(*enode.Node) + addConsensus(*enode.Node) + removeConsensus(*enode.Node) + removeConsensusFromQueue(*enode.Node) initRemoveConsensusPeerFn(removeConsensusPeerFn removeConsensusPeerFn) } func (srv *Server) run(dialstate dialer) { defer srv.loopWG.Done() var ( - peers = make(map[discover.NodeID]*Peer) + peers = make(map[enode.ID]*Peer) inboundCount = 0 - trusted = make(map[discover.NodeID]bool, len(srv.TrustedNodes)) - consensusNodes = make(map[discover.NodeID]bool, 0) + trusted = make(map[enode.ID]bool, len(srv.TrustedNodes)) + consensusNodes = make(map[enode.ID]bool, 0) taskdone = make(chan task, maxActiveDialTasks) runningTasks []task queuedTasks []task // tasks that can't run yet @@ -807,7 +650,7 @@ func (srv *Server) run(dialstate dialer) { // Put trusted nodes into a map to speed up checks. // Trusted peers are loaded on startup or added via AddTrustedPeer RPC. for _, n := range srv.TrustedNodes { - trusted[n.ID] = true + trusted[n.ID()] = true } // removes t from runningTasks @@ -839,10 +682,10 @@ func (srv *Server) run(dialstate dialer) { queuedTasks = append(queuedTasks, startTasks(nt)...) } } - dialstateRemoveConsensusPeerFn := func(node *discover.Node) { + dialstateRemoveConsensusPeerFn := func(node *enode.Node) { srv.log.Trace("Removing consensus node from dialstate", "node", node) dialstate.removeConsensusFromQueue(node) - if p, ok := peers[node.ID]; ok { + if p, ok := peers[node.ID()]; ok { p.Disconnect(DiscRequested) } } @@ -868,21 +711,22 @@ running: // stop keeping the node connected. srv.log.Trace("Removing static node", "node", n) dialstate.removeStatic(n) - if p, ok := peers[n.ID]; ok { + if p, ok := peers[n.ID()]; ok { p.Disconnect(DiscRequested) } case n := <-srv.addconsensus: // This channel is used by AddConsensusNode to add an enode // to the consensus node set. srv.log.Trace("Adding consensus node", "node", n) - if n.ID == srv.ourHandshake.ID { + + if id := n.ID(); !bytes.Equal(crypto.Keccak256(srv.ourHandshake.ID), id[:]) { srv.log.Debug("We are become an consensus node") srv.consensus = true } else { dialstate.addConsensus(n) } - consensusNodes[n.ID] = true - if p, ok := peers[n.ID]; ok { + consensusNodes[n.ID()] = true + if p, ok := peers[n.ID()]; ok { srv.log.Debug("Add consensus flag", "peer", n.ID) p.rw.set(consensusDialedConn, true) } @@ -890,15 +734,15 @@ running: // This channel is used by RemoveConsensusNode to remove an enode // from the consensus node set. srv.log.Trace("Removing consensus node", "node", n) - if n.ID == srv.ourHandshake.ID { + if id := n.ID(); !bytes.Equal(crypto.Keccak256(srv.ourHandshake.ID), id[:]) { srv.log.Debug("We are not an consensus node") srv.consensus = false } dialstate.removeConsensus(n) - if _, ok := consensusNodes[n.ID]; ok { - delete(consensusNodes, n.ID) + if _, ok := consensusNodes[n.ID()]; ok { + delete(consensusNodes, n.ID()) } - if p, ok := peers[n.ID]; ok { + if p, ok := peers[n.ID()]; ok { p.rw.set(consensusDialedConn, false) if !p.rw.is(staticDialedConn | trustedConn | inboundConn) { p.rw.set(dynDialedConn, true) @@ -913,18 +757,20 @@ running: // 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 + trusted[n.ID()] = true // Mark any already-connected peer as trusted - if p, ok := peers[n.ID]; ok { + 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) - delete(trusted, n.ID) + if _, ok := trusted[n.ID()]; ok { + delete(trusted, n.ID()) + } // Unmark any already-connected peer as trusted - if p, ok := peers[n.ID]; ok { + if p, ok := peers[n.ID()]; ok { p.rw.set(trustedConn, false) } case op := <-srv.peerOp: @@ -941,12 +787,12 @@ running: case c := <-srv.posthandshake: // A connection has passed the encryption handshake so // the remote identity is known (but hasn't been verified yet). - if trusted[c.id] { + if trusted[c.node.ID()] { // Ensure that the trusted flag is set before checking against MaxPeers. c.flags |= trustedConn } - if consensusNodes[c.id] { + if consensusNodes[c.node.ID()] { c.flags |= consensusDialedConn } @@ -971,7 +817,7 @@ running: name := truncateName(c.name) srv.log.Debug("Adding p2p peer", "name", name, "id", p.ID(), "addr", c.fd.RemoteAddr(), "flags", c.flags, "peers", len(peers)+1) go srv.runPeer(p) - peers[c.id] = p + peers[c.node.ID()] = p if p.Inbound() { inboundCount++ } @@ -1018,7 +864,7 @@ running: } } -func (srv *Server) protoHandshakeChecks(peers map[discover.NodeID]*Peer, inboundCount int, c *conn) error { +func (srv *Server) protoHandshakeChecks(peers map[enode.ID]*Peer, inboundCount int, c *conn) error { // Drop connections with no matching protocols. if len(srv.Protocols) > 0 && countMatchingProtocols(srv.Protocols, c.caps) == 0 { return DiscUselessPeer @@ -1028,7 +874,7 @@ func (srv *Server) protoHandshakeChecks(peers map[discover.NodeID]*Peer, inbound return srv.encHandshakeChecks(peers, inboundCount, c) } -func (srv *Server) numConsensusPeer(peers map[discover.NodeID]*Peer) int { +func (srv *Server) numConsensusPeer(peers map[enode.ID]*Peer) int { c := 0 for _, p := range peers { if p.rw.is(consensusDialedConn) { @@ -1038,7 +884,7 @@ func (srv *Server) numConsensusPeer(peers map[discover.NodeID]*Peer) int { return c } -func (srv *Server) encHandshakeChecks(peers map[discover.NodeID]*Peer, inboundCount int, c *conn) error { +func (srv *Server) encHandshakeChecks(peers map[enode.ID]*Peer, inboundCount int, c *conn) error { // Disconnect over limit non-consensus node. if srv.consensus && len(peers) >= srv.MaxPeers && c.is(consensusDialedConn) && srv.numConsensusPeer(peers) < srv.MaxConsensusPeers { for _, p := range peers { @@ -1059,9 +905,9 @@ func (srv *Server) encHandshakeChecks(peers map[discover.NodeID]*Peer, inboundCo return DiscTooManyPeers case !c.is(trustedConn|consensusDialedConn) && c.is(inboundConn) && inboundCount >= srv.maxInboundConns(): return DiscTooManyPeers - case peers[c.id] != nil: + case peers[c.node.ID()] != nil: return DiscAlreadyConnected - case c.id == srv.Self().ID: + case c.node.ID() == srv.Self().ID(): return DiscSelf default: return nil @@ -1091,7 +937,7 @@ type tempError interface { // inbound connections. func (srv *Server) listenLoop() { defer srv.loopWG.Done() - srv.log.Info("RLPx listener up", "self", srv.makeSelf(srv.listener, srv.ntab)) + srv.log.Info("RLPx listener up", "self", srv.Self()) tokens := defaultMaxPendingPeers if srv.MaxPendingPeers > 0 { @@ -1144,7 +990,7 @@ func (srv *Server) listenLoop() { // SetupConn runs the handshakes and attempts to add the connection // as a peer. It returns when the connection has been added as a peer // or the handshakes have failed. -func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *discover.Node) error { +func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.Node) error { self := srv.Self() if self == nil { return errors.New("shutdown") @@ -1153,12 +999,12 @@ func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *discover.Nod err := srv.setupConn(c, flags, dialDest) if err != nil { c.close(err) - srv.log.Trace("Setting up connection failed", "id", c.id, "err", err) + srv.log.Trace("Setting up connection failed", "addr", fd.RemoteAddr(), "err", err) } return err } -func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) error { +func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) error { // Prevent leftover pending conns from entering the handshake. srv.lock.Lock() running := srv.running @@ -1166,18 +1012,30 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) e if !running { 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. - var err error - if c.id, err = c.doEncHandshake(srv.PrivateKey, dialDest); err != nil { + remotePubkey, err := c.doEncHandshake(srv.PrivateKey, dialPubkey) + if err != nil { srv.log.Trace("Failed RLPx handshake", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err) return err } - clog := srv.log.New("id", c.id, "addr", c.fd.RemoteAddr(), "conn", c.flags) - // For dialed connections, check that the remote public key matches. - if dialDest != nil && c.id != dialDest.ID { - clog.Trace("Dialed identity mismatch", "want", c, dialDest.ID) - return DiscUnexpectedIdentity + if dialDest != nil { + // For dialed connections, check that the remote public key matches. + if dialPubkey.X.Cmp(remotePubkey.X) != 0 || dialPubkey.Y.Cmp(remotePubkey.Y) != 0 { + 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) if err != nil { clog.Trace("Rejected peer before protocol handshake", "err", err) @@ -1189,13 +1047,13 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) e clog.Trace("Failed proto handshake", "err", err) return err } - if phs.ID != c.id { - clog.Trace("Wrong devp2p handshake identity", "err", phs.ID) + if id := c.node.ID(); !bytes.Equal(crypto.Keccak256(phs.ID), id[:]) { + clog.Trace("Wrong devp2p handshake identity", "phsid", fmt.Sprintf("%x", phs.ID)) return DiscUnexpectedIdentity } // P2p protocol version and whitelist verification - if phs.Version < baseProtocolVersion && !srv.IsAllowNode(phs.ID) { - clog.Error("Low version of p2p protocol version", "err", phs.ID) + if phs.Version < baseProtocolVersion { + clog.Error("Low version of p2p protocol version", "err", phs.ID, "version", phs.Version) return DiscIncompatibleVersion } @@ -1211,6 +1069,16 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) e 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 { if len(s) > 20 { return s[:20] + "..." @@ -1286,13 +1154,13 @@ func (srv *Server) NodeInfo() *NodeInfo { info := &NodeInfo{ Name: srv.Name, Enode: node.String(), - ID: node.ID.String(), - IP: node.IP.String(), + ID: node.ID().String(), + IP: node.IP().String(), ListenAddr: srv.ListenAddr, Protocols: make(map[string]interface{}), } - info.Ports.Discovery = int(node.UDP) - info.Ports.Listener = int(node.TCP) + info.Ports.Discovery = node.UDP() + info.Ports.Listener = node.TCP() blskey, _ := srv.BlsPublicKey.MarshalText() info.BlsPub = string(blskey) @@ -1353,18 +1221,16 @@ func (srv *Server) watching() { log.Error("Received add validator event type error") continue } - log.Trace("Received AddValidatorEvent", "nodeID", addEv.NodeID.String()) - node := discover.NewNode(addEv.NodeID, nil, 0, 0) - srv.AddConsensusPeer(node) + log.Trace("Received AddValidatorEvent", "nodeID", addEv.Node.ID().String()) + srv.AddConsensusPeer(addEv.Node) case cbfttypes.RemoveValidatorEvent: removeEv, ok := ev.Data.(cbfttypes.RemoveValidatorEvent) if !ok { log.Error("Received remove validator event type error") continue } - log.Trace("Received RemoveValidatorEvent", "nodeID", removeEv.NodeID.String()) - node := discover.NewNode(removeEv.NodeID, nil, 0, 0) - srv.RemoveConsensusPeer(node) + log.Trace("Received RemoveValidatorEvent", "nodeID", removeEv.Node.ID().String()) + srv.RemoveConsensusPeer(removeEv.Node) default: log.Error("Received unexcepted event") } @@ -1374,41 +1240,3 @@ func (srv *Server) watching() { } } } - -type mockTransport struct { - id discover.NodeID - *rlpx - - closeErr error -} - -func newMockTransport(id discover.NodeID, fd net.Conn) transport { - wrapped := newRLPX(fd).(*rlpx) - wrapped.rw = newRLPXFrameRW(fd, secrets{ - MAC: zero16, - AES: zero16, - IngressMAC: sha3.NewKeccak256(), - EgressMAC: sha3.NewKeccak256(), - }) - return &mockTransport{id: id, rlpx: wrapped} -} - -func (c *mockTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *discover.Node) (discover.NodeID, error) { - return c.id, nil -} - -func (c *mockTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) { - return &protoHandshake{ID: c.id, Name: "test"}, nil -} - -func (c *mockTransport) close(err error) { - c.rlpx.fd.Close() - c.closeErr = err -} - -func randomID() (id discover.NodeID) { - for i := range id { - id[i] = byte(rand.Intn(255)) - } - return id -} diff --git a/p2p/server_test.go b/p2p/server_test.go index a5e013560d..5d22b5d620 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -19,31 +19,34 @@ package p2p import ( "crypto/ecdsa" "errors" + "math/rand" "net" "reflect" "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/stretchr/testify/assert" "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/crypto/sha3" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) -func init() { - // log.Root().SetHandler(log.LvlFilterHandler(log.LvlError, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) -} +// func init() { +// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) +// } type testTransport struct { - id discover.NodeID + rpub *ecdsa.PublicKey *rlpx closeErr error } -func newTestTransport(id discover.NodeID, fd net.Conn) transport { +func newTestTransport(rpub *ecdsa.PublicKey, fd net.Conn) transport { wrapped := newRLPX(fd).(*rlpx) wrapped.rw = newRLPXFrameRW(fd, secrets{ MAC: zero16, @@ -51,15 +54,16 @@ func newTestTransport(id discover.NodeID, fd net.Conn) transport { IngressMAC: sha3.NewKeccak256(), EgressMAC: sha3.NewKeccak256(), }) - return &testTransport{id: id, rlpx: wrapped} + return &testTransport{rpub: rpub, rlpx: wrapped} } -func (c *testTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *discover.Node) (discover.NodeID, error) { - return c.id, nil +func (c *testTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) { + return c.rpub, nil } func (c *testTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) { - return &protoHandshake{Version: baseProtocolVersion, ID: c.id, Name: "test"}, nil + pubkey := crypto.FromECDSAPub(c.rpub)[1:] + return &protoHandshake{ID: pubkey, Name: "test", Version: baseProtocolVersion}, nil } func (c *testTransport) close(err error) { @@ -67,7 +71,7 @@ func (c *testTransport) close(err error) { c.closeErr = err } -func startTestServer(t *testing.T, id discover.NodeID, pf func(*Peer)) *Server { +func startTestServer(t *testing.T, remoteKey *ecdsa.PublicKey, pf func(*Peer)) *Server { config := Config{ Name: "test", MaxPeers: 10, @@ -77,7 +81,7 @@ func startTestServer(t *testing.T, id discover.NodeID, pf func(*Peer)) *Server { server := &Server{ Config: config, newPeerHook: pf, - newTransport: func(fd net.Conn) transport { return newTestTransport(id, fd) }, + newTransport: func(fd net.Conn) transport { return newTestTransport(remoteKey, fd) }, } if err := server.Start(); err != nil { t.Fatalf("Could not start server: %v", err) @@ -88,14 +92,11 @@ func startTestServer(t *testing.T, id discover.NodeID, pf func(*Peer)) *Server { func TestServerListen(t *testing.T) { // start the test server connected := make(chan *Peer) - remid := randomID() + remid := &newkey().PublicKey srv := startTestServer(t, remid, func(p *Peer) { - if p.ID() != remid { + if p.ID() != enode.PubkeyToIDV4(remid) { t.Error("peer func called with wrong node id") } - if p == nil { - t.Error("peer func called with nil conn") - } connected <- p }) defer close(connected) @@ -130,7 +131,7 @@ func TestServerDial(t *testing.T) { t.Fatalf("could not setup listener: %v", err) } defer listener.Close() - accepted := make(chan net.Conn, 1) + accepted := make(chan net.Conn) go func() { conn, err := listener.Accept() if err != nil { @@ -142,14 +143,14 @@ func TestServerDial(t *testing.T) { // start the server connected := make(chan *Peer) - remid := randomID() + remid := &newkey().PublicKey srv := startTestServer(t, remid, func(p *Peer) { connected <- p }) defer close(connected) defer srv.Stop() // tell the server to connect tcpAddr := listener.Addr().(*net.TCPAddr) - node := &discover.Node{ID: remid, IP: tcpAddr.IP, TCP: uint16(tcpAddr.Port)} + node := enode.NewV4(remid, tcpAddr.IP, tcpAddr.Port, 0) srv.AddPeer(node) select { @@ -158,7 +159,7 @@ func TestServerDial(t *testing.T) { select { case peer := <-connected: - if peer.ID() != remid { + if peer.ID() != enode.PubkeyToIDV4(remid) { t.Errorf("peer has wrong id") } if peer.Name() != "test" { @@ -212,7 +213,7 @@ func TestServerTaskScheduling(t *testing.T) { quit, returned = make(chan struct{}), make(chan struct{}) tc = 0 tg = taskgen{ - newFunc: func(running int, peers map[discover.NodeID]*Peer) []task { + newFunc: func(running int, peers map[enode.ID]*Peer) []task { tc++ return []task{&testTask{index: tc - 1}} }, @@ -285,7 +286,7 @@ func TestServerManyTasks(t *testing.T) { defer srv.Stop() srv.loopWG.Add(1) go srv.run(taskgen{ - newFunc: func(running int, peers map[discover.NodeID]*Peer) []task { + newFunc: func(running int, peers map[enode.ID]*Peer) []task { start, end = end, end+maxActiveDialTasks+10 if end > len(alltasks) { end = len(alltasks) @@ -320,25 +321,25 @@ func TestServerManyTasks(t *testing.T) { } type taskgen struct { - newFunc func(running int, peers map[discover.NodeID]*Peer) []task + newFunc func(running int, peers map[enode.ID]*Peer) []task doneFunc func(task) } -func (tg taskgen) newTasks(running int, peers map[discover.NodeID]*Peer, now time.Time) []task { +func (tg taskgen) newTasks(running int, peers map[enode.ID]*Peer, now time.Time) []task { return tg.newFunc(running, peers) } func (tg taskgen) taskDone(t task, now time.Time) { tg.doneFunc(t) } -func (tg taskgen) addStatic(*discover.Node) { +func (tg taskgen) addStatic(*enode.Node) { } -func (tg taskgen) removeStatic(*discover.Node) { +func (tg taskgen) removeStatic(*enode.Node) { } -func (tg taskgen) addConsensus(*discover.Node) { +func (tg taskgen) addConsensus(*enode.Node) { } -func (tg taskgen) removeConsensus(*discover.Node) { +func (tg taskgen) removeConsensus(*enode.Node) { } -func (tg taskgen) removeConsensusFromQueue(*discover.Node) { +func (tg taskgen) removeConsensusFromQueue(*enode.Node) { } func (tg taskgen) initRemoveConsensusPeerFn(removeConsensusPeerFn removeConsensusPeerFn) { } @@ -356,14 +357,15 @@ func (t *testTask) Do(srv *Server) { // just after the encryption handshake when the server is // at capacity. Trusted connections should still be accepted. func TestServerAtCap(t *testing.T) { - trustedID := randomID() + trustedNode := newkey() + trustedID := enode.PubkeyToIDV4(&trustedNode.PublicKey) srv := &Server{ Config: Config{ PrivateKey: newkey(), MaxPeers: 10, MaxConsensusPeers: 2, NoDial: true, - TrustedNodes: []*discover.Node{{ID: trustedID}}, + TrustedNodes: []*enode.Node{newNode(trustedID, nil)}, }, } if err := srv.Start(); err != nil { @@ -371,10 +373,11 @@ func TestServerAtCap(t *testing.T) { } defer srv.Stop() - newconn := func(id discover.NodeID) *conn { + newconn := func(id enode.ID) *conn { fd, _ := net.Pipe() - tx := newTestTransport(id, fd) - return &conn{fd: fd, transport: tx, flags: inboundConn, id: id, cont: make(chan error)} + tx := newTestTransport(&trustedNode.PublicKey, fd) + node := enode.SignNull(new(enr.Record), id) + return &conn{fd: fd, transport: tx, flags: inboundConn, node: node, cont: make(chan error)} } // Inject a few connections to fill up the peer set. @@ -400,14 +403,14 @@ func TestServerAtCap(t *testing.T) { } // Remove from trusted set and try again - srv.RemoveTrustedPeer(&discover.Node{ID: trustedID}) + 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(&discover.Node{ID: anotherID}) + 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) @@ -419,7 +422,7 @@ func TestServerAtCap(t *testing.T) { // Try inserting a consensus connection. consensusID := randomID() srv.consensus = true - srv.AddConsensusPeer(&discover.Node{ID: consensusID}) + srv.AddConsensusPeer(newNode(consensusID, nil)) c = newconn(consensusID) if err := srv.checkpoint(c, srv.posthandshake); err != nil { t.Error("unexpected error for consensus conn @posthandshake:", err) @@ -436,14 +439,14 @@ func TestServerAtCap(t *testing.T) { } // Remove from consensus set and try again - srv.RemoveConsensusPeer(&discover.Node{ID: consensusID}) + srv.RemoveConsensusPeer(newNode(consensusID, nil)) c = newconn(consensusID) if err := srv.checkpoint(c, srv.posthandshake); err != DiscTooManyPeers { t.Error("wrong error for insert:", err) } anotherID = randomID() - srv.AddConsensusPeer(&discover.Node{ID: anotherID}) + srv.AddConsensusPeer(newNode(anotherID, nil)) c = newconn(anotherID) if err := srv.checkpoint(c, srv.posthandshake); err != nil { t.Error("unexpected error for consensus conn @posthandshake:", err) @@ -461,7 +464,7 @@ func TestServerAtCap(t *testing.T) { // Removing non-consensus connection srv.consensus = true - srv.AddConsensusPeer(&discover.Node{ID: consensusID}) + srv.AddConsensusPeer(newNode(consensusID, nil)) c = newconn(consensusID) if err := srv.checkpoint(c, srv.posthandshake); err != nil { t.Error("unexpected error for consensus conn @posthandshake:", err) @@ -475,27 +478,24 @@ func TestServerAtCap(t *testing.T) { assert.Equal(t, srv.PeerCount(), srv.MaxPeers) srv.MaxPeers = 9 - srv.RemoveConsensusPeer(&discover.Node{ID: consensusID}) + srv.RemoveConsensusPeer(newNode(consensusID, nil)) time.Sleep(time.Second) assert.Equal(t, srv.PeerCount(), srv.MaxPeers) } func TestServerPeerLimits(t *testing.T) { srvkey := newkey() + clientkey := newkey() + clientnode := enode.NewV4(&clientkey.PublicKey, nil, 0, 0) - clientid := randomID() - clientnode := &discover.Node{ID: clientid} - - var tp *setupTransport = &setupTransport{ - id: clientid, - phs: &protoHandshake{ - ID: clientid, + var tp = &setupTransport{ + pubkey: &clientkey.PublicKey, + phs: protoHandshake{ + ID: crypto.FromECDSAPub(&clientkey.PublicKey)[1:], // Force "DiscUselessPeer" due to unmatching caps // Caps: []Cap{discard.cap()}, }, } - var flags connFlag = dynDialedConn - var dialDest *discover.Node = &discover.Node{ID: clientid} srv := &Server{ Config: Config{ @@ -513,6 +513,8 @@ func TestServerPeerLimits(t *testing.T) { 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 { @@ -546,59 +548,61 @@ func TestServerPeerLimits(t *testing.T) { } func TestServerSetupConn(t *testing.T) { - id := randomID() - srvkey := newkey() - srvid := discover.PubkeyID(&srvkey.PublicKey) + var ( + clientkey, srvkey = newkey(), newkey() + clientpub = &clientkey.PublicKey + srvpub = &srvkey.PublicKey + ) tests := []struct { dontstart bool tt *setupTransport flags connFlag - dialDest *discover.Node + dialDest *enode.Node wantCloseErr error wantCalls string }{ { dontstart: true, - tt: &setupTransport{id: id}, + tt: &setupTransport{pubkey: clientpub}, wantCalls: "close,", wantCloseErr: errServerStopped, }, { - tt: &setupTransport{id: id, encHandshakeErr: errors.New("read error")}, + tt: &setupTransport{pubkey: clientpub, encHandshakeErr: errors.New("read error")}, flags: inboundConn, wantCalls: "doEncHandshake,close,", wantCloseErr: errors.New("read error"), }, { - tt: &setupTransport{id: id}, - dialDest: &discover.Node{ID: randomID()}, + tt: &setupTransport{pubkey: clientpub}, + dialDest: enode.NewV4(&newkey().PublicKey, nil, 0, 0), flags: dynDialedConn, wantCalls: "doEncHandshake,close,", wantCloseErr: DiscUnexpectedIdentity, }, { - tt: &setupTransport{id: id, phs: &protoHandshake{ID: randomID()}}, - dialDest: &discover.Node{ID: id}, + tt: &setupTransport{pubkey: clientpub, phs: protoHandshake{ID: randomID().Bytes()}}, + dialDest: enode.NewV4(clientpub, nil, 0, 0), flags: dynDialedConn, wantCalls: "doEncHandshake,doProtoHandshake,close,", wantCloseErr: DiscUnexpectedIdentity, }, { - tt: &setupTransport{id: id, protoHandshakeErr: errors.New("foo")}, - dialDest: &discover.Node{ID: id}, + tt: &setupTransport{pubkey: clientpub, protoHandshakeErr: errors.New("foo")}, + dialDest: enode.NewV4(clientpub, nil, 0, 0), flags: dynDialedConn, wantCalls: "doEncHandshake,doProtoHandshake,close,", wantCloseErr: errors.New("foo"), }, { - tt: &setupTransport{id: srvid, phs: &protoHandshake{ID: srvid}}, + tt: &setupTransport{pubkey: srvpub, phs: protoHandshake{ID: crypto.FromECDSAPub(srvpub)[1:]}}, flags: inboundConn, wantCalls: "doEncHandshake,close,", wantCloseErr: DiscSelf, }, { - tt: &setupTransport{id: id, phs: &protoHandshake{ID: id}}, + tt: &setupTransport{pubkey: clientpub, phs: protoHandshake{ID: crypto.FromECDSAPub(clientpub)[1:]}}, flags: inboundConn, wantCalls: "doEncHandshake,doProtoHandshake,close,", wantCloseErr: DiscUselessPeer, @@ -633,30 +637,27 @@ func TestServerSetupConn(t *testing.T) { } type setupTransport struct { - id discover.NodeID - encHandshakeErr error - - phs *protoHandshake + pubkey *ecdsa.PublicKey + encHandshakeErr error + phs protoHandshake protoHandshakeErr error calls string closeErr error } -func (c *setupTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *discover.Node) (discover.NodeID, error) { +func (c *setupTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) { c.calls += "doEncHandshake," - return c.id, c.encHandshakeErr + return c.pubkey, c.encHandshakeErr } func (c *setupTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) { c.calls += "doProtoHandshake," - if c.phs != nil { - c.phs.Version = baseProtocolVersion - } + c.phs.Version = baseProtocolVersion if c.protoHandshakeErr != nil { return nil, c.protoHandshakeErr } - return c.phs, nil + return &c.phs, nil } func (c *setupTransport) close(err error) { @@ -679,3 +680,10 @@ func newkey() *ecdsa.PrivateKey { } return key } + +func randomID() (id enode.ID) { + for i := range id { + id[i] = byte(rand.Intn(255)) + } + return id +} diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index b85039b92a..254bf745fc 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -35,13 +35,14 @@ import ( "syscall" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/docker/docker/pkg/reexec" "github.com/gorilla/websocket" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rpc" ) @@ -58,7 +59,7 @@ type ExecAdapter struct { // simulation node are created. BaseDir string - nodes map[discover.NodeID]*ExecNode + nodes map[enode.ID]*ExecNode } // NewExecAdapter returns an ExecAdapter which stores node data in @@ -66,7 +67,7 @@ type ExecAdapter struct { func NewExecAdapter(baseDir string) *ExecAdapter { return &ExecAdapter{ BaseDir: baseDir, - nodes: make(map[discover.NodeID]*ExecNode), + nodes: make(map[enode.ID]*ExecNode), } } @@ -108,7 +109,7 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { conf.Stack.P2P.NAT = nil conf.Stack.NoUSB = true - // Listen on a localhost port, which we set when we + // listen on a localhost port, which we set when we // initialise NodeConfig (usually a random port) conf.Stack.P2P.ListenAddr = fmt.Sprintf(":%d", config.Port) @@ -126,7 +127,7 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { // ExecNode starts a simulation node by exec'ing the current binary and // running the configured services type ExecNode struct { - ID discover.NodeID + ID enode.ID Dir string Config *execNodeConfig Cmd *exec.Cmd @@ -153,7 +154,9 @@ func (n *ExecNode) Client() (*rpc.Client, error) { return n.client, nil } -// and the node config encoded as JSON in an environment variable. +// Start exec's the node passing the ID and service as command line arguments +// and the node config encoded as JSON in the _P2P_NODE_CONFIG environment +// variable func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { if n.Cmd != nil { return errors.New("already started") @@ -528,7 +531,7 @@ type wsRPCDialer struct { // DialRPC implements the RPCDialer interface by creating a WebSocket RPC // client of the given node -func (w *wsRPCDialer) DialRPC(id discover.NodeID) (*rpc.Client, error) { +func (w *wsRPCDialer) DialRPC(id enode.ID) (*rpc.Client, error) { addr, ok := w.addrs[id.String()] if !ok { return nil, fmt.Errorf("unknown node: %s", id) diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index a2182b8565..04f459e1d8 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -27,7 +27,7 @@ import ( "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/pipes" "github.com/AlayaNetwork/Alaya-Go/rpc" @@ -39,7 +39,7 @@ import ( type SimAdapter struct { pipe func() (net.Conn, net.Conn, error) mtx sync.RWMutex - nodes map[discover.NodeID]*SimNode + nodes map[enode.ID]*SimNode services map[string]ServiceFunc } @@ -50,7 +50,7 @@ type SimAdapter struct { func NewSimAdapter(services map[string]ServiceFunc) *SimAdapter { return &SimAdapter{ pipe: pipes.NetPipe, - nodes: make(map[discover.NodeID]*SimNode), + nodes: make(map[enode.ID]*SimNode), services: services, } } @@ -58,7 +58,7 @@ func NewSimAdapter(services map[string]ServiceFunc) *SimAdapter { func NewTCPAdapter(services map[string]ServiceFunc) *SimAdapter { return &SimAdapter{ pipe: pipes.TCPPipe, - nodes: make(map[discover.NodeID]*SimNode), + nodes: make(map[enode.ID]*SimNode), services: services, } } @@ -117,14 +117,14 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) { // Dial implements the p2p.NodeDialer interface by connecting to the node using // an in-memory net.Pipe -func (s *SimAdapter) Dial(dest *discover.Node) (conn net.Conn, err error) { - node, ok := s.GetNode(dest.ID) +func (s *SimAdapter) Dial(dest *enode.Node) (conn net.Conn, err error) { + node, ok := s.GetNode(dest.ID()) if !ok { - return nil, fmt.Errorf("unknown node: %s", dest.ID) + return nil, fmt.Errorf("unknown node: %s", dest.ID()) } srv := node.Server() if srv == nil { - return nil, fmt.Errorf("node not running: %s", dest.ID) + return nil, fmt.Errorf("node not running: %s", dest.ID()) } // SimAdapter.pipe is net.Pipe (NewSimAdapter) pipe1, pipe2, err := s.pipe() @@ -140,7 +140,7 @@ func (s *SimAdapter) Dial(dest *discover.Node) (conn net.Conn, err error) { // DialRPC implements the RPCDialer interface by creating an in-memory RPC // client of the given node -func (s *SimAdapter) DialRPC(id discover.NodeID) (*rpc.Client, error) { +func (s *SimAdapter) DialRPC(id enode.ID) (*rpc.Client, error) { node, ok := s.GetNode(id) if !ok { return nil, fmt.Errorf("unknown node: %s", id) @@ -153,7 +153,7 @@ func (s *SimAdapter) DialRPC(id discover.NodeID) (*rpc.Client, error) { } // GetNode returns the node with the given ID if it exists -func (s *SimAdapter) GetNode(id discover.NodeID) (*SimNode, bool) { +func (s *SimAdapter) GetNode(id enode.ID) (*SimNode, bool) { s.mtx.RLock() defer s.mtx.RUnlock() node, ok := s.nodes[id] @@ -165,7 +165,7 @@ func (s *SimAdapter) GetNode(id discover.NodeID) (*SimNode, bool) { // pipe type SimNode struct { lock sync.RWMutex - ID discover.NodeID + ID enode.ID config *NodeConfig adapter *SimAdapter node *node.Node @@ -185,9 +185,9 @@ func (sn *SimNode) Addr() []byte { return []byte(sn.Node().String()) } -// Node returns a discover.Node representing the SimNode -func (sn *SimNode) Node() *discover.Node { - return discover.NewNode(sn.ID, net.IP{127, 0, 0, 1}, 16789, 16789) +// Node returns a node descriptor representing the SimNode +func (sn *SimNode) Node() *enode.Node { + return sn.config.Node() } // Client returns an rpc.Client which can be used to communicate with the diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go index 3615a8e104..6311a444d8 100644 --- a/p2p/simulations/adapters/types.go +++ b/p2p/simulations/adapters/types.go @@ -25,12 +25,13 @@ import ( "os" "strconv" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/docker/docker/pkg/reexec" "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rpc" "github.com/gorilla/websocket" @@ -81,7 +82,7 @@ type NodeAdapter interface { type NodeConfig struct { // ID is the node's ID which is used to identify the node in the // simulation network - ID discover.NodeID + ID enode.ID // PrivateKey is the node's private key which is used by the devp2p // stack to encrypt communications @@ -100,7 +101,7 @@ type NodeConfig struct { Services []string // function to sanction or prevent suggesting a peer - Reachable func(id discover.NodeID) bool + Reachable func(id enode.ID) bool Port uint16 } @@ -141,11 +142,9 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error { } if confJSON.ID != "" { - nodeID, err := discover.HexID(confJSON.ID) - if err != nil { + if err := n.ID.UnmarshalText([]byte(confJSON.ID)); err != nil { return err } - n.ID = nodeID } if confJSON.PrivateKey != "" { @@ -168,6 +167,11 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error { 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 // PrivateKey func RandomNodeConfig() *NodeConfig { @@ -176,7 +180,7 @@ func RandomNodeConfig() *NodeConfig { panic("unable to generate key") } - id := discover.PubkeyID(&key.PublicKey) + id := enode.PubkeyToIDV4(&key.PublicKey) port, err := assignTCPPort() if err != nil { panic("unable to assign tcp port") @@ -221,7 +225,7 @@ type ServiceContext struct { // other nodes in the network (for example a simulated Swarm node which needs // to connect to a PlatON node to resolve ENS names) type RPCDialer interface { - DialRPC(id discover.NodeID) (*rpc.Client, error) + DialRPC(id enode.ID) (*rpc.Client, error) } // Services is a collection of services which can be run in a simulation diff --git a/p2p/simulations/examples/ping-pong.go b/p2p/simulations/examples/ping-pong.go index ead8cc4615..15aa228c0d 100644 --- a/p2p/simulations/examples/ping-pong.go +++ b/p2p/simulations/examples/ping-pong.go @@ -25,10 +25,11 @@ import ( "sync/atomic" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" "github.com/AlayaNetwork/Alaya-Go/rpc" @@ -88,12 +89,12 @@ func main() { // sends a ping to all its connected peers every 10s and receives a pong in // return type pingPongService struct { - id discover.NodeID + id enode.ID log log.Logger received int64 } -func newPingPongService(id discover.NodeID) *pingPongService { +func newPingPongService(id enode.ID) *pingPongService { return &pingPongService{ id: id, log: log.New("node.id", id), diff --git a/p2p/simulations/http.go b/p2p/simulations/http.go index d1882aaa94..27eec2f727 100644 --- a/p2p/simulations/http.go +++ b/p2p/simulations/http.go @@ -29,12 +29,13 @@ import ( "strings" "sync" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/gorilla/websocket" "github.com/julienschmidt/httprouter" "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" "github.com/AlayaNetwork/Alaya-Go/rpc" ) @@ -714,8 +715,9 @@ func (s *Server) wrapHandler(handler http.HandlerFunc) httprouter.Handle { ctx := context.Background() if id := params.ByName("nodeid"); id != "" { + var nodeID enode.ID var node *Node - if nodeID, err := discover.HexID(id); err == nil { + if nodeID.UnmarshalText([]byte(id)) == nil { node = s.network.GetNode(nodeID) } else { node = s.network.GetNodeByName(id) @@ -728,8 +730,9 @@ func (s *Server) wrapHandler(handler http.HandlerFunc) httprouter.Handle { } if id := params.ByName("peerid"); id != "" { + var peerID enode.ID var peer *Node - if peerID, err := discover.HexID(id); err == nil { + if peerID.UnmarshalText([]byte(id)) == nil { peer = s.network.GetNode(peerID) } else { peer = s.network.GetNodeByName(id) diff --git a/p2p/simulations/http_test.go b/p2p/simulations/http_test.go index aad4d445b3..cdf41c23bb 100644 --- a/p2p/simulations/http_test.go +++ b/p2p/simulations/http_test.go @@ -27,10 +27,11 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" "github.com/AlayaNetwork/Alaya-Go/rpc" ) @@ -38,12 +39,12 @@ import ( // testService implements the node.Service interface and provides protocols // and APIs which are useful for testing nodes in a simulation network type testService struct { - id discover.NodeID + id enode.ID // peerCount is incremented once a peer handshake has been performed peerCount int64 - peers map[discover.NodeID]*testPeer + peers map[enode.ID]*testPeer peersMtx sync.Mutex // state stores []byte which is used to test creating and loading @@ -54,7 +55,7 @@ type testService struct { func newTestService(ctx *adapters.ServiceContext) (node.Service, error) { svc := &testService{ id: ctx.Config.ID, - peers: make(map[discover.NodeID]*testPeer), + peers: make(map[enode.ID]*testPeer), } svc.state.Store(ctx.Snapshot) return svc, nil @@ -65,7 +66,7 @@ type testPeer struct { dumReady chan struct{} } -func (t *testService) peer(id discover.NodeID) *testPeer { +func (t *testService) peer(id enode.ID) *testPeer { t.peersMtx.Lock() defer t.peersMtx.Unlock() if peer, ok := t.peers[id]; ok { @@ -410,7 +411,7 @@ func (t *expectEvents) nodeEvent(id string, up bool) *Event { Type: EventTypeNode, Node: &Node{ Config: &adapters.NodeConfig{ - ID: discover.MustHexID(id), + ID: enode.HexID(id), }, Up: up, }, @@ -421,8 +422,8 @@ func (t *expectEvents) connEvent(one, other string, up bool) *Event { return &Event{ Type: EventTypeConn, Conn: &Conn{ - One: discover.MustHexID(one), - Other: discover.MustHexID(other), + One: enode.HexID(one), + Other: enode.HexID(other), Up: up, }, } diff --git a/p2p/simulations/mocker.go b/p2p/simulations/mocker.go index 2bda32559d..4c81fea8a8 100644 --- a/p2p/simulations/mocker.go +++ b/p2p/simulations/mocker.go @@ -24,8 +24,9 @@ import ( "sync" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" ) @@ -154,7 +155,7 @@ func probabilistic(net *Network, quit chan struct{}, nodeCount int) { wg.Done() continue } - go func(id discover.NodeID) { + go func(id enode.ID) { time.Sleep(randWait) err := net.Start(id) if err != nil { @@ -169,8 +170,8 @@ func probabilistic(net *Network, quit chan struct{}, nodeCount int) { } //connect nodeCount number of nodes in a ring -func connectNodesInRing(net *Network, nodeCount int) ([]discover.NodeID, error) { - ids := make([]discover.NodeID, nodeCount) +func connectNodesInRing(net *Network, nodeCount int) ([]enode.ID, error) { + ids := make([]enode.ID, nodeCount) for i := 0; i < nodeCount; i++ { conf := adapters.RandomNodeConfig() node, err := net.NewNodeWithConfig(conf) diff --git a/p2p/simulations/mocker_test.go b/p2p/simulations/mocker_test.go index 7ef79935e2..b41dc309b3 100644 --- a/p2p/simulations/mocker_test.go +++ b/p2p/simulations/mocker_test.go @@ -27,7 +27,7 @@ import ( "testing" "time" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" ) func TestMocker(t *testing.T) { @@ -82,7 +82,7 @@ func TestMocker(t *testing.T) { defer sub.Unsubscribe() //wait until all nodes are started and connected //store every node up event in a map (value is irrelevant, mimic Set datatype) - nodemap := make(map[discover.NodeID]bool) + nodemap := make(map[enode.ID]bool) wg.Add(1) nodesComplete := false connCount := 0 diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go index ff55d94a71..669627f411 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -28,7 +28,7 @@ import ( "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" ) @@ -52,7 +52,7 @@ type Network struct { NetworkConfig Nodes []*Node `json:"nodes"` - nodeMap map[discover.NodeID]int + nodeMap map[enode.ID]int Conns []*Conn `json:"conns"` connMap map[string]int @@ -68,7 +68,7 @@ func NewNetwork(nodeAdapter adapters.NodeAdapter, conf *NetworkConfig) *Network return &Network{ NetworkConfig: *conf, nodeAdapter: nodeAdapter, - nodeMap: make(map[discover.NodeID]int), + nodeMap: make(map[enode.ID]int), connMap: make(map[string]int), quitc: make(chan struct{}), } @@ -86,7 +86,7 @@ func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) defer net.lock.Unlock() if conf.Reachable == nil { - conf.Reachable = func(otherID discover.NodeID) bool { + conf.Reachable = func(otherID enode.ID) bool { _, err := net.InitConn(conf.ID, otherID) if err != nil && bytes.Compare(conf.ID.Bytes(), otherID.Bytes()) < 0 { return false @@ -159,16 +159,15 @@ func (net *Network) StopAll() error { } // Start starts the node with the given ID -func (net *Network) Start(id discover.NodeID) error { +func (net *Network) Start(id enode.ID) error { return net.startWithSnapshots(id, nil) } // startWithSnapshots starts the node with the given ID using the give // snapshots -func (net *Network) startWithSnapshots(id discover.NodeID, snapshots map[string][]byte) error { +func (net *Network) startWithSnapshots(id enode.ID, snapshots map[string][]byte) error { net.lock.Lock() defer net.lock.Unlock() - node := net.getNode(id) if node == nil { return fmt.Errorf("node %v does not exist", id) @@ -202,7 +201,7 @@ func (net *Network) startWithSnapshots(id discover.NodeID, snapshots map[string] // watchPeerEvents reads peer events from the given channel and emits // corresponding network events -func (net *Network) watchPeerEvents(id discover.NodeID, events chan *p2p.PeerEvent, sub event.Subscription) { +func (net *Network) watchPeerEvents(id enode.ID, events chan *p2p.PeerEvent, sub event.Subscription) { defer func() { sub.Unsubscribe() @@ -249,7 +248,7 @@ func (net *Network) watchPeerEvents(id discover.NodeID, events chan *p2p.PeerEve } // Stop stops the node with the given ID -func (net *Network) Stop(id discover.NodeID) error { +func (net *Network) Stop(id enode.ID) error { net.lock.Lock() node := net.getNode(id) if node == nil { @@ -275,7 +274,7 @@ func (net *Network) Stop(id discover.NodeID) error { // Connect connects two nodes together by calling the "admin_addPeer" RPC // method on the "one" node so that it connects to the "other" node -func (net *Network) Connect(oneID, otherID discover.NodeID) error { +func (net *Network) Connect(oneID, otherID enode.ID) error { log.Debug("Connecting nodes with addPeer", "id", oneID, "other", otherID) conn, err := net.InitConn(oneID, otherID) if err != nil { @@ -291,7 +290,7 @@ func (net *Network) Connect(oneID, otherID discover.NodeID) error { // Disconnect disconnects two nodes by calling the "admin_removePeer" RPC // method on the "one" node so that it disconnects from the "other" node -func (net *Network) Disconnect(oneID, otherID discover.NodeID) error { +func (net *Network) Disconnect(oneID, otherID enode.ID) error { conn := net.GetConn(oneID, otherID) if conn == nil { return fmt.Errorf("connection between %v and %v does not exist", oneID, otherID) @@ -308,7 +307,7 @@ func (net *Network) Disconnect(oneID, otherID discover.NodeID) error { } // DidConnect tracks the fact that the "one" node connected to the "other" node -func (net *Network) DidConnect(one, other discover.NodeID) error { +func (net *Network) DidConnect(one, other enode.ID) error { net.lock.Lock() defer net.lock.Unlock() conn, err := net.getOrCreateConn(one, other) @@ -325,7 +324,7 @@ func (net *Network) DidConnect(one, other discover.NodeID) error { // DidDisconnect tracks the fact that the "one" node disconnected from the // "other" node -func (net *Network) DidDisconnect(one, other discover.NodeID) error { +func (net *Network) DidDisconnect(one, other enode.ID) error { net.lock.Lock() defer net.lock.Unlock() conn := net.getConn(one, other) @@ -342,7 +341,7 @@ func (net *Network) DidDisconnect(one, other discover.NodeID) error { } // DidSend tracks the fact that "sender" sent a message to "receiver" -func (net *Network) DidSend(sender, receiver discover.NodeID, proto string, code uint64) error { +func (net *Network) DidSend(sender, receiver enode.ID, proto string, code uint64) error { msg := &Msg{ One: sender, Other: receiver, @@ -355,7 +354,7 @@ func (net *Network) DidSend(sender, receiver discover.NodeID, proto string, code } // DidReceive tracks the fact that "receiver" received a message from "sender" -func (net *Network) DidReceive(sender, receiver discover.NodeID, proto string, code uint64) error { +func (net *Network) DidReceive(sender, receiver enode.ID, proto string, code uint64) error { msg := &Msg{ One: sender, Other: receiver, @@ -369,7 +368,7 @@ func (net *Network) DidReceive(sender, receiver discover.NodeID, proto string, c // GetNode gets the node with the given ID, returning nil if the node does not // exist -func (net *Network) GetNode(id discover.NodeID) *Node { +func (net *Network) GetNode(id enode.ID) *Node { net.lock.Lock() defer net.lock.Unlock() return net.getNode(id) @@ -392,7 +391,7 @@ func (net *Network) GetNodes() (nodes []*Node) { return nodes } -func (net *Network) getNode(id discover.NodeID) *Node { +func (net *Network) getNode(id enode.ID) *Node { i, found := net.nodeMap[id] if !found { return nil @@ -411,7 +410,7 @@ func (net *Network) getNodeByName(name string) *Node { // GetConn returns the connection which exists between "one" and "other" // regardless of which node initiated the connection -func (net *Network) GetConn(oneID, otherID discover.NodeID) *Conn { +func (net *Network) GetConn(oneID, otherID enode.ID) *Conn { net.lock.Lock() defer net.lock.Unlock() return net.getConn(oneID, otherID) @@ -419,13 +418,13 @@ func (net *Network) GetConn(oneID, otherID discover.NodeID) *Conn { // GetOrCreateConn is like GetConn but creates the connection if it doesn't // already exist -func (net *Network) GetOrCreateConn(oneID, otherID discover.NodeID) (*Conn, error) { +func (net *Network) GetOrCreateConn(oneID, otherID enode.ID) (*Conn, error) { net.lock.Lock() defer net.lock.Unlock() return net.getOrCreateConn(oneID, otherID) } -func (net *Network) getOrCreateConn(oneID, otherID discover.NodeID) (*Conn, error) { +func (net *Network) getOrCreateConn(oneID, otherID enode.ID) (*Conn, error) { if conn := net.getConn(oneID, otherID); conn != nil { return conn, nil } @@ -450,7 +449,7 @@ func (net *Network) getOrCreateConn(oneID, otherID discover.NodeID) (*Conn, erro return conn, nil } -func (net *Network) getConn(oneID, otherID discover.NodeID) *Conn { +func (net *Network) getConn(oneID, otherID enode.ID) *Conn { label := ConnLabel(oneID, otherID) i, found := net.connMap[label] if !found { @@ -467,7 +466,7 @@ func (net *Network) getConn(oneID, otherID discover.NodeID) *Conn { // 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 // remote peers attempt to connect to a node which will then not initiate the connection -func (net *Network) InitConn(oneID, otherID discover.NodeID) (*Conn, error) { +func (net *Network) InitConn(oneID, otherID enode.ID) (*Conn, error) { net.lock.Lock() defer net.lock.Unlock() if oneID == otherID { @@ -519,7 +518,7 @@ func (net *Network) Reset() { //re-initialize the maps net.connMap = make(map[string]int) - net.nodeMap = make(map[discover.NodeID]int) + net.nodeMap = make(map[enode.ID]int) net.Nodes = nil net.Conns = nil @@ -538,7 +537,7 @@ type Node struct { } // ID returns the ID of the node -func (n *Node) ID() discover.NodeID { +func (n *Node) ID() enode.ID { return n.Config.ID } @@ -575,10 +574,10 @@ func (n *Node) MarshalJSON() ([]byte, error) { // Conn represents a connection between two nodes in the network type Conn struct { // One is the node which initiated the connection - One discover.NodeID `json:"one"` + One enode.ID `json:"one"` // Other is the node which the connection was made to - Other discover.NodeID `json:"other"` + Other enode.ID `json:"other"` // Up tracks whether or not the connection is active Up bool `json:"up"` @@ -607,11 +606,11 @@ func (c *Conn) String() string { // Msg represents a p2p message sent between two nodes in the network type Msg struct { - One discover.NodeID `json:"one"` - Other discover.NodeID `json:"other"` - Protocol string `json:"protocol"` - Code uint64 `json:"code"` - Received bool `json:"received"` + One enode.ID `json:"one"` + Other enode.ID `json:"other"` + Protocol string `json:"protocol"` + Code uint64 `json:"code"` + Received bool `json:"received"` } // String returns a log-friendly string @@ -622,8 +621,8 @@ func (m *Msg) String() string { // ConnLabel generates a deterministic string which represents a connection // between two nodes, used to compare if two connections are between the same // nodes -func ConnLabel(source, target discover.NodeID) string { - var first, second discover.NodeID +func ConnLabel(source, target enode.ID) string { + var first, second enode.ID if bytes.Compare(source.Bytes(), target.Bytes()) > 0 { first = target second = source diff --git a/p2p/simulations/network_test.go b/p2p/simulations/network_test.go index 7b7ac9c20c..eb27509fe3 100644 --- a/p2p/simulations/network_test.go +++ b/p2p/simulations/network_test.go @@ -22,7 +22,7 @@ import ( "testing" "time" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" ) @@ -39,7 +39,7 @@ func TestNetworkSimulation(t *testing.T) { }) defer network.Shutdown() nodeCount := 20 - ids := make([]discover.NodeID, nodeCount) + ids := make([]enode.ID, nodeCount) for i := 0; i < nodeCount; i++ { conf := adapters.RandomNodeConfig() node, err := network.NewNodeWithConfig(conf) @@ -64,7 +64,7 @@ func TestNetworkSimulation(t *testing.T) { } return nil } - check := func(ctx context.Context, id discover.NodeID) (bool, error) { + check := func(ctx context.Context, id enode.ID) (bool, error) { // check we haven't run out of time select { case <-ctx.Done(): @@ -102,7 +102,7 @@ func TestNetworkSimulation(t *testing.T) { defer cancel() // trigger a check every 100ms - trigger := make(chan discover.NodeID) + trigger := make(chan enode.ID) go triggerChecks(ctx, ids, trigger, 100*time.Millisecond) result := NewSimulation(network).Run(ctx, &Step{ @@ -140,7 +140,7 @@ func TestNetworkSimulation(t *testing.T) { } } -func triggerChecks(ctx context.Context, ids []discover.NodeID, trigger chan discover.NodeID, interval time.Duration) { +func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, interval time.Duration) { tick := time.NewTicker(interval) defer tick.Stop() for { diff --git a/p2p/simulations/simulation.go b/p2p/simulations/simulation.go index bce49c4ead..1b698fbdf9 100644 --- a/p2p/simulations/simulation.go +++ b/p2p/simulations/simulation.go @@ -20,7 +20,7 @@ import ( "context" "time" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" ) // 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 - nodes := make(map[discover.NodeID]struct{}, len(step.Expect.Nodes)) + nodes := make(map[enode.ID]struct{}, len(step.Expect.Nodes)) for _, id := range step.Expect.Nodes { nodes[id] = struct{}{} } @@ -119,7 +119,7 @@ type Step struct { // Trigger is a channel which receives node ids and triggers an // expectation check for that node - Trigger chan discover.NodeID + Trigger chan enode.ID // Expect is the expectation to wait for when performing this step Expect *Expectation @@ -127,15 +127,15 @@ type Step struct { type Expectation struct { // Nodes is a list of nodes to check - Nodes []discover.NodeID + Nodes []enode.ID // Check checks whether a given node meets the expectation - Check func(context.Context, discover.NodeID) (bool, error) + Check func(context.Context, enode.ID) (bool, error) } func newStepResult() *StepResult { return &StepResult{ - Passes: make(map[discover.NodeID]time.Time), + Passes: make(map[enode.ID]time.Time), } } @@ -150,7 +150,7 @@ type StepResult struct { FinishedAt time.Time // Passes are the timestamps of the successful node expectations - Passes map[discover.NodeID]time.Time + Passes map[enode.ID]time.Time // NetworkEvents are the network events which occurred during the step NetworkEvents []*Event diff --git a/p2p/testing/peerpool.go b/p2p/testing/peerpool.go index b2886acde6..59e709c292 100644 --- a/p2p/testing/peerpool.go +++ b/p2p/testing/peerpool.go @@ -20,23 +20,24 @@ import ( "fmt" "sync" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) type TestPeer interface { - ID() discover.NodeID + ID() enode.ID Drop(error) } // TestPeerPool is an example peerPool to demonstrate registration of peer connections type TestPeerPool struct { lock sync.Mutex - peers map[discover.NodeID]TestPeer + peers map[enode.ID]TestPeer } func NewTestPeerPool() *TestPeerPool { - return &TestPeerPool{peers: make(map[discover.NodeID]TestPeer)} + return &TestPeerPool{peers: make(map[enode.ID]TestPeer)} } func (p *TestPeerPool) Add(peer TestPeer) { @@ -53,14 +54,14 @@ func (p *TestPeerPool) Remove(peer TestPeer) { delete(p.peers, peer.ID()) } -func (p *TestPeerPool) Has(id discover.NodeID) bool { +func (p *TestPeerPool) Has(id enode.ID) bool { p.lock.Lock() defer p.lock.Unlock() _, ok := p.peers[id] return ok } -func (p *TestPeerPool) Get(id discover.NodeID) TestPeer { +func (p *TestPeerPool) Get(id enode.ID) TestPeer { p.lock.Lock() defer p.lock.Unlock() return p.peers[id] diff --git a/p2p/testing/protocolsession.go b/p2p/testing/protocolsession.go index 962dfd3a1b..5fc29099bd 100644 --- a/p2p/testing/protocolsession.go +++ b/p2p/testing/protocolsession.go @@ -22,9 +22,10 @@ import ( "sync" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" ) @@ -35,7 +36,7 @@ var errTimedOut = errors.New("timed out") // receive (expect) messages type ProtocolSession struct { Server *p2p.Server - IDs []discover.NodeID + Nodes []*enode.Node adapter *adapters.SimAdapter events chan *p2p.PeerEvent } @@ -56,32 +57,32 @@ type Exchange struct { // Trigger is part of the exchange, incoming message for the pivot node // sent by a peer type Trigger struct { - Msg interface{} // type of message to be sent - Code uint64 // code of message is given - Peer discover.NodeID // the peer to send the message to - Timeout time.Duration // timeout duration for the sending + Msg interface{} // type of message to be sent + Code uint64 // code of message is given + Peer enode.ID // the peer to send the message to + Timeout time.Duration // timeout duration for the sending } // Expect is part of an exchange, outgoing message from the pivot node // received by a peer type Expect struct { - Msg interface{} // type of message to expect - Code uint64 // code of message is now given - Peer discover.NodeID // the peer that expects the message - Timeout time.Duration // timeout duration for receiving + Msg interface{} // type of message to expect + Code uint64 // code of message is now given + Peer enode.ID // the peer that expects the message + Timeout time.Duration // timeout duration for receiving } // Disconnect represents a disconnect event, used and checked by TestDisconnected type Disconnect struct { - Peer discover.NodeID // discconnected peer - Error error // disconnect reason + Peer enode.ID // discconnected peer + Error error // disconnect reason } // trigger sends messages from peers func (s *ProtocolSession) trigger(trig Trigger) error { simNode, ok := s.adapter.GetNode(trig.Peer) if !ok { - return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(s.IDs)) + return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(s.Nodes)) } mockNode, ok := simNode.Services()[0].(*mockNode) if !ok { @@ -111,7 +112,7 @@ func (s *ProtocolSession) trigger(trig Trigger) error { // expect checks an expectation of a message sent out by the pivot node func (s *ProtocolSession) expect(exps []Expect) error { // construct a map of expectations for each node - peerExpects := make(map[discover.NodeID][]Expect) + peerExpects := make(map[enode.ID][]Expect) for _, exp := range exps { if exp.Msg == nil { return errors.New("no message to expect") @@ -120,11 +121,11 @@ func (s *ProtocolSession) expect(exps []Expect) error { } // construct a map of mockNodes for each node - mockNodes := make(map[discover.NodeID]*mockNode) + mockNodes := make(map[enode.ID]*mockNode) for nodeID := range peerExpects { simNode, ok := s.adapter.GetNode(nodeID) if !ok { - return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(s.IDs)) + return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(s.Nodes)) } mockNode, ok := simNode.Services()[0].(*mockNode) if !ok { @@ -253,7 +254,7 @@ func (s *ProtocolSession) testExchange(e Exchange) error { // TestDisconnected tests the disconnections given as arguments // the disconnect structs describe what disconnect error is expected on which peer func (s *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error { - expects := make(map[discover.NodeID]error) + expects := make(map[enode.ID]error) for _, disconnect := range disconnects { expects[disconnect.Peer] = disconnect.Error } diff --git a/p2p/testing/protocoltester.go b/p2p/testing/protocoltester.go index 5d7db0395e..e3cb36db5f 100644 --- a/p2p/testing/protocoltester.go +++ b/p2p/testing/protocoltester.go @@ -35,7 +35,7 @@ import ( "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" "github.com/AlayaNetwork/Alaya-Go/rlp" @@ -52,7 +52,7 @@ type ProtocolTester struct { // NewProtocolTester constructs a new ProtocolTester // 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 -func NewProtocolTester(t *testing.T, id discover.NodeID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester { +func NewProtocolTester(t *testing.T, id enode.ID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester { services := adapters.Services{ "test": func(ctx *adapters.ServiceContext) (node.Service, error) { return &testNode{run}, nil @@ -76,17 +76,17 @@ func NewProtocolTester(t *testing.T, id discover.NodeID, n int, run func(*p2p.Pe node := net.GetNode(id).Node.(*adapters.SimNode) peers := make([]*adapters.NodeConfig, n) - peerIDs := make([]discover.NodeID, n) + nodes := make([]*enode.Node, n) for i := 0; i < n; i++ { peers[i] = adapters.RandomNodeConfig() peers[i].Services = []string{"mock"} - peerIDs[i] = peers[i].ID + nodes[i] = peers[i].Node() } events := make(chan *p2p.PeerEvent, 1000) node.SubscribeEvents(events) ps := &ProtocolSession{ Server: node.Server(), - IDs: peerIDs, + Nodes: nodes, adapter: adapter, events: events, } @@ -108,7 +108,7 @@ func (t *ProtocolTester) Stop() error { // Connect brings up the remote peer node and connects it using the // p2p/simulations network connection with the in memory network adapter -func (t *ProtocolTester) Connect(selfID discover.NodeID, peers ...*adapters.NodeConfig) { +func (t *ProtocolTester) Connect(selfID enode.ID, peers ...*adapters.NodeConfig) { for _, peer := range peers { log.Trace(fmt.Sprintf("start node %v", peer.ID)) if _, err := t.network.NewNodeWithConfig(peer); err != nil { diff --git a/params/config.go b/params/config.go index c01a89cb01..f0e9570c19 100644 --- a/params/config.go +++ b/params/config.go @@ -20,9 +20,10 @@ import ( "fmt" "math/big" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) // Genesis hashes to enforce below configs on. @@ -180,7 +181,7 @@ type ChainConfig struct { } type CbftNode struct { - Node discover.Node `json:"node"` + Node *enode.Node `json:"node"` BlsPubKey bls.PublicKey `json:"blsPubKey"` } @@ -333,8 +334,8 @@ func ConvertNodeUrl(initialNodes []initNode) []CbftNode { cbftNode := new(CbftNode) - if node, err := discover.ParseNode(n.Enode); nil == err { - cbftNode.Node = *node + if node, err := enode.ParseV4(n.Enode); nil == err { + cbftNode.Node = node } if n.BlsPubkey != "" { diff --git a/x/gov/gov.go b/x/gov/gov.go index 9a2d6ebd01..bb3287a331 100644 --- a/x/gov/gov.go +++ b/x/gov/gov.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package gov import ( @@ -24,14 +23,14 @@ import ( "math/big" "strconv" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/common/vm" "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/common/byteutil" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/node" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/x/staking" "github.com/AlayaNetwork/Alaya-Go/x/xcom" "github.com/AlayaNetwork/Alaya-Go/x/xutil" @@ -39,12 +38,12 @@ import ( type Staking interface { GetVerifierList(blockHash common.Hash, blockNumber uint64, isCommit bool) (staking.ValidatorExQueue, error) - ListVerifierNodeID(blockHash common.Hash, blockNumber uint64) ([]discover.NodeID, error) + ListVerifierNodeID(blockHash common.Hash, blockNumber uint64) ([]enode.IDv0, error) GetCanBaseList(blockHash common.Hash, blockNumber uint64) (staking.CandidateBaseQueue, error) GetCandidateInfo(blockHash common.Hash, addr common.NodeAddress) (*staking.Candidate, error) GetCanBase(blockHash common.Hash, addr common.NodeAddress) (*staking.CandidateBase, error) GetCanMutable(blockHash common.Hash, addr common.NodeAddress) (*staking.CandidateMutable, error) - DeclarePromoteNotify(blockHash common.Hash, blockNumber uint64, nodeId discover.NodeID, programVersion uint32) error + DeclarePromoteNotify(blockHash common.Hash, blockNumber uint64, nodeId enode.IDv0, programVersion uint32) error } const ( @@ -269,7 +268,7 @@ func Vote(from common.Address, vote VoteInfo, blockHash common.Hash, blockNumber //the proposal is version type, so add the node ID to active node list. if proposal.GetProposalType() == Version { if err := AddActiveNode(blockHash, vote.ProposalID, vote.VoteNodeID); err != nil { - log.Error("add nodeID to active node list error", "proposalID", vote.ProposalID, "nodeID", byteutil.PrintNodeID(vote.VoteNodeID)) + log.Error("add nodeID to active node list error", "proposalID", vote.ProposalID, "nodeID", vote.VoteNodeID.TerminalString()) return err } } @@ -278,7 +277,7 @@ func Vote(from common.Address, vote VoteInfo, blockHash common.Hash, blockNumber } // node declares it's version -func DeclareVersion(from common.Address, declaredNodeID discover.NodeID, declaredVersion uint32, programVersionSign common.VersionSign, blockHash common.Hash, blockNumber uint64, stk Staking, state xcom.StateDB) error { +func DeclareVersion(from common.Address, declaredNodeID enode.IDv0, declaredVersion uint32, programVersionSign common.VersionSign, blockHash common.Hash, blockNumber uint64, stk Staking, state xcom.StateDB) error { log.Debug("call DeclareVersion", "from", from, "blockHash", blockHash, "blockNumber", blockNumber, "declaredNodeID", declaredNodeID, "declaredVersion", declaredVersion, "versionSign", programVersionSign) if !node.GetCryptoHandler().IsSignedByNodeID(declaredVersion, programVersionSign.Bytes(), declaredNodeID) { @@ -368,7 +367,7 @@ func DeclareVersion(from common.Address, declaredNodeID discover.NodeID, declare } // check if the node a verifier, and the caller address is same as the staking address -func checkVerifier(from common.Address, nodeID discover.NodeID, blockHash common.Hash, blockNumber uint64, stk Staking) error { +func checkVerifier(from common.Address, nodeID enode.IDv0, blockHash common.Hash, blockNumber uint64, stk Staking) error { log.Debug("call checkVerifier", "from", from, "blockHash", blockHash, "blockNumber", blockNumber, "nodeID", nodeID) _, err := xutil.NodeId2Addr(nodeID) @@ -489,7 +488,7 @@ func FindVotingProposal(blockHash common.Hash, state xcom.StateDB, proposalTypes // GetMaxEndVotingBlock returns the max endVotingBlock of proposals those are at voting stage, and the nodeID has voted for those proposals. // or returns 0 if there's no proposal at voting stage, or nodeID didn't voted for any proposal. // if any error happened, return 0 and the error -func GetMaxEndVotingBlock(nodeID discover.NodeID, blockHash common.Hash, state xcom.StateDB) (uint64, error) { +func GetMaxEndVotingBlock(nodeID enode.IDv0, blockHash common.Hash, state xcom.StateDB) (uint64, error) { if proposalIDList, err := ListVotingProposal(blockHash); err != nil { return 0, err } else { @@ -514,7 +513,7 @@ func GetMaxEndVotingBlock(nodeID discover.NodeID, blockHash common.Hash, state x } // NotifyPunishedVerifiers receives punished verifies notification from Staking -func NotifyPunishedVerifiers(blockHash common.Hash, punishedVerifierMap map[discover.NodeID]struct{}, state xcom.StateDB) error { +func NotifyPunishedVerifiers(blockHash common.Hash, punishedVerifierMap map[enode.IDv0]struct{}, state xcom.StateDB) error { if punishedVerifierMap == nil || len(punishedVerifierMap) == 0 { return nil } @@ -658,7 +657,7 @@ func FindGovernParam(module, name string, blockHash common.Hash) (*GovernParam, } // check if the node a candidate, and the caller address is same as the staking address -func checkCandidate(from common.Address, nodeID discover.NodeID, blockHash common.Hash, blockNumber uint64, stk Staking) error { +func checkCandidate(from common.Address, nodeID enode.IDv0, blockHash common.Hash, blockNumber uint64, stk Staking) error { _, err := xutil.NodeId2Addr(nodeID) if nil != err { diff --git a/x/gov/gov_db.go b/x/gov/gov_db.go index 15a9eb8df3..16910db9c2 100644 --- a/x/gov/gov_db.go +++ b/x/gov/gov_db.go @@ -14,13 +14,14 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package gov import ( "encoding/json" "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/rlp" @@ -30,7 +31,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/common/vm" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/x/xcom" ) @@ -110,7 +110,7 @@ func GetProposalList(blockHash common.Hash, state xcom.StateDB) ([]Proposal, err } //Add the Vote detail -func AddVoteValue(proposalID common.Hash, voter discover.NodeID, option VoteOption, blockHash common.Hash) error { +func AddVoteValue(proposalID common.Hash, voter enode.IDv0, option VoteOption, blockHash common.Hash) error { voteValueList, err := ListVoteValue(proposalID, blockHash) if err != nil { return err @@ -175,8 +175,8 @@ func ClearVoteValue(proposalID common.Hash, blockHash common.Hash) error { } /* -func ListVotedVerifier(proposalID common.Hash, state xcom.StateDB) ([]discover.NodeID, error) { - var voterList []discover.NodeID +func ListVotedVerifier(proposalID common.Hash, state xcom.StateDB) ([]enode.IDv0, error) { + var voterList []enode.IDv0 valueList, err := ListVoteValue(proposalID, state) if err != nil { return nil, err @@ -189,13 +189,13 @@ func ListVotedVerifier(proposalID common.Hash, state xcom.StateDB) ([]discover.N } */ -func GetVotedVerifierMap(proposalID common.Hash, blockHash common.Hash) (map[discover.NodeID]struct{}, error) { +func GetVotedVerifierMap(proposalID common.Hash, blockHash common.Hash) (map[enode.IDv0]struct{}, error) { valueList, err := ListVoteValue(proposalID, blockHash) if err != nil { return nil, err } - votedMap := make(map[discover.NodeID]struct{}, len(valueList)) + votedMap := make(map[enode.IDv0]struct{}, len(valueList)) for _, value := range valueList { votedMap[value.VoteNodeID] = struct{}{} } @@ -405,7 +405,7 @@ func MovePreActiveProposalIDToEnd(blockHash common.Hash, proposalID common.Hash) } // Add the node that has made a new version declare or vote during voting period -func AddActiveNode(blockHash common.Hash, proposalID common.Hash, nodeID discover.NodeID) error { +func AddActiveNode(blockHash common.Hash, proposalID common.Hash, nodeID enode.IDv0) error { if err := addActiveNode(blockHash, nodeID, proposalID); err != nil { log.Error("add active node to snapshot db failed", "blockHash", blockHash.Hex(), "proposalID", proposalID, "error", err) return err @@ -414,7 +414,7 @@ func AddActiveNode(blockHash common.Hash, proposalID common.Hash, nodeID discove } // Get the node list that have made a new version declare or vote during voting period -func GetActiveNodeList(blockHash common.Hash, proposalID common.Hash) ([]discover.NodeID, error) { +func GetActiveNodeList(blockHash common.Hash, proposalID common.Hash) ([]enode.IDv0, error) { nodes, err := getActiveNodeList(blockHash, proposalID) if err != nil { log.Error("get active nodes from snapshot db failed", "blockHash", blockHash.Hex(), "proposalID", proposalID, "error", err) @@ -434,7 +434,7 @@ func ClearActiveNodes(blockHash common.Hash, proposalID common.Hash) error { } // AccuVerifiers accumulates all distinct verifiers those can vote this proposal ID -func AccuVerifiers(blockHash common.Hash, proposalID common.Hash, verifierList []discover.NodeID) error { +func AccuVerifiers(blockHash common.Hash, proposalID common.Hash, verifierList []enode.IDv0) error { if err := addAccuVerifiers(blockHash, proposalID, verifierList); err != nil { log.Error("accumulates verifiers to snapshot db failed", "blockHash", blockHash.Hex(), "proposalID", proposalID, "error", err) return err @@ -443,7 +443,7 @@ func AccuVerifiers(blockHash common.Hash, proposalID common.Hash, verifierList [ } // Get the total number of all voting verifiers -func ListAccuVerifier(blockHash common.Hash, proposalID common.Hash) ([]discover.NodeID, error) { +func ListAccuVerifier(blockHash common.Hash, proposalID common.Hash) ([]enode.IDv0, error) { if l, err := getAccuVerifiers(blockHash, proposalID); err != nil { log.Error("list accumulated verifiers failed", "blockHash", blockHash.Hex(), "proposalID", proposalID, "error", err) return nil, err diff --git a/x/gov/gov_db_test.go b/x/gov/gov_db_test.go index 97a73b3af3..d9a31cdd94 100644 --- a/x/gov/gov_db_test.go +++ b/x/gov/gov_db_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package gov import ( @@ -22,6 +21,8 @@ import ( "fmt" "math/big" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/params" @@ -36,7 +37,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" "github.com/AlayaNetwork/Alaya-Go/crypto/sha3" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -966,7 +966,7 @@ func getTxtProposal() *TextProposal { ProposalType: Text, PIPID: "em1", SubmitBlock: uint64(1000), - Proposer: discover.NodeID{}, + Proposer: enode.IDv0{}, } } @@ -977,7 +977,7 @@ func getVerProposal(proposalId common.Hash) *VersionProposal { PIPID: "em2", SubmitBlock: uint64(1000), EndVotingRounds: uint64(8), - Proposer: discover.NodeID{}, + Proposer: enode.IDv0{}, NewVersion: 32, } } @@ -989,7 +989,7 @@ func getCancelProposal() *CancelProposal { PIPID: "em3", SubmitBlock: uint64(1000), EndVotingRounds: uint64(5), - Proposer: discover.NodeID{}, + Proposer: enode.IDv0{}, TobeCanceled: common.Hash{0x02}, } } @@ -1000,7 +1000,7 @@ func getParamProposal() *ParamProposal { ProposalType: Param, PIPID: "em5", SubmitBlock: uint64(1000), - Proposer: discover.NodeID{}, + Proposer: enode.IDv0{}, Module: "PPOS", Name: "testName1", NewValue: "newValue1", @@ -1009,32 +1009,32 @@ func getParamProposal() *ParamProposal { var voteValueList = []VoteValue{ { - VoteNodeID: discover.MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + VoteNodeID: enode.MustHexIDv0("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), VoteOption: Yes, }, { - VoteNodeID: discover.MustHexID("0x1dd8d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + VoteNodeID: enode.MustHexIDv0("0x1dd8d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), VoteOption: Yes, }, { - VoteNodeID: discover.MustHexID("0x1dd7d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + VoteNodeID: enode.MustHexIDv0("0x1dd7d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), VoteOption: Yes, }, { - VoteNodeID: discover.MustHexID("0x1dd6d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + VoteNodeID: enode.MustHexIDv0("0x1dd6d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), VoteOption: Yes, }, { - VoteNodeID: discover.MustHexID("0x1dd5d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), + VoteNodeID: enode.MustHexIDv0("0x1dd5d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), VoteOption: Yes, }, } -var NodeIDList = []discover.NodeID{ - discover.MustHexID("5a942bc607d970259e203f5110887d6105cc787f7433c16ce28390fb39f1e67897b0fb445710cc836b89ed7f951c57a1f26a0940ca308d630448b5bd391a8aa6"), - discover.MustHexID("c453d29394e613e85999129b8fb93146d584d5a0be16f7d13fd1f44de2d01bae104878eba8e8f6b8d2c162b5a35d5939d38851f856e56186471dd7de57e9bfa9"), - discover.MustHexID("2c1733caf5c23086612a309f5ee8e76ca45455351f7cf069bcde59c07175607325cf2bf2485daa0fbf1f9cdee6eea246e5e00b9a0d0bfed0f02b37f3b0c70490"), - discover.MustHexID("e7edfb4f9c3e1fe0288ddcf0894535214fa03acea941c7360ccf90e86460aefa118ba9f2573921349c392cd1b5d4db90b4795ab353df3c915b2e8481d241ec57"), +var NodeIDList = []enode.IDv0{ + enode.MustHexIDv0("5a942bc607d970259e203f5110887d6105cc787f7433c16ce28390fb39f1e67897b0fb445710cc836b89ed7f951c57a1f26a0940ca308d630448b5bd391a8aa6"), + enode.MustHexIDv0("c453d29394e613e85999129b8fb93146d584d5a0be16f7d13fd1f44de2d01bae104878eba8e8f6b8d2c162b5a35d5939d38851f856e56186471dd7de57e9bfa9"), + enode.MustHexIDv0("2c1733caf5c23086612a309f5ee8e76ca45455351f7cf069bcde59c07175607325cf2bf2485daa0fbf1f9cdee6eea246e5e00b9a0d0bfed0f02b37f3b0c70490"), + enode.MustHexIDv0("e7edfb4f9c3e1fe0288ddcf0894535214fa03acea941c7360ccf90e86460aefa118ba9f2573921349c392cd1b5d4db90b4795ab353df3c915b2e8481d241ec57"), } func generateHash(n string) common.Hash { diff --git a/x/gov/gov_snapdb.go b/x/gov/gov_snapdb.go index dedacba35c..f7724ba949 100644 --- a/x/gov/gov_snapdb.go +++ b/x/gov/gov_snapdb.go @@ -14,14 +14,13 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package gov import ( "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/AlayaNetwork/Alaya-Go/x/xutil" ) @@ -150,7 +149,7 @@ func getAllProposalIDList(blockHash common.Hash) ([]common.Hash, error) { return total, nil } -func addActiveNode(blockHash common.Hash, node discover.NodeID, proposalId common.Hash) error { +func addActiveNode(blockHash common.Hash, node enode.IDv0, proposalId common.Hash) error { nodes, err := getActiveNodeList(blockHash, proposalId) if snapshotdb.NonDbNotFoundErr(err) { return err @@ -165,12 +164,12 @@ func addActiveNode(blockHash common.Hash, node discover.NodeID, proposalId commo } } -func getActiveNodeList(blockHash common.Hash, proposalId common.Hash) ([]discover.NodeID, error) { +func getActiveNodeList(blockHash common.Hash, proposalId common.Hash) ([]enode.IDv0, error) { value, err := get(blockHash, KeyActiveNodes(proposalId)) if snapshotdb.NonDbNotFoundErr(err) { return nil, err } - var nodes []discover.NodeID + var nodes []enode.IDv0 if len(value) > 0 { if err := rlp.DecodeBytes(value, &nodes); err != nil { return nil, err @@ -183,12 +182,12 @@ func deleteActiveNodeList(blockHash common.Hash, proposalId common.Hash) error { return del(blockHash, KeyActiveNodes(proposalId)) } -func addAccuVerifiers(blockHash common.Hash, proposalId common.Hash, nodes []discover.NodeID) error { +func addAccuVerifiers(blockHash common.Hash, proposalId common.Hash, nodes []enode.IDv0) error { value, err := get(blockHash, KeyAccuVerifier(proposalId)) if snapshotdb.NonDbNotFoundErr(err) { return err } - var accuVerifiers []discover.NodeID + var accuVerifiers []enode.IDv0 if value != nil { if err := rlp.DecodeBytes(value, &accuVerifiers); err != nil { @@ -196,7 +195,7 @@ func addAccuVerifiers(blockHash common.Hash, proposalId common.Hash, nodes []dis } } - existMap := make(map[discover.NodeID]struct{}, len(accuVerifiers)) + existMap := make(map[enode.IDv0]struct{}, len(accuVerifiers)) for _, nodeID := range accuVerifiers { existMap[nodeID] = struct{}{} } @@ -215,14 +214,14 @@ func addAccuVerifiers(blockHash common.Hash, proposalId common.Hash, nodes []dis return put(blockHash, KeyAccuVerifier(proposalId), accuVerifiers) } -func getAccuVerifiers(blockHash common.Hash, proposalId common.Hash) ([]discover.NodeID, error) { +func getAccuVerifiers(blockHash common.Hash, proposalId common.Hash) ([]enode.IDv0, error) { value, err := get(blockHash, KeyAccuVerifier(proposalId)) if snapshotdb.NonDbNotFoundErr(err) { return nil, err } if len(value) > 0 { - var verifiers []discover.NodeID + var verifiers []enode.IDv0 if err := rlp.DecodeBytes(value, &verifiers); err != nil { return nil, err } else { diff --git a/x/gov/gov_test.go b/x/gov/gov_test.go index c491dd9695..c3214f6188 100644 --- a/x/gov/gov_test.go +++ b/x/gov/gov_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package gov import ( @@ -22,6 +21,8 @@ import ( "math/big" "testing" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/common/vm" "github.com/AlayaNetwork/Alaya-Go/x/xcom" @@ -33,7 +34,6 @@ import ( "github.com/stretchr/testify/assert" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/common" @@ -42,7 +42,7 @@ import ( var ( sender = common.MustBech32ToAddress("atx1pmhjxvfqeccm87kzpkkr08djgvpp5535gxm6s5") - nodeID = discover.MustHexID("0x362003c50ed3a523cdede37a001803b8f0fed27cb402b3d6127a1a96661ec202318f68f4c76d9b0bfbabfd551a178d4335eaeaa9b7981a4df30dfc8c0bfe3384") + nodeID = enode.MustHexIDv0("0x362003c50ed3a523cdede37a001803b8f0fed27cb402b3d6127a1a96661ec202318f68f4c76d9b0bfbabfd551a178d4335eaeaa9b7981a4df30dfc8c0bfe3384") priKey = crypto.HexMustToECDSA("0c6ccec28e36dc5581ea3d8af1303c774b51523da397f55cdc4acd9d2b988132") @@ -61,7 +61,7 @@ var ( ) type MockStaking struct { - DeclaeredVodes map[discover.NodeID]uint32 + DeclaeredVodes map[enode.IDv0]uint32 } func (stk *MockStaking) GetVerifierList(blockHash common.Hash, blockNumber uint64, isCommit bool) (staking.ValidatorExQueue, error) { @@ -75,8 +75,8 @@ func (stk *MockStaking) GetVerifierList(blockHash common.Hash, blockNumber uint6 return []*staking.ValidatorEx{valEx}, nil } -func (stk *MockStaking) ListVerifierNodeID(blockHash common.Hash, blockNumber uint64) ([]discover.NodeID, error) { - return []discover.NodeID{nodeID}, nil +func (stk *MockStaking) ListVerifierNodeID(blockHash common.Hash, blockNumber uint64) ([]enode.IDv0, error) { + return []enode.IDv0{nodeID}, nil } func (stk *MockStaking) GetCanBaseList(blockHash common.Hash, blockNumber uint64) (staking.CandidateBaseQueue, error) { @@ -101,15 +101,15 @@ func (stk *MockStaking) GetCanMutable(blockHash common.Hash, addr common.NodeAdd can := &staking.CandidateMutable{Status: staking.Valided} return can, nil } -func (stk *MockStaking) DeclarePromoteNotify(blockHash common.Hash, blockNumber uint64, nodeId discover.NodeID, programVersion uint32) error { +func (stk *MockStaking) DeclarePromoteNotify(blockHash common.Hash, blockNumber uint64, nodeId enode.IDv0, programVersion uint32) error { if stk.DeclaeredVodes == nil { - stk.DeclaeredVodes = make(map[discover.NodeID]uint32) + stk.DeclaeredVodes = make(map[enode.IDv0]uint32) } stk.DeclaeredVodes[nodeID] = programVersion return nil } -func (stk *MockStaking) ListDeclaredNode() map[discover.NodeID]uint32 { +func (stk *MockStaking) ListDeclaredNode() map[enode.IDv0]uint32 { return stk.DeclaeredVodes } @@ -556,7 +556,7 @@ func TestGov_NotifyPunishedVerifiers(t *testing.T) { assert.Equal(t, 1, len(vvList)) } - punishedVerifierMap := make(map[discover.NodeID]struct{}) + punishedVerifierMap := make(map[enode.IDv0]struct{}) punishedVerifierMap[nodeID] = struct{}{} if err := NotifyPunishedVerifiers(chain.CurrentHeader().Hash(), punishedVerifierMap, chain.StateDB); err != nil { diff --git a/x/gov/gov_types.go b/x/gov/gov_types.go index c9660f88de..1c848b45d4 100644 --- a/x/gov/gov_types.go +++ b/x/gov/gov_types.go @@ -14,12 +14,11 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package gov import ( "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" ) type TallyResult struct { @@ -33,14 +32,14 @@ type TallyResult struct { } type VoteInfo struct { - ProposalID common.Hash `json:"proposalID"` - VoteNodeID discover.NodeID `json:"voteNodeID"` - VoteOption VoteOption `json:"voteOption"` + ProposalID common.Hash `json:"proposalID"` + VoteNodeID enode.IDv0 `json:"voteNodeID"` + VoteOption VoteOption `json:"voteOption"` } type VoteValue struct { - VoteNodeID discover.NodeID `json:"voteNodeID"` - VoteOption VoteOption `json:"voteOption"` + VoteNodeID enode.IDv0 `json:"voteNodeID"` + VoteOption VoteOption `json:"voteOption"` } type ActiveVersionValue struct { diff --git a/x/gov/proposals.go b/x/gov/proposals.go index b65cbf53fb..a38db5448e 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -14,16 +14,16 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package gov import ( "fmt" "math/big" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/x/xcom" "github.com/AlayaNetwork/Alaya-Go/x/xutil" ) @@ -97,7 +97,7 @@ type Proposal interface { GetPIPID() string GetSubmitBlock() uint64 GetEndVotingBlock() uint64 - GetProposer() discover.NodeID + GetProposer() enode.IDv0 GetTallyResult() TallyResult Verify(blockNumber uint64, blockHash common.Hash, state xcom.StateDB, chainID *big.Int) error String() string @@ -109,7 +109,7 @@ type TextProposal struct { PIPID string SubmitBlock uint64 EndVotingBlock uint64 - Proposer discover.NodeID + Proposer enode.IDv0 Result TallyResult `json:"-"` } @@ -133,7 +133,7 @@ func (tp *TextProposal) GetEndVotingBlock() uint64 { return tp.EndVotingBlock } -func (tp *TextProposal) GetProposer() discover.NodeID { +func (tp *TextProposal) GetProposer() enode.IDv0 { return tp.Proposer } @@ -178,7 +178,7 @@ type VersionProposal struct { SubmitBlock uint64 EndVotingRounds uint64 EndVotingBlock uint64 - Proposer discover.NodeID + Proposer enode.IDv0 Result TallyResult `json:"-"` NewVersion uint32 ActiveBlock uint64 @@ -204,7 +204,7 @@ func (vp *VersionProposal) GetEndVotingBlock() uint64 { return vp.EndVotingBlock } -func (vp *VersionProposal) GetProposer() discover.NodeID { +func (vp *VersionProposal) GetProposer() enode.IDv0 { return vp.Proposer } @@ -294,7 +294,7 @@ type CancelProposal struct { SubmitBlock uint64 EndVotingRounds uint64 EndVotingBlock uint64 - Proposer discover.NodeID + Proposer enode.IDv0 TobeCanceled common.Hash Result TallyResult `json:"-"` } @@ -319,7 +319,7 @@ func (cp *CancelProposal) GetEndVotingBlock() uint64 { return cp.EndVotingBlock } -func (cp *CancelProposal) GetProposer() discover.NodeID { +func (cp *CancelProposal) GetProposer() enode.IDv0 { return cp.Proposer } @@ -390,7 +390,7 @@ type ParamProposal struct { PIPID string SubmitBlock uint64 EndVotingBlock uint64 - Proposer discover.NodeID + Proposer enode.IDv0 Result TallyResult `json:"-"` Module string Name string @@ -417,7 +417,7 @@ func (pp *ParamProposal) GetEndVotingBlock() uint64 { return pp.EndVotingBlock } -func (pp *ParamProposal) GetProposer() discover.NodeID { +func (pp *ParamProposal) GetProposer() enode.IDv0 { return pp.Proposer } @@ -513,7 +513,7 @@ func verifyBasic(p Proposal, blockHash common.Hash, state xcom.StateDB) error { return ProposalIDEmpty } - if p.GetProposer() == discover.ZeroNodeID { + if p.GetProposer() == enode.ZeroIDv0 { return ProposerEmpty } diff --git a/x/handler/vrf_handler.go b/x/handler/vrf_handler.go index 95e25dc62e..bb1ac40004 100644 --- a/x/handler/vrf_handler.go +++ b/x/handler/vrf_handler.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package handler import ( @@ -25,12 +24,12 @@ import ( "math/big" "sync" - "github.com/AlayaNetwork/Alaya-Go/x/gov" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/x/gov" "github.com/AlayaNetwork/Alaya-Go/common" + "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" "github.com/AlayaNetwork/Alaya-Go/crypto/vrf" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/rlp" @@ -86,7 +85,7 @@ func (vh *VrfHandler) GenerateNonce(currentBlockNumber *big.Int, parentHash comm if len(value) > 0 { log.Info("Generate vrf proof Success", "blockNumber", currentBlockNumber.Uint64(), "parentHash", hex.EncodeToString(parentHash.Bytes()), "nonce", hex.EncodeToString(value), - "nodeId", discover.PubkeyID(&vh.privateKey.PublicKey).String()) + "nodeId", enode.PublicKeyToIDv0(&vh.privateKey.PublicKey).String()) return value, nil } } diff --git a/x/plugin/base_plugin.go b/x/plugin/base_plugin.go index 86894b9ac0..812b32c5a9 100644 --- a/x/plugin/base_plugin.go +++ b/x/plugin/base_plugin.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package plugin import ( @@ -23,20 +22,22 @@ import ( "fmt" "reflect" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + gerr "github.com/go-errors/errors" + "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/common/byteutil" "github.com/AlayaNetwork/Alaya-Go/core/types" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/AlayaNetwork/Alaya-Go/x/xcom" - gerr "github.com/go-errors/errors" ) type BasePlugin interface { BeginBlock(blockHash common.Hash, header *types.Header, state xcom.StateDB) error EndBlock(blockHash common.Hash, header *types.Header, state xcom.StateDB) error - Confirmed(nodeId discover.NodeID, block *types.Block) error + Confirmed(nodeId enode.IDv0, block *types.Block) error } var ( diff --git a/x/plugin/gov_plugin.go b/x/plugin/gov_plugin.go index 26935e9b90..fa31edeb63 100644 --- a/x/plugin/gov_plugin.go +++ b/x/plugin/gov_plugin.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package plugin import ( @@ -23,12 +22,12 @@ import ( "math/big" "sync" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" - "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/core/types" "github.com/AlayaNetwork/Alaya-Go/log" @@ -58,7 +57,7 @@ func GovPluginInstance() *GovPlugin { func (govPlugin *GovPlugin) SetChainID(chainId *big.Int) { govPlugin.chainID = chainId } -func (govPlugin *GovPlugin) Confirmed(nodeId discover.NodeID, block *types.Block) error { +func (govPlugin *GovPlugin) Confirmed(nodeId enode.IDv0, block *types.Block) error { return nil } diff --git a/x/plugin/gov_plugin_test.go b/x/plugin/gov_plugin_test.go index 586955156e..02615d919d 100644 --- a/x/plugin/gov_plugin_test.go +++ b/x/plugin/gov_plugin_test.go @@ -14,15 +14,16 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package plugin import ( "encoding/hex" + "testing" + "github.com/AlayaNetwork/Alaya-Go/common/vm" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/x/staking" - "testing" "github.com/stretchr/testify/assert" @@ -35,8 +36,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/rlp" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" - "github.com/AlayaNetwork/Alaya-Go/x/xcom" "github.com/AlayaNetwork/Alaya-Go/x/xutil" @@ -382,7 +381,7 @@ func TestGovPlugin_SubmitText_Proposer_empty(t *testing.T) { ProposalType: gov.Text, PIPID: "textPIPID", SubmitBlock: 1, - Proposer: discover.ZeroNodeID, + Proposer: enode.ZeroIDv0, } state := stateDB.(*mock.MockStateDB) @@ -1236,10 +1235,10 @@ func TestGovPlugin_printVersion(t *testing.T) { } func TestGovPlugin_TestNodeID(t *testing.T) { - var nodeID discover.NodeID + var nodeID enode.IDv0 nodeID = [64]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x01} - t.Logf("nodeID is empty, %t", nodeID == discover.ZeroNodeID) + t.Logf("nodeID is empty, %t", nodeID == enode.ZeroIDv0) } @@ -1324,7 +1323,7 @@ func TestGovPlugin_ForkVersion0140Proposal(t *testing.T) { PIPID: "em2", SubmitBlock: uint64(1000), EndVotingRounds: uint64(8), - Proposer: discover.NodeID{}, + Proposer: enode.IDv0{}, NewVersion: params.FORKVERSION_0_14_0, ActiveBlock: 1, } @@ -1365,7 +1364,7 @@ func TestGovPlugin_ForkVersion0140Proposal(t *testing.T) { var ( chandler *node.CryptoHandler priKey = crypto.HexMustToECDSA("8e1477549bea04b97ea15911e2e9b3041b7a9921f80bd6ddbe4c2b080473de22") - nodeID = discover.MustHexID("3e7864716b671c4de0dc2d7fd86215e0dcb8419e66430a770294eb2f37b714a07b6a3493055bb2d733dee9bfcc995e1c8e7885f338a69bf6c28930f3cf341819") + nodeID = enode.MustHexIDv0("3e7864716b671c4de0dc2d7fd86215e0dcb8419e66430a770294eb2f37b714a07b6a3493055bb2d733dee9bfcc995e1c8e7885f338a69bf6c28930f3cf341819") ) func initChandlerHandler() { diff --git a/x/plugin/issue1625_patch.go b/x/plugin/issue1625_patch.go index a9c9f3c2d6..d0ccf570a3 100644 --- a/x/plugin/issue1625_patch.go +++ b/x/plugin/issue1625_patch.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package plugin import ( @@ -23,7 +22,7 @@ import ( "math/big" "sort" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/params" @@ -473,7 +472,7 @@ type issue1625AccountDelInfo struct { //use for get staking stakingBlock uint64 canAddr common.NodeAddress - nodeID discover.NodeID + nodeID enode.IDv0 originRestrictingAmount, originFreeAmount *big.Int } diff --git a/x/plugin/issue1625_patch_test.go b/x/plugin/issue1625_patch_test.go index f6cc17dc75..c329148e53 100644 --- a/x/plugin/issue1625_patch_test.go +++ b/x/plugin/issue1625_patch_test.go @@ -20,7 +20,7 @@ import ( "sort" "testing" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/common" @@ -41,7 +41,7 @@ func TestIssue1625AccountDelInfos_Sort(t *testing.T) { del: &staking.Delegation{DelegateEpoch: 2}, candidate: &staking.Candidate{ &staking.CandidateBase{ - NodeId: [discover.NodeIDBits / 8]byte{13}, + NodeId: [crypto.PubkeyBytesBits / 8]byte{13}, }, &staking.CandidateMutable{ Status: staking.Invalided | staking.Withdrew, @@ -54,7 +54,7 @@ func TestIssue1625AccountDelInfos_Sort(t *testing.T) { del: &staking.Delegation{DelegateEpoch: 1}, candidate: &staking.Candidate{ &staking.CandidateBase{ - NodeId: [discover.NodeIDBits / 8]byte{11}, + NodeId: [crypto.PubkeyBytesBits / 8]byte{11}, }, &staking.CandidateMutable{ Status: staking.Invalided | staking.Withdrew, @@ -68,7 +68,7 @@ func TestIssue1625AccountDelInfos_Sort(t *testing.T) { del: &staking.Delegation{DelegateEpoch: 2}, candidate: &staking.Candidate{ &staking.CandidateBase{ - NodeId: [discover.NodeIDBits / 8]byte{2}, + NodeId: [crypto.PubkeyBytesBits / 8]byte{2}, }, &staking.CandidateMutable{ RewardPer: 10, @@ -80,7 +80,7 @@ func TestIssue1625AccountDelInfos_Sort(t *testing.T) { del: &staking.Delegation{DelegateEpoch: 2}, candidate: &staking.Candidate{ &staking.CandidateBase{ - NodeId: [discover.NodeIDBits / 8]byte{1}, + NodeId: [crypto.PubkeyBytesBits / 8]byte{1}, }, &staking.CandidateMutable{ RewardPer: 10, @@ -93,7 +93,7 @@ func TestIssue1625AccountDelInfos_Sort(t *testing.T) { del: &staking.Delegation{DelegateEpoch: 1}, candidate: &staking.Candidate{ &staking.CandidateBase{ - NodeId: [discover.NodeIDBits / 8]byte{3}, + NodeId: [crypto.PubkeyBytesBits / 8]byte{3}, }, &staking.CandidateMutable{ RewardPer: 15, diff --git a/x/plugin/issue1654_patch.go b/x/plugin/issue1654_patch.go index fdb0f6da87..459169bbb3 100644 --- a/x/plugin/issue1654_patch.go +++ b/x/plugin/issue1654_patch.go @@ -19,14 +19,14 @@ package plugin import ( "math/big" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/x/xcom" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/x/xutil" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" - "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/common" @@ -76,7 +76,7 @@ func (a *FixIssue1654Plugin) fix(blockHash common.Hash, chainID *big.Int, state log.Debug("fix issue1654,can is valid,update the can power", "nodeID", candidate.nodeID, "stakingNum", candidate.stakingNum, "sub", candidate.shouldSub, "newShare", can.Shares) } else { if can.Shares != nil { - if can.Shares.Cmp(candidate.shouldSub)>=0{ + if can.Shares.Cmp(candidate.shouldSub) >= 0 { can.SubShares(candidate.shouldSub) if err := stk.db.SetCanMutableStore(blockHash, canAddr, can.CandidateMutable); nil != err { return err @@ -91,7 +91,7 @@ func (a *FixIssue1654Plugin) fix(blockHash common.Hash, chainID *big.Int, state } type issue1654Candidate struct { - nodeID discover.NodeID + nodeID enode.IDv0 stakingNum uint64 shouldSub *big.Int } @@ -122,7 +122,7 @@ func NewIssue1654Candidates() ([]issue1654Candidate, error) { for _, c := range candidates { amount, _ := new(big.Int).SetString(c.Amount, 10) nodes = append(nodes, issue1654Candidate{ - nodeID: discover.MustHexID(c.Node), + nodeID: enode.MustHexIDv0(c.Node), stakingNum: uint64(c.Num), shouldSub: amount, }) diff --git a/x/plugin/platon_plugin_test.go b/x/plugin/platon_plugin_test.go index b28a8ed11e..ce3a607744 100644 --- a/x/plugin/platon_plugin_test.go +++ b/x/plugin/platon_plugin_test.go @@ -14,18 +14,19 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package plugin import ( "crypto/ecdsa" "fmt" - "github.com/AlayaNetwork/Alaya-Go/params" "math/big" "math/rand" "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/params" + "github.com/AlayaNetwork/Alaya-Go/x/gov" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" @@ -43,7 +44,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/core/types" // "github.com/AlayaNetwork/Alaya-Go/core/vm" "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/AlayaNetwork/Alaya-Go/x/restricting" "github.com/AlayaNetwork/Alaya-Go/x/staking" @@ -57,34 +57,34 @@ func init() { } var ( - nodeIdArr = []discover.NodeID{ - discover.MustHexID("5a942bc607d970259e203f5110887d6105cc787f7433c16ce28390fb39f1e67897b0fb445710cc836b89ed7f951c57a1f26a0940ca308d630448b5bd391a8aa6"), - discover.MustHexID("c453d29394e613e85999129b8fb93146d584d5a0be16f7d13fd1f44de2d01bae104878eba8e8f6b8d2c162b5a35d5939d38851f856e56186471dd7de57e9bfa9"), - discover.MustHexID("2c1733caf5c23086612a309f5ee8e76ca45455351f7cf069bcde59c07175607325cf2bf2485daa0fbf1f9cdee6eea246e5e00b9a0d0bfed0f02b37f3b0c70490"), - discover.MustHexID("e7edfb4f9c3e1fe0288ddcf0894535214fa03acea941c7360ccf90e86460aefa118ba9f2573921349c392cd1b5d4db90b4795ab353df3c915b2e8481d241ec57"), - - discover.MustHexID("3a06953a2d5d45b29167bef58208f1287225bdd2591260af29ae1300aeed362e9b548369dfc1659abbef403c9b3b07a8a194040e966acd6e5b6d55aa2df7c1d8"), - discover.MustHexID("fd06314e027c3812bd0d1cf0ce1b5742d21d1ae5a397da6e7eed463ad1172c268092c2b3de52a204aabb3a6048be48f4880ce54ff3116a3843d4087d219db054"), - discover.MustHexID("811eb49e3127389065f41aac395d15e1e9968555f43913447ebb358705a63b2de37ab890f06854034a2dd171daf873adf8647498200a54cf376fcbe07d12ecd8"), - discover.MustHexID("b3d3667793ea2c2a77848b89bed514cd6fd7d685af4ee9d2482b6c58f8b3dd371cf8a41aa638e45ce420df323dfff6ed041213c343066348b4e1b39bd1396f48"), - - discover.MustHexID("0x248af08a775ff63a47a5970e4928bcccd1a8cef984fd4142ea7f89cd13015bdab9ca4a8c5e1070dc00fa81a047542f53ca596f553c4acfb7abe75a8fb5019057"), - discover.MustHexID("0xfd790ff5dc48baccb9418ce5cfac6a10c3646f20a3fe32d9502c4edce3a77fa90bfee0361d8a72093b7994f8cbc28ee537bdda2b634c5966b1a9253d9d270145"), - discover.MustHexID("0x56d243db84a521cb204f582ee84bca7f4af29437dd447a6e36d17f4853888e05343844bd64294b99b835ca7f72ef5b1325ef1c89b0c5c2744154cdadf7c4e9fa"), - discover.MustHexID("0x8796a6fcefd9037d8433e3a959ff8f3c4552a482ce727b00a90bfd1ec365ce2faa33e19aa6a172b5c186b51f5a875b5acd35063171f0d9501a9c8f1c98513825"), - discover.MustHexID("0x547b876036165d66274ce31692165c8acb6f140a65cab0e0e12f1f09d1c7d8d53decf997830919e4f5cacb2df1adfe914c53d22e3ab284730b78f5c63a273b8c"), - discover.MustHexID("0x9fdbeb873bea2557752eabd2c96419b8a700b680716081472601ddf7498f0db9b8a40797b677f2fac541031f742c2bbd110ff264ae3400bf177c456a76a93d42"), - discover.MustHexID("0xc553783799bfef7c34a84b2737f2c77f8f2c5cfedc3fd7af2d944da6ece90aa94cf621e6de5c4495881fbfc9beec655ffb10e39cb4ca9be7768d284409040f32"), - discover.MustHexID("0x75ad2ee8ca77619c3ba0ddcec5dab1375fe4fa90bab9e751caef3996ce082dfed32fe4c137401ee05e501c079b2e4400397b09de14b08b09c9e7f9698e9e4f0a"), - discover.MustHexID("0xdb18af9be2af9dff2347c3d06db4b1bada0598d099a210275251b68fa7b5a863d47fcdd382cc4b3ea01e5b55e9dd0bdbce654133b7f58928ce74629d5e68b974"), - discover.MustHexID("0x472d19e5e9888368c02f24ebbbe0f2132096e7183d213ab65d96b8c03205f88398924af8876f3c615e08aa0f9a26c38911fda26d51c602c8d4f8f3cb866808d7"), - discover.MustHexID("4f1f036e5e18cc812347d5073cbec2a8da7930de323063c39b0d4413a396e088bfa90e8c28174313d8d82e9a14bc0884b13a48fc28e619e44c48a49b4fd9f107"), - discover.MustHexID("f18c596232d637409c6295abb1e720db99ffc12363a1eb8123d6f54af80423a5edd06f91115115a1dca1377e97b9031e2ddb864d34d9b3491d6fa07e8d9b951b"), - discover.MustHexID("7a8f7a28ac1c4eaf98b2be890f372e5abc58ebe6d3aab47aedcb0076e34eb42882e926676ebab327a4ef4e2ea5c4296e9c7bc0991360cb44f52672631012db1b"), - discover.MustHexID("9eeb448babf9e93449e831b91f98d9cbc0c2324fe8c43baac69d090717454f3f930713084713fe3a9f01e4ca59b80a0f2b41dbd6d531f414650bab0363e3691a"), - discover.MustHexID("cc1d7314c15e30dc5587f675eb5f803b1a2d88bfe76cec591cec1ff678bc6abce98f40054325bdcb44fb83174f27d38a54fbce4846af8f027b333868bc5144a4"), - discover.MustHexID("e4d99694be2fc8a53d8c2446f947aec1c7de3ee26f7cd43f4f6f77371f56f11156218dec32b51ddce470e97127624d330bb7a3237ba5f0d87d2d3166faf1035e"), - discover.MustHexID("9c61f59f70296b6d494e7230888e58f19b13c5c6c85562e57e1fe02d0ff872b4957238c73559d017c8770b999891056aa6329dbf628bc19028d8f4d35ec35823"), + nodeIdArr = []enode.IDv0{ + enode.MustHexIDv0("5a942bc607d970259e203f5110887d6105cc787f7433c16ce28390fb39f1e67897b0fb445710cc836b89ed7f951c57a1f26a0940ca308d630448b5bd391a8aa6"), + enode.MustHexIDv0("c453d29394e613e85999129b8fb93146d584d5a0be16f7d13fd1f44de2d01bae104878eba8e8f6b8d2c162b5a35d5939d38851f856e56186471dd7de57e9bfa9"), + enode.MustHexIDv0("2c1733caf5c23086612a309f5ee8e76ca45455351f7cf069bcde59c07175607325cf2bf2485daa0fbf1f9cdee6eea246e5e00b9a0d0bfed0f02b37f3b0c70490"), + enode.MustHexIDv0("e7edfb4f9c3e1fe0288ddcf0894535214fa03acea941c7360ccf90e86460aefa118ba9f2573921349c392cd1b5d4db90b4795ab353df3c915b2e8481d241ec57"), + + enode.MustHexIDv0("3a06953a2d5d45b29167bef58208f1287225bdd2591260af29ae1300aeed362e9b548369dfc1659abbef403c9b3b07a8a194040e966acd6e5b6d55aa2df7c1d8"), + enode.MustHexIDv0("fd06314e027c3812bd0d1cf0ce1b5742d21d1ae5a397da6e7eed463ad1172c268092c2b3de52a204aabb3a6048be48f4880ce54ff3116a3843d4087d219db054"), + enode.MustHexIDv0("811eb49e3127389065f41aac395d15e1e9968555f43913447ebb358705a63b2de37ab890f06854034a2dd171daf873adf8647498200a54cf376fcbe07d12ecd8"), + enode.MustHexIDv0("b3d3667793ea2c2a77848b89bed514cd6fd7d685af4ee9d2482b6c58f8b3dd371cf8a41aa638e45ce420df323dfff6ed041213c343066348b4e1b39bd1396f48"), + + enode.MustHexIDv0("0x248af08a775ff63a47a5970e4928bcccd1a8cef984fd4142ea7f89cd13015bdab9ca4a8c5e1070dc00fa81a047542f53ca596f553c4acfb7abe75a8fb5019057"), + enode.MustHexIDv0("0xfd790ff5dc48baccb9418ce5cfac6a10c3646f20a3fe32d9502c4edce3a77fa90bfee0361d8a72093b7994f8cbc28ee537bdda2b634c5966b1a9253d9d270145"), + enode.MustHexIDv0("0x56d243db84a521cb204f582ee84bca7f4af29437dd447a6e36d17f4853888e05343844bd64294b99b835ca7f72ef5b1325ef1c89b0c5c2744154cdadf7c4e9fa"), + enode.MustHexIDv0("0x8796a6fcefd9037d8433e3a959ff8f3c4552a482ce727b00a90bfd1ec365ce2faa33e19aa6a172b5c186b51f5a875b5acd35063171f0d9501a9c8f1c98513825"), + enode.MustHexIDv0("0x547b876036165d66274ce31692165c8acb6f140a65cab0e0e12f1f09d1c7d8d53decf997830919e4f5cacb2df1adfe914c53d22e3ab284730b78f5c63a273b8c"), + enode.MustHexIDv0("0x9fdbeb873bea2557752eabd2c96419b8a700b680716081472601ddf7498f0db9b8a40797b677f2fac541031f742c2bbd110ff264ae3400bf177c456a76a93d42"), + enode.MustHexIDv0("0xc553783799bfef7c34a84b2737f2c77f8f2c5cfedc3fd7af2d944da6ece90aa94cf621e6de5c4495881fbfc9beec655ffb10e39cb4ca9be7768d284409040f32"), + enode.MustHexIDv0("0x75ad2ee8ca77619c3ba0ddcec5dab1375fe4fa90bab9e751caef3996ce082dfed32fe4c137401ee05e501c079b2e4400397b09de14b08b09c9e7f9698e9e4f0a"), + enode.MustHexIDv0("0xdb18af9be2af9dff2347c3d06db4b1bada0598d099a210275251b68fa7b5a863d47fcdd382cc4b3ea01e5b55e9dd0bdbce654133b7f58928ce74629d5e68b974"), + enode.MustHexIDv0("0x472d19e5e9888368c02f24ebbbe0f2132096e7183d213ab65d96b8c03205f88398924af8876f3c615e08aa0f9a26c38911fda26d51c602c8d4f8f3cb866808d7"), + enode.MustHexIDv0("4f1f036e5e18cc812347d5073cbec2a8da7930de323063c39b0d4413a396e088bfa90e8c28174313d8d82e9a14bc0884b13a48fc28e619e44c48a49b4fd9f107"), + enode.MustHexIDv0("f18c596232d637409c6295abb1e720db99ffc12363a1eb8123d6f54af80423a5edd06f91115115a1dca1377e97b9031e2ddb864d34d9b3491d6fa07e8d9b951b"), + enode.MustHexIDv0("7a8f7a28ac1c4eaf98b2be890f372e5abc58ebe6d3aab47aedcb0076e34eb42882e926676ebab327a4ef4e2ea5c4296e9c7bc0991360cb44f52672631012db1b"), + enode.MustHexIDv0("9eeb448babf9e93449e831b91f98d9cbc0c2324fe8c43baac69d090717454f3f930713084713fe3a9f01e4ca59b80a0f2b41dbd6d531f414650bab0363e3691a"), + enode.MustHexIDv0("cc1d7314c15e30dc5587f675eb5f803b1a2d88bfe76cec591cec1ff678bc6abce98f40054325bdcb44fb83174f27d38a54fbce4846af8f027b333868bc5144a4"), + enode.MustHexIDv0("e4d99694be2fc8a53d8c2446f947aec1c7de3ee26f7cd43f4f6f77371f56f11156218dec32b51ddce470e97127624d330bb7a3237ba5f0d87d2d3166faf1035e"), + enode.MustHexIDv0("9c61f59f70296b6d494e7230888e58f19b13c5c6c85562e57e1fe02d0ff872b4957238c73559d017c8770b999891056aa6329dbf628bc19028d8f4d35ec35823"), } addrArr = []common.Address{ @@ -340,19 +340,19 @@ func build_staking_data_more(block uint64) { balance = new(big.Int).Add(balance, big.NewInt(int64(weight))) - randBuildFunc := func() (discover.NodeID, common.Address, error) { + randBuildFunc := func() (enode.IDv0, common.Address, error) { privateKey, err := crypto.GenerateKey() if nil != err { fmt.Printf("Failed to generate random NodeId private key: %v", err) - return discover.NodeID{}, common.ZeroAddr, err + return enode.IDv0{}, common.ZeroAddr, err } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) privateKey, err = crypto.GenerateKey() if nil != err { fmt.Printf("Failed to generate random Address private key: %v", err) - return discover.NodeID{}, common.ZeroAddr, err + return enode.IDv0{}, common.ZeroAddr, err } addr := crypto.PubkeyToAddress(privateKey.PublicKey) @@ -360,7 +360,7 @@ func build_staking_data_more(block uint64) { return nodeId, addr, nil } - var nodeId discover.NodeID + var nodeId enode.IDv0 var addr common.Address if i < 25 { @@ -493,19 +493,19 @@ func build_staking_data(genesisHash common.Hash) { balance = new(big.Int).Add(balance, big.NewInt(int64(weight))) - randBuildFunc := func() (discover.NodeID, common.Address, error) { + randBuildFunc := func() (enode.IDv0, common.Address, error) { privateKey, err := crypto.GenerateKey() if nil != err { fmt.Printf("Failed to generate random NodeId private key: %v", err) - return discover.NodeID{}, common.ZeroAddr, err + return enode.IDv0{}, common.ZeroAddr, err } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) privateKey, err = crypto.GenerateKey() if nil != err { fmt.Printf("Failed to generate random Address private key: %v", err) - return discover.NodeID{}, common.ZeroAddr, err + return enode.IDv0{}, common.ZeroAddr, err } addr := crypto.PubkeyToAddress(privateKey.PublicKey) @@ -513,7 +513,7 @@ func build_staking_data(genesisHash common.Hash) { return nodeId, addr, nil } - var nodeId discover.NodeID + var nodeId enode.IDv0 var addr common.Address if i < 25 { diff --git a/x/plugin/restricting_plugin.go b/x/plugin/restricting_plugin.go index 50d7f4a701..68f996f6f8 100644 --- a/x/plugin/restricting_plugin.go +++ b/x/plugin/restricting_plugin.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package plugin import ( @@ -23,14 +22,14 @@ import ( "sort" "sync" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/x/staking" "github.com/AlayaNetwork/Alaya-Go/x/gov" "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" - "github.com/AlayaNetwork/Alaya-Go/common/hexutil" "github.com/AlayaNetwork/Alaya-Go/common" @@ -90,7 +89,7 @@ func (rp *RestrictingPlugin) EndBlock(blockHash common.Hash, head *types.Header, } // Confirmed is empty function -func (rp *RestrictingPlugin) Confirmed(nodeId discover.NodeID, block *types.Block) error { +func (rp *RestrictingPlugin) Confirmed(nodeId enode.IDv0, block *types.Block) error { return nil } diff --git a/x/plugin/reward_plugin.go b/x/plugin/reward_plugin.go index 271abb2d9f..e84b50e2fa 100644 --- a/x/plugin/reward_plugin.go +++ b/x/plugin/reward_plugin.go @@ -14,19 +14,20 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package plugin import ( "encoding/json" "errors" "fmt" - "github.com/AlayaNetwork/Alaya-Go/x/gov" "math" "math/big" "sort" "sync" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/x/gov" + "github.com/AlayaNetwork/Alaya-Go/common/hexutil" "github.com/AlayaNetwork/Alaya-Go/rlp" @@ -35,8 +36,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" - "github.com/AlayaNetwork/Alaya-Go/x/staking" "github.com/AlayaNetwork/Alaya-Go/common" @@ -50,7 +49,7 @@ import ( type RewardMgrPlugin struct { db snapshotdb.DB - nodeID discover.NodeID + nodeID enode.IDv0 nodeADD common.NodeAddress stakingPlugin *StakingPlugin } @@ -144,11 +143,11 @@ func (rmp *RewardMgrPlugin) EndBlock(blockHash common.Hash, head *types.Header, } // Confirmed does nothing -func (rmp *RewardMgrPlugin) Confirmed(nodeId discover.NodeID, block *types.Block) error { +func (rmp *RewardMgrPlugin) Confirmed(nodeId enode.IDv0, block *types.Block) error { return nil } -func (rmp *RewardMgrPlugin) SetCurrentNodeID(nodeId discover.NodeID) { +func (rmp *RewardMgrPlugin) SetCurrentNodeID(nodeId enode.IDv0) { rmp.nodeID = nodeId add, err := xutil.NodeId2Addr(rmp.nodeID) if err != nil { @@ -332,7 +331,7 @@ func (rmp *RewardMgrPlugin) WithdrawDelegateReward(blockHash common.Hash, blockN return rewards, nil } -func (rmp *RewardMgrPlugin) GetDelegateReward(blockHash common.Hash, blockNum uint64, account common.Address, nodes []discover.NodeID, state xcom.StateDB) ([]reward.NodeDelegateRewardPresenter, error) { +func (rmp *RewardMgrPlugin) GetDelegateReward(blockHash common.Hash, blockNum uint64, account common.Address, nodes []enode.IDv0, state xcom.StateDB) ([]reward.NodeDelegateRewardPresenter, error) { log.Debug("Call RewardMgrPlugin: query delegate reward result begin", "account", account, "nodes", nodes, "num", blockNum) dls, err := rmp.stakingPlugin.db.GetDelegatesInfo(blockHash, account) @@ -344,7 +343,7 @@ func (rmp *RewardMgrPlugin) GetDelegateReward(blockHash common.Hash, blockNum ui return nil, reward.ErrDelegationNotFound } if len(nodes) > 0 { - nodeMap := make(map[discover.NodeID]struct{}) + nodeMap := make(map[enode.IDv0]struct{}) for _, node := range nodes { nodeMap[node] = struct{}{} } @@ -426,15 +425,15 @@ func (rmp *RewardMgrPlugin) rewardStakingByValidatorList(state xcom.StateDB, lis return nil } -func (rmp *RewardMgrPlugin) getBlockMinderAddress(blockHash common.Hash, head *types.Header) (discover.NodeID, common.NodeAddress, error) { +func (rmp *RewardMgrPlugin) getBlockMinderAddress(blockHash common.Hash, head *types.Header) (enode.IDv0, common.NodeAddress, error) { if blockHash == common.ZeroHash { return rmp.nodeID, rmp.nodeADD, nil } pk := head.CachePublicKey() if pk == nil { - return discover.ZeroNodeID, common.ZeroNodeAddr, errors.New("failed to get the public key of the block producer") + return enode.ZeroIDv0, common.ZeroNodeAddr, errors.New("failed to get the public key of the block producer") } - return discover.PubkeyID(pk), crypto.PubkeyToNodeAddress(*pk), nil + return enode.PublicKeyToIDv0(pk), crypto.PubkeyToNodeAddress(*pk), nil } // AllocatePackageBlock used for reward new block. it returns coinbase and error @@ -500,11 +499,11 @@ func NewDelegationInfoWithRewardPerList(delegationInfo *staking.DelegationInfo, return &DelegationInfoWithRewardPerList{delegationInfo, rewardPerList} } -func (rmp *RewardMgrPlugin) GetDelegateRewardPerList(blockHash common.Hash, nodeID discover.NodeID, stakingNum, fromEpoch, toEpoch uint64) ([]*reward.DelegateRewardPer, error) { +func (rmp *RewardMgrPlugin) GetDelegateRewardPerList(blockHash common.Hash, nodeID enode.IDv0, stakingNum, fromEpoch, toEpoch uint64) ([]*reward.DelegateRewardPer, error) { return getDelegateRewardPerList(blockHash, nodeID, stakingNum, fromEpoch, toEpoch, rmp.db) } -func getDelegateRewardPerList(blockHash common.Hash, nodeID discover.NodeID, stakingNum, fromEpoch, toEpoch uint64, db snapshotdb.DB) ([]*reward.DelegateRewardPer, error) { +func getDelegateRewardPerList(blockHash common.Hash, nodeID enode.IDv0, stakingNum, fromEpoch, toEpoch uint64, db snapshotdb.DB) ([]*reward.DelegateRewardPer, error) { keys := reward.DelegateRewardPerKeys(nodeID, stakingNum, fromEpoch, toEpoch) pers := make([]*reward.DelegateRewardPer, 0) for _, key := range keys { @@ -528,7 +527,7 @@ func getDelegateRewardPerList(blockHash common.Hash, nodeID discover.NodeID, sta return pers, nil } -func AppendDelegateRewardPer(blockHash common.Hash, nodeID discover.NodeID, stakingNum uint64, per *reward.DelegateRewardPer, db snapshotdb.DB) error { +func AppendDelegateRewardPer(blockHash common.Hash, nodeID enode.IDv0, stakingNum uint64, per *reward.DelegateRewardPer, db snapshotdb.DB) error { key := reward.DelegateRewardPerKey(nodeID, stakingNum, per.Epoch) list := reward.NewDelegateRewardPerList() val, err := db.Get(blockHash, key) @@ -554,7 +553,7 @@ func AppendDelegateRewardPer(blockHash common.Hash, nodeID discover.NodeID, stak return nil } -func UpdateDelegateRewardPer(blockHash common.Hash, nodeID discover.NodeID, stakingNum uint64, receives []reward.DelegateRewardReceipt, db snapshotdb.DB) error { +func UpdateDelegateRewardPer(blockHash common.Hash, nodeID enode.IDv0, stakingNum uint64, receives []reward.DelegateRewardReceipt, db snapshotdb.DB) error { if len(receives) == 0 { return nil } diff --git a/x/plugin/reward_plugin_test.go b/x/plugin/reward_plugin_test.go index f1913eb92a..61cca3911f 100644 --- a/x/plugin/reward_plugin_test.go +++ b/x/plugin/reward_plugin_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package plugin import ( @@ -24,6 +23,8 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/x/gov" "github.com/AlayaNetwork/Alaya-Go/core/types" @@ -33,7 +34,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/common/mock" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/rlp" @@ -58,7 +58,7 @@ func buildTestStakingData(epochStart, epochEnd uint64) (staking.ValidatorQueue, return nil, err } addr := crypto.PubkeyToNodeAddress(privateKey.PublicKey) - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) canTmp := &staking.Candidate{ CandidateBase: &staking.CandidateBase{ NodeId: nodeId, @@ -455,7 +455,7 @@ func TestSaveRewardDelegateRewardPer(t *testing.T) { }, nil, nil) type delegateInfo struct { - nodeID discover.NodeID + nodeID enode.IDv0 stakingNum uint64 currentReward, totalDelegateReward, totalDelegate *big.Int } @@ -692,7 +692,7 @@ func generateStk(rewardPer uint16, delegateTotal *big.Int, blockNumber uint64) ( if nil != err { panic(err) } - nodeID, add := discover.PubkeyID(&privateKey.PublicKey), crypto.PubkeyToAddress(privateKey.PublicKey) + nodeID, add := enode.PublicKeyToIDv0(&privateKey.PublicKey), crypto.PubkeyToAddress(privateKey.PublicKey) canBase.BenefitAddress = add canBase.NodeId = nodeID canBase.StakingBlockNum = 100 diff --git a/x/plugin/slashing_plugin.go b/x/plugin/slashing_plugin.go index 5786417d86..8fb6b59582 100644 --- a/x/plugin/slashing_plugin.go +++ b/x/plugin/slashing_plugin.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package plugin import ( @@ -23,10 +22,12 @@ import ( "encoding/hex" "encoding/json" "fmt" - "github.com/AlayaNetwork/Alaya-Go/rlp" "math/big" "sync" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/rlp" + "github.com/AlayaNetwork/Alaya-Go/x/gov" "github.com/AlayaNetwork/Alaya-Go/x/slashing" @@ -40,7 +41,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/core/types" "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/x/staking" "github.com/AlayaNetwork/Alaya-Go/x/xcom" "github.com/AlayaNetwork/Alaya-Go/x/xutil" @@ -62,7 +62,7 @@ var ( // Nodes with zero blocks will construct this structure and store it in the queue waiting for punishment. type WaitSlashingNode struct { - NodeId discover.NodeID + NodeId enode.IDv0 // The number of consensus rounds when the first zero block appeared Round uint64 // Used to record the number of times the node has zero blocks. @@ -143,7 +143,7 @@ func (sp *SlashingPlugin) BeginBlock(blockHash common.Hash, header *types.Header return errors.New("Failed to get CurrentActiveVersion") } // Stores all consensus nodes in the previous round and records whether each node has a production block in the previous round - validatorMap := make(map[discover.NodeID]bool) + validatorMap := make(map[enode.IDv0]bool) for _, validator := range preRoundVal.Arr { nodeId := validator.NodeId count := result[nodeId] @@ -179,11 +179,11 @@ func (sp *SlashingPlugin) EndBlock(blockHash common.Hash, header *types.Header, return nil } -func (sp *SlashingPlugin) Confirmed(nodeId discover.NodeID, block *types.Block) error { +func (sp *SlashingPlugin) Confirmed(nodeId enode.IDv0, block *types.Block) error { return nil } -func (sp *SlashingPlugin) zeroProduceProcess(blockHash common.Hash, header *types.Header, validatorMap map[discover.NodeID]bool, validatorQueue staking.ValidatorQueue) (staking.SlashQueue, error) { +func (sp *SlashingPlugin) zeroProduceProcess(blockHash common.Hash, header *types.Header, validatorMap map[enode.IDv0]bool, validatorQueue staking.ValidatorQueue) (staking.SlashQueue, error) { blockNumber := header.Number.Uint64() slashQueue := make(staking.SlashQueue, 0) waitSlashingNodeList, err := sp.getWaitSlashingNodeList(header.Number.Uint64(), blockHash) @@ -435,7 +435,7 @@ func (sp *SlashingPlugin) setWaitSlashingNodeList(blockNumber uint64, blockHash return nil } -func (sp *SlashingPlugin) getPackAmount(blockNumber uint64, blockHash common.Hash, nodeId discover.NodeID) (uint32, error) { +func (sp *SlashingPlugin) getPackAmount(blockNumber uint64, blockHash common.Hash, nodeId enode.IDv0) (uint32, error) { value, err := sp.db.Get(blockHash, buildKey(blockNumber, nodeId.Bytes())) if snapshotdb.NonDbNotFoundErr(err) { return 0, err @@ -488,8 +488,8 @@ func (sp *SlashingPlugin) switchEpoch(blockNumber uint64, blockHash common.Hash) } // Get the consensus rate of all nodes in the previous round -func (sp *SlashingPlugin) GetPrePackAmount(blockNumber uint64, parentHash common.Hash) (map[discover.NodeID]uint32, error) { - result := make(map[discover.NodeID]uint32) +func (sp *SlashingPlugin) GetPrePackAmount(blockNumber uint64, parentHash common.Hash) (map[enode.IDv0]uint32, error) { + result := make(map[enode.IDv0]uint32) prefixKey := buildPrefixByRound(xutil.CalculateRound(blockNumber) - 1) iter := sp.db.Ranking(parentHash, prefixKey, 0) @@ -661,23 +661,23 @@ func (sp *SlashingPlugin) Slash(evidence consensus.Evidence, blockHash common.Ha return nil } -func (sp *SlashingPlugin) CheckDuplicateSign(nodeId discover.NodeID, blockNumber uint64, dupType consensus.EvidenceType, stateDB xcom.StateDB) ([]byte, error) { +func (sp *SlashingPlugin) CheckDuplicateSign(nodeId enode.IDv0, blockNumber uint64, dupType consensus.EvidenceType, stateDB xcom.StateDB) ([]byte, error) { if value := sp.getSlashTxHash(nodeId, blockNumber, dupType, stateDB); len(value) > 0 { return value, nil } return nil, nil } -func (sp *SlashingPlugin) putSlashTxHash(nodeId discover.NodeID, blockNumber uint64, dupType consensus.EvidenceType, stateDB xcom.StateDB) { +func (sp *SlashingPlugin) putSlashTxHash(nodeId enode.IDv0, blockNumber uint64, dupType consensus.EvidenceType, stateDB xcom.StateDB) { stateDB.SetState(vm.SlashingContractAddr, duplicateSignKey(nodeId, blockNumber, dupType), stateDB.TxHash().Bytes()) } -func (sp *SlashingPlugin) getSlashTxHash(nodeId discover.NodeID, blockNumber uint64, dupType consensus.EvidenceType, stateDB xcom.StateDB) []byte { +func (sp *SlashingPlugin) getSlashTxHash(nodeId enode.IDv0, blockNumber uint64, dupType consensus.EvidenceType, stateDB xcom.StateDB) []byte { return stateDB.GetState(vm.SlashingContractAddr, duplicateSignKey(nodeId, blockNumber, dupType)) } // duplicate signature result key format addr+blockNumber+_+type -func duplicateSignKey(nodeId discover.NodeID, blockNumber uint64, dupType consensus.EvidenceType) []byte { +func duplicateSignKey(nodeId enode.IDv0, blockNumber uint64, dupType consensus.EvidenceType) []byte { return append(append(nodeId.Bytes(), common.Uint64ToBytes(blockNumber)...), common.Uint16ToBytes(uint16(dupType))...) } @@ -693,24 +693,24 @@ func buildPrefixByRound(round uint64) []byte { return append(packAmountPrefix, common.Uint64ToBytes(round)...) } -func getNodeId(prefix []byte, key []byte) (discover.NodeID, error) { +func getNodeId(prefix []byte, key []byte) (enode.IDv0, error) { key = key[len(prefix):] - nodeId, err := discover.BytesID(key) + nodeId, err := enode.BytesToIDv0(key) if nil != err { - return discover.NodeID{}, err + return enode.ZeroIDv0, err } return nodeId, nil } -func parseNodeId(header *types.Header) (discover.NodeID, error) { +func parseNodeId(header *types.Header) (enode.IDv0, error) { if xutil.IsWorker(header.Extra) { - return discover.PubkeyID(&SlashInstance().privateKey.PublicKey), nil + return enode.PublicKeyToIDv0(&SlashInstance().privateKey.PublicKey), nil } else { pk := header.CachePublicKey() if pk == nil { - return discover.NodeID{}, errors.New("failed to get the public key of the block producer") + return enode.ZeroIDv0, errors.New("failed to get the public key of the block producer") } - return discover.PubkeyID(pk), nil + return enode.PublicKeyToIDv0(pk), nil } } diff --git a/x/plugin/slashing_plugin_test.go b/x/plugin/slashing_plugin_test.go index 6506d2d2d3..e376f7ddb0 100644 --- a/x/plugin/slashing_plugin_test.go +++ b/x/plugin/slashing_plugin_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package plugin import ( @@ -25,6 +24,8 @@ import ( "math/big" "testing" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/AlayaNetwork/Alaya-Go/x/gov" @@ -45,7 +46,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" "github.com/AlayaNetwork/Alaya-Go/core/types" "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/x/staking" "github.com/AlayaNetwork/Alaya-Go/x/xcom" "github.com/AlayaNetwork/Alaya-Go/x/xutil" @@ -86,7 +86,7 @@ func buildStakingData(blockNumber uint64, blockHash common.Hash, pri *ecdsa.Priv pri = sk } - nodeIdA := discover.PubkeyID(&pri.PublicKey) + nodeIdA := enode.PublicKeyToIDv0(&pri.PublicKey) addrA, _ := xutil.NodeId2Addr(nodeIdA) nodeIdB := nodeIdArr[1] @@ -493,7 +493,7 @@ func TestSlashingPlugin_Slash(t *testing.T) { }` blockNumber = new(big.Int).Add(blockNumber, common.Big1) stakingAddr := common.MustBech32ToAddress("atx1r9tx0n00etv5c5smmlctlpg8jas7p78nmnfw8v") - stakingNodeId, err := discover.HexID("51c0559c065400151377d71acd7a17282a7c8abcfefdb11992dcecafde15e100b8e31e1a5e74834a04792d016f166c80b9923423fe280570e8131debf591d483") + stakingNodeId, err := enode.HexIDv0("51c0559c065400151377d71acd7a17282a7c8abcfefdb11992dcecafde15e100b8e31e1a5e74834a04792d016f166c80b9923423fe280570e8131debf591d483") if nil != err { t.Fatal(err) } @@ -546,7 +546,7 @@ func TestSlashingPlugin_Slash(t *testing.T) { if err := si.Slash(normalEvidence, common.ZeroHash, blockNumber.Uint64(), stateDB, anotherSender); nil != err { t.Fatal(err) } - slashNodeId, err := discover.HexID("c0b49363fa1c2a0d3c55cafec4955cb261a537afd4fe45ff21c7b84cba660d5157865d984c2d2a61b4df1d3d028634136d04030ed6a388b429eaa6e2bdefaed1") + slashNodeId, err := enode.HexIDv0("c0b49363fa1c2a0d3c55cafec4955cb261a537afd4fe45ff21c7b84cba660d5157865d984c2d2a61b4df1d3d028634136d04030ed6a388b429eaa6e2bdefaed1") if nil != err { t.Fatal(err) } @@ -608,7 +608,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { validatorQueue := make(staking.ValidatorQueue, 0) // The following uses multiple nodes to simulate a variety of different scenarios - validatorMap := make(map[discover.NodeID]bool) + validatorMap := make(map[enode.IDv0]bool) // Blocks were produced in the last round; removed from pending list // bits:1 -> delete validatorMap[nodeIdArr[0]] = true @@ -619,7 +619,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { if err != nil { panic(err) } - noSlashingNodeId := discover.PubkeyID(&nodePrivate.PublicKey) + noSlashingNodeId := enode.PublicKeyToIDv0(&nodePrivate.PublicKey) // Current round of production blocks; removed from pending list // bits: 1 -> delete validatorMap[noSlashingNodeId] = false @@ -727,7 +727,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { } // Third consensus round blockNumber.Add(blockNumber, new(big.Int).SetUint64(xutil.ConsensusSize())) - validatorMap = make(map[discover.NodeID]bool) + validatorMap = make(map[enode.IDv0]bool) validatorQueue = make(staking.ValidatorQueue, 0) validatorMap[nodeIdArr[0]] = false validatorQueue = append(validatorQueue, &staking.Validator{ @@ -753,7 +753,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { } // Fourth consensus round blockNumber.Add(blockNumber, new(big.Int).SetUint64(xutil.ConsensusSize())) - validatorMap = make(map[discover.NodeID]bool) + validatorMap = make(map[enode.IDv0]bool) validatorQueue = make(staking.ValidatorQueue, 0) validatorMap[nodeIdArr[0]] = true validatorQueue = append(validatorQueue, &staking.Validator{ @@ -767,7 +767,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { } // Fifth consensus round blockNumber.Add(blockNumber, new(big.Int).SetUint64(xutil.ConsensusSize())) - validatorMap = make(map[discover.NodeID]bool) + validatorMap = make(map[enode.IDv0]bool) validatorQueue = make(staking.ValidatorQueue, 0) validatorMap[nodeIdArr[2]] = false validatorMap[nodeIdArr[3]] = false @@ -799,7 +799,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { t.Fatal(err) } blockNumber.Add(blockNumber, new(big.Int).SetUint64(xutil.ConsensusSize())) - validatorMap = make(map[discover.NodeID]bool) + validatorMap = make(map[enode.IDv0]bool) validatorQueue = make(staking.ValidatorQueue, 0) validatorMap[nodeIdArr[1]] = false validatorMap[nodeIdArr[2]] = false @@ -827,7 +827,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { t.Fatal(err) } blockNumber.Add(blockNumber, new(big.Int).SetUint64(xutil.ConsensusSize())) - validatorMap = make(map[discover.NodeID]bool) + validatorMap = make(map[enode.IDv0]bool) validatorQueue = make(staking.ValidatorQueue, 0) validatorMap[nodeIdArr[5]] = false validatorQueue = append(validatorQueue, &staking.Validator{ @@ -848,7 +848,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { t.Errorf("waitSlashingNodeList amount: have %v, want %v", len(waitSlashingNodeList), 0) return } - expectMap := make(map[discover.NodeID]*WaitSlashingNode) + expectMap := make(map[enode.IDv0]*WaitSlashingNode) expectMap[nodeIdArr[1]] = &WaitSlashingNode{ CountBit: 1, Round: 5, diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index ec9976744c..c36d9e2ff6 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -27,6 +27,10 @@ import ( "strconv" "sync" + "github.com/AlayaNetwork/Alaya-Go/crypto" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/x/reward" "github.com/AlayaNetwork/Alaya-Go/common/math" @@ -45,7 +49,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/crypto/vrf" "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/x/staking" "github.com/AlayaNetwork/Alaya-Go/x/xcom" "github.com/AlayaNetwork/Alaya-Go/x/xutil" @@ -181,7 +184,7 @@ func (sk *StakingPlugin) EndBlock(blockHash common.Hash, header *types.Header, s return nil } -func (sk *StakingPlugin) Confirmed(nodeId discover.NodeID, block *types.Block) error { +func (sk *StakingPlugin) Confirmed(nodeId enode.IDv0, block *types.Block) error { if xutil.IsElection(block.NumberU64()) { @@ -202,7 +205,7 @@ func (sk *StakingPlugin) Confirmed(nodeId discover.NodeID, block *types.Block) e diff := make(staking.ValidatorQueue, 0) var isCurr, isNext bool - currMap := make(map[discover.NodeID]struct{}) + currMap := make(map[enode.IDv0]struct{}) for _, v := range current.Arr { currMap[v.NodeId] = struct{}{} if nodeId == v.NodeId { @@ -240,7 +243,11 @@ func (sk *StakingPlugin) Confirmed(nodeId discover.NodeID, block *types.Block) e func (sk *StakingPlugin) addConsensusNode(nodes staking.ValidatorQueue) { for _, node := range nodes { - if err := sk.eventMux.Post(cbfttypes.AddValidatorEvent{NodeID: node.NodeId}); nil != err { + pub, err := node.NodeId.Pubkey() + if err != nil { + panic(err) + } + if err := sk.eventMux.Post(cbfttypes.AddValidatorEvent{Node: enode.NewV4(pub, nil, 0, 0)}); nil != err { log.Error("post AddValidatorEvent failed", "nodeId", node.NodeId.TerminalString(), "err", err) } } @@ -789,12 +796,12 @@ func (sk *StakingPlugin) GetDelegatesInfo(blockHash common.Hash, delAddr common. } func (sk *StakingPlugin) GetDelegateInfo(blockHash common.Hash, delAddr common.Address, - nodeId discover.NodeID, stakeBlockNumber uint64) (*staking.Delegation, error) { + nodeId enode.IDv0, stakeBlockNumber uint64) (*staking.Delegation, error) { return sk.db.GetDelegateStore(blockHash, delAddr, nodeId, stakeBlockNumber) } func (sk *StakingPlugin) GetDelegateExInfo(blockHash common.Hash, delAddr common.Address, - nodeId discover.NodeID, stakeBlockNumber uint64) (*staking.DelegationEx, error) { + nodeId enode.IDv0, stakeBlockNumber uint64) (*staking.DelegationEx, error) { del, err := sk.db.GetDelegateStore(blockHash, delAddr, nodeId, stakeBlockNumber) if nil != err { @@ -815,7 +822,7 @@ func (sk *StakingPlugin) GetDelegateExInfo(blockHash common.Hash, delAddr common } func (sk *StakingPlugin) GetDelegateExCompactInfo(blockHash common.Hash, blockNumber uint64, delAddr common.Address, - nodeId discover.NodeID, stakeBlockNumber uint64) (*staking.DelegationEx, error) { + nodeId enode.IDv0, stakeBlockNumber uint64) (*staking.DelegationEx, error) { del, err := sk.db.GetDelegateStore(blockHash, delAddr, nodeId, stakeBlockNumber) if nil != err { @@ -841,13 +848,13 @@ func (sk *StakingPlugin) GetDelegateExCompactInfo(blockHash common.Hash, blockNu } func (sk *StakingPlugin) GetDelegateInfoByIrr(delAddr common.Address, - nodeId discover.NodeID, stakeBlockNumber uint64) (*staking.Delegation, error) { + nodeId enode.IDv0, stakeBlockNumber uint64) (*staking.Delegation, error) { return sk.db.GetDelegateStoreByIrr(delAddr, nodeId, stakeBlockNumber) } func (sk *StakingPlugin) GetDelegateExInfoByIrr(delAddr common.Address, - nodeId discover.NodeID, stakeBlockNumber uint64) (*staking.DelegationEx, error) { + nodeId enode.IDv0, stakeBlockNumber uint64) (*staking.DelegationEx, error) { del, err := sk.db.GetDelegateStoreByIrr(delAddr, nodeId, stakeBlockNumber) if nil != err { @@ -949,7 +956,7 @@ func (sk *StakingPlugin) Delegate(state xcom.StateDB, blockHash common.Hash, blo } func (sk *StakingPlugin) WithdrewDelegation(state xcom.StateDB, blockHash common.Hash, blockNumber, amount *big.Int, - delAddr common.Address, nodeId discover.NodeID, stakingBlockNum uint64, del *staking.Delegation, delegateRewardPerList []*reward.DelegateRewardPer) (*big.Int, error) { + delAddr common.Address, nodeId enode.IDv0, stakingBlockNum uint64, del *staking.Delegation, delegateRewardPerList []*reward.DelegateRewardPer) (*big.Int, error) { issueIncome := new(big.Int) canAddr, err := xutil.NodeId2Addr(nodeId) if nil != err { @@ -1370,7 +1377,7 @@ func (sk *StakingPlugin) GetVerifierList(blockHash common.Hash, blockNumber uint return queue, nil } -func (sk *StakingPlugin) IsCurrVerifier(blockHash common.Hash, blockNumber uint64, nodeId discover.NodeID, isCommit bool) (bool, error) { +func (sk *StakingPlugin) IsCurrVerifier(blockHash common.Hash, blockNumber uint64, nodeId enode.IDv0, isCommit bool) (bool, error) { verifierList, err := sk.getVerifierList(blockHash, blockNumber, isCommit) if nil != err { @@ -1387,7 +1394,7 @@ func (sk *StakingPlugin) IsCurrVerifier(blockHash common.Hash, blockNumber uint6 return flag, nil } -func (sk *StakingPlugin) ListVerifierNodeID(blockHash common.Hash, blockNumber uint64) ([]discover.NodeID, error) { +func (sk *StakingPlugin) ListVerifierNodeID(blockHash common.Hash, blockNumber uint64) ([]enode.IDv0, error) { verifierList, err := sk.getVerifierList(blockHash, blockNumber, QueryStartNotIrr) if nil != err { @@ -1402,7 +1409,7 @@ func (sk *StakingPlugin) ListVerifierNodeID(blockHash common.Hash, blockNumber u return nil, staking.ErrBlockNumberDisordered } - queue := make([]discover.NodeID, len(verifierList.Arr)) + queue := make([]enode.IDv0, len(verifierList.Arr)) for i, v := range verifierList.Arr { queue[i] = v.NodeId @@ -1590,14 +1597,14 @@ func (sk *StakingPlugin) GetCandidateONRound(blockHash common.Hash, blockNumber return queue, nil } -func (sk *StakingPlugin) ListCurrentValidatorID(blockHash common.Hash, blockNumber uint64) ([]discover.NodeID, error) { +func (sk *StakingPlugin) ListCurrentValidatorID(blockHash common.Hash, blockNumber uint64) ([]enode.IDv0, error) { arr, err := sk.getCurrValList(blockHash, blockNumber, QueryStartNotIrr) if nil != err { return nil, err } - queue := make([]discover.NodeID, len(arr.Arr)) + queue := make([]enode.IDv0, len(arr.Arr)) for i, candidate := range arr.Arr { queue[i] = candidate.NodeId @@ -1605,7 +1612,7 @@ func (sk *StakingPlugin) ListCurrentValidatorID(blockHash common.Hash, blockNumb return queue, err } -func (sk *StakingPlugin) IsCurrValidator(blockHash common.Hash, blockNumber uint64, nodeId discover.NodeID, isCommit bool) (bool, error) { +func (sk *StakingPlugin) IsCurrValidator(blockHash common.Hash, blockNumber uint64, nodeId enode.IDv0, isCommit bool) (bool, error) { validatorArr, err := sk.getCurrValList(blockHash, blockNumber, QueryStartNotIrr) if nil != err { @@ -1673,7 +1680,7 @@ func (sk *StakingPlugin) GetCanBaseList(blockHash common.Hash, blockNumber uint6 return queue, nil } -func (sk *StakingPlugin) IsCandidate(blockHash common.Hash, nodeId discover.NodeID, isCommit bool) (bool, error) { +func (sk *StakingPlugin) IsCandidate(blockHash common.Hash, nodeId enode.IDv0, isCommit bool) (bool, error) { var can *staking.Candidate addr, err := xutil.NodeId2Addr(nodeId) @@ -1716,7 +1723,7 @@ func (sk *StakingPlugin) GetRelatedListByDelAddr(blockHash common.Hash, addr com prefixLen := len(staking.DelegateKeyPrefix) - nodeIdLen := discover.NodeIDBits / 8 + nodeIdLen := crypto.PubkeyBytesBits / 8 // delAddr delAddrByte := key[prefixLen : prefixLen+common.AddressLength] @@ -1724,7 +1731,7 @@ func (sk *StakingPlugin) GetRelatedListByDelAddr(blockHash common.Hash, addr com // nodeId nodeIdByte := key[prefixLen+common.AddressLength : prefixLen+common.AddressLength+nodeIdLen] - nodeId := discover.MustBytesID(nodeIdByte) + nodeId := enode.MustBytesToIDv0(nodeIdByte) // stakenum stakeNumByte := key[prefixLen+common.AddressLength+nodeIdLen:] @@ -1784,7 +1791,7 @@ func (sk *StakingPlugin) Election(blockHash common.Hash, header *types.Header, s invalidLen := 0 // the num that the can need to remove // 收集 失效的 can集合 (被惩罚的 + 主动解除质押的 + 版本号低的) - invalidCan := make(map[discover.NodeID]struct{}) + invalidCan := make(map[enode.IDv0]struct{}) // 收集需要被优先移除的 can集合 (被惩罚的 + 版本号低的 + 主动撤销且不在当前101人中的<一般是处于跨epoch时处理>) removeCans := make(staking.NeedRemoveCans) // the candidates need to remove @@ -1793,7 +1800,7 @@ func (sk *StakingPlugin) Election(blockHash common.Hash, header *types.Header, s // 收集 主动解除质押 (但 没有被 惩罚过的, 主要用来做 过滤的, 使之继续保留在 当前epoch 101人中) withdrewCans := make(staking.CandidateMap) // the candidates had withdrew // 其实 和 withdrewCans 是对应的 (可以考虑合成一个) - withdrewQueue := make([]discover.NodeID, 0) + withdrewQueue := make([]enode.IDv0, 0) // 收集 低出块率的 (现有的 代码逻辑 基本不会进入这个, 最后为 空) lowRatioValidAddrs := make([]common.NodeAddress, 0) // The addr of candidate that need to clean lowRatio status @@ -1822,7 +1829,7 @@ func (sk *StakingPlugin) Election(blockHash common.Hash, header *types.Header, s } // 收集当前的 (验证人Id => Power) - currMap := make(map[discover.NodeID]*big.Int, len(curr.Arr)) + currMap := make(map[enode.IDv0]*big.Int, len(curr.Arr)) // 验证人信息 (基本上和 curr.Arr 一致) currqueen := make([]*staking.Validator, 0) for _, v := range curr.Arr { @@ -2170,9 +2177,9 @@ func (sk *StakingPlugin) SlashCandidates(state xcom.StateDB, blockHash common.Ha // Nodes that need to be deleted from the candidate list // Keep governance votes that have been voted - invalidNodeIdMap := make(map[discover.NodeID]struct{}, 0) + invalidNodeIdMap := make(map[enode.IDv0]struct{}, 0) // Need to remove eligibility to govern voting - invalidRemoveGovNodeIdMap := make(map[discover.NodeID]struct{}, 0) + invalidRemoveGovNodeIdMap := make(map[enode.IDv0]struct{}, 0) for _, slashItem := range queue { needRemove, err := sk.toSlash(state, blockNumber, blockHash, slashItem) @@ -2418,7 +2425,7 @@ func (sk *StakingPlugin) toSlash(state xcom.StateDB, blockNumber uint64, blockHa return needRemove, nil } -func (sk *StakingPlugin) removeFromVerifiers(blockNumber uint64, blockHash common.Hash, slashNodeIdMap map[discover.NodeID]struct{}) error { +func (sk *StakingPlugin) removeFromVerifiers(blockNumber uint64, blockHash common.Hash, slashNodeIdMap map[enode.IDv0]struct{}) error { verifier, err := sk.getVerifierList(blockHash, blockNumber, QueryStartNotIrr) if nil != err { log.Error("Failed to SlashCandidates: Query Verifier List is failed", "blockNumber", blockNumber, @@ -2540,7 +2547,7 @@ func slashBalanceFn(slashAmount, canBalance *big.Int, isNotify bool, return slashAmountTmp, balanceTmp, nil } -func (sk *StakingPlugin) ProposalPassedNotify(blockHash common.Hash, blockNumber uint64, nodeIds []discover.NodeID, +func (sk *StakingPlugin) ProposalPassedNotify(blockHash common.Hash, blockNumber uint64, nodeIds []enode.IDv0, programVersion uint32, state xcom.StateDB) error { log.Info("Call ProposalPassedNotify to promote candidate programVersion", "blockNumber", blockNumber, @@ -2616,7 +2623,7 @@ func (sk *StakingPlugin) ProposalPassedNotify(blockHash common.Hash, blockNumber return nil } -func (sk *StakingPlugin) DeclarePromoteNotify(blockHash common.Hash, blockNumber uint64, nodeId discover.NodeID, +func (sk *StakingPlugin) DeclarePromoteNotify(blockHash common.Hash, blockNumber uint64, nodeId enode.IDv0, programVersion uint32) error { log.Info("Call DeclarePromoteNotify to promote candidate programVersion", "blockNumber", blockNumber, @@ -2693,7 +2700,7 @@ func (sk *StakingPlugin) GetValidator(blockNumber uint64) (*cbfttypes.Validators } // NOTE: Verify that it is the validator of the current Epoch -func (sk *StakingPlugin) IsCandidateNode(nodeID discover.NodeID) bool { +func (sk *StakingPlugin) IsCandidateNode(nodeID enode.IDv0) bool { indexs, err := sk.db.GetEpochValIndexByIrr() if nil != err { @@ -2734,11 +2741,11 @@ func buildCbftValidators(start uint64, arr staking.ValidatorQueue) *cbfttypes.Va Index: uint32(i), Address: v.NodeAddress, PubKey: pubKey, - NodeID: v.NodeId, + NodeID: enode.PubkeyToIDV4(pubKey), BlsPubKey: blsPk, } - valMap[v.NodeId] = vn + valMap[v.NodeId.ID()] = vn } res := &cbfttypes.Validators{ @@ -3568,7 +3575,7 @@ func (sk *StakingPlugin) setVerifierListByIndex(blockNumber uint64, blockHash co return nil } -func (sk *StakingPlugin) addErrorAccountUnStakeItem(blockNumber uint64, blockHash common.Hash, nodeId discover.NodeID, canAddr common.NodeAddress, stakingBlockNum uint64) error { +func (sk *StakingPlugin) addErrorAccountUnStakeItem(blockNumber uint64, blockHash common.Hash, nodeId enode.IDv0, canAddr common.NodeAddress, stakingBlockNum uint64) error { targetEpoch := xutil.CalculateEpoch(blockNumber) + 1 if err := sk.db.AddUnStakeItemStore(blockHash, targetEpoch, canAddr, stakingBlockNum, false); nil != err { return err @@ -3579,7 +3586,7 @@ func (sk *StakingPlugin) addErrorAccountUnStakeItem(blockNumber uint64, blockHas } func (sk *StakingPlugin) addUnStakeItem(state xcom.StateDB, blockNumber uint64, blockHash common.Hash, epoch uint64, - nodeId discover.NodeID, canAddr common.NodeAddress, stakingBlockNum uint64) error { + nodeId enode.IDv0, canAddr common.NodeAddress, stakingBlockNum uint64) error { endVoteNum, err := gov.GetMaxEndVotingBlock(nodeId, blockHash, state) if nil != err { @@ -3614,7 +3621,7 @@ func (sk *StakingPlugin) addUnStakeItem(state xcom.StateDB, blockNumber uint64, return nil } -func (sk *StakingPlugin) addRecoveryUnStakeItem(blockNumber uint64, blockHash common.Hash, nodeId discover.NodeID, +func (sk *StakingPlugin) addRecoveryUnStakeItem(blockNumber uint64, blockHash common.Hash, nodeId enode.IDv0, canAddr common.NodeAddress, stakingBlockNum uint64) error { duration, err := gov.GovernZeroProduceFreezeDuration(blockNumber, blockHash) diff --git a/x/plugin/staking_plugin_test.go b/x/plugin/staking_plugin_test.go index 07ac5699ee..b8c4977f30 100644 --- a/x/plugin/staking_plugin_test.go +++ b/x/plugin/staking_plugin_test.go @@ -28,6 +28,8 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/crypto/vrf" "github.com/AlayaNetwork/Alaya-Go/x/gov" @@ -50,7 +52,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" "github.com/AlayaNetwork/Alaya-Go/event" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/AlayaNetwork/Alaya-Go/x/staking" "github.com/AlayaNetwork/Alaya-Go/x/xcom" @@ -145,7 +146,7 @@ func buildPrepareData(genesis *types.Block, t *testing.T) (*types.Header, error) return nil, err } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) privateKey, err = crypto.GenerateKey() if nil != err { @@ -324,7 +325,7 @@ func buildPrepareData(genesis *types.Block, t *testing.T) (*types.Header, error) t.Errorf("Failed to generate random Address private key: %v", err) return nil, err } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) currentHash := crypto.Keccak256Hash([]byte(nodeId.String())) newNumber := big.NewInt(int64(xutil.ConsensusSize() - xcom.ElectionDistance())) // 50 preNum1 := new(big.Int).Sub(newNumber, big.NewInt(1)) @@ -496,7 +497,7 @@ func TestStakingPlugin_EndBlock(t *testing.T) { return } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) currentHash := crypto.Keccak256Hash([]byte(nodeId.String())) currentNumber := big.NewInt(1) @@ -521,7 +522,7 @@ func TestStakingPlugin_EndBlock(t *testing.T) { return } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) privateKey, err = crypto.GenerateKey() if !assert.Nil(t, err, fmt.Sprintf("Failed to generate random Address private key: %v", err)) { @@ -742,7 +743,7 @@ func TestStakingPlugin_EndBlock(t *testing.T) { t.Errorf("Failed to generate random Address private key: %v", err) return } - nodeId2 := discover.PubkeyID(&privateKey2.PublicKey) + nodeId2 := enode.PublicKeyToIDv0(&privateKey2.PublicKey) currentHash = crypto.Keccak256Hash([]byte(nodeId2.String())) /** @@ -813,7 +814,7 @@ func TestStakingPlugin_Confirmed(t *testing.T) { return } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) currentHash := crypto.Keccak256Hash([]byte(nodeId.String())) currentNumber := big.NewInt(1) @@ -839,7 +840,7 @@ func TestStakingPlugin_Confirmed(t *testing.T) { return } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) privateKey, err = crypto.GenerateKey() if !assert.Nil(t, err, fmt.Sprintf("Failed to generate random Address private key: %v", err)) { @@ -2099,7 +2100,7 @@ func TestStakingPlugin_ElectNextVerifierList(t *testing.T) { return } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) privateKey, err = crypto.GenerateKey() if nil != err { @@ -2286,7 +2287,7 @@ func TestStakingPlugin_Election(t *testing.T) { return } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) privateKey, err = crypto.GenerateKey() if nil != err { @@ -2497,7 +2498,7 @@ func TestStakingPlugin_SlashCandidates(t *testing.T) { return } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) privateKey, err = crypto.GenerateKey() if nil != err { @@ -2810,7 +2811,7 @@ func TestStakingPlugin_DeclarePromoteNotify(t *testing.T) { return } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) privateKey, err = crypto.GenerateKey() if nil != err { @@ -2918,7 +2919,7 @@ func TestStakingPlugin_ProposalPassedNotify(t *testing.T) { validatorQueue := make(staking.ValidatorQueue, 0) - nodeIdArr := make([]discover.NodeID, 0) + nodeIdArr := make([]enode.IDv0, 0) for i := 0; i < 1000; i++ { var index int @@ -2942,7 +2943,7 @@ func TestStakingPlugin_ProposalPassedNotify(t *testing.T) { return } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) privateKey, err = crypto.GenerateKey() if nil != err { @@ -3314,7 +3315,7 @@ func TestStakingPlugin_IsCandidate(t *testing.T) { return } - nodeIdArr := make([]discover.NodeID, 0) + nodeIdArr := make([]enode.IDv0, 0) for i := 0; i < 1000; i++ { @@ -3339,7 +3340,7 @@ func TestStakingPlugin_IsCandidate(t *testing.T) { return } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) privateKey, err = crypto.GenerateKey() if nil != err { @@ -3607,7 +3608,7 @@ func TestStakingPlugin_ProbabilityElection(t *testing.T) { var blsKey bls.SecretKey blsKey.SetByCSPRNG() privKey, _ := ecdsa.GenerateKey(curve, rand.Reader) - nodeId := discover.PubkeyID(&privKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privKey.PublicKey) addr := crypto.PubkeyToNodeAddress(privKey.PublicKey) var blsKeyHex bls.PublicKeyHex @@ -3661,7 +3662,7 @@ func TestStakingPlugin_ProbabilityElectionDifferentWeights(t *testing.T) { var blsKey bls.SecretKey blsKey.SetByCSPRNG() privKey, _ := ecdsa.GenerateKey(curve, rand.Reader) - nodeId := discover.PubkeyID(&privKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privKey.PublicKey) addr := crypto.PubkeyToNodeAddress(privKey.PublicKey) var blsKeyHex bls.PublicKeyHex @@ -3736,7 +3737,7 @@ func TestStakingPlugin_RandomOrderValidatorQueue(t *testing.T) { dataList = append(dataList, data) tempPrivateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - nodeId := discover.PubkeyID(&tempPrivateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&tempPrivateKey.PublicKey) addr := crypto.PubkeyToNodeAddress(tempPrivateKey.PublicKey) v := &staking.Validator{ NodeAddress: addr, @@ -3808,7 +3809,7 @@ func Test_IteratorCandidate(t *testing.T) { return } - nodeId := discover.PubkeyID(&privateKey.PublicKey) + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) privateKey, err = crypto.GenerateKey() if nil != err { diff --git a/x/reward/reward_db_key.go b/x/reward/reward_db_key.go index cad2338806..9adcf3ecb0 100644 --- a/x/reward/reward_db_key.go +++ b/x/reward/reward_db_key.go @@ -14,14 +14,12 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package reward import ( + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/x/xutil" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" - "github.com/AlayaNetwork/Alaya-Go/common" ) @@ -49,7 +47,7 @@ func HistoryBalancePrefix(year uint32) []byte { return append(LastYearEndBalancePrefix, common.Uint32ToBytes(year)...) } -func DelegateRewardPerKey(nodeID discover.NodeID, stakingNum, epoch uint64) []byte { +func DelegateRewardPerKey(nodeID enode.IDv0, stakingNum, epoch uint64) []byte { index := uint32(epoch / DelegateRewardPerLength) add, err := xutil.NodeId2Addr(nodeID) if err != nil { @@ -66,7 +64,7 @@ func DelegateRewardPerKey(nodeID discover.NodeID, stakingNum, epoch uint64) []by return keyAdd } -func DelegateRewardPerKeys(nodeID discover.NodeID, stakingNum, fromEpoch, toEpoch uint64) [][]byte { +func DelegateRewardPerKeys(nodeID enode.IDv0, stakingNum, fromEpoch, toEpoch uint64) [][]byte { indexFrom := uint32(fromEpoch / DelegateRewardPerLength) indexTo := uint32(toEpoch / DelegateRewardPerLength) add, err := xutil.NodeId2Addr(nodeID) diff --git a/x/reward/rward_test.go b/x/reward/reward_test.go similarity index 91% rename from x/reward/rward_test.go rename to x/reward/reward_test.go index 879628740c..497591b5a0 100644 --- a/x/reward/rward_test.go +++ b/x/reward/reward_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package reward import ( @@ -22,7 +21,8 @@ import ( "math/big" "testing" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -66,7 +66,7 @@ func TestSize(t *testing.T) { delegate := new(big.Int).Mul(new(big.Int).SetInt64(10000000), big.NewInt(params.ATP)) reward, _ := new(big.Int).SetString("135840374364973262032076", 10) per := new(big.Int).Div(reward, delegate) - key := DelegateRewardPerKey(discover.MustHexID("0aa9805681d8f77c05f317efc141c97d5adb511ffb51f5a251d2d7a4a3a96d9a12adf39f06b702f0ccdff9eddc1790eb272dca31b0c47751d49b5931c58701e7"), 100, 10) + key := DelegateRewardPerKey(enode.MustHexIDv0("0aa9805681d8f77c05f317efc141c97d5adb511ffb51f5a251d2d7a4a3a96d9a12adf39f06b702f0ccdff9eddc1790eb272dca31b0c47751d49b5931c58701e7"), 100, 10) list := NewDelegateRewardPerList() for i := 0; i < DelegateRewardPerLength; i++ { diff --git a/x/reward/reward_type.go b/x/reward/reward_type.go index 6130891f02..59911cb6bc 100644 --- a/x/reward/reward_type.go +++ b/x/reward/reward_type.go @@ -14,17 +14,17 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package reward import ( "encoding/json" "math/big" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/common/hexutil" "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) func NewDelegateRewardPer(epoch uint64, totalReward, totalDelegate *big.Int) *DelegateRewardPer { @@ -107,15 +107,15 @@ func (d *DelegateRewardPerList) IsChange() bool { } type NodeDelegateReward struct { - NodeID discover.NodeID `json:"nodeID"` - StakingNum uint64 `json:"stakingNum"` - Reward *big.Int `json:"reward" rlp:"nil"` + NodeID enode.IDv0 `json:"nodeID"` + StakingNum uint64 `json:"stakingNum"` + Reward *big.Int `json:"reward" rlp:"nil"` } type NodeDelegateRewardPresenter struct { - NodeID discover.NodeID `json:"nodeID" ` - Reward *hexutil.Big `json:"reward" ` - StakingNum uint64 `json:"stakingNum"` + NodeID enode.IDv0 `json:"nodeID" ` + Reward *hexutil.Big `json:"reward" ` + StakingNum uint64 `json:"stakingNum"` } type DelegateRewardReceipt struct { diff --git a/x/staking/staking_db.go b/x/staking/staking_db.go index 60f0bf866b..5f30f28e76 100644 --- a/x/staking/staking_db.go +++ b/x/staking/staking_db.go @@ -14,17 +14,17 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package staking import ( "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -417,7 +417,7 @@ func (db *StakingDB) DelUnStakeItemStore(blockHash common.Hash, epoch, index uin // about delegate ... -func (db *StakingDB) GetDelegateStore(blockHash common.Hash, delAddr common.Address, nodeId discover.NodeID, stakeBlockNumber uint64) (*Delegation, error) { +func (db *StakingDB) GetDelegateStore(blockHash common.Hash, delAddr common.Address, nodeId enode.IDv0, stakeBlockNumber uint64) (*Delegation, error) { key := GetDelegateKey(delAddr, nodeId, stakeBlockNumber) @@ -434,7 +434,7 @@ func (db *StakingDB) GetDelegateStore(blockHash common.Hash, delAddr common.Addr return &del, nil } -func (db *StakingDB) GetDelegateStoreByIrr(delAddr common.Address, nodeId discover.NodeID, stakeBlockNumber uint64) (*Delegation, error) { +func (db *StakingDB) GetDelegateStoreByIrr(delAddr common.Address, nodeId enode.IDv0, stakeBlockNumber uint64) (*Delegation, error) { key := GetDelegateKey(delAddr, nodeId, stakeBlockNumber) delByte, err := db.getFromCommitted(key) @@ -464,7 +464,7 @@ func (db *StakingDB) GetDelegateStoreBySuffix(blockHash common.Hash, keySuffix [ } type DelegationInfo struct { - NodeID discover.NodeID + NodeID enode.IDv0 StakeBlockNumber uint64 Delegation *Delegation } @@ -496,7 +496,7 @@ func (db *StakingDB) GetDelegatesInfo(blockHash common.Hash, delAddr common.Addr return infos, nil } -func (db *StakingDB) SetDelegateStore(blockHash common.Hash, delAddr common.Address, nodeId discover.NodeID, +func (db *StakingDB) SetDelegateStore(blockHash common.Hash, delAddr common.Address, nodeId enode.IDv0, stakeBlockNumber uint64, del *Delegation) error { key := GetDelegateKey(delAddr, nodeId, stakeBlockNumber) @@ -519,7 +519,7 @@ func (db *StakingDB) SetDelegateStoreBySuffix(blockHash common.Hash, suffix []by return db.put(blockHash, key, delByte) } -func (db *StakingDB) DelDelegateStore(blockHash common.Hash, delAddr common.Address, nodeId discover.NodeID, +func (db *StakingDB) DelDelegateStore(blockHash common.Hash, delAddr common.Address, nodeId enode.IDv0, stakeBlockNumber uint64) error { key := GetDelegateKey(delAddr, nodeId, stakeBlockNumber) diff --git a/x/staking/staking_db_key.go b/x/staking/staking_db_key.go index a8c3021e7b..2c41843f2a 100644 --- a/x/staking/staking_db_key.go +++ b/x/staking/staking_db_key.go @@ -14,17 +14,17 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package staking import ( "math/big" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/x/xutil" "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/common/math" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) const ( @@ -81,7 +81,7 @@ func CanMutableKeyBySuffix(addr []byte) []byte { } // the candidate power key -func TallyPowerKey(programVersion uint32, shares *big.Int, stakeBlockNum uint64, stakeTxIndex uint32, nodeID discover.NodeID) []byte { +func TallyPowerKey(programVersion uint32, shares *big.Int, stakeBlockNum uint64, stakeTxIndex uint32, nodeID enode.IDv0) []byte { // Only sort Major and Minor // eg. 1.1.x => 1.1.0 @@ -137,7 +137,7 @@ func GetUnStakeItemKey(epoch, index uint64) []byte { return key } -func GetDelegateKey(delAddr common.Address, nodeId discover.NodeID, stakeBlockNumber uint64) []byte { +func GetDelegateKey(delAddr common.Address, nodeId enode.IDv0, stakeBlockNumber uint64) []byte { delAddrByte := delAddr.Bytes() nodeIdByte := nodeId.Bytes() @@ -158,12 +158,12 @@ func GetDelegateKey(delAddr common.Address, nodeId discover.NodeID, stakeBlockNu } //notice this assume key must right -func DecodeDelegateKey(key []byte) (delAddr common.Address, nodeId discover.NodeID, stakeBlockNumber uint64) { +func DecodeDelegateKey(key []byte) (delAddr common.Address, nodeId enode.IDv0, stakeBlockNumber uint64) { delegateKeyPrefixLength := len(DelegateKeyPrefix) delAddrLength := len(delAddr) + delegateKeyPrefixLength nodeIdLength := len(nodeId) + delAddrLength delAddr = common.BytesToAddress(key[delegateKeyPrefixLength:delAddrLength]) - nodeId = discover.MustBytesID(key[delAddrLength:nodeIdLength]) + nodeId = enode.MustBytesToIDv0(key[delAddrLength:nodeIdLength]) stakeBlockNumber = common.BytesToUint64(key[nodeIdLength:]) return } diff --git a/x/staking/staking_types.go b/x/staking/staking_types.go index d2635f26d8..7e635caaf1 100644 --- a/x/staking/staking_types.go +++ b/x/staking/staking_types.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package staking import ( @@ -22,13 +21,14 @@ import ( "math/big" "strings" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/x/xutil" "github.com/AlayaNetwork/Alaya-Go/common/hexutil" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" ) const ( @@ -167,7 +167,7 @@ func (can *Candidate) IsEmpty() bool { } type CandidateBase struct { - NodeId discover.NodeID + NodeId enode.IDv0 // bls public key BlsPubKey bls.PublicKeyHex // The account used to initiate the staking @@ -401,7 +401,7 @@ func (can *CandidateMutable) IsInvalidWithdrew() bool { // Display amount field using 0x hex type CandidateHex struct { - NodeId discover.NodeID + NodeId enode.IDv0 BlsPubKey bls.PublicKeyHex StakingAddress common.Address BenefitAddress common.Address @@ -574,7 +574,7 @@ type Validator struct { ValidatorTerm uint32 // Validator's term in the consensus round StakingBlockNum uint64 NodeAddress common.NodeAddress - NodeId discover.NodeID + NodeId enode.IDv0 BlsPubKey bls.PublicKeyHex Shares *big.Int } @@ -609,9 +609,9 @@ func (queue ValidatorQueue) String() string { return "[" + strings.Join(arr, ",") + "]" } -type CandidateMap map[discover.NodeID]*Candidate +type CandidateMap map[enode.IDv0]*Candidate -type NeedRemoveCans map[discover.NodeID]*Candidate +type NeedRemoveCans map[enode.IDv0]*Candidate func (arr ValidatorQueue) ValidatorSort(removes NeedRemoveCans, compare func(slashs NeedRemoveCans, c, can *Validator) int) { @@ -962,7 +962,7 @@ func (v ValidatorArray) String() string { type ValidatorEx struct { //NodeAddress common.Address - NodeId discover.NodeID + NodeId enode.IDv0 // bls public key BlsPubKey bls.PublicKeyHex // The account used to initiate the staking @@ -1120,7 +1120,7 @@ func (del *DelegationHex) IsEmpty() bool { type DelegationEx struct { Addr common.Address - NodeId discover.NodeID + NodeId enode.IDv0 StakingBlockNum uint64 DelegationHex } @@ -1148,7 +1148,7 @@ func (dex *DelegationEx) IsEmpty() bool { type DelegateRelated struct { Addr common.Address - NodeId discover.NodeID + NodeId enode.IDv0 StakingBlockNum uint64 } @@ -1230,7 +1230,7 @@ func (queue ValArrIndexQueue) String() string { // An item that exists for slash type SlashNodeItem struct { // the nodeId will be slashed - NodeId discover.NodeID + NodeId enode.IDv0 // the amount of von with slashed Amount *big.Int // slash type diff --git a/x/xutil/calculate.go b/x/xutil/calculate.go index c6447594dd..c3ba19fab4 100644 --- a/x/xutil/calculate.go +++ b/x/xutil/calculate.go @@ -20,13 +20,14 @@ import ( "bytes" "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" "github.com/AlayaNetwork/Alaya-Go/x/xcom" ) -func NodeId2Addr(nodeId discover.NodeID) (common.NodeAddress, error) { +func NodeId2Addr(nodeId enode.IDv0) (common.NodeAddress, error) { if pk, err := nodeId.Pubkey(); nil != err { return common.ZeroNodeAddr, err } else { @@ -152,7 +153,7 @@ func CalculateRound(blockNumber uint64) uint64 { return round } -func InNodeIDList(nodeID discover.NodeID, nodeIDList []discover.NodeID) bool { +func InNodeIDList(nodeID enode.IDv0, nodeIDList []enode.IDv0) bool { for _, v := range nodeIDList { if nodeID == v { return true From bb9898303a6d6251f6f9ff983357fa7c62c19dbe Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 20 Aug 2021 15:50:34 +0800 Subject: [PATCH 002/299] p2p: add enode URL to PeerInfo (#17838) --- p2p/peer.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index ae74488a3b..1436925f8f 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -437,9 +437,10 @@ func (rw *protoRW) ReadMsg() (Msg, error) { // peer. Sub-protocol independent fields are contained and initialized here, with // protocol specifics delegated to all connected sub-protocols. type PeerInfo struct { - 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 - Caps []string `json:"caps"` // Sum-protocols advertised by this particular peer + Enode string `json:"enode"` // Node URL + ID string `json:"id"` // Unique node identifier + 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 { LocalAddress string `json:"localAddress"` // Local endpoint of the TCP data connection RemoteAddress string `json:"remoteAddress"` // Remote endpoint of the TCP data connection @@ -460,6 +461,7 @@ func (p *Peer) Info() *PeerInfo { } // Assemble the generic peer metadata info := &PeerInfo{ + Enode: p.Node().String(), ID: p.ID().String(), Name: p.Name(), Caps: caps, From 66f6e71e7131377f7525ebf58a1ca31bb3c567c1 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 20 Aug 2021 16:49:08 +0800 Subject: [PATCH 003/299] p2p, p2p/discover: add signed ENR generation #17753 --- cmd/bootnode/main.go | 11 +- node/node_test.go | 6 +- p2p/dial.go | 7 +- p2p/dial_test.go | 16 +- p2p/discover/table.go | 41 +++--- p2p/discover/table_test.go | 10 +- p2p/discover/table_util_test.go | 25 +++- p2p/discover/udp.go | 91 ++++++------ p2p/discover/udp_test.go | 11 +- p2p/discv5/udp.go | 3 +- p2p/enode/localnode.go | 246 +++++++++++++++++++++++++++++++ p2p/enode/localnode_test.go | 76 ++++++++++ p2p/enode/node.go | 7 + p2p/enode/nodedb.go | 124 +++++++++++----- p2p/enode/nodedb_test.go | 4 +- p2p/enr/enr.go | 2 +- p2p/enr/enr_test.go | 12 ++ p2p/nat/nat.go | 21 +-- p2p/nat/nat_test.go | 2 +- p2p/netutil/iptrack.go | 130 ++++++++++++++++ p2p/netutil/iptrack_test.go | 138 +++++++++++++++++ p2p/protocol.go | 11 +- p2p/server.go | 236 +++++++++++++++-------------- p2p/server_test.go | 26 ++-- p2p/simulations/adapters/exec.go | 6 +- p2p/simulations/events.go | 3 + 26 files changed, 985 insertions(+), 280 deletions(-) create mode 100644 p2p/enode/localnode.go create mode 100644 p2p/enode/localnode_test.go create mode 100644 p2p/netutil/iptrack.go create mode 100644 p2p/netutil/iptrack_test.go diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go index 8244895d2f..e8b8801378 100644 --- a/cmd/bootnode/main.go +++ b/cmd/bootnode/main.go @@ -120,16 +120,17 @@ func main() { } if *runv5 { - if _, err := discv5.ListenUDP(nodeKey, conn, realaddr, "", restrictList); err != nil { + if _, err := discv5.ListenUDP(nodeKey, conn, "", restrictList); err != nil { utils.Fatalf("%v", err) } } else { + db, _ := enode.OpenDB("") + ln := enode.NewLocalNode(db, nodeKey) cfg := discover.Config{ - PrivateKey: nodeKey, - AnnounceAddr: realaddr, - NetRestrict: restrictList, + PrivateKey: nodeKey, + NetRestrict: restrictList, } - if _, err := discover.ListenUDP(conn, cfg); err != nil { + if _, err := discover.ListenUDP(conn, ln, cfg); err != nil { utils.Fatalf("%v", err) } } diff --git a/node/node_test.go b/node/node_test.go index 53963a4782..a5c28dcda8 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -479,9 +479,9 @@ func TestProtocolGather(t *testing.T) { Count int Maker InstrumentingWrapper }{ - "Zero Protocols": {0, InstrumentedServiceMakerA}, - "Single Protocol": {1, InstrumentedServiceMakerB}, - "Many Protocols": {25, InstrumentedServiceMakerC}, + "zero": {0, InstrumentedServiceMakerA}, + "one": {1, InstrumentedServiceMakerB}, + "many": {10, InstrumentedServiceMakerC}, } for id, config := range services { protocols := make([]p2p.Protocol, config.Count) diff --git a/p2p/dial.go b/p2p/dial.go index 60c1fe6521..5c6d48eb33 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -73,6 +73,7 @@ type dialstate struct { maxDynDials int ntab discoverTable netrestrict *netutil.Netlist + self enode.ID lookupRunning bool dialing map[enode.ID]connFlag @@ -88,7 +89,6 @@ type dialstate struct { } type discoverTable interface { - Self() *enode.Node Close() Resolve(*enode.Node) *enode.Node LookupRandom() []*enode.Node @@ -130,10 +130,11 @@ type waitExpireTask struct { time.Duration } -func newDialState(static []*enode.Node, bootnodes []*enode.Node, ntab discoverTable, maxdyn int, netrestrict *netutil.Netlist, maxConsensusPeers int) *dialstate { +func newDialState(self enode.ID, static []*enode.Node, bootnodes []*enode.Node, ntab discoverTable, maxdyn int, netrestrict *netutil.Netlist, maxConsensusPeers int) *dialstate { s := &dialstate{ maxDynDials: maxdyn, ntab: ntab, + self: self, netrestrict: netrestrict, static: make(map[enode.ID]*dialTask), consensus: NewDialedTasks(maxConsensusPeers*2, nil), @@ -307,7 +308,7 @@ func (s *dialstate) checkDial(n *enode.Node, peers map[enode.ID]*Peer) error { return errAlreadyDialing case peers[n.ID()] != nil: return errAlreadyConnected - case s.ntab != nil && n.ID() == s.ntab.Self().ID(): + case n.ID() == s.self: return errSelf case s.netrestrict != nil && !s.netrestrict.Contains(n.IP()): return errNotWhitelisted diff --git a/p2p/dial_test.go b/p2p/dial_test.go index c2f6ae5089..cbd6c82e90 100644 --- a/p2p/dial_test.go +++ b/p2p/dial_test.go @@ -91,7 +91,7 @@ func (t fakeTable) ReadRandomNodes(buf []*enode.Node) int { return copy(buf, t) // This test checks that dynamic dials are launched from discovery results func TestDialStateDynDial(t *testing.T) { runDialTest(t, dialtest{ - init: newDialState(nil, nil, fakeTable{}, 5, nil, 75), + init: newDialState(enode.ID{}, nil, nil, fakeTable{}, 5, nil, 75), rounds: []round{ // A discovery query is launched. { @@ -238,7 +238,7 @@ func TestDialStateDynDialBootnode(t *testing.T) { newNode(uintID(8), nil), } runDialTest(t, dialtest{ - init: newDialState(nil, bootnodes, table, 5, nil, 75), + init: newDialState(enode.ID{}, nil, bootnodes, table, 5, nil, 75), rounds: []round{ // 2 dynamic dials attempted, bootnodes pending fallback interval { @@ -326,7 +326,7 @@ func TestDialStateDynDialFromTable(t *testing.T) { } runDialTest(t, dialtest{ - init: newDialState(nil, nil, table, 10, nil, 75), + init: newDialState(enode.ID{}, nil, nil, table, 10, nil, 75), rounds: []round{ // 5 out of 8 of the nodes returned by ReadRandomNodes are dialed. { @@ -432,7 +432,7 @@ func TestDialStateNetRestrict(t *testing.T) { restrict.Add("127.0.2.0/24") runDialTest(t, dialtest{ - init: newDialState(nil, nil, table, 10, restrict, 75), + init: newDialState(enode.ID{}, nil, nil, table, 10, restrict, 75), rounds: []round{ { new: []task{ @@ -455,7 +455,7 @@ func TestDialStateStaticDial(t *testing.T) { } runDialTest(t, dialtest{ - init: newDialState(wantStatic, nil, fakeTable{}, 0, nil, 75), + init: newDialState(enode.ID{}, wantStatic, nil, fakeTable{}, 0, nil, 75), rounds: []round{ // Static dials are launched for the nodes that // aren't yet connected. @@ -559,7 +559,7 @@ func TestDialStaticAfterReset(t *testing.T) { }, } dTest := dialtest{ - init: newDialState(wantStatic, nil, fakeTable{}, 0, nil, 75), + init: newDialState(enode.ID{}, wantStatic, nil, fakeTable{}, 0, nil, 75), rounds: rounds, } runDialTest(t, dTest) @@ -580,7 +580,7 @@ func TestDialStateCache(t *testing.T) { } runDialTest(t, dialtest{ - init: newDialState(wantStatic, nil, fakeTable{}, 0, nil, 75), + init: newDialState(enode.ID{}, wantStatic, nil, fakeTable{}, 0, nil, 75), rounds: []round{ // Static dials are launched for the nodes that // aren't yet connected. @@ -642,7 +642,7 @@ func TestDialStateCache(t *testing.T) { func TestDialResolve(t *testing.T) { resolved := newNode(uintID(1), net.IP{127, 0, 55, 234}) table := &resolveMock{answer: resolved} - state := newDialState(nil, nil, table, 0, nil, 75) + state := newDialState(enode.ID{}, nil, nil, table, 0, nil, 75) // Check that the task is generated with an incomplete ID. dest := newNode(uintID(1), nil) diff --git a/p2p/discover/table.go b/p2p/discover/table.go index cb6ab973f1..5f1616b127 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -73,21 +73,20 @@ type Table struct { ips netutil.DistinctNetSet db *enode.DB // database of known nodes + net transport refreshReq chan chan struct{} initDone chan struct{} closeReq chan struct{} closed chan struct{} nodeAddedHook func(*node) // for testing - - net transport - self *node // metadata of the local node } // transport is implemented by the UDP transport. // it is an interface so we can test without opening lots of UDP // sockets and without generating a private key. type transport interface { + self() *enode.Node ping(enode.ID, *net.UDPAddr) error findnode(toid enode.ID, addr *net.UDPAddr, target encPubkey) ([]*node, error) close() @@ -101,11 +100,10 @@ type bucket struct { ips netutil.DistinctNetSet } -func newTable(t transport, self *enode.Node, db *enode.DB, bootnodes []*enode.Node) (*Table, error) { +func newTable(t transport, db *enode.DB, bootnodes []*enode.Node) (*Table, error) { tab := &Table{ net: t, db: db, - self: wrapNode(self), refreshReq: make(chan chan struct{}), initDone: make(chan struct{}), closeReq: make(chan struct{}), @@ -128,6 +126,10 @@ func newTable(t transport, self *enode.Node, db *enode.DB, bootnodes []*enode.No return tab, nil } +func (tab *Table) self() *enode.Node { + return tab.net.self() +} + func (tab *Table) seedRand() { var b [8]byte crand.Read(b[:]) @@ -137,11 +139,6 @@ func (tab *Table) seedRand() { tab.mutex.Unlock() } -// Self returns the local node. -func (tab *Table) Self() *enode.Node { - return unwrapNode(tab.self) -} - // ReadRandomNodes fills the given slice with random nodes from the table. The results // are guaranteed to be unique for a single invocation, no node will appear twice. func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) { @@ -184,6 +181,10 @@ func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) { // Close terminates the network listener and flushes the node database. func (tab *Table) Close() { + if tab.net != nil { + tab.net.close() + } + select { case <-tab.closed: // already closed. @@ -258,7 +259,7 @@ func (tab *Table) lookup(targetKey encPubkey, refreshIfEmpty bool) []*node { ) // don't query further if we hit ourself. // unlikely to happen often in practice. - asked[tab.self.ID()] = true + asked[tab.self().ID()] = true for { tab.mutex.Lock() @@ -341,8 +342,8 @@ func (tab *Table) loop() { revalidate = time.NewTimer(tab.nextRevalidateTime()) refresh = time.NewTicker(refreshInterval) copyNodes = time.NewTicker(copyNodesInterval) - revalidateDone = make(chan struct{}) 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 ) defer refresh.Stop() @@ -373,9 +374,11 @@ loop: } waiting, refreshDone = nil, nil case <-revalidate.C: + revalidateDone = make(chan struct{}) go tab.doRevalidate(revalidateDone) case <-revalidateDone: revalidate.Reset(tab.nextRevalidateTime()) + revalidateDone = nil case <-copyNodes.C: go tab.copyLiveNodes() case <-tab.closeReq: @@ -383,15 +386,15 @@ loop: } } - if tab.net != nil { - tab.net.close() - } if refreshDone != nil { <-refreshDone } for _, ch := range waiting { close(ch) } + if revalidateDone != nil { + <-revalidateDone + } close(tab.closed) } @@ -409,7 +412,7 @@ func (tab *Table) doRefresh(done chan struct{}) { // Run self lookup to discover new neighbor nodes. // We can only do this if we have a secp256k1 identity. var key ecdsa.PublicKey - if err := tab.self.Load((*enode.Secp256k1)(&key)); err == nil { + if err := tab.self().Load((*enode.Secp256k1)(&key)); err == nil { tab.lookup(encodePubkey(&key), false) } @@ -531,7 +534,7 @@ func (tab *Table) len() (n int) { // bucket returns the bucket for the given node ID hash. func (tab *Table) bucket(id enode.ID) *bucket { - d := enode.LogDist(tab.self.ID(), id) + d := enode.LogDist(tab.self().ID(), id) if d <= bucketMinDistance { return tab.buckets[0] } @@ -544,7 +547,7 @@ func (tab *Table) bucket(id enode.ID) *bucket { // // The caller must not hold tab.mutex. func (tab *Table) add(n *node) { - if n.ID() == tab.self.ID() { + if n.ID() == tab.self().ID() { return } @@ -577,7 +580,7 @@ func (tab *Table) stuff(nodes []*node) { defer tab.mutex.Unlock() for _, n := range nodes { - if n.ID() == tab.self.ID() { + if n.ID() == tab.self().ID() { continue // don't add self } b := tab.bucket(n.ID()) diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go index 2d5aa26182..0a3690d657 100644 --- a/p2p/discover/table_test.go +++ b/p2p/discover/table_test.go @@ -141,7 +141,7 @@ func TestTable_IPLimit(t *testing.T) { defer db.Close() for i := 0; i < tableIPLimit+1; i++ { - n := nodeAtDistance(tab.self.ID(), i, net.IP{172, 0, 1, byte(i)}) + n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)}) tab.add(n) } if tab.len() > tableIPLimit { @@ -158,7 +158,7 @@ func TestTable_BucketIPLimit(t *testing.T) { d := 3 for i := 0; i < bucketIPLimit+1; i++ { - n := nodeAtDistance(tab.self.ID(), d, net.IP{172, 0, 1, byte(i)}) + n := nodeAtDistance(tab.self().ID(), d, net.IP{172, 0, 1, byte(i)}) tab.add(n) } if tab.len() > bucketIPLimit { @@ -240,7 +240,7 @@ func TestTable_ReadRandomNodesGetAll(t *testing.T) { for i := 0; i < len(buf); i++ { ld := cfg.Rand.Intn(len(tab.buckets)) - tab.stuff([]*node{nodeAtDistance(tab.self.ID(), ld, intIP(ld))}) + tab.stuff([]*node{nodeAtDistance(tab.self().ID(), ld, intIP(ld))}) } gotN := tab.ReadRandomNodes(buf) if gotN != tab.len() { @@ -510,6 +510,10 @@ type preminedTestnet struct { dists [hashBits + 1][]encPubkey } +func (tn *preminedTestnet) self() *enode.Node { + return nullNode +} + func (tn *preminedTestnet) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) { // current log distance is encoded in port number // fmt.Println("findnode query at dist", toaddr.Port) diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go index 71aa493512..9e75352fff 100644 --- a/p2p/discover/table_util_test.go +++ b/p2p/discover/table_util_test.go @@ -28,12 +28,17 @@ import ( "github.com/AlayaNetwork/Alaya-Go/p2p/enr" ) -func newTestTable(t transport) (*Table, *enode.DB) { +var nullNode *enode.Node + +func init() { var r enr.Record r.Set(enr.IP{0, 0, 0, 0}) - n := enode.SignNull(&r, enode.ID{}) + nullNode = enode.SignNull(&r, enode.ID{}) +} + +func newTestTable(t transport) (*Table, *enode.DB) { db, _ := enode.OpenDB("") - tab, _ := newTable(t, n, db, nil) + tab, _ := newTable(t, db, nil) return tab, db } @@ -70,10 +75,10 @@ func intIP(i int) net.IP { // 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()) + 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))) + b.entries = append(b.entries, nodeAtDistance(tab.self().ID(), ld, intIP(ld))) } return b.entries[bucketSize-1] } @@ -81,15 +86,25 @@ func fillBucket(tab *Table, n *node) (last *node) { 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 } diff --git a/p2p/discover/udp.go b/p2p/discover/udp.go index c79ea726b0..ca721d3619 100644 --- a/p2p/discover/udp.go +++ b/p2p/discover/udp.go @@ -25,13 +25,13 @@ import ( "math/big" "net" "reflect" + "sync" "time" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/nat" "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -126,9 +126,11 @@ type ( ) func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint { - ip := addr.IP.To4() - if ip == nil { - ip = addr.IP.To16() + ip := net.IP{} + if ip4 := addr.IP.To4(); ip4 != nil { + ip = ip4 + } else if ip6 := addr.IP.To16(); ip6 != nil { + ip = ip6 } return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort} } @@ -173,20 +175,19 @@ type conn interface { LocalAddr() net.Addr } -// udp implements the RPC protocol. +// udp implements the discovery v4 UDP wire protocol. type udp struct { conn conn netrestrict *netutil.Netlist priv *ecdsa.PrivateKey - ourEndpoint rpcEndpoint + localNode *enode.LocalNode + db *enode.DB + tab *Table + wg sync.WaitGroup addpending chan *pending gotreply chan reply - - closing chan struct{} - nat nat.Interface - - *Table + closing chan struct{} } // pending represents a pending reply. @@ -241,16 +242,14 @@ type Config struct { ChainID *big.Int `toml:"-"` // These settings are optional: - AnnounceAddr *net.UDPAddr // local address announced in the DHT - NodeDBPath string // if set, the node database is stored at this filesystem location - NetRestrict *netutil.Netlist // network whitelist - Bootnodes []*enode.Node // list of bootstrap nodes - Unhandled chan<- ReadPacket // unhandled packets are sent on this channel + NetRestrict *netutil.Netlist // network whitelist + Bootnodes []*enode.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. -func ListenUDP(c conn, cfg Config) (*Table, error) { - tab, _, err := newUDP(c, cfg) +func ListenUDP(c conn, ln *enode.LocalNode, cfg Config) (*Table, error) { + tab, _, err := newUDP(c, ln, cfg) if err != nil { return nil, err } @@ -265,43 +264,43 @@ func ListenUDP(c conn, cfg Config) (*Table, error) { return tab, nil } -func newUDP(c conn, cfg Config) (*Table, *udp, error) { - realaddr := c.LocalAddr().(*net.UDPAddr) - if cfg.AnnounceAddr != nil { - realaddr = cfg.AnnounceAddr - } - self := enode.NewV4(&cfg.PrivateKey.PublicKey, realaddr.IP, realaddr.Port, realaddr.Port) - db, err := enode.OpenDB(cfg.NodeDBPath) - if err != nil { - return nil, nil, err - } - +func newUDP(c conn, ln *enode.LocalNode, cfg Config) (*Table, *udp, error) { udp := &udp{ conn: c, priv: cfg.PrivateKey, netrestrict: cfg.NetRestrict, + localNode: ln, + db: ln.Database(), closing: make(chan struct{}), gotreply: make(chan reply), addpending: make(chan *pending), } - // TODO: separate TCP port - udp.ourEndpoint = makeEndpoint(realaddr, uint16(realaddr.Port)) - tab, err := newTable(udp, self, db, cfg.Bootnodes) + tab, err := newTable(udp, ln.Database(), cfg.Bootnodes) if err != nil { return nil, nil, err } - udp.Table = tab + udp.tab = tab + udp.wg.Add(2) go udp.loop() go udp.readLoop(cfg.Unhandled) - return udp.Table, udp, nil + return udp.tab, udp, nil +} + +func (t *udp) self() *enode.Node { + return t.localNode.Node() } func (t *udp) close() { close(t.closing) t.conn.Close() - t.db.Close() - // TODO: wait for the loops to end. + t.wg.Wait() +} + +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. @@ -314,7 +313,7 @@ func (t *udp) ping(toid enode.ID, toaddr *net.UDPAddr) error { func (t *udp) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) <-chan error { req := &ping{ Version: 4, - From: t.ourEndpoint, + From: t.ourEndpoint(), To: makeEndpoint(toaddr, 0), // TODO: maybe use known TCP port from DB Expiration: uint64(time.Now().Add(expiration).Unix()), Rest: cRest, @@ -332,6 +331,7 @@ func (t *udp) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) <-ch } return ok }) + t.localNode.UDPContact(toaddr) t.write(toaddr, req.name(), packet) return errc } @@ -401,6 +401,8 @@ func (t *udp) handleReply(from enode.ID, ptype byte, req packet) bool { // loop runs in its own goroutine. it keeps track of // the refresh timer and the pending reply queue. func (t *udp) loop() { + defer t.wg.Done() + var ( plist = list.New() timeout = time.NewTimer(0) @@ -562,10 +564,11 @@ func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) (packet, // readLoop runs in its own goroutine. it handles incoming UDP packets. func (t *udp) readLoop(unhandled chan<- ReadPacket) { - defer t.conn.Close() + defer t.wg.Done() if unhandled != nil { defer close(unhandled) } + // Discovery packets are defined to be no larger than 1280 bytes. // Packets larger than this size will be cut at the end and treated // as invalid because their hash won't match. @@ -655,10 +658,11 @@ func (req *ping) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte n := wrapNode(enode.NewV4(key, from.IP, int(req.From.TCP), from.Port)) t.handleReply(n.ID(), pingPacket, req) if time.Since(t.db.LastPongReceived(n.ID())) > bondExpiration { - t.sendPing(n.ID(), from, func() { t.addThroughPing(n) }) + t.sendPing(n.ID(), from, func() { t.tab.addThroughPing(n) }) } else { - t.addThroughPing(n) + 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 } @@ -676,6 +680,7 @@ func (req *pong) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte if !t.handleReply(fromID, pongPacket, req) { 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 } @@ -700,9 +705,9 @@ func (req *findnode) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac [] return errUnknownNode } target := enode.ID(crypto.Keccak256Hash(req.Target[:])) - t.mutex.Lock() - closest := t.closest(target, bucketSize).entries - t.mutex.Unlock() + t.tab.mutex.Lock() + closest := t.tab.closest(target, bucketSize).entries + t.tab.mutex.Unlock() p := neighbors{ Expiration: uint64(time.Now().Add(expiration).Unix()), diff --git a/p2p/discover/udp_test.go b/p2p/discover/udp_test.go index 6f5389d6e3..077c98e8ee 100644 --- a/p2p/discover/udp_test.go +++ b/p2p/discover/udp_test.go @@ -73,7 +73,9 @@ func newUDPTest(t *testing.T) *udpTest { remotekey: newkey(), remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 16789}, } - test.table, test.udp, _ = newUDP(test.pipe, Config{PrivateKey: test.localkey}) + db, _ := enode.OpenDB("") + 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. <-test.table.initDone return test @@ -357,12 +359,13 @@ func TestUDP_successfulPing(t *testing.T) { // remote is unknown, the table pings back. hash, _ := test.waitPacketOut(func(p *ping) error { - if !reflect.DeepEqual(p.From, test.udp.ourEndpoint) { - t.Errorf("got ping.From %v, want %v", 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()) } wantTo := rpcEndpoint{ // The mirrored UDP address is the UDP packet sender. - IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port), + IP: test.remoteaddr.IP, + UDP: uint16(test.remoteaddr.Port), TCP: 0, } if !reflect.DeepEqual(p.To, wantTo) { diff --git a/p2p/discv5/udp.go b/p2p/discv5/udp.go index 1f5f989bb5..684333294f 100644 --- a/p2p/discv5/udp.go +++ b/p2p/discv5/udp.go @@ -230,7 +230,8 @@ type udp struct { } // ListenUDP returns a new table that listens for UDP packets on laddr. -func ListenUDP(priv *ecdsa.PrivateKey, conn conn, realaddr *net.UDPAddr, nodeDBPath string, netrestrict *netutil.Netlist) (*Network, error) { +func ListenUDP(priv *ecdsa.PrivateKey, conn conn, nodeDBPath string, netrestrict *netutil.Netlist) (*Network, error) { + realaddr := conn.LocalAddr().(*net.UDPAddr) transport, err := listenUDP(priv, conn, realaddr) if err != nil { return nil, err diff --git a/p2p/enode/localnode.go b/p2p/enode/localnode.go new file mode 100644 index 0000000000..07ba95b9b7 --- /dev/null +++ b/p2p/enode/localnode.go @@ -0,0 +1,246 @@ +// 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 . + +package enode + +import ( + "crypto/ecdsa" + "fmt" + "net" + "reflect" + "strconv" + "sync" + "sync/atomic" + "time" + + "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/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) +} diff --git a/p2p/enode/localnode_test.go b/p2p/enode/localnode_test.go new file mode 100644 index 0000000000..25804a0e16 --- /dev/null +++ b/p2p/enode/localnode_test.go @@ -0,0 +1,76 @@ +// 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 . + +package enode + +import ( + "testing" + + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/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) + } +} diff --git a/p2p/enode/node.go b/p2p/enode/node.go index 3f5a8c0b31..e754e71199 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -106,6 +106,13 @@ func (n *Node) Pubkey() *ecdsa.PublicKey { 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 +} + // IDv0 returns the node byte of the Pubkey. func (n *Node) IDv0() IDv0 { var id IDv0 diff --git a/p2p/enode/nodedb.go b/p2p/enode/nodedb.go index 51060498e2..fea734bbe6 100644 --- a/p2p/enode/nodedb.go +++ b/p2p/enode/nodedb.go @@ -39,11 +39,24 @@ import ( "github.com/AlayaNetwork/Alaya-Go/rlp" ) +// 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 ( - nodeDBNilID = ID{} // Special node ID to use as a nil element. - nodeDBNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped. - nodeDBCleanupCycle = time.Hour // Time period for running the expiration task. - nodeDBVersion = 6 + dbNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped. + dbCleanupCycle = time.Hour // Time period for running the expiration task. + dbVersion = 7 ) // DB is the node database, storing previously seen nodes and any collected metadata about @@ -54,17 +67,6 @@ type DB struct { quit chan struct{} // Channel to signal the expiring thread to stop } -// Schema layout for the node database -var ( - 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" -) - // OpenDB opens a 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 OpenDB(path string) (*DB, error) { @@ -97,13 +99,13 @@ func newPersistentDB(path string) (*DB, error) { // The nodes contained in the cache correspond to a certain protocol version. // Flush all nodes if the version doesn't match. currentVer := make([]byte, binary.MaxVarintLen64) - currentVer = currentVer[:binary.PutVarint(currentVer, int64(nodeDBVersion))] + currentVer = currentVer[:binary.PutVarint(currentVer, int64(dbVersion))] - blob, err := db.Get(nodeDBVersionKey, nil) + blob, err := db.Get([]byte(dbVersionKey), nil) switch err { case leveldb.ErrNotFound: // Version not found (i.e. empty cache), insert it - if err := db.Put(nodeDBVersionKey, currentVer, nil); err != nil { + if err := db.Put([]byte(dbVersionKey), currentVer, nil); err != nil { db.Close() return nil, err } @@ -124,28 +126,27 @@ func newPersistentDB(path string) (*DB, error) { // makeKey generates the leveldb key-blob from a node id and its particular // field of interest. func makeKey(id ID, field string) []byte { - if bytes.Equal(id[:], nodeDBNilID[:]) { + if (id == ID{}) { return []byte(field) } - return append(nodeDBItemPrefix, append(id[:], field...)...) + return append([]byte(dbItemPrefix), append(id[:], field...)...) } // splitKey tries to split a database key into a node id and a field part. func splitKey(key []byte) (id ID, field string) { // If the key is not of a node, return it plainly - if !bytes.HasPrefix(key, nodeDBItemPrefix) { + if !bytes.HasPrefix(key, []byte(dbItemPrefix)) { return ID{}, string(key) } // Otherwise split the id and field - item := key[len(nodeDBItemPrefix):] + item := key[len(dbItemPrefix):] copy(id[:], item[:len(id)]) field = string(item[len(id):]) return id, field } -// fetchInt64 retrieves an integer instance associated with a particular -// database key. +// fetchInt64 retrieves an integer associated with a particular key. func (db *DB) fetchInt64(key []byte) int64 { blob, err := db.lvl.Get(key, nil) if err != nil { @@ -158,18 +159,33 @@ func (db *DB) fetchInt64(key []byte) int64 { return val } -// storeInt64 update a specific database entry to the current time instance as a -// unix timestamp. +// storeInt64 stores an integer in the given key. func (db *DB) storeInt64(key []byte, n int64) error { blob := make([]byte, binary.MaxVarintLen64) blob = blob[:binary.PutVarint(blob, n)] + return db.lvl.Put(key, blob, nil) +} + +// fetchUint64 retrieves an integer associated with a particular key. +func (db *DB) fetchUint64(key []byte) uint64 { + blob, err := db.lvl.Get(key, 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, nodeDBDiscoverRoot), nil) + blob, err := db.lvl.Get(makeKey(id, dbDiscoverRoot), nil) if err != nil { return nil } @@ -188,11 +204,31 @@ func mustDecodeNode(id, data []byte) *Node { // UpdateNode inserts - potentially overwriting - a node into the peer database. func (db *DB) UpdateNode(node *Node) error { + if node.Seq() < db.NodeSeq(node.ID()) { + return nil + } blob, err := rlp.EncodeToBytes(&node.r) if err != nil { return err } - return db.lvl.Put(makeKey(node.ID(), nodeDBDiscoverRoot), blob, nil) + if err := db.lvl.Put(makeKey(node.ID(), dbDiscoverRoot), blob, nil); err != nil { + return err + } + return db.storeUint64(makeKey(node.ID(), dbDiscoverSeq), node.Seq()) +} + +// NodeSeq returns the stored record sequence number of the given node. +func (db *DB) NodeSeq(id ID) uint64 { + 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. @@ -222,7 +258,7 @@ func (db *DB) ensureExpirer() { // expirer should be started in a go routine, and is responsible for looping ad // infinitum and dropping stale data from the database. func (db *DB) expirer() { - tick := time.NewTicker(nodeDBCleanupCycle) + tick := time.NewTicker(dbCleanupCycle) defer tick.Stop() for { select { @@ -239,7 +275,7 @@ func (db *DB) expirer() { // expireNodes iterates over the database and deletes all nodes that have not // been seen (i.e. received a pong from) for some allotted time. func (db *DB) expireNodes() error { - threshold := time.Now().Add(-nodeDBNodeExpiration) + threshold := time.Now().Add(-dbNodeExpiration) // Find discovered nodes that are older than the allowance it := db.lvl.NewIterator(nil, nil) @@ -248,7 +284,7 @@ func (db *DB) expireNodes() error { for it.Next() { // Skip the item if not a discovery node id, field := splitKey(it.Key()) - if field != nodeDBDiscoverRoot { + if field != dbDiscoverRoot { continue } // Skip the node if not expired yet (and not self) @@ -264,34 +300,44 @@ func (db *DB) expireNodes() error { // LastPingReceived retrieves the time of the last ping packet received from // a remote node. func (db *DB) LastPingReceived(id ID) time.Time { - return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPing)), 0) + return time.Unix(db.fetchInt64(makeKey(id, dbDiscoverPing)), 0) } // UpdateLastPingReceived updates the last time we tried contacting a remote node. func (db *DB) UpdateLastPingReceived(id ID, instance time.Time) error { - return db.storeInt64(makeKey(id, nodeDBDiscoverPing), instance.Unix()) + return db.storeInt64(makeKey(id, dbDiscoverPing), instance.Unix()) } // LastPongReceived retrieves the time of the last successful pong from remote node. func (db *DB) LastPongReceived(id ID) time.Time { // Launch expirer db.ensureExpirer() - return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPong)), 0) + return time.Unix(db.fetchInt64(makeKey(id, dbDiscoverPong)), 0) } // UpdateLastPongReceived updates the last pong time of a node. func (db *DB) UpdateLastPongReceived(id ID, instance time.Time) error { - return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix()) + return db.storeInt64(makeKey(id, dbDiscoverPong), instance.Unix()) } // FindFails retrieves the number of findnode failures since bonding. func (db *DB) FindFails(id ID) int { - return int(db.fetchInt64(makeKey(id, nodeDBDiscoverFindFails))) + return int(db.fetchInt64(makeKey(id, dbDiscoverFindFails))) } // UpdateFindFails updates the number of findnode failures since bonding. func (db *DB) UpdateFindFails(id ID, fails int) error { - return db.storeInt64(makeKey(id, nodeDBDiscoverFindFails), int64(fails)) + return db.storeInt64(makeKey(id, dbDiscoverFindFails), int64(fails)) +} + +// LocalSeq retrieves the local record sequence counter. +func (db *DB) localSeq(id ID) uint64 { + return db.fetchUint64(makeKey(id, dbLocalSeq)) +} + +// storeLocalSeq stores the local record sequence counter. +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 @@ -313,7 +359,7 @@ seek: ctr := id[0] rand.Read(id[:]) id[0] = ctr + id[0]%16 - it.Seek(makeKey(id, nodeDBDiscoverRoot)) + it.Seek(makeKey(id, dbDiscoverRoot)) n := nextNode(it) if n == nil { @@ -338,7 +384,7 @@ seek: func nextNode(it iterator.Iterator) *Node { for end := false; !end; end = !it.Next() { id, field := splitKey(it.Key()) - if field != nodeDBDiscoverRoot { + if field != dbDiscoverRoot { continue } return mustDecodeNode(id[:], it.Value()) diff --git a/p2p/enode/nodedb_test.go b/p2p/enode/nodedb_test.go index a9efb6d43d..495a892a6e 100644 --- a/p2p/enode/nodedb_test.go +++ b/p2p/enode/nodedb_test.go @@ -332,7 +332,7 @@ var nodeDBExpirationNodes = []struct { 16789, 16789, ), - pong: time.Now().Add(-nodeDBNodeExpiration + time.Minute), + pong: time.Now().Add(-dbNodeExpiration + time.Minute), exp: false, }, { node: NewV4( @@ -341,7 +341,7 @@ var nodeDBExpirationNodes = []struct { 16789, 16789, ), - pong: time.Now().Add(-nodeDBNodeExpiration - time.Minute), + pong: time.Now().Add(-dbNodeExpiration - time.Minute), exp: true, }, } diff --git a/p2p/enr/enr.go b/p2p/enr/enr.go index 2c24f45c8c..658269b106 100644 --- a/p2p/enr/enr.go +++ b/p2p/enr/enr.go @@ -150,7 +150,7 @@ func (r *Record) Set(e Entry) { } func (r *Record) invalidate() { - if r.signature == nil { + if r.signature != nil { r.seq++ } r.signature = nil diff --git a/p2p/enr/enr_test.go b/p2p/enr/enr_test.go index 7da9375693..c24a0cbc6b 100644 --- a/p2p/enr/enr_test.go +++ b/p2p/enr/enr_test.go @@ -170,6 +170,18 @@ func TestDirty(t *testing.T) { } } +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. func TestGetSetOverwrite(t *testing.T) { var r Record diff --git a/p2p/nat/nat.go b/p2p/nat/nat.go index 2e010ddc7a..f46745b947 100644 --- a/p2p/nat/nat.go +++ b/p2p/nat/nat.go @@ -25,8 +25,9 @@ import ( "sync" "time" - "github.com/AlayaNetwork/Alaya-Go/log" "github.com/jackpal/go-nat-pmp" + + "github.com/AlayaNetwork/Alaya-Go/log" ) // An implementation of nat.Interface can map local ports to ports @@ -129,21 +130,15 @@ func Map(m Interface, c chan struct{}, protocol string, extport, intport int, na // ExtIP assumes that the local machine is reachable on the given // external IP address, and that any required ports were mapped manually. // Mapping operations will not return an error but won't actually do anything. -func ExtIP(ip net.IP) Interface { - if ip == nil { - panic("IP must not be nil") - } - return extIP(ip) -} - -type extIP net.IP +type ExtIP net.IP -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)) } +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. -func (extIP) AddMapping(string, int, int, string, time.Duration) error { return nil } -func (extIP) DeleteMapping(string, int, int) error { return nil } + +func (ExtIP) AddMapping(string, int, int, string, time.Duration) error { return nil } +func (ExtIP) DeleteMapping(string, int, int) error { return nil } // Any returns a port mapper that tries to discover any supported // mechanism on the local network. diff --git a/p2p/nat/nat_test.go b/p2p/nat/nat_test.go index 469101e997..814e6d9e14 100644 --- a/p2p/nat/nat_test.go +++ b/p2p/nat/nat_test.go @@ -28,7 +28,7 @@ import ( func TestAutoDiscRace(t *testing.T) { ad := startautodisc("thing", func() Interface { 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. diff --git a/p2p/netutil/iptrack.go b/p2p/netutil/iptrack.go new file mode 100644 index 0000000000..ab32d5a7e5 --- /dev/null +++ b/p2p/netutil/iptrack.go @@ -0,0 +1,130 @@ +// 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 . + +package netutil + +import ( + "time" + + "github.com/AlayaNetwork/Alaya-Go/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) + } + } +} diff --git a/p2p/netutil/iptrack_test.go b/p2p/netutil/iptrack_test.go new file mode 100644 index 0000000000..535bdb355f --- /dev/null +++ b/p2p/netutil/iptrack_test.go @@ -0,0 +1,138 @@ +// 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 . + +package netutil + +import ( + "fmt" + mrand "math/rand" + "testing" + "time" + + "github.com/AlayaNetwork/Alaya-Go/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)) + } +} diff --git a/p2p/protocol.go b/p2p/protocol.go index fb9d5b94c0..b701364493 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -19,6 +19,8 @@ package p2p import ( "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" ) @@ -52,6 +54,9 @@ type Protocol struct { // 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. PeerInfo func(id enode.ID) interface{} + + // Attributes contains protocol specific information for the node record. + Attributes []enr.Entry } func (p Protocol) cap() Cap { @@ -64,10 +69,6 @@ type Cap struct { Version uint } -func (cap Cap) RlpData() interface{} { - return []interface{}{cap.Name, cap.Version} -} - func (cap Cap) String() string { return fmt.Sprintf("%s/%d", cap.Name, cap.Version) } @@ -79,3 +80,5 @@ func (cs capsByNameAndVersion) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] } 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) } + +func (capsByNameAndVersion) ENRKey() string { return "cap" } diff --git a/p2p/server.go b/p2p/server.go index a7c24c4dc9..7503fcfb0d 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -20,14 +20,19 @@ package p2p import ( "bytes" "crypto/ecdsa" + "encoding/hex" "errors" "fmt" "math/big" "net" + "sort" "sync" "sync/atomic" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/rlp" + "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" @@ -174,6 +179,8 @@ type Server struct { lock sync.Mutex // protects running running bool + nodedb *enode.DB + localnode *enode.LocalNode ntab discoverTable listener net.Listener ourHandshake *protoHandshake @@ -388,43 +395,13 @@ func (srv *Server) SubscribeEvents(ch chan *PeerEvent) event.Subscription { // Self returns the local node's endpoint information. func (srv *Server) Self() *enode.Node { srv.lock.Lock() - running, listener, ntab := srv.running, srv.listener, srv.ntab + ln := srv.localnode srv.lock.Unlock() - if !running { + if ln == nil { return enode.NewV4(&srv.PrivateKey.PublicKey, net.ParseIP("0.0.0.0"), 0, 0) } - return srv.makeSelf(listener, ntab) -} - -func (srv *Server) makeSelf(listener net.Listener, ntab discoverTable) *enode.Node { - // If the node is running but discovery is off, manually assemble the node infos. - if ntab == nil { - addr := srv.tcpAddr(listener) - return enode.NewV4(&srv.PrivateKey.PublicKey, addr.IP, addr.Port, 0) - } - // Otherwise return the discovery node. - return ntab.Self() -} - -func (srv *Server) tcpAddr(listener net.Listener) net.TCPAddr { - addr := net.TCPAddr{IP: net.IP{0, 0, 0, 0}} - if listener == nil { - return addr // Inbound connections disabled, use zero address. - } - // Otherwise inject the listener address too. - if a, ok := listener.Addr().(*net.TCPAddr); ok { - addr = *a - } - if srv.NAT != nil { - if ip, err := srv.NAT.ExternalIP(); err == nil { - addr.IP = ip - } - } - if addr.IP.IsUnspecified() { - addr.IP = net.IP{127, 0, 0, 1} - } - return addr + return ln.Node() } // Stop terminates the server and all active peer connections. @@ -484,7 +461,9 @@ func (srv *Server) Start() (err error) { if srv.log == nil { srv.log = log.New() } - srv.log.Info("Starting P2P networking") + if srv.NoDial && srv.ListenAddr == "" { + srv.log.Warn("P2P server will be useless, neither dialing nor listening") + } // static fields if srv.PrivateKey == nil { @@ -509,66 +488,121 @@ func (srv *Server) Start() (err error) { srv.peerOp = make(chan peerOpFunc) srv.peerOpDone = make(chan struct{}) - var ( - conn *net.UDPConn - sconn *sharedUDPConn - realaddr *net.UDPAddr - unhandled chan discover.ReadPacket - ) - - if !srv.NoDiscovery || srv.DiscoveryV5 { - addr, err := net.ResolveUDPAddr("udp", srv.ListenAddr) - if err != nil { + if err := srv.setupLocalNode(); err != nil { + return err + } + if srv.ListenAddr != "" { + if err := srv.setupListening(); err != nil { return err } - conn, err = net.ListenUDP("udp", addr) - if err != nil { - return err + } + if err := srv.setupDiscovery(); err != nil { + return err + } + + dynPeers := srv.maxDialedConns() + dialer := newDialState(srv.localnode.ID(), srv.StaticNodes, srv.BootstrapNodes, srv.ntab, dynPeers, srv.NetRestrict, srv.MaxConsensusPeers) + 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) - if srv.NAT != nil { - if !realaddr.IP.IsLoopback() { - go nat.Map(srv.NAT, srv.quit, "udp", realaddr.Port, realaddr.Port, "ethereum discovery") - } - // TODO: react to external IP changes over time. - if ext, err := srv.NAT.ExternalIP(); err == nil { - realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port} + } + switch srv.NAT.(type) { + case nil: + // No NAT interface, do nothing. + case nat.ExtIP: + // ExtIP doesn't block, set the IP right away. + ip, _ := srv.NAT.ExternalIP() + srv.localnode.SetStaticIP(ip) + 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 +} - if !srv.NoDiscovery && srv.DiscoveryV5 { - unhandled = make(chan discover.ReadPacket, 100) - sconn = &sharedUDPConn{conn, unhandled} +func (srv *Server) setupDiscovery() error { + if srv.NoDiscovery && !srv.DiscoveryV5 { + return nil } - // node table + 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 + var unhandled chan discover.ReadPacket + var sconn *sharedUDPConn if !srv.NoDiscovery { + if srv.DiscoveryV5 { + unhandled = make(chan discover.ReadPacket, 100) + sconn = &sharedUDPConn{conn, unhandled} + } cfg := discover.Config{ - PrivateKey: srv.PrivateKey, - ChainID: srv.ChainID, - AnnounceAddr: realaddr, - NodeDBPath: srv.NodeDatabase, - NetRestrict: srv.NetRestrict, - Bootnodes: srv.BootstrapNodes, - Unhandled: unhandled, + PrivateKey: srv.PrivateKey, + ChainID: srv.ChainID, + NetRestrict: srv.NetRestrict, + Bootnodes: srv.BootstrapNodes, + Unhandled: unhandled, } - ntab, err := discover.ListenUDP(conn, cfg) + ntab, err := discover.ListenUDP(conn, srv.localnode, cfg) if err != nil { return err } srv.ntab = ntab } - + // Discovery V5 if srv.DiscoveryV5 { - var ( - ntab *discv5.Network - err error - ) + var ntab *discv5.Network + var err error if sconn != nil { - ntab, err = discv5.ListenUDP(srv.PrivateKey, sconn, realaddr, "", srv.NetRestrict) //srv.NodeDatabase) + ntab, err = discv5.ListenUDP(srv.PrivateKey, sconn, "", srv.NetRestrict) } else { - ntab, err = discv5.ListenUDP(srv.PrivateKey, conn, realaddr, "", srv.NetRestrict) //srv.NodeDatabase) + ntab, err = discv5.ListenUDP(srv.PrivateKey, conn, "", srv.NetRestrict) } if err != nil { return err @@ -578,32 +612,10 @@ func (srv *Server) Start() (err error) { } srv.DiscV5 = ntab } - - dynPeers := srv.maxDialedConns() - dialer := newDialState(srv.StaticNodes, srv.BootstrapNodes, srv.ntab, dynPeers, srv.NetRestrict, srv.MaxConsensusPeers) - - // 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()) - } - // 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) return nil } -func (srv *Server) startListening() error { +func (srv *Server) setupListening() error { // Launch the TCP listener. listener, err := net.Listen("tcp", srv.ListenAddr) if err != nil { @@ -612,8 +624,11 @@ func (srv *Server) startListening() error { laddr := listener.Addr().(*net.TCPAddr) srv.ListenAddr = laddr.String() srv.listener = listener + srv.localnode.Set(enr.TCP(laddr.Port)) + srv.loopWG.Add(1) go srv.listenLoop() + // Map the TCP listening port if NAT is configured. if !laddr.IP.IsLoopback() && srv.NAT != nil { srv.loopWG.Add(1) @@ -637,7 +652,10 @@ type dialer interface { } func (srv *Server) run(dialstate dialer) { + srv.log.Info("Started P2P networking", "self", srv.localnode.Node()) defer srv.loopWG.Done() + defer srv.nodedb.Close() + var ( peers = make(map[enode.ID]*Peer) inboundCount = 0 @@ -907,7 +925,7 @@ func (srv *Server) encHandshakeChecks(peers map[enode.ID]*Peer, inboundCount int return DiscTooManyPeers case peers[c.node.ID()] != nil: return DiscAlreadyConnected - case c.node.ID() == srv.Self().ID(): + case c.node.ID() == srv.localnode.ID(): return DiscSelf default: return nil @@ -929,15 +947,11 @@ func (srv *Server) maxDialedConns() int { return srv.MaxPeers / r } -type tempError interface { - Temporary() bool -} - // listenLoop runs in its own goroutine and accepts // inbound connections. func (srv *Server) listenLoop() { defer srv.loopWG.Done() - srv.log.Info("RLPx listener up", "self", srv.Self()) + srv.log.Debug("TCP listener up", "addr", srv.listener.Addr()) tokens := defaultMaxPendingPeers if srv.MaxPendingPeers > 0 { @@ -958,7 +972,7 @@ func (srv *Server) listenLoop() { ) for { fd, err = srv.listener.Accept() - if tempErr, ok := err.(tempError); ok && tempErr.Temporary() { + if netutil.IsTemporaryError(err) { srv.log.Debug("Temporary read error", "err", err) continue } else if err != nil { @@ -991,10 +1005,6 @@ func (srv *Server) listenLoop() { // as a peer. It returns when the connection has been added as a peer // or the handshakes have failed. func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.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)} err := srv.setupConn(c, flags, dialDest) if err != nil { @@ -1137,6 +1147,7 @@ type NodeInfo struct { Name string `json:"name"` // Name of the node, including client type, version, OS, custom data BlsPub string `json:"blsPubKey"` // BLS public key 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 Ports struct { Discovery int `json:"discovery"` // UDP listening port for discovery protocol @@ -1148,9 +1159,8 @@ type NodeInfo struct { // NodeInfo gathers and returns a collection of metadata known about the host. func (srv *Server) NodeInfo() *NodeInfo { - node := srv.Self() - // Gather and assemble the generic node infos + node := srv.Self() info := &NodeInfo{ Name: srv.Name, Enode: node.String(), @@ -1161,7 +1171,9 @@ func (srv *Server) NodeInfo() *NodeInfo { } info.Ports.Discovery = node.UDP() info.Ports.Listener = node.TCP() - + if enc, err := rlp.EncodeToBytes(node.Record()); err == nil { + info.ENR = "0x" + hex.EncodeToString(enc) + } blskey, _ := srv.BlsPublicKey.MarshalText() info.BlsPub = string(blskey) diff --git a/p2p/server_test.go b/p2p/server_test.go index 5d22b5d620..ca313b4124 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -228,12 +228,15 @@ func TestServerTaskScheduling(t *testing.T) { // The Server in this test isn't actually running // because we're only interested in what run does. + db, _ := enode.OpenDB("") srv := &Server{ - Config: Config{MaxPeers: 10}, - quit: make(chan struct{}), - ntab: fakeTable{}, - running: true, - log: log.New(), + Config: Config{MaxPeers: 10}, + localnode: enode.NewLocalNode(db, newkey()), + nodedb: db, + quit: make(chan struct{}), + ntab: fakeTable{}, + running: true, + log: log.New(), } srv.loopWG.Add(1) go func() { @@ -274,11 +277,14 @@ func TestServerManyTasks(t *testing.T) { } var ( - srv = &Server{ - quit: make(chan struct{}), - ntab: fakeTable{}, - running: true, - log: log.New(), + db, _ = enode.OpenDB("") + srv = &Server{ + quit: make(chan struct{}), + localnode: enode.NewLocalNode(db, newkey()), + nodedb: db, + ntab: fakeTable{}, + running: true, + log: log.New(), } done = make(chan *testTask) start, end = 0, 0 diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index 254bf745fc..0d8f688d8e 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -155,15 +155,13 @@ func (n *ExecNode) Client() (*rpc.Client, error) { } // Start exec's the node passing the ID and service as command line arguments -// and the node config encoded as JSON in the _P2P_NODE_CONFIG environment -// variable +// and the node config encoded as JSON in an environment variable. func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { if n.Cmd != nil { return errors.New("already started") } defer func() { if err != nil { - log.Error("node failed to start", "err", err) n.Stop() } }() @@ -203,7 +201,7 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { if status.Err != "" { return errors.New(status.Err) } - client, err := rpc.DialWebsocket(ctx, status.WSEndpoint, "") + client, err := rpc.DialWebsocket(ctx, status.WSEndpoint, "http://localhost") if err != nil { return fmt.Errorf("can't connect to RPC server: %v", err) } diff --git a/p2p/simulations/events.go b/p2p/simulations/events.go index f17958c689..9b2a990e07 100644 --- a/p2p/simulations/events.go +++ b/p2p/simulations/events.go @@ -58,6 +58,9 @@ type Event struct { // Msg is set if the type is EventTypeMsg Msg *Msg `json:"msg,omitempty"` + + //Optionally provide data (currently for simulation frontends only) + Data interface{} `json:"data"` } // NewEvent creates a new event for the given object which should be either a From 2391989ee5ae2d6720fd9c7d2810fbacb5e3c430 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 20 Aug 2021 16:53:44 +0800 Subject: [PATCH 004/299] p2p: meter peer traffic, emit metered peer events (#17695) --- p2p/dial.go | 2 +- p2p/metrics.go | 193 +++++++++++++++++++++++++++++++++++++++++++++---- p2p/server.go | 9 ++- 3 files changed, 186 insertions(+), 18 deletions(-) diff --git a/p2p/dial.go b/p2p/dial.go index 5c6d48eb33..904d9cb0da 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -391,7 +391,7 @@ func (t *dialTask) dial(srv *Server, dest *enode.Node) error { if err != nil { return &dialError{err} } - mfd := newMeteredConn(fd, false) + mfd := newMeteredConn(fd, false, dest.IP()) return srv.SetupConn(mfd, t.flags, dest) } diff --git a/p2p/metrics.go b/p2p/metrics.go index e1a87eaa30..5dfbde20de 100644 --- a/p2p/metrics.go +++ b/p2p/metrics.go @@ -19,53 +19,214 @@ package p2p import ( + "fmt" "net" + "sync" + "sync/atomic" + "time" + "github.com/AlayaNetwork/Alaya-Go/event" + "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/metrics" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +const ( + MetricsInboundConnects = "p2p/InboundConnects" // Name for the registered inbound connects meter + MetricsInboundTraffic = "p2p/InboundTraffic" // Name for the registered inbound traffic meter + MetricsOutboundConnects = "p2p/OutboundConnects" // Name for the registered outbound connects meter + MetricsOutboundTraffic = "p2p/OutboundTraffic" // Name for the registered outbound traffic meter + + MeteredPeerLimit = 1024 // This amount of peers are individually metered ) var ( - ingressConnectMeter = metrics.NewRegisteredMeter("p2p/InboundConnects", nil) - ingressTrafficMeter = metrics.NewRegisteredMeter("p2p/InboundTraffic", nil) - egressConnectMeter = metrics.NewRegisteredMeter("p2p/OutboundConnects", nil) - egressTrafficMeter = metrics.NewRegisteredMeter("p2p/OutboundTraffic", nil) + ingressConnectMeter = metrics.NewRegisteredMeter(MetricsInboundConnects, nil) // Meter counting the ingress connections + ingressTrafficMeter = metrics.NewRegisteredMeter(MetricsInboundTraffic, nil) // Meter metering the cumulative ingress traffic + egressConnectMeter = metrics.NewRegisteredMeter(MetricsOutboundConnects, nil) // Meter counting the egress connections + egressTrafficMeter = metrics.NewRegisteredMeter(MetricsOutboundTraffic, nil) // Meter metering the cumulative egress traffic + + PeerIngressRegistry = metrics.NewPrefixedChildRegistry(metrics.DefaultRegistry, MetricsInboundTraffic+"/") // Registry containing the peer ingress + PeerEgressRegistry = metrics.NewPrefixedChildRegistry(metrics.DefaultRegistry, MetricsOutboundTraffic+"/") // Registry containing the peer egress + + meteredPeerFeed event.Feed // Event feed for peer metrics + meteredPeerCount int32 // Actually stored peer connection count ) +// MeteredPeerEventType is the type of peer events emitted by a metered connection. +type MeteredPeerEventType int + +const ( + // PeerConnected is the type of event emitted when a peer successfully + // made the handshake. + PeerConnected MeteredPeerEventType = iota + + // PeerDisconnected is the type of event emitted when a peer disconnects. + PeerDisconnected + + // PeerHandshakeFailed is the type of event emitted when a peer fails to + // make the handshake or disconnects before the handshake. + PeerHandshakeFailed +) + +// MeteredPeerEvent is an event emitted when peers connect or disconnect. +type MeteredPeerEvent struct { + Type MeteredPeerEventType // Type of peer event + IP net.IP // IP address of the peer + ID string // NodeID of the peer + Elapsed time.Duration // Time elapsed between the connection and the handshake/disconnection + Ingress uint64 // Ingress count at the moment of the event + Egress uint64 // Egress count at the moment of the event +} + +// SubscribeMeteredPeerEvent registers a subscription for peer life-cycle events +// if metrics collection is enabled. +func SubscribeMeteredPeerEvent(ch chan<- MeteredPeerEvent) event.Subscription { + return meteredPeerFeed.Subscribe(ch) +} + // meteredConn is a wrapper around a net.Conn that meters both the // inbound and outbound network traffic. type meteredConn struct { net.Conn // Network connection to wrap with metering + + connected time.Time // Connection time of the peer + ip net.IP // IP address of the peer + id string // NodeID of the peer + + // trafficMetered denotes if the peer is registered in the traffic registries. + // Its value is true if the metered peer count doesn't reach the limit in the + // moment of the peer's connection. + trafficMetered bool + ingressMeter metrics.Meter // Meter for the read bytes of the peer + egressMeter metrics.Meter // Meter for the written bytes of the peer + + lock sync.RWMutex // Lock protecting the metered connection's internals } -// newMeteredConn creates a new metered connection, also bumping the ingress or -// egress connection meter. If the metrics system is disabled, this function -// returns the original object. -func newMeteredConn(conn net.Conn, ingress bool) net.Conn { +// newMeteredConn creates a new metered connection, bumps the ingress or egress +// connection meter and also increases the metered peer count. If the metrics +// system is disabled or the IP address is unspecified, this function returns +// the original object. +func newMeteredConn(conn net.Conn, ingress bool, ip net.IP) net.Conn { // Short circuit if metrics are disabled if !metrics.Enabled { return conn } - // Otherwise bump the connection counters and wrap the connection + if ip.IsUnspecified() { + log.Warn("Peer IP is unspecified") + return conn + } + // Bump the connection counters and wrap the connection if ingress { ingressConnectMeter.Mark(1) } else { egressConnectMeter.Mark(1) } - return &meteredConn{Conn: conn} + return &meteredConn{ + Conn: conn, + ip: ip, + connected: time.Now(), + } } -// Read delegates a network read to the underlying connection, bumping the ingress -// traffic meter along the way. +// Read delegates a network read to the underlying connection, bumping the common +// and the peer ingress traffic meters along the way. func (c *meteredConn) Read(b []byte) (n int, err error) { n, err = c.Conn.Read(b) ingressTrafficMeter.Mark(int64(n)) - return + c.lock.RLock() + if c.trafficMetered { + c.ingressMeter.Mark(int64(n)) + } + c.lock.RUnlock() + return n, err } -// Write delegates a network write to the underlying connection, bumping the -// egress traffic meter along the way. +// Write delegates a network write to the underlying connection, bumping the common +// and the peer egress traffic meters along the way. func (c *meteredConn) Write(b []byte) (n int, err error) { n, err = c.Conn.Write(b) egressTrafficMeter.Mark(int64(n)) - return + c.lock.RLock() + if c.trafficMetered { + c.egressMeter.Mark(int64(n)) + } + c.lock.RUnlock() + return n, err +} + +// handshakeDone is called when a peer handshake is done. Registers the peer to +// the ingress and the egress traffic registries using the peer's IP and node ID, +// also emits connect event. +func (c *meteredConn) handshakeDone(nodeID enode.ID) { + id := nodeID.String() + if atomic.AddInt32(&meteredPeerCount, 1) >= MeteredPeerLimit { + // Don't register the peer in the traffic registries. + atomic.AddInt32(&meteredPeerCount, -1) + c.lock.Lock() + c.id, c.trafficMetered = id, false + c.lock.Unlock() + log.Warn("Metered peer count reached the limit") + } else { + key := fmt.Sprintf("%s/%s", c.ip, id) + c.lock.Lock() + c.id, c.trafficMetered = id, true + c.ingressMeter = metrics.NewRegisteredMeter(key, PeerIngressRegistry) + c.egressMeter = metrics.NewRegisteredMeter(key, PeerEgressRegistry) + c.lock.Unlock() + } + meteredPeerFeed.Send(MeteredPeerEvent{ + Type: PeerConnected, + IP: c.ip, + ID: id, + Elapsed: time.Since(c.connected), + }) +} + +// Close delegates a close operation to the underlying connection, unregisters +// the peer from the traffic registries and emits close event. +func (c *meteredConn) Close() error { + err := c.Conn.Close() + c.lock.RLock() + if c.id == "" { + // If the peer disconnects before the handshake. + c.lock.RUnlock() + meteredPeerFeed.Send(MeteredPeerEvent{ + Type: PeerHandshakeFailed, + IP: c.ip, + Elapsed: time.Since(c.connected), + }) + return err + } + id := c.id + if !c.trafficMetered { + // If the peer isn't registered in the traffic registries. + c.lock.RUnlock() + meteredPeerFeed.Send(MeteredPeerEvent{ + Type: PeerDisconnected, + IP: c.ip, + ID: id, + }) + return err + } + ingress, egress := uint64(c.ingressMeter.Count()), uint64(c.egressMeter.Count()) + c.lock.RUnlock() + + // Decrement the metered peer count + atomic.AddInt32(&meteredPeerCount, -1) + + // Unregister the peer from the traffic registries + key := fmt.Sprintf("%s/%s", c.ip, id) + PeerIngressRegistry.Unregister(key) + PeerEgressRegistry.Unregister(key) + + meteredPeerFeed.Send(MeteredPeerEvent{ + Type: PeerDisconnected, + IP: c.ip, + ID: id, + Ingress: ingress, + Egress: egress, + }) + return err } diff --git a/p2p/server.go b/p2p/server.go index 7503fcfb0d..ec25012cfa 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -992,7 +992,11 @@ func (srv *Server) listenLoop() { } } - fd = newMeteredConn(fd, true) + var ip net.IP + if tcp, ok := fd.RemoteAddr().(*net.TCPAddr); ok { + ip = tcp.IP + } + fd = newMeteredConn(fd, true, ip) srv.log.Trace("Accepted connection", "addr", fd.RemoteAddr()) go func() { srv.SetupConn(fd, inboundConn, nil) @@ -1045,6 +1049,9 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro } else { c.node = nodeFromConn(remotePubkey, c.fd) } + if conn, ok := c.fd.(*meteredConn); ok { + conn.handshakeDone(c.node.ID()) + } clog := srv.log.New("id", c.node.ID(), "addr", c.fd.RemoteAddr(), "conn", c.flags) err = srv.checkpoint(c, srv.posthandshake) if err != nil { From 6962a140c201109b581af7b1ae5b62a4a1a08303 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 23 Aug 2021 10:14:28 +0800 Subject: [PATCH 005/299] p2p/protocols: accounting metrics rpc (#18336) p2p/simulations: eliminate concept of pivot (#18426) p2p/simulations: fix data race on swarm/network/simulations (#18464) p2p/testing, swarm: remove unused testing.T in protocol tester (#18500) p2p/protocols: fix rare data race in Peer.Handshake() (#18951) p2p/protocols: fix possible metrics loss in AccountingMetrics (#18956) p2p/discover, p2p/enode: rework endpoint proof handling, packet logging (#18963) p2p/discover: improve table addition code (#18974) --- cmd/alaya/consolecmd.go | 2 +- cmd/alaya/main.go | 1 + go.mod | 6 +- internal/web3ext/web3ext.go | 45 +++ metrics/counter.go | 30 ++ metrics/registry.go | 6 +- p2p/discover/node.go | 3 +- p2p/discover/table.go | 193 +++++++----- p2p/discover/table_test.go | 131 +++++++- p2p/discover/table_util_test.go | 21 +- p2p/discover/udp.go | 227 ++++++++------ p2p/discover/udp_test.go | 137 +++++--- p2p/discv5/net.go | 14 +- p2p/enode/idscheme.go | 7 +- p2p/enode/nodedb.go | 210 ++++++++----- p2p/enode/nodedb_test.go | 216 +++++++++---- p2p/metrics.go | 15 +- p2p/protocols/accounting.go | 182 +++++++++++ p2p/protocols/accounting_api.go | 94 ++++++ p2p/protocols/accounting_simulation_test.go | 320 +++++++++++++++++++ p2p/protocols/accounting_test.go | 223 +++++++++++++ p2p/protocols/protocol.go | 87 +++++- p2p/protocols/protocol_test.go | 214 ++++++++++++- p2p/protocols/reporter.go | 162 ++++++++++ p2p/protocols/repoter_test.go | 83 +++++ p2p/rlpx.go | 9 +- p2p/rlpx_test.go | 11 +- p2p/server.go | 2 +- p2p/server_test.go | 7 +- p2p/simulations/connect.go | 132 ++++++++ p2p/simulations/connect_test.go | 172 ++++++++++ p2p/simulations/http_test.go | 57 +++- p2p/simulations/network.go | 231 ++++++++++++-- p2p/simulations/network_test.go | 327 ++++++++++++++++++++ p2p/simulations/test.go | 134 ++++++++ p2p/testing/protocoltester.go | 3 +- 36 files changed, 3260 insertions(+), 454 deletions(-) create mode 100644 p2p/protocols/accounting.go create mode 100644 p2p/protocols/accounting_api.go create mode 100644 p2p/protocols/accounting_simulation_test.go create mode 100644 p2p/protocols/accounting_test.go create mode 100644 p2p/protocols/reporter.go create mode 100644 p2p/protocols/repoter_test.go create mode 100644 p2p/simulations/connect.go create mode 100644 p2p/simulations/connect_test.go create mode 100644 p2p/simulations/test.go diff --git a/cmd/alaya/consolecmd.go b/cmd/alaya/consolecmd.go index f2da2206d3..e1877001af 100644 --- a/cmd/alaya/consolecmd.go +++ b/cmd/alaya/consolecmd.go @@ -81,7 +81,7 @@ func localConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags node := makeFullNode(ctx) startNode(ctx, node) - defer node.Stop() + defer node.Close() // Attach to the newly started node and start the JavaScript console client, err := node.Attach() diff --git a/cmd/alaya/main.go b/cmd/alaya/main.go index 17de8fff60..1354183d66 100644 --- a/cmd/alaya/main.go +++ b/cmd/alaya/main.go @@ -291,6 +291,7 @@ func platon(ctx *cli.Context) error { return fmt.Errorf("invalid command: %q", args[0]) } node := makeFullNode(ctx) + defer node.Close() startNode(ctx, node) node.Wait() return nil diff --git a/go.mod b/go.mod index f0a80856ec..bafae6e21f 100644 --- a/go.mod +++ b/go.mod @@ -33,8 +33,8 @@ require ( github.com/julienschmidt/httprouter v1.3.0 github.com/karalabe/hid v0.0.0-20170821103837-f00545f9f374 github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mattn/go-colorable v0.0.8-0.20170210172801-5411d3eea597 - github.com/mattn/go-isatty v0.0.0-20170209175615-281032e84ae0 // indirect + github.com/mattn/go-colorable v0.1.8 + github.com/mattn/go-isatty v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/modern-go/reflect2 v1.0.1 github.com/mroth/weightedrand v0.3.0 @@ -60,7 +60,7 @@ require ( github.com/tealeg/xlsx v1.0.5 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/net v0.0.0-20200602114024-627f9648deb9 - golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c + golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 75a93a78b5..bffd3c9c13 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -18,6 +18,7 @@ package web3ext var Modules = map[string]string{ + "accounting": Accounting_JS, "admin": AdminJs, "chequebook": ChequebookJs, "clique": CliqueJs, @@ -673,3 +674,47 @@ web3._extend({ ] }); ` + +const Accounting_JS = ` +web3._extend({ + property: 'accounting', + methods: [ + new web3._extend.Property({ + name: 'balance', + getter: 'account_balance' + }), + new web3._extend.Property({ + name: 'balanceCredit', + getter: 'account_balanceCredit' + }), + new web3._extend.Property({ + name: 'balanceDebit', + getter: 'account_balanceDebit' + }), + new web3._extend.Property({ + name: 'bytesCredit', + getter: 'account_bytesCredit' + }), + new web3._extend.Property({ + name: 'bytesDebit', + getter: 'account_bytesDebit' + }), + new web3._extend.Property({ + name: 'msgCredit', + getter: 'account_msgCredit' + }), + new web3._extend.Property({ + name: 'msgDebit', + getter: 'account_msgDebit' + }), + new web3._extend.Property({ + name: 'peerDrops', + getter: 'account_peerDrops' + }), + new web3._extend.Property({ + name: 'selfDrops', + getter: 'account_selfDrops' + }), + ] +}); +` diff --git a/metrics/counter.go b/metrics/counter.go index c7f2b4bd3a..2ccb9fd9a3 100644 --- a/metrics/counter.go +++ b/metrics/counter.go @@ -20,6 +20,17 @@ func GetOrRegisterCounter(name string, r Registry) Counter { return r.GetOrRegister(name, NewCounter).(Counter) } +// GetOrRegisterCounterForced returns an existing Counter or constructs and registers a +// new Counter no matter the global switch is enabled or not. +// Be sure to unregister the counter from the registry once it is of no use to +// allow for garbage collection. +func GetOrRegisterCounterForced(name string, r Registry) Counter { + if nil == r { + r = DefaultRegistry + } + return r.GetOrRegister(name, NewCounterForced).(Counter) +} + // NewCounter constructs a new StandardCounter. func NewCounter() Counter { if !Enabled { @@ -28,6 +39,12 @@ func NewCounter() Counter { return &StandardCounter{0} } +// NewCounterForced constructs a new StandardCounter and returns it no matter if +// the global switch is enabled or not. +func NewCounterForced() Counter { + return &StandardCounter{0} +} + // NewRegisteredCounter constructs and registers a new StandardCounter. func NewRegisteredCounter(name string, r Registry) Counter { c := NewCounter() @@ -38,6 +55,19 @@ func NewRegisteredCounter(name string, r Registry) Counter { return c } +// NewRegisteredCounterForced constructs and registers a new StandardCounter +// and launches a goroutine no matter the global switch is enabled or not. +// Be sure to unregister the counter from the registry once it is of no use to +// allow for garbage collection. +func NewRegisteredCounterForced(name string, r Registry) Counter { + c := NewCounterForced() + if nil == r { + r = DefaultRegistry + } + r.Register(name, c) + return c +} + // CounterSnapshot is a read-only copy of another Counter. type CounterSnapshot int64 diff --git a/metrics/registry.go b/metrics/registry.go index cc34c9dfd2..c5435adf24 100644 --- a/metrics/registry.go +++ b/metrics/registry.go @@ -311,7 +311,11 @@ func (r *PrefixedRegistry) UnregisterAll() { r.underlying.UnregisterAll() } -var DefaultRegistry Registry = NewRegistry() +var ( + DefaultRegistry = NewRegistry() + EphemeralRegistry = NewRegistry() + AccountingRegistry = NewRegistry() // registry used in swarm +) // Call the given function for each registered metric. func Each(f func(string, interface{})) { diff --git a/p2p/discover/node.go b/p2p/discover/node.go index 1d41dd8886..48feee9438 100644 --- a/p2p/discover/node.go +++ b/p2p/discover/node.go @@ -34,7 +34,8 @@ import ( // The fields of Node may not be modified. type node struct { enode.Node - addedAt time.Time // time when the node was added to the table + addedAt time.Time // time when the node was added to the table + livenessChecks uint // how often liveness was checked } type encPubkey [64]byte diff --git a/p2p/discover/table.go b/p2p/discover/table.go index 5f1616b127..fab3e870e5 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -76,8 +76,10 @@ type Table struct { net transport refreshReq chan chan struct{} initDone chan struct{} - closeReq chan struct{} - closed chan struct{} + + closeOnce sync.Once + closeReq chan struct{} + closed chan struct{} nodeAddedHook func(*node) // for testing } @@ -181,16 +183,14 @@ func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) { // Close terminates the network listener and flushes the node database. func (tab *Table) Close() { - if tab.net != nil { - tab.net.close() - } - - select { - case <-tab.closed: - // already closed. - case tab.closeReq <- struct{}{}: - <-tab.closed // wait for refreshLoop to end. - } + tab.closeOnce.Do(func() { + if tab.net != nil { + tab.net.close() + } + // Wait for loop to end. + close(tab.closeReq) + <-tab.closed + }) } // setFallbackNodes sets the initial points of contact. These nodes @@ -291,12 +291,16 @@ func (tab *Table) lookup(targetKey encPubkey, refreshIfEmpty bool) []*node { // we have asked all closest nodes, stop the search break } - // wait for the next reply - for _, n := range <-reply { - if n != nil && !seen[n.ID()] { - seen[n.ID()] = true - result.push(n, bucketSize) + select { + case nodes := <-reply: + for _, n := range nodes { + if n != nil && !seen[n.ID()] { + seen[n.ID()] = true + result.push(n, bucketSize) + } } + case <-tab.closeReq: + return nil // shutdown, no need to continue. } pendingQueries-- } @@ -304,24 +308,28 @@ func (tab *Table) lookup(targetKey encPubkey, refreshIfEmpty bool) []*node { } func (tab *Table) findnode(n *node, targetKey encPubkey, reply chan<- []*node) { - fails := tab.db.FindFails(n.ID()) + fails := tab.db.FindFails(n.ID(), n.IP()) r, err := tab.net.findnode(n.ID(), n.addr(), targetKey) - if err != nil || len(r) == 0 { + if err == errClosed { + // Avoid recording failures on shutdown. + reply <- nil + return + } else if err != nil || len(r) == 0 { fails++ - tab.db.UpdateFindFails(n.ID(), fails) + tab.db.UpdateFindFails(n.ID(), n.IP(), 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) + tab.db.UpdateFindFails(n.ID(), n.IP(), 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) + tab.addSeenNode(n) } reply <- r } @@ -330,7 +338,7 @@ func (tab *Table) refresh() <-chan struct{} { done := make(chan struct{}) select { case tab.refreshReq <- done: - case <-tab.closed: + case <-tab.closeReq: close(done) } return done @@ -434,9 +442,9 @@ func (tab *Table) loadSeedNodes() { seeds = append(seeds, tab.nursery...) for i := range seeds { 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.LastPongReceived(seed.ID(), seed.IP())) }} log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age) - tab.add(seed) + tab.addSeenNode(seed) } } @@ -459,16 +467,17 @@ func (tab *Table) doRevalidate(done chan<- struct{}) { b := tab.buckets[bi] if err == nil { // The node responded, move it to the front. - log.Debug("Revalidated node", "b", bi, "id", last.ID()) - b.bump(last) + last.livenessChecks++ + log.Debug("Revalidated node", "b", bi, "id", last.ID(), "checks", last.livenessChecks) + tab.bumpInBucket(b, last) return } // No reply received, pick a replacement or delete the node if there aren't // any replacements. 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(), "checks", last.livenessChecks, "r", r.ID(), "rip", r.IP()) } 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(), "checks", last.livenessChecks) } } @@ -503,7 +512,7 @@ func (tab *Table) copyLiveNodes() { now := time.Now() for _, b := range &tab.buckets { for _, n := range b.entries { - if now.Sub(n.addedAt) >= seedMinTableTime { + if n.livenessChecks > 0 && now.Sub(n.addedAt) >= seedMinTableTime { tab.db.UpdateNode(unwrapNode(n)) } } @@ -519,7 +528,9 @@ func (tab *Table) closest(target enode.ID, nresults int) *nodesByDistance { close := &nodesByDistance{target: target} for _, b := range &tab.buckets { for _, n := range b.entries { - close.push(n, nresults) + if n.livenessChecks > 0 { + close.push(n, nresults) + } } } return close @@ -541,12 +552,12 @@ func (tab *Table) bucket(id enode.ID) *bucket { return tab.buckets[d-bucketMinDistance-1] } -// add attempts to add the given node to its corresponding bucket. If the bucket has space -// available, adding the node succeeds immediately. Otherwise, the node is added if the -// least recently active node in the bucket does not respond to a ping packet. +// addSeenNode adds a node which may or may not be live to the end of a bucket. If the +// bucket has space available, adding the node succeeds immediately. Otherwise, the node is +// added to the replacements list. // // The caller must not hold tab.mutex. -func (tab *Table) add(n *node) { +func (tab *Table) addSeenNode(n *node) { if n.ID() == tab.self().ID() { return } @@ -554,39 +565,67 @@ func (tab *Table) add(n *node) { tab.mutex.Lock() 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. + if contains(b.entries, n.ID()) { + // Already in bucket, don't add. + return + } + if len(b.entries) >= bucketSize { + // Bucket full, maybe add as replacement. tab.addReplacement(b, n) + return + } + if !tab.addIP(b, n.IP()) { + // Can't add: IP limit reached. + return + } + // Add to end of bucket: + b.entries = append(b.entries, n) + b.replacements = deleteNode(b.replacements, n) + n.addedAt = time.Now() + if tab.nodeAddedHook != nil { + tab.nodeAddedHook(n) } } -// addThroughPing adds the given node to the table. Compared to plain -// 'add' there is an additional safety measure: if the table is still -// initializing the node is not added. This prevents an attack where the -// table could be filled by just sending ping repeatedly. +// addVerifiedNode adds a node whose existence has been verified recently to the front of a +// bucket. If the node is already in the bucket, it is moved to the front. If the bucket +// has no space, the node is added to the replacements list. +// +// There is an additional safety measure: if the table is still initializing the node +// is not added. This prevents an attack where the table could be filled by just sending +// ping repeatedly. // // The caller must not hold tab.mutex. -func (tab *Table) addThroughPing(n *node) { +func (tab *Table) addVerifiedNode(n *node) { if !tab.isInitDone() { return } - tab.add(n) -} + if n.ID() == tab.self().ID() { + return + } -// 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. -func (tab *Table) stuff(nodes []*node) { tab.mutex.Lock() defer tab.mutex.Unlock() - - for _, n := range nodes { - if n.ID() == tab.self().ID() { - continue // don't add self - } - b := tab.bucket(n.ID()) - if len(b.entries) < bucketSize { - tab.bumpOrAdd(b, n) - } + b := tab.bucket(n.ID()) + if tab.bumpInBucket(b, n) { + // Already in bucket, moved to front. + return + } + if len(b.entries) >= bucketSize { + // Bucket full, maybe add as replacement. + tab.addReplacement(b, n) + return + } + if !tab.addIP(b, n.IP()) { + // Can't add: IP limit reached. + return + } + // Add to front of bucket. + b.entries, _ = pushNode(b.entries, n, bucketSize) + b.replacements = deleteNode(b.replacements, n) + n.addedAt = time.Now() + if tab.nodeAddedHook != nil { + tab.nodeAddedHook(n) } } @@ -658,12 +697,21 @@ func (tab *Table) replace(b *bucket, last *node) *node { return r } -// bump moves the given node to the front of the bucket entry list +// bumpInBucket moves the given node to the front of the bucket entry list // if it is contained in that list. -func (b *bucket) bump(n *node) bool { +func (tab *Table) bumpInBucket(b *bucket, n *node) bool { for i := range b.entries { if b.entries[i].ID() == n.ID() { - // move it to the front + if !n.IP().Equal(b.entries[i].IP()) { + // Endpoint has changed, ensure that the new IP fits into table limits. + tab.removeIP(b, b.entries[i].IP()) + if !tab.addIP(b, n.IP()) { + // It doesn't, put the previous one back. + tab.addIP(b, b.entries[i].IP()) + return false + } + } + // Move it to the front. copy(b.entries[1:], b.entries[:i]) b.entries[0] = n return true @@ -672,29 +720,20 @@ func (b *bucket) bump(n *node) bool { return false } -// 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. -func (tab *Table) bumpOrAdd(b *bucket, n *node) bool { - if b.bump(n) { - return true - } - if len(b.entries) >= bucketSize || !tab.addIP(b, n.IP()) { - return false - } - b.entries, _ = pushNode(b.entries, n, bucketSize) - b.replacements = deleteNode(b.replacements, n) - n.addedAt = time.Now() - if tab.nodeAddedHook != nil { - tab.nodeAddedHook(n) - } - return true -} - func (tab *Table) deleteInBucket(b *bucket, n *node) { b.entries = deleteNode(b.entries, n) tab.removeIP(b, n.IP()) } +func contains(ns []*node, id enode.ID) bool { + for _, n := range ns { + if n.ID() == id { + return true + } + } + return false +} + // pushNode adds n to the front of list, keeping at most max items. func pushNode(list []*node, n *node, max int) ([]*node, *node) { if len(list) < max { diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go index 0a3690d657..79975a0373 100644 --- a/p2p/discover/table_test.go +++ b/p2p/discover/table_test.go @@ -26,6 +26,8 @@ import ( "testing/quick" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/enr" @@ -50,8 +52,8 @@ func TestTable_pingReplace(t *testing.T) { func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding bool) { transport := newPingRecorder() tab, db := newTestTable(transport) - defer tab.Close() defer db.Close() + defer tab.Close() <-tab.initDone @@ -64,7 +66,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding // its bucket if it is unresponsive. Revalidate again to ensure that transport.dead[last.ID()] = !lastInBucketIsResponding transport.dead[pingSender.ID()] = !newNodeIsResponding - tab.add(pingSender) + tab.addSeenNode(pingSender) tab.doRevalidate(make(chan struct{}, 1)) tab.doRevalidate(make(chan struct{}, 1)) @@ -114,10 +116,14 @@ func TestBucket_bumpNoDuplicates(t *testing.T) { } prop := func(nodes []*node, bumps []int) (ok bool) { + tab, db := newTestTable(newPingRecorder()) + defer db.Close() + defer tab.Close() + b := &bucket{entries: make([]*node, len(nodes))} copy(b.entries, nodes) for i, pos := range bumps { - b.bump(b.entries[pos]) + tab.bumpInBucket(b, b.entries[pos]) if hasDuplicates(b.entries) { t.Logf("bucket has duplicates after %d/%d bumps:", i+1, len(bumps)) for _, n := range b.entries { @@ -126,6 +132,7 @@ func TestBucket_bumpNoDuplicates(t *testing.T) { return false } } + checkIPLimitInvariant(t, tab) return true } if err := quick.Check(prop, cfg); err != nil { @@ -137,33 +144,51 @@ func TestBucket_bumpNoDuplicates(t *testing.T) { func TestTable_IPLimit(t *testing.T) { transport := newPingRecorder() tab, db := newTestTable(transport) - defer tab.Close() defer db.Close() + defer tab.Close() for i := 0; i < tableIPLimit+1; i++ { n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)}) - tab.add(n) + tab.addSeenNode(n) } if tab.len() > tableIPLimit { t.Errorf("too many nodes in table") } + checkIPLimitInvariant(t, tab) } // This checks that the per-bucket IP limit is applied correctly. func TestTable_BucketIPLimit(t *testing.T) { transport := newPingRecorder() tab, db := newTestTable(transport) - defer tab.Close() defer db.Close() + defer tab.Close() d := 3 for i := 0; i < bucketIPLimit+1; i++ { n := nodeAtDistance(tab.self().ID(), d, net.IP{172, 0, 1, byte(i)}) - tab.add(n) + tab.addSeenNode(n) } if tab.len() > bucketIPLimit { t.Errorf("too many nodes in table") } + checkIPLimitInvariant(t, tab) +} + +// checkIPLimitInvariant checks that ip limit sets contain an entry for every +// node in the table and no extra entries. +func checkIPLimitInvariant(t *testing.T, tab *Table) { + t.Helper() + + tabset := netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit} + for _, b := range tab.buckets { + for _, n := range b.entries { + tabset.Add(n.IP()) + } + } + if tabset.String() != tab.ips.String() { + t.Errorf("table IP set is incorrect:\nhave: %v\nwant: %v", tab.ips, tabset) + } } func TestTable_closest(t *testing.T) { @@ -173,9 +198,9 @@ func TestTable_closest(t *testing.T) { // for any node table, Target and N transport := newPingRecorder() tab, db := newTestTable(transport) - defer tab.Close() defer db.Close() - tab.stuff(test.All) + defer tab.Close() + fillTable(tab, test.All) // check that closest(Target, N) returns nodes result := tab.closest(test.Target, test.N).entries @@ -234,13 +259,13 @@ func TestTable_ReadRandomNodesGetAll(t *testing.T) { test := func(buf []*enode.Node) bool { transport := newPingRecorder() tab, db := newTestTable(transport) - defer tab.Close() defer db.Close() + defer tab.Close() <-tab.initDone for i := 0; i < len(buf); i++ { ld := cfg.Rand.Intn(len(tab.buckets)) - tab.stuff([]*node{nodeAtDistance(tab.self().ID(), ld, intIP(ld))}) + fillTable(tab, []*node{nodeAtDistance(tab.self().ID(), ld, intIP(ld))}) } gotN := tab.ReadRandomNodes(buf) if gotN != tab.len() { @@ -272,16 +297,82 @@ func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value { N: rand.Intn(bucketSize), } for _, id := range gen([]enode.ID{}, rand).([]enode.ID) { - n := enode.SignNull(new(enr.Record), id) - t.All = append(t.All, wrapNode(n)) + r := new(enr.Record) + r.Set(enr.IP(genIP(rand))) + n := wrapNode(enode.SignNull(r, id)) + n.livenessChecks = 1 + t.All = append(t.All, n) } return reflect.ValueOf(t) } +func TestTable_addVerifiedNode(t *testing.T) { + tab, db := newTestTable(newPingRecorder()) + <-tab.initDone + defer db.Close() + defer tab.Close() + + // Insert two nodes. + n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1}) + n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2}) + tab.addSeenNode(n1) + tab.addSeenNode(n2) + + // Verify bucket content: + bcontent := []*node{n1, n2} + if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) { + t.Fatalf("wrong bucket content: %v", tab.bucket(n1.ID()).entries) + } + + // Add a changed version of n2. + newrec := n2.Record() + newrec.Set(enr.IP{99, 99, 99, 99}) + newn2 := wrapNode(enode.SignNull(newrec, n2.ID())) + tab.addVerifiedNode(newn2) + + // Check that bucket is updated correctly. + newBcontent := []*node{newn2, n1} + if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, newBcontent) { + t.Fatalf("wrong bucket content after update: %v", tab.bucket(n1.ID()).entries) + } + checkIPLimitInvariant(t, tab) +} + +func TestTable_addSeenNode(t *testing.T) { + tab, db := newTestTable(newPingRecorder()) + <-tab.initDone + defer db.Close() + defer tab.Close() + + // Insert two nodes. + n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1}) + n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2}) + tab.addSeenNode(n1) + tab.addSeenNode(n2) + + // Verify bucket content: + bcontent := []*node{n1, n2} + if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) { + t.Fatalf("wrong bucket content: %v", tab.bucket(n1.ID()).entries) + } + + // Add a changed version of n2. + newrec := n2.Record() + newrec.Set(enr.IP{99, 99, 99, 99}) + newn2 := wrapNode(enode.SignNull(newrec, n2.ID())) + tab.addSeenNode(newn2) + + // Check that bucket content is unchanged. + if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) { + t.Fatalf("wrong bucket content after update: %v", tab.bucket(n1.ID()).entries) + } + checkIPLimitInvariant(t, tab) +} + func TestTable_Lookup(t *testing.T) { tab, db := newTestTable(lookupTestnet) - defer tab.Close() defer db.Close() + defer tab.Close() // lookup on empty table returns no nodes if results := tab.lookup(lookupTestnet.target, false); len(results) > 0 { @@ -289,8 +380,9 @@ func TestTable_Lookup(t *testing.T) { } // seed table with initial node (otherwise lookup will terminate immediately) seedKey, _ := decodePubkey(lookupTestnet.dists[256][0]) - seed := wrapNode(enode.NewV4(seedKey, net.IP{}, 0, 256)) - tab.stuff([]*node{seed}) + seed := wrapNode(enode.NewV4(seedKey, net.IP{127, 0, 0, 1}, 0, 256)) + seed.livenessChecks = 1 + fillTable(tab, []*node{seed}) results := tab.lookup(lookupTestnet.target, true) t.Logf("results:") @@ -531,7 +623,6 @@ func (tn *preminedTestnet) findnode(toid enode.ID, toaddr *net.UDPAddr, target e } func (*preminedTestnet) close() {} -func (*preminedTestnet) waitping(from enode.ID) error { return nil } func (*preminedTestnet) ping(toid enode.ID, toaddr *net.UDPAddr) error { return nil } // mine generates a testnet struct literal with nodes at @@ -578,6 +669,12 @@ func gen(typ interface{}, rand *rand.Rand) interface{} { return v.Interface() } +func genIP(rand *rand.Rand) net.IP { + ip := make(net.IP, 4) + rand.Read(ip) + return ip +} + func quickcfg() *quick.Config { return &quick.Config{ MaxCount: 5000, diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go index 9e75352fff..b02aa81c6c 100644 --- a/p2p/discover/table_util_test.go +++ b/p2p/discover/table_util_test.go @@ -83,6 +83,14 @@ func fillBucket(tab *Table, n *node) (last *node) { return b.entries[bucketSize-1] } +// fillTable adds nodes the table to the end of their corresponding bucket +// if the bucket is not full. The caller must not hold tab.mutex. +func fillTable(tab *Table, nodes []*node) { + for _, n := range nodes { + tab.addSeenNode(n) + } +} + type pingRecorder struct { mu sync.Mutex dead, pinged map[enode.ID]bool @@ -109,10 +117,6 @@ func (t *pingRecorder) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPu 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() @@ -141,15 +145,6 @@ func hasDuplicates(slice []*node) bool { 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 { diff --git a/p2p/discover/udp.go b/p2p/discover/udp.go index ca721d3619..458fd19ded 100644 --- a/p2p/discover/udp.go +++ b/p2p/discover/udp.go @@ -75,6 +75,8 @@ var ( // RPC request structures type ( ping struct { + senderKey *ecdsa.PublicKey // filled in by preverify + Version uint From, To rpcEndpoint Expiration uint64 @@ -163,8 +165,13 @@ func nodeToRPC(n *node) rpcNode { return rpcNode{ID: ekey, IP: n.IP(), UDP: uint16(n.UDP()), TCP: uint16(n.TCP())} } +// packet is implemented by all protocol messages. type packet interface { - handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error + // preverify checks whether the packet is valid and should be handled at all. + preverify(t *udp, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error + // handle handles the packet. + handle(t *udp, from *net.UDPAddr, fromID enode.ID, mac []byte) + // name returns the name of the packet for logging purposes. name() string } @@ -185,43 +192,48 @@ type udp struct { tab *Table wg sync.WaitGroup - addpending chan *pending - gotreply chan reply - closing chan struct{} + addReplyMatcher chan *replyMatcher + gotreply chan reply + closing chan struct{} } // pending represents a pending reply. // -// some implementations of the protocol wish to send more than one -// reply packet to findnode. in general, any neighbors packet cannot +// Some implementations of the protocol wish to send more than one +// reply packet to findnode. In general, any neighbors packet cannot // be matched up with a specific findnode packet. // -// our implementation handles this by storing a callback function for -// each pending reply. incoming packets from a node are dispatched -// to all the callback functions for that node. -type pending struct { +// Our implementation handles this by storing a callback function for +// each pending reply. Incoming packets from a node are dispatched +// to all callback functions for that node. +type replyMatcher struct { // these fields must match in the reply. from enode.ID + ip net.IP ptype byte // time when the request must complete deadline time.Time - // callback is called when a matching reply arrives. if it returns - // true, the callback is removed from the pending reply queue. - // if it returns false, the reply is considered incomplete and - // the callback will be invoked again for the next matching reply. - callback func(resp interface{}) (done bool) + // callback is called when a matching reply arrives. If it returns matched == true, the + // reply was acceptable. The second return value indicates whether the callback should + // be removed from the pending reply queue. If it returns false, the reply is considered + // incomplete and the callback will be invoked again for the next matching reply. + callback replyMatchFunc // errc receives nil when the callback indicates completion or an // error if no further reply is received within the timeout. errc chan<- error } +type replyMatchFunc func(interface{}) (matched bool, requestDone bool) + type reply struct { from enode.ID + ip net.IP ptype byte - data interface{} + data packet + // loop indicates whether there was // a matching request by sending on this channel. matched chan<- bool @@ -266,14 +278,14 @@ func ListenUDP(c conn, ln *enode.LocalNode, cfg Config) (*Table, error) { func newUDP(c conn, ln *enode.LocalNode, cfg Config) (*Table, *udp, error) { udp := &udp{ - conn: c, - priv: cfg.PrivateKey, - netrestrict: cfg.NetRestrict, - localNode: ln, - db: ln.Database(), - closing: make(chan struct{}), - gotreply: make(chan reply), - addpending: make(chan *pending), + conn: c, + priv: cfg.PrivateKey, + netrestrict: cfg.NetRestrict, + localNode: ln, + db: ln.Database(), + closing: make(chan struct{}), + gotreply: make(chan reply), + addReplyMatcher: make(chan *replyMatcher), } tab, err := newTable(udp, ln.Database(), cfg.Bootnodes) if err != nil { @@ -324,35 +336,37 @@ func (t *udp) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) <-ch errc <- err return errc } - errc := t.pending(toid, pongPacket, func(p interface{}) bool { - ok := bytes.Equal(p.(*pong).ReplyTok, hash) - if ok && callback != nil { + // Add a matcher for the reply to the pending reply queue. Pongs are matched if they + // reference the ping we're about to send. + errc := t.pending(toid, toaddr.IP, pongPacket, func(p interface{}) (matched bool, requestDone bool) { + matched = bytes.Equal(p.(*pong).ReplyTok, hash) + if matched && callback != nil { callback() } - return ok + return matched, matched }) + // Send the packet. t.localNode.UDPContact(toaddr) - t.write(toaddr, req.name(), packet) + t.write(toaddr, toid, req.name(), packet) return errc } -func (t *udp) waitping(from enode.ID) error { - return <-t.pending(from, pingPacket, func(interface{}) bool { return true }) -} - // findnode sends a findnode request to the given node and waits until // the node has sent up to k neighbors. func (t *udp) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) { // If we haven't seen a ping from the destination node for a while, it won't remember // our endpoint proof and reject findnode. Solicit a ping first. - if time.Since(t.db.LastPingReceived(toid)) > bondExpiration { + if time.Since(t.db.LastPingReceived(toid, toaddr.IP)) > bondExpiration { t.ping(toid, toaddr) - t.waitping(toid) + // Wait for them to ping back and process our pong. + time.Sleep(respTimeout) } + // Add a matcher for 'neighbours' replies to the pending reply queue. The matcher is + // active until enough nodes have been received. nodes := make([]*node, 0, bucketSize) nreceived := 0 - errc := t.pending(toid, neighborsPacket, func(r interface{}) bool { + errc := t.pending(toid, toaddr.IP, neighborsPacket, func(r interface{}) (matched bool, requestDone bool) { reply := r.(*neighbors) for _, rn := range reply.Nodes { nreceived++ @@ -363,9 +377,9 @@ func (t *udp) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([] } nodes = append(nodes, n) } - return nreceived >= bucketSize + return true, nreceived >= bucketSize }) - t.send(toaddr, findnodePacket, &findnode{ + t.send(toaddr, toid, findnodePacket, &findnode{ Target: target, Expiration: uint64(time.Now().Add(expiration).Unix()), Rest: cRest, @@ -373,13 +387,13 @@ func (t *udp) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([] return nodes, <-errc } -// pending adds a reply callback to the pending reply queue. -// 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 { +// pending adds a reply matcher to the pending reply queue. +// see the documentation of type replyMatcher for a detailed explanation. +func (t *udp) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchFunc) <-chan error { ch := make(chan error, 1) - p := &pending{from: id, ptype: ptype, callback: callback, errc: ch} + p := &replyMatcher{from: id, ip: ip, ptype: ptype, callback: callback, errc: ch} select { - case t.addpending <- p: + case t.addReplyMatcher <- p: // loop will handle it case <-t.closing: ch <- errClosed @@ -387,10 +401,12 @@ func (t *udp) pending(id enode.ID, ptype byte, callback func(interface{}) bool) return ch } -func (t *udp) handleReply(from enode.ID, ptype byte, req packet) bool { +// handleReply dispatches a reply packet, invoking reply matchers. It returns +// whether any matcher considered the packet acceptable. +func (t *udp) handleReply(from enode.ID, fromIP net.IP, ptype byte, req packet) bool { matched := make(chan bool, 1) select { - case t.gotreply <- reply{from, ptype, req, matched}: + case t.gotreply <- reply{from, fromIP, ptype, req, matched}: // loop will handle it return <-matched case <-t.closing: @@ -406,8 +422,8 @@ func (t *udp) loop() { var ( plist = list.New() timeout = time.NewTimer(0) - nextTimeout *pending // head of plist when timeout was last reset - contTimeouts = 0 // number of continuous timeouts to do NTP checks + nextTimeout *replyMatcher // head of plist when timeout was last reset + contTimeouts = 0 // number of continuous timeouts to do NTP checks ntpWarnTime = time.Unix(0, 0) ) <-timeout.C // ignore first timeout @@ -420,7 +436,7 @@ func (t *udp) loop() { // Start the timer so it fires when the next pending reply has expired. now := time.Now() for el := plist.Front(); el != nil; el = el.Next() { - nextTimeout = el.Value.(*pending) + nextTimeout = el.Value.(*replyMatcher) if dist := nextTimeout.deadline.Sub(now); dist < 2*respTimeout { timeout.Reset(dist) return @@ -441,25 +457,23 @@ func (t *udp) loop() { select { case <-t.closing: for el := plist.Front(); el != nil; el = el.Next() { - el.Value.(*pending).errc <- errClosed + el.Value.(*replyMatcher).errc <- errClosed } return - case p := <-t.addpending: + case p := <-t.addReplyMatcher: p.deadline = time.Now().Add(respTimeout) plist.PushBack(p) case r := <-t.gotreply: - var matched bool + var matched bool // whether any replyMatcher considered the reply acceptable. for el := plist.Front(); el != nil; el = el.Next() { - p := el.Value.(*pending) - if p.from == r.from && p.ptype == r.ptype { - matched = true - // Remove the matcher if its callback indicates - // that all replies have been received. This is - // required for packet types that expect multiple - // reply packets. - if p.callback(r.data) { + p := el.Value.(*replyMatcher) + if p.from == r.from && p.ptype == r.ptype && p.ip.Equal(r.ip) { + ok, requestDone := p.callback(r.data) + matched = matched || ok + // Remove the matcher if callback indicates that all replies have been received. + if requestDone { p.errc <- nil plist.Remove(el) } @@ -474,7 +488,7 @@ func (t *udp) loop() { // Notify and remove callbacks whose deadline is in the past. for el := plist.Front(); el != nil; el = el.Next() { - p := el.Value.(*pending) + p := el.Value.(*replyMatcher) if now.After(p.deadline) || now.Equal(p.deadline) { p.errc <- errTimeout plist.Remove(el) @@ -525,17 +539,17 @@ func init() { } } -func (t *udp) send(toaddr *net.UDPAddr, ptype byte, req packet) ([]byte, error) { +func (t *udp) send(toaddr *net.UDPAddr, toid enode.ID, ptype byte, req packet) ([]byte, error) { packet, hash, err := encodePacket(t.priv, ptype, req) if err != nil { return hash, err } - return hash, t.write(toaddr, req.name(), packet) + return hash, t.write(toaddr, toid, req.name(), packet) } -func (t *udp) write(toaddr *net.UDPAddr, what string, packet []byte) error { +func (t *udp) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet []byte) error { _, err := t.conn.WriteToUDP(packet, toaddr) - log.Trace(">> "+what, "addr", toaddr, "err", err) + log.Trace(">> "+what, "id", toid, "addr", toaddr, "err", err) return err } @@ -594,13 +608,19 @@ func (t *udp) readLoop(unhandled chan<- ReadPacket) { } func (t *udp) handlePacket(from *net.UDPAddr, buf []byte) error { - packet, fromID, hash, err := decodePacket(buf) + packet, fromKey, hash, err := decodePacket(buf) if err != nil { log.Debug("Bad discv4 packet", "addr", from, "err", err) return err } - err = packet.handle(t, from, fromID, hash) - log.Trace("<< "+packet.name(), "addr", from, "err", err) + fromID := fromKey.id() + if err == nil { + err = packet.preverify(t, from, fromID, fromKey) + } + log.Trace("<< "+packet.name(), "id", fromID, "addr", from, "err", err) + if err == nil { + packet.handle(t, from, fromID, hash) + } return err } @@ -636,7 +656,9 @@ func decodePacket(buf []byte) (packet, encPubkey, []byte, error) { return req, fromKey, hash, err } -func (req *ping) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error { +// Packet Handlers + +func (req *ping) preverify(t *udp, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { if expired(req.Expiration) { return errExpired } @@ -647,55 +669,63 @@ func (req *ping) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte key, err := decodePubkey(fromKey) if err != nil { - return fmt.Errorf("invalid public key: %v", err) + return errors.New("invalid public key") } - t.send(from, pongPacket, &pong{ + req.senderKey = key + return nil +} + +func (req *ping) handle(t *udp, from *net.UDPAddr, fromID enode.ID, mac []byte) { + // Reply. + t.send(from, fromID, pongPacket, &pong{ To: makeEndpoint(from, req.From.TCP), ReplyTok: mac, Expiration: uint64(time.Now().Add(expiration).Unix()), Rest: cRest, }) - n := wrapNode(enode.NewV4(key, from.IP, int(req.From.TCP), from.Port)) - t.handleReply(n.ID(), pingPacket, req) - if time.Since(t.db.LastPongReceived(n.ID())) > bondExpiration { - t.sendPing(n.ID(), from, func() { t.tab.addThroughPing(n) }) + + // Ping back if our last pong on file is too far in the past. + n := wrapNode(enode.NewV4(req.senderKey, from.IP, int(req.From.TCP), from.Port)) + if time.Since(t.db.LastPongReceived(n.ID(), from.IP)) > bondExpiration { + t.sendPing(fromID, from, func() { + t.tab.addVerifiedNode(n) + }) } else { - t.tab.addThroughPing(n) + t.tab.addVerifiedNode(n) } + + // Update node database and endpoint predictor. + t.db.UpdateLastPingReceived(n.ID(), from.IP, time.Now()) t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)}) - t.db.UpdateLastPingReceived(n.ID(), time.Now()) - return nil } func (req *ping) name() string { return "PING/v4" } -func (req *pong) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error { +func (req *pong) preverify(t *udp, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { if expired(req.Expiration) { return errExpired } - if !reflect.DeepEqual(req.Rest, cRest) { - return errData - } - fromID := fromKey.id() - if !t.handleReply(fromID, pongPacket, req) { + if !t.handleReply(fromID, from.IP, pongPacket, req) { 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 } +func (req *pong) handle(t *udp, from *net.UDPAddr, fromID enode.ID, mac []byte) { + t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)}) + t.db.UpdateLastPongReceived(fromID, from.IP, time.Now()) +} + 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) preverify(t *udp, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { if expired(req.Expiration) { return errExpired } if !reflect.DeepEqual(req.Rest, cRest) { return errData } - fromID := fromKey.id() - if time.Since(t.db.LastPongReceived(fromID)) > bondExpiration { + if time.Since(t.db.LastPongReceived(fromID, from.IP)) > bondExpiration { // No endpoint proof pong exists, we don't process the packet. This prevents an // attack vector where the discovery protocol could be used to amplify traffic in a // DDOS attack. A malicious actor would send a findnode request with the IP address @@ -704,49 +734,56 @@ func (req *findnode) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac [] // findnode) to the victim. return errUnknownNode } + return nil +} + +func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID enode.ID, mac []byte) { + // Determine closest nodes. target := enode.ID(crypto.Keccak256Hash(req.Target[:])) t.tab.mutex.Lock() closest := t.tab.closest(target, bucketSize).entries t.tab.mutex.Unlock() + // Send neighbors in chunks with at most maxNeighbors per packet + // to stay below the 1280 byte limit. p := neighbors{ Expiration: uint64(time.Now().Add(expiration).Unix()), Rest: cRest, } var sent bool - // Send neighbors in chunks with at most maxNeighbors per packet - // to stay below the 1280 byte limit. for _, n := range closest { if netutil.CheckRelayIP(from.IP, n.IP()) == nil { p.Nodes = append(p.Nodes, nodeToRPC(n)) } if len(p.Nodes) == maxNeighbors { - t.send(from, neighborsPacket, &p) + t.send(from, fromID, neighborsPacket, &p) p.Nodes = p.Nodes[:0] sent = true } } if len(p.Nodes) > 0 || !sent { - t.send(from, neighborsPacket, &p) + t.send(from, fromID, neighborsPacket, &p) } - return nil } 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) preverify(t *udp, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { if expired(req.Expiration) { return errExpired } if !reflect.DeepEqual(req.Rest, cRest) { return errData } - if !t.handleReply(fromKey.id(), neighborsPacket, req) { + if !t.handleReply(fromID, from.IP, neighborsPacket, req) { return errUnsolicitedReply } return nil } +func (req *neighbors) handle(t *udp, from *net.UDPAddr, fromID enode.ID, mac []byte) { +} + func (req *neighbors) name() string { return "NEIGHBORS/v4" } func expired(ts uint64) bool { diff --git a/p2p/discover/udp_test.go b/p2p/discover/udp_test.go index 077c98e8ee..de0f74ee7e 100644 --- a/p2p/discover/udp_test.go +++ b/p2p/discover/udp_test.go @@ -19,6 +19,7 @@ package discover import ( "bytes" "crypto/ecdsa" + crand "crypto/rand" "encoding/binary" "encoding/hex" "errors" @@ -59,6 +60,7 @@ type udpTest struct { t *testing.T pipe *dgramPipe table *Table + db *enode.DB udp *udp sent [][]byte localkey, remotekey *ecdsa.PrivateKey @@ -73,22 +75,32 @@ func newUDPTest(t *testing.T) *udpTest { remotekey: newkey(), remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 16789}, } - db, _ := enode.OpenDB("") - ln := enode.NewLocalNode(db, test.localkey) + test.db, _ = enode.OpenDB("") + ln := enode.NewLocalNode(test.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. <-test.table.initDone return test } +func (test *udpTest) close() { + test.table.Close() + test.db.Close() +} + // handles a packet as if it had been sent to the transport. func (test *udpTest) packetIn(wantError error, ptype byte, data packet) error { - enc, _, err := encodePacket(test.remotekey, ptype, data) + return test.packetInFrom(wantError, test.remotekey, test.remoteaddr, ptype, data) +} + +// handles a packet as if it had been sent to the transport by the key/endpoint. +func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *net.UDPAddr, ptype byte, data packet) error { + enc, _, err := encodePacket(key, ptype, data) if err != nil { return test.errorf("packet (%d) encode error: %v", ptype, err) } test.sent = append(test.sent, enc) - if err = test.udp.handlePacket(test.remoteaddr, enc); err != wantError { + if err = test.udp.handlePacket(addr, enc); err != wantError { return test.errorf("error mismatch: got %q, want %q", err, wantError) } return nil @@ -96,19 +108,19 @@ func (test *udpTest) packetIn(wantError error, ptype byte, data packet) error { // waits for a packet to be sent by the transport. // validate should have type func(*udpTest, X) error, where X is a packet type. -func (test *udpTest) waitPacketOut(validate interface{}) ([]byte, error) { +func (test *udpTest) waitPacketOut(validate interface{}) (*net.UDPAddr, []byte, error) { dgram := test.pipe.waitPacketOut() - p, _, hash, err := decodePacket(dgram) + p, _, hash, err := decodePacket(dgram.data) if err != nil { - return hash, test.errorf("sent packet decode error: %v", err) + return &dgram.to, hash, test.errorf("sent packet decode error: %v", err) } fn := reflect.ValueOf(validate) exptype := fn.Type().In(0) if reflect.TypeOf(p) != exptype { - return hash, test.errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype) + return &dgram.to, hash, test.errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype) } fn.Call([]reflect.Value{reflect.ValueOf(p)}) - return hash, nil + return &dgram.to, hash, nil } func (test *udpTest) errorf(format string, args ...interface{}) error { @@ -127,7 +139,7 @@ func (test *udpTest) errorf(format string, args ...interface{}) error { func TestUDP_packetErrors(t *testing.T) { test := newUDPTest(t) - defer test.table.Close() + defer test.close() test.packetIn(errExpired, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: 4, Rest: cRest}) test.packetIn(errUnsolicitedReply, pongPacket, &pong{ReplyTok: []byte{}, Expiration: futureExp, Rest: cRest}) @@ -138,7 +150,7 @@ func TestUDP_packetErrors(t *testing.T) { func TestUDP_pingTimeout(t *testing.T) { t.Parallel() test := newUDPTest(t) - defer test.table.Close() + defer test.close() toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} toid := enode.ID{1, 2, 3, 4} @@ -150,7 +162,7 @@ func TestUDP_pingTimeout(t *testing.T) { func TestUDP_responseTimeouts(t *testing.T) { t.Parallel() test := newUDPTest(t) - defer test.table.Close() + defer test.close() rand.Seed(time.Now().UnixNano()) randomDuration := func(max time.Duration) time.Duration { @@ -168,20 +180,20 @@ func TestUDP_responseTimeouts(t *testing.T) { // with ptype <= 128 will not get a reply and should time out. // For all other requests, a reply is scheduled to arrive // within the timeout window. - p := &pending{ + p := &replyMatcher{ ptype: byte(rand.Intn(255)), - callback: func(interface{}) bool { return true }, + callback: func(interface{}) (bool, bool) { return true, true }, } binary.BigEndian.PutUint64(p.from[:], uint64(i)) if p.ptype <= 128 { p.errc = timeoutErr - test.udp.addpending <- p + test.udp.addReplyMatcher <- p nTimeouts++ } else { p.errc = nilErr - test.udp.addpending <- p + test.udp.addReplyMatcher <- p time.AfterFunc(randomDuration(60*time.Millisecond), func() { - if !test.udp.handleReply(p.from, p.ptype, nil) { + if !test.udp.handleReply(p.from, p.ip, p.ptype, nil) { t.Logf("not matched: %v", p) } }) @@ -222,7 +234,7 @@ func TestUDP_responseTimeouts(t *testing.T) { func TestUDP_findnodeTimeout(t *testing.T) { t.Parallel() test := newUDPTest(t) - defer test.table.Close() + defer test.close() toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} toid := enode.ID{1, 2, 3, 4} @@ -238,50 +250,65 @@ func TestUDP_findnodeTimeout(t *testing.T) { func TestUDP_findnode(t *testing.T) { test := newUDPTest(t) - defer test.table.Close() + defer test.close() // put a few nodes into the table. their exact // distribution shouldn't matter much, although we need to // take care not to overflow any bucket. nodes := &nodesByDistance{target: testTarget.id()} - for i := 0; i < bucketSize; i++ { + live := make(map[enode.ID]bool) + numCandidates := 2 * bucketSize + for i := 0; i < numCandidates; i++ { key := newkey() - n := wrapNode(enode.NewV4(&key.PublicKey, net.IP{10, 13, 0, 1}, 0, i)) - nodes.push(n, bucketSize) + ip := net.IP{10, 13, 0, byte(i)} + n := wrapNode(enode.NewV4(&key.PublicKey, ip, 0, 2000)) + // Ensure half of table content isn't verified live yet. + if i > numCandidates/2 { + n.livenessChecks = 1 + live[n.ID()] = true + } + nodes.push(n, numCandidates) } - test.table.stuff(nodes.entries) + fillTable(test.table, nodes.entries) // ensure there's a bond with the test node, // findnode won't be accepted otherwise. remoteID := encodePubkey(&test.remotekey.PublicKey).id() - test.table.db.UpdateLastPongReceived(remoteID, time.Now()) + test.table.db.UpdateLastPongReceived(remoteID, test.remoteaddr.IP, time.Now()) // check that closest neighbors are returned. - test.packetIn(nil, findnodePacket, &findnode{Target: testTarget, Expiration: futureExp, Rest: cRest}) expected := test.table.closest(testTarget.id(), bucketSize) - + test.packetIn(nil, findnodePacket, &findnode{Target: testTarget, Expiration: futureExp, Rest: cRest}) waitNeighbors := func(want []*node) { test.waitPacketOut(func(p *neighbors) { if len(p.Nodes) != len(want) { t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSize) } - for i := range p.Nodes { - if p.Nodes[i].ID.id() != want[i].ID() { - t.Errorf("result mismatch at %d:\n got: %v\n want: %v", i, p.Nodes[i], expected.entries[i]) + for i, n := range p.Nodes { + if n.ID.id() != want[i].ID() { + t.Errorf("result mismatch at %d:\n got: %v\n want: %v", i, n, expected.entries[i]) + } + if !live[n.ID.id()] { + t.Errorf("result includes dead node %v", n.ID.id()) } } }) } - waitNeighbors(expected.entries[:maxNeighbors]) - waitNeighbors(expected.entries[maxNeighbors:]) + // Receive replies. + want := expected.entries + if len(want) > maxNeighbors { + waitNeighbors(want[:maxNeighbors]) + want = want[maxNeighbors:] + } + waitNeighbors(want) } func TestUDP_findnodeMultiReply(t *testing.T) { test := newUDPTest(t) - defer test.table.Close() + defer test.close() rid := enode.PubkeyToIDV4(&test.remotekey.PublicKey) - test.table.db.UpdateLastPingReceived(rid, time.Now()) + test.table.db.UpdateLastPingReceived(rid, test.remoteaddr.IP, time.Now()) // queue a pending findnode request resultc, errc := make(chan []*node), make(chan error) @@ -331,11 +358,40 @@ func TestUDP_findnodeMultiReply(t *testing.T) { } } +func TestUDP_pingMatch(t *testing.T) { + test := newUDPTest(t) + defer test.close() + + randToken := make([]byte, 32) + crand.Read(randToken) + + test.packetIn(nil, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) + test.waitPacketOut(func(*pong) error { return nil }) + test.waitPacketOut(func(*ping) error { return nil }) + test.packetIn(errUnsolicitedReply, pongPacket, &pong{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp}) +} + +func TestUDP_pingMatchIP(t *testing.T) { + test := newUDPTest(t) + defer test.close() + + test.packetIn(nil, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) + test.waitPacketOut(func(*pong) error { return nil }) + + _, hash, _ := test.waitPacketOut(func(*ping) error { return nil }) + wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 1, 2}, Port: 30000} + test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, pongPacket, &pong{ + ReplyTok: hash, + To: testLocalAnnounced, + Expiration: futureExp, + }) +} + func TestUDP_successfulPing(t *testing.T) { test := newUDPTest(t) added := make(chan *node, 1) test.table.nodeAddedHook = func(n *node) { added <- n } - defer test.table.Close() + defer test.close() // The remote side sends a ping packet to initiate the exchange. go test.packetIn(nil, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp, Rest: cRest}) @@ -358,7 +414,7 @@ func TestUDP_successfulPing(t *testing.T) { }) // 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()) { t.Errorf("got ping.From %#v, want %#v", p.From, test.udp.ourEndpoint()) } @@ -512,7 +568,12 @@ type dgramPipe struct { cond *sync.Cond closing chan struct{} closed bool - queue [][]byte + queue []dgram +} + +type dgram struct { + to net.UDPAddr + data []byte } func newpipe() *dgramPipe { @@ -533,7 +594,7 @@ func (c *dgramPipe) WriteToUDP(b []byte, to *net.UDPAddr) (n int, err error) { if c.closed { return 0, errors.New("closed") } - c.queue = append(c.queue, msg) + c.queue = append(c.queue, dgram{*to, b}) c.cond.Signal() return len(b), nil } @@ -558,7 +619,7 @@ func (c *dgramPipe) LocalAddr() net.Addr { return &net.UDPAddr{IP: testLocal.IP, Port: int(testLocal.UDP)} } -func (c *dgramPipe) waitPacketOut() []byte { +func (c *dgramPipe) waitPacketOut() dgram { c.mu.Lock() defer c.mu.Unlock() for len(c.queue) == 0 { diff --git a/p2p/discv5/net.go b/p2p/discv5/net.go index ca4c0706bd..f05f44b86e 100644 --- a/p2p/discv5/net.go +++ b/p2p/discv5/net.go @@ -24,10 +24,11 @@ import ( "net" "time" + "golang.org/x/crypto/sha3" + "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/common/mclock" "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/crypto/sha3" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" "github.com/AlayaNetwork/Alaya-Go/rlp" @@ -567,12 +568,11 @@ loop: net.ticketStore.searchLookupDone(res.target, res.nodes, func(n *Node, topic Topic) []byte { if n.state != nil && n.state.canQuery { return net.conn.send(n, topicQueryPacket, topicQuery{Topic: topic}) // TODO: set expiration - } else { - if n.state == unknown { - net.ping(n, n.addr()) - } - return nil } + if n.state == unknown { + net.ping(n, n.addr()) + } + return nil }) case <-statsDump.C: @@ -1235,7 +1235,7 @@ func (net *Network) checkTopicRegister(data *topicRegister) (*pong, error) { } func rlpHash(x interface{}) (h common.Hash) { - hw := sha3.NewKeccak256() + hw := sha3.NewLegacyKeccak256() rlp.Encode(hw, x) hw.Sum(h[:0]) return h diff --git a/p2p/enode/idscheme.go b/p2p/enode/idscheme.go index d5fa0a0894..95829b8315 100644 --- a/p2p/enode/idscheme.go +++ b/p2p/enode/idscheme.go @@ -23,9 +23,10 @@ import ( "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "golang.org/x/crypto/sha3" + "github.com/AlayaNetwork/Alaya-Go/common/math" "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/crypto/sha3" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -49,7 +50,7 @@ func SignV4(r *enr.Record, privkey *ecdsa.PrivateKey) error { cpy.Set(enr.ID("v4")) cpy.Set(Secp256k1(privkey.PublicKey)) - h := sha3.NewKeccak256() + h := sha3.NewLegacyKeccak256() rlp.Encode(h, cpy.AppendElements(nil)) sig, err := crypto.Sign(h.Sum(nil), privkey) if err != nil { @@ -70,7 +71,7 @@ func (V4ID) Verify(r *enr.Record, sig []byte) error { return fmt.Errorf("invalid public key") } - h := sha3.NewKeccak256() + h := sha3.NewLegacyKeccak256() rlp.Encode(h, r.AppendElements(nil)) if !crypto.VerifySignature(entry, h.Sum(nil), sig) { return enr.ErrInvalidSig diff --git a/p2p/enode/nodedb.go b/p2p/enode/nodedb.go index fea734bbe6..7781ce0b2f 100644 --- a/p2p/enode/nodedb.go +++ b/p2p/enode/nodedb.go @@ -24,6 +24,7 @@ import ( "crypto/rand" "encoding/binary" "fmt" + "net" "os" "sync" "time" @@ -35,30 +36,36 @@ import ( "github.com/syndtr/goleveldb/leveldb/storage" "github.com/syndtr/goleveldb/leveldb/util" - "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/rlp" ) // 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" + dbVersionKey = "version" // Version of the database to flush if changes + dbNodePrefix = "n:" // Identifier to prefix node entries with + dbLocalPrefix = "local:" + dbDiscoverRoot = "v4" + + // These fields are stored per ID and IP, the full key is "n::v4::findfail". + // Use nodeItemKey to create those keys. + dbNodeFindFails = "findfail" + dbNodePing = "lastping" + dbNodePong = "lastpong" + dbNodeSeq = "seq" + + // Local information is keyed by ID only, the full key is "local::seq". + // Use localItemKey to create those keys. + dbLocalSeq = "seq" ) -var ( +const ( dbNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped. dbCleanupCycle = time.Hour // Time period for running the expiration task. - dbVersion = 7 + dbVersion = 8 ) +var zeroIP = make(net.IP, 16) + // DB is the node database, storing previously seen nodes and any collected metadata about // them for QoS purposes. type DB struct { @@ -123,27 +130,58 @@ func newPersistentDB(path string) (*DB, error) { return &DB{lvl: db, quit: make(chan struct{})}, nil } -// makeKey generates the leveldb key-blob from a node id and its particular -// field of interest. -func makeKey(id ID, field string) []byte { - if (id == ID{}) { - return []byte(field) - } - return append([]byte(dbItemPrefix), append(id[:], field...)...) +// nodeKey returns the database key for a node record. +func nodeKey(id ID) []byte { + key := append([]byte(dbNodePrefix), id[:]...) + key = append(key, ':') + key = append(key, dbDiscoverRoot...) + return key } -// splitKey tries to split a database key into a node id and a field part. -func splitKey(key []byte) (id ID, field string) { - // If the key is not of a node, return it plainly - if !bytes.HasPrefix(key, []byte(dbItemPrefix)) { - return ID{}, string(key) +// splitNodeKey returns the node ID of a key created by nodeKey. +func splitNodeKey(key []byte) (id ID, rest []byte) { + if !bytes.HasPrefix(key, []byte(dbNodePrefix)) { + return ID{}, nil } - // Otherwise split the id and field - item := key[len(dbItemPrefix):] + item := key[len(dbNodePrefix):] copy(id[:], item[:len(id)]) - field = string(item[len(id):]) + return id, item[len(id)+1:] +} - return id, field +// nodeItemKey returns the database key for a node metadata field. +func nodeItemKey(id ID, ip net.IP, field string) []byte { + ip16 := ip.To16() + if ip16 == nil { + panic(fmt.Errorf("invalid IP (length %d)", len(ip))) + } + return bytes.Join([][]byte{nodeKey(id), ip16, []byte(field)}, []byte{':'}) +} + +// splitNodeItemKey returns the components of a key created by nodeItemKey. +func splitNodeItemKey(key []byte) (id ID, ip net.IP, field string) { + id, key = splitNodeKey(key) + // Skip discover root. + if string(key) == dbDiscoverRoot { + return id, nil, "" + } + key = key[len(dbDiscoverRoot)+1:] + // Split out the IP. + ip = net.IP(key[:16]) + if ip4 := ip.To4(); ip4 != nil { + ip = ip4 + } + key = key[16+1:] + // Field is the remainder of key. + field = string(key) + return id, ip, field +} + +// localItemKey returns the key of a local node item. +func localItemKey(id ID, field string) []byte { + key := append([]byte(dbLocalPrefix), id[:]...) + key = append(key, ':') + key = append(key, field...) + return key } // fetchInt64 retrieves an integer associated with a particular key. @@ -185,7 +223,7 @@ func (db *DB) storeUint64(key []byte, n uint64) error { // 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) + blob, err := db.lvl.Get(nodeKey(id), nil) if err != nil { return nil } @@ -211,15 +249,15 @@ func (db *DB) UpdateNode(node *Node) error { if err != nil { return err } - if err := db.lvl.Put(makeKey(node.ID(), dbDiscoverRoot), blob, nil); err != nil { + if err := db.lvl.Put(nodeKey(node.ID()), blob, nil); err != nil { return err } - return db.storeUint64(makeKey(node.ID(), dbDiscoverSeq), node.Seq()) + return db.storeUint64(nodeItemKey(node.ID(), zeroIP, dbNodeSeq), node.Seq()) } // NodeSeq returns the stored record sequence number of the given node. func (db *DB) NodeSeq(id ID) uint64 { - return db.fetchUint64(makeKey(id, dbDiscoverSeq)) + return db.fetchUint64(nodeItemKey(id, zeroIP, dbNodeSeq)) } // Resolve returns the stored record of the node if it has a larger sequence @@ -231,15 +269,17 @@ func (db *DB) Resolve(n *Node) *Node { 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) - for deleter.Next() { - if err := db.lvl.Delete(deleter.Key(), nil); err != nil { - return err - } +// DeleteNode deletes all information associated with a node. +func (db *DB) DeleteNode(id ID) { + deleteRange(db.lvl, nodeKey(id)) +} + +func deleteRange(db *leveldb.DB, prefix []byte) { + it := db.NewIterator(util.BytesPrefix(prefix), nil) + defer it.Release() + for it.Next() { + db.Delete(it.Key(), nil) } - return nil } // ensureExpirer is a small helper method ensuring that the data expiration @@ -263,9 +303,7 @@ func (db *DB) expirer() { for { select { case <-tick.C: - if err := db.expireNodes(); err != nil { - log.Error("Failed to expire nodedb items", "err", err) - } + db.expireNodes() case <-db.quit: return } @@ -273,71 +311,85 @@ func (db *DB) expirer() { } // expireNodes iterates over the database and deletes all nodes that have not -// been seen (i.e. received a pong from) for some allotted time. -func (db *DB) expireNodes() error { - threshold := time.Now().Add(-dbNodeExpiration) - - // Find discovered nodes that are older than the allowance - it := db.lvl.NewIterator(nil, nil) +// been seen (i.e. received a pong from) for some time. +func (db *DB) expireNodes() { + it := db.lvl.NewIterator(util.BytesPrefix([]byte(dbNodePrefix)), nil) defer it.Release() + if !it.Next() { + return + } - for it.Next() { - // Skip the item if not a discovery node - id, field := splitKey(it.Key()) - if field != dbDiscoverRoot { - continue + var ( + threshold = time.Now().Add(-dbNodeExpiration).Unix() + youngestPong int64 + atEnd = false + ) + for !atEnd { + id, ip, field := splitNodeItemKey(it.Key()) + if field == dbNodePong { + time, _ := binary.Varint(it.Value()) + if time > youngestPong { + youngestPong = time + } + if time < threshold { + // Last pong from this IP older than threshold, remove fields belonging to it. + deleteRange(db.lvl, nodeItemKey(id, ip, "")) + } } - // Skip the node if not expired yet (and not self) - if seen := db.LastPongReceived(id); seen.After(threshold) { - continue + atEnd = !it.Next() + nextID, _ := splitNodeKey(it.Key()) + if atEnd || nextID != id { + // We've moved beyond the last entry of the current ID. + // Remove everything if there was no recent enough pong. + if youngestPong > 0 && youngestPong < threshold { + deleteRange(db.lvl, nodeKey(id)) + } + youngestPong = 0 } - // Otherwise delete all associated information - db.DeleteNode(id) } - return nil } // LastPingReceived retrieves the time of the last ping packet received from // a remote node. -func (db *DB) LastPingReceived(id ID) time.Time { - return time.Unix(db.fetchInt64(makeKey(id, dbDiscoverPing)), 0) +func (db *DB) LastPingReceived(id ID, ip net.IP) time.Time { + return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePing)), 0) } // UpdateLastPingReceived updates the last time we tried contacting a remote node. -func (db *DB) UpdateLastPingReceived(id ID, instance time.Time) error { - return db.storeInt64(makeKey(id, dbDiscoverPing), instance.Unix()) +func (db *DB) UpdateLastPingReceived(id ID, ip net.IP, instance time.Time) error { + return db.storeInt64(nodeItemKey(id, ip, dbNodePing), instance.Unix()) } // LastPongReceived retrieves the time of the last successful pong from remote node. -func (db *DB) LastPongReceived(id ID) time.Time { +func (db *DB) LastPongReceived(id ID, ip net.IP) time.Time { // Launch expirer db.ensureExpirer() - return time.Unix(db.fetchInt64(makeKey(id, dbDiscoverPong)), 0) + return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePong)), 0) } // UpdateLastPongReceived updates the last pong time of a node. -func (db *DB) UpdateLastPongReceived(id ID, instance time.Time) error { - return db.storeInt64(makeKey(id, dbDiscoverPong), instance.Unix()) +func (db *DB) UpdateLastPongReceived(id ID, ip net.IP, instance time.Time) error { + return db.storeInt64(nodeItemKey(id, ip, dbNodePong), instance.Unix()) } // FindFails retrieves the number of findnode failures since bonding. -func (db *DB) FindFails(id ID) int { - return int(db.fetchInt64(makeKey(id, dbDiscoverFindFails))) +func (db *DB) FindFails(id ID, ip net.IP) int { + return int(db.fetchInt64(nodeItemKey(id, ip, dbNodeFindFails))) } // UpdateFindFails updates the number of findnode failures since bonding. -func (db *DB) UpdateFindFails(id ID, fails int) error { - return db.storeInt64(makeKey(id, dbDiscoverFindFails), int64(fails)) +func (db *DB) UpdateFindFails(id ID, ip net.IP, fails int) error { + return db.storeInt64(nodeItemKey(id, ip, dbNodeFindFails), int64(fails)) } // LocalSeq retrieves the local record sequence counter. func (db *DB) localSeq(id ID) uint64 { - return db.fetchUint64(makeKey(id, dbLocalSeq)) + return db.fetchUint64(nodeItemKey(id, zeroIP, dbLocalSeq)) } // storeLocalSeq stores the local record sequence counter. func (db *DB) storeLocalSeq(id ID, n uint64) { - db.storeUint64(makeKey(id, dbLocalSeq), n) + db.storeUint64(nodeItemKey(id, zeroIP, dbLocalSeq), n) } // QuerySeeds retrieves random nodes to be used as potential seed nodes @@ -359,14 +411,14 @@ seek: ctr := id[0] rand.Read(id[:]) id[0] = ctr + id[0]%16 - it.Seek(makeKey(id, dbDiscoverRoot)) + it.Seek(nodeKey(id)) n := nextNode(it) if n == nil { id[0] = 0 continue seek // iterator exhausted } - if now.Sub(db.LastPongReceived(n.ID())) > maxAge { + if now.Sub(db.LastPongReceived(n.ID(), n.IP())) > maxAge { continue seek } for i := range nodes { @@ -383,8 +435,8 @@ seek: // database entries. func nextNode(it iterator.Iterator) *Node { for end := false; !end; end = !it.Next() { - id, field := splitKey(it.Key()) - if field != dbDiscoverRoot { + id, rest := splitNodeKey(it.Key()) + if string(rest) != dbDiscoverRoot { continue } return mustDecodeNode(id[:], it.Value()) diff --git a/p2p/enode/nodedb_test.go b/p2p/enode/nodedb_test.go index 495a892a6e..5f81376a40 100644 --- a/p2p/enode/nodedb_test.go +++ b/p2p/enode/nodedb_test.go @@ -28,42 +28,54 @@ import ( "time" ) -var nodeDBKeyTests = []struct { - id ID - field string - key []byte -}{ - { - id: ID{}, - field: "version", - key: []byte{0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e}, // field - }, - { - id: HexID("51232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - field: ":discover", - key: []byte{ - 0x6e, 0x3a, // prefix - 0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, // node id - 0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, // - 0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, // - 0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, // - 0x3a, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, // field - }, - }, +var keytestID = HexID("51232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439") + +func TestDBNodeKey(t *testing.T) { + enc := nodeKey(keytestID) + want := []byte{ + 'n', ':', + 0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, // node id + 0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, // + 0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, // + 0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, // + ':', 'v', '4', + } + if !bytes.Equal(enc, want) { + t.Errorf("wrong encoded key:\ngot %q\nwant %q", enc, want) + } + id, _ := splitNodeKey(enc) + if id != keytestID { + t.Errorf("wrong ID from splitNodeKey") + } } -func TestDBKeys(t *testing.T) { - for i, tt := range nodeDBKeyTests { - 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) - } - id, field := splitKey(tt.key) - if !bytes.Equal(id[:], tt.id[:]) { - t.Errorf("split test %d: id mismatch: have 0x%x, want 0x%x", i, id, tt.id) - } - if field != tt.field { - t.Errorf("split test %d: field mismatch: have 0x%x, want 0x%x", i, field, tt.field) - } +func TestDBNodeItemKey(t *testing.T) { + wantIP := net.IP{127, 0, 0, 3} + wantField := "foobar" + enc := nodeItemKey(keytestID, wantIP, wantField) + want := []byte{ + 'n', ':', + 0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, // node id + 0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, // + 0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, // + 0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, // + ':', 'v', '4', ':', + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IP + 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x03, // + ':', 'f', 'o', 'o', 'b', 'a', 'r', + } + if !bytes.Equal(enc, want) { + t.Errorf("wrong encoded key:\ngot %q\nwant %q", enc, want) + } + id, ip, field := splitNodeItemKey(enc) + if id != keytestID { + t.Errorf("splitNodeItemKey returned wrong ID: %v", id) + } + if !bytes.Equal(ip, wantIP) { + t.Errorf("splitNodeItemKey returned wrong IP: %v", ip) + } + if field != wantField { + t.Errorf("splitNodeItemKey returned wrong field: %q", field) } } @@ -113,33 +125,33 @@ func TestDBFetchStore(t *testing.T) { defer db.Close() // Check fetch/store operations on a node ping object - if stored := db.LastPingReceived(node.ID()); stored.Unix() != 0 { + if stored := db.LastPingReceived(node.ID(), node.IP()); stored.Unix() != 0 { t.Errorf("ping: non-existing object: %v", stored) } - if err := db.UpdateLastPingReceived(node.ID(), inst); err != nil { + if err := db.UpdateLastPingReceived(node.ID(), node.IP(), inst); err != nil { t.Errorf("ping: failed to update: %v", err) } - if stored := db.LastPingReceived(node.ID()); stored.Unix() != inst.Unix() { + if stored := db.LastPingReceived(node.ID(), node.IP()); stored.Unix() != inst.Unix() { t.Errorf("ping: value mismatch: have %v, want %v", stored, inst) } // Check fetch/store operations on a node pong object - if stored := db.LastPongReceived(node.ID()); stored.Unix() != 0 { + if stored := db.LastPongReceived(node.ID(), node.IP()); stored.Unix() != 0 { t.Errorf("pong: non-existing object: %v", stored) } - if err := db.UpdateLastPongReceived(node.ID(), inst); err != nil { + if err := db.UpdateLastPongReceived(node.ID(), node.IP(), inst); err != nil { t.Errorf("pong: failed to update: %v", err) } - if stored := db.LastPongReceived(node.ID()); stored.Unix() != inst.Unix() { + if stored := db.LastPongReceived(node.ID(), node.IP()); stored.Unix() != inst.Unix() { t.Errorf("pong: value mismatch: have %v, want %v", stored, inst) } // Check fetch/store operations on a node findnode-failure object - if stored := db.FindFails(node.ID()); stored != 0 { + if stored := db.FindFails(node.ID(), node.IP()); stored != 0 { 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(), node.IP(), num); err != nil { t.Errorf("find-node fails: failed to update: %v", err) } - if stored := db.FindFails(node.ID()); stored != num { + if stored := db.FindFails(node.ID(), node.IP()); stored != num { t.Errorf("find-node fails: value mismatch: have %v, want %v", stored, num) } // Check fetch/store operations on an actual node object @@ -256,7 +268,7 @@ func testSeedQuery() error { if err := db.UpdateNode(seed.node); err != nil { return fmt.Errorf("node %d: failed to insert: %v", i, err) } - if err := db.UpdateLastPongReceived(seed.node.ID(), seed.pong); err != nil { + if err := db.UpdateLastPongReceived(seed.node.ID(), seed.node.IP(), seed.pong); err != nil { return fmt.Errorf("node %d: failed to insert bondTime: %v", i, err) } } @@ -321,10 +333,12 @@ func TestDBPersistency(t *testing.T) { } var nodeDBExpirationNodes = []struct { - node *Node - pong time.Time - exp bool + node *Node + pong time.Time + storeNode bool + exp bool }{ + // Node has new enough pong time and isn't expired: { node: NewV4( hexPubkey("8d110e2ed4b446d9b5fb50f117e5f37fb7597af455e1dab0e6f045a6eeaa786a6781141659020d38bdc5e698ed3d4d2bafa8b5061810dfa63e8ac038db2e9b67"), @@ -332,17 +346,79 @@ var nodeDBExpirationNodes = []struct { 16789, 16789, ), - pong: time.Now().Add(-dbNodeExpiration + time.Minute), - exp: false, - }, { + storeNode: true, + pong: time.Now().Add(-dbNodeExpiration + time.Minute), + exp: false, + }, + // Node with pong time before expiration is removed: + { node: NewV4( hexPubkey("913a205579c32425b220dfba999d215066e5bdbf900226b11da1907eae5e93eb40616d47412cf819664e9eacbdfcca6b0c6e07e09847a38472d4be46ab0c3672"), net.IP{127, 0, 0, 2}, 16789, 16789, ), - pong: time.Now().Add(-dbNodeExpiration - time.Minute), - exp: true, + storeNode: true, + pong: time.Now().Add(-dbNodeExpiration - time.Minute), + exp: true, + }, + // Just pong time, no node stored: + { + node: NewV4( + hexPubkey("b56670e0b6bad2c5dab9f9fe6f061a16cf78d68b6ae2cfda3144262d08d97ce5f46fd8799b6d1f709b1abe718f2863e224488bd7518e5e3b43809ac9bd1138ca"), + net.IP{127, 0, 0, 3}, + 16789, + 16789, + ), + storeNode: false, + pong: time.Now().Add(-dbNodeExpiration - time.Minute), + exp: true, + }, + // Node with multiple pong times, all older than expiration. + { + node: NewV4( + hexPubkey("29f619cebfd32c9eab34aec797ed5e3fe15b9b45be95b4df3f5fe6a9ae892f433eb08d7698b2ef3621568b0fb70d57b515ab30d4e72583b798298e0f0a66b9d1"), + net.IP{127, 0, 0, 4}, + 16789, + 16789, + ), + storeNode: true, + pong: time.Now().Add(-dbNodeExpiration - time.Minute), + exp: true, + }, + { + node: NewV4( + hexPubkey("29f619cebfd32c9eab34aec797ed5e3fe15b9b45be95b4df3f5fe6a9ae892f433eb08d7698b2ef3621568b0fb70d57b515ab30d4e72583b798298e0f0a66b9d1"), + net.IP{127, 0, 0, 5}, + 16789, + 16789, + ), + storeNode: false, + pong: time.Now().Add(-dbNodeExpiration - 2*time.Minute), + exp: true, + }, + // Node with multiple pong times, one newer, one older than expiration. + { + node: NewV4( + hexPubkey("3b73a9e5f4af6c4701c57c73cc8cfa0f4802840b24c11eba92aac3aef65644a3728b4b2aec8199f6d72bd66be2c65861c773129039bd47daa091ca90a6d4c857"), + net.IP{127, 0, 0, 6}, + 16789, + 16789, + ), + storeNode: true, + pong: time.Now().Add(-dbNodeExpiration + time.Minute), + exp: false, + }, + { + node: NewV4( + hexPubkey("3b73a9e5f4af6c4701c57c73cc8cfa0f4802840b24c11eba92aac3aef65644a3728b4b2aec8199f6d72bd66be2c65861c773129039bd47daa091ca90a6d4c857"), + net.IP{127, 0, 0, 7}, + 16789, + 16789, + ), + storeNode: false, + pong: time.Now().Add(-dbNodeExpiration - time.Minute), + exp: true, }, } @@ -350,23 +426,39 @@ func TestDBExpiration(t *testing.T) { db, _ := OpenDB("") 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 { - if err := db.UpdateNode(seed.node); err != nil { - t.Fatalf("node %d: failed to insert: %v", i, err) + if seed.storeNode { + if err := db.UpdateNode(seed.node); err != nil { + t.Fatalf("node %d: failed to insert: %v", i, err) + } } - if err := db.UpdateLastPongReceived(seed.node.ID(), seed.pong); err != nil { + if err := db.UpdateLastPongReceived(seed.node.ID(), seed.node.IP(), seed.pong); err != nil { t.Fatalf("node %d: failed to update bondTime: %v", i, err) } } - // Expire some of them, and check the rest - if err := db.expireNodes(); err != nil { - t.Fatalf("failed to expire nodes: %v", err) - } + + db.expireNodes() + + // Check that expired entries have been removed. + unixZeroTime := time.Unix(0, 0) for i, seed := range nodeDBExpirationNodes { node := db.Node(seed.node.ID()) - if (node == nil && !seed.exp) || (node != nil && seed.exp) { - t.Errorf("node %d: expiration mismatch: have %v, want %v", i, node, seed.exp) + pong := db.LastPongReceived(seed.node.ID(), seed.node.IP()) + if seed.exp { + if seed.storeNode && node != nil { + t.Errorf("node %d (%s) shouldn't be present after expiration", i, seed.node.ID().TerminalString()) + } + if !pong.Equal(unixZeroTime) { + t.Errorf("pong time %d (%s %v) shouldn't be present after expiration", i, seed.node.ID().TerminalString(), seed.node.IP()) + } + } else { + if seed.storeNode && node == nil { + t.Errorf("node %d (%s) should be present after expiration", i, seed.node.ID().TerminalString()) + } + if !pong.Equal(seed.pong.Truncate(1 * time.Second)) { + t.Errorf("pong time %d (%s) should be %v after expiration, but is %v", i, seed.node.ID().TerminalString(), seed.pong, pong) + } } } } diff --git a/p2p/metrics.go b/p2p/metrics.go index 5dfbde20de..96cebc356b 100644 --- a/p2p/metrics.go +++ b/p2p/metrics.go @@ -46,8 +46,8 @@ var ( egressConnectMeter = metrics.NewRegisteredMeter(MetricsOutboundConnects, nil) // Meter counting the egress connections egressTrafficMeter = metrics.NewRegisteredMeter(MetricsOutboundTraffic, nil) // Meter metering the cumulative egress traffic - PeerIngressRegistry = metrics.NewPrefixedChildRegistry(metrics.DefaultRegistry, MetricsInboundTraffic+"/") // Registry containing the peer ingress - PeerEgressRegistry = metrics.NewPrefixedChildRegistry(metrics.DefaultRegistry, MetricsOutboundTraffic+"/") // Registry containing the peer egress + PeerIngressRegistry = metrics.NewPrefixedChildRegistry(metrics.EphemeralRegistry, MetricsInboundTraffic+"/") // Registry containing the peer ingress + PeerEgressRegistry = metrics.NewPrefixedChildRegistry(metrics.EphemeralRegistry, MetricsOutboundTraffic+"/") // Registry containing the peer egress meteredPeerFeed event.Feed // Event feed for peer metrics meteredPeerCount int32 // Actually stored peer connection count @@ -73,7 +73,7 @@ const ( type MeteredPeerEvent struct { Type MeteredPeerEventType // Type of peer event IP net.IP // IP address of the peer - ID string // NodeID of the peer + ID enode.ID // NodeID of the peer Elapsed time.Duration // Time elapsed between the connection and the handshake/disconnection Ingress uint64 // Ingress count at the moment of the event Egress uint64 // Egress count at the moment of the event @@ -92,7 +92,7 @@ type meteredConn struct { connected time.Time // Connection time of the peer ip net.IP // IP address of the peer - id string // NodeID of the peer + id enode.ID // NodeID of the peer // trafficMetered denotes if the peer is registered in the traffic registries. // Its value is true if the metered peer count doesn't reach the limit in the @@ -159,8 +159,7 @@ func (c *meteredConn) Write(b []byte) (n int, err error) { // handshakeDone is called when a peer handshake is done. Registers the peer to // the ingress and the egress traffic registries using the peer's IP and node ID, // also emits connect event. -func (c *meteredConn) handshakeDone(nodeID enode.ID) { - id := nodeID.String() +func (c *meteredConn) handshakeDone(id enode.ID) { if atomic.AddInt32(&meteredPeerCount, 1) >= MeteredPeerLimit { // Don't register the peer in the traffic registries. atomic.AddInt32(&meteredPeerCount, -1) @@ -169,7 +168,7 @@ func (c *meteredConn) handshakeDone(nodeID enode.ID) { c.lock.Unlock() log.Warn("Metered peer count reached the limit") } else { - key := fmt.Sprintf("%s/%s", c.ip, id) + key := fmt.Sprintf("%s/%s", c.ip, id.String()) c.lock.Lock() c.id, c.trafficMetered = id, true c.ingressMeter = metrics.NewRegisteredMeter(key, PeerIngressRegistry) @@ -189,7 +188,7 @@ func (c *meteredConn) handshakeDone(nodeID enode.ID) { func (c *meteredConn) Close() error { err := c.Conn.Close() c.lock.RLock() - if c.id == "" { + if c.id == (enode.ID{}) { // If the peer disconnects before the handshake. c.lock.RUnlock() meteredPeerFeed.Send(MeteredPeerEvent{ diff --git a/p2p/protocols/accounting.go b/p2p/protocols/accounting.go new file mode 100644 index 0000000000..756b8a3ade --- /dev/null +++ b/p2p/protocols/accounting.go @@ -0,0 +1,182 @@ +// 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 . + +package protocols + +import ( + "time" + + "github.com/AlayaNetwork/Alaya-Go/metrics" +) + +// define some metrics +var ( + // All metrics are cumulative + + // total amount of units credited + mBalanceCredit = metrics.NewRegisteredCounterForced("account.balance.credit", metrics.AccountingRegistry) + // total amount of units debited + mBalanceDebit = metrics.NewRegisteredCounterForced("account.balance.debit", metrics.AccountingRegistry) + // total amount of bytes credited + mBytesCredit = metrics.NewRegisteredCounterForced("account.bytes.credit", metrics.AccountingRegistry) + // total amount of bytes debited + mBytesDebit = metrics.NewRegisteredCounterForced("account.bytes.debit", metrics.AccountingRegistry) + // total amount of credited messages + mMsgCredit = metrics.NewRegisteredCounterForced("account.msg.credit", metrics.AccountingRegistry) + // total amount of debited messages + mMsgDebit = metrics.NewRegisteredCounterForced("account.msg.debit", metrics.AccountingRegistry) + // how many times local node had to drop remote peers + mPeerDrops = metrics.NewRegisteredCounterForced("account.peerdrops", metrics.AccountingRegistry) + // how many times local node overdrafted and dropped + mSelfDrops = metrics.NewRegisteredCounterForced("account.selfdrops", metrics.AccountingRegistry) +) + +// Prices defines how prices are being passed on to the accounting instance +type Prices interface { + // Return the Price for a message + Price(interface{}) *Price +} + +type Payer bool + +const ( + Sender = Payer(true) + Receiver = Payer(false) +) + +// Price represents the costs of a message +type Price struct { + Value uint64 + PerByte bool // True if the price is per byte or for unit + Payer Payer +} + +// For gives back the price for a message +// A protocol provides the message price in absolute value +// This method then returns the correct signed amount, +// depending on who pays, which is identified by the `payer` argument: +// `Send` will pass a `Sender` payer, `Receive` will pass the `Receiver` argument. +// Thus: If Sending and sender pays, amount positive, otherwise negative +// If Receiving, and receiver pays, amount positive, otherwise negative +func (p *Price) For(payer Payer, size uint32) int64 { + price := p.Value + if p.PerByte { + price *= uint64(size) + } + if p.Payer == payer { + return 0 - int64(price) + } + return int64(price) +} + +// Balance is the actual accounting instance +// Balance defines the operations needed for accounting +// Implementations internally maintain the balance for every peer +type Balance interface { + // Adds amount to the local balance with remote node `peer`; + // positive amount = credit local node + // negative amount = debit local node + Add(amount int64, peer *Peer) error +} + +// Accounting implements the Hook interface +// It interfaces to the balances through the Balance interface, +// while interfacing with protocols and its prices through the Prices interface +type Accounting struct { + Balance // interface to accounting logic + Prices // interface to prices logic +} + +func NewAccounting(balance Balance, po Prices) *Accounting { + ah := &Accounting{ + Prices: po, + Balance: balance, + } + return ah +} + +// SetupAccountingMetrics uses a separate registry for p2p accounting metrics; +// this registry should be independent of any other metrics as it persists at different endpoints. +// It also starts the persisting go-routine which +// at the passed interval writes the metrics to a LevelDB +func SetupAccountingMetrics(reportInterval time.Duration, path string) *AccountingMetrics { + // create the DB and start persisting + return NewAccountingMetrics(metrics.AccountingRegistry, reportInterval, path) +} + +// Send takes a peer, a size and a msg and +// - calculates the cost for the local node sending a msg of size to peer using the Prices interface +// - credits/debits local node using balance interface +func (ah *Accounting) Send(peer *Peer, size uint32, msg interface{}) error { + // get the price for a message (through the protocol spec) + price := ah.Price(msg) + // this message doesn't need accounting + if price == nil { + return nil + } + // evaluate the price for sending messages + costToLocalNode := price.For(Sender, size) + // do the accounting + err := ah.Add(costToLocalNode, peer) + // record metrics: just increase counters for user-facing metrics + ah.doMetrics(costToLocalNode, size, err) + return err +} + +// Receive takes a peer, a size and a msg and +// - calculates the cost for the local node receiving a msg of size from peer using the Prices interface +// - credits/debits local node using balance interface +func (ah *Accounting) Receive(peer *Peer, size uint32, msg interface{}) error { + // get the price for a message (through the protocol spec) + price := ah.Price(msg) + // this message doesn't need accounting + if price == nil { + return nil + } + // evaluate the price for receiving messages + costToLocalNode := price.For(Receiver, size) + // do the accounting + err := ah.Add(costToLocalNode, peer) + // record metrics: just increase counters for user-facing metrics + ah.doMetrics(costToLocalNode, size, err) + return err +} + +// record some metrics +// this is not an error handling. `err` is returned by both `Send` and `Receive` +// `err` will only be non-nil if a limit has been violated (overdraft), in which case the peer has been dropped. +// if the limit has been violated and `err` is thus not nil: +// * if the price is positive, local node has been credited; thus `err` implicitly signals the REMOTE has been dropped +// * if the price is negative, local node has been debited, thus `err` implicitly signals LOCAL node "overdraft" +func (ah *Accounting) doMetrics(price int64, size uint32, err error) { + if price > 0 { + mBalanceCredit.Inc(price) + mBytesCredit.Inc(int64(size)) + mMsgCredit.Inc(1) + if err != nil { + // increase the number of times a remote node has been dropped due to "overdraft" + mPeerDrops.Inc(1) + } + } else { + mBalanceDebit.Inc(price) + mBytesDebit.Inc(int64(size)) + mMsgDebit.Inc(1) + if err != nil { + // increase the number of times the local node has done an "overdraft" in respect to other nodes + mSelfDrops.Inc(1) + } + } +} diff --git a/p2p/protocols/accounting_api.go b/p2p/protocols/accounting_api.go new file mode 100644 index 0000000000..48e2af9fea --- /dev/null +++ b/p2p/protocols/accounting_api.go @@ -0,0 +1,94 @@ +package protocols + +import ( + "errors" +) + +// Textual version number of accounting API +const AccountingVersion = "1.0" + +var errNoAccountingMetrics = errors.New("accounting metrics not enabled") + +// AccountingApi provides an API to access account related information +type AccountingApi struct { + metrics *AccountingMetrics +} + +// NewAccountingApi creates a new AccountingApi +// m will be used to check if accounting metrics are enabled +func NewAccountingApi(m *AccountingMetrics) *AccountingApi { + return &AccountingApi{m} +} + +// Balance returns local node balance (units credited - units debited) +func (self *AccountingApi) Balance() (int64, error) { + if self.metrics == nil { + return 0, errNoAccountingMetrics + } + balance := mBalanceCredit.Count() - mBalanceDebit.Count() + return balance, nil +} + +// BalanceCredit returns total amount of units credited by local node +func (self *AccountingApi) BalanceCredit() (int64, error) { + if self.metrics == nil { + return 0, errNoAccountingMetrics + } + return mBalanceCredit.Count(), nil +} + +// BalanceCredit returns total amount of units debited by local node +func (self *AccountingApi) BalanceDebit() (int64, error) { + if self.metrics == nil { + return 0, errNoAccountingMetrics + } + return mBalanceDebit.Count(), nil +} + +// BytesCredit returns total amount of bytes credited by local node +func (self *AccountingApi) BytesCredit() (int64, error) { + if self.metrics == nil { + return 0, errNoAccountingMetrics + } + return mBytesCredit.Count(), nil +} + +// BalanceCredit returns total amount of bytes debited by local node +func (self *AccountingApi) BytesDebit() (int64, error) { + if self.metrics == nil { + return 0, errNoAccountingMetrics + } + return mBytesDebit.Count(), nil +} + +// MsgCredit returns total amount of messages credited by local node +func (self *AccountingApi) MsgCredit() (int64, error) { + if self.metrics == nil { + return 0, errNoAccountingMetrics + } + return mMsgCredit.Count(), nil +} + +// MsgDebit returns total amount of messages debited by local node +func (self *AccountingApi) MsgDebit() (int64, error) { + if self.metrics == nil { + return 0, errNoAccountingMetrics + } + return mMsgDebit.Count(), nil +} + +// PeerDrops returns number of times when local node had to drop remote peers +func (self *AccountingApi) PeerDrops() (int64, error) { + if self.metrics == nil { + return 0, errNoAccountingMetrics + } + return mPeerDrops.Count(), nil +} + +// SelfDrops returns number of times when local node was overdrafted and dropped +func (self *AccountingApi) SelfDrops() (int64, error) { + if self.metrics == nil { + return 0, errNoAccountingMetrics + } + return mSelfDrops.Count(), nil +} diff --git a/p2p/protocols/accounting_simulation_test.go b/p2p/protocols/accounting_simulation_test.go new file mode 100644 index 0000000000..76ae3cd15f --- /dev/null +++ b/p2p/protocols/accounting_simulation_test.go @@ -0,0 +1,320 @@ +// 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 . + +package protocols + +import ( + "context" + "flag" + "fmt" + "io/ioutil" + "math/rand" + "os" + "path/filepath" + "reflect" + "sync" + "testing" + "time" + + "github.com/mattn/go-colorable" + + "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/rpc" + + "github.com/AlayaNetwork/Alaya-Go/node" + "github.com/AlayaNetwork/Alaya-Go/p2p" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/simulations" + "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" +) + +const ( + content = "123456789" +) + +var ( + nodes = flag.Int("nodes", 30, "number of nodes to create (default 30)") + msgs = flag.Int("msgs", 100, "number of messages sent by node (default 100)") + loglevel = flag.Int("loglevel", 0, "verbosity of logs") + rawlog = flag.Bool("rawlog", false, "remove terminal formatting from logs") +) + +func init() { + flag.Parse() + log.PrintOrigins(true) + log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(!*rawlog)))) +} + +//TestAccountingSimulation runs a p2p/simulations simulation +//It creates a *nodes number of nodes, connects each one with each other, +//then sends out a random selection of messages up to *msgs amount of messages +//from the test protocol spec. +//The spec has some accounted messages defined through the Prices interface. +//The test does accounting for all the message exchanged, and then checks +//that every node has the same balance with a peer, but with opposite signs. +//Balance(AwithB) = 0 - Balance(BwithA) or Abs|Balance(AwithB)| == Abs|Balance(BwithA)| +func TestAccountingSimulation(t *testing.T) { + //setup the balances objects for every node + bal := newBalances(*nodes) + //setup the metrics system or tests will fail trying to write metrics + dir, err := ioutil.TempDir("", "account-sim") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + SetupAccountingMetrics(1*time.Second, filepath.Join(dir, "metrics.db")) + //define the node.Service for this test + services := adapters.Services{ + "accounting": func(ctx *adapters.ServiceContext) (node.Service, error) { + return bal.newNode(), nil + }, + } + //setup the simulation + adapter := adapters.NewSimAdapter(services) + net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{DefaultService: "accounting"}) + defer net.Shutdown() + + // we send msgs messages per node, wait for all messages to arrive + bal.wg.Add(*nodes * *msgs) + trigger := make(chan enode.ID) + go func() { + // wait for all of them to arrive + bal.wg.Wait() + // then trigger a check + // the selected node for the trigger is irrelevant, + // we just want to trigger the end of the simulation + trigger <- net.Nodes[0].ID() + }() + + // create nodes and start them + for i := 0; i < *nodes; i++ { + conf := adapters.RandomNodeConfig() + bal.id2n[conf.ID] = i + if _, err := net.NewNodeWithConfig(conf); err != nil { + t.Fatal(err) + } + if err := net.Start(conf.ID); err != nil { + t.Fatal(err) + } + } + // fully connect nodes + for i, n := range net.Nodes { + for _, m := range net.Nodes[i+1:] { + if err := net.Connect(n.ID(), m.ID()); err != nil { + t.Fatal(err) + } + } + } + + // empty action + action := func(ctx context.Context) error { + return nil + } + // check always checks out + check := func(ctx context.Context, id enode.ID) (bool, error) { + return true, nil + } + + // run simulation + timeout := 30 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{ + Action: action, + Trigger: trigger, + Expect: &simulations.Expectation{ + Nodes: []enode.ID{net.Nodes[0].ID()}, + Check: check, + }, + }) + + if result.Error != nil { + t.Fatal(result.Error) + } + + // check if balance matrix is symmetric + if err := bal.symmetric(); err != nil { + t.Fatal(err) + } +} + +// matrix is a matrix of nodes and its balances +// matrix is in fact a linear array of size n*n, +// so the balance for any node A with B is at index +// A*n + B, while the balance of node B with A is at +// B*n + A +// (n entries in the array will not be filled - +// the balance of a node with itself) +type matrix struct { + n int //number of nodes + m []int64 //array of balances +} + +// create a new matrix +func newMatrix(n int) *matrix { + return &matrix{ + n: n, + m: make([]int64, n*n), + } +} + +// called from the testBalance's Add accounting function: register balance change +func (m *matrix) add(i, j int, v int64) error { + // index for the balance of local node i with remote nodde j is + // i * number of nodes + remote node + mi := i*m.n + j + // register that balance + m.m[mi] += v + return nil +} + +// check that the balances are symmetric: +// balance of node i with node j is the same as j with i but with inverted signs +func (m *matrix) symmetric() error { + //iterate all nodes + for i := 0; i < m.n; i++ { + //iterate starting +1 + for j := i + 1; j < m.n; j++ { + log.Debug("bal", "1", i, "2", j, "i,j", m.m[i*m.n+j], "j,i", m.m[j*m.n+i]) + if m.m[i*m.n+j] != -m.m[j*m.n+i] { + return fmt.Errorf("value mismatch. m[%v, %v] = %v; m[%v, %v] = %v", i, j, m.m[i*m.n+j], j, i, m.m[j*m.n+i]) + } + } + } + return nil +} + +// all the balances +type balances struct { + i int + *matrix + id2n map[enode.ID]int + wg *sync.WaitGroup +} + +func newBalances(n int) *balances { + return &balances{ + matrix: newMatrix(n), + id2n: make(map[enode.ID]int), + wg: &sync.WaitGroup{}, + } +} + +// create a new testNode for every node created as part of the service +func (b *balances) newNode() *testNode { + defer func() { b.i++ }() + return &testNode{ + bal: b, + i: b.i, + peers: make([]*testPeer, b.n), //a node will be connected to n-1 peers + } +} + +type testNode struct { + bal *balances + i int + lock sync.Mutex + peers []*testPeer + peerCount int +} + +// do the accounting for the peer's test protocol +// testNode implements protocols.Balance +func (t *testNode) Add(a int64, p *Peer) error { + //get the index for the remote peer + remote := t.bal.id2n[p.ID()] + log.Debug("add", "local", t.i, "remote", remote, "amount", a) + return t.bal.add(t.i, remote, a) +} + +//run the p2p protocol +//for every node, represented by testNode, create a remote testPeer +func (t *testNode) run(p *p2p.Peer, rw p2p.MsgReadWriter) error { + spec := createTestSpec() + //create accounting hook + spec.Hook = NewAccounting(t, &dummyPrices{}) + + //create a peer for this node + tp := &testPeer{NewPeer(p, rw, spec), t.i, t.bal.id2n[p.ID()], t.bal.wg} + t.lock.Lock() + t.peers[t.bal.id2n[p.ID()]] = tp + t.peerCount++ + if t.peerCount == t.bal.n-1 { + //when all peer connections are established, start sending messages from this peer + go t.send() + } + t.lock.Unlock() + return tp.Run(tp.handle) +} + +// p2p message receive handler function +func (tp *testPeer) handle(ctx context.Context, msg interface{}) error { + tp.wg.Done() + log.Debug("receive", "from", tp.remote, "to", tp.local, "type", reflect.TypeOf(msg), "msg", msg) + return nil +} + +type testPeer struct { + *Peer + local, remote int + wg *sync.WaitGroup +} + +func (t *testNode) send() { + log.Debug("start sending") + for i := 0; i < *msgs; i++ { + //determine randomly to which peer to send + whom := rand.Intn(t.bal.n - 1) + if whom >= t.i { + whom++ + } + t.lock.Lock() + p := t.peers[whom] + t.lock.Unlock() + + //determine a random message from the spec's messages to be sent + which := rand.Intn(len(p.spec.Messages)) + msg := p.spec.Messages[which] + switch msg.(type) { + case *perBytesMsgReceiverPays: + msg = &perBytesMsgReceiverPays{Content: content[:rand.Intn(len(content))]} + case *perBytesMsgSenderPays: + msg = &perBytesMsgSenderPays{Content: content[:rand.Intn(len(content))]} + } + log.Debug("send", "from", t.i, "to", whom, "type", reflect.TypeOf(msg), "msg", msg) + p.Send(context.TODO(), msg) + } +} + +// define the protocol +func (t *testNode) Protocols() []p2p.Protocol { + return []p2p.Protocol{{ + Length: 100, + Run: t.run, + }} +} + +func (t *testNode) APIs() []rpc.API { + return nil +} + +func (t *testNode) Start(server *p2p.Server) error { + return nil +} + +func (t *testNode) Stop() error { + return nil +} diff --git a/p2p/protocols/accounting_test.go b/p2p/protocols/accounting_test.go new file mode 100644 index 0000000000..c74f0db50a --- /dev/null +++ b/p2p/protocols/accounting_test.go @@ -0,0 +1,223 @@ +// 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 . + +package protocols + +import ( + "testing" + + "github.com/AlayaNetwork/Alaya-Go/p2p" + "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" + "github.com/AlayaNetwork/Alaya-Go/rlp" +) + +//dummy Balance implementation +type dummyBalance struct { + amount int64 + peer *Peer +} + +//dummy Prices implementation +type dummyPrices struct{} + +//a dummy message which needs size based accounting +//sender pays +type perBytesMsgSenderPays struct { + Content string +} + +//a dummy message which needs size based accounting +//receiver pays +type perBytesMsgReceiverPays struct { + Content string +} + +//a dummy message which is paid for per unit +//sender pays +type perUnitMsgSenderPays struct{} + +//receiver pays +type perUnitMsgReceiverPays struct{} + +//a dummy message which has zero as its price +type zeroPriceMsg struct{} + +//a dummy message which has no accounting +type nilPriceMsg struct{} + +//return the price for the defined messages +func (d *dummyPrices) Price(msg interface{}) *Price { + switch msg.(type) { + //size based message cost, receiver pays + case *perBytesMsgReceiverPays: + return &Price{ + PerByte: true, + Value: uint64(100), + Payer: Receiver, + } + //size based message cost, sender pays + case *perBytesMsgSenderPays: + return &Price{ + PerByte: true, + Value: uint64(100), + Payer: Sender, + } + //unitary cost, receiver pays + case *perUnitMsgReceiverPays: + return &Price{ + PerByte: false, + Value: uint64(99), + Payer: Receiver, + } + //unitary cost, sender pays + case *perUnitMsgSenderPays: + return &Price{ + PerByte: false, + Value: uint64(99), + Payer: Sender, + } + case *zeroPriceMsg: + return &Price{ + PerByte: false, + Value: uint64(0), + Payer: Sender, + } + case *nilPriceMsg: + return nil + } + return nil +} + +//dummy accounting implementation, only stores values for later check +func (d *dummyBalance) Add(amount int64, peer *Peer) error { + d.amount = amount + d.peer = peer + return nil +} + +type testCase struct { + msg interface{} + size uint32 + sendResult int64 + recvResult int64 +} + +//lowest level unit test +func TestBalance(t *testing.T) { + //create instances + balance := &dummyBalance{} + prices := &dummyPrices{} + //create the spec + spec := createTestSpec() + //create the accounting hook for the spec + acc := NewAccounting(balance, prices) + //create a peer + id := adapters.RandomNodeConfig().ID + p := p2p.NewPeer(id, "testPeer", nil) + peer := NewPeer(p, &dummyRW{}, spec) + //price depends on size, receiver pays + msg := &perBytesMsgReceiverPays{Content: "testBalance"} + size, _ := rlp.EncodeToBytes(msg) + + testCases := []testCase{ + { + msg, + uint32(len(size)), + int64(len(size) * 100), + int64(len(size) * -100), + }, + { + &perBytesMsgSenderPays{Content: "testBalance"}, + uint32(len(size)), + int64(len(size) * -100), + int64(len(size) * 100), + }, + { + &perUnitMsgSenderPays{}, + 0, + int64(-99), + int64(99), + }, + { + &perUnitMsgReceiverPays{}, + 0, + int64(99), + int64(-99), + }, + { + &zeroPriceMsg{}, + 0, + int64(0), + int64(0), + }, + { + &nilPriceMsg{}, + 0, + int64(0), + int64(0), + }, + } + checkAccountingTestCases(t, testCases, acc, peer, balance, true) + checkAccountingTestCases(t, testCases, acc, peer, balance, false) +} + +func checkAccountingTestCases(t *testing.T, cases []testCase, acc *Accounting, peer *Peer, balance *dummyBalance, send bool) { + for _, c := range cases { + var err error + var expectedResult int64 + //reset balance before every check + balance.amount = 0 + if send { + err = acc.Send(peer, c.size, c.msg) + expectedResult = c.sendResult + } else { + err = acc.Receive(peer, c.size, c.msg) + expectedResult = c.recvResult + } + + checkResults(t, err, balance, peer, expectedResult) + } +} + +func checkResults(t *testing.T, err error, balance *dummyBalance, peer *Peer, result int64) { + if err != nil { + t.Fatal(err) + } + if balance.peer != peer { + t.Fatalf("expected Add to be called with peer %v, got %v", peer, balance.peer) + } + if balance.amount != result { + t.Fatalf("Expected balance to be %d but is %d", result, balance.amount) + } +} + +//create a test spec +func createTestSpec() *Spec { + spec := &Spec{ + Name: "test", + Version: 42, + MaxMsgSize: 10 * 1024, + Messages: []interface{}{ + &perBytesMsgReceiverPays{}, + &perBytesMsgSenderPays{}, + &perUnitMsgReceiverPays{}, + &perUnitMsgSenderPays{}, + &zeroPriceMsg{}, + &nilPriceMsg{}, + }, + } + return spec +} diff --git a/p2p/protocols/protocol.go b/p2p/protocols/protocol.go index 04342ee192..ff231e53aa 100644 --- a/p2p/protocols/protocol.go +++ b/p2p/protocols/protocol.go @@ -120,6 +120,16 @@ type WrappedMsg struct { Payload []byte } +//For accounting, the design is to allow the Spec to describe which and how its messages are priced +//To access this functionality, we provide a Hook interface which will call accounting methods +//NOTE: there could be more such (horizontal) hooks in the future +type Hook interface { + //A hook for sending messages + Send(peer *Peer, size uint32, msg interface{}) error + //A hook for receiving messages + Receive(peer *Peer, size uint32, msg interface{}) error +} + // Spec is a protocol specification including its name and version as well as // the types of messages which are exchanged type Spec struct { @@ -139,6 +149,9 @@ type Spec struct { // each message must have a single unique data type Messages []interface{} + //hook for accounting (could be extended to multiple hooks in the future) + Hook Hook + initOnce sync.Once codes map[reflect.Type]uint64 types map[uint64]reflect.Type @@ -241,6 +254,25 @@ func (p *Peer) Send(ctx context.Context, msg interface{}) error { metrics.GetOrRegisterCounter("peer.send", nil).Inc(1) var b bytes.Buffer + /*if tracing.Enabled { + writer := bufio.NewWriter(&b) + + tracer := opentracing.GlobalTracer() + + sctx := spancontext.FromContext(ctx) + + if sctx != nil { + err := tracer.Inject( + sctx, + opentracing.Binary, + writer) + if err != nil { + return err + } + } + + writer.Flush() + }*/ r, err := rlp.EncodeToBytes(msg) if err != nil { @@ -253,6 +285,15 @@ func (p *Peer) Send(ctx context.Context, msg interface{}) error { Payload: r, } + //if the accounting hook is set, call it + if p.spec.Hook != nil { + err := p.spec.Hook.Send(p, wmsg.Size, msg) + if err != nil { + p.Drop(err) + return err + } + } + code, found := p.spec.GetCode(msg) if !found { return errorf(ErrInvalidMsgType, "%v", code) @@ -290,6 +331,23 @@ func (p *Peer) handleIncoming(handle func(ctx context.Context, msg interface{}) ctx := context.Background() + // if tracing is enabled and the context coming within the request is + // not empty, try to unmarshal it + /*if tracing.Enabled && len(wmsg.Context) > 0 { + var sctx opentracing.SpanContext + + tracer := opentracing.GlobalTracer() + sctx, err = tracer.Extract( + opentracing.Binary, + bytes.NewReader(wmsg.Context)) + if err != nil { + log.Error(err.Error()) + return err + } + + ctx = spancontext.WithContext(ctx, sctx) + }*/ + val, ok := p.spec.NewMsg(msg.Code) if !ok { return errorf(ErrInvalidMsgCode, "%v", msg.Code) @@ -298,6 +356,14 @@ func (p *Peer) handleIncoming(handle func(ctx context.Context, msg interface{}) return errorf(ErrDecode, "<= %v: %v", msg, err) } + //if the accounting hook is set, call it + if p.spec.Hook != nil { + err := p.spec.Hook.Receive(p, wmsg.Size, val) + if err != nil { + return err + } + } + // call the registered handler callbacks // a registered callback take the decoded message as argument as an interface // which the handler is supposed to cast to the appropriate type @@ -313,15 +379,17 @@ func (p *Peer) handleIncoming(handle func(ctx context.Context, msg interface{}) // * arguments // * context // * the local handshake to be sent to the remote peer -// * funcion to be called on the remote handshake (can be nil) +// * function to be called on the remote handshake (can be nil) // * expects a remote handshake back of the same type // * the dialing peer needs to send the handshake first and then waits for remote // * the listening peer waits for the remote handshake and then sends it // returns the remote handshake and an error -func (p *Peer) Handshake(ctx context.Context, hs interface{}, verify func(interface{}) error) (rhs interface{}, err error) { +func (p *Peer) Handshake(ctx context.Context, hs interface{}, verify func(interface{}) error) (interface{}, error) { if _, ok := p.spec.GetCode(hs); !ok { return nil, errorf(ErrHandshake, "unknown handshake message type: %T", hs) } + + var rhs interface{} errc := make(chan error, 2) handle := func(ctx context.Context, msg interface{}) error { rhs = msg @@ -344,6 +412,7 @@ func (p *Peer) Handshake(ctx context.Context, hs interface{}, verify func(interf }() for i := 0; i < 2; i++ { + var err error select { case err = <-errc: case <-ctx.Done(): @@ -355,3 +424,17 @@ func (p *Peer) Handshake(ctx context.Context, hs interface{}, verify func(interf } return rhs, nil } + +// HasCap returns true if Peer has a capability +// with provided name. +func (p *Peer) HasCap(capName string) (yes bool) { + if p == nil || p.Peer == nil { + return false + } + for _, c := range p.Caps() { + if c.Name == capName { + return true + } + } + return false +} diff --git a/p2p/protocols/protocol_test.go b/p2p/protocols/protocol_test.go index 91779e5c0d..9bcd2208ca 100644 --- a/p2p/protocols/protocol_test.go +++ b/p2p/protocols/protocol_test.go @@ -17,6 +17,7 @@ package protocols import ( + "bytes" "context" "errors" "fmt" @@ -24,6 +25,7 @@ import ( "time" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" @@ -140,9 +142,9 @@ func newProtocol(pp *p2ptest.TestPeerPool) func(*p2p.Peer, p2p.MsgReadWriter) er } } -func protocolTester(t *testing.T, pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTester { +func protocolTester(pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTester { conf := adapters.RandomNodeConfig() - return p2ptest.NewProtocolTester(t, conf.ID, 2, newProtocol(pp)) + return p2ptest.NewProtocolTester(conf.ID, 2, newProtocol(pp)) } func protoHandshakeExchange(id enode.ID, proto *protoHandshake) []p2ptest.Exchange { @@ -171,7 +173,7 @@ func protoHandshakeExchange(id enode.ID, proto *protoHandshake) []p2ptest.Exchan func runProtoHandshake(t *testing.T, proto *protoHandshake, errs ...error) { pp := p2ptest.NewTestPeerPool() - s := protocolTester(t, pp) + s := protocolTester(pp) // TODO: make this more than one handshake node := s.Nodes[0] if err := s.TestExchanges(protoHandshakeExchange(node.ID(), proto)...); err != nil { @@ -186,6 +188,172 @@ func runProtoHandshake(t *testing.T, proto *protoHandshake, errs ...error) { } } +type dummyHook struct { + peer *Peer + size uint32 + msg interface{} + send bool + err error + waitC chan struct{} +} + +type dummyMsg struct { + Content string +} + +func (d *dummyHook) Send(peer *Peer, size uint32, msg interface{}) error { + d.peer = peer + d.size = size + d.msg = msg + d.send = true + return d.err +} + +func (d *dummyHook) Receive(peer *Peer, size uint32, msg interface{}) error { + d.peer = peer + d.size = size + d.msg = msg + d.send = false + d.waitC <- struct{}{} + return d.err +} + +func TestProtocolHook(t *testing.T) { + testHook := &dummyHook{ + waitC: make(chan struct{}, 1), + } + spec := &Spec{ + Name: "test", + Version: 42, + MaxMsgSize: 10 * 1024, + Messages: []interface{}{ + dummyMsg{}, + }, + Hook: testHook, + } + + runFunc := func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + peer := NewPeer(p, rw, spec) + ctx := context.TODO() + err := peer.Send(ctx, &dummyMsg{ + Content: "handshake"}) + + if err != nil { + t.Fatal(err) + } + + handle := func(ctx context.Context, msg interface{}) error { + return nil + } + + return peer.Run(handle) + } + + conf := adapters.RandomNodeConfig() + tester := p2ptest.NewProtocolTester(conf.ID, 2, runFunc) + err := tester.TestExchanges(p2ptest.Exchange{ + Expects: []p2ptest.Expect{ + { + Code: 0, + Msg: &dummyMsg{Content: "handshake"}, + Peer: tester.Nodes[0].ID(), + }, + }, + }) + if err != nil { + t.Fatal(err) + } + if testHook.msg == nil || testHook.msg.(*dummyMsg).Content != "handshake" { + t.Fatal("Expected msg to be set, but it is not") + } + if !testHook.send { + t.Fatal("Expected a send message, but it is not") + } + if testHook.peer == nil { + t.Fatal("Expected peer to be set, is nil") + } + if peerId := testHook.peer.ID(); peerId != tester.Nodes[0].ID() && peerId != tester.Nodes[1].ID() { + t.Fatalf("Expected peer ID to be set correctly, but it is not (got %v, exp %v or %v", peerId, tester.Nodes[0].ID(), tester.Nodes[1].ID()) + } + if testHook.size != 11 { //11 is the length of the encoded message + t.Fatalf("Expected size to be %d, but it is %d ", 1, testHook.size) + } + + err = tester.TestExchanges(p2ptest.Exchange{ + Triggers: []p2ptest.Trigger{ + { + Code: 0, + Msg: &dummyMsg{Content: "response"}, + Peer: tester.Nodes[1].ID(), + }, + }, + }) + + <-testHook.waitC + + if err != nil { + t.Fatal(err) + } + if testHook.msg == nil || testHook.msg.(*dummyMsg).Content != "response" { + t.Fatal("Expected msg to be set, but it is not") + } + if testHook.send { + t.Fatal("Expected a send message, but it is not") + } + if testHook.peer == nil || testHook.peer.ID() != tester.Nodes[1].ID() { + t.Fatal("Expected peer ID to be set correctly, but it is not") + } + if testHook.size != 10 { //11 is the length of the encoded message + t.Fatalf("Expected size to be %d, but it is %d ", 1, testHook.size) + } + + testHook.err = fmt.Errorf("dummy error") + err = tester.TestExchanges(p2ptest.Exchange{ + Triggers: []p2ptest.Trigger{ + { + Code: 0, + Msg: &dummyMsg{Content: "response"}, + Peer: tester.Nodes[1].ID(), + }, + }, + }) + + <-testHook.waitC + + time.Sleep(100 * time.Millisecond) + err = tester.TestDisconnected(&p2ptest.Disconnect{Peer: tester.Nodes[1].ID(), Error: testHook.err}) + if err != nil { + t.Fatalf("Expected a specific disconnect error, but got different one: %v", err) + } + +} + +//We need to test that if the hook is not defined, then message infrastructure +//(send,receive) still works +func TestNoHook(t *testing.T) { + //create a test spec + spec := createTestSpec() + //a random node + id := adapters.RandomNodeConfig().ID + //a peer + p := p2p.NewPeer(id, "testPeer", nil) + rw := &dummyRW{} + peer := NewPeer(p, rw, spec) + ctx := context.TODO() + msg := &perBytesMsgSenderPays{Content: "testBalance"} + //send a message + err := peer.Send(ctx, msg) + if err != nil { + t.Fatal(err) + } + //simulate receiving a message + rw.msg = msg + peer.handleIncoming(func(ctx context.Context, msg interface{}) error { + return nil + }) + //all should just work and not result in any error +} + func TestProtoHandshakeVersionMismatch(t *testing.T) { runProtoHandshake(t, &protoHandshake{41, "420"}, errorf(ErrHandshake, errorf(ErrHandler, "(msg code 0): 41 (!= 42)").Error())) } @@ -224,7 +392,7 @@ func moduleHandshakeExchange(id enode.ID, resp uint) []p2ptest.Exchange { func runModuleHandshake(t *testing.T, resp uint, errs ...error) { pp := p2ptest.NewTestPeerPool() - s := protocolTester(t, pp) + s := protocolTester(pp) node := s.Nodes[0] if err := s.TestExchanges(protoHandshakeExchange(node.ID(), &protoHandshake{42, "420"})...); err != nil { t.Fatal(err) @@ -304,7 +472,7 @@ func testMultiPeerSetup(a, b enode.ID) []p2ptest.Exchange { func runMultiplePeers(t *testing.T, peer int, errs ...error) { pp := p2ptest.NewTestPeerPool() - s := protocolTester(t, pp) + s := protocolTester(pp) if err := s.TestExchanges(testMultiPeerSetup(s.Nodes[0].ID(), s.Nodes[1].ID())...); err != nil { t.Fatal(err) @@ -387,3 +555,39 @@ func XTestMultiplePeersDropOther(t *testing.T) { fmt.Errorf("subprotocol error"), ) } + +//dummy implementation of a MsgReadWriter +//this allows for quick and easy unit tests without +//having to build up the complete protocol +type dummyRW struct { + msg interface{} + size uint32 + code uint64 +} + +func (d *dummyRW) WriteMsg(msg p2p.Msg) error { + return nil +} + +func (d *dummyRW) ReadMsg() (p2p.Msg, error) { + enc := bytes.NewReader(d.getDummyMsg()) + return p2p.Msg{ + Code: d.code, + Size: d.size, + Payload: enc, + ReceivedAt: time.Now(), + }, nil +} + +func (d *dummyRW) getDummyMsg() []byte { + r, _ := rlp.EncodeToBytes(d.msg) + var b bytes.Buffer + wmsg := WrappedMsg{ + Context: b.Bytes(), + Size: uint32(len(r)), + Payload: r, + } + rr, _ := rlp.EncodeToBytes(wmsg) + d.size = uint32(len(rr)) + return rr +} diff --git a/p2p/protocols/reporter.go b/p2p/protocols/reporter.go new file mode 100644 index 0000000000..9f3cf5a5b8 --- /dev/null +++ b/p2p/protocols/reporter.go @@ -0,0 +1,162 @@ +// 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 . + +package protocols + +import ( + "encoding/binary" + "time" + + "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/metrics" + + "github.com/syndtr/goleveldb/leveldb" +) + +//AccountMetrics abstracts away the metrics DB and +//the reporter to persist metrics +type AccountingMetrics struct { + reporter *reporter +} + +//Close will be called when the node is being shutdown +//for a graceful cleanup +func (am *AccountingMetrics) Close() { + close(am.reporter.quit) + // wait for reporter loop to finish saving metrics + // before reporter database is closed + select { + case <-time.After(10 * time.Second): + log.Error("accounting metrics reporter timeout") + case <-am.reporter.done: + } + am.reporter.db.Close() +} + +//reporter is an internal structure used to write p2p accounting related +//metrics to a LevelDB. It will periodically write the accrued metrics to the DB. +type reporter struct { + reg metrics.Registry //the registry for these metrics (independent of other metrics) + interval time.Duration //duration at which the reporter will persist metrics + db *leveldb.DB //the actual DB + quit chan struct{} //quit the reporter loop + done chan struct{} //signal that reporter loop is done +} + +//NewMetricsDB creates a new LevelDB instance used to persist metrics defined +//inside p2p/protocols/accounting.go +func NewAccountingMetrics(r metrics.Registry, d time.Duration, path string) *AccountingMetrics { + var val = make([]byte, 8) + var err error + + //Create the LevelDB + db, err := leveldb.OpenFile(path, nil) + if err != nil { + log.Error(err.Error()) + return nil + } + + //Check for all defined metrics that there is a value in the DB + //If there is, assign it to the metric. This means that the node + //has been running before and that metrics have been persisted. + metricsMap := map[string]metrics.Counter{ + "account.balance.credit": mBalanceCredit, + "account.balance.debit": mBalanceDebit, + "account.bytes.credit": mBytesCredit, + "account.bytes.debit": mBytesDebit, + "account.msg.credit": mMsgCredit, + "account.msg.debit": mMsgDebit, + "account.peerdrops": mPeerDrops, + "account.selfdrops": mSelfDrops, + } + //iterate the map and get the values + for key, metric := range metricsMap { + val, err = db.Get([]byte(key), nil) + //until the first time a value is being written, + //this will return an error. + //it could be beneficial though to log errors later, + //but that would require a different logic + if err == nil { + metric.Inc(int64(binary.BigEndian.Uint64(val))) + } + } + + //create the reporter + rep := &reporter{ + reg: r, + interval: d, + db: db, + quit: make(chan struct{}), + done: make(chan struct{}), + } + + //run the go routine + go rep.run() + + m := &AccountingMetrics{ + reporter: rep, + } + + return m +} + +//run is the goroutine which periodically sends the metrics to the configured LevelDB +func (r *reporter) run() { + // signal that the reporter loop is done + defer close(r.done) + + intervalTicker := time.NewTicker(r.interval) + + for { + select { + case <-intervalTicker.C: + //at each tick send the metrics + if err := r.save(); err != nil { + log.Error("unable to send metrics to LevelDB", "err", err) + //If there is an error in writing, exit the routine; we assume here that the error is + //severe and don't attempt to write again. + //Also, this should prevent leaking when the node is stopped + return + } + case <-r.quit: + //graceful shutdown + if err := r.save(); err != nil { + log.Error("unable to send metrics to LevelDB", "err", err) + } + return + } + } +} + +//send the metrics to the DB +func (r *reporter) save() error { + //create a LevelDB Batch + batch := leveldb.Batch{} + //for each metric in the registry (which is independent)... + r.reg.Each(func(name string, i interface{}) { + metric, ok := i.(metrics.Counter) + if ok { + //assuming every metric here to be a Counter (separate registry) + //...create a snapshot... + ms := metric.Snapshot() + byteVal := make([]byte, 8) + binary.BigEndian.PutUint64(byteVal, uint64(ms.Count())) + //...and save the value to the DB + batch.Put([]byte(name), byteVal) + } + }) + return r.db.Write(&batch, nil) +} diff --git a/p2p/protocols/repoter_test.go b/p2p/protocols/repoter_test.go new file mode 100644 index 0000000000..85a8a11a79 --- /dev/null +++ b/p2p/protocols/repoter_test.go @@ -0,0 +1,83 @@ +// 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 . + +package protocols + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" + + "github.com/AlayaNetwork/Alaya-Go/log" +) + +//TestReporter tests that the metrics being collected for p2p accounting +//are being persisted and available after restart of a node. +//It simulates restarting by just recreating the DB as if the node had restarted. +func TestReporter(t *testing.T) { + //create a test directory + dir, err := ioutil.TempDir("", "reporter-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + //setup the metrics + log.Debug("Setting up metrics first time") + reportInterval := 5 * time.Millisecond + metrics := SetupAccountingMetrics(reportInterval, filepath.Join(dir, "test.db")) + log.Debug("Done.") + + //change metrics + mBalanceCredit.Inc(12) + mBytesCredit.Inc(34) + mMsgDebit.Inc(9) + + //store expected metrics + expectedBalanceCredit := mBalanceCredit.Count() + expectedBytesCredit := mBytesCredit.Count() + expectedMsgDebit := mMsgDebit.Count() + + //give the reporter time to write the metrics to DB + time.Sleep(20 * time.Millisecond) + + //close the DB also, or we can't create a new one + metrics.Close() + + //clear the metrics - this effectively simulates the node having shut down... + mBalanceCredit.Clear() + mBytesCredit.Clear() + mMsgDebit.Clear() + + //setup the metrics again + log.Debug("Setting up metrics second time") + metrics = SetupAccountingMetrics(reportInterval, filepath.Join(dir, "test.db")) + defer metrics.Close() + log.Debug("Done.") + + //now check the metrics, they should have the same value as before "shutdown" + if mBalanceCredit.Count() != expectedBalanceCredit { + t.Fatalf("Expected counter to be %d, but is %d", expectedBalanceCredit, mBalanceCredit.Count()) + } + if mBytesCredit.Count() != expectedBytesCredit { + t.Fatalf("Expected counter to be %d, but is %d", expectedBytesCredit, mBytesCredit.Count()) + } + if mMsgDebit.Count() != expectedMsgDebit { + t.Fatalf("Expected counter to be %d, but is %d", expectedMsgDebit, mMsgDebit.Count()) + } +} diff --git a/p2p/rlpx.go b/p2p/rlpx.go index 6788e940cd..203090c8c3 100644 --- a/p2p/rlpx.go +++ b/p2p/rlpx.go @@ -41,9 +41,10 @@ import ( "github.com/golang/snappy" + "golang.org/x/crypto/sha3" + "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/crypto/ecies" - "github.com/AlayaNetwork/Alaya-Go/crypto/sha3" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -154,7 +155,7 @@ func readProtocolHandshake(rw MsgReader, our *protoHandshake) (*protoHandshake, } if msg.Code == discMsg { // Disconnect before protocol handshake is valid according to the - // spec and we send it ourself if the posthanshake checks fail. + // spec and we send it ourself if the post-handshake checks fail. // We can't return the reason directly, though, because it is echoed // back otherwise. Wrap it in a string instead. var reason [1]DiscReason @@ -256,10 +257,10 @@ func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) { } // setup sha3 instances for the MACs - mac1 := sha3.NewKeccak256() + mac1 := sha3.NewLegacyKeccak256() mac1.Write(xor(s.MAC, h.respNonce)) mac1.Write(auth) - mac2 := sha3.NewKeccak256() + mac2 := sha3.NewLegacyKeccak256() mac2.Write(xor(s.MAC, h.initNonce)) mac2.Write(authResp) if h.initiator { diff --git a/p2p/rlpx_test.go b/p2p/rlpx_test.go index d4641feca3..a874627de0 100644 --- a/p2p/rlpx_test.go +++ b/p2p/rlpx_test.go @@ -33,9 +33,10 @@ import ( "github.com/davecgh/go-spew/spew" + "golang.org/x/crypto/sha3" + "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/crypto/ecies" - "github.com/AlayaNetwork/Alaya-Go/crypto/sha3" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/pipes" "github.com/AlayaNetwork/Alaya-Go/rlp" ) @@ -335,8 +336,8 @@ func TestRLPXFrameRW(t *testing.T) { s1 := secrets{ AES: aesSecret, MAC: macSecret, - EgressMAC: sha3.NewKeccak256(), - IngressMAC: sha3.NewKeccak256(), + EgressMAC: sha3.NewLegacyKeccak256(), + IngressMAC: sha3.NewLegacyKeccak256(), } s1.EgressMAC.Write(egressMACinit) s1.IngressMAC.Write(ingressMACinit) @@ -345,8 +346,8 @@ func TestRLPXFrameRW(t *testing.T) { s2 := secrets{ AES: aesSecret, MAC: macSecret, - EgressMAC: sha3.NewKeccak256(), - IngressMAC: sha3.NewKeccak256(), + EgressMAC: sha3.NewLegacyKeccak256(), + IngressMAC: sha3.NewLegacyKeccak256(), } s2.EgressMAC.Write(ingressMACinit) s2.IngressMAC.Write(egressMACinit) diff --git a/p2p/server.go b/p2p/server.go index ec25012cfa..07e1460ad8 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -249,7 +249,7 @@ type transport interface { MsgReadWriter // transports must provide Close because we use MsgPipe in some of // the tests. Closing the actual network connection doesn't do - // anything in those tests because NsgPipe doesn't use it. + // anything in those tests because MsgPipe doesn't use it. close(err error) } diff --git a/p2p/server_test.go b/p2p/server_test.go index ca313b4124..2eeff3f9dd 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -30,8 +30,9 @@ import ( "github.com/stretchr/testify/assert" + "golang.org/x/crypto/sha3" + "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/crypto/sha3" "github.com/AlayaNetwork/Alaya-Go/log" ) @@ -51,8 +52,8 @@ func newTestTransport(rpub *ecdsa.PublicKey, fd net.Conn) transport { wrapped.rw = newRLPXFrameRW(fd, secrets{ MAC: zero16, AES: zero16, - IngressMAC: sha3.NewKeccak256(), - EgressMAC: sha3.NewKeccak256(), + IngressMAC: sha3.NewLegacyKeccak256(), + EgressMAC: sha3.NewLegacyKeccak256(), }) return &testTransport{rpub: rpub, rlpx: wrapped} } diff --git a/p2p/simulations/connect.go b/p2p/simulations/connect.go new file mode 100644 index 0000000000..f3ed1e6b21 --- /dev/null +++ b/p2p/simulations/connect.go @@ -0,0 +1,132 @@ +// 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 . + +package simulations + +import ( + "errors" + "strings" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +var ( + ErrNodeNotFound = errors.New("node not found") +) + +// ConnectToLastNode connects the node with provided NodeID +// to the last node that is up, and avoiding connection to self. +// It is useful when constructing a chain network topology +// when Network adds and removes nodes dynamically. +func (net *Network) ConnectToLastNode(id enode.ID) (err error) { + ids := net.getUpNodeIDs() + l := len(ids) + if l < 2 { + return nil + } + last := ids[l-1] + if last == id { + last = ids[l-2] + } + return net.connect(last, id) +} + +// ConnectToRandomNode connects the node with provided NodeID +// to a random node that is up. +func (net *Network) ConnectToRandomNode(id enode.ID) (err error) { + selected := net.GetRandomUpNode(id) + if selected == nil { + return ErrNodeNotFound + } + return net.connect(selected.ID(), id) +} + +// ConnectNodesFull connects all nodes one to another. +// It provides a complete connectivity in the network +// which should be rarely needed. +func (net *Network) ConnectNodesFull(ids []enode.ID) (err error) { + if ids == nil { + ids = net.getUpNodeIDs() + } + for i, lid := range ids { + for _, rid := range ids[i+1:] { + if err = net.connect(lid, rid); err != nil { + return err + } + } + } + return nil +} + +// ConnectNodesChain connects all nodes in a chain topology. +// If ids argument is nil, all nodes that are up will be connected. +func (net *Network) ConnectNodesChain(ids []enode.ID) (err error) { + if ids == nil { + ids = net.getUpNodeIDs() + } + l := len(ids) + for i := 0; i < l-1; i++ { + if err := net.connect(ids[i], ids[i+1]); err != nil { + return err + } + } + return nil +} + +// ConnectNodesRing connects all nodes in a ring topology. +// If ids argument is nil, all nodes that are up will be connected. +func (net *Network) ConnectNodesRing(ids []enode.ID) (err error) { + if ids == nil { + ids = net.getUpNodeIDs() + } + l := len(ids) + if l < 2 { + return nil + } + if err := net.ConnectNodesChain(ids); err != nil { + return err + } + return net.connect(ids[l-1], ids[0]) +} + +// ConnectNodesStar connects all nodes into a star topology +// If ids argument is nil, all nodes that are up will be connected. +func (net *Network) ConnectNodesStar(ids []enode.ID, center enode.ID) (err error) { + if ids == nil { + ids = net.getUpNodeIDs() + } + for _, id := range ids { + if center == id { + continue + } + if err := net.connect(center, id); err != nil { + return err + } + } + return nil +} + +// connect connects two nodes but ignores already connected error. +func (net *Network) connect(oneID, otherID enode.ID) error { + return ignoreAlreadyConnectedErr(net.Connect(oneID, otherID)) +} + +func ignoreAlreadyConnectedErr(err error) error { + if err == nil || strings.Contains(err.Error(), "already connected") { + return nil + } + return err +} diff --git a/p2p/simulations/connect_test.go b/p2p/simulations/connect_test.go new file mode 100644 index 0000000000..f71e1981da --- /dev/null +++ b/p2p/simulations/connect_test.go @@ -0,0 +1,172 @@ +// 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 . + +package simulations + +import ( + "testing" + + "github.com/AlayaNetwork/Alaya-Go/node" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" +) + +func newTestNetwork(t *testing.T, nodeCount int) (*Network, []enode.ID) { + t.Helper() + adapter := adapters.NewSimAdapter(adapters.Services{ + "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { + return NewNoopService(nil), nil + }, + }) + + // create network + network := NewNetwork(adapter, &NetworkConfig{ + DefaultService: "noopwoop", + }) + + // create and start nodes + ids := make([]enode.ID, nodeCount) + for i := range ids { + conf := adapters.RandomNodeConfig() + node, err := network.NewNodeWithConfig(conf) + if err != nil { + t.Fatalf("error creating node: %s", err) + } + if err := network.Start(node.ID()); err != nil { + t.Fatalf("error starting node: %s", err) + } + ids[i] = node.ID() + } + + if len(network.Conns) > 0 { + t.Fatal("no connections should exist after just adding nodes") + } + + return network, ids +} + +func TestConnectToLastNode(t *testing.T) { + net, ids := newTestNetwork(t, 10) + defer net.Shutdown() + + first := ids[0] + if err := net.ConnectToLastNode(first); err != nil { + t.Fatal(err) + } + + last := ids[len(ids)-1] + for i, id := range ids { + if id == first || id == last { + continue + } + + if net.GetConn(first, id) != nil { + t.Errorf("connection must not exist with node(ind: %v, id: %v)", i, id) + } + } + + if net.GetConn(first, last) == nil { + t.Error("first and last node must be connected") + } +} + +func TestConnectToRandomNode(t *testing.T) { + net, ids := newTestNetwork(t, 10) + defer net.Shutdown() + + err := net.ConnectToRandomNode(ids[0]) + if err != nil { + t.Fatal(err) + } + + var cc int + for i, a := range ids { + for _, b := range ids[i:] { + if net.GetConn(a, b) != nil { + cc++ + } + } + } + + if cc != 1 { + t.Errorf("expected one connection, got %v", cc) + } +} + +func TestConnectNodesFull(t *testing.T) { + tests := []struct { + name string + nodeCount int + }{ + {name: "no node", nodeCount: 0}, + {name: "single node", nodeCount: 1}, + {name: "2 nodes", nodeCount: 2}, + {name: "3 nodes", nodeCount: 3}, + {name: "even number of nodes", nodeCount: 12}, + {name: "odd number of nodes", nodeCount: 13}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + net, ids := newTestNetwork(t, test.nodeCount) + defer net.Shutdown() + + err := net.ConnectNodesFull(ids) + if err != nil { + t.Fatal(err) + } + + VerifyFull(t, net, ids) + }) + } +} + +func TestConnectNodesChain(t *testing.T) { + net, ids := newTestNetwork(t, 10) + defer net.Shutdown() + + err := net.ConnectNodesChain(ids) + if err != nil { + t.Fatal(err) + } + + VerifyChain(t, net, ids) +} + +func TestConnectNodesRing(t *testing.T) { + net, ids := newTestNetwork(t, 10) + defer net.Shutdown() + + err := net.ConnectNodesRing(ids) + if err != nil { + t.Fatal(err) + } + + VerifyRing(t, net, ids) +} + +func TestConnectNodesStar(t *testing.T) { + net, ids := newTestNetwork(t, 10) + defer net.Shutdown() + + pivotIndex := 2 + + err := net.ConnectNodesStar(ids, ids[pivotIndex]) + if err != nil { + t.Fatal(err) + } + + VerifyStar(t, net, ids, pivotIndex) +} diff --git a/p2p/simulations/http_test.go b/p2p/simulations/http_test.go index cdf41c23bb..f9c3bdc585 100644 --- a/p2p/simulations/http_test.go +++ b/p2p/simulations/http_test.go @@ -18,6 +18,7 @@ package simulations import ( "context" + "flag" "fmt" "math/rand" "net/http/httptest" @@ -27,6 +28,8 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/event" @@ -34,8 +37,21 @@ import ( "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" "github.com/AlayaNetwork/Alaya-Go/rpc" + + "github.com/mattn/go-colorable" +) + +var ( + loglevel = flag.Int("loglevel", 2, "verbosity of logs") ) +func init() { + flag.Parse() + + log.PrintOrigins(true) + log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) +} + // testService implements the node.Service interface and provides protocols // and APIs which are useful for testing nodes in a simulation network type testService struct { @@ -585,9 +601,26 @@ func TestHTTPNodeRPC(t *testing.T) { // TestHTTPSnapshot tests creating and loading network snapshots func TestHTTPSnapshot(t *testing.T) { // start the server - _, s := testHTTPServer(t) + network, s := testHTTPServer(t) defer s.Close() + var eventsDone = make(chan struct{}) + count := 1 + eventsDoneChan := make(chan *Event) + eventSub := network.Events().Subscribe(eventsDoneChan) + go func() { + defer eventSub.Unsubscribe() + for event := range eventsDoneChan { + if event.Type == EventTypeConn && !event.Control { + count-- + if count == 0 { + eventsDone <- struct{}{} + return + } + } + } + }() + // create a two-node network client := NewClient(s.URL) nodeCount := 2 @@ -621,7 +654,7 @@ func TestHTTPSnapshot(t *testing.T) { } states[i] = state } - + <-eventsDone // create a snapshot snap, err := client.CreateSnapshot() if err != nil { @@ -635,9 +668,23 @@ func TestHTTPSnapshot(t *testing.T) { } // create another network - _, s = testHTTPServer(t) + network2, s := testHTTPServer(t) defer s.Close() client = NewClient(s.URL) + count = 1 + eventSub = network2.Events().Subscribe(eventsDoneChan) + go func() { + defer eventSub.Unsubscribe() + for event := range eventsDoneChan { + if event.Type == EventTypeConn && !event.Control { + count-- + if count == 0 { + eventsDone <- struct{}{} + return + } + } + } + }() // subscribe to events so we can check them later events := make(chan *Event, 100) @@ -652,6 +699,7 @@ func TestHTTPSnapshot(t *testing.T) { if err := client.LoadSnapshot(snap); err != nil { t.Fatalf("error loading snapshot: %s", err) } + <-eventsDone // check the nodes and connection exists net, err := client.GetNetwork() @@ -677,6 +725,9 @@ func TestHTTPSnapshot(t *testing.T) { if conn.Other.String() != nodes[1].ID { t.Fatalf("expected connection to have other=%q, got other=%q", nodes[1].ID, conn.Other) } + if !conn.Up { + t.Fatal("should be up") + } // check the node states were restored for i, node := range nodes { diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go index 669627f411..3f17ab9259 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -20,8 +20,10 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" + "math/rand" "sync" "time" @@ -117,7 +119,7 @@ func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) Node: adapterNode, Config: conf, } - log.Trace(fmt.Sprintf("node %v created", conf.ID)) + log.Trace("Node created", "id", conf.ID) net.nodeMap[conf.ID] = len(net.Nodes) net.Nodes = append(net.Nodes, node) @@ -167,23 +169,28 @@ func (net *Network) Start(id enode.ID) error { // snapshots func (net *Network) startWithSnapshots(id enode.ID, snapshots map[string][]byte) error { net.lock.Lock() - defer net.lock.Unlock() + node := net.getNode(id) if node == nil { + net.lock.Unlock() return fmt.Errorf("node %v does not exist", id) } if node.Up { + net.lock.Unlock() return fmt.Errorf("node %v already up", id) } log.Trace("Starting node", "id", id, "adapter", net.nodeAdapter.Name()) if err := node.Start(snapshots); err != nil { + net.lock.Unlock() log.Warn("Node startup failed", "id", id, "err", err) return err } node.Up = true log.Info("Started node", "id", id) + ev := NewEvent(node) + net.lock.Unlock() - net.events.Send(NewEvent(node)) + net.events.Send(ev) // subscribe to peer events client, err := node.Client() @@ -208,12 +215,14 @@ func (net *Network) watchPeerEvents(id enode.ID, events chan *p2p.PeerEvent, sub // assume the node is now down net.lock.Lock() defer net.lock.Unlock() + node := net.getNode(id) if node == nil { return } node.Up = false - net.events.Send(NewEvent(node)) + ev := NewEvent(node) + net.events.Send(ev) }() for { select { @@ -240,7 +249,7 @@ func (net *Network) watchPeerEvents(id enode.ID, events chan *p2p.PeerEvent, sub case err := <-sub.Err(): if err != nil { - log.Error(fmt.Sprintf("error getting peer events for node %v", id), "err", err) + log.Error("Error in peer event subscription", "id", id, "err", err) } return } @@ -252,9 +261,11 @@ func (net *Network) Stop(id enode.ID) error { net.lock.Lock() node := net.getNode(id) if node == nil { + net.lock.Unlock() return fmt.Errorf("node %v does not exist", id) } if !node.Up { + net.lock.Unlock() return fmt.Errorf("node %v already down", id) } node.Up = false @@ -268,7 +279,10 @@ func (net *Network) Stop(id enode.ID) error { return err } log.Info("Stopped node", "id", id, "err", err) - net.events.Send(ControlEvent(node)) + net.lock.Lock() + ev := ControlEvent(node) + net.lock.Unlock() + net.events.Send(ev) return nil } @@ -369,23 +383,32 @@ func (net *Network) DidReceive(sender, receiver enode.ID, proto string, code uin // GetNode gets the node with the given ID, returning nil if the node does not // exist func (net *Network) GetNode(id enode.ID) *Node { - net.lock.Lock() - defer net.lock.Unlock() + net.lock.RLock() + defer net.lock.RUnlock() return net.getNode(id) } // GetNode gets the node with the given name, returning nil if the node does // not exist func (net *Network) GetNodeByName(name string) *Node { - net.lock.Lock() - defer net.lock.Unlock() + net.lock.RLock() + defer net.lock.RUnlock() return net.getNodeByName(name) } +func (net *Network) getNodeByName(name string) *Node { + for _, node := range net.Nodes { + if node.Config.Name == name { + return node + } + } + return nil +} + // GetNodes returns the existing nodes func (net *Network) GetNodes() (nodes []*Node) { - net.lock.Lock() - defer net.lock.Unlock() + net.lock.RLock() + defer net.lock.RUnlock() nodes = append(nodes, net.Nodes...) return nodes @@ -399,20 +422,67 @@ func (net *Network) getNode(id enode.ID) *Node { return net.Nodes[i] } -func (net *Network) getNodeByName(name string) *Node { +// GetRandomUpNode returns a random node on the network, which is running. +func (net *Network) GetRandomUpNode(excludeIDs ...enode.ID) *Node { + net.lock.RLock() + defer net.lock.RUnlock() + return net.getRandomNode(net.getUpNodeIDs(), excludeIDs) +} + +func (net *Network) getUpNodeIDs() (ids []enode.ID) { for _, node := range net.Nodes { - if node.Config.Name == name { - return node + if node.Up { + ids = append(ids, node.ID()) } } - return nil + return ids +} + +// GetRandomDownNode returns a random node on the network, which is stopped. +func (net *Network) GetRandomDownNode(excludeIDs ...enode.ID) *Node { + net.lock.RLock() + defer net.lock.RUnlock() + return net.getRandomNode(net.getDownNodeIDs(), excludeIDs) +} + +func (net *Network) getDownNodeIDs() (ids []enode.ID) { + for _, node := range net.GetNodes() { + if !node.Up { + ids = append(ids, node.ID()) + } + } + return ids +} + +func (net *Network) getRandomNode(ids []enode.ID, excludeIDs []enode.ID) *Node { + filtered := filterIDs(ids, excludeIDs) + + l := len(filtered) + if l == 0 { + return nil + } + return net.GetNode(filtered[rand.Intn(l)]) +} + +func filterIDs(ids []enode.ID, excludeIDs []enode.ID) []enode.ID { + exclude := make(map[enode.ID]bool) + for _, id := range excludeIDs { + exclude[id] = true + } + var filtered []enode.ID + for _, id := range ids { + if _, found := exclude[id]; !found { + filtered = append(filtered, id) + } + } + return filtered } // GetConn returns the connection which exists between "one" and "other" // regardless of which node initiated the connection func (net *Network) GetConn(oneID, otherID enode.ID) *Conn { - net.lock.Lock() - defer net.lock.Unlock() + net.lock.RLock() + defer net.lock.RUnlock() return net.getConn(oneID, otherID) } @@ -458,7 +528,7 @@ func (net *Network) getConn(oneID, otherID enode.ID) *Conn { return net.Conns[i] } -// InitConn(one, other) retrieves the connectiton model for the connection between +// InitConn(one, other) retrieves the connection model for the connection between // peers one and other, or creates a new one if it does not exist // the order of nodes does not matter, i.e., Conn(i,j) == Conn(j, i) // it checks if the connection is already up, and if the nodes are running @@ -485,7 +555,7 @@ func (net *Network) InitConn(oneID, otherID enode.ID) (*Conn, error) { err = conn.nodesUp() if err != nil { - log.Trace(fmt.Sprintf("nodes not up: %v", err)) + log.Trace("Nodes not up", "err", err) return nil, fmt.Errorf("nodes not up: %v", err) } log.Debug("Connection initiated", "id", oneID, "other", otherID) @@ -510,8 +580,8 @@ func (net *Network) Shutdown() { close(net.quitc) } -//Reset resets all network properties: -//emtpies the nodes and the connection list +// Reset resets all network properties: +// empties the nodes and the connection list func (net *Network) Reset() { net.lock.Lock() defer net.lock.Unlock() @@ -650,6 +720,14 @@ type NodeSnapshot struct { // Snapshot creates a network snapshot func (net *Network) Snapshot() (*Snapshot, error) { + return net.snapshot(nil, nil) +} + +func (net *Network) SnapshotWithServices(addServices []string, removeServices []string) (*Snapshot, error) { + return net.snapshot(addServices, removeServices) +} + +func (net *Network) snapshot(addServices []string, removeServices []string) (*Snapshot, error) { net.lock.Lock() defer net.lock.Unlock() snap := &Snapshot{ @@ -666,15 +744,49 @@ func (net *Network) Snapshot() (*Snapshot, error) { return nil, err } snap.Nodes[i].Snapshots = snapshots + for _, addSvc := range addServices { + haveSvc := false + for _, svc := range snap.Nodes[i].Node.Config.Services { + if svc == addSvc { + haveSvc = true + break + } + } + if !haveSvc { + snap.Nodes[i].Node.Config.Services = append(snap.Nodes[i].Node.Config.Services, addSvc) + } + } + if len(removeServices) > 0 { + var cleanedServices []string + for _, svc := range snap.Nodes[i].Node.Config.Services { + haveSvc := false + for _, rmSvc := range removeServices { + if rmSvc == svc { + haveSvc = true + break + } + } + if !haveSvc { + cleanedServices = append(cleanedServices, svc) + } + + } + snap.Nodes[i].Node.Config.Services = cleanedServices + } } - for i, conn := range net.Conns { - snap.Conns[i] = *conn + for _, conn := range net.Conns { + if conn.Up { + snap.Conns = append(snap.Conns, *conn) + } } return snap, nil } +var snapshotLoadTimeout = 120 * time.Second + // Load loads a network snapshot func (net *Network) Load(snap *Snapshot) error { + // Start nodes. for _, n := range snap.Nodes { if _, err := net.NewNodeWithConfig(n.Node.Config); err != nil { return err @@ -686,6 +798,69 @@ func (net *Network) Load(snap *Snapshot) error { return err } } + + // Prepare connection events counter. + allConnected := make(chan struct{}) // closed when all connections are established + done := make(chan struct{}) // ensures that the event loop goroutine is terminated + defer close(done) + + // Subscribe to event channel. + // It needs to be done outside of the event loop goroutine (created below) + // to ensure that the event channel is blocking before connect calls are made. + events := make(chan *Event) + sub := net.Events().Subscribe(events) + defer sub.Unsubscribe() + + go func() { + // Expected number of connections. + total := len(snap.Conns) + // Set of all established connections from the snapshot, not other connections. + // Key array element 0 is the connection One field value, and element 1 connection Other field. + connections := make(map[[2]enode.ID]struct{}, total) + + for { + select { + case e := <-events: + // Ignore control events as they do not represent + // connect or disconnect (Up) state change. + if e.Control { + continue + } + // Detect only connection events. + if e.Type != EventTypeConn { + continue + } + connection := [2]enode.ID{e.Conn.One, e.Conn.Other} + // Nodes are still not connected or have been disconnected. + if !e.Conn.Up { + // Delete the connection from the set of established connections. + // This will prevent false positive in case disconnections happen. + delete(connections, connection) + log.Warn("load snapshot: unexpected disconnection", "one", e.Conn.One, "other", e.Conn.Other) + continue + } + // Check that the connection is from the snapshot. + for _, conn := range snap.Conns { + if conn.One == e.Conn.One && conn.Other == e.Conn.Other { + // Add the connection to the set of established connections. + connections[connection] = struct{}{} + if len(connections) == total { + // Signal that all nodes are connected. + close(allConnected) + return + } + + break + } + } + case <-done: + // Load function returned, terminate this goroutine. + return + } + } + }() + + // Start connecting. for _, conn := range snap.Conns { if !net.GetNode(conn.One).Up || !net.GetNode(conn.Other).Up { @@ -697,6 +872,14 @@ func (net *Network) Load(snap *Snapshot) error { return err } } + + select { + // Wait until all connections from the snapshot are established. + case <-allConnected: + // Make sure that we do not wait forever. + case <-time.After(snapshotLoadTimeout): + return errors.New("snapshot connections not established") + } return nil } diff --git a/p2p/simulations/network_test.go b/p2p/simulations/network_test.go index eb27509fe3..73ebd486fd 100644 --- a/p2p/simulations/network_test.go +++ b/p2p/simulations/network_test.go @@ -18,14 +18,266 @@ package simulations import ( "context" + "encoding/json" "fmt" + "strconv" + "strings" "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" ) +// Tests that a created snapshot with a minimal service only contains the expected connections +// and that a network when loaded with this snapshot only contains those same connections +func TestSnapshot(t *testing.T) { + + // PART I + // create snapshot from ring network + + // this is a minimal service, whose protocol will take exactly one message OR close of connection before quitting + adapter := adapters.NewSimAdapter(adapters.Services{ + "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { + return NewNoopService(nil), nil + }, + }) + + // create network + network := NewNetwork(adapter, &NetworkConfig{ + DefaultService: "noopwoop", + }) + // \todo consider making a member of network, set to true threadsafe when shutdown + runningOne := true + defer func() { + if runningOne { + network.Shutdown() + } + }() + + // create and start nodes + nodeCount := 20 + ids := make([]enode.ID, nodeCount) + for i := 0; i < nodeCount; i++ { + conf := adapters.RandomNodeConfig() + node, err := network.NewNodeWithConfig(conf) + if err != nil { + t.Fatalf("error creating node: %s", err) + } + if err := network.Start(node.ID()); err != nil { + t.Fatalf("error starting node: %s", err) + } + ids[i] = node.ID() + } + + // subscribe to peer events + evC := make(chan *Event) + sub := network.Events().Subscribe(evC) + defer sub.Unsubscribe() + + // connect nodes in a ring + // spawn separate thread to avoid deadlock in the event listeners + go func() { + for i, id := range ids { + peerID := ids[(i+1)%len(ids)] + if err := network.Connect(id, peerID); err != nil { + t.Fatal(err) + } + } + }() + + // collect connection events up to expected number + ctx, cancel := context.WithTimeout(context.TODO(), time.Second) + defer cancel() + checkIds := make(map[enode.ID][]enode.ID) + connEventCount := nodeCount +OUTER: + for { + select { + case <-ctx.Done(): + t.Fatal(ctx.Err()) + case ev := <-evC: + if ev.Type == EventTypeConn && !ev.Control { + + // fail on any disconnect + if !ev.Conn.Up { + t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other) + } + checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other) + checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One) + connEventCount-- + log.Debug("ev", "count", connEventCount) + if connEventCount == 0 { + break OUTER + } + } + } + } + + // create snapshot of current network + snap, err := network.Snapshot() + if err != nil { + t.Fatal(err) + } + j, err := json.Marshal(snap) + if err != nil { + t.Fatal(err) + } + log.Debug("snapshot taken", "nodes", len(snap.Nodes), "conns", len(snap.Conns), "json", string(j)) + + // verify that the snap element numbers check out + if len(checkIds) != len(snap.Conns) || len(checkIds) != len(snap.Nodes) { + t.Fatalf("snapshot wrong node,conn counts %d,%d != %d", len(snap.Nodes), len(snap.Conns), len(checkIds)) + } + + // shut down sim network + runningOne = false + sub.Unsubscribe() + network.Shutdown() + + // check that we have all the expected connections in the snapshot + for nodid, nodConns := range checkIds { + for _, nodConn := range nodConns { + var match bool + for _, snapConn := range snap.Conns { + if snapConn.One == nodid && snapConn.Other == nodConn { + match = true + break + } else if snapConn.Other == nodid && snapConn.One == nodConn { + match = true + break + } + } + if !match { + t.Fatalf("snapshot missing conn %v -> %v", nodid, nodConn) + } + } + } + log.Info("snapshot checked") + + // PART II + // load snapshot and verify that exactly same connections are formed + + adapter = adapters.NewSimAdapter(adapters.Services{ + "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { + return NewNoopService(nil), nil + }, + }) + network = NewNetwork(adapter, &NetworkConfig{ + DefaultService: "noopwoop", + }) + defer func() { + network.Shutdown() + }() + + // subscribe to peer events + // every node up and conn up event will generate one additional control event + // therefore multiply the count by two + evC = make(chan *Event, (len(snap.Conns)*2)+(len(snap.Nodes)*2)) + sub = network.Events().Subscribe(evC) + defer sub.Unsubscribe() + + // load the snapshot + // spawn separate thread to avoid deadlock in the event listeners + err = network.Load(snap) + if err != nil { + t.Fatal(err) + } + + // collect connection events up to expected number + ctx, cancel = context.WithTimeout(context.TODO(), time.Second*3) + defer cancel() + + connEventCount = nodeCount + +OUTER_TWO: + for { + select { + case <-ctx.Done(): + t.Fatal(ctx.Err()) + case ev := <-evC: + if ev.Type == EventTypeConn && !ev.Control { + + // fail on any disconnect + if !ev.Conn.Up { + t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other) + } + log.Debug("conn", "on", ev.Conn.One, "other", ev.Conn.Other) + checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other) + checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One) + connEventCount-- + log.Debug("ev", "count", connEventCount) + if connEventCount == 0 { + break OUTER_TWO + } + } + } + } + + // check that we have all expected connections in the network + for _, snapConn := range snap.Conns { + var match bool + for nodid, nodConns := range checkIds { + for _, nodConn := range nodConns { + if snapConn.One == nodid && snapConn.Other == nodConn { + match = true + break + } else if snapConn.Other == nodid && snapConn.One == nodConn { + match = true + break + } + } + } + if !match { + t.Fatalf("network missing conn %v -> %v", snapConn.One, snapConn.Other) + } + } + + // verify that network didn't generate any other additional connection events after the ones we have collected within a reasonable period of time + ctx, cancel = context.WithTimeout(context.TODO(), time.Second) + defer cancel() + select { + case <-ctx.Done(): + case ev := <-evC: + if ev.Type == EventTypeConn { + t.Fatalf("Superfluous conn found %v -> %v", ev.Conn.One, ev.Conn.Other) + } + } + + // This test validates if all connections from the snapshot + // are created in the network. + t.Run("conns after load", func(t *testing.T) { + // Create new network. + n := NewNetwork( + adapters.NewSimAdapter(adapters.Services{ + "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { + return NewNoopService(nil), nil + }, + }), + &NetworkConfig{ + DefaultService: "noopwoop", + }, + ) + defer n.Shutdown() + + // Load the same snapshot. + err := n.Load(snap) + if err != nil { + t.Fatal(err) + } + + // Check every connection from the snapshot + // if it is in the network, too. + for _, c := range snap.Conns { + if n.GetConn(c.One, c.Other) == nil { + t.Errorf("missing connection: %s -> %s", c.One, c.Other) + } + } + }) +} + // TestNetworkSimulation creates a multi-node simulation network with each node // connected in a ring topology, checks that all nodes successfully handshake // with each other and that a snapshot fully represents the desired topology @@ -158,3 +410,78 @@ func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, i } } } + +// \todo: refactor to implement shapshots +// and connect configuration methods once these are moved from +// swarm/network/simulations/connect.go +func BenchmarkMinimalService(b *testing.B) { + b.Run("ring/32", benchmarkMinimalServiceTmp) +} + +func benchmarkMinimalServiceTmp(b *testing.B) { + + // stop timer to discard setup time pollution + args := strings.Split(b.Name(), "/") + nodeCount, err := strconv.ParseInt(args[2], 10, 16) + if err != nil { + b.Fatal(err) + } + + for i := 0; i < b.N; i++ { + // this is a minimal service, whose protocol will close a channel upon run of protocol + // making it possible to bench the time it takes for the service to start and protocol actually to be run + protoCMap := make(map[enode.ID]map[enode.ID]chan struct{}) + adapter := adapters.NewSimAdapter(adapters.Services{ + "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { + protoCMap[ctx.Config.ID] = make(map[enode.ID]chan struct{}) + svc := NewNoopService(protoCMap[ctx.Config.ID]) + return svc, nil + }, + }) + + // create network + network := NewNetwork(adapter, &NetworkConfig{ + DefaultService: "noopwoop", + }) + defer network.Shutdown() + + // create and start nodes + ids := make([]enode.ID, nodeCount) + for i := 0; i < int(nodeCount); i++ { + conf := adapters.RandomNodeConfig() + node, err := network.NewNodeWithConfig(conf) + if err != nil { + b.Fatalf("error creating node: %s", err) + } + if err := network.Start(node.ID()); err != nil { + b.Fatalf("error starting node: %s", err) + } + ids[i] = node.ID() + } + + // ready, set, go + b.ResetTimer() + + // connect nodes in a ring + for i, id := range ids { + peerID := ids[(i+1)%len(ids)] + if err := network.Connect(id, peerID); err != nil { + b.Fatal(err) + } + } + + // wait for all protocols to signal to close down + ctx, cancel := context.WithTimeout(context.TODO(), time.Second) + defer cancel() + for nodid, peers := range protoCMap { + for peerid, peerC := range peers { + log.Debug("getting ", "node", nodid, "peer", peerid) + select { + case <-ctx.Done(): + b.Fatal(ctx.Err()) + case <-peerC: + } + } + } + } +} diff --git a/p2p/simulations/test.go b/p2p/simulations/test.go new file mode 100644 index 0000000000..184e6b8902 --- /dev/null +++ b/p2p/simulations/test.go @@ -0,0 +1,134 @@ +package simulations + +import ( + "testing" + + "github.com/AlayaNetwork/Alaya-Go/p2p" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/rpc" +) + +// NoopService is the service that does not do anything +// but implements node.Service interface. +type NoopService struct { + c map[enode.ID]chan struct{} +} + +func NewNoopService(ackC map[enode.ID]chan struct{}) *NoopService { + return &NoopService{ + c: ackC, + } +} + +func (t *NoopService) Protocols() []p2p.Protocol { + return []p2p.Protocol{ + { + Name: "noop", + Version: 666, + Length: 0, + Run: func(peer *p2p.Peer, rw p2p.MsgReadWriter) error { + if t.c != nil { + t.c[peer.ID()] = make(chan struct{}) + close(t.c[peer.ID()]) + } + rw.ReadMsg() + return nil + }, + NodeInfo: func() interface{} { + return struct{}{} + }, + PeerInfo: func(id enode.ID) interface{} { + return struct{}{} + }, + Attributes: []enr.Entry{}, + }, + } +} + +func (t *NoopService) APIs() []rpc.API { + return []rpc.API{} +} + +func (t *NoopService) Start(server *p2p.Server) error { + return nil +} + +func (t *NoopService) Stop() error { + return nil +} + +func VerifyRing(t *testing.T, net *Network, ids []enode.ID) { + t.Helper() + n := len(ids) + for i := 0; i < n; i++ { + for j := i + 1; j < n; j++ { + c := net.GetConn(ids[i], ids[j]) + if i == j-1 || (i == 0 && j == n-1) { + if c == nil { + t.Errorf("nodes %v and %v are not connected, but they should be", i, j) + } + } else { + if c != nil { + t.Errorf("nodes %v and %v are connected, but they should not be", i, j) + } + } + } + } +} + +func VerifyChain(t *testing.T, net *Network, ids []enode.ID) { + t.Helper() + n := len(ids) + for i := 0; i < n; i++ { + for j := i + 1; j < n; j++ { + c := net.GetConn(ids[i], ids[j]) + if i == j-1 { + if c == nil { + t.Errorf("nodes %v and %v are not connected, but they should be", i, j) + } + } else { + if c != nil { + t.Errorf("nodes %v and %v are connected, but they should not be", i, j) + } + } + } + } +} + +func VerifyFull(t *testing.T, net *Network, ids []enode.ID) { + t.Helper() + n := len(ids) + var connections int + for i, lid := range ids { + for _, rid := range ids[i+1:] { + if net.GetConn(lid, rid) != nil { + connections++ + } + } + } + + want := n * (n - 1) / 2 + if connections != want { + t.Errorf("wrong number of connections, got: %v, want: %v", connections, want) + } +} + +func VerifyStar(t *testing.T, net *Network, ids []enode.ID, centerIndex int) { + t.Helper() + n := len(ids) + for i := 0; i < n; i++ { + for j := i + 1; j < n; j++ { + c := net.GetConn(ids[i], ids[j]) + if i == centerIndex || j == centerIndex { + if c == nil { + t.Errorf("nodes %v and %v are not connected, but they should be", i, j) + } + } else { + if c != nil { + t.Errorf("nodes %v and %v are connected, but they should not be", i, j) + } + } + } + } +} diff --git a/p2p/testing/protocoltester.go b/p2p/testing/protocoltester.go index e3cb36db5f..1c514c4843 100644 --- a/p2p/testing/protocoltester.go +++ b/p2p/testing/protocoltester.go @@ -30,7 +30,6 @@ import ( "io/ioutil" "strings" "sync" - "testing" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/node" @@ -52,7 +51,7 @@ type ProtocolTester struct { // NewProtocolTester constructs a new ProtocolTester // 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 -func NewProtocolTester(t *testing.T, id enode.ID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester { +func NewProtocolTester(id enode.ID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester { services := adapters.Services{ "test": func(ctx *adapters.ServiceContext) (node.Service, error) { return &testNode{run}, nil From d4e23895a430fef3c48774cb3010584e1cb09cc7 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 23 Aug 2021 12:11:29 +0800 Subject: [PATCH 006/299] swarm: fix network/stream data races (#19051) p2p, swarm: fix node up races by granular locking (#18976) p2p/discover: make maximum packet size a constant (#19061) p2p/discover: remove unused function p2p/enode: use localItemKey for local sequence number p2p/simulations: enforce camel case variable names (#19053) p2p/protocols: fix race condition in TestAccountingSimulation (#19228) p2p/protocols: fix data race in TestProtocolHook (#19242) --- cmd/utils/flags.go | 3 +- p2p/discover/table_test.go | 2 + p2p/discover/table_util_test.go | 9 -- p2p/discover/udp.go | 16 +- p2p/enode/nodedb.go | 6 +- p2p/enode/nodedb_test.go | 2 +- p2p/protocols/accounting_simulation_test.go | 7 +- p2p/protocols/protocol_test.go | 39 ++++- p2p/simulations/adapters/exec.go | 9 +- p2p/simulations/adapters/types.go | 3 + p2p/simulations/connect.go | 43 ++++-- p2p/simulations/events.go | 2 +- p2p/simulations/http_test.go | 19 ++- p2p/simulations/mocker_test.go | 21 +-- p2p/simulations/network.go | 161 +++++++++++++------- p2p/simulations/network_test.go | 139 ++++++++++++++++- p2p/testing/protocoltester.go | 11 +- 17 files changed, 369 insertions(+), 123 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 8b1ee3a5fc..204fce829f 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -20,7 +20,6 @@ package utils import ( "crypto/ecdsa" "fmt" - "github.com/AlayaNetwork/Alaya-Go/miner" "io/ioutil" "os" "path/filepath" @@ -28,6 +27,8 @@ import ( "strings" "time" + "github.com/AlayaNetwork/Alaya-Go/miner" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go index 79975a0373..6f83a6a8f0 100644 --- a/p2p/discover/table_test.go +++ b/p2p/discover/table_test.go @@ -625,6 +625,8 @@ func (tn *preminedTestnet) findnode(toid enode.ID, toaddr *net.UDPAddr, target e func (*preminedTestnet) close() {} func (*preminedTestnet) ping(toid enode.ID, toaddr *net.UDPAddr) error { return nil } +var _ = (*preminedTestnet).mine // avoid linter warning about mine being dead code. + // mine generates a testnet struct literal with nodes at // various distances to the given target. func (tn *preminedTestnet) mine(target encPubkey) { diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go index b02aa81c6c..9c59d2e269 100644 --- a/p2p/discover/table_util_test.go +++ b/p2p/discover/table_util_test.go @@ -17,7 +17,6 @@ package discover import ( - "crypto/ecdsa" "encoding/hex" "fmt" "math/rand" @@ -167,11 +166,3 @@ func hexEncPubkey(h string) (ret encPubkey) { copy(ret[:], b) return ret } - -func hexPubkey(h string) *ecdsa.PublicKey { - k, err := decodePubkey(hexEncPubkey(h)) - if err != nil { - panic(err) - } - return k -} diff --git a/p2p/discover/udp.go b/p2p/discover/udp.go index 458fd19ded..8d010593ab 100644 --- a/p2p/discover/udp.go +++ b/p2p/discover/udp.go @@ -58,6 +58,11 @@ const ( ntpFailureThreshold = 32 // Continuous timeouts after which to check NTP ntpWarningCooldown = 10 * time.Minute // Minimum amount of time to pass before repeating NTP warning driftThreshold = 10 * time.Second // Allowed clock drift before warning user + + // Discovery packets are defined to be no larger than 1280 bytes. + // Packets larger than this size will be cut at the end and treated + // as invalid because their hash won't match. + maxPacketSize = 1280 ) // RPC packet types @@ -517,7 +522,7 @@ var ( headSpace = make([]byte, headSize) // Neighbors replies are sent across multiple packets to - // stay below the 1280 byte limit. We compute the maximum number + // stay below the packet size limit. We compute the maximum number // of entries by stuffing a packet until it grows too large. maxNeighbors int ) @@ -532,7 +537,7 @@ func init() { // If this ever happens, it will be caught by the unit tests. panic("cannot encode: " + err.Error()) } - if headSize+size+1 >= 1280 { + if headSize+size+1 >= maxPacketSize { maxNeighbors = n break } @@ -583,10 +588,7 @@ func (t *udp) readLoop(unhandled chan<- ReadPacket) { defer close(unhandled) } - // Discovery packets are defined to be no larger than 1280 bytes. - // Packets larger than this size will be cut at the end and treated - // as invalid because their hash won't match. - buf := make([]byte, 1280) + buf := make([]byte, maxPacketSize) for { nbytes, from, err := t.conn.ReadFromUDP(buf) if netutil.IsTemporaryError(err) { @@ -745,7 +747,7 @@ func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID enode.ID, mac []by t.tab.mutex.Unlock() // Send neighbors in chunks with at most maxNeighbors per packet - // to stay below the 1280 byte limit. + // to stay below the packet size limit. p := neighbors{ Expiration: uint64(time.Now().Add(expiration).Unix()), Rest: cRest, diff --git a/p2p/enode/nodedb.go b/p2p/enode/nodedb.go index 7781ce0b2f..00422d33ba 100644 --- a/p2p/enode/nodedb.go +++ b/p2p/enode/nodedb.go @@ -61,7 +61,7 @@ const ( const ( dbNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped. dbCleanupCycle = time.Hour // Time period for running the expiration task. - dbVersion = 8 + dbVersion = 9 ) var zeroIP = make(net.IP, 16) @@ -384,12 +384,12 @@ func (db *DB) UpdateFindFails(id ID, ip net.IP, fails int) error { // LocalSeq retrieves the local record sequence counter. func (db *DB) localSeq(id ID) uint64 { - return db.fetchUint64(nodeItemKey(id, zeroIP, dbLocalSeq)) + return db.fetchUint64(localItemKey(id, dbLocalSeq)) } // storeLocalSeq stores the local record sequence counter. func (db *DB) storeLocalSeq(id ID, n uint64) { - db.storeUint64(nodeItemKey(id, zeroIP, dbLocalSeq), n) + db.storeUint64(localItemKey(id, dbLocalSeq), n) } // QuerySeeds retrieves random nodes to be used as potential seed nodes diff --git a/p2p/enode/nodedb_test.go b/p2p/enode/nodedb_test.go index 5f81376a40..bcf628b825 100644 --- a/p2p/enode/nodedb_test.go +++ b/p2p/enode/nodedb_test.go @@ -71,7 +71,7 @@ func TestDBNodeItemKey(t *testing.T) { if id != keytestID { t.Errorf("splitNodeItemKey returned wrong ID: %v", id) } - if !bytes.Equal(ip, wantIP) { + if !ip.Equal(wantIP) { t.Errorf("splitNodeItemKey returned wrong IP: %v", ip) } if field != wantField { diff --git a/p2p/protocols/accounting_simulation_test.go b/p2p/protocols/accounting_simulation_test.go index 76ae3cd15f..b7f54f06f2 100644 --- a/p2p/protocols/accounting_simulation_test.go +++ b/p2p/protocols/accounting_simulation_test.go @@ -159,8 +159,9 @@ func TestAccountingSimulation(t *testing.T) { // (n entries in the array will not be filled - // the balance of a node with itself) type matrix struct { - n int //number of nodes - m []int64 //array of balances + n int //number of nodes + m []int64 //array of balances + lock sync.Mutex } // create a new matrix @@ -177,7 +178,9 @@ func (m *matrix) add(i, j int, v int64) error { // i * number of nodes + remote node mi := i*m.n + j // register that balance + m.lock.Lock() m.m[mi] += v + m.lock.Unlock() return nil } diff --git a/p2p/protocols/protocol_test.go b/p2p/protocols/protocol_test.go index 9bcd2208ca..d5d91e48cf 100644 --- a/p2p/protocols/protocol_test.go +++ b/p2p/protocols/protocol_test.go @@ -21,6 +21,7 @@ import ( "context" "errors" "fmt" + "sync" "testing" "time" @@ -172,8 +173,11 @@ func protoHandshakeExchange(id enode.ID, proto *protoHandshake) []p2ptest.Exchan } func runProtoHandshake(t *testing.T, proto *protoHandshake, errs ...error) { + t.Helper() pp := p2ptest.NewTestPeerPool() s := protocolTester(pp) + defer s.Stop() + // TODO: make this more than one handshake node := s.Nodes[0] if err := s.TestExchanges(protoHandshakeExchange(node.ID(), proto)...); err != nil { @@ -195,6 +199,7 @@ type dummyHook struct { send bool err error waitC chan struct{} + mu sync.Mutex } type dummyMsg struct { @@ -202,6 +207,9 @@ type dummyMsg struct { } func (d *dummyHook) Send(peer *Peer, size uint32, msg interface{}) error { + d.mu.Lock() + defer d.mu.Unlock() + d.peer = peer d.size = size d.msg = msg @@ -210,6 +218,9 @@ func (d *dummyHook) Send(peer *Peer, size uint32, msg interface{}) error { } func (d *dummyHook) Receive(peer *Peer, size uint32, msg interface{}) error { + d.mu.Lock() + defer d.mu.Unlock() + d.peer = peer d.size = size d.msg = msg @@ -263,6 +274,7 @@ func TestProtocolHook(t *testing.T) { if err != nil { t.Fatal(err) } + testHook.mu.Lock() if testHook.msg == nil || testHook.msg.(*dummyMsg).Content != "handshake" { t.Fatal("Expected msg to be set, but it is not") } @@ -278,6 +290,7 @@ func TestProtocolHook(t *testing.T) { if testHook.size != 11 { //11 is the length of the encoded message t.Fatalf("Expected size to be %d, but it is %d ", 1, testHook.size) } + testHook.mu.Unlock() err = tester.TestExchanges(p2ptest.Exchange{ Triggers: []p2ptest.Trigger{ @@ -294,6 +307,8 @@ func TestProtocolHook(t *testing.T) { if err != nil { t.Fatal(err) } + + testHook.mu.Lock() if testHook.msg == nil || testHook.msg.(*dummyMsg).Content != "response" { t.Fatal("Expected msg to be set, but it is not") } @@ -306,6 +321,7 @@ func TestProtocolHook(t *testing.T) { if testHook.size != 10 { //11 is the length of the encoded message t.Fatalf("Expected size to be %d, but it is %d ", 1, testHook.size) } + testHook.mu.Unlock() testHook.err = fmt.Errorf("dummy error") err = tester.TestExchanges(p2ptest.Exchange{ @@ -325,7 +341,6 @@ func TestProtocolHook(t *testing.T) { if err != nil { t.Fatalf("Expected a specific disconnect error, but got different one: %v", err) } - } //We need to test that if the hook is not defined, then message infrastructure @@ -342,16 +357,19 @@ func TestNoHook(t *testing.T) { ctx := context.TODO() msg := &perBytesMsgSenderPays{Content: "testBalance"} //send a message - err := peer.Send(ctx, msg) - if err != nil { + + if err := peer.Send(ctx, msg); err != nil { t.Fatal(err) } //simulate receiving a message rw.msg = msg - peer.handleIncoming(func(ctx context.Context, msg interface{}) error { + handler := func(ctx context.Context, msg interface{}) error { return nil - }) - //all should just work and not result in any error + } + + if err := peer.handleIncoming(handler); err != nil { + t.Fatal(err) + } } func TestProtoHandshakeVersionMismatch(t *testing.T) { @@ -391,8 +409,11 @@ func moduleHandshakeExchange(id enode.ID, resp uint) []p2ptest.Exchange { } func runModuleHandshake(t *testing.T, resp uint, errs ...error) { + t.Helper() pp := p2ptest.NewTestPeerPool() s := protocolTester(pp) + defer s.Stop() + node := s.Nodes[0] if err := s.TestExchanges(protoHandshakeExchange(node.ID(), &protoHandshake{42, "420"})...); err != nil { t.Fatal(err) @@ -471,8 +492,10 @@ func testMultiPeerSetup(a, b enode.ID) []p2ptest.Exchange { } func runMultiplePeers(t *testing.T, peer int, errs ...error) { + t.Helper() pp := p2ptest.NewTestPeerPool() s := protocolTester(pp) + defer s.Stop() if err := s.TestExchanges(testMultiPeerSetup(s.Nodes[0].ID(), s.Nodes[1].ID())...); err != nil { t.Fatal(err) @@ -542,14 +565,14 @@ WAIT: } } -func XTestMultiplePeersDropSelf(t *testing.T) { +func TestMultiplePeersDropSelf(t *testing.T) { runMultiplePeers(t, 0, fmt.Errorf("subprotocol error"), fmt.Errorf("Message handler error: (msg code 3): dropped"), ) } -func XTestMultiplePeersDropOther(t *testing.T) { +func TestMultiplePeersDropOther(t *testing.T) { runMultiplePeers(t, 1, fmt.Errorf("Message handler error: (msg code 3): dropped"), fmt.Errorf("subprotocol error"), diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index cdd0524a00..19158340db 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -48,7 +48,7 @@ import ( func init() { // Register a reexec function to start a simulation node when the current binary is - // executed as "p2p-node" (rather than whataver the main() function would normally do). + // executed as "p2p-node" (rather than whatever the main() function would normally do). reexec.Register("p2p-node", execP2PNode) } @@ -99,8 +99,11 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { Stack: node.DefaultConfig, Node: config, } - conf.Stack.DataDir = filepath.Join(dir, "data") - // these parameters are crucial for execadapter node to run correctly + if config.DataDir != "" { + conf.Stack.DataDir = config.DataDir + } else { + conf.Stack.DataDir = filepath.Join(dir, "data") + } conf.Stack.WSHost = "127.0.0.1" conf.Stack.WSPort = 0 conf.Stack.WSOrigins = []string{"*"} diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go index 6311a444d8..05f4b3baf8 100644 --- a/p2p/simulations/adapters/types.go +++ b/p2p/simulations/adapters/types.go @@ -94,6 +94,9 @@ type NodeConfig struct { // Name is a human friendly name for the node like "node01" Name string + // Use an existing database instead of a temporary one if non-empty + DataDir string + // Services are the names of the services which should be run when // starting the node (for SimNodes it should be the names of services // contained in SimAdapter.services, for other nodes it should be diff --git a/p2p/simulations/connect.go b/p2p/simulations/connect.go index f3ed1e6b21..88094e5463 100644 --- a/p2p/simulations/connect.go +++ b/p2p/simulations/connect.go @@ -32,6 +32,9 @@ var ( // It is useful when constructing a chain network topology // when Network adds and removes nodes dynamically. func (net *Network) ConnectToLastNode(id enode.ID) (err error) { + net.lock.Lock() + defer net.lock.Unlock() + ids := net.getUpNodeIDs() l := len(ids) if l < 2 { @@ -41,29 +44,35 @@ func (net *Network) ConnectToLastNode(id enode.ID) (err error) { if last == id { last = ids[l-2] } - return net.connect(last, id) + return net.connectNotConnected(last, id) } // ConnectToRandomNode connects the node with provided NodeID // to a random node that is up. func (net *Network) ConnectToRandomNode(id enode.ID) (err error) { - selected := net.GetRandomUpNode(id) + net.lock.Lock() + defer net.lock.Unlock() + + selected := net.getRandomUpNode(id) if selected == nil { return ErrNodeNotFound } - return net.connect(selected.ID(), id) + return net.connectNotConnected(selected.ID(), id) } // ConnectNodesFull connects all nodes one to another. // It provides a complete connectivity in the network // which should be rarely needed. func (net *Network) ConnectNodesFull(ids []enode.ID) (err error) { + net.lock.Lock() + defer net.lock.Unlock() + if ids == nil { ids = net.getUpNodeIDs() } for i, lid := range ids { for _, rid := range ids[i+1:] { - if err = net.connect(lid, rid); err != nil { + if err = net.connectNotConnected(lid, rid); err != nil { return err } } @@ -74,12 +83,19 @@ func (net *Network) ConnectNodesFull(ids []enode.ID) (err error) { // ConnectNodesChain connects all nodes in a chain topology. // If ids argument is nil, all nodes that are up will be connected. func (net *Network) ConnectNodesChain(ids []enode.ID) (err error) { + net.lock.Lock() + defer net.lock.Unlock() + + return net.connectNodesChain(ids) +} + +func (net *Network) connectNodesChain(ids []enode.ID) (err error) { if ids == nil { ids = net.getUpNodeIDs() } l := len(ids) for i := 0; i < l-1; i++ { - if err := net.connect(ids[i], ids[i+1]); err != nil { + if err := net.connectNotConnected(ids[i], ids[i+1]); err != nil { return err } } @@ -89,6 +105,9 @@ func (net *Network) ConnectNodesChain(ids []enode.ID) (err error) { // ConnectNodesRing connects all nodes in a ring topology. // If ids argument is nil, all nodes that are up will be connected. func (net *Network) ConnectNodesRing(ids []enode.ID) (err error) { + net.lock.Lock() + defer net.lock.Unlock() + if ids == nil { ids = net.getUpNodeIDs() } @@ -96,15 +115,18 @@ func (net *Network) ConnectNodesRing(ids []enode.ID) (err error) { if l < 2 { return nil } - if err := net.ConnectNodesChain(ids); err != nil { + if err := net.connectNodesChain(ids); err != nil { return err } - return net.connect(ids[l-1], ids[0]) + return net.connectNotConnected(ids[l-1], ids[0]) } // ConnectNodesStar connects all nodes into a star topology // If ids argument is nil, all nodes that are up will be connected. func (net *Network) ConnectNodesStar(ids []enode.ID, center enode.ID) (err error) { + net.lock.Lock() + defer net.lock.Unlock() + if ids == nil { ids = net.getUpNodeIDs() } @@ -112,16 +134,15 @@ func (net *Network) ConnectNodesStar(ids []enode.ID, center enode.ID) (err error if center == id { continue } - if err := net.connect(center, id); err != nil { + if err := net.connectNotConnected(center, id); err != nil { return err } } return nil } -// connect connects two nodes but ignores already connected error. -func (net *Network) connect(oneID, otherID enode.ID) error { - return ignoreAlreadyConnectedErr(net.Connect(oneID, otherID)) +func (net *Network) connectNotConnected(oneID, otherID enode.ID) error { + return ignoreAlreadyConnectedErr(net.connect(oneID, otherID)) } func ignoreAlreadyConnectedErr(err error) error { diff --git a/p2p/simulations/events.go b/p2p/simulations/events.go index 9b2a990e07..984c2e088f 100644 --- a/p2p/simulations/events.go +++ b/p2p/simulations/events.go @@ -100,7 +100,7 @@ func ControlEvent(v interface{}) *Event { func (e *Event) String() string { switch e.Type { case EventTypeNode: - return fmt.Sprintf(" id: %s up: %t", e.Node.ID().TerminalString(), e.Node.Up) + return fmt.Sprintf(" id: %s up: %t", e.Node.ID().TerminalString(), e.Node.Up()) case EventTypeConn: return fmt.Sprintf(" nodes: %s->%s up: %t", e.Conn.One.TerminalString(), e.Conn.Other.TerminalString(), e.Conn.Up) case EventTypeMsg: diff --git a/p2p/simulations/http_test.go b/p2p/simulations/http_test.go index f9c3bdc585..b71445f23e 100644 --- a/p2p/simulations/http_test.go +++ b/p2p/simulations/http_test.go @@ -297,6 +297,7 @@ var testServices = adapters.Services{ } func testHTTPServer(t *testing.T) (*Network, *httptest.Server) { + t.Helper() adapter := adapters.NewSimAdapter(testServices) network := NewNetwork(adapter, &NetworkConfig{ DefaultService: "test", @@ -423,14 +424,15 @@ type expectEvents struct { } func (t *expectEvents) nodeEvent(id string, up bool) *Event { + node := Node{ + Config: &adapters.NodeConfig{ + ID: enode.HexID(id), + }, + up: up, + } return &Event{ Type: EventTypeNode, - Node: &Node{ - Config: &adapters.NodeConfig{ - ID: enode.HexID(id), - }, - Up: up, - }, + Node: &node, } } @@ -482,6 +484,7 @@ loop: } func (t *expectEvents) expect(events ...*Event) { + t.Helper() timeout := time.After(10 * time.Second) i := 0 for { @@ -503,8 +506,8 @@ func (t *expectEvents) expect(events ...*Event) { if event.Node.ID() != expected.Node.ID() { t.Fatalf("expected node event %d to have id %q, got %q", i, expected.Node.ID().TerminalString(), event.Node.ID().TerminalString()) } - if event.Node.Up != expected.Node.Up { - t.Fatalf("expected node event %d to have up=%t, got up=%t", i, expected.Node.Up, event.Node.Up) + if event.Node.Up() != expected.Node.Up() { + t.Fatalf("expected node event %d to have up=%t, got up=%t", i, expected.Node.Up(), event.Node.Up()) } case EventTypeConn: diff --git a/p2p/simulations/mocker_test.go b/p2p/simulations/mocker_test.go index b41dc309b3..aa0aa6e4b6 100644 --- a/p2p/simulations/mocker_test.go +++ b/p2p/simulations/mocker_test.go @@ -90,15 +90,12 @@ func TestMocker(t *testing.T) { for { select { case event := <-events: - //if the event is a node Up event only - if event.Node != nil && event.Node.Up { + if isNodeUp(event) { //add the correspondent node ID to the map nodemap[event.Node.Config.ID] = true //this means all nodes got a nodeUp event, so we can continue the test if len(nodemap) == nodeCount { nodesComplete = true - //wait for 3s as the mocker will need time to connect the nodes - //time.Sleep( 3 *time.Second) } } else if event.Conn != nil && nodesComplete { connCount += 1 @@ -135,13 +132,13 @@ func TestMocker(t *testing.T) { wg.Wait() //check there are nodeCount number of nodes in the network - nodes_info, err := client.GetNodes() + nodesInfo, err := client.GetNodes() if err != nil { t.Fatalf("Could not get nodes list: %s", err) } - if len(nodes_info) != nodeCount { - t.Fatalf("Expected %d number of nodes, got: %d", nodeCount, len(nodes_info)) + if len(nodesInfo) != nodeCount { + t.Fatalf("Expected %d number of nodes, got: %d", nodeCount, len(nodesInfo)) } //stop the mocker @@ -160,12 +157,16 @@ func TestMocker(t *testing.T) { } //now the number of nodes in the network should be zero - nodes_info, err = client.GetNodes() + nodesInfo, err = client.GetNodes() if err != nil { t.Fatalf("Could not get nodes list: %s", err) } - if len(nodes_info) != 0 { - t.Fatalf("Expected empty list of nodes, got: %d", len(nodes_info)) + if len(nodesInfo) != 0 { + t.Fatalf("Expected empty list of nodes, got: %d", len(nodesInfo)) } } + +func isNodeUp(event *Event) bool { + return event.Node != nil && event.Node.Up() +} diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go index 3f17ab9259..08fc2956f5 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -137,7 +137,7 @@ func (net *Network) Config() *NetworkConfig { // StartAll starts all nodes in the network func (net *Network) StartAll() error { for _, node := range net.Nodes { - if node.Up { + if node.Up() { continue } if err := net.Start(node.ID()); err != nil { @@ -150,7 +150,7 @@ func (net *Network) StartAll() error { // StopAll stops all nodes in the network func (net *Network) StopAll() error { for _, node := range net.Nodes { - if !node.Up { + if !node.Up() { continue } if err := net.Stop(node.ID()); err != nil { @@ -169,27 +169,23 @@ func (net *Network) Start(id enode.ID) error { // snapshots func (net *Network) startWithSnapshots(id enode.ID, snapshots map[string][]byte) error { net.lock.Lock() + defer net.lock.Unlock() node := net.getNode(id) if node == nil { - net.lock.Unlock() return fmt.Errorf("node %v does not exist", id) } - if node.Up { - net.lock.Unlock() + if node.Up() { return fmt.Errorf("node %v already up", id) } log.Trace("Starting node", "id", id, "adapter", net.nodeAdapter.Name()) if err := node.Start(snapshots); err != nil { - net.lock.Unlock() log.Warn("Node startup failed", "id", id, "err", err) return err } - node.Up = true + node.SetUp(true) log.Info("Started node", "id", id) ev := NewEvent(node) - net.lock.Unlock() - net.events.Send(ev) // subscribe to peer events @@ -220,7 +216,7 @@ func (net *Network) watchPeerEvents(id enode.ID, events chan *p2p.PeerEvent, sub if node == nil { return } - node.Up = false + node.SetUp(false) ev := NewEvent(node) net.events.Send(ev) }() @@ -258,30 +254,42 @@ func (net *Network) watchPeerEvents(id enode.ID, events chan *p2p.PeerEvent, sub // Stop stops the node with the given ID func (net *Network) Stop(id enode.ID) error { - net.lock.Lock() - node := net.getNode(id) - if node == nil { - net.lock.Unlock() - return fmt.Errorf("node %v does not exist", id) - } - if !node.Up { - net.lock.Unlock() - return fmt.Errorf("node %v already down", id) + // IMPORTANT: node.Stop() must NOT be called under net.lock as + // node.Reachable() closure has a reference to the network and + // calls net.InitConn() what also locks the network. => DEADLOCK + // That holds until the following ticket is not resolved: + + var err error + + node, err := func() (*Node, error) { + net.lock.Lock() + defer net.lock.Unlock() + + node := net.getNode(id) + if node == nil { + return nil, fmt.Errorf("node %v does not exist", id) + } + if !node.Up() { + return nil, fmt.Errorf("node %v already down", id) + } + node.SetUp(false) + return node, nil + }() + if err != nil { + return err } - node.Up = false - net.lock.Unlock() - err := node.Stop() + err = node.Stop() // must be called without net.lock + + net.lock.Lock() + defer net.lock.Unlock() + if err != nil { - net.lock.Lock() - node.Up = true - net.lock.Unlock() + node.SetUp(true) return err } log.Info("Stopped node", "id", id, "err", err) - net.lock.Lock() ev := ControlEvent(node) - net.lock.Unlock() net.events.Send(ev) return nil } @@ -289,8 +297,14 @@ func (net *Network) Stop(id enode.ID) error { // Connect connects two nodes together by calling the "admin_addPeer" RPC // method on the "one" node so that it connects to the "other" node func (net *Network) Connect(oneID, otherID enode.ID) error { + net.lock.Lock() + defer net.lock.Unlock() + return net.connect(oneID, otherID) +} + +func (net *Network) connect(oneID, otherID enode.ID) error { log.Debug("Connecting nodes with addPeer", "id", oneID, "other", otherID) - conn, err := net.InitConn(oneID, otherID) + conn, err := net.initConn(oneID, otherID) if err != nil { return err } @@ -388,6 +402,14 @@ func (net *Network) GetNode(id enode.ID) *Node { return net.getNode(id) } +func (net *Network) getNode(id enode.ID) *Node { + i, found := net.nodeMap[id] + if !found { + return nil + } + return net.Nodes[i] +} + // GetNode gets the node with the given name, returning nil if the node does // not exist func (net *Network) GetNodeByName(name string) *Node { @@ -410,28 +432,29 @@ func (net *Network) GetNodes() (nodes []*Node) { net.lock.RLock() defer net.lock.RUnlock() - nodes = append(nodes, net.Nodes...) - return nodes + return net.getNodes() } -func (net *Network) getNode(id enode.ID) *Node { - i, found := net.nodeMap[id] - if !found { - return nil - } - return net.Nodes[i] +func (net *Network) getNodes() (nodes []*Node) { + nodes = append(nodes, net.Nodes...) + return nodes } // GetRandomUpNode returns a random node on the network, which is running. func (net *Network) GetRandomUpNode(excludeIDs ...enode.ID) *Node { net.lock.RLock() defer net.lock.RUnlock() + return net.getRandomUpNode(excludeIDs...) +} + +// GetRandomUpNode returns a random node on the network, which is running. +func (net *Network) getRandomUpNode(excludeIDs ...enode.ID) *Node { return net.getRandomNode(net.getUpNodeIDs(), excludeIDs) } func (net *Network) getUpNodeIDs() (ids []enode.ID) { for _, node := range net.Nodes { - if node.Up { + if node.Up() { ids = append(ids, node.ID()) } } @@ -446,8 +469,8 @@ func (net *Network) GetRandomDownNode(excludeIDs ...enode.ID) *Node { } func (net *Network) getDownNodeIDs() (ids []enode.ID) { - for _, node := range net.GetNodes() { - if !node.Up { + for _, node := range net.getNodes() { + if !node.Up() { ids = append(ids, node.ID()) } } @@ -461,7 +484,7 @@ func (net *Network) getRandomNode(ids []enode.ID, excludeIDs []enode.ID) *Node { if l == 0 { return nil } - return net.GetNode(filtered[rand.Intn(l)]) + return net.getNode(filtered[rand.Intn(l)]) } func filterIDs(ids []enode.ID, excludeIDs []enode.ID) []enode.ID { @@ -539,6 +562,10 @@ func (net *Network) getConn(oneID, otherID enode.ID) *Conn { func (net *Network) InitConn(oneID, otherID enode.ID) (*Conn, error) { net.lock.Lock() defer net.lock.Unlock() + return net.initConn(oneID, otherID) +} + +func (net *Network) initConn(oneID, otherID enode.ID) (*Conn, error) { if oneID == otherID { return nil, fmt.Errorf("refusing to connect to self %v", oneID) } @@ -602,8 +629,21 @@ type Node struct { // Config if the config used to created the node Config *adapters.NodeConfig `json:"config"` - // Up tracks whether or not the node is running - Up bool `json:"up"` + // up tracks whether or not the node is running + up bool + upMu sync.RWMutex +} + +func (n *Node) Up() bool { + n.upMu.RLock() + defer n.upMu.RUnlock() + return n.up +} + +func (n *Node) SetUp(up bool) { + n.upMu.Lock() + defer n.upMu.Unlock() + n.up = up } // ID returns the ID of the node @@ -637,10 +677,29 @@ func (n *Node) MarshalJSON() ([]byte, error) { }{ Info: n.NodeInfo(), Config: n.Config, - Up: n.Up, + Up: n.Up(), }) } +// UnmarshalJSON implements json.Unmarshaler interface so that we don't lose +// Node.up status. IMPORTANT: The implementation is incomplete; we lose +// p2p.NodeInfo. +func (n *Node) UnmarshalJSON(raw []byte) error { + // TODO: How should we turn back NodeInfo into n.Node? + // Ticket: https://github.com/ethersphere/go-ethereum/issues/1177 + node := struct { + Config *adapters.NodeConfig `json:"config,omitempty"` + Up bool `json:"up"` + }{} + if err := json.Unmarshal(raw, &node); err != nil { + return err + } + + n.SetUp(node.Up) + n.Config = node.Config + return nil +} + // Conn represents a connection between two nodes in the network type Conn struct { // One is the node which initiated the connection @@ -660,10 +719,10 @@ type Conn struct { // nodesUp returns whether both nodes are currently up func (c *Conn) nodesUp() error { - if !c.one.Up { + if !c.one.Up() { return fmt.Errorf("one %v is not up", c.One) } - if !c.other.Up { + if !c.other.Up() { return fmt.Errorf("other %v is not up", c.Other) } return nil @@ -732,11 +791,10 @@ func (net *Network) snapshot(addServices []string, removeServices []string) (*Sn defer net.lock.Unlock() snap := &Snapshot{ Nodes: make([]NodeSnapshot, len(net.Nodes)), - Conns: make([]Conn, len(net.Conns)), } for i, node := range net.Nodes { snap.Nodes[i] = NodeSnapshot{Node: *node} - if !node.Up { + if !node.Up() { continue } snapshots, err := node.Snapshots() @@ -782,7 +840,8 @@ func (net *Network) snapshot(addServices []string, removeServices []string) (*Sn return snap, nil } -var snapshotLoadTimeout = 120 * time.Second +// longrunning tests may need a longer timeout +var snapshotLoadTimeout = 900 * time.Second // Load loads a network snapshot func (net *Network) Load(snap *Snapshot) error { @@ -791,7 +850,7 @@ func (net *Network) Load(snap *Snapshot) error { if _, err := net.NewNodeWithConfig(n.Node.Config); err != nil { return err } - if !n.Node.Up { + if !n.Node.Up() { continue } if err := net.startWithSnapshots(n.Node.Config.ID, n.Snapshots); err != nil { @@ -863,7 +922,7 @@ func (net *Network) Load(snap *Snapshot) error { // Start connecting. for _, conn := range snap.Conns { - if !net.GetNode(conn.One).Up || !net.GetNode(conn.Other).Up { + if !net.GetNode(conn.One).Up() || !net.GetNode(conn.Other).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 continue @@ -917,7 +976,7 @@ func (net *Network) executeControlEvent(event *Event) { } func (net *Network) executeNodeEvent(e *Event) error { - if !e.Node.Up { + if !e.Node.Up() { return net.Stop(e.Node.ID()) } diff --git a/p2p/simulations/network_test.go b/p2p/simulations/network_test.go index 73ebd486fd..229e027abb 100644 --- a/p2p/simulations/network_test.go +++ b/p2p/simulations/network_test.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "reflect" "strconv" "strings" "testing" @@ -192,7 +193,7 @@ OUTER: connEventCount = nodeCount -OUTER_TWO: +OuterTwo: for { select { case <-ctx.Done(): @@ -210,7 +211,7 @@ OUTER_TWO: connEventCount-- log.Debug("ev", "count", connEventCount) if connEventCount == 0 { - break OUTER_TWO + break OuterTwo } } } @@ -485,3 +486,137 @@ func benchmarkMinimalServiceTmp(b *testing.B) { } } } + +func TestNode_UnmarshalJSON(t *testing.T) { + t.Run( + "test unmarshal of Node up field", + func(t *testing.T) { + runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONUpField()) + }, + ) + t.Run( + "test unmarshal of Node Config field", + func(t *testing.T) { + runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONConfigField()) + }, + ) +} + +func runNodeUnmarshalJSON(t *testing.T, tests []nodeUnmarshalTestCase) { + t.Helper() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got Node + if err := got.UnmarshalJSON([]byte(tt.marshaled)); err != nil { + expectErrorMessageToContain(t, err, tt.wantErr) + } + expectNodeEquality(t, got, tt.want) + }) + } +} + +type nodeUnmarshalTestCase struct { + name string + marshaled string + want Node + wantErr string +} + +func expectErrorMessageToContain(t *testing.T, got error, want string) { + t.Helper() + if got == nil && want == "" { + return + } + + if got == nil && want != "" { + t.Errorf("error was expected, got: nil, want: %v", want) + return + } + + if !strings.Contains(got.Error(), want) { + t.Errorf( + "unexpected error message, got %v, want: %v", + want, + got, + ) + } +} + +func expectNodeEquality(t *testing.T, got Node, want Node) { + t.Helper() + if !reflect.DeepEqual(got, want) { + t.Errorf("Node.UnmarshalJSON() = %v, want %v", got, want) + } +} + +func casesNodeUnmarshalJSONUpField() []nodeUnmarshalTestCase { + return []nodeUnmarshalTestCase{ + { + name: "empty json", + marshaled: "{}", + want: Node{ + up: false, + }, + }, + { + name: "a stopped node", + marshaled: "{\"up\": false}", + want: Node{ + up: false, + }, + }, + { + name: "a running node", + marshaled: "{\"up\": true}", + want: Node{ + up: true, + }, + }, + { + name: "invalid JSON value on valid key", + marshaled: "{\"up\": foo}", + wantErr: "invalid character", + }, + { + name: "invalid JSON key and value", + marshaled: "{foo: bar}", + wantErr: "invalid character", + }, + { + name: "bool value expected but got something else (string)", + marshaled: "{\"up\": \"true\"}", + wantErr: "cannot unmarshal string into Go struct", + }, + } +} + +func casesNodeUnmarshalJSONConfigField() []nodeUnmarshalTestCase { + // Don't do a big fuss around testing, as adapters.NodeConfig should + // handle it's own serialization. Just do a sanity check. + return []nodeUnmarshalTestCase{ + { + name: "Config field is omitted", + marshaled: "{}", + want: Node{ + Config: nil, + }, + }, + { + name: "Config field is nil", + marshaled: "{\"config\": nil}", + want: Node{ + Config: nil, + }, + }, + { + name: "a non default Config field", + marshaled: "{\"config\":{\"name\":\"node_ecdd0\",\"port\":44665}}", + want: Node{ + Config: &adapters.NodeConfig{ + Name: "node_ecdd0", + Port: 44665, + }, + }, + }, + } +} diff --git a/p2p/testing/protocoltester.go b/p2p/testing/protocoltester.go index 1c514c4843..60ba2945b2 100644 --- a/p2p/testing/protocoltester.go +++ b/p2p/testing/protocoltester.go @@ -51,7 +51,7 @@ type ProtocolTester struct { // NewProtocolTester constructs a new ProtocolTester // 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 -func NewProtocolTester(id enode.ID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester { +func NewProtocolTester(id enode.ID, nodeCount int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester { services := adapters.Services{ "test": func(ctx *adapters.ServiceContext) (node.Service, error) { return &testNode{run}, nil @@ -74,9 +74,9 @@ func NewProtocolTester(id enode.ID, n int, run func(*p2p.Peer, p2p.MsgReadWriter } node := net.GetNode(id).Node.(*adapters.SimNode) - peers := make([]*adapters.NodeConfig, n) - nodes := make([]*enode.Node, n) - for i := 0; i < n; i++ { + peers := make([]*adapters.NodeConfig, nodeCount) + nodes := make([]*enode.Node, nodeCount) + for i := 0; i < nodeCount; i++ { peers[i] = adapters.RandomNodeConfig() peers[i].Services = []string{"mock"} nodes[i] = peers[i].Node() @@ -100,9 +100,8 @@ func NewProtocolTester(id enode.ID, n int, run func(*p2p.Peer, p2p.MsgReadWriter } // Stop stops the p2p server -func (t *ProtocolTester) Stop() error { +func (t *ProtocolTester) Stop() { t.Server.Stop() - return nil } // Connect brings up the remote peer node and connects it using the From 967357b3fa34091841ffe9cd56f888e62dab404d Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Thu, 26 Aug 2021 11:23:35 +0800 Subject: [PATCH 007/299] swarm, p2p: Prerequities for ENR replacing handshake (#19275) cmd/swarm, p2p, swarm: Enable ENR in binary/execadapter (#19309) swarm/network: Use different privatekey for bzz overlay in sim (#19313) p2p/discover: bump failure counter only if no nodes were provided (#19362) p2p/discover: split out discv4 code #19513 p2p/discover: fix nil-dereference due to race #19562 --- go.mod | 2 +- internal/testlog/testlog.go | 46 +++ node/crypto_handler.go | 3 +- p2p/discover/common.go | 61 +++ p2p/discover/table.go | 209 +++------- p2p/discover/table_test.go | 310 +-------------- p2p/discover/table_util_test.go | 49 ++- p2p/discover/{udp.go => v4_udp.go} | 398 ++++++++++++------- p2p/discover/v4_udp_lookup_test.go | 220 ++++++++++ p2p/discover/{udp_test.go => v4_udp_test.go} | 177 +++++---- p2p/discv5/net.go | 3 +- p2p/message.go | 6 +- p2p/peer.go | 16 +- p2p/protocols/protocol.go | 5 +- p2p/protocols/protocol_test.go | 21 +- p2p/rlpx.go | 3 +- p2p/simulations/adapters/exec.go | 11 + p2p/simulations/adapters/inproc.go | 14 +- p2p/simulations/adapters/types.go | 49 ++- p2p/testing/peerpool.go | 2 +- p2p/testing/protocoltester.go | 30 +- x/gov/gov_db_test.go | 4 +- 22 files changed, 880 insertions(+), 759 deletions(-) create mode 100644 internal/testlog/testlog.go create mode 100644 p2p/discover/common.go rename p2p/discover/{udp.go => v4_udp.go} (67%) create mode 100644 p2p/discover/v4_udp_lookup_test.go rename p2p/discover/{udp_test.go => v4_udp_test.go} (82%) diff --git a/go.mod b/go.mod index 6d4ac9fd5f..2e51626dd3 100644 --- a/go.mod +++ b/go.mod @@ -62,7 +62,7 @@ require ( github.com/tealeg/xlsx v1.0.5 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/net v0.0.0-20200602114024-627f9648deb9 - golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c + golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 diff --git a/internal/testlog/testlog.go b/internal/testlog/testlog.go new file mode 100644 index 0000000000..282ccb064b --- /dev/null +++ b/internal/testlog/testlog.go @@ -0,0 +1,46 @@ +// Copyright 2017 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 . + +// Package testlog provides a log handler for unit tests. +package testlog + +import ( + "testing" + + "github.com/AlayaNetwork/Alaya-Go/log" +) + +// Logger returns a logger which logs to the unit test log of t. +func Logger(t *testing.T, level log.Lvl) log.Logger { + l := log.New() + l.SetHandler(Handler(t, level)) + return l +} + +// Handler returns a log handler which logs to the unit test log of t. +func Handler(t *testing.T, level log.Lvl) log.Handler { + return log.LvlFilterHandler(level, &handler{t, log.TerminalFormat(false)}) +} + +type handler struct { + t *testing.T + fmt log.Format +} + +func (h *handler) Log(r *log.Record) error { + h.t.Logf("%s", h.fmt.Format(r)) + return nil +} diff --git a/node/crypto_handler.go b/node/crypto_handler.go index c99b6f57fa..927b8148f3 100755 --- a/node/crypto_handler.go +++ b/node/crypto_handler.go @@ -19,9 +19,10 @@ package node import ( "crypto/ecdsa" "encoding/hex" - "golang.org/x/crypto/sha3" "sync" + "golang.org/x/crypto/sha3" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/rlp" diff --git a/p2p/discover/common.go b/p2p/discover/common.go new file mode 100644 index 0000000000..b70e31b2ef --- /dev/null +++ b/p2p/discover/common.go @@ -0,0 +1,61 @@ +// 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 . + +package discover + +import ( + "crypto/ecdsa" + "math/big" + "net" + + "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" +) + +type UDPConn interface { + ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) + WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) + Close() error + LocalAddr() net.Addr +} + +// Config holds Table-related settings. +type Config struct { + // These settings are required and configure the UDP listener: + PrivateKey *ecdsa.PrivateKey + + // chainId identifies the current chain and is used for replay protection + ChainID *big.Int `toml:"-"` + + // These settings are optional: + NetRestrict *netutil.Netlist // network whitelist + Bootnodes []*enode.Node // list of bootstrap nodes + Unhandled chan<- ReadPacket // unhandled packets are sent on this channel + Log log.Logger // if set, log messages go here +} + +// ListenUDP starts listening for discovery packets on the given UDP socket. +func ListenUDP(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) { + return ListenV4(c, ln, cfg) +} + +// ReadPacket is a packet that couldn't be handled. Those packets are sent to the unhandled +// channel if configured. +type ReadPacket struct { + Data []byte + Addr *net.UDPAddr +} diff --git a/p2p/discover/table.go b/p2p/discover/table.go index fab3e870e5..dc04b47d3d 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -23,7 +23,6 @@ package discover import ( - "crypto/ecdsa" crand "crypto/rand" "encoding/binary" "fmt" @@ -36,7 +35,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" ) @@ -72,26 +70,23 @@ type Table struct { rand *mrand.Rand // source of randomness, periodically reseeded ips netutil.DistinctNetSet + log log.Logger db *enode.DB // database of known nodes net transport refreshReq chan chan struct{} initDone chan struct{} - - closeOnce sync.Once - closeReq chan struct{} - closed chan struct{} + closeReq chan struct{} + closed chan struct{} nodeAddedHook func(*node) // for testing } -// transport is implemented by the UDP transport. -// it is an interface so we can test without opening lots of UDP -// sockets and without generating a private key. +// transport is implemented by UDP transports. type transport interface { - self() *enode.Node - ping(enode.ID, *net.UDPAddr) error - findnode(toid enode.ID, addr *net.UDPAddr, target encPubkey) ([]*node, error) - close() + Self() *enode.Node + lookupRandom() []*enode.Node + lookupSelf() []*enode.Node + ping(*enode.Node) error } // bucket contains nodes, ordered by their last activity. the entry @@ -102,7 +97,7 @@ type bucket struct { ips netutil.DistinctNetSet } -func newTable(t transport, db *enode.DB, bootnodes []*enode.Node) (*Table, error) { +func newTable(t transport, db *enode.DB, bootnodes []*enode.Node, log log.Logger) (*Table, error) { tab := &Table{ net: t, db: db, @@ -112,6 +107,7 @@ func newTable(t transport, db *enode.DB, bootnodes []*enode.Node) (*Table, error closed: make(chan struct{}), rand: mrand.New(mrand.NewSource(0)), ips: netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit}, + log: log, } if err := tab.setFallbackNodes(bootnodes); err != nil { return nil, err @@ -124,12 +120,11 @@ func newTable(t transport, db *enode.DB, bootnodes []*enode.Node) (*Table, error tab.seedRand() tab.loadSeedNodes() - go tab.loop() return tab, nil } func (tab *Table) self() *enode.Node { - return tab.net.self() + return tab.net.Self() } func (tab *Table) seedRand() { @@ -181,16 +176,22 @@ func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) { return i + 1 } -// Close terminates the network listener and flushes the node database. -func (tab *Table) Close() { - tab.closeOnce.Do(func() { - if tab.net != nil { - tab.net.close() - } - // Wait for loop to end. - close(tab.closeReq) - <-tab.closed - }) +// Resolve searches for a specific node with the given ID. +// It returns nil if the node could not be found. +func (tab *Table) Resolve(n *enode.Node) *enode.Node { + tab.mutex.Lock() + cl := tab.closest(n.ID(), 1, false) + tab.mutex.Unlock() + if len(cl.entries) > 0 && cl.entries[0].ID() == n.ID() { + return unwrapNode(cl.entries[0]) + } + return nil +} + +// close terminates the network listener and flushes the node database. +func (tab *Table) close() { + close(tab.closeReq) + <-tab.closed } // setFallbackNodes sets the initial points of contact. These nodes @@ -216,124 +217,6 @@ func (tab *Table) isInitDone() bool { } } -// Resolve searches for a specific node with the given ID. -// It returns nil if the node could not be found. -func (tab *Table) Resolve(n *enode.Node) *enode.Node { - // If the node is present in the local table, no - // network interaction is required. - hash := n.ID() - tab.mutex.Lock() - cl := tab.closest(hash, 1) - tab.mutex.Unlock() - if len(cl.entries) > 0 && cl.entries[0].ID() == hash { - return unwrapNode(cl.entries[0]) - } - // Otherwise, do a network lookup. - result := tab.lookup(encodePubkey(n.Pubkey()), true) - for _, n := range result { - if n.ID() == hash { - return unwrapNode(n) - } - } - return nil -} - -// LookupRandom finds random nodes in the network. -func (tab *Table) LookupRandom() []*enode.Node { - var target encPubkey - crand.Read(target[:]) - return unwrapNodes(tab.lookup(target, true)) -} - -// lookup performs a network search for nodes close to the given target. It approaches the -// 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 ( - target = enode.ID(crypto.Keccak256Hash(targetKey[:])) - asked = make(map[enode.ID]bool) - seen = make(map[enode.ID]bool) - reply = make(chan []*node, alpha) - pendingQueries = 0 - result *nodesByDistance - ) - // don't query further if we hit ourself. - // unlikely to happen often in practice. - asked[tab.self().ID()] = true - - for { - tab.mutex.Lock() - // generate initial result set - result = tab.closest(target, bucketSize) - tab.mutex.Unlock() - if len(result.entries) > 0 || !refreshIfEmpty { - break - } - // The result set is empty, all nodes were dropped, refresh. - // We actually wait for the refresh to complete here. The very - // first query will hit this case and run the bootstrapping - // logic. - <-tab.refresh() - refreshIfEmpty = false - } - - for { - // ask the alpha closest nodes that we haven't asked yet - for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ { - n := result.entries[i] - if !asked[n.ID()] { - asked[n.ID()] = true - pendingQueries++ - go tab.findnode(n, targetKey, reply) - } - } - if pendingQueries == 0 { - // we have asked all closest nodes, stop the search - break - } - select { - case nodes := <-reply: - for _, n := range nodes { - if n != nil && !seen[n.ID()] { - seen[n.ID()] = true - result.push(n, bucketSize) - } - } - case <-tab.closeReq: - return nil // shutdown, no need to continue. - } - pendingQueries-- - } - return result.entries -} - -func (tab *Table) findnode(n *node, targetKey encPubkey, reply chan<- []*node) { - fails := tab.db.FindFails(n.ID(), n.IP()) - r, err := tab.net.findnode(n.ID(), n.addr(), targetKey) - if err == errClosed { - // Avoid recording failures on shutdown. - reply <- nil - return - } else if err != nil || len(r) == 0 { - fails++ - tab.db.UpdateFindFails(n.ID(), n.IP(), 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(), n.IP(), 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.addSeenNode(n) - } - reply <- r -} - func (tab *Table) refresh() <-chan struct{} { done := make(chan struct{}) select { @@ -418,11 +301,7 @@ func (tab *Table) doRefresh(done chan struct{}) { tab.loadSeedNodes() // Run self lookup to discover new neighbor nodes. - // We can only do this if we have a secp256k1 identity. - var key ecdsa.PublicKey - if err := tab.self().Load((*enode.Secp256k1)(&key)); err == nil { - tab.lookup(encodePubkey(&key), false) - } + tab.net.lookupSelf() // The Kademlia paper specifies that the bucket refresh should // perform a lookup in the least recently used bucket. We cannot @@ -431,9 +310,7 @@ func (tab *Table) doRefresh(done chan struct{}) { // sha3 preimage that falls into a chosen bucket. // We perform a few lookups with a random target instead. for i := 0; i < 3; i++ { - var target encPubkey - crand.Read(target[:]) - tab.lookup(target, false) + tab.net.lookupRandom() } } @@ -443,7 +320,7 @@ func (tab *Table) loadSeedNodes() { for i := range seeds { seed := seeds[i] age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.LastPongReceived(seed.ID(), seed.IP())) }} - log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age) + tab.log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age) tab.addSeenNode(seed) } } @@ -460,7 +337,7 @@ func (tab *Table) doRevalidate(done chan<- struct{}) { } // Ping the selected node and wait for a pong. - err := tab.net.ping(last.ID(), last.addr()) + err := tab.net.ping(unwrapNode(last)) tab.mutex.Lock() defer tab.mutex.Unlock() @@ -468,16 +345,16 @@ func (tab *Table) doRevalidate(done chan<- struct{}) { if err == nil { // The node responded, move it to the front. last.livenessChecks++ - log.Debug("Revalidated node", "b", bi, "id", last.ID(), "checks", last.livenessChecks) + tab.log.Debug("Revalidated node", "b", bi, "id", last.ID(), "checks", last.livenessChecks) tab.bumpInBucket(b, last) return } // No reply received, pick a replacement or delete the node if there aren't // any replacements. if r := tab.replace(b, last); r != nil { - log.Debug("Replaced dead node", "b", bi, "id", last.ID(), "ip", last.IP(), "checks", last.livenessChecks, "r", r.ID(), "rip", r.IP()) + tab.log.Debug("Replaced dead node", "b", bi, "id", last.ID(), "ip", last.IP(), "checks", last.livenessChecks, "r", r.ID(), "rip", r.IP()) } else { - log.Debug("Removed dead node", "b", bi, "id", last.ID(), "ip", last.IP(), "checks", last.livenessChecks) + tab.log.Debug("Removed dead node", "b", bi, "id", last.ID(), "ip", last.IP(), "checks", last.livenessChecks) } } @@ -521,22 +398,27 @@ func (tab *Table) copyLiveNodes() { // closest returns the n nodes in the table that are closest to the // given id. The caller must hold tab.mutex. -func (tab *Table) closest(target enode.ID, nresults int) *nodesByDistance { +func (tab *Table) closest(target enode.ID, nresults int, checklive bool) *nodesByDistance { // This is a very wasteful way to find the closest nodes but // obviously correct. I believe that tree-based buckets would make // this easier to implement efficiently. close := &nodesByDistance{target: target} for _, b := range &tab.buckets { for _, n := range b.entries { - if n.livenessChecks > 0 { - close.push(n, nresults) + if checklive && n.livenessChecks == 0 { + continue } + close.push(n, nresults) } } return close } +// len returns the number of nodes in the table. func (tab *Table) len() (n int) { + tab.mutex.Lock() + defer tab.mutex.Unlock() + for _, b := range &tab.buckets { n += len(b.entries) } @@ -642,11 +524,11 @@ func (tab *Table) addIP(b *bucket, ip net.IP) bool { return true } if !tab.ips.Add(ip) { - log.Debug("IP exceeds table limit", "ip", ip) + tab.log.Debug("IP exceeds table limit", "ip", ip) return false } if !b.ips.Add(ip) { - log.Debug("IP exceeds bucket limit", "ip", ip) + tab.log.Debug("IP exceeds bucket limit", "ip", ip) tab.ips.Remove(ip) return false } @@ -755,8 +637,7 @@ func deleteNode(list []*node, n *node) []*node { return list } -// nodesByDistance is a list of nodes, ordered by -// distance to target. +// nodesByDistance is a list of nodes, ordered by distance to target. type nodesByDistance struct { entries []*node target enode.ID diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go index 6f83a6a8f0..6496079574 100644 --- a/p2p/discover/table_test.go +++ b/p2p/discover/table_test.go @@ -53,7 +53,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding transport := newPingRecorder() tab, db := newTestTable(transport) defer db.Close() - defer tab.Close() + defer tab.close() <-tab.initDone @@ -118,7 +118,7 @@ func TestBucket_bumpNoDuplicates(t *testing.T) { prop := func(nodes []*node, bumps []int) (ok bool) { tab, db := newTestTable(newPingRecorder()) defer db.Close() - defer tab.Close() + defer tab.close() b := &bucket{entries: make([]*node, len(nodes))} copy(b.entries, nodes) @@ -145,7 +145,7 @@ func TestTable_IPLimit(t *testing.T) { transport := newPingRecorder() tab, db := newTestTable(transport) defer db.Close() - defer tab.Close() + defer tab.close() for i := 0; i < tableIPLimit+1; i++ { n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)}) @@ -162,7 +162,7 @@ func TestTable_BucketIPLimit(t *testing.T) { transport := newPingRecorder() tab, db := newTestTable(transport) defer db.Close() - defer tab.Close() + defer tab.close() d := 3 for i := 0; i < bucketIPLimit+1; i++ { @@ -199,11 +199,11 @@ func TestTable_closest(t *testing.T) { transport := newPingRecorder() tab, db := newTestTable(transport) defer db.Close() - defer tab.Close() + defer tab.close() fillTable(tab, test.All) // check that closest(Target, N) returns nodes - result := tab.closest(test.Target, test.N).entries + result := tab.closest(test.Target, test.N, false).entries if hasDuplicates(result) { t.Errorf("result contains duplicates") return false @@ -260,7 +260,7 @@ func TestTable_ReadRandomNodesGetAll(t *testing.T) { transport := newPingRecorder() tab, db := newTestTable(transport) defer db.Close() - defer tab.Close() + defer tab.close() <-tab.initDone for i := 0; i < len(buf); i++ { @@ -310,7 +310,7 @@ func TestTable_addVerifiedNode(t *testing.T) { tab, db := newTestTable(newPingRecorder()) <-tab.initDone defer db.Close() - defer tab.Close() + defer tab.close() // Insert two nodes. n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1}) @@ -342,7 +342,7 @@ func TestTable_addSeenNode(t *testing.T) { tab, db := newTestTable(newPingRecorder()) <-tab.initDone defer db.Close() - defer tab.Close() + defer tab.close() // Insert two nodes. n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1}) @@ -369,298 +369,6 @@ func TestTable_addSeenNode(t *testing.T) { checkIPLimitInvariant(t, tab) } -func TestTable_Lookup(t *testing.T) { - tab, db := newTestTable(lookupTestnet) - defer db.Close() - defer tab.Close() - - // lookup on empty table returns no nodes - if results := tab.lookup(lookupTestnet.target, false); 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 := wrapNode(enode.NewV4(seedKey, net.IP{127, 0, 0, 1}, 0, 256)) - seed.livenessChecks = 1 - fillTable(tab, []*node{seed}) - - results := tab.lookup(lookupTestnet.target, true) - t.Logf("results:") - for _, e := range results { - t.Logf(" ld=%d, %x", enode.LogDist(lookupTestnet.targetSha, e.ID()), e.ID().Bytes()) - } - if len(results) != bucketSize { - t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize) - } - if hasDuplicates(results) { - t.Errorf("result set contains duplicate entries") - } - if !sortedByDistanceTo(lookupTestnet.targetSha, results) { - t.Errorf("result set not sorted by distance to target") - } - // TODO: check result nodes are actually closest -} - -// This is the test network for the Lookup test. -// The nodes were obtained by running testnet.mine with a random NodeID as target. -var lookupTestnet = &preminedTestnet{ - target: hexEncPubkey("166aea4f556532c6d34e8b740e5d314af7e9ac0ca79833bd751d6b665f12dfd38ec563c363b32f02aef4a80b44fd3def94612d497b99cb5f17fd24de454927ec"), - targetSha: enode.HexID("5c944ee51c5ae9f72a95eccb8aed0374eecb5119d720cbea6813e8e0d6ad9261"), - dists: [257][]encPubkey{ - 240: { - hexEncPubkey("2001ad5e3e80c71b952161bc0186731cf5ffe942d24a79230a0555802296238e57ea7a32f5b6f18564eadc1c65389448481f8c9338df0a3dbd18f708cbc2cbcb"), - hexEncPubkey("6ba3f4f57d084b6bf94cc4555b8c657e4a8ac7b7baf23c6874efc21dd1e4f56b7eb2721e07f5242d2f1d8381fc8cae535e860197c69236798ba1ad231b105794"), - }, - 244: { - hexEncPubkey("696ba1f0a9d55c59246f776600542a9e6432490f0cd78f8bb55a196918df2081a9b521c3c3ba48e465a75c10768807717f8f689b0b4adce00e1c75737552a178"), - }, - 246: { - hexEncPubkey("d6d32178bdc38416f46ffb8b3ec9e4cb2cfff8d04dd7e4311a70e403cb62b10be1b447311b60b4f9ee221a8131fc2cbd45b96dd80deba68a949d467241facfa8"), - hexEncPubkey("3ea3d04a43a3dfb5ac11cffc2319248cf41b6279659393c2f55b8a0a5fc9d12581a9d97ef5d8ff9b5abf3321a290e8f63a4f785f450dc8a672aba3ba2ff4fdab"), - hexEncPubkey("2fc897f05ae585553e5c014effd3078f84f37f9333afacffb109f00ca8e7a3373de810a3946be971cbccdfd40249f9fe7f322118ea459ac71acca85a1ef8b7f4"), - }, - 247: { - hexEncPubkey("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"), - hexEncPubkey("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"), - hexEncPubkey("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"), - hexEncPubkey("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"), - hexEncPubkey("8b58c6073dd98bbad4e310b97186c8f822d3a5c7d57af40e2136e88e315afd115edb27d2d0685a908cfe5aa49d0debdda6e6e63972691d6bd8c5af2d771dd2a9"), - hexEncPubkey("2cbb718b7dc682da19652e7d9eb4fefaf7b7147d82c1c2b6805edf77b85e29fde9f6da195741467ff2638dc62c8d3e014ea5686693c15ed0080b6de90354c137"), - hexEncPubkey("e84027696d3f12f2de30a9311afea8fbd313c2360daff52bb5fc8c7094d5295758bec3134e4eef24e4cdf377b40da344993284628a7a346eba94f74160998feb"), - hexEncPubkey("f1357a4f04f9d33753a57c0b65ba20a5d8777abbffd04e906014491c9103fb08590e45548d37aa4bd70965e2e81ddba94f31860348df01469eec8c1829200a68"), - hexEncPubkey("4ab0a75941b12892369b4490a1928c8ca52a9ad6d3dffbd1d8c0b907bc200fe74c022d011ec39b64808a39c0ca41f1d3254386c3e7733e7044c44259486461b6"), - hexEncPubkey("d45150a72dc74388773e68e03133a3b5f51447fe91837d566706b3c035ee4b56f160c878c6273394daee7f56cc398985269052f22f75a8057df2fe6172765354"), - }, - 248: { - hexEncPubkey("6aadfce366a189bab08ac84721567483202c86590642ea6d6a14f37ca78d82bdb6509eb7b8b2f6f63c78ae3ae1d8837c89509e41497d719b23ad53dd81574afa"), - hexEncPubkey("a605ecfd6069a4cf4cf7f5840e5bc0ce10d23a3ac59e2aaa70c6afd5637359d2519b4524f56fc2ca180cdbebe54262f720ccaae8c1b28fd553c485675831624d"), - hexEncPubkey("29701451cb9448ca33fc33680b44b840d815be90146eb521641efbffed0859c154e8892d3906eae9934bfacee72cd1d2fa9dd050fd18888eea49da155ab0efd2"), - hexEncPubkey("3ed426322dee7572b08592e1e079f8b6c6b30e10e6243edd144a6a48fdbdb83df73a6e41b1143722cb82604f2203a32758610b5d9544f44a1a7921ba001528c1"), - hexEncPubkey("b2e2a2b7fdd363572a3256e75435fab1da3b16f7891a8bd2015f30995dae665d7eabfd194d87d99d5df628b4bbc7b04e5b492c596422dd8272746c7a1b0b8e4f"), - hexEncPubkey("0c69c9756162c593e85615b814ce57a2a8ca2df6c690b9c4e4602731b61e1531a3bbe3f7114271554427ffabea80ad8f36fa95a49fa77b675ae182c6ccac1728"), - hexEncPubkey("8d28be21d5a97b0876442fa4f5e5387f5bf3faad0b6f13b8607b64d6e448c0991ca28dd7fe2f64eb8eadd7150bff5d5666aa6ed868b84c71311f4ba9a38569dd"), - hexEncPubkey("2c677e1c64b9c9df6359348a7f5f33dc79e22f0177042486d125f8b6ca7f0dc756b1f672aceee5f1746bcff80aaf6f92a8dc0c9fbeb259b3fa0da060de5ab7e8"), - hexEncPubkey("3994880f94a8678f0cd247a43f474a8af375d2a072128da1ad6cae84a244105ff85e94fc7d8496f639468de7ee998908a91c7e33ef7585fff92e984b210941a1"), - hexEncPubkey("b45a9153c08d002a48090d15d61a7c7dad8c2af85d4ff5bd36ce23a9a11e0709bf8d56614c7b193bc028c16cbf7f20dfbcc751328b64a924995d47b41e452422"), - hexEncPubkey("057ab3a9e53c7a84b0f3fc586117a525cdd18e313f52a67bf31798d48078e325abe5cfee3f6c2533230cb37d0549289d692a29dd400e899b8552d4b928f6f907"), - hexEncPubkey("0ddf663d308791eb92e6bd88a2f8cb45e4f4f35bb16708a0e6ff7f1362aa6a73fedd0a1b1557fb3365e38e1b79d6918e2fae2788728b70c9ab6b51a3b94a4338"), - hexEncPubkey("f637e07ff50cc1e3731735841c4798411059f2023abcf3885674f3e8032531b0edca50fd715df6feb489b6177c345374d64f4b07d257a7745de393a107b013a5"), - hexEncPubkey("e24ec7c6eec094f63c7b3239f56d311ec5a3e45bc4e622a1095a65b95eea6fe13e29f3b6b7a2cbfe40906e3989f17ac834c3102dd0cadaaa26e16ee06d782b72"), - hexEncPubkey("b76ea1a6fd6506ef6e3506a4f1f60ed6287fff8114af6141b2ff13e61242331b54082b023cfea5b3083354a4fb3f9eb8be01fb4a518f579e731a5d0707291a6b"), - hexEncPubkey("9b53a37950ca8890ee349b325032d7b672cab7eced178d3060137b24ef6b92a43977922d5bdfb4a3409a2d80128e02f795f9dae6d7d99973ad0e23a2afb8442f"), - }, - 249: { - hexEncPubkey("675ae65567c3c72c50c73bc0fd4f61f202ea5f93346ca57b551de3411ccc614fad61cb9035493af47615311b9d44ee7a161972ee4d77c28fe1ec029d01434e6a"), - hexEncPubkey("8eb81408389da88536ae5800392b16ef5109d7ea132c18e9a82928047ecdb502693f6e4a4cdd18b54296caf561db937185731456c456c98bfe7de0baf0eaa495"), - hexEncPubkey("2adba8b1612a541771cb93a726a38a4b88e97b18eced2593eb7daf82f05a5321ca94a72cc780c306ff21e551a932fc2c6d791e4681907b5ceab7f084c3fa2944"), - hexEncPubkey("b1b4bfbda514d9b8f35b1c28961da5d5216fe50548f4066f69af3b7666a3b2e06eac646735e963e5c8f8138a2fb95af15b13b23ff00c6986eccc0efaa8ee6fb4"), - hexEncPubkey("d2139281b289ad0e4d7b4243c4364f5c51aac8b60f4806135de06b12b5b369c9e43a6eb494eab860d115c15c6fbb8c5a1b0e382972e0e460af395b8385363de7"), - hexEncPubkey("4a693df4b8fc5bdc7cec342c3ed2e228d7c5b4ab7321ddaa6cccbeb45b05a9f1d95766b4002e6d4791c2deacb8a667aadea6a700da28a3eea810a30395701bbc"), - hexEncPubkey("ab41611195ec3c62bb8cd762ee19fb182d194fd141f4a66780efbef4b07ce916246c022b841237a3a6b512a93431157edd221e854ed2a259b72e9c5351f44d0c"), - hexEncPubkey("68e8e26099030d10c3c703ae7045c0a48061fb88058d853b3e67880014c449d4311014da99d617d3150a20f1a3da5e34bf0f14f1c51fe4dd9d58afd222823176"), - hexEncPubkey("3fbcacf546fb129cd70fc48de3b593ba99d3c473798bc309292aca280320e0eacc04442c914cad5c4cf6950345ba79b0d51302df88285d4e83ee3fe41339eee7"), - hexEncPubkey("1d4a623659f7c8f80b6c3939596afdf42e78f892f682c768ad36eb7bfba402dbf97aea3a268f3badd8fe7636be216edf3d67ee1e08789ebbc7be625056bd7109"), - hexEncPubkey("a283c474ab09da02bbc96b16317241d0627646fcc427d1fe790b76a7bf1989ced90f92101a973047ae9940c92720dffbac8eff21df8cae468a50f72f9e159417"), - hexEncPubkey("dbf7e5ad7f87c3dfecae65d87c3039e14ed0bdc56caf00ce81931073e2e16719d746295512ff7937a15c3b03603e7c41a4f9df94fcd37bb200dd8f332767e9cb"), - hexEncPubkey("caaa070a26692f64fc77f30d7b5ae980d419b4393a0f442b1c821ef58c0862898b0d22f74a4f8c5d83069493e3ec0b92f17dc1fe6e4cd437c1ec25039e7ce839"), - hexEncPubkey("874cc8d1213beb65c4e0e1de38ef5d8165235893ac74ab5ea937c885eaab25c8d79dad0456e9fd3e9450626cac7e107b004478fb59842f067857f39a47cee695"), - hexEncPubkey("d94193f236105010972f5df1b7818b55846592a0445b9cdc4eaed811b8c4c0f7c27dc8cc9837a4774656d6b34682d6d329d42b6ebb55da1d475c2474dc3dfdf4"), - hexEncPubkey("edd9af6aded4094e9785637c28fccbd3980cbe28e2eb9a411048a23c2ace4bd6b0b7088a7817997b49a3dd05fc6929ca6c7abbb69438dbdabe65e971d2a794b2"), - }, - 250: { - hexEncPubkey("53a5bd1215d4ab709ae8fdc2ced50bba320bced78bd9c5dc92947fb402250c914891786db0978c898c058493f86fc68b1c5de8a5cb36336150ac7a88655b6c39"), - hexEncPubkey("b7f79e3ab59f79262623c9ccefc8f01d682323aee56ffbe295437487e9d5acaf556a9c92e1f1c6a9601f2b9eb6b027ae1aeaebac71d61b9b78e88676efd3e1a3"), - hexEncPubkey("d374bf7e8d7ffff69cc00bebff38ef5bc1dcb0a8d51c1a3d70e61ac6b2e2d6617109254b0ac224354dfbf79009fe4239e09020c483cc60c071e00b9238684f30"), - hexEncPubkey("1e1eac1c9add703eb252eb991594f8f5a173255d526a855fab24ae57dc277e055bc3c7a7ae0b45d437c4f47a72d97eb7b126f2ba344ba6c0e14b2c6f27d4b1e6"), - hexEncPubkey("ae28953f63d4bc4e706712a59319c111f5ff8f312584f65d7436b4cd3d14b217b958f8486bad666b4481fe879019fb1f767cf15b3e3e2711efc33b56d460448a"), - hexEncPubkey("934bb1edf9c7a318b82306aca67feb3d6b434421fa275d694f0b4927afd8b1d3935b727fd4ff6e3d012e0c82f1824385174e8c6450ade59c2a43281a4b3446b6"), - hexEncPubkey("9eef3f28f70ce19637519a0916555bf76d26de31312ac656cf9d3e379899ea44e4dd7ffcce923b4f3563f8a00489a34bd6936db0cbb4c959d32c49f017e07d05"), - hexEncPubkey("82200872e8f871c48f1fad13daec6478298099b591bb3dbc4ef6890aa28ebee5860d07d70be62f4c0af85085a90ae8179ee8f937cf37915c67ea73e704b03ee7"), - hexEncPubkey("6c75a5834a08476b7fc37ff3dc2011dc3ea3b36524bad7a6d319b18878fad813c0ba76d1f4555cacd3890c865438c21f0e0aed1f80e0a157e642124c69f43a11"), - hexEncPubkey("995b873742206cb02b736e73a88580c2aacb0bd4a3c97a647b647bcab3f5e03c0e0736520a8b3600da09edf4248991fb01091ec7ff3ec7cdc8a1beae011e7aae"), - hexEncPubkey("c773a056594b5cdef2e850d30891ff0e927c3b1b9c35cd8e8d53a1017001e237468e1ece3ae33d612ca3e6abb0a9169aa352e9dcda358e5af2ad982b577447db"), - hexEncPubkey("2b46a5f6923f475c6be99ec6d134437a6d11f6bb4b4ac6bcd94572fa1092639d1c08aeefcb51f0912f0a060f71d4f38ee4da70ecc16010b05dd4a674aab14c3a"), - hexEncPubkey("af6ab501366debbaa0d22e20e9688f32ef6b3b644440580fd78de4fe0e99e2a16eb5636bbae0d1c259df8ddda77b35b9a35cbc36137473e9c68fbc9d203ba842"), - hexEncPubkey("c9f6f2dd1a941926f03f770695bda289859e85fabaf94baaae20b93e5015dc014ba41150176a36a1884adb52f405194693e63b0c464a6891cc9cc1c80d450326"), - hexEncPubkey("5b116f0751526868a909b61a30b0c5282c37df6925cc03ddea556ef0d0602a9595fd6c14d371f8ed7d45d89918a032dcd22be4342a8793d88fdbeb3ca3d75bd7"), - hexEncPubkey("50f3222fb6b82481c7c813b2172e1daea43e2710a443b9c2a57a12bd160dd37e20f87aa968c82ad639af6972185609d47036c0d93b4b7269b74ebd7073221c10"), - }, - 251: { - hexEncPubkey("9b8f702a62d1bee67bedfeb102eca7f37fa1713e310f0d6651cc0c33ea7c5477575289ccd463e5a2574a00a676a1fdce05658ba447bb9d2827f0ba47b947e894"), - hexEncPubkey("b97532eb83054ed054b4abdf413bb30c00e4205545c93521554dbe77faa3cfaa5bd31ef466a107b0b34a71ec97214c0c83919720142cddac93aa7a3e928d4708"), - hexEncPubkey("2f7a5e952bfb67f2f90b8441b5fadc9ee13b1dcde3afeeb3dd64bf937f86663cc5c55d1fa83952b5422763c7df1b7f2794b751c6be316ebc0beb4942e65ab8c1"), - hexEncPubkey("42c7483781727051a0b3660f14faf39e0d33de5e643702ae933837d036508ab856ce7eec8ec89c4929a4901256e5233a3d847d5d4893f91bcf21835a9a880fee"), - hexEncPubkey("873bae27bf1dc854408fba94046a53ab0c965cebe1e4e12290806fc62b88deb1f4a47f9e18f78fc0e7913a0c6e42ac4d0fc3a20cea6bc65f0c8a0ca90b67521e"), - hexEncPubkey("a7e3a370bbd761d413f8d209e85886f68bf73d5c3089b2dc6fa42aab1ecb5162635497eed95dee2417f3c9c74a3e76319625c48ead2e963c7de877cd4551f347"), - hexEncPubkey("528597534776a40df2addaaea15b6ff832ce36b9748a265768368f657e76d58569d9f30dbb91e91cf0ae7efe8f402f17aa0ae15f5c55051ba03ba830287f4c42"), - hexEncPubkey("461d8bd4f13c3c09031fdb84f104ed737a52f630261463ce0bdb5704259bab4b737dda688285b8444dbecaecad7f50f835190b38684ced5e90c54219e5adf1bc"), - hexEncPubkey("6ec50c0be3fd232737090fc0111caaf0bb6b18f72be453428087a11a97fd6b52db0344acbf789a689bd4f5f50f79017ea784f8fd6fe723ad6ae675b9e3b13e21"), - hexEncPubkey("12fc5e2f77a83fdcc727b79d8ae7fe6a516881138d3011847ee136b400fed7cfba1f53fd7a9730253c7aa4f39abeacd04f138417ba7fcb0f36cccc3514e0dab6"), - hexEncPubkey("4fdbe75914ccd0bce02101606a1ccf3657ec963e3b3c20239d5fec87673fe446d649b4f15f1fe1a40e6cfbd446dda2d31d40bb602b1093b8fcd5f139ba0eb46a"), - hexEncPubkey("3753668a0f6281e425ea69b52cb2d17ab97afbe6eb84cf5d25425bc5e53009388857640668fadd7c110721e6047c9697803bd8a6487b43bb343bfa32ebf24039"), - hexEncPubkey("2e81b16346637dec4410fd88e527346145b9c0a849dbf2628049ac7dae016c8f4305649d5659ec77f1e8a0fac0db457b6080547226f06283598e3740ad94849a"), - hexEncPubkey("802c3cc27f91c89213223d758f8d2ecd41135b357b6d698f24d811cdf113033a81c38e0bdff574a5c005b00a8c193dc2531f8c1fa05fa60acf0ab6f2858af09f"), - hexEncPubkey("fcc9a2e1ac3667026ff16192876d1813bb75abdbf39b929a92863012fe8b1d890badea7a0de36274d5c1eb1e8f975785532c50d80fd44b1a4b692f437303393f"), - hexEncPubkey("6d8b3efb461151dd4f6de809b62726f5b89e9b38e9ba1391967f61cde844f7528fecf821b74049207cee5a527096b31f3ad623928cd3ce51d926fa345a6b2951"), - }, - 252: { - hexEncPubkey("f1ae93157cc48c2075dd5868fbf523e79e06caf4b8198f352f6e526680b78ff4227263de92612f7d63472bd09367bb92a636fff16fe46ccf41614f7a72495c2a"), - hexEncPubkey("587f482d111b239c27c0cb89b51dd5d574db8efd8de14a2e6a1400c54d4567e77c65f89c1da52841212080b91604104768350276b6682f2f961cdaf4039581c7"), - hexEncPubkey("e3f88274d35cefdaabdf205afe0e80e936cc982b8e3e47a84ce664c413b29016a4fb4f3a3ebae0a2f79671f8323661ed462bf4390af94c424dc8ace0c301b90f"), - hexEncPubkey("0ddc736077da9a12ba410dc5ea63cbcbe7659dd08596485b2bff3435221f82c10d263efd9af938e128464be64a178b7cd22e19f400d5802f4c9df54bf89f2619"), - hexEncPubkey("784aa34d833c6ce63fcc1279630113c3272e82c4ae8c126c5a52a88ac461b6baeed4244e607b05dc14e5b2f41c70a273c3804dea237f14f7a1e546f6d1309d14"), - hexEncPubkey("f253a2c354ee0e27cfcae786d726753d4ad24be6516b279a936195a487de4a59dbc296accf20463749ff55293263ed8c1b6365eecb248d44e75e9741c0d18205"), - hexEncPubkey("a1910b80357b3ad9b4593e0628922939614dc9056a5fbf477279c8b2c1d0b4b31d89a0c09d0d41f795271d14d3360ef08a3f821e65e7e1f56c07a36afe49c7c5"), - hexEncPubkey("f1168552c2efe541160f0909b0b4a9d6aeedcf595cdf0e9b165c97e3e197471a1ee6320e93389edfba28af6eaf10de98597ad56e7ab1b504ed762451996c3b98"), - hexEncPubkey("b0c8e5d2c8634a7930e1a6fd082e448c6cf9d2d8b7293558b59238815a4df926c286bf297d2049f14e8296a6eb3256af614ec1812c4f2bbe807673b58bf14c8c"), - hexEncPubkey("0fb346076396a38badc342df3679b55bd7f40a609ab103411fe45082c01f12ea016729e95914b2b5540e987ff5c9b133e85862648e7f36abdfd23100d248d234"), - hexEncPubkey("f736e0cc83417feaa280d9483f5d4d72d1b036cd0c6d9cbdeb8ac35ceb2604780de46dddaa32a378474e1d5ccdf79b373331c30c7911ade2ae32f98832e5de1f"), - hexEncPubkey("8b02991457602f42b38b342d3f2259ae4100c354b3843885f7e4e07bd644f64dab94bb7f38a3915f8b7f11d8e3f81c28e07a0078cf79d7397e38a7b7e0c857e2"), - hexEncPubkey("9221d9f04a8a184993d12baa91116692bb685f887671302999d69300ad103eb2d2c75a09d8979404c6dd28f12362f58a1a43619c493d9108fd47588a23ce5824"), - hexEncPubkey("652797801744dada833fff207d67484742eea6835d695925f3e618d71b68ec3c65bdd85b4302b2cdcb835ad3f94fd00d8da07e570b41bc0d2bcf69a8de1b3284"), - hexEncPubkey("d84f06fe64debc4cd0625e36d19b99014b6218375262cc2209202bdbafd7dffcc4e34ce6398e182e02fd8faeed622c3e175545864902dfd3d1ac57647cddf4c6"), - hexEncPubkey("d0ed87b294f38f1d741eb601020eeec30ac16331d05880fe27868f1e454446de367d7457b41c79e202eaf9525b029e4f1d7e17d85a55f83a557c005c68d7328a"), - }, - 253: { - hexEncPubkey("ad4485e386e3cc7c7310366a7c38fb810b8896c0d52e55944bfd320ca294e7912d6c53c0a0cf85e7ce226e92491d60430e86f8f15cda0161ed71893fb4a9e3a1"), - hexEncPubkey("36d0e7e5b7734f98c6183eeeb8ac5130a85e910a925311a19c4941b1290f945d4fc3996b12ef4966960b6fa0fb29b1604f83a0f81bd5fd6398d2e1a22e46af0c"), - hexEncPubkey("7d307d8acb4a561afa23bdf0bd945d35c90245e26345ec3a1f9f7df354222a7cdcb81339c9ed6744526c27a1a0c8d10857e98df942fa433602facac71ac68a31"), - hexEncPubkey("d97bf55f88c83fae36232661af115d66ca600fc4bd6d1fb35ff9bb4dad674c02cf8c8d05f317525b5522250db58bb1ecafb7157392bf5aa61b178c61f098d995"), - hexEncPubkey("7045d678f1f9eb7a4613764d17bd5698796494d0bf977b16f2dbc272b8a0f7858a60805c022fc3d1fe4f31c37e63cdaca0416c0d053ef48a815f8b19121605e0"), - hexEncPubkey("14e1f21418d445748de2a95cd9a8c3b15b506f86a0acabd8af44bb968ce39885b19c8822af61b3dd58a34d1f265baec30e3ae56149dc7d2aa4a538f7319f69c8"), - hexEncPubkey("b9453d78281b66a4eac95a1546017111eaaa5f92a65d0de10b1122940e92b319728a24edf4dec6acc412321b1c95266d39c7b3a5d265c629c3e49a65fb022c09"), - hexEncPubkey("e8a49248419e3824a00d86af422f22f7366e2d4922b304b7169937616a01d9d6fa5abf5cc01061a352dc866f48e1fa2240dbb453d872b1d7be62bdfc1d5e248c"), - hexEncPubkey("bebcff24b52362f30e0589ee573ce2d86f073d58d18e6852a592fa86ceb1a6c9b96d7fb9ec7ed1ed98a51b6743039e780279f6bb49d0a04327ac7a182d9a56f6"), - hexEncPubkey("d0835e5a4291db249b8d2fca9f503049988180c7d247bedaa2cf3a1bad0a76709360a85d4f9a1423b2cbc82bb4d94b47c0cde20afc430224834c49fe312a9ae3"), - hexEncPubkey("6b087fe2a2da5e4f0b0f4777598a4a7fb66bf77dbd5bfc44e8a7eaa432ab585a6e226891f56a7d4f5ed11a7c57b90f1661bba1059590ca4267a35801c2802913"), - hexEncPubkey("d901e5bde52d1a0f4ddf010a686a53974cdae4ebe5c6551b3c37d6b6d635d38d5b0e5f80bc0186a2c7809dbf3a42870dd09643e68d32db896c6da8ba734579e7"), - hexEncPubkey("96419fb80efae4b674402bb969ebaab86c1274f29a83a311e24516d36cdf148fe21754d46c97688cdd7468f24c08b13e4727c29263393638a3b37b99ff60ebca"), - hexEncPubkey("7b9c1889ae916a5d5abcdfb0aaedcc9c6f9eb1c1a4f68d0c2d034fe79ac610ce917c3abc670744150fa891bfcd8ab14fed6983fca964de920aa393fa7b326748"), - hexEncPubkey("7a369b2b8962cc4c65900be046482fbf7c14f98a135bbbae25152c82ad168fb2097b3d1429197cf46d3ce9fdeb64808f908a489cc6019725db040060fdfe5405"), - hexEncPubkey("47bcae48288da5ecc7f5058dfa07cf14d89d06d6e449cb946e237aa6652ea050d9f5a24a65efdc0013ccf232bf88670979eddef249b054f63f38da9d7796dbd8"), - }, - 254: { - hexEncPubkey("099739d7abc8abd38ecc7a816c521a1168a4dbd359fa7212a5123ab583ffa1cf485a5fed219575d6475dbcdd541638b2d3631a6c7fce7474e7fe3cba1d4d5853"), - hexEncPubkey("c2b01603b088a7182d0cf7ef29fb2b04c70acb320fccf78526bf9472e10c74ee70b3fcfa6f4b11d167bd7d3bc4d936b660f2c9bff934793d97cb21750e7c3d31"), - hexEncPubkey("20e4d8f45f2f863e94b45548c1ef22a11f7d36f263e4f8623761e05a64c4572379b000a52211751e2561b0f14f4fc92dd4130410c8ccc71eb4f0e95a700d4ca9"), - hexEncPubkey("27f4a16cc085e72d86e25c98bd2eca173eaaee7565c78ec5a52e9e12b2211f35de81b5b45e9195de2ebfe29106742c59112b951a04eb7ae48822911fc1f9389e"), - hexEncPubkey("55db5ee7d98e7f0b1c3b9d5be6f2bc619a1b86c3cdd513160ad4dcf267037a5fffad527ac15d50aeb32c59c13d1d4c1e567ebbf4de0d25236130c8361f9aac63"), - hexEncPubkey("883df308b0130fc928a8559fe50667a0fff80493bc09685d18213b2db241a3ad11310ed86b0ef662b3ce21fc3d9aa7f3fc24b8d9afe17c7407e9afd3345ae548"), - hexEncPubkey("c7af968cc9bc8200c3ee1a387405f7563be1dce6710a3439f42ea40657d0eae9d2b3c16c42d779605351fcdece4da637b9804e60ca08cfb89aec32c197beffa6"), - hexEncPubkey("3e66f2b788e3ff1d04106b80597915cd7afa06c405a7ae026556b6e583dca8e05cfbab5039bb9a1b5d06083ffe8de5780b1775550e7218f5e98624bf7af9a0a8"), - hexEncPubkey("4fc7f53764de3337fdaec0a711d35d3a923e72fa65025444d12230b3552ed43d9b2d1ad08ccb11f2d50c58809e6dd74dde910e195294fca3b47ae5a3967cc479"), - hexEncPubkey("bafdfdcf6ccaa989436752fa97c77477b6baa7deb374b16c095492c529eb133e8e2f99e1977012b64767b9d34b2cf6d2048ed489bd822b5139b523f6a423167b"), - hexEncPubkey("7f5d78008a4312fe059104ce80202c82b8915c2eb4411c6b812b16f7642e57c00f2c9425121f5cbac4257fe0b3e81ef5dea97ea2dbaa98f6a8b6fd4d1e5980bb"), - hexEncPubkey("598c37fe78f922751a052f463aeb0cb0bc7f52b7c2a4cf2da72ec0931c7c32175d4165d0f8998f7320e87324ac3311c03f9382a5385c55f0407b7a66b2acd864"), - hexEncPubkey("f758c4136e1c148777a7f3275a76e2db0b2b04066fd738554ec398c1c6cc9fb47e14a3b4c87bd47deaeab3ffd2110514c3855685a374794daff87b605b27ee2e"), - hexEncPubkey("0307bb9e4fd865a49dcf1fe4333d1b944547db650ab580af0b33e53c4fef6c789531110fac801bbcbce21fc4d6f61b6d5b24abdf5b22e3030646d579f6dca9c2"), - hexEncPubkey("82504b6eb49bb2c0f91a7006ce9cefdbaf6df38706198502c2e06601091fc9dc91e4f15db3410d45c6af355bc270b0f268d3dff560f956985c7332d4b10bd1ed"), - hexEncPubkey("b39b5b677b45944ceebe76e76d1f051de2f2a0ec7b0d650da52135743e66a9a5dba45f638258f9a7545d9a790c7fe6d3fdf82c25425c7887323e45d27d06c057"), - }, - 255: { - hexEncPubkey("5c4d58d46e055dd1f093f81ee60a675e1f02f54da6206720adee4dccef9b67a31efc5c2a2949c31a04ee31beadc79aba10da31440a1f9ff2a24093c63c36d784"), - hexEncPubkey("ea72161ffdd4b1e124c7b93b0684805f4c4b58d617ed498b37a145c670dbc2e04976f8785583d9c805ffbf343c31d492d79f841652bbbd01b61ed85640b23495"), - hexEncPubkey("51caa1d93352d47a8e531692a3612adac1e8ac68d0a200d086c1c57ae1e1a91aa285ab242e8c52ef9d7afe374c9485b122ae815f1707b875569d0433c1c3ce85"), - hexEncPubkey("c08397d5751b47bd3da044b908be0fb0e510d3149574dff7aeab33749b023bb171b5769990fe17469dbebc100bc150e798aeda426a2dcc766699a225fddd75c6"), - hexEncPubkey("0222c1c194b749736e593f937fad67ee348ac57287a15c7e42877aa38a9b87732a408bca370f812efd0eedbff13e6d5b854bf3ba1dec431a796ed47f32552b09"), - hexEncPubkey("03d859cd46ef02d9bfad5268461a6955426845eef4126de6be0fa4e8d7e0727ba2385b78f1a883a8239e95ebb814f2af8379632c7d5b100688eebc5841209582"), - hexEncPubkey("64d5004b7e043c39ff0bd10cb20094c287721d5251715884c280a612b494b3e9e1c64ba6f67614994c7d969a0d0c0295d107d53fc225d47c44c4b82852d6f960"), - hexEncPubkey("b0a5eefb2dab6f786670f35bf9641eefe6dd87fd3f1362bcab4aaa792903500ab23d88fae68411372e0813b057535a601d46e454323745a948017f6063a47b1f"), - hexEncPubkey("0cc6df0a3433d448b5684d2a3ffa9d1a825388177a18f44ad0008c7bd7702f1ec0fc38b83506f7de689c3b6ecb552599927e29699eed6bb867ff08f80068b287"), - hexEncPubkey("50772f7b8c03a4e153355fbbf79c8a80cf32af656ff0c7873c99911099d04a0dae0674706c357e0145ad017a0ade65e6052cb1b0d574fcd6f67da3eee0ace66b"), - hexEncPubkey("1ae37829c9ef41f8b508b82259ebac76b1ed900d7a45c08b7970f25d2d48ddd1829e2f11423a18749940b6dab8598c6e416cef0efd47e46e51f29a0bc65b37cd"), - hexEncPubkey("ba973cab31c2af091fc1644a93527d62b2394999e2b6ccbf158dd5ab9796a43d408786f1803ef4e29debfeb62fce2b6caa5ab2b24d1549c822a11c40c2856665"), - hexEncPubkey("bc413ad270dd6ea25bddba78f3298b03b8ba6f8608ac03d06007d4116fa78ef5a0cfe8c80155089382fc7a193243ee5500082660cb5d7793f60f2d7d18650964"), - hexEncPubkey("5a6a9ef07634d9eec3baa87c997b529b92652afa11473dfee41ef7037d5c06e0ddb9fe842364462d79dd31cff8a59a1b8d5bc2b810dea1d4cbbd3beb80ecec83"), - hexEncPubkey("f492c6ee2696d5f682f7f537757e52744c2ae560f1090a07024609e903d334e9e174fc01609c5a229ddbcac36c9d21adaf6457dab38a25bfd44f2f0ee4277998"), - hexEncPubkey("459e4db99298cb0467a90acee6888b08bb857450deac11015cced5104853be5adce5b69c740968bc7f931495d671a70cad9f48546d7cd203357fe9af0e8d2164"), - }, - 256: { - hexEncPubkey("a8593af8a4aef7b806b5197612017951bac8845a1917ca9a6a15dd6086d608505144990b245785c4cd2d67a295701c7aac2aa18823fb0033987284b019656268"), - hexEncPubkey("d2eebef914928c3aad77fc1b2a495f52d2294acf5edaa7d8a530b540f094b861a68fe8348a46a7c302f08ab609d85912a4968eacfea0740847b29421b4795d9e"), - hexEncPubkey("b14bfcb31495f32b650b63cf7d08492e3e29071fdc73cf2da0da48d4b191a70ba1a65f42ad8c343206101f00f8a48e8db4b08bf3f622c0853e7323b250835b91"), - hexEncPubkey("7feaee0d818c03eb30e4e0bf03ade0f3c21ca38e938a761aa1781cf70bda8cc5cd631a6cc53dd44f1d4a6d3e2dae6513c6c66ee50cb2f0e9ad6f7e319b309fd9"), - hexEncPubkey("4ca3b657b139311db8d583c25dd5963005e46689e1317620496cc64129c7f3e52870820e0ec7941d28809311df6db8a2867bbd4f235b4248af24d7a9c22d1232"), - hexEncPubkey("1181defb1d16851d42dd951d84424d6bd1479137f587fa184d5a8152be6b6b16ed08bcdb2c2ed8539bcde98c80c432875f9f724737c316a2bd385a39d3cab1d8"), - hexEncPubkey("d9dd818769fa0c3ec9f553c759b92476f082817252a04a47dc1777740b1731d280058c66f982812f173a294acf4944a85ba08346e2de153ba3ba41ce8a62cb64"), - hexEncPubkey("bd7c4f8a9e770aa915c771b15e107ca123d838762da0d3ffc53aa6b53e9cd076cffc534ec4d2e4c334c683f1f5ea72e0e123f6c261915ed5b58ac1b59f003d88"), - hexEncPubkey("3dd5739c73649d510456a70e9d6b46a855864a4a3f744e088fd8c8da11b18e4c9b5f2d7da50b1c147b2bae5ca9609ae01f7a3cdea9dce34f80a91d29cd82f918"), - hexEncPubkey("f0d7df1efc439b4bcc0b762118c1cfa99b2a6143a9f4b10e3c9465125f4c9fca4ab88a2504169bbcad65492cf2f50da9dd5d077c39574a944f94d8246529066b"), - hexEncPubkey("dd598b9ba441448e5fb1a6ec6c5f5aa9605bad6e223297c729b1705d11d05f6bfd3d41988b694681ae69bb03b9a08bff4beab5596503d12a39bffb5cd6e94c7c"), - hexEncPubkey("3fce284ac97e567aebae681b15b7a2b6df9d873945536335883e4bbc26460c064370537f323fd1ada828ea43154992d14ac0cec0940a2bd2a3f42ec156d60c83"), - hexEncPubkey("7c8dfa8c1311cb14fb29a8ac11bca23ecc115e56d9fcf7b7ac1db9066aa4eb39f8b1dabf46e192a65be95ebfb4e839b5ab4533fef414921825e996b210dd53bd"), - hexEncPubkey("cafa6934f82120456620573d7f801390ed5e16ed619613a37e409e44ab355ef755e83565a913b48a9466db786f8d4fbd590bfec474c2524d4a2608d4eafd6abd"), - hexEncPubkey("9d16600d0dd310d77045769fed2cb427f32db88cd57d86e49390c2ba8a9698cfa856f775be2013237226e7bf47b248871cf865d23015937d1edeb20db5e3e760"), - hexEncPubkey("17be6b6ba54199b1d80eff866d348ea11d8a4b341d63ad9a6681d3ef8a43853ac564d153eb2a8737f0afc9ab320f6f95c55aa11aaa13bbb1ff422fd16bdf8188"), - }, - }, -} - -type preminedTestnet struct { - target encPubkey - targetSha enode.ID // sha3(target) - dists [hashBits + 1][]encPubkey -} - -func (tn *preminedTestnet) self() *enode.Node { - return nullNode -} - -func (tn *preminedTestnet) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) { - // current log distance is encoded in port number - // fmt.Println("findnode query at dist", toaddr.Port) - if toaddr.Port == 0 { - panic("query to node at distance 0") - } - next := toaddr.Port - 1 - var result []*node - for i, ekey := range tn.dists[toaddr.Port] { - key, _ := decodePubkey(ekey) - node := wrapNode(enode.NewV4(key, net.ParseIP("127.0.0.1"), i, next)) - result = append(result, node) - } - return result, nil -} - -func (*preminedTestnet) close() {} -func (*preminedTestnet) ping(toid enode.ID, toaddr *net.UDPAddr) error { return nil } - -var _ = (*preminedTestnet).mine // avoid linter warning about mine being dead code. - -// mine generates a testnet struct literal with nodes at -// various distances to the given target. -func (tn *preminedTestnet) mine(target encPubkey) { - tn.target = target - tn.targetSha = tn.target.id() - found := 0 - for found < bucketSize*10 { - k := newkey() - key := encodePubkey(&k.PublicKey) - ld := enode.LogDist(tn.targetSha, key.id()) - if len(tn.dists[ld]) < bucketSize { - tn.dists[ld] = append(tn.dists[ld], key) - fmt.Println("found ID with ld", ld) - found++ - } - } - fmt.Println("&preminedTestnet{") - fmt.Printf(" target: %#v,\n", tn.target) - fmt.Printf(" targetSha: %#v,\n", tn.targetSha) - fmt.Printf(" dists: [%d][]encPubkey{\n", len(tn.dists)) - for ld, ns := range tn.dists { - if len(ns) == 0 { - continue - } - fmt.Printf(" %d: []encPubkey{\n", ld) - for _, n := range ns { - fmt.Printf(" hexEncPubkey(\"%x\"),\n", n[:]) - } - fmt.Println(" },") - } - fmt.Println(" },") - fmt.Println("}") -} - // gen wraps quick.Value so it's easier to use. // it generates a random value of the given value's type. func gen(typ interface{}, rand *rand.Rand) interface{} { diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go index 9c59d2e269..8bad52cae1 100644 --- a/p2p/discover/table_util_test.go +++ b/p2p/discover/table_util_test.go @@ -17,12 +17,17 @@ package discover import ( + "crypto/ecdsa" "encoding/hex" "fmt" "math/rand" "net" + "sort" "sync" + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/enr" ) @@ -37,7 +42,8 @@ func init() { func newTestTable(t transport) (*Table, *enode.DB) { db, _ := enode.OpenDB("") - tab, _ := newTable(t, db, nil) + tab, _ := newTable(t, db, nil, log.Root()) + go tab.loop() return tab, db } @@ -108,26 +114,30 @@ func newPingRecorder() *pingRecorder { } } -func (t *pingRecorder) self() *enode.Node { +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) ping(toid enode.ID, toaddr *net.UDPAddr) error { +func (t *pingRecorder) ping(n *enode.Node) error { t.mu.Lock() defer t.mu.Unlock() - t.pinged[toid] = true - if t.dead[toid] { + t.pinged[n.ID()] = true + if t.dead[n.ID()] { return errTimeout } else { return nil } } +func (t *pingRecorder) lookupSelf() []*enode.Node { + return nil +} + +func (t *pingRecorder) lookupRandom() []*enode.Node { + return nil +} + func (t *pingRecorder) close() {} func hasDuplicates(slice []*node) bool { @@ -145,14 +155,21 @@ func hasDuplicates(slice []*node) bool { } 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 sort.SliceIsSorted(slice, func(i, j int) bool { + return enode.DistCmp(distbase, slice[i].ID(), slice[j].ID()) < 0 + }) +} + +func hexEncPrivkey(h string) *ecdsa.PrivateKey { + b, err := hex.DecodeString(h) + if err != nil { + panic(err) + } + key, err := crypto.ToECDSA(b) + if err != nil { + panic(err) } - return true + return key } func hexEncPubkey(h string) (ret encPubkey) { diff --git a/p2p/discover/udp.go b/p2p/discover/v4_udp.go similarity index 67% rename from p2p/discover/udp.go rename to p2p/discover/v4_udp.go index 8d010593ab..8f40638418 100644 --- a/p2p/discover/udp.go +++ b/p2p/discover/v4_udp.go @@ -20,9 +20,10 @@ import ( "bytes" "container/list" "crypto/ecdsa" + crand "crypto/rand" "errors" "fmt" - "math/big" + "io" "net" "reflect" "sync" @@ -67,10 +68,10 @@ const ( // RPC packet types const ( - pingPacket = iota + 1 // zero is 'reserved' - pongPacket - findnodePacket - neighborsPacket + p_pingV4 = iota + 1 // zero is 'reserved' + p_pongV4 + p_findnodeV4 + p_neighborsV4 ) var ( @@ -79,7 +80,7 @@ var ( // RPC request structures type ( - ping struct { + pingV4 struct { senderKey *ecdsa.PublicKey // filled in by preverify Version uint @@ -89,8 +90,8 @@ type ( Rest []rlp.RawValue `rlp:"tail"` } - // pong is the reply to ping. - pong struct { + // pongV4 is the reply to pingV4. + pongV4 struct { // This field should mirror the UDP envelope address // of the ping packet, which provides a way to discover the // the external address (after NAT). @@ -102,16 +103,16 @@ type ( Rest []rlp.RawValue `rlp:"tail"` } - // findnode is a query for nodes close to the given target. - findnode struct { + // findnodeV4 is a query for nodes close to the given target. + findnodeV4 struct { Target encPubkey Expiration uint64 // Ignore additional fields (for forward compatibility). Rest []rlp.RawValue `rlp:"tail"` } - // reply to findnode - neighbors struct { + // neighborsV4 is the reply to findnodeV4. + neighborsV4 struct { Nodes []rpcNode Expiration uint64 // Ignore additional fields (for forward compatibility). @@ -132,6 +133,16 @@ type ( } ) +// packet is implemented by all v4 protocol messages. +type packetV4 interface { + // preverify checks whether the packet is valid and should be handled at all. + preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error + // handle handles the packet. + handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) + // name returns the name of the packet for logging purposes. + name() string +} + func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint { ip := net.IP{} if ip4 := addr.IP.To4(); ip4 != nil { @@ -142,7 +153,7 @@ func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint { return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort} } -func (t *udp) nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*node, error) { +func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*node, error) { if rn.UDP <= 1024 { return nil, errors.New("low port") } @@ -170,31 +181,16 @@ func nodeToRPC(n *node) rpcNode { return rpcNode{ID: ekey, IP: n.IP(), UDP: uint16(n.UDP()), TCP: uint16(n.TCP())} } -// packet is implemented by all protocol messages. -type packet interface { - // preverify checks whether the packet is valid and should be handled at all. - preverify(t *udp, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error - // handle handles the packet. - handle(t *udp, from *net.UDPAddr, fromID enode.ID, mac []byte) - // name returns the name of the packet for logging purposes. - name() string -} - -type conn interface { - ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) - WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) - Close() error - LocalAddr() net.Addr -} - -// udp implements the discovery v4 UDP wire protocol. -type udp struct { - conn conn +// UDPv4 implements the v4 wire protocol. +type UDPv4 struct { + conn UDPConn + log log.Logger netrestrict *netutil.Netlist priv *ecdsa.PrivateKey localNode *enode.LocalNode db *enode.DB tab *Table + closeOnce sync.Once wg sync.WaitGroup addReplyMatcher chan *replyMatcher @@ -237,105 +233,215 @@ type reply struct { from enode.ID ip net.IP ptype byte - data packet + data packetV4 // loop indicates whether there was // a matching request by sending on this channel. matched chan<- bool } -// ReadPacket is sent to the unhandled channel when it could not be processed -type ReadPacket struct { - Data []byte - Addr *net.UDPAddr -} - -// Config holds Table-related settings. -type Config struct { - // These settings are required and configure the UDP listener: - PrivateKey *ecdsa.PrivateKey - - // chainId identifies the current chain and is used for replay protection - ChainID *big.Int `toml:"-"` - - // These settings are optional: - NetRestrict *netutil.Netlist // network whitelist - Bootnodes []*enode.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. -func ListenUDP(c conn, ln *enode.LocalNode, cfg Config) (*Table, error) { - tab, _, err := newUDP(c, ln, cfg) +func ListenV4(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) { + t := &UDPv4{ + conn: c, + priv: cfg.PrivateKey, + netrestrict: cfg.NetRestrict, + localNode: ln, + db: ln.Database(), + closing: make(chan struct{}), + gotreply: make(chan reply), + addReplyMatcher: make(chan *replyMatcher), + log: cfg.Log, + } + if t.log == nil { + t.log = log.Root() + } + tab, err := newTable(t, ln.Database(), cfg.Bootnodes, t.log) if err != nil { return nil, err } + t.tab = tab + go tab.loop() + + t.wg.Add(2) + go t.loop() + go t.readLoop(cfg.Unhandled) if cfg.ChainID != nil { bytes_ChainId, _ := rlp.EncodeToBytes(cfg.ChainID) log.Info("UDP listener up", "chainId", cfg.ChainID, "bytes_ChainId", bytes_ChainId) cRest = []rlp.RawValue{bytes_ChainId, bytes_ChainId} } + return t, nil +} - log.Info("UDP listener up", "self", tab.self) - return tab, nil +// Self returns the local node. +func (t *UDPv4) Self() *enode.Node { + return t.localNode.Node() } -func newUDP(c conn, ln *enode.LocalNode, cfg Config) (*Table, *udp, error) { - udp := &udp{ - conn: c, - priv: cfg.PrivateKey, - netrestrict: cfg.NetRestrict, - localNode: ln, - db: ln.Database(), - closing: make(chan struct{}), - gotreply: make(chan reply), - addReplyMatcher: make(chan *replyMatcher), +// Close shuts down the socket and aborts any running queries. +func (t *UDPv4) Close() { + t.closeOnce.Do(func() { + close(t.closing) + t.conn.Close() + t.wg.Wait() + t.tab.close() + }) +} + +// ReadRandomNodes reads random nodes from the local table. +func (t *UDPv4) ReadRandomNodes(buf []*enode.Node) int { + return t.tab.ReadRandomNodes(buf) +} + +// LookupRandom finds random nodes in the network. +func (t *UDPv4) LookupRandom() []*enode.Node { + if t.tab.len() == 0 { + // All nodes were dropped, refresh. The very first query will hit this + // case and run the bootstrapping logic. + <-t.tab.refresh() } - tab, err := newTable(udp, ln.Database(), cfg.Bootnodes) - if err != nil { - return nil, nil, err + return t.lookupRandom() +} + +func (t *UDPv4) LookupPubkey(key *ecdsa.PublicKey) []*enode.Node { + if t.tab.len() == 0 { + // All nodes were dropped, refresh. The very first query will hit this + // case and run the bootstrapping logic. + <-t.tab.refresh() } - udp.tab = tab + return unwrapNodes(t.lookup(encodePubkey(key))) +} - udp.wg.Add(2) - go udp.loop() - go udp.readLoop(cfg.Unhandled) - return udp.tab, udp, nil +func (t *UDPv4) lookupRandom() []*enode.Node { + var target encPubkey + crand.Read(target[:]) + return unwrapNodes(t.lookup(target)) } -func (t *udp) self() *enode.Node { - return t.localNode.Node() +func (t *UDPv4) lookupSelf() []*enode.Node { + return unwrapNodes(t.lookup(encodePubkey(&t.priv.PublicKey))) } -func (t *udp) close() { - close(t.closing) - t.conn.Close() - t.wg.Wait() +// lookup performs a network search for nodes close to the given target. It approaches the +// 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 (t *UDPv4) lookup(targetKey encPubkey) []*node { + var ( + target = enode.ID(crypto.Keccak256Hash(targetKey[:])) + asked = make(map[enode.ID]bool) + seen = make(map[enode.ID]bool) + reply = make(chan []*node, alpha) + pendingQueries = 0 + result *nodesByDistance + ) + // Don't query further if we hit ourself. + // Unlikely to happen often in practice. + asked[t.Self().ID()] = true + + // Generate the initial result set. + t.tab.mutex.Lock() + result = t.tab.closest(target, bucketSize, false) + t.tab.mutex.Unlock() + + for { + // ask the alpha closest nodes that we haven't asked yet + for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ { + n := result.entries[i] + if !asked[n.ID()] { + asked[n.ID()] = true + pendingQueries++ + go t.lookupWorker(n, targetKey, reply) + } + } + if pendingQueries == 0 { + // we have asked all closest nodes, stop the search + break + } + select { + case nodes := <-reply: + for _, n := range nodes { + if n != nil && !seen[n.ID()] { + seen[n.ID()] = true + result.push(n, bucketSize) + } + } + case <-t.tab.closeReq: + return nil // shutdown, no need to continue. + } + pendingQueries-- + } + return result.entries +} + +func (t *UDPv4) lookupWorker(n *node, targetKey encPubkey, reply chan<- []*node) { + fails := t.db.FindFails(n.ID(), n.IP()) + r, err := t.findnode(n.ID(), n.addr(), targetKey) + if err == errClosed { + // Avoid recording failures on shutdown. + reply <- nil + return + } else if len(r) == 0 { + fails++ + t.db.UpdateFindFails(n.ID(), n.IP(), fails) + t.log.Trace("Findnode failed", "id", n.ID(), "failcount", fails, "err", err) + if fails >= maxFindnodeFailures { + t.log.Trace("Too many findnode failures, dropping", "id", n.ID(), "failcount", fails) + t.tab.delete(n) + } + } else if fails > 0 { + t.db.UpdateFindFails(n.ID(), n.IP(), 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 { + t.tab.addSeenNode(n) + } + reply <- r } -func (t *udp) ourEndpoint() rpcEndpoint { - n := t.self() +// Resolve searches for a specific node with the given ID. +// It returns nil if the node could not be found. +func (t *UDPv4) Resolve(n *enode.Node) *enode.Node { + // If the node is present in the local table, no + // network interaction is required. + if intab := t.tab.Resolve(n); intab != nil { + return intab + } + // Otherwise, do a network lookup. + hash := n.ID() + result := t.LookupPubkey(n.Pubkey()) + for _, n := range result { + if n.ID() == hash { + return n + } + } + return nil +} + +func (t *UDPv4) 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. -func (t *udp) ping(toid enode.ID, toaddr *net.UDPAddr) error { - return <-t.sendPing(toid, toaddr, nil) +func (t *UDPv4) ping(n *enode.Node) error { + return <-t.sendPing(n.ID(), &net.UDPAddr{IP: n.IP(), Port: n.UDP()}, 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{ +func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) <-chan error { + req := &pingV4{ Version: 4, From: t.ourEndpoint(), To: makeEndpoint(toaddr, 0), // TODO: maybe use known TCP port from DB Expiration: uint64(time.Now().Add(expiration).Unix()), Rest: cRest, } - packet, hash, err := encodePacket(t.priv, pingPacket, req) + packet, hash, err := t.encode(t.priv, p_pingV4, req) if err != nil { errc := make(chan error, 1) errc <- err @@ -343,8 +449,8 @@ func (t *udp) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) <-ch } // Add a matcher for the reply to the pending reply queue. Pongs are matched if they // reference the ping we're about to send. - errc := t.pending(toid, toaddr.IP, pongPacket, func(p interface{}) (matched bool, requestDone bool) { - matched = bytes.Equal(p.(*pong).ReplyTok, hash) + errc := t.pending(toid, toaddr.IP, p_pongV4, func(p interface{}) (matched bool, requestDone bool) { + matched = bytes.Equal(p.(*pongV4).ReplyTok, hash) if matched && callback != nil { callback() } @@ -358,11 +464,11 @@ func (t *udp) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) <-ch // findnode sends a findnode request to the given node and waits until // the node has sent up to k neighbors. -func (t *udp) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) { +func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) { // If we haven't seen a ping from the destination node for a while, it won't remember // our endpoint proof and reject findnode. Solicit a ping first. if time.Since(t.db.LastPingReceived(toid, toaddr.IP)) > bondExpiration { - t.ping(toid, toaddr) + <-t.sendPing(toid, toaddr, nil) // Wait for them to ping back and process our pong. time.Sleep(respTimeout) } @@ -371,20 +477,20 @@ func (t *udp) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([] // active until enough nodes have been received. nodes := make([]*node, 0, bucketSize) nreceived := 0 - errc := t.pending(toid, toaddr.IP, neighborsPacket, func(r interface{}) (matched bool, requestDone bool) { - reply := r.(*neighbors) + errc := t.pending(toid, toaddr.IP, p_neighborsV4, func(r interface{}) (matched bool, requestDone bool) { + reply := r.(*neighborsV4) for _, rn := range reply.Nodes { nreceived++ n, err := t.nodeFromRPC(toaddr, rn) if err != nil { - log.Trace("Invalid neighbor node received", "ip", rn.IP, "addr", toaddr, "err", err) + t.log.Trace("Invalid neighbor node received", "ip", rn.IP, "addr", toaddr, "err", err) continue } nodes = append(nodes, n) } return true, nreceived >= bucketSize }) - t.send(toaddr, toid, findnodePacket, &findnode{ + t.send(toaddr, toid, p_findnodeV4, &findnodeV4{ Target: target, Expiration: uint64(time.Now().Add(expiration).Unix()), Rest: cRest, @@ -394,7 +500,7 @@ func (t *udp) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([] // pending adds a reply matcher to the pending reply queue. // see the documentation of type replyMatcher for a detailed explanation. -func (t *udp) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchFunc) <-chan error { +func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchFunc) <-chan error { ch := make(chan error, 1) p := &replyMatcher{from: id, ip: ip, ptype: ptype, callback: callback, errc: ch} select { @@ -408,7 +514,7 @@ func (t *udp) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchFun // handleReply dispatches a reply packet, invoking reply matchers. It returns // whether any matcher considered the packet acceptable. -func (t *udp) handleReply(from enode.ID, fromIP net.IP, ptype byte, req packet) bool { +func (t *UDPv4) handleReply(from enode.ID, fromIP net.IP, ptype byte, req packetV4) bool { matched := make(chan bool, 1) select { case t.gotreply <- reply{from, fromIP, ptype, req, matched}: @@ -421,7 +527,7 @@ func (t *udp) handleReply(from enode.ID, fromIP net.IP, ptype byte, req packet) // loop runs in its own goroutine. it keeps track of // the refresh timer and the pending reply queue. -func (t *udp) loop() { +func (t *UDPv4) loop() { defer t.wg.Done() var ( @@ -528,7 +634,7 @@ var ( ) func init() { - p := neighbors{Expiration: ^uint64(0)} + p := neighborsV4{Expiration: ^uint64(0)} maxSizeNode := rpcNode{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)} for n := 0; ; n++ { p.Nodes = append(p.Nodes, maxSizeNode) @@ -544,32 +650,32 @@ func init() { } } -func (t *udp) send(toaddr *net.UDPAddr, toid enode.ID, ptype byte, req packet) ([]byte, error) { - packet, hash, err := encodePacket(t.priv, ptype, req) +func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, ptype byte, req packetV4) ([]byte, error) { + packet, hash, err := t.encode(t.priv, ptype, req) if err != nil { return hash, err } return hash, t.write(toaddr, toid, req.name(), packet) } -func (t *udp) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet []byte) error { +func (t *UDPv4) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet []byte) error { _, err := t.conn.WriteToUDP(packet, toaddr) - log.Trace(">> "+what, "id", toid, "addr", toaddr, "err", err) + t.log.Trace(">> "+what, "id", toid, "addr", toaddr, "err", err) return err } -func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) (packet, hash []byte, err error) { +func (t *UDPv4) encode(priv *ecdsa.PrivateKey, ptype byte, req interface{}) (packet, hash []byte, err error) { b := new(bytes.Buffer) b.Write(headSpace) b.WriteByte(ptype) if err := rlp.Encode(b, req); err != nil { - log.Error("Can't encode discv4 packet", "err", err) + t.log.Error("Can't encode discv4 packet", "err", err) return nil, nil, err } packet = b.Bytes() sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv) if err != nil { - log.Error("Can't sign discv4 packet", "err", err) + t.log.Error("Can't sign discv4 packet", "err", err) return nil, nil, err } copy(packet[macSize:], sig) @@ -582,7 +688,7 @@ func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) (packet, } // readLoop runs in its own goroutine. it handles incoming UDP packets. -func (t *udp) readLoop(unhandled chan<- ReadPacket) { +func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) { defer t.wg.Done() if unhandled != nil { defer close(unhandled) @@ -593,11 +699,13 @@ func (t *udp) readLoop(unhandled chan<- ReadPacket) { nbytes, from, err := t.conn.ReadFromUDP(buf) if netutil.IsTemporaryError(err) { // Ignore temporary read errors. - log.Debug("Temporary UDP read error", "err", err) + t.log.Debug("Temporary UDP read error", "err", err) continue } else if err != nil { // Shut down the loop for permament errors. - log.Debug("UDP read error", "err", err) + if err != io.EOF { + t.log.Debug("UDP read error", "err", err) + } return } if t.handlePacket(from, buf[:nbytes]) != nil && unhandled != nil { @@ -609,24 +717,24 @@ func (t *udp) readLoop(unhandled chan<- ReadPacket) { } } -func (t *udp) handlePacket(from *net.UDPAddr, buf []byte) error { - packet, fromKey, hash, err := decodePacket(buf) +func (t *UDPv4) handlePacket(from *net.UDPAddr, buf []byte) error { + packet, fromKey, hash, err := decodeV4(buf) if err != nil { - log.Debug("Bad discv4 packet", "addr", from, "err", err) + t.log.Debug("Bad discv4 packet", "addr", from, "err", err) return err } fromID := fromKey.id() if err == nil { err = packet.preverify(t, from, fromID, fromKey) } - log.Trace("<< "+packet.name(), "id", fromID, "addr", from, "err", err) + t.log.Trace("<< "+packet.name(), "id", fromID, "addr", from, "err", err) if err == nil { packet.handle(t, from, fromID, hash) } return err } -func decodePacket(buf []byte) (packet, encPubkey, []byte, error) { +func decodeV4(buf []byte) (packetV4, encPubkey, []byte, error) { if len(buf) < headSize+1 { return nil, encPubkey{}, nil, errPacketTooSmall } @@ -640,16 +748,16 @@ func decodePacket(buf []byte) (packet, encPubkey, []byte, error) { return nil, fromKey, hash, err } - var req packet + var req packetV4 switch ptype := sigdata[0]; ptype { - case pingPacket: - req = new(ping) - case pongPacket: - req = new(pong) - case findnodePacket: - req = new(findnode) - case neighborsPacket: - req = new(neighbors) + case p_pingV4: + req = new(pingV4) + case p_pongV4: + req = new(pongV4) + case p_findnodeV4: + req = new(findnodeV4) + case p_neighborsV4: + req = new(neighborsV4) default: return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype) } @@ -660,7 +768,7 @@ func decodePacket(buf []byte) (packet, encPubkey, []byte, error) { // Packet Handlers -func (req *ping) preverify(t *udp, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { +func (req *pingV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { if expired(req.Expiration) { return errExpired } @@ -677,9 +785,9 @@ func (req *ping) preverify(t *udp, from *net.UDPAddr, fromID enode.ID, fromKey e return nil } -func (req *ping) handle(t *udp, from *net.UDPAddr, fromID enode.ID, mac []byte) { +func (req *pingV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) { // Reply. - t.send(from, fromID, pongPacket, &pong{ + t.send(from, fromID, p_pongV4, &pongV4{ To: makeEndpoint(from, req.From.TCP), ReplyTok: mac, Expiration: uint64(time.Now().Add(expiration).Unix()), @@ -701,26 +809,26 @@ func (req *ping) handle(t *udp, from *net.UDPAddr, fromID enode.ID, mac []byte) t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)}) } -func (req *ping) name() string { return "PING/v4" } +func (req *pingV4) name() string { return "PING/v4" } -func (req *pong) preverify(t *udp, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { +func (req *pongV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { if expired(req.Expiration) { return errExpired } - if !t.handleReply(fromID, from.IP, pongPacket, req) { + if !t.handleReply(fromID, from.IP, p_pongV4, req) { return errUnsolicitedReply } return nil } -func (req *pong) handle(t *udp, from *net.UDPAddr, fromID enode.ID, mac []byte) { +func (req *pongV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) { t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)}) t.db.UpdateLastPongReceived(fromID, from.IP, time.Now()) } -func (req *pong) name() string { return "PONG/v4" } +func (req *pongV4) name() string { return "PONG/v4" } -func (req *findnode) preverify(t *udp, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { +func (req *findnodeV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { if expired(req.Expiration) { return errExpired } @@ -739,16 +847,16 @@ func (req *findnode) preverify(t *udp, from *net.UDPAddr, fromID enode.ID, fromK return nil } -func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID enode.ID, mac []byte) { +func (req *findnodeV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) { // Determine closest nodes. target := enode.ID(crypto.Keccak256Hash(req.Target[:])) t.tab.mutex.Lock() - closest := t.tab.closest(target, bucketSize).entries + closest := t.tab.closest(target, bucketSize, true).entries t.tab.mutex.Unlock() // Send neighbors in chunks with at most maxNeighbors per packet // to stay below the packet size limit. - p := neighbors{ + p := neighborsV4{ Expiration: uint64(time.Now().Add(expiration).Unix()), Rest: cRest, } @@ -758,35 +866,35 @@ func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID enode.ID, mac []by p.Nodes = append(p.Nodes, nodeToRPC(n)) } if len(p.Nodes) == maxNeighbors { - t.send(from, fromID, neighborsPacket, &p) + t.send(from, fromID, p_neighborsV4, &p) p.Nodes = p.Nodes[:0] sent = true } } if len(p.Nodes) > 0 || !sent { - t.send(from, fromID, neighborsPacket, &p) + t.send(from, fromID, p_neighborsV4, &p) } } -func (req *findnode) name() string { return "FINDNODE/v4" } +func (req *findnodeV4) name() string { return "FINDNODE/v4" } -func (req *neighbors) preverify(t *udp, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { +func (req *neighborsV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { if expired(req.Expiration) { return errExpired } if !reflect.DeepEqual(req.Rest, cRest) { return errData } - if !t.handleReply(fromID, from.IP, neighborsPacket, req) { + if !t.handleReply(fromID, from.IP, p_neighborsV4, req) { return errUnsolicitedReply } return nil } -func (req *neighbors) handle(t *udp, from *net.UDPAddr, fromID enode.ID, mac []byte) { +func (req *neighborsV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) { } -func (req *neighbors) name() string { return "NEIGHBORS/v4" } +func (req *neighborsV4) name() string { return "NEIGHBORS/v4" } func expired(ts uint64) bool { return time.Unix(int64(ts), 0).Before(time.Now()) diff --git a/p2p/discover/v4_udp_lookup_test.go b/p2p/discover/v4_udp_lookup_test.go new file mode 100644 index 0000000000..6b3ba7a074 --- /dev/null +++ b/p2p/discover/v4_udp_lookup_test.go @@ -0,0 +1,220 @@ +// 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 . + +package discover + +import ( + "crypto/ecdsa" + "fmt" + "net" + "reflect" + "sort" + "testing" + + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +func TestUDPv4_Lookup(t *testing.T) { + t.Parallel() + test := newUDPTest(t) + + // Lookup on empty table returns no nodes. + targetKey, _ := decodePubkey(lookupTestnet.target) + if results := test.udp.LookupPubkey(targetKey); len(results) > 0 { + t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results) + } + + // Seed table with initial node. + fillTable(test.table, []*node{wrapNode(lookupTestnet.node(256, 0))}) + + // Start the lookup. + resultC := make(chan []*enode.Node, 1) + go func() { + resultC <- test.udp.LookupPubkey(targetKey) + test.close() + }() + + // Answer lookup packets. + for done := false; !done; { + done = test.waitPacketOut(func(p packetV4, to *net.UDPAddr, hash []byte) { + n, key := lookupTestnet.nodeByAddr(to) + switch p.(type) { + case *pingV4: + test.packetInFrom(nil, key, to, p_pongV4, &pongV4{Expiration: futureExp, ReplyTok: hash}) + case *findnodeV4: + dist := enode.LogDist(n.ID(), lookupTestnet.target.id()) + nodes := lookupTestnet.nodesAtDistance(dist - 1) + test.packetInFrom(nil, key, to, p_neighborsV4, &neighborsV4{Expiration: futureExp, Nodes: nodes}) + } + }) + } + + // Verify result nodes. + results := <-resultC + t.Logf("results:") + for _, e := range results { + t.Logf(" ld=%d, %x", enode.LogDist(lookupTestnet.target.id(), e.ID()), e.ID().Bytes()) + } + if len(results) != bucketSize { + t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize) + } + if hasDuplicates(wrapNodes(results)) { + t.Errorf("result set contains duplicate entries") + } + if !sortedByDistanceTo(lookupTestnet.target.id(), wrapNodes(results)) { + t.Errorf("result set not sorted by distance to target") + } + if !reflect.DeepEqual(results, lookupTestnet.closest(bucketSize)) { + t.Errorf("results aren't the closest %d nodes", bucketSize) + } +} + +// This is the test network for the Lookup test. +// The nodes were obtained by running lookupTestnet.mine with a random NodeID as target. +var lookupTestnet = &preminedTestnet{ + target: hexEncPubkey("5d485bdcbe9bc89314a10ae9231e429d33853e3a8fa2af39f5f827370a2e4185e344ace5d16237491dad41f278f1d3785210d29ace76cd627b9147ee340b1125"), + dists: [257][]*ecdsa.PrivateKey{ + 251: { + hexEncPrivkey("29738ba0c1a4397d6a65f292eee07f02df8e58d41594ba2be3cf84ce0fc58169"), + hexEncPrivkey("511b1686e4e58a917f7f848e9bf5539d206a68f5ad6b54b552c2399fe7d174ae"), + hexEncPrivkey("d09e5eaeec0fd596236faed210e55ef45112409a5aa7f3276d26646080dcfaeb"), + hexEncPrivkey("c1e20dbbf0d530e50573bd0a260b32ec15eb9190032b4633d44834afc8afe578"), + hexEncPrivkey("ed5f38f5702d92d306143e5d9154fb21819777da39af325ea359f453d179e80b"), + }, + 252: { + hexEncPrivkey("1c9b1cafbec00848d2c174b858219914b42a7d5c9359b1ca03fd650e8239ae94"), + hexEncPrivkey("e0e1e8db4a6f13c1ffdd3e96b72fa7012293ced187c9dcdcb9ba2af37a46fa10"), + hexEncPrivkey("3d53823e0a0295cb09f3e11d16c1b44d07dd37cec6f739b8df3a590189fe9fb9"), + }, + 253: { + hexEncPrivkey("2d0511ae9bf590166597eeab86b6f27b1ab761761eaea8965487b162f8703847"), + hexEncPrivkey("6cfbd7b8503073fc3dbdb746a7c672571648d3bd15197ccf7f7fef3d904f53a2"), + hexEncPrivkey("a30599b12827b69120633f15b98a7f6bc9fc2e9a0fd6ae2ebb767c0e64d743ab"), + hexEncPrivkey("14a98db9b46a831d67eff29f3b85b1b485bb12ae9796aea98d91be3dc78d8a91"), + hexEncPrivkey("2369ff1fc1ff8ca7d20b17e2673adc3365c3674377f21c5d9dafaff21fe12e24"), + hexEncPrivkey("9ae91101d6b5048607f41ec0f690ef5d09507928aded2410aabd9237aa2727d7"), + hexEncPrivkey("05e3c59090a3fd1ae697c09c574a36fcf9bedd0afa8fe3946f21117319ca4973"), + hexEncPrivkey("06f31c5ea632658f718a91a1b1b9ae4b7549d7b3bc61cbc2be5f4a439039f3ad"), + }, + 254: { + hexEncPrivkey("dec742079ec00ff4ec1284d7905bc3de2366f67a0769431fd16f80fd68c58a7c"), + hexEncPrivkey("ff02c8861fa12fbd129d2a95ea663492ef9c1e51de19dcfbbfe1c59894a28d2b"), + hexEncPrivkey("4dded9e4eefcbce4262be4fd9e8a773670ab0b5f448f286ec97dfc8cf681444a"), + hexEncPrivkey("750d931e2a8baa2c9268cb46b7cd851f4198018bed22f4dceb09dd334a2395f6"), + hexEncPrivkey("ce1435a956a98ffec484cd11489c4f165cf1606819ab6b521cee440f0c677e9e"), + hexEncPrivkey("996e7f8d1638be92d7328b4770f47e5420fc4bafecb4324fd33b1f5d9f403a75"), + hexEncPrivkey("ebdc44e77a6cc0eb622e58cf3bb903c3da4c91ca75b447b0168505d8fc308b9c"), + hexEncPrivkey("46bd1eddcf6431bea66fc19ebc45df191c1c7d6ed552dcdc7392885009c322f0"), + }, + 255: { + hexEncPrivkey("da8645f90826e57228d9ea72aff84500060ad111a5d62e4af831ed8e4b5acfb8"), + hexEncPrivkey("3c944c5d9af51d4c1d43f5d0f3a1a7ef65d5e82744d669b58b5fed242941a566"), + hexEncPrivkey("5ebcde76f1d579eebf6e43b0ffe9157e65ffaa391175d5b9aa988f47df3e33da"), + hexEncPrivkey("97f78253a7d1d796e4eaabce721febcc4550dd68fb11cc818378ba807a2cb7de"), + hexEncPrivkey("a38cd7dc9b4079d1c0406afd0fdb1165c285f2c44f946eca96fc67772c988c7d"), + hexEncPrivkey("d64cbb3ffdf712c372b7a22a176308ef8f91861398d5dbaf326fd89c6eaeef1c"), + hexEncPrivkey("d269609743ef29d6446e3355ec647e38d919c82a4eb5837e442efd7f4218944f"), + hexEncPrivkey("d8f7bcc4a530efde1d143717007179e0d9ace405ddaaf151c4d863753b7fd64c"), + }, + 256: { + hexEncPrivkey("8c5b422155d33ea8e9d46f71d1ad3e7b24cb40051413ffa1a81cff613d243ba9"), + hexEncPrivkey("937b1af801def4e8f5a3a8bd225a8bcff1db764e41d3e177f2e9376e8dd87233"), + hexEncPrivkey("120260dce739b6f71f171da6f65bc361b5fad51db74cf02d3e973347819a6518"), + hexEncPrivkey("1fa56cf25d4b46c2bf94e82355aa631717b63190785ac6bae545a88aadc304a9"), + hexEncPrivkey("3c38c503c0376f9b4adcbe935d5f4b890391741c764f61b03cd4d0d42deae002"), + hexEncPrivkey("3a54af3e9fa162bc8623cdf3e5d9b70bf30ade1d54cc3abea8659aba6cff471f"), + hexEncPrivkey("6799a02ea1999aefdcbcc4d3ff9544478be7365a328d0d0f37c26bd95ade0cda"), + hexEncPrivkey("e24a7bc9051058f918646b0f6e3d16884b2a55a15553b89bab910d55ebc36116"), + }, + }, +} + +type preminedTestnet struct { + target encPubkey + dists [hashBits + 1][]*ecdsa.PrivateKey +} + +func (tn *preminedTestnet) node(dist, index int) *enode.Node { + key := tn.dists[dist][index] + ip := net.IP{127, byte(dist >> 8), byte(dist), byte(index)} + return enode.NewV4(&key.PublicKey, ip, 0, 5000) +} + +func (tn *preminedTestnet) nodeByAddr(addr *net.UDPAddr) (*enode.Node, *ecdsa.PrivateKey) { + dist := int(addr.IP[1])<<8 + int(addr.IP[2]) + index := int(addr.IP[3]) + key := tn.dists[dist][index] + return tn.node(dist, index), key +} + +func (tn *preminedTestnet) nodesAtDistance(dist int) []rpcNode { + result := make([]rpcNode, len(tn.dists[dist])) + for i := range result { + result[i] = nodeToRPC(wrapNode(tn.node(dist, i))) + } + return result +} + +func (tn *preminedTestnet) closest(n int) (nodes []*enode.Node) { + for d := range tn.dists { + for i := range tn.dists[d] { + nodes = append(nodes, tn.node(d, i)) + } + } + sort.Slice(nodes, func(i, j int) bool { + return enode.DistCmp(tn.target.id(), nodes[i].ID(), nodes[j].ID()) < 0 + }) + return nodes[:n] +} + +var _ = (*preminedTestnet).mine // avoid linter warning about mine being dead code. + +// mine generates a testnet struct literal with nodes at +// various distances to the network's target. +func (tn *preminedTestnet) mine() { + // Clear existing slices first (useful when re-mining). + for i := range tn.dists { + tn.dists[i] = nil + } + + targetSha := tn.target.id() + found, need := 0, 40 + for found < need { + k := newkey() + ld := enode.LogDist(targetSha, encodePubkey(&k.PublicKey).id()) + if len(tn.dists[ld]) < 8 { + tn.dists[ld] = append(tn.dists[ld], k) + found++ + fmt.Printf("found ID with ld %d (%d/%d)\n", ld, found, need) + } + } + fmt.Printf("&preminedTestnet{\n") + fmt.Printf(" target: hexEncPubkey(\"%x\"),\n", tn.target[:]) + fmt.Printf(" dists: [%d][]*ecdsa.PrivateKey{\n", len(tn.dists)) + for ld, ns := range tn.dists { + if len(ns) == 0 { + continue + } + fmt.Printf(" %d: {\n", ld) + for _, key := range ns { + fmt.Printf(" hexEncPrivkey(\"%x\"),\n", crypto.FromECDSA(key)) + } + fmt.Printf(" },\n") + } + fmt.Printf(" },\n") + fmt.Printf("}\n") +} diff --git a/p2p/discover/udp_test.go b/p2p/discover/v4_udp_test.go similarity index 82% rename from p2p/discover/udp_test.go rename to p2p/discover/v4_udp_test.go index de0f74ee7e..721cc075a4 100644 --- a/p2p/discover/udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -23,17 +23,18 @@ import ( "encoding/binary" "encoding/hex" "errors" - "fmt" "io" "math/rand" "net" - "path/filepath" "reflect" - "runtime" "sync" "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/internal/testlog" + + "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/davecgh/go-spew/spew" @@ -61,7 +62,7 @@ type udpTest struct { pipe *dgramPipe table *Table db *enode.DB - udp *udp + udp *UDPv4 sent [][]byte localkey, remotekey *ecdsa.PrivateKey remoteaddr *net.UDPAddr @@ -75,91 +76,93 @@ func newUDPTest(t *testing.T) *udpTest { remotekey: newkey(), remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 16789}, } + test.db, _ = enode.OpenDB("") ln := enode.NewLocalNode(test.db, test.localkey) - test.table, test.udp, _ = newUDP(test.pipe, ln, Config{PrivateKey: test.localkey}) + test.udp, _ = ListenV4(test.pipe, ln, Config{ + PrivateKey: test.localkey, + Log: testlog.Logger(t, log.LvlTrace), + }) + test.table = test.udp.tab // Wait for initial refresh so the table doesn't send unexpected findnode. <-test.table.initDone return test } func (test *udpTest) close() { - test.table.Close() + test.udp.Close() test.db.Close() } // handles a packet as if it had been sent to the transport. -func (test *udpTest) packetIn(wantError error, ptype byte, data packet) error { - return test.packetInFrom(wantError, test.remotekey, test.remoteaddr, ptype, data) +func (test *udpTest) packetIn(wantError error, ptype byte, data packetV4) { + test.t.Helper() + + test.packetInFrom(wantError, test.remotekey, test.remoteaddr, ptype, data) } // handles a packet as if it had been sent to the transport by the key/endpoint. -func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *net.UDPAddr, ptype byte, data packet) error { - enc, _, err := encodePacket(key, ptype, data) +func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *net.UDPAddr, ptype byte, data packetV4) { + test.t.Helper() + + enc, _, err := test.udp.encode(key, ptype, data) if err != nil { - return test.errorf("packet (%d) encode error: %v", ptype, err) + test.t.Errorf("packet (%d) encode error: %v", ptype, err) } test.sent = append(test.sent, enc) if err = test.udp.handlePacket(addr, enc); err != wantError { - return test.errorf("error mismatch: got %q, want %q", err, wantError) + test.t.Errorf("error mismatch: got %q, want %q", err, wantError) } - return nil } // waits for a packet to be sent by the transport. -// validate should have type func(*udpTest, X) error, where X is a packet type. -func (test *udpTest) waitPacketOut(validate interface{}) (*net.UDPAddr, []byte, error) { - dgram := test.pipe.waitPacketOut() - p, _, hash, err := decodePacket(dgram.data) +// validate should have type func(X, *net.UDPAddr, []byte), where X is a packet type. +func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) { + test.t.Helper() + + dgram, ok := test.pipe.receive() + if !ok { + return true + } + p, _, hash, err := decodeV4(dgram.data) if err != nil { - return &dgram.to, hash, test.errorf("sent packet decode error: %v", err) + test.t.Errorf("sent packet decode error: %v", err) + return false } fn := reflect.ValueOf(validate) exptype := fn.Type().In(0) - if reflect.TypeOf(p) != exptype { - return &dgram.to, hash, test.errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype) - } - fn.Call([]reflect.Value{reflect.ValueOf(p)}) - return &dgram.to, hash, nil -} - -func (test *udpTest) errorf(format string, args ...interface{}) error { - _, file, line, ok := runtime.Caller(2) // errorf + waitPacketOut - if ok { - file = filepath.Base(file) - } else { - file = "???" - line = 1 + if !reflect.TypeOf(p).AssignableTo(exptype) { + test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype) + return false } - err := fmt.Errorf(format, args...) - fmt.Printf("\t%s:%d: %v\n", file, line, err) - test.t.Fail() - return err + fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(&dgram.to), reflect.ValueOf(hash)}) + return false } -func TestUDP_packetErrors(t *testing.T) { +func TestUDPv4_packetErrors(t *testing.T) { test := newUDPTest(t) defer test.close() - test.packetIn(errExpired, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: 4, Rest: cRest}) - test.packetIn(errUnsolicitedReply, pongPacket, &pong{ReplyTok: []byte{}, Expiration: futureExp, Rest: cRest}) - test.packetIn(errUnknownNode, findnodePacket, &findnode{Expiration: futureExp, Rest: cRest}) - test.packetIn(errUnsolicitedReply, neighborsPacket, &neighbors{Expiration: futureExp, Rest: cRest}) + test.packetIn(errExpired, p_pingV4, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4}) + test.packetIn(errUnsolicitedReply, p_pongV4, &pongV4{ReplyTok: []byte{}, Expiration: futureExp}) + test.packetIn(errUnknownNode, p_findnodeV4, &findnodeV4{Expiration: futureExp}) + test.packetIn(errUnsolicitedReply, p_neighborsV4, &neighborsV4{Expiration: futureExp}) } -func TestUDP_pingTimeout(t *testing.T) { +func TestUDPv4_pingTimeout(t *testing.T) { t.Parallel() test := newUDPTest(t) defer test.close() + key := newkey() toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} - toid := enode.ID{1, 2, 3, 4} - if err := test.udp.ping(toid, toaddr); err != errTimeout { + node := enode.NewV4(&key.PublicKey, toaddr.IP, 0, toaddr.Port) + if err := test.udp.ping(node); err != errTimeout { t.Error("expected timeout error, got", err) } } -func TestUDP_responseTimeouts(t *testing.T) { +func TestUDPv4_responseTimeouts(t *testing.T) { t.Parallel() test := newUDPTest(t) defer test.close() @@ -231,7 +234,7 @@ func TestUDP_responseTimeouts(t *testing.T) { } } -func TestUDP_findnodeTimeout(t *testing.T) { +func TestUDPv4_findnodeTimeout(t *testing.T) { t.Parallel() test := newUDPTest(t) defer test.close() @@ -248,7 +251,7 @@ func TestUDP_findnodeTimeout(t *testing.T) { } } -func TestUDP_findnode(t *testing.T) { +func TestUDPv4_findnode(t *testing.T) { test := newUDPTest(t) defer test.close() @@ -277,10 +280,10 @@ func TestUDP_findnode(t *testing.T) { test.table.db.UpdateLastPongReceived(remoteID, test.remoteaddr.IP, time.Now()) // check that closest neighbors are returned. - expected := test.table.closest(testTarget.id(), bucketSize) - test.packetIn(nil, findnodePacket, &findnode{Target: testTarget, Expiration: futureExp, Rest: cRest}) + expected := test.table.closest(testTarget.id(), bucketSize, true) + test.packetIn(nil, p_findnodeV4, &findnodeV4{Target: testTarget, Expiration: futureExp}) waitNeighbors := func(want []*node) { - test.waitPacketOut(func(p *neighbors) { + test.waitPacketOut(func(p *neighborsV4, to *net.UDPAddr, hash []byte) { if len(p.Nodes) != len(want) { t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSize) } @@ -303,7 +306,7 @@ func TestUDP_findnode(t *testing.T) { waitNeighbors(want) } -func TestUDP_findnodeMultiReply(t *testing.T) { +func TestUDPv4_findnodeMultiReply(t *testing.T) { test := newUDPTest(t) defer test.close() @@ -324,7 +327,7 @@ func TestUDP_findnodeMultiReply(t *testing.T) { // wait for the findnode to be sent. // after it is sent, the transport is waiting for a reply - test.waitPacketOut(func(p *findnode) { + test.waitPacketOut(func(p *findnodeV4, to *net.UDPAddr, hash []byte) { if p.Target != testTarget { t.Errorf("wrong target: got %v, want %v", p.Target, testTarget) } @@ -341,8 +344,8 @@ func TestUDP_findnodeMultiReply(t *testing.T) { for i := range list { rpclist[i] = nodeToRPC(list[i]) } - test.packetIn(nil, neighborsPacket, &neighbors{Expiration: futureExp, Nodes: rpclist[:2], Rest: cRest}) - test.packetIn(nil, neighborsPacket, &neighbors{Expiration: futureExp, Nodes: rpclist[2:], Rest: cRest}) + test.packetIn(nil, p_neighborsV4, &neighborsV4{Expiration: futureExp, Nodes: rpclist[:2], Rest: cRest}) + test.packetIn(nil, p_neighborsV4, &neighborsV4{Expiration: futureExp, Nodes: rpclist[2:], Rest: cRest}) // check that the sent neighbors are all returned by findnode select { @@ -358,46 +361,47 @@ func TestUDP_findnodeMultiReply(t *testing.T) { } } -func TestUDP_pingMatch(t *testing.T) { +func TestUDPv4_pingMatch(t *testing.T) { test := newUDPTest(t) defer test.close() randToken := make([]byte, 32) crand.Read(randToken) - test.packetIn(nil, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) - test.waitPacketOut(func(*pong) error { return nil }) - test.waitPacketOut(func(*ping) error { return nil }) - test.packetIn(errUnsolicitedReply, pongPacket, &pong{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp}) + test.packetIn(nil, p_pingV4, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) + test.waitPacketOut(func(*pongV4, *net.UDPAddr, []byte) {}) + test.waitPacketOut(func(*pingV4, *net.UDPAddr, []byte) {}) + test.packetIn(errUnsolicitedReply, p_pongV4, &pongV4{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp}) } -func TestUDP_pingMatchIP(t *testing.T) { +func TestUDPv4_pingMatchIP(t *testing.T) { test := newUDPTest(t) defer test.close() - test.packetIn(nil, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) - test.waitPacketOut(func(*pong) error { return nil }) + test.packetIn(nil, p_pingV4, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) + test.waitPacketOut(func(*pongV4, *net.UDPAddr, []byte) {}) - _, hash, _ := test.waitPacketOut(func(*ping) error { return nil }) - wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 1, 2}, Port: 30000} - test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, pongPacket, &pong{ - ReplyTok: hash, - To: testLocalAnnounced, - Expiration: futureExp, + test.waitPacketOut(func(p *pingV4, to *net.UDPAddr, hash []byte) { + wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 1, 2}, Port: 30000} + test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, p_pongV4, &pongV4{ + ReplyTok: hash, + To: testLocalAnnounced, + Expiration: futureExp, + }) }) } -func TestUDP_successfulPing(t *testing.T) { +func TestUDPv4_successfulPing(t *testing.T) { test := newUDPTest(t) added := make(chan *node, 1) test.table.nodeAddedHook = func(n *node) { added <- n } defer test.close() // The remote side sends a ping packet to initiate the exchange. - go test.packetIn(nil, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp, Rest: cRest}) + go test.packetIn(nil, p_pingV4, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp, Rest: cRest}) // the ping is replied to. - test.waitPacketOut(func(p *pong) { + test.waitPacketOut(func(p *pongV4, to *net.UDPAddr, hash []byte) { pinghash := test.sent[0][:macSize] if !bytes.Equal(p.ReplyTok, pinghash) { t.Errorf("got pong.ReplyTok %x, want %x", p.ReplyTok, pinghash) @@ -414,7 +418,7 @@ func TestUDP_successfulPing(t *testing.T) { }) // remote is unknown, the table pings back. - _, hash, _ := test.waitPacketOut(func(p *ping) error { + test.waitPacketOut(func(p *pingV4, to *net.UDPAddr, hash []byte) { if !reflect.DeepEqual(p.From, test.udp.ourEndpoint()) { t.Errorf("got ping.From %#v, want %#v", p.From, test.udp.ourEndpoint()) } @@ -427,9 +431,8 @@ func TestUDP_successfulPing(t *testing.T) { if !reflect.DeepEqual(p.To, wantTo) { t.Errorf("got ping.To %v, want %v", p.To, wantTo) } - return nil + test.packetIn(nil, p_pongV4, &pongV4{ReplyTok: hash, Expiration: futureExp, Rest: cRest}) }) - test.packetIn(nil, pongPacket, &pong{ReplyTok: hash, Expiration: futureExp, Rest: cRest}) // the node should be added to the table shortly after getting the // pong packet. @@ -459,7 +462,7 @@ var testPackets = []struct { }{ { input: "71dbda3a79554728d4f94411e42ee1f8b0d561c10e1e5f5893367948c6a7d70bb87b235fa28a77070271b6c164a2dce8c7e13a5739b53b5e96f2e5acb0e458a02902f5965d55ecbeb2ebb6cabb8b2b232896a36b737666c55265ad0a68412f250001ea04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a355", - wantPacket: &ping{ + wantPacket: &pingV4{ Version: 4, From: rpcEndpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, To: rpcEndpoint{net.ParseIP("::1"), 2222, 3333}, @@ -469,7 +472,7 @@ var testPackets = []struct { }, { input: "e9614ccfd9fc3e74360018522d30e1419a143407ffcce748de3e22116b7e8dc92ff74788c0b6663aaa3d67d641936511c8f8d6ad8698b820a7cf9e1be7155e9a241f556658c55428ec0563514365799a4be2be5a685a80971ddcfa80cb422cdd0101ec04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a3550102", - wantPacket: &ping{ + wantPacket: &pingV4{ Version: 4, From: rpcEndpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, To: rpcEndpoint{net.ParseIP("::1"), 2222, 3333}, @@ -479,7 +482,7 @@ var testPackets = []struct { }, { input: "577be4349c4dd26768081f58de4c6f375a7a22f3f7adda654d1428637412c3d7fe917cadc56d4e5e7ffae1dbe3efffb9849feb71b262de37977e7c7a44e677295680e9e38ab26bee2fcbae207fba3ff3d74069a50b902a82c9903ed37cc993c50001f83e82022bd79020010db83c4d001500000000abcdef12820cfa8215a8d79020010db885a308d313198a2e037073488208ae82823a8443b9a355c5010203040531b9019afde696e582a78fa8d95ea13ce3297d4afb8ba6433e4154caa5ac6431af1b80ba76023fa4090c408f6b4bc3701562c031041d4702971d102c9ab7fa5eed4cd6bab8f7af956f7d565ee1917084a95398b6a21eac920fe3dd1345ec0a7ef39367ee69ddf092cbfe5b93e5e568ebc491983c09c76d922dc3", - wantPacket: &ping{ + wantPacket: &pingV4{ Version: 555, From: rpcEndpoint{net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 3322, 5544}, To: rpcEndpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338}, @@ -489,7 +492,7 @@ var testPackets = []struct { }, { input: "09b2428d83348d27cdf7064ad9024f526cebc19e4958f0fdad87c15eb598dd61d08423e0bf66b2069869e1724125f820d851c136684082774f870e614d95a2855d000f05d1648b2d5945470bc187c2d2216fbe870f43ed0909009882e176a46b0102f846d79020010db885a308d313198a2e037073488208ae82823aa0fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c9548443b9a355c6010203c2040506a0c969a58f6f9095004c0177a6b47f451530cab38966a25cca5cb58f055542124e", - wantPacket: &pong{ + wantPacket: &pongV4{ To: rpcEndpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338}, ReplyTok: common.Hex2Bytes("fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c954"), Expiration: 1136239445, @@ -498,7 +501,7 @@ var testPackets = []struct { }, { input: "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260add7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396", - wantPacket: &findnode{ + wantPacket: &findnodeV4{ Target: hexEncPubkey("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"), Expiration: 1136239445, Rest: []rlp.RawValue{{0x82, 0x99, 0x99}, {0x83, 0x99, 0x99, 0x99}}, @@ -506,7 +509,7 @@ var testPackets = []struct { }, { input: "c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db8403155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d313198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df738443b9a355010203b525a138aa34383fec3d2719a0", - wantPacket: &neighbors{ + wantPacket: &neighborsV4{ Nodes: []rpcNode{ { ID: hexEncPubkey("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"), @@ -539,7 +542,7 @@ var testPackets = []struct { }, } -func TestForwardCompatibility(t *testing.T) { +func TestUDPv4_forwardCompatibility(t *testing.T) { testkey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") wantNodeKey := encodePubkey(&testkey.PublicKey) @@ -548,7 +551,7 @@ func TestForwardCompatibility(t *testing.T) { if err != nil { t.Fatalf("invalid hex: %s", test.input) } - packet, nodekey, _, err := decodePacket(input) + packet, nodekey, _, err := decodeV4(input) if err != nil { t.Errorf("did not accept packet %s\n%v", test.input, err) continue @@ -612,6 +615,7 @@ func (c *dgramPipe) Close() error { close(c.closing) c.closed = true } + c.cond.Broadcast() return nil } @@ -619,14 +623,17 @@ func (c *dgramPipe) LocalAddr() net.Addr { return &net.UDPAddr{IP: testLocal.IP, Port: int(testLocal.UDP)} } -func (c *dgramPipe) waitPacketOut() dgram { +func (c *dgramPipe) receive() (dgram, bool) { c.mu.Lock() defer c.mu.Unlock() - for len(c.queue) == 0 { + for len(c.queue) == 0 && !c.closed { c.cond.Wait() } + if c.closed { + return dgram{}, false + } p := c.queue[0] copy(c.queue, c.queue[1:]) c.queue = c.queue[:len(c.queue)-1] - return p + return p, true } diff --git a/p2p/discv5/net.go b/p2p/discv5/net.go index d611bf47e8..49b1b64552 100644 --- a/p2p/discv5/net.go +++ b/p2p/discv5/net.go @@ -21,10 +21,11 @@ import ( "crypto/ecdsa" "errors" "fmt" - "golang.org/x/crypto/sha3" "net" "time" + "golang.org/x/crypto/sha3" + "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/common/mclock" "github.com/AlayaNetwork/Alaya-Go/crypto" diff --git a/p2p/message.go b/p2p/message.go index 594f7c3e7a..3ddb33eee5 100644 --- a/p2p/message.go +++ b/p2p/message.go @@ -253,9 +253,9 @@ func ExpectMsg(r MsgReader, code uint64, content interface{}) error { type msgEventer struct { MsgReadWriter - feed *event.Feed - peerID enode.ID - Protocol string + feed *event.Feed + peerID enode.ID + Protocol string localAddress string remoteAddress string } diff --git a/p2p/peer.go b/p2p/peer.go index a2053cca31..fe34f1e074 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -92,14 +92,14 @@ const ( // 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 type PeerEvent struct { - Type PeerEventType `json:"type"` - Peer enode.ID `json:"peer"` - Error string `json:"error,omitempty"` - Protocol string `json:"protocol,omitempty"` - MsgCode *uint64 `json:"msg_code,omitempty"` - MsgSize *uint32 `json:"msg_size,omitempty"` - LocalAddress string `json:"local,omitempty"` - RemoteAddress string `json:"remote,omitempty"` + Type PeerEventType `json:"type"` + Peer enode.ID `json:"peer"` + Error string `json:"error,omitempty"` + Protocol string `json:"protocol,omitempty"` + MsgCode *uint64 `json:"msg_code,omitempty"` + MsgSize *uint32 `json:"msg_size,omitempty"` + LocalAddress string `json:"local,omitempty"` + RemoteAddress string `json:"remote,omitempty"` } // Peer represents a connected remote node. diff --git a/p2p/protocols/protocol.go b/p2p/protocols/protocol.go index ff231e53aa..af5bc1243d 100644 --- a/p2p/protocols/protocol.go +++ b/p2p/protocols/protocol.go @@ -241,7 +241,7 @@ func (p *Peer) Run(handler func(ctx context.Context, msg interface{}) error) err // Drop disconnects a peer. // TODO: may need to implement protocol drop only? don't want to kick off the peer // if they are useful for other protocols -func (p *Peer) Drop(err error) { +func (p *Peer) Drop() { p.Disconnect(p2p.DiscSubprotocolError) } @@ -252,6 +252,7 @@ func (p *Peer) Drop(err error) { func (p *Peer) Send(ctx context.Context, msg interface{}) error { defer metrics.GetOrRegisterResettingTimer("peer.send_t", nil).UpdateSince(time.Now()) metrics.GetOrRegisterCounter("peer.send", nil).Inc(1) + metrics.GetOrRegisterCounter(fmt.Sprintf("peer.send.%T", msg), nil).Inc(1) var b bytes.Buffer /*if tracing.Enabled { @@ -289,7 +290,7 @@ func (p *Peer) Send(ctx context.Context, msg interface{}) error { if p.spec.Hook != nil { err := p.spec.Hook.Send(p, wmsg.Size, msg) if err != nil { - p.Drop(err) + p.Drop() return err } } diff --git a/p2p/protocols/protocol_test.go b/p2p/protocols/protocol_test.go index d5d91e48cf..3b55f9f34f 100644 --- a/p2p/protocols/protocol_test.go +++ b/p2p/protocols/protocol_test.go @@ -25,6 +25,8 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/rlp" @@ -125,7 +127,7 @@ func newProtocol(pp *p2ptest.TestPeerPool) func(*p2p.Peer, p2p.MsgReadWriter) er case *kill: // demonstrates use of peerPool, killing another peer connection as a response to a message id := msg.C - pp.Get(id).Drop(errors.New("killed")) + pp.Get(id).Drop() return nil case *drop: @@ -144,8 +146,11 @@ func newProtocol(pp *p2ptest.TestPeerPool) func(*p2p.Peer, p2p.MsgReadWriter) er } func protocolTester(pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTester { - conf := adapters.RandomNodeConfig() - return p2ptest.NewProtocolTester(conf.ID, 2, newProtocol(pp)) + prvkey, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + return p2ptest.NewProtocolTester(prvkey, 2, newProtocol(pp)) } func protoHandshakeExchange(id enode.ID, proto *protoHandshake) []p2ptest.Exchange { @@ -260,9 +265,13 @@ func TestProtocolHook(t *testing.T) { return peer.Run(handle) } - conf := adapters.RandomNodeConfig() - tester := p2ptest.NewProtocolTester(conf.ID, 2, runFunc) - err := tester.TestExchanges(p2ptest.Exchange{ + prvkey, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + tester := p2ptest.NewProtocolTester(prvkey, 2, runFunc) + defer tester.Stop() + err = tester.TestExchanges(p2ptest.Exchange{ Expects: []p2ptest.Expect{ { Code: 0, diff --git a/p2p/rlpx.go b/p2p/rlpx.go index eee76f5730..8b8f82e805 100644 --- a/p2p/rlpx.go +++ b/p2p/rlpx.go @@ -27,7 +27,6 @@ import ( "encoding/binary" "errors" "fmt" - "golang.org/x/crypto/sha3" "hash" "io" "io/ioutil" @@ -36,6 +35,8 @@ import ( "sync" "time" + "golang.org/x/crypto/sha3" + "github.com/AlayaNetwork/Alaya-Go/common/bitutil" "github.com/AlayaNetwork/Alaya-Go/log" diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index 19158340db..13bd4f6d50 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -94,6 +94,10 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { return nil, fmt.Errorf("error creating node directory: %s", err) } + err := config.initDummyEnode() + if err != nil { + return nil, err + } // generate the config conf := &execNodeConfig{ Stack: node.DefaultConfig, @@ -415,6 +419,13 @@ func startExecNodeStack() (*node.Node, error) { if err := json.Unmarshal([]byte(confEnv), &conf); err != nil { return nil, fmt.Errorf("error decoding %s: %v", envNodeConfig, err) } + + // create enode record + nodeTcpConn, err := net.ResolveTCPAddr("tcp", conf.Stack.P2P.ListenAddr) + if nodeTcpConn.IP == nil { + nodeTcpConn.IP = net.IPv4(127, 0, 0, 1) + } + conf.Node.initEnode(nodeTcpConn.IP, nodeTcpConn.Port, nodeTcpConn.Port) conf.Stack.P2P.PrivateKey = conf.Node.PrivateKey conf.Stack.Logger = log.New("node.id", conf.Node.ID.String()) diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index 04f459e1d8..b03c346f1c 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -73,8 +73,13 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) { s.mtx.Lock() defer s.mtx.Unlock() - // check a node with the ID doesn't already exist id := config.ID + // verify that the node has a private key in the config + if config.PrivateKey == nil { + return nil, fmt.Errorf("node is missing private key: %s", id) + } + + // check a node with the ID doesn't already exist if _, exists := s.nodes[id]; exists { return nil, fmt.Errorf("node already exists: %s", id) } @@ -89,6 +94,11 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) { } } + err := config.initDummyEnode() + if err != nil { + return nil, err + } + n, err := node.New(&node.Config{ P2P: p2p.Config{ PrivateKey: config.PrivateKey, @@ -132,7 +142,7 @@ func (s *SimAdapter) Dial(dest *enode.Node) (conn net.Conn, err error) { return nil, err } // this is simulated 'listening' - // asynchronously call the dialed destintion node's p2p server + // asynchronously call the dialed destination node's p2p server // to set up connection on the 'listening' side go srv.SetupConn(pipe1, 0, nil) return pipe2, nil diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go index 05f4b3baf8..c801ad9fcc 100644 --- a/p2p/simulations/adapters/types.go +++ b/p2p/simulations/adapters/types.go @@ -25,6 +25,9 @@ import ( "os" "strconv" + "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/docker/docker/pkg/reexec" @@ -103,6 +106,12 @@ type NodeConfig struct { // services registered by calling the RegisterService function) Services []string + // Enode + node *enode.Node + + // ENR Record with entries to overwrite + Record enr.Record + // function to sanction or prevent suggesting a peer Reachable func(id enode.ID) bool @@ -172,26 +181,27 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error { // 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)) + return n.node } // RandomNodeConfig returns node configuration with a randomly generated ID and // PrivateKey func RandomNodeConfig() *NodeConfig { - key, err := crypto.GenerateKey() + prvkey, err := crypto.GenerateKey() if err != nil { panic("unable to generate key") } - id := enode.PubkeyToIDV4(&key.PublicKey) port, err := assignTCPPort() if err != nil { panic("unable to assign tcp port") } + + enodId := enode.PubkeyToIDV4(&prvkey.PublicKey) return &NodeConfig{ - ID: id, - Name: fmt.Sprintf("node_%s", id.String()), - PrivateKey: key, + PrivateKey: prvkey, + ID: enodId, + Name: fmt.Sprintf("node_%s", enodId.String()), Port: port, EnableMsgEvents: true, } @@ -261,3 +271,30 @@ func RegisterServices(services Services) { os.Exit(0) } } + +// adds the host part to the configuration's ENR, signs it +// creates and the corresponding enode object to the configuration +func (n *NodeConfig) initEnode(ip net.IP, tcpport int, udpport int) error { + enrIp := enr.IP(ip) + n.Record.Set(&enrIp) + enrTcpPort := enr.TCP(tcpport) + n.Record.Set(&enrTcpPort) + enrUdpPort := enr.UDP(udpport) + n.Record.Set(&enrUdpPort) + + err := enode.SignV4(&n.Record, n.PrivateKey) + if err != nil { + return fmt.Errorf("unable to generate ENR: %v", err) + } + nod, err := enode.New(enode.V4ID{}, &n.Record) + if err != nil { + return fmt.Errorf("unable to create enode: %v", err) + } + log.Trace("simnode new", "record", n.Record) + n.node = nod + return nil +} + +func (n *NodeConfig) initDummyEnode() error { + return n.initEnode(net.IPv4(127, 0, 0, 1), 0, 0) +} diff --git a/p2p/testing/peerpool.go b/p2p/testing/peerpool.go index 59e709c292..8d31cf6c66 100644 --- a/p2p/testing/peerpool.go +++ b/p2p/testing/peerpool.go @@ -27,7 +27,7 @@ import ( type TestPeer interface { ID() enode.ID - Drop(error) + Drop() } // TestPeerPool is an example peerPool to demonstrate registration of peer connections diff --git a/p2p/testing/protocoltester.go b/p2p/testing/protocoltester.go index 60ba2945b2..1be56c026e 100644 --- a/p2p/testing/protocoltester.go +++ b/p2p/testing/protocoltester.go @@ -25,6 +25,7 @@ package testing import ( "bytes" + "crypto/ecdsa" "fmt" "io" "io/ioutil" @@ -51,7 +52,7 @@ type ProtocolTester struct { // NewProtocolTester constructs a new ProtocolTester // 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 -func NewProtocolTester(id enode.ID, nodeCount int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester { +func NewProtocolTester(prvkey *ecdsa.PrivateKey, nodeCount int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester { services := adapters.Services{ "test": func(ctx *adapters.ServiceContext) (node.Service, error) { return &testNode{run}, nil @@ -62,23 +63,30 @@ func NewProtocolTester(id enode.ID, nodeCount int, run func(*p2p.Peer, p2p.MsgRe } adapter := adapters.NewSimAdapter(services) net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{}) - if _, err := net.NewNodeWithConfig(&adapters.NodeConfig{ - ID: id, + nodeConfig := &adapters.NodeConfig{ + PrivateKey: prvkey, EnableMsgEvents: true, Services: []string{"test"}, - }); err != nil { + } + if _, err := net.NewNodeWithConfig(nodeConfig); err != nil { panic(err.Error()) } - if err := net.Start(id); err != nil { + if err := net.Start(nodeConfig.ID); err != nil { panic(err.Error()) } - node := net.GetNode(id).Node.(*adapters.SimNode) + node := net.GetNode(nodeConfig.ID).Node.(*adapters.SimNode) peers := make([]*adapters.NodeConfig, nodeCount) nodes := make([]*enode.Node, nodeCount) for i := 0; i < nodeCount; i++ { peers[i] = adapters.RandomNodeConfig() peers[i].Services = []string{"mock"} + if _, err := net.NewNodeWithConfig(peers[i]); err != nil { + panic(fmt.Sprintf("error initializing peer %v: %v", peers[i].ID, err)) + } + if err := net.Start(peers[i].ID); err != nil { + panic(fmt.Sprintf("error starting peer %v: %v", peers[i].ID, err)) + } nodes[i] = peers[i].Node() } events := make(chan *p2p.PeerEvent, 1000) @@ -94,7 +102,7 @@ func NewProtocolTester(id enode.ID, nodeCount int, run func(*p2p.Peer, p2p.MsgRe network: net, } - self.Connect(id, peers...) + self.Connect(nodeConfig.ID, peers...) return self } @@ -102,19 +110,13 @@ func NewProtocolTester(id enode.ID, nodeCount int, run func(*p2p.Peer, p2p.MsgRe // Stop stops the p2p server func (t *ProtocolTester) Stop() { t.Server.Stop() + t.network.Shutdown() } // Connect brings up the remote peer node and connects it using the // p2p/simulations network connection with the in memory network adapter func (t *ProtocolTester) Connect(selfID enode.ID, peers ...*adapters.NodeConfig) { for _, peer := range peers { - log.Trace(fmt.Sprintf("start node %v", peer.ID)) - if _, err := t.network.NewNodeWithConfig(peer); err != nil { - panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err)) - } - if err := t.network.Start(peer.ID); err != nil { - panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err)) - } log.Trace(fmt.Sprintf("connect to %v", peer.ID)) if err := t.network.Connect(selfID, peer.ID); err != nil { panic(fmt.Sprintf("error connecting to peer %v: %v", peer.ID, err)) diff --git a/x/gov/gov_db_test.go b/x/gov/gov_db_test.go index d3518ca9b2..52b44a23a5 100644 --- a/x/gov/gov_db_test.go +++ b/x/gov/gov_db_test.go @@ -14,15 +14,15 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package gov import ( "bytes" "fmt" - "golang.org/x/crypto/sha3" "math/big" + "golang.org/x/crypto/sha3" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/log" From 14a0069cea1ca0c69fa2abe19ad2d1dafca3cf7c Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 26 Oct 2021 09:54:17 +0800 Subject: [PATCH 008/299] update for p2p --- build/deb/ethereum-swarm/deb.changelog | 5 - build/deb/ethereum-swarm/deb.control | 19 - build/deb/ethereum-swarm/deb.copyright | 14 - build/deb/ethereum-swarm/deb.docs | 1 - build/deb/ethereum-swarm/deb.install | 1 - build/deb/ethereum-swarm/deb.rules | 13 - build/deb/ethereum/deb.control | 4 +- build/update-license.go | 3 +- cmd/bootnode/main.go | 16 +- cmd/ctool/test/addr.json | 20 +- cmd/ctool/test/privateKeys.txt | Bin 132109 -> 135557 bytes cmd/devp2p/discv4cmd.go | 167 + cmd/devp2p/enrcmd.go | 199 ++ cmd/devp2p/main.go | 69 + cmd/faucet/faucet.go | 23 +- cmd/utils/flags.go | 21 +- contracts/chequebook/api.go | 68 - contracts/chequebook/cheque.go | 604 ---- contracts/chequebook/cheque_test.go | 488 --- contracts/chequebook/contract/chequebook.go | 367 -- contracts/chequebook/contract/chequebook.sol | 47 - contracts/chequebook/contract/code.go | 5 - contracts/chequebook/contract/mortal.sol | 10 - contracts/chequebook/contract/owned.sol | 15 - contracts/chequebook/gencode.go | 70 - contracts/ens/README.md | 20 - contracts/ens/contract/AbstractENS.sol | 23 - contracts/ens/contract/ENS.sol | 94 - contracts/ens/contract/FIFSRegistrar.sol | 39 - contracts/ens/contract/PublicResolver.sol | 212 -- contracts/ens/contract/ens.go | 879 ----- contracts/ens/contract/fifsregistrar.go | 195 -- contracts/ens/contract/publicresolver.go | 1321 ------- contracts/ens/ens.go | 195 -- contracts/ens/ens_test.go | 70 - crypto/crypto.go | 6 +- ethstats/ethstats.go | 32 +- event/subscription.go | 17 +- event/subscription_test.go | 2 +- les/api_backend.go | 223 -- les/backend.go | 242 -- les/bloombits.go | 74 - les/commons.go | 104 - les/distributor.go | 283 -- les/distributor_test.go | 185 - les/execqueue.go | 97 - les/execqueue_test.go | 62 - les/fetcher.go | 780 ----- les/flowcontrol/control.go | 184 - les/flowcontrol/manager.go | 224 -- les/freeclient.go | 278 -- les/freeclient_test.go | 140 - les/handler.go | 1249 ------- les/handler_test.go | 580 ---- les/helper_test.go | 440 --- les/metrics.go | 111 - les/odr.go | 129 - les/odr_requests.go | 560 --- les/odr_test.go | 207 -- les/peer.go | 652 ---- les/protocol.go | 226 -- les/randselect.go | 170 - les/randselect_test.go | 67 - les/request_test.go | 122 - les/retrieve.go | 414 --- les/server.go | 380 --- les/serverpool.go | 856 ----- les/sync.go | 78 - les/txrelay.go | 175 - light/lightchain.go | 530 --- light/lightchain_test.go | 298 -- light/nodeset.go | 162 - light/odr.go | 175 - light/odr_test.go | 318 -- light/odr_util.go | 229 -- light/postprocess.go | 388 --- light/trie.go | 255 -- light/trie_test.go | 82 - light/txpool.go | 529 --- light/txpool_test.go | 130 - mobile/discover.go | 12 +- mobile/params.go | 11 +- mobile/platon.go | 11 +- node/api.go | 4 +- node/config.go | 2 +- node/node_test.go | 2 +- p2p/dial.go | 777 +++-- p2p/dial_test.go | 1180 ++++--- p2p/discover/common.go | 36 +- p2p/discover/lookup.go | 226 ++ p2p/discover/node.go | 22 +- p2p/discover/table.go | 143 +- p2p/discover/table_test.go | 34 +- p2p/discover/table_util_test.go | 102 +- ...4_udp_lookup_test.go => v4_lookup_test.go} | 172 +- p2p/discover/v4_udp.go | 772 ++--- p2p/discover/v4_udp_test.go | 374 +- p2p/discover/v4wire/v4wire.go | 293 ++ p2p/discover/v4wire/v4wire_test.go | 133 + p2p/discover/v5_udp.go | 859 +++++ p2p/discover/v5_udp_test.go | 810 +++++ p2p/discover/v5wire/crypto.go | 181 + p2p/discover/v5wire/crypto_test.go | 124 + p2p/discover/v5wire/encoding.go | 648 ++++ p2p/discover/v5wire/encoding_test.go | 634 ++++ p2p/discover/v5wire/msg.go | 249 ++ p2p/discover/v5wire/session.go | 143 + .../testdata/v5.1-ping-handshake-enr.txt | 27 + .../v5wire/testdata/v5.1-ping-handshake.txt | 23 + .../v5wire/testdata/v5.1-ping-message.txt | 10 + .../v5wire/testdata/v5.1-whoareyou.txt | 9 + p2p/discv5/database.go | 411 --- p2p/discv5/database_test.go | 380 --- p2p/discv5/metrics.go | 8 - p2p/discv5/net.go | 1273 ------- p2p/discv5/net_test.go | 374 -- p2p/discv5/node.go | 438 --- p2p/discv5/node_test.go | 307 -- p2p/discv5/nodeevent_string.go | 17 - p2p/discv5/ntp.go | 126 - p2p/discv5/sim_run_test.go | 126 - p2p/discv5/sim_test.go | 456 --- p2p/discv5/table.go | 322 -- p2p/discv5/table_test.go | 337 -- p2p/discv5/ticket.go | 954 ------ p2p/discv5/topic.go | 407 --- p2p/discv5/topic_test.go | 72 - p2p/discv5/udp.go | 445 --- p2p/discv5/udp_test.go | 450 --- p2p/enode/iter.go | 288 ++ p2p/enode/iter_test.go | 291 ++ p2p/enode/localnode.go | 163 +- p2p/enode/localnode_test.go | 70 +- p2p/enode/node.go | 94 +- p2p/enode/node_test.go | 87 +- p2p/enode/nodedb.go | 60 +- p2p/enode/nodedb_test.go | 11 + p2p/enode/urlv4.go | 37 +- p2p/enode/urlv4_test.go | 204 +- p2p/enr/enr.go | 37 +- p2p/enr/enr_test.go | 51 +- p2p/enr/entries.go | 76 +- p2p/message.go | 14 +- p2p/message_test.go | 22 - p2p/metrics.go | 175 +- p2p/nat/nat.go | 9 +- p2p/nat/natupnp.go | 80 +- p2p/nat/natupnp_test.go | 7 +- .../addrutil.go} | 38 +- p2p/netutil/error.go | 8 + p2p/netutil/net.go | 2 +- p2p/peer.go | 80 +- p2p/peer_error.go | 2 +- p2p/peer_test.go | 62 +- p2p/protocol.go | 7 +- p2p/protocols/accounting.go | 182 - p2p/protocols/accounting_api.go | 94 - p2p/protocols/accounting_simulation_test.go | 323 -- p2p/protocols/accounting_test.go | 223 -- p2p/protocols/protocol.go | 441 --- p2p/protocols/protocol_test.go | 625 ---- p2p/protocols/reporter.go | 162 - p2p/protocols/repoter_test.go | 83 - p2p/rlpx.go | 753 ---- p2p/rlpx/buffer.go | 127 + p2p/rlpx/buffer_test.go | 52 + p2p/rlpx/rlpx.go | 678 ++++ p2p/rlpx/rlpx_test.go | 455 +++ p2p/rlpx_test.go | 603 ---- p2p/server.go | 672 ++-- p2p/server_test.go | 527 ++- p2p/simulations/adapters/exec.go | 13 +- p2p/simulations/adapters/inproc.go | 5 +- p2p/simulations/adapters/inproc_test.go | 269 +- p2p/simulations/adapters/types.go | 10 +- p2p/simulations/base/000544.ldb | Bin 0 -> 252 bytes p2p/simulations/base/000545.ldb | Bin 0 -> 252 bytes p2p/simulations/base/000548.ldb | Bin 0 -> 252 bytes p2p/simulations/base/000551.ldb | Bin 0 -> 252 bytes p2p/simulations/base/000554.ldb | Bin 0 -> 252 bytes p2p/simulations/base/000557.ldb | Bin 0 -> 252 bytes p2p/simulations/base/000560.ldb | Bin 0 -> 252 bytes p2p/simulations/base/000563.ldb | Bin 0 -> 252 bytes p2p/simulations/base/000566.ldb | Bin 0 -> 252 bytes p2p/simulations/base/000569.ldb | Bin 0 -> 252 bytes p2p/simulations/base/000572.ldb | Bin 0 -> 252 bytes p2p/simulations/base/000575.ldb | Bin 0 -> 252 bytes p2p/simulations/base/000576.log | Bin 0 -> 158 bytes p2p/simulations/base/000578.ldb | Bin 0 -> 252 bytes p2p/simulations/base/CURRENT | 1 + p2p/simulations/base/CURRENT.bak | 1 + p2p/simulations/base/LOCK | 0 p2p/simulations/base/LOG | 3039 +++++++++++++++++ p2p/simulations/base/MANIFEST-000577 | Bin 0 -> 1116 bytes p2p/simulations/events.go | 3 +- p2p/simulations/http.go | 9 +- p2p/simulations/http_test.go | 25 +- p2p/simulations/mocker_test.go | 21 +- p2p/simulations/network.go | 153 +- p2p/simulations/network_test.go | 333 +- p2p/testing/protocolsession.go | 1 + p2p/transport.go | 183 + p2p/transport_test.go | 149 + p2p/util.go | 85 + p2p/util_test.go | 56 + params/config.go | 2 +- rlp/decode.go | 453 ++- rlp/decode_test.go | 372 +- rlp/doc.go | 152 +- rlp/encode.go | 374 +- rlp/encode_test.go | 221 +- rlp/encoder_example_test.go | 12 +- rlp/iterator.go | 60 + rlp/iterator_test.go | 59 + rlp/raw.go | 105 + rlp/raw_test.go | 93 +- rlp/safe.go | 27 + rlp/typecache.go | 200 +- rlp/unsafe.go | 35 + 219 files changed, 15965 insertions(+), 31199 deletions(-) delete mode 100644 build/deb/ethereum-swarm/deb.changelog delete mode 100644 build/deb/ethereum-swarm/deb.control delete mode 100644 build/deb/ethereum-swarm/deb.copyright delete mode 100644 build/deb/ethereum-swarm/deb.docs delete mode 100644 build/deb/ethereum-swarm/deb.install delete mode 100644 build/deb/ethereum-swarm/deb.rules create mode 100644 cmd/devp2p/discv4cmd.go create mode 100644 cmd/devp2p/enrcmd.go create mode 100644 cmd/devp2p/main.go delete mode 100644 contracts/chequebook/api.go delete mode 100644 contracts/chequebook/cheque.go delete mode 100644 contracts/chequebook/cheque_test.go delete mode 100644 contracts/chequebook/contract/chequebook.go delete mode 100644 contracts/chequebook/contract/chequebook.sol delete mode 100644 contracts/chequebook/contract/code.go delete mode 100644 contracts/chequebook/contract/mortal.sol delete mode 100644 contracts/chequebook/contract/owned.sol delete mode 100644 contracts/chequebook/gencode.go delete mode 100644 contracts/ens/README.md delete mode 100644 contracts/ens/contract/AbstractENS.sol delete mode 100644 contracts/ens/contract/ENS.sol delete mode 100644 contracts/ens/contract/FIFSRegistrar.sol delete mode 100644 contracts/ens/contract/PublicResolver.sol delete mode 100644 contracts/ens/contract/ens.go delete mode 100644 contracts/ens/contract/fifsregistrar.go delete mode 100644 contracts/ens/contract/publicresolver.go delete mode 100644 contracts/ens/ens.go delete mode 100644 contracts/ens/ens_test.go delete mode 100644 les/api_backend.go delete mode 100644 les/backend.go delete mode 100644 les/bloombits.go delete mode 100644 les/commons.go delete mode 100644 les/distributor.go delete mode 100644 les/distributor_test.go delete mode 100644 les/execqueue.go delete mode 100644 les/execqueue_test.go delete mode 100644 les/fetcher.go delete mode 100644 les/flowcontrol/control.go delete mode 100644 les/flowcontrol/manager.go delete mode 100644 les/freeclient.go delete mode 100644 les/freeclient_test.go delete mode 100644 les/handler.go delete mode 100644 les/handler_test.go delete mode 100644 les/helper_test.go delete mode 100644 les/metrics.go delete mode 100644 les/odr.go delete mode 100644 les/odr_requests.go delete mode 100644 les/odr_test.go delete mode 100644 les/peer.go delete mode 100644 les/protocol.go delete mode 100644 les/randselect.go delete mode 100644 les/randselect_test.go delete mode 100644 les/request_test.go delete mode 100644 les/retrieve.go delete mode 100644 les/server.go delete mode 100644 les/serverpool.go delete mode 100644 les/sync.go delete mode 100644 les/txrelay.go delete mode 100644 light/lightchain.go delete mode 100644 light/lightchain_test.go delete mode 100644 light/nodeset.go delete mode 100644 light/odr.go delete mode 100644 light/odr_test.go delete mode 100644 light/odr_util.go delete mode 100644 light/postprocess.go delete mode 100644 light/trie.go delete mode 100644 light/trie_test.go delete mode 100644 light/txpool.go delete mode 100644 light/txpool_test.go create mode 100644 p2p/discover/lookup.go rename p2p/discover/{v4_udp_lookup_test.go => v4_lookup_test.go} (64%) create mode 100644 p2p/discover/v4wire/v4wire.go create mode 100644 p2p/discover/v4wire/v4wire_test.go create mode 100644 p2p/discover/v5_udp.go create mode 100644 p2p/discover/v5_udp_test.go create mode 100644 p2p/discover/v5wire/crypto.go create mode 100644 p2p/discover/v5wire/crypto_test.go create mode 100644 p2p/discover/v5wire/encoding.go create mode 100644 p2p/discover/v5wire/encoding_test.go create mode 100644 p2p/discover/v5wire/msg.go create mode 100644 p2p/discover/v5wire/session.go create mode 100644 p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt create mode 100644 p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt create mode 100644 p2p/discover/v5wire/testdata/v5.1-ping-message.txt create mode 100644 p2p/discover/v5wire/testdata/v5.1-whoareyou.txt delete mode 100644 p2p/discv5/database.go delete mode 100644 p2p/discv5/database_test.go delete mode 100644 p2p/discv5/metrics.go delete mode 100644 p2p/discv5/net.go delete mode 100644 p2p/discv5/net_test.go delete mode 100644 p2p/discv5/node.go delete mode 100644 p2p/discv5/node_test.go delete mode 100644 p2p/discv5/nodeevent_string.go delete mode 100644 p2p/discv5/ntp.go delete mode 100644 p2p/discv5/sim_run_test.go delete mode 100644 p2p/discv5/sim_test.go delete mode 100644 p2p/discv5/table.go delete mode 100644 p2p/discv5/table_test.go delete mode 100644 p2p/discv5/ticket.go delete mode 100644 p2p/discv5/topic.go delete mode 100644 p2p/discv5/topic_test.go delete mode 100644 p2p/discv5/udp.go delete mode 100644 p2p/discv5/udp_test.go create mode 100644 p2p/enode/iter.go create mode 100644 p2p/enode/iter_test.go rename p2p/{discv5/sim_testmain_test.go => netutil/addrutil.go} (56%) delete mode 100644 p2p/protocols/accounting.go delete mode 100644 p2p/protocols/accounting_api.go delete mode 100644 p2p/protocols/accounting_simulation_test.go delete mode 100644 p2p/protocols/accounting_test.go delete mode 100644 p2p/protocols/protocol.go delete mode 100644 p2p/protocols/protocol_test.go delete mode 100644 p2p/protocols/reporter.go delete mode 100644 p2p/protocols/repoter_test.go delete mode 100644 p2p/rlpx.go create mode 100644 p2p/rlpx/buffer.go create mode 100644 p2p/rlpx/buffer_test.go create mode 100644 p2p/rlpx/rlpx.go create mode 100644 p2p/rlpx/rlpx_test.go delete mode 100644 p2p/rlpx_test.go create mode 100644 p2p/simulations/base/000544.ldb create mode 100644 p2p/simulations/base/000545.ldb create mode 100644 p2p/simulations/base/000548.ldb create mode 100644 p2p/simulations/base/000551.ldb create mode 100644 p2p/simulations/base/000554.ldb create mode 100644 p2p/simulations/base/000557.ldb create mode 100644 p2p/simulations/base/000560.ldb create mode 100644 p2p/simulations/base/000563.ldb create mode 100644 p2p/simulations/base/000566.ldb create mode 100644 p2p/simulations/base/000569.ldb create mode 100644 p2p/simulations/base/000572.ldb create mode 100644 p2p/simulations/base/000575.ldb create mode 100644 p2p/simulations/base/000576.log create mode 100644 p2p/simulations/base/000578.ldb create mode 100644 p2p/simulations/base/CURRENT create mode 100644 p2p/simulations/base/CURRENT.bak create mode 100644 p2p/simulations/base/LOCK create mode 100644 p2p/simulations/base/LOG create mode 100644 p2p/simulations/base/MANIFEST-000577 create mode 100644 p2p/transport.go create mode 100644 p2p/transport_test.go create mode 100644 p2p/util.go create mode 100644 p2p/util_test.go create mode 100644 rlp/iterator.go create mode 100644 rlp/iterator_test.go create mode 100644 rlp/safe.go create mode 100644 rlp/unsafe.go diff --git a/build/deb/ethereum-swarm/deb.changelog b/build/deb/ethereum-swarm/deb.changelog deleted file mode 100644 index 83f804a833..0000000000 --- a/build/deb/ethereum-swarm/deb.changelog +++ /dev/null @@ -1,5 +0,0 @@ -{{.Name}} ({{.VersionString}}) {{.Distro}}; urgency=low - - * git build of {{.Env.Commit}} - - -- {{.Author}} {{.Time}} diff --git a/build/deb/ethereum-swarm/deb.control b/build/deb/ethereum-swarm/deb.control deleted file mode 100644 index 2e799f9b99..0000000000 --- a/build/deb/ethereum-swarm/deb.control +++ /dev/null @@ -1,19 +0,0 @@ -Source: {{.Name}} -Section: science -Priority: extra -Maintainer: {{.Author}} -Build-Depends: debhelper (>= 8.0.0), golang-1.16 -Standards-Version: 3.9.5 -Homepage: https://ethereum.org -Vcs-Git: git://github.com/ethereum/go-ethereum.git -Vcs-Browser: https://github.com/ethereum/go-ethereum - -{{range .Executables}} -Package: {{$.ExeName .}} -Conflicts: {{$.ExeConflicts .}} -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends} -Built-Using: ${misc:Built-Using} -Description: {{.Description}} - {{.Description}} -{{end}} diff --git a/build/deb/ethereum-swarm/deb.copyright b/build/deb/ethereum-swarm/deb.copyright deleted file mode 100644 index fe6e36ad9d..0000000000 --- a/build/deb/ethereum-swarm/deb.copyright +++ /dev/null @@ -1,14 +0,0 @@ -Copyright 2018 The go-ethereum Authors - -go-ethereum is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -go-ethereum 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 General Public License for more details. - -You should have received a copy of the GNU General Public License -along with go-ethereum. If not, see . diff --git a/build/deb/ethereum-swarm/deb.docs b/build/deb/ethereum-swarm/deb.docs deleted file mode 100644 index 62deb04972..0000000000 --- a/build/deb/ethereum-swarm/deb.docs +++ /dev/null @@ -1 +0,0 @@ -AUTHORS diff --git a/build/deb/ethereum-swarm/deb.install b/build/deb/ethereum-swarm/deb.install deleted file mode 100644 index e7666ce5fb..0000000000 --- a/build/deb/ethereum-swarm/deb.install +++ /dev/null @@ -1 +0,0 @@ -build/bin/{{.BinaryName}} usr/bin diff --git a/build/deb/ethereum-swarm/deb.rules b/build/deb/ethereum-swarm/deb.rules deleted file mode 100644 index 8138e65753..0000000000 --- a/build/deb/ethereum-swarm/deb.rules +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/make -f -# -*- makefile -*- - -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - -override_dh_auto_build: - build/env.sh /usr/lib/go-1.16/bin/go run build/ci.go install -git-commit={{.Env.Commit}} -git-branch={{.Env.Branch}} -git-tag={{.Env.Tag}} -buildnum={{.Env.Buildnum}} -pull-request={{.Env.IsPullRequest}} - -override_dh_auto_test: - -%: - dh $@ diff --git a/build/deb/ethereum/deb.control b/build/deb/ethereum/deb.control index 31af3dd431..6344730c63 100644 --- a/build/deb/ethereum/deb.control +++ b/build/deb/ethereum/deb.control @@ -11,8 +11,8 @@ Vcs-Browser: https://github.com/ethereum/go-ethereum Package: {{.Name}} Architecture: any Depends: ${misc:Depends}, {{.ExeList}} -Description: Meta-package to install geth, swarm, and other tools - Meta-package to install geth, swarm and other tools +Description: Meta-package to install geth, and other tools + Meta-package to install geth and other tools {{range .Executables}} Package: {{$.ExeName .}} diff --git a/build/update-license.go b/build/update-license.go index f7d2574bbd..4dc2be6788 100644 --- a/build/update-license.go +++ b/build/update-license.go @@ -1,3 +1,4 @@ +//go:build none // +build none /* @@ -56,8 +57,6 @@ var ( "internal/jsre/deps", "log/", "common/bitutil/bitutil", - // don't license generated files - "contracts/chequebook/contract/code.go", } // paths with this prefix are licensed as GPL. all other files are LGPL. diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go index e8b8801378..e51d52f767 100644 --- a/cmd/bootnode/main.go +++ b/cmd/bootnode/main.go @@ -30,7 +30,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p/discover" - "github.com/AlayaNetwork/Alaya-Go/p2p/discv5" "github.com/AlayaNetwork/Alaya-Go/p2p/nat" "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" ) @@ -118,18 +117,19 @@ func main() { realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port} } } + db, _ := enode.OpenDB("") + ln := enode.NewLocalNode(db, nodeKey) + cfg := discover.Config{ + PrivateKey: nodeKey, + NetRestrict: restrictList, + } if *runv5 { - if _, err := discv5.ListenUDP(nodeKey, conn, "", restrictList); err != nil { + if _, err := discover.ListenV5(conn, ln, cfg); err != nil { utils.Fatalf("%v", err) } } else { - db, _ := enode.OpenDB("") - ln := enode.NewLocalNode(db, nodeKey) - cfg := discover.Config{ - PrivateKey: nodeKey, - NetRestrict: restrictList, - } + if _, err := discover.ListenUDP(conn, ln, cfg); err != nil { utils.Fatalf("%v", err) } diff --git a/cmd/ctool/test/addr.json b/cmd/ctool/test/addr.json index 7366d8f06c..78382d7561 100644 --- a/cmd/ctool/test/addr.json +++ b/cmd/ctool/test/addr.json @@ -1,12 +1,12 @@ [ - "atp1k6cnw7smykrpyrlvwmyvzxr34femahrdu4lt5m", - "atp1m7f39g4ad66xazy36ltr7ar2zjad50tfv6ns32", - "atp1slmg0p92dup7enhdxp8jqkdjqa9kmjl6y3e3dn", - "atp14yvww49quk5ay0m7nfd8pvm0660h3wrx85rm0r", - "atp1v2nynex7w6ye5yew2s4v3gy555ts6844cj0gqv", - "atp1swayyl7mzmlut46g05dnm4289pw726fshd8u89", - "atp1t5exk2apc30jpu8xfjesj9mn573c4x82tz2q0v", - "atp12x95r6jjp225zy2s2kgj7gs3qm5q8rwtfthyrc", - "atp1kzrjajfarknmywf0zepahe8g0f9pzhae86nn9g", - "atp1xsejd4ks0se534xfmd228as3wtj5xzfrfzdftw" + "atp1af7ul6nr7y309szlq9c5k6d544pfcmsry9rl02", + "atp1yavh2xlkp8chye2pvrdtdjzzsq8wzwlxuve2yj", + "atp1lysxaxtptuzslhpcsft8xmaj35hzes5d7uwqnk", + "atp1gc2w7f0msfl8kyn5fwtepaa9y5k8y9gkzps7h6", + "atp12gct74l3s4a3n8gdhau6qfmsa44ud9harg2jly", + "atp13vgvpwnev6l336h9xjv43x6x6wuzu6qu69pyx4", + "atp1tsjqlduwqvz7zl3v97h2u5vtnwxku7s4zgxp75", + "atp192d8w7awsfczpgmx9l7a7wjzt8uvhzwn23fugk", + "atp12ms9epwmxxa2skdy9glj3vd9rrf2g3t5pl7vfw", + "atp1lkc77l9vt34wj53308x6qqqa97q95q30dcet9m" ] \ No newline at end of file diff --git a/cmd/ctool/test/privateKeys.txt b/cmd/ctool/test/privateKeys.txt index ea60fd950800aeedb89c0e5bb8785bb1daa3c4ea..8623073c3b49a953d79810f632acbc9c4661cf5f 100644 GIT binary patch delta 4278 zcmZu!c|4Ze7Ph~*rc)%9;rsX~r%8xJW;%2-6@^IVQjyFJDq{m~inqKd6;i1T*?3Kb zQ>W4_O(#ld35#&Z_GrkBTVYT6o%&_z%Q4ZA|}I68Hf9tBF;e+c_Z%j8MGR4ch|s`l|6*` z+#rLD5qt9mQshdzks-nnQZ5L~&j>(x)*Uazrb{9vE>Z$5M4Zwx|ukh81VfYHq zdgh6^kQvAeaVFMi72=v7!yC-SxqO_b3C}Mba6=5QnvY*r!5Mkukt&z;3o=98djWDo zT*4fB^)LcltS|v7bK~NX5n?m);ee<-3hUw!5^#B+kqKhWQ(!qQ8I}ysjE`TKz{1&C z73({hBYbABH)8Ud1vv2Qsu_)9*j97~gfLg+pa7CCA!jdT-h>3g6$NsNO5Vt#% zBJXgeQOWH@D%_{-0FG@e0Vw&!G{8LqFN6n9xuf!jehA-fcSoG%R7#_FD{?{HrFRej zQ&Odf(<==T*Qta20cb1iq<#;XB0N#dAK^)9aAr%E3dXD%!fJT{O1IDlaT6ZWjrMj> z4C%3GC1P8W;7gnc7?{u`K3<*(2;|G05WfBn;(5bEYk;}MC;+iC&j8{;J3wTt3*!UqGCt@B)vC&=Fu4Z8&OYz8hj=^gtM1x*!a;X%Q?> zSW%70?W6j#?4tBQe6TiqMdN%&kT1gKs{!MIlduvz@j&cGg!q-q;B4-kEquU+x&yA| zE9njQLgaz?Gu)$;$!AG25WdFJhdd!wNS_xTV5hO`7xo0|HOhzrUW7^ zG;>2Zh5^Y09EAkmbOsqRoo9vk@)--@{}w#0e+;qP5Bo!AWN6}js(~Cgpy1x9|Yy8DTl2Th@8}d@r^F>+YI@|tZJw+|M1|RZkPwN?+3eauJ znI&;c1R*3p1{~@C!-J)OmUJtqO+~S8Af|YPr`9g#ZyZjKpX9u}Mdi=?NI{82vy-}FmdQw4(t;ms(aig&( zH#AFB=tPH$cC|^_8NZe`bKDZu&>8C!vkluSNZE%&T#eqpo-MK5y(M62PHp8-aD`mG zqEU*~n$wTE`M#4U{k?_8etY%nO38t z#=;`;4;Y*q1+mor8&W&5P!)eH0BcC^72?Pq3&iDyf=Zb9C_WB|1gDr;5A=kLtU|0s zHOOfiM^j#U0eH!%r!t9?rMx7@fn%L-7vO_!V4w}pRd9X>$nMx{4|Hfe(!l*cY!FwI z3NFC~KY|aKqf3=>ydzjZdx$r}X-Tk$Qid<$?wNzSu|E$)oiG9HY1wX6#lv5tLEi&e z;5?PSQ1Hq&^5mTc`9>0U#>hq55tbYvL!?kcGko%tQKElbO#W5F>3~Ca9^WT;ox#c8 zIqRf6?qo!l8;eRmw23zZ5-sC929hu9S{eq(EZH%0)2kXOm!jSyFGIvn?NeMR{h*8z z{rB*f*uXlauD9*3+E&-hn*O?Nzg^Z*b#=Ar&GGx?)$4%hF_DP>7m7%Fqdub}f0$`Q z$vDy(EcEFPaJYK}?5Oew*d%@u4^s1Spys(V6spK~6I2m?-U7br_Xx7nIH-yXKA0m$ zO{jv$sluY$UIqVY2ffKK6DYe~3uIX|0&k+?2;A9Y8gR?CHUWpM+Ev(4^a|t%JL!QZ zY)Yf-M`r+k+|v@e4%`fSXMRX1;7)M^gnidQhkkYu9y=wHHFw_4uCs#*cWBwsp9Tq(D-ze0mvjJh>VT9 z3G;ye4VVX5wGl9P(eJP&H@yL^KzwH%(%?o0$gUVgk~^L>Y$eBMgp$4>a)o3v7A}>s zkq(Eyt$pl$`^x@;Z5nNvJpDypvA+IalcVFp7K`amsDgxj%1K+7`T0IK*ina3lCXcjTdJ*73ZtpW`E19DtN!*8fVf6 zNgWf4mIRIWAN(^i&@6sn!;S8;!CN8+8?>Gr5!n!%WCCz& zEI#M=Ky^Ffaix!`=F z=Ufp!a^D{bT*-4!vWv9MBemqTJ2^4id?qAO;D@GNu;4)O3_KyYF~LawgC>$QPN2ce zIY--{#Z?Fof0+c~K0XL(l)MUV*K;WZ8N)HNlp}Pze<_v=Wvf9(0c6%b)|CD+lFpf0h$?tGhD(=mupzaUImjmPT5Y z&H*s3>ig5admtD=J0}Hzjk)z3AD1nHX-RE0AB%c|b=u_s-{=1XA8c&)gjr}k04rEW z4=bspjd__ZJhCkU4Q{y!!5K}WA?j+;;1*iJfKczIjE}j(;lpn{k@B!Ggk<}Ai^-67 zLz3Vcv5AzEeOj9253+Amh`;5S?w+iC@_w{k(4P0@eRBrQ>q~T&*hRB_r~Os^(pktu z|JkBF#zr$!j@~NRy-;bpK~sbJ5`~9u*ORicdsO7JrD(3$(nq1CE6T#>Z&o=G_)Dpx zFevp*?b$1bv%Y@{$4{p@L9WPHP$Yh05n5xJ;@hDQ@*5rbVaC1M=B;k`Z_}drgblBGnlI26OohR?MlXIRM$Xn-mTgfeQTtuKv z;yifTqXB=>uS#jEn)2J5ZJ8^!z4bb|C^hMb*wywcsk~UX>*}jwAQ-{d2T^Th7PBu6 znfO^oU&v`b_g&^=mYl@xIal{Z#h16xfUV~`D@rIG;%@xj94pKy-__lona-;-=(CA% zcp>+9cNh(LEGXhXv2b7BP|euyf#6aRHE$_tYO4F(K^tsnFeuD92nINAq#OoUR`?23 zFzzK)!GW)E1Gmowl^C^xPgnGUxEXnB+P(aQo<8n0vg#qD57(BLEW6@Gul+XdBa6m4ZCy=0x8wgX#v+514O2&MSxG- zg+Fi&u27KqF0^93Qs6l*^MijF1A!{|{#rPzc>}N{i~)}MH{jvLt>AEx0d$}D8>w94 zV(4)Lw}5U&+g1h3ErE`uJ{5QuokI5C3+|8bR#ZR6}^Dpj+psj^~TKXtV4{Fbv?G)PCTJ<&CKkArk_(d*wB@e-^zYY%Pr$;!AczgCxq!mdgW>10<6 z!zhH*xdSwh*m3hhQXRXorlvkCyFd4%MAD02vT%+cidqAch*v*OB#z2HsVU79dvrQ! z<+g8viN`EN3SXymQa&#t%-9l3!_~4F@+XyMrWE2d_c&6P9WwdoF`ZfAc%|Ahii~m##jh;)qWD7FBy&8ad z-J->$I+sabsD;=h3M8XoA9<+sz4Qthb85&_`JSWzQbk{(rqOhbtctHvFd5kySc`fa z#Of|jt=#=21n82$aLN@hoKYhMHjGIkUGMPVmF)qfS2|dDy#`uKIw+0QoWD^F8Q0^G zrY;sG_^0G(V*YFqDO-wQ_FtP!?6E(d)R+dzR_id2R9~aOwI2ptheLp?nw*hF#W&%# zdJ-r5yiO*O@t_$yn$Hu;^*oSrNo^EIeC%p68C~B(#MW;hA|HGyn$)JzQtsO`SZ}XK zg3@18iSud}kUHBcp+j556-N%(xa5ma;zJ#=r0heq2)L)k%n#285+`&);m+td(%-yc z&(Cz{qci-8hneHZsQCeuS9}jgsD>VjCEnHxGF^W} z$KzfXc}5KbPQKd*S{mLJ^R|r#J z`!{S=nGIA&{t5oCmI%J*YoP^KEkmB2ap*SXuk!5i2r#eyAnlhNp}C;q9w4g`?~6j6 zrGR_-37yh7^Bm$rE-odGT#o~~ccZNTF?S)cyCW=HF$wY%OcZXzNl5D)3K0$;PlV|= ziRl+xpvQz`c6K`o#kQB(d4&OguW7=LifSZNVeg=@k!7Hr$iJf<_28NW7Ke(r7nr#; z4cxD_Be_vy!lnur83DCrm`K$ICX(K3^W>Y>Kw{^w;hG~X`{=EWyKVc=LX#d0sut)G!Xr(P8vFPR9z%{MX0i;vql=NP1PT^&X0cAt#TgU`W4 zZ4pvsUVJ+7y~R-QR*@$+6b3-Q@Dfu9Y&GJAc9Lg*y^DVu-+7{umk6{~R>9?b;=nkTd;(^yp_o zX;37dn@3;_P7MZJy(!wp90eL*+zb2OxN}_7hmN*z_X|LIvym(Yim29TORO8gS&Xd zv|k7h%fLrrSU$O{?p*l{zB35JNIm^nv`HRi=CG0Q#amVQeyglR35eQ=^>u4;s|GxT zG5D7X9Gcbr!M`3NbW@-wx4nX>GlON5=|6-o_R=qqP!0P?v>N{20Fy)OSzaeqyuSjehtcU)39Zms9yb6#V z(@;LeQ3H*+5g2d%E@Rja8V-z23qX740L`je`)fhOH3<;Av0gg6{w5?e?ZVE6_oDG% z^Nc%J4MyRm+t94ec{VQ1g%7jKVVLM$81;tN1|V=K67NKY(k$T6b7z=2 zWjY#Z{!QBb{HAy-pj}8En=ek!9fez$fe?)Mn80a$G4>hBBatD@TYe(+auFoB%agr2 W@k3x*>%|ZALgBo(Bvn>8{P=%>9;luG diff --git a/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go new file mode 100644 index 0000000000..8a88956e79 --- /dev/null +++ b/cmd/devp2p/discv4cmd.go @@ -0,0 +1,167 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "fmt" + "net" + "sort" + "strings" + "time" + + "gopkg.in/urfave/cli.v1" + + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/p2p/discover" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/params" +) + +var ( + discv4Command = cli.Command{ + Name: "discv4", + Usage: "Node Discovery v4 tools", + Subcommands: []cli.Command{ + discv4PingCommand, + discv4RequestRecordCommand, + discv4ResolveCommand, + }, + } + discv4PingCommand = cli.Command{ + Name: "ping", + Usage: "Sends ping to a node", + Action: discv4Ping, + } + discv4RequestRecordCommand = cli.Command{ + Name: "requestenr", + Usage: "Requests a node record using EIP-868 enrRequest", + Action: discv4RequestRecord, + } + discv4ResolveCommand = cli.Command{ + Name: "resolve", + Usage: "Finds a node in the DHT", + Action: discv4Resolve, + Flags: []cli.Flag{bootnodesFlag}, + } +) + +var bootnodesFlag = cli.StringFlag{ + Name: "bootnodes", + Usage: "Comma separated nodes used for bootstrapping", +} + +func discv4Ping(ctx *cli.Context) error { + n, disc, err := getNodeArgAndStartV4(ctx) + if err != nil { + return err + } + defer disc.Close() + + start := time.Now() + if err := disc.Ping(n); err != nil { + return fmt.Errorf("node didn't respond: %v", err) + } + fmt.Printf("node responded to ping (RTT %v).\n", time.Since(start)) + return nil +} + +func discv4RequestRecord(ctx *cli.Context) error { + n, disc, err := getNodeArgAndStartV4(ctx) + if err != nil { + return err + } + defer disc.Close() + + respN, err := disc.RequestENR(n) + if err != nil { + return fmt.Errorf("can't retrieve record: %v", err) + } + fmt.Println(respN.String()) + return nil +} + +func discv4Resolve(ctx *cli.Context) error { + n, disc, err := getNodeArgAndStartV4(ctx) + if err != nil { + return err + } + defer disc.Close() + + fmt.Println(disc.Resolve(n).String()) + return nil +} + +func getNodeArgAndStartV4(ctx *cli.Context) (*enode.Node, *discover.UDPv4, error) { + if ctx.NArg() != 1 { + return nil, nil, fmt.Errorf("missing node as command-line argument") + } + n, err := parseNode(ctx.Args()[0]) + if err != nil { + return nil, nil, err + } + var bootnodes []*enode.Node + if commandHasFlag(ctx, bootnodesFlag) { + bootnodes, err = parseBootnodes(ctx) + if err != nil { + return nil, nil, err + } + } + disc, err := startV4(bootnodes) + return n, disc, err +} + +func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) { + s := params.AlayanetBootnodes + if ctx.IsSet(bootnodesFlag.Name) { + s = strings.Split(ctx.String(bootnodesFlag.Name), ",") + } + nodes := make([]*enode.Node, len(s)) + var err error + for i, record := range s { + nodes[i], err = parseNode(record) + if err != nil { + return nil, fmt.Errorf("invalid bootstrap node: %v", err) + } + } + return nodes, nil +} + +// commandHasFlag returns true if the current command supports the given flag. +func commandHasFlag(ctx *cli.Context, flag cli.Flag) bool { + flags := ctx.FlagNames() + sort.Strings(flags) + i := sort.SearchStrings(flags, flag.GetName()) + return i != len(flags) && flags[i] == flag.GetName() +} + +// startV4 starts an ephemeral discovery V4 node. +func startV4(bootnodes []*enode.Node) (*discover.UDPv4, error) { + var cfg discover.Config + cfg.Bootnodes = bootnodes + cfg.PrivateKey, _ = crypto.GenerateKey() + db, _ := enode.OpenDB("") + ln := enode.NewLocalNode(db, cfg.PrivateKey) + + socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{0, 0, 0, 0}}) + if err != nil { + return nil, err + } + addr := socket.LocalAddr().(*net.UDPAddr) + ln.SetFallbackIP(net.IP{127, 0, 0, 1}) + ln.SetFallbackUDP(addr.Port) + return discover.ListenUDP(socket, ln, cfg) +} diff --git a/cmd/devp2p/enrcmd.go b/cmd/devp2p/enrcmd.go new file mode 100644 index 0000000000..18d54fee3b --- /dev/null +++ b/cmd/devp2p/enrcmd.go @@ -0,0 +1,199 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "fmt" + "io/ioutil" + "net" + "os" + "strconv" + "strings" + + "gopkg.in/urfave/cli.v1" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/rlp" +) + +var enrdumpCommand = cli.Command{ + Name: "enrdump", + Usage: "Pretty-prints node records", + Action: enrdump, + Flags: []cli.Flag{ + cli.StringFlag{Name: "file"}, + }, +} + +func enrdump(ctx *cli.Context) error { + var source string + if file := ctx.String("file"); file != "" { + if ctx.NArg() != 0 { + return fmt.Errorf("can't dump record from command-line argument in -file mode") + } + var b []byte + var err error + if file == "-" { + b, err = ioutil.ReadAll(os.Stdin) + } else { + b, err = ioutil.ReadFile(file) + } + if err != nil { + return err + } + source = string(b) + } else if ctx.NArg() == 1 { + source = ctx.Args()[0] + } else { + return fmt.Errorf("need record as argument") + } + + r, err := parseRecord(source) + if err != nil { + return fmt.Errorf("INVALID: %v", err) + } + fmt.Print(dumpRecord(r)) + return nil +} + +// dumpRecord creates a human-readable description of the given node record. +func dumpRecord(r *enr.Record) string { + out := new(bytes.Buffer) + if n, err := enode.New(enode.ValidSchemes, r); err != nil { + fmt.Fprintf(out, "INVALID: %v\n", err) + } else { + fmt.Fprintf(out, "Node ID: %v\n", n.ID()) + } + kv := r.AppendElements(nil)[1:] + fmt.Fprintf(out, "Record has sequence number %d and %d key/value pairs.\n", r.Seq(), len(kv)/2) + fmt.Fprint(out, dumpRecordKV(kv, 2)) + return out.String() +} + +func dumpRecordKV(kv []interface{}, indent int) string { + // Determine the longest key name for alignment. + var out string + var longestKey = 0 + for i := 0; i < len(kv); i += 2 { + key := kv[i].(string) + if len(key) > longestKey { + longestKey = len(key) + } + } + // Print the keys, invoking formatters for known keys. + for i := 0; i < len(kv); i += 2 { + key := kv[i].(string) + val := kv[i+1].(rlp.RawValue) + pad := longestKey - len(key) + out += strings.Repeat(" ", indent) + strconv.Quote(key) + strings.Repeat(" ", pad+1) + formatter := attrFormatters[key] + if formatter == nil { + formatter = formatAttrRaw + } + fmtval, ok := formatter(val) + if ok { + out += fmtval + "\n" + } else { + out += hex.EncodeToString(val) + " (!)\n" + } + } + return out +} + +// parseNode parses a node record and verifies its signature. +func parseNode(source string) (*enode.Node, error) { + if strings.HasPrefix(source, "enode://") { + return enode.ParseV4(source) + } + r, err := parseRecord(source) + if err != nil { + return nil, err + } + return enode.New(enode.ValidSchemes, r) +} + +// parseRecord parses a node record from hex, base64, or raw binary input. +func parseRecord(source string) (*enr.Record, error) { + bin := []byte(source) + if d, ok := decodeRecordHex(bytes.TrimSpace(bin)); ok { + bin = d + } else if d, ok := decodeRecordBase64(bytes.TrimSpace(bin)); ok { + bin = d + } + var r enr.Record + err := rlp.DecodeBytes(bin, &r) + return &r, err +} + +func decodeRecordHex(b []byte) ([]byte, bool) { + if bytes.HasPrefix(b, []byte("0x")) { + b = b[2:] + } + dec := make([]byte, hex.DecodedLen(len(b))) + _, err := hex.Decode(dec, b) + return dec, err == nil +} + +func decodeRecordBase64(b []byte) ([]byte, bool) { + if bytes.HasPrefix(b, []byte("enr:")) { + b = b[4:] + } + dec := make([]byte, base64.RawURLEncoding.DecodedLen(len(b))) + n, err := base64.RawURLEncoding.Decode(dec, b) + return dec[:n], err == nil +} + +// attrFormatters contains formatting functions for well-known ENR keys. +var attrFormatters = map[string]func(rlp.RawValue) (string, bool){ + "id": formatAttrString, + "ip": formatAttrIP, + "ip6": formatAttrIP, + "tcp": formatAttrUint, + "tcp6": formatAttrUint, + "udp": formatAttrUint, + "udp6": formatAttrUint, +} + +func formatAttrRaw(v rlp.RawValue) (string, bool) { + s := hex.EncodeToString(v) + return s, true +} + +func formatAttrString(v rlp.RawValue) (string, bool) { + content, _, err := rlp.SplitString(v) + return strconv.Quote(string(content)), err == nil +} + +func formatAttrIP(v rlp.RawValue) (string, bool) { + content, _, err := rlp.SplitString(v) + if err != nil || len(content) != 4 && len(content) != 6 { + return "", false + } + return net.IP(content).String(), true +} + +func formatAttrUint(v rlp.RawValue) (string, bool) { + var x uint64 + if err := rlp.DecodeBytes(v, &x); err != nil { + return "", false + } + return strconv.FormatUint(x, 10), true +} diff --git a/cmd/devp2p/main.go b/cmd/devp2p/main.go new file mode 100644 index 0000000000..0bb54b49cf --- /dev/null +++ b/cmd/devp2p/main.go @@ -0,0 +1,69 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "fmt" + "os" + "path/filepath" + + "gopkg.in/urfave/cli.v1" + + "github.com/AlayaNetwork/Alaya-Go/internal/debug" + "github.com/AlayaNetwork/Alaya-Go/params" +) + +var ( + // Git information set by linker when building with ci.go. + gitCommit string + gitDate string + app = &cli.App{ + Name: filepath.Base(os.Args[0]), + Usage: "Alaya devp2p tool", + Version: params.VersionWithCommit(gitCommit, gitDate), + Writer: os.Stdout, + HideVersion: true, + } +) + +func init() { + // Set up the CLI app. + app.Flags = append(app.Flags, debug.Flags...) + app.Before = func(ctx *cli.Context) error { + return debug.Setup(ctx, "") + } + app.After = func(ctx *cli.Context) error { + debug.Exit() + return nil + } + app.CommandNotFound = func(ctx *cli.Context, cmd string) { + fmt.Fprintf(os.Stderr, "No such command: %s\n", cmd) + os.Exit(1) + } + // Add subcommands. + app.Commands = []cli.Command{ + enrdumpCommand, + discv4Command, + } +} + +func main() { + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index da2dd1713f..088425a6e1 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -50,15 +50,10 @@ import ( "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/core" "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/eth" - "github.com/AlayaNetwork/Alaya-Go/eth/downloader" "github.com/AlayaNetwork/Alaya-Go/ethclient" - "github.com/AlayaNetwork/Alaya-Go/ethstats" - "github.com/AlayaNetwork/Alaya-Go/les" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discv5" "github.com/AlayaNetwork/Alaya-Go/p2p/nat" "github.com/AlayaNetwork/Alaya-Go/params" ) @@ -158,9 +153,9 @@ func main() { log.Crit("Failed to parse genesis block json", "err", err) } // Convert the bootnodes to internal enode representations - var enodes []*discv5.Node + var enodes []*enode.Node for _, boot := range strings.Split(*bootFlag, ",") { - if url, err := discv5.ParseNode(boot); err == nil { + if url, err := enode.ParseV4(boot); err == nil { enodes = append(enodes, url) } else { log.Error("Failed to parse bootnode URL", "url", boot, "err", err) @@ -225,7 +220,7 @@ type faucet struct { lock sync.RWMutex // Lock protecting the faucet's internals } -func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { +func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { // Assemble the raw devp2p protocol stack stack, err := node.New(&node.Config{ Name: "platon", @@ -245,20 +240,22 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u } // Assemble the Ethereum light client protocol if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - cfg := eth.DefaultConfig + return nil, errors.New("not support light yet") + /*cfg := eth.DefaultConfig cfg.SyncMode = downloader.LightSync cfg.NetworkId = network cfg.Genesis = genesis - return les.New(ctx, &cfg) + return les.New(ctx, &cfg)*/ }); err != nil { return nil, err } // Assemble the ethstats monitoring and reporting service' if stats != "" { if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - var serv *les.LightEthereum + return nil, errors.New("not support light yet") + /*var serv *les.LightEthereum ctx.Service(&serv) - return ethstats.New(stats, nil, serv) + return ethstats.New(stats, nil, serv)*/ }); err != nil { return nil, err } @@ -268,7 +265,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u return nil, err } for _, boot := range enodes { - old, err := enode.ParseV4(boot.String()) + old, err := enode.Parse(enode.ValidSchemes, boot.String()) if err != nil { stack.Server().AddPeer(old) } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index d74d68cffe..e1bc5c8042 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -51,7 +51,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/eth/gasprice" "github.com/AlayaNetwork/Alaya-Go/ethdb" "github.com/AlayaNetwork/Alaya-Go/ethstats" - "github.com/AlayaNetwork/Alaya-Go/les" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/metrics" "github.com/AlayaNetwork/Alaya-Go/metrics/influxdb" @@ -668,7 +667,7 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { cfg.BootstrapNodes = make([]*enode.Node, 0, len(urls)) for _, url := range urls { - node, err := enode.ParseV4(url) + node, err := enode.Parse(enode.ValidSchemes, url) if err != nil { log.Crit("Bootstrap URL invalid", "enode", url, "err", err) } @@ -1181,13 +1180,7 @@ func SetCbft(ctx *cli.Context, cfg *types.OptionsConfig, nodeCfg *node.Config) { func RegisterEthService(stack *node.Node, cfg *eth.Config) { var err error if cfg.SyncMode == downloader.LightSync { - err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - light, err := les.New(ctx, cfg) - if err == nil { - stack.ChainID = light.ApiBackend.ChainConfig().ChainID - } - return light, err - }) + Fatalf("Failed to register the Alaya-Go service: %v", "not support LightSync") } else { err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { fullNode, err := eth.New(ctx, cfg) @@ -1196,8 +1189,8 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { } if fullNode != nil && cfg.LightServ > 0 { - ls, _ := les.NewLesServer(fullNode, cfg) - fullNode.AddLesServer(ls) + /*ls, _ := les.NewLesServer(fullNode, cfg) + fullNode.AddLesServer(ls)*/ } return fullNode, err }) @@ -1224,10 +1217,10 @@ func RegisterEthStatsService(stack *node.Node, url string) { var ethServ *eth.Ethereum ctx.Service(ðServ) - var lesServ *les.LightEthereum - ctx.Service(&lesServ) + //var lesServ *les.LightEthereum + //ctx.Service(&lesServ) - return ethstats.New(url, ethServ, lesServ) + return ethstats.New(url, ethServ) }); err != nil { Fatalf("Failed to register the Alaya-Go Stats service: %v", err) } diff --git a/contracts/chequebook/api.go b/contracts/chequebook/api.go deleted file mode 100644 index cd271b5324..0000000000 --- a/contracts/chequebook/api.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2016 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 . - -package chequebook - -import ( - "errors" - "math/big" - - "github.com/AlayaNetwork/Alaya-Go/common" -) - -const Version = "1.0" - -var errNoChequebook = errors.New("no chequebook") - -type API struct { - chequebookf func() *Chequebook -} - -func NewAPI(ch func() *Chequebook) *API { - return &API{ch} -} - -func (a *API) Balance() (string, error) { - ch := a.chequebookf() - if ch == nil { - return "", errNoChequebook - } - return ch.Balance().String(), nil -} - -func (a *API) Issue(beneficiary common.Address, amount *big.Int) (cheque *Cheque, err error) { - ch := a.chequebookf() - if ch == nil { - return nil, errNoChequebook - } - return ch.Issue(beneficiary, amount) -} - -func (a *API) Cash(cheque *Cheque) (txhash string, err error) { - ch := a.chequebookf() - if ch == nil { - return "", errNoChequebook - } - return ch.Cash(cheque) -} - -func (a *API) Deposit(amount *big.Int) (txhash string, err error) { - ch := a.chequebookf() - if ch == nil { - return "", errNoChequebook - } - return ch.Deposit(amount) -} diff --git a/contracts/chequebook/cheque.go b/contracts/chequebook/cheque.go deleted file mode 100644 index 098a9f0f7c..0000000000 --- a/contracts/chequebook/cheque.go +++ /dev/null @@ -1,604 +0,0 @@ -// Copyright 2016 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 . - -// Package chequebook package wraps the 'chequebook' Ethereum smart contract. -// -// The functions in this package allow using chequebook for -// issuing, receiving, verifying cheques in ether; (auto)cashing cheques in ether -// as well as (auto)depositing ether to the chequebook contract. -package chequebook - -//go:generate abigen --sol contract/chequebook.sol --exc contract/mortal.sol:mortal,contract/owned.sol:owned --pkg contract --out contract/chequebook.go -//go:generate go run ./gencode.go - -import ( - "bytes" - "context" - "crypto/ecdsa" - "encoding/json" - "fmt" - "io/ioutil" - "math/big" - "os" - "sync" - "time" - - "github.com/AlayaNetwork/Alaya-Go/accounts/abi/bind" - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/common/hexutil" - "github.com/AlayaNetwork/Alaya-Go/contracts/chequebook/contract" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/log" -) - -// TODO(zelig): watch peer solvency and notify of bouncing cheques -// TODO(zelig): enable paying with cheque by signing off - -// Some functionality requires interacting with the blockchain: -// * setting current balance on peer's chequebook -// * sending the transaction to cash the cheque -// * depositing ether to the chequebook -// * watching incoming ether - -var ( - gasToCash = uint64(2000000) // gas cost of a cash transaction using chequebook - // gasToDeploy = uint64(3000000) -) - -// Backend wraps all methods required for chequebook operation. -type Backend interface { - bind.ContractBackend - TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) - BalanceAt(ctx context.Context, address common.Address, blockNum *big.Int) (*big.Int, error) -} - -// Cheque represents a payment promise to a single beneficiary. -type Cheque struct { - Contract common.Address // address of chequebook, needed to avoid cross-contract submission - Beneficiary common.Address - Amount *big.Int // cumulative amount of all funds sent - Sig []byte // signature Sign(Keccak256(contract, beneficiary, amount), prvKey) -} - -func (ch *Cheque) String() string { - return fmt.Sprintf("contract: %s, beneficiary: %s, amount: %v, signature: %x", ch.Contract.String(), ch.Beneficiary.String(), ch.Amount, ch.Sig) -} - -type Params struct { - ContractCode, ContractAbi string -} - -var ContractParams = &Params{contract.ChequebookBin, contract.ChequebookABI} - -// Chequebook can create and sign cheques from a single contract to multiple beneficiaries. -// It is the outgoing payment handler for peer to peer micropayments. -type Chequebook struct { - path string // path to chequebook file - prvKey *ecdsa.PrivateKey // private key to sign cheque with - lock sync.Mutex // - backend Backend // blockchain API - quit chan bool // when closed causes autodeposit to stop - owner common.Address // owner address (derived from pubkey) - contract *contract.Chequebook // abigen binding - session *contract.ChequebookSession // abigen binding with Tx Opts - - // persisted fields - balance *big.Int // not synced with blockchain - contractAddr common.Address // contract address - sent map[common.Address]*big.Int //tallies for beneficiaries - - txhash string // tx hash of last deposit tx - threshold *big.Int // threshold that triggers autodeposit if not nil - buffer *big.Int // buffer to keep on top of balance for fork protection - - log log.Logger // contextual logger with the contract address embedded -} - -func (cb *Chequebook) String() string { - return fmt.Sprintf("contract: %s, owner: %s, balance: %v, signer: %x", cb.contractAddr.String(), cb.owner.String(), cb.balance, cb.prvKey.PublicKey) -} - -// NewChequebook creates a new Chequebook. -func NewChequebook(path string, contractAddr common.Address, prvKey *ecdsa.PrivateKey, backend Backend) (*Chequebook, error) { - balance := new(big.Int) - sent := make(map[common.Address]*big.Int) - - chbook, err := contract.NewChequebook(contractAddr, backend) - if err != nil { - return nil, err - } - transactOpts := bind.NewKeyedTransactor(prvKey) - session := &contract.ChequebookSession{ - Contract: chbook, - TransactOpts: *transactOpts, - } - - cb := &Chequebook{ - prvKey: prvKey, - balance: balance, - contractAddr: contractAddr, - sent: sent, - path: path, - backend: backend, - owner: transactOpts.From, - contract: chbook, - session: session, - log: log.New("contract", contractAddr), - } - if (contractAddr != common.Address{}) { - cb.setBalanceFromBlockChain() - cb.log.Trace("New chequebook initialised", "owner", cb.owner, "balance", cb.balance) - } - return cb, nil -} - -func (cb *Chequebook) setBalanceFromBlockChain() { - balance, err := cb.backend.BalanceAt(context.TODO(), cb.contractAddr, nil) - if err != nil { - log.Error("Failed to retrieve chequebook balance", "err", err) - } else { - cb.balance.Set(balance) - } -} - -// LoadChequebook loads a chequebook from disk (file path). -func LoadChequebook(path string, prvKey *ecdsa.PrivateKey, backend Backend, checkBalance bool) (*Chequebook, error) { - data, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - cb, _ := NewChequebook(path, common.Address{}, prvKey, backend) - - if err = json.Unmarshal(data, cb); err != nil { - return nil, err - } - if checkBalance { - cb.setBalanceFromBlockChain() - } - log.Trace("Loaded chequebook from disk", "path", path) - - return cb, nil -} - -// chequebookFile is the JSON representation of a chequebook. -type chequebookFile struct { - Balance string - Contract string - Owner string - Sent map[string]string -} - -// UnmarshalJSON deserialises a chequebook. -func (cb *Chequebook) UnmarshalJSON(data []byte) error { - var file chequebookFile - err := json.Unmarshal(data, &file) - if err != nil { - return err - } - _, ok := cb.balance.SetString(file.Balance, 10) - if !ok { - return fmt.Errorf("cumulative amount sent: unable to convert string to big integer: %v", file.Balance) - } - cb.contractAddr = common.MustBech32ToAddress(file.Contract) - for addr, sent := range file.Sent { - cb.sent[common.MustBech32ToAddress(addr)], ok = new(big.Int).SetString(sent, 10) - if !ok { - return fmt.Errorf("beneficiary %v cumulative amount sent: unable to convert string to big integer: %v", addr, sent) - } - } - return nil -} - -// MarshalJSON serialises a chequebook. -func (cb *Chequebook) MarshalJSON() ([]byte, error) { - var file = &chequebookFile{ - Balance: cb.balance.String(), - Contract: cb.contractAddr.String(), - Owner: cb.owner.String(), - Sent: make(map[string]string), - } - for addr, sent := range cb.sent { - file.Sent[addr.String()] = sent.String() - } - return json.Marshal(file) -} - -// Save persists the chequebook on disk, remembering balance, contract address and -// cumulative amount of funds sent for each beneficiary. -func (cb *Chequebook) Save() error { - data, err := json.MarshalIndent(cb, "", " ") - if err != nil { - return err - } - cb.log.Trace("Saving chequebook to disk", cb.path) - - return ioutil.WriteFile(cb.path, data, os.ModePerm) -} - -// Stop quits the autodeposit go routine to terminate -func (cb *Chequebook) Stop() { - defer cb.lock.Unlock() - cb.lock.Lock() - if cb.quit != nil { - close(cb.quit) - cb.quit = nil - } -} - -// Issue creates a cheque signed by the chequebook owner's private key. The -// signer commits to a contract (one that they own), a beneficiary and amount. -func (cb *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (*Cheque, error) { - defer cb.lock.Unlock() - cb.lock.Lock() - - if amount.Sign() <= 0 { - return nil, fmt.Errorf("amount must be greater than zero (%v)", amount) - } - var ( - ch *Cheque - err error - ) - if cb.balance.Cmp(amount) < 0 { - err = fmt.Errorf("insufficient funds to issue cheque for amount: %v. balance: %v", amount, cb.balance) - } else { - var sig []byte - sent, found := cb.sent[beneficiary] - if !found { - sent = new(big.Int) - cb.sent[beneficiary] = sent - } - sum := new(big.Int).Set(sent) - sum.Add(sum, amount) - - sig, err = crypto.Sign(sigHash(cb.contractAddr, beneficiary, sum), cb.prvKey) - if err == nil { - ch = &Cheque{ - Contract: cb.contractAddr, - Beneficiary: beneficiary, - Amount: sum, - Sig: sig, - } - sent.Set(sum) - cb.balance.Sub(cb.balance, amount) // subtract amount from balance - } - } - // auto deposit if threshold is set and balance is less then threshold - // note this is called even if issuing cheque fails - // so we reattempt depositing - if cb.threshold != nil { - if cb.balance.Cmp(cb.threshold) < 0 { - send := new(big.Int).Sub(cb.buffer, cb.balance) - cb.deposit(send) - } - } - return ch, err -} - -// Cash is a convenience method to cash any cheque. -func (cb *Chequebook) Cash(ch *Cheque) (string, error) { - return ch.Cash(cb.session) -} - -// data to sign: contract address, beneficiary, cumulative amount of funds ever sent -func sigHash(contract, beneficiary common.Address, sum *big.Int) []byte { - bigamount := sum.Bytes() - if len(bigamount) > 32 { - return nil - } - var amount32 [32]byte - copy(amount32[32-len(bigamount):32], bigamount) - input := append(contract.Bytes(), beneficiary.Bytes()...) - input = append(input, amount32[:]...) - return crypto.Keccak256(input) -} - -// Balance returns the current balance of the chequebook. -func (cb *Chequebook) Balance() *big.Int { - defer cb.lock.Unlock() - cb.lock.Lock() - return new(big.Int).Set(cb.balance) -} - -// Owner returns the owner account of the chequebook. -func (cb *Chequebook) Owner() common.Address { - return cb.owner -} - -// Address returns the on-chain contract address of the chequebook. -func (cb *Chequebook) Address() common.Address { - return cb.contractAddr -} - -// Deposit deposits money to the chequebook account. -func (cb *Chequebook) Deposit(amount *big.Int) (string, error) { - defer cb.lock.Unlock() - cb.lock.Lock() - return cb.deposit(amount) -} - -// deposit deposits amount to the chequebook account. -// The caller must hold lock. -func (cb *Chequebook) deposit(amount *big.Int) (string, error) { - // since the amount is variable here, we do not use sessions - depositTransactor := bind.NewKeyedTransactor(cb.prvKey) - depositTransactor.Value = amount - chbookRaw := &contract.ChequebookRaw{Contract: cb.contract} - tx, err := chbookRaw.Transfer(depositTransactor) - if err != nil { - cb.log.Warn("Failed to fund chequebook", "amount", amount, "balance", cb.balance, "target", cb.buffer, "err", err) - return "", err - } - // assume that transaction is actually successful, we add the amount to balance right away - cb.balance.Add(cb.balance, amount) - cb.log.Trace("Deposited funds to chequebook", "amount", amount, "balance", cb.balance, "target", cb.buffer) - return tx.Hash().Hex(), nil -} - -// AutoDeposit (re)sets interval time and amount which triggers sending funds to the -// chequebook. Contract backend needs to be set if threshold is not less than buffer, then -// deposit will be triggered on every new cheque issued. -func (cb *Chequebook) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { - defer cb.lock.Unlock() - cb.lock.Lock() - cb.threshold = threshold - cb.buffer = buffer - cb.autoDeposit(interval) -} - -// autoDeposit starts a goroutine that periodically sends funds to the chequebook -// contract caller holds the lock the go routine terminates if Chequebook.quit is closed. -func (cb *Chequebook) autoDeposit(interval time.Duration) { - if cb.quit != nil { - close(cb.quit) - cb.quit = nil - } - // if threshold >= balance autodeposit after every cheque issued - if interval == time.Duration(0) || cb.threshold != nil && cb.buffer != nil && cb.threshold.Cmp(cb.buffer) >= 0 { - return - } - - ticker := time.NewTicker(interval) - cb.quit = make(chan bool) - quit := cb.quit - - go func() { - for { - select { - case <-quit: - return - case <-ticker.C: - cb.lock.Lock() - if cb.balance.Cmp(cb.buffer) < 0 { - amount := new(big.Int).Sub(cb.buffer, cb.balance) - txhash, err := cb.deposit(amount) - if err == nil { - cb.txhash = txhash - } - } - cb.lock.Unlock() - } - } - }() -} - -// Outbox can issue cheques from a single contract to a single beneficiary. -type Outbox struct { - chequeBook *Chequebook - beneficiary common.Address -} - -// NewOutbox creates an outbox. -func NewOutbox(cb *Chequebook, beneficiary common.Address) *Outbox { - return &Outbox{cb, beneficiary} -} - -//// Issue creates cheque. -//func (self *Outbox) Issue(amount *big.Int) (swap.Promise, error) { -// return self.chequeBook.Issue(self.beneficiary, amount) -//} - -// AutoDeposit enables auto-deposits on the underlying chequebook. -func (o *Outbox) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { - o.chequeBook.AutoDeposit(interval, threshold, buffer) -} - -// Stop helps satisfy the swap.OutPayment interface. -func (o *Outbox) Stop() {} - -// String implements fmt.Stringer. -func (o *Outbox) String() string { - return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", o.chequeBook.Address().String(), o.beneficiary.String(), o.chequeBook.Balance()) -} - -// Inbox can deposit, verify and cash cheques from a single contract to a single -// beneficiary. It is the incoming payment handler for peer to peer micropayments. -type Inbox struct { - lock sync.Mutex - contract common.Address // peer's chequebook contract - beneficiary common.Address // local peer's receiving address - sender common.Address // local peer's address to send cashing tx from - signer *ecdsa.PublicKey // peer's public key - txhash string // tx hash of last cashing tx - session *contract.ChequebookSession // abi contract backend with tx opts - quit chan bool // when closed causes autocash to stop - maxUncashed *big.Int // threshold that triggers autocashing - cashed *big.Int // cumulative amount cashed - cheque *Cheque // last cheque, nil if none yet received - log log.Logger // contextual logger with the contract address embedded -} - -// NewInbox creates an Inbox. An Inboxes is not persisted, the cumulative sum is updated -// from blockchain when first cheque is received. -func NewInbox(prvKey *ecdsa.PrivateKey, contractAddr, beneficiary common.Address, signer *ecdsa.PublicKey, abigen bind.ContractBackend) (*Inbox, error) { - if signer == nil { - return nil, fmt.Errorf("signer is null") - } - chbook, err := contract.NewChequebook(contractAddr, abigen) - if err != nil { - return nil, err - } - transactOpts := bind.NewKeyedTransactor(prvKey) - transactOpts.GasLimit = gasToCash - session := &contract.ChequebookSession{ - Contract: chbook, - TransactOpts: *transactOpts, - } - sender := transactOpts.From - - inbox := &Inbox{ - contract: contractAddr, - beneficiary: beneficiary, - sender: sender, - signer: signer, - session: session, - cashed: new(big.Int).Set(common.Big0), - log: log.New("contract", contractAddr), - } - inbox.log.Trace("New chequebook inbox initialized", "beneficiary", inbox.beneficiary, "signer", hexutil.Bytes(crypto.FromECDSAPub(signer))) - return inbox, nil -} - -func (i *Inbox) String() string { - return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", i.contract.String(), i.beneficiary.String(), i.cheque.Amount) -} - -// Stop quits the autocash goroutine. -func (i *Inbox) Stop() { - defer i.lock.Unlock() - i.lock.Lock() - if i.quit != nil { - close(i.quit) - i.quit = nil - } -} - -// Cash attempts to cash the current cheque. -func (i *Inbox) Cash() (string, error) { - if i.cheque == nil { - return "", nil - } - txhash, err := i.cheque.Cash(i.session) - i.log.Trace("Cashing in chequebook cheque", "amount", i.cheque.Amount, "beneficiary", i.beneficiary) - i.cashed = i.cheque.Amount - - return txhash, err -} - -// AutoCash (re)sets maximum time and amount which triggers cashing of the last uncashed -// cheque if maxUncashed is set to 0, then autocash on receipt. -func (i *Inbox) AutoCash(cashInterval time.Duration, maxUncashed *big.Int) { - defer i.lock.Unlock() - i.lock.Lock() - i.maxUncashed = maxUncashed - i.autoCash(cashInterval) -} - -// autoCash starts a loop that periodically clears the last cheque -// if the peer is trusted. Clearing period could be 24h or a week. -// The caller must hold lock. -func (i *Inbox) autoCash(cashInterval time.Duration) { - if i.quit != nil { - close(i.quit) - i.quit = nil - } - // if maxUncashed is set to 0, then autocash on receipt - if cashInterval == time.Duration(0) || i.maxUncashed != nil && i.maxUncashed.Sign() == 0 { - return - } - - ticker := time.NewTicker(cashInterval) - i.quit = make(chan bool) - quit := i.quit - - go func() { - for { - select { - case <-quit: - return - case <-ticker.C: - i.lock.Lock() - if i.cheque != nil && i.cheque.Amount.Cmp(i.cashed) != 0 { - txhash, err := i.Cash() - if err == nil { - i.txhash = txhash - } - } - i.lock.Unlock() - } - } - }() -} - -// Verify verifies cheque for signer, contract, beneficiary, amount, valid signature. -func (ch *Cheque) Verify(signerKey *ecdsa.PublicKey, contract, beneficiary common.Address, sum *big.Int) (*big.Int, error) { - log.Trace("Verifying chequebook cheque", "cheque", ch, "sum", sum) - if sum == nil { - return nil, fmt.Errorf("invalid amount") - } - - if ch.Beneficiary != beneficiary { - return nil, fmt.Errorf("beneficiary mismatch: %v != %v", ch.Beneficiary.String(), beneficiary.String()) - } - if ch.Contract != contract { - return nil, fmt.Errorf("contract mismatch: %v != %v", ch.Contract.String(), contract.String()) - } - - amount := new(big.Int).Set(ch.Amount) - if sum != nil { - amount.Sub(amount, sum) - if amount.Sign() <= 0 { - return nil, fmt.Errorf("incorrect amount: %v <= 0", amount) - } - } - - pubKey, err := crypto.SigToPub(sigHash(ch.Contract, beneficiary, ch.Amount), ch.Sig) - if err != nil { - return nil, fmt.Errorf("invalid signature: %v", err) - } - if !bytes.Equal(crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) { - return nil, fmt.Errorf("signer mismatch: %x != %x", crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) - } - return amount, nil -} - -// v/r/s representation of signature -func sig2vrs(sig []byte) (v byte, r, s [32]byte) { - v = sig[64] + 27 - copy(r[:], sig[:32]) - copy(s[:], sig[32:64]) - return -} - -// Cash cashes the cheque by sending an Ethereum transaction. -func (ch *Cheque) Cash(session *contract.ChequebookSession) (string, error) { - v, r, s := sig2vrs(ch.Sig) - tx, err := session.Cash(ch.Beneficiary, ch.Amount, v, r, s) - if err != nil { - return "", err - } - return tx.Hash().Hex(), nil -} - -// ValidateCode checks that the on-chain code at address matches the expected chequebook -// contract code. This is used to detect suicided chequebooks. -func ValidateCode(ctx context.Context, b Backend, address common.Address) (bool, error) { - code, err := b.CodeAt(ctx, address, nil) - if err != nil { - return false, err - } - return bytes.Equal(code, common.FromHex(contract.ContractDeployedCode)), nil -} diff --git a/contracts/chequebook/cheque_test.go b/contracts/chequebook/cheque_test.go deleted file mode 100644 index 601ba4e631..0000000000 --- a/contracts/chequebook/cheque_test.go +++ /dev/null @@ -1,488 +0,0 @@ -// Copyright 2016 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 . - -package chequebook - -import ( - "crypto/ecdsa" - "github.com/AlayaNetwork/Alaya-Go/accounts/abi/bind" - "github.com/AlayaNetwork/Alaya-Go/accounts/abi/bind/backends" - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/contracts/chequebook/contract" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "math/big" - "testing" -) - -var ( - key0, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - key1, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") - key2, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") - addr0 = crypto.PubkeyToAddress(key0.PublicKey) - addr1 = crypto.PubkeyToAddress(key1.PublicKey) - addr2 = crypto.PubkeyToAddress(key2.PublicKey) -) - -func newTestBackend() *backends.SimulatedBackend { - return backends.NewSimulatedBackend(core.GenesisAlloc{ - addr0: {Balance: big.NewInt(1000000000)}, - addr1: {Balance: big.NewInt(1000000000)}, - addr2: {Balance: big.NewInt(1000000000)}, - }, 10000000) -} - -func deploy(prvKey *ecdsa.PrivateKey, amount *big.Int, backend *backends.SimulatedBackend) (common.Address, error) { - deployTransactor := bind.NewKeyedTransactor(prvKey) - deployTransactor.Value = amount - addr, _, _, err := contract.DeployChequebook(deployTransactor, backend) - if err != nil { - return common.Address{}, err - } - backend.Commit() - return addr, nil -} - -func TestIssueAndReceive(t *testing.T) { - // TODO test - /*path := filepath.Join(os.TempDir(), "chequebook-test.json") - backend := newTestBackend() - addr0, err := deploy(key0, big.NewInt(0), backend) - if err != nil { - t.Fatalf("deploy contract: expected no error, got %v", err) - } - chbook, err := NewChequebook(path, addr0, key0, backend) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - chbook.sent[addr1] = new(big.Int).SetUint64(42) - amount := common.Big1 - - if _, err = chbook.Issue(addr1, amount); err == nil { - t.Fatalf("expected insufficient funds error, got none") - } - - chbook.balance = new(big.Int).Set(common.Big1) - if chbook.Balance().Cmp(common.Big1) != 0 { - t.Fatalf("expected: %v, got %v", "0", chbook.Balance()) - } - - ch, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - if chbook.Balance().Sign() != 0 { - t.Errorf("expected: %v, got %v", "0", chbook.Balance()) - } - - chbox, err := NewInbox(key1, addr0, addr1, &key0.PublicKey, backend) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - received, err := chbox.Receive(ch) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - if received.Cmp(big.NewInt(43)) != 0 { - t.Errorf("expected: %v, got %v", "43", received) - }*/ - -} - -func TestCheckbookFile(t *testing.T) { - // TODO test - /*path := filepath.Join(os.TempDir(), "chequebook-test.json") - backend := newTestBackend() - chbook, err := NewChequebook(path, addr0, key0, backend) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - chbook.sent[addr1] = new(big.Int).SetUint64(42) - chbook.balance = new(big.Int).Set(common.Big1) - - chbook.Save() - - chbook, err = LoadChequebook(path, key0, backend, false) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - if chbook.Balance().Cmp(common.Big1) != 0 { - t.Errorf("expected: %v, got %v", "0", chbook.Balance()) - } - - var ch *Cheque - if ch, err = chbook.Issue(addr1, common.Big1); err != nil { - t.Fatalf("expected no error, got %v", err) - } - if ch.Amount.Cmp(new(big.Int).SetUint64(43)) != 0 { - t.Errorf("expected: %v, got %v", "0", ch.Amount) - } - - err = chbook.Save() - if err != nil { - t.Fatalf("expected no error, got %v", err) - }*/ -} - -func TestVerifyErrors(t *testing.T) { - // TODO test - /*path0 := filepath.Join(os.TempDir(), "chequebook-test-0.json") - backend := newTestBackend() - contr0, err := deploy(key0, common.Big2, backend) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - chbook0, err := NewChequebook(path0, contr0, key0, backend) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - path1 := filepath.Join(os.TempDir(), "chequebook-test-1.json") - contr1, _ := deploy(key1, common.Big2, backend) - chbook1, err := NewChequebook(path1, contr1, key1, backend) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - chbook0.sent[addr1] = new(big.Int).SetUint64(42) - chbook0.balance = new(big.Int).Set(common.Big2) - chbook1.balance = new(big.Int).Set(common.Big1) - amount := common.Big1 - ch0, err := chbook0.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - chbox, err := NewInbox(key1, contr0, addr1, &key0.PublicKey, backend) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - received, err := chbox.Receive(ch0) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - if received.Cmp(big.NewInt(43)) != 0 { - t.Errorf("expected: %v, got %v", "43", received) - } - - ch1, err := chbook0.Issue(addr2, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - received, err = chbox.Receive(ch1) - t.Logf("correct error: %v", err) - if err == nil { - t.Fatalf("expected receiver error, got none and value %v", received) - } - - ch2, err := chbook1.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - received, err = chbox.Receive(ch2) - t.Logf("correct error: %v", err) - if err == nil { - t.Fatalf("expected sender error, got none and value %v", received) - } - - _, err = chbook1.Issue(addr1, new(big.Int).SetInt64(-1)) - t.Logf("correct error: %v", err) - if err == nil { - t.Fatalf("expected incorrect amount error, got none") - } - - received, err = chbox.Receive(ch0) - t.Logf("correct error: %v", err) - if err == nil { - t.Fatalf("expected incorrect amount error, got none and value %v", received) - }*/ - -} - -func TestDeposit(t *testing.T) { - // TODO test - /*path0 := filepath.Join(os.TempDir(), "chequebook-test-0.json") - backend := newTestBackend() - contr0, _ := deploy(key0, new(big.Int), backend) - - chbook, err := NewChequebook(path0, contr0, key0, backend) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - balance := new(big.Int).SetUint64(42) - chbook.Deposit(balance) - backend.Commit() - if chbook.Balance().Cmp(balance) != 0 { - t.Fatalf("expected balance %v, got %v", balance, chbook.Balance()) - } - - amount := common.Big1 - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - exp := new(big.Int).SetUint64(41) - if chbook.Balance().Cmp(exp) != 0 { - t.Fatalf("expected balance %v, got %v", exp, chbook.Balance()) - } - - // autodeposit on each issue - chbook.AutoDeposit(0, balance, balance) - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - if chbook.Balance().Cmp(balance) != 0 { - t.Fatalf("expected balance %v, got %v", balance, chbook.Balance()) - } - - // autodeposit off - chbook.AutoDeposit(0, common.Big0, balance) - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - exp = new(big.Int).SetUint64(40) - if chbook.Balance().Cmp(exp) != 0 { - t.Fatalf("expected balance %v, got %v", exp, chbook.Balance()) - } - - // autodeposit every 200ms if new cheque issued - interval := 200 * time.Millisecond - chbook.AutoDeposit(interval, common.Big1, balance) - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - exp = new(big.Int).SetUint64(38) - if chbook.Balance().Cmp(exp) != 0 { - t.Fatalf("expected balance %v, got %v", exp, chbook.Balance()) - } - - time.Sleep(3 * interval) - backend.Commit() - if chbook.Balance().Cmp(balance) != 0 { - t.Fatalf("expected balance %v, got %v", balance, chbook.Balance()) - } - - exp = new(big.Int).SetUint64(40) - chbook.AutoDeposit(4*interval, exp, balance) - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - time.Sleep(3 * interval) - backend.Commit() - if chbook.Balance().Cmp(exp) != 0 { - t.Fatalf("expected balance %v, got %v", exp, chbook.Balance()) - } - - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - time.Sleep(1 * interval) - backend.Commit() - - if chbook.Balance().Cmp(balance) != 0 { - t.Fatalf("expected balance %v, got %v", balance, chbook.Balance()) - } - - chbook.AutoDeposit(1*interval, common.Big0, balance) - chbook.Stop() - - _, err = chbook.Issue(addr1, common.Big1) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - _, err = chbook.Issue(addr1, common.Big2) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - time.Sleep(1 * interval) - backend.Commit() - - exp = new(big.Int).SetUint64(39) - if chbook.Balance().Cmp(exp) != 0 { - t.Fatalf("expected balance %v, got %v", exp, chbook.Balance()) - }*/ - -} - -func TestCash(t *testing.T) { - // TODO test - /*path := filepath.Join(os.TempDir(), "chequebook-test.json") - backend := newTestBackend() - contr0, _ := deploy(key0, common.Big2, backend) - - chbook, err := NewChequebook(path, contr0, key0, backend) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - chbook.sent[addr1] = new(big.Int).SetUint64(42) - amount := common.Big1 - chbook.balance = new(big.Int).Set(common.Big1) - ch, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - chbox, err := NewInbox(key1, contr0, addr1, &key0.PublicKey, backend) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - // cashing latest cheque - if _, err = chbox.Receive(ch); err != nil { - t.Fatalf("expected no error, got %v", err) - } - if _, err = ch.Cash(chbook.session); err != nil { - t.Fatal("Cash failed:", err) - } - backend.Commit() - - chbook.balance = new(big.Int).Set(common.Big3) - ch0, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - ch1, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - interval := 10 * time.Millisecond - // setting autocash with interval of 10ms - chbox.AutoCash(interval, nil) - _, err = chbox.Receive(ch0) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - _, err = chbox.Receive(ch1) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - // after 3x interval time and 2 cheques received, exactly one cashing tx is sent - time.Sleep(4 * interval) - backend.Commit() - - // after stopping autocash no more tx are sent - ch2, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - chbox.Stop() - _, err = chbox.Receive(ch2) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - time.Sleep(2 * interval) - backend.Commit() - - // autocash below 1 - chbook.balance = big.NewInt(2) - chbox.AutoCash(0, common.Big1) - - ch3, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - ch4, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - _, err = chbox.Receive(ch3) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - _, err = chbox.Receive(ch4) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - // autochash on receipt when maxUncashed is 0 - chbook.balance = new(big.Int).Set(common.Big2) - chbox.AutoCash(0, common.Big0) - - ch5, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - ch6, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - _, err = chbox.Receive(ch5) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - _, err = chbox.Receive(ch6) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit()*/ - -} diff --git a/contracts/chequebook/contract/chequebook.go b/contracts/chequebook/contract/chequebook.go deleted file mode 100644 index d0928706db..0000000000 --- a/contracts/chequebook/contract/chequebook.go +++ /dev/null @@ -1,367 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package contract - -import ( - "math/big" - "strings" - - ethereum "github.com/AlayaNetwork/Alaya-Go" - "github.com/AlayaNetwork/Alaya-Go/accounts/abi" - "github.com/AlayaNetwork/Alaya-Go/accounts/abi/bind" - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/event" -) - -// ChequebookABI is the input ABI used to generate the binding from. -const ChequebookABI = "[{\"constant\":false,\"inputs\":[],\"name\":\"kill\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"sent\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"beneficiary\",\"type\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\"},{\"name\":\"sig_v\",\"type\":\"uint8\"},{\"name\":\"sig_r\",\"type\":\"bytes32\"},{\"name\":\"sig_s\",\"type\":\"bytes32\"}],\"name\":\"cash\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"deadbeat\",\"type\":\"address\"}],\"name\":\"Overdraft\",\"type\":\"event\"}]" - -// ChequebookBin is the compiled bytecode used for deploying new contracts. -const ChequebookBin = `0x606060405260008054600160a060020a033316600160a060020a03199091161790556102ec806100306000396000f3006060604052600436106100565763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166341c0e1b581146100585780637bf786f81461006b578063fbf788d61461009c575b005b341561006357600080fd5b6100566100ca565b341561007657600080fd5b61008a600160a060020a03600435166100f1565b60405190815260200160405180910390f35b34156100a757600080fd5b610056600160a060020a036004351660243560ff60443516606435608435610103565b60005433600160a060020a03908116911614156100ef57600054600160a060020a0316ff5b565b60016020526000908152604090205481565b600160a060020a0385166000908152600160205260408120548190861161012957600080fd5b3087876040516c01000000000000000000000000600160a060020a03948516810282529290931690910260148301526028820152604801604051809103902091506001828686866040516000815260200160405260006040516020015260405193845260ff90921660208085019190915260408085019290925260608401929092526080909201915160208103908084039060008661646e5a03f115156101cf57600080fd5b505060206040510351600054600160a060020a039081169116146101f257600080fd5b50600160a060020a03808716600090815260016020526040902054860390301631811161026257600160a060020a0387166000818152600160205260409081902088905582156108fc0290839051600060405180830381858888f19350505050151561025d57600080fd5b6102b7565b6000547f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f97890600160a060020a0316604051600160a060020a03909116815260200160405180910390a186600160a060020a0316ff5b505050505050505600a165627a7a72305820533e856fc37e3d64d1706bcc7dfb6b1d490c8d566ea498d9d01ec08965a896ca0029` - -// DeployChequebook deploys a new Ethereum contract, binding an instance of Chequebook to it. -func DeployChequebook(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Chequebook, error) { - parsed, err := abi.JSON(strings.NewReader(ChequebookABI)) - if err != nil { - return common.Address{}, nil, nil, err - } - address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(ChequebookBin), backend) - if err != nil { - return common.Address{}, nil, nil, err - } - return address, tx, &Chequebook{ChequebookCaller: ChequebookCaller{contract: contract}, ChequebookTransactor: ChequebookTransactor{contract: contract}, ChequebookFilterer: ChequebookFilterer{contract: contract}}, nil -} - -// Chequebook is an auto generated Go binding around an Ethereum contract. -type Chequebook struct { - ChequebookCaller // Read-only binding to the contract - ChequebookTransactor // Write-only binding to the contract - ChequebookFilterer // Log filterer for contract events -} - -// ChequebookCaller is an auto generated read-only Go binding around an Ethereum contract. -type ChequebookCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// ChequebookTransactor is an auto generated write-only Go binding around an Ethereum contract. -type ChequebookTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// ChequebookFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type ChequebookFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// ChequebookSession is an auto generated Go binding around an Ethereum contract, -// with pre-set call and transact options. -type ChequebookSession struct { - Contract *Chequebook // Generic contract binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// ChequebookCallerSession is an auto generated read-only Go binding around an Ethereum contract, -// with pre-set call options. -type ChequebookCallerSession struct { - Contract *ChequebookCaller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session -} - -// ChequebookTransactorSession is an auto generated write-only Go binding around an Ethereum contract, -// with pre-set transact options. -type ChequebookTransactorSession struct { - Contract *ChequebookTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// ChequebookRaw is an auto generated low-level Go binding around an Ethereum contract. -type ChequebookRaw struct { - Contract *Chequebook // Generic contract binding to access the raw methods on -} - -// ChequebookCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. -type ChequebookCallerRaw struct { - Contract *ChequebookCaller // Generic read-only contract binding to access the raw methods on -} - -// ChequebookTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. -type ChequebookTransactorRaw struct { - Contract *ChequebookTransactor // Generic write-only contract binding to access the raw methods on -} - -// NewChequebook creates a new instance of Chequebook, bound to a specific deployed contract. -func NewChequebook(address common.Address, backend bind.ContractBackend) (*Chequebook, error) { - contract, err := bindChequebook(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &Chequebook{ChequebookCaller: ChequebookCaller{contract: contract}, ChequebookTransactor: ChequebookTransactor{contract: contract}, ChequebookFilterer: ChequebookFilterer{contract: contract}}, nil -} - -// NewChequebookCaller creates a new read-only instance of Chequebook, bound to a specific deployed contract. -func NewChequebookCaller(address common.Address, caller bind.ContractCaller) (*ChequebookCaller, error) { - contract, err := bindChequebook(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &ChequebookCaller{contract: contract}, nil -} - -// NewChequebookTransactor creates a new write-only instance of Chequebook, bound to a specific deployed contract. -func NewChequebookTransactor(address common.Address, transactor bind.ContractTransactor) (*ChequebookTransactor, error) { - contract, err := bindChequebook(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &ChequebookTransactor{contract: contract}, nil -} - -// NewChequebookFilterer creates a new log filterer instance of Chequebook, bound to a specific deployed contract. -func NewChequebookFilterer(address common.Address, filterer bind.ContractFilterer) (*ChequebookFilterer, error) { - contract, err := bindChequebook(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &ChequebookFilterer{contract: contract}, nil -} - -// bindChequebook binds a generic wrapper to an already deployed contract. -func bindChequebook(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := abi.JSON(strings.NewReader(ChequebookABI)) - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_Chequebook *ChequebookRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { - return _Chequebook.Contract.ChequebookCaller.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_Chequebook *ChequebookRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _Chequebook.Contract.ChequebookTransactor.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_Chequebook *ChequebookRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _Chequebook.Contract.ChequebookTransactor.contract.Transact(opts, method, params...) -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_Chequebook *ChequebookCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { - return _Chequebook.Contract.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_Chequebook *ChequebookTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _Chequebook.Contract.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_Chequebook *ChequebookTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _Chequebook.Contract.contract.Transact(opts, method, params...) -} - -// Sent is a free data retrieval call binding the contract method 0x7bf786f8. -// -// Solidity: function sent( address) constant returns(uint256) -func (_Chequebook *ChequebookCaller) Sent(opts *bind.CallOpts, arg0 common.Address) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _Chequebook.contract.Call(opts, out, "sent", arg0) - return *ret0, err -} - -// Sent is a free data retrieval call binding the contract method 0x7bf786f8. -// -// Solidity: function sent( address) constant returns(uint256) -func (_Chequebook *ChequebookSession) Sent(arg0 common.Address) (*big.Int, error) { - return _Chequebook.Contract.Sent(&_Chequebook.CallOpts, arg0) -} - -// Sent is a free data retrieval call binding the contract method 0x7bf786f8. -// -// Solidity: function sent( address) constant returns(uint256) -func (_Chequebook *ChequebookCallerSession) Sent(arg0 common.Address) (*big.Int, error) { - return _Chequebook.Contract.Sent(&_Chequebook.CallOpts, arg0) -} - -// Cash is a paid mutator transaction binding the contract method 0xfbf788d6. -// -// Solidity: function cash(beneficiary address, amount uint256, sig_v uint8, sig_r bytes32, sig_s bytes32) returns() -func (_Chequebook *ChequebookTransactor) Cash(opts *bind.TransactOpts, beneficiary common.Address, amount *big.Int, sigV uint8, sigR [32]byte, sigS [32]byte) (*types.Transaction, error) { - return _Chequebook.contract.Transact(opts, "cash", beneficiary, amount, sigV, sigR, sigS) -} - -// Cash is a paid mutator transaction binding the contract method 0xfbf788d6. -// -// Solidity: function cash(beneficiary address, amount uint256, sig_v uint8, sig_r bytes32, sig_s bytes32) returns() -func (_Chequebook *ChequebookSession) Cash(beneficiary common.Address, amount *big.Int, sigV uint8, sigR [32]byte, sigS [32]byte) (*types.Transaction, error) { - return _Chequebook.Contract.Cash(&_Chequebook.TransactOpts, beneficiary, amount, sigV, sigR, sigS) -} - -// Cash is a paid mutator transaction binding the contract method 0xfbf788d6. -// -// Solidity: function cash(beneficiary address, amount uint256, sig_v uint8, sig_r bytes32, sig_s bytes32) returns() -func (_Chequebook *ChequebookTransactorSession) Cash(beneficiary common.Address, amount *big.Int, sigV uint8, sigR [32]byte, sigS [32]byte) (*types.Transaction, error) { - return _Chequebook.Contract.Cash(&_Chequebook.TransactOpts, beneficiary, amount, sigV, sigR, sigS) -} - -// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. -// -// Solidity: function kill() returns() -func (_Chequebook *ChequebookTransactor) Kill(opts *bind.TransactOpts) (*types.Transaction, error) { - return _Chequebook.contract.Transact(opts, "kill") -} - -// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. -// -// Solidity: function kill() returns() -func (_Chequebook *ChequebookSession) Kill() (*types.Transaction, error) { - return _Chequebook.Contract.Kill(&_Chequebook.TransactOpts) -} - -// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. -// -// Solidity: function kill() returns() -func (_Chequebook *ChequebookTransactorSession) Kill() (*types.Transaction, error) { - return _Chequebook.Contract.Kill(&_Chequebook.TransactOpts) -} - -// ChequebookOverdraftIterator is returned from FilterOverdraft and is used to iterate over the raw logs and unpacked data for Overdraft events raised by the Chequebook contract. -type ChequebookOverdraftIterator struct { - Event *ChequebookOverdraft // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ChequebookOverdraftIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ChequebookOverdraft) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ChequebookOverdraft) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error retruned any retrieval or parsing error occurred during filtering. -func (it *ChequebookOverdraftIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ChequebookOverdraftIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ChequebookOverdraft represents a Overdraft event raised by the Chequebook contract. -type ChequebookOverdraft struct { - Deadbeat common.Address - Raw types.Log // Blockchain specific contextual infos -} - -// FilterOverdraft is a free log retrieval operation binding the contract event 0x2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f978. -// -// Solidity: event Overdraft(deadbeat address) -func (_Chequebook *ChequebookFilterer) FilterOverdraft(opts *bind.FilterOpts) (*ChequebookOverdraftIterator, error) { - - logs, sub, err := _Chequebook.contract.FilterLogs(opts, "Overdraft") - if err != nil { - return nil, err - } - return &ChequebookOverdraftIterator{contract: _Chequebook.contract, event: "Overdraft", logs: logs, sub: sub}, nil -} - -// WatchOverdraft is a free log subscription operation binding the contract event 0x2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f978. -// -// Solidity: event Overdraft(deadbeat address) -func (_Chequebook *ChequebookFilterer) WatchOverdraft(opts *bind.WatchOpts, sink chan<- *ChequebookOverdraft) (event.Subscription, error) { - - logs, sub, err := _Chequebook.contract.WatchLogs(opts, "Overdraft") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ChequebookOverdraft) - if err := _Chequebook.contract.UnpackLog(event, "Overdraft", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} diff --git a/contracts/chequebook/contract/chequebook.sol b/contracts/chequebook/contract/chequebook.sol deleted file mode 100644 index c386cceed8..0000000000 --- a/contracts/chequebook/contract/chequebook.sol +++ /dev/null @@ -1,47 +0,0 @@ -pragma solidity ^0.4.18; - -import "./mortal.sol"; - -/// @title Chequebook for Ethereum micropayments -/// @author Daniel A. Nagy -contract chequebook is mortal { - // Cumulative paid amount in wei to each beneficiary - mapping (address => uint256) public sent; - - /// @notice Overdraft event - event Overdraft(address deadbeat); - - // Allow sending ether to the chequebook. - function() public payable { } - - /// @notice Cash cheque - /// - /// @param beneficiary beneficiary address - /// @param amount cumulative amount in wei - /// @param sig_v signature parameter v - /// @param sig_r signature parameter r - /// @param sig_s signature parameter s - /// The digital signature is calculated on the concatenated triplet of contract address, beneficiary address and cumulative amount - function cash(address beneficiary, uint256 amount, uint8 sig_v, bytes32 sig_r, bytes32 sig_s) public { - // Check if the cheque is old. - // Only cheques that are more recent than the last cashed one are considered. - require(amount > sent[beneficiary]); - // Check the digital signature of the cheque. - bytes32 hash = keccak256(address(this), beneficiary, amount); - require(owner == ecrecover(hash, sig_v, sig_r, sig_s)); - // Attempt sending the difference between the cumulative amount on the cheque - // and the cumulative amount on the last cashed cheque to beneficiary. - uint256 diff = amount - sent[beneficiary]; - if (diff <= this.balance) { - // update the cumulative amount before sending - sent[beneficiary] = amount; - beneficiary.transfer(diff); - } else { - // Upon failure, punish owner for writing a bounced cheque. - // owner.sendToDebtorsPrison(); - Overdraft(owner); - // Compensate beneficiary. - selfdestruct(beneficiary); - } - } -} diff --git a/contracts/chequebook/contract/code.go b/contracts/chequebook/contract/code.go deleted file mode 100644 index d837a9d601..0000000000 --- a/contracts/chequebook/contract/code.go +++ /dev/null @@ -1,5 +0,0 @@ -package contract - -// ContractDeployedCode is used to detect suicides. This constant needs to be -// updated when the contract code is changed. -const ContractDeployedCode = "0x6060604052600436106100565763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166341c0e1b581146100585780637bf786f81461006b578063fbf788d61461009c575b005b341561006357600080fd5b6100566100ca565b341561007657600080fd5b61008a600160a060020a03600435166100f1565b60405190815260200160405180910390f35b34156100a757600080fd5b610056600160a060020a036004351660243560ff60443516606435608435610103565b60005433600160a060020a03908116911614156100ef57600054600160a060020a0316ff5b565b60016020526000908152604090205481565b600160a060020a0385166000908152600160205260408120548190861161012957600080fd5b3087876040516c01000000000000000000000000600160a060020a03948516810282529290931690910260148301526028820152604801604051809103902091506001828686866040516000815260200160405260006040516020015260405193845260ff90921660208085019190915260408085019290925260608401929092526080909201915160208103908084039060008661646e5a03f115156101cf57600080fd5b505060206040510351600054600160a060020a039081169116146101f257600080fd5b50600160a060020a03808716600090815260016020526040902054860390301631811161026257600160a060020a0387166000818152600160205260409081902088905582156108fc0290839051600060405180830381858888f19350505050151561025d57600080fd5b6102b7565b6000547f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f97890600160a060020a0316604051600160a060020a03909116815260200160405180910390a186600160a060020a0316ff5b505050505050505600a165627a7a72305820533e856fc37e3d64d1706bcc7dfb6b1d490c8d566ea498d9d01ec08965a896ca0029" diff --git a/contracts/chequebook/contract/mortal.sol b/contracts/chequebook/contract/mortal.sol deleted file mode 100644 index c43f1e4f79..0000000000 --- a/contracts/chequebook/contract/mortal.sol +++ /dev/null @@ -1,10 +0,0 @@ -pragma solidity ^0.4.0; - -import "./owned.sol"; - -contract mortal is owned { - function kill() public { - if (msg.sender == owner) - selfdestruct(owner); - } -} diff --git a/contracts/chequebook/contract/owned.sol b/contracts/chequebook/contract/owned.sol deleted file mode 100644 index ee9860d343..0000000000 --- a/contracts/chequebook/contract/owned.sol +++ /dev/null @@ -1,15 +0,0 @@ -pragma solidity ^0.4.0; - -contract owned { - address owner; - - modifier onlyowner() { - if (msg.sender == owner) { - _; - } - } - - function owned() public { - owner = msg.sender; - } -} diff --git a/contracts/chequebook/gencode.go b/contracts/chequebook/gencode.go deleted file mode 100644 index 2322ec29dd..0000000000 --- a/contracts/chequebook/gencode.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2016 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 . - -// +build none - -// This program generates contract/code.go, which contains the chequebook code -// after deployment. -package main - -import ( - "fmt" - "io/ioutil" - "math/big" - - "github.com/AlayaNetwork/Alaya-Go/accounts/abi/bind" - "github.com/AlayaNetwork/Alaya-Go/accounts/abi/bind/backends" - "github.com/AlayaNetwork/Alaya-Go/contracts/chequebook/contract" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/crypto" -) - -var ( - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testAlloc = core.GenesisAlloc{ - crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(500000000000)}, - } -) - -func main() { - backend := backends.NewSimulatedBackend(testAlloc, uint64(100000000)) - auth := bind.NewKeyedTransactor(testKey) - - // Deploy the contract, get the code. - addr, _, _, err := contract.DeployChequebook(auth, backend) - if err != nil { - panic(err) - } - backend.Commit() - code, err := backend.CodeAt(nil, addr, nil) - if err != nil { - panic(err) - } - if len(code) == 0 { - panic("empty code") - } - - // Write the output file. - content := fmt.Sprintf(`package contract - -// ContractDeployedCode is used to detect suicides. This constant needs to be -// updated when the contract code is changed. -const ContractDeployedCode = "%#x" -`, code) - if err := ioutil.WriteFile("contract/code.go", []byte(content), 0644); err != nil { - panic(err) - } -} diff --git a/contracts/ens/README.md b/contracts/ens/README.md deleted file mode 100644 index c09b47e39d..0000000000 --- a/contracts/ens/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Swarm ENS interface - -## Usage - -Full documentation for the Ethereum Name Service [can be found as EIP 137](https://github.com/ethereum/EIPs/issues/137). -This package offers a simple binding that streamlines the registration of arbitrary UTF8 domain names to swarm content hashes. - -## Development - -The SOL file in contract subdirectory implements the ENS root registry, a simple -first-in, first-served registrar for the root namespace, and a simple resolver contract; -they're used in tests, and can be used to deploy these contracts for your own purposes. - -The solidity source code can be found at [github.com/arachnid/ens/](https://github.com/arachnid/ens/). - -The go bindings for ENS contracts are generated using `abigen` via the go generator: - -```shell -go generate ./contracts/ens -``` diff --git a/contracts/ens/contract/AbstractENS.sol b/contracts/ens/contract/AbstractENS.sol deleted file mode 100644 index b80a1b0e6b..0000000000 --- a/contracts/ens/contract/AbstractENS.sol +++ /dev/null @@ -1,23 +0,0 @@ -pragma solidity ^0.4.0; - -contract AbstractENS { - function owner(bytes32 node) constant returns(address); - function resolver(bytes32 node) constant returns(address); - function ttl(bytes32 node) constant returns(uint64); - function setOwner(bytes32 node, address owner); - function setSubnodeOwner(bytes32 node, bytes32 label, address owner); - function setResolver(bytes32 node, address resolver); - function setTTL(bytes32 node, uint64 ttl); - - // Logged when the owner of a node assigns a new owner to a subnode. - event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); - - // Logged when the owner of a node transfers ownership to a new account. - event Transfer(bytes32 indexed node, address owner); - - // Logged when the resolver for a node changes. - event NewResolver(bytes32 indexed node, address resolver); - - // Logged when the TTL of a node changes - event NewTTL(bytes32 indexed node, uint64 ttl); -} diff --git a/contracts/ens/contract/ENS.sol b/contracts/ens/contract/ENS.sol deleted file mode 100644 index 47050c19da..0000000000 --- a/contracts/ens/contract/ENS.sol +++ /dev/null @@ -1,94 +0,0 @@ -pragma solidity ^0.4.0; - -import './AbstractENS.sol'; - -/** - * The ENS registry contract. - */ -contract ENS is AbstractENS { - struct Record { - address owner; - address resolver; - uint64 ttl; - } - - mapping(bytes32=>Record) records; - - // Permits modifications only by the owner of the specified node. - modifier only_owner(bytes32 node) { - if (records[node].owner != msg.sender) throw; - _; - } - - /** - * Constructs a new ENS registrar. - */ - function ENS() { - records[0].owner = msg.sender; - } - - /** - * Returns the address that owns the specified node. - */ - function owner(bytes32 node) constant returns (address) { - return records[node].owner; - } - - /** - * Returns the address of the resolver for the specified node. - */ - function resolver(bytes32 node) constant returns (address) { - return records[node].resolver; - } - - /** - * Returns the TTL of a node, and any records associated with it. - */ - function ttl(bytes32 node) constant returns (uint64) { - return records[node].ttl; - } - - /** - * Transfers ownership of a node to a new address. May only be called by the current - * owner of the node. - * @param node The node to transfer ownership of. - * @param owner The address of the new owner. - */ - function setOwner(bytes32 node, address owner) only_owner(node) { - Transfer(node, owner); - records[node].owner = owner; - } - - /** - * Transfers ownership of a subnode sha3(node, label) to a new address. May only be - * called by the owner of the parent node. - * @param node The parent node. - * @param label The hash of the label specifying the subnode. - * @param owner The address of the new owner. - */ - function setSubnodeOwner(bytes32 node, bytes32 label, address owner) only_owner(node) { - var subnode = sha3(node, label); - NewOwner(node, label, owner); - records[subnode].owner = owner; - } - - /** - * Sets the resolver address for the specified node. - * @param node The node to update. - * @param resolver The address of the resolver. - */ - function setResolver(bytes32 node, address resolver) only_owner(node) { - NewResolver(node, resolver); - records[node].resolver = resolver; - } - - /** - * Sets the TTL for the specified node. - * @param node The node to update. - * @param ttl The TTL in seconds. - */ - function setTTL(bytes32 node, uint64 ttl) only_owner(node) { - NewTTL(node, ttl); - records[node].ttl = ttl; - } -} diff --git a/contracts/ens/contract/FIFSRegistrar.sol b/contracts/ens/contract/FIFSRegistrar.sol deleted file mode 100644 index 51629c2b65..0000000000 --- a/contracts/ens/contract/FIFSRegistrar.sol +++ /dev/null @@ -1,39 +0,0 @@ -pragma solidity ^0.4.0; - -import './AbstractENS.sol'; - -/** - * A registrar that allocates subdomains to the first person to claim them. - */ -contract FIFSRegistrar { - AbstractENS ens; - bytes32 rootNode; - - modifier only_owner(bytes32 subnode) { - var node = sha3(rootNode, subnode); - var currentOwner = ens.owner(node); - - if (currentOwner != 0 && currentOwner != msg.sender) throw; - - _; - } - - /** - * Constructor. - * @param ensAddr The address of the ENS registry. - * @param node The node that this registrar administers. - */ - function FIFSRegistrar(AbstractENS ensAddr, bytes32 node) { - ens = ensAddr; - rootNode = node; - } - - /** - * Register a name, or change the owner of an existing registration. - * @param subnode The hash of the label to register. - * @param owner The address of the new owner. - */ - function register(bytes32 subnode, address owner) only_owner(subnode) { - ens.setSubnodeOwner(rootNode, subnode, owner); - } -} diff --git a/contracts/ens/contract/PublicResolver.sol b/contracts/ens/contract/PublicResolver.sol deleted file mode 100644 index 9dcc95689e..0000000000 --- a/contracts/ens/contract/PublicResolver.sol +++ /dev/null @@ -1,212 +0,0 @@ -pragma solidity ^0.4.0; - -import './AbstractENS.sol'; - -/** - * A simple resolver anyone can use; only allows the owner of a node to set its - * address. - */ -contract PublicResolver { - bytes4 constant INTERFACE_META_ID = 0x01ffc9a7; - bytes4 constant ADDR_INTERFACE_ID = 0x3b3b57de; - bytes4 constant CONTENT_INTERFACE_ID = 0xd8389dc5; - bytes4 constant NAME_INTERFACE_ID = 0x691f3431; - bytes4 constant ABI_INTERFACE_ID = 0x2203ab56; - bytes4 constant PUBKEY_INTERFACE_ID = 0xc8690233; - bytes4 constant TEXT_INTERFACE_ID = 0x59d1d43c; - - event AddrChanged(bytes32 indexed node, address a); - event ContentChanged(bytes32 indexed node, bytes32 hash); - event NameChanged(bytes32 indexed node, string name); - event ABIChanged(bytes32 indexed node, uint256 indexed contentType); - event PubkeyChanged(bytes32 indexed node, bytes32 x, bytes32 y); - event TextChanged(bytes32 indexed node, string indexed indexedKey, string key); - - struct PublicKey { - bytes32 x; - bytes32 y; - } - - struct Record { - address addr; - bytes32 content; - string name; - PublicKey pubkey; - mapping(string=>string) text; - mapping(uint256=>bytes) abis; - } - - AbstractENS ens; - mapping(bytes32=>Record) records; - - modifier only_owner(bytes32 node) { - if (ens.owner(node) != msg.sender) throw; - _; - } - - /** - * Constructor. - * @param ensAddr The ENS registrar contract. - */ - function PublicResolver(AbstractENS ensAddr) { - ens = ensAddr; - } - - /** - * Returns true if the resolver implements the interface specified by the provided hash. - * @param interfaceID The ID of the interface to check for. - * @return True if the contract implements the requested interface. - */ - function supportsInterface(bytes4 interfaceID) constant returns (bool) { - return interfaceID == ADDR_INTERFACE_ID || - interfaceID == CONTENT_INTERFACE_ID || - interfaceID == NAME_INTERFACE_ID || - interfaceID == ABI_INTERFACE_ID || - interfaceID == PUBKEY_INTERFACE_ID || - interfaceID == TEXT_INTERFACE_ID || - interfaceID == INTERFACE_META_ID; - } - - /** - * Returns the address associated with an ENS node. - * @param node The ENS node to query. - * @return The associated address. - */ - function addr(bytes32 node) constant returns (address ret) { - ret = records[node].addr; - } - - /** - * Sets the address associated with an ENS node. - * May only be called by the owner of that node in the ENS registry. - * @param node The node to update. - * @param addr The address to set. - */ - function setAddr(bytes32 node, address addr) only_owner(node) { - records[node].addr = addr; - AddrChanged(node, addr); - } - - /** - * Returns the content hash associated with an ENS node. - * Note that this resource type is not standardized, and will likely change - * in future to a resource type based on multihash. - * @param node The ENS node to query. - * @return The associated content hash. - */ - function content(bytes32 node) constant returns (bytes32 ret) { - ret = records[node].content; - } - - /** - * Sets the content hash associated with an ENS node. - * May only be called by the owner of that node in the ENS registry. - * Note that this resource type is not standardized, and will likely change - * in future to a resource type based on multihash. - * @param node The node to update. - * @param hash The content hash to set - */ - function setContent(bytes32 node, bytes32 hash) only_owner(node) { - records[node].content = hash; - ContentChanged(node, hash); - } - - /** - * Returns the name associated with an ENS node, for reverse records. - * Defined in EIP181. - * @param node The ENS node to query. - * @return The associated name. - */ - function name(bytes32 node) constant returns (string ret) { - ret = records[node].name; - } - - /** - * Sets the name associated with an ENS node, for reverse records. - * May only be called by the owner of that node in the ENS registry. - * @param node The node to update. - * @param name The name to set. - */ - function setName(bytes32 node, string name) only_owner(node) { - records[node].name = name; - NameChanged(node, name); - } - - /** - * Returns the ABI associated with an ENS node. - * Defined in EIP205. - * @param node The ENS node to query - * @param contentTypes A bitwise OR of the ABI formats accepted by the caller. - * @return contentType The content type of the return value - * @return data The ABI data - */ - function ABI(bytes32 node, uint256 contentTypes) constant returns (uint256 contentType, bytes data) { - var record = records[node]; - for(contentType = 1; contentType <= contentTypes; contentType <<= 1) { - if ((contentType & contentTypes) != 0 && record.abis[contentType].length > 0) { - data = record.abis[contentType]; - return; - } - } - contentType = 0; - } - - /** - * Sets the ABI associated with an ENS node. - * Nodes may have one ABI of each content type. To remove an ABI, set it to - * the empty string. - * @param node The node to update. - * @param contentType The content type of the ABI - * @param data The ABI data. - */ - function setABI(bytes32 node, uint256 contentType, bytes data) only_owner(node) { - // Content types must be powers of 2 - if (((contentType - 1) & contentType) != 0) throw; - - records[node].abis[contentType] = data; - ABIChanged(node, contentType); - } - - /** - * Returns the SECP256k1 public key associated with an ENS node. - * Defined in EIP 619. - * @param node The ENS node to query - * @return x, y the X and Y coordinates of the curve point for the public key. - */ - function pubkey(bytes32 node) constant returns (bytes32 x, bytes32 y) { - return (records[node].pubkey.x, records[node].pubkey.y); - } - - /** - * Sets the SECP256k1 public key associated with an ENS node. - * @param node The ENS node to query - * @param x the X coordinate of the curve point for the public key. - * @param y the Y coordinate of the curve point for the public key. - */ - function setPubkey(bytes32 node, bytes32 x, bytes32 y) only_owner(node) { - records[node].pubkey = PublicKey(x, y); - PubkeyChanged(node, x, y); - } - - /** - * Returns the text data associated with an ENS node and key. - * @param node The ENS node to query. - * @param key The text data key to query. - * @return The associated text data. - */ - function text(bytes32 node, string key) constant returns (string ret) { - ret = records[node].text[key]; - } - - /** - * Sets the text data associated with an ENS node and key. - * May only be called by the owner of that node in the ENS registry. - * @param node The node to update. - * @param key The key to set. - * @param value The text data value to set. - */ - function setText(bytes32 node, string key, string value) only_owner(node) { - records[node].text[key] = value; - TextChanged(node, key, key); - } -} diff --git a/contracts/ens/contract/ens.go b/contracts/ens/contract/ens.go deleted file mode 100644 index a0c9ec3e13..0000000000 --- a/contracts/ens/contract/ens.go +++ /dev/null @@ -1,879 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package contract - -import ( - "strings" - - ethereum "github.com/AlayaNetwork/Alaya-Go" - "github.com/AlayaNetwork/Alaya-Go/accounts/abi" - "github.com/AlayaNetwork/Alaya-Go/accounts/abi/bind" - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/event" -) - -// ENSABI is the input ABI used to generate the binding from. -const ENSABI = "[{\"constant\":true,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"}],\"name\":\"resolver\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"}],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"},{\"name\":\"label\",\"type\":\"bytes32\"},{\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"setSubnodeOwner\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"},{\"name\":\"ttl\",\"type\":\"uint64\"}],\"name\":\"setTTL\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"}],\"name\":\"ttl\",\"outputs\":[{\"name\":\"\",\"type\":\"uint64\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"},{\"name\":\"resolver\",\"type\":\"address\"}],\"name\":\"setResolver\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"},{\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"node\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"label\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"NewOwner\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"node\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"node\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"resolver\",\"type\":\"address\"}],\"name\":\"NewResolver\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"node\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"ttl\",\"type\":\"uint64\"}],\"name\":\"NewTTL\",\"type\":\"event\"}]" - -// ENSBin is the compiled bytecode used for deploying new contracts. -const ENSBin = `0x6060604052341561000f57600080fd5b60008080526020527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb58054600160a060020a033316600160a060020a0319909116179055610503806100626000396000f3006060604052600436106100825763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416630178b8bf811461008757806302571be3146100b957806306ab5923146100cf57806314ab9038146100f657806316a25cbd146101195780631896f70a1461014c5780635b0fc9c31461016e575b600080fd5b341561009257600080fd5b61009d600435610190565b604051600160a060020a03909116815260200160405180910390f35b34156100c457600080fd5b61009d6004356101ae565b34156100da57600080fd5b6100f4600435602435600160a060020a03604435166101c9565b005b341561010157600080fd5b6100f460043567ffffffffffffffff6024351661028b565b341561012457600080fd5b61012f600435610357565b60405167ffffffffffffffff909116815260200160405180910390f35b341561015757600080fd5b6100f4600435600160a060020a036024351661038e565b341561017957600080fd5b6100f4600435600160a060020a0360243516610434565b600090815260208190526040902060010154600160a060020a031690565b600090815260208190526040902054600160a060020a031690565b600083815260208190526040812054849033600160a060020a039081169116146101f257600080fd5b8484604051918252602082015260409081019051908190039020915083857fce0457fe73731f824cc272376169235128c118b49d344817417c6d108d155e8285604051600160a060020a03909116815260200160405180910390a3506000908152602081905260409020805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a03929092169190911790555050565b600082815260208190526040902054829033600160a060020a039081169116146102b457600080fd5b827f1d4f9bbfc9cab89d66e1a1562f2233ccbf1308cb4f63de2ead5787adddb8fa688360405167ffffffffffffffff909116815260200160405180910390a250600091825260208290526040909120600101805467ffffffffffffffff90921674010000000000000000000000000000000000000000027fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff909216919091179055565b60009081526020819052604090206001015474010000000000000000000000000000000000000000900467ffffffffffffffff1690565b600082815260208190526040902054829033600160a060020a039081169116146103b757600080fd5b827f335721b01866dc23fbee8b6b2c7b1e14d6f05c28cd35a2c934239f94095602a083604051600160a060020a03909116815260200160405180910390a250600091825260208290526040909120600101805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a03909216919091179055565b600082815260208190526040902054829033600160a060020a0390811691161461045d57600080fd5b827fd4735d920b0f87494915f556dd9b54c8f309026070caea5c737245152564d26683604051600160a060020a03909116815260200160405180910390a250600091825260208290526040909120805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a039092169190911790555600a165627a7a72305820f4c798d4c84c9912f389f64631e85e8d16c3e6644f8c2e1579936015c7d5f6660029` - -// DeployENS deploys a new Ethereum contract, binding an instance of ENS to it. -func DeployENS(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *ENS, error) { - parsed, err := abi.JSON(strings.NewReader(ENSABI)) - if err != nil { - return common.Address{}, nil, nil, err - } - address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(ENSBin), backend) - if err != nil { - return common.Address{}, nil, nil, err - } - return address, tx, &ENS{ENSCaller: ENSCaller{contract: contract}, ENSTransactor: ENSTransactor{contract: contract}, ENSFilterer: ENSFilterer{contract: contract}}, nil -} - -// ENS is an auto generated Go binding around an Ethereum contract. -type ENS struct { - ENSCaller // Read-only binding to the contract - ENSTransactor // Write-only binding to the contract - ENSFilterer // Log filterer for contract events -} - -// ENSCaller is an auto generated read-only Go binding around an Ethereum contract. -type ENSCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// ENSTransactor is an auto generated write-only Go binding around an Ethereum contract. -type ENSTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// ENSFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type ENSFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// ENSSession is an auto generated Go binding around an Ethereum contract, -// with pre-set call and transact options. -type ENSSession struct { - Contract *ENS // Generic contract binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// ENSCallerSession is an auto generated read-only Go binding around an Ethereum contract, -// with pre-set call options. -type ENSCallerSession struct { - Contract *ENSCaller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session -} - -// ENSTransactorSession is an auto generated write-only Go binding around an Ethereum contract, -// with pre-set transact options. -type ENSTransactorSession struct { - Contract *ENSTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// ENSRaw is an auto generated low-level Go binding around an Ethereum contract. -type ENSRaw struct { - Contract *ENS // Generic contract binding to access the raw methods on -} - -// ENSCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. -type ENSCallerRaw struct { - Contract *ENSCaller // Generic read-only contract binding to access the raw methods on -} - -// ENSTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. -type ENSTransactorRaw struct { - Contract *ENSTransactor // Generic write-only contract binding to access the raw methods on -} - -// NewENS creates a new instance of ENS, bound to a specific deployed contract. -func NewENS(address common.Address, backend bind.ContractBackend) (*ENS, error) { - contract, err := bindENS(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &ENS{ENSCaller: ENSCaller{contract: contract}, ENSTransactor: ENSTransactor{contract: contract}, ENSFilterer: ENSFilterer{contract: contract}}, nil -} - -// NewENSCaller creates a new read-only instance of ENS, bound to a specific deployed contract. -func NewENSCaller(address common.Address, caller bind.ContractCaller) (*ENSCaller, error) { - contract, err := bindENS(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &ENSCaller{contract: contract}, nil -} - -// NewENSTransactor creates a new write-only instance of ENS, bound to a specific deployed contract. -func NewENSTransactor(address common.Address, transactor bind.ContractTransactor) (*ENSTransactor, error) { - contract, err := bindENS(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &ENSTransactor{contract: contract}, nil -} - -// NewENSFilterer creates a new log filterer instance of ENS, bound to a specific deployed contract. -func NewENSFilterer(address common.Address, filterer bind.ContractFilterer) (*ENSFilterer, error) { - contract, err := bindENS(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &ENSFilterer{contract: contract}, nil -} - -// bindENS binds a generic wrapper to an already deployed contract. -func bindENS(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := abi.JSON(strings.NewReader(ENSABI)) - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_ENS *ENSRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { - return _ENS.Contract.ENSCaller.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_ENS *ENSRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _ENS.Contract.ENSTransactor.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_ENS *ENSRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _ENS.Contract.ENSTransactor.contract.Transact(opts, method, params...) -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_ENS *ENSCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { - return _ENS.Contract.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_ENS *ENSTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _ENS.Contract.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_ENS *ENSTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _ENS.Contract.contract.Transact(opts, method, params...) -} - -// Owner is a free data retrieval call binding the contract method 0x02571be3. -// -// Solidity: function owner(node bytes32) constant returns(address) -func (_ENS *ENSCaller) Owner(opts *bind.CallOpts, node [32]byte) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _ENS.contract.Call(opts, out, "owner", node) - return *ret0, err -} - -// Owner is a free data retrieval call binding the contract method 0x02571be3. -// -// Solidity: function owner(node bytes32) constant returns(address) -func (_ENS *ENSSession) Owner(node [32]byte) (common.Address, error) { - return _ENS.Contract.Owner(&_ENS.CallOpts, node) -} - -// Owner is a free data retrieval call binding the contract method 0x02571be3. -// -// Solidity: function owner(node bytes32) constant returns(address) -func (_ENS *ENSCallerSession) Owner(node [32]byte) (common.Address, error) { - return _ENS.Contract.Owner(&_ENS.CallOpts, node) -} - -// Resolver is a free data retrieval call binding the contract method 0x0178b8bf. -// -// Solidity: function resolver(node bytes32) constant returns(address) -func (_ENS *ENSCaller) Resolver(opts *bind.CallOpts, node [32]byte) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _ENS.contract.Call(opts, out, "resolver", node) - return *ret0, err -} - -// Resolver is a free data retrieval call binding the contract method 0x0178b8bf. -// -// Solidity: function resolver(node bytes32) constant returns(address) -func (_ENS *ENSSession) Resolver(node [32]byte) (common.Address, error) { - return _ENS.Contract.Resolver(&_ENS.CallOpts, node) -} - -// Resolver is a free data retrieval call binding the contract method 0x0178b8bf. -// -// Solidity: function resolver(node bytes32) constant returns(address) -func (_ENS *ENSCallerSession) Resolver(node [32]byte) (common.Address, error) { - return _ENS.Contract.Resolver(&_ENS.CallOpts, node) -} - -// TTL is a free data retrieval call binding the contract method 0x16a25cbd. -// -// Solidity: function ttl(node bytes32) constant returns(uint64) -func (_ENS *ENSCaller) TTL(opts *bind.CallOpts, node [32]byte) (uint64, error) { - var ( - ret0 = new(uint64) - ) - out := ret0 - err := _ENS.contract.Call(opts, out, "ttl", node) - return *ret0, err -} - -// TTL is a free data retrieval call binding the contract method 0x16a25cbd. -// -// Solidity: function ttl(node bytes32) constant returns(uint64) -func (_ENS *ENSSession) TTL(node [32]byte) (uint64, error) { - return _ENS.Contract.TTL(&_ENS.CallOpts, node) -} - -// TTL is a free data retrieval call binding the contract method 0x16a25cbd. -// -// Solidity: function ttl(node bytes32) constant returns(uint64) -func (_ENS *ENSCallerSession) TTL(node [32]byte) (uint64, error) { - return _ENS.Contract.TTL(&_ENS.CallOpts, node) -} - -// SetOwner is a paid mutator transaction binding the contract method 0x5b0fc9c3. -// -// Solidity: function setOwner(node bytes32, owner address) returns() -func (_ENS *ENSTransactor) SetOwner(opts *bind.TransactOpts, node [32]byte, owner common.Address) (*types.Transaction, error) { - return _ENS.contract.Transact(opts, "setOwner", node, owner) -} - -// SetOwner is a paid mutator transaction binding the contract method 0x5b0fc9c3. -// -// Solidity: function setOwner(node bytes32, owner address) returns() -func (_ENS *ENSSession) SetOwner(node [32]byte, owner common.Address) (*types.Transaction, error) { - return _ENS.Contract.SetOwner(&_ENS.TransactOpts, node, owner) -} - -// SetOwner is a paid mutator transaction binding the contract method 0x5b0fc9c3. -// -// Solidity: function setOwner(node bytes32, owner address) returns() -func (_ENS *ENSTransactorSession) SetOwner(node [32]byte, owner common.Address) (*types.Transaction, error) { - return _ENS.Contract.SetOwner(&_ENS.TransactOpts, node, owner) -} - -// SetResolver is a paid mutator transaction binding the contract method 0x1896f70a. -// -// Solidity: function setResolver(node bytes32, resolver address) returns() -func (_ENS *ENSTransactor) SetResolver(opts *bind.TransactOpts, node [32]byte, resolver common.Address) (*types.Transaction, error) { - return _ENS.contract.Transact(opts, "setResolver", node, resolver) -} - -// SetResolver is a paid mutator transaction binding the contract method 0x1896f70a. -// -// Solidity: function setResolver(node bytes32, resolver address) returns() -func (_ENS *ENSSession) SetResolver(node [32]byte, resolver common.Address) (*types.Transaction, error) { - return _ENS.Contract.SetResolver(&_ENS.TransactOpts, node, resolver) -} - -// SetResolver is a paid mutator transaction binding the contract method 0x1896f70a. -// -// Solidity: function setResolver(node bytes32, resolver address) returns() -func (_ENS *ENSTransactorSession) SetResolver(node [32]byte, resolver common.Address) (*types.Transaction, error) { - return _ENS.Contract.SetResolver(&_ENS.TransactOpts, node, resolver) -} - -// SetSubnodeOwner is a paid mutator transaction binding the contract method 0x06ab5923. -// -// Solidity: function setSubnodeOwner(node bytes32, label bytes32, owner address) returns() -func (_ENS *ENSTransactor) SetSubnodeOwner(opts *bind.TransactOpts, node [32]byte, label [32]byte, owner common.Address) (*types.Transaction, error) { - return _ENS.contract.Transact(opts, "setSubnodeOwner", node, label, owner) -} - -// SetSubnodeOwner is a paid mutator transaction binding the contract method 0x06ab5923. -// -// Solidity: function setSubnodeOwner(node bytes32, label bytes32, owner address) returns() -func (_ENS *ENSSession) SetSubnodeOwner(node [32]byte, label [32]byte, owner common.Address) (*types.Transaction, error) { - return _ENS.Contract.SetSubnodeOwner(&_ENS.TransactOpts, node, label, owner) -} - -// SetSubnodeOwner is a paid mutator transaction binding the contract method 0x06ab5923. -// -// Solidity: function setSubnodeOwner(node bytes32, label bytes32, owner address) returns() -func (_ENS *ENSTransactorSession) SetSubnodeOwner(node [32]byte, label [32]byte, owner common.Address) (*types.Transaction, error) { - return _ENS.Contract.SetSubnodeOwner(&_ENS.TransactOpts, node, label, owner) -} - -// SetTTL is a paid mutator transaction binding the contract method 0x14ab9038. -// -// Solidity: function setTTL(node bytes32, ttl uint64) returns() -func (_ENS *ENSTransactor) SetTTL(opts *bind.TransactOpts, node [32]byte, ttl uint64) (*types.Transaction, error) { - return _ENS.contract.Transact(opts, "setTTL", node, ttl) -} - -// SetTTL is a paid mutator transaction binding the contract method 0x14ab9038. -// -// Solidity: function setTTL(node bytes32, ttl uint64) returns() -func (_ENS *ENSSession) SetTTL(node [32]byte, ttl uint64) (*types.Transaction, error) { - return _ENS.Contract.SetTTL(&_ENS.TransactOpts, node, ttl) -} - -// SetTTL is a paid mutator transaction binding the contract method 0x14ab9038. -// -// Solidity: function setTTL(node bytes32, ttl uint64) returns() -func (_ENS *ENSTransactorSession) SetTTL(node [32]byte, ttl uint64) (*types.Transaction, error) { - return _ENS.Contract.SetTTL(&_ENS.TransactOpts, node, ttl) -} - -// ENSNewOwnerIterator is returned from FilterNewOwner and is used to iterate over the raw logs and unpacked data for NewOwner events raised by the ENS contract. -type ENSNewOwnerIterator struct { - Event *ENSNewOwner // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ENSNewOwnerIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ENSNewOwner) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ENSNewOwner) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error retruned any retrieval or parsing error occurred during filtering. -func (it *ENSNewOwnerIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ENSNewOwnerIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ENSNewOwner represents a NewOwner event raised by the ENS contract. -type ENSNewOwner struct { - Node [32]byte - Label [32]byte - Owner common.Address - Raw types.Log // Blockchain specific contextual infos -} - -// FilterNewOwner is a free log retrieval operation binding the contract event 0xce0457fe73731f824cc272376169235128c118b49d344817417c6d108d155e82. -// -// Solidity: event NewOwner(node indexed bytes32, label indexed bytes32, owner address) -func (_ENS *ENSFilterer) FilterNewOwner(opts *bind.FilterOpts, node [][32]byte, label [][32]byte) (*ENSNewOwnerIterator, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - var labelRule []interface{} - for _, labelItem := range label { - labelRule = append(labelRule, labelItem) - } - - logs, sub, err := _ENS.contract.FilterLogs(opts, "NewOwner", nodeRule, labelRule) - if err != nil { - return nil, err - } - return &ENSNewOwnerIterator{contract: _ENS.contract, event: "NewOwner", logs: logs, sub: sub}, nil -} - -// WatchNewOwner is a free log subscription operation binding the contract event 0xce0457fe73731f824cc272376169235128c118b49d344817417c6d108d155e82. -// -// Solidity: event NewOwner(node indexed bytes32, label indexed bytes32, owner address) -func (_ENS *ENSFilterer) WatchNewOwner(opts *bind.WatchOpts, sink chan<- *ENSNewOwner, node [][32]byte, label [][32]byte) (event.Subscription, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - var labelRule []interface{} - for _, labelItem := range label { - labelRule = append(labelRule, labelItem) - } - - logs, sub, err := _ENS.contract.WatchLogs(opts, "NewOwner", nodeRule, labelRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ENSNewOwner) - if err := _ENS.contract.UnpackLog(event, "NewOwner", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ENSNewResolverIterator is returned from FilterNewResolver and is used to iterate over the raw logs and unpacked data for NewResolver events raised by the ENS contract. -type ENSNewResolverIterator struct { - Event *ENSNewResolver // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ENSNewResolverIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ENSNewResolver) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ENSNewResolver) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error retruned any retrieval or parsing error occurred during filtering. -func (it *ENSNewResolverIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ENSNewResolverIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ENSNewResolver represents a NewResolver event raised by the ENS contract. -type ENSNewResolver struct { - Node [32]byte - Resolver common.Address - Raw types.Log // Blockchain specific contextual infos -} - -// FilterNewResolver is a free log retrieval operation binding the contract event 0x335721b01866dc23fbee8b6b2c7b1e14d6f05c28cd35a2c934239f94095602a0. -// -// Solidity: event NewResolver(node indexed bytes32, resolver address) -func (_ENS *ENSFilterer) FilterNewResolver(opts *bind.FilterOpts, node [][32]byte) (*ENSNewResolverIterator, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - - logs, sub, err := _ENS.contract.FilterLogs(opts, "NewResolver", nodeRule) - if err != nil { - return nil, err - } - return &ENSNewResolverIterator{contract: _ENS.contract, event: "NewResolver", logs: logs, sub: sub}, nil -} - -// WatchNewResolver is a free log subscription operation binding the contract event 0x335721b01866dc23fbee8b6b2c7b1e14d6f05c28cd35a2c934239f94095602a0. -// -// Solidity: event NewResolver(node indexed bytes32, resolver address) -func (_ENS *ENSFilterer) WatchNewResolver(opts *bind.WatchOpts, sink chan<- *ENSNewResolver, node [][32]byte) (event.Subscription, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - - logs, sub, err := _ENS.contract.WatchLogs(opts, "NewResolver", nodeRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ENSNewResolver) - if err := _ENS.contract.UnpackLog(event, "NewResolver", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ENSNewTTLIterator is returned from FilterNewTTL and is used to iterate over the raw logs and unpacked data for NewTTL events raised by the ENS contract. -type ENSNewTTLIterator struct { - Event *ENSNewTTL // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ENSNewTTLIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ENSNewTTL) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ENSNewTTL) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error retruned any retrieval or parsing error occurred during filtering. -func (it *ENSNewTTLIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ENSNewTTLIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ENSNewTTL represents a NewTTL event raised by the ENS contract. -type ENSNewTTL struct { - Node [32]byte - TTL uint64 - Raw types.Log // Blockchain specific contextual infos -} - -// FilterNewTTL is a free log retrieval operation binding the contract event 0x1d4f9bbfc9cab89d66e1a1562f2233ccbf1308cb4f63de2ead5787adddb8fa68. -// -// Solidity: event NewTTL(node indexed bytes32, ttl uint64) -func (_ENS *ENSFilterer) FilterNewTTL(opts *bind.FilterOpts, node [][32]byte) (*ENSNewTTLIterator, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - - logs, sub, err := _ENS.contract.FilterLogs(opts, "NewTTL", nodeRule) - if err != nil { - return nil, err - } - return &ENSNewTTLIterator{contract: _ENS.contract, event: "NewTTL", logs: logs, sub: sub}, nil -} - -// WatchNewTTL is a free log subscription operation binding the contract event 0x1d4f9bbfc9cab89d66e1a1562f2233ccbf1308cb4f63de2ead5787adddb8fa68. -// -// Solidity: event NewTTL(node indexed bytes32, ttl uint64) -func (_ENS *ENSFilterer) WatchNewTTL(opts *bind.WatchOpts, sink chan<- *ENSNewTTL, node [][32]byte) (event.Subscription, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - - logs, sub, err := _ENS.contract.WatchLogs(opts, "NewTTL", nodeRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ENSNewTTL) - if err := _ENS.contract.UnpackLog(event, "NewTTL", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ENSTransferIterator is returned from FilterTransfer and is used to iterate over the raw logs and unpacked data for Transfer events raised by the ENS contract. -type ENSTransferIterator struct { - Event *ENSTransfer // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ENSTransferIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ENSTransfer) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ENSTransfer) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error retruned any retrieval or parsing error occurred during filtering. -func (it *ENSTransferIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ENSTransferIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ENSTransfer represents a Transfer event raised by the ENS contract. -type ENSTransfer struct { - Node [32]byte - Owner common.Address - Raw types.Log // Blockchain specific contextual infos -} - -// FilterTransfer is a free log retrieval operation binding the contract event 0xd4735d920b0f87494915f556dd9b54c8f309026070caea5c737245152564d266. -// -// Solidity: event Transfer(node indexed bytes32, owner address) -func (_ENS *ENSFilterer) FilterTransfer(opts *bind.FilterOpts, node [][32]byte) (*ENSTransferIterator, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - - logs, sub, err := _ENS.contract.FilterLogs(opts, "Transfer", nodeRule) - if err != nil { - return nil, err - } - return &ENSTransferIterator{contract: _ENS.contract, event: "Transfer", logs: logs, sub: sub}, nil -} - -// WatchTransfer is a free log subscription operation binding the contract event 0xd4735d920b0f87494915f556dd9b54c8f309026070caea5c737245152564d266. -// -// Solidity: event Transfer(node indexed bytes32, owner address) -func (_ENS *ENSFilterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *ENSTransfer, node [][32]byte) (event.Subscription, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - - logs, sub, err := _ENS.contract.WatchLogs(opts, "Transfer", nodeRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ENSTransfer) - if err := _ENS.contract.UnpackLog(event, "Transfer", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} diff --git a/contracts/ens/contract/fifsregistrar.go b/contracts/ens/contract/fifsregistrar.go deleted file mode 100644 index 27454c0a37..0000000000 --- a/contracts/ens/contract/fifsregistrar.go +++ /dev/null @@ -1,195 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package contract - -import ( - "strings" - - "github.com/AlayaNetwork/Alaya-Go/accounts/abi" - "github.com/AlayaNetwork/Alaya-Go/accounts/abi/bind" - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core/types" -) - -// FIFSRegistrarABI is the input ABI used to generate the binding from. -const FIFSRegistrarABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"subnode\",\"type\":\"bytes32\"},{\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"register\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"ensAddr\",\"type\":\"address\"},{\"name\":\"node\",\"type\":\"bytes32\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]" - -// FIFSRegistrarBin is the compiled bytecode used for deploying new contracts. -const FIFSRegistrarBin = `0x6060604052341561000f57600080fd5b604051604080610224833981016040528080519190602001805160008054600160a060020a03909516600160a060020a03199095169490941790935550506001556101c58061005f6000396000f3006060604052600436106100275763ffffffff60e060020a600035041663d22057a9811461002c575b600080fd5b341561003757600080fd5b61004e600435600160a060020a0360243516610050565b005b816000806001548360405191825260208201526040908101905190819003902060008054919350600160a060020a03909116906302571be39084906040516020015260405160e060020a63ffffffff84160281526004810191909152602401602060405180830381600087803b15156100c857600080fd5b6102c65a03f115156100d957600080fd5b5050506040518051915050600160a060020a0381161580159061010e575033600160a060020a031681600160a060020a031614155b1561011857600080fd5b600054600154600160a060020a03909116906306ab592390878760405160e060020a63ffffffff861602815260048101939093526024830191909152600160a060020a03166044820152606401600060405180830381600087803b151561017e57600080fd5b6102c65a03f1151561018f57600080fd5b50505050505050505600a165627a7a723058206fb963cb168d5e3a51af12cd6bb23e324dbd32dd4954f43653ba27e66b68ea650029` - -// DeployFIFSRegistrar deploys a new Ethereum contract, binding an instance of FIFSRegistrar to it. -func DeployFIFSRegistrar(auth *bind.TransactOpts, backend bind.ContractBackend, ensAddr common.Address, node [32]byte) (common.Address, *types.Transaction, *FIFSRegistrar, error) { - parsed, err := abi.JSON(strings.NewReader(FIFSRegistrarABI)) - if err != nil { - return common.Address{}, nil, nil, err - } - address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(FIFSRegistrarBin), backend, ensAddr, node) - if err != nil { - return common.Address{}, nil, nil, err - } - return address, tx, &FIFSRegistrar{FIFSRegistrarCaller: FIFSRegistrarCaller{contract: contract}, FIFSRegistrarTransactor: FIFSRegistrarTransactor{contract: contract}, FIFSRegistrarFilterer: FIFSRegistrarFilterer{contract: contract}}, nil -} - -// FIFSRegistrar is an auto generated Go binding around an Ethereum contract. -type FIFSRegistrar struct { - FIFSRegistrarCaller // Read-only binding to the contract - FIFSRegistrarTransactor // Write-only binding to the contract - FIFSRegistrarFilterer // Log filterer for contract events -} - -// FIFSRegistrarCaller is an auto generated read-only Go binding around an Ethereum contract. -type FIFSRegistrarCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// FIFSRegistrarTransactor is an auto generated write-only Go binding around an Ethereum contract. -type FIFSRegistrarTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// FIFSRegistrarFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type FIFSRegistrarFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// FIFSRegistrarSession is an auto generated Go binding around an Ethereum contract, -// with pre-set call and transact options. -type FIFSRegistrarSession struct { - Contract *FIFSRegistrar // Generic contract binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// FIFSRegistrarCallerSession is an auto generated read-only Go binding around an Ethereum contract, -// with pre-set call options. -type FIFSRegistrarCallerSession struct { - Contract *FIFSRegistrarCaller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session -} - -// FIFSRegistrarTransactorSession is an auto generated write-only Go binding around an Ethereum contract, -// with pre-set transact options. -type FIFSRegistrarTransactorSession struct { - Contract *FIFSRegistrarTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// FIFSRegistrarRaw is an auto generated low-level Go binding around an Ethereum contract. -type FIFSRegistrarRaw struct { - Contract *FIFSRegistrar // Generic contract binding to access the raw methods on -} - -// FIFSRegistrarCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. -type FIFSRegistrarCallerRaw struct { - Contract *FIFSRegistrarCaller // Generic read-only contract binding to access the raw methods on -} - -// FIFSRegistrarTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. -type FIFSRegistrarTransactorRaw struct { - Contract *FIFSRegistrarTransactor // Generic write-only contract binding to access the raw methods on -} - -// NewFIFSRegistrar creates a new instance of FIFSRegistrar, bound to a specific deployed contract. -func NewFIFSRegistrar(address common.Address, backend bind.ContractBackend) (*FIFSRegistrar, error) { - contract, err := bindFIFSRegistrar(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &FIFSRegistrar{FIFSRegistrarCaller: FIFSRegistrarCaller{contract: contract}, FIFSRegistrarTransactor: FIFSRegistrarTransactor{contract: contract}, FIFSRegistrarFilterer: FIFSRegistrarFilterer{contract: contract}}, nil -} - -// NewFIFSRegistrarCaller creates a new read-only instance of FIFSRegistrar, bound to a specific deployed contract. -func NewFIFSRegistrarCaller(address common.Address, caller bind.ContractCaller) (*FIFSRegistrarCaller, error) { - contract, err := bindFIFSRegistrar(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &FIFSRegistrarCaller{contract: contract}, nil -} - -// NewFIFSRegistrarTransactor creates a new write-only instance of FIFSRegistrar, bound to a specific deployed contract. -func NewFIFSRegistrarTransactor(address common.Address, transactor bind.ContractTransactor) (*FIFSRegistrarTransactor, error) { - contract, err := bindFIFSRegistrar(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &FIFSRegistrarTransactor{contract: contract}, nil -} - -// NewFIFSRegistrarFilterer creates a new log filterer instance of FIFSRegistrar, bound to a specific deployed contract. -func NewFIFSRegistrarFilterer(address common.Address, filterer bind.ContractFilterer) (*FIFSRegistrarFilterer, error) { - contract, err := bindFIFSRegistrar(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &FIFSRegistrarFilterer{contract: contract}, nil -} - -// bindFIFSRegistrar binds a generic wrapper to an already deployed contract. -func bindFIFSRegistrar(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := abi.JSON(strings.NewReader(FIFSRegistrarABI)) - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_FIFSRegistrar *FIFSRegistrarRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { - return _FIFSRegistrar.Contract.FIFSRegistrarCaller.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_FIFSRegistrar *FIFSRegistrarRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _FIFSRegistrar.Contract.FIFSRegistrarTransactor.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_FIFSRegistrar *FIFSRegistrarRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _FIFSRegistrar.Contract.FIFSRegistrarTransactor.contract.Transact(opts, method, params...) -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_FIFSRegistrar *FIFSRegistrarCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { - return _FIFSRegistrar.Contract.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_FIFSRegistrar *FIFSRegistrarTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _FIFSRegistrar.Contract.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_FIFSRegistrar *FIFSRegistrarTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _FIFSRegistrar.Contract.contract.Transact(opts, method, params...) -} - -// Register is a paid mutator transaction binding the contract method 0xd22057a9. -// -// Solidity: function register(subnode bytes32, owner address) returns() -func (_FIFSRegistrar *FIFSRegistrarTransactor) Register(opts *bind.TransactOpts, subnode [32]byte, owner common.Address) (*types.Transaction, error) { - return _FIFSRegistrar.contract.Transact(opts, "register", subnode, owner) -} - -// Register is a paid mutator transaction binding the contract method 0xd22057a9. -// -// Solidity: function register(subnode bytes32, owner address) returns() -func (_FIFSRegistrar *FIFSRegistrarSession) Register(subnode [32]byte, owner common.Address) (*types.Transaction, error) { - return _FIFSRegistrar.Contract.Register(&_FIFSRegistrar.TransactOpts, subnode, owner) -} - -// Register is a paid mutator transaction binding the contract method 0xd22057a9. -// -// Solidity: function register(subnode bytes32, owner address) returns() -func (_FIFSRegistrar *FIFSRegistrarTransactorSession) Register(subnode [32]byte, owner common.Address) (*types.Transaction, error) { - return _FIFSRegistrar.Contract.Register(&_FIFSRegistrar.TransactOpts, subnode, owner) -} diff --git a/contracts/ens/contract/publicresolver.go b/contracts/ens/contract/publicresolver.go deleted file mode 100644 index ebe3fec773..0000000000 --- a/contracts/ens/contract/publicresolver.go +++ /dev/null @@ -1,1321 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package contract - -import ( - "math/big" - "strings" - - ethereum "github.com/AlayaNetwork/Alaya-Go" - "github.com/AlayaNetwork/Alaya-Go/accounts/abi" - "github.com/AlayaNetwork/Alaya-Go/accounts/abi/bind" - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/event" -) - -// PublicResolverABI is the input ABI used to generate the binding from. -const PublicResolverABI = "[{\"constant\":true,\"inputs\":[{\"name\":\"interfaceID\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"},{\"name\":\"key\",\"type\":\"string\"},{\"name\":\"value\",\"type\":\"string\"}],\"name\":\"setText\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"},{\"name\":\"contentTypes\",\"type\":\"uint256\"}],\"name\":\"ABI\",\"outputs\":[{\"name\":\"contentType\",\"type\":\"uint256\"},{\"name\":\"data\",\"type\":\"bytes\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"},{\"name\":\"x\",\"type\":\"bytes32\"},{\"name\":\"y\",\"type\":\"bytes32\"}],\"name\":\"setPubkey\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"}],\"name\":\"content\",\"outputs\":[{\"name\":\"ret\",\"type\":\"bytes32\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"}],\"name\":\"addr\",\"outputs\":[{\"name\":\"ret\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"},{\"name\":\"key\",\"type\":\"string\"}],\"name\":\"text\",\"outputs\":[{\"name\":\"ret\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"},{\"name\":\"contentType\",\"type\":\"uint256\"},{\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"setABI\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"}],\"name\":\"name\",\"outputs\":[{\"name\":\"ret\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"},{\"name\":\"name\",\"type\":\"string\"}],\"name\":\"setName\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"},{\"name\":\"hash\",\"type\":\"bytes32\"}],\"name\":\"setContent\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"}],\"name\":\"pubkey\",\"outputs\":[{\"name\":\"x\",\"type\":\"bytes32\"},{\"name\":\"y\",\"type\":\"bytes32\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"node\",\"type\":\"bytes32\"},{\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setAddr\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"ensAddr\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"node\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"a\",\"type\":\"address\"}],\"name\":\"AddrChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"node\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"hash\",\"type\":\"bytes32\"}],\"name\":\"ContentChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"node\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"name\",\"type\":\"string\"}],\"name\":\"NameChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"node\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"contentType\",\"type\":\"uint256\"}],\"name\":\"ABIChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"node\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"x\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"y\",\"type\":\"bytes32\"}],\"name\":\"PubkeyChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"node\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"indexedKey\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"key\",\"type\":\"string\"}],\"name\":\"TextChanged\",\"type\":\"event\"}]" - -// PublicResolverBin is the compiled bytecode used for deploying new contracts. -const PublicResolverBin = `0x6060604052341561000f57600080fd5b6040516020806111b28339810160405280805160008054600160a060020a03909216600160a060020a0319909216919091179055505061115e806100546000396000f3006060604052600436106100ab5763ffffffff60e060020a60003504166301ffc9a781146100b057806310f13a8c146100e45780632203ab561461017e57806329cd62ea146102155780632dff6941146102315780633b3b57de1461025957806359d1d43c1461028b578063623195b014610358578063691f3431146103b457806377372213146103ca578063c3d014d614610420578063c869023314610439578063d5fa2b0014610467575b600080fd5b34156100bb57600080fd5b6100d0600160e060020a031960043516610489565b604051901515815260200160405180910390f35b34156100ef57600080fd5b61017c600480359060446024803590810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284378201915050505050509190803590602001908201803590602001908080601f0160208091040260200160405190810160405281815292919060208401838380828437509496506105f695505050505050565b005b341561018957600080fd5b610197600435602435610807565b60405182815260406020820181815290820183818151815260200191508051906020019080838360005b838110156101d95780820151838201526020016101c1565b50505050905090810190601f1680156102065780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b341561022057600080fd5b61017c600435602435604435610931565b341561023c57600080fd5b610247600435610a30565b60405190815260200160405180910390f35b341561026457600080fd5b61026f600435610a46565b604051600160a060020a03909116815260200160405180910390f35b341561029657600080fd5b6102e1600480359060446024803590810190830135806020601f82018190048102016040519081016040528181529291906020840183838082843750949650610a6195505050505050565b60405160208082528190810183818151815260200191508051906020019080838360005b8381101561031d578082015183820152602001610305565b50505050905090810190601f16801561034a5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561036357600080fd5b61017c600480359060248035919060649060443590810190830135806020601f82018190048102016040519081016040528181529291906020840183838082843750949650610b8095505050505050565b34156103bf57600080fd5b6102e1600435610c7c565b34156103d557600080fd5b61017c600480359060446024803590810190830135806020601f82018190048102016040519081016040528181529291906020840183838082843750949650610d4295505050505050565b341561042b57600080fd5b61017c600435602435610e8c565b341561044457600080fd5b61044f600435610f65565b60405191825260208201526040908101905180910390f35b341561047257600080fd5b61017c600435600160a060020a0360243516610f82565b6000600160e060020a031982167f3b3b57de0000000000000000000000000000000000000000000000000000000014806104ec5750600160e060020a031982167fd8389dc500000000000000000000000000000000000000000000000000000000145b806105205750600160e060020a031982167f691f343100000000000000000000000000000000000000000000000000000000145b806105545750600160e060020a031982167f2203ab5600000000000000000000000000000000000000000000000000000000145b806105885750600160e060020a031982167fc869023300000000000000000000000000000000000000000000000000000000145b806105bc5750600160e060020a031982167f59d1d43c00000000000000000000000000000000000000000000000000000000145b806105f05750600160e060020a031982167f01ffc9a700000000000000000000000000000000000000000000000000000000145b92915050565b600080548491600160a060020a033381169216906302571be39084906040516020015260405160e060020a63ffffffff84160281526004810191909152602401602060405180830381600087803b151561064f57600080fd5b6102c65a03f1151561066057600080fd5b50505060405180519050600160a060020a031614151561067f57600080fd5b6000848152600160205260409081902083916005909101908590518082805190602001908083835b602083106106c65780518252601f1990920191602091820191016106a7565b6001836020036101000a038019825116818451168082178552505050505050905001915050908152602001604051809103902090805161070a929160200190611085565b50826040518082805190602001908083835b6020831061073b5780518252601f19909201916020918201910161071c565b6001836020036101000a0380198251168184511617909252505050919091019250604091505051908190039020847fd8c9334b1a9c2f9da342a0a2b32629c1a229b6445dad78947f674b44444a75508560405160208082528190810183818151815260200191508051906020019080838360005b838110156107c75780820151838201526020016107af565b50505050905090810190601f1680156107f45780820380516001836020036101000a031916815260200191505b509250505060405180910390a350505050565b6000610811611103565b60008481526001602081905260409091209092505b838311610924578284161580159061085f5750600083815260068201602052604081205460026000196101006001841615020190911604115b15610919578060060160008481526020019081526020016000208054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561090d5780601f106108e25761010080835404028352916020019161090d565b820191906000526020600020905b8154815290600101906020018083116108f057829003601f168201915b50505050509150610929565b600290920291610826565b600092505b509250929050565b600080548491600160a060020a033381169216906302571be39084906040516020015260405160e060020a63ffffffff84160281526004810191909152602401602060405180830381600087803b151561098a57600080fd5b6102c65a03f1151561099b57600080fd5b50505060405180519050600160a060020a03161415156109ba57600080fd5b6040805190810160409081528482526020808301859052600087815260019091522060030181518155602082015160019091015550837f1d6f5e03d3f63eb58751986629a5439baee5079ff04f345becb66e23eb154e46848460405191825260208201526040908101905180910390a250505050565b6000908152600160208190526040909120015490565b600090815260016020526040902054600160a060020a031690565b610a69611103565b60008381526001602052604090819020600501908390518082805190602001908083835b60208310610aac5780518252601f199092019160209182019101610a8d565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610b735780601f10610b4857610100808354040283529160200191610b73565b820191906000526020600020905b815481529060010190602001808311610b5657829003601f168201915b5050505050905092915050565b600080548491600160a060020a033381169216906302571be39084906040516020015260405160e060020a63ffffffff84160281526004810191909152602401602060405180830381600087803b1515610bd957600080fd5b6102c65a03f11515610bea57600080fd5b50505060405180519050600160a060020a0316141515610c0957600080fd5b6000198301831615610c1a57600080fd5b60008481526001602090815260408083208684526006019091529020828051610c47929160200190611085565b5082847faa121bbeef5f32f5961a2a28966e769023910fc9479059ee3495d4c1a696efe360405160405180910390a350505050565b610c84611103565b6001600083600019166000191681526020019081526020016000206002018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610d365780601f10610d0b57610100808354040283529160200191610d36565b820191906000526020600020905b815481529060010190602001808311610d1957829003601f168201915b50505050509050919050565b600080548391600160a060020a033381169216906302571be39084906040516020015260405160e060020a63ffffffff84160281526004810191909152602401602060405180830381600087803b1515610d9b57600080fd5b6102c65a03f11515610dac57600080fd5b50505060405180519050600160a060020a0316141515610dcb57600080fd5b6000838152600160205260409020600201828051610ded929160200190611085565b50827fb7d29e911041e8d9b843369e890bcb72c9388692ba48b65ac54e7214c4c348f78360405160208082528190810183818151815260200191508051906020019080838360005b83811015610e4d578082015183820152602001610e35565b50505050905090810190601f168015610e7a5780820380516001836020036101000a031916815260200191505b509250505060405180910390a2505050565b600080548391600160a060020a033381169216906302571be39084906040516020015260405160e060020a63ffffffff84160281526004810191909152602401602060405180830381600087803b1515610ee557600080fd5b6102c65a03f11515610ef657600080fd5b50505060405180519050600160a060020a0316141515610f1557600080fd5b6000838152600160208190526040918290200183905583907f0424b6fe0d9c3bdbece0e7879dc241bb0c22e900be8b6c168b4ee08bd9bf83bc9084905190815260200160405180910390a2505050565b600090815260016020526040902060038101546004909101549091565b600080548391600160a060020a033381169216906302571be39084906040516020015260405160e060020a63ffffffff84160281526004810191909152602401602060405180830381600087803b1515610fdb57600080fd5b6102c65a03f11515610fec57600080fd5b50505060405180519050600160a060020a031614151561100b57600080fd5b60008381526001602052604090819020805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a03851617905583907f52d7d861f09ab3d26239d492e8968629f95e9e318cf0b73bfddc441522a15fd290849051600160a060020a03909116815260200160405180910390a2505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106110c657805160ff19168380011785556110f3565b828001600101855582156110f3579182015b828111156110f35782518255916020019190600101906110d8565b506110ff929150611115565b5090565b60206040519081016040526000815290565b61112f91905b808211156110ff576000815560010161111b565b905600a165627a7a723058201ecacbc445b9fbcd91b0ab164389f69d7283b856883bc7437eeed1008345a4920029` - -// DeployPublicResolver deploys a new Ethereum contract, binding an instance of PublicResolver to it. -func DeployPublicResolver(auth *bind.TransactOpts, backend bind.ContractBackend, ensAddr common.Address) (common.Address, *types.Transaction, *PublicResolver, error) { - parsed, err := abi.JSON(strings.NewReader(PublicResolverABI)) - if err != nil { - return common.Address{}, nil, nil, err - } - address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(PublicResolverBin), backend, ensAddr) - if err != nil { - return common.Address{}, nil, nil, err - } - return address, tx, &PublicResolver{PublicResolverCaller: PublicResolverCaller{contract: contract}, PublicResolverTransactor: PublicResolverTransactor{contract: contract}, PublicResolverFilterer: PublicResolverFilterer{contract: contract}}, nil -} - -// PublicResolver is an auto generated Go binding around an Ethereum contract. -type PublicResolver struct { - PublicResolverCaller // Read-only binding to the contract - PublicResolverTransactor // Write-only binding to the contract - PublicResolverFilterer // Log filterer for contract events -} - -// PublicResolverCaller is an auto generated read-only Go binding around an Ethereum contract. -type PublicResolverCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// PublicResolverTransactor is an auto generated write-only Go binding around an Ethereum contract. -type PublicResolverTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// PublicResolverFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type PublicResolverFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// PublicResolverSession is an auto generated Go binding around an Ethereum contract, -// with pre-set call and transact options. -type PublicResolverSession struct { - Contract *PublicResolver // Generic contract binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// PublicResolverCallerSession is an auto generated read-only Go binding around an Ethereum contract, -// with pre-set call options. -type PublicResolverCallerSession struct { - Contract *PublicResolverCaller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session -} - -// PublicResolverTransactorSession is an auto generated write-only Go binding around an Ethereum contract, -// with pre-set transact options. -type PublicResolverTransactorSession struct { - Contract *PublicResolverTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// PublicResolverRaw is an auto generated low-level Go binding around an Ethereum contract. -type PublicResolverRaw struct { - Contract *PublicResolver // Generic contract binding to access the raw methods on -} - -// PublicResolverCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. -type PublicResolverCallerRaw struct { - Contract *PublicResolverCaller // Generic read-only contract binding to access the raw methods on -} - -// PublicResolverTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. -type PublicResolverTransactorRaw struct { - Contract *PublicResolverTransactor // Generic write-only contract binding to access the raw methods on -} - -// NewPublicResolver creates a new instance of PublicResolver, bound to a specific deployed contract. -func NewPublicResolver(address common.Address, backend bind.ContractBackend) (*PublicResolver, error) { - contract, err := bindPublicResolver(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &PublicResolver{PublicResolverCaller: PublicResolverCaller{contract: contract}, PublicResolverTransactor: PublicResolverTransactor{contract: contract}, PublicResolverFilterer: PublicResolverFilterer{contract: contract}}, nil -} - -// NewPublicResolverCaller creates a new read-only instance of PublicResolver, bound to a specific deployed contract. -func NewPublicResolverCaller(address common.Address, caller bind.ContractCaller) (*PublicResolverCaller, error) { - contract, err := bindPublicResolver(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &PublicResolverCaller{contract: contract}, nil -} - -// NewPublicResolverTransactor creates a new write-only instance of PublicResolver, bound to a specific deployed contract. -func NewPublicResolverTransactor(address common.Address, transactor bind.ContractTransactor) (*PublicResolverTransactor, error) { - contract, err := bindPublicResolver(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &PublicResolverTransactor{contract: contract}, nil -} - -// NewPublicResolverFilterer creates a new log filterer instance of PublicResolver, bound to a specific deployed contract. -func NewPublicResolverFilterer(address common.Address, filterer bind.ContractFilterer) (*PublicResolverFilterer, error) { - contract, err := bindPublicResolver(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &PublicResolverFilterer{contract: contract}, nil -} - -// bindPublicResolver binds a generic wrapper to an already deployed contract. -func bindPublicResolver(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := abi.JSON(strings.NewReader(PublicResolverABI)) - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_PublicResolver *PublicResolverRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { - return _PublicResolver.Contract.PublicResolverCaller.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_PublicResolver *PublicResolverRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _PublicResolver.Contract.PublicResolverTransactor.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_PublicResolver *PublicResolverRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _PublicResolver.Contract.PublicResolverTransactor.contract.Transact(opts, method, params...) -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_PublicResolver *PublicResolverCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { - return _PublicResolver.Contract.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_PublicResolver *PublicResolverTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _PublicResolver.Contract.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_PublicResolver *PublicResolverTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _PublicResolver.Contract.contract.Transact(opts, method, params...) -} - -// ABI is a free data retrieval call binding the contract method 0x2203ab56. -// -// Solidity: function ABI(node bytes32, contentTypes uint256) constant returns(contentType uint256, data bytes) -func (_PublicResolver *PublicResolverCaller) ABI(opts *bind.CallOpts, node [32]byte, contentTypes *big.Int) (struct { - ContentType *big.Int - Data []byte -}, error) { - ret := new(struct { - ContentType *big.Int - Data []byte - }) - out := ret - err := _PublicResolver.contract.Call(opts, out, "ABI", node, contentTypes) - return *ret, err -} - -// ABI is a free data retrieval call binding the contract method 0x2203ab56. -// -// Solidity: function ABI(node bytes32, contentTypes uint256) constant returns(contentType uint256, data bytes) -func (_PublicResolver *PublicResolverSession) ABI(node [32]byte, contentTypes *big.Int) (struct { - ContentType *big.Int - Data []byte -}, error) { - return _PublicResolver.Contract.ABI(&_PublicResolver.CallOpts, node, contentTypes) -} - -// ABI is a free data retrieval call binding the contract method 0x2203ab56. -// -// Solidity: function ABI(node bytes32, contentTypes uint256) constant returns(contentType uint256, data bytes) -func (_PublicResolver *PublicResolverCallerSession) ABI(node [32]byte, contentTypes *big.Int) (struct { - ContentType *big.Int - Data []byte -}, error) { - return _PublicResolver.Contract.ABI(&_PublicResolver.CallOpts, node, contentTypes) -} - -// Addr is a free data retrieval call binding the contract method 0x3b3b57de. -// -// Solidity: function addr(node bytes32) constant returns(ret address) -func (_PublicResolver *PublicResolverCaller) Addr(opts *bind.CallOpts, node [32]byte) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _PublicResolver.contract.Call(opts, out, "addr", node) - return *ret0, err -} - -// Addr is a free data retrieval call binding the contract method 0x3b3b57de. -// -// Solidity: function addr(node bytes32) constant returns(ret address) -func (_PublicResolver *PublicResolverSession) Addr(node [32]byte) (common.Address, error) { - return _PublicResolver.Contract.Addr(&_PublicResolver.CallOpts, node) -} - -// Addr is a free data retrieval call binding the contract method 0x3b3b57de. -// -// Solidity: function addr(node bytes32) constant returns(ret address) -func (_PublicResolver *PublicResolverCallerSession) Addr(node [32]byte) (common.Address, error) { - return _PublicResolver.Contract.Addr(&_PublicResolver.CallOpts, node) -} - -// Content is a free data retrieval call binding the contract method 0x2dff6941. -// -// Solidity: function content(node bytes32) constant returns(ret bytes32) -func (_PublicResolver *PublicResolverCaller) Content(opts *bind.CallOpts, node [32]byte) ([32]byte, error) { - var ( - ret0 = new([32]byte) - ) - out := ret0 - err := _PublicResolver.contract.Call(opts, out, "content", node) - return *ret0, err -} - -// Content is a free data retrieval call binding the contract method 0x2dff6941. -// -// Solidity: function content(node bytes32) constant returns(ret bytes32) -func (_PublicResolver *PublicResolverSession) Content(node [32]byte) ([32]byte, error) { - return _PublicResolver.Contract.Content(&_PublicResolver.CallOpts, node) -} - -// Content is a free data retrieval call binding the contract method 0x2dff6941. -// -// Solidity: function content(node bytes32) constant returns(ret bytes32) -func (_PublicResolver *PublicResolverCallerSession) Content(node [32]byte) ([32]byte, error) { - return _PublicResolver.Contract.Content(&_PublicResolver.CallOpts, node) -} - -// Name is a free data retrieval call binding the contract method 0x691f3431. -// -// Solidity: function name(node bytes32) constant returns(ret string) -func (_PublicResolver *PublicResolverCaller) Name(opts *bind.CallOpts, node [32]byte) (string, error) { - var ( - ret0 = new(string) - ) - out := ret0 - err := _PublicResolver.contract.Call(opts, out, "name", node) - return *ret0, err -} - -// Name is a free data retrieval call binding the contract method 0x691f3431. -// -// Solidity: function name(node bytes32) constant returns(ret string) -func (_PublicResolver *PublicResolverSession) Name(node [32]byte) (string, error) { - return _PublicResolver.Contract.Name(&_PublicResolver.CallOpts, node) -} - -// Name is a free data retrieval call binding the contract method 0x691f3431. -// -// Solidity: function name(node bytes32) constant returns(ret string) -func (_PublicResolver *PublicResolverCallerSession) Name(node [32]byte) (string, error) { - return _PublicResolver.Contract.Name(&_PublicResolver.CallOpts, node) -} - -// Pubkey is a free data retrieval call binding the contract method 0xc8690233. -// -// Solidity: function pubkey(node bytes32) constant returns(x bytes32, y bytes32) -func (_PublicResolver *PublicResolverCaller) Pubkey(opts *bind.CallOpts, node [32]byte) (struct { - X [32]byte - Y [32]byte -}, error) { - ret := new(struct { - X [32]byte - Y [32]byte - }) - out := ret - err := _PublicResolver.contract.Call(opts, out, "pubkey", node) - return *ret, err -} - -// Pubkey is a free data retrieval call binding the contract method 0xc8690233. -// -// Solidity: function pubkey(node bytes32) constant returns(x bytes32, y bytes32) -func (_PublicResolver *PublicResolverSession) Pubkey(node [32]byte) (struct { - X [32]byte - Y [32]byte -}, error) { - return _PublicResolver.Contract.Pubkey(&_PublicResolver.CallOpts, node) -} - -// Pubkey is a free data retrieval call binding the contract method 0xc8690233. -// -// Solidity: function pubkey(node bytes32) constant returns(x bytes32, y bytes32) -func (_PublicResolver *PublicResolverCallerSession) Pubkey(node [32]byte) (struct { - X [32]byte - Y [32]byte -}, error) { - return _PublicResolver.Contract.Pubkey(&_PublicResolver.CallOpts, node) -} - -// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. -// -// Solidity: function supportsInterface(interfaceID bytes4) constant returns(bool) -func (_PublicResolver *PublicResolverCaller) SupportsInterface(opts *bind.CallOpts, interfaceID [4]byte) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _PublicResolver.contract.Call(opts, out, "supportsInterface", interfaceID) - return *ret0, err -} - -// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. -// -// Solidity: function supportsInterface(interfaceID bytes4) constant returns(bool) -func (_PublicResolver *PublicResolverSession) SupportsInterface(interfaceID [4]byte) (bool, error) { - return _PublicResolver.Contract.SupportsInterface(&_PublicResolver.CallOpts, interfaceID) -} - -// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. -// -// Solidity: function supportsInterface(interfaceID bytes4) constant returns(bool) -func (_PublicResolver *PublicResolverCallerSession) SupportsInterface(interfaceID [4]byte) (bool, error) { - return _PublicResolver.Contract.SupportsInterface(&_PublicResolver.CallOpts, interfaceID) -} - -// Text is a free data retrieval call binding the contract method 0x59d1d43c. -// -// Solidity: function text(node bytes32, key string) constant returns(ret string) -func (_PublicResolver *PublicResolverCaller) Text(opts *bind.CallOpts, node [32]byte, key string) (string, error) { - var ( - ret0 = new(string) - ) - out := ret0 - err := _PublicResolver.contract.Call(opts, out, "text", node, key) - return *ret0, err -} - -// Text is a free data retrieval call binding the contract method 0x59d1d43c. -// -// Solidity: function text(node bytes32, key string) constant returns(ret string) -func (_PublicResolver *PublicResolverSession) Text(node [32]byte, key string) (string, error) { - return _PublicResolver.Contract.Text(&_PublicResolver.CallOpts, node, key) -} - -// Text is a free data retrieval call binding the contract method 0x59d1d43c. -// -// Solidity: function text(node bytes32, key string) constant returns(ret string) -func (_PublicResolver *PublicResolverCallerSession) Text(node [32]byte, key string) (string, error) { - return _PublicResolver.Contract.Text(&_PublicResolver.CallOpts, node, key) -} - -// SetABI is a paid mutator transaction binding the contract method 0x623195b0. -// -// Solidity: function setABI(node bytes32, contentType uint256, data bytes) returns() -func (_PublicResolver *PublicResolverTransactor) SetABI(opts *bind.TransactOpts, node [32]byte, contentType *big.Int, data []byte) (*types.Transaction, error) { - return _PublicResolver.contract.Transact(opts, "setABI", node, contentType, data) -} - -// SetABI is a paid mutator transaction binding the contract method 0x623195b0. -// -// Solidity: function setABI(node bytes32, contentType uint256, data bytes) returns() -func (_PublicResolver *PublicResolverSession) SetABI(node [32]byte, contentType *big.Int, data []byte) (*types.Transaction, error) { - return _PublicResolver.Contract.SetABI(&_PublicResolver.TransactOpts, node, contentType, data) -} - -// SetABI is a paid mutator transaction binding the contract method 0x623195b0. -// -// Solidity: function setABI(node bytes32, contentType uint256, data bytes) returns() -func (_PublicResolver *PublicResolverTransactorSession) SetABI(node [32]byte, contentType *big.Int, data []byte) (*types.Transaction, error) { - return _PublicResolver.Contract.SetABI(&_PublicResolver.TransactOpts, node, contentType, data) -} - -// SetAddr is a paid mutator transaction binding the contract method 0xd5fa2b00. -// -// Solidity: function setAddr(node bytes32, addr address) returns() -func (_PublicResolver *PublicResolverTransactor) SetAddr(opts *bind.TransactOpts, node [32]byte, addr common.Address) (*types.Transaction, error) { - return _PublicResolver.contract.Transact(opts, "setAddr", node, addr) -} - -// SetAddr is a paid mutator transaction binding the contract method 0xd5fa2b00. -// -// Solidity: function setAddr(node bytes32, addr address) returns() -func (_PublicResolver *PublicResolverSession) SetAddr(node [32]byte, addr common.Address) (*types.Transaction, error) { - return _PublicResolver.Contract.SetAddr(&_PublicResolver.TransactOpts, node, addr) -} - -// SetAddr is a paid mutator transaction binding the contract method 0xd5fa2b00. -// -// Solidity: function setAddr(node bytes32, addr address) returns() -func (_PublicResolver *PublicResolverTransactorSession) SetAddr(node [32]byte, addr common.Address) (*types.Transaction, error) { - return _PublicResolver.Contract.SetAddr(&_PublicResolver.TransactOpts, node, addr) -} - -// SetContent is a paid mutator transaction binding the contract method 0xc3d014d6. -// -// Solidity: function setContent(node bytes32, hash bytes32) returns() -func (_PublicResolver *PublicResolverTransactor) SetContent(opts *bind.TransactOpts, node [32]byte, hash [32]byte) (*types.Transaction, error) { - return _PublicResolver.contract.Transact(opts, "setContent", node, hash) -} - -// SetContent is a paid mutator transaction binding the contract method 0xc3d014d6. -// -// Solidity: function setContent(node bytes32, hash bytes32) returns() -func (_PublicResolver *PublicResolverSession) SetContent(node [32]byte, hash [32]byte) (*types.Transaction, error) { - return _PublicResolver.Contract.SetContent(&_PublicResolver.TransactOpts, node, hash) -} - -// SetContent is a paid mutator transaction binding the contract method 0xc3d014d6. -// -// Solidity: function setContent(node bytes32, hash bytes32) returns() -func (_PublicResolver *PublicResolverTransactorSession) SetContent(node [32]byte, hash [32]byte) (*types.Transaction, error) { - return _PublicResolver.Contract.SetContent(&_PublicResolver.TransactOpts, node, hash) -} - -// SetName is a paid mutator transaction binding the contract method 0x77372213. -// -// Solidity: function setName(node bytes32, name string) returns() -func (_PublicResolver *PublicResolverTransactor) SetName(opts *bind.TransactOpts, node [32]byte, name string) (*types.Transaction, error) { - return _PublicResolver.contract.Transact(opts, "setName", node, name) -} - -// SetName is a paid mutator transaction binding the contract method 0x77372213. -// -// Solidity: function setName(node bytes32, name string) returns() -func (_PublicResolver *PublicResolverSession) SetName(node [32]byte, name string) (*types.Transaction, error) { - return _PublicResolver.Contract.SetName(&_PublicResolver.TransactOpts, node, name) -} - -// SetName is a paid mutator transaction binding the contract method 0x77372213. -// -// Solidity: function setName(node bytes32, name string) returns() -func (_PublicResolver *PublicResolverTransactorSession) SetName(node [32]byte, name string) (*types.Transaction, error) { - return _PublicResolver.Contract.SetName(&_PublicResolver.TransactOpts, node, name) -} - -// SetPubkey is a paid mutator transaction binding the contract method 0x29cd62ea. -// -// Solidity: function setPubkey(node bytes32, x bytes32, y bytes32) returns() -func (_PublicResolver *PublicResolverTransactor) SetPubkey(opts *bind.TransactOpts, node [32]byte, x [32]byte, y [32]byte) (*types.Transaction, error) { - return _PublicResolver.contract.Transact(opts, "setPubkey", node, x, y) -} - -// SetPubkey is a paid mutator transaction binding the contract method 0x29cd62ea. -// -// Solidity: function setPubkey(node bytes32, x bytes32, y bytes32) returns() -func (_PublicResolver *PublicResolverSession) SetPubkey(node [32]byte, x [32]byte, y [32]byte) (*types.Transaction, error) { - return _PublicResolver.Contract.SetPubkey(&_PublicResolver.TransactOpts, node, x, y) -} - -// SetPubkey is a paid mutator transaction binding the contract method 0x29cd62ea. -// -// Solidity: function setPubkey(node bytes32, x bytes32, y bytes32) returns() -func (_PublicResolver *PublicResolverTransactorSession) SetPubkey(node [32]byte, x [32]byte, y [32]byte) (*types.Transaction, error) { - return _PublicResolver.Contract.SetPubkey(&_PublicResolver.TransactOpts, node, x, y) -} - -// SetText is a paid mutator transaction binding the contract method 0x10f13a8c. -// -// Solidity: function setText(node bytes32, key string, value string) returns() -func (_PublicResolver *PublicResolverTransactor) SetText(opts *bind.TransactOpts, node [32]byte, key string, value string) (*types.Transaction, error) { - return _PublicResolver.contract.Transact(opts, "setText", node, key, value) -} - -// SetText is a paid mutator transaction binding the contract method 0x10f13a8c. -// -// Solidity: function setText(node bytes32, key string, value string) returns() -func (_PublicResolver *PublicResolverSession) SetText(node [32]byte, key string, value string) (*types.Transaction, error) { - return _PublicResolver.Contract.SetText(&_PublicResolver.TransactOpts, node, key, value) -} - -// SetText is a paid mutator transaction binding the contract method 0x10f13a8c. -// -// Solidity: function setText(node bytes32, key string, value string) returns() -func (_PublicResolver *PublicResolverTransactorSession) SetText(node [32]byte, key string, value string) (*types.Transaction, error) { - return _PublicResolver.Contract.SetText(&_PublicResolver.TransactOpts, node, key, value) -} - -// PublicResolverABIChangedIterator is returned from FilterABIChanged and is used to iterate over the raw logs and unpacked data for ABIChanged events raised by the PublicResolver contract. -type PublicResolverABIChangedIterator struct { - Event *PublicResolverABIChanged // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *PublicResolverABIChangedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(PublicResolverABIChanged) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(PublicResolverABIChanged) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error retruned any retrieval or parsing error occurred during filtering. -func (it *PublicResolverABIChangedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *PublicResolverABIChangedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// PublicResolverABIChanged represents a ABIChanged event raised by the PublicResolver contract. -type PublicResolverABIChanged struct { - Node [32]byte - ContentType *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterABIChanged is a free log retrieval operation binding the contract event 0xaa121bbeef5f32f5961a2a28966e769023910fc9479059ee3495d4c1a696efe3. -// -// Solidity: event ABIChanged(node indexed bytes32, contentType indexed uint256) -func (_PublicResolver *PublicResolverFilterer) FilterABIChanged(opts *bind.FilterOpts, node [][32]byte, contentType []*big.Int) (*PublicResolverABIChangedIterator, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - var contentTypeRule []interface{} - for _, contentTypeItem := range contentType { - contentTypeRule = append(contentTypeRule, contentTypeItem) - } - - logs, sub, err := _PublicResolver.contract.FilterLogs(opts, "ABIChanged", nodeRule, contentTypeRule) - if err != nil { - return nil, err - } - return &PublicResolverABIChangedIterator{contract: _PublicResolver.contract, event: "ABIChanged", logs: logs, sub: sub}, nil -} - -// WatchABIChanged is a free log subscription operation binding the contract event 0xaa121bbeef5f32f5961a2a28966e769023910fc9479059ee3495d4c1a696efe3. -// -// Solidity: event ABIChanged(node indexed bytes32, contentType indexed uint256) -func (_PublicResolver *PublicResolverFilterer) WatchABIChanged(opts *bind.WatchOpts, sink chan<- *PublicResolverABIChanged, node [][32]byte, contentType []*big.Int) (event.Subscription, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - var contentTypeRule []interface{} - for _, contentTypeItem := range contentType { - contentTypeRule = append(contentTypeRule, contentTypeItem) - } - - logs, sub, err := _PublicResolver.contract.WatchLogs(opts, "ABIChanged", nodeRule, contentTypeRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(PublicResolverABIChanged) - if err := _PublicResolver.contract.UnpackLog(event, "ABIChanged", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// PublicResolverAddrChangedIterator is returned from FilterAddrChanged and is used to iterate over the raw logs and unpacked data for AddrChanged events raised by the PublicResolver contract. -type PublicResolverAddrChangedIterator struct { - Event *PublicResolverAddrChanged // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *PublicResolverAddrChangedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(PublicResolverAddrChanged) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(PublicResolverAddrChanged) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error retruned any retrieval or parsing error occurred during filtering. -func (it *PublicResolverAddrChangedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *PublicResolverAddrChangedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// PublicResolverAddrChanged represents a AddrChanged event raised by the PublicResolver contract. -type PublicResolverAddrChanged struct { - Node [32]byte - A common.Address - Raw types.Log // Blockchain specific contextual infos -} - -// FilterAddrChanged is a free log retrieval operation binding the contract event 0x52d7d861f09ab3d26239d492e8968629f95e9e318cf0b73bfddc441522a15fd2. -// -// Solidity: event AddrChanged(node indexed bytes32, a address) -func (_PublicResolver *PublicResolverFilterer) FilterAddrChanged(opts *bind.FilterOpts, node [][32]byte) (*PublicResolverAddrChangedIterator, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - - logs, sub, err := _PublicResolver.contract.FilterLogs(opts, "AddrChanged", nodeRule) - if err != nil { - return nil, err - } - return &PublicResolverAddrChangedIterator{contract: _PublicResolver.contract, event: "AddrChanged", logs: logs, sub: sub}, nil -} - -// WatchAddrChanged is a free log subscription operation binding the contract event 0x52d7d861f09ab3d26239d492e8968629f95e9e318cf0b73bfddc441522a15fd2. -// -// Solidity: event AddrChanged(node indexed bytes32, a address) -func (_PublicResolver *PublicResolverFilterer) WatchAddrChanged(opts *bind.WatchOpts, sink chan<- *PublicResolverAddrChanged, node [][32]byte) (event.Subscription, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - - logs, sub, err := _PublicResolver.contract.WatchLogs(opts, "AddrChanged", nodeRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(PublicResolverAddrChanged) - if err := _PublicResolver.contract.UnpackLog(event, "AddrChanged", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// PublicResolverContentChangedIterator is returned from FilterContentChanged and is used to iterate over the raw logs and unpacked data for ContentChanged events raised by the PublicResolver contract. -type PublicResolverContentChangedIterator struct { - Event *PublicResolverContentChanged // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *PublicResolverContentChangedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(PublicResolverContentChanged) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(PublicResolverContentChanged) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error retruned any retrieval or parsing error occurred during filtering. -func (it *PublicResolverContentChangedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *PublicResolverContentChangedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// PublicResolverContentChanged represents a ContentChanged event raised by the PublicResolver contract. -type PublicResolverContentChanged struct { - Node [32]byte - Hash [32]byte - Raw types.Log // Blockchain specific contextual infos -} - -// FilterContentChanged is a free log retrieval operation binding the contract event 0x0424b6fe0d9c3bdbece0e7879dc241bb0c22e900be8b6c168b4ee08bd9bf83bc. -// -// Solidity: event ContentChanged(node indexed bytes32, hash bytes32) -func (_PublicResolver *PublicResolverFilterer) FilterContentChanged(opts *bind.FilterOpts, node [][32]byte) (*PublicResolverContentChangedIterator, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - - logs, sub, err := _PublicResolver.contract.FilterLogs(opts, "ContentChanged", nodeRule) - if err != nil { - return nil, err - } - return &PublicResolverContentChangedIterator{contract: _PublicResolver.contract, event: "ContentChanged", logs: logs, sub: sub}, nil -} - -// WatchContentChanged is a free log subscription operation binding the contract event 0x0424b6fe0d9c3bdbece0e7879dc241bb0c22e900be8b6c168b4ee08bd9bf83bc. -// -// Solidity: event ContentChanged(node indexed bytes32, hash bytes32) -func (_PublicResolver *PublicResolverFilterer) WatchContentChanged(opts *bind.WatchOpts, sink chan<- *PublicResolverContentChanged, node [][32]byte) (event.Subscription, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - - logs, sub, err := _PublicResolver.contract.WatchLogs(opts, "ContentChanged", nodeRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(PublicResolverContentChanged) - if err := _PublicResolver.contract.UnpackLog(event, "ContentChanged", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// PublicResolverNameChangedIterator is returned from FilterNameChanged and is used to iterate over the raw logs and unpacked data for NameChanged events raised by the PublicResolver contract. -type PublicResolverNameChangedIterator struct { - Event *PublicResolverNameChanged // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *PublicResolverNameChangedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(PublicResolverNameChanged) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(PublicResolverNameChanged) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error retruned any retrieval or parsing error occurred during filtering. -func (it *PublicResolverNameChangedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *PublicResolverNameChangedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// PublicResolverNameChanged represents a NameChanged event raised by the PublicResolver contract. -type PublicResolverNameChanged struct { - Node [32]byte - Name string - Raw types.Log // Blockchain specific contextual infos -} - -// FilterNameChanged is a free log retrieval operation binding the contract event 0xb7d29e911041e8d9b843369e890bcb72c9388692ba48b65ac54e7214c4c348f7. -// -// Solidity: event NameChanged(node indexed bytes32, name string) -func (_PublicResolver *PublicResolverFilterer) FilterNameChanged(opts *bind.FilterOpts, node [][32]byte) (*PublicResolverNameChangedIterator, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - - logs, sub, err := _PublicResolver.contract.FilterLogs(opts, "NameChanged", nodeRule) - if err != nil { - return nil, err - } - return &PublicResolverNameChangedIterator{contract: _PublicResolver.contract, event: "NameChanged", logs: logs, sub: sub}, nil -} - -// WatchNameChanged is a free log subscription operation binding the contract event 0xb7d29e911041e8d9b843369e890bcb72c9388692ba48b65ac54e7214c4c348f7. -// -// Solidity: event NameChanged(node indexed bytes32, name string) -func (_PublicResolver *PublicResolverFilterer) WatchNameChanged(opts *bind.WatchOpts, sink chan<- *PublicResolverNameChanged, node [][32]byte) (event.Subscription, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - - logs, sub, err := _PublicResolver.contract.WatchLogs(opts, "NameChanged", nodeRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(PublicResolverNameChanged) - if err := _PublicResolver.contract.UnpackLog(event, "NameChanged", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// PublicResolverPubkeyChangedIterator is returned from FilterPubkeyChanged and is used to iterate over the raw logs and unpacked data for PubkeyChanged events raised by the PublicResolver contract. -type PublicResolverPubkeyChangedIterator struct { - Event *PublicResolverPubkeyChanged // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *PublicResolverPubkeyChangedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(PublicResolverPubkeyChanged) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(PublicResolverPubkeyChanged) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error retruned any retrieval or parsing error occurred during filtering. -func (it *PublicResolverPubkeyChangedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *PublicResolverPubkeyChangedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// PublicResolverPubkeyChanged represents a PubkeyChanged event raised by the PublicResolver contract. -type PublicResolverPubkeyChanged struct { - Node [32]byte - X [32]byte - Y [32]byte - Raw types.Log // Blockchain specific contextual infos -} - -// FilterPubkeyChanged is a free log retrieval operation binding the contract event 0x1d6f5e03d3f63eb58751986629a5439baee5079ff04f345becb66e23eb154e46. -// -// Solidity: event PubkeyChanged(node indexed bytes32, x bytes32, y bytes32) -func (_PublicResolver *PublicResolverFilterer) FilterPubkeyChanged(opts *bind.FilterOpts, node [][32]byte) (*PublicResolverPubkeyChangedIterator, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - - logs, sub, err := _PublicResolver.contract.FilterLogs(opts, "PubkeyChanged", nodeRule) - if err != nil { - return nil, err - } - return &PublicResolverPubkeyChangedIterator{contract: _PublicResolver.contract, event: "PubkeyChanged", logs: logs, sub: sub}, nil -} - -// WatchPubkeyChanged is a free log subscription operation binding the contract event 0x1d6f5e03d3f63eb58751986629a5439baee5079ff04f345becb66e23eb154e46. -// -// Solidity: event PubkeyChanged(node indexed bytes32, x bytes32, y bytes32) -func (_PublicResolver *PublicResolverFilterer) WatchPubkeyChanged(opts *bind.WatchOpts, sink chan<- *PublicResolverPubkeyChanged, node [][32]byte) (event.Subscription, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - - logs, sub, err := _PublicResolver.contract.WatchLogs(opts, "PubkeyChanged", nodeRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(PublicResolverPubkeyChanged) - if err := _PublicResolver.contract.UnpackLog(event, "PubkeyChanged", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// PublicResolverTextChangedIterator is returned from FilterTextChanged and is used to iterate over the raw logs and unpacked data for TextChanged events raised by the PublicResolver contract. -type PublicResolverTextChangedIterator struct { - Event *PublicResolverTextChanged // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *PublicResolverTextChangedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(PublicResolverTextChanged) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(PublicResolverTextChanged) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error retruned any retrieval or parsing error occurred during filtering. -func (it *PublicResolverTextChangedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *PublicResolverTextChangedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// PublicResolverTextChanged represents a TextChanged event raised by the PublicResolver contract. -type PublicResolverTextChanged struct { - Node [32]byte - IndexedKey common.Hash - Key string - Raw types.Log // Blockchain specific contextual infos -} - -// FilterTextChanged is a free log retrieval operation binding the contract event 0xd8c9334b1a9c2f9da342a0a2b32629c1a229b6445dad78947f674b44444a7550. -// -// Solidity: event TextChanged(node indexed bytes32, indexedKey indexed string, key string) -func (_PublicResolver *PublicResolverFilterer) FilterTextChanged(opts *bind.FilterOpts, node [][32]byte, indexedKey []string) (*PublicResolverTextChangedIterator, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - var indexedKeyRule []interface{} - for _, indexedKeyItem := range indexedKey { - indexedKeyRule = append(indexedKeyRule, indexedKeyItem) - } - - logs, sub, err := _PublicResolver.contract.FilterLogs(opts, "TextChanged", nodeRule, indexedKeyRule) - if err != nil { - return nil, err - } - return &PublicResolverTextChangedIterator{contract: _PublicResolver.contract, event: "TextChanged", logs: logs, sub: sub}, nil -} - -// WatchTextChanged is a free log subscription operation binding the contract event 0xd8c9334b1a9c2f9da342a0a2b32629c1a229b6445dad78947f674b44444a7550. -// -// Solidity: event TextChanged(node indexed bytes32, indexedKey indexed string, key string) -func (_PublicResolver *PublicResolverFilterer) WatchTextChanged(opts *bind.WatchOpts, sink chan<- *PublicResolverTextChanged, node [][32]byte, indexedKey []string) (event.Subscription, error) { - - var nodeRule []interface{} - for _, nodeItem := range node { - nodeRule = append(nodeRule, nodeItem) - } - var indexedKeyRule []interface{} - for _, indexedKeyItem := range indexedKey { - indexedKeyRule = append(indexedKeyRule, indexedKeyItem) - } - - logs, sub, err := _PublicResolver.contract.WatchLogs(opts, "TextChanged", nodeRule, indexedKeyRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(PublicResolverTextChanged) - if err := _PublicResolver.contract.UnpackLog(event, "TextChanged", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} diff --git a/contracts/ens/ens.go b/contracts/ens/ens.go deleted file mode 100644 index 5cd49715bf..0000000000 --- a/contracts/ens/ens.go +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2016 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 . - -package ens - -//go:generate abigen --sol contract/ENS.sol --exc contract/AbstractENS.sol:AbstractENS --pkg contract --out contract/ens.go -//go:generate abigen --sol contract/FIFSRegistrar.sol --exc contract/AbstractENS.sol:AbstractENS --pkg contract --out contract/fifsregistrar.go -//go:generate abigen --sol contract/PublicResolver.sol --exc contract/AbstractENS.sol:AbstractENS --pkg contract --out contract/publicresolver.go - -import ( - "strings" - - "github.com/AlayaNetwork/Alaya-Go/accounts/abi/bind" - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/contracts/ens/contract" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/crypto" -) - -var ( - MainNetAddress = common.MustBech32ToAddress("atp1x9q4jfjamrdmxyry97v02rqxv9euzfvm0sc0dx") - TestNetAddress = common.MustBech32ToAddress("atx1zy3rg32u8ge06yfrp3pw00xdf2zwqgqstk8wlv") -) - -// ENS is the swarm domain name registry and resolver -type ENS struct { - *contract.ENSSession - contractBackend bind.ContractBackend -} - -// NewENS creates a struct exposing convenient high-level operations for interacting with -// the Ethereum Name Service. -func NewENS(transactOpts *bind.TransactOpts, contractAddr common.Address, contractBackend bind.ContractBackend) (*ENS, error) { - ens, err := contract.NewENS(contractAddr, contractBackend) - if err != nil { - return nil, err - } - return &ENS{ - &contract.ENSSession{ - Contract: ens, - TransactOpts: *transactOpts, - }, - contractBackend, - }, nil -} - -// DeployENS deploys an instance of the ENS nameservice, with a 'first-in, first-served' root registrar. -func DeployENS(transactOpts *bind.TransactOpts, contractBackend bind.ContractBackend) (common.Address, *ENS, error) { - // Deploy the ENS registry - ensAddr, _, _, err := contract.DeployENS(transactOpts, contractBackend) - if err != nil { - return ensAddr, nil, err - } - ens, err := NewENS(transactOpts, ensAddr, contractBackend) - if err != nil { - return ensAddr, nil, err - } - // Deploy the registrar - regAddr, _, _, err := contract.DeployFIFSRegistrar(transactOpts, contractBackend, ensAddr, [32]byte{}) - if err != nil { - return ensAddr, nil, err - } - // Set the registrar as owner of the ENS root - if _, err = ens.SetOwner([32]byte{}, regAddr); err != nil { - return ensAddr, nil, err - } - return ensAddr, ens, nil -} - -func ensParentNode(name string) (common.Hash, common.Hash) { - parts := strings.SplitN(name, ".", 2) - label := crypto.Keccak256Hash([]byte(parts[0])) - if len(parts) == 1 { - return [32]byte{}, label - } - parentNode, parentLabel := ensParentNode(parts[1]) - return crypto.Keccak256Hash(parentNode[:], parentLabel[:]), label -} - -func EnsNode(name string) common.Hash { - parentNode, parentLabel := ensParentNode(name) - return crypto.Keccak256Hash(parentNode[:], parentLabel[:]) -} - -func (ens *ENS) getResolver(node [32]byte) (*contract.PublicResolverSession, error) { - resolverAddr, err := ens.Resolver(node) - if err != nil { - return nil, err - } - resolver, err := contract.NewPublicResolver(resolverAddr, ens.contractBackend) - if err != nil { - return nil, err - } - return &contract.PublicResolverSession{ - Contract: resolver, - TransactOpts: ens.TransactOpts, - }, nil -} - -func (ens *ENS) getRegistrar(node [32]byte) (*contract.FIFSRegistrarSession, error) { - registrarAddr, err := ens.Owner(node) - if err != nil { - return nil, err - } - registrar, err := contract.NewFIFSRegistrar(registrarAddr, ens.contractBackend) - if err != nil { - return nil, err - } - return &contract.FIFSRegistrarSession{ - Contract: registrar, - TransactOpts: ens.TransactOpts, - }, nil -} - -// Resolve is a non-transactional call that returns the content hash associated with a name. -func (ens *ENS) Resolve(name string) (common.Hash, error) { - node := EnsNode(name) - - resolver, err := ens.getResolver(node) - if err != nil { - return common.Hash{}, err - } - ret, err := resolver.Content(node) - if err != nil { - return common.Hash{}, err - } - return common.BytesToHash(ret[:]), nil -} - -// Addr is a non-transactional call that returns the address associated with a name. -func (ens *ENS) Addr(name string) (common.Address, error) { - node := EnsNode(name) - - resolver, err := ens.getResolver(node) - if err != nil { - return common.Address{}, err - } - ret, err := resolver.Addr(node) - if err != nil { - return common.Address{}, err - } - return common.BytesToAddress(ret[:]), nil -} - -// SetAddress sets the address associated with a name. Only works if the caller -// owns the name, and the associated resolver implements a `setAddress` function. -func (ens *ENS) SetAddr(name string, addr common.Address) (*types.Transaction, error) { - node := EnsNode(name) - - resolver, err := ens.getResolver(node) - if err != nil { - return nil, err - } - opts := ens.TransactOpts - opts.GasLimit = 200000 - return resolver.Contract.SetAddr(&opts, node, addr) -} - -// Register registers a new domain name for the caller, making them the owner of the new name. -// Only works if the registrar for the parent domain implements the FIFS registrar protocol. -func (ens *ENS) Register(name string) (*types.Transaction, error) { - parentNode, label := ensParentNode(name) - registrar, err := ens.getRegistrar(parentNode) - if err != nil { - return nil, err - } - return registrar.Contract.Register(&ens.TransactOpts, label, ens.TransactOpts.From) -} - -// SetContentHash sets the content hash associated with a name. Only works if the caller -// owns the name, and the associated resolver implements a `setContent` function. -func (ens *ENS) SetContentHash(name string, hash common.Hash) (*types.Transaction, error) { - node := EnsNode(name) - - resolver, err := ens.getResolver(node) - if err != nil { - return nil, err - } - opts := ens.TransactOpts - opts.GasLimit = 200000 - return resolver.Contract.SetContent(&opts, node, hash) -} diff --git a/contracts/ens/ens_test.go b/contracts/ens/ens_test.go deleted file mode 100644 index e7124db1ad..0000000000 --- a/contracts/ens/ens_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2016 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 . - -package ens - -import ( - "testing" -) - -var ( -// key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") -// name = "my name on ENS" -// hash = crypto.Keccak256Hash([]byte("my content")) -) - -func TestENS(t *testing.T) { - /*ppos_storage.NewPPosTemp(ethdb.NewMemDatabase()) - contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 10000000) - transactOpts := bind.NewKeyedTransactor(key) - - ensAddr, ens, err := DeployENS(transactOpts, contractBackend) - if err != nil { - t.Fatalf("can't deploy root registry: %v", err) - } - contractBackend.Commit() - - // Set ourself as the owner of the name. - if _, err := ens.Register(name); err != nil { - t.Fatalf("can't register: %v", err) - } - contractBackend.Commit() - - // Deploy a resolver and make it responsible for the name. - resolverAddr, _, _, err := contract.DeployPublicResolver(transactOpts, contractBackend, ensAddr) - if err != nil { - t.Fatalf("can't deploy resolver: %v", err) - } - if _, err := ens.SetResolver(EnsNode(name), resolverAddr); err != nil { - t.Fatalf("can't set resolver: %v", err) - } - contractBackend.Commit() - - // Set the content hash for the name. - if _, err = ens.SetContentHash(name, hash); err != nil { - t.Fatalf("can't set content hash: %v", err) - } - contractBackend.Commit() - - // Try to resolve the name. - vhost, err := ens.Resolve(name) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - if vhost != hash { - t.Fatalf("resolve error, expected %v, got %v", hash.Hex(), vhost.Hex()) - }*/ -} diff --git a/crypto/crypto.go b/crypto/crypto.go index 419af5bddf..fb7038ef6a 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -23,19 +23,23 @@ import ( "encoding/hex" "errors" "fmt" - "golang.org/x/crypto/sha3" "hash" "io" "io/ioutil" "math/big" "os" + "golang.org/x/crypto/sha3" + "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/common/math" "github.com/AlayaNetwork/Alaya-Go/rlp" ) +//SignatureLength indicates the byte length required to carry a signature with recovery id. +const SignatureLength = 64 + 1 // 64 bytes ECDSA signature + 1 byte recovery id + var ( secp256k1N, _ = new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16) secp256k1halfN = new(big.Int).Div(secp256k1N, big.NewInt(2)) diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index ba9f045b78..f1d498c018 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -38,7 +38,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/core/types" "github.com/AlayaNetwork/Alaya-Go/eth" "github.com/AlayaNetwork/Alaya-Go/event" - "github.com/AlayaNetwork/Alaya-Go/les" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/rpc" @@ -69,10 +68,9 @@ type blockChain interface { // Service implements an Ethereum netstats reporting daemon that pushes local // chain statistics up to a monitoring server. type Service struct { - server *p2p.Server // Peer-to-peer server to retrieve networking infos - eth *eth.Ethereum // Full Ethereum service if monitoring a full node - les *les.LightEthereum // Light Ethereum service if monitoring a light node - engine consensus.Engine // Consensus engine to retrieve variadic block fields + server *p2p.Server // Peer-to-peer server to retrieve networking infos + eth *eth.Ethereum // Full Ethereum service if monitoring a full node + engine consensus.Engine // Consensus engine to retrieve variadic block fields node string // Name of the node to display on the monitoring page pass string // Password to authorize access to the monitoring page @@ -83,7 +81,7 @@ type Service struct { } // New returns a monitoring service ready for stats reporting. -func New(url string, ethServ *eth.Ethereum, lesServ *les.LightEthereum) (*Service, error) { +func New(url string, ethServ *eth.Ethereum) (*Service, error) { // Parse the netstats connection url re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)") parts := re.FindStringSubmatch(url) @@ -94,12 +92,9 @@ func New(url string, ethServ *eth.Ethereum, lesServ *les.LightEthereum) (*Servic var engine consensus.Engine if ethServ != nil { engine = ethServ.Engine() - } else { - engine = lesServ.Engine() } return &Service{ eth: ethServ, - les: lesServ, engine: engine, node: parts[1], pass: parts[3], @@ -141,9 +136,6 @@ func (s *Service) loop() { if s.eth != nil { blockchain = s.eth.BlockChain() txpool = s.eth.TxPool() - } else { - blockchain = s.les.BlockChain() - txpool = s.les.TxPool() } chainHeadCh := make(chan core.ChainHeadEvent, chainHeadChanSize) @@ -376,9 +368,6 @@ func (s *Service) login(conn *websocket.Conn) error { if info := infos.Protocols["platon"]; info != nil { network = fmt.Sprintf("%d", info.(*eth.NodeInfo).Network) protocol = fmt.Sprintf("alaya/%d", eth.ProtocolVersions[0]) - } else { - network = fmt.Sprintf("%d", infos.Protocols["les"].(*les.NodeInfo).Network) - protocol = fmt.Sprintf("les/%d", les.ClientProtocolVersions[0]) } auth := &authMsg{ ID: s.node, @@ -526,8 +515,6 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats { // Light nodes would need on-demand lookups for transactions, skip if block != nil { header = block.Header() - } else { - header = s.les.BlockChain().CurrentHeader() } txs = []txStats{} } @@ -561,8 +548,6 @@ func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error { var head int64 if s.eth != nil { head = s.eth.BlockChain().CurrentHeader().Number.Int64() - } else { - head = s.les.BlockChain().CurrentHeader().Number.Int64() } start := head - historyUpdateRange + 1 if start < 0 { @@ -579,10 +564,6 @@ func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error { var block *types.Block if s.eth != nil { block = s.eth.BlockChain().GetBlockByNumber(number) - } else { - if header := s.les.BlockChain().GetHeaderByNumber(number); header != nil { - block = types.NewBlockWithHeader(header) - } } // If we do have the block, add to the history and continue if block != nil { @@ -621,8 +602,6 @@ func (s *Service) reportPending(conn *websocket.Conn) error { var pending int if s.eth != nil { pending, _ = s.eth.TxPool().Stats() - } else { - pending = s.les.TxPool().Stats() } // Assemble the transaction stats and send it to the server log.Trace("Sending pending transactions to ethstats", "count", pending) @@ -667,9 +646,6 @@ func (s *Service) reportStats(conn *websocket.Conn) error { price, _ := s.eth.APIBackend.SuggestPrice(context.Background()) gasprice = int(price.Uint64()) - } else { - sync := s.les.Downloader().Progress() - syncing = s.les.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock } // Assemble the node stats and send it to the server log.Trace("Sending node details to ethstats") diff --git a/event/subscription.go b/event/subscription.go index 33500eac9b..d7c7d4f4a9 100644 --- a/event/subscription.go +++ b/event/subscription.go @@ -145,7 +145,6 @@ func (s *resubscribeSub) loop() { func (s *resubscribeSub) subscribe() Subscription { subscribed := make(chan error) var sub Subscription -retry: for { s.lastTry = mclock.Now() ctx, cancel := context.WithCancel(context.Background()) @@ -157,19 +156,19 @@ retry: select { case err := <-subscribed: cancel() - if err != nil { - // Subscribing failed, wait before launching the next try. - if s.backoffWait() { - return nil + if err == nil { + if sub == nil { + panic("event: ResubscribeFunc returned nil subscription and no error") } - continue retry + return sub } - if sub == nil { - panic("event: ResubscribeFunc returned nil subscription and no error") + // Subscribing failed, wait before launching the next try. + if s.backoffWait() { + return nil // unsubscribed during wait } - return sub case <-s.unsub: cancel() + <-subscribed // avoid leaking the s.fn goroutine. return nil } } diff --git a/event/subscription_test.go b/event/subscription_test.go index 5b8a2c8ede..c48be3aa30 100644 --- a/event/subscription_test.go +++ b/event/subscription_test.go @@ -102,7 +102,7 @@ func TestResubscribe(t *testing.T) { func TestResubscribeAbort(t *testing.T) { t.Parallel() - done := make(chan error) + done := make(chan error, 1) sub := Resubscribe(0, func(ctx context.Context) (Subscription, error) { select { case <-ctx.Done(): diff --git a/les/api_backend.go b/les/api_backend.go deleted file mode 100644 index 38e366e466..0000000000 --- a/les/api_backend.go +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright 2016 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 . - -package les - -import ( - "context" - "errors" - "math/big" - - "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" - - "github.com/AlayaNetwork/Alaya-Go/consensus" - - "github.com/AlayaNetwork/Alaya-Go/accounts" - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/bloombits" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/state" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/core/vm" - "github.com/AlayaNetwork/Alaya-Go/eth/downloader" - "github.com/AlayaNetwork/Alaya-Go/eth/gasprice" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/event" - "github.com/AlayaNetwork/Alaya-Go/light" - "github.com/AlayaNetwork/Alaya-Go/params" - "github.com/AlayaNetwork/Alaya-Go/rpc" -) - -type LesApiBackend struct { - extRPCEnabled bool - eth *LightEthereum - gpo *gasprice.Oracle -} - -func (b *LesApiBackend) ChainConfig() *params.ChainConfig { - return b.eth.chainConfig -} -func (b *LesApiBackend) Engine() consensus.Engine { - return b.eth.engine -} -func (b *LesApiBackend) CurrentBlock() *types.Block { - return types.NewBlockWithHeader(b.eth.BlockChain().CurrentHeader()) -} - -func (b *LesApiBackend) SetHead(number uint64) { - b.eth.protocolManager.downloader.Cancel() - b.eth.blockchain.SetHead(number) -} - -func (b *LesApiBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) { - if blockNr == rpc.LatestBlockNumber || blockNr == rpc.PendingBlockNumber { - return b.eth.blockchain.CurrentHeader(), nil - } - return b.eth.blockchain.GetHeaderByNumberOdr(ctx, uint64(blockNr)) -} - -func (b *LesApiBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { - return b.eth.blockchain.GetHeaderByHash(hash), nil -} - -func (b *LesApiBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error) { - header, err := b.HeaderByNumber(ctx, blockNr) - if header == nil || err != nil { - return nil, err - } - return b.GetBlock(ctx, header.Hash()) -} - -func (b *LesApiBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*state.StateDB, *types.Header, error) { - header, err := b.HeaderByNumber(ctx, blockNr) - if err != nil { - return nil, nil, err - } - if header == nil { - return nil, nil, errors.New("header not found") - } - return light.NewState(ctx, header, b.eth.odr), header, nil -} - -func (b *LesApiBackend) GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error) { - return b.eth.blockchain.GetBlockByHash(ctx, blockHash) -} - -func (b *LesApiBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { - if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil { - return light.GetBlockReceipts(ctx, b.eth.odr, hash, *number) - } - return nil, nil -} - -func (b *LesApiBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) { - if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil { - return light.GetBlockLogs(ctx, b.eth.odr, hash, *number) - } - return nil, nil -} - -func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { - context := core.NewEVMContext(msg, header, b.eth.blockchain) - return vm.NewEVM(context, snapshotdb.Instance(), state, b.eth.chainConfig, vm.Config{}), state.Error, nil -} - -func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { - return b.eth.txPool.Add(ctx, signedTx) -} - -func (b *LesApiBackend) RemoveTx(txHash common.Hash) { - b.eth.txPool.RemoveTx(txHash) -} - -func (b *LesApiBackend) GetPoolTransactions() (types.Transactions, error) { - return b.eth.txPool.GetTransactions() -} - -func (b *LesApiBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { - return b.eth.txPool.GetTransaction(txHash) -} - -func (b *LesApiBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { - return nil, common.ZeroHash, 0, 0, nil -} - -func (b *LesApiBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { - return b.eth.txPool.GetNonce(ctx, addr) -} - -func (b *LesApiBackend) Stats() (pending int, queued int) { - return b.eth.txPool.Stats(), 0 -} - -func (b *LesApiBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { - return b.eth.txPool.Content() -} - -func (b *LesApiBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { - return b.eth.txPool.SubscribeNewTxsEvent(ch) -} - -func (b *LesApiBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { - return b.eth.blockchain.SubscribeChainEvent(ch) -} - -func (b *LesApiBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { - return b.eth.blockchain.SubscribeChainHeadEvent(ch) -} - -func (b *LesApiBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { - return b.eth.blockchain.SubscribeChainSideEvent(ch) -} - -func (b *LesApiBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { - return b.eth.blockchain.SubscribeLogsEvent(ch) -} - -func (b *LesApiBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { - return b.eth.blockchain.SubscribeRemovedLogsEvent(ch) -} - -func (b *LesApiBackend) Downloader() *downloader.Downloader { - return b.eth.Downloader() -} - -func (b *LesApiBackend) ProtocolVersion() int { - return b.eth.LesVersion() + 10000 -} - -func (b *LesApiBackend) SuggestPrice(ctx context.Context) (*big.Int, error) { - return b.gpo.SuggestPrice(ctx) -} - -func (b *LesApiBackend) ChainDb() ethdb.Database { - return b.eth.chainDb -} - -func (b *LesApiBackend) EventMux() *event.TypeMux { - return b.eth.eventMux -} - -func (b *LesApiBackend) AccountManager() *accounts.Manager { - return b.eth.accountManager -} - -func (b *LesApiBackend) ExtRPCEnabled() bool { - return b.extRPCEnabled -} - -func (b *LesApiBackend) RPCGasCap() *big.Int { - return b.eth.config.RPCGasCap -} - -func (b *LesApiBackend) BloomStatus() (uint64, uint64) { - if b.eth.bloomIndexer == nil { - return 0, 0 - } - sections, _, _ := b.eth.bloomIndexer.Sections() - return params.BloomBitsBlocksClient, sections -} - -func (b *LesApiBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { - for i := 0; i < bloomFilterThreads; i++ { - go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) - } -} - -func (b *LesApiBackend) WasmType() string { - return "" -} diff --git a/les/backend.go b/les/backend.go deleted file mode 100644 index cbef1305c1..0000000000 --- a/les/backend.go +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright 2016 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 . - -// Package les implements the Light Ethereum Subprotocol. -package les - -import ( - "sync" - "time" - - "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" - - "github.com/AlayaNetwork/Alaya-Go/accounts" - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/consensus" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/bloombits" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/eth" - "github.com/AlayaNetwork/Alaya-Go/eth/downloader" - "github.com/AlayaNetwork/Alaya-Go/eth/filters" - "github.com/AlayaNetwork/Alaya-Go/eth/gasprice" - "github.com/AlayaNetwork/Alaya-Go/event" - "github.com/AlayaNetwork/Alaya-Go/internal/ethapi" - "github.com/AlayaNetwork/Alaya-Go/light" - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/node" - "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discv5" - "github.com/AlayaNetwork/Alaya-Go/params" - "github.com/AlayaNetwork/Alaya-Go/rpc" -) - -type LightEthereum struct { - lesCommons - - odr *LesOdr - relay *LesTxRelay - chainConfig *params.ChainConfig - // Channel for shutting down the service - shutdownChan chan bool - - // Handlers - peers *peerSet - txPool *light.TxPool - blockchain *light.LightChain - serverPool *serverPool - reqDist *requestDistributor - retriever *retrieveManager - - bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests - bloomIndexer *core.ChainIndexer - - ApiBackend *LesApiBackend - - eventMux *event.TypeMux - engine consensus.Engine - accountManager *accounts.Manager - - networkId uint64 - netRPCService *ethapi.PublicNetAPI - - wg sync.WaitGroup -} - -func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { - chainDb, err := ctx.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/") - if err != nil { - return nil, err - } - basedb, err := snapshotdb.Open(ctx.ResolvePath(snapshotdb.DBPath), 0, 0, true) - if err != nil { - return nil, err - } - chainConfig, genesisHash, genesisErr := core.SetupGenesisBlock(chainDb, basedb, config.Genesis) - if _, isCompat := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !isCompat { - return nil, genesisErr - } - if err := basedb.Close(); err != nil { - return nil, err - } - log.Info("Initialised chain configuration", "config", chainConfig) - - peers := newPeerSet() - quitSync := make(chan struct{}) - - leth := &LightEthereum{ - lesCommons: lesCommons{ - chainDb: chainDb, - config: config, - iConfig: light.DefaultClientIndexerConfig, - }, - chainConfig: chainConfig, - eventMux: ctx.EventMux, - peers: peers, - reqDist: newRequestDistributor(peers, quitSync), - accountManager: ctx.AccountManager, - engine: eth.CreateConsensusEngine(ctx, chainConfig, false, chainDb, &config.CbftConfig, ctx.EventMux), - shutdownChan: make(chan bool), - networkId: config.NetworkId, - bloomRequests: make(chan chan *bloombits.Retrieval), - bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), - } - - leth.relay = NewLesTxRelay(peers, leth.reqDist) - leth.serverPool = newServerPool(chainDb, quitSync, &leth.wg) - leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool) - - leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.retriever) - leth.chtIndexer = light.NewChtIndexer(chainDb, leth.odr, params.CHTFrequency, params.HelperTrieConfirmations) - leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency) - leth.odr.SetIndexers(leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer) - - // Note: NewLightChain adds the trusted checkpoint so it needs an ODR with - // indexers already set but not started yet - if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine); err != nil { - return nil, err - } - // Note: AddChildIndexer starts the update process for the child - leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer) - leth.chtIndexer.Start(leth.blockchain) - leth.bloomIndexer.Start(leth.blockchain) - - // Rewind the chain in case of an incompatible config upgrade. - if compat, ok := genesisErr.(*params.ConfigCompatError); ok { - log.Warn("Rewinding chain to upgrade configuration", "err", compat) - leth.blockchain.SetHead(compat.RewindTo) - rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) - } - - leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay) - if leth.protocolManager, err = NewProtocolManager(leth.chainConfig, light.DefaultClientIndexerConfig, true, config.NetworkId, leth.eventMux, leth.engine, leth.peers, leth.blockchain, nil, chainDb, leth.odr, leth.relay, leth.serverPool, quitSync, &leth.wg); err != nil { - return nil, err - } - leth.ApiBackend = &LesApiBackend{ctx.ExtRPCEnabled(), leth, nil} - gpoParams := config.GPO - if gpoParams.Default == nil { - gpoParams.Default = config.Miner.GasPrice - } - leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams) - return leth, nil -} - -func lesTopic(genesisHash common.Hash, protocolVersion uint) discv5.Topic { - var name string - switch protocolVersion { - case lpv1: - name = "LES" - case lpv2: - name = "LES2" - default: - panic(nil) - } - return discv5.Topic(name + "@" + common.Bytes2Hex(genesisHash.Bytes()[0:8])) -} - -// APIs returns the collection of RPC services the ethereum package offers. -// NOTE, some of these services probably need to be moved to somewhere else. -func (s *LightEthereum) APIs() []rpc.API { - return append(ethapi.GetAPIs(s.ApiBackend), []rpc.API{ - { - Namespace: "platon", - Version: "1.0", - Service: downloader.NewPublicDownloaderAPI(s.protocolManager.downloader, s.eventMux), - Public: true, - }, { - Namespace: "platon", - Version: "1.0", - Service: filters.NewPublicFilterAPI(s.ApiBackend, true), - Public: true, - }, { - Namespace: "net", - Version: "1.0", - Service: s.netRPCService, - Public: true, - }, - }...) -} - -func (s *LightEthereum) ResetWithGenesisBlock(gb *types.Block) { - s.blockchain.ResetWithGenesisBlock(gb) -} - -func (s *LightEthereum) BlockChain() *light.LightChain { return s.blockchain } -func (s *LightEthereum) TxPool() *light.TxPool { return s.txPool } -func (s *LightEthereum) Engine() consensus.Engine { return s.engine } -func (s *LightEthereum) LesVersion() int { return int(ClientProtocolVersions[0]) } -func (s *LightEthereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } -func (s *LightEthereum) EventMux() *event.TypeMux { return s.eventMux } - -// Protocols implements node.Service, returning all the currently configured -// network protocols to start. -func (s *LightEthereum) Protocols() []p2p.Protocol { - return s.makeProtocols(ClientProtocolVersions) -} - -// Start implements node.Service, starting all internal goroutines needed by the -// Ethereum protocol implementation. -func (s *LightEthereum) Start(srvr *p2p.Server) error { - log.Warn("Light client mode is an experimental feature") - s.startBloomHandlers(params.BloomBitsBlocksClient) - s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.networkId) - // clients are searching for the first advertised protocol in the list - protocolVersion := AdvertiseProtocolVersions[0] - s.serverPool.start(srvr, lesTopic(s.blockchain.Genesis().Hash(), protocolVersion)) - s.protocolManager.Start(s.config.LightPeers) - return nil -} - -// Stop implements node.Service, terminating all internal goroutines used by the -// Ethereum protocol. -func (s *LightEthereum) Stop() error { - s.odr.Stop() - s.bloomIndexer.Close() - s.chtIndexer.Close() - s.blockchain.Stop() - s.protocolManager.Stop() - s.txPool.Stop() - s.engine.Close() - - s.eventMux.Stop() - - time.Sleep(time.Millisecond * 200) - s.chainDb.Close() - close(s.shutdownChan) - - return nil -} diff --git a/les/bloombits.go b/les/bloombits.go deleted file mode 100644 index bb25498dba..0000000000 --- a/les/bloombits.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2017 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 . - -package les - -import ( - "time" - - "github.com/AlayaNetwork/Alaya-Go/common/bitutil" - "github.com/AlayaNetwork/Alaya-Go/light" -) - -const ( - // bloomServiceThreads is the number of goroutines used globally by an Ethereum - // instance to service bloombits lookups for all running filters. - bloomServiceThreads = 16 - - // bloomFilterThreads is the number of goroutines used locally per filter to - // multiplex requests onto the global servicing goroutines. - bloomFilterThreads = 3 - - // bloomRetrievalBatch is the maximum number of bloom bit retrievals to service - // in a single batch. - bloomRetrievalBatch = 16 - - // bloomRetrievalWait is the maximum time to wait for enough bloom bit requests - // to accumulate request an entire batch (avoiding hysteresis). - bloomRetrievalWait = time.Microsecond * 100 -) - -// startBloomHandlers starts a batch of goroutines to accept bloom bit database -// retrievals from possibly a range of filters and serving the data to satisfy. -func (eth *LightEthereum) startBloomHandlers(sectionSize uint64) { - for i := 0; i < bloomServiceThreads; i++ { - go func() { - for { - select { - case <-eth.shutdownChan: - return - - case request := <-eth.bloomRequests: - task := <-request - task.Bitsets = make([][]byte, len(task.Sections)) - compVectors, err := light.GetBloomBits(task.Context, eth.odr, task.Bit, task.Sections) - if err == nil { - for i := range task.Sections { - if blob, err := bitutil.DecompressBytes(compVectors[i], int(sectionSize/8)); err == nil { - task.Bitsets[i] = blob - } else { - task.Error = err - } - } - } else { - task.Error = err - } - request <- task - } - } - }() - } -} diff --git a/les/commons.go b/les/commons.go deleted file mode 100644 index 68f2d479c4..0000000000 --- a/les/commons.go +++ /dev/null @@ -1,104 +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 . - -package les - -import ( - "fmt" - - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/eth" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/light" - "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/params" -) - -// lesCommons contains fields needed by both server and client. -type lesCommons struct { - config *eth.Config - iConfig *light.IndexerConfig - chainDb ethdb.Database - protocolManager *ProtocolManager - chtIndexer, bloomTrieIndexer *core.ChainIndexer -} - -// NodeInfo represents a short summary of the Ethereum sub-protocol metadata -// known about the host peer. -type NodeInfo struct { - Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3, Rinkeby=4) - Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block - Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules - Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block - CHT params.TrustedCheckpoint `json:"cht"` // Trused CHT checkpoint for fast catchup -} - -// makeProtocols creates protocol descriptors for the given LES versions. -func (c *lesCommons) makeProtocols(versions []uint) []p2p.Protocol { - protos := make([]p2p.Protocol, len(versions)) - for i, version := range versions { - version := version - protos[i] = p2p.Protocol{ - Name: "les", - Version: version, - Length: ProtocolLengths[version], - NodeInfo: c.nodeInfo, - Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - return c.protocolManager.runPeer(version, p, rw) - }, - PeerInfo: func(id enode.ID) interface{} { - if p := c.protocolManager.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil { - return p.Info() - } - return nil - }, - } - } - return protos -} - -// nodeInfo retrieves some protocol metadata about the running host node. -func (c *lesCommons) nodeInfo() interface{} { - var cht params.TrustedCheckpoint - sections, _, _ := c.chtIndexer.Sections() - sections2, _, _ := c.bloomTrieIndexer.Sections() - - if sections2 < sections { - sections = sections2 - } - if sections > 0 { - sectionIndex := sections - 1 - sectionHead := c.bloomTrieIndexer.SectionHead(sectionIndex) - cht = params.TrustedCheckpoint{ - SectionIndex: sectionIndex, - SectionHead: sectionHead, - CHTRoot: light.GetChtRoot(c.chainDb, sectionIndex, sectionHead), - BloomRoot: light.GetBloomTrieRoot(c.chainDb, sectionIndex, sectionHead), - } - } - - chain := c.protocolManager.blockchain - return &NodeInfo{ - Network: c.config.NetworkId, - Genesis: chain.Genesis().Hash(), - Config: chain.Config(), - Head: chain.CurrentHeader().Hash(), - CHT: cht, - } -} diff --git a/les/distributor.go b/les/distributor.go deleted file mode 100644 index f90765b624..0000000000 --- a/les/distributor.go +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright 2017 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 . - -// Package light implements on-demand retrieval capable state and chain objects -// for the Ethereum Light Client. -package les - -import ( - "container/list" - "sync" - "time" -) - -// requestDistributor implements a mechanism that distributes requests to -// suitable peers, obeying flow control rules and prioritizing them in creation -// order (even when a resend is necessary). -type requestDistributor struct { - reqQueue *list.List - lastReqOrder uint64 - peers map[distPeer]struct{} - peerLock sync.RWMutex - stopChn, loopChn chan struct{} - loopNextSent bool - lock sync.Mutex -} - -// distPeer is an LES server peer interface for the request distributor. -// waitBefore returns either the necessary waiting time before sending a request -// with the given upper estimated cost or the estimated remaining relative buffer -// value after sending such a request (in which case the request can be sent -// immediately). At least one of these values is always zero. -type distPeer interface { - waitBefore(uint64) (time.Duration, float64) - canQueue() bool - queueSend(f func()) -} - -// distReq is the request abstraction used by the distributor. It is based on -// three callback functions: -// - getCost returns the upper estimate of the cost of sending the request to a given peer -// - canSend tells if the server peer is suitable to serve the request -// - request prepares sending the request to the given peer and returns a function that -// does the actual sending. Request order should be preserved but the callback itself should not -// block until it is sent because other peers might still be able to receive requests while -// one of them is blocking. Instead, the returned function is put in the peer's send queue. -type distReq struct { - getCost func(distPeer) uint64 - canSend func(distPeer) bool - request func(distPeer) func() - - reqOrder uint64 - sentChn chan distPeer - element *list.Element -} - -// newRequestDistributor creates a new request distributor -func newRequestDistributor(peers *peerSet, stopChn chan struct{}) *requestDistributor { - d := &requestDistributor{ - reqQueue: list.New(), - loopChn: make(chan struct{}, 2), - stopChn: stopChn, - peers: make(map[distPeer]struct{}), - } - if peers != nil { - peers.notify(d) - } - go d.loop() - return d -} - -// registerPeer implements peerSetNotify -func (d *requestDistributor) registerPeer(p *peer) { - d.peerLock.Lock() - d.peers[p] = struct{}{} - d.peerLock.Unlock() -} - -// unregisterPeer implements peerSetNotify -func (d *requestDistributor) unregisterPeer(p *peer) { - d.peerLock.Lock() - delete(d.peers, p) - d.peerLock.Unlock() -} - -// registerTestPeer adds a new test peer -func (d *requestDistributor) registerTestPeer(p distPeer) { - d.peerLock.Lock() - d.peers[p] = struct{}{} - d.peerLock.Unlock() -} - -// distMaxWait is the maximum waiting time after which further necessary waiting -// times are recalculated based on new feedback from the servers -const distMaxWait = time.Millisecond * 10 - -// main event loop -func (d *requestDistributor) loop() { - for { - select { - case <-d.stopChn: - d.lock.Lock() - elem := d.reqQueue.Front() - for elem != nil { - req := elem.Value.(*distReq) - close(req.sentChn) - req.sentChn = nil - elem = elem.Next() - } - d.lock.Unlock() - return - case <-d.loopChn: - d.lock.Lock() - d.loopNextSent = false - loop: - for { - peer, req, wait := d.nextRequest() - if req != nil && wait == 0 { - chn := req.sentChn // save sentChn because remove sets it to nil - d.remove(req) - send := req.request(peer) - if send != nil { - peer.queueSend(send) - } - chn <- peer - close(chn) - } else { - if wait == 0 { - // no request to send and nothing to wait for; the next - // queued request will wake up the loop - break loop - } - d.loopNextSent = true // a "next" signal has been sent, do not send another one until this one has been received - if wait > distMaxWait { - // waiting times may be reduced by incoming request replies, if it is too long, recalculate it periodically - wait = distMaxWait - } - go func() { - time.Sleep(wait) - d.loopChn <- struct{}{} - }() - break loop - } - } - d.lock.Unlock() - } - } -} - -// selectPeerItem represents a peer to be selected for a request by weightedRandomSelect -type selectPeerItem struct { - peer distPeer - req *distReq - weight int64 -} - -// Weight implements wrsItem interface -func (sp selectPeerItem) Weight() int64 { - return sp.weight -} - -// nextRequest returns the next possible request from any peer, along with the -// associated peer and necessary waiting time -func (d *requestDistributor) nextRequest() (distPeer, *distReq, time.Duration) { - checkedPeers := make(map[distPeer]struct{}) - elem := d.reqQueue.Front() - var ( - bestPeer distPeer - bestReq *distReq - bestWait time.Duration - sel *weightedRandomSelect - ) - - d.peerLock.RLock() - defer d.peerLock.RUnlock() - - for (len(d.peers) > 0 || elem == d.reqQueue.Front()) && elem != nil { - req := elem.Value.(*distReq) - canSend := false - for peer := range d.peers { - if _, ok := checkedPeers[peer]; !ok && peer.canQueue() && req.canSend(peer) { - canSend = true - cost := req.getCost(peer) - wait, bufRemain := peer.waitBefore(cost) - if wait == 0 { - if sel == nil { - sel = newWeightedRandomSelect() - } - sel.update(selectPeerItem{peer: peer, req: req, weight: int64(bufRemain*1000000) + 1}) - } else { - if bestReq == nil || wait < bestWait { - bestPeer = peer - bestReq = req - bestWait = wait - } - } - checkedPeers[peer] = struct{}{} - } - } - next := elem.Next() - if !canSend && elem == d.reqQueue.Front() { - close(req.sentChn) - d.remove(req) - } - elem = next - } - - if sel != nil { - c := sel.choose().(selectPeerItem) - return c.peer, c.req, 0 - } - return bestPeer, bestReq, bestWait -} - -// queue adds a request to the distribution queue, returns a channel where the -// receiving peer is sent once the request has been sent (request callback returned). -// If the request is cancelled or timed out without suitable peers, the channel is -// closed without sending any peer references to it. -func (d *requestDistributor) queue(r *distReq) chan distPeer { - d.lock.Lock() - defer d.lock.Unlock() - - if r.reqOrder == 0 { - d.lastReqOrder++ - r.reqOrder = d.lastReqOrder - } - - back := d.reqQueue.Back() - if back == nil || r.reqOrder > back.Value.(*distReq).reqOrder { - r.element = d.reqQueue.PushBack(r) - } else { - before := d.reqQueue.Front() - for before.Value.(*distReq).reqOrder < r.reqOrder { - before = before.Next() - } - r.element = d.reqQueue.InsertBefore(r, before) - } - - if !d.loopNextSent { - d.loopNextSent = true - d.loopChn <- struct{}{} - } - - r.sentChn = make(chan distPeer, 1) - return r.sentChn -} - -// cancel removes a request from the queue if it has not been sent yet (returns -// false if it has been sent already). It is guaranteed that the callback functions -// will not be called after cancel returns. -func (d *requestDistributor) cancel(r *distReq) bool { - d.lock.Lock() - defer d.lock.Unlock() - - if r.sentChn == nil { - return false - } - - close(r.sentChn) - d.remove(r) - return true -} - -// remove removes a request from the queue -func (d *requestDistributor) remove(r *distReq) { - r.sentChn = nil - if r.element != nil { - d.reqQueue.Remove(r.element) - r.element = nil - } -} diff --git a/les/distributor_test.go b/les/distributor_test.go deleted file mode 100644 index 8c7621f26b..0000000000 --- a/les/distributor_test.go +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2017 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 . - -// Package light implements on-demand retrieval capable state and chain objects -// for the Ethereum Light Client. -package les - -import ( - "math/rand" - "sync" - "testing" - "time" -) - -type testDistReq struct { - cost, procTime, order uint64 - canSendTo map[*testDistPeer]struct{} -} - -func (r *testDistReq) getCost(dp distPeer) uint64 { - return r.cost -} - -func (r *testDistReq) canSend(dp distPeer) bool { - _, ok := r.canSendTo[dp.(*testDistPeer)] - return ok -} - -func (r *testDistReq) request(dp distPeer) func() { - return func() { dp.(*testDistPeer).send(r) } -} - -type testDistPeer struct { - sent []*testDistReq - sumCost uint64 - lock sync.RWMutex -} - -func (p *testDistPeer) send(r *testDistReq) { - p.lock.Lock() - defer p.lock.Unlock() - - p.sent = append(p.sent, r) - p.sumCost += r.cost -} - -func (p *testDistPeer) worker(t *testing.T, checkOrder bool, stop chan struct{}) { - var last uint64 - for { - wait := time.Millisecond - p.lock.Lock() - if len(p.sent) > 0 { - rq := p.sent[0] - wait = time.Duration(rq.procTime) - p.sumCost -= rq.cost - if checkOrder { - if rq.order <= last { - t.Errorf("Requests processed in wrong order") - } - last = rq.order - } - p.sent = p.sent[1:] - } - p.lock.Unlock() - select { - case <-stop: - return - case <-time.After(wait): - } - } -} - -const ( - testDistBufLimit = 10000000 - testDistMaxCost = 1000000 - testDistPeerCount = 5 - testDistReqCount = 5000 - testDistMaxResendCount = 3 -) - -func (p *testDistPeer) waitBefore(cost uint64) (time.Duration, float64) { - p.lock.RLock() - sumCost := p.sumCost + cost - p.lock.RUnlock() - if sumCost < testDistBufLimit { - return 0, float64(testDistBufLimit-sumCost) / float64(testDistBufLimit) - } - return time.Duration(sumCost - testDistBufLimit), 0 -} - -func (p *testDistPeer) canQueue() bool { - return true -} - -func (p *testDistPeer) queueSend(f func()) { - f() -} - -func TestRequestDistributor(t *testing.T) { - testRequestDistributor(t, false) -} - -func TestRequestDistributorResend(t *testing.T) { - testRequestDistributor(t, true) -} - -func testRequestDistributor(t *testing.T, resend bool) { - stop := make(chan struct{}) - defer close(stop) - - dist := newRequestDistributor(nil, stop) - var peers [testDistPeerCount]*testDistPeer - for i := range peers { - peers[i] = &testDistPeer{} - go peers[i].worker(t, !resend, stop) - dist.registerTestPeer(peers[i]) - } - - var wg sync.WaitGroup - - for i := 1; i <= testDistReqCount; i++ { - cost := uint64(rand.Int63n(testDistMaxCost)) - procTime := uint64(rand.Int63n(int64(cost + 1))) - rq := &testDistReq{ - cost: cost, - procTime: procTime, - order: uint64(i), - canSendTo: make(map[*testDistPeer]struct{}), - } - for _, peer := range peers { - if rand.Intn(2) != 0 { - rq.canSendTo[peer] = struct{}{} - } - } - - wg.Add(1) - req := &distReq{ - getCost: rq.getCost, - canSend: rq.canSend, - request: rq.request, - } - chn := dist.queue(req) - go func() { - cnt := 1 - if resend && len(rq.canSendTo) != 0 { - cnt = rand.Intn(testDistMaxResendCount) + 1 - } - for i := 0; i < cnt; i++ { - if i != 0 { - chn = dist.queue(req) - } - p := <-chn - if p == nil { - if len(rq.canSendTo) != 0 { - t.Errorf("Request that could have been sent was dropped") - } - } else { - peer := p.(*testDistPeer) - if _, ok := rq.canSendTo[peer]; !ok { - t.Errorf("Request sent to wrong peer") - } - } - } - wg.Done() - }() - if rand.Intn(1000) == 0 { - time.Sleep(time.Duration(rand.Intn(5000000))) - } - } - - wg.Wait() -} diff --git a/les/execqueue.go b/les/execqueue.go deleted file mode 100644 index 614721bf0d..0000000000 --- a/les/execqueue.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2017 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 . - -package les - -import "sync" - -// execQueue implements a queue that executes function calls in a single thread, -// in the same order as they have been queued. -type execQueue struct { - mu sync.Mutex - cond *sync.Cond - funcs []func() - closeWait chan struct{} -} - -// newExecQueue creates a new execution queue. -func newExecQueue(capacity int) *execQueue { - q := &execQueue{funcs: make([]func(), 0, capacity)} - q.cond = sync.NewCond(&q.mu) - go q.loop() - return q -} - -func (q *execQueue) loop() { - for f := q.waitNext(false); f != nil; f = q.waitNext(true) { - f() - } - close(q.closeWait) -} - -func (q *execQueue) waitNext(drop bool) (f func()) { - q.mu.Lock() - if drop { - // Remove the function that just executed. We do this here instead of when - // dequeuing so len(q.funcs) includes the function that is running. - q.funcs = append(q.funcs[:0], q.funcs[1:]...) - } - for !q.isClosed() { - if len(q.funcs) > 0 { - f = q.funcs[0] - break - } - q.cond.Wait() - } - q.mu.Unlock() - return f -} - -func (q *execQueue) isClosed() bool { - return q.closeWait != nil -} - -// canQueue returns true if more function calls can be added to the execution queue. -func (q *execQueue) canQueue() bool { - q.mu.Lock() - ok := !q.isClosed() && len(q.funcs) < cap(q.funcs) - q.mu.Unlock() - return ok -} - -// queue adds a function call to the execution queue. Returns true if successful. -func (q *execQueue) queue(f func()) bool { - q.mu.Lock() - ok := !q.isClosed() && len(q.funcs) < cap(q.funcs) - if ok { - q.funcs = append(q.funcs, f) - q.cond.Signal() - } - q.mu.Unlock() - return ok -} - -// quit stops the exec queue. -// quit waits for the current execution to finish before returning. -func (q *execQueue) quit() { - q.mu.Lock() - if !q.isClosed() { - q.closeWait = make(chan struct{}) - q.cond.Signal() - } - q.mu.Unlock() - <-q.closeWait -} diff --git a/les/execqueue_test.go b/les/execqueue_test.go deleted file mode 100644 index cd45b03f22..0000000000 --- a/les/execqueue_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2017 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 . - -package les - -import ( - "testing" -) - -func TestExecQueue(t *testing.T) { - var ( - N = 10000 - q = newExecQueue(N) - counter int - execd = make(chan int) - testexit = make(chan struct{}) - ) - defer q.quit() - defer close(testexit) - - check := func(state string, wantOK bool) { - c := counter - counter++ - qf := func() { - select { - case execd <- c: - case <-testexit: - } - } - if q.canQueue() != wantOK { - t.Fatalf("canQueue() == %t for %s", !wantOK, state) - } - if q.queue(qf) != wantOK { - t.Fatalf("canQueue() == %t for %s", !wantOK, state) - } - } - - for i := 0; i < N; i++ { - check("queue below cap", true) - } - check("full queue", false) - for i := 0; i < N; i++ { - if c := <-execd; c != i { - t.Fatal("execution out of order") - } - } - q.quit() - check("closed queue", false) -} diff --git a/les/fetcher.go b/les/fetcher.go deleted file mode 100644 index afe9bdf470..0000000000 --- a/les/fetcher.go +++ /dev/null @@ -1,780 +0,0 @@ -// Copyright 2016 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 . - -// Package les implements the Light Ethereum Subprotocol. -package les - -import ( - "math/big" - "sync" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/common/mclock" - "github.com/AlayaNetwork/Alaya-Go/consensus" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/light" - "github.com/AlayaNetwork/Alaya-Go/log" -) - -const ( - blockDelayTimeout = time.Second * 10 // timeout for a peer to announce a head that has already been confirmed by others - maxNodeCount = 20 // maximum number of fetcherTreeNode entries remembered for each peer -) - -// lightFetcher implements retrieval of newly announced headers. It also provides a peerHasBlock function for the -// ODR system to ensure that we only request data related to a certain block from peers who have already processed -// and announced that block. -type lightFetcher struct { - pm *ProtocolManager - odr *LesOdr - chain *light.LightChain - - lock sync.Mutex // lock protects access to the fetcher's internal state variables except sent requests - maxConfirmedBn *big.Int - peers map[*peer]*fetcherPeerInfo - lastUpdateStats *updateStatsEntry - syncing bool - syncDone chan *peer - - reqMu sync.RWMutex // reqMu protects access to sent header fetch requests - requested map[uint64]fetchRequest - deliverChn chan fetchResponse - timeoutChn chan uint64 - requestChn chan bool // true if initiated from outside -} - -// fetcherPeerInfo holds fetcher-specific information about each active peer -type fetcherPeerInfo struct { - root, lastAnnounced *fetcherTreeNode - nodeCnt int - confirmedBn *big.Int - bestConfirmed *fetcherTreeNode - nodeByHash map[common.Hash]*fetcherTreeNode - firstUpdateStats *updateStatsEntry -} - -// fetcherTreeNode is a node of a tree that holds information about blocks recently -// announced and confirmed by a certain peer. Each new announce message from a peer -// adds nodes to the tree, based on the previous announced head and the reorg depth. -// There are three possible states for a tree node: -// - announced: not downloaded (known) yet, but we know its head, number and td -// - intermediate: not known, hash and td are empty, they are filled out when it becomes known -// - known: both announced by this peer and downloaded (from any peer). -// This structure makes it possible to always know which peer has a certain block, -// which is necessary for selecting a suitable peer for ODR requests and also for -// canonizing new heads. It also helps to always download the minimum necessary -// amount of headers with a single request. -type fetcherTreeNode struct { - hash common.Hash - number uint64 - known, requested bool - parent *fetcherTreeNode - children []*fetcherTreeNode -} - -// fetchRequest represents a header download request -type fetchRequest struct { - hash common.Hash - amount uint64 - peer *peer - sent mclock.AbsTime - timeout bool -} - -// fetchResponse represents a header download response -type fetchResponse struct { - reqID uint64 - headers []*types.Header - peer *peer -} - -// newLightFetcher creates a new light fetcher -func newLightFetcher(pm *ProtocolManager) *lightFetcher { - f := &lightFetcher{ - pm: pm, - chain: pm.blockchain.(*light.LightChain), - odr: pm.odr, - peers: make(map[*peer]*fetcherPeerInfo), - deliverChn: make(chan fetchResponse, 100), - requested: make(map[uint64]fetchRequest), - timeoutChn: make(chan uint64), - requestChn: make(chan bool, 100), - syncDone: make(chan *peer), - maxConfirmedBn: big.NewInt(0), - } - pm.peers.notify(f) - - f.pm.wg.Add(1) - go f.syncLoop() - return f -} - -// syncLoop is the main event loop of the light fetcher -func (f *lightFetcher) syncLoop() { - requesting := false - defer f.pm.wg.Done() - for { - select { - case <-f.pm.quitSync: - return - // when a new announce is received, request loop keeps running until - // no further requests are necessary or possible - case newAnnounce := <-f.requestChn: - f.lock.Lock() - s := requesting - requesting = false - var ( - rq *distReq - reqID uint64 - ) - if !f.syncing && !(newAnnounce && s) { - rq, reqID = f.nextRequest() - } - syncing := f.syncing - f.lock.Unlock() - - if rq != nil { - requesting = true - _, ok := <-f.pm.reqDist.queue(rq) - if !ok { - f.requestChn <- false - } - - if !syncing { - go func() { - time.Sleep(softRequestTimeout) - f.reqMu.Lock() - req, ok := f.requested[reqID] - if ok { - req.timeout = true - f.requested[reqID] = req - } - f.reqMu.Unlock() - // keep starting new requests while possible - f.requestChn <- false - }() - } - } - case reqID := <-f.timeoutChn: - f.reqMu.Lock() - req, ok := f.requested[reqID] - if ok { - delete(f.requested, reqID) - } - f.reqMu.Unlock() - if ok { - f.pm.serverPool.adjustResponseTime(req.peer.poolEntry, time.Duration(mclock.Now()-req.sent), true) - req.peer.Log().Debug("Fetching data timed out hard") - go f.pm.removePeer(req.peer.id) - } - case resp := <-f.deliverChn: - f.reqMu.Lock() - req, ok := f.requested[resp.reqID] - if ok && req.peer != resp.peer { - ok = false - } - if ok { - delete(f.requested, resp.reqID) - } - f.reqMu.Unlock() - if ok { - f.pm.serverPool.adjustResponseTime(req.peer.poolEntry, time.Duration(mclock.Now()-req.sent), req.timeout) - } - f.lock.Lock() - if !ok || !(f.syncing || f.processResponse(req, resp)) { - resp.peer.Log().Debug("Failed processing response") - go f.pm.removePeer(resp.peer.id) - } - f.lock.Unlock() - case p := <-f.syncDone: - f.lock.Lock() - p.Log().Debug("Done synchronising with peer") - f.checkSyncedHeaders(p) - f.syncing = false - f.lock.Unlock() - } - } -} - -// registerPeer adds a new peer to the fetcher's peer set -func (f *lightFetcher) registerPeer(p *peer) { - p.lock.Lock() - p.hasBlock = func(hash common.Hash, number uint64) bool { - return f.peerHasBlock(p, hash, number) - } - p.lock.Unlock() - - f.lock.Lock() - defer f.lock.Unlock() - - f.peers[p] = &fetcherPeerInfo{nodeByHash: make(map[common.Hash]*fetcherTreeNode)} -} - -// unregisterPeer removes a new peer from the fetcher's peer set -func (f *lightFetcher) unregisterPeer(p *peer) { - p.lock.Lock() - p.hasBlock = nil - p.lock.Unlock() - - f.lock.Lock() - defer f.lock.Unlock() - - // check for potential timed out block delay statistics - f.checkUpdateStats(p, nil) - delete(f.peers, p) -} - -// announce processes a new announcement message received from a peer, adding new -// nodes to the peer's block tree and removing old nodes if necessary -func (f *lightFetcher) announce(p *peer, head *announceData) { - f.lock.Lock() - defer f.lock.Unlock() - p.Log().Debug("Received new announcement", "number", head.Number, "hash", head.Hash, "reorg", head.ReorgDepth) - - fp := f.peers[p] - if fp == nil { - p.Log().Debug("Announcement from unknown peer") - return - } - - if fp.lastAnnounced != nil && head.Number <= fp.lastAnnounced.number { - // announced tds should be strictly monotonic - //p.Log().Debug("Received non-monotonic td", "current", head.Td, "previous", fp.lastAnnounced.td) - go f.pm.removePeer(p.id) - return - } - - n := fp.lastAnnounced - for i := uint64(0); i < head.ReorgDepth; i++ { - if n == nil { - break - } - n = n.parent - } - // n is now the reorg common ancestor, add a new branch of nodes - if n != nil && (head.Number >= n.number+maxNodeCount || head.Number <= n.number) { - // if announced head block height is lower or same as n or too far from it to add - // intermediate nodes then discard previous announcement info and trigger a resync - n = nil - fp.nodeCnt = 0 - fp.nodeByHash = make(map[common.Hash]*fetcherTreeNode) - } - if n != nil { - // check if the node count is too high to add new nodes, discard oldest ones if necessary - locked := false - for uint64(fp.nodeCnt)+head.Number-n.number > maxNodeCount && fp.root != nil { - if !locked { - f.chain.LockChain() - defer f.chain.UnlockChain() - locked = true - } - // if one of root's children is canonical, keep it, delete other branches and root itself - var newRoot *fetcherTreeNode - for i, nn := range fp.root.children { - if rawdb.ReadCanonicalHash(f.pm.chainDb, nn.number) == nn.hash { - fp.root.children = append(fp.root.children[:i], fp.root.children[i+1:]...) - nn.parent = nil - newRoot = nn - break - } - } - fp.deleteNode(fp.root) - if n == fp.root { - n = newRoot - } - fp.root = newRoot - if newRoot == nil || !f.checkKnownNode(p, newRoot) { - fp.bestConfirmed = nil - fp.confirmedBn = nil - } - - if n == nil { - break - } - } - if n != nil { - for n.number < head.Number { - nn := &fetcherTreeNode{number: n.number + 1, parent: n} - n.children = append(n.children, nn) - n = nn - fp.nodeCnt++ - } - n.hash = head.Hash - //n.td = head.Td - fp.nodeByHash[n.hash] = n - } - } - if n == nil { - // could not find reorg common ancestor or had to delete entire tree, a new root and a resync is needed - if fp.root != nil { - fp.deleteNode(fp.root) - } - n = &fetcherTreeNode{hash: head.Hash, number: head.Number} - fp.root = n - fp.nodeCnt++ - fp.nodeByHash[n.hash] = n - fp.bestConfirmed = nil - fp.confirmedBn = nil - } - - f.checkKnownNode(p, n) - p.lock.Lock() - p.headInfo = head - fp.lastAnnounced = n - p.lock.Unlock() - f.checkUpdateStats(p, nil) - f.requestChn <- true -} - -// peerHasBlock returns true if we can assume the peer knows the given block -// based on its announcements -func (f *lightFetcher) peerHasBlock(p *peer, hash common.Hash, number uint64) bool { - f.lock.Lock() - defer f.lock.Unlock() - - if f.syncing { - // always return true when syncing - // false positives are acceptable, a more sophisticated condition can be implemented later - return true - } - - fp := f.peers[p] - if fp == nil || fp.root == nil { - return false - } - - if number >= fp.root.number { - // it is recent enough that if it is known, is should be in the peer's block tree - return fp.nodeByHash[hash] != nil - } - f.chain.LockChain() - defer f.chain.UnlockChain() - // if it's older than the peer's block tree root but it's in the same canonical chain - // as the root, we can still be sure the peer knows it - // - // when syncing, just check if it is part of the known chain, there is nothing better we - // can do since we do not know the most recent block hash yet - return rawdb.ReadCanonicalHash(f.pm.chainDb, fp.root.number) == fp.root.hash && rawdb.ReadCanonicalHash(f.pm.chainDb, number) == hash -} - -// requestAmount calculates the amount of headers to be downloaded starting -// from a certain head backwards -func (f *lightFetcher) requestAmount(p *peer, n *fetcherTreeNode) uint64 { - amount := uint64(0) - nn := n - for nn != nil && !f.checkKnownNode(p, nn) { - nn = nn.parent - amount++ - } - if nn == nil { - amount = n.number - } - return amount -} - -// requestedID tells if a certain reqID has been requested by the fetcher -func (f *lightFetcher) requestedID(reqID uint64) bool { - f.reqMu.RLock() - _, ok := f.requested[reqID] - f.reqMu.RUnlock() - return ok -} - -// nextRequest selects the peer and announced head to be requested next, amount -// to be downloaded starting from the head backwards is also returned -func (f *lightFetcher) nextRequest() (*distReq, uint64) { - var ( - bestHash common.Hash - bestAmount uint64 - ) - //bestTd := f.maxConfirmedTd - bestBn := f.maxConfirmedBn - bestSyncing := false - - for p, fp := range f.peers { - for hash, n := range fp.nodeByHash { - if !f.checkKnownNode(p, n) && !n.requested && (bestBn == nil || n.number >= bestBn.Uint64()) { - amount := f.requestAmount(p, n) - if bestBn == nil || n.number > bestBn.Uint64() || amount < bestAmount { - bestHash = hash - bestAmount = amount - bestBn = new (big.Int).SetUint64(n.number) - bestSyncing = fp.bestConfirmed == nil || fp.root == nil || !f.checkKnownNode(p, fp.root) - } - } - } - } - if bestBn == f.maxConfirmedBn { - return nil, 0 - } - - f.syncing = bestSyncing - - var rq *distReq - reqID := genReqID() - if f.syncing { - rq = &distReq{ - getCost: func(dp distPeer) uint64 { - return 0 - }, - canSend: func(dp distPeer) bool { - p := dp.(*peer) - f.lock.Lock() - defer f.lock.Unlock() - - fp := f.peers[p] - return fp != nil && fp.nodeByHash[bestHash] != nil - }, - request: func(dp distPeer) func() { - go func() { - p := dp.(*peer) - p.Log().Debug("Synchronisation started") - f.pm.synchronise(p) - f.syncDone <- p - }() - return nil - }, - } - } else { - rq = &distReq{ - getCost: func(dp distPeer) uint64 { - p := dp.(*peer) - return p.GetRequestCost(GetBlockHeadersMsg, int(bestAmount)) - }, - canSend: func(dp distPeer) bool { - p := dp.(*peer) - f.lock.Lock() - defer f.lock.Unlock() - - fp := f.peers[p] - if fp == nil { - return false - } - n := fp.nodeByHash[bestHash] - return n != nil && !n.requested - }, - request: func(dp distPeer) func() { - p := dp.(*peer) - f.lock.Lock() - fp := f.peers[p] - if fp != nil { - n := fp.nodeByHash[bestHash] - if n != nil { - n.requested = true - } - } - f.lock.Unlock() - - cost := p.GetRequestCost(GetBlockHeadersMsg, int(bestAmount)) - p.fcServer.QueueRequest(reqID, cost) - f.reqMu.Lock() - f.requested[reqID] = fetchRequest{hash: bestHash, amount: bestAmount, peer: p, sent: mclock.Now()} - f.reqMu.Unlock() - go func() { - time.Sleep(hardRequestTimeout) - f.timeoutChn <- reqID - }() - return func() { p.RequestHeadersByHash(reqID, cost, bestHash, int(bestAmount), 0, true) } - }, - } - } - return rq, reqID -} - -// deliverHeaders delivers header download request responses for processing -func (f *lightFetcher) deliverHeaders(peer *peer, reqID uint64, headers []*types.Header) { - f.deliverChn <- fetchResponse{reqID: reqID, headers: headers, peer: peer} -} - -// processResponse processes header download request responses, returns true if successful -func (f *lightFetcher) processResponse(req fetchRequest, resp fetchResponse) bool { - if uint64(len(resp.headers)) != req.amount || resp.headers[0].Hash() != req.hash { - req.peer.Log().Debug("Response content mismatch", "requested", len(resp.headers), "reqfrom", resp.headers[0], "delivered", req.amount, "delfrom", req.hash) - return false - } - headers := make([]*types.Header, req.amount) - for i, header := range resp.headers { - headers[int(req.amount)-1-i] = header - } - if _, err := f.chain.InsertHeaderChain(headers, 1); err != nil { - if err == consensus.ErrFutureBlock { - return true - } - log.Debug("Failed to insert header chain", "err", err) - return false - } - /*tds := make([]*big.Int, len(headers)) - for i, header := range headers { - td := f.chain.GetTd(header.Hash(), header.Number.Uint64()) - if td == nil { - log.Debug("Total difficulty not found for header", "index", i+1, "number", header.Number, "hash", header.Hash()) - return false - } - tds[i] = td - }*/ - f.newHeaders(headers) - return true -} - -// newHeaders updates the block trees of all active peers according to a newly -// downloaded and validated batch or headers -func (f *lightFetcher) newHeaders(headers []*types.Header) { - var maxBn *big.Int - for p, fp := range f.peers { - if !f.checkAnnouncedHeaders(fp, headers) { - p.Log().Debug("Inconsistent announcement") - go f.pm.removePeer(p.id) - } - if fp.confirmedBn != nil && (maxBn == nil || maxBn.Cmp(fp.confirmedBn) > 0) { - maxBn = fp.confirmedBn - } - } - if maxBn != nil { - f.updateMaxConfirmedBn(maxBn) - } -} - -// checkAnnouncedHeaders updates peer's block tree if necessary after validating -// a batch of headers. It searches for the latest header in the batch that has a -// matching tree node (if any), and if it has not been marked as known already, -// sets it and its parents to known (even those which are older than the currently -// validated ones). Return value shows if all hashes, numbers and Tds matched -// correctly to the announced values (otherwise the peer should be dropped). -func (f *lightFetcher) checkAnnouncedHeaders(fp *fetcherPeerInfo, headers []*types.Header) bool { - var ( - n *fetcherTreeNode - header *types.Header - //td *big.Int - ) - - for i := len(headers) - 1; ; i-- { - if i < 0 { - if n == nil { - // no more headers and nothing to match - return true - } - // we ran out of recently delivered headers but have not reached a node known by this peer yet, continue matching - hash, number := header.ParentHash, header.Number.Uint64()-1 - //td = f.chain.GetTd(hash, number) - header = f.chain.GetHeader(hash, number) - if header == nil { - log.Error("Missing parent of validated header", "hash", hash, "number", number) - return false - } - } else { - header = headers[i] - if header == nil { - return true - } - //td = tds[i] - } - hash := header.Hash() - number := header.Number.Uint64() - numberBig := header.Number - if n == nil { - n = fp.nodeByHash[hash] - } - if n != nil { - if n.hash == (common.Hash{}) { - // node was unannounced - if nn := fp.nodeByHash[hash]; nn != nil { - // if there was already a node with the same hash, continue there and drop this one - nn.children = append(nn.children, n.children...) - n.children = nil - fp.deleteNode(n) - n = nn - } else { - n.hash = hash - //n.td = td - fp.nodeByHash[hash] = n - } - } - // check if it matches the header - if n.hash != hash || n.number != number { - // peer has previously made an invalid announcement - return false - } - if n.known { - // we reached a known node that matched our expectations, return with success - return true - } - n.known = true - if fp.confirmedBn == nil || numberBig.Cmp(fp.confirmedBn) > 0 { - fp.confirmedBn = numberBig - fp.bestConfirmed = n - } - n = n.parent - if n == nil { - return true - } - } - } -} - -// checkSyncedHeaders updates peer's block tree after synchronisation by marking -// downloaded headers as known. If none of the announced headers are found after -// syncing, the peer is dropped. -func (f *lightFetcher) checkSyncedHeaders(p *peer) { - fp := f.peers[p] - if fp == nil { - p.Log().Debug("Unknown peer to check sync headers") - return - } - n := fp.lastAnnounced - //var td *big.Int - for n != nil { - if n.hash != (common.Hash{}){ - break - } - n = n.parent - } - // now n is the latest downloaded header after syncing - if n == nil { - p.Log().Debug("Synchronisation failed") - go f.pm.removePeer(p.id) - } else { - header := f.chain.GetHeader(n.hash, n.number) - f.newHeaders([]*types.Header{header}) - } -} - -// checkKnownNode checks if a block tree node is known (downloaded and validated) -// If it was not known previously but found in the database, sets its known flag -func (f *lightFetcher) checkKnownNode(p *peer, n *fetcherTreeNode) bool { - if n.known { - return true - } - /*td := f.chain.GetTd(n.hash, n.number) - if td == nil { - return false - }*/ - header := f.chain.GetHeader(n.hash, n.number) - // check the availability of both header and td because reads are not protected by chain db mutex - // Note: returning false is always safe here - if header == nil { - return false - } - - fp := f.peers[p] - if fp == nil { - p.Log().Debug("Unknown peer to check known nodes") - return false - } - if !f.checkAnnouncedHeaders(fp, []*types.Header{header}) { - p.Log().Debug("Inconsistent announcement") - go f.pm.removePeer(p.id) - } - if fp.confirmedBn != nil { - f.updateMaxConfirmedBn(fp.confirmedBn) - } - return n.known -} - -// deleteNode deletes a node and its child subtrees from a peer's block tree -func (fp *fetcherPeerInfo) deleteNode(n *fetcherTreeNode) { - if n.parent != nil { - for i, nn := range n.parent.children { - if nn == n { - n.parent.children = append(n.parent.children[:i], n.parent.children[i+1:]...) - break - } - } - } - for { - if n.hash != (common.Hash{}) { - delete(fp.nodeByHash, n.hash) - } - fp.nodeCnt-- - if len(n.children) == 0 { - return - } - for i, nn := range n.children { - if i == 0 { - n = nn - } else { - fp.deleteNode(nn) - } - } - } -} - -// updateStatsEntry items form a linked list that is expanded with a new item every time a new head with a higher Td -// than the previous one has been downloaded and validated. The list contains a series of maximum confirmed Td values -// and the time these values have been confirmed, both increasing monotonically. A maximum confirmed Td is calculated -// both globally for all peers and also for each individual peer (meaning that the given peer has announced the head -// and it has also been downloaded from any peer, either before or after the given announcement). -// The linked list has a global tail where new confirmed Td entries are added and a separate head for each peer, -// pointing to the next Td entry that is higher than the peer's max confirmed Td (nil if it has already confirmed -// the current global head). -type updateStatsEntry struct { - time mclock.AbsTime - bn *big.Int - next *updateStatsEntry -} - -// updateMaxConfirmedTd updates the block delay statistics of active peers. Whenever a new highest Td is confirmed, -// adds it to the end of a linked list together with the time it has been confirmed. Then checks which peers have -// already confirmed a head with the same or higher Td (which counts as zero block delay) and updates their statistics. -// Those who have not confirmed such a head by now will be updated by a subsequent checkUpdateStats call with a -// positive block delay value. -func (f *lightFetcher) updateMaxConfirmedBn(bn *big.Int) { - if f.maxConfirmedBn == nil || bn.Cmp(f.maxConfirmedBn) > 0 { - f.maxConfirmedBn = bn - newEntry := &updateStatsEntry{ - time: mclock.Now(), - bn: bn, - } - if f.lastUpdateStats != nil { - f.lastUpdateStats.next = newEntry - } - f.lastUpdateStats = newEntry - for p := range f.peers { - f.checkUpdateStats(p, newEntry) - } - } -} - -// checkUpdateStats checks those peers who have not confirmed a certain highest Td (or a larger one) by the time it -// has been confirmed by another peer. If they have confirmed such a head by now, their stats are updated with the -// block delay which is (this peer's confirmation time)-(first confirmation time). After blockDelayTimeout has passed, -// the stats are updated with blockDelayTimeout value. In either case, the confirmed or timed out updateStatsEntry -// items are removed from the head of the linked list. -// If a new entry has been added to the global tail, it is passed as a parameter here even though this function -// assumes that it has already been added, so that if the peer's list is empty (all heads confirmed, head is nil), -// it can set the new head to newEntry. -func (f *lightFetcher) checkUpdateStats(p *peer, newEntry *updateStatsEntry) { - now := mclock.Now() - fp := f.peers[p] - if fp == nil { - p.Log().Debug("Unknown peer to check update stats") - return - } - if newEntry != nil && fp.firstUpdateStats == nil { - fp.firstUpdateStats = newEntry - } - for fp.firstUpdateStats != nil && fp.firstUpdateStats.time <= now-mclock.AbsTime(blockDelayTimeout) { - f.pm.serverPool.adjustBlockDelay(p.poolEntry, blockDelayTimeout) - fp.firstUpdateStats = fp.firstUpdateStats.next - } - if fp.confirmedBn != nil { - for fp.firstUpdateStats != nil && fp.firstUpdateStats.bn.Cmp(fp.confirmedBn) <= 0 { - f.pm.serverPool.adjustBlockDelay(p.poolEntry, time.Duration(now-fp.firstUpdateStats.time)) - fp.firstUpdateStats = fp.firstUpdateStats.next - } - } -} diff --git a/les/flowcontrol/control.go b/les/flowcontrol/control.go deleted file mode 100644 index 90306b2e1e..0000000000 --- a/les/flowcontrol/control.go +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright 2016 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 . - -// Package flowcontrol implements a client side flow control mechanism -package flowcontrol - -import ( - "sync" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common/mclock" -) - -const fcTimeConst = time.Millisecond - -type ServerParams struct { - BufLimit, MinRecharge uint64 -} - -type ClientNode struct { - params *ServerParams - bufValue uint64 - lastTime mclock.AbsTime - lock sync.Mutex - cm *ClientManager - cmNode *cmNode -} - -func NewClientNode(cm *ClientManager, params *ServerParams) *ClientNode { - node := &ClientNode{ - cm: cm, - params: params, - bufValue: params.BufLimit, - lastTime: mclock.Now(), - } - node.cmNode = cm.addNode(node) - return node -} - -func (peer *ClientNode) Remove(cm *ClientManager) { - cm.removeNode(peer.cmNode) -} - -func (peer *ClientNode) recalcBV(time mclock.AbsTime) { - dt := uint64(time - peer.lastTime) - if time < peer.lastTime { - dt = 0 - } - peer.bufValue += peer.params.MinRecharge * dt / uint64(fcTimeConst) - if peer.bufValue > peer.params.BufLimit { - peer.bufValue = peer.params.BufLimit - } - peer.lastTime = time -} - -func (peer *ClientNode) AcceptRequest() (uint64, bool) { - peer.lock.Lock() - defer peer.lock.Unlock() - - time := mclock.Now() - peer.recalcBV(time) - return peer.bufValue, peer.cm.accept(peer.cmNode, time) -} - -func (peer *ClientNode) RequestProcessed(cost uint64) (bv, realCost uint64) { - peer.lock.Lock() - defer peer.lock.Unlock() - - time := mclock.Now() - peer.recalcBV(time) - peer.bufValue -= cost - peer.recalcBV(time) - rcValue, rcost := peer.cm.processed(peer.cmNode, time) - if rcValue < peer.params.BufLimit { - bv := peer.params.BufLimit - rcValue - if bv > peer.bufValue { - peer.bufValue = bv - } - } - return peer.bufValue, rcost -} - -type ServerNode struct { - bufEstimate uint64 - lastTime mclock.AbsTime - params *ServerParams - sumCost uint64 // sum of req costs sent to this server - pending map[uint64]uint64 // value = sumCost after sending the given req - lock sync.RWMutex -} - -func NewServerNode(params *ServerParams) *ServerNode { - return &ServerNode{ - bufEstimate: params.BufLimit, - lastTime: mclock.Now(), - params: params, - pending: make(map[uint64]uint64), - } -} - -func (peer *ServerNode) recalcBLE(time mclock.AbsTime) { - dt := uint64(time - peer.lastTime) - if time < peer.lastTime { - dt = 0 - } - peer.bufEstimate += peer.params.MinRecharge * dt / uint64(fcTimeConst) - if peer.bufEstimate > peer.params.BufLimit { - peer.bufEstimate = peer.params.BufLimit - } - peer.lastTime = time -} - -// safetyMargin is added to the flow control waiting time when estimated buffer value is low -const safetyMargin = time.Millisecond - -func (peer *ServerNode) canSend(maxCost uint64) (time.Duration, float64) { - peer.recalcBLE(mclock.Now()) - maxCost += uint64(safetyMargin) * peer.params.MinRecharge / uint64(fcTimeConst) - if maxCost > peer.params.BufLimit { - maxCost = peer.params.BufLimit - } - if peer.bufEstimate >= maxCost { - return 0, float64(peer.bufEstimate-maxCost) / float64(peer.params.BufLimit) - } - return time.Duration((maxCost - peer.bufEstimate) * uint64(fcTimeConst) / peer.params.MinRecharge), 0 -} - -// CanSend returns the minimum waiting time required before sending a request -// with the given maximum estimated cost. Second return value is the relative -// estimated buffer level after sending the request (divided by BufLimit). -func (peer *ServerNode) CanSend(maxCost uint64) (time.Duration, float64) { - peer.lock.RLock() - defer peer.lock.RUnlock() - - return peer.canSend(maxCost) -} - -// QueueRequest should be called when the request has been assigned to the given -// server node, before putting it in the send queue. It is mandatory that requests -// are sent in the same order as the QueueRequest calls are made. -func (peer *ServerNode) QueueRequest(reqID, maxCost uint64) { - peer.lock.Lock() - defer peer.lock.Unlock() - - peer.bufEstimate -= maxCost - peer.sumCost += maxCost - peer.pending[reqID] = peer.sumCost -} - -// GotReply adjusts estimated buffer value according to the value included in -// the latest request reply. -func (peer *ServerNode) GotReply(reqID, bv uint64) { - - peer.lock.Lock() - defer peer.lock.Unlock() - - if bv > peer.params.BufLimit { - bv = peer.params.BufLimit - } - sc, ok := peer.pending[reqID] - if !ok { - return - } - delete(peer.pending, reqID) - cc := peer.sumCost - sc - peer.bufEstimate = 0 - if bv > cc { - peer.bufEstimate = bv - cc - } - peer.lastTime = mclock.Now() -} diff --git a/les/flowcontrol/manager.go b/les/flowcontrol/manager.go deleted file mode 100644 index a59c735466..0000000000 --- a/les/flowcontrol/manager.go +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright 2016 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 . - -// Package flowcontrol implements a client side flow control mechanism -package flowcontrol - -import ( - "sync" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common/mclock" -) - -const rcConst = 1000000 - -type cmNode struct { - node *ClientNode - lastUpdate mclock.AbsTime - serving, recharging bool - rcWeight uint64 - rcValue, rcDelta, startValue int64 - finishRecharge mclock.AbsTime -} - -func (node *cmNode) update(time mclock.AbsTime) { - dt := int64(time - node.lastUpdate) - node.rcValue += node.rcDelta * dt / rcConst - node.lastUpdate = time - if node.recharging && time >= node.finishRecharge { - node.recharging = false - node.rcDelta = 0 - node.rcValue = 0 - } -} - -func (node *cmNode) set(serving bool, simReqCnt, sumWeight uint64) { - if node.serving && !serving { - node.recharging = true - sumWeight += node.rcWeight - } - node.serving = serving - if node.recharging && serving { - node.recharging = false - sumWeight -= node.rcWeight - } - - node.rcDelta = 0 - if serving { - node.rcDelta = int64(rcConst / simReqCnt) - } - if node.recharging { - node.rcDelta = -int64(node.node.cm.rcRecharge * node.rcWeight / sumWeight) - node.finishRecharge = node.lastUpdate + mclock.AbsTime(node.rcValue*rcConst/(-node.rcDelta)) - } -} - -type ClientManager struct { - lock sync.Mutex - nodes map[*cmNode]struct{} - simReqCnt, sumWeight, rcSumValue uint64 - maxSimReq, maxRcSum uint64 - rcRecharge uint64 - resumeQueue chan chan bool - time mclock.AbsTime -} - -func NewClientManager(rcTarget, maxSimReq, maxRcSum uint64) *ClientManager { - cm := &ClientManager{ - nodes: make(map[*cmNode]struct{}), - resumeQueue: make(chan chan bool), - rcRecharge: rcConst * rcConst / (100*rcConst/rcTarget - rcConst), - maxSimReq: maxSimReq, - maxRcSum: maxRcSum, - } - go cm.queueProc() - return cm -} - -func (self *ClientManager) Stop() { - self.lock.Lock() - defer self.lock.Unlock() - - // signal any waiting accept routines to return false - self.nodes = make(map[*cmNode]struct{}) - close(self.resumeQueue) -} - -func (self *ClientManager) addNode(cnode *ClientNode) *cmNode { - time := mclock.Now() - node := &cmNode{ - node: cnode, - lastUpdate: time, - finishRecharge: time, - rcWeight: 1, - } - self.lock.Lock() - defer self.lock.Unlock() - - self.nodes[node] = struct{}{} - self.update(mclock.Now()) - return node -} - -func (self *ClientManager) removeNode(node *cmNode) { - self.lock.Lock() - defer self.lock.Unlock() - - time := mclock.Now() - self.stop(node, time) - delete(self.nodes, node) - self.update(time) -} - -// recalc sumWeight -func (self *ClientManager) updateNodes(time mclock.AbsTime) (rce bool) { - var sumWeight, rcSum uint64 - for node := range self.nodes { - rc := node.recharging - node.update(time) - if rc && !node.recharging { - rce = true - } - if node.recharging { - sumWeight += node.rcWeight - } - rcSum += uint64(node.rcValue) - } - self.sumWeight = sumWeight - self.rcSumValue = rcSum - return -} - -func (self *ClientManager) update(time mclock.AbsTime) { - for { - firstTime := time - for node := range self.nodes { - if node.recharging && node.finishRecharge < firstTime { - firstTime = node.finishRecharge - } - } - if self.updateNodes(firstTime) { - for node := range self.nodes { - if node.recharging { - node.set(node.serving, self.simReqCnt, self.sumWeight) - } - } - } else { - self.time = time - return - } - } -} - -func (self *ClientManager) canStartReq() bool { - return self.simReqCnt < self.maxSimReq && self.rcSumValue < self.maxRcSum -} - -func (self *ClientManager) queueProc() { - for rc := range self.resumeQueue { - for { - time.Sleep(time.Millisecond * 10) - self.lock.Lock() - self.update(mclock.Now()) - cs := self.canStartReq() - self.lock.Unlock() - if cs { - break - } - } - close(rc) - } -} - -func (self *ClientManager) accept(node *cmNode, time mclock.AbsTime) bool { - self.lock.Lock() - defer self.lock.Unlock() - - self.update(time) - if !self.canStartReq() { - resume := make(chan bool) - self.lock.Unlock() - self.resumeQueue <- resume - <-resume - self.lock.Lock() - if _, ok := self.nodes[node]; !ok { - return false // reject if node has been removed or manager has been stopped - } - } - self.simReqCnt++ - node.set(true, self.simReqCnt, self.sumWeight) - node.startValue = node.rcValue - self.update(self.time) - return true -} - -func (self *ClientManager) stop(node *cmNode, time mclock.AbsTime) { - if node.serving { - self.update(time) - self.simReqCnt-- - node.set(false, self.simReqCnt, self.sumWeight) - self.update(time) - } -} - -func (self *ClientManager) processed(node *cmNode, time mclock.AbsTime) (rcValue, rcCost uint64) { - self.lock.Lock() - defer self.lock.Unlock() - - self.stop(node, time) - return uint64(node.rcValue), uint64(node.rcValue - node.startValue) -} diff --git a/les/freeclient.go b/les/freeclient.go deleted file mode 100644 index 54044a3caa..0000000000 --- a/les/freeclient.go +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright 2016 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 . - -// Package les implements the Light Ethereum Subprotocol. -package les - -import ( - "io" - "math" - "sync" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common/mclock" - "github.com/AlayaNetwork/Alaya-Go/common/prque" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -// freeClientPool implements a client database that limits the connection time -// of each client and manages accepting/rejecting incoming connections and even -// kicking out some connected clients. The pool calculates recent usage time -// for each known client (a value that increases linearly when the client is -// connected and decreases exponentially when not connected). Clients with lower -// recent usage are preferred, unknown nodes have the highest priority. Already -// connected nodes receive a small bias in their favor in order to avoid accepting -// and instantly kicking out clients. -// -// Note: the pool can use any string for client identification. Using signature -// keys for that purpose would not make sense when being known has a negative -// value for the client. Currently the LES protocol manager uses IP addresses -// (without port address) to identify clients. -type freeClientPool struct { - db ethdb.Database - lock sync.Mutex - clock mclock.Clock - closed bool - - connectedLimit, totalLimit int - - addressMap map[string]*freeClientPoolEntry - connPool, disconnPool *prque.Prque - startupTime mclock.AbsTime - logOffsetAtStartup int64 -} - -const ( - recentUsageExpTC = time.Hour // time constant of the exponential weighting window for "recent" server usage - fixedPointMultiplier = 0x1000000 // constant to convert logarithms to fixed point format - connectedBias = time.Minute // this bias is applied in favor of already connected clients in order to avoid kicking them out very soon -) - -// newFreeClientPool creates a new free client pool -func newFreeClientPool(db ethdb.Database, connectedLimit, totalLimit int, clock mclock.Clock) *freeClientPool { - pool := &freeClientPool{ - db: db, - clock: clock, - addressMap: make(map[string]*freeClientPoolEntry), - connPool: prque.New(poolSetIndex), - disconnPool: prque.New(poolSetIndex), - connectedLimit: connectedLimit, - totalLimit: totalLimit, - } - pool.loadFromDb() - return pool -} - -func (f *freeClientPool) stop() { - f.lock.Lock() - f.closed = true - f.saveToDb() - f.lock.Unlock() -} - -// connect should be called after a successful handshake. If the connection was -// rejected, there is no need to call disconnect. -// -// Note: the disconnectFn callback should not block. -func (f *freeClientPool) connect(address string, disconnectFn func()) bool { - f.lock.Lock() - defer f.lock.Unlock() - - if f.closed { - return false - } - e := f.addressMap[address] - now := f.clock.Now() - var recentUsage int64 - if e == nil { - e = &freeClientPoolEntry{address: address, index: -1} - f.addressMap[address] = e - } else { - if e.connected { - log.Debug("Client already connected", "address", address) - return false - } - recentUsage = int64(math.Exp(float64(e.logUsage-f.logOffset(now)) / fixedPointMultiplier)) - } - e.linUsage = recentUsage - int64(now) - // check whether (linUsage+connectedBias) is smaller than the highest entry in the connected pool - if f.connPool.Size() == f.connectedLimit { - i := f.connPool.PopItem().(*freeClientPoolEntry) - if e.linUsage+int64(connectedBias)-i.linUsage < 0 { - // kick it out and accept the new client - f.connPool.Remove(i.index) - f.calcLogUsage(i, now) - i.connected = false - f.disconnPool.Push(i, -i.logUsage) - log.Debug("Client kicked out", "address", i.address) - i.disconnectFn() - } else { - // keep the old client and reject the new one - f.connPool.Push(i, i.linUsage) - log.Debug("Client rejected", "address", address) - return false - } - } - f.disconnPool.Remove(e.index) - e.connected = true - e.disconnectFn = disconnectFn - f.connPool.Push(e, e.linUsage) - if f.connPool.Size()+f.disconnPool.Size() > f.totalLimit { - f.disconnPool.Pop() - } - log.Debug("Client accepted", "address", address) - return true -} - -// disconnect should be called when a connection is terminated. If the disconnection -// was initiated by the pool itself using disconnectFn then calling disconnect is -// not necessary but permitted. -func (f *freeClientPool) disconnect(address string) { - f.lock.Lock() - defer f.lock.Unlock() - - if f.closed { - return - } - e := f.addressMap[address] - now := f.clock.Now() - if !e.connected { - log.Debug("Client already disconnected", "address", address) - return - } - - f.connPool.Remove(e.index) - f.calcLogUsage(e, now) - e.connected = false - f.disconnPool.Push(e, -e.logUsage) - log.Debug("Client disconnected", "address", address) -} - -// logOffset calculates the time-dependent offset for the logarithmic -// representation of recent usage -func (f *freeClientPool) logOffset(now mclock.AbsTime) int64 { - // Note: fixedPointMultiplier acts as a multiplier here; the reason for dividing the divisor - // is to avoid int64 overflow. We assume that int64(recentUsageExpTC) >> fixedPointMultiplier. - logDecay := int64((time.Duration(now - f.startupTime)) / (recentUsageExpTC / fixedPointMultiplier)) - return f.logOffsetAtStartup + logDecay -} - -// calcLogUsage converts recent usage from linear to logarithmic representation -// when disconnecting a peer or closing the client pool -func (f *freeClientPool) calcLogUsage(e *freeClientPoolEntry, now mclock.AbsTime) { - dt := e.linUsage + int64(now) - if dt < 1 { - dt = 1 - } - e.logUsage = int64(math.Log(float64(dt))*fixedPointMultiplier) + f.logOffset(now) -} - -// freeClientPoolStorage is the RLP representation of the pool's database storage -type freeClientPoolStorage struct { - LogOffset uint64 - List []*freeClientPoolEntry -} - -// loadFromDb restores pool status from the database storage -// (automatically called at initialization) -func (f *freeClientPool) loadFromDb() { - enc, err := f.db.Get([]byte("freeClientPool")) - if err != nil { - return - } - var storage freeClientPoolStorage - err = rlp.DecodeBytes(enc, &storage) - if err != nil { - log.Error("Failed to decode client list", "err", err) - return - } - f.logOffsetAtStartup = int64(storage.LogOffset) - f.startupTime = f.clock.Now() - for _, e := range storage.List { - log.Debug("Loaded free client record", "address", e.address, "logUsage", e.logUsage) - f.addressMap[e.address] = e - f.disconnPool.Push(e, -e.logUsage) - } -} - -// saveToDb saves pool status to the database storage -// (automatically called during shutdown) -func (f *freeClientPool) saveToDb() { - now := f.clock.Now() - storage := freeClientPoolStorage{ - LogOffset: uint64(f.logOffset(now)), - List: make([]*freeClientPoolEntry, len(f.addressMap)), - } - i := 0 - for _, e := range f.addressMap { - if e.connected { - f.calcLogUsage(e, now) - } - storage.List[i] = e - i++ - } - enc, err := rlp.EncodeToBytes(storage) - if err != nil { - log.Error("Failed to encode client list", "err", err) - } else { - f.db.Put([]byte("freeClientPool"), enc) - } -} - -// freeClientPoolEntry represents a client address known by the pool. -// When connected, recent usage is calculated as linUsage + int64(clock.Now()) -// When disconnected, it is calculated as exp(logUsage - logOffset) where logOffset -// also grows linearly with time while the server is running. -// Conversion between linear and logarithmic representation happens when connecting -// or disconnecting the node. -// -// Note: linUsage and logUsage are values used with constantly growing offsets so -// even though they are close to each other at any time they may wrap around int64 -// limits over time. Comparison should be performed accordingly. -type freeClientPoolEntry struct { - address string - connected bool - disconnectFn func() - linUsage, logUsage int64 - index int -} - -func (e *freeClientPoolEntry) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, []interface{}{e.address, uint64(e.logUsage)}) -} - -func (e *freeClientPoolEntry) DecodeRLP(s *rlp.Stream) error { - var entry struct { - Address string - LogUsage uint64 - } - if err := s.Decode(&entry); err != nil { - return err - } - e.address = entry.Address - e.logUsage = int64(entry.LogUsage) - e.connected = false - e.index = -1 - return nil -} - -// poolSetIndex callback is used by both priority queues to set/update the index of -// the element in the queue. Index is needed to remove elements other than the top one. -func poolSetIndex(a interface{}, i int) { - a.(*freeClientPoolEntry).index = i -} diff --git a/les/freeclient_test.go b/les/freeclient_test.go deleted file mode 100644 index b4a454502d..0000000000 --- a/les/freeclient_test.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2017 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 . - -// Package light implements on-demand retrieval capable state and chain objects -// for the Ethereum Light Client. -package les - -import ( - "fmt" - "math/rand" - "testing" - "time" - - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - - "github.com/AlayaNetwork/Alaya-Go/common/mclock" -) - -func TestFreeClientPoolL10C100(t *testing.T) { - testFreeClientPool(t, 10, 100) -} - -func TestFreeClientPoolL40C200(t *testing.T) { - testFreeClientPool(t, 40, 200) -} - -func TestFreeClientPoolL100C300(t *testing.T) { - testFreeClientPool(t, 100, 300) -} - -const testFreeClientPoolTicks = 500000 - -func testFreeClientPool(t *testing.T, connLimit, clientCount int) { - var ( - clock mclock.Simulated - db = rawdb.NewMemoryDatabase() - pool = newFreeClientPool(db, connLimit, 10000, &clock) - connected = make([]bool, clientCount) - connTicks = make([]int, clientCount) - disconnCh = make(chan int, clientCount) - ) - peerId := func(i int) string { - return fmt.Sprintf("test peer #%d", i) - } - disconnFn := func(i int) func() { - return func() { - disconnCh <- i - } - } - - // pool should accept new peers up to its connected limit - for i := 0; i < connLimit; i++ { - if pool.connect(peerId(i), disconnFn(i)) { - connected[i] = true - } else { - t.Fatalf("Test peer #%d rejected", i) - } - } - // since all accepted peers are new and should not be kicked out, the next one should be rejected - if pool.connect(peerId(connLimit), disconnFn(connLimit)) { - connected[connLimit] = true - t.Fatalf("Peer accepted over connected limit") - } - - // randomly connect and disconnect peers, expect to have a similar total connection time at the end - for tickCounter := 0; tickCounter < testFreeClientPoolTicks; tickCounter++ { - clock.Run(1 * time.Second) - - i := rand.Intn(clientCount) - if connected[i] { - pool.disconnect(peerId(i)) - connected[i] = false - connTicks[i] += tickCounter - } else { - if pool.connect(peerId(i), disconnFn(i)) { - connected[i] = true - connTicks[i] -= tickCounter - } - } - pollDisconnects: - for { - select { - case i := <-disconnCh: - pool.disconnect(peerId(i)) - if connected[i] { - connTicks[i] += tickCounter - connected[i] = false - } - default: - break pollDisconnects - } - } - } - - expTicks := testFreeClientPoolTicks * connLimit / clientCount - expMin := expTicks - expTicks/10 - expMax := expTicks + expTicks/10 - - // check if the total connected time of peers are all in the expected range - for i, c := range connected { - if c { - connTicks[i] += testFreeClientPoolTicks - } - if connTicks[i] < expMin || connTicks[i] > expMax { - t.Errorf("Total connected time of test node #%d (%d) outside expected range (%d to %d)", i, connTicks[i], expMin, expMax) - } - } - - // a previously unknown peer should be accepted now - if !pool.connect("newPeer", func() {}) { - t.Fatalf("Previously unknown peer rejected") - } - - // close and restart pool - pool.stop() - pool = newFreeClientPool(db, connLimit, 10000, &clock) - - // try connecting all known peers (connLimit should be filled up) - for i := 0; i < clientCount; i++ { - pool.connect(peerId(i), func() {}) - } - // expect pool to remember known nodes and kick out one of them to accept a new one - if !pool.connect("newPeer2", func() {}) { - t.Errorf("Previously unknown peer rejected after restarting pool") - } - pool.stop() -} diff --git a/les/handler.go b/les/handler.go deleted file mode 100644 index b37f269b5b..0000000000 --- a/les/handler.go +++ /dev/null @@ -1,1249 +0,0 @@ -// Copyright 2016 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 . - -// Package les implements the Light Ethereum Subprotocol. -package les - -import ( - "encoding/binary" - "encoding/json" - "fmt" - "math/big" - "net" - "sync" - "time" - - "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/common/mclock" - "github.com/AlayaNetwork/Alaya-Go/consensus" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/state" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/eth/downloader" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/event" - "github.com/AlayaNetwork/Alaya-Go/light" - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discv5" - "github.com/AlayaNetwork/Alaya-Go/params" - "github.com/AlayaNetwork/Alaya-Go/rlp" - "github.com/AlayaNetwork/Alaya-Go/trie" -) - -const ( - softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data. - estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header - - ethVersion = 63 // equivalent eth version for the downloader - - MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request - MaxBodyFetch = 32 // Amount of block bodies to be fetched per retrieval request - MaxReceiptFetch = 128 // Amount of transaction receipts to allow fetching per request - MaxCodeFetch = 64 // Amount of contract codes to allow fetching per request - MaxProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request - MaxHelperTrieProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request - MaxTxSend = 64 // Amount of transactions to be send per request - MaxTxStatus = 256 // Amount of transactions to queried per request - - disableClientRemovePeer = false -) - -func errResp(code errCode, format string, v ...interface{}) error { - return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...)) -} - -type BlockChain interface { - Config() *params.ChainConfig - HasHeader(hash common.Hash, number uint64) bool - GetHeader(hash common.Hash, number uint64) *types.Header - GetHeaderByHash(hash common.Hash) *types.Header - CurrentHeader() *types.Header - State() (*state.StateDB, error) - InsertHeaderChain(chain []*types.Header, checkFreq int) (int, error) - Rollback(chain []common.Hash) - GetHeaderByNumber(number uint64) *types.Header - GetAncestor(hash common.Hash, number, ancestor uint64, maxNonCanonical *uint64) (common.Hash, uint64) - Genesis() *types.Block - SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription -} - -type txPool interface { - AddRemotes(txs []*types.Transaction) []error - Status(hashes []common.Hash) []core.TxStatus -} - -type ProtocolManager struct { - lightSync bool - txpool txPool - txrelay *LesTxRelay - networkId uint64 - chainConfig *params.ChainConfig - iConfig *light.IndexerConfig - blockchain BlockChain - chainDb ethdb.Database - odr *LesOdr - server *LesServer - serverPool *serverPool - clientPool *freeClientPool - lesTopic discv5.Topic - reqDist *requestDistributor - retriever *retrieveManager - - downloader *downloader.Downloader - fetcher *lightFetcher - peers *peerSet - maxPeers int - - eventMux *event.TypeMux - - // channels for fetcher, syncer, txsyncLoop - newPeerCh chan *peer - quitSync chan struct{} - noMorePeers chan struct{} - - // wait group is used for graceful shutdowns during downloading - // and processing - wg *sync.WaitGroup -} - -// NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable -// with the ethereum network. -func NewProtocolManager(chainConfig *params.ChainConfig, indexerConfig *light.IndexerConfig, lightSync bool, networkId uint64, mux *event.TypeMux, engine consensus.Engine, peers *peerSet, blockchain BlockChain, txpool txPool, chainDb ethdb.Database, odr *LesOdr, txrelay *LesTxRelay, serverPool *serverPool, quitSync chan struct{}, wg *sync.WaitGroup) (*ProtocolManager, error) { - // Create the protocol manager with the base fields - manager := &ProtocolManager{ - lightSync: lightSync, - eventMux: mux, - blockchain: blockchain, - chainConfig: chainConfig, - iConfig: indexerConfig, - chainDb: chainDb, - odr: odr, - networkId: networkId, - txpool: txpool, - txrelay: txrelay, - serverPool: serverPool, - peers: peers, - newPeerCh: make(chan *peer), - quitSync: quitSync, - wg: wg, - noMorePeers: make(chan struct{}), - } - if odr != nil { - manager.retriever = odr.retriever - manager.reqDist = odr.retriever.dist - } - - removePeer := manager.removePeer - if disableClientRemovePeer { - removePeer = func(id string) {} - } - - if lightSync { - manager.downloader = downloader.New(chainDb, snapshotdb.Instance(), nil, manager.eventMux, nil, blockchain, removePeer, nil) - manager.peers.notify((*downloaderPeerNotify)(manager)) - manager.fetcher = newLightFetcher(manager) - } - - return manager, nil -} - -// removePeer initiates disconnection from a peer by removing it from the peer set -func (pm *ProtocolManager) removePeer(id string) { - pm.peers.Unregister(id) -} - -func (pm *ProtocolManager) Start(maxPeers int) { - pm.maxPeers = maxPeers - - if pm.lightSync { - go pm.syncer() - } else { - pm.clientPool = newFreeClientPool(pm.chainDb, maxPeers, 10000, mclock.System{}) - go func() { - for range pm.newPeerCh { - } - }() - } -} - -func (pm *ProtocolManager) Stop() { - // Showing a log message. During download / process this could actually - // take between 5 to 10 seconds and therefor feedback is required. - log.Info("Stopping light Alaya-Go protocol") - - // Quit the sync loop. - // After this send has completed, no new peers will be accepted. - pm.noMorePeers <- struct{}{} - - close(pm.quitSync) // quits syncer, fetcher - if pm.clientPool != nil { - pm.clientPool.stop() - } - - // Disconnect existing sessions. - // This also closes the gate for any new registrations on the peer set. - // sessions which are already established but not added to pm.peers yet - // will exit when they try to register. - pm.peers.Close() - - // Wait for any process action - pm.wg.Wait() - - log.Info("Light Alaya-Go protocol stopped") -} - -// runPeer is the p2p protocol run function for the given version. -func (pm *ProtocolManager) runPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) error { - var entry *poolEntry - peer := pm.newPeer(int(version), pm.networkId, p, rw) - if pm.serverPool != nil { - entry = pm.serverPool.connect(peer, peer.Node()) - } - peer.poolEntry = entry - select { - case pm.newPeerCh <- peer: - pm.wg.Add(1) - defer pm.wg.Done() - err := pm.handle(peer) - if entry != nil { - pm.serverPool.disconnect(entry) - } - return err - case <-pm.quitSync: - if entry != nil { - pm.serverPool.disconnect(entry) - } - return p2p.DiscQuitting - } -} - -func (pm *ProtocolManager) newPeer(pv int, nv uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { - return newPeer(pv, nv, p, newMeteredMsgWriter(rw)) -} - -// handle is the callback invoked to manage the life cycle of a les peer. When -// this function terminates, the peer is disconnected. -func (pm *ProtocolManager) handle(p *peer) error { - // Ignore maxPeers if this is a trusted peer - // In server mode we try to check into the client pool after handshake - if pm.lightSync && pm.peers.Len() >= pm.maxPeers && !p.Peer.Info().Network.Trusted && !p.Peer.Info().Network.Consensus { - return p2p.DiscTooManyPeers - } - - p.Log().Debug("Light Alaya-Go peer connected", "name", p.Name()) - - // Execute the LES handshake - var ( - genesis = pm.blockchain.Genesis() - head = pm.blockchain.CurrentHeader() - hash = head.Hash() - number = head.Number.Uint64() - ) - if err := p.Handshake(hash, number, genesis.Hash(), pm.server); err != nil { - p.Log().Debug("Light Alaya-Go handshake failed", "err", err) - return err - } - - if !pm.lightSync && !p.Peer.Info().Network.Trusted { - addr, ok := p.RemoteAddr().(*net.TCPAddr) - // test peer address is not a tcp address, don't use client pool if can not typecast - if ok { - id := addr.IP.String() - if !pm.clientPool.connect(id, func() { go pm.removePeer(p.id) }) { - return p2p.DiscTooManyPeers - } - defer pm.clientPool.disconnect(id) - } - } - - if rw, ok := p.rw.(*meteredMsgReadWriter); ok { - rw.Init(p.version) - } - // Register the peer locally - if err := pm.peers.Register(p); err != nil { - p.Log().Error("Light Alaya-Go peer registration failed", "err", err) - return err - } - defer func() { - if pm.server != nil && pm.server.fcManager != nil && p.fcClient != nil { - p.fcClient.Remove(pm.server.fcManager) - } - pm.removePeer(p.id) - }() - // Register the peer in the downloader. If the downloader considers it banned, we disconnect - if pm.lightSync { - p.lock.Lock() - head := p.headInfo - p.lock.Unlock() - if pm.fetcher != nil { - pm.fetcher.announce(p, head) - } - - if p.poolEntry != nil { - pm.serverPool.registered(p.poolEntry) - } - } - - stop := make(chan struct{}) - defer close(stop) - go func() { - // new block announce loop - for { - select { - case announce := <-p.announceChn: - p.SendAnnounce(announce) - case <-stop: - return - } - } - }() - - // main loop. handle incoming messages. - for { - if err := pm.handleMsg(p); err != nil { - p.Log().Debug("Light Alaya-Go message handling failed", "err", err) - return err - } - } -} - -var reqList = []uint64{GetBlockHeadersMsg, GetBlockBodiesMsg, GetCodeMsg, GetReceiptsMsg, GetProofsV1Msg, SendTxMsg, SendTxV2Msg, GetTxStatusMsg, GetHeaderProofsMsg, GetProofsV2Msg, GetHelperTrieProofsMsg} - -// handleMsg is invoked whenever an inbound message is received from a remote -// peer. The remote connection is torn down upon returning any error. -func (pm *ProtocolManager) handleMsg(p *peer) error { - // Read the next message from the remote peer, and ensure it's fully consumed - msg, err := p.rw.ReadMsg() - if err != nil { - return err - } - p.Log().Trace("Light Alaya-Go message arrived", "code", msg.Code, "bytes", msg.Size) - - costs := p.fcCosts[msg.Code] - reject := func(reqCnt, maxCnt uint64) bool { - if p.fcClient == nil || reqCnt > maxCnt { - return true - } - bufValue, _ := p.fcClient.AcceptRequest() - cost := costs.baseCost + reqCnt*costs.reqCost - if cost > pm.server.defParams.BufLimit { - cost = pm.server.defParams.BufLimit - } - if cost > bufValue { - recharge := time.Duration((cost - bufValue) * 1000000 / pm.server.defParams.MinRecharge) - p.Log().Error("Request came too early", "recharge", common.PrettyDuration(recharge)) - return true - } - return false - } - - if msg.Size > ProtocolMaxMsgSize { - return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) - } - defer msg.Discard() - - var deliverMsg *Msg - - // Handle the message depending on its contents - switch msg.Code { - case StatusMsg: - p.Log().Trace("Received status message") - // Status messages should never arrive after the handshake - return errResp(ErrExtraStatusMsg, "uncontrolled status message") - - // Block header query, collect the requested headers and reply - case AnnounceMsg: - p.Log().Trace("Received announce message") - if p.requestAnnounceType == announceTypeNone { - return errResp(ErrUnexpectedResponse, "") - } - - var req announceData - if err := msg.Decode(&req); err != nil { - return errResp(ErrDecode, "%v: %v", msg, err) - } - - if p.requestAnnounceType == announceTypeSigned { - if err := req.checkSignature(p.ID()); err != nil { - p.Log().Trace("Invalid announcement signature", "err", err) - return err - } - p.Log().Trace("Valid announcement signature") - } - - p.Log().Trace("Announce message content", "number", req.Number, "hash", req.Hash, "reorg", req.ReorgDepth) - if pm.fetcher != nil { - pm.fetcher.announce(p, &req) - } - - case GetBlockHeadersMsg: - p.Log().Trace("Received block header request") - // Decode the complex header query - var req struct { - ReqID uint64 - Query getBlockHeadersData - } - if err := msg.Decode(&req); err != nil { - return errResp(ErrDecode, "%v: %v", msg, err) - } - - query := req.Query - if reject(query.Amount, MaxHeaderFetch) { - return errResp(ErrRequestRejected, "") - } - - hashMode := query.Origin.Hash != (common.Hash{}) - first := true - maxNonCanonical := uint64(100) - - // Gather headers until the fetch or network limits is reached - var ( - bytes common.StorageSize - headers []*types.Header - unknown bool - ) - for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit { - // Retrieve the next header satisfying the query - var origin *types.Header - if hashMode { - if first { - first = false - origin = pm.blockchain.GetHeaderByHash(query.Origin.Hash) - if origin != nil { - query.Origin.Number = origin.Number.Uint64() - } - } else { - origin = pm.blockchain.GetHeader(query.Origin.Hash, query.Origin.Number) - } - } else { - origin = pm.blockchain.GetHeaderByNumber(query.Origin.Number) - } - if origin == nil { - break - } - headers = append(headers, origin) - bytes += estHeaderRlpSize - - // Advance to the next header of the query - switch { - case hashMode && query.Reverse: - // Hash based traversal towards the genesis block - ancestor := query.Skip + 1 - if ancestor == 0 { - unknown = true - } else { - query.Origin.Hash, query.Origin.Number = pm.blockchain.GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical) - unknown = (query.Origin.Hash == common.Hash{}) - } - case hashMode && !query.Reverse: - // Hash based traversal towards the leaf block - var ( - current = origin.Number.Uint64() - next = current + query.Skip + 1 - ) - if next <= current { - infos, _ := json.MarshalIndent(p.Peer.Info(), "", " ") - p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos) - unknown = true - } else { - if header := pm.blockchain.GetHeaderByNumber(next); header != nil { - nextHash := header.Hash() - expOldHash, _ := pm.blockchain.GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical) - if expOldHash == query.Origin.Hash { - query.Origin.Hash, query.Origin.Number = nextHash, next - } else { - unknown = true - } - } else { - unknown = true - } - } - case query.Reverse: - // Number based traversal towards the genesis block - if query.Origin.Number >= query.Skip+1 { - query.Origin.Number -= query.Skip + 1 - } else { - unknown = true - } - - case !query.Reverse: - // Number based traversal towards the leaf block - query.Origin.Number += query.Skip + 1 - } - } - - bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + query.Amount*costs.reqCost) - pm.server.fcCostStats.update(msg.Code, query.Amount, rcost) - return p.SendBlockHeaders(req.ReqID, bv, headers) - - case BlockHeadersMsg: - if pm.downloader == nil { - return errResp(ErrUnexpectedResponse, "") - } - - p.Log().Trace("Received block header response message") - // A batch of headers arrived to one of our previous requests - var resp struct { - ReqID, BV uint64 - Headers []*types.Header - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - p.fcServer.GotReply(resp.ReqID, resp.BV) - if pm.fetcher != nil && pm.fetcher.requestedID(resp.ReqID) { - pm.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers) - } else { - err := pm.downloader.DeliverHeaders(p.id, resp.Headers) - if err != nil { - log.Debug(fmt.Sprint(err)) - } - } - - case GetBlockBodiesMsg: - p.Log().Trace("Received block bodies request") - // Decode the retrieval message - var req struct { - ReqID uint64 - Hashes []common.Hash - } - if err := msg.Decode(&req); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Gather blocks until the fetch or network limits is reached - var ( - bytes int - bodies []rlp.RawValue - ) - reqCnt := len(req.Hashes) - if reject(uint64(reqCnt), MaxBodyFetch) { - return errResp(ErrRequestRejected, "") - } - for _, hash := range req.Hashes { - if bytes >= softResponseLimit { - break - } - // Retrieve the requested block body, stopping if enough was found - if number := rawdb.ReadHeaderNumber(pm.chainDb, hash); number != nil { - if data := rawdb.ReadBodyRLP(pm.chainDb, hash, *number); len(data) != 0 { - bodies = append(bodies, data) - bytes += len(data) - } - } - } - bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) - pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost) - return p.SendBlockBodiesRLP(req.ReqID, bv, bodies) - - case BlockBodiesMsg: - if pm.odr == nil { - return errResp(ErrUnexpectedResponse, "") - } - - p.Log().Trace("Received block bodies response") - // A batch of block bodies arrived to one of our previous requests - var resp struct { - ReqID, BV uint64 - Data []*types.Body - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - p.fcServer.GotReply(resp.ReqID, resp.BV) - deliverMsg = &Msg{ - MsgType: MsgBlockBodies, - ReqID: resp.ReqID, - Obj: resp.Data, - } - - case GetCodeMsg: - p.Log().Trace("Received code request") - // Decode the retrieval message - var req struct { - ReqID uint64 - Reqs []CodeReq - } - if err := msg.Decode(&req); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Gather state data until the fetch or network limits is reached - var ( - bytes int - data [][]byte - ) - reqCnt := len(req.Reqs) - if reject(uint64(reqCnt), MaxCodeFetch) { - return errResp(ErrRequestRejected, "") - } - for _, req := range req.Reqs { - // Retrieve the requested state entry, stopping if enough was found - if number := rawdb.ReadHeaderNumber(pm.chainDb, req.BHash); number != nil { - if header := rawdb.ReadHeader(pm.chainDb, req.BHash, *number); header != nil { - statedb, err := pm.blockchain.State() - if err != nil { - continue - } - account, err := pm.getAccount(statedb, header.Root, common.BytesToHash(req.AccKey)) - if err != nil { - continue - } - code, _ := statedb.Database().TrieDB().Node(common.BytesToHash(account.CodeHash)) - - data = append(data, code) - if bytes += len(code); bytes >= softResponseLimit { - break - } - } - } - } - bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) - pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost) - return p.SendCode(req.ReqID, bv, data) - - case CodeMsg: - if pm.odr == nil { - return errResp(ErrUnexpectedResponse, "") - } - - p.Log().Trace("Received code response") - // A batch of node state data arrived to one of our previous requests - var resp struct { - ReqID, BV uint64 - Data [][]byte - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - p.fcServer.GotReply(resp.ReqID, resp.BV) - deliverMsg = &Msg{ - MsgType: MsgCode, - ReqID: resp.ReqID, - Obj: resp.Data, - } - - case GetReceiptsMsg: - p.Log().Trace("Received receipts request") - // Decode the retrieval message - var req struct { - ReqID uint64 - Hashes []common.Hash - } - if err := msg.Decode(&req); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Gather state data until the fetch or network limits is reached - var ( - bytes int - receipts []rlp.RawValue - ) - reqCnt := len(req.Hashes) - if reject(uint64(reqCnt), MaxReceiptFetch) { - return errResp(ErrRequestRejected, "") - } - for _, hash := range req.Hashes { - if bytes >= softResponseLimit { - break - } - // Retrieve the requested block's receipts, skipping if unknown to us - var results types.Receipts - if number := rawdb.ReadHeaderNumber(pm.chainDb, hash); number != nil { - results = rawdb.ReadRawReceipts(pm.chainDb, hash, *number) - } - if results == nil { - if header := pm.blockchain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { - continue - } - } - // If known, encode and queue for response packet - if encoded, err := rlp.EncodeToBytes(results); err != nil { - log.Error("Failed to encode receipt", "err", err) - } else { - receipts = append(receipts, encoded) - bytes += len(encoded) - } - } - bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) - pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost) - return p.SendReceiptsRLP(req.ReqID, bv, receipts) - - case ReceiptsMsg: - if pm.odr == nil { - return errResp(ErrUnexpectedResponse, "") - } - - p.Log().Trace("Received receipts response") - // A batch of receipts arrived to one of our previous requests - var resp struct { - ReqID, BV uint64 - Receipts []types.Receipts - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - p.fcServer.GotReply(resp.ReqID, resp.BV) - deliverMsg = &Msg{ - MsgType: MsgReceipts, - ReqID: resp.ReqID, - Obj: resp.Receipts, - } - - case GetProofsV1Msg: - p.Log().Trace("Received proofs request") - // Decode the retrieval message - var req struct { - ReqID uint64 - Reqs []ProofReq - } - if err := msg.Decode(&req); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Gather state data until the fetch or network limits is reached - var ( - bytes int - proofs proofsData - ) - reqCnt := len(req.Reqs) - if reject(uint64(reqCnt), MaxProofsFetch) { - return errResp(ErrRequestRejected, "") - } - for _, req := range req.Reqs { - // Retrieve the requested state entry, stopping if enough was found - if number := rawdb.ReadHeaderNumber(pm.chainDb, req.BHash); number != nil { - if header := rawdb.ReadHeader(pm.chainDb, req.BHash, *number); header != nil { - statedb, err := pm.blockchain.State() - if err != nil { - continue - } - var trie state.Trie - if len(req.AccKey) > 0 { - account, err := pm.getAccount(statedb, header.Root, common.BytesToHash(req.AccKey)) - if err != nil { - continue - } - trie, _ = statedb.Database().OpenStorageTrie(common.BytesToHash(req.AccKey), account.Root) - } else { - trie, _ = statedb.Database().OpenTrie(header.Root) - } - if trie != nil { - var proof light.NodeList - trie.Prove(req.Key, 0, &proof) - - proofs = append(proofs, proof) - if bytes += proof.DataSize(); bytes >= softResponseLimit { - break - } - } - } - } - } - bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) - pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost) - return p.SendProofs(req.ReqID, bv, proofs) - - case GetProofsV2Msg: - p.Log().Trace("Received les/2 proofs request") - // Decode the retrieval message - var req struct { - ReqID uint64 - Reqs []ProofReq - } - if err := msg.Decode(&req); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Gather state data until the fetch or network limits is reached - var ( - lastBHash common.Hash - statedb *state.StateDB - root common.Hash - ) - reqCnt := len(req.Reqs) - if reject(uint64(reqCnt), MaxProofsFetch) { - return errResp(ErrRequestRejected, "") - } - - nodes := light.NewNodeSet() - - for _, req := range req.Reqs { - // Look up the state belonging to the request - if statedb == nil || req.BHash != lastBHash { - statedb, root, lastBHash = nil, common.Hash{}, req.BHash - - if number := rawdb.ReadHeaderNumber(pm.chainDb, req.BHash); number != nil { - if header := rawdb.ReadHeader(pm.chainDb, req.BHash, *number); header != nil { - statedb, _ = pm.blockchain.State() - root = header.Root - } - } - } - if statedb == nil { - continue - } - // Pull the account or storage trie of the request - var trie state.Trie - if len(req.AccKey) > 0 { - account, err := pm.getAccount(statedb, root, common.BytesToHash(req.AccKey)) - if err != nil { - continue - } - trie, _ = statedb.Database().OpenStorageTrie(common.BytesToHash(req.AccKey), account.Root) - } else { - trie, _ = statedb.Database().OpenTrie(root) - } - if trie == nil { - continue - } - // Prove the user's request from the account or stroage trie - trie.Prove(req.Key, req.FromLevel, nodes) - if nodes.DataSize() >= softResponseLimit { - break - } - } - bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) - pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost) - return p.SendProofsV2(req.ReqID, bv, nodes.NodeList()) - - case ProofsV1Msg: - if pm.odr == nil { - return errResp(ErrUnexpectedResponse, "") - } - - p.Log().Trace("Received proofs response") - // A batch of merkle proofs arrived to one of our previous requests - var resp struct { - ReqID, BV uint64 - Data []light.NodeList - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - p.fcServer.GotReply(resp.ReqID, resp.BV) - deliverMsg = &Msg{ - MsgType: MsgProofsV1, - ReqID: resp.ReqID, - Obj: resp.Data, - } - - case ProofsV2Msg: - if pm.odr == nil { - return errResp(ErrUnexpectedResponse, "") - } - - p.Log().Trace("Received les/2 proofs response") - // A batch of merkle proofs arrived to one of our previous requests - var resp struct { - ReqID, BV uint64 - Data light.NodeList - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - p.fcServer.GotReply(resp.ReqID, resp.BV) - deliverMsg = &Msg{ - MsgType: MsgProofsV2, - ReqID: resp.ReqID, - Obj: resp.Data, - } - - case GetHeaderProofsMsg: - p.Log().Trace("Received headers proof request") - // Decode the retrieval message - var req struct { - ReqID uint64 - Reqs []ChtReq - } - if err := msg.Decode(&req); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Gather state data until the fetch or network limits is reached - var ( - bytes int - proofs []ChtResp - ) - reqCnt := len(req.Reqs) - if reject(uint64(reqCnt), MaxHelperTrieProofsFetch) { - return errResp(ErrRequestRejected, "") - } - trieDb := trie.NewDatabase(rawdb.NewTable(pm.chainDb, light.ChtTablePrefix)) - for _, req := range req.Reqs { - if header := pm.blockchain.GetHeaderByNumber(req.BlockNum); header != nil { - sectionHead := rawdb.ReadCanonicalHash(pm.chainDb, req.ChtNum*pm.iConfig.ChtSize-1) - if root := light.GetChtRoot(pm.chainDb, req.ChtNum-1, sectionHead); root != (common.Hash{}) { - trie, err := trie.New(root, trieDb) - if err != nil { - continue - } - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], req.BlockNum) - - var proof light.NodeList - trie.Prove(encNumber[:], 0, &proof) - - proofs = append(proofs, ChtResp{Header: header, Proof: proof}) - if bytes += proof.DataSize() + estHeaderRlpSize; bytes >= softResponseLimit { - break - } - } - } - } - bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) - pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost) - return p.SendHeaderProofs(req.ReqID, bv, proofs) - - case GetHelperTrieProofsMsg: - p.Log().Trace("Received helper trie proof request") - // Decode the retrieval message - var req struct { - ReqID uint64 - Reqs []HelperTrieReq - } - if err := msg.Decode(&req); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Gather state data until the fetch or network limits is reached - var ( - auxBytes int - auxData [][]byte - ) - reqCnt := len(req.Reqs) - if reject(uint64(reqCnt), MaxHelperTrieProofsFetch) { - return errResp(ErrRequestRejected, "") - } - - var ( - lastIdx uint64 - lastType uint - root common.Hash - auxTrie *trie.Trie - ) - nodes := light.NewNodeSet() - for _, req := range req.Reqs { - if auxTrie == nil || req.Type != lastType || req.TrieIdx != lastIdx { - auxTrie, lastType, lastIdx = nil, req.Type, req.TrieIdx - - var prefix string - if root, prefix = pm.getHelperTrie(req.Type, req.TrieIdx); root != (common.Hash{}) { - auxTrie, _ = trie.New(root, trie.NewDatabase(rawdb.NewTable(pm.chainDb, prefix))) - } - } - if req.AuxReq == auxRoot { - var data []byte - if root != (common.Hash{}) { - data = root[:] - } - auxData = append(auxData, data) - auxBytes += len(data) - } else { - if auxTrie != nil { - auxTrie.Prove(req.Key, req.FromLevel, nodes) - } - if req.AuxReq != 0 { - data := pm.getHelperTrieAuxData(req) - auxData = append(auxData, data) - auxBytes += len(data) - } - } - if nodes.DataSize()+auxBytes >= softResponseLimit { - break - } - } - bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) - pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost) - return p.SendHelperTrieProofs(req.ReqID, bv, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData}) - - case HeaderProofsMsg: - if pm.odr == nil { - return errResp(ErrUnexpectedResponse, "") - } - - p.Log().Trace("Received headers proof response") - var resp struct { - ReqID, BV uint64 - Data []ChtResp - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - p.fcServer.GotReply(resp.ReqID, resp.BV) - deliverMsg = &Msg{ - MsgType: MsgHeaderProofs, - ReqID: resp.ReqID, - Obj: resp.Data, - } - - case HelperTrieProofsMsg: - if pm.odr == nil { - return errResp(ErrUnexpectedResponse, "") - } - - p.Log().Trace("Received helper trie proof response") - var resp struct { - ReqID, BV uint64 - Data HelperTrieResps - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - - p.fcServer.GotReply(resp.ReqID, resp.BV) - deliverMsg = &Msg{ - MsgType: MsgHelperTrieProofs, - ReqID: resp.ReqID, - Obj: resp.Data, - } - - case SendTxMsg: - if pm.txpool == nil { - return errResp(ErrRequestRejected, "") - } - // Transactions arrived, parse all of them and deliver to the pool - var txs []*types.Transaction - if err := msg.Decode(&txs); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - reqCnt := len(txs) - if reject(uint64(reqCnt), MaxTxSend) { - return errResp(ErrRequestRejected, "") - } - pm.txpool.AddRemotes(txs) - - _, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) - pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost) - - case SendTxV2Msg: - if pm.txpool == nil { - return errResp(ErrRequestRejected, "") - } - // Transactions arrived, parse all of them and deliver to the pool - var req struct { - ReqID uint64 - Txs []*types.Transaction - } - if err := msg.Decode(&req); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - reqCnt := len(req.Txs) - if reject(uint64(reqCnt), MaxTxSend) { - return errResp(ErrRequestRejected, "") - } - - hashes := make([]common.Hash, len(req.Txs)) - for i, tx := range req.Txs { - hashes[i] = tx.Hash() - } - stats := pm.txStatus(hashes) - for i, stat := range stats { - if stat.Status == core.TxStatusUnknown { - if errs := pm.txpool.AddRemotes([]*types.Transaction{req.Txs[i]}); errs[0] != nil { - stats[i].Error = errs[0].Error() - continue - } - stats[i] = pm.txStatus([]common.Hash{hashes[i]})[0] - } - } - - bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) - pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost) - - return p.SendTxStatus(req.ReqID, bv, stats) - - case GetTxStatusMsg: - if pm.txpool == nil { - return errResp(ErrUnexpectedResponse, "") - } - // Transactions arrived, parse all of them and deliver to the pool - var req struct { - ReqID uint64 - Hashes []common.Hash - } - if err := msg.Decode(&req); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - reqCnt := len(req.Hashes) - if reject(uint64(reqCnt), MaxTxStatus) { - return errResp(ErrRequestRejected, "") - } - bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) - pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost) - - return p.SendTxStatus(req.ReqID, bv, pm.txStatus(req.Hashes)) - - case TxStatusMsg: - if pm.odr == nil { - return errResp(ErrUnexpectedResponse, "") - } - - p.Log().Trace("Received tx status response") - var resp struct { - ReqID, BV uint64 - Status []txStatus - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - - p.fcServer.GotReply(resp.ReqID, resp.BV) - - default: - p.Log().Trace("Received unknown message", "code", msg.Code) - return errResp(ErrInvalidMsgCode, "%v", msg.Code) - } - - if deliverMsg != nil { - err := pm.retriever.deliver(p, deliverMsg) - if err != nil { - p.responseErrors++ - if p.responseErrors > maxResponseErrors { - return err - } - } - } - return nil -} - -// getAccount retrieves an account from the state based at root. -func (pm *ProtocolManager) getAccount(statedb *state.StateDB, root, hash common.Hash) (state.Account, error) { - trie, err := trie.New(root, statedb.Database().TrieDB()) - if err != nil { - return state.Account{}, err - } - blob, err := trie.TryGet(hash[:]) - if err != nil { - return state.Account{}, err - } - var account state.Account - if err = rlp.DecodeBytes(blob, &account); err != nil { - return state.Account{}, err - } - return account, nil -} - -// getHelperTrie returns the post-processed trie root for the given trie ID and section index -func (pm *ProtocolManager) getHelperTrie(id uint, idx uint64) (common.Hash, string) { - switch id { - case htCanonical: - sectionHead := rawdb.ReadCanonicalHash(pm.chainDb, (idx+1)*pm.iConfig.ChtSize-1) - return light.GetChtRoot(pm.chainDb, idx, sectionHead), light.ChtTablePrefix - case htBloomBits: - sectionHead := rawdb.ReadCanonicalHash(pm.chainDb, (idx+1)*pm.iConfig.BloomTrieSize-1) - return light.GetBloomTrieRoot(pm.chainDb, idx, sectionHead), light.BloomTrieTablePrefix - } - return common.Hash{}, "" -} - -// getHelperTrieAuxData returns requested auxiliary data for the given HelperTrie request -func (pm *ProtocolManager) getHelperTrieAuxData(req HelperTrieReq) []byte { - if req.Type == htCanonical && req.AuxReq == auxHeader && len(req.Key) == 8 { - blockNum := binary.BigEndian.Uint64(req.Key) - hash := rawdb.ReadCanonicalHash(pm.chainDb, blockNum) - return rawdb.ReadHeaderRLP(pm.chainDb, hash, blockNum) - } - return nil -} - -func (pm *ProtocolManager) txStatus(hashes []common.Hash) []txStatus { - stats := make([]txStatus, len(hashes)) - for i, stat := range pm.txpool.Status(hashes) { - // Save the status we've got from the transaction pool - stats[i].Status = stat - - // If the transaction is unknown to the pool, try looking it up locally - if stat == core.TxStatusUnknown { - if tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(pm.chainDb, hashes[i]); tx != nil { - stats[i].Status = core.TxStatusIncluded - stats[i].Lookup = &rawdb.LegacyTxLookupEntry{BlockHash: blockHash, BlockIndex: blockNumber, Index: txIndex} - } - } - } - return stats -} - -// downloaderPeerNotify implements peerSetNotify -type downloaderPeerNotify ProtocolManager - -type peerConnection struct { - manager *ProtocolManager - peer *peer -} - -func (pc *peerConnection) Head() (common.Hash, *big.Int) { - return pc.peer.HeadAndNum() -} - -func (pc *peerConnection) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error { - reqID := genReqID() - rq := &distReq{ - getCost: func(dp distPeer) uint64 { - peer := dp.(*peer) - return peer.GetRequestCost(GetBlockHeadersMsg, amount) - }, - canSend: func(dp distPeer) bool { - return dp.(*peer) == pc.peer - }, - request: func(dp distPeer) func() { - peer := dp.(*peer) - cost := peer.GetRequestCost(GetBlockHeadersMsg, amount) - peer.fcServer.QueueRequest(reqID, cost) - return func() { peer.RequestHeadersByHash(reqID, cost, origin, amount, skip, reverse) } - }, - } - _, ok := <-pc.manager.reqDist.queue(rq) - if !ok { - return light.ErrNoPeers - } - return nil -} - -func (pc *peerConnection) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error { - reqID := genReqID() - rq := &distReq{ - getCost: func(dp distPeer) uint64 { - peer := dp.(*peer) - return peer.GetRequestCost(GetBlockHeadersMsg, amount) - }, - canSend: func(dp distPeer) bool { - return dp.(*peer) == pc.peer - }, - request: func(dp distPeer) func() { - peer := dp.(*peer) - cost := peer.GetRequestCost(GetBlockHeadersMsg, amount) - peer.fcServer.QueueRequest(reqID, cost) - return func() { peer.RequestHeadersByNumber(reqID, cost, origin, amount, skip, reverse) } - }, - } - _, ok := <-pc.manager.reqDist.queue(rq) - if !ok { - return light.ErrNoPeers - } - return nil -} - -func (d *downloaderPeerNotify) registerPeer(p *peer) { - pm := (*ProtocolManager)(d) - pc := &peerConnection{ - manager: pm, - peer: p, - } - pm.downloader.RegisterLightPeer(p.id, ethVersion, pc) -} - -func (d *downloaderPeerNotify) unregisterPeer(p *peer) { - pm := (*ProtocolManager)(d) - pm.downloader.UnregisterPeer(p.id) -} diff --git a/les/handler_test.go b/les/handler_test.go deleted file mode 100644 index 9bbd0e8c65..0000000000 --- a/les/handler_test.go +++ /dev/null @@ -1,580 +0,0 @@ -// Copyright 2016 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 . - -package les - -import ( - "encoding/binary" - "math/rand" - "testing" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/eth/downloader" - "github.com/AlayaNetwork/Alaya-Go/light" - "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/rlp" - "github.com/AlayaNetwork/Alaya-Go/trie" -) - -func expectResponse(r p2p.MsgReader, msgcode, reqID, bv uint64, data interface{}) error { - type resp struct { - ReqID, BV uint64 - Data interface{} - } - return p2p.ExpectMsg(r, msgcode, resp{reqID, bv, data}) -} - -// Tests that block headers can be retrieved from a remote chain based on user queries. -//func TestGetBlockHeadersLes1(t *testing.T) { testGetBlockHeaders(t, 1) } -//func TestGetBlockHeadersLes2(t *testing.T) { testGetBlockHeaders(t, 2) } - -func testGetBlockHeaders(t *testing.T, protocol int) { - server, tearDown := newServerEnv(t, downloader.MaxHashFetch+15, protocol, nil) - defer tearDown() - bc := server.pm.blockchain.(*core.BlockChain) - - // Create a "random" unknown hash for testing - var unknown common.Hash - for i := range unknown { - unknown[i] = byte(i) - } - - // Create a batch of tests for various scenarios - limit := uint64(MaxHeaderFetch) - tests := []struct { - query *getBlockHeadersData // The query to execute for header retrieval - expect []common.Hash // The hashes of the block whose headers are expected - }{ - - // A single random block should be retrievable by hash and number too - { - &getBlockHeadersData{Origin: hashOrNumber{Hash: bc.GetBlockByNumber(limit / 2).Hash()}, Amount: 1}, - []common.Hash{bc.GetBlockByNumber(limit / 2).Hash()}, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 1}, - []common.Hash{bc.GetBlockByNumber(limit / 2).Hash()}, - }, - // Multiple headers should be retrievable in both directions - { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3}, - []common.Hash{ - bc.GetBlockByNumber(limit / 2).Hash(), - bc.GetBlockByNumber(limit/2 + 1).Hash(), - bc.GetBlockByNumber(limit/2 + 2).Hash(), - }, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true}, - []common.Hash{ - bc.GetBlockByNumber(limit / 2).Hash(), - bc.GetBlockByNumber(limit/2 - 1).Hash(), - bc.GetBlockByNumber(limit/2 - 2).Hash(), - }, - }, - // Multiple headers with skip lists should be retrievable - { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3}, - []common.Hash{ - bc.GetBlockByNumber(limit / 2).Hash(), - bc.GetBlockByNumber(limit/2 + 4).Hash(), - bc.GetBlockByNumber(limit/2 + 8).Hash(), - }, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true}, - []common.Hash{ - bc.GetBlockByNumber(limit / 2).Hash(), - bc.GetBlockByNumber(limit/2 - 4).Hash(), - bc.GetBlockByNumber(limit/2 - 8).Hash(), - }, - }, - // The chain endpoints should be retrievable - { - &getBlockHeadersData{Origin: hashOrNumber{Number: 0}, Amount: 1}, - []common.Hash{bc.GetBlockByNumber(0).Hash()}, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64()}, Amount: 1}, - []common.Hash{bc.CurrentBlock().Hash()}, - }, - - // Check that requesting more than available is handled gracefully - { - &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3}, - []common.Hash{ - bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 4).Hash(), - bc.GetBlockByNumber(bc.CurrentBlock().NumberU64()).Hash(), - }, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true}, - []common.Hash{ - bc.GetBlockByNumber(4).Hash(), - bc.GetBlockByNumber(0).Hash(), - }, - }, - // Check that requesting more than available is handled gracefully, even if mid skip - { - &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3}, - []common.Hash{ - bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 4).Hash(), - bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1).Hash(), - }, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true}, - []common.Hash{ - bc.GetBlockByNumber(4).Hash(), - bc.GetBlockByNumber(1).Hash(), - }, - }, - // Check that non existing headers aren't returned - { - &getBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1}, - []common.Hash{}, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() + 1}, Amount: 1}, - []common.Hash{}, - }, - } - // Run each of the tests and verify the results against the chain - var reqID uint64 - for i, tt := range tests { - // Collect the headers to expect in the response - headers := []*types.Header{} - for _, hash := range tt.expect { - headers = append(headers, bc.GetHeaderByHash(hash)) - } - // Send the hash request and verify the response - reqID++ - cost := server.tPeer.GetRequestCost(GetBlockHeadersMsg, int(tt.query.Amount)) - sendRequest(server.tPeer.app, GetBlockHeadersMsg, reqID, cost, tt.query) - if err := expectResponse(server.tPeer.app, BlockHeadersMsg, reqID, testBufLimit, headers); err != nil { - t.Errorf("test %d: headers mismatch: %v", i, err) - } - } -} - -// Tests that block contents can be retrieved from a remote chain based on their hashes. -//func TestGetBlockBodiesLes1(t *testing.T) { testGetBlockBodies(t, 1) } -//func TestGetBlockBodiesLes2(t *testing.T) { testGetBlockBodies(t, 2) } - -func testGetBlockBodies(t *testing.T, protocol int) { - server, tearDown := newServerEnv(t, downloader.MaxBlockFetch+15, protocol, nil) - defer tearDown() - bc := server.pm.blockchain.(*core.BlockChain) - - // Create a batch of tests for various scenarios - limit := MaxBodyFetch - tests := []struct { - random int // Number of blocks to fetch randomly from the chain - explicit []common.Hash // Explicitly requested blocks - available []bool // Availability of explicitly requested blocks - expected int // Total number of existing blocks to expect - }{ - {1, nil, nil, 1}, // A single random block should be retrievable - {10, nil, nil, 10}, // Multiple random blocks should be retrievable - {limit, nil, nil, limit}, // The maximum possible blocks should be retrievable - //{limit + 1, nil, nil, limit}, // No more than the possible block count should be returned - {0, []common.Hash{bc.Genesis().Hash()}, []bool{true}, 1}, // The genesis block should be retrievable - {0, []common.Hash{bc.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable - {0, []common.Hash{{}}, []bool{false}, 0}, // A non existent block should not be returned - - // Existing and non-existing blocks interleaved should not cause problems - {0, []common.Hash{ - {}, - bc.GetBlockByNumber(1).Hash(), - {}, - bc.GetBlockByNumber(10).Hash(), - {}, - bc.GetBlockByNumber(100).Hash(), - {}, - }, []bool{false, true, false, true, false, true, false}, 3}, - } - // Run each of the tests and verify the results against the chain - var reqID uint64 - for i, tt := range tests { - // Collect the hashes to request, and the response to expect - hashes, seen := []common.Hash{}, make(map[int64]bool) - bodies := []*types.Body{} - - for j := 0; j < tt.random; j++ { - for { - num := rand.Int63n(int64(bc.CurrentBlock().NumberU64())) - if !seen[num] { - seen[num] = true - - block := bc.GetBlockByNumber(uint64(num)) - hashes = append(hashes, block.Hash()) - if len(bodies) < tt.expected { - bodies = append(bodies, &types.Body{Transactions: block.Transactions()}) - } - break - } - } - } - for j, hash := range tt.explicit { - hashes = append(hashes, hash) - if tt.available[j] && len(bodies) < tt.expected { - block := bc.GetBlockByHash(hash) - bodies = append(bodies, &types.Body{Transactions: block.Transactions()}) - } - } - reqID++ - // Send the hash request and verify the response - cost := server.tPeer.GetRequestCost(GetBlockBodiesMsg, len(hashes)) - sendRequest(server.tPeer.app, GetBlockBodiesMsg, reqID, cost, hashes) - if err := expectResponse(server.tPeer.app, BlockBodiesMsg, reqID, testBufLimit, bodies); err != nil { - t.Errorf("test %d: bodies mismatch: %v", i, err) - } - } -} - -// Tests that the contract codes can be retrieved based on account addresses. -//func TestGetCodeLes1(t *testing.T) { testGetCode(t, 1) } -//func TestGetCodeLes2(t *testing.T) { testGetCode(t, 2) } - -func testGetCode(t *testing.T, protocol int) { - // Assemble the test environment - server, tearDown := newServerEnv(t, 4, protocol, nil) - defer tearDown() - bc := server.pm.blockchain.(*core.BlockChain) - - var codereqs []*CodeReq - var codes [][]byte - - for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ { - header := bc.GetHeaderByNumber(i) - req := &CodeReq{ - BHash: header.Hash(), - AccKey: crypto.Keccak256(testContractAddr[:]), - } - codereqs = append(codereqs, req) - if i >= testContractDeployed { - codes = append(codes, testContractCodeDeployed) - } - } - - cost := server.tPeer.GetRequestCost(GetCodeMsg, len(codereqs)) - sendRequest(server.tPeer.app, GetCodeMsg, 42, cost, codereqs) - if err := expectResponse(server.tPeer.app, CodeMsg, 42, testBufLimit, codes); err != nil { - t.Errorf("codes mismatch: %v", err) - } -} - -// Tests that the transaction receipts can be retrieved based on hashes. -func TestGetReceiptLes1(t *testing.T) { testGetReceipt(t, 1) } -func TestGetReceiptLes2(t *testing.T) { testGetReceipt(t, 2) } - -func testGetReceipt(t *testing.T, protocol int) { - // Assemble the test environment - server, tearDown := newServerEnv(t, 4, protocol, nil) - defer tearDown() - bc := server.pm.blockchain.(*core.BlockChain) - - // Collect the hashes to request, and the response to expect - hashes, receipts := []common.Hash{}, []types.Receipts{} - for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ { - block := bc.GetBlockByNumber(i) - - hashes = append(hashes, block.Hash()) - receipts = append(receipts, rawdb.ReadRawReceipts(server.db, block.Hash(), block.NumberU64())) - } - // Send the hash request and verify the response - cost := server.tPeer.GetRequestCost(GetReceiptsMsg, len(hashes)) - sendRequest(server.tPeer.app, GetReceiptsMsg, 42, cost, hashes) - if err := expectResponse(server.tPeer.app, ReceiptsMsg, 42, testBufLimit, receipts); err != nil { - t.Errorf("receipts mismatch: %v", err) - } -} - -// Tests that trie merkle proofs can be retrieved -func TestGetProofsLes1(t *testing.T) { testGetProofs(t, 1) } -func TestGetProofsLes2(t *testing.T) { testGetProofs(t, 2) } - -func testGetProofs(t *testing.T, protocol int) { - // Assemble the test environment - server, tearDown := newServerEnv(t, 4, protocol, nil) - defer tearDown() - bc := server.pm.blockchain.(*core.BlockChain) - - var ( - proofreqs []ProofReq - proofsV1 [][]rlp.RawValue - ) - proofsV2 := light.NewNodeSet() - - accounts := []common.Address{testBankAddress, acc1Addr, acc2Addr, {}} - for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ { - header := bc.GetHeaderByNumber(i) - root := header.Root - trie, _ := trie.New(root, trie.NewDatabase(server.db)) - - for _, acc := range accounts { - req := ProofReq{ - BHash: header.Hash(), - Key: crypto.Keccak256(acc[:]), - } - proofreqs = append(proofreqs, req) - - switch protocol { - case 1: - var proof light.NodeList - trie.Prove(crypto.Keccak256(acc[:]), 0, &proof) - proofsV1 = append(proofsV1, proof) - case 2: - trie.Prove(crypto.Keccak256(acc[:]), 0, proofsV2) - } - } - } - // Send the proof request and verify the response - switch protocol { - case 1: - cost := server.tPeer.GetRequestCost(GetProofsV1Msg, len(proofreqs)) - sendRequest(server.tPeer.app, GetProofsV1Msg, 42, cost, proofreqs) - if err := expectResponse(server.tPeer.app, ProofsV1Msg, 42, testBufLimit, proofsV1); err != nil { - t.Errorf("proofs mismatch: %v", err) - } - case 2: - cost := server.tPeer.GetRequestCost(GetProofsV2Msg, len(proofreqs)) - sendRequest(server.tPeer.app, GetProofsV2Msg, 42, cost, proofreqs) - if err := expectResponse(server.tPeer.app, ProofsV2Msg, 42, testBufLimit, proofsV2.NodeList()); err != nil { - t.Errorf("proofs mismatch: %v", err) - } - } -} - -// Tests that CHT proofs can be correctly retrieved. -//func TestGetCHTProofsLes1(t *testing.T) { testGetCHTProofs(t, 1) } -//func TestGetCHTProofsLes2(t *testing.T) { testGetCHTProofs(t, 2) } - -func testGetCHTProofs(t *testing.T, protocol int) { - config := light.TestServerIndexerConfig - frequency := config.ChtSize - if protocol == 2 { - frequency = config.PairChtSize - } - - waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { - expectSections := frequency / config.ChtSize - for { - cs, _, _ := cIndexer.Sections() - bs, _, _ := bIndexer.Sections() - if cs >= expectSections && bs >= expectSections { - break - } - time.Sleep(10 * time.Millisecond) - } - } - server, tearDown := newServerEnv(t, int(frequency+config.ChtConfirms), protocol, waitIndexers) - defer tearDown() - bc := server.pm.blockchain.(*core.BlockChain) - - // Assemble the proofs from the different protocols - header := bc.GetHeaderByNumber(frequency - 1) - rlp, _ := rlp.EncodeToBytes(header) - - key := make([]byte, 8) - binary.BigEndian.PutUint64(key, frequency-1) - - proofsV1 := []ChtResp{{ - Header: header, - }} - proofsV2 := HelperTrieResps{ - AuxData: [][]byte{rlp}, - } - switch protocol { - case 1: - root := light.GetChtRoot(server.db, 0, bc.GetHeaderByNumber(frequency-1).Hash()) - trie, _ := trie.New(root, trie.NewDatabase(rawdb.NewTable(server.db, light.ChtTablePrefix))) - - var proof light.NodeList - trie.Prove(key, 0, &proof) - proofsV1[0].Proof = proof - - case 2: - root := light.GetChtRoot(server.db, (frequency/config.ChtSize)-1, bc.GetHeaderByNumber(frequency-1).Hash()) - trie, _ := trie.New(root, trie.NewDatabase(rawdb.NewTable(server.db, light.ChtTablePrefix))) - trie.Prove(key, 0, &proofsV2.Proofs) - } - // Assemble the requests for the different protocols - requestsV1 := []ChtReq{{ - ChtNum: frequency / config.ChtSize, - BlockNum: frequency - 1, - }} - requestsV2 := []HelperTrieReq{{ - Type: htCanonical, - TrieIdx: frequency/config.PairChtSize - 1, - Key: key, - AuxReq: auxHeader, - }} - // Send the proof request and verify the response - switch protocol { - case 1: - cost := server.tPeer.GetRequestCost(GetHeaderProofsMsg, len(requestsV1)) - sendRequest(server.tPeer.app, GetHeaderProofsMsg, 42, cost, requestsV1) - if err := expectResponse(server.tPeer.app, HeaderProofsMsg, 42, testBufLimit, proofsV1); err != nil { - t.Errorf("proofs mismatch: %v", err) - } - case 2: - cost := server.tPeer.GetRequestCost(GetHelperTrieProofsMsg, len(requestsV2)) - sendRequest(server.tPeer.app, GetHelperTrieProofsMsg, 42, cost, requestsV2) - if err := expectResponse(server.tPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofsV2); err != nil { - t.Errorf("proofs mismatch: %v", err) - } - } -} - -// Tests that bloombits proofs can be correctly retrieved. -/*func TestGetBloombitsProofs(t *testing.T) { - config := light.TestServerIndexerConfig - - waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { - for { - cs, _, _ := cIndexer.Sections() - bs, _, _ := bIndexer.Sections() - bts, _, _ := btIndexer.Sections() - if cs >= 8 && bs >= 8 && bts >= 1 { - break - } - time.Sleep(10 * time.Millisecond) - } - } - server, tearDown := newServerEnv(t, int(config.BloomTrieSize+config.BloomTrieConfirms), 2, waitIndexers) - defer tearDown() - bc := server.pm.blockchain.(*core.BlockChain) - - // Request and verify each bit of the bloom bits proofs - for bit := 0; bit < 2048; bit++ { - // Assemble the request and proofs for the bloombits - key := make([]byte, 10) - - binary.BigEndian.PutUint16(key[:2], uint16(bit)) - // Only the first bloom section has data. - binary.BigEndian.PutUint64(key[2:], 0) - - requests := []HelperTrieReq{{ - Type: htBloomBits, - TrieIdx: 0, - Key: key, - }} - var proofs HelperTrieResps - - root := light.GetBloomTrieRoot(server.db, 0, bc.GetHeaderByNumber(config.BloomTrieSize-1).Hash()) - trie, _ := trie.New(root, trie.NewDatabase(ethdb.NewTable(server.db, light.BloomTrieTablePrefix))) - trie.Prove(key, 0, &proofs.Proofs) - - // Send the proof request and verify the response - cost := server.tPeer.GetRequestCost(GetHelperTrieProofsMsg, len(requests)) - sendRequest(server.tPeer.app, GetHelperTrieProofsMsg, 42, cost, requests) - if err := expectResponse(server.tPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofs); err != nil { - t.Errorf("bit %d: proofs mismatch: %v", bit, err) - } - } -} -*/ -/* -func TestTransactionStatusLes2(t *testing.T) { - db := ethdb.NewMemDatabase() - pm := newTestProtocolManagerMust(t, false, 0, nil, nil, nil, db) - chain := pm.blockchain.(*core.BlockChain) - blockChainCache := core.NewBlockChainCache(chain) - - config := core.DefaultTxPoolConfig - config.Journal = "" - txpool := core.NewTxPool(config, params.TestChainConfig, blockChainCache) - pm.txpool = txpool - peer, _ := newTestPeer(t, "peer", 2, pm, true) - defer peer.close() - - var reqID uint64 - - test := func(tx *types.Transaction, send bool, expStatus txStatus) { - reqID++ - if send { - cost := peer.GetRequestCost(SendTxV2Msg, 1) - sendRequest(peer.app, SendTxV2Msg, reqID, cost, types.Transactions{tx}) - } else { - cost := peer.GetRequestCost(GetTxStatusMsg, 1) - sendRequest(peer.app, GetTxStatusMsg, reqID, cost, []common.Hash{tx.Hash()}) - } - if err := expectResponse(peer.app, TxStatusMsg, reqID, testBufLimit, []txStatus{expStatus}); err != nil { - t.Errorf("transaction status mismatch") - } - } - - signer := types.NewEIP155Signer(params.TestChainConfig.ChainID) - - // test error status by sending an underpriced transaction - tx0, _ := types.SignTx(types.NewTransaction(0, acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testBankKey) - test(tx0, true, txStatus{Status: core.TxStatusUnknown, Error: core.ErrUnderpriced.Error()}) - - tx1, _ := types.SignTx(types.NewTransaction(0, acc1Addr, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, testBankKey) - test(tx1, false, txStatus{Status: core.TxStatusUnknown}) // query before sending, should be unknown - test(tx1, true, txStatus{Status: core.TxStatusPending}) // send valid processable tx, should return pending - test(tx1, true, txStatus{Status: core.TxStatusPending}) // adding it again should not return an error - - tx2, _ := types.SignTx(types.NewTransaction(1, acc1Addr, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, testBankKey) - tx3, _ := types.SignTx(types.NewTransaction(2, acc1Addr, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, testBankKey) - // send transactions in the wrong order, tx3 should be queued - test(tx3, true, txStatus{Status: core.TxStatusQueued}) - test(tx2, true, txStatus{Status: core.TxStatusPending}) - // query again, now tx3 should be pending too - test(tx3, false, txStatus{Status: core.TxStatusPending}) - - // generate and add a block with tx1 and tx2 included - gchain, _ := core.GenerateChain(params.TestChainConfig, chain.GetBlockByNumber(0), consensus.NewFaker(), db, 1, func(i int, block *core.BlockGen) { - block.AddTx(tx1) - block.AddTx(tx2) - }) - if _, err := chain.InsertChain(gchain); err != nil { - panic(err) - } - // wait until TxPool processes the inserted block - for i := 0; i < 10; i++ { - if pending, _ := txpool.Stats(); pending == 1 { - break - } - time.Sleep(100 * time.Millisecond) - } - if pending, _ := txpool.Stats(); pending != 1 { - t.Fatalf("pending count mismatch: have %d, want 1", pending) - } - - // check if their status is included now - block1hash := rawdb.ReadCanonicalHash(db, 1) - test(tx1, false, txStatus{Status: core.TxStatusIncluded, Lookup: &rawdb.TxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 0}}) - test(tx2, false, txStatus{Status: core.TxStatusIncluded, Lookup: &rawdb.TxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 1}}) - - // create a reorg that rolls them back - gchain, _ = core.GenerateChain(params.TestChainConfig, chain.GetBlockByNumber(0), consensus.NewFaker(), db, 2, func(i int, block *core.BlockGen) {}) - if _, err := chain.InsertChain(gchain); err != nil { - panic(err) - } - // wait until TxPool processes the reorg - for i := 0; i < 10; i++ { - if pending, _ := txpool.Stats(); pending == 3 { - break - } - time.Sleep(100 * time.Millisecond) - } - if pending, _ := txpool.Stats(); pending != 3 { - t.Fatalf("pending count mismatch: have %d, want 3", pending) - } - // check if their status is pending again - test(tx1, false, txStatus{Status: core.TxStatusPending}) - test(tx2, false, txStatus{Status: core.TxStatusPending}) -}*/ diff --git a/les/helper_test.go b/les/helper_test.go deleted file mode 100644 index fd7017a9dd..0000000000 --- a/les/helper_test.go +++ /dev/null @@ -1,440 +0,0 @@ -// Copyright 2016 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 . - -// This file contains some shares testing functionality, common to multiple -// different files and modules being tested. - -package les - -import ( - "crypto/rand" - "math/big" - "sync" - "testing" - "time" - - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/consensus" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/core/vm" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/eth" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/event" - "github.com/AlayaNetwork/Alaya-Go/les/flowcontrol" - "github.com/AlayaNetwork/Alaya-Go/light" - "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/params" - _ "github.com/AlayaNetwork/Alaya-Go/x/xcom" -) - -var ( - testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) - testBankFunds = big.NewInt(1000000000000000000) - - acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") - acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") - acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey) - acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey) - - testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056") - testContractAddr common.Address - testContractCodeDeployed = testContractCode[16:] - testContractDeployed = uint64(2) - - testEventEmitterCode = common.Hex2Bytes("60606040523415600e57600080fd5b7f57050ab73f6b9ebdd9f76b8d4997793f48cf956e965ee070551b9ca0bb71584e60405160405180910390a160358060476000396000f3006060604052600080fd00a165627a7a723058203f727efcad8b5811f8cb1fc2620ce5e8c63570d697aef968172de296ea3994140029") - testEventEmitterAddr common.Address - - testBufLimit = uint64(100) -) - -/* -contract test { - - uint256[100] data; - - function Put(uint256 addr, uint256 value) { - data[addr] = value; - } - - function Get(uint256 addr) constant returns (uint256 value) { - return data[addr]; - } -} -*/ - -func testChainGen(i int, block *core.BlockGen) { - signer := types.NewEIP155Signer(params.TestChainConfig.ChainID) - - switch i { - case 0: - // In block 1, the test bank sends account #1 some ether. - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testBankKey) - block.AddTx(tx) - case 1: - // In block 2, the test bank sends some more ether to account #1. - // acc1Addr passes it on to account #2. - // acc1Addr creates a test contract. - // acc1Addr creates a test event. - nonce := block.TxNonce(acc1Addr) - - tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testBankKey) - tx2, _ := types.SignTx(types.NewTransaction(nonce, acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key) - - block.AddTx(tx1) - block.AddTx(tx2) - case 2: - // Block 3 is empty but was mined by account #2. - block.SetCoinbase(acc2Addr) - block.SetExtra(common.FromHex("0xd782070186706c61746f6e86676f312e3131856c696e757800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")) - data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001") - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, nil, data), signer, testBankKey) - block.AddTx(tx) - case 3: - // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). - b2 := block.PrevBlock(1).Header() - b2.Extra = common.FromHex("0xd782070186706c61746f6e86676f312e3131856c696e757800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - b3 := block.PrevBlock(2).Header() - b3.Extra = common.FromHex("0xd782070186706c61746f6e86676f312e3131856c696e757800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002") - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, nil, data), signer, testBankKey) - block.AddTx(tx) - } -} - -// testIndexers creates a set of indexers with specified params for testing purpose. -func testIndexers(db ethdb.Database, odr light.OdrBackend, iConfig *light.IndexerConfig) (*core.ChainIndexer, *core.ChainIndexer, *core.ChainIndexer) { - chtIndexer := light.NewChtIndexer(db, odr, iConfig.ChtSize, iConfig.ChtConfirms) - bloomIndexer := eth.NewBloomIndexer(db, iConfig.BloomSize, iConfig.BloomConfirms) - bloomTrieIndexer := light.NewBloomTrieIndexer(db, odr, iConfig.BloomSize, iConfig.BloomTrieSize) - bloomIndexer.AddChildIndexer(bloomTrieIndexer) - return chtIndexer, bloomIndexer, bloomTrieIndexer -} - -func testRCL() RequestCostList { - cl := make(RequestCostList, len(reqList)) - for i, code := range reqList { - cl[i].MsgCode = code - cl[i].BaseCost = 0 - cl[i].ReqCost = 0 - } - return cl -} - -// newTestProtocolManager creates a new protocol manager for testing purposes, -// with the given number of blocks already known, potential notification -// channels for different events and relative chain indexers array. -func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *core.BlockGen), odr *LesOdr, peers *peerSet, db ethdb.Database) (*ProtocolManager, error) { - var ( - evmux = new(event.TypeMux) - engine = consensus.NewFaker() - gspec = core.Genesis{ - Config: params.TestChainConfig, - Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, - } - genesis = gspec.MustCommit(db) - chain BlockChain - ) - if peers == nil { - peers = newPeerSet() - } - - if lightSync { - chain, _ = light.NewLightChain(odr, gspec.Config, engine) - } else { - blockchain, _ := core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil) - gchain, _ := core.GenerateChain(gspec.Config, genesis, consensus.NewFaker(), db, blocks, generator) - if _, err := blockchain.InsertChain(gchain); err != nil { - panic(err) - } - chain = blockchain - } - - indexConfig := light.TestServerIndexerConfig - if lightSync { - indexConfig = light.TestClientIndexerConfig - } - pm, err := NewProtocolManager(gspec.Config, indexConfig, lightSync, NetworkId, evmux, engine, peers, chain, nil, db, odr, nil, nil, make(chan struct{}), new(sync.WaitGroup)) - if err != nil { - return nil, err - } - if !lightSync { - srv := &LesServer{lesCommons: lesCommons{protocolManager: pm}} - pm.server = srv - - srv.defParams = &flowcontrol.ServerParams{ - BufLimit: testBufLimit, - MinRecharge: 1, - } - - srv.fcManager = flowcontrol.NewClientManager(50, 10, 1000000000) - srv.fcCostStats = newCostStats(nil) - } - pm.Start(1000) - return pm, nil -} - -// newTestProtocolManagerMust creates a new protocol manager for testing purposes, -// with the given number of blocks already known, potential notification -// channels for different events and relative chain indexers array. In case of an error, the constructor force- -// fails the test. -func newTestProtocolManagerMust(t *testing.T, lightSync bool, blocks int, generator func(int, *core.BlockGen), odr *LesOdr, peers *peerSet, db ethdb.Database) *ProtocolManager { - pm, err := newTestProtocolManager(lightSync, blocks, generator, odr, peers, db) - if err != nil { - t.Fatalf("Failed to create protocol manager: %v", err) - } - return pm -} - -// testPeer is a simulated peer to allow testing direct network calls. -type testPeer struct { - net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging - app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side - *peer -} - -// newTestPeer creates a new peer registered at the given protocol manager. -func newTestPeer(t *testing.T, name string, version int, pm *ProtocolManager, shake bool) (*testPeer, <-chan error) { - // Create a message pipe to communicate through - app, net := p2p.MsgPipe() - - // Generate a random id and create the peer - var id enode.ID - rand.Read(id[:]) - - peer := pm.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) - - // Start the peer on a new thread - errc := make(chan error, 1) - go func() { - select { - case pm.newPeerCh <- peer: - errc <- pm.handle(peer) - case <-pm.quitSync: - errc <- p2p.DiscQuitting - } - }() - tp := &testPeer{ - app: app, - net: net, - peer: peer, - } - // Execute any implicitly requested handshakes and return - if shake { - var ( - genesis = pm.blockchain.Genesis() - head = pm.blockchain.CurrentHeader() - ) - tp.handshake(t, head.Hash(), head.Number.Uint64(), genesis.Hash()) - } - return tp, errc -} - -func newTestPeerPair(name string, version int, pm, pm2 *ProtocolManager) (*peer, <-chan error, *peer, <-chan error) { - // Create a message pipe to communicate through - app, net := p2p.MsgPipe() - - // Generate a random id and create the peer - var id enode.ID - rand.Read(id[:]) - - peer := pm.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) - peer2 := pm2.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), app) - - // Start the peer on a new thread - errc := make(chan error, 1) - errc2 := make(chan error, 1) - go func() { - select { - case pm.newPeerCh <- peer: - errc <- pm.handle(peer) - case <-pm.quitSync: - errc <- p2p.DiscQuitting - } - }() - go func() { - select { - case pm2.newPeerCh <- peer2: - errc2 <- pm2.handle(peer2) - case <-pm2.quitSync: - errc2 <- p2p.DiscQuitting - } - }() - return peer, errc, peer2, errc2 -} - -// handshake simulates a trivial handshake that expects the same state from the -// remote side as we are simulating locally. -func (p *testPeer) handshake(t *testing.T, head common.Hash, headNum uint64, genesis common.Hash) { - var expList keyValueList - expList = expList.add("protocolVersion", uint64(p.version)) - expList = expList.add("networkId", uint64(NetworkId)) - expList = expList.add("headHash", head) - expList = expList.add("headNum", headNum) - expList = expList.add("genesisHash", genesis) - sendList := make(keyValueList, len(expList)) - copy(sendList, expList) - expList = expList.add("serveHeaders", nil) - expList = expList.add("serveChainSince", uint64(0)) - expList = expList.add("serveStateSince", uint64(0)) - expList = expList.add("txRelay", nil) - expList = expList.add("flowControl/BL", testBufLimit) - expList = expList.add("flowControl/MRR", uint64(1)) - expList = expList.add("flowControl/MRC", testRCL()) - - if err := p2p.ExpectMsg(p.app, StatusMsg, expList); err != nil { - t.Fatalf("status recv: %v", err) - } - if err := p2p.Send(p.app, StatusMsg, sendList); err != nil { - t.Fatalf("status send: %v", err) - } - - p.fcServerParams = &flowcontrol.ServerParams{ - BufLimit: testBufLimit, - MinRecharge: 1, - } -} - -// close terminates the local side of the peer, notifying the remote protocol -// manager of termination. -func (p *testPeer) close() { - p.app.Close() -} - -// TestEntity represents a network entity for testing with necessary auxiliary fields. -type TestEntity struct { - db ethdb.Database - rPeer *peer - tPeer *testPeer - peers *peerSet - pm *ProtocolManager - // Indexers - chtIndexer *core.ChainIndexer - bloomIndexer *core.ChainIndexer - bloomTrieIndexer *core.ChainIndexer -} - -// newServerEnv creates a server testing environment with a connected test peer for testing purpose. -func newServerEnv(t *testing.T, blocks int, protocol int, waitIndexers func(*core.ChainIndexer, *core.ChainIndexer, *core.ChainIndexer)) (*TestEntity, func()) { - db := rawdb.NewMemoryDatabase() - cIndexer, bIndexer, btIndexer := testIndexers(db, nil, light.TestServerIndexerConfig) - - pm := newTestProtocolManagerMust(t, false, blocks, testChainGen, nil, nil, db) - peer, _ := newTestPeer(t, "peer", protocol, pm, true) - - cIndexer.Start(pm.blockchain.(*core.BlockChain)) - bIndexer.Start(pm.blockchain.(*core.BlockChain)) - - // Wait until indexers generate enough index data. - if waitIndexers != nil { - waitIndexers(cIndexer, bIndexer, btIndexer) - } - - return &TestEntity{ - db: db, - tPeer: peer, - pm: pm, - chtIndexer: cIndexer, - bloomIndexer: bIndexer, - bloomTrieIndexer: btIndexer, - }, func() { - peer.close() - // Note bloom trie indexer will be closed by it parent recursively. - cIndexer.Close() - bIndexer.Close() - } -} - -// newClientServerEnv creates a client/server arch environment with a connected les server and light client pair -// for testing purpose. -func newClientServerEnv(t *testing.T, blocks int, protocol int, waitIndexers func(*core.ChainIndexer, *core.ChainIndexer, *core.ChainIndexer), newPeer bool) (*TestEntity, *TestEntity, func()) { - db, ldb := rawdb.NewMemoryDatabase(), rawdb.NewMemoryDatabase() - peers, lPeers := newPeerSet(), newPeerSet() - - dist := newRequestDistributor(lPeers, make(chan struct{})) - rm := newRetrieveManager(lPeers, dist, nil) - odr := NewLesOdr(ldb, light.TestClientIndexerConfig, rm) - - cIndexer, bIndexer, btIndexer := testIndexers(db, nil, light.TestServerIndexerConfig) - lcIndexer, lbIndexer, lbtIndexer := testIndexers(ldb, odr, light.TestClientIndexerConfig) - odr.SetIndexers(lcIndexer, lbtIndexer, lbIndexer) - - pm := newTestProtocolManagerMust(t, false, blocks, testChainGen, nil, peers, db) - lpm := newTestProtocolManagerMust(t, true, 0, nil, odr, lPeers, ldb) - - startIndexers := func(clientMode bool, pm *ProtocolManager) { - if clientMode { - lcIndexer.Start(pm.blockchain.(*light.LightChain)) - lbIndexer.Start(pm.blockchain.(*light.LightChain)) - } else { - cIndexer.Start(pm.blockchain.(*core.BlockChain)) - bIndexer.Start(pm.blockchain.(*core.BlockChain)) - } - } - - startIndexers(false, pm) - startIndexers(true, lpm) - - // Execute wait until function if it is specified. - if waitIndexers != nil { - waitIndexers(cIndexer, bIndexer, btIndexer) - } - - var ( - peer, lPeer *peer - err1, err2 <-chan error - ) - if newPeer { - peer, err1, lPeer, err2 = newTestPeerPair("peer", protocol, pm, lpm) - select { - case <-time.After(time.Millisecond * 100): - case err := <-err1: - t.Fatalf("peer 1 handshake error: %v", err) - case err := <-err2: - t.Fatalf("peer 2 handshake error: %v", err) - } - } - - return &TestEntity{ - db: db, - pm: pm, - rPeer: peer, - peers: peers, - chtIndexer: cIndexer, - bloomIndexer: bIndexer, - bloomTrieIndexer: btIndexer, - }, &TestEntity{ - db: ldb, - pm: lpm, - rPeer: lPeer, - peers: lPeers, - chtIndexer: lcIndexer, - bloomIndexer: lbIndexer, - bloomTrieIndexer: lbtIndexer, - }, func() { - // Note bloom trie indexers will be closed by their parents recursively. - cIndexer.Close() - bIndexer.Close() - lcIndexer.Close() - lbIndexer.Close() - } -} diff --git a/les/metrics.go b/les/metrics.go deleted file mode 100644 index 973efc0ebd..0000000000 --- a/les/metrics.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2016 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 . - -package les - -import ( - "github.com/AlayaNetwork/Alaya-Go/metrics" - "github.com/AlayaNetwork/Alaya-Go/p2p" -) - -var ( - /* propTxnInPacketsMeter = metrics.NewMeter("eth/prop/txns/in/packets") - propTxnInTrafficMeter = metrics.NewMeter("eth/prop/txns/in/traffic") - propTxnOutPacketsMeter = metrics.NewMeter("eth/prop/txns/out/packets") - propTxnOutTrafficMeter = metrics.NewMeter("eth/prop/txns/out/traffic") - propHashInPacketsMeter = metrics.NewMeter("eth/prop/hashes/in/packets") - propHashInTrafficMeter = metrics.NewMeter("eth/prop/hashes/in/traffic") - propHashOutPacketsMeter = metrics.NewMeter("eth/prop/hashes/out/packets") - propHashOutTrafficMeter = metrics.NewMeter("eth/prop/hashes/out/traffic") - propBlockInPacketsMeter = metrics.NewMeter("eth/prop/blocks/in/packets") - propBlockInTrafficMeter = metrics.NewMeter("eth/prop/blocks/in/traffic") - propBlockOutPacketsMeter = metrics.NewMeter("eth/prop/blocks/out/packets") - propBlockOutTrafficMeter = metrics.NewMeter("eth/prop/blocks/out/traffic") - reqHashInPacketsMeter = metrics.NewMeter("eth/req/hashes/in/packets") - reqHashInTrafficMeter = metrics.NewMeter("eth/req/hashes/in/traffic") - reqHashOutPacketsMeter = metrics.NewMeter("eth/req/hashes/out/packets") - reqHashOutTrafficMeter = metrics.NewMeter("eth/req/hashes/out/traffic") - reqBlockInPacketsMeter = metrics.NewMeter("eth/req/blocks/in/packets") - reqBlockInTrafficMeter = metrics.NewMeter("eth/req/blocks/in/traffic") - reqBlockOutPacketsMeter = metrics.NewMeter("eth/req/blocks/out/packets") - reqBlockOutTrafficMeter = metrics.NewMeter("eth/req/blocks/out/traffic") - reqHeaderInPacketsMeter = metrics.NewMeter("eth/req/headers/in/packets") - reqHeaderInTrafficMeter = metrics.NewMeter("eth/req/headers/in/traffic") - reqHeaderOutPacketsMeter = metrics.NewMeter("eth/req/headers/out/packets") - reqHeaderOutTrafficMeter = metrics.NewMeter("eth/req/headers/out/traffic") - reqBodyInPacketsMeter = metrics.NewMeter("eth/req/bodies/in/packets") - reqBodyInTrafficMeter = metrics.NewMeter("eth/req/bodies/in/traffic") - reqBodyOutPacketsMeter = metrics.NewMeter("eth/req/bodies/out/packets") - reqBodyOutTrafficMeter = metrics.NewMeter("eth/req/bodies/out/traffic") - reqStateInPacketsMeter = metrics.NewMeter("eth/req/states/in/packets") - reqStateInTrafficMeter = metrics.NewMeter("eth/req/states/in/traffic") - reqStateOutPacketsMeter = metrics.NewMeter("eth/req/states/out/packets") - reqStateOutTrafficMeter = metrics.NewMeter("eth/req/states/out/traffic") - reqReceiptInPacketsMeter = metrics.NewMeter("eth/req/receipts/in/packets") - reqReceiptInTrafficMeter = metrics.NewMeter("eth/req/receipts/in/traffic") - reqReceiptOutPacketsMeter = metrics.NewMeter("eth/req/receipts/out/packets") - reqReceiptOutTrafficMeter = metrics.NewMeter("eth/req/receipts/out/traffic")*/ - miscInPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets", nil) - miscInTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic", nil) - miscOutPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets", nil) - miscOutTrafficMeter = metrics.NewRegisteredMeter("les/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 - 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 - packets.Mark(1) - traffic.Mark(int64(msg.Size)) - - // Send the packet to the p2p layer - return rw.MsgReadWriter.WriteMsg(msg) -} diff --git a/les/odr.go b/les/odr.go deleted file mode 100644 index bc9c978181..0000000000 --- a/les/odr.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2016 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 . - -package les - -import ( - "context" - - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/light" - "github.com/AlayaNetwork/Alaya-Go/log" -) - -// LesOdr implements light.OdrBackend -type LesOdr struct { - db ethdb.Database - indexerConfig *light.IndexerConfig - chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer - retriever *retrieveManager - stop chan struct{} -} - -func NewLesOdr(db ethdb.Database, config *light.IndexerConfig, retriever *retrieveManager) *LesOdr { - return &LesOdr{ - db: db, - indexerConfig: config, - retriever: retriever, - stop: make(chan struct{}), - } -} - -// Stop cancels all pending retrievals -func (odr *LesOdr) Stop() { - close(odr.stop) -} - -// Database returns the backing database -func (odr *LesOdr) Database() ethdb.Database { - return odr.db -} - -// SetIndexers adds the necessary chain indexers to the ODR backend -func (odr *LesOdr) SetIndexers(chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer) { - odr.chtIndexer = chtIndexer - odr.bloomTrieIndexer = bloomTrieIndexer - odr.bloomIndexer = bloomIndexer -} - -// ChtIndexer returns the CHT chain indexer -func (odr *LesOdr) ChtIndexer() *core.ChainIndexer { - return odr.chtIndexer -} - -// BloomTrieIndexer returns the bloom trie chain indexer -func (odr *LesOdr) BloomTrieIndexer() *core.ChainIndexer { - return odr.bloomTrieIndexer -} - -// BloomIndexer returns the bloombits chain indexer -func (odr *LesOdr) BloomIndexer() *core.ChainIndexer { - return odr.bloomIndexer -} - -// IndexerConfig returns the indexer config. -func (odr *LesOdr) IndexerConfig() *light.IndexerConfig { - return odr.indexerConfig -} - -const ( - MsgBlockBodies = iota - MsgCode - MsgReceipts - MsgProofsV1 - MsgProofsV2 - MsgHeaderProofs - MsgHelperTrieProofs -) - -// Msg encodes a LES message that delivers reply data for a request -type Msg struct { - MsgType int - ReqID uint64 - Obj interface{} -} - -// Retrieve tries to fetch an object from the LES network. -// If the network retrieval was successful, it stores the object in local db. -func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err error) { - lreq := LesRequest(req) - - reqID := genReqID() - rq := &distReq{ - getCost: func(dp distPeer) uint64 { - return lreq.GetCost(dp.(*peer)) - }, - canSend: func(dp distPeer) bool { - p := dp.(*peer) - return lreq.CanSend(p) - }, - request: func(dp distPeer) func() { - p := dp.(*peer) - cost := lreq.GetCost(p) - p.fcServer.QueueRequest(reqID, cost) - return func() { lreq.Request(reqID, p) } - }, - } - - if err = odr.retriever.retrieve(ctx, reqID, rq, func(p distPeer, msg *Msg) error { return lreq.Validate(odr.db, msg) }, odr.stop); err == nil { - // retrieved from network, store in db - req.StoreResult(odr.db) - } else { - log.Debug("Failed to retrieve data from network", "err", err) - } - return -} diff --git a/les/odr_requests.go b/les/odr_requests.go deleted file mode 100644 index 4be0b0231a..0000000000 --- a/les/odr_requests.go +++ /dev/null @@ -1,560 +0,0 @@ -// Copyright 2016 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 . - -// Package light implements on-demand retrieval capable state and chain objects -// for the Ethereum Light Client. -package les - -import ( - "encoding/binary" - "errors" - "fmt" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/light" - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/rlp" - "github.com/AlayaNetwork/Alaya-Go/trie" -) - -var ( - errInvalidMessageType = errors.New("invalid message type") - errInvalidEntryCount = errors.New("invalid number of response entries") - errHeaderUnavailable = errors.New("header unavailable") - errTxHashMismatch = errors.New("transaction hash mismatch") - errReceiptHashMismatch = errors.New("receipt hash mismatch") - errDataHashMismatch = errors.New("data hash mismatch") - errCHTHashMismatch = errors.New("cht hash mismatch") - errCHTNumberMismatch = errors.New("cht number mismatch") - errUselessNodes = errors.New("useless nodes in merkle proof nodeset") -) - -type LesOdrRequest interface { - GetCost(*peer) uint64 - CanSend(*peer) bool - Request(uint64, *peer) error - Validate(ethdb.Database, *Msg) error -} - -func LesRequest(req light.OdrRequest) LesOdrRequest { - switch r := req.(type) { - case *light.BlockRequest: - return (*BlockRequest)(r) - case *light.ReceiptsRequest: - return (*ReceiptsRequest)(r) - case *light.TrieRequest: - return (*TrieRequest)(r) - case *light.CodeRequest: - return (*CodeRequest)(r) - case *light.ChtRequest: - return (*ChtRequest)(r) - case *light.BloomRequest: - return (*BloomRequest)(r) - default: - return nil - } -} - -// BlockRequest is the ODR request type for block bodies -type BlockRequest light.BlockRequest - -// GetCost returns the cost of the given ODR request according to the serving -// peer's cost table (implementation of LesOdrRequest) -func (r *BlockRequest) GetCost(peer *peer) uint64 { - return peer.GetRequestCost(GetBlockBodiesMsg, 1) -} - -// CanSend tells if a certain peer is suitable for serving the given request -func (r *BlockRequest) CanSend(peer *peer) bool { - return peer.HasBlock(r.Hash, r.Number) -} - -// Request sends an ODR request to the LES network (implementation of LesOdrRequest) -func (r *BlockRequest) Request(reqID uint64, peer *peer) error { - peer.Log().Debug("Requesting block body", "hash", r.Hash) - return peer.RequestBodies(reqID, r.GetCost(peer), []common.Hash{r.Hash}) -} - -// Valid processes an ODR request reply message from the LES network -// returns true and stores results in memory if the message was a valid reply -// to the request (implementation of LesOdrRequest) -func (r *BlockRequest) Validate(db ethdb.Database, msg *Msg) error { - log.Debug("Validating block body", "hash", r.Hash) - - // Ensure we have a correct message with a single block body - if msg.MsgType != MsgBlockBodies { - return errInvalidMessageType - } - bodies := msg.Obj.([]*types.Body) - if len(bodies) != 1 { - return errInvalidEntryCount - } - body := bodies[0] - - // Retrieve our stored header and validate block content against it - header := rawdb.ReadHeader(db, r.Hash, r.Number) - if header == nil { - return errHeaderUnavailable - } - if header.TxHash != types.DeriveSha(types.Transactions(body.Transactions)) { - return errTxHashMismatch - } - // Validations passed, encode and store RLP - data, err := rlp.EncodeToBytes(body) - if err != nil { - return err - } - r.Rlp = data - return nil -} - -// ReceiptsRequest is the ODR request type for block receipts by block hash -type ReceiptsRequest light.ReceiptsRequest - -// GetCost returns the cost of the given ODR request according to the serving -// peer's cost table (implementation of LesOdrRequest) -func (r *ReceiptsRequest) GetCost(peer *peer) uint64 { - return peer.GetRequestCost(GetReceiptsMsg, 1) -} - -// CanSend tells if a certain peer is suitable for serving the given request -func (r *ReceiptsRequest) CanSend(peer *peer) bool { - return peer.HasBlock(r.Hash, r.Number) -} - -// Request sends an ODR request to the LES network (implementation of LesOdrRequest) -func (r *ReceiptsRequest) Request(reqID uint64, peer *peer) error { - peer.Log().Debug("Requesting block receipts", "hash", r.Hash) - return peer.RequestReceipts(reqID, r.GetCost(peer), []common.Hash{r.Hash}) -} - -// Valid processes an ODR request reply message from the LES network -// returns true and stores results in memory if the message was a valid reply -// to the request (implementation of LesOdrRequest) -func (r *ReceiptsRequest) Validate(db ethdb.Database, msg *Msg) error { - log.Debug("Validating block receipts", "hash", r.Hash) - - // Ensure we have a correct message with a single block receipt - if msg.MsgType != MsgReceipts { - return errInvalidMessageType - } - receipts := msg.Obj.([]types.Receipts) - if len(receipts) != 1 { - return errInvalidEntryCount - } - receipt := receipts[0] - - // Retrieve our stored header and validate receipt content against it - header := rawdb.ReadHeader(db, r.Hash, r.Number) - if header == nil { - return errHeaderUnavailable - } - if header.ReceiptHash != types.DeriveSha(receipt) { - return errReceiptHashMismatch - } - // Validations passed, store and return - r.Receipts = receipt - return nil -} - -type ProofReq struct { - BHash common.Hash - AccKey, Key []byte - FromLevel uint -} - -// ODR request type for state/storage trie entries, see LesOdrRequest interface -type TrieRequest light.TrieRequest - -// GetCost returns the cost of the given ODR request according to the serving -// peer's cost table (implementation of LesOdrRequest) -func (r *TrieRequest) GetCost(peer *peer) uint64 { - switch peer.version { - case lpv1: - return peer.GetRequestCost(GetProofsV1Msg, 1) - case lpv2: - return peer.GetRequestCost(GetProofsV2Msg, 1) - default: - panic(nil) - } -} - -// CanSend tells if a certain peer is suitable for serving the given request -func (r *TrieRequest) CanSend(peer *peer) bool { - return peer.HasBlock(r.Id.BlockHash, r.Id.BlockNumber) -} - -// Request sends an ODR request to the LES network (implementation of LesOdrRequest) -func (r *TrieRequest) Request(reqID uint64, peer *peer) error { - peer.Log().Debug("Requesting trie proof", "root", r.Id.Root, "key", r.Key) - req := ProofReq{ - BHash: r.Id.BlockHash, - AccKey: r.Id.AccKey, - Key: r.Key, - } - return peer.RequestProofs(reqID, r.GetCost(peer), []ProofReq{req}) -} - -// Valid processes an ODR request reply message from the LES network -// returns true and stores results in memory if the message was a valid reply -// to the request (implementation of LesOdrRequest) -func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error { - log.Debug("Validating trie proof", "root", r.Id.Root, "key", r.Key) - - switch msg.MsgType { - case MsgProofsV1: - proofs := msg.Obj.([]light.NodeList) - if len(proofs) != 1 { - return errInvalidEntryCount - } - nodeSet := proofs[0].NodeSet() - // Verify the proof and store if checks out - if _, _, err := trie.VerifyProof(r.Id.Root, r.Key, nodeSet); err != nil { - return fmt.Errorf("merkle proof verification failed: %v", err) - } - r.Proof = nodeSet - return nil - - case MsgProofsV2: - proofs := msg.Obj.(light.NodeList) - // Verify the proof and store if checks out - nodeSet := proofs.NodeSet() - reads := &readTraceDB{db: nodeSet} - if _, _, err := trie.VerifyProof(r.Id.Root, r.Key, reads); err != nil { - return fmt.Errorf("merkle proof verification failed: %v", err) - } - // check if all nodes have been read by VerifyProof - if len(reads.reads) != nodeSet.KeyCount() { - return errUselessNodes - } - r.Proof = nodeSet - return nil - - default: - return errInvalidMessageType - } -} - -type CodeReq struct { - BHash common.Hash - AccKey []byte -} - -// ODR request type for node data (used for retrieving contract code), see LesOdrRequest interface -type CodeRequest light.CodeRequest - -// GetCost returns the cost of the given ODR request according to the serving -// peer's cost table (implementation of LesOdrRequest) -func (r *CodeRequest) GetCost(peer *peer) uint64 { - return peer.GetRequestCost(GetCodeMsg, 1) -} - -// CanSend tells if a certain peer is suitable for serving the given request -func (r *CodeRequest) CanSend(peer *peer) bool { - return peer.HasBlock(r.Id.BlockHash, r.Id.BlockNumber) -} - -// Request sends an ODR request to the LES network (implementation of LesOdrRequest) -func (r *CodeRequest) Request(reqID uint64, peer *peer) error { - peer.Log().Debug("Requesting code data", "hash", r.Hash) - req := CodeReq{ - BHash: r.Id.BlockHash, - AccKey: r.Id.AccKey, - } - return peer.RequestCode(reqID, r.GetCost(peer), []CodeReq{req}) -} - -// Valid processes an ODR request reply message from the LES network -// returns true and stores results in memory if the message was a valid reply -// to the request (implementation of LesOdrRequest) -func (r *CodeRequest) Validate(db ethdb.Database, msg *Msg) error { - log.Debug("Validating code data", "hash", r.Hash) - - // Ensure we have a correct message with a single code element - if msg.MsgType != MsgCode { - return errInvalidMessageType - } - reply := msg.Obj.([][]byte) - if len(reply) != 1 { - return errInvalidEntryCount - } - data := reply[0] - - // Verify the data and store if checks out - if hash := crypto.Keccak256Hash(data); r.Hash != hash { - return errDataHashMismatch - } - r.Data = data - return nil -} - -const ( - // helper trie type constants - htCanonical = iota // Canonical hash trie - htBloomBits // BloomBits trie - - // applicable for all helper trie requests - auxRoot = 1 - // applicable for htCanonical - auxHeader = 2 -) - -type HelperTrieReq struct { - Type uint - TrieIdx uint64 - Key []byte - FromLevel, AuxReq uint -} - -type HelperTrieResps struct { // describes all responses, not just a single one - Proofs light.NodeList - AuxData [][]byte -} - -// legacy LES/1 -type ChtReq struct { - ChtNum, BlockNum uint64 - FromLevel uint -} - -// legacy LES/1 -type ChtResp struct { - Header *types.Header - Proof []rlp.RawValue -} - -// ODR request type for requesting headers by Canonical Hash Trie, see LesOdrRequest interface -type ChtRequest light.ChtRequest - -// GetCost returns the cost of the given ODR request according to the serving -// peer's cost table (implementation of LesOdrRequest) -func (r *ChtRequest) GetCost(peer *peer) uint64 { - switch peer.version { - case lpv1: - return peer.GetRequestCost(GetHeaderProofsMsg, 1) - case lpv2: - return peer.GetRequestCost(GetHelperTrieProofsMsg, 1) - default: - panic(nil) - } -} - -// CanSend tells if a certain peer is suitable for serving the given request -func (r *ChtRequest) CanSend(peer *peer) bool { - peer.lock.RLock() - defer peer.lock.RUnlock() - - return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize -} - -// Request sends an ODR request to the LES network (implementation of LesOdrRequest) -func (r *ChtRequest) Request(reqID uint64, peer *peer) error { - peer.Log().Debug("Requesting CHT", "cht", r.ChtNum, "block", r.BlockNum) - var encNum [8]byte - binary.BigEndian.PutUint64(encNum[:], r.BlockNum) - req := HelperTrieReq{ - Type: htCanonical, - TrieIdx: r.ChtNum, - Key: encNum[:], - AuxReq: auxHeader, - } - return peer.RequestHelperTrieProofs(reqID, r.GetCost(peer), []HelperTrieReq{req}) -} - -// Valid processes an ODR request reply message from the LES network -// returns true and stores results in memory if the message was a valid reply -// to the request (implementation of LesOdrRequest) -func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error { - log.Debug("Validating CHT", "cht", r.ChtNum, "block", r.BlockNum) - - switch msg.MsgType { - case MsgHeaderProofs: // LES/1 backwards compatibility - proofs := msg.Obj.([]ChtResp) - if len(proofs) != 1 { - return errInvalidEntryCount - } - proof := proofs[0] - - // Verify the CHT - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], r.BlockNum) - - value, _, err := trie.VerifyProof(r.ChtRoot, encNumber[:], light.NodeList(proof.Proof).NodeSet()) - if err != nil { - return err - } - var node light.ChtNode - if err := rlp.DecodeBytes(value, &node); err != nil { - return err - } - if node.Hash != proof.Header.Hash() { - return errCHTHashMismatch - } - // Verifications passed, store and return - r.Header = proof.Header - r.Proof = light.NodeList(proof.Proof).NodeSet() - case MsgHelperTrieProofs: - resp := msg.Obj.(HelperTrieResps) - if len(resp.AuxData) != 1 { - return errInvalidEntryCount - } - nodeSet := resp.Proofs.NodeSet() - headerEnc := resp.AuxData[0] - if len(headerEnc) == 0 { - return errHeaderUnavailable - } - header := new(types.Header) - if err := rlp.DecodeBytes(headerEnc, header); err != nil { - return errHeaderUnavailable - } - - // Verify the CHT - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], r.BlockNum) - - reads := &readTraceDB{db: nodeSet} - value, _, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads) - if err != nil { - return fmt.Errorf("merkle proof verification failed: %v", err) - } - if len(reads.reads) != nodeSet.KeyCount() { - return errUselessNodes - } - - var node light.ChtNode - if err := rlp.DecodeBytes(value, &node); err != nil { - return err - } - if node.Hash != header.Hash() { - return errCHTHashMismatch - } - if r.BlockNum != header.Number.Uint64() { - return errCHTNumberMismatch - } - // Verifications passed, store and return - r.Header = header - r.Proof = nodeSet - default: - return errInvalidMessageType - } - return nil -} - -type BloomReq struct { - BloomTrieNum, BitIdx, SectionIndex, FromLevel uint64 -} - -// ODR request type for requesting headers by Canonical Hash Trie, see LesOdrRequest interface -type BloomRequest light.BloomRequest - -// GetCost returns the cost of the given ODR request according to the serving -// peer's cost table (implementation of LesOdrRequest) -func (r *BloomRequest) GetCost(peer *peer) uint64 { - return peer.GetRequestCost(GetHelperTrieProofsMsg, len(r.SectionIndexList)) -} - -// CanSend tells if a certain peer is suitable for serving the given request -func (r *BloomRequest) CanSend(peer *peer) bool { - peer.lock.RLock() - defer peer.lock.RUnlock() - - if peer.version < lpv2 { - return false - } - return peer.headInfo.Number >= r.Config.BloomTrieConfirms && r.BloomTrieNum <= (peer.headInfo.Number-r.Config.BloomTrieConfirms)/r.Config.BloomTrieSize -} - -// Request sends an ODR request to the LES network (implementation of LesOdrRequest) -func (r *BloomRequest) Request(reqID uint64, peer *peer) error { - peer.Log().Debug("Requesting BloomBits", "bloomTrie", r.BloomTrieNum, "bitIdx", r.BitIdx, "sections", r.SectionIndexList) - reqs := make([]HelperTrieReq, len(r.SectionIndexList)) - - var encNumber [10]byte - binary.BigEndian.PutUint16(encNumber[:2], uint16(r.BitIdx)) - - for i, sectionIdx := range r.SectionIndexList { - binary.BigEndian.PutUint64(encNumber[2:], sectionIdx) - reqs[i] = HelperTrieReq{ - Type: htBloomBits, - TrieIdx: r.BloomTrieNum, - Key: common.CopyBytes(encNumber[:]), - } - } - return peer.RequestHelperTrieProofs(reqID, r.GetCost(peer), reqs) -} - -// Valid processes an ODR request reply message from the LES network -// returns true and stores results in memory if the message was a valid reply -// to the request (implementation of LesOdrRequest) -func (r *BloomRequest) Validate(db ethdb.Database, msg *Msg) error { - log.Debug("Validating BloomBits", "bloomTrie", r.BloomTrieNum, "bitIdx", r.BitIdx, "sections", r.SectionIndexList) - - // Ensure we have a correct message with a single proof element - if msg.MsgType != MsgHelperTrieProofs { - return errInvalidMessageType - } - resps := msg.Obj.(HelperTrieResps) - proofs := resps.Proofs - nodeSet := proofs.NodeSet() - reads := &readTraceDB{db: nodeSet} - - r.BloomBits = make([][]byte, len(r.SectionIndexList)) - - // Verify the proofs - var encNumber [10]byte - binary.BigEndian.PutUint16(encNumber[:2], uint16(r.BitIdx)) - - for i, idx := range r.SectionIndexList { - binary.BigEndian.PutUint64(encNumber[2:], idx) - value, _, err := trie.VerifyProof(r.BloomTrieRoot, encNumber[:], reads) - if err != nil { - return err - } - r.BloomBits[i] = value - } - - if len(reads.reads) != nodeSet.KeyCount() { - return errUselessNodes - } - r.Proofs = nodeSet - return nil -} - -// readTraceDB stores the keys of database reads. We use this to check that received node -// sets contain only the trie nodes necessary to make proofs pass. -type readTraceDB struct { - db ethdb.KeyValueReader - reads map[string]struct{} -} - -// Get returns a stored node -func (db *readTraceDB) Get(k []byte) ([]byte, error) { - if db.reads == nil { - db.reads = make(map[string]struct{}) - } - db.reads[string(k)] = struct{}{} - return db.db.Get(k) -} - -// Has returns true if the node set contains the given key -func (db *readTraceDB) Has(key []byte) (bool, error) { - _, err := db.Get(key) - return err == nil, nil -} diff --git a/les/odr_test.go b/les/odr_test.go deleted file mode 100644 index b10c7fce01..0000000000 --- a/les/odr_test.go +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2016 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 . - -package les - -import ( - "bytes" - "context" - "math/big" - "testing" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/common/math" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/state" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/core/vm" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/light" - "github.com/AlayaNetwork/Alaya-Go/params" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -type odrTestFn func(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte - -func TestOdrGetBlockLes1(t *testing.T) { testOdr(t, 1, 1, odrGetBlock) } - -func TestOdrGetBlockLes2(t *testing.T) { testOdr(t, 2, 1, odrGetBlock) } - -func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { - var block *types.Block - if bc != nil { - block = bc.GetBlockByHash(bhash) - } else { - block, _ = lc.GetBlockByHash(ctx, bhash) - } - if block == nil { - return nil - } - rlp, _ := rlp.EncodeToBytes(block) - return rlp -} - -func TestOdrGetReceiptsLes1(t *testing.T) { testOdr(t, 1, 1, odrGetReceipts) } - -func TestOdrGetReceiptsLes2(t *testing.T) { testOdr(t, 2, 1, odrGetReceipts) } - -func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { - var receipts types.Receipts - if bc != nil { - if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { - receipts = rawdb.ReadReceipts(db, bhash, *number, config) - } - } else { - if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { - receipts, _ = light.GetBlockReceipts(ctx, lc.Odr(), bhash, *number) - } - } - if receipts == nil { - return nil - } - rlp, _ := rlp.EncodeToBytes(receipts) - return rlp -} - -func TestOdrAccountsLes1(t *testing.T) { testOdr(t, 1, 1, odrAccounts) } - -func TestOdrAccountsLes2(t *testing.T) { testOdr(t, 2, 1, odrAccounts) } - -func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { - dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678") - acc := []common.Address{testBankAddress, acc1Addr, acc2Addr, dummyAddr} - - var ( - res []byte - st *state.StateDB - err error - ) - for _, addr := range acc { - if bc != nil { - header := bc.GetHeaderByHash(bhash) - st, err = state.New(header.Root, state.NewDatabase(db)) - } else { - header := lc.GetHeaderByHash(bhash) - st = light.NewState(ctx, header, lc.Odr()) - } - if err == nil { - bal := st.GetBalance(addr) - rlp, _ := rlp.EncodeToBytes(bal) - res = append(res, rlp...) - } - } - return res -} - -//func TestOdrContractCallLes1(t *testing.T) { testOdr(t, 1, 2, odrContractCall) } - -//func TestOdrContractCallLes2(t *testing.T) { testOdr(t, 2, 2, odrContractCall) } - -type callmsg struct { - types.Message -} - -func (callmsg) CheckNonce() bool { return false } - -func odrContractCall(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { - data := common.Hex2Bytes("60CD26850000000000000000000000000000000000000000000000000000000000000000") - - var res []byte - for i := 0; i < 3; i++ { - data[35] = byte(i) - if bc != nil { - header := bc.GetHeaderByHash(bhash) - statedb, err := state.New(header.Root, state.NewDatabase(db)) - - if err == nil { - from := statedb.GetOrNewStateObject(testBankAddress) - from.SetBalance(math.MaxBig256) - - msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)} - - context := core.NewEVMContext(msg, header, bc) - vmenv := vm.NewEVM(context, nil, statedb, config, vm.Config{}) - - //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{}) - gp := new(core.GasPool).AddGas(math.MaxUint64) - ret, _ := core.ApplyMessage(vmenv, msg, gp) - res = append(res, ret.Return()...) - } - } else { - header := lc.GetHeaderByHash(bhash) - state := light.NewState(ctx, header, lc.Odr()) - state.SetBalance(testBankAddress, math.MaxBig256) - msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)} - context := core.NewEVMContext(msg, header, lc) - vmenv := vm.NewEVM(context, nil, state, config, vm.Config{}) - gp := new(core.GasPool).AddGas(math.MaxUint64) - ret, _ := core.ApplyMessage(vmenv, msg, gp) - if state.Error() == nil { - res = append(res, ret.Return()...) - } - } - } - return res -} - -// testOdr tests odr requests whose validation guaranteed by block headers. -func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) { - // Assemble the test environment - server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, true) - defer tearDown() - client.pm.synchronise(client.rPeer) - - test := func(expFail uint64) { - // Mark this as a helper to put the failures at the correct lines - t.Helper() - - for i := uint64(0); i <= server.pm.blockchain.CurrentHeader().Number.Uint64(); i++ { - bhash := rawdb.ReadCanonicalHash(server.db, i) - b1 := fn(light.NoOdr, server.db, server.pm.chainConfig, server.pm.blockchain.(*core.BlockChain), nil, bhash) - - ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) - defer cancel() - b2 := fn(ctx, client.db, client.pm.chainConfig, nil, client.pm.blockchain.(*light.LightChain), bhash) - - eq := bytes.Equal(b1, b2) - exp := i < expFail - if exp && !eq { - t.Fatalf("odr mismatch: have %x, want %x", b2, b1) - } - if !exp && eq { - t.Fatalf("unexpected odr match") - } - } - } - // temporarily remove peer to test odr fails - // expect retrievals to fail (except genesis block) without a les peer - client.peers.Unregister(client.rPeer.id) - time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed - test(expFail) - // expect all retrievals to pass - client.peers.Register(client.rPeer) - time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed - client.peers.lock.Lock() - client.rPeer.hasBlock = func(common.Hash, uint64) bool { return true } - client.peers.lock.Unlock() - test(5) - // still expect all retrievals to pass, now data should be cached locally - client.peers.Unregister(client.rPeer.id) - time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed - test(5) -} diff --git a/les/peer.go b/les/peer.go deleted file mode 100644 index 130fda7be1..0000000000 --- a/les/peer.go +++ /dev/null @@ -1,652 +0,0 @@ -// Copyright 2016 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 . - -// Package les implements the Light Ethereum Subprotocol. -package les - -import ( - "errors" - "fmt" - "math/big" - "sync" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/eth" - "github.com/AlayaNetwork/Alaya-Go/les/flowcontrol" - "github.com/AlayaNetwork/Alaya-Go/light" - "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -var ( - errClosed = errors.New("peer set is closed") - errAlreadyRegistered = errors.New("peer is already registered") - errNotRegistered = errors.New("peer is not registered") - errInvalidHelpTrieReq = errors.New("invalid help trie request") -) - -const maxResponseErrors = 50 // number of invalid responses tolerated (makes the protocol less brittle but still avoids spam) - -const ( - announceTypeNone = iota - announceTypeSimple - announceTypeSigned -) - -type peer struct { - *p2p.Peer - - rw p2p.MsgReadWriter - - version int // Protocol version negotiated - network uint64 // Network ID being on - - announceType, requestAnnounceType uint64 - - id string - - headInfo *announceData - lock sync.RWMutex - - announceChn chan announceData - sendQueue *execQueue - - poolEntry *poolEntry - hasBlock func(common.Hash, uint64) bool - responseErrors int - - fcClient *flowcontrol.ClientNode // nil if the peer is server only - fcServer *flowcontrol.ServerNode // nil if the peer is client only - fcServerParams *flowcontrol.ServerParams - fcCosts requestCostTable -} - -func newPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { - id := p.ID() - - return &peer{ - Peer: p, - rw: rw, - version: version, - network: network, - id: fmt.Sprintf("%x", id[:8]), - announceChn: make(chan announceData, 20), - } -} - -func (p *peer) canQueue() bool { - return p.sendQueue.canQueue() -} - -func (p *peer) queueSend(f func()) { - p.sendQueue.queue(f) -} - -// Info gathers and returns a collection of metadata known about a peer. -func (p *peer) Info() *eth.PeerInfo { - return ð.PeerInfo{ - Version: p.version, - BN: new(big.Int).SetUint64(p.headInfo.Number), - Head: fmt.Sprintf("%x", p.Head()), - } -} - -// Head retrieves a copy of the current head (most recent) hash of the peer. -func (p *peer) Head() (hash common.Hash) { - p.lock.RLock() - defer p.lock.RUnlock() - - copy(hash[:], p.headInfo.Hash[:]) - return hash -} - -func (p *peer) HeadAndNum() (hash common.Hash, td *big.Int) { - p.lock.RLock() - defer p.lock.RUnlock() - - copy(hash[:], p.headInfo.Hash[:]) - return hash, new(big.Int).SetUint64(p.headInfo.Number) -} - -func (p *peer) headBlockInfo() blockInfo { - p.lock.RLock() - defer p.lock.RUnlock() - - return blockInfo{Hash: p.headInfo.Hash, Number: p.headInfo.Number} -} - -// Td retrieves the current total difficulty of a peer. -/*func (p *peer) Td() *big.Int { - p.lock.RLock() - defer p.lock.RUnlock() - - return new(big.Int).Set(p.headInfo.Td) -}*/ - -// waitBefore implements distPeer interface -func (p *peer) waitBefore(maxCost uint64) (time.Duration, float64) { - return p.fcServer.CanSend(maxCost) -} - -func sendRequest(w p2p.MsgWriter, msgcode, reqID, cost uint64, data interface{}) error { - type req struct { - ReqID uint64 - Data interface{} - } - return p2p.Send(w, msgcode, req{reqID, data}) -} - -func sendResponse(w p2p.MsgWriter, msgcode, reqID, bv uint64, data interface{}) error { - type resp struct { - ReqID, BV uint64 - Data interface{} - } - return p2p.Send(w, msgcode, resp{reqID, bv, data}) -} - -func (p *peer) GetRequestCost(msgcode uint64, amount int) uint64 { - p.lock.RLock() - defer p.lock.RUnlock() - - cost := p.fcCosts[msgcode].baseCost + p.fcCosts[msgcode].reqCost*uint64(amount) - if cost > p.fcServerParams.BufLimit { - cost = p.fcServerParams.BufLimit - } - return cost -} - -// HasBlock checks if the peer has a given block -func (p *peer) HasBlock(hash common.Hash, number uint64) bool { - p.lock.RLock() - hasBlock := p.hasBlock - p.lock.RUnlock() - return hasBlock != nil && hasBlock(hash, number) -} - -// SendAnnounce announces the availability of a number of blocks through -// a hash notification. -func (p *peer) SendAnnounce(request announceData) error { - return p2p.Send(p.rw, AnnounceMsg, request) -} - -// SendBlockHeaders sends a batch of block headers to the remote peer. -func (p *peer) SendBlockHeaders(reqID, bv uint64, headers []*types.Header) error { - return sendResponse(p.rw, BlockHeadersMsg, reqID, bv, headers) -} - -// SendBlockBodiesRLP sends a batch of block contents to the remote peer from -// an already RLP encoded format. -func (p *peer) SendBlockBodiesRLP(reqID, bv uint64, bodies []rlp.RawValue) error { - return sendResponse(p.rw, BlockBodiesMsg, reqID, bv, bodies) -} - -// SendCodeRLP sends a batch of arbitrary internal data, corresponding to the -// hashes requested. -func (p *peer) SendCode(reqID, bv uint64, data [][]byte) error { - return sendResponse(p.rw, CodeMsg, reqID, bv, data) -} - -// SendReceiptsRLP sends a batch of transaction receipts, corresponding to the -// ones requested from an already RLP encoded format. -func (p *peer) SendReceiptsRLP(reqID, bv uint64, receipts []rlp.RawValue) error { - return sendResponse(p.rw, ReceiptsMsg, reqID, bv, receipts) -} - -// SendProofs sends a batch of legacy LES/1 merkle proofs, corresponding to the ones requested. -func (p *peer) SendProofs(reqID, bv uint64, proofs proofsData) error { - return sendResponse(p.rw, ProofsV1Msg, reqID, bv, proofs) -} - -// SendProofsV2 sends a batch of merkle proofs, corresponding to the ones requested. -func (p *peer) SendProofsV2(reqID, bv uint64, proofs light.NodeList) error { - return sendResponse(p.rw, ProofsV2Msg, reqID, bv, proofs) -} - -// SendHeaderProofs sends a batch of legacy LES/1 header proofs, corresponding to the ones requested. -func (p *peer) SendHeaderProofs(reqID, bv uint64, proofs []ChtResp) error { - return sendResponse(p.rw, HeaderProofsMsg, reqID, bv, proofs) -} - -// SendHelperTrieProofs sends a batch of HelperTrie proofs, corresponding to the ones requested. -func (p *peer) SendHelperTrieProofs(reqID, bv uint64, resp HelperTrieResps) error { - return sendResponse(p.rw, HelperTrieProofsMsg, reqID, bv, resp) -} - -// SendTxStatus sends a batch of transaction status records, corresponding to the ones requested. -func (p *peer) SendTxStatus(reqID, bv uint64, stats []txStatus) error { - return sendResponse(p.rw, TxStatusMsg, reqID, bv, stats) -} - -// RequestHeadersByHash fetches a batch of blocks' headers corresponding to the -// specified header query, based on the hash of an origin block. -func (p *peer) RequestHeadersByHash(reqID, cost uint64, origin common.Hash, amount int, skip int, reverse bool) error { - p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse) - return sendRequest(p.rw, GetBlockHeadersMsg, reqID, cost, &getBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}) -} - -// RequestHeadersByNumber fetches a batch of blocks' headers corresponding to the -// specified header query, based on the number of an origin block. -func (p *peer) RequestHeadersByNumber(reqID, cost, origin uint64, amount int, skip int, reverse bool) error { - p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse) - return sendRequest(p.rw, GetBlockHeadersMsg, reqID, cost, &getBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}) -} - -// RequestBodies fetches a batch of blocks' bodies corresponding to the hashes -// specified. -func (p *peer) RequestBodies(reqID, cost uint64, hashes []common.Hash) error { - p.Log().Debug("Fetching batch of block bodies", "count", len(hashes)) - return sendRequest(p.rw, GetBlockBodiesMsg, reqID, cost, hashes) -} - -// RequestCode fetches a batch of arbitrary data from a node's known state -// data, corresponding to the specified hashes. -func (p *peer) RequestCode(reqID, cost uint64, reqs []CodeReq) error { - p.Log().Debug("Fetching batch of codes", "count", len(reqs)) - return sendRequest(p.rw, GetCodeMsg, reqID, cost, reqs) -} - -// RequestReceipts fetches a batch of transaction receipts from a remote node. -func (p *peer) RequestReceipts(reqID, cost uint64, hashes []common.Hash) error { - p.Log().Debug("Fetching batch of receipts", "count", len(hashes)) - return sendRequest(p.rw, GetReceiptsMsg, reqID, cost, hashes) -} - -// RequestProofs fetches a batch of merkle proofs from a remote node. -func (p *peer) RequestProofs(reqID, cost uint64, reqs []ProofReq) error { - p.Log().Debug("Fetching batch of proofs", "count", len(reqs)) - switch p.version { - case lpv1: - return sendRequest(p.rw, GetProofsV1Msg, reqID, cost, reqs) - case lpv2: - return sendRequest(p.rw, GetProofsV2Msg, reqID, cost, reqs) - default: - panic(nil) - } -} - -// RequestHelperTrieProofs fetches a batch of HelperTrie merkle proofs from a remote node. -func (p *peer) RequestHelperTrieProofs(reqID, cost uint64, data interface{}) error { - switch p.version { - case lpv1: - reqs, ok := data.([]ChtReq) - if !ok { - return errInvalidHelpTrieReq - } - p.Log().Debug("Fetching batch of header proofs", "count", len(reqs)) - return sendRequest(p.rw, GetHeaderProofsMsg, reqID, cost, reqs) - case lpv2: - reqs, ok := data.([]HelperTrieReq) - if !ok { - return errInvalidHelpTrieReq - } - p.Log().Debug("Fetching batch of HelperTrie proofs", "count", len(reqs)) - return sendRequest(p.rw, GetHelperTrieProofsMsg, reqID, cost, reqs) - default: - panic(nil) - } -} - -// RequestTxStatus fetches a batch of transaction status records from a remote node. -func (p *peer) RequestTxStatus(reqID, cost uint64, txHashes []common.Hash) error { - p.Log().Debug("Requesting transaction status", "count", len(txHashes)) - return sendRequest(p.rw, GetTxStatusMsg, reqID, cost, txHashes) -} - -// SendTxStatus sends a batch of transactions to be added to the remote transaction pool. -func (p *peer) SendTxs(reqID, cost uint64, txs types.Transactions) error { - p.Log().Debug("Fetching batch of transactions", "count", len(txs)) - switch p.version { - case lpv1: - return p2p.Send(p.rw, SendTxMsg, txs) // old message format does not include reqID - case lpv2: - return sendRequest(p.rw, SendTxV2Msg, reqID, cost, txs) - default: - panic(nil) - } -} - -type keyValueEntry struct { - Key string - Value rlp.RawValue -} -type keyValueList []keyValueEntry -type keyValueMap map[string]rlp.RawValue - -func (l keyValueList) add(key string, val interface{}) keyValueList { - var entry keyValueEntry - entry.Key = key - if val == nil { - val = uint64(0) - } - enc, err := rlp.EncodeToBytes(val) - if err == nil { - entry.Value = enc - } - return append(l, entry) -} - -func (l keyValueList) decode() keyValueMap { - m := make(keyValueMap) - for _, entry := range l { - m[entry.Key] = entry.Value - } - return m -} - -func (m keyValueMap) get(key string, val interface{}) error { - enc, ok := m[key] - if !ok { - return errResp(ErrMissingKey, "%s", key) - } - if val == nil { - return nil - } - return rlp.DecodeBytes(enc, val) -} - -func (p *peer) sendReceiveHandshake(sendList keyValueList) (keyValueList, error) { - // Send out own handshake in a new thread - errc := make(chan error, 1) - go func() { - errc <- p2p.Send(p.rw, StatusMsg, sendList) - }() - // In the mean time retrieve the remote status message - msg, err := p.rw.ReadMsg() - if err != nil { - return nil, err - } - if msg.Code != StatusMsg { - return nil, errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg) - } - if msg.Size > ProtocolMaxMsgSize { - return nil, errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) - } - // Decode the handshake - var recvList keyValueList - if err := msg.Decode(&recvList); err != nil { - return nil, errResp(ErrDecode, "msg %v: %v", msg, err) - } - if err := <-errc; err != nil { - return nil, err - } - return recvList, nil -} - -// Handshake executes the les protocol handshake, negotiating version number, -// network IDs, difficulties, head and genesis blocks. -func (p *peer) Handshake(head common.Hash, headNum uint64, genesis common.Hash, server *LesServer) error { - p.lock.Lock() - defer p.lock.Unlock() - - var send keyValueList - send = send.add("protocolVersion", uint64(p.version)) - send = send.add("networkId", p.network) - //send = send.add("headTd", td) - send = send.add("headHash", head) - send = send.add("headNum", headNum) - send = send.add("genesisHash", genesis) - if server != nil { - send = send.add("serveHeaders", nil) - send = send.add("serveChainSince", uint64(0)) - send = send.add("serveStateSince", uint64(0)) - send = send.add("txRelay", nil) - send = send.add("flowControl/BL", server.defParams.BufLimit) - send = send.add("flowControl/MRR", server.defParams.MinRecharge) - list := server.fcCostStats.getCurrentList() - send = send.add("flowControl/MRC", list) - p.fcCosts = list.decode() - } else { - p.requestAnnounceType = announceTypeSimple // set to default until "very light" client mode is implemented - send = send.add("announceType", p.requestAnnounceType) - } - recvList, err := p.sendReceiveHandshake(send) - if err != nil { - return err - } - recv := recvList.decode() - - var rGenesis, rHash common.Hash - var rVersion, rNetwork, rNum uint64 - - if err := recv.get("protocolVersion", &rVersion); err != nil { - return err - } - if err := recv.get("networkId", &rNetwork); err != nil { - return err - } - if err := recv.get("headHash", &rHash); err != nil { - return err - } - if err := recv.get("headNum", &rNum); err != nil { - return err - } - if err := recv.get("genesisHash", &rGenesis); err != nil { - return err - } - - if rGenesis != genesis { - return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", rGenesis[:8], genesis[:8]) - } - if rNetwork != p.network { - return errResp(ErrNetworkIdMismatch, "%d (!= %d)", rNetwork, p.network) - } - if int(rVersion) != p.version { - return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", rVersion, p.version) - } - if server != nil { - // until we have a proper peer connectivity API, allow LES connection to other servers - /*if recv.get("serveStateSince", nil) == nil { - return errResp(ErrUselessPeer, "wanted client, got server") - }*/ - if recv.get("announceType", &p.announceType) != nil { - p.announceType = announceTypeSimple - } - p.fcClient = flowcontrol.NewClientNode(server.fcManager, server.defParams) - } else { - if recv.get("serveChainSince", nil) != nil { - return errResp(ErrUselessPeer, "peer cannot serve chain") - } - if recv.get("serveStateSince", nil) != nil { - return errResp(ErrUselessPeer, "peer cannot serve state") - } - if recv.get("txRelay", nil) != nil { - return errResp(ErrUselessPeer, "peer cannot relay transactions") - } - params := &flowcontrol.ServerParams{} - if err := recv.get("flowControl/BL", ¶ms.BufLimit); err != nil { - return err - } - if err := recv.get("flowControl/MRR", ¶ms.MinRecharge); err != nil { - return err - } - var MRC RequestCostList - if err := recv.get("flowControl/MRC", &MRC); err != nil { - return err - } - p.fcServerParams = params - p.fcServer = flowcontrol.NewServerNode(params) - p.fcCosts = MRC.decode() - } - - p.headInfo = &announceData{Hash: rHash, Number: rNum} - return nil -} - -// String implements fmt.Stringer. -func (p *peer) String() string { - return fmt.Sprintf("Peer %s [%s]", p.id, - fmt.Sprintf("les/%d", p.version), - ) -} - -// peerSetNotify is a callback interface to notify services about added or -// removed peers -type peerSetNotify interface { - registerPeer(*peer) - unregisterPeer(*peer) -} - -// peerSet represents the collection of active peers currently participating in -// the Light Ethereum sub-protocol. -type peerSet struct { - peers map[string]*peer - lock sync.RWMutex - notifyList []peerSetNotify - closed bool -} - -// newPeerSet creates a new peer set to track the active participants. -func newPeerSet() *peerSet { - return &peerSet{ - peers: make(map[string]*peer), - } -} - -// notify adds a service to be notified about added or removed peers -func (ps *peerSet) notify(n peerSetNotify) { - ps.lock.Lock() - ps.notifyList = append(ps.notifyList, n) - peers := make([]*peer, 0, len(ps.peers)) - for _, p := range ps.peers { - peers = append(peers, p) - } - ps.lock.Unlock() - - for _, p := range peers { - n.registerPeer(p) - } -} - -// Register injects a new peer into the working set, or returns an error if the -// peer is already known. -func (ps *peerSet) Register(p *peer) error { - ps.lock.Lock() - if ps.closed { - ps.lock.Unlock() - return errClosed - } - if _, ok := ps.peers[p.id]; ok { - ps.lock.Unlock() - return errAlreadyRegistered - } - ps.peers[p.id] = p - p.sendQueue = newExecQueue(100) - peers := make([]peerSetNotify, len(ps.notifyList)) - copy(peers, ps.notifyList) - ps.lock.Unlock() - - for _, n := range peers { - n.registerPeer(p) - } - return nil -} - -// Unregister removes a remote peer from the active set, disabling any further -// actions to/from that particular entity. It also initiates disconnection at the networking layer. -func (ps *peerSet) Unregister(id string) error { - ps.lock.Lock() - if p, ok := ps.peers[id]; !ok { - ps.lock.Unlock() - return errNotRegistered - } else { - delete(ps.peers, id) - peers := make([]peerSetNotify, len(ps.notifyList)) - copy(peers, ps.notifyList) - ps.lock.Unlock() - - for _, n := range peers { - n.unregisterPeer(p) - } - p.sendQueue.quit() - p.Peer.Disconnect(p2p.DiscUselessPeer) - return nil - } -} - -// AllPeerIDs returns a list of all registered peer IDs -func (ps *peerSet) AllPeerIDs() []string { - ps.lock.RLock() - defer ps.lock.RUnlock() - - res := make([]string, len(ps.peers)) - idx := 0 - for id := range ps.peers { - res[idx] = id - idx++ - } - return res -} - -// Peer retrieves the registered peer with the given id. -func (ps *peerSet) Peer(id string) *peer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - return ps.peers[id] -} - -// Len returns if the current number of peers in the set. -func (ps *peerSet) Len() int { - ps.lock.RLock() - defer ps.lock.RUnlock() - - return len(ps.peers) -} - -// BestPeer retrieves the known peer with the currently highest total difficulty. -func (ps *peerSet) BestPeer() *peer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - var ( - bestPeer *peer - bestBn uint64 - ) - for _, p := range ps.peers { - if bn := p.headInfo.Number; bestPeer == nil || bn > bestBn { - bestPeer, bestBn = p, bn - } - } - return bestPeer -} - -// AllPeers returns all peers in a list -func (ps *peerSet) AllPeers() []*peer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - list := make([]*peer, len(ps.peers)) - i := 0 - for _, peer := range ps.peers { - list[i] = peer - i++ - } - return list -} - -// Close disconnects all peers. -// No new peers can be registered after Close has returned. -func (ps *peerSet) Close() { - ps.lock.Lock() - defer ps.lock.Unlock() - - for _, p := range ps.peers { - p.Disconnect(p2p.DiscQuitting) - } - ps.closed = true -} diff --git a/les/protocol.go b/les/protocol.go deleted file mode 100644 index 673719e64d..0000000000 --- a/les/protocol.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2016 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 . - -// Package les implements the Light Ethereum Subprotocol. -package les - -import ( - "crypto/ecdsa" - "errors" - "fmt" - "io" - - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -// Constants to match up protocol versions and messages -const ( - lpv1 = 1 - lpv2 = 2 -) - -// Supported versions of the les protocol (first is primary) -var ( - ClientProtocolVersions = []uint{lpv2, lpv1} - ServerProtocolVersions = []uint{lpv2, lpv1} - AdvertiseProtocolVersions = []uint{lpv2} // clients are searching for the first advertised protocol in the list -) - -// Number of implemented message corresponding to different protocol versions. -var ProtocolLengths = map[uint]uint64{lpv1: 15, lpv2: 22} - -const ( - NetworkId = 1 - ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message -) - -// les protocol message codes -const ( - // Protocol messages belonging to LPV1 - StatusMsg = 0x00 - AnnounceMsg = 0x01 - GetBlockHeadersMsg = 0x02 - BlockHeadersMsg = 0x03 - GetBlockBodiesMsg = 0x04 - BlockBodiesMsg = 0x05 - GetReceiptsMsg = 0x06 - ReceiptsMsg = 0x07 - GetProofsV1Msg = 0x08 - ProofsV1Msg = 0x09 - GetCodeMsg = 0x0a - CodeMsg = 0x0b - SendTxMsg = 0x0c - GetHeaderProofsMsg = 0x0d - HeaderProofsMsg = 0x0e - // Protocol messages belonging to LPV2 - GetProofsV2Msg = 0x0f - ProofsV2Msg = 0x10 - GetHelperTrieProofsMsg = 0x11 - HelperTrieProofsMsg = 0x12 - SendTxV2Msg = 0x13 - GetTxStatusMsg = 0x14 - TxStatusMsg = 0x15 -) - -type errCode int - -const ( - ErrMsgTooLarge = iota - ErrDecode - ErrInvalidMsgCode - ErrProtocolVersionMismatch - ErrNetworkIdMismatch - ErrGenesisBlockMismatch - ErrNoStatusMsg - ErrExtraStatusMsg - ErrSuspendedPeer - ErrUselessPeer - ErrRequestRejected - ErrUnexpectedResponse - ErrInvalidResponse - ErrTooManyTimeouts - ErrMissingKey -) - -func (e errCode) String() string { - return errorToString[int(e)] -} - -// XXX change once legacy code is out -var errorToString = map[int]string{ - ErrMsgTooLarge: "Message too long", - ErrDecode: "Invalid message", - ErrInvalidMsgCode: "Invalid message code", - ErrProtocolVersionMismatch: "Protocol version mismatch", - ErrNetworkIdMismatch: "NetworkId mismatch", - ErrGenesisBlockMismatch: "Genesis block mismatch", - ErrNoStatusMsg: "No status message", - ErrExtraStatusMsg: "Extra status message", - ErrSuspendedPeer: "Suspended peer", - ErrRequestRejected: "Request rejected", - ErrUnexpectedResponse: "Unexpected response", - ErrInvalidResponse: "Invalid response", - ErrTooManyTimeouts: "Too many request timeouts", - ErrMissingKey: "Key missing from list", -} - -type announceBlock struct { - Hash common.Hash // Hash of one particular block being announced - Number uint64 // Number of one particular block being announced - //Td *big.Int // Total difficulty of one particular block being announced -} - -// announceData is the network packet for the block announcements. -type announceData struct { - Hash common.Hash // Hash of one particular block being announced - Number uint64 // Number of one particular block being announced - //Td *big.Int // Total difficulty of one particular block being announced - ReorgDepth uint64 - Update keyValueList -} - -// sign adds a signature to the block announcement by the given privKey -func (a *announceData) sign(privKey *ecdsa.PrivateKey) { - rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number}) - sig, _ := crypto.Sign(crypto.Keccak256(rlp), privKey) - a.Update = a.Update.add("sign", sig) -} - -// checkSignature verifies if the block announcement has a valid signature by the given pubKey -func (a *announceData) checkSignature(id enode.ID) error { - var sig []byte - if err := a.Update.decode().get("sign", &sig); err != nil { - return err - } - rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number}) - recPubkey, err := crypto.SigToPub(crypto.Keccak256(rlp), sig) - if err != nil { - return err - } - if id == enode.PubkeyToIDV4(recPubkey) { - return nil - } - return errors.New("wrong signature") -} - -type blockInfo struct { - Hash common.Hash // Hash of one particular block being announced - Number uint64 // Number of one particular block being announced - //Td *big.Int // Total difficulty of one particular block being announced -} - -// getBlockHeadersData represents a block header query. -type getBlockHeadersData struct { - Origin hashOrNumber // Block from which to retrieve headers - Amount uint64 // Maximum number of headers to retrieve - Skip uint64 // Blocks to skip between consecutive headers - Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis) -} - -// hashOrNumber is a combined field for specifying an origin block. -type hashOrNumber struct { - Hash common.Hash // Block hash from which to retrieve headers (excludes Number) - Number uint64 // Block hash from which to retrieve headers (excludes Hash) -} - -// EncodeRLP is a specialized encoder for hashOrNumber to encode only one of the -// two contained union fields. -func (hn *hashOrNumber) EncodeRLP(w io.Writer) error { - if hn.Hash == (common.Hash{}) { - return rlp.Encode(w, hn.Number) - } - if hn.Number != 0 { - return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number) - } - return rlp.Encode(w, hn.Hash) -} - -// DecodeRLP is a specialized decoder for hashOrNumber to decode the contents -// into either a block hash or a block number. -func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error { - _, size, _ := s.Kind() - origin, err := s.Raw() - if err == nil { - switch { - case size == 32: - err = rlp.DecodeBytes(origin, &hn.Hash) - case size <= 8: - err = rlp.DecodeBytes(origin, &hn.Number) - default: - err = fmt.Errorf("invalid input size %d for origin", size) - } - } - return err -} - -// CodeData is the network response packet for a node data retrieval. -type CodeData []struct { - Value []byte -} - -type proofsData [][]rlp.RawValue - -type txStatus struct { - Status core.TxStatus - Lookup *rawdb.LegacyTxLookupEntry `rlp:"nil"` - Error string -} diff --git a/les/randselect.go b/les/randselect.go deleted file mode 100644 index 1cc1d3d3e0..0000000000 --- a/les/randselect.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2016 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 . - -// Package les implements the Light Ethereum Subprotocol. -package les - -import ( - "math/rand" -) - -// wrsItem interface should be implemented by any entries that are to be selected from -// a weightedRandomSelect set. Note that recalculating monotonously decreasing item -// weights on-demand (without constantly calling update) is allowed -type wrsItem interface { - Weight() int64 -} - -// weightedRandomSelect is capable of weighted random selection from a set of items -type weightedRandomSelect struct { - root *wrsNode - idx map[wrsItem]int -} - -// newWeightedRandomSelect returns a new weightedRandomSelect structure -func newWeightedRandomSelect() *weightedRandomSelect { - return &weightedRandomSelect{root: &wrsNode{maxItems: wrsBranches}, idx: make(map[wrsItem]int)} -} - -// update updates an item's weight, adds it if it was non-existent or removes it if -// the new weight is zero. Note that explicitly updating decreasing weights is not necessary. -func (w *weightedRandomSelect) update(item wrsItem) { - w.setWeight(item, item.Weight()) -} - -// remove removes an item from the set -func (w *weightedRandomSelect) remove(item wrsItem) { - w.setWeight(item, 0) -} - -// setWeight sets an item's weight to a specific value (removes it if zero) -func (w *weightedRandomSelect) setWeight(item wrsItem, weight int64) { - idx, ok := w.idx[item] - if ok { - w.root.setWeight(idx, weight) - if weight == 0 { - delete(w.idx, item) - } - } else { - if weight != 0 { - if w.root.itemCnt == w.root.maxItems { - // add a new level - newRoot := &wrsNode{sumWeight: w.root.sumWeight, itemCnt: w.root.itemCnt, level: w.root.level + 1, maxItems: w.root.maxItems * wrsBranches} - newRoot.items[0] = w.root - newRoot.weights[0] = w.root.sumWeight - w.root = newRoot - } - w.idx[item] = w.root.insert(item, weight) - } - } -} - -// choose randomly selects an item from the set, with a chance proportional to its -// current weight. If the weight of the chosen element has been decreased since the -// last stored value, returns it with a newWeight/oldWeight chance, otherwise just -// updates its weight and selects another one -func (w *weightedRandomSelect) choose() wrsItem { - for { - if w.root.sumWeight == 0 { - return nil - } - val := rand.Int63n(w.root.sumWeight) - choice, lastWeight := w.root.choose(val) - weight := choice.Weight() - if weight != lastWeight { - w.setWeight(choice, weight) - } - if weight >= lastWeight || rand.Int63n(lastWeight) < weight { - return choice - } - } -} - -const wrsBranches = 8 // max number of branches in the wrsNode tree - -// wrsNode is a node of a tree structure that can store wrsItems or further wrsNodes. -type wrsNode struct { - items [wrsBranches]interface{} - weights [wrsBranches]int64 - sumWeight int64 - level, itemCnt, maxItems int -} - -// insert recursively inserts a new item to the tree and returns the item index -func (n *wrsNode) insert(item wrsItem, weight int64) int { - branch := 0 - for n.items[branch] != nil && (n.level == 0 || n.items[branch].(*wrsNode).itemCnt == n.items[branch].(*wrsNode).maxItems) { - branch++ - if branch == wrsBranches { - panic(nil) - } - } - n.itemCnt++ - n.sumWeight += weight - n.weights[branch] += weight - if n.level == 0 { - n.items[branch] = item - return branch - } - var subNode *wrsNode - if n.items[branch] == nil { - subNode = &wrsNode{maxItems: n.maxItems / wrsBranches, level: n.level - 1} - n.items[branch] = subNode - } else { - subNode = n.items[branch].(*wrsNode) - } - subIdx := subNode.insert(item, weight) - return subNode.maxItems*branch + subIdx -} - -// setWeight updates the weight of a certain item (which should exist) and returns -// the change of the last weight value stored in the tree -func (n *wrsNode) setWeight(idx int, weight int64) int64 { - if n.level == 0 { - oldWeight := n.weights[idx] - n.weights[idx] = weight - diff := weight - oldWeight - n.sumWeight += diff - if weight == 0 { - n.items[idx] = nil - n.itemCnt-- - } - return diff - } - branchItems := n.maxItems / wrsBranches - branch := idx / branchItems - diff := n.items[branch].(*wrsNode).setWeight(idx-branch*branchItems, weight) - n.weights[branch] += diff - n.sumWeight += diff - if weight == 0 { - n.itemCnt-- - } - return diff -} - -// choose recursively selects an item from the tree and returns it along with its weight -func (n *wrsNode) choose(val int64) (wrsItem, int64) { - for i, w := range n.weights { - if val < w { - if n.level == 0 { - return n.items[i].(wrsItem), n.weights[i] - } - return n.items[i].(*wrsNode).choose(val) - } - val -= w - } - panic(nil) -} diff --git a/les/randselect_test.go b/les/randselect_test.go deleted file mode 100644 index 9ae7726ddd..0000000000 --- a/les/randselect_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2016 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 . - -package les - -import ( - "math/rand" - "testing" -) - -type testWrsItem struct { - idx int - widx *int -} - -func (t *testWrsItem) Weight() int64 { - w := *t.widx - if w == -1 || w == t.idx { - return int64(t.idx + 1) - } - return 0 -} - -func TestWeightedRandomSelect(t *testing.T) { - testFn := func(cnt int) { - s := newWeightedRandomSelect() - w := -1 - list := make([]testWrsItem, cnt) - for i := range list { - list[i] = testWrsItem{idx: i, widx: &w} - s.update(&list[i]) - } - w = rand.Intn(cnt) - c := s.choose() - if c == nil { - t.Errorf("expected item, got nil") - } else { - if c.(*testWrsItem).idx != w { - t.Errorf("expected another item") - } - } - w = -2 - if s.choose() != nil { - t.Errorf("expected nil, got item") - } - } - testFn(1) - testFn(10) - testFn(100) - testFn(1000) - testFn(10000) - testFn(100000) - testFn(1000000) -} diff --git a/les/request_test.go b/les/request_test.go deleted file mode 100644 index 708cb7a117..0000000000 --- a/les/request_test.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2016 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 . - -package les - -import ( - "context" - "testing" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/light" -) - -var testBankSecureTrieKey = secAddr(testBankAddress) - -func secAddr(addr common.Address) []byte { - return crypto.Keccak256(addr[:]) -} - -type accessTestFn func(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest - -func TestBlockAccessLes1(t *testing.T) { testAccess(t, 1, tfBlockAccess) } - -func TestBlockAccessLes2(t *testing.T) { testAccess(t, 2, tfBlockAccess) } - -func tfBlockAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest { - return &light.BlockRequest{Hash: bhash, Number: number} -} - -func TestReceiptsAccessLes1(t *testing.T) { testAccess(t, 1, tfReceiptsAccess) } - -func TestReceiptsAccessLes2(t *testing.T) { testAccess(t, 2, tfReceiptsAccess) } - -func tfReceiptsAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest { - return &light.ReceiptsRequest{Hash: bhash, Number: number} -} - -func TestTrieEntryAccessLes1(t *testing.T) { testAccess(t, 1, tfTrieEntryAccess) } - -func TestTrieEntryAccessLes2(t *testing.T) { testAccess(t, 2, tfTrieEntryAccess) } - -func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest { - if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { - return &light.TrieRequest{Id: light.StateTrieID(rawdb.ReadHeader(db, bhash, *number)), Key: testBankSecureTrieKey} - } - return nil -} - -func TestCodeAccessLes1(t *testing.T) { testAccess(t, 1, tfCodeAccess) } - -func TestCodeAccessLes2(t *testing.T) { testAccess(t, 2, tfCodeAccess) } - -func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrRequest { - number := rawdb.ReadHeaderNumber(db, bhash) - if number != nil { - return nil - } - header := rawdb.ReadHeader(db, bhash, *number) - if header.Number.Uint64() < testContractDeployed { - return nil - } - sti := light.StateTrieID(header) - ci := light.StorageTrieID(sti, crypto.Keccak256Hash(testContractAddr[:]), common.Hash{}) - return &light.CodeRequest{Id: ci, Hash: crypto.Keccak256Hash(testContractCodeDeployed)} -} - -func testAccess(t *testing.T, protocol int, fn accessTestFn) { - // Assemble the test environment - server, client, tearDown := newClientServerEnv(t, 2, protocol, nil, true) - defer tearDown() - client.pm.synchronise(client.rPeer) - - test := func(expFail uint64) { - for i := uint64(0); i <= server.pm.blockchain.CurrentHeader().Number.Uint64(); i++ { - bhash := rawdb.ReadCanonicalHash(server.db, i) - if req := fn(client.db, bhash, i); req != nil { - ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) - defer cancel() - err := client.pm.odr.Retrieve(ctx, req) - got := err == nil - exp := i < expFail - if exp && !got { - t.Errorf("object retrieval failed") - } - if !exp && got { - t.Errorf("unexpected object retrieval success") - } - } - } - } - - // temporarily remove peer to test odr fails - client.peers.Unregister(client.rPeer.id) - time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed - // expect retrievals to fail (except genesis block) without a les peer - test(0) - - client.peers.Register(client.rPeer) - time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed - client.rPeer.lock.Lock() - client.rPeer.hasBlock = func(common.Hash, uint64) bool { return true } - client.rPeer.lock.Unlock() - // expect all retrievals to pass - test(5) -} diff --git a/les/retrieve.go b/les/retrieve.go deleted file mode 100644 index d4bb825ba9..0000000000 --- a/les/retrieve.go +++ /dev/null @@ -1,414 +0,0 @@ -// Copyright 2017 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 . - -// Package light implements on-demand retrieval capable state and chain objects -// for the Ethereum Light Client. -package les - -import ( - "context" - "crypto/rand" - "encoding/binary" - "fmt" - "sync" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common/mclock" - "github.com/AlayaNetwork/Alaya-Go/light" -) - -var ( - retryQueue = time.Millisecond * 100 - softRequestTimeout = time.Millisecond * 500 - hardRequestTimeout = time.Second * 10 -) - -// retrieveManager is a layer on top of requestDistributor which takes care of -// matching replies by request ID and handles timeouts and resends if necessary. -type retrieveManager struct { - dist *requestDistributor - peers *peerSet - serverPool peerSelector - - lock sync.RWMutex - sentReqs map[uint64]*sentReq -} - -// validatorFunc is a function that processes a reply message -type validatorFunc func(distPeer, *Msg) error - -// peerSelector receives feedback info about response times and timeouts -type peerSelector interface { - adjustResponseTime(*poolEntry, time.Duration, bool) -} - -// sentReq represents a request sent and tracked by retrieveManager -type sentReq struct { - rm *retrieveManager - req *distReq - id uint64 - validate validatorFunc - - eventsCh chan reqPeerEvent - stopCh chan struct{} - stopped bool - err error - - lock sync.RWMutex // protect access to sentTo map - sentTo map[distPeer]sentReqToPeer - - lastReqQueued bool // last request has been queued but not sent - lastReqSentTo distPeer // if not nil then last request has been sent to given peer but not timed out - reqSrtoCount int // number of requests that reached soft (but not hard) timeout -} - -// sentReqToPeer notifies the request-from-peer goroutine (tryRequest) about a response -// delivered by the given peer. Only one delivery is allowed per request per peer, -// after which delivered is set to true, the validity of the response is sent on the -// valid channel and no more responses are accepted. -type sentReqToPeer struct { - delivered bool - valid chan bool -} - -// reqPeerEvent is sent by the request-from-peer goroutine (tryRequest) to the -// request state machine (retrieveLoop) through the eventsCh channel. -type reqPeerEvent struct { - event int - peer distPeer -} - -const ( - rpSent = iota // if peer == nil, not sent (no suitable peers) - rpSoftTimeout - rpHardTimeout - rpDeliveredValid - rpDeliveredInvalid -) - -// newRetrieveManager creates the retrieve manager -func newRetrieveManager(peers *peerSet, dist *requestDistributor, serverPool peerSelector) *retrieveManager { - return &retrieveManager{ - peers: peers, - dist: dist, - serverPool: serverPool, - sentReqs: make(map[uint64]*sentReq), - } -} - -// retrieve sends a request (to multiple peers if necessary) and waits for an answer -// that is delivered through the deliver function and successfully validated by the -// validator callback. It returns when a valid answer is delivered or the context is -// cancelled. -func (rm *retrieveManager) retrieve(ctx context.Context, reqID uint64, req *distReq, val validatorFunc, shutdown chan struct{}) error { - sentReq := rm.sendReq(reqID, req, val) - select { - case <-sentReq.stopCh: - case <-ctx.Done(): - sentReq.stop(ctx.Err()) - case <-shutdown: - sentReq.stop(fmt.Errorf("Client is shutting down")) - } - return sentReq.getError() -} - -// sendReq starts a process that keeps trying to retrieve a valid answer for a -// request from any suitable peers until stopped or succeeded. -func (rm *retrieveManager) sendReq(reqID uint64, req *distReq, val validatorFunc) *sentReq { - r := &sentReq{ - rm: rm, - req: req, - id: reqID, - sentTo: make(map[distPeer]sentReqToPeer), - stopCh: make(chan struct{}), - eventsCh: make(chan reqPeerEvent, 10), - validate: val, - } - - canSend := req.canSend - req.canSend = func(p distPeer) bool { - // add an extra check to canSend: the request has not been sent to the same peer before - r.lock.RLock() - _, sent := r.sentTo[p] - r.lock.RUnlock() - return !sent && canSend(p) - } - - request := req.request - req.request = func(p distPeer) func() { - // before actually sending the request, put an entry into the sentTo map - r.lock.Lock() - r.sentTo[p] = sentReqToPeer{false, make(chan bool, 1)} - r.lock.Unlock() - return request(p) - } - rm.lock.Lock() - rm.sentReqs[reqID] = r - rm.lock.Unlock() - - go r.retrieveLoop() - return r -} - -// deliver is called by the LES protocol manager to deliver reply messages to waiting requests -func (rm *retrieveManager) deliver(peer distPeer, msg *Msg) error { - rm.lock.RLock() - req, ok := rm.sentReqs[msg.ReqID] - rm.lock.RUnlock() - - if ok { - return req.deliver(peer, msg) - } - return errResp(ErrUnexpectedResponse, "reqID = %v", msg.ReqID) -} - -// reqStateFn represents a state of the retrieve loop state machine -type reqStateFn func() reqStateFn - -// retrieveLoop is the retrieval state machine event loop -func (r *sentReq) retrieveLoop() { - go r.tryRequest() - r.lastReqQueued = true - state := r.stateRequesting - - for state != nil { - state = state() - } - - r.rm.lock.Lock() - delete(r.rm.sentReqs, r.id) - r.rm.lock.Unlock() -} - -// stateRequesting: a request has been queued or sent recently; when it reaches soft timeout, -// a new request is sent to a new peer -func (r *sentReq) stateRequesting() reqStateFn { - select { - case ev := <-r.eventsCh: - r.update(ev) - switch ev.event { - case rpSent: - if ev.peer == nil { - // request send failed, no more suitable peers - if r.waiting() { - // we are already waiting for sent requests which may succeed so keep waiting - return r.stateNoMorePeers - } - // nothing to wait for, no more peers to ask, return with error - r.stop(light.ErrNoPeers) - // no need to go to stopped state because waiting() already returned false - return nil - } - case rpSoftTimeout: - // last request timed out, try asking a new peer - go r.tryRequest() - r.lastReqQueued = true - return r.stateRequesting - case rpDeliveredInvalid: - // if it was the last sent request (set to nil by update) then start a new one - if !r.lastReqQueued && r.lastReqSentTo == nil { - go r.tryRequest() - r.lastReqQueued = true - } - return r.stateRequesting - case rpDeliveredValid: - r.stop(nil) - return r.stateStopped - } - return r.stateRequesting - case <-r.stopCh: - return r.stateStopped - } -} - -// stateNoMorePeers: could not send more requests because no suitable peers are available. -// Peers may become suitable for a certain request later or new peers may appear so we -// keep trying. -func (r *sentReq) stateNoMorePeers() reqStateFn { - select { - case <-time.After(retryQueue): - go r.tryRequest() - r.lastReqQueued = true - return r.stateRequesting - case ev := <-r.eventsCh: - r.update(ev) - if ev.event == rpDeliveredValid { - r.stop(nil) - return r.stateStopped - } - if r.waiting() { - return r.stateNoMorePeers - } - r.stop(light.ErrNoPeers) - return nil - case <-r.stopCh: - return r.stateStopped - } -} - -// stateStopped: request succeeded or cancelled, just waiting for some peers -// to either answer or time out hard -func (r *sentReq) stateStopped() reqStateFn { - for r.waiting() { - r.update(<-r.eventsCh) - } - return nil -} - -// update updates the queued/sent flags and timed out peers counter according to the event -func (r *sentReq) update(ev reqPeerEvent) { - switch ev.event { - case rpSent: - r.lastReqQueued = false - r.lastReqSentTo = ev.peer - case rpSoftTimeout: - r.lastReqSentTo = nil - r.reqSrtoCount++ - case rpHardTimeout: - r.reqSrtoCount-- - case rpDeliveredValid, rpDeliveredInvalid: - if ev.peer == r.lastReqSentTo { - r.lastReqSentTo = nil - } else { - r.reqSrtoCount-- - } - } -} - -// waiting returns true if the retrieval mechanism is waiting for an answer from -// any peer -func (r *sentReq) waiting() bool { - return r.lastReqQueued || r.lastReqSentTo != nil || r.reqSrtoCount > 0 -} - -// tryRequest tries to send the request to a new peer and waits for it to either -// succeed or time out if it has been sent. It also sends the appropriate reqPeerEvent -// messages to the request's event channel. -func (r *sentReq) tryRequest() { - sent := r.rm.dist.queue(r.req) - var p distPeer - select { - case p = <-sent: - case <-r.stopCh: - if r.rm.dist.cancel(r.req) { - p = nil - } else { - p = <-sent - } - } - - r.eventsCh <- reqPeerEvent{rpSent, p} - if p == nil { - return - } - - reqSent := mclock.Now() - srto, hrto := false, false - - r.lock.RLock() - s, ok := r.sentTo[p] - r.lock.RUnlock() - if !ok { - panic(nil) - } - - defer func() { - // send feedback to server pool and remove peer if hard timeout happened - pp, ok := p.(*peer) - if ok && r.rm.serverPool != nil { - respTime := time.Duration(mclock.Now() - reqSent) - r.rm.serverPool.adjustResponseTime(pp.poolEntry, respTime, srto) - } - if hrto { - pp.Log().Debug("Request timed out hard") - if r.rm.peers != nil { - r.rm.peers.Unregister(pp.id) - } - } - - r.lock.Lock() - delete(r.sentTo, p) - r.lock.Unlock() - }() - - select { - case ok := <-s.valid: - if ok { - r.eventsCh <- reqPeerEvent{rpDeliveredValid, p} - } else { - r.eventsCh <- reqPeerEvent{rpDeliveredInvalid, p} - } - return - case <-time.After(softRequestTimeout): - srto = true - r.eventsCh <- reqPeerEvent{rpSoftTimeout, p} - } - - select { - case ok := <-s.valid: - if ok { - r.eventsCh <- reqPeerEvent{rpDeliveredValid, p} - } else { - r.eventsCh <- reqPeerEvent{rpDeliveredInvalid, p} - } - case <-time.After(hardRequestTimeout): - hrto = true - r.eventsCh <- reqPeerEvent{rpHardTimeout, p} - } -} - -// deliver a reply belonging to this request -func (r *sentReq) deliver(peer distPeer, msg *Msg) error { - r.lock.Lock() - defer r.lock.Unlock() - - s, ok := r.sentTo[peer] - if !ok || s.delivered { - return errResp(ErrUnexpectedResponse, "reqID = %v", msg.ReqID) - } - valid := r.validate(peer, msg) == nil - r.sentTo[peer] = sentReqToPeer{true, s.valid} - s.valid <- valid - if !valid { - return errResp(ErrInvalidResponse, "reqID = %v", msg.ReqID) - } - return nil -} - -// stop stops the retrieval process and sets an error code that will be returned -// by getError -func (r *sentReq) stop(err error) { - r.lock.Lock() - if !r.stopped { - r.stopped = true - r.err = err - close(r.stopCh) - } - r.lock.Unlock() -} - -// getError returns any retrieval error (either internally generated or set by the -// stop function) after stopCh has been closed -func (r *sentReq) getError() error { - return r.err -} - -// genReqID generates a new random request ID -func genReqID() uint64 { - var rnd [8]byte - rand.Read(rnd[:]) - return binary.BigEndian.Uint64(rnd[:]) -} diff --git a/les/server.go b/les/server.go deleted file mode 100644 index 3c490b0265..0000000000 --- a/les/server.go +++ /dev/null @@ -1,380 +0,0 @@ -// Copyright 2016 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 . - -// Package les implements the Light Ethereum Subprotocol. -package les - -import ( - "crypto/ecdsa" - "encoding/binary" - "math" - "sync" - - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/eth" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/les/flowcontrol" - "github.com/AlayaNetwork/Alaya-Go/light" - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discv5" - "github.com/AlayaNetwork/Alaya-Go/params" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -type LesServer struct { - lesCommons - - fcManager *flowcontrol.ClientManager // nil if our node is client only - fcCostStats *requestCostStats - defParams *flowcontrol.ServerParams - lesTopics []discv5.Topic - privateKey *ecdsa.PrivateKey - quitSync chan struct{} -} - -func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) { - quitSync := make(chan struct{}) - pm, err := NewProtocolManager(eth.BlockChain().Config(), light.DefaultServerIndexerConfig, false, config.NetworkId, eth.EventMux(), eth.Engine(), newPeerSet(), eth.BlockChain(), eth.TxPool(), eth.ChainDb(), nil, nil, nil, quitSync, new(sync.WaitGroup)) - if err != nil { - return nil, err - } - - lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions)) - for i, pv := range AdvertiseProtocolVersions { - lesTopics[i] = lesTopic(eth.BlockChain().Genesis().Hash(), pv) - } - - srv := &LesServer{ - lesCommons: lesCommons{ - config: config, - chainDb: eth.ChainDb(), - iConfig: light.DefaultServerIndexerConfig, - chtIndexer: light.NewChtIndexer(eth.ChainDb(), nil, params.CHTFrequency, params.HelperTrieProcessConfirmations), - bloomTrieIndexer: light.NewBloomTrieIndexer(eth.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency), - protocolManager: pm, - }, - quitSync: quitSync, - lesTopics: lesTopics, - } - - logger := log.New() - chtSectionCount, _, _ := srv.chtIndexer.Sections() - if chtSectionCount != 0 { - chtLastSection := chtSectionCount - 1 - chtSectionHead := srv.chtIndexer.SectionHead(chtLastSection) - chtRoot := light.GetChtRoot(pm.chainDb, chtLastSection, chtSectionHead) - logger.Info("Loaded CHT", "section", chtLastSection, "head", chtSectionHead, "root", chtRoot) - } - bloomTrieSectionCount, _, _ := srv.bloomTrieIndexer.Sections() - if bloomTrieSectionCount != 0 { - bloomTrieLastSection := bloomTrieSectionCount - 1 - bloomTrieSectionHead := srv.bloomTrieIndexer.SectionHead(bloomTrieLastSection) - bloomTrieRoot := light.GetBloomTrieRoot(pm.chainDb, bloomTrieLastSection, bloomTrieSectionHead) - logger.Info("Loaded bloom trie", "section", bloomTrieLastSection, "head", bloomTrieSectionHead, "root", bloomTrieRoot) - } - - srv.chtIndexer.Start(eth.BlockChain()) - pm.server = srv - - srv.defParams = &flowcontrol.ServerParams{ - BufLimit: 300000000, - MinRecharge: 50000, - } - srv.fcManager = flowcontrol.NewClientManager(uint64(config.LightServ), 10, 1000000000) - srv.fcCostStats = newCostStats(eth.ChainDb()) - return srv, nil -} - -func (s *LesServer) Protocols() []p2p.Protocol { - return s.makeProtocols(ServerProtocolVersions) -} - -// Start starts the LES server -func (s *LesServer) Start(srvr *p2p.Server) { - s.protocolManager.Start(s.config.LightPeers) - if srvr.DiscV5 != nil { - for _, topic := range s.lesTopics { - topic := topic - go func() { - logger := log.New("topic", topic) - logger.Info("Starting topic registration") - defer logger.Info("Terminated topic registration") - - srvr.DiscV5.RegisterTopic(topic, s.quitSync) - }() - } - } - s.privateKey = srvr.PrivateKey - s.protocolManager.blockLoop() -} - -func (s *LesServer) SetBloomBitsIndexer(bloomIndexer *core.ChainIndexer) { - bloomIndexer.AddChildIndexer(s.bloomTrieIndexer) -} - -// Stop stops the LES service -func (s *LesServer) Stop() { - s.chtIndexer.Close() - // bloom trie indexer is closed by parent bloombits indexer - s.fcCostStats.store() - s.fcManager.Stop() - go func() { - <-s.protocolManager.noMorePeers - }() - s.protocolManager.Stop() -} - -type requestCosts struct { - baseCost, reqCost uint64 -} - -type requestCostTable map[uint64]*requestCosts - -type RequestCostList []struct { - MsgCode, BaseCost, ReqCost uint64 -} - -func (list RequestCostList) decode() requestCostTable { - table := make(requestCostTable) - for _, e := range list { - table[e.MsgCode] = &requestCosts{ - baseCost: e.BaseCost, - reqCost: e.ReqCost, - } - } - return table -} - -type linReg struct { - sumX, sumY, sumXX, sumXY float64 - cnt uint64 -} - -const linRegMaxCnt = 100000 - -func (l *linReg) add(x, y float64) { - if l.cnt >= linRegMaxCnt { - sub := float64(l.cnt+1-linRegMaxCnt) / linRegMaxCnt - l.sumX -= l.sumX * sub - l.sumY -= l.sumY * sub - l.sumXX -= l.sumXX * sub - l.sumXY -= l.sumXY * sub - l.cnt = linRegMaxCnt - 1 - } - l.cnt++ - l.sumX += x - l.sumY += y - l.sumXX += x * x - l.sumXY += x * y -} - -func (l *linReg) calc() (b, m float64) { - if l.cnt == 0 { - return 0, 0 - } - cnt := float64(l.cnt) - d := cnt*l.sumXX - l.sumX*l.sumX - if d < 0.001 { - return l.sumY / cnt, 0 - } - m = (cnt*l.sumXY - l.sumX*l.sumY) / d - b = (l.sumY / cnt) - (m * l.sumX / cnt) - return b, m -} - -func (l *linReg) toBytes() []byte { - var arr [40]byte - binary.BigEndian.PutUint64(arr[0:8], math.Float64bits(l.sumX)) - binary.BigEndian.PutUint64(arr[8:16], math.Float64bits(l.sumY)) - binary.BigEndian.PutUint64(arr[16:24], math.Float64bits(l.sumXX)) - binary.BigEndian.PutUint64(arr[24:32], math.Float64bits(l.sumXY)) - binary.BigEndian.PutUint64(arr[32:40], l.cnt) - return arr[:] -} - -func linRegFromBytes(data []byte) *linReg { - if len(data) != 40 { - return nil - } - l := &linReg{} - l.sumX = math.Float64frombits(binary.BigEndian.Uint64(data[0:8])) - l.sumY = math.Float64frombits(binary.BigEndian.Uint64(data[8:16])) - l.sumXX = math.Float64frombits(binary.BigEndian.Uint64(data[16:24])) - l.sumXY = math.Float64frombits(binary.BigEndian.Uint64(data[24:32])) - l.cnt = binary.BigEndian.Uint64(data[32:40]) - return l -} - -type requestCostStats struct { - lock sync.RWMutex - db ethdb.Database - stats map[uint64]*linReg -} - -type requestCostStatsRlp []struct { - MsgCode uint64 - Data []byte -} - -var rcStatsKey = []byte("_requestCostStats") - -func newCostStats(db ethdb.Database) *requestCostStats { - stats := make(map[uint64]*linReg) - for _, code := range reqList { - stats[code] = &linReg{cnt: 100} - } - - if db != nil { - data, err := db.Get(rcStatsKey) - var statsRlp requestCostStatsRlp - if err == nil { - err = rlp.DecodeBytes(data, &statsRlp) - } - if err == nil { - for _, r := range statsRlp { - if stats[r.MsgCode] != nil { - if l := linRegFromBytes(r.Data); l != nil { - stats[r.MsgCode] = l - } - } - } - } - } - - return &requestCostStats{ - db: db, - stats: stats, - } -} - -func (s *requestCostStats) store() { - s.lock.Lock() - defer s.lock.Unlock() - - statsRlp := make(requestCostStatsRlp, len(reqList)) - for i, code := range reqList { - statsRlp[i].MsgCode = code - statsRlp[i].Data = s.stats[code].toBytes() - } - - if data, err := rlp.EncodeToBytes(statsRlp); err == nil { - s.db.Put(rcStatsKey, data) - } -} - -func (s *requestCostStats) getCurrentList() RequestCostList { - s.lock.Lock() - defer s.lock.Unlock() - - list := make(RequestCostList, len(reqList)) - //fmt.Println("RequestCostList") - for idx, code := range reqList { - b, m := s.stats[code].calc() - //fmt.Println(code, s.stats[code].cnt, b/1000000, m/1000000) - if m < 0 { - b += m - m = 0 - } - if b < 0 { - b = 0 - } - - list[idx].MsgCode = code - list[idx].BaseCost = uint64(b * 2) - list[idx].ReqCost = uint64(m * 2) - } - return list -} - -func (s *requestCostStats) update(msgCode, reqCnt, cost uint64) { - s.lock.Lock() - defer s.lock.Unlock() - - c, ok := s.stats[msgCode] - if !ok || reqCnt == 0 { - return - } - c.add(float64(reqCnt), float64(cost)) -} - -func (pm *ProtocolManager) blockLoop() { - pm.wg.Add(1) - headCh := make(chan core.ChainHeadEvent, 10) - headSub := pm.blockchain.SubscribeChainHeadEvent(headCh) - go func() { - var lastHead *types.Header - lastBroadcastBn := uint64(0) - for { - select { - case ev := <-headCh: - peers := pm.peers.AllPeers() - if len(peers) > 0 { - header := ev.Block.Header() - hash := header.Hash() - number := header.Number.Uint64() - if number > lastBroadcastBn { - var reorg uint64 - if lastHead != nil { - reorg = lastHead.Number.Uint64() - rawdb.FindCommonAncestor(pm.chainDb, header, lastHead).Number.Uint64() - } - lastHead = header - lastBroadcastBn = number - - log.Debug("Announcing block to peers", "number", number, "hash", hash, "reorg", reorg) - - announce := announceData{Hash: hash, Number: number, ReorgDepth: reorg} - var ( - signed bool - signedAnnounce announceData - ) - - for _, p := range peers { - switch p.announceType { - - case announceTypeSimple: - select { - case p.announceChn <- announce: - default: - pm.removePeer(p.id) - } - - case announceTypeSigned: - if !signed { - signedAnnounce = announce - signedAnnounce.sign(pm.server.privateKey) - signed = true - } - - select { - case p.announceChn <- signedAnnounce: - default: - pm.removePeer(p.id) - } - } - } - } - } - case <-pm.quitSync: - headSub.Unsubscribe() - pm.wg.Done() - return - } - } - }() -} diff --git a/les/serverpool.go b/les/serverpool.go deleted file mode 100644 index 8a1c415fc6..0000000000 --- a/les/serverpool.go +++ /dev/null @@ -1,856 +0,0 @@ -// Copyright 2016 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 . - -// Package les implements the Light Ethereum Subprotocol. -package les - -import ( - "crypto/ecdsa" - "fmt" - "io" - "math" - "math/rand" - "net" - "strconv" - "sync" - "time" - - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - - "github.com/AlayaNetwork/Alaya-Go/common/mclock" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discv5" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -const ( - // After a connection has been ended or timed out, there is a waiting period - // before it can be selected for connection again. - // waiting period = base delay * (1 + random(1)) - // base delay = shortRetryDelay for the first shortRetryCnt times after a - // successful connection, after that longRetryDelay is applied - shortRetryCnt = 5 - shortRetryDelay = time.Second * 5 - longRetryDelay = time.Minute * 10 - // maxNewEntries is the maximum number of newly discovered (never connected) nodes. - // If the limit is reached, the least recently discovered one is thrown out. - maxNewEntries = 1000 - // maxKnownEntries is the maximum number of known (already connected) nodes. - // If the limit is reached, the least recently connected one is thrown out. - // (not that unlike new entries, known entries are persistent) - maxKnownEntries = 1000 - // target for simultaneously connected servers - targetServerCount = 5 - // target for servers selected from the known table - // (we leave room for trying new ones if there is any) - targetKnownSelect = 3 - // after dialTimeout, consider the server unavailable and adjust statistics - dialTimeout = time.Second * 30 - // targetConnTime is the minimum expected connection duration before a server - // drops a client without any specific reason - targetConnTime = time.Minute * 10 - // new entry selection weight calculation based on most recent discovery time: - // unity until discoverExpireStart, then exponential decay with discoverExpireConst - discoverExpireStart = time.Minute * 20 - discoverExpireConst = time.Minute * 20 - // known entry selection weight is dropped by a factor of exp(-failDropLn) after - // each unsuccessful connection (restored after a successful one) - failDropLn = 0.1 - // known node connection success and quality statistics have a long term average - // and a short term value which is adjusted exponentially with a factor of - // pstatRecentAdjust with each dial/connection and also returned exponentially - // to the average with the time constant pstatReturnToMeanTC - pstatReturnToMeanTC = time.Hour - // node address selection weight is dropped by a factor of exp(-addrFailDropLn) after - // each unsuccessful connection (restored after a successful one) - addrFailDropLn = math.Ln2 - // responseScoreTC and delayScoreTC are exponential decay time constants for - // calculating selection chances from response times and block delay times - responseScoreTC = time.Millisecond * 100 - delayScoreTC = time.Second * 5 - timeoutPow = 10 - // initStatsWeight is used to initialize previously unknown peers with good - // statistics to give a chance to prove themselves - 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 -// known light server nodes. It received discovered nodes, stores statistics about -// known nodes and takes care of always having enough good quality servers connected. -type serverPool struct { - db ethdb.Database - dbKey []byte - server *p2p.Server - quit chan struct{} - wg *sync.WaitGroup - connWg sync.WaitGroup - - topic discv5.Topic - - discSetPeriod chan time.Duration - discNodes chan *enode.Node - discLookups chan bool - - entries map[enode.ID]*poolEntry - timeout, enableRetry chan *poolEntry - adjustStats chan poolStatAdjust - - connCh chan *connReq - disconnCh chan *disconnReq - registerCh chan *registerReq - - knownQueue, newQueue poolEntryQueue - knownSelect, newSelect *weightedRandomSelect - knownSelected, newSelected int - fastDiscover bool -} - -// newServerPool creates a new serverPool instance -func newServerPool(db ethdb.Database, quit chan struct{}, wg *sync.WaitGroup) *serverPool { - pool := &serverPool{ - db: db, - quit: quit, - wg: wg, - entries: make(map[enode.ID]*poolEntry), - timeout: make(chan *poolEntry, 1), - adjustStats: make(chan poolStatAdjust, 100), - enableRetry: make(chan *poolEntry, 1), - connCh: make(chan *connReq), - disconnCh: make(chan *disconnReq), - registerCh: make(chan *registerReq), - knownSelect: newWeightedRandomSelect(), - newSelect: newWeightedRandomSelect(), - fastDiscover: true, - } - pool.knownQueue = newPoolEntryQueue(maxKnownEntries, pool.removeEntry) - pool.newQueue = newPoolEntryQueue(maxNewEntries, pool.removeEntry) - return pool -} - -func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) { - pool.server = server - pool.topic = topic - pool.dbKey = append([]byte("serverPool/"), []byte(topic)...) - pool.wg.Add(1) - pool.loadNodes() - - if pool.server.DiscV5 != nil { - pool.discSetPeriod = make(chan time.Duration, 1) - pool.discNodes = make(chan *enode.Node, 100) - pool.discLookups = make(chan bool, 100) - go pool.discoverNodes() - } - pool.checkDial() - go pool.eventLoop() -} - -// 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 -// dialed by the server pool recently, the appropriate pool entry is returned. -// Otherwise, the connection should be rejected. -// Note that whenever a connection has been accepted and a pool entry has been returned, -// disconnect should also always be called. -func (pool *serverPool) connect(p *peer, node *enode.Node) *poolEntry { - log.Debug("Connect new entry", "enode", p.id) - req := &connReq{p: p, node: node, result: make(chan *poolEntry, 1)} - select { - case pool.connCh <- req: - case <-pool.quit: - return nil - } - return <-req.result -} - -// registered should be called after a successful handshake -func (pool *serverPool) registered(entry *poolEntry) { - log.Debug("Registered new entry", "enode", entry.node.ID()) - req := ®isterReq{entry: entry, done: make(chan struct{})} - select { - case pool.registerCh <- req: - case <-pool.quit: - return - } - <-req.done -} - -// disconnect should be called when ending a connection. Service quality statistics -// can be updated optionally (not updated if no registration happened, in this case -// only connection statistics are updated, just like in case of timeout) -func (pool *serverPool) disconnect(entry *poolEntry) { - stopped := false - select { - case <-pool.quit: - 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. - pool.disconnCh <- req - <-req.done -} - -const ( - pseBlockDelay = iota - pseResponseTime - pseResponseTimeout -) - -// poolStatAdjust records are sent to adjust peer block delay/response time statistics -type poolStatAdjust struct { - adjustType int - entry *poolEntry - time time.Duration -} - -// adjustBlockDelay adjusts the block announce delay statistics of a node -func (pool *serverPool) adjustBlockDelay(entry *poolEntry, time time.Duration) { - if entry == nil { - return - } - pool.adjustStats <- poolStatAdjust{pseBlockDelay, entry, time} -} - -// adjustResponseTime adjusts the request response time statistics of a node -func (pool *serverPool) adjustResponseTime(entry *poolEntry, time time.Duration, timeout bool) { - if entry == nil { - return - } - if timeout { - pool.adjustStats <- poolStatAdjust{pseResponseTimeout, entry, time} - } else { - pool.adjustStats <- poolStatAdjust{pseResponseTime, entry, time} - } -} - -// eventLoop handles pool events and mutex locking for all internal functions -func (pool *serverPool) eventLoop() { - lookupCnt := 0 - var convTime mclock.AbsTime - if pool.discSetPeriod != nil { - 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 { - select { - case entry := <-pool.timeout: - if !entry.removed { - pool.checkDialTimeout(entry) - } - - case entry := <-pool.enableRetry: - if !entry.removed { - entry.delayedRetry = false - pool.updateCheckDial(entry) - } - - case adj := <-pool.adjustStats: - switch adj.adjustType { - case pseBlockDelay: - adj.entry.delayStats.add(float64(adj.time), 1) - case pseResponseTime: - adj.entry.responseStats.add(float64(adj.time), 1) - adj.entry.timeoutStats.add(0, 1) - case pseResponseTimeout: - adj.entry.timeoutStats.add(1, 1) - } - - case node := <-pool.discNodes: - entry := pool.findOrNewNode(node) - pool.updateCheckDial(entry) - - case conv := <-pool.discLookups: - if conv { - if lookupCnt == 0 { - convTime = mclock.Now() - } - lookupCnt++ - if pool.fastDiscover && (lookupCnt == 50 || time.Duration(mclock.Now()-convTime) > time.Minute) { - pool.fastDiscover = false - if pool.discSetPeriod != nil { - pool.discSetPeriod <- time.Minute - } - } - } - - case req := <-pool.connCh: - // 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: - // 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: - // Handle peer disconnection requests. - disconnect(req, req.stopped) - - case <-pool.quit: - if pool.discSetPeriod != nil { - close(pool.discSetPeriod) - } - - // 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.wg.Done() - return - } - } -} - -func (pool *serverPool) findOrNewNode(node *enode.Node) *poolEntry { - now := mclock.Now() - entry := pool.entries[node.ID()] - if entry == nil { - log.Debug("Discovered new entry", "id", node.ID()) - entry = &poolEntry{ - node: node, - addr: make(map[string]*poolEntryAddress), - addrSelect: *newWeightedRandomSelect(), - shortRetry: shortRetryCnt, - } - pool.entries[node.ID()] = entry - // initialize previously unknown peers with good statistics to give a chance to prove themselves - entry.connectStats.add(1, initStatsWeight) - entry.delayStats.add(0, initStatsWeight) - entry.responseStats.add(0, initStatsWeight) - entry.timeoutStats.add(0, initStatsWeight) - } - entry.lastDiscovered = now - addr := &poolEntryAddress{ip: node.IP(), port: uint16(node.TCP())} - if a, ok := entry.addr[addr.strKey()]; ok { - addr = a - } else { - entry.addr[addr.strKey()] = addr - } - addr.lastSeen = now - entry.addrSelect.update(addr) - if !entry.known { - pool.newQueue.setLatest(entry) - } - return entry -} - -// loadNodes loads known nodes and their statistics from the database -func (pool *serverPool) loadNodes() { - enc, err := pool.db.Get(pool.dbKey) - if err != nil { - return - } - var list []*poolEntry - err = rlp.DecodeBytes(enc, &list) - if err != nil { - log.Debug("Failed to decode node list", "err", err) - return - } - for _, e := range list { - log.Debug("Loaded server stats", "id", e.node.ID(), "fails", e.lastConnected.fails, - "conn", fmt.Sprintf("%v/%v", e.connectStats.avg, e.connectStats.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), - "timeout", fmt.Sprintf("%v/%v", e.timeoutStats.avg, e.timeoutStats.weight)) - pool.entries[e.node.ID()] = e - pool.knownQueue.setLatest(e) - pool.knownSelect.update((*knownEntry)(e)) - } -} - -// saveNodes saves known nodes and their statistics into the database. Nodes are -// ordered from least to most recently connected. -func (pool *serverPool) saveNodes() { - list := make([]*poolEntry, len(pool.knownQueue.queue)) - for i := range list { - list[i] = pool.knownQueue.fetchOldest() - } - enc, err := rlp.EncodeToBytes(list) - if err == nil { - pool.db.Put(pool.dbKey, enc) - } -} - -// removeEntry removes a pool entry when the entry count limit is reached. -// Note that it is called by the new/known queues from which the entry has already -// been removed so removing it from the queues is not necessary. -func (pool *serverPool) removeEntry(entry *poolEntry) { - pool.newSelect.remove((*discoveredEntry)(entry)) - pool.knownSelect.remove((*knownEntry)(entry)) - entry.removed = true - delete(pool.entries, entry.node.ID()) -} - -// setRetryDial starts the timer which will enable dialing a certain node again -func (pool *serverPool) setRetryDial(entry *poolEntry) { - delay := longRetryDelay - if entry.shortRetry > 0 { - entry.shortRetry-- - delay = shortRetryDelay - } - delay += time.Duration(rand.Int63n(int64(delay) + 1)) - entry.delayedRetry = true - go func() { - select { - case <-pool.quit: - case <-time.After(delay): - select { - case <-pool.quit: - case pool.enableRetry <- entry: - } - } - }() -} - -// updateCheckDial is called when an entry can potentially be dialed again. It updates -// its selection weights and checks if new dials can/should be made. -func (pool *serverPool) updateCheckDial(entry *poolEntry) { - pool.newSelect.update((*discoveredEntry)(entry)) - pool.knownSelect.update((*knownEntry)(entry)) - pool.checkDial() -} - -// checkDial checks if new dials can/should be made. It tries to select servers both -// based on good statistics and recent discovery. -func (pool *serverPool) checkDial() { - fillWithKnownSelects := !pool.fastDiscover - for pool.knownSelected < targetKnownSelect { - entry := pool.knownSelect.choose() - if entry == nil { - fillWithKnownSelects = false - break - } - pool.dial((*poolEntry)(entry.(*knownEntry)), true) - } - for pool.knownSelected+pool.newSelected < targetServerCount { - entry := pool.newSelect.choose() - if entry == nil { - break - } - pool.dial((*poolEntry)(entry.(*discoveredEntry)), false) - } - if fillWithKnownSelects { - // no more newly discovered nodes to select and since fast discover period - // is over, we probably won't find more in the near future so select more - // known entries if possible - for pool.knownSelected < targetServerCount { - entry := pool.knownSelect.choose() - if entry == nil { - break - } - pool.dial((*poolEntry)(entry.(*knownEntry)), true) - } - } -} - -// dial initiates a new connection -func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) { - if pool.server == nil || entry.state != psNotConnected { - return - } - entry.state = psDialed - entry.knownSelected = knownSelected - if knownSelected { - pool.knownSelected++ - } else { - pool.newSelected++ - } - addr := entry.addrSelect.choose().(*poolEntryAddress) - log.Debug("Dialing new peer", "lesaddr", entry.node.ID().String()+"@"+addr.strKey(), "set", len(entry.addr), "known", knownSelected) - entry.dialed = addr - go func() { - pool.server.AddPeer(entry.node) - select { - case <-pool.quit: - case <-time.After(dialTimeout): - select { - case <-pool.quit: - case pool.timeout <- entry: - } - } - }() -} - -// checkDialTimeout checks if the node is still in dialed state and if so, resets it -// and adjusts connection statistics accordingly. -func (pool *serverPool) checkDialTimeout(entry *poolEntry) { - if entry.state != psDialed { - return - } - log.Debug("Dial timeout", "lesaddr", entry.node.ID().String()+"@"+entry.dialed.strKey()) - entry.state = psNotConnected - if entry.knownSelected { - pool.knownSelected-- - } else { - pool.newSelected-- - } - entry.connectStats.add(0, 1) - entry.dialed.fails++ - pool.setRetryDial(entry) -} - -const ( - psNotConnected = iota - psDialed - psConnected - psRegistered -) - -// poolEntry represents a server node and stores its current state and statistics. -type poolEntry struct { - peer *peer - pubkey [64]byte // secp256k1 key of the node - addr map[string]*poolEntryAddress - node *enode.Node - lastConnected, dialed *poolEntryAddress - addrSelect weightedRandomSelect - - lastDiscovered mclock.AbsTime - known, knownSelected bool - connectStats, delayStats poolStats - responseStats, timeoutStats poolStats - state int - regTime mclock.AbsTime - queueIdx int - removed bool - - delayedRetry bool - 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 { - return rlp.Encode(w, &poolEntryEnc{ - 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 { - var entry poolEntryEnc - if err := s.Decode(&entry); err != nil { - 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()} - e.node = enode.NewV4(pubkey, entry.IP, int(entry.Port), int(entry.Port)) - e.addr = make(map[string]*poolEntryAddress) - e.addr[addr.strKey()] = addr - e.addrSelect = *newWeightedRandomSelect() - e.addrSelect.update(addr) - e.lastConnected = addr - e.connectStats = entry.CStat - e.delayStats = entry.DStat - e.responseStats = entry.RStat - e.timeoutStats = entry.TStat - e.shortRetry = shortRetryCnt - e.known = true - 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 -type discoveredEntry poolEntry - -// Weight calculates random selection weight for newly discovered entries -func (e *discoveredEntry) Weight() int64 { - if e.state != psNotConnected || e.delayedRetry { - return 0 - } - t := time.Duration(mclock.Now() - e.lastDiscovered) - if t <= discoverExpireStart { - return 1000000000 - } - return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst))) -} - -// knownEntry implements wrsItem -type knownEntry poolEntry - -// Weight calculates random selection weight for known entries -func (e *knownEntry) Weight() int64 { - if e.state != psNotConnected || !e.known || e.delayedRetry { - return 0 - } - return int64(1000000000 * e.connectStats.recentAvg() * math.Exp(-float64(e.lastConnected.fails)*failDropLn-e.responseStats.recentAvg()/float64(responseScoreTC)-e.delayStats.recentAvg()/float64(delayScoreTC)) * math.Pow(1-e.timeoutStats.recentAvg(), timeoutPow)) -} - -// poolEntryAddress is a separate object because currently it is necessary to remember -// multiple potential network addresses for a pool entry. This will be removed after -// the final implementation of v5 discovery which will retrieve signed and serial -// numbered advertisements, making it clear which IP/port is the latest one. -type poolEntryAddress struct { - ip net.IP - port uint16 - lastSeen mclock.AbsTime // last time it was discovered, connected or loaded from db - fails uint // connection failures since last successful connection (persistent) -} - -func (a *poolEntryAddress) Weight() int64 { - t := time.Duration(mclock.Now() - a.lastSeen) - return int64(1000000*math.Exp(-float64(t)/float64(discoverExpireConst)-float64(a.fails)*addrFailDropLn)) + 1 -} - -func (a *poolEntryAddress) strKey() string { - return a.ip.String() + ":" + strconv.Itoa(int(a.port)) -} - -// poolStats implement statistics for a certain quantity with a long term average -// and a short term value which is adjusted exponentially with a factor of -// pstatRecentAdjust with each update and also returned exponentially to the -// average with the time constant pstatReturnToMeanTC -type poolStats struct { - sum, weight, avg, recent float64 - lastRecalc mclock.AbsTime -} - -// init initializes stats with a long term sum/update count pair retrieved from the database -func (s *poolStats) init(sum, weight float64) { - s.sum = sum - s.weight = weight - var avg float64 - if weight > 0 { - avg = s.sum / weight - } - s.avg = avg - s.recent = avg - s.lastRecalc = mclock.Now() -} - -// recalc recalculates recent value return-to-mean and long term average -func (s *poolStats) recalc() { - now := mclock.Now() - s.recent = s.avg + (s.recent-s.avg)*math.Exp(-float64(now-s.lastRecalc)/float64(pstatReturnToMeanTC)) - if s.sum == 0 { - s.avg = 0 - } else { - if s.sum > s.weight*1e30 { - s.avg = 1e30 - } else { - s.avg = s.sum / s.weight - } - } - s.lastRecalc = now -} - -// add updates the stats with a new value -func (s *poolStats) add(value, weight float64) { - s.weight += weight - s.sum += value * weight - s.recalc() -} - -// recentAvg returns the short-term adjusted average -func (s *poolStats) recentAvg() float64 { - s.recalc() - return s.recent -} - -func (s *poolStats) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, []interface{}{math.Float64bits(s.sum), math.Float64bits(s.weight)}) -} - -func (s *poolStats) DecodeRLP(st *rlp.Stream) error { - var stats struct { - SumUint, WeightUint uint64 - } - if err := st.Decode(&stats); err != nil { - return err - } - s.init(math.Float64frombits(stats.SumUint), math.Float64frombits(stats.WeightUint)) - return nil -} - -// poolEntryQueue keeps track of its least recently accessed entries and removes -// them when the number of entries reaches the limit -type poolEntryQueue struct { - queue map[int]*poolEntry // known nodes indexed by their latest lastConnCnt value - newPtr, oldPtr, maxCnt int - removeFromPool func(*poolEntry) -} - -// newPoolEntryQueue returns a new poolEntryQueue -func newPoolEntryQueue(maxCnt int, removeFromPool func(*poolEntry)) poolEntryQueue { - return poolEntryQueue{queue: make(map[int]*poolEntry), maxCnt: maxCnt, removeFromPool: removeFromPool} -} - -// fetchOldest returns and removes the least recently accessed entry -func (q *poolEntryQueue) fetchOldest() *poolEntry { - if len(q.queue) == 0 { - return nil - } - for { - if e := q.queue[q.oldPtr]; e != nil { - delete(q.queue, q.oldPtr) - q.oldPtr++ - return e - } - q.oldPtr++ - } -} - -// remove removes an entry from the queue -func (q *poolEntryQueue) remove(entry *poolEntry) { - if q.queue[entry.queueIdx] == entry { - delete(q.queue, entry.queueIdx) - } -} - -// setLatest adds or updates a recently accessed entry. It also checks if an old entry -// needs to be removed and removes it from the parent pool too with a callback function. -func (q *poolEntryQueue) setLatest(entry *poolEntry) { - if q.queue[entry.queueIdx] == entry { - delete(q.queue, entry.queueIdx) - } else { - if len(q.queue) == q.maxCnt { - e := q.fetchOldest() - q.remove(e) - q.removeFromPool(e) - } - } - entry.queueIdx = q.newPtr - q.queue[entry.queueIdx] = entry - q.newPtr++ -} diff --git a/les/sync.go b/les/sync.go deleted file mode 100644 index e92b5c24a7..0000000000 --- a/les/sync.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2016 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 . - -package les - -import ( - "context" - "time" - - "github.com/AlayaNetwork/Alaya-Go/eth/downloader" - "github.com/AlayaNetwork/Alaya-Go/light" - "math/big" -) - -// syncer is responsible for periodically synchronising with the network, both -// downloading hashes and blocks as well as handling the announcement handler. -func (pm *ProtocolManager) syncer() { - // Start and ensure cleanup of sync mechanisms - //pm.fetcher.Start() - //defer pm.fetcher.Stop() - defer pm.downloader.Terminate() - - // Wait for different events to fire synchronisation operations - //forceSync := time.Tick(forceSyncCycle) - for { - select { - case <-pm.newPeerCh: - /* // Make sure we have peers to select from, then sync - if pm.peers.Len() < minDesiredPeerCount { - break - } - go pm.synchronise(pm.peers.BestPeer()) - */ - /*case <-forceSync: - // Force a sync even if not enough peers are present - go pm.synchronise(pm.peers.BestPeer()) - */ - case <-pm.noMorePeers: - return - } - } -} - -func (pm *ProtocolManager) needToSync(peerHead blockInfo) bool { - head := pm.blockchain.CurrentHeader() - return peerHead.Number > head.Number.Uint64() -} - -// synchronise tries to sync up our local block chain with a remote peer. -func (pm *ProtocolManager) synchronise(peer *peer) { - // Short circuit if no peers are available - if peer == nil { - return - } - - // Make sure the peer's TD is higher than our own. - if !pm.needToSync(peer.headBlockInfo()) { - return - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() - pm.blockchain.(*light.LightChain).SyncCht(ctx) - pm.downloader.Synchronise(peer.id, peer.Head(), new(big.Int).SetUint64(peer.headInfo.Number), downloader.LightSync) -} diff --git a/les/txrelay.go b/les/txrelay.go deleted file mode 100644 index 1e5fffb4a1..0000000000 --- a/les/txrelay.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2016 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 . - -package les - -import ( - "sync" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core/types" -) - -type ltrInfo struct { - tx *types.Transaction - sentTo map[*peer]struct{} -} - -type LesTxRelay struct { - txSent map[common.Hash]*ltrInfo - txPending map[common.Hash]struct{} - ps *peerSet - peerList []*peer - peerStartPos int - lock sync.Mutex - - reqDist *requestDistributor -} - -func NewLesTxRelay(ps *peerSet, reqDist *requestDistributor) *LesTxRelay { - r := &LesTxRelay{ - txSent: make(map[common.Hash]*ltrInfo), - txPending: make(map[common.Hash]struct{}), - ps: ps, - reqDist: reqDist, - } - ps.notify(r) - return r -} - -func (self *LesTxRelay) registerPeer(p *peer) { - self.lock.Lock() - defer self.lock.Unlock() - - self.peerList = self.ps.AllPeers() -} - -func (self *LesTxRelay) unregisterPeer(p *peer) { - self.lock.Lock() - defer self.lock.Unlock() - - self.peerList = self.ps.AllPeers() -} - -// send sends a list of transactions to at most a given number of peers at -// once, never resending any particular transaction to the same peer twice -func (self *LesTxRelay) send(txs types.Transactions, count int) { - sendTo := make(map[*peer]types.Transactions) - - self.peerStartPos++ // rotate the starting position of the peer list - if self.peerStartPos >= len(self.peerList) { - self.peerStartPos = 0 - } - - for _, tx := range txs { - hash := tx.Hash() - ltr, ok := self.txSent[hash] - if !ok { - ltr = <rInfo{ - tx: tx, - sentTo: make(map[*peer]struct{}), - } - self.txSent[hash] = ltr - self.txPending[hash] = struct{}{} - } - - if len(self.peerList) > 0 { - cnt := count - pos := self.peerStartPos - for { - peer := self.peerList[pos] - if _, ok := ltr.sentTo[peer]; !ok { - sendTo[peer] = append(sendTo[peer], tx) - ltr.sentTo[peer] = struct{}{} - cnt-- - } - if cnt == 0 { - break // sent it to the desired number of peers - } - pos++ - if pos == len(self.peerList) { - pos = 0 - } - if pos == self.peerStartPos { - break // tried all available peers - } - } - } - } - - for p, list := range sendTo { - pp := p - ll := list - - reqID := genReqID() - rq := &distReq{ - getCost: func(dp distPeer) uint64 { - peer := dp.(*peer) - return peer.GetRequestCost(SendTxMsg, len(ll)) - }, - canSend: func(dp distPeer) bool { - return dp.(*peer) == pp - }, - request: func(dp distPeer) func() { - peer := dp.(*peer) - cost := peer.GetRequestCost(SendTxMsg, len(ll)) - peer.fcServer.QueueRequest(reqID, cost) - return func() { peer.SendTxs(reqID, cost, ll) } - }, - } - self.reqDist.queue(rq) - } -} - -func (self *LesTxRelay) Send(txs types.Transactions) { - self.lock.Lock() - defer self.lock.Unlock() - - self.send(txs, 3) -} - -func (self *LesTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash) { - self.lock.Lock() - defer self.lock.Unlock() - - for _, hash := range mined { - delete(self.txPending, hash) - } - - for _, hash := range rollback { - self.txPending[hash] = struct{}{} - } - - if len(self.txPending) > 0 { - txs := make(types.Transactions, len(self.txPending)) - i := 0 - for hash := range self.txPending { - txs[i] = self.txSent[hash].tx - i++ - } - self.send(txs, 1) - } -} - -func (self *LesTxRelay) Discard(hashes []common.Hash) { - self.lock.Lock() - defer self.lock.Unlock() - - for _, hash := range hashes { - delete(self.txSent, hash) - delete(self.txPending, hash) - } -} diff --git a/light/lightchain.go b/light/lightchain.go deleted file mode 100644 index 58ac885d0f..0000000000 --- a/light/lightchain.go +++ /dev/null @@ -1,530 +0,0 @@ -// Copyright 2016 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 . - -// Package light implements on-demand retrieval capable state and chain objects -// for the PlatON Light Client. -package light - -import ( - "context" - "errors" - "sync" - "sync/atomic" - "time" - - "github.com/hashicorp/golang-lru" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/consensus" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/state" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/event" - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/params" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -var ( - bodyCacheLimit = 256 - blockCacheLimit = 256 -) - -// LightChain represents a canonical chain that by default only handles block -// headers, downloading block bodies and receipts on demand through an ODR -// interface. It only does header validation during chain insertion. -type LightChain struct { - hc *core.HeaderChain - indexerConfig *IndexerConfig - chainDb ethdb.Database - engine consensus.Engine - odr OdrBackend - chainFeed event.Feed - chainSideFeed event.Feed - chainHeadFeed event.Feed - scope event.SubscriptionScope - genesisBlock *types.Block - - bodyCache *lru.Cache // Cache for the most recent block bodies - bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format - blockCache *lru.Cache // Cache for the most recent entire blocks - - chainmu sync.RWMutex // protects header inserts - quit chan struct{} - wg sync.WaitGroup - - // Atomic boolean switches: - running int32 // whether LightChain is running or stopped - procInterrupt int32 // interrupts chain insert - disableCheckFreq int32 // disables header verification -} - -// NewLightChain returns a fully initialised light chain using information -// available in the database. It initialises the default Ethereum header -// validator. -func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.Engine) (*LightChain, error) { - bodyCache, _ := lru.New(bodyCacheLimit) - bodyRLPCache, _ := lru.New(bodyCacheLimit) - blockCache, _ := lru.New(blockCacheLimit) - - bc := &LightChain{ - chainDb: odr.Database(), - indexerConfig: odr.IndexerConfig(), - odr: odr, - quit: make(chan struct{}), - bodyCache: bodyCache, - bodyRLPCache: bodyRLPCache, - blockCache: blockCache, - engine: engine, - } - var err error - bc.hc, err = core.NewHeaderChain(odr.Database(), config, bc.engine, bc.getProcInterrupt) - if err != nil { - return nil, err - } - bc.genesisBlock, _ = bc.GetBlockByNumber(NoOdr, 0) - if bc.genesisBlock == nil { - return nil, core.ErrNoGenesis - } - if cp, ok := params.TrustedCheckpoints[bc.genesisBlock.Hash()]; ok { - bc.addTrustedCheckpoint(cp) - } - if err := bc.loadLastState(); err != nil { - return nil, err - } - // Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain - //for hash := range core.BadHashes { - // if header := bc.GetHeaderByHash(hash); header != nil { - // log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash) - // bc.SetHead(header.Number.Uint64() - 1) - // log.Error("Chain rewind was successful, resuming normal operation") - // } - //} - return bc, nil -} - -// addTrustedCheckpoint adds a trusted checkpoint to the blockchain -func (lc *LightChain) addTrustedCheckpoint(cp *params.TrustedCheckpoint) { - if lc.odr.ChtIndexer() != nil { - StoreChtRoot(lc.chainDb, cp.SectionIndex, cp.SectionHead, cp.CHTRoot) - lc.odr.ChtIndexer().AddCheckpoint(cp.SectionIndex, cp.SectionHead) - } - if lc.odr.BloomTrieIndexer() != nil { - StoreBloomTrieRoot(lc.chainDb, cp.SectionIndex, cp.SectionHead, cp.BloomRoot) - lc.odr.BloomTrieIndexer().AddCheckpoint(cp.SectionIndex, cp.SectionHead) - } - if lc.odr.BloomIndexer() != nil { - lc.odr.BloomIndexer().AddCheckpoint(cp.SectionIndex, cp.SectionHead) - } - log.Info("Added trusted checkpoint", "chain", cp.Name, "block", (cp.SectionIndex+1)*lc.indexerConfig.ChtSize-1, "hash", cp.SectionHead) -} - -func (lc *LightChain) getProcInterrupt() bool { - return atomic.LoadInt32(&lc.procInterrupt) == 1 -} - -// Odr returns the ODR backend of the chain -func (lc *LightChain) Odr() OdrBackend { - return lc.odr -} - -// loadLastState loads the last known chain state from the database. This method -// assumes that the chain manager mutex is held. -func (lc *LightChain) loadLastState() error { - if head := rawdb.ReadHeadHeaderHash(lc.chainDb); head == (common.Hash{}) { - // Corrupt or empty database, init from scratch - lc.Reset() - } else { - if header := lc.GetHeaderByHash(head); header != nil { - lc.hc.SetCurrentHeader(header) - } - } - - // Issue a status log and return - header := lc.hc.CurrentHeader() - log.Info("Loaded most recent local header", "number", header.Number, "hash", header.Hash(), "age", common.PrettyAge(time.Unix(int64(header.Time), 0))) - - return nil -} - -// SetHead rewinds the local chain to a new head. Everything above the new -// head will be deleted and the new one set. -func (lc *LightChain) SetHead(head uint64) error { - lc.chainmu.Lock() - defer lc.chainmu.Unlock() - - //lc.hc.SetHead(head, nil) - //return lc.loadLastState() - return errors.New("not support yet") -} - -// GasLimit returns the gas limit of the current HEAD block. -func (lc *LightChain) GasLimit() uint64 { - return lc.hc.CurrentHeader().GasLimit -} - -// Reset purges the entire blockchain, restoring it to its genesis state. -func (lc *LightChain) Reset() { - lc.ResetWithGenesisBlock(lc.genesisBlock) -} - -// ResetWithGenesisBlock purges the entire blockchain, restoring it to the -// specified genesis state. -func (lc *LightChain) ResetWithGenesisBlock(genesis *types.Block) { - // Dump the entire block chain and purge the caches - lc.SetHead(0) - - lc.chainmu.Lock() - defer lc.chainmu.Unlock() - - // Prepare the genesis block and reinitialise the chain - rawdb.WriteBlock(lc.chainDb, genesis) - - lc.genesisBlock = genesis - lc.hc.SetGenesis(lc.genesisBlock.Header()) - lc.hc.SetCurrentHeader(lc.genesisBlock.Header()) -} - -// Accessors - -// Engine retrieves the light chain's consensus engine. -func (lc *LightChain) Engine() consensus.Engine { return lc.engine } - -// Genesis returns the genesis block -func (lc *LightChain) Genesis() *types.Block { - return lc.genesisBlock -} - -// State returns a new mutable state based on the current HEAD block. -func (bc *LightChain) State() (*state.StateDB, error) { - return nil, errors.New("not implemented, needs client/server interface split") -} - -// GetBody retrieves a block body (transactions) from the database -// or ODR service by hash, caching it if found. -func (lc *LightChain) GetBody(ctx context.Context, hash common.Hash) (*types.Body, error) { - // Short circuit if the body's already in the cache, retrieve otherwise - if cached, ok := lc.bodyCache.Get(hash); ok { - body := cached.(*types.Body) - return body, nil - } - number := lc.hc.GetBlockNumber(hash) - if number == nil { - return nil, errors.New("unknown block") - } - body, err := GetBody(ctx, lc.odr, hash, *number) - if err != nil { - return nil, err - } - // Cache the found body for next time and return - lc.bodyCache.Add(hash, body) - return body, nil -} - -// GetBodyRLP retrieves a block body in RLP encoding from the database or -// ODR service by hash, caching it if found. -func (lc *LightChain) GetBodyRLP(ctx context.Context, hash common.Hash) (rlp.RawValue, error) { - // Short circuit if the body's already in the cache, retrieve otherwise - if cached, ok := lc.bodyRLPCache.Get(hash); ok { - return cached.(rlp.RawValue), nil - } - number := lc.hc.GetBlockNumber(hash) - if number == nil { - return nil, errors.New("unknown block") - } - body, err := GetBodyRLP(ctx, lc.odr, hash, *number) - if err != nil { - return nil, err - } - // Cache the found body for next time and return - lc.bodyRLPCache.Add(hash, body) - return body, nil -} - -// HasBlock checks if a block is fully present in the database or not, caching -// it if present. -func (lc *LightChain) HasBlock(hash common.Hash, number uint64) bool { - blk, _ := lc.GetBlock(NoOdr, hash, number) - return blk != nil -} - -// GetBlock retrieves a block from the database or ODR service by hash and number, -// caching it if found. -func (lc *LightChain) GetBlock(ctx context.Context, hash common.Hash, number uint64) (*types.Block, error) { - // Short circuit if the block's already in the cache, retrieve otherwise - if block, ok := lc.blockCache.Get(hash); ok { - return block.(*types.Block), nil - } - block, err := GetBlock(ctx, lc.odr, hash, number) - if err != nil { - return nil, err - } - // Cache the found block for next time and return - lc.blockCache.Add(block.Hash(), block) - return block, nil -} - -// GetBlockByHash retrieves a block from the database or ODR service by hash, -// caching it if found. -func (lc *LightChain) GetBlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - number := lc.hc.GetBlockNumber(hash) - if number == nil { - return nil, errors.New("unknown block") - } - return lc.GetBlock(ctx, hash, *number) -} - -// GetBlockByNumber retrieves a block from the database or ODR service by -// number, caching it (associated with its hash) if found. -func (lc *LightChain) GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) { - hash, err := GetCanonicalHash(ctx, lc.odr, number) - if hash == (common.Hash{}) || err != nil { - return nil, err - } - return lc.GetBlock(ctx, hash, number) -} - -// Stop stops the blockchain service. If any imports are currently in progress -// it will abort them using the procInterrupt. -func (lc *LightChain) Stop() { - if !atomic.CompareAndSwapInt32(&lc.running, 0, 1) { - return - } - close(lc.quit) - atomic.StoreInt32(&lc.procInterrupt, 1) - - lc.wg.Wait() - log.Info("Blockchain manager stopped") -} - -// Rollback is designed to remove a chain of links from the database that aren't -// certain enough to be valid. -func (lc *LightChain) Rollback(chain []common.Hash) { - lc.chainmu.Lock() - defer lc.chainmu.Unlock() - - for i := len(chain) - 1; i >= 0; i-- { - hash := chain[i] - - if head := lc.hc.CurrentHeader(); head.Hash() == hash { - lc.hc.SetCurrentHeader(lc.GetHeader(head.ParentHash, head.Number.Uint64()-1)) - } - } -} - -// postChainEvents iterates over the events generated by a chain insertion and -// posts them into the event feed. -func (lc *LightChain) postChainEvents(events []interface{}) { - for _, event := range events { - switch ev := event.(type) { - case core.ChainEvent: - if lc.CurrentHeader().Hash() == ev.Hash { - lc.chainHeadFeed.Send(core.ChainHeadEvent{Block: ev.Block}) - } - lc.chainFeed.Send(ev) - case core.ChainSideEvent: - lc.chainSideFeed.Send(ev) - } - } -} - -// InsertHeaderChain attempts to insert the given header chain in to the local -// chain, possibly creating a reorg. If an error is returned, it will return the -// index number of the failing header as well an error describing what went wrong. -// -// The verify parameter can be used to fine tune whether nonce verification -// should be done or not. The reason behind the optional check is because some -// of the header retrieval mechanisms already need to verfy nonces, as well as -// because nonces can be verified sparsely, not needing to check each. -// -// In the case of a light chain, InsertHeaderChain also creates and posts light -// chain events when necessary. -func (lc *LightChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (int, error) { - if atomic.LoadInt32(&lc.disableCheckFreq) == 1 { - checkFreq = 0 - } - start := time.Now() - if i, err := lc.hc.ValidateHeaderChain(chain, checkFreq); err != nil { - return i, err - } - - // Make sure only one thread manipulates the chain at once - lc.chainmu.Lock() - defer lc.chainmu.Unlock() - - lc.wg.Add(1) - defer lc.wg.Done() - - var events []interface{} - whFunc := func(header *types.Header) error { - status, err := lc.hc.WriteHeader(header) - - switch status { - case core.CanonStatTy: - log.Debug("Inserted new header", "number", header.Number, "hash", header.Hash()) - events = append(events, core.ChainEvent{Block: types.NewBlockWithHeader(header), Hash: header.Hash()}) - - case core.SideStatTy: - log.Debug("Inserted forked header", "number", header.Number, "hash", header.Hash()) - events = append(events, core.ChainSideEvent{Block: types.NewBlockWithHeader(header)}) - } - return err - } - i, err := lc.hc.InsertHeaderChain(chain, whFunc, start) - lc.postChainEvents(events) - return i, err -} - -// CurrentHeader retrieves the current head header of the canonical chain. The -// header is retrieved from the HeaderChain's internal cache. -func (lc *LightChain) CurrentHeader() *types.Header { - return lc.hc.CurrentHeader() -} - -// GetHeader retrieves a block header from the database by hash and number, -// caching it if found. -func (lc *LightChain) GetHeader(hash common.Hash, number uint64) *types.Header { - return lc.hc.GetHeader(hash, number) -} - -// GetHeaderByHash retrieves a block header from the database by hash, caching it if -// found. -func (lc *LightChain) GetHeaderByHash(hash common.Hash) *types.Header { - return lc.hc.GetHeaderByHash(hash) -} - -// HasHeader checks if a block header is present in the database or not, caching -// it if present. -func (lc *LightChain) HasHeader(hash common.Hash, number uint64) bool { - return lc.hc.HasHeader(hash, number) -} - -// GetBlockHashesFromHash retrieves a number of block hashes starting at a given -// hash, fetching towards the genesis block. -func (lc *LightChain) GetBlockHashesFromHash(hash common.Hash, max uint64) []common.Hash { - return lc.hc.GetBlockHashesFromHash(hash, max) -} - -// GetAncestor retrieves the Nth ancestor of a given block. It assumes that either the given block or -// a close ancestor of it is canonical. maxNonCanonical points to a downwards counter limiting the -// number of blocks to be individually checked before we reach the canonical chain. -// -// Note: ancestor == 0 returns the same block, 1 returns its parent and so on. -func (lc *LightChain) GetAncestor(hash common.Hash, number, ancestor uint64, maxNonCanonical *uint64) (common.Hash, uint64) { - lc.chainmu.RLock() - defer lc.chainmu.RUnlock() - - return lc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical) -} - -// GetHeaderByNumber retrieves a block header from the database by number, -// caching it (associated with its hash) if found. -func (lc *LightChain) GetHeaderByNumber(number uint64) *types.Header { - return lc.hc.GetHeaderByNumber(number) -} - -// GetHeaderByNumberOdr retrieves a block header from the database or network -// by number, caching it (associated with its hash) if found. -func (lc *LightChain) GetHeaderByNumberOdr(ctx context.Context, number uint64) (*types.Header, error) { - if header := lc.hc.GetHeaderByNumber(number); header != nil { - return header, nil - } - return GetHeaderByNumber(ctx, lc.odr, number) -} - -// Config retrieves the header chain's chain configuration. -func (lc *LightChain) Config() *params.ChainConfig { return lc.hc.Config() } - -func (lc *LightChain) SyncCht(ctx context.Context) bool { - // If we don't have a CHT indexer, abort - if lc.odr.ChtIndexer() == nil { - return false - } - // Ensure the remote CHT head is ahead of us - head := lc.CurrentHeader().Number.Uint64() - sections, _, _ := lc.odr.ChtIndexer().Sections() - - latest := sections*lc.indexerConfig.ChtSize - 1 - if clique := lc.hc.Config().Clique; clique != nil { - latest -= latest % clique.Epoch // epoch snapshot for clique - } - if head >= latest { - return false - } - // Retrieve the latest useful header and update to it - if header, err := GetHeaderByNumber(ctx, lc.odr, latest); header != nil && err == nil { - lc.chainmu.Lock() - defer lc.chainmu.Unlock() - - // Ensure the chain didn't move past the latest block while retrieving it - if lc.hc.CurrentHeader().Number.Uint64() < header.Number.Uint64() { - log.Info("Updated latest header based on CHT", "number", header.Number, "hash", header.Hash(), "age", common.PrettyAge(time.Unix(int64(header.Time), 0))) - lc.hc.SetCurrentHeader(header) - } - return true - } - return false -} - -// LockChain locks the chain mutex for reading so that multiple canonical hashes can be -// retrieved while it is guaranteed that they belong to the same version of the chain -func (lc *LightChain) LockChain() { - lc.chainmu.RLock() -} - -// UnlockChain unlocks the chain mutex -func (lc *LightChain) UnlockChain() { - lc.chainmu.RUnlock() -} - -// SubscribeChainEvent registers a subscription of ChainEvent. -func (lc *LightChain) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { - return lc.scope.Track(lc.chainFeed.Subscribe(ch)) -} - -// SubscribeChainHeadEvent registers a subscription of ChainHeadEvent. -func (lc *LightChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { - return lc.scope.Track(lc.chainHeadFeed.Subscribe(ch)) -} - -// SubscribeChainSideEvent registers a subscription of ChainSideEvent. -func (lc *LightChain) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { - return lc.scope.Track(lc.chainSideFeed.Subscribe(ch)) -} - -// SubscribeLogsEvent implements the interface of filters.Backend -// LightChain does not send logs events, so return an empty subscription. -func (lc *LightChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { - return lc.scope.Track(new(event.Feed).Subscribe(ch)) -} - -// SubscribeRemovedLogsEvent implements the interface of filters.Backend -// LightChain does not send core.RemovedLogsEvent, so return an empty subscription. -func (lc *LightChain) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { - return lc.scope.Track(new(event.Feed).Subscribe(ch)) -} - -// DisableCheckFreq disables header validation. This is used for ultralight mode. -func (lc *LightChain) DisableCheckFreq() { - atomic.StoreInt32(&lc.disableCheckFreq, 1) -} - -// EnableCheckFreq enables header validation. -func (lc *LightChain) EnableCheckFreq() { - atomic.StoreInt32(&lc.disableCheckFreq, 0) -} diff --git a/light/lightchain_test.go b/light/lightchain_test.go deleted file mode 100644 index 6d954d5406..0000000000 --- a/light/lightchain_test.go +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright 2016 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 . - -package light - -import ( - "context" - "math/big" - "testing" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/consensus" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/params" - _ "github.com/AlayaNetwork/Alaya-Go/x/xcom" -) - -// So we can deterministically seed different blockchains -var ( - canonicalSeed = 1 - forkSeed = 2 -) - -// makeHeaderChain creates a deterministic chain of headers rooted at parent. -func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) []*types.Header { - blocks, _ := core.GenerateChain(params.TestChainConfig, types.NewBlockWithHeader(parent), consensus.NewFaker(), db, n, func(i int, b *core.BlockGen) { - b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)}) - }) - headers := make([]*types.Header, len(blocks)) - for i, block := range blocks { - headers[i] = block.Header() - } - return headers -} - -// newCanonical creates a chain database, and injects a deterministic canonical -// chain. Depending on the full flag, if creates either a full block chain or a -// header only chain. -func newCanonical(n int) (ethdb.Database, *LightChain, error) { - db := rawdb.NewMemoryDatabase() - gspec := core.Genesis{Config: params.TestChainConfig} - genesis := gspec.MustCommit(db) - blockchain, _ := NewLightChain(&dummyOdr{db: db, indexerConfig: TestClientIndexerConfig}, gspec.Config, consensus.NewFaker()) - - // Create and inject the requested chain - if n == 0 { - return db, blockchain, nil - } - // Header-only chain requested - headers := makeHeaderChain(genesis.Header(), n, db, canonicalSeed) - _, err := blockchain.InsertHeaderChain(headers, 1) - return db, blockchain, err -} - -// newTestLightChain creates a LightChain that doesn't validate anything. -func newTestLightChain() *LightChain { - db := rawdb.NewMemoryDatabase() - gspec := &core.Genesis{ - Config: params.TestChainConfig, - } - gspec.MustCommit(db) - lc, err := NewLightChain(&dummyOdr{db: db}, gspec.Config, consensus.NewFaker()) - if err != nil { - panic(err) - } - return lc -} - -// Test fork of length N starting from block i -func testFork(t *testing.T, LightChain *LightChain, i, n int, comparator func(td1, td2 *big.Int)) { - // Copy old chain up to #i into a new db - db, LightChain2, err := newCanonical(i) - if err != nil { - t.Fatal("could not make new canonical in testFork", err) - } - // Assert the chains have the same header/block at #i - var hash1, hash2 common.Hash - hash1 = LightChain.GetHeaderByNumber(uint64(i)).Hash() - hash2 = LightChain2.GetHeaderByNumber(uint64(i)).Hash() - if hash1 != hash2 { - t.Errorf("chain content mismatch at %d: have hash %v, want hash %v", i, hash2, hash1) - } - // Extend the newly created chain - headerChainB := makeHeaderChain(LightChain2.CurrentHeader(), n, db, forkSeed) - if _, err := LightChain2.InsertHeaderChain(headerChainB, 1); err != nil { - t.Fatalf("failed to insert forking chain: %v", err) - } -} - -// testHeaderChainImport tries to process a chain of header, writing them into -// the database if successful. -func testHeaderChainImport(chain []*types.Header, lightchain *LightChain) error { - for _, header := range chain { - // Try and validate the header - if err := lightchain.engine.VerifyHeader(lightchain.hc, header, true); err != nil { - return err - } - // Manually insert the header into the database, but don't reorganize (allows subsequent testing) - lightchain.chainmu.Lock() - - rawdb.WriteHeader(lightchain.chainDb, header) - lightchain.chainmu.Unlock() - } - return nil -} - -// Tests that given a starting canonical chain of a given size, it can be extended -// with various length chains. -func TestExtendCanonicalHeaders(t *testing.T) { - length := 5 - - // Make first chain starting from genesis - _, processor, err := newCanonical(length) - if err != nil { - t.Fatalf("failed to make new canonical chain: %v", err) - } - // Define the difficulty comparator - better := func(td1, td2 *big.Int) { - if td2.Cmp(td1) <= 0 { - t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1) - } - } - // Start fork from current height - testFork(t, processor, length, 1, better) - testFork(t, processor, length, 2, better) - testFork(t, processor, length, 5, better) - testFork(t, processor, length, 10, better) -} - -// Tests that given a starting canonical chain of a given size, creating shorter -// forks do not take canonical ownership. -func TestShorterForkHeaders(t *testing.T) { - length := 10 - - // Make first chain starting from genesis - _, processor, err := newCanonical(length) - if err != nil { - t.Fatalf("failed to make new canonical chain: %v", err) - } - // Define the difficulty comparator - worse := func(td1, td2 *big.Int) { - if td2.Cmp(td1) >= 0 { - t.Errorf("total difficulty mismatch: have %v, expected less than %v", td2, td1) - } - } - // Sum of numbers must be less than `length` for this to be a shorter fork - testFork(t, processor, 0, 3, worse) - testFork(t, processor, 0, 7, worse) - testFork(t, processor, 1, 1, worse) - testFork(t, processor, 1, 7, worse) - testFork(t, processor, 5, 3, worse) - testFork(t, processor, 5, 4, worse) -} - -// Tests that given a starting canonical chain of a given size, creating longer -// forks do take canonical ownership. -func TestLongerForkHeaders(t *testing.T) { - length := 10 - - // Make first chain starting from genesis - _, processor, err := newCanonical(length) - if err != nil { - t.Fatalf("failed to make new canonical chain: %v", err) - } - // Define the difficulty comparator - better := func(td1, td2 *big.Int) { - if td2.Cmp(td1) <= 0 { - t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1) - } - } - // Sum of numbers must be greater than `length` for this to be a longer fork - testFork(t, processor, 0, 11, better) - testFork(t, processor, 0, 15, better) - testFork(t, processor, 1, 10, better) - testFork(t, processor, 1, 12, better) - testFork(t, processor, 5, 6, better) - testFork(t, processor, 5, 8, better) -} - -// Tests that given a starting canonical chain of a given size, creating equal -// forks do take canonical ownership. -func TestEqualForkHeaders(t *testing.T) { - length := 10 - - // Make first chain starting from genesis - _, processor, err := newCanonical(length) - if err != nil { - t.Fatalf("failed to make new canonical chain: %v", err) - } - // Define the difficulty comparator - equal := func(td1, td2 *big.Int) { - if td2.Cmp(td1) != 0 { - t.Errorf("total difficulty mismatch: have %v, want %v", td2, td1) - } - } - // Sum of numbers must be equal to `length` for this to be an equal fork - testFork(t, processor, 0, 10, equal) - testFork(t, processor, 1, 9, equal) - testFork(t, processor, 2, 8, equal) - testFork(t, processor, 5, 5, equal) - testFork(t, processor, 6, 4, equal) - testFork(t, processor, 9, 1, equal) -} - -// Tests that chains missing links do not get accepted by the processor. -func TestBrokenHeaderChain(t *testing.T) { - // Make chain starting from genesis - db, LightChain, err := newCanonical(10) - if err != nil { - t.Fatalf("failed to make new canonical chain: %v", err) - } - // Create a forked chain, and try to insert with a missing link - chain := makeHeaderChain(LightChain.CurrentHeader(), 5, db, forkSeed)[1:] - if err := testHeaderChainImport(chain, LightChain); err == nil { - //t.Errorf("broken header chain not reported") - } -} - -func makeHeaderChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Header { - var chain []*types.Header - for i, _ := range d { - header := &types.Header{ - Coinbase: common.Address{seed}, - Number: big.NewInt(int64(i + 1)), - TxHash: types.EmptyRootHash, - ReceiptHash: types.EmptyRootHash, - } - if i == 0 { - header.ParentHash = genesis.Hash() - } else { - header.ParentHash = chain[i-1].Hash() - } - chain = append(chain, types.CopyHeader(header)) - } - return chain -} - -type dummyOdr struct { - OdrBackend - db ethdb.Database - indexerConfig *IndexerConfig -} - -func (odr *dummyOdr) Database() ethdb.Database { - return odr.db -} - -func (odr *dummyOdr) Retrieve(ctx context.Context, req OdrRequest) error { - return nil -} - -func (odr *dummyOdr) IndexerConfig() *IndexerConfig { - return odr.indexerConfig -} - -// Tests that reorganizing a long difficult chain after a short easy one -// overwrites the canonical numbers and links in the database. -func TestReorgLongHeaders(t *testing.T) { - testReorg(t, []int{1, 2, 4}, []int{1, 2, 3, 4}, 10) -} - -// Tests that reorganizing a short difficult chain after a long easy one -// overwrites the canonical numbers and links in the database. -func TestReorgShortHeaders(t *testing.T) { - testReorg(t, []int{1, 2, 3, 4}, []int{1, 10}, 11) -} - -func testReorg(t *testing.T, first, second []int, td int64) { - bc := newTestLightChain() - - // Insert an easy and a difficult chain afterwards - bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, first, 11), 1) - bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, second, 22), 1) - // Check that the chain is valid number and link wise - prev := bc.CurrentHeader() - for header := bc.GetHeaderByNumber(bc.CurrentHeader().Number.Uint64() - 1); header.Number.Uint64() != 0; prev, header = header, bc.GetHeaderByNumber(header.Number.Uint64()-1) { - if prev.ParentHash != header.Hash() { - t.Errorf("parent header hash mismatch: have %x, want %x", prev.ParentHash, header.Hash()) - } - } - -} diff --git a/light/nodeset.go b/light/nodeset.go deleted file mode 100644 index 1c105d6496..0000000000 --- a/light/nodeset.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright 2017 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 . - -package light - -import ( - "errors" - "sync" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -// NodeSet stores a set of trie nodes. It implements trie.Database and can also -// act as a cache for another trie.Database. -type NodeSet struct { - nodes map[string][]byte - order []string - - dataSize int - lock sync.RWMutex -} - -// NewNodeSet creates an empty node set -func NewNodeSet() *NodeSet { - return &NodeSet{ - nodes: make(map[string][]byte), - } -} - -// Put stores a new node in the set -func (db *NodeSet) Put(key []byte, value []byte) error { - db.lock.Lock() - defer db.lock.Unlock() - - if _, ok := db.nodes[string(key)]; ok { - return nil - } - keystr := string(key) - - db.nodes[keystr] = common.CopyBytes(value) - db.order = append(db.order, keystr) - db.dataSize += len(value) - - return nil -} - -// Delete removes a node from the set -func (db *NodeSet) Delete(key []byte) error { - db.lock.Lock() - defer db.lock.Unlock() - - delete(db.nodes, string(key)) - return nil -} - -// Get returns a stored node -func (db *NodeSet) Get(key []byte) ([]byte, error) { - db.lock.RLock() - defer db.lock.RUnlock() - - if entry, ok := db.nodes[string(key)]; ok { - return entry, nil - } - return nil, errors.New("not found") -} - -// Has returns true if the node set contains the given key -func (db *NodeSet) Has(key []byte) (bool, error) { - _, err := db.Get(key) - return err == nil, nil -} - -// KeyCount returns the number of nodes in the set -func (db *NodeSet) KeyCount() int { - db.lock.RLock() - defer db.lock.RUnlock() - - return len(db.nodes) -} - -// DataSize returns the aggregated data size of nodes in the set -func (db *NodeSet) DataSize() int { - db.lock.RLock() - defer db.lock.RUnlock() - - return db.dataSize -} - -// NodeList converts the node set to a NodeList -func (db *NodeSet) NodeList() NodeList { - db.lock.RLock() - defer db.lock.RUnlock() - - var values NodeList - for _, key := range db.order { - values = append(values, db.nodes[key]) - } - return values -} - -// Store writes the contents of the set to the given database -func (db *NodeSet) Store(target ethdb.KeyValueWriter) { - db.lock.RLock() - defer db.lock.RUnlock() - - for key, value := range db.nodes { - target.Put([]byte(key), value) - } -} - -// NodeList stores an ordered list of trie nodes. It implements ethdb.KeyValueWriter. -type NodeList []rlp.RawValue - -// Store writes the contents of the list to the given database -func (n NodeList) Store(db ethdb.KeyValueWriter) { - for _, node := range n { - db.Put(crypto.Keccak256(node), node) - } -} - -// NodeSet converts the node list to a NodeSet -func (n NodeList) NodeSet() *NodeSet { - db := NewNodeSet() - n.Store(db) - return db -} - -// Put stores a new node at the end of the list -func (n *NodeList) Put(key []byte, value []byte) error { - *n = append(*n, value) - return nil -} - -// Delete panics as there's no reason to remove a node from the list. -func (n *NodeList) Delete(key []byte) error { - panic("not supported") -} - -// DataSize returns the aggregated data size of nodes in the list -func (n NodeList) DataSize() int { - var size int - for _, node := range n { - size += len(node) - } - return size -} diff --git a/light/odr.go b/light/odr.go deleted file mode 100644 index 95733f41d3..0000000000 --- a/light/odr.go +++ /dev/null @@ -1,175 +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 . - -// Package light implements on-demand retrieval capable state and chain objects -// for the Ethereum Light Client. -package light - -import ( - "context" - "errors" - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/ethdb" -) - -// NoOdr is the default context passed to an ODR capable function when the ODR -// service is not required. -var NoOdr = context.Background() - -// ErrNoPeers is returned if no peers capable of serving a queued request are available -var ErrNoPeers = errors.New("no suitable peers available") - -// OdrBackend is an interface to a backend service that handles ODR retrievals type -type OdrBackend interface { - Database() ethdb.Database - ChtIndexer() *core.ChainIndexer - BloomTrieIndexer() *core.ChainIndexer - BloomIndexer() *core.ChainIndexer - Retrieve(ctx context.Context, req OdrRequest) error - IndexerConfig() *IndexerConfig -} - -// OdrRequest is an interface for retrieval requests -type OdrRequest interface { - StoreResult(db ethdb.Database) -} - -// TrieID identifies a state or account storage trie -type TrieID struct { - BlockHash, Root common.Hash - BlockNumber uint64 - AccKey []byte -} - -// StateTrieID returns a TrieID for a state trie belonging to a certain block -// header. -func StateTrieID(header *types.Header) *TrieID { - return &TrieID{ - BlockHash: header.Hash(), - BlockNumber: header.Number.Uint64(), - AccKey: nil, - Root: header.Root, - } -} - -// StorageTrieID returns a TrieID for a contract storage trie at a given account -// of a given state trie. It also requires the root hash of the trie for -// checking Merkle proofs. -func StorageTrieID(state *TrieID, addrHash, root common.Hash) *TrieID { - return &TrieID{ - BlockHash: state.BlockHash, - BlockNumber: state.BlockNumber, - AccKey: addrHash[:], - Root: root, - } -} - -// TrieRequest is the ODR request type for state/storage trie entries -type TrieRequest struct { - OdrRequest - Id *TrieID - Key []byte - Proof *NodeSet -} - -// StoreResult stores the retrieved data in local database -func (req *TrieRequest) StoreResult(db ethdb.Database) { - req.Proof.Store(db) -} - -// CodeRequest is the ODR request type for retrieving contract code -type CodeRequest struct { - OdrRequest - Id *TrieID // references storage trie of the account - Hash common.Hash - Data []byte -} - -// StoreResult stores the retrieved data in local database -func (req *CodeRequest) StoreResult(db ethdb.Database) { - db.Put(req.Hash[:], req.Data) -} - -// BlockRequest is the ODR request type for retrieving block bodies -type BlockRequest struct { - OdrRequest - Hash common.Hash - Number uint64 - Rlp []byte -} - -// StoreResult stores the retrieved data in local database -func (req *BlockRequest) StoreResult(db ethdb.Database) { - rawdb.WriteBodyRLP(db, req.Hash, req.Number, req.Rlp) -} - -// ReceiptsRequest is the ODR request type for retrieving block bodies -type ReceiptsRequest struct { - OdrRequest - Hash common.Hash - Number uint64 - Receipts types.Receipts -} - -// StoreResult stores the retrieved data in local database -func (req *ReceiptsRequest) StoreResult(db ethdb.Database) { - rawdb.WriteReceipts(db, req.Hash, req.Number, req.Receipts) -} - -// ChtRequest is the ODR request type for state/storage trie entries -type ChtRequest struct { - OdrRequest - Config *IndexerConfig - ChtNum, BlockNum uint64 - ChtRoot common.Hash - Header *types.Header - Proof *NodeSet -} - -// StoreResult stores the retrieved data in local database -func (req *ChtRequest) StoreResult(db ethdb.Database) { - hash, num := req.Header.Hash(), req.Header.Number.Uint64() - - rawdb.WriteHeader(db, req.Header) - rawdb.WriteCanonicalHash(db, hash, num) -} - -// BloomRequest is the ODR request type for retrieving bloom filters from a CHT structure -type BloomRequest struct { - OdrRequest - Config *IndexerConfig - BloomTrieNum uint64 - BitIdx uint - SectionIndexList []uint64 - BloomTrieRoot common.Hash - BloomBits [][]byte - Proofs *NodeSet -} - -// StoreResult stores the retrieved data in local database -func (req *BloomRequest) StoreResult(db ethdb.Database) { - for i, sectionIdx := range req.SectionIndexList { - sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*req.Config.BloomTrieSize-1) - // if we don't have the canonical hash stored for this section head number, we'll still store it under - // a key with a zero sectionHead. GetBloomBits will look there too if we still don't have the canonical - // hash. In the unlikely case we've retrieved the section head hash since then, we'll just retrieve the - // bit vector again from the network. - rawdb.WriteBloomBits(db, req.BitIdx, sectionIdx, sectionHead, req.BloomBits[i]) - } -} diff --git a/light/odr_test.go b/light/odr_test.go deleted file mode 100644 index 91244c6b75..0000000000 --- a/light/odr_test.go +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright 2016 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 . - -package light - -import ( - "bytes" - "context" - "errors" - "math/big" - "testing" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/common/math" - "github.com/AlayaNetwork/Alaya-Go/consensus" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/state" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/core/vm" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/params" - "github.com/AlayaNetwork/Alaya-Go/rlp" - "github.com/AlayaNetwork/Alaya-Go/trie" - _ "github.com/AlayaNetwork/Alaya-Go/x/xcom" -) - -var ( - testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) - testBankFunds = big.NewInt(100000000) - - acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") - acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") - acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey) - acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey) - - testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056") - testContractAddr common.Address -) - -type testOdr struct { - OdrBackend - indexerConfig *IndexerConfig - sdb, ldb ethdb.Database - disable bool -} - -func (odr *testOdr) Database() ethdb.Database { - return odr.ldb -} - -var ErrOdrDisabled = errors.New("ODR disabled") - -func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error { - if odr.disable { - return ErrOdrDisabled - } - switch req := req.(type) { - case *BlockRequest: - number := rawdb.ReadHeaderNumber(odr.sdb, req.Hash) - if number != nil { - req.Rlp = rawdb.ReadBodyRLP(odr.sdb, req.Hash, *number) - } - case *ReceiptsRequest: - number := rawdb.ReadHeaderNumber(odr.sdb, req.Hash) - if number != nil { - req.Receipts = rawdb.ReadRawReceipts(odr.sdb, req.Hash, *number) - } - case *TrieRequest: - t, _ := trie.New(req.Id.Root, trie.NewDatabase(odr.sdb)) - nodes := NewNodeSet() - t.Prove(req.Key, 0, nodes) - req.Proof = nodes - case *CodeRequest: - req.Data, _ = odr.sdb.Get(req.Hash[:]) - } - req.StoreResult(odr.ldb) - return nil -} - -func (odr *testOdr) IndexerConfig() *IndexerConfig { - return odr.indexerConfig -} - -type odrTestFn func(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) - -func TestOdrGetBlockLes2(t *testing.T) { testChainOdr(t, 1, odrGetBlock) } - -func odrGetBlock(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) { - var block *types.Block - if bc != nil { - block = bc.GetBlockByHash(bhash) - } else { - block, _ = lc.GetBlockByHash(ctx, bhash) - } - if block == nil { - return nil, nil - } - rlp, _ := rlp.EncodeToBytes(block) - return rlp, nil -} - -func TestOdrGetReceiptsLes2(t *testing.T) { testChainOdr(t, 1, odrGetReceipts) } - -func odrGetReceipts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) { - var receipts types.Receipts - if bc != nil { - number := rawdb.ReadHeaderNumber(db, bhash) - if number != nil { - receipts = rawdb.ReadReceipts(db, bhash, *number, bc.Config()) - } - } else { - number := rawdb.ReadHeaderNumber(db, bhash) - if number != nil { - receipts, _ = GetBlockReceipts(ctx, lc.Odr(), bhash, *number) - } - } - if receipts == nil { - return nil, nil - } - rlp, _ := rlp.EncodeToBytes(receipts) - return rlp, nil -} - -func TestOdrAccountsLes2(t *testing.T) { testChainOdr(t, 1, odrAccounts) } - -func odrAccounts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) { - dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678") - acc := []common.Address{testBankAddress, acc1Addr, acc2Addr, dummyAddr} - - var st *state.StateDB - if bc == nil { - header := lc.GetHeaderByHash(bhash) - st = NewState(ctx, header, lc.Odr()) - } else { - header := bc.GetHeaderByHash(bhash) - st, _ = state.New(header.Root, state.NewDatabase(db)) - } - - var res []byte - for _, addr := range acc { - bal := st.GetBalance(addr) - rlp, _ := rlp.EncodeToBytes(bal) - res = append(res, rlp...) - } - return res, st.Error() -} - -//func TestOdrContractCallLes2(t *testing.T) { testChainOdr(t, 1, odrContractCall) } - -type callmsg struct { - types.Message -} - -func (callmsg) CheckNonce() bool { return false } - -func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) { - data := common.Hex2Bytes("60CD26850000000000000000000000000000000000000000000000000000000000000000") - config := params.TestChainConfig - - var res []byte - for i := 0; i < 3; i++ { - data[35] = byte(i) - - var ( - st *state.StateDB - header *types.Header - chain core.ChainContext - ) - if bc == nil { - chain = lc - header = lc.GetHeaderByHash(bhash) - st = NewState(ctx, header, lc.Odr()) - } else { - chain = bc - header = bc.GetHeaderByHash(bhash) - st, _ = state.New(header.Root, state.NewDatabase(db)) - } - - // Perform read-only call. - st.SetBalance(testBankAddress, math.MaxBig256) - msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, new(big.Int), data, false)} - context := core.NewEVMContext(msg, header, chain) - vmenv := vm.NewEVM(context, nil, st, config, vm.Config{}) - gp := new(core.GasPool).AddGas(math.MaxUint64) - ret, _ := core.ApplyMessage(vmenv, msg, gp) - res = append(res, ret.Return()...) - if st.Error() != nil { - return res, st.Error() - } - } - return res, nil -} - -func testChainGen(i int, block *core.BlockGen) { - signer := types.NewEIP155Signer(params.TestChainConfig.ChainID) - switch i { - case 0: - // In block 1, the test bank sends account #1 some ether. - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testBankKey) - block.AddTx(tx) - case 1: - // In block 2, the test bank sends some more ether to account #1. - // acc1Addr passes it on to account #2. - // acc1Addr creates a test contract. - tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testBankKey) - nonce := block.TxNonce(acc1Addr) - tx2, _ := types.SignTx(types.NewTransaction(nonce, acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key) - nonce++ - //tx3, _ := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 1000000, big.NewInt(0), testContractCode), signer, acc1Key) - testContractAddr = crypto.CreateAddress(acc1Addr, nonce) - block.AddTx(tx1) - block.AddTx(tx2) - //block.AddTx(tx3) - case 2: - // Block 3 is empty but was mined by account #2. - block.SetCoinbase(acc2Addr) - block.SetExtra(common.FromHex("0xd782070186706c61746f6e86676f312e3131856c696e757800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")) - data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001") - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, nil, data), signer, testBankKey) - block.AddTx(tx) - case 3: - // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). - b2 := block.PrevBlock(1).Header() - b2.Extra = common.FromHex("0xd782070186706c61746f6e86676f312e3131856c696e757800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - // block.AddUncle(b2) - b3 := block.PrevBlock(2).Header() - b3.Extra = []byte("foo") - // block.AddUncle(b3) - data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002") - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, nil, data), signer, testBankKey) - block.AddTx(tx) - } -} - -func testChainOdr(t *testing.T, protocol int, fn odrTestFn) { - var ( - sdb = rawdb.NewMemoryDatabase() - ldb = rawdb.NewMemoryDatabase() - gspec = core.Genesis{Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}} - genesis = gspec.MustCommit(sdb) - ) - gspec.MustCommit(ldb) - // Assemble the test environment - blockchain, _ := core.NewBlockChain(sdb, nil, params.TestChainConfig, consensus.NewFaker(), vm.Config{}, nil) - gchain, _ := core.GenerateChain(params.TestChainConfig, genesis, consensus.NewFaker(), sdb, 4, testChainGen) - if _, err := blockchain.InsertChain(gchain); err != nil { - t.Fatal(err) - } - - odr := &testOdr{sdb: sdb, ldb: ldb, indexerConfig: TestClientIndexerConfig} - lightchain, err := NewLightChain(odr, params.TestChainConfig, consensus.NewFaker()) - if err != nil { - t.Fatal(err) - } - headers := make([]*types.Header, len(gchain)) - for i, block := range gchain { - headers[i] = block.Header() - } - if _, err := lightchain.InsertHeaderChain(headers, 1); err != nil { - t.Fatal(err) - } - - test := func(expFail int) { - for i := uint64(0); i <= blockchain.CurrentHeader().Number.Uint64(); i++ { - bhash := rawdb.ReadCanonicalHash(sdb, i) - b1, err := fn(NoOdr, sdb, blockchain, nil, bhash) - if err != nil { - t.Fatalf("error in full-node test for block %d: %v", i, err) - } - - ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) - defer cancel() - - exp := i < uint64(expFail) - b2, err := fn(ctx, ldb, nil, lightchain, bhash) - if err != nil && exp { - t.Errorf("error in ODR test for block %d: %v", i, err) - } - - eq := bytes.Equal(b1, b2) - if exp && !eq { - t.Errorf("ODR test output for block %d doesn't match full node", i) - } - } - } - - // expect retrievals to fail (except genesis block) without a les peer - t.Log("checking without ODR") - odr.disable = true - test(1) - - // expect all retrievals to pass with ODR enabled - t.Log("checking with ODR") - odr.disable = false - test(len(gchain)) - - // still expect all retrievals to pass, now data should be cached locally - t.Log("checking without ODR, should be cached") - odr.disable = true - test(len(gchain)) -} diff --git a/light/odr_util.go b/light/odr_util.go deleted file mode 100644 index 130cededc7..0000000000 --- a/light/odr_util.go +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright 2016 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 . - -package light - -import ( - "bytes" - "context" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -var sha3Nil = crypto.Keccak256Hash(nil) - -func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*types.Header, error) { - db := odr.Database() - hash := rawdb.ReadCanonicalHash(db, number) - if (hash != common.Hash{}) { - // if there is a canonical hash, there is a header too - header := rawdb.ReadHeader(db, hash, number) - if header == nil { - panic("Canonical hash present but header not found") - } - return header, nil - } - - var ( - chtCount, sectionHeadNum uint64 - sectionHead common.Hash - ) - if odr.ChtIndexer() != nil { - chtCount, sectionHeadNum, sectionHead = odr.ChtIndexer().Sections() - canonicalHash := rawdb.ReadCanonicalHash(db, sectionHeadNum) - // if the CHT was injected as a trusted checkpoint, we have no canonical hash yet so we accept zero hash too - for chtCount > 0 && canonicalHash != sectionHead && canonicalHash != (common.Hash{}) { - chtCount-- - if chtCount > 0 { - sectionHeadNum = chtCount*odr.IndexerConfig().ChtSize - 1 - sectionHead = odr.ChtIndexer().SectionHead(chtCount - 1) - canonicalHash = rawdb.ReadCanonicalHash(db, sectionHeadNum) - } - } - } - if number >= chtCount*odr.IndexerConfig().ChtSize { - return nil, ErrNoTrustedCht - } - r := &ChtRequest{ChtRoot: GetChtRoot(db, chtCount-1, sectionHead), ChtNum: chtCount - 1, BlockNum: number, Config: odr.IndexerConfig()} - if err := odr.Retrieve(ctx, r); err != nil { - return nil, err - } - return r.Header, nil -} - -func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (common.Hash, error) { - hash := rawdb.ReadCanonicalHash(odr.Database(), number) - if (hash != common.Hash{}) { - return hash, nil - } - header, err := GetHeaderByNumber(ctx, odr, number) - if header != nil { - return header.Hash(), nil - } - return common.Hash{}, err -} - -// GetBodyRLP retrieves the block body (transactions) in RLP encoding. -func GetBodyRLP(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (rlp.RawValue, error) { - if data := rawdb.ReadBodyRLP(odr.Database(), hash, number); data != nil { - return data, nil - } - r := &BlockRequest{Hash: hash, Number: number} - if err := odr.Retrieve(ctx, r); err != nil { - return nil, err - } else { - return r.Rlp, nil - } -} - -// GetBody retrieves the block body (transactons) corresponding to the -// hash. -func GetBody(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Body, error) { - data, err := GetBodyRLP(ctx, odr, hash, number) - if err != nil { - return nil, err - } - body := new(types.Body) - if err := rlp.Decode(bytes.NewReader(data), body); err != nil { - return nil, err - } - return body, nil -} - -// GetBlock retrieves an entire block corresponding to the hash, assembling it -// back from the stored header and body. -func GetBlock(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Block, error) { - // Retrieve the block header and body contents - header := rawdb.ReadHeader(odr.Database(), hash, number) - if header == nil { - return nil, ErrNoHeader - } - body, err := GetBody(ctx, odr, hash, number) - if err != nil { - return nil, err - } - // Reassemble the block and return - return types.NewBlockWithHeader(header).WithBody(body.Transactions, nil), nil -} - -// GetBlockReceipts retrieves the receipts generated by the transactions included -// in a block given by its hash. -func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (types.Receipts, error) { - // Retrieve the potentially incomplete receipts from disk or network - receipts := rawdb.ReadRawReceipts(odr.Database(), hash, number) - if receipts == nil { - r := &ReceiptsRequest{Hash: hash, Number: number} - if err := odr.Retrieve(ctx, r); err != nil { - return nil, err - } - receipts = r.Receipts - } - // If the receipts are incomplete, fill the derived fields - if len(receipts) > 0 && receipts[0].TxHash == (common.Hash{}) { - block, err := GetBlock(ctx, odr, hash, number) - if err != nil { - return nil, err - } - genesis := rawdb.ReadCanonicalHash(odr.Database(), 0) - config := rawdb.ReadChainConfig(odr.Database(), genesis) - - if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Transactions()); err != nil { - return nil, err - } - rawdb.WriteReceipts(odr.Database(), hash, number, receipts) - } - return receipts, nil -} - -// GetBlockLogs retrieves the logs generated by the transactions included in a -// block given by its hash. -func GetBlockLogs(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) ([][]*types.Log, error) { - // Retrieve the potentially incomplete receipts from disk or network - receipts, err := GetBlockReceipts(ctx, odr, hash, number) - if err != nil { - return nil, err - } - // Return the logs without deriving any computed fields on the receipts - logs := make([][]*types.Log, len(receipts)) - for i, receipt := range receipts { - logs[i] = receipt.Logs - } - return logs, nil -} - -// GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to the given bit index and section indexes -func GetBloomBits(ctx context.Context, odr OdrBackend, bitIdx uint, sectionIdxList []uint64) ([][]byte, error) { - var ( - db = odr.Database() - result = make([][]byte, len(sectionIdxList)) - reqList []uint64 - reqIdx []int - ) - - var ( - bloomTrieCount, sectionHeadNum uint64 - sectionHead common.Hash - ) - if odr.BloomTrieIndexer() != nil { - bloomTrieCount, sectionHeadNum, sectionHead = odr.BloomTrieIndexer().Sections() - canonicalHash := rawdb.ReadCanonicalHash(db, sectionHeadNum) - // if the BloomTrie was injected as a trusted checkpoint, we have no canonical hash yet so we accept zero hash too - for bloomTrieCount > 0 && canonicalHash != sectionHead && canonicalHash != (common.Hash{}) { - bloomTrieCount-- - if bloomTrieCount > 0 { - sectionHeadNum = bloomTrieCount*odr.IndexerConfig().BloomTrieSize - 1 - sectionHead = odr.BloomTrieIndexer().SectionHead(bloomTrieCount - 1) - canonicalHash = rawdb.ReadCanonicalHash(db, sectionHeadNum) - } - } - } - - for i, sectionIdx := range sectionIdxList { - sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*odr.IndexerConfig().BloomSize-1) - // if we don't have the canonical hash stored for this section head number, we'll still look for - // an entry with a zero sectionHead (we store it with zero section head too if we don't know it - // at the time of the retrieval) - bloomBits, err := rawdb.ReadBloomBits(db, bitIdx, sectionIdx, sectionHead) - if err == nil { - result[i] = bloomBits - } else { - // TODO(rjl493456442) Convert sectionIndex to BloomTrie relative index - if sectionIdx >= bloomTrieCount { - return nil, ErrNoTrustedBloomTrie - } - reqList = append(reqList, sectionIdx) - reqIdx = append(reqIdx, i) - } - } - if reqList == nil { - return result, nil - } - - r := &BloomRequest{BloomTrieRoot: GetBloomTrieRoot(db, bloomTrieCount-1, sectionHead), BloomTrieNum: bloomTrieCount - 1, - BitIdx: bitIdx, SectionIndexList: reqList, Config: odr.IndexerConfig()} - if err := odr.Retrieve(ctx, r); err != nil { - return nil, err - } else { - for i, idx := range reqIdx { - result[idx] = r.BloomBits[i] - } - return result, nil - } -} diff --git a/light/postprocess.go b/light/postprocess.go deleted file mode 100644 index 33d3f3d8c1..0000000000 --- a/light/postprocess.go +++ /dev/null @@ -1,388 +0,0 @@ -// Copyright 2017 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 . - -package light - -import ( - "context" - "encoding/binary" - "errors" - "fmt" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/common/bitutil" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/params" - "github.com/AlayaNetwork/Alaya-Go/rlp" - "github.com/AlayaNetwork/Alaya-Go/trie" -) - -// IndexerConfig includes a set of configs for chain indexers. -type IndexerConfig struct { - // The block frequency for creating CHTs. - ChtSize uint64 - - // A special auxiliary field represents client's chtsize for server config, otherwise represents server's chtsize. - PairChtSize uint64 - - // The number of confirmations needed to generate/accept a canonical hash help trie. - ChtConfirms uint64 - - // The block frequency for creating new bloom bits. - BloomSize uint64 - - // The number of confirmation needed before a bloom section is considered probably final and its rotated bits - // are calculated. - BloomConfirms uint64 - - // The block frequency for creating BloomTrie. - BloomTrieSize uint64 - - // The number of confirmations needed to generate/accept a bloom trie. - BloomTrieConfirms uint64 -} - -var ( - // DefaultServerIndexerConfig wraps a set of configs as a default indexer config for server side. - DefaultServerIndexerConfig = &IndexerConfig{ - ChtSize: params.CHTFrequency, - ChtConfirms: params.HelperTrieProcessConfirmations, - BloomSize: params.BloomBitsBlocks, - BloomConfirms: params.BloomConfirms, - BloomTrieSize: params.BloomTrieFrequency, - BloomTrieConfirms: params.HelperTrieProcessConfirmations, - } - // DefaultClientIndexerConfig wraps a set of configs as a default indexer config for client side. - DefaultClientIndexerConfig = &IndexerConfig{ - ChtSize: params.CHTFrequency, - ChtConfirms: params.HelperTrieConfirmations, - BloomSize: params.BloomBitsBlocksClient, - BloomConfirms: params.HelperTrieConfirmations, - BloomTrieSize: params.BloomTrieFrequency, - BloomTrieConfirms: params.HelperTrieConfirmations, - } - // TestServerIndexerConfig wraps a set of configs as a test indexer config for server side. - TestServerIndexerConfig = &IndexerConfig{ - ChtSize: 64, - PairChtSize: 512, - ChtConfirms: 4, - BloomSize: 64, - BloomConfirms: 4, - BloomTrieSize: 512, - BloomTrieConfirms: 4, - } - // TestClientIndexerConfig wraps a set of configs as a test indexer config for client side. - TestClientIndexerConfig = &IndexerConfig{ - ChtSize: 512, - PairChtSize: 64, - ChtConfirms: 32, - BloomSize: 512, - BloomConfirms: 32, - BloomTrieSize: 512, - BloomTrieConfirms: 32, - } -) - -var ( - ErrNoTrustedCht = errors.New("no trusted canonical hash trie") - ErrNoTrustedBloomTrie = errors.New("no trusted bloom trie") - ErrNoHeader = errors.New("header not found") - chtPrefix = []byte("chtRootV2-") // chtPrefix + chtNum (uint64 big endian) -> trie root hash - ChtTablePrefix = "cht-" -) - -// ChtNode structures are stored in the Canonical Hash Trie in an RLP encoded format -type ChtNode struct { - Hash common.Hash -} - -// GetChtRoot reads the CHT root associated to the given section from the database -func GetChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash { - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], sectionIdx) - data, _ := db.Get(append(append(chtPrefix, encNumber[:]...), sectionHead.Bytes()...)) - return common.BytesToHash(data) -} - -// StoreChtRoot writes the CHT root associated to the given section into the database -func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) { - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], sectionIdx) - db.Put(append(append(chtPrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes()) -} - -// ChtIndexerBackend implements core.ChainIndexerBackend. -type ChtIndexerBackend struct { - diskdb, trieTable ethdb.Database - odr OdrBackend - triedb *trie.Database - section, sectionSize uint64 - lastHash common.Hash - trie *trie.Trie -} - -// NewChtIndexer creates a Cht chain indexer -func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64) *core.ChainIndexer { - trieTable := rawdb.NewTable(db, ChtTablePrefix) - backend := &ChtIndexerBackend{ - diskdb: db, - odr: odr, - trieTable: trieTable, - triedb: trie.NewDatabaseWithCache(trieTable, 1), // Use a tiny cache only to keep memory down - sectionSize: size, - } - return core.NewChainIndexer(db, rawdb.NewTable(db, "chtIndexV2-"), backend, size, confirms, time.Millisecond*100, "cht") -} - -// fetchMissingNodes tries to retrieve the last entry of the latest trusted CHT from the -// ODR backend in order to be able to add new entries and calculate subsequent root hashes -func (c *ChtIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error { - batch := c.trieTable.NewBatch() - r := &ChtRequest{ChtRoot: root, ChtNum: section - 1, BlockNum: section*c.sectionSize - 1, Config: c.odr.IndexerConfig()} - for { - err := c.odr.Retrieve(ctx, r) - switch err { - case nil: - r.Proof.Store(batch) - return batch.Write() - case ErrNoPeers: - // if there are no peers to serve, retry later - select { - case <-ctx.Done(): - return ctx.Err() - case <-time.After(time.Second * 10): - // stay in the loop and try again - } - default: - return err - } - } -} - -// Reset implements core.ChainIndexerBackend -func (c *ChtIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error { - var root common.Hash - if section > 0 { - root = GetChtRoot(c.diskdb, section-1, lastSectionHead) - } - var err error - c.trie, err = trie.New(root, c.triedb) - - if err != nil && c.odr != nil { - err = c.fetchMissingNodes(ctx, section, root) - if err == nil { - c.trie, err = trie.New(root, c.triedb) - } - } - - c.section = section - return err -} - -// Process implements core.ChainIndexerBackend -func (c *ChtIndexerBackend) Process(ctx context.Context, header *types.Header) error { - hash, num := header.Hash(), header.Number.Uint64() - c.lastHash = hash - - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], num) - data, _ := rlp.EncodeToBytes(ChtNode{hash}) - c.trie.Update(encNumber[:], data) - return nil -} - -// Commit implements core.ChainIndexerBackend -func (c *ChtIndexerBackend) Commit() error { - root, err := c.trie.Commit(nil) - if err != nil { - return err - } - c.triedb.Commit(root, false, true) - - log.Info("Storing CHT", "section", c.section, "head", fmt.Sprintf("%064x", c.lastHash), "root", fmt.Sprintf("%064x", root)) - StoreChtRoot(c.diskdb, c.section, c.lastHash, root) - return nil -} - -var ( - bloomTriePrefix = []byte("bltRoot-") // bloomTriePrefix + bloomTrieNum (uint64 big endian) -> trie root hash - BloomTrieTablePrefix = "blt-" -) - -// GetBloomTrieRoot reads the BloomTrie root assoctiated to the given section from the database -func GetBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash { - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], sectionIdx) - data, _ := db.Get(append(append(bloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...)) - return common.BytesToHash(data) -} - -// StoreBloomTrieRoot writes the BloomTrie root assoctiated to the given section into the database -func StoreBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) { - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], sectionIdx) - db.Put(append(append(bloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes()) -} - -// BloomTrieIndexerBackend implements core.ChainIndexerBackend -type BloomTrieIndexerBackend struct { - diskdb, trieTable ethdb.Database - triedb *trie.Database - odr OdrBackend - section uint64 - parentSize uint64 - size uint64 - bloomTrieRatio uint64 - trie *trie.Trie - sectionHeads []common.Hash -} - -// NewBloomTrieIndexer creates a BloomTrie chain indexer -func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uint64) *core.ChainIndexer { - trieTable := rawdb.NewTable(db, BloomTrieTablePrefix) - backend := &BloomTrieIndexerBackend{ - diskdb: db, - odr: odr, - trieTable: trieTable, - triedb: trie.NewDatabaseWithCache(trieTable, 1), // Use a tiny cache only to keep memory down - parentSize: parentSize, - size: size, - } - backend.bloomTrieRatio = size / parentSize - backend.sectionHeads = make([]common.Hash, backend.bloomTrieRatio) - return core.NewChainIndexer(db, rawdb.NewTable(db, "bltIndex-"), backend, size, 0, time.Millisecond*100, "bloomtrie") -} - -// fetchMissingNodes tries to retrieve the last entries of the latest trusted bloom trie from the -// ODR backend in order to be able to add new entries and calculate subsequent root hashes -func (b *BloomTrieIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error { - indexCh := make(chan uint, types.BloomBitLength) - type res struct { - nodes *NodeSet - err error - } - resCh := make(chan res, types.BloomBitLength) - for i := 0; i < 20; i++ { - go func() { - for bitIndex := range indexCh { - r := &BloomRequest{BloomTrieRoot: root, BloomTrieNum: section - 1, BitIdx: bitIndex, SectionIndexList: []uint64{section - 1}, Config: b.odr.IndexerConfig()} - for { - if err := b.odr.Retrieve(ctx, r); err == ErrNoPeers { - // if there are no peers to serve, retry later - select { - case <-ctx.Done(): - resCh <- res{nil, ctx.Err()} - return - case <-time.After(time.Second * 10): - // stay in the loop and try again - } - } else { - resCh <- res{r.Proofs, err} - break - } - } - } - }() - } - - for i := uint(0); i < types.BloomBitLength; i++ { - indexCh <- i - } - close(indexCh) - batch := b.trieTable.NewBatch() - for i := uint(0); i < types.BloomBitLength; i++ { - res := <-resCh - if res.err != nil { - return res.err - } - res.nodes.Store(batch) - } - return batch.Write() -} - -// Reset implements core.ChainIndexerBackend -func (b *BloomTrieIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error { - var root common.Hash - if section > 0 { - root = GetBloomTrieRoot(b.diskdb, section-1, lastSectionHead) - } - var err error - b.trie, err = trie.New(root, b.triedb) - if err != nil && b.odr != nil { - err = b.fetchMissingNodes(ctx, section, root) - if err == nil { - b.trie, err = trie.New(root, b.triedb) - } - } - b.section = section - return err -} - -// Process implements core.ChainIndexerBackend -func (b *BloomTrieIndexerBackend) Process(ctx context.Context, header *types.Header) error { - num := header.Number.Uint64() - b.section*b.size - if (num+1)%b.parentSize == 0 { - b.sectionHeads[num/b.parentSize] = header.Hash() - } - return nil -} - -// Commit implements core.ChainIndexerBackend -func (b *BloomTrieIndexerBackend) Commit() error { - var compSize, decompSize uint64 - - for i := uint(0); i < types.BloomBitLength; i++ { - var encKey [10]byte - binary.BigEndian.PutUint16(encKey[0:2], uint16(i)) - binary.BigEndian.PutUint64(encKey[2:10], b.section) - var decomp []byte - for j := uint64(0); j < b.bloomTrieRatio; j++ { - data, err := rawdb.ReadBloomBits(b.diskdb, i, b.section*b.bloomTrieRatio+j, b.sectionHeads[j]) - if err != nil { - return err - } - decompData, err2 := bitutil.DecompressBytes(data, int(b.parentSize/8)) - if err2 != nil { - return err2 - } - decomp = append(decomp, decompData...) - } - comp := bitutil.CompressBytes(decomp) - - decompSize += uint64(len(decomp)) - compSize += uint64(len(comp)) - if len(comp) > 0 { - b.trie.Update(encKey[:], comp) - } else { - b.trie.Delete(encKey[:]) - } - } - root, err := b.trie.Commit(nil) - if err != nil { - return err - } - b.triedb.Commit(root, false, true) - - sectionHead := b.sectionHeads[b.bloomTrieRatio-1] - log.Info("Storing bloom trie", "section", b.section, "head", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression", float64(compSize)/float64(decompSize)) - StoreBloomTrieRoot(b.diskdb, b.section, sectionHead, root) - return nil -} diff --git a/light/trie.go b/light/trie.go deleted file mode 100644 index 4b2162299d..0000000000 --- a/light/trie.go +++ /dev/null @@ -1,255 +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 . - -package light - -import ( - "context" - "errors" - "fmt" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core/state" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/trie" -) - -func NewState(ctx context.Context, head *types.Header, odr OdrBackend) *state.StateDB { - state, _ := state.New(head.Root, NewStateDatabase(ctx, head, odr)) - return state -} - -func NewStateDatabase(ctx context.Context, head *types.Header, odr OdrBackend) state.Database { - return &odrDatabase{ctx, StateTrieID(head), odr} -} - -type odrDatabase struct { - ctx context.Context - id *TrieID - backend OdrBackend -} - -func (db *odrDatabase) OpenTrie(root common.Hash) (state.Trie, error) { - return &odrTrie{db: db, id: db.id}, nil -} - -func (db *odrDatabase) OpenStorageTrie(addrHash, root common.Hash) (state.Trie, error) { - return &odrTrie{db: db, id: StorageTrieID(db.id, addrHash, root)}, nil -} - -func (db *odrDatabase) CopyTrie(t state.Trie) state.Trie { - switch t := t.(type) { - case *odrTrie: - cpy := &odrTrie{db: t.db, id: t.id} - if t.trie != nil { - cpytrie := *t.trie - cpy.trie = &cpytrie - } - return cpy - default: - panic(fmt.Errorf("unknown trie type %T", t)) - } -} - -func (db *odrDatabase) NewTrie(t state.Trie) state.Trie { - return nil -} - -func (db *odrDatabase) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) { - if codeHash == sha3Nil { - return nil, nil - } - if code, err := db.backend.Database().Get(codeHash[:]); err == nil { - return code, nil - } - id := *db.id - id.AccKey = addrHash[:] - req := &CodeRequest{Id: &id, Hash: codeHash} - err := db.backend.Retrieve(db.ctx, req) - return req.Data, err -} - -func (db *odrDatabase) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) { - code, err := db.ContractCode(addrHash, codeHash) - return len(code), err -} - -func (db *odrDatabase) TrieDB() *trie.Database { - return nil -} - -type odrTrie struct { - db *odrDatabase - id *TrieID - trie *trie.Trie -} - -func (t *odrTrie) TryGet(key []byte) ([]byte, error) { - key = crypto.Keccak256(key) - var res []byte - err := t.do(key, func() (err error) { - res, err = t.trie.TryGet(key) - return err - }) - return res, err -} - -func (t *odrTrie) TryUpdate(key, value []byte) error { - key = crypto.Keccak256(key) - return t.do(key, func() error { - return t.trie.TryUpdate(key, value) - }) -} - -func (t *odrTrie) TryDelete(key []byte) error { - key = crypto.Keccak256(key) - return t.do(key, func() error { - return t.trie.TryDelete(key) - }) -} - -func (t *odrTrie) Commit(onleaf trie.LeafCallback) (common.Hash, error) { - if t.trie == nil { - return t.id.Root, nil - } - return t.trie.Commit(onleaf) -} - -func (t *odrTrie) ParallelCommit(onleaf trie.LeafCallback) (common.Hash, error) { - return t.Commit(onleaf) -} - -func (t *odrTrie) Hash() common.Hash { - if t.trie == nil { - return t.id.Root - } - return t.trie.Hash() -} - -func (t *odrTrie) ParallelHash() common.Hash { - return t.Hash() -} - -func (t *odrTrie) NodeIterator(startkey []byte) trie.NodeIterator { - return newNodeIterator(t, startkey) -} - -func (t *odrTrie) GetKey(sha []byte) []byte { - return nil -} - -func (t *odrTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error { - return errors.New("not implemented, needs client/server interface split") -} - -// do tries and retries to execute a function until it returns with no error or -// an error type other than MissingNodeError -func (t *odrTrie) do(key []byte, fn func() error) error { - for { - var err error - if t.trie == nil { - t.trie, err = trie.New(t.id.Root, trie.NewDatabase(t.db.backend.Database())) - } - if err == nil { - err = fn() - } - if _, ok := err.(*trie.MissingNodeError); !ok { - return err - } - r := &TrieRequest{Id: t.id, Key: key} - if err := t.db.backend.Retrieve(t.db.ctx, r); err != nil { - return err - } - } -} - -type nodeIterator struct { - trie.NodeIterator - t *odrTrie - err error -} - -func newNodeIterator(t *odrTrie, startkey []byte) trie.NodeIterator { - it := &nodeIterator{t: t} - // Open the actual non-ODR trie if that hasn't happened yet. - if t.trie == nil { - it.do(func() error { - t, err := trie.New(t.id.Root, trie.NewDatabase(t.db.backend.Database())) - if err == nil { - it.t.trie = t - } - return err - }) - } - it.do(func() error { - it.NodeIterator = it.t.trie.NodeIterator(startkey) - return it.NodeIterator.Error() - }) - return it -} - -func (it *nodeIterator) Next(descend bool) bool { - var ok bool - it.do(func() error { - ok = it.NodeIterator.Next(descend) - return it.NodeIterator.Error() - }) - return ok -} - -// do runs fn and attempts to fill in missing nodes by retrieving. -func (it *nodeIterator) do(fn func() error) { - var lasthash common.Hash - for { - it.err = fn() - missing, ok := it.err.(*trie.MissingNodeError) - if !ok { - return - } - if missing.NodeHash == lasthash { - it.err = fmt.Errorf("retrieve loop for trie node %x", missing.NodeHash) - return - } - lasthash = missing.NodeHash - r := &TrieRequest{Id: it.t.id, Key: nibblesToKey(missing.Path)} - if it.err = it.t.db.backend.Retrieve(it.t.db.ctx, r); it.err != nil { - return - } - } -} - -func (it *nodeIterator) Error() error { - if it.err != nil { - return it.err - } - return it.NodeIterator.Error() -} - -func nibblesToKey(nib []byte) []byte { - if len(nib) > 0 && nib[len(nib)-1] == 0x10 { - nib = nib[:len(nib)-1] // drop terminator - } - if len(nib)&1 == 1 { - nib = append(nib, 0) // make even - } - key := make([]byte, len(nib)/2) - for bi, ni := 0, 0; ni < len(nib); bi, ni = bi+1, ni+2 { - key[bi] = nib[ni]<<4 | nib[ni+1] - } - return key -} diff --git a/light/trie_test.go b/light/trie_test.go deleted file mode 100644 index 1407227802..0000000000 --- a/light/trie_test.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2017 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 . - -package light - -import ( - "bytes" - "context" - "fmt" - "testing" - - "github.com/AlayaNetwork/Alaya-Go/consensus" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/state" - "github.com/AlayaNetwork/Alaya-Go/core/vm" - "github.com/AlayaNetwork/Alaya-Go/params" - "github.com/AlayaNetwork/Alaya-Go/trie" - "github.com/davecgh/go-spew/spew" -) - -func TestNodeIterator(t *testing.T) { - var ( - fulldb = rawdb.NewMemoryDatabase() - lightdb = rawdb.NewMemoryDatabase() - gspec = core.Genesis{Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}} - genesis = gspec.MustCommit(fulldb) - ) - gspec.MustCommit(lightdb) - blockchain, _ := core.NewBlockChain(fulldb, nil, params.TestChainConfig, consensus.NewFaker(), vm.Config{}, nil) - gchain, _ := core.GenerateChain(params.TestChainConfig, genesis, consensus.NewFaker(), fulldb, 4, testChainGen) - if _, err := blockchain.InsertChain(gchain); err != nil { - panic(err) - } - - ctx := context.Background() - odr := &testOdr{sdb: fulldb, ldb: lightdb, indexerConfig: TestClientIndexerConfig} - head := blockchain.CurrentHeader() - lightTrie, _ := NewStateDatabase(ctx, head, odr).OpenTrie(head.Root) - fullTrie, _ := state.NewDatabase(fulldb).OpenTrie(head.Root) - if err := diffTries(fullTrie, lightTrie); err != nil { - t.Fatal(err) - } -} - -func diffTries(t1, t2 state.Trie) error { - i1 := trie.NewIterator(t1.NodeIterator(nil)) - i2 := trie.NewIterator(t2.NodeIterator(nil)) - for i1.Next() && i2.Next() { - if !bytes.Equal(i1.Key, i2.Key) { - spew.Dump(i2) - return fmt.Errorf("tries have different keys %x, %x", i1.Key, i2.Key) - } - if !bytes.Equal(i1.Value, i2.Value) { - return fmt.Errorf("tries differ at key %x", i1.Key) - } - } - switch { - case i1.Err != nil: - return fmt.Errorf("full trie iterator error: %v", i1.Err) - case i2.Err != nil: - return fmt.Errorf("light trie iterator error: %v", i1.Err) - case i1.Next(): - return fmt.Errorf("full trie iterator has more k/v pairs") - case i2.Next(): - return fmt.Errorf("light trie iterator has more k/v pairs") - } - return nil -} diff --git a/light/txpool.go b/light/txpool.go deleted file mode 100644 index cca7ec1feb..0000000000 --- a/light/txpool.go +++ /dev/null @@ -1,529 +0,0 @@ -// Copyright 2016 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 . - -package light - -import ( - "context" - "fmt" - "sync" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/state" - "github.com/AlayaNetwork/Alaya-Go/core/types" - "github.com/AlayaNetwork/Alaya-Go/ethdb" - "github.com/AlayaNetwork/Alaya-Go/event" - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/params" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -const ( - // chainHeadChanSize is the size of channel listening to ChainHeadEvent. - chainHeadChanSize = 10 -) - -// txPermanent is the number of mined blocks after a mined transaction is -// considered permanent and no rollback is expected -var txPermanent = uint64(500) - -// TxPool implements the transaction pool for light clients, which keeps track -// of the status of locally created transactions, detecting if they are included -// in a block (mined) or rolled back. There are no queued transactions since we -// always receive all locally signed transactions in the same order as they are -// created. -type TxPool struct { - config *params.ChainConfig - signer types.Signer - quit chan bool - txFeed event.Feed - scope event.SubscriptionScope - chainHeadCh chan core.ChainHeadEvent - chainHeadSub event.Subscription - mu sync.RWMutex - chain *LightChain - odr OdrBackend - chainDb ethdb.Database - relay TxRelayBackend - head common.Hash - nonce map[common.Address]uint64 // "pending" nonce - pending map[common.Hash]*types.Transaction // pending transactions by tx hash - mined map[common.Hash][]*types.Transaction // mined transactions by block hash - clearIdx uint64 // earliest block nr that can contain mined tx info -} - -// TxRelayBackend provides an interface to the mechanism that forwards transacions -// to the ETH network. The implementations of the functions should be non-blocking. -// -// Send instructs backend to forward new transactions -// NewHead notifies backend about a new head after processed by the tx pool, -// including mined and rolled back transactions since the last event -// Discard notifies backend about transactions that should be discarded either -// because they have been replaced by a re-send or because they have been mined -// long ago and no rollback is expected -type TxRelayBackend interface { - Send(txs types.Transactions) - NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash) - Discard(hashes []common.Hash) -} - -// NewTxPool creates a new light transaction pool -func NewTxPool(config *params.ChainConfig, chain *LightChain, relay TxRelayBackend) *TxPool { - pool := &TxPool{ - config: config, - signer: types.NewEIP155Signer(config.ChainID), - nonce: make(map[common.Address]uint64), - pending: make(map[common.Hash]*types.Transaction), - mined: make(map[common.Hash][]*types.Transaction), - quit: make(chan bool), - chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize), - chain: chain, - relay: relay, - odr: chain.Odr(), - chainDb: chain.Odr().Database(), - head: chain.CurrentHeader().Hash(), - clearIdx: chain.CurrentHeader().Number.Uint64(), - } - // Subscribe events from blockchain - pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh) - go pool.eventLoop() - - return pool -} - -// currentState returns the light state of the current head header -func (pool *TxPool) currentState(ctx context.Context) *state.StateDB { - return NewState(ctx, pool.chain.CurrentHeader(), pool.odr) -} - -// GetNonce returns the "pending" nonce of a given address. It always queries -// the nonce belonging to the latest header too in order to detect if another -// client using the same key sent a transaction. -func (pool *TxPool) GetNonce(ctx context.Context, addr common.Address) (uint64, error) { - state := pool.currentState(ctx) - nonce := state.GetNonce(addr) - if state.Error() != nil { - return 0, state.Error() - } - sn, ok := pool.nonce[addr] - if ok && sn > nonce { - nonce = sn - } - if !ok || sn < nonce { - pool.nonce[addr] = nonce - } - return nonce, nil -} - -// txStateChanges stores the recent changes between pending/mined states of -// transactions. True means mined, false means rolled back, no entry means no change -type txStateChanges map[common.Hash]bool - -// setState sets the status of a tx to either recently mined or recently rolled back -func (txc txStateChanges) setState(txHash common.Hash, mined bool) { - val, ent := txc[txHash] - if ent && (val != mined) { - delete(txc, txHash) - } else { - txc[txHash] = mined - } -} - -// getLists creates lists of mined and rolled back tx hashes -func (txc txStateChanges) getLists() (mined []common.Hash, rollback []common.Hash) { - for hash, val := range txc { - if val { - mined = append(mined, hash) - } else { - rollback = append(rollback, hash) - } - } - return -} - -// checkMinedTxs checks newly added blocks for the currently pending transactions -// and marks them as mined if necessary. It also stores block position in the db -// and adds them to the received txStateChanges map. -func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, number uint64, txc txStateChanges) error { - // If no transactions are pending, we don't care about anything - if len(pool.pending) == 0 { - return nil - } - block, err := GetBlock(ctx, pool.odr, hash, number) - if err != nil { - return err - } - // Gather all the local transaction mined in this block - list := pool.mined[hash] - for _, tx := range block.Transactions() { - if _, ok := pool.pending[tx.Hash()]; ok { - list = append(list, tx) - } - } - // If some transactions have been mined, write the needed data to disk and update - if list != nil { - // Retrieve all the receipts belonging to this block and write the loopup table - if _, err := GetBlockReceipts(ctx, pool.odr, hash, number); err != nil { // ODR caches, ignore results - return err - } - rawdb.WriteTxLookupEntries(pool.chainDb, block) - - // Update the transaction pool's state - for _, tx := range list { - delete(pool.pending, tx.Hash()) - txc.setState(tx.Hash(), true) - } - pool.mined[hash] = list - } - return nil -} - -// rollbackTxs marks the transactions contained in recently rolled back blocks -// as rolled back. It also removes any positional lookup entries. -func (pool *TxPool) rollbackTxs(hash common.Hash, txc txStateChanges) { - batch := pool.chainDb.NewBatch() - if list, ok := pool.mined[hash]; ok { - for _, tx := range list { - txHash := tx.Hash() - rawdb.DeleteTxLookupEntry(batch, txHash) - pool.pending[txHash] = tx - txc.setState(txHash, false) - } - delete(pool.mined, hash) - } - batch.Write() -} - -// reorgOnNewHead sets a new head header, processing (and rolling back if necessary) -// the blocks since the last known head and returns a txStateChanges map containing -// the recently mined and rolled back transaction hashes. If an error (context -// timeout) occurs during checking new blocks, it leaves the locally known head -// at the latest checked block and still returns a valid txStateChanges, making it -// possible to continue checking the missing blocks at the next chain head event -func (pool *TxPool) reorgOnNewHead(ctx context.Context, newHeader *types.Header) (txStateChanges, error) { - txc := make(txStateChanges) - oldh := pool.chain.GetHeaderByHash(pool.head) - newh := newHeader - // find common ancestor, create list of rolled back and new block hashes - var oldHashes, newHashes []common.Hash - for oldh.Hash() != newh.Hash() { - if oldh.Number.Uint64() >= newh.Number.Uint64() { - oldHashes = append(oldHashes, oldh.Hash()) - oldh = pool.chain.GetHeader(oldh.ParentHash, oldh.Number.Uint64()-1) - } - if oldh.Number.Uint64() < newh.Number.Uint64() { - newHashes = append(newHashes, newh.Hash()) - newh = pool.chain.GetHeader(newh.ParentHash, newh.Number.Uint64()-1) - if newh == nil { - // happens when CHT syncing, nothing to do - newh = oldh - } - } - } - if oldh.Number.Uint64() < pool.clearIdx { - pool.clearIdx = oldh.Number.Uint64() - } - // roll back old blocks - for _, hash := range oldHashes { - pool.rollbackTxs(hash, txc) - } - pool.head = oldh.Hash() - // check mined txs of new blocks (array is in reversed order) - for i := len(newHashes) - 1; i >= 0; i-- { - hash := newHashes[i] - if err := pool.checkMinedTxs(ctx, hash, newHeader.Number.Uint64()-uint64(i), txc); err != nil { - return txc, err - } - pool.head = hash - } - - // clear old mined tx entries of old blocks - if idx := newHeader.Number.Uint64(); idx > pool.clearIdx+txPermanent { - idx2 := idx - txPermanent - if len(pool.mined) > 0 { - for i := pool.clearIdx; i < idx2; i++ { - hash := rawdb.ReadCanonicalHash(pool.chainDb, i) - if list, ok := pool.mined[hash]; ok { - hashes := make([]common.Hash, len(list)) - for i, tx := range list { - hashes[i] = tx.Hash() - } - pool.relay.Discard(hashes) - delete(pool.mined, hash) - } - } - } - pool.clearIdx = idx2 - } - - return txc, nil -} - -// blockCheckTimeout is the time limit for checking new blocks for mined -// transactions. Checking resumes at the next chain head event if timed out. -const blockCheckTimeout = time.Second * 3 - -// eventLoop processes chain head events and also notifies the tx relay backend -// about the new head hash and tx state changes -func (pool *TxPool) eventLoop() { - for { - select { - case ev := <-pool.chainHeadCh: - pool.setNewHead(ev.Block.Header()) - // hack in order to avoid hogging the lock; this part will - // be replaced by a subsequent PR. - time.Sleep(time.Millisecond) - - // System stopped - case <-pool.chainHeadSub.Err(): - return - } - } -} - -func (pool *TxPool) setNewHead(head *types.Header) { - pool.mu.Lock() - defer pool.mu.Unlock() - - ctx, cancel := context.WithTimeout(context.Background(), blockCheckTimeout) - defer cancel() - - txc, _ := pool.reorgOnNewHead(ctx, head) - m, r := txc.getLists() - pool.relay.NewHead(pool.head, m, r) - pool.signer = types.NewEIP155Signer(pool.config.ChainID) -} - -// Stop stops the light transaction pool -func (pool *TxPool) Stop() { - // Unsubscribe all subscriptions registered from txpool - pool.scope.Close() - // Unsubscribe subscriptions registered from blockchain - pool.chainHeadSub.Unsubscribe() - close(pool.quit) - log.Info("Transaction pool stopped") -} - -// SubscribeNewTxsEvent registers a subscription of core.NewTxsEvent and -// starts sending event to the given channel. -func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { - return pool.scope.Track(pool.txFeed.Subscribe(ch)) -} - -// Stats returns the number of currently pending (locally created) transactions -func (pool *TxPool) Stats() (pending int) { - pool.mu.RLock() - defer pool.mu.RUnlock() - - pending = len(pool.pending) - return -} - -// validateTx checks whether a transaction is valid according to the consensus rules. -func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error { - // Validate sender - var ( - from common.Address - err error - ) - - // Validate the transaction sender and it's sig. Throw - // if the from fields is invalid. - if from, err = types.Sender(pool.signer, tx); err != nil { - return core.ErrInvalidSender - } - // Last but not least check for nonce errors - currentState := pool.currentState(ctx) - n := currentState.GetNonce(from) - log.Debug("check tx nonce", "account", from, "stateNonce", n, "txNonce", tx.Nonce()) - if n > tx.Nonce() { - return core.ErrNonceTooLow - } - - // Check the transaction doesn't exceed the current - // block limit gas. - header := pool.chain.GetHeaderByHash(pool.head) - if header.GasLimit < tx.Gas() { - return core.ErrGasLimit - } - - // Transactions can't be negative. This may never happen - // using RLP decoded transactions but may occur if you create - // a transaction using the RPC for example. - if tx.Value().Sign() < 0 { - return core.ErrNegativeValue - } - - // Transactor should have enough funds to cover the costs - // cost == V + GP * GL - if b := currentState.GetBalance(from); b.Cmp(tx.Cost()) < 0 { - return core.ErrInsufficientFunds - } - - // Should supply enough intrinsic gas - gas, err := core.IntrinsicGas(tx.Data(), tx.To() == nil, currentState) - if err != nil { - return err - } - if tx.Gas() < gas { - return core.ErrIntrinsicGas - } - return currentState.Error() -} - -// add validates a new transaction and sets its state pending if processable. -// It also updates the locally stored nonce if necessary. -func (pool *TxPool) add(ctx context.Context, tx *types.Transaction) error { - hash := tx.Hash() - - if pool.pending[hash] != nil { - return fmt.Errorf("Known transaction (%x)", hash[:4]) - } - err := pool.validateTx(ctx, tx) - if err != nil { - return err - } - - if _, ok := pool.pending[hash]; !ok { - pool.pending[hash] = tx - - nonce := tx.Nonce() + 1 - - addr, _ := types.Sender(pool.signer, tx) - if nonce > pool.nonce[addr] { - pool.nonce[addr] = nonce - } - - // Notify the subscribers. This event is posted in a goroutine - // because it's possible that somewhere during the post "Remove transaction" - // gets called which will then wait for the global tx pool lock and deadlock. - go pool.txFeed.Send(core.NewTxsEvent{Txs: types.Transactions{tx}}) - } - - // Print a log message if low enough level is set - log.Debug("Pooled new transaction", "hash", hash, "from", log.Lazy{Fn: func() common.Address { from, _ := types.Sender(pool.signer, tx); return from }}, "to", tx.To()) - return nil -} - -// Add adds a transaction to the pool if valid and passes it to the tx relay -// backend -func (pool *TxPool) Add(ctx context.Context, tx *types.Transaction) error { - pool.mu.Lock() - defer pool.mu.Unlock() - - data, err := rlp.EncodeToBytes(tx) - if err != nil { - return err - } - - if err := pool.add(ctx, tx); err != nil { - return err - } - //fmt.Println("Send", tx.Hash()) - pool.relay.Send(types.Transactions{tx}) - - pool.chainDb.Put(tx.Hash().Bytes(), data) - return nil -} - -// AddTransactions adds all valid transactions to the pool and passes them to -// the tx relay backend -func (pool *TxPool) AddBatch(ctx context.Context, txs []*types.Transaction) { - pool.mu.Lock() - defer pool.mu.Unlock() - var sendTx types.Transactions - - for _, tx := range txs { - if err := pool.add(ctx, tx); err == nil { - sendTx = append(sendTx, tx) - } - } - if len(sendTx) > 0 { - pool.relay.Send(sendTx) - } -} - -// GetTransaction returns a transaction if it is contained in the pool -// and nil otherwise. -func (pool *TxPool) GetTransaction(hash common.Hash) *types.Transaction { - // check the txs first - if tx, ok := pool.pending[hash]; ok { - return tx - } - return nil -} - -// GetTransactions returns all currently processable transactions. -// The returned slice may be modified by the caller. -func (pool *TxPool) GetTransactions() (txs types.Transactions, err error) { - pool.mu.RLock() - defer pool.mu.RUnlock() - - txs = make(types.Transactions, len(pool.pending)) - i := 0 - for _, tx := range pool.pending { - txs[i] = tx - i++ - } - return txs, nil -} - -// Content retrieves the data content of the transaction pool, returning all the -// pending as well as queued transactions, grouped by account and nonce. -func (pool *TxPool) Content() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { - pool.mu.RLock() - defer pool.mu.RUnlock() - - // Retrieve all the pending transactions and sort by account and by nonce - pending := make(map[common.Address]types.Transactions) - for _, tx := range pool.pending { - account, _ := types.Sender(pool.signer, tx) - pending[account] = append(pending[account], tx) - } - // There are no queued transactions in a light pool, just return an empty map - queued := make(map[common.Address]types.Transactions) - return pending, queued -} - -// RemoveTransactions removes all given transactions from the pool. -func (pool *TxPool) RemoveTransactions(txs types.Transactions) { - pool.mu.Lock() - defer pool.mu.Unlock() - - var hashes []common.Hash - batch := pool.chainDb.NewBatch() - for _, tx := range txs { - hash := tx.Hash() - delete(pool.pending, hash) - batch.Delete(hash.Bytes()) - hashes = append(hashes, hash) - } - batch.Write() - pool.relay.Discard(hashes) -} - -// RemoveTx removes the transaction with the given hash from the pool. -func (pool *TxPool) RemoveTx(hash common.Hash) { - pool.mu.Lock() - defer pool.mu.Unlock() - // delete from pending pool - delete(pool.pending, hash) - pool.chainDb.Delete(hash[:]) - pool.relay.Discard([]common.Hash{hash}) -} diff --git a/light/txpool_test.go b/light/txpool_test.go deleted file mode 100644 index a596c8c51c..0000000000 --- a/light/txpool_test.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2016 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 . - -package light - -import ( - "context" - "math" - "math/big" - "testing" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/consensus" - "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/core/types" - - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/vm" - "github.com/AlayaNetwork/Alaya-Go/params" -) - -type testTxRelay struct { - send, discard, mined chan int -} - -func (self *testTxRelay) Send(txs types.Transactions) { - self.send <- len(txs) -} - -func (self *testTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash) { - m := len(mined) - if m != 0 { - self.mined <- m - } -} - -func (self *testTxRelay) Discard(hashes []common.Hash) { - self.discard <- len(hashes) -} - -const poolTestTxs = 1000 -const poolTestBlocks = 100 - -// test tx 0..n-1 -var testTx [poolTestTxs]*types.Transaction - -// txs sent before block i -func sentTx(i int) int { - return int(math.Pow(float64(i)/float64(poolTestBlocks), 0.9) * poolTestTxs) -} - -// txs included in block i or before that (minedTx(i) <= sentTx(i)) -func minedTx(i int) int { - return int(math.Pow(float64(i)/float64(poolTestBlocks), 1.1) * poolTestTxs) -} - -func txPoolTestChainGen(i int, block *core.BlockGen) { - s := minedTx(i) - e := minedTx(i + 1) - for i := s; i < e; i++ { - block.AddTx(testTx[i]) - } -} - -func TestTxPool(t *testing.T) { - for i := range testTx { - testTx[i], _ = types.SignTx(types.NewTransaction(uint64(i), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), types.NewEIP155Signer(params.TestChainConfig.ChainID), testBankKey) - } - - var ( - sdb = rawdb.NewMemoryDatabase() - ldb = rawdb.NewMemoryDatabase() - gspec = core.Genesis{Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}} - genesis = gspec.MustCommit(sdb) - ) - gspec.MustCommit(ldb) - // Assemble the test environment - blockchain, _ := core.NewBlockChain(sdb, nil, params.TestChainConfig, consensus.NewFaker(), vm.Config{}, nil) - gchain, _ := core.GenerateChain(params.TestChainConfig, genesis, consensus.NewFaker(), sdb, poolTestBlocks, txPoolTestChainGen) - if _, err := blockchain.InsertChain(gchain); err != nil { - panic(err) - } - - odr := &testOdr{sdb: sdb, ldb: ldb, indexerConfig: TestClientIndexerConfig} - relay := &testTxRelay{ - send: make(chan int, 1), - discard: make(chan int, 1), - mined: make(chan int, 1), - } - lightchain, _ := NewLightChain(odr, params.TestChainConfig, consensus.NewFaker()) - txPermanent = 50 - pool := NewTxPool(params.TestChainConfig, lightchain, relay) - //TODO: cftf.faker do not supported - relay.mined <- 1 - - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - - for ii, block := range gchain { - i := ii + 1 - s := sentTx(i - 1) - e := sentTx(i) - for i := s; i < e; i++ { - pool.Add(ctx, testTx[i]) - got := <-relay.send - exp := 1 - if got != exp { - t.Errorf("relay.Send expected len = %d, got %d", exp, got) - } - } - - if _, err := lightchain.InsertHeaderChain([]*types.Header{block.Header()}, 1); err != nil { - panic(err) - } - } -} diff --git a/mobile/discover.go b/mobile/discover.go index 48f6e54cd1..dcd6c5b45a 100644 --- a/mobile/discover.go +++ b/mobile/discover.go @@ -22,12 +22,12 @@ package platon import ( "errors" - "github.com/AlayaNetwork/Alaya-Go/p2p/discv5" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" ) // Enode represents a host on the network. type Enode struct { - node *discv5.Node + node *enode.Node } // NewEnode parses a node designator. @@ -53,8 +53,8 @@ type Enode struct { // and UDP discovery port 30301. // // enode://@10.3.58.6:16789?discport=30301 -func NewEnode(rawurl string) (enode *Enode, _ error) { - node, err := discv5.ParseNode(rawurl) +func NewEnode(rawurl string) (*Enode, error) { + node, err := enode.Parse(enode.ValidSchemes, rawurl) if err != nil { return nil, err } @@ -62,12 +62,12 @@ func NewEnode(rawurl string) (enode *Enode, _ error) { } // Enodes represents a slice of accounts. -type Enodes struct{ nodes []*discv5.Node } +type Enodes struct{ nodes []*enode.Node } // NewEnodes creates a slice of uninitialized enodes. func NewEnodes(size int) *Enodes { return &Enodes{ - nodes: make([]*discv5.Node, size), + nodes: make([]*enode.Node, size), } } diff --git a/mobile/params.go b/mobile/params.go index 4ae248c610..365cc34f28 100644 --- a/mobile/params.go +++ b/mobile/params.go @@ -21,8 +21,9 @@ package platon import ( "encoding/json" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/core" - "github.com/AlayaNetwork/Alaya-Go/p2p/discv5" "github.com/AlayaNetwork/Alaya-Go/params" ) @@ -38,9 +39,13 @@ func TestnetGenesis() string { // FoundationBootnodes returns the enode URLs of the P2P bootstrap nodes operated // by the foundation running the V5 discovery protocol. func FoundationBootnodes() *Enodes { - nodes := &Enodes{nodes: make([]*discv5.Node, len(params.DiscoveryV5Bootnodes))} + nodes := &Enodes{nodes: make([]*enode.Node, len(params.DiscoveryV5Bootnodes))} for i, url := range params.DiscoveryV5Bootnodes { - nodes.nodes[i] = discv5.MustParseNode(url) + var err error + nodes.nodes[i], err = enode.Parse(enode.ValidSchemes, url) + if err != nil { + panic("invalid node URL: " + err.Error()) + } } return nodes } diff --git a/mobile/platon.go b/mobile/platon.go index cfcbd14a81..b050d9d99b 100644 --- a/mobile/platon.go +++ b/mobile/platon.go @@ -21,6 +21,7 @@ package platon import ( "encoding/json" + "errors" "fmt" "path/filepath" @@ -30,7 +31,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/ethclient" "github.com/AlayaNetwork/Alaya-Go/ethstats" "github.com/AlayaNetwork/Alaya-Go/internal/debug" - "github.com/AlayaNetwork/Alaya-Go/les" "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/p2p/nat" @@ -162,17 +162,18 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { ethConf.NetworkId = uint64(config.EthereumNetworkID) ethConf.DatabaseCache = config.EthereumDatabaseCache if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - return les.New(ctx, ðConf) + return nil, errors.New("not support light yet") + //return les.New(ctx, ðConf) }); err != nil { return nil, fmt.Errorf("ethereum init: %v", err) } // If netstats reporting is requested, do it if config.EthereumNetStats != "" { if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - var lesServ *les.LightEthereum - ctx.Service(&lesServ) + //var lesServ *les.LightEthereum + //ctx.Service(&lesServ) - return ethstats.New(config.EthereumNetStats, nil, lesServ) + return ethstats.New(config.EthereumNetStats, nil) }); err != nil { return nil, fmt.Errorf("netstats init: %v", err) } diff --git a/node/api.go b/node/api.go index 681c2d7a68..3e40de71ac 100644 --- a/node/api.go +++ b/node/api.go @@ -52,7 +52,7 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) { return false, ErrNodeStopped } // Try to add the url as a static peer and return - node, err := enode.ParseV4(url) + node, err := enode.Parse(enode.ValidSchemes, url) if err != nil { return false, fmt.Errorf("invalid enode: %v", err) } @@ -68,7 +68,7 @@ func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) { return false, ErrNodeStopped } // Try to remove the url as a static peer and return - node, err := enode.ParseV4(url) + node, err := enode.Parse(enode.ValidSchemes, url) if err != nil { return false, fmt.Errorf("invalid enode: %v", err) } diff --git a/node/config.go b/node/config.go index 9c94fa48d5..633739205d 100644 --- a/node/config.go +++ b/node/config.go @@ -444,7 +444,7 @@ func (c *Config) parsePersistentNodes(w *bool, path string) []*enode.Node { if url == "" { continue } - node, err := enode.ParseV4(url) + node, err := enode.Parse(enode.ValidSchemes, url) if err != nil { log.Error(fmt.Sprintf("Node URL %s: %v\n", url, err)) continue diff --git a/node/node_test.go b/node/node_test.go index a5c28dcda8..0337ae2d8e 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -505,7 +505,7 @@ func TestProtocolGather(t *testing.T) { defer stack.Stop() protocols := stack.Server().Protocols - if len(protocols) != 26 { + if len(protocols) != 11 { t.Fatalf("mismatching number of protocols launched: have %d, want %d", len(protocols), 26) } for id, config := range services { diff --git a/p2p/dial.go b/p2p/dial.go index 904d9cb0da..472bbe2a2f 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -17,29 +17,31 @@ package p2p import ( - "container/heap" + "context" + crand "crypto/rand" + "encoding/binary" "errors" "fmt" + mrand "math/rand" "net" + "sync" "time" + "github.com/AlayaNetwork/Alaya-Go/common/mclock" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" ) const ( - // This is the amount of time spent waiting in between - // redialing a certain node. - dialHistoryExpiration = 30 * time.Second + // This is the amount of time spent waiting in between redialing a certain node. The + // limit is a bit higher than inboundThrottleTime to prevent failing dials in small + // private networks. + dialHistoryExpiration = inboundThrottleTime + 5*time.Second - // Discovery lookups are throttled and can only run - // once every few seconds. - lookupInterval = 4 * time.Second - - // If no peers are found for this amount of time, the initial bootnodes are - // attempted to be connected. - fallbackInterval = 20 * time.Second + // Config for the "Looking for peers" message. + dialStatsLogInterval = 10 * time.Second // printed at most this often + dialStatsPeerLimit = 3 // but not if more than this many dialed peers // Endpoint resolution is throttled with bounded backoff. initialResolveDelay = 60 * time.Second @@ -49,350 +51,555 @@ const ( type removeConsensusPeerFn func(node *enode.Node) // 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 { - Dial(*enode.Node) (net.Conn, error) + Dial(context.Context, *enode.Node) (net.Conn, error) } -// TCPDialer implements the NodeDialer interface by using a net.Dialer to -// create TCP connections to nodes in the network -type TCPDialer struct { - *net.Dialer +type nodeResolver interface { + Resolve(*enode.Node) *enode.Node } -// Dial creates a TCP connection to the node -func (t TCPDialer) Dial(dest *enode.Node) (net.Conn, error) { - addr := &net.TCPAddr{IP: dest.IP(), Port: dest.TCP()} - return t.Dialer.Dial("tcp", addr.String()) +// tcpDialer implements NodeDialer using real TCP connections. +type tcpDialer struct { + d *net.Dialer } -// dialstate schedules dials and discovery lookups. -// it get's a chance to compute new tasks on every iteration -// of the main loop in Server.run. -type dialstate struct { - maxDynDials int - ntab discoverTable - netrestrict *netutil.Netlist - self enode.ID - - lookupRunning bool - dialing map[enode.ID]connFlag - lookupBuf []*enode.Node // current discovery lookup results - randomNodes []*enode.Node // filled from Table - static map[enode.ID]*dialTask - //consensus map[discover.NodeID]*dialTask - consensus *dialedTasks - hist *dialHistory - - start time.Time // time when the dialer was first used - bootnodes []*enode.Node // default dials when there are no peers +func (t tcpDialer) Dial(ctx context.Context, dest *enode.Node) (net.Conn, error) { + return t.d.DialContext(ctx, "tcp", nodeAddr(dest).String()) } -type discoverTable interface { - Close() - Resolve(*enode.Node) *enode.Node - LookupRandom() []*enode.Node - ReadRandomNodes([]*enode.Node) int +func nodeAddr(n *enode.Node) net.Addr { + return &net.TCPAddr{IP: n.IP(), Port: n.TCP()} } -// the dial history remembers recent dials. -type dialHistory []pastDial +// checkDial errors: +var ( + errSelf = errors.New("is self") + errAlreadyDialing = errors.New("already dialing") + errAlreadyConnected = errors.New("already connected") + errRecentlyDialed = errors.New("recently dialed") + errNetRestrict = errors.New("not contained in netrestrict list") + errNoPort = errors.New("node does not provide TCP port") +) -// pastDial is an entry in the dial history. -type pastDial struct { - id enode.ID - exp time.Time -} +// dialer creates outbound connections and submits them into Server. +// Two types of peer connections can be created: +// +// - static dials are pre-configured connections. The dialer attempts +// keep these nodes connected at all times. +// +// - dynamic dials are created from node discovery results. The dialer +// continuously reads candidate nodes from its input iterator and attempts +// to create peer connections to nodes arriving through the iterator. +// +type dialScheduler struct { + dialConfig + setupFunc dialSetupFunc + wg sync.WaitGroup + cancel context.CancelFunc + ctx context.Context + nodesIn chan *enode.Node + doneCh chan *dialTask + addStaticCh chan *enode.Node + remStaticCh chan *enode.Node + addPeerCh chan *conn + remPeerCh chan *conn + + addconsensus chan *enode.Node + removeconsensus chan *enode.Node + + // Everything below here belongs to loop and + // should only be accessed by code on the loop goroutine. + dialing map[enode.ID]*dialTask // active tasks + peers map[enode.ID]struct{} // all connected peers + dialPeers int // current number of dialed peers + + // The static map tracks all static dial tasks. The subset of usable static dial tasks + // (i.e. those passing checkDial) is kept in staticPool. The scheduler prefers + // launching random static tasks from the pool over launching dynamic dials from the + // iterator. + static map[enode.ID]*dialTask + staticPool []*dialTask -type task interface { - Do(*Server) -} + consensus *dialedTasks -// A dialTask is generated for each node that is dialed. Its -// fields cannot be accessed while the task is running. -type dialTask struct { - flags connFlag - dest *enode.Node - lastResolved time.Time - resolveDelay time.Duration -} + // The dial history keeps recently dialed nodes. Members of history are not dialed. + history expHeap + historyTimer mclock.Timer + historyTimerTime mclock.AbsTime -// discoverTask runs discovery table operations. -// Only one discoverTask is active at any time. -// discoverTask.Do performs a random lookup. -type discoverTask struct { - results []*enode.Node + // for logStats + lastStatsLog mclock.AbsTime + doneSinceLastLog int } -// A waitExpireTask is generated if there are no other tasks -// to keep the loop in Server.run ticking. -type waitExpireTask struct { - time.Duration +type dialSetupFunc func(net.Conn, connFlag, *enode.Node) error + +type dialConfig struct { + self enode.ID // our own ID + maxDialPeers int // maximum number of dialed peers + MaxConsensusPeers int + maxActiveDials int // maximum number of active dials + netRestrict *netutil.Netlist // IP netrestrict list, disabled if nil + resolver nodeResolver + dialer NodeDialer + log log.Logger + clock mclock.Clock + rand *mrand.Rand } -func newDialState(self enode.ID, static []*enode.Node, bootnodes []*enode.Node, ntab discoverTable, maxdyn int, netrestrict *netutil.Netlist, maxConsensusPeers int) *dialstate { - s := &dialstate{ - maxDynDials: maxdyn, - ntab: ntab, - self: self, - netrestrict: netrestrict, - static: make(map[enode.ID]*dialTask), - consensus: NewDialedTasks(maxConsensusPeers*2, nil), - dialing: make(map[enode.ID]connFlag), - bootnodes: make([]*enode.Node, len(bootnodes)), - randomNodes: make([]*enode.Node, maxdyn/2), - hist: new(dialHistory), +func (cfg dialConfig) withDefaults() dialConfig { + if cfg.maxActiveDials == 0 { + cfg.maxActiveDials = defaultMaxPendingPeers + } + if cfg.log == nil { + cfg.log = log.Root() + } + if cfg.clock == nil { + cfg.clock = mclock.System{} } - copy(s.bootnodes, bootnodes) - for _, n := range static { - s.addStatic(n) + if cfg.rand == nil { + seedb := make([]byte, 8) + crand.Read(seedb) + seed := int64(binary.BigEndian.Uint64(seedb)) + cfg.rand = mrand.New(mrand.NewSource(seed)) } - return s + return cfg +} + +func newDialScheduler(config dialConfig, it enode.Iterator, setupFunc dialSetupFunc) *dialScheduler { + d := &dialScheduler{ + dialConfig: config.withDefaults(), + setupFunc: setupFunc, + dialing: make(map[enode.ID]*dialTask), + static: make(map[enode.ID]*dialTask), + peers: make(map[enode.ID]struct{}), + doneCh: make(chan *dialTask), + nodesIn: make(chan *enode.Node), + addStaticCh: make(chan *enode.Node), + remStaticCh: make(chan *enode.Node), + addPeerCh: make(chan *conn), + remPeerCh: make(chan *conn), + addconsensus: make(chan *enode.Node), + removeconsensus: make(chan *enode.Node), + consensus: NewDialedTasks(config.MaxConsensusPeers*2, nil), + } + d.lastStatsLog = d.clock.Now() + d.ctx, d.cancel = context.WithCancel(context.Background()) + d.wg.Add(2) + go d.readNodes(it) + go d.loop(it) + return d } -func (s *dialstate) addStatic(n *enode.Node) { - // This overwrites the task instead of updating an existing - // entry, giving users the opportunity to force a resolve operation. - s.static[n.ID()] = &dialTask{flags: staticDialedConn, dest: n} +// stop shuts down the dialer, canceling all current dial tasks. +func (d *dialScheduler) stop() { + d.cancel() + d.wg.Wait() } -func (s *dialstate) removeStatic(n *enode.Node) { - // This removes a task so future attempts to connect will not be made. - delete(s.static, n.ID()) - // This removes a previous dial timestamp so that application - // can force a server to reconnect with chosen peer immediately. - s.hist.remove(n.ID()) +// addStatic adds a static dial candidate. +func (d *dialScheduler) addStatic(n *enode.Node) { + select { + case d.addStaticCh <- n: + case <-d.ctx.Done(): + } } -func (s *dialstate) addConsensus(n *enode.Node) { - log.Warn("dial adding consensus node", "node", n) - //s.consensus[n.ID] = &dialTask{flags: consensusDialedConn, dest: n} - s.consensus.AddTask(&dialTask{flags: consensusDialedConn, dest: n}) +// removeStatic removes a static dial candidate. +func (d *dialScheduler) removeStatic(n *enode.Node) { + select { + case d.remStaticCh <- n: + case <-d.ctx.Done(): + } } -func (s *dialstate) removeConsensus(n *enode.Node) { - //delete(s.consensus, n.ID) - s.consensus.RemoveTask(n) - s.hist.remove(n.ID()) +func (d *dialScheduler) addConsensus(n *enode.Node) { + select { + case d.addconsensus <- n: + case <-d.ctx.Done(): + } } -func (s *dialstate) removeConsensusFromQueue(n *enode.Node) { - //delete(s.consensus, n.ID) - //s.consensus.RemoveTask(n.ID) - s.hist.remove(n.ID()) +func (d *dialScheduler) removeConsensus(n *enode.Node) { + select { + case d.removeconsensus <- n: + case <-d.ctx.Done(): + } } -func (s *dialstate) initRemoveConsensusPeerFn(removeConsensusPeerFn removeConsensusPeerFn) { - s.consensus.InitRemoveConsensusPeerFn(removeConsensusPeerFn) +func (d *dialScheduler) removeConsensusFromQueue(n *enode.Node) { + d.history.remove(string(n.ID().Bytes())) } -func (s *dialstate) newTasks(nRunning int, peers map[enode.ID]*Peer, now time.Time) []task { - if s.start.IsZero() { - s.start = now - } +func (d *dialScheduler) initRemoveConsensusPeerFn(removeConsensusPeerFn removeConsensusPeerFn) { + d.consensus.InitRemoveConsensusPeerFn(removeConsensusPeerFn) +} - var newtasks []task - addDial := func(flag connFlag, n *enode.Node) bool { - 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) - return false - } - s.dialing[n.ID()] = flag - newtasks = append(newtasks, &dialTask{flags: flag, dest: n}) - return true +// peerAdded updates the peer set. +func (d *dialScheduler) peerAdded(c *conn) { + select { + case d.addPeerCh <- c: + case <-d.ctx.Done(): } +} - // Compute number of dynamic dials necessary at this point. - needDynDials := s.maxDynDials - for _, p := range peers { - if p.rw.is(dynDialedConn) { - needDynDials-- - } +// peerRemoved updates the peer set. +func (d *dialScheduler) peerRemoved(c *conn) { + select { + case d.remPeerCh <- c: + case <-d.ctx.Done(): } - for _, flag := range s.dialing { - if flag&dynDialedConn != 0 { - needDynDials-- +} + +// loop is the main loop of the dialer. +func (d *dialScheduler) loop(it enode.Iterator) { + var ( + nodesCh chan *enode.Node + historyExp = make(chan struct{}, 1) + ) + +loop: + for { + // Launch new dials if slots are available. + slots := d.freeDialSlots() + slots -= d.startConsensusDials(slots) + slots -= d.startStaticDials(slots) + if slots > 0 { + nodesCh = d.nodesIn + } else { + nodesCh = nil } - } + d.rearmHistoryTimer(historyExp) + d.logStats() + + select { + case node := <-nodesCh: + if err := d.checkDial(node); err != nil { + d.log.Trace("Discarding dial candidate", "id", node.ID(), "ip", node.IP(), "reason", err) + } else { + d.startDial(newDialTask(node, dynDialedConn)) + } - // Expire the dial history on every invocation. - s.hist.expire(now) + case task := <-d.doneCh: + id := task.dest.ID() + delete(d.dialing, id) + d.updateStaticPool(id) + d.doneSinceLastLog++ - // Create dials for static nodes if they are not connected. - for id, t := range s.static { - err := s.checkDial(t.dest, peers) - switch err { - 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) - delete(s.static, t.dest.ID()) - case nil: - s.dialing[id] = t.flags - newtasks = append(newtasks, t) + case c := <-d.addPeerCh: + if c.is(dynDialedConn) || c.is(staticDialedConn) { + d.dialPeers++ + } + id := c.node.ID() + d.peers[id] = struct{}{} + // Remove from static pool because the node is now connected. + task := d.static[id] + if task != nil && task.staticPoolIndex >= 0 { + d.removeFromStaticPool(task.staticPoolIndex) + } + // TODO: cancel dials to connected peers + + case c := <-d.remPeerCh: + if c.is(dynDialedConn) || c.is(staticDialedConn) { + d.dialPeers-- + } + delete(d.peers, c.node.ID()) + d.updateStaticPool(c.node.ID()) + + case node := <-d.addStaticCh: + id := node.ID() + _, exists := d.static[id] + d.log.Trace("Adding static node", "id", id, "ip", node.IP(), "added", !exists) + if exists { + continue loop + } + task := newDialTask(node, staticDialedConn) + d.static[id] = task + if d.checkDial(node) == nil { + d.addToStaticPool(task) + } + + case node := <-d.remStaticCh: + id := node.ID() + task := d.static[id] + d.log.Trace("Removing static node", "id", id, "ok", task != nil) + if task != nil { + delete(d.static, id) + if task.staticPoolIndex >= 0 { + d.removeFromStaticPool(task.staticPoolIndex) + } + } + case node := <-d.addconsensus: + log.Warn("dial adding consensus node", "node", node) + d.consensus.AddTask(&dialTask{flags: consensusDialedConn, dest: node}) + case node := <-d.removeconsensus: + d.consensus.RemoveTask(node) + + case <-historyExp: + d.expireHistory() + + case <-d.ctx.Done(): + it.Close() + break loop } } - // Create dials for consensus nodes if they are not connected. - for _, t := range s.consensus.ListTask() { - err := s.checkDial(t.dest, peers) - switch err { - case errNotWhitelisted, errSelf: - //delete(s.consensus, t.dest.ID) - s.consensus.RemoveTask(t.dest) - case nil: - s.dialing[t.dest.ID()] = t.flags - newtasks = append(newtasks, t) - } + d.stopHistoryTimer(historyExp) + for range d.dialing { + <-d.doneCh } + d.wg.Done() +} - // If we don't have any peers whatsoever, try to dial a random bootnode. This - // scenario is useful for the testnet (and private networks) where the discovery - // table might be full of mostly bad peers, making it hard to find good ones. - if len(peers) == 0 && len(s.bootnodes) > 0 && needDynDials > 0 && now.Sub(s.start) > fallbackInterval { - bootnode := s.bootnodes[0] - s.bootnodes = append(s.bootnodes[:0], s.bootnodes[1:]...) - s.bootnodes = append(s.bootnodes, bootnode) +// readNodes runs in its own goroutine and delivers nodes from +// the input iterator to the nodesIn channel. +func (d *dialScheduler) readNodes(it enode.Iterator) { + defer d.wg.Done() - if addDial(dynDialedConn, bootnode) { - needDynDials-- + for it.Next() { + select { + case d.nodesIn <- it.Node(): + case <-d.ctx.Done(): } } - // Use random nodes from the table for half of the necessary - // dynamic dials. - randomCandidates := needDynDials / 2 - if randomCandidates > 0 { - n := s.ntab.ReadRandomNodes(s.randomNodes) - for i := 0; i < randomCandidates && i < n; i++ { - if addDial(dynDialedConn, s.randomNodes[i]) { - needDynDials-- - } - } +} + +// logStats prints dialer statistics to the log. The message is suppressed when enough +// peers are connected because users should only see it while their client is starting up +// or comes back online. +func (d *dialScheduler) logStats() { + now := d.clock.Now() + if d.lastStatsLog.Add(dialStatsLogInterval) > now { + return } - // Create dynamic dials from random lookup results, removing tried - // items from the result buffer. - i := 0 - for ; i < len(s.lookupBuf) && needDynDials > 0; i++ { - if addDial(dynDialedConn, s.lookupBuf[i]) { - needDynDials-- - } + if d.dialPeers < dialStatsPeerLimit && d.dialPeers < d.maxDialPeers { + d.log.Info("Looking for peers", "peercount", len(d.peers), "tried", d.doneSinceLastLog, "static", len(d.static)) } - s.lookupBuf = s.lookupBuf[:copy(s.lookupBuf, s.lookupBuf[i:])] - // Launch a discovery lookup if more candidates are needed. - if len(s.lookupBuf) < needDynDials && !s.lookupRunning { - s.lookupRunning = true - newtasks = append(newtasks, &discoverTask{}) + d.doneSinceLastLog = 0 + d.lastStatsLog = now +} + +// rearmHistoryTimer configures d.historyTimer to fire when the +// next item in d.history expires. +func (d *dialScheduler) rearmHistoryTimer(ch chan struct{}) { + if len(d.history) == 0 || d.historyTimerTime == d.history.nextExpiry() { + return } + d.stopHistoryTimer(ch) + d.historyTimerTime = d.history.nextExpiry() + timeout := time.Duration(d.historyTimerTime - d.clock.Now()) + d.historyTimer = d.clock.AfterFunc(timeout, func() { ch <- struct{}{} }) +} - // Launch a timer to wait for the next node to expire if all - // candidates have been tried and no task is currently active. - // This should prevent cases where the dialer logic is not ticked - // because there are no pending events. - if nRunning == 0 && len(newtasks) == 0 && s.hist.Len() > 0 { - t := &waitExpireTask{s.hist.min().exp.Sub(now)} - newtasks = append(newtasks, t) +// stopHistoryTimer stops the timer and drains the channel it sends on. +func (d *dialScheduler) stopHistoryTimer(ch chan struct{}) { + if d.historyTimer != nil && !d.historyTimer.Stop() { + <-ch } - return newtasks } -var ( - errSelf = errors.New("is self") - errAlreadyDialing = errors.New("already dialing") - errAlreadyConnected = errors.New("already connected") - errRecentlyDialed = errors.New("recently dialed") - errNotWhitelisted = errors.New("not contained in netrestrict whitelist") -) +// expireHistory removes expired items from d.history. +func (d *dialScheduler) expireHistory() { + d.historyTimer.Stop() + d.historyTimer = nil + d.historyTimerTime = 0 + d.history.expire(d.clock.Now(), func(hkey string) { + var id enode.ID + copy(id[:], hkey) + d.updateStaticPool(id) + }) +} + +// freeDialSlots returns the number of free dial slots. The result can be negative +// when peers are connected while their task is still running. +func (d *dialScheduler) freeDialSlots() int { + slots := (d.maxDialPeers - d.dialPeers) * 2 + if slots > d.maxActiveDials { + slots = d.maxActiveDials + } + free := slots - len(d.dialing) + return free +} -func (s *dialstate) checkDial(n *enode.Node, peers map[enode.ID]*Peer) error { - _, dialing := s.dialing[n.ID()] - switch { - case dialing: +// checkDial returns an error if node n should not be dialed. +func (d *dialScheduler) checkDial(n *enode.Node) error { + if n.ID() == d.self { + return errSelf + } + if n.IP() != nil && n.TCP() == 0 { + // This check can trigger if a non-TCP node is found + // by discovery. If there is no IP, the node is a static + // node and the actual endpoint will be resolved later in dialTask. + return errNoPort + } + if _, ok := d.dialing[n.ID()]; ok { return errAlreadyDialing - case peers[n.ID()] != nil: + } + if _, ok := d.peers[n.ID()]; ok { return errAlreadyConnected - case n.ID() == s.self: - return errSelf - case s.netrestrict != nil && !s.netrestrict.Contains(n.IP()): - return errNotWhitelisted - case s.hist.contains(n.ID()): + } + if d.netRestrict != nil && !d.netRestrict.Contains(n.IP()) { + return errNetRestrict + } + if d.history.contains(string(n.ID().Bytes())) { return errRecentlyDialed } return nil } -func (s *dialstate) taskDone(t task, now time.Time) { - switch t := t.(type) { - case *dialTask: - s.hist.add(t.dest.ID(), now.Add(dialHistoryExpiration)) - delete(s.dialing, t.dest.ID()) - case *discoverTask: - s.lookupRunning = false - s.lookupBuf = append(s.lookupBuf, t.results...) +// startStaticDials starts n static dial tasks. +func (d *dialScheduler) startStaticDials(n int) (started int) { + for started = 0; started < n && len(d.staticPool) > 0; started++ { + idx := d.rand.Intn(len(d.staticPool)) + task := d.staticPool[idx] + d.startDial(task) + d.removeFromStaticPool(idx) } + return started } -func (t *dialTask) Do(srv *Server) { - if t.dest.Incomplete() { - if !t.resolve(srv) { - return +func (d *dialScheduler) startConsensusDials(n int) (started int) { + // Create dials for consensus nodes if they are not connected. + i := 0 + for _, t := range d.consensus.ListTask() { + if i >= n { + break + } + err := d.checkDial(t.dest) + switch err { + case errNetRestrict, errSelf: + d.consensus.RemoveTask(t.dest) + case nil: + d.startDial(t) + i++ } } - err := t.dial(srv, t.dest) + return i +} + +// updateStaticPool attempts to move the given static dial back into staticPool. +func (d *dialScheduler) updateStaticPool(id enode.ID) { + task, ok := d.static[id] + if ok && task.staticPoolIndex < 0 && d.checkDial(task.dest) == nil { + d.addToStaticPool(task) + } +} + +func (d *dialScheduler) addToStaticPool(task *dialTask) { + if task.staticPoolIndex >= 0 { + panic("attempt to add task to staticPool twice") + } + d.staticPool = append(d.staticPool, task) + task.staticPoolIndex = len(d.staticPool) - 1 +} + +// removeFromStaticPool removes the task at idx from staticPool. It does that by moving the +// current last element of the pool to idx and then shortening the pool by one. +func (d *dialScheduler) removeFromStaticPool(idx int) { + task := d.staticPool[idx] + end := len(d.staticPool) - 1 + d.staticPool[idx] = d.staticPool[end] + d.staticPool[idx].staticPoolIndex = idx + d.staticPool[end] = nil + d.staticPool = d.staticPool[:end] + task.staticPoolIndex = -1 +} + +// startDial runs the given dial task in a separate goroutine. +func (d *dialScheduler) startDial(task *dialTask) { + d.log.Trace("Starting p2p dial", "id", task.dest.ID(), "ip", task.dest.IP(), "flag", task.flags) + hkey := string(task.dest.ID().Bytes()) + d.history.add(hkey, d.clock.Now().Add(dialHistoryExpiration)) + d.dialing[task.dest.ID()] = task + go func() { + task.run(d) + d.doneCh <- task + }() +} + +// A dialTask generated for each node that is dialed. +type dialTask struct { + staticPoolIndex int + flags connFlag + // These fields are private to the task and should not be + // accessed by dialScheduler while the task is running. + dest *enode.Node + lastResolved mclock.AbsTime + resolveDelay time.Duration +} + +func newDialTask(dest *enode.Node, flags connFlag) *dialTask { + return &dialTask{dest: dest, flags: flags, staticPoolIndex: -1} +} + +type dialError struct { + error +} + +func (t *dialTask) run(d *dialScheduler) { + if t.needResolve() && !t.resolve(d) { + return + } + + err := t.dial(d, t.dest) if err != nil { - log.Trace("Dial error", "task", t, "err", err) - // Try resolving the ID of static nodes if dialing failed. + // For static nodes, resolve one more time if dialing fails. if _, ok := err.(*dialError); ok && t.flags&staticDialedConn != 0 { - if t.resolve(srv) { - t.dial(srv, t.dest) + if t.resolve(d) { + t.dial(d, t.dest) } } } } +func (t *dialTask) needResolve() bool { + return t.flags&staticDialedConn != 0 && t.dest.IP() == nil +} + // resolve attempts to find the current endpoint for the destination // using discovery. // // Resolve operations are throttled with backoff to avoid flooding the // discovery network with useless queries for nodes that don't exist. // The backoff delay resets when the node is found. -func (t *dialTask) resolve(srv *Server) bool { - if srv.ntab == nil { - log.Debug("Can't resolve node", "id", t.dest.ID, "err", "discovery is disabled") +func (t *dialTask) resolve(d *dialScheduler) bool { + if d.resolver == nil { return false } if t.resolveDelay == 0 { t.resolveDelay = initialResolveDelay } - if time.Since(t.lastResolved) < t.resolveDelay { + if t.lastResolved > 0 && time.Duration(d.clock.Now()-t.lastResolved) < t.resolveDelay { return false } - resolved := srv.ntab.Resolve(t.dest) - t.lastResolved = time.Now() + resolved := d.resolver.Resolve(t.dest) + t.lastResolved = d.clock.Now() if resolved == nil { t.resolveDelay *= 2 if t.resolveDelay > maxResolveDelay { t.resolveDelay = maxResolveDelay } - log.Debug("Resolving node failed", "id", t.dest.ID, "newdelay", t.resolveDelay) + d.log.Debug("Resolving node failed", "id", t.dest.ID(), "newdelay", t.resolveDelay) return false } // The node was found. t.resolveDelay = initialResolveDelay t.dest = resolved - log.Debug("Resolved node", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP(), Port: t.dest.TCP()}) + d.log.Debug("Resolved node", "id", t.dest.ID(), "addr", &net.TCPAddr{IP: t.dest.IP(), Port: t.dest.TCP()}) return true } -type dialError struct { - error -} - // dial performs the actual connection attempt. -func (t *dialTask) dial(srv *Server, dest *enode.Node) error { - fd, err := srv.Dialer.Dial(dest) +func (t *dialTask) dial(d *dialScheduler, dest *enode.Node) error { + fd, err := d.dialer.Dial(d.ctx, t.dest) if err != nil { + d.log.Trace("Dial error", "id", t.dest.ID(), "addr", nodeAddr(t.dest), "conn", t.flags, "err", cleanupDialErr(err)) return &dialError{err} } - mfd := newMeteredConn(fd, false, dest.IP()) - return srv.SetupConn(mfd, t.flags, dest) + mfd := newMeteredConn(fd, false, &net.TCPAddr{IP: dest.IP(), Port: dest.TCP()}) + return d.setupFunc(mfd, t.flags, dest) } func (t *dialTask) String() string { @@ -400,75 +607,9 @@ func (t *dialTask) String() string { return fmt.Sprintf("%v %x %v:%d", t.flags, id[:8], t.dest.IP(), t.dest.TCP()) } -func (t *discoverTask) Do(srv *Server) { - // newTasks generates a lookup task whenever dynamic dials are - // necessary. Lookups need to take some time, otherwise the - // event loop spins too fast. - next := srv.lastLookup.Add(lookupInterval) - if now := time.Now(); now.Before(next) { - time.Sleep(next.Sub(now)) - } - srv.lastLookup = time.Now() - t.results = srv.ntab.LookupRandom() -} - -func (t *discoverTask) String() string { - s := "discovery lookup" - if len(t.results) > 0 { - s += fmt.Sprintf(" (%d results)", len(t.results)) - } - return s -} - -func (t waitExpireTask) Do(*Server) { - time.Sleep(t.Duration) -} -func (t waitExpireTask) String() string { - return fmt.Sprintf("wait for dial hist expire (%v)", t.Duration) -} - -// Use only these methods to access or modify dialHistory. -func (h dialHistory) min() pastDial { - return h[0] -} -func (h *dialHistory) add(id enode.ID, exp time.Time) { - heap.Push(h, pastDial{id, exp}) - -} -func (h *dialHistory) remove(id enode.ID) bool { - for i, v := range *h { - if v.id == id { - heap.Remove(h, i) - return true - } +func cleanupDialErr(err error) error { + if netErr, ok := err.(*net.OpError); ok && netErr.Op == "dial" { + return netErr.Err } - return false -} -func (h dialHistory) contains(id enode.ID) bool { - for _, v := range h { - if v.id == id { - return true - } - } - return false -} -func (h *dialHistory) expire(now time.Time) { - for h.Len() > 0 && h.min().exp.Before(now) { - heap.Pop(h) - } -} - -// heap.Interface boilerplate -func (h dialHistory) Len() int { return len(h) } -func (h dialHistory) Less(i, j int) bool { return h[i].exp.Before(h[j].exp) } -func (h dialHistory) Swap(i, j int) { h[i], h[j] = h[j], h[i] } -func (h *dialHistory) Push(x interface{}) { - *h = append(*h, x.(pastDial)) -} -func (h *dialHistory) Pop() interface{} { - old := *h - n := len(old) - x := old[n-1] - *h = old[0 : n-1] - return x + return err } diff --git a/p2p/dial_test.go b/p2p/dial_test.go index cbd6c82e90..0063f08556 100644 --- a/p2p/dial_test.go +++ b/p2p/dial_test.go @@ -17,691 +17,659 @@ package p2p import ( - "encoding/binary" + "context" + "errors" + "fmt" + "math/rand" "net" "reflect" + "sync" "testing" "time" - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/common/mclock" + + "github.com/AlayaNetwork/Alaya-Go/internal/testlog" + "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/davecgh/go-spew/spew" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" ) -func init() { - spew.Config.Indent = "\t" -} - -type dialtest struct { - init *dialstate // state before and after the test. - rounds []round -} - -type round struct { - peers []*Peer // current peer set - done []task // tasks that got done this round - new []task // the result must match this one -} +// This test checks that dynamic dials are launched from discovery results. +func TestDialSchedDynDial(t *testing.T) { + t.Parallel() -func runDialTest(t *testing.T, test dialtest) { - var ( - vtime time.Time - running int - ) - pm := func(ps []*Peer) map[enode.ID]*Peer { - m := make(map[enode.ID]*Peer) - for _, p := range ps { - m[p.ID()] = p - } - return m + config := dialConfig{ + maxActiveDials: 5, + maxDialPeers: 4, } - for i, round := range test.rounds { - for _, task := range round.done { - running-- - if running < 0 { - panic("running task counter underflow") - } - test.init.taskDone(task, vtime) - } + runDialTest(t, config, []dialTestRound{ + // 3 out of 4 peers are connected, leaving 2 dial slots. + // 9 nodes are discovered, but only 2 are dialed. + { + peersAdded: []*conn{ + {flags: staticDialedConn, node: newNode(uintID(0x00), "")}, + {flags: dynDialedConn, node: newNode(uintID(0x01), "")}, + {flags: dynDialedConn, node: newNode(uintID(0x02), "")}, + }, + discovered: []*enode.Node{ + newNode(uintID(0x00), "127.0.0.1:30303"), // not dialed because already connected as static peer + newNode(uintID(0x02), "127.0.0.1:30303"), // ... + newNode(uintID(0x03), "127.0.0.1:30303"), + newNode(uintID(0x04), "127.0.0.1:30303"), + newNode(uintID(0x05), "127.0.0.1:30303"), // not dialed because there are only two slots + newNode(uintID(0x06), "127.0.0.1:30303"), // ... + newNode(uintID(0x07), "127.0.0.1:30303"), // ... + newNode(uintID(0x08), "127.0.0.1:30303"), // ... + }, + wantNewDials: []*enode.Node{ + newNode(uintID(0x03), "127.0.0.1:30303"), + newNode(uintID(0x04), "127.0.0.1:30303"), + }, + }, - new := test.init.newTasks(running, pm(round.peers), vtime) - if !sametasks(new, round.new) { - 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)) - } - t.Log("tasks:", spew.Sdump(new)) + // One dial completes, freeing one dial slot. + { + failed: []enode.ID{ + uintID(0x04), + }, + wantNewDials: []*enode.Node{ + newNode(uintID(0x05), "127.0.0.1:30303"), + }, + }, - // Time advances by 16 seconds on every round. - vtime = vtime.Add(16 * time.Second) - running += len(new) - } -} + // Dial to 0x03 completes, filling the last remaining peer slot. + { + succeeded: []enode.ID{ + uintID(0x03), + }, + failed: []enode.ID{ + uintID(0x05), + }, + discovered: []*enode.Node{ + newNode(uintID(0x09), "127.0.0.1:30303"), // not dialed because there are no free slots + }, + }, -type fakeTable []*enode.Node - -func (t fakeTable) Self() *enode.Node { return new(enode.Node) } -func (t fakeTable) Close() {} -func (t fakeTable) LookupRandom() []*enode.Node { return nil } -func (t fakeTable) Resolve(*enode.Node) *enode.Node { return nil } -func (t fakeTable) ReadRandomNodes(buf []*enode.Node) int { return copy(buf, t) } - -// This test checks that dynamic dials are launched from discovery results -func TestDialStateDynDial(t *testing.T) { - runDialTest(t, dialtest{ - init: newDialState(enode.ID{}, nil, nil, fakeTable{}, 5, nil, 75), - rounds: []round{ - // A discovery query is launched. - { - peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, - }, - new: []task{&discoverTask{}}, - }, - // Dynamic dials are launched when it completes. - { - peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, - }, - done: []task{ - &discoverTask{results: []*enode.Node{ - newNode(uintID(2), nil), // this one is already connected and not dialed. - newNode(uintID(3), nil), - newNode(uintID(4), nil), - newNode(uintID(5), nil), - newNode(uintID(6), nil), // these are not tried because max dyn dials is 5 - newNode(uintID(7), nil), // ... - }}, - }, - new: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, - }, - }, - // 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. - { - peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(3), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}}, - }, - done: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, - }, - }, - // No new dial tasks are launched in the this round because - // maxDynDials has been reached. - { - peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(3), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}}, - }, - done: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, - }, - new: []task{ - &waitExpireTask{Duration: 14 * time.Second}, - }, - }, - // In this round, the peer with id 2 drops off. The query - // results from last discovery lookup are reused. - { - peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(3), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}}, - }, - new: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(6), nil)}, - }, - }, - // More peers (3,4) drop off and dial for ID 6 completes. - // The last query result from the discovery lookup is reused - // and a new one is spawned because more candidates are needed. - { - peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}}, - }, - done: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(6), nil)}, - }, - new: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(7), nil)}, - &discoverTask{}, - }, - }, - // Peer 7 is connected, but there still aren't enough dynamic peers - // (4 out of 5). However, a discovery is already running, so ensure - // no new is started. - { - peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(7), nil)}}, - }, - done: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(7), nil)}, - }, - }, - // Finish the running node discovery with an empty set. A new lookup - // should be immediately requested. - { - peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(7), nil)}}, - }, - done: []task{ - &discoverTask{}, - }, - new: []task{ - &discoverTask{}, - }, + // 3 peers drop off, creating 6 dial slots. Check that 5 of those slots + // (i.e. up to maxActiveDialTasks) are used. + { + peersRemoved: []enode.ID{ + uintID(0x00), + uintID(0x01), + uintID(0x02), + }, + discovered: []*enode.Node{ + newNode(uintID(0x0a), "127.0.0.1:30303"), + newNode(uintID(0x0b), "127.0.0.1:30303"), + newNode(uintID(0x0c), "127.0.0.1:30303"), + newNode(uintID(0x0d), "127.0.0.1:30303"), + newNode(uintID(0x0f), "127.0.0.1:30303"), + }, + wantNewDials: []*enode.Node{ + newNode(uintID(0x06), "127.0.0.1:30303"), + newNode(uintID(0x07), "127.0.0.1:30303"), + newNode(uintID(0x08), "127.0.0.1:30303"), + newNode(uintID(0x09), "127.0.0.1:30303"), + newNode(uintID(0x0a), "127.0.0.1:30303"), }, }, }) } -// Tests that bootnodes are dialed if no peers are connectd, but not otherwise. -func TestDialStateDynDialBootnode(t *testing.T) { - bootnodes := []*enode.Node{ - newNode(uintID(1), nil), - newNode(uintID(2), nil), - newNode(uintID(3), nil), +// This test checks that candidates that do not match the netrestrict list are not dialed. +func TestDialSchedNetRestrict(t *testing.T) { + t.Parallel() + + nodes := []*enode.Node{ + newNode(uintID(0x01), "127.0.0.1:30303"), + newNode(uintID(0x02), "127.0.0.2:30303"), + newNode(uintID(0x03), "127.0.0.3:30303"), + newNode(uintID(0x04), "127.0.0.4:30303"), + newNode(uintID(0x05), "127.0.2.5:30303"), + newNode(uintID(0x06), "127.0.2.6:30303"), + newNode(uintID(0x07), "127.0.2.7:30303"), + newNode(uintID(0x08), "127.0.2.8:30303"), } - table := fakeTable{ - newNode(uintID(4), nil), - newNode(uintID(5), nil), - newNode(uintID(6), nil), - newNode(uintID(7), nil), - newNode(uintID(8), nil), + config := dialConfig{ + netRestrict: new(netutil.Netlist), + maxActiveDials: 10, + maxDialPeers: 10, } - runDialTest(t, dialtest{ - init: newDialState(enode.ID{}, nil, bootnodes, table, 5, nil, 75), - rounds: []round{ - // 2 dynamic dials attempted, bootnodes pending fallback interval - { - new: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, - &discoverTask{}, - }, - }, - // No dials succeed, bootnodes still pending fallback interval - { - done: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, - }, - }, - // No dials succeed, bootnodes still pending fallback interval - {}, - // No dials succeed, 2 dynamic dials attempted and 1 bootnode too as fallback interval was reached - { - new: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, - }, - }, - // No dials succeed, 2nd bootnode is attempted - { - done: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, - }, - new: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)}, - }, - }, - // No dials succeed, 3rd bootnode is attempted - { - done: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)}, - }, - new: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, - }, - }, - // No dials succeed, 1st bootnode is attempted again, expired random nodes retried - { - done: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, - }, - new: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, - }, - }, - // Random dial succeeds, no more bootnodes are attempted - { - peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}}, - }, - done: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, - }, + config.netRestrict.Add("127.0.2.0/24") + runDialTest(t, config, []dialTestRound{ + { + discovered: nodes, + wantNewDials: nodes[4:8], + }, + { + succeeded: []enode.ID{ + nodes[4].ID(), + nodes[5].ID(), + nodes[6].ID(), + nodes[7].ID(), }, }, }) } -func TestDialStateDynDialFromTable(t *testing.T) { - // This table always returns the same random nodes - // in the order given below. - table := fakeTable{ - newNode(uintID(1), nil), - newNode(uintID(2), nil), - newNode(uintID(3), nil), - newNode(uintID(4), nil), - newNode(uintID(5), nil), - newNode(uintID(6), nil), - newNode(uintID(7), nil), - newNode(uintID(8), nil), - } +// This test checks that static dials work and obey the limits. +func TestDialSchedStaticDial(t *testing.T) { + t.Parallel() - runDialTest(t, dialtest{ - init: newDialState(enode.ID{}, nil, nil, table, 10, nil, 75), - rounds: []round{ - // 5 out of 8 of the nodes returned by ReadRandomNodes are dialed. - { - new: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, - &discoverTask{}, - }, - }, - // Dialing nodes 1,2 succeeds. Dials from the lookup are launched. - { - peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, - }, - done: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)}, - &discoverTask{results: []*enode.Node{ - newNode(uintID(10), nil), - newNode(uintID(11), nil), - newNode(uintID(12), nil), - }}, - }, - new: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(10), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(11), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(12), nil)}, - &discoverTask{}, - }, - }, - // Dialing nodes 3,4,5 fails. The dials from the lookup succeed. - { - peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(10), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(11), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(12), nil)}}, - }, - done: []task{ - &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(10), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(11), nil)}, - &dialTask{flags: dynDialedConn, dest: newNode(uintID(12), nil)}, - }, - }, - // Waiting for expiry. No waitExpireTask is launched because the - // discovery query is still running. - { - peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(10), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(11), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(12), nil)}}, - }, - }, - // Nodes 3,4 are not tried again because only the first two - // returned random nodes (nodes 1,2) are tried and they're - // already connected. - { - peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(10), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(11), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(12), nil)}}, - }, + config := dialConfig{ + maxActiveDials: 5, + maxDialPeers: 4, + } + runDialTest(t, config, []dialTestRound{ + // Static dials are launched for the nodes that + // aren't yet connected. + { + peersAdded: []*conn{ + {flags: dynDialedConn, node: newNode(uintID(0x01), "127.0.0.1:30303")}, + {flags: dynDialedConn, node: newNode(uintID(0x02), "127.0.0.2:30303")}, + }, + update: func(d *dialScheduler) { + // These two are not dialed because they're already connected + // as dynamic peers. + d.addStatic(newNode(uintID(0x01), "127.0.0.1:30303")) + d.addStatic(newNode(uintID(0x02), "127.0.0.2:30303")) + // These nodes will be dialed: + d.addStatic(newNode(uintID(0x03), "127.0.0.3:30303")) + d.addStatic(newNode(uintID(0x04), "127.0.0.4:30303")) + d.addStatic(newNode(uintID(0x05), "127.0.0.5:30303")) + d.addStatic(newNode(uintID(0x06), "127.0.0.6:30303")) + d.addStatic(newNode(uintID(0x07), "127.0.0.7:30303")) + d.addStatic(newNode(uintID(0x08), "127.0.0.8:30303")) + d.addStatic(newNode(uintID(0x09), "127.0.0.9:30303")) + }, + wantNewDials: []*enode.Node{ + newNode(uintID(0x03), "127.0.0.3:30303"), + newNode(uintID(0x04), "127.0.0.4:30303"), + newNode(uintID(0x05), "127.0.0.5:30303"), + newNode(uintID(0x06), "127.0.0.6:30303"), + }, + }, + // Dial to 0x03 completes, filling a peer slot. One slot remains, + // two dials are launched to attempt to fill it. + { + succeeded: []enode.ID{ + uintID(0x03), + }, + failed: []enode.ID{ + uintID(0x04), + uintID(0x05), + uintID(0x06), + }, + wantResolves: map[enode.ID]*enode.Node{ + uintID(0x04): nil, + uintID(0x05): nil, + uintID(0x06): nil, + }, + wantNewDials: []*enode.Node{ + newNode(uintID(0x08), "127.0.0.8:30303"), + newNode(uintID(0x09), "127.0.0.9:30303"), + }, + }, + // Peer 0x01 drops and 0x07 connects as inbound peer. + // Only 0x01 is dialed. + { + peersAdded: []*conn{ + {flags: inboundConn, node: newNode(uintID(0x07), "127.0.0.7:30303")}, + }, + peersRemoved: []enode.ID{ + uintID(0x01), + }, + wantNewDials: []*enode.Node{ + newNode(uintID(0x01), "127.0.0.1:30303"), }, }, }) } -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 removing static nodes stops connecting to them. +func TestDialSchedRemoveStatic(t *testing.T) { + t.Parallel() -// This test checks that candidates that do not match the netrestrict list are not dialed. -func TestDialStateNetRestrict(t *testing.T) { - // This table always returns the same random nodes - // in the order given below. - table := fakeTable{ - newNode(uintID(1), net.ParseIP("127.0.0.1")), - newNode(uintID(2), net.ParseIP("127.0.0.2")), - newNode(uintID(3), net.ParseIP("127.0.0.3")), - newNode(uintID(4), net.ParseIP("127.0.0.4")), - newNode(uintID(5), net.ParseIP("127.0.2.5")), - newNode(uintID(6), net.ParseIP("127.0.2.6")), - newNode(uintID(7), net.ParseIP("127.0.2.7")), - newNode(uintID(8), net.ParseIP("127.0.2.8")), + config := dialConfig{ + maxActiveDials: 1, + maxDialPeers: 1, } - restrict := new(netutil.Netlist) - restrict.Add("127.0.2.0/24") - - runDialTest(t, dialtest{ - init: newDialState(enode.ID{}, nil, nil, table, 10, restrict, 75), - rounds: []round{ - { - new: []task{ - &dialTask{flags: dynDialedConn, dest: table[4]}, - &discoverTask{}, - }, + runDialTest(t, config, []dialTestRound{ + // Add static nodes. + { + update: func(d *dialScheduler) { + d.addStatic(newNode(uintID(0x01), "127.0.0.1:30303")) + d.addStatic(newNode(uintID(0x02), "127.0.0.2:30303")) + d.addStatic(newNode(uintID(0x03), "127.0.0.3:30303")) + }, + wantNewDials: []*enode.Node{ + newNode(uintID(0x01), "127.0.0.1:30303"), + }, + }, + // Dial to 0x01 fails. + { + failed: []enode.ID{ + uintID(0x01), + }, + wantResolves: map[enode.ID]*enode.Node{ + uintID(0x01): nil, + }, + wantNewDials: []*enode.Node{ + newNode(uintID(0x02), "127.0.0.2:30303"), }, }, + // All static nodes are removed. 0x01 is in history, 0x02 is being + // dialed, 0x03 is in staticPool. + { + update: func(d *dialScheduler) { + d.removeStatic(newNode(uintID(0x01), "127.0.0.1:30303")) + d.removeStatic(newNode(uintID(0x02), "127.0.0.2:30303")) + d.removeStatic(newNode(uintID(0x03), "127.0.0.3:30303")) + }, + failed: []enode.ID{ + uintID(0x02), + }, + wantResolves: map[enode.ID]*enode.Node{ + uintID(0x02): nil, + }, + }, + // Since all static nodes are removed, they should not be dialed again. + {}, {}, {}, }) } -// This test checks that static dials are launched. -func TestDialStateStaticDial(t *testing.T) { - wantStatic := []*enode.Node{ - newNode(uintID(1), nil), - newNode(uintID(2), nil), - newNode(uintID(3), nil), - newNode(uintID(4), nil), - newNode(uintID(5), nil), - } +// This test checks that static dials are selected at random. +func TestDialSchedManyStaticNodes(t *testing.T) { + t.Parallel() - runDialTest(t, dialtest{ - init: newDialState(enode.ID{}, wantStatic, nil, fakeTable{}, 0, nil, 75), - rounds: []round{ - // Static dials are launched for the nodes that - // aren't yet connected. - { - peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, - }, - new: []task{ - &dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)}, - &dialTask{flags: staticDialedConn, dest: newNode(uintID(4), nil)}, - &dialTask{flags: staticDialedConn, dest: newNode(uintID(5), nil)}, - }, - }, - // No new tasks are launched in this round because all static - // nodes are either connected or still being dialed. - { - peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}}, - }, - done: []task{ - &dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)}, - }, - }, - // No new dial tasks are launched because all static - // nodes are now connected. - { - peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}}, - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(4), nil)}}, - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(5), nil)}}, - }, - done: []task{ - &dialTask{flags: staticDialedConn, dest: newNode(uintID(4), nil)}, - &dialTask{flags: staticDialedConn, dest: newNode(uintID(5), nil)}, - }, - new: []task{ - &waitExpireTask{Duration: 14 * time.Second}, - }, - }, - // Wait a round for dial history to expire, no new tasks should spawn. - { - peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}}, - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(4), nil)}}, - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(5), nil)}}, - }, - }, - // If a static node is dropped, it should be immediately redialed, - // irrespective whether it was originally static or dynamic. - { - peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}}, - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(5), nil)}}, - }, - new: []task{ - &dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)}, - &dialTask{flags: staticDialedConn, dest: newNode(uintID(4), nil)}, - }, + config := dialConfig{maxDialPeers: 2} + runDialTest(t, config, []dialTestRound{ + { + peersAdded: []*conn{ + {flags: dynDialedConn, node: newNode(uintID(0xFFFE), "")}, + {flags: dynDialedConn, node: newNode(uintID(0xFFFF), "")}, + }, + update: func(d *dialScheduler) { + for id := uint16(0); id < 2000; id++ { + n := newNode(uintID(id), "127.0.0.1:30303") + d.addStatic(n) + } + }, + }, + { + peersRemoved: []enode.ID{ + uintID(0xFFFE), + uintID(0xFFFF), + }, + wantNewDials: []*enode.Node{ + newNode(uintID(0x0085), "127.0.0.1:30303"), + newNode(uintID(0x02dc), "127.0.0.1:30303"), + newNode(uintID(0x0285), "127.0.0.1:30303"), + newNode(uintID(0x00cb), "127.0.0.1:30303"), }, }, }) } -// This test checks that static peers will be redialed immediately if they were re-added to a static list. -func TestDialStaticAfterReset(t *testing.T) { - wantStatic := []*enode.Node{ - newNode(uintID(1), nil), - newNode(uintID(2), nil), - } +// This test checks that past dials are not retried for some time. +func TestDialSchedHistory(t *testing.T) { + t.Parallel() - rounds := []round{ - // Static dials are launched for the nodes that aren't yet connected. + config := dialConfig{ + maxActiveDials: 3, + maxDialPeers: 3, + } + runDialTest(t, config, []dialTestRound{ { - peers: nil, - new: []task{ - &dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)}, - &dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)}, + update: func(d *dialScheduler) { + d.addStatic(newNode(uintID(0x01), "127.0.0.1:30303")) + d.addStatic(newNode(uintID(0x02), "127.0.0.2:30303")) + d.addStatic(newNode(uintID(0x03), "127.0.0.3:30303")) + }, + wantNewDials: []*enode.Node{ + newNode(uintID(0x01), "127.0.0.1:30303"), + newNode(uintID(0x02), "127.0.0.2:30303"), + newNode(uintID(0x03), "127.0.0.3:30303"), }, }, - // No new dial tasks, all peers are connected. + // No new tasks are launched in this round because all static + // nodes are either connected or still being dialed. { - peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(2), nil)}}, + succeeded: []enode.ID{ + uintID(0x01), + uintID(0x02), }, - done: []task{ - &dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)}, - &dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)}, + failed: []enode.ID{ + uintID(0x03), }, - new: []task{ - &waitExpireTask{Duration: 30 * time.Second}, + wantResolves: map[enode.ID]*enode.Node{ + uintID(0x03): nil, }, }, - } - dTest := dialtest{ - init: newDialState(enode.ID{}, wantStatic, nil, fakeTable{}, 0, nil, 75), - rounds: rounds, - } - runDialTest(t, dTest) - for _, n := range wantStatic { - dTest.init.removeStatic(n) - dTest.init.addStatic(n) - } - // without removing peers they will be considered recently dialed - runDialTest(t, dTest) + // Nothing happens in this round because we're waiting for + // node 0x3's history entry to expire. + {}, + // The cache entry for node 0x03 has expired and is retried. + { + wantNewDials: []*enode.Node{ + newNode(uintID(0x03), "127.0.0.3:30303"), + }, + }, + }) } -// This test checks that past dials are not retried for some time. -func TestDialStateCache(t *testing.T) { - wantStatic := []*enode.Node{ - newNode(uintID(1), nil), - newNode(uintID(2), nil), - newNode(uintID(3), nil), - } +func TestDialSchedResolve(t *testing.T) { + t.Parallel() - runDialTest(t, dialtest{ - init: newDialState(enode.ID{}, wantStatic, nil, fakeTable{}, 0, nil, 75), - rounds: []round{ - // Static dials are launched for the nodes that - // aren't yet connected. - { - peers: nil, - new: []task{ - &dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)}, - &dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)}, - &dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)}, - }, - }, - // No new tasks are launched in this round because all static - // nodes are either connected or still being dialed. - { - peers: []*Peer{ - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: staticDialedConn, node: newNode(uintID(2), nil)}}, - }, - done: []task{ - &dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)}, - &dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)}, - }, - }, - // A salvage task is launched to wait for node 3's history - // entry to expire. - { - peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, - }, - done: []task{ - &dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)}, - }, - new: []task{ - &waitExpireTask{Duration: 14 * time.Second}, - }, - }, - // Still waiting for node 3's entry to expire in the cache. - { - peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, - }, - }, - // The cache entry for node 3 has expired and is retried. - { - peers: []*Peer{ - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}}, - {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}}, - }, - new: []task{ - &dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)}, - }, + config := dialConfig{ + maxActiveDials: 1, + maxDialPeers: 1, + } + node := newNode(uintID(0x01), "") + resolved := newNode(uintID(0x01), "127.0.0.1:30303") + resolved2 := newNode(uintID(0x01), "127.0.0.55:30303") + runDialTest(t, config, []dialTestRound{ + { + update: func(d *dialScheduler) { + d.addStatic(node) + }, + wantResolves: map[enode.ID]*enode.Node{ + uintID(0x01): resolved, + }, + wantNewDials: []*enode.Node{ + resolved, + }, + }, + { + failed: []enode.ID{ + uintID(0x01), + }, + wantResolves: map[enode.ID]*enode.Node{ + uintID(0x01): resolved2, + }, + wantNewDials: []*enode.Node{ + resolved2, }, }, }) } -func TestDialResolve(t *testing.T) { - resolved := newNode(uintID(1), net.IP{127, 0, 55, 234}) - table := &resolveMock{answer: resolved} - state := newDialState(enode.ID{}, nil, nil, table, 0, nil, 75) - - // Check that the task is generated with an incomplete ID. - dest := newNode(uintID(1), nil) - state.addStatic(dest) - tasks := state.newTasks(0, nil, time.Time{}) - if !reflect.DeepEqual(tasks, []task{&dialTask{flags: staticDialedConn, dest: dest}}) { - t.Fatalf("expected dial task, got %#v", tasks) - } +// ------- +// Code below here is the framework for the tests above. + +type dialTestRound struct { + peersAdded []*conn + peersRemoved []enode.ID + update func(*dialScheduler) // called at beginning of round + discovered []*enode.Node // newly discovered nodes + succeeded []enode.ID // dials which succeed this round + failed []enode.ID // dials which fail this round + wantResolves map[enode.ID]*enode.Node + wantNewDials []*enode.Node // dials that should be launched in this round +} + +func runDialTest(t *testing.T, config dialConfig, rounds []dialTestRound) { + var ( + clock = new(mclock.Simulated) + iterator = newDialTestIterator() + dialer = newDialTestDialer() + resolver = new(dialTestResolver) + peers = make(map[enode.ID]*conn) + setupCh = make(chan *conn) + ) - // Now run the task, it should resolve the ID once. - config := Config{Dialer: TCPDialer{&net.Dialer{Deadline: time.Now().Add(-5 * time.Minute)}}} - srv := &Server{ntab: table, Config: config} - tasks[0].Do(srv) - if !reflect.DeepEqual(table.resolveCalls, []*enode.Node{dest}) { - t.Fatalf("wrong resolve calls, got %v", table.resolveCalls) + // Override config. + config.clock = clock + config.dialer = dialer + config.resolver = resolver + config.log = testlog.Logger(t, log.LvlTrace) + config.rand = rand.New(rand.NewSource(0x1111)) + + // Set up the dialer. The setup function below runs on the dialTask + // goroutine and adds the peer. + var dialsched *dialScheduler + setup := func(fd net.Conn, f connFlag, node *enode.Node) error { + conn := &conn{flags: f, node: node} + dialsched.peerAdded(conn) + setupCh <- conn + return nil } + dialsched = newDialScheduler(config, iterator, setup) + defer dialsched.stop() + + for i, round := range rounds { + // Apply peer set updates. + for _, c := range round.peersAdded { + if peers[c.node.ID()] != nil { + t.Fatalf("round %d: peer %v already connected", i, c.node.ID()) + } + dialsched.peerAdded(c) + peers[c.node.ID()] = c + } + for _, id := range round.peersRemoved { + c := peers[id] + if c == nil { + t.Fatalf("round %d: can't remove non-existent peer %v", i, id) + } + dialsched.peerRemoved(c) + } + + // Init round. + t.Logf("round %d (%d peers)", i, len(peers)) + resolver.setAnswers(round.wantResolves) + if round.update != nil { + round.update(dialsched) + } + iterator.addNodes(round.discovered) + + // Unblock dialTask goroutines. + if err := dialer.completeDials(round.succeeded, nil); err != nil { + t.Fatalf("round %d: %v", i, err) + } + for range round.succeeded { + conn := <-setupCh + peers[conn.node.ID()] = conn + } + if err := dialer.completeDials(round.failed, errors.New("oops")); err != nil { + t.Fatalf("round %d: %v", i, err) + } + + // Wait for new tasks. + if err := dialer.waitForDials(round.wantNewDials); err != nil { + t.Fatalf("round %d: %v", i, err) + } + if !resolver.checkCalls() { + t.Fatalf("unexpected calls to Resolve: %v", resolver.calls) + } - // Report it as done to the dialer, which should update the static node record. - state.taskDone(tasks[0], time.Now()) - if state.static[uintID(1)].dest != resolved { - t.Fatalf("state.dest not updated") + clock.Run(16 * time.Second) } } -// compares task lists but doesn't care about the order. -func sametasks(a, b []task) bool { - if len(a) != len(b) { +// dialTestIterator is the input iterator for dialer tests. This works a bit like a channel +// with infinite buffer: nodes are added to the buffer with addNodes, which unblocks Next +// and returns them from the iterator. +type dialTestIterator struct { + cur *enode.Node + + mu sync.Mutex + buf []*enode.Node + cond *sync.Cond + closed bool +} + +func newDialTestIterator() *dialTestIterator { + it := &dialTestIterator{} + it.cond = sync.NewCond(&it.mu) + return it +} + +// addNodes adds nodes to the iterator buffer and unblocks Next. +func (it *dialTestIterator) addNodes(nodes []*enode.Node) { + it.mu.Lock() + defer it.mu.Unlock() + + it.buf = append(it.buf, nodes...) + it.cond.Signal() +} + +// Node returns the current node. +func (it *dialTestIterator) Node() *enode.Node { + return it.cur +} + +// Next moves to the next node. +func (it *dialTestIterator) Next() bool { + it.mu.Lock() + defer it.mu.Unlock() + + it.cur = nil + for len(it.buf) == 0 && !it.closed { + it.cond.Wait() + } + if it.closed { return false } -next: - for _, ta := range a { - for _, tb := range b { - if reflect.DeepEqual(ta, tb) { - continue next + it.cur = it.buf[0] + copy(it.buf[:], it.buf[1:]) + it.buf = it.buf[:len(it.buf)-1] + return true +} + +// Close ends the iterator, unblocking Next. +func (it *dialTestIterator) Close() { + it.mu.Lock() + defer it.mu.Unlock() + + it.closed = true + it.buf = nil + it.cond.Signal() +} + +// dialTestDialer is the NodeDialer used by runDialTest. +type dialTestDialer struct { + init chan *dialTestReq + blocked map[enode.ID]*dialTestReq +} + +type dialTestReq struct { + n *enode.Node + unblock chan error +} + +func newDialTestDialer() *dialTestDialer { + return &dialTestDialer{ + init: make(chan *dialTestReq), + blocked: make(map[enode.ID]*dialTestReq), + } +} + +// Dial implements NodeDialer. +func (d *dialTestDialer) Dial(ctx context.Context, n *enode.Node) (net.Conn, error) { + req := &dialTestReq{n: n, unblock: make(chan error, 1)} + select { + case d.init <- req: + select { + case err := <-req.unblock: + pipe, _ := net.Pipe() + return pipe, err + case <-ctx.Done(): + return nil, ctx.Err() + } + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +// waitForDials waits for calls to Dial with the given nodes as argument. +// Those calls will be held blocking until completeDials is called with the same nodes. +func (d *dialTestDialer) waitForDials(nodes []*enode.Node) error { + waitset := make(map[enode.ID]*enode.Node) + for _, n := range nodes { + waitset[n.ID()] = n + } + timeout := time.NewTimer(1 * time.Second) + defer timeout.Stop() + + for len(waitset) > 0 { + select { + case req := <-d.init: + want, ok := waitset[req.n.ID()] + if !ok { + return fmt.Errorf("attempt to dial unexpected node %v", req.n.ID()) } + if !reflect.DeepEqual(req.n, want) { + return fmt.Errorf("ENR of dialed node %v does not match test", req.n.ID()) + } + delete(waitset, req.n.ID()) + d.blocked[req.n.ID()] = req + case <-timeout.C: + var waitlist []enode.ID + for id := range waitset { + waitlist = append(waitlist, id) + } + return fmt.Errorf("timed out waiting for dials to %v", waitlist) } - return false } - return true + + return d.checkUnexpectedDial() } -func uintID(i uint32) enode.ID { - var id enode.ID - binary.BigEndian.PutUint32(id[:], i) - return id +func (d *dialTestDialer) checkUnexpectedDial() error { + select { + case req := <-d.init: + return fmt.Errorf("attempt to dial unexpected node %v", req.n.ID()) + case <-time.After(150 * time.Millisecond): + return nil + } } -// implements discoverTable for TestDialResolve -type resolveMock struct { - resolveCalls []*enode.Node - answer *enode.Node +// completeDials unblocks calls to Dial for the given nodes. +func (d *dialTestDialer) completeDials(ids []enode.ID, err error) error { + for _, id := range ids { + req := d.blocked[id] + if req == nil { + return fmt.Errorf("can't complete dial to %v", id) + } + req.unblock <- err + } + return nil } -func (t *resolveMock) Resolve(n *enode.Node) *enode.Node { - t.resolveCalls = append(t.resolveCalls, n) - return t.answer +// dialTestResolver tracks calls to resolve. +type dialTestResolver struct { + mu sync.Mutex + calls []enode.ID + answers map[enode.ID]*enode.Node } -func (t *resolveMock) Self() *enode.Node { return new(enode.Node) } -func (t *resolveMock) Close() {} -func (t *resolveMock) LookupRandom() []*enode.Node { return nil } -func (t *resolveMock) ReadRandomNodes(buf []*enode.Node) int { return 0 } +func (t *dialTestResolver) setAnswers(m map[enode.ID]*enode.Node) { + t.mu.Lock() + defer t.mu.Unlock() + + t.answers = m + t.calls = nil +} + +func (t *dialTestResolver) checkCalls() bool { + t.mu.Lock() + defer t.mu.Unlock() + + for _, id := range t.calls { + if _, ok := t.answers[id]; !ok { + return false + } + } + return true +} + +func (t *dialTestResolver) Resolve(n *enode.Node) *enode.Node { + t.mu.Lock() + defer t.mu.Unlock() + + t.calls = append(t.calls, n.ID()) + return t.answers[n.ID()] +} diff --git a/p2p/discover/common.go b/p2p/discover/common.go index b70e31b2ef..43555226d1 100644 --- a/p2p/discover/common.go +++ b/p2p/discover/common.go @@ -21,11 +21,15 @@ import ( "math/big" "net" + "github.com/AlayaNetwork/Alaya-Go/common/mclock" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" ) +// UDPConn is a network connection on which discovery can operate. type UDPConn interface { ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) @@ -33,7 +37,7 @@ type UDPConn interface { LocalAddr() net.Addr } -// Config holds Table-related settings. +// Config holds settings for the discovery listener. type Config struct { // These settings are required and configure the UDP listener: PrivateKey *ecdsa.PrivateKey @@ -42,10 +46,25 @@ type Config struct { ChainID *big.Int `toml:"-"` // These settings are optional: - NetRestrict *netutil.Netlist // network whitelist - Bootnodes []*enode.Node // list of bootstrap nodes - Unhandled chan<- ReadPacket // unhandled packets are sent on this channel - Log log.Logger // if set, log messages go here + NetRestrict *netutil.Netlist // list of allowed IP networks + Bootnodes []*enode.Node // list of bootstrap nodes + Unhandled chan<- ReadPacket // unhandled packets are sent on this channel + Log log.Logger // if set, log messages go here + ValidSchemes enr.IdentityScheme // allowed identity schemes + Clock mclock.Clock +} + +func (cfg Config) withDefaults() Config { + if cfg.Log == nil { + cfg.Log = log.Root() + } + if cfg.ValidSchemes == nil { + cfg.ValidSchemes = enode.ValidSchemes + } + if cfg.Clock == nil { + cfg.Clock = mclock.System{} + } + return cfg } // ListenUDP starts listening for discovery packets on the given UDP socket. @@ -59,3 +78,10 @@ type ReadPacket struct { Data []byte Addr *net.UDPAddr } + +func min(x, y int) int { + if x > y { + return y + } + return x +} diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go new file mode 100644 index 0000000000..51ac370aae --- /dev/null +++ b/p2p/discover/lookup.go @@ -0,0 +1,226 @@ +// 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 . + +package discover + +import ( + "context" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +// lookup performs a network search for nodes close to the given target. It approaches the +// target by querying nodes that are closer to it on each iteration. The given target does +// not need to be an actual node identifier. +type lookup struct { + tab *Table + queryfunc func(*node) ([]*node, error) + replyCh chan []*node + cancelCh <-chan struct{} + asked, seen map[enode.ID]bool + result nodesByDistance + replyBuffer []*node + queries int +} + +type queryFunc func(*node) ([]*node, error) + +func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *lookup { + it := &lookup{ + tab: tab, + queryfunc: q, + asked: make(map[enode.ID]bool), + seen: make(map[enode.ID]bool), + result: nodesByDistance{target: target}, + replyCh: make(chan []*node, alpha), + cancelCh: ctx.Done(), + queries: -1, + } + // Don't query further if we hit ourself. + // Unlikely to happen often in practice. + it.asked[tab.self().ID()] = true + return it +} + +// run runs the lookup to completion and returns the closest nodes found. +func (it *lookup) run() []*enode.Node { + for it.advance() { + } + return unwrapNodes(it.result.entries) +} + +// advance advances the lookup until any new nodes have been found. +// It returns false when the lookup has ended. +func (it *lookup) advance() bool { + for it.startQueries() { + select { + case nodes := <-it.replyCh: + it.replyBuffer = it.replyBuffer[:0] + for _, n := range nodes { + if n != nil && !it.seen[n.ID()] { + it.seen[n.ID()] = true + it.result.push(n, bucketSize) + it.replyBuffer = append(it.replyBuffer, n) + } + } + it.queries-- + if len(it.replyBuffer) > 0 { + return true + } + case <-it.cancelCh: + it.shutdown() + } + } + return false +} + +func (it *lookup) shutdown() { + for it.queries > 0 { + <-it.replyCh + it.queries-- + } + it.queryfunc = nil + it.replyBuffer = nil +} + +func (it *lookup) startQueries() bool { + if it.queryfunc == nil { + return false + } + + // The first query returns nodes from the local table. + if it.queries == -1 { + closest := it.tab.findnodeByID(it.result.target, bucketSize, false) + // Avoid finishing the lookup too quickly if table is empty. It'd be better to wait + // for the table to fill in this case, but there is no good mechanism for that + // yet. + if len(closest.entries) == 0 { + it.slowdown() + } + it.queries = 1 + it.replyCh <- closest.entries + return true + } + + // Ask the closest nodes that we haven't asked yet. + for i := 0; i < len(it.result.entries) && it.queries < alpha; i++ { + n := it.result.entries[i] + if !it.asked[n.ID()] { + it.asked[n.ID()] = true + it.queries++ + go it.query(n, it.replyCh) + } + } + // The lookup ends when no more nodes can be asked. + return it.queries > 0 +} + +func (it *lookup) slowdown() { + sleep := time.NewTimer(1 * time.Second) + defer sleep.Stop() + select { + case <-sleep.C: + case <-it.tab.closeReq: + } +} + +func (it *lookup) query(n *node, reply chan<- []*node) { + fails := it.tab.db.FindFails(n.ID(), n.IP()) + r, err := it.queryfunc(n) + if err == errClosed { + // Avoid recording failures on shutdown. + reply <- nil + return + } else if len(r) == 0 { + fails++ + it.tab.db.UpdateFindFails(n.ID(), n.IP(), fails) + // Remove the node from the local table if it fails to return anything useful too + // many times, but only if there are enough other nodes in the bucket. + dropped := false + if fails >= maxFindnodeFailures && it.tab.bucketLen(n.ID()) >= bucketSize/2 { + dropped = true + it.tab.delete(n) + } + it.tab.log.Trace("FINDNODE failed", "id", n.ID(), "failcount", fails, "dropped", dropped, "err", err) + } else if fails > 0 { + // Reset failure counter because it counts _consecutive_ failures. + it.tab.db.UpdateFindFails(n.ID(), n.IP(), 0) + } + + // 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 { + it.tab.addSeenNode(n) + } + reply <- r +} + +// lookupIterator performs lookup operations and iterates over all seen nodes. +// When a lookup finishes, a new one is created through nextLookup. +type lookupIterator struct { + buffer []*node + nextLookup lookupFunc + ctx context.Context + cancel func() + lookup *lookup +} + +type lookupFunc func(ctx context.Context) *lookup + +func newLookupIterator(ctx context.Context, next lookupFunc) *lookupIterator { + ctx, cancel := context.WithCancel(ctx) + return &lookupIterator{ctx: ctx, cancel: cancel, nextLookup: next} +} + +// Node returns the current node. +func (it *lookupIterator) Node() *enode.Node { + if len(it.buffer) == 0 { + return nil + } + return unwrapNode(it.buffer[0]) +} + +// Next moves to the next node. +func (it *lookupIterator) Next() bool { + // Consume next node in buffer. + if len(it.buffer) > 0 { + it.buffer = it.buffer[1:] + } + // Advance the lookup to refill the buffer. + for len(it.buffer) == 0 { + if it.ctx.Err() != nil { + it.lookup = nil + it.buffer = nil + return false + } + if it.lookup == nil { + it.lookup = it.nextLookup(it.ctx) + continue + } + if !it.lookup.advance() { + it.lookup = nil + continue + } + it.buffer = it.lookup.replyBuffer + } + return true +} + +// Close ends the iterator. +func (it *lookupIterator) Close() { + it.cancel() +} diff --git a/p2p/discover/node.go b/p2p/discover/node.go index 48feee9438..7ab0ad82f1 100644 --- a/p2p/discover/node.go +++ b/p2p/discover/node.go @@ -18,13 +18,13 @@ package discover import ( "crypto/ecdsa" + "crypto/elliptic" "errors" "math/big" "net" "time" "github.com/AlayaNetwork/Alaya-Go/common/math" - "github.com/AlayaNetwork/Alaya-Go/crypto/secp256k1" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/crypto" @@ -47,13 +47,16 @@ func encodePubkey(key *ecdsa.PublicKey) encPubkey { return e } -func decodePubkey(e encPubkey) (*ecdsa.PublicKey, error) { - p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)} +func decodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) { + if len(e) != len(encPubkey{}) { + return nil, errors.New("wrong size public key data") + } + p := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)} half := len(e) / 2 p.X.SetBytes(e[:half]) p.Y.SetBytes(e[half:]) if !p.Curve.IsOnCurve(p.X, p.Y) { - return nil, errors.New("invalid secp256k1 curve point") + return nil, errors.New("invalid curve point") } return p, nil } @@ -62,17 +65,6 @@ func (e encPubkey) id() enode.ID { return enode.ID(crypto.Keccak256Hash(e[:])) } -// recoverNodeKey computes the public key used to sign the -// given hash from the signature. -func recoverNodeKey(hash, sig []byte) (key encPubkey, err error) { - pubkey, err := secp256k1.RecoverPubkey(hash, sig) - if err != nil { - return key, err - } - copy(key[:], pubkey[1:]) - return key, nil -} - func wrapNode(n *enode.Node) *node { return &node{Node: *n} } diff --git a/p2p/discover/table.go b/p2p/discover/table.go index dc04b47d3d..3f59fe0ec3 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -54,15 +54,17 @@ const ( bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24 tableIPLimit, tableSubnet = 10, 24 - maxFindnodeFailures = 5 // Nodes exceeding this limit are dropped - refreshInterval = 30 * time.Minute - revalidateInterval = 10 * time.Second - copyNodesInterval = 30 * time.Second - seedMinTableTime = 5 * time.Minute - seedCount = 30 - seedMaxAge = 5 * 24 * time.Hour + refreshInterval = 30 * time.Minute + revalidateInterval = 10 * time.Second + copyNodesInterval = 30 * time.Second + seedMinTableTime = 5 * time.Minute + seedCount = 30 + seedMaxAge = 5 * 24 * time.Hour ) +// Table is the 'node table', a Kademlia-like index of neighbor nodes. The table keeps +// itself up-to-date by verifying the liveness of neighbors and requesting their node +// records when announcements of a new record version are received. type Table struct { mutex sync.Mutex // protects buckets, bucket content, nursery, rand buckets [nBuckets]*bucket // index of known nodes by distance @@ -81,12 +83,13 @@ type Table struct { nodeAddedHook func(*node) // for testing } -// transport is implemented by UDP transports. +// transport is implemented by the UDP transports. type transport interface { Self() *enode.Node + RequestENR(*enode.Node) (*enode.Node, error) lookupRandom() []*enode.Node lookupSelf() []*enode.Node - ping(*enode.Node) error + ping(*enode.Node) (seq uint64, err error) } // bucket contains nodes, ordered by their last activity. the entry @@ -145,45 +148,30 @@ func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) { tab.mutex.Lock() defer tab.mutex.Unlock() - // Find all non-empty buckets and get a fresh slice of their entries. - var buckets [][]*node + var nodes []*enode.Node for _, b := range &tab.buckets { - if len(b.entries) > 0 { - buckets = append(buckets, b.entries) + for _, n := range b.entries { + nodes = append(nodes, unwrapNode(n)) } } - if len(buckets) == 0 { - return 0 + // Shuffle. + for i := 0; i < len(nodes); i++ { + j := tab.rand.Intn(len(nodes)) + nodes[i], nodes[j] = nodes[j], nodes[i] } - // Shuffle the buckets. - for i := len(buckets) - 1; i > 0; i-- { - j := tab.rand.Intn(len(buckets)) - buckets[i], buckets[j] = buckets[j], buckets[i] - } - // Move head of each bucket into buf, removing buckets that become empty. - var i, j int - for ; i < len(buf); i, j = i+1, (j+1)%len(buckets) { - b := buckets[j] - buf[i] = unwrapNode(b[0]) - buckets[j] = b[1:] - if len(b) == 1 { - buckets = append(buckets[:j], buckets[j+1:]...) - } - if len(buckets) == 0 { - break - } - } - return i + 1 + return copy(buf, nodes) } -// Resolve searches for a specific node with the given ID. -// It returns nil if the node could not be found. -func (tab *Table) Resolve(n *enode.Node) *enode.Node { +// getNode returns the node with the given ID or nil if it isn't in the table. +func (tab *Table) getNode(id enode.ID) *enode.Node { tab.mutex.Lock() - cl := tab.closest(n.ID(), 1, false) - tab.mutex.Unlock() - if len(cl.entries) > 0 && cl.entries[0].ID() == n.ID() { - return unwrapNode(cl.entries[0]) + defer tab.mutex.Unlock() + + b := tab.bucket(id) + for _, e := range b.entries { + if e.ID() == id { + return unwrapNode(e) + } } return nil } @@ -227,7 +215,7 @@ func (tab *Table) refresh() <-chan struct{} { return done } -// loop schedules refresh, revalidate runs and coordinates shutdown. +// loop schedules runs of doRefresh, doRevalidate and copyLiveNodes. func (tab *Table) loop() { var ( revalidate = time.NewTimer(tab.nextRevalidateTime()) @@ -289,9 +277,8 @@ loop: close(tab.closed) } -// doRefresh performs a lookup for a random target to keep buckets -// full. seed nodes are inserted if the table is empty (initial -// bootstrap or discarded faulty peers). +// doRefresh performs a lookup for a random target to keep buckets full. seed nodes are +// inserted if the table is empty (initial bootstrap or discarded faulty peers). func (tab *Table) doRefresh(done chan struct{}) { defer close(done) @@ -325,8 +312,8 @@ func (tab *Table) loadSeedNodes() { } } -// doRevalidate checks that the last node in a random bucket is still live -// and replaces or deletes the node if it isn't. +// doRevalidate checks that the last node in a random bucket is still live and replaces or +// deletes the node if it isn't. func (tab *Table) doRevalidate(done chan<- struct{}) { defer func() { done <- struct{}{} }() @@ -337,7 +324,17 @@ func (tab *Table) doRevalidate(done chan<- struct{}) { } // Ping the selected node and wait for a pong. - err := tab.net.ping(unwrapNode(last)) + remoteSeq, err := tab.net.ping(unwrapNode(last)) + + // Also fetch record if the node replied and returned a higher sequence number. + if last.Seq() < remoteSeq { + n, err := tab.net.RequestENR(unwrapNode(last)) + if err != nil { + tab.log.Debug("ENR request failed", "id", last.ID(), "addr", last.addr(), "err", err) + } else { + last = &node{Node: *n, addedAt: last.addedAt, livenessChecks: last.livenessChecks} + } + } tab.mutex.Lock() defer tab.mutex.Unlock() @@ -396,22 +393,35 @@ func (tab *Table) copyLiveNodes() { } } -// closest returns the n nodes in the table that are closest to the -// given id. The caller must hold tab.mutex. -func (tab *Table) closest(target enode.ID, nresults int, checklive bool) *nodesByDistance { - // This is a very wasteful way to find the closest nodes but - // obviously correct. I believe that tree-based buckets would make - // this easier to implement efficiently. - close := &nodesByDistance{target: target} +// findnodeByID returns the n nodes in the table that are closest to the given id. +// This is used by the FINDNODE/v4 handler. +// +// The preferLive parameter says whether the caller wants liveness-checked results. If +// preferLive is true and the table contains any verified nodes, the result will not +// contain unverified nodes. However, if there are no verified nodes at all, the result +// will contain unverified nodes. +func (tab *Table) findnodeByID(target enode.ID, nresults int, preferLive bool) *nodesByDistance { + tab.mutex.Lock() + defer tab.mutex.Unlock() + + // Scan all buckets. There might be a better way to do this, but there aren't that many + // buckets, so this solution should be fine. The worst-case complexity of this loop + // is O(tab.len() * nresults). + nodes := &nodesByDistance{target: target} + liveNodes := &nodesByDistance{target: target} for _, b := range &tab.buckets { for _, n := range b.entries { - if checklive && n.livenessChecks == 0 { - continue + nodes.push(n, nresults) + if preferLive && n.livenessChecks > 0 { + liveNodes.push(n, nresults) } - close.push(n, nresults) } } - return close + + if preferLive && len(liveNodes.entries) > 0 { + return liveNodes + } + return nodes } // len returns the number of nodes in the table. @@ -425,9 +435,21 @@ func (tab *Table) len() (n int) { return n } +// bucketLen returns the number of nodes in the bucket for the given ID. +func (tab *Table) bucketLen(id enode.ID) int { + tab.mutex.Lock() + defer tab.mutex.Unlock() + + return len(tab.bucket(id).entries) +} + // bucket returns the bucket for the given node ID hash. func (tab *Table) bucket(id enode.ID) *bucket { d := enode.LogDist(tab.self().ID(), id) + return tab.bucketAtDistance(d) +} + +func (tab *Table) bucketAtDistance(d int) *bucket { if d <= bucketMinDistance { return tab.buckets[0] } @@ -520,6 +542,9 @@ func (tab *Table) delete(node *node) { } func (tab *Table) addIP(b *bucket, ip net.IP) bool { + if len(ip) == 0 { + return false // Nodes without IP cannot be added. + } if netutil.IsLAN(ip) { return true } diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go index 6496079574..9593c31da4 100644 --- a/p2p/discover/table_test.go +++ b/p2p/discover/table_test.go @@ -59,7 +59,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding // Fill up the sender's bucket. pingKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8") - pingSender := wrapNode(enode.NewV4(&pingKey.PublicKey, net.IP{}, 99, 99)) + pingSender := wrapNode(enode.NewV4(&pingKey.PublicKey, net.IP{127, 0, 0, 1}, 99, 99)) last := fillBucket(tab, pingSender) // Add the sender as if it just pinged us. Revalidate should replace the last node in @@ -191,7 +191,7 @@ func checkIPLimitInvariant(t *testing.T, tab *Table) { } } -func TestTable_closest(t *testing.T) { +func TestTable_findnodeByID(t *testing.T) { t.Parallel() test := func(test *closeTest) bool { @@ -203,7 +203,7 @@ func TestTable_closest(t *testing.T) { fillTable(tab, test.All) // check that closest(Target, N) returns nodes - result := tab.closest(test.Target, test.N, false).entries + result := tab.findnodeByID(test.Target, test.N, false).entries if hasDuplicates(result) { t.Errorf("result contains duplicates") return false @@ -369,6 +369,34 @@ func TestTable_addSeenNode(t *testing.T) { checkIPLimitInvariant(t, tab) } +// This test checks that ENR updates happen during revalidation. If a node in the table +// announces a new sequence number, the new record should be pulled. +func TestTable_revalidateSyncRecord(t *testing.T) { + transport := newPingRecorder() + tab, db := newTestTable(transport) + <-tab.initDone + defer db.Close() + defer tab.close() + + // Insert a node. + var r enr.Record + r.Set(enr.IP(net.IP{127, 0, 0, 1})) + id := enode.ID{1} + n1 := wrapNode(enode.SignNull(&r, id)) + tab.addSeenNode(n1) + + // Update the node record. + r.Set(enr.WithEntry("foo", "bar")) + n2 := enode.SignNull(&r, id) + transport.updateRecord(n2) + + tab.doRevalidate(make(chan struct{}, 1)) + intable := tab.getNode(id) + if !reflect.DeepEqual(intable, n2) { + t.Fatalf("table contains old record with seq %d, want seq %d", intable.Seq(), n2.Seq()) + } +} + // gen wraps quick.Value so it's easier to use. // it generates a random value of the given value's type. func gen(typ interface{}, rand *rand.Rand) interface{} { diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go index 8bad52cae1..2b702e1bfa 100644 --- a/p2p/discover/table_util_test.go +++ b/p2p/discover/table_util_test.go @@ -17,8 +17,10 @@ package discover import ( + "bytes" "crypto/ecdsa" "encoding/hex" + "errors" "fmt" "math/rand" "net" @@ -54,6 +56,23 @@ func nodeAtDistance(base enode.ID, ld int, ip net.IP) *node { return wrapNode(enode.SignNull(&r, idAtDistance(base, ld))) } +// nodesAtDistance creates n nodes for which enode.LogDist(base, node.ID()) == ld. +func nodesAtDistance(base enode.ID, ld int, n int) []*enode.Node { + results := make([]*enode.Node, n) + for i := range results { + results[i] = unwrapNode(nodeAtDistance(base, ld, intIP(i))) + } + return results +} + +func nodesToRecords(nodes []*enode.Node) []*enr.Record { + records := make([]*enr.Record, len(nodes)) + for i := range nodes { + records[i] = nodes[i].Record() + } + return records +} + // 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 { @@ -99,6 +118,7 @@ func fillTable(tab *Table, nodes []*node) { type pingRecorder struct { mu sync.Mutex dead, pinged map[enode.ID]bool + records map[enode.ID]*enode.Node n *enode.Node } @@ -108,38 +128,52 @@ func newPingRecorder() *pingRecorder { n := enode.SignNull(&r, enode.ID{}) return &pingRecorder{ - dead: make(map[enode.ID]bool), - pinged: make(map[enode.ID]bool), - n: n, + dead: make(map[enode.ID]bool), + pinged: make(map[enode.ID]bool), + records: make(map[enode.ID]*enode.Node), + n: n, } } -func (t *pingRecorder) Self() *enode.Node { - return nullNode +// setRecord updates a node record. Future calls to ping and +// requestENR will return this record. +func (t *pingRecorder) updateRecord(n *enode.Node) { + t.mu.Lock() + defer t.mu.Unlock() + t.records[n.ID()] = n } -func (t *pingRecorder) ping(n *enode.Node) error { +// Stubs to satisfy the transport interface. +func (t *pingRecorder) Self() *enode.Node { return nullNode } +func (t *pingRecorder) lookupSelf() []*enode.Node { return nil } +func (t *pingRecorder) lookupRandom() []*enode.Node { return nil } + +// ping simulates a ping request. +func (t *pingRecorder) ping(n *enode.Node) (seq uint64, err error) { t.mu.Lock() defer t.mu.Unlock() t.pinged[n.ID()] = true if t.dead[n.ID()] { - return errTimeout - } else { - return nil + return 0, errTimeout + } + if t.records[n.ID()] != nil { + seq = t.records[n.ID()].Seq() } + return seq, nil } -func (t *pingRecorder) lookupSelf() []*enode.Node { - return nil -} +// requestENR simulates an ENR request. +func (t *pingRecorder) RequestENR(n *enode.Node) (*enode.Node, error) { + t.mu.Lock() + defer t.mu.Unlock() -func (t *pingRecorder) lookupRandom() []*enode.Node { - return nil + if t.dead[n.ID()] || t.records[n.ID()] == nil { + return nil, errTimeout + } + return t.records[n.ID()], nil } -func (t *pingRecorder) close() {} - func hasDuplicates(slice []*node) bool { seen := make(map[enode.ID]bool) for i, e := range slice { @@ -154,12 +188,47 @@ func hasDuplicates(slice []*node) bool { return false } +// checkNodesEqual checks whether the two given node lists contain the same nodes. +func checkNodesEqual(got, want []*enode.Node) error { + if len(got) == len(want) { + for i := range got { + if !nodeEqual(got[i], want[i]) { + goto NotEqual + } + } + } + return nil + +NotEqual: + output := new(bytes.Buffer) + fmt.Fprintf(output, "got %d nodes:\n", len(got)) + for _, n := range got { + fmt.Fprintf(output, " %v %v\n", n.ID(), n) + } + fmt.Fprintf(output, "want %d:\n", len(want)) + for _, n := range want { + fmt.Fprintf(output, " %v %v\n", n.ID(), n) + } + return errors.New(output.String()) +} + +func nodeEqual(n1 *enode.Node, n2 *enode.Node) bool { + return n1.ID() == n2.ID() && n1.IP().Equal(n2.IP()) +} + +func sortByID(nodes []*enode.Node) { + sort.Slice(nodes, func(i, j int) bool { + return string(nodes[i].ID().Bytes()) < string(nodes[j].ID().Bytes()) + }) +} + func sortedByDistanceTo(distbase enode.ID, slice []*node) bool { return sort.SliceIsSorted(slice, func(i, j int) bool { return enode.DistCmp(distbase, slice[i].ID(), slice[j].ID()) < 0 }) } +// hexEncPrivkey decodes h as a private key. func hexEncPrivkey(h string) *ecdsa.PrivateKey { b, err := hex.DecodeString(h) if err != nil { @@ -172,6 +241,7 @@ func hexEncPrivkey(h string) *ecdsa.PrivateKey { return key } +// hexEncPubkey decodes h as a public key. func hexEncPubkey(h string) (ret encPubkey) { b, err := hex.DecodeString(h) if err != nil { diff --git a/p2p/discover/v4_udp_lookup_test.go b/p2p/discover/v4_lookup_test.go similarity index 64% rename from p2p/discover/v4_udp_lookup_test.go rename to p2p/discover/v4_lookup_test.go index 6b3ba7a074..bb16062185 100644 --- a/p2p/discover/v4_udp_lookup_test.go +++ b/p2p/discover/v4_lookup_test.go @@ -20,10 +20,12 @@ import ( "crypto/ecdsa" "fmt" "net" - "reflect" "sort" "testing" + "github.com/AlayaNetwork/Alaya-Go/p2p/discover/v4wire" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" ) @@ -33,7 +35,7 @@ func TestUDPv4_Lookup(t *testing.T) { test := newUDPTest(t) // Lookup on empty table returns no nodes. - targetKey, _ := decodePubkey(lookupTestnet.target) + targetKey, _ := decodePubkey(crypto.S256(), lookupTestnet.target[:]) if results := test.udp.LookupPubkey(targetKey); len(results) > 0 { t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results) } @@ -49,19 +51,7 @@ func TestUDPv4_Lookup(t *testing.T) { }() // Answer lookup packets. - for done := false; !done; { - done = test.waitPacketOut(func(p packetV4, to *net.UDPAddr, hash []byte) { - n, key := lookupTestnet.nodeByAddr(to) - switch p.(type) { - case *pingV4: - test.packetInFrom(nil, key, to, p_pongV4, &pongV4{Expiration: futureExp, ReplyTok: hash}) - case *findnodeV4: - dist := enode.LogDist(n.ID(), lookupTestnet.target.id()) - nodes := lookupTestnet.nodesAtDistance(dist - 1) - test.packetInFrom(nil, key, to, p_neighborsV4, &neighborsV4{Expiration: futureExp, Nodes: nodes}) - } - }) - } + serveTestnet(test, lookupTestnet) // Verify result nodes. results := <-resultC @@ -72,14 +62,112 @@ func TestUDPv4_Lookup(t *testing.T) { if len(results) != bucketSize { t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize) } + checkLookupResults(t, lookupTestnet, results) +} + +func TestUDPv4_LookupIterator(t *testing.T) { + t.Parallel() + test := newUDPTest(t) + defer test.close() + + // Seed table with initial nodes. + bootnodes := make([]*node, len(lookupTestnet.dists[256])) + for i := range lookupTestnet.dists[256] { + bootnodes[i] = wrapNode(lookupTestnet.node(256, i)) + } + fillTable(test.table, bootnodes) + go serveTestnet(test, lookupTestnet) + + // Create the iterator and collect the nodes it yields. + iter := test.udp.RandomNodes() + seen := make(map[enode.ID]*enode.Node) + for limit := lookupTestnet.len(); iter.Next() && len(seen) < limit; { + seen[iter.Node().ID()] = iter.Node() + } + iter.Close() + + // Check that all nodes in lookupTestnet were seen by the iterator. + results := make([]*enode.Node, 0, len(seen)) + for _, n := range seen { + results = append(results, n) + } + sortByID(results) + want := lookupTestnet.nodes() + if err := checkNodesEqual(results, want); err != nil { + t.Fatal(err) + } +} + +// TestUDPv4_LookupIteratorClose checks that lookupIterator ends when its Close +// method is called. +func TestUDPv4_LookupIteratorClose(t *testing.T) { + t.Parallel() + test := newUDPTest(t) + defer test.close() + + // Seed table with initial nodes. + bootnodes := make([]*node, len(lookupTestnet.dists[256])) + for i := range lookupTestnet.dists[256] { + bootnodes[i] = wrapNode(lookupTestnet.node(256, i)) + } + fillTable(test.table, bootnodes) + go serveTestnet(test, lookupTestnet) + + it := test.udp.RandomNodes() + if ok := it.Next(); !ok || it.Node() == nil { + t.Fatalf("iterator didn't return any node") + } + + it.Close() + + ncalls := 0 + for ; ncalls < 100 && it.Next(); ncalls++ { + if it.Node() == nil { + t.Error("iterator returned Node() == nil node after Next() == true") + } + } + t.Logf("iterator returned %d nodes after close", ncalls) + if it.Next() { + t.Errorf("Next() == true after close and %d more calls", ncalls) + } + if n := it.Node(); n != nil { + t.Errorf("iterator returned non-nil node after close and %d more calls", ncalls) + } +} + +func serveTestnet(test *udpTest, testnet *preminedTestnet) { + for done := false; !done; { + done = test.waitPacketOut(func(p v4wire.Packet, to *net.UDPAddr, hash []byte) { + n, key := testnet.nodeByAddr(to) + switch p.(type) { + case *v4wire.Ping: + test.packetInFrom(nil, key, to, &v4wire.Pong{Expiration: futureExp, ReplyTok: hash, Rest: cRest}) + case *v4wire.Findnode: + dist := enode.LogDist(n.ID(), testnet.target.id()) + nodes := testnet.nodesAtDistance(dist - 1) + test.packetInFrom(nil, key, to, &v4wire.Neighbors{Expiration: futureExp, Nodes: nodes, Rest: cRest}) + } + }) + } +} + +// checkLookupResults verifies that the results of a lookup are the closest nodes to +// the testnet's target. +func checkLookupResults(t *testing.T, tn *preminedTestnet, results []*enode.Node) { + t.Helper() + t.Logf("results:") + for _, e := range results { + t.Logf(" ld=%d, %x", enode.LogDist(tn.target.id(), e.ID()), e.ID().Bytes()) + } if hasDuplicates(wrapNodes(results)) { t.Errorf("result set contains duplicate entries") } - if !sortedByDistanceTo(lookupTestnet.target.id(), wrapNodes(results)) { + if !sortedByDistanceTo(tn.target.id(), wrapNodes(results)) { t.Errorf("result set not sorted by distance to target") } - if !reflect.DeepEqual(results, lookupTestnet.closest(bucketSize)) { - t.Errorf("results aren't the closest %d nodes", bucketSize) + wantNodes := tn.closest(len(results)) + if err := checkNodesEqual(results, wantNodes); err != nil { + t.Error(err) } } @@ -148,10 +236,33 @@ type preminedTestnet struct { dists [hashBits + 1][]*ecdsa.PrivateKey } +func (tn *preminedTestnet) len() int { + n := 0 + for _, keys := range tn.dists { + n += len(keys) + } + return n +} + +func (tn *preminedTestnet) nodes() []*enode.Node { + result := make([]*enode.Node, 0, tn.len()) + for dist, keys := range tn.dists { + for index := range keys { + result = append(result, tn.node(dist, index)) + } + } + sortByID(result) + return result +} + func (tn *preminedTestnet) node(dist, index int) *enode.Node { key := tn.dists[dist][index] - ip := net.IP{127, byte(dist >> 8), byte(dist), byte(index)} - return enode.NewV4(&key.PublicKey, ip, 0, 5000) + rec := new(enr.Record) + rec.Set(enr.IP{127, byte(dist >> 8), byte(dist), byte(index)}) + rec.Set(enr.UDP(5000)) + enode.SignV4(rec, key) + n, _ := enode.New(enode.ValidSchemes, rec) + return n } func (tn *preminedTestnet) nodeByAddr(addr *net.UDPAddr) (*enode.Node, *ecdsa.PrivateKey) { @@ -161,14 +272,31 @@ func (tn *preminedTestnet) nodeByAddr(addr *net.UDPAddr) (*enode.Node, *ecdsa.Pr return tn.node(dist, index), key } -func (tn *preminedTestnet) nodesAtDistance(dist int) []rpcNode { - result := make([]rpcNode, len(tn.dists[dist])) +func (tn *preminedTestnet) nodesAtDistance(dist int) []v4wire.Node { + result := make([]v4wire.Node, len(tn.dists[dist])) for i := range result { result[i] = nodeToRPC(wrapNode(tn.node(dist, i))) } return result } +func (tn *preminedTestnet) neighborsAtDistances(base *enode.Node, distances []uint, elems int) []*enode.Node { + var result []*enode.Node + for d := range lookupTestnet.dists { + for i := range lookupTestnet.dists[d] { + n := lookupTestnet.node(d, i) + d := enode.LogDist(base.ID(), n.ID()) + if containsUint(uint(d), distances) { + result = append(result, n) + if len(result) >= elems { + return result + } + } + } + } + return result +} + func (tn *preminedTestnet) closest(n int) (nodes []*enode.Node) { for d := range tn.dists { for i := range tn.dists[d] { diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go index 8f40638418..aff6c29ad5 100644 --- a/p2p/discover/v4_udp.go +++ b/p2p/discover/v4_udp.go @@ -19,6 +19,7 @@ package discover import ( "bytes" "container/list" + "context" "crypto/ecdsa" crand "crypto/rand" "errors" @@ -29,6 +30,8 @@ import ( "sync" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/discover/v4wire" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/crypto" @@ -39,23 +42,26 @@ import ( // Errors var ( - errPacketTooSmall = errors.New("too small") - errBadHash = errors.New("bad hash") errExpired = errors.New("expired") errUnsolicitedReply = errors.New("unsolicited reply") errUnknownNode = errors.New("unknown node") errTimeout = errors.New("RPC timeout") errClockWarp = errors.New("reply deadline too far in the future") errClosed = errors.New("socket closed") + errLowPort = errors.New("low port") errData = errors.New("received data error") ) -// Timeouts +var ( + cRest = []rlp.RawValue{{0x65}, {0x65}} +) + const ( respTimeout = 500 * time.Millisecond expiration = 20 * time.Second bondExpiration = 24 * time.Hour + maxFindnodeFailures = 5 // nodes exceeding this limit are dropped ntpFailureThreshold = 32 // Continuous timeouts after which to check NTP ntpWarningCooldown = 10 * time.Minute // Minimum amount of time to pass before repeating NTP warning driftThreshold = 10 * time.Second // Allowed clock drift before warning user @@ -66,121 +72,6 @@ const ( maxPacketSize = 1280 ) -// RPC packet types -const ( - p_pingV4 = iota + 1 // zero is 'reserved' - p_pongV4 - p_findnodeV4 - p_neighborsV4 -) - -var ( - cRest = []rlp.RawValue{{0x65}, {0x65}} -) - -// RPC request structures -type ( - pingV4 struct { - senderKey *ecdsa.PublicKey // filled in by preverify - - Version uint - From, To rpcEndpoint - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // pongV4 is the reply to pingV4. - pongV4 struct { - // This field should mirror the UDP envelope address - // of the ping packet, which provides a way to discover the - // the external address (after NAT). - To rpcEndpoint - - ReplyTok []byte // This contains the hash of the ping packet. - Expiration uint64 // Absolute timestamp at which the packet becomes invalid. - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // findnodeV4 is a query for nodes close to the given target. - findnodeV4 struct { - Target encPubkey - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // neighborsV4 is the reply to findnodeV4. - neighborsV4 struct { - Nodes []rpcNode - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - rpcNode struct { - IP net.IP // len 4 for IPv4 or 16 for IPv6 - UDP uint16 // for discovery protocol - TCP uint16 // for RLPx protocol - ID encPubkey - } - - rpcEndpoint struct { - IP net.IP // len 4 for IPv4 or 16 for IPv6 - UDP uint16 // for discovery protocol - TCP uint16 // for RLPx protocol - } -) - -// packet is implemented by all v4 protocol messages. -type packetV4 interface { - // preverify checks whether the packet is valid and should be handled at all. - preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error - // handle handles the packet. - handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) - // name returns the name of the packet for logging purposes. - name() string -} - -func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint { - ip := net.IP{} - if ip4 := addr.IP.To4(); ip4 != nil { - ip = ip4 - } else if ip6 := addr.IP.To16(); ip6 != nil { - ip = ip6 - } - return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort} -} - -func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*node, error) { - if rn.UDP <= 1024 { - return nil, errors.New("low port") - } - if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil { - return nil, err - } - if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) { - return nil, errors.New("not contained in netrestrict whitelist") - } - key, err := decodePubkey(rn.ID) - if err != nil { - return nil, err - } - n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP))) - err = n.ValidateComplete() - return n, err -} - -func nodeToRPC(n *node) rpcNode { - var key ecdsa.PublicKey - 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())} -} - // UDPv4 implements the v4 wire protocol. type UDPv4 struct { conn UDPConn @@ -195,10 +86,11 @@ type UDPv4 struct { addReplyMatcher chan *replyMatcher gotreply chan reply - closing chan struct{} + closeCtx context.Context + cancelCloseCtx context.CancelFunc } -// pending represents a pending reply. +// replyMatcher represents a pending reply. // // Some implementations of the protocol wish to send more than one // reply packet to findnode. In general, any neighbors packet cannot @@ -224,37 +116,41 @@ type replyMatcher struct { // errc receives nil when the callback indicates completion or an // error if no further reply is received within the timeout. - errc chan<- error + errc chan error + + // reply contains the most recent reply. This field is safe for reading after errc has + // received a value. + reply v4wire.Packet } -type replyMatchFunc func(interface{}) (matched bool, requestDone bool) +type replyMatchFunc func(v4wire.Packet) (matched bool, requestDone bool) +// reply is a reply packet from a certain node. type reply struct { - from enode.ID - ip net.IP - ptype byte - data packetV4 - + from enode.ID + ip net.IP + data v4wire.Packet // loop indicates whether there was // a matching request by sending on this channel. matched chan<- bool } func ListenV4(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) { + cfg = cfg.withDefaults() + closeCtx, cancel := context.WithCancel(context.Background()) t := &UDPv4{ conn: c, priv: cfg.PrivateKey, netrestrict: cfg.NetRestrict, localNode: ln, db: ln.Database(), - closing: make(chan struct{}), gotreply: make(chan reply), addReplyMatcher: make(chan *replyMatcher), + closeCtx: closeCtx, + cancelCloseCtx: cancel, log: cfg.Log, } - if t.log == nil { - t.log = log.Root() - } + tab, err := newTable(t, ln.Database(), cfg.Bootnodes, t.log) if err != nil { return nil, err @@ -282,175 +178,78 @@ func (t *UDPv4) Self() *enode.Node { // Close shuts down the socket and aborts any running queries. func (t *UDPv4) Close() { t.closeOnce.Do(func() { - close(t.closing) + t.cancelCloseCtx() t.conn.Close() t.wg.Wait() t.tab.close() }) } -// ReadRandomNodes reads random nodes from the local table. -func (t *UDPv4) ReadRandomNodes(buf []*enode.Node) int { - return t.tab.ReadRandomNodes(buf) -} - -// LookupRandom finds random nodes in the network. -func (t *UDPv4) LookupRandom() []*enode.Node { - if t.tab.len() == 0 { - // All nodes were dropped, refresh. The very first query will hit this - // case and run the bootstrapping logic. - <-t.tab.refresh() - } - return t.lookupRandom() -} - -func (t *UDPv4) LookupPubkey(key *ecdsa.PublicKey) []*enode.Node { - if t.tab.len() == 0 { - // All nodes were dropped, refresh. The very first query will hit this - // case and run the bootstrapping logic. - <-t.tab.refresh() - } - return unwrapNodes(t.lookup(encodePubkey(key))) -} - -func (t *UDPv4) lookupRandom() []*enode.Node { - var target encPubkey - crand.Read(target[:]) - return unwrapNodes(t.lookup(target)) -} - -func (t *UDPv4) lookupSelf() []*enode.Node { - return unwrapNodes(t.lookup(encodePubkey(&t.priv.PublicKey))) -} - -// lookup performs a network search for nodes close to the given target. It approaches the -// 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 (t *UDPv4) lookup(targetKey encPubkey) []*node { - var ( - target = enode.ID(crypto.Keccak256Hash(targetKey[:])) - asked = make(map[enode.ID]bool) - seen = make(map[enode.ID]bool) - reply = make(chan []*node, alpha) - pendingQueries = 0 - result *nodesByDistance - ) - // Don't query further if we hit ourself. - // Unlikely to happen often in practice. - asked[t.Self().ID()] = true - - // Generate the initial result set. - t.tab.mutex.Lock() - result = t.tab.closest(target, bucketSize, false) - t.tab.mutex.Unlock() - - for { - // ask the alpha closest nodes that we haven't asked yet - for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ { - n := result.entries[i] - if !asked[n.ID()] { - asked[n.ID()] = true - pendingQueries++ - go t.lookupWorker(n, targetKey, reply) - } - } - if pendingQueries == 0 { - // we have asked all closest nodes, stop the search - break - } - select { - case nodes := <-reply: - for _, n := range nodes { - if n != nil && !seen[n.ID()] { - seen[n.ID()] = true - result.push(n, bucketSize) - } - } - case <-t.tab.closeReq: - return nil // shutdown, no need to continue. - } - pendingQueries-- - } - return result.entries -} - -func (t *UDPv4) lookupWorker(n *node, targetKey encPubkey, reply chan<- []*node) { - fails := t.db.FindFails(n.ID(), n.IP()) - r, err := t.findnode(n.ID(), n.addr(), targetKey) - if err == errClosed { - // Avoid recording failures on shutdown. - reply <- nil - return - } else if len(r) == 0 { - fails++ - t.db.UpdateFindFails(n.ID(), n.IP(), fails) - t.log.Trace("Findnode failed", "id", n.ID(), "failcount", fails, "err", err) - if fails >= maxFindnodeFailures { - t.log.Trace("Too many findnode failures, dropping", "id", n.ID(), "failcount", fails) - t.tab.delete(n) +// Resolve searches for a specific node with the given ID and tries to get the most recent +// version of the node record for it. It returns n if the node could not be resolved. +func (t *UDPv4) Resolve(n *enode.Node) *enode.Node { + // Try asking directly. This works if the node is still responding on the endpoint we have. + if rn, err := t.RequestENR(n); err == nil { + return rn + } + // Check table for the ID, we might have a newer version there. + if intable := t.tab.getNode(n.ID()); intable != nil && intable.Seq() > n.Seq() { + n = intable + if rn, err := t.RequestENR(n); err == nil { + return rn } - } else if fails > 0 { - t.db.UpdateFindFails(n.ID(), n.IP(), 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 { - t.tab.addSeenNode(n) + // Otherwise perform a network lookup. + var key enode.Secp256k1 + if n.Load(&key) != nil { + return n // no secp256k1 key } - reply <- r -} - -// Resolve searches for a specific node with the given ID. -// It returns nil if the node could not be found. -func (t *UDPv4) Resolve(n *enode.Node) *enode.Node { - // If the node is present in the local table, no - // network interaction is required. - if intab := t.tab.Resolve(n); intab != nil { - return intab - } - // Otherwise, do a network lookup. - hash := n.ID() - result := t.LookupPubkey(n.Pubkey()) - for _, n := range result { - if n.ID() == hash { - return n + result := t.LookupPubkey((*ecdsa.PublicKey)(&key)) + for _, rn := range result { + if rn.ID() == n.ID() { + if rn, err := t.RequestENR(rn); err == nil { + return rn + } } } - return nil + return n } -func (t *UDPv4) ourEndpoint() rpcEndpoint { +func (t *UDPv4) ourEndpoint() v4wire.Endpoint { n := t.Self() a := &net.UDPAddr{IP: n.IP(), Port: n.UDP()} - return makeEndpoint(a, uint16(n.TCP())) + return v4wire.NewEndpoint(a, uint16(n.TCP())) +} + +// Ping sends a ping message to the given node. +func (t *UDPv4) Ping(n *enode.Node) error { + _, err := t.ping(n) + return err } // ping sends a ping message to the given node and waits for a reply. -func (t *UDPv4) ping(n *enode.Node) error { - return <-t.sendPing(n.ID(), &net.UDPAddr{IP: n.IP(), Port: n.UDP()}, nil) +func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) { + rm := t.sendPing(n.ID(), &net.UDPAddr{IP: n.IP(), Port: n.UDP()}, nil) + if err = <-rm.errc; err == nil { + seq = rm.reply.(*v4wire.Pong).ENRSeq + } + return seq, err } // sendPing sends a ping message to the given node and invokes the callback // when the reply arrives. -func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) <-chan error { - req := &pingV4{ - Version: 4, - From: t.ourEndpoint(), - To: makeEndpoint(toaddr, 0), // TODO: maybe use known TCP port from DB - Expiration: uint64(time.Now().Add(expiration).Unix()), - Rest: cRest, - } - packet, hash, err := t.encode(t.priv, p_pingV4, req) +func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *replyMatcher { + req := t.makePing(toaddr) + packet, hash, err := v4wire.Encode(t.priv, req) if err != nil { errc := make(chan error, 1) errc <- err - return errc + return &replyMatcher{errc: errc} } // Add a matcher for the reply to the pending reply queue. Pongs are matched if they // reference the ping we're about to send. - errc := t.pending(toid, toaddr.IP, p_pongV4, func(p interface{}) (matched bool, requestDone bool) { - matched = bytes.Equal(p.(*pongV4).ReplyTok, hash) + rm := t.pending(toid, toaddr.IP, v4wire.PongPacket, func(p v4wire.Packet) (matched bool, requestDone bool) { + matched = bytes.Equal(p.(*v4wire.Pong).ReplyTok, hash) if matched && callback != nil { callback() } @@ -458,27 +257,72 @@ func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) <- }) // Send the packet. t.localNode.UDPContact(toaddr) - t.write(toaddr, toid, req.name(), packet) - return errc + t.write(toaddr, toid, req.Name(), packet) + return rm +} + +func (t *UDPv4) makePing(toaddr *net.UDPAddr) *v4wire.Ping { + return &v4wire.Ping{ + Version: 4, + From: t.ourEndpoint(), + To: v4wire.NewEndpoint(toaddr, 0), + Expiration: uint64(time.Now().Add(expiration).Unix()), + ENRSeq: t.localNode.Node().Seq(), + Rest: cRest, + } +} + +// LookupPubkey finds the closest nodes to the given public key. +func (t *UDPv4) LookupPubkey(key *ecdsa.PublicKey) []*enode.Node { + if t.tab.len() == 0 { + // All nodes were dropped, refresh. The very first query will hit this + // case and run the bootstrapping logic. + <-t.tab.refresh() + } + return t.newLookup(t.closeCtx, encodePubkey(key)).run() +} + +// RandomNodes is an iterator yielding nodes from a random walk of the DHT. +func (t *UDPv4) RandomNodes() enode.Iterator { + return newLookupIterator(t.closeCtx, t.newRandomLookup) +} + +// lookupRandom implements transport. +func (t *UDPv4) lookupRandom() []*enode.Node { + return t.newRandomLookup(t.closeCtx).run() +} + +// lookupSelf implements transport. +func (t *UDPv4) lookupSelf() []*enode.Node { + return t.newLookup(t.closeCtx, encodePubkey(&t.priv.PublicKey)).run() +} + +func (t *UDPv4) newRandomLookup(ctx context.Context) *lookup { + var target encPubkey + crand.Read(target[:]) + return t.newLookup(ctx, target) +} + +func (t *UDPv4) newLookup(ctx context.Context, targetKey encPubkey) *lookup { + target := enode.ID(crypto.Keccak256Hash(targetKey[:])) + ekey := v4wire.Pubkey(targetKey) + it := newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) { + return t.findnode(n.ID(), n.addr(), ekey) + }) + return it } // findnode sends a findnode request to the given node and waits until // the node has sent up to k neighbors. -func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) { - // If we haven't seen a ping from the destination node for a while, it won't remember - // our endpoint proof and reject findnode. Solicit a ping first. - if time.Since(t.db.LastPingReceived(toid, toaddr.IP)) > bondExpiration { - <-t.sendPing(toid, toaddr, nil) - // Wait for them to ping back and process our pong. - time.Sleep(respTimeout) - } +func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubkey) ([]*node, error) { + t.ensureBond(toid, toaddr) // Add a matcher for 'neighbours' replies to the pending reply queue. The matcher is // active until enough nodes have been received. nodes := make([]*node, 0, bucketSize) nreceived := 0 - errc := t.pending(toid, toaddr.IP, p_neighborsV4, func(r interface{}) (matched bool, requestDone bool) { - reply := r.(*neighborsV4) + rm := t.pending(toid, toaddr.IP, v4wire.NeighborsPacket, func(r v4wire.Packet) (matched bool, requestDone bool) { + reply := r.(*v4wire.Neighbors) for _, rn := range reply.Nodes { nreceived++ n, err := t.nodeFromRPC(toaddr, rn) @@ -490,37 +334,87 @@ func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ( } return true, nreceived >= bucketSize }) - t.send(toaddr, toid, p_findnodeV4, &findnodeV4{ + t.send(toaddr, toid, &v4wire.Findnode{ Target: target, Expiration: uint64(time.Now().Add(expiration).Unix()), Rest: cRest, }) - return nodes, <-errc + // Ensure that callers don't see a timeout if the node actually responded. Since + // findnode can receive more than one neighbors response, the reply matcher will be + // active until the remote node sends enough nodes. If the remote end doesn't have + // enough nodes the reply matcher will time out waiting for the second reply, but + // there's no need for an error in that case. + err := <-rm.errc + if err == errTimeout && rm.reply != nil { + err = nil + } + return nodes, err +} + +// RequestENR sends enrRequest to the given node and waits for a response. +func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) { + addr := &net.UDPAddr{IP: n.IP(), Port: n.UDP()} + t.ensureBond(n.ID(), addr) + + req := &v4wire.ENRRequest{ + Expiration: uint64(time.Now().Add(expiration).Unix()), + } + packet, hash, err := v4wire.Encode(t.priv, req) + if err != nil { + return nil, err + } + + // Add a matcher for the reply to the pending reply queue. Responses are matched if + // they reference the request we're about to send. + rm := t.pending(n.ID(), addr.IP, v4wire.ENRResponsePacket, func(r v4wire.Packet) (matched bool, requestDone bool) { + matched = bytes.Equal(r.(*v4wire.ENRResponse).ReplyTok, hash) + return matched, matched + }) + // Send the packet and wait for the reply. + t.write(addr, n.ID(), req.Name(), packet) + if err := <-rm.errc; err != nil { + return nil, err + } + // Verify the response record. + respN, err := enode.New(enode.ValidSchemes, &rm.reply.(*v4wire.ENRResponse).Record) + if err != nil { + return nil, err + } + if respN.ID() != n.ID() { + return nil, fmt.Errorf("invalid ID in response record") + } + if respN.Seq() < n.Seq() { + return n, nil // response record is older + } + if err := netutil.CheckRelayIP(addr.IP, respN.IP()); err != nil { + return nil, fmt.Errorf("invalid IP in response record: %v", err) + } + return respN, nil } // pending adds a reply matcher to the pending reply queue. // see the documentation of type replyMatcher for a detailed explanation. -func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchFunc) <-chan error { +func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchFunc) *replyMatcher { ch := make(chan error, 1) p := &replyMatcher{from: id, ip: ip, ptype: ptype, callback: callback, errc: ch} select { case t.addReplyMatcher <- p: // loop will handle it - case <-t.closing: + case <-t.closeCtx.Done(): ch <- errClosed } - return ch + return p } // handleReply dispatches a reply packet, invoking reply matchers. It returns // whether any matcher considered the packet acceptable. -func (t *UDPv4) handleReply(from enode.ID, fromIP net.IP, ptype byte, req packetV4) bool { +func (t *UDPv4) handleReply(from enode.ID, fromIP net.IP, req v4wire.Packet) bool { matched := make(chan bool, 1) select { - case t.gotreply <- reply{from, fromIP, ptype, req, matched}: + case t.gotreply <- reply{from, fromIP, req, matched}: // loop will handle it return <-matched - case <-t.closing: + case <-t.closeCtx.Done(): return false } } @@ -566,7 +460,7 @@ func (t *UDPv4) loop() { resetTimeout() select { - case <-t.closing: + case <-t.closeCtx.Done(): for el := plist.Front(); el != nil; el = el.Next() { el.Value.(*replyMatcher).errc <- errClosed } @@ -580,9 +474,10 @@ func (t *UDPv4) loop() { var matched bool // whether any replyMatcher considered the reply acceptable. for el := plist.Front(); el != nil; el = el.Next() { p := el.Value.(*replyMatcher) - if p.from == r.from && p.ptype == r.ptype && p.ip.Equal(r.ip) { + if p.from == r.from && p.ptype == r.data.Kind() && p.ip.Equal(r.ip) { ok, requestDone := p.callback(r.data) matched = matched || ok + p.reply = r.data // Remove the matcher if callback indicates that all replies have been received. if requestDone { p.errc <- nil @@ -618,44 +513,12 @@ func (t *UDPv4) loop() { } } -const ( - macSize = 256 / 8 - sigSize = 520 / 8 - headSize = macSize + sigSize // space of packet frame data -) - -var ( - headSpace = make([]byte, headSize) - - // Neighbors replies are sent across multiple packets to - // stay below the packet size limit. We compute the maximum number - // of entries by stuffing a packet until it grows too large. - maxNeighbors int -) - -func init() { - p := neighborsV4{Expiration: ^uint64(0)} - maxSizeNode := rpcNode{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)} - for n := 0; ; n++ { - p.Nodes = append(p.Nodes, maxSizeNode) - size, _, err := rlp.EncodeToReader(p) - if err != nil { - // If this ever happens, it will be caught by the unit tests. - panic("cannot encode: " + err.Error()) - } - if headSize+size+1 >= maxPacketSize { - maxNeighbors = n - break - } - } -} - -func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, ptype byte, req packetV4) ([]byte, error) { - packet, hash, err := t.encode(t.priv, ptype, req) +func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, req v4wire.Packet) ([]byte, error) { + packet, hash, err := v4wire.Encode(t.priv, req) if err != nil { return hash, err } - return hash, t.write(toaddr, toid, req.name(), packet) + return hash, t.write(toaddr, toid, req.Name(), packet) } func (t *UDPv4) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet []byte) error { @@ -664,29 +527,6 @@ func (t *UDPv4) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet [] return err } -func (t *UDPv4) encode(priv *ecdsa.PrivateKey, ptype byte, req interface{}) (packet, hash []byte, err error) { - b := new(bytes.Buffer) - b.Write(headSpace) - b.WriteByte(ptype) - if err := rlp.Encode(b, req); err != nil { - t.log.Error("Can't encode discv4 packet", "err", err) - return nil, nil, err - } - packet = b.Bytes() - sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv) - if err != nil { - t.log.Error("Can't sign discv4 packet", "err", err) - return nil, nil, err - } - copy(packet[macSize:], sig) - // add the hash to the front. Note: this doesn't protect the - // packet in any way. Our public key will be part of this hash in - // The future. - hash = crypto.Keccak256(packet[macSize:]) - copy(packet, hash) - return packet, hash, nil -} - // readLoop runs in its own goroutine. it handles incoming UDP packets. func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) { defer t.wg.Done() @@ -718,84 +558,133 @@ func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) { } func (t *UDPv4) handlePacket(from *net.UDPAddr, buf []byte) error { - packet, fromKey, hash, err := decodeV4(buf) + rawpacket, fromKey, hash, err := v4wire.Decode(buf) if err != nil { t.log.Debug("Bad discv4 packet", "addr", from, "err", err) return err } - fromID := fromKey.id() - if err == nil { - err = packet.preverify(t, from, fromID, fromKey) + packet := t.wrapPacket(rawpacket) + fromID := fromKey.ID() + if err == nil && packet.preverify != nil { + err = packet.preverify(packet, from, fromID, fromKey) } - t.log.Trace("<< "+packet.name(), "id", fromID, "addr", from, "err", err) - if err == nil { - packet.handle(t, from, fromID, hash) + t.log.Trace("<< "+packet.Name(), "id", fromID, "addr", from, "err", err) + if err == nil && packet.handle != nil { + packet.handle(packet, from, fromID, hash) } return err } -func decodeV4(buf []byte) (packetV4, encPubkey, []byte, error) { - if len(buf) < headSize+1 { - return nil, encPubkey{}, nil, errPacketTooSmall +// checkBond checks if the given node has a recent enough endpoint proof. +func (t *UDPv4) checkBond(id enode.ID, ip net.IP) bool { + return time.Since(t.db.LastPongReceived(id, ip)) < bondExpiration +} + +// ensureBond solicits a ping from a node if we haven't seen a ping from it for a while. +// This ensures there is a valid endpoint proof on the remote end. +func (t *UDPv4) ensureBond(toid enode.ID, toaddr *net.UDPAddr) { + tooOld := time.Since(t.db.LastPingReceived(toid, toaddr.IP)) > bondExpiration + if tooOld || t.db.FindFails(toid, toaddr.IP) > maxFindnodeFailures { + rm := t.sendPing(toid, toaddr, nil) + <-rm.errc + // Wait for them to ping back and process our pong. + time.Sleep(respTimeout) } - hash, sig, sigdata := buf[:macSize], buf[macSize:headSize], buf[headSize:] - shouldhash := crypto.Keccak256(buf[macSize:]) - if !bytes.Equal(hash, shouldhash) { - return nil, encPubkey{}, nil, errBadHash +} + +func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn v4wire.Node) (*node, error) { + if rn.UDP <= 1024 { + return nil, errLowPort } - fromKey, err := recoverNodeKey(crypto.Keccak256(buf[headSize:]), sig) - if err != nil { - return nil, fromKey, hash, err + if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil { + return nil, err } - - var req packetV4 - switch ptype := sigdata[0]; ptype { - case p_pingV4: - req = new(pingV4) - case p_pongV4: - req = new(pongV4) - case p_findnodeV4: - req = new(findnodeV4) - case p_neighborsV4: - req = new(neighborsV4) - default: - return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype) + if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) { + return nil, errors.New("not contained in netrestrict list") } - s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0) - err = s.Decode(req) - return req, fromKey, hash, err + key, err := v4wire.DecodePubkey(crypto.S256(), rn.ID) + if err != nil { + return nil, err + } + n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP))) + err = n.ValidateComplete() + return n, err } -// Packet Handlers +func nodeToRPC(n *node) v4wire.Node { + var key ecdsa.PublicKey + var ekey v4wire.Pubkey + if err := n.Load((*enode.Secp256k1)(&key)); err == nil { + ekey = v4wire.EncodePubkey(&key) + } + return v4wire.Node{ID: ekey, IP: n.IP(), UDP: uint16(n.UDP()), TCP: uint16(n.TCP())} +} + +// wrapPacket returns the handler functions applicable to a packet. +func (t *UDPv4) wrapPacket(p v4wire.Packet) *packetHandlerV4 { + var h packetHandlerV4 + h.Packet = p + switch p.(type) { + case *v4wire.Ping: + h.preverify = t.verifyPing + h.handle = t.handlePing + case *v4wire.Pong: + h.preverify = t.verifyPong + case *v4wire.Findnode: + h.preverify = t.verifyFindnode + h.handle = t.handleFindnode + case *v4wire.Neighbors: + h.preverify = t.verifyNeighbors + case *v4wire.ENRRequest: + h.preverify = t.verifyENRRequest + h.handle = t.handleENRRequest + case *v4wire.ENRResponse: + h.preverify = t.verifyENRResponse + } + return &h +} + +// packetHandlerV4 wraps a packet with handler functions. +type packetHandlerV4 struct { + v4wire.Packet + senderKey *ecdsa.PublicKey // used for ping -func (req *pingV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { - if expired(req.Expiration) { - return errExpired - } + // preverify checks whether the packet is valid and should be handled at all. + preverify func(p *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error + // handle handles the packet. + handle func(req *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) +} - if !reflect.DeepEqual(req.Rest, cRest) { - return errData - } +// PING/v4 + +func (t *UDPv4) verifyPing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error { + req := h.Packet.(*v4wire.Ping) - key, err := decodePubkey(fromKey) + senderKey, err := v4wire.DecodePubkey(crypto.S256(), fromKey) if err != nil { - return errors.New("invalid public key") + return err + } + if v4wire.Expired(req.Expiration) { + return errExpired } - req.senderKey = key + h.senderKey = senderKey return nil } -func (req *pingV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) { +func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) { + req := h.Packet.(*v4wire.Ping) + // Reply. - t.send(from, fromID, p_pongV4, &pongV4{ - To: makeEndpoint(from, req.From.TCP), + t.send(from, fromID, &v4wire.Pong{ + To: v4wire.NewEndpoint(from, req.From.TCP), ReplyTok: mac, Expiration: uint64(time.Now().Add(expiration).Unix()), + ENRSeq: t.localNode.Node().Seq(), Rest: cRest, }) // Ping back if our last pong on file is too far in the past. - n := wrapNode(enode.NewV4(req.senderKey, from.IP, int(req.From.TCP), from.Port)) + n := wrapNode(enode.NewV4(h.senderKey, from.IP, int(req.From.TCP), from.Port)) if time.Since(t.db.LastPongReceived(n.ID(), from.IP)) > bondExpiration { t.sendPing(fromID, from, func() { t.tab.addVerifiedNode(n) @@ -809,33 +698,34 @@ func (req *pingV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []by t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)}) } -func (req *pingV4) name() string { return "PING/v4" } +// PONG/v4 -func (req *pongV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { - if expired(req.Expiration) { +func (t *UDPv4) verifyPong(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error { + req := h.Packet.(*v4wire.Pong) + + if v4wire.Expired(req.Expiration) { return errExpired } - if !t.handleReply(fromID, from.IP, p_pongV4, req) { + if !t.handleReply(fromID, from.IP, req) { return errUnsolicitedReply } - return nil -} - -func (req *pongV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) { t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)}) t.db.UpdateLastPongReceived(fromID, from.IP, time.Now()) + return nil } -func (req *pongV4) name() string { return "PONG/v4" } +// FINDNODE/v4 + +func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error { + req := h.Packet.(*v4wire.Findnode) -func (req *findnodeV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { - if expired(req.Expiration) { + if v4wire.Expired(req.Expiration) { return errExpired } if !reflect.DeepEqual(req.Rest, cRest) { return errData } - if time.Since(t.db.LastPongReceived(fromID, from.IP)) > bondExpiration { + if !t.checkBond(fromID, from.IP) { // No endpoint proof pong exists, we don't process the packet. This prevents an // attack vector where the discovery protocol could be used to amplify traffic in a // DDOS attack. A malicious actor would send a findnode request with the IP address @@ -847,55 +737,75 @@ func (req *findnodeV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, f return nil } -func (req *findnodeV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) { +func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) { + req := h.Packet.(*v4wire.Findnode) + // Determine closest nodes. target := enode.ID(crypto.Keccak256Hash(req.Target[:])) - t.tab.mutex.Lock() - closest := t.tab.closest(target, bucketSize, true).entries - t.tab.mutex.Unlock() + closest := t.tab.findnodeByID(target, bucketSize, true).entries // Send neighbors in chunks with at most maxNeighbors per packet // to stay below the packet size limit. - p := neighborsV4{ - Expiration: uint64(time.Now().Add(expiration).Unix()), - Rest: cRest, - } + p := v4wire.Neighbors{Expiration: uint64(time.Now().Add(expiration).Unix()), Rest: cRest} var sent bool for _, n := range closest { if netutil.CheckRelayIP(from.IP, n.IP()) == nil { p.Nodes = append(p.Nodes, nodeToRPC(n)) } - if len(p.Nodes) == maxNeighbors { - t.send(from, fromID, p_neighborsV4, &p) + if len(p.Nodes) == v4wire.MaxNeighbors { + t.send(from, fromID, &p) p.Nodes = p.Nodes[:0] sent = true } } if len(p.Nodes) > 0 || !sent { - t.send(from, fromID, p_neighborsV4, &p) + t.send(from, fromID, &p) } } -func (req *findnodeV4) name() string { return "FINDNODE/v4" } +// NEIGHBORS/v4 + +func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error { + req := h.Packet.(*v4wire.Neighbors) -func (req *neighborsV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error { - if expired(req.Expiration) { + if v4wire.Expired(req.Expiration) { return errExpired } if !reflect.DeepEqual(req.Rest, cRest) { return errData } - if !t.handleReply(fromID, from.IP, p_neighborsV4, req) { + if !t.handleReply(fromID, from.IP, h.Packet) { return errUnsolicitedReply } return nil } -func (req *neighborsV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) { +// ENRREQUEST/v4 + +func (t *UDPv4) verifyENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error { + req := h.Packet.(*v4wire.ENRRequest) + + if v4wire.Expired(req.Expiration) { + return errExpired + } + if !t.checkBond(fromID, from.IP) { + return errUnknownNode + } + return nil +} + +func (t *UDPv4) handleENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) { + t.send(from, fromID, &v4wire.ENRResponse{ + ReplyTok: mac, + Record: *t.localNode.Node().Record(), + }) } -func (req *neighborsV4) name() string { return "NEIGHBORS/v4" } +// ENRRESPONSE/v4 -func expired(ts uint64) bool { - return time.Unix(int64(ts), 0).Before(time.Now()) +func (t *UDPv4) verifyENRResponse(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error { + if !t.handleReply(fromID, from.IP, h.Packet) { + return errUnsolicitedReply + } + return nil } diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go index 721cc075a4..422ddc5ec4 100644 --- a/p2p/discover/v4_udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -21,8 +21,8 @@ import ( "crypto/ecdsa" crand "crypto/rand" "encoding/binary" - "encoding/hex" "errors" + "fmt" "io" "math/rand" "net" @@ -35,26 +35,18 @@ import ( "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/p2p/discover/v4wire" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - - "github.com/davecgh/go-spew/spew" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/rlp" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" ) -func init() { - spew.Config.DisableMethods = true -} - // shared test variables var ( 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} - 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} - testLocal = rpcEndpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6} + testTarget = v4wire.Pubkey{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1} + testRemote = v4wire.Endpoint{IP: net.ParseIP("1.1.1.1").To4(), UDP: 1, TCP: 2} + testLocalAnnounced = v4wire.Endpoint{IP: net.ParseIP("2.2.2.2").To4(), UDP: 3, TCP: 4} + testLocal = v4wire.Endpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6} ) type udpTest struct { @@ -95,19 +87,19 @@ func (test *udpTest) close() { } // handles a packet as if it had been sent to the transport. -func (test *udpTest) packetIn(wantError error, ptype byte, data packetV4) { +func (test *udpTest) packetIn(wantError error, data v4wire.Packet) { test.t.Helper() - test.packetInFrom(wantError, test.remotekey, test.remoteaddr, ptype, data) + test.packetInFrom(wantError, test.remotekey, test.remoteaddr, data) } // handles a packet as if it had been sent to the transport by the key/endpoint. -func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *net.UDPAddr, ptype byte, data packetV4) { +func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *net.UDPAddr, data v4wire.Packet) { test.t.Helper() - enc, _, err := test.udp.encode(key, ptype, data) + enc, _, err := v4wire.Encode(key, data) if err != nil { - test.t.Errorf("packet (%d) encode error: %v", ptype, err) + test.t.Errorf("%s encode error: %v", data.Name(), err) } test.sent = append(test.sent, enc) if err = test.udp.handlePacket(addr, enc); err != wantError { @@ -120,11 +112,14 @@ func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr * func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) { test.t.Helper() - dgram, ok := test.pipe.receive() - if !ok { + dgram, err := test.pipe.receive() + if err == errClosed { return true + } else if err != nil { + test.t.Error("packet receive error:", err) + return false } - p, _, hash, err := decodeV4(dgram.data) + p, _, hash, err := v4wire.Decode(dgram.data) if err != nil { test.t.Errorf("sent packet decode error: %v", err) return false @@ -143,10 +138,10 @@ func TestUDPv4_packetErrors(t *testing.T) { test := newUDPTest(t) defer test.close() - test.packetIn(errExpired, p_pingV4, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4}) - test.packetIn(errUnsolicitedReply, p_pongV4, &pongV4{ReplyTok: []byte{}, Expiration: futureExp}) - test.packetIn(errUnknownNode, p_findnodeV4, &findnodeV4{Expiration: futureExp}) - test.packetIn(errUnsolicitedReply, p_neighborsV4, &neighborsV4{Expiration: futureExp}) + test.packetIn(errExpired, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Rest: cRest}) + test.packetIn(errUnsolicitedReply, &v4wire.Pong{ReplyTok: []byte{}, Expiration: futureExp, Rest: cRest}) + test.packetIn(errUnknownNode, &v4wire.Findnode{Expiration: futureExp, Rest: cRest}) + test.packetIn(errUnsolicitedReply, &v4wire.Neighbors{Expiration: futureExp, Rest: cRest}) } func TestUDPv4_pingTimeout(t *testing.T) { @@ -157,11 +152,16 @@ func TestUDPv4_pingTimeout(t *testing.T) { key := newkey() toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} node := enode.NewV4(&key.PublicKey, toaddr.IP, 0, toaddr.Port) - if err := test.udp.ping(node); err != errTimeout { + if _, err := test.udp.ping(node); err != errTimeout { t.Error("expected timeout error, got", err) } } +type testPacket byte + +func (req testPacket) Kind() byte { return byte(req) } +func (req testPacket) Name() string { return "" } + func TestUDPv4_responseTimeouts(t *testing.T) { t.Parallel() test := newUDPTest(t) @@ -185,7 +185,7 @@ func TestUDPv4_responseTimeouts(t *testing.T) { // within the timeout window. p := &replyMatcher{ ptype: byte(rand.Intn(255)), - callback: func(interface{}) (bool, bool) { return true, true }, + callback: func(v4wire.Packet) (bool, bool) { return true, true }, } binary.BigEndian.PutUint64(p.from[:], uint64(i)) if p.ptype <= 128 { @@ -196,7 +196,7 @@ func TestUDPv4_responseTimeouts(t *testing.T) { p.errc = nilErr test.udp.addReplyMatcher <- p time.AfterFunc(randomDuration(60*time.Millisecond), func() { - if !test.udp.handleReply(p.from, p.ip, p.ptype, nil) { + if !test.udp.handleReply(p.from, p.ip, testPacket(p.ptype)) { t.Logf("not matched: %v", p) } }) @@ -241,7 +241,7 @@ func TestUDPv4_findnodeTimeout(t *testing.T) { toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} toid := enode.ID{1, 2, 3, 4} - target := encPubkey{4, 5, 6, 7} + target := v4wire.Pubkey{4, 5, 6, 7} result, err := test.udp.findnode(toid, toaddr, target) if err != errTimeout { t.Error("expected timeout error, got", err) @@ -258,7 +258,7 @@ func TestUDPv4_findnode(t *testing.T) { // put a few nodes into the table. their exact // distribution shouldn't matter much, although we need to // take care not to overflow any bucket. - nodes := &nodesByDistance{target: testTarget.id()} + nodes := &nodesByDistance{target: testTarget.ID()} live := make(map[enode.ID]bool) numCandidates := 2 * bucketSize for i := 0; i < numCandidates; i++ { @@ -276,32 +276,32 @@ func TestUDPv4_findnode(t *testing.T) { // ensure there's a bond with the test node, // findnode won't be accepted otherwise. - remoteID := encodePubkey(&test.remotekey.PublicKey).id() + remoteID := v4wire.EncodePubkey(&test.remotekey.PublicKey).ID() test.table.db.UpdateLastPongReceived(remoteID, test.remoteaddr.IP, time.Now()) // check that closest neighbors are returned. - expected := test.table.closest(testTarget.id(), bucketSize, true) - test.packetIn(nil, p_findnodeV4, &findnodeV4{Target: testTarget, Expiration: futureExp}) + expected := test.table.findnodeByID(testTarget.ID(), bucketSize, true) + test.packetIn(nil, &v4wire.Findnode{Target: testTarget, Expiration: futureExp, Rest: cRest}) waitNeighbors := func(want []*node) { - test.waitPacketOut(func(p *neighborsV4, to *net.UDPAddr, hash []byte) { + test.waitPacketOut(func(p *v4wire.Neighbors, to *net.UDPAddr, hash []byte) { if len(p.Nodes) != len(want) { t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSize) } for i, n := range p.Nodes { - if n.ID.id() != want[i].ID() { + if n.ID.ID() != want[i].ID() { t.Errorf("result mismatch at %d:\n got: %v\n want: %v", i, n, expected.entries[i]) } - if !live[n.ID.id()] { - t.Errorf("result includes dead node %v", n.ID.id()) + if !live[n.ID.ID()] { + t.Errorf("result includes dead node %v", n.ID.ID()) } } }) } // Receive replies. want := expected.entries - if len(want) > maxNeighbors { - waitNeighbors(want[:maxNeighbors]) - want = want[maxNeighbors:] + if len(want) > v4wire.MaxNeighbors { + waitNeighbors(want[:v4wire.MaxNeighbors]) + want = want[v4wire.MaxNeighbors:] } waitNeighbors(want) } @@ -327,7 +327,7 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) { // wait for the findnode to be sent. // after it is sent, the transport is waiting for a reply - test.waitPacketOut(func(p *findnodeV4, to *net.UDPAddr, hash []byte) { + test.waitPacketOut(func(p *v4wire.Findnode, to *net.UDPAddr, hash []byte) { if p.Target != testTarget { t.Errorf("wrong target: got %v, want %v", p.Target, testTarget) } @@ -335,17 +335,17 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) { // send the reply as two packets. list := []*node{ - wrapNode(enode.MustParseV4("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304")), - wrapNode(enode.MustParseV4("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303")), - wrapNode(enode.MustParseV4("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17")), - wrapNode(enode.MustParseV4("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303")), + wrapNode(enode.MustParse("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304")), + wrapNode(enode.MustParse("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303")), + wrapNode(enode.MustParse("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17")), + wrapNode(enode.MustParse("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303")), } - rpclist := make([]rpcNode, len(list)) + rpclist := make([]v4wire.Node, len(list)) for i := range list { rpclist[i] = nodeToRPC(list[i]) } - test.packetIn(nil, p_neighborsV4, &neighborsV4{Expiration: futureExp, Nodes: rpclist[:2], Rest: cRest}) - test.packetIn(nil, p_neighborsV4, &neighborsV4{Expiration: futureExp, Nodes: rpclist[2:], Rest: cRest}) + test.packetIn(nil, &v4wire.Neighbors{Expiration: futureExp, Nodes: rpclist[:2], Rest: cRest}) + test.packetIn(nil, &v4wire.Neighbors{Expiration: futureExp, Nodes: rpclist[2:], Rest: cRest}) // check that the sent neighbors are all returned by findnode select { @@ -361,6 +361,7 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) { } } +// This test checks that reply matching of pong verifies the ping hash. func TestUDPv4_pingMatch(t *testing.T) { test := newUDPTest(t) defer test.close() @@ -368,25 +369,27 @@ func TestUDPv4_pingMatch(t *testing.T) { randToken := make([]byte, 32) crand.Read(randToken) - test.packetIn(nil, p_pingV4, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) - test.waitPacketOut(func(*pongV4, *net.UDPAddr, []byte) {}) - test.waitPacketOut(func(*pingV4, *net.UDPAddr, []byte) {}) - test.packetIn(errUnsolicitedReply, p_pongV4, &pongV4{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp}) + test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp, Rest: cRest}) + test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {}) + test.waitPacketOut(func(*v4wire.Ping, *net.UDPAddr, []byte) {}) + test.packetIn(errUnsolicitedReply, &v4wire.Pong{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp, Rest: cRest}) } +// This test checks that reply matching of pong verifies the sender IP address. func TestUDPv4_pingMatchIP(t *testing.T) { test := newUDPTest(t) defer test.close() - test.packetIn(nil, p_pingV4, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) - test.waitPacketOut(func(*pongV4, *net.UDPAddr, []byte) {}) + test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp, Rest: cRest}) + test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {}) - test.waitPacketOut(func(p *pingV4, to *net.UDPAddr, hash []byte) { + test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) { wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 1, 2}, Port: 30000} - test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, p_pongV4, &pongV4{ + test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, &v4wire.Pong{ ReplyTok: hash, To: testLocalAnnounced, Expiration: futureExp, + Rest: cRest, }) }) } @@ -398,15 +401,15 @@ func TestUDPv4_successfulPing(t *testing.T) { defer test.close() // The remote side sends a ping packet to initiate the exchange. - go test.packetIn(nil, p_pingV4, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp, Rest: cRest}) + go test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp, Rest: cRest}) - // the ping is replied to. - test.waitPacketOut(func(p *pongV4, to *net.UDPAddr, hash []byte) { - pinghash := test.sent[0][:macSize] + // The ping is replied to. + test.waitPacketOut(func(p *v4wire.Pong, to *net.UDPAddr, hash []byte) { + pinghash := test.sent[0][:32] if !bytes.Equal(p.ReplyTok, pinghash) { t.Errorf("got pong.ReplyTok %x, want %x", p.ReplyTok, pinghash) } - wantTo := rpcEndpoint{ + wantTo := v4wire.Endpoint{ // The mirrored UDP address is the UDP packet sender IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port), // The mirrored TCP port is the one from the ping packet @@ -417,12 +420,12 @@ func TestUDPv4_successfulPing(t *testing.T) { } }) - // remote is unknown, the table pings back. - test.waitPacketOut(func(p *pingV4, to *net.UDPAddr, hash []byte) { + // Remote is unknown, the table pings back. + test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) { if !reflect.DeepEqual(p.From, test.udp.ourEndpoint()) { t.Errorf("got ping.From %#v, want %#v", p.From, test.udp.ourEndpoint()) } - wantTo := rpcEndpoint{ + wantTo := v4wire.Endpoint{ // The mirrored UDP address is the UDP packet sender. IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port), @@ -431,10 +434,10 @@ func TestUDPv4_successfulPing(t *testing.T) { if !reflect.DeepEqual(p.To, wantTo) { t.Errorf("got ping.To %v, want %v", p.To, wantTo) } - test.packetIn(nil, p_pongV4, &pongV4{ReplyTok: hash, Expiration: futureExp, Rest: cRest}) + test.packetIn(nil, &v4wire.Pong{ReplyTok: hash, Expiration: futureExp, Rest: cRest}) }) - // the node should be added to the table shortly after getting the + // The node should be added to the table shortly after getting the // pong packet. select { case n := <-added: @@ -445,7 +448,7 @@ func TestUDPv4_successfulPing(t *testing.T) { if !n.IP().Equal(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 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) { @@ -456,113 +459,127 @@ func TestUDPv4_successfulPing(t *testing.T) { } } -var testPackets = []struct { - input string - wantPacket interface{} -}{ - { - input: "71dbda3a79554728d4f94411e42ee1f8b0d561c10e1e5f5893367948c6a7d70bb87b235fa28a77070271b6c164a2dce8c7e13a5739b53b5e96f2e5acb0e458a02902f5965d55ecbeb2ebb6cabb8b2b232896a36b737666c55265ad0a68412f250001ea04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a355", - wantPacket: &pingV4{ - Version: 4, - From: rpcEndpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, - To: rpcEndpoint{net.ParseIP("::1"), 2222, 3333}, - Expiration: 1136239445, - Rest: []rlp.RawValue{}, - }, - }, - { - input: "e9614ccfd9fc3e74360018522d30e1419a143407ffcce748de3e22116b7e8dc92ff74788c0b6663aaa3d67d641936511c8f8d6ad8698b820a7cf9e1be7155e9a241f556658c55428ec0563514365799a4be2be5a685a80971ddcfa80cb422cdd0101ec04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a3550102", - wantPacket: &pingV4{ - Version: 4, - From: rpcEndpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, - To: rpcEndpoint{net.ParseIP("::1"), 2222, 3333}, - Expiration: 1136239445, - Rest: []rlp.RawValue{{0x01}, {0x02}}, - }, - }, - { - input: "577be4349c4dd26768081f58de4c6f375a7a22f3f7adda654d1428637412c3d7fe917cadc56d4e5e7ffae1dbe3efffb9849feb71b262de37977e7c7a44e677295680e9e38ab26bee2fcbae207fba3ff3d74069a50b902a82c9903ed37cc993c50001f83e82022bd79020010db83c4d001500000000abcdef12820cfa8215a8d79020010db885a308d313198a2e037073488208ae82823a8443b9a355c5010203040531b9019afde696e582a78fa8d95ea13ce3297d4afb8ba6433e4154caa5ac6431af1b80ba76023fa4090c408f6b4bc3701562c031041d4702971d102c9ab7fa5eed4cd6bab8f7af956f7d565ee1917084a95398b6a21eac920fe3dd1345ec0a7ef39367ee69ddf092cbfe5b93e5e568ebc491983c09c76d922dc3", - wantPacket: &pingV4{ - Version: 555, - From: rpcEndpoint{net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 3322, 5544}, - To: rpcEndpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338}, - Expiration: 1136239445, - Rest: []rlp.RawValue{{0xC5, 0x01, 0x02, 0x03, 0x04, 0x05}}, - }, - }, - { - input: "09b2428d83348d27cdf7064ad9024f526cebc19e4958f0fdad87c15eb598dd61d08423e0bf66b2069869e1724125f820d851c136684082774f870e614d95a2855d000f05d1648b2d5945470bc187c2d2216fbe870f43ed0909009882e176a46b0102f846d79020010db885a308d313198a2e037073488208ae82823aa0fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c9548443b9a355c6010203c2040506a0c969a58f6f9095004c0177a6b47f451530cab38966a25cca5cb58f055542124e", - wantPacket: &pongV4{ - To: rpcEndpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338}, - ReplyTok: common.Hex2Bytes("fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c954"), - Expiration: 1136239445, - Rest: []rlp.RawValue{{0xC6, 0x01, 0x02, 0x03, 0xC2, 0x04, 0x05}, {0x06}}, - }, - }, - { - input: "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260add7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396", - wantPacket: &findnodeV4{ - Target: hexEncPubkey("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"), - Expiration: 1136239445, - Rest: []rlp.RawValue{{0x82, 0x99, 0x99}, {0x83, 0x99, 0x99, 0x99}}, - }, - }, - { - input: "c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db8403155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d313198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df738443b9a355010203b525a138aa34383fec3d2719a0", - wantPacket: &neighborsV4{ - Nodes: []rpcNode{ - { - ID: hexEncPubkey("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"), - IP: net.ParseIP("99.33.22.55").To4(), - UDP: 4444, - TCP: 4445, - }, - { - ID: hexEncPubkey("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"), - IP: net.ParseIP("1.2.3.4").To4(), - UDP: 1, - TCP: 1, - }, - { - ID: hexEncPubkey("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"), - IP: net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), - UDP: 3333, - TCP: 3333, - }, - { - ID: hexEncPubkey("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"), - IP: net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), - UDP: 999, - TCP: 1000, - }, - }, - Expiration: 1136239445, - Rest: []rlp.RawValue{{0x01}, {0x02}, {0x03}}, - }, - }, -} +// This test checks that EIP-868 requests work. +func TestUDPv4_EIP868(t *testing.T) { + test := newUDPTest(t) + defer test.close() -func TestUDPv4_forwardCompatibility(t *testing.T) { - testkey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - wantNodeKey := encodePubkey(&testkey.PublicKey) + test.udp.localNode.Set(enr.WithEntry("foo", "bar")) + wantNode := test.udp.localNode.Node() - for _, test := range testPackets { - input, err := hex.DecodeString(test.input) - if err != nil { - t.Fatalf("invalid hex: %s", test.input) + // ENR requests aren't allowed before endpoint proof. + test.packetIn(errUnknownNode, &v4wire.ENRRequest{Expiration: futureExp, Rest: cRest}) + + // Perform endpoint proof and check for sequence number in packet tail. + test.packetIn(nil, &v4wire.Ping{Expiration: futureExp, Rest: cRest}) + test.waitPacketOut(func(p *v4wire.Pong, addr *net.UDPAddr, hash []byte) { + if p.ENRSeq != wantNode.Seq() { + t.Errorf("wrong sequence number in pong: %d, want %d", p.ENRSeq, wantNode.Seq()) } - packet, nodekey, _, err := decodeV4(input) + }) + test.waitPacketOut(func(p *v4wire.Ping, addr *net.UDPAddr, hash []byte) { + if p.ENRSeq != wantNode.Seq() { + t.Errorf("wrong sequence number in ping: %d, want %d", p.ENRSeq, wantNode.Seq()) + } + test.packetIn(nil, &v4wire.Pong{Expiration: futureExp, ReplyTok: hash, Rest: cRest}) + }) + + // Request should work now. + test.packetIn(nil, &v4wire.ENRRequest{Expiration: futureExp}) + test.waitPacketOut(func(p *v4wire.ENRResponse, addr *net.UDPAddr, hash []byte) { + n, err := enode.New(enode.ValidSchemes, &p.Record) if err != nil { - t.Errorf("did not accept packet %s\n%v", test.input, err) - continue + t.Fatalf("invalid record: %v", err) } - if !reflect.DeepEqual(packet, test.wantPacket) { - t.Errorf("got %s\nwant %s", spew.Sdump(packet), spew.Sdump(test.wantPacket)) + if !reflect.DeepEqual(n, wantNode) { + t.Fatalf("wrong node in enrResponse: %v", n) } - if nodekey != wantNodeKey { - t.Errorf("got id %v\nwant id %v", nodekey, wantNodeKey) + }) +} + +// This test verifies that a small network of nodes can boot up into a healthy state. +func TestUDPv4_smallNetConvergence(t *testing.T) { + t.Parallel() + + // Start the network. + nodes := make([]*UDPv4, 4) + for i := range nodes { + var cfg Config + if i > 0 { + bn := nodes[0].Self() + cfg.Bootnodes = []*enode.Node{bn} } + nodes[i] = startLocalhostV4(t, cfg) + defer nodes[i].Close() } + + // Run through the iterator on all nodes until + // they have all found each other. + status := make(chan error, len(nodes)) + for i := range nodes { + node := nodes[i] + go func() { + found := make(map[enode.ID]bool, len(nodes)) + it := node.RandomNodes() + for it.Next() { + found[it.Node().ID()] = true + if len(found) == len(nodes) { + status <- nil + return + } + } + status <- fmt.Errorf("node %s didn't find all nodes", node.Self().ID().TerminalString()) + }() + } + + // Wait for all status reports. + timeout := time.NewTimer(30 * time.Second) + defer timeout.Stop() + for received := 0; received < len(nodes); { + select { + case <-timeout.C: + for _, node := range nodes { + node.Close() + } + case err := <-status: + received++ + if err != nil { + t.Error("ERROR:", err) + return + } + } + } +} + +func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 { + t.Helper() + + cfg.PrivateKey = newkey() + db, _ := enode.OpenDB("") + ln := enode.NewLocalNode(db, cfg.PrivateKey) + + // Prefix logs with node ID. + lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString()) + lfmt := log.TerminalFormat(false) + cfg.Log = testlog.Logger(t, log.LvlTrace) + cfg.Log.SetHandler(log.FuncHandler(func(r *log.Record) error { + t.Logf("%s %s", lprefix, lfmt.Format(r)) + return nil + })) + + // Listen. + socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}}) + if err != nil { + t.Fatal(err) + } + realaddr := socket.LocalAddr().(*net.UDPAddr) + ln.SetStaticIP(realaddr.IP) + ln.SetFallbackUDP(realaddr.Port) + udp, err := ListenV4(socket, ln, cfg) + if err != nil { + t.Fatal(err) + } + return udp } // dgramPipe is a fake UDP socket. It queues all sent datagrams. @@ -623,17 +640,30 @@ func (c *dgramPipe) LocalAddr() net.Addr { return &net.UDPAddr{IP: testLocal.IP, Port: int(testLocal.UDP)} } -func (c *dgramPipe) receive() (dgram, bool) { +func (c *dgramPipe) receive() (dgram, error) { c.mu.Lock() defer c.mu.Unlock() - for len(c.queue) == 0 && !c.closed { + + var timedOut bool + timer := time.AfterFunc(3*time.Second, func() { + c.mu.Lock() + timedOut = true + c.mu.Unlock() + c.cond.Broadcast() + }) + defer timer.Stop() + + for len(c.queue) == 0 && !c.closed && !timedOut { c.cond.Wait() } if c.closed { - return dgram{}, false + return dgram{}, errClosed + } + if timedOut { + return dgram{}, errTimeout } p := c.queue[0] copy(c.queue, c.queue[1:]) c.queue = c.queue[:len(c.queue)-1] - return p, true + return p, nil } diff --git a/p2p/discover/v4wire/v4wire.go b/p2p/discover/v4wire/v4wire.go new file mode 100644 index 0000000000..4ff65f5dc5 --- /dev/null +++ b/p2p/discover/v4wire/v4wire.go @@ -0,0 +1,293 @@ +// 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 . + +// Package v4wire implements the Discovery v4 Wire Protocol. +package v4wire + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "errors" + "fmt" + "math/big" + "net" + "time" + + "github.com/AlayaNetwork/Alaya-Go/common/math" + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/rlp" +) + +// RPC packet types +const ( + PingPacket = iota + 1 // zero is 'reserved' + PongPacket + FindnodePacket + NeighborsPacket + ENRRequestPacket + ENRResponsePacket +) + +// RPC request structures +type ( + Ping struct { + Version uint + From, To Endpoint + Expiration uint64 + ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. + + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` + } + + // Pong is the reply to ping. + Pong struct { + // This field should mirror the UDP envelope address + // of the ping packet, which provides a way to discover the + // the external address (after NAT). + To Endpoint + ReplyTok []byte // This contains the hash of the ping packet. + Expiration uint64 // Absolute timestamp at which the packet becomes invalid. + ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. + + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` + } + + // Findnode is a query for nodes close to the given target. + Findnode struct { + Target Pubkey + Expiration uint64 + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` + } + + // Neighbors is the reply to findnode. + Neighbors struct { + Nodes []Node + Expiration uint64 + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` + } + + // enrRequest queries for the remote node's record. + ENRRequest struct { + Expiration uint64 + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` + } + + // enrResponse is the reply to enrRequest. + ENRResponse struct { + ReplyTok []byte // Hash of the enrRequest packet. + Record enr.Record + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` + } +) + +// This number is the maximum number of neighbor nodes in a Neigbors packet. +const MaxNeighbors = 12 + +// This code computes the MaxNeighbors constant value. + +// func init() { +// var maxNeighbors int +// p := Neighbors{Expiration: ^uint64(0)} +// maxSizeNode := Node{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)} +// for n := 0; ; n++ { +// p.Nodes = append(p.Nodes, maxSizeNode) +// size, _, err := rlp.EncodeToReader(p) +// if err != nil { +// // If this ever happens, it will be caught by the unit tests. +// panic("cannot encode: " + err.Error()) +// } +// if headSize+size+1 >= 1280 { +// maxNeighbors = n +// break +// } +// } +// fmt.Println("maxNeighbors", maxNeighbors) +// } + +// Pubkey represents an encoded 64-byte secp256k1 public key. +type Pubkey [64]byte + +// ID returns the node ID corresponding to the public key. +func (e Pubkey) ID() enode.ID { + return enode.ID(crypto.Keccak256Hash(e[:])) +} + +// Node represents information about a node. +type Node struct { + IP net.IP // len 4 for IPv4 or 16 for IPv6 + UDP uint16 // for discovery protocol + TCP uint16 // for RLPx protocol + ID Pubkey +} + +// Endpoint represents a network endpoint. +type Endpoint struct { + IP net.IP // len 4 for IPv4 or 16 for IPv6 + UDP uint16 // for discovery protocol + TCP uint16 // for RLPx protocol +} + +// NewEndpoint creates an endpoint. +func NewEndpoint(addr *net.UDPAddr, tcpPort uint16) Endpoint { + ip := net.IP{} + if ip4 := addr.IP.To4(); ip4 != nil { + ip = ip4 + } else if ip6 := addr.IP.To16(); ip6 != nil { + ip = ip6 + } + return Endpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort} +} + +type Packet interface { + // packet name and type for logging purposes. + Name() string + Kind() byte +} + +func (req *Ping) Name() string { return "PING/v4" } +func (req *Ping) Kind() byte { return PingPacket } + +func (req *Pong) Name() string { return "PONG/v4" } +func (req *Pong) Kind() byte { return PongPacket } + +func (req *Findnode) Name() string { return "FINDNODE/v4" } +func (req *Findnode) Kind() byte { return FindnodePacket } + +func (req *Neighbors) Name() string { return "NEIGHBORS/v4" } +func (req *Neighbors) Kind() byte { return NeighborsPacket } + +func (req *ENRRequest) Name() string { return "ENRREQUEST/v4" } +func (req *ENRRequest) Kind() byte { return ENRRequestPacket } + +func (req *ENRResponse) Name() string { return "ENRRESPONSE/v4" } +func (req *ENRResponse) Kind() byte { return ENRResponsePacket } + +// Expired checks whether the given UNIX time stamp is in the past. +func Expired(ts uint64) bool { + return time.Unix(int64(ts), 0).Before(time.Now()) +} + +// Encoder/decoder. + +const ( + macSize = 32 + sigSize = crypto.SignatureLength + headSize = macSize + sigSize // space of packet frame data +) + +var ( + ErrPacketTooSmall = errors.New("too small") + ErrBadHash = errors.New("bad hash") + ErrBadPoint = errors.New("invalid curve point") +) + +var headSpace = make([]byte, headSize) + +// Decode reads a discovery v4 packet. +func Decode(input []byte) (Packet, Pubkey, []byte, error) { + if len(input) < headSize+1 { + return nil, Pubkey{}, nil, ErrPacketTooSmall + } + hash, sig, sigdata := input[:macSize], input[macSize:headSize], input[headSize:] + shouldhash := crypto.Keccak256(input[macSize:]) + if !bytes.Equal(hash, shouldhash) { + return nil, Pubkey{}, nil, ErrBadHash + } + fromKey, err := recoverNodeKey(crypto.Keccak256(input[headSize:]), sig) + if err != nil { + return nil, fromKey, hash, err + } + + var req Packet + switch ptype := sigdata[0]; ptype { + case PingPacket: + req = new(Ping) + case PongPacket: + req = new(Pong) + case FindnodePacket: + req = new(Findnode) + case NeighborsPacket: + req = new(Neighbors) + case ENRRequestPacket: + req = new(ENRRequest) + case ENRResponsePacket: + req = new(ENRResponse) + default: + return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype) + } + s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0) + err = s.Decode(req) + return req, fromKey, hash, err +} + +// Encode encodes a discovery packet. +func Encode(priv *ecdsa.PrivateKey, req Packet) (packet, hash []byte, err error) { + b := new(bytes.Buffer) + b.Write(headSpace) + b.WriteByte(req.Kind()) + if err := rlp.Encode(b, req); err != nil { + return nil, nil, err + } + packet = b.Bytes() + sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv) + if err != nil { + return nil, nil, err + } + copy(packet[macSize:], sig) + // Add the hash to the front. Note: this doesn't protect the packet in any way. + hash = crypto.Keccak256(packet[macSize:]) + copy(packet, hash) + return packet, hash, nil +} + +// recoverNodeKey computes the public key used to sign the given hash from the signature. +func recoverNodeKey(hash, sig []byte) (key Pubkey, err error) { + pubkey, err := crypto.Ecrecover(hash, sig) + if err != nil { + return key, err + } + copy(key[:], pubkey[1:]) + return key, nil +} + +// EncodePubkey encodes a secp256k1 public key. +func EncodePubkey(key *ecdsa.PublicKey) Pubkey { + var e Pubkey + math.ReadBits(key.X, e[:len(e)/2]) + math.ReadBits(key.Y, e[len(e)/2:]) + return e +} + +// DecodePubkey reads an encoded secp256k1 public key. +func DecodePubkey(curve elliptic.Curve, e Pubkey) (*ecdsa.PublicKey, error) { + p := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)} + half := len(e) / 2 + p.X.SetBytes(e[:half]) + p.Y.SetBytes(e[half:]) + if !p.Curve.IsOnCurve(p.X, p.Y) { + return nil, ErrBadPoint + } + return p, nil +} diff --git a/p2p/discover/v4wire/v4wire_test.go b/p2p/discover/v4wire/v4wire_test.go new file mode 100644 index 0000000000..228c12995e --- /dev/null +++ b/p2p/discover/v4wire/v4wire_test.go @@ -0,0 +1,133 @@ +// 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 . + +package v4wire + +import ( + "encoding/hex" + "net" + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" + + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/rlp" +) + +// EIP-8 test vectors. +var testPackets = []struct { + input string + wantPacket interface{} +}{ + { + input: "71dbda3a79554728d4f94411e42ee1f8b0d561c10e1e5f5893367948c6a7d70bb87b235fa28a77070271b6c164a2dce8c7e13a5739b53b5e96f2e5acb0e458a02902f5965d55ecbeb2ebb6cabb8b2b232896a36b737666c55265ad0a68412f250001ea04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a355", + wantPacket: &Ping{ + Version: 4, + From: Endpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, + To: Endpoint{net.ParseIP("::1"), 2222, 3333}, + Expiration: 1136239445, + }, + }, + { + input: "e9614ccfd9fc3e74360018522d30e1419a143407ffcce748de3e22116b7e8dc92ff74788c0b6663aaa3d67d641936511c8f8d6ad8698b820a7cf9e1be7155e9a241f556658c55428ec0563514365799a4be2be5a685a80971ddcfa80cb422cdd0101ec04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a3550102", + wantPacket: &Ping{ + Version: 4, + From: Endpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, + To: Endpoint{net.ParseIP("::1"), 2222, 3333}, + Expiration: 1136239445, + ENRSeq: 1, + Rest: []rlp.RawValue{{0x02}}, + }, + }, + { + input: "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260add7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396", + wantPacket: &Findnode{ + Target: hexPubkey("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"), + Expiration: 1136239445, + Rest: []rlp.RawValue{{0x82, 0x99, 0x99}, {0x83, 0x99, 0x99, 0x99}}, + }, + }, + { + input: "c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db8403155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d313198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df738443b9a355010203b525a138aa34383fec3d2719a0", + wantPacket: &Neighbors{ + Nodes: []Node{ + { + ID: hexPubkey("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"), + IP: net.ParseIP("99.33.22.55").To4(), + UDP: 4444, + TCP: 4445, + }, + { + ID: hexPubkey("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"), + IP: net.ParseIP("1.2.3.4").To4(), + UDP: 1, + TCP: 1, + }, + { + ID: hexPubkey("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"), + IP: net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), + UDP: 3333, + TCP: 3333, + }, + { + ID: hexPubkey("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"), + IP: net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), + UDP: 999, + TCP: 1000, + }, + }, + Expiration: 1136239445, + Rest: []rlp.RawValue{{0x01}, {0x02}, {0x03}}, + }, + }, +} + +// This test checks that the decoder accepts packets according to EIP-8. +func TestForwardCompatibility(t *testing.T) { + testkey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + wantNodeKey := EncodePubkey(&testkey.PublicKey) + + for _, test := range testPackets { + input, err := hex.DecodeString(test.input) + if err != nil { + t.Fatalf("invalid hex: %s", test.input) + } + packet, nodekey, _, err := Decode(input) + if err != nil { + t.Errorf("did not accept packet %s\n%v", test.input, err) + continue + } + if !reflect.DeepEqual(packet, test.wantPacket) { + t.Errorf("got %s\nwant %s", spew.Sdump(packet), spew.Sdump(test.wantPacket)) + } + if nodekey != wantNodeKey { + t.Errorf("got id %v\nwant id %v", nodekey, wantNodeKey) + } + } +} + +func hexPubkey(h string) (ret Pubkey) { + b, err := hex.DecodeString(h) + if err != nil { + panic(err) + } + if len(b) != len(ret) { + panic("invalid length") + } + copy(ret[:], b) + return ret +} diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go new file mode 100644 index 0000000000..37cd81e182 --- /dev/null +++ b/p2p/discover/v5_udp.go @@ -0,0 +1,859 @@ +// 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 . + +package discover + +import ( + "bytes" + "context" + "crypto/ecdsa" + crand "crypto/rand" + "errors" + "fmt" + "io" + "math" + "net" + "sync" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/discover/v5wire" + + "github.com/AlayaNetwork/Alaya-Go/common/mclock" + "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" +) + +const ( + lookupRequestLimit = 3 // max requests against a single node during lookup + findnodeResultLimit = 16 // applies in FINDNODE handler + totalNodesResponseLimit = 5 // applies in waitForNodes + nodesResponseItemLimit = 3 // applies in sendNodes + + respTimeoutV5 = 700 * time.Millisecond +) + +// codecV5 is implemented by v5wire.Codec (and testCodec). +// +// The UDPv5 transport is split into two objects: the codec object deals with +// encoding/decoding and with the handshake; the UDPv5 object handles higher-level concerns. +type codecV5 interface { + // Encode encodes a packet. + Encode(enode.ID, string, v5wire.Packet, *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) + + // decode decodes a packet. It returns a *v5wire.Unknown packet if decryption fails. + // The *enode.Node return value is non-nil when the input contains a handshake response. + Decode([]byte, string) (enode.ID, *enode.Node, v5wire.Packet, error) +} + +// UDPv5 is the implementation of protocol version 5. +type UDPv5 struct { + // static fields + conn UDPConn + tab *Table + netrestrict *netutil.Netlist + priv *ecdsa.PrivateKey + localNode *enode.LocalNode + db *enode.DB + log log.Logger + clock mclock.Clock + validSchemes enr.IdentityScheme + + // talkreq handler registry + trlock sync.Mutex + trhandlers map[string]TalkRequestHandler + + // channels into dispatch + packetInCh chan ReadPacket + readNextCh chan struct{} + callCh chan *callV5 + callDoneCh chan *callV5 + respTimeoutCh chan *callTimeout + + // state of dispatch + codec codecV5 + activeCallByNode map[enode.ID]*callV5 + activeCallByAuth map[v5wire.Nonce]*callV5 + callQueue map[enode.ID][]*callV5 + + // shutdown stuff + closeOnce sync.Once + closeCtx context.Context + cancelCloseCtx context.CancelFunc + wg sync.WaitGroup +} + +// TalkRequestHandler callback processes a talk request and optionally returns a reply +type TalkRequestHandler func(enode.ID, *net.UDPAddr, []byte) []byte + +// callV5 represents a remote procedure call against another node. +type callV5 struct { + node *enode.Node + packet v5wire.Packet + responseType byte // expected packet type of response + reqid []byte + ch chan v5wire.Packet // responses sent here + err chan error // errors sent here + + // Valid for active calls only: + nonce v5wire.Nonce // nonce of request packet + handshakeCount int // # times we attempted handshake for this call + challenge *v5wire.Whoareyou // last sent handshake challenge + timeout mclock.Timer +} + +// callTimeout is the response timeout event of a call. +type callTimeout struct { + c *callV5 + timer mclock.Timer +} + +// ListenV5 listens on the given connection. +func ListenV5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) { + t, err := newUDPv5(conn, ln, cfg) + if err != nil { + return nil, err + } + go t.tab.loop() + t.wg.Add(2) + go t.readLoop() + go t.dispatch() + return t, nil +} + +// newUDPv5 creates a UDPv5 transport, but doesn't start any goroutines. +func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) { + closeCtx, cancelCloseCtx := context.WithCancel(context.Background()) + cfg = cfg.withDefaults() + t := &UDPv5{ + // static fields + conn: conn, + localNode: ln, + db: ln.Database(), + netrestrict: cfg.NetRestrict, + priv: cfg.PrivateKey, + log: cfg.Log, + validSchemes: cfg.ValidSchemes, + clock: cfg.Clock, + trhandlers: make(map[string]TalkRequestHandler), + // channels into dispatch + packetInCh: make(chan ReadPacket, 1), + readNextCh: make(chan struct{}, 1), + callCh: make(chan *callV5), + callDoneCh: make(chan *callV5), + respTimeoutCh: make(chan *callTimeout), + // state of dispatch + codec: v5wire.NewCodec(ln, cfg.PrivateKey, cfg.Clock), + activeCallByNode: make(map[enode.ID]*callV5), + activeCallByAuth: make(map[v5wire.Nonce]*callV5), + callQueue: make(map[enode.ID][]*callV5), + // shutdown + closeCtx: closeCtx, + cancelCloseCtx: cancelCloseCtx, + } + tab, err := newTable(t, t.db, cfg.Bootnodes, cfg.Log) + if err != nil { + return nil, err + } + t.tab = tab + return t, nil +} + +// Self returns the local node record. +func (t *UDPv5) Self() *enode.Node { + return t.localNode.Node() +} + +// Close shuts down packet processing. +func (t *UDPv5) Close() { + t.closeOnce.Do(func() { + t.cancelCloseCtx() + t.conn.Close() + t.wg.Wait() + t.tab.close() + }) +} + +// Ping sends a ping message to the given node. +func (t *UDPv5) Ping(n *enode.Node) error { + _, err := t.ping(n) + return err +} + +// Resolve searches for a specific node with the given ID and tries to get the most recent +// version of the node record for it. It returns n if the node could not be resolved. +func (t *UDPv5) Resolve(n *enode.Node) *enode.Node { + if intable := t.tab.getNode(n.ID()); intable != nil && intable.Seq() > n.Seq() { + n = intable + } + // Try asking directly. This works if the node is still responding on the endpoint we have. + if resp, err := t.RequestENR(n); err == nil { + return resp + } + // Otherwise do a network lookup. + result := t.Lookup(n.ID()) + for _, rn := range result { + if rn.ID() == n.ID() && rn.Seq() > n.Seq() { + return rn + } + } + return n +} + +// AllNodes returns all the nodes stored in the local table. +func (t *UDPv5) AllNodes() []*enode.Node { + t.tab.mutex.Lock() + defer t.tab.mutex.Unlock() + nodes := make([]*enode.Node, 0) + + for _, b := range &t.tab.buckets { + for _, n := range b.entries { + nodes = append(nodes, unwrapNode(n)) + } + } + return nodes +} + +// LocalNode returns the current local node running the +// protocol. +func (t *UDPv5) LocalNode() *enode.LocalNode { + return t.localNode +} + +// RegisterTalkHandler adds a handler for 'talk requests'. The handler function is called +// whenever a request for the given protocol is received and should return the response +// data or nil. +func (t *UDPv5) RegisterTalkHandler(protocol string, handler TalkRequestHandler) { + t.trlock.Lock() + defer t.trlock.Unlock() + t.trhandlers[protocol] = handler +} + +// TalkRequest sends a talk request to n and waits for a response. +func (t *UDPv5) TalkRequest(n *enode.Node, protocol string, request []byte) ([]byte, error) { + req := &v5wire.TalkRequest{Protocol: protocol, Message: request} + resp := t.call(n, v5wire.TalkResponseMsg, req) + defer t.callDone(resp) + select { + case respMsg := <-resp.ch: + return respMsg.(*v5wire.TalkResponse).Message, nil + case err := <-resp.err: + return nil, err + } +} + +// RandomNodes returns an iterator that finds random nodes in the DHT. +func (t *UDPv5) RandomNodes() enode.Iterator { + if t.tab.len() == 0 { + // All nodes were dropped, refresh. The very first query will hit this + // case and run the bootstrapping logic. + <-t.tab.refresh() + } + + return newLookupIterator(t.closeCtx, t.newRandomLookup) +} + +// Lookup performs a recursive lookup for the given target. +// It returns the closest nodes to target. +func (t *UDPv5) Lookup(target enode.ID) []*enode.Node { + return t.newLookup(t.closeCtx, target).run() +} + +// lookupRandom looks up a random target. +// This is needed to satisfy the transport interface. +func (t *UDPv5) lookupRandom() []*enode.Node { + return t.newRandomLookup(t.closeCtx).run() +} + +// lookupSelf looks up our own node ID. +// This is needed to satisfy the transport interface. +func (t *UDPv5) lookupSelf() []*enode.Node { + return t.newLookup(t.closeCtx, t.Self().ID()).run() +} + +func (t *UDPv5) newRandomLookup(ctx context.Context) *lookup { + var target enode.ID + crand.Read(target[:]) + return t.newLookup(ctx, target) +} + +func (t *UDPv5) newLookup(ctx context.Context, target enode.ID) *lookup { + return newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) { + return t.lookupWorker(n, target) + }) +} + +// lookupWorker performs FINDNODE calls against a single node during lookup. +func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) { + var ( + dists = lookupDistances(target, destNode.ID()) + nodes = nodesByDistance{target: target} + err error + ) + var r []*enode.Node + r, err = t.findnode(unwrapNode(destNode), dists) + if err == errClosed { + return nil, err + } + for _, n := range r { + if n.ID() != t.Self().ID() { + nodes.push(wrapNode(n), findnodeResultLimit) + } + } + return nodes.entries, err +} + +// lookupDistances computes the distance parameter for FINDNODE calls to dest. +// It chooses distances adjacent to logdist(target, dest), e.g. for a target +// with logdist(target, dest) = 255 the result is [255, 256, 254]. +func lookupDistances(target, dest enode.ID) (dists []uint) { + td := enode.LogDist(target, dest) + dists = append(dists, uint(td)) + for i := 1; len(dists) < lookupRequestLimit; i++ { + if td+i < 256 { + dists = append(dists, uint(td+i)) + } + if td-i > 0 { + dists = append(dists, uint(td-i)) + } + } + return dists +} + +// ping calls PING on a node and waits for a PONG response. +func (t *UDPv5) ping(n *enode.Node) (uint64, error) { + req := &v5wire.Ping{ENRSeq: t.localNode.Node().Seq()} + resp := t.call(n, v5wire.PongMsg, req) + defer t.callDone(resp) + + select { + case pong := <-resp.ch: + return pong.(*v5wire.Pong).ENRSeq, nil + case err := <-resp.err: + return 0, err + } +} + +// requestENR requests n's record. +func (t *UDPv5) RequestENR(n *enode.Node) (*enode.Node, error) { + nodes, err := t.findnode(n, []uint{0}) + if err != nil { + return nil, err + } + if len(nodes) != 1 { + return nil, fmt.Errorf("%d nodes in response for distance zero", len(nodes)) + } + return nodes[0], nil +} + +// findnode calls FINDNODE on a node and waits for responses. +func (t *UDPv5) findnode(n *enode.Node, distances []uint) ([]*enode.Node, error) { + resp := t.call(n, v5wire.NodesMsg, &v5wire.Findnode{Distances: distances}) + return t.waitForNodes(resp, distances) +} + +// waitForNodes waits for NODES responses to the given call. +func (t *UDPv5) waitForNodes(c *callV5, distances []uint) ([]*enode.Node, error) { + defer t.callDone(c) + + var ( + nodes []*enode.Node + seen = make(map[enode.ID]struct{}) + received, total = 0, -1 + ) + for { + select { + case responseP := <-c.ch: + response := responseP.(*v5wire.Nodes) + for _, record := range response.Nodes { + node, err := t.verifyResponseNode(c, record, distances, seen) + if err != nil { + t.log.Debug("Invalid record in "+response.Name(), "id", c.node.ID(), "err", err) + continue + } + nodes = append(nodes, node) + } + if total == -1 { + total = min(int(response.Total), totalNodesResponseLimit) + } + if received++; received == total { + return nodes, nil + } + case err := <-c.err: + return nodes, err + } + } +} + +// verifyResponseNode checks validity of a record in a NODES response. +func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, seen map[enode.ID]struct{}) (*enode.Node, error) { + node, err := enode.New(t.validSchemes, r) + if err != nil { + return nil, err + } + if err := netutil.CheckRelayIP(c.node.IP(), node.IP()); err != nil { + return nil, err + } + if c.node.UDP() <= 1024 { + return nil, errLowPort + } + if distances != nil { + nd := enode.LogDist(c.node.ID(), node.ID()) + if !containsUint(uint(nd), distances) { + return nil, errors.New("does not match any requested distance") + } + } + if _, ok := seen[node.ID()]; ok { + return nil, fmt.Errorf("duplicate record") + } + seen[node.ID()] = struct{}{} + return node, nil +} + +func containsUint(x uint, xs []uint) bool { + for _, v := range xs { + if x == v { + return true + } + } + return false +} + +// call sends the given call and sets up a handler for response packets (of message type +// responseType). Responses are dispatched to the call's response channel. +func (t *UDPv5) call(node *enode.Node, responseType byte, packet v5wire.Packet) *callV5 { + c := &callV5{ + node: node, + packet: packet, + responseType: responseType, + reqid: make([]byte, 8), + ch: make(chan v5wire.Packet, 1), + err: make(chan error, 1), + } + // Assign request ID. + crand.Read(c.reqid) + packet.SetRequestID(c.reqid) + // Send call to dispatch. + select { + case t.callCh <- c: + case <-t.closeCtx.Done(): + c.err <- errClosed + } + return c +} + +// callDone tells dispatch that the active call is done. +func (t *UDPv5) callDone(c *callV5) { + // This needs a loop because further responses may be incoming until the + // send to callDoneCh has completed. Such responses need to be discarded + // in order to avoid blocking the dispatch loop. + for { + select { + case <-c.ch: + // late response, discard. + case <-c.err: + // late error, discard. + case t.callDoneCh <- c: + return + case <-t.closeCtx.Done(): + return + } + } +} + +// dispatch runs in its own goroutine, handles incoming packets and deals with calls. +// +// For any destination node there is at most one 'active call', stored in the t.activeCall* +// maps. A call is made active when it is sent. The active call can be answered by a +// matching response, in which case c.ch receives the response; or by timing out, in which case +// c.err receives the error. When the function that created the call signals the active +// call is done through callDone, the next call from the call queue is started. +// +// Calls may also be answered by a WHOAREYOU packet referencing the call packet's authTag. +// When that happens the call is simply re-sent to complete the handshake. We allow one +// handshake attempt per call. +func (t *UDPv5) dispatch() { + defer t.wg.Done() + + // Arm first read. + t.readNextCh <- struct{}{} + + for { + select { + case c := <-t.callCh: + id := c.node.ID() + t.callQueue[id] = append(t.callQueue[id], c) + t.sendNextCall(id) + + case ct := <-t.respTimeoutCh: + active := t.activeCallByNode[ct.c.node.ID()] + if ct.c == active && ct.timer == active.timeout { + ct.c.err <- errTimeout + } + + case c := <-t.callDoneCh: + id := c.node.ID() + active := t.activeCallByNode[id] + if active != c { + panic("BUG: callDone for inactive call") + } + c.timeout.Stop() + delete(t.activeCallByAuth, c.nonce) + delete(t.activeCallByNode, id) + t.sendNextCall(id) + + case p := <-t.packetInCh: + t.handlePacket(p.Data, p.Addr) + // Arm next read. + t.readNextCh <- struct{}{} + + case <-t.closeCtx.Done(): + close(t.readNextCh) + for id, queue := range t.callQueue { + for _, c := range queue { + c.err <- errClosed + } + delete(t.callQueue, id) + } + for id, c := range t.activeCallByNode { + c.err <- errClosed + delete(t.activeCallByNode, id) + delete(t.activeCallByAuth, c.nonce) + } + return + } + } +} + +// startResponseTimeout sets the response timer for a call. +func (t *UDPv5) startResponseTimeout(c *callV5) { + if c.timeout != nil { + c.timeout.Stop() + } + var ( + timer mclock.Timer + done = make(chan struct{}) + ) + timer = t.clock.AfterFunc(respTimeoutV5, func() { + <-done + select { + case t.respTimeoutCh <- &callTimeout{c, timer}: + case <-t.closeCtx.Done(): + } + }) + c.timeout = timer + close(done) +} + +// sendNextCall sends the next call in the call queue if there is no active call. +func (t *UDPv5) sendNextCall(id enode.ID) { + queue := t.callQueue[id] + if len(queue) == 0 || t.activeCallByNode[id] != nil { + return + } + t.activeCallByNode[id] = queue[0] + t.sendCall(t.activeCallByNode[id]) + if len(queue) == 1 { + delete(t.callQueue, id) + } else { + copy(queue, queue[1:]) + t.callQueue[id] = queue[:len(queue)-1] + } +} + +// sendCall encodes and sends a request packet to the call's recipient node. +// This performs a handshake if needed. +func (t *UDPv5) sendCall(c *callV5) { + // The call might have a nonce from a previous handshake attempt. Remove the entry for + // the old nonce because we're about to generate a new nonce for this call. + if c.nonce != (v5wire.Nonce{}) { + delete(t.activeCallByAuth, c.nonce) + } + + addr := &net.UDPAddr{IP: c.node.IP(), Port: c.node.UDP()} + newNonce, _ := t.send(c.node.ID(), addr, c.packet, c.challenge) + c.nonce = newNonce + t.activeCallByAuth[newNonce] = c + t.startResponseTimeout(c) +} + +// sendResponse sends a response packet to the given node. +// This doesn't trigger a handshake even if no keys are available. +func (t *UDPv5) sendResponse(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet) error { + _, err := t.send(toID, toAddr, packet, nil) + return err +} + +// send sends a packet to the given node. +func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet, c *v5wire.Whoareyou) (v5wire.Nonce, error) { + addr := toAddr.String() + enc, nonce, err := t.codec.Encode(toID, addr, packet, c) + if err != nil { + t.log.Warn(">> "+packet.Name(), "id", toID, "addr", addr, "err", err) + return nonce, err + } + _, err = t.conn.WriteToUDP(enc, toAddr) + t.log.Trace(">> "+packet.Name(), "id", toID, "addr", addr) + return nonce, err +} + +// readLoop runs in its own goroutine and reads packets from the network. +func (t *UDPv5) readLoop() { + defer t.wg.Done() + + buf := make([]byte, maxPacketSize) + for range t.readNextCh { + nbytes, from, err := t.conn.ReadFromUDP(buf) + if netutil.IsTemporaryError(err) { + // Ignore temporary read errors. + t.log.Debug("Temporary UDP read error", "err", err) + continue + } else if err != nil { + // Shut down the loop for permament errors. + if err != io.EOF { + t.log.Debug("UDP read error", "err", err) + } + return + } + t.dispatchReadPacket(from, buf[:nbytes]) + } +} + +// dispatchReadPacket sends a packet into the dispatch loop. +func (t *UDPv5) dispatchReadPacket(from *net.UDPAddr, content []byte) bool { + select { + case t.packetInCh <- ReadPacket{content, from}: + return true + case <-t.closeCtx.Done(): + return false + } +} + +// handlePacket decodes and processes an incoming packet from the network. +func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error { + addr := fromAddr.String() + fromID, fromNode, packet, err := t.codec.Decode(rawpacket, addr) + if err != nil { + t.log.Debug("Bad discv5 packet", "id", fromID, "addr", addr, "err", err) + return err + } + if fromNode != nil { + // Handshake succeeded, add to table. + t.tab.addSeenNode(wrapNode(fromNode)) + } + if packet.Kind() != v5wire.WhoareyouPacket { + // WHOAREYOU logged separately to report errors. + t.log.Trace("<< "+packet.Name(), "id", fromID, "addr", addr) + } + t.handle(packet, fromID, fromAddr) + return nil +} + +// handleCallResponse dispatches a response packet to the call waiting for it. +func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr *net.UDPAddr, p v5wire.Packet) bool { + ac := t.activeCallByNode[fromID] + if ac == nil || !bytes.Equal(p.RequestID(), ac.reqid) { + t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.Name()), "id", fromID, "addr", fromAddr) + return false + } + if !fromAddr.IP.Equal(ac.node.IP()) || fromAddr.Port != ac.node.UDP() { + t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.Name()), "id", fromID, "addr", fromAddr) + return false + } + if p.Kind() != ac.responseType { + t.log.Debug(fmt.Sprintf("Wrong discv5 response type %s", p.Name()), "id", fromID, "addr", fromAddr) + return false + } + t.startResponseTimeout(ac) + ac.ch <- p + return true +} + +// getNode looks for a node record in table and database. +func (t *UDPv5) getNode(id enode.ID) *enode.Node { + if n := t.tab.getNode(id); n != nil { + return n + } + if n := t.localNode.Database().Node(id); n != nil { + return n + } + return nil +} + +// handle processes incoming packets according to their message type. +func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr) { + switch p := p.(type) { + case *v5wire.Unknown: + t.handleUnknown(p, fromID, fromAddr) + case *v5wire.Whoareyou: + t.handleWhoareyou(p, fromID, fromAddr) + case *v5wire.Ping: + t.handlePing(p, fromID, fromAddr) + case *v5wire.Pong: + if t.handleCallResponse(fromID, fromAddr, p) { + t.localNode.UDPEndpointStatement(fromAddr, &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)}) + } + case *v5wire.Findnode: + t.handleFindnode(p, fromID, fromAddr) + case *v5wire.Nodes: + t.handleCallResponse(fromID, fromAddr, p) + case *v5wire.TalkRequest: + t.handleTalkRequest(p, fromID, fromAddr) + case *v5wire.TalkResponse: + t.handleCallResponse(fromID, fromAddr, p) + } +} + +// handleUnknown initiates a handshake by responding with WHOAREYOU. +func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr *net.UDPAddr) { + challenge := &v5wire.Whoareyou{Nonce: p.Nonce} + crand.Read(challenge.IDNonce[:]) + if n := t.getNode(fromID); n != nil { + challenge.Node = n + challenge.RecordSeq = n.Seq() + } + t.sendResponse(fromID, fromAddr, challenge) +} + +var ( + errChallengeNoCall = errors.New("no matching call") + errChallengeTwice = errors.New("second handshake") +) + +// handleWhoareyou resends the active call as a handshake packet. +func (t *UDPv5) handleWhoareyou(p *v5wire.Whoareyou, fromID enode.ID, fromAddr *net.UDPAddr) { + c, err := t.matchWithCall(fromID, p.Nonce) + if err != nil { + t.log.Debug("Invalid "+p.Name(), "addr", fromAddr, "err", err) + return + } + + // Resend the call that was answered by WHOAREYOU. + t.log.Trace("<< "+p.Name(), "id", c.node.ID(), "addr", fromAddr) + c.handshakeCount++ + c.challenge = p + p.Node = c.node + t.sendCall(c) +} + +// matchWithCall checks whether a handshake attempt matches the active call. +func (t *UDPv5) matchWithCall(fromID enode.ID, nonce v5wire.Nonce) (*callV5, error) { + c := t.activeCallByAuth[nonce] + if c == nil { + return nil, errChallengeNoCall + } + if c.handshakeCount > 0 { + return nil, errChallengeTwice + } + return c, nil +} + +// handlePing sends a PONG response. +func (t *UDPv5) handlePing(p *v5wire.Ping, fromID enode.ID, fromAddr *net.UDPAddr) { + remoteIP := fromAddr.IP + // Handle IPv4 mapped IPv6 addresses in the + // event the local node is binded to an + // ipv6 interface. + if remoteIP.To4() != nil { + remoteIP = remoteIP.To4() + } + t.sendResponse(fromID, fromAddr, &v5wire.Pong{ + ReqID: p.ReqID, + ToIP: remoteIP, + ToPort: uint16(fromAddr.Port), + ENRSeq: t.localNode.Node().Seq(), + }) +} + +// handleFindnode returns nodes to the requester. +func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr *net.UDPAddr) { + nodes := t.collectTableNodes(fromAddr.IP, p.Distances, findnodeResultLimit) + for _, resp := range packNodes(p.ReqID, nodes) { + t.sendResponse(fromID, fromAddr, resp) + } +} + +// collectTableNodes creates a FINDNODE result set for the given distances. +func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*enode.Node { + var nodes []*enode.Node + var processed = make(map[uint]struct{}) + for _, dist := range distances { + // Reject duplicate / invalid distances. + _, seen := processed[dist] + if seen || dist > 256 { + continue + } + + // Get the nodes. + var bn []*enode.Node + if dist == 0 { + bn = []*enode.Node{t.Self()} + } else if dist <= 256 { + t.tab.mutex.Lock() + bn = unwrapNodes(t.tab.bucketAtDistance(int(dist)).entries) + t.tab.mutex.Unlock() + } + processed[dist] = struct{}{} + + // Apply some pre-checks to avoid sending invalid nodes. + for _, n := range bn { + // TODO livenessChecks > 1 + if netutil.CheckRelayIP(rip, n.IP()) != nil { + continue + } + nodes = append(nodes, n) + if len(nodes) >= limit { + return nodes + } + } + } + return nodes +} + +// packNodes creates NODES response packets for the given node list. +func packNodes(reqid []byte, nodes []*enode.Node) []*v5wire.Nodes { + if len(nodes) == 0 { + return []*v5wire.Nodes{{ReqID: reqid, Total: 1}} + } + + total := uint8(math.Ceil(float64(len(nodes)) / 3)) + var resp []*v5wire.Nodes + for len(nodes) > 0 { + p := &v5wire.Nodes{ReqID: reqid, Total: total} + items := min(nodesResponseItemLimit, len(nodes)) + for i := 0; i < items; i++ { + p.Nodes = append(p.Nodes, nodes[i].Record()) + } + nodes = nodes[items:] + resp = append(resp, p) + } + return resp +} + +// handleTalkRequest runs the talk request handler of the requested protocol. +func (t *UDPv5) handleTalkRequest(p *v5wire.TalkRequest, fromID enode.ID, fromAddr *net.UDPAddr) { + t.trlock.Lock() + handler := t.trhandlers[p.Protocol] + t.trlock.Unlock() + + var response []byte + if handler != nil { + response = handler(fromID, fromAddr, p.Message) + } + resp := &v5wire.TalkResponse{ReqID: p.ReqID, Message: response} + t.sendResponse(fromID, fromAddr, resp) +} diff --git a/p2p/discover/v5_udp_test.go b/p2p/discover/v5_udp_test.go new file mode 100644 index 0000000000..b6d39bddc5 --- /dev/null +++ b/p2p/discover/v5_udp_test.go @@ -0,0 +1,810 @@ +// 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 . + +package discover + +import ( + "bytes" + "crypto/ecdsa" + "encoding/binary" + "fmt" + "math/rand" + "net" + "reflect" + "sort" + "testing" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/discover/v5wire" + + "github.com/AlayaNetwork/Alaya-Go/internal/testlog" + "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/rlp" +) + +// Real sockets, real crypto: this test checks end-to-end connectivity for UDPv5. +func TestUDPv5_lookupE2E(t *testing.T) { + t.Parallel() + + const N = 5 + var nodes []*UDPv5 + for i := 0; i < N; i++ { + var cfg Config + if len(nodes) > 0 { + bn := nodes[0].Self() + cfg.Bootnodes = []*enode.Node{bn} + } + node := startLocalhostV5(t, cfg) + nodes = append(nodes, node) + defer node.Close() + } + last := nodes[N-1] + target := nodes[rand.Intn(N-2)].Self() + + // It is expected that all nodes can be found. + expectedResult := make([]*enode.Node, len(nodes)) + for i := range nodes { + expectedResult[i] = nodes[i].Self() + } + sort.Slice(expectedResult, func(i, j int) bool { + return enode.DistCmp(target.ID(), expectedResult[i].ID(), expectedResult[j].ID()) < 0 + }) + + // Do the lookup. + results := last.Lookup(target.ID()) + if err := checkNodesEqual(results, expectedResult); err != nil { + t.Fatalf("lookup returned wrong results: %v", err) + } +} + +func startLocalhostV5(t *testing.T, cfg Config) *UDPv5 { + cfg.PrivateKey = newkey() + db, _ := enode.OpenDB("") + ln := enode.NewLocalNode(db, cfg.PrivateKey) + + // Prefix logs with node ID. + lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString()) + lfmt := log.TerminalFormat(false) + cfg.Log = testlog.Logger(t, log.LvlTrace) + cfg.Log.SetHandler(log.FuncHandler(func(r *log.Record) error { + t.Logf("%s %s", lprefix, lfmt.Format(r)) + return nil + })) + + // Listen. + socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}}) + if err != nil { + t.Fatal(err) + } + realaddr := socket.LocalAddr().(*net.UDPAddr) + ln.SetStaticIP(realaddr.IP) + ln.Set(enr.UDP(realaddr.Port)) + udp, err := ListenV5(socket, ln, cfg) + if err != nil { + t.Fatal(err) + } + return udp +} + +// This test checks that incoming PING calls are handled correctly. +func TestUDPv5_pingHandling(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + test.packetIn(&v5wire.Ping{ReqID: []byte("foo")}) + test.waitPacketOut(func(p *v5wire.Pong, addr *net.UDPAddr, _ v5wire.Nonce) { + if !bytes.Equal(p.ReqID, []byte("foo")) { + t.Error("wrong request ID in response:", p.ReqID) + } + if p.ENRSeq != test.table.self().Seq() { + t.Error("wrong ENR sequence number in response:", p.ENRSeq) + } + }) +} + +// This test checks that incoming 'unknown' packets trigger the handshake. +func TestUDPv5_unknownPacket(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + nonce := v5wire.Nonce{1, 2, 3} + check := func(p *v5wire.Whoareyou, wantSeq uint64) { + t.Helper() + if p.Nonce != nonce { + t.Error("wrong nonce in WHOAREYOU:", p.Nonce, nonce) + } + if p.IDNonce == ([16]byte{}) { + t.Error("all zero ID nonce") + } + if p.RecordSeq != wantSeq { + t.Errorf("wrong record seq %d in WHOAREYOU, want %d", p.RecordSeq, wantSeq) + } + } + + // Unknown packet from unknown node. + test.packetIn(&v5wire.Unknown{Nonce: nonce}) + test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) { + check(p, 0) + }) + + // Make node known. + n := test.getNode(test.remotekey, test.remoteaddr).Node() + test.table.addSeenNode(wrapNode(n)) + + test.packetIn(&v5wire.Unknown{Nonce: nonce}) + test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) { + check(p, n.Seq()) + }) +} + +// This test checks that incoming FINDNODE calls are handled correctly. +func TestUDPv5_findnodeHandling(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + // Create test nodes and insert them into the table. + nodes253 := nodesAtDistance(test.table.self().ID(), 253, 10) + nodes249 := nodesAtDistance(test.table.self().ID(), 249, 4) + nodes248 := nodesAtDistance(test.table.self().ID(), 248, 10) + fillTable(test.table, wrapNodes(nodes253)) + fillTable(test.table, wrapNodes(nodes249)) + fillTable(test.table, wrapNodes(nodes248)) + + // Requesting with distance zero should return the node's own record. + test.packetIn(&v5wire.Findnode{ReqID: []byte{0}, Distances: []uint{0}}) + test.expectNodes([]byte{0}, 1, []*enode.Node{test.udp.Self()}) + + // Requesting with distance > 256 shouldn't crash. + test.packetIn(&v5wire.Findnode{ReqID: []byte{1}, Distances: []uint{4234098}}) + test.expectNodes([]byte{1}, 1, nil) + + // Requesting with empty distance list shouldn't crash either. + test.packetIn(&v5wire.Findnode{ReqID: []byte{2}, Distances: []uint{}}) + test.expectNodes([]byte{2}, 1, nil) + + // This request gets no nodes because the corresponding bucket is empty. + test.packetIn(&v5wire.Findnode{ReqID: []byte{3}, Distances: []uint{254}}) + test.expectNodes([]byte{3}, 1, nil) + + // This request gets all the distance-253 nodes. + test.packetIn(&v5wire.Findnode{ReqID: []byte{4}, Distances: []uint{253}}) + test.expectNodes([]byte{4}, 4, nodes253) + + // This request gets all the distance-249 nodes and some more at 248 because + // the bucket at 249 is not full. + test.packetIn(&v5wire.Findnode{ReqID: []byte{5}, Distances: []uint{249, 248}}) + var nodes []*enode.Node + nodes = append(nodes, nodes249...) + nodes = append(nodes, nodes248[:10]...) + test.expectNodes([]byte{5}, 5, nodes) +} + +func (test *udpV5Test) expectNodes(wantReqID []byte, wantTotal uint8, wantNodes []*enode.Node) { + nodeSet := make(map[enode.ID]*enr.Record) + for _, n := range wantNodes { + nodeSet[n.ID()] = n.Record() + } + + for { + test.waitPacketOut(func(p *v5wire.Nodes, addr *net.UDPAddr, _ v5wire.Nonce) { + if !bytes.Equal(p.ReqID, wantReqID) { + test.t.Fatalf("wrong request ID %v in response, want %v", p.ReqID, wantReqID) + } + if len(p.Nodes) > 3 { + test.t.Fatalf("too many nodes in response") + } + if p.Total != wantTotal { + test.t.Fatalf("wrong total response count %d, want %d", p.Total, wantTotal) + } + for _, record := range p.Nodes { + n, _ := enode.New(enode.ValidSchemesForTesting, record) + want := nodeSet[n.ID()] + if want == nil { + test.t.Fatalf("unexpected node in response: %v", n) + } + if !reflect.DeepEqual(record, want) { + test.t.Fatalf("wrong record in response: %v", n) + } + delete(nodeSet, n.ID()) + } + }) + if len(nodeSet) == 0 { + return + } + } +} + +// This test checks that outgoing PING calls work. +func TestUDPv5_pingCall(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + remote := test.getNode(test.remotekey, test.remoteaddr).Node() + done := make(chan error, 1) + + // This ping times out. + go func() { + _, err := test.udp.ping(remote) + done <- err + }() + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {}) + if err := <-done; err != errTimeout { + t.Fatalf("want errTimeout, got %q", err) + } + + // This ping works. + go func() { + _, err := test.udp.ping(remote) + done <- err + }() + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) { + test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.Pong{ReqID: p.ReqID}) + }) + if err := <-done; err != nil { + t.Fatal(err) + } + + // This ping gets a reply from the wrong endpoint. + go func() { + _, err := test.udp.ping(remote) + done <- err + }() + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) { + wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 55, 22}, Port: 10101} + test.packetInFrom(test.remotekey, wrongAddr, &v5wire.Pong{ReqID: p.ReqID}) + }) + if err := <-done; err != errTimeout { + t.Fatalf("want errTimeout for reply from wrong IP, got %q", err) + } +} + +// This test checks that outgoing FINDNODE calls work and multiple NODES +// replies are aggregated. +func TestUDPv5_findnodeCall(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + // Launch the request: + var ( + distances = []uint{230} + remote = test.getNode(test.remotekey, test.remoteaddr).Node() + nodes = nodesAtDistance(remote.ID(), int(distances[0]), 8) + done = make(chan error, 1) + response []*enode.Node + ) + go func() { + var err error + response, err = test.udp.findnode(remote, distances) + done <- err + }() + + // Serve the responses: + test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) { + if !reflect.DeepEqual(p.Distances, distances) { + t.Fatalf("wrong distances in request: %v", p.Distances) + } + test.packetIn(&v5wire.Nodes{ + ReqID: p.ReqID, + Total: 2, + Nodes: nodesToRecords(nodes[:4]), + }) + test.packetIn(&v5wire.Nodes{ + ReqID: p.ReqID, + Total: 2, + Nodes: nodesToRecords(nodes[4:]), + }) + }) + + // Check results: + if err := <-done; err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !reflect.DeepEqual(response, nodes) { + t.Fatalf("wrong nodes in response") + } + + // TODO: check invalid IPs + // TODO: check invalid/unsigned record +} + +// This test checks that pending calls are re-sent when a handshake happens. +func TestUDPv5_callResend(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + remote := test.getNode(test.remotekey, test.remoteaddr).Node() + done := make(chan error, 2) + go func() { + _, err := test.udp.ping(remote) + done <- err + }() + go func() { + _, err := test.udp.ping(remote) + done <- err + }() + + // Ping answered by WHOAREYOU. + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) { + test.packetIn(&v5wire.Whoareyou{Nonce: nonce}) + }) + // Ping should be re-sent. + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) { + test.packetIn(&v5wire.Pong{ReqID: p.ReqID}) + }) + // Answer the other ping. + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) { + test.packetIn(&v5wire.Pong{ReqID: p.ReqID}) + }) + if err := <-done; err != nil { + t.Fatalf("unexpected ping error: %v", err) + } + if err := <-done; err != nil { + t.Fatalf("unexpected ping error: %v", err) + } +} + +// This test ensures we don't allow multiple rounds of WHOAREYOU for a single call. +func TestUDPv5_multipleHandshakeRounds(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + remote := test.getNode(test.remotekey, test.remoteaddr).Node() + done := make(chan error, 1) + go func() { + _, err := test.udp.ping(remote) + done <- err + }() + + // Ping answered by WHOAREYOU. + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) { + test.packetIn(&v5wire.Whoareyou{Nonce: nonce}) + }) + // Ping answered by WHOAREYOU again. + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) { + test.packetIn(&v5wire.Whoareyou{Nonce: nonce}) + }) + if err := <-done; err != errTimeout { + t.Fatalf("unexpected ping error: %q", err) + } +} + +// This test checks that calls with n replies may take up to n * respTimeout. +func TestUDPv5_callTimeoutReset(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + // Launch the request: + var ( + distance = uint(230) + remote = test.getNode(test.remotekey, test.remoteaddr).Node() + nodes = nodesAtDistance(remote.ID(), int(distance), 8) + done = make(chan error, 1) + ) + go func() { + _, err := test.udp.findnode(remote, []uint{distance}) + done <- err + }() + + // Serve two responses, slowly. + test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) { + time.Sleep(respTimeout - 50*time.Millisecond) + test.packetIn(&v5wire.Nodes{ + ReqID: p.ReqID, + Total: 2, + Nodes: nodesToRecords(nodes[:4]), + }) + + time.Sleep(respTimeout - 50*time.Millisecond) + test.packetIn(&v5wire.Nodes{ + ReqID: p.ReqID, + Total: 2, + Nodes: nodesToRecords(nodes[4:]), + }) + }) + if err := <-done; err != nil { + t.Fatalf("unexpected error: %q", err) + } +} + +// This test checks that TALKREQ calls the registered handler function. +func TestUDPv5_talkHandling(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + var recvMessage []byte + test.udp.RegisterTalkHandler("test", func(id enode.ID, addr *net.UDPAddr, message []byte) []byte { + recvMessage = message + return []byte("test response") + }) + + // Successful case: + test.packetIn(&v5wire.TalkRequest{ + ReqID: []byte("foo"), + Protocol: "test", + Message: []byte("test request"), + }) + test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) { + if !bytes.Equal(p.ReqID, []byte("foo")) { + t.Error("wrong request ID in response:", p.ReqID) + } + if string(p.Message) != "test response" { + t.Errorf("wrong talk response message: %q", p.Message) + } + if string(recvMessage) != "test request" { + t.Errorf("wrong message received in handler: %q", recvMessage) + } + }) + + // Check that empty response is returned for unregistered protocols. + recvMessage = nil + test.packetIn(&v5wire.TalkRequest{ + ReqID: []byte("2"), + Protocol: "wrong", + Message: []byte("test request"), + }) + test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) { + if !bytes.Equal(p.ReqID, []byte("2")) { + t.Error("wrong request ID in response:", p.ReqID) + } + if string(p.Message) != "" { + t.Errorf("wrong talk response message: %q", p.Message) + } + if recvMessage != nil { + t.Errorf("handler was called for wrong protocol: %q", recvMessage) + } + }) +} + +// This test checks that outgoing TALKREQ calls work. +func TestUDPv5_talkRequest(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + remote := test.getNode(test.remotekey, test.remoteaddr).Node() + done := make(chan error, 1) + + // This request times out. + go func() { + _, err := test.udp.TalkRequest(remote, "test", []byte("test request")) + done <- err + }() + test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {}) + if err := <-done; err != errTimeout { + t.Fatalf("want errTimeout, got %q", err) + } + + // This request works. + go func() { + _, err := test.udp.TalkRequest(remote, "test", []byte("test request")) + done <- err + }() + test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) { + if p.Protocol != "test" { + t.Errorf("wrong protocol ID in talk request: %q", p.Protocol) + } + if string(p.Message) != "test request" { + t.Errorf("wrong message talk request: %q", p.Message) + } + test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.TalkResponse{ + ReqID: p.ReqID, + Message: []byte("test response"), + }) + }) + if err := <-done; err != nil { + t.Fatal(err) + } +} + +// This test checks that lookup works. +func TestUDPv5_lookup(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + + // Lookup on empty table returns no nodes. + if results := test.udp.Lookup(lookupTestnet.target.id()); len(results) > 0 { + t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results) + } + + // Ensure the tester knows all nodes in lookupTestnet by IP. + for d, nn := range lookupTestnet.dists { + for i, key := range nn { + n := lookupTestnet.node(d, i) + test.getNode(key, &net.UDPAddr{IP: n.IP(), Port: n.UDP()}) + } + } + + // Seed table with initial node. + initialNode := lookupTestnet.node(256, 0) + fillTable(test.table, []*node{wrapNode(initialNode)}) + + // Start the lookup. + resultC := make(chan []*enode.Node, 1) + go func() { + resultC <- test.udp.Lookup(lookupTestnet.target.id()) + test.close() + }() + + // Answer lookup packets. + asked := make(map[enode.ID]bool) + for done := false; !done; { + done = test.waitPacketOut(func(p v5wire.Packet, to *net.UDPAddr, _ v5wire.Nonce) { + recipient, key := lookupTestnet.nodeByAddr(to) + switch p := p.(type) { + case *v5wire.Ping: + test.packetInFrom(key, to, &v5wire.Pong{ReqID: p.ReqID}) + case *v5wire.Findnode: + if asked[recipient.ID()] { + t.Error("Asked node", recipient.ID(), "twice") + } + asked[recipient.ID()] = true + nodes := lookupTestnet.neighborsAtDistances(recipient, p.Distances, 16) + t.Logf("Got FINDNODE for %v, returning %d nodes", p.Distances, len(nodes)) + for _, resp := range packNodes(p.ReqID, nodes) { + test.packetInFrom(key, to, resp) + } + } + }) + } + + // Verify result nodes. + results := <-resultC + checkLookupResults(t, lookupTestnet, results) +} + +// This test checks the local node can be utilised to set key-values. +func TestUDPv5_LocalNode(t *testing.T) { + t.Parallel() + var cfg Config + node := startLocalhostV5(t, cfg) + defer node.Close() + localNd := node.LocalNode() + + // set value in node's local record + testVal := [4]byte{'A', 'B', 'C', 'D'} + localNd.Set(enr.WithEntry("testing", &testVal)) + + // retrieve the value from self to make sure it matches. + outputVal := [4]byte{} + if err := node.Self().Load(enr.WithEntry("testing", &outputVal)); err != nil { + t.Errorf("Could not load value from record: %v", err) + } + if testVal != outputVal { + t.Errorf("Wanted %#x to be retrieved from the record but instead got %#x", testVal, outputVal) + } +} + +func TestUDPv5_PingWithIPV4MappedAddress(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + rawIP := net.IPv4(0xFF, 0x12, 0x33, 0xE5) + test.remoteaddr = &net.UDPAddr{ + IP: rawIP.To16(), + Port: 0, + } + remote := test.getNode(test.remotekey, test.remoteaddr).Node() + done := make(chan struct{}, 1) + + // This handler will truncate the ipv4-mapped in ipv6 address. + go func() { + test.udp.handlePing(&v5wire.Ping{ENRSeq: 1}, remote.ID(), test.remoteaddr) + done <- struct{}{} + }() + test.waitPacketOut(func(p *v5wire.Pong, addr *net.UDPAddr, _ v5wire.Nonce) { + if len(p.ToIP) == net.IPv6len { + t.Error("Received untruncated ip address") + } + if len(p.ToIP) != net.IPv4len { + t.Errorf("Received ip address with incorrect length: %d", len(p.ToIP)) + } + if !p.ToIP.Equal(rawIP) { + t.Errorf("Received incorrect ip address: wanted %s but received %s", rawIP.String(), p.ToIP.String()) + } + }) + <-done +} + +// udpV5Test is the framework for all tests above. +// It runs the UDPv5 transport on a virtual socket and allows testing outgoing packets. +type udpV5Test struct { + t *testing.T + pipe *dgramPipe + table *Table + db *enode.DB + udp *UDPv5 + localkey, remotekey *ecdsa.PrivateKey + remoteaddr *net.UDPAddr + nodesByID map[enode.ID]*enode.LocalNode + nodesByIP map[string]*enode.LocalNode +} + +// testCodec is the packet encoding used by protocol tests. This codec does not perform encryption. +type testCodec struct { + test *udpV5Test + id enode.ID + ctr uint64 +} + +type testCodecFrame struct { + NodeID enode.ID + AuthTag v5wire.Nonce + Ptype byte + Packet rlp.RawValue +} + +func (c *testCodec) Encode(toID enode.ID, addr string, p v5wire.Packet, _ *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) { + c.ctr++ + var authTag v5wire.Nonce + binary.BigEndian.PutUint64(authTag[:], c.ctr) + + penc, _ := rlp.EncodeToBytes(p) + frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.Kind(), penc}) + return frame, authTag, err +} + +func (c *testCodec) Decode(input []byte, addr string) (enode.ID, *enode.Node, v5wire.Packet, error) { + frame, p, err := c.decodeFrame(input) + if err != nil { + return enode.ID{}, nil, nil, err + } + return frame.NodeID, nil, p, nil +} + +func (c *testCodec) decodeFrame(input []byte) (frame testCodecFrame, p v5wire.Packet, err error) { + if err = rlp.DecodeBytes(input, &frame); err != nil { + return frame, nil, fmt.Errorf("invalid frame: %v", err) + } + switch frame.Ptype { + case v5wire.UnknownPacket: + dec := new(v5wire.Unknown) + err = rlp.DecodeBytes(frame.Packet, &dec) + p = dec + case v5wire.WhoareyouPacket: + dec := new(v5wire.Whoareyou) + err = rlp.DecodeBytes(frame.Packet, &dec) + p = dec + default: + p, err = v5wire.DecodeMessage(frame.Ptype, frame.Packet) + } + return frame, p, err +} + +func newUDPV5Test(t *testing.T) *udpV5Test { + test := &udpV5Test{ + t: t, + pipe: newpipe(), + localkey: newkey(), + remotekey: newkey(), + remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 30303}, + nodesByID: make(map[enode.ID]*enode.LocalNode), + nodesByIP: make(map[string]*enode.LocalNode), + } + test.db, _ = enode.OpenDB("") + ln := enode.NewLocalNode(test.db, test.localkey) + ln.SetStaticIP(net.IP{10, 0, 0, 1}) + ln.Set(enr.UDP(30303)) + test.udp, _ = ListenV5(test.pipe, ln, Config{ + PrivateKey: test.localkey, + Log: testlog.Logger(t, log.LvlTrace), + ValidSchemes: enode.ValidSchemesForTesting, + }) + test.udp.codec = &testCodec{test: test, id: ln.ID()} + test.table = test.udp.tab + test.nodesByID[ln.ID()] = ln + // Wait for initial refresh so the table doesn't send unexpected findnode. + <-test.table.initDone + return test +} + +// handles a packet as if it had been sent to the transport. +func (test *udpV5Test) packetIn(packet v5wire.Packet) { + test.t.Helper() + test.packetInFrom(test.remotekey, test.remoteaddr, packet) +} + +// handles a packet as if it had been sent to the transport by the key/endpoint. +func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, packet v5wire.Packet) { + test.t.Helper() + + ln := test.getNode(key, addr) + codec := &testCodec{test: test, id: ln.ID()} + enc, _, err := codec.Encode(test.udp.Self().ID(), addr.String(), packet, nil) + if err != nil { + test.t.Errorf("%s encode error: %v", packet.Name(), err) + } + if test.udp.dispatchReadPacket(addr, enc) { + <-test.udp.readNextCh // unblock UDPv5.dispatch + } +} + +// getNode ensures the test knows about a node at the given endpoint. +func (test *udpV5Test) getNode(key *ecdsa.PrivateKey, addr *net.UDPAddr) *enode.LocalNode { + id := encodePubkey(&key.PublicKey).id() + ln := test.nodesByID[id] + if ln == nil { + db, _ := enode.OpenDB("") + ln = enode.NewLocalNode(db, key) + ln.SetStaticIP(addr.IP) + ln.Set(enr.UDP(addr.Port)) + test.nodesByID[id] = ln + } + test.nodesByIP[string(addr.IP)] = ln + return ln +} + +// waitPacketOut waits for the next output packet and handles it using the given 'validate' +// function. The function must be of type func (X, *net.UDPAddr, v5wire.Nonce) where X is +// assignable to packetV5. +func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) { + test.t.Helper() + + fn := reflect.ValueOf(validate) + exptype := fn.Type().In(0) + + dgram, err := test.pipe.receive() + if err == errClosed { + return true + } + if err == errTimeout { + test.t.Fatalf("timed out waiting for %v", exptype) + return false + } + ln := test.nodesByIP[string(dgram.to.IP)] + if ln == nil { + test.t.Fatalf("attempt to send to non-existing node %v", &dgram.to) + return false + } + codec := &testCodec{test: test, id: ln.ID()} + frame, p, err := codec.decodeFrame(dgram.data) + if err != nil { + test.t.Errorf("sent packet decode error: %v", err) + return false + } + if !reflect.TypeOf(p).AssignableTo(exptype) { + test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype) + return false + } + fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(&dgram.to), reflect.ValueOf(frame.AuthTag)}) + return false +} + +func (test *udpV5Test) close() { + test.t.Helper() + + test.udp.Close() + test.db.Close() + for id, n := range test.nodesByID { + if id != test.udp.Self().ID() { + n.Database().Close() + } + } + if len(test.pipe.queue) != 0 { + test.t.Fatalf("%d unmatched UDP packets in queue", len(test.pipe.queue)) + } +} diff --git a/p2p/discover/v5wire/crypto.go b/p2p/discover/v5wire/crypto.go new file mode 100644 index 0000000000..2d34012a3f --- /dev/null +++ b/p2p/discover/v5wire/crypto.go @@ -0,0 +1,181 @@ +// 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 . + +package v5wire + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + "crypto/elliptic" + "errors" + "fmt" + "hash" + + "golang.org/x/crypto/hkdf" + + "github.com/AlayaNetwork/Alaya-Go/common/math" + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +const ( + // Encryption/authentication parameters. + aesKeySize = 16 + gcmNonceSize = 12 +) + +// Nonce represents a nonce used for AES/GCM. +type Nonce [gcmNonceSize]byte + +// EncodePubkey encodes a public key. +func EncodePubkey(key *ecdsa.PublicKey) []byte { + switch key.Curve { + case crypto.S256(): + return crypto.CompressPubkey(key) + default: + panic("unsupported curve " + key.Curve.Params().Name + " in EncodePubkey") + } +} + +// DecodePubkey decodes a public key in compressed format. +func DecodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) { + switch curve { + case crypto.S256(): + if len(e) != 33 { + return nil, errors.New("wrong size public key data") + } + return crypto.DecompressPubkey(e) + default: + return nil, fmt.Errorf("unsupported curve %s in DecodePubkey", curve.Params().Name) + } +} + +// idNonceHash computes the ID signature hash used in the handshake. +func idNonceHash(h hash.Hash, challenge, ephkey []byte, destID enode.ID) []byte { + h.Reset() + h.Write([]byte("discovery v5 identity proof")) + h.Write(challenge) + h.Write(ephkey) + h.Write(destID[:]) + return h.Sum(nil) +} + +// makeIDSignature creates the ID nonce signature. +func makeIDSignature(hash hash.Hash, key *ecdsa.PrivateKey, challenge, ephkey []byte, destID enode.ID) ([]byte, error) { + input := idNonceHash(hash, challenge, ephkey, destID) + switch key.Curve { + case crypto.S256(): + idsig, err := crypto.Sign(input, key) + if err != nil { + return nil, err + } + return idsig[:len(idsig)-1], nil // remove recovery ID + default: + return nil, fmt.Errorf("unsupported curve %s", key.Curve.Params().Name) + } +} + +// s256raw is an unparsed secp256k1 public key ENR entry. +type s256raw []byte + +func (s256raw) ENRKey() string { return "secp256k1" } + +// verifyIDSignature checks that signature over idnonce was made by the given node. +func verifyIDSignature(hash hash.Hash, sig []byte, n *enode.Node, challenge, ephkey []byte, destID enode.ID) error { + switch idscheme := n.Record().IdentityScheme(); idscheme { + case "v4": + var pubkey s256raw + if n.Load(&pubkey) != nil { + return errors.New("no secp256k1 public key in record") + } + input := idNonceHash(hash, challenge, ephkey, destID) + if !crypto.VerifySignature(pubkey, input, sig) { + return errInvalidNonceSig + } + return nil + default: + return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme) + } +} + +type hashFn func() hash.Hash + +// deriveKeys creates the session keys. +func deriveKeys(hash hashFn, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, n1, n2 enode.ID, challenge []byte) *session { + const text = "discovery v5 key agreement" + var info = make([]byte, 0, len(text)+len(n1)+len(n2)) + info = append(info, text...) + info = append(info, n1[:]...) + info = append(info, n2[:]...) + + eph := ecdh(priv, pub) + if eph == nil { + return nil + } + kdf := hkdf.New(hash, eph, challenge, info) + sec := session{writeKey: make([]byte, aesKeySize), readKey: make([]byte, aesKeySize)} + kdf.Read(sec.writeKey) + kdf.Read(sec.readKey) + for i := range eph { + eph[i] = 0 + } + return &sec +} + +// ecdh creates a shared secret. +func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte { + secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes()) + if secX == nil { + return nil + } + sec := make([]byte, 33) + sec[0] = 0x02 | byte(secY.Bit(0)) + math.ReadBits(secX, sec[1:]) + return sec +} + +// encryptGCM encrypts pt using AES-GCM with the given key and nonce. The ciphertext is +// appended to dest, which must not overlap with plaintext. The resulting ciphertext is 16 +// bytes longer than plaintext because it contains an authentication tag. +func encryptGCM(dest, key, nonce, plaintext, authData []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + panic(fmt.Errorf("can't create block cipher: %v", err)) + } + aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) + if err != nil { + panic(fmt.Errorf("can't create GCM: %v", err)) + } + return aesgcm.Seal(dest, nonce, plaintext, authData), nil +} + +// decryptGCM decrypts ct using AES-GCM with the given key and nonce. +func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, fmt.Errorf("can't create block cipher: %v", err) + } + if len(nonce) != gcmNonceSize { + return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce)) + } + aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) + if err != nil { + return nil, fmt.Errorf("can't create GCM: %v", err) + } + pt := make([]byte, 0, len(ct)) + return aesgcm.Open(pt, nonce, ct, authData) +} diff --git a/p2p/discover/v5wire/crypto_test.go b/p2p/discover/v5wire/crypto_test.go new file mode 100644 index 0000000000..56d1ad29aa --- /dev/null +++ b/p2p/discover/v5wire/crypto_test.go @@ -0,0 +1,124 @@ +// 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 . + +package v5wire + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/sha256" + "reflect" + "strings" + "testing" + + "github.com/AlayaNetwork/Alaya-Go/common/hexutil" + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +func TestVector_ECDH(t *testing.T) { + var ( + staticKey = hexPrivkey("0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736") + publicKey = hexPubkey(crypto.S256(), "0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231") + want = hexutil.MustDecode("0x033b11a2a1f214567e1537ce5e509ffd9b21373247f2a3ff6841f4976f53165e7e") + ) + result := ecdh(staticKey, publicKey) + check(t, "shared-secret", result, want) +} + +func TestVector_KDF(t *testing.T) { + var ( + ephKey = hexPrivkey("0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736") + cdata = hexutil.MustDecode("0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000") + net = newHandshakeTest() + ) + defer net.close() + + destKey := &testKeyB.PublicKey + s := deriveKeys(sha256.New, ephKey, destKey, net.nodeA.id(), net.nodeB.id(), cdata) + t.Logf("ephemeral-key = %#x", ephKey.D) + t.Logf("dest-pubkey = %#x", EncodePubkey(destKey)) + t.Logf("node-id-a = %#x", net.nodeA.id().Bytes()) + t.Logf("node-id-b = %#x", net.nodeB.id().Bytes()) + t.Logf("challenge-data = %#x", cdata) + check(t, "initiator-key", s.writeKey, hexutil.MustDecode("0xdccc82d81bd610f4f76d3ebe97a40571")) + check(t, "recipient-key", s.readKey, hexutil.MustDecode("0xac74bb8773749920b0d3a8881c173ec5")) +} + +func TestVector_IDSignature(t *testing.T) { + var ( + key = hexPrivkey("0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736") + destID = enode.HexID("0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9") + ephkey = hexutil.MustDecode("0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231") + cdata = hexutil.MustDecode("0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000") + ) + + sig, err := makeIDSignature(sha256.New(), key, cdata, ephkey, destID) + if err != nil { + t.Fatal(err) + } + t.Logf("static-key = %#x", key.D) + t.Logf("challenge-data = %#x", cdata) + t.Logf("ephemeral-pubkey = %#x", ephkey) + t.Logf("node-id-B = %#x", destID.Bytes()) + expected := "0x94852a1e2318c4e5e9d422c98eaf19d1d90d876b29cd06ca7cb7546d0fff7b484fe86c09a064fe72bdbef73ba8e9c34df0cd2b53e9d65528c2c7f336d5dfc6e6" + check(t, "id-signature", sig, hexutil.MustDecode(expected)) +} + +func TestDeriveKeys(t *testing.T) { + t.Parallel() + + var ( + n1 = enode.ID{1} + n2 = enode.ID{2} + cdata = []byte{1, 2, 3, 4} + ) + sec1 := deriveKeys(sha256.New, testKeyA, &testKeyB.PublicKey, n1, n2, cdata) + sec2 := deriveKeys(sha256.New, testKeyB, &testKeyA.PublicKey, n1, n2, cdata) + if sec1 == nil || sec2 == nil { + t.Fatal("key agreement failed") + } + if !reflect.DeepEqual(sec1, sec2) { + t.Fatalf("keys not equal:\n %+v\n %+v", sec1, sec2) + } +} + +func check(t *testing.T, what string, x, y []byte) { + t.Helper() + + if !bytes.Equal(x, y) { + t.Errorf("wrong %s: %#x != %#x", what, x, y) + } else { + t.Logf("%s = %#x", what, x) + } +} + +func hexPrivkey(input string) *ecdsa.PrivateKey { + key, err := crypto.HexToECDSA(strings.TrimPrefix(input, "0x")) + if err != nil { + panic(err) + } + return key +} + +func hexPubkey(curve elliptic.Curve, input string) *ecdsa.PublicKey { + key, err := DecodePubkey(curve, hexutil.MustDecode(input)) + if err != nil { + panic(err) + } + return key +} diff --git a/p2p/discover/v5wire/encoding.go b/p2p/discover/v5wire/encoding.go new file mode 100644 index 0000000000..9002234163 --- /dev/null +++ b/p2p/discover/v5wire/encoding.go @@ -0,0 +1,648 @@ +// 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 . + +package v5wire + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + crand "crypto/rand" + "crypto/sha256" + "encoding/binary" + "errors" + "fmt" + "hash" + + "github.com/AlayaNetwork/Alaya-Go/common/mclock" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/rlp" +) + +// TODO concurrent WHOAREYOU tie-breaker +// TODO rehandshake after X packets + +// Header represents a packet header. +type Header struct { + IV [sizeofMaskingIV]byte + StaticHeader + AuthData []byte + + src enode.ID // used by decoder +} + +// StaticHeader contains the static fields of a packet header. +type StaticHeader struct { + ProtocolID [6]byte + Version uint16 + Flag byte + Nonce Nonce + AuthSize uint16 +} + +// Authdata layouts. +type ( + whoareyouAuthData struct { + IDNonce [16]byte // ID proof data + RecordSeq uint64 // highest known ENR sequence of requester + } + + handshakeAuthData struct { + h struct { + SrcID enode.ID + SigSize byte // ignature data + PubkeySize byte // offset of + } + // Trailing variable-size data. + signature, pubkey, record []byte + } + + messageAuthData struct { + SrcID enode.ID + } +) + +// Packet header flag values. +const ( + flagMessage = iota + flagWhoareyou + flagHandshake +) + +// Protocol constants. +const ( + version = 1 + minVersion = 1 + sizeofMaskingIV = 16 + + minMessageSize = 48 // this refers to data after static headers + randomPacketMsgSize = 20 +) + +var protocolID = [6]byte{'d', 'i', 's', 'c', 'v', '5'} + +// Errors. +var ( + errTooShort = errors.New("packet too short") + errInvalidHeader = errors.New("invalid packet header") + errInvalidFlag = errors.New("invalid flag value in header") + errMinVersion = errors.New("version of packet header below minimum") + errMsgTooShort = errors.New("message/handshake packet below minimum size") + errAuthSize = errors.New("declared auth size is beyond packet length") + errUnexpectedHandshake = errors.New("unexpected auth response, not in handshake") + errInvalidAuthKey = errors.New("invalid ephemeral pubkey") + errNoRecord = errors.New("expected ENR in handshake but none sent") + errInvalidNonceSig = errors.New("invalid ID nonce signature") + errMessageTooShort = errors.New("message contains no data") + errMessageDecrypt = errors.New("cannot decrypt message") +) + +// Public errors. +var ( + ErrInvalidReqID = errors.New("request ID larger than 8 bytes") +) + +// Packet sizes. +var ( + sizeofStaticHeader = binary.Size(StaticHeader{}) + sizeofWhoareyouAuthData = binary.Size(whoareyouAuthData{}) + sizeofHandshakeAuthData = binary.Size(handshakeAuthData{}.h) + sizeofMessageAuthData = binary.Size(messageAuthData{}) + sizeofStaticPacketData = sizeofMaskingIV + sizeofStaticHeader +) + +// Codec encodes and decodes Discovery v5 packets. +// This type is not safe for concurrent use. +type Codec struct { + sha256 hash.Hash + localnode *enode.LocalNode + privkey *ecdsa.PrivateKey + sc *SessionCache + + // encoder buffers + buf bytes.Buffer // whole packet + headbuf bytes.Buffer // packet header + msgbuf bytes.Buffer // message RLP plaintext + msgctbuf []byte // message data ciphertext + + // decoder buffer + reader bytes.Reader +} + +// NewCodec creates a wire codec. +func NewCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock) *Codec { + c := &Codec{ + sha256: sha256.New(), + localnode: ln, + privkey: key, + sc: NewSessionCache(1024, clock), + } + return c +} + +// Encode encodes a packet to a node. 'id' and 'addr' specify the destination node. The +// 'challenge' parameter should be the most recently received WHOAREYOU packet from that +// node. +func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoareyou) ([]byte, Nonce, error) { + // Create the packet header. + var ( + head Header + session *session + msgData []byte + err error + ) + switch { + case packet.Kind() == WhoareyouPacket: + head, err = c.encodeWhoareyou(id, packet.(*Whoareyou)) + case challenge != nil: + // We have an unanswered challenge, send handshake. + head, session, err = c.encodeHandshakeHeader(id, addr, challenge) + default: + session = c.sc.session(id, addr) + if session != nil { + // There is a session, use it. + head, err = c.encodeMessageHeader(id, session) + } else { + // No keys, send random data to kick off the handshake. + head, msgData, err = c.encodeRandom(id) + } + } + if err != nil { + return nil, Nonce{}, err + } + + // Generate masking IV. + if err := c.sc.maskingIVGen(head.IV[:]); err != nil { + return nil, Nonce{}, fmt.Errorf("can't generate masking IV: %v", err) + } + + // Encode header data. + c.writeHeaders(&head) + + // Store sent WHOAREYOU challenges. + if challenge, ok := packet.(*Whoareyou); ok { + challenge.ChallengeData = bytesCopy(&c.buf) + c.sc.storeSentHandshake(id, addr, challenge) + } else if msgData == nil { + headerData := c.buf.Bytes() + msgData, err = c.encryptMessage(session, packet, &head, headerData) + if err != nil { + return nil, Nonce{}, err + } + } + + enc, err := c.EncodeRaw(id, head, msgData) + return enc, head.Nonce, err +} + +// EncodeRaw encodes a packet with the given header. +func (c *Codec) EncodeRaw(id enode.ID, head Header, msgdata []byte) ([]byte, error) { + c.writeHeaders(&head) + + // Apply masking. + masked := c.buf.Bytes()[sizeofMaskingIV:] + mask := head.mask(id) + mask.XORKeyStream(masked[:], masked[:]) + + // Write message data. + c.buf.Write(msgdata) + return c.buf.Bytes(), nil +} + +func (c *Codec) writeHeaders(head *Header) { + c.buf.Reset() + c.buf.Write(head.IV[:]) + binary.Write(&c.buf, binary.BigEndian, &head.StaticHeader) + c.buf.Write(head.AuthData) +} + +// makeHeader creates a packet header. +func (c *Codec) makeHeader(toID enode.ID, flag byte, authsizeExtra int) Header { + var authsize int + switch flag { + case flagMessage: + authsize = sizeofMessageAuthData + case flagWhoareyou: + authsize = sizeofWhoareyouAuthData + case flagHandshake: + authsize = sizeofHandshakeAuthData + default: + panic(fmt.Errorf("BUG: invalid packet header flag %x", flag)) + } + authsize += authsizeExtra + if authsize > int(^uint16(0)) { + panic(fmt.Errorf("BUG: auth size %d overflows uint16", authsize)) + } + return Header{ + StaticHeader: StaticHeader{ + ProtocolID: protocolID, + Version: version, + Flag: flag, + AuthSize: uint16(authsize), + }, + } +} + +// encodeRandom encodes a packet with random content. +func (c *Codec) encodeRandom(toID enode.ID) (Header, []byte, error) { + head := c.makeHeader(toID, flagMessage, 0) + + // Encode auth data. + auth := messageAuthData{SrcID: c.localnode.ID()} + if _, err := crand.Read(head.Nonce[:]); err != nil { + return head, nil, fmt.Errorf("can't get random data: %v", err) + } + c.headbuf.Reset() + binary.Write(&c.headbuf, binary.BigEndian, auth) + head.AuthData = c.headbuf.Bytes() + + // Fill message ciphertext buffer with random bytes. + c.msgctbuf = append(c.msgctbuf[:0], make([]byte, randomPacketMsgSize)...) + crand.Read(c.msgctbuf) + return head, c.msgctbuf, nil +} + +// encodeWhoareyou encodes a WHOAREYOU packet. +func (c *Codec) encodeWhoareyou(toID enode.ID, packet *Whoareyou) (Header, error) { + // Sanity check node field to catch misbehaving callers. + if packet.RecordSeq > 0 && packet.Node == nil { + panic("BUG: missing node in whoareyou with non-zero seq") + } + + // Create header. + head := c.makeHeader(toID, flagWhoareyou, 0) + head.AuthData = bytesCopy(&c.buf) + head.Nonce = packet.Nonce + + // Encode auth data. + auth := &whoareyouAuthData{ + IDNonce: packet.IDNonce, + RecordSeq: packet.RecordSeq, + } + c.headbuf.Reset() + binary.Write(&c.headbuf, binary.BigEndian, auth) + head.AuthData = c.headbuf.Bytes() + return head, nil +} + +// encodeHandshakeMessage encodes the handshake message packet header. +func (c *Codec) encodeHandshakeHeader(toID enode.ID, addr string, challenge *Whoareyou) (Header, *session, error) { + // Ensure calling code sets challenge.node. + if challenge.Node == nil { + panic("BUG: missing challenge.Node in encode") + } + + // Generate new secrets. + auth, session, err := c.makeHandshakeAuth(toID, addr, challenge) + if err != nil { + return Header{}, nil, err + } + + // Generate nonce for message. + nonce, err := c.sc.nextNonce(session) + if err != nil { + return Header{}, nil, fmt.Errorf("can't generate nonce: %v", err) + } + + // TODO: this should happen when the first authenticated message is received + c.sc.storeNewSession(toID, addr, session) + + // Encode the auth header. + var ( + authsizeExtra = len(auth.pubkey) + len(auth.signature) + len(auth.record) + head = c.makeHeader(toID, flagHandshake, authsizeExtra) + ) + c.headbuf.Reset() + binary.Write(&c.headbuf, binary.BigEndian, &auth.h) + c.headbuf.Write(auth.signature) + c.headbuf.Write(auth.pubkey) + c.headbuf.Write(auth.record) + head.AuthData = c.headbuf.Bytes() + head.Nonce = nonce + return head, session, err +} + +// encodeAuthHeader creates the auth header on a request packet following WHOAREYOU. +func (c *Codec) makeHandshakeAuth(toID enode.ID, addr string, challenge *Whoareyou) (*handshakeAuthData, *session, error) { + auth := new(handshakeAuthData) + auth.h.SrcID = c.localnode.ID() + + // Create the ephemeral key. This needs to be first because the + // key is part of the ID nonce signature. + var remotePubkey = new(ecdsa.PublicKey) + if err := challenge.Node.Load((*enode.Secp256k1)(remotePubkey)); err != nil { + return nil, nil, fmt.Errorf("can't find secp256k1 key for recipient") + } + ephkey, err := c.sc.ephemeralKeyGen() + if err != nil { + return nil, nil, fmt.Errorf("can't generate ephemeral key") + } + ephpubkey := EncodePubkey(&ephkey.PublicKey) + auth.pubkey = ephpubkey[:] + auth.h.PubkeySize = byte(len(auth.pubkey)) + + // Add ID nonce signature to response. + cdata := challenge.ChallengeData + idsig, err := makeIDSignature(c.sha256, c.privkey, cdata, ephpubkey[:], toID) + if err != nil { + return nil, nil, fmt.Errorf("can't sign: %v", err) + } + auth.signature = idsig + auth.h.SigSize = byte(len(auth.signature)) + + // Add our record to response if it's newer than what remote side has. + ln := c.localnode.Node() + if challenge.RecordSeq < ln.Seq() { + auth.record, _ = rlp.EncodeToBytes(ln.Record()) + } + + // Create session keys. + sec := deriveKeys(sha256.New, ephkey, remotePubkey, c.localnode.ID(), challenge.Node.ID(), cdata) + if sec == nil { + return nil, nil, fmt.Errorf("key derivation failed") + } + return auth, sec, err +} + +// encodeMessage encodes an encrypted message packet. +func (c *Codec) encodeMessageHeader(toID enode.ID, s *session) (Header, error) { + head := c.makeHeader(toID, flagMessage, 0) + + // Create the header. + nonce, err := c.sc.nextNonce(s) + if err != nil { + return Header{}, fmt.Errorf("can't generate nonce: %v", err) + } + auth := messageAuthData{SrcID: c.localnode.ID()} + c.buf.Reset() + binary.Write(&c.buf, binary.BigEndian, &auth) + head.AuthData = bytesCopy(&c.buf) + head.Nonce = nonce + return head, err +} + +func (c *Codec) encryptMessage(s *session, p Packet, head *Header, headerData []byte) ([]byte, error) { + // Encode message plaintext. + c.msgbuf.Reset() + c.msgbuf.WriteByte(p.Kind()) + if err := rlp.Encode(&c.msgbuf, p); err != nil { + return nil, err + } + messagePT := c.msgbuf.Bytes() + + // Encrypt into message ciphertext buffer. + messageCT, err := encryptGCM(c.msgctbuf[:0], s.writeKey, head.Nonce[:], messagePT, headerData) + if err == nil { + c.msgctbuf = messageCT + } + return messageCT, err +} + +// Decode decodes a discovery packet. +func (c *Codec) Decode(input []byte, addr string) (src enode.ID, n *enode.Node, p Packet, err error) { + // Unmask the static header. + if len(input) < sizeofStaticPacketData { + return enode.ID{}, nil, nil, errTooShort + } + var head Header + copy(head.IV[:], input[:sizeofMaskingIV]) + mask := head.mask(c.localnode.ID()) + staticHeader := input[sizeofMaskingIV:sizeofStaticPacketData] + mask.XORKeyStream(staticHeader, staticHeader) + + // Decode and verify the static header. + c.reader.Reset(staticHeader) + binary.Read(&c.reader, binary.BigEndian, &head.StaticHeader) + remainingInput := len(input) - sizeofStaticPacketData + if err := head.checkValid(remainingInput); err != nil { + return enode.ID{}, nil, nil, err + } + + // Unmask auth data. + authDataEnd := sizeofStaticPacketData + int(head.AuthSize) + authData := input[sizeofStaticPacketData:authDataEnd] + mask.XORKeyStream(authData, authData) + head.AuthData = authData + + // Delete timed-out handshakes. This must happen before decoding to avoid + // processing the same handshake twice. + c.sc.handshakeGC() + + // Decode auth part and message. + headerData := input[:authDataEnd] + msgData := input[authDataEnd:] + switch head.Flag { + case flagWhoareyou: + p, err = c.decodeWhoareyou(&head, headerData) + case flagHandshake: + n, p, err = c.decodeHandshakeMessage(addr, &head, headerData, msgData) + case flagMessage: + p, err = c.decodeMessage(addr, &head, headerData, msgData) + default: + err = errInvalidFlag + } + return head.src, n, p, err +} + +// decodeWhoareyou reads packet data after the header as a WHOAREYOU packet. +func (c *Codec) decodeWhoareyou(head *Header, headerData []byte) (Packet, error) { + if len(head.AuthData) != sizeofWhoareyouAuthData { + return nil, fmt.Errorf("invalid auth size %d for WHOAREYOU", len(head.AuthData)) + } + var auth whoareyouAuthData + c.reader.Reset(head.AuthData) + binary.Read(&c.reader, binary.BigEndian, &auth) + p := &Whoareyou{ + Nonce: head.Nonce, + IDNonce: auth.IDNonce, + RecordSeq: auth.RecordSeq, + ChallengeData: make([]byte, len(headerData)), + } + copy(p.ChallengeData, headerData) + return p, nil +} + +func (c *Codec) decodeHandshakeMessage(fromAddr string, head *Header, headerData, msgData []byte) (n *enode.Node, p Packet, err error) { + node, auth, session, err := c.decodeHandshake(fromAddr, head) + if err != nil { + c.sc.deleteHandshake(auth.h.SrcID, fromAddr) + return nil, nil, err + } + + // Decrypt the message using the new session keys. + msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, session.readKey) + if err != nil { + c.sc.deleteHandshake(auth.h.SrcID, fromAddr) + return node, msg, err + } + + // Handshake OK, drop the challenge and store the new session keys. + c.sc.storeNewSession(auth.h.SrcID, fromAddr, session) + c.sc.deleteHandshake(auth.h.SrcID, fromAddr) + return node, msg, nil +} + +func (c *Codec) decodeHandshake(fromAddr string, head *Header) (n *enode.Node, auth handshakeAuthData, s *session, err error) { + if auth, err = c.decodeHandshakeAuthData(head); err != nil { + return nil, auth, nil, err + } + + // Verify against our last WHOAREYOU. + challenge := c.sc.getHandshake(auth.h.SrcID, fromAddr) + if challenge == nil { + return nil, auth, nil, errUnexpectedHandshake + } + // Get node record. + n, err = c.decodeHandshakeRecord(challenge.Node, auth.h.SrcID, auth.record) + if err != nil { + return nil, auth, nil, err + } + // Verify ID nonce signature. + sig := auth.signature + cdata := challenge.ChallengeData + err = verifyIDSignature(c.sha256, sig, n, cdata, auth.pubkey, c.localnode.ID()) + if err != nil { + return nil, auth, nil, err + } + // Verify ephemeral key is on curve. + ephkey, err := DecodePubkey(c.privkey.Curve, auth.pubkey) + if err != nil { + return nil, auth, nil, errInvalidAuthKey + } + // Derive sesssion keys. + session := deriveKeys(sha256.New, c.privkey, ephkey, auth.h.SrcID, c.localnode.ID(), cdata) + session = session.keysFlipped() + return n, auth, session, nil +} + +// decodeHandshakeAuthData reads the authdata section of a handshake packet. +func (c *Codec) decodeHandshakeAuthData(head *Header) (auth handshakeAuthData, err error) { + // Decode fixed size part. + if len(head.AuthData) < sizeofHandshakeAuthData { + return auth, fmt.Errorf("header authsize %d too low for handshake", head.AuthSize) + } + c.reader.Reset(head.AuthData) + binary.Read(&c.reader, binary.BigEndian, &auth.h) + head.src = auth.h.SrcID + + // Decode variable-size part. + var ( + vardata = head.AuthData[sizeofHandshakeAuthData:] + sigAndKeySize = int(auth.h.SigSize) + int(auth.h.PubkeySize) + keyOffset = int(auth.h.SigSize) + recOffset = keyOffset + int(auth.h.PubkeySize) + ) + if len(vardata) < sigAndKeySize { + return auth, errTooShort + } + auth.signature = vardata[:keyOffset] + auth.pubkey = vardata[keyOffset:recOffset] + auth.record = vardata[recOffset:] + return auth, nil +} + +// decodeHandshakeRecord verifies the node record contained in a handshake packet. The +// remote node should include the record if we don't have one or if ours is older than the +// latest sequence number. +func (c *Codec) decodeHandshakeRecord(local *enode.Node, wantID enode.ID, remote []byte) (*enode.Node, error) { + node := local + if len(remote) > 0 { + var record enr.Record + if err := rlp.DecodeBytes(remote, &record); err != nil { + return nil, err + } + if local == nil || local.Seq() < record.Seq() { + n, err := enode.New(enode.ValidSchemes, &record) + if err != nil { + return nil, fmt.Errorf("invalid node record: %v", err) + } + if n.ID() != wantID { + return nil, fmt.Errorf("record in handshake has wrong ID: %v", n.ID()) + } + node = n + } + } + if node == nil { + return nil, errNoRecord + } + return node, nil +} + +// decodeMessage reads packet data following the header as an ordinary message packet. +func (c *Codec) decodeMessage(fromAddr string, head *Header, headerData, msgData []byte) (Packet, error) { + if len(head.AuthData) != sizeofMessageAuthData { + return nil, fmt.Errorf("invalid auth size %d for message packet", len(head.AuthData)) + } + var auth messageAuthData + c.reader.Reset(head.AuthData) + binary.Read(&c.reader, binary.BigEndian, &auth) + head.src = auth.SrcID + + // Try decrypting the message. + key := c.sc.readKey(auth.SrcID, fromAddr) + msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, key) + if err == errMessageDecrypt { + // It didn't work. Start the handshake since this is an ordinary message packet. + return &Unknown{Nonce: head.Nonce}, nil + } + return msg, err +} + +func (c *Codec) decryptMessage(input, nonce, headerData, readKey []byte) (Packet, error) { + msgdata, err := decryptGCM(readKey, nonce, input, headerData) + if err != nil { + return nil, errMessageDecrypt + } + if len(msgdata) == 0 { + return nil, errMessageTooShort + } + return DecodeMessage(msgdata[0], msgdata[1:]) +} + +// checkValid performs some basic validity checks on the header. +// The packetLen here is the length remaining after the static header. +func (h *StaticHeader) checkValid(packetLen int) error { + if h.ProtocolID != protocolID { + return errInvalidHeader + } + if h.Version < minVersion { + return errMinVersion + } + if h.Flag != flagWhoareyou && packetLen < minMessageSize { + return errMsgTooShort + } + if int(h.AuthSize) > packetLen { + return errAuthSize + } + return nil +} + +// headerMask returns a cipher for 'masking' / 'unmasking' packet headers. +func (h *Header) mask(destID enode.ID) cipher.Stream { + block, err := aes.NewCipher(destID[:16]) + if err != nil { + panic("can't create cipher") + } + return cipher.NewCTR(block, h.IV[:]) +} + +func bytesCopy(r *bytes.Buffer) []byte { + b := make([]byte, r.Len()) + copy(b, r.Bytes()) + return b +} diff --git a/p2p/discover/v5wire/encoding_test.go b/p2p/discover/v5wire/encoding_test.go new file mode 100644 index 0000000000..b1986c1af1 --- /dev/null +++ b/p2p/discover/v5wire/encoding_test.go @@ -0,0 +1,634 @@ +// 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 . + +package v5wire + +import ( + "bytes" + "crypto/ecdsa" + "encoding/hex" + "errors" + "flag" + "fmt" + "io/ioutil" + "net" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/davecgh/go-spew/spew" + + "github.com/AlayaNetwork/Alaya-Go/common/hexutil" + "github.com/AlayaNetwork/Alaya-Go/common/mclock" + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +// To regenerate discv5 test vectors, run +// +// go test -run TestVectors -write-test-vectors +// +var writeTestVectorsFlag = flag.Bool("write-test-vectors", false, "Overwrite discv5 test vectors in testdata/") + +var ( + testKeyA, _ = crypto.HexToECDSA("eef77acb6c6a6eebc5b363a475ac583ec7eccdb42b6481424c60f59aa326547f") + testKeyB, _ = crypto.HexToECDSA("66fb62bfbd66b9177a138c1e5cddbe4f7c30c343e94e68df8769459cb1cde628") + testEphKey, _ = crypto.HexToECDSA("0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6") + testIDnonce = [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} +) + +// This test checks that the minPacketSize and randomPacketMsgSize constants are well-defined. +func TestMinSizes(t *testing.T) { + var ( + gcmTagSize = 16 + emptyMsg = sizeofMessageAuthData + gcmTagSize + ) + t.Log("static header size", sizeofStaticPacketData) + t.Log("whoareyou size", sizeofStaticPacketData+sizeofWhoareyouAuthData) + t.Log("empty msg size", sizeofStaticPacketData+emptyMsg) + if want := emptyMsg; minMessageSize != want { + t.Fatalf("wrong minMessageSize %d, want %d", minMessageSize, want) + } + if sizeofMessageAuthData+randomPacketMsgSize < minMessageSize { + t.Fatalf("randomPacketMsgSize %d too small", randomPacketMsgSize) + } +} + +// This test checks the basic handshake flow where A talks to B and A has no secrets. +func TestHandshake(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + // A -> B RANDOM PACKET + packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{}) + resp := net.nodeB.expectDecode(t, UnknownPacket, packet) + + // A <- B WHOAREYOU + challenge := &Whoareyou{ + Nonce: resp.(*Unknown).Nonce, + IDNonce: testIDnonce, + RecordSeq: 0, + } + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // A -> B FINDNODE (handshake packet) + findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecode(t, FindnodeMsg, findnode) + if len(net.nodeB.c.sc.handshakes) > 0 { + t.Fatalf("node B didn't remove handshake from challenge map") + } + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1}) + net.nodeA.expectDecode(t, NodesMsg, nodes) +} + +// This test checks that handshake attempts are removed within the timeout. +func TestHandshake_timeout(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + // A -> B RANDOM PACKET + packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{}) + resp := net.nodeB.expectDecode(t, UnknownPacket, packet) + + // A <- B WHOAREYOU + challenge := &Whoareyou{ + Nonce: resp.(*Unknown).Nonce, + IDNonce: testIDnonce, + RecordSeq: 0, + } + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // A -> B FINDNODE (handshake packet) after timeout + net.clock.Run(handshakeTimeout + 1) + findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, findnode) +} + +// This test checks handshake behavior when no record is sent in the auth response. +func TestHandshake_norecord(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + // A -> B RANDOM PACKET + packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{}) + resp := net.nodeB.expectDecode(t, UnknownPacket, packet) + + // A <- B WHOAREYOU + nodeA := net.nodeA.n() + if nodeA.Seq() == 0 { + t.Fatal("need non-zero sequence number") + } + challenge := &Whoareyou{ + Nonce: resp.(*Unknown).Nonce, + IDNonce: testIDnonce, + RecordSeq: nodeA.Seq(), + Node: nodeA, + } + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // A -> B FINDNODE + findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecode(t, FindnodeMsg, findnode) + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1}) + net.nodeA.expectDecode(t, NodesMsg, nodes) +} + +// In this test, A tries to send FINDNODE with existing secrets but B doesn't know +// anything about A. +func TestHandshake_rekey(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + session := &session{ + readKey: []byte("BBBBBBBBBBBBBBBB"), + writeKey: []byte("AAAAAAAAAAAAAAAA"), + } + net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session) + + // A -> B FINDNODE (encrypted with zero keys) + findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{}) + net.nodeB.expectDecode(t, UnknownPacket, findnode) + + // A <- B WHOAREYOU + challenge := &Whoareyou{Nonce: authTag, IDNonce: testIDnonce} + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // Check that new keys haven't been stored yet. + sa := net.nodeA.c.sc.session(net.nodeB.id(), net.nodeB.addr()) + if !bytes.Equal(sa.writeKey, session.writeKey) || !bytes.Equal(sa.readKey, session.readKey) { + t.Fatal("node A stored keys too early") + } + if s := net.nodeB.c.sc.session(net.nodeA.id(), net.nodeA.addr()); s != nil { + t.Fatal("node B stored keys too early") + } + + // A -> B FINDNODE encrypted with new keys + findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecode(t, FindnodeMsg, findnode) + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1}) + net.nodeA.expectDecode(t, NodesMsg, nodes) +} + +// In this test A and B have different keys before the handshake. +func TestHandshake_rekey2(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + initKeysA := &session{ + readKey: []byte("BBBBBBBBBBBBBBBB"), + writeKey: []byte("AAAAAAAAAAAAAAAA"), + } + initKeysB := &session{ + readKey: []byte("CCCCCCCCCCCCCCCC"), + writeKey: []byte("DDDDDDDDDDDDDDDD"), + } + net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeysA) + net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), initKeysB) + + // A -> B FINDNODE encrypted with initKeysA + findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{Distances: []uint{3}}) + net.nodeB.expectDecode(t, UnknownPacket, findnode) + + // A <- B WHOAREYOU + challenge := &Whoareyou{Nonce: authTag, IDNonce: testIDnonce} + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // A -> B FINDNODE (handshake packet) + findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecode(t, FindnodeMsg, findnode) + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1}) + net.nodeA.expectDecode(t, NodesMsg, nodes) +} + +func TestHandshake_BadHandshakeAttack(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + // A -> B RANDOM PACKET + packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{}) + resp := net.nodeB.expectDecode(t, UnknownPacket, packet) + + // A <- B WHOAREYOU + challenge := &Whoareyou{ + Nonce: resp.(*Unknown).Nonce, + IDNonce: testIDnonce, + RecordSeq: 0, + } + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // A -> B FINDNODE + incorrect_challenge := &Whoareyou{ + IDNonce: [16]byte{5, 6, 7, 8, 9, 6, 11, 12}, + RecordSeq: challenge.RecordSeq, + Node: challenge.Node, + sent: challenge.sent, + } + incorrect_findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, incorrect_challenge, &Findnode{}) + incorrect_findnode2 := make([]byte, len(incorrect_findnode)) + copy(incorrect_findnode2, incorrect_findnode) + + net.nodeB.expectDecodeErr(t, errInvalidNonceSig, incorrect_findnode) + + // Reject new findnode as previous handshake is now deleted. + net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, incorrect_findnode2) + + // The findnode packet is again rejected even with a valid challenge this time. + findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, findnode) +} + +// This test checks some malformed packets. +func TestDecodeErrorsV5(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + net.nodeA.expectDecodeErr(t, errTooShort, []byte{}) + // TODO some more tests would be nice :) + // - check invalid authdata sizes + // - check invalid handshake data sizes +} + +// This test checks that all test vectors can be decoded. +func TestTestVectorsV5(t *testing.T) { + var ( + idA = enode.PubkeyToIDV4(&testKeyA.PublicKey) + idB = enode.PubkeyToIDV4(&testKeyB.PublicKey) + addr = "127.0.0.1" + session = &session{ + writeKey: hexutil.MustDecode("0x00000000000000000000000000000000"), + readKey: hexutil.MustDecode("0x01010101010101010101010101010101"), + } + challenge0A, challenge1A, challenge0B Whoareyou + ) + + // Create challenge packets. + c := Whoareyou{ + Nonce: Nonce{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, + IDNonce: testIDnonce, + } + challenge0A, challenge1A, challenge0B = c, c, c + challenge1A.RecordSeq = 1 + net := newHandshakeTest() + challenge0A.Node = net.nodeA.n() + challenge0B.Node = net.nodeB.n() + challenge1A.Node = net.nodeA.n() + net.close() + + type testVectorTest struct { + name string // test vector name + packet Packet // the packet to be encoded + challenge *Whoareyou // handshake challenge passed to encoder + prep func(*handshakeTest) // called before encode/decode + } + tests := []testVectorTest{ + { + name: "v5.1-whoareyou", + packet: &challenge0B, + }, + { + name: "v5.1-ping-message", + packet: &Ping{ + ReqID: []byte{0, 0, 0, 1}, + ENRSeq: 2, + }, + prep: func(net *handshakeTest) { + net.nodeA.c.sc.storeNewSession(idB, addr, session) + net.nodeB.c.sc.storeNewSession(idA, addr, session.keysFlipped()) + }, + }, + { + name: "v5.1-ping-handshake-enr", + packet: &Ping{ + ReqID: []byte{0, 0, 0, 1}, + ENRSeq: 1, + }, + challenge: &challenge0A, + prep: func(net *handshakeTest) { + // Update challenge.Header.AuthData. + net.nodeA.c.Encode(idB, "", &challenge0A, nil) + net.nodeB.c.sc.storeSentHandshake(idA, addr, &challenge0A) + }, + }, + { + name: "v5.1-ping-handshake", + packet: &Ping{ + ReqID: []byte{0, 0, 0, 1}, + ENRSeq: 1, + }, + challenge: &challenge1A, + prep: func(net *handshakeTest) { + // Update challenge data. + net.nodeA.c.Encode(idB, "", &challenge1A, nil) + net.nodeB.c.sc.storeSentHandshake(idA, addr, &challenge1A) + }, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + net := newHandshakeTest() + defer net.close() + + // Override all random inputs. + net.nodeA.c.sc.nonceGen = func(counter uint32) (Nonce, error) { + return Nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, nil + } + net.nodeA.c.sc.maskingIVGen = func(buf []byte) error { + return nil // all zero + } + net.nodeA.c.sc.ephemeralKeyGen = func() (*ecdsa.PrivateKey, error) { + return testEphKey, nil + } + + // Prime the codec for encoding/decoding. + if test.prep != nil { + test.prep(net) + } + + file := filepath.Join("testdata", test.name+".txt") + if *writeTestVectorsFlag { + // Encode the packet. + d, nonce := net.nodeA.encodeWithChallenge(t, net.nodeB, test.challenge, test.packet) + comment := testVectorComment(net, test.packet, test.challenge, nonce) + writeTestVector(file, comment, d) + } + enc := hexFile(file) + net.nodeB.expectDecode(t, test.packet.Kind(), enc) + }) + } +} + +// testVectorComment creates the commentary for discv5 test vector files. +func testVectorComment(net *handshakeTest, p Packet, challenge *Whoareyou, nonce Nonce) string { + o := new(strings.Builder) + printWhoareyou := func(p *Whoareyou) { + fmt.Fprintf(o, "whoareyou.challenge-data = %#x\n", p.ChallengeData) + fmt.Fprintf(o, "whoareyou.request-nonce = %#x\n", p.Nonce[:]) + fmt.Fprintf(o, "whoareyou.id-nonce = %#x\n", p.IDNonce[:]) + fmt.Fprintf(o, "whoareyou.enr-seq = %d\n", p.RecordSeq) + } + + fmt.Fprintf(o, "src-node-id = %#x\n", net.nodeA.id().Bytes()) + fmt.Fprintf(o, "dest-node-id = %#x\n", net.nodeB.id().Bytes()) + switch p := p.(type) { + case *Whoareyou: + // WHOAREYOU packet. + printWhoareyou(p) + case *Ping: + fmt.Fprintf(o, "nonce = %#x\n", nonce[:]) + fmt.Fprintf(o, "read-key = %#x\n", net.nodeA.c.sc.session(net.nodeB.id(), net.nodeB.addr()).writeKey) + fmt.Fprintf(o, "ping.req-id = %#x\n", p.ReqID) + fmt.Fprintf(o, "ping.enr-seq = %d\n", p.ENRSeq) + if challenge != nil { + // Handshake message packet. + fmt.Fprint(o, "\nhandshake inputs:\n\n") + printWhoareyou(challenge) + fmt.Fprintf(o, "ephemeral-key = %#x\n", testEphKey.D.Bytes()) + fmt.Fprintf(o, "ephemeral-pubkey = %#x\n", crypto.CompressPubkey(&testEphKey.PublicKey)) + } + default: + panic(fmt.Errorf("unhandled packet type %T", p)) + } + return o.String() +} + +// This benchmark checks performance of handshake packet decoding. +func BenchmarkV5_DecodeHandshakePingSecp256k1(b *testing.B) { + net := newHandshakeTest() + defer net.close() + + var ( + idA = net.nodeA.id() + challenge = &Whoareyou{Node: net.nodeB.n()} + message = &Ping{ReqID: []byte("reqid")} + ) + enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), "", message, challenge) + if err != nil { + b.Fatal("can't encode handshake packet") + } + challenge.Node = nil // force ENR signature verification in decoder + b.ResetTimer() + + input := make([]byte, len(enc)) + for i := 0; i < b.N; i++ { + copy(input, enc) + net.nodeB.c.sc.storeSentHandshake(idA, "", challenge) + _, _, _, err := net.nodeB.c.Decode(input, "") + if err != nil { + b.Fatal(err) + } + } +} + +// This benchmark checks how long it takes to decode an encrypted ping packet. +func BenchmarkV5_DecodePing(b *testing.B) { + net := newHandshakeTest() + defer net.close() + + session := &session{ + readKey: []byte{233, 203, 93, 195, 86, 47, 177, 186, 227, 43, 2, 141, 244, 230, 120, 17}, + writeKey: []byte{79, 145, 252, 171, 167, 216, 252, 161, 208, 190, 176, 106, 214, 39, 178, 134}, + } + net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session) + net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), session.keysFlipped()) + addrB := net.nodeA.addr() + ping := &Ping{ReqID: []byte("reqid"), ENRSeq: 5} + enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), addrB, ping, nil) + if err != nil { + b.Fatalf("can't encode: %v", err) + } + b.ResetTimer() + + input := make([]byte, len(enc)) + for i := 0; i < b.N; i++ { + copy(input, enc) + _, _, packet, _ := net.nodeB.c.Decode(input, addrB) + if _, ok := packet.(*Ping); !ok { + b.Fatalf("wrong packet type %T", packet) + } + } +} + +var pp = spew.NewDefaultConfig() + +type handshakeTest struct { + nodeA, nodeB handshakeTestNode + clock mclock.Simulated +} + +type handshakeTestNode struct { + ln *enode.LocalNode + c *Codec +} + +func newHandshakeTest() *handshakeTest { + t := new(handshakeTest) + t.nodeA.init(testKeyA, net.IP{127, 0, 0, 1}, &t.clock) + t.nodeB.init(testKeyB, net.IP{127, 0, 0, 1}, &t.clock) + return t +} + +func (t *handshakeTest) close() { + t.nodeA.ln.Database().Close() + t.nodeB.ln.Database().Close() +} + +func (n *handshakeTestNode) init(key *ecdsa.PrivateKey, ip net.IP, clock mclock.Clock) { + db, _ := enode.OpenDB("") + n.ln = enode.NewLocalNode(db, key) + n.ln.SetStaticIP(ip) + n.c = NewCodec(n.ln, key, clock) +} + +func (n *handshakeTestNode) encode(t testing.TB, to handshakeTestNode, p Packet) ([]byte, Nonce) { + t.Helper() + return n.encodeWithChallenge(t, to, nil, p) +} + +func (n *handshakeTestNode) encodeWithChallenge(t testing.TB, to handshakeTestNode, c *Whoareyou, p Packet) ([]byte, Nonce) { + t.Helper() + + // Copy challenge and add destination node. This avoids sharing 'c' among the two codecs. + var challenge *Whoareyou + if c != nil { + challengeCopy := *c + challenge = &challengeCopy + challenge.Node = to.n() + } + // Encode to destination. + enc, nonce, err := n.c.Encode(to.id(), to.addr(), p, challenge) + if err != nil { + t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err)) + } + t.Logf("(%s) -> (%s) %s\n%s", n.ln.ID().TerminalString(), to.id().TerminalString(), p.Name(), hex.Dump(enc)) + return enc, nonce +} + +func (n *handshakeTestNode) expectDecode(t *testing.T, ptype byte, p []byte) Packet { + t.Helper() + + dec, err := n.decode(p) + if err != nil { + t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err)) + } + t.Logf("(%s) %#v", n.ln.ID().TerminalString(), pp.NewFormatter(dec)) + if dec.Kind() != ptype { + t.Fatalf("expected packet type %d, got %d", ptype, dec.Kind()) + } + return dec +} + +func (n *handshakeTestNode) expectDecodeErr(t *testing.T, wantErr error, p []byte) { + t.Helper() + if _, err := n.decode(p); !errors.Is(err, wantErr) { + t.Fatal(fmt.Errorf("(%s) got err %q, want %q", n.ln.ID().TerminalString(), err, wantErr)) + } +} + +func (n *handshakeTestNode) decode(input []byte) (Packet, error) { + _, _, p, err := n.c.Decode(input, "127.0.0.1") + return p, err +} + +func (n *handshakeTestNode) n() *enode.Node { + return n.ln.Node() +} + +func (n *handshakeTestNode) addr() string { + return n.ln.Node().IP().String() +} + +func (n *handshakeTestNode) id() enode.ID { + return n.ln.ID() +} + +// hexFile reads the given file and decodes the hex data contained in it. +// Whitespace and any lines beginning with the # character are ignored. +func hexFile(file string) []byte { + fileContent, err := ioutil.ReadFile(file) + if err != nil { + panic(err) + } + + // Gather hex data, ignore comments. + var text []byte + for _, line := range bytes.Split(fileContent, []byte("\n")) { + line = bytes.TrimSpace(line) + if len(line) > 0 && line[0] == '#' { + continue + } + text = append(text, line...) + } + + // Parse the hex. + if bytes.HasPrefix(text, []byte("0x")) { + text = text[2:] + } + data := make([]byte, hex.DecodedLen(len(text))) + if _, err := hex.Decode(data, text); err != nil { + panic("invalid hex in " + file) + } + return data +} + +// writeTestVector writes a test vector file with the given commentary and binary data. +func writeTestVector(file, comment string, data []byte) { + fd, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + panic(err) + } + defer fd.Close() + + if len(comment) > 0 { + for _, line := range strings.Split(strings.TrimSpace(comment), "\n") { + fmt.Fprintf(fd, "# %s\n", line) + } + fmt.Fprintln(fd) + } + for len(data) > 0 { + var chunk []byte + if len(data) < 32 { + chunk = data + } else { + chunk = data[:32] + } + data = data[len(chunk):] + fmt.Fprintf(fd, "%x\n", chunk) + } +} diff --git a/p2p/discover/v5wire/msg.go b/p2p/discover/v5wire/msg.go new file mode 100644 index 0000000000..c596e9dd1d --- /dev/null +++ b/p2p/discover/v5wire/msg.go @@ -0,0 +1,249 @@ +// 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 . + +package v5wire + +import ( + "fmt" + "net" + + "github.com/AlayaNetwork/Alaya-Go/common/mclock" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/rlp" +) + +// Packet is implemented by all message types. +type Packet interface { + Name() string // Name returns a string corresponding to the message type. + Kind() byte // Kind returns the message type. + RequestID() []byte // Returns the request ID. + SetRequestID([]byte) // Sets the request ID. +} + +// Message types. +const ( + PingMsg byte = iota + 1 + PongMsg + FindnodeMsg + NodesMsg + TalkRequestMsg + TalkResponseMsg + RequestTicketMsg + TicketMsg + RegtopicMsg + RegconfirmationMsg + TopicQueryMsg + + UnknownPacket = byte(255) // any non-decryptable packet + WhoareyouPacket = byte(254) // the WHOAREYOU packet +) + +// Protocol messages. +type ( + // Unknown represents any packet that can't be decrypted. + Unknown struct { + Nonce Nonce + } + + // WHOAREYOU contains the handshake challenge. + Whoareyou struct { + ChallengeData []byte // Encoded challenge + Nonce Nonce // Nonce of request packet + IDNonce [16]byte // Identity proof data + RecordSeq uint64 // ENR sequence number of recipient + + // Node is the locally known node record of recipient. + // This must be set by the caller of Encode. + Node *enode.Node + + sent mclock.AbsTime // for handshake GC. + } + + // PING is sent during liveness checks. + Ping struct { + ReqID []byte + ENRSeq uint64 + } + + // PONG is the reply to PING. + Pong struct { + ReqID []byte + ENRSeq uint64 + ToIP net.IP // These fields should mirror the UDP envelope address of the ping + ToPort uint16 // packet, which provides a way to discover the the external address (after NAT). + } + + // FINDNODE is a query for nodes in the given bucket. + Findnode struct { + ReqID []byte + Distances []uint + } + + // NODES is the reply to FINDNODE and TOPICQUERY. + Nodes struct { + ReqID []byte + Total uint8 + Nodes []*enr.Record + } + + // TALKREQ is an application-level request. + TalkRequest struct { + ReqID []byte + Protocol string + Message []byte + } + + // TALKRESP is the reply to TALKREQ. + TalkResponse struct { + ReqID []byte + Message []byte + } + + // REQUESTTICKET requests a ticket for a topic queue. + RequestTicket struct { + ReqID []byte + Topic []byte + } + + // TICKET is the response to REQUESTTICKET. + Ticket struct { + ReqID []byte + Ticket []byte + } + + // REGTOPIC registers the sender in a topic queue using a ticket. + Regtopic struct { + ReqID []byte + Ticket []byte + ENR *enr.Record + } + + // REGCONFIRMATION is the reply to REGTOPIC. + Regconfirmation struct { + ReqID []byte + Registered bool + } + + // TOPICQUERY asks for nodes with the given topic. + TopicQuery struct { + ReqID []byte + Topic []byte + } +) + +// DecodeMessage decodes the message body of a packet. +func DecodeMessage(ptype byte, body []byte) (Packet, error) { + var dec Packet + switch ptype { + case PingMsg: + dec = new(Ping) + case PongMsg: + dec = new(Pong) + case FindnodeMsg: + dec = new(Findnode) + case NodesMsg: + dec = new(Nodes) + case TalkRequestMsg: + dec = new(TalkRequest) + case TalkResponseMsg: + dec = new(TalkResponse) + case RequestTicketMsg: + dec = new(RequestTicket) + case TicketMsg: + dec = new(Ticket) + case RegtopicMsg: + dec = new(Regtopic) + case RegconfirmationMsg: + dec = new(Regconfirmation) + case TopicQueryMsg: + dec = new(TopicQuery) + default: + return nil, fmt.Errorf("unknown packet type %d", ptype) + } + if err := rlp.DecodeBytes(body, dec); err != nil { + return nil, err + } + if dec.RequestID() != nil && len(dec.RequestID()) > 8 { + return nil, ErrInvalidReqID + } + return dec, nil +} + +func (*Whoareyou) Name() string { return "WHOAREYOU/v5" } +func (*Whoareyou) Kind() byte { return WhoareyouPacket } +func (*Whoareyou) RequestID() []byte { return nil } +func (*Whoareyou) SetRequestID([]byte) {} + +func (*Unknown) Name() string { return "UNKNOWN/v5" } +func (*Unknown) Kind() byte { return UnknownPacket } +func (*Unknown) RequestID() []byte { return nil } +func (*Unknown) SetRequestID([]byte) {} + +func (*Ping) Name() string { return "PING/v5" } +func (*Ping) Kind() byte { return PingMsg } +func (p *Ping) RequestID() []byte { return p.ReqID } +func (p *Ping) SetRequestID(id []byte) { p.ReqID = id } + +func (*Pong) Name() string { return "PONG/v5" } +func (*Pong) Kind() byte { return PongMsg } +func (p *Pong) RequestID() []byte { return p.ReqID } +func (p *Pong) SetRequestID(id []byte) { p.ReqID = id } + +func (*Findnode) Name() string { return "FINDNODE/v5" } +func (*Findnode) Kind() byte { return FindnodeMsg } +func (p *Findnode) RequestID() []byte { return p.ReqID } +func (p *Findnode) SetRequestID(id []byte) { p.ReqID = id } + +func (*Nodes) Name() string { return "NODES/v5" } +func (*Nodes) Kind() byte { return NodesMsg } +func (p *Nodes) RequestID() []byte { return p.ReqID } +func (p *Nodes) SetRequestID(id []byte) { p.ReqID = id } + +func (*TalkRequest) Name() string { return "TALKREQ/v5" } +func (*TalkRequest) Kind() byte { return TalkRequestMsg } +func (p *TalkRequest) RequestID() []byte { return p.ReqID } +func (p *TalkRequest) SetRequestID(id []byte) { p.ReqID = id } + +func (*TalkResponse) Name() string { return "TALKRESP/v5" } +func (*TalkResponse) Kind() byte { return TalkResponseMsg } +func (p *TalkResponse) RequestID() []byte { return p.ReqID } +func (p *TalkResponse) SetRequestID(id []byte) { p.ReqID = id } + +func (*RequestTicket) Name() string { return "REQTICKET/v5" } +func (*RequestTicket) Kind() byte { return RequestTicketMsg } +func (p *RequestTicket) RequestID() []byte { return p.ReqID } +func (p *RequestTicket) SetRequestID(id []byte) { p.ReqID = id } + +func (*Regtopic) Name() string { return "REGTOPIC/v5" } +func (*Regtopic) Kind() byte { return RegtopicMsg } +func (p *Regtopic) RequestID() []byte { return p.ReqID } +func (p *Regtopic) SetRequestID(id []byte) { p.ReqID = id } + +func (*Ticket) Name() string { return "TICKET/v5" } +func (*Ticket) Kind() byte { return TicketMsg } +func (p *Ticket) RequestID() []byte { return p.ReqID } +func (p *Ticket) SetRequestID(id []byte) { p.ReqID = id } + +func (*Regconfirmation) Name() string { return "REGCONFIRMATION/v5" } +func (*Regconfirmation) Kind() byte { return RegconfirmationMsg } +func (p *Regconfirmation) RequestID() []byte { return p.ReqID } +func (p *Regconfirmation) SetRequestID(id []byte) { p.ReqID = id } + +func (*TopicQuery) Name() string { return "TOPICQUERY/v5" } +func (*TopicQuery) Kind() byte { return TopicQueryMsg } +func (p *TopicQuery) RequestID() []byte { return p.ReqID } +func (p *TopicQuery) SetRequestID(id []byte) { p.ReqID = id } diff --git a/p2p/discover/v5wire/session.go b/p2p/discover/v5wire/session.go new file mode 100644 index 0000000000..ad11139db0 --- /dev/null +++ b/p2p/discover/v5wire/session.go @@ -0,0 +1,143 @@ +// 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 . + +package v5wire + +import ( + "crypto/ecdsa" + crand "crypto/rand" + "encoding/binary" + "time" + + "github.com/hashicorp/golang-lru/simplelru" + + "github.com/AlayaNetwork/Alaya-Go/common/mclock" + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +const handshakeTimeout = time.Second + +// The SessionCache keeps negotiated encryption keys and +// state for in-progress handshakes in the Discovery v5 wire protocol. +type SessionCache struct { + sessions *simplelru.LRU + handshakes map[sessionID]*Whoareyou + clock mclock.Clock + + // hooks for overriding randomness. + nonceGen func(uint32) (Nonce, error) + maskingIVGen func([]byte) error + ephemeralKeyGen func() (*ecdsa.PrivateKey, error) +} + +// sessionID identifies a session or handshake. +type sessionID struct { + id enode.ID + addr string +} + +// session contains session information +type session struct { + writeKey []byte + readKey []byte + nonceCounter uint32 +} + +// keysFlipped returns a copy of s with the read and write keys flipped. +func (s *session) keysFlipped() *session { + return &session{s.readKey, s.writeKey, s.nonceCounter} +} + +func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache { + cache, err := simplelru.NewLRU(maxItems, nil) + if err != nil { + panic("can't create session cache") + } + return &SessionCache{ + sessions: cache, + handshakes: make(map[sessionID]*Whoareyou), + clock: clock, + nonceGen: generateNonce, + maskingIVGen: generateMaskingIV, + ephemeralKeyGen: crypto.GenerateKey, + } +} + +func generateNonce(counter uint32) (n Nonce, err error) { + binary.BigEndian.PutUint32(n[:4], counter) + _, err = crand.Read(n[4:]) + return n, err +} + +func generateMaskingIV(buf []byte) error { + _, err := crand.Read(buf) + return err +} + +// nextNonce creates a nonce for encrypting a message to the given session. +func (sc *SessionCache) nextNonce(s *session) (Nonce, error) { + s.nonceCounter++ + return sc.nonceGen(s.nonceCounter) +} + +// session returns the current session for the given node, if any. +func (sc *SessionCache) session(id enode.ID, addr string) *session { + item, ok := sc.sessions.Get(sessionID{id, addr}) + if !ok { + return nil + } + return item.(*session) +} + +// readKey returns the current read key for the given node. +func (sc *SessionCache) readKey(id enode.ID, addr string) []byte { + if s := sc.session(id, addr); s != nil { + return s.readKey + } + return nil +} + +// storeNewSession stores new encryption keys in the cache. +func (sc *SessionCache) storeNewSession(id enode.ID, addr string, s *session) { + sc.sessions.Add(sessionID{id, addr}, s) +} + +// getHandshake gets the handshake challenge we previously sent to the given remote node. +func (sc *SessionCache) getHandshake(id enode.ID, addr string) *Whoareyou { + return sc.handshakes[sessionID{id, addr}] +} + +// storeSentHandshake stores the handshake challenge sent to the given remote node. +func (sc *SessionCache) storeSentHandshake(id enode.ID, addr string, challenge *Whoareyou) { + challenge.sent = sc.clock.Now() + sc.handshakes[sessionID{id, addr}] = challenge +} + +// deleteHandshake deletes handshake data for the given node. +func (sc *SessionCache) deleteHandshake(id enode.ID, addr string) { + delete(sc.handshakes, sessionID{id, addr}) +} + +// handshakeGC deletes timed-out handshakes. +func (sc *SessionCache) handshakeGC() { + deadline := sc.clock.Now().Add(-handshakeTimeout) + for key, challenge := range sc.handshakes { + if challenge.sent < deadline { + delete(sc.handshakes, key) + } + } +} diff --git a/p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt b/p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt new file mode 100644 index 0000000000..477f9e15a8 --- /dev/null +++ b/p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt @@ -0,0 +1,27 @@ +# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb +# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9 +# nonce = 0xffffffffffffffffffffffff +# read-key = 0x53b1c075f41876423154e157470c2f48 +# ping.req-id = 0x00000001 +# ping.enr-seq = 1 +# +# handshake inputs: +# +# whoareyou.challenge-data = 0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000 +# whoareyou.request-nonce = 0x0102030405060708090a0b0c +# whoareyou.id-nonce = 0x0102030405060708090a0b0c0d0e0f10 +# whoareyou.enr-seq = 0 +# ephemeral-key = 0x0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6 +# ephemeral-pubkey = 0x039a003ba6517b473fa0cd74aefe99dadfdb34627f90fec6362df85803908f53a5 + +00000000000000000000000000000000088b3d4342774649305f313964a39e55 +ea96c005ad539c8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3 +4c4f53245d08da4bb23698868350aaad22e3ab8dd034f548a1c43cd246be9856 +2fafa0a1fa86d8e7a3b95ae78cc2b988ded6a5b59eb83ad58097252188b902b2 +1481e30e5e285f19735796706adff216ab862a9186875f9494150c4ae06fa4d1 +f0396c93f215fa4ef524e0ed04c3c21e39b1868e1ca8105e585ec17315e755e6 +cfc4dd6cb7fd8e1a1f55e49b4b5eb024221482105346f3c82b15fdaae36a3bb1 +2a494683b4a3c7f2ae41306252fed84785e2bbff3b022812d0882f06978df84a +80d443972213342d04b9048fc3b1d5fcb1df0f822152eced6da4d3f6df27e70e +4539717307a0208cd208d65093ccab5aa596a34d7511401987662d8cf62b1394 +71 diff --git a/p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt b/p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt new file mode 100644 index 0000000000..b3f304766c --- /dev/null +++ b/p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt @@ -0,0 +1,23 @@ +# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb +# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9 +# nonce = 0xffffffffffffffffffffffff +# read-key = 0x4f9fac6de7567d1e3b1241dffe90f662 +# ping.req-id = 0x00000001 +# ping.enr-seq = 1 +# +# handshake inputs: +# +# whoareyou.challenge-data = 0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000001 +# whoareyou.request-nonce = 0x0102030405060708090a0b0c +# whoareyou.id-nonce = 0x0102030405060708090a0b0c0d0e0f10 +# whoareyou.enr-seq = 1 +# ephemeral-key = 0x0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6 +# ephemeral-pubkey = 0x039a003ba6517b473fa0cd74aefe99dadfdb34627f90fec6362df85803908f53a5 + +00000000000000000000000000000000088b3d4342774649305f313964a39e55 +ea96c005ad521d8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3 +4c4f53245d08da4bb252012b2cba3f4f374a90a75cff91f142fa9be3e0a5f3ef +268ccb9065aeecfd67a999e7fdc137e062b2ec4a0eb92947f0d9a74bfbf44dfb +a776b21301f8b65efd5796706adff216ab862a9186875f9494150c4ae06fa4d1 +f0396c93f215fa4ef524f1eadf5f0f4126b79336671cbcf7a885b1f8bd2a5d83 +9cf8 diff --git a/p2p/discover/v5wire/testdata/v5.1-ping-message.txt b/p2p/discover/v5wire/testdata/v5.1-ping-message.txt new file mode 100644 index 0000000000..f82b99c3bc --- /dev/null +++ b/p2p/discover/v5wire/testdata/v5.1-ping-message.txt @@ -0,0 +1,10 @@ +# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb +# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9 +# nonce = 0xffffffffffffffffffffffff +# read-key = 0x00000000000000000000000000000000 +# ping.req-id = 0x00000001 +# ping.enr-seq = 2 + +00000000000000000000000000000000088b3d4342774649325f313964a39e55 +ea96c005ad52be8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3 +4c4f53245d08dab84102ed931f66d1492acb308fa1c6715b9d139b81acbdcc diff --git a/p2p/discover/v5wire/testdata/v5.1-whoareyou.txt b/p2p/discover/v5wire/testdata/v5.1-whoareyou.txt new file mode 100644 index 0000000000..1a75f525ee --- /dev/null +++ b/p2p/discover/v5wire/testdata/v5.1-whoareyou.txt @@ -0,0 +1,9 @@ +# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb +# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9 +# whoareyou.challenge-data = 0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000 +# whoareyou.request-nonce = 0x0102030405060708090a0b0c +# whoareyou.id-nonce = 0x0102030405060708090a0b0c0d0e0f10 +# whoareyou.enr-seq = 0 + +00000000000000000000000000000000088b3d434277464933a1ccc59f5967ad +1d6035f15e528627dde75cd68292f9e6c27d6b66c8100a873fcbaed4e16b8d diff --git a/p2p/discv5/database.go b/p2p/discv5/database.go deleted file mode 100644 index 5435e854a7..0000000000 --- a/p2p/discv5/database.go +++ /dev/null @@ -1,411 +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 . - -// Contains the node database, storing previously seen nodes and any collected -// metadata about them for QoS purposes. - -package discv5 - -import ( - "bytes" - "crypto/rand" - "encoding/binary" - "fmt" - "os" - "sync" - "time" - - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/rlp" - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/errors" - "github.com/syndtr/goleveldb/leveldb/iterator" - "github.com/syndtr/goleveldb/leveldb/opt" - "github.com/syndtr/goleveldb/leveldb/storage" - "github.com/syndtr/goleveldb/leveldb/util" -) - -var ( - nodeDBNilNodeID = NodeID{} // Special node ID to use as a nil element. - nodeDBNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped. - nodeDBCleanupCycle = time.Hour // Time period for running the expiration task. -) - -// nodeDB stores all nodes we know about. -type nodeDB struct { - 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 - quit chan struct{} // Channel to signal the expiring thread to stop -} - -// Schema layout for the node database -var ( - 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" - nodeDBDiscoverLocalEndpoint = nodeDBDiscoverRoot + ":localendpoint" - nodeDBTopicRegTickets = ":tickets" -) - -// 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 == "" { - return newMemoryNodeDB(self) - } - return newPersistentNodeDB(path, version, self) -} - -// newMemoryNodeDB creates a new in-memory node database without a persistent -// backend. -func newMemoryNodeDB(self NodeID) (*nodeDB, error) { - db, err := leveldb.Open(storage.NewMemStorage(), nil) - if err != nil { - return nil, err - } - return &nodeDB{ - lvl: db, - self: self, - quit: make(chan struct{}), - }, nil -} - -// newPersistentNodeDB creates/opens a leveldb backed persistent node database, -// also flushing its contents in case of a version mismatch. -func newPersistentNodeDB(path string, version int, self NodeID) (*nodeDB, error) { - opts := &opt.Options{OpenFilesCacheCapacity: 5} - db, err := leveldb.OpenFile(path, opts) - if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted { - db, err = leveldb.RecoverFile(path, nil) - } - if err != nil { - return nil, err - } - // The nodes contained in the cache correspond to a certain protocol version. - // Flush all nodes if the version doesn't match. - currentVer := make([]byte, binary.MaxVarintLen64) - currentVer = currentVer[:binary.PutVarint(currentVer, int64(version))] - - blob, err := db.Get(nodeDBVersionKey, nil) - switch err { - case leveldb.ErrNotFound: - // Version not found (i.e. empty cache), insert it - if err := db.Put(nodeDBVersionKey, currentVer, nil); err != nil { - db.Close() - return nil, err - } - - case nil: - // Version present, flush if different - if !bytes.Equal(blob, currentVer) { - db.Close() - if err = os.RemoveAll(path); err != nil { - return nil, err - } - return newPersistentNodeDB(path, version, self) - } - } - return &nodeDB{ - lvl: db, - self: self, - quit: make(chan struct{}), - }, nil -} - -// makeKey generates the leveldb key-blob from a node id and its particular -// field of interest. -func makeKey(id NodeID, field string) []byte { - if bytes.Equal(id[:], nodeDBNilNodeID[:]) { - return []byte(field) - } - return append(nodeDBItemPrefix, append(id[:], field...)...) -} - -// splitKey tries to split a database key into a node id and a field part. -func splitKey(key []byte) (id NodeID, field string) { - // If the key is not of a node, return it plainly - if !bytes.HasPrefix(key, nodeDBItemPrefix) { - return NodeID{}, string(key) - } - // Otherwise split the id and field - item := key[len(nodeDBItemPrefix):] - copy(id[:], item[:len(id)]) - field = string(item[len(id):]) - - return id, field -} - -// fetchInt64 retrieves an integer instance associated with a particular -// database key. -func (db *nodeDB) fetchInt64(key []byte) int64 { - blob, err := db.lvl.Get(key, nil) - if err != nil { - return 0 - } - val, read := binary.Varint(blob) - if read <= 0 { - return 0 - } - return val -} - -// storeInt64 update a specific database entry to the current time instance as a -// unix timestamp. -func (db *nodeDB) storeInt64(key []byte, n int64) error { - blob := make([]byte, binary.MaxVarintLen64) - blob = blob[:binary.PutVarint(blob, n)] - return db.lvl.Put(key, blob, nil) -} - -func (db *nodeDB) storeRLP(key []byte, val interface{}) error { - blob, err := rlp.EncodeToBytes(val) - if err != nil { - return err - } - return db.lvl.Put(key, blob, nil) -} - -func (db *nodeDB) fetchRLP(key []byte, val interface{}) error { - blob, err := db.lvl.Get(key, nil) - if err != nil { - return err - } - err = rlp.DecodeBytes(blob, val) - if err != nil { - log.Warn(fmt.Sprintf("key %x (%T) %v", key, val, err)) - } - return err -} - -// node retrieves a node with a given id from the database. -func (db *nodeDB) node(id NodeID) *Node { - var node Node - if err := db.fetchRLP(makeKey(id, nodeDBDiscoverRoot), &node); err != nil { - return nil - } - node.sha = crypto.Keccak256Hash(node.ID[:]) - return &node -} - -// updateNode inserts - potentially overwriting - a node into the peer database. -func (db *nodeDB) updateNode(node *Node) error { - return db.storeRLP(makeKey(node.ID, nodeDBDiscoverRoot), node) -} - -// deleteNode deletes all information/keys associated with a node. -func (db *nodeDB) deleteNode(id NodeID) error { - deleter := db.lvl.NewIterator(util.BytesPrefix(makeKey(id, "")), nil) - for deleter.Next() { - if err := db.lvl.Delete(deleter.Key(), nil); err != nil { - return err - } - } - return nil -} - -// ensureExpirer is a small helper method ensuring that the data expiration -// mechanism is running. If the expiration goroutine is already running, this -// method simply returns. -// -// The goal is to start the data evacuation only after the network successfully -// bootstrapped itself (to prevent dumping potentially useful seed nodes). Since -// it would require significant overhead to exactly trace the first successful -// convergence, it's simpler to "ensure" the correct state when an appropriate -// condition occurs (i.e. a successful bonding), and discard further events. -func (db *nodeDB) ensureExpirer() { - db.runner.Do(func() { go db.expirer() }) -} - -// expirer should be started in a go routine, and is responsible for looping ad -// infinitum and dropping stale data from the database. -func (db *nodeDB) expirer() { - tick := time.NewTicker(nodeDBCleanupCycle) - defer tick.Stop() - for { - select { - case <-tick.C: - if err := db.expireNodes(); err != nil { - log.Error(fmt.Sprintf("Failed to expire nodedb items: %v", err)) - } - case <-db.quit: - return - } - } -} - -// expireNodes iterates over the database and deletes all nodes that have not -// been seen (i.e. received a pong from) for some allotted time. -func (db *nodeDB) expireNodes() error { - threshold := time.Now().Add(-nodeDBNodeExpiration) - - // Find discovered nodes that are older than the allowance - it := db.lvl.NewIterator(nil, nil) - defer it.Release() - - for it.Next() { - // Skip the item if not a discovery node - id, field := splitKey(it.Key()) - if field != nodeDBDiscoverRoot { - continue - } - // Skip the node if not expired yet (and not self) - if !bytes.Equal(id[:], db.self[:]) { - if seen := db.lastPong(id); seen.After(threshold) { - continue - } - } - // Otherwise delete all associated information - db.deleteNode(id) - } - return nil -} - -// lastPing retrieves the time of the last ping packet send to a remote node, -// requesting binding. -func (db *nodeDB) lastPing(id NodeID) time.Time { - return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPing)), 0) -} - -// updateLastPing updates the last time we tried contacting a remote node. -func (db *nodeDB) updateLastPing(id NodeID, instance time.Time) error { - return db.storeInt64(makeKey(id, nodeDBDiscoverPing), instance.Unix()) -} - -// lastPong retrieves the time of the last successful contact from remote node. -func (db *nodeDB) lastPong(id NodeID) time.Time { - return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPong)), 0) -} - -// updateLastPong updates the last time a remote node successfully contacted. -func (db *nodeDB) updateLastPong(id NodeID, instance time.Time) error { - return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix()) -} - -// findFails retrieves the number of findnode failures since bonding. -func (db *nodeDB) findFails(id NodeID) int { - return int(db.fetchInt64(makeKey(id, nodeDBDiscoverFindFails))) -} - -// updateFindFails updates the number of findnode failures since bonding. -func (db *nodeDB) updateFindFails(id NodeID, fails int) error { - return db.storeInt64(makeKey(id, nodeDBDiscoverFindFails), int64(fails)) -} - -// localEndpoint returns the last local endpoint communicated to the -// given remote node. -func (db *nodeDB) localEndpoint(id NodeID) *rpcEndpoint { - var ep rpcEndpoint - if err := db.fetchRLP(makeKey(id, nodeDBDiscoverLocalEndpoint), &ep); err != nil { - return nil - } - return &ep -} - -func (db *nodeDB) updateLocalEndpoint(id NodeID, ep rpcEndpoint) error { - return db.storeRLP(makeKey(id, nodeDBDiscoverLocalEndpoint), &ep) -} - -// querySeeds retrieves random nodes to be used as potential seed nodes -// for bootstrapping. -func (db *nodeDB) querySeeds(n int, maxAge time.Duration) []*Node { - var ( - now = time.Now() - nodes = make([]*Node, 0, n) - it = db.lvl.NewIterator(nil, nil) - id NodeID - ) - defer it.Release() - -seek: - for seeks := 0; len(nodes) < n && seeks < n*5; seeks++ { - // Seek to a random entry. The first byte is incremented by a - // random amount each time in order to increase the likelihood - // of hitting all existing nodes in very small databases. - ctr := id[0] - rand.Read(id[:]) - id[0] = ctr + id[0]%16 - it.Seek(makeKey(id, nodeDBDiscoverRoot)) - - n := nextNode(it) - if n == nil { - id[0] = 0 - continue seek // iterator exhausted - } - if n.ID == db.self { - continue seek - } - if now.Sub(db.lastPong(n.ID)) > maxAge { - continue seek - } - for i := range nodes { - if nodes[i].ID == n.ID { - continue seek // duplicate - } - } - nodes = append(nodes, n) - } - return nodes -} - -func (db *nodeDB) fetchTopicRegTickets(id NodeID) (issued, used uint32) { - key := makeKey(id, nodeDBTopicRegTickets) - blob, _ := db.lvl.Get(key, nil) - if len(blob) != 8 { - return 0, 0 - } - issued = binary.BigEndian.Uint32(blob[0:4]) - used = binary.BigEndian.Uint32(blob[4:8]) - return -} - -func (db *nodeDB) updateTopicRegTickets(id NodeID, issued, used uint32) error { - key := makeKey(id, nodeDBTopicRegTickets) - blob := make([]byte, 8) - binary.BigEndian.PutUint32(blob[0:4], issued) - binary.BigEndian.PutUint32(blob[4:8], used) - return db.lvl.Put(key, blob, nil) -} - -// reads the next node record from the iterator, skipping over other -// database entries. -func nextNode(it iterator.Iterator) *Node { - for end := false; !end; end = !it.Next() { - id, field := splitKey(it.Key()) - if field != nodeDBDiscoverRoot { - continue - } - var n Node - if err := rlp.DecodeBytes(it.Value(), &n); err != nil { - log.Warn(fmt.Sprintf("invalid node %x: %v", id, err)) - continue - } - return &n - } - return nil -} - -// close flushes and closes the database files. -func (db *nodeDB) close() { - close(db.quit) - db.lvl.Close() -} diff --git a/p2p/discv5/database_test.go b/p2p/discv5/database_test.go deleted file mode 100644 index 8d7b409dda..0000000000 --- a/p2p/discv5/database_test.go +++ /dev/null @@ -1,380 +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 . - -package discv5 - -import ( - "bytes" - "io/ioutil" - "net" - "os" - "path/filepath" - "reflect" - "testing" - "time" -) - -var nodeDBKeyTests = []struct { - id NodeID - field string - key []byte -}{ - { - id: NodeID{}, - field: "version", - key: []byte{0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e}, // field - }, - { - id: MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - field: ":discover", - key: []byte{0x6e, 0x3a, // prefix - 0x1d, 0xd9, 0xd6, 0x5c, 0x45, 0x52, 0xb5, 0xeb, // 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, // - 0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, // - 0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, // - 0x3a, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, // field - }, - }, -} - -func TestNodeDBKeys(t *testing.T) { - for i, tt := range nodeDBKeyTests { - 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) - } - id, field := splitKey(tt.key) - if !bytes.Equal(id[:], tt.id[:]) { - t.Errorf("split test %d: id mismatch: have 0x%x, want 0x%x", i, id, tt.id) - } - if field != tt.field { - t.Errorf("split test %d: field mismatch: have 0x%x, want 0x%x", i, field, tt.field) - } - } -} - -var nodeDBInt64Tests = []struct { - key []byte - value int64 -}{ - {key: []byte{0x01}, value: 1}, - {key: []byte{0x02}, value: 2}, - {key: []byte{0x03}, value: 3}, -} - -func TestNodeDBInt64(t *testing.T) { - db, _ := newNodeDB("", Version, NodeID{}) - defer db.close() - - tests := nodeDBInt64Tests - for i := 0; i < len(tests); i++ { - // Insert the next value - if err := db.storeInt64(tests[i].key, tests[i].value); err != nil { - t.Errorf("test %d: failed to store value: %v", i, err) - } - // Check all existing and non existing values - for j := 0; j < len(tests); j++ { - num := db.fetchInt64(tests[j].key) - switch { - case j <= i && num != tests[j].value: - t.Errorf("test %d, item %d: value mismatch: have %v, want %v", i, j, num, tests[j].value) - case j > i && num != 0: - t.Errorf("test %d, item %d: value mismatch: have %v, want %v", i, j, num, 0) - } - } - } -} - -func TestNodeDBFetchStore(t *testing.T) { - node := NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{192, 168, 0, 1}, - 16789, - 16789, - ) - inst := time.Now() - num := 314 - - db, _ := newNodeDB("", Version, NodeID{}) - defer db.close() - - // Check fetch/store operations on a node ping object - if stored := db.lastPing(node.ID); stored.Unix() != 0 { - t.Errorf("ping: non-existing object: %v", stored) - } - if err := db.updateLastPing(node.ID, inst); err != nil { - t.Errorf("ping: failed to update: %v", err) - } - if stored := db.lastPing(node.ID); stored.Unix() != inst.Unix() { - t.Errorf("ping: value mismatch: have %v, want %v", stored, inst) - } - // Check fetch/store operations on a node pong object - if stored := db.lastPong(node.ID); stored.Unix() != 0 { - t.Errorf("pong: non-existing object: %v", stored) - } - if err := db.updateLastPong(node.ID, inst); err != nil { - t.Errorf("pong: failed to update: %v", err) - } - if stored := db.lastPong(node.ID); stored.Unix() != inst.Unix() { - t.Errorf("pong: value mismatch: have %v, want %v", stored, inst) - } - // Check fetch/store operations on a node findnode-failure object - if stored := db.findFails(node.ID); stored != 0 { - t.Errorf("find-node fails: non-existing object: %v", stored) - } - if err := db.updateFindFails(node.ID, num); err != nil { - t.Errorf("find-node fails: failed to update: %v", err) - } - if stored := db.findFails(node.ID); stored != num { - t.Errorf("find-node fails: value mismatch: have %v, want %v", stored, num) - } - // Check fetch/store operations on an actual node object - if stored := db.node(node.ID); stored != nil { - t.Errorf("node: non-existing object: %v", stored) - } - if err := db.updateNode(node); err != nil { - t.Errorf("node: failed to update: %v", err) - } - if stored := db.node(node.ID); stored == nil { - t.Errorf("node: not found") - } else if !reflect.DeepEqual(stored, node) { - t.Errorf("node: data mismatch: have %v, want %v", stored, node) - } -} - -var nodeDBSeedQueryNodes = []struct { - node *Node - pong time.Time -}{ - // This one should not be in the result set because its last - // pong time is too far in the past. - { - node: NewNode( - MustHexID("0x84d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 3}, - 16789, - 16789, - ), - pong: time.Now().Add(-3 * time.Hour), - }, - // This one shouldn't be in in the result set because its - // nodeID is the local node's ID. - { - node: NewNode( - MustHexID("0x57d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 3}, - 16789, - 16789, - ), - pong: time.Now().Add(-4 * time.Second), - }, - - // These should be in the result set. - { - node: NewNode( - MustHexID("0x22d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 1}, - 16789, - 16789, - ), - pong: time.Now().Add(-2 * time.Second), - }, - { - node: NewNode( - MustHexID("0x44d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 2}, - 16789, - 16789, - ), - pong: time.Now().Add(-3 * time.Second), - }, - { - node: NewNode( - MustHexID("0xe2d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 3}, - 16789, - 16789, - ), - pong: time.Now().Add(-1 * time.Second), - }, -} - -func TestNodeDBSeedQuery(t *testing.T) { - db, _ := newNodeDB("", Version, nodeDBSeedQueryNodes[1].node.ID) - defer db.close() - - // Insert a batch of nodes for querying - for i, seed := range nodeDBSeedQueryNodes { - if err := db.updateNode(seed.node); err != nil { - t.Fatalf("node %d: failed to insert: %v", i, err) - } - if err := db.updateLastPong(seed.node.ID, seed.pong); err != nil { - t.Fatalf("node %d: failed to insert lastPong: %v", i, err) - } - } - - // Retrieve the entire batch and check for duplicates - seeds := db.querySeeds(len(nodeDBSeedQueryNodes)*2, time.Hour) - have := make(map[NodeID]struct{}) - for _, seed := range seeds { - have[seed.ID] = struct{}{} - } - want := make(map[NodeID]struct{}) - for _, seed := range nodeDBSeedQueryNodes[2:] { - want[seed.node.ID] = struct{}{} - } - if len(seeds) != len(want) { - t.Errorf("seed count mismatch: have %v, want %v", len(seeds), len(want)) - } - for id := range have { - if _, ok := want[id]; !ok { - t.Errorf("extra seed: %v", id) - } - } - for id := range want { - if _, ok := have[id]; !ok { - t.Errorf("missing seed: %v", id) - } - } -} - -func TestNodeDBPersistency(t *testing.T) { - root, err := ioutil.TempDir("", "nodedb-") - if err != nil { - t.Fatalf("failed to create temporary data folder: %v", err) - } - defer os.RemoveAll(root) - - var ( - testKey = []byte("somekey") - testInt = int64(314) - ) - - // Create a persistent database and store some values - db, err := newNodeDB(filepath.Join(root, "database"), Version, NodeID{}) - if err != nil { - t.Fatalf("failed to create persistent database: %v", err) - } - if err := db.storeInt64(testKey, testInt); err != nil { - t.Fatalf("failed to store value: %v.", err) - } - db.close() - - // Reopen the database and check the value - db, err = newNodeDB(filepath.Join(root, "database"), Version, NodeID{}) - if err != nil { - t.Fatalf("failed to open persistent database: %v", err) - } - if val := db.fetchInt64(testKey); val != testInt { - t.Fatalf("value mismatch: have %v, want %v", val, testInt) - } - 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 { - node *Node - pong time.Time - exp bool -}{ - { - node: NewNode( - MustHexID("0x01d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 1}, - 16789, - 16789, - ), - pong: time.Now().Add(-nodeDBNodeExpiration + time.Minute), - exp: false, - }, { - node: NewNode( - MustHexID("0x02d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 2}, - 16789, - 16789, - ), - pong: time.Now().Add(-nodeDBNodeExpiration - time.Minute), - exp: true, - }, -} - -func TestNodeDBExpiration(t *testing.T) { - db, _ := newNodeDB("", Version, NodeID{}) - 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.updateLastPong(seed.node.ID, seed.pong); err != nil { - t.Fatalf("node %d: failed to update pong: %v", i, err) - } - } - // Expire some of them, and check the rest - if err := db.expireNodes(); err != nil { - t.Fatalf("failed to expire nodes: %v", err) - } - for i, seed := range nodeDBExpirationNodes { - node := db.node(seed.node.ID) - if (node == nil && !seed.exp) || (node != nil && 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.updateLastPong(seed.node.ID, seed.pong); err != nil { - t.Fatalf("node %d: failed to update pong: %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") - } -} diff --git a/p2p/discv5/metrics.go b/p2p/discv5/metrics.go deleted file mode 100644 index 0214f80a7f..0000000000 --- a/p2p/discv5/metrics.go +++ /dev/null @@ -1,8 +0,0 @@ -package discv5 - -import "github.com/AlayaNetwork/Alaya-Go/metrics" - -var ( - ingressTrafficMeter = metrics.NewRegisteredMeter("discv5/InboundTraffic", nil) - egressTrafficMeter = metrics.NewRegisteredMeter("discv5/OutboundTraffic", nil) -) diff --git a/p2p/discv5/net.go b/p2p/discv5/net.go deleted file mode 100644 index 49b1b64552..0000000000 --- a/p2p/discv5/net.go +++ /dev/null @@ -1,1273 +0,0 @@ -// Copyright 2016 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 . - -package discv5 - -import ( - "bytes" - "crypto/ecdsa" - "errors" - "fmt" - "net" - "time" - - "golang.org/x/crypto/sha3" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/common/mclock" - "github.com/AlayaNetwork/Alaya-Go/crypto" - - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -var ( - errInvalidEvent = errors.New("invalid in current state") - errNoQuery = errors.New("no pending query") -) - -const ( - autoRefreshInterval = 1 * time.Hour - bucketRefreshInterval = 1 * time.Minute - seedCount = 30 - seedMaxAge = 5 * 24 * time.Hour - lowPort = 1024 -) - -const testTopic = "foo" - -const ( - printTestImgLogs = false -) - -// Network manages the table and all protocol interaction. -type Network struct { - db *nodeDB // database of known nodes - conn transport - netrestrict *netutil.Netlist - - closed chan struct{} // closed when loop is done - closeReq chan struct{} // 'request to close' - refreshReq chan []*Node // lookups ask for refresh on this channel - refreshResp chan (<-chan struct{}) // ...and get the channel to block on from this one - read chan ingressPacket // ingress packets arrive here - timeout chan timeoutEvent - queryReq chan *findnodeQuery // lookups submit findnode queries on this channel - tableOpReq chan func() - tableOpResp chan struct{} - topicRegisterReq chan topicRegisterReq - topicSearchReq chan topicSearchReq - - // State of the main loop. - tab *Table - topictab *topicTable - ticketStore *ticketStore - nursery []*Node - nodes map[NodeID]*Node // tracks active nodes with state != known - timeoutTimers map[timeoutEvent]*time.Timer - - // Revalidation queues. - // Nodes put on these queues will be pinged eventually. - slowRevalidateQueue []*Node - fastRevalidateQueue []*Node - - // Buffers for state transition. - sendBuf []*ingressPacket -} - -// transport is implemented by the UDP transport. -// it is an interface so we can test without opening lots of UDP -// sockets and without generating a private key. -type transport interface { - sendPing(remote *Node, remoteAddr *net.UDPAddr, topics []Topic) (hash []byte) - sendNeighbours(remote *Node, nodes []*Node) - sendFindnodeHash(remote *Node, target common.Hash) - sendTopicRegister(remote *Node, topics []Topic, topicIdx int, pong []byte) - sendTopicNodes(remote *Node, queryHash common.Hash, nodes []*Node) - - send(remote *Node, ptype nodeEvent, p interface{}) (hash []byte) - - localAddr() *net.UDPAddr - Close() -} - -type findnodeQuery struct { - remote *Node - target common.Hash - reply chan<- []*Node - nresults int // counter for received nodes -} - -type topicRegisterReq struct { - add bool - topic Topic -} - -type topicSearchReq struct { - topic Topic - found chan<- *Node - lookup chan<- bool - delay time.Duration -} - -type topicSearchResult struct { - target lookupInfo - nodes []*Node -} - -type timeoutEvent struct { - ev nodeEvent - node *Node -} - -func newNetwork(conn transport, ourPubkey ecdsa.PublicKey, dbPath string, netrestrict *netutil.Netlist) (*Network, error) { - ourID := PubkeyID(&ourPubkey) - - var db *nodeDB - if dbPath != "" { - var err error - if db, err = newNodeDB(dbPath, Version, ourID); err != nil { - return nil, err - } - } - - tab := newTable(ourID, conn.localAddr()) - net := &Network{ - db: db, - conn: conn, - netrestrict: netrestrict, - tab: tab, - topictab: newTopicTable(db, tab.self), - ticketStore: newTicketStore(), - refreshReq: make(chan []*Node), - refreshResp: make(chan (<-chan struct{})), - closed: make(chan struct{}), - closeReq: make(chan struct{}), - read: make(chan ingressPacket, 100), - timeout: make(chan timeoutEvent), - timeoutTimers: make(map[timeoutEvent]*time.Timer), - tableOpReq: make(chan func()), - tableOpResp: make(chan struct{}), - queryReq: make(chan *findnodeQuery), - topicRegisterReq: make(chan topicRegisterReq), - topicSearchReq: make(chan topicSearchReq), - nodes: make(map[NodeID]*Node), - } - go net.loop() - return net, nil -} - -// Close terminates the network listener and flushes the node database. -func (net *Network) Close() { - net.conn.Close() - select { - case <-net.closed: - case net.closeReq <- struct{}{}: - <-net.closed - } -} - -// Self returns the local node. -// The returned node should not be modified by the caller. -func (net *Network) Self() *Node { - return net.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 (net *Network) ReadRandomNodes(buf []*Node) (n int) { - net.reqTableOp(func() { n = net.tab.readRandomNodes(buf) }) - return n -} - -// SetFallbackNodes sets the initial points of contact. These nodes -// are used to connect to the network if the table is empty and there -// are no known nodes in the database. -func (net *Network) SetFallbackNodes(nodes []*Node) error { - nursery := make([]*Node, 0, len(nodes)) - for _, n := range nodes { - if err := n.validateComplete(); err != nil { - return fmt.Errorf("bad bootstrap/fallback node %q (%v)", n, err) - } - // Recompute cpy.sha because the node might not have been - // created by NewNode or ParseNode. - cpy := *n - cpy.sha = crypto.Keccak256Hash(n.ID[:]) - nursery = append(nursery, &cpy) - } - net.reqRefresh(nursery) - return nil -} - -// Resolve searches for a specific node with the given ID. -// It returns nil if the node could not be found. -func (net *Network) Resolve(targetID NodeID) *Node { - result := net.lookup(crypto.Keccak256Hash(targetID[:]), true) - for _, n := range result { - if n.ID == targetID { - return n - } - } - return nil -} - -// Lookup performs a network search for nodes close -// to the given target. It approaches the target by querying -// nodes that are closer to it on each iteration. -// The given target does not need to be an actual node -// identifier. -// -// The local node may be included in the result. -func (net *Network) Lookup(targetID NodeID) []*Node { - return net.lookup(crypto.Keccak256Hash(targetID[:]), false) -} - -func (net *Network) lookup(target common.Hash, stopOnMatch bool) []*Node { - var ( - asked = make(map[NodeID]bool) - seen = make(map[NodeID]bool) - reply = make(chan []*Node, alpha) - result = nodesByDistance{target: target} - pendingQueries = 0 - ) - // Get initial answers from the local node. - result.push(net.tab.self, bucketSize) - for { - // Ask the α closest nodes that we haven't asked yet. - for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ { - n := result.entries[i] - if !asked[n.ID] { - asked[n.ID] = true - pendingQueries++ - net.reqQueryFindnode(n, target, reply) - } - } - if pendingQueries == 0 { - // We have asked all closest nodes, stop the search. - break - } - // Wait for the next reply. - select { - case nodes := <-reply: - for _, n := range nodes { - if n != nil && !seen[n.ID] { - seen[n.ID] = true - result.push(n, bucketSize) - if stopOnMatch && n.sha == target { - return result.entries - } - } - } - pendingQueries-- - case <-time.After(respTimeout): - // forget all pending requests, start new ones - pendingQueries = 0 - reply = make(chan []*Node, alpha) - } - } - return result.entries -} - -func (net *Network) RegisterTopic(topic Topic, stop <-chan struct{}) { - select { - case net.topicRegisterReq <- topicRegisterReq{true, topic}: - case <-net.closed: - return - } - select { - case <-net.closed: - case <-stop: - select { - case net.topicRegisterReq <- topicRegisterReq{false, topic}: - case <-net.closed: - } - } -} - -func (net *Network) SearchTopic(topic Topic, setPeriod <-chan time.Duration, found chan<- *Node, lookup chan<- bool) { - for { - select { - case <-net.closed: - return - case delay, ok := <-setPeriod: - select { - case net.topicSearchReq <- topicSearchReq{topic: topic, found: found, lookup: lookup, delay: delay}: - case <-net.closed: - return - } - if !ok { - return - } - } - } -} - -func (net *Network) reqRefresh(nursery []*Node) <-chan struct{} { - select { - case net.refreshReq <- nursery: - return <-net.refreshResp - case <-net.closed: - return net.closed - } -} - -func (net *Network) reqQueryFindnode(n *Node, target common.Hash, reply chan []*Node) bool { - q := &findnodeQuery{remote: n, target: target, reply: reply} - select { - case net.queryReq <- q: - return true - case <-net.closed: - return false - } -} - -func (net *Network) reqReadPacket(pkt ingressPacket) { - select { - case net.read <- pkt: - case <-net.closed: - } -} - -func (net *Network) reqTableOp(f func()) (called bool) { - select { - case net.tableOpReq <- f: - <-net.tableOpResp - return true - case <-net.closed: - return false - } -} - -// TODO: external address handling. - -type topicSearchInfo struct { - lookupChn chan<- bool - period time.Duration -} - -const maxSearchCount = 5 - -func (net *Network) loop() { - var ( - refreshTimer = time.NewTicker(autoRefreshInterval) - bucketRefreshTimer = time.NewTimer(bucketRefreshInterval) - refreshDone chan struct{} // closed when the 'refresh' lookup has ended - ) - - // Tracking the next ticket to register. - var ( - nextTicket *ticketRef - nextRegisterTimer *time.Timer - nextRegisterTime <-chan time.Time - ) - defer func() { - if nextRegisterTimer != nil { - nextRegisterTimer.Stop() - } - }() - resetNextTicket := func() { - ticket, timeout := net.ticketStore.nextFilteredTicket() - if nextTicket != ticket { - nextTicket = ticket - if nextRegisterTimer != nil { - nextRegisterTimer.Stop() - nextRegisterTime = nil - } - if ticket != nil { - nextRegisterTimer = time.NewTimer(timeout) - nextRegisterTime = nextRegisterTimer.C - } - } - } - - // Tracking registration and search lookups. - var ( - topicRegisterLookupTarget lookupInfo - topicRegisterLookupDone chan []*Node - topicRegisterLookupTick = time.NewTimer(0) - searchReqWhenRefreshDone []topicSearchReq - searchInfo = make(map[Topic]topicSearchInfo) - activeSearchCount int - ) - topicSearchLookupDone := make(chan topicSearchResult, 100) - topicSearch := make(chan Topic, 100) - <-topicRegisterLookupTick.C - - statsDump := time.NewTicker(10 * time.Second) - -loop: - for { - resetNextTicket() - - select { - case <-net.closeReq: - log.Trace("<-net.closeReq") - break loop - - // Ingress packet handling. - case pkt := <-net.read: - //fmt.Println("read", pkt.ev) - log.Trace("<-net.read") - n := net.internNode(&pkt) - prestate := n.state - status := "ok" - if err := net.handle(n, pkt.ev, &pkt); err != nil { - status = err.Error() - } - log.Trace("", "msg", log.Lazy{Fn: func() string { - return fmt.Sprintf("<<< (%d) %v from %x@%v: %v -> %v (%v)", - net.tab.count, pkt.ev, pkt.remoteID[:8], pkt.remoteAddr, prestate, n.state, status) - }}) - // TODO: persist state if n.state goes >= known, delete if it goes <= known - - // State transition timeouts. - case timeout := <-net.timeout: - log.Trace("<-net.timeout") - if net.timeoutTimers[timeout] == nil { - // Stale timer (was aborted). - continue - } - delete(net.timeoutTimers, timeout) - prestate := timeout.node.state - status := "ok" - if err := net.handle(timeout.node, timeout.ev, nil); err != nil { - status = err.Error() - } - log.Trace("", "msg", log.Lazy{Fn: func() string { - return fmt.Sprintf("--- (%d) %v for %x@%v: %v -> %v (%v)", - net.tab.count, timeout.ev, timeout.node.ID[:8], timeout.node.addr(), prestate, timeout.node.state, status) - }}) - - // Querying. - case q := <-net.queryReq: - log.Trace("<-net.queryReq") - if !q.start(net) { - q.remote.deferQuery(q) - } - - // Interacting with the table. - case f := <-net.tableOpReq: - log.Trace("<-net.tableOpReq") - f() - net.tableOpResp <- struct{}{} - - // Topic registration stuff. - case req := <-net.topicRegisterReq: - log.Trace("<-net.topicRegisterReq") - if !req.add { - net.ticketStore.removeRegisterTopic(req.topic) - continue - } - net.ticketStore.addTopic(req.topic, true) - // If we're currently waiting idle (nothing to look up), give the ticket store a - // chance to start it sooner. This should speed up convergence of the radius - // determination for new topics. - // if topicRegisterLookupDone == nil { - if topicRegisterLookupTarget.target == (common.Hash{}) { - log.Trace("topicRegisterLookupTarget == null") - if topicRegisterLookupTick.Stop() { - <-topicRegisterLookupTick.C - } - target, delay := net.ticketStore.nextRegisterLookup() - topicRegisterLookupTarget = target - topicRegisterLookupTick.Reset(delay) - } - - case nodes := <-topicRegisterLookupDone: - log.Trace("<-topicRegisterLookupDone") - net.ticketStore.registerLookupDone(topicRegisterLookupTarget, nodes, func(n *Node) []byte { - net.ping(n, n.addr()) - return n.pingEcho - }) - target, delay := net.ticketStore.nextRegisterLookup() - topicRegisterLookupTarget = target - topicRegisterLookupTick.Reset(delay) - topicRegisterLookupDone = nil - - case <-topicRegisterLookupTick.C: - log.Trace("<-topicRegisterLookupTick") - if (topicRegisterLookupTarget.target == common.Hash{}) { - target, delay := net.ticketStore.nextRegisterLookup() - topicRegisterLookupTarget = target - topicRegisterLookupTick.Reset(delay) - topicRegisterLookupDone = nil - } else { - topicRegisterLookupDone = make(chan []*Node) - target := topicRegisterLookupTarget.target - go func() { topicRegisterLookupDone <- net.lookup(target, false) }() - } - - case <-nextRegisterTime: - log.Trace("<-nextRegisterTime") - net.ticketStore.ticketRegistered(*nextTicket) - //fmt.Println("sendTopicRegister", nextTicket.t.node.addr().String(), nextTicket.t.topics, nextTicket.idx, nextTicket.t.pong) - net.conn.sendTopicRegister(nextTicket.t.node, nextTicket.t.topics, nextTicket.idx, nextTicket.t.pong) - - case req := <-net.topicSearchReq: - if refreshDone == nil { - log.Trace("<-net.topicSearchReq") - info, ok := searchInfo[req.topic] - if ok { - if req.delay == time.Duration(0) { - delete(searchInfo, req.topic) - net.ticketStore.removeSearchTopic(req.topic) - } else { - info.period = req.delay - searchInfo[req.topic] = info - } - continue - } - if req.delay != time.Duration(0) { - var info topicSearchInfo - info.period = req.delay - info.lookupChn = req.lookup - searchInfo[req.topic] = info - net.ticketStore.addSearchTopic(req.topic, req.found) - topicSearch <- req.topic - } - } else { - searchReqWhenRefreshDone = append(searchReqWhenRefreshDone, req) - } - - case topic := <-topicSearch: - if activeSearchCount < maxSearchCount { - activeSearchCount++ - target := net.ticketStore.nextSearchLookup(topic) - go func() { - nodes := net.lookup(target.target, false) - topicSearchLookupDone <- topicSearchResult{target: target, nodes: nodes} - }() - } - period := searchInfo[topic].period - if period != time.Duration(0) { - go func() { - time.Sleep(period) - topicSearch <- topic - }() - } - - case res := <-topicSearchLookupDone: - activeSearchCount-- - if lookupChn := searchInfo[res.target.topic].lookupChn; lookupChn != nil { - lookupChn <- net.ticketStore.radius[res.target.topic].converged - } - net.ticketStore.searchLookupDone(res.target, res.nodes, func(n *Node, topic Topic) []byte { - if n.state != nil && n.state.canQuery { - return net.conn.send(n, topicQueryPacket, topicQuery{Topic: topic}) // TODO: set expiration - } - if n.state == unknown { - net.ping(n, n.addr()) - } - return nil - }) - - case <-statsDump.C: - log.Trace("<-statsDump.C") - /*r, ok := net.ticketStore.radius[testTopic] - if !ok { - fmt.Printf("(%x) no radius @ %v\n", net.tab.self.ID[:8], time.Now()) - } else { - topics := len(net.ticketStore.tickets) - tickets := len(net.ticketStore.nodes) - rad := r.radius / (maxRadius/10000+1) - fmt.Printf("(%x) topics:%d radius:%d tickets:%d @ %v\n", net.tab.self.ID[:8], topics, rad, tickets, time.Now()) - }*/ - - tm := mclock.Now() - for topic, r := range net.ticketStore.radius { - if printTestImgLogs { - rad := r.radius / (maxRadius/1000000 + 1) - minrad := r.minRadius / (maxRadius/1000000 + 1) - fmt.Printf("*R %d %v %016x %v\n", tm/1000000, topic, net.tab.self.sha[:8], rad) - fmt.Printf("*MR %d %v %016x %v\n", tm/1000000, topic, net.tab.self.sha[:8], minrad) - } - } - for topic, t := range net.topictab.topics { - wp := t.wcl.nextWaitPeriod(tm) - if printTestImgLogs { - fmt.Printf("*W %d %v %016x %d\n", tm/1000000, topic, net.tab.self.sha[:8], wp/1000000) - } - } - - // Periodic / lookup-initiated bucket refresh. - case <-refreshTimer.C: - log.Trace("<-refreshTimer.C") - // TODO: ideally we would start the refresh timer after - // fallback nodes have been set for the first time. - if refreshDone == nil { - refreshDone = make(chan struct{}) - net.refresh(refreshDone) - } - case <-bucketRefreshTimer.C: - target := net.tab.chooseBucketRefreshTarget() - go func() { - net.lookup(target, false) - bucketRefreshTimer.Reset(bucketRefreshInterval) - }() - case newNursery := <-net.refreshReq: - log.Trace("<-net.refreshReq") - if newNursery != nil { - net.nursery = newNursery - } - if refreshDone == nil { - refreshDone = make(chan struct{}) - net.refresh(refreshDone) - } - net.refreshResp <- refreshDone - case <-refreshDone: - log.Trace("<-net.refreshDone", "table size", net.tab.count) - if net.tab.count != 0 { - refreshDone = nil - list := searchReqWhenRefreshDone - searchReqWhenRefreshDone = nil - go func() { - for _, req := range list { - net.topicSearchReq <- req - } - }() - } else { - refreshDone = make(chan struct{}) - net.refresh(refreshDone) - } - } - } - log.Trace("loop stopped") - - log.Debug(fmt.Sprintf("shutting down")) - if net.conn != nil { - net.conn.Close() - } - if refreshDone != nil { - // TODO: wait for pending refresh. - //<-refreshResults - } - // Cancel all pending timeouts. - for _, timer := range net.timeoutTimers { - timer.Stop() - } - if net.db != nil { - net.db.close() - } - close(net.closed) -} - -// Everything below runs on the Network.loop goroutine -// and can modify Node, Table and Network at any time without locking. - -func (net *Network) refresh(done chan<- struct{}) { - var seeds []*Node - if net.db != nil { - seeds = net.db.querySeeds(seedCount, seedMaxAge) - } - if len(seeds) == 0 { - seeds = net.nursery - } - if len(seeds) == 0 { - log.Trace("no seed nodes found") - time.AfterFunc(time.Second*10, func() { close(done) }) - return - } - for _, n := range seeds { - log.Debug("", "msg", log.Lazy{Fn: func() string { - var age string - if net.db != nil { - age = time.Since(net.db.lastPong(n.ID)).String() - } else { - age = "unknown" - } - return fmt.Sprintf("seed node (age %s): %v", age, n) - }}) - n = net.internNodeFromDB(n) - if n.state == unknown { - net.transition(n, verifyinit) - } - // Force-add the seed node so Lookup does something. - // It will be deleted again if verification fails. - net.tab.add(n) - } - // Start self lookup to fill up the buckets. - go func() { - net.Lookup(net.tab.self.ID) - close(done) - }() -} - -// Node Interning. - -func (net *Network) internNode(pkt *ingressPacket) *Node { - if n := net.nodes[pkt.remoteID]; n != nil { - n.IP = pkt.remoteAddr.IP - n.UDP = uint16(pkt.remoteAddr.Port) - n.TCP = uint16(pkt.remoteAddr.Port) - return n - } - n := NewNode(pkt.remoteID, pkt.remoteAddr.IP, uint16(pkt.remoteAddr.Port), uint16(pkt.remoteAddr.Port)) - n.state = unknown - net.nodes[pkt.remoteID] = n - return n -} - -func (net *Network) internNodeFromDB(dbn *Node) *Node { - if n := net.nodes[dbn.ID]; n != nil { - return n - } - n := NewNode(dbn.ID, dbn.IP, dbn.UDP, dbn.TCP) - n.state = unknown - net.nodes[n.ID] = n - return n -} - -func (net *Network) internNodeFromNeighbours(sender *net.UDPAddr, rn rpcNode) (n *Node, err error) { - if rn.ID == net.tab.self.ID { - return nil, errors.New("is self") - } - if rn.UDP <= lowPort { - return nil, errors.New("low port") - } - n = net.nodes[rn.ID] - if n == nil { - // We haven't seen this node before. - n, err = nodeFromRPC(sender, rn) - if net.netrestrict != nil && !net.netrestrict.Contains(n.IP) { - return n, errors.New("not contained in netrestrict whitelist") - } - if err == nil { - n.state = unknown - net.nodes[n.ID] = n - } - return n, err - } - if !n.IP.Equal(rn.IP) || n.UDP != rn.UDP || n.TCP != rn.TCP { - if n.state == known { - // reject address change if node is known by us - err = fmt.Errorf("metadata mismatch: got %v, want %v", rn, n) - } else { - // accept otherwise; this will be handled nicer with signed ENRs - n.IP = rn.IP - n.UDP = rn.UDP - n.TCP = rn.TCP - } - } - return n, err -} - -// nodeNetGuts is embedded in Node and contains fields. -type nodeNetGuts struct { - // 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 - - // State machine fields. Access to these fields - // is restricted to the Network.loop goroutine. - state *nodeState - pingEcho []byte // hash of last ping sent by us - pingTopics []Topic // topic set sent by us in last ping - deferredQueries []*findnodeQuery // queries that can't be sent yet - pendingNeighbours *findnodeQuery // current query, waiting for reply - queryTimeouts int -} - -func (n *nodeNetGuts) deferQuery(q *findnodeQuery) { - n.deferredQueries = append(n.deferredQueries, q) -} - -func (n *nodeNetGuts) startNextQuery(net *Network) { - if len(n.deferredQueries) == 0 { - return - } - nextq := n.deferredQueries[0] - if nextq.start(net) { - n.deferredQueries = append(n.deferredQueries[:0], n.deferredQueries[1:]...) - } -} - -func (q *findnodeQuery) start(net *Network) bool { - // Satisfy queries against the local node directly. - if q.remote == net.tab.self { - closest := net.tab.closest(q.target, bucketSize) - q.reply <- closest.entries - return true - } - if q.remote.state.canQuery && q.remote.pendingNeighbours == nil { - net.conn.sendFindnodeHash(q.remote, q.target) - net.timedEvent(respTimeout, q.remote, neighboursTimeout) - q.remote.pendingNeighbours = q - return true - } - // If the node is not known yet, it won't accept queries. - // Initiate the transition to known. - // The request will be sent later when the node reaches known state. - if q.remote.state == unknown { - net.transition(q.remote, verifyinit) - } - return false -} - -// Node Events (the input to the state machine). - -type nodeEvent uint - -//go:generate stringer -type=nodeEvent - -const ( - - // Packet type events. - // These correspond to packet types in the UDP protocol. - pingPacket = iota + 1 - pongPacket - findnodePacket - neighborsPacket - findnodeHashPacket - topicRegisterPacket - topicQueryPacket - topicNodesPacket - - // Non-packet events. - // Event values in this category are allocated outside - // the packet type range (packet types are encoded as a single byte). - pongTimeout nodeEvent = iota + 256 - pingTimeout - neighboursTimeout -) - -// Node State Machine. - -type nodeState struct { - name string - handle func(*Network, *Node, nodeEvent, *ingressPacket) (next *nodeState, err error) - enter func(*Network, *Node) - canQuery bool -} - -func (s *nodeState) String() string { - return s.name -} - -var ( - unknown *nodeState - verifyinit *nodeState - verifywait *nodeState - remoteverifywait *nodeState - known *nodeState - contested *nodeState - unresponsive *nodeState -) - -func init() { - unknown = &nodeState{ - name: "unknown", - enter: func(net *Network, n *Node) { - net.tab.delete(n) - n.pingEcho = nil - // Abort active queries. - for _, q := range n.deferredQueries { - q.reply <- nil - } - n.deferredQueries = nil - if n.pendingNeighbours != nil { - n.pendingNeighbours.reply <- nil - n.pendingNeighbours = nil - } - n.queryTimeouts = 0 - }, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - net.ping(n, pkt.remoteAddr) - return verifywait, nil - default: - return unknown, errInvalidEvent - } - }, - } - - verifyinit = &nodeState{ - name: "verifyinit", - enter: func(net *Network, n *Node) { - net.ping(n, n.addr()) - }, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - return verifywait, nil - case pongPacket: - err := net.handleKnownPong(n, pkt) - return remoteverifywait, err - case pongTimeout: - return unknown, nil - default: - return verifyinit, errInvalidEvent - } - }, - } - - verifywait = &nodeState{ - name: "verifywait", - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - return verifywait, nil - case pongPacket: - err := net.handleKnownPong(n, pkt) - return known, err - case pongTimeout: - return unknown, nil - default: - return verifywait, errInvalidEvent - } - }, - } - - remoteverifywait = &nodeState{ - name: "remoteverifywait", - enter: func(net *Network, n *Node) { - net.timedEvent(respTimeout, n, pingTimeout) - }, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - return remoteverifywait, nil - case pingTimeout: - return known, nil - default: - return remoteverifywait, errInvalidEvent - } - }, - } - - known = &nodeState{ - name: "known", - canQuery: true, - enter: func(net *Network, n *Node) { - n.queryTimeouts = 0 - n.startNextQuery(net) - // Insert into the table and start revalidation of the last node - // in the bucket if it is full. - last := net.tab.add(n) - if last != nil && last.state == known { - // TODO: do this asynchronously - net.transition(last, contested) - } - }, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - return known, nil - case pongPacket: - err := net.handleKnownPong(n, pkt) - return known, err - default: - return net.handleQueryEvent(n, ev, pkt) - } - }, - } - - contested = &nodeState{ - name: "contested", - canQuery: true, - enter: func(net *Network, n *Node) { - net.ping(n, n.addr()) - }, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pongPacket: - // Node is still alive. - err := net.handleKnownPong(n, pkt) - return known, err - case pongTimeout: - net.tab.deleteReplace(n) - return unresponsive, nil - case pingPacket: - net.handlePing(n, pkt) - return contested, nil - default: - return net.handleQueryEvent(n, ev, pkt) - } - }, - } - - unresponsive = &nodeState{ - name: "unresponsive", - canQuery: true, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - return known, nil - case pongPacket: - err := net.handleKnownPong(n, pkt) - return known, err - default: - return net.handleQueryEvent(n, ev, pkt) - } - }, - } -} - -// handle processes packets sent by n and events related to n. -func (net *Network) handle(n *Node, ev nodeEvent, pkt *ingressPacket) error { - //fmt.Println("handle", n.addr().String(), n.state, ev) - if pkt != nil { - if err := net.checkPacket(n, ev, pkt); err != nil { - //fmt.Println("check err:", err) - return err - } - // Start the background expiration goroutine after the first - // successful communication. Subsequent calls have no effect if it - // is already running. We do this here instead of somewhere else - // so that the search for seed nodes also considers older nodes - // that would otherwise be removed by the expirer. - if net.db != nil { - net.db.ensureExpirer() - } - } - if n.state == nil { - n.state = unknown //??? - } - next, err := n.state.handle(net, n, ev, pkt) - net.transition(n, next) - //fmt.Println("new state:", n.state) - return err -} - -func (net *Network) checkPacket(n *Node, ev nodeEvent, pkt *ingressPacket) error { - // Replay prevention checks. - switch ev { - case pingPacket, findnodeHashPacket, neighborsPacket: - // TODO: check date is > last date seen - // TODO: check ping version - case pongPacket: - if !bytes.Equal(pkt.data.(*pong).ReplyTok, n.pingEcho) { - // fmt.Println("pong reply token mismatch") - return fmt.Errorf("pong reply token mismatch") - } - n.pingEcho = nil - } - // Address validation. - // TODO: Ideally we would do the following: - // - reject all packets with wrong address except ping. - // - for ping with new address, transition to verifywait but keep the - // previous node (with old address) around. if the new one reaches known, - // swap it out. - return nil -} - -func (net *Network) transition(n *Node, next *nodeState) { - if n.state != next { - n.state = next - if next.enter != nil { - next.enter(net, n) - } - } - - // TODO: persist/unpersist node -} - -func (net *Network) timedEvent(d time.Duration, n *Node, ev nodeEvent) { - timeout := timeoutEvent{ev, n} - net.timeoutTimers[timeout] = time.AfterFunc(d, func() { - select { - case net.timeout <- timeout: - case <-net.closed: - } - }) -} - -func (net *Network) abortTimedEvent(n *Node, ev nodeEvent) { - timer := net.timeoutTimers[timeoutEvent{ev, n}] - if timer != nil { - timer.Stop() - delete(net.timeoutTimers, timeoutEvent{ev, n}) - } -} - -func (net *Network) ping(n *Node, addr *net.UDPAddr) { - //fmt.Println("ping", n.addr().String(), n.ID.String(), n.sha.Hex()) - if n.pingEcho != nil || n.ID == net.tab.self.ID { - //fmt.Println(" not sent") - return - } - log.Trace("Pinging remote node", "node", n.ID) - n.pingTopics = net.ticketStore.regTopicSet() - n.pingEcho = net.conn.sendPing(n, addr, n.pingTopics) - net.timedEvent(respTimeout, n, pongTimeout) -} - -func (net *Network) handlePing(n *Node, pkt *ingressPacket) { - log.Trace("Handling remote ping", "node", n.ID) - ping := pkt.data.(*ping) - n.TCP = ping.From.TCP - t := net.topictab.getTicket(n, ping.Topics) - - pong := &pong{ - To: makeEndpoint(n.addr(), n.TCP), // TODO: maybe use known TCP port from DB - ReplyTok: pkt.hash, - Expiration: uint64(time.Now().Add(expiration).Unix()), - } - ticketToPong(t, pong) - net.conn.send(n, pongPacket, pong) -} - -func (net *Network) handleKnownPong(n *Node, pkt *ingressPacket) error { - log.Trace("Handling known pong", "node", n.ID) - net.abortTimedEvent(n, pongTimeout) - now := mclock.Now() - ticket, err := pongToTicket(now, n.pingTopics, n, pkt) - if err == nil { - // fmt.Printf("(%x) ticket: %+v\n", net.tab.self.ID[:8], pkt.data) - net.ticketStore.addTicket(now, pkt.data.(*pong).ReplyTok, ticket) - } else { - log.Trace("Failed to convert pong to ticket", "err", err) - } - n.pingEcho = nil - n.pingTopics = nil - return err -} - -func (net *Network) handleQueryEvent(n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case findnodePacket: - target := crypto.Keccak256Hash(pkt.data.(*findnode).Target[:]) - results := net.tab.closest(target, bucketSize).entries - net.conn.sendNeighbours(n, results) - return n.state, nil - case neighborsPacket: - err := net.handleNeighboursPacket(n, pkt) - return n.state, err - case neighboursTimeout: - if n.pendingNeighbours != nil { - n.pendingNeighbours.reply <- nil - n.pendingNeighbours = nil - } - n.queryTimeouts++ - if n.queryTimeouts > maxFindnodeFailures && n.state == known { - return contested, errors.New("too many timeouts") - } - return n.state, nil - - // v5 - - case findnodeHashPacket: - results := net.tab.closest(pkt.data.(*findnodeHash).Target, bucketSize).entries - net.conn.sendNeighbours(n, results) - return n.state, nil - case topicRegisterPacket: - //fmt.Println("got topicRegisterPacket") - regdata := pkt.data.(*topicRegister) - pong, err := net.checkTopicRegister(regdata) - if err != nil { - //fmt.Println(err) - return n.state, fmt.Errorf("bad waiting ticket: %v", err) - } - net.topictab.useTicket(n, pong.TicketSerial, regdata.Topics, int(regdata.Idx), pong.Expiration, pong.WaitPeriods) - return n.state, nil - case topicQueryPacket: - // TODO: handle expiration - topic := pkt.data.(*topicQuery).Topic - results := net.topictab.getEntries(topic) - if _, ok := net.ticketStore.tickets[topic]; ok { - results = append(results, net.tab.self) // we're not registering in our own table but if we're advertising, return ourselves too - } - if len(results) > 10 { - results = results[:10] - } - var hash common.Hash - copy(hash[:], pkt.hash) - net.conn.sendTopicNodes(n, hash, results) - return n.state, nil - case topicNodesPacket: - p := pkt.data.(*topicNodes) - if net.ticketStore.gotTopicNodes(n, p.Echo, p.Nodes) { - n.queryTimeouts++ - if n.queryTimeouts > maxFindnodeFailures && n.state == known { - return contested, errors.New("too many timeouts") - } - } - return n.state, nil - - default: - return n.state, errInvalidEvent - } -} - -func (net *Network) checkTopicRegister(data *topicRegister) (*pong, error) { - var pongpkt ingressPacket - if err := decodePacket(data.Pong, &pongpkt); err != nil { - return nil, err - } - if pongpkt.ev != pongPacket { - return nil, errors.New("is not pong packet") - } - if pongpkt.remoteID != net.tab.self.ID { - return nil, errors.New("not signed by us") - } - // check that we previously authorised all topics - // that the other side is trying to register. - if rlpHash(data.Topics) != pongpkt.data.(*pong).TopicHash { - return nil, errors.New("topic hash mismatch") - } - if data.Idx >= uint(len(data.Topics)) { - return nil, errors.New("topic index out of range") - } - return pongpkt.data.(*pong), nil -} - -func rlpHash(x interface{}) (h common.Hash) { - hw := sha3.NewLegacyKeccak256() - rlp.Encode(hw, x) - hw.Sum(h[:0]) - return h -} - -func (net *Network) handleNeighboursPacket(n *Node, pkt *ingressPacket) error { - if n.pendingNeighbours == nil { - return errNoQuery - } - net.abortTimedEvent(n, neighboursTimeout) - - req := pkt.data.(*neighbors) - nodes := make([]*Node, len(req.Nodes)) - for i, rn := range req.Nodes { - nn, err := net.internNodeFromNeighbours(pkt.remoteAddr, rn) - if err != nil { - log.Debug(fmt.Sprintf("invalid neighbour (%v) from %x@%v: %v", rn.IP, n.ID[:8], pkt.remoteAddr, err)) - continue - } - nodes[i] = nn - // Start validation of query results immediately. - // This fills the table quickly. - // TODO: generates way too many packets, maybe do it via queue. - if nn.state == unknown { - net.transition(nn, verifyinit) - } - } - // TODO: don't ignore second packet - n.pendingNeighbours.reply <- nodes - n.pendingNeighbours = nil - // Now that this query is done, start the next one. - n.startNextQuery(net) - return nil -} diff --git a/p2p/discv5/net_test.go b/p2p/discv5/net_test.go deleted file mode 100644 index f71c1265c0..0000000000 --- a/p2p/discv5/net_test.go +++ /dev/null @@ -1,374 +0,0 @@ -// Copyright 2016 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 . - -package discv5 - -import ( - "fmt" - "net" - "testing" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/crypto" -) - -func TestNetwork_Lookup(t *testing.T) { - key, _ := crypto.GenerateKey() - network, err := newNetwork(lookupTestnet, key.PublicKey, "", nil) - if err != nil { - t.Fatal(err) - } - lookupTestnet.net = network - defer network.Close() - - // lookup on empty table returns no nodes - // if results := network.Lookup(lookupTestnet.target, false); 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) - seeds := []*Node{NewNode(lookupTestnet.dists[256][0], net.IP{10, 0, 2, 99}, lowPort+256, 999)} - if err := network.SetFallbackNodes(seeds); err != nil { - t.Fatal(err) - } - time.Sleep(3 * time.Second) - - results := network.Lookup(lookupTestnet.target) - t.Logf("results:") - for _, e := range results { - t.Logf(" ld=%d, %x", logdist(lookupTestnet.targetSha, e.sha), e.sha[:]) - } - if len(results) != bucketSize { - t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize) - } - if hasDuplicates(results) { - t.Errorf("result set contains duplicate entries") - } - if !sortedByDistanceTo(lookupTestnet.targetSha, results) { - t.Errorf("result set not sorted by distance to target") - } - // TODO: check result nodes are actually closest -} - -// This is the test network for the Lookup test. -// The nodes were obtained by running testnet.mine with a random NodeID as target. -var lookupTestnet = &preminedTestnet{ - target: MustHexID("166aea4f556532c6d34e8b740e5d314af7e9ac0ca79833bd751d6b665f12dfd38ec563c363b32f02aef4a80b44fd3def94612d497b99cb5f17fd24de454927ec"), - 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][]NodeID{ - 240: { - MustHexID("2001ad5e3e80c71b952161bc0186731cf5ffe942d24a79230a0555802296238e57ea7a32f5b6f18564eadc1c65389448481f8c9338df0a3dbd18f708cbc2cbcb"), - MustHexID("6ba3f4f57d084b6bf94cc4555b8c657e4a8ac7b7baf23c6874efc21dd1e4f56b7eb2721e07f5242d2f1d8381fc8cae535e860197c69236798ba1ad231b105794"), - }, - 244: { - MustHexID("696ba1f0a9d55c59246f776600542a9e6432490f0cd78f8bb55a196918df2081a9b521c3c3ba48e465a75c10768807717f8f689b0b4adce00e1c75737552a178"), - }, - 246: { - MustHexID("d6d32178bdc38416f46ffb8b3ec9e4cb2cfff8d04dd7e4311a70e403cb62b10be1b447311b60b4f9ee221a8131fc2cbd45b96dd80deba68a949d467241facfa8"), - MustHexID("3ea3d04a43a3dfb5ac11cffc2319248cf41b6279659393c2f55b8a0a5fc9d12581a9d97ef5d8ff9b5abf3321a290e8f63a4f785f450dc8a672aba3ba2ff4fdab"), - MustHexID("2fc897f05ae585553e5c014effd3078f84f37f9333afacffb109f00ca8e7a3373de810a3946be971cbccdfd40249f9fe7f322118ea459ac71acca85a1ef8b7f4"), - }, - 247: { - MustHexID("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"), - MustHexID("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"), - MustHexID("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"), - MustHexID("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"), - MustHexID("8b58c6073dd98bbad4e310b97186c8f822d3a5c7d57af40e2136e88e315afd115edb27d2d0685a908cfe5aa49d0debdda6e6e63972691d6bd8c5af2d771dd2a9"), - MustHexID("2cbb718b7dc682da19652e7d9eb4fefaf7b7147d82c1c2b6805edf77b85e29fde9f6da195741467ff2638dc62c8d3e014ea5686693c15ed0080b6de90354c137"), - MustHexID("e84027696d3f12f2de30a9311afea8fbd313c2360daff52bb5fc8c7094d5295758bec3134e4eef24e4cdf377b40da344993284628a7a346eba94f74160998feb"), - MustHexID("f1357a4f04f9d33753a57c0b65ba20a5d8777abbffd04e906014491c9103fb08590e45548d37aa4bd70965e2e81ddba94f31860348df01469eec8c1829200a68"), - MustHexID("4ab0a75941b12892369b4490a1928c8ca52a9ad6d3dffbd1d8c0b907bc200fe74c022d011ec39b64808a39c0ca41f1d3254386c3e7733e7044c44259486461b6"), - MustHexID("d45150a72dc74388773e68e03133a3b5f51447fe91837d566706b3c035ee4b56f160c878c6273394daee7f56cc398985269052f22f75a8057df2fe6172765354"), - }, - 248: { - MustHexID("6aadfce366a189bab08ac84721567483202c86590642ea6d6a14f37ca78d82bdb6509eb7b8b2f6f63c78ae3ae1d8837c89509e41497d719b23ad53dd81574afa"), - MustHexID("a605ecfd6069a4cf4cf7f5840e5bc0ce10d23a3ac59e2aaa70c6afd5637359d2519b4524f56fc2ca180cdbebe54262f720ccaae8c1b28fd553c485675831624d"), - MustHexID("29701451cb9448ca33fc33680b44b840d815be90146eb521641efbffed0859c154e8892d3906eae9934bfacee72cd1d2fa9dd050fd18888eea49da155ab0efd2"), - MustHexID("3ed426322dee7572b08592e1e079f8b6c6b30e10e6243edd144a6a48fdbdb83df73a6e41b1143722cb82604f2203a32758610b5d9544f44a1a7921ba001528c1"), - MustHexID("b2e2a2b7fdd363572a3256e75435fab1da3b16f7891a8bd2015f30995dae665d7eabfd194d87d99d5df628b4bbc7b04e5b492c596422dd8272746c7a1b0b8e4f"), - MustHexID("0c69c9756162c593e85615b814ce57a2a8ca2df6c690b9c4e4602731b61e1531a3bbe3f7114271554427ffabea80ad8f36fa95a49fa77b675ae182c6ccac1728"), - MustHexID("8d28be21d5a97b0876442fa4f5e5387f5bf3faad0b6f13b8607b64d6e448c0991ca28dd7fe2f64eb8eadd7150bff5d5666aa6ed868b84c71311f4ba9a38569dd"), - MustHexID("2c677e1c64b9c9df6359348a7f5f33dc79e22f0177042486d125f8b6ca7f0dc756b1f672aceee5f1746bcff80aaf6f92a8dc0c9fbeb259b3fa0da060de5ab7e8"), - MustHexID("3994880f94a8678f0cd247a43f474a8af375d2a072128da1ad6cae84a244105ff85e94fc7d8496f639468de7ee998908a91c7e33ef7585fff92e984b210941a1"), - MustHexID("b45a9153c08d002a48090d15d61a7c7dad8c2af85d4ff5bd36ce23a9a11e0709bf8d56614c7b193bc028c16cbf7f20dfbcc751328b64a924995d47b41e452422"), - MustHexID("057ab3a9e53c7a84b0f3fc586117a525cdd18e313f52a67bf31798d48078e325abe5cfee3f6c2533230cb37d0549289d692a29dd400e899b8552d4b928f6f907"), - MustHexID("0ddf663d308791eb92e6bd88a2f8cb45e4f4f35bb16708a0e6ff7f1362aa6a73fedd0a1b1557fb3365e38e1b79d6918e2fae2788728b70c9ab6b51a3b94a4338"), - MustHexID("f637e07ff50cc1e3731735841c4798411059f2023abcf3885674f3e8032531b0edca50fd715df6feb489b6177c345374d64f4b07d257a7745de393a107b013a5"), - MustHexID("e24ec7c6eec094f63c7b3239f56d311ec5a3e45bc4e622a1095a65b95eea6fe13e29f3b6b7a2cbfe40906e3989f17ac834c3102dd0cadaaa26e16ee06d782b72"), - MustHexID("b76ea1a6fd6506ef6e3506a4f1f60ed6287fff8114af6141b2ff13e61242331b54082b023cfea5b3083354a4fb3f9eb8be01fb4a518f579e731a5d0707291a6b"), - MustHexID("9b53a37950ca8890ee349b325032d7b672cab7eced178d3060137b24ef6b92a43977922d5bdfb4a3409a2d80128e02f795f9dae6d7d99973ad0e23a2afb8442f"), - }, - 249: { - MustHexID("675ae65567c3c72c50c73bc0fd4f61f202ea5f93346ca57b551de3411ccc614fad61cb9035493af47615311b9d44ee7a161972ee4d77c28fe1ec029d01434e6a"), - MustHexID("8eb81408389da88536ae5800392b16ef5109d7ea132c18e9a82928047ecdb502693f6e4a4cdd18b54296caf561db937185731456c456c98bfe7de0baf0eaa495"), - MustHexID("2adba8b1612a541771cb93a726a38a4b88e97b18eced2593eb7daf82f05a5321ca94a72cc780c306ff21e551a932fc2c6d791e4681907b5ceab7f084c3fa2944"), - MustHexID("b1b4bfbda514d9b8f35b1c28961da5d5216fe50548f4066f69af3b7666a3b2e06eac646735e963e5c8f8138a2fb95af15b13b23ff00c6986eccc0efaa8ee6fb4"), - MustHexID("d2139281b289ad0e4d7b4243c4364f5c51aac8b60f4806135de06b12b5b369c9e43a6eb494eab860d115c15c6fbb8c5a1b0e382972e0e460af395b8385363de7"), - MustHexID("4a693df4b8fc5bdc7cec342c3ed2e228d7c5b4ab7321ddaa6cccbeb45b05a9f1d95766b4002e6d4791c2deacb8a667aadea6a700da28a3eea810a30395701bbc"), - MustHexID("ab41611195ec3c62bb8cd762ee19fb182d194fd141f4a66780efbef4b07ce916246c022b841237a3a6b512a93431157edd221e854ed2a259b72e9c5351f44d0c"), - MustHexID("68e8e26099030d10c3c703ae7045c0a48061fb88058d853b3e67880014c449d4311014da99d617d3150a20f1a3da5e34bf0f14f1c51fe4dd9d58afd222823176"), - MustHexID("3fbcacf546fb129cd70fc48de3b593ba99d3c473798bc309292aca280320e0eacc04442c914cad5c4cf6950345ba79b0d51302df88285d4e83ee3fe41339eee7"), - MustHexID("1d4a623659f7c8f80b6c3939596afdf42e78f892f682c768ad36eb7bfba402dbf97aea3a268f3badd8fe7636be216edf3d67ee1e08789ebbc7be625056bd7109"), - MustHexID("a283c474ab09da02bbc96b16317241d0627646fcc427d1fe790b76a7bf1989ced90f92101a973047ae9940c92720dffbac8eff21df8cae468a50f72f9e159417"), - MustHexID("dbf7e5ad7f87c3dfecae65d87c3039e14ed0bdc56caf00ce81931073e2e16719d746295512ff7937a15c3b03603e7c41a4f9df94fcd37bb200dd8f332767e9cb"), - MustHexID("caaa070a26692f64fc77f30d7b5ae980d419b4393a0f442b1c821ef58c0862898b0d22f74a4f8c5d83069493e3ec0b92f17dc1fe6e4cd437c1ec25039e7ce839"), - MustHexID("874cc8d1213beb65c4e0e1de38ef5d8165235893ac74ab5ea937c885eaab25c8d79dad0456e9fd3e9450626cac7e107b004478fb59842f067857f39a47cee695"), - MustHexID("d94193f236105010972f5df1b7818b55846592a0445b9cdc4eaed811b8c4c0f7c27dc8cc9837a4774656d6b34682d6d329d42b6ebb55da1d475c2474dc3dfdf4"), - MustHexID("edd9af6aded4094e9785637c28fccbd3980cbe28e2eb9a411048a23c2ace4bd6b0b7088a7817997b49a3dd05fc6929ca6c7abbb69438dbdabe65e971d2a794b2"), - }, - 250: { - MustHexID("53a5bd1215d4ab709ae8fdc2ced50bba320bced78bd9c5dc92947fb402250c914891786db0978c898c058493f86fc68b1c5de8a5cb36336150ac7a88655b6c39"), - MustHexID("b7f79e3ab59f79262623c9ccefc8f01d682323aee56ffbe295437487e9d5acaf556a9c92e1f1c6a9601f2b9eb6b027ae1aeaebac71d61b9b78e88676efd3e1a3"), - MustHexID("d374bf7e8d7ffff69cc00bebff38ef5bc1dcb0a8d51c1a3d70e61ac6b2e2d6617109254b0ac224354dfbf79009fe4239e09020c483cc60c071e00b9238684f30"), - MustHexID("1e1eac1c9add703eb252eb991594f8f5a173255d526a855fab24ae57dc277e055bc3c7a7ae0b45d437c4f47a72d97eb7b126f2ba344ba6c0e14b2c6f27d4b1e6"), - MustHexID("ae28953f63d4bc4e706712a59319c111f5ff8f312584f65d7436b4cd3d14b217b958f8486bad666b4481fe879019fb1f767cf15b3e3e2711efc33b56d460448a"), - MustHexID("934bb1edf9c7a318b82306aca67feb3d6b434421fa275d694f0b4927afd8b1d3935b727fd4ff6e3d012e0c82f1824385174e8c6450ade59c2a43281a4b3446b6"), - MustHexID("9eef3f28f70ce19637519a0916555bf76d26de31312ac656cf9d3e379899ea44e4dd7ffcce923b4f3563f8a00489a34bd6936db0cbb4c959d32c49f017e07d05"), - MustHexID("82200872e8f871c48f1fad13daec6478298099b591bb3dbc4ef6890aa28ebee5860d07d70be62f4c0af85085a90ae8179ee8f937cf37915c67ea73e704b03ee7"), - MustHexID("6c75a5834a08476b7fc37ff3dc2011dc3ea3b36524bad7a6d319b18878fad813c0ba76d1f4555cacd3890c865438c21f0e0aed1f80e0a157e642124c69f43a11"), - MustHexID("995b873742206cb02b736e73a88580c2aacb0bd4a3c97a647b647bcab3f5e03c0e0736520a8b3600da09edf4248991fb01091ec7ff3ec7cdc8a1beae011e7aae"), - MustHexID("c773a056594b5cdef2e850d30891ff0e927c3b1b9c35cd8e8d53a1017001e237468e1ece3ae33d612ca3e6abb0a9169aa352e9dcda358e5af2ad982b577447db"), - MustHexID("2b46a5f6923f475c6be99ec6d134437a6d11f6bb4b4ac6bcd94572fa1092639d1c08aeefcb51f0912f0a060f71d4f38ee4da70ecc16010b05dd4a674aab14c3a"), - MustHexID("af6ab501366debbaa0d22e20e9688f32ef6b3b644440580fd78de4fe0e99e2a16eb5636bbae0d1c259df8ddda77b35b9a35cbc36137473e9c68fbc9d203ba842"), - MustHexID("c9f6f2dd1a941926f03f770695bda289859e85fabaf94baaae20b93e5015dc014ba41150176a36a1884adb52f405194693e63b0c464a6891cc9cc1c80d450326"), - MustHexID("5b116f0751526868a909b61a30b0c5282c37df6925cc03ddea556ef0d0602a9595fd6c14d371f8ed7d45d89918a032dcd22be4342a8793d88fdbeb3ca3d75bd7"), - MustHexID("50f3222fb6b82481c7c813b2172e1daea43e2710a443b9c2a57a12bd160dd37e20f87aa968c82ad639af6972185609d47036c0d93b4b7269b74ebd7073221c10"), - }, - 251: { - MustHexID("9b8f702a62d1bee67bedfeb102eca7f37fa1713e310f0d6651cc0c33ea7c5477575289ccd463e5a2574a00a676a1fdce05658ba447bb9d2827f0ba47b947e894"), - MustHexID("b97532eb83054ed054b4abdf413bb30c00e4205545c93521554dbe77faa3cfaa5bd31ef466a107b0b34a71ec97214c0c83919720142cddac93aa7a3e928d4708"), - MustHexID("2f7a5e952bfb67f2f90b8441b5fadc9ee13b1dcde3afeeb3dd64bf937f86663cc5c55d1fa83952b5422763c7df1b7f2794b751c6be316ebc0beb4942e65ab8c1"), - MustHexID("42c7483781727051a0b3660f14faf39e0d33de5e643702ae933837d036508ab856ce7eec8ec89c4929a4901256e5233a3d847d5d4893f91bcf21835a9a880fee"), - MustHexID("873bae27bf1dc854408fba94046a53ab0c965cebe1e4e12290806fc62b88deb1f4a47f9e18f78fc0e7913a0c6e42ac4d0fc3a20cea6bc65f0c8a0ca90b67521e"), - MustHexID("a7e3a370bbd761d413f8d209e85886f68bf73d5c3089b2dc6fa42aab1ecb5162635497eed95dee2417f3c9c74a3e76319625c48ead2e963c7de877cd4551f347"), - MustHexID("528597534776a40df2addaaea15b6ff832ce36b9748a265768368f657e76d58569d9f30dbb91e91cf0ae7efe8f402f17aa0ae15f5c55051ba03ba830287f4c42"), - MustHexID("461d8bd4f13c3c09031fdb84f104ed737a52f630261463ce0bdb5704259bab4b737dda688285b8444dbecaecad7f50f835190b38684ced5e90c54219e5adf1bc"), - MustHexID("6ec50c0be3fd232737090fc0111caaf0bb6b18f72be453428087a11a97fd6b52db0344acbf789a689bd4f5f50f79017ea784f8fd6fe723ad6ae675b9e3b13e21"), - MustHexID("12fc5e2f77a83fdcc727b79d8ae7fe6a516881138d3011847ee136b400fed7cfba1f53fd7a9730253c7aa4f39abeacd04f138417ba7fcb0f36cccc3514e0dab6"), - MustHexID("4fdbe75914ccd0bce02101606a1ccf3657ec963e3b3c20239d5fec87673fe446d649b4f15f1fe1a40e6cfbd446dda2d31d40bb602b1093b8fcd5f139ba0eb46a"), - MustHexID("3753668a0f6281e425ea69b52cb2d17ab97afbe6eb84cf5d25425bc5e53009388857640668fadd7c110721e6047c9697803bd8a6487b43bb343bfa32ebf24039"), - MustHexID("2e81b16346637dec4410fd88e527346145b9c0a849dbf2628049ac7dae016c8f4305649d5659ec77f1e8a0fac0db457b6080547226f06283598e3740ad94849a"), - MustHexID("802c3cc27f91c89213223d758f8d2ecd41135b357b6d698f24d811cdf113033a81c38e0bdff574a5c005b00a8c193dc2531f8c1fa05fa60acf0ab6f2858af09f"), - MustHexID("fcc9a2e1ac3667026ff16192876d1813bb75abdbf39b929a92863012fe8b1d890badea7a0de36274d5c1eb1e8f975785532c50d80fd44b1a4b692f437303393f"), - MustHexID("6d8b3efb461151dd4f6de809b62726f5b89e9b38e9ba1391967f61cde844f7528fecf821b74049207cee5a527096b31f3ad623928cd3ce51d926fa345a6b2951"), - }, - 252: { - MustHexID("f1ae93157cc48c2075dd5868fbf523e79e06caf4b8198f352f6e526680b78ff4227263de92612f7d63472bd09367bb92a636fff16fe46ccf41614f7a72495c2a"), - MustHexID("587f482d111b239c27c0cb89b51dd5d574db8efd8de14a2e6a1400c54d4567e77c65f89c1da52841212080b91604104768350276b6682f2f961cdaf4039581c7"), - MustHexID("e3f88274d35cefdaabdf205afe0e80e936cc982b8e3e47a84ce664c413b29016a4fb4f3a3ebae0a2f79671f8323661ed462bf4390af94c424dc8ace0c301b90f"), - MustHexID("0ddc736077da9a12ba410dc5ea63cbcbe7659dd08596485b2bff3435221f82c10d263efd9af938e128464be64a178b7cd22e19f400d5802f4c9df54bf89f2619"), - MustHexID("784aa34d833c6ce63fcc1279630113c3272e82c4ae8c126c5a52a88ac461b6baeed4244e607b05dc14e5b2f41c70a273c3804dea237f14f7a1e546f6d1309d14"), - MustHexID("f253a2c354ee0e27cfcae786d726753d4ad24be6516b279a936195a487de4a59dbc296accf20463749ff55293263ed8c1b6365eecb248d44e75e9741c0d18205"), - MustHexID("a1910b80357b3ad9b4593e0628922939614dc9056a5fbf477279c8b2c1d0b4b31d89a0c09d0d41f795271d14d3360ef08a3f821e65e7e1f56c07a36afe49c7c5"), - MustHexID("f1168552c2efe541160f0909b0b4a9d6aeedcf595cdf0e9b165c97e3e197471a1ee6320e93389edfba28af6eaf10de98597ad56e7ab1b504ed762451996c3b98"), - MustHexID("b0c8e5d2c8634a7930e1a6fd082e448c6cf9d2d8b7293558b59238815a4df926c286bf297d2049f14e8296a6eb3256af614ec1812c4f2bbe807673b58bf14c8c"), - MustHexID("0fb346076396a38badc342df3679b55bd7f40a609ab103411fe45082c01f12ea016729e95914b2b5540e987ff5c9b133e85862648e7f36abdfd23100d248d234"), - MustHexID("f736e0cc83417feaa280d9483f5d4d72d1b036cd0c6d9cbdeb8ac35ceb2604780de46dddaa32a378474e1d5ccdf79b373331c30c7911ade2ae32f98832e5de1f"), - MustHexID("8b02991457602f42b38b342d3f2259ae4100c354b3843885f7e4e07bd644f64dab94bb7f38a3915f8b7f11d8e3f81c28e07a0078cf79d7397e38a7b7e0c857e2"), - MustHexID("9221d9f04a8a184993d12baa91116692bb685f887671302999d69300ad103eb2d2c75a09d8979404c6dd28f12362f58a1a43619c493d9108fd47588a23ce5824"), - MustHexID("652797801744dada833fff207d67484742eea6835d695925f3e618d71b68ec3c65bdd85b4302b2cdcb835ad3f94fd00d8da07e570b41bc0d2bcf69a8de1b3284"), - MustHexID("d84f06fe64debc4cd0625e36d19b99014b6218375262cc2209202bdbafd7dffcc4e34ce6398e182e02fd8faeed622c3e175545864902dfd3d1ac57647cddf4c6"), - MustHexID("d0ed87b294f38f1d741eb601020eeec30ac16331d05880fe27868f1e454446de367d7457b41c79e202eaf9525b029e4f1d7e17d85a55f83a557c005c68d7328a"), - }, - 253: { - MustHexID("ad4485e386e3cc7c7310366a7c38fb810b8896c0d52e55944bfd320ca294e7912d6c53c0a0cf85e7ce226e92491d60430e86f8f15cda0161ed71893fb4a9e3a1"), - MustHexID("36d0e7e5b7734f98c6183eeeb8ac5130a85e910a925311a19c4941b1290f945d4fc3996b12ef4966960b6fa0fb29b1604f83a0f81bd5fd6398d2e1a22e46af0c"), - MustHexID("7d307d8acb4a561afa23bdf0bd945d35c90245e26345ec3a1f9f7df354222a7cdcb81339c9ed6744526c27a1a0c8d10857e98df942fa433602facac71ac68a31"), - MustHexID("d97bf55f88c83fae36232661af115d66ca600fc4bd6d1fb35ff9bb4dad674c02cf8c8d05f317525b5522250db58bb1ecafb7157392bf5aa61b178c61f098d995"), - MustHexID("7045d678f1f9eb7a4613764d17bd5698796494d0bf977b16f2dbc272b8a0f7858a60805c022fc3d1fe4f31c37e63cdaca0416c0d053ef48a815f8b19121605e0"), - MustHexID("14e1f21418d445748de2a95cd9a8c3b15b506f86a0acabd8af44bb968ce39885b19c8822af61b3dd58a34d1f265baec30e3ae56149dc7d2aa4a538f7319f69c8"), - MustHexID("b9453d78281b66a4eac95a1546017111eaaa5f92a65d0de10b1122940e92b319728a24edf4dec6acc412321b1c95266d39c7b3a5d265c629c3e49a65fb022c09"), - MustHexID("e8a49248419e3824a00d86af422f22f7366e2d4922b304b7169937616a01d9d6fa5abf5cc01061a352dc866f48e1fa2240dbb453d872b1d7be62bdfc1d5e248c"), - MustHexID("bebcff24b52362f30e0589ee573ce2d86f073d58d18e6852a592fa86ceb1a6c9b96d7fb9ec7ed1ed98a51b6743039e780279f6bb49d0a04327ac7a182d9a56f6"), - MustHexID("d0835e5a4291db249b8d2fca9f503049988180c7d247bedaa2cf3a1bad0a76709360a85d4f9a1423b2cbc82bb4d94b47c0cde20afc430224834c49fe312a9ae3"), - MustHexID("6b087fe2a2da5e4f0b0f4777598a4a7fb66bf77dbd5bfc44e8a7eaa432ab585a6e226891f56a7d4f5ed11a7c57b90f1661bba1059590ca4267a35801c2802913"), - MustHexID("d901e5bde52d1a0f4ddf010a686a53974cdae4ebe5c6551b3c37d6b6d635d38d5b0e5f80bc0186a2c7809dbf3a42870dd09643e68d32db896c6da8ba734579e7"), - MustHexID("96419fb80efae4b674402bb969ebaab86c1274f29a83a311e24516d36cdf148fe21754d46c97688cdd7468f24c08b13e4727c29263393638a3b37b99ff60ebca"), - MustHexID("7b9c1889ae916a5d5abcdfb0aaedcc9c6f9eb1c1a4f68d0c2d034fe79ac610ce917c3abc670744150fa891bfcd8ab14fed6983fca964de920aa393fa7b326748"), - MustHexID("7a369b2b8962cc4c65900be046482fbf7c14f98a135bbbae25152c82ad168fb2097b3d1429197cf46d3ce9fdeb64808f908a489cc6019725db040060fdfe5405"), - MustHexID("47bcae48288da5ecc7f5058dfa07cf14d89d06d6e449cb946e237aa6652ea050d9f5a24a65efdc0013ccf232bf88670979eddef249b054f63f38da9d7796dbd8"), - }, - 254: { - MustHexID("099739d7abc8abd38ecc7a816c521a1168a4dbd359fa7212a5123ab583ffa1cf485a5fed219575d6475dbcdd541638b2d3631a6c7fce7474e7fe3cba1d4d5853"), - MustHexID("c2b01603b088a7182d0cf7ef29fb2b04c70acb320fccf78526bf9472e10c74ee70b3fcfa6f4b11d167bd7d3bc4d936b660f2c9bff934793d97cb21750e7c3d31"), - MustHexID("20e4d8f45f2f863e94b45548c1ef22a11f7d36f263e4f8623761e05a64c4572379b000a52211751e2561b0f14f4fc92dd4130410c8ccc71eb4f0e95a700d4ca9"), - MustHexID("27f4a16cc085e72d86e25c98bd2eca173eaaee7565c78ec5a52e9e12b2211f35de81b5b45e9195de2ebfe29106742c59112b951a04eb7ae48822911fc1f9389e"), - MustHexID("55db5ee7d98e7f0b1c3b9d5be6f2bc619a1b86c3cdd513160ad4dcf267037a5fffad527ac15d50aeb32c59c13d1d4c1e567ebbf4de0d25236130c8361f9aac63"), - MustHexID("883df308b0130fc928a8559fe50667a0fff80493bc09685d18213b2db241a3ad11310ed86b0ef662b3ce21fc3d9aa7f3fc24b8d9afe17c7407e9afd3345ae548"), - MustHexID("c7af968cc9bc8200c3ee1a387405f7563be1dce6710a3439f42ea40657d0eae9d2b3c16c42d779605351fcdece4da637b9804e60ca08cfb89aec32c197beffa6"), - MustHexID("3e66f2b788e3ff1d04106b80597915cd7afa06c405a7ae026556b6e583dca8e05cfbab5039bb9a1b5d06083ffe8de5780b1775550e7218f5e98624bf7af9a0a8"), - MustHexID("4fc7f53764de3337fdaec0a711d35d3a923e72fa65025444d12230b3552ed43d9b2d1ad08ccb11f2d50c58809e6dd74dde910e195294fca3b47ae5a3967cc479"), - MustHexID("bafdfdcf6ccaa989436752fa97c77477b6baa7deb374b16c095492c529eb133e8e2f99e1977012b64767b9d34b2cf6d2048ed489bd822b5139b523f6a423167b"), - MustHexID("7f5d78008a4312fe059104ce80202c82b8915c2eb4411c6b812b16f7642e57c00f2c9425121f5cbac4257fe0b3e81ef5dea97ea2dbaa98f6a8b6fd4d1e5980bb"), - MustHexID("598c37fe78f922751a052f463aeb0cb0bc7f52b7c2a4cf2da72ec0931c7c32175d4165d0f8998f7320e87324ac3311c03f9382a5385c55f0407b7a66b2acd864"), - MustHexID("f758c4136e1c148777a7f3275a76e2db0b2b04066fd738554ec398c1c6cc9fb47e14a3b4c87bd47deaeab3ffd2110514c3855685a374794daff87b605b27ee2e"), - MustHexID("0307bb9e4fd865a49dcf1fe4333d1b944547db650ab580af0b33e53c4fef6c789531110fac801bbcbce21fc4d6f61b6d5b24abdf5b22e3030646d579f6dca9c2"), - MustHexID("82504b6eb49bb2c0f91a7006ce9cefdbaf6df38706198502c2e06601091fc9dc91e4f15db3410d45c6af355bc270b0f268d3dff560f956985c7332d4b10bd1ed"), - MustHexID("b39b5b677b45944ceebe76e76d1f051de2f2a0ec7b0d650da52135743e66a9a5dba45f638258f9a7545d9a790c7fe6d3fdf82c25425c7887323e45d27d06c057"), - }, - 255: { - MustHexID("5c4d58d46e055dd1f093f81ee60a675e1f02f54da6206720adee4dccef9b67a31efc5c2a2949c31a04ee31beadc79aba10da31440a1f9ff2a24093c63c36d784"), - MustHexID("ea72161ffdd4b1e124c7b93b0684805f4c4b58d617ed498b37a145c670dbc2e04976f8785583d9c805ffbf343c31d492d79f841652bbbd01b61ed85640b23495"), - MustHexID("51caa1d93352d47a8e531692a3612adac1e8ac68d0a200d086c1c57ae1e1a91aa285ab242e8c52ef9d7afe374c9485b122ae815f1707b875569d0433c1c3ce85"), - MustHexID("c08397d5751b47bd3da044b908be0fb0e510d3149574dff7aeab33749b023bb171b5769990fe17469dbebc100bc150e798aeda426a2dcc766699a225fddd75c6"), - MustHexID("0222c1c194b749736e593f937fad67ee348ac57287a15c7e42877aa38a9b87732a408bca370f812efd0eedbff13e6d5b854bf3ba1dec431a796ed47f32552b09"), - MustHexID("03d859cd46ef02d9bfad5268461a6955426845eef4126de6be0fa4e8d7e0727ba2385b78f1a883a8239e95ebb814f2af8379632c7d5b100688eebc5841209582"), - MustHexID("64d5004b7e043c39ff0bd10cb20094c287721d5251715884c280a612b494b3e9e1c64ba6f67614994c7d969a0d0c0295d107d53fc225d47c44c4b82852d6f960"), - MustHexID("b0a5eefb2dab6f786670f35bf9641eefe6dd87fd3f1362bcab4aaa792903500ab23d88fae68411372e0813b057535a601d46e454323745a948017f6063a47b1f"), - MustHexID("0cc6df0a3433d448b5684d2a3ffa9d1a825388177a18f44ad0008c7bd7702f1ec0fc38b83506f7de689c3b6ecb552599927e29699eed6bb867ff08f80068b287"), - MustHexID("50772f7b8c03a4e153355fbbf79c8a80cf32af656ff0c7873c99911099d04a0dae0674706c357e0145ad017a0ade65e6052cb1b0d574fcd6f67da3eee0ace66b"), - MustHexID("1ae37829c9ef41f8b508b82259ebac76b1ed900d7a45c08b7970f25d2d48ddd1829e2f11423a18749940b6dab8598c6e416cef0efd47e46e51f29a0bc65b37cd"), - MustHexID("ba973cab31c2af091fc1644a93527d62b2394999e2b6ccbf158dd5ab9796a43d408786f1803ef4e29debfeb62fce2b6caa5ab2b24d1549c822a11c40c2856665"), - MustHexID("bc413ad270dd6ea25bddba78f3298b03b8ba6f8608ac03d06007d4116fa78ef5a0cfe8c80155089382fc7a193243ee5500082660cb5d7793f60f2d7d18650964"), - MustHexID("5a6a9ef07634d9eec3baa87c997b529b92652afa11473dfee41ef7037d5c06e0ddb9fe842364462d79dd31cff8a59a1b8d5bc2b810dea1d4cbbd3beb80ecec83"), - MustHexID("f492c6ee2696d5f682f7f537757e52744c2ae560f1090a07024609e903d334e9e174fc01609c5a229ddbcac36c9d21adaf6457dab38a25bfd44f2f0ee4277998"), - MustHexID("459e4db99298cb0467a90acee6888b08bb857450deac11015cced5104853be5adce5b69c740968bc7f931495d671a70cad9f48546d7cd203357fe9af0e8d2164"), - }, - 256: { - MustHexID("a8593af8a4aef7b806b5197612017951bac8845a1917ca9a6a15dd6086d608505144990b245785c4cd2d67a295701c7aac2aa18823fb0033987284b019656268"), - MustHexID("d2eebef914928c3aad77fc1b2a495f52d2294acf5edaa7d8a530b540f094b861a68fe8348a46a7c302f08ab609d85912a4968eacfea0740847b29421b4795d9e"), - MustHexID("b14bfcb31495f32b650b63cf7d08492e3e29071fdc73cf2da0da48d4b191a70ba1a65f42ad8c343206101f00f8a48e8db4b08bf3f622c0853e7323b250835b91"), - MustHexID("7feaee0d818c03eb30e4e0bf03ade0f3c21ca38e938a761aa1781cf70bda8cc5cd631a6cc53dd44f1d4a6d3e2dae6513c6c66ee50cb2f0e9ad6f7e319b309fd9"), - MustHexID("4ca3b657b139311db8d583c25dd5963005e46689e1317620496cc64129c7f3e52870820e0ec7941d28809311df6db8a2867bbd4f235b4248af24d7a9c22d1232"), - MustHexID("1181defb1d16851d42dd951d84424d6bd1479137f587fa184d5a8152be6b6b16ed08bcdb2c2ed8539bcde98c80c432875f9f724737c316a2bd385a39d3cab1d8"), - MustHexID("d9dd818769fa0c3ec9f553c759b92476f082817252a04a47dc1777740b1731d280058c66f982812f173a294acf4944a85ba08346e2de153ba3ba41ce8a62cb64"), - MustHexID("bd7c4f8a9e770aa915c771b15e107ca123d838762da0d3ffc53aa6b53e9cd076cffc534ec4d2e4c334c683f1f5ea72e0e123f6c261915ed5b58ac1b59f003d88"), - MustHexID("3dd5739c73649d510456a70e9d6b46a855864a4a3f744e088fd8c8da11b18e4c9b5f2d7da50b1c147b2bae5ca9609ae01f7a3cdea9dce34f80a91d29cd82f918"), - MustHexID("f0d7df1efc439b4bcc0b762118c1cfa99b2a6143a9f4b10e3c9465125f4c9fca4ab88a2504169bbcad65492cf2f50da9dd5d077c39574a944f94d8246529066b"), - MustHexID("dd598b9ba441448e5fb1a6ec6c5f5aa9605bad6e223297c729b1705d11d05f6bfd3d41988b694681ae69bb03b9a08bff4beab5596503d12a39bffb5cd6e94c7c"), - MustHexID("3fce284ac97e567aebae681b15b7a2b6df9d873945536335883e4bbc26460c064370537f323fd1ada828ea43154992d14ac0cec0940a2bd2a3f42ec156d60c83"), - MustHexID("7c8dfa8c1311cb14fb29a8ac11bca23ecc115e56d9fcf7b7ac1db9066aa4eb39f8b1dabf46e192a65be95ebfb4e839b5ab4533fef414921825e996b210dd53bd"), - MustHexID("cafa6934f82120456620573d7f801390ed5e16ed619613a37e409e44ab355ef755e83565a913b48a9466db786f8d4fbd590bfec474c2524d4a2608d4eafd6abd"), - MustHexID("9d16600d0dd310d77045769fed2cb427f32db88cd57d86e49390c2ba8a9698cfa856f775be2013237226e7bf47b248871cf865d23015937d1edeb20db5e3e760"), - MustHexID("17be6b6ba54199b1d80eff866d348ea11d8a4b341d63ad9a6681d3ef8a43853ac564d153eb2a8737f0afc9ab320f6f95c55aa11aaa13bbb1ff422fd16bdf8188"), - }, - }, -} - -type preminedTestnet struct { - target NodeID - targetSha common.Hash // sha3(target) - dists [hashBits + 1][]NodeID - net *Network -} - -func (tn *preminedTestnet) sendFindnode(to *Node, target NodeID) { - panic("sendFindnode called") -} - -func (tn *preminedTestnet) sendFindnodeHash(to *Node, target common.Hash) { - // current log distance is encoded in port number - // fmt.Println("findnode query at dist", toaddr.Port) - if to.UDP <= lowPort { - panic("query to node at or below distance 0") - } - next := to.UDP - 1 - var result []rpcNode - for i, id := range tn.dists[to.UDP-lowPort] { - result = append(result, nodeToRPC(NewNode(id, net.ParseIP("10.0.2.99"), next, uint16(i)+1+lowPort))) - } - injectResponse(tn.net, to, neighborsPacket, &neighbors{Nodes: result}) -} - -func (tn *preminedTestnet) sendPing(to *Node, addr *net.UDPAddr, topics []Topic) []byte { - injectResponse(tn.net, to, pongPacket, &pong{ReplyTok: []byte{1}}) - return []byte{1} -} - -func (tn *preminedTestnet) send(to *Node, ptype nodeEvent, data interface{}) (hash []byte) { - switch ptype { - case pingPacket: - injectResponse(tn.net, to, pongPacket, &pong{ReplyTok: []byte{1}}) - case pongPacket: - // ignored - case findnodeHashPacket: - // current log distance is encoded in port number - // fmt.Println("findnode query at dist", toaddr.Port-lowPort) - if to.UDP <= lowPort { - panic("query to node at or below distance 0") - } - next := to.UDP - 1 - var result []rpcNode - for i, id := range tn.dists[to.UDP-lowPort] { - result = append(result, nodeToRPC(NewNode(id, net.ParseIP("10.0.2.99"), next, uint16(i)+1+lowPort))) - } - injectResponse(tn.net, to, neighborsPacket, &neighbors{Nodes: result}) - default: - panic("send(" + ptype.String() + ")") - } - return []byte{2} -} - -func (tn *preminedTestnet) sendNeighbours(to *Node, nodes []*Node) { - panic("sendNeighbours called") -} - -func (tn *preminedTestnet) sendTopicQuery(to *Node, topic Topic) { - panic("sendTopicQuery called") -} - -func (tn *preminedTestnet) sendTopicNodes(to *Node, queryHash common.Hash, nodes []*Node) { - panic("sendTopicNodes called") -} - -func (tn *preminedTestnet) sendTopicRegister(to *Node, topics []Topic, idx int, pong []byte) { - panic("sendTopicRegister called") -} - -func (*preminedTestnet) Close() {} - -func (*preminedTestnet) localAddr() *net.UDPAddr { - return &net.UDPAddr{IP: net.ParseIP("10.0.1.1"), Port: 40000} -} - -// mine generates a testnet struct literal with nodes at -// various distances to the given target. -func (tn *preminedTestnet) mine(target NodeID) { - tn.target = target - tn.targetSha = crypto.Keccak256Hash(tn.target[:]) - found := 0 - for found < bucketSize*10 { - k := newkey() - id := PubkeyID(&k.PublicKey) - sha := crypto.Keccak256Hash(id[:]) - ld := logdist(tn.targetSha, sha) - if len(tn.dists[ld]) < bucketSize { - tn.dists[ld] = append(tn.dists[ld], id) - fmt.Println("found ID with ld", ld) - found++ - } - } - fmt.Println("&preminedTestnet{") - fmt.Printf(" target: %#v,\n", tn.target) - fmt.Printf(" targetSha: %#v,\n", tn.targetSha) - fmt.Printf(" dists: [%d][]NodeID{\n", len(tn.dists)) - for ld, ns := range &tn.dists { - if len(ns) == 0 { - continue - } - fmt.Printf(" %d: []NodeID{\n", ld) - for _, n := range ns { - fmt.Printf(" MustHexID(\"%x\"),\n", n[:]) - } - fmt.Println(" },") - } - fmt.Println(" },") - fmt.Println("}") -} - -func injectResponse(net *Network, from *Node, ev nodeEvent, packet interface{}) { - go net.reqReadPacket(ingressPacket{remoteID: from.ID, remoteAddr: from.addr(), ev: ev, data: packet}) -} diff --git a/p2p/discv5/node.go b/p2p/discv5/node.go deleted file mode 100644 index b8a8bfe4e0..0000000000 --- a/p2p/discv5/node.go +++ /dev/null @@ -1,438 +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 . - -package discv5 - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "encoding/hex" - "errors" - "fmt" - "math/big" - "math/rand" - "net" - "net/url" - "regexp" - "strconv" - "strings" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/crypto" -) - -// Node represents a host on the network. -// The public fields of Node may not be modified. -type Node struct { - IP net.IP // len 4 for IPv4 or 16 for IPv6 - UDP, TCP uint16 // port numbers - ID NodeID // the node's public key - - // Network-related fields are contained in nodeNetGuts. - // These fields are not supposed to be used off the - // Network.loop goroutine. - nodeNetGuts -} - -// NewNode creates a new node. It is mostly meant to be used for -// testing purposes. -func NewNode(id NodeID, ip net.IP, udpPort, tcpPort uint16) *Node { - if ipv4 := ip.To4(); ipv4 != nil { - ip = ipv4 - } - return &Node{ - IP: ip, - UDP: udpPort, - TCP: tcpPort, - ID: id, - nodeNetGuts: nodeNetGuts{sha: crypto.Keccak256Hash(id[:])}, - } -} - -func (n *Node) addr() *net.UDPAddr { - return &net.UDPAddr{IP: n.IP, Port: int(n.UDP)} -} - -func (n *Node) setAddr(a *net.UDPAddr) { - n.IP = a.IP - if ipv4 := a.IP.To4(); ipv4 != nil { - n.IP = ipv4 - } - n.UDP = uint16(a.Port) -} - -// compares the given address against the stored values. -func (n *Node) addrEqual(a *net.UDPAddr) bool { - ip := a.IP - if ipv4 := a.IP.To4(); ipv4 != nil { - ip = ipv4 - } - return n.UDP == uint16(a.Port) && n.IP.Equal(ip) -} - -// 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:// -// -// -// 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 16789 -// and UDP discovery port 30301. -// -// enode://@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 -} - -// type nodeQueue []*Node -// -// // pushNew adds n to the end if it is not present. -// func (nl *nodeList) appendNew(n *Node) { -// for _, entry := range n { -// if entry == n { -// return -// } -// } -// *nq = append(*nq, n) -// } -// -// // popRandom removes a random node. Nodes closer to -// // to the head of the beginning of the have a slightly higher probability. -// func (nl *nodeList) popRandom() *Node { -// ix := rand.Intn(len(*nq)) -// //TODO: probability as mentioned above. -// nl.removeIndex(ix) -// } -// -// func (nl *nodeList) removeIndex(i int) *Node { -// slice = *nl -// if len(*slice) <= i { -// return nil -// } -// *nl = append(slice[:i], slice[i+1:]...) -// } - -const nodeIDBits = 512 - -// NodeID is a unique identifier for each node. -// The node identifier is a marshaled elliptic curve public key. -type NodeID [nodeIDBits / 8]byte - -// 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]) -} - -// 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 (n NodeID) Pubkey() (*ecdsa.PublicKey, error) { - p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)} - half := len(n) / 2 - p.X.SetBytes(n[:half]) - p.Y.SetBytes(n[half:]) - if !p.Curve.IsOnCurve(p.X, p.Y) { - return nil, errors.New("id is invalid secp256k1 curve point") - } - return p, nil -} - -func (id NodeID) mustPubkey() ecdsa.PublicKey { - pk, err := id.Pubkey() - if err != nil { - panic(err) - } - return *pk -} - -// recoverNodeID computes the public key used to sign the -// given hash from the signature. -func recoverNodeID(hash, sig []byte) (id NodeID, err error) { - pubkey, err := crypto.Ecrecover(hash, sig) - if err != nil { - return id, err - } - if len(pubkey)-1 != len(id) { - return id, fmt.Errorf("recovered pubkey has %d bits, want %d bits", len(pubkey)*8, (len(id)+1)*8) - } - for i := range id { - id[i] = pubkey[i+1] - } - 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 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 -} - -// table of leading zero counts for bytes [0..255] -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 -} diff --git a/p2p/discv5/node_test.go b/p2p/discv5/node_test.go deleted file mode 100644 index ad5ac36913..0000000000 --- a/p2p/discv5/node_test.go +++ /dev/null @@ -1,307 +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 . - -package discv5 - -import ( - "fmt" - "math/big" - "math/rand" - "net" - "reflect" - "strings" - "testing" - "testing/quick" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/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, 16789) - 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]:16789?discport=52150 - // n1.Incomplete() -> false - // n2: enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439 - // n2.Incomplete() -> true -} - -var parseNodeTests = []struct { - rawurl string - wantError string - wantResult *Node -}{ - { - rawurl: "http://foobar", - wantError: `invalid URL scheme, want "enode"`, - }, - { - rawurl: "enode://01010101@123.124.125.126:3", - wantError: `invalid node ID (wrong length, want 128 hex chars)`, - }, - // Complete nodes with IP address. - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3", - wantError: `invalid IP address`, - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo", - wantError: `invalid port`, - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo", - wantError: `invalid discport in query`, - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{0x7f, 0x0, 0x0, 0x1}, - 52150, - 52150, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.ParseIP("::"), - 52150, - 52150, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), - 52150, - 52150, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{0x7f, 0x0, 0x0, 0x1}, - 22334, - 52150, - ), - }, - // Incomplete nodes with no address. - { - rawurl: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - nil, 0, 0, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - nil, 0, 0, - ), - }, - // Invalid URLs - { - rawurl: "01010101", - wantError: `invalid node ID (wrong length, want 128 hex chars)`, - }, - { - rawurl: "enode://01010101", - wantError: `invalid node ID (wrong length, want 128 hex chars)`, - }, - { - // This test checks that errors from url.Parse are handled. - rawurl: "://foo", - wantError: `parse ://foo: missing protocol scheme`, - }, -} - -func TestParseNode(t *testing.T) { - for _, test := range parseNodeTests { - n, err := ParseNode(test.rawurl) - if test.wantError != "" { - if err == nil { - t.Errorf("test %q:\n got nil error, expected %#q", test.rawurl, test.wantError) - continue - } - - //else if err.Error() != test.wantError { - // t.Errorf("test %q:\n got error %#q, expected %#q", test.rawurl, err.Error(), test.wantError) - // continue - //} - } else { - if err != nil { - t.Errorf("test %q:\n unexpected error: %v", test.rawurl, err) - continue - } - if !reflect.DeepEqual(n, test.wantResult) { - t.Errorf("test %q:\n result mismatch:\ngot: %#v, want: %#v", test.rawurl, n, test.wantResult) - } - } - } -} - -func TestNodeString(t *testing.T) { - for i, test := range parseNodeTests { - if test.wantError == "" && strings.HasPrefix(test.rawurl, "enode://") { - str := test.wantResult.String() - if str != test.rawurl { - t.Errorf("test %d: Node.String() mismatch:\ngot: %s\nwant: %s", i, str, test.rawurl) - } - } - } -} - -func TestHexID(t *testing.T) { - 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 := MustHexID("0x000000000000000000000000000000000000000000000000000000000000000000000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") - id2 := MustHexID("000000000000000000000000000000000000000000000000000000000000000000000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") - - if id1 != ref { - t.Errorf("wrong id1\ngot %v\nwant %v", id1[:], ref[:]) - } - if id2 != ref { - t.Errorf("wrong id2\ngot %v\nwant %v", id2[:], ref[:]) - } -} - -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) { - distcmpBig := func(target, a, b common.Hash) int { - tbig := new(big.Int).SetBytes(target[:]) - abig := new(big.Int).SetBytes(a[:]) - bbig := new(big.Int).SetBytes(b[:]) - return new(big.Int).Xor(tbig, abig).Cmp(new(big.Int).Xor(tbig, bbig)) - } - if err := quick.CheckEqual(distcmp, distcmpBig, quickcfg()); err != nil { - t.Error(err) - } -} - -// the random tests is likely to miss the case where they're equal. -func TestNodeID_distcmpEqual(t *testing.T) { - base := common.Hash{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - 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 { - t.Errorf("distcmp(base, x, x) != 0") - } -} - -func TestNodeID_logdist(t *testing.T) { - logdistBig := func(a, b common.Hash) int { - abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:]) - return new(big.Int).Xor(abig, bbig).BitLen() - } - if err := quick.CheckEqual(logdist, logdistBig, quickcfg()); err != nil { - t.Error(err) - } -} - -// the random tests is likely to miss the case where they're equal. -func TestNodeID_logdistEqual(t *testing.T) { - x := common.Hash{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - if 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) -} diff --git a/p2p/discv5/nodeevent_string.go b/p2p/discv5/nodeevent_string.go deleted file mode 100644 index 38c1993bac..0000000000 --- a/p2p/discv5/nodeevent_string.go +++ /dev/null @@ -1,17 +0,0 @@ -// Code generated by "stringer -type=nodeEvent"; DO NOT EDIT. - -package discv5 - -import "strconv" - -const _nodeEvent_name = "pongTimeoutpingTimeoutneighboursTimeout" - -var _nodeEvent_index = [...]uint8{0, 11, 22, 39} - -func (i nodeEvent) String() string { - i -= 264 - if i >= nodeEvent(len(_nodeEvent_index)-1) { - return "nodeEvent(" + strconv.FormatInt(int64(i+264), 10) + ")" - } - return _nodeEvent_name[_nodeEvent_index[i]:_nodeEvent_index[i+1]] -} diff --git a/p2p/discv5/ntp.go b/p2p/discv5/ntp.go deleted file mode 100644 index e977148a9b..0000000000 --- a/p2p/discv5/ntp.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2016 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 . - -// Contains the NTP time drift detection via the SNTP protocol: -// https://tools.ietf.org/html/rfc4330 - -package discv5 - -import ( - "fmt" - "net" - "sort" - "strings" - "time" - - "github.com/AlayaNetwork/Alaya-Go/log" -) - -const ( - ntpPool = "pool.ntp.org" // ntpPool is the NTP server to query for the current time - ntpChecks = 3 // Number of measurements to do against the NTP server -) - -// durationSlice attaches the methods of sort.Interface to []time.Duration, -// sorting in increasing order. -type durationSlice []time.Duration - -func (s durationSlice) Len() int { return len(s) } -func (s durationSlice) Less(i, j int) bool { return s[i] < s[j] } -func (s durationSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -// checkClockDrift queries an NTP server for clock drifts and warns the user if -// one large enough is detected. -func checkClockDrift() { - drift, err := sntpDrift(ntpChecks) - if err != nil { - return - } - if drift < -driftThreshold || drift > driftThreshold { - warning := fmt.Sprintf("System clock seems off by %v, which can prevent network connectivity", drift) - howtofix := fmt.Sprintf("Please enable network time synchronisation in system settings") - separator := strings.Repeat("-", len(warning)) - - log.Warn(separator) - log.Warn(warning) - log.Warn(howtofix) - log.Warn(separator) - } else { - log.Debug(fmt.Sprintf("Sanity NTP check reported %v drift, all ok", drift)) - } -} - -// sntpDrift does a naive time resolution against an NTP server and returns the -// measured drift. This method uses the simple version of NTP. It's not precise -// but should be fine for these purposes. -// -// Note, it executes two extra measurements compared to the number of requested -// ones to be able to discard the two extremes as outliers. -func sntpDrift(measurements int) (time.Duration, error) { - // Resolve the address of the NTP server - addr, err := net.ResolveUDPAddr("udp", ntpPool+":123") - if err != nil { - return 0, err - } - // Construct the time request (empty package with only 2 fields set): - // Bits 3-5: Protocol version, 3 - // Bits 6-8: Mode of operation, client, 3 - request := make([]byte, 48) - request[0] = 3<<3 | 3 - - // Execute each of the measurements - drifts := []time.Duration{} - for i := 0; i < measurements+2; i++ { - // Dial the NTP server and send the time retrieval request - conn, err := net.DialUDP("udp", nil, addr) - if err != nil { - return 0, err - } - defer conn.Close() - - sent := time.Now() - if _, err = conn.Write(request); err != nil { - return 0, err - } - // Retrieve the reply and calculate the elapsed time - conn.SetDeadline(time.Now().Add(5 * time.Second)) - - reply := make([]byte, 48) - if _, err = conn.Read(reply); err != nil { - return 0, err - } - elapsed := time.Since(sent) - - // Reconstruct the time from the reply data - sec := uint64(reply[43]) | uint64(reply[42])<<8 | uint64(reply[41])<<16 | uint64(reply[40])<<24 - frac := uint64(reply[47]) | uint64(reply[46])<<8 | uint64(reply[45])<<16 | uint64(reply[44])<<24 - - nanosec := sec*1e9 + (frac*1e9)>>32 - - t := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(nanosec)).Local() - - // Calculate the drift based on an assumed answer time of RRT/2 - drifts = append(drifts, sent.Sub(t)+elapsed/2) - } - // Calculate average drif (drop two extremities to avoid outliers) - sort.Sort(durationSlice(drifts)) - - drift := time.Duration(0) - for i := 1; i < len(drifts)-1; i++ { - drift += drifts[i] - } - return drift / time.Duration(measurements), nil -} diff --git a/p2p/discv5/sim_run_test.go b/p2p/discv5/sim_run_test.go deleted file mode 100644 index bded0cc023..0000000000 --- a/p2p/discv5/sim_run_test.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2016 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 . - -package discv5 - -import ( - "bufio" - "bytes" - "encoding/binary" - "errors" - "fmt" - "io" - "os" - "os/exec" - "runtime" - "strings" - "testing" -) - -func getnacl() (string, error) { - switch runtime.GOARCH { - case "amd64": - _, err := exec.LookPath("sel_ldr_x86_64") - return "amd64p32", err - case "i386": - _, err := exec.LookPath("sel_ldr_i386") - return "i386", err - default: - return "", errors.New("nacl is not supported on " + runtime.GOARCH) - } -} - -// runWithPlaygroundTime executes the caller -// in the NaCl sandbox with faketime enabled. -// -// This function must be called from a Test* function -// and the caller must skip the actual test when isHost is true. -func runWithPlaygroundTime(t *testing.T) (isHost bool) { - if runtime.GOOS == "nacl" { - return false - } - - // Get the caller. - callerPC, _, _, ok := runtime.Caller(1) - if !ok { - panic("can't get caller") - } - callerFunc := runtime.FuncForPC(callerPC) - if callerFunc == nil { - panic("can't get caller") - } - callerName := callerFunc.Name()[strings.LastIndexByte(callerFunc.Name(), '.')+1:] - if !strings.HasPrefix(callerName, "Test") { - panic("must be called from witin a Test* function") - } - testPattern := "^" + callerName + "$" - - // Unfortunately runtime.faketime (playground time mode) only works on NaCl. The NaCl - // SDK must be installed and linked into PATH for this to work. - arch, err := getnacl() - if err != nil { - t.Skip(err) - } - - // Compile and run the calling test using NaCl. - // The extra tag ensures that the TestMain function in sim_main_test.go is used. - cmd := exec.Command("go", "test", "-v", "-tags", "faketime_simulation", "-timeout", "100h", "-run", testPattern, ".") - cmd.Env = append([]string{"GOOS=nacl", "GOARCH=" + arch}, os.Environ()...) - stdout, _ := cmd.StdoutPipe() - stderr, _ := cmd.StderrPipe() - go skipPlaygroundOutputHeaders(os.Stdout, stdout) - go skipPlaygroundOutputHeaders(os.Stderr, stderr) - if err := cmd.Run(); err != nil { - t.Error(err) - } - - // Ensure that the test function doesn't run in the (non-NaCl) host process. - return true -} - -func skipPlaygroundOutputHeaders(out io.Writer, in io.Reader) { - // Additional output can be printed without the headers - // before the NaCl binary starts running (e.g. compiler error messages). - bufin := bufio.NewReader(in) - output, err := bufin.ReadBytes(0) - output = bytes.TrimSuffix(output, []byte{0}) - if len(output) > 0 { - out.Write(output) - } - if err != nil { - return - } - bufin.UnreadByte() - - // Playback header: 0 0 P B <8-byte time> <4-byte data length> - head := make([]byte, 4+8+4) - for { - if _, err := io.ReadFull(bufin, head); err != nil { - if err != io.EOF { - fmt.Fprintln(out, "read error:", err) - } - return - } - if !bytes.HasPrefix(head, []byte{0x00, 0x00, 'P', 'B'}) { - fmt.Fprintf(out, "expected playback header, got %q\n", head) - io.Copy(out, bufin) - return - } - // Copy data until next header. - size := binary.BigEndian.Uint32(head[12:]) - io.CopyN(out, bufin, int64(size)) - } -} diff --git a/p2p/discv5/sim_test.go b/p2p/discv5/sim_test.go deleted file mode 100644 index 4f59af10fa..0000000000 --- a/p2p/discv5/sim_test.go +++ /dev/null @@ -1,456 +0,0 @@ -// Copyright 2016 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 . - -package discv5 - -import ( - "crypto/ecdsa" - "encoding/binary" - "fmt" - "math/rand" - "net" - "strconv" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" -) - -// In this test, nodes try to randomly resolve each other. -func TestSimRandomResolve(t *testing.T) { - t.Skip("boring") - if runWithPlaygroundTime(t) { - return - } - - sim := newSimulation() - bootnode := sim.launchNode(false) - - // A new node joins every 10s. - launcher := time.NewTicker(10 * time.Second) - go func() { - for range launcher.C { - net := sim.launchNode(false) - go randomResolves(t, sim, net) - if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil { - panic(err) - } - fmt.Printf("launched @ %v: %x\n", time.Now(), net.Self().ID[:16]) - } - }() - - time.Sleep(3 * time.Hour) - launcher.Stop() - sim.shutdown() - sim.printStats() -} - -func TestSimTopics(t *testing.T) { - t.Skip("NaCl test") - if runWithPlaygroundTime(t) { - return - } - sim := newSimulation() - bootnode := sim.launchNode(false) - - go func() { - nets := make([]*Network, 1024) - for i := range nets { - net := sim.launchNode(false) - nets[i] = net - if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil { - panic(err) - } - time.Sleep(time.Second * 5) - } - - for i, net := range nets { - if i < 256 { - stop := make(chan struct{}) - go net.RegisterTopic(testTopic, stop) - go func() { - //time.Sleep(time.Second * 36000) - time.Sleep(time.Second * 40000) - close(stop) - }() - time.Sleep(time.Millisecond * 100) - } - // time.Sleep(time.Second * 10) - //time.Sleep(time.Second) - /*if i%500 == 499 { - time.Sleep(time.Second * 9501) - } else { - time.Sleep(time.Second) - }*/ - } - }() - - // A new node joins every 10s. - /* launcher := time.NewTicker(5 * time.Second) - cnt := 0 - var printNet *Network - go func() { - for range launcher.C { - cnt++ - if cnt <= 1000 { - log := false //(cnt == 500) - net := sim.launchNode(log) - if log { - printNet = net - } - if cnt > 500 { - go net.RegisterTopic(testTopic, nil) - } - if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil { - panic(err) - } - } - //fmt.Printf("launched @ %v: %x\n", time.Now(), net.Self().ID[:16]) - } - }() - */ - time.Sleep(55000 * time.Second) - //launcher.Stop() - sim.shutdown() - //sim.printStats() - //printNet.log.printLogs() -} - -/*func testHierarchicalTopics(i int) []Topic { - digits := strconv.FormatInt(int64(256+i/4), 4) - res := make([]Topic, 5) - for i, _ := range res { - res[i] = Topic("foo" + digits[1:i+1]) - } - return res -}*/ - -func testHierarchicalTopics(i int) []Topic { - digits := strconv.FormatInt(int64(128+i/8), 2) - res := make([]Topic, 8) - for i := range res { - res[i] = Topic("foo" + digits[1:i+1]) - } - return res -} - -func TestSimTopicHierarchy(t *testing.T) { - t.Skip("NaCl test") - if runWithPlaygroundTime(t) { - return - } - sim := newSimulation() - bootnode := sim.launchNode(false) - - go func() { - nets := make([]*Network, 1024) - for i := range nets { - net := sim.launchNode(false) - nets[i] = net - if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil { - panic(err) - } - time.Sleep(time.Second * 5) - } - - stop := make(chan struct{}) - for i, net := range nets { - //if i < 256 { - for _, topic := range testHierarchicalTopics(i)[:5] { - //fmt.Println("reg", topic) - go net.RegisterTopic(topic, stop) - } - time.Sleep(time.Millisecond * 100) - //} - } - time.Sleep(time.Second * 90000) - close(stop) - }() - - time.Sleep(100000 * time.Second) - sim.shutdown() -} - -func randomResolves(t *testing.T, s *simulation, net *Network) { - randtime := func() time.Duration { - return time.Duration(rand.Intn(50)+20) * time.Second - } - lookup := func(target NodeID) bool { - result := net.Resolve(target) - return result != nil && result.ID == target - } - - timer := time.NewTimer(randtime()) - for { - select { - case <-timer.C: - target := s.randomNode().Self().ID - if !lookup(target) { - t.Errorf("node %x: target %x not found", net.Self().ID[:8], target[:8]) - } - timer.Reset(randtime()) - case <-net.closed: - return - } - } -} - -type simulation struct { - mu sync.RWMutex - nodes map[NodeID]*Network - nodectr uint32 -} - -func newSimulation() *simulation { - return &simulation{nodes: make(map[NodeID]*Network)} -} - -func (s *simulation) shutdown() { - s.mu.RLock() - alive := make([]*Network, 0, len(s.nodes)) - for _, n := range s.nodes { - alive = append(alive, n) - } - defer s.mu.RUnlock() - - for _, n := range alive { - n.Close() - } -} - -func (s *simulation) printStats() { - s.mu.Lock() - defer s.mu.Unlock() - fmt.Println("node counter:", s.nodectr) - fmt.Println("alive nodes:", len(s.nodes)) - - // for _, n := range s.nodes { - // fmt.Printf("%x\n", n.tab.self.ID[:8]) - // transport := n.conn.(*simTransport) - // fmt.Println(" joined:", transport.joinTime) - // fmt.Println(" sends:", transport.hashctr) - // fmt.Println(" table size:", n.tab.count) - // } - - /*for _, n := range s.nodes { - fmt.Println() - fmt.Printf("*** Node %x\n", n.tab.self.ID[:8]) - n.log.printLogs() - }*/ - -} - -func (s *simulation) randomNode() *Network { - s.mu.Lock() - defer s.mu.Unlock() - - n := rand.Intn(len(s.nodes)) - for _, net := range s.nodes { - if n == 0 { - return net - } - n-- - } - return nil -} - -func (s *simulation) launchNode(log bool) *Network { - var ( - num = s.nodectr - key = newkey() - id = PubkeyID(&key.PublicKey) - ip = make(net.IP, 4) - ) - s.nodectr++ - binary.BigEndian.PutUint32(ip, num) - ip[0] = 10 - addr := &net.UDPAddr{IP: ip, Port: 16789} - - transport := &simTransport{joinTime: time.Now(), sender: id, senderAddr: addr, sim: s, priv: key} - net, err := newNetwork(transport, key.PublicKey, "", nil) - if err != nil { - panic("cannot launch new node: " + err.Error()) - } - - s.mu.Lock() - s.nodes[id] = net - s.mu.Unlock() - - return net -} - -func (s *simulation) dropNode(id NodeID) { - s.mu.Lock() - n := s.nodes[id] - delete(s.nodes, id) - s.mu.Unlock() - - n.Close() -} - -type simTransport struct { - joinTime time.Time - sender NodeID - senderAddr *net.UDPAddr - sim *simulation - hashctr uint64 - priv *ecdsa.PrivateKey -} - -func (st *simTransport) localAddr() *net.UDPAddr { - return st.senderAddr -} - -func (st *simTransport) Close() {} - -func (st *simTransport) send(remote *Node, ptype nodeEvent, data interface{}) (hash []byte) { - hash = st.nextHash() - var raw []byte - if ptype == pongPacket { - var err error - raw, _, err = encodePacket(st.priv, byte(ptype), data) - if err != nil { - panic(err) - } - } - - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: hash, - ev: ptype, - data: data, - rawData: raw, - }) - return hash -} - -func (st *simTransport) sendPing(remote *Node, remoteAddr *net.UDPAddr, topics []Topic) []byte { - hash := st.nextHash() - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: hash, - ev: pingPacket, - data: &ping{ - Version: 4, - From: rpcEndpoint{IP: st.senderAddr.IP, UDP: uint16(st.senderAddr.Port), TCP: 16789}, - To: rpcEndpoint{IP: remoteAddr.IP, UDP: uint16(remoteAddr.Port), TCP: 16789}, - Expiration: uint64(time.Now().Unix() + int64(expiration)), - Topics: topics, - }, - }) - return hash -} - -func (st *simTransport) sendPong(remote *Node, pingHash []byte) { - raddr := remote.addr() - - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: st.nextHash(), - ev: pongPacket, - data: &pong{ - To: rpcEndpoint{IP: raddr.IP, UDP: uint16(raddr.Port), TCP: 16789}, - ReplyTok: pingHash, - Expiration: uint64(time.Now().Unix() + int64(expiration)), - }, - }) -} - -func (st *simTransport) sendFindnodeHash(remote *Node, target common.Hash) { - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: st.nextHash(), - ev: findnodeHashPacket, - data: &findnodeHash{ - Target: target, - Expiration: uint64(time.Now().Unix() + int64(expiration)), - }, - }) -} - -func (st *simTransport) sendTopicRegister(remote *Node, topics []Topic, idx int, pong []byte) { - //fmt.Println("send", topics, pong) - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: st.nextHash(), - ev: topicRegisterPacket, - data: &topicRegister{ - Topics: topics, - Idx: uint(idx), - Pong: pong, - }, - }) -} - -func (st *simTransport) sendTopicNodes(remote *Node, queryHash common.Hash, nodes []*Node) { - rnodes := make([]rpcNode, len(nodes)) - for i := range nodes { - rnodes[i] = nodeToRPC(nodes[i]) - } - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: st.nextHash(), - ev: topicNodesPacket, - data: &topicNodes{Echo: queryHash, Nodes: rnodes}, - }) -} - -func (st *simTransport) sendNeighbours(remote *Node, nodes []*Node) { - // TODO: send multiple packets - rnodes := make([]rpcNode, len(nodes)) - for i := range nodes { - rnodes[i] = nodeToRPC(nodes[i]) - } - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: st.nextHash(), - ev: neighborsPacket, - data: &neighbors{ - Nodes: rnodes, - Expiration: uint64(time.Now().Unix() + int64(expiration)), - }, - }) -} - -func (st *simTransport) nextHash() []byte { - v := atomic.AddUint64(&st.hashctr, 1) - var hash common.Hash - binary.BigEndian.PutUint64(hash[:], v) - return hash[:] -} - -const packetLoss = 0 // 1/1000 - -func (st *simTransport) sendPacket(remote NodeID, p ingressPacket) { - if rand.Int31n(1000) >= packetLoss { - st.sim.mu.RLock() - recipient := st.sim.nodes[remote] - st.sim.mu.RUnlock() - - time.AfterFunc(200*time.Millisecond, func() { - recipient.reqReadPacket(p) - }) - } -} diff --git a/p2p/discv5/table.go b/p2p/discv5/table.go deleted file mode 100644 index 152cbc032e..0000000000 --- a/p2p/discv5/table.go +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright 2016 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 . - -// Package discv5 implements the RLPx v5 Topic Discovery Protocol. -// -// The Topic Discovery protocol provides a way to find RLPx nodes that -// can be connected to. It uses a Kademlia-like protocol to maintain a -// distributed database of the IDs and endpoints of all listening -// nodes. -package discv5 - -import ( - "crypto/rand" - "encoding/binary" - "fmt" - "net" - "sort" - - "github.com/AlayaNetwork/Alaya-Go/common" -) - -const ( - alpha = 3 // Kademlia concurrency factor - bucketSize = 16 // Kademlia bucket size - hashBits = len(common.Hash{}) * 8 - nBuckets = hashBits + 1 // Number of buckets - - maxFindnodeFailures = 5 -) - -type Table struct { - count int // number of nodes - buckets [nBuckets]*bucket // index of known nodes by distance - nodeAddedHook func(*Node) // for testing - self *Node // metadata of the local node -} - -// bucket contains nodes, ordered by their last activity. the entry -// that was most recently active is the first element in entries. -type bucket struct { - entries []*Node - replacements []*Node -} - -func newTable(ourID NodeID, ourAddr *net.UDPAddr) *Table { - self := NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)) - tab := &Table{self: self} - for i := range tab.buckets { - tab.buckets[i] = new(bucket) - } - return tab -} - -const printTable = false - -// chooseBucketRefreshTarget selects random refresh targets to keep all Kademlia -// buckets filled with live connections and keep the network topology healthy. -// This requires selecting addresses closer to our own with a higher probability -// in order to refresh closer buckets too. -// -// This algorithm approximates the distance distribution of existing nodes in the -// table by selecting a random node from the table and selecting a target address -// with a distance less than twice of that of the selected node. -// This algorithm will be improved later to specifically target the least recently -// used buckets. -func (tab *Table) chooseBucketRefreshTarget() common.Hash { - entries := 0 - if printTable { - fmt.Println() - } - for i, b := range &tab.buckets { - entries += len(b.entries) - if printTable { - for _, e := range b.entries { - fmt.Println(i, e.state, e.addr().String(), e.ID.String(), e.sha.Hex()) - } - } - } - - prefix := binary.BigEndian.Uint64(tab.self.sha[0:8]) - dist := ^uint64(0) - entry := int(randUint(uint32(entries + 1))) - for _, b := range &tab.buckets { - if entry < len(b.entries) { - n := b.entries[entry] - dist = binary.BigEndian.Uint64(n.sha[0:8]) ^ prefix - break - } - entry -= len(b.entries) - } - - ddist := ^uint64(0) - if dist+dist > dist { - ddist = dist - } - targetPrefix := prefix ^ randUint64n(ddist) - - var target common.Hash - binary.BigEndian.PutUint64(target[0:8], targetPrefix) - rand.Read(target[8:]) - return target -} - -// 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) { - // TODO: tree-based buckets would help here - // Find all non-empty buckets and get a fresh slice of their entries. - var buckets [][]*Node - for _, b := range &tab.buckets { - if len(b.entries) > 0 { - buckets = append(buckets, b.entries) - } - } - if len(buckets) == 0 { - return 0 - } - // Shuffle the buckets. - for i := uint32(len(buckets)) - 1; i > 0; i-- { - j := randUint(i) - buckets[i], buckets[j] = buckets[j], buckets[i] - } - // Move head of each bucket into buf, removing buckets that become empty. - var i, j int - for ; i < len(buf); i, j = i+1, (j+1)%len(buckets) { - b := buckets[j] - buf[i] = &(*b[0]) - buckets[j] = b[1:] - if len(b) == 1 { - buckets = append(buckets[:j], buckets[j+1:]...) - } - if len(buckets) == 0 { - break - } - } - return i + 1 -} - -func randUint(max uint32) uint32 { - if max < 2 { - return 0 - } - var b [4]byte - rand.Read(b[:]) - return binary.BigEndian.Uint32(b[:]) % max -} - -func randUint64n(max uint64) uint64 { - if max < 2 { - return 0 - } - var b [8]byte - rand.Read(b[:]) - return binary.BigEndian.Uint64(b[:]) % max -} - -// closest returns the n nodes in the table that are closest to the -// given id. The caller must hold tab.mutex. -func (tab *Table) closest(target common.Hash, nresults int) *nodesByDistance { - // This is a very wasteful way to find the closest nodes but - // obviously correct. I believe that tree-based buckets would make - // this easier to implement efficiently. - close := &nodesByDistance{target: target} - for _, b := range &tab.buckets { - for _, n := range b.entries { - close.push(n, nresults) - } - } - return close -} - -// add attempts to add the given node its corresponding bucket. If the -// bucket has space available, adding the node succeeds immediately. -// Otherwise, the node is added to the replacement cache for the bucket. -func (tab *Table) add(n *Node) (contested *Node) { - //fmt.Println("add", n.addr().String(), n.ID.String(), n.sha.Hex()) - if n.ID == tab.self.ID { - return - } - b := tab.buckets[logdist(tab.self.sha, n.sha)] - switch { - case b.bump(n): - // n exists in b. - return nil - case len(b.entries) < bucketSize: - // b has space available. - b.addFront(n) - tab.count++ - if tab.nodeAddedHook != nil { - tab.nodeAddedHook(n) - } - return nil - default: - // b has no space left, add to replacement cache - // and revalidate the last entry. - // TODO: drop previous node - b.replacements = append(b.replacements, n) - if len(b.replacements) > bucketSize { - copy(b.replacements, b.replacements[1:]) - b.replacements = b.replacements[:len(b.replacements)-1] - } - return b.entries[len(b.entries)-1] - } -} - -// stuff adds nodes the table to the end of their corresponding bucket -// if the bucket is not full. -func (tab *Table) stuff(nodes []*Node) { -outer: - for _, n := range nodes { - if n.ID == tab.self.ID { - continue // don't add self - } - bucket := tab.buckets[logdist(tab.self.sha, n.sha)] - for i := range bucket.entries { - if bucket.entries[i].ID == n.ID { - continue outer // already in bucket - } - } - if len(bucket.entries) < bucketSize { - bucket.entries = append(bucket.entries, n) - tab.count++ - if tab.nodeAddedHook != nil { - tab.nodeAddedHook(n) - } - } - } -} - -// delete removes an entry from the node table (used to evacuate -// failed/non-bonded discovery peers). -func (tab *Table) delete(node *Node) { - //fmt.Println("delete", node.addr().String(), node.ID.String(), node.sha.Hex()) - bucket := tab.buckets[logdist(tab.self.sha, node.sha)] - for i := range bucket.entries { - if bucket.entries[i].ID == node.ID { - bucket.entries = append(bucket.entries[:i], bucket.entries[i+1:]...) - tab.count-- - return - } - } -} - -func (tab *Table) deleteReplace(node *Node) { - b := tab.buckets[logdist(tab.self.sha, node.sha)] - i := 0 - for i < len(b.entries) { - if b.entries[i].ID == node.ID { - b.entries = append(b.entries[:i], b.entries[i+1:]...) - tab.count-- - } else { - i++ - } - } - // refill from replacement cache - // TODO: maybe use random index - if len(b.entries) < bucketSize && len(b.replacements) > 0 { - ri := len(b.replacements) - 1 - b.addFront(b.replacements[ri]) - tab.count++ - b.replacements[ri] = nil - b.replacements = b.replacements[:ri] - } -} - -func (b *bucket) addFront(n *Node) { - b.entries = append(b.entries, nil) - copy(b.entries[1:], b.entries) - b.entries[0] = n -} - -func (b *bucket) bump(n *Node) bool { - for i := range b.entries { - if b.entries[i].ID == n.ID { - // move it to the front - copy(b.entries[1:], b.entries[:i]) - b.entries[0] = n - return true - } - } - return false -} - -// nodesByDistance is a list of nodes, ordered by -// distance to target. -type nodesByDistance struct { - entries []*Node - target common.Hash -} - -// push adds the given node to the list, keeping the total size below maxElems. -func (h *nodesByDistance) push(n *Node, maxElems int) { - ix := sort.Search(len(h.entries), func(i int) bool { - return distcmp(h.target, h.entries[i].sha, n.sha) > 0 - }) - if len(h.entries) < maxElems { - h.entries = append(h.entries, n) - } - if ix == len(h.entries) { - // farther away than all nodes we already have. - // if there was room for it, the node is now the last element. - } else { - // slide existing entries down to make room - // this will overwrite the entry we just appended. - copy(h.entries[ix+1:], h.entries[ix:]) - h.entries[ix] = n - } -} diff --git a/p2p/discv5/table_test.go b/p2p/discv5/table_test.go deleted file mode 100644 index e81ed463f9..0000000000 --- a/p2p/discv5/table_test.go +++ /dev/null @@ -1,337 +0,0 @@ -// Copyright 2016 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 . - -package discv5 - -import ( - "crypto/ecdsa" - "fmt" - "math/rand" - - "net" - "reflect" - "testing" - "testing/quick" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/crypto" -) - -type nullTransport struct{} - -func (nullTransport) sendPing(remote *Node, remoteAddr *net.UDPAddr) []byte { return []byte{1} } -func (nullTransport) sendPong(remote *Node, pingHash []byte) {} -func (nullTransport) sendFindnode(remote *Node, target NodeID) {} -func (nullTransport) sendNeighbours(remote *Node, nodes []*Node) {} -func (nullTransport) localAddr() *net.UDPAddr { return new(net.UDPAddr) } -func (nullTransport) Close() {} - -// func TestTable_pingReplace(t *testing.T) { -// doit := func(newNodeIsResponding, lastInBucketIsResponding bool) { -// transport := newPingRecorder() -// tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}) -// defer tab.Close() -// pingSender := NewNode(MustHexID("a502af0f59b2aab7746995408c79e9ca312d2793cc997e44fc55eda62f0150bbb8c59a6f9269ba3a081518b62699ee807c7c19c20125ddfccca872608af9e370"), net.IP{}, 99, 99) -// -// // fill up the sender's bucket. -// last := fillBucket(tab, 253) -// -// // this call to bond should replace the last node -// // in its bucket if the node is not responding. -// transport.responding[last.ID] = lastInBucketIsResponding -// transport.responding[pingSender.ID] = newNodeIsResponding -// tab.bond(true, pingSender.ID, &net.UDPAddr{}, 0) -// -// // first ping goes to sender (bonding pingback) -// if !transport.pinged[pingSender.ID] { -// t.Error("table did not ping back sender") -// } -// if newNodeIsResponding { -// // second ping goes to oldest node in bucket -// // to see whether it is still alive. -// if !transport.pinged[last.ID] { -// t.Error("table did not ping last node in bucket") -// } -// } -// -// tab.mutex.Lock() -// defer tab.mutex.Unlock() -// if l := len(tab.buckets[253].entries); l != bucketSize { -// t.Errorf("wrong bucket size after bond: got %d, want %d", l, bucketSize) -// } -// -// if lastInBucketIsResponding || !newNodeIsResponding { -// if !contains(tab.buckets[253].entries, last.ID) { -// t.Error("last entry was removed") -// } -// if contains(tab.buckets[253].entries, pingSender.ID) { -// t.Error("new entry was added") -// } -// } else { -// if contains(tab.buckets[253].entries, last.ID) { -// t.Error("last entry was not removed") -// } -// if !contains(tab.buckets[253].entries, pingSender.ID) { -// t.Error("new entry was not added") -// } -// } -// } -// -// doit(true, true) -// doit(false, true) -// doit(true, false) -// doit(false, false) -// } - -func TestBucket_bumpNoDuplicates(t *testing.T) { - t.Parallel() - cfg := &quick.Config{ - MaxCount: 1000, - Rand: rand.New(rand.NewSource(time.Now().Unix())), - Values: func(args []reflect.Value, rand *rand.Rand) { - // generate a random list of nodes. this will be the content of the bucket. - n := rand.Intn(bucketSize-1) + 1 - nodes := make([]*Node, n) - for i := range nodes { - nodes[i] = nodeAtDistance(common.Hash{}, 200) - } - args[0] = reflect.ValueOf(nodes) - // generate random bump positions. - bumps := make([]int, rand.Intn(100)) - for i := range bumps { - bumps[i] = rand.Intn(len(nodes)) - } - args[1] = reflect.ValueOf(bumps) - }, - } - - prop := func(nodes []*Node, bumps []int) (ok bool) { - b := &bucket{entries: make([]*Node, len(nodes))} - copy(b.entries, nodes) - for i, pos := range bumps { - b.bump(b.entries[pos]) - if hasDuplicates(b.entries) { - t.Logf("bucket has duplicates after %d/%d bumps:", i+1, len(bumps)) - for _, n := range b.entries { - t.Logf(" %p", n) - } - return false - } - } - return true - } - if err := quick.Check(prop, cfg); err != nil { - t.Error(err) - } -} - -// fillBucket inserts nodes into the given bucket until -// it is full. The node's IDs dont correspond to their -// hashes. -func fillBucket(tab *Table, ld int) (last *Node) { - b := tab.buckets[ld] - 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) - copy(n.ID[:], n.sha[:]) // ensure the node still has a unique ID - return n -} - -type pingRecorder struct{ responding, pinged map[NodeID]bool } - -func newPingRecorder() *pingRecorder { - return &pingRecorder{make(map[NodeID]bool), make(map[NodeID]bool)} -} - -func (t *pingRecorder) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node, error) { - panic("findnode called on pingRecorder") -} -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.pinged[toid] = true - if t.responding[toid] { - return nil - } else { - return errTimeout - } -} - -func TestTable_closest(t *testing.T) { - t.Parallel() - - test := func(test *closeTest) bool { - // for any node table, Target and N - tab := newTable(test.Self, &net.UDPAddr{}) - tab.stuff(test.All) - - // check that doClosest(Target, N) returns nodes - result := tab.closest(test.Target, test.N).entries - if hasDuplicates(result) { - t.Errorf("result contains duplicates") - return false - } - if !sortedByDistanceTo(test.Target, result) { - t.Errorf("result is not sorted by distance to target") - return false - } - - // check that the number of results is min(N, tablen) - wantN := test.N - if tab.count < test.N { - wantN = tab.count - } - if len(result) != wantN { - t.Errorf("wrong number of nodes: got %d, want %d", len(result), wantN) - return false - } else if len(result) == 0 { - return true // no need to check distance - } - - // check that the result nodes have minimum distance to target. - for _, b := range tab.buckets { - for _, n := range b.entries { - if contains(result, n.ID) { - continue // don't run the check below for nodes in result - } - farthestResult := result[len(result)-1].sha - 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.Logf(" Target: %v", test.Target) - t.Logf(" Farthest Result: %v", farthestResult) - t.Logf(" ID: %v", n.ID) - return false - } - } - } - return true - } - if err := quick.Check(test, quickcfg()); err != nil { - t.Error(err) - } -} - -func TestTable_ReadRandomNodesGetAll(t *testing.T) { - cfg := &quick.Config{ - MaxCount: 200, - Rand: rand.New(rand.NewSource(time.Now().Unix())), - Values: func(args []reflect.Value, rand *rand.Rand) { - args[0] = reflect.ValueOf(make([]*Node, rand.Intn(1000))) - }, - } - test := func(buf []*Node) bool { - tab := newTable(NodeID{}, &net.UDPAddr{}) - for i := 0; i < len(buf); i++ { - ld := cfg.Rand.Intn(len(tab.buckets)) - tab.stuff([]*Node{nodeAtDistance(tab.self.sha, ld)}) - } - gotN := tab.readRandomNodes(buf) - if gotN != tab.count { - t.Errorf("wrong number of nodes, got %d, want %d", gotN, tab.count) - return false - } - if hasDuplicates(buf[:gotN]) { - t.Errorf("result contains duplicates") - return false - } - return true - } - if err := quick.Check(test, cfg); err != nil { - t.Error(err) - } -} - -type closeTest struct { - Self NodeID - Target common.Hash - All []*Node - N int -} - -func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value { - t := &closeTest{ - Self: gen(NodeID{}, rand).(NodeID), - Target: gen(common.Hash{}, rand).(common.Hash), - N: rand.Intn(bucketSize), - } - for _, id := range gen([]NodeID{}, rand).([]NodeID) { - t.All = append(t.All, &Node{ID: id}) - } - return reflect.ValueOf(t) -} - -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. -// it generates a random value of the given value's type. -func gen(typ interface{}, rand *rand.Rand) interface{} { - v, ok := quick.Value(reflect.TypeOf(typ), rand) - if !ok { - panic(fmt.Sprintf("couldn't generate random value of type %T", typ)) - } - return v.Interface() -} - -func newkey() *ecdsa.PrivateKey { - key, err := crypto.GenerateKey() - if err != nil { - panic("couldn't generate key: " + err.Error()) - } - return key -} diff --git a/p2p/discv5/ticket.go b/p2p/discv5/ticket.go deleted file mode 100644 index fddc38beff..0000000000 --- a/p2p/discv5/ticket.go +++ /dev/null @@ -1,954 +0,0 @@ -// Copyright 2016 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 . - -package discv5 - -import ( - "bytes" - "encoding/binary" - "fmt" - "math" - "math/rand" - "sort" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/common/mclock" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/log" -) - -const ( - ticketTimeBucketLen = time.Minute - timeWindow = 10 // * ticketTimeBucketLen - wantTicketsInWindow = 10 - collectFrequency = time.Second * 30 - registerFrequency = time.Second * 60 - maxCollectDebt = 10 - maxRegisterDebt = 5 - keepTicketConst = time.Minute * 10 - keepTicketExp = time.Minute * 5 - targetWaitTime = time.Minute * 10 - topicQueryTimeout = time.Second * 5 - topicQueryResend = time.Minute - // topic radius detection - maxRadius = 0xffffffffffffffff - radiusTC = time.Minute * 20 - radiusBucketsPerBit = 8 - minSlope = 1 - minPeakSize = 40 - maxNoAdjust = 20 - lookupWidth = 8 - minRightSum = 20 - searchForceQuery = 4 -) - -// timeBucket represents absolute monotonic time in minutes. -// It is used as the index into the per-topic ticket buckets. -type timeBucket int - -type ticket struct { - topics []Topic - regTime []mclock.AbsTime // Per-topic local absolute time when the ticket can be used. - - // The serial number that was issued by the server. - serial uint32 - // Used by registrar, tracks absolute time when the ticket was created. - issueTime mclock.AbsTime - - // Fields used only by registrants - node *Node // the registrar node that signed this ticket - refCnt int // tracks number of topics that will be registered using this ticket - pong []byte // encoded pong packet signed by the registrar -} - -// ticketRef refers to a single topic in a ticket. -type ticketRef struct { - t *ticket - idx int // index of the topic in t.topics and t.regTime -} - -func (ref ticketRef) topic() Topic { - return ref.t.topics[ref.idx] -} - -func (ref ticketRef) topicRegTime() mclock.AbsTime { - return ref.t.regTime[ref.idx] -} - -func pongToTicket(localTime mclock.AbsTime, topics []Topic, node *Node, p *ingressPacket) (*ticket, error) { - wps := p.data.(*pong).WaitPeriods - if len(topics) != len(wps) { - return nil, fmt.Errorf("bad wait period list: got %d values, want %d", len(topics), len(wps)) - } - if rlpHash(topics) != p.data.(*pong).TopicHash { - return nil, fmt.Errorf("bad topic hash") - } - t := &ticket{ - issueTime: localTime, - node: node, - topics: topics, - pong: p.rawData, - regTime: make([]mclock.AbsTime, len(wps)), - } - // Convert wait periods to local absolute time. - for i, wp := range wps { - t.regTime[i] = localTime + mclock.AbsTime(time.Second*time.Duration(wp)) - } - return t, nil -} - -func ticketToPong(t *ticket, pong *pong) { - pong.Expiration = uint64(t.issueTime / mclock.AbsTime(time.Second)) - pong.TopicHash = rlpHash(t.topics) - pong.TicketSerial = t.serial - pong.WaitPeriods = make([]uint32, len(t.regTime)) - for i, regTime := range t.regTime { - pong.WaitPeriods[i] = uint32(time.Duration(regTime-t.issueTime) / time.Second) - } -} - -type ticketStore struct { - // radius detector and target address generator - // exists for both searched and registered topics - radius map[Topic]*topicRadius - - // Contains buckets (for each absolute minute) of tickets - // that can be used in that minute. - // This is only set if the topic is being registered. - tickets map[Topic]*topicTickets - - regQueue []Topic // Topic registration queue for round robin attempts - regSet map[Topic]struct{} // Topic registration queue contents for fast filling - - nodes map[*Node]*ticket - nodeLastReq map[*Node]reqInfo - - lastBucketFetched timeBucket - nextTicketCached *ticketRef - nextTicketReg mclock.AbsTime - - searchTopicMap map[Topic]searchTopic - nextTopicQueryCleanup mclock.AbsTime - queriesSent map[*Node]map[common.Hash]sentQuery -} - -type searchTopic struct { - foundChn chan<- *Node -} - -type sentQuery struct { - sent mclock.AbsTime - lookup lookupInfo -} - -type topicTickets struct { - buckets map[timeBucket][]ticketRef - nextLookup mclock.AbsTime - nextReg mclock.AbsTime -} - -func newTicketStore() *ticketStore { - return &ticketStore{ - radius: make(map[Topic]*topicRadius), - tickets: make(map[Topic]*topicTickets), - regSet: make(map[Topic]struct{}), - nodes: make(map[*Node]*ticket), - nodeLastReq: make(map[*Node]reqInfo), - searchTopicMap: make(map[Topic]searchTopic), - queriesSent: make(map[*Node]map[common.Hash]sentQuery), - } -} - -// addTopic starts tracking a topic. If register is true, -// the local node will register the topic and tickets will be collected. -func (s *ticketStore) addTopic(topic Topic, register bool) { - log.Trace("Adding discovery topic", "topic", topic, "register", register) - if s.radius[topic] == nil { - s.radius[topic] = newTopicRadius(topic) - } - if register && s.tickets[topic] == nil { - s.tickets[topic] = &topicTickets{buckets: make(map[timeBucket][]ticketRef)} - } -} - -func (s *ticketStore) addSearchTopic(t Topic, foundChn chan<- *Node) { - s.addTopic(t, false) - if s.searchTopicMap[t].foundChn == nil { - s.searchTopicMap[t] = searchTopic{foundChn: foundChn} - } -} - -func (s *ticketStore) removeSearchTopic(t Topic) { - if st := s.searchTopicMap[t]; st.foundChn != nil { - delete(s.searchTopicMap, t) - } -} - -// removeRegisterTopic deletes all tickets for the given topic. -func (s *ticketStore) removeRegisterTopic(topic Topic) { - log.Trace("Removing discovery topic", "topic", topic) - if s.tickets[topic] == nil { - log.Warn("Removing non-existent discovery topic", "topic", topic) - return - } - for _, list := range s.tickets[topic].buckets { - for _, ref := range list { - ref.t.refCnt-- - if ref.t.refCnt == 0 { - delete(s.nodes, ref.t.node) - delete(s.nodeLastReq, ref.t.node) - } - } - } - delete(s.tickets, topic) -} - -func (s *ticketStore) regTopicSet() []Topic { - topics := make([]Topic, 0, len(s.tickets)) - for topic := range s.tickets { - topics = append(topics, topic) - } - return topics -} - -// nextRegisterLookup returns the target of the next lookup for ticket collection. -func (s *ticketStore) nextRegisterLookup() (lookupInfo, time.Duration) { - // Queue up any new topics (or discarded ones), preserving iteration order - for topic := range s.tickets { - if _, ok := s.regSet[topic]; !ok { - s.regQueue = append(s.regQueue, topic) - s.regSet[topic] = struct{}{} - } - } - // Iterate over the set of all topics and look up the next suitable one - for len(s.regQueue) > 0 { - // Fetch the next topic from the queue, and ensure it still exists - topic := s.regQueue[0] - s.regQueue = s.regQueue[1:] - delete(s.regSet, topic) - - if s.tickets[topic] == nil { - continue - } - // If the topic needs more tickets, return it - if s.tickets[topic].nextLookup < mclock.Now() { - next, delay := s.radius[topic].nextTarget(false), 100*time.Millisecond - log.Trace("Found discovery topic to register", "topic", topic, "target", next.target, "delay", delay) - return next, delay - } - } - // No registration topics found or all exhausted, sleep - delay := 40 * time.Second - log.Trace("No topic found to register", "delay", delay) - return lookupInfo{}, delay -} - -func (s *ticketStore) nextSearchLookup(topic Topic) lookupInfo { - tr := s.radius[topic] - target := tr.nextTarget(tr.radiusLookupCnt >= searchForceQuery) - if target.radiusLookup { - tr.radiusLookupCnt++ - } else { - tr.radiusLookupCnt = 0 - } - return target -} - -// ticketsInWindow returns the tickets of a given topic in the registration window. -func (s *ticketStore) ticketsInWindow(topic Topic) []ticketRef { - // Sanity check that the topic still exists before operating on it - if s.tickets[topic] == nil { - log.Warn("Listing non-existing discovery tickets", "topic", topic) - return nil - } - // Gather all the tickers in the next time window - var tickets []ticketRef - - buckets := s.tickets[topic].buckets - for idx := timeBucket(0); idx < timeWindow; idx++ { - tickets = append(tickets, buckets[s.lastBucketFetched+idx]...) - } - log.Trace("Retrieved discovery registration tickets", "topic", topic, "from", s.lastBucketFetched, "tickets", len(tickets)) - return tickets -} - -func (s *ticketStore) removeExcessTickets(t Topic) { - tickets := s.ticketsInWindow(t) - if len(tickets) <= wantTicketsInWindow { - return - } - sort.Sort(ticketRefByWaitTime(tickets)) - for _, r := range tickets[wantTicketsInWindow:] { - s.removeTicketRef(r) - } -} - -type ticketRefByWaitTime []ticketRef - -// Len is the number of elements in the collection. -func (s ticketRefByWaitTime) Len() int { - return len(s) -} - -func (ref ticketRef) waitTime() mclock.AbsTime { - return ref.t.regTime[ref.idx] - ref.t.issueTime -} - -// Less reports whether the element with -// index i should sort before the element with index j. -func (s ticketRefByWaitTime) Less(i, j int) bool { - return s[i].waitTime() < s[j].waitTime() -} - -// Swap swaps the elements with indexes i and j. -func (s ticketRefByWaitTime) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -func (s *ticketStore) addTicketRef(r ticketRef) { - topic := r.t.topics[r.idx] - tickets := s.tickets[topic] - if tickets == nil { - log.Warn("Adding ticket to non-existent topic", "topic", topic) - return - } - bucket := timeBucket(r.t.regTime[r.idx] / mclock.AbsTime(ticketTimeBucketLen)) - tickets.buckets[bucket] = append(tickets.buckets[bucket], r) - r.t.refCnt++ - - min := mclock.Now() - mclock.AbsTime(collectFrequency)*maxCollectDebt - if tickets.nextLookup < min { - tickets.nextLookup = min - } - tickets.nextLookup += mclock.AbsTime(collectFrequency) - - //s.removeExcessTickets(topic) -} - -func (s *ticketStore) nextFilteredTicket() (*ticketRef, time.Duration) { - now := mclock.Now() - for { - ticket, wait := s.nextRegisterableTicket() - if ticket == nil { - return ticket, wait - } - log.Trace("Found discovery ticket to register", "node", ticket.t.node, "serial", ticket.t.serial, "wait", wait) - - regTime := now + mclock.AbsTime(wait) - topic := ticket.t.topics[ticket.idx] - if s.tickets[topic] != nil && regTime >= s.tickets[topic].nextReg { - return ticket, wait - } - s.removeTicketRef(*ticket) - } -} - -func (s *ticketStore) ticketRegistered(ref ticketRef) { - now := mclock.Now() - - topic := ref.t.topics[ref.idx] - tickets := s.tickets[topic] - min := now - mclock.AbsTime(registerFrequency)*maxRegisterDebt - if min > tickets.nextReg { - tickets.nextReg = min - } - tickets.nextReg += mclock.AbsTime(registerFrequency) - s.tickets[topic] = tickets - - s.removeTicketRef(ref) -} - -// nextRegisterableTicket returns the next ticket that can be used -// to register. -// -// If the returned wait time <= zero the ticket can be used. For a positive -// wait time, the caller should requery the next ticket later. -// -// A ticket can be returned more than once with <= zero wait time in case -// the ticket contains multiple topics. -func (s *ticketStore) nextRegisterableTicket() (*ticketRef, time.Duration) { - now := mclock.Now() - if s.nextTicketCached != nil { - return s.nextTicketCached, time.Duration(s.nextTicketCached.topicRegTime() - now) - } - - for bucket := s.lastBucketFetched; ; bucket++ { - var ( - empty = true // true if there are no tickets - nextTicket ticketRef // uninitialized if this bucket is empty - ) - for _, tickets := range s.tickets { - //s.removeExcessTickets(topic) - if len(tickets.buckets) != 0 { - empty = false - - list := tickets.buckets[bucket] - for _, ref := range list { - //debugLog(fmt.Sprintf(" nrt bucket = %d node = %x sn = %v wait = %v", bucket, ref.t.node.ID[:8], ref.t.serial, time.Duration(ref.topicRegTime()-now))) - if nextTicket.t == nil || ref.topicRegTime() < nextTicket.topicRegTime() { - nextTicket = ref - } - } - } - } - if empty { - return nil, 0 - } - if nextTicket.t != nil { - s.nextTicketCached = &nextTicket - return &nextTicket, time.Duration(nextTicket.topicRegTime() - now) - } - s.lastBucketFetched = bucket - } -} - -// removeTicket removes a ticket from the ticket store -func (s *ticketStore) removeTicketRef(ref ticketRef) { - log.Trace("Removing discovery ticket reference", "node", ref.t.node.ID, "serial", ref.t.serial) - - // Make nextRegisterableTicket return the next available ticket. - s.nextTicketCached = nil - - topic := ref.topic() - tickets := s.tickets[topic] - - if tickets == nil { - log.Trace("Removing tickets from unknown topic", "topic", topic) - return - } - bucket := timeBucket(ref.t.regTime[ref.idx] / mclock.AbsTime(ticketTimeBucketLen)) - list := tickets.buckets[bucket] - idx := -1 - for i, bt := range list { - if bt.t == ref.t { - idx = i - break - } - } - if idx == -1 { - panic(nil) - } - list = append(list[:idx], list[idx+1:]...) - if len(list) != 0 { - tickets.buckets[bucket] = list - } else { - delete(tickets.buckets, bucket) - } - ref.t.refCnt-- - if ref.t.refCnt == 0 { - delete(s.nodes, ref.t.node) - delete(s.nodeLastReq, ref.t.node) - } -} - -type lookupInfo struct { - target common.Hash - topic Topic - radiusLookup bool -} - -type reqInfo struct { - pingHash []byte - lookup lookupInfo - time mclock.AbsTime -} - -// returns -1 if not found -func (t *ticket) findIdx(topic Topic) int { - for i, tt := range t.topics { - if tt == topic { - return i - } - } - return -1 -} - -func (s *ticketStore) registerLookupDone(lookup lookupInfo, nodes []*Node, ping func(n *Node) []byte) { - now := mclock.Now() - for i, n := range nodes { - if i == 0 || (binary.BigEndian.Uint64(n.sha[:8])^binary.BigEndian.Uint64(lookup.target[:8])) < s.radius[lookup.topic].minRadius { - if lookup.radiusLookup { - if lastReq, ok := s.nodeLastReq[n]; !ok || time.Duration(now-lastReq.time) > radiusTC { - s.nodeLastReq[n] = reqInfo{pingHash: ping(n), lookup: lookup, time: now} - } - } else { - if s.nodes[n] == nil { - s.nodeLastReq[n] = reqInfo{pingHash: ping(n), lookup: lookup, time: now} - } - } - } - } -} - -func (s *ticketStore) searchLookupDone(lookup lookupInfo, nodes []*Node, query func(n *Node, topic Topic) []byte) { - now := mclock.Now() - for i, n := range nodes { - if i == 0 || (binary.BigEndian.Uint64(n.sha[:8])^binary.BigEndian.Uint64(lookup.target[:8])) < s.radius[lookup.topic].minRadius { - if lookup.radiusLookup { - if lastReq, ok := s.nodeLastReq[n]; !ok || time.Duration(now-lastReq.time) > radiusTC { - s.nodeLastReq[n] = reqInfo{pingHash: nil, lookup: lookup, time: now} - } - } // else { - if s.canQueryTopic(n, lookup.topic) { - hash := query(n, lookup.topic) - if hash != nil { - s.addTopicQuery(common.BytesToHash(hash), n, lookup) - } - } - //} - } - } -} - -func (s *ticketStore) adjustWithTicket(now mclock.AbsTime, targetHash common.Hash, t *ticket) { - for i, topic := range t.topics { - if tt, ok := s.radius[topic]; ok { - tt.adjustWithTicket(now, targetHash, ticketRef{t, i}) - } - } -} - -func (s *ticketStore) addTicket(localTime mclock.AbsTime, pingHash []byte, ticket *ticket) { - log.Trace("Adding discovery ticket", "node", ticket.node.ID, "serial", ticket.serial) - - lastReq, ok := s.nodeLastReq[ticket.node] - if !(ok && bytes.Equal(pingHash, lastReq.pingHash)) { - return - } - s.adjustWithTicket(localTime, lastReq.lookup.target, ticket) - - if lastReq.lookup.radiusLookup || s.nodes[ticket.node] != nil { - return - } - - topic := lastReq.lookup.topic - topicIdx := ticket.findIdx(topic) - if topicIdx == -1 { - return - } - - bucket := timeBucket(localTime / mclock.AbsTime(ticketTimeBucketLen)) - if s.lastBucketFetched == 0 || bucket < s.lastBucketFetched { - s.lastBucketFetched = bucket - } - - if _, ok := s.tickets[topic]; ok { - wait := ticket.regTime[topicIdx] - localTime - rnd := rand.ExpFloat64() - if rnd > 10 { - rnd = 10 - } - if float64(wait) < float64(keepTicketConst)+float64(keepTicketExp)*rnd { - // use the ticket to register this topic - //fmt.Println("addTicket", ticket.node.ID[:8], ticket.node.addr().String(), ticket.serial, ticket.pong) - s.addTicketRef(ticketRef{ticket, topicIdx}) - } - } - - if ticket.refCnt > 0 { - s.nextTicketCached = nil - s.nodes[ticket.node] = ticket - } -} - -func (s *ticketStore) getNodeTicket(node *Node) *ticket { - if s.nodes[node] == nil { - log.Trace("Retrieving node ticket", "node", node.ID, "serial", nil) - } else { - log.Trace("Retrieving node ticket", "node", node.ID, "serial", s.nodes[node].serial) - } - return s.nodes[node] -} - -func (s *ticketStore) canQueryTopic(node *Node, topic Topic) bool { - qq := s.queriesSent[node] - if qq != nil { - now := mclock.Now() - for _, sq := range qq { - if sq.lookup.topic == topic && sq.sent > now-mclock.AbsTime(topicQueryResend) { - return false - } - } - } - return true -} - -func (s *ticketStore) addTopicQuery(hash common.Hash, node *Node, lookup lookupInfo) { - now := mclock.Now() - qq := s.queriesSent[node] - if qq == nil { - qq = make(map[common.Hash]sentQuery) - s.queriesSent[node] = qq - } - qq[hash] = sentQuery{sent: now, lookup: lookup} - s.cleanupTopicQueries(now) -} - -func (s *ticketStore) cleanupTopicQueries(now mclock.AbsTime) { - if s.nextTopicQueryCleanup > now { - return - } - exp := now - mclock.AbsTime(topicQueryResend) - for n, qq := range s.queriesSent { - for h, q := range qq { - if q.sent < exp { - delete(qq, h) - } - } - if len(qq) == 0 { - delete(s.queriesSent, n) - } - } - s.nextTopicQueryCleanup = now + mclock.AbsTime(topicQueryTimeout) -} - -func (s *ticketStore) gotTopicNodes(from *Node, hash common.Hash, nodes []rpcNode) (timeout bool) { - now := mclock.Now() - //fmt.Println("got", from.addr().String(), hash, len(nodes)) - qq := s.queriesSent[from] - if qq == nil { - return true - } - q, ok := qq[hash] - if !ok || now > q.sent+mclock.AbsTime(topicQueryTimeout) { - return true - } - inside := float64(0) - if len(nodes) > 0 { - inside = 1 - } - s.radius[q.lookup.topic].adjust(now, q.lookup.target, from.sha, inside) - chn := s.searchTopicMap[q.lookup.topic].foundChn - if chn == nil { - //fmt.Println("no channel") - return false - } - for _, node := range nodes { - ip := node.IP - if ip.IsUnspecified() || ip.IsLoopback() { - ip = from.IP - } - n := NewNode(node.ID, ip, node.UDP, node.TCP) - select { - case chn <- n: - default: - return false - } - } - return false -} - -type topicRadius struct { - topic Topic - topicHashPrefix uint64 - radius, minRadius uint64 - buckets []topicRadiusBucket - converged bool - radiusLookupCnt int -} - -type topicRadiusEvent int - -const ( - trOutside topicRadiusEvent = iota - trInside - trNoAdjust - trCount -) - -type topicRadiusBucket struct { - weights [trCount]float64 - lastTime mclock.AbsTime - value float64 - lookupSent map[common.Hash]mclock.AbsTime -} - -func (b *topicRadiusBucket) update(now mclock.AbsTime) { - if now == b.lastTime { - return - } - exp := math.Exp(-float64(now-b.lastTime) / float64(radiusTC)) - for i, w := range b.weights { - b.weights[i] = w * exp - } - b.lastTime = now - - for target, tm := range b.lookupSent { - if now-tm > mclock.AbsTime(respTimeout) { - b.weights[trNoAdjust] += 1 - delete(b.lookupSent, target) - } - } -} - -func (b *topicRadiusBucket) adjust(now mclock.AbsTime, inside float64) { - b.update(now) - if inside <= 0 { - b.weights[trOutside] += 1 - } else { - if inside >= 1 { - b.weights[trInside] += 1 - } else { - b.weights[trInside] += inside - b.weights[trOutside] += 1 - inside - } - } -} - -func newTopicRadius(t Topic) *topicRadius { - topicHash := crypto.Keccak256Hash([]byte(t)) - topicHashPrefix := binary.BigEndian.Uint64(topicHash[0:8]) - - return &topicRadius{ - topic: t, - topicHashPrefix: topicHashPrefix, - radius: maxRadius, - minRadius: maxRadius, - } -} - -func (r *topicRadius) getBucketIdx(addrHash common.Hash) int { - prefix := binary.BigEndian.Uint64(addrHash[0:8]) - var log2 float64 - if prefix != r.topicHashPrefix { - log2 = math.Log2(float64(prefix ^ r.topicHashPrefix)) - } - bucket := int((64 - log2) * radiusBucketsPerBit) - max := 64*radiusBucketsPerBit - 1 - if bucket > max { - return max - } - if bucket < 0 { - return 0 - } - return bucket -} - -func (r *topicRadius) targetForBucket(bucket int) common.Hash { - min := math.Pow(2, 64-float64(bucket+1)/radiusBucketsPerBit) - max := math.Pow(2, 64-float64(bucket)/radiusBucketsPerBit) - a := uint64(min) - b := randUint64n(uint64(max - min)) - xor := a + b - if xor < a { - xor = ^uint64(0) - } - prefix := r.topicHashPrefix ^ xor - var target common.Hash - binary.BigEndian.PutUint64(target[0:8], prefix) - globalRandRead(target[8:]) - return target -} - -// package rand provides a Read function in Go 1.6 and later, but -// we can't use it yet because we still support Go 1.5. -func globalRandRead(b []byte) { - pos := 0 - val := 0 - for n := 0; n < len(b); n++ { - if pos == 0 { - val = rand.Int() - pos = 7 - } - b[n] = byte(val) - val >>= 8 - pos-- - } -} - -func (r *topicRadius) isInRadius(addrHash common.Hash) bool { - nodePrefix := binary.BigEndian.Uint64(addrHash[0:8]) - dist := nodePrefix ^ r.topicHashPrefix - return dist < r.radius -} - -func (r *topicRadius) chooseLookupBucket(a, b int) int { - if a < 0 { - a = 0 - } - if a > b { - return -1 - } - c := 0 - for i := a; i <= b; i++ { - if i >= len(r.buckets) || r.buckets[i].weights[trNoAdjust] < maxNoAdjust { - c++ - } - } - if c == 0 { - return -1 - } - rnd := randUint(uint32(c)) - for i := a; i <= b; i++ { - if i >= len(r.buckets) || r.buckets[i].weights[trNoAdjust] < maxNoAdjust { - if rnd == 0 { - return i - } - rnd-- - } - } - panic(nil) // should never happen -} - -func (r *topicRadius) needMoreLookups(a, b int, maxValue float64) bool { - var max float64 - if a < 0 { - a = 0 - } - if b >= len(r.buckets) { - b = len(r.buckets) - 1 - if r.buckets[b].value > max { - max = r.buckets[b].value - } - } - if b >= a { - for i := a; i <= b; i++ { - if r.buckets[i].value > max { - max = r.buckets[i].value - } - } - } - return maxValue-max < minPeakSize -} - -func (r *topicRadius) recalcRadius() (radius uint64, radiusLookup int) { - maxBucket := 0 - maxValue := float64(0) - now := mclock.Now() - v := float64(0) - for i := range r.buckets { - r.buckets[i].update(now) - v += r.buckets[i].weights[trOutside] - r.buckets[i].weights[trInside] - r.buckets[i].value = v - //fmt.Printf("%v %v | ", v, r.buckets[i].weights[trNoAdjust]) - } - //fmt.Println() - slopeCross := -1 - for i, b := range r.buckets { - v := b.value - if v < float64(i)*minSlope { - slopeCross = i - break - } - if v > maxValue { - maxValue = v - maxBucket = i + 1 - } - } - - minRadBucket := len(r.buckets) - sum := float64(0) - for minRadBucket > 0 && sum < minRightSum { - minRadBucket-- - b := r.buckets[minRadBucket] - sum += b.weights[trInside] + b.weights[trOutside] - } - r.minRadius = uint64(math.Pow(2, 64-float64(minRadBucket)/radiusBucketsPerBit)) - - lookupLeft := -1 - if r.needMoreLookups(0, maxBucket-lookupWidth-1, maxValue) { - lookupLeft = r.chooseLookupBucket(maxBucket-lookupWidth, maxBucket-1) - } - lookupRight := -1 - if slopeCross != maxBucket && (minRadBucket <= maxBucket || r.needMoreLookups(maxBucket+lookupWidth, len(r.buckets)-1, maxValue)) { - for len(r.buckets) <= maxBucket+lookupWidth { - r.buckets = append(r.buckets, topicRadiusBucket{lookupSent: make(map[common.Hash]mclock.AbsTime)}) - } - lookupRight = r.chooseLookupBucket(maxBucket, maxBucket+lookupWidth-1) - } - if lookupLeft == -1 { - radiusLookup = lookupRight - } else { - if lookupRight == -1 { - radiusLookup = lookupLeft - } else { - if randUint(2) == 0 { - radiusLookup = lookupLeft - } else { - radiusLookup = lookupRight - } - } - } - - //fmt.Println("mb", maxBucket, "sc", slopeCross, "mrb", minRadBucket, "ll", lookupLeft, "lr", lookupRight, "mv", maxValue) - - if radiusLookup == -1 { - // no more radius lookups needed at the moment, return a radius - r.converged = true - rad := maxBucket - if minRadBucket < rad { - rad = minRadBucket - } - radius = ^uint64(0) - if rad > 0 { - radius = uint64(math.Pow(2, 64-float64(rad)/radiusBucketsPerBit)) - } - r.radius = radius - } - - return -} - -func (r *topicRadius) nextTarget(forceRegular bool) lookupInfo { - if !forceRegular { - _, radiusLookup := r.recalcRadius() - if radiusLookup != -1 { - target := r.targetForBucket(radiusLookup) - r.buckets[radiusLookup].lookupSent[target] = mclock.Now() - return lookupInfo{target: target, topic: r.topic, radiusLookup: true} - } - } - - radExt := r.radius / 2 - if radExt > maxRadius-r.radius { - radExt = maxRadius - r.radius - } - rnd := randUint64n(r.radius) + randUint64n(2*radExt) - if rnd > radExt { - rnd -= radExt - } else { - rnd = radExt - rnd - } - - prefix := r.topicHashPrefix ^ rnd - var target common.Hash - binary.BigEndian.PutUint64(target[0:8], prefix) - globalRandRead(target[8:]) - return lookupInfo{target: target, topic: r.topic, radiusLookup: false} -} - -func (r *topicRadius) adjustWithTicket(now mclock.AbsTime, targetHash common.Hash, t ticketRef) { - wait := t.t.regTime[t.idx] - t.t.issueTime - inside := float64(wait)/float64(targetWaitTime) - 0.5 - if inside > 1 { - inside = 1 - } - if inside < 0 { - inside = 0 - } - r.adjust(now, targetHash, t.t.node.sha, inside) -} - -func (r *topicRadius) adjust(now mclock.AbsTime, targetHash, addrHash common.Hash, inside float64) { - bucket := r.getBucketIdx(addrHash) - //fmt.Println("adjust", bucket, len(r.buckets), inside) - if bucket >= len(r.buckets) { - return - } - r.buckets[bucket].adjust(now, inside) - delete(r.buckets[bucket].lookupSent, targetHash) -} diff --git a/p2p/discv5/topic.go b/p2p/discv5/topic.go deleted file mode 100644 index 9162402d0e..0000000000 --- a/p2p/discv5/topic.go +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright 2016 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 . - -package discv5 - -import ( - "container/heap" - "fmt" - "math" - "math/rand" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common/mclock" - "github.com/AlayaNetwork/Alaya-Go/log" -) - -const ( - maxEntries = 10000 - maxEntriesPerTopic = 50 - - fallbackRegistrationExpiry = 1 * time.Hour -) - -type Topic string - -type topicEntry struct { - topic Topic - fifoIdx uint64 - node *Node - expire mclock.AbsTime -} - -type topicInfo struct { - entries map[uint64]*topicEntry - fifoHead, fifoTail uint64 - rqItem *topicRequestQueueItem - wcl waitControlLoop -} - -// removes tail element from the fifo -func (t *topicInfo) getFifoTail() *topicEntry { - for t.entries[t.fifoTail] == nil { - t.fifoTail++ - } - tail := t.entries[t.fifoTail] - t.fifoTail++ - return tail -} - -type nodeInfo struct { - entries map[Topic]*topicEntry - lastIssuedTicket, lastUsedTicket uint32 - // you can't register a ticket newer than lastUsedTicket before noRegUntil (absolute time) - noRegUntil mclock.AbsTime -} - -type topicTable struct { - db *nodeDB - self *Node - nodes map[*Node]*nodeInfo - topics map[Topic]*topicInfo - globalEntries uint64 - requested topicRequestQueue - requestCnt uint64 - lastGarbageCollection mclock.AbsTime -} - -func newTopicTable(db *nodeDB, self *Node) *topicTable { - if printTestImgLogs { - fmt.Printf("*N %016x\n", self.sha[:8]) - } - return &topicTable{ - db: db, - nodes: make(map[*Node]*nodeInfo), - topics: make(map[Topic]*topicInfo), - self: self, - } -} - -func (t *topicTable) getOrNewTopic(topic Topic) *topicInfo { - ti := t.topics[topic] - if ti == nil { - rqItem := &topicRequestQueueItem{ - topic: topic, - priority: t.requestCnt, - } - ti = &topicInfo{ - entries: make(map[uint64]*topicEntry), - rqItem: rqItem, - } - t.topics[topic] = ti - heap.Push(&t.requested, rqItem) - } - return ti -} - -func (t *topicTable) checkDeleteTopic(topic Topic) { - ti := t.topics[topic] - if ti == nil { - return - } - if len(ti.entries) == 0 && ti.wcl.hasMinimumWaitPeriod() { - delete(t.topics, topic) - heap.Remove(&t.requested, ti.rqItem.index) - } -} - -func (t *topicTable) getOrNewNode(node *Node) *nodeInfo { - n := t.nodes[node] - if n == nil { - //fmt.Printf("newNode %016x %016x\n", t.self.sha[:8], node.sha[:8]) - var issued, used uint32 - if t.db != nil { - issued, used = t.db.fetchTopicRegTickets(node.ID) - } - n = &nodeInfo{ - entries: make(map[Topic]*topicEntry), - lastIssuedTicket: issued, - lastUsedTicket: used, - } - t.nodes[node] = n - } - return n -} - -func (t *topicTable) checkDeleteNode(node *Node) { - if n, ok := t.nodes[node]; ok && len(n.entries) == 0 && n.noRegUntil < mclock.Now() { - //fmt.Printf("deleteNode %016x %016x\n", t.self.sha[:8], node.sha[:8]) - delete(t.nodes, node) - } -} - -func (t *topicTable) storeTicketCounters(node *Node) { - n := t.getOrNewNode(node) - if t.db != nil { - t.db.updateTopicRegTickets(node.ID, n.lastIssuedTicket, n.lastUsedTicket) - } -} - -func (t *topicTable) getEntries(topic Topic) []*Node { - t.collectGarbage() - - te := t.topics[topic] - if te == nil { - return nil - } - nodes := make([]*Node, len(te.entries)) - i := 0 - for _, e := range te.entries { - nodes[i] = e.node - i++ - } - t.requestCnt++ - t.requested.update(te.rqItem, t.requestCnt) - return nodes -} - -func (t *topicTable) addEntry(node *Node, topic Topic) { - n := t.getOrNewNode(node) - // clear previous entries by the same node - for _, e := range n.entries { - t.deleteEntry(e) - } - // *** - n = t.getOrNewNode(node) - - tm := mclock.Now() - te := t.getOrNewTopic(topic) - - if len(te.entries) == maxEntriesPerTopic { - t.deleteEntry(te.getFifoTail()) - } - - if t.globalEntries == maxEntries { - t.deleteEntry(t.leastRequested()) // not empty, no need to check for nil - } - - fifoIdx := te.fifoHead - te.fifoHead++ - entry := &topicEntry{ - topic: topic, - fifoIdx: fifoIdx, - node: node, - expire: tm + mclock.AbsTime(fallbackRegistrationExpiry), - } - if printTestImgLogs { - fmt.Printf("*+ %d %v %016x %016x\n", tm/1000000, topic, t.self.sha[:8], node.sha[:8]) - } - te.entries[fifoIdx] = entry - n.entries[topic] = entry - t.globalEntries++ - te.wcl.registered(tm) -} - -// removes least requested element from the fifo -func (t *topicTable) leastRequested() *topicEntry { - for t.requested.Len() > 0 && t.topics[t.requested[0].topic] == nil { - heap.Pop(&t.requested) - } - if t.requested.Len() == 0 { - return nil - } - return t.topics[t.requested[0].topic].getFifoTail() -} - -// entry should exist -func (t *topicTable) deleteEntry(e *topicEntry) { - if printTestImgLogs { - fmt.Printf("*- %d %v %016x %016x\n", mclock.Now()/1000000, e.topic, t.self.sha[:8], e.node.sha[:8]) - } - ne := t.nodes[e.node].entries - delete(ne, e.topic) - if len(ne) == 0 { - t.checkDeleteNode(e.node) - } - te := t.topics[e.topic] - delete(te.entries, e.fifoIdx) - if len(te.entries) == 0 { - t.checkDeleteTopic(e.topic) - } - t.globalEntries-- -} - -// It is assumed that topics and waitPeriods have the same length. -func (t *topicTable) useTicket(node *Node, serialNo uint32, topics []Topic, idx int, issueTime uint64, waitPeriods []uint32) (registered bool) { - log.Trace("Using discovery ticket", "serial", serialNo, "topics", topics, "waits", waitPeriods) - //fmt.Println("useTicket", serialNo, topics, waitPeriods) - t.collectGarbage() - - n := t.getOrNewNode(node) - if serialNo < n.lastUsedTicket { - return false - } - - tm := mclock.Now() - if serialNo > n.lastUsedTicket && tm < n.noRegUntil { - return false - } - if serialNo != n.lastUsedTicket { - n.lastUsedTicket = serialNo - n.noRegUntil = tm + mclock.AbsTime(noRegTimeout()) - t.storeTicketCounters(node) - } - - currTime := uint64(tm / mclock.AbsTime(time.Second)) - regTime := issueTime + uint64(waitPeriods[idx]) - relTime := int64(currTime - regTime) - if relTime >= -1 && relTime <= regTimeWindow+1 { // give clients a little security margin on both ends - if e := n.entries[topics[idx]]; e == nil { - t.addEntry(node, topics[idx]) - } else { - // if there is an active entry, don't move to the front of the FIFO but prolong expire time - e.expire = tm + mclock.AbsTime(fallbackRegistrationExpiry) - } - return true - } - - return false -} - -func (t *topicTable) getTicket(node *Node, topics []Topic) *ticket { - t.collectGarbage() - - now := mclock.Now() - n := t.getOrNewNode(node) - n.lastIssuedTicket++ - t.storeTicketCounters(node) - - tic := &ticket{ - issueTime: now, - topics: topics, - serial: n.lastIssuedTicket, - regTime: make([]mclock.AbsTime, len(topics)), - } - for i, topic := range topics { - var waitPeriod time.Duration - if topic := t.topics[topic]; topic != nil { - waitPeriod = topic.wcl.waitPeriod - } else { - waitPeriod = minWaitPeriod - } - - tic.regTime[i] = now + mclock.AbsTime(waitPeriod) - } - return tic -} - -const gcInterval = time.Minute - -func (t *topicTable) collectGarbage() { - tm := mclock.Now() - if time.Duration(tm-t.lastGarbageCollection) < gcInterval { - return - } - t.lastGarbageCollection = tm - - for node, n := range t.nodes { - for _, e := range n.entries { - if e.expire <= tm { - t.deleteEntry(e) - } - } - - t.checkDeleteNode(node) - } - - for topic := range t.topics { - t.checkDeleteTopic(topic) - } -} - -const ( - minWaitPeriod = time.Minute - regTimeWindow = 10 // seconds - avgnoRegTimeout = time.Minute * 10 - // target average interval between two incoming ad requests - wcTargetRegInterval = time.Minute * 10 / maxEntriesPerTopic - // - wcTimeConst = time.Minute * 10 -) - -// initialization is not required, will set to minWaitPeriod at first registration -type waitControlLoop struct { - lastIncoming mclock.AbsTime - waitPeriod time.Duration -} - -func (w *waitControlLoop) registered(tm mclock.AbsTime) { - w.waitPeriod = w.nextWaitPeriod(tm) - w.lastIncoming = tm -} - -func (w *waitControlLoop) nextWaitPeriod(tm mclock.AbsTime) time.Duration { - period := tm - w.lastIncoming - wp := time.Duration(float64(w.waitPeriod) * math.Exp((float64(wcTargetRegInterval)-float64(period))/float64(wcTimeConst))) - if wp < minWaitPeriod { - wp = minWaitPeriod - } - return wp -} - -func (w *waitControlLoop) hasMinimumWaitPeriod() bool { - return w.nextWaitPeriod(mclock.Now()) == minWaitPeriod -} - -func noRegTimeout() time.Duration { - e := rand.ExpFloat64() - if e > 100 { - e = 100 - } - return time.Duration(float64(avgnoRegTimeout) * e) -} - -type topicRequestQueueItem struct { - topic Topic - priority uint64 - index int -} - -// A topicRequestQueue implements heap.Interface and holds topicRequestQueueItems. -type topicRequestQueue []*topicRequestQueueItem - -func (tq topicRequestQueue) Len() int { return len(tq) } - -func (tq topicRequestQueue) Less(i, j int) bool { - return tq[i].priority < tq[j].priority -} - -func (tq topicRequestQueue) Swap(i, j int) { - tq[i], tq[j] = tq[j], tq[i] - tq[i].index = i - tq[j].index = j -} - -func (tq *topicRequestQueue) Push(x interface{}) { - n := len(*tq) - item := x.(*topicRequestQueueItem) - item.index = n - *tq = append(*tq, item) -} - -func (tq *topicRequestQueue) Pop() interface{} { - old := *tq - n := len(old) - item := old[n-1] - item.index = -1 - *tq = old[0 : n-1] - return item -} - -func (tq *topicRequestQueue) update(item *topicRequestQueueItem, priority uint64) { - item.priority = priority - heap.Fix(tq, item.index) -} diff --git a/p2p/discv5/topic_test.go b/p2p/discv5/topic_test.go deleted file mode 100644 index 0fe7c24e92..0000000000 --- a/p2p/discv5/topic_test.go +++ /dev/null @@ -1,72 +0,0 @@ - -// Copyright 2016 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 . - -package discv5 - -import ( - "encoding/binary" - "testing" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/common/mclock" -) - -func TestTopicRadius(t *testing.T) { - now := mclock.Now() - topic := Topic("qwerty") - rad := newTopicRadius(topic) - targetRad := (^uint64(0)) / 100 - - waitFn := func(addr common.Hash) time.Duration { - prefix := binary.BigEndian.Uint64(addr[0:8]) - dist := prefix ^ rad.topicHashPrefix - relDist := float64(dist) / float64(targetRad) - relTime := (1 - relDist/2) * 2 - if relTime < 0 { - relTime = 0 - } - return time.Duration(float64(targetWaitTime) * relTime) - } - - bcnt := 0 - cnt := 0 - var sum float64 - for cnt < 100 { - addr := rad.nextTarget(false).target - wait := waitFn(addr) - ticket := &ticket{ - topics: []Topic{topic}, - regTime: []mclock.AbsTime{mclock.AbsTime(wait)}, - node: &Node{nodeNetGuts: nodeNetGuts{sha: addr}}, - } - rad.adjustWithTicket(now, addr, ticketRef{ticket, 0}) - if rad.radius != maxRadius { - cnt++ - sum += float64(rad.radius) - } else { - bcnt++ - if bcnt > 500 { - t.Errorf("Radius did not converge in 500 iterations") - } - } - } - avgRel := sum / float64(cnt) / float64(targetRad) - if avgRel > 1.05 || avgRel < 0.95 { - t.Errorf("Average/target ratio is too far from 1 (%v)", avgRel) - } -} diff --git a/p2p/discv5/udp.go b/p2p/discv5/udp.go deleted file mode 100644 index 684333294f..0000000000 --- a/p2p/discv5/udp.go +++ /dev/null @@ -1,445 +0,0 @@ -// Copyright 2016 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 . - -package discv5 - -import ( - "bytes" - "crypto/ecdsa" - "errors" - "fmt" - "net" - "time" - - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p/nat" - "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -const Version = 4 - -// Errors -var ( - errPacketTooSmall = errors.New("too small") - errBadPrefix = errors.New("bad prefix") - errTimeout = errors.New("RPC timeout") -) - -// Timeouts -const ( - respTimeout = 500 * time.Millisecond - expiration = 20 * time.Second - - driftThreshold = 10 * time.Second // Allowed clock drift before warning user -) - -// RPC request structures -type ( - ping struct { - Version uint - From, To rpcEndpoint - Expiration uint64 - - // v5 - Topics []Topic - - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // pong is the reply to ping. - pong struct { - // This field should mirror the UDP envelope address - // of the ping packet, which provides a way to discover the - // the external address (after NAT). - To rpcEndpoint - - ReplyTok []byte // This contains the hash of the ping packet. - Expiration uint64 // Absolute timestamp at which the packet becomes invalid. - - // v5 - TopicHash common.Hash - TicketSerial uint32 - WaitPeriods []uint32 - - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // findnode is a query for nodes close to the given target. - findnode struct { - Target NodeID // doesn't need to be an actual public key - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // findnode is a query for nodes close to the given target. - findnodeHash struct { - Target common.Hash - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // reply to findnode - neighbors struct { - Nodes []rpcNode - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - topicRegister struct { - Topics []Topic - Idx uint - Pong []byte - } - - topicQuery struct { - Topic Topic - Expiration uint64 - } - - // reply to topicQuery - topicNodes struct { - Echo common.Hash - Nodes []rpcNode - } - - rpcNode struct { - IP net.IP // len 4 for IPv4 or 16 for IPv6 - UDP uint16 // for discovery protocol - TCP uint16 // for RLPx protocol - ID NodeID - } - - rpcEndpoint struct { - IP net.IP // len 4 for IPv4 or 16 for IPv6 - UDP uint16 // for discovery protocol - TCP uint16 // for RLPx protocol - } -) - -var ( - versionPrefix = []byte("temporary discovery v5") - versionPrefixSize = len(versionPrefix) - sigSize = 520 / 8 - headSize = versionPrefixSize + sigSize // space of packet frame data -) - -// Neighbors replies are sent across multiple packets to -// stay below the 1280 byte limit. We compute the maximum number -// of entries by stuffing a packet until it grows too large. -var maxNeighbors = func() int { - p := neighbors{Expiration: ^uint64(0)} - maxSizeNode := rpcNode{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)} - for n := 0; ; n++ { - p.Nodes = append(p.Nodes, maxSizeNode) - size, _, err := rlp.EncodeToReader(p) - if err != nil { - // If this ever happens, it will be caught by the unit tests. - panic("cannot encode: " + err.Error()) - } - if headSize+size+1 >= 1280 { - return n - } - } -}() - -var maxTopicNodes = func() int { - p := topicNodes{} - maxSizeNode := rpcNode{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)} - for n := 0; ; n++ { - p.Nodes = append(p.Nodes, maxSizeNode) - size, _, err := rlp.EncodeToReader(p) - if err != nil { - // If this ever happens, it will be caught by the unit tests. - panic("cannot encode: " + err.Error()) - } - if headSize+size+1 >= 1280 { - return n - } - } -}() - -func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint { - ip := addr.IP.To4() - if ip == nil { - ip = addr.IP.To16() - } - return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort} -} - -func (e1 rpcEndpoint) equal(e2 rpcEndpoint) bool { - return e1.UDP == e2.UDP && e1.TCP == e2.TCP && e1.IP.Equal(e2.IP) -} - -func nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*Node, error) { - if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil { - return nil, err - } - n := NewNode(rn.ID, rn.IP, rn.UDP, rn.TCP) - err := n.validateComplete() - return n, err -} - -func nodeToRPC(n *Node) rpcNode { - return rpcNode{ID: n.ID, IP: n.IP, UDP: n.UDP, TCP: n.TCP} -} - -type ingressPacket struct { - remoteID NodeID - remoteAddr *net.UDPAddr - ev nodeEvent - hash []byte - data interface{} // one of the RPC structs - rawData []byte -} - -type conn interface { - ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) - WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) - Close() error - LocalAddr() net.Addr -} - -// udp implements the RPC protocol. -type udp struct { - conn conn - priv *ecdsa.PrivateKey - ourEndpoint rpcEndpoint - nat nat.Interface - net *Network -} - -// 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) { - realaddr := conn.LocalAddr().(*net.UDPAddr) - transport, err := listenUDP(priv, conn, realaddr) - if err != nil { - return nil, err - } - net, err := newNetwork(transport, priv.PublicKey, nodeDBPath, netrestrict) - if err != nil { - return nil, err - } - log.Info("UDP listener up", "net", net.tab.self) - transport.net = net - go transport.readLoop() - return net, nil -} - -func listenUDP(priv *ecdsa.PrivateKey, conn conn, realaddr *net.UDPAddr) (*udp, error) { - return &udp{conn: conn, priv: priv, ourEndpoint: makeEndpoint(realaddr, uint16(realaddr.Port))}, nil -} - -func (t *udp) localAddr() *net.UDPAddr { - return t.conn.LocalAddr().(*net.UDPAddr) -} - -func (t *udp) Close() { - t.conn.Close() -} - -func (t *udp) send(remote *Node, ptype nodeEvent, data interface{}) (hash []byte) { - hash, _ = t.sendPacket(remote.ID, remote.addr(), byte(ptype), data) - return hash -} - -func (t *udp) sendPing(remote *Node, toaddr *net.UDPAddr, topics []Topic) (hash []byte) { - hash, _ = t.sendPacket(remote.ID, toaddr, byte(pingPacket), ping{ - Version: Version, - From: t.ourEndpoint, - To: makeEndpoint(toaddr, uint16(toaddr.Port)), // TODO: maybe use known TCP port from DB - Expiration: uint64(time.Now().Add(expiration).Unix()), - Topics: topics, - }) - return hash -} - -func (t *udp) sendFindnode(remote *Node, target NodeID) { - t.sendPacket(remote.ID, remote.addr(), byte(findnodePacket), findnode{ - Target: target, - Expiration: uint64(time.Now().Add(expiration).Unix()), - }) -} - -func (t *udp) sendNeighbours(remote *Node, results []*Node) { - // Send neighbors in chunks with at most maxNeighbors per packet - // to stay below the 1280 byte limit. - p := neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())} - for i, result := range results { - p.Nodes = append(p.Nodes, nodeToRPC(result)) - if len(p.Nodes) == maxNeighbors || i == len(results)-1 { - t.sendPacket(remote.ID, remote.addr(), byte(neighborsPacket), p) - p.Nodes = p.Nodes[:0] - } - } -} - -func (t *udp) sendFindnodeHash(remote *Node, target common.Hash) { - t.sendPacket(remote.ID, remote.addr(), byte(findnodeHashPacket), findnodeHash{ - Target: target, - Expiration: uint64(time.Now().Add(expiration).Unix()), - }) -} - -func (t *udp) sendTopicRegister(remote *Node, topics []Topic, idx int, pong []byte) { - t.sendPacket(remote.ID, remote.addr(), byte(topicRegisterPacket), topicRegister{ - Topics: topics, - Idx: uint(idx), - Pong: pong, - }) -} - -func (t *udp) sendTopicNodes(remote *Node, queryHash common.Hash, nodes []*Node) { - p := topicNodes{Echo: queryHash} - var sent bool - for _, result := range nodes { - if result.IP.Equal(t.net.tab.self.IP) || netutil.CheckRelayIP(remote.IP, result.IP) == nil { - p.Nodes = append(p.Nodes, nodeToRPC(result)) - } - if len(p.Nodes) == maxTopicNodes { - t.sendPacket(remote.ID, remote.addr(), byte(topicNodesPacket), p) - p.Nodes = p.Nodes[:0] - sent = true - } - } - if !sent || len(p.Nodes) > 0 { - t.sendPacket(remote.ID, remote.addr(), byte(topicNodesPacket), p) - } -} - -func (t *udp) sendPacket(toid NodeID, toaddr *net.UDPAddr, ptype byte, req interface{}) (hash []byte, err error) { - //fmt.Println("sendPacket", nodeEvent(ptype), toaddr.String(), toid.String()) - packet, hash, err := encodePacket(t.priv, ptype, req) - if err != nil { - //fmt.Println(err) - return hash, err - } - log.Trace(fmt.Sprintf(">>> %v to %x@%v", nodeEvent(ptype), toid[:8], toaddr)) - if nbytes, err := t.conn.WriteToUDP(packet, toaddr); err != nil { - log.Trace(fmt.Sprint("UDP send failed:", err)) - } else { - egressTrafficMeter.Mark(int64(nbytes)) - } - //fmt.Println(err) - return hash, err -} - -// zeroed padding space for encodePacket. -var headSpace = make([]byte, headSize) - -func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) (p, hash []byte, err error) { - b := new(bytes.Buffer) - b.Write(headSpace) - b.WriteByte(ptype) - if err := rlp.Encode(b, req); err != nil { - log.Error(fmt.Sprint("error encoding packet:", err)) - return nil, nil, err - } - packet := b.Bytes() - sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv) - if err != nil { - log.Error(fmt.Sprint("could not sign packet:", err)) - return nil, nil, err - } - copy(packet, versionPrefix) - copy(packet[versionPrefixSize:], sig) - hash = crypto.Keccak256(packet[versionPrefixSize:]) - return packet, hash, nil -} - -// readLoop runs in its own goroutine. it injects ingress UDP packets -// into the network loop. -func (t *udp) readLoop() { - defer t.conn.Close() - // Discovery packets are defined to be no larger than 1280 bytes. - // Packets larger than this size will be cut at the end and treated - // as invalid because their hash won't match. - buf := make([]byte, 1280) - for { - nbytes, from, err := t.conn.ReadFromUDP(buf) - ingressTrafficMeter.Mark(int64(nbytes)) - if netutil.IsTemporaryError(err) { - // Ignore temporary read errors. - log.Debug(fmt.Sprintf("Temporary read error: %v", err)) - continue - } else if err != nil { - // Shut down the loop for permament errors. - log.Debug(fmt.Sprintf("Read error: %v", err)) - return - } - t.handlePacket(from, buf[:nbytes]) - } -} - -func (t *udp) handlePacket(from *net.UDPAddr, buf []byte) error { - pkt := ingressPacket{remoteAddr: from} - if err := decodePacket(buf, &pkt); err != nil { - log.Debug(fmt.Sprintf("Bad packet from %v: %v", from, err)) - //fmt.Println("bad packet", err) - return err - } - t.net.reqReadPacket(pkt) - return nil -} - -func decodePacket(buffer []byte, pkt *ingressPacket) error { - if len(buffer) < headSize+1 { - return errPacketTooSmall - } - buf := make([]byte, len(buffer)) - copy(buf, buffer) - prefix, sig, sigdata := buf[:versionPrefixSize], buf[versionPrefixSize:headSize], buf[headSize:] - if !bytes.Equal(prefix, versionPrefix) { - return errBadPrefix - } - fromID, err := recoverNodeID(crypto.Keccak256(buf[headSize:]), sig) - if err != nil { - return err - } - pkt.rawData = buf - pkt.hash = crypto.Keccak256(buf[versionPrefixSize:]) - pkt.remoteID = fromID - switch pkt.ev = nodeEvent(sigdata[0]); pkt.ev { - case pingPacket: - pkt.data = new(ping) - case pongPacket: - pkt.data = new(pong) - case findnodePacket: - pkt.data = new(findnode) - case neighborsPacket: - pkt.data = new(neighbors) - case findnodeHashPacket: - pkt.data = new(findnodeHash) - case topicRegisterPacket: - pkt.data = new(topicRegister) - case topicQueryPacket: - pkt.data = new(topicQuery) - case topicNodesPacket: - pkt.data = new(topicNodes) - default: - return fmt.Errorf("unknown packet type: %d", sigdata[0]) - } - s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0) - err = s.Decode(pkt.data) - return err -} diff --git a/p2p/discv5/udp_test.go b/p2p/discv5/udp_test.go deleted file mode 100644 index 148a89f1ec..0000000000 --- a/p2p/discv5/udp_test.go +++ /dev/null @@ -1,450 +0,0 @@ -// Copyright 2016 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 . - -package discv5 - -import ( - "encoding/hex" - "errors" - "io" - "net" - "reflect" - "sync" - "testing" - - "github.com/davecgh/go-spew/spew" - "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -func init() { - spew.Config.DisableMethods = true -} - -// shared test variables -var ( - testLocal = rpcEndpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6} -) - -// type udpTest struct { -// t *testing.T -// pipe *dgramPipe -// table *Table -// udp *udp -// sent [][]byte -// localkey, remotekey *ecdsa.PrivateKey -// remoteaddr *net.UDPAddr -// } -// -// func newUDPTest(t *testing.T) *udpTest { -// test := &udpTest{ -// t: t, -// pipe: newpipe(), -// localkey: newkey(), -// remotekey: newkey(), -// remoteaddr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 16789}, -// } -// test.table, test.udp, _ = newUDP(test.localkey, test.pipe, nil, "") -// return test -// } -// -// // handles a packet as if it had been sent to the transport. -// func (test *udpTest) packetIn(wantError error, ptype byte, data packet) error { -// enc, err := encodePacket(test.remotekey, ptype, data) -// if err != nil { -// return test.errorf("packet (%d) encode error: %v", ptype, err) -// } -// test.sent = append(test.sent, enc) -// if err = test.udp.handlePacket(test.remoteaddr, enc); err != wantError { -// return test.errorf("error mismatch: got %q, want %q", err, wantError) -// } -// return nil -// } -// -// // waits for a packet to be sent by the transport. -// // validate should have type func(*udpTest, X) error, where X is a packet type. -// func (test *udpTest) waitPacketOut(validate interface{}) error { -// dgram := test.pipe.waitPacketOut() -// p, _, _, err := decodePacket(dgram) -// if err != nil { -// return test.errorf("sent packet decode error: %v", err) -// } -// fn := reflect.ValueOf(validate) -// exptype := fn.Type().In(0) -// if reflect.TypeOf(p) != exptype { -// return test.errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype) -// } -// fn.Call([]reflect.Value{reflect.ValueOf(p)}) -// return nil -// } -// -// func (test *udpTest) errorf(format string, args ...interface{}) error { -// _, file, line, ok := runtime.Caller(2) // errorf + waitPacketOut -// if ok { -// file = filepath.Base(file) -// } else { -// file = "???" -// line = 1 -// } -// err := fmt.Errorf(format, args...) -// fmt.Printf("\t%s:%d: %v\n", file, line, err) -// test.t.Fail() -// return err -// } -// -// func TestUDP_packetErrors(t *testing.T) { -// test := newUDPTest(t) -// defer test.table.Close() -// -// test.packetIn(errExpired, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: Version}) -// test.packetIn(errUnsolicitedReply, pongPacket, &pong{ReplyTok: []byte{}, Expiration: futureExp}) -// test.packetIn(errUnknownNode, findnodePacket, &findnode{Expiration: futureExp}) -// test.packetIn(errUnsolicitedReply, neighborsPacket, &neighbors{Expiration: futureExp}) -// } -// -// func TestUDP_findnode(t *testing.T) { -// test := newUDPTest(t) -// defer test.table.Close() -// -// // put a few nodes into the table. their exact -// // distribution shouldn't matter much, although we need to -// // take care not to overflow any bucket. -// targetHash := crypto.Keccak256Hash(testTarget[:]) -// nodes := &nodesByDistance{target: targetHash} -// for i := 0; i < bucketSize; i++ { -// nodes.push(nodeAtDistance(test.table.self.sha, i+2), bucketSize) -// } -// test.table.stuff(nodes.entries) -// -// // ensure there's a bond with the test node, -// // findnode won't be accepted otherwise. -// test.table.db.updateNode(NewNode( -// PubkeyID(&test.remotekey.PublicKey), -// test.remoteaddr.IP, -// uint16(test.remoteaddr.Port), -// 99, -// )) -// // check that closest neighbors are returned. -// test.packetIn(nil, findnodePacket, &findnode{Target: testTarget, Expiration: futureExp}) -// expected := test.table.closest(targetHash, bucketSize) -// -// waitNeighbors := func(want []*Node) { -// test.waitPacketOut(func(p *neighbors) { -// if len(p.Nodes) != len(want) { -// t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSize) -// } -// for i := range p.Nodes { -// 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]) -// } -// } -// }) -// } -// waitNeighbors(expected.entries[:maxNeighbors]) -// waitNeighbors(expected.entries[maxNeighbors:]) -// } -// -// func TestUDP_findnodeMultiReply(t *testing.T) { -// test := newUDPTest(t) -// defer test.table.Close() -// -// // queue a pending findnode request -// resultc, errc := make(chan []*Node), make(chan error) -// go func() { -// rid := PubkeyID(&test.remotekey.PublicKey) -// ns, err := test.udp.findnode(rid, test.remoteaddr, testTarget) -// if err != nil && len(ns) == 0 { -// errc <- err -// } else { -// resultc <- ns -// } -// }() -// -// // wait for the findnode to be sent. -// // after it is sent, the transport is waiting for a reply -// test.waitPacketOut(func(p *findnode) { -// if p.Target != testTarget { -// t.Errorf("wrong target: got %v, want %v", p.Target, testTarget) -// } -// }) -// -// // send the reply as two packets. -// list := []*Node{ -// MustParseNode("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304"), -// MustParseNode("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303"), -// MustParseNode("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17"), -// MustParseNode("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303"), -// } -// rpclist := make([]rpcNode, len(list)) -// for i := range list { -// rpclist[i] = nodeToRPC(list[i]) -// } -// test.packetIn(nil, neighborsPacket, &neighbors{Expiration: futureExp, Nodes: rpclist[:2]}) -// test.packetIn(nil, neighborsPacket, &neighbors{Expiration: futureExp, Nodes: rpclist[2:]}) -// -// // check that the sent neighbors are all returned by findnode -// select { -// case result := <-resultc: -// if !reflect.DeepEqual(result, list) { -// t.Errorf("neighbors mismatch:\n got: %v\n want: %v", result, list) -// } -// case err := <-errc: -// t.Errorf("findnode error: %v", err) -// case <-time.After(5 * time.Second): -// t.Error("findnode did not return within 5 seconds") -// } -// } -// -// func TestUDP_successfulPing(t *testing.T) { -// test := newUDPTest(t) -// added := make(chan *Node, 1) -// test.table.nodeAddedHook = func(n *Node) { added <- n } -// defer test.table.Close() -// -// // The remote side sends a ping packet to initiate the exchange. -// go test.packetIn(nil, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: Version, Expiration: futureExp}) -// -// // the ping is replied to. -// test.waitPacketOut(func(p *pong) { -// pinghash := test.sent[0][:macSize] -// if !bytes.Equal(p.ReplyTok, pinghash) { -// t.Errorf("got pong.ReplyTok %x, want %x", p.ReplyTok, pinghash) -// } -// wantTo := rpcEndpoint{ -// // The mirrored UDP address is the UDP packet sender -// IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port), -// // The mirrored TCP port is the one from the ping packet -// TCP: testRemote.TCP, -// } -// if !reflect.DeepEqual(p.To, wantTo) { -// t.Errorf("got pong.To %v, want %v", p.To, wantTo) -// } -// }) -// -// // remote is unknown, the table pings back. -// test.waitPacketOut(func(p *ping) error { -// if !reflect.DeepEqual(p.From, test.udp.ourEndpoint) { -// t.Errorf("got ping.From %v, want %v", p.From, test.udp.ourEndpoint) -// } -// wantTo := rpcEndpoint{ -// // The mirrored UDP address is the UDP packet sender. -// IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port), -// TCP: 0, -// } -// if !reflect.DeepEqual(p.To, wantTo) { -// t.Errorf("got ping.To %v, want %v", p.To, wantTo) -// } -// return nil -// }) -// test.packetIn(nil, pongPacket, &pong{Expiration: futureExp}) -// -// // the node should be added to the table shortly after getting the -// // pong packet. -// select { -// case n := <-added: -// rid := PubkeyID(&test.remotekey.PublicKey) -// if n.ID != rid { -// t.Errorf("node has wrong ID: got %v, want %v", n.ID, rid) -// } -// if !bytes.Equal(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 { -// t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP, test.remoteaddr.Port) -// } -// if 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): -// t.Errorf("node was not added within 2 seconds") -// } -// } - -var testPackets = []struct { - input string - wantPacket interface{} -}{ - { - input: "71dbda3a79554728d4f94411e42ee1f8b0d561c10e1e5f5893367948c6a7d70bb87b235fa28a77070271b6c164a2dce8c7e13a5739b53b5e96f2e5acb0e458a02902f5965d55ecbeb2ebb6cabb8b2b232896a36b737666c55265ad0a68412f250001ea04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a355", - wantPacket: &ping{ - Version: 4, - From: rpcEndpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, - To: rpcEndpoint{net.ParseIP("::1"), 2222, 3333}, - Expiration: 1136239445, - Rest: []rlp.RawValue{}, - }, - }, - { - input: "e9614ccfd9fc3e74360018522d30e1419a143407ffcce748de3e22116b7e8dc92ff74788c0b6663aaa3d67d641936511c8f8d6ad8698b820a7cf9e1be7155e9a241f556658c55428ec0563514365799a4be2be5a685a80971ddcfa80cb422cdd0101ec04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a3550102", - wantPacket: &ping{ - Version: 4, - From: rpcEndpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, - To: rpcEndpoint{net.ParseIP("::1"), 2222, 3333}, - Expiration: 1136239445, - Rest: []rlp.RawValue{{0x01}, {0x02}}, - }, - }, - { - input: "577be4349c4dd26768081f58de4c6f375a7a22f3f7adda654d1428637412c3d7fe917cadc56d4e5e7ffae1dbe3efffb9849feb71b262de37977e7c7a44e677295680e9e38ab26bee2fcbae207fba3ff3d74069a50b902a82c9903ed37cc993c50001f83e82022bd79020010db83c4d001500000000abcdef12820cfa8215a8d79020010db885a308d313198a2e037073488208ae82823a8443b9a355c5010203040531b9019afde696e582a78fa8d95ea13ce3297d4afb8ba6433e4154caa5ac6431af1b80ba76023fa4090c408f6b4bc3701562c031041d4702971d102c9ab7fa5eed4cd6bab8f7af956f7d565ee1917084a95398b6a21eac920fe3dd1345ec0a7ef39367ee69ddf092cbfe5b93e5e568ebc491983c09c76d922dc3", - wantPacket: &ping{ - Version: 555, - From: rpcEndpoint{net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 3322, 5544}, - To: rpcEndpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338}, - Expiration: 1136239445, - Rest: []rlp.RawValue{{0xC5, 0x01, 0x02, 0x03, 0x04, 0x05}}, - }, - }, - { - input: "09b2428d83348d27cdf7064ad9024f526cebc19e4958f0fdad87c15eb598dd61d08423e0bf66b2069869e1724125f820d851c136684082774f870e614d95a2855d000f05d1648b2d5945470bc187c2d2216fbe870f43ed0909009882e176a46b0102f846d79020010db885a308d313198a2e037073488208ae82823aa0fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c9548443b9a355c6010203c2040506a0c969a58f6f9095004c0177a6b47f451530cab38966a25cca5cb58f055542124e", - wantPacket: &pong{ - To: rpcEndpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338}, - ReplyTok: common.Hex2Bytes("fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c954"), - Expiration: 1136239445, - Rest: []rlp.RawValue{{0xC6, 0x01, 0x02, 0x03, 0xC2, 0x04, 0x05}, {0x06}}, - }, - }, - { - input: "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260add7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396", - wantPacket: &findnode{ - Target: MustHexID("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"), - Expiration: 1136239445, - Rest: []rlp.RawValue{{0x82, 0x99, 0x99}, {0x83, 0x99, 0x99, 0x99}}, - }, - }, - { - input: "c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db8403155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d313198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df738443b9a355010203b525a138aa34383fec3d2719a0", - wantPacket: &neighbors{ - Nodes: []rpcNode{ - { - ID: MustHexID("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"), - IP: net.ParseIP("99.33.22.55").To4(), - UDP: 4444, - TCP: 4445, - }, - { - ID: MustHexID("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"), - IP: net.ParseIP("1.2.3.4").To4(), - UDP: 1, - TCP: 1, - }, - { - ID: MustHexID("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"), - IP: net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), - UDP: 3333, - TCP: 3333, - }, - { - ID: MustHexID("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"), - IP: net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), - UDP: 999, - TCP: 1000, - }, - }, - Expiration: 1136239445, - Rest: []rlp.RawValue{{0x01}, {0x02}, {0x03}}, - }, - }, -} - -func TestForwardCompatibility(t *testing.T) { - t.Skip("skipped while working on discovery v5") - - testkey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - wantNodeID := PubkeyID(&testkey.PublicKey) - - for _, test := range testPackets { - input, err := hex.DecodeString(test.input) - if err != nil { - t.Fatalf("invalid hex: %s", test.input) - } - var pkt ingressPacket - if err := decodePacket(input, &pkt); err != nil { - t.Errorf("did not accept packet %s\n%v", test.input, err) - continue - } - if !reflect.DeepEqual(pkt.data, test.wantPacket) { - t.Errorf("got %s\nwant %s", spew.Sdump(pkt.data), spew.Sdump(test.wantPacket)) - } - if pkt.remoteID != wantNodeID { - t.Errorf("got id %v\nwant id %v", pkt.remoteID, wantNodeID) - } - } -} - -// dgramPipe is a fake UDP socket. It queues all sent datagrams. -type dgramPipe struct { - mu *sync.Mutex - cond *sync.Cond - closing chan struct{} - closed bool - queue [][]byte -} - -func newpipe() *dgramPipe { - mu := new(sync.Mutex) - return &dgramPipe{ - closing: make(chan struct{}), - cond: &sync.Cond{L: mu}, - mu: mu, - } -} - -// WriteToUDP queues a datagram. -func (c *dgramPipe) WriteToUDP(b []byte, to *net.UDPAddr) (n int, err error) { - msg := make([]byte, len(b)) - copy(msg, b) - c.mu.Lock() - defer c.mu.Unlock() - if c.closed { - return 0, errors.New("closed") - } - c.queue = append(c.queue, msg) - c.cond.Signal() - return len(b), nil -} - -// ReadFromUDP just hangs until the pipe is closed. -func (c *dgramPipe) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) { - <-c.closing - return 0, nil, io.EOF -} - -func (c *dgramPipe) Close() error { - c.mu.Lock() - defer c.mu.Unlock() - if !c.closed { - close(c.closing) - c.closed = true - } - return nil -} - -func (c *dgramPipe) LocalAddr() net.Addr { - return &net.UDPAddr{IP: testLocal.IP, Port: int(testLocal.UDP)} -} - -func (c *dgramPipe) waitPacketOut() []byte { - c.mu.Lock() - defer c.mu.Unlock() - for len(c.queue) == 0 { - c.cond.Wait() - } - p := c.queue[0] - copy(c.queue, c.queue[1:]) - c.queue = c.queue[:len(c.queue)-1] - return p -} diff --git a/p2p/enode/iter.go b/p2p/enode/iter.go new file mode 100644 index 0000000000..664964f534 --- /dev/null +++ b/p2p/enode/iter.go @@ -0,0 +1,288 @@ +// 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 . + +package enode + +import ( + "sync" + "time" +) + +// Iterator represents a sequence of nodes. The Next method moves to the next node in the +// sequence. It returns false when the sequence has ended or the iterator is closed. Close +// may be called concurrently with Next and Node, and interrupts Next if it is blocked. +type Iterator interface { + Next() bool // moves to next node + Node() *Node // returns current node + Close() // ends the iterator +} + +// ReadNodes reads at most n nodes from the given iterator. The return value contains no +// duplicates and no nil values. To prevent looping indefinitely for small repeating node +// sequences, this function calls Next at most n times. +func ReadNodes(it Iterator, n int) []*Node { + seen := make(map[ID]*Node, n) + for i := 0; i < n && it.Next(); i++ { + // Remove duplicates, keeping the node with higher seq. + node := it.Node() + prevNode, ok := seen[node.ID()] + if ok && prevNode.Seq() > node.Seq() { + continue + } + seen[node.ID()] = node + } + result := make([]*Node, 0, len(seen)) + for _, node := range seen { + result = append(result, node) + } + return result +} + +// IterNodes makes an iterator which runs through the given nodes once. +func IterNodes(nodes []*Node) Iterator { + return &sliceIter{nodes: nodes, index: -1} +} + +// CycleNodes makes an iterator which cycles through the given nodes indefinitely. +func CycleNodes(nodes []*Node) Iterator { + return &sliceIter{nodes: nodes, index: -1, cycle: true} +} + +type sliceIter struct { + mu sync.Mutex + nodes []*Node + index int + cycle bool +} + +func (it *sliceIter) Next() bool { + it.mu.Lock() + defer it.mu.Unlock() + + if len(it.nodes) == 0 { + return false + } + it.index++ + if it.index == len(it.nodes) { + if it.cycle { + it.index = 0 + } else { + it.nodes = nil + return false + } + } + return true +} + +func (it *sliceIter) Node() *Node { + it.mu.Lock() + defer it.mu.Unlock() + if len(it.nodes) == 0 { + return nil + } + return it.nodes[it.index] +} + +func (it *sliceIter) Close() { + it.mu.Lock() + defer it.mu.Unlock() + + it.nodes = nil +} + +// Filter wraps an iterator such that Next only returns nodes for which +// the 'check' function returns true. +func Filter(it Iterator, check func(*Node) bool) Iterator { + return &filterIter{it, check} +} + +type filterIter struct { + Iterator + check func(*Node) bool +} + +func (f *filterIter) Next() bool { + for f.Iterator.Next() { + if f.check(f.Node()) { + return true + } + } + return false +} + +// FairMix aggregates multiple node iterators. The mixer itself is an iterator which ends +// only when Close is called. Source iterators added via AddSource are removed from the +// mix when they end. +// +// The distribution of nodes returned by Next is approximately fair, i.e. FairMix +// attempts to draw from all sources equally often. However, if a certain source is slow +// and doesn't return a node within the configured timeout, a node from any other source +// will be returned. +// +// It's safe to call AddSource and Close concurrently with Next. +type FairMix struct { + wg sync.WaitGroup + fromAny chan *Node + timeout time.Duration + cur *Node + + mu sync.Mutex + closed chan struct{} + sources []*mixSource + last int +} + +type mixSource struct { + it Iterator + next chan *Node + timeout time.Duration +} + +// NewFairMix creates a mixer. +// +// The timeout specifies how long the mixer will wait for the next fairly-chosen source +// before giving up and taking a node from any other source. A good way to set the timeout +// is deciding how long you'd want to wait for a node on average. Passing a negative +// timeout makes the mixer completely fair. +func NewFairMix(timeout time.Duration) *FairMix { + m := &FairMix{ + fromAny: make(chan *Node), + closed: make(chan struct{}), + timeout: timeout, + } + return m +} + +// AddSource adds a source of nodes. +func (m *FairMix) AddSource(it Iterator) { + m.mu.Lock() + defer m.mu.Unlock() + + if m.closed == nil { + return + } + m.wg.Add(1) + source := &mixSource{it, make(chan *Node), m.timeout} + m.sources = append(m.sources, source) + go m.runSource(m.closed, source) +} + +// Close shuts down the mixer and all current sources. +// Calling this is required to release resources associated with the mixer. +func (m *FairMix) Close() { + m.mu.Lock() + defer m.mu.Unlock() + + if m.closed == nil { + return + } + for _, s := range m.sources { + s.it.Close() + } + close(m.closed) + m.wg.Wait() + close(m.fromAny) + m.sources = nil + m.closed = nil +} + +// Next returns a node from a random source. +func (m *FairMix) Next() bool { + m.cur = nil + + var timeout <-chan time.Time + if m.timeout >= 0 { + timer := time.NewTimer(m.timeout) + timeout = timer.C + defer timer.Stop() + } + for { + source := m.pickSource() + if source == nil { + return m.nextFromAny() + } + select { + case n, ok := <-source.next: + if ok { + m.cur = n + source.timeout = m.timeout + return true + } + // This source has ended. + m.deleteSource(source) + case <-timeout: + source.timeout /= 2 + return m.nextFromAny() + } + } +} + +// Node returns the current node. +func (m *FairMix) Node() *Node { + return m.cur +} + +// nextFromAny is used when there are no sources or when the 'fair' choice +// doesn't turn up a node quickly enough. +func (m *FairMix) nextFromAny() bool { + n, ok := <-m.fromAny + if ok { + m.cur = n + } + return ok +} + +// pickSource chooses the next source to read from, cycling through them in order. +func (m *FairMix) pickSource() *mixSource { + m.mu.Lock() + defer m.mu.Unlock() + + if len(m.sources) == 0 { + return nil + } + m.last = (m.last + 1) % len(m.sources) + return m.sources[m.last] +} + +// deleteSource deletes a source. +func (m *FairMix) deleteSource(s *mixSource) { + m.mu.Lock() + defer m.mu.Unlock() + + for i := range m.sources { + if m.sources[i] == s { + copy(m.sources[i:], m.sources[i+1:]) + m.sources[len(m.sources)-1] = nil + m.sources = m.sources[:len(m.sources)-1] + break + } + } +} + +// runSource reads a single source in a loop. +func (m *FairMix) runSource(closed chan struct{}, s *mixSource) { + defer m.wg.Done() + defer close(s.next) + for s.it.Next() { + n := s.it.Node() + select { + case s.next <- n: + case m.fromAny <- n: + case <-closed: + return + } + } +} diff --git a/p2p/enode/iter_test.go b/p2p/enode/iter_test.go new file mode 100644 index 0000000000..f19d09c39e --- /dev/null +++ b/p2p/enode/iter_test.go @@ -0,0 +1,291 @@ +// 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 . + +package enode + +import ( + "encoding/binary" + "runtime" + "sync/atomic" + "testing" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" +) + +func TestReadNodes(t *testing.T) { + nodes := ReadNodes(new(genIter), 10) + checkNodes(t, nodes, 10) +} + +// This test checks that ReadNodes terminates when reading N nodes from an iterator +// which returns less than N nodes in an endless cycle. +func TestReadNodesCycle(t *testing.T) { + iter := &callCountIter{ + Iterator: CycleNodes([]*Node{ + testNode(0, 0), + testNode(1, 0), + testNode(2, 0), + }), + } + nodes := ReadNodes(iter, 10) + checkNodes(t, nodes, 3) + if iter.count != 10 { + t.Fatalf("%d calls to Next, want %d", iter.count, 100) + } +} + +func TestFilterNodes(t *testing.T) { + nodes := make([]*Node, 100) + for i := range nodes { + nodes[i] = testNode(uint64(i), uint64(i)) + } + + it := Filter(IterNodes(nodes), func(n *Node) bool { + return n.Seq() >= 50 + }) + for i := 50; i < len(nodes); i++ { + if !it.Next() { + t.Fatal("Next returned false") + } + if it.Node() != nodes[i] { + t.Fatalf("iterator returned wrong node %v\nwant %v", it.Node(), nodes[i]) + } + } + if it.Next() { + t.Fatal("Next returned true after underlying iterator has ended") + } +} + +func checkNodes(t *testing.T, nodes []*Node, wantLen int) { + if len(nodes) != wantLen { + t.Errorf("slice has %d nodes, want %d", len(nodes), wantLen) + return + } + seen := make(map[ID]bool) + for i, e := range nodes { + if e == nil { + t.Errorf("nil node at index %d", i) + return + } + if seen[e.ID()] { + t.Errorf("slice has duplicate node %v", e.ID()) + return + } + seen[e.ID()] = true + } +} + +// This test checks fairness of FairMix in the happy case where all sources return nodes +// within the context's deadline. +func TestFairMix(t *testing.T) { + for i := 0; i < 500; i++ { + testMixerFairness(t) + } +} + +func testMixerFairness(t *testing.T) { + mix := NewFairMix(1 * time.Second) + mix.AddSource(&genIter{index: 1}) + mix.AddSource(&genIter{index: 2}) + mix.AddSource(&genIter{index: 3}) + defer mix.Close() + + nodes := ReadNodes(mix, 500) + checkNodes(t, nodes, 500) + + // Verify that the nodes slice contains an approximately equal number of nodes + // from each source. + d := idPrefixDistribution(nodes) + for _, count := range d { + if approxEqual(count, len(nodes)/3, 30) { + t.Fatalf("ID distribution is unfair: %v", d) + } + } +} + +// This test checks that FairMix falls back to an alternative source when +// the 'fair' choice doesn't return a node within the timeout. +func TestFairMixNextFromAll(t *testing.T) { + mix := NewFairMix(1 * time.Millisecond) + mix.AddSource(&genIter{index: 1}) + mix.AddSource(CycleNodes(nil)) + defer mix.Close() + + nodes := ReadNodes(mix, 500) + checkNodes(t, nodes, 500) + + d := idPrefixDistribution(nodes) + if len(d) > 1 || d[1] != len(nodes) { + t.Fatalf("wrong ID distribution: %v", d) + } +} + +// This test ensures FairMix works for Next with no sources. +func TestFairMixEmpty(t *testing.T) { + var ( + mix = NewFairMix(1 * time.Second) + testN = testNode(1, 1) + ch = make(chan *Node) + ) + defer mix.Close() + + go func() { + mix.Next() + ch <- mix.Node() + }() + + mix.AddSource(CycleNodes([]*Node{testN})) + if n := <-ch; n != testN { + t.Errorf("got wrong node: %v", n) + } +} + +// This test checks closing a source while Next runs. +func TestFairMixRemoveSource(t *testing.T) { + mix := NewFairMix(1 * time.Second) + source := make(blockingIter) + mix.AddSource(source) + + sig := make(chan *Node) + go func() { + <-sig + mix.Next() + sig <- mix.Node() + }() + + sig <- nil + runtime.Gosched() + source.Close() + + wantNode := testNode(0, 0) + mix.AddSource(CycleNodes([]*Node{wantNode})) + n := <-sig + + if len(mix.sources) != 1 { + t.Fatalf("have %d sources, want one", len(mix.sources)) + } + if n != wantNode { + t.Fatalf("mixer returned wrong node") + } +} + +type blockingIter chan struct{} + +func (it blockingIter) Next() bool { + <-it + return false +} + +func (it blockingIter) Node() *Node { + return nil +} + +func (it blockingIter) Close() { + close(it) +} + +func TestFairMixClose(t *testing.T) { + for i := 0; i < 20 && !t.Failed(); i++ { + testMixerClose(t) + } +} + +func testMixerClose(t *testing.T) { + mix := NewFairMix(-1) + mix.AddSource(CycleNodes(nil)) + mix.AddSource(CycleNodes(nil)) + + done := make(chan struct{}) + go func() { + defer close(done) + if mix.Next() { + t.Error("Next returned true") + } + }() + // This call is supposed to make it more likely that NextNode is + // actually executing by the time we call Close. + runtime.Gosched() + + mix.Close() + select { + case <-done: + case <-time.After(3 * time.Second): + t.Fatal("Next didn't unblock on Close") + } + + mix.Close() // shouldn't crash +} + +func idPrefixDistribution(nodes []*Node) map[uint32]int { + d := make(map[uint32]int) + for _, node := range nodes { + id := node.ID() + d[binary.BigEndian.Uint32(id[:4])]++ + } + return d +} + +func approxEqual(x, y, ε int) bool { + if y > x { + x, y = y, x + } + return x-y > ε +} + +// genIter creates fake nodes with numbered IDs based on 'index' and 'gen' +type genIter struct { + node *Node + index, gen uint32 +} + +func (s *genIter) Next() bool { + index := atomic.LoadUint32(&s.index) + if index == ^uint32(0) { + s.node = nil + return false + } + s.node = testNode(uint64(index)<<32|uint64(s.gen), 0) + s.gen++ + return true +} + +func (s *genIter) Node() *Node { + return s.node +} + +func (s *genIter) Close() { + atomic.StoreUint32(&s.index, ^uint32(0)) +} + +func testNode(id, seq uint64) *Node { + var nodeID ID + binary.BigEndian.PutUint64(nodeID[:], id) + r := new(enr.Record) + r.SetSeq(seq) + return SignNull(r, nodeID) +} + +// callCountIter counts calls to NextNode. +type callCountIter struct { + Iterator + count int +} + +func (it *callCountIter) Next() bool { + it.count++ + return it.Iterator.Next() +} diff --git a/p2p/enode/localnode.go b/p2p/enode/localnode.go index 07ba95b9b7..21068029bf 100644 --- a/p2p/enode/localnode.go +++ b/p2p/enode/localnode.go @@ -36,38 +36,53 @@ const ( iptrackMinStatements = 10 iptrackWindow = 5 * time.Minute iptrackContactWindow = 10 * time.Minute + + // time needed to wait between two updates to the local ENR + recordUpdateThrottle = time.Millisecond ) // 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. + 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 + mu sync.RWMutex + seq uint64 + update time.Time // timestamp when the record was last updated + entries map[string]enr.Entry + endpoint4 lnEndpoint + endpoint6 lnEndpoint +} + +type lnEndpoint struct { + track *netutil.IPTracker + staticIP, 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), + id: PubkeyToIDV4(&key.PublicKey), + db: db, + key: key, + entries: make(map[string]enr.Entry), + endpoint4: lnEndpoint{ + track: netutil.NewIPTracker(iptrackWindow, iptrackContactWindow, iptrackMinStatements), + }, + endpoint6: lnEndpoint{ + track: netutil.NewIPTracker(iptrackWindow, iptrackContactWindow, iptrackMinStatements), + }, } ln.seq = db.localSeq(ln.id) - ln.invalidate() + ln.update = time.Now() + ln.cur.Store((*Node)(nil)) return ln } @@ -78,24 +93,57 @@ func (ln *LocalNode) Database() *DB { // Node returns the current version of the local node record. func (ln *LocalNode) Node() *Node { + // If we have a valid record, return that n := ln.cur.Load().(*Node) if n != nil { return n } + // Record was invalidated, sign a new copy. ln.mu.Lock() defer ln.mu.Unlock() + + // Double check the current record, since multiple goroutines might be waiting + // on the write mutex. + if n = ln.cur.Load().(*Node); n != nil { + return n + } + + // The initial sequence number is the current timestamp in milliseconds. To ensure + // that the initial sequence number will always be higher than any previous sequence + // number (assuming the clock is correct), we want to avoid updating the record faster + // than once per ms. So we need to sleep here until the next possible update time has + // arrived. + lastChange := time.Since(ln.update) + if lastChange < recordUpdateThrottle { + time.Sleep(recordUpdateThrottle - lastChange) + } + ln.sign() + ln.update = time.Now() return ln.cur.Load().(*Node) } +// Seq returns the current sequence number of the local node record. +func (ln *LocalNode) Seq() uint64 { + ln.mu.Lock() + defer ln.mu.Unlock() + + return ln.seq +} + // 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. +// Set puts the given entry into the local record, overwriting any existing value. +// Use Set*IP and SetFallbackUDP to set IP addresses and UDP port, otherwise they'll +// be overwritten by the endpoint predictor. +// +// Since node record updates are throttled to one per second, Set is asynchronous. +// Any update will be queued up and published when at least one second passes from +// the last change. func (ln *LocalNode) Set(e enr.Entry) { ln.mu.Lock() defer ln.mu.Unlock() @@ -127,13 +175,20 @@ func (ln *LocalNode) delete(e enr.Entry) { } } +func (ln *LocalNode) endpointForIP(ip net.IP) *lnEndpoint { + if ip.To4() != nil { + return &ln.endpoint4 + } + return &ln.endpoint6 +} + // 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.endpointForIP(ip).staticIP = ip ln.updateEndpoints() } @@ -143,17 +198,18 @@ func (ln *LocalNode) SetFallbackIP(ip net.IP) { ln.mu.Lock() defer ln.mu.Unlock() - ln.fallbackIP = ip + ln.endpointForIP(ip).fallbackIP = ip ln.updateEndpoints() } -// SetFallbackUDP sets the last-resort UDP port. This port is used +// SetFallbackUDP sets the last-resort UDP-on-IPv4 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.endpoint4.fallbackUDP = port + ln.endpoint6.fallbackUDP = port ln.updateEndpoints() } @@ -163,7 +219,7 @@ func (ln *LocalNode) UDPEndpointStatement(fromaddr, endpoint *net.UDPAddr) { ln.mu.Lock() defer ln.mu.Unlock() - ln.udpTrack.AddStatement(fromaddr.String(), endpoint.String()) + ln.endpointForIP(endpoint.IP).track.AddStatement(fromaddr.String(), endpoint.String()) ln.updateEndpoints() } @@ -173,34 +229,52 @@ func (ln *LocalNode) UDPContact(toaddr *net.UDPAddr) { ln.mu.Lock() defer ln.mu.Unlock() - ln.udpTrack.AddContact(toaddr.String()) + ln.endpointForIP(toaddr.IP).track.AddContact(toaddr.String()) ln.updateEndpoints() } +// updateEndpoints updates the record with predicted endpoints. 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 - } + ip4, udp4 := ln.endpoint4.get() + ip6, udp6 := ln.endpoint6.get() - // 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)) - } + if ip4 != nil && !ip4.IsUnspecified() { + ln.set(enr.IPv4(ip4)) + } else { + ln.delete(enr.IPv4{}) + } + if ip6 != nil && !ip6.IsUnspecified() { + ln.set(enr.IPv6(ip6)) + } else { + ln.delete(enr.IPv6{}) + } + if udp4 != 0 { + ln.set(enr.UDP(udp4)) + } else { + ln.delete(enr.UDP(0)) + } + if udp6 != 0 && udp6 != udp4 { + ln.set(enr.UDP6(udp6)) } else { - ln.delete(enr.IP{}) + ln.delete(enr.UDP6(0)) } } +// get returns the endpoint with highest precedence. +func (e *lnEndpoint) get() (newIP net.IP, newPort int) { + newPort = e.fallbackUDP + if e.fallbackIP != nil { + newIP = e.fallbackIP + } + if e.staticIP != nil { + newIP = e.staticIP + } else if ip, port := predictAddr(e.track); ip != nil { + newIP = ip + newPort = port + } + return newIP, newPort +} + // predictAddr wraps IPTracker.PredictEndpoint, converting from its string-based // endpoint representation to IP and port types. func predictAddr(t *netutil.IPTracker) (net.IP, int) { @@ -244,3 +318,12 @@ func (ln *LocalNode) bumpSeq() { ln.seq++ ln.db.storeLocalSeq(ln.id, ln.seq) } + +// nowMilliseconds gives the current timestamp at millisecond precision. +func nowMilliseconds() uint64 { + ns := time.Now().UnixNano() + if ns < 0 { + return 0 + } + return uint64(ns / 1000 / 1000) +} diff --git a/p2p/enode/localnode_test.go b/p2p/enode/localnode_test.go index 25804a0e16..fabbebb894 100644 --- a/p2p/enode/localnode_test.go +++ b/p2p/enode/localnode_test.go @@ -17,8 +17,12 @@ package enode import ( + "math/rand" + "net" "testing" + "github.com/stretchr/testify/assert" + "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/p2p/enr" ) @@ -46,31 +50,81 @@ func TestLocalNode(t *testing.T) { } } +// This test checks that the sequence number is persisted between restarts. func TestLocalNodeSeqPersist(t *testing.T) { + timestamp := nowMilliseconds() + ln, db := newLocalNodeForTesting() defer db.Close() - if s := ln.Node().Seq(); s != 1 { - t.Fatalf("wrong initial seq %d, want 1", s) + initialSeq := ln.Node().Seq() + if initialSeq < timestamp { + t.Fatalf("wrong initial seq %d, want at least %d", initialSeq, timestamp) } + ln.Set(enr.WithEntry("x", uint(1))) - if s := ln.Node().Seq(); s != 2 { - t.Fatalf("wrong seq %d after set, want 2", s) + if s := ln.Node().Seq(); s != initialSeq+1 { + t.Fatalf("wrong seq %d after set, want %d", s, initialSeq+1) } // 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) + if s := ln2.Node().Seq(); s != initialSeq+2 { + t.Fatalf("wrong seq %d on new instance, want %d", s, initialSeq+2) } + finalSeq := ln2.Node().Seq() + // 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) + if s := ln3.Node().Seq(); s < finalSeq { + t.Fatalf("wrong seq %d on instance with changed key, want >= %d", s, finalSeq) + } +} + +// This test checks behavior of the endpoint predictor. +func TestLocalNodeEndpoint(t *testing.T) { + var ( + fallback = &net.UDPAddr{IP: net.IP{127, 0, 0, 1}, Port: 80} + predicted = &net.UDPAddr{IP: net.IP{127, 0, 1, 2}, Port: 81} + staticIP = net.IP{127, 0, 1, 2} + ) + ln, db := newLocalNodeForTesting() + defer db.Close() + + // Nothing is set initially. + assert.Equal(t, net.IP(nil), ln.Node().IP()) + assert.Equal(t, 0, ln.Node().UDP()) + initialSeq := ln.Node().Seq() + + // Set up fallback address. + ln.SetFallbackIP(fallback.IP) + ln.SetFallbackUDP(fallback.Port) + assert.Equal(t, fallback.IP, ln.Node().IP()) + assert.Equal(t, fallback.Port, ln.Node().UDP()) + assert.Equal(t, initialSeq+1, ln.Node().Seq()) + + // Add endpoint statements from random hosts. + for i := 0; i < iptrackMinStatements; i++ { + assert.Equal(t, fallback.IP, ln.Node().IP()) + assert.Equal(t, fallback.Port, ln.Node().UDP()) + assert.Equal(t, initialSeq+1, ln.Node().Seq()) + + from := &net.UDPAddr{IP: make(net.IP, 4), Port: 90} + rand.Read(from.IP) + ln.UDPEndpointStatement(from, predicted) } + assert.Equal(t, predicted.IP, ln.Node().IP()) + assert.Equal(t, predicted.Port, ln.Node().UDP()) + assert.Equal(t, initialSeq+2, ln.Node().Seq()) + + // Static IP overrides prediction. + ln.SetStaticIP(staticIP) + assert.Equal(t, staticIP, ln.Node().IP()) + assert.Equal(t, fallback.Port, ln.Node().UDP()) + assert.Equal(t, initialSeq+3, ln.Node().Seq()) } diff --git a/p2p/enode/node.go b/p2p/enode/node.go index e754e71199..adff3d9774 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -19,16 +19,17 @@ package enode import ( "crypto/ecdsa" "crypto/elliptic" + "encoding/base64" "encoding/hex" "errors" "fmt" "math/big" "math/bits" - "math/rand" "net" "strings" "github.com/AlayaNetwork/Alaya-Go/common/math" + "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/AlayaNetwork/Alaya-Go/crypto" @@ -37,6 +38,8 @@ import ( "github.com/AlayaNetwork/Alaya-Go/p2p/enr" ) +var errMissingPrefix = errors.New("missing 'enr:' prefix for base64-encoded record") + // Node represents a host on the network. type Node struct { r enr.Record @@ -56,6 +59,34 @@ func New(validSchemes enr.IdentityScheme, r *enr.Record) (*Node, error) { return node, nil } +// MustParse parses a node record or enode:// URL. It panics if the input is invalid. +func MustParse(rawurl string) *Node { + n, err := Parse(ValidSchemes, rawurl) + if err != nil { + panic("invalid node: " + err.Error()) + } + return n +} + +// Parse decodes and verifies a base64-encoded node record. +func Parse(validSchemes enr.IdentityScheme, input string) (*Node, error) { + if strings.HasPrefix(input, "enode://") { + return ParseV4(input) + } + if !strings.HasPrefix(input, "enr:") { + return nil, errMissingPrefix + } + bin, err := base64.RawURLEncoding.DecodeString(input[4:]) + if err != nil { + return nil, err + } + var r enr.Record + if err := rlp.DecodeBytes(bin, &r); err != nil { + return nil, err + } + return New(validSchemes, &r) +} + // ID returns the node identifier. func (n *Node) ID() ID { return n.id @@ -76,11 +107,19 @@ func (n *Node) Load(k enr.Entry) error { return n.r.Load(k) } -// IP returns the IP address of the node. +// IP returns the IP address of the node. This prefers IPv4 addresses. func (n *Node) IP() net.IP { - var ip net.IP - n.Load((*enr.IP)(&ip)) - return ip + var ( + ip4 enr.IPv4 + ip6 enr.IPv6 + ) + if n.Load(&ip4) == nil { + return net.IP(ip4) + } + if n.Load(&ip6) == nil { + return net.IP(ip6) + } + return nil } // UDP returns the UDP port of the node. @@ -124,10 +163,11 @@ func (n *Node) IDv0() IDv0 { return PublicKeyToIDv0(pub) } -// checks whether n is a valid complete node. +// ValidateComplete checks whether n has a valid IP and UDP port. +// Deprecated: don't use this method. func (n *Node) ValidateComplete() error { if n.Incomplete() { - return errors.New("incomplete node") + return errors.New("missing IP address") } if n.UDP() == 0 { return errors.New("missing UDP port") @@ -141,20 +181,24 @@ func (n *Node) ValidateComplete() error { return n.Load(&key) } -// The string representation of a Node is a URL. -// Please see ParseNode for a description of the format. +// String returns the text representation of the record. func (n *Node) String() string { - return n.v4URL() + if isNewV4(n) { + return n.URLv4() // backwards-compatibility glue for NewV4 nodes + } + enc, _ := rlp.EncodeToBytes(&n.r) // always succeeds because record is valid + b64 := base64.RawURLEncoding.EncodeToString(enc) + return "enr:" + b64 } // MarshalText implements encoding.TextMarshaler. func (n *Node) MarshalText() ([]byte, error) { - return []byte(n.v4URL()), nil + return []byte(n.String()), nil } // UnmarshalText implements encoding.TextUnmarshaler. func (n *Node) UnmarshalText(text []byte) error { - dec, err := ParseV4(string(text)) + dec, err := Parse(ValidSchemes, string(text)) if err == nil { *n = *dec } @@ -191,7 +235,7 @@ func (n ID) MarshalText() ([]byte, error) { // UnmarshalText implements the encoding.TextUnmarshaler interface. func (n *ID) UnmarshalText(text []byte) error { - id, err := parseID(string(text)) + id, err := ParseID(string(text)) if err != nil { return err } @@ -203,14 +247,14 @@ func (n *ID) UnmarshalText(text []byte) error { // 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) + id, err := ParseID(in) if err != nil { panic(err) } return id } -func parseID(in string) (ID, error) { +func ParseID(in string) (ID, error) { var id ID b, err := hex.DecodeString(strings.TrimPrefix(in, "0x")) if err != nil { @@ -253,26 +297,6 @@ func LogDist(a, b ID) int { 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 -} - var ( ZeroIDv0 = MustHexIDv0(IDv0{}.String()) ) diff --git a/p2p/enode/node_test.go b/p2p/enode/node_test.go index 1abd5d70ce..9ee8211f08 100644 --- a/p2p/enode/node_test.go +++ b/p2p/enode/node_test.go @@ -17,9 +17,12 @@ package enode import ( + "bytes" "encoding/hex" "fmt" + "math/big" "testing" + "testing/quick" "github.com/stretchr/testify/assert" @@ -44,7 +47,7 @@ func TestPythonInterop(t *testing.T) { var ( wantID = HexID("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7") wantSeq = uint64(1) - wantIP = enr.IP{127, 0, 0, 1} + wantIP = enr.IPv4{127, 0, 0, 1} wantUDP = enr.UDP(30303) ) if n.Seq() != wantSeq { @@ -53,7 +56,7 @@ func TestPythonInterop(t *testing.T) { 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} + want := map[enr.Entry]interface{}{new(enr.IPv4): &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) { @@ -61,3 +64,83 @@ func TestPythonInterop(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} + id1 := HexID("0x00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") + id2 := HexID("00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") + + if id1 != ref { + t.Errorf("wrong id1\ngot %v\nwant %v", id1[:], ref[:]) + } + if id2 != ref { + t.Errorf("wrong id2\ngot %v\nwant %v", id2[:], ref[:]) + } +} + +func TestID_textEncoding(t *testing.T) { + ref := ID{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, + 0x31, 0x32, + } + hex := "0102030405060708091011121314151617181920212223242526272829303132" + + text, err := ref.MarshalText() + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(text, []byte(hex)) { + t.Fatalf("text encoding did not match\nexpected: %s\ngot: %s", hex, text) + } + + id := new(ID) + if err := id.UnmarshalText(text); err != nil { + t.Fatal(err) + } + if *id != ref { + t.Fatalf("text decoding did not match\nexpected: %s\ngot: %s", ref, id) + } +} + +func TestID_distcmp(t *testing.T) { + distcmpBig := func(target, a, b ID) int { + tbig := new(big.Int).SetBytes(target[:]) + abig := new(big.Int).SetBytes(a[:]) + bbig := new(big.Int).SetBytes(b[:]) + return new(big.Int).Xor(tbig, abig).Cmp(new(big.Int).Xor(tbig, bbig)) + } + if err := quick.CheckEqual(DistCmp, distcmpBig, nil); err != nil { + t.Error(err) + } +} + +// The random tests is likely to miss the case where a and b are equal, +// this test checks it explicitly. +func TestID_distcmpEqual(t *testing.T) { + base := ID{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} + if DistCmp(base, x, x) != 0 { + t.Errorf("DistCmp(base, x, x) != 0") + } +} + +func TestID_logdist(t *testing.T) { + logdistBig := func(a, b ID) int { + abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:]) + return new(big.Int).Xor(abig, bbig).BitLen() + } + if err := quick.CheckEqual(LogDist, logdistBig, nil); err != nil { + t.Error(err) + } +} + +// The random tests is likely to miss the case where a and b are equal, +// this test checks it explicitly. +func TestID_logdistEqual(t *testing.T) { + x := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} + if LogDist(x, x) != 0 { + t.Errorf("LogDist(x, x) != 0") + } +} diff --git a/p2p/enode/nodedb.go b/p2p/enode/nodedb.go index 00422d33ba..4ee9d1782b 100644 --- a/p2p/enode/nodedb.go +++ b/p2p/enode/nodedb.go @@ -45,6 +45,7 @@ const ( dbNodePrefix = "n:" // Identifier to prefix node entries with dbLocalPrefix = "local:" dbDiscoverRoot = "v4" + dbDiscv5Root = "v5" // These fields are stored per ID and IP, the full key is "n::v4::findfail". // Use nodeItemKey to create those keys. @@ -64,6 +65,10 @@ const ( dbVersion = 9 ) +var ( + errInvalidIP = errors.New("invalid IP") +) + var zeroIP = make(net.IP, 16) // DB is the node database, storing previously seen nodes and any collected metadata about @@ -166,7 +171,7 @@ func splitNodeItemKey(key []byte) (id ID, ip net.IP, field string) { } key = key[len(dbDiscoverRoot)+1:] // Split out the IP. - ip = net.IP(key[:16]) + ip = key[:16] if ip4 := ip.To4(); ip4 != nil { ip = ip4 } @@ -176,6 +181,16 @@ func splitNodeItemKey(key []byte) (id ID, ip net.IP, field string) { return id, ip, field } +func v5Key(id ID, ip net.IP, field string) []byte { + return bytes.Join([][]byte{ + []byte(dbNodePrefix), + id[:], + []byte(dbDiscv5Root), + ip.To16(), + []byte(field), + }, []byte{':'}) +} + // localItemKey returns the key of a local node item. func localItemKey(id ID, field string) []byte { key := append([]byte(dbLocalPrefix), id[:]...) @@ -352,16 +367,25 @@ func (db *DB) expireNodes() { // LastPingReceived retrieves the time of the last ping packet received from // a remote node. func (db *DB) LastPingReceived(id ID, ip net.IP) time.Time { + if ip = ip.To16(); ip == nil { + return time.Time{} + } return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePing)), 0) } // UpdateLastPingReceived updates the last time we tried contacting a remote node. func (db *DB) UpdateLastPingReceived(id ID, ip net.IP, instance time.Time) error { + if ip = ip.To16(); ip == nil { + return errInvalidIP + } return db.storeInt64(nodeItemKey(id, ip, dbNodePing), instance.Unix()) } // LastPongReceived retrieves the time of the last successful pong from remote node. func (db *DB) LastPongReceived(id ID, ip net.IP) time.Time { + if ip = ip.To16(); ip == nil { + return time.Time{} + } // Launch expirer db.ensureExpirer() return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePong)), 0) @@ -369,22 +393,52 @@ func (db *DB) LastPongReceived(id ID, ip net.IP) time.Time { // UpdateLastPongReceived updates the last pong time of a node. func (db *DB) UpdateLastPongReceived(id ID, ip net.IP, instance time.Time) error { + if ip = ip.To16(); ip == nil { + return errInvalidIP + } return db.storeInt64(nodeItemKey(id, ip, dbNodePong), instance.Unix()) } // FindFails retrieves the number of findnode failures since bonding. func (db *DB) FindFails(id ID, ip net.IP) int { + if ip = ip.To16(); ip == nil { + return 0 + } return int(db.fetchInt64(nodeItemKey(id, ip, dbNodeFindFails))) } // UpdateFindFails updates the number of findnode failures since bonding. func (db *DB) UpdateFindFails(id ID, ip net.IP, fails int) error { + if ip = ip.To16(); ip == nil { + return errInvalidIP + } return db.storeInt64(nodeItemKey(id, ip, dbNodeFindFails), int64(fails)) } -// LocalSeq retrieves the local record sequence counter. +// FindFailsV5 retrieves the discv5 findnode failure counter. +func (db *DB) FindFailsV5(id ID, ip net.IP) int { + if ip = ip.To16(); ip == nil { + return 0 + } + return int(db.fetchInt64(v5Key(id, ip, dbNodeFindFails))) +} + +// UpdateFindFailsV5 stores the discv5 findnode failure counter. +func (db *DB) UpdateFindFailsV5(id ID, ip net.IP, fails int) error { + if ip = ip.To16(); ip == nil { + return errInvalidIP + } + return db.storeInt64(v5Key(id, ip, dbNodeFindFails), int64(fails)) +} + +// localSeq retrieves the local record sequence counter, defaulting to the current +// timestamp if no previous exists. This ensures that wiping all data associated +// with a node (apart from its key) will not generate already used sequence nums. func (db *DB) localSeq(id ID) uint64 { - return db.fetchUint64(localItemKey(id, dbLocalSeq)) + if seq := db.fetchUint64(localItemKey(id, dbLocalSeq)); seq > 0 { + return seq + } + return nowMilliseconds() } // storeLocalSeq stores the local record sequence counter. diff --git a/p2p/enode/nodedb_test.go b/p2p/enode/nodedb_test.go index bcf628b825..dbb26a21fa 100644 --- a/p2p/enode/nodedb_test.go +++ b/p2p/enode/nodedb_test.go @@ -462,3 +462,14 @@ func TestDBExpiration(t *testing.T) { } } } + +// This test checks that expiration works when discovery v5 data is present +// in the database. +func TestDBExpireV5(t *testing.T) { + db, _ := OpenDB("") + defer db.Close() + + ip := net.IP{127, 0, 0, 1} + db.UpdateFindFailsV5(ID{}, ip, 4) + db.expireNodes() +} diff --git a/p2p/enode/urlv4.go b/p2p/enode/urlv4.go index 7066fa0e77..ff060e12e2 100644 --- a/p2p/enode/urlv4.go +++ b/p2p/enode/urlv4.go @@ -31,7 +31,10 @@ import ( "github.com/AlayaNetwork/Alaya-Go/p2p/enr" ) -var incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$") +var ( + incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$") + lookupIPFunc = net.LookupIP +) // MustParseV4 parses a node URL. It panics if the URL is not valid. func MustParseV4(rawurl string) *Node { @@ -70,7 +73,7 @@ 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 nil, fmt.Errorf("invalid public key (%v)", err) } return NewV4(id, nil, 0, 0), nil } @@ -81,7 +84,7 @@ func ParseV4(rawurl string) (*Node, error) { // 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 { + if len(ip) > 0 { r.Set(enr.IP(ip)) } if udp != 0 { @@ -98,10 +101,15 @@ func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp int) *Node { return n } +// isNewV4 returns true for nodes created by NewV4. +func isNewV4(n *Node) bool { + var k s256raw + return n.r.IdentityScheme() == "" && n.r.Load(&k) == nil && len(n.r.Signature()) == 0 +} + func parseComplete(rawurl string) (*Node, error) { var ( id *ecdsa.PublicKey - ip net.IP tcpPort, udpPort uint64 ) u, err := url.Parse(rawurl) @@ -116,26 +124,23 @@ func parseComplete(rawurl string) (*Node, error) { 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) + return nil, fmt.Errorf("invalid public key (%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 { - if ips, err := net.LookupIP(host); err == nil && len(ips) != 0 { - ip = ips[0] - } else { - return nil, errors.New("invalid IP address") + ip := net.ParseIP(u.Hostname()) + if ip == nil { + ips, err := lookupIPFunc(u.Hostname()) + if err != nil { + return nil, err } + ip = ips[0] } // 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 { + if tcpPort, err = strconv.ParseUint(u.Port(), 10, 16); err != nil { return nil, errors.New("invalid port") } udpPort = tcpPort @@ -161,7 +166,7 @@ func parsePubkey(in string) (*ecdsa.PublicKey, error) { return crypto.UnmarshalPubkey(b) } -func (n *Node) v4URL() string { +func (n *Node) URLv4() string { var ( scheme enr.ID nodeid string diff --git a/p2p/enode/urlv4_test.go b/p2p/enode/urlv4_test.go index 7dfcb9e03c..c99305bf57 100644 --- a/p2p/enode/urlv4_test.go +++ b/p2p/enode/urlv4_test.go @@ -17,56 +17,82 @@ package enode import ( - "bytes" "crypto/ecdsa" - "math/big" + "errors" "net" - "os" "reflect" "strings" "testing" - "testing/quick" - "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" ) +func init() { + lookupIPFunc = func(name string) ([]net.IP, error) { + if name == "node.example.org" { + return []net.IP{{33, 44, 55, 66}}, nil + } + return nil, errors.New("no such host") + } +} + var parseNodeTests = []struct { - rawurl string + input string wantError string wantResult *Node }{ + // Records { - rawurl: "http://foobar", - wantError: `invalid URL scheme, want "enode"`, + input: "enr:-IS4QGrdq0ugARp5T2BZ41TrZOqLc_oKvZoPuZP5--anqWE_J-Tucc1xgkOL7qXl0puJgT7qc2KSvcupc4NCb0nr4tdjgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQM6UUF2Rm-oFe1IH_rQkRCi00T2ybeMHRSvw1HDpRvjPYN1ZHCCdl8", + wantResult: func() *Node { + testKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8") + var r enr.Record + r.Set(enr.IP{127, 0, 0, 1}) + r.Set(enr.UDP(30303)) + r.SetSeq(99) + SignV4(&r, testKey) + n, _ := New(ValidSchemes, &r) + return n + }(), }, + // Invalid Records { - rawurl: "enode://01010101@123.124.125.126:3", - wantError: `invalid node ID (wrong length, want 128 hex chars)`, + input: "enr:", + wantError: "EOF", // could be nicer }, - // Complete nodes with IP address. { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3", - wantError: `invalid IP address`, + input: "enr:x", + wantError: "illegal base64 data at input byte 0", }, { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo", + input: "enr:-EmGZm9vYmFyY4JpZIJ2NIJpcIR_AAABiXNlY3AyNTZrMaEDOlFBdkZvqBXtSB_60JEQotNE9sm3jB0Ur8NRw6Ub4z2DdWRwgnZf", + wantError: enr.ErrInvalidSig.Error(), + }, + // Complete node URLs with IP address and ports + { + input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@invalid.:3", + wantError: `no such host`, + }, + { + input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo", wantError: `invalid port`, }, { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo", + input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo", wantError: `invalid discport in query`, }, { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150", + input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150", wantResult: NewV4( hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{0x7f, 0x0, 0x0, 0x1}, + net.IP{127, 0, 0, 1}, 52150, 52150, ), }, { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150", + input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150", wantResult: NewV4( hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.ParseIP("::"), @@ -75,7 +101,7 @@ var parseNodeTests = []struct { ), }, { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150", + input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150", wantResult: NewV4( hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), @@ -84,7 +110,7 @@ var parseNodeTests = []struct { ), }, { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334", + input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334", wantResult: NewV4( hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), net.IP{0x7f, 0x0, 0x0, 0x1}, @@ -92,34 +118,42 @@ var parseNodeTests = []struct { 22334, ), }, - // Incomplete nodes with no address. + // Incomplete node URLs with no address { - rawurl: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", + input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", wantResult: NewV4( hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), nil, 0, 0, ), }, + // Invalid URLs { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", - wantResult: NewV4( - hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - nil, 0, 0, - ), + input: "", + wantError: errMissingPrefix.Error(), + }, + { + input: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", + wantError: errMissingPrefix.Error(), }, - // Invalid URLs { - rawurl: "01010101", - wantError: `invalid node ID (wrong length, want 128 hex chars)`, + input: "01010101", + wantError: errMissingPrefix.Error(), }, { - rawurl: "enode://01010101", - wantError: `invalid node ID (wrong length, want 128 hex chars)`, + input: "enode://01010101@123.124.125.126:3", + wantError: `invalid public key (wrong length, want 128 hex chars)`, }, { - // This test checks that errors from url.Parse are handled. - rawurl: "://foo", - wantError: `parse ://foo: missing protocol scheme`, + input: "enode://01010101", + wantError: `invalid public key (wrong length, want 128 hex chars)`, + }, + { + input: "http://foobar", + wantError: errMissingPrefix.Error(), + }, + { + input: "://foo", + wantError: errMissingPrefix.Error(), }, } @@ -132,25 +166,23 @@ func hexPubkey(h string) *ecdsa.PublicKey { } func TestParseNode(t *testing.T) { - log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(6), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))) - for _, test := range parseNodeTests { - n, err := ParseV4(test.rawurl) + n, err := Parse(ValidSchemes, test.input) if test.wantError != "" { 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.input, test.wantError) continue - } else if err.Error() != test.wantError { - t.Errorf("test %q:\n got error %#q, expected %#q", test.rawurl, err.Error(), test.wantError) + } else if !strings.Contains(err.Error(), test.wantError) { + t.Errorf("test %q:\n got error %#q, expected %#q", test.input, err.Error(), test.wantError) continue } } else { if err != nil { - t.Errorf("test %q:\n unexpected error: %v", test.rawurl, err) + t.Errorf("test %q:\n unexpected error: %v", test.input, err) continue } 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\nwant: %#v", test.input, n, test.wantResult) } } } @@ -158,91 +190,11 @@ func TestParseNode(t *testing.T) { func TestNodeString(t *testing.T) { for i, test := range parseNodeTests { - if test.wantError == "" && strings.HasPrefix(test.rawurl, "enode://") { + if test.wantError == "" && strings.HasPrefix(test.input, "enode://") { str := test.wantResult.String() - if str != test.rawurl { - t.Errorf("test %d: Node.String() mismatch:\ngot: %s\nwant: %s", i, str, test.rawurl) + if str != test.input { + t.Errorf("test %d: Node.String() mismatch:\ngot: %s\nwant: %s", i, str, test.input) } } } } - -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} - id1 := HexID("0x00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") - id2 := HexID("00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") - - if id1 != ref { - t.Errorf("wrong id1\ngot %v\nwant %v", id1[:], ref[:]) - } - if id2 != ref { - t.Errorf("wrong id2\ngot %v\nwant %v", id2[:], ref[:]) - } -} - -func TestID_textEncoding(t *testing.T) { - ref := ID{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, - 0x31, 0x32, - } - hex := "0102030405060708091011121314151617181920212223242526272829303132" - - text, err := ref.MarshalText() - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(text, []byte(hex)) { - t.Fatalf("text encoding did not match\nexpected: %s\ngot: %s", hex, text) - } - - id := new(ID) - if err := id.UnmarshalText(text); err != nil { - t.Fatal(err) - } - if *id != ref { - t.Fatalf("text decoding did not match\nexpected: %s\ngot: %s", ref, id) - } -} - -func TestNodeID_distcmp(t *testing.T) { - distcmpBig := func(target, a, b ID) int { - tbig := new(big.Int).SetBytes(target[:]) - abig := new(big.Int).SetBytes(a[:]) - bbig := new(big.Int).SetBytes(b[:]) - return new(big.Int).Xor(tbig, abig).Cmp(new(big.Int).Xor(tbig, bbig)) - } - if err := quick.CheckEqual(DistCmp, distcmpBig, nil); err != nil { - t.Error(err) - } -} - -// The random tests is likely to miss the case where a and b are equal, -// this test checks it explicitly. -func TestNodeID_distcmpEqual(t *testing.T) { - base := ID{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} - if DistCmp(base, x, x) != 0 { - t.Errorf("DistCmp(base, x, x) != 0") - } -} - -func TestNodeID_logdist(t *testing.T) { - logdistBig := func(a, b ID) int { - abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:]) - return new(big.Int).Xor(abig, bbig).BitLen() - } - if err := quick.CheckEqual(LogDist, logdistBig, nil); err != nil { - t.Error(err) - } -} - -// The random tests is likely to miss the case where a and b are equal, -// this test checks it explicitly. -func TestNodeID_logdistEqual(t *testing.T) { - x := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - if LogDist(x, x) != 0 { - t.Errorf("LogDist(x, x) != 0") - } -} diff --git a/p2p/enr/enr.go b/p2p/enr/enr.go index 658269b106..3d71f41e07 100644 --- a/p2p/enr/enr.go +++ b/p2p/enr/enr.go @@ -15,14 +15,20 @@ // along with the go-ethereum library. If not, see . // 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. -// -// Records contain named keys. To store and retrieve key/values in a record, use the Entry +// arbitrary information about a node on the peer-to-peer network. Node information is +// stored in key/value pairs. To store and retrieve key/values in a record, use the Entry // interface. // -// 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 -// signature. Modifying a record invalidates the signature. +// Signature Handling +// +// Records must be signed before transmitting them to another node. +// +// 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 @@ -44,6 +50,7 @@ var ( errNotSorted = errors.New("record key/value pairs are not sorted by key") errDuplicateKey = errors.New("record contains duplicate key") errIncompletePair = errors.New("record contains incomplete k/v pair") + errIncompleteList = errors.New("record contains less than two list elements") errTooBig = fmt.Errorf("record bigger than %d bytes", SizeLimit) errEncodeUnsigned = errors.New("can't encode unsigned record") errNotFound = errors.New("no such key in record") @@ -157,6 +164,16 @@ func (r *Record) invalidate() { r.raw = nil } +// Signature returns the signature of the record. +func (r *Record) Signature() []byte { + if r.signature == nil { + return nil + } + cpy := make([]byte, len(r.signature)) + copy(cpy, r.signature) + return cpy +} + // EncodeRLP implements rlp.Encoder. Encoding fails if // the record is unsigned. func (r Record) EncodeRLP(w io.Writer) error { @@ -167,7 +184,7 @@ func (r Record) EncodeRLP(w io.Writer) error { return err } -// DecodeRLP implements rlp.Decoder. Decoding verifies the signature. +// DecodeRLP implements rlp.Decoder. Decoding doesn't verify the signature. func (r *Record) DecodeRLP(s *rlp.Stream) error { dec, raw, err := decodeRecord(s) if err != nil { @@ -193,9 +210,15 @@ func decodeRecord(s *rlp.Stream) (dec Record, raw []byte, err error) { return dec, raw, err } if err = s.Decode(&dec.signature); err != nil { + if err == rlp.EOL { + err = errIncompleteList + } return dec, raw, err } if err = s.Decode(&dec.seq); err != nil { + if err == rlp.EOL { + err = errIncompleteList + } return dec, raw, err } // The rest of the record contains sorted k/v pairs. diff --git a/p2p/enr/enr_test.go b/p2p/enr/enr_test.go index c24a0cbc6b..d1dceba412 100644 --- a/p2p/enr/enr_test.go +++ b/p2p/enr/enr_test.go @@ -50,28 +50,28 @@ func TestGetSetID(t *testing.T) { } // TestGetSetIP4 tests encoding/decoding and setting/getting of the IP key. -func TestGetSetIP4(t *testing.T) { - ip := IP{192, 168, 0, 3} +func TestGetSetIPv4(t *testing.T) { + ip := IPv4{192, 168, 0, 3} var r Record r.Set(ip) - var ip2 IP + var ip2 IPv4 require.NoError(t, r.Load(&ip2)) assert.Equal(t, ip, ip2) } -// TestGetSetIP6 tests encoding/decoding and setting/getting of the IP key. -func TestGetSetIP6(t *testing.T) { - ip := IP{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68} +// TestGetSetIP6 tests encoding/decoding and setting/getting of the IP6 key. +func TestGetSetIPv6(t *testing.T) { + ip := IPv6{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68} var r Record r.Set(ip) - var ip2 IP + var ip2 IPv6 require.NoError(t, r.Load(&ip2)) assert.Equal(t, ip, ip2) } -// TestGetSetDiscPort tests encoding/decoding and setting/getting of the DiscPort key. +// TestGetSetUDP tests encoding/decoding and setting/getting of the UDP key. func TestGetSetUDP(t *testing.T) { port := UDP(30309) var r Record @@ -84,7 +84,7 @@ func TestGetSetUDP(t *testing.T) { func TestLoadErrors(t *testing.T) { var r Record - ip4 := IP{127, 0, 0, 1} + ip4 := IPv4{127, 0, 0, 1} r.Set(ip4) // Check error for missing keys. @@ -186,13 +186,13 @@ func TestSeq(t *testing.T) { func TestGetSetOverwrite(t *testing.T) { var r Record - ip := IP{192, 168, 0, 3} + ip := IPv4{192, 168, 0, 3} r.Set(ip) - ip2 := IP{192, 168, 0, 4} + ip2 := IPv4{192, 168, 0, 4} r.Set(ip2) - var ip3 IP + var ip3 IPv4 require.NoError(t, r.Load(&ip3)) assert.Equal(t, ip2, ip3) } @@ -200,8 +200,8 @@ func TestGetSetOverwrite(t *testing.T) { // TestSignEncodeAndDecode tests signing, RLP encoding and RLP decoding of a record. func TestSignEncodeAndDecode(t *testing.T) { var r Record - r.Set(UDP(16789)) - r.Set(IP{127, 0, 0, 1}) + r.Set(UDP(30303)) + r.Set(IPv4{127, 0, 0, 1}) require.NoError(t, signTest([]byte{5}, &r)) blob, err := rlp.EncodeToBytes(r) @@ -232,6 +232,29 @@ func TestRecordTooBig(t *testing.T) { require.NoError(t, signTest([]byte{5}, &r)) } +// This checks that incomplete RLP inputs are handled correctly. +func TestDecodeIncomplete(t *testing.T) { + type decTest struct { + input []byte + err error + } + tests := []decTest{ + {[]byte{0xC0}, errIncompleteList}, + {[]byte{0xC1, 0x1}, errIncompleteList}, + {[]byte{0xC2, 0x1, 0x2}, nil}, + {[]byte{0xC3, 0x1, 0x2, 0x3}, errIncompletePair}, + {[]byte{0xC4, 0x1, 0x2, 0x3, 0x4}, nil}, + {[]byte{0xC5, 0x1, 0x2, 0x3, 0x4, 0x5}, errIncompletePair}, + } + for _, test := range tests { + var r Record + err := rlp.DecodeBytes(test.input, &r) + if err != test.err { + t.Errorf("wrong error for %X: %v", test.input, err) + } + } +} + // TestSignEncodeAndDecodeRandom tests encoding/decoding of records containing random key/value pairs. func TestSignEncodeAndDecodeRandom(t *testing.T) { var r Record diff --git a/p2p/enr/entries.go b/p2p/enr/entries.go index fc7c36cefd..5bca4842b1 100644 --- a/p2p/enr/entries.go +++ b/p2p/enr/entries.go @@ -60,11 +60,21 @@ type TCP uint16 func (v TCP) ENRKey() string { return "tcp" } +// UDP is the "udp" key, which holds the IPv6-specific UDP port of the node. +type TCP6 uint16 + +func (v TCP6) ENRKey() string { return "tcp6" } + // UDP is the "udp" key, which holds the UDP port of the node. type UDP uint16 func (v UDP) ENRKey() string { return "udp" } +// UDP is the "udp" key, which holds the IPv6-specific UDP port of the node. +type UDP6 uint16 + +func (v UDP6) ENRKey() string { return "udp6" } + // ID is the "id" key, which holds the name of the identity scheme. type ID string @@ -72,17 +82,27 @@ const IDv4 = ID("v4") // the default identity scheme func (v ID) ENRKey() string { return "id" } -// IP is the "ip" key, which holds the IP address of the node. +// IP is either the "ip" or "ip6" key, depending on the value. +// Use this value to encode IP addresses that can be either v4 or v6. +// To load an address from a record use the IPv4 or IPv6 types. type IP net.IP -func (v IP) ENRKey() string { return "ip" } +func (v IP) ENRKey() string { + if net.IP(v).To4() == nil { + return "ip6" + } + return "ip" +} // EncodeRLP implements rlp.Encoder. func (v IP) EncodeRLP(w io.Writer) error { if ip4 := net.IP(v).To4(); ip4 != nil { return rlp.Encode(w, ip4) } - return rlp.Encode(w, net.IP(v)) + if ip6 := net.IP(v).To16(); ip6 != nil { + return rlp.Encode(w, ip6) + } + return fmt.Errorf("invalid IP address: %v", net.IP(v)) } // DecodeRLP implements rlp.Decoder. @@ -96,6 +116,56 @@ func (v *IP) DecodeRLP(s *rlp.Stream) error { return nil } +// IPv4 is the "ip" key, which holds the IP address of the node. +type IPv4 net.IP + +func (v IPv4) ENRKey() string { return "ip" } + +// EncodeRLP implements rlp.Encoder. +func (v IPv4) EncodeRLP(w io.Writer) error { + ip4 := net.IP(v).To4() + if ip4 == nil { + return fmt.Errorf("invalid IPv4 address: %v", net.IP(v)) + } + return rlp.Encode(w, ip4) +} + +// DecodeRLP implements rlp.Decoder. +func (v *IPv4) DecodeRLP(s *rlp.Stream) error { + if err := s.Decode((*net.IP)(v)); err != nil { + return err + } + if len(*v) != 4 { + return fmt.Errorf("invalid IPv4 address, want 4 bytes: %v", *v) + } + return nil +} + +// IPv6 is the "ip6" key, which holds the IP address of the node. +type IPv6 net.IP + +func (v IPv6) ENRKey() string { return "ip6" } + +// EncodeRLP implements rlp.Encoder. +func (v IPv6) EncodeRLP(w io.Writer) error { + ip6 := net.IP(v).To16() + if ip6 == nil { + return fmt.Errorf("invalid IPv6 address: %v", net.IP(v)) + } + return rlp.Encode(w, ip6) +} + +// DecodeRLP implements rlp.Decoder. +func (v *IPv6) 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 +} + // KeyError is an error related to a key. type KeyError struct { Key string diff --git a/p2p/message.go b/p2p/message.go index 3ddb33eee5..1a9c7a35d9 100644 --- a/p2p/message.go +++ b/p2p/message.go @@ -40,9 +40,13 @@ import ( // separate Msg with a bytes.Reader as Payload for each send. type Msg struct { Code uint64 - Size uint32 // size of the paylod + Size uint32 // Size of the raw payload Payload io.Reader ReceivedAt time.Time + + meterCap Cap // Protocol name and version for egress metering + meterCode uint64 // Message within protocol for egress metering + meterSize uint32 // Compressed message size for ingress metering } // Decode parses the RLP content of a message into @@ -67,6 +71,10 @@ func (msg Msg) Discard() error { return err } +func (msg Msg) Time() time.Time { + return msg.ReceivedAt +} + type MsgReader interface { ReadMsg() (Msg, error) } @@ -170,7 +178,7 @@ type MsgPipeRW struct { closed *int32 } -// WriteMsg sends a messsage on the pipe. +// WriteMsg sends a message on the pipe. // It blocks until the receiver has consumed the message payload. func (p *MsgPipeRW) WriteMsg(msg Msg) error { if atomic.LoadInt32(p.closed) == 0 { @@ -262,7 +270,7 @@ type msgEventer struct { // newMsgEventer returns a msgEventer which sends message events to the given // feed -func newMsgEventer(rw MsgReadWriter, feed *event.Feed, peerID enode.ID, remote, local, proto string) *msgEventer { +func newMsgEventer(rw MsgReadWriter, feed *event.Feed, peerID enode.ID, proto, remote, local string) *msgEventer { return &msgEventer{ MsgReadWriter: rw, feed: feed, diff --git a/p2p/message_test.go b/p2p/message_test.go index d2059d32f0..e575c5d96e 100644 --- a/p2p/message_test.go +++ b/p2p/message_test.go @@ -18,11 +18,9 @@ package p2p import ( "bytes" - "encoding/hex" "fmt" "io" "runtime" - "strings" "testing" "time" ) @@ -141,23 +139,3 @@ func TestEOFSignal(t *testing.T) { default: } } - -func unhex(str string) []byte { - r := strings.NewReplacer("\t", "", " ", "", "\n", "") - b, err := hex.DecodeString(r.Replace(str)) - if err != nil { - panic(fmt.Sprintf("invalid hex string: %q", str)) - } - return b -} - -func TestMsgCode(t *testing.T) { - fmt.Printf("%x\n", 456) - - msg := Msg{Code: 0x0a} - - x := fmt.Sprintf("msg.Code: 0x%x", msg.Code) - - fmt.Println(x) - -} diff --git a/p2p/metrics.go b/p2p/metrics.go index 96cebc356b..e2ffd9da88 100644 --- a/p2p/metrics.go +++ b/p2p/metrics.go @@ -19,115 +19,52 @@ package p2p import ( - "fmt" "net" - "sync" - "sync/atomic" - "time" - "github.com/AlayaNetwork/Alaya-Go/event" - "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/metrics" - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" ) const ( - MetricsInboundConnects = "p2p/InboundConnects" // Name for the registered inbound connects meter - MetricsInboundTraffic = "p2p/InboundTraffic" // Name for the registered inbound traffic meter - MetricsOutboundConnects = "p2p/OutboundConnects" // Name for the registered outbound connects meter - MetricsOutboundTraffic = "p2p/OutboundTraffic" // Name for the registered outbound traffic meter + // ingressMeterName is the prefix of the per-packet inbound metrics. + ingressMeterName = "p2p/ingress" - MeteredPeerLimit = 1024 // This amount of peers are individually metered -) - -var ( - ingressConnectMeter = metrics.NewRegisteredMeter(MetricsInboundConnects, nil) // Meter counting the ingress connections - ingressTrafficMeter = metrics.NewRegisteredMeter(MetricsInboundTraffic, nil) // Meter metering the cumulative ingress traffic - egressConnectMeter = metrics.NewRegisteredMeter(MetricsOutboundConnects, nil) // Meter counting the egress connections - egressTrafficMeter = metrics.NewRegisteredMeter(MetricsOutboundTraffic, nil) // Meter metering the cumulative egress traffic - - PeerIngressRegistry = metrics.NewPrefixedChildRegistry(metrics.EphemeralRegistry, MetricsInboundTraffic+"/") // Registry containing the peer ingress - PeerEgressRegistry = metrics.NewPrefixedChildRegistry(metrics.EphemeralRegistry, MetricsOutboundTraffic+"/") // Registry containing the peer egress + // egressMeterName is the prefix of the per-packet outbound metrics. + egressMeterName = "p2p/egress" - meteredPeerFeed event.Feed // Event feed for peer metrics - meteredPeerCount int32 // Actually stored peer connection count + // HandleHistName is the prefix of the per-packet serving time histograms. + HandleHistName = "p2p/handle" ) -// MeteredPeerEventType is the type of peer events emitted by a metered connection. -type MeteredPeerEventType int - -const ( - // PeerConnected is the type of event emitted when a peer successfully - // made the handshake. - PeerConnected MeteredPeerEventType = iota - - // PeerDisconnected is the type of event emitted when a peer disconnects. - PeerDisconnected - - // PeerHandshakeFailed is the type of event emitted when a peer fails to - // make the handshake or disconnects before the handshake. - PeerHandshakeFailed +var ( + ingressConnectMeter = metrics.NewRegisteredMeter("p2p/serves", nil) + ingressTrafficMeter = metrics.NewRegisteredMeter(ingressMeterName, nil) + egressConnectMeter = metrics.NewRegisteredMeter("p2p/dials", nil) + egressTrafficMeter = metrics.NewRegisteredMeter(egressMeterName, nil) + activePeerGauge = metrics.NewRegisteredGauge("p2p/peers", nil) ) -// MeteredPeerEvent is an event emitted when peers connect or disconnect. -type MeteredPeerEvent struct { - Type MeteredPeerEventType // Type of peer event - IP net.IP // IP address of the peer - ID enode.ID // NodeID of the peer - Elapsed time.Duration // Time elapsed between the connection and the handshake/disconnection - Ingress uint64 // Ingress count at the moment of the event - Egress uint64 // Egress count at the moment of the event -} - -// SubscribeMeteredPeerEvent registers a subscription for peer life-cycle events -// if metrics collection is enabled. -func SubscribeMeteredPeerEvent(ch chan<- MeteredPeerEvent) event.Subscription { - return meteredPeerFeed.Subscribe(ch) -} - // meteredConn is a wrapper around a net.Conn that meters both the // inbound and outbound network traffic. type meteredConn struct { - net.Conn // Network connection to wrap with metering - - connected time.Time // Connection time of the peer - ip net.IP // IP address of the peer - id enode.ID // NodeID of the peer - - // trafficMetered denotes if the peer is registered in the traffic registries. - // Its value is true if the metered peer count doesn't reach the limit in the - // moment of the peer's connection. - trafficMetered bool - ingressMeter metrics.Meter // Meter for the read bytes of the peer - egressMeter metrics.Meter // Meter for the written bytes of the peer - - lock sync.RWMutex // Lock protecting the metered connection's internals + net.Conn } // newMeteredConn creates a new metered connection, bumps the ingress or egress // connection meter and also increases the metered peer count. If the metrics -// system is disabled or the IP address is unspecified, this function returns -// the original object. -func newMeteredConn(conn net.Conn, ingress bool, ip net.IP) net.Conn { +// system is disabled, function returns the original connection. +func newMeteredConn(conn net.Conn, ingress bool, addr *net.TCPAddr) net.Conn { // Short circuit if metrics are disabled if !metrics.Enabled { return conn } - if ip.IsUnspecified() { - log.Warn("Peer IP is unspecified") - return conn - } // Bump the connection counters and wrap the connection if ingress { ingressConnectMeter.Mark(1) } else { egressConnectMeter.Mark(1) } - return &meteredConn{ - Conn: conn, - ip: ip, - connected: time.Now(), - } + activePeerGauge.Inc(1) + return &meteredConn{Conn: conn} } // Read delegates a network read to the underlying connection, bumping the common @@ -135,11 +72,6 @@ func newMeteredConn(conn net.Conn, ingress bool, ip net.IP) net.Conn { func (c *meteredConn) Read(b []byte) (n int, err error) { n, err = c.Conn.Read(b) ingressTrafficMeter.Mark(int64(n)) - c.lock.RLock() - if c.trafficMetered { - c.ingressMeter.Mark(int64(n)) - } - c.lock.RUnlock() return n, err } @@ -148,84 +80,15 @@ func (c *meteredConn) Read(b []byte) (n int, err error) { func (c *meteredConn) Write(b []byte) (n int, err error) { n, err = c.Conn.Write(b) egressTrafficMeter.Mark(int64(n)) - c.lock.RLock() - if c.trafficMetered { - c.egressMeter.Mark(int64(n)) - } - c.lock.RUnlock() return n, err } -// handshakeDone is called when a peer handshake is done. Registers the peer to -// the ingress and the egress traffic registries using the peer's IP and node ID, -// also emits connect event. -func (c *meteredConn) handshakeDone(id enode.ID) { - if atomic.AddInt32(&meteredPeerCount, 1) >= MeteredPeerLimit { - // Don't register the peer in the traffic registries. - atomic.AddInt32(&meteredPeerCount, -1) - c.lock.Lock() - c.id, c.trafficMetered = id, false - c.lock.Unlock() - log.Warn("Metered peer count reached the limit") - } else { - key := fmt.Sprintf("%s/%s", c.ip, id.String()) - c.lock.Lock() - c.id, c.trafficMetered = id, true - c.ingressMeter = metrics.NewRegisteredMeter(key, PeerIngressRegistry) - c.egressMeter = metrics.NewRegisteredMeter(key, PeerEgressRegistry) - c.lock.Unlock() - } - meteredPeerFeed.Send(MeteredPeerEvent{ - Type: PeerConnected, - IP: c.ip, - ID: id, - Elapsed: time.Since(c.connected), - }) -} - // Close delegates a close operation to the underlying connection, unregisters // the peer from the traffic registries and emits close event. func (c *meteredConn) Close() error { err := c.Conn.Close() - c.lock.RLock() - if c.id == (enode.ID{}) { - // If the peer disconnects before the handshake. - c.lock.RUnlock() - meteredPeerFeed.Send(MeteredPeerEvent{ - Type: PeerHandshakeFailed, - IP: c.ip, - Elapsed: time.Since(c.connected), - }) - return err - } - id := c.id - if !c.trafficMetered { - // If the peer isn't registered in the traffic registries. - c.lock.RUnlock() - meteredPeerFeed.Send(MeteredPeerEvent{ - Type: PeerDisconnected, - IP: c.ip, - ID: id, - }) - return err + if err == nil { + activePeerGauge.Dec(1) } - ingress, egress := uint64(c.ingressMeter.Count()), uint64(c.egressMeter.Count()) - c.lock.RUnlock() - - // Decrement the metered peer count - atomic.AddInt32(&meteredPeerCount, -1) - - // Unregister the peer from the traffic registries - key := fmt.Sprintf("%s/%s", c.ip, id) - PeerIngressRegistry.Unregister(key) - PeerEgressRegistry.Unregister(key) - - meteredPeerFeed.Send(MeteredPeerEvent{ - Type: PeerDisconnected, - IP: c.ip, - ID: id, - Ingress: ingress, - Egress: egress, - }) return err } diff --git a/p2p/nat/nat.go b/p2p/nat/nat.go index f46745b947..bc6b3ed017 100644 --- a/p2p/nat/nat.go +++ b/p2p/nat/nat.go @@ -92,15 +92,14 @@ func Parse(spec string) (Interface, error) { } const ( - mapTimeout = 20 * time.Minute - mapUpdateInterval = 15 * time.Minute + mapTimeout = 10 * time.Minute ) // Map adds a port mapping on m and keeps it alive until c is closed. // This function is typically invoked in its own goroutine. -func Map(m Interface, c chan struct{}, protocol string, extport, intport int, name string) { +func Map(m Interface, c <-chan struct{}, protocol string, extport, intport int, name string) { log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m) - refresh := time.NewTimer(mapUpdateInterval) + refresh := time.NewTimer(mapTimeout) defer func() { refresh.Stop() log.Debug("Deleting port mapping") @@ -122,7 +121,7 @@ func Map(m Interface, c chan struct{}, protocol string, extport, intport int, na if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil { log.Debug("Couldn't add port mapping", "err", err) } - refresh.Reset(mapUpdateInterval) + refresh.Reset(mapTimeout) } } } diff --git a/p2p/nat/natupnp.go b/p2p/nat/natupnp.go index 029143b7bc..1f5d714664 100644 --- a/p2p/nat/natupnp.go +++ b/p2p/nat/natupnp.go @@ -21,6 +21,7 @@ import ( "fmt" "net" "strings" + "sync" "time" "github.com/huin/goupnp" @@ -28,12 +29,17 @@ import ( "github.com/huin/goupnp/dcps/internetgateway2" ) -const soapRequestTimeout = 3 * time.Second +const ( + soapRequestTimeout = 3 * time.Second + rateLimit = 200 * time.Millisecond +) type upnp struct { - dev *goupnp.RootDevice - service string - client upnpClient + dev *goupnp.RootDevice + service string + client upnpClient + mu sync.Mutex + lastReqTime time.Time } type upnpClient interface { @@ -43,8 +49,23 @@ type upnpClient interface { GetNATRSIPStatus() (sip bool, nat bool, err error) } +func (n *upnp) natEnabled() bool { + var ok bool + var err error + n.withRateLimit(func() error { + _, ok, err = n.client.GetNATRSIPStatus() + return err + }) + return err == nil && ok +} + func (n *upnp) ExternalIP() (addr net.IP, err error) { - ipString, err := n.client.GetExternalIPAddress() + var ipString string + n.withRateLimit(func() error { + ipString, err = n.client.GetExternalIPAddress() + return err + }) + if err != nil { return nil, err } @@ -63,7 +84,10 @@ func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, li protocol = strings.ToUpper(protocol) lifetimeS := uint32(lifetime / time.Second) n.DeleteMapping(protocol, extport, intport) - return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS) + + return n.withRateLimit(func() error { + return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS) + }) } func (n *upnp) internalAddress() (net.IP, error) { @@ -90,36 +114,51 @@ func (n *upnp) internalAddress() (net.IP, error) { } func (n *upnp) DeleteMapping(protocol string, extport, intport int) error { - return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol)) + return n.withRateLimit(func() error { + return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol)) + }) } func (n *upnp) String() string { return "UPNP " + n.service } +func (n *upnp) withRateLimit(fn func() error) error { + n.mu.Lock() + defer n.mu.Unlock() + + lastreq := time.Since(n.lastReqTime) + if lastreq < rateLimit { + time.Sleep(rateLimit - lastreq) + } + err := fn() + n.lastReqTime = time.Now() + return err +} + // discoverUPnP searches for Internet Gateway Devices // and returns the first one it can find on the local network. func discoverUPnP() Interface { found := make(chan *upnp, 2) // IGDv1 - go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(dev *goupnp.RootDevice, sc goupnp.ServiceClient) *upnp { + go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(sc goupnp.ServiceClient) *upnp { switch sc.Service.ServiceType { case internetgateway1.URN_WANIPConnection_1: - return &upnp{dev, "IGDv1-IP1", &internetgateway1.WANIPConnection1{ServiceClient: sc}} + return &upnp{service: "IGDv1-IP1", client: &internetgateway1.WANIPConnection1{ServiceClient: sc}} case internetgateway1.URN_WANPPPConnection_1: - return &upnp{dev, "IGDv1-PPP1", &internetgateway1.WANPPPConnection1{ServiceClient: sc}} + return &upnp{service: "IGDv1-PPP1", client: &internetgateway1.WANPPPConnection1{ServiceClient: sc}} } return nil }) // IGDv2 - go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(dev *goupnp.RootDevice, sc goupnp.ServiceClient) *upnp { + go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(sc goupnp.ServiceClient) *upnp { switch sc.Service.ServiceType { case internetgateway2.URN_WANIPConnection_1: - return &upnp{dev, "IGDv2-IP1", &internetgateway2.WANIPConnection1{ServiceClient: sc}} + return &upnp{service: "IGDv2-IP1", client: &internetgateway2.WANIPConnection1{ServiceClient: sc}} case internetgateway2.URN_WANIPConnection_2: - return &upnp{dev, "IGDv2-IP2", &internetgateway2.WANIPConnection2{ServiceClient: sc}} + return &upnp{service: "IGDv2-IP2", client: &internetgateway2.WANIPConnection2{ServiceClient: sc}} case internetgateway2.URN_WANPPPConnection_1: - return &upnp{dev, "IGDv2-PPP1", &internetgateway2.WANPPPConnection1{ServiceClient: sc}} + return &upnp{service: "IGDv2-PPP1", client: &internetgateway2.WANPPPConnection1{ServiceClient: sc}} } return nil }) @@ -134,7 +173,7 @@ func discoverUPnP() Interface { // finds devices matching the given target and calls matcher for all // advertised services of each device. The first non-nil service found // is sent into out. If no service matched, nil is sent. -func discover(out chan<- *upnp, target string, matcher func(*goupnp.RootDevice, goupnp.ServiceClient) *upnp) { +func discover(out chan<- *upnp, target string, matcher func(goupnp.ServiceClient) *upnp) { devs, err := goupnp.DiscoverDevices(target) if err != nil { out <- nil @@ -157,16 +196,17 @@ func discover(out chan<- *upnp, target string, matcher func(*goupnp.RootDevice, Service: service, } sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout - upnp := matcher(devs[i].Root, sc) + upnp := matcher(sc) if upnp == nil { return } + upnp.dev = devs[i].Root + // check whether port mapping is enabled - if _, nat, err := upnp.client.GetNATRSIPStatus(); err != nil || !nat { - return + if upnp.natEnabled() { + out <- upnp + found = true } - out <- upnp - found = true }) } if !found { diff --git a/p2p/nat/natupnp_test.go b/p2p/nat/natupnp_test.go index 79f6d25ae8..17483a7036 100644 --- a/p2p/nat/natupnp_test.go +++ b/p2p/nat/natupnp_test.go @@ -21,6 +21,7 @@ import ( "io" "net" "net/http" + "os" "runtime" "strings" "testing" @@ -162,7 +163,11 @@ func TestUPNP_DDWRT(t *testing.T) { // Attempt to discover the fake device. discovered := discoverUPnP() if discovered == nil { - t.Fatalf("not discovered") + if os.Getenv("CI") != "" { + t.Fatalf("not discovered") + } else { + t.Skipf("UPnP not discovered (known issue, see https://github.com/ethereum/go-ethereum/issues/21476)") + } } upnp, _ := discovered.(*upnp) if upnp.service != "IGDv1-IP1" { diff --git a/p2p/discv5/sim_testmain_test.go b/p2p/netutil/addrutil.go similarity index 56% rename from p2p/discv5/sim_testmain_test.go rename to p2p/netutil/addrutil.go index 77e751c419..b261a52955 100644 --- a/p2p/discv5/sim_testmain_test.go +++ b/p2p/netutil/addrutil.go @@ -14,30 +14,20 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// +build go1.4,nacl,faketime_simulation +package netutil -package discv5 +import "net" -import ( - "os" - "runtime" - "testing" - "unsafe" -) - -// Enable fake time mode in the runtime, like on the go playground. -// There is a slight chance that this won't work because some go code -// might have executed before the variable is set. - -//go:linkname faketime runtime.faketime -var faketime = 1 - -func TestMain(m *testing.M) { - // We need to use unsafe somehow in order to get access to go:linkname. - _ = unsafe.Sizeof(0) - - // Run the actual test. runWithPlaygroundTime ensures that the only test - // that runs is the one calling it. - runtime.GOMAXPROCS(8) - os.Exit(m.Run()) +// AddrIP gets the IP address contained in addr. It returns nil if no address is present. +func AddrIP(addr net.Addr) net.IP { + switch a := addr.(type) { + case *net.IPAddr: + return a.IP + case *net.TCPAddr: + return a.IP + case *net.UDPAddr: + return a.IP + default: + return nil + } } diff --git a/p2p/netutil/error.go b/p2p/netutil/error.go index cb21b9cd4c..5d3d9bfd65 100644 --- a/p2p/netutil/error.go +++ b/p2p/netutil/error.go @@ -23,3 +23,11 @@ func IsTemporaryError(err error) bool { }) return ok && tempErr.Temporary() || isPacketTooBig(err) } + +// IsTimeout checks whether the given error is a timeout. +func IsTimeout(err error) bool { + timeoutErr, ok := err.(interface { + Timeout() bool + }) + return ok && timeoutErr.Timeout() +} diff --git a/p2p/netutil/net.go b/p2p/netutil/net.go index 656abb6825..d5da3c694f 100644 --- a/p2p/netutil/net.go +++ b/p2p/netutil/net.go @@ -212,7 +212,7 @@ func sameNet(bits uint, ip, other net.IP) bool { if mask != 0 && nb < len(ip) && ip[nb]&mask != other[nb]&mask { return false } - return nb <= len(ip) && bytes.Equal(ip[:nb], other[:nb]) + return nb <= len(ip) && ip[:nb].Equal(other[:nb]) } // DistinctNetSet tracks IPs, ensuring that at most N of them diff --git a/p2p/peer.go b/p2p/peer.go index fe34f1e074..332c952565 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -25,6 +25,8 @@ import ( "sync" "time" + "github.com/AlayaNetwork/Alaya-Go/metrics" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/enr" @@ -115,7 +117,8 @@ type Peer struct { disc chan DiscReason // events receives message send / receive events if set - events *event.Feed + events *event.Feed + testPipe *MsgPipeRW // for testing } // NewPeer returns a peer for testing purposes. @@ -123,11 +126,20 @@ func NewPeer(id enode.ID, name string, caps []Cap) *Peer { pipe, _ := net.Pipe() node := enode.SignNull(new(enr.Record), id) conn := &conn{fd: pipe, transport: nil, node: node, caps: caps, name: name} - peer := newPeer(conn, nil) + peer := newPeer(log.Root(), conn, nil) close(peer.closed) // ensures Disconnect doesn't block return peer } +// NewPeerPipe creates a peer for testing purposes. +// The message pipe given as the last parameter is closed when +// Disconnect is called on the peer. +func NewPeerPipe(id enode.ID, name string, caps []Cap, pipe *MsgPipeRW) *Peer { + p := NewPeer(id, name, caps) + p.testPipe = pipe + return p +} + // ID returns the node's public key. func (p *Peer) ID() enode.ID { return p.rw.node.ID() @@ -143,8 +155,17 @@ func (p *Peer) Node() *enode.Node { return p.rw.node } -// Name returns the node name that the remote node advertised. +// Name returns an abbreviated form of the name func (p *Peer) Name() string { + s := p.rw.name + if len(s) > 20 { + return s[:20] + "..." + } + return s +} + +// Fullname returns the node name that the remote node advertised. +func (p *Peer) Fullname() string { return p.rw.name } @@ -154,6 +175,20 @@ func (p *Peer) Caps() []Cap { return p.rw.caps } +// RunningCap returns true if the peer is actively connected using any of the +// enumerated versions of a specific protocol, meaning that at least one of the +// versions is supported by both this node and the peer p. +func (p *Peer) RunningCap(protocol string, versions []uint) bool { + if proto, ok := p.running[protocol]; ok { + for _, ver := range versions { + if proto.Version == ver { + return true + } + } + } + return false +} + // RemoteAddr returns the remote address of the network connection. func (p *Peer) RemoteAddr() net.Addr { return p.rw.fd.RemoteAddr() @@ -167,6 +202,10 @@ func (p *Peer) LocalAddr() net.Addr { // Disconnect terminates the peer connection with the given reason. // It returns immediately and does not wait until the connection is closed. func (p *Peer) Disconnect(reason DiscReason) { + if p.testPipe != nil { + p.testPipe.Close() + } + select { case p.disc <- reason: case <-p.closed: @@ -184,7 +223,7 @@ func (p *Peer) Inbound() bool { return p.rw.is(inboundConn) } -func newPeer(conn *conn, protocols []Protocol) *Peer { +func newPeer(log log.Logger, conn *conn, protocols []Protocol) *Peer { protomap := matchProtocols(protocols, conn.caps, conn) p := &Peer{ rw: conn, @@ -226,7 +265,6 @@ loop: // there was no error. if err != nil { reason = DiscNetworkError - log.Debug("network error while sending message to peer", "err", err) break loop } writeStart <- struct{}{} @@ -237,15 +275,12 @@ loop: } else { reason = DiscNetworkError } - log.Debug("network error while reading message from peer", "err", err) break loop case err = <-p.protoErr: reason = discReasonForError(err) - log.Debug("protoErr", "err", err) break loop case err = <-p.disc: reason = discReasonForError(err) - log.Debug("disconnection", "err", err) break loop } } @@ -310,6 +345,11 @@ func (p *Peer) handle(msg Msg) error { if err != nil { return fmt.Errorf("msg code out of range: %v", msg.Code) } + if metrics.Enabled { + m := fmt.Sprintf("%s/%s/%d/%#02x", ingressMeterName, proto.Name, proto.Version, msg.Code-proto.offset) + metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize)) + metrics.GetOrRegisterMeter(m+"/packets", nil).Mark(1) + } select { case proto.in <- msg: return nil @@ -370,6 +410,7 @@ func (p *Peer) startProtocols(writeStart <-chan struct{}, writeErr chan<- error) } p.log.Trace(fmt.Sprintf("Starting protocol %s/%d", proto.Name, proto.Version)) go func() { + defer p.wg.Done() err := proto.Run(p, rw) if err == nil { p.log.Trace(fmt.Sprintf("Protocol %s/%d returned", proto.Name, proto.Version)) @@ -378,7 +419,6 @@ func (p *Peer) startProtocols(writeStart <-chan struct{}, writeErr chan<- error) p.log.Trace(fmt.Sprintf("Protocol %s/%d failed", proto.Name, proto.Version), "err", err) } p.protoErr <- err - p.wg.Done() }() } } @@ -408,7 +448,11 @@ func (rw *protoRW) WriteMsg(msg Msg) (err error) { if msg.Code >= rw.Length { return newPeerError(errInvalidMsgCode, "not handled") } + msg.meterCap = rw.cap() + msg.meterCode = msg.Code + msg.Code += rw.offset + select { case <-rw.wstart: err = rw.w.WriteMsg(msg) @@ -418,7 +462,6 @@ func (rw *protoRW) WriteMsg(msg Msg) (err error) { // as well but we don't want to rely on that. rw.werr <- err case <-rw.closed: - log.Debug("send message to peer error cause peer is shutting down") err = ErrShuttingDown } return err @@ -430,7 +473,6 @@ func (rw *protoRW) ReadMsg() (Msg, error) { msg.Code -= rw.offset return msg, nil case <-rw.closed: - log.Error("ReadMsg from peer error, connection is closed") return Msg{}, io.EOF } } @@ -439,10 +481,11 @@ func (rw *protoRW) ReadMsg() (Msg, error) { // peer. Sub-protocol independent fields are contained and initialized here, with // protocol specifics delegated to all connected sub-protocols. type PeerInfo struct { - Enode string `json:"enode"` // Node URL - ID string `json:"id"` // Unique node identifier - Name string `json:"name"` // Name of the node, including client type, version, OS, custom data - Caps []string `json:"caps"` // Protocols advertised by this peer + ENR string `json:"enr,omitempty"` // Ethereum Node Record + Enode string `json:"enode"` // Node URL + ID string `json:"id"` // Unique node identifier + 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 { LocalAddress string `json:"localAddress"` // Local endpoint of the TCP data connection RemoteAddress string `json:"remoteAddress"` // Remote endpoint of the TCP data connection @@ -463,12 +506,15 @@ func (p *Peer) Info() *PeerInfo { } // Assemble the generic peer metadata info := &PeerInfo{ - Enode: p.Node().String(), + Enode: p.Node().URLv4(), ID: p.ID().String(), - Name: p.Name(), + Name: p.Fullname(), Caps: caps, Protocols: make(map[string]interface{}), } + if p.Node().Seq() > 0 { + info.ENR = p.Node().String() + } info.Network.LocalAddress = p.LocalAddr().String() info.Network.RemoteAddress = p.RemoteAddr().String() info.Network.Inbound = p.rw.is(inboundConn) diff --git a/p2p/peer_error.go b/p2p/peer_error.go index e992026ee4..b0c4060efe 100644 --- a/p2p/peer_error.go +++ b/p2p/peer_error.go @@ -91,7 +91,7 @@ var discReasonToString = [...]string{ } func (d DiscReason) String() string { - if len(discReasonToString) < int(d) { + if len(discReasonToString) <= int(d) { return fmt.Sprintf("unknown disconnect reason %d", d) } return discReasonToString[d] diff --git a/p2p/peer_test.go b/p2p/peer_test.go index 5aa64a32e1..c3cf71ebbf 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -17,13 +17,21 @@ package p2p import ( + "encoding/binary" "errors" "fmt" "math/rand" "net" "reflect" + "strconv" + "strings" "testing" "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + + "github.com/AlayaNetwork/Alaya-Go/log" ) var discard = Protocol{ @@ -43,16 +51,57 @@ var discard = Protocol{ }, } +// uintID encodes i into a node ID. +func uintID(i uint16) enode.ID { + var id enode.ID + binary.BigEndian.PutUint16(id[:], i) + return id +} + +// newNode creates a node record with the given address. +func newNode(id enode.ID, addr string) *enode.Node { + var r enr.Record + if addr != "" { + // Set the port if present. + if strings.Contains(addr, ":") { + hs, ps, err := net.SplitHostPort(addr) + if err != nil { + panic(fmt.Errorf("invalid address %q", addr)) + } + port, err := strconv.Atoi(ps) + if err != nil { + panic(fmt.Errorf("invalid port in %q", addr)) + } + r.Set(enr.TCP(port)) + r.Set(enr.UDP(port)) + addr = hs + } + // Set the IP. + ip := net.ParseIP(addr) + if ip == nil { + panic(fmt.Errorf("invalid IP %q", addr)) + } + r.Set(enr.IP(ip)) + } + return enode.SignNull(&r, id) +} + func testPeer(protos []Protocol) (func(), *conn, *Peer, <-chan error) { - fd1, fd2 := net.Pipe() - c1 := &conn{fd: fd1, node: newNode(randomID(), nil), transport: newTestTransport(&newkey().PublicKey, fd1)} - c2 := &conn{fd: fd2, node: newNode(randomID(), nil), transport: newTestTransport(&newkey().PublicKey, fd2)} + var ( + fd1, fd2 = net.Pipe() + key1, key2 = newkey(), newkey() + t1 = newTestTransport(&key2.PublicKey, fd1, nil) + t2 = newTestTransport(&key1.PublicKey, fd2, &key1.PublicKey) + ) + + c1 := &conn{fd: fd1, node: newNode(uintID(1), ""), transport: t1} + c2 := &conn{fd: fd2, node: newNode(uintID(2), ""), transport: t2} for _, p := range protos { c1.caps = append(c1.caps, p.cap()) c2.caps = append(c2.caps, p.cap()) } - peer := newPeer(c1, protos) + peer := newPeer(log.Root(), c1, protos) errc := make(chan error, 1) go func() { _, err := peer.run() @@ -131,9 +180,12 @@ func TestPeerPing(t *testing.T) { } } +// This test checks that a disconnect message sent by a peer is returned +// as the error from Peer.run. func TestPeerDisconnect(t *testing.T) { closer, rw, _, disc := testPeer(nil) defer closer() + if err := SendItems(rw, discMsg, DiscQuitting); err != nil { t.Fatal(err) } @@ -150,7 +202,7 @@ func TestPeerDisconnect(t *testing.T) { // This test is supposed to verify that Peer can reliably handle // multiple causes of disconnection occurring at the same time. func TestPeerDisconnectRace(t *testing.T) { - maybe := func() bool { return rand.Intn(1) == 1 } + maybe := func() bool { return rand.Intn(2) == 1 } for i := 0; i < 1000; i++ { protoclose := make(chan error) diff --git a/p2p/protocol.go b/p2p/protocol.go index b701364493..2472834277 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -55,6 +55,11 @@ type Protocol struct { // but returns nil, it is assumed that the protocol handshake is still running. PeerInfo func(id enode.ID) interface{} + // DialCandidates, if non-nil, is a way to tell Server about protocol-specific nodes + // that should be dialed. The server continuously reads nodes from the iterator and + // attempts to create connections to them. + DialCandidates enode.Iterator + // Attributes contains protocol specific information for the node record. Attributes []enr.Entry } @@ -80,5 +85,3 @@ func (cs capsByNameAndVersion) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] } 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) } - -func (capsByNameAndVersion) ENRKey() string { return "cap" } diff --git a/p2p/protocols/accounting.go b/p2p/protocols/accounting.go deleted file mode 100644 index 756b8a3ade..0000000000 --- a/p2p/protocols/accounting.go +++ /dev/null @@ -1,182 +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 . - -package protocols - -import ( - "time" - - "github.com/AlayaNetwork/Alaya-Go/metrics" -) - -// define some metrics -var ( - // All metrics are cumulative - - // total amount of units credited - mBalanceCredit = metrics.NewRegisteredCounterForced("account.balance.credit", metrics.AccountingRegistry) - // total amount of units debited - mBalanceDebit = metrics.NewRegisteredCounterForced("account.balance.debit", metrics.AccountingRegistry) - // total amount of bytes credited - mBytesCredit = metrics.NewRegisteredCounterForced("account.bytes.credit", metrics.AccountingRegistry) - // total amount of bytes debited - mBytesDebit = metrics.NewRegisteredCounterForced("account.bytes.debit", metrics.AccountingRegistry) - // total amount of credited messages - mMsgCredit = metrics.NewRegisteredCounterForced("account.msg.credit", metrics.AccountingRegistry) - // total amount of debited messages - mMsgDebit = metrics.NewRegisteredCounterForced("account.msg.debit", metrics.AccountingRegistry) - // how many times local node had to drop remote peers - mPeerDrops = metrics.NewRegisteredCounterForced("account.peerdrops", metrics.AccountingRegistry) - // how many times local node overdrafted and dropped - mSelfDrops = metrics.NewRegisteredCounterForced("account.selfdrops", metrics.AccountingRegistry) -) - -// Prices defines how prices are being passed on to the accounting instance -type Prices interface { - // Return the Price for a message - Price(interface{}) *Price -} - -type Payer bool - -const ( - Sender = Payer(true) - Receiver = Payer(false) -) - -// Price represents the costs of a message -type Price struct { - Value uint64 - PerByte bool // True if the price is per byte or for unit - Payer Payer -} - -// For gives back the price for a message -// A protocol provides the message price in absolute value -// This method then returns the correct signed amount, -// depending on who pays, which is identified by the `payer` argument: -// `Send` will pass a `Sender` payer, `Receive` will pass the `Receiver` argument. -// Thus: If Sending and sender pays, amount positive, otherwise negative -// If Receiving, and receiver pays, amount positive, otherwise negative -func (p *Price) For(payer Payer, size uint32) int64 { - price := p.Value - if p.PerByte { - price *= uint64(size) - } - if p.Payer == payer { - return 0 - int64(price) - } - return int64(price) -} - -// Balance is the actual accounting instance -// Balance defines the operations needed for accounting -// Implementations internally maintain the balance for every peer -type Balance interface { - // Adds amount to the local balance with remote node `peer`; - // positive amount = credit local node - // negative amount = debit local node - Add(amount int64, peer *Peer) error -} - -// Accounting implements the Hook interface -// It interfaces to the balances through the Balance interface, -// while interfacing with protocols and its prices through the Prices interface -type Accounting struct { - Balance // interface to accounting logic - Prices // interface to prices logic -} - -func NewAccounting(balance Balance, po Prices) *Accounting { - ah := &Accounting{ - Prices: po, - Balance: balance, - } - return ah -} - -// SetupAccountingMetrics uses a separate registry for p2p accounting metrics; -// this registry should be independent of any other metrics as it persists at different endpoints. -// It also starts the persisting go-routine which -// at the passed interval writes the metrics to a LevelDB -func SetupAccountingMetrics(reportInterval time.Duration, path string) *AccountingMetrics { - // create the DB and start persisting - return NewAccountingMetrics(metrics.AccountingRegistry, reportInterval, path) -} - -// Send takes a peer, a size and a msg and -// - calculates the cost for the local node sending a msg of size to peer using the Prices interface -// - credits/debits local node using balance interface -func (ah *Accounting) Send(peer *Peer, size uint32, msg interface{}) error { - // get the price for a message (through the protocol spec) - price := ah.Price(msg) - // this message doesn't need accounting - if price == nil { - return nil - } - // evaluate the price for sending messages - costToLocalNode := price.For(Sender, size) - // do the accounting - err := ah.Add(costToLocalNode, peer) - // record metrics: just increase counters for user-facing metrics - ah.doMetrics(costToLocalNode, size, err) - return err -} - -// Receive takes a peer, a size and a msg and -// - calculates the cost for the local node receiving a msg of size from peer using the Prices interface -// - credits/debits local node using balance interface -func (ah *Accounting) Receive(peer *Peer, size uint32, msg interface{}) error { - // get the price for a message (through the protocol spec) - price := ah.Price(msg) - // this message doesn't need accounting - if price == nil { - return nil - } - // evaluate the price for receiving messages - costToLocalNode := price.For(Receiver, size) - // do the accounting - err := ah.Add(costToLocalNode, peer) - // record metrics: just increase counters for user-facing metrics - ah.doMetrics(costToLocalNode, size, err) - return err -} - -// record some metrics -// this is not an error handling. `err` is returned by both `Send` and `Receive` -// `err` will only be non-nil if a limit has been violated (overdraft), in which case the peer has been dropped. -// if the limit has been violated and `err` is thus not nil: -// * if the price is positive, local node has been credited; thus `err` implicitly signals the REMOTE has been dropped -// * if the price is negative, local node has been debited, thus `err` implicitly signals LOCAL node "overdraft" -func (ah *Accounting) doMetrics(price int64, size uint32, err error) { - if price > 0 { - mBalanceCredit.Inc(price) - mBytesCredit.Inc(int64(size)) - mMsgCredit.Inc(1) - if err != nil { - // increase the number of times a remote node has been dropped due to "overdraft" - mPeerDrops.Inc(1) - } - } else { - mBalanceDebit.Inc(price) - mBytesDebit.Inc(int64(size)) - mMsgDebit.Inc(1) - if err != nil { - // increase the number of times the local node has done an "overdraft" in respect to other nodes - mSelfDrops.Inc(1) - } - } -} diff --git a/p2p/protocols/accounting_api.go b/p2p/protocols/accounting_api.go deleted file mode 100644 index 48e2af9fea..0000000000 --- a/p2p/protocols/accounting_api.go +++ /dev/null @@ -1,94 +0,0 @@ -package protocols - -import ( - "errors" -) - -// Textual version number of accounting API -const AccountingVersion = "1.0" - -var errNoAccountingMetrics = errors.New("accounting metrics not enabled") - -// AccountingApi provides an API to access account related information -type AccountingApi struct { - metrics *AccountingMetrics -} - -// NewAccountingApi creates a new AccountingApi -// m will be used to check if accounting metrics are enabled -func NewAccountingApi(m *AccountingMetrics) *AccountingApi { - return &AccountingApi{m} -} - -// Balance returns local node balance (units credited - units debited) -func (self *AccountingApi) Balance() (int64, error) { - if self.metrics == nil { - return 0, errNoAccountingMetrics - } - balance := mBalanceCredit.Count() - mBalanceDebit.Count() - return balance, nil -} - -// BalanceCredit returns total amount of units credited by local node -func (self *AccountingApi) BalanceCredit() (int64, error) { - if self.metrics == nil { - return 0, errNoAccountingMetrics - } - return mBalanceCredit.Count(), nil -} - -// BalanceCredit returns total amount of units debited by local node -func (self *AccountingApi) BalanceDebit() (int64, error) { - if self.metrics == nil { - return 0, errNoAccountingMetrics - } - return mBalanceDebit.Count(), nil -} - -// BytesCredit returns total amount of bytes credited by local node -func (self *AccountingApi) BytesCredit() (int64, error) { - if self.metrics == nil { - return 0, errNoAccountingMetrics - } - return mBytesCredit.Count(), nil -} - -// BalanceCredit returns total amount of bytes debited by local node -func (self *AccountingApi) BytesDebit() (int64, error) { - if self.metrics == nil { - return 0, errNoAccountingMetrics - } - return mBytesDebit.Count(), nil -} - -// MsgCredit returns total amount of messages credited by local node -func (self *AccountingApi) MsgCredit() (int64, error) { - if self.metrics == nil { - return 0, errNoAccountingMetrics - } - return mMsgCredit.Count(), nil -} - -// MsgDebit returns total amount of messages debited by local node -func (self *AccountingApi) MsgDebit() (int64, error) { - if self.metrics == nil { - return 0, errNoAccountingMetrics - } - return mMsgDebit.Count(), nil -} - -// PeerDrops returns number of times when local node had to drop remote peers -func (self *AccountingApi) PeerDrops() (int64, error) { - if self.metrics == nil { - return 0, errNoAccountingMetrics - } - return mPeerDrops.Count(), nil -} - -// SelfDrops returns number of times when local node was overdrafted and dropped -func (self *AccountingApi) SelfDrops() (int64, error) { - if self.metrics == nil { - return 0, errNoAccountingMetrics - } - return mSelfDrops.Count(), nil -} diff --git a/p2p/protocols/accounting_simulation_test.go b/p2p/protocols/accounting_simulation_test.go deleted file mode 100644 index b7f54f06f2..0000000000 --- a/p2p/protocols/accounting_simulation_test.go +++ /dev/null @@ -1,323 +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 . - -package protocols - -import ( - "context" - "flag" - "fmt" - "io/ioutil" - "math/rand" - "os" - "path/filepath" - "reflect" - "sync" - "testing" - "time" - - "github.com/mattn/go-colorable" - - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/rpc" - - "github.com/AlayaNetwork/Alaya-Go/node" - "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - "github.com/AlayaNetwork/Alaya-Go/p2p/simulations" - "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" -) - -const ( - content = "123456789" -) - -var ( - nodes = flag.Int("nodes", 30, "number of nodes to create (default 30)") - msgs = flag.Int("msgs", 100, "number of messages sent by node (default 100)") - loglevel = flag.Int("loglevel", 0, "verbosity of logs") - rawlog = flag.Bool("rawlog", false, "remove terminal formatting from logs") -) - -func init() { - flag.Parse() - log.PrintOrigins(true) - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(!*rawlog)))) -} - -//TestAccountingSimulation runs a p2p/simulations simulation -//It creates a *nodes number of nodes, connects each one with each other, -//then sends out a random selection of messages up to *msgs amount of messages -//from the test protocol spec. -//The spec has some accounted messages defined through the Prices interface. -//The test does accounting for all the message exchanged, and then checks -//that every node has the same balance with a peer, but with opposite signs. -//Balance(AwithB) = 0 - Balance(BwithA) or Abs|Balance(AwithB)| == Abs|Balance(BwithA)| -func TestAccountingSimulation(t *testing.T) { - //setup the balances objects for every node - bal := newBalances(*nodes) - //setup the metrics system or tests will fail trying to write metrics - dir, err := ioutil.TempDir("", "account-sim") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - SetupAccountingMetrics(1*time.Second, filepath.Join(dir, "metrics.db")) - //define the node.Service for this test - services := adapters.Services{ - "accounting": func(ctx *adapters.ServiceContext) (node.Service, error) { - return bal.newNode(), nil - }, - } - //setup the simulation - adapter := adapters.NewSimAdapter(services) - net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{DefaultService: "accounting"}) - defer net.Shutdown() - - // we send msgs messages per node, wait for all messages to arrive - bal.wg.Add(*nodes * *msgs) - trigger := make(chan enode.ID) - go func() { - // wait for all of them to arrive - bal.wg.Wait() - // then trigger a check - // the selected node for the trigger is irrelevant, - // we just want to trigger the end of the simulation - trigger <- net.Nodes[0].ID() - }() - - // create nodes and start them - for i := 0; i < *nodes; i++ { - conf := adapters.RandomNodeConfig() - bal.id2n[conf.ID] = i - if _, err := net.NewNodeWithConfig(conf); err != nil { - t.Fatal(err) - } - if err := net.Start(conf.ID); err != nil { - t.Fatal(err) - } - } - // fully connect nodes - for i, n := range net.Nodes { - for _, m := range net.Nodes[i+1:] { - if err := net.Connect(n.ID(), m.ID()); err != nil { - t.Fatal(err) - } - } - } - - // empty action - action := func(ctx context.Context) error { - return nil - } - // check always checks out - check := func(ctx context.Context, id enode.ID) (bool, error) { - return true, nil - } - - // run simulation - timeout := 30 * time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{ - Action: action, - Trigger: trigger, - Expect: &simulations.Expectation{ - Nodes: []enode.ID{net.Nodes[0].ID()}, - Check: check, - }, - }) - - if result.Error != nil { - t.Fatal(result.Error) - } - - // check if balance matrix is symmetric - if err := bal.symmetric(); err != nil { - t.Fatal(err) - } -} - -// matrix is a matrix of nodes and its balances -// matrix is in fact a linear array of size n*n, -// so the balance for any node A with B is at index -// A*n + B, while the balance of node B with A is at -// B*n + A -// (n entries in the array will not be filled - -// the balance of a node with itself) -type matrix struct { - n int //number of nodes - m []int64 //array of balances - lock sync.Mutex -} - -// create a new matrix -func newMatrix(n int) *matrix { - return &matrix{ - n: n, - m: make([]int64, n*n), - } -} - -// called from the testBalance's Add accounting function: register balance change -func (m *matrix) add(i, j int, v int64) error { - // index for the balance of local node i with remote nodde j is - // i * number of nodes + remote node - mi := i*m.n + j - // register that balance - m.lock.Lock() - m.m[mi] += v - m.lock.Unlock() - return nil -} - -// check that the balances are symmetric: -// balance of node i with node j is the same as j with i but with inverted signs -func (m *matrix) symmetric() error { - //iterate all nodes - for i := 0; i < m.n; i++ { - //iterate starting +1 - for j := i + 1; j < m.n; j++ { - log.Debug("bal", "1", i, "2", j, "i,j", m.m[i*m.n+j], "j,i", m.m[j*m.n+i]) - if m.m[i*m.n+j] != -m.m[j*m.n+i] { - return fmt.Errorf("value mismatch. m[%v, %v] = %v; m[%v, %v] = %v", i, j, m.m[i*m.n+j], j, i, m.m[j*m.n+i]) - } - } - } - return nil -} - -// all the balances -type balances struct { - i int - *matrix - id2n map[enode.ID]int - wg *sync.WaitGroup -} - -func newBalances(n int) *balances { - return &balances{ - matrix: newMatrix(n), - id2n: make(map[enode.ID]int), - wg: &sync.WaitGroup{}, - } -} - -// create a new testNode for every node created as part of the service -func (b *balances) newNode() *testNode { - defer func() { b.i++ }() - return &testNode{ - bal: b, - i: b.i, - peers: make([]*testPeer, b.n), //a node will be connected to n-1 peers - } -} - -type testNode struct { - bal *balances - i int - lock sync.Mutex - peers []*testPeer - peerCount int -} - -// do the accounting for the peer's test protocol -// testNode implements protocols.Balance -func (t *testNode) Add(a int64, p *Peer) error { - //get the index for the remote peer - remote := t.bal.id2n[p.ID()] - log.Debug("add", "local", t.i, "remote", remote, "amount", a) - return t.bal.add(t.i, remote, a) -} - -//run the p2p protocol -//for every node, represented by testNode, create a remote testPeer -func (t *testNode) run(p *p2p.Peer, rw p2p.MsgReadWriter) error { - spec := createTestSpec() - //create accounting hook - spec.Hook = NewAccounting(t, &dummyPrices{}) - - //create a peer for this node - tp := &testPeer{NewPeer(p, rw, spec), t.i, t.bal.id2n[p.ID()], t.bal.wg} - t.lock.Lock() - t.peers[t.bal.id2n[p.ID()]] = tp - t.peerCount++ - if t.peerCount == t.bal.n-1 { - //when all peer connections are established, start sending messages from this peer - go t.send() - } - t.lock.Unlock() - return tp.Run(tp.handle) -} - -// p2p message receive handler function -func (tp *testPeer) handle(ctx context.Context, msg interface{}) error { - tp.wg.Done() - log.Debug("receive", "from", tp.remote, "to", tp.local, "type", reflect.TypeOf(msg), "msg", msg) - return nil -} - -type testPeer struct { - *Peer - local, remote int - wg *sync.WaitGroup -} - -func (t *testNode) send() { - log.Debug("start sending") - for i := 0; i < *msgs; i++ { - //determine randomly to which peer to send - whom := rand.Intn(t.bal.n - 1) - if whom >= t.i { - whom++ - } - t.lock.Lock() - p := t.peers[whom] - t.lock.Unlock() - - //determine a random message from the spec's messages to be sent - which := rand.Intn(len(p.spec.Messages)) - msg := p.spec.Messages[which] - switch msg.(type) { - case *perBytesMsgReceiverPays: - msg = &perBytesMsgReceiverPays{Content: content[:rand.Intn(len(content))]} - case *perBytesMsgSenderPays: - msg = &perBytesMsgSenderPays{Content: content[:rand.Intn(len(content))]} - } - log.Debug("send", "from", t.i, "to", whom, "type", reflect.TypeOf(msg), "msg", msg) - p.Send(context.TODO(), msg) - } -} - -// define the protocol -func (t *testNode) Protocols() []p2p.Protocol { - return []p2p.Protocol{{ - Length: 100, - Run: t.run, - }} -} - -func (t *testNode) APIs() []rpc.API { - return nil -} - -func (t *testNode) Start(server *p2p.Server) error { - return nil -} - -func (t *testNode) Stop() error { - return nil -} diff --git a/p2p/protocols/accounting_test.go b/p2p/protocols/accounting_test.go deleted file mode 100644 index c74f0db50a..0000000000 --- a/p2p/protocols/accounting_test.go +++ /dev/null @@ -1,223 +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 . - -package protocols - -import ( - "testing" - - "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -//dummy Balance implementation -type dummyBalance struct { - amount int64 - peer *Peer -} - -//dummy Prices implementation -type dummyPrices struct{} - -//a dummy message which needs size based accounting -//sender pays -type perBytesMsgSenderPays struct { - Content string -} - -//a dummy message which needs size based accounting -//receiver pays -type perBytesMsgReceiverPays struct { - Content string -} - -//a dummy message which is paid for per unit -//sender pays -type perUnitMsgSenderPays struct{} - -//receiver pays -type perUnitMsgReceiverPays struct{} - -//a dummy message which has zero as its price -type zeroPriceMsg struct{} - -//a dummy message which has no accounting -type nilPriceMsg struct{} - -//return the price for the defined messages -func (d *dummyPrices) Price(msg interface{}) *Price { - switch msg.(type) { - //size based message cost, receiver pays - case *perBytesMsgReceiverPays: - return &Price{ - PerByte: true, - Value: uint64(100), - Payer: Receiver, - } - //size based message cost, sender pays - case *perBytesMsgSenderPays: - return &Price{ - PerByte: true, - Value: uint64(100), - Payer: Sender, - } - //unitary cost, receiver pays - case *perUnitMsgReceiverPays: - return &Price{ - PerByte: false, - Value: uint64(99), - Payer: Receiver, - } - //unitary cost, sender pays - case *perUnitMsgSenderPays: - return &Price{ - PerByte: false, - Value: uint64(99), - Payer: Sender, - } - case *zeroPriceMsg: - return &Price{ - PerByte: false, - Value: uint64(0), - Payer: Sender, - } - case *nilPriceMsg: - return nil - } - return nil -} - -//dummy accounting implementation, only stores values for later check -func (d *dummyBalance) Add(amount int64, peer *Peer) error { - d.amount = amount - d.peer = peer - return nil -} - -type testCase struct { - msg interface{} - size uint32 - sendResult int64 - recvResult int64 -} - -//lowest level unit test -func TestBalance(t *testing.T) { - //create instances - balance := &dummyBalance{} - prices := &dummyPrices{} - //create the spec - spec := createTestSpec() - //create the accounting hook for the spec - acc := NewAccounting(balance, prices) - //create a peer - id := adapters.RandomNodeConfig().ID - p := p2p.NewPeer(id, "testPeer", nil) - peer := NewPeer(p, &dummyRW{}, spec) - //price depends on size, receiver pays - msg := &perBytesMsgReceiverPays{Content: "testBalance"} - size, _ := rlp.EncodeToBytes(msg) - - testCases := []testCase{ - { - msg, - uint32(len(size)), - int64(len(size) * 100), - int64(len(size) * -100), - }, - { - &perBytesMsgSenderPays{Content: "testBalance"}, - uint32(len(size)), - int64(len(size) * -100), - int64(len(size) * 100), - }, - { - &perUnitMsgSenderPays{}, - 0, - int64(-99), - int64(99), - }, - { - &perUnitMsgReceiverPays{}, - 0, - int64(99), - int64(-99), - }, - { - &zeroPriceMsg{}, - 0, - int64(0), - int64(0), - }, - { - &nilPriceMsg{}, - 0, - int64(0), - int64(0), - }, - } - checkAccountingTestCases(t, testCases, acc, peer, balance, true) - checkAccountingTestCases(t, testCases, acc, peer, balance, false) -} - -func checkAccountingTestCases(t *testing.T, cases []testCase, acc *Accounting, peer *Peer, balance *dummyBalance, send bool) { - for _, c := range cases { - var err error - var expectedResult int64 - //reset balance before every check - balance.amount = 0 - if send { - err = acc.Send(peer, c.size, c.msg) - expectedResult = c.sendResult - } else { - err = acc.Receive(peer, c.size, c.msg) - expectedResult = c.recvResult - } - - checkResults(t, err, balance, peer, expectedResult) - } -} - -func checkResults(t *testing.T, err error, balance *dummyBalance, peer *Peer, result int64) { - if err != nil { - t.Fatal(err) - } - if balance.peer != peer { - t.Fatalf("expected Add to be called with peer %v, got %v", peer, balance.peer) - } - if balance.amount != result { - t.Fatalf("Expected balance to be %d but is %d", result, balance.amount) - } -} - -//create a test spec -func createTestSpec() *Spec { - spec := &Spec{ - Name: "test", - Version: 42, - MaxMsgSize: 10 * 1024, - Messages: []interface{}{ - &perBytesMsgReceiverPays{}, - &perBytesMsgSenderPays{}, - &perUnitMsgReceiverPays{}, - &perUnitMsgSenderPays{}, - &zeroPriceMsg{}, - &nilPriceMsg{}, - }, - } - return spec -} diff --git a/p2p/protocols/protocol.go b/p2p/protocols/protocol.go deleted file mode 100644 index af5bc1243d..0000000000 --- a/p2p/protocols/protocol.go +++ /dev/null @@ -1,441 +0,0 @@ -// Copyright 2017 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 . - -/* -Package protocols is an extension to p2p. It offers a user friendly simple way to define -devp2p subprotocols by abstracting away code standardly shared by protocols. - -* automate assignments of code indexes to messages -* automate RLP decoding/encoding based on reflecting -* provide the forever loop to read incoming messages -* standardise error handling related to communication -* standardised handshake negotiation -* TODO: automatic generation of wire protocol specification for peers - -*/ -package protocols - -import ( - "bytes" - "context" - "fmt" - "io" - "reflect" - "sync" - "time" - - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/metrics" - "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/rlp" - // "github.com/AlayaNetwork/Alaya-Go/swarm/spancontext" - // "github.com/AlayaNetwork/Alaya-Go/swarm/tracing" -) - -// error codes used by this protocol scheme -const ( - ErrMsgTooLong = iota - ErrDecode - ErrWrite - ErrInvalidMsgCode - ErrInvalidMsgType - ErrHandshake - ErrNoHandler - ErrHandler -) - -// error description strings associated with the codes -var errorToString = map[int]string{ - ErrMsgTooLong: "Message too long", - ErrDecode: "Invalid message (RLP error)", - ErrWrite: "Error sending message", - ErrInvalidMsgCode: "Invalid message code", - ErrInvalidMsgType: "Invalid message type", - ErrHandshake: "Handshake error", - ErrNoHandler: "No handler registered error", - ErrHandler: "Message handler error", -} - -/* -Error implements the standard go error interface. -Use: - - errorf(code, format, params ...interface{}) - -Prints as: - - :
- -where description is given by code in errorToString -and details is fmt.Sprintf(format, params...) - -exported field Code can be checked -*/ -type Error struct { - Code int - message string - format string - params []interface{} -} - -func (e Error) Error() (message string) { - if len(e.message) == 0 { - name, ok := errorToString[e.Code] - if !ok { - panic("invalid message code") - } - e.message = name - if e.format != "" { - e.message += ": " + fmt.Sprintf(e.format, e.params...) - } - } - return e.message -} - -func errorf(code int, format string, params ...interface{}) *Error { - return &Error{ - Code: code, - format: format, - params: params, - } -} - -// WrappedMsg is used to propagate marshalled context alongside message payloads -type WrappedMsg struct { - Context []byte - Size uint32 - Payload []byte -} - -//For accounting, the design is to allow the Spec to describe which and how its messages are priced -//To access this functionality, we provide a Hook interface which will call accounting methods -//NOTE: there could be more such (horizontal) hooks in the future -type Hook interface { - //A hook for sending messages - Send(peer *Peer, size uint32, msg interface{}) error - //A hook for receiving messages - Receive(peer *Peer, size uint32, msg interface{}) error -} - -// Spec is a protocol specification including its name and version as well as -// the types of messages which are exchanged -type Spec struct { - // Name is the name of the protocol, often a three-letter word - Name string - - // Version is the version number of the protocol - Version uint - - // MaxMsgSize is the maximum accepted length of the message payload - MaxMsgSize uint32 - - // Messages is a list of message data types which this protocol uses, with - // each message type being sent with its array index as the code (so - // [&foo{}, &bar{}, &baz{}] would send foo, bar and baz with codes - // 0, 1 and 2 respectively) - // each message must have a single unique data type - Messages []interface{} - - //hook for accounting (could be extended to multiple hooks in the future) - Hook Hook - - initOnce sync.Once - codes map[reflect.Type]uint64 - types map[uint64]reflect.Type -} - -func (s *Spec) init() { - s.initOnce.Do(func() { - s.codes = make(map[reflect.Type]uint64, len(s.Messages)) - s.types = make(map[uint64]reflect.Type, len(s.Messages)) - for i, msg := range s.Messages { - code := uint64(i) - typ := reflect.TypeOf(msg) - if typ.Kind() == reflect.Ptr { - typ = typ.Elem() - } - s.codes[typ] = code - s.types[code] = typ - } - }) -} - -// Length returns the number of message types in the protocol -func (s *Spec) Length() uint64 { - return uint64(len(s.Messages)) -} - -// GetCode returns the message code of a type, and boolean second argument is -// false if the message type is not found -func (s *Spec) GetCode(msg interface{}) (uint64, bool) { - s.init() - typ := reflect.TypeOf(msg) - if typ.Kind() == reflect.Ptr { - typ = typ.Elem() - } - code, ok := s.codes[typ] - return code, ok -} - -// NewMsg construct a new message type given the code -func (s *Spec) NewMsg(code uint64) (interface{}, bool) { - s.init() - typ, ok := s.types[code] - if !ok { - return nil, false - } - return reflect.New(typ).Interface(), true -} - -// Peer represents a remote peer or protocol instance that is running on a peer connection with -// a remote peer -type Peer struct { - *p2p.Peer // the p2p.Peer object representing the remote - rw p2p.MsgReadWriter // p2p.MsgReadWriter to send messages to and read messages from - spec *Spec -} - -// NewPeer constructs a new peer -// this constructor is called by the p2p.Protocol#Run function -// the first two arguments are the arguments passed to p2p.Protocol.Run function -// the third argument is the Spec describing the protocol -func NewPeer(p *p2p.Peer, rw p2p.MsgReadWriter, spec *Spec) *Peer { - return &Peer{ - Peer: p, - rw: rw, - spec: spec, - } -} - -// Run starts the forever loop that handles incoming messages -// called within the p2p.Protocol#Run function -// the handler argument is a function which is called for each message received -// from the remote peer, a returned error causes the loop to exit -// resulting in disconnection -func (p *Peer) Run(handler func(ctx context.Context, msg interface{}) error) error { - for { - if err := p.handleIncoming(handler); err != nil { - if err != io.EOF { - metrics.GetOrRegisterCounter("peer.handleincoming.error", nil).Inc(1) - log.Error("peer.handleIncoming", "err", err) - } - - return err - } - } -} - -// Drop disconnects a peer. -// TODO: may need to implement protocol drop only? don't want to kick off the peer -// if they are useful for other protocols -func (p *Peer) Drop() { - p.Disconnect(p2p.DiscSubprotocolError) -} - -// Send takes a message, encodes it in RLP, finds the right message code and sends the -// message off to the peer -// this low level call will be wrapped by libraries providing routed or broadcast sends -// but often just used to forward and push messages to directly connected peers -func (p *Peer) Send(ctx context.Context, msg interface{}) error { - defer metrics.GetOrRegisterResettingTimer("peer.send_t", nil).UpdateSince(time.Now()) - metrics.GetOrRegisterCounter("peer.send", nil).Inc(1) - metrics.GetOrRegisterCounter(fmt.Sprintf("peer.send.%T", msg), nil).Inc(1) - - var b bytes.Buffer - /*if tracing.Enabled { - writer := bufio.NewWriter(&b) - - tracer := opentracing.GlobalTracer() - - sctx := spancontext.FromContext(ctx) - - if sctx != nil { - err := tracer.Inject( - sctx, - opentracing.Binary, - writer) - if err != nil { - return err - } - } - - writer.Flush() - }*/ - - r, err := rlp.EncodeToBytes(msg) - if err != nil { - return err - } - - wmsg := WrappedMsg{ - Context: b.Bytes(), - Size: uint32(len(r)), - Payload: r, - } - - //if the accounting hook is set, call it - if p.spec.Hook != nil { - err := p.spec.Hook.Send(p, wmsg.Size, msg) - if err != nil { - p.Drop() - return err - } - } - - code, found := p.spec.GetCode(msg) - if !found { - return errorf(ErrInvalidMsgType, "%v", code) - } - return p2p.Send(p.rw, code, wmsg) -} - -// handleIncoming(code) -// is called each cycle of the main forever loop that dispatches incoming messages -// if this returns an error the loop returns and the peer is disconnected with the error -// this generic handler -// * checks message size, -// * checks for out-of-range message codes, -// * handles decoding with reflection, -// * call handlers as callbacks -func (p *Peer) handleIncoming(handle func(ctx context.Context, msg interface{}) error) error { - msg, err := p.rw.ReadMsg() - if err != nil { - return err - } - // make sure that the payload has been fully consumed - defer msg.Discard() - - if msg.Size > p.spec.MaxMsgSize { - return errorf(ErrMsgTooLong, "%v > %v", msg.Size, p.spec.MaxMsgSize) - } - - // unmarshal wrapped msg, which might contain context - var wmsg WrappedMsg - err = msg.Decode(&wmsg) - if err != nil { - log.Error(err.Error()) - return err - } - - ctx := context.Background() - - // if tracing is enabled and the context coming within the request is - // not empty, try to unmarshal it - /*if tracing.Enabled && len(wmsg.Context) > 0 { - var sctx opentracing.SpanContext - - tracer := opentracing.GlobalTracer() - sctx, err = tracer.Extract( - opentracing.Binary, - bytes.NewReader(wmsg.Context)) - if err != nil { - log.Error(err.Error()) - return err - } - - ctx = spancontext.WithContext(ctx, sctx) - }*/ - - val, ok := p.spec.NewMsg(msg.Code) - if !ok { - return errorf(ErrInvalidMsgCode, "%v", msg.Code) - } - if err := rlp.DecodeBytes(wmsg.Payload, val); err != nil { - return errorf(ErrDecode, "<= %v: %v", msg, err) - } - - //if the accounting hook is set, call it - if p.spec.Hook != nil { - err := p.spec.Hook.Receive(p, wmsg.Size, val) - if err != nil { - return err - } - } - - // call the registered handler callbacks - // a registered callback take the decoded message as argument as an interface - // which the handler is supposed to cast to the appropriate type - // it is entirely safe not to check the cast in the handler since the handler is - // chosen based on the proper type in the first place - if err := handle(ctx, val); err != nil { - return errorf(ErrHandler, "(msg code %v): %v", msg.Code, err) - } - return nil -} - -// Handshake negotiates a handshake on the peer connection -// * arguments -// * context -// * the local handshake to be sent to the remote peer -// * function to be called on the remote handshake (can be nil) -// * expects a remote handshake back of the same type -// * the dialing peer needs to send the handshake first and then waits for remote -// * the listening peer waits for the remote handshake and then sends it -// returns the remote handshake and an error -func (p *Peer) Handshake(ctx context.Context, hs interface{}, verify func(interface{}) error) (interface{}, error) { - if _, ok := p.spec.GetCode(hs); !ok { - return nil, errorf(ErrHandshake, "unknown handshake message type: %T", hs) - } - - var rhs interface{} - errc := make(chan error, 2) - handle := func(ctx context.Context, msg interface{}) error { - rhs = msg - if verify != nil { - return verify(rhs) - } - return nil - } - send := func() { errc <- p.Send(ctx, hs) } - receive := func() { errc <- p.handleIncoming(handle) } - - go func() { - if p.Inbound() { - receive() - send() - } else { - send() - receive() - } - }() - - for i := 0; i < 2; i++ { - var err error - select { - case err = <-errc: - case <-ctx.Done(): - err = ctx.Err() - } - if err != nil { - return nil, errorf(ErrHandshake, err.Error()) - } - } - return rhs, nil -} - -// HasCap returns true if Peer has a capability -// with provided name. -func (p *Peer) HasCap(capName string) (yes bool) { - if p == nil || p.Peer == nil { - return false - } - for _, c := range p.Caps() { - if c.Name == capName { - return true - } - } - return false -} diff --git a/p2p/protocols/protocol_test.go b/p2p/protocols/protocol_test.go deleted file mode 100644 index 3b55f9f34f..0000000000 --- a/p2p/protocols/protocol_test.go +++ /dev/null @@ -1,625 +0,0 @@ -// Copyright 2017 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 . - -package protocols - -import ( - "bytes" - "context" - "errors" - "fmt" - "sync" - "testing" - "time" - - "github.com/AlayaNetwork/Alaya-Go/crypto" - - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - "github.com/AlayaNetwork/Alaya-Go/rlp" - - "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/adapters" - p2ptest "github.com/AlayaNetwork/Alaya-Go/p2p/testing" -) - -// handshake message type -type hs0 struct { - C uint -} - -// message to kill/drop the peer with nodeID -type kill struct { - C enode.ID -} - -// message to drop connection -type drop struct { -} - -/// protoHandshake represents module-independent aspects of the protocol and is -// the first message peers send and receive as part the initial exchange -type protoHandshake struct { - Version uint // local and remote peer should have identical version - NetworkID string // local and remote peer should have identical network id -} - -// checkProtoHandshake verifies local and remote protoHandshakes match -func checkProtoHandshake(testVersion uint, testNetworkID string) func(interface{}) error { - return func(rhs interface{}) error { - remote := rhs.(*protoHandshake) - if remote.NetworkID != testNetworkID { - return fmt.Errorf("%s (!= %s)", remote.NetworkID, testNetworkID) - } - - if remote.Version != testVersion { - return fmt.Errorf("%d (!= %d)", remote.Version, testVersion) - } - return nil - } -} - -// newProtocol sets up a protocol -// the run function here demonstrates a typical protocol using peerPool, handshake -// and messages registered to handlers -func newProtocol(pp *p2ptest.TestPeerPool) func(*p2p.Peer, p2p.MsgReadWriter) error { - spec := &Spec{ - Name: "test", - Version: 42, - MaxMsgSize: 10 * 1024, - Messages: []interface{}{ - protoHandshake{}, - hs0{}, - kill{}, - drop{}, - }, - } - return func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - peer := NewPeer(p, rw, spec) - - // initiate one-off protohandshake and check validity - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - phs := &protoHandshake{42, "420"} - hsCheck := checkProtoHandshake(phs.Version, phs.NetworkID) - _, err := peer.Handshake(ctx, phs, hsCheck) - if err != nil { - return err - } - - lhs := &hs0{42} - // module handshake demonstrating a simple repeatable exchange of same-type message - hs, err := peer.Handshake(ctx, lhs, nil) - if err != nil { - return err - } - - if rmhs := hs.(*hs0); rmhs.C > lhs.C { - return fmt.Errorf("handshake mismatch remote %v > local %v", rmhs.C, lhs.C) - } - - handle := func(ctx context.Context, msg interface{}) error { - switch msg := msg.(type) { - - case *protoHandshake: - return errors.New("duplicate handshake") - - case *hs0: - rhs := msg - if rhs.C > lhs.C { - return fmt.Errorf("handshake mismatch remote %v > local %v", rhs.C, lhs.C) - } - lhs.C += rhs.C - return peer.Send(ctx, lhs) - - case *kill: - // demonstrates use of peerPool, killing another peer connection as a response to a message - id := msg.C - pp.Get(id).Drop() - return nil - - case *drop: - // for testing we can trigger self induced disconnect upon receiving drop message - return errors.New("dropped") - - default: - return fmt.Errorf("unknown message type: %T", msg) - } - } - - pp.Add(peer) - defer pp.Remove(peer) - return peer.Run(handle) - } -} - -func protocolTester(pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTester { - prvkey, err := crypto.GenerateKey() - if err != nil { - panic(err) - } - return p2ptest.NewProtocolTester(prvkey, 2, newProtocol(pp)) -} - -func protoHandshakeExchange(id enode.ID, proto *protoHandshake) []p2ptest.Exchange { - - return []p2ptest.Exchange{ - { - Expects: []p2ptest.Expect{ - { - Code: 0, - Msg: &protoHandshake{42, "420"}, - Peer: id, - }, - }, - }, - { - Triggers: []p2ptest.Trigger{ - { - Code: 0, - Msg: proto, - Peer: id, - }, - }, - }, - } -} - -func runProtoHandshake(t *testing.T, proto *protoHandshake, errs ...error) { - t.Helper() - pp := p2ptest.NewTestPeerPool() - s := protocolTester(pp) - defer s.Stop() - - // TODO: make this more than one handshake - node := s.Nodes[0] - if err := s.TestExchanges(protoHandshakeExchange(node.ID(), proto)...); err != nil { - t.Fatal(err) - } - var disconnects []*p2ptest.Disconnect - for i, err := range errs { - disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err}) - } - if err := s.TestDisconnected(disconnects...); err != nil { - t.Fatal(err) - } -} - -type dummyHook struct { - peer *Peer - size uint32 - msg interface{} - send bool - err error - waitC chan struct{} - mu sync.Mutex -} - -type dummyMsg struct { - Content string -} - -func (d *dummyHook) Send(peer *Peer, size uint32, msg interface{}) error { - d.mu.Lock() - defer d.mu.Unlock() - - d.peer = peer - d.size = size - d.msg = msg - d.send = true - return d.err -} - -func (d *dummyHook) Receive(peer *Peer, size uint32, msg interface{}) error { - d.mu.Lock() - defer d.mu.Unlock() - - d.peer = peer - d.size = size - d.msg = msg - d.send = false - d.waitC <- struct{}{} - return d.err -} - -func TestProtocolHook(t *testing.T) { - testHook := &dummyHook{ - waitC: make(chan struct{}, 1), - } - spec := &Spec{ - Name: "test", - Version: 42, - MaxMsgSize: 10 * 1024, - Messages: []interface{}{ - dummyMsg{}, - }, - Hook: testHook, - } - - runFunc := func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - peer := NewPeer(p, rw, spec) - ctx := context.TODO() - err := peer.Send(ctx, &dummyMsg{ - Content: "handshake"}) - - if err != nil { - t.Fatal(err) - } - - handle := func(ctx context.Context, msg interface{}) error { - return nil - } - - return peer.Run(handle) - } - - prvkey, err := crypto.GenerateKey() - if err != nil { - panic(err) - } - tester := p2ptest.NewProtocolTester(prvkey, 2, runFunc) - defer tester.Stop() - err = tester.TestExchanges(p2ptest.Exchange{ - Expects: []p2ptest.Expect{ - { - Code: 0, - Msg: &dummyMsg{Content: "handshake"}, - Peer: tester.Nodes[0].ID(), - }, - }, - }) - if err != nil { - t.Fatal(err) - } - testHook.mu.Lock() - if testHook.msg == nil || testHook.msg.(*dummyMsg).Content != "handshake" { - t.Fatal("Expected msg to be set, but it is not") - } - if !testHook.send { - t.Fatal("Expected a send message, but it is not") - } - if testHook.peer == nil { - t.Fatal("Expected peer to be set, is nil") - } - if peerId := testHook.peer.ID(); peerId != tester.Nodes[0].ID() && peerId != tester.Nodes[1].ID() { - t.Fatalf("Expected peer ID to be set correctly, but it is not (got %v, exp %v or %v", peerId, tester.Nodes[0].ID(), tester.Nodes[1].ID()) - } - if testHook.size != 11 { //11 is the length of the encoded message - t.Fatalf("Expected size to be %d, but it is %d ", 1, testHook.size) - } - testHook.mu.Unlock() - - err = tester.TestExchanges(p2ptest.Exchange{ - Triggers: []p2ptest.Trigger{ - { - Code: 0, - Msg: &dummyMsg{Content: "response"}, - Peer: tester.Nodes[1].ID(), - }, - }, - }) - - <-testHook.waitC - - if err != nil { - t.Fatal(err) - } - - testHook.mu.Lock() - if testHook.msg == nil || testHook.msg.(*dummyMsg).Content != "response" { - t.Fatal("Expected msg to be set, but it is not") - } - if testHook.send { - t.Fatal("Expected a send message, but it is not") - } - if testHook.peer == nil || testHook.peer.ID() != tester.Nodes[1].ID() { - t.Fatal("Expected peer ID to be set correctly, but it is not") - } - if testHook.size != 10 { //11 is the length of the encoded message - t.Fatalf("Expected size to be %d, but it is %d ", 1, testHook.size) - } - testHook.mu.Unlock() - - testHook.err = fmt.Errorf("dummy error") - err = tester.TestExchanges(p2ptest.Exchange{ - Triggers: []p2ptest.Trigger{ - { - Code: 0, - Msg: &dummyMsg{Content: "response"}, - Peer: tester.Nodes[1].ID(), - }, - }, - }) - - <-testHook.waitC - - time.Sleep(100 * time.Millisecond) - err = tester.TestDisconnected(&p2ptest.Disconnect{Peer: tester.Nodes[1].ID(), Error: testHook.err}) - if err != nil { - t.Fatalf("Expected a specific disconnect error, but got different one: %v", err) - } -} - -//We need to test that if the hook is not defined, then message infrastructure -//(send,receive) still works -func TestNoHook(t *testing.T) { - //create a test spec - spec := createTestSpec() - //a random node - id := adapters.RandomNodeConfig().ID - //a peer - p := p2p.NewPeer(id, "testPeer", nil) - rw := &dummyRW{} - peer := NewPeer(p, rw, spec) - ctx := context.TODO() - msg := &perBytesMsgSenderPays{Content: "testBalance"} - //send a message - - if err := peer.Send(ctx, msg); err != nil { - t.Fatal(err) - } - //simulate receiving a message - rw.msg = msg - handler := func(ctx context.Context, msg interface{}) error { - return nil - } - - if err := peer.handleIncoming(handler); err != nil { - t.Fatal(err) - } -} - -func TestProtoHandshakeVersionMismatch(t *testing.T) { - runProtoHandshake(t, &protoHandshake{41, "420"}, errorf(ErrHandshake, errorf(ErrHandler, "(msg code 0): 41 (!= 42)").Error())) -} - -func TestProtoHandshakeNetworkIDMismatch(t *testing.T) { - runProtoHandshake(t, &protoHandshake{42, "421"}, errorf(ErrHandshake, errorf(ErrHandler, "(msg code 0): 421 (!= 420)").Error())) -} - -func TestProtoHandshakeSuccess(t *testing.T) { - runProtoHandshake(t, &protoHandshake{42, "420"}) -} - -func moduleHandshakeExchange(id enode.ID, resp uint) []p2ptest.Exchange { - - return []p2ptest.Exchange{ - { - Expects: []p2ptest.Expect{ - { - Code: 1, - Msg: &hs0{42}, - Peer: id, - }, - }, - }, - { - Triggers: []p2ptest.Trigger{ - { - Code: 1, - Msg: &hs0{resp}, - Peer: id, - }, - }, - }, - } -} - -func runModuleHandshake(t *testing.T, resp uint, errs ...error) { - t.Helper() - pp := p2ptest.NewTestPeerPool() - s := protocolTester(pp) - defer s.Stop() - - node := s.Nodes[0] - if err := s.TestExchanges(protoHandshakeExchange(node.ID(), &protoHandshake{42, "420"})...); err != nil { - t.Fatal(err) - } - if err := s.TestExchanges(moduleHandshakeExchange(node.ID(), resp)...); err != nil { - t.Fatal(err) - } - var disconnects []*p2ptest.Disconnect - for i, err := range errs { - disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err}) - } - if err := s.TestDisconnected(disconnects...); err != nil { - t.Fatal(err) - } -} - -func TestModuleHandshakeError(t *testing.T) { - runModuleHandshake(t, 43, fmt.Errorf("handshake mismatch remote 43 > local 42")) -} - -func TestModuleHandshakeSuccess(t *testing.T) { - runModuleHandshake(t, 42) -} - -// testing complex interactions over multiple peers, relaying, dropping -func testMultiPeerSetup(a, b enode.ID) []p2ptest.Exchange { - - return []p2ptest.Exchange{ - { - Label: "primary handshake", - Expects: []p2ptest.Expect{ - { - Code: 0, - Msg: &protoHandshake{42, "420"}, - Peer: a, - }, - { - Code: 0, - Msg: &protoHandshake{42, "420"}, - Peer: b, - }, - }, - }, - { - Label: "module handshake", - Triggers: []p2ptest.Trigger{ - { - Code: 0, - Msg: &protoHandshake{42, "420"}, - Peer: a, - }, - { - Code: 0, - Msg: &protoHandshake{42, "420"}, - Peer: b, - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 1, - Msg: &hs0{42}, - Peer: a, - }, - { - Code: 1, - Msg: &hs0{42}, - Peer: b, - }, - }, - }, - - {Label: "alternative module handshake", Triggers: []p2ptest.Trigger{{Code: 1, Msg: &hs0{41}, Peer: a}, - {Code: 1, Msg: &hs0{41}, Peer: b}}}, - {Label: "repeated module handshake", Triggers: []p2ptest.Trigger{{Code: 1, Msg: &hs0{1}, Peer: a}}}, - {Label: "receiving repeated module handshake", Expects: []p2ptest.Expect{{Code: 1, Msg: &hs0{43}, Peer: a}}}} -} - -func runMultiplePeers(t *testing.T, peer int, errs ...error) { - t.Helper() - pp := p2ptest.NewTestPeerPool() - s := protocolTester(pp) - defer s.Stop() - - if err := s.TestExchanges(testMultiPeerSetup(s.Nodes[0].ID(), s.Nodes[1].ID())...); err != nil { - t.Fatal(err) - } - // after some exchanges of messages, we can test state changes - // here this is simply demonstrated by the peerPool - // after the handshake negotiations peers must be added to the pool - // time.Sleep(1) - tick := time.NewTicker(10 * time.Millisecond) - timeout := time.NewTimer(1 * time.Second) -WAIT: - for { - select { - case <-tick.C: - if pp.Has(s.Nodes[0].ID()) { - break WAIT - } - case <-timeout.C: - t.Fatal("timeout") - } - } - if !pp.Has(s.Nodes[1].ID()) { - t.Fatalf("missing peer test-1: %v (%v)", pp, s.Nodes) - } - - // peer 0 sends kill request for peer with index - err := s.TestExchanges(p2ptest.Exchange{ - Triggers: []p2ptest.Trigger{ - { - Code: 2, - Msg: &kill{s.Nodes[peer].ID()}, - Peer: s.Nodes[0].ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } - - // the peer not killed sends a drop request - err = s.TestExchanges(p2ptest.Exchange{ - Triggers: []p2ptest.Trigger{ - { - Code: 3, - Msg: &drop{}, - Peer: s.Nodes[(peer+1)%2].ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } - - // check the actual discconnect errors on the individual peers - var disconnects []*p2ptest.Disconnect - for i, err := range errs { - disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err}) - } - if err := s.TestDisconnected(disconnects...); err != nil { - t.Fatal(err) - } - // test if disconnected peers have been removed from peerPool - if pp.Has(s.Nodes[peer].ID()) { - t.Fatalf("peer test-%v not dropped: %v (%v)", peer, pp, s.Nodes) - } - -} -func TestMultiplePeersDropSelf(t *testing.T) { - runMultiplePeers(t, 0, - fmt.Errorf("subprotocol error"), - fmt.Errorf("Message handler error: (msg code 3): dropped"), - ) -} - -func TestMultiplePeersDropOther(t *testing.T) { - runMultiplePeers(t, 1, - fmt.Errorf("Message handler error: (msg code 3): dropped"), - fmt.Errorf("subprotocol error"), - ) -} - -//dummy implementation of a MsgReadWriter -//this allows for quick and easy unit tests without -//having to build up the complete protocol -type dummyRW struct { - msg interface{} - size uint32 - code uint64 -} - -func (d *dummyRW) WriteMsg(msg p2p.Msg) error { - return nil -} - -func (d *dummyRW) ReadMsg() (p2p.Msg, error) { - enc := bytes.NewReader(d.getDummyMsg()) - return p2p.Msg{ - Code: d.code, - Size: d.size, - Payload: enc, - ReceivedAt: time.Now(), - }, nil -} - -func (d *dummyRW) getDummyMsg() []byte { - r, _ := rlp.EncodeToBytes(d.msg) - var b bytes.Buffer - wmsg := WrappedMsg{ - Context: b.Bytes(), - Size: uint32(len(r)), - Payload: r, - } - rr, _ := rlp.EncodeToBytes(wmsg) - d.size = uint32(len(rr)) - return rr -} diff --git a/p2p/protocols/reporter.go b/p2p/protocols/reporter.go deleted file mode 100644 index 9f3cf5a5b8..0000000000 --- a/p2p/protocols/reporter.go +++ /dev/null @@ -1,162 +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 . - -package protocols - -import ( - "encoding/binary" - "time" - - "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/metrics" - - "github.com/syndtr/goleveldb/leveldb" -) - -//AccountMetrics abstracts away the metrics DB and -//the reporter to persist metrics -type AccountingMetrics struct { - reporter *reporter -} - -//Close will be called when the node is being shutdown -//for a graceful cleanup -func (am *AccountingMetrics) Close() { - close(am.reporter.quit) - // wait for reporter loop to finish saving metrics - // before reporter database is closed - select { - case <-time.After(10 * time.Second): - log.Error("accounting metrics reporter timeout") - case <-am.reporter.done: - } - am.reporter.db.Close() -} - -//reporter is an internal structure used to write p2p accounting related -//metrics to a LevelDB. It will periodically write the accrued metrics to the DB. -type reporter struct { - reg metrics.Registry //the registry for these metrics (independent of other metrics) - interval time.Duration //duration at which the reporter will persist metrics - db *leveldb.DB //the actual DB - quit chan struct{} //quit the reporter loop - done chan struct{} //signal that reporter loop is done -} - -//NewMetricsDB creates a new LevelDB instance used to persist metrics defined -//inside p2p/protocols/accounting.go -func NewAccountingMetrics(r metrics.Registry, d time.Duration, path string) *AccountingMetrics { - var val = make([]byte, 8) - var err error - - //Create the LevelDB - db, err := leveldb.OpenFile(path, nil) - if err != nil { - log.Error(err.Error()) - return nil - } - - //Check for all defined metrics that there is a value in the DB - //If there is, assign it to the metric. This means that the node - //has been running before and that metrics have been persisted. - metricsMap := map[string]metrics.Counter{ - "account.balance.credit": mBalanceCredit, - "account.balance.debit": mBalanceDebit, - "account.bytes.credit": mBytesCredit, - "account.bytes.debit": mBytesDebit, - "account.msg.credit": mMsgCredit, - "account.msg.debit": mMsgDebit, - "account.peerdrops": mPeerDrops, - "account.selfdrops": mSelfDrops, - } - //iterate the map and get the values - for key, metric := range metricsMap { - val, err = db.Get([]byte(key), nil) - //until the first time a value is being written, - //this will return an error. - //it could be beneficial though to log errors later, - //but that would require a different logic - if err == nil { - metric.Inc(int64(binary.BigEndian.Uint64(val))) - } - } - - //create the reporter - rep := &reporter{ - reg: r, - interval: d, - db: db, - quit: make(chan struct{}), - done: make(chan struct{}), - } - - //run the go routine - go rep.run() - - m := &AccountingMetrics{ - reporter: rep, - } - - return m -} - -//run is the goroutine which periodically sends the metrics to the configured LevelDB -func (r *reporter) run() { - // signal that the reporter loop is done - defer close(r.done) - - intervalTicker := time.NewTicker(r.interval) - - for { - select { - case <-intervalTicker.C: - //at each tick send the metrics - if err := r.save(); err != nil { - log.Error("unable to send metrics to LevelDB", "err", err) - //If there is an error in writing, exit the routine; we assume here that the error is - //severe and don't attempt to write again. - //Also, this should prevent leaking when the node is stopped - return - } - case <-r.quit: - //graceful shutdown - if err := r.save(); err != nil { - log.Error("unable to send metrics to LevelDB", "err", err) - } - return - } - } -} - -//send the metrics to the DB -func (r *reporter) save() error { - //create a LevelDB Batch - batch := leveldb.Batch{} - //for each metric in the registry (which is independent)... - r.reg.Each(func(name string, i interface{}) { - metric, ok := i.(metrics.Counter) - if ok { - //assuming every metric here to be a Counter (separate registry) - //...create a snapshot... - ms := metric.Snapshot() - byteVal := make([]byte, 8) - binary.BigEndian.PutUint64(byteVal, uint64(ms.Count())) - //...and save the value to the DB - batch.Put([]byte(name), byteVal) - } - }) - return r.db.Write(&batch, nil) -} diff --git a/p2p/protocols/repoter_test.go b/p2p/protocols/repoter_test.go deleted file mode 100644 index 85a8a11a79..0000000000 --- a/p2p/protocols/repoter_test.go +++ /dev/null @@ -1,83 +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 . - -package protocols - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - "time" - - "github.com/AlayaNetwork/Alaya-Go/log" -) - -//TestReporter tests that the metrics being collected for p2p accounting -//are being persisted and available after restart of a node. -//It simulates restarting by just recreating the DB as if the node had restarted. -func TestReporter(t *testing.T) { - //create a test directory - dir, err := ioutil.TempDir("", "reporter-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - //setup the metrics - log.Debug("Setting up metrics first time") - reportInterval := 5 * time.Millisecond - metrics := SetupAccountingMetrics(reportInterval, filepath.Join(dir, "test.db")) - log.Debug("Done.") - - //change metrics - mBalanceCredit.Inc(12) - mBytesCredit.Inc(34) - mMsgDebit.Inc(9) - - //store expected metrics - expectedBalanceCredit := mBalanceCredit.Count() - expectedBytesCredit := mBytesCredit.Count() - expectedMsgDebit := mMsgDebit.Count() - - //give the reporter time to write the metrics to DB - time.Sleep(20 * time.Millisecond) - - //close the DB also, or we can't create a new one - metrics.Close() - - //clear the metrics - this effectively simulates the node having shut down... - mBalanceCredit.Clear() - mBytesCredit.Clear() - mMsgDebit.Clear() - - //setup the metrics again - log.Debug("Setting up metrics second time") - metrics = SetupAccountingMetrics(reportInterval, filepath.Join(dir, "test.db")) - defer metrics.Close() - log.Debug("Done.") - - //now check the metrics, they should have the same value as before "shutdown" - if mBalanceCredit.Count() != expectedBalanceCredit { - t.Fatalf("Expected counter to be %d, but is %d", expectedBalanceCredit, mBalanceCredit.Count()) - } - if mBytesCredit.Count() != expectedBytesCredit { - t.Fatalf("Expected counter to be %d, but is %d", expectedBytesCredit, mBytesCredit.Count()) - } - if mMsgDebit.Count() != expectedMsgDebit { - t.Fatalf("Expected counter to be %d, but is %d", expectedMsgDebit, mMsgDebit.Count()) - } -} diff --git a/p2p/rlpx.go b/p2p/rlpx.go deleted file mode 100644 index 8b8f82e805..0000000000 --- a/p2p/rlpx.go +++ /dev/null @@ -1,753 +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 . - -package p2p - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/hmac" - "crypto/rand" - "encoding/binary" - "errors" - "fmt" - "hash" - "io" - "io/ioutil" - mrand "math/rand" - "net" - "sync" - "time" - - "golang.org/x/crypto/sha3" - - "github.com/AlayaNetwork/Alaya-Go/common/bitutil" - - "github.com/AlayaNetwork/Alaya-Go/log" - - "github.com/golang/snappy" - - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/crypto/ecies" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -const ( - maxUint24 = ^uint32(0) >> 8 - - sskLen = 16 // ecies.MaxSharedKeyLength(pubKey) / 2 - sigLen = 65 // elliptic S256 - pubLen = 64 // 512 bit pubkey in uncompressed representation without format byte - shaLen = 32 // hash length (for nonce etc) - - authMsgLen = sigLen + shaLen + pubLen + shaLen + 1 - authRespLen = pubLen + shaLen + 1 - - eciesOverhead = 65 /* pubkey */ + 16 /* IV */ + 32 /* MAC */ - - encAuthMsgLen = authMsgLen + eciesOverhead // size of encrypted pre-EIP-8 initiator handshake - encAuthRespLen = authRespLen + eciesOverhead // size of encrypted pre-EIP-8 handshake reply - - // total timeout for encryption handshake and protocol - // handshake in both directions. - handshakeTimeout = 5 * time.Second - - // This is the timeout for sending the disconnect reason. - // This is shorter than the usual timeout because we don't want - // to wait if the connection is known to be bad anyway. - discWriteTimeout = 1 * time.Second -) - -// errPlainMessageTooLarge is returned if a decompressed message length exceeds -// the allowed 24 bits (i.e. length >= 16MB). -var errPlainMessageTooLarge = errors.New("message length >= 16MB") - -// rlpx is the transport protocol used by actual (non-test) connections. -// It wraps the frame encoder with locks and read/write deadlines. -type rlpx struct { - fd net.Conn - - rmu, wmu sync.Mutex - rw *rlpxFrameRW -} - -func newRLPX(fd net.Conn) transport { - fd.SetDeadline(time.Now().Add(handshakeTimeout)) - return &rlpx{fd: fd} -} - -func (t *rlpx) ReadMsg() (Msg, error) { - t.rmu.Lock() - defer t.rmu.Unlock() - t.fd.SetReadDeadline(time.Now().Add(frameReadTimeout)) - return t.rw.ReadMsg() -} - -func (t *rlpx) WriteMsg(msg Msg) error { - t.wmu.Lock() - defer t.wmu.Unlock() - t.fd.SetWriteDeadline(time.Now().Add(frameWriteTimeout)) - return t.rw.WriteMsg(msg) -} - -func (t *rlpx) close(err error) { - t.wmu.Lock() - defer t.wmu.Unlock() - // Tell the remote end why we're disconnecting if possible. - if t.rw != nil { - if r, ok := err.(DiscReason); ok && r != DiscNetworkError { - // rlpx tries to send DiscReason to disconnected peer - // if the connection is net.Pipe (in-memory simulation) - // it hangs forever, since net.Pipe does not implement - // a write deadline. Because of this only try to send - // the disconnect reason message if there is no error. - if err := t.fd.SetWriteDeadline(time.Now().Add(discWriteTimeout)); err == nil { - SendItems(t.rw, discMsg, r) - } - } - } - t.fd.Close() -} - -func (t *rlpx) doProtoHandshake(our *protoHandshake) (their *protoHandshake, err error) { - // Writing our handshake happens concurrently, we prefer - // returning the handshake read error. If the remote side - // disconnects us early with a valid reason, we should return it - // as the error so it can be tracked elsewhere. - werr := make(chan error, 1) - go func() { werr <- Send(t.rw, handshakeMsg, our) }() - if their, err = readProtocolHandshake(t.rw); err != nil { - <-werr // make sure the write terminates too - return nil, err - } - if err := <-werr; err != nil { - return nil, fmt.Errorf("write error: %v", err) - } - // If the protocol version supports Snappy encoding, upgrade immediately - t.rw.snappy = their.Version >= snappyProtocolVersion - - return their, nil -} - -func readProtocolHandshake(rw MsgReader) (*protoHandshake, error) { - msg, err := rw.ReadMsg() - if err != nil { - return nil, err - } - if msg.Size > baseProtocolMaxMsgSize { - return nil, fmt.Errorf("message too big") - } - if msg.Code == discMsg { - // Disconnect before protocol handshake is valid according to the - // spec and we send it ourself if the post-handshake checks fail. - // We can't return the reason directly, though, because it is echoed - // back otherwise. Wrap it in a string instead. - var reason [1]DiscReason - rlp.Decode(msg.Payload, &reason) - return nil, reason[0] - } - if msg.Code != handshakeMsg { - return nil, fmt.Errorf("expected handshake, got %x", msg.Code) - } - var hs protoHandshake - if err := msg.Decode(&hs); err != nil { - return nil, err - } - if len(hs.ID) != 64 || !bitutil.TestBytes(hs.ID) { - return nil, DiscInvalidIdentity - } - return &hs, nil -} - -// doEncHandshake runs the protocol handshake using authenticated -// messages. the protocol handshake is the first authenticated message -// and also verifies whether the encryption handshake 'worked' and the -// remote side actually provided the right public key. -func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *ecdsa.PublicKey) (*ecdsa.PublicKey, error) { - var ( - sec secrets - err error - ) - if dial == nil { - sec, err = receiverEncHandshake(t.fd, prv) - } else { - sec, err = initiatorEncHandshake(t.fd, prv, dial) - } - if err != nil { - return nil, err - } - t.wmu.Lock() - t.rw = newRLPXFrameRW(t.fd, sec) - t.wmu.Unlock() - return sec.Remote.ExportECDSA(), nil -} - -// encHandshake contains the state of the encryption handshake. -type encHandshake struct { - initiator bool - remote *ecies.PublicKey // remote-pubk - initNonce, respNonce []byte // nonce - randomPrivKey *ecies.PrivateKey // ecdhe-random - remoteRandomPub *ecies.PublicKey // ecdhe-random-pubk -} - -// secrets represents the connection secrets -// which are negotiated during the encryption handshake. -type secrets struct { - Remote *ecies.PublicKey - AES, MAC []byte - EgressMAC, IngressMAC hash.Hash - Token []byte -} - -// RLPx v4 handshake auth (defined in EIP-8). -type authMsgV4 struct { - gotPlain bool // whether read packet had plain format. - - Signature [sigLen]byte - InitiatorPubkey [pubLen]byte - Nonce [shaLen]byte - Version uint - - // Ignore additional fields (forward-compatibility) - Rest []rlp.RawValue `rlp:"tail"` -} - -// RLPx v4 handshake response (defined in EIP-8). -type authRespV4 struct { - RandomPubkey [pubLen]byte - Nonce [shaLen]byte - Version uint - - // Ignore additional fields (forward-compatibility) - Rest []rlp.RawValue `rlp:"tail"` -} - -// secrets is called after the handshake is completed. -// It extracts the connection secrets from the handshake values. -func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) { - ecdheSecret, err := h.randomPrivKey.GenerateShared(h.remoteRandomPub, sskLen, sskLen) - if err != nil { - return secrets{}, err - } - - // derive base secrets from ephemeral key agreement - sharedSecret := crypto.Keccak256(ecdheSecret, crypto.Keccak256(h.respNonce, h.initNonce)) - aesSecret := crypto.Keccak256(ecdheSecret, sharedSecret) - s := secrets{ - Remote: h.remote, - AES: aesSecret, - MAC: crypto.Keccak256(ecdheSecret, aesSecret), - } - - // setup sha3 instances for the MACs - mac1 := sha3.NewLegacyKeccak256() - mac1.Write(xor(s.MAC, h.respNonce)) - mac1.Write(auth) - mac2 := sha3.NewLegacyKeccak256() - mac2.Write(xor(s.MAC, h.initNonce)) - mac2.Write(authResp) - if h.initiator { - s.EgressMAC, s.IngressMAC = mac1, mac2 - } else { - s.EgressMAC, s.IngressMAC = mac2, mac1 - } - - return s, nil -} - -// staticSharedSecret returns the static shared secret, the result -// of key agreement between the local and remote static node key. -func (h *encHandshake) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error) { - return ecies.ImportECDSA(prv).GenerateShared(h.remote, sskLen, sskLen) -} - -// initiatorEncHandshake negotiates a session token on conn. -// it should be called on the dialing side of the connection. -// -// prv is the local client's private key. -func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s secrets, err error) { - h := &encHandshake{initiator: true, remote: ecies.ImportECDSAPublic(remote)} - authMsg, err := h.makeAuthMsg(prv) - if err != nil { - return s, err - } - authPacket, err := sealEIP8(authMsg, h) - if err != nil { - return s, err - } - if _, err = conn.Write(authPacket); err != nil { - return s, err - } - - authRespMsg := new(authRespV4) - authRespPacket, err := readHandshakeMsg(authRespMsg, encAuthRespLen, prv, conn) - if err != nil { - return s, err - } - if err := h.handleAuthResp(authRespMsg); err != nil { - return s, err - } - return h.secrets(authPacket, authRespPacket) -} - -// makeAuthMsg creates the initiator handshake message. -func (h *encHandshake) makeAuthMsg(prv *ecdsa.PrivateKey) (*authMsgV4, error) { - // Generate random initiator nonce. - h.initNonce = make([]byte, shaLen) - _, err := rand.Read(h.initNonce) - if err != nil { - return nil, err - } - // Generate random keypair to for ECDH. - h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil) - if err != nil { - return nil, err - } - - // Sign known message: static-shared-secret ^ nonce - token, err := h.staticSharedSecret(prv) - if err != nil { - return nil, err - } - signed := xor(token, h.initNonce) - signature, err := crypto.Sign(signed, h.randomPrivKey.ExportECDSA()) - if err != nil { - return nil, err - } - - msg := new(authMsgV4) - copy(msg.Signature[:], signature) - copy(msg.InitiatorPubkey[:], crypto.FromECDSAPub(&prv.PublicKey)[1:]) - copy(msg.Nonce[:], h.initNonce) - msg.Version = 4 - return msg, nil -} - -func (h *encHandshake) handleAuthResp(msg *authRespV4) (err error) { - h.respNonce = msg.Nonce[:] - h.remoteRandomPub, err = importPublicKey(msg.RandomPubkey[:]) - return err -} - -// receiverEncHandshake negotiates a session token on conn. -// it should be called on the listening side of the connection. -// -// prv is the local client's private key. -func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s secrets, err error) { - authMsg := new(authMsgV4) - authPacket, err := readHandshakeMsg(authMsg, encAuthMsgLen, prv, conn) - if err != nil { - return s, err - } - h := new(encHandshake) - if err := h.handleAuthMsg(authMsg, prv); err != nil { - return s, err - } - - authRespMsg, err := h.makeAuthResp() - if err != nil { - return s, err - } - var authRespPacket []byte - if authMsg.gotPlain { - authRespPacket, err = authRespMsg.sealPlain(h) - } else { - authRespPacket, err = sealEIP8(authRespMsg, h) - } - if err != nil { - return s, err - } - if _, err = conn.Write(authRespPacket); err != nil { - return s, err - } - return h.secrets(authPacket, authRespPacket) -} - -func (h *encHandshake) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error { - // Import the remote identity. - rpub, err := importPublicKey(msg.InitiatorPubkey[:]) - if err != nil { - return err - } - h.initNonce = msg.Nonce[:] - h.remote = rpub - - // Generate random keypair for ECDH. - // If a private key is already set, use it instead of generating one (for testing). - if h.randomPrivKey == nil { - h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil) - if err != nil { - return err - } - } - - // Check the signature. - token, err := h.staticSharedSecret(prv) - if err != nil { - return err - } - signedMsg := xor(token, h.initNonce) - remoteRandomPub, err := crypto.Ecrecover(signedMsg, msg.Signature[:]) - if err != nil { - return err - } - h.remoteRandomPub, _ = importPublicKey(remoteRandomPub) - return nil -} - -func (h *encHandshake) makeAuthResp() (msg *authRespV4, err error) { - // Generate random nonce. - h.respNonce = make([]byte, shaLen) - if _, err = rand.Read(h.respNonce); err != nil { - return nil, err - } - - msg = new(authRespV4) - copy(msg.Nonce[:], h.respNonce) - copy(msg.RandomPubkey[:], exportPubkey(&h.randomPrivKey.PublicKey)) - msg.Version = 4 - return msg, nil -} - -func (msg *authMsgV4) sealPlain(h *encHandshake) ([]byte, error) { - buf := make([]byte, authMsgLen) - n := copy(buf, msg.Signature[:]) - n += copy(buf[n:], crypto.Keccak256(exportPubkey(&h.randomPrivKey.PublicKey))) - n += copy(buf[n:], msg.InitiatorPubkey[:]) - n += copy(buf[n:], msg.Nonce[:]) - buf[n] = 0 // token-flag - return ecies.Encrypt(rand.Reader, h.remote, buf, nil, nil) -} - -func (msg *authMsgV4) decodePlain(input []byte) { - n := copy(msg.Signature[:], input) - n += shaLen // skip sha3(initiator-ephemeral-pubk) - n += copy(msg.InitiatorPubkey[:], input[n:]) - copy(msg.Nonce[:], input[n:]) - msg.Version = 4 - msg.gotPlain = true -} - -func (msg *authRespV4) sealPlain(hs *encHandshake) ([]byte, error) { - buf := make([]byte, authRespLen) - n := copy(buf, msg.RandomPubkey[:]) - copy(buf[n:], msg.Nonce[:]) - return ecies.Encrypt(rand.Reader, hs.remote, buf, nil, nil) -} - -func (msg *authRespV4) decodePlain(input []byte) { - n := copy(msg.RandomPubkey[:], input) - copy(msg.Nonce[:], input[n:]) - msg.Version = 4 -} - -var padSpace = make([]byte, 300) - -func sealEIP8(msg interface{}, h *encHandshake) ([]byte, error) { - buf := new(bytes.Buffer) - if err := rlp.Encode(buf, msg); err != nil { - return nil, err - } - // pad with random amount of data. the amount needs to be at least 100 bytes to make - // the message distinguishable from pre-EIP-8 handshakes. - pad := padSpace[:mrand.Intn(len(padSpace)-100)+100] - buf.Write(pad) - prefix := make([]byte, 2) - binary.BigEndian.PutUint16(prefix, uint16(buf.Len()+eciesOverhead)) - - enc, err := ecies.Encrypt(rand.Reader, h.remote, buf.Bytes(), nil, prefix) - return append(prefix, enc...), err -} - -type plainDecoder interface { - decodePlain([]byte) -} - -func readHandshakeMsg(msg plainDecoder, plainSize int, prv *ecdsa.PrivateKey, r io.Reader) ([]byte, error) { - buf := make([]byte, plainSize) - if _, err := io.ReadFull(r, buf); err != nil { - return buf, err - } - // Attempt decoding pre-EIP-8 "plain" format. - key := ecies.ImportECDSA(prv) - if dec, err := key.Decrypt(buf, nil, nil); err == nil { - msg.decodePlain(dec) - return buf, nil - } - // Could be EIP-8 format, try that. - prefix := buf[:2] - size := binary.BigEndian.Uint16(prefix) - if size < uint16(plainSize) { - return buf, fmt.Errorf("size underflow, need at least %d bytes", plainSize) - } - buf = append(buf, make([]byte, size-uint16(plainSize)+2)...) - if _, err := io.ReadFull(r, buf[plainSize:]); err != nil { - return buf, err - } - dec, err := key.Decrypt(buf[2:], nil, prefix) - if err != nil { - return buf, err - } - // Can't use rlp.DecodeBytes here because it rejects - // trailing data (forward-compatibility). - s := rlp.NewStream(bytes.NewReader(dec), 0) - return buf, s.Decode(msg) -} - -// importPublicKey unmarshals 512 bit public keys. -func importPublicKey(pubKey []byte) (*ecies.PublicKey, error) { - var pubKey65 []byte - switch len(pubKey) { - case 64: - // add 'uncompressed key' flag - pubKey65 = append([]byte{0x04}, pubKey...) - case 65: - pubKey65 = pubKey - default: - return nil, fmt.Errorf("invalid public key length %v (expect 64/65)", len(pubKey)) - } - // TODO: fewer pointless conversions - pub, err := crypto.UnmarshalPubkey(pubKey65) - if err != nil { - return nil, err - } - return ecies.ImportECDSAPublic(pub), nil -} - -func exportPubkey(pub *ecies.PublicKey) []byte { - if pub == nil { - panic("nil pubkey") - } - return elliptic.Marshal(pub.Curve, pub.X, pub.Y)[1:] -} - -func xor(one, other []byte) (xor []byte) { - xor = make([]byte, len(one)) - for i := 0; i < len(one); i++ { - xor[i] = one[i] ^ other[i] - } - return xor -} - -var ( - // this is used in place of actual frame header data. - // TODO: replace this when Msg contains the protocol type code. - zeroHeader = []byte{0xC2, 0x80, 0x80} - // sixteen zero bytes - zero16 = make([]byte, 16) -) - -// rlpxFrameRW implements a simplified version of RLPx framing. -// chunked messages are not supported and all headers are equal to -// zeroHeader. -// -// rlpxFrameRW is not safe for concurrent use from multiple goroutines. -type rlpxFrameRW struct { - conn io.ReadWriter - enc cipher.Stream - dec cipher.Stream - - macCipher cipher.Block - egressMAC hash.Hash - ingressMAC hash.Hash - - snappy bool -} - -func newRLPXFrameRW(conn io.ReadWriter, s secrets) *rlpxFrameRW { - macc, err := aes.NewCipher(s.MAC) - if err != nil { - panic("invalid MAC secret: " + err.Error()) - } - encc, err := aes.NewCipher(s.AES) - if err != nil { - panic("invalid AES secret: " + err.Error()) - } - // we use an all-zeroes IV for AES because the key used - // for encryption is ephemeral. - iv := make([]byte, encc.BlockSize()) - return &rlpxFrameRW{ - conn: conn, - enc: cipher.NewCTR(encc, iv), - dec: cipher.NewCTR(encc, iv), - macCipher: macc, - egressMAC: s.EgressMAC, - ingressMAC: s.IngressMAC, - } -} - -func (rw *rlpxFrameRW) WriteMsg(msg Msg) error { - ptype, _ := rlp.EncodeToBytes(msg.Code) - - // if snappy is enabled, compress message now - if rw.snappy { - if msg.Size > maxUint24 { - return errPlainMessageTooLarge - } - payload, _ := ioutil.ReadAll(msg.Payload) - payload = snappy.Encode(nil, payload) - - msg.Payload = bytes.NewReader(payload) - msg.Size = uint32(len(payload)) - } - // write header - headbuf := make([]byte, 32) - fsize := uint32(len(ptype)) + msg.Size - if fsize > maxUint24 { - return errors.New("message size overflows uint24") - } - putInt24(fsize, headbuf) // TODO: check overflow - copy(headbuf[3:], zeroHeader) - rw.enc.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now encrypted - - // write header MAC - copy(headbuf[16:], updateMAC(rw.egressMAC, rw.macCipher, headbuf[:16])) - if _, err := rw.conn.Write(headbuf); err != nil { - log.Error("WriteMsg error1", "err", err) - return err - } - - // write encrypted frame, updating the egress MAC hash with - // the data written to conn. - tee := cipher.StreamWriter{S: rw.enc, W: io.MultiWriter(rw.conn, rw.egressMAC)} - if _, err := tee.Write(ptype); err != nil { - log.Error("WriteMsg error2", "err", err) - return err - } - if _, err := io.Copy(tee, msg.Payload); err != nil { - log.Error("WriteMsg error3", "err", err) - return err - } - if padding := fsize % 16; padding > 0 { - if _, err := tee.Write(zero16[:16-padding]); err != nil { - log.Error("WriteMsg error4", "err", err) - return err - } - } - - // write frame MAC. egress MAC hash is up to date because - // frame content was written to it as well. - fmacseed := rw.egressMAC.Sum(nil) - mac := updateMAC(rw.egressMAC, rw.macCipher, fmacseed) - _, err := rw.conn.Write(mac) - if err != nil { - log.Error("WriteMsg error5", "err", err) - } - return err -} - -func (rw *rlpxFrameRW) ReadMsg() (msg Msg, err error) { - // read the header - headbuf := make([]byte, 32) - if _, err := io.ReadFull(rw.conn, headbuf); err != nil { - log.Error("ReadMsg error1", "err", err) - return msg, err - } - // verify header mac - shouldMAC := updateMAC(rw.ingressMAC, rw.macCipher, headbuf[:16]) - if !hmac.Equal(shouldMAC, headbuf[16:]) { - return msg, errors.New("bad header MAC") - } - rw.dec.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now decrypted - fsize := readInt24(headbuf) - // ignore protocol type for now - - // read the frame content - var rsize = fsize // frame size rounded up to 16 byte boundary - if padding := fsize % 16; padding > 0 { - rsize += 16 - padding - } - framebuf := make([]byte, rsize) - if _, err := io.ReadFull(rw.conn, framebuf); err != nil { - log.Error("ReadMsg error2", "err", err) - return msg, err - } - - // read and validate frame MAC. we can re-use headbuf for that. - rw.ingressMAC.Write(framebuf) - fmacseed := rw.ingressMAC.Sum(nil) - if _, err := io.ReadFull(rw.conn, headbuf[:16]); err != nil { - log.Error("ReadMsg error3", "err", err) - return msg, err - } - shouldMAC = updateMAC(rw.ingressMAC, rw.macCipher, fmacseed) - if !hmac.Equal(shouldMAC, headbuf[:16]) { - return msg, errors.New("bad frame MAC") - } - - // decrypt frame content - rw.dec.XORKeyStream(framebuf, framebuf) - - // decode message code - content := bytes.NewReader(framebuf[:fsize]) - if err := rlp.Decode(content, &msg.Code); err != nil { - log.Error("ReadMsg error4", "err", err) - return msg, err - } - msg.Size = uint32(content.Len()) - msg.Payload = content - - // if snappy is enabled, verify and decompress message - if rw.snappy { - payload, err := ioutil.ReadAll(msg.Payload) - if err != nil { - log.Error("ReadMsg error5", "err", err) - return msg, err - } - size, err := snappy.DecodedLen(payload) - if err != nil { - log.Error("ReadMsg error6", "err", err) - return msg, err - } - if size > int(maxUint24) { - log.Error("ReadMsg error7", "err", errPlainMessageTooLarge) - return msg, errPlainMessageTooLarge - } - payload, err = snappy.Decode(nil, payload) - if err != nil { - log.Error("ReadMsg error8", "err", errPlainMessageTooLarge) - return msg, err - } - msg.Size, msg.Payload = uint32(size), bytes.NewReader(payload) - } - return msg, nil -} - -// updateMAC reseeds the given hash with encrypted seed. -// it returns the first 16 bytes of the hash sum after seeding. -func updateMAC(mac hash.Hash, block cipher.Block, seed []byte) []byte { - aesbuf := make([]byte, aes.BlockSize) - block.Encrypt(aesbuf, mac.Sum(nil)) - for i := range aesbuf { - aesbuf[i] ^= seed[i] - } - mac.Write(aesbuf) - return mac.Sum(nil)[:16] -} - -func readInt24(b []byte) uint32 { - return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16 -} - -func putInt24(v uint32, b []byte) { - b[0] = byte(v >> 16) - b[1] = byte(v >> 8) - b[2] = byte(v) -} diff --git a/p2p/rlpx/buffer.go b/p2p/rlpx/buffer.go new file mode 100644 index 0000000000..bb38e10577 --- /dev/null +++ b/p2p/rlpx/buffer.go @@ -0,0 +1,127 @@ +// Copyright 2021 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 . + +package rlpx + +import ( + "io" +) + +// readBuffer implements buffering for network reads. This type is similar to bufio.Reader, +// with two crucial differences: the buffer slice is exposed, and the buffer keeps all +// read data available until reset. +// +// How to use this type: +// +// Keep a readBuffer b alongside the underlying network connection. When reading a packet +// from the connection, first call b.reset(). This empties b.data. Now perform reads +// through b.read() until the end of the packet is reached. The complete packet data is +// now available in b.data. +type readBuffer struct { + data []byte + end int +} + +// reset removes all processed data which was read since the last call to reset. +// After reset, len(b.data) is zero. +func (b *readBuffer) reset() { + unprocessed := b.end - len(b.data) + copy(b.data[:unprocessed], b.data[len(b.data):b.end]) + b.end = unprocessed + b.data = b.data[:0] +} + +// read reads at least n bytes from r, returning the bytes. +// The returned slice is valid until the next call to reset. +func (b *readBuffer) read(r io.Reader, n int) ([]byte, error) { + offset := len(b.data) + have := b.end - len(b.data) + + // If n bytes are available in the buffer, there is no need to read from r at all. + if have >= n { + b.data = b.data[:offset+n] + return b.data[offset : offset+n], nil + } + + // Make buffer space available. + need := n - have + b.grow(need) + + // Read. + rn, err := io.ReadAtLeast(r, b.data[b.end:cap(b.data)], need) + if err != nil { + return nil, err + } + b.end += rn + b.data = b.data[:offset+n] + return b.data[offset : offset+n], nil +} + +// grow ensures the buffer has at least n bytes of unused space. +func (b *readBuffer) grow(n int) { + if cap(b.data)-b.end >= n { + return + } + need := n - (cap(b.data) - b.end) + offset := len(b.data) + b.data = append(b.data[:cap(b.data)], make([]byte, need)...) + b.data = b.data[:offset] +} + +// writeBuffer implements buffering for network writes. This is essentially +// a convenience wrapper around a byte slice. +type writeBuffer struct { + data []byte +} + +func (b *writeBuffer) reset() { + b.data = b.data[:0] +} + +func (b *writeBuffer) appendZero(n int) []byte { + offset := len(b.data) + b.data = append(b.data, make([]byte, n)...) + return b.data[offset : offset+n] +} + +func (b *writeBuffer) Write(data []byte) (int, error) { + b.data = append(b.data, data...) + return len(data), nil +} + +const maxUint24 = int(^uint32(0) >> 8) + +func readUint24(b []byte) uint32 { + return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16 +} + +func putUint24(v uint32, b []byte) { + b[0] = byte(v >> 16) + b[1] = byte(v >> 8) + b[2] = byte(v) +} + +// growslice ensures b has the wanted length by either expanding it to its capacity +// or allocating a new slice if b has insufficient capacity. +func growslice(b []byte, wantLength int) []byte { + if len(b) >= wantLength { + return b + } + if cap(b) >= wantLength { + return b[:cap(b)] + } + return make([]byte, wantLength) +} diff --git a/p2p/rlpx/buffer_test.go b/p2p/rlpx/buffer_test.go new file mode 100644 index 0000000000..450b62442f --- /dev/null +++ b/p2p/rlpx/buffer_test.go @@ -0,0 +1,52 @@ +// Copyright 2021 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 . + +package rlpx + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/AlayaNetwork/Alaya-Go/common/hexutil" +) + +func TestReadBufferReset(t *testing.T) { + reader := bytes.NewReader(hexutil.MustDecode("0x010202030303040505")) + var b readBuffer + + s1, _ := b.read(reader, 1) + s2, _ := b.read(reader, 2) + s3, _ := b.read(reader, 3) + + assert.Equal(t, []byte{1}, s1) + assert.Equal(t, []byte{2, 2}, s2) + assert.Equal(t, []byte{3, 3, 3}, s3) + + b.reset() + + s4, _ := b.read(reader, 1) + s5, _ := b.read(reader, 2) + + assert.Equal(t, []byte{4}, s4) + assert.Equal(t, []byte{5, 5}, s5) + + s6, err := b.read(reader, 2) + + assert.EqualError(t, err, "EOF") + assert.Nil(t, s6) +} diff --git a/p2p/rlpx/rlpx.go b/p2p/rlpx/rlpx.go new file mode 100644 index 0000000000..9f3afb2b64 --- /dev/null +++ b/p2p/rlpx/rlpx.go @@ -0,0 +1,678 @@ +// 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 . + +// Package rlpx implements the RLPx transport protocol. +package rlpx + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/hmac" + "crypto/rand" + "encoding/binary" + "errors" + "fmt" + "hash" + "io" + mrand "math/rand" + "net" + "time" + + "golang.org/x/crypto/sha3" + + "github.com/golang/snappy" + + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/crypto/ecies" + "github.com/AlayaNetwork/Alaya-Go/rlp" +) + +// Conn is an RLPx network connection. It wraps a low-level network connection. The +// underlying connection should not be used for other activity when it is wrapped by Conn. +// +// Before sending messages, a handshake must be performed by calling the Handshake method. +// This type is not generally safe for concurrent use, but reading and writing of messages +// may happen concurrently after the handshake. +type Conn struct { + dialDest *ecdsa.PublicKey + conn net.Conn + session *sessionState + + // These are the buffers for snappy compression. + // Compression is enabled if they are non-nil. + snappyReadBuffer []byte + snappyWriteBuffer []byte +} + +// sessionState contains the session keys. +type sessionState struct { + enc cipher.Stream + dec cipher.Stream + + egressMAC hashMAC + ingressMAC hashMAC + rbuf readBuffer + wbuf writeBuffer +} + +// hashMAC holds the state of the RLPx v4 MAC contraption. +type hashMAC struct { + cipher cipher.Block + hash hash.Hash + aesBuffer [16]byte + hashBuffer [32]byte + seedBuffer [32]byte +} + +func newHashMAC(cipher cipher.Block, h hash.Hash) hashMAC { + m := hashMAC{cipher: cipher, hash: h} + if cipher.BlockSize() != len(m.aesBuffer) { + panic(fmt.Errorf("invalid MAC cipher block size %d", cipher.BlockSize())) + } + if h.Size() != len(m.hashBuffer) { + panic(fmt.Errorf("invalid MAC digest size %d", h.Size())) + } + return m +} + +// NewConn wraps the given network connection. If dialDest is non-nil, the connection +// behaves as the initiator during the handshake. +func NewConn(conn net.Conn, dialDest *ecdsa.PublicKey) *Conn { + return &Conn{ + dialDest: dialDest, + conn: conn, + } +} + +// SetSnappy enables or disables snappy compression of messages. This is usually called +// after the devp2p Hello message exchange when the negotiated version indicates that +// compression is available on both ends of the connection. +func (c *Conn) SetSnappy(snappy bool) { + if snappy { + c.snappyReadBuffer = []byte{} + c.snappyWriteBuffer = []byte{} + } else { + c.snappyReadBuffer = nil + c.snappyWriteBuffer = nil + } +} + +// SetReadDeadline sets the deadline for all future read operations. +func (c *Conn) SetReadDeadline(time time.Time) error { + return c.conn.SetReadDeadline(time) +} + +// SetWriteDeadline sets the deadline for all future write operations. +func (c *Conn) SetWriteDeadline(time time.Time) error { + return c.conn.SetWriteDeadline(time) +} + +// SetDeadline sets the deadline for all future read and write operations. +func (c *Conn) SetDeadline(time time.Time) error { + return c.conn.SetDeadline(time) +} + +// Read reads a message from the connection. +// The returned data buffer is valid until the next call to Read. +func (c *Conn) Read() (code uint64, data []byte, wireSize int, err error) { + if c.session == nil { + panic("can't ReadMsg before handshake") + } + + frame, err := c.session.readFrame(c.conn) + if err != nil { + return 0, nil, 0, err + } + code, data, err = rlp.SplitUint64(frame) + if err != nil { + return 0, nil, 0, fmt.Errorf("invalid message code: %v", err) + } + wireSize = len(data) + + // If snappy is enabled, verify and decompress message. + if c.snappyReadBuffer != nil { + var actualSize int + actualSize, err = snappy.DecodedLen(data) + if err != nil { + return code, nil, 0, err + } + if actualSize > maxUint24 { + return code, nil, 0, errPlainMessageTooLarge + } + c.snappyReadBuffer = growslice(c.snappyReadBuffer, actualSize) + data, err = snappy.Decode(c.snappyReadBuffer, data) + } + return code, data, wireSize, err +} + +func (h *sessionState) readFrame(conn io.Reader) ([]byte, error) { + h.rbuf.reset() + + // Read the frame header. + header, err := h.rbuf.read(conn, 32) + if err != nil { + return nil, err + } + + // Verify header MAC. + wantHeaderMAC := h.ingressMAC.computeHeader(header[:16]) + if !hmac.Equal(wantHeaderMAC, header[16:]) { + return nil, errors.New("bad header MAC") + } + + // Decrypt the frame header to get the frame size. + h.dec.XORKeyStream(header[:16], header[:16]) + fsize := readUint24(header[:16]) + // Frame size rounded up to 16 byte boundary for padding. + rsize := fsize + if padding := fsize % 16; padding > 0 { + rsize += 16 - padding + } + + // Read the frame content. + frame, err := h.rbuf.read(conn, int(rsize)) + if err != nil { + return nil, err + } + + // Validate frame MAC. + frameMAC, err := h.rbuf.read(conn, 16) + if err != nil { + return nil, err + } + wantFrameMAC := h.ingressMAC.computeFrame(frame) + if !hmac.Equal(wantFrameMAC, frameMAC) { + return nil, errors.New("bad frame MAC") + } + + // Decrypt the frame data. + h.dec.XORKeyStream(frame, frame) + return frame[:fsize], nil +} + +// Write writes a message to the connection. +// +// Write returns the written size of the message data. This may be less than or equal to +// len(data) depending on whether snappy compression is enabled. +func (c *Conn) Write(code uint64, data []byte) (uint32, error) { + if c.session == nil { + panic("can't WriteMsg before handshake") + } + if len(data) > maxUint24 { + return 0, errPlainMessageTooLarge + } + if c.snappyWriteBuffer != nil { + // Ensure the buffer has sufficient size. + // Package snappy will allocate its own buffer if the provided + // one is smaller than MaxEncodedLen. + c.snappyWriteBuffer = growslice(c.snappyWriteBuffer, snappy.MaxEncodedLen(len(data))) + data = snappy.Encode(c.snappyWriteBuffer, data) + } + + wireSize := uint32(len(data)) + err := c.session.writeFrame(c.conn, code, data) + return wireSize, err +} + +func (h *sessionState) writeFrame(conn io.Writer, code uint64, data []byte) error { + h.wbuf.reset() + + // Write header. + fsize := rlp.IntSize(code) + len(data) + if fsize > maxUint24 { + return errPlainMessageTooLarge + } + header := h.wbuf.appendZero(16) + putUint24(uint32(fsize), header) + copy(header[3:], zeroHeader) + h.enc.XORKeyStream(header, header) + + // Write header MAC. + h.wbuf.Write(h.egressMAC.computeHeader(header)) + + // Encode and encrypt the frame data. + offset := len(h.wbuf.data) + h.wbuf.data = rlp.AppendUint64(h.wbuf.data, code) + h.wbuf.Write(data) + if padding := fsize % 16; padding > 0 { + h.wbuf.appendZero(16 - padding) + } + framedata := h.wbuf.data[offset:] + h.enc.XORKeyStream(framedata, framedata) + + // Write frame MAC. + h.wbuf.Write(h.egressMAC.computeFrame(framedata)) + + _, err := conn.Write(h.wbuf.data) + return err +} + +// computeHeader computes the MAC of a frame header. +func (m *hashMAC) computeHeader(header []byte) []byte { + sum1 := m.hash.Sum(m.hashBuffer[:0]) + return m.compute(sum1, header) +} + +// computeFrame computes the MAC of framedata. +func (m *hashMAC) computeFrame(framedata []byte) []byte { + m.hash.Write(framedata) + seed := m.hash.Sum(m.seedBuffer[:0]) + return m.compute(seed, seed[:16]) +} + +// compute computes the MAC of a 16-byte 'seed'. +// +// To do this, it encrypts the current value of the hash state, then XORs the ciphertext +// with seed. The obtained value is written back into the hash state and hash output is +// taken again. The first 16 bytes of the resulting sum are the MAC value. +// +// This MAC construction is a horrible, legacy thing. +func (m *hashMAC) compute(sum1, seed []byte) []byte { + if len(seed) != len(m.aesBuffer) { + panic("invalid MAC seed") + } + + m.cipher.Encrypt(m.aesBuffer[:], sum1) + for i := range m.aesBuffer { + m.aesBuffer[i] ^= seed[i] + } + m.hash.Write(m.aesBuffer[:]) + sum2 := m.hash.Sum(m.hashBuffer[:0]) + return sum2[:16] +} + +// Handshake performs the handshake. This must be called before any data is written +// or read from the connection. +func (c *Conn) Handshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) { + var ( + sec Secrets + err error + h handshakeState + ) + if c.dialDest != nil { + sec, err = h.runInitiator(c.conn, prv, c.dialDest) + } else { + sec, err = h.runRecipient(c.conn, prv) + } + if err != nil { + return nil, err + } + c.InitWithSecrets(sec) + c.session.rbuf = h.rbuf + c.session.wbuf = h.wbuf + return sec.remote, err +} + +// InitWithSecrets injects connection secrets as if a handshake had +// been performed. This cannot be called after the handshake. +func (c *Conn) InitWithSecrets(sec Secrets) { + if c.session != nil { + panic("can't handshake twice") + } + macc, err := aes.NewCipher(sec.MAC) + if err != nil { + panic("invalid MAC secret: " + err.Error()) + } + encc, err := aes.NewCipher(sec.AES) + if err != nil { + panic("invalid AES secret: " + err.Error()) + } + // we use an all-zeroes IV for AES because the key used + // for encryption is ephemeral. + iv := make([]byte, encc.BlockSize()) + c.session = &sessionState{ + enc: cipher.NewCTR(encc, iv), + dec: cipher.NewCTR(encc, iv), + egressMAC: newHashMAC(macc, sec.EgressMAC), + ingressMAC: newHashMAC(macc, sec.IngressMAC), + } +} + +// Close closes the underlying network connection. +func (c *Conn) Close() error { + return c.conn.Close() +} + +// Constants for the handshake. +const ( + sskLen = 16 // ecies.MaxSharedKeyLength(pubKey) / 2 + sigLen = crypto.SignatureLength // elliptic S256 + pubLen = 64 // 512 bit pubkey in uncompressed representation without format byte + shaLen = 32 // hash length (for nonce etc) + + eciesOverhead = 65 /* pubkey */ + 16 /* IV */ + 32 /* MAC */ +) + +var ( + // this is used in place of actual frame header data. + // TODO: replace this when Msg contains the protocol type code. + zeroHeader = []byte{0xC2, 0x80, 0x80} + + // errPlainMessageTooLarge is returned if a decompressed message length exceeds + // the allowed 24 bits (i.e. length >= 16MB). + errPlainMessageTooLarge = errors.New("message length >= 16MB") +) + +// Secrets represents the connection secrets which are negotiated during the handshake. +type Secrets struct { + AES, MAC []byte + EgressMAC, IngressMAC hash.Hash + remote *ecdsa.PublicKey +} + +// handshakeState contains the state of the encryption handshake. +type handshakeState struct { + initiator bool + remote *ecies.PublicKey // remote-pubk + initNonce, respNonce []byte // nonce + randomPrivKey *ecies.PrivateKey // ecdhe-random + remoteRandomPub *ecies.PublicKey // ecdhe-random-pubk + + rbuf readBuffer + wbuf writeBuffer +} + +// RLPx v4 handshake auth (defined in EIP-8). +type authMsgV4 struct { + Signature [sigLen]byte + InitiatorPubkey [pubLen]byte + Nonce [shaLen]byte + Version uint + + // Ignore additional fields (forward-compatibility) + Rest []rlp.RawValue `rlp:"tail"` +} + +// RLPx v4 handshake response (defined in EIP-8). +type authRespV4 struct { + RandomPubkey [pubLen]byte + Nonce [shaLen]byte + Version uint + + // Ignore additional fields (forward-compatibility) + Rest []rlp.RawValue `rlp:"tail"` +} + +// runRecipient negotiates a session token on conn. +// it should be called on the listening side of the connection. +// +// prv is the local client's private key. +func (h *handshakeState) runRecipient(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s Secrets, err error) { + authMsg := new(authMsgV4) + authPacket, err := h.readMsg(authMsg, prv, conn) + if err != nil { + return s, err + } + if err := h.handleAuthMsg(authMsg, prv); err != nil { + return s, err + } + + authRespMsg, err := h.makeAuthResp() + if err != nil { + return s, err + } + authRespPacket, err := h.sealEIP8(authRespMsg) + if err != nil { + return s, err + } + if _, err = conn.Write(authRespPacket); err != nil { + return s, err + } + + return h.secrets(authPacket, authRespPacket) +} + +func (h *handshakeState) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error { + // Import the remote identity. + rpub, err := importPublicKey(msg.InitiatorPubkey[:]) + if err != nil { + return err + } + h.initNonce = msg.Nonce[:] + h.remote = rpub + + // Generate random keypair for ECDH. + // If a private key is already set, use it instead of generating one (for testing). + if h.randomPrivKey == nil { + h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil) + if err != nil { + return err + } + } + + // Check the signature. + token, err := h.staticSharedSecret(prv) + if err != nil { + return err + } + signedMsg := xor(token, h.initNonce) + remoteRandomPub, err := crypto.Ecrecover(signedMsg, msg.Signature[:]) + if err != nil { + return err + } + h.remoteRandomPub, _ = importPublicKey(remoteRandomPub) + return nil +} + +// secrets is called after the handshake is completed. +// It extracts the connection secrets from the handshake values. +func (h *handshakeState) secrets(auth, authResp []byte) (Secrets, error) { + ecdheSecret, err := h.randomPrivKey.GenerateShared(h.remoteRandomPub, sskLen, sskLen) + if err != nil { + return Secrets{}, err + } + + // derive base secrets from ephemeral key agreement + sharedSecret := crypto.Keccak256(ecdheSecret, crypto.Keccak256(h.respNonce, h.initNonce)) + aesSecret := crypto.Keccak256(ecdheSecret, sharedSecret) + s := Secrets{ + remote: h.remote.ExportECDSA(), + AES: aesSecret, + MAC: crypto.Keccak256(ecdheSecret, aesSecret), + } + + // setup sha3 instances for the MACs + mac1 := sha3.NewLegacyKeccak256() + mac1.Write(xor(s.MAC, h.respNonce)) + mac1.Write(auth) + mac2 := sha3.NewLegacyKeccak256() + mac2.Write(xor(s.MAC, h.initNonce)) + mac2.Write(authResp) + if h.initiator { + s.EgressMAC, s.IngressMAC = mac1, mac2 + } else { + s.EgressMAC, s.IngressMAC = mac2, mac1 + } + + return s, nil +} + +// staticSharedSecret returns the static shared secret, the result +// of key agreement between the local and remote static node key. +func (h *handshakeState) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error) { + return ecies.ImportECDSA(prv).GenerateShared(h.remote, sskLen, sskLen) +} + +// runInitiator negotiates a session token on conn. +// it should be called on the dialing side of the connection. +// +// prv is the local client's private key. +func (h *handshakeState) runInitiator(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s Secrets, err error) { + h.initiator = true + h.remote = ecies.ImportECDSAPublic(remote) + + authMsg, err := h.makeAuthMsg(prv) + if err != nil { + return s, err + } + authPacket, err := h.sealEIP8(authMsg) + if err != nil { + return s, err + } + + if _, err = conn.Write(authPacket); err != nil { + return s, err + } + + authRespMsg := new(authRespV4) + authRespPacket, err := h.readMsg(authRespMsg, prv, conn) + if err != nil { + return s, err + } + if err := h.handleAuthResp(authRespMsg); err != nil { + return s, err + } + + return h.secrets(authPacket, authRespPacket) +} + +// makeAuthMsg creates the initiator handshake message. +func (h *handshakeState) makeAuthMsg(prv *ecdsa.PrivateKey) (*authMsgV4, error) { + // Generate random initiator nonce. + h.initNonce = make([]byte, shaLen) + _, err := rand.Read(h.initNonce) + if err != nil { + return nil, err + } + // Generate random keypair to for ECDH. + h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil) + if err != nil { + return nil, err + } + + // Sign known message: static-shared-secret ^ nonce + token, err := h.staticSharedSecret(prv) + if err != nil { + return nil, err + } + signed := xor(token, h.initNonce) + signature, err := crypto.Sign(signed, h.randomPrivKey.ExportECDSA()) + if err != nil { + return nil, err + } + + msg := new(authMsgV4) + copy(msg.Signature[:], signature) + copy(msg.InitiatorPubkey[:], crypto.FromECDSAPub(&prv.PublicKey)[1:]) + copy(msg.Nonce[:], h.initNonce) + msg.Version = 4 + return msg, nil +} + +func (h *handshakeState) handleAuthResp(msg *authRespV4) (err error) { + h.respNonce = msg.Nonce[:] + h.remoteRandomPub, err = importPublicKey(msg.RandomPubkey[:]) + return err +} + +func (h *handshakeState) makeAuthResp() (msg *authRespV4, err error) { + // Generate random nonce. + h.respNonce = make([]byte, shaLen) + if _, err = rand.Read(h.respNonce); err != nil { + return nil, err + } + + msg = new(authRespV4) + copy(msg.Nonce[:], h.respNonce) + copy(msg.RandomPubkey[:], exportPubkey(&h.randomPrivKey.PublicKey)) + msg.Version = 4 + return msg, nil +} + +// readMsg reads an encrypted handshake message, decoding it into msg. +func (h *handshakeState) readMsg(msg interface{}, prv *ecdsa.PrivateKey, r io.Reader) ([]byte, error) { + h.rbuf.reset() + h.rbuf.grow(512) + + // Read the size prefix. + prefix, err := h.rbuf.read(r, 2) + if err != nil { + return nil, err + } + size := binary.BigEndian.Uint16(prefix) + + // Read the handshake packet. + packet, err := h.rbuf.read(r, int(size)) + if err != nil { + return nil, err + } + dec, err := ecies.ImportECDSA(prv).Decrypt(packet, nil, prefix) + if err != nil { + return nil, err + } + // Can't use rlp.DecodeBytes here because it rejects + // trailing data (forward-compatibility). + s := rlp.NewStream(bytes.NewReader(dec), 0) + err = s.Decode(msg) + return h.rbuf.data[:len(prefix)+len(packet)], err +} + +// sealEIP8 encrypts a handshake message. +func (h *handshakeState) sealEIP8(msg interface{}) ([]byte, error) { + h.wbuf.reset() + + // Write the message plaintext. + if err := rlp.Encode(&h.wbuf, msg); err != nil { + return nil, err + } + // Pad with random amount of data. the amount needs to be at least 100 bytes to make + // the message distinguishable from pre-EIP-8 handshakes. + h.wbuf.appendZero(mrand.Intn(100) + 100) + + prefix := make([]byte, 2) + binary.BigEndian.PutUint16(prefix, uint16(len(h.wbuf.data)+eciesOverhead)) + + enc, err := ecies.Encrypt(rand.Reader, h.remote, h.wbuf.data, nil, prefix) + return append(prefix, enc...), err +} + +// importPublicKey unmarshals 512 bit public keys. +func importPublicKey(pubKey []byte) (*ecies.PublicKey, error) { + var pubKey65 []byte + switch len(pubKey) { + case 64: + // add 'uncompressed key' flag + pubKey65 = append([]byte{0x04}, pubKey...) + case 65: + pubKey65 = pubKey + default: + return nil, fmt.Errorf("invalid public key length %v (expect 64/65)", len(pubKey)) + } + // TODO: fewer pointless conversions + pub, err := crypto.UnmarshalPubkey(pubKey65) + if err != nil { + return nil, err + } + return ecies.ImportECDSAPublic(pub), nil +} + +func exportPubkey(pub *ecies.PublicKey) []byte { + if pub == nil { + panic("nil pubkey") + } + return elliptic.Marshal(pub.Curve, pub.X, pub.Y)[1:] +} + +func xor(one, other []byte) (xor []byte) { + xor = make([]byte, len(one)) + for i := 0; i < len(one); i++ { + xor[i] = one[i] ^ other[i] + } + return xor +} diff --git a/p2p/rlpx/rlpx_test.go b/p2p/rlpx/rlpx_test.go new file mode 100644 index 0000000000..3e2521455f --- /dev/null +++ b/p2p/rlpx/rlpx_test.go @@ -0,0 +1,455 @@ +// 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 . + +package rlpx + +import ( + "bytes" + "crypto/ecdsa" + "encoding/hex" + "fmt" + "io" + "math/rand" + "net" + "reflect" + "strings" + "testing" + + "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/pipes" + + "github.com/davecgh/go-spew/spew" + "github.com/stretchr/testify/assert" + + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/crypto/ecies" + "github.com/AlayaNetwork/Alaya-Go/rlp" +) + +type message struct { + code uint64 + data []byte + err error +} + +func TestHandshake(t *testing.T) { + p1, p2 := createPeers(t) + p1.Close() + p2.Close() +} + +// This test checks that messages can be sent and received through WriteMsg/ReadMsg. +func TestReadWriteMsg(t *testing.T) { + peer1, peer2 := createPeers(t) + defer peer1.Close() + defer peer2.Close() + + testCode := uint64(23) + testData := []byte("test") + checkMsgReadWrite(t, peer1, peer2, testCode, testData) + + t.Log("enabling snappy") + peer1.SetSnappy(true) + peer2.SetSnappy(true) + checkMsgReadWrite(t, peer1, peer2, testCode, testData) +} + +func checkMsgReadWrite(t *testing.T, p1, p2 *Conn, msgCode uint64, msgData []byte) { + // Set up the reader. + ch := make(chan message, 1) + go func() { + var msg message + msg.code, msg.data, _, msg.err = p1.Read() + ch <- msg + }() + + // Write the message. + _, err := p2.Write(msgCode, msgData) + if err != nil { + t.Fatal(err) + } + + // Check it was received correctly. + msg := <-ch + assert.Equal(t, msgCode, msg.code, "wrong message code returned from ReadMsg") + assert.Equal(t, msgData, msg.data, "wrong message data returned from ReadMsg") +} + +func createPeers(t *testing.T) (peer1, peer2 *Conn) { + conn1, conn2 := net.Pipe() + key1, key2 := newkey(), newkey() + peer1 = NewConn(conn1, &key2.PublicKey) // dialer + peer2 = NewConn(conn2, nil) // listener + doHandshake(t, peer1, peer2, key1, key2) + return peer1, peer2 +} + +func doHandshake(t *testing.T, peer1, peer2 *Conn, key1, key2 *ecdsa.PrivateKey) { + keyChan := make(chan *ecdsa.PublicKey, 1) + go func() { + pubKey, err := peer2.Handshake(key2) + if err != nil { + t.Errorf("peer2 could not do handshake: %v", err) + } + keyChan <- pubKey + }() + + pubKey2, err := peer1.Handshake(key1) + if err != nil { + t.Errorf("peer1 could not do handshake: %v", err) + } + pubKey1 := <-keyChan + + // Confirm the handshake was successful. + if !reflect.DeepEqual(pubKey1, &key1.PublicKey) || !reflect.DeepEqual(pubKey2, &key2.PublicKey) { + t.Fatal("unsuccessful handshake") + } +} + +// This test checks the frame data of written messages. +func TestFrameReadWrite(t *testing.T) { + conn := NewConn(nil, nil) + hash := fakeHash([]byte{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}) + conn.InitWithSecrets(Secrets{ + AES: crypto.Keccak256(), + MAC: crypto.Keccak256(), + IngressMAC: hash, + EgressMAC: hash, + }) + h := conn.session + + golden := unhex(` + 00828ddae471818bb0bfa6b551d1cb42 + 01010101010101010101010101010101 + ba628a4ba590cb43f7848f41c4382885 + 01010101010101010101010101010101 + `) + msgCode := uint64(8) + msg := []uint{1, 2, 3, 4} + msgEnc, _ := rlp.EncodeToBytes(msg) + + // Check writeFrame. The frame that's written should be equal to the test vector. + buf := new(bytes.Buffer) + if err := h.writeFrame(buf, msgCode, msgEnc); err != nil { + t.Fatalf("WriteMsg error: %v", err) + } + if !bytes.Equal(buf.Bytes(), golden) { + t.Fatalf("output mismatch:\n got: %x\n want: %x", buf.Bytes(), golden) + } + + // Check readFrame on the test vector. + content, err := h.readFrame(bytes.NewReader(golden)) + if err != nil { + t.Fatalf("ReadMsg error: %v", err) + } + wantContent := unhex("08C401020304") + if !bytes.Equal(content, wantContent) { + t.Errorf("frame content mismatch:\ngot %x\nwant %x", content, wantContent) + } +} + +type fakeHash []byte + +func (fakeHash) Write(p []byte) (int, error) { return len(p), nil } +func (fakeHash) Reset() {} +func (fakeHash) BlockSize() int { return 0 } +func (h fakeHash) Size() int { return len(h) } +func (h fakeHash) Sum(b []byte) []byte { return append(b, h...) } + +type handshakeAuthTest struct { + input string + wantVersion uint + wantRest []rlp.RawValue +} + +var eip8HandshakeAuthTests = []handshakeAuthTest{ + // (Auth₂) EIP-8 encoding + { + input: ` + 01b304ab7578555167be8154d5cc456f567d5ba302662433674222360f08d5f1534499d3678b513b + 0fca474f3a514b18e75683032eb63fccb16c156dc6eb2c0b1593f0d84ac74f6e475f1b8d56116b84 + 9634a8c458705bf83a626ea0384d4d7341aae591fae42ce6bd5c850bfe0b999a694a49bbbaf3ef6c + da61110601d3b4c02ab6c30437257a6e0117792631a4b47c1d52fc0f8f89caadeb7d02770bf999cc + 147d2df3b62e1ffb2c9d8c125a3984865356266bca11ce7d3a688663a51d82defaa8aad69da39ab6 + d5470e81ec5f2a7a47fb865ff7cca21516f9299a07b1bc63ba56c7a1a892112841ca44b6e0034dee + 70c9adabc15d76a54f443593fafdc3b27af8059703f88928e199cb122362a4b35f62386da7caad09 + c001edaeb5f8a06d2b26fb6cb93c52a9fca51853b68193916982358fe1e5369e249875bb8d0d0ec3 + 6f917bc5e1eafd5896d46bd61ff23f1a863a8a8dcd54c7b109b771c8e61ec9c8908c733c0263440e + 2aa067241aaa433f0bb053c7b31a838504b148f570c0ad62837129e547678c5190341e4f1693956c + 3bf7678318e2d5b5340c9e488eefea198576344afbdf66db5f51204a6961a63ce072c8926c + `, + wantVersion: 4, + wantRest: []rlp.RawValue{}, + }, + // (Auth₃) RLPx v4 EIP-8 encoding with version 56, additional list elements + { + input: ` + 01b8044c6c312173685d1edd268aa95e1d495474c6959bcdd10067ba4c9013df9e40ff45f5bfd6f7 + 2471f93a91b493f8e00abc4b80f682973de715d77ba3a005a242eb859f9a211d93a347fa64b597bf + 280a6b88e26299cf263b01b8dfdb712278464fd1c25840b995e84d367d743f66c0e54a586725b7bb + f12acca27170ae3283c1073adda4b6d79f27656993aefccf16e0d0409fe07db2dc398a1b7e8ee93b + cd181485fd332f381d6a050fba4c7641a5112ac1b0b61168d20f01b479e19adf7fdbfa0905f63352 + bfc7e23cf3357657455119d879c78d3cf8c8c06375f3f7d4861aa02a122467e069acaf513025ff19 + 6641f6d2810ce493f51bee9c966b15c5043505350392b57645385a18c78f14669cc4d960446c1757 + 1b7c5d725021babbcd786957f3d17089c084907bda22c2b2675b4378b114c601d858802a55345a15 + 116bc61da4193996187ed70d16730e9ae6b3bb8787ebcaea1871d850997ddc08b4f4ea668fbf3740 + 7ac044b55be0908ecb94d4ed172ece66fd31bfdadf2b97a8bc690163ee11f5b575a4b44e36e2bfb2 + f0fce91676fd64c7773bac6a003f481fddd0bae0a1f31aa27504e2a533af4cef3b623f4791b2cca6 + d490 + `, + wantVersion: 56, + wantRest: []rlp.RawValue{{0x01}, {0x02}, {0xC2, 0x04, 0x05}}, + }, +} + +type handshakeAckTest struct { + input string + wantVersion uint + wantRest []rlp.RawValue +} + +var eip8HandshakeRespTests = []handshakeAckTest{ + // (Ack₂) EIP-8 encoding + { + input: ` + 01ea0451958701280a56482929d3b0757da8f7fbe5286784beead59d95089c217c9b917788989470 + b0e330cc6e4fb383c0340ed85fab836ec9fb8a49672712aeabbdfd1e837c1ff4cace34311cd7f4de + 05d59279e3524ab26ef753a0095637ac88f2b499b9914b5f64e143eae548a1066e14cd2f4bd7f814 + c4652f11b254f8a2d0191e2f5546fae6055694aed14d906df79ad3b407d94692694e259191cde171 + ad542fc588fa2b7333313d82a9f887332f1dfc36cea03f831cb9a23fea05b33deb999e85489e645f + 6aab1872475d488d7bd6c7c120caf28dbfc5d6833888155ed69d34dbdc39c1f299be1057810f34fb + e754d021bfca14dc989753d61c413d261934e1a9c67ee060a25eefb54e81a4d14baff922180c395d + 3f998d70f46f6b58306f969627ae364497e73fc27f6d17ae45a413d322cb8814276be6ddd13b885b + 201b943213656cde498fa0e9ddc8e0b8f8a53824fbd82254f3e2c17e8eaea009c38b4aa0a3f306e8 + 797db43c25d68e86f262e564086f59a2fc60511c42abfb3057c247a8a8fe4fb3ccbadde17514b7ac + 8000cdb6a912778426260c47f38919a91f25f4b5ffb455d6aaaf150f7e5529c100ce62d6d92826a7 + 1778d809bdf60232ae21ce8a437eca8223f45ac37f6487452ce626f549b3b5fdee26afd2072e4bc7 + 5833c2464c805246155289f4 + `, + wantVersion: 4, + wantRest: []rlp.RawValue{}, + }, + // (Ack₃) EIP-8 encoding with version 57, additional list elements + { + input: ` + 01f004076e58aae772bb101ab1a8e64e01ee96e64857ce82b1113817c6cdd52c09d26f7b90981cd7 + ae835aeac72e1573b8a0225dd56d157a010846d888dac7464baf53f2ad4e3d584531fa203658fab0 + 3a06c9fd5e35737e417bc28c1cbf5e5dfc666de7090f69c3b29754725f84f75382891c561040ea1d + dc0d8f381ed1b9d0d4ad2a0ec021421d847820d6fa0ba66eaf58175f1b235e851c7e2124069fbc20 + 2888ddb3ac4d56bcbd1b9b7eab59e78f2e2d400905050f4a92dec1c4bdf797b3fc9b2f8e84a482f3 + d800386186712dae00d5c386ec9387a5e9c9a1aca5a573ca91082c7d68421f388e79127a5177d4f8 + 590237364fd348c9611fa39f78dcdceee3f390f07991b7b47e1daa3ebcb6ccc9607811cb17ce51f1 + c8c2c5098dbdd28fca547b3f58c01a424ac05f869f49c6a34672ea2cbbc558428aa1fe48bbfd6115 + 8b1b735a65d99f21e70dbc020bfdface9f724a0d1fb5895db971cc81aa7608baa0920abb0a565c9c + 436e2fd13323428296c86385f2384e408a31e104670df0791d93e743a3a5194ee6b076fb6323ca59 + 3011b7348c16cf58f66b9633906ba54a2ee803187344b394f75dd2e663a57b956cb830dd7a908d4f + 39a2336a61ef9fda549180d4ccde21514d117b6c6fd07a9102b5efe710a32af4eeacae2cb3b1dec0 + 35b9593b48b9d3ca4c13d245d5f04169b0b1 + `, + wantVersion: 57, + wantRest: []rlp.RawValue{{0x06}, {0xC2, 0x07, 0x08}, {0x81, 0xFA}}, + }, +} + +var ( + keyA, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + keyB, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") +) + +func TestHandshakeForwardCompatibility(t *testing.T) { + var ( + pubA = crypto.FromECDSAPub(&keyA.PublicKey)[1:] + pubB = crypto.FromECDSAPub(&keyB.PublicKey)[1:] + ephA, _ = crypto.HexToECDSA("869d6ecf5211f1cc60418a13b9d870b22959d0c16f02bec714c960dd2298a32d") + ephB, _ = crypto.HexToECDSA("e238eb8e04fee6511ab04c6dd3c89ce097b11f25d584863ac2b6d5b35b1847e4") + ephPubA = crypto.FromECDSAPub(&ephA.PublicKey)[1:] + ephPubB = crypto.FromECDSAPub(&ephB.PublicKey)[1:] + nonceA = unhex("7e968bba13b6c50e2c4cd7f241cc0d64d1ac25c7f5952df231ac6a2bda8ee5d6") + nonceB = unhex("559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd") + _, _, _, _ = pubA, pubB, ephPubA, ephPubB + authSignature = unhex("299ca6acfd35e3d72d8ba3d1e2b60b5561d5af5218eb5bc182045769eb4226910a301acae3b369fffc4a4899d6b02531e89fd4fe36a2cf0d93607ba470b50f7800") + _ = authSignature + ) + makeAuth := func(test handshakeAuthTest) *authMsgV4 { + msg := &authMsgV4{Version: test.wantVersion, Rest: test.wantRest} + copy(msg.Signature[:], authSignature) + copy(msg.InitiatorPubkey[:], pubA) + copy(msg.Nonce[:], nonceA) + return msg + } + makeAck := func(test handshakeAckTest) *authRespV4 { + msg := &authRespV4{Version: test.wantVersion, Rest: test.wantRest} + copy(msg.RandomPubkey[:], ephPubB) + copy(msg.Nonce[:], nonceB) + return msg + } + + // check auth msg parsing + for _, test := range eip8HandshakeAuthTests { + var h handshakeState + r := bytes.NewReader(unhex(test.input)) + msg := new(authMsgV4) + ciphertext, err := h.readMsg(msg, keyB, r) + if err != nil { + t.Errorf("error for input %x:\n %v", unhex(test.input), err) + continue + } + if !bytes.Equal(ciphertext, unhex(test.input)) { + t.Errorf("wrong ciphertext for input %x:\n %x", unhex(test.input), ciphertext) + } + want := makeAuth(test) + if !reflect.DeepEqual(msg, want) { + t.Errorf("wrong msg for input %x:\ngot %s\nwant %s", unhex(test.input), spew.Sdump(msg), spew.Sdump(want)) + } + } + + // check auth resp parsing + for _, test := range eip8HandshakeRespTests { + var h handshakeState + input := unhex(test.input) + r := bytes.NewReader(input) + msg := new(authRespV4) + ciphertext, err := h.readMsg(msg, keyA, r) + if err != nil { + t.Errorf("error for input %x:\n %v", input, err) + continue + } + if !bytes.Equal(ciphertext, input) { + t.Errorf("wrong ciphertext for input %x:\n %x", input, err) + } + want := makeAck(test) + if !reflect.DeepEqual(msg, want) { + t.Errorf("wrong msg for input %x:\ngot %s\nwant %s", input, spew.Sdump(msg), spew.Sdump(want)) + } + } + + // check derivation for (Auth₂, Ack₂) on recipient side + var ( + hs = &handshakeState{ + initiator: false, + respNonce: nonceB, + randomPrivKey: ecies.ImportECDSA(ephB), + } + authCiphertext = unhex(eip8HandshakeAuthTests[0].input) + authRespCiphertext = unhex(eip8HandshakeRespTests[0].input) + authMsg = makeAuth(eip8HandshakeAuthTests[0]) + wantAES = unhex("80e8632c05fed6fc2a13b0f8d31a3cf645366239170ea067065aba8e28bac487") + wantMAC = unhex("2ea74ec5dae199227dff1af715362700e989d889d7a493cb0639691efb8e5f98") + wantFooIngressHash = unhex("0c7ec6340062cc46f5e9f1e3cf86f8c8c403c5a0964f5df0ebd34a75ddc86db5") + ) + if err := hs.handleAuthMsg(authMsg, keyB); err != nil { + t.Fatalf("handleAuthMsg: %v", err) + } + derived, err := hs.secrets(authCiphertext, authRespCiphertext) + if err != nil { + t.Fatalf("secrets: %v", err) + } + if !bytes.Equal(derived.AES, wantAES) { + t.Errorf("aes-secret mismatch:\ngot %x\nwant %x", derived.AES, wantAES) + } + if !bytes.Equal(derived.MAC, wantMAC) { + t.Errorf("mac-secret mismatch:\ngot %x\nwant %x", derived.MAC, wantMAC) + } + io.WriteString(derived.IngressMAC, "foo") + fooIngressHash := derived.IngressMAC.Sum(nil) + if !bytes.Equal(fooIngressHash, wantFooIngressHash) { + t.Errorf("ingress-mac('foo') mismatch:\ngot %x\nwant %x", fooIngressHash, wantFooIngressHash) + } +} + +func BenchmarkHandshakeRead(b *testing.B) { + var input = unhex(eip8HandshakeAuthTests[0].input) + + for i := 0; i < b.N; i++ { + var ( + h handshakeState + r = bytes.NewReader(input) + msg = new(authMsgV4) + ) + if _, err := h.readMsg(msg, keyB, r); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkThroughput(b *testing.B) { + pipe1, pipe2, err := pipes.TCPPipe() + if err != nil { + b.Fatal(err) + } + + var ( + conn1, conn2 = NewConn(pipe1, nil), NewConn(pipe2, &keyA.PublicKey) + handshakeDone = make(chan error, 1) + msgdata = make([]byte, 1024) + rand = rand.New(rand.NewSource(1337)) + ) + rand.Read(msgdata) + + // Server side. + go func() { + defer conn1.Close() + // Perform handshake. + _, err := conn1.Handshake(keyA) + handshakeDone <- err + if err != nil { + return + } + conn1.SetSnappy(true) + // Keep sending messages until connection closed. + for { + if _, err := conn1.Write(0, msgdata); err != nil { + return + } + } + }() + + // Set up client side. + defer conn2.Close() + if _, err := conn2.Handshake(keyB); err != nil { + b.Fatal("client handshake error:", err) + } + conn2.SetSnappy(true) + if err := <-handshakeDone; err != nil { + b.Fatal("server hanshake error:", err) + } + + // Read N messages. + b.SetBytes(int64(len(msgdata))) + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _, _, err := conn2.Read() + if err != nil { + b.Fatal("read error:", err) + } + } +} + +func unhex(str string) []byte { + r := strings.NewReplacer("\t", "", " ", "", "\n", "") + b, err := hex.DecodeString(r.Replace(str)) + if err != nil { + panic(fmt.Sprintf("invalid hex string: %q", str)) + } + return b +} + +func newkey() *ecdsa.PrivateKey { + key, err := crypto.GenerateKey() + if err != nil { + panic("couldn't generate key: " + err.Error()) + } + return key +} diff --git a/p2p/rlpx_test.go b/p2p/rlpx_test.go deleted file mode 100644 index a874627de0..0000000000 --- a/p2p/rlpx_test.go +++ /dev/null @@ -1,603 +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 . - -package p2p - -import ( - "bytes" - "crypto/ecdsa" - "crypto/rand" - "errors" - "fmt" - "io" - "io/ioutil" - "net" - "reflect" - "strings" - "sync" - "testing" - "time" - - "github.com/davecgh/go-spew/spew" - - "golang.org/x/crypto/sha3" - - "github.com/AlayaNetwork/Alaya-Go/crypto" - "github.com/AlayaNetwork/Alaya-Go/crypto/ecies" - "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/pipes" - "github.com/AlayaNetwork/Alaya-Go/rlp" -) - -func TestSharedSecret(t *testing.T) { - prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader) - pub0 := &prv0.PublicKey - prv1, _ := crypto.GenerateKey() - pub1 := &prv1.PublicKey - - ss0, err := ecies.ImportECDSA(prv0).GenerateShared(ecies.ImportECDSAPublic(pub1), sskLen, sskLen) - if err != nil { - return - } - ss1, err := ecies.ImportECDSA(prv1).GenerateShared(ecies.ImportECDSAPublic(pub0), sskLen, sskLen) - if err != nil { - return - } - t.Logf("Secret:\n%v %x\n%v %x", len(ss0), ss0, len(ss0), ss1) - if !bytes.Equal(ss0, ss1) { - t.Errorf("dont match :(") - } -} - -func TestEncHandshake(t *testing.T) { - for i := 0; i < 10; i++ { - start := time.Now() - if err := testEncHandshake(nil); err != nil { - t.Fatalf("i=%d %v", i, err) - } - t.Logf("(without token) %d %v\n", i+1, time.Since(start)) - } - for i := 0; i < 10; i++ { - tok := make([]byte, shaLen) - rand.Reader.Read(tok) - start := time.Now() - if err := testEncHandshake(tok); err != nil { - t.Fatalf("i=%d %v", i, err) - } - t.Logf("(with token) %d %v\n", i+1, time.Since(start)) - } -} - -func testEncHandshake(token []byte) error { - type result struct { - side string - pubkey *ecdsa.PublicKey - err error - } - var ( - prv0, _ = crypto.GenerateKey() - prv1, _ = crypto.GenerateKey() - fd0, fd1 = net.Pipe() - c0, c1 = newRLPX(fd0).(*rlpx), newRLPX(fd1).(*rlpx) - output = make(chan result) - ) - - go func() { - r := result{side: "initiator"} - defer func() { output <- r }() - defer fd0.Close() - - r.pubkey, r.err = c0.doEncHandshake(prv0, &prv1.PublicKey) - if r.err != nil { - return - } - if !reflect.DeepEqual(r.pubkey, &prv1.PublicKey) { - r.err = fmt.Errorf("remote pubkey mismatch: got %v, want: %v", r.pubkey, &prv1.PublicKey) - } - }() - go func() { - r := result{side: "receiver"} - defer func() { output <- r }() - defer fd1.Close() - - r.pubkey, r.err = c1.doEncHandshake(prv1, nil) - if r.err != nil { - return - } - if !reflect.DeepEqual(r.pubkey, &prv0.PublicKey) { - r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.pubkey, &prv0.PublicKey) - } - }() - - // wait for results from both sides - r1, r2 := <-output, <-output - if r1.err != nil { - return fmt.Errorf("%s side error: %v", r1.side, r1.err) - } - if r2.err != nil { - return fmt.Errorf("%s side error: %v", r2.side, r2.err) - } - - // compare derived secrets - if !reflect.DeepEqual(c0.rw.egressMAC, c1.rw.ingressMAC) { - return fmt.Errorf("egress mac mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.egressMAC, c1.rw.ingressMAC) - } - if !reflect.DeepEqual(c0.rw.ingressMAC, c1.rw.egressMAC) { - return fmt.Errorf("ingress mac mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.ingressMAC, c1.rw.egressMAC) - } - if !reflect.DeepEqual(c0.rw.enc, c1.rw.enc) { - return fmt.Errorf("enc cipher mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.enc, c1.rw.enc) - } - if !reflect.DeepEqual(c0.rw.dec, c1.rw.dec) { - return fmt.Errorf("dec cipher mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.dec, c1.rw.dec) - } - return nil -} - -func TestProtocolHandshake(t *testing.T) { - var ( - prv0, _ = crypto.GenerateKey() - pub0 = crypto.FromECDSAPub(&prv0.PublicKey)[1:] - hs0 = &protoHandshake{Version: 3, ID: pub0, Caps: []Cap{{"a", 0}, {"b", 2}}} - - prv1, _ = crypto.GenerateKey() - pub1 = crypto.FromECDSAPub(&prv1.PublicKey)[1:] - hs1 = &protoHandshake{Version: 3, ID: pub1, Caps: []Cap{{"c", 1}, {"d", 3}}} - - wg sync.WaitGroup - ) - - fd0, fd1, err := pipes.TCPPipe() - if err != nil { - t.Fatal(err) - } - - wg.Add(2) - go func() { - defer wg.Done() - defer fd0.Close() - rlpx := newRLPX(fd0) - rpubkey, err := rlpx.doEncHandshake(prv0, &prv1.PublicKey) - if err != nil { - t.Errorf("dial side enc handshake failed: %v", err) - return - } - if !reflect.DeepEqual(rpubkey, &prv1.PublicKey) { - t.Errorf("dial side remote pubkey mismatch: got %v, want %v", rpubkey, &prv1.PublicKey) - return - } - - phs, err := rlpx.doProtoHandshake(hs0) - if err != nil { - t.Errorf("dial side proto handshake error: %v", err) - return - } - phs.Rest = nil - if !reflect.DeepEqual(phs, hs1) { - t.Errorf("dial side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs1)) - return - } - rlpx.close(DiscQuitting) - }() - go func() { - defer wg.Done() - defer fd1.Close() - rlpx := newRLPX(fd1) - rpubkey, err := rlpx.doEncHandshake(prv1, nil) - if err != nil { - t.Errorf("listen side enc handshake failed: %v", err) - return - } - if !reflect.DeepEqual(rpubkey, &prv0.PublicKey) { - t.Errorf("listen side remote pubkey mismatch: got %v, want %v", rpubkey, &prv0.PublicKey) - return - } - - phs, err := rlpx.doProtoHandshake(hs1) - if err != nil { - t.Errorf("listen side proto handshake error: %v", err) - return - } - phs.Rest = nil - if !reflect.DeepEqual(phs, hs0) { - t.Errorf("listen side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs0)) - return - } - - if err := ExpectMsg(rlpx, discMsg, []DiscReason{DiscQuitting}); err != nil { - t.Errorf("error receiving disconnect: %v", err) - } - }() - wg.Wait() -} - -func TestProtocolHandshakeErrors(t *testing.T) { - our := &protoHandshake{Version: 3, Caps: []Cap{{"foo", 2}, {"bar", 3}}, Name: "quux"} - tests := []struct { - code uint64 - msg interface{} - err error - }{ - { - code: discMsg, - msg: []DiscReason{DiscQuitting}, - err: DiscQuitting, - }, - { - code: 0x989898, - msg: []byte{1}, - err: errors.New("expected handshake, got 989898"), - }, - { - code: handshakeMsg, - msg: make([]byte, baseProtocolMaxMsgSize+2), - err: errors.New("message too big"), - }, - { - code: handshakeMsg, - msg: []byte{1, 2, 3}, - err: newPeerError(errInvalidMsg, "(code 0) (size 4) rlp: expected input list for p2p.protoHandshake"), - }, - { - code: handshakeMsg, - msg: &protoHandshake{Version: 3}, - err: DiscInvalidIdentity, - }, - } - - for i, test := range tests { - p1, p2 := MsgPipe() - go Send(p1, test.code, test.msg) - _, err := readProtocolHandshake(p2, our) - if !reflect.DeepEqual(err, test.err) { - t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err) - } - } -} - -func TestRLPXFrameFake(t *testing.T) { - buf := new(bytes.Buffer) - hash := fakeHash([]byte{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}) - rw := newRLPXFrameRW(buf, secrets{ - AES: crypto.Keccak256(), - MAC: crypto.Keccak256(), - IngressMAC: hash, - EgressMAC: hash, - }) - - golden := unhex(` -00828ddae471818bb0bfa6b551d1cb42 -01010101010101010101010101010101 -ba628a4ba590cb43f7848f41c4382885 -01010101010101010101010101010101 -`) - - // Check WriteMsg. This puts a message into the buffer. - if err := Send(rw, 8, []uint{1, 2, 3, 4}); err != nil { - t.Fatalf("WriteMsg error: %v", err) - } - written := buf.Bytes() - if !bytes.Equal(written, golden) { - t.Fatalf("output mismatch:\n got: %x\n want: %x", written, golden) - } - - // Check ReadMsg. It reads the message encoded by WriteMsg, which - // is equivalent to the golden message above. - msg, err := rw.ReadMsg() - if err != nil { - t.Fatalf("ReadMsg error: %v", err) - } - if msg.Size != 5 { - t.Errorf("msg size mismatch: got %d, want %d", msg.Size, 5) - } - if msg.Code != 8 { - t.Errorf("msg code mismatch: got %d, want %d", msg.Code, 8) - } - payload, _ := ioutil.ReadAll(msg.Payload) - wantPayload := unhex("C401020304") - if !bytes.Equal(payload, wantPayload) { - t.Errorf("msg payload mismatch:\ngot %x\nwant %x", payload, wantPayload) - } -} - -type fakeHash []byte - -func (fakeHash) Write(p []byte) (int, error) { return len(p), nil } -func (fakeHash) Reset() {} -func (fakeHash) BlockSize() int { return 0 } - -func (h fakeHash) Size() int { return len(h) } -func (h fakeHash) Sum(b []byte) []byte { return append(b, h...) } - -func TestRLPXFrameRW(t *testing.T) { - var ( - aesSecret = make([]byte, 16) - macSecret = make([]byte, 16) - egressMACinit = make([]byte, 32) - ingressMACinit = make([]byte, 32) - ) - for _, s := range [][]byte{aesSecret, macSecret, egressMACinit, ingressMACinit} { - rand.Read(s) - } - conn := new(bytes.Buffer) - - s1 := secrets{ - AES: aesSecret, - MAC: macSecret, - EgressMAC: sha3.NewLegacyKeccak256(), - IngressMAC: sha3.NewLegacyKeccak256(), - } - s1.EgressMAC.Write(egressMACinit) - s1.IngressMAC.Write(ingressMACinit) - rw1 := newRLPXFrameRW(conn, s1) - - s2 := secrets{ - AES: aesSecret, - MAC: macSecret, - EgressMAC: sha3.NewLegacyKeccak256(), - IngressMAC: sha3.NewLegacyKeccak256(), - } - s2.EgressMAC.Write(ingressMACinit) - s2.IngressMAC.Write(egressMACinit) - rw2 := newRLPXFrameRW(conn, s2) - - // send some messages - for i := 0; i < 10; i++ { - // write message into conn buffer - wmsg := []interface{}{"foo", "bar", strings.Repeat("test", i)} - err := Send(rw1, uint64(i), wmsg) - if err != nil { - t.Fatalf("WriteMsg error (i=%d): %v", i, err) - } - - // read message that rw1 just wrote - msg, err := rw2.ReadMsg() - if err != nil { - t.Fatalf("ReadMsg error (i=%d): %v", i, err) - } - if msg.Code != uint64(i) { - t.Fatalf("msg code mismatch: got %d, want %d", msg.Code, i) - } - payload, _ := ioutil.ReadAll(msg.Payload) - wantPayload, _ := rlp.EncodeToBytes(wmsg) - if !bytes.Equal(payload, wantPayload) { - t.Fatalf("msg payload mismatch:\ngot %x\nwant %x", payload, wantPayload) - } - } -} - -type handshakeAuthTest struct { - input string - isPlain bool - wantVersion uint - wantRest []rlp.RawValue -} - -var eip8HandshakeAuthTests = []handshakeAuthTest{ - // (Auth₁) RLPx v4 plain encoding - { - input: ` - 048ca79ad18e4b0659fab4853fe5bc58eb83992980f4c9cc147d2aa31532efd29a3d3dc6a3d89eaf - 913150cfc777ce0ce4af2758bf4810235f6e6ceccfee1acc6b22c005e9e3a49d6448610a58e98744 - ba3ac0399e82692d67c1f58849050b3024e21a52c9d3b01d871ff5f210817912773e610443a9ef14 - 2e91cdba0bd77b5fdf0769b05671fc35f83d83e4d3b0b000c6b2a1b1bba89e0fc51bf4e460df3105 - c444f14be226458940d6061c296350937ffd5e3acaceeaaefd3c6f74be8e23e0f45163cc7ebd7622 - 0f0128410fd05250273156d548a414444ae2f7dea4dfca2d43c057adb701a715bf59f6fb66b2d1d2 - 0f2c703f851cbf5ac47396d9ca65b6260bd141ac4d53e2de585a73d1750780db4c9ee4cd4d225173 - a4592ee77e2bd94d0be3691f3b406f9bba9b591fc63facc016bfa8 - `, - isPlain: true, - wantVersion: 4, - }, - // (Auth₂) EIP-8 encoding - { - input: ` - 01b304ab7578555167be8154d5cc456f567d5ba302662433674222360f08d5f1534499d3678b513b - 0fca474f3a514b18e75683032eb63fccb16c156dc6eb2c0b1593f0d84ac74f6e475f1b8d56116b84 - 9634a8c458705bf83a626ea0384d4d7341aae591fae42ce6bd5c850bfe0b999a694a49bbbaf3ef6c - da61110601d3b4c02ab6c30437257a6e0117792631a4b47c1d52fc0f8f89caadeb7d02770bf999cc - 147d2df3b62e1ffb2c9d8c125a3984865356266bca11ce7d3a688663a51d82defaa8aad69da39ab6 - d5470e81ec5f2a7a47fb865ff7cca21516f9299a07b1bc63ba56c7a1a892112841ca44b6e0034dee - 70c9adabc15d76a54f443593fafdc3b27af8059703f88928e199cb122362a4b35f62386da7caad09 - c001edaeb5f8a06d2b26fb6cb93c52a9fca51853b68193916982358fe1e5369e249875bb8d0d0ec3 - 6f917bc5e1eafd5896d46bd61ff23f1a863a8a8dcd54c7b109b771c8e61ec9c8908c733c0263440e - 2aa067241aaa433f0bb053c7b31a838504b148f570c0ad62837129e547678c5190341e4f1693956c - 3bf7678318e2d5b5340c9e488eefea198576344afbdf66db5f51204a6961a63ce072c8926c - `, - wantVersion: 4, - wantRest: []rlp.RawValue{}, - }, - // (Auth₃) RLPx v4 EIP-8 encoding with version 56, additional list elements - { - input: ` - 01b8044c6c312173685d1edd268aa95e1d495474c6959bcdd10067ba4c9013df9e40ff45f5bfd6f7 - 2471f93a91b493f8e00abc4b80f682973de715d77ba3a005a242eb859f9a211d93a347fa64b597bf - 280a6b88e26299cf263b01b8dfdb712278464fd1c25840b995e84d367d743f66c0e54a586725b7bb - f12acca27170ae3283c1073adda4b6d79f27656993aefccf16e0d0409fe07db2dc398a1b7e8ee93b - cd181485fd332f381d6a050fba4c7641a5112ac1b0b61168d20f01b479e19adf7fdbfa0905f63352 - bfc7e23cf3357657455119d879c78d3cf8c8c06375f3f7d4861aa02a122467e069acaf513025ff19 - 6641f6d2810ce493f51bee9c966b15c5043505350392b57645385a18c78f14669cc4d960446c1757 - 1b7c5d725021babbcd786957f3d17089c084907bda22c2b2675b4378b114c601d858802a55345a15 - 116bc61da4193996187ed70d16730e9ae6b3bb8787ebcaea1871d850997ddc08b4f4ea668fbf3740 - 7ac044b55be0908ecb94d4ed172ece66fd31bfdadf2b97a8bc690163ee11f5b575a4b44e36e2bfb2 - f0fce91676fd64c7773bac6a003f481fddd0bae0a1f31aa27504e2a533af4cef3b623f4791b2cca6 - d490 - `, - wantVersion: 56, - wantRest: []rlp.RawValue{{0x01}, {0x02}, {0xC2, 0x04, 0x05}}, - }, -} - -type handshakeAckTest struct { - input string - wantVersion uint - wantRest []rlp.RawValue -} - -var eip8HandshakeRespTests = []handshakeAckTest{ - // (Ack₁) RLPx v4 plain encoding - { - input: ` - 049f8abcfa9c0dc65b982e98af921bc0ba6e4243169348a236abe9df5f93aa69d99cadddaa387662 - b0ff2c08e9006d5a11a278b1b3331e5aaabf0a32f01281b6f4ede0e09a2d5f585b26513cb794d963 - 5a57563921c04a9090b4f14ee42be1a5461049af4ea7a7f49bf4c97a352d39c8d02ee4acc416388c - 1c66cec761d2bc1c72da6ba143477f049c9d2dde846c252c111b904f630ac98e51609b3b1f58168d - dca6505b7196532e5f85b259a20c45e1979491683fee108e9660edbf38f3add489ae73e3dda2c71b - d1497113d5c755e942d1 - `, - wantVersion: 4, - }, - // (Ack₂) EIP-8 encoding - { - input: ` - 01ea0451958701280a56482929d3b0757da8f7fbe5286784beead59d95089c217c9b917788989470 - b0e330cc6e4fb383c0340ed85fab836ec9fb8a49672712aeabbdfd1e837c1ff4cace34311cd7f4de - 05d59279e3524ab26ef753a0095637ac88f2b499b9914b5f64e143eae548a1066e14cd2f4bd7f814 - c4652f11b254f8a2d0191e2f5546fae6055694aed14d906df79ad3b407d94692694e259191cde171 - ad542fc588fa2b7333313d82a9f887332f1dfc36cea03f831cb9a23fea05b33deb999e85489e645f - 6aab1872475d488d7bd6c7c120caf28dbfc5d6833888155ed69d34dbdc39c1f299be1057810f34fb - e754d021bfca14dc989753d61c413d261934e1a9c67ee060a25eefb54e81a4d14baff922180c395d - 3f998d70f46f6b58306f969627ae364497e73fc27f6d17ae45a413d322cb8814276be6ddd13b885b - 201b943213656cde498fa0e9ddc8e0b8f8a53824fbd82254f3e2c17e8eaea009c38b4aa0a3f306e8 - 797db43c25d68e86f262e564086f59a2fc60511c42abfb3057c247a8a8fe4fb3ccbadde17514b7ac - 8000cdb6a912778426260c47f38919a91f25f4b5ffb455d6aaaf150f7e5529c100ce62d6d92826a7 - 1778d809bdf60232ae21ce8a437eca8223f45ac37f6487452ce626f549b3b5fdee26afd2072e4bc7 - 5833c2464c805246155289f4 - `, - wantVersion: 4, - wantRest: []rlp.RawValue{}, - }, - // (Ack₃) EIP-8 encoding with version 57, additional list elements - { - input: ` - 01f004076e58aae772bb101ab1a8e64e01ee96e64857ce82b1113817c6cdd52c09d26f7b90981cd7 - ae835aeac72e1573b8a0225dd56d157a010846d888dac7464baf53f2ad4e3d584531fa203658fab0 - 3a06c9fd5e35737e417bc28c1cbf5e5dfc666de7090f69c3b29754725f84f75382891c561040ea1d - dc0d8f381ed1b9d0d4ad2a0ec021421d847820d6fa0ba66eaf58175f1b235e851c7e2124069fbc20 - 2888ddb3ac4d56bcbd1b9b7eab59e78f2e2d400905050f4a92dec1c4bdf797b3fc9b2f8e84a482f3 - d800386186712dae00d5c386ec9387a5e9c9a1aca5a573ca91082c7d68421f388e79127a5177d4f8 - 590237364fd348c9611fa39f78dcdceee3f390f07991b7b47e1daa3ebcb6ccc9607811cb17ce51f1 - c8c2c5098dbdd28fca547b3f58c01a424ac05f869f49c6a34672ea2cbbc558428aa1fe48bbfd6115 - 8b1b735a65d99f21e70dbc020bfdface9f724a0d1fb5895db971cc81aa7608baa0920abb0a565c9c - 436e2fd13323428296c86385f2384e408a31e104670df0791d93e743a3a5194ee6b076fb6323ca59 - 3011b7348c16cf58f66b9633906ba54a2ee803187344b394f75dd2e663a57b956cb830dd7a908d4f - 39a2336a61ef9fda549180d4ccde21514d117b6c6fd07a9102b5efe710a32af4eeacae2cb3b1dec0 - 35b9593b48b9d3ca4c13d245d5f04169b0b1 - `, - wantVersion: 57, - wantRest: []rlp.RawValue{{0x06}, {0xC2, 0x07, 0x08}, {0x81, 0xFA}}, - }, -} - -func TestHandshakeForwardCompatibility(t *testing.T) { - var ( - keyA, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") - keyB, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - pubA = crypto.FromECDSAPub(&keyA.PublicKey)[1:] - pubB = crypto.FromECDSAPub(&keyB.PublicKey)[1:] - ephA, _ = crypto.HexToECDSA("869d6ecf5211f1cc60418a13b9d870b22959d0c16f02bec714c960dd2298a32d") - ephB, _ = crypto.HexToECDSA("e238eb8e04fee6511ab04c6dd3c89ce097b11f25d584863ac2b6d5b35b1847e4") - ephPubA = crypto.FromECDSAPub(&ephA.PublicKey)[1:] - ephPubB = crypto.FromECDSAPub(&ephB.PublicKey)[1:] - nonceA = unhex("7e968bba13b6c50e2c4cd7f241cc0d64d1ac25c7f5952df231ac6a2bda8ee5d6") - nonceB = unhex("559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd") - _, _, _, _ = pubA, pubB, ephPubA, ephPubB - authSignature = unhex("299ca6acfd35e3d72d8ba3d1e2b60b5561d5af5218eb5bc182045769eb4226910a301acae3b369fffc4a4899d6b02531e89fd4fe36a2cf0d93607ba470b50f7800") - _ = authSignature - ) - makeAuth := func(test handshakeAuthTest) *authMsgV4 { - msg := &authMsgV4{Version: test.wantVersion, Rest: test.wantRest, gotPlain: test.isPlain} - copy(msg.Signature[:], authSignature) - copy(msg.InitiatorPubkey[:], pubA) - copy(msg.Nonce[:], nonceA) - return msg - } - makeAck := func(test handshakeAckTest) *authRespV4 { - msg := &authRespV4{Version: test.wantVersion, Rest: test.wantRest} - copy(msg.RandomPubkey[:], ephPubB) - copy(msg.Nonce[:], nonceB) - return msg - } - - // check auth msg parsing - for _, test := range eip8HandshakeAuthTests { - r := bytes.NewReader(unhex(test.input)) - msg := new(authMsgV4) - ciphertext, err := readHandshakeMsg(msg, encAuthMsgLen, keyB, r) - if err != nil { - t.Errorf("error for input %x:\n %v", unhex(test.input), err) - continue - } - if !bytes.Equal(ciphertext, unhex(test.input)) { - t.Errorf("wrong ciphertext for input %x:\n %x", unhex(test.input), ciphertext) - } - want := makeAuth(test) - if !reflect.DeepEqual(msg, want) { - t.Errorf("wrong msg for input %x:\ngot %s\nwant %s", unhex(test.input), spew.Sdump(msg), spew.Sdump(want)) - } - } - - // check auth resp parsing - for _, test := range eip8HandshakeRespTests { - input := unhex(test.input) - r := bytes.NewReader(input) - msg := new(authRespV4) - ciphertext, err := readHandshakeMsg(msg, encAuthRespLen, keyA, r) - if err != nil { - t.Errorf("error for input %x:\n %v", input, err) - continue - } - if !bytes.Equal(ciphertext, input) { - t.Errorf("wrong ciphertext for input %x:\n %x", input, err) - } - want := makeAck(test) - if !reflect.DeepEqual(msg, want) { - t.Errorf("wrong msg for input %x:\ngot %s\nwant %s", input, spew.Sdump(msg), spew.Sdump(want)) - } - } - - // check derivation for (Auth₂, Ack₂) on recipient side - var ( - hs = &encHandshake{ - initiator: false, - respNonce: nonceB, - randomPrivKey: ecies.ImportECDSA(ephB), - } - authCiphertext = unhex(eip8HandshakeAuthTests[1].input) - authRespCiphertext = unhex(eip8HandshakeRespTests[1].input) - authMsg = makeAuth(eip8HandshakeAuthTests[1]) - wantAES = unhex("80e8632c05fed6fc2a13b0f8d31a3cf645366239170ea067065aba8e28bac487") - wantMAC = unhex("2ea74ec5dae199227dff1af715362700e989d889d7a493cb0639691efb8e5f98") - wantFooIngressHash = unhex("0c7ec6340062cc46f5e9f1e3cf86f8c8c403c5a0964f5df0ebd34a75ddc86db5") - ) - if err := hs.handleAuthMsg(authMsg, keyB); err != nil { - t.Fatalf("handleAuthMsg: %v", err) - } - derived, err := hs.secrets(authCiphertext, authRespCiphertext) - if err != nil { - t.Fatalf("secrets: %v", err) - } - if !bytes.Equal(derived.AES, wantAES) { - t.Errorf("aes-secret mismatch:\ngot %x\nwant %x", derived.AES, wantAES) - } - if !bytes.Equal(derived.MAC, wantMAC) { - t.Errorf("mac-secret mismatch:\ngot %x\nwant %x", derived.MAC, wantMAC) - } - io.WriteString(derived.IngressMAC, "foo") - fooIngressHash := derived.IngressMAC.Sum(nil) - if !bytes.Equal(fooIngressHash, wantFooIngressHash) { - t.Errorf("ingress-mac('foo') mismatch:\ngot %x\nwant %x", fooIngressHash, wantFooIngressHash) - } -} diff --git a/p2p/server.go b/p2p/server.go index e171c593d1..bfbe878fa4 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -31,7 +31,6 @@ import ( "time" "github.com/AlayaNetwork/Alaya-Go/p2p/enr" - "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" @@ -43,7 +42,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p/discover" - "github.com/AlayaNetwork/Alaya-Go/p2p/discv5" "github.com/AlayaNetwork/Alaya-Go/p2p/nat" "github.com/AlayaNetwork/Alaya-Go/p2p/netutil" ) @@ -51,11 +49,18 @@ import ( const ( defaultDialTimeout = 15 * time.Second + // This is the fairness knob for the discovery mixer. When looking for peers, we'll + // wait this long for a single source of candidates before moving on and trying other + // sources. + discmixTimeout = 5 * time.Second + // Connectivity defaults. - maxActiveDialTasks = 16 defaultMaxPendingPeers = 50 defaultDialRatio = 3 + // This time limits inbound connection attempts per source IP. + inboundThrottleTime = 30 * time.Second + // Maximum time allowed for reading a complete message. // This is effectively the amount of time a connection can be idle. frameReadTimeout = 30 * time.Second @@ -114,7 +119,7 @@ type Config struct { // BootstrapNodesV5 are used to establish connectivity // with the rest of the network using the V5 discovery // protocol. - BootstrapNodesV5 []*discv5.Node `toml:",omitempty"` + BootstrapNodesV5 []*enode.Node `toml:",omitempty"` // Static nodes are used as pre-configured connections which are always // maintained and re-connected on disconnects. @@ -164,6 +169,8 @@ type Config struct { // Logger is a custom logger to use with the p2p.Server. Logger log.Logger `toml:",omitempty"` + + clock mclock.Clock } // Server manages all peer connections. @@ -173,40 +180,43 @@ type Server struct { // Hooks for testing. These are useful because we can inhibit // the whole protocol stack. - newTransport func(net.Conn) transport + newTransport func(net.Conn, *ecdsa.PublicKey) transport newPeerHook func(*Peer) + listenFunc func(network, addr string) (net.Listener, error) lock sync.Mutex // protects running running bool - nodedb *enode.DB - localnode *enode.LocalNode - ntab discoverTable listener net.Listener ourHandshake *protoHandshake - lastLookup time.Time - DiscV5 *discv5.Network - - // These are for Peers, PeerCount (and nothing else). - peerOp chan peerOpFunc - peerOpDone chan struct{} - - quit chan struct{} - addstatic chan *enode.Node - removestatic chan *enode.Node + loopWG sync.WaitGroup // loop, listenLoop + peerFeed event.Feed + log log.Logger + + nodedb *enode.DB + localnode *enode.LocalNode + ntab *discover.UDPv4 + DiscV5 *discover.UDPv5 + discmix *enode.FairMix + dialsched *dialScheduler + + // Channels into the run loop. + quit chan struct{} + addtrusted chan *enode.Node + removetrusted chan *enode.Node + peerOp chan peerOpFunc + peerOpDone chan struct{} + delpeer chan peerDrop + checkpointPostHandshake chan *conn + checkpointAddPeer chan *conn + + // State of run loop and listenLoop. + inboundHistory expHeap + + eventMux *event.TypeMux + consensus bool addconsensus chan *enode.Node removeconsensus chan *enode.Node - addtrusted chan *enode.Node - removetrusted chan *enode.Node - posthandshake chan *conn - addpeer chan *conn - delpeer chan peerDrop - loopWG sync.WaitGroup // loop, listenLoop - peerFeed event.Feed - log log.Logger - - eventMux *event.TypeMux - consensus bool } type peerOpFunc func(map[enode.ID]*Peer) @@ -241,7 +251,7 @@ type conn struct { type transport interface { // The two handshakes. - doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) + doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) // The MsgReadWriter can only be used after the encryption // handshake has completed. The code uses conn.id to track this @@ -305,50 +315,65 @@ func (c *conn) set(f connFlag, val bool) { } } +// LocalNode returns the local node record. +func (srv *Server) LocalNode() *enode.LocalNode { + return srv.localnode +} + // Peers returns all connected peers. func (srv *Server) Peers() []*Peer { var ps []*Peer - select { - // Note: We'd love to put this function into a variable but - // that seems to cause a weird compiler error in some - // environments. - case srv.peerOp <- func(peers map[enode.ID]*Peer) { + srv.doPeerOp(func(peers map[enode.ID]*Peer) { for _, p := range peers { ps = append(ps, p) } - }: - <-srv.peerOpDone - case <-srv.quit: - } + }) return ps } // PeerCount returns the number of connected peers. func (srv *Server) PeerCount() int { var count int - select { - case srv.peerOp <- func(ps map[enode.ID]*Peer) { count = len(ps) }: - <-srv.peerOpDone - case <-srv.quit: - } + srv.doPeerOp(func(ps map[enode.ID]*Peer) { + count = len(ps) + }) return count } -// 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 -// attempt to reconnect the peer. +// AddPeer adds the given node to the static node set. When there is room in the peer set, +// the server will connect to the node. If the connection fails for any reason, the server +// will attempt to reconnect the peer. func (srv *Server) AddPeer(node *enode.Node) { - select { - case srv.addstatic <- node: - case <-srv.quit: - } + srv.dialsched.addStatic(node) } -// RemovePeer disconnects from the given node +// RemovePeer removes a node from the static node set. It also disconnects from the given +// node if it is currently connected as a peer. +// +// This method blocks until all protocols have exited and the peer is removed. Do not use +// RemovePeer in protocol implementations, call Disconnect on the Peer instead. func (srv *Server) RemovePeer(node *enode.Node) { - select { - case srv.removestatic <- node: - case <-srv.quit: + var ( + ch chan *PeerEvent + sub event.Subscription + ) + // Disconnect the peer on the main loop. + srv.doPeerOp(func(peers map[enode.ID]*Peer) { + srv.dialsched.removeStatic(node) + if peer := peers[node.ID()]; peer != nil { + ch = make(chan *PeerEvent, 1) + sub = srv.peerFeed.Subscribe(ch) + peer.Disconnect(DiscRequested) + } + }) + // Wait for the peer connection to end. + if ch != nil { + defer sub.Unsubscribe() + for ev := range ch { + if ev.Peer == node.ID() && ev.Type == PeerEventTypeDrop { + return + } + } } } @@ -356,17 +381,33 @@ func (srv *Server) RemovePeer(node *enode.Node) { // server is shut down. If the connection fails for any reason, the server will // attempt to reconnect the peer. func (srv *Server) AddConsensusPeer(node *enode.Node) { - select { - case srv.addconsensus <- node: - case <-srv.quit: - } + srv.dialsched.addConsensus(node) } // RemoveConsensusPeer disconnects from the given consensus node func (srv *Server) RemoveConsensusPeer(node *enode.Node) { - select { - case srv.removeconsensus <- node: - case <-srv.quit: + var ( + ch chan *PeerEvent + sub event.Subscription + ) + // Disconnect the peer on the main loop. + srv.doPeerOp(func(peers map[enode.ID]*Peer) { + srv.dialsched.removeConsensus(node) + if peer := peers[node.ID()]; peer != nil { + ch = make(chan *PeerEvent, 1) + sub = srv.peerFeed.Subscribe(ch) + peer.Disconnect(DiscRequested) + } + }) + srv.log.Trace("Removing consensus node from dialstate", "node", node) + // Wait for the peer connection to end. + if ch != nil { + defer sub.Unsubscribe() + for ev := range ch { + if ev.Peer == node.ID() && ev.Type == PeerEventTypeDrop { + return + } + } } } @@ -387,7 +428,7 @@ func (srv *Server) RemoveTrustedPeer(node *enode.Node) { } } -// SubscribePeers subscribes the given channel to peer events +// SubscribeEvents subscribes the given channel to peer events func (srv *Server) SubscribeEvents(ch chan *PeerEvent) event.Subscription { return srv.peerFeed.Subscribe(ch) } @@ -429,11 +470,11 @@ type sharedUDPConn struct { unhandled chan discover.ReadPacket } -// ReadFromUDP implements discv5.conn +// ReadFromUDP implements discover.UDPConn func (s *sharedUDPConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) { packet, ok := <-s.unhandled if !ok { - return 0, nil, errors.New("Connection was closed") + return 0, nil, errors.New("connection was closed") } l := len(packet.Data) if l > len(b) { @@ -443,7 +484,7 @@ func (s *sharedUDPConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err err return l, packet.Addr, nil } -// Close implements discv5.conn +// Close implements discover.UDPConn func (s *sharedUDPConn) Close() error { return nil } @@ -459,7 +500,10 @@ func (srv *Server) Start() (err error) { srv.running = true srv.log = srv.Config.Logger if srv.log == nil { - srv.log = log.New() + srv.log = log.Root() + } + if srv.clock == nil { + srv.clock = mclock.System{} } if srv.NoDial && srv.ListenAddr == "" { srv.log.Warn("P2P server will be useless, neither dialing nor listening") @@ -472,17 +516,13 @@ func (srv *Server) Start() (err error) { if srv.newTransport == nil { srv.newTransport = newRLPX } - if srv.Dialer == nil { - srv.Dialer = TCPDialer{&net.Dialer{Timeout: defaultDialTimeout}} + if srv.listenFunc == nil { + srv.listenFunc = net.Listen } srv.quit = make(chan struct{}) - srv.addpeer = make(chan *conn) srv.delpeer = make(chan peerDrop) - srv.posthandshake = make(chan *conn) - srv.addstatic = make(chan *enode.Node) - srv.removestatic = make(chan *enode.Node) - srv.addconsensus = make(chan *enode.Node) - srv.removeconsensus = make(chan *enode.Node) + srv.checkpointPostHandshake = make(chan *conn) + srv.checkpointAddPeer = make(chan *conn) srv.addtrusted = make(chan *enode.Node) srv.removetrusted = make(chan *enode.Node) srv.peerOp = make(chan peerOpFunc) @@ -499,11 +539,10 @@ func (srv *Server) Start() (err error) { if err := srv.setupDiscovery(); err != nil { return err } + srv.setupDialScheduler() - dynPeers := srv.maxDialedConns() - dialer := newDialState(srv.localnode.ID(), srv.StaticNodes, srv.BootstrapNodes, srv.ntab, dynPeers, srv.NetRestrict, srv.MaxConsensusPeers) srv.loopWG.Add(1) - go srv.run(dialer) + go srv.run() return nil } @@ -524,7 +563,6 @@ func (srv *Server) setupLocalNode() error { 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 { @@ -553,6 +591,18 @@ func (srv *Server) setupLocalNode() error { } func (srv *Server) setupDiscovery() error { + srv.discmix = enode.NewFairMix(discmixTimeout) + + // Add protocol-specific discovery sources. + added := make(map[string]bool) + for _, proto := range srv.Protocols { + if proto.DialCandidates != nil && !added[proto.Name] { + srv.discmix.AddSource(proto.DialCandidates) + added[proto.Name] = true + } + } + + // Don't listen on UDP endpoint if DHT is disabled. if srv.NoDiscovery && !srv.DiscoveryV5 { return nil } @@ -569,7 +619,11 @@ func (srv *Server) setupDiscovery() error { 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.loopWG.Add(1) + go func() { + nat.Map(srv.NAT, srv.quit, "udp", realaddr.Port, realaddr.Port, "ethereum discovery") + srv.loopWG.Done() + }() } } srv.localnode.SetFallbackUDP(realaddr.Port) @@ -588,82 +642,138 @@ func (srv *Server) setupDiscovery() error { NetRestrict: srv.NetRestrict, Bootnodes: srv.BootstrapNodes, Unhandled: unhandled, + Log: srv.log, } - ntab, err := discover.ListenUDP(conn, srv.localnode, cfg) + ntab, err := discover.ListenV4(conn, srv.localnode, cfg) if err != nil { return err } srv.ntab = ntab + srv.discmix.AddSource(ntab.RandomNodes()) } + // Discovery V5 if srv.DiscoveryV5 { - var ntab *discv5.Network + cfg := discover.Config{ + PrivateKey: srv.PrivateKey, + NetRestrict: srv.NetRestrict, + Bootnodes: srv.BootstrapNodesV5, + Log: srv.log, + } var err error if sconn != nil { - ntab, err = discv5.ListenUDP(srv.PrivateKey, sconn, "", srv.NetRestrict) + srv.DiscV5, err = discover.ListenV5(sconn, srv.localnode, cfg) } else { - ntab, err = discv5.ListenUDP(srv.PrivateKey, conn, "", srv.NetRestrict) + srv.DiscV5, err = discover.ListenV5(conn, srv.localnode, cfg) } if err != nil { return err } - if err := ntab.SetFallbackNodes(srv.BootstrapNodesV5); err != nil { - return err - } - srv.DiscV5 = ntab } return nil } +func (srv *Server) setupDialScheduler() { + config := dialConfig{ + self: srv.localnode.ID(), + maxDialPeers: srv.maxDialedConns(), + maxActiveDials: srv.MaxPendingPeers, + log: srv.Logger, + netRestrict: srv.NetRestrict, + dialer: srv.Dialer, + clock: srv.clock, + MaxConsensusPeers: srv.MaxConsensusPeers, + } + if srv.ntab != nil { + config.resolver = srv.ntab + } + if config.dialer == nil { + config.dialer = tcpDialer{&net.Dialer{Timeout: defaultDialTimeout}} + } + srv.dialsched = newDialScheduler(config, srv.discmix, srv.SetupConn) + + dialstateRemoveConsensusPeerFn := func(node *enode.Node) { + srv.doPeerOp(func(peers map[enode.ID]*Peer) { + srv.dialsched.removeConsensusFromQueue(node) + if p, ok := peers[node.ID()]; ok { + p.Disconnect(DiscRequested) + } + }) + } + + srv.dialsched.consensus.InitRemoveConsensusPeerFn(dialstateRemoveConsensusPeerFn) + for _, n := range srv.StaticNodes { + srv.dialsched.addStatic(n) + } +} + +func (srv *Server) maxInboundConns() int { + return srv.MaxPeers - srv.maxDialedConns() +} + +func (srv *Server) maxDialedConns() (limit int) { + if srv.NoDial || srv.MaxPeers == 0 { + return 0 + } + if srv.DialRatio == 0 { + limit = srv.MaxPeers / defaultDialRatio + } else { + limit = srv.MaxPeers / srv.DialRatio + } + if limit == 0 { + limit = 1 + } + return limit +} + func (srv *Server) setupListening() error { - // Launch the TCP listener. - listener, err := net.Listen("tcp", srv.ListenAddr) + // Launch the listener. + listener, err := srv.listenFunc("tcp", srv.ListenAddr) if err != nil { return err } - laddr := listener.Addr().(*net.TCPAddr) - srv.ListenAddr = laddr.String() srv.listener = listener - srv.localnode.Set(enr.TCP(laddr.Port)) + srv.ListenAddr = listener.Addr().String() + + // Update the local node record and map the TCP listening port if NAT is configured. + if tcp, ok := listener.Addr().(*net.TCPAddr); ok { + srv.localnode.Set(enr.TCP(tcp.Port)) + if !tcp.IP.IsLoopback() && srv.NAT != nil { + srv.loopWG.Add(1) + go func() { + nat.Map(srv.NAT, srv.quit, "tcp", tcp.Port, tcp.Port, "ethereum p2p") + srv.loopWG.Done() + }() + } + } srv.loopWG.Add(1) go srv.listenLoop() - - // Map the TCP listening port if NAT is configured. - if !laddr.IP.IsLoopback() && srv.NAT != nil { - srv.loopWG.Add(1) - go func() { - nat.Map(srv.NAT, srv.quit, "tcp", laddr.Port, laddr.Port, "ethereum p2p") - srv.loopWG.Done() - }() - } return nil } -type dialer interface { - newTasks(running int, peers map[enode.ID]*Peer, now time.Time) []task - taskDone(task, time.Time) - addStatic(*enode.Node) - removeStatic(*enode.Node) - addConsensus(*enode.Node) - removeConsensus(*enode.Node) - removeConsensusFromQueue(*enode.Node) - initRemoveConsensusPeerFn(removeConsensusPeerFn removeConsensusPeerFn) +// doPeerOp runs fn on the main loop. +func (srv *Server) doPeerOp(fn peerOpFunc) { + select { + case srv.peerOp <- fn: + <-srv.peerOpDone + case <-srv.quit: + } } -func (srv *Server) run(dialstate dialer) { - srv.log.Info("Started P2P networking", "self", srv.localnode.Node()) +// run is the main loop of the server. +func (srv *Server) run() { + srv.log.Info("Started P2P networking", "self", srv.localnode.Node().URLv4()) defer srv.loopWG.Done() defer srv.nodedb.Close() + defer srv.discmix.Close() + defer srv.dialsched.stop() var ( peers = make(map[enode.ID]*Peer) inboundCount = 0 trusted = make(map[enode.ID]bool, len(srv.TrustedNodes)) consensusNodes = make(map[enode.ID]bool, 0) - taskdone = make(chan task, maxActiveDialTasks) - runningTasks []task - queuedTasks []task // tasks that can't run yet ) // Put trusted nodes into a map to speed up checks. // Trusted peers are loaded on startup or added via AddTrustedPeer RPC. @@ -671,67 +781,13 @@ func (srv *Server) run(dialstate dialer) { trusted[n.ID()] = true } - // removes t from runningTasks - delTask := func(t task) { - for i := range runningTasks { - if runningTasks[i] == t { - runningTasks = append(runningTasks[:i], runningTasks[i+1:]...) - break - } - } - } - // starts until max number of active tasks is satisfied - startTasks := func(ts []task) (rest []task) { - i := 0 - for ; len(runningTasks) < maxActiveDialTasks && i < len(ts); i++ { - t := ts[i] - srv.log.Trace("New dial task", "task", t) - go func() { t.Do(srv); taskdone <- t }() - runningTasks = append(runningTasks, t) - } - return ts[i:] - } - scheduleTasks := func() { - // Start from queue first. - queuedTasks = append(queuedTasks[:0], startTasks(queuedTasks)...) - // Query dialer for new tasks and start as many as possible now. - if len(runningTasks) < maxActiveDialTasks { - nt := dialstate.newTasks(len(runningTasks)+len(queuedTasks), peers, time.Now()) - queuedTasks = append(queuedTasks, startTasks(nt)...) - } - } - dialstateRemoveConsensusPeerFn := func(node *enode.Node) { - srv.log.Trace("Removing consensus node from dialstate", "node", node) - dialstate.removeConsensusFromQueue(node) - if p, ok := peers[node.ID()]; ok { - p.Disconnect(DiscRequested) - } - } - dialstate.initRemoveConsensusPeerFn(dialstateRemoveConsensusPeerFn) - running: for { - scheduleTasks() - select { case <-srv.quit: // The server was stopped. Run the cleanup logic. break running - case n := <-srv.addstatic: - // This channel is used by AddPeer to add to the - // ephemeral static peer list. Add it to the dialer, - // it will keep the node connected. - srv.log.Trace("Adding static node", "node", n) - dialstate.addStatic(n) - case n := <-srv.removestatic: - // This channel is used by RemovePeer to send a - // disconnect request to a peer and begin the - // stop keeping the node connected. - srv.log.Trace("Removing static node", "node", n) - dialstate.removeStatic(n) - if p, ok := peers[n.ID()]; ok { - p.Disconnect(DiscRequested) - } + case n := <-srv.addconsensus: // This channel is used by AddConsensusNode to add an enode // to the consensus node set. @@ -741,7 +797,7 @@ running: srv.log.Debug("We are become an consensus node") srv.consensus = true } else { - dialstate.addConsensus(n) + srv.dialsched.addConsensus(n) } consensusNodes[n.ID()] = true if p, ok := peers[n.ID()]; ok { @@ -756,7 +812,7 @@ running: srv.log.Debug("We are not an consensus node") srv.consensus = false } - dialstate.removeConsensus(n) + srv.dialsched.removeConsensus(n) if _, ok := consensusNodes[n.ID()]; ok { delete(consensusNodes, n.ID()) } @@ -772,37 +828,29 @@ running: } } case n := <-srv.addtrusted: - // This channel is used by AddTrustedPeer to add an enode + // This channel is used by AddTrustedPeer to add a node // 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 + // This channel is used by RemoveTrustedPeer to remove a node // 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 + delete(trusted, n.ID()) if p, ok := peers[n.ID()]; ok { p.rw.set(trustedConn, false) } + case op := <-srv.peerOp: // This channel is used by Peers and PeerCount. op(peers) srv.peerOpDone <- struct{}{} - case t := <-taskdone: - // A task got done. Tell dialstate about it so it - // can update its state and remove it from the active - // tasks list. - srv.log.Trace("Dial task done", "task", t) - dialstate.taskDone(t, time.Now()) - delTask(t) - case c := <-srv.posthandshake: + + case c := <-srv.checkpointPostHandshake: // A connection has passed the encryption handshake so // the remote identity is known (but hasn't been verified yet). if trusted[c.node.ID()] { @@ -815,44 +863,30 @@ running: } // TODO: track in-progress inbound node IDs (pre-Peer) to avoid dialing them. - select { - case c.cont <- srv.encHandshakeChecks(peers, inboundCount, c): - case <-srv.quit: - break running - } - case c := <-srv.addpeer: + c.cont <- srv.postHandshakeChecks(peers, inboundCount, c) + + case c := <-srv.checkpointAddPeer: // At this point the connection is past the protocol handshake. // Its capabilities are known and the remote identity is verified. - err := srv.protoHandshakeChecks(peers, inboundCount, c) + err := srv.addPeerChecks(peers, inboundCount, c) if err == nil { // The handshakes are done and it passed all checks. - p := newPeer(c, srv.Protocols) - // If message events are enabled, pass the peerFeed - // to the peer - if srv.EnableMsgEvents { - p.events = &srv.peerFeed - } - name := truncateName(c.name) - p.log.Debug("Adding p2p peer", "addr", p.RemoteAddr(), "peers", len(peers)+1, "name", name, "id", p.ID(), "flags", c.flags) - go srv.runPeer(p) + p := srv.launchPeer(c) peers[c.node.ID()] = p + srv.log.Debug("Adding p2p peer", "peercount", len(peers), "id", p.ID(), "conn", c.flags, "addr", p.RemoteAddr(), "name", p.Name()) + srv.dialsched.peerAdded(c) if p.Inbound() { inboundCount++ } } - // The dialer logic relies on the assumption that - // dial tasks complete after the peer has been added or - // discarded. Unblock the task last. - select { - case c.cont <- err: - case <-srv.quit: - break running - } + c.cont <- err + case pd := <-srv.delpeer: // A peer disconnected. d := common.PrettyDuration(mclock.Now() - pd.created) - pd.log.Debug("Removing p2p peer", "addr", pd.RemoteAddr(), "peers", len(peers)-1, "duration", d, "req", pd.requested, "err", pd.err) delete(peers, pd.ID()) + srv.log.Debug("Removing p2p peer", "peercount", len(peers), "id", pd.ID(), "duration", d, "req", pd.requested, "err", pd.err) + srv.dialsched.peerRemoved(pd.rw) if pd.Inbound() { inboundCount-- } @@ -877,32 +911,12 @@ running: // is closed. for len(peers) > 0 { p := <-srv.delpeer - p.log.Trace("<-delpeer (spindown)", "remainingTasks", len(runningTasks)) + p.log.Trace("<-delpeer (spindown)") delete(peers, p.ID()) } } -func (srv *Server) protoHandshakeChecks(peers map[enode.ID]*Peer, inboundCount int, c *conn) error { - // Drop connections with no matching protocols. - if len(srv.Protocols) > 0 && countMatchingProtocols(srv.Protocols, c.caps) == 0 { - return DiscUselessPeer - } - // Repeat the encryption handshake checks because the - // peer set might have changed between the handshakes. - return srv.encHandshakeChecks(peers, inboundCount, c) -} - -func (srv *Server) numConsensusPeer(peers map[enode.ID]*Peer) int { - c := 0 - for _, p := range peers { - if p.rw.is(consensusDialedConn) { - c++ - } - } - return c -} - -func (srv *Server) encHandshakeChecks(peers map[enode.ID]*Peer, inboundCount int, c *conn) error { +func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount int, c *conn) error { // Disconnect over limit non-consensus node. if srv.consensus && len(peers) >= srv.MaxPeers && c.is(consensusDialedConn) && srv.numConsensusPeer(peers) < srv.MaxConsensusPeers { for _, p := range peers { @@ -932,27 +946,32 @@ func (srv *Server) encHandshakeChecks(peers map[enode.ID]*Peer, inboundCount int } } -func (srv *Server) maxInboundConns() int { - return srv.MaxPeers - srv.maxDialedConns() +func (srv *Server) numConsensusPeer(peers map[enode.ID]*Peer) int { + c := 0 + for _, p := range peers { + if p.rw.is(consensusDialedConn) { + c++ + } + } + return c } -func (srv *Server) maxDialedConns() int { - if srv.NoDiscovery || srv.NoDial { - return 0 - } - r := srv.DialRatio - if r == 0 { - r = defaultDialRatio +func (srv *Server) addPeerChecks(peers map[enode.ID]*Peer, inboundCount int, c *conn) error { + // Drop connections with no matching protocols. + if len(srv.Protocols) > 0 && countMatchingProtocols(srv.Protocols, c.caps) == 0 { + return DiscUselessPeer } - return srv.MaxPeers / r + // Repeat the post-handshake checks because the + // peer set might have changed since those checks were performed. + return srv.postHandshakeChecks(peers, inboundCount, c) } // listenLoop runs in its own goroutine and accepts // inbound connections. func (srv *Server) listenLoop() { - defer srv.loopWG.Done() srv.log.Debug("TCP listener up", "addr", srv.listener.Addr()) + // The slots channel limits accepts of new connections. tokens := defaultMaxPendingPeers if srv.MaxPendingPeers > 0 { tokens = srv.MaxPendingPeers @@ -962,42 +981,56 @@ func (srv *Server) listenLoop() { slots <- struct{}{} } + // Wait for slots to be returned on exit. This ensures all connection goroutines + // are down before listenLoop returns. + defer srv.loopWG.Done() + defer func() { + for i := 0; i < cap(slots); i++ { + <-slots + } + }() + for { - // Wait for a handshake slot before accepting. + // Wait for a free slot before accepting. <-slots var ( - fd net.Conn - err error + fd net.Conn + err error + lastLog time.Time ) for { fd, err = srv.listener.Accept() if netutil.IsTemporaryError(err) { - srv.log.Debug("Temporary read error", "err", err) + if time.Since(lastLog) > 1*time.Second { + srv.log.Debug("Temporary read error", "err", err) + lastLog = time.Now() + } + time.Sleep(time.Millisecond * 200) continue } else if err != nil { srv.log.Debug("Read error", "err", err) + slots <- struct{}{} return } break } - // Reject connections that do not match NetRestrict. - if srv.NetRestrict != nil { - if tcp, ok := fd.RemoteAddr().(*net.TCPAddr); ok && !srv.NetRestrict.Contains(tcp.IP) { - srv.log.Debug("Rejected conn (not whitelisted in NetRestrict)", "addr", fd.RemoteAddr()) - fd.Close() - slots <- struct{}{} - continue - } + remoteIP := netutil.AddrIP(fd.RemoteAddr()) + if err := srv.checkInboundConn(remoteIP); err != nil { + srv.log.Debug("Rejected inbound connection", "addr", fd.RemoteAddr(), "err", err) + fd.Close() + slots <- struct{}{} + continue } - - var ip net.IP - if tcp, ok := fd.RemoteAddr().(*net.TCPAddr); ok { - ip = tcp.IP + if remoteIP != nil { + var addr *net.TCPAddr + if tcp, ok := fd.RemoteAddr().(*net.TCPAddr); ok { + addr = tcp + } + fd = newMeteredConn(fd, true, addr) + srv.log.Trace("Accepted connection", "addr", fd.RemoteAddr()) } - fd = newMeteredConn(fd, true, ip) - srv.log.Trace("Accepted connection", "addr", fd.RemoteAddr()) go func() { srv.SetupConn(fd, inboundConn, nil) slots <- struct{}{} @@ -1005,15 +1038,38 @@ func (srv *Server) listenLoop() { } } +func (srv *Server) checkInboundConn(remoteIP net.IP) error { + if remoteIP == nil { + return nil + } + // Reject connections that do not match NetRestrict. + if srv.NetRestrict != nil && !srv.NetRestrict.Contains(remoteIP) { + return fmt.Errorf("not in netrestrict list") + } + // Reject Internet peers that try too often. + now := srv.clock.Now() + srv.inboundHistory.expire(now, nil) + if !netutil.IsLAN(remoteIP) && srv.inboundHistory.contains(remoteIP.String()) { + return fmt.Errorf("too many attempts") + } + srv.inboundHistory.add(remoteIP.String(), now.Add(inboundThrottleTime)) + return nil +} + // SetupConn runs the handshakes and attempts to add the connection // as a peer. It returns when the connection has been added as a peer // or the handshakes have failed. func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.Node) error { - c := &conn{fd: fd, transport: srv.newTransport(fd), flags: flags, cont: make(chan error)} + c := &conn{fd: fd, flags: flags, cont: make(chan error)} + if dialDest == nil { + c.transport = srv.newTransport(fd, nil) + } else { + c.transport = srv.newTransport(fd, dialDest.Pubkey()) + } + err := srv.setupConn(c, flags, dialDest) if err != nil { c.close(err) - srv.log.Trace("Setting up connection failed", "addr", fd.RemoteAddr(), "err", err) } return err } @@ -1026,57 +1082,53 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro if !running { 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") + err = errors.New("dial destination doesn't have a secp256k1 public key") + srv.log.Trace("Setting up connection failed", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err) + return err } } - // Run the encryption handshake. - remotePubkey, err := c.doEncHandshake(srv.PrivateKey, dialPubkey) + + // Run the RLPx handshake. + remotePubkey, err := c.doEncHandshake(srv.PrivateKey) if err != nil { srv.log.Trace("Failed RLPx handshake", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err) return err } if dialDest != nil { - // For dialed connections, check that the remote public key matches. - if dialPubkey.X.Cmp(remotePubkey.X) != 0 || dialPubkey.Y.Cmp(remotePubkey.Y) != 0 { - return DiscUnexpectedIdentity - } c.node = dialDest } else { c.node = nodeFromConn(remotePubkey, c.fd) } - if conn, ok := c.fd.(*meteredConn); ok { - conn.handshakeDone(c.node.ID()) - } 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.checkpointPostHandshake) if err != nil { - clog.Trace("Rejected peer before protocol handshake", "err", err) + clog.Trace("Rejected peer", "err", err) return err } - // Run the protocol handshake + + // Run the capability negotiation handshake. phs, err := c.doProtoHandshake(srv.ourHandshake) if err != nil { - clog.Trace("Failed proto handshake", "err", err) + clog.Trace("Failed p2p handshake", "err", err) return err } if id := c.node.ID(); !bytes.Equal(crypto.Keccak256(phs.ID), id[:]) { - clog.Trace("Wrong devp2p handshake identity", "phsid", fmt.Sprintf("%x", phs.ID)) + clog.Trace("Wrong devp2p handshake identity", "phsid", hex.EncodeToString(phs.ID)) return DiscUnexpectedIdentity } c.caps, c.name = phs.Caps, phs.Name - err = srv.checkpoint(c, srv.addpeer) + err = srv.checkpoint(c, srv.checkpointAddPeer) if err != nil { clog.Trace("Rejected peer", "err", err) return err } - // If the checks completed successfully, runPeer has now been - // launched by run. - clog.Trace("connection set up", "inbound", dialDest == nil) + return nil } @@ -1090,13 +1142,6 @@ func nodeFromConn(pubkey *ecdsa.PublicKey, conn net.Conn) *enode.Node { return enode.NewV4(pubkey, ip, port, port) } -func truncateName(s string) string { - if len(s) > 20 { - return s[:20] + "..." - } - return s -} - // checkpoint sends the conn to run, which performs the // post-handshake checks for the stage (posthandshake, addpeer). func (srv *Server) checkpoint(c *conn, stage chan<- *conn) error { @@ -1105,23 +1150,25 @@ func (srv *Server) checkpoint(c *conn, stage chan<- *conn) error { case <-srv.quit: return errServerStopped } - select { - case err := <-c.cont: - return err - case <-srv.quit: - return errServerStopped + return <-c.cont +} + +func (srv *Server) launchPeer(c *conn) *Peer { + p := newPeer(srv.log, c, srv.Protocols) + if srv.EnableMsgEvents { + // If message events are enabled, pass the peerFeed + // to the peer. + p.events = &srv.peerFeed } + go srv.runPeer(p) + return p } // runPeer runs in its own goroutine for each peer. -// it waits until the Peer logic returns and removes -// the peer. func (srv *Server) runPeer(p *Peer) { if srv.newPeerHook != nil { srv.newPeerHook(p) } - - // broadcast peer add srv.peerFeed.Send(&PeerEvent{ Type: PeerEventTypeAdd, Peer: p.ID(), @@ -1129,10 +1176,18 @@ func (srv *Server) runPeer(p *Peer) { LocalAddress: p.LocalAddr().String(), }) - // run the protocol + // Run the per-peer main loop. remoteRequested, err := p.run() - // broadcast peer drop + // Announce disconnect on the main loop to update the peer set. + // The main loop waits for existing peers to be sent on srv.delpeer + // before returning, so this send should not select on srv.quit. + srv.delpeer <- peerDrop{p, err, remoteRequested} + + // Broadcast peer drop to external subscribers. This needs to be + // after the send to delpeer so subscribers have a consistent view of + // the peer set (i.e. Server.Peers() doesn't include the peer when the + // event is received. srv.peerFeed.Send(&PeerEvent{ Type: PeerEventTypeDrop, Peer: p.ID(), @@ -1140,10 +1195,6 @@ func (srv *Server) runPeer(p *Peer) { RemoteAddress: p.RemoteAddr().String(), LocalAddress: p.LocalAddr().String(), }) - - // Note: run waits for existing peers to be sent on srv.delpeer - // before returning, so this send should not select on srv.quit. - srv.delpeer <- peerDrop{p, err, remoteRequested} } // NodeInfo represents a short summary of the information known about the host. @@ -1168,7 +1219,7 @@ func (srv *Server) NodeInfo() *NodeInfo { node := srv.Self() info := &NodeInfo{ Name: srv.Name, - Enode: node.String(), + Enode: node.URLv4(), ID: node.ID().String(), IP: node.IP().String(), ListenAddr: srv.ListenAddr, @@ -1176,9 +1227,8 @@ func (srv *Server) NodeInfo() *NodeInfo { } info.Ports.Discovery = node.UDP() info.Ports.Listener = node.TCP() - if enc, err := rlp.EncodeToBytes(node.Record()); err == nil { - info.ENR = "0x" + hex.EncodeToString(enc) - } + info.ENR = node.String() + blskey, _ := srv.BlsPublicKey.MarshalText() info.BlsPub = string(blskey) diff --git a/p2p/server_test.go b/p2p/server_test.go index 407901dcb1..26c16a8c91 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -18,47 +18,43 @@ package p2p import ( "crypto/ecdsa" + "crypto/sha256" "errors" + "io" "math/rand" "net" "reflect" "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/rlpx" + + "github.com/AlayaNetwork/Alaya-Go/internal/testlog" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/enr" - "github.com/stretchr/testify/assert" - - "golang.org/x/crypto/sha3" - "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/log" ) -// func init() { -// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) -// } - type testTransport struct { - rpub *ecdsa.PublicKey - *rlpx - + *rlpxTransport + rpub *ecdsa.PublicKey closeErr error } -func newTestTransport(id discover.NodeID, fd net.Conn) transport { - wrapped := newRLPX(fd).(*rlpx) - wrapped.rw = newRLPXFrameRW(fd, secrets{ - MAC: zero16, - AES: zero16, - IngressMAC: sha3.NewLegacyKeccak256(), - EgressMAC: sha3.NewLegacyKeccak256(), +func newTestTransport(rpub *ecdsa.PublicKey, fd net.Conn, dialDest *ecdsa.PublicKey) transport { + wrapped := newRLPX(fd, dialDest).(*rlpxTransport) + wrapped.conn.InitWithSecrets(rlpx.Secrets{ + AES: make([]byte, 16), + MAC: make([]byte, 16), + EgressMAC: sha256.New(), + IngressMAC: sha256.New(), }) - return &testTransport{rpub: rpub, rlpx: wrapped} + return &testTransport{rpub: rpub, rlpxTransport: wrapped} } -func (c *testTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) { +func (c *testTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) { return c.rpub, nil } @@ -68,21 +64,25 @@ func (c *testTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, } func (c *testTransport) close(err error) { - c.rlpx.fd.Close() + c.conn.Close() c.closeErr = err } func startTestServer(t *testing.T, remoteKey *ecdsa.PublicKey, pf func(*Peer)) *Server { config := Config{ - Name: "test", - MaxPeers: 10, - ListenAddr: "127.0.0.1:0", - PrivateKey: newkey(), + Name: "test", + MaxPeers: 10, + ListenAddr: "127.0.0.1:0", + NoDiscovery: true, + PrivateKey: newkey(), + Logger: testlog.Logger(t, log.LvlTrace), } server := &Server{ - Config: config, - newPeerHook: pf, - newTransport: func(fd net.Conn) transport { return newTestTransport(remoteKey, fd) }, + Config: config, + newPeerHook: pf, + newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport { + return newTestTransport(remoteKey, fd, dialDest) + }, } if err := server.Start(); err != nil { t.Fatalf("Could not start server: %v", err) @@ -132,7 +132,7 @@ func TestServerDial(t *testing.T) { t.Fatalf("could not setup listener: %v", err) } defer listener.Close() - accepted := make(chan net.Conn) + accepted := make(chan net.Conn, 1) go func() { conn, err := listener.Accept() if err != nil { @@ -206,173 +206,49 @@ func TestServerDial(t *testing.T) { } } -// This test checks that tasks generated by dialstate are -// actually executed and taskdone is called for them. -func TestServerTaskScheduling(t *testing.T) { - var ( - done = make(chan *testTask) - quit, returned = make(chan struct{}), make(chan struct{}) - tc = 0 - tg = taskgen{ - newFunc: func(running int, peers map[enode.ID]*Peer) []task { - tc++ - return []task{&testTask{index: tc - 1}} - }, - doneFunc: func(t task) { - select { - case done <- t.(*testTask): - case <-quit: - } - }, - } - ) - - // The Server in this test isn't actually running - // because we're only interested in what run does. - db, _ := enode.OpenDB("") - srv := &Server{ - Config: Config{MaxPeers: 10}, - localnode: enode.NewLocalNode(db, newkey()), - nodedb: db, - quit: make(chan struct{}), - ntab: fakeTable{}, - running: true, - log: log.New(), - } - srv.loopWG.Add(1) - go func() { - srv.run(tg) - close(returned) - }() +// This test checks that RemovePeer disconnects the peer if it is connected. +func TestServerRemovePeerDisconnect(t *testing.T) { + srv1 := &Server{Config: Config{ + PrivateKey: newkey(), + MaxPeers: 1, + NoDiscovery: true, + Logger: testlog.Logger(t, log.LvlTrace).New("server", "1"), + }} + srv2 := &Server{Config: Config{ + PrivateKey: newkey(), + MaxPeers: 1, + NoDiscovery: true, + NoDial: true, + ListenAddr: "127.0.0.1:0", + Logger: testlog.Logger(t, log.LvlTrace).New("server", "2"), + }} + srv1.Start() + defer srv1.Stop() + srv2.Start() + defer srv2.Stop() - var gotdone []*testTask - for i := 0; i < 100; i++ { - gotdone = append(gotdone, <-done) + if !syncAddPeer(srv1, srv2.Self()) { + t.Fatal("peer not connected") } - for i, task := range gotdone { - if task.index != i { - t.Errorf("task %d has wrong index, got %d", i, task.index) - break - } - if !task.called { - t.Errorf("task %d was not called", i) - break - } - } - - close(quit) - srv.Stop() - select { - case <-returned: - case <-time.After(500 * time.Millisecond): - t.Error("Server.run did not return within 500ms") + srv1.RemovePeer(srv2.Self()) + if srv1.PeerCount() > 0 { + t.Fatal("removed peer still connected") } } -// This test checks that Server doesn't drop tasks, -// even if newTasks returns more than the maximum number of tasks. -func TestServerManyTasks(t *testing.T) { - alltasks := make([]task, 300) - for i := range alltasks { - alltasks[i] = &testTask{index: i} - } - - var ( - db, _ = enode.OpenDB("") - srv = &Server{ - quit: make(chan struct{}), - localnode: enode.NewLocalNode(db, newkey()), - nodedb: db, - ntab: fakeTable{}, - running: true, - log: log.New(), - } - done = make(chan *testTask) - start, end = 0, 0 - ) - defer srv.Stop() - srv.loopWG.Add(1) - go srv.run(taskgen{ - newFunc: func(running int, peers map[enode.ID]*Peer) []task { - start, end = end, end+maxActiveDialTasks+10 - if end > len(alltasks) { - end = len(alltasks) - } - return alltasks[start:end] - }, - doneFunc: func(tt task) { - done <- tt.(*testTask) - }, - }) - - doneset := make(map[int]bool) - timeout := time.After(2 * time.Second) - for len(doneset) < len(alltasks) { - select { - case tt := <-done: - if doneset[tt.index] { - t.Errorf("task %d got done more than once", tt.index) - } else { - doneset[tt.index] = true - } - case <-timeout: - t.Errorf("%d of %d tasks got done within 2s", len(doneset), len(alltasks)) - for i := 0; i < len(alltasks); i++ { - if !doneset[i] { - t.Logf("task %d not done", i) - } - } - return - } - } -} - -type taskgen struct { - newFunc func(running int, peers map[enode.ID]*Peer) []task - doneFunc func(task) -} - -func (tg taskgen) newTasks(running int, peers map[enode.ID]*Peer, now time.Time) []task { - return tg.newFunc(running, peers) -} -func (tg taskgen) taskDone(t task, now time.Time) { - tg.doneFunc(t) -} -func (tg taskgen) addStatic(*enode.Node) { -} -func (tg taskgen) removeStatic(*enode.Node) { -} -func (tg taskgen) addConsensus(*enode.Node) { -} -func (tg taskgen) removeConsensus(*enode.Node) { -} -func (tg taskgen) removeConsensusFromQueue(*enode.Node) { -} -func (tg taskgen) initRemoveConsensusPeerFn(removeConsensusPeerFn removeConsensusPeerFn) { -} - -type testTask struct { - index int - called bool -} - -func (t *testTask) Do(srv *Server) { - t.called = true -} - -// This test checks that connections are disconnected -// just after the encryption handshake when the server is -// at capacity. Trusted connections should still be accepted. +// This test checks that connections are disconnected just after the encryption handshake +// when the server is at capacity. Trusted connections should still be accepted. func TestServerAtCap(t *testing.T) { trustedNode := newkey() trustedID := enode.PubkeyToIDV4(&trustedNode.PublicKey) srv := &Server{ Config: Config{ - PrivateKey: newkey(), - MaxPeers: 10, - MaxConsensusPeers: 2, - NoDial: true, - TrustedNodes: []*enode.Node{newNode(trustedID, nil)}, + PrivateKey: newkey(), + MaxPeers: 10, + NoDial: true, + NoDiscovery: true, + TrustedNodes: []*enode.Node{newNode(trustedID, "")}, + Logger: testlog.Logger(t, log.LvlTrace), }, } if err := srv.Start(); err != nil { @@ -382,7 +258,7 @@ func TestServerAtCap(t *testing.T) { newconn := func(id enode.ID) *conn { fd, _ := net.Pipe() - tx := newTestTransport(&trustedNode.PublicKey, fd) + tx := newTestTransport(&trustedNode.PublicKey, fd, nil) node := enode.SignNull(new(enr.Record), id) return &conn{fd: fd, transport: tx, flags: inboundConn, node: node, cont: make(chan error)} } @@ -390,19 +266,19 @@ func TestServerAtCap(t *testing.T) { // Inject a few connections to fill up the peer set. for i := 0; i < 10; i++ { c := newconn(randomID()) - if err := srv.checkpoint(c, srv.addpeer); err != nil { + if err := srv.checkpoint(c, srv.checkpointAddPeer); err != nil { t.Fatalf("could not add conn %d: %v", i, err) } } // Try inserting a non-trusted connection. anotherID := randomID() c := newconn(anotherID) - if err := srv.checkpoint(c, srv.posthandshake); err != DiscTooManyPeers { + if err := srv.checkpoint(c, srv.checkpointPostHandshake); err != DiscTooManyPeers { t.Error("wrong error for insert:", err) } // Try inserting a trusted connection. c = newconn(trustedID) - if err := srv.checkpoint(c, srv.posthandshake); err != nil { + if err := srv.checkpoint(c, srv.checkpointPostHandshake); err != nil { t.Error("unexpected error for trusted conn @posthandshake:", err) } if !c.is(trustedConn) { @@ -410,84 +286,21 @@ func TestServerAtCap(t *testing.T) { } // Remove from trusted set and try again - srv.RemoveTrustedPeer(newNode(trustedID, nil)) + srv.RemoveTrustedPeer(newNode(trustedID, "")) c = newconn(trustedID) - if err := srv.checkpoint(c, srv.posthandshake); err != DiscTooManyPeers { + if err := srv.checkpoint(c, srv.checkpointPostHandshake); err != DiscTooManyPeers { t.Error("wrong error for insert:", err) } // Add anotherID to trusted set and try again - srv.AddTrustedPeer(newNode(anotherID, nil)) + srv.AddTrustedPeer(newNode(anotherID, "")) c = newconn(anotherID) - if err := srv.checkpoint(c, srv.posthandshake); err != nil { + if err := srv.checkpoint(c, srv.checkpointPostHandshake); err != nil { t.Error("unexpected error for trusted conn @posthandshake:", err) } if !c.is(trustedConn) { t.Error("Server did not set trusted flag") } - - // Try inserting a consensus connection. - consensusID := randomID() - srv.consensus = true - srv.AddConsensusPeer(newNode(consensusID, nil)) - c = newconn(consensusID) - if err := srv.checkpoint(c, srv.posthandshake); err != nil { - t.Error("unexpected error for consensus conn @posthandshake:", err) - } - if !c.is(consensusDialedConn) { - t.Error("Server did not set consensus flag") - } - - // An InboundConn connection was broken in the previous step, and an InboundConn connection is added - time.Sleep(time.Second) // Waiting remove peer - c = newconn(randomID()) - if err := srv.checkpoint(c, srv.addpeer); err != nil { - t.Fatalf("could not add conn: %v", err) - } - - // Remove from consensus set and try again - srv.RemoveConsensusPeer(newNode(consensusID, nil)) - c = newconn(consensusID) - if err := srv.checkpoint(c, srv.posthandshake); err != DiscTooManyPeers { - t.Error("wrong error for insert:", err) - } - - anotherID = randomID() - srv.AddConsensusPeer(newNode(anotherID, nil)) - c = newconn(anotherID) - if err := srv.checkpoint(c, srv.posthandshake); err != nil { - t.Error("unexpected error for consensus conn @posthandshake:", err) - } - if !c.is(consensusDialedConn) { - t.Error("Server did not set consensus flag") - } - - // An InboundConn connection was broken in the previous step, and an InboundConn connection is added - time.Sleep(time.Second) // Waiting remove peer - c = newconn(randomID()) - if err := srv.checkpoint(c, srv.addpeer); err != nil { - t.Fatalf("could not add conn: %v", err) - } - - // Removing non-consensus connection - srv.consensus = true - srv.AddConsensusPeer(newNode(consensusID, nil)) - c = newconn(consensusID) - if err := srv.checkpoint(c, srv.posthandshake); err != nil { - t.Error("unexpected error for consensus conn @posthandshake:", err) - } - time.Sleep(time.Second) // Waiting remove peer - assert.Equal(t, srv.PeerCount(), srv.MaxPeers-1) - - if err := srv.checkpoint(c, srv.addpeer); err != nil { - t.Error("unexpected error for consensus conn @addpeer:", err) - } - assert.Equal(t, srv.PeerCount(), srv.MaxPeers) - - srv.MaxPeers = 9 - srv.RemoveConsensusPeer(newNode(consensusID, nil)) - time.Sleep(time.Second) - assert.Equal(t, srv.PeerCount(), srv.MaxPeers) } func TestServerPeerLimits(t *testing.T) { @@ -506,13 +319,14 @@ func TestServerPeerLimits(t *testing.T) { srv := &Server{ Config: Config{ - PrivateKey: srvkey, - MaxPeers: 0, - NoDial: true, - Protocols: []Protocol{discard}, + PrivateKey: srvkey, + MaxPeers: 0, + NoDial: true, + NoDiscovery: true, + Protocols: []Protocol{discard}, + Logger: testlog.Logger(t, log.LvlTrace), }, - newTransport: func(fd net.Conn) transport { return tp }, - log: log.New(), + newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport { return tp }, } if err := srv.Start(); err != nil { t.Fatalf("couldn't start server: %v", err) @@ -559,6 +373,8 @@ func TestServerSetupConn(t *testing.T) { clientkey, srvkey = newkey(), newkey() clientpub = &clientkey.PublicKey srvpub = &srvkey.PublicKey + fooErr = errors.New("foo") + readErr = errors.New("read error") ) tests := []struct { dontstart bool @@ -576,17 +392,10 @@ func TestServerSetupConn(t *testing.T) { wantCloseErr: errServerStopped, }, { - tt: &setupTransport{pubkey: clientpub, encHandshakeErr: errors.New("read error")}, + tt: &setupTransport{pubkey: clientpub, encHandshakeErr: readErr}, flags: inboundConn, wantCalls: "doEncHandshake,close,", - wantCloseErr: errors.New("read error"), - }, - { - tt: &setupTransport{pubkey: clientpub}, - dialDest: enode.NewV4(&newkey().PublicKey, nil, 0, 0), - flags: dynDialedConn, - wantCalls: "doEncHandshake,close,", - wantCloseErr: DiscUnexpectedIdentity, + wantCloseErr: readErr, }, { tt: &setupTransport{pubkey: clientpub, phs: protoHandshake{ID: randomID().Bytes()}}, @@ -596,11 +405,11 @@ func TestServerSetupConn(t *testing.T) { wantCloseErr: DiscUnexpectedIdentity, }, { - tt: &setupTransport{pubkey: clientpub, protoHandshakeErr: errors.New("foo")}, + tt: &setupTransport{pubkey: clientpub, protoHandshakeErr: fooErr}, dialDest: enode.NewV4(clientpub, nil, 0, 0), flags: dynDialedConn, wantCalls: "doEncHandshake,doProtoHandshake,close,", - wantCloseErr: errors.New("foo"), + wantCloseErr: fooErr, }, { tt: &setupTransport{pubkey: srvpub, phs: protoHandshake{ID: crypto.FromECDSAPub(srvpub)[1:]}}, @@ -617,29 +426,35 @@ func TestServerSetupConn(t *testing.T) { } for i, test := range tests { - srv := &Server{ - Config: Config{ - PrivateKey: srvkey, - MaxPeers: 10, - NoDial: true, - Protocols: []Protocol{discard}, - }, - newTransport: func(fd net.Conn) transport { return test.tt }, - log: log.New(), - } - if !test.dontstart { - if err := srv.Start(); err != nil { - t.Fatalf("couldn't start server: %v", err) + t.Run(test.wantCalls, func(t *testing.T) { + cfg := Config{ + PrivateKey: srvkey, + MaxPeers: 10, + NoDial: true, + NoDiscovery: true, + Protocols: []Protocol{discard}, + Logger: testlog.Logger(t, log.LvlTrace), } - } - p1, _ := net.Pipe() - srv.SetupConn(p1, test.flags, test.dialDest) - if !reflect.DeepEqual(test.tt.closeErr, test.wantCloseErr) { - t.Errorf("test %d: close error mismatch: got %q, want %q", i, test.tt.closeErr, test.wantCloseErr) - } - if test.tt.calls != test.wantCalls { - t.Errorf("test %d: calls mismatch: got %q, want %q", i, test.tt.calls, test.wantCalls) - } + srv := &Server{ + Config: cfg, + newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport { return test.tt }, + log: cfg.Logger, + } + if !test.dontstart { + if err := srv.Start(); err != nil { + t.Fatalf("couldn't start server: %v", err) + } + defer srv.Stop() + } + p1, _ := net.Pipe() + srv.SetupConn(p1, test.flags, test.dialDest) + if !errors.Is(test.tt.closeErr, test.wantCloseErr) { + t.Errorf("test %d: close error mismatch: got %q, want %q", i, test.tt.closeErr, test.wantCloseErr) + } + if test.tt.calls != test.wantCalls { + t.Errorf("test %d: calls mismatch: got %q, want %q", i, test.tt.calls, test.wantCalls) + } + }) } } @@ -653,7 +468,7 @@ type setupTransport struct { closeErr error } -func (c *setupTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) { +func (c *setupTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) { c.calls += "doEncHandshake," return c.pubkey, c.encHandshakeErr } @@ -666,7 +481,6 @@ func (c *setupTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, } return &c.phs, nil } - func (c *setupTransport) close(err error) { c.calls += "close," c.closeErr = err @@ -694,3 +508,120 @@ func randomID() (id enode.ID) { } return id } + +// This test checks that inbound connections are throttled by IP. +func TestServerInboundThrottle(t *testing.T) { + const timeout = 5 * time.Second + newTransportCalled := make(chan struct{}) + srv := &Server{ + Config: Config{ + PrivateKey: newkey(), + ListenAddr: "127.0.0.1:0", + MaxPeers: 10, + NoDial: true, + NoDiscovery: true, + Protocols: []Protocol{discard}, + Logger: testlog.Logger(t, log.LvlTrace), + }, + newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport { + newTransportCalled <- struct{}{} + return newRLPX(fd, dialDest) + }, + listenFunc: func(network, laddr string) (net.Listener, error) { + fakeAddr := &net.TCPAddr{IP: net.IP{95, 33, 21, 2}, Port: 4444} + return listenFakeAddr(network, laddr, fakeAddr) + }, + } + if err := srv.Start(); err != nil { + t.Fatal("can't start: ", err) + } + defer srv.Stop() + + // Dial the test server. + conn, err := net.DialTimeout("tcp", srv.ListenAddr, timeout) + if err != nil { + t.Fatalf("could not dial: %v", err) + } + select { + case <-newTransportCalled: + // OK + case <-time.After(timeout): + t.Error("newTransport not called") + } + conn.Close() + + // Dial again. This time the server should close the connection immediately. + connClosed := make(chan struct{}, 1) + conn, err = net.DialTimeout("tcp", srv.ListenAddr, timeout) + if err != nil { + t.Fatalf("could not dial: %v", err) + } + defer conn.Close() + go func() { + conn.SetDeadline(time.Now().Add(timeout)) + buf := make([]byte, 10) + if n, err := conn.Read(buf); err != io.EOF || n != 0 { + t.Errorf("expected io.EOF and n == 0, got error %q and n == %d", err, n) + } + connClosed <- struct{}{} + }() + select { + case <-connClosed: + // OK + case <-newTransportCalled: + t.Error("newTransport called for second attempt") + case <-time.After(timeout): + t.Error("connection not closed within timeout") + } +} + +func listenFakeAddr(network, laddr string, remoteAddr net.Addr) (net.Listener, error) { + l, err := net.Listen(network, laddr) + if err == nil { + l = &fakeAddrListener{l, remoteAddr} + } + return l, err +} + +// fakeAddrListener is a listener that creates connections with a mocked remote address. +type fakeAddrListener struct { + net.Listener + remoteAddr net.Addr +} + +type fakeAddrConn struct { + net.Conn + remoteAddr net.Addr +} + +func (l *fakeAddrListener) Accept() (net.Conn, error) { + c, err := l.Listener.Accept() + if err != nil { + return nil, err + } + return &fakeAddrConn{c, l.remoteAddr}, nil +} + +func (c *fakeAddrConn) RemoteAddr() net.Addr { + return c.remoteAddr +} + +func syncAddPeer(srv *Server, node *enode.Node) bool { + var ( + ch = make(chan *PeerEvent) + sub = srv.SubscribeEvents(ch) + timeout = time.After(2 * time.Second) + ) + defer sub.Unsubscribe() + srv.AddPeer(node) + for { + select { + case ev := <-ch: + if ev.Type == PeerEventTypeAdd && ev.Peer == node.ID() { + return true + } + case <-timeout: + return false + } + } +} diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index 13bd4f6d50..21abeab9a9 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -19,7 +19,6 @@ package adapters import ( "bytes" "context" - "crypto/ecdsa" "encoding/json" "errors" "fmt" @@ -98,6 +97,7 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { if err != nil { return nil, err } + // generate the config conf := &execNodeConfig{ Stack: node.DefaultConfig, @@ -108,6 +108,8 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { } else { conf.Stack.DataDir = filepath.Join(dir, "data") } + + // these parameters are crucial for execadapter node to run correctly conf.Stack.WSHost = "127.0.0.1" conf.Stack.WSPort = 0 conf.Stack.WSOrigins = []string{"*"} @@ -117,7 +119,7 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { conf.Stack.P2P.NAT = nil conf.Stack.NoUSB = true - // listen on a localhost port, which we set when we + // Listen on a localhost port, which we set when we // initialise NodeConfig (usually a random port) conf.Stack.P2P.ListenAddr = fmt.Sprintf(":%d", config.Port) @@ -145,7 +147,6 @@ type ExecNode struct { client *rpc.Client wsAddr string newCmd func() *exec.Cmd - key *ecdsa.PrivateKey } // Addr returns the node's enode URL @@ -209,7 +210,7 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { if status.Err != "" { return errors.New(status.Err) } - client, err := rpc.DialWebsocket(ctx, status.WSEndpoint, "http://localhost") + client, err := rpc.DialWebsocket(ctx, status.WSEndpoint, "") if err != nil { return fmt.Errorf("can't connect to RPC server: %v", err) } @@ -288,7 +289,7 @@ func (n *ExecNode) Stop() error { if err := n.Cmd.Process.Signal(syscall.SIGTERM); err != nil { return n.Cmd.Process.Kill() } - waitErr := make(chan error) + waitErr := make(chan error, 1) go func() { waitErr <- n.Cmd.Wait() }() @@ -421,7 +422,7 @@ func startExecNodeStack() (*node.Node, error) { } // create enode record - nodeTcpConn, err := net.ResolveTCPAddr("tcp", conf.Stack.P2P.ListenAddr) + nodeTcpConn, _ := net.ResolveTCPAddr("tcp", conf.Stack.P2P.ListenAddr) if nodeTcpConn.IP == nil { nodeTcpConn.IP = net.IPv4(127, 0, 0, 1) } diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index b03c346f1c..fb7e031ed7 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -17,6 +17,7 @@ package adapters import ( + "context" "errors" "fmt" "math" @@ -127,7 +128,7 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) { // Dial implements the p2p.NodeDialer interface by connecting to the node using // an in-memory net.Pipe -func (s *SimAdapter) Dial(dest *enode.Node) (conn net.Conn, err error) { +func (s *SimAdapter) Dial(ctx context.Context, dest *enode.Node) (conn net.Conn, err error) { node, ok := s.GetNode(dest.ID()) if !ok { return nil, fmt.Errorf("unknown node: %s", dest.ID()) @@ -212,7 +213,7 @@ func (sn *SimNode) Client() (*rpc.Client, error) { } // ServeRPC serves RPC requests over the given connection by creating an -// in-memory client to the node's RPC server +// in-memory client to the node's RPC server. func (sn *SimNode) ServeRPC(conn *websocket.Conn) error { handler, err := sn.node.RPCHandler() if err != nil { diff --git a/p2p/simulations/adapters/inproc_test.go b/p2p/simulations/adapters/inproc_test.go index 29baf35a2e..a94f7b626b 100644 --- a/p2p/simulations/adapters/inproc_test.go +++ b/p2p/simulations/adapters/inproc_test.go @@ -20,8 +20,8 @@ import ( "bytes" "encoding/binary" "fmt" + "sync" "testing" - "time" "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/pipes" ) @@ -32,42 +32,26 @@ func TestTCPPipe(t *testing.T) { t.Fatal(err) } - done := make(chan struct{}) - - go func() { - msgs := 50 - size := 1024 - for i := 0; i < msgs; i++ { - msg := make([]byte, size) - _ = binary.PutUvarint(msg, uint64(i)) - - _, err := c1.Write(msg) - if err != nil { - t.Fatal(err) - } + msgs := 50 + size := 1024 + for i := 0; i < msgs; i++ { + msg := make([]byte, size) + binary.PutUvarint(msg, uint64(i)) + if _, err := c1.Write(msg); err != nil { + t.Fatal(err) } + } - for i := 0; i < msgs; i++ { - msg := make([]byte, size) - _ = binary.PutUvarint(msg, uint64(i)) - - out := make([]byte, size) - _, err := c2.Read(out) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(msg, out) { - t.Fatalf("expected %#v, got %#v", msg, out) - } + for i := 0; i < msgs; i++ { + msg := make([]byte, size) + binary.PutUvarint(msg, uint64(i)) + out := make([]byte, size) + if _, err := c2.Read(out); err != nil { + t.Fatal(err) + } + if !bytes.Equal(msg, out) { + t.Fatalf("expected %#v, got %#v", msg, out) } - done <- struct{}{} - }() - - select { - case <-done: - case <-time.After(5 * time.Second): - t.Fatal("test timeout") } } @@ -77,60 +61,41 @@ func TestTCPPipeBidirections(t *testing.T) { t.Fatal(err) } - done := make(chan struct{}) - - go func() { - msgs := 50 - size := 7 - for i := 0; i < msgs; i++ { - msg := []byte(fmt.Sprintf("ping %02d", i)) - - _, err := c1.Write(msg) - if err != nil { - t.Fatal(err) - } + msgs := 50 + size := 7 + for i := 0; i < msgs; i++ { + msg := []byte(fmt.Sprintf("ping %02d", i)) + if _, err := c1.Write(msg); err != nil { + t.Fatal(err) } + } - for i := 0; i < msgs; i++ { - expected := []byte(fmt.Sprintf("ping %02d", i)) - - out := make([]byte, size) - _, err := c2.Read(out) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(expected, out) { - t.Fatalf("expected %#v, got %#v", out, expected) - } else { - msg := []byte(fmt.Sprintf("pong %02d", i)) - _, err := c2.Write(msg) - if err != nil { - t.Fatal(err) - } - } + for i := 0; i < msgs; i++ { + expected := []byte(fmt.Sprintf("ping %02d", i)) + out := make([]byte, size) + if _, err := c2.Read(out); err != nil { + t.Fatal(err) } - for i := 0; i < msgs; i++ { - expected := []byte(fmt.Sprintf("pong %02d", i)) - - out := make([]byte, size) - _, err := c1.Read(out) - if err != nil { + if !bytes.Equal(expected, out) { + t.Fatalf("expected %#v, got %#v", out, expected) + } else { + msg := []byte(fmt.Sprintf("pong %02d", i)) + if _, err := c2.Write(msg); err != nil { t.Fatal(err) } - - if !bytes.Equal(expected, out) { - t.Fatalf("expected %#v, got %#v", out, expected) - } } - done <- struct{}{} - }() + } - select { - case <-done: - case <-time.After(5 * time.Second): - t.Fatal("test timeout") + for i := 0; i < msgs; i++ { + expected := []byte(fmt.Sprintf("pong %02d", i)) + out := make([]byte, size) + if _, err := c1.Read(out); err != nil { + t.Fatal(err) + } + if !bytes.Equal(expected, out) { + t.Fatalf("expected %#v, got %#v", out, expected) + } } } @@ -140,46 +105,35 @@ func TestNetPipe(t *testing.T) { t.Fatal(err) } - done := make(chan struct{}) + msgs := 50 + size := 1024 + var wg sync.WaitGroup + defer wg.Wait() + // netPipe is blocking, so writes are emitted asynchronously + wg.Add(1) go func() { - msgs := 50 - size := 1024 - // netPipe is blocking, so writes are emitted asynchronously - go func() { - for i := 0; i < msgs; i++ { - msg := make([]byte, size) - _ = binary.PutUvarint(msg, uint64(i)) - - _, err := c1.Write(msg) - if err != nil { - t.Fatal(err) - } - } - }() + defer wg.Done() for i := 0; i < msgs; i++ { msg := make([]byte, size) - _ = binary.PutUvarint(msg, uint64(i)) - - out := make([]byte, size) - _, err := c2.Read(out) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(msg, out) { - t.Fatalf("expected %#v, got %#v", msg, out) + binary.PutUvarint(msg, uint64(i)) + if _, err := c1.Write(msg); err != nil { + t.Error(err) } } - - done <- struct{}{} }() - select { - case <-done: - case <-time.After(5 * time.Second): - t.Fatal("test timeout") + for i := 0; i < msgs; i++ { + msg := make([]byte, size) + binary.PutUvarint(msg, uint64(i)) + out := make([]byte, size) + if _, err := c2.Read(out); err != nil { + t.Error(err) + } + if !bytes.Equal(msg, out) { + t.Errorf("expected %#v, got %#v", msg, out) + } } } @@ -189,71 +143,60 @@ func TestNetPipeBidirections(t *testing.T) { t.Fatal(err) } - done := make(chan struct{}) + msgs := 1000 + size := 8 + pingTemplate := "ping %03d" + pongTemplate := "pong %03d" + var wg sync.WaitGroup + defer wg.Wait() + // netPipe is blocking, so writes are emitted asynchronously + wg.Add(1) go func() { - msgs := 1000 - size := 8 - pingTemplate := "ping %03d" - pongTemplate := "pong %03d" - - // netPipe is blocking, so writes are emitted asynchronously - go func() { - for i := 0; i < msgs; i++ { - msg := []byte(fmt.Sprintf(pingTemplate, i)) + defer wg.Done() - _, err := c1.Write(msg) - if err != nil { - t.Fatal(err) - } - } - }() - - // netPipe is blocking, so reads for pong are emitted asynchronously - go func() { - for i := 0; i < msgs; i++ { - expected := []byte(fmt.Sprintf(pongTemplate, i)) - - out := make([]byte, size) - _, err := c1.Read(out) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(expected, out) { - t.Fatalf("expected %#v, got %#v", expected, out) - } + for i := 0; i < msgs; i++ { + msg := []byte(fmt.Sprintf(pingTemplate, i)) + if _, err := c1.Write(msg); err != nil { + t.Error(err) } + } + }() - done <- struct{}{} - }() + // netPipe is blocking, so reads for pong are emitted asynchronously + wg.Add(1) + go func() { + defer wg.Done() - // expect to read pings, and respond with pongs to the alternate connection for i := 0; i < msgs; i++ { - expected := []byte(fmt.Sprintf(pingTemplate, i)) - + expected := []byte(fmt.Sprintf(pongTemplate, i)) out := make([]byte, size) - _, err := c2.Read(out) - if err != nil { - t.Fatal(err) + if _, err := c1.Read(out); err != nil { + t.Error(err) } - if !bytes.Equal(expected, out) { - t.Fatalf("expected %#v, got %#v", expected, out) - } else { - msg := []byte(fmt.Sprintf(pongTemplate, i)) - - _, err := c2.Write(msg) - if err != nil { - t.Fatal(err) - } + t.Errorf("expected %#v, got %#v", expected, out) } } }() - select { - case <-done: - case <-time.After(5 * time.Second): - t.Fatal("test timeout") + // expect to read pings, and respond with pongs to the alternate connection + for i := 0; i < msgs; i++ { + expected := []byte(fmt.Sprintf(pingTemplate, i)) + + out := make([]byte, size) + _, err := c2.Read(out) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(expected, out) { + t.Errorf("expected %#v, got %#v", expected, out) + } else { + msg := []byte(fmt.Sprintf(pongTemplate, i)) + if _, err := c2.Write(msg); err != nil { + t.Fatal(err) + } + } } } diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go index c801ad9fcc..cbefe87862 100644 --- a/p2p/simulations/adapters/types.go +++ b/p2p/simulations/adapters/types.go @@ -106,6 +106,11 @@ type NodeConfig struct { // services registered by calling the RegisterService function) Services []string + // Properties are the names of the properties this node should hold + // within running services (e.g. "bootnode", "lightnode" or any custom values) + // These values need to be checked and acted upon by node Services + Properties []string + // Enode node *enode.Node @@ -125,6 +130,7 @@ type nodeConfigJSON struct { PrivateKey string `json:"private_key"` Name string `json:"name"` Services []string `json:"services"` + Properties []string `json:"properties"` EnableMsgEvents bool `json:"enable_msg_events"` Port uint16 `json:"port"` } @@ -136,6 +142,7 @@ func (n *NodeConfig) MarshalJSON() ([]byte, error) { ID: n.ID.String(), Name: n.Name, Services: n.Services, + Properties: n.Properties, Port: n.Port, EnableMsgEvents: n.EnableMsgEvents, } @@ -173,6 +180,7 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error { n.Name = confJSON.Name n.Services = confJSON.Services + n.Properties = confJSON.Properties n.Port = confJSON.Port n.EnableMsgEvents = confJSON.EnableMsgEvents @@ -296,5 +304,5 @@ func (n *NodeConfig) initEnode(ip net.IP, tcpport int, udpport int) error { } func (n *NodeConfig) initDummyEnode() error { - return n.initEnode(net.IPv4(127, 0, 0, 1), 0, 0) + return n.initEnode(net.IPv4(127, 0, 0, 1), int(n.Port), 0) } diff --git a/p2p/simulations/base/000544.ldb b/p2p/simulations/base/000544.ldb new file mode 100644 index 0000000000000000000000000000000000000000..371d1a66f95b324ab797f0a9b972613401da0589 GIT binary patch literal 252 zcmeBVtYA=JD$YwRD9*?)Nl9`pEhDoqHa v3v3qCG<`!c5e81C62||Wj4BL?a2eH@l-OE>O^l)-5dt9mcSE;IsrzjJp{zLY literal 0 HcmV?d00001 diff --git a/p2p/simulations/base/000545.ldb b/p2p/simulations/base/000545.ldb new file mode 100644 index 0000000000000000000000000000000000000000..8e3a26d6b13e081a9467538a5a1ce460f3ee1f51 GIT binary patch literal 252 zcmeBVtYA=JD$YwRD9*?)Nl9`pEh~Dgv+SLq{P-5Y+@7zi4XwczZ<$$O5JY*0M(Q_?f?J) literal 0 HcmV?d00001 diff --git a/p2p/simulations/base/000551.ldb b/p2p/simulations/base/000551.ldb new file mode 100644 index 0000000000000000000000000000000000000000..0fdcc927140b0241554c9948d4cc250e3ef0a7fe GIT binary patch literal 252 zcmeBVtYA=JD$YwRD9*?)Nl9`pEh~Dgv+SLq{P-5Y+@7zi4XwczZ<$$O5JY*0P|@(82|tP literal 0 HcmV?d00001 diff --git a/p2p/simulations/base/000557.ldb b/p2p/simulations/base/000557.ldb new file mode 100644 index 0000000000000000000000000000000000000000..14276a0febfe5a6705d78607dcf4f79592c43786 GIT binary patch literal 252 zcmeBVtYA=JD$YwRD9*?)Nl9`pEhuoFr94j;$ zIJlZtuz^H5ftZ`&i~B!Dy)6uiOlg@pC8tk7x#aRdRrJ2nbIYpTa#G7ub5fG@oJxWG%sikq`MGXjsWc&w vF0ffl)AS9+L>M@kN*MohGO92n!evxrQetZjHZh8VLb?NitVx(G=LliAYcSx_m`UjI96yh zaBwxPU;~MA0x>tk7x#aRdRrJ2nbIYpTa#G7ub5fG@oJxWG%sikq`MGXjsWc&w vF0ffl)AS9+L>M@kN*MohGO92n!evxrQetZjHZh8VLtk7x#aRdRrJ2nbIYpTa#G7ub5fG@oJxWG%sikq`MGXjsWc&w vF0ffl)AS9+L>M@kN*MohGO92n!evxrQetZjHZh8VLtk7x#aRdRrJ2nbIYpTa#G7ub5fG@oJxWG%sikq`MGXjsWc&w vF0ffl)AS9+L>M@kN*MohGO92n!evxrQetZjHZh8VLi#jujdW z99&H+*g&G3K+MhX#r+?n-WCQ$rnJnQlGGx-oYb<^oRlOzr&1t4GY@D@ey$r>DoqHa v3v3qCG<`!c5e81C62||Wj4BL?a2eH@l-OE>O^l)-5dt9mcSE;IsrzjJ3==th literal 0 HcmV?d00001 diff --git a/p2p/simulations/base/000576.log b/p2p/simulations/base/000576.log new file mode 100644 index 0000000000000000000000000000000000000000..63307febb0515e001b2aa410ce4399e29bd5d64c GIT binary patch literal 158 zcmew%Avt|I1LIpJ1~6a-l8my&d5HzZ8TlnCNzSE3MX7lu9+~MGsl_EuIr+)i%8wcr gKosL986_}`a7rvr^()O~I@rJ{f*~24T7p#&0DjsnF#rGn literal 0 HcmV?d00001 diff --git a/p2p/simulations/base/000578.ldb b/p2p/simulations/base/000578.ldb new file mode 100644 index 0000000000000000000000000000000000000000..70dd4e1d5c35bb91523199433bae267bf0ed7712 GIT binary patch literal 252 zcmeBVtYA=JD$YwRD9*?)Nl9`pEhi#jujdW z99&H+*g&G3K+MhX#r+?n-WCQ$rnJnQlGGx-oYb<^oRlOzr&1t4GY@D@ey$r>DoqHa v3v3qCG<`!c5e81C62||Wj4BL?a2eH@l-OE>O^l)-5dt9mcSE;IsrzjJ3==th literal 0 HcmV?d00001 diff --git a/p2p/simulations/base/CURRENT b/p2p/simulations/base/CURRENT new file mode 100644 index 0000000000..d767468ff9 --- /dev/null +++ b/p2p/simulations/base/CURRENT @@ -0,0 +1 @@ +MANIFEST-000577 diff --git a/p2p/simulations/base/CURRENT.bak b/p2p/simulations/base/CURRENT.bak new file mode 100644 index 0000000000..992dea4df5 --- /dev/null +++ b/p2p/simulations/base/CURRENT.bak @@ -0,0 +1 @@ +MANIFEST-000574 diff --git a/p2p/simulations/base/LOCK b/p2p/simulations/base/LOCK new file mode 100644 index 0000000000..e69de29bb2 diff --git a/p2p/simulations/base/LOG b/p2p/simulations/base/LOG new file mode 100644 index 0000000000..dfcac2edaf --- /dev/null +++ b/p2p/simulations/base/LOG @@ -0,0 +1,3039 @@ +=============== Oct 25, 2021 (CST) =============== +18:12:09.613431 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.158100 db@open opening +18:12:10.158294 version@stat F·[] S·0B[] Sc·[] +18:12:10.160133 db@janitor F·2 G·0 +18:12:10.160144 db@open done T·2.0299ms +18:12:10.160349 db@close closing +18:12:10.160419 db@close done T·69.5µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.160605 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.160704 version@stat F·[] S·0B[] Sc·[] +18:12:10.160731 db@open opening +18:12:10.160756 journal@recovery F·1 +18:12:10.160829 journal@recovery recovering @1 +18:12:10.163262 memdb@flush created L0@2 N·3 S·250B "sna..Num,v2":"sna..Set,v3" +18:12:10.163697 version@stat F·[1] S·250B[250B] Sc·[0.25] +18:12:10.171196 db@janitor F·3 G·0 +18:12:10.171212 db@open done T·10.4736ms +18:12:10.171473 db@close closing +18:12:10.171523 db@close done T·50.6µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.171718 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.171783 version@stat F·[1] S·250B[250B] Sc·[0.25] +18:12:10.171809 db@open opening +18:12:10.171837 journal@recovery F·1 +18:12:10.172751 journal@recovery recovering @3 +18:12:10.176081 memdb@flush created L0@5 N·3 S·250B "sna..Num,v6":"sna..Set,v7" +18:12:10.176464 version@stat F·[2] S·500B[500B] Sc·[0.50] +18:12:10.183964 db@janitor F·4 G·0 +18:12:10.183975 db@open done T·12.1596ms +18:12:10.184158 db@close closing +18:12:10.184248 db@close done T·88.4µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.184449 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.184512 version@stat F·[2] S·500B[500B] Sc·[0.50] +18:12:10.184538 db@open opening +18:12:10.184568 journal@recovery F·1 +18:12:10.184858 journal@recovery recovering @6 +18:12:10.187208 memdb@flush created L0@8 N·3 S·250B "sna..Num,v10":"sna..Set,v11" +18:12:10.187652 version@stat F·[3] S·750B[750B] Sc·[0.75] +18:12:10.194618 db@janitor F·5 G·0 +18:12:10.194630 db@open done T·10.0852ms +18:12:10.194806 db@close closing +18:12:10.194841 db@close done T·35.2µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.195039 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.195124 version@stat F·[3] S·750B[750B] Sc·[0.75] +18:12:10.195152 db@open opening +18:12:10.195183 journal@recovery F·1 +18:12:10.195259 journal@recovery recovering @9 +18:12:10.198045 memdb@flush created L0@11 N·3 S·250B "sna..Num,v14":"sna..Set,v15" +18:12:10.198183 version@stat F·[4] S·1000B[1000B] Sc·[1.00] +18:12:10.203391 db@janitor F·6 G·0 +18:12:10.203403 db@open done T·8.2439ms +18:12:10.203496 table@compaction L0·4 -> L1·0 S·1000B Q·16 +18:12:10.203551 db@close closing +18:12:10.205995 table@build created L1@14 N·3 S·250B "sna..Num,v14":"sna..Set,v15" +18:12:10.206010 table@build exiting +18:12:10.206015 table@build revert @14 +18:12:10.206101 db@close done T·2.5487ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.206347 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.206418 version@stat F·[4] S·1000B[1000B] Sc·[1.00] +18:12:10.206444 db@open opening +18:12:10.206476 journal@recovery F·1 +18:12:10.206852 journal@recovery recovering @12 +18:12:10.209514 memdb@flush created L0@14 N·3 S·250B "sna..Num,v18":"sna..Set,v19" +18:12:10.209638 version@stat F·[5] S·1KiB[1KiB] Sc·[1.25] +18:12:10.215054 db@janitor F·7 G·0 +18:12:10.215066 db@open done T·8.6156ms +18:12:10.215104 table@compaction L0·5 -> L1·0 S·1KiB Q·20 +18:12:10.215379 db@close closing +18:12:10.217081 table@build created L1@17 N·3 S·250B "sna..Num,v18":"sna..Set,v19" +18:12:10.217098 table@build exiting +18:12:10.217104 table@build revert @17 +18:12:10.217172 db@close done T·1.7927ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.217383 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.217450 version@stat F·[5] S·1KiB[1KiB] Sc·[1.25] +18:12:10.217479 db@open opening +18:12:10.217512 journal@recovery F·1 +18:12:10.218055 journal@recovery recovering @15 +18:12:10.221100 memdb@flush created L0@17 N·3 S·250B "sna..Num,v22":"sna..Set,v23" +18:12:10.221542 version@stat F·[6] S·1KiB[1KiB] Sc·[1.50] +18:12:10.226358 db@janitor F·8 G·0 +18:12:10.226385 db@open done T·8.8993ms +18:12:10.226452 table@compaction L0·6 -> L1·0 S·1KiB Q·24 +18:12:10.226663 db@close closing +18:12:10.228208 table@build created L1@20 N·3 S·250B "sna..Num,v22":"sna..Set,v23" +18:12:10.228217 table@build exiting +18:12:10.228221 table@build revert @20 +18:12:10.228287 db@close done T·1.6246ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.228492 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.228552 version@stat F·[6] S·1KiB[1KiB] Sc·[1.50] +18:12:10.228578 db@open opening +18:12:10.228611 journal@recovery F·1 +18:12:10.228919 journal@recovery recovering @18 +18:12:10.231113 memdb@flush created L0@20 N·3 S·250B "sna..Num,v26":"sna..Set,v27" +18:12:10.231491 version@stat F·[7] S·1KiB[1KiB] Sc·[1.75] +18:12:10.236214 db@janitor F·9 G·0 +18:12:10.236226 db@open done T·7.6418ms +18:12:10.236269 table@compaction L0·7 -> L1·0 S·1KiB Q·28 +18:12:10.236534 db@close closing +18:12:10.239151 table@build created L1@23 N·3 S·250B "sna..Num,v26":"sna..Set,v27" +18:12:10.239169 table@build exiting +18:12:10.239173 table@build revert @23 +18:12:10.239263 db@close done T·2.7285ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.239506 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.239578 version@stat F·[7] S·1KiB[1KiB] Sc·[1.75] +18:12:10.239603 db@open opening +18:12:10.239639 journal@recovery F·1 +18:12:10.240081 journal@recovery recovering @21 +18:12:10.242242 memdb@flush created L0@23 N·3 S·250B "sna..Num,v30":"sna..Set,v31" +18:12:10.242611 version@stat F·[8] S·1KiB[1KiB] Sc·[2.00] +18:12:10.247825 db@janitor F·10 G·0 +18:12:10.247853 db@open done T·8.2423ms +18:12:10.247901 table@compaction L0·8 -> L1·0 S·1KiB Q·32 +18:12:10.249685 table@build created L1@26 N·3 S·250B "sna..Num,v30":"sna..Set,v31" +18:12:10.249722 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:10.249816 db@close closing +18:12:10.251158 table@commit exiting +18:12:10.251176 db@write was delayed N·1 T·1.692ms +18:12:10.251201 db@close done T·1.3853ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.251452 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.251540 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:10.251581 db@open opening +18:12:10.251621 journal@recovery F·1 +18:12:10.252034 journal@recovery recovering @24 +18:12:10.254191 memdb@flush created L0@27 N·3 S·250B "sna..Num,v34":"sna..Set,v35" +18:12:10.254858 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:10.260079 db@janitor F·12 G·8 +18:12:10.260089 db@janitor removing table-5 +18:12:10.260109 db@janitor removing table-11 +18:12:10.260130 db@janitor removing table-8 +18:12:10.260148 db@janitor removing table-2 +18:12:10.260164 db@janitor removing table-20 +18:12:10.260180 db@janitor removing table-23 +18:12:10.260196 db@janitor removing table-14 +18:12:10.260212 db@janitor removing table-17 +18:12:10.260247 db@open done T·8.6564ms +18:12:10.260376 db@close closing +18:12:10.260409 db@close done T·32.8µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.273249 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.273336 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:10.273363 db@open opening +18:12:10.273392 journal@recovery F·1 +18:12:10.273712 journal@recovery recovering @28 +18:12:10.277549 memdb@flush created L0@30 N·3 S·250B "sna..Num,v38":"sna..Set,v39" +18:12:10.277894 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:10.284048 db@janitor F·5 G·0 +18:12:10.284079 db@open done T·10.7073ms +18:12:10.284266 db@close closing +18:12:10.284312 db@close done T·46.8µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.284538 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.284613 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:10.284639 db@open opening +18:12:10.284670 journal@recovery F·1 +18:12:10.285189 journal@recovery recovering @31 +18:12:10.287551 memdb@flush created L0@33 N·3 S·250B "sna..Num,v42":"sna..Set,v43" +18:12:10.288074 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] +18:12:10.293802 db@janitor F·6 G·0 +18:12:10.293814 db@open done T·9.1679ms +18:12:10.293981 db@close closing +18:12:10.294018 db@close done T·38µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.294220 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.294295 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] +18:12:10.294320 db@open opening +18:12:10.294351 journal@recovery F·1 +18:12:10.294675 journal@recovery recovering @34 +18:12:10.296772 memdb@flush created L0@36 N·3 S·250B "sna..Num,v46":"sna..Set,v47" +18:12:10.297223 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] +18:12:10.302631 db@janitor F·7 G·0 +18:12:10.302644 db@open done T·8.3172ms +18:12:10.302675 table@compaction L0·4 -> L1·1 S·1KiB Q·48 +18:12:10.302859 db@close closing +18:12:10.304725 table@build created L1@39 N·3 S·250B "sna..Num,v46":"sna..Set,v47" +18:12:10.304746 table@build exiting +18:12:10.304751 table@build revert @39 +18:12:10.304847 db@close done T·1.9866ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.305079 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.305158 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] +18:12:10.305185 db@open opening +18:12:10.305218 journal@recovery F·1 +18:12:10.305716 journal@recovery recovering @37 +18:12:10.308159 memdb@flush created L0@39 N·3 S·250B "sna..Num,v50":"sna..Set,v51" +18:12:10.308731 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:10.313963 db@janitor F·8 G·0 +18:12:10.313973 db@open done T·8.7816ms +18:12:10.314028 table@compaction L0·5 -> L1·1 S·1KiB Q·52 +18:12:10.314186 db@close closing +18:12:10.314277 db@close done T·90.1µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.314459 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.314520 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:10.314546 db@open opening +18:12:10.314579 journal@recovery F·1 +18:12:10.314644 journal@recovery recovering @40 +18:12:10.316609 memdb@flush created L0@42 N·3 S·250B "sna..Num,v54":"sna..Set,v55" +18:12:10.317028 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:10.323515 db@janitor F·9 G·0 +18:12:10.323530 db@open done T·8.9775ms +18:12:10.323573 table@compaction L0·6 -> L1·1 S·1KiB Q·56 +18:12:10.323736 db@close closing +18:12:10.325889 table@build created L1@45 N·3 S·250B "sna..Num,v54":"sna..Set,v55" +18:12:10.325899 table@build exiting +18:12:10.325902 table@build revert @45 +18:12:10.325986 db@close done T·2.2493ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.326216 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.326302 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:10.326332 db@open opening +18:12:10.326366 journal@recovery F·1 +18:12:10.326676 journal@recovery recovering @43 +18:12:10.328673 memdb@flush created L0@45 N·3 S·250B "sna..Num,v58":"sna..Set,v59" +18:12:10.329009 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:10.334169 db@janitor F·10 G·0 +18:12:10.334179 db@open done T·7.8398ms +18:12:10.334228 table@compaction L0·7 -> L1·1 S·1KiB Q·60 +18:12:10.334410 db@close closing +18:12:10.337354 table@build created L1@48 N·3 S·250B "sna..Num,v58":"sna..Set,v59" +18:12:10.337377 table@build exiting +18:12:10.337382 table@build revert @48 +18:12:10.337480 db@close done T·3.0693ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.337686 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.337766 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:10.337794 db@open opening +18:12:10.337829 journal@recovery F·1 +18:12:10.338192 journal@recovery recovering @46 +18:12:10.341247 memdb@flush created L0@48 N·3 S·250B "sna..Num,v62":"sna..Set,v63" +18:12:10.341595 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:10.348172 db@janitor F·11 G·0 +18:12:10.348183 db@open done T·10.3826ms +18:12:10.348260 table@compaction L0·8 -> L1·1 S·2KiB Q·64 +18:12:10.350205 db@close closing +18:12:10.351071 table@build created L1@51 N·3 S·250B "sna..Num,v62":"sna..Set,v63" +18:12:10.351080 table@build exiting +18:12:10.351084 table@build revert @51 +18:12:10.351136 db@write was delayed N·1 T·1.7224ms +18:12:10.351161 db@close done T·958.3µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.351363 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.351433 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:10.351459 db@open opening +18:12:10.351494 journal@recovery F·1 +18:12:10.351845 journal@recovery recovering @49 +18:12:10.354453 memdb@flush created L0@51 N·3 S·250B "sna..Num,v66":"sna..Set,v67" +18:12:10.355144 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] +18:12:10.360812 db@janitor F·12 G·0 +18:12:10.360825 db@open done T·9.3581ms +18:12:10.360923 table@compaction L0·9 -> L1·1 S·2KiB Q·68 +18:12:10.362533 db@close closing +18:12:10.363647 table@build created L1@54 N·3 S·250B "sna..Num,v66":"sna..Set,v67" +18:12:10.363658 table@build exiting +18:12:10.363662 table@build revert @54 +18:12:10.363722 db@write was delayed N·1 T·1.4564ms +18:12:10.363770 db@close done T·1.2384ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.363987 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.364080 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] +18:12:10.364108 db@open opening +18:12:10.364144 journal@recovery F·1 +18:12:10.364464 journal@recovery recovering @52 +18:12:10.366318 memdb@flush created L0@54 N·3 S·250B "sna..Num,v70":"sna..Set,v71" +18:12:10.366669 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] +18:12:10.372783 db@janitor F·13 G·0 +18:12:10.372797 db@open done T·8.682ms +18:12:10.372860 table@compaction L0·10 -> L1·1 S·2KiB Q·72 +18:12:10.374273 db@close closing +18:12:10.374742 table@build created L1@57 N·3 S·250B "sna..Num,v70":"sna..Set,v71" +18:12:10.374750 table@build exiting +18:12:10.374754 table@build revert @57 +18:12:10.374803 db@write was delayed N·1 T·1.1525ms +18:12:10.374829 db@close done T·557.6µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.375021 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.375095 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] +18:12:10.375122 db@open opening +18:12:10.375159 journal@recovery F·1 +18:12:10.375463 journal@recovery recovering @55 +18:12:10.377280 memdb@flush created L0@57 N·3 S·250B "sna..Num,v74":"sna..Set,v75" +18:12:10.377803 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] +18:12:10.382762 db@janitor F·14 G·0 +18:12:10.382773 db@open done T·7.6449ms +18:12:10.382811 table@compaction L0·11 -> L1·1 S·2KiB Q·76 +18:12:10.384122 db@close closing +18:12:10.384712 table@build created L1@60 N·3 S·250B "sna..Num,v74":"sna..Set,v75" +18:12:10.384720 table@build exiting +18:12:10.384723 table@build revert @60 +18:12:10.384774 db@write was delayed N·1 T·1.056ms +18:12:10.384818 db@close done T·696µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.388041 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.388138 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] +18:12:10.388164 db@open opening +18:12:10.388204 journal@recovery F·1 +18:12:10.388736 journal@recovery recovering @58 +18:12:10.391604 memdb@flush created L0@60 N·3 S·250B "sna..Num,v78":"sna..Set,v79" +18:12:10.391968 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] +18:12:10.397107 db@janitor F·15 G·0 +18:12:10.397135 db@open done T·8.9637ms +18:12:10.397175 table@compaction L0·12 -> L1·1 S·3KiB Q·80 +18:12:10.398556 db@close closing +18:12:10.398948 table@build created L1@63 N·3 S·250B "sna..Num,v78":"sna..Set,v79" +18:12:10.398956 table@build exiting +18:12:10.398959 table@build revert @63 +18:12:10.399008 db@write was delayed N·1 T·1.0222ms +18:12:10.399058 db@close done T·501.6µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.401944 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.402049 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] +18:12:10.402079 db@open opening +18:12:10.402117 journal@recovery F·1 +18:12:10.402655 journal@recovery recovering @61 +18:12:10.405546 memdb@flush created L0@63 N·3 S·250B "sna..Num,v82":"sna..Set,v83" +18:12:10.405911 version@stat F·[13 1] S·3KiB[3KiB 250B] Sc·[3.25 0.00] +18:12:10.411908 db@janitor F·16 G·0 +18:12:10.411936 db@open done T·9.8498ms +18:12:10.411981 table@compaction L0·13 -> L1·1 S·3KiB Q·84 +18:12:10.414047 table@build created L1@66 N·3 S·250B "sna..Num,v82":"sna..Set,v83" +18:12:10.414076 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:10.414149 db@close closing +18:12:10.415794 table@commit exiting +18:12:10.415813 db@write was delayed N·1 T·1.7515ms +18:12:10.415859 table@remove removed @60 +18:12:10.415927 db@close done T·1.7779ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.416131 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.416211 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:10.416238 db@open opening +18:12:10.416280 journal@recovery F·1 +18:12:10.416590 journal@recovery recovering @64 +18:12:10.418443 memdb@flush created L0@67 N·3 S·250B "sna..Num,v86":"sna..Set,v87" +18:12:10.419059 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:10.424620 db@janitor F·17 G·13 +18:12:10.424629 db@janitor removing table-48 +18:12:10.424650 db@janitor removing table-51 +18:12:10.424667 db@janitor removing table-27 +18:12:10.424683 db@janitor removing table-45 +18:12:10.424698 db@janitor removing table-36 +18:12:10.424713 db@janitor removing table-42 +18:12:10.424729 db@janitor removing table-26 +18:12:10.424745 db@janitor removing table-63 +18:12:10.424760 db@janitor removing table-33 +18:12:10.424779 db@janitor removing table-54 +18:12:10.424796 db@janitor removing table-30 +18:12:10.424811 db@janitor removing table-39 +18:12:10.424827 db@janitor removing table-57 +18:12:10.424864 db@open done T·8.6188ms +18:12:10.425015 db@close closing +18:12:10.425059 db@close done T·44.3µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.430559 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.430673 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:10.430702 db@open opening +18:12:10.430738 journal@recovery F·1 +18:12:10.431140 journal@recovery recovering @68 +18:12:10.435981 memdb@flush created L0@70 N·3 S·250B "sna..Num,v90":"sna..Set,v91" +18:12:10.436698 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:10.445434 db@janitor F·5 G·0 +18:12:10.445447 db@open done T·14.7317ms +18:12:10.445667 db@close closing +18:12:10.445710 db@close done T·43.8µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.445995 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.446057 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:10.446085 db@open opening +18:12:10.446115 journal@recovery F·1 +18:12:10.446456 journal@recovery recovering @71 +18:12:10.448818 memdb@flush created L0@73 N·3 S·250B "sna..Num,v94":"sna..Set,v95" +18:12:10.449154 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] +18:12:10.454212 db@janitor F·6 G·0 +18:12:10.454224 db@open done T·8.1324ms +18:12:10.454396 db@close closing +18:12:10.454437 db@close done T·40.8µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.454700 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.454761 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] +18:12:10.454788 db@open opening +18:12:10.454820 journal@recovery F·1 +18:12:10.455162 journal@recovery recovering @74 +18:12:10.457402 memdb@flush created L0@76 N·3 S·250B "sna..Num,v98":"sna..Set,v99" +18:12:10.458054 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] +18:12:10.463787 db@janitor F·7 G·0 +18:12:10.463814 db@open done T·9.0189ms +18:12:10.463842 table@compaction L0·4 -> L1·1 S·1KiB Q·100 +18:12:10.463997 db@close closing +18:12:10.465657 table@build created L1@79 N·3 S·250B "sna..Num,v98":"sna..Set,v99" +18:12:10.465665 table@build exiting +18:12:10.465669 table@build revert @79 +18:12:10.465736 db@close done T·1.7398ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.499060 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.499173 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] +18:12:10.499207 db@open opening +18:12:10.499275 journal@recovery F·1 +18:12:10.499626 journal@recovery recovering @77 +18:12:10.504517 memdb@flush created L0@79 N·3 S·250B "sna..Num,v102":"sna..Set,v103" +18:12:10.505000 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:10.513705 db@janitor F·8 G·0 +18:12:10.513718 db@open done T·14.5022ms +18:12:10.513765 table@compaction L0·5 -> L1·1 S·1KiB Q·104 +18:12:10.513918 db@close closing +18:12:10.513971 db@close done T·52.2µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.514415 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.514486 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:10.514513 db@open opening +18:12:10.514544 journal@recovery F·1 +18:12:10.514848 journal@recovery recovering @80 +18:12:10.517499 memdb@flush created L0@82 N·3 S·250B "sna..Num,v106":"sna..Set,v107" +18:12:10.517879 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:10.525445 db@janitor F·9 G·0 +18:12:10.525473 db@open done T·10.9525ms +18:12:10.525556 table@compaction L0·6 -> L1·1 S·1KiB Q·108 +18:12:10.525680 db@close closing +18:12:10.527406 table@build created L1@85 N·3 S·250B "sna..Num,v106":"sna..Set,v107" +18:12:10.527418 table@build exiting +18:12:10.527422 table@build revert @85 +18:12:10.527489 db@close done T·1.808ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.527893 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.527968 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:10.527991 db@open opening +18:12:10.528026 journal@recovery F·1 +18:12:10.528530 journal@recovery recovering @83 +18:12:10.530659 memdb@flush created L0@85 N·3 S·250B "sna..Num,v110":"sna..Set,v111" +18:12:10.531222 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:10.536897 db@janitor F·10 G·0 +18:12:10.536924 db@open done T·8.9267ms +18:12:10.536965 table@compaction L0·7 -> L1·1 S·1KiB Q·112 +18:12:10.537243 db@close closing +18:12:10.539246 table@build created L1@88 N·3 S·250B "sna..Num,v110":"sna..Set,v111" +18:12:10.539259 table@build exiting +18:12:10.539263 table@build revert @88 +18:12:10.539344 db@close done T·2.1005ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.539822 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.539896 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:10.539922 db@open opening +18:12:10.539955 journal@recovery F·1 +18:12:10.540042 journal@recovery recovering @86 +18:12:10.542105 memdb@flush created L0@88 N·3 S·250B "sna..Num,v114":"sna..Set,v115" +18:12:10.542433 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:10.549829 db@janitor F·11 G·0 +18:12:10.549856 db@open done T·9.9274ms +18:12:10.549897 table@compaction L0·8 -> L1·1 S·2KiB Q·116 +18:12:10.551390 db@close closing +18:12:10.552461 table@build created L1@91 N·3 S·250B "sna..Num,v114":"sna..Set,v115" +18:12:10.552470 table@build exiting +18:12:10.552475 table@build revert @91 +18:12:10.552528 db@write was delayed N·1 T·1.1881ms +18:12:10.552570 db@close done T·1.1808ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.553003 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.553087 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:10.553114 db@open opening +18:12:10.553149 journal@recovery F·1 +18:12:10.553482 journal@recovery recovering @89 +18:12:10.556052 memdb@flush created L0@91 N·3 S·250B "sna..Num,v118":"sna..Set,v119" +18:12:10.556399 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] +18:12:10.562956 db@janitor F·12 G·0 +18:12:10.562966 db@open done T·9.846ms +18:12:10.563012 table@compaction L0·9 -> L1·1 S·2KiB Q·120 +18:12:10.564322 db@close closing +18:12:10.565311 table@build created L1@94 N·3 S·250B "sna..Num,v118":"sna..Set,v119" +18:12:10.565319 table@build exiting +18:12:10.565322 table@build revert @94 +18:12:10.565372 db@write was delayed N·1 T·1.1424ms +18:12:10.565396 db@close done T·1.0772ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.565768 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.565836 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] +18:12:10.565866 db@open opening +18:12:10.565903 journal@recovery F·1 +18:12:10.566210 journal@recovery recovering @92 +18:12:10.567995 memdb@flush created L0@94 N·3 S·250B "sna..Num,v122":"sna..Set,v123" +18:12:10.568335 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] +18:12:10.573229 db@janitor F·13 G·0 +18:12:10.573254 db@open done T·7.3811ms +18:12:10.573289 table@compaction L0·10 -> L1·1 S·2KiB Q·124 +18:12:10.574687 db@close closing +18:12:10.575073 table@build created L1@97 N·3 S·250B "sna..Num,v122":"sna..Set,v123" +18:12:10.575081 table@build exiting +18:12:10.575084 table@build revert @97 +18:12:10.575132 db@write was delayed N·1 T·1.1805ms +18:12:10.575159 db@close done T·471.7µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.575462 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.575534 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] +18:12:10.575563 db@open opening +18:12:10.575609 journal@recovery F·1 +18:12:10.575918 journal@recovery recovering @95 +18:12:10.577631 memdb@flush created L0@97 N·3 S·250B "sna..Num,v126":"sna..Set,v127" +18:12:10.578000 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] +18:12:10.582726 db@janitor F·14 G·0 +18:12:10.582753 db@open done T·7.183ms +18:12:10.582799 table@compaction L0·11 -> L1·1 S·2KiB Q·128 +18:12:10.584404 table@build created L1@100 N·3 S·250B "sna..Num,v126":"sna..Set,v127" +18:12:10.584441 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:10.584483 db@close closing +18:12:10.586289 table@commit exiting +18:12:10.586307 db@write was delayed N·1 T·1.4164ms +18:12:10.586350 table@remove removed @94 +18:12:10.586421 db@close done T·1.938ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.586700 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.586774 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:10.586806 db@open opening +18:12:10.586847 journal@recovery F·1 +18:12:10.587178 journal@recovery recovering @98 +18:12:10.588955 memdb@flush created L0@101 N·3 S·251B "sna..Num,v130":"sna..Set,v131" +18:12:10.589297 version@stat F·[1 1] S·501B[251B 250B] Sc·[0.25 0.00] +18:12:10.594368 db@janitor F·15 G·11 +18:12:10.594375 db@janitor removing table-70 +18:12:10.594395 db@janitor removing table-88 +18:12:10.594412 db@janitor removing table-67 +18:12:10.594429 db@janitor removing table-76 +18:12:10.594445 db@janitor removing table-73 +18:12:10.594461 db@janitor removing table-91 +18:12:10.594476 db@janitor removing table-79 +18:12:10.594492 db@janitor removing table-82 +18:12:10.594507 db@janitor removing table-85 +18:12:10.594523 db@janitor removing table-97 +18:12:10.594539 db@janitor removing table-66 +18:12:10.594573 db@open done T·7.7596ms +18:12:10.594673 db@close closing +18:12:10.594708 db@close done T·35.1µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.594998 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.595055 version@stat F·[1 1] S·501B[251B 250B] Sc·[0.25 0.00] +18:12:10.595065 db@open opening +18:12:10.595087 journal@recovery F·1 +18:12:10.595645 journal@recovery recovering @102 +18:12:10.598093 memdb@flush created L0@104 N·3 S·250B "sna..Num,v134":"sna..Set,v135" +18:12:10.598469 version@stat F·[2 1] S·751B[501B 250B] Sc·[0.50 0.00] +18:12:10.603198 db@janitor F·5 G·0 +18:12:10.603218 db@open done T·8.1495ms +18:12:10.603334 db@close closing +18:12:10.603355 db@close done T·19.6µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.603587 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.603645 version@stat F·[2 1] S·751B[501B 250B] Sc·[0.50 0.00] +18:12:10.603656 db@open opening +18:12:10.603678 journal@recovery F·1 +18:12:10.604021 journal@recovery recovering @105 +18:12:10.605879 memdb@flush created L0@107 N·3 S·250B "sna..Num,v138":"sna..Set,v139" +18:12:10.606232 version@stat F·[3 1] S·1001B[751B 250B] Sc·[0.75 0.00] +18:12:10.611124 db@janitor F·6 G·0 +18:12:10.611148 db@open done T·7.4881ms +18:12:10.611290 db@close closing +18:12:10.611331 db@close done T·41.9µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.611555 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.611620 version@stat F·[3 1] S·1001B[751B 250B] Sc·[0.75 0.00] +18:12:10.611647 db@open opening +18:12:10.611677 journal@recovery F·1 +18:12:10.611978 journal@recovery recovering @108 +18:12:10.613853 memdb@flush created L0@110 N·3 S·250B "sna..Num,v142":"sna..Set,v143" +18:12:10.614402 version@stat F·[4 1] S·1KiB[1001B 250B] Sc·[1.00 0.00] +18:12:10.619428 db@janitor F·7 G·0 +18:12:10.619455 db@open done T·7.801ms +18:12:10.619508 table@compaction L0·4 -> L1·1 S·1KiB Q·144 +18:12:10.619632 db@close closing +18:12:10.619682 db@close done T·49.5µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.619884 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.619948 version@stat F·[4 1] S·1KiB[1001B 250B] Sc·[1.00 0.00] +18:12:10.619992 db@open opening +18:12:10.620024 journal@recovery F·1 +18:12:10.620334 journal@recovery recovering @111 +18:12:10.622532 memdb@flush created L0@113 N·3 S·250B "sna..Num,v146":"sna..Set,v147" +18:12:10.622882 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:10.627674 db@janitor F·8 G·0 +18:12:10.627697 db@open done T·7.6987ms +18:12:10.627738 table@compaction L0·5 -> L1·1 S·1KiB Q·148 +18:12:10.627875 db@close closing +18:12:10.629447 table@build created L1@116 N·3 S·250B "sna..Num,v146":"sna..Set,v147" +18:12:10.629456 table@build exiting +18:12:10.629460 table@build revert @116 +18:12:10.629524 db@close done T·1.6484ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.670634 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.670753 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:10.670766 db@open opening +18:12:10.670807 journal@recovery F·1 +18:12:10.671148 journal@recovery recovering @114 +18:12:10.677376 memdb@flush created L0@116 N·3 S·250B "sna..Num,v150":"sna..Set,v151" +18:12:10.677686 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:10.685482 db@janitor F·9 G·0 +18:12:10.685492 db@open done T·14.7217ms +18:12:10.685569 table@compaction L0·6 -> L1·1 S·1KiB Q·152 +18:12:10.685690 db@close closing +18:12:10.687313 table@build created L1@119 N·3 S·250B "sna..Num,v150":"sna..Set,v151" +18:12:10.687322 table@build exiting +18:12:10.687326 table@build revert @119 +18:12:10.687406 db@close done T·1.7155ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.687879 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.687952 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:10.687980 db@open opening +18:12:10.688017 journal@recovery F·1 +18:12:10.688324 journal@recovery recovering @117 +18:12:10.690447 memdb@flush created L0@119 N·3 S·250B "sna..Num,v154":"sna..Set,v155" +18:12:10.690793 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:10.696156 db@janitor F·10 G·0 +18:12:10.696180 db@open done T·8.1917ms +18:12:10.696211 table@compaction L0·7 -> L1·1 S·1KiB Q·156 +18:12:10.696476 db@close closing +18:12:10.698135 table@build created L1@122 N·3 S·250B "sna..Num,v154":"sna..Set,v155" +18:12:10.698147 table@build exiting +18:12:10.698150 table@build revert @122 +18:12:10.698239 db@close done T·1.7615ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.698693 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.698764 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:10.698794 db@open opening +18:12:10.698829 journal@recovery F·1 +18:12:10.699157 journal@recovery recovering @120 +18:12:10.701181 memdb@flush created L0@122 N·3 S·251B "sna..Num,v158":"sna..Set,v159" +18:12:10.701601 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:10.706189 db@janitor F·11 G·0 +18:12:10.706215 db@open done T·7.4131ms +18:12:10.706251 table@compaction L0·8 -> L1·1 S·2KiB Q·160 +18:12:10.707990 table@build created L1@125 N·3 S·251B "sna..Num,v158":"sna..Set,v159" +18:12:10.708002 table@commit exiting +18:12:10.708002 db@close closing +18:12:10.708022 db@write was delayed N·1 T·1.5374ms +18:12:10.708046 db@close done T·44.5µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.708464 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.708536 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:10.708565 db@open opening +18:12:10.708602 journal@recovery F·1 +18:12:10.709108 journal@recovery recovering @123 +18:12:10.711507 memdb@flush created L0@125 N·3 S·250B "sna..Num,v162":"sna..Set,v163" +18:12:10.711621 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] +18:12:10.716543 db@janitor F·12 G·0 +18:12:10.716570 db@open done T·7.9981ms +18:12:10.716608 table@compaction L0·9 -> L1·1 S·2KiB Q·164 +18:12:10.718343 table@build created L1@128 N·3 S·250B "sna..Num,v162":"sna..Set,v163" +18:12:10.718382 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:10.718469 db@close closing +18:12:10.719920 table@commit exiting +18:12:10.719954 db@write was delayed N·1 T·1.629ms +18:12:10.719980 db@close done T·1.5115ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.720381 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.720466 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:10.720494 db@open opening +18:12:10.720531 journal@recovery F·1 +18:12:10.720601 journal@recovery recovering @126 +18:12:10.722507 memdb@flush created L0@129 N·3 S·250B "sna..Num,v166":"sna..Set,v167" +18:12:10.722686 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:10.727944 db@janitor F·14 G·10 +18:12:10.727951 db@janitor removing table-101 +18:12:10.727971 db@janitor removing table-104 +18:12:10.727989 db@janitor removing table-113 +18:12:10.728005 db@janitor removing table-100 +18:12:10.728021 db@janitor removing table-122 +18:12:10.728037 db@janitor removing table-119 +18:12:10.728052 db@janitor removing table-116 +18:12:10.728067 db@janitor removing table-107 +18:12:10.728083 db@janitor removing table-110 +18:12:10.728100 db@janitor removing table-125 +18:12:10.728135 db@open done T·7.6336ms +18:12:10.728272 db@close closing +18:12:10.728313 db@close done T·40.6µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.728682 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.728750 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:10.728778 db@open opening +18:12:10.728805 journal@recovery F·1 +18:12:10.728872 journal@recovery recovering @130 +18:12:10.732171 memdb@flush created L0@132 N·3 S·250B "sna..Num,v170":"sna..Set,v171" +18:12:10.732553 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:10.740240 db@janitor F·5 G·0 +18:12:10.740252 db@open done T·11.4669ms +18:12:10.740367 db@close closing +18:12:10.740397 db@close done T·29.5µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.740722 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.740789 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:10.740816 db@open opening +18:12:10.740856 journal@recovery F·1 +18:12:10.741221 journal@recovery recovering @133 +18:12:10.744258 memdb@flush created L0@135 N·3 S·250B "sna..Num,v174":"sna..Set,v175" +18:12:10.744764 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] +18:12:10.752039 db@janitor F·6 G·0 +18:12:10.752066 db@open done T·11.2425ms +18:12:10.752230 db@close closing +18:12:10.752264 db@close done T·33.9µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.752566 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.752638 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] +18:12:10.752665 db@open opening +18:12:10.752696 journal@recovery F·1 +18:12:10.752999 journal@recovery recovering @136 +18:12:10.755341 memdb@flush created L0@138 N·3 S·250B "sna..Num,v178":"sna..Set,v179" +18:12:10.755679 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] +18:12:10.760872 db@janitor F·7 G·0 +18:12:10.760900 db@open done T·8.2272ms +18:12:10.760941 table@compaction L0·4 -> L1·1 S·1KiB Q·180 +18:12:10.761103 db@close closing +18:12:10.762669 table@build created L1@141 N·3 S·250B "sna..Num,v178":"sna..Set,v179" +18:12:10.762678 table@build exiting +18:12:10.762682 table@build revert @141 +18:12:10.762749 db@close done T·1.6458ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.763058 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.763132 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] +18:12:10.763159 db@open opening +18:12:10.763190 journal@recovery F·1 +18:12:10.763510 journal@recovery recovering @139 +18:12:10.765867 memdb@flush created L0@141 N·3 S·250B "sna..Num,v182":"sna..Set,v183" +18:12:10.766208 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:10.770802 db@janitor F·8 G·0 +18:12:10.770828 db@open done T·7.6625ms +18:12:10.770867 table@compaction L0·5 -> L1·1 S·1KiB Q·184 +18:12:10.771105 db@close closing +18:12:10.772622 table@build created L1@144 N·3 S·250B "sna..Num,v182":"sna..Set,v183" +18:12:10.772632 table@build exiting +18:12:10.772636 table@build revert @144 +18:12:10.772704 db@close done T·1.5983ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.772956 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.773028 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:10.773059 db@open opening +18:12:10.773091 journal@recovery F·1 +18:12:10.773420 journal@recovery recovering @142 +18:12:10.775339 memdb@flush created L0@144 N·3 S·250B "sna..Num,v186":"sna..Set,v187" +18:12:10.775700 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:10.780692 db@janitor F·9 G·0 +18:12:10.780703 db@open done T·7.6369ms +18:12:10.780758 table@compaction L0·6 -> L1·1 S·1KiB Q·188 +18:12:10.780897 db@close closing +18:12:10.780950 db@close done T·52.7µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.781223 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.781292 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:10.781319 db@open opening +18:12:10.781363 journal@recovery F·1 +18:12:10.781867 journal@recovery recovering @145 +18:12:10.784342 memdb@flush created L0@147 N·3 S·250B "sna..Num,v190":"sna..Set,v191" +18:12:10.784721 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:10.789819 db@janitor F·10 G·0 +18:12:10.789845 db@open done T·8.5191ms +18:12:10.789900 table@compaction L0·7 -> L1·1 S·1KiB Q·192 +18:12:10.790071 db@close closing +18:12:10.791680 table@build created L1@150 N·3 S·250B "sna..Num,v190":"sna..Set,v191" +18:12:10.791689 table@build exiting +18:12:10.791692 table@build revert @150 +18:12:10.791759 db@close done T·1.6879ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.791967 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.792032 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:10.792041 db@open opening +18:12:10.792070 journal@recovery F·1 +18:12:10.792371 journal@recovery recovering @148 +18:12:10.794399 memdb@flush created L0@150 N·3 S·250B "sna..Num,v194":"sna..Set,v195" +18:12:10.794772 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:10.800002 db@janitor F·11 G·0 +18:12:10.800027 db@open done T·7.981ms +18:12:10.800070 table@compaction L0·8 -> L1·1 S·2KiB Q·196 +18:12:10.801411 db@close closing +18:12:10.802016 table@build created L1@153 N·3 S·250B "sna..Num,v194":"sna..Set,v195" +18:12:10.802024 table@build exiting +18:12:10.802028 table@build revert @153 +18:12:10.802077 db@write was delayed N·1 T·1.1383ms +18:12:10.802105 db@close done T·691.8µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.802271 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.802337 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:10.802364 db@open opening +18:12:10.802417 journal@recovery F·1 +18:12:10.802878 journal@recovery recovering @151 +18:12:10.805251 memdb@flush created L0@153 N·3 S·250B "sna..Num,v198":"sna..Set,v199" +18:12:10.805605 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] +18:12:10.810312 db@janitor F·12 G·0 +18:12:10.810324 db@open done T·7.9523ms +18:12:10.810375 table@compaction L0·9 -> L1·1 S·2KiB Q·200 +18:12:10.812060 table@build created L1@156 N·3 S·250B "sna..Num,v198":"sna..Set,v199" +18:12:10.812097 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:10.812162 db@close closing +18:12:10.813828 table@commit exiting +18:12:10.813873 db@write was delayed N·1 T·1.5506ms +18:12:10.813902 db@close done T·1.7378ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.828324 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.828431 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:10.828444 db@open opening +18:12:10.828494 journal@recovery F·1 +18:12:10.828861 journal@recovery recovering @154 +18:12:10.834054 memdb@flush created L0@157 N·3 S·250B "sna..Num,v202":"sna..Set,v203" +18:12:10.834425 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:10.843450 db@janitor F·14 G·10 +18:12:10.843458 db@janitor removing table-153 +18:12:10.843478 db@janitor removing table-128 +18:12:10.843495 db@janitor removing table-147 +18:12:10.843512 db@janitor removing table-138 +18:12:10.843529 db@janitor removing table-135 +18:12:10.843545 db@janitor removing table-132 +18:12:10.843561 db@janitor removing table-129 +18:12:10.843577 db@janitor removing table-141 +18:12:10.843592 db@janitor removing table-144 +18:12:10.843607 db@janitor removing table-150 +18:12:10.843642 db@open done T·15.193ms +18:12:10.843752 db@close closing +18:12:10.843788 db@close done T·35µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.844061 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.844120 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:10.844130 db@open opening +18:12:10.844180 journal@recovery F·1 +18:12:10.844493 journal@recovery recovering @158 +18:12:10.847150 memdb@flush created L0@160 N·3 S·250B "sna..Num,v206":"sna..Set,v207" +18:12:10.847769 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:10.853277 db@janitor F·5 G·0 +18:12:10.853288 db@open done T·9.1407ms +18:12:10.853421 db@close closing +18:12:10.853445 db@close done T·23.5µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.853691 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.853754 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:10.853782 db@open opening +18:12:10.853812 journal@recovery F·1 +18:12:10.854134 journal@recovery recovering @161 +18:12:10.855992 memdb@flush created L0@163 N·3 S·250B "sna..Num,v210":"sna..Set,v211" +18:12:10.856332 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] +18:12:10.861453 db@janitor F·6 G·0 +18:12:10.861463 db@open done T·7.6744ms +18:12:10.861576 db@close closing +18:12:10.861620 db@close done T·42.2µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.861841 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.861899 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] +18:12:10.861927 db@open opening +18:12:10.861957 journal@recovery F·1 +18:12:10.862254 journal@recovery recovering @164 +18:12:10.864068 memdb@flush created L0@166 N·3 S·250B "sna..Num,v214":"sna..Set,v215" +18:12:10.864492 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] +18:12:10.869915 db@janitor F·7 G·0 +18:12:10.869926 db@open done T·7.9917ms +18:12:10.869997 table@compaction L0·4 -> L1·1 S·1KiB Q·216 +18:12:10.870141 db@close closing +18:12:10.871717 table@build created L1@169 N·3 S·250B "sna..Num,v214":"sna..Set,v215" +18:12:10.871725 table@build exiting +18:12:10.871729 table@build revert @169 +18:12:10.871792 db@close done T·1.6494ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.872038 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.872106 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] +18:12:10.872136 db@open opening +18:12:10.872168 journal@recovery F·1 +18:12:10.872470 journal@recovery recovering @167 +18:12:10.874336 memdb@flush created L0@169 N·3 S·251B "sna..Num,v218":"sna..Set,v219" +18:12:10.874676 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:10.879469 db@janitor F·8 G·0 +18:12:10.879494 db@open done T·7.3512ms +18:12:10.879531 table@compaction L0·5 -> L1·1 S·1KiB Q·220 +18:12:10.879783 db@close closing +18:12:10.881121 table@build created L1@172 N·3 S·251B "sna..Num,v218":"sna..Set,v219" +18:12:10.881131 table@build exiting +18:12:10.881135 table@build revert @172 +18:12:10.881200 db@close done T·1.4158ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.881433 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.881507 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:10.881536 db@open opening +18:12:10.881570 journal@recovery F·1 +18:12:10.881890 journal@recovery recovering @170 +18:12:10.883907 memdb@flush created L0@172 N·3 S·250B "sna..Num,v222":"sna..Set,v223" +18:12:10.884257 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:10.888879 db@janitor F·9 G·0 +18:12:10.888889 db@open done T·7.3463ms +18:12:10.888931 table@compaction L0·6 -> L1·1 S·1KiB Q·224 +18:12:10.889211 db@close closing +18:12:10.890500 table@build created L1@175 N·3 S·250B "sna..Num,v222":"sna..Set,v223" +18:12:10.890508 table@build exiting +18:12:10.890512 table@build revert @175 +18:12:10.890593 db@close done T·1.3811ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.890800 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.890892 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:10.890919 db@open opening +18:12:10.890954 journal@recovery F·1 +18:12:10.891258 journal@recovery recovering @173 +18:12:10.893622 memdb@flush created L0@175 N·3 S·250B "sna..Num,v226":"sna..Set,v227" +18:12:10.894239 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:10.901088 db@janitor F·10 G·0 +18:12:10.901116 db@open done T·10.1888ms +18:12:10.901167 table@compaction L0·7 -> L1·1 S·1KiB Q·228 +18:12:10.901463 db@close closing +18:12:10.903784 table@build created L1@178 N·3 S·250B "sna..Num,v226":"sna..Set,v227" +18:12:10.903793 table@build exiting +18:12:10.903797 table@build revert @178 +18:12:10.903865 db@close done T·2.4019ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.904082 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.904148 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:10.904158 db@open opening +18:12:10.904187 journal@recovery F·1 +18:12:10.904502 journal@recovery recovering @176 +18:12:10.907063 memdb@flush created L0@178 N·3 S·250B "sna..Num,v230":"sna..Set,v231" +18:12:10.907409 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:10.912843 db@janitor F·11 G·0 +18:12:10.912858 db@open done T·8.6954ms +18:12:10.912905 table@compaction L0·8 -> L1·1 S·2KiB Q·232 +18:12:10.914250 db@close closing +18:12:10.915387 table@build created L1@181 N·3 S·250B "sna..Num,v230":"sna..Set,v231" +18:12:10.915398 table@build exiting +18:12:10.915401 table@build revert @181 +18:12:10.915458 db@write was delayed N·1 T·1.0383ms +18:12:10.915503 db@close done T·1.2534ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.915801 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.915876 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:10.915905 db@open opening +18:12:10.915941 journal@recovery F·1 +18:12:10.916270 journal@recovery recovering @179 +18:12:10.918105 memdb@flush created L0@181 N·3 S·250B "sna..Num,v234":"sna..Set,v235" +18:12:10.918488 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] +18:12:10.923522 db@janitor F·12 G·0 +18:12:10.923537 db@open done T·7.6253ms +18:12:10.923568 table@compaction L0·9 -> L1·1 S·2KiB Q·236 +18:12:10.925371 table@build created L1@184 N·3 S·250B "sna..Num,v234":"sna..Set,v235" +18:12:10.925420 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:10.925488 db@close closing +18:12:10.926955 table@commit exiting +18:12:10.926978 db@write was delayed N·1 T·1.5707ms +18:12:10.927010 db@close done T·1.5236ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.927279 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.927374 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:10.927403 db@open opening +18:12:10.927463 journal@recovery F·1 +18:12:10.928066 journal@recovery recovering @182 +18:12:10.930923 memdb@flush created L0@185 N·3 S·250B "sna..Num,v238":"sna..Set,v239" +18:12:10.931309 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:10.936288 db@janitor F·14 G·10 +18:12:10.936297 db@janitor removing table-178 +18:12:10.936318 db@janitor removing table-166 +18:12:10.936337 db@janitor removing table-163 +18:12:10.936355 db@janitor removing table-156 +18:12:10.936371 db@janitor removing table-157 +18:12:10.936388 db@janitor removing table-169 +18:12:10.936404 db@janitor removing table-181 +18:12:10.936419 db@janitor removing table-172 +18:12:10.936434 db@janitor removing table-175 +18:12:10.936450 db@janitor removing table-160 +18:12:10.936489 db@open done T·9.0792ms +18:12:10.936639 db@close closing +18:12:10.936695 db@close done T·56.5µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.954014 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.954149 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:10.954166 db@open opening +18:12:10.954224 journal@recovery F·1 +18:12:10.954593 journal@recovery recovering @186 +18:12:10.958782 memdb@flush created L0@188 N·3 S·250B "sna..Num,v242":"sna..Set,v243" +18:12:10.959228 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:10.966291 db@janitor F·5 G·0 +18:12:10.966318 db@open done T·12.1468ms +18:12:10.966498 db@close closing +18:12:10.966542 db@close done T·43.4µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.966822 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.966887 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:10.966898 db@open opening +18:12:10.966922 journal@recovery F·1 +18:12:10.967265 journal@recovery recovering @189 +18:12:10.969445 memdb@flush created L0@191 N·3 S·250B "sna..Num,v246":"sna..Set,v247" +18:12:10.969822 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] +18:12:10.975097 db@janitor F·6 G·0 +18:12:10.975109 db@open done T·8.2069ms +18:12:10.975287 db@close closing +18:12:10.975332 db@close done T·44.6µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.975573 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.975639 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] +18:12:10.975676 db@open opening +18:12:10.975706 journal@recovery F·1 +18:12:10.976072 journal@recovery recovering @192 +18:12:10.977970 memdb@flush created L0@194 N·3 S·250B "sna..Num,v250":"sna..Set,v251" +18:12:10.978387 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] +18:12:10.983610 db@janitor F·7 G·0 +18:12:10.983623 db@open done T·7.9397ms +18:12:10.983679 table@compaction L0·4 -> L1·1 S·1KiB Q·252 +18:12:10.983820 db@close closing +18:12:10.983856 db@close done T·34.9µs +=============== Oct 25, 2021 (CST) =============== +18:12:10.984121 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.984185 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] +18:12:10.984213 db@open opening +18:12:10.984244 journal@recovery F·1 +18:12:10.984585 journal@recovery recovering @195 +18:12:10.986501 memdb@flush created L0@197 N·3 S·250B "sna..Num,v254":"sna..Set,v255" +18:12:10.986901 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:10.991785 db@janitor F·8 G·0 +18:12:10.991797 db@open done T·7.5774ms +18:12:10.991924 table@compaction L0·5 -> L1·1 S·1KiB Q·256 +18:12:10.991958 db@close closing +18:12:10.993629 table@build created L1@200 N·3 S·250B "sna..Num,v254":"sna..Set,v255" +18:12:10.993652 table@build exiting +18:12:10.993660 table@build revert @200 +18:12:10.993752 db@close done T·1.7933ms +=============== Oct 25, 2021 (CST) =============== +18:12:10.993997 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:10.994086 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:10.994095 db@open opening +18:12:10.994134 journal@recovery F·1 +18:12:10.994469 journal@recovery recovering @198 +18:12:10.997033 memdb@flush created L0@200 N·3 S·250B "sna..Num,v258":"sna..Set,v259" +18:12:10.997942 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:11.004499 db@janitor F·9 G·0 +18:12:11.004514 db@open done T·10.4144ms +18:12:11.004578 table@compaction L0·6 -> L1·1 S·1KiB Q·260 +18:12:11.004788 db@close closing +18:12:11.006713 table@build created L1@203 N·3 S·250B "sna..Num,v258":"sna..Set,v259" +18:12:11.006729 table@build exiting +18:12:11.006734 table@build revert @203 +18:12:11.006833 db@close done T·2.0445ms +=============== Oct 25, 2021 (CST) =============== +18:12:11.007146 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.007226 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:11.007253 db@open opening +18:12:11.007286 journal@recovery F·1 +18:12:11.007649 journal@recovery recovering @201 +18:12:11.009507 memdb@flush created L0@203 N·3 S·250B "sna..Num,v262":"sna..Set,v263" +18:12:11.009887 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:11.015368 db@janitor F·10 G·0 +18:12:11.015380 db@open done T·8.1205ms +18:12:11.015434 table@compaction L0·7 -> L1·1 S·1KiB Q·264 +18:12:11.015599 db@close closing +18:12:11.017273 table@build created L1@206 N·3 S·250B "sna..Num,v262":"sna..Set,v263" +18:12:11.017285 table@build exiting +18:12:11.017290 table@build revert @206 +18:12:11.017380 db@close done T·1.7801ms +=============== Oct 25, 2021 (CST) =============== +18:12:11.017656 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.017730 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:11.017755 db@open opening +18:12:11.017791 journal@recovery F·1 +18:12:11.018111 journal@recovery recovering @204 +18:12:11.020167 memdb@flush created L0@206 N·3 S·250B "sna..Num,v266":"sna..Set,v267" +18:12:11.020554 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:11.025332 db@janitor F·11 G·0 +18:12:11.025343 db@open done T·7.581ms +18:12:11.025389 table@compaction L0·8 -> L1·1 S·2KiB Q·268 +18:12:11.026695 db@close closing +18:12:11.027273 table@build created L1@209 N·3 S·250B "sna..Num,v266":"sna..Set,v267" +18:12:11.027282 table@build exiting +18:12:11.027286 table@build revert @209 +18:12:11.027338 db@write was delayed N·1 T·1.0823ms +18:12:11.027368 db@close done T·672.6µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.027659 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.027756 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:11.027769 db@open opening +18:12:11.027801 journal@recovery F·1 +18:12:11.028113 journal@recovery recovering @207 +18:12:11.030148 memdb@flush created L0@209 N·3 S·250B "sna..Num,v270":"sna..Set,v271" +18:12:11.030555 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] +18:12:11.036299 db@janitor F·12 G·0 +18:12:11.036313 db@open done T·8.5404ms +18:12:11.036416 table@compaction L0·9 -> L1·1 S·2KiB Q·272 +18:12:11.038267 db@close closing +18:12:11.038851 table@build created L1@212 N·3 S·250B "sna..Num,v270":"sna..Set,v271" +18:12:11.038860 table@build exiting +18:12:11.038863 table@build revert @212 +18:12:11.038921 db@write was delayed N·1 T·1.2763ms +18:12:11.038967 db@close done T·701.1µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.039253 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.039339 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] +18:12:11.039367 db@open opening +18:12:11.039412 journal@recovery F·1 +18:12:11.039767 journal@recovery recovering @210 +18:12:11.041812 memdb@flush created L0@212 N·3 S·250B "sna..Num,v274":"sna..Set,v275" +18:12:11.042180 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] +18:12:11.047713 db@janitor F·13 G·0 +18:12:11.047742 db@open done T·8.3679ms +18:12:11.047788 table@compaction L0·10 -> L1·1 S·2KiB Q·276 +18:12:11.049381 db@close closing +18:12:11.049568 table@build created L1@215 N·3 S·250B "sna..Num,v274":"sna..Set,v275" +18:12:11.049576 table@build exiting +18:12:11.049580 table@build revert @215 +18:12:11.049635 db@write was delayed N·1 T·1.3457ms +18:12:11.049688 db@close done T·308µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.049921 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.050012 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] +18:12:11.050039 db@open opening +18:12:11.050077 journal@recovery F·1 +18:12:11.050409 journal@recovery recovering @213 +18:12:11.052263 memdb@flush created L0@215 N·3 S·250B "sna..Num,v278":"sna..Set,v279" +18:12:11.052916 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] +18:12:11.058713 db@janitor F·14 G·0 +18:12:11.058742 db@open done T·8.696ms +18:12:11.058810 table@compaction L0·11 -> L1·1 S·2KiB Q·280 +18:12:11.060385 db@close closing +18:12:11.060852 table@build created L1@218 N·3 S·250B "sna..Num,v278":"sna..Set,v279" +18:12:11.060862 table@build exiting +18:12:11.060865 table@build revert @218 +18:12:11.060921 db@write was delayed N·1 T·1.3201ms +18:12:11.060954 db@close done T·572µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.076675 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.076805 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] +18:12:11.076841 db@open opening +18:12:11.076897 journal@recovery F·1 +18:12:11.078704 journal@recovery recovering @216 +18:12:11.083888 memdb@flush created L0@218 N·3 S·250B "sna..Num,v282":"sna..Set,v283" +18:12:11.084355 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] +18:12:11.091416 db@janitor F·15 G·0 +18:12:11.091452 db@open done T·14.6029ms +18:12:11.091498 table@compaction L0·12 -> L1·1 S·3KiB Q·284 +18:12:11.093191 db@close closing +18:12:11.093814 table@build created L1@221 N·3 S·250B "sna..Num,v282":"sna..Set,v283" +18:12:11.093826 table@build exiting +18:12:11.093830 table@build revert @221 +18:12:11.093893 db@write was delayed N·1 T·1.3236ms +18:12:11.093946 db@close done T·756.9µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.094272 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.094381 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] +18:12:11.094423 db@open opening +18:12:11.094465 journal@recovery F·1 +18:12:11.094868 journal@recovery recovering @219 +18:12:11.097351 memdb@flush created L0@221 N·3 S·250B "sna..Num,v286":"sna..Set,v287" +18:12:11.097790 version@stat F·[13 1] S·3KiB[3KiB 250B] Sc·[3.25 0.00] +18:12:11.103116 db@janitor F·16 G·0 +18:12:11.103145 db@open done T·8.7134ms +18:12:11.103192 table@compaction L0·13 -> L1·1 S·3KiB Q·288 +18:12:11.105441 table@build created L1@224 N·3 S·250B "sna..Num,v286":"sna..Set,v287" +18:12:11.105480 db@close closing +18:12:11.105493 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:11.106991 table@commit exiting +18:12:11.107030 db@write was delayed N·1 T·2.0314ms +18:12:11.107122 db@close done T·1.6412ms +=============== Oct 25, 2021 (CST) =============== +18:12:11.107489 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.107564 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:11.107574 db@open opening +18:12:11.107613 journal@recovery F·1 +18:12:11.107963 journal@recovery recovering @222 +18:12:11.109995 memdb@flush created L0@225 N·3 S·250B "sna..Num,v290":"sna..Set,v291" +18:12:11.110366 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:11.115704 db@janitor F·18 G·14 +18:12:11.115713 db@janitor removing table-194 +18:12:11.115734 db@janitor removing table-209 +18:12:11.115752 db@janitor removing table-185 +18:12:11.115769 db@janitor removing table-206 +18:12:11.115785 db@janitor removing table-200 +18:12:11.115801 db@janitor removing table-191 +18:12:11.115817 db@janitor removing table-188 +18:12:11.115832 db@janitor removing table-221 +18:12:11.115848 db@janitor removing table-218 +18:12:11.115865 db@janitor removing table-203 +18:12:11.115881 db@janitor removing table-197 +18:12:11.115896 db@janitor removing table-184 +18:12:11.115912 db@janitor removing table-215 +18:12:11.115929 db@janitor removing table-212 +18:12:11.115966 db@open done T·8.3876ms +18:12:11.116130 db@close closing +18:12:11.116171 db@close done T·39.7µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.116487 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.116548 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:11.116578 db@open opening +18:12:11.116608 journal@recovery F·1 +18:12:11.116941 journal@recovery recovering @226 +18:12:11.120145 memdb@flush created L0@228 N·3 S·250B "sna..Num,v294":"sna..Set,v295" +18:12:11.120545 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:11.125521 db@janitor F·5 G·0 +18:12:11.125532 db@open done T·8.9472ms +18:12:11.125651 db@close closing +18:12:11.125673 db@close done T·22.3µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.125884 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.125951 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:11.125978 db@open opening +18:12:11.126008 journal@recovery F·1 +18:12:11.126349 journal@recovery recovering @229 +18:12:11.128250 memdb@flush created L0@231 N·3 S·250B "sna..Num,v298":"sna..Set,v299" +18:12:11.128937 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] +18:12:11.134008 db@janitor F·6 G·0 +18:12:11.134022 db@open done T·8.0372ms +18:12:11.134192 db@close closing +18:12:11.134238 db@close done T·46.6µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.134450 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.134525 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] +18:12:11.134553 db@open opening +18:12:11.134585 journal@recovery F·1 +18:12:11.134943 journal@recovery recovering @232 +18:12:11.136837 memdb@flush created L0@234 N·3 S·250B "sna..Num,v302":"sna..Set,v303" +18:12:11.137258 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] +18:12:11.142005 db@janitor F·7 G·0 +18:12:11.142017 db@open done T·7.4567ms +18:12:11.142082 table@compaction L0·4 -> L1·1 S·1KiB Q·304 +18:12:11.142252 db@close closing +18:12:11.144079 table@build created L1@237 N·3 S·250B "sna..Num,v302":"sna..Set,v303" +18:12:11.144089 table@build exiting +18:12:11.144093 table@build revert @237 +18:12:11.144159 db@close done T·1.9072ms +=============== Oct 25, 2021 (CST) =============== +18:12:11.144346 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.144409 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] +18:12:11.144439 db@open opening +18:12:11.144470 journal@recovery F·1 +18:12:11.144792 journal@recovery recovering @235 +18:12:11.146663 memdb@flush created L0@237 N·3 S·250B "sna..Num,v306":"sna..Set,v307" +18:12:11.147052 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:11.151931 db@janitor F·8 G·0 +18:12:11.151960 db@open done T·7.5131ms +18:12:11.152010 table@compaction L0·5 -> L1·1 S·1KiB Q·308 +18:12:11.152166 db@close closing +18:12:11.154076 table@build created L1@240 N·3 S·250B "sna..Num,v306":"sna..Set,v307" +18:12:11.154095 table@build exiting +18:12:11.154100 table@build revert @240 +18:12:11.154202 db@close done T·2.0312ms +=============== Oct 25, 2021 (CST) =============== +18:12:11.154426 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.154503 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:11.154532 db@open opening +18:12:11.154567 journal@recovery F·1 +18:12:11.154907 journal@recovery recovering @238 +18:12:11.156772 memdb@flush created L0@240 N·3 S·250B "sna..Num,v310":"sna..Set,v311" +18:12:11.157246 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:11.162548 db@janitor F·9 G·0 +18:12:11.162574 db@open done T·8.0345ms +18:12:11.162633 table@compaction L0·6 -> L1·1 S·1KiB Q·312 +18:12:11.162780 db@close closing +18:12:11.162828 db@close done T·47.3µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.163020 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.163102 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:11.163132 db@open opening +18:12:11.163172 journal@recovery F·1 +18:12:11.163738 journal@recovery recovering @241 +18:12:11.166719 memdb@flush created L0@243 N·3 S·250B "sna..Num,v314":"sna..Set,v315" +18:12:11.167159 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:11.172281 db@janitor F·10 G·0 +18:12:11.172309 db@open done T·9.1687ms +18:12:11.172355 table@compaction L0·7 -> L1·1 S·1KiB Q·316 +18:12:11.172560 db@close closing +18:12:11.174271 table@build created L1@246 N·3 S·250B "sna..Num,v314":"sna..Set,v315" +18:12:11.174282 table@build exiting +18:12:11.174285 table@build revert @246 +18:12:11.174359 db@close done T·1.8001ms +=============== Oct 25, 2021 (CST) =============== +18:12:11.174578 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.174650 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:11.174676 db@open opening +18:12:11.174712 journal@recovery F·1 +18:12:11.175034 journal@recovery recovering @244 +18:12:11.176834 memdb@flush created L0@246 N·3 S·250B "sna..Num,v318":"sna..Set,v319" +18:12:11.177463 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:11.182890 db@janitor F·11 G·0 +18:12:11.182905 db@open done T·8.2217ms +18:12:11.182936 table@compaction L0·8 -> L1·1 S·2KiB Q·320 +18:12:11.185004 db@close closing +18:12:11.185104 table@build created L1@249 N·3 S·250B "sna..Num,v318":"sna..Set,v319" +18:12:11.185114 table@build exiting +18:12:11.185118 table@build revert @249 +18:12:11.185177 db@write was delayed N·1 T·1.8132ms +18:12:11.185225 db@close done T·222.3µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.321676 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.321798 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:11.321825 db@open opening +18:12:11.321861 journal@recovery F·1 +18:12:11.321936 journal@recovery recovering @247 +18:12:11.330528 memdb@flush created L0@249 N·3 S·250B "sna..Num,v322":"sna..Set,v323" +18:12:11.330724 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] +18:12:11.341691 db@janitor F·12 G·0 +18:12:11.341730 db@open done T·19.8977ms +18:12:11.341805 table@compaction L0·9 -> L1·1 S·2KiB Q·324 +18:12:11.343253 db@close closing +18:12:11.344898 table@build created L1@252 N·3 S·250B "sna..Num,v322":"sna..Set,v323" +18:12:11.344907 table@build exiting +18:12:11.344911 table@build revert @252 +18:12:11.344962 db@write was delayed N·1 T·1.2056ms +18:12:11.344988 db@close done T·1.7348ms +=============== Oct 25, 2021 (CST) =============== +18:12:11.345226 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.345306 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] +18:12:11.345332 db@open opening +18:12:11.345368 journal@recovery F·1 +18:12:11.346544 journal@recovery recovering @250 +18:12:11.348912 memdb@flush created L0@252 N·3 S·250B "sna..Num,v326":"sna..Set,v327" +18:12:11.349040 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] +18:12:11.355168 db@janitor F·13 G·0 +18:12:11.355192 db@open done T·9.8538ms +18:12:11.355257 table@compaction L0·10 -> L1·1 S·2KiB Q·328 +18:12:11.357043 db@close closing +18:12:11.357574 table@build created L1@255 N·3 S·250B "sna..Num,v326":"sna..Set,v327" +18:12:11.357583 table@build exiting +18:12:11.357587 table@build revert @255 +18:12:11.357639 db@write was delayed N·1 T·1.1053ms +18:12:11.357686 db@close done T·642.7µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.357949 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.358033 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] +18:12:11.358060 db@open opening +18:12:11.358097 journal@recovery F·1 +18:12:11.359300 journal@recovery recovering @253 +18:12:11.361515 memdb@flush created L0@255 N·3 S·250B "sna..Num,v330":"sna..Set,v331" +18:12:11.361634 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] +18:12:11.367277 db@janitor F·14 G·0 +18:12:11.367303 db@open done T·9.2365ms +18:12:11.367369 table@compaction L0·11 -> L1·1 S·2KiB Q·332 +18:12:11.368718 db@close closing +18:12:11.369645 table@build created L1@258 N·3 S·250B "sna..Num,v330":"sna..Set,v331" +18:12:11.369654 table@build exiting +18:12:11.369658 table@build revert @258 +18:12:11.369707 db@write was delayed N·1 T·1.116ms +18:12:11.369750 db@close done T·1.0311ms +=============== Oct 25, 2021 (CST) =============== +18:12:11.370009 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.370085 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] +18:12:11.370111 db@open opening +18:12:11.370150 journal@recovery F·1 +18:12:11.371288 journal@recovery recovering @256 +18:12:11.374167 memdb@flush created L0@258 N·3 S·250B "sna..Num,v334":"sna..Set,v335" +18:12:11.374280 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] +18:12:11.382395 db@janitor F·15 G·0 +18:12:11.382424 db@open done T·12.3057ms +18:12:11.382517 table@compaction L0·12 -> L1·1 S·3KiB Q·336 +18:12:11.383878 db@close closing +18:12:11.385149 table@build created L1@261 N·3 S·250B "sna..Num,v334":"sna..Set,v335" +18:12:11.385158 table@build exiting +18:12:11.385161 table@build revert @261 +18:12:11.385212 db@write was delayed N·1 T·1.1454ms +18:12:11.385240 db@close done T·1.3627ms +=============== Oct 25, 2021 (CST) =============== +18:12:11.385519 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.385604 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] +18:12:11.385631 db@open opening +18:12:11.385672 journal@recovery F·1 +18:12:11.386846 journal@recovery recovering @259 +18:12:11.390287 memdb@flush created L0@261 N·3 S·250B "sna..Num,v338":"sna..Set,v339" +18:12:11.390396 version@stat F·[13 1] S·3KiB[3KiB 250B] Sc·[3.25 0.00] +18:12:11.396609 db@janitor F·16 G·0 +18:12:11.396635 db@open done T·10.9967ms +18:12:11.396710 table@compaction L0·13 -> L1·1 S·3KiB Q·340 +18:12:11.398425 db@close closing +18:12:11.398582 table@build created L1@264 N·3 S·250B "sna..Num,v338":"sna..Set,v339" +18:12:11.398593 table@build exiting +18:12:11.398597 table@build revert @264 +18:12:11.398647 db@write was delayed N·1 T·1.4663ms +18:12:11.398676 db@close done T·252.1µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.398900 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.398986 version@stat F·[13 1] S·3KiB[3KiB 250B] Sc·[3.25 0.00] +18:12:11.399015 db@open opening +18:12:11.399053 journal@recovery F·1 +18:12:11.400209 journal@recovery recovering @262 +18:12:11.402445 memdb@flush created L0@264 N·3 S·250B "sna..Num,v342":"sna..Set,v343" +18:12:11.402556 version@stat F·[14 1] S·3KiB[3KiB 250B] Sc·[3.50 0.00] +18:12:11.408307 db@janitor F·17 G·0 +18:12:11.408318 db@open done T·9.2963ms +18:12:11.408411 table@compaction L0·14 -> L1·1 S·3KiB Q·344 +18:12:11.409788 db@close closing +18:12:11.410160 table@build created L1@267 N·3 S·250B "sna..Num,v342":"sna..Set,v343" +18:12:11.410168 table@build exiting +18:12:11.410171 table@build revert @267 +18:12:11.410219 db@write was delayed N·1 T·1.1047ms +18:12:11.410265 db@close done T·477.1µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.410522 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.410612 version@stat F·[14 1] S·3KiB[3KiB 250B] Sc·[3.50 0.00] +18:12:11.410642 db@open opening +18:12:11.410684 journal@recovery F·1 +18:12:11.411856 journal@recovery recovering @265 +18:12:11.413968 memdb@flush created L0@267 N·3 S·250B "sna..Num,v346":"sna..Set,v347" +18:12:11.414120 version@stat F·[15 1] S·3KiB[3KiB 250B] Sc·[3.75 0.00] +18:12:11.419989 db@janitor F·18 G·0 +18:12:11.419999 db@open done T·9.3505ms +18:12:11.420109 table@compaction L0·15 -> L1·1 S·3KiB Q·348 +18:12:11.421477 db@close closing +18:12:11.422008 table@build created L1@270 N·3 S·250B "sna..Num,v346":"sna..Set,v347" +18:12:11.422016 table@build exiting +18:12:11.422020 table@build revert @270 +18:12:11.422071 db@write was delayed N·1 T·1.1065ms +18:12:11.422120 db@close done T·642.3µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.422364 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.422450 version@stat F·[15 1] S·3KiB[3KiB 250B] Sc·[3.75 0.00] +18:12:11.422477 db@open opening +18:12:11.422518 journal@recovery F·1 +18:12:11.423664 journal@recovery recovering @268 +18:12:11.426076 memdb@flush created L0@270 N·3 S·250B "sna..Num,v350":"sna..Set,v351" +18:12:11.426181 version@stat F·[16 1] S·4KiB[3KiB 250B] Sc·[4.00 0.00] +18:12:11.431856 db@janitor F·19 G·0 +18:12:11.431870 db@open done T·9.386ms +18:12:11.431982 table@compaction L0·16 -> L1·1 S·4KiB Q·352 +18:12:11.433425 db@close closing +18:12:11.433682 table@build created L1@273 N·3 S·250B "sna..Num,v350":"sna..Set,v351" +18:12:11.433691 table@build exiting +18:12:11.433694 table@build revert @273 +18:12:11.433742 db@write was delayed N·1 T·1.1099ms +18:12:11.433787 db@close done T·361.7µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.434050 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.434124 version@stat F·[16 1] S·4KiB[3KiB 250B] Sc·[4.00 0.00] +18:12:11.434151 db@open opening +18:12:11.434194 journal@recovery F·1 +18:12:11.434263 journal@recovery recovering @271 +18:12:11.436651 memdb@flush created L0@273 N·3 S·250B "sna..Num,v354":"sna..Set,v355" +18:12:11.436755 version@stat F·[17 1] S·4KiB[4KiB 250B] Sc·[4.25 0.00] +18:12:11.442626 db@janitor F·20 G·0 +18:12:11.442637 db@open done T·8.4793ms +18:12:11.442728 table@compaction L0·17 -> L1·1 S·4KiB Q·356 +18:12:11.444654 db@close closing +18:12:11.445644 table@build created L1@276 N·3 S·250B "sna..Num,v354":"sna..Set,v355" +18:12:11.445654 table@build exiting +18:12:11.445658 table@build revert @276 +18:12:11.445714 db@write was delayed N·1 T·1.1408ms +18:12:11.445754 db@close done T·1.1005ms +=============== Oct 25, 2021 (CST) =============== +18:12:11.446000 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.446090 version@stat F·[17 1] S·4KiB[4KiB 250B] Sc·[4.25 0.00] +18:12:11.446120 db@open opening +18:12:11.446164 journal@recovery F·1 +18:12:11.446235 journal@recovery recovering @274 +18:12:11.448232 memdb@flush created L0@276 N·3 S·250B "sna..Num,v358":"sna..Set,v359" +18:12:11.448342 version@stat F·[18 1] S·4KiB[4KiB 250B] Sc·[4.50 0.00] +18:12:11.454015 db@janitor F·21 G·0 +18:12:11.454023 db@open done T·7.8969ms +18:12:11.454060 table@compaction L0·18 -> L1·1 S·4KiB Q·360 +18:12:11.456586 db@close closing +18:12:11.456739 table@build created L1@279 N·3 S·250B "sna..Num,v358":"sna..Set,v359" +18:12:11.456749 table@build exiting +18:12:11.456753 table@build revert @279 +18:12:11.456807 db@write was delayed N·1 T·1.2809ms +18:12:11.456888 db@close done T·301.7µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.457174 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.457257 version@stat F·[18 1] S·4KiB[4KiB 250B] Sc·[4.50 0.00] +18:12:11.457283 db@open opening +18:12:11.457331 journal@recovery F·1 +18:12:11.457400 journal@recovery recovering @277 +18:12:11.459481 memdb@flush created L0@279 N·3 S·251B "sna..Num,v362":"sna..Set,v363" +18:12:11.459590 version@stat F·[19 1] S·4KiB[4KiB 250B] Sc·[4.75 0.00] +18:12:11.464640 db@janitor F·22 G·0 +18:12:11.464649 db@open done T·7.3604ms +18:12:11.464687 table@compaction L0·19 -> L1·1 S·4KiB Q·364 +18:12:11.466481 table@build created L1@282 N·3 S·251B "sna..Num,v362":"sna..Set,v363" +18:12:11.466520 version@stat F·[0 1] S·251B[0B 251B] Sc·[0.00 0.00] +18:12:11.466612 db@close closing +18:12:11.468013 table@commit exiting +18:12:11.468034 db@write was delayed N·1 T·1.1088ms +18:12:11.468074 db@close done T·1.4631ms +=============== Oct 25, 2021 (CST) =============== +18:12:11.468357 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.468448 version@stat F·[0 1] S·251B[0B 251B] Sc·[0.00 0.00] +18:12:11.468475 db@open opening +18:12:11.468521 journal@recovery F·1 +18:12:11.468606 journal@recovery recovering @280 +18:12:11.471021 memdb@flush created L0@283 N·3 S·250B "sna..Num,v366":"sna..Set,v367" +18:12:11.471126 version@stat F·[1 1] S·501B[250B 251B] Sc·[0.25 0.00] +18:12:11.475700 db@janitor F·24 G·20 +18:12:11.475706 db@janitor removing table-261 +18:12:11.475735 db@janitor removing table-270 +18:12:11.475756 db@janitor removing table-234 +18:12:11.475775 db@janitor removing table-225 +18:12:11.475792 db@janitor removing table-237 +18:12:11.475808 db@janitor removing table-252 +18:12:11.475825 db@janitor removing table-224 +18:12:11.475841 db@janitor removing table-279 +18:12:11.475857 db@janitor removing table-267 +18:12:11.475872 db@janitor removing table-264 +18:12:11.475888 db@janitor removing table-255 +18:12:11.475903 db@janitor removing table-228 +18:12:11.475919 db@janitor removing table-273 +18:12:11.475934 db@janitor removing table-276 +18:12:11.475950 db@janitor removing table-240 +18:12:11.475966 db@janitor removing table-246 +18:12:11.475981 db@janitor removing table-231 +18:12:11.475997 db@janitor removing table-243 +18:12:11.476012 db@janitor removing table-258 +18:12:11.476028 db@janitor removing table-249 +18:12:11.476062 db@open done T·7.58ms +18:12:11.476655 db@close closing +18:12:11.476695 db@close done T·40µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.476963 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.477023 version@stat F·[1 1] S·501B[250B 251B] Sc·[0.25 0.00] +18:12:11.477033 db@open opening +18:12:11.477067 journal@recovery F·1 +18:12:11.477144 journal@recovery recovering @284 +18:12:11.480212 memdb@flush created L0@286 N·3 S·250B "sna..Num,v370":"sna..Set,v371" +18:12:11.480319 version@stat F·[2 1] S·751B[500B 251B] Sc·[0.50 0.00] +18:12:11.485452 db@janitor F·5 G·0 +18:12:11.485462 db@open done T·8.4247ms +18:12:11.486128 db@close closing +18:12:11.486188 db@close done T·59.9µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.486446 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.486512 version@stat F·[2 1] S·751B[500B 251B] Sc·[0.50 0.00] +18:12:11.486539 db@open opening +18:12:11.486569 journal@recovery F·1 +18:12:11.486641 journal@recovery recovering @287 +18:12:11.489594 memdb@flush created L0@289 N·3 S·250B "sna..Num,v374":"sna..Set,v375" +18:12:11.490151 version@stat F·[3 1] S·1001B[750B 251B] Sc·[0.75 0.00] +18:12:11.495283 db@janitor F·6 G·0 +18:12:11.495292 db@open done T·8.7468ms +18:12:11.495445 db@close closing +18:12:11.495486 db@close done T·40.9µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.495741 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.495804 version@stat F·[3 1] S·1001B[750B 251B] Sc·[0.75 0.00] +18:12:11.495845 db@open opening +18:12:11.495876 journal@recovery F·1 +18:12:11.495954 journal@recovery recovering @290 +18:12:11.498476 memdb@flush created L0@292 N·3 S·250B "sna..Num,v378":"sna..Set,v379" +18:12:11.498589 version@stat F·[4 1] S·1KiB[1000B 251B] Sc·[1.00 0.00] +18:12:11.503635 db@janitor F·7 G·0 +18:12:11.503646 db@open done T·7.7941ms +18:12:11.503693 table@compaction L0·4 -> L1·1 S·1KiB Q·380 +18:12:11.503821 db@close closing +18:12:11.503913 db@close done T·91.1µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.504160 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.504224 version@stat F·[4 1] S·1KiB[1000B 251B] Sc·[1.00 0.00] +18:12:11.504252 db@open opening +18:12:11.504283 journal@recovery F·1 +18:12:11.504371 journal@recovery recovering @293 +18:12:11.506370 memdb@flush created L0@295 N·3 S·250B "sna..Num,v382":"sna..Set,v383" +18:12:11.506988 version@stat F·[5 1] S·1KiB[1KiB 251B] Sc·[1.25 0.00] +18:12:11.513243 db@janitor F·8 G·0 +18:12:11.513255 db@open done T·8.9954ms +18:12:11.513345 table@compaction L0·5 -> L1·1 S·1KiB Q·384 +18:12:11.513438 db@close closing +18:12:11.515143 table@build created L1@298 N·3 S·250B "sna..Num,v382":"sna..Set,v383" +18:12:11.515153 table@build exiting +18:12:11.515157 table@build revert @298 +18:12:11.515226 db@close done T·1.7874ms +=============== Oct 25, 2021 (CST) =============== +18:12:11.515529 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.515596 version@stat F·[5 1] S·1KiB[1KiB 251B] Sc·[1.25 0.00] +18:12:11.515621 db@open opening +18:12:11.515652 journal@recovery F·1 +18:12:11.515726 journal@recovery recovering @296 +18:12:11.518072 memdb@flush created L0@298 N·3 S·250B "sna..Num,v386":"sna..Set,v387" +18:12:11.518177 version@stat F·[6 1] S·1KiB[1KiB 251B] Sc·[1.50 0.00] +18:12:11.523308 db@janitor F·9 G·0 +18:12:11.523316 db@open done T·7.6894ms +18:12:11.523415 table@compaction L0·6 -> L1·1 S·1KiB Q·388 +18:12:11.523973 db@close closing +18:12:11.525784 table@build created L1@301 N·3 S·250B "sna..Num,v386":"sna..Set,v387" +18:12:11.525792 table@build exiting +18:12:11.525796 table@build revert @301 +18:12:11.525873 db@close done T·1.8994ms +=============== Oct 25, 2021 (CST) =============== +18:12:11.526136 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.526201 version@stat F·[6 1] S·1KiB[1KiB 251B] Sc·[1.50 0.00] +18:12:11.526229 db@open opening +18:12:11.526264 journal@recovery F·1 +18:12:11.526335 journal@recovery recovering @299 +18:12:11.528744 memdb@flush created L0@301 N·3 S·250B "sna..Num,v390":"sna..Set,v391" +18:12:11.529324 version@stat F·[7 1] S·1KiB[1KiB 251B] Sc·[1.75 0.00] +18:12:11.536838 db@janitor F·10 G·0 +18:12:11.536849 db@open done T·10.6134ms +18:12:11.536915 table@compaction L0·7 -> L1·1 S·1KiB Q·392 +18:12:11.537089 db@close closing +18:12:11.537163 db@close done T·73.5µs +=============== Oct 25, 2021 (CST) =============== +18:12:11.537385 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.537463 version@stat F·[7 1] S·1KiB[1KiB 251B] Sc·[1.75 0.00] +18:12:11.537491 db@open opening +18:12:11.537527 journal@recovery F·1 +18:12:11.537600 journal@recovery recovering @302 +18:12:11.540616 memdb@flush created L0@304 N·3 S·250B "sna..Num,v394":"sna..Set,v395" +18:12:11.542929 version@stat F·[8 1] S·2KiB[1KiB 251B] Sc·[2.00 0.00] +18:12:11.552118 db@janitor F·11 G·0 +18:12:11.552129 db@open done T·14.631ms +18:12:11.552188 table@compaction L0·8 -> L1·1 S·2KiB Q·396 +18:12:11.554160 table@build created L1@307 N·3 S·250B "sna..Num,v394":"sna..Set,v395" +18:12:11.554177 db@close closing +18:12:11.554184 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:11.556075 table@commit exiting +18:12:11.556096 db@write was delayed N·1 T·1.3412ms +18:12:11.556124 db@close done T·1.9475ms +=============== Oct 25, 2021 (CST) =============== +18:12:11.556321 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:11.556406 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:11.556432 db@open opening +18:12:11.556469 journal@recovery F·1 +18:12:11.556793 journal@recovery recovering @305 +18:12:11.559593 memdb@flush created L0@308 N·3 S·250B "sna..Num,v398":"sna..Set,v399" +18:12:11.559943 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:11.565629 db@janitor F·13 G·9 +18:12:11.565636 db@janitor removing table-289 +18:12:11.565656 db@janitor removing table-283 +18:12:11.565673 db@janitor removing table-292 +18:12:11.565690 db@janitor removing table-282 +18:12:11.565706 db@janitor removing table-286 +18:12:11.565723 db@janitor removing table-301 +18:12:11.565738 db@janitor removing table-304 +18:12:11.565754 db@janitor removing table-298 +18:12:11.565768 db@janitor removing table-295 +18:12:11.565803 db@open done T·9.3638ms +18:12:11.565913 db@close closing +18:12:11.565943 db@close done T·30.2µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.631778 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.631862 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:12.631888 db@open opening +18:12:12.631940 journal@recovery F·1 +18:12:12.632273 journal@recovery recovering @309 +18:12:12.640077 memdb@flush created L0@311 N·3 S·250B "sna..Num,v402":"sna..Set,v403" +18:12:12.640939 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:12.650253 db@janitor F·5 G·0 +18:12:12.650281 db@open done T·18.3854ms +18:12:12.650448 db@close closing +18:12:12.650481 db@close done T·32.2µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.650735 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.650802 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:12.650832 db@open opening +18:12:12.650862 journal@recovery F·1 +18:12:12.651543 journal@recovery recovering @312 +18:12:12.653620 memdb@flush created L0@314 N·3 S·249B "sna..Num,v406":"sna..Set,v407" +18:12:12.654333 version@stat F·[3 1] S·999B[749B 250B] Sc·[0.75 0.00] +18:12:12.659932 db@janitor F·6 G·0 +18:12:12.659943 db@open done T·9.1046ms +18:12:12.660088 db@close closing +18:12:12.660112 db@close done T·23.9µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.660352 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.660417 version@stat F·[3 1] S·999B[749B 250B] Sc·[0.75 0.00] +18:12:12.660446 db@open opening +18:12:12.660480 journal@recovery F·1 +18:12:12.661187 journal@recovery recovering @315 +18:12:12.663409 memdb@flush created L0@317 N·3 S·250B "sna..Num,v410":"sna..Set,v411" +18:12:12.664146 version@stat F·[4 1] S·1KiB[999B 250B] Sc·[1.00 0.00] +18:12:12.669371 db@janitor F·7 G·0 +18:12:12.669398 db@open done T·8.9416ms +18:12:12.669447 table@compaction L0·4 -> L1·1 S·1KiB Q·412 +18:12:12.669572 db@close closing +18:12:12.669635 db@close done T·61.8µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.669879 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.669937 version@stat F·[4 1] S·1KiB[999B 250B] Sc·[1.00 0.00] +18:12:12.669963 db@open opening +18:12:12.669994 journal@recovery F·1 +18:12:12.670680 journal@recovery recovering @318 +18:12:12.672943 memdb@flush created L0@320 N·3 S·250B "sna..Num,v414":"sna..Set,v415" +18:12:12.673653 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:12.678102 db@janitor F·8 G·0 +18:12:12.678114 db@open done T·8.144ms +18:12:12.678161 table@compaction L0·5 -> L1·1 S·1KiB Q·416 +18:12:12.678365 db@close closing +18:12:12.680101 table@build created L1@323 N·3 S·250B "sna..Num,v414":"sna..Set,v415" +18:12:12.680111 table@build exiting +18:12:12.680114 table@build revert @323 +18:12:12.680183 db@close done T·1.8175ms +=============== Oct 25, 2021 (CST) =============== +18:12:12.680436 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.680503 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:12.680529 db@open opening +18:12:12.680563 journal@recovery F·1 +18:12:12.681255 journal@recovery recovering @321 +18:12:12.683076 memdb@flush created L0@323 N·3 S·250B "sna..Num,v418":"sna..Set,v419" +18:12:12.683783 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:12.688934 db@janitor F·9 G·0 +18:12:12.688944 db@open done T·8.4081ms +18:12:12.688989 table@compaction L0·6 -> L1·1 S·1KiB Q·420 +18:12:12.689167 db@close closing +18:12:12.690399 table@build created L1@326 N·3 S·250B "sna..Num,v418":"sna..Set,v419" +18:12:12.690407 table@build exiting +18:12:12.690411 table@build revert @326 +18:12:12.690493 db@close done T·1.3252ms +=============== Oct 25, 2021 (CST) =============== +18:12:12.690799 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.690865 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:12.690893 db@open opening +18:12:12.690927 journal@recovery F·1 +18:12:12.691609 journal@recovery recovering @324 +18:12:12.693420 memdb@flush created L0@326 N·3 S·250B "sna..Num,v422":"sna..Set,v423" +18:12:12.694132 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:12.699229 db@janitor F·10 G·0 +18:12:12.699240 db@open done T·8.3398ms +18:12:12.699296 table@compaction L0·7 -> L1·1 S·1KiB Q·424 +18:12:12.699483 db@close closing +18:12:12.701229 table@build created L1@329 N·3 S·250B "sna..Num,v422":"sna..Set,v423" +18:12:12.701238 table@build exiting +18:12:12.701241 table@build revert @329 +18:12:12.701309 db@close done T·1.8251ms +=============== Oct 25, 2021 (CST) =============== +18:12:12.701552 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.701623 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:12.701654 db@open opening +18:12:12.701691 journal@recovery F·1 +18:12:12.702367 journal@recovery recovering @327 +18:12:12.704411 memdb@flush created L0@329 N·3 S·250B "sna..Num,v426":"sna..Set,v427" +18:12:12.705215 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:12.710239 db@janitor F·11 G·0 +18:12:12.710250 db@open done T·8.5889ms +18:12:12.710305 table@compaction L0·8 -> L1·1 S·2KiB Q·428 +18:12:12.711588 db@close closing +18:12:12.712249 table@build created L1@332 N·3 S·250B "sna..Num,v426":"sna..Set,v427" +18:12:12.712257 table@build exiting +18:12:12.712265 table@build revert @332 +18:12:12.712313 db@write was delayed N·1 T·1.116ms +18:12:12.712353 db@close done T·765µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.712608 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.712671 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:12.712697 db@open opening +18:12:12.712732 journal@recovery F·1 +18:12:12.713577 journal@recovery recovering @330 +18:12:12.715938 memdb@flush created L0@332 N·3 S·250B "sna..Num,v430":"sna..Set,v431" +18:12:12.716727 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] +18:12:12.721947 db@janitor F·12 G·0 +18:12:12.721975 db@open done T·9.2698ms +18:12:12.722051 table@compaction L0·9 -> L1·1 S·2KiB Q·432 +18:12:12.723406 db@close closing +18:12:12.723793 table@build created L1@335 N·3 S·250B "sna..Num,v430":"sna..Set,v431" +18:12:12.723800 table@build exiting +18:12:12.723804 table@build revert @335 +18:12:12.723855 db@write was delayed N·1 T·1.1077ms +18:12:12.723905 db@close done T·498.7µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.724189 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.724264 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] +18:12:12.724293 db@open opening +18:12:12.724330 journal@recovery F·1 +18:12:12.725009 journal@recovery recovering @333 +18:12:12.726869 memdb@flush created L0@335 N·3 S·250B "sna..Num,v434":"sna..Set,v435" +18:12:12.727531 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] +18:12:12.733429 db@janitor F·13 G·0 +18:12:12.733442 db@open done T·9.1417ms +18:12:12.733556 table@compaction L0·10 -> L1·1 S·2KiB Q·436 +18:12:12.734821 db@close closing +18:12:12.735607 table@build created L1@338 N·3 S·250B "sna..Num,v434":"sna..Set,v435" +18:12:12.735615 table@build exiting +18:12:12.735618 table@build revert @338 +18:12:12.735669 db@write was delayed N·1 T·1.1181ms +18:12:12.735714 db@close done T·892.7µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.735978 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.736056 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] +18:12:12.736084 db@open opening +18:12:12.736124 journal@recovery F·1 +18:12:12.736679 journal@recovery recovering @336 +18:12:12.738529 memdb@flush created L0@338 N·3 S·250B "sna..Num,v438":"sna..Set,v439" +18:12:12.738632 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] +18:12:12.743345 db@janitor F·14 G·0 +18:12:12.743354 db@open done T·7.263ms +18:12:12.743465 table@compaction L0·11 -> L1·1 S·2KiB Q·440 +18:12:12.745054 db@close closing +18:12:12.745677 table@build created L1@341 N·3 S·250B "sna..Num,v438":"sna..Set,v439" +18:12:12.745687 table@build exiting +18:12:12.745691 table@build revert @341 +18:12:12.745751 db@write was delayed N·1 T·1.1127ms +18:12:12.745846 db@close done T·793.2µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.746104 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.746179 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] +18:12:12.746206 db@open opening +18:12:12.746245 journal@recovery F·1 +18:12:12.746784 journal@recovery recovering @339 +18:12:12.749105 memdb@flush created L0@341 N·3 S·250B "sna..Num,v442":"sna..Set,v443" +18:12:12.749211 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] +18:12:12.753578 db@janitor F·15 G·0 +18:12:12.753587 db@open done T·7.375ms +18:12:12.753705 table@compaction L0·12 -> L1·1 S·3KiB Q·444 +18:12:12.755331 db@close closing +18:12:12.755472 table@build created L1@344 N·3 S·250B "sna..Num,v442":"sna..Set,v443" +18:12:12.755480 table@build exiting +18:12:12.755484 table@build revert @344 +18:12:12.755535 db@write was delayed N·1 T·1.1238ms +18:12:12.755582 db@close done T·251.3µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.755832 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.755900 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] +18:12:12.755939 db@open opening +18:12:12.755978 journal@recovery F·1 +18:12:12.756046 journal@recovery recovering @342 +18:12:12.758401 memdb@flush created L0@344 N·3 S·250B "sna..Num,v446":"sna..Set,v447" +18:12:12.758500 version@stat F·[13 1] S·3KiB[3KiB 250B] Sc·[3.25 0.00] +18:12:12.764781 db@janitor F·16 G·0 +18:12:12.764809 db@open done T·8.8621ms +18:12:12.764858 table@compaction L0·13 -> L1·1 S·3KiB Q·448 +18:12:12.766653 db@close closing +18:12:12.767505 table@build created L1@347 N·3 S·250B "sna..Num,v446":"sna..Set,v447" +18:12:12.767515 table@build exiting +18:12:12.767519 table@build revert @347 +18:12:12.767574 db@write was delayed N·1 T·1.1084ms +18:12:12.767624 db@close done T·971.8µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.767929 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.768014 version@stat F·[13 1] S·3KiB[3KiB 250B] Sc·[3.25 0.00] +18:12:12.768042 db@open opening +18:12:12.768082 journal@recovery F·1 +18:12:12.769042 journal@recovery recovering @345 +18:12:12.771562 memdb@flush created L0@347 N·3 S·250B "sna..Num,v450":"sna..Set,v451" +18:12:12.771674 version@stat F·[14 1] S·3KiB[3KiB 250B] Sc·[3.50 0.00] +18:12:12.778110 db@janitor F·17 G·0 +18:12:12.778121 db@open done T·10.0726ms +18:12:12.778231 table@compaction L0·14 -> L1·1 S·3KiB Q·452 +18:12:12.779604 db@close closing +18:12:12.780079 table@build created L1@350 N·3 S·250B "sna..Num,v450":"sna..Set,v451" +18:12:12.780088 table@build exiting +18:12:12.780092 table@build revert @350 +18:12:12.780142 db@write was delayed N·1 T·1.1199ms +18:12:12.780207 db@close done T·602.1µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.780501 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.780577 version@stat F·[14 1] S·3KiB[3KiB 250B] Sc·[3.50 0.00] +18:12:12.780603 db@open opening +18:12:12.780643 journal@recovery F·1 +18:12:12.781052 journal@recovery recovering @348 +18:12:12.786743 memdb@flush created L0@350 N·3 S·250B "sna..Num,v454":"sna..Set,v455" +18:12:12.787113 version@stat F·[15 1] S·3KiB[3KiB 250B] Sc·[3.75 0.00] +18:12:12.792537 db@janitor F·18 G·0 +18:12:12.792547 db@open done T·11.9368ms +18:12:12.792665 table@compaction L0·15 -> L1·1 S·3KiB Q·456 +18:12:12.794115 db@close closing +18:12:12.794876 table@build created L1@353 N·3 S·250B "sna..Num,v454":"sna..Set,v455" +18:12:12.794885 table@build exiting +18:12:12.794888 table@build revert @353 +18:12:12.794937 db@write was delayed N·1 T·1.1624ms +18:12:12.794983 db@close done T·868.4µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.795230 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.795309 version@stat F·[15 1] S·3KiB[3KiB 250B] Sc·[3.75 0.00] +18:12:12.795349 db@open opening +18:12:12.795392 journal@recovery F·1 +18:12:12.795722 journal@recovery recovering @351 +18:12:12.797908 memdb@flush created L0@353 N·3 S·250B "sna..Num,v458":"sna..Set,v459" +18:12:12.798253 version@stat F·[16 1] S·4KiB[3KiB 250B] Sc·[4.00 0.00] +18:12:12.803022 db@janitor F·19 G·0 +18:12:12.803030 db@open done T·7.6754ms +18:12:12.803080 table@compaction L0·16 -> L1·1 S·4KiB Q·460 +18:12:12.804481 db@close closing +18:12:12.804806 table@build created L1@356 N·3 S·250B "sna..Num,v458":"sna..Set,v459" +18:12:12.804814 table@build exiting +18:12:12.804817 table@build revert @356 +18:12:12.804865 db@write was delayed N·1 T·1.11ms +18:12:12.804919 db@close done T·437.7µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.805148 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.805220 version@stat F·[16 1] S·4KiB[3KiB 250B] Sc·[4.00 0.00] +18:12:12.805247 db@open opening +18:12:12.805290 journal@recovery F·1 +18:12:12.805588 journal@recovery recovering @354 +18:12:12.807371 memdb@flush created L0@356 N·3 S·250B "sna..Num,v462":"sna..Set,v463" +18:12:12.807729 version@stat F·[17 1] S·4KiB[4KiB 250B] Sc·[4.25 0.00] +18:12:12.812898 db@janitor F·20 G·0 +18:12:12.812916 db@open done T·7.6533ms +18:12:12.813014 table@compaction L0·17 -> L1·1 S·4KiB Q·464 +18:12:12.814387 db@close closing +18:12:12.814805 table@build created L1@359 N·3 S·250B "sna..Num,v462":"sna..Set,v463" +18:12:12.814814 table@build exiting +18:12:12.814817 table@build revert @359 +18:12:12.814863 db@write was delayed N·1 T·1.1192ms +18:12:12.814919 db@close done T·531.1µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.815184 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.815283 version@stat F·[17 1] S·4KiB[4KiB 250B] Sc·[4.25 0.00] +18:12:12.815312 db@open opening +18:12:12.815360 journal@recovery F·1 +18:12:12.815662 journal@recovery recovering @357 +18:12:12.817575 memdb@flush created L0@359 N·3 S·250B "sna..Num,v466":"sna..Set,v467" +18:12:12.817924 version@stat F·[18 1] S·4KiB[4KiB 250B] Sc·[4.50 0.00] +18:12:12.823014 db@janitor F·21 G·0 +18:12:12.823022 db@open done T·7.7042ms +18:12:12.823066 table@compaction L0·18 -> L1·1 S·4KiB Q·468 +18:12:12.824871 table@build created L1@362 N·3 S·250B "sna..Num,v466":"sna..Set,v467" +18:12:12.824911 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:12.824959 db@close closing +18:12:12.826634 table@commit exiting +18:12:12.826668 db@write was delayed N·1 T·1.5116ms +18:12:12.826721 db@close done T·1.7613ms +=============== Oct 25, 2021 (CST) =============== +18:12:12.826981 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.827100 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] +18:12:12.827137 db@open opening +18:12:12.827186 journal@recovery F·1 +18:12:12.827488 journal@recovery recovering @360 +18:12:12.829348 memdb@flush created L0@363 N·3 S·250B "sna..Num,v470":"sna..Set,v471" +18:12:12.829683 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:12.834234 db@janitor F·23 G·19 +18:12:12.834239 db@janitor removing table-329 +18:12:12.834260 db@janitor removing table-320 +18:12:12.834277 db@janitor removing table-332 +18:12:12.834294 db@janitor removing table-335 +18:12:12.834309 db@janitor removing table-356 +18:12:12.834325 db@janitor removing table-338 +18:12:12.834341 db@janitor removing table-341 +18:12:12.834356 db@janitor removing table-317 +18:12:12.834371 db@janitor removing table-347 +18:12:12.834386 db@janitor removing table-326 +18:12:12.834401 db@janitor removing table-359 +18:12:12.834417 db@janitor removing table-307 +18:12:12.834434 db@janitor removing table-314 +18:12:12.834450 db@janitor removing table-344 +18:12:12.834465 db@janitor removing table-308 +18:12:12.834481 db@janitor removing table-323 +18:12:12.834496 db@janitor removing table-350 +18:12:12.834511 db@janitor removing table-353 +18:12:12.834526 db@janitor removing table-311 +18:12:12.834561 db@open done T·7.4171ms +18:12:12.834654 db@close closing +18:12:12.834681 db@close done T·27.3µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.834945 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.835006 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] +18:12:12.835033 db@open opening +18:12:12.835063 journal@recovery F·1 +18:12:12.835386 journal@recovery recovering @364 +18:12:12.837832 memdb@flush created L0@366 N·3 S·250B "sna..Num,v474":"sna..Set,v475" +18:12:12.838171 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:12.843544 db@janitor F·5 G·0 +18:12:12.843555 db@open done T·8.5145ms +18:12:12.843671 db@close closing +18:12:12.843704 db@close done T·32.7µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.843933 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.843991 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] +18:12:12.844017 db@open opening +18:12:12.844054 journal@recovery F·1 +18:12:12.844355 journal@recovery recovering @367 +18:12:12.846222 memdb@flush created L0@369 N·3 S·250B "sna..Num,v478":"sna..Set,v479" +18:12:12.846559 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] +18:12:12.851561 db@janitor F·6 G·0 +18:12:12.851572 db@open done T·7.5472ms +18:12:12.851719 db@close closing +18:12:12.851791 db@close done T·71.1µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.852111 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.852172 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] +18:12:12.852198 db@open opening +18:12:12.852229 journal@recovery F·1 +18:12:12.852532 journal@recovery recovering @370 +18:12:12.854488 memdb@flush created L0@372 N·3 S·249B "sna..Num,v482":"sna..Set,v483" +18:12:12.854921 version@stat F·[4 1] S·1KiB[999B 250B] Sc·[1.00 0.00] +18:12:12.860051 db@janitor F·7 G·0 +18:12:12.860061 db@open done T·7.8568ms +18:12:12.860123 table@compaction L0·4 -> L1·1 S·1KiB Q·484 +18:12:12.860238 db@close closing +18:12:12.861721 table@build created L1@375 N·3 S·249B "sna..Num,v482":"sna..Set,v483" +18:12:12.861730 table@build exiting +18:12:12.861734 table@build revert @375 +18:12:12.861817 db@close done T·1.5782ms +=============== Oct 25, 2021 (CST) =============== +18:12:12.862063 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.862132 version@stat F·[4 1] S·1KiB[999B 250B] Sc·[1.00 0.00] +18:12:12.862159 db@open opening +18:12:12.862201 journal@recovery F·1 +18:12:12.862554 journal@recovery recovering @373 +18:12:12.864569 memdb@flush created L0@375 N·3 S·250B "sna..Num,v486":"sna..Set,v487" +18:12:12.864932 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:12.869425 db@janitor F·8 G·0 +18:12:12.869434 db@open done T·7.2689ms +18:12:12.869481 table@compaction L0·5 -> L1·1 S·1KiB Q·488 +18:12:12.869634 db@close closing +18:12:12.871295 table@build created L1@378 N·3 S·250B "sna..Num,v486":"sna..Set,v487" +18:12:12.871303 table@build exiting +18:12:12.871306 table@build revert @378 +18:12:12.871367 db@close done T·1.733ms +=============== Oct 25, 2021 (CST) =============== +18:12:12.871599 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.871667 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] +18:12:12.871694 db@open opening +18:12:12.871729 journal@recovery F·1 +18:12:12.872033 journal@recovery recovering @376 +18:12:12.874212 memdb@flush created L0@378 N·3 S·250B "sna..Num,v490":"sna..Set,v491" +18:12:12.874583 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:12.879111 db@janitor F·9 G·0 +18:12:12.879120 db@open done T·7.4194ms +18:12:12.879180 table@compaction L0·6 -> L1·1 S·1KiB Q·492 +18:12:12.879304 db@close closing +18:12:12.880818 table@build created L1@381 N·3 S·250B "sna..Num,v490":"sna..Set,v491" +18:12:12.880827 table@build exiting +18:12:12.880831 table@build revert @381 +18:12:12.880899 db@close done T·1.5945ms +=============== Oct 25, 2021 (CST) =============== +18:12:12.881157 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.881225 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] +18:12:12.881253 db@open opening +18:12:12.881287 journal@recovery F·1 +18:12:12.881594 journal@recovery recovering @379 +18:12:12.883420 memdb@flush created L0@381 N·3 S·250B "sna..Num,v494":"sna..Set,v495" +18:12:12.883769 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:12.888413 db@janitor F·10 G·0 +18:12:12.888425 db@open done T·7.1658ms +18:12:12.888477 table@compaction L0·7 -> L1·1 S·1KiB Q·496 +18:12:12.888669 db@close closing +18:12:12.890375 table@build created L1@384 N·3 S·250B "sna..Num,v494":"sna..Set,v495" +18:12:12.890383 table@build exiting +18:12:12.890386 table@build revert @384 +18:12:12.890450 db@close done T·1.7813ms +=============== Oct 25, 2021 (CST) =============== +18:12:12.890702 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.890771 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] +18:12:12.890799 db@open opening +18:12:12.890836 journal@recovery F·1 +18:12:12.891141 journal@recovery recovering @382 +18:12:12.892882 memdb@flush created L0@384 N·3 S·250B "sna..Num,v498":"sna..Set,v499" +18:12:12.893224 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:12.898069 db@janitor F·11 G·0 +18:12:12.898079 db@open done T·7.2734ms +18:12:12.898131 table@compaction L0·8 -> L1·1 S·2KiB Q·500 +18:12:12.899469 db@close closing +18:12:12.899953 table@build created L1@387 N·3 S·250B "sna..Num,v498":"sna..Set,v499" +18:12:12.899961 table@build exiting +18:12:12.899964 table@build revert @387 +18:12:12.900029 db@write was delayed N·1 T·1.0787ms +18:12:12.900069 db@close done T·599.8µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.900345 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.900409 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] +18:12:12.900435 db@open opening +18:12:12.900473 journal@recovery F·1 +18:12:12.900782 journal@recovery recovering @385 +18:12:12.903036 memdb@flush created L0@387 N·3 S·250B "sna..Num,v502":"sna..Set,v503" +18:12:12.903396 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] +18:12:12.909641 db@janitor F·12 G·0 +18:12:12.909649 db@open done T·9.2078ms +18:12:12.909712 table@compaction L0·9 -> L1·1 S·2KiB Q·504 +18:12:12.911062 db@close closing +18:12:12.912384 table@build created L1@390 N·3 S·250B "sna..Num,v502":"sna..Set,v503" +18:12:12.912393 table@build exiting +18:12:12.912397 table@build revert @390 +18:12:12.912448 db@write was delayed N·1 T·1.1462ms +18:12:12.912491 db@close done T·1.4283ms +=============== Oct 25, 2021 (CST) =============== +18:12:12.912779 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.912849 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] +18:12:12.912876 db@open opening +18:12:12.912914 journal@recovery F·1 +18:12:12.913225 journal@recovery recovering @388 +18:12:12.915759 memdb@flush created L0@390 N·3 S·250B "sna..Num,v506":"sna..Set,v507" +18:12:12.916094 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] +18:12:12.923199 db@janitor F·13 G·0 +18:12:12.923225 db@open done T·10.3424ms +18:12:12.923275 table@compaction L0·10 -> L1·1 S·2KiB Q·508 +18:12:12.924639 db@close closing +18:12:12.925354 table@build created L1@393 N·3 S·250B "sna..Num,v506":"sna..Set,v507" +18:12:12.925362 table@build exiting +18:12:12.925366 table@build revert @393 +18:12:12.925413 db@write was delayed N·1 T·1.1201ms +18:12:12.925455 db@close done T·815µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.925715 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.925785 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] +18:12:12.925812 db@open opening +18:12:12.925849 journal@recovery F·1 +18:12:12.926166 journal@recovery recovering @391 +18:12:12.927796 memdb@flush created L0@393 N·3 S·250B "sna..Num,v510":"sna..Set,v511" +18:12:12.928143 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] +18:12:12.933182 db@janitor F·14 G·0 +18:12:12.933191 db@open done T·7.3724ms +18:12:12.933246 table@compaction L0·11 -> L1·1 S·2KiB Q·512 +18:12:12.934533 db@close closing +18:12:12.934956 table@build created L1@396 N·3 S·250B "sna..Num,v510":"sna..Set,v511" +18:12:12.934967 table@build exiting +18:12:12.934971 table@build revert @396 +18:12:12.935024 db@write was delayed N·1 T·1.1067ms +18:12:12.935067 db@close done T·534µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.935338 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.935414 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] +18:12:12.935441 db@open opening +18:12:12.935481 journal@recovery F·1 +18:12:12.935793 journal@recovery recovering @394 +18:12:12.937586 memdb@flush created L0@396 N·3 S·252B "sna..Num,v514":"sna..Set,v515" +18:12:12.937939 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] +18:12:12.943280 db@janitor F·15 G·0 +18:12:12.943304 db@open done T·7.8561ms +18:12:12.943338 table@compaction L0·12 -> L1·1 S·3KiB Q·516 +18:12:12.944685 table@build created L1@399 N·3 S·252B "sna..Num,v514":"sna..Set,v515" +18:12:12.944721 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] +18:12:12.944775 db@close closing +18:12:12.946276 table@commit exiting +18:12:12.946318 db@write was delayed N·1 T·1.1411ms +18:12:12.946374 db@close done T·1.599ms +=============== Oct 25, 2021 (CST) =============== +18:12:12.946697 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.946788 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] +18:12:12.946815 db@open opening +18:12:12.946855 journal@recovery F·1 +18:12:12.947224 journal@recovery recovering @397 +18:12:12.949072 memdb@flush created L0@400 N·3 S·252B "sna..Num,v518":"sna..Set,v519" +18:12:12.949417 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] +18:12:12.954074 db@janitor F·17 G·13 +18:12:12.954080 db@janitor removing table-381 +18:12:12.954100 db@janitor removing table-390 +18:12:12.954118 db@janitor removing table-375 +18:12:12.954135 db@janitor removing table-363 +18:12:12.954151 db@janitor removing table-366 +18:12:12.954168 db@janitor removing table-362 +18:12:12.954183 db@janitor removing table-387 +18:12:12.954199 db@janitor removing table-384 +18:12:12.954214 db@janitor removing table-393 +18:12:12.954229 db@janitor removing table-396 +18:12:12.954245 db@janitor removing table-372 +18:12:12.954261 db@janitor removing table-369 +18:12:12.954276 db@janitor removing table-378 +18:12:12.954312 db@open done T·7.4901ms +18:12:12.954464 db@close closing +18:12:12.954491 db@close done T·27.1µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.954752 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.954806 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] +18:12:12.954833 db@open opening +18:12:12.954861 journal@recovery F·1 +18:12:12.955477 journal@recovery recovering @401 +18:12:12.957629 memdb@flush created L0@403 N·3 S·253B "sna..Num,v522":"sna..Set,v523" +18:12:12.957971 version@stat F·[2 1] S·757B[505B 252B] Sc·[0.50 0.00] +18:12:12.962536 db@janitor F·5 G·0 +18:12:12.962546 db@open done T·7.7061ms +18:12:12.962695 db@close closing +18:12:12.962734 db@close done T·39.5µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.962994 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.963057 version@stat F·[2 1] S·757B[505B 252B] Sc·[0.50 0.00] +18:12:12.963083 db@open opening +18:12:12.963113 journal@recovery F·1 +18:12:12.963826 journal@recovery recovering @404 +18:12:12.965917 memdb@flush created L0@406 N·3 S·252B "sna..Num,v526":"sna..Set,v527" +18:12:12.966878 version@stat F·[3 1] S·1009B[757B 252B] Sc·[0.75 0.00] +18:12:12.972731 db@janitor F·6 G·0 +18:12:12.972742 db@open done T·9.6531ms +18:12:12.972935 db@close closing +18:12:12.972976 db@close done T·41.5µs +=============== Oct 25, 2021 (CST) =============== +18:12:12.973240 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.973309 version@stat F·[3 1] S·1009B[757B 252B] Sc·[0.75 0.00] +18:12:12.973335 db@open opening +18:12:12.973367 journal@recovery F·1 +18:12:12.974047 journal@recovery recovering @407 +18:12:12.976129 memdb@flush created L0@409 N·3 S·252B "sna..Num,v530":"sna..Set,v531" +18:12:12.976479 version@stat F·[4 1] S·1KiB[1009B 252B] Sc·[1.00 0.00] +18:12:12.981803 db@janitor F·7 G·0 +18:12:12.981822 db@open done T·8.4724ms +18:12:12.981900 table@compaction L0·4 -> L1·1 S·1KiB Q·532 +18:12:12.982016 db@close closing +18:12:12.983938 table@build created L1@412 N·3 S·252B "sna..Num,v530":"sna..Set,v531" +18:12:12.983948 table@build exiting +18:12:12.983952 table@build revert @412 +18:12:12.984038 db@close done T·2.0215ms +=============== Oct 25, 2021 (CST) =============== +18:12:12.984279 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.984343 version@stat F·[4 1] S·1KiB[1009B 252B] Sc·[1.00 0.00] +18:12:12.984370 db@open opening +18:12:12.984401 journal@recovery F·1 +18:12:12.985072 journal@recovery recovering @410 +18:12:12.987229 memdb@flush created L0@412 N·3 S·252B "sna..Num,v534":"sna..Set,v535" +18:12:12.987336 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] +18:12:12.992962 db@janitor F·8 G·0 +18:12:12.992971 db@open done T·8.5949ms +18:12:12.993026 table@compaction L0·5 -> L1·1 S·1KiB Q·536 +18:12:12.993621 db@close closing +18:12:12.994659 table@build created L1@415 N·3 S·252B "sna..Num,v534":"sna..Set,v535" +18:12:12.994667 table@build exiting +18:12:12.994671 table@build revert @415 +18:12:12.994743 db@close done T·1.1214ms +=============== Oct 25, 2021 (CST) =============== +18:12:12.995023 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:12.995090 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] +18:12:12.995117 db@open opening +18:12:12.995151 journal@recovery F·1 +18:12:12.995236 journal@recovery recovering @413 +18:12:12.997540 memdb@flush created L0@415 N·3 S·252B "sna..Num,v538":"sna..Set,v539" +18:12:12.997647 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] +18:12:13.002410 db@janitor F·9 G·0 +18:12:13.002434 db@open done T·7.3099ms +18:12:13.002468 table@compaction L0·6 -> L1·1 S·1KiB Q·540 +18:12:13.003134 db@close closing +18:12:13.004164 table@build created L1@418 N·3 S·252B "sna..Num,v538":"sna..Set,v539" +18:12:13.004172 table@build exiting +18:12:13.004175 table@build revert @418 +18:12:13.004259 db@close done T·1.1242ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.004521 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.004585 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] +18:12:13.004612 db@open opening +18:12:13.004646 journal@recovery F·1 +18:12:13.004718 journal@recovery recovering @416 +18:12:13.007126 memdb@flush created L0@418 N·3 S·252B "sna..Num,v542":"sna..Set,v543" +18:12:13.007222 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] +18:12:13.011710 db@janitor F·10 G·0 +18:12:13.011719 db@open done T·7.1008ms +18:12:13.011789 table@compaction L0·7 -> L1·1 S·1KiB Q·544 +18:12:13.012442 db@close closing +18:12:13.013460 table@build created L1@421 N·3 S·252B "sna..Num,v542":"sna..Set,v543" +18:12:13.013470 table@build exiting +18:12:13.013473 table@build revert @421 +18:12:13.013561 db@close done T·1.119ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.013864 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.013938 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] +18:12:13.013965 db@open opening +18:12:13.014001 journal@recovery F·1 +18:12:13.014074 journal@recovery recovering @419 +18:12:13.016325 memdb@flush created L0@421 N·3 S·252B "sna..Num,v546":"sna..Set,v547" +18:12:13.016429 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] +18:12:13.021867 db@janitor F·11 G·0 +18:12:13.021893 db@open done T·7.9204ms +18:12:13.021929 table@compaction L0·8 -> L1·1 S·2KiB Q·548 +18:12:13.023811 table@build created L1@424 N·3 S·252B "sna..Num,v546":"sna..Set,v547" +18:12:13.023827 db@close closing +18:12:13.023837 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] +18:12:13.025286 table@commit exiting +18:12:13.025323 db@write was delayed N·1 T·1.1096ms +18:12:13.025405 db@close done T·1.5765ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.025677 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.025748 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] +18:12:13.025774 db@open opening +18:12:13.025813 journal@recovery F·1 +18:12:13.025884 journal@recovery recovering @422 +18:12:13.028175 memdb@flush created L0@425 N·3 S·252B "sna..Num,v550":"sna..Set,v551" +18:12:13.029943 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] +18:12:13.035834 db@janitor F·13 G·9 +18:12:13.035841 db@janitor removing table-415 +18:12:13.035861 db@janitor removing table-399 +18:12:13.035879 db@janitor removing table-418 +18:12:13.035895 db@janitor removing table-421 +18:12:13.035910 db@janitor removing table-412 +18:12:13.035926 db@janitor removing table-403 +18:12:13.035943 db@janitor removing table-409 +18:12:13.035959 db@janitor removing table-400 +18:12:13.035975 db@janitor removing table-406 +18:12:13.036013 db@open done T·10.2315ms +18:12:13.036126 db@close closing +18:12:13.036155 db@close done T·28.5µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.036425 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.036485 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] +18:12:13.036513 db@open opening +18:12:13.036542 journal@recovery F·1 +18:12:13.036854 journal@recovery recovering @426 +18:12:13.039068 memdb@flush created L0@428 N·3 S·252B "sna..Num,v554":"sna..Set,v555" +18:12:13.039409 version@stat F·[2 1] S·756B[504B 252B] Sc·[0.50 0.00] +18:12:13.044415 db@janitor F·5 G·0 +18:12:13.044439 db@open done T·7.9185ms +18:12:13.044562 db@close closing +18:12:13.044601 db@close done T·37.8µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.044797 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.044867 version@stat F·[2 1] S·756B[504B 252B] Sc·[0.50 0.00] +18:12:13.044897 db@open opening +18:12:13.044927 journal@recovery F·1 +18:12:13.045237 journal@recovery recovering @429 +18:12:13.047404 memdb@flush created L0@431 N·3 S·252B "sna..Num,v558":"sna..Set,v559" +18:12:13.047751 version@stat F·[3 1] S·1008B[756B 252B] Sc·[0.75 0.00] +18:12:13.053869 db@janitor F·6 G·0 +18:12:13.053894 db@open done T·8.9898ms +18:12:13.054064 db@close closing +18:12:13.054108 db@close done T·45.1µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.176524 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.176678 version@stat F·[3 1] S·1008B[756B 252B] Sc·[0.75 0.00] +18:12:13.176708 db@open opening +18:12:13.176739 journal@recovery F·1 +18:12:13.177370 journal@recovery recovering @432 +18:12:13.183868 memdb@flush created L0@434 N·3 S·252B "sna..Num,v562":"sna..Set,v563" +18:12:13.184343 version@stat F·[4 1] S·1KiB[1008B 252B] Sc·[1.00 0.00] +18:12:13.192929 db@janitor F·7 G·0 +18:12:13.192962 db@open done T·16.2463ms +18:12:13.193025 table@compaction L0·4 -> L1·1 S·1KiB Q·564 +18:12:13.193120 db@close closing +18:12:13.195443 table@build created L1@437 N·3 S·252B "sna..Num,v562":"sna..Set,v563" +18:12:13.195467 table@build exiting +18:12:13.195473 table@build revert @437 +18:12:13.195575 db@close done T·2.4529ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.195949 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.196059 version@stat F·[4 1] S·1KiB[1008B 252B] Sc·[1.00 0.00] +18:12:13.196093 db@open opening +18:12:13.196127 journal@recovery F·1 +18:12:13.196535 journal@recovery recovering @435 +18:12:13.199185 memdb@flush created L0@437 N·3 S·252B "sna..Num,v566":"sna..Set,v567" +18:12:13.199792 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] +18:12:13.207549 db@janitor F·8 G·0 +18:12:13.207567 db@open done T·11.4666ms +18:12:13.207690 table@compaction L0·5 -> L1·1 S·1KiB Q·568 +18:12:13.207807 db@close closing +18:12:13.210192 table@build created L1@440 N·3 S·252B "sna..Num,v566":"sna..Set,v567" +18:12:13.210213 table@build exiting +18:12:13.210219 table@build revert @440 +18:12:13.210315 db@close done T·2.5067ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.210629 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.210739 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] +18:12:13.210768 db@open opening +18:12:13.210802 journal@recovery F·1 +18:12:13.211202 journal@recovery recovering @438 +18:12:13.213465 memdb@flush created L0@440 N·3 S·252B "sna..Num,v570":"sna..Set,v571" +18:12:13.214050 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] +18:12:13.220781 db@janitor F·9 G·0 +18:12:13.220796 db@open done T·10.0212ms +18:12:13.220838 table@compaction L0·6 -> L1·1 S·1KiB Q·572 +18:12:13.221028 db@close closing +18:12:13.222948 table@build created L1@443 N·3 S·252B "sna..Num,v570":"sna..Set,v571" +18:12:13.222970 table@build exiting +18:12:13.222976 table@build revert @443 +18:12:13.223073 db@close done T·2.0445ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.223410 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.223512 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] +18:12:13.223541 db@open opening +18:12:13.223575 journal@recovery F·1 +18:12:13.223968 journal@recovery recovering @441 +18:12:13.226183 memdb@flush created L0@443 N·3 S·252B "sna..Num,v574":"sna..Set,v575" +18:12:13.226689 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] +18:12:13.233014 db@janitor F·10 G·0 +18:12:13.233045 db@open done T·9.4969ms +18:12:13.233144 table@compaction L0·7 -> L1·1 S·1KiB Q·576 +18:12:13.233287 db@close closing +18:12:13.235176 table@build created L1@446 N·3 S·252B "sna..Num,v574":"sna..Set,v575" +18:12:13.235198 table@build exiting +18:12:13.235204 table@build revert @446 +18:12:13.235317 db@close done T·2.0315ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.235640 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.235767 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] +18:12:13.235820 db@open opening +18:12:13.235870 journal@recovery F·1 +18:12:13.236272 journal@recovery recovering @444 +18:12:13.238590 memdb@flush created L0@446 N·3 S·252B "sna..Num,v578":"sna..Set,v579" +18:12:13.239076 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] +18:12:13.245506 db@janitor F·11 G·0 +18:12:13.245523 db@open done T·9.6954ms +18:12:13.245614 table@compaction L0·8 -> L1·1 S·2KiB Q·580 +18:12:13.247625 db@close closing +18:12:13.248485 table@build created L1@449 N·3 S·252B "sna..Num,v578":"sna..Set,v579" +18:12:13.248508 table@build exiting +18:12:13.248515 table@build revert @449 +18:12:13.248606 db@write was delayed N·1 T·1.728ms +18:12:13.248695 db@close done T·1.0722ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.249066 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.249170 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] +18:12:13.249181 db@open opening +18:12:13.249213 journal@recovery F·1 +18:12:13.249611 journal@recovery recovering @447 +18:12:13.252545 memdb@flush created L0@449 N·3 S·252B "sna..Num,v582":"sna..Set,v583" +18:12:13.253091 version@stat F·[9 1] S·2KiB[2KiB 252B] Sc·[2.25 0.00] +18:12:13.259406 db@janitor F·12 G·0 +18:12:13.259439 db@open done T·10.2522ms +18:12:13.259492 table@compaction L0·9 -> L1·1 S·2KiB Q·584 +18:12:13.261873 table@build created L1@452 N·3 S·252B "sna..Num,v582":"sna..Set,v583" +18:12:13.261910 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] +18:12:13.261937 db@close closing +18:12:13.264066 table@commit exiting +18:12:13.264135 db@write was delayed N·1 T·2.0585ms +18:12:13.264179 db@close done T·2.2422ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.264549 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.264680 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] +18:12:13.264714 db@open opening +18:12:13.264758 journal@recovery F·1 +18:12:13.265123 journal@recovery recovering @450 +18:12:13.268961 memdb@flush created L0@453 N·3 S·252B "sna..Num,v586":"sna..Set,v587" +18:12:13.269483 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] +18:12:13.276090 db@janitor F·14 G·10 +18:12:13.276100 db@janitor removing table-434 +18:12:13.276122 db@janitor removing table-437 +18:12:13.276141 db@janitor removing table-431 +18:12:13.276158 db@janitor removing table-424 +18:12:13.276174 db@janitor removing table-440 +18:12:13.276191 db@janitor removing table-428 +18:12:13.276207 db@janitor removing table-443 +18:12:13.276222 db@janitor removing table-449 +18:12:13.276237 db@janitor removing table-446 +18:12:13.276253 db@janitor removing table-425 +18:12:13.276291 db@open done T·11.5683ms +18:12:13.276478 db@close closing +18:12:13.276527 db@close done T·48.4µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.276913 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.277008 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] +18:12:13.277021 db@open opening +18:12:13.277068 journal@recovery F·1 +18:12:13.277510 journal@recovery recovering @454 +18:12:13.280220 memdb@flush created L0@456 N·3 S·252B "sna..Num,v590":"sna..Set,v591" +18:12:13.280731 version@stat F·[2 1] S·756B[504B 252B] Sc·[0.50 0.00] +18:12:13.294412 db@janitor F·5 G·0 +18:12:13.294426 db@open done T·17.3991ms +18:12:13.294593 db@close closing +18:12:13.294627 db@close done T·34.9µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.294927 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.295008 version@stat F·[2 1] S·756B[504B 252B] Sc·[0.50 0.00] +18:12:13.295037 db@open opening +18:12:13.295071 journal@recovery F·1 +18:12:13.295459 journal@recovery recovering @457 +18:12:13.298492 memdb@flush created L0@459 N·3 S·252B "sna..Num,v594":"sna..Set,v595" +18:12:13.299099 version@stat F·[3 1] S·1008B[756B 252B] Sc·[0.75 0.00] +18:12:13.306239 db@janitor F·6 G·0 +18:12:13.306253 db@open done T·11.208ms +18:12:13.306394 db@close closing +18:12:13.306425 db@close done T·31.3µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.306755 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.306840 version@stat F·[3 1] S·1008B[756B 252B] Sc·[0.75 0.00] +18:12:13.306870 db@open opening +18:12:13.306902 journal@recovery F·1 +18:12:13.307299 journal@recovery recovering @460 +18:12:13.309856 memdb@flush created L0@462 N·3 S·252B "sna..Num,v598":"sna..Set,v599" +18:12:13.310295 version@stat F·[4 1] S·1KiB[1008B 252B] Sc·[1.00 0.00] +18:12:13.316947 db@janitor F·7 G·0 +18:12:13.316962 db@open done T·10.0846ms +18:12:13.316998 table@compaction L0·4 -> L1·1 S·1KiB Q·600 +18:12:13.317250 db@close closing +18:12:13.319443 table@build created L1@465 N·3 S·252B "sna..Num,v598":"sna..Set,v599" +18:12:13.319475 table@build exiting +18:12:13.319483 table@build revert @465 +18:12:13.319603 db@close done T·2.3526ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.319962 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.320057 version@stat F·[4 1] S·1KiB[1008B 252B] Sc·[1.00 0.00] +18:12:13.320084 db@open opening +18:12:13.320118 journal@recovery F·1 +18:12:13.320587 journal@recovery recovering @463 +18:12:13.322869 memdb@flush created L0@465 N·3 S·252B "sna..Num,v602":"sna..Set,v603" +18:12:13.323295 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] +18:12:13.329623 db@janitor F·8 G·0 +18:12:13.329640 db@open done T·9.5478ms +18:12:13.329756 table@compaction L0·5 -> L1·1 S·1KiB Q·604 +18:12:13.329951 db@close closing +18:12:13.331681 table@build created L1@468 N·3 S·252B "sna..Num,v602":"sna..Set,v603" +18:12:13.331707 table@build exiting +18:12:13.331716 table@build revert @468 +18:12:13.331838 db@close done T·1.8857ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.332142 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.332237 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] +18:12:13.332267 db@open opening +18:12:13.332302 journal@recovery F·1 +18:12:13.332757 journal@recovery recovering @466 +18:12:13.335309 memdb@flush created L0@468 N·3 S·252B "sna..Num,v606":"sna..Set,v607" +18:12:13.335889 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] +18:12:13.342116 db@janitor F·9 G·0 +18:12:13.342130 db@open done T·9.8556ms +18:12:13.342223 table@compaction L0·6 -> L1·1 S·1KiB Q·608 +18:12:13.342302 db@close closing +18:12:13.344019 table@build created L1@471 N·3 S·252B "sna..Num,v606":"sna..Set,v607" +18:12:13.344038 table@build exiting +18:12:13.344043 table@build revert @471 +18:12:13.344137 db@close done T·1.8328ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.344413 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.344506 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] +18:12:13.344537 db@open opening +18:12:13.344582 journal@recovery F·1 +18:12:13.344995 journal@recovery recovering @469 +18:12:13.347173 memdb@flush created L0@471 N·3 S·252B "sna..Num,v610":"sna..Set,v611" +18:12:13.347631 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] +18:12:13.353404 db@janitor F·10 G·0 +18:12:13.353420 db@open done T·8.8747ms +18:12:13.353473 table@compaction L0·7 -> L1·1 S·1KiB Q·612 +18:12:13.353704 db@close closing +18:12:13.355477 table@build created L1@474 N·3 S·252B "sna..Num,v610":"sna..Set,v611" +18:12:13.355496 table@build exiting +18:12:13.355501 table@build revert @474 +18:12:13.355616 db@close done T·1.9118ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.356022 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.356122 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] +18:12:13.356186 db@open opening +18:12:13.356236 journal@recovery F·1 +18:12:13.356598 journal@recovery recovering @472 +18:12:13.359162 memdb@flush created L0@474 N·3 S·252B "sna..Num,v614":"sna..Set,v615" +18:12:13.359619 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] +18:12:13.367224 db@janitor F·11 G·0 +18:12:13.367238 db@open done T·11.0451ms +18:12:13.367280 table@compaction L0·8 -> L1·1 S·2KiB Q·616 +18:12:13.368747 db@close closing +18:12:13.369708 table@build created L1@477 N·3 S·252B "sna..Num,v614":"sna..Set,v615" +18:12:13.369725 table@build exiting +18:12:13.369730 table@build revert @477 +18:12:13.369800 db@write was delayed N·1 T·1.1193ms +18:12:13.369854 db@close done T·1.1083ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.370153 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.370241 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] +18:12:13.370271 db@open opening +18:12:13.370310 journal@recovery F·1 +18:12:13.370697 journal@recovery recovering @475 +18:12:13.373701 memdb@flush created L0@477 N·3 S·252B "sna..Num,v618":"sna..Set,v619" +18:12:13.374136 version@stat F·[9 1] S·2KiB[2KiB 252B] Sc·[2.25 0.00] +18:12:13.380294 db@janitor F·12 G·0 +18:12:13.380307 db@open done T·10.029ms +18:12:13.380413 table@compaction L0·9 -> L1·1 S·2KiB Q·620 +18:12:13.382742 db@close closing +18:12:13.382794 table@build created L1@480 N·3 S·252B "sna..Num,v618":"sna..Set,v619" +18:12:13.382806 table@build exiting +18:12:13.382810 table@build revert @480 +18:12:13.382874 db@write was delayed N·1 T·1.9984ms +18:12:13.382926 db@close done T·185.8µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.383232 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.383318 version@stat F·[9 1] S·2KiB[2KiB 252B] Sc·[2.25 0.00] +18:12:13.383346 db@open opening +18:12:13.383384 journal@recovery F·1 +18:12:13.383792 journal@recovery recovering @478 +18:12:13.386146 memdb@flush created L0@480 N·3 S·252B "sna..Num,v622":"sna..Set,v623" +18:12:13.386684 version@stat F·[10 1] S·2KiB[2KiB 252B] Sc·[2.50 0.00] +18:12:13.393324 db@janitor F·13 G·0 +18:12:13.393340 db@open done T·9.9867ms +18:12:13.393365 table@compaction L0·10 -> L1·1 S·2KiB Q·624 +18:12:13.394949 db@close closing +18:12:13.395511 table@build created L1@483 N·3 S·252B "sna..Num,v622":"sna..Set,v623" +18:12:13.395526 table@build exiting +18:12:13.395531 table@build revert @483 +18:12:13.395612 db@write was delayed N·1 T·1.1311ms +18:12:13.395696 db@close done T·748.6µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.396032 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.396120 version@stat F·[10 1] S·2KiB[2KiB 252B] Sc·[2.50 0.00] +18:12:13.396152 db@open opening +18:12:13.396200 journal@recovery F·1 +18:12:13.396687 journal@recovery recovering @481 +18:12:13.399703 memdb@flush created L0@483 N·3 S·252B "sna..Num,v626":"sna..Set,v627" +18:12:13.400181 version@stat F·[11 1] S·2KiB[2KiB 252B] Sc·[2.75 0.00] +18:12:13.407255 db@janitor F·14 G·0 +18:12:13.407269 db@open done T·11.1095ms +18:12:13.407374 table@compaction L0·11 -> L1·1 S·2KiB Q·628 +18:12:13.408745 db@close closing +18:12:13.410048 table@build created L1@486 N·3 S·252B "sna..Num,v626":"sna..Set,v627" +18:12:13.410067 table@build exiting +18:12:13.410071 table@build revert @486 +18:12:13.410144 db@write was delayed N·1 T·1.1303ms +18:12:13.410204 db@close done T·1.4612ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.410531 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.410626 version@stat F·[11 1] S·2KiB[2KiB 252B] Sc·[2.75 0.00] +18:12:13.410663 db@open opening +18:12:13.410704 journal@recovery F·1 +18:12:13.411056 journal@recovery recovering @484 +18:12:13.413744 memdb@flush created L0@486 N·3 S·252B "sna..Num,v630":"sna..Set,v631" +18:12:13.414239 version@stat F·[12 1] S·3KiB[2KiB 252B] Sc·[3.00 0.00] +18:12:13.419970 db@janitor F·15 G·0 +18:12:13.419988 db@open done T·9.317ms +18:12:13.420027 table@compaction L0·12 -> L1·1 S·3KiB Q·632 +18:12:13.421514 db@close closing +18:12:13.422553 table@build created L1@489 N·3 S·252B "sna..Num,v630":"sna..Set,v631" +18:12:13.422565 table@build exiting +18:12:13.422568 table@build revert @489 +18:12:13.422629 db@write was delayed N·1 T·1.1255ms +18:12:13.422692 db@close done T·1.179ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.423019 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.423120 version@stat F·[12 1] S·3KiB[2KiB 252B] Sc·[3.00 0.00] +18:12:13.423148 db@open opening +18:12:13.423192 journal@recovery F·1 +18:12:13.423574 journal@recovery recovering @487 +18:12:13.425813 memdb@flush created L0@489 N·3 S·252B "sna..Num,v634":"sna..Set,v635" +18:12:13.426180 version@stat F·[13 1] S·3KiB[3KiB 252B] Sc·[3.25 0.00] +18:12:13.431721 db@janitor F·16 G·0 +18:12:13.431736 db@open done T·8.5804ms +18:12:13.431819 table@compaction L0·13 -> L1·1 S·3KiB Q·636 +18:12:13.433205 db@close closing +18:12:13.433649 table@build created L1@492 N·3 S·252B "sna..Num,v634":"sna..Set,v635" +18:12:13.433669 table@build exiting +18:12:13.433673 table@build revert @492 +18:12:13.433734 db@write was delayed N·1 T·1.1144ms +18:12:13.433787 db@close done T·581.5µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.434032 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.434122 version@stat F·[13 1] S·3KiB[3KiB 252B] Sc·[3.25 0.00] +18:12:13.434162 db@open opening +18:12:13.434204 journal@recovery F·1 +18:12:13.434535 journal@recovery recovering @490 +18:12:13.436462 memdb@flush created L0@492 N·3 S·252B "sna..Num,v638":"sna..Set,v639" +18:12:13.436881 version@stat F·[14 1] S·3KiB[3KiB 252B] Sc·[3.50 0.00] +18:12:13.442433 db@janitor F·17 G·0 +18:12:13.442446 db@open done T·8.2764ms +18:12:13.442518 table@compaction L0·14 -> L1·1 S·3KiB Q·640 +18:12:13.443946 db@close closing +18:12:13.444378 table@build created L1@495 N·3 S·252B "sna..Num,v638":"sna..Set,v639" +18:12:13.444387 table@build exiting +18:12:13.444391 table@build revert @495 +18:12:13.444448 db@write was delayed N·1 T·1.1206ms +18:12:13.444502 db@close done T·556.4µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.452002 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.452152 version@stat F·[14 1] S·3KiB[3KiB 252B] Sc·[3.50 0.00] +18:12:13.452162 db@open opening +18:12:13.452197 journal@recovery F·1 +18:12:13.452605 journal@recovery recovering @493 +18:12:13.456626 memdb@flush created L0@495 N·3 S·253B "sna..Num,v642":"sna..Set,v643" +18:12:13.457204 version@stat F·[15 1] S·3KiB[3KiB 252B] Sc·[3.75 0.00] +18:12:13.464955 db@janitor F·18 G·0 +18:12:13.464970 db@open done T·12.8039ms +18:12:13.465009 table@compaction L0·15 -> L1·1 S·3KiB Q·644 +18:12:13.466475 db@close closing +18:12:13.466922 table@build created L1@498 N·3 S·253B "sna..Num,v642":"sna..Set,v643" +18:12:13.466932 table@build exiting +18:12:13.466937 table@build revert @498 +18:12:13.466998 db@write was delayed N·1 T·1.143ms +18:12:13.467045 db@close done T·570.1µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.467298 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.467382 version@stat F·[15 1] S·3KiB[3KiB 252B] Sc·[3.75 0.00] +18:12:13.467409 db@open opening +18:12:13.467453 journal@recovery F·1 +18:12:13.467832 journal@recovery recovering @496 +18:12:13.470113 memdb@flush created L0@498 N·3 S·252B "sna..Num,v646":"sna..Set,v647" +18:12:13.470507 version@stat F·[16 1] S·4KiB[3KiB 252B] Sc·[4.00 0.00] +18:12:13.476120 db@janitor F·19 G·0 +18:12:13.476149 db@open done T·8.7328ms +18:12:13.476199 table@compaction L0·16 -> L1·1 S·4KiB Q·648 +18:12:13.477645 db@close closing +18:12:13.478193 table@build created L1@501 N·3 S·252B "sna..Num,v646":"sna..Set,v647" +18:12:13.478206 table@build exiting +18:12:13.478210 table@build revert @501 +18:12:13.478275 db@write was delayed N·1 T·1.1281ms +18:12:13.478333 db@close done T·690.1µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.478560 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.478678 version@stat F·[16 1] S·4KiB[3KiB 252B] Sc·[4.00 0.00] +18:12:13.478708 db@open opening +18:12:13.478758 journal@recovery F·1 +18:12:13.479130 journal@recovery recovering @499 +18:12:13.481102 memdb@flush created L0@501 N·3 S·252B "sna..Num,v650":"sna..Set,v651" +18:12:13.481489 version@stat F·[17 1] S·4KiB[4KiB 252B] Sc·[4.25 0.00] +18:12:13.487169 db@janitor F·20 G·0 +18:12:13.487200 db@open done T·8.4843ms +18:12:13.487243 table@compaction L0·17 -> L1·1 S·4KiB Q·652 +18:12:13.488803 db@close closing +18:12:13.489424 table@build created L1@504 N·3 S·252B "sna..Num,v650":"sna..Set,v651" +18:12:13.489438 table@build exiting +18:12:13.489443 table@build revert @504 +18:12:13.489505 db@write was delayed N·1 T·1.1879ms +18:12:13.489564 db@close done T·762.9µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.489803 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.489905 version@stat F·[17 1] S·4KiB[4KiB 252B] Sc·[4.25 0.00] +18:12:13.489932 db@open opening +18:12:13.489979 journal@recovery F·1 +18:12:13.490302 journal@recovery recovering @502 +18:12:13.492282 memdb@flush created L0@504 N·3 S·252B "sna..Num,v654":"sna..Set,v655" +18:12:13.492719 version@stat F·[18 1] S·4KiB[4KiB 252B] Sc·[4.50 0.00] +18:12:13.498369 db@janitor F·21 G·0 +18:12:13.498398 db@open done T·8.4582ms +18:12:13.498479 table@compaction L0·18 -> L1·1 S·4KiB Q·656 +18:12:13.499982 db@close closing +18:12:13.500471 table@build created L1@507 N·3 S·252B "sna..Num,v654":"sna..Set,v655" +18:12:13.500483 table@build exiting +18:12:13.500488 table@build revert @507 +18:12:13.500550 db@write was delayed N·1 T·1.0713ms +18:12:13.500635 db@close done T·653.3µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.500839 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.500924 version@stat F·[18 1] S·4KiB[4KiB 252B] Sc·[4.50 0.00] +18:12:13.500952 db@open opening +18:12:13.501000 journal@recovery F·1 +18:12:13.501324 journal@recovery recovering @505 +18:12:13.503211 memdb@flush created L0@507 N·3 S·252B "sna..Num,v658":"sna..Set,v659" +18:12:13.503746 version@stat F·[19 1] S·4KiB[4KiB 252B] Sc·[4.75 0.00] +18:12:13.509352 db@janitor F·22 G·0 +18:12:13.509382 db@open done T·8.4218ms +18:12:13.509431 table@compaction L0·19 -> L1·1 S·4KiB Q·660 +18:12:13.511433 table@build created L1@510 N·3 S·252B "sna..Num,v658":"sna..Set,v659" +18:12:13.511459 db@close closing +18:12:13.511467 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] +18:12:13.513094 table@commit exiting +18:12:13.513133 db@write was delayed N·1 T·1.5512ms +18:12:13.513189 table@remove removed @504 +18:12:13.513276 db@close done T·1.8171ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.520157 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.520252 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] +18:12:13.520280 db@open opening +18:12:13.520326 journal@recovery F·1 +18:12:13.520799 journal@recovery recovering @508 +18:12:13.523545 memdb@flush created L0@511 N·3 S·253B "sna..Num,v662":"sna..Set,v663" +18:12:13.524003 version@stat F·[1 1] S·505B[253B 252B] Sc·[0.25 0.00] +18:12:13.530794 db@janitor F·23 G·19 +18:12:13.530805 db@janitor removing table-498 +18:12:13.530825 db@janitor removing table-480 +18:12:13.530845 db@janitor removing table-489 +18:12:13.530861 db@janitor removing table-483 +18:12:13.530877 db@janitor removing table-474 +18:12:13.530900 db@janitor removing table-486 +18:12:13.530920 db@janitor removing table-465 +18:12:13.530936 db@janitor removing table-501 +18:12:13.530953 db@janitor removing table-456 +18:12:13.530969 db@janitor removing table-471 +18:12:13.530985 db@janitor removing table-453 +18:12:13.531000 db@janitor removing table-495 +18:12:13.531016 db@janitor removing table-492 +18:12:13.531031 db@janitor removing table-507 +18:12:13.531046 db@janitor removing table-462 +18:12:13.531079 db@janitor removing table-468 +18:12:13.531102 db@janitor removing table-477 +18:12:13.531119 db@janitor removing table-459 +18:12:13.531134 db@janitor removing table-452 +18:12:13.531171 db@open done T·10.8834ms +18:12:13.531314 db@close closing +18:12:13.531356 db@close done T·40µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.531541 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.531612 version@stat F·[1 1] S·505B[253B 252B] Sc·[0.25 0.00] +18:12:13.531640 db@open opening +18:12:13.531678 journal@recovery F·1 +18:12:13.532033 journal@recovery recovering @512 +18:12:13.534859 memdb@flush created L0@514 N·3 S·252B "sna..Num,v666":"sna..Set,v667" +18:12:13.535225 version@stat F·[2 1] S·757B[505B 252B] Sc·[0.50 0.00] +18:12:13.541602 db@janitor F·5 G·0 +18:12:13.541617 db@open done T·9.9698ms +18:12:13.541749 db@close closing +18:12:13.541800 db@close done T·49.7µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.542029 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.542099 version@stat F·[2 1] S·757B[505B 252B] Sc·[0.50 0.00] +18:12:13.542125 db@open opening +18:12:13.542156 journal@recovery F·1 +18:12:13.542505 journal@recovery recovering @515 +18:12:13.545489 memdb@flush created L0@517 N·3 S·252B "sna..Num,v670":"sna..Set,v671" +18:12:13.545889 version@stat F·[3 1] S·1009B[757B 252B] Sc·[0.75 0.00] +18:12:13.553258 db@janitor F·6 G·0 +18:12:13.553271 db@open done T·11.139ms +18:12:13.553450 db@close closing +18:12:13.553495 db@close done T·45.3µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.553696 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.553762 version@stat F·[3 1] S·1009B[757B 252B] Sc·[0.75 0.00] +18:12:13.553791 db@open opening +18:12:13.553821 journal@recovery F·1 +18:12:13.554120 journal@recovery recovering @518 +18:12:13.556794 memdb@flush created L0@520 N·3 S·252B "sna..Num,v674":"sna..Set,v675" +18:12:13.557267 version@stat F·[4 1] S·1KiB[1009B 252B] Sc·[1.00 0.00] +18:12:13.563417 db@janitor F·7 G·0 +18:12:13.563454 db@open done T·9.6554ms +18:12:13.563504 table@compaction L0·4 -> L1·1 S·1KiB Q·676 +18:12:13.563670 db@close closing +18:12:13.563728 db@close done T·57.3µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.563920 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.563985 version@stat F·[4 1] S·1KiB[1009B 252B] Sc·[1.00 0.00] +18:12:13.564015 db@open opening +18:12:13.564047 journal@recovery F·1 +18:12:13.564401 journal@recovery recovering @521 +18:12:13.566694 memdb@flush created L0@523 N·3 S·252B "sna..Num,v678":"sna..Set,v679" +18:12:13.567189 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] +18:12:13.573284 db@janitor F·8 G·0 +18:12:13.573299 db@open done T·9.2764ms +18:12:13.573363 table@compaction L0·5 -> L1·1 S·1KiB Q·680 +18:12:13.573592 db@close closing +18:12:13.575892 table@build created L1@526 N·3 S·252B "sna..Num,v678":"sna..Set,v679" +18:12:13.575913 table@build exiting +18:12:13.575928 table@build revert @526 +18:12:13.576024 db@close done T·2.4317ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.584056 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.584146 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] +18:12:13.584172 db@open opening +18:12:13.584202 journal@recovery F·1 +18:12:13.584616 journal@recovery recovering @524 +18:12:13.588786 memdb@flush created L0@526 N·3 S·253B "sna..Num,v682":"sna..Set,v683" +18:12:13.589296 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] +18:12:13.596244 db@janitor F·9 G·0 +18:12:13.596274 db@open done T·12.0946ms +18:12:13.596342 table@compaction L0·6 -> L1·1 S·1KiB Q·684 +18:12:13.596587 db@close closing +18:12:13.598276 table@build created L1@529 N·3 S·253B "sna..Num,v682":"sna..Set,v683" +18:12:13.598289 table@build exiting +18:12:13.598293 table@build revert @529 +18:12:13.598398 db@close done T·1.8105ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.598593 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.598672 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] +18:12:13.598705 db@open opening +18:12:13.598739 journal@recovery F·1 +18:12:13.599060 journal@recovery recovering @527 +18:12:13.601141 memdb@flush created L0@529 N·3 S·252B "sna..Num,v686":"sna..Set,v687" +18:12:13.601534 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] +18:12:13.606933 db@janitor F·10 G·0 +18:12:13.606947 db@open done T·8.2347ms +18:12:13.607001 table@compaction L0·7 -> L1·1 S·1KiB Q·688 +18:12:13.607387 db@close closing +18:12:13.609169 table@build created L1@532 N·3 S·252B "sna..Num,v686":"sna..Set,v687" +18:12:13.609185 table@build exiting +18:12:13.609190 table@build revert @532 +18:12:13.609280 db@close done T·1.8923ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.609491 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.609587 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] +18:12:13.609623 db@open opening +18:12:13.609673 journal@recovery F·1 +18:12:13.610004 journal@recovery recovering @530 +18:12:13.612151 memdb@flush created L0@532 N·3 S·252B "sna..Num,v690":"sna..Set,v691" +18:12:13.612518 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] +18:12:13.617923 db@janitor F·11 G·0 +18:12:13.617950 db@open done T·8.3201ms +18:12:13.618020 table@compaction L0·8 -> L1·1 S·2KiB Q·692 +18:12:13.619441 db@close closing +18:12:13.619890 table@build created L1@535 N·3 S·252B "sna..Num,v690":"sna..Set,v691" +18:12:13.619902 table@build exiting +18:12:13.619907 table@build revert @535 +18:12:13.619972 db@write was delayed N·1 T·1.1781ms +18:12:13.620006 db@close done T·566.7µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.620279 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.620411 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] +18:12:13.620441 db@open opening +18:12:13.620482 journal@recovery F·1 +18:12:13.620904 journal@recovery recovering @533 +18:12:13.623183 memdb@flush created L0@535 N·3 S·252B "sna..Num,v694":"sna..Set,v695" +18:12:13.623538 version@stat F·[9 1] S·2KiB[2KiB 252B] Sc·[2.25 0.00] +18:12:13.628720 db@janitor F·12 G·0 +18:12:13.628731 db@open done T·8.282ms +18:12:13.628772 table@compaction L0·9 -> L1·1 S·2KiB Q·696 +18:12:13.630561 db@close closing +18:12:13.630636 table@build created L1@538 N·3 S·252B "sna..Num,v694":"sna..Set,v695" +18:12:13.630646 table@build exiting +18:12:13.630649 table@build revert @538 +18:12:13.630706 db@write was delayed N·1 T·1.5503ms +18:12:13.630752 db@close done T·192.4µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.630977 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.631054 version@stat F·[9 1] S·2KiB[2KiB 252B] Sc·[2.25 0.00] +18:12:13.631081 db@open opening +18:12:13.631118 journal@recovery F·1 +18:12:13.631434 journal@recovery recovering @536 +18:12:13.633582 memdb@flush created L0@538 N·3 S·252B "sna..Num,v698":"sna..Set,v699" +18:12:13.633935 version@stat F·[10 1] S·2KiB[2KiB 252B] Sc·[2.50 0.00] +18:12:13.639278 db@janitor F·13 G·0 +18:12:13.639290 db@open done T·8.2023ms +18:12:13.639337 table@compaction L0·10 -> L1·1 S·2KiB Q·700 +18:12:13.640576 db@close closing +18:12:13.641188 table@build created L1@541 N·3 S·252B "sna..Num,v698":"sna..Set,v699" +18:12:13.641200 table@build exiting +18:12:13.641205 table@build revert @541 +18:12:13.641288 db@write was delayed N·1 T·1.0567ms +18:12:13.641322 db@close done T·746.9µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.647662 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.647747 version@stat F·[10 1] S·2KiB[2KiB 252B] Sc·[2.50 0.00] +18:12:13.647774 db@open opening +18:12:13.647810 journal@recovery F·1 +18:12:13.648134 journal@recovery recovering @539 +18:12:13.650604 memdb@flush created L0@541 N·3 S·252B "sna..Num,v702":"sna..Set,v703" +18:12:13.650961 version@stat F·[11 1] S·2KiB[2KiB 252B] Sc·[2.75 0.00] +18:12:13.656805 db@janitor F·14 G·0 +18:12:13.656817 db@open done T·9.0366ms +18:12:13.656868 table@compaction L0·11 -> L1·1 S·2KiB Q·704 +18:12:13.658882 table@build created L1@544 N·3 S·252B "sna..Num,v702":"sna..Set,v703" +18:12:13.658928 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] +18:12:13.658981 db@close closing +18:12:13.660850 table@commit exiting +18:12:13.660870 db@write was delayed N·1 T·1.8896ms +18:12:13.660901 db@close done T·1.92ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.661124 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.661203 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] +18:12:13.661230 db@open opening +18:12:13.661269 journal@recovery F·1 +18:12:13.661584 journal@recovery recovering @542 +18:12:13.663912 memdb@flush created L0@545 N·3 S·252B "sna..Num,v706":"sna..Set,v707" +18:12:13.664560 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] +18:12:13.670467 db@janitor F·16 G·12 +18:12:13.670478 db@janitor removing table-510 +18:12:13.670499 db@janitor removing table-541 +18:12:13.670521 db@janitor removing table-535 +18:12:13.670540 db@janitor removing table-529 +18:12:13.670556 db@janitor removing table-511 +18:12:13.670573 db@janitor removing table-526 +18:12:13.670588 db@janitor removing table-532 +18:12:13.670604 db@janitor removing table-538 +18:12:13.670619 db@janitor removing table-520 +18:12:13.670636 db@janitor removing table-517 +18:12:13.670666 db@janitor removing table-514 +18:12:13.670692 db@janitor removing table-523 +18:12:13.670731 db@open done T·9.4943ms +18:12:13.670930 db@close closing +18:12:13.670964 db@close done T·33.3µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.671169 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.671238 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] +18:12:13.671276 db@open opening +18:12:13.671306 journal@recovery F·1 +18:12:13.671618 journal@recovery recovering @546 +18:12:13.674421 memdb@flush created L0@548 N·3 S·252B "sna..Num,v710":"sna..Set,v711" +18:12:13.674777 version@stat F·[2 1] S·756B[504B 252B] Sc·[0.50 0.00] +18:12:13.680539 db@janitor F·5 G·0 +18:12:13.680565 db@open done T·9.2824ms +18:12:13.680725 db@close closing +18:12:13.680767 db@close done T·42.8µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.680959 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.681015 version@stat F·[2 1] S·756B[504B 252B] Sc·[0.50 0.00] +18:12:13.681041 db@open opening +18:12:13.681071 journal@recovery F·1 +18:12:13.681372 journal@recovery recovering @549 +18:12:13.683991 memdb@flush created L0@551 N·3 S·252B "sna..Num,v714":"sna..Set,v715" +18:12:13.684333 version@stat F·[3 1] S·1008B[756B 252B] Sc·[0.75 0.00] +18:12:13.689512 db@janitor F·6 G·0 +18:12:13.689524 db@open done T·8.476ms +18:12:13.689687 db@close closing +18:12:13.689753 db@close done T·65.8µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.689939 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.690000 version@stat F·[3 1] S·1008B[756B 252B] Sc·[0.75 0.00] +18:12:13.690027 db@open opening +18:12:13.690058 journal@recovery F·1 +18:12:13.690372 journal@recovery recovering @552 +18:12:13.692430 memdb@flush created L0@554 N·3 S·252B "sna..Num,v718":"sna..Set,v719" +18:12:13.692780 version@stat F·[4 1] S·1KiB[1008B 252B] Sc·[1.00 0.00] +18:12:13.697670 db@janitor F·7 G·0 +18:12:13.697697 db@open done T·7.6618ms +18:12:13.697737 table@compaction L0·4 -> L1·1 S·1KiB Q·720 +18:12:13.697878 db@close closing +18:12:13.697930 db@close done T·50.9µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.698116 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.698191 version@stat F·[4 1] S·1KiB[1008B 252B] Sc·[1.00 0.00] +18:12:13.698218 db@open opening +18:12:13.698254 journal@recovery F·1 +18:12:13.698556 journal@recovery recovering @555 +18:12:13.700466 memdb@flush created L0@557 N·3 S·252B "sna..Num,v722":"sna..Set,v723" +18:12:13.700800 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] +18:12:13.706055 db@janitor F·8 G·0 +18:12:13.706066 db@open done T·7.8416ms +18:12:13.706119 table@compaction L0·5 -> L1·1 S·1KiB Q·724 +18:12:13.706284 db@close closing +18:12:13.708223 table@build created L1@560 N·3 S·252B "sna..Num,v722":"sna..Set,v723" +18:12:13.708237 table@build exiting +18:12:13.708241 table@build revert @560 +18:12:13.708342 db@close done T·2.0559ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.714685 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.714757 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] +18:12:13.714785 db@open opening +18:12:13.714818 journal@recovery F·1 +18:12:13.715140 journal@recovery recovering @558 +18:12:13.718431 memdb@flush created L0@560 N·3 S·252B "sna..Num,v726":"sna..Set,v727" +18:12:13.718860 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] +18:12:13.726820 db@janitor F·9 G·0 +18:12:13.726848 db@open done T·12.0546ms +18:12:13.726886 table@compaction L0·6 -> L1·1 S·1KiB Q·728 +18:12:13.727102 db@close closing +18:12:13.729654 table@build created L1@563 N·3 S·252B "sna..Num,v726":"sna..Set,v727" +18:12:13.729663 table@build exiting +18:12:13.729666 table@build revert @563 +18:12:13.729750 db@close done T·2.6467ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.729954 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.730031 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] +18:12:13.730059 db@open opening +18:12:13.730107 journal@recovery F·1 +18:12:13.730432 journal@recovery recovering @561 +18:12:13.733422 memdb@flush created L0@563 N·3 S·252B "sna..Num,v730":"sna..Set,v731" +18:12:13.733816 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] +18:12:13.739233 db@janitor F·10 G·0 +18:12:13.739244 db@open done T·9.1659ms +18:12:13.739292 table@compaction L0·7 -> L1·1 S·1KiB Q·732 +18:12:13.739490 db@close closing +18:12:13.740998 table@build created L1@566 N·3 S·252B "sna..Num,v730":"sna..Set,v731" +18:12:13.741008 table@build exiting +18:12:13.741012 table@build revert @566 +18:12:13.741098 db@close done T·1.6073ms +=============== Oct 25, 2021 (CST) =============== +18:12:13.741346 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.741435 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] +18:12:13.741502 db@open opening +18:12:13.741547 journal@recovery F·1 +18:12:13.741859 journal@recovery recovering @564 +18:12:13.743748 memdb@flush created L0@566 N·3 S·252B "sna..Num,v734":"sna..Set,v735" +18:12:13.744121 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] +18:12:13.749800 db@janitor F·11 G·0 +18:12:13.749811 db@open done T·8.3019ms +18:12:13.749855 table@compaction L0·8 -> L1·1 S·2KiB Q·736 +18:12:13.751455 db@close closing +18:12:13.751653 table@build created L1@569 N·3 S·252B "sna..Num,v734":"sna..Set,v735" +18:12:13.751662 table@build exiting +18:12:13.751666 table@build revert @569 +18:12:13.751722 db@write was delayed N·1 T·1.4042ms +18:12:13.751768 db@close done T·312.4µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.751985 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.752065 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] +18:12:13.752094 db@open opening +18:12:13.752129 journal@recovery F·1 +18:12:13.752507 journal@recovery recovering @567 +18:12:13.754762 memdb@flush created L0@569 N·3 S·252B "sna..Num,v738":"sna..Set,v739" +18:12:13.755145 version@stat F·[9 1] S·2KiB[2KiB 252B] Sc·[2.25 0.00] +18:12:13.760700 db@janitor F·12 G·0 +18:12:13.760713 db@open done T·8.6119ms +18:12:13.760798 table@compaction L0·9 -> L1·1 S·2KiB Q·740 +18:12:13.762124 db@close closing +18:12:13.762549 table@build created L1@572 N·3 S·252B "sna..Num,v738":"sna..Set,v739" +18:12:13.762560 table@build exiting +18:12:13.762563 table@build revert @572 +18:12:13.762621 db@write was delayed N·1 T·1.1598ms +18:12:13.762657 db@close done T·532.5µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.762859 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.762933 version@stat F·[9 1] S·2KiB[2KiB 252B] Sc·[2.25 0.00] +18:12:13.762961 db@open opening +18:12:13.762997 journal@recovery F·1 +18:12:13.763306 journal@recovery recovering @570 +18:12:13.765767 memdb@flush created L0@572 N·3 S·252B "sna..Num,v742":"sna..Set,v743" +18:12:13.766116 version@stat F·[10 1] S·2KiB[2KiB 252B] Sc·[2.50 0.00] +18:12:13.771601 db@janitor F·13 G·0 +18:12:13.771613 db@open done T·8.6454ms +18:12:13.771729 table@compaction L0·10 -> L1·1 S·2KiB Q·744 +18:12:13.773114 db@close closing +18:12:13.773722 table@build created L1@575 N·3 S·252B "sna..Num,v742":"sna..Set,v743" +18:12:13.773732 table@build exiting +18:12:13.773736 table@build revert @575 +18:12:13.773791 db@write was delayed N·1 T·1.1954ms +18:12:13.773839 db@close done T·725.3µs +=============== Oct 25, 2021 (CST) =============== +18:12:13.774038 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +18:12:13.774114 version@stat F·[10 1] S·2KiB[2KiB 252B] Sc·[2.50 0.00] +18:12:13.774141 db@open opening +18:12:13.774180 journal@recovery F·1 +18:12:13.774511 journal@recovery recovering @573 +18:12:13.776383 memdb@flush created L0@575 N·3 S·252B "sna..Num,v746":"sna..Set,v747" +18:12:13.776758 version@stat F·[11 1] S·2KiB[2KiB 252B] Sc·[2.75 0.00] +18:12:13.782069 db@janitor F·14 G·0 +18:12:13.782082 db@open done T·7.9343ms +18:12:13.782104 table@compaction L0·11 -> L1·1 S·2KiB Q·748 +18:12:13.783868 table@build created L1@578 N·3 S·252B "sna..Num,v746":"sna..Set,v747" +18:12:13.783911 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] +18:12:13.784001 db@close closing +18:12:13.785735 table@commit exiting +18:12:13.785776 db@write was delayed N·1 T·1.5634ms +18:12:13.785831 db@close done T·1.8303ms diff --git a/p2p/simulations/base/MANIFEST-000577 b/p2p/simulations/base/MANIFEST-000577 new file mode 100644 index 0000000000000000000000000000000000000000..f424ccf55ba58a34433fb19178d5e9ab9dd19576 GIT binary patch literal 1116 zcmb``!Ae3w6vpv6-tqbl(V{2lA#_&|guTEO2MW|HbH}6?v2B7N2!bF;f@Ita1qB5L z2?+)T3DqOC=!BkC_0gh%|7wQcoXtu0wx_2a(OStIno_Z_nLTo*eh`^lSgr;(aG_0| zYcJONJ)KtTQ6;EG`=Kiqay4sB#pR8oc(b zYcX2TxIo6G7)@xz$VgTOoj~IZ8RO;U?v|y7A}`UuE$8YppO@2DBW}nIxIWk8x?G2A Sb1kl^eI6`BF&yqM%lru@FkLGE literal 0 HcmV?d00001 diff --git a/p2p/simulations/events.go b/p2p/simulations/events.go index 984c2e088f..d0d03794ed 100644 --- a/p2p/simulations/events.go +++ b/p2p/simulations/events.go @@ -73,8 +73,7 @@ func NewEvent(v interface{}) *Event { switch v := v.(type) { case *Node: event.Type = EventTypeNode - node := *v - event.Node = &node + event.Node = v.copy() case *Conn: event.Type = EventTypeConn conn := *v diff --git a/p2p/simulations/http.go b/p2p/simulations/http.go index 27eec2f727..d60124cf4a 100644 --- a/p2p/simulations/http.go +++ b/p2p/simulations/http.go @@ -386,12 +386,6 @@ func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) { sub := s.network.events.Subscribe(events) defer sub.Unsubscribe() - // stop the stream if the client goes away - var clientGone <-chan bool - if cn, ok := w.(http.CloseNotifier); ok { - clientGone = cn.CloseNotify() - } - // write writes the given event and data to the stream like: // // event: @@ -457,6 +451,7 @@ func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) { } } + clientGone := req.Context().Done() for { select { case event := <-events: @@ -712,7 +707,7 @@ func (s *Server) wrapHandler(handler http.HandlerFunc) httprouter.Handle { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") - ctx := context.Background() + ctx := req.Context() if id := params.ByName("nodeid"); id != "" { var nodeID enode.ID diff --git a/p2p/simulations/http_test.go b/p2p/simulations/http_test.go index b71445f23e..0c6be9244c 100644 --- a/p2p/simulations/http_test.go +++ b/p2p/simulations/http_test.go @@ -22,6 +22,7 @@ import ( "fmt" "math/rand" "net/http/httptest" + "os" "reflect" "sync" "sync/atomic" @@ -41,15 +42,13 @@ import ( "github.com/mattn/go-colorable" ) -var ( - loglevel = flag.Int("loglevel", 2, "verbosity of logs") -) +func TestMain(m *testing.M) { + loglevel := flag.Int("loglevel", 2, "verbosity of logs") -func init() { flag.Parse() - log.PrintOrigins(true) log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) + os.Exit(m.Run()) } // testService implements the node.Service interface and provides protocols @@ -424,16 +423,8 @@ type expectEvents struct { } func (t *expectEvents) nodeEvent(id string, up bool) *Event { - node := Node{ - Config: &adapters.NodeConfig{ - ID: enode.HexID(id), - }, - up: up, - } - return &Event{ - Type: EventTypeNode, - Node: &node, - } + config := &adapters.NodeConfig{ID: enode.HexID(id)} + return &Event{Type: EventTypeNode, Node: newNode(nil, config, up)} } func (t *expectEvents) connEvent(one, other string, up bool) *Event { @@ -454,7 +445,7 @@ loop: for { select { case event := <-t.events: - t.Logf("received %s event: %s", event.Type, event) + t.Logf("received %s event: %v", event.Type, event) if event.Type != EventTypeMsg || event.Msg.Received { continue loop @@ -490,7 +481,7 @@ func (t *expectEvents) expect(events ...*Event) { for { select { case event := <-t.events: - t.Logf("received %s event: %s", event.Type, event) + t.Logf("received %s event: %v", event.Type, event) expected := events[i] if event.Type != expected.Type { diff --git a/p2p/simulations/mocker_test.go b/p2p/simulations/mocker_test.go index aa0aa6e4b6..a8d8edc27b 100644 --- a/p2p/simulations/mocker_test.go +++ b/p2p/simulations/mocker_test.go @@ -15,7 +15,7 @@ // along with the go-ethereum library. If not, see . // Package simulations simulates p2p networks. -// A mokcer simulates starting and stopping real nodes in a network. +// A mocker simulates starting and stopping real nodes in a network. package simulations import ( @@ -80,14 +80,17 @@ func TestMocker(t *testing.T) { var opts SubscribeOpts sub, err := client.SubscribeNetwork(events, opts) defer sub.Unsubscribe() - //wait until all nodes are started and connected - //store every node up event in a map (value is irrelevant, mimic Set datatype) + + // wait until all nodes are started and connected + // store every node up event in a map (value is irrelevant, mimic Set datatype) nodemap := make(map[enode.ID]bool) - wg.Add(1) nodesComplete := false connCount := 0 + wg.Add(1) go func() { - for { + defer wg.Done() + + for connCount < (nodeCount-1)*2 { select { case event := <-events: if isNodeUp(event) { @@ -99,14 +102,10 @@ func TestMocker(t *testing.T) { } } else if event.Conn != nil && nodesComplete { connCount += 1 - if connCount == (nodeCount-1)*2 { - wg.Done() - return - } } case <-time.After(30 * time.Second): - wg.Done() - t.Fatalf("Timeout waiting for nodes being started up!") + t.Errorf("Timeout waiting for nodes being started up!") + return } } }() diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go index 08fc2956f5..5aa77f7749 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -56,6 +56,9 @@ type Network struct { Nodes []*Node `json:"nodes"` nodeMap map[enode.ID]int + // Maps a node property string to node indexes of all nodes that hold this property + propertyMap map[string][]int + Conns []*Conn `json:"conns"` connMap map[string]int @@ -71,6 +74,7 @@ func NewNetwork(nodeAdapter adapters.NodeAdapter, conf *NetworkConfig) *Network NetworkConfig: *conf, nodeAdapter: nodeAdapter, nodeMap: make(map[enode.ID]int), + propertyMap: make(map[string][]int), connMap: make(map[string]int), quitc: make(chan struct{}), } @@ -115,14 +119,18 @@ func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) if err != nil { return nil, err } - node := &Node{ - Node: adapterNode, - Config: conf, - } + node := newNode(adapterNode, conf, false) log.Trace("Node created", "id", conf.ID) - net.nodeMap[conf.ID] = len(net.Nodes) + + nodeIndex := len(net.Nodes) + net.nodeMap[conf.ID] = nodeIndex net.Nodes = append(net.Nodes, node) + // Register any node properties with the network-level propertyMap + for _, property := range conf.Properties { + net.propertyMap[property] = append(net.propertyMap[property], nodeIndex) + } + // emit a "control" event net.events.Send(ControlEvent(node)) @@ -410,7 +418,7 @@ func (net *Network) getNode(id enode.ID) *Node { return net.Nodes[i] } -// GetNode gets the node with the given name, returning nil if the node does +// GetNodeByName gets the node with the given name, returning nil if the node does // not exist func (net *Network) GetNodeByName(name string) *Node { net.lock.RLock() @@ -427,19 +435,104 @@ func (net *Network) getNodeByName(name string) *Node { return nil } -// GetNodes returns the existing nodes -func (net *Network) GetNodes() (nodes []*Node) { +// GetNodeIDs returns the IDs of all existing nodes +// Nodes can optionally be excluded by specifying their enode.ID. +func (net *Network) GetNodeIDs(excludeIDs ...enode.ID) []enode.ID { + net.lock.RLock() + defer net.lock.RUnlock() + + return net.getNodeIDs(excludeIDs) +} + +func (net *Network) getNodeIDs(excludeIDs []enode.ID) []enode.ID { + // Get all current nodeIDs + nodeIDs := make([]enode.ID, 0, len(net.nodeMap)) + for id := range net.nodeMap { + nodeIDs = append(nodeIDs, id) + } + + if len(excludeIDs) > 0 { + // Return the difference of nodeIDs and excludeIDs + return filterIDs(nodeIDs, excludeIDs) + } else { + return nodeIDs + } +} + +// GetNodes returns the existing nodes. +// Nodes can optionally be excluded by specifying their enode.ID. +func (net *Network) GetNodes(excludeIDs ...enode.ID) []*Node { net.lock.RLock() defer net.lock.RUnlock() - return net.getNodes() + return net.getNodes(excludeIDs) +} + +func (net *Network) getNodes(excludeIDs []enode.ID) []*Node { + if len(excludeIDs) > 0 { + nodeIDs := net.getNodeIDs(excludeIDs) + return net.getNodesByID(nodeIDs) + } else { + return net.Nodes + } } -func (net *Network) getNodes() (nodes []*Node) { - nodes = append(nodes, net.Nodes...) +// GetNodesByID returns existing nodes with the given enode.IDs. +// If a node doesn't exist with a given enode.ID, it is ignored. +func (net *Network) GetNodesByID(nodeIDs []enode.ID) []*Node { + net.lock.RLock() + defer net.lock.RUnlock() + + return net.getNodesByID(nodeIDs) +} + +func (net *Network) getNodesByID(nodeIDs []enode.ID) []*Node { + nodes := make([]*Node, 0, len(nodeIDs)) + for _, id := range nodeIDs { + node := net.getNode(id) + if node != nil { + nodes = append(nodes, node) + } + } + return nodes } +// GetNodesByProperty returns existing nodes that have the given property string registered in their NodeConfig +func (net *Network) GetNodesByProperty(property string) []*Node { + net.lock.RLock() + defer net.lock.RUnlock() + + return net.getNodesByProperty(property) +} + +func (net *Network) getNodesByProperty(property string) []*Node { + nodes := make([]*Node, 0, len(net.propertyMap[property])) + for _, nodeIndex := range net.propertyMap[property] { + nodes = append(nodes, net.Nodes[nodeIndex]) + } + + return nodes +} + +// GetNodeIDsByProperty returns existing node's enode IDs that have the given property string registered in the NodeConfig +func (net *Network) GetNodeIDsByProperty(property string) []enode.ID { + net.lock.RLock() + defer net.lock.RUnlock() + + return net.getNodeIDsByProperty(property) +} + +func (net *Network) getNodeIDsByProperty(property string) []enode.ID { + nodeIDs := make([]enode.ID, 0, len(net.propertyMap[property])) + for _, nodeIndex := range net.propertyMap[property] { + node := net.Nodes[nodeIndex] + nodeIDs = append(nodeIDs, node.ID()) + } + + return nodeIDs +} + // GetRandomUpNode returns a random node on the network, which is running. func (net *Network) GetRandomUpNode(excludeIDs ...enode.ID) *Node { net.lock.RLock() @@ -469,7 +562,7 @@ func (net *Network) GetRandomDownNode(excludeIDs ...enode.ID) *Node { } func (net *Network) getDownNodeIDs() (ids []enode.ID) { - for _, node := range net.getNodes() { + for _, node := range net.Nodes { if !node.Up() { ids = append(ids, node.ID()) } @@ -477,6 +570,13 @@ func (net *Network) getDownNodeIDs() (ids []enode.ID) { return ids } +// GetRandomNode returns a random node on the network, regardless of whether it is running or not +func (net *Network) GetRandomNode(excludeIDs ...enode.ID) *Node { + net.lock.RLock() + defer net.lock.RUnlock() + return net.getRandomNode(net.getNodeIDs(nil), excludeIDs) // no need to exclude twice +} + func (net *Network) getRandomNode(ids []enode.ID, excludeIDs []enode.ID) *Node { filtered := filterIDs(ids, excludeIDs) @@ -616,6 +716,7 @@ func (net *Network) Reset() { //re-initialize the maps net.connMap = make(map[string]int) net.nodeMap = make(map[enode.ID]int) + net.propertyMap = make(map[string][]int) net.Nodes = nil net.Conns = nil @@ -631,15 +732,26 @@ type Node struct { // up tracks whether or not the node is running up bool - upMu sync.RWMutex + upMu *sync.RWMutex +} + +func newNode(an adapters.Node, ac *adapters.NodeConfig, up bool) *Node { + return &Node{Node: an, Config: ac, up: up, upMu: new(sync.RWMutex)} } +func (n *Node) copy() *Node { + configCpy := *n.Config + return newNode(n.Node, &configCpy, n.Up()) +} + +// Up returns whether the node is currently up (online) func (n *Node) Up() bool { n.upMu.RLock() defer n.upMu.RUnlock() return n.up } +// SetUp sets the up (online) status of the nodes with the given value func (n *Node) SetUp(up bool) { n.upMu.Lock() defer n.upMu.Unlock() @@ -681,22 +793,19 @@ func (n *Node) MarshalJSON() ([]byte, error) { }) } -// UnmarshalJSON implements json.Unmarshaler interface so that we don't lose -// Node.up status. IMPORTANT: The implementation is incomplete; we lose -// p2p.NodeInfo. +// UnmarshalJSON implements json.Unmarshaler interface so that we don't lose Node.up +// status. IMPORTANT: The implementation is incomplete; we lose p2p.NodeInfo. func (n *Node) UnmarshalJSON(raw []byte) error { // TODO: How should we turn back NodeInfo into n.Node? // Ticket: https://github.com/ethersphere/go-ethereum/issues/1177 - node := struct { + var node struct { Config *adapters.NodeConfig `json:"config,omitempty"` Up bool `json:"up"` - }{} + } if err := json.Unmarshal(raw, &node); err != nil { return err } - - n.SetUp(node.Up) - n.Config = node.Config + *n = *newNode(nil, node.Config, node.Up) return nil } @@ -793,7 +902,7 @@ func (net *Network) snapshot(addServices []string, removeServices []string) (*Sn Nodes: make([]NodeSnapshot, len(net.Nodes)), } for i, node := range net.Nodes { - snap.Nodes[i] = NodeSnapshot{Node: *node} + snap.Nodes[i] = NodeSnapshot{Node: *node.copy()} if !node.Up() { continue } diff --git a/p2p/simulations/network_test.go b/p2p/simulations/network_test.go index 229e027abb..5c1281dc36 100644 --- a/p2p/simulations/network_test.go +++ b/p2p/simulations/network_test.go @@ -17,6 +17,7 @@ package simulations import ( + "bytes" "context" "encoding/json" "fmt" @@ -80,11 +81,13 @@ func TestSnapshot(t *testing.T) { // connect nodes in a ring // spawn separate thread to avoid deadlock in the event listeners + connectErr := make(chan error, 1) go func() { for i, id := range ids { peerID := ids[(i+1)%len(ids)] if err := network.Connect(id, peerID); err != nil { - t.Fatal(err) + connectErr <- err + return } } }() @@ -99,9 +102,10 @@ OUTER: select { case <-ctx.Done(): t.Fatal(ctx.Err()) + case err := <-connectErr: + t.Fatal(err) case ev := <-evC: if ev.Type == EventTypeConn && !ev.Control { - // fail on any disconnect if !ev.Conn.Up { t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other) @@ -393,6 +397,275 @@ func TestNetworkSimulation(t *testing.T) { } } +func createTestNodes(count int, network *Network) (nodes []*Node, err error) { + for i := 0; i < count; i++ { + nodeConf := adapters.RandomNodeConfig() + node, err := network.NewNodeWithConfig(nodeConf) + if err != nil { + return nil, err + } + if err := network.Start(node.ID()); err != nil { + return nil, err + } + + nodes = append(nodes, node) + } + + return nodes, nil +} + +func createTestNodesWithProperty(property string, count int, network *Network) (propertyNodes []*Node, err error) { + for i := 0; i < count; i++ { + nodeConf := adapters.RandomNodeConfig() + nodeConf.Properties = append(nodeConf.Properties, property) + + node, err := network.NewNodeWithConfig(nodeConf) + if err != nil { + return nil, err + } + if err := network.Start(node.ID()); err != nil { + return nil, err + } + + propertyNodes = append(propertyNodes, node) + } + + return propertyNodes, nil +} + +// TestGetNodeIDs creates a set of nodes and attempts to retrieve their IDs,. +// It then tests again whilst excluding a node ID from being returned. +// If a node ID is not returned, or more node IDs than expected are returned, the test fails. +func TestGetNodeIDs(t *testing.T) { + adapter := adapters.NewSimAdapter(adapters.Services{ + "test": newTestService, + }) + network := NewNetwork(adapter, &NetworkConfig{ + DefaultService: "test", + }) + defer network.Shutdown() + + numNodes := 5 + nodes, err := createTestNodes(numNodes, network) + if err != nil { + t.Fatalf("Could not creat test nodes %v", err) + } + + gotNodeIDs := network.GetNodeIDs() + if len(gotNodeIDs) != numNodes { + t.Fatalf("Expected %d nodes, got %d", numNodes, len(gotNodeIDs)) + } + + for _, node1 := range nodes { + match := false + for _, node2ID := range gotNodeIDs { + if bytes.Equal(node1.ID().Bytes(), node2ID.Bytes()) { + match = true + break + } + } + + if !match { + t.Fatalf("A created node was not returned by GetNodes(), ID: %s", node1.ID().String()) + } + } + + excludeNodeID := nodes[3].ID() + gotNodeIDsExcl := network.GetNodeIDs(excludeNodeID) + if len(gotNodeIDsExcl) != numNodes-1 { + t.Fatalf("Expected one less node ID to be returned") + } + for _, nodeID := range gotNodeIDsExcl { + if bytes.Equal(excludeNodeID.Bytes(), nodeID.Bytes()) { + t.Fatalf("GetNodeIDs returned the node ID we excluded, ID: %s", nodeID.String()) + } + } +} + +// TestGetNodes creates a set of nodes and attempts to retrieve them again. +// It then tests again whilst excluding a node from being returned. +// If a node is not returned, or more nodes than expected are returned, the test fails. +func TestGetNodes(t *testing.T) { + adapter := adapters.NewSimAdapter(adapters.Services{ + "test": newTestService, + }) + network := NewNetwork(adapter, &NetworkConfig{ + DefaultService: "test", + }) + defer network.Shutdown() + + numNodes := 5 + nodes, err := createTestNodes(numNodes, network) + if err != nil { + t.Fatalf("Could not creat test nodes %v", err) + } + + gotNodes := network.GetNodes() + if len(gotNodes) != numNodes { + t.Fatalf("Expected %d nodes, got %d", numNodes, len(gotNodes)) + } + + for _, node1 := range nodes { + match := false + for _, node2 := range gotNodes { + if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) { + match = true + break + } + } + + if !match { + t.Fatalf("A created node was not returned by GetNodes(), ID: %s", node1.ID().String()) + } + } + + excludeNodeID := nodes[3].ID() + gotNodesExcl := network.GetNodes(excludeNodeID) + if len(gotNodesExcl) != numNodes-1 { + t.Fatalf("Expected one less node to be returned") + } + for _, node := range gotNodesExcl { + if bytes.Equal(excludeNodeID.Bytes(), node.ID().Bytes()) { + t.Fatalf("GetNodes returned the node we excluded, ID: %s", node.ID().String()) + } + } +} + +// TestGetNodesByID creates a set of nodes and attempts to retrieve a subset of them by ID +// If a node is not returned, or more nodes than expected are returned, the test fails. +func TestGetNodesByID(t *testing.T) { + adapter := adapters.NewSimAdapter(adapters.Services{ + "test": newTestService, + }) + network := NewNetwork(adapter, &NetworkConfig{ + DefaultService: "test", + }) + defer network.Shutdown() + + numNodes := 5 + nodes, err := createTestNodes(numNodes, network) + if err != nil { + t.Fatalf("Could not create test nodes: %v", err) + } + + numSubsetNodes := 2 + subsetNodes := nodes[0:numSubsetNodes] + var subsetNodeIDs []enode.ID + for _, node := range subsetNodes { + subsetNodeIDs = append(subsetNodeIDs, node.ID()) + } + + gotNodesByID := network.GetNodesByID(subsetNodeIDs) + if len(gotNodesByID) != numSubsetNodes { + t.Fatalf("Expected %d nodes, got %d", numSubsetNodes, len(gotNodesByID)) + } + + for _, node1 := range subsetNodes { + match := false + for _, node2 := range gotNodesByID { + if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) { + match = true + break + } + } + + if !match { + t.Fatalf("A created node was not returned by GetNodesByID(), ID: %s", node1.ID().String()) + } + } +} + +// TestGetNodesByProperty creates a subset of nodes with a property assigned. +// GetNodesByProperty is then checked for correctness by comparing the nodes returned to those initially created. +// If a node with a property is not found, or more nodes than expected are returned, the test fails. +func TestGetNodesByProperty(t *testing.T) { + adapter := adapters.NewSimAdapter(adapters.Services{ + "test": newTestService, + }) + network := NewNetwork(adapter, &NetworkConfig{ + DefaultService: "test", + }) + defer network.Shutdown() + + numNodes := 3 + _, err := createTestNodes(numNodes, network) + if err != nil { + t.Fatalf("Failed to create nodes: %v", err) + } + + numPropertyNodes := 3 + propertyTest := "test" + propertyNodes, err := createTestNodesWithProperty(propertyTest, numPropertyNodes, network) + if err != nil { + t.Fatalf("Failed to create nodes with property: %v", err) + } + + gotNodesByProperty := network.GetNodesByProperty(propertyTest) + if len(gotNodesByProperty) != numPropertyNodes { + t.Fatalf("Expected %d nodes with a property, got %d", numPropertyNodes, len(gotNodesByProperty)) + } + + for _, node1 := range propertyNodes { + match := false + for _, node2 := range gotNodesByProperty { + if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) { + match = true + break + } + } + + if !match { + t.Fatalf("A created node with property was not returned by GetNodesByProperty(), ID: %s", node1.ID().String()) + } + } +} + +// TestGetNodeIDsByProperty creates a subset of nodes with a property assigned. +// GetNodeIDsByProperty is then checked for correctness by comparing the node IDs returned to those initially created. +// If a node ID with a property is not found, or more nodes IDs than expected are returned, the test fails. +func TestGetNodeIDsByProperty(t *testing.T) { + adapter := adapters.NewSimAdapter(adapters.Services{ + "test": newTestService, + }) + network := NewNetwork(adapter, &NetworkConfig{ + DefaultService: "test", + }) + defer network.Shutdown() + + numNodes := 3 + _, err := createTestNodes(numNodes, network) + if err != nil { + t.Fatalf("Failed to create nodes: %v", err) + } + + numPropertyNodes := 3 + propertyTest := "test" + propertyNodes, err := createTestNodesWithProperty(propertyTest, numPropertyNodes, network) + if err != nil { + t.Fatalf("Failed to created nodes with property: %v", err) + } + + gotNodeIDsByProperty := network.GetNodeIDsByProperty(propertyTest) + if len(gotNodeIDsByProperty) != numPropertyNodes { + t.Fatalf("Expected %d nodes with a property, got %d", numPropertyNodes, len(gotNodeIDsByProperty)) + } + + for _, node1 := range propertyNodes { + match := false + id1 := node1.ID() + for _, id2 := range gotNodeIDsByProperty { + if bytes.Equal(id1.Bytes(), id2.Bytes()) { + match = true + break + } + } + + if !match { + t.Fatalf("Not all nodes IDs were returned by GetNodeIDsByProperty(), ID: %s", id1.String()) + } + } +} + func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, interval time.Duration) { tick := time.NewTicker(interval) defer tick.Stop() @@ -488,27 +761,22 @@ func benchmarkMinimalServiceTmp(b *testing.B) { } func TestNode_UnmarshalJSON(t *testing.T) { - t.Run( - "test unmarshal of Node up field", - func(t *testing.T) { - runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONUpField()) - }, - ) - t.Run( - "test unmarshal of Node Config field", - func(t *testing.T) { - runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONConfigField()) - }, - ) + t.Run("up_field", func(t *testing.T) { + runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONUpField()) + }) + t.Run("config_field", func(t *testing.T) { + runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONConfigField()) + }) } func runNodeUnmarshalJSON(t *testing.T, tests []nodeUnmarshalTestCase) { t.Helper() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var got Node - if err := got.UnmarshalJSON([]byte(tt.marshaled)); err != nil { + var got *Node + if err := json.Unmarshal([]byte(tt.marshaled), &got); err != nil { expectErrorMessageToContain(t, err, tt.wantErr) + got = nil } expectNodeEquality(t, got, tt.want) }) @@ -518,7 +786,7 @@ func runNodeUnmarshalJSON(t *testing.T, tests []nodeUnmarshalTestCase) { type nodeUnmarshalTestCase struct { name string marshaled string - want Node + want *Node wantErr string } @@ -542,7 +810,7 @@ func expectErrorMessageToContain(t *testing.T, got error, want string) { } } -func expectNodeEquality(t *testing.T, got Node, want Node) { +func expectNodeEquality(t *testing.T, got, want *Node) { t.Helper() if !reflect.DeepEqual(got, want) { t.Errorf("Node.UnmarshalJSON() = %v, want %v", got, want) @@ -554,23 +822,17 @@ func casesNodeUnmarshalJSONUpField() []nodeUnmarshalTestCase { { name: "empty json", marshaled: "{}", - want: Node{ - up: false, - }, + want: newNode(nil, nil, false), }, { name: "a stopped node", marshaled: "{\"up\": false}", - want: Node{ - up: false, - }, + want: newNode(nil, nil, false), }, { name: "a running node", marshaled: "{\"up\": true}", - want: Node{ - up: true, - }, + want: newNode(nil, nil, true), }, { name: "invalid JSON value on valid key", @@ -597,26 +859,17 @@ func casesNodeUnmarshalJSONConfigField() []nodeUnmarshalTestCase { { name: "Config field is omitted", marshaled: "{}", - want: Node{ - Config: nil, - }, + want: newNode(nil, nil, false), }, { name: "Config field is nil", - marshaled: "{\"config\": nil}", - want: Node{ - Config: nil, - }, + marshaled: "{\"config\": null}", + want: newNode(nil, nil, false), }, { name: "a non default Config field", marshaled: "{\"config\":{\"name\":\"node_ecdd0\",\"port\":44665}}", - want: Node{ - Config: &adapters.NodeConfig{ - Name: "node_ecdd0", - Port: 44665, - }, - }, + want: newNode(nil, &adapters.NodeConfig{Name: "node_ecdd0", Port: 44665}, false), }, } } diff --git a/p2p/testing/protocolsession.go b/p2p/testing/protocolsession.go index 5fc29099bd..65a4909688 100644 --- a/p2p/testing/protocolsession.go +++ b/p2p/testing/protocolsession.go @@ -243,6 +243,7 @@ func (s *ProtocolSession) testExchange(e Exchange) error { t = 2000 * time.Millisecond } alarm := time.NewTimer(t) + defer alarm.Stop() select { case err := <-errc: return err diff --git a/p2p/transport.go b/p2p/transport.go new file mode 100644 index 0000000000..c82d94ed13 --- /dev/null +++ b/p2p/transport.go @@ -0,0 +1,183 @@ +// 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 . + +package p2p + +import ( + "bytes" + "crypto/ecdsa" + "fmt" + "io" + "net" + "sync" + "time" + + "github.com/AlayaNetwork/Alaya-Go/common" + + "github.com/AlayaNetwork/Alaya-Go/common/bitutil" + "github.com/AlayaNetwork/Alaya-Go/metrics" + "github.com/AlayaNetwork/Alaya-Go/p2p/rlpx" + "github.com/AlayaNetwork/Alaya-Go/rlp" +) + +const ( + // total timeout for encryption handshake and protocol + // handshake in both directions. + handshakeTimeout = 5 * time.Second + + // This is the timeout for sending the disconnect reason. + // This is shorter than the usual timeout because we don't want + // to wait if the connection is known to be bad anyway. + discWriteTimeout = 1 * time.Second +) + +// rlpxTransport is the transport used by actual (non-test) connections. +// It wraps an RLPx connection with locks and read/write deadlines. +type rlpxTransport struct { + rmu, wmu sync.Mutex + wbuf bytes.Buffer + conn *rlpx.Conn +} + +func newRLPX(conn net.Conn, dialDest *ecdsa.PublicKey) transport { + return &rlpxTransport{conn: rlpx.NewConn(conn, dialDest)} +} + +func (t *rlpxTransport) ReadMsg() (Msg, error) { + t.rmu.Lock() + defer t.rmu.Unlock() + + var msg Msg + t.conn.SetReadDeadline(time.Now().Add(frameReadTimeout)) + code, data, wireSize, err := t.conn.Read() + if err == nil { + // Protocol messages are dispatched to subprotocol handlers asynchronously, + // but package rlpx may reuse the returned 'data' buffer on the next call + // to Read. Copy the message data to avoid this being an issue. + data = common.CopyBytes(data) + msg = Msg{ + ReceivedAt: time.Now(), + Code: code, + Size: uint32(len(data)), + meterSize: uint32(wireSize), + Payload: bytes.NewReader(data), + } + } + return msg, err +} + +func (t *rlpxTransport) WriteMsg(msg Msg) error { + t.wmu.Lock() + defer t.wmu.Unlock() + + // Copy message data to write buffer. + t.wbuf.Reset() + if _, err := io.CopyN(&t.wbuf, msg.Payload, int64(msg.Size)); err != nil { + return err + } + + // Write the message. + t.conn.SetWriteDeadline(time.Now().Add(frameWriteTimeout)) + size, err := t.conn.Write(msg.Code, t.wbuf.Bytes()) + if err != nil { + return err + } + + // Set metrics. + msg.meterSize = size + if metrics.Enabled && msg.meterCap.Name != "" { // don't meter non-subprotocol messages + m := fmt.Sprintf("%s/%s/%d/%#02x", egressMeterName, msg.meterCap.Name, msg.meterCap.Version, msg.meterCode) + metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize)) + metrics.GetOrRegisterMeter(m+"/packets", nil).Mark(1) + } + return nil +} + +func (t *rlpxTransport) close(err error) { + t.wmu.Lock() + defer t.wmu.Unlock() + + // Tell the remote end why we're disconnecting if possible. + // We only bother doing this if the underlying connection supports + // setting a timeout tough. + if t.conn != nil { + if r, ok := err.(DiscReason); ok && r != DiscNetworkError { + deadline := time.Now().Add(discWriteTimeout) + if err := t.conn.SetWriteDeadline(deadline); err == nil { + // Connection supports write deadline. + t.wbuf.Reset() + rlp.Encode(&t.wbuf, []DiscReason{r}) + t.conn.Write(discMsg, t.wbuf.Bytes()) + } + } + } + t.conn.Close() +} + +func (t *rlpxTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) { + t.conn.SetDeadline(time.Now().Add(handshakeTimeout)) + return t.conn.Handshake(prv) +} + +func (t *rlpxTransport) doProtoHandshake(our *protoHandshake) (their *protoHandshake, err error) { + // Writing our handshake happens concurrently, we prefer + // returning the handshake read error. If the remote side + // disconnects us early with a valid reason, we should return it + // as the error so it can be tracked elsewhere. + werr := make(chan error, 1) + go func() { werr <- Send(t, handshakeMsg, our) }() + if their, err = readProtocolHandshake(t); err != nil { + <-werr // make sure the write terminates too + return nil, err + } + if err := <-werr; err != nil { + return nil, fmt.Errorf("write error: %v", err) + } + // If the protocol version supports Snappy encoding, upgrade immediately + t.conn.SetSnappy(their.Version >= snappyProtocolVersion) + + return their, nil +} + +func readProtocolHandshake(rw MsgReader) (*protoHandshake, error) { + msg, err := rw.ReadMsg() + if err != nil { + return nil, err + } + if msg.Size > baseProtocolMaxMsgSize { + return nil, fmt.Errorf("message too big") + } + if msg.Code == discMsg { + // Disconnect before protocol handshake is valid according to the + // spec and we send it ourself if the post-handshake checks fail. + // We can't return the reason directly, though, because it is echoed + // back otherwise. Wrap it in a string instead. + var reason [1]DiscReason + rlp.Decode(msg.Payload, &reason) + return nil, reason[0] + } + if msg.Code != handshakeMsg { + return nil, fmt.Errorf("expected handshake, got %x", msg.Code) + } + var hs protoHandshake + if err := msg.Decode(&hs); err != nil { + return nil, err + } + if len(hs.ID) != 64 || !bitutil.TestBytes(hs.ID) { + return nil, DiscInvalidIdentity + } + return &hs, nil +} diff --git a/p2p/transport_test.go b/p2p/transport_test.go new file mode 100644 index 0000000000..7c76beb2ed --- /dev/null +++ b/p2p/transport_test.go @@ -0,0 +1,149 @@ +// 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 . + +package p2p + +import ( + "errors" + "reflect" + "sync" + "testing" + + "github.com/davecgh/go-spew/spew" + + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/p2p/simulations/pipes" +) + +func TestProtocolHandshake(t *testing.T) { + var ( + prv0, _ = crypto.GenerateKey() + pub0 = crypto.FromECDSAPub(&prv0.PublicKey)[1:] + hs0 = &protoHandshake{Version: 3, ID: pub0, Caps: []Cap{{"a", 0}, {"b", 2}}} + + prv1, _ = crypto.GenerateKey() + pub1 = crypto.FromECDSAPub(&prv1.PublicKey)[1:] + hs1 = &protoHandshake{Version: 3, ID: pub1, Caps: []Cap{{"c", 1}, {"d", 3}}} + + wg sync.WaitGroup + ) + + fd0, fd1, err := pipes.TCPPipe() + if err != nil { + t.Fatal(err) + } + + wg.Add(2) + go func() { + defer wg.Done() + defer fd0.Close() + frame := newRLPX(fd0, &prv1.PublicKey) + rpubkey, err := frame.doEncHandshake(prv0) + if err != nil { + t.Errorf("dial side enc handshake failed: %v", err) + return + } + if !reflect.DeepEqual(rpubkey, &prv1.PublicKey) { + t.Errorf("dial side remote pubkey mismatch: got %v, want %v", rpubkey, &prv1.PublicKey) + return + } + + phs, err := frame.doProtoHandshake(hs0) + if err != nil { + t.Errorf("dial side proto handshake error: %v", err) + return + } + phs.Rest = nil + if !reflect.DeepEqual(phs, hs1) { + t.Errorf("dial side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs1)) + return + } + frame.close(DiscQuitting) + }() + go func() { + defer wg.Done() + defer fd1.Close() + rlpx := newRLPX(fd1, nil) + rpubkey, err := rlpx.doEncHandshake(prv1) + if err != nil { + t.Errorf("listen side enc handshake failed: %v", err) + return + } + if !reflect.DeepEqual(rpubkey, &prv0.PublicKey) { + t.Errorf("listen side remote pubkey mismatch: got %v, want %v", rpubkey, &prv0.PublicKey) + return + } + + phs, err := rlpx.doProtoHandshake(hs1) + if err != nil { + t.Errorf("listen side proto handshake error: %v", err) + return + } + phs.Rest = nil + if !reflect.DeepEqual(phs, hs0) { + t.Errorf("listen side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs0)) + return + } + + if err := ExpectMsg(rlpx, discMsg, []DiscReason{DiscQuitting}); err != nil { + t.Errorf("error receiving disconnect: %v", err) + } + }() + wg.Wait() +} + +func TestProtocolHandshakeErrors(t *testing.T) { + tests := []struct { + code uint64 + msg interface{} + err error + }{ + { + code: discMsg, + msg: []DiscReason{DiscQuitting}, + err: DiscQuitting, + }, + { + code: 0x989898, + msg: []byte{1}, + err: errors.New("expected handshake, got 989898"), + }, + { + code: handshakeMsg, + msg: make([]byte, baseProtocolMaxMsgSize+2), + err: errors.New("message too big"), + }, + { + code: handshakeMsg, + msg: []byte{1, 2, 3}, + err: newPeerError(errInvalidMsg, "(code 0) (size 4) rlp: expected input list for p2p.protoHandshake"), + }, + { + code: handshakeMsg, + msg: &protoHandshake{Version: 3}, + err: DiscInvalidIdentity, + }, + } + + for i, test := range tests { + p1, p2 := MsgPipe() + go Send(p1, test.code, test.msg) + _, err := readProtocolHandshake(p2) + if !reflect.DeepEqual(err, test.err) { + t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err) + } + } +} diff --git a/p2p/util.go b/p2p/util.go new file mode 100644 index 0000000000..20a8f0a54e --- /dev/null +++ b/p2p/util.go @@ -0,0 +1,85 @@ +// 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 . + +package p2p + +import ( + "container/heap" + + "github.com/AlayaNetwork/Alaya-Go/common/mclock" +) + +// expHeap tracks strings and their expiry time. +type expHeap []expItem + +// expItem is an entry in addrHistory. +type expItem struct { + item string + exp mclock.AbsTime +} + +// nextExpiry returns the next expiry time. +func (h *expHeap) nextExpiry() mclock.AbsTime { + return (*h)[0].exp +} + +// add adds an item and sets its expiry time. +func (h *expHeap) add(item string, exp mclock.AbsTime) { + heap.Push(h, expItem{item, exp}) +} + +// contains checks whether an item is present. +func (h expHeap) contains(item string) bool { + for _, v := range h { + if v.item == item { + return true + } + } + return false +} + +// expire removes items with expiry time before 'now'. +func (h *expHeap) expire(now mclock.AbsTime, onExp func(string)) { + for h.Len() > 0 && h.nextExpiry() < now { + item := heap.Pop(h) + if onExp != nil { + onExp(item.(expItem).item) + } + } +} + +func (h *expHeap) remove(item string) bool { + for i, v := range *h { + if v.item == item { + heap.Remove(h, i) + return true + } + } + return false +} + +// heap.Interface boilerplate +func (h expHeap) Len() int { return len(h) } +func (h expHeap) Less(i, j int) bool { return h[i].exp < h[j].exp } +func (h expHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } +func (h *expHeap) Push(x interface{}) { *h = append(*h, x.(expItem)) } +func (h *expHeap) Pop() interface{} { + old := *h + n := len(old) + x := old[n-1] + *h = old[0 : n-1] + return x +} diff --git a/p2p/util_test.go b/p2p/util_test.go new file mode 100644 index 0000000000..af4848fff7 --- /dev/null +++ b/p2p/util_test.go @@ -0,0 +1,56 @@ +// 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 . + +package p2p + +import ( + "testing" + "time" + + "github.com/AlayaNetwork/Alaya-Go/common/mclock" +) + +func TestExpHeap(t *testing.T) { + var h expHeap + + var ( + basetime = mclock.AbsTime(10) + exptimeA = basetime.Add(2 * time.Second) + exptimeB = basetime.Add(3 * time.Second) + exptimeC = basetime.Add(4 * time.Second) + ) + h.add("b", exptimeB) + h.add("a", exptimeA) + h.add("c", exptimeC) + + if h.nextExpiry() != exptimeA { + t.Fatal("wrong nextExpiry") + } + if !h.contains("a") || !h.contains("b") || !h.contains("c") { + t.Fatal("heap doesn't contain all live items") + } + + h.expire(exptimeA.Add(1), nil) + if h.nextExpiry() != exptimeB { + t.Fatal("wrong nextExpiry") + } + if h.contains("a") { + t.Fatal("heap contains a even though it has already expired") + } + if !h.contains("b") || !h.contains("c") { + t.Fatal("heap doesn't contain all live items") + } +} diff --git a/params/config.go b/params/config.go index f0e9570c19..8b4f4d2707 100644 --- a/params/config.go +++ b/params/config.go @@ -334,7 +334,7 @@ func ConvertNodeUrl(initialNodes []initNode) []CbftNode { cbftNode := new(CbftNode) - if node, err := enode.ParseV4(n.Enode); nil == err { + if node, err := enode.Parse(enode.ValidSchemes, n.Enode); nil == err { cbftNode.Node = node } diff --git a/rlp/decode.go b/rlp/decode.go index e1c692f869..5f2e5ad5fe 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -29,12 +29,13 @@ import ( "sync" ) -var ( - // EOL is returned when the end of the current list - // has been reached during streaming. - EOL = errors.New("rlp: end of list") +//lint:ignore ST1012 EOL is not an error. + +// EOL is returned when the end of the current list +// has been reached during streaming. +var EOL = errors.New("rlp: end of list") - // Actual Errors +var ( ErrExpectedString = errors.New("rlp: expected String or Byte") ErrExpectedList = errors.New("rlp: expected List") ErrCanonInt = errors.New("rlp: non-canonical integer format") @@ -55,81 +56,23 @@ var ( } ) -// Decoder is implemented by types that require custom RLP -// decoding rules or need to decode into private fields. +// Decoder is implemented by types that require custom RLP decoding rules or need to decode +// into private fields. // -// The DecodeRLP method should read one value from the given -// Stream. It is not forbidden to read less or more, but it might -// be confusing. +// The DecodeRLP method should read one value from the given Stream. It is not forbidden to +// read less or more, but it might be confusing. type Decoder interface { DecodeRLP(*Stream) error } -// Decode parses RLP-encoded data from r and stores the result in the -// value pointed to by val. Val must be a non-nil pointer. If r does -// not implement ByteReader, Decode will do its own buffering. -// -// Decode uses the following type-dependent decoding rules: -// -// If the type implements the Decoder interface, decode calls -// DecodeRLP. -// -// To decode into a pointer, Decode will decode into the value pointed -// to. If the pointer is nil, a new value of the pointer's element -// type is allocated. If the pointer is non-nil, the existing value -// will be reused. -// -// To decode into a struct, Decode expects the input to be an RLP -// list. The decoded elements of the list are assigned to each public -// field in the order given by the struct's definition. The input list -// must contain an element for each decoded field. Decode returns an -// error if there are too few or too many elements. -// -// The decoding of struct fields honours certain struct tags, "tail", -// "nil" and "-". -// -// The "-" tag ignores fields. -// -// For an explanation of "tail", see the example. -// -// The "nil" tag applies to pointer-typed fields and changes the decoding -// rules for the field such that input values of size zero decode as a nil -// pointer. This tag can be useful when decoding recursive types. -// -// type StructWithEmptyOK struct { -// Foo *[20]byte `rlp:"nil"` -// } +// Decode parses RLP-encoded data from r and stores the result in the value pointed to by +// val. Please see package-level documentation for the decoding rules. Val must be a +// non-nil pointer. // -// To decode into a slice, the input must be a list and the resulting -// slice will contain the input elements in order. For byte slices, -// the input must be an RLP string. Array types decode similarly, with -// the additional restriction that the number of input elements (or -// bytes) must match the array's length. +// If r does not implement ByteReader, Decode will do its own buffering. // -// To decode into a Go string, the input must be an RLP string. The -// input bytes are taken as-is and will not necessarily be valid UTF-8. -// -// To decode into an unsigned integer type, the input must also be an RLP -// string. The bytes are interpreted as a big endian representation of -// the integer. If the RLP string is larger than the bit size of the -// type, Decode will return an error. Decode also supports *big.Int. -// There is no size limit for big integers. -// -// To decode into a boolean, the input must contain an unsigned integer -// of value zero (false) or one (true). -// -// To decode into an interface value, Decode stores one of these -// in the value: -// -// []interface{}, for RLP lists -// []byte, for RLP strings -// -// Non-empty interface types are not supported, nor are signed integers, -// floating point numbers, maps, channels and functions. -// -// Note that Decode does not set an input limit for all readers -// and may be vulnerable to panics cause by huge value sizes. If -// you need an input limit, use +// Note that Decode does not set an input limit for all readers and may be vulnerable to +// panics cause by huge value sizes. If you need an input limit, use // // NewStream(r, limit).Decode(val) func Decode(r io.Reader, val interface{}) error { @@ -140,11 +83,11 @@ func Decode(r io.Reader, val interface{}) error { return stream.Decode(val) } -// DecodeBytes parses RLP data from b into val. -// Please see the documentation of Decode for the decoding rules. -// The input must contain exactly one value and no trailing data. +// DecodeBytes parses RLP data from b into val. Please see package-level documentation for +// the decoding rules. The input must contain exactly one value and no trailing data. func DecodeBytes(b []byte, val interface{}) error { r := bytes.NewReader(b) + stream := streamPool.Get().(*Stream) defer streamPool.Put(stream) @@ -153,7 +96,6 @@ func DecodeBytes(b []byte, val interface{}) error { return err } if r.Len() > 0 { - //log.Error("Failed to DecodeBytes", "err", ErrMoreThanOneValue, "[]bytecontext", fmt.Sprintf(" pointer:%p, value:%+v, len:%d, context's Hash:%v", b, b, len(b), hex.EncodeToString(b))) return ErrMoreThanOneValue } return nil @@ -211,14 +153,14 @@ func makeDecoder(typ reflect.Type, tags tags) (dec decoder, err error) { switch { case typ == rawValueType: return decodeRawValue, nil - case typ.Implements(decoderInterface): - return decodeDecoder, nil - case kind != reflect.Ptr && reflect.PtrTo(typ).Implements(decoderInterface): - return decodeDecoderNoPtr, nil case typ.AssignableTo(reflect.PtrTo(bigInt)): return decodeBigInt, nil case typ.AssignableTo(bigInt): return decodeBigIntNoPtr, nil + case kind == reflect.Ptr: + return makePtrDecoder(typ, tags) + case reflect.PtrTo(typ).Implements(decoderInterface): + return decodeDecoder, nil case isUint(kind): return decodeUint, nil case kind == reflect.Bool: @@ -229,11 +171,6 @@ func makeDecoder(typ reflect.Type, tags tags) (dec decoder, err error) { return makeListDecoder(typ, tags) case kind == reflect.Struct: return makeStructDecoder(typ) - case kind == reflect.Ptr: - if tags.nilOK { - return makeOptionalPtrDecoder(typ) - } - return makePtrDecoder(typ) case kind == reflect.Interface: return decodeInterface, nil default: @@ -283,20 +220,51 @@ func decodeBigIntNoPtr(s *Stream, val reflect.Value) error { } func decodeBigInt(s *Stream, val reflect.Value) error { - b, err := s.Bytes() - if err != nil { + var buffer []byte + kind, size, err := s.Kind() + switch { + case err != nil: return wrapStreamError(err, val.Type()) + case kind == List: + return wrapStreamError(ErrExpectedString, val.Type()) + case kind == Byte: + buffer = s.uintbuf[:1] + buffer[0] = s.byteval + s.kind = -1 // re-arm Kind + case size == 0: + // Avoid zero-length read. + s.kind = -1 + case size <= uint64(len(s.uintbuf)): + // For integers smaller than s.uintbuf, allocating a buffer + // can be avoided. + buffer = s.uintbuf[:size] + if err := s.readFull(buffer); err != nil { + return wrapStreamError(err, val.Type()) + } + // Reject inputs where single byte encoding should have been used. + if size == 1 && buffer[0] < 128 { + return wrapStreamError(ErrCanonSize, val.Type()) + } + default: + // For large integers, a temporary buffer is needed. + buffer = make([]byte, size) + if err := s.readFull(buffer); err != nil { + return wrapStreamError(err, val.Type()) + } } + + // Reject leading zero bytes. + if len(buffer) > 0 && buffer[0] == 0 { + return wrapStreamError(ErrCanonInt, val.Type()) + } + + // Set the integer bytes. i := val.Interface().(*big.Int) if i == nil { i = new(big.Int) val.Set(reflect.ValueOf(i)) } - // Reject leading zero bytes - if len(b) > 0 && b[0] == 0 { - return wrapStreamError(ErrCanonInt, val.Type()) - } - i.SetBytes(b) + i.SetBytes(buffer) return nil } @@ -308,7 +276,7 @@ func makeListDecoder(typ reflect.Type, tag tags) (decoder, error) { } return decodeByteSlice, nil } - etypeinfo := cachedTypeInfo1(etype, tags{}) + etypeinfo := theTC.infoWhileGenerating(etype, tags{}) if etypeinfo.decoderErr != nil { return nil, etypeinfo.decoderErr } @@ -411,25 +379,23 @@ func decodeByteArray(s *Stream, val reflect.Value) error { if err != nil { return err } - vlen := val.Len() + slice := byteArrayBytes(val, val.Len()) switch kind { case Byte: - if vlen == 0 { + if len(slice) == 0 { return &decodeError{msg: "input string too long", typ: val.Type()} - } - if vlen > 1 { + } else if len(slice) > 1 { return &decodeError{msg: "input string too short", typ: val.Type()} } - bv, _ := s.Uint() - val.Index(0).SetUint(bv) + slice[0] = s.byteval + s.kind = -1 case String: - if uint64(vlen) < size { + if uint64(len(slice)) < size { return &decodeError{msg: "input string too long", typ: val.Type()} } - if uint64(vlen) > size { + if uint64(len(slice)) > size { return &decodeError{msg: "input string too short", typ: val.Type()} } - slice := val.Slice(0, vlen).Interface().([]byte) if err := s.readFull(slice); err != nil { return err } @@ -448,13 +414,25 @@ func makeStructDecoder(typ reflect.Type) (decoder, error) { if err != nil { return nil, err } + for _, f := range fields { + if f.info.decoderErr != nil { + return nil, structFieldError{typ, f.index, f.info.decoderErr} + } + } dec := func(s *Stream, val reflect.Value) (err error) { if _, err := s.List(); err != nil { return wrapStreamError(err, typ) } - for _, f := range fields { + for i, f := range fields { err := f.info.decoder(s, val.Field(f.index)) if err == EOL { + if f.optional { + // The field is optional, so reaching the end of the list before + // reaching the last field is acceptable. All remaining undecoded + // fields are zeroed. + zeroFields(val, fields[i:]) + break + } return &decodeError{msg: "too few elements", typ: typ} } else if err != nil { return addErrorContext(err, "."+typ.Field(f.index).Name) @@ -465,15 +443,29 @@ func makeStructDecoder(typ reflect.Type) (decoder, error) { return dec, nil } -// makePtrDecoder creates a decoder that decodes into -// the pointer's element type. -func makePtrDecoder(typ reflect.Type) (decoder, error) { +func zeroFields(structval reflect.Value, fields []field) { + for _, f := range fields { + fv := structval.Field(f.index) + fv.Set(reflect.Zero(fv.Type())) + } +} + +// makePtrDecoder creates a decoder that decodes into the pointer's element type. +func makePtrDecoder(typ reflect.Type, tag tags) (decoder, error) { etype := typ.Elem() - etypeinfo := cachedTypeInfo1(etype, tags{}) - if etypeinfo.decoderErr != nil { + etypeinfo := theTC.infoWhileGenerating(etype, tags{}) + switch { + case etypeinfo.decoderErr != nil: return nil, etypeinfo.decoderErr + case !tag.nilOK: + return makeSimplePtrDecoder(etype, etypeinfo), nil + default: + return makeNilPtrDecoder(etype, etypeinfo, tag.nilKind), nil } - dec := func(s *Stream, val reflect.Value) (err error) { +} + +func makeSimplePtrDecoder(etype reflect.Type, etypeinfo *typeinfo) decoder { + return func(s *Stream, val reflect.Value) (err error) { newval := val if val.IsNil() { newval = reflect.New(etype) @@ -483,30 +475,35 @@ func makePtrDecoder(typ reflect.Type) (decoder, error) { } return err } - return dec, nil } -// makeOptionalPtrDecoder creates a decoder that decodes empty values -// as nil. Non-empty values are decoded into a value of the element type, -// just like makePtrDecoder does. +// makeNilPtrDecoder creates a decoder that decodes empty values as nil. Non-empty +// values are decoded into a value of the element type, just like makePtrDecoder does. // // This decoder is used for pointer-typed struct fields with struct tag "nil". -func makeOptionalPtrDecoder(typ reflect.Type) (decoder, error) { - etype := typ.Elem() - etypeinfo := cachedTypeInfo1(etype, tags{}) - if etypeinfo.decoderErr != nil { - return nil, etypeinfo.decoderErr - } - dec := func(s *Stream, val reflect.Value) (err error) { +func makeNilPtrDecoder(etype reflect.Type, etypeinfo *typeinfo, nilKind Kind) decoder { + typ := reflect.PtrTo(etype) + nilPtr := reflect.Zero(typ) + return func(s *Stream, val reflect.Value) (err error) { kind, size, err := s.Kind() - if err != nil || size == 0 && kind != Byte { + if err != nil { + val.Set(nilPtr) + return wrapStreamError(err, typ) + } + // Handle empty values as a nil pointer. + if kind != Byte && size == 0 { + if kind != nilKind { + return &decodeError{ + msg: fmt.Sprintf("wrong kind of empty value (got %v, want %v)", kind, nilKind), + typ: typ, + } + } // rearm s.Kind. This is important because the input // position must advance to the next value even though // we don't read anything. s.kind = -1 - // set the pointer to nil. - val.Set(reflect.Zero(typ)) - return err + val.Set(nilPtr) + return nil } newval := val if val.IsNil() { @@ -517,7 +514,6 @@ func makeOptionalPtrDecoder(typ reflect.Type) (decoder, error) { } return err } - return dec, nil } var ifsliceType = reflect.TypeOf([]interface{}{}) @@ -546,25 +542,12 @@ func decodeInterface(s *Stream, val reflect.Value) error { return nil } -// This decoder is used for non-pointer values of types -// that implement the Decoder interface using a pointer receiver. -func decodeDecoderNoPtr(s *Stream, val reflect.Value) error { - return val.Addr().Interface().(Decoder).DecodeRLP(s) -} - func decodeDecoder(s *Stream, val reflect.Value) error { - // Decoder instances are not handled using the pointer rule if the type - // implements Decoder with pointer receiver (i.e. always) - // because it might handle empty values specially. - // We need to allocate one here in this case, like makePtrDecoder does. - if val.Kind() == reflect.Ptr && val.IsNil() { - val.Set(reflect.New(val.Type().Elem())) - } - return val.Interface().(Decoder).DecodeRLP(s) + return val.Addr().Interface().(Decoder).DecodeRLP(s) } // Kind represents the kind of value contained in an RLP stream. -type Kind int +type Kind int8 const ( Byte Kind = iota @@ -607,22 +590,16 @@ type ByteReader interface { type Stream struct { r ByteReader - // number of bytes remaining to be read from r. - remaining uint64 - limited bool - - // auxiliary buffer for integer decoding - uintbuf []byte - - kind Kind // kind of value ahead - size uint64 // size of value ahead - byteval byte // value of single byte in type tag - kinderr error // error from last readKind - stack []listpos + remaining uint64 // number of bytes remaining to be read from r + size uint64 // size of value ahead + kinderr error // error from last readKind + stack []uint64 // list sizes + uintbuf [32]byte // auxiliary buffer for integer decoding + kind Kind // kind of value ahead + byteval byte // value of single byte in type tag + limited bool // true if input limit is in effect } -type listpos struct{ pos, size uint64 } - // NewStream creates a new decoding stream reading from r. // // If r implements the ByteReader interface, Stream will @@ -692,8 +669,8 @@ func (s *Stream) Raw() ([]byte, error) { s.kind = -1 // rearm Kind return []byte{s.byteval}, nil } - // the original header has already been read and is no longer - // available. read content and put a new header in front of it. + // The original header has already been read and is no longer + // available. Read content and put a new header in front of it. start := headsize(size) buf := make([]byte, uint64(start)+size) if err := s.readFull(buf[start:]); err != nil { @@ -776,7 +753,14 @@ func (s *Stream) List() (size uint64, err error) { if kind != List { return 0, ErrExpectedList } - s.stack = append(s.stack, listpos{0, size}) + + // Remove size of inner list from outer list before pushing the new size + // onto the stack. This ensures that the remaining outer list size will + // be correct after the matching call to ListEnd. + if inList, limit := s.listLimit(); inList { + s.stack[len(s.stack)-1] = limit - size + } + s.stack = append(s.stack, size) s.kind = -1 s.size = 0 return size, nil @@ -785,17 +769,13 @@ func (s *Stream) List() (size uint64, err error) { // ListEnd returns to the enclosing list. // The input reader must be positioned at the end of a list. func (s *Stream) ListEnd() error { - if len(s.stack) == 0 { + // Ensure that no more data is remaining in the current list. + if inList, listLimit := s.listLimit(); !inList { return errNotInList - } - tos := s.stack[len(s.stack)-1] - if tos.pos != tos.size { + } else if listLimit > 0 { return errNotAtEOL } s.stack = s.stack[:len(s.stack)-1] // pop - if len(s.stack) > 0 { - s.stack[len(s.stack)-1].pos += tos.size - } s.kind = -1 s.size = 0 return nil @@ -823,7 +803,7 @@ func (s *Stream) Decode(val interface{}) error { err = decoder(s, rval.Elem()) if decErr, ok := err.(*decodeError); ok && len(decErr.ctx) > 0 { - // add decode target type to error so context has more meaning + // Add decode target type to error so context has more meaning. decErr.ctx = append(decErr.ctx, fmt.Sprint("(", rtyp.Elem(), ")")) } return err @@ -846,6 +826,9 @@ func (s *Stream) Reset(r io.Reader, inputLimit uint64) { case *bytes.Reader: s.remaining = uint64(br.Len()) s.limited = true + case *bytes.Buffer: + s.remaining = uint64(br.Len()) + s.limited = true case *strings.Reader: s.remaining = uint64(br.Len()) s.limited = true @@ -864,10 +847,8 @@ func (s *Stream) Reset(r io.Reader, inputLimit uint64) { s.size = 0 s.kind = -1 s.kinderr = nil - if s.uintbuf == nil { - s.uintbuf = make([]byte, 8) - } s.byteval = 0 + s.uintbuf = [32]byte{} } // Kind returns the kind and size of the next value in the @@ -882,35 +863,29 @@ func (s *Stream) Reset(r io.Reader, inputLimit uint64) { // the value. Subsequent calls to Kind (until the value is decoded) // will not advance the input reader and return cached information. func (s *Stream) Kind() (kind Kind, size uint64, err error) { - var tos *listpos - if len(s.stack) > 0 { - tos = &s.stack[len(s.stack)-1] - } - if s.kind < 0 { - s.kinderr = nil - // Don't read further if we're at the end of the - // innermost list. - if tos != nil && tos.pos == tos.size { - return 0, 0, EOL - } - s.kind, s.size, s.kinderr = s.readKind() - if s.kinderr == nil { - if tos == nil { - // At toplevel, check that the value is smaller - // than the remaining input length. - if s.limited && s.size > s.remaining { - s.kinderr = ErrValueTooLarge - } - } else { - // Inside a list, check that the value doesn't overflow the list. - if s.size > tos.size-tos.pos { - s.kinderr = ErrElemTooLarge - } - } + if s.kind >= 0 { + return s.kind, s.size, s.kinderr + } + + // Check for end of list. This needs to be done here because readKind + // checks against the list size, and would return the wrong error. + inList, listLimit := s.listLimit() + if inList && listLimit == 0 { + return 0, 0, EOL + } + // Read the actual size tag. + s.kind, s.size, s.kinderr = s.readKind() + if s.kinderr == nil { + // Check the data size of the value ahead against input limits. This + // is done here because many decoders require allocating an input + // buffer matching the value size. Checking it here protects those + // decoders from inputs declaring very large value size. + if inList && s.size > listLimit { + s.kinderr = ErrElemTooLarge + } else if s.limited && s.size > s.remaining { + s.kinderr = ErrValueTooLarge } } - // Note: this might return a sticky error generated - // by an earlier call to readKind. return s.kind, s.size, s.kinderr } @@ -937,37 +912,35 @@ func (s *Stream) readKind() (kind Kind, size uint64, err error) { s.byteval = b return Byte, 0, nil case b < 0xB8: - // Otherwise, if a string is 0-55 bytes long, - // the RLP encoding consists of a single byte with value 0x80 plus the - // length of the string followed by the string. The range of the first - // byte is thus [0x80, 0xB7]. + // Otherwise, if a string is 0-55 bytes long, the RLP encoding consists + // of a single byte with value 0x80 plus the length of the string + // followed by the string. The range of the first byte is thus [0x80, 0xB7]. return String, uint64(b - 0x80), nil case b < 0xC0: - // If a string is more than 55 bytes long, the - // RLP encoding consists of a single byte with value 0xB7 plus the length - // of the length of the string in binary form, followed by the length of - // the string, followed by the string. For example, a length-1024 string - // would be encoded as 0xB90400 followed by the string. The range of - // the first byte is thus [0xB8, 0xBF]. + // If a string is more than 55 bytes long, the RLP encoding consists of a + // single byte with value 0xB7 plus the length of the length of the + // string in binary form, followed by the length of the string, followed + // by the string. For example, a length-1024 string would be encoded as + // 0xB90400 followed by the string. The range of the first byte is thus + // [0xB8, 0xBF]. size, err = s.readUint(b - 0xB7) if err == nil && size < 56 { err = ErrCanonSize } return String, size, err case b < 0xF8: - // If the total payload of a list - // (i.e. the combined length of all its items) is 0-55 bytes long, the - // RLP encoding consists of a single byte with value 0xC0 plus the length - // of the list followed by the concatenation of the RLP encodings of the - // items. The range of the first byte is thus [0xC0, 0xF7]. + // If the total payload of a list (i.e. the combined length of all its + // items) is 0-55 bytes long, the RLP encoding consists of a single byte + // with value 0xC0 plus the length of the list followed by the + // concatenation of the RLP encodings of the items. The range of the + // first byte is thus [0xC0, 0xF7]. return List, uint64(b - 0xC0), nil default: - // If the total payload of a list is more than 55 bytes long, - // the RLP encoding consists of a single byte with value 0xF7 - // plus the length of the length of the payload in binary - // form, followed by the length of the payload, followed by - // the concatenation of the RLP encodings of the items. The - // range of the first byte is thus [0xF8, 0xFF]. + // If the total payload of a list is more than 55 bytes long, the RLP + // encoding consists of a single byte with value 0xF7 plus the length of + // the length of the payload in binary form, followed by the length of + // the payload, followed by the concatenation of the RLP encodings of + // the items. The range of the first byte is thus [0xF8, 0xFF]. size, err = s.readUint(b - 0xF7) if err == nil && size < 56 { err = ErrCanonSize @@ -985,23 +958,24 @@ func (s *Stream) readUint(size byte) (uint64, error) { b, err := s.readByte() return uint64(b), err default: - start := int(8 - size) - for i := 0; i < start; i++ { - s.uintbuf[i] = 0 + buffer := s.uintbuf[:8] + for i := range buffer { + buffer[i] = 0 } - if err := s.readFull(s.uintbuf[start:]); err != nil { + start := int(8 - size) + if err := s.readFull(buffer[start:]); err != nil { return 0, err } - if s.uintbuf[start] == 0 { - // Note: readUint is also used to decode integer - // values. The error needs to be adjusted to become - // ErrCanonInt in this case. + if buffer[start] == 0 { + // Note: readUint is also used to decode integer values. + // The error needs to be adjusted to become ErrCanonInt in this case. return 0, ErrCanonSize } - return binary.BigEndian.Uint64(s.uintbuf), nil + return binary.BigEndian.Uint64(buffer[:]), nil } } +// readFull reads into buf from the underlying stream. func (s *Stream) readFull(buf []byte) (err error) { if err := s.willRead(uint64(len(buf))); err != nil { return err @@ -1012,11 +986,18 @@ func (s *Stream) readFull(buf []byte) (err error) { n += nn } if err == io.EOF { - err = io.ErrUnexpectedEOF + if n < len(buf) { + err = io.ErrUnexpectedEOF + } else { + // Readers are allowed to give EOF even though the read succeeded. + // In such cases, we discard the EOF, like io.ReadFull() does. + err = nil + } } return err } +// readByte reads a single byte from the underlying stream. func (s *Stream) readByte() (byte, error) { if err := s.willRead(1); err != nil { return 0, err @@ -1028,16 +1009,16 @@ func (s *Stream) readByte() (byte, error) { return b, err } +// willRead is called before any read from the underlying stream. It checks +// n against size limits, and updates the limits if n doesn't overflow them. func (s *Stream) willRead(n uint64) error { s.kind = -1 // rearm Kind - if len(s.stack) > 0 { - // check list overflow - tos := s.stack[len(s.stack)-1] - if n > tos.size-tos.pos { + if inList, limit := s.listLimit(); inList { + if n > limit { return ErrElemTooLarge } - s.stack[len(s.stack)-1].pos += n + s.stack[len(s.stack)-1] = limit - n } if s.limited { if n > s.remaining { @@ -1047,3 +1028,11 @@ func (s *Stream) willRead(n uint64) error { } return nil } + +// listLimit returns the amount of data remaining in the innermost list. +func (s *Stream) listLimit() (inList bool, limit uint64) { + if len(s.stack) == 0 { + return false, 0 + } + return true, s.stack[len(s.stack)-1] +} diff --git a/rlp/decode_test.go b/rlp/decode_test.go index 17c38e838d..fa97d5d95f 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -26,6 +26,8 @@ import ( "reflect" "strings" "testing" + + "github.com/AlayaNetwork/Alaya-Go/common/math" ) func TestStreamKind(t *testing.T) { @@ -357,6 +359,15 @@ type recstruct struct { Child *recstruct `rlp:"nil"` } +type bigIntStruct struct { + I *big.Int + B string +} + +type invalidNilTag struct { + X []byte `rlp:"nil"` +} + type invalidTail1 struct { A uint `rlp:"tail"` B string @@ -380,22 +391,62 @@ type tailUint struct { type tailPrivateFields struct { A uint Tail []uint `rlp:"tail"` - x, y bool + x, y bool //lint:ignore U1000 unused fields required for testing purposes. } -var ( - veryBigInt = big.NewInt(0).Add( - big.NewInt(0).Lsh(big.NewInt(0xFFFFFFFFFFFFFF), 16), - big.NewInt(0xFFFF), - ) -) +type nilListUint struct { + X *uint `rlp:"nilList"` +} + +type nilStringSlice struct { + X *[]uint `rlp:"nilString"` +} + +type intField struct { + X int +} + +type optionalFields struct { + A uint + B uint `rlp:"optional"` + C uint `rlp:"optional"` +} + +type optionalAndTailField struct { + A uint + B uint `rlp:"optional"` + Tail []uint `rlp:"tail"` +} -type hasIgnoredField struct { +type optionalBigIntField struct { + A uint + B *big.Int `rlp:"optional"` +} + +type optionalPtrField struct { + A uint + B *[3]byte `rlp:"optional"` +} + +type optionalPtrFieldNil struct { + A uint + B *[3]byte `rlp:"optional,nil"` +} + +type ignoredField struct { A uint B uint `rlp:"-"` C uint } +var ( + veryBigInt = new(big.Int).Add( + big.NewInt(0).Lsh(big.NewInt(0xFFFFFFFFFFFFFF), 16), + big.NewInt(0xFFFF), + ) + veryVeryBigInt = new(big.Int).Exp(veryBigInt, big.NewInt(8), nil) +) + var decodeTests = []decodeTest{ // booleans {input: "01", ptr: new(bool), value: true}, @@ -464,12 +515,15 @@ var decodeTests = []decodeTest{ {input: "C0", ptr: new(string), error: "rlp: expected input string or byte for string"}, // big ints + {input: "80", ptr: new(*big.Int), value: big.NewInt(0)}, {input: "01", ptr: new(*big.Int), value: big.NewInt(1)}, {input: "89FFFFFFFFFFFFFFFFFF", ptr: new(*big.Int), value: veryBigInt}, + {input: "B848FFFFFFFFFFFFFFFFF800000000000000001BFFFFFFFFFFFFFFFFC8000000000000000045FFFFFFFFFFFFFFFFC800000000000000001BFFFFFFFFFFFFFFFFF8000000000000000001", ptr: new(*big.Int), value: veryVeryBigInt}, {input: "10", ptr: new(big.Int), value: *big.NewInt(16)}, // non-pointer also works {input: "C0", ptr: new(*big.Int), error: "rlp: expected input string or byte for *big.Int"}, - {input: "820001", ptr: new(big.Int), error: "rlp: non-canonical integer (leading zero bytes) for *big.Int"}, - {input: "8105", ptr: new(big.Int), error: "rlp: non-canonical size information for *big.Int"}, + {input: "00", ptr: new(*big.Int), error: "rlp: non-canonical integer (leading zero bytes) for *big.Int"}, + {input: "820001", ptr: new(*big.Int), error: "rlp: non-canonical integer (leading zero bytes) for *big.Int"}, + {input: "8105", ptr: new(*big.Int), error: "rlp: non-canonical size information for *big.Int"}, // structs { @@ -482,6 +536,13 @@ var decodeTests = []decodeTest{ ptr: new(recstruct), value: recstruct{1, &recstruct{2, &recstruct{3, nil}}}, }, + { + // This checks that empty big.Int works correctly in struct context. It's easy to + // miss the update of s.kind for this case, so it needs its own test. + input: "C58083343434", + ptr: new(bigIntStruct), + value: bigIntStruct{new(big.Int), "444"}, + }, // struct errors { @@ -515,20 +576,20 @@ var decodeTests = []decodeTest{ error: "rlp: expected input string or byte for uint, decoding into (rlp.recstruct).Child.I", }, { - input: "C0", - ptr: new(invalidTail1), - error: "rlp: invalid struct tag \"tail\" for rlp.invalidTail1.A (must be on last field)", - }, - { - input: "C0", - ptr: new(invalidTail2), - error: "rlp: invalid struct tag \"tail\" for rlp.invalidTail2.B (field type is not slice)", + input: "C103", + ptr: new(intField), + error: "rlp: type int is not RLP-serializable (struct field rlp.intField.X)", }, { input: "C50102C20102", ptr: new(tailUint), error: "rlp: expected input string or byte for uint, decoding into (rlp.tailUint).Tail[1]", }, + { + input: "C0", + ptr: new(invalidNilTag), + error: `rlp: invalid struct tag "nil" for rlp.invalidNilTag.X (field is not a pointer)`, + }, // struct tag "tail" { @@ -551,12 +612,163 @@ var decodeTests = []decodeTest{ ptr: new(tailPrivateFields), value: tailPrivateFields{A: 1, Tail: []uint{2, 3}}, }, + { + input: "C0", + ptr: new(invalidTail1), + error: `rlp: invalid struct tag "tail" for rlp.invalidTail1.A (must be on last field)`, + }, + { + input: "C0", + ptr: new(invalidTail2), + error: `rlp: invalid struct tag "tail" for rlp.invalidTail2.B (field type is not slice)`, + }, // struct tag "-" { input: "C20102", - ptr: new(hasIgnoredField), - value: hasIgnoredField{A: 1, C: 2}, + ptr: new(ignoredField), + value: ignoredField{A: 1, C: 2}, + }, + + // struct tag "nilList" + { + input: "C180", + ptr: new(nilListUint), + error: "rlp: wrong kind of empty value (got String, want List) for *uint, decoding into (rlp.nilListUint).X", + }, + { + input: "C1C0", + ptr: new(nilListUint), + value: nilListUint{}, + }, + { + input: "C103", + ptr: new(nilListUint), + value: func() interface{} { + v := uint(3) + return nilListUint{X: &v} + }(), + }, + + // struct tag "nilString" + { + input: "C1C0", + ptr: new(nilStringSlice), + error: "rlp: wrong kind of empty value (got List, want String) for *[]uint, decoding into (rlp.nilStringSlice).X", + }, + { + input: "C180", + ptr: new(nilStringSlice), + value: nilStringSlice{}, + }, + { + input: "C2C103", + ptr: new(nilStringSlice), + value: nilStringSlice{X: &[]uint{3}}, + }, + + // struct tag "optional" + { + input: "C101", + ptr: new(optionalFields), + value: optionalFields{1, 0, 0}, + }, + { + input: "C20102", + ptr: new(optionalFields), + value: optionalFields{1, 2, 0}, + }, + { + input: "C3010203", + ptr: new(optionalFields), + value: optionalFields{1, 2, 3}, + }, + { + input: "C401020304", + ptr: new(optionalFields), + error: "rlp: input list has too many elements for rlp.optionalFields", + }, + { + input: "C101", + ptr: new(optionalAndTailField), + value: optionalAndTailField{A: 1}, + }, + { + input: "C20102", + ptr: new(optionalAndTailField), + value: optionalAndTailField{A: 1, B: 2, Tail: []uint{}}, + }, + { + input: "C401020304", + ptr: new(optionalAndTailField), + value: optionalAndTailField{A: 1, B: 2, Tail: []uint{3, 4}}, + }, + { + input: "C101", + ptr: new(optionalBigIntField), + value: optionalBigIntField{A: 1, B: nil}, + }, + { + input: "C20102", + ptr: new(optionalBigIntField), + value: optionalBigIntField{A: 1, B: big.NewInt(2)}, + }, + { + input: "C101", + ptr: new(optionalPtrField), + value: optionalPtrField{A: 1}, + }, + { + input: "C20180", // not accepted because "optional" doesn't enable "nil" + ptr: new(optionalPtrField), + error: "rlp: input string too short for [3]uint8, decoding into (rlp.optionalPtrField).B", + }, + { + input: "C20102", + ptr: new(optionalPtrField), + error: "rlp: input string too short for [3]uint8, decoding into (rlp.optionalPtrField).B", + }, + { + input: "C50183010203", + ptr: new(optionalPtrField), + value: optionalPtrField{A: 1, B: &[3]byte{1, 2, 3}}, + }, + { + input: "C101", + ptr: new(optionalPtrFieldNil), + value: optionalPtrFieldNil{A: 1}, + }, + { + input: "C20180", // accepted because "nil" tag allows empty input + ptr: new(optionalPtrFieldNil), + value: optionalPtrFieldNil{A: 1}, + }, + { + input: "C20102", + ptr: new(optionalPtrFieldNil), + error: "rlp: input string too short for [3]uint8, decoding into (rlp.optionalPtrFieldNil).B", + }, + + // struct tag "optional" field clearing + { + input: "C101", + ptr: &optionalFields{A: 9, B: 8, C: 7}, + value: optionalFields{A: 1, B: 0, C: 0}, + }, + { + input: "C20102", + ptr: &optionalFields{A: 9, B: 8, C: 7}, + value: optionalFields{A: 1, B: 2, C: 0}, + }, + { + input: "C20102", + ptr: &optionalAndTailField{A: 9, B: 8, Tail: []uint{7, 6, 5}}, + value: optionalAndTailField{A: 1, B: 2, Tail: []uint{}}, + }, + { + input: "C101", + ptr: &optionalPtrField{A: 9, B: &[3]byte{8, 7, 6}}, + value: optionalPtrField{A: 1}, }, // RawValue @@ -632,6 +844,26 @@ func TestDecodeWithByteReader(t *testing.T) { }) } +func testDecodeWithEncReader(t *testing.T, n int) { + s := strings.Repeat("0", n) + _, r, _ := EncodeToReader(s) + var decoded string + err := Decode(r, &decoded) + if err != nil { + t.Errorf("Unexpected decode error with n=%v: %v", n, err) + } + if decoded != s { + t.Errorf("Decode mismatch with n=%v", n) + } +} + +// This is a regression test checking that decoding from encReader +// works for RLP values of size 8192 bytes or more. +func TestDecodeWithEncReader(t *testing.T) { + testDecodeWithEncReader(t, 8188) // length with header is 8191 + testDecodeWithEncReader(t, 8189) // length with header is 8192 +} + // plainReader reads from a byte slice but does not // implement ReadByte. It is also not recognized by the // size validation. This is useful to test how the decoder @@ -702,6 +934,22 @@ func TestDecodeDecoder(t *testing.T) { } } +func TestDecodeDecoderNilPointer(t *testing.T) { + var s struct { + T1 *testDecoder `rlp:"nil"` + T2 *testDecoder + } + if err := Decode(bytes.NewReader(unhex("C2C002")), &s); err != nil { + t.Fatalf("Decode error: %v", err) + } + if s.T1 != nil { + t.Errorf("decoder T1 allocated for empty input (called: %v)", s.T1.called) + } + if s.T2 == nil || !s.T2.called { + t.Errorf("decoder T2 not allocated/called") + } +} + type byteDecoder byte func (bd *byteDecoder) DecodeRLP(s *Stream) error { @@ -753,13 +1001,46 @@ func TestDecoderFunc(t *testing.T) { x() } +// This tests the validity checks for fields with struct tag "optional". +func TestInvalidOptionalField(t *testing.T) { + type ( + invalid1 struct { + A uint `rlp:"optional"` + B uint + } + invalid2 struct { + T []uint `rlp:"tail,optional"` + } + invalid3 struct { + T []uint `rlp:"optional,tail"` + } + ) + + tests := []struct { + v interface{} + err string + }{ + {v: new(invalid1), err: `rlp: struct field rlp.invalid1.B needs "optional" tag`}, + {v: new(invalid2), err: `rlp: invalid struct tag "optional" for rlp.invalid2.T (also has "tail" tag)`}, + {v: new(invalid3), err: `rlp: invalid struct tag "tail" for rlp.invalid3.T (also has "optional" tag)`}, + } + for _, test := range tests { + err := DecodeBytes(unhex("C20102"), test.v) + if err == nil { + t.Errorf("no error for %T", test.v) + } else if err.Error() != test.err { + t.Errorf("wrong error for %T: %v", test.v, err.Error()) + } + } + +} + func ExampleDecode() { input, _ := hex.DecodeString("C90A1486666F6F626172") type example struct { - A, B uint - private uint // private fields are ignored - String string + A, B uint + String string } var s example @@ -770,7 +1051,7 @@ func ExampleDecode() { fmt.Printf("Decoded value: %#v\n", s) } // Output: - // Decoded value: rlp.example{A:0xa, B:0x14, private:0x0, String:"foobar"} + // Decoded value: rlp.example{A:0xa, B:0x14, String:"foobar"} } func ExampleDecode_structTagNil() { @@ -806,7 +1087,6 @@ func ExampleStream() { // Check what kind of value lies ahead kind, size, _ := s.Kind() - //fmt.Println(kind) fmt.Printf("Kind: %v size:%d\n", kind, size) // Enter the list @@ -831,7 +1111,7 @@ func ExampleStream() { // [102 111 111 98 97 114] } -func BenchmarkDecode(b *testing.B) { +func BenchmarkDecodeUints(b *testing.B) { enc := encodeTestSlice(90000) b.SetBytes(int64(len(enc))) b.ReportAllocs() @@ -846,7 +1126,7 @@ func BenchmarkDecode(b *testing.B) { } } -func BenchmarkDecodeIntSliceReuse(b *testing.B) { +func BenchmarkDecodeUintsReused(b *testing.B) { enc := encodeTestSlice(100000) b.SetBytes(int64(len(enc))) b.ReportAllocs() @@ -861,6 +1141,44 @@ func BenchmarkDecodeIntSliceReuse(b *testing.B) { } } +func BenchmarkDecodeByteArrayStruct(b *testing.B) { + enc, err := EncodeToBytes(&byteArrayStruct{}) + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(len(enc))) + b.ReportAllocs() + b.ResetTimer() + + var out byteArrayStruct + for i := 0; i < b.N; i++ { + if err := DecodeBytes(enc, &out); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDecodeBigInts(b *testing.B) { + ints := make([]*big.Int, 200) + for i := range ints { + ints[i] = math.BigPow(2, int64(i)) + } + enc, err := EncodeToBytes(ints) + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(len(enc))) + b.ReportAllocs() + b.ResetTimer() + + var out []*big.Int + for i := 0; i < b.N; i++ { + if err := DecodeBytes(enc, &out); err != nil { + b.Fatal(err) + } + } +} + func encodeTestSlice(n uint) []byte { s := make([]uint, n) for i := uint(0); i < n; i++ { diff --git a/rlp/doc.go b/rlp/doc.go index b3a81fe232..113828e39b 100644 --- a/rlp/doc.go +++ b/rlp/doc.go @@ -17,17 +17,145 @@ /* Package rlp implements the RLP serialization format. -The purpose of RLP (Recursive Linear Prefix) is to encode arbitrarily -nested arrays of binary data, and RLP is the main encoding method used -to serialize objects in Ethereum. The only purpose of RLP is to encode -structure; encoding specific atomic data types (eg. strings, ints, -floats) is left up to higher-order protocols; in Ethereum integers -must be represented in big endian binary form with no leading zeroes -(thus making the integer value zero equivalent to the empty byte -array). - -RLP values are distinguished by a type tag. The type tag precedes the -value in the input stream and defines the size and kind of the bytes -that follow. +The purpose of RLP (Recursive Linear Prefix) is to encode arbitrarily nested arrays of +binary data, and RLP is the main encoding method used to serialize objects in Ethereum. +The only purpose of RLP is to encode structure; encoding specific atomic data types (eg. +strings, ints, floats) is left up to higher-order protocols. In Ethereum integers must be +represented in big endian binary form with no leading zeroes (thus making the integer +value zero equivalent to the empty string). + +RLP values are distinguished by a type tag. The type tag precedes the value in the input +stream and defines the size and kind of the bytes that follow. + + +Encoding Rules + +Package rlp uses reflection and encodes RLP based on the Go type of the value. + +If the type implements the Encoder interface, Encode calls EncodeRLP. It does not +call EncodeRLP on nil pointer values. + +To encode a pointer, the value being pointed to is encoded. A nil pointer to a struct +type, slice or array always encodes as an empty RLP list unless the slice or array has +elememt type byte. A nil pointer to any other value encodes as the empty string. + +Struct values are encoded as an RLP list of all their encoded public fields. Recursive +struct types are supported. + +To encode slices and arrays, the elements are encoded as an RLP list of the value's +elements. Note that arrays and slices with element type uint8 or byte are always encoded +as an RLP string. + +A Go string is encoded as an RLP string. + +An unsigned integer value is encoded as an RLP string. Zero always encodes as an empty RLP +string. big.Int values are treated as integers. Signed integers (int, int8, int16, ...) +are not supported and will return an error when encoding. + +Boolean values are encoded as the unsigned integers zero (false) and one (true). + +An interface value encodes as the value contained in the interface. + +Floating point numbers, maps, channels and functions are not supported. + + +Decoding Rules + +Decoding uses the following type-dependent rules: + +If the type implements the Decoder interface, DecodeRLP is called. + +To decode into a pointer, the value will be decoded as the element type of the pointer. If +the pointer is nil, a new value of the pointer's element type is allocated. If the pointer +is non-nil, the existing value will be reused. Note that package rlp never leaves a +pointer-type struct field as nil unless one of the "nil" struct tags is present. + +To decode into a struct, decoding expects the input to be an RLP list. The decoded +elements of the list are assigned to each public field in the order given by the struct's +definition. The input list must contain an element for each decoded field. Decoding +returns an error if there are too few or too many elements for the struct. + +To decode into a slice, the input must be a list and the resulting slice will contain the +input elements in order. For byte slices, the input must be an RLP string. Array types +decode similarly, with the additional restriction that the number of input elements (or +bytes) must match the array's defined length. + +To decode into a Go string, the input must be an RLP string. The input bytes are taken +as-is and will not necessarily be valid UTF-8. + +To decode into an unsigned integer type, the input must also be an RLP string. The bytes +are interpreted as a big endian representation of the integer. If the RLP string is larger +than the bit size of the type, decoding will return an error. Decode also supports +*big.Int. There is no size limit for big integers. + +To decode into a boolean, the input must contain an unsigned integer of value zero (false) +or one (true). + +To decode into an interface value, one of these types is stored in the value: + + []interface{}, for RLP lists + []byte, for RLP strings + +Non-empty interface types are not supported when decoding. +Signed integers, floating point numbers, maps, channels and functions cannot be decoded into. + + +Struct Tags + +As with other encoding packages, the "-" tag ignores fields. + + type StructWithIgnoredField struct{ + Ignored uint `rlp:"-"` + Field uint + } + +Go struct values encode/decode as RLP lists. There are two ways of influencing the mapping +of fields to list elements. The "tail" tag, which may only be used on the last exported +struct field, allows slurping up any excess list elements into a slice. + + type StructWithTail struct{ + Field uint + Tail []string `rlp:"tail"` + } + +The "optional" tag says that the field may be omitted if it is zero-valued. If this tag is +used on a struct field, all subsequent public fields must also be declared optional. + +When encoding a struct with optional fields, the output RLP list contains all values up to +the last non-zero optional field. + +When decoding into a struct, optional fields may be omitted from the end of the input +list. For the example below, this means input lists of one, two, or three elements are +accepted. + + type StructWithOptionalFields struct{ + Required uint + Optional1 uint `rlp:"optional"` + Optional2 uint `rlp:"optional"` + } + +The "nil", "nilList" and "nilString" tags apply to pointer-typed fields only, and change +the decoding rules for the field type. For regular pointer fields without the "nil" tag, +input values must always match the required input length exactly and the decoder does not +produce nil values. When the "nil" tag is set, input values of size zero decode as a nil +pointer. This is especially useful for recursive types. + + type StructWithNilField struct { + Field *[3]byte `rlp:"nil"` + } + +In the example above, Field allows two possible input sizes. For input 0xC180 (a list +containing an empty string) Field is set to nil after decoding. For input 0xC483000000 (a +list containing a 3-byte string), Field is set to a non-nil array pointer. + +RLP supports two kinds of empty values: empty lists and empty strings. When using the +"nil" tag, the kind of empty value allowed for a type is chosen automatically. A field +whose Go type is a pointer to an unsigned integer, string, boolean or byte array/slice +expects an empty RLP string. Any other pointer field type encodes/decodes as an empty RLP +list. + +The choice of null value can be made explicit with the "nilList" and "nilString" struct +tags. Using these tags encodes/decodes a Go nil pointer value as the empty RLP value kind +defined by the tag. */ package rlp diff --git a/rlp/encode.go b/rlp/encode.go index 9c8dc818ff..1623e97a3e 100644 --- a/rlp/encode.go +++ b/rlp/encode.go @@ -49,34 +49,7 @@ type Encoder interface { // perform many small writes in some cases. Consider making w // buffered. // -// Encode uses the following type-dependent encoding rules: -// -// If the type implements the Encoder interface, Encode calls -// EncodeRLP. This is true even for nil pointers, please see the -// documentation for Encoder. -// -// To encode a pointer, the value being pointed to is encoded. For nil -// pointers, Encode will encode the zero value of the type. A nil -// pointer to a struct type always encodes as an empty RLP list. -// A nil pointer to an array encodes as an empty list (or empty string -// if the array has element type byte). -// -// Struct values are encoded as an RLP list of all their encoded -// public fields. Recursive struct types are supported. -// -// To encode slices and arrays, the elements are encoded as an RLP -// list of the value's elements. Note that arrays and slices with -// element type uint8 or byte are always encoded as an RLP string. -// -// A Go string is encoded as an RLP string. -// -// An unsigned integer value is encoded as an RLP string. Zero always -// encodes as an empty RLP string. Encode also supports *big.Int. -// -// An interface value encodes as the value contained in the interface. -// -// Signed integers are not supported, nor are floating point numbers, maps, -// channels and functions. +// Please see package-level documentation of encoding rules. func Encode(w io.Writer, val interface{}) error { if outer, ok := w.(*encbuf); ok { // Encode was called by some type's EncodeRLP. @@ -93,7 +66,7 @@ func Encode(w io.Writer, val interface{}) error { } // EncodeToBytes returns the RLP encoding of val. -// Please see the documentation of Encode for the encoding rules. +// Please see package-level documentation for the encoding rules. func EncodeToBytes(val interface{}) ([]byte, error) { eb := encbufPool.Get().(*encbuf) defer encbufPool.Put(eb) @@ -104,8 +77,6 @@ func EncodeToBytes(val interface{}) ([]byte, error) { return eb.toBytes(), nil } - - // EncodeToReader returns a reader from which the RLP encoding of val // can be read. The returned size is the total size of the encoded // data. @@ -120,13 +91,6 @@ func EncodeToReader(val interface{}) (size int, r io.Reader, err error) { return eb.size(), &encReader{buf: eb}, nil } -type encbuf struct { - str []byte // string data, contains everything except list headers - lheads []*listhead // all list headers - lhsize int // sum of sizes of all encoded list headers - sizebuf []byte // 9-byte auxiliary buffer for uint encoding -} - type listhead struct { offset int // index of this header in string data size int // total size of encoded data (including list headers) @@ -159,19 +123,22 @@ func puthead(buf []byte, smalltag, largetag byte, size uint64) int { return sizesize + 1 } +type encbuf struct { + str []byte // string data, contains everything except list headers + lheads []listhead // all list headers + lhsize int // sum of sizes of all encoded list headers + sizebuf [9]byte // auxiliary buffer for uint encoding +} + // encbufs are pooled. var encbufPool = sync.Pool{ - New: func() interface{} { return &encbuf{sizebuf: make([]byte, 9)} }, + New: func() interface{} { return new(encbuf) }, } func (w *encbuf) reset() { w.lhsize = 0 - if w.str != nil { - w.str = w.str[:0] - } - if w.lheads != nil { - w.lheads = w.lheads[:0] - } + w.str = w.str[:0] + w.lheads = w.lheads[:0] } // encbuf implements io.Writer so it can be passed it into EncodeRLP. @@ -193,7 +160,6 @@ func (w *encbuf) encodeStringHeader(size int) { if size < 56 { w.str = append(w.str, 0x80+byte(size)) } else { - // TODO: encode to w.str directly sizesize := putint(w.sizebuf[1:], uint64(size)) w.sizebuf[0] = 0xB7 + byte(sizesize) w.str = append(w.str, w.sizebuf[:sizesize+1]...) @@ -210,13 +176,29 @@ func (w *encbuf) encodeString(b []byte) { } } -func (w *encbuf) list() *listhead { - lh := &listhead{offset: len(w.str), size: w.lhsize} - w.lheads = append(w.lheads, lh) - return lh +func (w *encbuf) encodeUint(i uint64) { + if i == 0 { + w.str = append(w.str, 0x80) + } else if i < 128 { + // fits single byte + w.str = append(w.str, byte(i)) + } else { + s := putint(w.sizebuf[1:], i) + w.sizebuf[0] = 0x80 + byte(s) + w.str = append(w.str, w.sizebuf[:s+1]...) + } } -func (w *encbuf) listEnd(lh *listhead) { +// list adds a new list header to the header stack. It returns the index +// of the header. The caller must call listEnd with this index after encoding +// the content of the list. +func (w *encbuf) list() int { + w.lheads = append(w.lheads, listhead{offset: len(w.str), size: w.lhsize}) + return len(w.lheads) - 1 +} + +func (w *encbuf) listEnd(index int) { + lh := &w.lheads[index] lh.size = w.size() - lh.offset - lh.size if lh.size < 56 { w.lhsize++ // length encoded into kind tag @@ -259,7 +241,7 @@ func (w *encbuf) toWriter(out io.Writer) (err error) { } } // write the header - enc := head.encode(w.sizebuf) + enc := head.encode(w.sizebuf[:]) if _, err = out.Write(enc); err != nil { return err } @@ -325,7 +307,7 @@ func (r *encReader) next() []byte { return p } r.lhpos++ - return head.encode(r.buf.sizebuf) + return head.encode(r.buf.sizebuf[:]) case r.strpos < len(r.buf.str): // String data at the end, after all list headers. @@ -338,10 +320,7 @@ func (r *encReader) next() []byte { } } -var ( - encoderInterface = reflect.TypeOf(new(Encoder)).Elem() - big0 = big.NewInt(0) -) +var encoderInterface = reflect.TypeOf(new(Encoder)).Elem() // makeWriter creates a writer function for the given type. func makeWriter(typ reflect.Type, ts tags) (writer, error) { @@ -349,16 +328,14 @@ func makeWriter(typ reflect.Type, ts tags) (writer, error) { switch { case typ == rawValueType: return writeRawValue, nil - case typ.Implements(encoderInterface): - return writeEncoder, nil - case kind != reflect.Ptr && reflect.PtrTo(typ).Implements(encoderInterface): - return writeEncoderNoPtr, nil - case kind == reflect.Interface: - return writeInterface, nil case typ.AssignableTo(reflect.PtrTo(bigInt)): return writeBigIntPtr, nil case typ.AssignableTo(bigInt): return writeBigIntNoPtr, nil + case kind == reflect.Ptr: + return makePtrWriter(typ, ts) + case reflect.PtrTo(typ).Implements(encoderInterface): + return makeEncoderWriter(typ), nil case isUint(kind): return writeUint, nil case kind == reflect.Bool: @@ -368,40 +345,25 @@ func makeWriter(typ reflect.Type, ts tags) (writer, error) { case kind == reflect.Slice && isByte(typ.Elem()): return writeBytes, nil case kind == reflect.Array && isByte(typ.Elem()): - return writeByteArray, nil + return makeByteArrayWriter(typ), nil case kind == reflect.Slice || kind == reflect.Array: return makeSliceWriter(typ, ts) case kind == reflect.Struct: return makeStructWriter(typ) - case kind == reflect.Ptr: - return makePtrWriter(typ) + case kind == reflect.Interface: + return writeInterface, nil default: return nil, fmt.Errorf("rlp: type %v is not RLP-serializable", typ) } } -func isByte(typ reflect.Type) bool { - return typ.Kind() == reflect.Uint8 && !typ.Implements(encoderInterface) -} - func writeRawValue(val reflect.Value, w *encbuf) error { w.str = append(w.str, val.Bytes()...) return nil } func writeUint(val reflect.Value, w *encbuf) error { - i := val.Uint() - if i == 0 { - w.str = append(w.str, 0x80) - } else if i < 128 { - // fits single byte - w.str = append(w.str, byte(i)) - } else { - // TODO: encode int to w.str directly - s := putint(w.sizebuf[1:], i) - w.sizebuf[0] = 0x80 + byte(s) - w.str = append(w.str, w.sizebuf[:s+1]...) - } + w.encodeUint(val.Uint()) return nil } @@ -428,13 +390,32 @@ func writeBigIntNoPtr(val reflect.Value, w *encbuf) error { return writeBigInt(&i, w) } +// wordBytes is the number of bytes in a big.Word +const wordBytes = (32 << (uint64(^big.Word(0)) >> 63)) / 8 + func writeBigInt(i *big.Int, w *encbuf) error { - if cmp := i.Cmp(big0); cmp == -1 { + if i.Sign() == -1 { return fmt.Errorf("rlp: cannot encode negative *big.Int") - } else if cmp == 0 { - w.str = append(w.str, 0x80) - } else { - w.encodeString(i.Bytes()) + } + bitlen := i.BitLen() + if bitlen <= 64 { + w.encodeUint(i.Uint64()) + return nil + } + // Integer is larger than 64 bits, encode from i.Bits(). + // The minimal byte length is bitlen rounded up to the next + // multiple of 8, divided by 8. + length := ((bitlen + 7) & -8) >> 3 + w.encodeStringHeader(length) + w.str = append(w.str, make([]byte, length)...) + index := length + buf := w.str[len(w.str)-length:] + for _, d := range i.Bits() { + for j := 0; j < wordBytes && index > 0; j++ { + index-- + buf[index] = byte(d) + d >>= 8 + } } return nil } @@ -444,17 +425,42 @@ func writeBytes(val reflect.Value, w *encbuf) error { return nil } -func writeByteArray(val reflect.Value, w *encbuf) error { - if !val.CanAddr() { - // Slice requires the value to be addressable. - // Make it addressable by copying. - copy := reflect.New(val.Type()).Elem() - copy.Set(val) - val = copy +func makeByteArrayWriter(typ reflect.Type) writer { + switch typ.Len() { + case 0: + return writeLengthZeroByteArray + case 1: + return writeLengthOneByteArray + default: + length := typ.Len() + return func(val reflect.Value, w *encbuf) error { + if !val.CanAddr() { + // Getting the byte slice of val requires it to be addressable. Make it + // addressable by copying. + copy := reflect.New(val.Type()).Elem() + copy.Set(val) + val = copy + } + slice := byteArrayBytes(val, length) + w.encodeStringHeader(len(slice)) + w.str = append(w.str, slice...) + return nil + } + } +} + +func writeLengthZeroByteArray(val reflect.Value, w *encbuf) error { + w.str = append(w.str, 0x80) + return nil +} + +func writeLengthOneByteArray(val reflect.Value, w *encbuf) error { + b := byte(val.Index(0).Uint()) + if b <= 0x7f { + w.str = append(w.str, b) + } else { + w.str = append(w.str, 0x81, b) } - size := val.Len() - slice := val.Slice(0, size).Bytes() - w.encodeString(slice) return nil } @@ -470,26 +476,6 @@ func writeString(val reflect.Value, w *encbuf) error { return nil } -func writeEncoder(val reflect.Value, w *encbuf) error { - return val.Interface().(Encoder).EncodeRLP(w) -} - -// writeEncoderNoPtr handles non-pointer values that implement Encoder -// with a pointer receiver. -func writeEncoderNoPtr(val reflect.Value, w *encbuf) error { - if !val.CanAddr() { - // We can't get the address. It would be possible to make the - // value addressable by creating a shallow copy, but this - // creates other problems so we're not doing it (yet). - // - // package json simply doesn't call MarshalJSON for cases like - // this, but encodes the value as if it didn't implement the - // interface. We don't want to handle it that way. - return fmt.Errorf("rlp: game over: unadressable value of type %v, EncodeRLP is pointer method", val.Type()) - } - return val.Addr().Interface().(Encoder).EncodeRLP(w) -} - func writeInterface(val reflect.Value, w *encbuf) error { if val.IsNil() { // Write empty list. This is consistent with the previous RLP @@ -507,23 +493,43 @@ func writeInterface(val reflect.Value, w *encbuf) error { } func makeSliceWriter(typ reflect.Type, ts tags) (writer, error) { - etypeinfo := cachedTypeInfo1(typ.Elem(), tags{}) + etypeinfo := theTC.infoWhileGenerating(typ.Elem(), tags{}) if etypeinfo.writerErr != nil { return nil, etypeinfo.writerErr } - writer := func(val reflect.Value, w *encbuf) error { - if !ts.tail { - defer w.listEnd(w.list()) + + var wfn writer + if ts.tail { + // This is for struct tail slices. + // w.list is not called for them. + wfn = func(val reflect.Value, w *encbuf) error { + vlen := val.Len() + for i := 0; i < vlen; i++ { + if err := etypeinfo.writer(val.Index(i), w); err != nil { + return err + } + } + return nil } - vlen := val.Len() - for i := 0; i < vlen; i++ { - if err := etypeinfo.writer(val.Index(i), w); err != nil { - return err + } else { + // This is for regular slices and arrays. + wfn = func(val reflect.Value, w *encbuf) error { + vlen := val.Len() + if vlen == 0 { + w.str = append(w.str, 0xC0) + return nil + } + listOffset := w.list() + for i := 0; i < vlen; i++ { + if err := etypeinfo.writer(val.Index(i), w); err != nil { + return err + } } + w.listEnd(listOffset) + return nil } - return nil } - return writer, nil + return wfn, nil } func makeStructWriter(typ reflect.Type) (writer, error) { @@ -531,57 +537,103 @@ func makeStructWriter(typ reflect.Type) (writer, error) { if err != nil { return nil, err } - writer := func(val reflect.Value, w *encbuf) error { - lh := w.list() - for _, f := range fields { - if err := f.info.writer(val.Field(f.index), w); err != nil { - return err + for _, f := range fields { + if f.info.writerErr != nil { + return nil, structFieldError{typ, f.index, f.info.writerErr} + } + } + + var writer writer + firstOptionalField := firstOptionalField(fields) + if firstOptionalField == len(fields) { + // This is the writer function for structs without any optional fields. + writer = func(val reflect.Value, w *encbuf) error { + lh := w.list() + for _, f := range fields { + if err := f.info.writer(val.Field(f.index), w); err != nil { + return err + } } + w.listEnd(lh) + return nil + } + } else { + // If there are any "optional" fields, the writer needs to perform additional + // checks to determine the output list length. + writer = func(val reflect.Value, w *encbuf) error { + lastField := len(fields) - 1 + for ; lastField >= firstOptionalField; lastField-- { + if !val.Field(fields[lastField].index).IsZero() { + break + } + } + lh := w.list() + for i := 0; i <= lastField; i++ { + if err := fields[i].info.writer(val.Field(fields[i].index), w); err != nil { + return err + } + } + w.listEnd(lh) + return nil } - w.listEnd(lh) - return nil } return writer, nil } -func makePtrWriter(typ reflect.Type) (writer, error) { - etypeinfo := cachedTypeInfo1(typ.Elem(), tags{}) - if etypeinfo.writerErr != nil { - return nil, etypeinfo.writerErr +// nilEncoding returns the encoded value of a nil pointer. +func nilEncoding(typ reflect.Type, ts tags) uint8 { + var nilKind Kind + if ts.nilOK { + nilKind = ts.nilKind // use struct tag if provided + } else { + nilKind = defaultNilKind(typ.Elem()) } - // determine nil pointer handler - var nilfunc func(*encbuf) error - kind := typ.Elem().Kind() - switch { - case kind == reflect.Array && isByte(typ.Elem().Elem()): - nilfunc = func(w *encbuf) error { - w.str = append(w.str, 0x80) - return nil - } - case kind == reflect.Struct || kind == reflect.Array: - nilfunc = func(w *encbuf) error { - // encoding the zero value of a struct/array could trigger - // infinite recursion, avoid that. - w.listEnd(w.list()) - return nil - } + switch nilKind { + case String: + return 0x80 + case List: + return 0xC0 default: - zero := reflect.Zero(typ.Elem()) - nilfunc = func(w *encbuf) error { - return etypeinfo.writer(zero, w) - } + panic(fmt.Errorf("rlp: invalid nil kind %d", nilKind)) + } +} + +func makePtrWriter(typ reflect.Type, ts tags) (writer, error) { + etypeinfo := theTC.infoWhileGenerating(typ.Elem(), tags{}) + if etypeinfo.writerErr != nil { + return nil, etypeinfo.writerErr } + nilEncoding := nilEncoding(typ, ts) writer := func(val reflect.Value, w *encbuf) error { - if val.IsNil() { - return nilfunc(w) + if ev := val.Elem(); ev.IsValid() { + return etypeinfo.writer(ev, w) } - return etypeinfo.writer(val.Elem(), w) + w.str = append(w.str, nilEncoding) + return nil } return writer, nil } +func makeEncoderWriter(typ reflect.Type) writer { + if typ.Implements(encoderInterface) { + return func(val reflect.Value, w *encbuf) error { + return val.Interface().(Encoder).EncodeRLP(w) + } + } + w := func(val reflect.Value, w *encbuf) error { + if !val.CanAddr() { + // package json simply doesn't call MarshalJSON for this case, but encodes the + // value as if it didn't implement the interface. We don't want to handle it that + // way. + return fmt.Errorf("rlp: unadressable value of type %v, EncodeRLP is pointer method", val.Type()) + } + return val.Addr().Interface().(Encoder).EncodeRLP(w) + } + return w +} + // putint writes i to the beginning of b in big endian byte // order, using the least number of bytes needed to represent i. func putint(b []byte, i uint64) (size int) { diff --git a/rlp/encode_test.go b/rlp/encode_test.go index 425bc0facc..cfd806d235 100644 --- a/rlp/encode_test.go +++ b/rlp/encode_test.go @@ -26,9 +26,12 @@ import ( "io/ioutil" "math/big" "reflect" + "runtime" "sync" "testing" + "github.com/AlayaNetwork/Alaya-Go/common/math" + "github.com/AlayaNetwork/Alaya-Go/common/hexutil" ) @@ -38,12 +41,19 @@ type testEncoder struct { func (e *testEncoder) EncodeRLP(w io.Writer) error { if e == nil { - w.Write([]byte{0, 0, 0, 0}) - } else if e.err != nil { + panic("EncodeRLP called on nil value") + } + if e.err != nil { return e.err - } else { - w.Write([]byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 1}) } + w.Write([]byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 1}) + return nil +} + +type testEncoderValueMethod struct{} + +func (e testEncoderValueMethod) EncodeRLP(w io.Writer) error { + w.Write([]byte{0xFA, 0xFE, 0xF0}) return nil } @@ -57,8 +67,8 @@ func (e byteEncoder) EncodeRLP(w io.Writer) error { type undecodableEncoder func() func (f undecodableEncoder) EncodeRLP(w io.Writer) error { - _, err := w.Write(EmptyList) - return err + w.Write([]byte{0xF5, 0xF5, 0xF5}) + return nil } type encodableReader struct { @@ -126,6 +136,14 @@ var encTests = []encTest{ val: big.NewInt(0).SetBytes(unhex("010000000000000000000000000000000000000000000000000000000000000000")), output: "A1010000000000000000000000000000000000000000000000000000000000000000", }, + { + val: veryBigInt, + output: "89FFFFFFFFFFFFFFFFFF", + }, + { + val: veryVeryBigInt, + output: "B848FFFFFFFFFFFFFFFFF800000000000000001BFFFFFFFFFFFFFFFFC8000000000000000045FFFFFFFFFFFFFFFFC800000000000000001BFFFFFFFFFFFFFFFFF8000000000000000001", + }, // non-pointer big.Int {val: *big.NewInt(0), output: "80"}, @@ -226,11 +244,30 @@ var encTests = []encTest{ {val: simplestruct{A: 3, B: "foo"}, output: "C50383666F6F"}, {val: &recstruct{5, nil}, output: "C205C0"}, {val: &recstruct{5, &recstruct{4, &recstruct{3, nil}}}, output: "C605C404C203C0"}, + {val: &intField{X: 3}, error: "rlp: type int is not RLP-serializable (struct field rlp.intField.X)"}, + + // struct tag "-" + {val: &ignoredField{A: 1, B: 2, C: 3}, output: "C20103"}, + + // struct tag "tail" {val: &tailRaw{A: 1, Tail: []RawValue{unhex("02"), unhex("03")}}, output: "C3010203"}, {val: &tailRaw{A: 1, Tail: []RawValue{unhex("02")}}, output: "C20102"}, {val: &tailRaw{A: 1, Tail: []RawValue{}}, output: "C101"}, {val: &tailRaw{A: 1, Tail: nil}, output: "C101"}, - {val: &hasIgnoredField{A: 1, B: 2, C: 3}, output: "C20103"}, + + // struct tag "optional" + {val: &optionalFields{}, output: "C180"}, + {val: &optionalFields{A: 1}, output: "C101"}, + {val: &optionalFields{A: 1, B: 2}, output: "C20102"}, + {val: &optionalFields{A: 1, B: 2, C: 3}, output: "C3010203"}, + {val: &optionalFields{A: 1, B: 0, C: 3}, output: "C3018003"}, + {val: &optionalAndTailField{A: 1}, output: "C101"}, + {val: &optionalAndTailField{A: 1, B: 2}, output: "C20102"}, + {val: &optionalAndTailField{A: 1, Tail: []uint{5, 6}}, output: "C401800506"}, + {val: &optionalAndTailField{A: 1, Tail: []uint{5, 6}}, output: "C401800506"}, + {val: &optionalBigIntField{A: 1}, output: "C101"}, + {val: &optionalPtrField{A: 1}, output: "C101"}, + {val: &optionalPtrFieldNil{A: 1}, output: "C101"}, // nil {val: (*uint)(nil), output: "80"}, @@ -244,22 +281,66 @@ var encTests = []encTest{ {val: (*[]struct{ uint })(nil), output: "C0"}, {val: (*interface{})(nil), output: "C0"}, + // nil struct fields + { + val: struct { + X *[]byte + }{}, + output: "C180", + }, + { + val: struct { + X *[2]byte + }{}, + output: "C180", + }, + { + val: struct { + X *uint64 + }{}, + output: "C180", + }, + { + val: struct { + X *uint64 `rlp:"nilList"` + }{}, + output: "C1C0", + }, + { + val: struct { + X *[]uint64 + }{}, + output: "C1C0", + }, + { + val: struct { + X *[]uint64 `rlp:"nilString"` + }{}, + output: "C180", + }, + // interfaces {val: []io.Reader{reader}, output: "C3C20102"}, // the contained value is a struct // Encoder - {val: (*testEncoder)(nil), output: "00000000"}, + {val: (*testEncoder)(nil), output: "C0"}, {val: &testEncoder{}, output: "00010001000100010001"}, {val: &testEncoder{errors.New("test error")}, error: "test error"}, - // verify that the Encoder interface works for unsupported types like func(). - {val: undecodableEncoder(func() {}), output: "C0"}, - // verify that pointer method testEncoder.EncodeRLP is called for + {val: struct{ E testEncoderValueMethod }{}, output: "C3FAFEF0"}, + {val: struct{ E *testEncoderValueMethod }{}, output: "C1C0"}, + + // Verify that the Encoder interface works for unsupported types like func(). + {val: undecodableEncoder(func() {}), output: "F5F5F5"}, + + // Verify that pointer method testEncoder.EncodeRLP is called for // addressable non-pointer values. {val: &struct{ TE testEncoder }{testEncoder{}}, output: "CA00010001000100010001"}, {val: &struct{ TE testEncoder }{testEncoder{errors.New("test error")}}, error: "test error"}, - // verify the error for non-addressable non-pointer Encoder - {val: testEncoder{}, error: "rlp: game over: unadressable value of type rlp.testEncoder, EncodeRLP is pointer method"}, - // verify the special case for []byte + + // Verify the error for non-addressable non-pointer Encoder. + {val: testEncoder{}, error: "rlp: unadressable value of type rlp.testEncoder, EncodeRLP is pointer method"}, + + // Verify Encoder takes precedence over []byte. {val: []byteEncoder{0, 1, 2, 3, 4}, output: "C5C0C0C0C0C0"}, } @@ -463,3 +544,115 @@ func TestEncodeToReaderReturnToPool(t *testing.T) { } wg.Wait() } + +var sink interface{} + +func BenchmarkIntsize(b *testing.B) { + for i := 0; i < b.N; i++ { + sink = intsize(0x12345678) + } +} + +func BenchmarkPutint(b *testing.B) { + buf := make([]byte, 8) + for i := 0; i < b.N; i++ { + putint(buf, 0x12345678) + sink = buf + } +} + +func BenchmarkEncodeBigInts(b *testing.B) { + ints := make([]*big.Int, 200) + for i := range ints { + ints[i] = math.BigPow(2, int64(i)) + } + out := bytes.NewBuffer(make([]byte, 0, 4096)) + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + out.Reset() + if err := Encode(out, ints); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkEncodeConcurrentInterface(b *testing.B) { + type struct1 struct { + A string + B *big.Int + C [20]byte + } + value := []interface{}{ + uint(999), + &struct1{A: "hello", B: big.NewInt(0xFFFFFFFF)}, + [10]byte{1, 2, 3, 4, 5, 6}, + []string{"yeah", "yeah", "yeah"}, + } + + var wg sync.WaitGroup + for cpu := 0; cpu < runtime.NumCPU(); cpu++ { + wg.Add(1) + go func() { + defer wg.Done() + + var buffer bytes.Buffer + for i := 0; i < b.N; i++ { + buffer.Reset() + err := Encode(&buffer, value) + if err != nil { + panic(err) + } + } + }() + } + wg.Wait() +} + +type byteArrayStruct struct { + A [20]byte + B [32]byte + C [32]byte +} + +func BenchmarkEncodeByteArrayStruct(b *testing.B) { + var out bytes.Buffer + var value byteArrayStruct + + b.ReportAllocs() + for i := 0; i < b.N; i++ { + out.Reset() + if err := Encode(&out, &value); err != nil { + b.Fatal(err) + } + } +} + +type structSliceElem struct { + X uint64 + Y uint64 + Z uint64 +} + +type structPtrSlice []*structSliceElem + +func BenchmarkEncodeStructPtrSlice(b *testing.B) { + var out bytes.Buffer + var value = structPtrSlice{ + &structSliceElem{1, 1, 1}, + &structSliceElem{2, 2, 2}, + &structSliceElem{3, 3, 3}, + &structSliceElem{5, 5, 5}, + &structSliceElem{6, 6, 6}, + &structSliceElem{7, 7, 7}, + } + + b.ReportAllocs() + for i := 0; i < b.N; i++ { + out.Reset() + if err := Encode(&out, &value); err != nil { + b.Fatal(err) + } + } +} diff --git a/rlp/encoder_example_test.go b/rlp/encoder_example_test.go index 1cffa241c2..42c1c5c890 100644 --- a/rlp/encoder_example_test.go +++ b/rlp/encoder_example_test.go @@ -28,15 +28,7 @@ type MyCoolType struct { // EncodeRLP writes x as RLP list [a, b] that omits the Name field. func (x *MyCoolType) EncodeRLP(w io.Writer) (err error) { - // Note: the receiver can be a nil pointer. This allows you to - // control the encoding of nil, but it also means that you have to - // check for a nil receiver. - if x == nil { - err = Encode(w, []uint{0, 0}) - } else { - err = Encode(w, []uint{x.a, x.b}) - } - return err + return Encode(w, []uint{x.a, x.b}) } func ExampleEncoder() { @@ -49,6 +41,6 @@ func ExampleEncoder() { fmt.Printf("%v → %X\n", t, bytes) // Output: - // → C28080 + // → C0 // &{foobar 5 6} → C20506 } diff --git a/rlp/iterator.go b/rlp/iterator.go new file mode 100644 index 0000000000..c28866dbc1 --- /dev/null +++ b/rlp/iterator.go @@ -0,0 +1,60 @@ +// 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 . + +package rlp + +type listIterator struct { + data []byte + next []byte + err error +} + +// NewListIterator creates an iterator for the (list) represented by data +func NewListIterator(data RawValue) (*listIterator, error) { + k, t, c, err := readKind(data) + if err != nil { + return nil, err + } + if k != List { + return nil, ErrExpectedList + } + it := &listIterator{ + data: data[t : t+c], + } + return it, nil + +} + +// Next forwards the iterator one step, returns true if it was not at end yet +func (it *listIterator) Next() bool { + if len(it.data) == 0 { + return false + } + _, t, c, err := readKind(it.data) + it.next = it.data[:t+c] + it.data = it.data[t+c:] + it.err = err + return true +} + +// Value returns the current value +func (it *listIterator) Value() []byte { + return it.next +} + +func (it *listIterator) Err() error { + return it.err +} diff --git a/rlp/iterator_test.go b/rlp/iterator_test.go new file mode 100644 index 0000000000..9e57ee6761 --- /dev/null +++ b/rlp/iterator_test.go @@ -0,0 +1,59 @@ +// 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 . + +package rlp + +import ( + "testing" + + "github.com/AlayaNetwork/Alaya-Go/common/hexutil" +) + +// TestIterator tests some basic things about the ListIterator. A more +// comprehensive test can be found in core/rlp_test.go, where we can +// use both types and rlp without dependency cycles +func TestIterator(t *testing.T) { + bodyRlpHex := "0xf902cbf8d6f869800182c35094000000000000000000000000000000000000aaaa808a000000000000000000001ba01025c66fad28b4ce3370222624d952c35529e602af7cbe04f667371f61b0e3b3a00ab8813514d1217059748fd903288ace1b4001a4bc5fbde2790debdc8167de2ff869010182c35094000000000000000000000000000000000000aaaa808a000000000000000000001ca05ac4cf1d19be06f3742c21df6c49a7e929ceb3dbaf6a09f3cfb56ff6828bd9a7a06875970133a35e63ac06d360aa166d228cc013e9b96e0a2cae7f55b22e1ee2e8f901f0f901eda0c75448377c0e426b8017b23c5f77379ecf69abc1d5c224284ad3ba1c46c59adaa00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808080808080a00000000000000000000000000000000000000000000000000000000000000000880000000000000000" + bodyRlp := hexutil.MustDecode(bodyRlpHex) + + it, err := NewListIterator(bodyRlp) + if err != nil { + t.Fatal(err) + } + // Check that txs exist + if !it.Next() { + t.Fatal("expected two elems, got zero") + } + txs := it.Value() + // Check that uncles exist + if !it.Next() { + t.Fatal("expected two elems, got one") + } + txit, err := NewListIterator(txs) + if err != nil { + t.Fatal(err) + } + var i = 0 + for txit.Next() { + if txit.err != nil { + t.Fatal(txit.err) + } + i++ + } + if exp := 2; i != exp { + t.Errorf("count wrong, expected %d got %d", i, exp) + } +} diff --git a/rlp/raw.go b/rlp/raw.go index 2b3f328f66..f355efc144 100644 --- a/rlp/raw.go +++ b/rlp/raw.go @@ -34,6 +34,14 @@ func ListSize(contentSize uint64) uint64 { return uint64(headsize(contentSize)) + contentSize } +// IntSize returns the encoded size of the integer x. +func IntSize(x uint64) int { + if x < 0x80 { + return 1 + } + return 1 + intsize(x) +} + // Split returns the content of first RLP value and any // bytes after the value as subslices of b. func Split(b []byte) (k Kind, content, rest []byte, err error) { @@ -57,6 +65,32 @@ func SplitString(b []byte) (content, rest []byte, err error) { return content, rest, nil } +// SplitUint64 decodes an integer at the beginning of b. +// It also returns the remaining data after the integer in 'rest'. +func SplitUint64(b []byte) (x uint64, rest []byte, err error) { + content, rest, err := SplitString(b) + if err != nil { + return 0, b, err + } + switch { + case len(content) == 0: + return 0, rest, nil + case len(content) == 1: + if content[0] == 0 { + return 0, b, ErrCanonInt + } + return uint64(content[0]), rest, nil + case len(content) > 8: + return 0, b, errUintOverflow + default: + x, err = readSize(content, byte(len(content))) + if err != nil { + return 0, b, ErrCanonInt + } + return x, rest, nil + } +} + // SplitList splits b into the content of a list and any remaining // bytes after the list. func SplitList(b []byte) (content, rest []byte, err error) { @@ -154,3 +188,74 @@ func readSize(b []byte, slen byte) (uint64, error) { } return s, nil } + +// AppendUint64 appends the RLP encoding of i to b, and returns the resulting slice. +func AppendUint64(b []byte, i uint64) []byte { + if i == 0 { + return append(b, 0x80) + } else if i < 128 { + return append(b, byte(i)) + } + switch { + case i < (1 << 8): + return append(b, 0x81, byte(i)) + case i < (1 << 16): + return append(b, 0x82, + byte(i>>8), + byte(i), + ) + case i < (1 << 24): + return append(b, 0x83, + byte(i>>16), + byte(i>>8), + byte(i), + ) + case i < (1 << 32): + return append(b, 0x84, + byte(i>>24), + byte(i>>16), + byte(i>>8), + byte(i), + ) + case i < (1 << 40): + return append(b, 0x85, + byte(i>>32), + byte(i>>24), + byte(i>>16), + byte(i>>8), + byte(i), + ) + + case i < (1 << 48): + return append(b, 0x86, + byte(i>>40), + byte(i>>32), + byte(i>>24), + byte(i>>16), + byte(i>>8), + byte(i), + ) + case i < (1 << 56): + return append(b, 0x87, + byte(i>>48), + byte(i>>40), + byte(i>>32), + byte(i>>24), + byte(i>>16), + byte(i>>8), + byte(i), + ) + + default: + return append(b, 0x88, + byte(i>>56), + byte(i>>48), + byte(i>>40), + byte(i>>32), + byte(i>>24), + byte(i>>16), + byte(i>>8), + byte(i), + ) + } +} diff --git a/rlp/raw_test.go b/rlp/raw_test.go index 2aad042100..46adff22c5 100644 --- a/rlp/raw_test.go +++ b/rlp/raw_test.go @@ -18,9 +18,10 @@ package rlp import ( "bytes" + "errors" "io" - "reflect" "testing" + "testing/quick" ) func TestCountValues(t *testing.T) { @@ -53,7 +54,7 @@ func TestCountValues(t *testing.T) { if count != test.count { t.Errorf("test %d: count mismatch, got %d want %d\ninput: %s", i, count, test.count, test.input) } - if !reflect.DeepEqual(err, test.err) { + if !errors.Is(err, test.err) { t.Errorf("test %d: err mismatch, got %q want %q\ninput: %s", i, err, test.err, test.input) } } @@ -71,6 +72,49 @@ func TestSplitTypes(t *testing.T) { } } +func TestSplitUint64(t *testing.T) { + tests := []struct { + input string + val uint64 + rest string + err error + }{ + {"01", 1, "", nil}, + {"7FFF", 0x7F, "FF", nil}, + {"80FF", 0, "FF", nil}, + {"81FAFF", 0xFA, "FF", nil}, + {"82FAFAFF", 0xFAFA, "FF", nil}, + {"83FAFAFAFF", 0xFAFAFA, "FF", nil}, + {"84FAFAFAFAFF", 0xFAFAFAFA, "FF", nil}, + {"85FAFAFAFAFAFF", 0xFAFAFAFAFA, "FF", nil}, + {"86FAFAFAFAFAFAFF", 0xFAFAFAFAFAFA, "FF", nil}, + {"87FAFAFAFAFAFAFAFF", 0xFAFAFAFAFAFAFA, "FF", nil}, + {"88FAFAFAFAFAFAFAFAFF", 0xFAFAFAFAFAFAFAFA, "FF", nil}, + + // errors + {"", 0, "", io.ErrUnexpectedEOF}, + {"00", 0, "00", ErrCanonInt}, + {"81", 0, "81", ErrValueTooLarge}, + {"8100", 0, "8100", ErrCanonSize}, + {"8200FF", 0, "8200FF", ErrCanonInt}, + {"8103FF", 0, "8103FF", ErrCanonSize}, + {"89FAFAFAFAFAFAFAFAFAFF", 0, "89FAFAFAFAFAFAFAFAFAFF", errUintOverflow}, + } + + for i, test := range tests { + val, rest, err := SplitUint64(unhex(test.input)) + if val != test.val { + t.Errorf("test %d: val mismatch: got %x, want %x (input %q)", i, val, test.val, test.input) + } + if !bytes.Equal(rest, unhex(test.rest)) { + t.Errorf("test %d: rest mismatch: got %x, want %s (input %q)", i, rest, test.rest, test.input) + } + if err != test.err { + t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err) + } + } +} + func TestSplit(t *testing.T) { tests := []struct { input string @@ -78,7 +122,9 @@ func TestSplit(t *testing.T) { val, rest string err error }{ + {input: "00FFFF", kind: Byte, val: "00", rest: "FFFF"}, {input: "01FFFF", kind: Byte, val: "01", rest: "FFFF"}, + {input: "7FFFFF", kind: Byte, val: "7F", rest: "FFFF"}, {input: "80FFFF", kind: String, val: "", rest: "FFFF"}, {input: "C3010203", kind: List, val: "010203"}, @@ -194,3 +240,46 @@ func TestReadSize(t *testing.T) { } } } + +func TestAppendUint64(t *testing.T) { + tests := []struct { + input uint64 + slice []byte + output string + }{ + {0, nil, "80"}, + {1, nil, "01"}, + {2, nil, "02"}, + {127, nil, "7F"}, + {128, nil, "8180"}, + {129, nil, "8181"}, + {0xFFFFFF, nil, "83FFFFFF"}, + {127, []byte{1, 2, 3}, "0102037F"}, + {0xFFFFFF, []byte{1, 2, 3}, "01020383FFFFFF"}, + } + + for _, test := range tests { + x := AppendUint64(test.slice, test.input) + if !bytes.Equal(x, unhex(test.output)) { + t.Errorf("AppendUint64(%v, %d): got %x, want %s", test.slice, test.input, x, test.output) + } + + // Check that IntSize returns the appended size. + length := len(x) - len(test.slice) + if s := IntSize(test.input); s != length { + t.Errorf("IntSize(%d): got %d, want %d", test.input, s, length) + } + } +} + +func TestAppendUint64Random(t *testing.T) { + fn := func(i uint64) bool { + enc, _ := EncodeToBytes(i) + encAppend := AppendUint64(nil, i) + return bytes.Equal(enc, encAppend) + } + config := quick.Config{MaxCountScale: 50} + if err := quick.Check(fn, &config); err != nil { + t.Fatal(err) + } +} diff --git a/rlp/safe.go b/rlp/safe.go new file mode 100644 index 0000000000..3c910337b6 --- /dev/null +++ b/rlp/safe.go @@ -0,0 +1,27 @@ +// Copyright 2021 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 . + +//go:build nacl || js || !cgo +// +build nacl js !cgo + +package rlp + +import "reflect" + +// byteArrayBytes returns a slice of the byte array v. +func byteArrayBytes(v reflect.Value, length int) []byte { + return v.Slice(0, length).Bytes() +} diff --git a/rlp/typecache.go b/rlp/typecache.go index e22212b56f..62553d3b55 100644 --- a/rlp/typecache.go +++ b/rlp/typecache.go @@ -21,36 +21,40 @@ import ( "reflect" "strings" "sync" + "sync/atomic" ) -var ( - typeCacheMutex sync.RWMutex - typeCache = make(map[typekey]*typeinfo) -) - +// typeinfo is an entry in the type cache. type typeinfo struct { - decoder + decoder decoder decoderErr error // error from makeDecoder - writer + writer writer writerErr error // error from makeWriter } -// represents struct tags +// tags represents struct tags. type tags struct { // rlp:"nil" controls whether empty input results in a nil pointer. - nilOK bool - // rlp:"tail" controls whether this field swallows additional list - // elements. It can only be set for the last field, which must be - // of slice type. + // nilKind is the kind of empty value allowed for the field. + nilKind Kind + nilOK bool + + // rlp:"optional" allows for a field to be missing in the input list. + // If this is set, all subsequent fields must also be optional. + optional bool + + // rlp:"tail" controls whether this field swallows additional list elements. It can + // only be set for the last field, which must be of slice type. tail bool + // rlp:"-" ignores fields. ignored bool } +// typekey is the key of a type in typeCache. It includes the struct tags because +// they might generate a different decoder. type typekey struct { reflect.Type - // the key must include the struct tags because they - // might generate a different decoder. tags } @@ -58,68 +62,145 @@ type decoder func(*Stream, reflect.Value) error type writer func(reflect.Value, *encbuf) error +var theTC = newTypeCache() + +type typeCache struct { + cur atomic.Value + + // This lock synchronizes writers. + mu sync.Mutex + next map[typekey]*typeinfo +} + +func newTypeCache() *typeCache { + c := new(typeCache) + c.cur.Store(make(map[typekey]*typeinfo)) + return c +} + func cachedDecoder(typ reflect.Type) (decoder, error) { - info := cachedTypeInfo(typ, tags{}) + info := theTC.info(typ) return info.decoder, info.decoderErr } func cachedWriter(typ reflect.Type) (writer, error) { - info := cachedTypeInfo(typ, tags{}) + info := theTC.info(typ) return info.writer, info.writerErr } -func cachedTypeInfo(typ reflect.Type, tags tags) *typeinfo { - typeCacheMutex.RLock() - info := typeCache[typekey{typ, tags}] - typeCacheMutex.RUnlock() - if info != nil { +func (c *typeCache) info(typ reflect.Type) *typeinfo { + key := typekey{Type: typ} + if info := c.cur.Load().(map[typekey]*typeinfo)[key]; info != nil { + return info + } + + // Not in the cache, need to generate info for this type. + return c.generate(typ, tags{}) +} + +func (c *typeCache) generate(typ reflect.Type, tags tags) *typeinfo { + c.mu.Lock() + defer c.mu.Unlock() + + cur := c.cur.Load().(map[typekey]*typeinfo) + if info := cur[typekey{typ, tags}]; info != nil { return info } - // not in the cache, need to generate info for this type. - typeCacheMutex.Lock() - defer typeCacheMutex.Unlock() - return cachedTypeInfo1(typ, tags) + + // Copy cur to next. + c.next = make(map[typekey]*typeinfo, len(cur)+1) + for k, v := range cur { + c.next[k] = v + } + + // Generate. + info := c.infoWhileGenerating(typ, tags) + + // next -> cur + c.cur.Store(c.next) + c.next = nil + return info } -func cachedTypeInfo1(typ reflect.Type, tags tags) *typeinfo { +func (c *typeCache) infoWhileGenerating(typ reflect.Type, tags tags) *typeinfo { key := typekey{typ, tags} - info := typeCache[key] - if info != nil { - // another goroutine got the write lock first + if info := c.next[key]; info != nil { return info } - // put a dummy value into the cache before generating. - // if the generator tries to lookup itself, it will get + // Put a dummy value into the cache before generating. + // If the generator tries to lookup itself, it will get // the dummy value and won't call itself recursively. - info = new(typeinfo) - typeCache[key] = info + info := new(typeinfo) + c.next[key] = info info.generate(typ, tags) return info } type field struct { - index int - info *typeinfo + index int + info *typeinfo + optional bool } +// structFields resolves the typeinfo of all public fields in a struct type. func structFields(typ reflect.Type) (fields []field, err error) { - lastPublic := lastPublicField(typ) + var ( + lastPublic = lastPublicField(typ) + anyOptional = false + ) for i := 0; i < typ.NumField(); i++ { if f := typ.Field(i); f.PkgPath == "" { // exported tags, err := parseStructTag(typ, i, lastPublic) if err != nil { return nil, err } + + // Skip rlp:"-" fields. if tags.ignored { continue } - info := cachedTypeInfo1(f.Type, tags) - fields = append(fields, field{i, info}) + // If any field has the "optional" tag, subsequent fields must also have it. + if tags.optional || tags.tail { + anyOptional = true + } else if anyOptional { + return nil, fmt.Errorf(`rlp: struct field %v.%s needs "optional" tag`, typ, f.Name) + } + info := theTC.infoWhileGenerating(f.Type, tags) + fields = append(fields, field{i, info, tags.optional}) } } return fields, nil } +// anyOptionalFields returns the index of the first field with "optional" tag. +func firstOptionalField(fields []field) int { + for i, f := range fields { + if f.optional { + return i + } + } + return len(fields) +} + +type structFieldError struct { + typ reflect.Type + field int + err error +} + +func (e structFieldError) Error() string { + return fmt.Sprintf("%v (struct field %v.%s)", e.err, e.typ, e.typ.Field(e.field).Name) +} + +type structTagError struct { + typ reflect.Type + field, tag, err string +} + +func (e structTagError) Error() string { + return fmt.Sprintf("rlp: invalid struct tag %q for %v.%s (%s)", e.tag, e.typ, e.field, e.err) +} + func parseStructTag(typ reflect.Type, fi, lastPublic int) (tags, error) { f := typ.Field(fi) var ts tags @@ -128,15 +209,34 @@ func parseStructTag(typ reflect.Type, fi, lastPublic int) (tags, error) { case "": case "-": ts.ignored = true - case "nil": + case "nil", "nilString", "nilList": ts.nilOK = true + if f.Type.Kind() != reflect.Ptr { + return ts, structTagError{typ, f.Name, t, "field is not a pointer"} + } + switch t { + case "nil": + ts.nilKind = defaultNilKind(f.Type.Elem()) + case "nilString": + ts.nilKind = String + case "nilList": + ts.nilKind = List + } + case "optional": + ts.optional = true + if ts.tail { + return ts, structTagError{typ, f.Name, t, `also has "tail" tag`} + } case "tail": ts.tail = true if fi != lastPublic { - return ts, fmt.Errorf(`rlp: invalid struct tag "tail" for %v.%s (must be on last field)`, typ, f.Name) + return ts, structTagError{typ, f.Name, t, "must be on last field"} + } + if ts.optional { + return ts, structTagError{typ, f.Name, t, `also has "optional" tag`} } if f.Type.Kind() != reflect.Slice { - return ts, fmt.Errorf(`rlp: invalid struct tag "tail" for %v.%s (field type is not slice)`, typ, f.Name) + return ts, structTagError{typ, f.Name, t, "field type is not slice"} } default: return ts, fmt.Errorf("rlp: unknown struct tag %q on %v.%s", t, typ, f.Name) @@ -160,6 +260,24 @@ func (i *typeinfo) generate(typ reflect.Type, tags tags) { i.writer, i.writerErr = makeWriter(typ, tags) } +// defaultNilKind determines whether a nil pointer to typ encodes/decodes +// as an empty string or empty list. +func defaultNilKind(typ reflect.Type) Kind { + k := typ.Kind() + if isUint(k) || k == reflect.String || k == reflect.Bool || isByteArray(typ) { + return String + } + return List +} + func isUint(k reflect.Kind) bool { return k >= reflect.Uint && k <= reflect.Uintptr } + +func isByte(typ reflect.Type) bool { + return typ.Kind() == reflect.Uint8 && !typ.Implements(encoderInterface) +} + +func isByteArray(typ reflect.Type) bool { + return (typ.Kind() == reflect.Slice || typ.Kind() == reflect.Array) && isByte(typ.Elem()) +} diff --git a/rlp/unsafe.go b/rlp/unsafe.go new file mode 100644 index 0000000000..2152ba35fc --- /dev/null +++ b/rlp/unsafe.go @@ -0,0 +1,35 @@ +// Copyright 2021 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 . + +//go:build !nacl && !js && cgo +// +build !nacl,!js,cgo + +package rlp + +import ( + "reflect" + "unsafe" +) + +// byteArrayBytes returns a slice of the byte array v. +func byteArrayBytes(v reflect.Value, length int) []byte { + var s []byte + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s)) + hdr.Data = v.UnsafeAddr() + hdr.Cap = length + hdr.Len = length + return s +} From 05e276be6be0f1aef7098184304095bb777a9d7a Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 26 Oct 2021 09:55:31 +0800 Subject: [PATCH 009/299] remove no use --- p2p/simulations/base/000544.ldb | Bin 252 -> 0 bytes p2p/simulations/base/000545.ldb | Bin 252 -> 0 bytes p2p/simulations/base/000548.ldb | Bin 252 -> 0 bytes p2p/simulations/base/000551.ldb | Bin 252 -> 0 bytes p2p/simulations/base/000554.ldb | Bin 252 -> 0 bytes p2p/simulations/base/000557.ldb | Bin 252 -> 0 bytes p2p/simulations/base/000560.ldb | Bin 252 -> 0 bytes p2p/simulations/base/000563.ldb | Bin 252 -> 0 bytes p2p/simulations/base/000566.ldb | Bin 252 -> 0 bytes p2p/simulations/base/000569.ldb | Bin 252 -> 0 bytes p2p/simulations/base/000572.ldb | Bin 252 -> 0 bytes p2p/simulations/base/000575.ldb | Bin 252 -> 0 bytes p2p/simulations/base/000576.log | Bin 158 -> 0 bytes p2p/simulations/base/000578.ldb | Bin 252 -> 0 bytes p2p/simulations/base/CURRENT | 1 - p2p/simulations/base/CURRENT.bak | 1 - p2p/simulations/base/LOCK | 0 p2p/simulations/base/LOG | 3039 -------------------------- p2p/simulations/base/MANIFEST-000577 | Bin 1116 -> 0 bytes 19 files changed, 3041 deletions(-) delete mode 100644 p2p/simulations/base/000544.ldb delete mode 100644 p2p/simulations/base/000545.ldb delete mode 100644 p2p/simulations/base/000548.ldb delete mode 100644 p2p/simulations/base/000551.ldb delete mode 100644 p2p/simulations/base/000554.ldb delete mode 100644 p2p/simulations/base/000557.ldb delete mode 100644 p2p/simulations/base/000560.ldb delete mode 100644 p2p/simulations/base/000563.ldb delete mode 100644 p2p/simulations/base/000566.ldb delete mode 100644 p2p/simulations/base/000569.ldb delete mode 100644 p2p/simulations/base/000572.ldb delete mode 100644 p2p/simulations/base/000575.ldb delete mode 100644 p2p/simulations/base/000576.log delete mode 100644 p2p/simulations/base/000578.ldb delete mode 100644 p2p/simulations/base/CURRENT delete mode 100644 p2p/simulations/base/CURRENT.bak delete mode 100644 p2p/simulations/base/LOCK delete mode 100644 p2p/simulations/base/LOG delete mode 100644 p2p/simulations/base/MANIFEST-000577 diff --git a/p2p/simulations/base/000544.ldb b/p2p/simulations/base/000544.ldb deleted file mode 100644 index 371d1a66f95b324ab797f0a9b972613401da0589..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 252 zcmeBVtYA=JD$YwRD9*?)Nl9`pEhDoqHa v3v3qCG<`!c5e81C62||Wj4BL?a2eH@l-OE>O^l)-5dt9mcSE;IsrzjJp{zLY diff --git a/p2p/simulations/base/000545.ldb b/p2p/simulations/base/000545.ldb deleted file mode 100644 index 8e3a26d6b13e081a9467538a5a1ce460f3ee1f51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 252 zcmeBVtYA=JD$YwRD9*?)Nl9`pEh~Dgv+SLq{P-5Y+@7zi4XwczZ<$$O5JY*0M(Q_?f?J) diff --git a/p2p/simulations/base/000551.ldb b/p2p/simulations/base/000551.ldb deleted file mode 100644 index 0fdcc927140b0241554c9948d4cc250e3ef0a7fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 252 zcmeBVtYA=JD$YwRD9*?)Nl9`pEh~Dgv+SLq{P-5Y+@7zi4XwczZ<$$O5JY*0P|@(82|tP diff --git a/p2p/simulations/base/000557.ldb b/p2p/simulations/base/000557.ldb deleted file mode 100644 index 14276a0febfe5a6705d78607dcf4f79592c43786..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 252 zcmeBVtYA=JD$YwRD9*?)Nl9`pEhuoFr94j;$ zIJlZtuz^H5ftZ`&i~B!Dy)6uiOlg@pC8tk7x#aRdRrJ2nbIYpTa#G7ub5fG@oJxWG%sikq`MGXjsWc&w vF0ffl)AS9+L>M@kN*MohGO92n!evxrQetZjHZh8VLb?NitVx(G=LliAYcSx_m`UjI96yh zaBwxPU;~MA0x>tk7x#aRdRrJ2nbIYpTa#G7ub5fG@oJxWG%sikq`MGXjsWc&w vF0ffl)AS9+L>M@kN*MohGO92n!evxrQetZjHZh8VLtk7x#aRdRrJ2nbIYpTa#G7ub5fG@oJxWG%sikq`MGXjsWc&w vF0ffl)AS9+L>M@kN*MohGO92n!evxrQetZjHZh8VLtk7x#aRdRrJ2nbIYpTa#G7ub5fG@oJxWG%sikq`MGXjsWc&w vF0ffl)AS9+L>M@kN*MohGO92n!evxrQetZjHZh8VLi#jujdW z99&H+*g&G3K+MhX#r+?n-WCQ$rnJnQlGGx-oYb<^oRlOzr&1t4GY@D@ey$r>DoqHa v3v3qCG<`!c5e81C62||Wj4BL?a2eH@l-OE>O^l)-5dt9mcSE;IsrzjJ3==th diff --git a/p2p/simulations/base/000576.log b/p2p/simulations/base/000576.log deleted file mode 100644 index 63307febb0515e001b2aa410ce4399e29bd5d64c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 158 zcmew%Avt|I1LIpJ1~6a-l8my&d5HzZ8TlnCNzSE3MX7lu9+~MGsl_EuIr+)i%8wcr gKosL986_}`a7rvr^()O~I@rJ{f*~24T7p#&0DjsnF#rGn diff --git a/p2p/simulations/base/000578.ldb b/p2p/simulations/base/000578.ldb deleted file mode 100644 index 70dd4e1d5c35bb91523199433bae267bf0ed7712..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 252 zcmeBVtYA=JD$YwRD9*?)Nl9`pEhi#jujdW z99&H+*g&G3K+MhX#r+?n-WCQ$rnJnQlGGx-oYb<^oRlOzr&1t4GY@D@ey$r>DoqHa v3v3qCG<`!c5e81C62||Wj4BL?a2eH@l-OE>O^l)-5dt9mcSE;IsrzjJ3==th diff --git a/p2p/simulations/base/CURRENT b/p2p/simulations/base/CURRENT deleted file mode 100644 index d767468ff9..0000000000 --- a/p2p/simulations/base/CURRENT +++ /dev/null @@ -1 +0,0 @@ -MANIFEST-000577 diff --git a/p2p/simulations/base/CURRENT.bak b/p2p/simulations/base/CURRENT.bak deleted file mode 100644 index 992dea4df5..0000000000 --- a/p2p/simulations/base/CURRENT.bak +++ /dev/null @@ -1 +0,0 @@ -MANIFEST-000574 diff --git a/p2p/simulations/base/LOCK b/p2p/simulations/base/LOCK deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/p2p/simulations/base/LOG b/p2p/simulations/base/LOG deleted file mode 100644 index dfcac2edaf..0000000000 --- a/p2p/simulations/base/LOG +++ /dev/null @@ -1,3039 +0,0 @@ -=============== Oct 25, 2021 (CST) =============== -18:12:09.613431 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.158100 db@open opening -18:12:10.158294 version@stat F·[] S·0B[] Sc·[] -18:12:10.160133 db@janitor F·2 G·0 -18:12:10.160144 db@open done T·2.0299ms -18:12:10.160349 db@close closing -18:12:10.160419 db@close done T·69.5µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.160605 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.160704 version@stat F·[] S·0B[] Sc·[] -18:12:10.160731 db@open opening -18:12:10.160756 journal@recovery F·1 -18:12:10.160829 journal@recovery recovering @1 -18:12:10.163262 memdb@flush created L0@2 N·3 S·250B "sna..Num,v2":"sna..Set,v3" -18:12:10.163697 version@stat F·[1] S·250B[250B] Sc·[0.25] -18:12:10.171196 db@janitor F·3 G·0 -18:12:10.171212 db@open done T·10.4736ms -18:12:10.171473 db@close closing -18:12:10.171523 db@close done T·50.6µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.171718 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.171783 version@stat F·[1] S·250B[250B] Sc·[0.25] -18:12:10.171809 db@open opening -18:12:10.171837 journal@recovery F·1 -18:12:10.172751 journal@recovery recovering @3 -18:12:10.176081 memdb@flush created L0@5 N·3 S·250B "sna..Num,v6":"sna..Set,v7" -18:12:10.176464 version@stat F·[2] S·500B[500B] Sc·[0.50] -18:12:10.183964 db@janitor F·4 G·0 -18:12:10.183975 db@open done T·12.1596ms -18:12:10.184158 db@close closing -18:12:10.184248 db@close done T·88.4µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.184449 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.184512 version@stat F·[2] S·500B[500B] Sc·[0.50] -18:12:10.184538 db@open opening -18:12:10.184568 journal@recovery F·1 -18:12:10.184858 journal@recovery recovering @6 -18:12:10.187208 memdb@flush created L0@8 N·3 S·250B "sna..Num,v10":"sna..Set,v11" -18:12:10.187652 version@stat F·[3] S·750B[750B] Sc·[0.75] -18:12:10.194618 db@janitor F·5 G·0 -18:12:10.194630 db@open done T·10.0852ms -18:12:10.194806 db@close closing -18:12:10.194841 db@close done T·35.2µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.195039 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.195124 version@stat F·[3] S·750B[750B] Sc·[0.75] -18:12:10.195152 db@open opening -18:12:10.195183 journal@recovery F·1 -18:12:10.195259 journal@recovery recovering @9 -18:12:10.198045 memdb@flush created L0@11 N·3 S·250B "sna..Num,v14":"sna..Set,v15" -18:12:10.198183 version@stat F·[4] S·1000B[1000B] Sc·[1.00] -18:12:10.203391 db@janitor F·6 G·0 -18:12:10.203403 db@open done T·8.2439ms -18:12:10.203496 table@compaction L0·4 -> L1·0 S·1000B Q·16 -18:12:10.203551 db@close closing -18:12:10.205995 table@build created L1@14 N·3 S·250B "sna..Num,v14":"sna..Set,v15" -18:12:10.206010 table@build exiting -18:12:10.206015 table@build revert @14 -18:12:10.206101 db@close done T·2.5487ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.206347 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.206418 version@stat F·[4] S·1000B[1000B] Sc·[1.00] -18:12:10.206444 db@open opening -18:12:10.206476 journal@recovery F·1 -18:12:10.206852 journal@recovery recovering @12 -18:12:10.209514 memdb@flush created L0@14 N·3 S·250B "sna..Num,v18":"sna..Set,v19" -18:12:10.209638 version@stat F·[5] S·1KiB[1KiB] Sc·[1.25] -18:12:10.215054 db@janitor F·7 G·0 -18:12:10.215066 db@open done T·8.6156ms -18:12:10.215104 table@compaction L0·5 -> L1·0 S·1KiB Q·20 -18:12:10.215379 db@close closing -18:12:10.217081 table@build created L1@17 N·3 S·250B "sna..Num,v18":"sna..Set,v19" -18:12:10.217098 table@build exiting -18:12:10.217104 table@build revert @17 -18:12:10.217172 db@close done T·1.7927ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.217383 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.217450 version@stat F·[5] S·1KiB[1KiB] Sc·[1.25] -18:12:10.217479 db@open opening -18:12:10.217512 journal@recovery F·1 -18:12:10.218055 journal@recovery recovering @15 -18:12:10.221100 memdb@flush created L0@17 N·3 S·250B "sna..Num,v22":"sna..Set,v23" -18:12:10.221542 version@stat F·[6] S·1KiB[1KiB] Sc·[1.50] -18:12:10.226358 db@janitor F·8 G·0 -18:12:10.226385 db@open done T·8.8993ms -18:12:10.226452 table@compaction L0·6 -> L1·0 S·1KiB Q·24 -18:12:10.226663 db@close closing -18:12:10.228208 table@build created L1@20 N·3 S·250B "sna..Num,v22":"sna..Set,v23" -18:12:10.228217 table@build exiting -18:12:10.228221 table@build revert @20 -18:12:10.228287 db@close done T·1.6246ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.228492 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.228552 version@stat F·[6] S·1KiB[1KiB] Sc·[1.50] -18:12:10.228578 db@open opening -18:12:10.228611 journal@recovery F·1 -18:12:10.228919 journal@recovery recovering @18 -18:12:10.231113 memdb@flush created L0@20 N·3 S·250B "sna..Num,v26":"sna..Set,v27" -18:12:10.231491 version@stat F·[7] S·1KiB[1KiB] Sc·[1.75] -18:12:10.236214 db@janitor F·9 G·0 -18:12:10.236226 db@open done T·7.6418ms -18:12:10.236269 table@compaction L0·7 -> L1·0 S·1KiB Q·28 -18:12:10.236534 db@close closing -18:12:10.239151 table@build created L1@23 N·3 S·250B "sna..Num,v26":"sna..Set,v27" -18:12:10.239169 table@build exiting -18:12:10.239173 table@build revert @23 -18:12:10.239263 db@close done T·2.7285ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.239506 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.239578 version@stat F·[7] S·1KiB[1KiB] Sc·[1.75] -18:12:10.239603 db@open opening -18:12:10.239639 journal@recovery F·1 -18:12:10.240081 journal@recovery recovering @21 -18:12:10.242242 memdb@flush created L0@23 N·3 S·250B "sna..Num,v30":"sna..Set,v31" -18:12:10.242611 version@stat F·[8] S·1KiB[1KiB] Sc·[2.00] -18:12:10.247825 db@janitor F·10 G·0 -18:12:10.247853 db@open done T·8.2423ms -18:12:10.247901 table@compaction L0·8 -> L1·0 S·1KiB Q·32 -18:12:10.249685 table@build created L1@26 N·3 S·250B "sna..Num,v30":"sna..Set,v31" -18:12:10.249722 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:10.249816 db@close closing -18:12:10.251158 table@commit exiting -18:12:10.251176 db@write was delayed N·1 T·1.692ms -18:12:10.251201 db@close done T·1.3853ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.251452 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.251540 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:10.251581 db@open opening -18:12:10.251621 journal@recovery F·1 -18:12:10.252034 journal@recovery recovering @24 -18:12:10.254191 memdb@flush created L0@27 N·3 S·250B "sna..Num,v34":"sna..Set,v35" -18:12:10.254858 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:10.260079 db@janitor F·12 G·8 -18:12:10.260089 db@janitor removing table-5 -18:12:10.260109 db@janitor removing table-11 -18:12:10.260130 db@janitor removing table-8 -18:12:10.260148 db@janitor removing table-2 -18:12:10.260164 db@janitor removing table-20 -18:12:10.260180 db@janitor removing table-23 -18:12:10.260196 db@janitor removing table-14 -18:12:10.260212 db@janitor removing table-17 -18:12:10.260247 db@open done T·8.6564ms -18:12:10.260376 db@close closing -18:12:10.260409 db@close done T·32.8µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.273249 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.273336 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:10.273363 db@open opening -18:12:10.273392 journal@recovery F·1 -18:12:10.273712 journal@recovery recovering @28 -18:12:10.277549 memdb@flush created L0@30 N·3 S·250B "sna..Num,v38":"sna..Set,v39" -18:12:10.277894 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:10.284048 db@janitor F·5 G·0 -18:12:10.284079 db@open done T·10.7073ms -18:12:10.284266 db@close closing -18:12:10.284312 db@close done T·46.8µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.284538 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.284613 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:10.284639 db@open opening -18:12:10.284670 journal@recovery F·1 -18:12:10.285189 journal@recovery recovering @31 -18:12:10.287551 memdb@flush created L0@33 N·3 S·250B "sna..Num,v42":"sna..Set,v43" -18:12:10.288074 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] -18:12:10.293802 db@janitor F·6 G·0 -18:12:10.293814 db@open done T·9.1679ms -18:12:10.293981 db@close closing -18:12:10.294018 db@close done T·38µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.294220 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.294295 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] -18:12:10.294320 db@open opening -18:12:10.294351 journal@recovery F·1 -18:12:10.294675 journal@recovery recovering @34 -18:12:10.296772 memdb@flush created L0@36 N·3 S·250B "sna..Num,v46":"sna..Set,v47" -18:12:10.297223 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] -18:12:10.302631 db@janitor F·7 G·0 -18:12:10.302644 db@open done T·8.3172ms -18:12:10.302675 table@compaction L0·4 -> L1·1 S·1KiB Q·48 -18:12:10.302859 db@close closing -18:12:10.304725 table@build created L1@39 N·3 S·250B "sna..Num,v46":"sna..Set,v47" -18:12:10.304746 table@build exiting -18:12:10.304751 table@build revert @39 -18:12:10.304847 db@close done T·1.9866ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.305079 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.305158 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] -18:12:10.305185 db@open opening -18:12:10.305218 journal@recovery F·1 -18:12:10.305716 journal@recovery recovering @37 -18:12:10.308159 memdb@flush created L0@39 N·3 S·250B "sna..Num,v50":"sna..Set,v51" -18:12:10.308731 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:10.313963 db@janitor F·8 G·0 -18:12:10.313973 db@open done T·8.7816ms -18:12:10.314028 table@compaction L0·5 -> L1·1 S·1KiB Q·52 -18:12:10.314186 db@close closing -18:12:10.314277 db@close done T·90.1µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.314459 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.314520 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:10.314546 db@open opening -18:12:10.314579 journal@recovery F·1 -18:12:10.314644 journal@recovery recovering @40 -18:12:10.316609 memdb@flush created L0@42 N·3 S·250B "sna..Num,v54":"sna..Set,v55" -18:12:10.317028 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:10.323515 db@janitor F·9 G·0 -18:12:10.323530 db@open done T·8.9775ms -18:12:10.323573 table@compaction L0·6 -> L1·1 S·1KiB Q·56 -18:12:10.323736 db@close closing -18:12:10.325889 table@build created L1@45 N·3 S·250B "sna..Num,v54":"sna..Set,v55" -18:12:10.325899 table@build exiting -18:12:10.325902 table@build revert @45 -18:12:10.325986 db@close done T·2.2493ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.326216 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.326302 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:10.326332 db@open opening -18:12:10.326366 journal@recovery F·1 -18:12:10.326676 journal@recovery recovering @43 -18:12:10.328673 memdb@flush created L0@45 N·3 S·250B "sna..Num,v58":"sna..Set,v59" -18:12:10.329009 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:10.334169 db@janitor F·10 G·0 -18:12:10.334179 db@open done T·7.8398ms -18:12:10.334228 table@compaction L0·7 -> L1·1 S·1KiB Q·60 -18:12:10.334410 db@close closing -18:12:10.337354 table@build created L1@48 N·3 S·250B "sna..Num,v58":"sna..Set,v59" -18:12:10.337377 table@build exiting -18:12:10.337382 table@build revert @48 -18:12:10.337480 db@close done T·3.0693ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.337686 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.337766 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:10.337794 db@open opening -18:12:10.337829 journal@recovery F·1 -18:12:10.338192 journal@recovery recovering @46 -18:12:10.341247 memdb@flush created L0@48 N·3 S·250B "sna..Num,v62":"sna..Set,v63" -18:12:10.341595 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:10.348172 db@janitor F·11 G·0 -18:12:10.348183 db@open done T·10.3826ms -18:12:10.348260 table@compaction L0·8 -> L1·1 S·2KiB Q·64 -18:12:10.350205 db@close closing -18:12:10.351071 table@build created L1@51 N·3 S·250B "sna..Num,v62":"sna..Set,v63" -18:12:10.351080 table@build exiting -18:12:10.351084 table@build revert @51 -18:12:10.351136 db@write was delayed N·1 T·1.7224ms -18:12:10.351161 db@close done T·958.3µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.351363 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.351433 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:10.351459 db@open opening -18:12:10.351494 journal@recovery F·1 -18:12:10.351845 journal@recovery recovering @49 -18:12:10.354453 memdb@flush created L0@51 N·3 S·250B "sna..Num,v66":"sna..Set,v67" -18:12:10.355144 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] -18:12:10.360812 db@janitor F·12 G·0 -18:12:10.360825 db@open done T·9.3581ms -18:12:10.360923 table@compaction L0·9 -> L1·1 S·2KiB Q·68 -18:12:10.362533 db@close closing -18:12:10.363647 table@build created L1@54 N·3 S·250B "sna..Num,v66":"sna..Set,v67" -18:12:10.363658 table@build exiting -18:12:10.363662 table@build revert @54 -18:12:10.363722 db@write was delayed N·1 T·1.4564ms -18:12:10.363770 db@close done T·1.2384ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.363987 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.364080 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] -18:12:10.364108 db@open opening -18:12:10.364144 journal@recovery F·1 -18:12:10.364464 journal@recovery recovering @52 -18:12:10.366318 memdb@flush created L0@54 N·3 S·250B "sna..Num,v70":"sna..Set,v71" -18:12:10.366669 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] -18:12:10.372783 db@janitor F·13 G·0 -18:12:10.372797 db@open done T·8.682ms -18:12:10.372860 table@compaction L0·10 -> L1·1 S·2KiB Q·72 -18:12:10.374273 db@close closing -18:12:10.374742 table@build created L1@57 N·3 S·250B "sna..Num,v70":"sna..Set,v71" -18:12:10.374750 table@build exiting -18:12:10.374754 table@build revert @57 -18:12:10.374803 db@write was delayed N·1 T·1.1525ms -18:12:10.374829 db@close done T·557.6µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.375021 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.375095 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] -18:12:10.375122 db@open opening -18:12:10.375159 journal@recovery F·1 -18:12:10.375463 journal@recovery recovering @55 -18:12:10.377280 memdb@flush created L0@57 N·3 S·250B "sna..Num,v74":"sna..Set,v75" -18:12:10.377803 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] -18:12:10.382762 db@janitor F·14 G·0 -18:12:10.382773 db@open done T·7.6449ms -18:12:10.382811 table@compaction L0·11 -> L1·1 S·2KiB Q·76 -18:12:10.384122 db@close closing -18:12:10.384712 table@build created L1@60 N·3 S·250B "sna..Num,v74":"sna..Set,v75" -18:12:10.384720 table@build exiting -18:12:10.384723 table@build revert @60 -18:12:10.384774 db@write was delayed N·1 T·1.056ms -18:12:10.384818 db@close done T·696µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.388041 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.388138 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] -18:12:10.388164 db@open opening -18:12:10.388204 journal@recovery F·1 -18:12:10.388736 journal@recovery recovering @58 -18:12:10.391604 memdb@flush created L0@60 N·3 S·250B "sna..Num,v78":"sna..Set,v79" -18:12:10.391968 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] -18:12:10.397107 db@janitor F·15 G·0 -18:12:10.397135 db@open done T·8.9637ms -18:12:10.397175 table@compaction L0·12 -> L1·1 S·3KiB Q·80 -18:12:10.398556 db@close closing -18:12:10.398948 table@build created L1@63 N·3 S·250B "sna..Num,v78":"sna..Set,v79" -18:12:10.398956 table@build exiting -18:12:10.398959 table@build revert @63 -18:12:10.399008 db@write was delayed N·1 T·1.0222ms -18:12:10.399058 db@close done T·501.6µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.401944 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.402049 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] -18:12:10.402079 db@open opening -18:12:10.402117 journal@recovery F·1 -18:12:10.402655 journal@recovery recovering @61 -18:12:10.405546 memdb@flush created L0@63 N·3 S·250B "sna..Num,v82":"sna..Set,v83" -18:12:10.405911 version@stat F·[13 1] S·3KiB[3KiB 250B] Sc·[3.25 0.00] -18:12:10.411908 db@janitor F·16 G·0 -18:12:10.411936 db@open done T·9.8498ms -18:12:10.411981 table@compaction L0·13 -> L1·1 S·3KiB Q·84 -18:12:10.414047 table@build created L1@66 N·3 S·250B "sna..Num,v82":"sna..Set,v83" -18:12:10.414076 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:10.414149 db@close closing -18:12:10.415794 table@commit exiting -18:12:10.415813 db@write was delayed N·1 T·1.7515ms -18:12:10.415859 table@remove removed @60 -18:12:10.415927 db@close done T·1.7779ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.416131 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.416211 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:10.416238 db@open opening -18:12:10.416280 journal@recovery F·1 -18:12:10.416590 journal@recovery recovering @64 -18:12:10.418443 memdb@flush created L0@67 N·3 S·250B "sna..Num,v86":"sna..Set,v87" -18:12:10.419059 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:10.424620 db@janitor F·17 G·13 -18:12:10.424629 db@janitor removing table-48 -18:12:10.424650 db@janitor removing table-51 -18:12:10.424667 db@janitor removing table-27 -18:12:10.424683 db@janitor removing table-45 -18:12:10.424698 db@janitor removing table-36 -18:12:10.424713 db@janitor removing table-42 -18:12:10.424729 db@janitor removing table-26 -18:12:10.424745 db@janitor removing table-63 -18:12:10.424760 db@janitor removing table-33 -18:12:10.424779 db@janitor removing table-54 -18:12:10.424796 db@janitor removing table-30 -18:12:10.424811 db@janitor removing table-39 -18:12:10.424827 db@janitor removing table-57 -18:12:10.424864 db@open done T·8.6188ms -18:12:10.425015 db@close closing -18:12:10.425059 db@close done T·44.3µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.430559 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.430673 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:10.430702 db@open opening -18:12:10.430738 journal@recovery F·1 -18:12:10.431140 journal@recovery recovering @68 -18:12:10.435981 memdb@flush created L0@70 N·3 S·250B "sna..Num,v90":"sna..Set,v91" -18:12:10.436698 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:10.445434 db@janitor F·5 G·0 -18:12:10.445447 db@open done T·14.7317ms -18:12:10.445667 db@close closing -18:12:10.445710 db@close done T·43.8µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.445995 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.446057 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:10.446085 db@open opening -18:12:10.446115 journal@recovery F·1 -18:12:10.446456 journal@recovery recovering @71 -18:12:10.448818 memdb@flush created L0@73 N·3 S·250B "sna..Num,v94":"sna..Set,v95" -18:12:10.449154 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] -18:12:10.454212 db@janitor F·6 G·0 -18:12:10.454224 db@open done T·8.1324ms -18:12:10.454396 db@close closing -18:12:10.454437 db@close done T·40.8µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.454700 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.454761 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] -18:12:10.454788 db@open opening -18:12:10.454820 journal@recovery F·1 -18:12:10.455162 journal@recovery recovering @74 -18:12:10.457402 memdb@flush created L0@76 N·3 S·250B "sna..Num,v98":"sna..Set,v99" -18:12:10.458054 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] -18:12:10.463787 db@janitor F·7 G·0 -18:12:10.463814 db@open done T·9.0189ms -18:12:10.463842 table@compaction L0·4 -> L1·1 S·1KiB Q·100 -18:12:10.463997 db@close closing -18:12:10.465657 table@build created L1@79 N·3 S·250B "sna..Num,v98":"sna..Set,v99" -18:12:10.465665 table@build exiting -18:12:10.465669 table@build revert @79 -18:12:10.465736 db@close done T·1.7398ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.499060 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.499173 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] -18:12:10.499207 db@open opening -18:12:10.499275 journal@recovery F·1 -18:12:10.499626 journal@recovery recovering @77 -18:12:10.504517 memdb@flush created L0@79 N·3 S·250B "sna..Num,v102":"sna..Set,v103" -18:12:10.505000 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:10.513705 db@janitor F·8 G·0 -18:12:10.513718 db@open done T·14.5022ms -18:12:10.513765 table@compaction L0·5 -> L1·1 S·1KiB Q·104 -18:12:10.513918 db@close closing -18:12:10.513971 db@close done T·52.2µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.514415 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.514486 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:10.514513 db@open opening -18:12:10.514544 journal@recovery F·1 -18:12:10.514848 journal@recovery recovering @80 -18:12:10.517499 memdb@flush created L0@82 N·3 S·250B "sna..Num,v106":"sna..Set,v107" -18:12:10.517879 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:10.525445 db@janitor F·9 G·0 -18:12:10.525473 db@open done T·10.9525ms -18:12:10.525556 table@compaction L0·6 -> L1·1 S·1KiB Q·108 -18:12:10.525680 db@close closing -18:12:10.527406 table@build created L1@85 N·3 S·250B "sna..Num,v106":"sna..Set,v107" -18:12:10.527418 table@build exiting -18:12:10.527422 table@build revert @85 -18:12:10.527489 db@close done T·1.808ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.527893 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.527968 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:10.527991 db@open opening -18:12:10.528026 journal@recovery F·1 -18:12:10.528530 journal@recovery recovering @83 -18:12:10.530659 memdb@flush created L0@85 N·3 S·250B "sna..Num,v110":"sna..Set,v111" -18:12:10.531222 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:10.536897 db@janitor F·10 G·0 -18:12:10.536924 db@open done T·8.9267ms -18:12:10.536965 table@compaction L0·7 -> L1·1 S·1KiB Q·112 -18:12:10.537243 db@close closing -18:12:10.539246 table@build created L1@88 N·3 S·250B "sna..Num,v110":"sna..Set,v111" -18:12:10.539259 table@build exiting -18:12:10.539263 table@build revert @88 -18:12:10.539344 db@close done T·2.1005ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.539822 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.539896 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:10.539922 db@open opening -18:12:10.539955 journal@recovery F·1 -18:12:10.540042 journal@recovery recovering @86 -18:12:10.542105 memdb@flush created L0@88 N·3 S·250B "sna..Num,v114":"sna..Set,v115" -18:12:10.542433 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:10.549829 db@janitor F·11 G·0 -18:12:10.549856 db@open done T·9.9274ms -18:12:10.549897 table@compaction L0·8 -> L1·1 S·2KiB Q·116 -18:12:10.551390 db@close closing -18:12:10.552461 table@build created L1@91 N·3 S·250B "sna..Num,v114":"sna..Set,v115" -18:12:10.552470 table@build exiting -18:12:10.552475 table@build revert @91 -18:12:10.552528 db@write was delayed N·1 T·1.1881ms -18:12:10.552570 db@close done T·1.1808ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.553003 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.553087 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:10.553114 db@open opening -18:12:10.553149 journal@recovery F·1 -18:12:10.553482 journal@recovery recovering @89 -18:12:10.556052 memdb@flush created L0@91 N·3 S·250B "sna..Num,v118":"sna..Set,v119" -18:12:10.556399 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] -18:12:10.562956 db@janitor F·12 G·0 -18:12:10.562966 db@open done T·9.846ms -18:12:10.563012 table@compaction L0·9 -> L1·1 S·2KiB Q·120 -18:12:10.564322 db@close closing -18:12:10.565311 table@build created L1@94 N·3 S·250B "sna..Num,v118":"sna..Set,v119" -18:12:10.565319 table@build exiting -18:12:10.565322 table@build revert @94 -18:12:10.565372 db@write was delayed N·1 T·1.1424ms -18:12:10.565396 db@close done T·1.0772ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.565768 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.565836 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] -18:12:10.565866 db@open opening -18:12:10.565903 journal@recovery F·1 -18:12:10.566210 journal@recovery recovering @92 -18:12:10.567995 memdb@flush created L0@94 N·3 S·250B "sna..Num,v122":"sna..Set,v123" -18:12:10.568335 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] -18:12:10.573229 db@janitor F·13 G·0 -18:12:10.573254 db@open done T·7.3811ms -18:12:10.573289 table@compaction L0·10 -> L1·1 S·2KiB Q·124 -18:12:10.574687 db@close closing -18:12:10.575073 table@build created L1@97 N·3 S·250B "sna..Num,v122":"sna..Set,v123" -18:12:10.575081 table@build exiting -18:12:10.575084 table@build revert @97 -18:12:10.575132 db@write was delayed N·1 T·1.1805ms -18:12:10.575159 db@close done T·471.7µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.575462 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.575534 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] -18:12:10.575563 db@open opening -18:12:10.575609 journal@recovery F·1 -18:12:10.575918 journal@recovery recovering @95 -18:12:10.577631 memdb@flush created L0@97 N·3 S·250B "sna..Num,v126":"sna..Set,v127" -18:12:10.578000 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] -18:12:10.582726 db@janitor F·14 G·0 -18:12:10.582753 db@open done T·7.183ms -18:12:10.582799 table@compaction L0·11 -> L1·1 S·2KiB Q·128 -18:12:10.584404 table@build created L1@100 N·3 S·250B "sna..Num,v126":"sna..Set,v127" -18:12:10.584441 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:10.584483 db@close closing -18:12:10.586289 table@commit exiting -18:12:10.586307 db@write was delayed N·1 T·1.4164ms -18:12:10.586350 table@remove removed @94 -18:12:10.586421 db@close done T·1.938ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.586700 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.586774 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:10.586806 db@open opening -18:12:10.586847 journal@recovery F·1 -18:12:10.587178 journal@recovery recovering @98 -18:12:10.588955 memdb@flush created L0@101 N·3 S·251B "sna..Num,v130":"sna..Set,v131" -18:12:10.589297 version@stat F·[1 1] S·501B[251B 250B] Sc·[0.25 0.00] -18:12:10.594368 db@janitor F·15 G·11 -18:12:10.594375 db@janitor removing table-70 -18:12:10.594395 db@janitor removing table-88 -18:12:10.594412 db@janitor removing table-67 -18:12:10.594429 db@janitor removing table-76 -18:12:10.594445 db@janitor removing table-73 -18:12:10.594461 db@janitor removing table-91 -18:12:10.594476 db@janitor removing table-79 -18:12:10.594492 db@janitor removing table-82 -18:12:10.594507 db@janitor removing table-85 -18:12:10.594523 db@janitor removing table-97 -18:12:10.594539 db@janitor removing table-66 -18:12:10.594573 db@open done T·7.7596ms -18:12:10.594673 db@close closing -18:12:10.594708 db@close done T·35.1µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.594998 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.595055 version@stat F·[1 1] S·501B[251B 250B] Sc·[0.25 0.00] -18:12:10.595065 db@open opening -18:12:10.595087 journal@recovery F·1 -18:12:10.595645 journal@recovery recovering @102 -18:12:10.598093 memdb@flush created L0@104 N·3 S·250B "sna..Num,v134":"sna..Set,v135" -18:12:10.598469 version@stat F·[2 1] S·751B[501B 250B] Sc·[0.50 0.00] -18:12:10.603198 db@janitor F·5 G·0 -18:12:10.603218 db@open done T·8.1495ms -18:12:10.603334 db@close closing -18:12:10.603355 db@close done T·19.6µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.603587 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.603645 version@stat F·[2 1] S·751B[501B 250B] Sc·[0.50 0.00] -18:12:10.603656 db@open opening -18:12:10.603678 journal@recovery F·1 -18:12:10.604021 journal@recovery recovering @105 -18:12:10.605879 memdb@flush created L0@107 N·3 S·250B "sna..Num,v138":"sna..Set,v139" -18:12:10.606232 version@stat F·[3 1] S·1001B[751B 250B] Sc·[0.75 0.00] -18:12:10.611124 db@janitor F·6 G·0 -18:12:10.611148 db@open done T·7.4881ms -18:12:10.611290 db@close closing -18:12:10.611331 db@close done T·41.9µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.611555 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.611620 version@stat F·[3 1] S·1001B[751B 250B] Sc·[0.75 0.00] -18:12:10.611647 db@open opening -18:12:10.611677 journal@recovery F·1 -18:12:10.611978 journal@recovery recovering @108 -18:12:10.613853 memdb@flush created L0@110 N·3 S·250B "sna..Num,v142":"sna..Set,v143" -18:12:10.614402 version@stat F·[4 1] S·1KiB[1001B 250B] Sc·[1.00 0.00] -18:12:10.619428 db@janitor F·7 G·0 -18:12:10.619455 db@open done T·7.801ms -18:12:10.619508 table@compaction L0·4 -> L1·1 S·1KiB Q·144 -18:12:10.619632 db@close closing -18:12:10.619682 db@close done T·49.5µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.619884 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.619948 version@stat F·[4 1] S·1KiB[1001B 250B] Sc·[1.00 0.00] -18:12:10.619992 db@open opening -18:12:10.620024 journal@recovery F·1 -18:12:10.620334 journal@recovery recovering @111 -18:12:10.622532 memdb@flush created L0@113 N·3 S·250B "sna..Num,v146":"sna..Set,v147" -18:12:10.622882 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:10.627674 db@janitor F·8 G·0 -18:12:10.627697 db@open done T·7.6987ms -18:12:10.627738 table@compaction L0·5 -> L1·1 S·1KiB Q·148 -18:12:10.627875 db@close closing -18:12:10.629447 table@build created L1@116 N·3 S·250B "sna..Num,v146":"sna..Set,v147" -18:12:10.629456 table@build exiting -18:12:10.629460 table@build revert @116 -18:12:10.629524 db@close done T·1.6484ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.670634 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.670753 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:10.670766 db@open opening -18:12:10.670807 journal@recovery F·1 -18:12:10.671148 journal@recovery recovering @114 -18:12:10.677376 memdb@flush created L0@116 N·3 S·250B "sna..Num,v150":"sna..Set,v151" -18:12:10.677686 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:10.685482 db@janitor F·9 G·0 -18:12:10.685492 db@open done T·14.7217ms -18:12:10.685569 table@compaction L0·6 -> L1·1 S·1KiB Q·152 -18:12:10.685690 db@close closing -18:12:10.687313 table@build created L1@119 N·3 S·250B "sna..Num,v150":"sna..Set,v151" -18:12:10.687322 table@build exiting -18:12:10.687326 table@build revert @119 -18:12:10.687406 db@close done T·1.7155ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.687879 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.687952 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:10.687980 db@open opening -18:12:10.688017 journal@recovery F·1 -18:12:10.688324 journal@recovery recovering @117 -18:12:10.690447 memdb@flush created L0@119 N·3 S·250B "sna..Num,v154":"sna..Set,v155" -18:12:10.690793 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:10.696156 db@janitor F·10 G·0 -18:12:10.696180 db@open done T·8.1917ms -18:12:10.696211 table@compaction L0·7 -> L1·1 S·1KiB Q·156 -18:12:10.696476 db@close closing -18:12:10.698135 table@build created L1@122 N·3 S·250B "sna..Num,v154":"sna..Set,v155" -18:12:10.698147 table@build exiting -18:12:10.698150 table@build revert @122 -18:12:10.698239 db@close done T·1.7615ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.698693 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.698764 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:10.698794 db@open opening -18:12:10.698829 journal@recovery F·1 -18:12:10.699157 journal@recovery recovering @120 -18:12:10.701181 memdb@flush created L0@122 N·3 S·251B "sna..Num,v158":"sna..Set,v159" -18:12:10.701601 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:10.706189 db@janitor F·11 G·0 -18:12:10.706215 db@open done T·7.4131ms -18:12:10.706251 table@compaction L0·8 -> L1·1 S·2KiB Q·160 -18:12:10.707990 table@build created L1@125 N·3 S·251B "sna..Num,v158":"sna..Set,v159" -18:12:10.708002 table@commit exiting -18:12:10.708002 db@close closing -18:12:10.708022 db@write was delayed N·1 T·1.5374ms -18:12:10.708046 db@close done T·44.5µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.708464 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.708536 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:10.708565 db@open opening -18:12:10.708602 journal@recovery F·1 -18:12:10.709108 journal@recovery recovering @123 -18:12:10.711507 memdb@flush created L0@125 N·3 S·250B "sna..Num,v162":"sna..Set,v163" -18:12:10.711621 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] -18:12:10.716543 db@janitor F·12 G·0 -18:12:10.716570 db@open done T·7.9981ms -18:12:10.716608 table@compaction L0·9 -> L1·1 S·2KiB Q·164 -18:12:10.718343 table@build created L1@128 N·3 S·250B "sna..Num,v162":"sna..Set,v163" -18:12:10.718382 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:10.718469 db@close closing -18:12:10.719920 table@commit exiting -18:12:10.719954 db@write was delayed N·1 T·1.629ms -18:12:10.719980 db@close done T·1.5115ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.720381 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.720466 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:10.720494 db@open opening -18:12:10.720531 journal@recovery F·1 -18:12:10.720601 journal@recovery recovering @126 -18:12:10.722507 memdb@flush created L0@129 N·3 S·250B "sna..Num,v166":"sna..Set,v167" -18:12:10.722686 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:10.727944 db@janitor F·14 G·10 -18:12:10.727951 db@janitor removing table-101 -18:12:10.727971 db@janitor removing table-104 -18:12:10.727989 db@janitor removing table-113 -18:12:10.728005 db@janitor removing table-100 -18:12:10.728021 db@janitor removing table-122 -18:12:10.728037 db@janitor removing table-119 -18:12:10.728052 db@janitor removing table-116 -18:12:10.728067 db@janitor removing table-107 -18:12:10.728083 db@janitor removing table-110 -18:12:10.728100 db@janitor removing table-125 -18:12:10.728135 db@open done T·7.6336ms -18:12:10.728272 db@close closing -18:12:10.728313 db@close done T·40.6µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.728682 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.728750 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:10.728778 db@open opening -18:12:10.728805 journal@recovery F·1 -18:12:10.728872 journal@recovery recovering @130 -18:12:10.732171 memdb@flush created L0@132 N·3 S·250B "sna..Num,v170":"sna..Set,v171" -18:12:10.732553 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:10.740240 db@janitor F·5 G·0 -18:12:10.740252 db@open done T·11.4669ms -18:12:10.740367 db@close closing -18:12:10.740397 db@close done T·29.5µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.740722 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.740789 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:10.740816 db@open opening -18:12:10.740856 journal@recovery F·1 -18:12:10.741221 journal@recovery recovering @133 -18:12:10.744258 memdb@flush created L0@135 N·3 S·250B "sna..Num,v174":"sna..Set,v175" -18:12:10.744764 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] -18:12:10.752039 db@janitor F·6 G·0 -18:12:10.752066 db@open done T·11.2425ms -18:12:10.752230 db@close closing -18:12:10.752264 db@close done T·33.9µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.752566 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.752638 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] -18:12:10.752665 db@open opening -18:12:10.752696 journal@recovery F·1 -18:12:10.752999 journal@recovery recovering @136 -18:12:10.755341 memdb@flush created L0@138 N·3 S·250B "sna..Num,v178":"sna..Set,v179" -18:12:10.755679 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] -18:12:10.760872 db@janitor F·7 G·0 -18:12:10.760900 db@open done T·8.2272ms -18:12:10.760941 table@compaction L0·4 -> L1·1 S·1KiB Q·180 -18:12:10.761103 db@close closing -18:12:10.762669 table@build created L1@141 N·3 S·250B "sna..Num,v178":"sna..Set,v179" -18:12:10.762678 table@build exiting -18:12:10.762682 table@build revert @141 -18:12:10.762749 db@close done T·1.6458ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.763058 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.763132 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] -18:12:10.763159 db@open opening -18:12:10.763190 journal@recovery F·1 -18:12:10.763510 journal@recovery recovering @139 -18:12:10.765867 memdb@flush created L0@141 N·3 S·250B "sna..Num,v182":"sna..Set,v183" -18:12:10.766208 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:10.770802 db@janitor F·8 G·0 -18:12:10.770828 db@open done T·7.6625ms -18:12:10.770867 table@compaction L0·5 -> L1·1 S·1KiB Q·184 -18:12:10.771105 db@close closing -18:12:10.772622 table@build created L1@144 N·3 S·250B "sna..Num,v182":"sna..Set,v183" -18:12:10.772632 table@build exiting -18:12:10.772636 table@build revert @144 -18:12:10.772704 db@close done T·1.5983ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.772956 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.773028 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:10.773059 db@open opening -18:12:10.773091 journal@recovery F·1 -18:12:10.773420 journal@recovery recovering @142 -18:12:10.775339 memdb@flush created L0@144 N·3 S·250B "sna..Num,v186":"sna..Set,v187" -18:12:10.775700 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:10.780692 db@janitor F·9 G·0 -18:12:10.780703 db@open done T·7.6369ms -18:12:10.780758 table@compaction L0·6 -> L1·1 S·1KiB Q·188 -18:12:10.780897 db@close closing -18:12:10.780950 db@close done T·52.7µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.781223 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.781292 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:10.781319 db@open opening -18:12:10.781363 journal@recovery F·1 -18:12:10.781867 journal@recovery recovering @145 -18:12:10.784342 memdb@flush created L0@147 N·3 S·250B "sna..Num,v190":"sna..Set,v191" -18:12:10.784721 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:10.789819 db@janitor F·10 G·0 -18:12:10.789845 db@open done T·8.5191ms -18:12:10.789900 table@compaction L0·7 -> L1·1 S·1KiB Q·192 -18:12:10.790071 db@close closing -18:12:10.791680 table@build created L1@150 N·3 S·250B "sna..Num,v190":"sna..Set,v191" -18:12:10.791689 table@build exiting -18:12:10.791692 table@build revert @150 -18:12:10.791759 db@close done T·1.6879ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.791967 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.792032 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:10.792041 db@open opening -18:12:10.792070 journal@recovery F·1 -18:12:10.792371 journal@recovery recovering @148 -18:12:10.794399 memdb@flush created L0@150 N·3 S·250B "sna..Num,v194":"sna..Set,v195" -18:12:10.794772 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:10.800002 db@janitor F·11 G·0 -18:12:10.800027 db@open done T·7.981ms -18:12:10.800070 table@compaction L0·8 -> L1·1 S·2KiB Q·196 -18:12:10.801411 db@close closing -18:12:10.802016 table@build created L1@153 N·3 S·250B "sna..Num,v194":"sna..Set,v195" -18:12:10.802024 table@build exiting -18:12:10.802028 table@build revert @153 -18:12:10.802077 db@write was delayed N·1 T·1.1383ms -18:12:10.802105 db@close done T·691.8µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.802271 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.802337 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:10.802364 db@open opening -18:12:10.802417 journal@recovery F·1 -18:12:10.802878 journal@recovery recovering @151 -18:12:10.805251 memdb@flush created L0@153 N·3 S·250B "sna..Num,v198":"sna..Set,v199" -18:12:10.805605 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] -18:12:10.810312 db@janitor F·12 G·0 -18:12:10.810324 db@open done T·7.9523ms -18:12:10.810375 table@compaction L0·9 -> L1·1 S·2KiB Q·200 -18:12:10.812060 table@build created L1@156 N·3 S·250B "sna..Num,v198":"sna..Set,v199" -18:12:10.812097 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:10.812162 db@close closing -18:12:10.813828 table@commit exiting -18:12:10.813873 db@write was delayed N·1 T·1.5506ms -18:12:10.813902 db@close done T·1.7378ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.828324 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.828431 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:10.828444 db@open opening -18:12:10.828494 journal@recovery F·1 -18:12:10.828861 journal@recovery recovering @154 -18:12:10.834054 memdb@flush created L0@157 N·3 S·250B "sna..Num,v202":"sna..Set,v203" -18:12:10.834425 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:10.843450 db@janitor F·14 G·10 -18:12:10.843458 db@janitor removing table-153 -18:12:10.843478 db@janitor removing table-128 -18:12:10.843495 db@janitor removing table-147 -18:12:10.843512 db@janitor removing table-138 -18:12:10.843529 db@janitor removing table-135 -18:12:10.843545 db@janitor removing table-132 -18:12:10.843561 db@janitor removing table-129 -18:12:10.843577 db@janitor removing table-141 -18:12:10.843592 db@janitor removing table-144 -18:12:10.843607 db@janitor removing table-150 -18:12:10.843642 db@open done T·15.193ms -18:12:10.843752 db@close closing -18:12:10.843788 db@close done T·35µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.844061 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.844120 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:10.844130 db@open opening -18:12:10.844180 journal@recovery F·1 -18:12:10.844493 journal@recovery recovering @158 -18:12:10.847150 memdb@flush created L0@160 N·3 S·250B "sna..Num,v206":"sna..Set,v207" -18:12:10.847769 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:10.853277 db@janitor F·5 G·0 -18:12:10.853288 db@open done T·9.1407ms -18:12:10.853421 db@close closing -18:12:10.853445 db@close done T·23.5µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.853691 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.853754 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:10.853782 db@open opening -18:12:10.853812 journal@recovery F·1 -18:12:10.854134 journal@recovery recovering @161 -18:12:10.855992 memdb@flush created L0@163 N·3 S·250B "sna..Num,v210":"sna..Set,v211" -18:12:10.856332 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] -18:12:10.861453 db@janitor F·6 G·0 -18:12:10.861463 db@open done T·7.6744ms -18:12:10.861576 db@close closing -18:12:10.861620 db@close done T·42.2µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.861841 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.861899 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] -18:12:10.861927 db@open opening -18:12:10.861957 journal@recovery F·1 -18:12:10.862254 journal@recovery recovering @164 -18:12:10.864068 memdb@flush created L0@166 N·3 S·250B "sna..Num,v214":"sna..Set,v215" -18:12:10.864492 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] -18:12:10.869915 db@janitor F·7 G·0 -18:12:10.869926 db@open done T·7.9917ms -18:12:10.869997 table@compaction L0·4 -> L1·1 S·1KiB Q·216 -18:12:10.870141 db@close closing -18:12:10.871717 table@build created L1@169 N·3 S·250B "sna..Num,v214":"sna..Set,v215" -18:12:10.871725 table@build exiting -18:12:10.871729 table@build revert @169 -18:12:10.871792 db@close done T·1.6494ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.872038 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.872106 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] -18:12:10.872136 db@open opening -18:12:10.872168 journal@recovery F·1 -18:12:10.872470 journal@recovery recovering @167 -18:12:10.874336 memdb@flush created L0@169 N·3 S·251B "sna..Num,v218":"sna..Set,v219" -18:12:10.874676 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:10.879469 db@janitor F·8 G·0 -18:12:10.879494 db@open done T·7.3512ms -18:12:10.879531 table@compaction L0·5 -> L1·1 S·1KiB Q·220 -18:12:10.879783 db@close closing -18:12:10.881121 table@build created L1@172 N·3 S·251B "sna..Num,v218":"sna..Set,v219" -18:12:10.881131 table@build exiting -18:12:10.881135 table@build revert @172 -18:12:10.881200 db@close done T·1.4158ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.881433 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.881507 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:10.881536 db@open opening -18:12:10.881570 journal@recovery F·1 -18:12:10.881890 journal@recovery recovering @170 -18:12:10.883907 memdb@flush created L0@172 N·3 S·250B "sna..Num,v222":"sna..Set,v223" -18:12:10.884257 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:10.888879 db@janitor F·9 G·0 -18:12:10.888889 db@open done T·7.3463ms -18:12:10.888931 table@compaction L0·6 -> L1·1 S·1KiB Q·224 -18:12:10.889211 db@close closing -18:12:10.890500 table@build created L1@175 N·3 S·250B "sna..Num,v222":"sna..Set,v223" -18:12:10.890508 table@build exiting -18:12:10.890512 table@build revert @175 -18:12:10.890593 db@close done T·1.3811ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.890800 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.890892 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:10.890919 db@open opening -18:12:10.890954 journal@recovery F·1 -18:12:10.891258 journal@recovery recovering @173 -18:12:10.893622 memdb@flush created L0@175 N·3 S·250B "sna..Num,v226":"sna..Set,v227" -18:12:10.894239 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:10.901088 db@janitor F·10 G·0 -18:12:10.901116 db@open done T·10.1888ms -18:12:10.901167 table@compaction L0·7 -> L1·1 S·1KiB Q·228 -18:12:10.901463 db@close closing -18:12:10.903784 table@build created L1@178 N·3 S·250B "sna..Num,v226":"sna..Set,v227" -18:12:10.903793 table@build exiting -18:12:10.903797 table@build revert @178 -18:12:10.903865 db@close done T·2.4019ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.904082 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.904148 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:10.904158 db@open opening -18:12:10.904187 journal@recovery F·1 -18:12:10.904502 journal@recovery recovering @176 -18:12:10.907063 memdb@flush created L0@178 N·3 S·250B "sna..Num,v230":"sna..Set,v231" -18:12:10.907409 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:10.912843 db@janitor F·11 G·0 -18:12:10.912858 db@open done T·8.6954ms -18:12:10.912905 table@compaction L0·8 -> L1·1 S·2KiB Q·232 -18:12:10.914250 db@close closing -18:12:10.915387 table@build created L1@181 N·3 S·250B "sna..Num,v230":"sna..Set,v231" -18:12:10.915398 table@build exiting -18:12:10.915401 table@build revert @181 -18:12:10.915458 db@write was delayed N·1 T·1.0383ms -18:12:10.915503 db@close done T·1.2534ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.915801 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.915876 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:10.915905 db@open opening -18:12:10.915941 journal@recovery F·1 -18:12:10.916270 journal@recovery recovering @179 -18:12:10.918105 memdb@flush created L0@181 N·3 S·250B "sna..Num,v234":"sna..Set,v235" -18:12:10.918488 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] -18:12:10.923522 db@janitor F·12 G·0 -18:12:10.923537 db@open done T·7.6253ms -18:12:10.923568 table@compaction L0·9 -> L1·1 S·2KiB Q·236 -18:12:10.925371 table@build created L1@184 N·3 S·250B "sna..Num,v234":"sna..Set,v235" -18:12:10.925420 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:10.925488 db@close closing -18:12:10.926955 table@commit exiting -18:12:10.926978 db@write was delayed N·1 T·1.5707ms -18:12:10.927010 db@close done T·1.5236ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.927279 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.927374 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:10.927403 db@open opening -18:12:10.927463 journal@recovery F·1 -18:12:10.928066 journal@recovery recovering @182 -18:12:10.930923 memdb@flush created L0@185 N·3 S·250B "sna..Num,v238":"sna..Set,v239" -18:12:10.931309 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:10.936288 db@janitor F·14 G·10 -18:12:10.936297 db@janitor removing table-178 -18:12:10.936318 db@janitor removing table-166 -18:12:10.936337 db@janitor removing table-163 -18:12:10.936355 db@janitor removing table-156 -18:12:10.936371 db@janitor removing table-157 -18:12:10.936388 db@janitor removing table-169 -18:12:10.936404 db@janitor removing table-181 -18:12:10.936419 db@janitor removing table-172 -18:12:10.936434 db@janitor removing table-175 -18:12:10.936450 db@janitor removing table-160 -18:12:10.936489 db@open done T·9.0792ms -18:12:10.936639 db@close closing -18:12:10.936695 db@close done T·56.5µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.954014 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.954149 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:10.954166 db@open opening -18:12:10.954224 journal@recovery F·1 -18:12:10.954593 journal@recovery recovering @186 -18:12:10.958782 memdb@flush created L0@188 N·3 S·250B "sna..Num,v242":"sna..Set,v243" -18:12:10.959228 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:10.966291 db@janitor F·5 G·0 -18:12:10.966318 db@open done T·12.1468ms -18:12:10.966498 db@close closing -18:12:10.966542 db@close done T·43.4µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.966822 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.966887 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:10.966898 db@open opening -18:12:10.966922 journal@recovery F·1 -18:12:10.967265 journal@recovery recovering @189 -18:12:10.969445 memdb@flush created L0@191 N·3 S·250B "sna..Num,v246":"sna..Set,v247" -18:12:10.969822 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] -18:12:10.975097 db@janitor F·6 G·0 -18:12:10.975109 db@open done T·8.2069ms -18:12:10.975287 db@close closing -18:12:10.975332 db@close done T·44.6µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.975573 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.975639 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] -18:12:10.975676 db@open opening -18:12:10.975706 journal@recovery F·1 -18:12:10.976072 journal@recovery recovering @192 -18:12:10.977970 memdb@flush created L0@194 N·3 S·250B "sna..Num,v250":"sna..Set,v251" -18:12:10.978387 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] -18:12:10.983610 db@janitor F·7 G·0 -18:12:10.983623 db@open done T·7.9397ms -18:12:10.983679 table@compaction L0·4 -> L1·1 S·1KiB Q·252 -18:12:10.983820 db@close closing -18:12:10.983856 db@close done T·34.9µs -=============== Oct 25, 2021 (CST) =============== -18:12:10.984121 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.984185 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] -18:12:10.984213 db@open opening -18:12:10.984244 journal@recovery F·1 -18:12:10.984585 journal@recovery recovering @195 -18:12:10.986501 memdb@flush created L0@197 N·3 S·250B "sna..Num,v254":"sna..Set,v255" -18:12:10.986901 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:10.991785 db@janitor F·8 G·0 -18:12:10.991797 db@open done T·7.5774ms -18:12:10.991924 table@compaction L0·5 -> L1·1 S·1KiB Q·256 -18:12:10.991958 db@close closing -18:12:10.993629 table@build created L1@200 N·3 S·250B "sna..Num,v254":"sna..Set,v255" -18:12:10.993652 table@build exiting -18:12:10.993660 table@build revert @200 -18:12:10.993752 db@close done T·1.7933ms -=============== Oct 25, 2021 (CST) =============== -18:12:10.993997 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:10.994086 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:10.994095 db@open opening -18:12:10.994134 journal@recovery F·1 -18:12:10.994469 journal@recovery recovering @198 -18:12:10.997033 memdb@flush created L0@200 N·3 S·250B "sna..Num,v258":"sna..Set,v259" -18:12:10.997942 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:11.004499 db@janitor F·9 G·0 -18:12:11.004514 db@open done T·10.4144ms -18:12:11.004578 table@compaction L0·6 -> L1·1 S·1KiB Q·260 -18:12:11.004788 db@close closing -18:12:11.006713 table@build created L1@203 N·3 S·250B "sna..Num,v258":"sna..Set,v259" -18:12:11.006729 table@build exiting -18:12:11.006734 table@build revert @203 -18:12:11.006833 db@close done T·2.0445ms -=============== Oct 25, 2021 (CST) =============== -18:12:11.007146 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.007226 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:11.007253 db@open opening -18:12:11.007286 journal@recovery F·1 -18:12:11.007649 journal@recovery recovering @201 -18:12:11.009507 memdb@flush created L0@203 N·3 S·250B "sna..Num,v262":"sna..Set,v263" -18:12:11.009887 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:11.015368 db@janitor F·10 G·0 -18:12:11.015380 db@open done T·8.1205ms -18:12:11.015434 table@compaction L0·7 -> L1·1 S·1KiB Q·264 -18:12:11.015599 db@close closing -18:12:11.017273 table@build created L1@206 N·3 S·250B "sna..Num,v262":"sna..Set,v263" -18:12:11.017285 table@build exiting -18:12:11.017290 table@build revert @206 -18:12:11.017380 db@close done T·1.7801ms -=============== Oct 25, 2021 (CST) =============== -18:12:11.017656 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.017730 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:11.017755 db@open opening -18:12:11.017791 journal@recovery F·1 -18:12:11.018111 journal@recovery recovering @204 -18:12:11.020167 memdb@flush created L0@206 N·3 S·250B "sna..Num,v266":"sna..Set,v267" -18:12:11.020554 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:11.025332 db@janitor F·11 G·0 -18:12:11.025343 db@open done T·7.581ms -18:12:11.025389 table@compaction L0·8 -> L1·1 S·2KiB Q·268 -18:12:11.026695 db@close closing -18:12:11.027273 table@build created L1@209 N·3 S·250B "sna..Num,v266":"sna..Set,v267" -18:12:11.027282 table@build exiting -18:12:11.027286 table@build revert @209 -18:12:11.027338 db@write was delayed N·1 T·1.0823ms -18:12:11.027368 db@close done T·672.6µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.027659 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.027756 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:11.027769 db@open opening -18:12:11.027801 journal@recovery F·1 -18:12:11.028113 journal@recovery recovering @207 -18:12:11.030148 memdb@flush created L0@209 N·3 S·250B "sna..Num,v270":"sna..Set,v271" -18:12:11.030555 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] -18:12:11.036299 db@janitor F·12 G·0 -18:12:11.036313 db@open done T·8.5404ms -18:12:11.036416 table@compaction L0·9 -> L1·1 S·2KiB Q·272 -18:12:11.038267 db@close closing -18:12:11.038851 table@build created L1@212 N·3 S·250B "sna..Num,v270":"sna..Set,v271" -18:12:11.038860 table@build exiting -18:12:11.038863 table@build revert @212 -18:12:11.038921 db@write was delayed N·1 T·1.2763ms -18:12:11.038967 db@close done T·701.1µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.039253 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.039339 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] -18:12:11.039367 db@open opening -18:12:11.039412 journal@recovery F·1 -18:12:11.039767 journal@recovery recovering @210 -18:12:11.041812 memdb@flush created L0@212 N·3 S·250B "sna..Num,v274":"sna..Set,v275" -18:12:11.042180 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] -18:12:11.047713 db@janitor F·13 G·0 -18:12:11.047742 db@open done T·8.3679ms -18:12:11.047788 table@compaction L0·10 -> L1·1 S·2KiB Q·276 -18:12:11.049381 db@close closing -18:12:11.049568 table@build created L1@215 N·3 S·250B "sna..Num,v274":"sna..Set,v275" -18:12:11.049576 table@build exiting -18:12:11.049580 table@build revert @215 -18:12:11.049635 db@write was delayed N·1 T·1.3457ms -18:12:11.049688 db@close done T·308µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.049921 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.050012 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] -18:12:11.050039 db@open opening -18:12:11.050077 journal@recovery F·1 -18:12:11.050409 journal@recovery recovering @213 -18:12:11.052263 memdb@flush created L0@215 N·3 S·250B "sna..Num,v278":"sna..Set,v279" -18:12:11.052916 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] -18:12:11.058713 db@janitor F·14 G·0 -18:12:11.058742 db@open done T·8.696ms -18:12:11.058810 table@compaction L0·11 -> L1·1 S·2KiB Q·280 -18:12:11.060385 db@close closing -18:12:11.060852 table@build created L1@218 N·3 S·250B "sna..Num,v278":"sna..Set,v279" -18:12:11.060862 table@build exiting -18:12:11.060865 table@build revert @218 -18:12:11.060921 db@write was delayed N·1 T·1.3201ms -18:12:11.060954 db@close done T·572µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.076675 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.076805 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] -18:12:11.076841 db@open opening -18:12:11.076897 journal@recovery F·1 -18:12:11.078704 journal@recovery recovering @216 -18:12:11.083888 memdb@flush created L0@218 N·3 S·250B "sna..Num,v282":"sna..Set,v283" -18:12:11.084355 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] -18:12:11.091416 db@janitor F·15 G·0 -18:12:11.091452 db@open done T·14.6029ms -18:12:11.091498 table@compaction L0·12 -> L1·1 S·3KiB Q·284 -18:12:11.093191 db@close closing -18:12:11.093814 table@build created L1@221 N·3 S·250B "sna..Num,v282":"sna..Set,v283" -18:12:11.093826 table@build exiting -18:12:11.093830 table@build revert @221 -18:12:11.093893 db@write was delayed N·1 T·1.3236ms -18:12:11.093946 db@close done T·756.9µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.094272 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.094381 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] -18:12:11.094423 db@open opening -18:12:11.094465 journal@recovery F·1 -18:12:11.094868 journal@recovery recovering @219 -18:12:11.097351 memdb@flush created L0@221 N·3 S·250B "sna..Num,v286":"sna..Set,v287" -18:12:11.097790 version@stat F·[13 1] S·3KiB[3KiB 250B] Sc·[3.25 0.00] -18:12:11.103116 db@janitor F·16 G·0 -18:12:11.103145 db@open done T·8.7134ms -18:12:11.103192 table@compaction L0·13 -> L1·1 S·3KiB Q·288 -18:12:11.105441 table@build created L1@224 N·3 S·250B "sna..Num,v286":"sna..Set,v287" -18:12:11.105480 db@close closing -18:12:11.105493 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:11.106991 table@commit exiting -18:12:11.107030 db@write was delayed N·1 T·2.0314ms -18:12:11.107122 db@close done T·1.6412ms -=============== Oct 25, 2021 (CST) =============== -18:12:11.107489 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.107564 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:11.107574 db@open opening -18:12:11.107613 journal@recovery F·1 -18:12:11.107963 journal@recovery recovering @222 -18:12:11.109995 memdb@flush created L0@225 N·3 S·250B "sna..Num,v290":"sna..Set,v291" -18:12:11.110366 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:11.115704 db@janitor F·18 G·14 -18:12:11.115713 db@janitor removing table-194 -18:12:11.115734 db@janitor removing table-209 -18:12:11.115752 db@janitor removing table-185 -18:12:11.115769 db@janitor removing table-206 -18:12:11.115785 db@janitor removing table-200 -18:12:11.115801 db@janitor removing table-191 -18:12:11.115817 db@janitor removing table-188 -18:12:11.115832 db@janitor removing table-221 -18:12:11.115848 db@janitor removing table-218 -18:12:11.115865 db@janitor removing table-203 -18:12:11.115881 db@janitor removing table-197 -18:12:11.115896 db@janitor removing table-184 -18:12:11.115912 db@janitor removing table-215 -18:12:11.115929 db@janitor removing table-212 -18:12:11.115966 db@open done T·8.3876ms -18:12:11.116130 db@close closing -18:12:11.116171 db@close done T·39.7µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.116487 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.116548 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:11.116578 db@open opening -18:12:11.116608 journal@recovery F·1 -18:12:11.116941 journal@recovery recovering @226 -18:12:11.120145 memdb@flush created L0@228 N·3 S·250B "sna..Num,v294":"sna..Set,v295" -18:12:11.120545 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:11.125521 db@janitor F·5 G·0 -18:12:11.125532 db@open done T·8.9472ms -18:12:11.125651 db@close closing -18:12:11.125673 db@close done T·22.3µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.125884 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.125951 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:11.125978 db@open opening -18:12:11.126008 journal@recovery F·1 -18:12:11.126349 journal@recovery recovering @229 -18:12:11.128250 memdb@flush created L0@231 N·3 S·250B "sna..Num,v298":"sna..Set,v299" -18:12:11.128937 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] -18:12:11.134008 db@janitor F·6 G·0 -18:12:11.134022 db@open done T·8.0372ms -18:12:11.134192 db@close closing -18:12:11.134238 db@close done T·46.6µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.134450 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.134525 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] -18:12:11.134553 db@open opening -18:12:11.134585 journal@recovery F·1 -18:12:11.134943 journal@recovery recovering @232 -18:12:11.136837 memdb@flush created L0@234 N·3 S·250B "sna..Num,v302":"sna..Set,v303" -18:12:11.137258 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] -18:12:11.142005 db@janitor F·7 G·0 -18:12:11.142017 db@open done T·7.4567ms -18:12:11.142082 table@compaction L0·4 -> L1·1 S·1KiB Q·304 -18:12:11.142252 db@close closing -18:12:11.144079 table@build created L1@237 N·3 S·250B "sna..Num,v302":"sna..Set,v303" -18:12:11.144089 table@build exiting -18:12:11.144093 table@build revert @237 -18:12:11.144159 db@close done T·1.9072ms -=============== Oct 25, 2021 (CST) =============== -18:12:11.144346 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.144409 version@stat F·[4 1] S·1KiB[1000B 250B] Sc·[1.00 0.00] -18:12:11.144439 db@open opening -18:12:11.144470 journal@recovery F·1 -18:12:11.144792 journal@recovery recovering @235 -18:12:11.146663 memdb@flush created L0@237 N·3 S·250B "sna..Num,v306":"sna..Set,v307" -18:12:11.147052 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:11.151931 db@janitor F·8 G·0 -18:12:11.151960 db@open done T·7.5131ms -18:12:11.152010 table@compaction L0·5 -> L1·1 S·1KiB Q·308 -18:12:11.152166 db@close closing -18:12:11.154076 table@build created L1@240 N·3 S·250B "sna..Num,v306":"sna..Set,v307" -18:12:11.154095 table@build exiting -18:12:11.154100 table@build revert @240 -18:12:11.154202 db@close done T·2.0312ms -=============== Oct 25, 2021 (CST) =============== -18:12:11.154426 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.154503 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:11.154532 db@open opening -18:12:11.154567 journal@recovery F·1 -18:12:11.154907 journal@recovery recovering @238 -18:12:11.156772 memdb@flush created L0@240 N·3 S·250B "sna..Num,v310":"sna..Set,v311" -18:12:11.157246 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:11.162548 db@janitor F·9 G·0 -18:12:11.162574 db@open done T·8.0345ms -18:12:11.162633 table@compaction L0·6 -> L1·1 S·1KiB Q·312 -18:12:11.162780 db@close closing -18:12:11.162828 db@close done T·47.3µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.163020 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.163102 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:11.163132 db@open opening -18:12:11.163172 journal@recovery F·1 -18:12:11.163738 journal@recovery recovering @241 -18:12:11.166719 memdb@flush created L0@243 N·3 S·250B "sna..Num,v314":"sna..Set,v315" -18:12:11.167159 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:11.172281 db@janitor F·10 G·0 -18:12:11.172309 db@open done T·9.1687ms -18:12:11.172355 table@compaction L0·7 -> L1·1 S·1KiB Q·316 -18:12:11.172560 db@close closing -18:12:11.174271 table@build created L1@246 N·3 S·250B "sna..Num,v314":"sna..Set,v315" -18:12:11.174282 table@build exiting -18:12:11.174285 table@build revert @246 -18:12:11.174359 db@close done T·1.8001ms -=============== Oct 25, 2021 (CST) =============== -18:12:11.174578 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.174650 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:11.174676 db@open opening -18:12:11.174712 journal@recovery F·1 -18:12:11.175034 journal@recovery recovering @244 -18:12:11.176834 memdb@flush created L0@246 N·3 S·250B "sna..Num,v318":"sna..Set,v319" -18:12:11.177463 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:11.182890 db@janitor F·11 G·0 -18:12:11.182905 db@open done T·8.2217ms -18:12:11.182936 table@compaction L0·8 -> L1·1 S·2KiB Q·320 -18:12:11.185004 db@close closing -18:12:11.185104 table@build created L1@249 N·3 S·250B "sna..Num,v318":"sna..Set,v319" -18:12:11.185114 table@build exiting -18:12:11.185118 table@build revert @249 -18:12:11.185177 db@write was delayed N·1 T·1.8132ms -18:12:11.185225 db@close done T·222.3µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.321676 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.321798 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:11.321825 db@open opening -18:12:11.321861 journal@recovery F·1 -18:12:11.321936 journal@recovery recovering @247 -18:12:11.330528 memdb@flush created L0@249 N·3 S·250B "sna..Num,v322":"sna..Set,v323" -18:12:11.330724 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] -18:12:11.341691 db@janitor F·12 G·0 -18:12:11.341730 db@open done T·19.8977ms -18:12:11.341805 table@compaction L0·9 -> L1·1 S·2KiB Q·324 -18:12:11.343253 db@close closing -18:12:11.344898 table@build created L1@252 N·3 S·250B "sna..Num,v322":"sna..Set,v323" -18:12:11.344907 table@build exiting -18:12:11.344911 table@build revert @252 -18:12:11.344962 db@write was delayed N·1 T·1.2056ms -18:12:11.344988 db@close done T·1.7348ms -=============== Oct 25, 2021 (CST) =============== -18:12:11.345226 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.345306 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] -18:12:11.345332 db@open opening -18:12:11.345368 journal@recovery F·1 -18:12:11.346544 journal@recovery recovering @250 -18:12:11.348912 memdb@flush created L0@252 N·3 S·250B "sna..Num,v326":"sna..Set,v327" -18:12:11.349040 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] -18:12:11.355168 db@janitor F·13 G·0 -18:12:11.355192 db@open done T·9.8538ms -18:12:11.355257 table@compaction L0·10 -> L1·1 S·2KiB Q·328 -18:12:11.357043 db@close closing -18:12:11.357574 table@build created L1@255 N·3 S·250B "sna..Num,v326":"sna..Set,v327" -18:12:11.357583 table@build exiting -18:12:11.357587 table@build revert @255 -18:12:11.357639 db@write was delayed N·1 T·1.1053ms -18:12:11.357686 db@close done T·642.7µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.357949 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.358033 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] -18:12:11.358060 db@open opening -18:12:11.358097 journal@recovery F·1 -18:12:11.359300 journal@recovery recovering @253 -18:12:11.361515 memdb@flush created L0@255 N·3 S·250B "sna..Num,v330":"sna..Set,v331" -18:12:11.361634 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] -18:12:11.367277 db@janitor F·14 G·0 -18:12:11.367303 db@open done T·9.2365ms -18:12:11.367369 table@compaction L0·11 -> L1·1 S·2KiB Q·332 -18:12:11.368718 db@close closing -18:12:11.369645 table@build created L1@258 N·3 S·250B "sna..Num,v330":"sna..Set,v331" -18:12:11.369654 table@build exiting -18:12:11.369658 table@build revert @258 -18:12:11.369707 db@write was delayed N·1 T·1.116ms -18:12:11.369750 db@close done T·1.0311ms -=============== Oct 25, 2021 (CST) =============== -18:12:11.370009 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.370085 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] -18:12:11.370111 db@open opening -18:12:11.370150 journal@recovery F·1 -18:12:11.371288 journal@recovery recovering @256 -18:12:11.374167 memdb@flush created L0@258 N·3 S·250B "sna..Num,v334":"sna..Set,v335" -18:12:11.374280 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] -18:12:11.382395 db@janitor F·15 G·0 -18:12:11.382424 db@open done T·12.3057ms -18:12:11.382517 table@compaction L0·12 -> L1·1 S·3KiB Q·336 -18:12:11.383878 db@close closing -18:12:11.385149 table@build created L1@261 N·3 S·250B "sna..Num,v334":"sna..Set,v335" -18:12:11.385158 table@build exiting -18:12:11.385161 table@build revert @261 -18:12:11.385212 db@write was delayed N·1 T·1.1454ms -18:12:11.385240 db@close done T·1.3627ms -=============== Oct 25, 2021 (CST) =============== -18:12:11.385519 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.385604 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] -18:12:11.385631 db@open opening -18:12:11.385672 journal@recovery F·1 -18:12:11.386846 journal@recovery recovering @259 -18:12:11.390287 memdb@flush created L0@261 N·3 S·250B "sna..Num,v338":"sna..Set,v339" -18:12:11.390396 version@stat F·[13 1] S·3KiB[3KiB 250B] Sc·[3.25 0.00] -18:12:11.396609 db@janitor F·16 G·0 -18:12:11.396635 db@open done T·10.9967ms -18:12:11.396710 table@compaction L0·13 -> L1·1 S·3KiB Q·340 -18:12:11.398425 db@close closing -18:12:11.398582 table@build created L1@264 N·3 S·250B "sna..Num,v338":"sna..Set,v339" -18:12:11.398593 table@build exiting -18:12:11.398597 table@build revert @264 -18:12:11.398647 db@write was delayed N·1 T·1.4663ms -18:12:11.398676 db@close done T·252.1µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.398900 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.398986 version@stat F·[13 1] S·3KiB[3KiB 250B] Sc·[3.25 0.00] -18:12:11.399015 db@open opening -18:12:11.399053 journal@recovery F·1 -18:12:11.400209 journal@recovery recovering @262 -18:12:11.402445 memdb@flush created L0@264 N·3 S·250B "sna..Num,v342":"sna..Set,v343" -18:12:11.402556 version@stat F·[14 1] S·3KiB[3KiB 250B] Sc·[3.50 0.00] -18:12:11.408307 db@janitor F·17 G·0 -18:12:11.408318 db@open done T·9.2963ms -18:12:11.408411 table@compaction L0·14 -> L1·1 S·3KiB Q·344 -18:12:11.409788 db@close closing -18:12:11.410160 table@build created L1@267 N·3 S·250B "sna..Num,v342":"sna..Set,v343" -18:12:11.410168 table@build exiting -18:12:11.410171 table@build revert @267 -18:12:11.410219 db@write was delayed N·1 T·1.1047ms -18:12:11.410265 db@close done T·477.1µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.410522 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.410612 version@stat F·[14 1] S·3KiB[3KiB 250B] Sc·[3.50 0.00] -18:12:11.410642 db@open opening -18:12:11.410684 journal@recovery F·1 -18:12:11.411856 journal@recovery recovering @265 -18:12:11.413968 memdb@flush created L0@267 N·3 S·250B "sna..Num,v346":"sna..Set,v347" -18:12:11.414120 version@stat F·[15 1] S·3KiB[3KiB 250B] Sc·[3.75 0.00] -18:12:11.419989 db@janitor F·18 G·0 -18:12:11.419999 db@open done T·9.3505ms -18:12:11.420109 table@compaction L0·15 -> L1·1 S·3KiB Q·348 -18:12:11.421477 db@close closing -18:12:11.422008 table@build created L1@270 N·3 S·250B "sna..Num,v346":"sna..Set,v347" -18:12:11.422016 table@build exiting -18:12:11.422020 table@build revert @270 -18:12:11.422071 db@write was delayed N·1 T·1.1065ms -18:12:11.422120 db@close done T·642.3µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.422364 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.422450 version@stat F·[15 1] S·3KiB[3KiB 250B] Sc·[3.75 0.00] -18:12:11.422477 db@open opening -18:12:11.422518 journal@recovery F·1 -18:12:11.423664 journal@recovery recovering @268 -18:12:11.426076 memdb@flush created L0@270 N·3 S·250B "sna..Num,v350":"sna..Set,v351" -18:12:11.426181 version@stat F·[16 1] S·4KiB[3KiB 250B] Sc·[4.00 0.00] -18:12:11.431856 db@janitor F·19 G·0 -18:12:11.431870 db@open done T·9.386ms -18:12:11.431982 table@compaction L0·16 -> L1·1 S·4KiB Q·352 -18:12:11.433425 db@close closing -18:12:11.433682 table@build created L1@273 N·3 S·250B "sna..Num,v350":"sna..Set,v351" -18:12:11.433691 table@build exiting -18:12:11.433694 table@build revert @273 -18:12:11.433742 db@write was delayed N·1 T·1.1099ms -18:12:11.433787 db@close done T·361.7µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.434050 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.434124 version@stat F·[16 1] S·4KiB[3KiB 250B] Sc·[4.00 0.00] -18:12:11.434151 db@open opening -18:12:11.434194 journal@recovery F·1 -18:12:11.434263 journal@recovery recovering @271 -18:12:11.436651 memdb@flush created L0@273 N·3 S·250B "sna..Num,v354":"sna..Set,v355" -18:12:11.436755 version@stat F·[17 1] S·4KiB[4KiB 250B] Sc·[4.25 0.00] -18:12:11.442626 db@janitor F·20 G·0 -18:12:11.442637 db@open done T·8.4793ms -18:12:11.442728 table@compaction L0·17 -> L1·1 S·4KiB Q·356 -18:12:11.444654 db@close closing -18:12:11.445644 table@build created L1@276 N·3 S·250B "sna..Num,v354":"sna..Set,v355" -18:12:11.445654 table@build exiting -18:12:11.445658 table@build revert @276 -18:12:11.445714 db@write was delayed N·1 T·1.1408ms -18:12:11.445754 db@close done T·1.1005ms -=============== Oct 25, 2021 (CST) =============== -18:12:11.446000 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.446090 version@stat F·[17 1] S·4KiB[4KiB 250B] Sc·[4.25 0.00] -18:12:11.446120 db@open opening -18:12:11.446164 journal@recovery F·1 -18:12:11.446235 journal@recovery recovering @274 -18:12:11.448232 memdb@flush created L0@276 N·3 S·250B "sna..Num,v358":"sna..Set,v359" -18:12:11.448342 version@stat F·[18 1] S·4KiB[4KiB 250B] Sc·[4.50 0.00] -18:12:11.454015 db@janitor F·21 G·0 -18:12:11.454023 db@open done T·7.8969ms -18:12:11.454060 table@compaction L0·18 -> L1·1 S·4KiB Q·360 -18:12:11.456586 db@close closing -18:12:11.456739 table@build created L1@279 N·3 S·250B "sna..Num,v358":"sna..Set,v359" -18:12:11.456749 table@build exiting -18:12:11.456753 table@build revert @279 -18:12:11.456807 db@write was delayed N·1 T·1.2809ms -18:12:11.456888 db@close done T·301.7µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.457174 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.457257 version@stat F·[18 1] S·4KiB[4KiB 250B] Sc·[4.50 0.00] -18:12:11.457283 db@open opening -18:12:11.457331 journal@recovery F·1 -18:12:11.457400 journal@recovery recovering @277 -18:12:11.459481 memdb@flush created L0@279 N·3 S·251B "sna..Num,v362":"sna..Set,v363" -18:12:11.459590 version@stat F·[19 1] S·4KiB[4KiB 250B] Sc·[4.75 0.00] -18:12:11.464640 db@janitor F·22 G·0 -18:12:11.464649 db@open done T·7.3604ms -18:12:11.464687 table@compaction L0·19 -> L1·1 S·4KiB Q·364 -18:12:11.466481 table@build created L1@282 N·3 S·251B "sna..Num,v362":"sna..Set,v363" -18:12:11.466520 version@stat F·[0 1] S·251B[0B 251B] Sc·[0.00 0.00] -18:12:11.466612 db@close closing -18:12:11.468013 table@commit exiting -18:12:11.468034 db@write was delayed N·1 T·1.1088ms -18:12:11.468074 db@close done T·1.4631ms -=============== Oct 25, 2021 (CST) =============== -18:12:11.468357 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.468448 version@stat F·[0 1] S·251B[0B 251B] Sc·[0.00 0.00] -18:12:11.468475 db@open opening -18:12:11.468521 journal@recovery F·1 -18:12:11.468606 journal@recovery recovering @280 -18:12:11.471021 memdb@flush created L0@283 N·3 S·250B "sna..Num,v366":"sna..Set,v367" -18:12:11.471126 version@stat F·[1 1] S·501B[250B 251B] Sc·[0.25 0.00] -18:12:11.475700 db@janitor F·24 G·20 -18:12:11.475706 db@janitor removing table-261 -18:12:11.475735 db@janitor removing table-270 -18:12:11.475756 db@janitor removing table-234 -18:12:11.475775 db@janitor removing table-225 -18:12:11.475792 db@janitor removing table-237 -18:12:11.475808 db@janitor removing table-252 -18:12:11.475825 db@janitor removing table-224 -18:12:11.475841 db@janitor removing table-279 -18:12:11.475857 db@janitor removing table-267 -18:12:11.475872 db@janitor removing table-264 -18:12:11.475888 db@janitor removing table-255 -18:12:11.475903 db@janitor removing table-228 -18:12:11.475919 db@janitor removing table-273 -18:12:11.475934 db@janitor removing table-276 -18:12:11.475950 db@janitor removing table-240 -18:12:11.475966 db@janitor removing table-246 -18:12:11.475981 db@janitor removing table-231 -18:12:11.475997 db@janitor removing table-243 -18:12:11.476012 db@janitor removing table-258 -18:12:11.476028 db@janitor removing table-249 -18:12:11.476062 db@open done T·7.58ms -18:12:11.476655 db@close closing -18:12:11.476695 db@close done T·40µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.476963 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.477023 version@stat F·[1 1] S·501B[250B 251B] Sc·[0.25 0.00] -18:12:11.477033 db@open opening -18:12:11.477067 journal@recovery F·1 -18:12:11.477144 journal@recovery recovering @284 -18:12:11.480212 memdb@flush created L0@286 N·3 S·250B "sna..Num,v370":"sna..Set,v371" -18:12:11.480319 version@stat F·[2 1] S·751B[500B 251B] Sc·[0.50 0.00] -18:12:11.485452 db@janitor F·5 G·0 -18:12:11.485462 db@open done T·8.4247ms -18:12:11.486128 db@close closing -18:12:11.486188 db@close done T·59.9µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.486446 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.486512 version@stat F·[2 1] S·751B[500B 251B] Sc·[0.50 0.00] -18:12:11.486539 db@open opening -18:12:11.486569 journal@recovery F·1 -18:12:11.486641 journal@recovery recovering @287 -18:12:11.489594 memdb@flush created L0@289 N·3 S·250B "sna..Num,v374":"sna..Set,v375" -18:12:11.490151 version@stat F·[3 1] S·1001B[750B 251B] Sc·[0.75 0.00] -18:12:11.495283 db@janitor F·6 G·0 -18:12:11.495292 db@open done T·8.7468ms -18:12:11.495445 db@close closing -18:12:11.495486 db@close done T·40.9µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.495741 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.495804 version@stat F·[3 1] S·1001B[750B 251B] Sc·[0.75 0.00] -18:12:11.495845 db@open opening -18:12:11.495876 journal@recovery F·1 -18:12:11.495954 journal@recovery recovering @290 -18:12:11.498476 memdb@flush created L0@292 N·3 S·250B "sna..Num,v378":"sna..Set,v379" -18:12:11.498589 version@stat F·[4 1] S·1KiB[1000B 251B] Sc·[1.00 0.00] -18:12:11.503635 db@janitor F·7 G·0 -18:12:11.503646 db@open done T·7.7941ms -18:12:11.503693 table@compaction L0·4 -> L1·1 S·1KiB Q·380 -18:12:11.503821 db@close closing -18:12:11.503913 db@close done T·91.1µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.504160 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.504224 version@stat F·[4 1] S·1KiB[1000B 251B] Sc·[1.00 0.00] -18:12:11.504252 db@open opening -18:12:11.504283 journal@recovery F·1 -18:12:11.504371 journal@recovery recovering @293 -18:12:11.506370 memdb@flush created L0@295 N·3 S·250B "sna..Num,v382":"sna..Set,v383" -18:12:11.506988 version@stat F·[5 1] S·1KiB[1KiB 251B] Sc·[1.25 0.00] -18:12:11.513243 db@janitor F·8 G·0 -18:12:11.513255 db@open done T·8.9954ms -18:12:11.513345 table@compaction L0·5 -> L1·1 S·1KiB Q·384 -18:12:11.513438 db@close closing -18:12:11.515143 table@build created L1@298 N·3 S·250B "sna..Num,v382":"sna..Set,v383" -18:12:11.515153 table@build exiting -18:12:11.515157 table@build revert @298 -18:12:11.515226 db@close done T·1.7874ms -=============== Oct 25, 2021 (CST) =============== -18:12:11.515529 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.515596 version@stat F·[5 1] S·1KiB[1KiB 251B] Sc·[1.25 0.00] -18:12:11.515621 db@open opening -18:12:11.515652 journal@recovery F·1 -18:12:11.515726 journal@recovery recovering @296 -18:12:11.518072 memdb@flush created L0@298 N·3 S·250B "sna..Num,v386":"sna..Set,v387" -18:12:11.518177 version@stat F·[6 1] S·1KiB[1KiB 251B] Sc·[1.50 0.00] -18:12:11.523308 db@janitor F·9 G·0 -18:12:11.523316 db@open done T·7.6894ms -18:12:11.523415 table@compaction L0·6 -> L1·1 S·1KiB Q·388 -18:12:11.523973 db@close closing -18:12:11.525784 table@build created L1@301 N·3 S·250B "sna..Num,v386":"sna..Set,v387" -18:12:11.525792 table@build exiting -18:12:11.525796 table@build revert @301 -18:12:11.525873 db@close done T·1.8994ms -=============== Oct 25, 2021 (CST) =============== -18:12:11.526136 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.526201 version@stat F·[6 1] S·1KiB[1KiB 251B] Sc·[1.50 0.00] -18:12:11.526229 db@open opening -18:12:11.526264 journal@recovery F·1 -18:12:11.526335 journal@recovery recovering @299 -18:12:11.528744 memdb@flush created L0@301 N·3 S·250B "sna..Num,v390":"sna..Set,v391" -18:12:11.529324 version@stat F·[7 1] S·1KiB[1KiB 251B] Sc·[1.75 0.00] -18:12:11.536838 db@janitor F·10 G·0 -18:12:11.536849 db@open done T·10.6134ms -18:12:11.536915 table@compaction L0·7 -> L1·1 S·1KiB Q·392 -18:12:11.537089 db@close closing -18:12:11.537163 db@close done T·73.5µs -=============== Oct 25, 2021 (CST) =============== -18:12:11.537385 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.537463 version@stat F·[7 1] S·1KiB[1KiB 251B] Sc·[1.75 0.00] -18:12:11.537491 db@open opening -18:12:11.537527 journal@recovery F·1 -18:12:11.537600 journal@recovery recovering @302 -18:12:11.540616 memdb@flush created L0@304 N·3 S·250B "sna..Num,v394":"sna..Set,v395" -18:12:11.542929 version@stat F·[8 1] S·2KiB[1KiB 251B] Sc·[2.00 0.00] -18:12:11.552118 db@janitor F·11 G·0 -18:12:11.552129 db@open done T·14.631ms -18:12:11.552188 table@compaction L0·8 -> L1·1 S·2KiB Q·396 -18:12:11.554160 table@build created L1@307 N·3 S·250B "sna..Num,v394":"sna..Set,v395" -18:12:11.554177 db@close closing -18:12:11.554184 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:11.556075 table@commit exiting -18:12:11.556096 db@write was delayed N·1 T·1.3412ms -18:12:11.556124 db@close done T·1.9475ms -=============== Oct 25, 2021 (CST) =============== -18:12:11.556321 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:11.556406 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:11.556432 db@open opening -18:12:11.556469 journal@recovery F·1 -18:12:11.556793 journal@recovery recovering @305 -18:12:11.559593 memdb@flush created L0@308 N·3 S·250B "sna..Num,v398":"sna..Set,v399" -18:12:11.559943 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:11.565629 db@janitor F·13 G·9 -18:12:11.565636 db@janitor removing table-289 -18:12:11.565656 db@janitor removing table-283 -18:12:11.565673 db@janitor removing table-292 -18:12:11.565690 db@janitor removing table-282 -18:12:11.565706 db@janitor removing table-286 -18:12:11.565723 db@janitor removing table-301 -18:12:11.565738 db@janitor removing table-304 -18:12:11.565754 db@janitor removing table-298 -18:12:11.565768 db@janitor removing table-295 -18:12:11.565803 db@open done T·9.3638ms -18:12:11.565913 db@close closing -18:12:11.565943 db@close done T·30.2µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.631778 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.631862 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:12.631888 db@open opening -18:12:12.631940 journal@recovery F·1 -18:12:12.632273 journal@recovery recovering @309 -18:12:12.640077 memdb@flush created L0@311 N·3 S·250B "sna..Num,v402":"sna..Set,v403" -18:12:12.640939 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:12.650253 db@janitor F·5 G·0 -18:12:12.650281 db@open done T·18.3854ms -18:12:12.650448 db@close closing -18:12:12.650481 db@close done T·32.2µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.650735 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.650802 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:12.650832 db@open opening -18:12:12.650862 journal@recovery F·1 -18:12:12.651543 journal@recovery recovering @312 -18:12:12.653620 memdb@flush created L0@314 N·3 S·249B "sna..Num,v406":"sna..Set,v407" -18:12:12.654333 version@stat F·[3 1] S·999B[749B 250B] Sc·[0.75 0.00] -18:12:12.659932 db@janitor F·6 G·0 -18:12:12.659943 db@open done T·9.1046ms -18:12:12.660088 db@close closing -18:12:12.660112 db@close done T·23.9µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.660352 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.660417 version@stat F·[3 1] S·999B[749B 250B] Sc·[0.75 0.00] -18:12:12.660446 db@open opening -18:12:12.660480 journal@recovery F·1 -18:12:12.661187 journal@recovery recovering @315 -18:12:12.663409 memdb@flush created L0@317 N·3 S·250B "sna..Num,v410":"sna..Set,v411" -18:12:12.664146 version@stat F·[4 1] S·1KiB[999B 250B] Sc·[1.00 0.00] -18:12:12.669371 db@janitor F·7 G·0 -18:12:12.669398 db@open done T·8.9416ms -18:12:12.669447 table@compaction L0·4 -> L1·1 S·1KiB Q·412 -18:12:12.669572 db@close closing -18:12:12.669635 db@close done T·61.8µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.669879 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.669937 version@stat F·[4 1] S·1KiB[999B 250B] Sc·[1.00 0.00] -18:12:12.669963 db@open opening -18:12:12.669994 journal@recovery F·1 -18:12:12.670680 journal@recovery recovering @318 -18:12:12.672943 memdb@flush created L0@320 N·3 S·250B "sna..Num,v414":"sna..Set,v415" -18:12:12.673653 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:12.678102 db@janitor F·8 G·0 -18:12:12.678114 db@open done T·8.144ms -18:12:12.678161 table@compaction L0·5 -> L1·1 S·1KiB Q·416 -18:12:12.678365 db@close closing -18:12:12.680101 table@build created L1@323 N·3 S·250B "sna..Num,v414":"sna..Set,v415" -18:12:12.680111 table@build exiting -18:12:12.680114 table@build revert @323 -18:12:12.680183 db@close done T·1.8175ms -=============== Oct 25, 2021 (CST) =============== -18:12:12.680436 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.680503 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:12.680529 db@open opening -18:12:12.680563 journal@recovery F·1 -18:12:12.681255 journal@recovery recovering @321 -18:12:12.683076 memdb@flush created L0@323 N·3 S·250B "sna..Num,v418":"sna..Set,v419" -18:12:12.683783 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:12.688934 db@janitor F·9 G·0 -18:12:12.688944 db@open done T·8.4081ms -18:12:12.688989 table@compaction L0·6 -> L1·1 S·1KiB Q·420 -18:12:12.689167 db@close closing -18:12:12.690399 table@build created L1@326 N·3 S·250B "sna..Num,v418":"sna..Set,v419" -18:12:12.690407 table@build exiting -18:12:12.690411 table@build revert @326 -18:12:12.690493 db@close done T·1.3252ms -=============== Oct 25, 2021 (CST) =============== -18:12:12.690799 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.690865 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:12.690893 db@open opening -18:12:12.690927 journal@recovery F·1 -18:12:12.691609 journal@recovery recovering @324 -18:12:12.693420 memdb@flush created L0@326 N·3 S·250B "sna..Num,v422":"sna..Set,v423" -18:12:12.694132 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:12.699229 db@janitor F·10 G·0 -18:12:12.699240 db@open done T·8.3398ms -18:12:12.699296 table@compaction L0·7 -> L1·1 S·1KiB Q·424 -18:12:12.699483 db@close closing -18:12:12.701229 table@build created L1@329 N·3 S·250B "sna..Num,v422":"sna..Set,v423" -18:12:12.701238 table@build exiting -18:12:12.701241 table@build revert @329 -18:12:12.701309 db@close done T·1.8251ms -=============== Oct 25, 2021 (CST) =============== -18:12:12.701552 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.701623 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:12.701654 db@open opening -18:12:12.701691 journal@recovery F·1 -18:12:12.702367 journal@recovery recovering @327 -18:12:12.704411 memdb@flush created L0@329 N·3 S·250B "sna..Num,v426":"sna..Set,v427" -18:12:12.705215 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:12.710239 db@janitor F·11 G·0 -18:12:12.710250 db@open done T·8.5889ms -18:12:12.710305 table@compaction L0·8 -> L1·1 S·2KiB Q·428 -18:12:12.711588 db@close closing -18:12:12.712249 table@build created L1@332 N·3 S·250B "sna..Num,v426":"sna..Set,v427" -18:12:12.712257 table@build exiting -18:12:12.712265 table@build revert @332 -18:12:12.712313 db@write was delayed N·1 T·1.116ms -18:12:12.712353 db@close done T·765µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.712608 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.712671 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:12.712697 db@open opening -18:12:12.712732 journal@recovery F·1 -18:12:12.713577 journal@recovery recovering @330 -18:12:12.715938 memdb@flush created L0@332 N·3 S·250B "sna..Num,v430":"sna..Set,v431" -18:12:12.716727 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] -18:12:12.721947 db@janitor F·12 G·0 -18:12:12.721975 db@open done T·9.2698ms -18:12:12.722051 table@compaction L0·9 -> L1·1 S·2KiB Q·432 -18:12:12.723406 db@close closing -18:12:12.723793 table@build created L1@335 N·3 S·250B "sna..Num,v430":"sna..Set,v431" -18:12:12.723800 table@build exiting -18:12:12.723804 table@build revert @335 -18:12:12.723855 db@write was delayed N·1 T·1.1077ms -18:12:12.723905 db@close done T·498.7µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.724189 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.724264 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] -18:12:12.724293 db@open opening -18:12:12.724330 journal@recovery F·1 -18:12:12.725009 journal@recovery recovering @333 -18:12:12.726869 memdb@flush created L0@335 N·3 S·250B "sna..Num,v434":"sna..Set,v435" -18:12:12.727531 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] -18:12:12.733429 db@janitor F·13 G·0 -18:12:12.733442 db@open done T·9.1417ms -18:12:12.733556 table@compaction L0·10 -> L1·1 S·2KiB Q·436 -18:12:12.734821 db@close closing -18:12:12.735607 table@build created L1@338 N·3 S·250B "sna..Num,v434":"sna..Set,v435" -18:12:12.735615 table@build exiting -18:12:12.735618 table@build revert @338 -18:12:12.735669 db@write was delayed N·1 T·1.1181ms -18:12:12.735714 db@close done T·892.7µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.735978 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.736056 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] -18:12:12.736084 db@open opening -18:12:12.736124 journal@recovery F·1 -18:12:12.736679 journal@recovery recovering @336 -18:12:12.738529 memdb@flush created L0@338 N·3 S·250B "sna..Num,v438":"sna..Set,v439" -18:12:12.738632 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] -18:12:12.743345 db@janitor F·14 G·0 -18:12:12.743354 db@open done T·7.263ms -18:12:12.743465 table@compaction L0·11 -> L1·1 S·2KiB Q·440 -18:12:12.745054 db@close closing -18:12:12.745677 table@build created L1@341 N·3 S·250B "sna..Num,v438":"sna..Set,v439" -18:12:12.745687 table@build exiting -18:12:12.745691 table@build revert @341 -18:12:12.745751 db@write was delayed N·1 T·1.1127ms -18:12:12.745846 db@close done T·793.2µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.746104 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.746179 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] -18:12:12.746206 db@open opening -18:12:12.746245 journal@recovery F·1 -18:12:12.746784 journal@recovery recovering @339 -18:12:12.749105 memdb@flush created L0@341 N·3 S·250B "sna..Num,v442":"sna..Set,v443" -18:12:12.749211 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] -18:12:12.753578 db@janitor F·15 G·0 -18:12:12.753587 db@open done T·7.375ms -18:12:12.753705 table@compaction L0·12 -> L1·1 S·3KiB Q·444 -18:12:12.755331 db@close closing -18:12:12.755472 table@build created L1@344 N·3 S·250B "sna..Num,v442":"sna..Set,v443" -18:12:12.755480 table@build exiting -18:12:12.755484 table@build revert @344 -18:12:12.755535 db@write was delayed N·1 T·1.1238ms -18:12:12.755582 db@close done T·251.3µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.755832 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.755900 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] -18:12:12.755939 db@open opening -18:12:12.755978 journal@recovery F·1 -18:12:12.756046 journal@recovery recovering @342 -18:12:12.758401 memdb@flush created L0@344 N·3 S·250B "sna..Num,v446":"sna..Set,v447" -18:12:12.758500 version@stat F·[13 1] S·3KiB[3KiB 250B] Sc·[3.25 0.00] -18:12:12.764781 db@janitor F·16 G·0 -18:12:12.764809 db@open done T·8.8621ms -18:12:12.764858 table@compaction L0·13 -> L1·1 S·3KiB Q·448 -18:12:12.766653 db@close closing -18:12:12.767505 table@build created L1@347 N·3 S·250B "sna..Num,v446":"sna..Set,v447" -18:12:12.767515 table@build exiting -18:12:12.767519 table@build revert @347 -18:12:12.767574 db@write was delayed N·1 T·1.1084ms -18:12:12.767624 db@close done T·971.8µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.767929 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.768014 version@stat F·[13 1] S·3KiB[3KiB 250B] Sc·[3.25 0.00] -18:12:12.768042 db@open opening -18:12:12.768082 journal@recovery F·1 -18:12:12.769042 journal@recovery recovering @345 -18:12:12.771562 memdb@flush created L0@347 N·3 S·250B "sna..Num,v450":"sna..Set,v451" -18:12:12.771674 version@stat F·[14 1] S·3KiB[3KiB 250B] Sc·[3.50 0.00] -18:12:12.778110 db@janitor F·17 G·0 -18:12:12.778121 db@open done T·10.0726ms -18:12:12.778231 table@compaction L0·14 -> L1·1 S·3KiB Q·452 -18:12:12.779604 db@close closing -18:12:12.780079 table@build created L1@350 N·3 S·250B "sna..Num,v450":"sna..Set,v451" -18:12:12.780088 table@build exiting -18:12:12.780092 table@build revert @350 -18:12:12.780142 db@write was delayed N·1 T·1.1199ms -18:12:12.780207 db@close done T·602.1µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.780501 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.780577 version@stat F·[14 1] S·3KiB[3KiB 250B] Sc·[3.50 0.00] -18:12:12.780603 db@open opening -18:12:12.780643 journal@recovery F·1 -18:12:12.781052 journal@recovery recovering @348 -18:12:12.786743 memdb@flush created L0@350 N·3 S·250B "sna..Num,v454":"sna..Set,v455" -18:12:12.787113 version@stat F·[15 1] S·3KiB[3KiB 250B] Sc·[3.75 0.00] -18:12:12.792537 db@janitor F·18 G·0 -18:12:12.792547 db@open done T·11.9368ms -18:12:12.792665 table@compaction L0·15 -> L1·1 S·3KiB Q·456 -18:12:12.794115 db@close closing -18:12:12.794876 table@build created L1@353 N·3 S·250B "sna..Num,v454":"sna..Set,v455" -18:12:12.794885 table@build exiting -18:12:12.794888 table@build revert @353 -18:12:12.794937 db@write was delayed N·1 T·1.1624ms -18:12:12.794983 db@close done T·868.4µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.795230 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.795309 version@stat F·[15 1] S·3KiB[3KiB 250B] Sc·[3.75 0.00] -18:12:12.795349 db@open opening -18:12:12.795392 journal@recovery F·1 -18:12:12.795722 journal@recovery recovering @351 -18:12:12.797908 memdb@flush created L0@353 N·3 S·250B "sna..Num,v458":"sna..Set,v459" -18:12:12.798253 version@stat F·[16 1] S·4KiB[3KiB 250B] Sc·[4.00 0.00] -18:12:12.803022 db@janitor F·19 G·0 -18:12:12.803030 db@open done T·7.6754ms -18:12:12.803080 table@compaction L0·16 -> L1·1 S·4KiB Q·460 -18:12:12.804481 db@close closing -18:12:12.804806 table@build created L1@356 N·3 S·250B "sna..Num,v458":"sna..Set,v459" -18:12:12.804814 table@build exiting -18:12:12.804817 table@build revert @356 -18:12:12.804865 db@write was delayed N·1 T·1.11ms -18:12:12.804919 db@close done T·437.7µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.805148 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.805220 version@stat F·[16 1] S·4KiB[3KiB 250B] Sc·[4.00 0.00] -18:12:12.805247 db@open opening -18:12:12.805290 journal@recovery F·1 -18:12:12.805588 journal@recovery recovering @354 -18:12:12.807371 memdb@flush created L0@356 N·3 S·250B "sna..Num,v462":"sna..Set,v463" -18:12:12.807729 version@stat F·[17 1] S·4KiB[4KiB 250B] Sc·[4.25 0.00] -18:12:12.812898 db@janitor F·20 G·0 -18:12:12.812916 db@open done T·7.6533ms -18:12:12.813014 table@compaction L0·17 -> L1·1 S·4KiB Q·464 -18:12:12.814387 db@close closing -18:12:12.814805 table@build created L1@359 N·3 S·250B "sna..Num,v462":"sna..Set,v463" -18:12:12.814814 table@build exiting -18:12:12.814817 table@build revert @359 -18:12:12.814863 db@write was delayed N·1 T·1.1192ms -18:12:12.814919 db@close done T·531.1µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.815184 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.815283 version@stat F·[17 1] S·4KiB[4KiB 250B] Sc·[4.25 0.00] -18:12:12.815312 db@open opening -18:12:12.815360 journal@recovery F·1 -18:12:12.815662 journal@recovery recovering @357 -18:12:12.817575 memdb@flush created L0@359 N·3 S·250B "sna..Num,v466":"sna..Set,v467" -18:12:12.817924 version@stat F·[18 1] S·4KiB[4KiB 250B] Sc·[4.50 0.00] -18:12:12.823014 db@janitor F·21 G·0 -18:12:12.823022 db@open done T·7.7042ms -18:12:12.823066 table@compaction L0·18 -> L1·1 S·4KiB Q·468 -18:12:12.824871 table@build created L1@362 N·3 S·250B "sna..Num,v466":"sna..Set,v467" -18:12:12.824911 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:12.824959 db@close closing -18:12:12.826634 table@commit exiting -18:12:12.826668 db@write was delayed N·1 T·1.5116ms -18:12:12.826721 db@close done T·1.7613ms -=============== Oct 25, 2021 (CST) =============== -18:12:12.826981 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.827100 version@stat F·[0 1] S·250B[0B 250B] Sc·[0.00 0.00] -18:12:12.827137 db@open opening -18:12:12.827186 journal@recovery F·1 -18:12:12.827488 journal@recovery recovering @360 -18:12:12.829348 memdb@flush created L0@363 N·3 S·250B "sna..Num,v470":"sna..Set,v471" -18:12:12.829683 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:12.834234 db@janitor F·23 G·19 -18:12:12.834239 db@janitor removing table-329 -18:12:12.834260 db@janitor removing table-320 -18:12:12.834277 db@janitor removing table-332 -18:12:12.834294 db@janitor removing table-335 -18:12:12.834309 db@janitor removing table-356 -18:12:12.834325 db@janitor removing table-338 -18:12:12.834341 db@janitor removing table-341 -18:12:12.834356 db@janitor removing table-317 -18:12:12.834371 db@janitor removing table-347 -18:12:12.834386 db@janitor removing table-326 -18:12:12.834401 db@janitor removing table-359 -18:12:12.834417 db@janitor removing table-307 -18:12:12.834434 db@janitor removing table-314 -18:12:12.834450 db@janitor removing table-344 -18:12:12.834465 db@janitor removing table-308 -18:12:12.834481 db@janitor removing table-323 -18:12:12.834496 db@janitor removing table-350 -18:12:12.834511 db@janitor removing table-353 -18:12:12.834526 db@janitor removing table-311 -18:12:12.834561 db@open done T·7.4171ms -18:12:12.834654 db@close closing -18:12:12.834681 db@close done T·27.3µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.834945 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.835006 version@stat F·[1 1] S·500B[250B 250B] Sc·[0.25 0.00] -18:12:12.835033 db@open opening -18:12:12.835063 journal@recovery F·1 -18:12:12.835386 journal@recovery recovering @364 -18:12:12.837832 memdb@flush created L0@366 N·3 S·250B "sna..Num,v474":"sna..Set,v475" -18:12:12.838171 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:12.843544 db@janitor F·5 G·0 -18:12:12.843555 db@open done T·8.5145ms -18:12:12.843671 db@close closing -18:12:12.843704 db@close done T·32.7µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.843933 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.843991 version@stat F·[2 1] S·750B[500B 250B] Sc·[0.50 0.00] -18:12:12.844017 db@open opening -18:12:12.844054 journal@recovery F·1 -18:12:12.844355 journal@recovery recovering @367 -18:12:12.846222 memdb@flush created L0@369 N·3 S·250B "sna..Num,v478":"sna..Set,v479" -18:12:12.846559 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] -18:12:12.851561 db@janitor F·6 G·0 -18:12:12.851572 db@open done T·7.5472ms -18:12:12.851719 db@close closing -18:12:12.851791 db@close done T·71.1µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.852111 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.852172 version@stat F·[3 1] S·1000B[750B 250B] Sc·[0.75 0.00] -18:12:12.852198 db@open opening -18:12:12.852229 journal@recovery F·1 -18:12:12.852532 journal@recovery recovering @370 -18:12:12.854488 memdb@flush created L0@372 N·3 S·249B "sna..Num,v482":"sna..Set,v483" -18:12:12.854921 version@stat F·[4 1] S·1KiB[999B 250B] Sc·[1.00 0.00] -18:12:12.860051 db@janitor F·7 G·0 -18:12:12.860061 db@open done T·7.8568ms -18:12:12.860123 table@compaction L0·4 -> L1·1 S·1KiB Q·484 -18:12:12.860238 db@close closing -18:12:12.861721 table@build created L1@375 N·3 S·249B "sna..Num,v482":"sna..Set,v483" -18:12:12.861730 table@build exiting -18:12:12.861734 table@build revert @375 -18:12:12.861817 db@close done T·1.5782ms -=============== Oct 25, 2021 (CST) =============== -18:12:12.862063 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.862132 version@stat F·[4 1] S·1KiB[999B 250B] Sc·[1.00 0.00] -18:12:12.862159 db@open opening -18:12:12.862201 journal@recovery F·1 -18:12:12.862554 journal@recovery recovering @373 -18:12:12.864569 memdb@flush created L0@375 N·3 S·250B "sna..Num,v486":"sna..Set,v487" -18:12:12.864932 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:12.869425 db@janitor F·8 G·0 -18:12:12.869434 db@open done T·7.2689ms -18:12:12.869481 table@compaction L0·5 -> L1·1 S·1KiB Q·488 -18:12:12.869634 db@close closing -18:12:12.871295 table@build created L1@378 N·3 S·250B "sna..Num,v486":"sna..Set,v487" -18:12:12.871303 table@build exiting -18:12:12.871306 table@build revert @378 -18:12:12.871367 db@close done T·1.733ms -=============== Oct 25, 2021 (CST) =============== -18:12:12.871599 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.871667 version@stat F·[5 1] S·1KiB[1KiB 250B] Sc·[1.25 0.00] -18:12:12.871694 db@open opening -18:12:12.871729 journal@recovery F·1 -18:12:12.872033 journal@recovery recovering @376 -18:12:12.874212 memdb@flush created L0@378 N·3 S·250B "sna..Num,v490":"sna..Set,v491" -18:12:12.874583 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:12.879111 db@janitor F·9 G·0 -18:12:12.879120 db@open done T·7.4194ms -18:12:12.879180 table@compaction L0·6 -> L1·1 S·1KiB Q·492 -18:12:12.879304 db@close closing -18:12:12.880818 table@build created L1@381 N·3 S·250B "sna..Num,v490":"sna..Set,v491" -18:12:12.880827 table@build exiting -18:12:12.880831 table@build revert @381 -18:12:12.880899 db@close done T·1.5945ms -=============== Oct 25, 2021 (CST) =============== -18:12:12.881157 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.881225 version@stat F·[6 1] S·1KiB[1KiB 250B] Sc·[1.50 0.00] -18:12:12.881253 db@open opening -18:12:12.881287 journal@recovery F·1 -18:12:12.881594 journal@recovery recovering @379 -18:12:12.883420 memdb@flush created L0@381 N·3 S·250B "sna..Num,v494":"sna..Set,v495" -18:12:12.883769 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:12.888413 db@janitor F·10 G·0 -18:12:12.888425 db@open done T·7.1658ms -18:12:12.888477 table@compaction L0·7 -> L1·1 S·1KiB Q·496 -18:12:12.888669 db@close closing -18:12:12.890375 table@build created L1@384 N·3 S·250B "sna..Num,v494":"sna..Set,v495" -18:12:12.890383 table@build exiting -18:12:12.890386 table@build revert @384 -18:12:12.890450 db@close done T·1.7813ms -=============== Oct 25, 2021 (CST) =============== -18:12:12.890702 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.890771 version@stat F·[7 1] S·1KiB[1KiB 250B] Sc·[1.75 0.00] -18:12:12.890799 db@open opening -18:12:12.890836 journal@recovery F·1 -18:12:12.891141 journal@recovery recovering @382 -18:12:12.892882 memdb@flush created L0@384 N·3 S·250B "sna..Num,v498":"sna..Set,v499" -18:12:12.893224 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:12.898069 db@janitor F·11 G·0 -18:12:12.898079 db@open done T·7.2734ms -18:12:12.898131 table@compaction L0·8 -> L1·1 S·2KiB Q·500 -18:12:12.899469 db@close closing -18:12:12.899953 table@build created L1@387 N·3 S·250B "sna..Num,v498":"sna..Set,v499" -18:12:12.899961 table@build exiting -18:12:12.899964 table@build revert @387 -18:12:12.900029 db@write was delayed N·1 T·1.0787ms -18:12:12.900069 db@close done T·599.8µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.900345 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.900409 version@stat F·[8 1] S·2KiB[1KiB 250B] Sc·[2.00 0.00] -18:12:12.900435 db@open opening -18:12:12.900473 journal@recovery F·1 -18:12:12.900782 journal@recovery recovering @385 -18:12:12.903036 memdb@flush created L0@387 N·3 S·250B "sna..Num,v502":"sna..Set,v503" -18:12:12.903396 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] -18:12:12.909641 db@janitor F·12 G·0 -18:12:12.909649 db@open done T·9.2078ms -18:12:12.909712 table@compaction L0·9 -> L1·1 S·2KiB Q·504 -18:12:12.911062 db@close closing -18:12:12.912384 table@build created L1@390 N·3 S·250B "sna..Num,v502":"sna..Set,v503" -18:12:12.912393 table@build exiting -18:12:12.912397 table@build revert @390 -18:12:12.912448 db@write was delayed N·1 T·1.1462ms -18:12:12.912491 db@close done T·1.4283ms -=============== Oct 25, 2021 (CST) =============== -18:12:12.912779 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.912849 version@stat F·[9 1] S·2KiB[2KiB 250B] Sc·[2.25 0.00] -18:12:12.912876 db@open opening -18:12:12.912914 journal@recovery F·1 -18:12:12.913225 journal@recovery recovering @388 -18:12:12.915759 memdb@flush created L0@390 N·3 S·250B "sna..Num,v506":"sna..Set,v507" -18:12:12.916094 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] -18:12:12.923199 db@janitor F·13 G·0 -18:12:12.923225 db@open done T·10.3424ms -18:12:12.923275 table@compaction L0·10 -> L1·1 S·2KiB Q·508 -18:12:12.924639 db@close closing -18:12:12.925354 table@build created L1@393 N·3 S·250B "sna..Num,v506":"sna..Set,v507" -18:12:12.925362 table@build exiting -18:12:12.925366 table@build revert @393 -18:12:12.925413 db@write was delayed N·1 T·1.1201ms -18:12:12.925455 db@close done T·815µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.925715 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.925785 version@stat F·[10 1] S·2KiB[2KiB 250B] Sc·[2.50 0.00] -18:12:12.925812 db@open opening -18:12:12.925849 journal@recovery F·1 -18:12:12.926166 journal@recovery recovering @391 -18:12:12.927796 memdb@flush created L0@393 N·3 S·250B "sna..Num,v510":"sna..Set,v511" -18:12:12.928143 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] -18:12:12.933182 db@janitor F·14 G·0 -18:12:12.933191 db@open done T·7.3724ms -18:12:12.933246 table@compaction L0·11 -> L1·1 S·2KiB Q·512 -18:12:12.934533 db@close closing -18:12:12.934956 table@build created L1@396 N·3 S·250B "sna..Num,v510":"sna..Set,v511" -18:12:12.934967 table@build exiting -18:12:12.934971 table@build revert @396 -18:12:12.935024 db@write was delayed N·1 T·1.1067ms -18:12:12.935067 db@close done T·534µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.935338 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.935414 version@stat F·[11 1] S·2KiB[2KiB 250B] Sc·[2.75 0.00] -18:12:12.935441 db@open opening -18:12:12.935481 journal@recovery F·1 -18:12:12.935793 journal@recovery recovering @394 -18:12:12.937586 memdb@flush created L0@396 N·3 S·252B "sna..Num,v514":"sna..Set,v515" -18:12:12.937939 version@stat F·[12 1] S·3KiB[2KiB 250B] Sc·[3.00 0.00] -18:12:12.943280 db@janitor F·15 G·0 -18:12:12.943304 db@open done T·7.8561ms -18:12:12.943338 table@compaction L0·12 -> L1·1 S·3KiB Q·516 -18:12:12.944685 table@build created L1@399 N·3 S·252B "sna..Num,v514":"sna..Set,v515" -18:12:12.944721 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] -18:12:12.944775 db@close closing -18:12:12.946276 table@commit exiting -18:12:12.946318 db@write was delayed N·1 T·1.1411ms -18:12:12.946374 db@close done T·1.599ms -=============== Oct 25, 2021 (CST) =============== -18:12:12.946697 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.946788 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] -18:12:12.946815 db@open opening -18:12:12.946855 journal@recovery F·1 -18:12:12.947224 journal@recovery recovering @397 -18:12:12.949072 memdb@flush created L0@400 N·3 S·252B "sna..Num,v518":"sna..Set,v519" -18:12:12.949417 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] -18:12:12.954074 db@janitor F·17 G·13 -18:12:12.954080 db@janitor removing table-381 -18:12:12.954100 db@janitor removing table-390 -18:12:12.954118 db@janitor removing table-375 -18:12:12.954135 db@janitor removing table-363 -18:12:12.954151 db@janitor removing table-366 -18:12:12.954168 db@janitor removing table-362 -18:12:12.954183 db@janitor removing table-387 -18:12:12.954199 db@janitor removing table-384 -18:12:12.954214 db@janitor removing table-393 -18:12:12.954229 db@janitor removing table-396 -18:12:12.954245 db@janitor removing table-372 -18:12:12.954261 db@janitor removing table-369 -18:12:12.954276 db@janitor removing table-378 -18:12:12.954312 db@open done T·7.4901ms -18:12:12.954464 db@close closing -18:12:12.954491 db@close done T·27.1µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.954752 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.954806 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] -18:12:12.954833 db@open opening -18:12:12.954861 journal@recovery F·1 -18:12:12.955477 journal@recovery recovering @401 -18:12:12.957629 memdb@flush created L0@403 N·3 S·253B "sna..Num,v522":"sna..Set,v523" -18:12:12.957971 version@stat F·[2 1] S·757B[505B 252B] Sc·[0.50 0.00] -18:12:12.962536 db@janitor F·5 G·0 -18:12:12.962546 db@open done T·7.7061ms -18:12:12.962695 db@close closing -18:12:12.962734 db@close done T·39.5µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.962994 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.963057 version@stat F·[2 1] S·757B[505B 252B] Sc·[0.50 0.00] -18:12:12.963083 db@open opening -18:12:12.963113 journal@recovery F·1 -18:12:12.963826 journal@recovery recovering @404 -18:12:12.965917 memdb@flush created L0@406 N·3 S·252B "sna..Num,v526":"sna..Set,v527" -18:12:12.966878 version@stat F·[3 1] S·1009B[757B 252B] Sc·[0.75 0.00] -18:12:12.972731 db@janitor F·6 G·0 -18:12:12.972742 db@open done T·9.6531ms -18:12:12.972935 db@close closing -18:12:12.972976 db@close done T·41.5µs -=============== Oct 25, 2021 (CST) =============== -18:12:12.973240 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.973309 version@stat F·[3 1] S·1009B[757B 252B] Sc·[0.75 0.00] -18:12:12.973335 db@open opening -18:12:12.973367 journal@recovery F·1 -18:12:12.974047 journal@recovery recovering @407 -18:12:12.976129 memdb@flush created L0@409 N·3 S·252B "sna..Num,v530":"sna..Set,v531" -18:12:12.976479 version@stat F·[4 1] S·1KiB[1009B 252B] Sc·[1.00 0.00] -18:12:12.981803 db@janitor F·7 G·0 -18:12:12.981822 db@open done T·8.4724ms -18:12:12.981900 table@compaction L0·4 -> L1·1 S·1KiB Q·532 -18:12:12.982016 db@close closing -18:12:12.983938 table@build created L1@412 N·3 S·252B "sna..Num,v530":"sna..Set,v531" -18:12:12.983948 table@build exiting -18:12:12.983952 table@build revert @412 -18:12:12.984038 db@close done T·2.0215ms -=============== Oct 25, 2021 (CST) =============== -18:12:12.984279 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.984343 version@stat F·[4 1] S·1KiB[1009B 252B] Sc·[1.00 0.00] -18:12:12.984370 db@open opening -18:12:12.984401 journal@recovery F·1 -18:12:12.985072 journal@recovery recovering @410 -18:12:12.987229 memdb@flush created L0@412 N·3 S·252B "sna..Num,v534":"sna..Set,v535" -18:12:12.987336 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] -18:12:12.992962 db@janitor F·8 G·0 -18:12:12.992971 db@open done T·8.5949ms -18:12:12.993026 table@compaction L0·5 -> L1·1 S·1KiB Q·536 -18:12:12.993621 db@close closing -18:12:12.994659 table@build created L1@415 N·3 S·252B "sna..Num,v534":"sna..Set,v535" -18:12:12.994667 table@build exiting -18:12:12.994671 table@build revert @415 -18:12:12.994743 db@close done T·1.1214ms -=============== Oct 25, 2021 (CST) =============== -18:12:12.995023 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:12.995090 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] -18:12:12.995117 db@open opening -18:12:12.995151 journal@recovery F·1 -18:12:12.995236 journal@recovery recovering @413 -18:12:12.997540 memdb@flush created L0@415 N·3 S·252B "sna..Num,v538":"sna..Set,v539" -18:12:12.997647 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] -18:12:13.002410 db@janitor F·9 G·0 -18:12:13.002434 db@open done T·7.3099ms -18:12:13.002468 table@compaction L0·6 -> L1·1 S·1KiB Q·540 -18:12:13.003134 db@close closing -18:12:13.004164 table@build created L1@418 N·3 S·252B "sna..Num,v538":"sna..Set,v539" -18:12:13.004172 table@build exiting -18:12:13.004175 table@build revert @418 -18:12:13.004259 db@close done T·1.1242ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.004521 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.004585 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] -18:12:13.004612 db@open opening -18:12:13.004646 journal@recovery F·1 -18:12:13.004718 journal@recovery recovering @416 -18:12:13.007126 memdb@flush created L0@418 N·3 S·252B "sna..Num,v542":"sna..Set,v543" -18:12:13.007222 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] -18:12:13.011710 db@janitor F·10 G·0 -18:12:13.011719 db@open done T·7.1008ms -18:12:13.011789 table@compaction L0·7 -> L1·1 S·1KiB Q·544 -18:12:13.012442 db@close closing -18:12:13.013460 table@build created L1@421 N·3 S·252B "sna..Num,v542":"sna..Set,v543" -18:12:13.013470 table@build exiting -18:12:13.013473 table@build revert @421 -18:12:13.013561 db@close done T·1.119ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.013864 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.013938 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] -18:12:13.013965 db@open opening -18:12:13.014001 journal@recovery F·1 -18:12:13.014074 journal@recovery recovering @419 -18:12:13.016325 memdb@flush created L0@421 N·3 S·252B "sna..Num,v546":"sna..Set,v547" -18:12:13.016429 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] -18:12:13.021867 db@janitor F·11 G·0 -18:12:13.021893 db@open done T·7.9204ms -18:12:13.021929 table@compaction L0·8 -> L1·1 S·2KiB Q·548 -18:12:13.023811 table@build created L1@424 N·3 S·252B "sna..Num,v546":"sna..Set,v547" -18:12:13.023827 db@close closing -18:12:13.023837 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] -18:12:13.025286 table@commit exiting -18:12:13.025323 db@write was delayed N·1 T·1.1096ms -18:12:13.025405 db@close done T·1.5765ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.025677 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.025748 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] -18:12:13.025774 db@open opening -18:12:13.025813 journal@recovery F·1 -18:12:13.025884 journal@recovery recovering @422 -18:12:13.028175 memdb@flush created L0@425 N·3 S·252B "sna..Num,v550":"sna..Set,v551" -18:12:13.029943 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] -18:12:13.035834 db@janitor F·13 G·9 -18:12:13.035841 db@janitor removing table-415 -18:12:13.035861 db@janitor removing table-399 -18:12:13.035879 db@janitor removing table-418 -18:12:13.035895 db@janitor removing table-421 -18:12:13.035910 db@janitor removing table-412 -18:12:13.035926 db@janitor removing table-403 -18:12:13.035943 db@janitor removing table-409 -18:12:13.035959 db@janitor removing table-400 -18:12:13.035975 db@janitor removing table-406 -18:12:13.036013 db@open done T·10.2315ms -18:12:13.036126 db@close closing -18:12:13.036155 db@close done T·28.5µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.036425 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.036485 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] -18:12:13.036513 db@open opening -18:12:13.036542 journal@recovery F·1 -18:12:13.036854 journal@recovery recovering @426 -18:12:13.039068 memdb@flush created L0@428 N·3 S·252B "sna..Num,v554":"sna..Set,v555" -18:12:13.039409 version@stat F·[2 1] S·756B[504B 252B] Sc·[0.50 0.00] -18:12:13.044415 db@janitor F·5 G·0 -18:12:13.044439 db@open done T·7.9185ms -18:12:13.044562 db@close closing -18:12:13.044601 db@close done T·37.8µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.044797 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.044867 version@stat F·[2 1] S·756B[504B 252B] Sc·[0.50 0.00] -18:12:13.044897 db@open opening -18:12:13.044927 journal@recovery F·1 -18:12:13.045237 journal@recovery recovering @429 -18:12:13.047404 memdb@flush created L0@431 N·3 S·252B "sna..Num,v558":"sna..Set,v559" -18:12:13.047751 version@stat F·[3 1] S·1008B[756B 252B] Sc·[0.75 0.00] -18:12:13.053869 db@janitor F·6 G·0 -18:12:13.053894 db@open done T·8.9898ms -18:12:13.054064 db@close closing -18:12:13.054108 db@close done T·45.1µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.176524 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.176678 version@stat F·[3 1] S·1008B[756B 252B] Sc·[0.75 0.00] -18:12:13.176708 db@open opening -18:12:13.176739 journal@recovery F·1 -18:12:13.177370 journal@recovery recovering @432 -18:12:13.183868 memdb@flush created L0@434 N·3 S·252B "sna..Num,v562":"sna..Set,v563" -18:12:13.184343 version@stat F·[4 1] S·1KiB[1008B 252B] Sc·[1.00 0.00] -18:12:13.192929 db@janitor F·7 G·0 -18:12:13.192962 db@open done T·16.2463ms -18:12:13.193025 table@compaction L0·4 -> L1·1 S·1KiB Q·564 -18:12:13.193120 db@close closing -18:12:13.195443 table@build created L1@437 N·3 S·252B "sna..Num,v562":"sna..Set,v563" -18:12:13.195467 table@build exiting -18:12:13.195473 table@build revert @437 -18:12:13.195575 db@close done T·2.4529ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.195949 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.196059 version@stat F·[4 1] S·1KiB[1008B 252B] Sc·[1.00 0.00] -18:12:13.196093 db@open opening -18:12:13.196127 journal@recovery F·1 -18:12:13.196535 journal@recovery recovering @435 -18:12:13.199185 memdb@flush created L0@437 N·3 S·252B "sna..Num,v566":"sna..Set,v567" -18:12:13.199792 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] -18:12:13.207549 db@janitor F·8 G·0 -18:12:13.207567 db@open done T·11.4666ms -18:12:13.207690 table@compaction L0·5 -> L1·1 S·1KiB Q·568 -18:12:13.207807 db@close closing -18:12:13.210192 table@build created L1@440 N·3 S·252B "sna..Num,v566":"sna..Set,v567" -18:12:13.210213 table@build exiting -18:12:13.210219 table@build revert @440 -18:12:13.210315 db@close done T·2.5067ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.210629 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.210739 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] -18:12:13.210768 db@open opening -18:12:13.210802 journal@recovery F·1 -18:12:13.211202 journal@recovery recovering @438 -18:12:13.213465 memdb@flush created L0@440 N·3 S·252B "sna..Num,v570":"sna..Set,v571" -18:12:13.214050 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] -18:12:13.220781 db@janitor F·9 G·0 -18:12:13.220796 db@open done T·10.0212ms -18:12:13.220838 table@compaction L0·6 -> L1·1 S·1KiB Q·572 -18:12:13.221028 db@close closing -18:12:13.222948 table@build created L1@443 N·3 S·252B "sna..Num,v570":"sna..Set,v571" -18:12:13.222970 table@build exiting -18:12:13.222976 table@build revert @443 -18:12:13.223073 db@close done T·2.0445ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.223410 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.223512 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] -18:12:13.223541 db@open opening -18:12:13.223575 journal@recovery F·1 -18:12:13.223968 journal@recovery recovering @441 -18:12:13.226183 memdb@flush created L0@443 N·3 S·252B "sna..Num,v574":"sna..Set,v575" -18:12:13.226689 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] -18:12:13.233014 db@janitor F·10 G·0 -18:12:13.233045 db@open done T·9.4969ms -18:12:13.233144 table@compaction L0·7 -> L1·1 S·1KiB Q·576 -18:12:13.233287 db@close closing -18:12:13.235176 table@build created L1@446 N·3 S·252B "sna..Num,v574":"sna..Set,v575" -18:12:13.235198 table@build exiting -18:12:13.235204 table@build revert @446 -18:12:13.235317 db@close done T·2.0315ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.235640 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.235767 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] -18:12:13.235820 db@open opening -18:12:13.235870 journal@recovery F·1 -18:12:13.236272 journal@recovery recovering @444 -18:12:13.238590 memdb@flush created L0@446 N·3 S·252B "sna..Num,v578":"sna..Set,v579" -18:12:13.239076 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] -18:12:13.245506 db@janitor F·11 G·0 -18:12:13.245523 db@open done T·9.6954ms -18:12:13.245614 table@compaction L0·8 -> L1·1 S·2KiB Q·580 -18:12:13.247625 db@close closing -18:12:13.248485 table@build created L1@449 N·3 S·252B "sna..Num,v578":"sna..Set,v579" -18:12:13.248508 table@build exiting -18:12:13.248515 table@build revert @449 -18:12:13.248606 db@write was delayed N·1 T·1.728ms -18:12:13.248695 db@close done T·1.0722ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.249066 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.249170 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] -18:12:13.249181 db@open opening -18:12:13.249213 journal@recovery F·1 -18:12:13.249611 journal@recovery recovering @447 -18:12:13.252545 memdb@flush created L0@449 N·3 S·252B "sna..Num,v582":"sna..Set,v583" -18:12:13.253091 version@stat F·[9 1] S·2KiB[2KiB 252B] Sc·[2.25 0.00] -18:12:13.259406 db@janitor F·12 G·0 -18:12:13.259439 db@open done T·10.2522ms -18:12:13.259492 table@compaction L0·9 -> L1·1 S·2KiB Q·584 -18:12:13.261873 table@build created L1@452 N·3 S·252B "sna..Num,v582":"sna..Set,v583" -18:12:13.261910 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] -18:12:13.261937 db@close closing -18:12:13.264066 table@commit exiting -18:12:13.264135 db@write was delayed N·1 T·2.0585ms -18:12:13.264179 db@close done T·2.2422ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.264549 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.264680 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] -18:12:13.264714 db@open opening -18:12:13.264758 journal@recovery F·1 -18:12:13.265123 journal@recovery recovering @450 -18:12:13.268961 memdb@flush created L0@453 N·3 S·252B "sna..Num,v586":"sna..Set,v587" -18:12:13.269483 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] -18:12:13.276090 db@janitor F·14 G·10 -18:12:13.276100 db@janitor removing table-434 -18:12:13.276122 db@janitor removing table-437 -18:12:13.276141 db@janitor removing table-431 -18:12:13.276158 db@janitor removing table-424 -18:12:13.276174 db@janitor removing table-440 -18:12:13.276191 db@janitor removing table-428 -18:12:13.276207 db@janitor removing table-443 -18:12:13.276222 db@janitor removing table-449 -18:12:13.276237 db@janitor removing table-446 -18:12:13.276253 db@janitor removing table-425 -18:12:13.276291 db@open done T·11.5683ms -18:12:13.276478 db@close closing -18:12:13.276527 db@close done T·48.4µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.276913 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.277008 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] -18:12:13.277021 db@open opening -18:12:13.277068 journal@recovery F·1 -18:12:13.277510 journal@recovery recovering @454 -18:12:13.280220 memdb@flush created L0@456 N·3 S·252B "sna..Num,v590":"sna..Set,v591" -18:12:13.280731 version@stat F·[2 1] S·756B[504B 252B] Sc·[0.50 0.00] -18:12:13.294412 db@janitor F·5 G·0 -18:12:13.294426 db@open done T·17.3991ms -18:12:13.294593 db@close closing -18:12:13.294627 db@close done T·34.9µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.294927 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.295008 version@stat F·[2 1] S·756B[504B 252B] Sc·[0.50 0.00] -18:12:13.295037 db@open opening -18:12:13.295071 journal@recovery F·1 -18:12:13.295459 journal@recovery recovering @457 -18:12:13.298492 memdb@flush created L0@459 N·3 S·252B "sna..Num,v594":"sna..Set,v595" -18:12:13.299099 version@stat F·[3 1] S·1008B[756B 252B] Sc·[0.75 0.00] -18:12:13.306239 db@janitor F·6 G·0 -18:12:13.306253 db@open done T·11.208ms -18:12:13.306394 db@close closing -18:12:13.306425 db@close done T·31.3µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.306755 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.306840 version@stat F·[3 1] S·1008B[756B 252B] Sc·[0.75 0.00] -18:12:13.306870 db@open opening -18:12:13.306902 journal@recovery F·1 -18:12:13.307299 journal@recovery recovering @460 -18:12:13.309856 memdb@flush created L0@462 N·3 S·252B "sna..Num,v598":"sna..Set,v599" -18:12:13.310295 version@stat F·[4 1] S·1KiB[1008B 252B] Sc·[1.00 0.00] -18:12:13.316947 db@janitor F·7 G·0 -18:12:13.316962 db@open done T·10.0846ms -18:12:13.316998 table@compaction L0·4 -> L1·1 S·1KiB Q·600 -18:12:13.317250 db@close closing -18:12:13.319443 table@build created L1@465 N·3 S·252B "sna..Num,v598":"sna..Set,v599" -18:12:13.319475 table@build exiting -18:12:13.319483 table@build revert @465 -18:12:13.319603 db@close done T·2.3526ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.319962 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.320057 version@stat F·[4 1] S·1KiB[1008B 252B] Sc·[1.00 0.00] -18:12:13.320084 db@open opening -18:12:13.320118 journal@recovery F·1 -18:12:13.320587 journal@recovery recovering @463 -18:12:13.322869 memdb@flush created L0@465 N·3 S·252B "sna..Num,v602":"sna..Set,v603" -18:12:13.323295 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] -18:12:13.329623 db@janitor F·8 G·0 -18:12:13.329640 db@open done T·9.5478ms -18:12:13.329756 table@compaction L0·5 -> L1·1 S·1KiB Q·604 -18:12:13.329951 db@close closing -18:12:13.331681 table@build created L1@468 N·3 S·252B "sna..Num,v602":"sna..Set,v603" -18:12:13.331707 table@build exiting -18:12:13.331716 table@build revert @468 -18:12:13.331838 db@close done T·1.8857ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.332142 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.332237 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] -18:12:13.332267 db@open opening -18:12:13.332302 journal@recovery F·1 -18:12:13.332757 journal@recovery recovering @466 -18:12:13.335309 memdb@flush created L0@468 N·3 S·252B "sna..Num,v606":"sna..Set,v607" -18:12:13.335889 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] -18:12:13.342116 db@janitor F·9 G·0 -18:12:13.342130 db@open done T·9.8556ms -18:12:13.342223 table@compaction L0·6 -> L1·1 S·1KiB Q·608 -18:12:13.342302 db@close closing -18:12:13.344019 table@build created L1@471 N·3 S·252B "sna..Num,v606":"sna..Set,v607" -18:12:13.344038 table@build exiting -18:12:13.344043 table@build revert @471 -18:12:13.344137 db@close done T·1.8328ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.344413 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.344506 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] -18:12:13.344537 db@open opening -18:12:13.344582 journal@recovery F·1 -18:12:13.344995 journal@recovery recovering @469 -18:12:13.347173 memdb@flush created L0@471 N·3 S·252B "sna..Num,v610":"sna..Set,v611" -18:12:13.347631 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] -18:12:13.353404 db@janitor F·10 G·0 -18:12:13.353420 db@open done T·8.8747ms -18:12:13.353473 table@compaction L0·7 -> L1·1 S·1KiB Q·612 -18:12:13.353704 db@close closing -18:12:13.355477 table@build created L1@474 N·3 S·252B "sna..Num,v610":"sna..Set,v611" -18:12:13.355496 table@build exiting -18:12:13.355501 table@build revert @474 -18:12:13.355616 db@close done T·1.9118ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.356022 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.356122 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] -18:12:13.356186 db@open opening -18:12:13.356236 journal@recovery F·1 -18:12:13.356598 journal@recovery recovering @472 -18:12:13.359162 memdb@flush created L0@474 N·3 S·252B "sna..Num,v614":"sna..Set,v615" -18:12:13.359619 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] -18:12:13.367224 db@janitor F·11 G·0 -18:12:13.367238 db@open done T·11.0451ms -18:12:13.367280 table@compaction L0·8 -> L1·1 S·2KiB Q·616 -18:12:13.368747 db@close closing -18:12:13.369708 table@build created L1@477 N·3 S·252B "sna..Num,v614":"sna..Set,v615" -18:12:13.369725 table@build exiting -18:12:13.369730 table@build revert @477 -18:12:13.369800 db@write was delayed N·1 T·1.1193ms -18:12:13.369854 db@close done T·1.1083ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.370153 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.370241 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] -18:12:13.370271 db@open opening -18:12:13.370310 journal@recovery F·1 -18:12:13.370697 journal@recovery recovering @475 -18:12:13.373701 memdb@flush created L0@477 N·3 S·252B "sna..Num,v618":"sna..Set,v619" -18:12:13.374136 version@stat F·[9 1] S·2KiB[2KiB 252B] Sc·[2.25 0.00] -18:12:13.380294 db@janitor F·12 G·0 -18:12:13.380307 db@open done T·10.029ms -18:12:13.380413 table@compaction L0·9 -> L1·1 S·2KiB Q·620 -18:12:13.382742 db@close closing -18:12:13.382794 table@build created L1@480 N·3 S·252B "sna..Num,v618":"sna..Set,v619" -18:12:13.382806 table@build exiting -18:12:13.382810 table@build revert @480 -18:12:13.382874 db@write was delayed N·1 T·1.9984ms -18:12:13.382926 db@close done T·185.8µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.383232 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.383318 version@stat F·[9 1] S·2KiB[2KiB 252B] Sc·[2.25 0.00] -18:12:13.383346 db@open opening -18:12:13.383384 journal@recovery F·1 -18:12:13.383792 journal@recovery recovering @478 -18:12:13.386146 memdb@flush created L0@480 N·3 S·252B "sna..Num,v622":"sna..Set,v623" -18:12:13.386684 version@stat F·[10 1] S·2KiB[2KiB 252B] Sc·[2.50 0.00] -18:12:13.393324 db@janitor F·13 G·0 -18:12:13.393340 db@open done T·9.9867ms -18:12:13.393365 table@compaction L0·10 -> L1·1 S·2KiB Q·624 -18:12:13.394949 db@close closing -18:12:13.395511 table@build created L1@483 N·3 S·252B "sna..Num,v622":"sna..Set,v623" -18:12:13.395526 table@build exiting -18:12:13.395531 table@build revert @483 -18:12:13.395612 db@write was delayed N·1 T·1.1311ms -18:12:13.395696 db@close done T·748.6µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.396032 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.396120 version@stat F·[10 1] S·2KiB[2KiB 252B] Sc·[2.50 0.00] -18:12:13.396152 db@open opening -18:12:13.396200 journal@recovery F·1 -18:12:13.396687 journal@recovery recovering @481 -18:12:13.399703 memdb@flush created L0@483 N·3 S·252B "sna..Num,v626":"sna..Set,v627" -18:12:13.400181 version@stat F·[11 1] S·2KiB[2KiB 252B] Sc·[2.75 0.00] -18:12:13.407255 db@janitor F·14 G·0 -18:12:13.407269 db@open done T·11.1095ms -18:12:13.407374 table@compaction L0·11 -> L1·1 S·2KiB Q·628 -18:12:13.408745 db@close closing -18:12:13.410048 table@build created L1@486 N·3 S·252B "sna..Num,v626":"sna..Set,v627" -18:12:13.410067 table@build exiting -18:12:13.410071 table@build revert @486 -18:12:13.410144 db@write was delayed N·1 T·1.1303ms -18:12:13.410204 db@close done T·1.4612ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.410531 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.410626 version@stat F·[11 1] S·2KiB[2KiB 252B] Sc·[2.75 0.00] -18:12:13.410663 db@open opening -18:12:13.410704 journal@recovery F·1 -18:12:13.411056 journal@recovery recovering @484 -18:12:13.413744 memdb@flush created L0@486 N·3 S·252B "sna..Num,v630":"sna..Set,v631" -18:12:13.414239 version@stat F·[12 1] S·3KiB[2KiB 252B] Sc·[3.00 0.00] -18:12:13.419970 db@janitor F·15 G·0 -18:12:13.419988 db@open done T·9.317ms -18:12:13.420027 table@compaction L0·12 -> L1·1 S·3KiB Q·632 -18:12:13.421514 db@close closing -18:12:13.422553 table@build created L1@489 N·3 S·252B "sna..Num,v630":"sna..Set,v631" -18:12:13.422565 table@build exiting -18:12:13.422568 table@build revert @489 -18:12:13.422629 db@write was delayed N·1 T·1.1255ms -18:12:13.422692 db@close done T·1.179ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.423019 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.423120 version@stat F·[12 1] S·3KiB[2KiB 252B] Sc·[3.00 0.00] -18:12:13.423148 db@open opening -18:12:13.423192 journal@recovery F·1 -18:12:13.423574 journal@recovery recovering @487 -18:12:13.425813 memdb@flush created L0@489 N·3 S·252B "sna..Num,v634":"sna..Set,v635" -18:12:13.426180 version@stat F·[13 1] S·3KiB[3KiB 252B] Sc·[3.25 0.00] -18:12:13.431721 db@janitor F·16 G·0 -18:12:13.431736 db@open done T·8.5804ms -18:12:13.431819 table@compaction L0·13 -> L1·1 S·3KiB Q·636 -18:12:13.433205 db@close closing -18:12:13.433649 table@build created L1@492 N·3 S·252B "sna..Num,v634":"sna..Set,v635" -18:12:13.433669 table@build exiting -18:12:13.433673 table@build revert @492 -18:12:13.433734 db@write was delayed N·1 T·1.1144ms -18:12:13.433787 db@close done T·581.5µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.434032 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.434122 version@stat F·[13 1] S·3KiB[3KiB 252B] Sc·[3.25 0.00] -18:12:13.434162 db@open opening -18:12:13.434204 journal@recovery F·1 -18:12:13.434535 journal@recovery recovering @490 -18:12:13.436462 memdb@flush created L0@492 N·3 S·252B "sna..Num,v638":"sna..Set,v639" -18:12:13.436881 version@stat F·[14 1] S·3KiB[3KiB 252B] Sc·[3.50 0.00] -18:12:13.442433 db@janitor F·17 G·0 -18:12:13.442446 db@open done T·8.2764ms -18:12:13.442518 table@compaction L0·14 -> L1·1 S·3KiB Q·640 -18:12:13.443946 db@close closing -18:12:13.444378 table@build created L1@495 N·3 S·252B "sna..Num,v638":"sna..Set,v639" -18:12:13.444387 table@build exiting -18:12:13.444391 table@build revert @495 -18:12:13.444448 db@write was delayed N·1 T·1.1206ms -18:12:13.444502 db@close done T·556.4µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.452002 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.452152 version@stat F·[14 1] S·3KiB[3KiB 252B] Sc·[3.50 0.00] -18:12:13.452162 db@open opening -18:12:13.452197 journal@recovery F·1 -18:12:13.452605 journal@recovery recovering @493 -18:12:13.456626 memdb@flush created L0@495 N·3 S·253B "sna..Num,v642":"sna..Set,v643" -18:12:13.457204 version@stat F·[15 1] S·3KiB[3KiB 252B] Sc·[3.75 0.00] -18:12:13.464955 db@janitor F·18 G·0 -18:12:13.464970 db@open done T·12.8039ms -18:12:13.465009 table@compaction L0·15 -> L1·1 S·3KiB Q·644 -18:12:13.466475 db@close closing -18:12:13.466922 table@build created L1@498 N·3 S·253B "sna..Num,v642":"sna..Set,v643" -18:12:13.466932 table@build exiting -18:12:13.466937 table@build revert @498 -18:12:13.466998 db@write was delayed N·1 T·1.143ms -18:12:13.467045 db@close done T·570.1µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.467298 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.467382 version@stat F·[15 1] S·3KiB[3KiB 252B] Sc·[3.75 0.00] -18:12:13.467409 db@open opening -18:12:13.467453 journal@recovery F·1 -18:12:13.467832 journal@recovery recovering @496 -18:12:13.470113 memdb@flush created L0@498 N·3 S·252B "sna..Num,v646":"sna..Set,v647" -18:12:13.470507 version@stat F·[16 1] S·4KiB[3KiB 252B] Sc·[4.00 0.00] -18:12:13.476120 db@janitor F·19 G·0 -18:12:13.476149 db@open done T·8.7328ms -18:12:13.476199 table@compaction L0·16 -> L1·1 S·4KiB Q·648 -18:12:13.477645 db@close closing -18:12:13.478193 table@build created L1@501 N·3 S·252B "sna..Num,v646":"sna..Set,v647" -18:12:13.478206 table@build exiting -18:12:13.478210 table@build revert @501 -18:12:13.478275 db@write was delayed N·1 T·1.1281ms -18:12:13.478333 db@close done T·690.1µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.478560 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.478678 version@stat F·[16 1] S·4KiB[3KiB 252B] Sc·[4.00 0.00] -18:12:13.478708 db@open opening -18:12:13.478758 journal@recovery F·1 -18:12:13.479130 journal@recovery recovering @499 -18:12:13.481102 memdb@flush created L0@501 N·3 S·252B "sna..Num,v650":"sna..Set,v651" -18:12:13.481489 version@stat F·[17 1] S·4KiB[4KiB 252B] Sc·[4.25 0.00] -18:12:13.487169 db@janitor F·20 G·0 -18:12:13.487200 db@open done T·8.4843ms -18:12:13.487243 table@compaction L0·17 -> L1·1 S·4KiB Q·652 -18:12:13.488803 db@close closing -18:12:13.489424 table@build created L1@504 N·3 S·252B "sna..Num,v650":"sna..Set,v651" -18:12:13.489438 table@build exiting -18:12:13.489443 table@build revert @504 -18:12:13.489505 db@write was delayed N·1 T·1.1879ms -18:12:13.489564 db@close done T·762.9µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.489803 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.489905 version@stat F·[17 1] S·4KiB[4KiB 252B] Sc·[4.25 0.00] -18:12:13.489932 db@open opening -18:12:13.489979 journal@recovery F·1 -18:12:13.490302 journal@recovery recovering @502 -18:12:13.492282 memdb@flush created L0@504 N·3 S·252B "sna..Num,v654":"sna..Set,v655" -18:12:13.492719 version@stat F·[18 1] S·4KiB[4KiB 252B] Sc·[4.50 0.00] -18:12:13.498369 db@janitor F·21 G·0 -18:12:13.498398 db@open done T·8.4582ms -18:12:13.498479 table@compaction L0·18 -> L1·1 S·4KiB Q·656 -18:12:13.499982 db@close closing -18:12:13.500471 table@build created L1@507 N·3 S·252B "sna..Num,v654":"sna..Set,v655" -18:12:13.500483 table@build exiting -18:12:13.500488 table@build revert @507 -18:12:13.500550 db@write was delayed N·1 T·1.0713ms -18:12:13.500635 db@close done T·653.3µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.500839 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.500924 version@stat F·[18 1] S·4KiB[4KiB 252B] Sc·[4.50 0.00] -18:12:13.500952 db@open opening -18:12:13.501000 journal@recovery F·1 -18:12:13.501324 journal@recovery recovering @505 -18:12:13.503211 memdb@flush created L0@507 N·3 S·252B "sna..Num,v658":"sna..Set,v659" -18:12:13.503746 version@stat F·[19 1] S·4KiB[4KiB 252B] Sc·[4.75 0.00] -18:12:13.509352 db@janitor F·22 G·0 -18:12:13.509382 db@open done T·8.4218ms -18:12:13.509431 table@compaction L0·19 -> L1·1 S·4KiB Q·660 -18:12:13.511433 table@build created L1@510 N·3 S·252B "sna..Num,v658":"sna..Set,v659" -18:12:13.511459 db@close closing -18:12:13.511467 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] -18:12:13.513094 table@commit exiting -18:12:13.513133 db@write was delayed N·1 T·1.5512ms -18:12:13.513189 table@remove removed @504 -18:12:13.513276 db@close done T·1.8171ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.520157 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.520252 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] -18:12:13.520280 db@open opening -18:12:13.520326 journal@recovery F·1 -18:12:13.520799 journal@recovery recovering @508 -18:12:13.523545 memdb@flush created L0@511 N·3 S·253B "sna..Num,v662":"sna..Set,v663" -18:12:13.524003 version@stat F·[1 1] S·505B[253B 252B] Sc·[0.25 0.00] -18:12:13.530794 db@janitor F·23 G·19 -18:12:13.530805 db@janitor removing table-498 -18:12:13.530825 db@janitor removing table-480 -18:12:13.530845 db@janitor removing table-489 -18:12:13.530861 db@janitor removing table-483 -18:12:13.530877 db@janitor removing table-474 -18:12:13.530900 db@janitor removing table-486 -18:12:13.530920 db@janitor removing table-465 -18:12:13.530936 db@janitor removing table-501 -18:12:13.530953 db@janitor removing table-456 -18:12:13.530969 db@janitor removing table-471 -18:12:13.530985 db@janitor removing table-453 -18:12:13.531000 db@janitor removing table-495 -18:12:13.531016 db@janitor removing table-492 -18:12:13.531031 db@janitor removing table-507 -18:12:13.531046 db@janitor removing table-462 -18:12:13.531079 db@janitor removing table-468 -18:12:13.531102 db@janitor removing table-477 -18:12:13.531119 db@janitor removing table-459 -18:12:13.531134 db@janitor removing table-452 -18:12:13.531171 db@open done T·10.8834ms -18:12:13.531314 db@close closing -18:12:13.531356 db@close done T·40µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.531541 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.531612 version@stat F·[1 1] S·505B[253B 252B] Sc·[0.25 0.00] -18:12:13.531640 db@open opening -18:12:13.531678 journal@recovery F·1 -18:12:13.532033 journal@recovery recovering @512 -18:12:13.534859 memdb@flush created L0@514 N·3 S·252B "sna..Num,v666":"sna..Set,v667" -18:12:13.535225 version@stat F·[2 1] S·757B[505B 252B] Sc·[0.50 0.00] -18:12:13.541602 db@janitor F·5 G·0 -18:12:13.541617 db@open done T·9.9698ms -18:12:13.541749 db@close closing -18:12:13.541800 db@close done T·49.7µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.542029 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.542099 version@stat F·[2 1] S·757B[505B 252B] Sc·[0.50 0.00] -18:12:13.542125 db@open opening -18:12:13.542156 journal@recovery F·1 -18:12:13.542505 journal@recovery recovering @515 -18:12:13.545489 memdb@flush created L0@517 N·3 S·252B "sna..Num,v670":"sna..Set,v671" -18:12:13.545889 version@stat F·[3 1] S·1009B[757B 252B] Sc·[0.75 0.00] -18:12:13.553258 db@janitor F·6 G·0 -18:12:13.553271 db@open done T·11.139ms -18:12:13.553450 db@close closing -18:12:13.553495 db@close done T·45.3µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.553696 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.553762 version@stat F·[3 1] S·1009B[757B 252B] Sc·[0.75 0.00] -18:12:13.553791 db@open opening -18:12:13.553821 journal@recovery F·1 -18:12:13.554120 journal@recovery recovering @518 -18:12:13.556794 memdb@flush created L0@520 N·3 S·252B "sna..Num,v674":"sna..Set,v675" -18:12:13.557267 version@stat F·[4 1] S·1KiB[1009B 252B] Sc·[1.00 0.00] -18:12:13.563417 db@janitor F·7 G·0 -18:12:13.563454 db@open done T·9.6554ms -18:12:13.563504 table@compaction L0·4 -> L1·1 S·1KiB Q·676 -18:12:13.563670 db@close closing -18:12:13.563728 db@close done T·57.3µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.563920 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.563985 version@stat F·[4 1] S·1KiB[1009B 252B] Sc·[1.00 0.00] -18:12:13.564015 db@open opening -18:12:13.564047 journal@recovery F·1 -18:12:13.564401 journal@recovery recovering @521 -18:12:13.566694 memdb@flush created L0@523 N·3 S·252B "sna..Num,v678":"sna..Set,v679" -18:12:13.567189 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] -18:12:13.573284 db@janitor F·8 G·0 -18:12:13.573299 db@open done T·9.2764ms -18:12:13.573363 table@compaction L0·5 -> L1·1 S·1KiB Q·680 -18:12:13.573592 db@close closing -18:12:13.575892 table@build created L1@526 N·3 S·252B "sna..Num,v678":"sna..Set,v679" -18:12:13.575913 table@build exiting -18:12:13.575928 table@build revert @526 -18:12:13.576024 db@close done T·2.4317ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.584056 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.584146 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] -18:12:13.584172 db@open opening -18:12:13.584202 journal@recovery F·1 -18:12:13.584616 journal@recovery recovering @524 -18:12:13.588786 memdb@flush created L0@526 N·3 S·253B "sna..Num,v682":"sna..Set,v683" -18:12:13.589296 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] -18:12:13.596244 db@janitor F·9 G·0 -18:12:13.596274 db@open done T·12.0946ms -18:12:13.596342 table@compaction L0·6 -> L1·1 S·1KiB Q·684 -18:12:13.596587 db@close closing -18:12:13.598276 table@build created L1@529 N·3 S·253B "sna..Num,v682":"sna..Set,v683" -18:12:13.598289 table@build exiting -18:12:13.598293 table@build revert @529 -18:12:13.598398 db@close done T·1.8105ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.598593 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.598672 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] -18:12:13.598705 db@open opening -18:12:13.598739 journal@recovery F·1 -18:12:13.599060 journal@recovery recovering @527 -18:12:13.601141 memdb@flush created L0@529 N·3 S·252B "sna..Num,v686":"sna..Set,v687" -18:12:13.601534 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] -18:12:13.606933 db@janitor F·10 G·0 -18:12:13.606947 db@open done T·8.2347ms -18:12:13.607001 table@compaction L0·7 -> L1·1 S·1KiB Q·688 -18:12:13.607387 db@close closing -18:12:13.609169 table@build created L1@532 N·3 S·252B "sna..Num,v686":"sna..Set,v687" -18:12:13.609185 table@build exiting -18:12:13.609190 table@build revert @532 -18:12:13.609280 db@close done T·1.8923ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.609491 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.609587 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] -18:12:13.609623 db@open opening -18:12:13.609673 journal@recovery F·1 -18:12:13.610004 journal@recovery recovering @530 -18:12:13.612151 memdb@flush created L0@532 N·3 S·252B "sna..Num,v690":"sna..Set,v691" -18:12:13.612518 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] -18:12:13.617923 db@janitor F·11 G·0 -18:12:13.617950 db@open done T·8.3201ms -18:12:13.618020 table@compaction L0·8 -> L1·1 S·2KiB Q·692 -18:12:13.619441 db@close closing -18:12:13.619890 table@build created L1@535 N·3 S·252B "sna..Num,v690":"sna..Set,v691" -18:12:13.619902 table@build exiting -18:12:13.619907 table@build revert @535 -18:12:13.619972 db@write was delayed N·1 T·1.1781ms -18:12:13.620006 db@close done T·566.7µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.620279 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.620411 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] -18:12:13.620441 db@open opening -18:12:13.620482 journal@recovery F·1 -18:12:13.620904 journal@recovery recovering @533 -18:12:13.623183 memdb@flush created L0@535 N·3 S·252B "sna..Num,v694":"sna..Set,v695" -18:12:13.623538 version@stat F·[9 1] S·2KiB[2KiB 252B] Sc·[2.25 0.00] -18:12:13.628720 db@janitor F·12 G·0 -18:12:13.628731 db@open done T·8.282ms -18:12:13.628772 table@compaction L0·9 -> L1·1 S·2KiB Q·696 -18:12:13.630561 db@close closing -18:12:13.630636 table@build created L1@538 N·3 S·252B "sna..Num,v694":"sna..Set,v695" -18:12:13.630646 table@build exiting -18:12:13.630649 table@build revert @538 -18:12:13.630706 db@write was delayed N·1 T·1.5503ms -18:12:13.630752 db@close done T·192.4µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.630977 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.631054 version@stat F·[9 1] S·2KiB[2KiB 252B] Sc·[2.25 0.00] -18:12:13.631081 db@open opening -18:12:13.631118 journal@recovery F·1 -18:12:13.631434 journal@recovery recovering @536 -18:12:13.633582 memdb@flush created L0@538 N·3 S·252B "sna..Num,v698":"sna..Set,v699" -18:12:13.633935 version@stat F·[10 1] S·2KiB[2KiB 252B] Sc·[2.50 0.00] -18:12:13.639278 db@janitor F·13 G·0 -18:12:13.639290 db@open done T·8.2023ms -18:12:13.639337 table@compaction L0·10 -> L1·1 S·2KiB Q·700 -18:12:13.640576 db@close closing -18:12:13.641188 table@build created L1@541 N·3 S·252B "sna..Num,v698":"sna..Set,v699" -18:12:13.641200 table@build exiting -18:12:13.641205 table@build revert @541 -18:12:13.641288 db@write was delayed N·1 T·1.0567ms -18:12:13.641322 db@close done T·746.9µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.647662 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.647747 version@stat F·[10 1] S·2KiB[2KiB 252B] Sc·[2.50 0.00] -18:12:13.647774 db@open opening -18:12:13.647810 journal@recovery F·1 -18:12:13.648134 journal@recovery recovering @539 -18:12:13.650604 memdb@flush created L0@541 N·3 S·252B "sna..Num,v702":"sna..Set,v703" -18:12:13.650961 version@stat F·[11 1] S·2KiB[2KiB 252B] Sc·[2.75 0.00] -18:12:13.656805 db@janitor F·14 G·0 -18:12:13.656817 db@open done T·9.0366ms -18:12:13.656868 table@compaction L0·11 -> L1·1 S·2KiB Q·704 -18:12:13.658882 table@build created L1@544 N·3 S·252B "sna..Num,v702":"sna..Set,v703" -18:12:13.658928 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] -18:12:13.658981 db@close closing -18:12:13.660850 table@commit exiting -18:12:13.660870 db@write was delayed N·1 T·1.8896ms -18:12:13.660901 db@close done T·1.92ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.661124 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.661203 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] -18:12:13.661230 db@open opening -18:12:13.661269 journal@recovery F·1 -18:12:13.661584 journal@recovery recovering @542 -18:12:13.663912 memdb@flush created L0@545 N·3 S·252B "sna..Num,v706":"sna..Set,v707" -18:12:13.664560 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] -18:12:13.670467 db@janitor F·16 G·12 -18:12:13.670478 db@janitor removing table-510 -18:12:13.670499 db@janitor removing table-541 -18:12:13.670521 db@janitor removing table-535 -18:12:13.670540 db@janitor removing table-529 -18:12:13.670556 db@janitor removing table-511 -18:12:13.670573 db@janitor removing table-526 -18:12:13.670588 db@janitor removing table-532 -18:12:13.670604 db@janitor removing table-538 -18:12:13.670619 db@janitor removing table-520 -18:12:13.670636 db@janitor removing table-517 -18:12:13.670666 db@janitor removing table-514 -18:12:13.670692 db@janitor removing table-523 -18:12:13.670731 db@open done T·9.4943ms -18:12:13.670930 db@close closing -18:12:13.670964 db@close done T·33.3µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.671169 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.671238 version@stat F·[1 1] S·504B[252B 252B] Sc·[0.25 0.00] -18:12:13.671276 db@open opening -18:12:13.671306 journal@recovery F·1 -18:12:13.671618 journal@recovery recovering @546 -18:12:13.674421 memdb@flush created L0@548 N·3 S·252B "sna..Num,v710":"sna..Set,v711" -18:12:13.674777 version@stat F·[2 1] S·756B[504B 252B] Sc·[0.50 0.00] -18:12:13.680539 db@janitor F·5 G·0 -18:12:13.680565 db@open done T·9.2824ms -18:12:13.680725 db@close closing -18:12:13.680767 db@close done T·42.8µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.680959 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.681015 version@stat F·[2 1] S·756B[504B 252B] Sc·[0.50 0.00] -18:12:13.681041 db@open opening -18:12:13.681071 journal@recovery F·1 -18:12:13.681372 journal@recovery recovering @549 -18:12:13.683991 memdb@flush created L0@551 N·3 S·252B "sna..Num,v714":"sna..Set,v715" -18:12:13.684333 version@stat F·[3 1] S·1008B[756B 252B] Sc·[0.75 0.00] -18:12:13.689512 db@janitor F·6 G·0 -18:12:13.689524 db@open done T·8.476ms -18:12:13.689687 db@close closing -18:12:13.689753 db@close done T·65.8µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.689939 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.690000 version@stat F·[3 1] S·1008B[756B 252B] Sc·[0.75 0.00] -18:12:13.690027 db@open opening -18:12:13.690058 journal@recovery F·1 -18:12:13.690372 journal@recovery recovering @552 -18:12:13.692430 memdb@flush created L0@554 N·3 S·252B "sna..Num,v718":"sna..Set,v719" -18:12:13.692780 version@stat F·[4 1] S·1KiB[1008B 252B] Sc·[1.00 0.00] -18:12:13.697670 db@janitor F·7 G·0 -18:12:13.697697 db@open done T·7.6618ms -18:12:13.697737 table@compaction L0·4 -> L1·1 S·1KiB Q·720 -18:12:13.697878 db@close closing -18:12:13.697930 db@close done T·50.9µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.698116 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.698191 version@stat F·[4 1] S·1KiB[1008B 252B] Sc·[1.00 0.00] -18:12:13.698218 db@open opening -18:12:13.698254 journal@recovery F·1 -18:12:13.698556 journal@recovery recovering @555 -18:12:13.700466 memdb@flush created L0@557 N·3 S·252B "sna..Num,v722":"sna..Set,v723" -18:12:13.700800 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] -18:12:13.706055 db@janitor F·8 G·0 -18:12:13.706066 db@open done T·7.8416ms -18:12:13.706119 table@compaction L0·5 -> L1·1 S·1KiB Q·724 -18:12:13.706284 db@close closing -18:12:13.708223 table@build created L1@560 N·3 S·252B "sna..Num,v722":"sna..Set,v723" -18:12:13.708237 table@build exiting -18:12:13.708241 table@build revert @560 -18:12:13.708342 db@close done T·2.0559ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.714685 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.714757 version@stat F·[5 1] S·1KiB[1KiB 252B] Sc·[1.25 0.00] -18:12:13.714785 db@open opening -18:12:13.714818 journal@recovery F·1 -18:12:13.715140 journal@recovery recovering @558 -18:12:13.718431 memdb@flush created L0@560 N·3 S·252B "sna..Num,v726":"sna..Set,v727" -18:12:13.718860 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] -18:12:13.726820 db@janitor F·9 G·0 -18:12:13.726848 db@open done T·12.0546ms -18:12:13.726886 table@compaction L0·6 -> L1·1 S·1KiB Q·728 -18:12:13.727102 db@close closing -18:12:13.729654 table@build created L1@563 N·3 S·252B "sna..Num,v726":"sna..Set,v727" -18:12:13.729663 table@build exiting -18:12:13.729666 table@build revert @563 -18:12:13.729750 db@close done T·2.6467ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.729954 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.730031 version@stat F·[6 1] S·1KiB[1KiB 252B] Sc·[1.50 0.00] -18:12:13.730059 db@open opening -18:12:13.730107 journal@recovery F·1 -18:12:13.730432 journal@recovery recovering @561 -18:12:13.733422 memdb@flush created L0@563 N·3 S·252B "sna..Num,v730":"sna..Set,v731" -18:12:13.733816 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] -18:12:13.739233 db@janitor F·10 G·0 -18:12:13.739244 db@open done T·9.1659ms -18:12:13.739292 table@compaction L0·7 -> L1·1 S·1KiB Q·732 -18:12:13.739490 db@close closing -18:12:13.740998 table@build created L1@566 N·3 S·252B "sna..Num,v730":"sna..Set,v731" -18:12:13.741008 table@build exiting -18:12:13.741012 table@build revert @566 -18:12:13.741098 db@close done T·1.6073ms -=============== Oct 25, 2021 (CST) =============== -18:12:13.741346 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.741435 version@stat F·[7 1] S·1KiB[1KiB 252B] Sc·[1.75 0.00] -18:12:13.741502 db@open opening -18:12:13.741547 journal@recovery F·1 -18:12:13.741859 journal@recovery recovering @564 -18:12:13.743748 memdb@flush created L0@566 N·3 S·252B "sna..Num,v734":"sna..Set,v735" -18:12:13.744121 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] -18:12:13.749800 db@janitor F·11 G·0 -18:12:13.749811 db@open done T·8.3019ms -18:12:13.749855 table@compaction L0·8 -> L1·1 S·2KiB Q·736 -18:12:13.751455 db@close closing -18:12:13.751653 table@build created L1@569 N·3 S·252B "sna..Num,v734":"sna..Set,v735" -18:12:13.751662 table@build exiting -18:12:13.751666 table@build revert @569 -18:12:13.751722 db@write was delayed N·1 T·1.4042ms -18:12:13.751768 db@close done T·312.4µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.751985 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.752065 version@stat F·[8 1] S·2KiB[1KiB 252B] Sc·[2.00 0.00] -18:12:13.752094 db@open opening -18:12:13.752129 journal@recovery F·1 -18:12:13.752507 journal@recovery recovering @567 -18:12:13.754762 memdb@flush created L0@569 N·3 S·252B "sna..Num,v738":"sna..Set,v739" -18:12:13.755145 version@stat F·[9 1] S·2KiB[2KiB 252B] Sc·[2.25 0.00] -18:12:13.760700 db@janitor F·12 G·0 -18:12:13.760713 db@open done T·8.6119ms -18:12:13.760798 table@compaction L0·9 -> L1·1 S·2KiB Q·740 -18:12:13.762124 db@close closing -18:12:13.762549 table@build created L1@572 N·3 S·252B "sna..Num,v738":"sna..Set,v739" -18:12:13.762560 table@build exiting -18:12:13.762563 table@build revert @572 -18:12:13.762621 db@write was delayed N·1 T·1.1598ms -18:12:13.762657 db@close done T·532.5µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.762859 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.762933 version@stat F·[9 1] S·2KiB[2KiB 252B] Sc·[2.25 0.00] -18:12:13.762961 db@open opening -18:12:13.762997 journal@recovery F·1 -18:12:13.763306 journal@recovery recovering @570 -18:12:13.765767 memdb@flush created L0@572 N·3 S·252B "sna..Num,v742":"sna..Set,v743" -18:12:13.766116 version@stat F·[10 1] S·2KiB[2KiB 252B] Sc·[2.50 0.00] -18:12:13.771601 db@janitor F·13 G·0 -18:12:13.771613 db@open done T·8.6454ms -18:12:13.771729 table@compaction L0·10 -> L1·1 S·2KiB Q·744 -18:12:13.773114 db@close closing -18:12:13.773722 table@build created L1@575 N·3 S·252B "sna..Num,v742":"sna..Set,v743" -18:12:13.773732 table@build exiting -18:12:13.773736 table@build revert @575 -18:12:13.773791 db@write was delayed N·1 T·1.1954ms -18:12:13.773839 db@close done T·725.3µs -=============== Oct 25, 2021 (CST) =============== -18:12:13.774038 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -18:12:13.774114 version@stat F·[10 1] S·2KiB[2KiB 252B] Sc·[2.50 0.00] -18:12:13.774141 db@open opening -18:12:13.774180 journal@recovery F·1 -18:12:13.774511 journal@recovery recovering @573 -18:12:13.776383 memdb@flush created L0@575 N·3 S·252B "sna..Num,v746":"sna..Set,v747" -18:12:13.776758 version@stat F·[11 1] S·2KiB[2KiB 252B] Sc·[2.75 0.00] -18:12:13.782069 db@janitor F·14 G·0 -18:12:13.782082 db@open done T·7.9343ms -18:12:13.782104 table@compaction L0·11 -> L1·1 S·2KiB Q·748 -18:12:13.783868 table@build created L1@578 N·3 S·252B "sna..Num,v746":"sna..Set,v747" -18:12:13.783911 version@stat F·[0 1] S·252B[0B 252B] Sc·[0.00 0.00] -18:12:13.784001 db@close closing -18:12:13.785735 table@commit exiting -18:12:13.785776 db@write was delayed N·1 T·1.5634ms -18:12:13.785831 db@close done T·1.8303ms diff --git a/p2p/simulations/base/MANIFEST-000577 b/p2p/simulations/base/MANIFEST-000577 deleted file mode 100644 index f424ccf55ba58a34433fb19178d5e9ab9dd19576..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1116 zcmb``!Ae3w6vpv6-tqbl(V{2lA#_&|guTEO2MW|HbH}6?v2B7N2!bF;f@Ita1qB5L z2?+)T3DqOC=!BkC_0gh%|7wQcoXtu0wx_2a(OStIno_Z_nLTo*eh`^lSgr;(aG_0| zYcJONJ)KtTQ6;EG`=Kiqay4sB#pR8oc(b zYcX2TxIo6G7)@xz$VgTOoj~IZ8RO;U?v|y7A}`UuE$8YppO@2DBW}nIxIWk8x?G2A Sb1kl^eI6`BF&yqM%lru@FkLGE From ecf646ad4f40f3e0033d69b6338bdcd02567df30 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 27 Oct 2021 14:45:09 +0800 Subject: [PATCH 010/299] pubsub --- cmd/alaya/config.go | 1 + cmd/utils/flags.go | 21 +++ consensus/cbft/cbft.go | 4 +- consensus/cbft/validator/validator.go | 52 +++++- consensus/cbft/validator/validator_test.go | 87 ++++++++++ core/cbfttypes/type.go | 182 +++++++++++++++------ eth/backend.go | 3 - node/node.go | 2 + p2p/pubsub/pubsub.go | 76 +++++++++ p2p/pubsub/server.go | 24 +++ 10 files changed, 400 insertions(+), 52 deletions(-) create mode 100644 p2p/pubsub/pubsub.go create mode 100644 p2p/pubsub/server.go diff --git a/cmd/alaya/config.go b/cmd/alaya/config.go index e5cb2fc417..c47647b10f 100644 --- a/cmd/alaya/config.go +++ b/cmd/alaya/config.go @@ -181,6 +181,7 @@ func makeFullNode(ctx *cli.Context) *node.Node { snapshotdb.SetDBPathWithNode(stack.ResolvePath(snapshotdb.DBPath)) utils.RegisterEthService(stack, &cfg.Eth) + utils.RegisterPubSubService(stack, &cfg.Eth) // Add the Ethereum Stats daemon if requested. if cfg.Ethstats.URL != "" { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index fde02fa7f1..b31c6b1ed7 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1207,6 +1207,27 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { } } +// RegisterPubSubService adds a PubSub client to the stack. +func RegisterPubSubService(stack *node.Node, cfg *eth.Config) { + var err error + err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { + fullNode, err := eth.New(ctx, cfg) + if err == nil { + stack.ChainID = fullNode.APIBackend.ChainConfig().ChainID + } + + if fullNode != nil && cfg.LightServ > 0 { + ls, _ := les.NewLesServer(fullNode, cfg) + fullNode.AddLesServer(ls) + } + return fullNode, err + }) + + if err != nil { + Fatalf("Failed to register the Alaya-Go service: %v", err) + } +} + // RegisterShhService configures Whisper and adds it to the given node. //func RegisterShhService(stack *node.Node, cfg *whisper.Config) { // if err := stack.Register(func(n *node.ServiceContext) (node.Service, error) { diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 6624ce7cbe..5908347efe 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -22,6 +22,7 @@ import ( "crypto/elliptic" "encoding/json" "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "strings" "sync/atomic" @@ -123,6 +124,7 @@ type Cbft struct { evPool evidence.EvidencePool log log.Logger network *network.EngineManager + pubsub *pubsub.PubSub start int32 syncing int32 @@ -871,7 +873,7 @@ func (cbft *Cbft) APIs(chain consensus.ChainReader) []rpc.API { // Protocols return consensus engine to provide protocol information. func (cbft *Cbft) Protocols() []p2p.Protocol { - return cbft.network.Protocols() + return append(cbft.network.Protocols(), cbft.pubsub.Protocols()) } // NextBaseBlock is used to calculate the next block. diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index d05bc9d4f3..a9a10eacad 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -298,8 +298,12 @@ type ValidatorPool struct { // Current node's public key nodeID discover.NodeID + // The group ID of the current node + groupID uint32 + // Uint ID of the group where the current node is located + unitID uint32 - // A block number which validators switch point. + // A block number which validators switch to current. switchPoint uint64 lastNumber uint64 @@ -307,7 +311,6 @@ type ValidatorPool struct { prevValidators *cbfttypes.Validators // Previous validators currentValidators *cbfttypes.Validators // Current validators - } // NewValidatorPool new a validator pool. @@ -338,6 +341,8 @@ func NewValidatorPool(agency consensus.Agency, blockNumber uint64, epoch uint64, pool.switchPoint = pool.currentValidators.ValidBlockNumber - 1 } + pool.groupID = pool.currentValidators.GroupID(nodeID) + pool.unitID = pool.currentValidators.UnitID(nodeID) log.Debug("Update validator", "validators", pool.currentValidators.String(), "switchpoint", pool.switchPoint, "epoch", pool.epoch, "lastNumber", pool.lastNumber) return pool } @@ -684,6 +689,49 @@ func (vp *ValidatorPool) Commit(block *types.Block) error { return vp.agency.OnCommit(block) } +// GetGroupNodes return GroupValidators according epoch +func (vp *ValidatorPool) GetGroupNodes(epoch uint64) []*cbfttypes.GroupValidators { + if epoch > vp.epoch { + panic(fmt.Sprintf("get unknown epoch, current:%d, request:%d", vp.epoch, epoch)) + } + if epoch+1 == vp.epoch { + return vp.prevValidators.GroupNodes + } + return vp.currentValidators.GroupNodes +} + +// GetGroupID return GroupID according epoch & NodeID +func (vp *ValidatorPool) GetGroupID(epoch uint64, nodeID discover.NodeID) (uint32, error) { + if epoch > vp.epoch { + panic(fmt.Sprintf("get unknown epoch, current:%d, request:%d", vp.epoch, epoch)) + } + if epoch+1 == vp.epoch { + return vp.prevValidators.GroupID(nodeID), nil + } + return vp.groupID, nil +} + +// GroupID return current node's GroupID according epoch +func (vp *ValidatorPool) GroupID(epoch uint64) (uint32, error) { + return vp.GetGroupID(epoch,vp.nodeID) +} + +// GetUnitID return index according epoch & NodeID +func (vp *ValidatorPool) GetUnitID(epoch uint64, nodeID discover.NodeID) (uint32, error) { + if epoch > vp.epoch { + panic(fmt.Sprintf("get unknown epoch, current:%d, request:%d", vp.epoch, epoch)) + } + if epoch+1 == vp.epoch { + return vp.prevValidators.UnitID(nodeID), nil + } + return vp.unitID, nil +} + +// UnitID return current node's index according epoch +func (vp *ValidatorPool) UnitID(epoch uint64) (uint32, error) { + return vp.GetUnitID(epoch,vp.nodeID) +} + func NextRound(blockNumber uint64) uint64 { return blockNumber + 1 } diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index faf4f9c770..4e475d9c81 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -130,6 +130,29 @@ func newTestNode3() []params.CbftNode { return nodes } +func newTestNodeByNum(num int) []params.CbftNode { + nodes := make([]params.CbftNode, 0, num) + + randomID := func() discover.NodeID { + ecdsaKey, _ := crypto.GenerateKey() + publicKey := hex.EncodeToString(crypto.FromECDSAPub(&ecdsaKey.PublicKey)[1:]) + return discover.MustHexID(publicKey) + } + + for i := 0; i < num; i++ { + var sec bls.SecretKey + sec.SetByCSPRNG() + node := params.CbftNode{ + Node: discover.Node{ + ID: randomID(), + }, + BlsPubKey: *sec.GetPublicKey(), + } + nodes = append(nodes, node) + } + return nodes +} + func TestValidators(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNode() @@ -596,3 +619,67 @@ func TestValidatorPoolReset(t *testing.T) { assert.Equal(t, vp.epoch, uint64(15)) assert.Equal(t, vp.switchPoint, uint64(149)) } + +func TestValidatorGrouped(t *testing.T) { + nodes := newTestNodeByNum(100) + vs := newValidators(nodes, 0) + vs.Grouped(25, 5) + assert.Equal(t, 4, len(vs.GroupNodes)) + assert.Equal(t, 25, len(vs.GroupNodes[3].Nodes)) + assert.Equal(t, uint32(74), vs.GroupNodes[2].Nodes[24].Index) + assert.Equal(t, 5, len(vs.GroupNodes[2].Units)) + assert.Equal(t, uint32(74), vs.GroupNodes[2].Units[4][4]) + + vs.Grouped(28, 5) + assert.Equal(t, 4, len(vs.GroupNodes)) + assert.Equal(t, 25, len(vs.GroupNodes[0].Nodes)) + assert.Equal(t, 25, len(vs.GroupNodes[2].Nodes)) + assert.Equal(t, 25, len(vs.GroupNodes[3].Nodes)) + assert.Equal(t, uint32(79), vs.GroupNodes[3].Units[0][4]) + + vs.Grouped(19, 3) + assert.Equal(t, 6, len(vs.GroupNodes)) + assert.Equal(t, 17, len(vs.GroupNodes[0].Nodes)) + assert.Equal(t, 6, len(vs.GroupNodes[2].Units)) + assert.Equal(t, uint32(48), vs.GroupNodes[2].Units[4][2]) +} + +func TestGetGroupID(t *testing.T) { + bls.Init(bls.BLS12_381) + nodes := newTestNodeByNum(100) + agency := newTestInnerAgency(nodes) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID) + + grpID,_ := vp.GetGroupID(0, nodes[0].Node.ID) + assert.Equal(t, 0, grpID) +} + +func TestGetUintID(t *testing.T) { + bls.Init(bls.BLS12_381) + nodes := newTestNodeByNum(100) + agency := newTestInnerAgency(nodes) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID) + + untID,_ := vp.GetGroupID(0, nodes[0].Node.ID) + assert.Equal(t, 0, untID) +} + +func TestXXXX(t *testing.T) { + c1 := make(chan int, 1) + c2 := make(chan int, 1) + go func() { + c1 <- 1 + c2 <- 2 + }() + + for { + select { + case <-c2: + fmt.Println("c2 ready") + case <-c1: + fmt.Println("c1 ready") + //default: + // fmt.Println("default") + } + } +} diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 433d5bedc5..1e80e6f16c 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -24,6 +24,7 @@ import ( "errors" "fmt" "github.com/AlayaNetwork/Alaya-Go/common/hexutil" + "github.com/AlayaNetwork/Alaya-Go/log" "math" "math/big" "sort" @@ -65,28 +66,6 @@ type CbftResult struct { ChainStateUpdateCB func() } -type ProducerState struct { - count int - miner common.Address -} - -func (ps *ProducerState) Add(miner common.Address) { - if ps.miner == miner { - ps.count++ - } else { - ps.miner = miner - ps.count = 1 - } -} - -func (ps *ProducerState) Get() (common.Address, int) { - return ps.miner, ps.count -} - -func (ps *ProducerState) Validate(period int) bool { - return ps.count < period -} - type AddValidatorEvent struct { NodeID discover.NodeID } @@ -103,21 +82,31 @@ type ValidateNode struct { PubKey *ecdsa.PublicKey `json:"-"` NodeID discover.NodeID `json:"nodeID"` BlsPubKey *bls.PublicKey `json:"blsPubKey"` + Distance int } type ValidateNodeMap map[discover.NodeID]*ValidateNode -type SortedValidatorNode []*ValidateNode +type SortedIndexValidatorNode []*ValidateNode + +func (sv SortedIndexValidatorNode) Len() int { return len(sv) } +func (sv SortedIndexValidatorNode) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] } +func (sv SortedIndexValidatorNode) Less(i, j int) bool { return sv[i].Index < sv[j].Index } -func (sv SortedValidatorNode) Len() int { return len(sv) } -func (sv SortedValidatorNode) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] } -func (sv SortedValidatorNode) Less(i, j int) bool { return sv[i].Index < sv[j].Index } +type GroupValidators struct { + Nodes []*ValidateNode + Units [][]uint32 +} type Validators struct { Nodes ValidateNodeMap `json:"validateNodes"` ValidBlockNumber uint64 `json:"validateBlockNumber"` - sortedNodes SortedValidatorNode + // Sorting based on node index + SortedNodes SortedIndexValidatorNode `json:"sortedNodes"` + //// Sorting based on node index + // Node grouping info + GroupNodes []*GroupValidators `json:"groupNodes"` } func (vn *ValidateNode) String() string { @@ -160,31 +149,31 @@ func (vs *Validators) NodeList() []discover.NodeID { } func (vs *Validators) NodeListByIndexes(indexes []uint32) ([]*ValidateNode, error) { - if len(vs.sortedNodes) == 0 { - vs.sort() + if len(vs.SortedNodes) == 0 { + vs.Sort() } l := make([]*ValidateNode, 0) for _, index := range indexes { - if int(index) >= len(vs.sortedNodes) { + if int(index) >= len(vs.SortedNodes) { return nil, errors.New("invalid index") } - l = append(l, vs.sortedNodes[int(index)]) + l = append(l, vs.SortedNodes[int(index)]) } return l, nil } func (vs *Validators) NodeListByBitArray(vSet *utils.BitArray) ([]*ValidateNode, error) { - if len(vs.sortedNodes) == 0 { - vs.sort() + if len(vs.SortedNodes) == 0 { + vs.Sort() } l := make([]*ValidateNode, 0) for index := uint32(0); index < vSet.Size(); index++ { if vSet.GetIndex(index) { - if int(index) >= len(vs.sortedNodes) { + if int(index) >= len(vs.SortedNodes) { return nil, errors.New("invalid index") } - l = append(l, vs.sortedNodes[int(index)]) + l = append(l, vs.SortedNodes[int(index)]) } } return l, nil @@ -199,13 +188,13 @@ func (vs *Validators) FindNodeByID(id discover.NodeID) (*ValidateNode, error) { } func (vs *Validators) FindNodeByIndex(index int) (*ValidateNode, error) { - if len(vs.sortedNodes) == 0 { - vs.sort() + if len(vs.SortedNodes) == 0 { + vs.Sort() } - if index >= len(vs.sortedNodes) { + if index >= len(vs.SortedNodes) { return nil, errors.New("not found the specified validator") } else { - return vs.sortedNodes[index], nil + return vs.SortedNodes[index], nil } } @@ -219,13 +208,13 @@ func (vs *Validators) FindNodeByAddress(addr common.NodeAddress) (*ValidateNode, } func (vs *Validators) NodeID(idx int) discover.NodeID { - if len(vs.sortedNodes) == 0 { - vs.sort() + if len(vs.SortedNodes) == 0 { + vs.Sort() } - if idx >= vs.sortedNodes.Len() { + if idx >= vs.SortedNodes.Len() { return discover.NodeID{} } - return vs.sortedNodes[idx].NodeID + return vs.SortedNodes[idx].NodeID } func (vs *Validators) Index(nodeID discover.NodeID) (uint32, error) { @@ -254,9 +243,110 @@ func (vs *Validators) Equal(rsh *Validators) bool { return equal } -func (vs *Validators) sort() { +func (vs *Validators) Sort() { for _, node := range vs.Nodes { - vs.sortedNodes = append(vs.sortedNodes, node) + vs.SortedNodes = append(vs.SortedNodes, node) + } + sort.Sort(vs.SortedNodes) +} + +func (vs *Validators) GroupID(nodeID discover.NodeID) uint32 { + if len(vs.SortedNodes) == 0 { + vs.Sort() + } + + idx, err := vs.Index(nodeID) + if err != nil { + log.Error("get preValidator index failed!", "err", err) + return idx + } + + groupID := uint32(0) + for _, group := range vs.GroupNodes { + goupLen := len(group.Nodes) + if idx <= group.Nodes[goupLen-1].Index { + break + } else { + groupID = groupID + 1 + } + } + return groupID +} + +func (vs *Validators) UnitID(nodeID discover.NodeID) uint32 { + if len(vs.SortedNodes) == 0 { + vs.Sort() + } + + idx, err := vs.Index(nodeID) + if err != nil { + log.Error("get preValidator index failed!", "err", err) + return idx + } + + groupID := vs.GroupID(nodeID) + unitID := uint32(0) + for i, node := range vs.GroupNodes[groupID].Nodes { + if idx == node.Index { + unitID = uint32(i) + } + } + return unitID +} + +func (gvs *GroupValidators) GroupedUnits(coordinatorLimit int) error { + unit := make([]uint32, 0, coordinatorLimit) + for i, n := range gvs.Nodes { + unit = append(unit, n.Index) + if len(unit) >= coordinatorLimit || i == len(gvs.Nodes)-1 { + gvs.Units = append(gvs.Units, unit) + unit = make([]uint32, 0, coordinatorLimit) + } + } + return nil +} + +// Grouped fill validators into groups +// groupValidatorsLimit is a factor to determine how many groups are grouped +// eg: [validatorCount,groupValidatorsLimit]= +// [50,25] = 25,25;[43,25] = 22,21; [101,25] = 21,20,20,20,20 +func (vs *Validators) Grouped(groupValidatorsLimit int, coordinatorLimit int) error { + // sort nodes by index + if len(vs.SortedNodes) == 0 { + vs.Sort() + } + + validatorCount := vs.SortedNodes.Len() + groupNum := validatorCount / groupValidatorsLimit + mod := validatorCount % groupValidatorsLimit + if mod > 0 { + groupNum = groupNum +1 + } + + memberMinCount := validatorCount / groupNum + remainder := validatorCount % groupNum + vs.GroupNodes = make([]*GroupValidators, groupNum, groupNum) + begin := 0 + end := 0 + for i := 0; i < groupNum; i++ { + begin = end + if remainder > 0 { + end = begin + memberMinCount + 1 + remainder = remainder - 1 + } else { + end = begin + memberMinCount + } + if end > validatorCount { + end = validatorCount + } + groupValidators := new(GroupValidators) + groupValidators.Nodes = vs.SortedNodes[begin:end] + vs.GroupNodes[i] = groupValidators } - sort.Sort(vs.sortedNodes) + + // fill group unit + for _, gvs := range vs.GroupNodes { + gvs.GroupedUnits(coordinatorLimit) + } + return nil } diff --git a/eth/backend.go b/eth/backend.go index d96bc64d06..004af4b870 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -79,9 +79,6 @@ type Ethereum struct { blockchain *core.BlockChain protocolManager *ProtocolManager lesServer LesServer - // modify - //mpcPool *core.MPCPool - //vcPool *core.VCPool // DB interfaces chainDb ethdb.Database // Block chain database diff --git a/node/node.go b/node/node.go index 15baf81a31..f5bf977156 100644 --- a/node/node.go +++ b/node/node.go @@ -19,6 +19,7 @@ package node import ( "errors" "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "math/big" "net" "os" @@ -55,6 +56,7 @@ type Node struct { ChainID *big.Int `toml:"-"` serverConfig p2p.Config server *p2p.Server // Currently running P2P networking layer + pubsub *pubsub.Server // Currently running pubsub networking layer serviceFuncs []ServiceConstructor // Service constructors (in dependency order) services map[reflect.Type]Service // Currently running services diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go new file mode 100644 index 0000000000..1ecae8776b --- /dev/null +++ b/p2p/pubsub/pubsub.go @@ -0,0 +1,76 @@ +// Copyright 2021 The Alaya Network Authors +// This file is part of the Alaya-Go library. +// +// The Alaya-Go 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 Alaya-Go 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 Alaya-Go library. If not, see . + + +package pubsub + +import ( + "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p" + "github.com/AlayaNetwork/Alaya-Go/p2p/discover" +) + +const ( + + // CbftPubSubProtocolName is protocol name of CBFT.PubSub + CbftPubSubProtocolName = "cbft.pubsub" + + // CbftPubSubProtocolVersion is protocol version of CBFT.PubSub + CbftPubSubProtocolVersion = 1 + + // CbftPubSubProtocolLength are the number of implemented message corresponding to cbft.pubsub protocol versions. + CbftPubSubProtocolLength = 40 +) + +// PubSub is the implementation of the pubsub system. +type PubSub struct { + // atomic counter for seqnos + // NOTE: Must be declared at the top of the struct as we perform atomic + // operations on this field. + // + // See: https://golang.org/pkg/sync/atomic/#pkg-note-BUG + counter uint64 + + // maxMessageSize is the maximum message size; it applies globally to all + // topics. + maxMessageSize int + + // size of the outbound message channel that we maintain for each peer + peerOutboundQueueSize int +} + +// Protocols return consensus engine to provide protocol information. +func (p *PubSub) Protocols() []p2p.Protocol { + return []p2p.Protocol{ + { + Name: CbftPubSubProtocolName, + Version: CbftPubSubProtocolVersion, + Length: CbftPubSubProtocolLength, + Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + return p.handler(p, rw) + }, + NodeInfo: func() interface{} { + return p.NodeInfo() + }, + PeerInfo: func(id discover.NodeID) interface{} { + if p, err := p.peers.get(fmt.Sprintf("%x", id[:8])); err == nil { + return p.Info() + } + return nil + }, + }, + } +} \ No newline at end of file diff --git a/p2p/pubsub/server.go b/p2p/pubsub/server.go new file mode 100644 index 0000000000..00c20d5490 --- /dev/null +++ b/p2p/pubsub/server.go @@ -0,0 +1,24 @@ +// Copyright 2021 The Alaya Network Authors +// This file is part of the Alaya-Go library. +// +// The Alaya-Go 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 Alaya-Go 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 Alaya-Go library. If not, see . + + +package pubsub + +// Server manages all pubsub peers. +type Server struct { + p *PubSub +} + From e2732e6f42eaea5600a5ff5d14077140878a2924 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 27 Oct 2021 17:51:11 +0800 Subject: [PATCH 011/299] pubsub --- cmd/utils/flags.go | 2 +- consensus/cbft/cbft.go | 5 +++ consensus/cbft/validator/validator_test.go | 37 +++++----------------- node/node.go | 6 ++-- p2p/pubsub/pubsub.go | 2 +- p2p/pubsub/server.go | 26 +++++++++++++-- 6 files changed, 42 insertions(+), 36 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 5b018fd30a..f156eb8a1a 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1183,7 +1183,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { Fatalf("Failed to register the Alaya-Go service: %v", "not support LightSync") } else { err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - fullNode, err := eth.New(stack.Pubsub, ctx, cfg) + fullNode, err := eth.New(stack.PbSvr, ctx, cfg) if err == nil { stack.ChainID = fullNode.APIBackend.ChainConfig().ChainID } diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 522449e824..847ebd8280 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -296,6 +296,11 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCacheWriter consensus. cbft.log.Error("Load wal failed", "err", err) return err } + + if err := cbft.pbSvr.Start(); err != nil { + return err + } + utils.SetFalse(&cbft.loading) go cbft.receiveLoop() diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index e8b1981556..02797285ce 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -17,6 +17,7 @@ package validator import ( + "crypto/ecdsa" "encoding/binary" "encoding/hex" "encoding/json" @@ -133,19 +134,16 @@ func newTestNode3() []params.CbftNode { func newTestNodeByNum(num int) []params.CbftNode { nodes := make([]params.CbftNode, 0, num) - randomID := func() discover.NodeID { + randomPubKey := func() *ecdsa.PublicKey { ecdsaKey, _ := crypto.GenerateKey() - publicKey := hex.EncodeToString(crypto.FromECDSAPub(&ecdsaKey.PublicKey)[1:]) - return discover.MustHexID(publicKey) + return &ecdsaKey.PublicKey } for i := 0; i < num; i++ { var sec bls.SecretKey sec.SetByCSPRNG() node := params.CbftNode{ - Node: discover.Node{ - ID: randomID(), - }, + Node: enode.NewV4(randomPubKey(), nil, 0, 0), BlsPubKey: *sec.GetPublicKey(), } nodes = append(nodes, node) @@ -648,9 +646,9 @@ func TestGetGroupID(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNodeByNum(100) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID()) - grpID,_ := vp.GetGroupID(0, nodes[0].Node.ID) + grpID,_ := vp.GetGroupID(0, nodes[0].Node.ID()) assert.Equal(t, 0, grpID) } @@ -658,28 +656,9 @@ func TestGetUintID(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNodeByNum(100) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID()) - untID,_ := vp.GetGroupID(0, nodes[0].Node.ID) + untID,_ := vp.GetGroupID(0, nodes[0].Node.ID()) assert.Equal(t, 0, untID) } -func TestXXXX(t *testing.T) { - c1 := make(chan int, 1) - c2 := make(chan int, 1) - go func() { - c1 <- 1 - c2 <- 2 - }() - - for { - select { - case <-c2: - fmt.Println("c2 ready") - case <-c1: - fmt.Println("c1 ready") - //default: - // fmt.Println("default") - } - } -} diff --git a/node/node.go b/node/node.go index 866b3bb857..90b66da6cf 100644 --- a/node/node.go +++ b/node/node.go @@ -55,8 +55,8 @@ type Node struct { // chainId identifies the current chain and is used for replay protection ChainID *big.Int `toml:"-"` serverConfig p2p.Config - server *p2p.Server // Currently running P2P networking layer - Pubsub *pubsub.Server // Currently running pubsub networking layer + server *p2p.Server // Currently running P2P networking layer + PbSvr *pubsub.Server // Currently running pubsub networking layer serviceFuncs []ServiceConstructor // Service constructors (in dependency order) services map[reflect.Type]Service // Currently running services @@ -204,7 +204,7 @@ func (n *Node) Start() error { } running := &p2p.Server{Config: n.serverConfig} - n.Pubsub = &pubsub.Server{Pb: pubsub.NewPubSub()} + n.PbSvr = &pubsub.Server{Pb: pubsub.NewPubSub()} n.log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 44f55ebc29..eb4b20995b 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -31,7 +31,7 @@ const ( CbftPubSubProtocolVersion = 1 // CbftPubSubProtocolLength are the number of implemented message corresponding to cbft.pubsub protocol versions. - CbftPubSubProtocolLength = 40 + CbftPubSubProtocolLength = 10 // DefaultMaximumMessageSize is 1mb. DefaultMaxMessageSize = 1 << 20 diff --git a/p2p/pubsub/server.go b/p2p/pubsub/server.go index ea325dc1f1..eab496dea2 100644 --- a/p2p/pubsub/server.go +++ b/p2p/pubsub/server.go @@ -17,15 +17,37 @@ package pubsub -import "github.com/AlayaNetwork/Alaya-Go/p2p" +import ( + "errors" + "github.com/AlayaNetwork/Alaya-Go/p2p" + "sync" +) // Server manages all pubsub peers. type Server struct { + lock sync.Mutex // protects running + running bool Pb *PubSub } - // Protocols return consensus engine to provide protocol information. func (s *Server) Protocols() []p2p.Protocol { return s.Pb.Protocols() +} + +// Start starts running the server. +// Servers can not be re-used after stopping. +func (srv *Server) Start() (err error) { + srv.lock.Lock() + defer srv.lock.Unlock() + if srv.running { + return errors.New("server already running") + } + srv.running = true + return nil +} + +// run is the main loop of the server. +func (s *Server) run() { + } \ No newline at end of file From e7d9aea6b9002e341ebefc090bc58c532c35ed42 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 27 Oct 2021 18:23:27 +0800 Subject: [PATCH 012/299] pubsub --- consensus/cbft/cbft.go | 10 ++++---- consensus/cbft/network/handler.go | 29 ++++++++++++++++++++++ consensus/cbft/network/interface.go | 4 +++ p2p/pubsub/pubsub.go | 38 +---------------------------- p2p/pubsub/server.go | 13 +++++----- 5 files changed, 46 insertions(+), 48 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 847ebd8280..0c1a4a40d3 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -880,11 +880,7 @@ func (cbft *Cbft) APIs(chain consensus.ChainReader) []rpc.API { // Protocols return consensus engine to provide protocol information. func (cbft *Cbft) Protocols() []p2p.Protocol { - p := cbft.network.Protocols() - for _,sp := range cbft.pbSvr.Protocols() { - p = append(p, sp) - } - return p + return cbft.network.Protocols() } // NextBaseBlock is used to calculate the next block. @@ -1853,3 +1849,7 @@ func (cbft *Cbft) DecodeExtra(extra []byte) (common.Hash, uint64, error) { } return qc.BlockHash, qc.BlockNumber, nil } + +func (cbft *Cbft) PubSubServer() *pubsub.Server { + return cbft.pbSvr +} diff --git a/consensus/cbft/network/handler.go b/consensus/cbft/network/handler.go index ee63b2cc76..70d54b95b5 100644 --- a/consensus/cbft/network/handler.go +++ b/consensus/cbft/network/handler.go @@ -47,6 +47,18 @@ const ( // CbftProtocolLength are the number of implemented message corresponding to cbft protocol versions. CbftProtocolLength = 40 + // CbftPubSubProtocolName is protocol name of CBFT.PubSub + CbftPubSubProtocolName = "cbft.pubsub" + + // CbftPubSubProtocolVersion is protocol version of CBFT.PubSub + CbftPubSubProtocolVersion = 1 + + // CbftPubSubProtocolLength are the number of implemented message corresponding to cbft.pubsub protocol versions. + CbftPubSubProtocolLength = 10 + + // DefaultMaximumMessageSize is 1mb. + DefaultMaxMessageSize = 1 << 20 + // sendQueueSize is maximum threshold for the queue of messages waiting to be sent. sendQueueSize = 10240 @@ -306,6 +318,23 @@ func (h *EngineManager) Protocols() []p2p.Protocol { return nil }, }, + { + Name: CbftPubSubProtocolName, + Version: CbftPubSubProtocolVersion, + Length: CbftPubSubProtocolLength, + Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + return h.engine.PubSubServer().Handle(p, rw) + }, + NodeInfo: func() interface{} { + return h.NodeInfo() + }, + PeerInfo: func(id enode.ID) interface{} { + if p, err := h.peers.get(fmt.Sprintf("%x", id[:8])); err == nil { + return p.Info() + } + return nil + }, + }, } } diff --git a/consensus/cbft/network/interface.go b/consensus/cbft/network/interface.go index 0d13cd675a..9a13e84ee8 100644 --- a/consensus/cbft/network/interface.go +++ b/consensus/cbft/network/interface.go @@ -21,6 +21,7 @@ import ( "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" ) // Cbft defines the network layer to use the relevant interface @@ -67,4 +68,7 @@ type Cbft interface { // BlockExists determines if a block exists. BlockExists(blockNumber uint64, blockHash common.Hash) error + + // PubSubServer return pubsub.Server + PubSubServer() *pubsub.Server } diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index eb4b20995b..3cb145da9d 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -19,22 +19,6 @@ package pubsub import ( "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" -) - -const ( - - // CbftPubSubProtocolName is protocol name of CBFT.PubSub - CbftPubSubProtocolName = "cbft.pubsub" - - // CbftPubSubProtocolVersion is protocol version of CBFT.PubSub - CbftPubSubProtocolVersion = 1 - - // CbftPubSubProtocolLength are the number of implemented message corresponding to cbft.pubsub protocol versions. - CbftPubSubProtocolLength = 10 - - // DefaultMaximumMessageSize is 1mb. - DefaultMaxMessageSize = 1 << 20 ) // PubSub is the implementation of the pubsub system. @@ -60,31 +44,11 @@ func (pubsub *PubSub) handler(p *p2p.Peer, rw p2p.MsgReadWriter) error { return nil } -// Protocols return consensus engine to provide protocol information. -func (pubsub *PubSub) Protocols() []p2p.Protocol { - return []p2p.Protocol{ - { - Name: CbftPubSubProtocolName, - Version: CbftPubSubProtocolVersion, - Length: CbftPubSubProtocolLength, - Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - return pubsub.handler(p, rw) - }, - NodeInfo: func() interface{} { - return nil - }, - PeerInfo: func(id enode.ID) interface{} { - return nil - }, - }, - } -} - // NewPubSub returns a new PubSub management object. func NewPubSub() *PubSub { ps := &PubSub{ counter: 0, - maxMessageSize: DefaultMaxMessageSize, + maxMessageSize: 2048, peerOutboundQueueSize: 32, } return ps diff --git a/p2p/pubsub/server.go b/p2p/pubsub/server.go index eab496dea2..c433884e9c 100644 --- a/p2p/pubsub/server.go +++ b/p2p/pubsub/server.go @@ -30,11 +30,6 @@ type Server struct { Pb *PubSub } -// Protocols return consensus engine to provide protocol information. -func (s *Server) Protocols() []p2p.Protocol { - return s.Pb.Protocols() -} - // Start starts running the server. // Servers can not be re-used after stopping. func (srv *Server) Start() (err error) { @@ -50,4 +45,10 @@ func (srv *Server) Start() (err error) { // run is the main loop of the server. func (s *Server) run() { -} \ No newline at end of file +} + +// After the node is successfully connected and the message belongs +// to the cbft.pubsub protocol message, the method is called. +func (s *Server) Handle(p *p2p.Peer, rw p2p.MsgReadWriter) error { + return nil +} From 85197fe7e8eb6c6b5fab448a2a00618b41a9532a Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 29 Oct 2021 15:08:35 +0800 Subject: [PATCH 013/299] init pubsub --- go.mod | 22 +- p2p/enode/node.go | 4 + p2p/pubsub/blacklist.go | 65 + p2p/pubsub/gossip_tracer.go | 184 +++ p2p/pubsub/gossippub.go | 1904 +++++++++++++++++++++++++++++ p2p/pubsub/gossipsub_feat.go | 50 + p2p/pubsub/interfaces.go | 191 +++ p2p/pubsub/mcache.go | 104 ++ p2p/pubsub/message/mesage.go | 250 ++++ p2p/pubsub/message/trace.go | 715 +++++++++++ p2p/pubsub/notify.go | 62 + p2p/pubsub/peer_gater.go | 452 +++++++ p2p/pubsub/peerstore.go | 36 + p2p/pubsub/pubsub.go | 1361 +++++++++++++++++++++ p2p/pubsub/pubsub_common.go | 210 ++++ p2p/pubsub/score.go | 1074 ++++++++++++++++ p2p/pubsub/score_params.go | 293 +++++ p2p/pubsub/sign.go | 129 ++ p2p/pubsub/subscription.go | 51 + p2p/pubsub/subscription_filter.go | 31 + p2p/pubsub/tag_tracer.go | 263 ++++ p2p/pubsub/topic.go | 390 ++++++ p2p/pubsub/trace.go | 529 ++++++++ p2p/pubsub/tracer.go | 296 +++++ p2p/pubsub/validation.go | 533 ++++++++ 25 files changed, 9192 insertions(+), 7 deletions(-) create mode 100644 p2p/pubsub/blacklist.go create mode 100644 p2p/pubsub/gossip_tracer.go create mode 100644 p2p/pubsub/gossippub.go create mode 100644 p2p/pubsub/gossipsub_feat.go create mode 100644 p2p/pubsub/interfaces.go create mode 100644 p2p/pubsub/mcache.go create mode 100644 p2p/pubsub/message/mesage.go create mode 100644 p2p/pubsub/message/trace.go create mode 100644 p2p/pubsub/notify.go create mode 100644 p2p/pubsub/peer_gater.go create mode 100644 p2p/pubsub/peerstore.go create mode 100644 p2p/pubsub/pubsub.go create mode 100644 p2p/pubsub/pubsub_common.go create mode 100644 p2p/pubsub/score.go create mode 100644 p2p/pubsub/score_params.go create mode 100644 p2p/pubsub/sign.go create mode 100644 p2p/pubsub/subscription.go create mode 100644 p2p/pubsub/subscription_filter.go create mode 100644 p2p/pubsub/tag_tracer.go create mode 100644 p2p/pubsub/topic.go create mode 100644 p2p/pubsub/trace.go create mode 100644 p2p/pubsub/tracer.go create mode 100644 p2p/pubsub/validation.go diff --git a/go.mod b/go.mod index 4a733f77cd..e7dcae3dd4 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/PlatONnetwork/wagon v0.6.1-0.20201026015350-67507c2a7b96 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/VictoriaMetrics/fastcache v1.5.7 - github.com/btcsuite/btcd v0.20.1-beta - github.com/btcsuite/btcutil v1.0.2 + github.com/btcsuite/btcd v0.22.0-beta + github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce github.com/cespare/cp v0.1.0 github.com/cespare/xxhash v1.1.0 github.com/davecgh/go-spew v1.1.1 @@ -22,6 +22,7 @@ require ( github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 github.com/go-ole/go-ole v1.2.4 // indirect github.com/go-stack/stack v1.8.0 + github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.2 github.com/golang/snappy v0.0.1 github.com/gorilla/websocket v1.4.2 @@ -29,14 +30,21 @@ require ( github.com/holiman/uint256 v1.1.1 github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3 github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 + github.com/ipfs/go-cid v0.1.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 github.com/julienschmidt/httprouter v1.3.0 github.com/karalabe/hid v0.0.0-20170821103837-f00545f9f374 + github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/libp2p/go-libp2p-core v0.11.0 + github.com/libp2p/go-msgio v0.0.6 github.com/mattn/go-colorable v0.1.8 github.com/mattn/go-isatty v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mroth/weightedrand v0.3.0 + github.com/multiformats/go-base32 v0.0.4 // indirect + github.com/multiformats/go-multiaddr v0.4.1 // indirect + github.com/multiformats/go-multihash v0.0.16 // indirect github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/olekukonko/tablewriter v0.0.5 @@ -55,13 +63,13 @@ require ( github.com/shirou/gopsutil v2.20.5+incompatible github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d github.com/tealeg/xlsx v1.0.5 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/net v0.0.0-20200602114024-627f9648deb9 - golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf - golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984 // indirect + github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 + golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 + golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 google.golang.org/protobuf v1.27.1 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce diff --git a/p2p/enode/node.go b/p2p/enode/node.go index adff3d9774..7a31d27422 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -205,6 +205,10 @@ func (n *Node) UnmarshalText(text []byte) error { return err } +var ( + ZeroID = HexID("0x0") +) + // ID is a unique identifier for each node. type ID [32]byte diff --git a/p2p/pubsub/blacklist.go b/p2p/pubsub/blacklist.go new file mode 100644 index 0000000000..97c3898285 --- /dev/null +++ b/p2p/pubsub/blacklist.go @@ -0,0 +1,65 @@ +package pubsub + +import ( + "sync" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + "github.com/whyrusleeping/timecache" +) + +// Blacklist is an interface for peer blacklisting. +type Blacklist interface { + Add(id enode.ID) bool + Contains(enode.ID) bool +} + +// MapBlacklist is a blacklist implementation using a perfect map +type MapBlacklist map[enode.ID]struct{} + +// NewMapBlacklist creates a new MapBlacklist +func NewMapBlacklist() Blacklist { + return MapBlacklist(make(map[enode.ID]struct{})) +} + +func (b MapBlacklist) Add(p enode.ID) bool { + b[p] = struct{}{} + return true +} + +func (b MapBlacklist) Contains(p enode.ID) bool { + _, ok := b[p] + return ok +} + +// TimeCachedBlacklist is a blacklist implementation using a time cache +type TimeCachedBlacklist struct { + sync.RWMutex + tc *timecache.TimeCache +} + +// NewTimeCachedBlacklist creates a new TimeCachedBlacklist with the given expiry duration +func NewTimeCachedBlacklist(expiry time.Duration) (Blacklist, error) { + b := &TimeCachedBlacklist{tc: timecache.NewTimeCache(expiry)} + return b, nil +} + +// Add returns a bool saying whether Add of peer was successful +func (b *TimeCachedBlacklist) Add(p enode.ID) bool { + b.Lock() + defer b.Unlock() + s := p.String() + if b.tc.Has(s) { + return false + } + b.tc.Add(s) + return true +} + +func (b *TimeCachedBlacklist) Contains(p enode.ID) bool { + b.RLock() + defer b.RUnlock() + + return b.tc.Has(p.String()) +} diff --git a/p2p/pubsub/gossip_tracer.go b/p2p/pubsub/gossip_tracer.go new file mode 100644 index 0000000000..922cb99b57 --- /dev/null +++ b/p2p/pubsub/gossip_tracer.go @@ -0,0 +1,184 @@ +package pubsub + +import ( + "math/rand" + "sync" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +// gossipTracer is an internal tracer that tracks IWANT requests in order to penalize +// peers who don't follow up on IWANT requests after an IHAVE advertisement. +// The tracking of promises is probabilistic to avoid using too much memory. +type gossipTracer struct { + sync.Mutex + + msgID MsgIdFunction + + followUpTime time.Duration + + // promises for messages by message ID; for each message tracked, we track the promise + // expiration time for each peer. + promises map[string]map[enode.ID]time.Time + // promises for each peer; for each peer, we track the promised message IDs. + // this index allows us to quickly void promises when a peer is throttled. + peerPromises map[enode.ID]map[string]struct{} +} + +func newGossipTracer() *gossipTracer { + return &gossipTracer{ + msgID: DefaultMsgIdFn, + promises: make(map[string]map[enode.ID]time.Time), + peerPromises: make(map[enode.ID]map[string]struct{}), + } +} + +func (gt *gossipTracer) Start(gs *GossipSubRouter) { + if gt == nil { + return + } + + gt.msgID = gs.p.msgID + gt.followUpTime = gs.params.IWantFollowupTime +} + +// track a promise to deliver a message from a list of msgIDs we are requesting +func (gt *gossipTracer) AddPromise(p enode.ID, msgIDs []string) { + if gt == nil { + return + } + + idx := rand.Intn(len(msgIDs)) + mid := msgIDs[idx] + + gt.Lock() + defer gt.Unlock() + + promises, ok := gt.promises[mid] + if !ok { + promises = make(map[enode.ID]time.Time) + gt.promises[mid] = promises + } + + _, ok = promises[p] + if !ok { + promises[p] = time.Now().Add(gt.followUpTime) + peerPromises, ok := gt.peerPromises[p] + if !ok { + peerPromises = make(map[string]struct{}) + gt.peerPromises[p] = peerPromises + } + peerPromises[mid] = struct{}{} + } +} + +// returns the number of broken promises for each peer who didn't follow up +// on an IWANT request. +func (gt *gossipTracer) GetBrokenPromises() map[enode.ID]int { + if gt == nil { + return nil + } + + gt.Lock() + defer gt.Unlock() + + var res map[enode.ID]int + now := time.Now() + + // find broken promises from peers + for mid, promises := range gt.promises { + for p, expire := range promises { + if expire.Before(now) { + if res == nil { + res = make(map[enode.ID]int) + } + res[p]++ + + delete(promises, p) + + peerPromises := gt.peerPromises[p] + delete(peerPromises, mid) + if len(peerPromises) == 0 { + delete(gt.peerPromises, p) + } + } + } + + if len(promises) == 0 { + delete(gt.promises, mid) + } + } + + return res +} + +//var _ RawTracer = (*gossipTracer)(nil) + +func (gt *gossipTracer) fulfillPromise(msg *Message) { + mid := gt.msgID(msg.Message) + + gt.Lock() + defer gt.Unlock() + + delete(gt.promises, mid) +} + +func (gt *gossipTracer) DeliverMessage(msg *Message) { + // someone delivered a message, fulfill promises for it + gt.fulfillPromise(msg) +} + +func (gt *gossipTracer) RejectMessage(msg *Message, reason string) { + // A message got rejected, so we can fulfill promises and let the score penalty apply + // from invalid message delivery. + // We do take exception and apply promise penalty regardless in the following cases, where + // the peer delivered an obviously invalid message. + switch reason { + case RejectMissingSignature: + return + case RejectInvalidSignature: + return + } + + gt.fulfillPromise(msg) +} + +func (gt *gossipTracer) ValidateMessage(msg *Message) { + // we consider the promise fulfilled as soon as the message begins validation + // if it was a case of signature issue it would have been rejected immediately + // without triggering the Validate trace + gt.fulfillPromise(msg) +} + +func (gt *gossipTracer) AddPeer(p *enode.Node, proto ProtocolID) {} +func (gt *gossipTracer) RemovePeer(p enode.ID) {} +func (gt *gossipTracer) Join(topic string) {} +func (gt *gossipTracer) Leave(topic string) {} +func (gt *gossipTracer) Graft(p enode.ID, topic string) {} +func (gt *gossipTracer) Prune(p enode.ID, topic string) {} +func (gt *gossipTracer) DuplicateMessage(msg *Message) {} +func (gt *gossipTracer) RecvRPC(rpc *RPC) {} +func (gt *gossipTracer) SendRPC(rpc *RPC, p enode.ID) {} +func (gt *gossipTracer) DropRPC(rpc *RPC, p enode.ID) {} +func (gt *gossipTracer) UndeliverableMessage(msg *Message) {} + +func (gt *gossipTracer) ThrottlePeer(p enode.ID) { + gt.Lock() + defer gt.Unlock() + + peerPromises, ok := gt.peerPromises[p] + if !ok { + return + } + + for mid := range peerPromises { + promises := gt.promises[mid] + delete(promises, p) + if len(promises) == 0 { + delete(gt.promises, mid) + } + } + + delete(gt.peerPromises, p) +} diff --git a/p2p/pubsub/gossippub.go b/p2p/pubsub/gossippub.go new file mode 100644 index 0000000000..fe934745f2 --- /dev/null +++ b/p2p/pubsub/gossippub.go @@ -0,0 +1,1904 @@ +package pubsub + +import ( + "context" + "fmt" + "math/rand" + "sort" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + "github.com/AlayaNetwork/Alaya-Go/log" +) + +const ( + // GossipSubID_v10 is the protocol ID for version 1.0.0 of the GossipSub protocol. + // It is advertised along with GossipSubID_v11 for backwards compatibility. + GossipSubID_v10 = ProtocolID("/meshsub/1.0.0") + + // GossipSubID_v11 is the protocol ID for version 1.1.0 of the GossipSub protocol. + // See the spec for details about how v1.1.0 compares to v1.0.0: + // https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md + GossipSubID_v11 = ProtocolID("/meshsub/1.1.0") +) + +// Defines the default gossipsub parameters. +var ( + GossipSubD = 6 + GossipSubDlo = 5 + GossipSubDhi = 12 + GossipSubDscore = 4 + GossipSubDout = 2 + GossipSubHistoryLength = 5 + GossipSubHistoryGossip = 3 + GossipSubDlazy = 6 + GossipSubGossipFactor = 0.25 + GossipSubGossipRetransmission = 3 + GossipSubHeartbeatInitialDelay = 100 * time.Millisecond + GossipSubHeartbeatInterval = 1 * time.Second + GossipSubFanoutTTL = 60 * time.Second + GossipSubPrunePeers = 16 + GossipSubPruneBackoff = time.Minute + GossipSubConnectors = 8 + GossipSubMaxPendingConnections = 128 + GossipSubConnectionTimeout = 30 * time.Second + GossipSubDirectConnectTicks uint64 = 300 + GossipSubDirectConnectInitialDelay = time.Second + GossipSubOpportunisticGraftTicks uint64 = 60 + GossipSubOpportunisticGraftPeers = 2 + GossipSubGraftFloodThreshold = 10 * time.Second + GossipSubMaxIHaveLength = 5000 + GossipSubMaxIHaveMessages = 10 + GossipSubIWantFollowupTime = 3 * time.Second +) + +// GossipSubParams defines all the gossipsub specific parameters. +type GossipSubParams struct { + // overlay parameters. + + // D sets the optimal degree for a GossipSub topic mesh. For example, if D == 6, + // each peer will want to have about six peers in their mesh for each topic they're subscribed to. + // D should be set somewhere between Dlo and Dhi. + D int + + // Dlo sets the lower bound on the number of peers we keep in a GossipSub topic mesh. + // If we have fewer than Dlo peers, we will attempt to graft some more into the mesh at + // the next heartbeat. + Dlo int + + // Dhi sets the upper bound on the number of peers we keep in a GossipSub topic mesh. + // If we have more than Dhi peers, we will select some to prune from the mesh at the next heartbeat. + Dhi int + + // Dscore affects how peers are selected when pruning a mesh due to over subscription. + // At least Dscore of the retained peers will be high-scoring, while the remainder are + // chosen randomly. + Dscore int + + // Dout sets the quota for the number of outbound connections to maintain in a topic mesh. + // When the mesh is pruned due to over subscription, we make sure that we have outbound connections + // to at least Dout of the survivor peers. This prevents sybil attackers from overwhelming + // our mesh with incoming connections. + // + // Dout must be set below Dlo, and must not exceed D / 2. + Dout int + + // gossip parameters + + // HistoryLength controls the size of the message cache used for gossip. + // The message cache will remember messages for HistoryLength heartbeats. + HistoryLength int + + // HistoryGossip controls how many cached message ids we will advertise in + // IHAVE gossip messages. When asked for our seen message IDs, we will return + // only those from the most recent HistoryGossip heartbeats. The slack between + // HistoryGossip and HistoryLength allows us to avoid advertising messages + // that will be expired by the time they're requested. + // + // HistoryGossip must be less than or equal to HistoryLength to + // avoid a runtime panic. + HistoryGossip int + + // Dlazy affects how many peers we will emit gossip to at each heartbeat. + // We will send gossip to at least Dlazy peers outside our mesh. The actual + // number may be more, depending on GossipFactor and how many peers we're + // connected to. + Dlazy int + + // GossipFactor affects how many peers we will emit gossip to at each heartbeat. + // We will send gossip to GossipFactor * (total number of non-mesh peers), or + // Dlazy, whichever is greater. + GossipFactor float64 + + // GossipRetransmission controls how many times we will allow a peer to request + // the same message id through IWANT gossip before we start ignoring them. This is designed + // to prevent peers from spamming us with requests and wasting our resources. + GossipRetransmission int + + // heartbeat interval + + // HeartbeatInitialDelay is the short delay before the heartbeat timer begins + // after the router is initialized. + HeartbeatInitialDelay time.Duration + + // HeartbeatInterval controls the time between heartbeats. + HeartbeatInterval time.Duration + + // SlowHeartbeatWarning is the duration threshold for heartbeat processing before emitting + // a warning; this would be indicative of an overloaded peer. + SlowHeartbeatWarning float64 + + // FanoutTTL controls how long we keep track of the fanout state. If it's been + // FanoutTTL since we've published to a topic that we're not subscribed to, + // we'll delete the fanout map for that topic. + FanoutTTL time.Duration + + // PrunePeers controls the number of peers to include in prune Peer eXchange. + // When we prune a peer that's eligible for PX (has a good score, etc), we will try to + // send them signed peer records for up to PrunePeers other peers that we + // know of. + PrunePeers int + + // PruneBackoff controls the backoff time for pruned peers. This is how long + // a peer must wait before attempting to graft into our mesh again after being pruned. + // When pruning a peer, we send them our value of PruneBackoff so they know + // the minimum time to wait. Peers running older versions may not send a backoff time, + // so if we receive a prune message without one, we will wait at least PruneBackoff + // before attempting to re-graft. + PruneBackoff time.Duration + + // Connectors controls the number of active connection attempts for peers obtained through PX. + Connectors int + + // MaxPendingConnections sets the maximum number of pending connections for peers attempted through px. + MaxPendingConnections int + + // ConnectionTimeout controls the timeout for connection attempts. + ConnectionTimeout time.Duration + + // DirectConnectTicks is the number of heartbeat ticks for attempting to reconnect direct peers + // that are not currently connected. + DirectConnectTicks uint64 + + // DirectConnectInitialDelay is the initial delay before opening connections to direct peers + DirectConnectInitialDelay time.Duration + + // OpportunisticGraftTicks is the number of heartbeat ticks for attempting to improve the mesh + // with opportunistic grafting. Every OpportunisticGraftTicks we will attempt to select some + // high-scoring mesh peers to replace lower-scoring ones, if the median score of our mesh peers falls + // below a threshold (see https://godoc.org/github.com/libp2p/go-libp2p-pubsub#PeerScoreThresholds). + OpportunisticGraftTicks uint64 + + // OpportunisticGraftPeers is the number of peers to opportunistically graft. + OpportunisticGraftPeers int + + // If a GRAFT comes before GraftFloodThreshold has elapsed since the last PRUNE, + // then there is an extra score penalty applied to the peer through P7. + GraftFloodThreshold time.Duration + + // MaxIHaveLength is the maximum number of messages to include in an IHAVE message. + // Also controls the maximum number of IHAVE ids we will accept and request with IWANT from a + // peer within a heartbeat, to protect from IHAVE floods. You should adjust this value from the + // default if your system is pushing more than 5000 messages in HistoryGossip heartbeats; + // with the defaults this is 1666 messages/s. + MaxIHaveLength int + + // MaxIHaveMessages is the maximum number of IHAVE messages to accept from a peer within a heartbeat. + MaxIHaveMessages int + + // Time to wait for a message requested through IWANT following an IHAVE advertisement. + // If the message is not received within this window, a broken promise is declared and + // the router may apply bahavioural penalties. + IWantFollowupTime time.Duration +} + +// NewGossipSub returns a new PubSub object using GossipSubRouter as the router. +func NewGossipSub(ctx context.Context, h Host, opts ...Option) (*PubSub, error) { + params := DefaultGossipSubParams() + rt := &GossipSubRouter{ + peers: make(map[enode.ID]ProtocolID), + mesh: make(map[string]map[enode.ID]struct{}), + fanout: make(map[string]map[enode.ID]struct{}), + lastpub: make(map[string]int64), + gossip: make(map[enode.ID][]*message.ControlIHave), + control: make(map[enode.ID]*message.ControlMessage), + backoff: make(map[string]map[enode.ID]time.Time), + peerhave: make(map[enode.ID]int), + iasked: make(map[enode.ID]int), + outbound: make(map[enode.ID]bool), + connect: make(chan connectInfo, params.MaxPendingConnections), + mcache: NewMessageCache(params.HistoryGossip, params.HistoryLength), + protos: GossipSubDefaultProtocols, + feature: GossipSubDefaultFeatures, + tagTracer: newTagTracer(h.ConnManager()), + params: params, + } + + // hook the tag tracer + opts = append(opts, WithRawTracer(rt.tagTracer)) + return NewPubSub(ctx, h, rt, opts...) +} + +// DefaultGossipSubParams returns the default gossip sub parameters +// as a config. +func DefaultGossipSubParams() GossipSubParams { + return GossipSubParams{ + D: GossipSubD, + Dlo: GossipSubDlo, + Dhi: GossipSubDhi, + Dscore: GossipSubDscore, + Dout: GossipSubDout, + HistoryLength: GossipSubHistoryLength, + HistoryGossip: GossipSubHistoryLength, + Dlazy: GossipSubDlazy, + GossipFactor: GossipSubGossipFactor, + GossipRetransmission: GossipSubGossipRetransmission, + HeartbeatInitialDelay: GossipSubHeartbeatInitialDelay, + HeartbeatInterval: GossipSubHeartbeatInterval, + FanoutTTL: GossipSubFanoutTTL, + PrunePeers: GossipSubPrunePeers, + PruneBackoff: GossipSubPruneBackoff, + Connectors: GossipSubConnectors, + MaxPendingConnections: GossipSubMaxPendingConnections, + ConnectionTimeout: GossipSubConnectionTimeout, + DirectConnectTicks: GossipSubDirectConnectTicks, + DirectConnectInitialDelay: GossipSubDirectConnectInitialDelay, + OpportunisticGraftTicks: GossipSubOpportunisticGraftTicks, + OpportunisticGraftPeers: GossipSubOpportunisticGraftPeers, + GraftFloodThreshold: GossipSubGraftFloodThreshold, + MaxIHaveLength: GossipSubMaxIHaveLength, + MaxIHaveMessages: GossipSubMaxIHaveMessages, + IWantFollowupTime: GossipSubIWantFollowupTime, + SlowHeartbeatWarning: 0.1, + } +} + +// WithPeerScore is a gossipsub router option that enables peer scoring. +func WithPeerScore(params *PeerScoreParams, thresholds *PeerScoreThresholds) Option { + return func(ps *PubSub) error { + gs, ok := ps.rt.(*GossipSubRouter) + if !ok { + return fmt.Errorf("pubsub router is not gossipsub") + } + + // sanity check: validate the score parameters + err := params.validate() + if err != nil { + return err + } + + // sanity check: validate the threshold values + err = thresholds.validate() + if err != nil { + return err + } + + gs.score = newPeerScore(params) + gs.gossipThreshold = thresholds.GossipThreshold + gs.publishThreshold = thresholds.PublishThreshold + gs.graylistThreshold = thresholds.GraylistThreshold + gs.acceptPXThreshold = thresholds.AcceptPXThreshold + gs.opportunisticGraftThreshold = thresholds.OpportunisticGraftThreshold + + gs.gossipTracer = newGossipTracer() + + // hook the tracer + if ps.tracer != nil { + ps.tracer.raw = append(ps.tracer.raw, gs.score, gs.gossipTracer) + } else { + ps.tracer = &pubsubTracer{ + raw: []RawTracer{gs.score, gs.gossipTracer}, + pid: ps.host.ID().ID(), + msgID: ps.msgID, + } + } + + return nil + } +} + +// WithPeerExchange is a gossipsub router option that enables Peer eXchange on PRUNE. +// This should generally be enabled in bootstrappers and well connected/trusted nodes +// used for bootstrapping. +func WithPeerExchange(doPX bool) Option { + return func(ps *PubSub) error { + gs, ok := ps.rt.(*GossipSubRouter) + if !ok { + return fmt.Errorf("pubsub router is not gossipsub") + } + + gs.doPX = doPX + + return nil + } +} + +// WithDirectPeers is a gossipsub router option that specifies peers with direct +// peering agreements. These peers are connected outside of the mesh, with all (valid) +// message unconditionally forwarded to them. The router will maintain open connections +// to these peers. Note that the peering agreement should be reciprocal with direct peers +// symmetrically configured at both ends. +func WithDirectPeers(pis []*enode.Node) Option { + return func(ps *PubSub) error { + gs, ok := ps.rt.(*GossipSubRouter) + if !ok { + return fmt.Errorf("pubsub router is not gossipsub") + } + + direct := make(map[enode.ID]struct{}) + for _, pi := range pis { + direct[pi.ID()] = struct{}{} + ps.host.Peerstore().AddAddrs(pi.ID(), PermanentAddrTTL) + } + + gs.direct = direct + + if gs.tagTracer != nil { + gs.tagTracer.direct = direct + } + + return nil + } +} + +// WithDirectConnectTicks is a gossipsub router option that sets the number of +// heartbeat ticks between attempting to reconnect direct peers that are not +// currently connected. A "tick" is based on the heartbeat interval, which is +// 1s by default. The default value for direct connect ticks is 300. +func WithDirectConnectTicks(t uint64) Option { + return func(ps *PubSub) error { + gs, ok := ps.rt.(*GossipSubRouter) + if !ok { + return fmt.Errorf("pubsub router is not gossipsub") + } + gs.params.DirectConnectTicks = t + return nil + } +} + +// WithGossipSubParams is a gossip sub router option that allows a custom +// config to be set when instantiating the gossipsub router. +func WithGossipSubParams(cfg GossipSubParams) Option { + return func(ps *PubSub) error { + gs, ok := ps.rt.(*GossipSubRouter) + if !ok { + return fmt.Errorf("pubsub router is not gossipsub") + } + // Overwrite current config and associated variables in the router. + gs.params = cfg + gs.connect = make(chan connectInfo, cfg.MaxPendingConnections) + gs.mcache = NewMessageCache(cfg.HistoryGossip, cfg.HistoryLength) + + return nil + } +} + +// GossipSubRouter is a router that implements the gossipsub protocol. +// For each topic we have joined, we maintain an overlay through which +// messages flow; this is the mesh map. +// For each topic we publish to without joining, we maintain a list of peers +// to use for injecting our messages in the overlay with stable routes; this +// is the fanout map. Fanout peer lists are expired if we don't publish any +// messages to their topic for GossipSubFanoutTTL. +type GossipSubRouter struct { + p *PubSub + peers map[enode.ID]ProtocolID // peer protocols + direct map[enode.ID]struct{} // direct peers + mesh map[string]map[enode.ID]struct{} // topic meshes + fanout map[string]map[enode.ID]struct{} // topic fanout + lastpub map[string]int64 // last publish time for fanout topics + gossip map[enode.ID][]*message.ControlIHave // pending gossip + control map[enode.ID]*message.ControlMessage // pending control messages + peerhave map[enode.ID]int // number of IHAVEs received from peer in the last heartbeat + iasked map[enode.ID]int // number of messages we have asked from peer in the last heartbeat + outbound map[enode.ID]bool // connection direction cache, marks peers with outbound connections + backoff map[string]map[enode.ID]time.Time // prune backoff + connect chan connectInfo // px connection requests + + protos []ProtocolID + feature GossipSubFeatureTest + + mcache *MessageCache + tracer *pubsubTracer + score *peerScore + gossipTracer *gossipTracer + tagTracer *tagTracer + gate *peerGater + + // config for gossipsub parameters + params GossipSubParams + + // whether PX is enabled; this should be enabled in bootstrappers and other well connected/trusted + // nodes. + doPX bool + + // threshold for accepting PX from a peer; this should be positive and limited to scores + // attainable by bootstrappers and trusted nodes + acceptPXThreshold float64 + + // threshold for peer score to emit/accept gossip + // If the peer score is below this threshold, we won't emit or accept gossip from the peer. + // When there is no score, this value is 0. + gossipThreshold float64 + + // flood publish score threshold; we only publish to peers with score >= to the threshold + // when using flood publishing or the peer is a fanout or floodsub peer. + publishThreshold float64 + + // threshold for peer score before we graylist the peer and silently ignore its RPCs + graylistThreshold float64 + + // threshold for median peer score before triggering opportunistic grafting + opportunisticGraftThreshold float64 + + // whether to use flood publishing + floodPublish bool + + // number of heartbeats since the beginning of time; this allows us to amortize some resource + // clean up -- eg backoff clean up. + heartbeatTicks uint64 +} + +type connectInfo struct { + p enode.ID + //spr *record.Envelope +} + +func (gs *GossipSubRouter) Protocols() []ProtocolID { + return gs.protos +} + +func (gs *GossipSubRouter) Attach(p *PubSub) { + gs.p = p + gs.tracer = p.tracer + + // start the scoring + gs.score.Start(gs) + + // and the gossip tracing + gs.gossipTracer.Start(gs) + + // and the tracer for connmgr tags + gs.tagTracer.Start(gs) + + // start using the same msg ID function as PubSub for caching messages. + gs.mcache.SetMsgIdFn(p.msgID) + + // start the heartbeat + go gs.heartbeatTimer() + + // start the PX connectors + for i := 0; i < gs.params.Connectors; i++ { + go gs.connector() + } + + // connect to direct peers + if len(gs.direct) > 0 { + go func() { + if gs.params.DirectConnectInitialDelay > 0 { + time.Sleep(gs.params.DirectConnectInitialDelay) + } + + for p, _ := range gs.direct { + gs.connect <- connectInfo{p: p} + } + }() + } +} + +func (gs *GossipSubRouter) AddPeer(p *enode.Node, proto ProtocolID) { + log.Debug("PEERUP: Add new peer %s using %s", "peer", p, "proto", proto) + gs.tracer.AddPeer(p, proto) + gs.peers[p.ID()] = proto + + // track the connection direction + outbound := false + conns := gs.p.host.Network().ConnsToPeer(p.ID()) +loop: + for _, c := range conns { + stat := c.Stat() + + if stat.Transient { + continue + } + + if stat.Direction == DirOutbound { + // only count the connection if it has a pubsub stream + for _, s := range c.GetStreams() { + if s.Protocol() == proto { + outbound = true + break loop + } + } + } + } + gs.outbound[p.ID()] = outbound +} + +func (gs *GossipSubRouter) RemovePeer(p enode.ID) { + log.Debug("PEERDOWN: Remove disconnected peer %s", "peer", p) + gs.tracer.RemovePeer(p) + delete(gs.peers, p) + for _, peers := range gs.mesh { + delete(peers, p) + } + for _, peers := range gs.fanout { + delete(peers, p) + } + delete(gs.gossip, p) + delete(gs.control, p) + delete(gs.outbound, p) +} + +func (gs *GossipSubRouter) EnoughPeers(topic string, suggested int) bool { + // check all peers in the topic + tmap, ok := gs.p.topics[topic] + if !ok { + return false + } + + fsPeers, gsPeers := 0, 0 + // floodsub peers + for p := range tmap { + if !gs.feature(GossipSubFeatureMesh, gs.peers[p]) { + fsPeers++ + } + } + + // gossipsub peers + gsPeers = len(gs.mesh[topic]) + + if suggested == 0 { + suggested = gs.params.Dlo + } + + if fsPeers+gsPeers >= suggested || gsPeers >= gs.params.Dhi { + return true + } + + return false +} + +func (gs *GossipSubRouter) AcceptFrom(p *enode.Node) AcceptStatus { + _, direct := gs.direct[p.ID()] + if direct { + return AcceptAll + } + + if gs.score.Score(p.ID()) < gs.graylistThreshold { + return AcceptNone + } + + return gs.gate.AcceptFrom(p) +} + +func (gs *GossipSubRouter) HandleRPC(rpc *RPC) { + ctl := rpc.GetControl() + if ctl == nil { + return + } + + iwant := gs.handleIHave(rpc.from.ID(), ctl) + ihave := gs.handleIWant(rpc.from.ID(), ctl) + prune := gs.handleGraft(rpc.from.ID(), ctl) + gs.handlePrune(rpc.from.ID(), ctl) + + if len(iwant) == 0 && len(ihave) == 0 && len(prune) == 0 { + return + } + + out := rpcWithControl(ihave, nil, iwant, nil, prune) + gs.sendRPC(rpc.from.ID(), out) +} + +func (gs *GossipSubRouter) handleIHave(p enode.ID, ctl *message.ControlMessage) []*message.ControlIWant { + // we ignore IHAVE gossip from any peer whose score is below the gossip threshold + score := gs.score.Score(p) + if score < gs.gossipThreshold { + log.Debug("IHAVE: ignoring peer %s with score below threshold [score = %f]", "peer", p, "score", score) + return nil + } + + // IHAVE flood protection + gs.peerhave[p]++ + if gs.peerhave[p] > gs.params.MaxIHaveMessages { + log.Debug("IHAVE: peer %s has advertised too many times (%d) within this heartbeat interval; ignoring", "peer", "messages", p, gs.peerhave[p]) + return nil + } + + if gs.iasked[p] >= gs.params.MaxIHaveLength { + log.Debug("IHAVE: peer %s has already advertised too many messages (%d); ignoring", "peer", p, "messages", gs.iasked[p]) + return nil + } + + iwant := make(map[string]struct{}) + for _, ihave := range ctl.GetIhave() { + topic := ihave.GetTopicID() + _, ok := gs.mesh[topic] + if !ok { + continue + } + + if !gs.p.peerFilter(p, topic) { + continue + } + + for _, mid := range ihave.GetMessageIDs() { + if gs.p.seenMessage(mid) { + continue + } + iwant[mid] = struct{}{} + } + } + + if len(iwant) == 0 { + return nil + } + + iask := len(iwant) + if iask+gs.iasked[p] > gs.params.MaxIHaveLength { + iask = gs.params.MaxIHaveLength - gs.iasked[p] + } + + log.Debug("IHAVE: Asking for %d out of %d messages from %s", "iask", iask, "iwant", len(iwant), "peer", p) + + iwantlst := make([]string, 0, len(iwant)) + for mid := range iwant { + iwantlst = append(iwantlst, mid) + } + + // ask in random order + shuffleStrings(iwantlst) + + // truncate to the messages we are actually asking for and update the iasked counter + iwantlst = iwantlst[:iask] + gs.iasked[p] += iask + + gs.gossipTracer.AddPromise(p, iwantlst) + + return []*message.ControlIWant{{MessageIDs: iwantlst}} +} + +func (gs *GossipSubRouter) handleIWant(p enode.ID, ctl *message.ControlMessage) []*message.Message { + // we don't respond to IWANT requests from any peer whose score is below the gossip threshold + score := gs.score.Score(p) + if score < gs.gossipThreshold { + log.Debug("IWANT: ignoring peer %s with score below threshold [score = %f]", "peer", p, "score", score) + return nil + } + + ihave := make(map[string]*message.Message) + for _, iwant := range ctl.GetIwant() { + for _, mid := range iwant.GetMessageIDs() { + msg, count, ok := gs.mcache.GetForPeer(mid, p) + if !ok { + continue + } + + if !gs.p.peerFilter(p, msg.GetTopic()) { + continue + } + + if count > gs.params.GossipRetransmission { + log.Debug("IWANT: Peer %s has asked for message %s too many times; ignoring request", "peer", p, "mid", mid) + continue + } + + ihave[mid] = msg + } + } + + if len(ihave) == 0 { + return nil + } + + log.Debug("IWANT: Sending %d messages to %s", "ihave", len(ihave), "peer", p) + + msgs := make([]*message.Message, 0, len(ihave)) + for _, msg := range ihave { + msgs = append(msgs, msg) + } + + return msgs +} + +func (gs *GossipSubRouter) handleGraft(p enode.ID, ctl *message.ControlMessage) []*message.ControlPrune { + var prune []string + + doPX := gs.doPX + score := gs.score.Score(p) + now := time.Now() + + for _, graft := range ctl.GetGraft() { + topic := graft.GetTopicID() + + if !gs.p.peerFilter(p, topic) { + continue + } + + peers, ok := gs.mesh[topic] + if !ok { + // don't do PX when there is an unknown topic to avoid leaking our peers + doPX = false + // spam hardening: ignore GRAFTs for unknown topics + continue + } + + // check if it is already in the mesh; if so do nothing (we might have concurrent grafting) + _, inMesh := peers[p] + if inMesh { + continue + } + + // we don't GRAFT to/from direct peers; complain loudly if this happens + _, direct := gs.direct[p] + if direct { + log.Warn("GRAFT: ignoring request from direct peer %s", "peer", p) + // this is possibly a bug from non-reciprocal configuration; send a PRUNE + prune = append(prune, topic) + // but don't PX + doPX = false + continue + } + + // make sure we are not backing off that peer + expire, backoff := gs.backoff[topic][p] + if backoff && now.Before(expire) { + log.Debug("GRAFT: ignoring backed off peer %s", "peer", p) + // add behavioural penalty + gs.score.AddPenalty(p, 1) + // no PX + doPX = false + // check the flood cutoff -- is the GRAFT coming too fast? + floodCutoff := expire.Add(gs.params.GraftFloodThreshold - gs.params.PruneBackoff) + if now.Before(floodCutoff) { + // extra penalty + gs.score.AddPenalty(p, 1) + } + // refresh the backoff + gs.addBackoff(p, topic) + prune = append(prune, topic) + continue + } + + // check the score + if score < 0 { + // we don't GRAFT peers with negative score + log.Debug("GRAFT: ignoring peer %s with negative score [score = %f, topic = %s]", "peer", p, "score", score, "topic", topic) + // we do send them PRUNE however, because it's a matter of protocol correctness + prune = append(prune, topic) + // but we won't PX to them + doPX = false + // add/refresh backoff so that we don't reGRAFT too early even if the score decays back up + gs.addBackoff(p, topic) + continue + } + + // check the number of mesh peers; if it is at (or over) Dhi, we only accept grafts + // from peers with outbound connections; this is a defensive check to restrict potential + // mesh takeover attacks combined with love bombing + if len(peers) >= gs.params.Dhi && !gs.outbound[p] { + prune = append(prune, topic) + gs.addBackoff(p, topic) + continue + } + + log.Debug("GRAFT: add mesh link from %s in %s", "peer", p, "topic", topic) + gs.tracer.Graft(p, topic) + peers[p] = struct{}{} + } + + if len(prune) == 0 { + return nil + } + + cprune := make([]*message.ControlPrune, 0, len(prune)) + for _, topic := range prune { + cprune = append(cprune, gs.makePrune(p, topic, doPX)) + } + + return cprune +} + +func (gs *GossipSubRouter) handlePrune(p enode.ID, ctl *message.ControlMessage) { + score := gs.score.Score(p) + + for _, prune := range ctl.GetPrune() { + topic := prune.GetTopicID() + peers, ok := gs.mesh[topic] + if !ok { + continue + } + + log.Debug("PRUNE: Remove mesh link to %s in %s", "peer", p, "topic", topic) + gs.tracer.Prune(p, topic) + delete(peers, p) + // is there a backoff specified by the peer? if so obey it. + backoff := prune.GetBackoff() + if backoff > 0 { + gs.doAddBackoff(p, topic, time.Duration(backoff)*time.Second) + } else { + gs.addBackoff(p, topic) + } + + px := prune.GetPeers() + if len(px) > 0 { + // we ignore PX from peers with insufficient score + if score < gs.acceptPXThreshold { + log.Debug("PRUNE: ignoring PX from peer %s with insufficient score [score = %f, topic = %s]", "peer", p, "score", score, "topic", topic) + continue + } + + gs.pxConnect(px) + } + } +} + +func (gs *GossipSubRouter) addBackoff(p enode.ID, topic string) { + gs.doAddBackoff(p, topic, gs.params.PruneBackoff) +} + +func (gs *GossipSubRouter) doAddBackoff(p enode.ID, topic string, interval time.Duration) { + backoff, ok := gs.backoff[topic] + if !ok { + backoff = make(map[enode.ID]time.Time) + gs.backoff[topic] = backoff + } + expire := time.Now().Add(interval) + if backoff[p].Before(expire) { + backoff[p] = expire + } +} + +func (gs *GossipSubRouter) pxConnect(peers []*message.PeerInfo) { + if len(peers) > gs.params.PrunePeers { + shufflePeerInfo(peers) + peers = peers[:gs.params.PrunePeers] + } + + toconnect := make([]connectInfo, 0, len(peers)) + + for _, pi := range peers { + p := pi.GetPeerID() + + _, connected := gs.peers[p] + if connected { + continue + } + + /* var spr *record.Envelope + if pi.SignedPeerRecord != nil { + // the peer sent us a signed record; ensure that it is valid + envelope, r, err := record.ConsumeEnvelope(pi.SignedPeerRecord, peer.PeerRecordEnvelopeDomain) + if err != nil { + log.Warnf("error unmarshalling peer record obtained through px: %s", err) + continue + } + rec, ok := r.(*peer.PeerRecord) + if !ok { + log.Warnf("bogus peer record obtained through px: envelope payload is not PeerRecord") + continue + } + if rec.PeerID != p { + log.Warnf("bogus peer record obtained through px: peer ID %s doesn't match expected peer %s", rec.PeerID, p) + continue + } + spr = envelope + }*/ + + toconnect = append(toconnect, connectInfo{p}) + } + + if len(toconnect) == 0 { + return + } + + for _, ci := range toconnect { + select { + case gs.connect <- ci: + default: + log.Debug("ignoring peer connection attempt; too many pending connections") + } + } +} + +func (gs *GossipSubRouter) connector() { + for { + select { + case ci := <-gs.connect: + if gs.p.host.Network().Connectedness(ci.p) == Connected { + continue + } + + log.Debug("connecting to peer", "peer", ci.p) + /* cab, ok := peerstore.GetCertifiedAddrBook(gs.p.host.Peerstore()) + if ok && ci.spr != nil { + _, err := cab.ConsumePeerRecord(ci.spr, peerstore.TempAddrTTL) + if err != nil { + log.Debugf("error processing peer record: %s", err) + } + }*/ + + ctx, cancel := context.WithTimeout(gs.p.ctx, gs.params.ConnectionTimeout) + err := gs.p.host.Connect(ctx, ci.p) + cancel() + if err != nil { + log.Debug("error connecting to peer", "peer", ci.p, "err", err) + } + + case <-gs.p.ctx.Done(): + return + } + } +} + +func (gs *GossipSubRouter) Publish(msg *Message) { + gs.mcache.Put(msg.Message) + + from := msg.ReceivedFrom + topic := msg.GetTopic() + + tosend := make(map[enode.ID]struct{}) + + // any peers in the topic? + tmap, ok := gs.p.topics[topic] + if !ok { + return + } + + if gs.floodPublish && from.ID() == gs.p.host.ID().ID() { + for p := range tmap { + _, direct := gs.direct[p] + if direct || gs.score.Score(p) >= gs.publishThreshold { + tosend[p] = struct{}{} + } + } + } else { + // direct peers + for p := range gs.direct { + _, inTopic := tmap[p] + if inTopic { + tosend[p] = struct{}{} + } + } + + // floodsub peers + for p := range tmap { + if !gs.feature(GossipSubFeatureMesh, gs.peers[p]) && gs.score.Score(p) >= gs.publishThreshold { + tosend[p] = struct{}{} + } + } + + // gossipsub peers + gmap, ok := gs.mesh[topic] + if !ok { + // we are not in the mesh for topic, use fanout peers + gmap, ok = gs.fanout[topic] + if !ok || len(gmap) == 0 { + // we don't have any, pick some with score above the publish threshold + peers := gs.getPeers(topic, gs.params.D, func(p enode.ID) bool { + _, direct := gs.direct[p] + return !direct && gs.score.Score(p) >= gs.publishThreshold + }) + + if len(peers) > 0 { + gmap = peerListToMap(peers) + gs.fanout[topic] = gmap + } + } + gs.lastpub[topic] = time.Now().UnixNano() + } + + for p := range gmap { + tosend[p] = struct{}{} + } + } + + out := rpcWithMessages(msg.Message) + for pid := range tosend { + if pid == from.ID() || pid == enode.ID(msg.GetFrom()) { + continue + } + + gs.sendRPC(pid, out) + } +} + +func (gs *GossipSubRouter) Join(topic string) { + gmap, ok := gs.mesh[topic] + if ok { + return + } + + log.Debug("JOIN topic", "topic", topic) + gs.tracer.Join(topic) + + gmap, ok = gs.fanout[topic] + if ok { + // these peers have a score above the publish threshold, which may be negative + // so drop the ones with a negative score + for p := range gmap { + if gs.score.Score(p) < 0 { + delete(gmap, p) + } + } + + if len(gmap) < gs.params.D { + // we need more peers; eager, as this would get fixed in the next heartbeat + more := gs.getPeers(topic, gs.params.D-len(gmap), func(p enode.ID) bool { + // filter our current peers, direct peers, and peers with negative scores + _, inMesh := gmap[p] + _, direct := gs.direct[p] + return !inMesh && !direct && gs.score.Score(p) >= 0 + }) + for _, p := range more { + gmap[p] = struct{}{} + } + } + gs.mesh[topic] = gmap + delete(gs.fanout, topic) + delete(gs.lastpub, topic) + } else { + peers := gs.getPeers(topic, gs.params.D, func(p enode.ID) bool { + // filter direct peers and peers with negative score + _, direct := gs.direct[p] + return !direct && gs.score.Score(p) >= 0 + }) + gmap = peerListToMap(peers) + gs.mesh[topic] = gmap + } + + for p := range gmap { + log.Debug("JOIN: Add mesh link to %s in %s", "peer", p, "topic", topic) + gs.tracer.Graft(p, topic) + gs.sendGraft(p, topic) + } +} + +func (gs *GossipSubRouter) Leave(topic string) { + gmap, ok := gs.mesh[topic] + if !ok { + return + } + + log.Debug("LEAVE topic", "topic", topic) + gs.tracer.Leave(topic) + + delete(gs.mesh, topic) + + for p := range gmap { + log.Debug("LEAVE: Remove mesh link to %s in %s", "peer", p, "topic", topic) + gs.tracer.Prune(p, topic) + gs.sendPrune(p, topic) + } +} + +func (gs *GossipSubRouter) sendGraft(p enode.ID, topic string) { + graft := []*message.ControlGraft{{TopicID: &topic}} + out := rpcWithControl(nil, nil, nil, graft, nil) + gs.sendRPC(p, out) +} + +func (gs *GossipSubRouter) sendPrune(p enode.ID, topic string) { + prune := []*message.ControlPrune{gs.makePrune(p, topic, gs.doPX)} + out := rpcWithControl(nil, nil, nil, nil, prune) + gs.sendRPC(p, out) +} + +func (gs *GossipSubRouter) sendRPC(p enode.ID, out *RPC) { + // do we own the RPC? + own := false + + // piggyback control message retries + ctl, ok := gs.control[p] + if ok { + out = copyRPC(out) + own = true + gs.piggybackControl(p, out, ctl) + delete(gs.control, p) + } + + // piggyback gossip + ihave, ok := gs.gossip[p] + if ok { + if !own { + out = copyRPC(out) + own = true + } + gs.piggybackGossip(p, out, ihave) + delete(gs.gossip, p) + } + + mch, ok := gs.p.peers[p] + if !ok { + return + } + + // If we're below the max message size, go ahead and send + if out.Size() < gs.p.maxMessageSize { + gs.doSendRPC(out, p, mch) + return + } + + // If we're too big, fragment into multiple RPCs and send each sequentially + outRPCs, err := fragmentRPC(out, gs.p.maxMessageSize) + if err != nil { + gs.doDropRPC(out, p, fmt.Sprintf("unable to fragment RPC: %s", err)) + return + } + + for _, rpc := range outRPCs { + gs.doSendRPC(rpc, p, mch) + } +} + +func (gs *GossipSubRouter) doDropRPC(rpc *RPC, p enode.ID, reason string) { + log.Debug("dropping message to peer", "peer", p.TerminalString(), "reason", reason) + gs.tracer.DropRPC(rpc, p) + // push control messages that need to be retried + ctl := rpc.GetControl() + if ctl != nil { + gs.pushControl(p, ctl) + } +} + +func (gs *GossipSubRouter) doSendRPC(rpc *RPC, p enode.ID, mch chan *RPC) { + select { + case mch <- rpc: + gs.tracer.SendRPC(rpc, p) + default: + gs.doDropRPC(rpc, p, "queue full") + } +} + +func fragmentRPC(rpc *RPC, limit int) ([]*RPC, error) { + if rpc.Size() < limit { + return []*RPC{rpc}, nil + } + + c := (rpc.Size() / limit) + 1 + rpcs := make([]*RPC, 1, c) + rpcs[0] = &RPC{RPC: message.RPC{}, from: rpc.from} + + // outRPC returns the current RPC message if it will fit sizeToAdd more bytes + // otherwise, it will create a new RPC message and add it to the list. + // if withCtl is true, the returned message will have a non-nil empty Control message. + outRPC := func(sizeToAdd int, withCtl bool) *RPC { + current := rpcs[len(rpcs)-1] + // check if we can fit the new data, plus an extra byte for the protobuf field tag + if current.Size()+sizeToAdd+1 < limit { + if withCtl && current.Control == nil { + current.Control = &message.ControlMessage{} + } + return current + } + var ctl *message.ControlMessage + if withCtl { + ctl = &message.ControlMessage{} + } + next := &RPC{RPC: message.RPC{Control: ctl}, from: rpc.from} + rpcs = append(rpcs, next) + return next + } + + for _, msg := range rpc.GetPublish() { + s := msg.Size() + // if an individual message is too large, we can't fragment it and have to fail entirely + if s > limit { + return nil, fmt.Errorf("message with len=%d exceeds limit %d", s, limit) + } + out := outRPC(s, false) + out.Publish = append(out.Publish, msg) + } + + for _, sub := range rpc.GetSubscriptions() { + out := outRPC(sub.Size(), false) + out.Subscriptions = append(out.Subscriptions, sub) + } + + ctl := rpc.GetControl() + if ctl == nil { + // if there were no control messages, we're done + return rpcs, nil + } + // if all the control messages fit into one RPC, we just add it to the end and return + ctlOut := &RPC{RPC: message.RPC{Control: ctl}, from: rpc.from} + if ctlOut.Size() < limit { + rpcs = append(rpcs, ctlOut) + return rpcs, nil + } + + // we need to split up the control messages into multiple RPCs + for _, graft := range ctl.Graft { + out := outRPC(graft.Size(), true) + out.Control.Graft = append(out.Control.Graft, graft) + } + for _, prune := range ctl.Prune { + out := outRPC(prune.Size(), true) + out.Control.Prune = append(out.Control.Prune, prune) + } + + // An individual IWANT or IHAVE message could be larger than the limit if we have + // a lot of message IDs. fragmentMessageIds will split them into buckets that + // fit within the limit, with some overhead for the control messages themselves + for _, iwant := range ctl.Iwant { + const protobufOverhead = 6 + idBuckets := fragmentMessageIds(iwant.MessageIDs, limit-protobufOverhead) + for _, ids := range idBuckets { + iwant := &message.ControlIWant{MessageIDs: ids} + out := outRPC(iwant.Size(), true) + out.Control.Iwant = append(out.Control.Iwant, iwant) + } + } + for _, ihave := range ctl.Ihave { + const protobufOverhead = 6 + idBuckets := fragmentMessageIds(ihave.MessageIDs, limit-protobufOverhead) + for _, ids := range idBuckets { + ihave := &message.ControlIHave{MessageIDs: ids} + out := outRPC(ihave.Size(), true) + out.Control.Ihave = append(out.Control.Ihave, ihave) + } + } + return rpcs, nil +} + +func fragmentMessageIds(msgIds []string, limit int) [][]string { + // account for two bytes of protobuf overhead per array element + const protobufOverhead = 2 + + out := [][]string{{}} + var currentBucket int + var bucketLen int + for i := 0; i < len(msgIds); i++ { + size := len(msgIds[i]) + protobufOverhead + if size > limit { + // pathological case where a single message ID exceeds the limit. + log.Warn("message ID length %d exceeds limit %d, removing from outgoing gossip", "size", size, "limit", limit) + continue + } + bucketLen += size + if bucketLen > limit { + out = append(out, []string{}) + currentBucket++ + bucketLen = size + } + out[currentBucket] = append(out[currentBucket], msgIds[i]) + } + return out +} + +func (gs *GossipSubRouter) heartbeatTimer() { + time.Sleep(gs.params.HeartbeatInitialDelay) + select { + case gs.p.eval <- gs.heartbeat: + case <-gs.p.ctx.Done(): + return + } + + ticker := time.NewTicker(gs.params.HeartbeatInterval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + select { + case gs.p.eval <- gs.heartbeat: + case <-gs.p.ctx.Done(): + return + } + case <-gs.p.ctx.Done(): + return + } + } +} + +func (gs *GossipSubRouter) heartbeat() { + start := time.Now() + defer func() { + if gs.params.SlowHeartbeatWarning > 0 { + slowWarning := time.Duration(gs.params.SlowHeartbeatWarning * float64(gs.params.HeartbeatInterval)) + if dt := time.Since(start); dt > slowWarning { + log.Warn("slow heartbeat", "took", dt) + } + } + }() + + gs.heartbeatTicks++ + + tograft := make(map[enode.ID][]string) + toprune := make(map[enode.ID][]string) + noPX := make(map[enode.ID]bool) + + // clean up expired backoffs + gs.clearBackoff() + + // clean up iasked counters + gs.clearIHaveCounters() + + // apply IWANT request penalties + gs.applyIwantPenalties() + + // ensure direct peers are connected + gs.directConnect() + + // cache scores throughout the heartbeat + scores := make(map[enode.ID]float64) + score := func(p enode.ID) float64 { + s, ok := scores[p] + if !ok { + s = gs.score.Score(p) + scores[p] = s + } + return s + } + + // maintain the mesh for topics we have joined + for topic, peers := range gs.mesh { + prunePeer := func(p enode.ID) { + gs.tracer.Prune(p, topic) + delete(peers, p) + gs.addBackoff(p, topic) + topics := toprune[p] + toprune[p] = append(topics, topic) + } + + graftPeer := func(p enode.ID) { + log.Debug("HEARTBEAT: Add mesh link to %s in %s", "peer", p, "topic", topic) + gs.tracer.Graft(p, topic) + peers[p] = struct{}{} + topics := tograft[p] + tograft[p] = append(topics, topic) + } + + // drop all peers with negative score, without PX + for p := range peers { + if score(p) < 0 { + log.Debug("HEARTBEAT: Prune peer %s with negative score [score = %f, topic = %s]", "peer", p, "score", score(p), "topic", topic) + prunePeer(p) + noPX[p] = true + } + } + + // do we have enough peers? + if l := len(peers); l < gs.params.Dlo { + backoff := gs.backoff[topic] + ineed := gs.params.D - l + plst := gs.getPeers(topic, ineed, func(p enode.ID) bool { + // filter our current and direct peers, peers we are backing off, and peers with negative score + _, inMesh := peers[p] + _, doBackoff := backoff[p] + _, direct := gs.direct[p] + return !inMesh && !doBackoff && !direct && score(p) >= 0 + }) + + for _, p := range plst { + graftPeer(p) + } + } + + // do we have too many peers? + if len(peers) > gs.params.Dhi { + plst := peerMapToList(peers) + + // sort by score (but shuffle first for the case we don't use the score) + shufflePeers(plst) + sort.Slice(plst, func(i, j int) bool { + return score(plst[i]) > score(plst[j]) + }) + + // We keep the first D_score peers by score and the remaining up to D randomly + // under the constraint that we keep D_out peers in the mesh (if we have that many) + shufflePeers(plst[gs.params.Dscore:]) + + // count the outbound peers we are keeping + outbound := 0 + for _, p := range plst[:gs.params.D] { + if gs.outbound[p] { + outbound++ + } + } + + // if it's less than D_out, bubble up some outbound peers from the random selection + if outbound < gs.params.Dout { + rotate := func(i int) { + // rotate the plst to the right and put the ith peer in the front + p := plst[i] + for j := i; j > 0; j-- { + plst[j] = plst[j-1] + } + plst[0] = p + } + + // first bubble up all outbound peers already in the selection to the front + if outbound > 0 { + ihave := outbound + for i := 1; i < gs.params.D && ihave > 0; i++ { + p := plst[i] + if gs.outbound[p] { + rotate(i) + ihave-- + } + } + } + + // now bubble up enough outbound peers outside the selection to the front + ineed := gs.params.Dout - outbound + for i := gs.params.D; i < len(plst) && ineed > 0; i++ { + p := plst[i] + if gs.outbound[p] { + rotate(i) + ineed-- + } + } + } + + // prune the excess peers + for _, p := range plst[gs.params.D:] { + log.Debug("HEARTBEAT: Remove mesh link to %s in %s", "peer", p, "topic", topic) + prunePeer(p) + } + } + + // do we have enough outboud peers? + if len(peers) >= gs.params.Dlo { + // count the outbound peers we have + outbound := 0 + for p := range peers { + if gs.outbound[p] { + outbound++ + } + } + + // if it's less than D_out, select some peers with outbound connections and graft them + if outbound < gs.params.Dout { + ineed := gs.params.Dout - outbound + backoff := gs.backoff[topic] + plst := gs.getPeers(topic, ineed, func(p enode.ID) bool { + // filter our current and direct peers, peers we are backing off, and peers with negative score + _, inMesh := peers[p] + _, doBackoff := backoff[p] + _, direct := gs.direct[p] + return !inMesh && !doBackoff && !direct && gs.outbound[p] && score(p) >= 0 + }) + + for _, p := range plst { + graftPeer(p) + } + } + } + + // should we try to improve the mesh with opportunistic grafting? + if gs.heartbeatTicks%gs.params.OpportunisticGraftTicks == 0 && len(peers) > 1 { + // Opportunistic grafting works as follows: we check the median score of peers in the + // mesh; if this score is below the opportunisticGraftThreshold, we select a few peers at + // random with score over the median. + // The intention is to (slowly) improve an underperforming mesh by introducing good + // scoring peers that may have been gossiping at us. This allows us to get out of sticky + // situations where we are stuck with poor peers and also recover from churn of good peers. + + // now compute the median peer score in the mesh + plst := peerMapToList(peers) + sort.Slice(plst, func(i, j int) bool { + return score(plst[i]) < score(plst[j]) + }) + medianIndex := len(peers) / 2 + medianScore := scores[plst[medianIndex]] + + // if the median score is below the threshold, select a better peer (if any) and GRAFT + if medianScore < gs.opportunisticGraftThreshold { + backoff := gs.backoff[topic] + plst = gs.getPeers(topic, gs.params.OpportunisticGraftPeers, func(p enode.ID) bool { + _, inMesh := peers[p] + _, doBackoff := backoff[p] + _, direct := gs.direct[p] + return !inMesh && !doBackoff && !direct && score(p) > medianScore + }) + + for _, p := range plst { + log.Debug("HEARTBEAT: Opportunistically graft peer %s on topic %s", "peer", p, "topic", topic) + graftPeer(p) + } + } + } + + // 2nd arg are mesh peers excluded from gossip. We already push + // messages to them, so its redundant to gossip IHAVEs. + gs.emitGossip(topic, peers) + } + + // expire fanout for topics we haven't published to in a while + now := time.Now().UnixNano() + for topic, lastpub := range gs.lastpub { + if lastpub+int64(gs.params.FanoutTTL) < now { + delete(gs.fanout, topic) + delete(gs.lastpub, topic) + } + } + + // maintain our fanout for topics we are publishing but we have not joined + for topic, peers := range gs.fanout { + // check whether our peers are still in the topic and have a score above the publish threshold + for p := range peers { + _, ok := gs.p.topics[topic][p] + if !ok || score(p) < gs.publishThreshold { + delete(peers, p) + } + } + + // do we need more peers? + if len(peers) < gs.params.D { + ineed := gs.params.D - len(peers) + plst := gs.getPeers(topic, ineed, func(p enode.ID) bool { + // filter our current and direct peers and peers with score above the publish threshold + _, inFanout := peers[p] + _, direct := gs.direct[p] + return !inFanout && !direct && score(p) >= gs.publishThreshold + }) + + for _, p := range plst { + peers[p] = struct{}{} + } + } + + // 2nd arg are fanout peers excluded from gossip. We already push + // messages to them, so its redundant to gossip IHAVEs. + gs.emitGossip(topic, peers) + } + + // send coalesced GRAFT/PRUNE messages (will piggyback gossip) + gs.sendGraftPrune(tograft, toprune, noPX) + + // flush all pending gossip that wasn't piggybacked above + gs.flush() + + // advance the message history window + gs.mcache.Shift() +} + +func (gs *GossipSubRouter) clearIHaveCounters() { + if len(gs.peerhave) > 0 { + // throw away the old map and make a new one + gs.peerhave = make(map[enode.ID]int) + } + + if len(gs.iasked) > 0 { + // throw away the old map and make a new one + gs.iasked = make(map[enode.ID]int) + } +} + +func (gs *GossipSubRouter) applyIwantPenalties() { + for p, count := range gs.gossipTracer.GetBrokenPromises() { + log.Info("peer %s didn't follow up in %d IWANT requests; adding penalty", "peer", p, "count", count) + gs.score.AddPenalty(p, count) + } +} + +func (gs *GossipSubRouter) clearBackoff() { + // we only clear once every 15 ticks to avoid iterating over the map(s) too much + if gs.heartbeatTicks%15 != 0 { + return + } + + now := time.Now() + for topic, backoff := range gs.backoff { + for p, expire := range backoff { + // add some slack time to the expiration + // https://github.com/libp2p/specs/pull/289 + if expire.Add(2 * GossipSubHeartbeatInterval).Before(now) { + delete(backoff, p) + } + } + if len(backoff) == 0 { + delete(gs.backoff, topic) + } + } +} + +func (gs *GossipSubRouter) directConnect() { + // we donly do this every some ticks to allow pending connections to complete and account + // for restarts/downtime + if gs.heartbeatTicks%gs.params.DirectConnectTicks != 0 { + return + } + + var toconnect []enode.ID + for p := range gs.direct { + _, connected := gs.peers[p] + if !connected { + toconnect = append(toconnect, p) + } + } + + if len(toconnect) > 0 { + go func() { + for _, p := range toconnect { + gs.connect <- connectInfo{p: p} + } + }() + } +} + +func (gs *GossipSubRouter) sendGraftPrune(tograft, toprune map[enode.ID][]string, noPX map[enode.ID]bool) { + for p, topics := range tograft { + graft := make([]*message.ControlGraft, 0, len(topics)) + for _, topic := range topics { + // copy topic string here since + // the reference to the string + // topic here changes with every + // iteration of the slice. + copiedID := topic + graft = append(graft, &message.ControlGraft{TopicID: &copiedID}) + } + + var prune []*message.ControlPrune + pruning, ok := toprune[p] + if ok { + delete(toprune, p) + prune = make([]*message.ControlPrune, 0, len(pruning)) + for _, topic := range pruning { + prune = append(prune, gs.makePrune(p, topic, gs.doPX && !noPX[p])) + } + } + + out := rpcWithControl(nil, nil, nil, graft, prune) + gs.sendRPC(p, out) + } + + for p, topics := range toprune { + prune := make([]*message.ControlPrune, 0, len(topics)) + for _, topic := range topics { + prune = append(prune, gs.makePrune(p, topic, gs.doPX && !noPX[p])) + } + + out := rpcWithControl(nil, nil, nil, nil, prune) + gs.sendRPC(p, out) + } +} + +// emitGossip emits IHAVE gossip advertising items in the message cache window +// of this topic. +func (gs *GossipSubRouter) emitGossip(topic string, exclude map[enode.ID]struct{}) { + mids := gs.mcache.GetGossipIDs(topic) + if len(mids) == 0 { + return + } + + // shuffle to emit in random order + shuffleStrings(mids) + + // if we are emitting more than GossipSubMaxIHaveLength mids, truncate the list + if len(mids) > gs.params.MaxIHaveLength { + // we do the truncation (with shuffling) per peer below + log.Debug("too many messages for gossip; will truncate IHAVE list (%d messages)", "mids", len(mids)) + } + + // Send gossip to GossipFactor peers above threshold, with a minimum of D_lazy. + // First we collect the peers above gossipThreshold that are not in the exclude set + // and then randomly select from that set. + // We also exclude direct peers, as there is no reason to emit gossip to them. + peers := make([]enode.ID, 0, len(gs.p.topics[topic])) + for p := range gs.p.topics[topic] { + _, inExclude := exclude[p] + _, direct := gs.direct[p] + if !inExclude && !direct && gs.feature(GossipSubFeatureMesh, gs.peers[p]) && gs.score.Score(p) >= gs.gossipThreshold { + peers = append(peers, p) + } + } + + target := gs.params.Dlazy + factor := int(gs.params.GossipFactor * float64(len(peers))) + if factor > target { + target = factor + } + + if target > len(peers) { + target = len(peers) + } else { + shufflePeers(peers) + } + peers = peers[:target] + + // Emit the IHAVE gossip to the selected peers. + for _, p := range peers { + peerMids := mids + if len(mids) > gs.params.MaxIHaveLength { + // we do this per peer so that we emit a different set for each peer. + // we have enough redundancy in the system that this will significantly increase the message + // coverage when we do truncate. + peerMids = make([]string, gs.params.MaxIHaveLength) + shuffleStrings(mids) + copy(peerMids, mids) + } + gs.enqueueGossip(p, &message.ControlIHave{TopicID: &topic, MessageIDs: peerMids}) + } +} + +func (gs *GossipSubRouter) flush() { + // send gossip first, which will also piggyback pending control + for p, ihave := range gs.gossip { + delete(gs.gossip, p) + out := rpcWithControl(nil, ihave, nil, nil, nil) + gs.sendRPC(p, out) + } + + // send the remaining control messages that wasn't merged with gossip + for p, ctl := range gs.control { + delete(gs.control, p) + out := rpcWithControl(nil, nil, nil, ctl.Graft, ctl.Prune) + gs.sendRPC(p, out) + } +} + +func (gs *GossipSubRouter) enqueueGossip(p enode.ID, ihave *message.ControlIHave) { + gossip := gs.gossip[p] + gossip = append(gossip, ihave) + gs.gossip[p] = gossip +} + +func (gs *GossipSubRouter) piggybackGossip(p enode.ID, out *RPC, ihave []*message.ControlIHave) { + ctl := out.GetControl() + if ctl == nil { + ctl = &message.ControlMessage{} + out.Control = ctl + } + + ctl.Ihave = ihave +} + +func (gs *GossipSubRouter) pushControl(p enode.ID, ctl *message.ControlMessage) { + // remove IHAVE/IWANT from control message, gossip is not retried + ctl.Ihave = nil + ctl.Iwant = nil + if ctl.Graft != nil || ctl.Prune != nil { + gs.control[p] = ctl + } +} + +func (gs *GossipSubRouter) piggybackControl(p enode.ID, out *RPC, ctl *message.ControlMessage) { + // check control message for staleness first + var tograft []*message.ControlGraft + var toprune []*message.ControlPrune + + for _, graft := range ctl.GetGraft() { + topic := graft.GetTopicID() + peers, ok := gs.mesh[topic] + if !ok { + continue + } + _, ok = peers[p] + if ok { + tograft = append(tograft, graft) + } + } + + for _, prune := range ctl.GetPrune() { + topic := prune.GetTopicID() + peers, ok := gs.mesh[topic] + if !ok { + toprune = append(toprune, prune) + continue + } + _, ok = peers[p] + if !ok { + toprune = append(toprune, prune) + } + } + + if len(tograft) == 0 && len(toprune) == 0 { + return + } + + xctl := out.Control + if xctl == nil { + xctl = &message.ControlMessage{} + out.Control = xctl + } + + if len(tograft) > 0 { + xctl.Graft = append(xctl.Graft, tograft...) + } + if len(toprune) > 0 { + xctl.Prune = append(xctl.Prune, toprune...) + } +} + +func (gs *GossipSubRouter) makePrune(p enode.ID, topic string, doPX bool) *message.ControlPrune { + if !gs.feature(GossipSubFeaturePX, gs.peers[p]) { + // GossipSub v1.0 -- no peer exchange, the peer won't be able to parse it anyway + return &message.ControlPrune{TopicID: &topic} + } + + backoff := uint64(gs.params.PruneBackoff / time.Second) + var px []*message.PeerInfo + if doPX { + // select peers for Peer eXchange + /*peers := gs.getPeers(topic, gs.params.PrunePeers, func(xp enode.ID) bool { + return p != xp && gs.score.Score(xp) >= 0 + }) + + cab, ok := peerstore.GetCertifiedAddrBook(gs.p.host.Peerstore()) + px = make([]*message.PeerInfo, 0, len(peers)) + for _, p := range peers { + // see if we have a signed peer record to send back; if we don't, just send + // the peer ID and let the pruned peer find them in the DHT -- we can't trust + // unsigned address records through px anyway. + var recordBytes []byte + if ok { + spr := cab.GetPeerRecord(p) + var err error + if spr != nil { + recordBytes, err = spr.Marshal() + if err != nil { + log.Warnf("error marshaling signed peer record for %s: %s", p, err) + } + } + } + px = append(px, &message.PeerInfo{PeerID: []byte(p), SignedPeerRecord: recordBytes}) + }*/ + } + + return &message.ControlPrune{TopicID: &topic, Peers: px, Backoff: &backoff} +} + +func (gs *GossipSubRouter) getPeers(topic string, count int, filter func(enode.ID) bool) []enode.ID { + tmap, ok := gs.p.topics[topic] + if !ok { + return nil + } + + peers := make([]enode.ID, 0, len(tmap)) + for p := range tmap { + if gs.feature(GossipSubFeatureMesh, gs.peers[p]) && filter(p) && gs.p.peerFilter(p, topic) { + peers = append(peers, p) + } + } + + shufflePeers(peers) + + if count > 0 && len(peers) > count { + peers = peers[:count] + } + + return peers +} + +func peerListToMap(peers []enode.ID) map[enode.ID]struct{} { + pmap := make(map[enode.ID]struct{}) + for _, p := range peers { + pmap[p] = struct{}{} + } + return pmap +} + +func peerMapToList(peers map[enode.ID]struct{}) []enode.ID { + plst := make([]enode.ID, 0, len(peers)) + for p := range peers { + plst = append(plst, p) + } + return plst +} + +func shufflePeers(peers []enode.ID) { + for i := range peers { + j := rand.Intn(i + 1) + peers[i], peers[j] = peers[j], peers[i] + } +} + +func shufflePeerInfo(peers []*message.PeerInfo) { + for i := range peers { + j := rand.Intn(i + 1) + peers[i], peers[j] = peers[j], peers[i] + } +} + +func shuffleStrings(lst []string) { + for i := range lst { + j := rand.Intn(i + 1) + lst[i], lst[j] = lst[j], lst[i] + } +} diff --git a/p2p/pubsub/gossipsub_feat.go b/p2p/pubsub/gossipsub_feat.go new file mode 100644 index 0000000000..d95724a2b6 --- /dev/null +++ b/p2p/pubsub/gossipsub_feat.go @@ -0,0 +1,50 @@ +package pubsub + +import ( + "fmt" +) + +// GossipSubFeatureTest is a feature test function; it takes a feature and a protocol ID and +// should return true if the feature is supported by the protocol +type GossipSubFeatureTest = func(GossipSubFeature, ProtocolID) bool + +// GossipSubFeature is a feature discriminant enum +type GossipSubFeature int + +const ( + // Protocol supports basic GossipSub Mesh -- gossipsub-v1.0 compatible + GossipSubFeatureMesh = iota + // Protocol supports Peer eXchange on prune -- gossipsub-v1.1 compatible + GossipSubFeaturePX +) + +// GossipSubDefaultProtocols is the default gossipsub router protocol list +var GossipSubDefaultProtocols = []ProtocolID{GossipSubID_v11, GossipSubID_v10} + +// GossipSubDefaultFeatures is the feature test function for the default gossipsub protocols +func GossipSubDefaultFeatures(feat GossipSubFeature, proto ProtocolID) bool { + switch feat { + case GossipSubFeatureMesh: + return proto == GossipSubID_v11 || proto == GossipSubID_v10 + case GossipSubFeaturePX: + return proto == GossipSubID_v11 + default: + return false + } +} + +// WithGossipSubProtocols is a gossipsub router option that configures a custom protocol list +// and feature test function +func WithGossipSubProtocols(protos []ProtocolID, feature GossipSubFeatureTest) Option { + return func(ps *PubSub) error { + gs, ok := ps.rt.(*GossipSubRouter) + if !ok { + return fmt.Errorf("pubsub router is not gossipsub") + } + + gs.protos = protos + gs.feature = feature + + return nil + } +} diff --git a/p2p/pubsub/interfaces.go b/p2p/pubsub/interfaces.go new file mode 100644 index 0000000000..90fe86191c --- /dev/null +++ b/p2p/pubsub/interfaces.go @@ -0,0 +1,191 @@ +package pubsub + +import ( + "context" + "io" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p" + + "github.com/libp2p/go-libp2p-core/connmgr" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +// Connectedness signals the capacity for a connection with a given node. +// It is used to signal to services and other peers whether a node is reachable. +type Connectedness int + +const ( + // NotConnected means no connection to peer, and no extra information (default) + NotConnected Connectedness = iota + + // Connected means has an open, live connection to peer + Connected + + // CanConnect means recently connected to peer, terminated gracefully + CanConnect + + // CannotConnect means recently attempted connecting but failed to connect. + // (should signal "made effort, failed") + CannotConnect +) + +type ProtocolID string + +// Host is an object participating in a p2p network, which +// implements protocols or provides services. It handles +// requests like a Server, and issues requests like a Client. +// It is called Host because it is both Server and Client (and Peer +// may be confusing). +type Host interface { + // ID returns the (local) peer.ID associated with this Host + ID() *enode.Node + + // Peerstore returns the Host's repository of Peer Addresses and Keys. + Peerstore() Peerstore + + // Networks returns the Network interface of the Host + Network() Network + + // Connect ensures there is a connection between this host and the peer with + // given peer.ID. Connect will absorb the addresses in pi into its internal + // peerstore. If there is not an active connection, Connect will issue a + // h.Network.Dial, and block until a connection is open, or an error is + // returned. // TODO: Relay + NAT. + Connect(ctx context.Context, pi enode.ID) error + + // SetStreamHandler sets the protocol handler on the Host's Mux. + // This is equivalent to: + // host.Mux().SetHandler(proto, handler) + // (Threadsafe) + SetStreamHandler(pid ProtocolID, handler StreamHandler) + + // SetStreamHandlerMatch sets the protocol handler on the Host's Mux + // using a matching function for protocol selection. + SetStreamHandlerMatch(ProtocolID, func(string) bool, StreamHandler) + + // RemoveStreamHandler removes a handler on the mux that was set by + // SetStreamHandler + RemoveStreamHandler(pid ProtocolID) + + // NewStream opens a new stream to given peer p, and writes a p2p/protocol + // header with given ProtocolID. If there is no connection to p, attempts + // to create one. If ProtocolID is "", writes no header. + // (Threadsafe) + NewStream(ctx context.Context, p enode.ID, pids ...ProtocolID) (Stream, error) + + // Close shuts down the host, its Network, and services. + Close() error + + // ConnManager returns this hosts connection manager + ConnManager() connmgr.ConnManager +} + +// StreamHandler is the type of function used to listen for +// streams opened by the remote side. +type StreamHandler func(Stream) + +// Network is the interface used to connect to the outside world. +// It dials and listens for connections. it uses a Swarm to pool +// connections (see swarm pkg, and peerstream.Swarm). Connections +// are encrypted with a TLS-like protocol. +type Network interface { + io.Closer + + // ConnsToPeer returns the connections in this Netowrk for given peer. + ConnsToPeer(p enode.ID) []Conn + + // Connectedness returns a state signaling connection capabilities + Connectedness(enode.ID) Connectedness + + // Notify/StopNotify register and unregister a notifiee for signals + Notify(Notifiee) + + // Peers returns the peers connected + Peers() []enode.ID +} + +type Notifiee interface { + Connected(Network, Conn) // called when a connection opened +} + +// Stream represents a bidirectional channel between two agents in +// a libp2p network. "agent" is as granular as desired, potentially +// being a "request -> reply" pair, or whole protocols. +// +// Streams are backed by a multiplexer underneath the hood. +type Stream interface { + Protocol() ProtocolID + + // Conn returns the connection this stream is part of. + Conn() Conn + + ReadWriter() p2p.MsgReadWriter // + + // Reset closes both ends of the stream. Use this to tell the remote + // side to hang up and go away. + Reset() error + + // Close closes the stream. + // + // * Any buffered data for writing will be flushed. + // * Future reads will fail. + // * Any in-progress reads/writes will be interrupted. + // + // Close may be asynchronous and _does not_ guarantee receipt of the + // data. + // + // Close closes the stream for both reading and writing. + // Close is equivalent to calling `CloseRead` and `CloseWrite`. Importantly, Close will not wait for any form of acknowledgment. + // If acknowledgment is required, the caller must call `CloseWrite`, then wait on the stream for a response (or an EOF), + // then call Close() to free the stream object. + // + // When done with a stream, the user must call either Close() or `Reset()` to discard the stream, even after calling `CloseRead` and/or `CloseWrite`. + io.Closer +} + +// Conn is a connection to a remote peer. It multiplexes streams. +// Usually there is no need to use a Conn directly, but it may +// be useful to get information about the peer on the other side: +// stream.Conn().RemotePeer() +type Conn interface { + io.Closer + + // ID returns an identifier that uniquely identifies this Conn within this + // host, during this run. Connection IDs may repeat across restarts. + ID() string + + // GetStreams returns all open streams over this conn. + GetStreams() []Stream + + // Stat stores metadata pertaining to this conn. + Stat() Stat + + // RemotePeer returns the peer ID of the remote peer. + RemotePeer() *enode.Node +} + +// Stat stores metadata pertaining to a given Stream/Conn. +type Stat struct { + // Direction specifies whether this is an inbound or an outbound connection. + Direction Direction + // Opened is the timestamp when this connection was opened. + Opened time.Time + // Transient indicates that this connection is transient and may be closed soon. + Transient bool + // Extra stores additional metadata about this connection. + Extra map[interface{}]interface{} +} + +// Direction represents which peer in a stream initiated a connection. +type Direction int + +const ( + // DirUnknown is the default direction. + DirUnknown Direction = iota + // DirInbound is for when the remote peer initiated a connection. + DirInbound + // DirOutbound is for when the local peer initiated a connection. + DirOutbound +) diff --git a/p2p/pubsub/mcache.go b/p2p/pubsub/mcache.go new file mode 100644 index 0000000000..87fa2d7ad5 --- /dev/null +++ b/p2p/pubsub/mcache.go @@ -0,0 +1,104 @@ +package pubsub + +import ( + "fmt" + + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +// NewMessageCache creates a sliding window cache that remembers messages for as +// long as `history` slots. +// +// When queried for messages to advertise, the cache only returns messages in +// the last `gossip` slots. +// +// The `gossip` parameter must be smaller or equal to `history`, or this +// function will panic. +// +// The slack between `gossip` and `history` accounts for the reaction time +// between when a message is advertised via IHAVE gossip, and the peer pulls it +// via an IWANT command. +func NewMessageCache(gossip, history int) *MessageCache { + if gossip > history { + err := fmt.Errorf("invalid parameters for message cache; gossip slots (%d) cannot be larger than history slots (%d)", + gossip, history) + panic(err) + } + return &MessageCache{ + msgs: make(map[string]*message.Message), + peertx: make(map[string]map[enode.ID]int), + history: make([][]CacheEntry, history), + gossip: gossip, + msgID: DefaultMsgIdFn, + } +} + +type MessageCache struct { + msgs map[string]*message.Message + peertx map[string]map[enode.ID]int + history [][]CacheEntry + gossip int + msgID MsgIdFunction +} + +func (mc *MessageCache) SetMsgIdFn(msgID MsgIdFunction) { + mc.msgID = msgID +} + +type CacheEntry struct { + mid string + topic string +} + +func (mc *MessageCache) Put(msg *message.Message) { + mid := mc.msgID(msg) + mc.msgs[mid] = msg + mc.history[0] = append(mc.history[0], CacheEntry{mid: mid, topic: msg.GetTopic()}) +} + +func (mc *MessageCache) Get(mid string) (*message.Message, bool) { + m, ok := mc.msgs[mid] + return m, ok +} + +func (mc *MessageCache) GetForPeer(mid string, p enode.ID) (*message.Message, int, bool) { + m, ok := mc.msgs[mid] + if !ok { + return nil, 0, false + } + + tx, ok := mc.peertx[mid] + if !ok { + tx = make(map[enode.ID]int) + mc.peertx[mid] = tx + } + tx[p]++ + + return m, tx[p], true +} + +func (mc *MessageCache) GetGossipIDs(topic string) []string { + var mids []string + for _, entries := range mc.history[:mc.gossip] { + for _, entry := range entries { + if entry.topic == topic { + mids = append(mids, entry.mid) + } + } + } + return mids +} + +func (mc *MessageCache) Shift() { + last := mc.history[len(mc.history)-1] + for _, entry := range last { + delete(mc.msgs, entry.mid) + delete(mc.peertx, entry.mid) + } + for i := len(mc.history) - 2; i >= 0; i-- { + mc.history[i+1] = mc.history[i] + } + mc.history[0] = nil +} diff --git a/p2p/pubsub/message/mesage.go b/p2p/pubsub/message/mesage.go new file mode 100644 index 0000000000..dcb10f5f7a --- /dev/null +++ b/p2p/pubsub/message/mesage.go @@ -0,0 +1,250 @@ +package message + +import "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + +type RPC struct { + Subscriptions []*RPC_SubOpts + Publish []*Message + Control *ControlMessage +} + +func (m *RPC) Size() (n int) { + return 0 +} + +func (m *RPC) GetSubscriptions() []*RPC_SubOpts { + if m != nil { + return m.Subscriptions + } + return nil +} + +func (m *RPC) GetPublish() []*Message { + if m != nil { + return m.Publish + } + return nil +} + +func (m *RPC) GetControl() *ControlMessage { + if m != nil { + return m.Control + } + return nil +} + +type RPC_SubOpts struct { + Subscribe *bool + Topicid *string +} + +func (m *RPC_SubOpts) GetSubscribe() bool { + if m != nil && m.Subscribe != nil { + return *m.Subscribe + } + return false +} + +func (m *RPC_SubOpts) GetTopicid() string { + if m != nil && m.Topicid != nil { + return *m.Topicid + } + return "" +} + +func (m *RPC_SubOpts) Size() (n int) { + return 0 +} + +type Message struct { + From enode.ID + Data []byte + Seqno []byte + Topic *string + Signature []byte + Key []byte +} + +func (m *Message) GetFrom() enode.ID { + if m != nil { + return m.From + } + return enode.ZeroID +} + +func (m *Message) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +func (m *Message) GetSeqno() []byte { + if m != nil { + return m.Seqno + } + return nil +} + +func (m *Message) GetTopic() string { + if m != nil && m.Topic != nil { + return *m.Topic + } + return "" +} + +func (m *Message) GetSignature() []byte { + if m != nil { + return m.Signature + } + return nil +} + +func (m *Message) GetKey() []byte { + if m != nil { + return m.Key + } + return nil +} + +func (m *Message) Size() (n int) { + return 0 +} + +type ControlMessage struct { + Ihave []*ControlIHave + Iwant []*ControlIWant + Graft []*ControlGraft + Prune []*ControlPrune +} + +func (m *ControlMessage) GetIhave() []*ControlIHave { + if m != nil { + return m.Ihave + } + return nil +} + +func (m *ControlMessage) GetIwant() []*ControlIWant { + if m != nil { + return m.Iwant + } + return nil +} + +func (m *ControlMessage) GetGraft() []*ControlGraft { + if m != nil { + return m.Graft + } + return nil +} + +func (m *ControlMessage) GetPrune() []*ControlPrune { + if m != nil { + return m.Prune + } + return nil +} + +type ControlIHave struct { + TopicID *string + MessageIDs []string +} + +func (m *ControlIHave) GetTopicID() string { + if m != nil && m.TopicID != nil { + return *m.TopicID + } + return "" +} + +func (m *ControlIHave) GetMessageIDs() []string { + if m != nil { + return m.MessageIDs + } + return nil +} + +func (m *ControlIHave) Size() (n int) { + return 0 +} + +type ControlIWant struct { + MessageIDs []string +} + +func (m *ControlIWant) GetMessageIDs() []string { + if m != nil { + return m.MessageIDs + } + return nil +} + +func (m *ControlIWant) Size() (n int) { + return 0 +} + +type ControlGraft struct { + TopicID *string +} + +func (m *ControlGraft) GetTopicID() string { + if m != nil && m.TopicID != nil { + return *m.TopicID + } + return "" +} + +func (m *ControlGraft) Size() (n int) { + return 0 +} + +type ControlPrune struct { + TopicID *string + Peers []*PeerInfo + Backoff *uint64 +} + +func (m *ControlPrune) GetTopicID() string { + if m != nil && m.TopicID != nil { + return *m.TopicID + } + return "" +} + +func (m *ControlPrune) GetPeers() []*PeerInfo { + if m != nil { + return m.Peers + } + return nil +} + +func (m *ControlPrune) GetBackoff() uint64 { + if m != nil && m.Backoff != nil { + return *m.Backoff + } + return 0 +} + +func (m *ControlPrune) Size() (n int) { + return 0 +} + +type PeerInfo struct { + PeerID enode.ID + SignedPeerRecord []byte +} + +func (m *PeerInfo) GetPeerID() enode.ID { + if m != nil { + return m.PeerID + } + return enode.ZeroID +} + +func (m *PeerInfo) GetSignedPeerRecord() []byte { + if m != nil { + return m.SignedPeerRecord + } + return nil +} diff --git a/p2p/pubsub/message/trace.go b/p2p/pubsub/message/trace.go new file mode 100644 index 0000000000..c1e642588e --- /dev/null +++ b/p2p/pubsub/message/trace.go @@ -0,0 +1,715 @@ +package message + +import ( + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + proto "github.com/gogo/protobuf/proto" +) + +type TraceEvent_Type int32 + +const ( + TraceEvent_PUBLISH_MESSAGE TraceEvent_Type = 0 + TraceEvent_REJECT_MESSAGE TraceEvent_Type = 1 + TraceEvent_DUPLICATE_MESSAGE TraceEvent_Type = 2 + TraceEvent_DELIVER_MESSAGE TraceEvent_Type = 3 + TraceEvent_ADD_PEER TraceEvent_Type = 4 + TraceEvent_REMOVE_PEER TraceEvent_Type = 5 + TraceEvent_RECV_RPC TraceEvent_Type = 6 + TraceEvent_SEND_RPC TraceEvent_Type = 7 + TraceEvent_DROP_RPC TraceEvent_Type = 8 + TraceEvent_JOIN TraceEvent_Type = 9 + TraceEvent_LEAVE TraceEvent_Type = 10 + TraceEvent_GRAFT TraceEvent_Type = 11 + TraceEvent_PRUNE TraceEvent_Type = 12 +) + +var TraceEvent_Type_name = map[int32]string{ + 0: "PUBLISH_MESSAGE", + 1: "REJECT_MESSAGE", + 2: "DUPLICATE_MESSAGE", + 3: "DELIVER_MESSAGE", + 4: "ADD_PEER", + 5: "REMOVE_PEER", + 6: "RECV_RPC", + 7: "SEND_RPC", + 8: "DROP_RPC", + 9: "JOIN", + 10: "LEAVE", + 11: "GRAFT", + 12: "PRUNE", +} + +var TraceEvent_Type_value = map[string]int32{ + "PUBLISH_MESSAGE": 0, + "REJECT_MESSAGE": 1, + "DUPLICATE_MESSAGE": 2, + "DELIVER_MESSAGE": 3, + "ADD_PEER": 4, + "REMOVE_PEER": 5, + "RECV_RPC": 6, + "SEND_RPC": 7, + "DROP_RPC": 8, + "JOIN": 9, + "LEAVE": 10, + "GRAFT": 11, + "PRUNE": 12, +} + +func (x TraceEvent_Type) Enum() *TraceEvent_Type { + p := new(TraceEvent_Type) + *p = x + return p +} + +func (x TraceEvent_Type) String() string { + return proto.EnumName(TraceEvent_Type_name, int32(x)) +} + +func (x *TraceEvent_Type) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(TraceEvent_Type_value, data, "TraceEvent_Type") + if err != nil { + return err + } + *x = TraceEvent_Type(value) + return nil +} + +type TraceEvent struct { + Type *TraceEvent_Type `protobuf:"varint,1,opt,name=type,enum=pubsub.pb.TraceEvent_Type" json:"type,omitempty"` + PeerID enode.ID `protobuf:"bytes,2,opt,name=peerID" json:"peerID,omitempty"` + Timestamp *int64 `protobuf:"varint,3,opt,name=timestamp" json:"timestamp,omitempty"` + PublishMessage *TraceEvent_PublishMessage `protobuf:"bytes,4,opt,name=publishMessage" json:"publishMessage,omitempty"` + RejectMessage *TraceEvent_RejectMessage `protobuf:"bytes,5,opt,name=rejectMessage" json:"rejectMessage,omitempty"` + DuplicateMessage *TraceEvent_DuplicateMessage `protobuf:"bytes,6,opt,name=duplicateMessage" json:"duplicateMessage,omitempty"` + DeliverMessage *TraceEvent_DeliverMessage `protobuf:"bytes,7,opt,name=deliverMessage" json:"deliverMessage,omitempty"` + AddPeer *TraceEvent_AddPeer `protobuf:"bytes,8,opt,name=addPeer" json:"addPeer,omitempty"` + RemovePeer *TraceEvent_RemovePeer `protobuf:"bytes,9,opt,name=removePeer" json:"removePeer,omitempty"` + RecvRPC *TraceEvent_RecvRPC `protobuf:"bytes,10,opt,name=recvRPC" json:"recvRPC,omitempty"` + SendRPC *TraceEvent_SendRPC `protobuf:"bytes,11,opt,name=sendRPC" json:"sendRPC,omitempty"` + DropRPC *TraceEvent_DropRPC `protobuf:"bytes,12,opt,name=dropRPC" json:"dropRPC,omitempty"` + Join *TraceEvent_Join `protobuf:"bytes,13,opt,name=join" json:"join,omitempty"` + Leave *TraceEvent_Leave `protobuf:"bytes,14,opt,name=leave" json:"leave,omitempty"` + Graft *TraceEvent_Graft `protobuf:"bytes,15,opt,name=graft" json:"graft,omitempty"` + Prune *TraceEvent_Prune `protobuf:"bytes,16,opt,name=prune" json:"prune,omitempty"` +} + +func (m *TraceEvent) Reset() { *m = TraceEvent{} } +func (m *TraceEvent) String() string { return "" } +func (*TraceEvent) ProtoMessage() {} + +func (m *TraceEvent) GetType() TraceEvent_Type { + if m != nil && m.Type != nil { + return *m.Type + } + return TraceEvent_PUBLISH_MESSAGE +} + +func (m *TraceEvent) GetPeerID() enode.ID { + if m != nil { + return m.PeerID + } + return enode.ZeroID +} + +func (m *TraceEvent) GetTimestamp() int64 { + if m != nil && m.Timestamp != nil { + return *m.Timestamp + } + return 0 +} + +func (m *TraceEvent) GetPublishMessage() *TraceEvent_PublishMessage { + if m != nil { + return m.PublishMessage + } + return nil +} + +func (m *TraceEvent) GetRejectMessage() *TraceEvent_RejectMessage { + if m != nil { + return m.RejectMessage + } + return nil +} + +func (m *TraceEvent) GetDuplicateMessage() *TraceEvent_DuplicateMessage { + if m != nil { + return m.DuplicateMessage + } + return nil +} + +func (m *TraceEvent) GetDeliverMessage() *TraceEvent_DeliverMessage { + if m != nil { + return m.DeliverMessage + } + return nil +} + +func (m *TraceEvent) GetAddPeer() *TraceEvent_AddPeer { + if m != nil { + return m.AddPeer + } + return nil +} + +func (m *TraceEvent) GetRemovePeer() *TraceEvent_RemovePeer { + if m != nil { + return m.RemovePeer + } + return nil +} + +func (m *TraceEvent) GetRecvRPC() *TraceEvent_RecvRPC { + if m != nil { + return m.RecvRPC + } + return nil +} + +func (m *TraceEvent) GetSendRPC() *TraceEvent_SendRPC { + if m != nil { + return m.SendRPC + } + return nil +} + +func (m *TraceEvent) GetDropRPC() *TraceEvent_DropRPC { + if m != nil { + return m.DropRPC + } + return nil +} + +func (m *TraceEvent) GetJoin() *TraceEvent_Join { + if m != nil { + return m.Join + } + return nil +} + +func (m *TraceEvent) GetLeave() *TraceEvent_Leave { + if m != nil { + return m.Leave + } + return nil +} + +func (m *TraceEvent) GetGraft() *TraceEvent_Graft { + if m != nil { + return m.Graft + } + return nil +} + +func (m *TraceEvent) GetPrune() *TraceEvent_Prune { + if m != nil { + return m.Prune + } + return nil +} + +type TraceEvent_PublishMessage struct { + MessageID []byte `protobuf:"bytes,1,opt,name=messageID" json:"messageID,omitempty"` + Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` +} + +func (m *TraceEvent_PublishMessage) Reset() { *m = TraceEvent_PublishMessage{} } +func (m *TraceEvent_PublishMessage) String() string { return "" } +func (*TraceEvent_PublishMessage) ProtoMessage() {} + +func (m *TraceEvent_PublishMessage) GetMessageID() []byte { + if m != nil { + return m.MessageID + } + return nil +} + +func (m *TraceEvent_PublishMessage) GetTopic() string { + if m != nil && m.Topic != nil { + return *m.Topic + } + return "" +} + +type TraceEvent_RejectMessage struct { + MessageID []byte `protobuf:"bytes,1,opt,name=messageID" json:"messageID,omitempty"` + ReceivedFrom enode.ID `protobuf:"bytes,2,opt,name=receivedFrom" json:"receivedFrom,omitempty"` + Reason *string `protobuf:"bytes,3,opt,name=reason" json:"reason,omitempty"` + Topic *string `protobuf:"bytes,4,opt,name=topic" json:"topic,omitempty"` +} + +func (m *TraceEvent_RejectMessage) Reset() { *m = TraceEvent_RejectMessage{} } +func (m *TraceEvent_RejectMessage) String() string { return "" } +func (*TraceEvent_RejectMessage) ProtoMessage() {} + +func (m *TraceEvent_RejectMessage) GetMessageID() []byte { + if m != nil { + return m.MessageID + } + return nil +} + +func (m *TraceEvent_RejectMessage) GetReceivedFrom() enode.ID { + if m != nil { + return m.ReceivedFrom + } + return enode.ZeroID +} + +func (m *TraceEvent_RejectMessage) GetReason() string { + if m != nil && m.Reason != nil { + return *m.Reason + } + return "" +} + +func (m *TraceEvent_RejectMessage) GetTopic() string { + if m != nil && m.Topic != nil { + return *m.Topic + } + return "" +} + +type TraceEvent_DuplicateMessage struct { + MessageID []byte `protobuf:"bytes,1,opt,name=messageID" json:"messageID,omitempty"` + ReceivedFrom enode.ID `protobuf:"bytes,2,opt,name=receivedFrom" json:"receivedFrom,omitempty"` + Topic *string `protobuf:"bytes,3,opt,name=topic" json:"topic,omitempty"` +} + +func (m *TraceEvent_DuplicateMessage) Reset() { *m = TraceEvent_DuplicateMessage{} } +func (m *TraceEvent_DuplicateMessage) String() string { return "" } +func (*TraceEvent_DuplicateMessage) ProtoMessage() {} + +func (m *TraceEvent_DuplicateMessage) GetMessageID() []byte { + if m != nil { + return m.MessageID + } + return nil +} + +func (m *TraceEvent_DuplicateMessage) GetReceivedFrom() enode.ID { + if m != nil { + return m.ReceivedFrom + } + return enode.ZeroID +} + +func (m *TraceEvent_DuplicateMessage) GetTopic() string { + if m != nil && m.Topic != nil { + return *m.Topic + } + return "" +} + +type TraceEvent_DeliverMessage struct { + MessageID []byte `protobuf:"bytes,1,opt,name=messageID" json:"messageID,omitempty"` + Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` + ReceivedFrom enode.ID `protobuf:"bytes,3,opt,name=receivedFrom" json:"receivedFrom,omitempty"` +} + +func (m *TraceEvent_DeliverMessage) Reset() { *m = TraceEvent_DeliverMessage{} } +func (m *TraceEvent_DeliverMessage) String() string { return "" } +func (*TraceEvent_DeliverMessage) ProtoMessage() {} + +func (m *TraceEvent_DeliverMessage) GetMessageID() []byte { + if m != nil { + return m.MessageID + } + return nil +} + +func (m *TraceEvent_DeliverMessage) GetTopic() string { + if m != nil && m.Topic != nil { + return *m.Topic + } + return "" +} + +func (m *TraceEvent_DeliverMessage) GetReceivedFrom() enode.ID { + if m != nil { + return m.ReceivedFrom + } + return enode.ZeroID +} + +type TraceEvent_AddPeer struct { + PeerID enode.ID `json:"peerID,omitempty"` + Proto *string `json:"proto,omitempty"` +} + +func (m *TraceEvent_AddPeer) Reset() { *m = TraceEvent_AddPeer{} } +func (m *TraceEvent_AddPeer) String() string { return "" } +func (*TraceEvent_AddPeer) ProtoMessage() {} + +func (m *TraceEvent_AddPeer) GetPeerID() enode.ID { + if m != nil { + return m.PeerID + } + return enode.ZeroID +} + +func (m *TraceEvent_AddPeer) GetProto() string { + if m != nil && m.Proto != nil { + return *m.Proto + } + return "" +} + +type TraceEvent_RemovePeer struct { + PeerID enode.ID `json:"peerID,omitempty"` +} + +func (m *TraceEvent_RemovePeer) Reset() { *m = TraceEvent_RemovePeer{} } +func (m *TraceEvent_RemovePeer) String() string { return "" } +func (*TraceEvent_RemovePeer) ProtoMessage() {} + +func (m *TraceEvent_RemovePeer) GetPeerID() enode.ID { + if m != nil { + return m.PeerID + } + return enode.ZeroID +} + +type TraceEvent_RecvRPC struct { + ReceivedFrom enode.ID `json:"receivedFrom,omitempty"` + Meta *TraceEvent_RPCMeta `json:"meta,omitempty"` +} + +func (m *TraceEvent_RecvRPC) Reset() { *m = TraceEvent_RecvRPC{} } +func (m *TraceEvent_RecvRPC) String() string { return "" } +func (*TraceEvent_RecvRPC) ProtoMessage() {} + +func (m *TraceEvent_RecvRPC) GetReceivedFrom() enode.ID { + if m != nil { + return m.ReceivedFrom + } + return enode.ZeroID +} + +func (m *TraceEvent_RecvRPC) GetMeta() *TraceEvent_RPCMeta { + if m != nil { + return m.Meta + } + return nil +} + +type TraceEvent_SendRPC struct { + SendTo enode.ID `protobuf:"bytes,1,opt,name=sendTo" json:"sendTo,omitempty"` + Meta *TraceEvent_RPCMeta `protobuf:"bytes,2,opt,name=meta" json:"meta,omitempty"` +} + +func (m *TraceEvent_SendRPC) Reset() { *m = TraceEvent_SendRPC{} } +func (m *TraceEvent_SendRPC) String() string { return "" } +func (*TraceEvent_SendRPC) ProtoMessage() {} + +func (m *TraceEvent_SendRPC) GetSendTo() enode.ID { + if m != nil { + return m.SendTo + } + return enode.ZeroID +} + +func (m *TraceEvent_SendRPC) GetMeta() *TraceEvent_RPCMeta { + if m != nil { + return m.Meta + } + return nil +} + +type TraceEvent_DropRPC struct { + SendTo enode.ID `json:"sendTo,omitempty"` + Meta *TraceEvent_RPCMeta `json:"meta,omitempty"` +} + +func (m *TraceEvent_DropRPC) Reset() { *m = TraceEvent_DropRPC{} } +func (m *TraceEvent_DropRPC) String() string { return "" } +func (*TraceEvent_DropRPC) ProtoMessage() {} + +func (m *TraceEvent_DropRPC) GetSendTo() enode.ID { + if m != nil { + return m.SendTo + } + return enode.ZeroID +} + +func (m *TraceEvent_DropRPC) GetMeta() *TraceEvent_RPCMeta { + if m != nil { + return m.Meta + } + return nil +} + +type TraceEvent_Join struct { + Topic *string `protobuf:"bytes,1,opt,name=topic" json:"topic,omitempty"` +} + +func (m *TraceEvent_Join) Reset() { *m = TraceEvent_Join{} } +func (m *TraceEvent_Join) String() string { return "" } +func (*TraceEvent_Join) ProtoMessage() {} + +func (m *TraceEvent_Join) GetTopic() string { + if m != nil && m.Topic != nil { + return *m.Topic + } + return "" +} + +type TraceEvent_Leave struct { + Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` +} + +func (m *TraceEvent_Leave) Reset() { *m = TraceEvent_Leave{} } +func (m *TraceEvent_Leave) String() string { return "" } +func (*TraceEvent_Leave) ProtoMessage() {} + +func (m *TraceEvent_Leave) GetTopic() string { + if m != nil && m.Topic != nil { + return *m.Topic + } + return "" +} + +type TraceEvent_Graft struct { + PeerID enode.ID `protobuf:"bytes,1,opt,name=peerID" json:"peerID,omitempty"` + Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` +} + +func (m *TraceEvent_Graft) Reset() { *m = TraceEvent_Graft{} } +func (m *TraceEvent_Graft) String() string { return "" } + +func (m *TraceEvent_Graft) GetPeerID() enode.ID { + if m != nil { + return m.PeerID + } + return enode.ZeroID +} + +func (m *TraceEvent_Graft) GetTopic() string { + if m != nil && m.Topic != nil { + return *m.Topic + } + return "" +} + +type TraceEvent_Prune struct { + PeerID enode.ID `json:"peerID,omitempty"` + Topic *string `json:"topic,omitempty"` +} + +func (m *TraceEvent_Prune) Reset() { *m = TraceEvent_Prune{} } +func (m *TraceEvent_Prune) String() string { return "" } + +func (m *TraceEvent_Prune) GetPeerID() enode.ID { + if m != nil { + return m.PeerID + } + return enode.ZeroID +} + +func (m *TraceEvent_Prune) GetTopic() string { + if m != nil && m.Topic != nil { + return *m.Topic + } + return "" +} + +type TraceEvent_RPCMeta struct { + Messages []*TraceEvent_MessageMeta `protobuf:"bytes,1,rep,name=messages" json:"messages,omitempty"` + Subscription []*TraceEvent_SubMeta `protobuf:"bytes,2,rep,name=subscription" json:"subscription,omitempty"` + Control *TraceEvent_ControlMeta `protobuf:"bytes,3,opt,name=control" json:"control,omitempty"` +} + +func (m *TraceEvent_RPCMeta) Reset() { *m = TraceEvent_RPCMeta{} } +func (m *TraceEvent_RPCMeta) String() string { return "" } + +func (m *TraceEvent_RPCMeta) GetMessages() []*TraceEvent_MessageMeta { + if m != nil { + return m.Messages + } + return nil +} + +func (m *TraceEvent_RPCMeta) GetSubscription() []*TraceEvent_SubMeta { + if m != nil { + return m.Subscription + } + return nil +} + +func (m *TraceEvent_RPCMeta) GetControl() *TraceEvent_ControlMeta { + if m != nil { + return m.Control + } + return nil +} + +type TraceEvent_MessageMeta struct { + MessageID []byte `protobuf:"bytes,1,opt,name=messageID" json:"messageID,omitempty"` + Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` +} + +func (m *TraceEvent_MessageMeta) Reset() { *m = TraceEvent_MessageMeta{} } +func (m *TraceEvent_MessageMeta) String() string { return "" } + +func (m *TraceEvent_MessageMeta) GetMessageID() []byte { + if m != nil { + return m.MessageID + } + return nil +} + +func (m *TraceEvent_MessageMeta) GetTopic() string { + if m != nil && m.Topic != nil { + return *m.Topic + } + return "" +} + +type TraceEvent_SubMeta struct { + Subscribe *bool `protobuf:"varint,1,opt,name=subscribe" json:"subscribe,omitempty"` + Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` +} + +func (m *TraceEvent_SubMeta) Reset() { *m = TraceEvent_SubMeta{} } +func (m *TraceEvent_SubMeta) String() string { return "" } +func (*TraceEvent_SubMeta) ProtoMessage() {} + +func (m *TraceEvent_SubMeta) GetSubscribe() bool { + if m != nil && m.Subscribe != nil { + return *m.Subscribe + } + return false +} + +func (m *TraceEvent_SubMeta) GetTopic() string { + if m != nil && m.Topic != nil { + return *m.Topic + } + return "" +} + +type TraceEvent_ControlMeta struct { + Ihave []*TraceEvent_ControlIHaveMeta `protobuf:"bytes,1,rep,name=ihave" json:"ihave,omitempty"` + Iwant []*TraceEvent_ControlIWantMeta `protobuf:"bytes,2,rep,name=iwant" json:"iwant,omitempty"` + Graft []*TraceEvent_ControlGraftMeta `protobuf:"bytes,3,rep,name=graft" json:"graft,omitempty"` + Prune []*TraceEvent_ControlPruneMeta `protobuf:"bytes,4,rep,name=prune" json:"prune,omitempty"` +} + +func (m *TraceEvent_ControlMeta) Reset() { *m = TraceEvent_ControlMeta{} } +func (m *TraceEvent_ControlMeta) String() string { return "" } + +func (m *TraceEvent_ControlMeta) GetIhave() []*TraceEvent_ControlIHaveMeta { + if m != nil { + return m.Ihave + } + return nil +} + +func (m *TraceEvent_ControlMeta) GetIwant() []*TraceEvent_ControlIWantMeta { + if m != nil { + return m.Iwant + } + return nil +} + +func (m *TraceEvent_ControlMeta) GetGraft() []*TraceEvent_ControlGraftMeta { + if m != nil { + return m.Graft + } + return nil +} + +func (m *TraceEvent_ControlMeta) GetPrune() []*TraceEvent_ControlPruneMeta { + if m != nil { + return m.Prune + } + return nil +} + +type TraceEvent_ControlIHaveMeta struct { + Topic *string `protobuf:"bytes,1,opt,name=topic" json:"topic,omitempty"` + MessageIDs [][]byte `protobuf:"bytes,2,rep,name=messageIDs" json:"messageIDs,omitempty"` +} + +func (m *TraceEvent_ControlIHaveMeta) Reset() { *m = TraceEvent_ControlIHaveMeta{} } +func (m *TraceEvent_ControlIHaveMeta) String() string { return "" } + +func (m *TraceEvent_ControlIHaveMeta) GetTopic() string { + if m != nil && m.Topic != nil { + return *m.Topic + } + return "" +} + +func (m *TraceEvent_ControlIHaveMeta) GetMessageIDs() [][]byte { + if m != nil { + return m.MessageIDs + } + return nil +} + +type TraceEvent_ControlIWantMeta struct { + MessageIDs [][]byte `protobuf:"bytes,1,rep,name=messageIDs" json:"messageIDs,omitempty"` +} + +func (m *TraceEvent_ControlIWantMeta) Reset() { *m = TraceEvent_ControlIWantMeta{} } +func (m *TraceEvent_ControlIWantMeta) String() string { return "" } + +func (m *TraceEvent_ControlIWantMeta) GetMessageIDs() [][]byte { + if m != nil { + return m.MessageIDs + } + return nil +} + +type TraceEvent_ControlGraftMeta struct { + Topic *string `protobuf:"bytes,1,opt,name=topic" json:"topic,omitempty"` +} + +func (m *TraceEvent_ControlGraftMeta) Reset() { *m = TraceEvent_ControlGraftMeta{} } +func (m *TraceEvent_ControlGraftMeta) String() string { return "" } + +func (m *TraceEvent_ControlGraftMeta) GetTopic() string { + if m != nil && m.Topic != nil { + return *m.Topic + } + return "" +} + +type TraceEvent_ControlPruneMeta struct { + Topic *string `protobuf:"bytes,1,opt,name=topic" json:"topic,omitempty"` + Peers []enode.ID `protobuf:"bytes,2,rep,name=peers" json:"peers,omitempty"` +} + +func (m *TraceEvent_ControlPruneMeta) Reset() { *m = TraceEvent_ControlPruneMeta{} } +func (m *TraceEvent_ControlPruneMeta) String() string { return "" } + +func (m *TraceEvent_ControlPruneMeta) GetTopic() string { + if m != nil && m.Topic != nil { + return *m.Topic + } + return "" +} + +func (m *TraceEvent_ControlPruneMeta) GetPeers() []enode.ID { + if m != nil { + return m.Peers + } + return nil +} + +type TraceEventBatch struct { + Batch []*TraceEvent `protobuf:"bytes,1,rep,name=batch" json:"batch,omitempty"` +} + +func (m *TraceEventBatch) Reset() { *m = TraceEventBatch{} } +func (m *TraceEventBatch) String() string { return "" } + +func (m *TraceEventBatch) GetBatch() []*TraceEvent { + if m != nil { + return m.Batch + } + return nil +} diff --git a/p2p/pubsub/notify.go b/p2p/pubsub/notify.go new file mode 100644 index 0000000000..2d9c78de09 --- /dev/null +++ b/p2p/pubsub/notify.go @@ -0,0 +1,62 @@ +package pubsub + +import ( + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +type PubSubNotif PubSub + +func (p *PubSubNotif) OpenedStream(n Network, s Stream) { +} + +func (p *PubSubNotif) ClosedStream(n Network, s Stream) { +} + +func (p *PubSubNotif) Connected(n Network, c Conn) { + // ignore transient connections + if c.Stat().Transient { + return + } + + go func() { + p.newPeersPrioLk.RLock() + p.newPeersMx.Lock() + p.newPeersPend[c.RemotePeer().ID()] = struct{}{} + p.newPeersMx.Unlock() + p.newPeersPrioLk.RUnlock() + + select { + case p.newPeers <- struct{}{}: + default: + } + }() +} + +func (p *PubSubNotif) Initialize() { + isTransient := func(pid enode.ID) bool { + for _, c := range p.host.Network().ConnsToPeer(pid) { + if !c.Stat().Transient { + return false + } + } + + return true + } + + p.newPeersPrioLk.RLock() + p.newPeersMx.Lock() + for _, pid := range p.host.Network().Peers() { + if isTransient(pid) { + continue + } + + p.newPeersPend[pid] = struct{}{} + } + p.newPeersMx.Unlock() + p.newPeersPrioLk.RUnlock() + + select { + case p.newPeers <- struct{}{}: + default: + } +} diff --git a/p2p/pubsub/peer_gater.go b/p2p/pubsub/peer_gater.go new file mode 100644 index 0000000000..2f12b4c881 --- /dev/null +++ b/p2p/pubsub/peer_gater.go @@ -0,0 +1,452 @@ +package pubsub + +import ( + "context" + "fmt" + "math/rand" + "sync" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + "github.com/AlayaNetwork/Alaya-Go/log" +) + +var ( + DefaultPeerGaterRetainStats = 6 * time.Hour + DefaultPeerGaterQuiet = time.Minute + DefaultPeerGaterDuplicateWeight = 0.125 + DefaultPeerGaterIgnoreWeight = 1.0 + DefaultPeerGaterRejectWeight = 16.0 + DefaultPeerGaterThreshold = 0.33 + DefaultPeerGaterGlobalDecay = ScoreParameterDecay(2 * time.Minute) + DefaultPeerGaterSourceDecay = ScoreParameterDecay(time.Hour) +) + +// PeerGaterParams groups together parameters that control the operation of the peer gater +type PeerGaterParams struct { + // when the ratio of throttled/validated messages exceeds this threshold, the gater turns on + Threshold float64 + // (linear) decay parameter for gater counters + GlobalDecay float64 // global counter decay + SourceDecay float64 // per IP counter decay + // decay interval + DecayInterval time.Duration + // counter zeroing threshold + DecayToZero float64 + // how long to retain stats + RetainStats time.Duration + // quiet interval before turning off the gater; if there are no validation throttle events + // for this interval, the gater turns off + Quiet time.Duration + // weight of duplicate message deliveries + DuplicateWeight float64 + // weight of ignored messages + IgnoreWeight float64 + // weight of rejected messages + RejectWeight float64 + + // priority topic delivery weights + TopicDeliveryWeights map[string]float64 +} + +func (p *PeerGaterParams) validate() error { + if p.Threshold <= 0 { + return fmt.Errorf("invalid Threshold; must be > 0") + } + if p.GlobalDecay <= 0 || p.GlobalDecay >= 1 { + return fmt.Errorf("invalid GlobalDecay; must be between 0 and 1") + } + if p.SourceDecay <= 0 || p.SourceDecay >= 1 { + return fmt.Errorf("invalid SourceDecay; must be between 0 and 1") + } + if p.DecayInterval < time.Second { + return fmt.Errorf("invalid DecayInterval; must be at least 1s") + } + if p.DecayToZero <= 0 || p.DecayToZero >= 1 { + return fmt.Errorf("invalid DecayToZero; must be between 0 and 1") + } + // no need to check stats retention; a value of 0 means we don't retain stats + if p.Quiet < time.Second { + return fmt.Errorf("invalud Quiet interval; must be at least 1s") + } + if p.DuplicateWeight <= 0 { + return fmt.Errorf("invalid DuplicateWeight; must be > 0") + } + if p.IgnoreWeight < 1 { + return fmt.Errorf("invalid IgnoreWeight; must be >= 1") + } + if p.RejectWeight < 1 { + return fmt.Errorf("invalud RejectWeight; must be >= 1") + } + + return nil +} + +// WithTopicDeliveryWeights is a fluid setter for the priority topic delivery weights +func (p *PeerGaterParams) WithTopicDeliveryWeights(w map[string]float64) *PeerGaterParams { + p.TopicDeliveryWeights = w + return p +} + +// NewPeerGaterParams creates a new PeerGaterParams struct, using the specified threshold and decay +// parameters and default values for all other parameters. +func NewPeerGaterParams(threshold, globalDecay, sourceDecay float64) *PeerGaterParams { + return &PeerGaterParams{ + Threshold: threshold, + GlobalDecay: globalDecay, + SourceDecay: sourceDecay, + DecayToZero: DefaultDecayToZero, + DecayInterval: DefaultDecayInterval, + RetainStats: DefaultPeerGaterRetainStats, + Quiet: DefaultPeerGaterQuiet, + DuplicateWeight: DefaultPeerGaterDuplicateWeight, + IgnoreWeight: DefaultPeerGaterIgnoreWeight, + RejectWeight: DefaultPeerGaterRejectWeight, + } +} + +// DefaultPeerGaterParams creates a new PeerGaterParams struct using default values +func DefaultPeerGaterParams() *PeerGaterParams { + return NewPeerGaterParams(DefaultPeerGaterThreshold, DefaultPeerGaterGlobalDecay, DefaultPeerGaterSourceDecay) +} + +// the gater object. +type peerGater struct { + sync.Mutex + + host Host + + // gater parameters + params *PeerGaterParams + + // counters + validate, throttle float64 + + // time of last validation throttle + lastThrottle time.Time + + // stats per enode.ID -- multiple peer IDs may share the same stats object if they are + // colocated in the same IP + peerStats map[enode.ID]*peerGaterStats + // stats per IP + ipStats map[string]*peerGaterStats + + // for unit tests + getIP func(enode.ID) string +} + +type peerGaterStats struct { + // number of connected peer IDs mapped to this stat object + connected int + // stats expiration time -- only valid if connected = 0 + expire time.Time + + // counters + deliver, duplicate, ignore, reject float64 +} + +// WithPeerGater is a gossipsub router option that enables reactive validation queue +// management. +// The Gater is activated if the ratio of throttled/validated messages exceeds the specified +// threshold. +// Once active, the Gater probabilistically throttles peers _before_ they enter the validation +// queue, performing Random Early Drop. +// The throttle decision is randomized, with the probability of allowing messages to enter the +// validation queue controlled by the statistical observations of the performance of all peers +// in the IP address of the gated peer. +// The Gater deactivates if there is no validation throttlinc occurring for the specified quiet +// interval. +func WithPeerGater(params *PeerGaterParams) Option { + return func(ps *PubSub) error { + gs, ok := ps.rt.(*GossipSubRouter) + if !ok { + return fmt.Errorf("pubsub router is not gossipsub") + } + + err := params.validate() + if err != nil { + return err + } + + gs.gate = newPeerGater(ps.ctx, ps.host, params) + + // hook the tracer + if ps.tracer != nil { + ps.tracer.raw = append(ps.tracer.raw, gs.gate) + } else { + ps.tracer = &pubsubTracer{ + raw: []RawTracer{gs.gate}, + pid: ps.host.ID().ID(), + msgID: ps.msgID, + } + } + + return nil + } +} + +func newPeerGater(ctx context.Context, host Host, params *PeerGaterParams) *peerGater { + pg := &peerGater{ + params: params, + peerStats: make(map[enode.ID]*peerGaterStats), + ipStats: make(map[string]*peerGaterStats), + host: host, + } + go pg.background(ctx) + return pg +} + +func (pg *peerGater) background(ctx context.Context) { + tick := time.NewTicker(pg.params.DecayInterval) + + defer tick.Stop() + + for { + select { + case <-tick.C: + pg.decayStats() + case <-ctx.Done(): + return + } + } +} + +func (pg *peerGater) decayStats() { + pg.Lock() + defer pg.Unlock() + + pg.validate *= pg.params.GlobalDecay + if pg.validate < pg.params.DecayToZero { + pg.validate = 0 + } + + pg.throttle *= pg.params.GlobalDecay + if pg.throttle < pg.params.DecayToZero { + pg.throttle = 0 + } + + now := time.Now() + for ip, st := range pg.ipStats { + if st.connected > 0 { + st.deliver *= pg.params.SourceDecay + if st.deliver < pg.params.DecayToZero { + st.deliver = 0 + } + + st.duplicate *= pg.params.SourceDecay + if st.duplicate < pg.params.DecayToZero { + st.duplicate = 0 + } + + st.ignore *= pg.params.SourceDecay + if st.ignore < pg.params.DecayToZero { + st.ignore = 0 + } + + st.reject *= pg.params.SourceDecay + if st.reject < pg.params.DecayToZero { + st.reject = 0 + } + } else if st.expire.Before(now) { + delete(pg.ipStats, ip) + } + } +} + +func (pg *peerGater) getPeerStats(p enode.ID) *peerGaterStats { + st, ok := pg.peerStats[p] + if !ok { + st = pg.getIPStats(p) + pg.peerStats[p] = st + } + return st +} + +func (pg *peerGater) getIPStats(p enode.ID) *peerGaterStats { + ip := pg.getPeerIP(p) + st, ok := pg.ipStats[ip] + if !ok { + st = &peerGaterStats{} + pg.ipStats[ip] = st + } + return st +} + +func (pg *peerGater) getPeerIP(p enode.ID) string { + if pg.getIP != nil { + return pg.getIP(p) + } + + /*connToIP := func(c network.Conn) string { + remote := c.RemoteMultiaddr() + ip, err := manet.ToIP(remote) + if err != nil { + log.Warnf("error determining IP for remote peer in %s: %s", remote, err) + return "" + } + return ip.String() + }*/ + //return p.IP().String() + conns := pg.host.Network().ConnsToPeer(p) + switch len(conns) { + case 0: + return "" + case 1: + return conns[0].RemotePeer().IP().String() + default: + return "" + //here we only have one conns in Alaya + + // we have multiple connections -- order by number of streams and use the one with the + // most streams; it's a nightmare to track multiple IPs per peer, so pick the best one. + /*streams := make(map[string]int) + for _, c := range conns { + if c.Stat().Transient { + // ignore transient + continue + } + streams[c.ID()] = len(c.GetStreams()) + } + sort.Slice(conns, func(i, j int) bool { + return streams[conns[i].ID()] > streams[conns[j].ID()] + }) + return connToIP(conns[0])*/ + } +} + +// router interface +func (pg *peerGater) AcceptFrom(p *enode.Node) AcceptStatus { + if pg == nil { + return AcceptAll + } + + pg.Lock() + defer pg.Unlock() + + // check the quiet period; if the validation queue has not throttled for more than the Quiet + // interval, we turn off the circuit breaker and accept. + if time.Since(pg.lastThrottle) > pg.params.Quiet { + return AcceptAll + } + + // no throttle events -- or they have decayed; accept. + if pg.throttle == 0 { + return AcceptAll + } + + // check the throttle/validate ration; if it is below threshold we accept. + if pg.validate != 0 && pg.throttle/pg.validate < pg.params.Threshold { + return AcceptAll + } + + st := pg.getPeerStats(p.ID()) + + // compute the goodput of the peer; the denominator is the weighted mix of message counters + total := st.deliver + pg.params.DuplicateWeight*st.duplicate + pg.params.IgnoreWeight*st.ignore + pg.params.RejectWeight*st.reject + if total == 0 { + return AcceptAll + } + + // we make a randomized decision based on the goodput of the peer. + // the probabiity is biased by adding 1 to the delivery counter so that we don't unconditionally + // throttle in the first negative event; it also ensures that a peer always has a chance of being + // accepted; this is not a sinkhole/blacklist. + threshold := (1 + st.deliver) / (1 + total) + if rand.Float64() < threshold { + return AcceptAll + } + + log.Debug("throttling peer %s with threshold %f", "peer", p, "threshold", threshold) + return AcceptControl +} + +// -- RawTracer interface methods +//var _ RawTracer = (*peerGater)(nil) + +// tracer interface +func (pg *peerGater) AddPeer(p *enode.Node, proto ProtocolID) { + pg.Lock() + defer pg.Unlock() + + st := pg.getPeerStats(p.ID()) + st.connected++ +} + +func (pg *peerGater) RemovePeer(p enode.ID) { + pg.Lock() + defer pg.Unlock() + + st := pg.getPeerStats(p) + st.connected-- + st.expire = time.Now().Add(pg.params.RetainStats) + + delete(pg.peerStats, p) +} + +func (pg *peerGater) Join(topic string) {} +func (pg *peerGater) Leave(topic string) {} +func (pg *peerGater) Graft(p enode.ID, topic string) {} +func (pg *peerGater) Prune(p enode.ID, topic string) {} + +func (pg *peerGater) ValidateMessage(msg *Message) { + pg.Lock() + defer pg.Unlock() + + pg.validate++ +} + +func (pg *peerGater) DeliverMessage(msg *Message) { + pg.Lock() + defer pg.Unlock() + + st := pg.getPeerStats(msg.ReceivedFrom.ID()) + + topic := msg.GetTopic() + weight := pg.params.TopicDeliveryWeights[topic] + + if weight == 0 { + weight = 1 + } + + st.deliver += weight +} + +func (pg *peerGater) RejectMessage(msg *Message, reason string) { + pg.Lock() + defer pg.Unlock() + + switch reason { + case RejectValidationQueueFull: + fallthrough + case RejectValidationThrottled: + pg.lastThrottle = time.Now() + pg.throttle++ + + case RejectValidationIgnored: + st := pg.getPeerStats(msg.ReceivedFrom.ID()) + st.ignore++ + + default: + st := pg.getPeerStats(msg.ReceivedFrom.ID()) + st.reject++ + } +} + +func (pg *peerGater) DuplicateMessage(msg *Message) { + pg.Lock() + defer pg.Unlock() + + st := pg.getPeerStats(msg.ReceivedFrom.ID()) + st.duplicate++ +} + +func (pg *peerGater) ThrottlePeer(p enode.ID) {} + +func (pg *peerGater) RecvRPC(rpc *RPC) {} + +func (pg *peerGater) SendRPC(rpc *RPC, p enode.ID) {} + +func (pg *peerGater) DropRPC(rpc *RPC, p enode.ID) {} + +func (pg *peerGater) UndeliverableMessage(msg *Message) {} diff --git a/p2p/pubsub/peerstore.go b/p2p/pubsub/peerstore.go new file mode 100644 index 0000000000..a5535df97c --- /dev/null +++ b/p2p/pubsub/peerstore.go @@ -0,0 +1,36 @@ +package pubsub + +import ( + "io" + "math" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +// Permanent TTLs (distinct so we can distinguish between them, constant as they +// are, in fact, permanent) +const ( + // PermanentAddrTTL is the ttl for a "permanent address" (e.g. bootstrap nodes). + PermanentAddrTTL = math.MaxInt64 - iota + + // ConnectedAddrTTL is the ttl used for the addresses of a peer to whom + // we're connected directly. This is basically permanent, as we will + // clear them + re-add under a TempAddrTTL after disconnecting. + ConnectedAddrTTL +) + +// Peerstore provides a threadsafe store of Peer related +// information. +type Peerstore interface { + io.Closer + + // AddAddrs gives this AddrBook addresses to use, with a given ttl + // (time-to-live), after which the address is no longer valid. + // If the manager has a longer TTL, the operation is a no-op for that address + AddAddrs(p enode.ID, ttl time.Duration) + + // PrivKey returns the private key of a peer, if known. Generally this might only be our own + // private key + //PrivKey(id enode.ID) *ecdsa.PrivateKey +} diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go new file mode 100644 index 0000000000..fb505363e4 --- /dev/null +++ b/p2p/pubsub/pubsub.go @@ -0,0 +1,1361 @@ +package pubsub + +import ( + "context" + "crypto/ecdsa" + "encoding/binary" + "errors" + "fmt" + "math/rand" + "sync" + "sync/atomic" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + "github.com/AlayaNetwork/Alaya-Go/log" + + timecache "github.com/whyrusleeping/timecache" +) + +// DefaultMaximumMessageSize is 1mb. +const DefaultMaxMessageSize = 1 << 20 + +var ( + TimeCacheDuration = 120 * time.Second + + // ErrSubscriptionCancelled may be returned when a subscription Next() is called after the + // subscription has been cancelled. + ErrSubscriptionCancelled = errors.New("subscription cancelled") +) + +type ProtocolMatchFn = func(string) func(string) bool + +// PubSub is the implementation of the pubsub system. +type PubSub struct { + // atomic counter for seqnos + // NOTE: Must be declared at the top of the struct as we perform atomic + // operations on this field. + // + // See: https://golang.org/pkg/sync/atomic/#pkg-note-BUG + counter uint64 + + host Host + + rt PubSubRouter + + val *validation + + //disc *discover + + tracer *pubsubTracer + + peerFilter PeerFilter + + // maxMessageSize is the maximum message size; it applies globally to all + // topics. + maxMessageSize int + + // size of the outbound message channel that we maintain for each peer + peerOutboundQueueSize int + + // incoming messages from other peers + incoming chan *RPC + + // addSub is a control channel for us to add and remove subscriptions + addSub chan *addSubReq + + // addRelay is a control channel for us to add and remove relays + addRelay chan *addRelayReq + + // rmRelay is a relay cancellation channel + rmRelay chan string + + // get list of topics we are subscribed to + getTopics chan *topicReq + + // get chan of peers we are connected to + getPeers chan *listPeerReq + + // send subscription here to cancel it + cancelCh chan *Subscription + + // addSub is a channel for us to add a topic + addTopic chan *addTopicReq + + // removeTopic is a topic cancellation channel + rmTopic chan *rmTopicReq + + // a notification channel for new peer connections accumulated + newPeers chan struct{} + newPeersPrioLk sync.RWMutex + newPeersMx sync.Mutex + newPeersPend map[enode.ID]struct{} + + // a notification channel for new outoging peer streams + newPeerStream chan Stream + + // a notification channel for errors opening new peer streams + newPeerError chan enode.ID + + // a notification channel for when our peers die + peerDead chan struct{} + peerDeadPrioLk sync.RWMutex + peerDeadMx sync.Mutex + peerDeadPend map[enode.ID]struct{} + + // The set of topics we are subscribed to + mySubs map[string]map[*Subscription]struct{} + + // The set of topics we are relaying for + myRelays map[string]int + + // The set of topics we are interested in + myTopics map[string]*Topic + + // topics tracks which topics each of our peers are subscribed to + topics map[string]map[enode.ID]struct{} + + // sendMsg handles messages that have been validated + sendMsg chan *Message + + // addVal handles validator registration requests + addVal chan *addValReq + + // rmVal handles validator unregistration requests + rmVal chan *rmValReq + + // eval thunk in event loop + eval chan func() + + // peer blacklist + blacklist Blacklist + blacklistPeer chan *enode.Node + + peers map[enode.ID]chan *RPC + + inboundStreamsMx sync.Mutex + inboundStreams map[enode.ID]Stream + + seenMessagesMx sync.Mutex + seenMessages *timecache.TimeCache + + // function used to compute the ID for a message + msgID MsgIdFunction + + // key for signing messages; nil when signing is disabled + signKey *ecdsa.PrivateKey + // source ID for signed messages; corresponds to signKey, empty when signing is disabled. + // If empty, the author and seq-nr are completely omitted from the messages. + signID enode.ID + // strict mode rejects all unsigned messages prior to validation + signPolicy MessageSignaturePolicy + + // filter for tracking subscriptions in topics of interest; if nil, then we track all subscriptions + subFilter SubscriptionFilter + + // protoMatchFunc is a matching function for protocol selection. + protoMatchFunc ProtocolMatchFn + + ctx context.Context +} + +// PubSubRouter is the message router component of PubSub. +type PubSubRouter interface { + // Protocols returns the list of protocols supported by the router. + Protocols() []ProtocolID + // Attach is invoked by the PubSub constructor to attach the router to a + // freshly initialized PubSub instance. + Attach(*PubSub) + // AddPeer notifies the router that a new peer has been connected. + AddPeer(*enode.Node, ProtocolID) + // RemovePeer notifies the router that a peer has been disconnected. + RemovePeer(enode.ID) + // EnoughPeers returns whether the router needs more peers before it's ready to publish new records. + // Suggested (if greater than 0) is a suggested number of peers that the router should need. + EnoughPeers(topic string, suggested int) bool + // AcceptFrom is invoked on any incoming message before pushing it to the validation pipeline + // or processing control information. + // Allows routers with internal scoring to vet peers before committing any processing resources + // to the message and implement an effective graylist and react to validation queue overload. + AcceptFrom(*enode.Node) AcceptStatus + // HandleRPC is invoked to process control messages in the RPC envelope. + // It is invoked after subscriptions and payload messages have been processed. + HandleRPC(*RPC) + // Publish is invoked to forward a new message that has been validated. + Publish(*Message) + // Join notifies the router that we want to receive and forward messages in a topic. + // It is invoked after the subscription announcement. + Join(topic string) + // Leave notifies the router that we are no longer interested in a topic. + // It is invoked after the unsubscription announcement. + Leave(topic string) +} + +type AcceptStatus int + +const ( + // AcceptNone signals to drop the incoming RPC + AcceptNone AcceptStatus = iota + // AcceptControl signals to accept the incoming RPC only for control message processing by + // the router. Included payload messages will _not_ be pushed to the validation queue. + AcceptControl + // AcceptAll signals to accept the incoming RPC for full processing + AcceptAll +) + +type Message struct { + *message.Message + ReceivedFrom *enode.Node + ValidatorData interface{} +} + +func (m *Message) GetFrom() enode.ID { + //var id enode.ID + //copy(id[:], m.Message.GetFrom()) + return m.Message.GetFrom() +} + +type RPC struct { + message.RPC + + // unexported on purpose, not sending this over the wire + from *enode.Node +} + +type Option func(*PubSub) error + +// NewPubSub returns a new PubSub management object. +func NewPubSub(ctx context.Context, h Host, rt PubSubRouter, opts ...Option) (*PubSub, error) { + ps := &PubSub{ + host: h, + ctx: ctx, + rt: rt, + val: newValidation(), + peerFilter: DefaultPeerFilter, + // disc: &discover{}, + maxMessageSize: DefaultMaxMessageSize, + peerOutboundQueueSize: 32, + signID: h.ID().ID(), + signKey: nil, + signPolicy: StrictSign, + incoming: make(chan *RPC, 32), + newPeers: make(chan struct{}, 1), + newPeersPend: make(map[enode.ID]struct{}), + newPeerStream: make(chan Stream), + newPeerError: make(chan enode.ID), + peerDead: make(chan struct{}, 1), + peerDeadPend: make(map[enode.ID]struct{}), + cancelCh: make(chan *Subscription), + getPeers: make(chan *listPeerReq), + addSub: make(chan *addSubReq), + addRelay: make(chan *addRelayReq), + rmRelay: make(chan string), + addTopic: make(chan *addTopicReq), + rmTopic: make(chan *rmTopicReq), + getTopics: make(chan *topicReq), + sendMsg: make(chan *Message, 32), + addVal: make(chan *addValReq), + rmVal: make(chan *rmValReq), + eval: make(chan func()), + myTopics: make(map[string]*Topic), + mySubs: make(map[string]map[*Subscription]struct{}), + myRelays: make(map[string]int), + topics: make(map[string]map[enode.ID]struct{}), + peers: make(map[enode.ID]chan *RPC), + inboundStreams: make(map[enode.ID]Stream), + blacklist: NewMapBlacklist(), + blacklistPeer: make(chan *enode.Node), + seenMessages: timecache.NewTimeCache(TimeCacheDuration), + msgID: DefaultMsgIdFn, + counter: uint64(time.Now().UnixNano()), + } + + for _, opt := range opts { + err := opt(ps) + if err != nil { + return nil, err + } + } + + if ps.signPolicy.mustSign() { + if ps.signID == enode.ZeroID { + return nil, fmt.Errorf("strict signature usage enabled but message author was disabled") + } + //ps.signKey = ps.host.Peerstore().PrivKey(ps.signID) + /*if ps.signKey == nil { + return nil, fmt.Errorf("can't sign for peer %s: no private key", ps.signID) + }*/ + } + + /* if err := ps.disc.Start(ps); err != nil { + return nil, err + }*/ + + rt.Attach(ps) + + for _, id := range rt.Protocols() { + if ps.protoMatchFunc != nil { + h.SetStreamHandlerMatch(id, ps.protoMatchFunc(string(id)), ps.handleNewStream) + } else { + h.SetStreamHandler(id, ps.handleNewStream) + } + } + h.Network().Notify((*PubSubNotif)(ps)) + + ps.val.Start(ps) + + go ps.processLoop(ctx) + + (*PubSubNotif)(ps).Initialize() + + return ps, nil +} + +// MsgIdFunction returns a unique ID for the passed Message, and PubSub can be customized to use any +// implementation of this function by configuring it with the Option from WithMessageIdFn. +type MsgIdFunction func(pmsg *message.Message) string + +// WithMessageIdFn is an option to customize the way a message ID is computed for a pubsub message. +// The default ID function is DefaultMsgIdFn (concatenate source and seq nr.), +// but it can be customized to e.g. the hash of the message. +func WithMessageIdFn(fn MsgIdFunction) Option { + return func(p *PubSub) error { + p.msgID = fn + // the tracer Option may already be set. Update its message ID function to make options order-independent. + if p.tracer != nil { + p.tracer.msgID = fn + } + return nil + } +} + +// PeerFilter is used to filter pubsub peers. It should return true for peers that are accepted for +// a given topic. PubSub can be customized to use any implementation of this function by configuring +// it with the Option from WithPeerFilter. +type PeerFilter func(pid enode.ID, topic string) bool + +// WithPeerFilter is an option to set a filter for pubsub peers. +// The default peer filter is DefaultPeerFilter (which always returns true), but it can be customized +// to any custom implementation. +func WithPeerFilter(filter PeerFilter) Option { + return func(p *PubSub) error { + p.peerFilter = filter + return nil + } +} + +// WithPeerOutboundQueueSize is an option to set the buffer size for outbound messages to a peer +// We start dropping messages to a peer if the outbound queue if full +func WithPeerOutboundQueueSize(size int) Option { + return func(p *PubSub) error { + if size <= 0 { + return errors.New("outbound queue size must always be positive") + } + p.peerOutboundQueueSize = size + return nil + } +} + +// WithMessageSignaturePolicy sets the mode of operation for producing and verifying message signatures. +func WithMessageSignaturePolicy(policy MessageSignaturePolicy) Option { + return func(p *PubSub) error { + p.signPolicy = policy + return nil + } +} + +// WithMessageSigning enables or disables message signing (enabled by default). +// Deprecated: signature verification without message signing, +// or message signing without verification, are not recommended. +func WithMessageSigning(enabled bool) Option { + return func(p *PubSub) error { + if enabled { + p.signPolicy |= msgSigning + } else { + p.signPolicy &^= msgSigning + } + return nil + } +} + +/* +// WithMessageAuthor sets the author for outbound messages to the given peer ID +// (defaults to the host's ID). If message signing is enabled, the private key +// must be available in the host's peerstore. +func WithMessageAuthor(author enode.ID) Option { + return func(p *PubSub) error { + author := author + if author == "" { + author = p.host.ID() + } + p.signID = author + return nil + } +}*/ + +// WithNoAuthor omits the author and seq-number data of messages, and disables the use of signatures. +// Not recommended to use with the default message ID function, see WithMessageIdFn. +func WithNoAuthor() Option { + return func(p *PubSub) error { + p.signID = enode.ID{} + p.signPolicy &^= msgSigning + return nil + } +} + +// WithStrictSignatureVerification is an option to enable or disable strict message signing. +// When enabled (which is the default), unsigned messages will be discarded. +// Deprecated: signature verification without message signing, +// or message signing without verification, are not recommended. +func WithStrictSignatureVerification(required bool) Option { + return func(p *PubSub) error { + if required { + p.signPolicy |= msgVerification + } else { + p.signPolicy &^= msgVerification + } + return nil + } +} + +// WithBlacklist provides an implementation of the blacklist; the default is a +// MapBlacklist +func WithBlacklist(b Blacklist) Option { + return func(p *PubSub) error { + p.blacklist = b + return nil + } +} + +// WithDiscovery provides a discovery mechanism used to bootstrap and provide peers into PubSub +/*func WithDiscovery(d discovery.Discovery, opts ...DiscoverOpt) Option { + return func(p *PubSub) error { + discoverOpts := defaultDiscoverOptions() + for _, opt := range opts { + err := opt(discoverOpts) + if err != nil { + return err + } + } + + p.disc.discovery = &pubSubDiscovery{Discovery: d, opts: discoverOpts.opts} + p.disc.options = discoverOpts + return nil + } +}*/ + +// WithEventTracer provides a tracer for the pubsub system +func WithEventTracer(tracer EventTracer) Option { + return func(p *PubSub) error { + if p.tracer != nil { + p.tracer.tracer = tracer + } else { + p.tracer = &pubsubTracer{tracer: tracer, pid: p.host.ID().ID(), msgID: p.msgID} + } + return nil + } +} + +// WithRawTracer adds a raw tracer to the pubsub system. +// Multiple tracers can be added using multiple invocations of the option. +func WithRawTracer(tracer RawTracer) Option { + return func(p *PubSub) error { + if p.tracer != nil { + p.tracer.raw = append(p.tracer.raw, tracer) + } else { + p.tracer = &pubsubTracer{raw: []RawTracer{tracer}, pid: p.host.ID().ID(), msgID: p.msgID} + } + return nil + } +} + +// WithMaxMessageSize sets the global maximum message size for pubsub wire +// messages. The default value is 1MiB (DefaultMaxMessageSize). +// +// Observe the following warnings when setting this option. +// +// WARNING #1: Make sure to change the default protocol prefixes for floodsub +// (FloodSubID) and gossipsub (GossipSubID). This avoids accidentally joining +// the public default network, which uses the default max message size, and +// therefore will cause messages to be dropped. +// +// WARNING #2: Reducing the default max message limit is fine, if you are +// certain that your application messages will not exceed the new limit. +// However, be wary of increasing the limit, as pubsub networks are naturally +// write-amplifying, i.e. for every message we receive, we send D copies of the +// message to our peers. If those messages are large, the bandwidth requirements +// will grow linearly. Note that propagation is sent on the uplink, which +// traditionally is more constrained than the downlink. Instead, consider +// out-of-band retrieval for large messages, by sending a CID (Content-ID) or +// another type of locator, such that messages can be fetched on-demand, rather +// than being pushed proactively. Under this design, you'd use the pubsub layer +// as a signalling system, rather than a data delivery system. +func WithMaxMessageSize(maxMessageSize int) Option { + return func(ps *PubSub) error { + ps.maxMessageSize = maxMessageSize + return nil + } +} + +// WithProtocolMatchFn sets a custom matching function for protocol selection to +// be used by the protocol handler on the Host's Mux. Should be combined with +// WithGossipSubProtocols feature function for checking if certain protocol features +// are supported +func WithProtocolMatchFn(m ProtocolMatchFn) Option { + return func(ps *PubSub) error { + ps.protoMatchFunc = m + return nil + } +} + +// processLoop handles all inputs arriving on the channels +func (p *PubSub) processLoop(ctx context.Context) { + defer func() { + // Clean up go routines. + for _, ch := range p.peers { + close(ch) + } + p.peers = nil + p.topics = nil + }() + + for { + select { + case <-p.newPeers: + p.handlePendingPeers() + + case s := <-p.newPeerStream: + pid := s.Conn().RemotePeer() + + ch, ok := p.peers[pid.ID()] + if !ok { + log.Warn("new stream for unknown peer: ", pid) + s.Reset() + continue + } + + if p.blacklist.Contains(pid.ID()) { + log.Warn("closing stream for blacklisted peer: ", pid) + close(ch) + delete(p.peers, pid.ID()) + s.Reset() + continue + } + + p.rt.AddPeer(pid, s.Protocol()) + + case pid := <-p.newPeerError: + delete(p.peers, pid) + + case <-p.peerDead: + p.handleDeadPeers() + + case treq := <-p.getTopics: + var out []string + for t := range p.mySubs { + out = append(out, t) + } + treq.resp <- out + case topic := <-p.addTopic: + p.handleAddTopic(topic) + case topic := <-p.rmTopic: + p.handleRemoveTopic(topic) + case sub := <-p.cancelCh: + p.handleRemoveSubscription(sub) + case sub := <-p.addSub: + p.handleAddSubscription(sub) + case relay := <-p.addRelay: + p.handleAddRelay(relay) + case topic := <-p.rmRelay: + p.handleRemoveRelay(topic) + case preq := <-p.getPeers: + tmap, ok := p.topics[preq.topic] + if preq.topic != "" && !ok { + preq.resp <- nil + continue + } + var peers []enode.ID + for p := range p.peers { + if preq.topic != "" { + _, ok := tmap[p] + if !ok { + continue + } + } + peers = append(peers, p) + } + preq.resp <- peers + case rpc := <-p.incoming: + p.handleIncomingRPC(rpc) + + case msg := <-p.sendMsg: + p.publishMessage(msg) + + case req := <-p.addVal: + p.val.AddValidator(req) + + case req := <-p.rmVal: + p.val.RemoveValidator(req) + + case thunk := <-p.eval: + thunk() + + case pid := <-p.blacklistPeer: + log.Info("Blacklisting peer", "peer", pid) + p.blacklist.Add(pid.ID()) + + ch, ok := p.peers[pid.ID()] + if ok { + close(ch) + delete(p.peers, pid.ID()) + for t, tmap := range p.topics { + if _, ok := tmap[pid.ID()]; ok { + delete(tmap, pid.ID()) + p.notifyLeave(t, pid.ID()) + } + } + p.rt.RemovePeer(pid.ID()) + } + + case <-ctx.Done(): + log.Info("pubsub processloop shutting down") + return + } + } +} + +func (p *PubSub) handlePendingPeers() { + p.newPeersPrioLk.Lock() + + if len(p.newPeersPend) == 0 { + p.newPeersPrioLk.Unlock() + return + } + + newPeers := p.newPeersPend + p.newPeersPend = make(map[enode.ID]struct{}) + p.newPeersPrioLk.Unlock() + + for pid := range newPeers { + if p.host.Network().Connectedness(pid) != Connected { + continue + } + + if _, ok := p.peers[pid]; ok { + log.Debug("already have connection to peer: ", pid) + continue + } + + if p.blacklist.Contains(pid) { + log.Warn("ignoring connection from blacklisted peer: ", pid) + continue + } + + messages := make(chan *RPC, p.peerOutboundQueueSize) + messages <- p.getHelloPacket() + go p.handleNewPeer(p.ctx, pid, messages) + p.peers[pid] = messages + } +} + +func (p *PubSub) handleDeadPeers() { + p.peerDeadPrioLk.Lock() + + if len(p.peerDeadPend) == 0 { + p.peerDeadPrioLk.Unlock() + return + } + + deadPeers := p.peerDeadPend + p.peerDeadPend = make(map[enode.ID]struct{}) + p.peerDeadPrioLk.Unlock() + + for pid := range deadPeers { + ch, ok := p.peers[pid] + if !ok { + continue + } + + close(ch) + + if p.host.Network().Connectedness(pid) == Connected { + // still connected, must be a duplicate connection being closed. + // we respawn the writer as we need to ensure there is a stream active + log.Debug("peer declared dead but still connected; respawning writer", "writer", pid) + messages := make(chan *RPC, p.peerOutboundQueueSize) + messages <- p.getHelloPacket() + go p.handleNewPeer(p.ctx, pid, messages) + p.peers[pid] = messages + continue + } + + delete(p.peers, pid) + for t, tmap := range p.topics { + if _, ok := tmap[pid]; ok { + delete(tmap, pid) + p.notifyLeave(t, pid) + } + } + + p.rt.RemovePeer(pid) + } +} + +// handleAddTopic adds a tracker for a particular topic. +// Only called from processLoop. +func (p *PubSub) handleAddTopic(req *addTopicReq) { + topic := req.topic + topicID := topic.topic + + t, ok := p.myTopics[topicID] + if ok { + req.resp <- t + return + } + + p.myTopics[topicID] = topic + req.resp <- topic +} + +// handleRemoveTopic removes Topic tracker from bookkeeping. +// Only called from processLoop. +func (p *PubSub) handleRemoveTopic(req *rmTopicReq) { + topic := p.myTopics[req.topic.topic] + + if topic == nil { + req.resp <- nil + return + } + + if len(topic.evtHandlers) == 0 && + len(p.mySubs[req.topic.topic]) == 0 && + p.myRelays[req.topic.topic] == 0 { + delete(p.myTopics, topic.topic) + req.resp <- nil + return + } + + req.resp <- fmt.Errorf("cannot close topic: outstanding event handlers or subscriptions") +} + +// handleRemoveSubscription removes Subscription sub from bookeeping. +// If this was the last subscription and no more relays exist for a given topic, +// it will also announce that this node is not subscribing to this topic anymore. +// Only called from processLoop. +func (p *PubSub) handleRemoveSubscription(sub *Subscription) { + subs := p.mySubs[sub.topic] + + if subs == nil { + return + } + + sub.err = ErrSubscriptionCancelled + sub.close() + delete(subs, sub) + + if len(subs) == 0 { + delete(p.mySubs, sub.topic) + + // stop announcing only if there are no more subs and relays + if p.myRelays[sub.topic] == 0 { + //p.disc.StopAdvertise(sub.topic) + p.announce(sub.topic, false) + p.rt.Leave(sub.topic) + } + } +} + +// handleAddSubscription adds a Subscription for a particular topic. If it is +// the first subscription and no relays exist so far for the topic, it will +// announce that this node subscribes to the topic. +// Only called from processLoop. +func (p *PubSub) handleAddSubscription(req *addSubReq) { + sub := req.sub + subs := p.mySubs[sub.topic] + + // announce we want this topic if neither subs nor relays exist so far + if len(subs) == 0 && p.myRelays[sub.topic] == 0 { + //p.disc.Advertise(sub.topic) + p.announce(sub.topic, true) + p.rt.Join(sub.topic) + } + + // make new if not there + if subs == nil { + p.mySubs[sub.topic] = make(map[*Subscription]struct{}) + } + + sub.cancelCh = p.cancelCh + + p.mySubs[sub.topic][sub] = struct{}{} + + req.resp <- sub +} + +// handleAddRelay adds a relay for a particular topic. If it is +// the first relay and no subscriptions exist so far for the topic , it will +// announce that this node relays for the topic. +// Only called from processLoop. +func (p *PubSub) handleAddRelay(req *addRelayReq) { + topic := req.topic + + p.myRelays[topic]++ + + // announce we want this topic if neither relays nor subs exist so far + if p.myRelays[topic] == 1 && len(p.mySubs[topic]) == 0 { + //p.disc.Advertise(topic) + p.announce(topic, true) + p.rt.Join(topic) + } + + // flag used to prevent calling cancel function multiple times + isCancelled := false + + relayCancelFunc := func() { + if isCancelled { + return + } + + select { + case p.rmRelay <- topic: + isCancelled = true + case <-p.ctx.Done(): + } + } + + req.resp <- relayCancelFunc +} + +// handleRemoveRelay removes one relay reference from bookkeeping. +// If this was the last relay reference and no more subscriptions exist +// for a given topic, it will also announce that this node is not relaying +// for this topic anymore. +// Only called from processLoop. +func (p *PubSub) handleRemoveRelay(topic string) { + if p.myRelays[topic] == 0 { + return + } + + p.myRelays[topic]-- + + if p.myRelays[topic] == 0 { + delete(p.myRelays, topic) + + // stop announcing only if there are no more relays and subs + if len(p.mySubs[topic]) == 0 { + //p.disc.StopAdvertise(topic) + p.announce(topic, false) + p.rt.Leave(topic) + } + } +} + +// announce announces whether or not this node is interested in a given topic +// Only called from processLoop. +func (p *PubSub) announce(topic string, sub bool) { + subopt := &message.RPC_SubOpts{ + Topicid: &topic, + Subscribe: &sub, + } + + out := rpcWithSubs(subopt) + for pid, peer := range p.peers { + select { + case peer <- out: + p.tracer.SendRPC(out, pid) + default: + log.Info("Can't send announce message to peer %s: queue full; scheduling retry", "peer", pid) + p.tracer.DropRPC(out, pid) + go p.announceRetry(pid, topic, sub) + } + } +} + +func (p *PubSub) announceRetry(pid enode.ID, topic string, sub bool) { + time.Sleep(time.Duration(1+rand.Intn(1000)) * time.Millisecond) + + retry := func() { + _, okSubs := p.mySubs[topic] + _, okRelays := p.myRelays[topic] + + ok := okSubs || okRelays + + if (ok && sub) || (!ok && !sub) { + p.doAnnounceRetry(pid, topic, sub) + } + } + + select { + case p.eval <- retry: + case <-p.ctx.Done(): + } +} + +func (p *PubSub) doAnnounceRetry(pid enode.ID, topic string, sub bool) { + peer, ok := p.peers[pid] + if !ok { + return + } + + subopt := &message.RPC_SubOpts{ + Topicid: &topic, + Subscribe: &sub, + } + + out := rpcWithSubs(subopt) + select { + case peer <- out: + p.tracer.SendRPC(out, pid) + default: + log.Info("Can't send announce message to peer %s: queue full; scheduling retry", "peer", pid) + p.tracer.DropRPC(out, pid) + go p.announceRetry(pid, topic, sub) + } +} + +// notifySubs sends a given message to all corresponding subscribers. +// Only called from processLoop. +func (p *PubSub) notifySubs(msg *Message) { + topic := msg.GetTopic() + subs := p.mySubs[topic] + for f := range subs { + select { + case f.ch <- msg: + default: + p.tracer.UndeliverableMessage(msg) + log.Info("Can't deliver message to subscription for topic %s; subscriber too slow", "topic", topic) + } + } +} + +// seenMessage returns whether we already saw this message before +func (p *PubSub) seenMessage(id string) bool { + p.seenMessagesMx.Lock() + defer p.seenMessagesMx.Unlock() + return p.seenMessages.Has(id) +} + +// markSeen marks a message as seen such that seenMessage returns `true' for the given id +// returns true if the message was freshly marked +func (p *PubSub) markSeen(id string) bool { + p.seenMessagesMx.Lock() + defer p.seenMessagesMx.Unlock() + if p.seenMessages.Has(id) { + return false + } + + p.seenMessages.Add(id) + return true +} + +// subscribedToMessage returns whether we are subscribed to one of the topics +// of a given message +func (p *PubSub) subscribedToMsg(msg *message.Message) bool { + if len(p.mySubs) == 0 { + return false + } + + topic := msg.GetTopic() + _, ok := p.mySubs[topic] + + return ok +} + +// canRelayMsg returns whether we are able to relay for one of the topics +// of a given message +func (p *PubSub) canRelayMsg(msg *message.Message) bool { + if len(p.myRelays) == 0 { + return false + } + + topic := msg.GetTopic() + relays := p.myRelays[topic] + + return relays > 0 +} + +func (p *PubSub) notifyLeave(topic string, pid enode.ID) { + if t, ok := p.myTopics[topic]; ok { + t.sendNotification(PeerEvent{Type: PeerLeave, Peer: pid}) + } +} + +func (p *PubSub) handleIncomingRPC(rpc *RPC) { + p.tracer.RecvRPC(rpc) + + subs := rpc.GetSubscriptions() + if len(subs) != 0 && p.subFilter != nil { + var err error + subs, err = p.subFilter.FilterIncomingSubscriptions(rpc.from.ID(), subs) + if err != nil { + log.Debug("subscription filter error: %s; ignoring RPC", "err", err) + return + } + } + + for _, subopt := range subs { + t := subopt.GetTopicid() + + if subopt.GetSubscribe() { + tmap, ok := p.topics[t] + if !ok { + tmap = make(map[enode.ID]struct{}) + p.topics[t] = tmap + } + + if _, ok = tmap[rpc.from.ID()]; !ok { + tmap[rpc.from.ID()] = struct{}{} + if topic, ok := p.myTopics[t]; ok { + peer := rpc.from + topic.sendNotification(PeerEvent{Type: PeerJoin, Peer: peer.ID()}) + } + } + } else { + tmap, ok := p.topics[t] + if !ok { + continue + } + + if _, ok := tmap[rpc.from.ID()]; ok { + delete(tmap, rpc.from.ID()) + p.notifyLeave(t, rpc.from.ID()) + } + } + } + + // ask the router to vet the peer before commiting any processing resources + switch p.rt.AcceptFrom(rpc.from) { + case AcceptNone: + log.Debug("received RPC from router graylisted peer %s; dropping RPC", "from", rpc.from) + return + + case AcceptControl: + if len(rpc.GetPublish()) > 0 { + log.Debug("peer %s was throttled by router; ignoring %d payload messages", "from", rpc.from, "lengths", len(rpc.GetPublish())) + } + p.tracer.ThrottlePeer(rpc.from.ID()) + + case AcceptAll: + for _, pmsg := range rpc.GetPublish() { + if !(p.subscribedToMsg(pmsg) || p.canRelayMsg(pmsg)) { + log.Debug("received message in topic we didn't subscribe to; ignoring message") + continue + } + + msg := &Message{pmsg, rpc.from, nil} + p.pushMsg(msg) + } + } + + p.rt.HandleRPC(rpc) +} + +// DefaultMsgIdFn returns a unique ID of the passed Message +func DefaultMsgIdFn(pmsg *message.Message) string { + return pmsg.GetFrom().String() + string(pmsg.GetSeqno()) +} + +// DefaultPeerFilter accepts all peers on all topics +func DefaultPeerFilter(pid enode.ID, topic string) bool { + return true +} + +// pushMsg pushes a message performing validation as necessary +func (p *PubSub) pushMsg(msg *Message) { + src := msg.ReceivedFrom + // reject messages from blacklisted peers + if p.blacklist.Contains(src.ID()) { + log.Debug("dropping message from blacklisted peer %s", "peer", src) + p.tracer.RejectMessage(msg, RejectBlacklstedPeer) + return + } + + // even if they are forwarded by good peers + if p.blacklist.Contains(msg.GetFrom()) { + log.Debug("dropping message from blacklisted source %s", "peer", src) + p.tracer.RejectMessage(msg, RejectBlacklistedSource) + return + } + + err := p.checkSigningPolicy(msg) + if err != nil { + log.Debug("dropping message from %s: %s", "peer", src, "err", err) + return + } + + // reject messages claiming to be from ourselves but not locally published + self := p.host.ID() + if msg.GetFrom() == self.ID() && src.ID() != self.ID() { + log.Debug("dropping message claiming to be from self but forwarded from %s", "peer", src) + p.tracer.RejectMessage(msg, RejectSelfOrigin) + return + } + + // have we already seen and validated this message? + id := p.msgID(msg.Message) + if p.seenMessage(id) { + p.tracer.DuplicateMessage(msg) + return + } + + if !p.val.Push(src.ID(), msg) { + return + } + + if p.markSeen(id) { + p.publishMessage(msg) + } +} + +func (p *PubSub) checkSigningPolicy(msg *Message) error { + // reject unsigned messages when strict before we even process the id + if p.signPolicy.mustVerify() { + if p.signPolicy.mustSign() { + if msg.Signature == nil { + p.tracer.RejectMessage(msg, RejectMissingSignature) + return ValidationError{Reason: RejectMissingSignature} + } + // Actual signature verification happens in the validation pipeline, + // after checking if the message was already seen or not, + // to avoid unnecessary signature verification processing-cost. + } else { + if msg.Signature != nil { + p.tracer.RejectMessage(msg, RejectUnexpectedSignature) + return ValidationError{Reason: RejectUnexpectedSignature} + } + // If we are expecting signed messages, and not authoring messages, + // then do no accept seq numbers, from data, or key data. + // The default msgID function still relies on Seqno and From, + // but is not used if we are not authoring messages ourselves. + if p.signID == enode.ZeroID { + if msg.Seqno != nil || msg.From != enode.ZeroID || msg.Key != nil { + p.tracer.RejectMessage(msg, RejectUnexpectedAuthInfo) + return ValidationError{Reason: RejectUnexpectedAuthInfo} + } + } + } + } + + return nil +} + +func (p *PubSub) publishMessage(msg *Message) { + p.tracer.DeliverMessage(msg) + p.notifySubs(msg) + p.rt.Publish(msg) +} + +type addTopicReq struct { + topic *Topic + resp chan *Topic +} + +type rmTopicReq struct { + topic *Topic + resp chan error +} + +type TopicOptions struct{} + +type TopicOpt func(t *Topic) error + +// Join joins the topic and returns a Topic handle. Only one Topic handle should exist per topic, and Join will error if +// the Topic handle already exists. +func (p *PubSub) Join(topic string, opts ...TopicOpt) (*Topic, error) { + t, ok, err := p.tryJoin(topic, opts...) + if err != nil { + return nil, err + } + + if !ok { + return nil, fmt.Errorf("topic already exists") + } + + return t, nil +} + +// tryJoin is an internal function that tries to join a topic +// Returns the topic if it can be created or found +// Returns true if the topic was newly created, false otherwise +// Can be removed once pubsub.Publish() and pubsub.Subscribe() are removed +func (p *PubSub) tryJoin(topic string, opts ...TopicOpt) (*Topic, bool, error) { + if p.subFilter != nil && !p.subFilter.CanSubscribe(topic) { + return nil, false, fmt.Errorf("topic is not allowed by the subscription filter") + } + + t := &Topic{ + p: p, + topic: topic, + evtHandlers: make(map[*TopicEventHandler]struct{}), + } + + for _, opt := range opts { + err := opt(t) + if err != nil { + return nil, false, err + } + } + + resp := make(chan *Topic, 1) + select { + case t.p.addTopic <- &addTopicReq{ + topic: t, + resp: resp, + }: + case <-t.p.ctx.Done(): + return nil, false, t.p.ctx.Err() + } + returnedTopic := <-resp + + if returnedTopic != t { + return returnedTopic, false, nil + } + + return t, true, nil +} + +type addSubReq struct { + sub *Subscription + resp chan *Subscription +} + +type SubOpt func(sub *Subscription) error + +// Subscribe returns a new Subscription for the given topic. +// Note that subscription is not an instantaneous operation. It may take some time +// before the subscription is processed by the pubsub main loop and propagated to our peers. +// +// Deprecated: use pubsub.Join() and topic.Subscribe() instead +func (p *PubSub) Subscribe(topic string, opts ...SubOpt) (*Subscription, error) { + // ignore whether the topic was newly created or not, since either way we have a valid topic to work with + topicHandle, _, err := p.tryJoin(topic) + if err != nil { + return nil, err + } + + return topicHandle.Subscribe(opts...) +} + +// WithBufferSize is a Subscribe option to customize the size of the subscribe output buffer. +// The default length is 32 but it can be configured to avoid dropping messages if the consumer is not reading fast +// enough. +func WithBufferSize(size int) SubOpt { + return func(sub *Subscription) error { + sub.ch = make(chan *Message, size) + return nil + } +} + +type topicReq struct { + resp chan []string +} + +// GetTopics returns the topics this node is subscribed to. +func (p *PubSub) GetTopics() []string { + out := make(chan []string, 1) + select { + case p.getTopics <- &topicReq{resp: out}: + case <-p.ctx.Done(): + return nil + } + return <-out +} + +// Publish publishes data to the given topic. +// +// Deprecated: use pubsub.Join() and topic.Publish() instead +func (p *PubSub) Publish(topic string, data []byte, opts ...PubOpt) error { + // ignore whether the topic was newly created or not, since either way we have a valid topic to work with + t, _, err := p.tryJoin(topic) + if err != nil { + return err + } + + return t.Publish(context.TODO(), data, opts...) +} + +func (p *PubSub) nextSeqno() []byte { + seqno := make([]byte, 8) + counter := atomic.AddUint64(&p.counter, 1) + binary.BigEndian.PutUint64(seqno, counter) + return seqno +} + +type listPeerReq struct { + resp chan []enode.ID + topic string +} + +// ListPeers returns a list of peers we are connected to in the given topic. +func (p *PubSub) ListPeers(topic string) []enode.ID { + out := make(chan []enode.ID) + select { + case p.getPeers <- &listPeerReq{ + resp: out, + topic: topic, + }: + case <-p.ctx.Done(): + return nil + } + return <-out +} + +// BlacklistPeer blacklists a peer; all messages from this peer will be unconditionally dropped. +func (p *PubSub) BlacklistPeer(pid *enode.Node) { + select { + case p.blacklistPeer <- pid: + case <-p.ctx.Done(): + } +} + +// RegisterTopicValidator registers a validator for topic. +// By default validators are asynchronous, which means they will run in a separate goroutine. +// The number of active goroutines is controlled by global and per topic validator +// throttles; if it exceeds the throttle threshold, messages will be dropped. +func (p *PubSub) RegisterTopicValidator(topic string, val interface{}, opts ...ValidatorOpt) error { + addVal := &addValReq{ + topic: topic, + validate: val, + resp: make(chan error, 1), + } + + for _, opt := range opts { + err := opt(addVal) + if err != nil { + return err + } + } + + select { + case p.addVal <- addVal: + case <-p.ctx.Done(): + return p.ctx.Err() + } + return <-addVal.resp +} + +// UnregisterTopicValidator removes a validator from a topic. +// Returns an error if there was no validator registered with the topic. +func (p *PubSub) UnregisterTopicValidator(topic string) error { + rmVal := &rmValReq{ + topic: topic, + resp: make(chan error, 1), + } + + select { + case p.rmVal <- rmVal: + case <-p.ctx.Done(): + return p.ctx.Err() + } + return <-rmVal.resp +} + +type RelayCancelFunc func() + +type addRelayReq struct { + topic string + resp chan RelayCancelFunc +} diff --git a/p2p/pubsub/pubsub_common.go b/p2p/pubsub/pubsub_common.go new file mode 100644 index 0000000000..9fa95708a7 --- /dev/null +++ b/p2p/pubsub/pubsub_common.go @@ -0,0 +1,210 @@ +package pubsub + +import ( + "context" + + "github.com/AlayaNetwork/Alaya-Go/p2p" + + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + "github.com/AlayaNetwork/Alaya-Go/log" + + "github.com/gogo/protobuf/proto" +) + +// get the initial RPC containing all of our subscriptions to send to new peers +func (p *PubSub) getHelloPacket() *RPC { + var rpc RPC + + subscriptions := make(map[string]bool) + + for t := range p.mySubs { + subscriptions[t] = true + } + + for t := range p.myRelays { + subscriptions[t] = true + } + + for t := range subscriptions { + as := &message.RPC_SubOpts{ + Topicid: proto.String(t), + Subscribe: proto.Bool(true), + } + rpc.Subscriptions = append(rpc.Subscriptions, as) + } + return &rpc +} + +func (p *PubSub) handleNewStream(s Stream) { + peer := s.Conn().RemotePeer() + + p.inboundStreamsMx.Lock() + other, dup := p.inboundStreams[peer.ID()] + if dup { + log.Debug("duplicate inbound stream , resetting other stream", "from", peer) + other.Reset() + } + p.inboundStreams[peer.ID()] = s + p.inboundStreamsMx.Unlock() + + defer func() { + p.inboundStreamsMx.Lock() + if p.inboundStreams[peer.ID()] == s { + delete(p.inboundStreams, peer.ID()) + } + p.inboundStreamsMx.Unlock() + }() + + //r := protoio.NewDelimitedReader(s, p.maxMessageSize) + for { + msg, err := s.ReadWriter().ReadMsg() + if err != nil { + log.Debug("Read pubsub peer message error", "err", err) + p.notifyPeerDead(peer.ID()) + return + } + + rpc := new(RPC) + if err := msg.Decode(rpc); err != nil { + log.Error("Decode message fail", "err", err) + } + + rpc.from = peer + select { + case p.incoming <- rpc: + case <-p.ctx.Done(): + // Close is useless because the other side isn't reading. + s.Reset() + return + } + } +} + +func (p *PubSub) notifyPeerDead(pid enode.ID) { + p.peerDeadPrioLk.RLock() + p.peerDeadMx.Lock() + p.peerDeadPend[pid] = struct{}{} + p.peerDeadMx.Unlock() + p.peerDeadPrioLk.RUnlock() + + select { + case p.peerDead <- struct{}{}: + default: + } +} + +func (p *PubSub) handleNewPeer(ctx context.Context, pid enode.ID, outgoing <-chan *RPC) { + s, err := p.host.NewStream(p.ctx, pid, p.rt.Protocols()...) + if err != nil { + log.Debug("opening new stream to peer: ", err, pid) + + select { + case p.newPeerError <- pid: + case <-ctx.Done(): + } + + return + } + + go p.handleSendingMessages(ctx, s, outgoing) + //go p.handlePeerEOF(ctx, s) + select { + case p.newPeerStream <- s: + case <-ctx.Done(): + } +} + +/*func (p *PubSub) handlePeerEOF(ctx context.Context, s Stream) { + pid := s.Conn().RemotePeer() + r := protoio.NewDelimitedReader(s, p.maxMessageSize) + rpc := new(RPC) + for { + err := r.ReadMsg(&rpc.RPC) + if err != nil { + p.notifyPeerDead(pid.ID()) + return + } + + log.Debugf("unexpected message from %s", pid) + } +}*/ + +func (p *PubSub) handleSendingMessages(ctx context.Context, s Stream, outgoing <-chan *RPC) { + //bufw := bufio.NewWriter(s) + //wc := protoio.NewDelimitedWriter(bufw) + + /*writeMsg := func(msg proto.Message) error { + err := wc.WriteMsg(msg) + if err != nil { + return err + } + + return bufw.Flush() + }*/ + + defer s.Close() + for { + select { + case rpc, ok := <-outgoing: + if !ok { + return + } + + if err := p2p.Send(s.ReadWriter(), 0xff, &rpc.RPC); err != nil { + log.Error("Send message fail", "err", err) + } + + /* err := writeMsg(&rpc.RPC) + if err != nil { + s.Reset() + log.Debugf("writing message to %s: %s", s.Conn().RemotePeer(), err) + return + }*/ + case <-ctx.Done(): + return + } + } +} + +func rpcWithSubs(subs ...*message.RPC_SubOpts) *RPC { + return &RPC{ + RPC: message.RPC{ + Subscriptions: subs, + }, + } +} + +func rpcWithMessages(msgs ...*message.Message) *RPC { + return &RPC{RPC: message.RPC{Publish: msgs}} +} + +func rpcWithControl(msgs []*message.Message, + ihave []*message.ControlIHave, + iwant []*message.ControlIWant, + graft []*message.ControlGraft, + prune []*message.ControlPrune) *RPC { + return &RPC{ + RPC: message.RPC{ + Publish: msgs, + Control: &message.ControlMessage{ + Ihave: ihave, + Iwant: iwant, + Graft: graft, + Prune: prune, + }, + }, + } +} + +func copyRPC(rpc *RPC) *RPC { + res := new(RPC) + *res = *rpc + if rpc.Control != nil { + res.Control = new(message.ControlMessage) + *res.Control = *rpc.Control + } + return res +} diff --git a/p2p/pubsub/score.go b/p2p/pubsub/score.go new file mode 100644 index 0000000000..95962bda58 --- /dev/null +++ b/p2p/pubsub/score.go @@ -0,0 +1,1074 @@ +package pubsub + +import ( + "context" + "fmt" + "net" + "sync" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + "github.com/AlayaNetwork/Alaya-Go/log" +) + +type peerStats struct { + // true if the peer is currently connected + connected bool + + // expiration time of the score stats for disconnected peers + expire time.Time + + // per topc stats + topics map[string]*topicStats + + // IP tracking; store as string for easy processing + ips []string + + // IP whitelisting cache + ipWhitelist map[string]bool + + // behavioural pattern penalties (applied by the router) + behaviourPenalty float64 +} + +type topicStats struct { + // true if the peer is in the mesh + inMesh bool + + // time when the peer was (last) GRAFTed; valid only when in mesh + graftTime time.Time + + // time in mesh (updated during refresh/decay to avoid calling gettimeofday on + // every score invocation) + meshTime time.Duration + + // first message deliveries + firstMessageDeliveries float64 + + // mesh message deliveries + meshMessageDeliveries float64 + + // true if the peer has been enough time in the mesh to activate mess message deliveries + meshMessageDeliveriesActive bool + + // sticky mesh rate failure penalty counter + meshFailurePenalty float64 + + // invalid message counter + invalidMessageDeliveries float64 +} + +type peerScore struct { + sync.Mutex + + // the score parameters + params *PeerScoreParams + + // per peer stats for score calculation + peerStats map[enode.ID]*peerStats + + // IP colocation tracking; maps IP => set of peers. + peerIPs map[string]map[enode.ID]struct{} + + // message delivery tracking + deliveries *messageDeliveries + + msgID MsgIdFunction + host Host + + // debugging inspection + inspect PeerScoreInspectFn + inspectEx ExtendedPeerScoreInspectFn + inspectPeriod time.Duration +} + +var _ RawTracer = (*peerScore)(nil) + +type messageDeliveries struct { + records map[string]*deliveryRecord + + // queue for cleaning up old delivery records + head *deliveryEntry + tail *deliveryEntry +} + +type deliveryRecord struct { + status int + firstSeen time.Time + validated time.Time + peers map[enode.ID]struct{} +} + +type deliveryEntry struct { + id string + expire time.Time + next *deliveryEntry +} + +// delivery record status +const ( + deliveryUnknown = iota // we don't know (yet) if the message is valid + deliveryValid // we know the message is valid + deliveryInvalid // we know the message is invalid + deliveryIgnored // we were intructed by the validator to ignore the message + deliveryThrottled // we can't tell if it is valid because validation throttled +) + +type ( + PeerScoreInspectFn = func(map[enode.ID]float64) + ExtendedPeerScoreInspectFn = func(map[enode.ID]*PeerScoreSnapshot) +) + +type PeerScoreSnapshot struct { + Score float64 + Topics map[string]*TopicScoreSnapshot + AppSpecificScore float64 + IPColocationFactor float64 + BehaviourPenalty float64 +} + +type TopicScoreSnapshot struct { + TimeInMesh time.Duration + FirstMessageDeliveries float64 + MeshMessageDeliveries float64 + InvalidMessageDeliveries float64 +} + +// WithPeerScoreInspect is a gossipsub router option that enables peer score debugging. +// When this option is enabled, the supplied function will be invoked periodically to allow +// the application to inspect or dump the scores for connected peers. +// The supplied function can have one of two signatures: +// - PeerScoreInspectFn, which takes a map of peer IDs to score. +// - ExtendedPeerScoreInspectFn, which takes a map of peer IDs to +// PeerScoreSnapshots and allows inspection of individual score +// components for debugging peer scoring. +// This option must be passed _after_ the WithPeerScore option. +func WithPeerScoreInspect(inspect interface{}, period time.Duration) Option { + return func(ps *PubSub) error { + gs, ok := ps.rt.(*GossipSubRouter) + if !ok { + return fmt.Errorf("pubsub router is not gossipsub") + } + + if gs.score == nil { + return fmt.Errorf("peer scoring is not enabled") + } + + if gs.score.inspect != nil || gs.score.inspectEx != nil { + return fmt.Errorf("duplicate peer score inspector") + } + + switch i := inspect.(type) { + case PeerScoreInspectFn: + gs.score.inspect = i + case ExtendedPeerScoreInspectFn: + gs.score.inspectEx = i + default: + return fmt.Errorf("unknown peer score insector type: %v", inspect) + } + + gs.score.inspectPeriod = period + + return nil + } +} + +// implementation +func newPeerScore(params *PeerScoreParams) *peerScore { + return &peerScore{ + params: params, + peerStats: make(map[enode.ID]*peerStats), + peerIPs: make(map[string]map[enode.ID]struct{}), + deliveries: &messageDeliveries{records: make(map[string]*deliveryRecord)}, + msgID: DefaultMsgIdFn, + } +} + +// SetTopicScoreParams sets new score parameters for a topic. +// If the topic previously had parameters and the parameters are lowering delivery caps, +// then the score counters are recapped appropriately. +// Note: assumes that the topic score parameters have already been validated +func (ps *peerScore) SetTopicScoreParams(topic string, p *TopicScoreParams) error { + ps.Lock() + defer ps.Unlock() + + old, exist := ps.params.Topics[topic] + ps.params.Topics[topic] = p + + if !exist { + return nil + } + + // check to see if the counter Caps are being lowered; if that's the case we need to recap them + recap := false + if p.FirstMessageDeliveriesCap < old.FirstMessageDeliveriesCap { + recap = true + } + if p.MeshMessageDeliveriesCap < old.MeshMessageDeliveriesCap { + recap = true + } + if !recap { + return nil + } + + // recap counters for topic + for _, pstats := range ps.peerStats { + tstats, ok := pstats.topics[topic] + if !ok { + continue + } + + if tstats.firstMessageDeliveries > p.FirstMessageDeliveriesCap { + tstats.firstMessageDeliveries = p.FirstMessageDeliveriesCap + } + + if tstats.meshMessageDeliveries > p.MeshMessageDeliveriesCap { + tstats.meshMessageDeliveries = p.MeshMessageDeliveriesCap + } + } + + return nil +} + +// router interface +func (ps *peerScore) Start(gs *GossipSubRouter) { + if ps == nil { + return + } + + ps.msgID = gs.p.msgID + ps.host = gs.p.host + go ps.background(gs.p.ctx) +} + +func (ps *peerScore) Score(p enode.ID) float64 { + if ps == nil { + return 0 + } + + ps.Lock() + defer ps.Unlock() + + return ps.score(p) +} + +func (ps *peerScore) score(p enode.ID) float64 { + pstats, ok := ps.peerStats[p] + if !ok { + return 0 + } + + var score float64 + + // topic scores + for topic, tstats := range pstats.topics { + // the topic parameters + topicParams, ok := ps.params.Topics[topic] + if !ok { + // we are not scoring this topic + continue + } + + // the topic score + var topicScore float64 + + // P1: time in Mesh + if tstats.inMesh { + p1 := float64(tstats.meshTime / topicParams.TimeInMeshQuantum) + if p1 > topicParams.TimeInMeshCap { + p1 = topicParams.TimeInMeshCap + } + topicScore += p1 * topicParams.TimeInMeshWeight + } + + // P2: first message deliveries + p2 := tstats.firstMessageDeliveries + topicScore += p2 * topicParams.FirstMessageDeliveriesWeight + + // P3: mesh message deliveries + if tstats.meshMessageDeliveriesActive { + if tstats.meshMessageDeliveries < topicParams.MeshMessageDeliveriesThreshold { + deficit := topicParams.MeshMessageDeliveriesThreshold - tstats.meshMessageDeliveries + p3 := deficit * deficit + topicScore += p3 * topicParams.MeshMessageDeliveriesWeight + } + } + + // P3b: + // NOTE: the weight of P3b is negative (validated in TopicScoreParams.validate), so this detracts. + p3b := tstats.meshFailurePenalty + topicScore += p3b * topicParams.MeshFailurePenaltyWeight + + // P4: invalid messages + // NOTE: the weight of P4 is negative (validated in TopicScoreParams.validate), so this detracts. + p4 := (tstats.invalidMessageDeliveries * tstats.invalidMessageDeliveries) + topicScore += p4 * topicParams.InvalidMessageDeliveriesWeight + + // update score, mixing with topic weight + score += topicScore * topicParams.TopicWeight + } + + // apply the topic score cap, if any + if ps.params.TopicScoreCap > 0 && score > ps.params.TopicScoreCap { + score = ps.params.TopicScoreCap + } + + // P5: application-specific score + p5 := ps.params.AppSpecificScore(p) + score += p5 * ps.params.AppSpecificWeight + + // P6: IP collocation factor + p6 := ps.ipColocationFactor(p) + score += p6 * ps.params.IPColocationFactorWeight + + // P7: behavioural pattern penalty + if pstats.behaviourPenalty > ps.params.BehaviourPenaltyThreshold { + excess := pstats.behaviourPenalty - ps.params.BehaviourPenaltyThreshold + p7 := excess * excess + score += p7 * ps.params.BehaviourPenaltyWeight + } + + return score +} + +func (ps *peerScore) ipColocationFactor(p enode.ID) float64 { + pstats, ok := ps.peerStats[p] + if !ok { + return 0 + } + + var result float64 +loop: + for _, ip := range pstats.ips { + if len(ps.params.IPColocationFactorWhitelist) > 0 { + if pstats.ipWhitelist == nil { + pstats.ipWhitelist = make(map[string]bool) + } + + whitelisted, ok := pstats.ipWhitelist[ip] + if !ok { + ipObj := net.ParseIP(ip) + for _, ipNet := range ps.params.IPColocationFactorWhitelist { + if ipNet.Contains(ipObj) { + pstats.ipWhitelist[ip] = true + continue loop + } + } + + pstats.ipWhitelist[ip] = false + } + + if whitelisted { + continue loop + } + } + + // P6 has a cliff (IPColocationFactorThreshold); it's only applied iff + // at least that many peers are connected to us from that source IP + // addr. It is quadratic, and the weight is negative (validated by + // PeerScoreParams.validate). + peersInIP := len(ps.peerIPs[ip]) + if peersInIP > ps.params.IPColocationFactorThreshold { + surpluss := float64(peersInIP - ps.params.IPColocationFactorThreshold) + result += surpluss * surpluss + } + } + + return result +} + +// behavioural pattern penalties +func (ps *peerScore) AddPenalty(p enode.ID, count int) { + if ps == nil { + return + } + + ps.Lock() + defer ps.Unlock() + + pstats, ok := ps.peerStats[p] + if !ok { + return + } + + pstats.behaviourPenalty += float64(count) +} + +// periodic maintenance +func (ps *peerScore) background(ctx context.Context) { + refreshScores := time.NewTicker(ps.params.DecayInterval) + defer refreshScores.Stop() + + refreshIPs := time.NewTicker(time.Minute) + defer refreshIPs.Stop() + + gcDeliveryRecords := time.NewTicker(time.Minute) + defer gcDeliveryRecords.Stop() + + var inspectScores <-chan time.Time + if ps.inspect != nil || ps.inspectEx != nil { + ticker := time.NewTicker(ps.inspectPeriod) + defer ticker.Stop() + // also dump at exit for one final sample + defer ps.inspectScores() + inspectScores = ticker.C + } + + for { + select { + case <-refreshScores.C: + ps.refreshScores() + + case <-refreshIPs.C: + ps.refreshIPs() + + case <-gcDeliveryRecords.C: + ps.gcDeliveryRecords() + + case <-inspectScores: + ps.inspectScores() + + case <-ctx.Done(): + return + } + } +} + +// inspectScores dumps all tracked scores into the inspect function. +func (ps *peerScore) inspectScores() { + if ps.inspect != nil { + ps.inspectScoresSimple() + } + if ps.inspectEx != nil { + ps.inspectScoresExtended() + } +} + +func (ps *peerScore) inspectScoresSimple() { + ps.Lock() + scores := make(map[enode.ID]float64, len(ps.peerStats)) + for p := range ps.peerStats { + scores[p] = ps.score(p) + } + ps.Unlock() + + // Since this is a user-injected function, it could be performing I/O, and + // we don't want to block the scorer's background loop. Therefore, we launch + // it in a separate goroutine. If the function needs to synchronise, it + // should do so locally. + go ps.inspect(scores) +} + +func (ps *peerScore) inspectScoresExtended() { + ps.Lock() + scores := make(map[enode.ID]*PeerScoreSnapshot, len(ps.peerStats)) + for p, pstats := range ps.peerStats { + pss := new(PeerScoreSnapshot) + pss.Score = ps.score(p) + if len(pstats.topics) > 0 { + pss.Topics = make(map[string]*TopicScoreSnapshot, len(pstats.topics)) + for t, ts := range pstats.topics { + tss := &TopicScoreSnapshot{ + FirstMessageDeliveries: ts.firstMessageDeliveries, + MeshMessageDeliveries: ts.meshMessageDeliveries, + InvalidMessageDeliveries: ts.invalidMessageDeliveries, + } + if ts.inMesh { + tss.TimeInMesh = ts.meshTime + } + pss.Topics[t] = tss + } + } + pss.AppSpecificScore = ps.params.AppSpecificScore(p) + pss.IPColocationFactor = ps.ipColocationFactor(p) + pss.BehaviourPenalty = pstats.behaviourPenalty + scores[p] = pss + } + ps.Unlock() + + go ps.inspectEx(scores) +} + +// refreshScores decays scores, and purges score records for disconnected peers, +// once their expiry has elapsed. +func (ps *peerScore) refreshScores() { + ps.Lock() + defer ps.Unlock() + + now := time.Now() + for p, pstats := range ps.peerStats { + if !pstats.connected { + // has the retention period expired? + if now.After(pstats.expire) { + // yes, throw it away (but clean up the IP tracking first) + ps.removeIPs(p, pstats.ips) + delete(ps.peerStats, p) + } + + // we don't decay retained scores, as the peer is not active. + // this way the peer cannot reset a negative score by simply disconnecting and reconnecting, + // unless the retention period has ellapsed. + // similarly, a well behaved peer does not lose its score by getting disconnected. + continue + } + + for topic, tstats := range pstats.topics { + // the topic parameters + topicParams, ok := ps.params.Topics[topic] + if !ok { + // we are not scoring this topic + continue + } + + // decay counters + tstats.firstMessageDeliveries *= topicParams.FirstMessageDeliveriesDecay + if tstats.firstMessageDeliveries < ps.params.DecayToZero { + tstats.firstMessageDeliveries = 0 + } + tstats.meshMessageDeliveries *= topicParams.MeshMessageDeliveriesDecay + if tstats.meshMessageDeliveries < ps.params.DecayToZero { + tstats.meshMessageDeliveries = 0 + } + tstats.meshFailurePenalty *= topicParams.MeshFailurePenaltyDecay + if tstats.meshFailurePenalty < ps.params.DecayToZero { + tstats.meshFailurePenalty = 0 + } + tstats.invalidMessageDeliveries *= topicParams.InvalidMessageDeliveriesDecay + if tstats.invalidMessageDeliveries < ps.params.DecayToZero { + tstats.invalidMessageDeliveries = 0 + } + // update mesh time and activate mesh message delivery parameter if need be + if tstats.inMesh { + tstats.meshTime = now.Sub(tstats.graftTime) + if tstats.meshTime > topicParams.MeshMessageDeliveriesActivation { + tstats.meshMessageDeliveriesActive = true + } + } + } + + // decay P7 counter + pstats.behaviourPenalty *= ps.params.BehaviourPenaltyDecay + if pstats.behaviourPenalty < ps.params.DecayToZero { + pstats.behaviourPenalty = 0 + } + } +} + +// refreshIPs refreshes IPs we know of peers we're tracking. +func (ps *peerScore) refreshIPs() { + ps.Lock() + defer ps.Unlock() + + // peer IPs may change, so we periodically refresh them + // + // TODO: it could be more efficient to collect connections for all peers + // from the Network, populate a new map, and replace it in place. We are + // incurring in those allocs anyway, and maybe even in more, in the form of + // slices. + for p, pstats := range ps.peerStats { + if pstats.connected { + ips := ps.getIPs(p) + ps.setIPs(p, ips, pstats.ips) + pstats.ips = ips + } + } +} + +func (ps *peerScore) gcDeliveryRecords() { + ps.Lock() + defer ps.Unlock() + + ps.deliveries.gc() +} + +// tracer interface +func (ps *peerScore) AddPeer(p *enode.Node, proto ProtocolID) { + ps.Lock() + defer ps.Unlock() + + pstats, ok := ps.peerStats[p.ID()] + if !ok { + pstats = &peerStats{topics: make(map[string]*topicStats)} + ps.peerStats[p.ID()] = pstats + } + + pstats.connected = true + ips := ps.getIPs(p.ID()) + ps.setIPs(p.ID(), ips, pstats.ips) + pstats.ips = ips +} + +func (ps *peerScore) RemovePeer(p enode.ID) { + ps.Lock() + defer ps.Unlock() + + pstats, ok := ps.peerStats[p] + if !ok { + return + } + + // decide whether to retain the score; this currently only retains non-positive scores + // to dissuade attacks on the score function. + if ps.score(p) > 0 { + ps.removeIPs(p, pstats.ips) + delete(ps.peerStats, p) + return + } + + // furthermore, when we decide to retain the score, the firstMessageDelivery counters are + // reset to 0 and mesh delivery penalties applied. + for topic, tstats := range pstats.topics { + tstats.firstMessageDeliveries = 0 + + threshold := ps.params.Topics[topic].MeshMessageDeliveriesThreshold + if tstats.inMesh && tstats.meshMessageDeliveriesActive && tstats.meshMessageDeliveries < threshold { + deficit := threshold - tstats.meshMessageDeliveries + tstats.meshFailurePenalty += deficit * deficit + } + + tstats.inMesh = false + } + + pstats.connected = false + pstats.expire = time.Now().Add(ps.params.RetainScore) +} + +func (ps *peerScore) Join(topic string) {} +func (ps *peerScore) Leave(topic string) {} + +func (ps *peerScore) Graft(p enode.ID, topic string) { + ps.Lock() + defer ps.Unlock() + + pstats, ok := ps.peerStats[p] + if !ok { + return + } + + tstats, ok := pstats.getTopicStats(topic, ps.params) + if !ok { + return + } + + tstats.inMesh = true + tstats.graftTime = time.Now() + tstats.meshTime = 0 + tstats.meshMessageDeliveriesActive = false +} + +func (ps *peerScore) Prune(p enode.ID, topic string) { + ps.Lock() + defer ps.Unlock() + + pstats, ok := ps.peerStats[p] + if !ok { + return + } + + tstats, ok := pstats.getTopicStats(topic, ps.params) + if !ok { + return + } + + // sticky mesh delivery rate failure penalty + threshold := ps.params.Topics[topic].MeshMessageDeliveriesThreshold + if tstats.meshMessageDeliveriesActive && tstats.meshMessageDeliveries < threshold { + deficit := threshold - tstats.meshMessageDeliveries + tstats.meshFailurePenalty += deficit * deficit + } + + tstats.inMesh = false +} + +func (ps *peerScore) ValidateMessage(msg *Message) { + ps.Lock() + defer ps.Unlock() + + // the pubsub subsystem is beginning validation; create a record to track time in + // the validation pipeline with an accurate firstSeen time. + _ = ps.deliveries.getRecord(ps.msgID(msg.Message)) +} + +func (ps *peerScore) DeliverMessage(msg *Message) { + ps.Lock() + defer ps.Unlock() + + ps.markFirstMessageDelivery(msg.ReceivedFrom.ID(), msg) + + drec := ps.deliveries.getRecord(ps.msgID(msg.Message)) + + // defensive check that this is the first delivery trace -- delivery status should be unknown + if drec.status != deliveryUnknown { + log.Debug("unexpected delivery trace: message from %s was first seen %s ago and has delivery status %d", "from", msg.ReceivedFrom, "seen", time.Since(drec.firstSeen), "status", drec.status) + return + } + + // mark the message as valid and reward mesh peers that have already forwarded it to us + drec.status = deliveryValid + drec.validated = time.Now() + for p := range drec.peers { + // this check is to make sure a peer can't send us a message twice and get a double count + // if it is a first delivery. + if p != msg.ReceivedFrom.ID() { + ps.markDuplicateMessageDelivery(p, msg, time.Time{}) + } + } +} + +func (ps *peerScore) RejectMessage(msg *Message, reason string) { + ps.Lock() + defer ps.Unlock() + + switch reason { + // we don't track those messages, but we penalize the peer as they are clearly invalid + case RejectMissingSignature: + fallthrough + case RejectInvalidSignature: + fallthrough + case RejectUnexpectedSignature: + fallthrough + case RejectUnexpectedAuthInfo: + fallthrough + case RejectSelfOrigin: + ps.markInvalidMessageDelivery(msg.ReceivedFrom.ID(), msg) + return + + // we ignore those messages, so do nothing. + case RejectBlacklstedPeer: + fallthrough + case RejectBlacklistedSource: + return + + case RejectValidationQueueFull: + // the message was rejected before it entered the validation pipeline; + // we don't know if this message has a valid signature, and thus we also don't know if + // it has a valid message ID; all we can do is ignore it. + return + } + + drec := ps.deliveries.getRecord(ps.msgID(msg.Message)) + + // defensive check that this is the first rejection trace -- delivery status should be unknown + if drec.status != deliveryUnknown { + log.Debug("unexpected rejection trace: message from %s was first seen %s ago and has delivery status %d", "from", msg.ReceivedFrom, "seen", time.Since(drec.firstSeen), "status", drec.status) + return + } + + switch reason { + case RejectValidationThrottled: + // if we reject with "validation throttled" we don't penalize the peer(s) that forward it + // because we don't know if it was valid. + drec.status = deliveryThrottled + // release the delivery time tracking map to free some memory early + drec.peers = nil + return + case RejectValidationIgnored: + // we were explicitly instructed by the validator to ignore the message but not penalize + // the peer + drec.status = deliveryIgnored + drec.peers = nil + return + } + + // mark the message as invalid and penalize peers that have already forwarded it. + drec.status = deliveryInvalid + + ps.markInvalidMessageDelivery(msg.ReceivedFrom.ID(), msg) + for p := range drec.peers { + ps.markInvalidMessageDelivery(p, msg) + } + + // release the delivery time tracking map to free some memory early + drec.peers = nil +} + +func (ps *peerScore) DuplicateMessage(msg *Message) { + ps.Lock() + defer ps.Unlock() + + drec := ps.deliveries.getRecord(ps.msgID(msg.Message)) + + _, ok := drec.peers[msg.ReceivedFrom.ID()] + if ok { + // we have already seen this duplicate! + return + } + + switch drec.status { + case deliveryUnknown: + // the message is being validated; track the peer delivery and wait for + // the Deliver/Reject notification. + drec.peers[msg.ReceivedFrom.ID()] = struct{}{} + + case deliveryValid: + // mark the peer delivery time to only count a duplicate delivery once. + drec.peers[msg.ReceivedFrom.ID()] = struct{}{} + ps.markDuplicateMessageDelivery(msg.ReceivedFrom.ID(), msg, drec.validated) + + case deliveryInvalid: + // we no longer track delivery time + ps.markInvalidMessageDelivery(msg.ReceivedFrom.ID(), msg) + + case deliveryThrottled: + // the message was throttled; do nothing (we don't know if it was valid) + case deliveryIgnored: + // the message was ignored; do nothing + } +} + +func (ps *peerScore) ThrottlePeer(p enode.ID) {} + +func (ps *peerScore) RecvRPC(rpc *RPC) {} + +func (ps *peerScore) SendRPC(rpc *RPC, p enode.ID) {} + +func (ps *peerScore) DropRPC(rpc *RPC, p enode.ID) {} + +func (ps *peerScore) UndeliverableMessage(msg *Message) {} + +// message delivery records +func (d *messageDeliveries) getRecord(id string) *deliveryRecord { + rec, ok := d.records[id] + if ok { + return rec + } + + now := time.Now() + + rec = &deliveryRecord{peers: make(map[enode.ID]struct{}), firstSeen: now} + d.records[id] = rec + + entry := &deliveryEntry{id: id, expire: now.Add(TimeCacheDuration)} + if d.tail != nil { + d.tail.next = entry + d.tail = entry + } else { + d.head = entry + d.tail = entry + } + + return rec +} + +func (d *messageDeliveries) gc() { + if d.head == nil { + return + } + + now := time.Now() + for d.head != nil && now.After(d.head.expire) { + delete(d.records, d.head.id) + d.head = d.head.next + } + + if d.head == nil { + d.tail = nil + } +} + +// getTopicStats returns existing topic stats for a given a given (peer, topic) +// tuple, or initialises a new topicStats object and inserts it in the +// peerStats, iff the topic is scored. +func (pstats *peerStats) getTopicStats(topic string, params *PeerScoreParams) (*topicStats, bool) { + tstats, ok := pstats.topics[topic] + if ok { + return tstats, true + } + + _, scoredTopic := params.Topics[topic] + if !scoredTopic { + return nil, false + } + + tstats = &topicStats{} + pstats.topics[topic] = tstats + + return tstats, true +} + +// markInvalidMessageDelivery increments the "invalid message deliveries" +// counter for all scored topics the message is published in. +func (ps *peerScore) markInvalidMessageDelivery(p enode.ID, msg *Message) { + pstats, ok := ps.peerStats[p] + if !ok { + return + } + + topic := msg.GetTopic() + tstats, ok := pstats.getTopicStats(topic, ps.params) + if !ok { + return + } + + tstats.invalidMessageDeliveries += 1 +} + +// markFirstMessageDelivery increments the "first message deliveries" counter +// for all scored topics the message is published in, as well as the "mesh +// message deliveries" counter, if the peer is in the mesh for the topic. +func (ps *peerScore) markFirstMessageDelivery(p enode.ID, msg *Message) { + pstats, ok := ps.peerStats[p] + if !ok { + return + } + + topic := msg.GetTopic() + tstats, ok := pstats.getTopicStats(topic, ps.params) + if !ok { + return + } + + cap := ps.params.Topics[topic].FirstMessageDeliveriesCap + tstats.firstMessageDeliveries += 1 + if tstats.firstMessageDeliveries > cap { + tstats.firstMessageDeliveries = cap + } + + if !tstats.inMesh { + return + } + + cap = ps.params.Topics[topic].MeshMessageDeliveriesCap + tstats.meshMessageDeliveries += 1 + if tstats.meshMessageDeliveries > cap { + tstats.meshMessageDeliveries = cap + } +} + +// markDuplicateMessageDelivery increments the "mesh message deliveries" counter +// for messages we've seen before, as long the message was received within the +// P3 window. +func (ps *peerScore) markDuplicateMessageDelivery(p enode.ID, msg *Message, validated time.Time) { + pstats, ok := ps.peerStats[p] + if !ok { + return + } + + topic := msg.GetTopic() + tstats, ok := pstats.getTopicStats(topic, ps.params) + if !ok { + return + } + + if !tstats.inMesh { + return + } + + tparams := ps.params.Topics[topic] + + // check against the mesh delivery window -- if the validated time is passed as 0, then + // the message was received before we finished validation and thus falls within the mesh + // delivery window. + if !validated.IsZero() && time.Since(validated) > tparams.MeshMessageDeliveriesWindow { + return + } + + cap := tparams.MeshMessageDeliveriesCap + tstats.meshMessageDeliveries += 1 + if tstats.meshMessageDeliveries > cap { + tstats.meshMessageDeliveries = cap + } +} + +// getIPs gets the current IPs for a peer. +func (ps *peerScore) getIPs(p enode.ID) []string { + // in unit tests this can be nil + if ps.host == nil { + return nil + } + + conns := ps.host.Network().ConnsToPeer(p) + res := make([]string, 0, 1) + for _, c := range conns { + if c.Stat().Transient { + // ignore transient + continue + } + peer := c.RemotePeer() + + ip := peer.IP() + /*remote := c.RemoteMultiaddr() + ip, err := manet.ToIP(remote) + if err != nil { + continue + }*/ + + // ignore those; loopback is used for unit testing + if ip.IsLoopback() { + continue + } + + if len(ip.To4()) == 4 { + // IPv4 address + ip4 := ip.String() + res = append(res, ip4) + } else { + // IPv6 address -- we add both the actual address and the /64 subnet + ip6 := ip.String() + res = append(res, ip6) + + ip6mask := ip.Mask(net.CIDRMask(64, 128)).String() + res = append(res, ip6mask) + } + } + + return res +} + +// setIPs adds tracking for the new IPs in the list, and removes tracking from +// the obsolete IPs. +func (ps *peerScore) setIPs(p enode.ID, newips, oldips []string) { +addNewIPs: + // add the new IPs to the tracking + for _, ip := range newips { + // check if it is in the old ips list + for _, xip := range oldips { + if ip == xip { + continue addNewIPs + } + } + // no, it's a new one -- add it to the tracker + peers, ok := ps.peerIPs[ip] + if !ok { + peers = make(map[enode.ID]struct{}) + ps.peerIPs[ip] = peers + } + peers[p] = struct{}{} + } + +removeOldIPs: + // remove the obsolete old IPs from the tracking + for _, ip := range oldips { + // check if it is in the new ips list + for _, xip := range newips { + if ip == xip { + continue removeOldIPs + } + } + // no, it's obsolete -- remove it from the tracker + peers, ok := ps.peerIPs[ip] + if !ok { + continue + } + delete(peers, p) + if len(peers) == 0 { + delete(ps.peerIPs, ip) + } + } +} + +// removeIPs removes an IP list from the tracking list for a peer. +func (ps *peerScore) removeIPs(p enode.ID, ips []string) { + for _, ip := range ips { + peers, ok := ps.peerIPs[ip] + if !ok { + continue + } + + delete(peers, p) + if len(peers) == 0 { + delete(ps.peerIPs, ip) + } + } +} diff --git a/p2p/pubsub/score_params.go b/p2p/pubsub/score_params.go new file mode 100644 index 0000000000..1de2fdc465 --- /dev/null +++ b/p2p/pubsub/score_params.go @@ -0,0 +1,293 @@ +package pubsub + +import ( + "fmt" + "math" + "net" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +type PeerScoreThresholds struct { + // GossipThreshold is the score threshold below which gossip propagation is supressed; + // should be negative. + GossipThreshold float64 + + // PublishThreshold is the score threshold below which we shouldn't publish when using flood + // publishing (also applies to fanout and floodsub peers); should be negative and <= GossipThreshold. + PublishThreshold float64 + + // GraylistThreshold is the score threshold below which message processing is supressed altogether, + // implementing an effective graylist according to peer score; should be negative and <= PublisThreshold. + GraylistThreshold float64 + + // AcceptPXThreshold is the score threshold below which PX will be ignored; this should be positive + // and limited to scores attainable by bootstrappers and other trusted nodes. + AcceptPXThreshold float64 + + // OpportunisticGraftThreshold is the median mesh score threshold before triggering opportunistic + // grafting; this should have a small positive value. + OpportunisticGraftThreshold float64 +} + +func (p *PeerScoreThresholds) validate() error { + if p.GossipThreshold > 0 || isInvalidNumber(p.GossipThreshold) { + return fmt.Errorf("invalid gossip threshold; it must be <= 0 and a valid number") + } + if p.PublishThreshold > 0 || p.PublishThreshold > p.GossipThreshold || isInvalidNumber(p.PublishThreshold) { + return fmt.Errorf("invalid publish threshold; it must be <= 0 and <= gossip threshold and a valid number") + } + if p.GraylistThreshold > 0 || p.GraylistThreshold > p.PublishThreshold || isInvalidNumber(p.GraylistThreshold) { + return fmt.Errorf("invalid graylist threshold; it must be <= 0 and <= publish threshold and a valid number") + } + if p.AcceptPXThreshold < 0 || isInvalidNumber(p.AcceptPXThreshold) { + return fmt.Errorf("invalid accept PX threshold; it must be >= 0 and a valid number") + } + if p.OpportunisticGraftThreshold < 0 || isInvalidNumber(p.OpportunisticGraftThreshold) { + return fmt.Errorf("invalid opportunistic grafting threshold; it must be >= 0 and a valid number") + } + return nil +} + +type PeerScoreParams struct { + // Score parameters per topic. + Topics map[string]*TopicScoreParams + + // Aggregate topic score cap; this limits the total contribution of topics towards a positive + // score. It must be positive (or 0 for no cap). + TopicScoreCap float64 + + // P5: Application-specific peer scoring + AppSpecificScore func(p enode.ID) float64 + AppSpecificWeight float64 + + // P6: IP-colocation factor. + // The parameter has an associated counter which counts the number of peers with the same IP. + // If the number of peers in the same IP exceeds IPColocationFactorThreshold, then the value + // is the square of the difference, ie (PeersInSameIP - IPColocationThreshold)^2. + // If the number of peers in the same IP is less than the threshold, then the value is 0. + // The weight of the parameter MUST be negative, unless you want to disable for testing. + // Note: In order to simulate many IPs in a managable manner when testing, you can set the weight to 0 + // thus disabling the IP colocation penalty. + IPColocationFactorWeight float64 + IPColocationFactorThreshold int + IPColocationFactorWhitelist []*net.IPNet + + // P7: behavioural pattern penalties. + // This parameter has an associated counter which tracks misbehaviour as detected by the + // router. The router currently applies penalties for the following behaviors: + // - attempting to re-graft before the prune backoff time has elapsed. + // - not following up in IWANT requests for messages advertised with IHAVE. + // + // The value of the parameter is the square of the counter over the threshold, which decays with + // BehaviourPenaltyDecay. + // The weight of the parameter MUST be negative (or zero to disable). + BehaviourPenaltyWeight, BehaviourPenaltyThreshold, BehaviourPenaltyDecay float64 + + // the decay interval for parameter counters. + DecayInterval time.Duration + + // counter value below which it is considered 0. + DecayToZero float64 + + // time to remember counters for a disconnected peer. + RetainScore time.Duration +} + +type TopicScoreParams struct { + // The weight of the topic. + TopicWeight float64 + + // P1: time in the mesh + // This is the time the peer has ben grafted in the mesh. + // The value of of the parameter is the time/TimeInMeshQuantum, capped by TimeInMeshCap + // The weight of the parameter MUST be positive (or zero to disable). + TimeInMeshWeight float64 + TimeInMeshQuantum time.Duration + TimeInMeshCap float64 + + // P2: first message deliveries + // This is the number of message deliveries in the topic. + // The value of the parameter is a counter, decaying with FirstMessageDeliveriesDecay, and capped + // by FirstMessageDeliveriesCap. + // The weight of the parameter MUST be positive (or zero to disable). + FirstMessageDeliveriesWeight, FirstMessageDeliveriesDecay float64 + FirstMessageDeliveriesCap float64 + + // P3: mesh message deliveries + // This is the number of message deliveries in the mesh, within the MeshMessageDeliveriesWindow of + // message validation; deliveries during validation also count and are retroactively applied + // when validation succeeds. + // This window accounts for the minimum time before a hostile mesh peer trying to game the score + // could replay back a valid message we just sent them. + // It effectively tracks first and near-first deliveries, ie a message seen from a mesh peer + // before we have forwarded it to them. + // The parameter has an associated counter, decaying with MeshMessageDeliveriesDecay. + // If the counter exceeds the threshold, its value is 0. + // If the counter is below the MeshMessageDeliveriesThreshold, the value is the square of + // the deficit, ie (MessageDeliveriesThreshold - counter)^2 + // The penalty is only activated after MeshMessageDeliveriesActivation time in the mesh. + // The weight of the parameter MUST be negative (or zero to disable). + MeshMessageDeliveriesWeight, MeshMessageDeliveriesDecay float64 + MeshMessageDeliveriesCap, MeshMessageDeliveriesThreshold float64 + MeshMessageDeliveriesWindow, MeshMessageDeliveriesActivation time.Duration + + // P3b: sticky mesh propagation failures + // This is a sticky penalty that applies when a peer gets pruned from the mesh with an active + // mesh message delivery penalty. + // The weight of the parameter MUST be negative (or zero to disable) + MeshFailurePenaltyWeight, MeshFailurePenaltyDecay float64 + + // P4: invalid messages + // This is the number of invalid messages in the topic. + // The value of the parameter is the square of the counter, decaying with + // InvalidMessageDeliveriesDecay. + // The weight of the parameter MUST be negative (or zero to disable). + InvalidMessageDeliveriesWeight, InvalidMessageDeliveriesDecay float64 +} + +// peer score parameter validation +func (p *PeerScoreParams) validate() error { + for topic, params := range p.Topics { + err := params.validate() + if err != nil { + return fmt.Errorf("invalid score parameters for topic %s: %w", topic, err) + } + } + + // check that the topic score is 0 or something positive + if p.TopicScoreCap < 0 || isInvalidNumber(p.TopicScoreCap) { + return fmt.Errorf("invalid topic score cap; must be positive (or 0 for no cap) and a valid number") + } + + // check that we have an app specific score; the weight can be anything (but expected positive) + if p.AppSpecificScore == nil { + return fmt.Errorf("missing application specific score function") + } + + // check the IP colocation factor + if p.IPColocationFactorWeight > 0 || isInvalidNumber(p.IPColocationFactorWeight) { + return fmt.Errorf("invalid IPColocationFactorWeight; must be negative (or 0 to disable) and a valid number") + } + if p.IPColocationFactorWeight != 0 && p.IPColocationFactorThreshold < 1 { + return fmt.Errorf("invalid IPColocationFactorThreshold; must be at least 1") + } + + // check the behaviour penalty + if p.BehaviourPenaltyWeight > 0 || isInvalidNumber(p.BehaviourPenaltyWeight) { + return fmt.Errorf("invalid BehaviourPenaltyWeight; must be negative (or 0 to disable) and a valid number") + } + if p.BehaviourPenaltyWeight != 0 && (p.BehaviourPenaltyDecay <= 0 || p.BehaviourPenaltyDecay >= 1 || isInvalidNumber(p.BehaviourPenaltyDecay)) { + return fmt.Errorf("invalid BehaviourPenaltyDecay; must be between 0 and 1") + } + if p.BehaviourPenaltyThreshold < 0 || isInvalidNumber(p.BehaviourPenaltyThreshold) { + return fmt.Errorf("invalid BehaviourPenaltyThreshold; must be >= 0 and a valid number") + } + + // check the decay parameters + if p.DecayInterval < time.Second { + return fmt.Errorf("invalid DecayInterval; must be at least 1s") + } + if p.DecayToZero <= 0 || p.DecayToZero >= 1 || isInvalidNumber(p.DecayToZero) { + return fmt.Errorf("invalid DecayToZero; must be between 0 and 1") + } + + // no need to check the score retention; a value of 0 means that we don't retain scores + return nil +} + +func (p *TopicScoreParams) validate() error { + // make sure we have a sane topic weight + if p.TopicWeight < 0 || isInvalidNumber(p.TopicWeight) { + return fmt.Errorf("invalid topic weight; must be >= 0 and a valid number") + } + + // check P1 + if p.TimeInMeshQuantum == 0 { + return fmt.Errorf("invalid TimeInMeshQuantum; must be non zero") + } + if p.TimeInMeshWeight < 0 || isInvalidNumber(p.TimeInMeshWeight) { + return fmt.Errorf("invalid TimeInMeshWeight; must be positive (or 0 to disable) and a valid number") + } + if p.TimeInMeshWeight != 0 && p.TimeInMeshQuantum <= 0 { + return fmt.Errorf("invalid TimeInMeshQuantum; must be positive") + } + if p.TimeInMeshWeight != 0 && (p.TimeInMeshCap <= 0 || isInvalidNumber(p.TimeInMeshCap)) { + return fmt.Errorf("invalid TimeInMeshCap; must be positive and a valid number") + } + + // check P2 + if p.FirstMessageDeliveriesWeight < 0 || isInvalidNumber(p.FirstMessageDeliveriesWeight) { + return fmt.Errorf("invallid FirstMessageDeliveriesWeight; must be positive (or 0 to disable) and a valid number") + } + if p.FirstMessageDeliveriesWeight != 0 && (p.FirstMessageDeliveriesDecay <= 0 || p.FirstMessageDeliveriesDecay >= 1 || isInvalidNumber(p.FirstMessageDeliveriesDecay)) { + return fmt.Errorf("invalid FirstMessageDeliveriesDecay; must be between 0 and 1") + } + if p.FirstMessageDeliveriesWeight != 0 && (p.FirstMessageDeliveriesCap <= 0 || isInvalidNumber(p.FirstMessageDeliveriesCap)) { + return fmt.Errorf("invalid FirstMessageDeliveriesCap; must be positive and a valid number") + } + + // check P3 + if p.MeshMessageDeliveriesWeight > 0 || isInvalidNumber(p.MeshMessageDeliveriesWeight) { + return fmt.Errorf("invalid MeshMessageDeliveriesWeight; must be negative (or 0 to disable) and a valid number") + } + if p.MeshMessageDeliveriesWeight != 0 && (p.MeshMessageDeliveriesDecay <= 0 || p.MeshMessageDeliveriesDecay >= 1 || isInvalidNumber(p.MeshMessageDeliveriesDecay)) { + return fmt.Errorf("invalid MeshMessageDeliveriesDecay; must be between 0 and 1") + } + if p.MeshMessageDeliveriesWeight != 0 && (p.MeshMessageDeliveriesCap <= 0 || isInvalidNumber(p.MeshMessageDeliveriesCap)) { + return fmt.Errorf("invalid MeshMessageDeliveriesCap; must be positive and a valid number") + } + if p.MeshMessageDeliveriesWeight != 0 && (p.MeshMessageDeliveriesThreshold <= 0 || isInvalidNumber(p.MeshMessageDeliveriesThreshold)) { + return fmt.Errorf("invalid MeshMessageDeliveriesThreshold; must be positive and a valid number") + } + if p.MeshMessageDeliveriesWindow < 0 { + return fmt.Errorf("invalid MeshMessageDeliveriesWindow; must be non-negative") + } + if p.MeshMessageDeliveriesWeight != 0 && p.MeshMessageDeliveriesActivation < time.Second { + return fmt.Errorf("invalid MeshMessageDeliveriesActivation; must be at least 1s") + } + + // check P3b + if p.MeshFailurePenaltyWeight > 0 || isInvalidNumber(p.MeshFailurePenaltyWeight) { + return fmt.Errorf("invalid MeshFailurePenaltyWeight; must be negative (or 0 to disable) and a valid number") + } + if p.MeshFailurePenaltyWeight != 0 && (isInvalidNumber(p.MeshFailurePenaltyDecay) || p.MeshFailurePenaltyDecay <= 0 || p.MeshFailurePenaltyDecay >= 1) { + return fmt.Errorf("invalid MeshFailurePenaltyDecay; must be between 0 and 1") + } + + // check P4 + if p.InvalidMessageDeliveriesWeight > 0 || isInvalidNumber(p.InvalidMessageDeliveriesWeight) { + return fmt.Errorf("invalid InvalidMessageDeliveriesWeight; must be negative (or 0 to disable) and a valid number") + } + if p.InvalidMessageDeliveriesDecay <= 0 || p.InvalidMessageDeliveriesDecay >= 1 || isInvalidNumber(p.InvalidMessageDeliveriesDecay) { + return fmt.Errorf("invalid InvalidMessageDeliveriesDecay; must be between 0 and 1") + } + + return nil +} + +const ( + DefaultDecayInterval = time.Second + DefaultDecayToZero = 0.01 +) + +// ScoreParameterDecay computes the decay factor for a parameter, assuming the DecayInterval is 1s +// and that the value decays to zero if it drops below 0.01 +func ScoreParameterDecay(decay time.Duration) float64 { + return ScoreParameterDecayWithBase(decay, DefaultDecayInterval, DefaultDecayToZero) +} + +// ScoreParameterDecay computes the decay factor for a parameter using base as the DecayInterval +func ScoreParameterDecayWithBase(decay time.Duration, base time.Duration, decayToZero float64) float64 { + // the decay is linear, so after n ticks the value is factor^n + // so factor^n = decayToZero => factor = decayToZero^(1/n) + ticks := float64(decay / base) + return math.Pow(decayToZero, 1/ticks) +} + +// checks whether the provided floating-point number is `Not a Number` +// or an infinite number. +func isInvalidNumber(num float64) bool { + return math.IsNaN(num) || math.IsInf(num, 0) +} diff --git a/p2p/pubsub/sign.go b/p2p/pubsub/sign.go new file mode 100644 index 0000000000..cb6b5c08cb --- /dev/null +++ b/p2p/pubsub/sign.go @@ -0,0 +1,129 @@ +package pubsub + +// MessageSignaturePolicy describes if signatures are produced, expected, and/or verified. +type MessageSignaturePolicy uint8 + +// LaxSign and LaxNoSign are deprecated. In the future msgSigning and msgVerification can be unified. +const ( + // msgSigning is set when the locally produced messages must be signed + msgSigning MessageSignaturePolicy = 1 << iota + // msgVerification is set when external messages must be verfied + msgVerification +) + +const ( + // StrictSign produces signatures and expects and verifies incoming signatures + StrictSign = msgSigning | msgVerification + // StrictNoSign does not produce signatures and drops and penalises incoming messages that carry one + StrictNoSign = msgVerification + // LaxSign produces signatures and validates incoming signatures iff one is present + // Deprecated: it is recommend to either strictly enable, or strictly disable, signatures. + LaxSign = msgSigning + // LaxNoSign does not produce signatures and validates incoming signatures iff one is present + // Deprecated: it is recommend to either strictly enable, or strictly disable, signatures. + LaxNoSign = 0 +) + +// mustVerify is true when a message signature must be verified. +// If signatures are not expected, verification checks if the signature is absent. +func (policy MessageSignaturePolicy) mustVerify() bool { + return policy&msgVerification != 0 +} + +// mustSign is true when messages should be signed, and incoming messages are expected to have a signature. +func (policy MessageSignaturePolicy) mustSign() bool { + return policy&msgSigning != 0 +} + +const SignPrefix = "libp2p-pubsub:" + +/*func verifyMessageSignature(m *message.Message) error { + pubk, err := messagePubKey(m) + if err != nil { + return err + } + + xm := *m + xm.Signature = nil + xm.Key = nil + bytes, err := xm.Marshal() + if err != nil { + return err + } + + bytes = withSignPrefix(bytes) + + valid, err := pubk.Verify(bytes, m.Signature) + if err != nil { + return err + } + + if !valid { + return fmt.Errorf("invalid signature") + } + + return nil +}*/ + +/*func messagePubKey(m *message.Message) (ecdsa.PublicKey, error) { + var pubk ecdsa.PublicKey + + pid, err := peer.IDFromBytes(m.From) + if err != nil { + return nil, err + } + + if m.Key == nil { + // no attached key, it must be extractable from the source ID + pubk, err = pid.ExtractPublicKey() + if err != nil { + return nil, fmt.Errorf("cannot extract signing key: %s", err.Error()) + } + if pubk == nil { + return nil, fmt.Errorf("cannot extract signing key") + } + } else { + pubk, err = crypto.UnmarshalPublicKey(m.Key) + if err != nil { + return nil, fmt.Errorf("cannot unmarshal signing key: %s", err.Error()) + } + + // verify that the source ID matches the attached key + if !pid.MatchesPublicKey(pubk) { + return nil, fmt.Errorf("bad signing key; source ID %s doesn't match key", pid) + } + } + + return pubk, nil +}*/ + +/*func signMessage(pid enode.ID, key *ecdsa.PrivateKey, m *message.Message) error { + bytes, err := m.Marshal() + if err != nil { + return err + } + + bytes = withSignPrefix(bytes) + + sig, err := key.Sign(bytes) + if err != nil { + return err + } + + m.Signature = sig + + pk, _ := pid.ExtractPublicKey() + if pk == nil { + pubk, err := crypto.MarshalPublicKey(key.GetPublic()) + if err != nil { + return err + } + m.Key = pubk + } + + return nil +}*/ + +func withSignPrefix(bytes []byte) []byte { + return append([]byte(SignPrefix), bytes...) +} diff --git a/p2p/pubsub/subscription.go b/p2p/pubsub/subscription.go new file mode 100644 index 0000000000..0745f8dc59 --- /dev/null +++ b/p2p/pubsub/subscription.go @@ -0,0 +1,51 @@ +package pubsub + +import ( + "context" + "sync" +) + +// Subscription handles the details of a particular Topic subscription. +// There may be many subscriptions for a given Topic. +type Subscription struct { + topic string + ch chan *Message + cancelCh chan<- *Subscription + ctx context.Context + err error + once sync.Once +} + +// Topic returns the topic string associated with the Subscription +func (sub *Subscription) Topic() string { + return sub.topic +} + +// Next returns the next message in our subscription +func (sub *Subscription) Next(ctx context.Context) (*Message, error) { + select { + case msg, ok := <-sub.ch: + if !ok { + return msg, sub.err + } + + return msg, nil + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +// Cancel closes the subscription. If this is the last active subscription then pubsub will send an unsubscribe +// announcement to the network. +func (sub *Subscription) Cancel() { + select { + case sub.cancelCh <- sub: + case <-sub.ctx.Done(): + } +} + +func (sub *Subscription) close() { + sub.once.Do(func() { + close(sub.ch) + }) +} diff --git a/p2p/pubsub/subscription_filter.go b/p2p/pubsub/subscription_filter.go new file mode 100644 index 0000000000..e26b753ac8 --- /dev/null +++ b/p2p/pubsub/subscription_filter.go @@ -0,0 +1,31 @@ +package pubsub + +import ( + "errors" + + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +// ErrTooManySubscriptions may be returned by a SubscriptionFilter to signal that there are too many +// subscriptions to process. +var ErrTooManySubscriptions = errors.New("too many subscriptions") + +// SubscriptionFilter is a function that tells us whether we are interested in allowing and tracking +// subscriptions for a given topic. +// +// The filter is consulted whenever a subscription notification is received by another peer; if the +// filter returns false, then the notification is ignored. +// +// The filter is also consulted when joining topics; if the filter returns false, then the Join +// operation will result in an error. +type SubscriptionFilter interface { + // CanSubscribe returns true if the topic is of interest and we can subscribe to it + CanSubscribe(topic string) bool + + // FilterIncomingSubscriptions is invoked for all RPCs containing subscription notifications. + // It should filter only the subscriptions of interest and my return an error if (for instance) + // there are too many subscriptions. + FilterIncomingSubscriptions(enode.ID, []*message.RPC_SubOpts) ([]*message.RPC_SubOpts, error) +} diff --git a/p2p/pubsub/tag_tracer.go b/p2p/pubsub/tag_tracer.go new file mode 100644 index 0000000000..075ee37aff --- /dev/null +++ b/p2p/pubsub/tag_tracer.go @@ -0,0 +1,263 @@ +package pubsub + +import ( + "fmt" + "sync" + "time" + + "github.com/libp2p/go-libp2p-core/peer" + + "github.com/AlayaNetwork/Alaya-Go/log" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + "github.com/libp2p/go-libp2p-core/connmgr" +) + +var ( + // GossipSubConnTagBumpMessageDelivery is the amount to add to the connection manager + // tag that tracks message deliveries. Each time a peer is the first to deliver a + // message within a topic, we "bump" a tag by this amount, up to a maximum + // of GossipSubConnTagMessageDeliveryCap. + // Note that the delivery tags decay over time, decreasing by GossipSubConnTagDecayAmount + // at every GossipSubConnTagDecayInterval. + GossipSubConnTagBumpMessageDelivery = 1 + + // GossipSubConnTagDecayInterval is the decay interval for decaying connection manager tags. + GossipSubConnTagDecayInterval = 10 * time.Minute + + // GossipSubConnTagDecayAmount is subtracted from decaying tag values at each decay interval. + GossipSubConnTagDecayAmount = 1 + + // GossipSubConnTagMessageDeliveryCap is the maximum value for the connection manager tags that + // track message deliveries. + GossipSubConnTagMessageDeliveryCap = 15 +) + +// tagTracer is an internal tracer that applies connection manager tags to peer +// connections based on their behavior. +// +// We tag a peer's connections for the following reasons: +// - Directly connected peers are tagged with GossipSubConnTagValueDirectPeer (default 1000). +// - Mesh peers are tagged with a value of GossipSubConnTagValueMeshPeer (default 20). +// If a peer is in multiple topic meshes, they'll be tagged for each. +// - For each message that we receive, we bump a delivery tag for peer that delivered the message +// first. +// The delivery tags have a maximum value, GossipSubConnTagMessageDeliveryCap, and they decay at +// a rate of GossipSubConnTagDecayAmount / GossipSubConnTagDecayInterval. +type tagTracer struct { + sync.RWMutex + + cmgr connmgr.ConnManager + msgID MsgIdFunction + decayer connmgr.Decayer + decaying map[string]connmgr.DecayingTag + direct map[enode.ID]struct{} + + // a map of message ids to the set of peers who delivered the message after the first delivery, + // but before the message was finished validating + nearFirst map[string]map[enode.ID]struct{} +} + +func newTagTracer(cmgr connmgr.ConnManager) *tagTracer { + decayer, ok := connmgr.SupportsDecay(cmgr) + if !ok { + log.Debug("connection manager does not support decaying tags, delivery tags will not be applied") + } + return &tagTracer{ + cmgr: cmgr, + msgID: DefaultMsgIdFn, + decayer: decayer, + decaying: make(map[string]connmgr.DecayingTag), + nearFirst: make(map[string]map[enode.ID]struct{}), + } +} + +func (t *tagTracer) Start(gs *GossipSubRouter) { + if t == nil { + return + } + + t.msgID = gs.p.msgID + t.direct = gs.direct +} + +func (t *tagTracer) tagPeerIfDirect(p enode.ID) { + if t.direct == nil { + return + } + + // tag peer if it is a direct peer + _, direct := t.direct[p] + if direct { + t.cmgr.Protect(peer.ID(p.String()), "pubsub:") + } +} + +func (t *tagTracer) tagMeshPeer(p enode.ID, topic string) { + tag := topicTag(topic) + t.cmgr.Protect(peer.ID(p.String()), tag) +} + +func (t *tagTracer) untagMeshPeer(p enode.ID, topic string) { + tag := topicTag(topic) + t.cmgr.Unprotect(peer.ID(p.String()), tag) +} + +func topicTag(topic string) string { + return fmt.Sprintf("pubsub:%s", topic) +} + +func (t *tagTracer) addDeliveryTag(topic string) { + if t.decayer == nil { + return + } + + name := fmt.Sprintf("pubsub-deliveries:%s", topic) + t.Lock() + defer t.Unlock() + tag, err := t.decayer.RegisterDecayingTag( + name, + GossipSubConnTagDecayInterval, + connmgr.DecayFixed(GossipSubConnTagDecayAmount), + connmgr.BumpSumBounded(0, GossipSubConnTagMessageDeliveryCap)) + + if err != nil { + log.Warn("unable to create decaying delivery tag", "err", err) + return + } + t.decaying[topic] = tag +} + +func (t *tagTracer) removeDeliveryTag(topic string) { + t.Lock() + defer t.Unlock() + tag, ok := t.decaying[topic] + if !ok { + return + } + err := tag.Close() + if err != nil { + log.Warn("error closing decaying connmgr tag", "err", err) + } + delete(t.decaying, topic) +} + +func (t *tagTracer) bumpDeliveryTag(p enode.ID, topic string) error { + t.RLock() + defer t.RUnlock() + + tag, ok := t.decaying[topic] + if !ok { + return fmt.Errorf("no decaying tag registered for topic %s", topic) + } + return tag.Bump(peer.ID(p.String()), GossipSubConnTagBumpMessageDelivery) +} + +func (t *tagTracer) bumpTagsForMessage(p enode.ID, msg *Message) { + topic := msg.GetTopic() + err := t.bumpDeliveryTag(p, topic) + if err != nil { + log.Warn("error bumping delivery tag", "err", err) + } +} + +// nearFirstPeers returns the peers who delivered the message while it was still validating +func (t *tagTracer) nearFirstPeers(msg *Message) []enode.ID { + t.Lock() + defer t.Unlock() + peersMap, ok := t.nearFirst[t.msgID(msg.Message)] + if !ok { + return nil + } + peers := make([]enode.ID, 0, len(peersMap)) + for p := range peersMap { + peers = append(peers, p) + } + return peers +} + +// -- RawTracer interface methods +var _ RawTracer = (*tagTracer)(nil) + +func (t *tagTracer) AddPeer(p *enode.Node, proto ProtocolID) { + t.tagPeerIfDirect(p.ID()) +} + +func (t *tagTracer) Join(topic string) { + t.addDeliveryTag(topic) +} + +func (t *tagTracer) DeliverMessage(msg *Message) { + nearFirst := t.nearFirstPeers(msg) + + t.bumpTagsForMessage(msg.ReceivedFrom.ID(), msg) + for _, p := range nearFirst { + t.bumpTagsForMessage(p, msg) + } + + // delete the delivery state for this message + t.Lock() + delete(t.nearFirst, t.msgID(msg.Message)) + t.Unlock() +} + +func (t *tagTracer) Leave(topic string) { + t.removeDeliveryTag(topic) +} + +func (t *tagTracer) Graft(p enode.ID, topic string) { + t.tagMeshPeer(p, topic) +} + +func (t *tagTracer) Prune(p enode.ID, topic string) { + t.untagMeshPeer(p, topic) +} + +func (t *tagTracer) ValidateMessage(msg *Message) { + t.Lock() + defer t.Unlock() + + // create map to start tracking the peers who deliver while we're validating + id := t.msgID(msg.Message) + if _, exists := t.nearFirst[id]; exists { + return + } + t.nearFirst[id] = make(map[enode.ID]struct{}) +} + +func (t *tagTracer) DuplicateMessage(msg *Message) { + t.Lock() + defer t.Unlock() + + id := t.msgID(msg.Message) + peers, ok := t.nearFirst[id] + if !ok { + return + } + peers[msg.ReceivedFrom.ID()] = struct{}{} +} + +func (t *tagTracer) RejectMessage(msg *Message, reason string) { + t.Lock() + defer t.Unlock() + + // We want to delete the near-first delivery tracking for messages that have passed through + // the validation pipeline. Other rejection reasons (missing signature, etc) skip the validation + // queue, so we don't want to remove the state in case the message is still validating. + switch reason { + case RejectValidationThrottled: + fallthrough + case RejectValidationIgnored: + fallthrough + case RejectValidationFailed: + delete(t.nearFirst, t.msgID(msg.Message)) + } +} + +func (t *tagTracer) RemovePeer(id enode.ID) {} +func (t *tagTracer) ThrottlePeer(p enode.ID) {} +func (t *tagTracer) RecvRPC(rpc *RPC) {} +func (t *tagTracer) SendRPC(rpc *RPC, p enode.ID) {} +func (t *tagTracer) DropRPC(rpc *RPC, p enode.ID) {} +func (t *tagTracer) UndeliverableMessage(msg *Message) {} diff --git a/p2p/pubsub/topic.go b/p2p/pubsub/topic.go new file mode 100644 index 0000000000..8b416339ba --- /dev/null +++ b/p2p/pubsub/topic.go @@ -0,0 +1,390 @@ +package pubsub + +import ( + "context" + "errors" + "fmt" + "sync" + + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +// ErrTopicClosed is returned if a Topic is utilized after it has been closed +var ErrTopicClosed = errors.New("this Topic is closed, try opening a new one") + +// Topic is the handle for a pubsub topic +type Topic struct { + p *PubSub + topic string + + evtHandlerMux sync.RWMutex + evtHandlers map[*TopicEventHandler]struct{} + + mux sync.RWMutex + closed bool +} + +// String returns the topic associated with t +func (t *Topic) String() string { + return t.topic +} + +// SetScoreParams sets the topic score parameters if the pubsub router supports peer +// scoring +func (t *Topic) SetScoreParams(p *TopicScoreParams) error { + err := p.validate() + if err != nil { + return fmt.Errorf("invalid topic score parameters: %w", err) + } + + t.mux.Lock() + defer t.mux.Unlock() + + if t.closed { + return ErrTopicClosed + } + + result := make(chan error, 1) + update := func() { + gs, ok := t.p.rt.(*GossipSubRouter) + if !ok { + result <- fmt.Errorf("pubsub router is not gossipsub") + return + } + + if gs.score == nil { + result <- fmt.Errorf("peer scoring is not enabled in router") + return + } + + err := gs.score.SetTopicScoreParams(t.topic, p) + result <- err + } + + select { + case t.p.eval <- update: + err = <-result + return err + + case <-t.p.ctx.Done(): + return t.p.ctx.Err() + } +} + +// EventHandler creates a handle for topic specific events +// Multiple event handlers may be created and will operate independently of each other +func (t *Topic) EventHandler(opts ...TopicEventHandlerOpt) (*TopicEventHandler, error) { + t.mux.RLock() + defer t.mux.RUnlock() + if t.closed { + return nil, ErrTopicClosed + } + + h := &TopicEventHandler{ + topic: t, + err: nil, + + evtLog: make(map[enode.ID]EventType), + evtLogCh: make(chan struct{}, 1), + } + + for _, opt := range opts { + err := opt(h) + if err != nil { + return nil, err + } + } + + done := make(chan struct{}, 1) + + select { + case t.p.eval <- func() { + tmap := t.p.topics[t.topic] + for p := range tmap { + h.evtLog[p] = PeerJoin + } + + t.evtHandlerMux.Lock() + t.evtHandlers[h] = struct{}{} + t.evtHandlerMux.Unlock() + done <- struct{}{} + }: + case <-t.p.ctx.Done(): + return nil, t.p.ctx.Err() + } + + <-done + + return h, nil +} + +func (t *Topic) sendNotification(evt PeerEvent) { + t.evtHandlerMux.RLock() + defer t.evtHandlerMux.RUnlock() + + for h := range t.evtHandlers { + h.sendNotification(evt) + } +} + +// Subscribe returns a new Subscription for the topic. +// Note that subscription is not an instantaneous operation. It may take some time +// before the subscription is processed by the pubsub main loop and propagated to our peers. +func (t *Topic) Subscribe(opts ...SubOpt) (*Subscription, error) { + t.mux.RLock() + defer t.mux.RUnlock() + if t.closed { + return nil, ErrTopicClosed + } + + sub := &Subscription{ + topic: t.topic, + ctx: t.p.ctx, + } + + for _, opt := range opts { + err := opt(sub) + if err != nil { + return nil, err + } + } + + if sub.ch == nil { + // apply the default size + sub.ch = make(chan *Message, 32) + } + + out := make(chan *Subscription, 1) + + //t.p.disc.Discover(sub.topic) + + select { + case t.p.addSub <- &addSubReq{ + sub: sub, + resp: out, + }: + case <-t.p.ctx.Done(): + return nil, t.p.ctx.Err() + } + + return <-out, nil +} + +// Relay enables message relaying for the topic and returns a reference +// cancel function. Subsequent calls increase the reference counter. +// To completely disable the relay, all references must be cancelled. +func (t *Topic) Relay() (RelayCancelFunc, error) { + t.mux.RLock() + defer t.mux.RUnlock() + if t.closed { + return nil, ErrTopicClosed + } + + out := make(chan RelayCancelFunc, 1) + + //t.p.disc.Discover(t.topic) + + select { + case t.p.addRelay <- &addRelayReq{ + topic: t.topic, + resp: out, + }: + case <-t.p.ctx.Done(): + return nil, t.p.ctx.Err() + } + + return <-out, nil +} + +// RouterReady is a function that decides if a router is ready to publish +type RouterReady func(rt PubSubRouter, topic string) (bool, error) + +type PublishOptions struct { + ready RouterReady +} + +type PubOpt func(pub *PublishOptions) error + +// Publish publishes data to topic. +func (t *Topic) Publish(ctx context.Context, data []byte, opts ...PubOpt) error { + t.mux.RLock() + defer t.mux.RUnlock() + if t.closed { + return ErrTopicClosed + } + + m := &message.Message{ + Data: data, + Topic: &t.topic, + From: enode.ZeroID, + Seqno: nil, + } + if t.p.signID != enode.ZeroID { + m.From = t.p.signID + m.Seqno = t.p.nextSeqno() + } + if t.p.signKey != nil { + /*m.From = t.p.signID.Bytes() + err := signMessage(t.p.signID, t.p.signKey, m) + if err != nil { + return err + }*/ + } + + pub := &PublishOptions{} + for _, opt := range opts { + err := opt(pub) + if err != nil { + return err + } + } + + if pub.ready != nil { + //t.p.disc.Bootstrap(ctx, t.topic, pub.ready) + } + + return t.p.val.PushLocal(&Message{m, t.p.host.ID(), nil}) +} + +// WithReadiness returns a publishing option for only publishing when the router is ready. +// This option is not useful unless PubSub is also using WithDiscovery +func WithReadiness(ready RouterReady) PubOpt { + return func(pub *PublishOptions) error { + pub.ready = ready + return nil + } +} + +// Close closes down the topic. Will return an error unless there are no active event handlers or subscriptions. +// Does not error if the topic is already closed. +func (t *Topic) Close() error { + t.mux.Lock() + defer t.mux.Unlock() + if t.closed { + return nil + } + + req := &rmTopicReq{t, make(chan error, 1)} + + select { + case t.p.rmTopic <- req: + case <-t.p.ctx.Done(): + return t.p.ctx.Err() + } + + err := <-req.resp + + if err == nil { + t.closed = true + } + + return err +} + +// ListPeers returns a list of peers we are connected to in the given topic. +func (t *Topic) ListPeers() []enode.ID { + t.mux.RLock() + defer t.mux.RUnlock() + if t.closed { + return []enode.ID{} + } + + return t.p.ListPeers(t.topic) +} + +type EventType int + +const ( + PeerJoin EventType = iota + PeerLeave +) + +// TopicEventHandler is used to manage topic specific events. No Subscription is required to receive events. +type TopicEventHandler struct { + topic *Topic + err error + + evtLogMx sync.Mutex + evtLog map[enode.ID]EventType + evtLogCh chan struct{} +} + +type TopicEventHandlerOpt func(t *TopicEventHandler) error + +type PeerEvent struct { + Type EventType + Peer enode.ID +} + +// Cancel closes the topic event handler +func (t *TopicEventHandler) Cancel() { + topic := t.topic + t.err = fmt.Errorf("topic event handler cancelled by calling handler.Cancel()") + + topic.evtHandlerMux.Lock() + delete(topic.evtHandlers, t) + t.topic.evtHandlerMux.Unlock() +} + +func (t *TopicEventHandler) sendNotification(evt PeerEvent) { + t.evtLogMx.Lock() + t.addToEventLog(evt) + t.evtLogMx.Unlock() +} + +// addToEventLog assumes a lock has been taken to protect the event log +func (t *TopicEventHandler) addToEventLog(evt PeerEvent) { + e, ok := t.evtLog[evt.Peer] + if !ok { + t.evtLog[evt.Peer] = evt.Type + // send signal that an event has been added to the event log + select { + case t.evtLogCh <- struct{}{}: + default: + } + } else if e != evt.Type { + delete(t.evtLog, evt.Peer) + } +} + +// pullFromEventLog assumes a lock has been taken to protect the event log +func (t *TopicEventHandler) pullFromEventLog() (PeerEvent, bool) { + for k, v := range t.evtLog { + evt := PeerEvent{Peer: k, Type: v} + delete(t.evtLog, k) + return evt, true + } + return PeerEvent{}, false +} + +// NextPeerEvent returns the next event regarding subscribed peers +// Guarantees: Peer Join and Peer Leave events for a given peer will fire in order. +// Unless a peer both Joins and Leaves before NextPeerEvent emits either event +// all events will eventually be received from NextPeerEvent. +func (t *TopicEventHandler) NextPeerEvent(ctx context.Context) (PeerEvent, error) { + for { + t.evtLogMx.Lock() + evt, ok := t.pullFromEventLog() + if ok { + // make sure an event log signal is available if there are events in the event log + if len(t.evtLog) > 0 { + select { + case t.evtLogCh <- struct{}{}: + default: + } + } + t.evtLogMx.Unlock() + return evt, nil + } + t.evtLogMx.Unlock() + + select { + case <-t.evtLogCh: + continue + case <-ctx.Done(): + return PeerEvent{}, ctx.Err() + } + } +} diff --git a/p2p/pubsub/trace.go b/p2p/pubsub/trace.go new file mode 100644 index 0000000000..80f652625e --- /dev/null +++ b/p2p/pubsub/trace.go @@ -0,0 +1,529 @@ +package pubsub + +import ( + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" +) + +// EventTracer is a generic event tracer interface. +// This is a high level tracing interface which delivers tracing events, as defined by the protobuf +// schema in pb/trace.proto. +type EventTracer interface { + Trace(evt *message.TraceEvent) +} + +// RawTracer is a low level tracing interface that allows an application to trace the internal +// operation of the pubsub subsystem. +// +// Note that the tracers are invoked synchronously, which means that application tracers must +// take care to not block or modify arguments. +// +// Warning: this interface is not fixed, we may be adding new methods as necessitated by the system +// in the future. +type RawTracer interface { + // AddPeer is invoked when a new peer is added. + AddPeer(p *enode.Node, proto ProtocolID) + // RemovePeer is invoked when a peer is removed. + RemovePeer(p enode.ID) + // Join is invoked when a new topic is joined + Join(topic string) + // Leave is invoked when a topic is abandoned + Leave(topic string) + // Graft is invoked when a new peer is grafted on the mesh (gossipsub) + Graft(p enode.ID, topic string) + // Prune is invoked when a peer is pruned from the message (gossipsub) + Prune(p enode.ID, topic string) + // ValidateMessage is invoked when a message first enters the validation pipeline. + ValidateMessage(msg *Message) + // DeliverMessage is invoked when a message is delivered + DeliverMessage(msg *Message) + // RejectMessage is invoked when a message is Rejected or Ignored. + // The reason argument can be one of the named strings Reject*. + RejectMessage(msg *Message, reason string) + // DuplicateMessage is invoked when a duplicate message is dropped. + DuplicateMessage(msg *Message) + // ThrottlePeer is invoked when a peer is throttled by the peer gater. + ThrottlePeer(p enode.ID) + // RecvRPC is invoked when an incoming RPC is received. + RecvRPC(rpc *RPC) + // SendRPC is invoked when a RPC is sent. + SendRPC(rpc *RPC, p enode.ID) + // DropRPC is invoked when an outbound RPC is dropped, typically because of a queue full. + DropRPC(rpc *RPC, p enode.ID) + // UndeliverableMessage is invoked when the consumer of Subscribe is not reading messages fast enough and + // the pressure release mechanism trigger, dropping messages. + UndeliverableMessage(msg *Message) +} + +// pubsub tracer details +type pubsubTracer struct { + tracer EventTracer + raw []RawTracer + pid enode.ID + msgID MsgIdFunction +} + +func (t *pubsubTracer) PublishMessage(msg *Message) { + if t == nil { + return + } + + if t.tracer == nil { + return + } + + now := time.Now().UnixNano() + evt := &message.TraceEvent{ + Type: message.TraceEvent_PUBLISH_MESSAGE.Enum(), + PeerID: t.pid, + Timestamp: &now, + PublishMessage: &message.TraceEvent_PublishMessage{ + MessageID: []byte(t.msgID(msg.Message)), + Topic: msg.Message.Topic, + }, + } + + t.tracer.Trace(evt) +} + +func (t *pubsubTracer) ValidateMessage(msg *Message) { + if t == nil { + return + } + + if msg.ReceivedFrom.ID() != t.pid { + for _, tr := range t.raw { + tr.ValidateMessage(msg) + } + } +} + +func (t *pubsubTracer) RejectMessage(msg *Message, reason string) { + if t == nil { + return + } + + if msg.ReceivedFrom.ID() != t.pid { + for _, tr := range t.raw { + tr.RejectMessage(msg, reason) + } + } + + if t.tracer == nil { + return + } + + now := time.Now().UnixNano() + evt := &message.TraceEvent{ + Type: message.TraceEvent_REJECT_MESSAGE.Enum(), + PeerID: t.pid, + Timestamp: &now, + RejectMessage: &message.TraceEvent_RejectMessage{ + MessageID: []byte(t.msgID(msg.Message)), + ReceivedFrom: msg.ReceivedFrom.ID(), + Reason: &reason, + Topic: msg.Topic, + }, + } + + t.tracer.Trace(evt) +} + +func (t *pubsubTracer) DuplicateMessage(msg *Message) { + if t == nil { + return + } + + if msg.ReceivedFrom.ID() != t.pid { + for _, tr := range t.raw { + tr.DuplicateMessage(msg) + } + } + + if t.tracer == nil { + return + } + + now := time.Now().UnixNano() + evt := &message.TraceEvent{ + Type: message.TraceEvent_DUPLICATE_MESSAGE.Enum(), + PeerID: t.pid, + Timestamp: &now, + DuplicateMessage: &message.TraceEvent_DuplicateMessage{ + MessageID: []byte(t.msgID(msg.Message)), + ReceivedFrom: msg.ReceivedFrom.ID(), + Topic: msg.Topic, + }, + } + + t.tracer.Trace(evt) +} + +func (t *pubsubTracer) DeliverMessage(msg *Message) { + if t == nil { + return + } + + if msg.ReceivedFrom.ID() != t.pid { + for _, tr := range t.raw { + tr.DeliverMessage(msg) + } + } + + if t.tracer == nil { + return + } + + now := time.Now().UnixNano() + evt := &message.TraceEvent{ + Type: message.TraceEvent_DELIVER_MESSAGE.Enum(), + PeerID: t.pid, + Timestamp: &now, + DeliverMessage: &message.TraceEvent_DeliverMessage{ + MessageID: []byte(t.msgID(msg.Message)), + Topic: msg.Topic, + ReceivedFrom: msg.ReceivedFrom.ID(), + }, + } + + t.tracer.Trace(evt) +} + +func (t *pubsubTracer) AddPeer(p *enode.Node, proto ProtocolID) { + if t == nil { + return + } + + for _, tr := range t.raw { + tr.AddPeer(p, proto) + } + + if t.tracer == nil { + return + } + + protoStr := string(proto) + now := time.Now().UnixNano() + evt := &message.TraceEvent{ + Type: message.TraceEvent_ADD_PEER.Enum(), + PeerID: t.pid, + Timestamp: &now, + AddPeer: &message.TraceEvent_AddPeer{ + PeerID: p.ID(), + Proto: &protoStr, + }, + } + + t.tracer.Trace(evt) +} + +func (t *pubsubTracer) RemovePeer(p enode.ID) { + if t == nil { + return + } + + for _, tr := range t.raw { + tr.RemovePeer(p) + } + + if t.tracer == nil { + return + } + + now := time.Now().UnixNano() + evt := &message.TraceEvent{ + Type: message.TraceEvent_REMOVE_PEER.Enum(), + PeerID: t.pid, + Timestamp: &now, + RemovePeer: &message.TraceEvent_RemovePeer{ + PeerID: p, + }, + } + + t.tracer.Trace(evt) +} + +func (t *pubsubTracer) RecvRPC(rpc *RPC) { + if t == nil { + return + } + + for _, tr := range t.raw { + tr.RecvRPC(rpc) + } + + if t.tracer == nil { + return + } + + now := time.Now().UnixNano() + evt := &message.TraceEvent{ + Type: message.TraceEvent_RECV_RPC.Enum(), + PeerID: t.pid, + Timestamp: &now, + RecvRPC: &message.TraceEvent_RecvRPC{ + ReceivedFrom: rpc.from.ID(), + Meta: t.traceRPCMeta(rpc), + }, + } + + t.tracer.Trace(evt) +} + +func (t *pubsubTracer) SendRPC(rpc *RPC, p enode.ID) { + if t == nil { + return + } + + for _, tr := range t.raw { + tr.SendRPC(rpc, p) + } + + if t.tracer == nil { + return + } + + now := time.Now().UnixNano() + evt := &message.TraceEvent{ + Type: message.TraceEvent_SEND_RPC.Enum(), + PeerID: t.pid, + Timestamp: &now, + SendRPC: &message.TraceEvent_SendRPC{ + SendTo: p, + Meta: t.traceRPCMeta(rpc), + }, + } + + t.tracer.Trace(evt) +} + +func (t *pubsubTracer) DropRPC(rpc *RPC, p enode.ID) { + if t == nil { + return + } + + for _, tr := range t.raw { + tr.DropRPC(rpc, p) + } + + if t.tracer == nil { + return + } + + now := time.Now().UnixNano() + evt := &message.TraceEvent{ + Type: message.TraceEvent_DROP_RPC.Enum(), + PeerID: t.pid, + Timestamp: &now, + DropRPC: &message.TraceEvent_DropRPC{ + SendTo: p, + Meta: t.traceRPCMeta(rpc), + }, + } + + t.tracer.Trace(evt) +} + +func (t *pubsubTracer) UndeliverableMessage(msg *Message) { + if t == nil { + return + } + + for _, tr := range t.raw { + tr.UndeliverableMessage(msg) + } +} + +func (t *pubsubTracer) traceRPCMeta(rpc *RPC) *message.TraceEvent_RPCMeta { + rpcMeta := new(message.TraceEvent_RPCMeta) + + var msgs []*message.TraceEvent_MessageMeta + for _, m := range rpc.Publish { + msgs = append(msgs, &message.TraceEvent_MessageMeta{ + MessageID: []byte(t.msgID(m)), + Topic: m.Topic, + }) + } + rpcMeta.Messages = msgs + + var subs []*message.TraceEvent_SubMeta + for _, sub := range rpc.Subscriptions { + subs = append(subs, &message.TraceEvent_SubMeta{ + Subscribe: sub.Subscribe, + Topic: sub.Topicid, + }) + } + rpcMeta.Subscription = subs + + if rpc.Control != nil { + var ihave []*message.TraceEvent_ControlIHaveMeta + for _, ctl := range rpc.Control.Ihave { + var mids [][]byte + for _, mid := range ctl.MessageIDs { + mids = append(mids, []byte(mid)) + } + ihave = append(ihave, &message.TraceEvent_ControlIHaveMeta{ + Topic: ctl.TopicID, + MessageIDs: mids, + }) + } + + var iwant []*message.TraceEvent_ControlIWantMeta + for _, ctl := range rpc.Control.Iwant { + var mids [][]byte + for _, mid := range ctl.MessageIDs { + mids = append(mids, []byte(mid)) + } + iwant = append(iwant, &message.TraceEvent_ControlIWantMeta{ + MessageIDs: mids, + }) + } + + var graft []*message.TraceEvent_ControlGraftMeta + for _, ctl := range rpc.Control.Graft { + graft = append(graft, &message.TraceEvent_ControlGraftMeta{ + Topic: ctl.TopicID, + }) + } + + var prune []*message.TraceEvent_ControlPruneMeta + for _, ctl := range rpc.Control.Prune { + peers := make([]enode.ID, 0, len(ctl.Peers)) + for _, pi := range ctl.Peers { + peers = append(peers, pi.PeerID) + } + prune = append(prune, &message.TraceEvent_ControlPruneMeta{ + Topic: ctl.TopicID, + Peers: peers, + }) + } + + rpcMeta.Control = &message.TraceEvent_ControlMeta{ + Ihave: ihave, + Iwant: iwant, + Graft: graft, + Prune: prune, + } + } + + return rpcMeta +} + +func (t *pubsubTracer) Join(topic string) { + if t == nil { + return + } + + for _, tr := range t.raw { + tr.Join(topic) + } + + if t.tracer == nil { + return + } + + now := time.Now().UnixNano() + evt := &message.TraceEvent{ + Type: message.TraceEvent_JOIN.Enum(), + PeerID: t.pid, + Timestamp: &now, + Join: &message.TraceEvent_Join{ + Topic: &topic, + }, + } + + t.tracer.Trace(evt) +} + +func (t *pubsubTracer) Leave(topic string) { + if t == nil { + return + } + + for _, tr := range t.raw { + tr.Leave(topic) + } + + if t.tracer == nil { + return + } + + now := time.Now().UnixNano() + evt := &message.TraceEvent{ + Type: message.TraceEvent_LEAVE.Enum(), + PeerID: t.pid, + Timestamp: &now, + Leave: &message.TraceEvent_Leave{ + Topic: &topic, + }, + } + + t.tracer.Trace(evt) +} + +func (t *pubsubTracer) Graft(p enode.ID, topic string) { + if t == nil { + return + } + + for _, tr := range t.raw { + tr.Graft(p, topic) + } + + if t.tracer == nil { + return + } + + now := time.Now().UnixNano() + evt := &message.TraceEvent{ + Type: message.TraceEvent_GRAFT.Enum(), + PeerID: t.pid, + Timestamp: &now, + Graft: &message.TraceEvent_Graft{ + PeerID: p, + Topic: &topic, + }, + } + + t.tracer.Trace(evt) +} + +func (t *pubsubTracer) Prune(p enode.ID, topic string) { + if t == nil { + return + } + + for _, tr := range t.raw { + tr.Prune(p, topic) + } + + if t.tracer == nil { + return + } + + now := time.Now().UnixNano() + evt := &message.TraceEvent{ + Type: message.TraceEvent_PRUNE.Enum(), + PeerID: t.pid, + Timestamp: &now, + Prune: &message.TraceEvent_Prune{ + PeerID: p, + Topic: &topic, + }, + } + + t.tracer.Trace(evt) +} + +func (t *pubsubTracer) ThrottlePeer(p enode.ID) { + if t == nil { + return + } + + for _, tr := range t.raw { + tr.ThrottlePeer(p) + } +} diff --git a/p2p/pubsub/tracer.go b/p2p/pubsub/tracer.go new file mode 100644 index 0000000000..1104c080da --- /dev/null +++ b/p2p/pubsub/tracer.go @@ -0,0 +1,296 @@ +package pubsub + +import ( + "encoding/json" + "io" + "os" + "sync" + + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" + + "github.com/AlayaNetwork/Alaya-Go/log" + + "github.com/libp2p/go-msgio/protoio" +) + +var TraceBufferSize = 1 << 16 // 64K ought to be enough for everyone; famous last words. +var MinTraceBatchSize = 16 + +// rejection reasons +const ( + RejectBlacklstedPeer = "blacklisted peer" + RejectBlacklistedSource = "blacklisted source" + RejectMissingSignature = "missing signature" + RejectUnexpectedSignature = "unexpected signature" + RejectUnexpectedAuthInfo = "unexpected auth info" + RejectInvalidSignature = "invalid signature" + RejectValidationQueueFull = "validation queue full" + RejectValidationThrottled = "validation throttled" + RejectValidationFailed = "validation failed" + RejectValidationIgnored = "validation ignored" + RejectSelfOrigin = "self originated message" +) + +type basicTracer struct { + ch chan struct{} + mx sync.Mutex + buf []*message.TraceEvent + lossy bool + closed bool +} + +func (t *basicTracer) Trace(evt *message.TraceEvent) { + t.mx.Lock() + defer t.mx.Unlock() + + if t.closed { + return + } + + if t.lossy && len(t.buf) > TraceBufferSize { + log.Debug("trace buffer overflow; dropping trace event") + } else { + t.buf = append(t.buf, evt) + } + + select { + case t.ch <- struct{}{}: + default: + } +} + +func (t *basicTracer) Close() { + t.mx.Lock() + defer t.mx.Unlock() + if !t.closed { + t.closed = true + close(t.ch) + } +} + +// JSONTracer is a tracer that writes events to a file, encoded in ndjson. +type JSONTracer struct { + basicTracer + w io.WriteCloser +} + +// NewJsonTracer creates a new JSONTracer writing traces to file. +func NewJSONTracer(file string) (*JSONTracer, error) { + return OpenJSONTracer(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) +} + +// OpenJSONTracer creates a new JSONTracer, with explicit control of OpenFile flags and permissions. +func OpenJSONTracer(file string, flags int, perm os.FileMode) (*JSONTracer, error) { + f, err := os.OpenFile(file, flags, perm) + if err != nil { + return nil, err + } + + tr := &JSONTracer{w: f, basicTracer: basicTracer{ch: make(chan struct{}, 1)}} + go tr.doWrite() + + return tr, nil +} + +func (t *JSONTracer) doWrite() { + var buf []*message.TraceEvent + enc := json.NewEncoder(t.w) + for { + _, ok := <-t.ch + + t.mx.Lock() + tmp := t.buf + t.buf = buf[:0] + buf = tmp + t.mx.Unlock() + + for i, evt := range buf { + err := enc.Encode(evt) + if err != nil { + log.Warn("error writing event trace", "err", err) + } + buf[i] = nil + } + + if !ok { + t.w.Close() + return + } + } +} + +var _ EventTracer = (*JSONTracer)(nil) + +// PBTracer is a tracer that writes events to a file, as delimited protobufs. +type PBTracer struct { + basicTracer + w io.WriteCloser +} + +func NewPBTracer(file string) (*PBTracer, error) { + return OpenPBTracer(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) +} + +// OpenPBTracer creates a new PBTracer, with explicit control of OpenFile flags and permissions. +func OpenPBTracer(file string, flags int, perm os.FileMode) (*PBTracer, error) { + f, err := os.OpenFile(file, flags, perm) + if err != nil { + return nil, err + } + + tr := &PBTracer{w: f, basicTracer: basicTracer{ch: make(chan struct{}, 1)}} + go tr.doWrite() + + return tr, nil +} + +func (t *PBTracer) doWrite() { + var buf []*message.TraceEvent + w := protoio.NewDelimitedWriter(t.w) + for { + _, ok := <-t.ch + + t.mx.Lock() + tmp := t.buf + t.buf = buf[:0] + buf = tmp + t.mx.Unlock() + + for i, evt := range buf { + err := w.WriteMsg(evt) + if err != nil { + log.Warn("error writing event trace", "err", err.Error()) + } + buf[i] = nil + } + + if !ok { + t.w.Close() + return + } + } +} + +var _ EventTracer = (*PBTracer)(nil) + +/*const RemoteTracerProtoID = ProtocolID("/libp2p/pubsub/tracer/1.0.0") + +// RemoteTracer is a tracer that sends trace events to a remote peer +type RemoteTracer struct { + basicTracer + ctx context.Context + host Host + peer enode.ID +} + +// NewRemoteTracer constructs a RemoteTracer, tracing to the peer identified by pi +func NewRemoteTracer(ctx context.Context, host Host, pi enode.Node) (*RemoteTracer, error) { + tr := &RemoteTracer{ctx: ctx, host: host, peer: pi.ID(), basicTracer: basicTracer{ch: make(chan struct{}, 1), lossy: true}} + host.Peerstore().AddAddrs(pi.ID(), PermanentAddrTTL) + go tr.doWrite() + return tr, nil +} + +func (t *RemoteTracer) doWrite() { + var buf []*message.TraceEvent + + s, err := t.openStream() + if err != nil { + log.Debugf("error opening remote tracer stream: %s", err.Error()) + return + } + + var batch message.TraceEventBatch + + gzipW := gzip.NewWriter(s) + w := protoio.NewDelimitedWriter(gzipW) + + for { + _, ok := <-t.ch + + // deadline for batch accumulation + deadline := time.Now().Add(time.Second) + + t.mx.Lock() + for len(t.buf) < MinTraceBatchSize && time.Now().Before(deadline) { + t.mx.Unlock() + time.Sleep(100 * time.Millisecond) + t.mx.Lock() + } + + tmp := t.buf + t.buf = buf[:0] + buf = tmp + t.mx.Unlock() + + if len(buf) == 0 { + goto end + } + + batch.Batch = buf + + err = w.WriteMsg(&batch) + if err != nil { + log.Debugf("error writing trace event batch: %s", err) + goto end + } + + err = gzipW.Flush() + if err != nil { + log.Debugf("error flushin gzip stream: %s", err) + goto end + } + + end: + // nil out the buffer to gc consumed events + for i := range buf { + buf[i] = nil + } + + if !ok { + if err != nil { + s.Reset() + } else { + gzipW.Close() + s.Close() + } + return + } + + if err != nil { + s.Reset() + s, err = t.openStream() + if err != nil { + log.Debugf("error opening remote tracer stream: %s", err.Error()) + return + } + + gzipW.Reset(s) + } + } +} + +func (t *RemoteTracer) openStream() (Stream, error) { + for { + ctx, cancel := context.WithTimeout(t.ctx, time.Minute) + s, err := t.host.NewStream(ctx, t.peer, RemoteTracerProtoID) + cancel() + if err != nil { + if t.ctx.Err() != nil { + return nil, err + } + + // wait a minute and try again, to account for transient server downtime + select { + case <-time.After(time.Minute): + continue + case <-t.ctx.Done(): + return nil, t.ctx.Err() + } + } + + return s, nil + } +} + +var _ EventTracer = (*RemoteTracer)(nil)*/ diff --git a/p2p/pubsub/validation.go b/p2p/pubsub/validation.go new file mode 100644 index 0000000000..eccacf3372 --- /dev/null +++ b/p2p/pubsub/validation.go @@ -0,0 +1,533 @@ +package pubsub + +import ( + "context" + "fmt" + "runtime" + "sync" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + "github.com/AlayaNetwork/Alaya-Go/log" +) + +const ( + defaultValidateQueueSize = 32 + defaultValidateConcurrency = 1024 + defaultValidateThrottle = 8192 +) + +// ValidationError is an error that may be signalled from message publication when the message +// fails validation +type ValidationError struct { + Reason string +} + +func (e ValidationError) Error() string { + return e.Reason +} + +// Validator is a function that validates a message with a binary decision: accept or reject. +type Validator func(context.Context, enode.ID, *Message) bool + +// ValidatorEx is an extended validation function that validates a message with an enumerated decision +type ValidatorEx func(context.Context, enode.ID, *Message) ValidationResult + +// ValidationResult represents the decision of an extended validator +type ValidationResult int + +const ( + // ValidationAccept is a validation decision that indicates a valid message that should be accepted and + // delivered to the application and forwarded to the network. + ValidationAccept = ValidationResult(0) + // ValidationReject is a validation decision that indicates an invalid message that should not be + // delivered to the application or forwarded to the application. Furthermore the peer that forwarded + // the message should be penalized by peer scoring routers. + ValidationReject = ValidationResult(1) + // ValidationIgnore is a validation decision that indicates a message that should be ignored: it will + // be neither delivered to the application nor forwarded to the network. However, in contrast to + // ValidationReject, the peer that forwarded the message must not be penalized by peer scoring routers. + ValidationIgnore = ValidationResult(2) + // internal + validationThrottled = ValidationResult(-1) +) + +// ValidatorOpt is an option for RegisterTopicValidator. +type ValidatorOpt func(addVal *addValReq) error + +// validation represents the validator pipeline. +// The validator pipeline performs signature validation and runs a +// sequence of user-configured validators per-topic. It is possible to +// adjust various concurrency parameters, such as the number of +// workers and the max number of simultaneous validations. The user +// can also attach inline validators that will be executed +// synchronously; this may be useful to prevent superfluous +// context-switching for lightweight tasks. +type validation struct { + p *PubSub + + // mx protects the validator map + mx sync.Mutex + // topicVals tracks per topic validators + topicVals map[string]*topicVal + + // validateQ is the front-end to the validation pipeline + validateQ chan *validateReq + + // validateThrottle limits the number of active validation goroutines + validateThrottle chan struct{} + + // this is the number of synchronous validation workers + validateWorkers int +} + +// validation requests +type validateReq struct { + vals []*topicVal + src enode.ID + msg *Message +} + +// representation of topic validators +type topicVal struct { + topic string + validate ValidatorEx + validateTimeout time.Duration + validateThrottle chan struct{} + validateInline bool +} + +// async request to add a topic validators +type addValReq struct { + topic string + validate interface{} + timeout time.Duration + throttle int + inline bool + resp chan error +} + +// async request to remove a topic validator +type rmValReq struct { + topic string + resp chan error +} + +// newValidation creates a new validation pipeline +func newValidation() *validation { + return &validation{ + topicVals: make(map[string]*topicVal), + validateQ: make(chan *validateReq, defaultValidateQueueSize), + validateThrottle: make(chan struct{}, defaultValidateThrottle), + validateWorkers: runtime.NumCPU(), + } +} + +// Start attaches the validation pipeline to a pubsub instance and starts background +// workers +func (v *validation) Start(p *PubSub) { + v.p = p + for i := 0; i < v.validateWorkers; i++ { + go v.validateWorker() + } +} + +// AddValidator adds a new validator +func (v *validation) AddValidator(req *addValReq) { + v.mx.Lock() + defer v.mx.Unlock() + + topic := req.topic + + _, ok := v.topicVals[topic] + if ok { + req.resp <- fmt.Errorf("duplicate validator for topic %s", topic) + return + } + + makeValidatorEx := func(v Validator) ValidatorEx { + return func(ctx context.Context, p enode.ID, msg *Message) ValidationResult { + if v(ctx, p, msg) { + return ValidationAccept + } else { + return ValidationReject + } + } + } + + var validator ValidatorEx + switch v := req.validate.(type) { + case func(ctx context.Context, p enode.ID, msg *Message) bool: + validator = makeValidatorEx(Validator(v)) + case Validator: + validator = makeValidatorEx(v) + + case func(ctx context.Context, p enode.ID, msg *Message) ValidationResult: + validator = ValidatorEx(v) + case ValidatorEx: + validator = v + + default: + req.resp <- fmt.Errorf("unknown validator type for topic %s; must be an instance of Validator or ValidatorEx", topic) + return + } + + val := &topicVal{ + topic: topic, + validate: validator, + validateTimeout: 0, + validateThrottle: make(chan struct{}, defaultValidateConcurrency), + validateInline: req.inline, + } + + if req.timeout > 0 { + val.validateTimeout = req.timeout + } + + if req.throttle > 0 { + val.validateThrottle = make(chan struct{}, req.throttle) + } + + v.topicVals[topic] = val + req.resp <- nil +} + +// RemoveValidator removes an existing validator +func (v *validation) RemoveValidator(req *rmValReq) { + v.mx.Lock() + defer v.mx.Unlock() + + topic := req.topic + + _, ok := v.topicVals[topic] + if ok { + delete(v.topicVals, topic) + req.resp <- nil + } else { + req.resp <- fmt.Errorf("no validator for topic %s", topic) + } +} + +// PushLocal synchronously pushes a locally published message and performs applicable +// validations. +// Returns an error if validation fails +func (v *validation) PushLocal(msg *Message) error { + + err := v.p.checkSigningPolicy(msg) + if err != nil { + return err + } + + vals := v.getValidators(msg) + return v.validate(vals, msg.ReceivedFrom.ID(), msg, true) +} + +// Push pushes a message into the validation pipeline. +// It returns true if the message can be forwarded immediately without validation. +func (v *validation) Push(src enode.ID, msg *Message) bool { + vals := v.getValidators(msg) + + if len(vals) > 0 || msg.Signature != nil { + select { + case v.validateQ <- &validateReq{vals, src, msg}: + default: + log.Debug("message validation throttled: queue full; dropping message from %s", "from", src) + } + return false + } + + return true +} + +// getValidators returns all validators that apply to a given message +func (v *validation) getValidators(msg *Message) []*topicVal { + v.mx.Lock() + defer v.mx.Unlock() + + topic := msg.GetTopic() + + val, ok := v.topicVals[topic] + if !ok { + return nil + } + + return []*topicVal{val} +} + +// validateWorker is an active goroutine performing inline validation +func (v *validation) validateWorker() { + for { + select { + case req := <-v.validateQ: + v.validate(req.vals, req.src, req.msg, false) + case <-v.p.ctx.Done(): + return + } + } +} + +// validate performs validation and only sends the message if all validators succeed +func (v *validation) validate(vals []*topicVal, src enode.ID, msg *Message, synchronous bool) error { + // If signature verification is enabled, but signing is disabled, + // the Signature is required to be nil upon receiving the message in PubSub.pushMsg. + if msg.Signature != nil { + if !v.validateSignature(msg) { + log.Debug("message signature validation failed; dropping message from %s", "src", src) + return ValidationError{Reason: RejectInvalidSignature} + } + } + + // we can mark the message as seen now that we have verified the signature + // and avoid invoking user validators more than once + id := v.p.msgID(msg.Message) + if !v.p.markSeen(id) { + return nil + } + + var inline, async []*topicVal + for _, val := range vals { + if val.validateInline || synchronous { + inline = append(inline, val) + } else { + async = append(async, val) + } + } + + // apply inline (synchronous) validators + result := ValidationAccept +loop: + for _, val := range inline { + switch val.validateMsg(v.p.ctx, src, msg) { + case ValidationAccept: + case ValidationReject: + result = ValidationReject + break loop + case ValidationIgnore: + result = ValidationIgnore + } + } + + if result == ValidationReject { + log.Debug("message validation failed; dropping message from %s", "src", src) + return ValidationError{Reason: RejectValidationFailed} + } + + // apply async validators + if len(async) > 0 { + select { + case v.validateThrottle <- struct{}{}: + go func() { + v.doValidateTopic(async, src, msg, result) + <-v.validateThrottle + }() + default: + log.Debug("message validation throttled; dropping message from %s", "src", src) + } + return nil + } + + if result == ValidationIgnore { + return ValidationError{Reason: RejectValidationIgnored} + } + + // no async validators, accepted message, send it! + select { + case v.p.sendMsg <- msg: + return nil + case <-v.p.ctx.Done(): + return v.p.ctx.Err() + } +} + +func (v *validation) validateSignature(msg *Message) bool { + /*err := verifyMessageSignature(msg.Message) + if err != nil { + log.Debugf("signature verification error: %s", err.Error()) + return false + }*/ + + return true +} + +func (v *validation) doValidateTopic(vals []*topicVal, src enode.ID, msg *Message, r ValidationResult) { + result := v.validateTopic(vals, src, msg) + + if result == ValidationAccept && r != ValidationAccept { + result = r + } + + switch result { + case ValidationAccept: + v.p.sendMsg <- msg + case ValidationReject: + log.Debug("message validation failed; dropping message from %s", "src", src) + return + case ValidationIgnore: + log.Debug("message validation punted; ignoring message from %s", "src", src) + return + case validationThrottled: + log.Debug("message validation throttled; ignoring message from %s", "src", src) + + default: + // BUG: this would be an internal programming error, so a panic seems appropiate. + panic(fmt.Errorf("unexpected validation result: %d", result)) + } +} + +func (v *validation) validateTopic(vals []*topicVal, src enode.ID, msg *Message) ValidationResult { + if len(vals) == 1 { + return v.validateSingleTopic(vals[0], src, msg) + } + + ctx, cancel := context.WithCancel(v.p.ctx) + defer cancel() + + rch := make(chan ValidationResult, len(vals)) + rcount := 0 + + for _, val := range vals { + rcount++ + + select { + case val.validateThrottle <- struct{}{}: + go func(val *topicVal) { + rch <- val.validateMsg(ctx, src, msg) + <-val.validateThrottle + }(val) + + default: + log.Debug("validation throttled for topic", "topic", val.topic) + rch <- validationThrottled + } + } + + result := ValidationAccept +loop: + for i := 0; i < rcount; i++ { + switch <-rch { + case ValidationAccept: + case ValidationReject: + result = ValidationReject + break loop + case ValidationIgnore: + // throttled validation has the same effect, but takes precedence over Ignore as it is not + // known whether the throttled validator would have signaled rejection. + if result != validationThrottled { + result = ValidationIgnore + } + case validationThrottled: + result = validationThrottled + } + } + + return result +} + +// fast path for single topic validation that avoids the extra goroutine +func (v *validation) validateSingleTopic(val *topicVal, src enode.ID, msg *Message) ValidationResult { + select { + case val.validateThrottle <- struct{}{}: + res := val.validateMsg(v.p.ctx, src, msg) + <-val.validateThrottle + return res + + default: + log.Debug("validation throttled for topic", "topic", val.topic) + return validationThrottled + } +} + +func (val *topicVal) validateMsg(ctx context.Context, src enode.ID, msg *Message) ValidationResult { + start := time.Now() + defer func() { + log.Debug("validation done; took %s", "time", time.Since(start)) + }() + + if val.validateTimeout > 0 { + var cancel func() + ctx, cancel = context.WithTimeout(ctx, val.validateTimeout) + defer cancel() + } + + r := val.validate(ctx, src, msg) + switch r { + case ValidationAccept: + fallthrough + case ValidationReject: + fallthrough + case ValidationIgnore: + return r + + default: + log.Warn("Unexpected result from validator: %d; ignoring message", "r", r) + return ValidationIgnore + } +} + +/// Options + +// WithValidateQueueSize sets the buffer of validate queue. Defaults to 32. +// When queue is full, validation is throttled and new messages are dropped. +func WithValidateQueueSize(n int) Option { + return func(ps *PubSub) error { + if n > 0 { + ps.val.validateQ = make(chan *validateReq, n) + return nil + } + return fmt.Errorf("validate queue size must be > 0") + } +} + +// WithValidateThrottle sets the upper bound on the number of active validation +// goroutines across all topics. The default is 8192. +func WithValidateThrottle(n int) Option { + return func(ps *PubSub) error { + ps.val.validateThrottle = make(chan struct{}, n) + return nil + } +} + +// WithValidateWorkers sets the number of synchronous validation worker goroutines. +// Defaults to NumCPU. +// +// The synchronous validation workers perform signature validation, apply inline +// user validators, and schedule asynchronous user validators. +// You can adjust this parameter to devote less cpu time to synchronous validation. +func WithValidateWorkers(n int) Option { + return func(ps *PubSub) error { + if n > 0 { + ps.val.validateWorkers = n + return nil + } + return fmt.Errorf("number of validation workers must be > 0") + } +} + +// WithValidatorTimeout is an option that sets a timeout for an (asynchronous) topic validator. +// By default there is no timeout in asynchronous validators. +func WithValidatorTimeout(timeout time.Duration) ValidatorOpt { + return func(addVal *addValReq) error { + addVal.timeout = timeout + return nil + } +} + +// WithValidatorConcurrency is an option that sets the topic validator throttle. +// This controls the number of active validation goroutines for the topic; the default is 1024. +func WithValidatorConcurrency(n int) ValidatorOpt { + return func(addVal *addValReq) error { + addVal.throttle = n + return nil + } +} + +// WithValidatorInline is an option that sets the validation disposition to synchronous: +// it will be executed inline in validation front-end, without spawning a new goroutine. +// This is suitable for simple or cpu-bound validators that do not block. +func WithValidatorInline(inline bool) ValidatorOpt { + return func(addVal *addValReq) error { + addVal.inline = inline + return nil + } +} From 0a1e12276aca084679c700ffc439c8c707e2cd4e Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 29 Oct 2021 15:14:48 +0800 Subject: [PATCH 014/299] fix node id --- p2p/enode/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/enode/node.go b/p2p/enode/node.go index 7a31d27422..557af1ad7c 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -206,7 +206,7 @@ func (n *Node) UnmarshalText(text []byte) error { } var ( - ZeroID = HexID("0x0") + ZeroID ID ) // ID is a unique identifier for each node. From b24ac5a3e060ac7f0e227267c1df980f52863cc1 Mon Sep 17 00:00:00 2001 From: benbaley Date: Mon, 1 Nov 2021 10:17:06 +0800 Subject: [PATCH 015/299] pubsub --- p2p/pubsub/pubsub.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 3cb145da9d..46560ee425 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -17,10 +17,6 @@ package pubsub -import ( - "github.com/AlayaNetwork/Alaya-Go/p2p" -) - // PubSub is the implementation of the pubsub system. type PubSub struct { // atomic counter for seqnos @@ -38,12 +34,6 @@ type PubSub struct { peerOutboundQueueSize int } -// After the node is successfully connected and the message belongs -// to the cbft.pubsub protocol message, the method is called. -func (pubsub *PubSub) handler(p *p2p.Peer, rw p2p.MsgReadWriter) error { - return nil -} - // NewPubSub returns a new PubSub management object. func NewPubSub() *PubSub { ps := &PubSub{ From d21007264b63b409e767b7609d39e658dd511956 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Thu, 4 Nov 2021 15:19:46 +0800 Subject: [PATCH 016/299] Implement network interface --- p2p/pubsub/interfaces.go | 6 +- p2p/pubsub/pubsub.go | 4 +- p2p/pubsub/pubsub_common.go | 1 + p2p/server.go | 2 + p2p/swarm/network.go | 136 ++++++++++++++++++++++++++++++++++++ 5 files changed, 142 insertions(+), 7 deletions(-) create mode 100644 p2p/swarm/network.go diff --git a/p2p/pubsub/interfaces.go b/p2p/pubsub/interfaces.go index 90fe86191c..fc72283083 100644 --- a/p2p/pubsub/interfaces.go +++ b/p2p/pubsub/interfaces.go @@ -91,8 +91,6 @@ type StreamHandler func(Stream) // connections (see swarm pkg, and peerstream.Swarm). Connections // are encrypted with a TLS-like protocol. type Network interface { - io.Closer - // ConnsToPeer returns the connections in this Netowrk for given peer. ConnsToPeer(p enode.ID) []Conn @@ -150,8 +148,6 @@ type Stream interface { // be useful to get information about the peer on the other side: // stream.Conn().RemotePeer() type Conn interface { - io.Closer - // ID returns an identifier that uniquely identifies this Conn within this // host, during this run. Connection IDs may repeat across restarts. ID() string @@ -188,4 +184,4 @@ const ( DirInbound // DirOutbound is for when the local peer initiated a connection. DirOutbound -) +) \ No newline at end of file diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index fb505363e4..5be0bfc83f 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -681,7 +681,7 @@ func (p *PubSub) handleDeadPeers() { close(ch) - if p.host.Network().Connectedness(pid) == Connected { + /*if p.host.Network().Connectedness(pid) == Connected { // still connected, must be a duplicate connection being closed. // we respawn the writer as we need to ensure there is a stream active log.Debug("peer declared dead but still connected; respawning writer", "writer", pid) @@ -690,7 +690,7 @@ func (p *PubSub) handleDeadPeers() { go p.handleNewPeer(p.ctx, pid, messages) p.peers[pid] = messages continue - } + }*/ delete(p.peers, pid) for t, tmap := range p.topics { diff --git a/p2p/pubsub/pubsub_common.go b/p2p/pubsub/pubsub_common.go index 9fa95708a7..86af7703af 100644 --- a/p2p/pubsub/pubsub_common.go +++ b/p2p/pubsub/pubsub_common.go @@ -155,6 +155,7 @@ func (p *PubSub) handleSendingMessages(ctx context.Context, s Stream, outgoing < if err := p2p.Send(s.ReadWriter(), 0xff, &rpc.RPC); err != nil { log.Error("Send message fail", "err", err) + return } /* err := writeMsg(&rpc.RPC) diff --git a/p2p/server.go b/p2p/server.go index bfbe878fa4..7569fac734 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -878,6 +878,8 @@ running: if p.Inbound() { inboundCount++ } + + // TODO pubsub network.NotifyAll } c.cont <- err diff --git a/p2p/swarm/network.go b/p2p/swarm/network.go new file mode 100644 index 0000000000..bf21d352e3 --- /dev/null +++ b/p2p/swarm/network.go @@ -0,0 +1,136 @@ +package swarm + +import ( + "github.com/AlayaNetwork/Alaya-Go/p2p" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" + "sync" +) + +type Network struct { + sync.RWMutex + server *p2p.Server + + m map[pubsub.Notifiee]struct{} + + conns struct { + sync.RWMutex + m map[enode.ID][]pubsub.Conn + } +} + +func NewNetwork(server *p2p.Server) *Network { + n := &Network{ + RWMutex: sync.RWMutex{}, + server: server, + m: make(map[pubsub.Notifiee]struct{}), + } + n.conns.m = make(map[enode.ID][]pubsub.Conn) + return n +} + +func (n *Network) SetConn(p enode.ID, conn pubsub.Conn) { + n.conns.RLock() + defer n.conns.RUnlock() + conns := n.conns.m[p] + if conns == nil { + conns = make([]pubsub.Conn, 0) + } + conns = append(conns, conn) + n.conns.m[p] = conns +} + +func (n *Network) ConnsToPeer(p enode.ID) []pubsub.Conn { + n.conns.RLock() + defer n.conns.RUnlock() + conns := n.conns.m[p] + output := make([]pubsub.Conn, len(conns)) + for i, c := range conns { + output[i] = c + } + return output +} + +func (n *Network) Connectedness(id enode.ID) pubsub.Connectedness { + for _, p := range n.server.Peers() { + if p.ID() == id { + return pubsub.Connected + } + } + return pubsub.NotConnected +} + +func (n *Network) Notify(f pubsub.Notifiee) { + n.Lock() + n.m[f] = struct{}{} + n.Unlock() +} + +func (n *Network) StopNotify(f pubsub.Notifiee) { + n.Lock() + delete(n.m, f) + n.Unlock() +} + +// notifyAll sends a signal to all Notifiees +func (n *Network) NotifyAll(conn pubsub.Conn) { + var wg sync.WaitGroup + + n.RLock() + wg.Add(len(n.m)) + for f := range n.m { + go func(f pubsub.Notifiee) { + defer wg.Done() + f.Connected(n, conn) + }(f) + } + + wg.Wait() + n.RUnlock() +} + +func (n *Network) Peers() []enode.ID { + var eids []enode.ID + for _, p := range n.server.Peers() { + eids = append(eids, p.ID()) + } + return eids +} + +type Conn struct { + remote *enode.Node + stat pubsub.Stat + + streams struct { + sync.Mutex + m map[pubsub.Stream]struct{} + } +} + +func(c *Conn) ID() string { + return c.remote.ID().String() +} + +func(c *Conn) SetStreams(stream pubsub.Stream) { + c.streams.Lock() + defer c.streams.Unlock() + c.streams.m[stream] = struct{}{} +} + +func(c *Conn) GetStreams() []pubsub.Stream { + c.streams.Lock() + defer c.streams.Unlock() + streams := make([]pubsub.Stream, 0, len(c.streams.m)) + for s := range c.streams.m { + streams = append(streams, s) + } + return streams +} + +func(c *Conn) Stat() pubsub.Stat { + return c.stat +} + +func(c *Conn) RemotePeer() *enode.Node { + return c.remote +} \ No newline at end of file From 08b473dc92b904b2511f10df08c57bc92fcab853 Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 4 Nov 2021 15:29:46 +0800 Subject: [PATCH 017/299] pubsub --- cmd/utils/flags.go | 6 +- consensus/cbft/cbft.go | 22 ++--- consensus/cbft/cbft_common_util.go | 2 +- consensus/cbft/network/handler.go | 17 ---- consensus/cbft/network/interface.go | 4 - consensus/cbft/network/pubsub.go | 55 +++++++++++ eth/backend.go | 11 +-- go.mod | 1 + node/node.go | 4 - p2p/pubsub/interfaces.go | 19 +++- p2p/pubsub/server.go | 102 ++++++++++++++++++-- p2p/server.go | 9 +- p2p/stream.go | 143 ++++++++++++++++++++++++++++ 13 files changed, 335 insertions(+), 60 deletions(-) create mode 100644 consensus/cbft/network/pubsub.go create mode 100644 p2p/stream.go diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index f156eb8a1a..c067866724 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1011,7 +1011,7 @@ func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) { // if ctx.GlobalIsSet(MPCActorFlag.Name) { // cfg.MpcActor = common.HexToAddress(ctx.GlobalString(MPCActorFlag.Name)) // } -// if file := ctx.GlobalString(MPCIceFileFlag.Name); file != "" { +// if file := ctx.GlobalString(MPCIceFileFlag.Name); file != "" {n // if _, err := os.Stat(file); err != nil { // fmt.Println("ice conf not exists.") // return @@ -1183,7 +1183,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { Fatalf("Failed to register the Alaya-Go service: %v", "not support LightSync") } else { err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - fullNode, err := eth.New(stack.PbSvr, ctx, cfg) + fullNode, err := eth.New(ctx, cfg) if err == nil { stack.ChainID = fullNode.APIBackend.ChainConfig().ChainID } @@ -1348,7 +1348,7 @@ func MakeChainForCBFT(ctx *cli.Context, stack *node.Node, cfg *eth.Config, nodeC var engine consensus.Engine if config.Cbft != nil { sc := node.NewServiceContext(nodeCfg, nil, stack.EventMux(), stack.AccountManager()) - engine = eth.CreateConsensusEngine(sc, config, false, chainDb, &cfg.CbftConfig, stack.EventMux(), nil) + engine = eth.CreateConsensusEngine(sc, config, false, chainDb, &cfg.CbftConfig, stack.EventMux()) } cache := &core.CacheConfig{ diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 0c1a4a40d3..642a731c6b 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -125,7 +125,7 @@ type Cbft struct { evPool evidence.EvidencePool log log.Logger network *network.EngineManager - pbSvr *pubsub.Server + subServer *pubsub.SubServer start int32 syncing int32 @@ -190,7 +190,7 @@ type Cbft struct { } // New returns a new CBFT. -func New(sysConfig *params.CbftConfig, optConfig *ctypes.OptionsConfig, eventMux *event.TypeMux, ctx *node.ServiceContext, pbSvr *pubsub.Server) *Cbft { +func New(sysConfig *params.CbftConfig, optConfig *ctypes.OptionsConfig, eventMux *event.TypeMux, ctx *node.ServiceContext) *Cbft { cbft := &Cbft{ config: ctypes.Config{Sys: sysConfig, Option: optConfig}, eventMux: eventMux, @@ -211,7 +211,7 @@ func New(sysConfig *params.CbftConfig, optConfig *ctypes.OptionsConfig, eventMux statQueues: make(map[common.Hash]map[string]int), messageHashCache: mapset.NewSet(), netLatencyMap: make(map[string]*list.List), - pbSvr: pbSvr, + subServer: pubsub.SubServerInstance(), } if evPool, err := evidence.NewEvidencePool(ctx, optConfig.EvidenceDir); err == nil { @@ -261,6 +261,7 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCacheWriter consensus. // init handler and router to process message. // cbft -> handler -> router. cbft.network = network.NewEngineManger(cbft) // init engineManager as handler. + // Start the handler to process the message. go cbft.network.Start() @@ -297,10 +298,6 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCacheWriter consensus. return err } - if err := cbft.pbSvr.Start(); err != nil { - return err - } - utils.SetFalse(&cbft.loading) go cbft.receiveLoop() @@ -880,7 +877,12 @@ func (cbft *Cbft) APIs(chain consensus.ChainReader) []rpc.API { // Protocols return consensus engine to provide protocol information. func (cbft *Cbft) Protocols() []p2p.Protocol { - return cbft.network.Protocols() + protocols := cbft.network.Protocols() + pubsubProtocols := cbft.subServer.Protocols() + for _,subProtol := range pubsubProtocols { + protocols = append(protocols, subProtol) + } + return protocols } // NextBaseBlock is used to calculate the next block. @@ -1849,7 +1851,3 @@ func (cbft *Cbft) DecodeExtra(extra []byte) (common.Hash, uint64, error) { } return qc.BlockHash, qc.BlockNumber, nil } - -func (cbft *Cbft) PubSubServer() *pubsub.Server { - return cbft.pbSvr -} diff --git a/consensus/cbft/cbft_common_util.go b/consensus/cbft/cbft_common_util.go index ade54a610c..f87589edda 100644 --- a/consensus/cbft/cbft_common_util.go +++ b/consensus/cbft/cbft_common_util.go @@ -148,7 +148,7 @@ func CreateCBFT(pk *ecdsa.PrivateKey, sk *bls.SecretKey, period uint64, amount u ctx := node.NewServiceContext(&node.Config{DataDir: ""}, nil, new(event.TypeMux), nil) - return New(sysConfig, optConfig, ctx.EventMux, ctx, nil) + return New(sysConfig, optConfig, ctx.EventMux, ctx) } func CreateGenesis(db ethdb.Database) (core.Genesis, *types.Block) { diff --git a/consensus/cbft/network/handler.go b/consensus/cbft/network/handler.go index 70d54b95b5..5a4b27e7b5 100644 --- a/consensus/cbft/network/handler.go +++ b/consensus/cbft/network/handler.go @@ -318,23 +318,6 @@ func (h *EngineManager) Protocols() []p2p.Protocol { return nil }, }, - { - Name: CbftPubSubProtocolName, - Version: CbftPubSubProtocolVersion, - Length: CbftPubSubProtocolLength, - Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - return h.engine.PubSubServer().Handle(p, rw) - }, - NodeInfo: func() interface{} { - return h.NodeInfo() - }, - PeerInfo: func(id enode.ID) interface{} { - if p, err := h.peers.get(fmt.Sprintf("%x", id[:8])); err == nil { - return p.Info() - } - return nil - }, - }, } } diff --git a/consensus/cbft/network/interface.go b/consensus/cbft/network/interface.go index 9a13e84ee8..0d13cd675a 100644 --- a/consensus/cbft/network/interface.go +++ b/consensus/cbft/network/interface.go @@ -21,7 +21,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" ) // Cbft defines the network layer to use the relevant interface @@ -68,7 +67,4 @@ type Cbft interface { // BlockExists determines if a block exists. BlockExists(blockNumber uint64, blockHash common.Hash) error - - // PubSubServer return pubsub.Server - PubSubServer() *pubsub.Server } diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go new file mode 100644 index 0000000000..59e455f047 --- /dev/null +++ b/consensus/cbft/network/pubsub.go @@ -0,0 +1,55 @@ +// Copyright 2021 The Alaya Network Authors +// This file is part of the Alaya-Go library. +// +// The Alaya-Go 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 Alaya-Go 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 Alaya-Go library. If not, see . + +package network + +import ( + "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" +) + +type PubSub struct { + pubsub pubsub.SubServer + + topicChan chan string + topics []string + // The set of topics we are subscribed to + mySubs map[string]map[*pubsub.Subscription]struct{} +} + +//Subscribe subscribe a topic +func (ps *PubSub) Subscribe(topic string) error { + if err := ps.pubsub.PublishMsg(topic); err != nil { + return err + } + ps.topics = append(ps.topics, topic) + return nil +} + +//UnSubscribe a topic +func (ps *PubSub) Cancel(topic string) error { + sm := ps.mySubs[topic] + if sm != nil { + for s, _ := range sm { + if s != nil { + s.Cancel() + } + } + return nil + } + + return fmt.Errorf("Can not find","topic", topic) +} \ No newline at end of file diff --git a/eth/backend.go b/eth/backend.go index 29f65459bc..3ba9820728 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -20,7 +20,6 @@ package eth import ( "errors" "fmt" - "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "math/big" "os" "sync" @@ -97,7 +96,6 @@ type Ethereum struct { gasPrice *big.Int networkID uint64 netRPCService *ethapi.PublicNetAPI - lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) } @@ -108,7 +106,7 @@ func (s *Ethereum) AddLesServer(ls LesServer) { // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) -func New(pbSvr *pubsub.Server, ctx *node.ServiceContext, config *Config) (*Ethereum, error) { +func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { // Ensure configuration values are compatible and sane if config.SyncMode == downloader.LightSync { return nil, errors.New("can't run eth.PlatON in light sync mode, use les.LightPlatON") @@ -214,13 +212,12 @@ func New(pbSvr *pubsub.Server, ctx *node.ServiceContext, config *Config) (*Ether } log.Info("Initialised chain configuration", "config", chainConfig) - eth := &Ethereum{ config: config, chainDb: chainDb, eventMux: ctx.EventMux, accountManager: ctx.AccountManager, - engine: CreateConsensusEngine(ctx, chainConfig, config.Miner.Noverify, chainDb, &config.CbftConfig, ctx.EventMux, pbSvr), + engine: CreateConsensusEngine(ctx, chainConfig, config.Miner.Noverify, chainDb, &config.CbftConfig, ctx.EventMux), closeBloomHandler: make(chan struct{}), networkID: config.NetworkId, gasPrice: config.Miner.GasPrice, @@ -391,9 +388,9 @@ func recoverSnapshotDB(blockChainCache *core.BlockChainCache) error { // CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainConfig, noverify bool, db ethdb.Database, - cbftConfig *ctypes.OptionsConfig, eventMux *event.TypeMux, pbSvr *pubsub.Server) consensus.Engine { + cbftConfig *ctypes.OptionsConfig, eventMux *event.TypeMux) consensus.Engine { // If proof-of-authority is requested, set it up - engine := cbft.New(chainConfig.Cbft, cbftConfig, eventMux, ctx, pbSvr) + engine := cbft.New(chainConfig.Cbft, cbftConfig, eventMux, ctx) if engine == nil { panic("create consensus engine fail") } diff --git a/go.mod b/go.mod index e7dcae3dd4..f109a16dbc 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/golang/snappy v0.0.1 github.com/gorilla/websocket v1.4.2 github.com/hashicorp/golang-lru v0.5.4 + github.com/herumi/bls v0.0.0-20211018080255-87e98a0e7697 github.com/holiman/uint256 v1.1.1 github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3 github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 diff --git a/node/node.go b/node/node.go index 90b66da6cf..4df5af36d5 100644 --- a/node/node.go +++ b/node/node.go @@ -19,7 +19,6 @@ package node import ( "errors" "fmt" - "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "math/big" "net" "os" @@ -56,7 +55,6 @@ type Node struct { ChainID *big.Int `toml:"-"` serverConfig p2p.Config server *p2p.Server // Currently running P2P networking layer - PbSvr *pubsub.Server // Currently running pubsub networking layer serviceFuncs []ServiceConstructor // Service constructors (in dependency order) services map[reflect.Type]Service // Currently running services @@ -204,8 +202,6 @@ func (n *Node) Start() error { } running := &p2p.Server{Config: n.serverConfig} - n.PbSvr = &pubsub.Server{Pb: pubsub.NewPubSub()} - n.log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name) // Otherwise copy and specialize the P2P configuration diff --git a/p2p/pubsub/interfaces.go b/p2p/pubsub/interfaces.go index 90fe86191c..33e6fc682c 100644 --- a/p2p/pubsub/interfaces.go +++ b/p2p/pubsub/interfaces.go @@ -35,8 +35,8 @@ type ProtocolID string // Host is an object participating in a p2p network, which // implements protocols or provides services. It handles -// requests like a Server, and issues requests like a Client. -// It is called Host because it is both Server and Client (and Peer +// requests like a SubServer, and issues requests like a Client. +// It is called Host because it is both SubServer and Client (and Peer // may be confusing). type Host interface { // ID returns the (local) peer.ID associated with this Host @@ -53,6 +53,7 @@ type Host interface { // peerstore. If there is not an active connection, Connect will issue a // h.Network.Dial, and block until a connection is open, or an error is // returned. // TODO: Relay + NAT. + // addConsensusNode Connect(ctx context.Context, pi enode.ID) error // SetStreamHandler sets the protocol handler on the Host's Mux. @@ -178,6 +179,20 @@ type Stat struct { Extra map[interface{}]interface{} } +// SubServer manages all pubsub peers. +type Server interface { + + Protocols() []p2p.Protocol + + // Start starts running the server. + // Servers can not be re-used after stopping. + Start() (err error) + + PublishMsg(topic string) error + + ReadTopicLoop() +} + // Direction represents which peer in a stream initiated a connection. type Direction int diff --git a/p2p/pubsub/server.go b/p2p/pubsub/server.go index c433884e9c..994010508e 100644 --- a/p2p/pubsub/server.go +++ b/p2p/pubsub/server.go @@ -19,20 +19,50 @@ package pubsub import ( "errors" + "fmt" + ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" + "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "sync" ) -// Server manages all pubsub peers. -type Server struct { - lock sync.Mutex // protects running - running bool - Pb *PubSub +const ( + + // CbftProtocolName is protocol name of CBFT.pubsub. + CbftPubSubProtocolName = "cbft.pubsub" + + // CbftProtocolVersion is protocol version of CBFT.pubsub. + CbftPubSubProtocolVersion = 1 + + // CbftProtocolLength are the number of implemented message corresponding to cbft.pubsub protocol versions. + CbftPubSubProtocolLength = 10 + +) + +type SubServer struct { + lock sync.Mutex // protects running + running bool + ps *PubSub + peerMsgCh chan *ctypes.MsgInfo +} + +var ( + subServerOnce sync.Once + svr *SubServer +) + +func SubServerInstance() *SubServer { + subServerOnce.Do(func() { + log.Info("Init SubServer ...") + svr = NewPubSubServer() + }) + return svr } // Start starts running the server. // Servers can not be re-used after stopping. -func (srv *Server) Start() (err error) { +func (srv *SubServer) Start() (err error) { srv.lock.Lock() defer srv.lock.Unlock() if srv.running { @@ -43,12 +73,68 @@ func (srv *Server) Start() (err error) { } // run is the main loop of the server. -func (s *Server) run() { +func (s *SubServer) run() { } // After the node is successfully connected and the message belongs // to the cbft.pubsub protocol message, the method is called. -func (s *Server) Handle(p *p2p.Peer, rw p2p.MsgReadWriter) error { +func (s *SubServer) Handle(p *p2p.Peer, rw p2p.MsgReadWriter) error { + return nil +} + +// watchingAddTopicValidatorEvent +func (s *SubServer) watchingAddTopicValidatorEvent() { + +} + +// watchingAddTopicValidatorEvent +func (s *SubServer) watchingRemoveTopicValidatorEvent() { + +} + +// PublishMsg +func (s *SubServer) PublishMsg(topic string) error { + if topic == "" { + return fmt.Errorf("topic is nil") + } + return nil +} + +func (s *SubServer) ReadTopicLoop() { + +} + +// Protocol.NodeInfo() +func (s *SubServer) NodeInfo() interface{} { return nil } + +// Protocol.Run() +func (s *SubServer) handler(peer *p2p.Peer, rw p2p.MsgReadWriter) error { + return nil +} + +//Protocols implemented the Protocols method and returned basic information about the CBFT.pubsub protocol. +func (s *SubServer) Protocols() []p2p.Protocol { + return []p2p.Protocol{ + { + Name: CbftPubSubProtocolName, + Version: CbftPubSubProtocolVersion, + Length: CbftPubSubProtocolLength, + Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + return s.handler(p, rw) + }, + NodeInfo: func() interface{} { + return s.NodeInfo() + }, + PeerInfo: func(id enode.ID) interface{} { + return nil + }, + }, + } +} + +func NewPubSubServer() *SubServer { + return &SubServer{} +} diff --git a/p2p/server.go b/p2p/server.go index bfbe878fa4..dd885d929a 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -23,6 +23,7 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "math/big" "net" "sort" @@ -217,6 +218,7 @@ type Server struct { consensus bool addconsensus chan *enode.Node removeconsensus chan *enode.Node + subServer *pubsub.SubServer } type peerOpFunc func(map[enode.ID]*Peer) @@ -511,7 +513,7 @@ func (srv *Server) Start() (err error) { // static fields if srv.PrivateKey == nil { - return errors.New("Server.PrivateKey must be set to a non-nil key") + return errors.New("SubServer.PrivateKey must be set to a non-nil key") } if srv.newTransport == nil { srv.newTransport = newRLPX @@ -541,6 +543,9 @@ func (srv *Server) Start() (err error) { } srv.setupDialScheduler() + srv.subServer = pubsub.SubServerInstance() + srv.subServer.Start() + srv.loopWG.Add(1) go srv.run() return nil @@ -1186,7 +1191,7 @@ func (srv *Server) runPeer(p *Peer) { // Broadcast peer drop to external subscribers. This needs to be // after the send to delpeer so subscribers have a consistent view of - // the peer set (i.e. Server.Peers() doesn't include the peer when the + // the peer set (i.e. SubServer.Peers() doesn't include the peer when the // event is received. srv.peerFeed.Send(&PeerEvent{ Type: PeerEventTypeDrop, diff --git a/p2p/stream.go b/p2p/stream.go new file mode 100644 index 0000000000..3f7ad12c58 --- /dev/null +++ b/p2p/stream.go @@ -0,0 +1,143 @@ +package p2p + +import ( + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/libp2p/go-libp2p-core/mux" + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/protocol" +) + +// Validate Stream conforms to the go-libp2p-net Stream interface +var _ network.Stream = &Stream{} + +// Stream is the stream type used by pubsub. In general +type Stream struct { + id uint64 + + stream mux.MuxedStream + conn *Conn + + closeOnce sync.Once + + notifyLk sync.Mutex + + protocol atomic.Value + + stat network.Stat +} + +func (s *Stream) ID() string { + // format: -- + return fmt.Sprintf("%s-%d", s.conn.ID(), s.id) +} + +func (s *Stream) String() string { + return fmt.Sprintf( + " %s (%s)>", + s.conn.conn.Transport(), + s.conn.LocalMultiaddr(), + s.conn.LocalPeer(), + s.conn.RemoteMultiaddr(), + s.conn.RemotePeer(), + ) +} + +// Conn returns the Conn associated with this stream, as an network.Conn +func (s *Stream) Conn() network.Conn { + return s.conn +} + +// Read reads bytes from a stream. +func (s *Stream) Read(p []byte) (int, error) { + return s.stream.Read(p) +} + +// Write writes bytes to a stream, flushing for each call. +func (s *Stream) Write(p []byte) (int, error) { + return s.stream.Write(p) +} + +// Close closes the stream, closing both ends and freeing all associated +// resources. +func (s *Stream) Close() error { + err := s.stream.Close() + s.closeOnce.Do(s.remove) + return err +} + +// Reset resets the stream, signaling an error on both ends and freeing all +// associated resources. +func (s *Stream) Reset() error { + err := s.stream.Reset() + s.closeOnce.Do(s.remove) + return err +} + +// Close closes the stream for writing, flushing all data and sending an EOF. +// This function does not free resources, call Close or Reset when done with the +// stream. +func (s *Stream) CloseWrite() error { + return s.stream.CloseWrite() +} + +// Close closes the stream for reading. This function does not free resources, +// call Close or Reset when done with the stream. +func (s *Stream) CloseRead() error { + return s.stream.CloseRead() +} + +func (s *Stream) remove() { + s.conn.removeStream(s) + + // We *must* do this in a goroutine. This can be called during a + // an open notification and will block until that notification is done. + go func() { + s.notifyLk.Lock() + defer s.notifyLk.Unlock() + + s.conn.swarm.notifyAll(func(f network.Notifiee) { + f.ClosedStream(s.conn.swarm, s) + }) + s.conn.swarm.refs.Done() + }() +} + +// Protocol returns the protocol negotiated on this stream (if set). +func (s *Stream) Protocol() protocol.ID { + // Ignore type error. It means that the protocol is unset. + p, _ := s.protocol.Load().(protocol.ID) + return p +} + +// SetProtocol sets the protocol for this stream. +// +// This doesn't actually *do* anything other than record the fact that we're +// speaking the given protocol over this stream. It's still up to the user to +// negotiate the protocol. This is usually done by the Host. +func (s *Stream) SetProtocol(p protocol.ID) { + s.protocol.Store(p) +} + +// SetDeadline sets the read and write deadlines for this stream. +func (s *Stream) SetDeadline(t time.Time) error { + return s.stream.SetDeadline(t) +} + +// SetReadDeadline sets the read deadline for this stream. +func (s *Stream) SetReadDeadline(t time.Time) error { + return s.stream.SetReadDeadline(t) +} + +// SetWriteDeadline sets the write deadline for this stream. +func (s *Stream) SetWriteDeadline(t time.Time) error { + return s.stream.SetWriteDeadline(t) +} + +// Stat returns metadata information for this stream. +func (s *Stream) Stat() network.Stat { + return s.stat +} From ab45eb505be3c7b3124f2528b65290371239edbc Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 4 Nov 2021 17:38:31 +0800 Subject: [PATCH 018/299] pubsub --- p2p/pubsub/interfaces.go | 22 ------- p2p/pubsub/pubsub.go | 2 - p2p/pubsub/pubsub_common.go | 6 +- p2p/stream.go | 118 +++++------------------------------- 4 files changed, 17 insertions(+), 131 deletions(-) diff --git a/p2p/pubsub/interfaces.go b/p2p/pubsub/interfaces.go index 6421d458ff..337f2b23d2 100644 --- a/p2p/pubsub/interfaces.go +++ b/p2p/pubsub/interfaces.go @@ -2,7 +2,6 @@ package pubsub import ( "context" - "io" "time" "github.com/AlayaNetwork/Alaya-Go/p2p" @@ -121,27 +120,6 @@ type Stream interface { Conn() Conn ReadWriter() p2p.MsgReadWriter // - - // Reset closes both ends of the stream. Use this to tell the remote - // side to hang up and go away. - Reset() error - - // Close closes the stream. - // - // * Any buffered data for writing will be flushed. - // * Future reads will fail. - // * Any in-progress reads/writes will be interrupted. - // - // Close may be asynchronous and _does not_ guarantee receipt of the - // data. - // - // Close closes the stream for both reading and writing. - // Close is equivalent to calling `CloseRead` and `CloseWrite`. Importantly, Close will not wait for any form of acknowledgment. - // If acknowledgment is required, the caller must call `CloseWrite`, then wait on the stream for a response (or an EOF), - // then call Close() to free the stream object. - // - // When done with a stream, the user must call either Close() or `Reset()` to discard the stream, even after calling `CloseRead` and/or `CloseWrite`. - io.Closer } // Conn is a connection to a remote peer. It multiplexes streams. diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 89b5f727fc..e149cac545 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -532,7 +532,6 @@ func (p *PubSub) processLoop(ctx context.Context) { ch, ok := p.peers[pid.ID()] if !ok { log.Warn("new stream for unknown peer: ", pid) - s.Reset() continue } @@ -540,7 +539,6 @@ func (p *PubSub) processLoop(ctx context.Context) { log.Warn("closing stream for blacklisted peer: ", pid) close(ch) delete(p.peers, pid.ID()) - s.Reset() continue } diff --git a/p2p/pubsub/pubsub_common.go b/p2p/pubsub/pubsub_common.go index 86af7703af..5c96a41d2c 100644 --- a/p2p/pubsub/pubsub_common.go +++ b/p2p/pubsub/pubsub_common.go @@ -42,10 +42,9 @@ func (p *PubSub) handleNewStream(s Stream) { peer := s.Conn().RemotePeer() p.inboundStreamsMx.Lock() - other, dup := p.inboundStreams[peer.ID()] + _, dup := p.inboundStreams[peer.ID()] if dup { log.Debug("duplicate inbound stream , resetting other stream", "from", peer) - other.Reset() } p.inboundStreams[peer.ID()] = s p.inboundStreamsMx.Unlock() @@ -76,8 +75,6 @@ func (p *PubSub) handleNewStream(s Stream) { select { case p.incoming <- rpc: case <-p.ctx.Done(): - // Close is useless because the other side isn't reading. - s.Reset() return } } @@ -145,7 +142,6 @@ func (p *PubSub) handleSendingMessages(ctx context.Context, s Stream, outgoing < return bufw.Flush() }*/ - defer s.Close() for { select { case rpc, ok := <-outgoing: diff --git a/p2p/stream.go b/p2p/stream.go index 3f7ad12c58..78ce866b50 100644 --- a/p2p/stream.go +++ b/p2p/stream.go @@ -2,110 +2,30 @@ package p2p import ( "fmt" - "sync" - "sync/atomic" - "time" - - "github.com/libp2p/go-libp2p-core/mux" - "github.com/libp2p/go-libp2p-core/network" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "github.com/libp2p/go-libp2p-core/protocol" + "sync/atomic" ) -// Validate Stream conforms to the go-libp2p-net Stream interface -var _ network.Stream = &Stream{} - // Stream is the stream type used by pubsub. In general type Stream struct { - id uint64 - - stream mux.MuxedStream - conn *Conn - - closeOnce sync.Once - - notifyLk sync.Mutex - + conn pubsub.Conn + rw MsgReadWriter protocol atomic.Value - - stat network.Stat -} - -func (s *Stream) ID() string { - // format: -- - return fmt.Sprintf("%s-%d", s.conn.ID(), s.id) } func (s *Stream) String() string { return fmt.Sprintf( - " %s (%s)>", - s.conn.conn.Transport(), - s.conn.LocalMultiaddr(), - s.conn.LocalPeer(), - s.conn.RemoteMultiaddr(), + " %s>", s.conn.RemotePeer(), ) } // Conn returns the Conn associated with this stream, as an network.Conn -func (s *Stream) Conn() network.Conn { +func (s *Stream) Conn() pubsub.Conn { return s.conn } -// Read reads bytes from a stream. -func (s *Stream) Read(p []byte) (int, error) { - return s.stream.Read(p) -} - -// Write writes bytes to a stream, flushing for each call. -func (s *Stream) Write(p []byte) (int, error) { - return s.stream.Write(p) -} - -// Close closes the stream, closing both ends and freeing all associated -// resources. -func (s *Stream) Close() error { - err := s.stream.Close() - s.closeOnce.Do(s.remove) - return err -} - -// Reset resets the stream, signaling an error on both ends and freeing all -// associated resources. -func (s *Stream) Reset() error { - err := s.stream.Reset() - s.closeOnce.Do(s.remove) - return err -} - -// Close closes the stream for writing, flushing all data and sending an EOF. -// This function does not free resources, call Close or Reset when done with the -// stream. -func (s *Stream) CloseWrite() error { - return s.stream.CloseWrite() -} - -// Close closes the stream for reading. This function does not free resources, -// call Close or Reset when done with the stream. -func (s *Stream) CloseRead() error { - return s.stream.CloseRead() -} - -func (s *Stream) remove() { - s.conn.removeStream(s) - - // We *must* do this in a goroutine. This can be called during a - // an open notification and will block until that notification is done. - go func() { - s.notifyLk.Lock() - defer s.notifyLk.Unlock() - - s.conn.swarm.notifyAll(func(f network.Notifiee) { - f.ClosedStream(s.conn.swarm, s) - }) - s.conn.swarm.refs.Done() - }() -} - // Protocol returns the protocol negotiated on this stream (if set). func (s *Stream) Protocol() protocol.ID { // Ignore type error. It means that the protocol is unset. @@ -122,22 +42,16 @@ func (s *Stream) SetProtocol(p protocol.ID) { s.protocol.Store(p) } -// SetDeadline sets the read and write deadlines for this stream. -func (s *Stream) SetDeadline(t time.Time) error { - return s.stream.SetDeadline(t) -} - -// SetReadDeadline sets the read deadline for this stream. -func (s *Stream) SetReadDeadline(t time.Time) error { - return s.stream.SetReadDeadline(t) -} - -// SetWriteDeadline sets the write deadline for this stream. -func (s *Stream) SetWriteDeadline(t time.Time) error { - return s.stream.SetWriteDeadline(t) +func (s *Stream) ReadWriter() MsgReadWriter { + return s.rw } -// Stat returns metadata information for this stream. -func (s *Stream) Stat() network.Stat { - return s.stat +// newStream creates a new Stream. +func newStream(conn pubsub.Conn, rw MsgReadWriter, id protocol.ID) *Stream { + s := &Stream{ + conn: conn, + rw: rw, + } + s.SetProtocol(id) + return s } From 869076c80b989097bd5745b9347d94a7f2c145bd Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 4 Nov 2021 18:24:59 +0800 Subject: [PATCH 019/299] pubsub --- p2p/stream.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/p2p/stream.go b/p2p/stream.go index 78ce866b50..b3f96eb487 100644 --- a/p2p/stream.go +++ b/p2p/stream.go @@ -3,7 +3,6 @@ package p2p import ( "fmt" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" - "github.com/libp2p/go-libp2p-core/protocol" "sync/atomic" ) @@ -27,9 +26,9 @@ func (s *Stream) Conn() pubsub.Conn { } // Protocol returns the protocol negotiated on this stream (if set). -func (s *Stream) Protocol() protocol.ID { +func (s *Stream) Protocol() pubsub.ProtocolID { // Ignore type error. It means that the protocol is unset. - p, _ := s.protocol.Load().(protocol.ID) + p, _ := s.protocol.Load().(pubsub.ProtocolID) return p } @@ -38,7 +37,7 @@ func (s *Stream) Protocol() protocol.ID { // This doesn't actually *do* anything other than record the fact that we're // speaking the given protocol over this stream. It's still up to the user to // negotiate the protocol. This is usually done by the Host. -func (s *Stream) SetProtocol(p protocol.ID) { +func (s *Stream) SetProtocol(p pubsub.ProtocolID) { s.protocol.Store(p) } @@ -47,7 +46,7 @@ func (s *Stream) ReadWriter() MsgReadWriter { } // newStream creates a new Stream. -func newStream(conn pubsub.Conn, rw MsgReadWriter, id protocol.ID) *Stream { +func newStream(conn pubsub.Conn, rw MsgReadWriter, id pubsub.ProtocolID) *Stream { s := &Stream{ conn: conn, rw: rw, From 87bbd4bf0bb871cf7d34115a52e87026f65a34cc Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 4 Nov 2021 18:53:28 +0800 Subject: [PATCH 020/299] pubsub --- cmd/utils/flags.go | 2 +- p2p/{ => swarm}/stream.go | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) rename p2p/{ => swarm}/stream.go (82%) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c067866724..e1bc5c8042 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1011,7 +1011,7 @@ func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) { // if ctx.GlobalIsSet(MPCActorFlag.Name) { // cfg.MpcActor = common.HexToAddress(ctx.GlobalString(MPCActorFlag.Name)) // } -// if file := ctx.GlobalString(MPCIceFileFlag.Name); file != "" {n +// if file := ctx.GlobalString(MPCIceFileFlag.Name); file != "" { // if _, err := os.Stat(file); err != nil { // fmt.Println("ice conf not exists.") // return diff --git a/p2p/stream.go b/p2p/swarm/stream.go similarity index 82% rename from p2p/stream.go rename to p2p/swarm/stream.go index b3f96eb487..6ba9b08682 100644 --- a/p2p/stream.go +++ b/p2p/swarm/stream.go @@ -1,15 +1,16 @@ -package p2p +package swarm import ( "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "sync/atomic" ) // Stream is the stream type used by pubsub. In general type Stream struct { - conn pubsub.Conn - rw MsgReadWriter + conn pubsub.Conn + rw p2p.MsgReadWriter protocol atomic.Value } @@ -41,12 +42,12 @@ func (s *Stream) SetProtocol(p pubsub.ProtocolID) { s.protocol.Store(p) } -func (s *Stream) ReadWriter() MsgReadWriter { +func (s *Stream) ReadWriter() p2p.MsgReadWriter { return s.rw } // newStream creates a new Stream. -func newStream(conn pubsub.Conn, rw MsgReadWriter, id pubsub.ProtocolID) *Stream { +func newStream(conn pubsub.Conn, rw p2p.MsgReadWriter, id pubsub.ProtocolID) *Stream { s := &Stream{ conn: conn, rw: rw, From 6e13550f1a6c839a601092cb93b15828bb334811 Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 4 Nov 2021 19:01:51 +0800 Subject: [PATCH 021/299] pubsub --- consensus/cbft/cbft.go | 5 ++- consensus/cbft/network/pubsub.go | 3 +- p2p/pubsub/interfaces.go | 6 ++-- p2p/{pubsub/server.go => pubsubserver.go} | 42 +++++++++++------------ p2p/server.go | 9 +++-- 5 files changed, 32 insertions(+), 33 deletions(-) rename p2p/{pubsub/server.go => pubsubserver.go} (74%) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 642a731c6b..75a5bcee81 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -22,7 +22,6 @@ import ( "crypto/elliptic" "encoding/json" "fmt" - "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "strings" "sync/atomic" @@ -125,7 +124,7 @@ type Cbft struct { evPool evidence.EvidencePool log log.Logger network *network.EngineManager - subServer *pubsub.SubServer + subServer *p2p.PubSubServer start int32 syncing int32 @@ -211,7 +210,7 @@ func New(sysConfig *params.CbftConfig, optConfig *ctypes.OptionsConfig, eventMux statQueues: make(map[common.Hash]map[string]int), messageHashCache: mapset.NewSet(), netLatencyMap: make(map[string]*list.List), - subServer: pubsub.SubServerInstance(), + subServer: p2p.SubServerInstance(), } if evPool, err := evidence.NewEvidencePool(ctx, optConfig.EvidenceDir); err == nil { diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index 59e455f047..fa0616eeda 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -18,11 +18,12 @@ package network import ( "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" ) type PubSub struct { - pubsub pubsub.SubServer + pubsub p2p.PubSubServer topicChan chan string topics []string diff --git a/p2p/pubsub/interfaces.go b/p2p/pubsub/interfaces.go index 337f2b23d2..48721cf92c 100644 --- a/p2p/pubsub/interfaces.go +++ b/p2p/pubsub/interfaces.go @@ -34,8 +34,8 @@ type ProtocolID string // Host is an object participating in a p2p network, which // implements protocols or provides services. It handles -// requests like a SubServer, and issues requests like a Client. -// It is called Host because it is both SubServer and Client (and Peer +// requests like a PubSubServer, and issues requests like a Client. +// It is called Host because it is both PubSubServer and Client (and Peer // may be confusing). type Host interface { // ID returns the (local) peer.ID associated with this Host @@ -153,7 +153,7 @@ type Stat struct { Extra map[interface{}]interface{} } -// SubServer manages all pubsub peers. +// PubSubServer manages all pubsub peers. type Server interface { Protocols() []p2p.Protocol diff --git a/p2p/pubsub/server.go b/p2p/pubsubserver.go similarity index 74% rename from p2p/pubsub/server.go rename to p2p/pubsubserver.go index 994010508e..bb52b73845 100644 --- a/p2p/pubsub/server.go +++ b/p2p/pubsubserver.go @@ -15,15 +15,15 @@ // along with the Alaya-Go library. If not, see . -package pubsub +package p2p import ( "errors" "fmt" ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/log" - "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "sync" ) @@ -40,21 +40,21 @@ const ( ) -type SubServer struct { +type PubSubServer struct { lock sync.Mutex // protects running running bool - ps *PubSub + ps *pubsub.PubSub peerMsgCh chan *ctypes.MsgInfo } var ( subServerOnce sync.Once - svr *SubServer + svr *PubSubServer ) -func SubServerInstance() *SubServer { +func SubServerInstance() *PubSubServer { subServerOnce.Do(func() { - log.Info("Init SubServer ...") + log.Info("Init PubSubServer ...") svr = NewPubSubServer() }) return svr @@ -62,7 +62,7 @@ func SubServerInstance() *SubServer { // Start starts running the server. // Servers can not be re-used after stopping. -func (srv *SubServer) Start() (err error) { +func (srv *PubSubServer) Start() (err error) { srv.lock.Lock() defer srv.lock.Unlock() if srv.running { @@ -73,56 +73,56 @@ func (srv *SubServer) Start() (err error) { } // run is the main loop of the server. -func (s *SubServer) run() { +func (s *PubSubServer) run() { } // After the node is successfully connected and the message belongs // to the cbft.pubsub protocol message, the method is called. -func (s *SubServer) Handle(p *p2p.Peer, rw p2p.MsgReadWriter) error { +func (s *PubSubServer) Handle(p *Peer, rw MsgReadWriter) error { return nil } // watchingAddTopicValidatorEvent -func (s *SubServer) watchingAddTopicValidatorEvent() { +func (s *PubSubServer) watchingAddTopicValidatorEvent() { } // watchingAddTopicValidatorEvent -func (s *SubServer) watchingRemoveTopicValidatorEvent() { +func (s *PubSubServer) watchingRemoveTopicValidatorEvent() { } // PublishMsg -func (s *SubServer) PublishMsg(topic string) error { +func (s *PubSubServer) PublishMsg(topic string) error { if topic == "" { return fmt.Errorf("topic is nil") } return nil } -func (s *SubServer) ReadTopicLoop() { +func (s *PubSubServer) ReadTopicLoop() { } // Protocol.NodeInfo() -func (s *SubServer) NodeInfo() interface{} { +func (s *PubSubServer) NodeInfo() interface{} { return nil } // Protocol.Run() -func (s *SubServer) handler(peer *p2p.Peer, rw p2p.MsgReadWriter) error { +func (s *PubSubServer) handler(peer *Peer, rw MsgReadWriter) error { return nil } //Protocols implemented the Protocols method and returned basic information about the CBFT.pubsub protocol. -func (s *SubServer) Protocols() []p2p.Protocol { - return []p2p.Protocol{ +func (s *PubSubServer) Protocols() []Protocol { + return []Protocol{ { Name: CbftPubSubProtocolName, Version: CbftPubSubProtocolVersion, Length: CbftPubSubProtocolLength, - Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + Run: func(p *Peer, rw MsgReadWriter) error { return s.handler(p, rw) }, NodeInfo: func() interface{} { @@ -135,6 +135,6 @@ func (s *SubServer) Protocols() []p2p.Protocol { } } -func NewPubSubServer() *SubServer { - return &SubServer{} +func NewPubSubServer() *PubSubServer { + return &PubSubServer{} } diff --git a/p2p/server.go b/p2p/server.go index 9ad43f8d10..e2f5b9b029 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -23,7 +23,6 @@ import ( "encoding/hex" "errors" "fmt" - "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "math/big" "net" "sort" @@ -218,7 +217,7 @@ type Server struct { consensus bool addconsensus chan *enode.Node removeconsensus chan *enode.Node - subServer *pubsub.SubServer + subServer *PubSubServer } type peerOpFunc func(map[enode.ID]*Peer) @@ -513,7 +512,7 @@ func (srv *Server) Start() (err error) { // static fields if srv.PrivateKey == nil { - return errors.New("SubServer.PrivateKey must be set to a non-nil key") + return errors.New("PubSubServer.PrivateKey must be set to a non-nil key") } if srv.newTransport == nil { srv.newTransport = newRLPX @@ -543,7 +542,7 @@ func (srv *Server) Start() (err error) { } srv.setupDialScheduler() - srv.subServer = pubsub.SubServerInstance() + srv.subServer = SubServerInstance() srv.subServer.Start() srv.loopWG.Add(1) @@ -1193,7 +1192,7 @@ func (srv *Server) runPeer(p *Peer) { // Broadcast peer drop to external subscribers. This needs to be // after the send to delpeer so subscribers have a consistent view of - // the peer set (i.e. SubServer.Peers() doesn't include the peer when the + // the peer set (i.e. PubSubServer.Peers() doesn't include the peer when the // event is received. srv.peerFeed.Send(&PeerEvent{ Type: PeerEventTypeDrop, From e979dc99356667aaa6f515109193ef10adef470d Mon Sep 17 00:00:00 2001 From: benbaley Date: Fri, 5 Nov 2021 10:29:38 +0800 Subject: [PATCH 022/299] pubsub --- consensus/cbft/cbft.go | 4 ++-- consensus/cbft/network/pubsub.go | 3 +-- node/api.go | 2 +- node/config.go | 2 +- p2p/dial.go | 2 +- p2p/peer.go | 8 ++++---- p2p/protocol.go | 2 +- p2p/pubsub/interfaces.go | 17 +---------------- p2p/server.go | 6 +++--- p2p/server_test.go | 4 ++-- p2p/simulations/adapters/inproc.go | 4 ++-- 11 files changed, 19 insertions(+), 35 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 75a5bcee81..3c83667afe 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -124,7 +124,7 @@ type Cbft struct { evPool evidence.EvidencePool log log.Logger network *network.EngineManager - subServer *p2p.PubSubServer + subServer p2p.PubSubServer start int32 syncing int32 @@ -210,7 +210,7 @@ func New(sysConfig *params.CbftConfig, optConfig *ctypes.OptionsConfig, eventMux statQueues: make(map[common.Hash]map[string]int), messageHashCache: mapset.NewSet(), netLatencyMap: make(map[string]*list.List), - subServer: p2p.SubServerInstance(), + subServer: *p2p.SubServerInstance(), } if evPool, err := evidence.NewEvidencePool(ctx, optConfig.EvidenceDir); err == nil { diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index fa0616eeda..b48327ae91 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -18,12 +18,11 @@ package network import ( "fmt" - "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" ) type PubSub struct { - pubsub p2p.PubSubServer + pubsub pubsub.PubSubServer topicChan chan string topics []string diff --git a/node/api.go b/node/api.go index 3e40de71ac..7e6acf23ae 100644 --- a/node/api.go +++ b/node/api.go @@ -77,7 +77,7 @@ func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) { } // PeerEvents creates an RPC subscription which receives peer events from the -// node's p2p.Server +// node's p2p.PubSubServer func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) { // Make sure the server is running, fail otherwise server := api.node.Server() diff --git a/node/config.go b/node/config.go index 633739205d..eae0414322 100644 --- a/node/config.go +++ b/node/config.go @@ -182,7 +182,7 @@ type Config struct { // Requests using ip address directly are not affected GraphQLVirtualHosts []string `toml:",omitempty"` - // Logger is a custom logger to use with the p2p.Server. + // Logger is a custom logger to use with the p2p.PubSubServer. Logger log.Logger `toml:",omitempty"` staticNodesWarning bool diff --git a/p2p/dial.go b/p2p/dial.go index 472bbe2a2f..a8b1a4d3e5 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -83,7 +83,7 @@ var ( errNoPort = errors.New("node does not provide TCP port") ) -// dialer creates outbound connections and submits them into Server. +// dialer creates outbound connections and submits them into PubSubServer. // Two types of peer connections can be created: // // - static dials are pre-configured connections. The dialer attempts diff --git a/p2p/peer.go b/p2p/peer.go index 332c952565..2a769ed08d 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -70,16 +70,16 @@ type protoHandshake struct { Rest []rlp.RawValue `rlp:"tail"` } -// PeerEventType is the type of peer events emitted by a p2p.Server +// PeerEventType is the type of peer events emitted by a p2p.PubSubServer type PeerEventType string const ( // PeerEventTypeAdd is the type of event emitted when a peer is added - // to a p2p.Server + // to a p2p.PubSubServer PeerEventTypeAdd PeerEventType = "add" // PeerEventTypeDrop is the type of event emitted when a peer is - // dropped from a p2p.Server + // dropped from a p2p.PubSubServer PeerEventTypeDrop PeerEventType = "drop" // PeerEventTypeMsgSend is the type of event emitted when a @@ -92,7 +92,7 @@ const ( ) // 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.PubSubServer or when a message is sent or received on a peer connection type PeerEvent struct { Type PeerEventType `json:"type"` Peer enode.ID `json:"peer"` diff --git a/p2p/protocol.go b/p2p/protocol.go index 2472834277..a0723f8836 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -55,7 +55,7 @@ type Protocol struct { // but returns nil, it is assumed that the protocol handshake is still running. PeerInfo func(id enode.ID) interface{} - // DialCandidates, if non-nil, is a way to tell Server about protocol-specific nodes + // DialCandidates, if non-nil, is a way to tell PubSubServer about protocol-specific nodes // that should be dialed. The server continuously reads nodes from the iterator and // attempts to create connections to them. DialCandidates enode.Iterator diff --git a/p2p/pubsub/interfaces.go b/p2p/pubsub/interfaces.go index 48721cf92c..517e7a2e6f 100644 --- a/p2p/pubsub/interfaces.go +++ b/p2p/pubsub/interfaces.go @@ -2,9 +2,8 @@ package pubsub import ( "context" - "time" - "github.com/AlayaNetwork/Alaya-Go/p2p" + "time" "github.com/libp2p/go-libp2p-core/connmgr" @@ -153,20 +152,6 @@ type Stat struct { Extra map[interface{}]interface{} } -// PubSubServer manages all pubsub peers. -type Server interface { - - Protocols() []p2p.Protocol - - // Start starts running the server. - // Servers can not be re-used after stopping. - Start() (err error) - - PublishMsg(topic string) error - - ReadTopicLoop() -} - // Direction represents which peer in a stream initiated a connection. type Direction int diff --git a/p2p/server.go b/p2p/server.go index e2f5b9b029..f19ac28755 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -71,7 +71,7 @@ const ( var errServerStopped = errors.New("server stopped") -// Config holds Server options. +// Config holds PubSubServer options. type Config struct { // This field must be set to a valid secp256k1 private key. PrivateKey *ecdsa.PrivateKey `toml:"-"` @@ -167,13 +167,13 @@ type Config struct { // whenever a message is sent to or received from a peer EnableMsgEvents bool - // Logger is a custom logger to use with the p2p.Server. + // Logger is a custom logger to use with the p2p.PubSubServer. Logger log.Logger `toml:",omitempty"` clock mclock.Clock } -// Server manages all peer connections. +// PubSubServer manages all peer connections. type Server struct { // Config fields may not be modified while the server is running. Config diff --git a/p2p/server_test.go b/p2p/server_test.go index 26c16a8c91..e4e2247526 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -282,7 +282,7 @@ func TestServerAtCap(t *testing.T) { t.Error("unexpected error for trusted conn @posthandshake:", err) } if !c.is(trustedConn) { - t.Error("Server did not set trusted flag") + t.Error("PubSubServer did not set trusted flag") } // Remove from trusted set and try again @@ -299,7 +299,7 @@ func TestServerAtCap(t *testing.T) { t.Error("unexpected error for trusted conn @posthandshake:", err) } if !c.is(trustedConn) { - t.Error("Server did not set trusted flag") + t.Error("PubSubServer did not set trusted flag") } } diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index fb7e031ed7..491da293e9 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -345,13 +345,13 @@ func (sn *SimNode) ServiceMap() map[string]node.Service { return services } -// Server returns the underlying p2p.Server +// Server returns the underlying p2p.PubSubServer func (sn *SimNode) Server() *p2p.Server { return sn.node.Server() } // SubscribeEvents subscribes the given channel to peer events from the -// underlying p2p.Server +// underlying p2p.PubSubServer func (sn *SimNode) SubscribeEvents(ch chan *p2p.PeerEvent) event.Subscription { srv := sn.Server() if srv == nil { From 1ceb0db4e9d9cafeb6c8a256cf0c01a04af4c685 Mon Sep 17 00:00:00 2001 From: benbaley Date: Fri, 5 Nov 2021 12:14:45 +0800 Subject: [PATCH 023/299] correct p2p.Server from p2p.PubSubServer --- eth/filters/filter_test.go | 2 +- node/api.go | 2 +- node/config.go | 2 +- p2p/dial.go | 2 +- p2p/peer.go | 8 ++++---- p2p/protocol.go | 2 +- p2p/pubsub/interfaces.go | 4 ++-- p2p/server.go | 10 +++++----- p2p/server_test.go | 4 ++-- p2p/simulations/adapters/inproc.go | 4 ++-- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index 8fe0821739..810626ec3b 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -71,7 +71,7 @@ func BenchmarkFilters(b *testing.B) { genesis := core.GenesisBlockForTesting(db, addr1, big.NewInt(1000000)) ctx := node.NewServiceContext(&node.Config{DataDir: ""}, nil, new(event.TypeMux), nil) - chain, receipts := core.GenerateChain(params.TestChainConfig, genesis, cbft.New(params.GrapeChainConfig.Cbft, nil, nil, ctx, nil), db, 100010, func(i int, gen *core.BlockGen) { + chain, receipts := core.GenerateChain(params.TestChainConfig, genesis, cbft.New(params.GrapeChainConfig.Cbft, nil, nil, ctx), db, 100010, func(i int, gen *core.BlockGen) { switch i { case 2403: receipt := makeReceipt(addr1) diff --git a/node/api.go b/node/api.go index 7e6acf23ae..3e40de71ac 100644 --- a/node/api.go +++ b/node/api.go @@ -77,7 +77,7 @@ func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) { } // PeerEvents creates an RPC subscription which receives peer events from the -// node's p2p.PubSubServer +// node's p2p.Server func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) { // Make sure the server is running, fail otherwise server := api.node.Server() diff --git a/node/config.go b/node/config.go index eae0414322..633739205d 100644 --- a/node/config.go +++ b/node/config.go @@ -182,7 +182,7 @@ type Config struct { // Requests using ip address directly are not affected GraphQLVirtualHosts []string `toml:",omitempty"` - // Logger is a custom logger to use with the p2p.PubSubServer. + // Logger is a custom logger to use with the p2p.Server. Logger log.Logger `toml:",omitempty"` staticNodesWarning bool diff --git a/p2p/dial.go b/p2p/dial.go index a8b1a4d3e5..472bbe2a2f 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -83,7 +83,7 @@ var ( errNoPort = errors.New("node does not provide TCP port") ) -// dialer creates outbound connections and submits them into PubSubServer. +// dialer creates outbound connections and submits them into Server. // Two types of peer connections can be created: // // - static dials are pre-configured connections. The dialer attempts diff --git a/p2p/peer.go b/p2p/peer.go index 2a769ed08d..332c952565 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -70,16 +70,16 @@ type protoHandshake struct { Rest []rlp.RawValue `rlp:"tail"` } -// PeerEventType is the type of peer events emitted by a p2p.PubSubServer +// PeerEventType is the type of peer events emitted by a p2p.Server type PeerEventType string const ( // PeerEventTypeAdd is the type of event emitted when a peer is added - // to a p2p.PubSubServer + // to a p2p.Server PeerEventTypeAdd PeerEventType = "add" // PeerEventTypeDrop is the type of event emitted when a peer is - // dropped from a p2p.PubSubServer + // dropped from a p2p.Server PeerEventTypeDrop PeerEventType = "drop" // PeerEventTypeMsgSend is the type of event emitted when a @@ -92,7 +92,7 @@ const ( ) // PeerEvent is an event emitted when peers are either added or dropped from -// a p2p.PubSubServer 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 PeerEventType `json:"type"` Peer enode.ID `json:"peer"` diff --git a/p2p/protocol.go b/p2p/protocol.go index a0723f8836..2472834277 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -55,7 +55,7 @@ type Protocol struct { // but returns nil, it is assumed that the protocol handshake is still running. PeerInfo func(id enode.ID) interface{} - // DialCandidates, if non-nil, is a way to tell PubSubServer about protocol-specific nodes + // DialCandidates, if non-nil, is a way to tell Server about protocol-specific nodes // that should be dialed. The server continuously reads nodes from the iterator and // attempts to create connections to them. DialCandidates enode.Iterator diff --git a/p2p/pubsub/interfaces.go b/p2p/pubsub/interfaces.go index 517e7a2e6f..6a2b419c2a 100644 --- a/p2p/pubsub/interfaces.go +++ b/p2p/pubsub/interfaces.go @@ -33,8 +33,8 @@ type ProtocolID string // Host is an object participating in a p2p network, which // implements protocols or provides services. It handles -// requests like a PubSubServer, and issues requests like a Client. -// It is called Host because it is both PubSubServer and Client (and Peer +// requests like a Server, and issues requests like a Client. +// It is called Host because it is both Server and Client (and Peer // may be confusing). type Host interface { // ID returns the (local) peer.ID associated with this Host diff --git a/p2p/server.go b/p2p/server.go index f19ac28755..1199142b49 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -71,7 +71,7 @@ const ( var errServerStopped = errors.New("server stopped") -// Config holds PubSubServer options. +// Config holds Server options. type Config struct { // This field must be set to a valid secp256k1 private key. PrivateKey *ecdsa.PrivateKey `toml:"-"` @@ -167,13 +167,13 @@ type Config struct { // whenever a message is sent to or received from a peer EnableMsgEvents bool - // Logger is a custom logger to use with the p2p.PubSubServer. + // Logger is a custom logger to use with the p2p.Server. Logger log.Logger `toml:",omitempty"` clock mclock.Clock } -// PubSubServer manages all peer connections. +// Server manages all peer connections. type Server struct { // Config fields may not be modified while the server is running. Config @@ -512,7 +512,7 @@ func (srv *Server) Start() (err error) { // static fields if srv.PrivateKey == nil { - return errors.New("PubSubServer.PrivateKey must be set to a non-nil key") + return errors.New("Server.PrivateKey must be set to a non-nil key") } if srv.newTransport == nil { srv.newTransport = newRLPX @@ -1192,7 +1192,7 @@ func (srv *Server) runPeer(p *Peer) { // Broadcast peer drop to external subscribers. This needs to be // after the send to delpeer so subscribers have a consistent view of - // the peer set (i.e. PubSubServer.Peers() doesn't include the peer when the + // the peer set (i.e. Server.Peers() doesn't include the peer when the // event is received. srv.peerFeed.Send(&PeerEvent{ Type: PeerEventTypeDrop, diff --git a/p2p/server_test.go b/p2p/server_test.go index e4e2247526..26c16a8c91 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -282,7 +282,7 @@ func TestServerAtCap(t *testing.T) { t.Error("unexpected error for trusted conn @posthandshake:", err) } if !c.is(trustedConn) { - t.Error("PubSubServer did not set trusted flag") + t.Error("Server did not set trusted flag") } // Remove from trusted set and try again @@ -299,7 +299,7 @@ func TestServerAtCap(t *testing.T) { t.Error("unexpected error for trusted conn @posthandshake:", err) } if !c.is(trustedConn) { - t.Error("PubSubServer did not set trusted flag") + t.Error("Server did not set trusted flag") } } diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index 491da293e9..fb7e031ed7 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -345,13 +345,13 @@ func (sn *SimNode) ServiceMap() map[string]node.Service { return services } -// Server returns the underlying p2p.PubSubServer +// Server returns the underlying p2p.Server func (sn *SimNode) Server() *p2p.Server { return sn.node.Server() } // SubscribeEvents subscribes the given channel to peer events from the -// underlying p2p.PubSubServer +// underlying p2p.Server func (sn *SimNode) SubscribeEvents(ch chan *p2p.PeerEvent) event.Subscription { srv := sn.Server() if srv == nil { From e63003026ef53cc0e47c444b07c414bc937d2733 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Mon, 8 Nov 2021 10:03:44 +0800 Subject: [PATCH 024/299] pubsub --- consensus/cbft/network/handler.go | 9 --- p2p/host.go | 64 +++++++++++++++++++ p2p/{swarm => }/network.go | 7 +- p2p/pubsub/interfaces.go | 9 ++- p2p/pubsub/pubsub_common.go | 19 ++---- p2p/{pubsubserver.go => pubsub_server.go} | 78 +++++++++++++++-------- p2p/server.go | 3 +- p2p/{swarm => }/stream.go | 38 ++++++++--- 8 files changed, 164 insertions(+), 63 deletions(-) create mode 100644 p2p/host.go rename p2p/{swarm => }/network.go (95%) rename p2p/{pubsubserver.go => pubsub_server.go} (69%) rename p2p/{swarm => }/stream.go (61%) diff --git a/consensus/cbft/network/handler.go b/consensus/cbft/network/handler.go index 5a4b27e7b5..b620957151 100644 --- a/consensus/cbft/network/handler.go +++ b/consensus/cbft/network/handler.go @@ -47,15 +47,6 @@ const ( // CbftProtocolLength are the number of implemented message corresponding to cbft protocol versions. CbftProtocolLength = 40 - // CbftPubSubProtocolName is protocol name of CBFT.PubSub - CbftPubSubProtocolName = "cbft.pubsub" - - // CbftPubSubProtocolVersion is protocol version of CBFT.PubSub - CbftPubSubProtocolVersion = 1 - - // CbftPubSubProtocolLength are the number of implemented message corresponding to cbft.pubsub protocol versions. - CbftPubSubProtocolLength = 10 - // DefaultMaximumMessageSize is 1mb. DefaultMaxMessageSize = 1 << 20 diff --git a/p2p/host.go b/p2p/host.go new file mode 100644 index 0000000000..530b344f21 --- /dev/null +++ b/p2p/host.go @@ -0,0 +1,64 @@ +package p2p + +import ( + "context" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" + "github.com/libp2p/go-libp2p-core/connmgr" + "sync" +) + +type Host struct { + node *enode.Node + network *Network + streams map[enode.ID]pubsub.Stream + sync.Mutex +} + +func (h *Host) ID() *enode.Node { + return h.node +} + +func (h *Host) Peerstore() pubsub.Peerstore { + return nil +} + +func (h *Host) Network() pubsub.Network { + return h.network +} + +func (h *Host) Connect(ctx context.Context, pi enode.ID) error { + return nil +} + +func (h *Host) SetStreamHandler(pid pubsub.ProtocolID, handler pubsub.StreamHandler) { + +} + +func (h *Host) SetStreamHandlerMatch(pubsub.ProtocolID, func(string) bool, pubsub.StreamHandler) { + +} + +func (h *Host) RemoveStreamHandler(pid pubsub.ProtocolID) { + +} + +func (h *Host) NewStream(ctx context.Context, nodeId enode.ID, pids ...pubsub.ProtocolID) (pubsub.Stream, error) { + h.Lock() + defer h.Unlock() + return h.streams[nodeId], nil +} + +func (h *Host) SetStream(nodeId enode.ID, stream pubsub.Stream) { + h.Lock() + defer h.Unlock() + h.streams[nodeId] = stream +} + +func (h *Host) Close() error { + return nil +} + +func (h *Host) ConnManager() connmgr.ConnManager { + return nil +} diff --git a/p2p/swarm/network.go b/p2p/network.go similarity index 95% rename from p2p/swarm/network.go rename to p2p/network.go index bf21d352e3..a662f65edb 100644 --- a/p2p/swarm/network.go +++ b/p2p/network.go @@ -1,7 +1,6 @@ -package swarm +package p2p import ( - "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "sync" @@ -9,7 +8,7 @@ import ( type Network struct { sync.RWMutex - server *p2p.Server + server *Server m map[pubsub.Notifiee]struct{} @@ -19,7 +18,7 @@ type Network struct { } } -func NewNetwork(server *p2p.Server) *Network { +func NewNetwork(server *Server) *Network { n := &Network{ RWMutex: sync.RWMutex{}, server: server, diff --git a/p2p/pubsub/interfaces.go b/p2p/pubsub/interfaces.go index 6a2b419c2a..b2a8b1e4b7 100644 --- a/p2p/pubsub/interfaces.go +++ b/p2p/pubsub/interfaces.go @@ -2,7 +2,6 @@ package pubsub import ( "context" - "github.com/AlayaNetwork/Alaya-Go/p2p" "time" "github.com/libp2p/go-libp2p-core/connmgr" @@ -118,7 +117,11 @@ type Stream interface { // Conn returns the connection this stream is part of. Conn() Conn - ReadWriter() p2p.MsgReadWriter // + Read(interface{}) error + + Write(interface{}) error + + Close() } // Conn is a connection to a remote peer. It multiplexes streams. @@ -162,4 +165,4 @@ const ( DirInbound // DirOutbound is for when the local peer initiated a connection. DirOutbound -) \ No newline at end of file +) diff --git a/p2p/pubsub/pubsub_common.go b/p2p/pubsub/pubsub_common.go index 5c96a41d2c..abadb8fc3c 100644 --- a/p2p/pubsub/pubsub_common.go +++ b/p2p/pubsub/pubsub_common.go @@ -3,8 +3,6 @@ package pubsub import ( "context" - "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" @@ -59,16 +57,12 @@ func (p *PubSub) handleNewStream(s Stream) { //r := protoio.NewDelimitedReader(s, p.maxMessageSize) for { - msg, err := s.ReadWriter().ReadMsg() - if err != nil { - log.Debug("Read pubsub peer message error", "err", err) - p.notifyPeerDead(peer.ID()) - return - } rpc := new(RPC) - if err := msg.Decode(rpc); err != nil { - log.Error("Decode message fail", "err", err) + if err := s.Read(&rpc.RPC); err != nil { + log.Debug("Read message error", "err", err) + p.notifyPeerDead(peer.ID()) + return } rpc.from = peer @@ -95,7 +89,7 @@ func (p *PubSub) notifyPeerDead(pid enode.ID) { func (p *PubSub) handleNewPeer(ctx context.Context, pid enode.ID, outgoing <-chan *RPC) { s, err := p.host.NewStream(p.ctx, pid, p.rt.Protocols()...) - if err != nil { + if err != nil || s == nil { log.Debug("opening new stream to peer: ", err, pid) select { @@ -106,6 +100,7 @@ func (p *PubSub) handleNewPeer(ctx context.Context, pid enode.ID, outgoing <-cha return } + go p.handleNewStream(s) go p.handleSendingMessages(ctx, s, outgoing) //go p.handlePeerEOF(ctx, s) select { @@ -149,7 +144,7 @@ func (p *PubSub) handleSendingMessages(ctx context.Context, s Stream, outgoing < return } - if err := p2p.Send(s.ReadWriter(), 0xff, &rpc.RPC); err != nil { + if err := s.Write(&rpc.RPC); err != nil { log.Error("Send message fail", "err", err) return } diff --git a/p2p/pubsubserver.go b/p2p/pubsub_server.go similarity index 69% rename from p2p/pubsubserver.go rename to p2p/pubsub_server.go index bb52b73845..2e6bdb9fcd 100644 --- a/p2p/pubsubserver.go +++ b/p2p/pubsub_server.go @@ -14,17 +14,17 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package p2p import ( + "context" "errors" "fmt" ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" - "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "sync" + "time" ) const ( @@ -38,26 +38,16 @@ const ( // CbftProtocolLength are the number of implemented message corresponding to cbft.pubsub protocol versions. CbftPubSubProtocolLength = 10 + // Msg code of PubSub's message + PubSubMsgCode = 0xff ) type PubSubServer struct { - lock sync.Mutex // protects running - running bool - ps *pubsub.PubSub - peerMsgCh chan *ctypes.MsgInfo -} - -var ( - subServerOnce sync.Once - svr *PubSubServer -) - -func SubServerInstance() *PubSubServer { - subServerOnce.Do(func() { - log.Info("Init PubSubServer ...") - svr = NewPubSubServer() - }) - return svr + lock sync.Mutex // protects running + running bool + ps *pubsub.PubSub + host *Host + peerMsgCh chan *ctypes.MsgInfo } // Start starts running the server. @@ -80,6 +70,7 @@ func (s *PubSubServer) run() { // After the node is successfully connected and the message belongs // to the cbft.pubsub protocol message, the method is called. func (s *PubSubServer) Handle(p *Peer, rw MsgReadWriter) error { + return nil } @@ -94,24 +85,46 @@ func (s *PubSubServer) watchingRemoveTopicValidatorEvent() { } // PublishMsg -func (s *PubSubServer) PublishMsg(topic string) error { +func (s *PubSubServer) PublishMsg(topic string) error { if topic == "" { return fmt.Errorf("topic is nil") } return nil } -func (s *PubSubServer) ReadTopicLoop() { +func (s *PubSubServer) ReadTopicLoop() { } // Protocol.NodeInfo() -func (s *PubSubServer) NodeInfo() interface{} { +func (s *PubSubServer) NodeInfo() interface{} { return nil } // Protocol.Run() -func (s *PubSubServer) handler(peer *Peer, rw MsgReadWriter) error { +func (s *PubSubServer) handler(peer *Peer, rw MsgReadWriter) error { + direction := pubsub.DirOutbound + if peer.Inbound() { + direction = pubsub.DirInbound + } + stat := pubsub.Stat{ + Direction: direction, + Opened: time.Now(), + Extra: make(map[interface{}]interface{}), + } + conn := &Conn{ + remote: peer.Node(), + stat: stat, + } + conn.streams.m = make(map[pubsub.Stream]struct{}) + stream := &Stream{ + rw: rw, + conn: conn, + } + conn.streams.m[stream] = struct{}{} + s.host.SetStream(peer.ID(), stream) + s.host.network.NotifyAll(conn) + return nil } @@ -135,6 +148,21 @@ func (s *PubSubServer) Protocols() []Protocol { } } -func NewPubSubServer() *PubSubServer { - return &PubSubServer{} +func NewPubSubServer(node *enode.Node, server *Server) *PubSubServer { + network := NewNetwork(server) + host := &Host{ + node: node, + streams: make(map[enode.ID]pubsub.Stream), + network: network, + } + pubsub, err := pubsub.NewGossipSub(context.Background(), host) + if err != nil { + panic("Failed to NewGossipSub: " + err.Error()) + } + + return &PubSubServer{ + ps: pubsub, + host: host, + peerMsgCh: make(chan *ctypes.MsgInfo), + } } diff --git a/p2p/server.go b/p2p/server.go index 1199142b49..532e05f784 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -542,7 +542,7 @@ func (srv *Server) Start() (err error) { } srv.setupDialScheduler() - srv.subServer = SubServerInstance() + srv.subServer = NewPubSubServer(srv.localnode.Node(), srv) srv.subServer.Start() srv.loopWG.Add(1) @@ -883,7 +883,6 @@ running: inboundCount++ } - // TODO pubsub network.NotifyAll } c.cont <- err diff --git a/p2p/swarm/stream.go b/p2p/stream.go similarity index 61% rename from p2p/swarm/stream.go rename to p2p/stream.go index 6ba9b08682..643ec17266 100644 --- a/p2p/swarm/stream.go +++ b/p2p/stream.go @@ -1,8 +1,8 @@ -package swarm +package p2p import ( "fmt" - "github.com/AlayaNetwork/Alaya-Go/p2p" + "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "sync/atomic" ) @@ -10,7 +10,7 @@ import ( // Stream is the stream type used by pubsub. In general type Stream struct { conn pubsub.Conn - rw p2p.MsgReadWriter + rw MsgReadWriter protocol atomic.Value } @@ -42,15 +42,37 @@ func (s *Stream) SetProtocol(p pubsub.ProtocolID) { s.protocol.Store(p) } -func (s *Stream) ReadWriter() p2p.MsgReadWriter { - return s.rw +func (s *Stream) Read(data interface{}) error { + msg, err := s.rw.ReadMsg() + if err != nil { + log.Debug("Failed to read PubSub message", "err", err) + return err + } + + if err := msg.Decode(data); err != nil { + log.Error("Decode PubSub message fail", "err", err) + return err + } + return nil +} + +func (s *Stream) Write(data interface{}) error { + if err := Send(s.rw, PubSubMsgCode, data); err != nil { + log.Error("Failed to send PubSub message", "err", err) + return err + } + return nil +} + +func (s *Stream) Close() { + } // newStream creates a new Stream. -func newStream(conn pubsub.Conn, rw p2p.MsgReadWriter, id pubsub.ProtocolID) *Stream { +func newStream(conn pubsub.Conn, rw MsgReadWriter, id pubsub.ProtocolID) *Stream { s := &Stream{ - conn: conn, - rw: rw, + conn: conn, + rw: rw, } s.SetProtocol(id) return s From 924070c6ded1a42b6259bcebad517f42b5ddecd8 Mon Sep 17 00:00:00 2001 From: benbaley Date: Mon, 8 Nov 2021 11:41:48 +0800 Subject: [PATCH 025/299] changge pubsub_server --- consensus/cbft/network/pubsub.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index b48327ae91..fa0616eeda 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -18,11 +18,12 @@ package network import ( "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" ) type PubSub struct { - pubsub pubsub.PubSubServer + pubsub p2p.PubSubServer topicChan chan string topics []string From 8eeab39d373ff1d489e492f6a65327c0f5112a0e Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Mon, 8 Nov 2021 15:45:49 +0800 Subject: [PATCH 026/299] Calculate message size --- p2p/pubsub/message/mesage.go | 55 +++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/p2p/pubsub/message/mesage.go b/p2p/pubsub/message/mesage.go index dcb10f5f7a..06547dc901 100644 --- a/p2p/pubsub/message/mesage.go +++ b/p2p/pubsub/message/mesage.go @@ -53,7 +53,16 @@ func (m *RPC_SubOpts) GetTopicid() string { } func (m *RPC_SubOpts) Size() (n int) { - return 0 + if m == nil { + return 0 + } + if m.Subscribe != nil { + n += 2 + } + if m.Topicid != nil { + n += 1 + (len(*m.Topicid)) + } + return n } type Message struct { @@ -108,7 +117,26 @@ func (m *Message) GetKey() []byte { } func (m *Message) Size() (n int) { - return 0 + if m == nil { + return 0 + } + n += len(m.From) + if m.Data != nil { + n += len(m.Data) + } + if m.Seqno != nil { + n += len(m.Seqno) + } + if m.Topic != nil { + n += len(*m.Topic) + } + if m.Signature != nil { + n += len(m.Signature) + } + if m.Key != nil { + n += len(m.Key) + } + return n } type ControlMessage struct { @@ -166,7 +194,18 @@ func (m *ControlIHave) GetMessageIDs() []string { } func (m *ControlIHave) Size() (n int) { - return 0 + if m == nil { + return 0 + } + if m.TopicID != nil { + n += len(*m.TopicID) + } + if m.MessageIDs != nil { + for _, mid := range m.MessageIDs { + n += len(mid) + } + } + return n } type ControlIWant struct { @@ -181,7 +220,15 @@ func (m *ControlIWant) GetMessageIDs() []string { } func (m *ControlIWant) Size() (n int) { - return 0 + if m == nil { + return 0 + } + if m.MessageIDs != nil { + for _, mid := range m.MessageIDs { + n += len(mid) + } + } + return n } type ControlGraft struct { From c960793ab9d3a024401998e21e00a3e2521e4bc3 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 8 Nov 2021 16:52:08 +0800 Subject: [PATCH 027/299] init discover --- go.mod | 1 + p2p/dial.go | 94 ++++-- p2p/discover.go | 229 +++++++++++++++ p2p/iterator.go | 36 +++ p2p/pubsub/discovery.go | 348 +++++++++++++++++++++++ p2p/pubsub/discovery/backoff.go | 228 +++++++++++++++ p2p/pubsub/discovery/backoffconnector.go | 96 +++++++ p2p/pubsub/discovery/discovery.go | 27 ++ p2p/pubsub/discovery/host.go | 24 ++ p2p/pubsub/discovery/options.go | 41 +++ p2p/pubsub/gossippub.go | 4 +- p2p/pubsub/interfaces.go | 2 +- p2p/pubsub/peerstore.go | 36 --- p2p/pubsub/pubsub.go | 32 ++- p2p/pubsub/topic.go | 6 +- p2p/server.go | 50 ++-- p2p/topic.go | 1 + 17 files changed, 1144 insertions(+), 111 deletions(-) create mode 100644 p2p/discover.go create mode 100644 p2p/iterator.go create mode 100644 p2p/pubsub/discovery.go create mode 100644 p2p/pubsub/discovery/backoff.go create mode 100644 p2p/pubsub/discovery/backoffconnector.go create mode 100644 p2p/pubsub/discovery/discovery.go create mode 100644 p2p/pubsub/discovery/host.go create mode 100644 p2p/pubsub/discovery/options.go delete mode 100644 p2p/pubsub/peerstore.go create mode 100644 p2p/topic.go diff --git a/go.mod b/go.mod index e7dcae3dd4..b7d90b7401 100644 --- a/go.mod +++ b/go.mod @@ -55,6 +55,7 @@ require ( github.com/peterh/liner v1.0.1-0.20170902204657-a37ad3984311 github.com/pkg/errors v0.9.1 github.com/prometheus/tsdb v0.10.0 + github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 // indirect github.com/rjeczalik/notify v0.9.1 github.com/robertkrimen/otto v0.0.0-20170205013659-6a77b7cbc37d github.com/robfig/cron v1.2.1-0.20190616124356-61d93e07d1be diff --git a/p2p/dial.go b/p2p/dial.go index 472bbe2a2f..6667b1e238 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -122,7 +122,8 @@ type dialScheduler struct { static map[enode.ID]*dialTask staticPool []*dialTask - consensus *dialedTasks + consensus map[enode.ID]*dialTask + consensusPool []*dialTask // The dial history keeps recently dialed nodes. Members of history are not dialed. history expHeap @@ -183,7 +184,7 @@ func newDialScheduler(config dialConfig, it enode.Iterator, setupFunc dialSetupF remPeerCh: make(chan *conn), addconsensus: make(chan *enode.Node), removeconsensus: make(chan *enode.Node), - consensus: NewDialedTasks(config.MaxConsensusPeers*2, nil), + consensus: make(map[enode.ID]*dialTask), } d.lastStatsLog = d.clock.Now() d.ctx, d.cancel = context.WithCancel(context.Background()) @@ -229,13 +230,14 @@ func (d *dialScheduler) removeConsensus(n *enode.Node) { } } +/* func (d *dialScheduler) removeConsensusFromQueue(n *enode.Node) { d.history.remove(string(n.ID().Bytes())) -} +}*/ -func (d *dialScheduler) initRemoveConsensusPeerFn(removeConsensusPeerFn removeConsensusPeerFn) { +/*func (d *dialScheduler) initRemoveConsensusPeerFn(removeConsensusPeerFn removeConsensusPeerFn) { d.consensus.InitRemoveConsensusPeerFn(removeConsensusPeerFn) -} +}*/ // peerAdded updates the peer set. func (d *dialScheduler) peerAdded(c *conn) { @@ -332,10 +334,28 @@ loop: } } case node := <-d.addconsensus: - log.Warn("dial adding consensus node", "node", node) - d.consensus.AddTask(&dialTask{flags: consensusDialedConn, dest: node}) + id := node.ID() + _, exists := d.consensus[id] + d.log.Trace("Adding consensus node", "id", id, "ip", node.IP(), "added", !exists) + if exists { + continue loop + } + task := newDialTask(node, consensusDialedConn) + d.consensus[id] = task + //todo if Consensus task too musch ,we may cut some + if d.checkDial(node) == nil { + d.addToConsensusPool(task) + } case node := <-d.removeconsensus: - d.consensus.RemoveTask(node) + id := node.ID() + task := d.consensus[id] + d.log.Trace("Removing consensus node", "id", id, "ok", task != nil) + if task != nil { + delete(d.consensus, id) + if task.staticPoolIndex >= 0 { + d.removeFromConsensusPool(task.staticPoolIndex) + } + } case <-historyExp: d.expireHistory() @@ -460,25 +480,6 @@ func (d *dialScheduler) startStaticDials(n int) (started int) { return started } -func (d *dialScheduler) startConsensusDials(n int) (started int) { - // Create dials for consensus nodes if they are not connected. - i := 0 - for _, t := range d.consensus.ListTask() { - if i >= n { - break - } - err := d.checkDial(t.dest) - switch err { - case errNetRestrict, errSelf: - d.consensus.RemoveTask(t.dest) - case nil: - d.startDial(t) - i++ - } - } - return i -} - // updateStaticPool attempts to move the given static dial back into staticPool. func (d *dialScheduler) updateStaticPool(id enode.ID) { task, ok := d.static[id] @@ -507,6 +508,45 @@ func (d *dialScheduler) removeFromStaticPool(idx int) { task.staticPoolIndex = -1 } +func (d *dialScheduler) startConsensusDials(n int) (started int) { + for started = 0; started < n && len(d.consensusPool) > 0; started++ { + //todo if rand here? + idx := d.rand.Intn(len(d.consensusPool)) + task := d.consensusPool[idx] + d.startDial(task) + d.removeFromConsensusPool(idx) + } + return started +} + +// updateConsensusPool attempts to move the given consensus dial back into consensusPool. +func (d *dialScheduler) updateConsensusPool(id enode.ID) { + task, ok := d.consensus[id] + if ok && task.staticPoolIndex < 0 && d.checkDial(task.dest) == nil { + d.addToConsensusPool(task) + } +} + +func (d *dialScheduler) addToConsensusPool(task *dialTask) { + if task.staticPoolIndex >= 0 { + panic("attempt to add task to consensusPool twice") + } + d.consensusPool = append(d.consensusPool, task) + task.staticPoolIndex = len(d.consensusPool) - 1 +} + +// removeFromConsensusPool removes the task at idx from consensusPool. It does that by moving the +// current last element of the pool to idx and then shortening the pool by one. +func (d *dialScheduler) removeFromConsensusPool(idx int) { + task := d.consensusPool[idx] + end := len(d.consensusPool) - 1 + d.consensusPool[idx] = d.consensusPool[end] + d.consensusPool[idx].staticPoolIndex = idx + d.consensusPool[end] = nil + d.consensusPool = d.consensusPool[:end] + task.staticPoolIndex = -1 +} + // startDial runs the given dial task in a separate goroutine. func (d *dialScheduler) startDial(task *dialTask) { d.log.Trace("Starting p2p dial", "id", task.dest.ID(), "ip", task.dest.IP(), "flag", task.flags) diff --git a/p2p/discover.go b/p2p/discover.go new file mode 100644 index 0000000000..5205c82c0f --- /dev/null +++ b/p2p/discover.go @@ -0,0 +1,229 @@ +package p2p + +import ( + "bytes" + "context" + "sync" + "time" + + "github.com/pkg/errors" + + "github.com/prysmaticlabs/go-bitfield" + + "github.com/prometheus/common/log" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" +) + +// subscribe to a static subnet with the given topic and index.A given validator and subscription handler is +// used to handle messages from the subnet. The base protobuf message is used to initialize new messages for decoding. +func (srv *Server) subscribeTopic(ctx context.Context, topic string) { + + ticker := time.NewTicker(time.Second * 1) + + go func() { + for { + select { + case <-ctx.Done(): + ticker.Stop() + return + case <-ticker.C: + if srv.running { + continue + } + + // Check there are enough peers + if !srv.validPeersExist(topic) { + log.Debugf("No peers found subscribed to attestation gossip subnet with " + + "committee index %d. Searching network for peers subscribed to the subnet.") + _, err := srv.FindPeersWithTopic( + ctx, + topic, + srv.Config.MinimumPeersPerTopic, + ) + if err != nil { + log.Error("Could not search for peers", "err", err) + return + } + } + + } + } + }() +} + +// find if we have peers who are subscribed to the same subnet +func (srv *Server) validPeersExist(subnetTopic string) bool { + numOfPeers := srv.pubsub.ListPeers(subnetTopic) + return len(numOfPeers) >= srv.Config.MinimumPeersPerTopic +} + +func (srv *Server) topicToIdx(topic string) uint64 { + return 0 +} + +// FindPeersWithTopic performs a network search for peers +// subscribed to a particular subnet. Then we try to connect +// with those peers. This method will block until the required amount of +// peers are found, the method only exits in the event of context timeouts. +func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, threshold int) (bool, error) { + + if srv.DiscV5 == nil { + // return if discovery isn't set + return false, nil + } + + //topic += s.Encoding().ProtocolSuffix() + iterator := srv.DiscV5.RandomNodes() + iterator = filterNodes(ctx, iterator, srv.filterPeerForTopic(srv.topicToIdx(topic))) + + currNum := len(srv.pubsub.ListPeers(topic)) + wg := new(sync.WaitGroup) + for { + if err := ctx.Err(); err != nil { + return false, err + } + if currNum >= threshold { + break + } + nodes := enode.ReadNodes(iterator, int(srv.Config.MinimumPeersInTopicSearch)) + for _, node := range nodes { + srv.AddConsensusPeer(node) + } + // Wait for all dials to be completed. + wg.Wait() + currNum = len(srv.pubsub.ListPeers(topic)) + } + return true, nil +} + +// returns a method with filters peers specifically for a particular attestation subnet. +func (srv *Server) filterPeerForTopic(index uint64) func(node *enode.Node) bool { + return func(node *enode.Node) bool { + if !srv.filterPeer(node) { + return false + } + topics, err := srv.getTopics(node.Record()) + if err != nil { + return false + } + indExists := false + for _, comIdx := range topics { + if comIdx == index { + indExists = true + break + } + } + return indExists + } +} + +// filterPeer validates each node that we retrieve from our dht. We +// try to ascertain that the peer can be a valid protocol peer. +// Validity Conditions: +// 1) The local node is still actively looking for peers to +// connect to. +// 2) Peer has a valid IP and TCP port set in their enr. +// 3) Peer hasn't been marked as 'bad' +// 4) Peer is not currently active or connected. +// 5) Peer is ready to receive incoming connections. +// 6) Peer's fork digest in their ENR matches that of +// our localnodes. +func (srv *Server) filterPeer(node *enode.Node) bool { + // Ignore nil node entries passed in. + if node == nil { + return false + } + // ignore nodes with no ip address stored. + if node.IP() == nil { + return false + } + // do not dial nodes with their tcp ports not set + if err := node.Record().Load(enr.WithEntry("tcp", new(enr.TCP))); err != nil { + if !enr.IsNotFound(err) { + log.Error("Could not retrieve tcp port", err) + } + return false + } + return true +} + +// RefreshENR uses an epoch to refresh the enr entry for our node +// with the tracked committee ids for the epoch, allowing our node +// to be dynamically discoverable by others given our tracked committee ids. +func (srv *Server) RefreshENR() { + // return early if discv5 isnt running + if srv.DiscV5 == nil { + return + } + bitTopics := bitfield.NewBitvector64() + committees := srv.GetAllTopics() + for _, idx := range committees { + bitTopics.SetBitAt(idx, true) + } + currentBitTopics, err := getBitvector(srv.Config.TopicKey, srv.DiscV5.Self().Record()) + if err != nil { + log.Errorf("Could not retrieve att bitfield: %v", err) + return + } + + if bytes.Equal(bitTopics, currentBitTopics) { + // return early if bitfields haven't changed + return + } + srv.updateTopicRecord(bitTopics) + // ping all peers to inform them of new metadata + //todo not sure ping here + //s.pingPeers() +} + +func (srv *Server) updateTopicRecord(bitVAtt bitfield.Bitvector64) { + entry := enr.WithEntry(srv.Config.TopicKey, &bitVAtt) + srv.DiscV5.LocalNode().Set(entry) +} + +func (srv *Server) GetAllTopics() []uint64 { + return nil +} + +// Reads the attestation subnets entry from a node's ENR and determines +// the committee indices of the attestation subnets the node is subscribed to. +func (srv *Server) getTopics(record *enr.Record) ([]uint64, error) { + bitV, err := getBitvector(srv.Config.TopicKey, record) + if err != nil { + return nil, err + } + if len(bitV) != byteCount(int(srv.Config.TopicCount)) { + return []uint64{}, errors.Errorf("invalid bitvector provided, it has a size of %d", len(bitV)) + } + var topicIdxs []uint64 + for i := uint64(0); i < srv.Config.TopicCount; i++ { + if bitV.BitAt(i) { + topicIdxs = append(topicIdxs, i) + } + } + return topicIdxs, nil +} + +// Parses the attestation subnets ENR entry in a node and extracts its value +// as a bitvector for further manipulation. +func getBitvector(key string, record *enr.Record) (bitfield.Bitvector64, error) { + bitV := bitfield.NewBitvector64() + entry := enr.WithEntry(key, &bitV) + err := record.Load(entry) + if err != nil { + return nil, err + } + return bitV, nil +} + +// Determines the number of bytes that are used +// to represent the provided number of bits. +func byteCount(bitCount int) int { + numOfBytes := bitCount / 8 + if bitCount%8 != 0 { + numOfBytes++ + } + return numOfBytes +} diff --git a/p2p/iterator.go b/p2p/iterator.go new file mode 100644 index 0000000000..ce2da8a2a8 --- /dev/null +++ b/p2p/iterator.go @@ -0,0 +1,36 @@ +package p2p + +import ( + "context" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +// filterNodes wraps an iterator such that Next only returns nodes for which +// the 'check' function returns true. This custom implementation also +// checks for context deadlines so that in the event the parent context has +// expired, we do exit from the search rather than perform more network +// lookups for additional peers. +func filterNodes(ctx context.Context, it enode.Iterator, check func(*enode.Node) bool) enode.Iterator { + return &filterIter{ctx, it, check} +} + +type filterIter struct { + context.Context + enode.Iterator + check func(*enode.Node) bool +} + +// Next looks up for the next valid node according to our +// filter criteria. +func (f *filterIter) Next() bool { + for f.Iterator.Next() { + if f.Context.Err() != nil { + return false + } + if f.check(f.Node()) { + return true + } + } + return false +} diff --git a/p2p/pubsub/discovery.go b/p2p/pubsub/discovery.go new file mode 100644 index 0000000000..fe3d3db762 --- /dev/null +++ b/p2p/pubsub/discovery.go @@ -0,0 +1,348 @@ +package pubsub + +import ( + "context" + "math/rand" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + "github.com/prometheus/common/log" + + discimpl "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/discovery" +) + +var ( + // poll interval + + // DiscoveryPollInitialDelay is how long the discovery system waits after it first starts before polling + DiscoveryPollInitialDelay = 0 * time.Millisecond + // DiscoveryPollInterval is approximately how long the discovery system waits in between checks for whether the + // more peers are needed for any topic + DiscoveryPollInterval = 1 * time.Second +) + +// interval at which to retry advertisements when they fail. +const discoveryAdvertiseRetryInterval = 2 * time.Minute + +type DiscoverOpt func(*discoverOptions) error + +type discoverOptions struct { + connFactory BackoffConnectorFactory + opts []discimpl.Option +} + +func defaultDiscoverOptions() *discoverOptions { + rngSrc := rand.NewSource(rand.Int63()) + minBackoff, maxBackoff := time.Second*10, time.Hour + cacheSize := 100 + dialTimeout := time.Minute * 2 + discoverOpts := &discoverOptions{ + connFactory: func(host Host) (*discimpl.BackoffConnector, error) { + backoff := discimpl.NewExponentialBackoff(minBackoff, maxBackoff, discimpl.FullJitter, time.Second, 5.0, 0, rand.New(rngSrc)) + return discimpl.NewBackoffConnector(host, cacheSize, dialTimeout, backoff) + }, + } + + return discoverOpts +} + +// discover represents the discovery pipeline. +// The discovery pipeline handles advertising and discovery of peers +type discover struct { + p *PubSub + + // discovery assists in discovering and advertising peers for a topic + discovery discimpl.Discovery + + // advertising tracks which topics are being advertised + advertising map[string]context.CancelFunc + + // discoverQ handles continuing peer discovery + discoverQ chan *discoverReq + + // ongoing tracks ongoing discovery requests + ongoing map[string]struct{} + + // done handles completion of a discovery request + done chan string + + // connector handles connecting to new peers found via discovery + connector *discimpl.BackoffConnector + + // options are the set of options to be used to complete struct construction in Start + options *discoverOptions +} + +// MinTopicSize returns a function that checks if a router is ready for publishing based on the topic size. +// The router ultimately decides the whether it is ready or not, the given size is just a suggestion. +func MinTopicSize(size int) RouterReady { + return func(rt PubSubRouter, topic string) (bool, error) { + return rt.EnoughPeers(topic, size), nil + } +} + +// Start attaches the discovery pipeline to a pubsub instance, initializes discovery and starts event loop +func (d *discover) Start(p *PubSub, opts ...DiscoverOpt) error { + if d.discovery == nil || p == nil { + return nil + } + + d.p = p + d.advertising = make(map[string]context.CancelFunc) + d.discoverQ = make(chan *discoverReq, 32) + d.ongoing = make(map[string]struct{}) + d.done = make(chan string) + + conn, err := d.options.connFactory(p.host) + if err != nil { + return err + } + d.connector = conn + + go d.discoverLoop() + go d.pollTimer() + + return nil +} + +func (d *discover) pollTimer() { + select { + case <-time.After(DiscoveryPollInitialDelay): + case <-d.p.ctx.Done(): + return + } + + select { + case d.p.eval <- d.requestDiscovery: + case <-d.p.ctx.Done(): + return + } + + ticker := time.NewTicker(DiscoveryPollInterval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + select { + case d.p.eval <- d.requestDiscovery: + case <-d.p.ctx.Done(): + return + } + case <-d.p.ctx.Done(): + return + } + } +} + +func (d *discover) requestDiscovery() { + for t := range d.p.myTopics { + if !d.p.rt.EnoughPeers(t, 0) { + d.discoverQ <- &discoverReq{topic: t, done: make(chan struct{}, 1)} + } + } +} + +func (d *discover) discoverLoop() { + for { + select { + case discover := <-d.discoverQ: + topic := discover.topic + + if _, ok := d.ongoing[topic]; ok { + discover.done <- struct{}{} + continue + } + + d.ongoing[topic] = struct{}{} + + go func() { + d.handleDiscovery(d.p.ctx, topic, discover.opts) + select { + case d.done <- topic: + case <-d.p.ctx.Done(): + } + discover.done <- struct{}{} + }() + case topic := <-d.done: + delete(d.ongoing, topic) + case <-d.p.ctx.Done(): + return + } + } +} + +// Advertise advertises this node's interest in a topic to a discovery service. Advertise is not thread-safe. +func (d *discover) Advertise(topic string) { + if d.discovery == nil { + return + } + + advertisingCtx, cancel := context.WithCancel(d.p.ctx) + + if _, ok := d.advertising[topic]; ok { + cancel() + return + } + d.advertising[topic] = cancel + + go func() { + next, err := d.discovery.Advertise(advertisingCtx, topic) + if err != nil { + log.Warnf("bootstrap: error providing rendezvous for %s: %s", topic, err.Error()) + if next == 0 { + next = discoveryAdvertiseRetryInterval + } + } + + t := time.NewTimer(next) + defer t.Stop() + + for advertisingCtx.Err() == nil { + select { + case <-t.C: + next, err = d.discovery.Advertise(advertisingCtx, topic) + if err != nil { + log.Warnf("bootstrap: error providing rendezvous for %s: %s", topic, err.Error()) + if next == 0 { + next = discoveryAdvertiseRetryInterval + } + } + t.Reset(next) + case <-advertisingCtx.Done(): + return + } + } + }() +} + +// StopAdvertise stops advertising this node's interest in a topic. StopAdvertise is not thread-safe. +func (d *discover) StopAdvertise(topic string) { + if d.discovery == nil { + return + } + + if advertiseCancel, ok := d.advertising[topic]; ok { + advertiseCancel() + delete(d.advertising, topic) + } +} + +// Discover searches for additional peers interested in a given topic +func (d *discover) Discover(topic string, opts ...discimpl.Option) { + if d.discovery == nil { + return + } + + d.discoverQ <- &discoverReq{topic, opts, make(chan struct{}, 1)} +} + +// Bootstrap attempts to bootstrap to a given topic. Returns true if bootstrapped successfully, false otherwise. +func (d *discover) Bootstrap(ctx context.Context, topic string, ready RouterReady, opts ...discimpl.Option) bool { + if d.discovery == nil { + return true + } + + t := time.NewTimer(time.Hour) + if !t.Stop() { + <-t.C + } + defer t.Stop() + + for { + // Check if ready for publishing + bootstrapped := make(chan bool, 1) + select { + case d.p.eval <- func() { + done, _ := ready(d.p.rt, topic) + bootstrapped <- done + }: + if <-bootstrapped { + return true + } + case <-d.p.ctx.Done(): + return false + case <-ctx.Done(): + return false + } + + // If not ready discover more peers + disc := &discoverReq{topic, opts, make(chan struct{}, 1)} + select { + case d.discoverQ <- disc: + case <-d.p.ctx.Done(): + return false + case <-ctx.Done(): + return false + } + + select { + case <-disc.done: + case <-d.p.ctx.Done(): + return false + case <-ctx.Done(): + return false + } + + t.Reset(time.Millisecond * 100) + select { + case <-t.C: + case <-d.p.ctx.Done(): + return false + case <-ctx.Done(): + return false + } + } +} + +func (d *discover) handleDiscovery(ctx context.Context, topic string, opts []discimpl.Option) { + discoverCtx, cancel := context.WithTimeout(ctx, time.Second*10) + defer cancel() + + peerCh, err := d.discovery.FindPeers(discoverCtx, topic, opts...) + if err != nil { + log.Debugf("error finding peers for topic %s: %v", topic, err) + return + } + + d.connector.Connect(ctx, peerCh) +} + +type discoverReq struct { + topic string + opts []discimpl.Option + done chan struct{} +} + +type pubSubDiscovery struct { + discimpl.Discovery + opts []discimpl.Option +} + +func (d *pubSubDiscovery) Advertise(ctx context.Context, ns string, opts ...discimpl.Option) (time.Duration, error) { + return d.Discovery.Advertise(ctx, "floodsub:"+ns, append(opts, d.opts...)...) +} + +func (d *pubSubDiscovery) FindPeers(ctx context.Context, ns string, opts ...discimpl.Option) (<-chan *enode.Node, error) { + return d.Discovery.FindPeers(ctx, "floodsub:"+ns, append(opts, d.opts...)...) +} + +// WithDiscoveryOpts passes libp2p Discovery options into the PubSub discovery subsystem +func WithDiscoveryOpts(opts ...discimpl.Option) DiscoverOpt { + return func(d *discoverOptions) error { + d.opts = opts + return nil + } +} + +// BackoffConnectorFactory creates a BackoffConnector that is attached to a given host +type BackoffConnectorFactory func(host Host) (*discimpl.BackoffConnector, error) + +// WithDiscoverConnector adds a custom connector that deals with how the discovery subsystem connects to peers +func WithDiscoverConnector(connFactory BackoffConnectorFactory) DiscoverOpt { + return func(d *discoverOptions) error { + d.connFactory = connFactory + return nil + } +} diff --git a/p2p/pubsub/discovery/backoff.go b/p2p/pubsub/discovery/backoff.go new file mode 100644 index 0000000000..384a95cf6f --- /dev/null +++ b/p2p/pubsub/discovery/backoff.go @@ -0,0 +1,228 @@ +package discovery + +import ( + "math" + "math/rand" + "sync" + "time" +) + +type BackoffFactory func() BackoffStrategy + +// BackoffStrategy describes how backoff will be implemented. BackoffStratgies are stateful. +type BackoffStrategy interface { + // Delay calculates how long the next backoff duration should be, given the prior calls to Delay + Delay() time.Duration + // Reset clears the internal state of the BackoffStrategy + Reset() +} + +// Jitter implementations taken roughly from https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ + +// Jitter must return a duration between min and max. Min must be lower than, or equal to, max. +type Jitter func(duration, min, max time.Duration, rng *rand.Rand) time.Duration + +// FullJitter returns a random number uniformly chose from the range [min, boundedDur]. +// boundedDur is the duration bounded between min and max. +func FullJitter(duration, min, max time.Duration, rng *rand.Rand) time.Duration { + if duration <= min { + return min + } + + normalizedDur := boundedDuration(duration, min, max) - min + + return boundedDuration(time.Duration(rng.Int63n(int64(normalizedDur)))+min, min, max) +} + +// NoJitter returns the duration bounded between min and max +func NoJitter(duration, min, max time.Duration, rng *rand.Rand) time.Duration { + return boundedDuration(duration, min, max) +} + +type randomizedBackoff struct { + min time.Duration + max time.Duration + rng *rand.Rand +} + +func (b *randomizedBackoff) BoundedDelay(duration time.Duration) time.Duration { + return boundedDuration(duration, b.min, b.max) +} + +func boundedDuration(d, min, max time.Duration) time.Duration { + if d < min { + return min + } + if d > max { + return max + } + return d +} + +type attemptBackoff struct { + attempt int + jitter Jitter + randomizedBackoff +} + +func (b *attemptBackoff) Reset() { + b.attempt = 0 +} + +// NewFixedBackoff creates a BackoffFactory with a constant backoff duration +func NewFixedBackoff(delay time.Duration) BackoffFactory { + return func() BackoffStrategy { + return &fixedBackoff{delay: delay} + } +} + +type fixedBackoff struct { + delay time.Duration +} + +func (b *fixedBackoff) Delay() time.Duration { + return b.delay +} + +func (b *fixedBackoff) Reset() {} + +// NewPolynomialBackoff creates a BackoffFactory with backoff of the form c0*x^0, c1*x^1, ...cn*x^n where x is the attempt number +// jitter is the function for adding randomness around the backoff +// timeUnits are the units of time the polynomial is evaluated in +// polyCoefs is the array of polynomial coefficients from [c0, c1, ... cn] +func NewPolynomialBackoff(min, max time.Duration, jitter Jitter, + timeUnits time.Duration, polyCoefs []float64, rngSrc rand.Source) BackoffFactory { + rng := rand.New(&lockedSource{src: rngSrc}) + return func() BackoffStrategy { + return &polynomialBackoff{ + attemptBackoff: attemptBackoff{ + randomizedBackoff: randomizedBackoff{ + min: min, + max: max, + rng: rng, + }, + jitter: jitter, + }, + timeUnits: timeUnits, + poly: polyCoefs, + } + } +} + +type polynomialBackoff struct { + attemptBackoff + timeUnits time.Duration + poly []float64 +} + +func (b *polynomialBackoff) Delay() time.Duration { + var polySum float64 + switch len(b.poly) { + case 0: + return 0 + case 1: + polySum = b.poly[0] + default: + polySum = b.poly[0] + exp := 1 + attempt := b.attempt + b.attempt++ + + for _, c := range b.poly[1:] { + exp *= attempt + polySum += float64(exp) * c + } + } + return b.jitter(time.Duration(float64(b.timeUnits)*polySum), b.min, b.max, b.rng) +} + +// NewExponentialBackoff creates a BackoffFactory with backoff of the form base^x + offset where x is the attempt number +// jitter is the function for adding randomness around the backoff +// timeUnits are the units of time the base^x is evaluated in +func NewExponentialBackoff(min, max time.Duration, jitter Jitter, + timeUnits time.Duration, base float64, offset time.Duration, rngSrc rand.Source) BackoffFactory { + rng := rand.New(&lockedSource{src: rngSrc}) + return func() BackoffStrategy { + return &exponentialBackoff{ + attemptBackoff: attemptBackoff{ + randomizedBackoff: randomizedBackoff{ + min: min, + max: max, + rng: rng, + }, + jitter: jitter, + }, + timeUnits: timeUnits, + base: base, + offset: offset, + } + } +} + +type exponentialBackoff struct { + attemptBackoff + timeUnits time.Duration + base float64 + offset time.Duration +} + +func (b *exponentialBackoff) Delay() time.Duration { + attempt := b.attempt + b.attempt++ + return b.jitter( + time.Duration(math.Pow(b.base, float64(attempt))*float64(b.timeUnits))+b.offset, b.min, b.max, b.rng) +} + +// NewExponentialDecorrelatedJitter creates a BackoffFactory with backoff of the roughly of the form base^x where x is the attempt number. +// Delays start at the minimum duration and after each attempt delay = rand(min, delay * base), bounded by the max +// See https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ for more information +func NewExponentialDecorrelatedJitter(min, max time.Duration, base float64, rngSrc rand.Source) BackoffFactory { + rng := rand.New(&lockedSource{src: rngSrc}) + return func() BackoffStrategy { + return &exponentialDecorrelatedJitter{ + randomizedBackoff: randomizedBackoff{ + min: min, + max: max, + rng: rng, + }, + base: base, + } + } +} + +type exponentialDecorrelatedJitter struct { + randomizedBackoff + base float64 + lastDelay time.Duration +} + +func (b *exponentialDecorrelatedJitter) Delay() time.Duration { + if b.lastDelay < b.min { + b.lastDelay = b.min + return b.lastDelay + } + + nextMax := int64(float64(b.lastDelay) * b.base) + b.lastDelay = boundedDuration(time.Duration(b.rng.Int63n(nextMax-int64(b.min)))+b.min, b.min, b.max) + return b.lastDelay +} + +func (b *exponentialDecorrelatedJitter) Reset() { b.lastDelay = 0 } + +type lockedSource struct { + lk sync.Mutex + src rand.Source +} + +func (r *lockedSource) Int63() (n int64) { + r.lk.Lock() + n = r.src.Int63() + r.lk.Unlock() + return +} + +func (r *lockedSource) Seed(seed int64) { + r.lk.Lock() + r.src.Seed(seed) + r.lk.Unlock() +} diff --git a/p2p/pubsub/discovery/backoffconnector.go b/p2p/pubsub/discovery/backoffconnector.go new file mode 100644 index 0000000000..3b2ffba9b6 --- /dev/null +++ b/p2p/pubsub/discovery/backoffconnector.go @@ -0,0 +1,96 @@ +package discovery + +import ( + "context" + "sync" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + + lru "github.com/hashicorp/golang-lru" + "github.com/prometheus/common/log" +) + +// BackoffConnector is a utility to connect to peers, but only if we have not recently tried connecting to them already +type BackoffConnector struct { + cache *lru.TwoQueueCache + host Host + connTryDur time.Duration + backoff BackoffFactory + mux sync.Mutex +} + +// NewBackoffConnector creates a utility to connect to peers, but only if we have not recently tried connecting to them already +// cacheSize is the size of a TwoQueueCache +// connectionTryDuration is how long we attempt to connect to a peer before giving up +// backoff describes the strategy used to decide how long to backoff after previously attempting to connect to a peer +func NewBackoffConnector(h Host, cacheSize int, connectionTryDuration time.Duration, backoff BackoffFactory) (*BackoffConnector, error) { + cache, err := lru.New2Q(cacheSize) + if err != nil { + return nil, err + } + + return &BackoffConnector{ + cache: cache, + host: h, + connTryDur: connectionTryDuration, + backoff: backoff, + }, nil +} + +type connCacheData struct { + nextTry time.Time + strat BackoffStrategy +} + +// Connect attempts to connect to the peers passed in by peerCh. Will not connect to peers if they are within the backoff period. +// As Connect will attempt to dial peers as soon as it learns about them, the caller should try to keep the number, +// and rate, of inbound peers manageable. +func (c *BackoffConnector) Connect(ctx context.Context, peerCh <-chan *enode.Node) { + for { + select { + case pi, ok := <-peerCh: + if !ok { + return + } + + if pi.ID() == c.host.ID().ID() /*|| pi.ID == ""*/ { + continue + } + + c.mux.Lock() + val, ok := c.cache.Get(pi) + var cachedPeer *connCacheData + if ok { + tv := val.(*connCacheData) + now := time.Now() + if now.Before(tv.nextTry) { + c.mux.Unlock() + continue + } + + tv.nextTry = now.Add(tv.strat.Delay()) + } else { + cachedPeer = &connCacheData{strat: c.backoff()} + cachedPeer.nextTry = time.Now().Add(cachedPeer.strat.Delay()) + c.cache.Add(pi, cachedPeer) + } + c.mux.Unlock() + + go func(pi enode.ID) { + ctx, cancel := context.WithTimeout(ctx, c.connTryDur) + defer cancel() + + err := c.host.Connect(ctx, pi) + if err != nil { + log.Debugf("Error connecting to pubsub peer %s: %s", pi, err.Error()) + return + } + }(pi.ID()) + + case <-ctx.Done(): + log.Infof("discovery: backoff connector context error %v", ctx.Err()) + return + } + } +} diff --git a/p2p/pubsub/discovery/discovery.go b/p2p/pubsub/discovery/discovery.go new file mode 100644 index 0000000000..4c2c2f602d --- /dev/null +++ b/p2p/pubsub/discovery/discovery.go @@ -0,0 +1,27 @@ +// Package discovery provides service advertisement and peer discovery interfaces for libp2p. +package discovery + +import ( + "context" + "time" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +// Advertiser is an interface for advertising services +type Advertiser interface { + // Advertise advertises a service + Advertise(ctx context.Context, ns string, opts ...Option) (time.Duration, error) +} + +// Discoverer is an interface for peer discovery +type Discoverer interface { + // FindPeers discovers peers providing a service + FindPeers(ctx context.Context, ns string, opts ...Option) (<-chan *enode.Node, error) +} + +// Discovery is an interface that combines service advertisement and peer discovery +type Discovery interface { + Advertiser + Discoverer +} diff --git a/p2p/pubsub/discovery/host.go b/p2p/pubsub/discovery/host.go new file mode 100644 index 0000000000..078761797b --- /dev/null +++ b/p2p/pubsub/discovery/host.go @@ -0,0 +1,24 @@ +package discovery + +import ( + "context" + + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +) + +// Host is an object participating in a p2p network, which +// implements protocols or provides services. It handles +// requests like a Server, and issues requests like a Client. +// It is called Host because it is both Server and Client (and Peer +// may be confusing). +type Host interface { + // ID returns the (local) peer.ID associated with this Host + ID() *enode.Node + + // Connect ensures there is a connection between this host and the peer with + // given peer.ID. Connect will absorb the addresses in pi into its internal + // peerstore. If there is not an active connection, Connect will issue a + // h.Network.Dial, and block until a connection is open, or an error is + // returned. // TODO: Relay + NAT. + Connect(ctx context.Context, pi enode.ID) error +} diff --git a/p2p/pubsub/discovery/options.go b/p2p/pubsub/discovery/options.go new file mode 100644 index 0000000000..7b28305268 --- /dev/null +++ b/p2p/pubsub/discovery/options.go @@ -0,0 +1,41 @@ +package discovery + +import "time" + +// DiscoveryOpt is a single discovery option. +type Option func(opts *Options) error + +// DiscoveryOpts is a set of discovery options. +type Options struct { + Ttl time.Duration + Limit int + + // Other (implementation-specific) options + Other map[interface{}]interface{} +} + +// Apply applies the given options to this DiscoveryOpts +func (opts *Options) Apply(options ...Option) error { + for _, o := range options { + if err := o(opts); err != nil { + return err + } + } + return nil +} + +// TTL is an option that provides a hint for the duration of an advertisement +func TTL(ttl time.Duration) Option { + return func(opts *Options) error { + opts.Ttl = ttl + return nil + } +} + +// Limit is an option that provides an upper bound on the peer count for discovery +func Limit(limit int) Option { + return func(opts *Options) error { + opts.Limit = limit + return nil + } +} diff --git a/p2p/pubsub/gossippub.go b/p2p/pubsub/gossippub.go index fe934745f2..922a3817ab 100644 --- a/p2p/pubsub/gossippub.go +++ b/p2p/pubsub/gossippub.go @@ -321,7 +321,7 @@ func WithPeerExchange(doPX bool) Option { // message unconditionally forwarded to them. The router will maintain open connections // to these peers. Note that the peering agreement should be reciprocal with direct peers // symmetrically configured at both ends. -func WithDirectPeers(pis []*enode.Node) Option { +/*func WithDirectPeers(pis []*enode.Node) Option { return func(ps *PubSub) error { gs, ok := ps.rt.(*GossipSubRouter) if !ok { @@ -342,7 +342,7 @@ func WithDirectPeers(pis []*enode.Node) Option { return nil } -} +}*/ // WithDirectConnectTicks is a gossipsub router option that sets the number of // heartbeat ticks between attempting to reconnect direct peers that are not diff --git a/p2p/pubsub/interfaces.go b/p2p/pubsub/interfaces.go index 90fe86191c..4936da3fb4 100644 --- a/p2p/pubsub/interfaces.go +++ b/p2p/pubsub/interfaces.go @@ -43,7 +43,7 @@ type Host interface { ID() *enode.Node // Peerstore returns the Host's repository of Peer Addresses and Keys. - Peerstore() Peerstore + //Peerstore() Peerstore // Networks returns the Network interface of the Host Network() Network diff --git a/p2p/pubsub/peerstore.go b/p2p/pubsub/peerstore.go deleted file mode 100644 index a5535df97c..0000000000 --- a/p2p/pubsub/peerstore.go +++ /dev/null @@ -1,36 +0,0 @@ -package pubsub - -import ( - "io" - "math" - "time" - - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" -) - -// Permanent TTLs (distinct so we can distinguish between them, constant as they -// are, in fact, permanent) -const ( - // PermanentAddrTTL is the ttl for a "permanent address" (e.g. bootstrap nodes). - PermanentAddrTTL = math.MaxInt64 - iota - - // ConnectedAddrTTL is the ttl used for the addresses of a peer to whom - // we're connected directly. This is basically permanent, as we will - // clear them + re-add under a TempAddrTTL after disconnecting. - ConnectedAddrTTL -) - -// Peerstore provides a threadsafe store of Peer related -// information. -type Peerstore interface { - io.Closer - - // AddAddrs gives this AddrBook addresses to use, with a given ttl - // (time-to-live), after which the address is no longer valid. - // If the manager has a longer TTL, the operation is a no-op for that address - AddAddrs(p enode.ID, ttl time.Duration) - - // PrivKey returns the private key of a peer, if known. Generally this might only be our own - // private key - //PrivKey(id enode.ID) *ecdsa.PrivateKey -} diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index fb505363e4..58c0fc347f 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -11,6 +11,8 @@ import ( "sync/atomic" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/discovery" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" @@ -48,7 +50,7 @@ type PubSub struct { val *validation - //disc *discover + disc *discover tracer *pubsubTracer @@ -230,12 +232,12 @@ type Option func(*PubSub) error // NewPubSub returns a new PubSub management object. func NewPubSub(ctx context.Context, h Host, rt PubSubRouter, opts ...Option) (*PubSub, error) { ps := &PubSub{ - host: h, - ctx: ctx, - rt: rt, - val: newValidation(), - peerFilter: DefaultPeerFilter, - // disc: &discover{}, + host: h, + ctx: ctx, + rt: rt, + val: newValidation(), + peerFilter: DefaultPeerFilter, + disc: &discover{}, maxMessageSize: DefaultMaxMessageSize, peerOutboundQueueSize: 32, signID: h.ID().ID(), @@ -290,9 +292,9 @@ func NewPubSub(ctx context.Context, h Host, rt PubSubRouter, opts ...Option) (*P }*/ } - /* if err := ps.disc.Start(ps); err != nil { + if err := ps.disc.Start(ps); err != nil { return nil, err - }*/ + } rt.Attach(ps) @@ -431,7 +433,7 @@ func WithBlacklist(b Blacklist) Option { } // WithDiscovery provides a discovery mechanism used to bootstrap and provide peers into PubSub -/*func WithDiscovery(d discovery.Discovery, opts ...DiscoverOpt) Option { +func WithDiscovery(d discovery.Discovery, opts ...DiscoverOpt) Option { return func(p *PubSub) error { discoverOpts := defaultDiscoverOptions() for _, opt := range opts { @@ -445,7 +447,7 @@ func WithBlacklist(b Blacklist) Option { p.disc.options = discoverOpts return nil } -}*/ +} // WithEventTracer provides a tracer for the pubsub system func WithEventTracer(tracer EventTracer) Option { @@ -761,7 +763,7 @@ func (p *PubSub) handleRemoveSubscription(sub *Subscription) { // stop announcing only if there are no more subs and relays if p.myRelays[sub.topic] == 0 { - //p.disc.StopAdvertise(sub.topic) + p.disc.StopAdvertise(sub.topic) p.announce(sub.topic, false) p.rt.Leave(sub.topic) } @@ -778,7 +780,7 @@ func (p *PubSub) handleAddSubscription(req *addSubReq) { // announce we want this topic if neither subs nor relays exist so far if len(subs) == 0 && p.myRelays[sub.topic] == 0 { - //p.disc.Advertise(sub.topic) + p.disc.Advertise(sub.topic) p.announce(sub.topic, true) p.rt.Join(sub.topic) } @@ -806,7 +808,7 @@ func (p *PubSub) handleAddRelay(req *addRelayReq) { // announce we want this topic if neither relays nor subs exist so far if p.myRelays[topic] == 1 && len(p.mySubs[topic]) == 0 { - //p.disc.Advertise(topic) + p.disc.Advertise(topic) p.announce(topic, true) p.rt.Join(topic) } @@ -846,7 +848,7 @@ func (p *PubSub) handleRemoveRelay(topic string) { // stop announcing only if there are no more relays and subs if len(p.mySubs[topic]) == 0 { - //p.disc.StopAdvertise(topic) + p.disc.StopAdvertise(topic) p.announce(topic, false) p.rt.Leave(topic) } diff --git a/p2p/pubsub/topic.go b/p2p/pubsub/topic.go index 8b416339ba..cac8328386 100644 --- a/p2p/pubsub/topic.go +++ b/p2p/pubsub/topic.go @@ -158,7 +158,7 @@ func (t *Topic) Subscribe(opts ...SubOpt) (*Subscription, error) { out := make(chan *Subscription, 1) - //t.p.disc.Discover(sub.topic) + t.p.disc.Discover(sub.topic) select { case t.p.addSub <- &addSubReq{ @@ -184,7 +184,7 @@ func (t *Topic) Relay() (RelayCancelFunc, error) { out := make(chan RelayCancelFunc, 1) - //t.p.disc.Discover(t.topic) + t.p.disc.Discover(t.topic) select { case t.p.addRelay <- &addRelayReq{ @@ -242,7 +242,7 @@ func (t *Topic) Publish(ctx context.Context, data []byte, opts ...PubOpt) error } if pub.ready != nil { - //t.p.disc.Bootstrap(ctx, t.topic, pub.ready) + t.p.disc.Bootstrap(ctx, t.topic, pub.ready) } return t.p.val.PushLocal(&Message{m, t.p.host.ID(), nil}) diff --git a/p2p/server.go b/p2p/server.go index bfbe878fa4..1199e44880 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -30,6 +30,8 @@ import ( "sync/atomic" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" "github.com/AlayaNetwork/Alaya-Go/crypto" @@ -171,6 +173,13 @@ type Config struct { Logger log.Logger `toml:",omitempty"` clock mclock.Clock + + TopicKey string // TopicKey is the ENR key of the topic bitfield in the enr. + TopicCount uint64 + + MinimumPeersInTopicSearch uint + + MinimumPeersPerTopic int } // Server manages all peer connections. @@ -217,6 +226,8 @@ type Server struct { consensus bool addconsensus chan *enode.Node removeconsensus chan *enode.Node + + pubsub *pubsub.PubSub } type peerOpFunc func(map[enode.ID]*Peer) @@ -381,33 +392,17 @@ func (srv *Server) RemovePeer(node *enode.Node) { // server is shut down. If the connection fails for any reason, the server will // attempt to reconnect the peer. func (srv *Server) AddConsensusPeer(node *enode.Node) { - srv.dialsched.addConsensus(node) + select { + case srv.addconsensus <- node: + case <-srv.quit: + } } // RemoveConsensusPeer disconnects from the given consensus node func (srv *Server) RemoveConsensusPeer(node *enode.Node) { - var ( - ch chan *PeerEvent - sub event.Subscription - ) - // Disconnect the peer on the main loop. - srv.doPeerOp(func(peers map[enode.ID]*Peer) { - srv.dialsched.removeConsensus(node) - if peer := peers[node.ID()]; peer != nil { - ch = make(chan *PeerEvent, 1) - sub = srv.peerFeed.Subscribe(ch) - peer.Disconnect(DiscRequested) - } - }) - srv.log.Trace("Removing consensus node from dialstate", "node", node) - // Wait for the peer connection to end. - if ch != nil { - defer sub.Unsubscribe() - for ev := range ch { - if ev.Peer == node.ID() && ev.Type == PeerEventTypeDrop { - return - } - } + select { + case srv.removeconsensus <- node: + case <-srv.quit: } } @@ -692,16 +687,16 @@ func (srv *Server) setupDialScheduler() { } srv.dialsched = newDialScheduler(config, srv.discmix, srv.SetupConn) - dialstateRemoveConsensusPeerFn := func(node *enode.Node) { + /*dialstateRemoveConsensusPeerFn := func(node *enode.Node) { srv.doPeerOp(func(peers map[enode.ID]*Peer) { srv.dialsched.removeConsensusFromQueue(node) if p, ok := peers[node.ID()]; ok { p.Disconnect(DiscRequested) } }) - } + }*/ - srv.dialsched.consensus.InitRemoveConsensusPeerFn(dialstateRemoveConsensusPeerFn) + //srv.dialsched.consensus.InitRemoveConsensusPeerFn(dialstateRemoveConsensusPeerFn) for _, n := range srv.StaticNodes { srv.dialsched.addStatic(n) } @@ -816,6 +811,7 @@ running: if _, ok := consensusNodes[n.ID()]; ok { delete(consensusNodes, n.ID()) } + //todo info all connect peers, update topic if p, ok := peers[n.ID()]; ok { p.rw.set(consensusDialedConn, false) if !p.rw.is(staticDialedConn | trustedConn | inboundConn) { @@ -823,7 +819,7 @@ running: } srv.log.Debug("Remove consensus flag", "peer", n.ID, "consensus", srv.consensus) if len(peers) > srv.MaxPeers && !p.rw.is(staticDialedConn|trustedConn) { - srv.log.Debug("Disconnect non-consensus node", "peer", n.ID, "flags", p.rw.flags, "peers", len(peers), "consensus", srv.consensus) + srv.log.Debug("Disconnect non-consensus node", "peer", n.ID(), "flags", p.rw.flags, "peers", len(peers), "consensus", srv.consensus) p.Disconnect(DiscRequested) } } diff --git a/p2p/topic.go b/p2p/topic.go new file mode 100644 index 0000000000..df4ddd305b --- /dev/null +++ b/p2p/topic.go @@ -0,0 +1 @@ +package p2p From bd30142495b3c984b8a4e4d772e750ad31b6430a Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Mon, 8 Nov 2021 18:34:47 +0800 Subject: [PATCH 028/299] pubsub --- consensus/cbft/cbft.go | 9 +- consensus/cbft/network/pubsub.go | 86 ++++++++++++++-- node/node.go | 3 +- node/service.go | 5 + p2p/host.go | 13 +++ p2p/network.go | 34 +++++-- p2p/pubsub/pubsub_common.go | 1 + p2p/pubsub_server.go | 168 ------------------------------- p2p/server.go | 4 - p2p/stream.go | 9 +- 10 files changed, 138 insertions(+), 194 deletions(-) delete mode 100644 p2p/pubsub_server.go diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 3c83667afe..2821b39cae 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -124,7 +124,7 @@ type Cbft struct { evPool evidence.EvidencePool log log.Logger network *network.EngineManager - subServer p2p.PubSubServer + pubSub *network.PubSub start int32 syncing int32 @@ -190,6 +190,7 @@ type Cbft struct { // New returns a new CBFT. func New(sysConfig *params.CbftConfig, optConfig *ctypes.OptionsConfig, eventMux *event.TypeMux, ctx *node.ServiceContext) *Cbft { + localNode := enode.NewV4(&ctx.NodePriKey().PublicKey, nil, 0, 0) cbft := &Cbft{ config: ctypes.Config{Sys: sysConfig, Option: optConfig}, eventMux: eventMux, @@ -210,7 +211,7 @@ func New(sysConfig *params.CbftConfig, optConfig *ctypes.OptionsConfig, eventMux statQueues: make(map[common.Hash]map[string]int), messageHashCache: mapset.NewSet(), netLatencyMap: make(map[string]*list.List), - subServer: *p2p.SubServerInstance(), + pubSub: network.NewPubSub(localNode, ctx.P2PServer()), } if evPool, err := evidence.NewEvidencePool(ctx, optConfig.EvidenceDir); err == nil { @@ -877,8 +878,8 @@ func (cbft *Cbft) APIs(chain consensus.ChainReader) []rpc.API { // Protocols return consensus engine to provide protocol information. func (cbft *Cbft) Protocols() []p2p.Protocol { protocols := cbft.network.Protocols() - pubsubProtocols := cbft.subServer.Protocols() - for _,subProtol := range pubsubProtocols { + pubsubProtocols := cbft.pubSub.Protocols() + for _, subProtol := range pubsubProtocols { protocols = append(protocols, subProtol) } return protocols diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index fa0616eeda..a3fafdebac 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -17,25 +17,93 @@ package network import ( + "context" "fmt" "github.com/AlayaNetwork/Alaya-Go/p2p" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" ) +const ( + + // CbftProtocolName is protocol name of CBFT.pubsub. + CbftPubSubProtocolName = "cbft.pubsub" + + // CbftProtocolVersion is protocol version of CBFT.pubsub. + CbftPubSubProtocolVersion = 1 + + // CbftProtocolLength are the number of implemented message corresponding to cbft.pubsub protocol versions. + CbftPubSubProtocolLength = 10 +) + type PubSub struct { - pubsub p2p.PubSubServer + host *p2p.Host + ps *pubsub.PubSub - topicChan chan string - topics []string + topicChan chan string + topics []string // The set of topics we are subscribed to - mySubs map[string]map[*pubsub.Subscription]struct{} + mySubs map[string]map[*pubsub.Subscription]struct{} +} + +// Protocol.Run() +func (ps *PubSub) handler(peer *p2p.Peer, rw p2p.MsgReadWriter) error { + + conn := p2p.NewConn(peer.Node(), peer.Inbound()) + + stream := p2p.NewStream(conn, rw, "") + conn.SetStream(stream) + + ps.host.SetStream(peer.ID(), stream) + ps.host.NotifyAll(conn) + + // TODO pubsub: stream.linsten close + return nil +} + +func (ps *PubSub) NodeInfo() interface{} { + return nil +} + +//Protocols implemented the Protocols method and returned basic information about the CBFT.pubsub protocol. +func (ps *PubSub) Protocols() []p2p.Protocol { + return []p2p.Protocol{ + { + Name: CbftPubSubProtocolName, + Version: CbftPubSubProtocolVersion, + Length: CbftPubSubProtocolLength, + Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + return ps.handler(p, rw) + }, + NodeInfo: func() interface{} { + return ps.NodeInfo() + }, + PeerInfo: func(id enode.ID) interface{} { + return nil + }, + }, + } +} + +func NewPubSub(localNode *enode.Node, server *p2p.Server) *PubSub { + network := p2p.NewNetwork(server) + host := p2p.NewHost(localNode, network) + pubsub, err := pubsub.NewGossipSub(context.Background(), host) + if err != nil { + panic("Failed to NewGossipSub: " + err.Error()) + } + + return &PubSub{ + ps: pubsub, + host: host, + } } //Subscribe subscribe a topic func (ps *PubSub) Subscribe(topic string) error { - if err := ps.pubsub.PublishMsg(topic); err != nil { - return err - } + //if err := ps.ps.PublishMsg(topic); err != nil { + // return err + //} ps.topics = append(ps.topics, topic) return nil } @@ -52,5 +120,5 @@ func (ps *PubSub) Cancel(topic string) error { return nil } - return fmt.Errorf("Can not find","topic", topic) -} \ No newline at end of file + return fmt.Errorf("Can not find", "topic", topic) +} diff --git a/node/node.go b/node/node.go index 4df5af36d5..724427d27d 100644 --- a/node/node.go +++ b/node/node.go @@ -54,7 +54,7 @@ type Node struct { // chainId identifies the current chain and is used for replay protection ChainID *big.Int `toml:"-"` serverConfig p2p.Config - server *p2p.Server // Currently running P2P networking layer + server *p2p.Server // Currently running P2P networking layer serviceFuncs []ServiceConstructor // Service constructors (in dependency order) services map[reflect.Type]Service // Currently running services @@ -215,6 +215,7 @@ func (n *Node) Start() error { EventMux: n.eventmux, AccountManager: n.accman, serverConfig: n.serverConfig, + p2pServer: running, } for kind, s := range services { // copy needed for threaded access ctx.services[kind] = s diff --git a/node/service.go b/node/service.go index c275691808..1e2a0696db 100644 --- a/node/service.go +++ b/node/service.go @@ -39,6 +39,7 @@ type ServiceContext struct { EventMux *event.TypeMux // Event multiplexer used for decoupled notifications AccountManager *accounts.Manager // Account manager created by the node. serverConfig p2p.Config + p2pServer *p2p.Server } func NewServiceContext(config *Config, services map[reflect.Type]Service, EventMux *event.TypeMux, AccountManager *accounts.Manager) *ServiceContext { @@ -122,6 +123,10 @@ func (ctx *ServiceContext) ExtRPCEnabled() bool { return ctx.config.ExtRPCEnabled() } +func (ctx *ServiceContext) P2PServer() *p2p.Server { + return ctx.p2pServer +} + // ServiceConstructor is the function signature of the constructors needed to be // registered for service instantiation. type ServiceConstructor func(ctx *ServiceContext) (Service, error) diff --git a/p2p/host.go b/p2p/host.go index 530b344f21..aa1aff0f6c 100644 --- a/p2p/host.go +++ b/p2p/host.go @@ -15,6 +15,15 @@ type Host struct { sync.Mutex } +func NewHost(localNode *enode.Node, network *Network) *Host { + host := &Host{ + node: localNode, + streams: make(map[enode.ID]pubsub.Stream), + network: network, + } + return host +} + func (h *Host) ID() *enode.Node { return h.node } @@ -62,3 +71,7 @@ func (h *Host) Close() error { func (h *Host) ConnManager() connmgr.ConnManager { return nil } + +func (h *Host) NotifyAll(conn pubsub.Conn) { + h.network.NotifyAll(conn) +} diff --git a/p2p/network.go b/p2p/network.go index a662f65edb..df4b93f9d0 100644 --- a/p2p/network.go +++ b/p2p/network.go @@ -4,8 +4,12 @@ import ( "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "sync" + "time" ) +type PeerManager interface { +} + type Network struct { sync.RWMutex server *Server @@ -106,17 +110,35 @@ type Conn struct { } } -func(c *Conn) ID() string { +func NewConn(node *enode.Node, inbound bool) *Conn { + stat := pubsub.Stat{ + Opened: time.Now(), + Extra: make(map[interface{}]interface{}), + } + if inbound { + stat.Direction = pubsub.DirInbound + } else { + stat.Direction = pubsub.DirOutbound + } + conn := &Conn{ + remote: node, + stat: stat, + } + conn.streams.m = make(map[pubsub.Stream]struct{}) + return conn +} + +func (c *Conn) ID() string { return c.remote.ID().String() } -func(c *Conn) SetStreams(stream pubsub.Stream) { +func (c *Conn) SetStream(stream pubsub.Stream) { c.streams.Lock() defer c.streams.Unlock() c.streams.m[stream] = struct{}{} } -func(c *Conn) GetStreams() []pubsub.Stream { +func (c *Conn) GetStreams() []pubsub.Stream { c.streams.Lock() defer c.streams.Unlock() streams := make([]pubsub.Stream, 0, len(c.streams.m)) @@ -126,10 +148,10 @@ func(c *Conn) GetStreams() []pubsub.Stream { return streams } -func(c *Conn) Stat() pubsub.Stat { +func (c *Conn) Stat() pubsub.Stat { return c.stat } -func(c *Conn) RemotePeer() *enode.Node { +func (c *Conn) RemotePeer() *enode.Node { return c.remote -} \ No newline at end of file +} diff --git a/p2p/pubsub/pubsub_common.go b/p2p/pubsub/pubsub_common.go index abadb8fc3c..2b2c5416f8 100644 --- a/p2p/pubsub/pubsub_common.go +++ b/p2p/pubsub/pubsub_common.go @@ -62,6 +62,7 @@ func (p *PubSub) handleNewStream(s Stream) { if err := s.Read(&rpc.RPC); err != nil { log.Debug("Read message error", "err", err) p.notifyPeerDead(peer.ID()) + s.Close() return } diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go deleted file mode 100644 index 2e6bdb9fcd..0000000000 --- a/p2p/pubsub_server.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2021 The Alaya Network Authors -// This file is part of the Alaya-Go library. -// -// The Alaya-Go 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 Alaya-Go 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 Alaya-Go library. If not, see . - -package p2p - -import ( - "context" - "errors" - "fmt" - ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" - "sync" - "time" -) - -const ( - - // CbftProtocolName is protocol name of CBFT.pubsub. - CbftPubSubProtocolName = "cbft.pubsub" - - // CbftProtocolVersion is protocol version of CBFT.pubsub. - CbftPubSubProtocolVersion = 1 - - // CbftProtocolLength are the number of implemented message corresponding to cbft.pubsub protocol versions. - CbftPubSubProtocolLength = 10 - - // Msg code of PubSub's message - PubSubMsgCode = 0xff -) - -type PubSubServer struct { - lock sync.Mutex // protects running - running bool - ps *pubsub.PubSub - host *Host - peerMsgCh chan *ctypes.MsgInfo -} - -// Start starts running the server. -// Servers can not be re-used after stopping. -func (srv *PubSubServer) Start() (err error) { - srv.lock.Lock() - defer srv.lock.Unlock() - if srv.running { - return errors.New("server already running") - } - srv.running = true - return nil -} - -// run is the main loop of the server. -func (s *PubSubServer) run() { - -} - -// After the node is successfully connected and the message belongs -// to the cbft.pubsub protocol message, the method is called. -func (s *PubSubServer) Handle(p *Peer, rw MsgReadWriter) error { - - return nil -} - -// watchingAddTopicValidatorEvent -func (s *PubSubServer) watchingAddTopicValidatorEvent() { - -} - -// watchingAddTopicValidatorEvent -func (s *PubSubServer) watchingRemoveTopicValidatorEvent() { - -} - -// PublishMsg -func (s *PubSubServer) PublishMsg(topic string) error { - if topic == "" { - return fmt.Errorf("topic is nil") - } - return nil -} - -func (s *PubSubServer) ReadTopicLoop() { - -} - -// Protocol.NodeInfo() -func (s *PubSubServer) NodeInfo() interface{} { - return nil -} - -// Protocol.Run() -func (s *PubSubServer) handler(peer *Peer, rw MsgReadWriter) error { - direction := pubsub.DirOutbound - if peer.Inbound() { - direction = pubsub.DirInbound - } - stat := pubsub.Stat{ - Direction: direction, - Opened: time.Now(), - Extra: make(map[interface{}]interface{}), - } - conn := &Conn{ - remote: peer.Node(), - stat: stat, - } - conn.streams.m = make(map[pubsub.Stream]struct{}) - stream := &Stream{ - rw: rw, - conn: conn, - } - conn.streams.m[stream] = struct{}{} - s.host.SetStream(peer.ID(), stream) - s.host.network.NotifyAll(conn) - - return nil -} - -//Protocols implemented the Protocols method and returned basic information about the CBFT.pubsub protocol. -func (s *PubSubServer) Protocols() []Protocol { - return []Protocol{ - { - Name: CbftPubSubProtocolName, - Version: CbftPubSubProtocolVersion, - Length: CbftPubSubProtocolLength, - Run: func(p *Peer, rw MsgReadWriter) error { - return s.handler(p, rw) - }, - NodeInfo: func() interface{} { - return s.NodeInfo() - }, - PeerInfo: func(id enode.ID) interface{} { - return nil - }, - }, - } -} - -func NewPubSubServer(node *enode.Node, server *Server) *PubSubServer { - network := NewNetwork(server) - host := &Host{ - node: node, - streams: make(map[enode.ID]pubsub.Stream), - network: network, - } - pubsub, err := pubsub.NewGossipSub(context.Background(), host) - if err != nil { - panic("Failed to NewGossipSub: " + err.Error()) - } - - return &PubSubServer{ - ps: pubsub, - host: host, - peerMsgCh: make(chan *ctypes.MsgInfo), - } -} diff --git a/p2p/server.go b/p2p/server.go index 532e05f784..76fa10bc99 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -217,7 +217,6 @@ type Server struct { consensus bool addconsensus chan *enode.Node removeconsensus chan *enode.Node - subServer *PubSubServer } type peerOpFunc func(map[enode.ID]*Peer) @@ -542,9 +541,6 @@ func (srv *Server) Start() (err error) { } srv.setupDialScheduler() - srv.subServer = NewPubSubServer(srv.localnode.Node(), srv) - srv.subServer.Start() - srv.loopWG.Add(1) go srv.run() return nil diff --git a/p2p/stream.go b/p2p/stream.go index 643ec17266..bfd0d110f3 100644 --- a/p2p/stream.go +++ b/p2p/stream.go @@ -7,6 +7,12 @@ import ( "sync/atomic" ) +const ( + + // Msg code of PubSub's message + PubSubMsgCode = 0xff +) + // Stream is the stream type used by pubsub. In general type Stream struct { conn pubsub.Conn @@ -68,8 +74,7 @@ func (s *Stream) Close() { } -// newStream creates a new Stream. -func newStream(conn pubsub.Conn, rw MsgReadWriter, id pubsub.ProtocolID) *Stream { +func NewStream(conn pubsub.Conn, rw MsgReadWriter, id pubsub.ProtocolID) *Stream { s := &Stream{ conn: conn, rw: rw, From 7bf7f3eacffae6d940e64a088bc0e64eac2a710e Mon Sep 17 00:00:00 2001 From: benbaley Date: Mon, 8 Nov 2021 18:40:48 +0800 Subject: [PATCH 029/299] optimizing code --- eth/handler.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/eth/handler.go b/eth/handler.go index de64a115d4..d3d0c58b22 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -229,9 +229,7 @@ func (pm *ProtocolManager) removePeer(id string) { log.Error("Peer removal failed", "peer", id, "err", err) } // Hard disconnect at the networking layer - if peer != nil { - peer.Peer.Disconnect(p2p.DiscUselessPeer) - } + peer.Peer.Disconnect(p2p.DiscUselessPeer) } func (pm *ProtocolManager) Start(maxPeers int) { @@ -413,7 +411,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { unknown = true } else { query.Origin.Hash, query.Origin.Number = pm.blockchain.GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical) - unknown = (query.Origin.Hash == common.Hash{}) + unknown = query.Origin.Hash == common.Hash{} } case hashMode && !query.Reverse: // Hash based traversal towards the leaf block @@ -836,7 +834,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { return errResp(ErrDecode, "msg %v: %v", msg, err) } log.Trace("Handler Receive GetPooledTransactions", "peer", p.id, "hashes", len(query)) - hashes, txs := pm.answerGetPooledTransactions(query, p) + hashes, txs := pm.answerGetPooledTransactions(query) if len(txs) > 0 { log.Trace("Handler Send PooledTransactions", "peer", p.id, "txs", len(txs)) return p.SendPooledTransactionsRLP(hashes, txs) @@ -978,7 +976,7 @@ func (pm *ProtocolManager) BroadcastTxs(txs types.Transactions) { "announce packs", annoPeers, "announced hashes", annoCount) } -func (pm *ProtocolManager) answerGetPooledTransactions(query GetPooledTransactionsPacket, peer *peer) ([]common.Hash, []rlp.RawValue) { +func (pm *ProtocolManager) answerGetPooledTransactions(query GetPooledTransactionsPacket) ([]common.Hash, []rlp.RawValue) { // Gather transactions until the fetch or network limits is reached var ( bytes int From a926e67d0b924a646008346b17f003b51bad7979 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Mon, 8 Nov 2021 19:24:04 +0800 Subject: [PATCH 030/299] Perfect stream --- consensus/cbft/network/pubsub.go | 11 ++++++++--- p2p/pubsub/interfaces.go | 2 +- p2p/pubsub/pubsub_common.go | 4 ++-- p2p/stream.go | 12 +++++++----- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index a3fafdebac..e681944746 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -19,6 +19,7 @@ package network import ( "context" "fmt" + "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" @@ -51,14 +52,18 @@ func (ps *PubSub) handler(peer *p2p.Peer, rw p2p.MsgReadWriter) error { conn := p2p.NewConn(peer.Node(), peer.Inbound()) - stream := p2p.NewStream(conn, rw, "") + // Wait for the connection to exit + errCh := make(chan error) + + stream := p2p.NewStream(conn, rw, errCh, "") conn.SetStream(stream) ps.host.SetStream(peer.ID(), stream) ps.host.NotifyAll(conn) - // TODO pubsub: stream.linsten close - return nil + handlerErr := <-errCh + log.Info("pubsub's handler ends", "err", handlerErr) + return handlerErr } func (ps *PubSub) NodeInfo() interface{} { diff --git a/p2p/pubsub/interfaces.go b/p2p/pubsub/interfaces.go index b2a8b1e4b7..7f2d2f4ef6 100644 --- a/p2p/pubsub/interfaces.go +++ b/p2p/pubsub/interfaces.go @@ -121,7 +121,7 @@ type Stream interface { Write(interface{}) error - Close() + Close(err error) } // Conn is a connection to a remote peer. It multiplexes streams. diff --git a/p2p/pubsub/pubsub_common.go b/p2p/pubsub/pubsub_common.go index 2b2c5416f8..d0a56d7484 100644 --- a/p2p/pubsub/pubsub_common.go +++ b/p2p/pubsub/pubsub_common.go @@ -60,9 +60,9 @@ func (p *PubSub) handleNewStream(s Stream) { rpc := new(RPC) if err := s.Read(&rpc.RPC); err != nil { - log.Debug("Read message error", "err", err) + log.Error("Read message error", "err", err) p.notifyPeerDead(peer.ID()) - s.Close() + s.Close(err) return } diff --git a/p2p/stream.go b/p2p/stream.go index bfd0d110f3..f4de13c360 100644 --- a/p2p/stream.go +++ b/p2p/stream.go @@ -18,6 +18,7 @@ type Stream struct { conn pubsub.Conn rw MsgReadWriter protocol atomic.Value + errCh chan error } func (s *Stream) String() string { @@ -70,14 +71,15 @@ func (s *Stream) Write(data interface{}) error { return nil } -func (s *Stream) Close() { - +func (s *Stream) Close(err error) { + s.errCh <- err } -func NewStream(conn pubsub.Conn, rw MsgReadWriter, id pubsub.ProtocolID) *Stream { +func NewStream(conn pubsub.Conn, rw MsgReadWriter, errCh chan error, id pubsub.ProtocolID) *Stream { s := &Stream{ - conn: conn, - rw: rw, + conn: conn, + rw: rw, + errCh: errCh, } s.SetProtocol(id) return s From e40954257d6ffb97267f90b382216f5375eba4a0 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 9 Nov 2021 11:08:33 +0800 Subject: [PATCH 031/299] update discover --- p2p/discover.go | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/p2p/discover.go b/p2p/discover.go index 5205c82c0f..fe3593b017 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -10,15 +10,14 @@ import ( "github.com/prysmaticlabs/go-bitfield" - "github.com/prometheus/common/log" - + "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/enr" ) -// subscribe to a static subnet with the given topic and index.A given validator and subscription handler is +// SubscribeTopic to the given topic.A given validator and subscription handler is // used to handle messages from the subnet. The base protobuf message is used to initialize new messages for decoding. -func (srv *Server) subscribeTopic(ctx context.Context, topic string) { +func (srv *Server) SubscribeTopic(ctx context.Context, topic string) { ticker := time.NewTicker(time.Second * 1) @@ -35,8 +34,7 @@ func (srv *Server) subscribeTopic(ctx context.Context, topic string) { // Check there are enough peers if !srv.validPeersExist(topic) { - log.Debugf("No peers found subscribed to attestation gossip subnet with " + - "committee index %d. Searching network for peers subscribed to the subnet.") + log.Debug("No peers found subscribed gossip topic . Searching network for peers subscribed to the topic.", "topic", topic) _, err := srv.FindPeersWithTopic( ctx, topic, @@ -55,7 +53,7 @@ func (srv *Server) subscribeTopic(ctx context.Context, topic string) { // find if we have peers who are subscribed to the same subnet func (srv *Server) validPeersExist(subnetTopic string) bool { - numOfPeers := srv.pubsub.ListPeers(subnetTopic) + numOfPeers := srv.subServer.ps.ListPeers(subnetTopic) return len(numOfPeers) >= srv.Config.MinimumPeersPerTopic } @@ -78,7 +76,7 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, thresho iterator := srv.DiscV5.RandomNodes() iterator = filterNodes(ctx, iterator, srv.filterPeerForTopic(srv.topicToIdx(topic))) - currNum := len(srv.pubsub.ListPeers(topic)) + currNum := len(srv.subServer.ps.ListPeers(topic)) wg := new(sync.WaitGroup) for { if err := ctx.Err(); err != nil { @@ -93,7 +91,7 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, thresho } // Wait for all dials to be completed. wg.Wait() - currNum = len(srv.pubsub.ListPeers(topic)) + currNum = len(srv.subServer.ps.ListPeers(topic)) } return true, nil } @@ -149,10 +147,7 @@ func (srv *Server) filterPeer(node *enode.Node) bool { return true } -// RefreshENR uses an epoch to refresh the enr entry for our node -// with the tracked committee ids for the epoch, allowing our node -// to be dynamically discoverable by others given our tracked committee ids. -func (srv *Server) RefreshENR() { +func (srv *Server) RefreshTopicENR() { // return early if discv5 isnt running if srv.DiscV5 == nil { return @@ -164,7 +159,7 @@ func (srv *Server) RefreshENR() { } currentBitTopics, err := getBitvector(srv.Config.TopicKey, srv.DiscV5.Self().Record()) if err != nil { - log.Errorf("Could not retrieve att bitfield: %v", err) + log.Error("Could not retrieve att bitfield", "err", err) return } From d2a26617be30572d7559b8eebd75cd7e1ca98854 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 9 Nov 2021 11:19:44 +0800 Subject: [PATCH 032/299] remove no use --- p2p/pubsub/discovery.go | 66 +++---- p2p/pubsub/discovery/backoff.go | 228 ----------------------- p2p/pubsub/discovery/backoffconnector.go | 96 ---------- p2p/pubsub/discovery/discovery.go | 27 --- p2p/pubsub/discovery/host.go | 24 --- p2p/pubsub/discovery/options.go | 41 ---- p2p/topic.go | 1 - 7 files changed, 33 insertions(+), 450 deletions(-) delete mode 100644 p2p/pubsub/discovery/backoff.go delete mode 100644 p2p/pubsub/discovery/backoffconnector.go delete mode 100644 p2p/pubsub/discovery/discovery.go delete mode 100644 p2p/pubsub/discovery/host.go delete mode 100644 p2p/pubsub/discovery/options.go delete mode 100644 p2p/topic.go diff --git a/p2p/pubsub/discovery.go b/p2p/pubsub/discovery.go index fe3d3db762..8874aff49b 100644 --- a/p2p/pubsub/discovery.go +++ b/p2p/pubsub/discovery.go @@ -2,14 +2,13 @@ package pubsub import ( "context" - "math/rand" "time" - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - "github.com/prometheus/common/log" - discimpl "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/discovery" + "github.com/libp2p/go-libp2p-core/discovery" + "github.com/libp2p/go-libp2p-core/peer" + // discimpl "github.com/libp2p/go-libp2p-discovery" ) var ( @@ -28,23 +27,24 @@ const discoveryAdvertiseRetryInterval = 2 * time.Minute type DiscoverOpt func(*discoverOptions) error type discoverOptions struct { - connFactory BackoffConnectorFactory - opts []discimpl.Option + // connFactory BackoffConnectorFactory + opts []discovery.Option } func defaultDiscoverOptions() *discoverOptions { - rngSrc := rand.NewSource(rand.Int63()) + /*rngSrc := rand.NewSource(rand.Int63()) minBackoff, maxBackoff := time.Second*10, time.Hour cacheSize := 100 dialTimeout := time.Minute * 2 discoverOpts := &discoverOptions{ - connFactory: func(host Host) (*discimpl.BackoffConnector, error) { + connFactory: func(host host.Host) (*discimpl.BackoffConnector, error) { backoff := discimpl.NewExponentialBackoff(minBackoff, maxBackoff, discimpl.FullJitter, time.Second, 5.0, 0, rand.New(rngSrc)) return discimpl.NewBackoffConnector(host, cacheSize, dialTimeout, backoff) }, } - return discoverOpts + return discoverOpts*/ + return nil } // discover represents the discovery pipeline. @@ -53,7 +53,7 @@ type discover struct { p *PubSub // discovery assists in discovering and advertising peers for a topic - discovery discimpl.Discovery + discovery discovery.Discovery // advertising tracks which topics are being advertised advertising map[string]context.CancelFunc @@ -68,7 +68,7 @@ type discover struct { done chan string // connector handles connecting to new peers found via discovery - connector *discimpl.BackoffConnector + // connector *discimpl.BackoffConnector // options are the set of options to be used to complete struct construction in Start options *discoverOptions @@ -94,11 +94,11 @@ func (d *discover) Start(p *PubSub, opts ...DiscoverOpt) error { d.ongoing = make(map[string]struct{}) d.done = make(chan string) - conn, err := d.options.connFactory(p.host) + /*conn, err := d.options.connFactory(p.host) if err != nil { return err } - d.connector = conn + d.connector = conn*/ go d.discoverLoop() go d.pollTimer() @@ -230,7 +230,7 @@ func (d *discover) StopAdvertise(topic string) { } // Discover searches for additional peers interested in a given topic -func (d *discover) Discover(topic string, opts ...discimpl.Option) { +func (d *discover) Discover(topic string, opts ...discovery.Option) { if d.discovery == nil { return } @@ -239,7 +239,7 @@ func (d *discover) Discover(topic string, opts ...discimpl.Option) { } // Bootstrap attempts to bootstrap to a given topic. Returns true if bootstrapped successfully, false otherwise. -func (d *discover) Bootstrap(ctx context.Context, topic string, ready RouterReady, opts ...discimpl.Option) bool { +func (d *discover) Bootstrap(ctx context.Context, topic string, ready RouterReady, opts ...discovery.Option) bool { if d.discovery == nil { return true } @@ -296,40 +296,40 @@ func (d *discover) Bootstrap(ctx context.Context, topic string, ready RouterRead } } -func (d *discover) handleDiscovery(ctx context.Context, topic string, opts []discimpl.Option) { - discoverCtx, cancel := context.WithTimeout(ctx, time.Second*10) - defer cancel() +func (d *discover) handleDiscovery(ctx context.Context, topic string, opts []discovery.Option) { + /*discoverCtx, cancel := context.WithTimeout(ctx, time.Second*10) + defer cancel()*/ - peerCh, err := d.discovery.FindPeers(discoverCtx, topic, opts...) - if err != nil { - log.Debugf("error finding peers for topic %s: %v", topic, err) - return - } + /* peerCh, err := d.discovery.FindPeers(discoverCtx, topic, opts...) + if err != nil { + log.Debugf("error finding peers for topic %s: %v", topic, err) + return + }*/ - d.connector.Connect(ctx, peerCh) + //d.connector.Connect(ctx, peerCh) } type discoverReq struct { topic string - opts []discimpl.Option + opts []discovery.Option done chan struct{} } type pubSubDiscovery struct { - discimpl.Discovery - opts []discimpl.Option + discovery.Discovery + opts []discovery.Option } -func (d *pubSubDiscovery) Advertise(ctx context.Context, ns string, opts ...discimpl.Option) (time.Duration, error) { +func (d *pubSubDiscovery) Advertise(ctx context.Context, ns string, opts ...discovery.Option) (time.Duration, error) { return d.Discovery.Advertise(ctx, "floodsub:"+ns, append(opts, d.opts...)...) } -func (d *pubSubDiscovery) FindPeers(ctx context.Context, ns string, opts ...discimpl.Option) (<-chan *enode.Node, error) { +func (d *pubSubDiscovery) FindPeers(ctx context.Context, ns string, opts ...discovery.Option) (<-chan peer.AddrInfo, error) { return d.Discovery.FindPeers(ctx, "floodsub:"+ns, append(opts, d.opts...)...) } // WithDiscoveryOpts passes libp2p Discovery options into the PubSub discovery subsystem -func WithDiscoveryOpts(opts ...discimpl.Option) DiscoverOpt { +func WithDiscoveryOpts(opts ...discovery.Option) DiscoverOpt { return func(d *discoverOptions) error { d.opts = opts return nil @@ -337,12 +337,12 @@ func WithDiscoveryOpts(opts ...discimpl.Option) DiscoverOpt { } // BackoffConnectorFactory creates a BackoffConnector that is attached to a given host -type BackoffConnectorFactory func(host Host) (*discimpl.BackoffConnector, error) +//type BackoffConnectorFactory func(host host.Host) (*discimpl.BackoffConnector, error) // WithDiscoverConnector adds a custom connector that deals with how the discovery subsystem connects to peers -func WithDiscoverConnector(connFactory BackoffConnectorFactory) DiscoverOpt { +/*func WithDiscoverConnector(connFactory BackoffConnectorFactory) DiscoverOpt { return func(d *discoverOptions) error { d.connFactory = connFactory return nil } -} +}*/ diff --git a/p2p/pubsub/discovery/backoff.go b/p2p/pubsub/discovery/backoff.go deleted file mode 100644 index 384a95cf6f..0000000000 --- a/p2p/pubsub/discovery/backoff.go +++ /dev/null @@ -1,228 +0,0 @@ -package discovery - -import ( - "math" - "math/rand" - "sync" - "time" -) - -type BackoffFactory func() BackoffStrategy - -// BackoffStrategy describes how backoff will be implemented. BackoffStratgies are stateful. -type BackoffStrategy interface { - // Delay calculates how long the next backoff duration should be, given the prior calls to Delay - Delay() time.Duration - // Reset clears the internal state of the BackoffStrategy - Reset() -} - -// Jitter implementations taken roughly from https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ - -// Jitter must return a duration between min and max. Min must be lower than, or equal to, max. -type Jitter func(duration, min, max time.Duration, rng *rand.Rand) time.Duration - -// FullJitter returns a random number uniformly chose from the range [min, boundedDur]. -// boundedDur is the duration bounded between min and max. -func FullJitter(duration, min, max time.Duration, rng *rand.Rand) time.Duration { - if duration <= min { - return min - } - - normalizedDur := boundedDuration(duration, min, max) - min - - return boundedDuration(time.Duration(rng.Int63n(int64(normalizedDur)))+min, min, max) -} - -// NoJitter returns the duration bounded between min and max -func NoJitter(duration, min, max time.Duration, rng *rand.Rand) time.Duration { - return boundedDuration(duration, min, max) -} - -type randomizedBackoff struct { - min time.Duration - max time.Duration - rng *rand.Rand -} - -func (b *randomizedBackoff) BoundedDelay(duration time.Duration) time.Duration { - return boundedDuration(duration, b.min, b.max) -} - -func boundedDuration(d, min, max time.Duration) time.Duration { - if d < min { - return min - } - if d > max { - return max - } - return d -} - -type attemptBackoff struct { - attempt int - jitter Jitter - randomizedBackoff -} - -func (b *attemptBackoff) Reset() { - b.attempt = 0 -} - -// NewFixedBackoff creates a BackoffFactory with a constant backoff duration -func NewFixedBackoff(delay time.Duration) BackoffFactory { - return func() BackoffStrategy { - return &fixedBackoff{delay: delay} - } -} - -type fixedBackoff struct { - delay time.Duration -} - -func (b *fixedBackoff) Delay() time.Duration { - return b.delay -} - -func (b *fixedBackoff) Reset() {} - -// NewPolynomialBackoff creates a BackoffFactory with backoff of the form c0*x^0, c1*x^1, ...cn*x^n where x is the attempt number -// jitter is the function for adding randomness around the backoff -// timeUnits are the units of time the polynomial is evaluated in -// polyCoefs is the array of polynomial coefficients from [c0, c1, ... cn] -func NewPolynomialBackoff(min, max time.Duration, jitter Jitter, - timeUnits time.Duration, polyCoefs []float64, rngSrc rand.Source) BackoffFactory { - rng := rand.New(&lockedSource{src: rngSrc}) - return func() BackoffStrategy { - return &polynomialBackoff{ - attemptBackoff: attemptBackoff{ - randomizedBackoff: randomizedBackoff{ - min: min, - max: max, - rng: rng, - }, - jitter: jitter, - }, - timeUnits: timeUnits, - poly: polyCoefs, - } - } -} - -type polynomialBackoff struct { - attemptBackoff - timeUnits time.Duration - poly []float64 -} - -func (b *polynomialBackoff) Delay() time.Duration { - var polySum float64 - switch len(b.poly) { - case 0: - return 0 - case 1: - polySum = b.poly[0] - default: - polySum = b.poly[0] - exp := 1 - attempt := b.attempt - b.attempt++ - - for _, c := range b.poly[1:] { - exp *= attempt - polySum += float64(exp) * c - } - } - return b.jitter(time.Duration(float64(b.timeUnits)*polySum), b.min, b.max, b.rng) -} - -// NewExponentialBackoff creates a BackoffFactory with backoff of the form base^x + offset where x is the attempt number -// jitter is the function for adding randomness around the backoff -// timeUnits are the units of time the base^x is evaluated in -func NewExponentialBackoff(min, max time.Duration, jitter Jitter, - timeUnits time.Duration, base float64, offset time.Duration, rngSrc rand.Source) BackoffFactory { - rng := rand.New(&lockedSource{src: rngSrc}) - return func() BackoffStrategy { - return &exponentialBackoff{ - attemptBackoff: attemptBackoff{ - randomizedBackoff: randomizedBackoff{ - min: min, - max: max, - rng: rng, - }, - jitter: jitter, - }, - timeUnits: timeUnits, - base: base, - offset: offset, - } - } -} - -type exponentialBackoff struct { - attemptBackoff - timeUnits time.Duration - base float64 - offset time.Duration -} - -func (b *exponentialBackoff) Delay() time.Duration { - attempt := b.attempt - b.attempt++ - return b.jitter( - time.Duration(math.Pow(b.base, float64(attempt))*float64(b.timeUnits))+b.offset, b.min, b.max, b.rng) -} - -// NewExponentialDecorrelatedJitter creates a BackoffFactory with backoff of the roughly of the form base^x where x is the attempt number. -// Delays start at the minimum duration and after each attempt delay = rand(min, delay * base), bounded by the max -// See https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ for more information -func NewExponentialDecorrelatedJitter(min, max time.Duration, base float64, rngSrc rand.Source) BackoffFactory { - rng := rand.New(&lockedSource{src: rngSrc}) - return func() BackoffStrategy { - return &exponentialDecorrelatedJitter{ - randomizedBackoff: randomizedBackoff{ - min: min, - max: max, - rng: rng, - }, - base: base, - } - } -} - -type exponentialDecorrelatedJitter struct { - randomizedBackoff - base float64 - lastDelay time.Duration -} - -func (b *exponentialDecorrelatedJitter) Delay() time.Duration { - if b.lastDelay < b.min { - b.lastDelay = b.min - return b.lastDelay - } - - nextMax := int64(float64(b.lastDelay) * b.base) - b.lastDelay = boundedDuration(time.Duration(b.rng.Int63n(nextMax-int64(b.min)))+b.min, b.min, b.max) - return b.lastDelay -} - -func (b *exponentialDecorrelatedJitter) Reset() { b.lastDelay = 0 } - -type lockedSource struct { - lk sync.Mutex - src rand.Source -} - -func (r *lockedSource) Int63() (n int64) { - r.lk.Lock() - n = r.src.Int63() - r.lk.Unlock() - return -} - -func (r *lockedSource) Seed(seed int64) { - r.lk.Lock() - r.src.Seed(seed) - r.lk.Unlock() -} diff --git a/p2p/pubsub/discovery/backoffconnector.go b/p2p/pubsub/discovery/backoffconnector.go deleted file mode 100644 index 3b2ffba9b6..0000000000 --- a/p2p/pubsub/discovery/backoffconnector.go +++ /dev/null @@ -1,96 +0,0 @@ -package discovery - -import ( - "context" - "sync" - "time" - - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - - lru "github.com/hashicorp/golang-lru" - "github.com/prometheus/common/log" -) - -// BackoffConnector is a utility to connect to peers, but only if we have not recently tried connecting to them already -type BackoffConnector struct { - cache *lru.TwoQueueCache - host Host - connTryDur time.Duration - backoff BackoffFactory - mux sync.Mutex -} - -// NewBackoffConnector creates a utility to connect to peers, but only if we have not recently tried connecting to them already -// cacheSize is the size of a TwoQueueCache -// connectionTryDuration is how long we attempt to connect to a peer before giving up -// backoff describes the strategy used to decide how long to backoff after previously attempting to connect to a peer -func NewBackoffConnector(h Host, cacheSize int, connectionTryDuration time.Duration, backoff BackoffFactory) (*BackoffConnector, error) { - cache, err := lru.New2Q(cacheSize) - if err != nil { - return nil, err - } - - return &BackoffConnector{ - cache: cache, - host: h, - connTryDur: connectionTryDuration, - backoff: backoff, - }, nil -} - -type connCacheData struct { - nextTry time.Time - strat BackoffStrategy -} - -// Connect attempts to connect to the peers passed in by peerCh. Will not connect to peers if they are within the backoff period. -// As Connect will attempt to dial peers as soon as it learns about them, the caller should try to keep the number, -// and rate, of inbound peers manageable. -func (c *BackoffConnector) Connect(ctx context.Context, peerCh <-chan *enode.Node) { - for { - select { - case pi, ok := <-peerCh: - if !ok { - return - } - - if pi.ID() == c.host.ID().ID() /*|| pi.ID == ""*/ { - continue - } - - c.mux.Lock() - val, ok := c.cache.Get(pi) - var cachedPeer *connCacheData - if ok { - tv := val.(*connCacheData) - now := time.Now() - if now.Before(tv.nextTry) { - c.mux.Unlock() - continue - } - - tv.nextTry = now.Add(tv.strat.Delay()) - } else { - cachedPeer = &connCacheData{strat: c.backoff()} - cachedPeer.nextTry = time.Now().Add(cachedPeer.strat.Delay()) - c.cache.Add(pi, cachedPeer) - } - c.mux.Unlock() - - go func(pi enode.ID) { - ctx, cancel := context.WithTimeout(ctx, c.connTryDur) - defer cancel() - - err := c.host.Connect(ctx, pi) - if err != nil { - log.Debugf("Error connecting to pubsub peer %s: %s", pi, err.Error()) - return - } - }(pi.ID()) - - case <-ctx.Done(): - log.Infof("discovery: backoff connector context error %v", ctx.Err()) - return - } - } -} diff --git a/p2p/pubsub/discovery/discovery.go b/p2p/pubsub/discovery/discovery.go deleted file mode 100644 index 4c2c2f602d..0000000000 --- a/p2p/pubsub/discovery/discovery.go +++ /dev/null @@ -1,27 +0,0 @@ -// Package discovery provides service advertisement and peer discovery interfaces for libp2p. -package discovery - -import ( - "context" - "time" - - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" -) - -// Advertiser is an interface for advertising services -type Advertiser interface { - // Advertise advertises a service - Advertise(ctx context.Context, ns string, opts ...Option) (time.Duration, error) -} - -// Discoverer is an interface for peer discovery -type Discoverer interface { - // FindPeers discovers peers providing a service - FindPeers(ctx context.Context, ns string, opts ...Option) (<-chan *enode.Node, error) -} - -// Discovery is an interface that combines service advertisement and peer discovery -type Discovery interface { - Advertiser - Discoverer -} diff --git a/p2p/pubsub/discovery/host.go b/p2p/pubsub/discovery/host.go deleted file mode 100644 index 078761797b..0000000000 --- a/p2p/pubsub/discovery/host.go +++ /dev/null @@ -1,24 +0,0 @@ -package discovery - -import ( - "context" - - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" -) - -// Host is an object participating in a p2p network, which -// implements protocols or provides services. It handles -// requests like a Server, and issues requests like a Client. -// It is called Host because it is both Server and Client (and Peer -// may be confusing). -type Host interface { - // ID returns the (local) peer.ID associated with this Host - ID() *enode.Node - - // Connect ensures there is a connection between this host and the peer with - // given peer.ID. Connect will absorb the addresses in pi into its internal - // peerstore. If there is not an active connection, Connect will issue a - // h.Network.Dial, and block until a connection is open, or an error is - // returned. // TODO: Relay + NAT. - Connect(ctx context.Context, pi enode.ID) error -} diff --git a/p2p/pubsub/discovery/options.go b/p2p/pubsub/discovery/options.go deleted file mode 100644 index 7b28305268..0000000000 --- a/p2p/pubsub/discovery/options.go +++ /dev/null @@ -1,41 +0,0 @@ -package discovery - -import "time" - -// DiscoveryOpt is a single discovery option. -type Option func(opts *Options) error - -// DiscoveryOpts is a set of discovery options. -type Options struct { - Ttl time.Duration - Limit int - - // Other (implementation-specific) options - Other map[interface{}]interface{} -} - -// Apply applies the given options to this DiscoveryOpts -func (opts *Options) Apply(options ...Option) error { - for _, o := range options { - if err := o(opts); err != nil { - return err - } - } - return nil -} - -// TTL is an option that provides a hint for the duration of an advertisement -func TTL(ttl time.Duration) Option { - return func(opts *Options) error { - opts.Ttl = ttl - return nil - } -} - -// Limit is an option that provides an upper bound on the peer count for discovery -func Limit(limit int) Option { - return func(opts *Options) error { - opts.Limit = limit - return nil - } -} diff --git a/p2p/topic.go b/p2p/topic.go deleted file mode 100644 index df4ddd305b..0000000000 --- a/p2p/topic.go +++ /dev/null @@ -1 +0,0 @@ -package p2p From f0459256003d29b364426cb335733d041d6fe8aa Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Tue, 9 Nov 2021 11:24:58 +0800 Subject: [PATCH 033/299] Define the pubsub interface required for consensus --- consensus/cbft/network/pubsub.go | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index e681944746..6f1e9d3a41 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -37,12 +37,20 @@ const ( CbftPubSubProtocolLength = 10 ) +// Group consensus message +type RGMsg struct { + Code uint64 + Data interface{} +} + type PubSub struct { host *p2p.Host ps *pubsub.PubSub - topicChan chan string - topics []string + // Messages of all topics subscribed are sent out from this channel uniformly + msgCh chan *RGMsg + // All topics subscribed + topics map[string]map[*pubsub.Topic]struct{} // The set of topics we are subscribed to mySubs map[string]map[*pubsub.Subscription]struct{} } @@ -93,14 +101,17 @@ func (ps *PubSub) Protocols() []p2p.Protocol { func NewPubSub(localNode *enode.Node, server *p2p.Server) *PubSub { network := p2p.NewNetwork(server) host := p2p.NewHost(localNode, network) - pubsub, err := pubsub.NewGossipSub(context.Background(), host) + gossipSub, err := pubsub.NewGossipSub(context.Background(), host) if err != nil { panic("Failed to NewGossipSub: " + err.Error()) } return &PubSub{ - ps: pubsub, - host: host, + ps: gossipSub, + host: host, + msgCh: make(chan *RGMsg), + topics: make(map[string]map[*pubsub.Topic]struct{}), + mySubs: make(map[string]map[*pubsub.Subscription]struct{}), } } @@ -109,7 +120,7 @@ func (ps *PubSub) Subscribe(topic string) error { //if err := ps.ps.PublishMsg(topic); err != nil { // return err //} - ps.topics = append(ps.topics, topic) + //ps.topics = append(ps.topics, topic) return nil } @@ -124,6 +135,13 @@ func (ps *PubSub) Cancel(topic string) error { } return nil } - return fmt.Errorf("Can not find", "topic", topic) } + +func (ps *PubSub) Publish(data *RGMsg) error { + return nil +} + +func (ps *PubSub) Receive() *RGMsg { + return <-ps.msgCh +} From 53719e8ea8da695f888c3ea5d41621005cee0921 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Tue, 9 Nov 2021 12:26:50 +0800 Subject: [PATCH 034/299] Add pubsubserver --- consensus/cbft/cbft.go | 2 +- consensus/cbft/network/pubsub.go | 17 +++++++---------- node/node.go | 4 +++- node/service.go | 6 +++--- p2p/discover.go | 6 +++--- p2p/pubsub_server.go | 25 +++++++++++++++++++++++++ p2p/server.go | 6 ++++++ 7 files changed, 48 insertions(+), 18 deletions(-) create mode 100644 p2p/pubsub_server.go diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 2821b39cae..3979ac3a7d 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -211,7 +211,7 @@ func New(sysConfig *params.CbftConfig, optConfig *ctypes.OptionsConfig, eventMux statQueues: make(map[common.Hash]map[string]int), messageHashCache: mapset.NewSet(), netLatencyMap: make(map[string]*list.List), - pubSub: network.NewPubSub(localNode, ctx.P2PServer()), + pubSub: network.NewPubSub(localNode, ctx.PubSubServer()), } if evPool, err := evidence.NewEvidencePool(ctx, optConfig.EvidenceDir); err == nil { diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index 6f1e9d3a41..bcd9ea15b5 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -17,7 +17,6 @@ package network import ( - "context" "fmt" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p" @@ -44,8 +43,7 @@ type RGMsg struct { } type PubSub struct { - host *p2p.Host - ps *pubsub.PubSub + ps *p2p.PubSubServer // Messages of all topics subscribed are sent out from this channel uniformly msgCh chan *RGMsg @@ -66,8 +64,8 @@ func (ps *PubSub) handler(peer *p2p.Peer, rw p2p.MsgReadWriter) error { stream := p2p.NewStream(conn, rw, errCh, "") conn.SetStream(stream) - ps.host.SetStream(peer.ID(), stream) - ps.host.NotifyAll(conn) + /*ps.host.SetStream(peer.ID(), stream) + ps.host.NotifyAll(conn)*/ handlerErr := <-errCh log.Info("pubsub's handler ends", "err", handlerErr) @@ -98,17 +96,16 @@ func (ps *PubSub) Protocols() []p2p.Protocol { } } -func NewPubSub(localNode *enode.Node, server *p2p.Server) *PubSub { - network := p2p.NewNetwork(server) +func NewPubSub(localNode *enode.Node, server *p2p.PubSubServer) *PubSub { + /*network := p2p.NewNetwork(server) host := p2p.NewHost(localNode, network) gossipSub, err := pubsub.NewGossipSub(context.Background(), host) if err != nil { panic("Failed to NewGossipSub: " + err.Error()) - } + }*/ return &PubSub{ - ps: gossipSub, - host: host, + ps: server, msgCh: make(chan *RGMsg), topics: make(map[string]map[*pubsub.Topic]struct{}), mySubs: make(map[string]map[*pubsub.Subscription]struct{}), diff --git a/node/node.go b/node/node.go index 724427d27d..8004ef21be 100644 --- a/node/node.go +++ b/node/node.go @@ -202,6 +202,8 @@ func (n *Node) Start() error { } running := &p2p.Server{Config: n.serverConfig} + pubSubServer := p2p.NewPubSubServer(nil, running) + running.SetPubSubServer(pubSubServer) n.log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name) // Otherwise copy and specialize the P2P configuration @@ -215,7 +217,7 @@ func (n *Node) Start() error { EventMux: n.eventmux, AccountManager: n.accman, serverConfig: n.serverConfig, - p2pServer: running, + pubSubServer: pubSubServer, } for kind, s := range services { // copy needed for threaded access ctx.services[kind] = s diff --git a/node/service.go b/node/service.go index 1e2a0696db..b0da5121e0 100644 --- a/node/service.go +++ b/node/service.go @@ -39,7 +39,7 @@ type ServiceContext struct { EventMux *event.TypeMux // Event multiplexer used for decoupled notifications AccountManager *accounts.Manager // Account manager created by the node. serverConfig p2p.Config - p2pServer *p2p.Server + pubSubServer *p2p.PubSubServer } func NewServiceContext(config *Config, services map[reflect.Type]Service, EventMux *event.TypeMux, AccountManager *accounts.Manager) *ServiceContext { @@ -123,8 +123,8 @@ func (ctx *ServiceContext) ExtRPCEnabled() bool { return ctx.config.ExtRPCEnabled() } -func (ctx *ServiceContext) P2PServer() *p2p.Server { - return ctx.p2pServer +func (ctx *ServiceContext) PubSubServer() *p2p.PubSubServer { + return ctx.pubSubServer } // ServiceConstructor is the function signature of the constructors needed to be diff --git a/p2p/discover.go b/p2p/discover.go index fe3593b017..9b2bd88cea 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -53,7 +53,7 @@ func (srv *Server) SubscribeTopic(ctx context.Context, topic string) { // find if we have peers who are subscribed to the same subnet func (srv *Server) validPeersExist(subnetTopic string) bool { - numOfPeers := srv.subServer.ps.ListPeers(subnetTopic) + numOfPeers := srv.pubSubServer.pubsub.ListPeers(subnetTopic) return len(numOfPeers) >= srv.Config.MinimumPeersPerTopic } @@ -76,7 +76,7 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, thresho iterator := srv.DiscV5.RandomNodes() iterator = filterNodes(ctx, iterator, srv.filterPeerForTopic(srv.topicToIdx(topic))) - currNum := len(srv.subServer.ps.ListPeers(topic)) + currNum := len(srv.pubSubServer.pubsub.ListPeers(topic)) wg := new(sync.WaitGroup) for { if err := ctx.Err(); err != nil { @@ -91,7 +91,7 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, thresho } // Wait for all dials to be completed. wg.Wait() - currNum = len(srv.subServer.ps.ListPeers(topic)) + currNum = len(srv.pubSubServer.pubsub.ListPeers(topic)) } return true, nil } diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go new file mode 100644 index 0000000000..566b060778 --- /dev/null +++ b/p2p/pubsub_server.go @@ -0,0 +1,25 @@ +package p2p + +import ( + "context" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + pubsub2 "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" +) + +type PubSubServer struct { + p2pServer *Server + pubsub *pubsub2.PubSub +} + +func NewPubSubServer(localNode *enode.Node, p2pServer *Server) *PubSubServer { + network := NewNetwork(p2pServer) + host := NewHost(localNode, network) + gossipSub, err := pubsub2.NewGossipSub(context.Background(), host) + if err != nil { + panic("Failed to NewGossipSub: " + err.Error()) + } + return &PubSubServer{ + p2pServer: p2pServer, + pubsub: gossipSub, + } +} diff --git a/p2p/server.go b/p2p/server.go index 25805c3d9b..3a393a9fd7 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -224,6 +224,8 @@ type Server struct { consensus bool addconsensus chan *enode.Node removeconsensus chan *enode.Node + + pubSubServer *PubSubServer } type peerOpFunc func(map[enode.ID]*Peer) @@ -1299,3 +1301,7 @@ func (srv *Server) watching() { } } } + +func (srv *Server) SetPubSubServer(pss *PubSubServer) { + srv.pubSubServer = pss +} From b704ef382450fdcd20f1ffae6bebac3b163e8fec Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 10 Nov 2021 15:41:29 +0800 Subject: [PATCH 035/299] update dial --- p2p/dial.go | 155 ++++++++++++++++-------------------------------- p2p/discover.go | 8 ++- p2p/server.go | 35 +++++++---- 3 files changed, 78 insertions(+), 120 deletions(-) diff --git a/p2p/dial.go b/p2p/dial.go index 6667b1e238..3975502ff5 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -95,19 +95,17 @@ var ( // type dialScheduler struct { dialConfig - setupFunc dialSetupFunc - wg sync.WaitGroup - cancel context.CancelFunc - ctx context.Context - nodesIn chan *enode.Node - doneCh chan *dialTask - addStaticCh chan *enode.Node - remStaticCh chan *enode.Node - addPeerCh chan *conn - remPeerCh chan *conn - - addconsensus chan *enode.Node - removeconsensus chan *enode.Node + setupFunc dialSetupFunc + wg sync.WaitGroup + cancel context.CancelFunc + ctx context.Context + nodesIn chan *enode.Node + consensusNodesIn chan *dialTask + doneCh chan *dialTask + addStaticCh chan *enode.Node + remStaticCh chan *enode.Node + addPeerCh chan *conn + remPeerCh chan *conn // Everything below here belongs to loop and // should only be accessed by code on the loop goroutine. @@ -122,9 +120,6 @@ type dialScheduler struct { static map[enode.ID]*dialTask staticPool []*dialTask - consensus map[enode.ID]*dialTask - consensusPool []*dialTask - // The dial history keeps recently dialed nodes. Members of history are not dialed. history expHeap historyTimer mclock.Timer @@ -171,20 +166,18 @@ func (cfg dialConfig) withDefaults() dialConfig { func newDialScheduler(config dialConfig, it enode.Iterator, setupFunc dialSetupFunc) *dialScheduler { d := &dialScheduler{ - dialConfig: config.withDefaults(), - setupFunc: setupFunc, - dialing: make(map[enode.ID]*dialTask), - static: make(map[enode.ID]*dialTask), - peers: make(map[enode.ID]struct{}), - doneCh: make(chan *dialTask), - nodesIn: make(chan *enode.Node), - addStaticCh: make(chan *enode.Node), - remStaticCh: make(chan *enode.Node), - addPeerCh: make(chan *conn), - remPeerCh: make(chan *conn), - addconsensus: make(chan *enode.Node), - removeconsensus: make(chan *enode.Node), - consensus: make(map[enode.ID]*dialTask), + dialConfig: config.withDefaults(), + setupFunc: setupFunc, + dialing: make(map[enode.ID]*dialTask), + static: make(map[enode.ID]*dialTask), + peers: make(map[enode.ID]struct{}), + doneCh: make(chan *dialTask), + nodesIn: make(chan *enode.Node), + consensusNodesIn: make(chan *dialTask), + addStaticCh: make(chan *enode.Node), + remStaticCh: make(chan *enode.Node), + addPeerCh: make(chan *conn), + remPeerCh: make(chan *conn), } d.lastStatsLog = d.clock.Now() d.ctx, d.cancel = context.WithCancel(context.Background()) @@ -216,19 +209,20 @@ func (d *dialScheduler) removeStatic(n *enode.Node) { } } -func (d *dialScheduler) addConsensus(n *enode.Node) { +func (d *dialScheduler) addConsensus(n *dialTask) { select { - case d.addconsensus <- n: + case d.consensusNodesIn <- n: case <-d.ctx.Done(): } } +/* func (d *dialScheduler) removeConsensus(n *enode.Node) { select { case d.removeconsensus <- n: case <-d.ctx.Done(): } -} +}*/ /* func (d *dialScheduler) removeConsensusFromQueue(n *enode.Node) { @@ -264,9 +258,18 @@ func (d *dialScheduler) loop(it enode.Iterator) { loop: for { + select { + case task := <-d.consensusNodesIn: + if err := d.checkDial(task.dest); err != nil { + d.log.Trace("Discarding dial candidate", "id", task.dest.ID(), "ip", task.dest.IP(), "reason", err) + } else { + d.startDial(task) + } + continue + default: + } // Launch new dials if slots are available. slots := d.freeDialSlots() - slots -= d.startConsensusDials(slots) slots -= d.startStaticDials(slots) if slots > 0 { nodesCh = d.nodesIn @@ -281,7 +284,7 @@ loop: if err := d.checkDial(node); err != nil { d.log.Trace("Discarding dial candidate", "id", node.ID(), "ip", node.IP(), "reason", err) } else { - d.startDial(newDialTask(node, dynDialedConn)) + d.startDial(newDialTask(node, dynDialedConn, nil)) } case task := <-d.doneCh: @@ -289,9 +292,12 @@ loop: delete(d.dialing, id) d.updateStaticPool(id) d.doneSinceLastLog++ + if task.doneHook != nil { + task.doneHook() + } case c := <-d.addPeerCh: - if c.is(dynDialedConn) || c.is(staticDialedConn) { + if c.is(dynDialedConn) || c.is(staticDialedConn) || c.is(consensusDialedConn) { d.dialPeers++ } id := c.node.ID() @@ -304,7 +310,7 @@ loop: // TODO: cancel dials to connected peers case c := <-d.remPeerCh: - if c.is(dynDialedConn) || c.is(staticDialedConn) { + if c.is(dynDialedConn) || c.is(staticDialedConn) || c.is(consensusDialedConn) { d.dialPeers-- } delete(d.peers, c.node.ID()) @@ -317,7 +323,7 @@ loop: if exists { continue loop } - task := newDialTask(node, staticDialedConn) + task := newDialTask(node, staticDialedConn, nil) d.static[id] = task if d.checkDial(node) == nil { d.addToStaticPool(task) @@ -333,29 +339,6 @@ loop: d.removeFromStaticPool(task.staticPoolIndex) } } - case node := <-d.addconsensus: - id := node.ID() - _, exists := d.consensus[id] - d.log.Trace("Adding consensus node", "id", id, "ip", node.IP(), "added", !exists) - if exists { - continue loop - } - task := newDialTask(node, consensusDialedConn) - d.consensus[id] = task - //todo if Consensus task too musch ,we may cut some - if d.checkDial(node) == nil { - d.addToConsensusPool(task) - } - case node := <-d.removeconsensus: - id := node.ID() - task := d.consensus[id] - d.log.Trace("Removing consensus node", "id", id, "ok", task != nil) - if task != nil { - delete(d.consensus, id) - if task.staticPoolIndex >= 0 { - d.removeFromConsensusPool(task.staticPoolIndex) - } - } case <-historyExp: d.expireHistory() @@ -508,45 +491,6 @@ func (d *dialScheduler) removeFromStaticPool(idx int) { task.staticPoolIndex = -1 } -func (d *dialScheduler) startConsensusDials(n int) (started int) { - for started = 0; started < n && len(d.consensusPool) > 0; started++ { - //todo if rand here? - idx := d.rand.Intn(len(d.consensusPool)) - task := d.consensusPool[idx] - d.startDial(task) - d.removeFromConsensusPool(idx) - } - return started -} - -// updateConsensusPool attempts to move the given consensus dial back into consensusPool. -func (d *dialScheduler) updateConsensusPool(id enode.ID) { - task, ok := d.consensus[id] - if ok && task.staticPoolIndex < 0 && d.checkDial(task.dest) == nil { - d.addToConsensusPool(task) - } -} - -func (d *dialScheduler) addToConsensusPool(task *dialTask) { - if task.staticPoolIndex >= 0 { - panic("attempt to add task to consensusPool twice") - } - d.consensusPool = append(d.consensusPool, task) - task.staticPoolIndex = len(d.consensusPool) - 1 -} - -// removeFromConsensusPool removes the task at idx from consensusPool. It does that by moving the -// current last element of the pool to idx and then shortening the pool by one. -func (d *dialScheduler) removeFromConsensusPool(idx int) { - task := d.consensusPool[idx] - end := len(d.consensusPool) - 1 - d.consensusPool[idx] = d.consensusPool[end] - d.consensusPool[idx].staticPoolIndex = idx - d.consensusPool[end] = nil - d.consensusPool = d.consensusPool[:end] - task.staticPoolIndex = -1 -} - // startDial runs the given dial task in a separate goroutine. func (d *dialScheduler) startDial(task *dialTask) { d.log.Trace("Starting p2p dial", "id", task.dest.ID(), "ip", task.dest.IP(), "flag", task.flags) @@ -568,10 +512,11 @@ type dialTask struct { dest *enode.Node lastResolved mclock.AbsTime resolveDelay time.Duration + doneHook func() } -func newDialTask(dest *enode.Node, flags connFlag) *dialTask { - return &dialTask{dest: dest, flags: flags, staticPoolIndex: -1} +func newDialTask(dest *enode.Node, flags connFlag, done func()) *dialTask { + return &dialTask{dest: dest, flags: flags, staticPoolIndex: -1, doneHook: done} } type dialError struct { @@ -585,8 +530,8 @@ func (t *dialTask) run(d *dialScheduler) { err := t.dial(d, t.dest) if err != nil { - // For static nodes, resolve one more time if dialing fails. - if _, ok := err.(*dialError); ok && t.flags&staticDialedConn != 0 { + // For static and consensus nodes, resolve one more time if dialing fails. + if _, ok := err.(*dialError); ok && (t.flags&staticDialedConn != 0 || t.flags&consensusDialedConn != 0) { if t.resolve(d) { t.dial(d, t.dest) } @@ -595,7 +540,7 @@ func (t *dialTask) run(d *dialScheduler) { } func (t *dialTask) needResolve() bool { - return t.flags&staticDialedConn != 0 && t.dest.IP() == nil + return (t.flags&staticDialedConn != 0 || t.flags&consensusDialedConn != 0) && t.dest.IP() == nil } // resolve attempts to find the current endpoint for the destination diff --git a/p2p/discover.go b/p2p/discover.go index 9b2bd88cea..a0832f24ff 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -86,8 +86,12 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, thresho break } nodes := enode.ReadNodes(iterator, int(srv.Config.MinimumPeersInTopicSearch)) - for _, node := range nodes { - srv.AddConsensusPeer(node) + + for i, _ := range nodes { + wg.Add(1) + srv.AddConsensusPeerWithDone(nodes[i], func() { + wg.Done() + }) } // Wait for all dials to be completed. wg.Wait() diff --git a/p2p/server.go b/p2p/server.go index 3a393a9fd7..80fb4bef94 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -222,7 +222,7 @@ type Server struct { eventMux *event.TypeMux consensus bool - addconsensus chan *enode.Node + addconsensus chan *dialTask removeconsensus chan *enode.Node pubSubServer *PubSubServer @@ -391,7 +391,14 @@ func (srv *Server) RemovePeer(node *enode.Node) { // attempt to reconnect the peer. func (srv *Server) AddConsensusPeer(node *enode.Node) { select { - case srv.addconsensus <- node: + case srv.addconsensus <- newDialTask(node, consensusDialedConn, nil): + case <-srv.quit: + } +} + +func (srv *Server) AddConsensusPeerWithDone(node *enode.Node, done func()) { + select { + case srv.addconsensus <- newDialTask(node, consensusDialedConn, done): case <-srv.quit: } } @@ -781,22 +788,24 @@ running: // The server was stopped. Run the cleanup logic. break running - case n := <-srv.addconsensus: + case task := <-srv.addconsensus: // This channel is used by AddConsensusNode to add an enode // to the consensus node set. - srv.log.Trace("Adding consensus node", "node", n) + srv.log.Trace("Adding consensus node", "node", task.dest) - if id := n.ID(); !bytes.Equal(crypto.Keccak256(srv.ourHandshake.ID), id[:]) { + if id := task.dest.ID(); !bytes.Equal(crypto.Keccak256(srv.ourHandshake.ID), id[:]) { srv.log.Debug("We are become an consensus node") srv.consensus = true } else { - srv.dialsched.addConsensus(n) - } - consensusNodes[n.ID()] = true - if p, ok := peers[n.ID()]; ok { - srv.log.Debug("Add consensus flag", "peer", n.ID) - p.rw.set(consensusDialedConn, true) + consensusNodes[task.dest.ID()] = true + if p, ok := peers[task.dest.ID()]; ok { + srv.log.Debug("Add consensus flag", "peer", task.dest.ID()) + p.rw.set(consensusDialedConn, true) + } else { + srv.dialsched.addConsensus(task) + } } + case n := <-srv.removeconsensus: // This channel is used by RemoveConsensusNode to remove an enode // from the consensus node set. @@ -805,7 +814,7 @@ running: srv.log.Debug("We are not an consensus node") srv.consensus = false } - srv.dialsched.removeConsensus(n) + //srv.dialsched.removeConsensus(n) if _, ok := consensusNodes[n.ID()]; ok { delete(consensusNodes, n.ID()) } @@ -814,7 +823,7 @@ running: if !p.rw.is(staticDialedConn | trustedConn | inboundConn) { p.rw.set(dynDialedConn, true) } - srv.log.Debug("Remove consensus flag", "peer", n.ID, "consensus", srv.consensus) + srv.log.Debug("Remove consensus flag", "peer", n.ID(), "consensus", srv.consensus) if len(peers) > srv.MaxPeers && !p.rw.is(staticDialedConn|trustedConn) { srv.log.Debug("Disconnect non-consensus node", "peer", n.ID(), "flags", p.rw.flags, "peers", len(peers), "consensus", srv.consensus) p.Disconnect(DiscRequested) From 4cf87791ac0f28409d6e6a30cecebafe5fbd9f88 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Wed, 10 Nov 2021 16:32:50 +0800 Subject: [PATCH 036/299] Improve pubsub under cbft --- consensus/cbft/cbft.go | 3 +- consensus/cbft/network/pubsub.go | 115 ++++++++++++++++++++----------- node/node.go | 4 +- p2p/pubsub_server.go | 28 +++++++- 4 files changed, 106 insertions(+), 44 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 3979ac3a7d..15179620fd 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -190,7 +190,6 @@ type Cbft struct { // New returns a new CBFT. func New(sysConfig *params.CbftConfig, optConfig *ctypes.OptionsConfig, eventMux *event.TypeMux, ctx *node.ServiceContext) *Cbft { - localNode := enode.NewV4(&ctx.NodePriKey().PublicKey, nil, 0, 0) cbft := &Cbft{ config: ctypes.Config{Sys: sysConfig, Option: optConfig}, eventMux: eventMux, @@ -211,7 +210,7 @@ func New(sysConfig *params.CbftConfig, optConfig *ctypes.OptionsConfig, eventMux statQueues: make(map[common.Hash]map[string]int), messageHashCache: mapset.NewSet(), netLatencyMap: make(map[string]*list.List), - pubSub: network.NewPubSub(localNode, ctx.PubSubServer()), + pubSub: network.NewPubSub(ctx.PubSubServer()), } if evPool, err := evidence.NewEvidencePool(ctx, optConfig.EvidenceDir); err == nil { diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index bcd9ea15b5..a1e763dce4 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -17,11 +17,14 @@ package network import ( - "fmt" + "context" + "errors" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" + "github.com/AlayaNetwork/Alaya-Go/rlp" + "sync" ) const ( @@ -36,6 +39,11 @@ const ( CbftPubSubProtocolLength = 10 ) +var ( + ErrExistsTopic = errors.New("topic already exists") + ErrNotExistsTopic = errors.New("topic does not exist") +) + // Group consensus message type RGMsg struct { Code uint64 @@ -43,29 +51,21 @@ type RGMsg struct { } type PubSub struct { - ps *p2p.PubSubServer + pss *p2p.PubSubServer // Messages of all topics subscribed are sent out from this channel uniformly msgCh chan *RGMsg // All topics subscribed - topics map[string]map[*pubsub.Topic]struct{} + topics map[string]*pubsub.Topic // The set of topics we are subscribed to - mySubs map[string]map[*pubsub.Subscription]struct{} + mySubs map[string]*pubsub.Subscription + sync.Mutex } // Protocol.Run() func (ps *PubSub) handler(peer *p2p.Peer, rw p2p.MsgReadWriter) error { - conn := p2p.NewConn(peer.Node(), peer.Inbound()) - - // Wait for the connection to exit - errCh := make(chan error) - - stream := p2p.NewStream(conn, rw, errCh, "") - conn.SetStream(stream) - - /*ps.host.SetStream(peer.ID(), stream) - ps.host.NotifyAll(conn)*/ + errCh := ps.pss.NewConn(peer, rw) handlerErr := <-errCh log.Info("pubsub's handler ends", "err", handlerErr) @@ -96,49 +96,86 @@ func (ps *PubSub) Protocols() []p2p.Protocol { } } -func NewPubSub(localNode *enode.Node, server *p2p.PubSubServer) *PubSub { - /*network := p2p.NewNetwork(server) - host := p2p.NewHost(localNode, network) - gossipSub, err := pubsub.NewGossipSub(context.Background(), host) - if err != nil { - panic("Failed to NewGossipSub: " + err.Error()) - }*/ - +func NewPubSub(server *p2p.PubSubServer) *PubSub { return &PubSub{ - ps: server, + pss: server, msgCh: make(chan *RGMsg), - topics: make(map[string]map[*pubsub.Topic]struct{}), - mySubs: make(map[string]map[*pubsub.Subscription]struct{}), + topics: make(map[string]*pubsub.Topic), + mySubs: make(map[string]*pubsub.Subscription), } } //Subscribe subscribe a topic func (ps *PubSub) Subscribe(topic string) error { - //if err := ps.ps.PublishMsg(topic); err != nil { - // return err - //} - //ps.topics = append(ps.topics, topic) + ps.Lock() + defer ps.Unlock() + if _, ok := ps.mySubs[topic]; ok { + return ErrExistsTopic + } + t, err := ps.pss.PubSub().Join(topic) + if err != nil { + return err + } + subscription, err := t.Subscribe() + if err != nil { + return err + } + ps.topics[topic] = t + ps.mySubs[topic] = subscription + + go ps.listen(subscription) return nil } -//UnSubscribe a topic -func (ps *PubSub) Cancel(topic string) error { - sm := ps.mySubs[topic] - if sm != nil { - for s, _ := range sm { - if s != nil { - s.Cancel() +func (ps *PubSub) listen(s *pubsub.Subscription) { + for { + msg, err := s.Next(context.Background()) + if err != nil { + if err != pubsub.ErrSubscriptionCancelled { + ps.Cancel(s.Topic()) + } + log.Error("Failed to listen to topic message", "error", err) + return + } + if msg != nil { + var msgData RGMsg + if err := rlp.DecodeBytes(msg.Data, &msgData); err != nil { + log.Error("Failed to parse topic message", "error", err) + ps.Cancel(s.Topic()) + return } + ps.msgCh <- &msgData } - return nil } - return fmt.Errorf("Can not find", "topic", topic) } -func (ps *PubSub) Publish(data *RGMsg) error { +//UnSubscribe a topic +func (ps *PubSub) Cancel(topic string) error { + ps.Lock() + defer ps.Unlock() + sb := ps.mySubs[topic] + if sb != nil { + sb.Cancel() + delete(ps.mySubs, topic) + delete(ps.topics, topic) + } return nil } +func (ps *PubSub) Publish(topic string, data *RGMsg) error { + ps.Lock() + defer ps.Unlock() + t := ps.topics[topic] + if t == nil { + return ErrNotExistsTopic + } + env, err := rlp.EncodeToBytes(data) + if err != nil { + return err + } + return t.Publish(context.Background(), env) +} + func (ps *PubSub) Receive() *RGMsg { return <-ps.msgCh } diff --git a/node/node.go b/node/node.go index 8004ef21be..44548668b1 100644 --- a/node/node.go +++ b/node/node.go @@ -19,6 +19,7 @@ package node import ( "errors" "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "math/big" "net" "os" @@ -202,7 +203,8 @@ func (n *Node) Start() error { } running := &p2p.Server{Config: n.serverConfig} - pubSubServer := p2p.NewPubSubServer(nil, running) + localNode := enode.NewV4(&n.serverConfig.PrivateKey.PublicKey, nil, 0, 0) + pubSubServer := p2p.NewPubSubServer(localNode, running) running.SetPubSubServer(pubSubServer) n.log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name) diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go index 566b060778..9850520d2a 100644 --- a/p2p/pubsub_server.go +++ b/p2p/pubsub_server.go @@ -8,7 +8,8 @@ import ( type PubSubServer struct { p2pServer *Server - pubsub *pubsub2.PubSub + pubSub *pubsub2.PubSub + host *Host } func NewPubSubServer(localNode *enode.Node, p2pServer *Server) *PubSubServer { @@ -18,8 +19,31 @@ func NewPubSubServer(localNode *enode.Node, p2pServer *Server) *PubSubServer { if err != nil { panic("Failed to NewGossipSub: " + err.Error()) } + return &PubSubServer{ p2pServer: p2pServer, - pubsub: gossipSub, + pubSub: gossipSub, } } + +func (pss *PubSubServer) Host() *Host { + return pss.host +} + +func (pss *PubSubServer) PubSub() *pubsub2.PubSub { + return pss.pubSub +} + +func (pss *PubSubServer) NewConn(peer *Peer, rw MsgReadWriter) chan error { + conn := NewConn(peer.Node(), peer.Inbound()) + + // Wait for the connection to exit + errCh := make(chan error) + + stream := NewStream(conn, rw, errCh, "") + conn.SetStream(stream) + + pss.Host().SetStream(peer.ID(), stream) + pss.Host().NotifyAll(conn) + return errCh +} From e8234ffc54ccfce767fdeb6939233409bd3fda99 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Thu, 11 Nov 2021 10:27:42 +0800 Subject: [PATCH 037/299] Calculate the size of the message --- p2p/pubsub/message/mesage.go | 82 ++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/p2p/pubsub/message/mesage.go b/p2p/pubsub/message/mesage.go index 06547dc901..877e25fd25 100644 --- a/p2p/pubsub/message/mesage.go +++ b/p2p/pubsub/message/mesage.go @@ -9,7 +9,23 @@ type RPC struct { } func (m *RPC) Size() (n int) { - return 0 + if m == nil { + return 0 + } + if m.Subscriptions != nil { + for _, v := range m.Subscriptions { + n += v.Size() + } + } + if m.Publish != nil { + for _, v := range m.Publish { + n += v.Size() + } + } + if m.Control != nil { + n += m.Control.Size() + } + return n } func (m *RPC) GetSubscriptions() []*RPC_SubOpts { @@ -57,7 +73,7 @@ func (m *RPC_SubOpts) Size() (n int) { return 0 } if m.Subscribe != nil { - n += 2 + n += 1 } if m.Topicid != nil { n += 1 + (len(*m.Topicid)) @@ -174,6 +190,33 @@ func (m *ControlMessage) GetPrune() []*ControlPrune { return nil } +func (m *ControlMessage) Size() (n int) { + if m == nil { + return 0 + } + if m.Ihave != nil { + for _, v := range m.Ihave { + n += v.Size() + } + } + if m.Iwant != nil { + for _, v := range m.Iwant { + n += v.Size() + } + } + if m.Graft != nil { + for _, v := range m.Graft { + n += v.Size() + } + } + if m.Prune != nil { + for _, v := range m.Prune { + n += v.Size() + } + } + return n +} + type ControlIHave struct { TopicID *string MessageIDs []string @@ -243,7 +286,13 @@ func (m *ControlGraft) GetTopicID() string { } func (m *ControlGraft) Size() (n int) { - return 0 + if m == nil { + return 0 + } + if m.TopicID != nil { + n += len(*m.TopicID) + } + return n } type ControlPrune struct { @@ -274,7 +323,21 @@ func (m *ControlPrune) GetBackoff() uint64 { } func (m *ControlPrune) Size() (n int) { - return 0 + if m == nil { + return 0 + } + if m.TopicID != nil { + n += len(*m.TopicID) + } + if m.Backoff != nil { + n += 8 + } + if m.Peers != nil { + for _, p := range m.Peers { + n += p.Size() + } + } + return n } type PeerInfo struct { @@ -295,3 +358,14 @@ func (m *PeerInfo) GetSignedPeerRecord() []byte { } return nil } + +func (m *PeerInfo) Size() (n int) { + if m == nil { + return 0 + } + n += len(m.PeerID) + if m.SignedPeerRecord != nil { + n += len(m.SignedPeerRecord) + } + return n +} From 06994da5e6dd7b67ca9339483d4c456f3c20e61f Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 11 Nov 2021 16:35:16 +0800 Subject: [PATCH 038/299] fix compile error --- p2p/pubsub/pubsub.go | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index ef0e87472a..48ecf55e90 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -11,8 +11,6 @@ import ( "sync/atomic" "time" - "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/discovery" - "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" @@ -433,20 +431,20 @@ func WithBlacklist(b Blacklist) Option { } // WithDiscovery provides a discovery mechanism used to bootstrap and provide peers into PubSub -func WithDiscovery(d discovery.Discovery, opts ...DiscoverOpt) Option { - return func(p *PubSub) error { - discoverOpts := defaultDiscoverOptions() - for _, opt := range opts { - err := opt(discoverOpts) - if err != nil { - return err - } - } - p.disc.discovery = &pubSubDiscovery{Discovery: d, opts: discoverOpts.opts} - p.disc.options = discoverOpts - return nil - } -} +//func WithDiscovery(d discovery.Discovery, opts ...DiscoverOpt) Option { +// return func(p *PubSub) error { +// discoverOpts := defaultDiscoverOptions() +// for _, opt := range opts { +// err := opt(discoverOpts) +// if err != nil { +// return err +// } +// } +// p.disc.discovery = &pubSubDiscovery{Discovery: d, opts: discoverOpts.opts} +// p.disc.options = discoverOpts +// return nil +// } +//} // WithEventTracer provides a tracer for the pubsub system func WithEventTracer(tracer EventTracer) Option { From 105d206bd8e3046c99bfa1598df8debde8a016bb Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 11 Nov 2021 16:55:21 +0800 Subject: [PATCH 039/299] fix compile error --- go.mod | 3 ++- p2p/discover.go | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index ad36b7771d..ce2cf83f62 100644 --- a/go.mod +++ b/go.mod @@ -55,8 +55,9 @@ require ( github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 github.com/peterh/liner v1.0.1-0.20170902204657-a37ad3984311 github.com/pkg/errors v0.9.1 + github.com/prometheus/common v0.4.1 github.com/prometheus/tsdb v0.10.0 - github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 // indirect + github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 github.com/rjeczalik/notify v0.9.1 github.com/robertkrimen/otto v0.0.0-20170205013659-6a77b7cbc37d github.com/robfig/cron v1.2.1-0.20190616124356-61d93e07d1be diff --git a/p2p/discover.go b/p2p/discover.go index a0832f24ff..1f7c0bed3f 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -53,7 +53,7 @@ func (srv *Server) SubscribeTopic(ctx context.Context, topic string) { // find if we have peers who are subscribed to the same subnet func (srv *Server) validPeersExist(subnetTopic string) bool { - numOfPeers := srv.pubSubServer.pubsub.ListPeers(subnetTopic) + numOfPeers := srv.pubSubServer.PubSub().ListPeers(subnetTopic) return len(numOfPeers) >= srv.Config.MinimumPeersPerTopic } @@ -76,7 +76,7 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, thresho iterator := srv.DiscV5.RandomNodes() iterator = filterNodes(ctx, iterator, srv.filterPeerForTopic(srv.topicToIdx(topic))) - currNum := len(srv.pubSubServer.pubsub.ListPeers(topic)) + currNum := len(srv.pubSubServer.PubSub().ListPeers(topic)) wg := new(sync.WaitGroup) for { if err := ctx.Err(); err != nil { @@ -95,7 +95,7 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, thresho } // Wait for all dials to be completed. wg.Wait() - currNum = len(srv.pubSubServer.pubsub.ListPeers(topic)) + currNum = len(srv.pubSubServer.PubSub().ListPeers(topic)) } return true, nil } From 1c7741a753444f8e28e784537a354d4821eff396 Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 11 Nov 2021 17:09:47 +0800 Subject: [PATCH 040/299] fix compile error --- p2p/host.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/p2p/host.go b/p2p/host.go index aa1aff0f6c..a4408e4439 100644 --- a/p2p/host.go +++ b/p2p/host.go @@ -28,10 +28,6 @@ func (h *Host) ID() *enode.Node { return h.node } -func (h *Host) Peerstore() pubsub.Peerstore { - return nil -} - func (h *Host) Network() pubsub.Network { return h.network } From 43bfd7314bb0319da13716a8beb679f21bb2fd8d Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Thu, 11 Nov 2021 18:01:42 +0800 Subject: [PATCH 041/299] Add a process for sending and processing grouped aggregate messages --- consensus/cbft/cbft.go | 282 ++++++++-- consensus/cbft/cbft_test.go | 80 ++- consensus/cbft/consensus_process.go | 554 ++++++++++++++++++- consensus/cbft/protocols/protocol.go | 247 ++++++++- consensus/cbft/rg_broadcast.go | 372 +++++++++++++ consensus/cbft/rules/safety_rules.go | 119 +++- consensus/cbft/rules/vote_rules.go | 1 - consensus/cbft/state/state.go | 732 +++++++++++++++++++++++-- consensus/cbft/state/state_test.go | 114 ++++ consensus/cbft/sync_process.go | 13 +- consensus/cbft/types/cache_test.go | 8 + consensus/cbft/types/crypto.go | 116 +++- consensus/cbft/types/crypto_test.go | 61 +++ consensus/cbft/types/message.go | 3 + consensus/cbft/utils/bit_array.go | 66 +++ consensus/cbft/utils/bit_array_test.go | 259 +++++++++ consensus/cbft/validator/validator.go | 36 +- 17 files changed, 2918 insertions(+), 145 deletions(-) create mode 100644 consensus/cbft/rg_broadcast.go diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 3979ac3a7d..d74613d423 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -182,6 +182,9 @@ type Cbft struct { netLatencyMap map[string]*list.List netLatencyLock sync.RWMutex + // RGMsg broadcast manager + RGBroadcastManager *RGBroadcastManager + //test insertBlockQCHook func(block *types.Block, qc *ctypes.QuorumCert) executeFinishHook func(index uint32) @@ -258,6 +261,9 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCacheWriter consensus. cbft.state.SetHighestLockBlock(block) cbft.state.SetHighestCommitBlock(block) + // init RGMsg broadcast manager + cbft.RGBroadcastManager = NewRGBroadcastManager(cbft, 1) // TODO cycleNumber + // init handler and router to process message. // cbft -> handler -> router. cbft.network = network.NewEngineManger(cbft) // init engineManager as handler. @@ -570,13 +576,17 @@ func (cbft *Cbft) handleConsensusMsg(info *ctypes.MsgInfo) error { switch msg := msg.(type) { case *protocols.PrepareBlock: - cbft.csPool.AddPrepareBlock(msg.BlockIndex, ctypes.NewInnerMsgInfo(info.Msg, info.PeerID)) + cbft.csPool.AddPrepareBlock(msg.BlockIndex, info) err = cbft.OnPrepareBlock(id, msg) case *protocols.PrepareVote: - cbft.csPool.AddPrepareVote(msg.BlockIndex, msg.ValidatorIndex, ctypes.NewInnerMsgInfo(info.Msg, info.PeerID)) + cbft.csPool.AddPrepareVote(msg.BlockIndex, msg.ValidatorIndex, info) err = cbft.OnPrepareVote(id, msg) case *protocols.ViewChange: err = cbft.OnViewChange(id, msg) + case *protocols.RGBlockQuorumCert: + err = cbft.OnRGBlockQuorumCert(id, msg) + case *protocols.RGViewChangeQuorumCert: + err = cbft.OnRGViewChangeQuorumCert(id, msg) } if err != nil { @@ -602,7 +612,7 @@ func (cbft *Cbft) handleSyncMsg(info *ctypes.MsgInfo) error { err = cbft.OnGetBlockQuorumCert(id, msg) case *protocols.BlockQuorumCert: - cbft.csPool.AddPrepareQC(msg.BlockQC.Epoch, msg.BlockQC.ViewNumber, msg.BlockQC.BlockIndex, ctypes.NewInnerMsgInfo(info.Msg, info.PeerID)) + cbft.csPool.AddPrepareQC(msg.BlockQC.Epoch, msg.BlockQC.ViewNumber, msg.BlockQC.BlockIndex, info) err = cbft.OnBlockQuorumCert(id, msg) case *protocols.GetPrepareVote: @@ -766,7 +776,7 @@ func (cbft *Cbft) OnSeal(block *types.Block, results chan<- *types.Block, stop < return } - me, err := cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.Node().ID()) + me, err := cbft.isCurrentValidator() if err != nil { cbft.log.Warn("Can not got the validator, seal fail", "epoch", cbft.state.Epoch(), "nodeID", cbft.Node().ID()) return @@ -783,7 +793,7 @@ func (cbft *Cbft) OnSeal(block *types.Block, results chan<- *types.Block, stop < ViewNumber: cbft.state.ViewNumber(), Block: block, BlockIndex: cbft.state.NextViewBlockIndex(), - ProposalIndex: uint32(me.Index), + ProposalIndex: me.Index, } // Next index is equal zero, This view does not produce a block. @@ -1166,7 +1176,7 @@ func (cbft *Cbft) OnShouldSeal(result chan error) { numValidators := cbft.validatorPool.Len(cbft.state.Epoch()) currentProposer := cbft.state.ViewNumber() % uint64(numValidators) - validator, err := cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.config.Option.Node.ID()) + validator, err := cbft.isCurrentValidator() if err != nil { cbft.log.Error("Should seal fail", "err", err) result <- err @@ -1290,10 +1300,21 @@ func (cbft *Cbft) HighestQCBlockBn() (uint64, common.Hash) { return cbft.state.HighestQCBlock().NumberU64(), cbft.state.HighestQCBlock().Hash() } +// threshold returns the minimum threshold for consensus among num nodes. +// (N -f) func (cbft *Cbft) threshold(num int) int { return num - (num-1)/3 } +func (cbft *Cbft) groupLen(epoch uint64, groupID uint32) int { + return cbft.validatorPool.LenByGroupID(epoch, groupID) +} + +func (cbft *Cbft) groupThreshold(epoch uint64, groupID uint32) int { + num := cbft.groupLen(epoch, groupID) + return num - (num-1)/3 +} + func (cbft *Cbft) commitBlock(commitBlock *types.Block, commitQC *ctypes.QuorumCert, lockBlock *types.Block, qcBlock *types.Block) { extra, err := ctypes.EncodeExtra(byte(cbftVersion), commitQC) if err != nil { @@ -1393,6 +1414,10 @@ func (cbft *Cbft) currentProposer() *cbfttypes.ValidateNode { return validator } +func (cbft *Cbft) getGroupByValidatorID(epoch uint64, nodeID enode.ID) (uint32, uint32, error) { + return cbft.validatorPool.GetGroupByValidatorID(epoch, nodeID) +} + func (cbft *Cbft) isProposer(epoch, viewNumber uint64, nodeIndex uint32) bool { if err := cbft.validatorPool.EnableVerifyEpoch(epoch); err != nil { return false @@ -1478,6 +1503,24 @@ func (cbft *Cbft) checkPrepareQC(msg ctypes.ConsensusMsg) error { if cm.BlockNumber != 0 && cm.PrepareQC == nil { return authFailedError{err: fmt.Errorf("viewChange need take PrepareQC, viewChange:%s", cm.String())} } + case *protocols.RGBlockQuorumCert: + if cm.BlockNum() == 1 && cm.ParentQC != nil { + return authFailedError{err: fmt.Errorf("RGBlockQuorumCert need not take PrepareQC, RGBlockQuorumCert:%s", cm.String())} + } + if cm.BlockNum() != 1 && cm.ParentQC == nil { + return authFailedError{err: fmt.Errorf("RGBlockQuorumCert need take PrepareQC, RGBlockQuorumCert:%s", cm.String())} + } + case *protocols.RGViewChangeQuorumCert: + viewChangeQC := cm.ViewChangeQC + prepareQCs := cm.PrepareQCs + for _, qc := range viewChangeQC.QCs { + if qc.BlockNumber == 0 && prepareQCs != nil && prepareQCs[qc.BlockHash] != nil { + return authFailedError{err: fmt.Errorf("RGViewChangeQuorumCert need not take PrepareQC, RGViewChangeQuorumCert:%s", cm.String())} + } + if qc.BlockNumber != 0 && (prepareQCs == nil || prepareQCs[qc.BlockHash] == nil) { + return authFailedError{err: fmt.Errorf("RGViewChangeQuorumCert need take PrepareQC, RGViewChangeQuorumCert:%s", cm.String())} + } + } default: return authFailedError{err: fmt.Errorf("invalid consensusMsg")} } @@ -1507,6 +1550,10 @@ func (cbft *Cbft) doubtDuplicate(msg ctypes.ConsensusMsg, node *cbfttypes.Valida return err } } + case *protocols.RGBlockQuorumCert: + cbft.log.Trace("RGBlockQuorumCert message does not need to check duplicate") + case *protocols.RGViewChangeQuorumCert: + cbft.log.Trace("RGViewChangeQuorumCert message does not need to check duplicate") default: return authFailedError{err: fmt.Errorf("invalid consensusMsg")} } @@ -1563,22 +1610,22 @@ func (cbft *Cbft) verifyConsensusMsg(msg ctypes.ConsensusMsg) (*cbfttypes.Valida oriNumber = localQC.BlockNumber oriHash = localQC.BlockHash - case *protocols.PrepareVote: + case *protocols.PrepareVote, *protocols.RGBlockQuorumCert: if cm.BlockNum() == 1 { return vnode, nil } - prepareQC = cm.ParentQC - if cm.BlockIndex == 0 { - _, localQC := cbft.blockTree.FindBlockAndQC(prepareQC.BlockHash, cm.BlockNumber-1) + prepareQC = cm.CheckQC() + if cm.BlockIndx() == 0 { + _, localQC := cbft.blockTree.FindBlockAndQC(prepareQC.BlockHash, cm.BlockNum()-1) if localQC == nil { - return nil, fmt.Errorf("parentBlock and parentQC not exists,number:%d,hash:%s", cm.BlockNumber-1, prepareQC.BlockHash.String()) + return nil, fmt.Errorf("parentBlock and parentQC not exists,number:%d,hash:%s", cm.BlockNum()-1, prepareQC.BlockHash.String()) } oriNumber = localQC.BlockNumber oriHash = localQC.BlockHash } else { - parentBlock := cbft.state.ViewBlockByIndex(cm.BlockIndex - 1) + parentBlock := cbft.state.ViewBlockByIndex(cm.BlockIndx() - 1) if parentBlock == nil { - return nil, fmt.Errorf("parentBlock not exists,blockIndex:%d", cm.BlockIndex-1) + return nil, fmt.Errorf("parentBlock not exists,blockIndex:%d", cm.BlockIndx()-1) } oriNumber = parentBlock.NumberU64() oriHash = parentBlock.Hash() @@ -1592,6 +1639,18 @@ func (cbft *Cbft) verifyConsensusMsg(msg ctypes.ConsensusMsg) (*cbfttypes.Valida prepareQC = cm.PrepareQC oriNumber = cm.BlockNumber oriHash = cm.BlockHash + + case *protocols.RGViewChangeQuorumCert: + viewChangeQC := cm.ViewChangeQC + prepareQCs := cm.PrepareQCs + for _, qc := range viewChangeQC.QCs { + if qc.BlockNumber != 0 { + if err := cbft.verifyPrepareQC(qc.BlockNumber, qc.BlockHash, prepareQCs[qc.BlockHash]); err != nil { + return nil, err + } + } + } + return vnode, nil } if err := cbft.verifyPrepareQC(oriNumber, oriHash, prepareQC); err != nil { @@ -1629,7 +1688,6 @@ func (cbft *Cbft) generatePrepareQC(votes map[uint32]*protocols.PrepareVote) *ct // Validator set prepareQC is the same as highestQC total := cbft.validatorPool.Len(cbft.state.Epoch()) - vSet := utils.NewBitArray(uint32(total)) vSet.SetIndex(vote.NodeIndex(), true) @@ -1649,7 +1707,7 @@ func (cbft *Cbft) generatePrepareQC(votes map[uint32]*protocols.PrepareVote) *ct for _, p := range votes { //Check whether two votes are equal if !vote.EqualState(p) { - cbft.log.Error(fmt.Sprintf("QuorumCert isn't same vote1:%s vote2:%s", vote.String(), p.String())) + cbft.log.Error(fmt.Sprintf("QuorumCert isn't same, vote1:%s vote2:%s", vote.String(), p.String())) return nil } if p.NodeIndex() != vote.NodeIndex() { @@ -1669,6 +1727,55 @@ func (cbft *Cbft) generatePrepareQC(votes map[uint32]*protocols.PrepareVote) *ct return qc } +func (cbft *Cbft) combinePrepareQC(qcs []*ctypes.QuorumCert) *ctypes.QuorumCert { + if len(qcs) == 0 { + return nil + } + + initqc := qcs[0] + + // Validator set prepareQC is the same as highestQC + total := cbft.validatorPool.Len(cbft.state.Epoch()) + vSet := utils.NewBitArray(uint32(total)) + vSet = vSet.Or(initqc.ValidatorSet) + + var aggSig bls.Sign + if err := aggSig.Deserialize(initqc.Signature.Bytes()); err != nil { + return nil + } + + qc := &ctypes.QuorumCert{ + Epoch: initqc.Epoch, + ViewNumber: initqc.ViewNumber, + BlockHash: initqc.BlockHash, + BlockNumber: initqc.BlockNumber, + BlockIndex: initqc.BlockIndex, + ValidatorSet: utils.NewBitArray(vSet.Size()), + } + for i := 1; i < len(qcs); i++ { + //Check whether two QuorumCert are equal + q := qcs[i] + if !initqc.EqualState(q) { + cbft.log.Error(fmt.Sprintf("QuorumCert isn't same, rgqc1:%s rgqc2:%s", initqc.String(), q.String())) + return nil + } + + var sig bls.Sign + err := sig.Deserialize(q.Signature.Bytes()) + if err != nil { + return nil + } + + aggSig.Add(&sig) + vSet = vSet.Or(q.ValidatorSet) + } + + qc.Signature.SetBytes(aggSig.Serialize()) + qc.ValidatorSet.Update(vSet) + log.Debug("Combine prepare qc", "hash", initqc.BlockHash, "number", initqc.BlockNumber, "qc", qc.String()) + return qc +} + func (cbft *Cbft) generateViewChangeQC(viewChanges map[uint32]*protocols.ViewChange) *ctypes.ViewChangeQC { type ViewChangeQC struct { cert *ctypes.ViewChangeQuorumCert @@ -1718,7 +1825,64 @@ func (cbft *Cbft) generateViewChangeQC(viewChanges map[uint32]*protocols.ViewCha q.cert.ValidatorSet.Update(q.ba) qc.QCs = append(qc.QCs, q.cert) } - log.Debug("Generate view change qc", "qc", qc.String()) + log.Debug("Generate viewChangeQC", "qc", qc.String()) + return qc +} + +func (cbft *Cbft) combineViewChangeQC(viewChangeQCs []*ctypes.ViewChangeQC) *ctypes.ViewChangeQC { + type ViewChangeQC struct { + cert *ctypes.ViewChangeQuorumCert + aggSig *bls.Sign + } + + total := uint32(cbft.validatorPool.Len(cbft.state.Epoch())) + + qcs := make(map[common.Hash]*ViewChangeQC) + + for _, rqc := range viewChangeQCs { + for _, qc := range rqc.QCs { + var aggSig bls.Sign + if err := aggSig.Deserialize(qc.Signature.Bytes()); err != nil { + return nil + } + if vqc, ok := qcs[qc.BlockHash]; !ok { + v := &ViewChangeQC{ + cert: &ctypes.ViewChangeQuorumCert{ + Epoch: qc.Epoch, + ViewNumber: qc.ViewNumber, + BlockHash: qc.BlockHash, + BlockNumber: qc.BlockNumber, + BlockEpoch: qc.BlockEpoch, + BlockViewNumber: qc.BlockViewNumber, + ValidatorSet: utils.NewBitArray(total), + }, + aggSig: &aggSig, + } + v.cert.ValidatorSet = v.cert.ValidatorSet.Or(qc.ValidatorSet) + qcs[qc.BlockHash] = v + } else { + if !vqc.cert.EqualState(qc) { + cbft.log.Error(fmt.Sprintf("QuorumCert isn't same, rgqc1:%s rgqc2:%s", vqc.cert.String(), qc.String())) + return nil + } + var sig bls.Sign + err := sig.Deserialize(qc.Signature.Bytes()) + if err != nil { + return nil + } + + vqc.aggSig.Add(&sig) + vqc.cert.ValidatorSet = vqc.cert.ValidatorSet.Or(qc.ValidatorSet) + } + } + } + + qc := &ctypes.ViewChangeQC{QCs: make([]*ctypes.ViewChangeQuorumCert, 0)} + for _, q := range qcs { + q.cert.Signature.SetBytes(q.aggSig.Serialize()) + qc.QCs = append(qc.QCs, q.cert) + } + log.Debug("Combine viewChangeQC", "qc", qc.String()) return qc } @@ -1734,37 +1898,49 @@ func (cbft *Cbft) verifyPrepareQC(oriNum uint64, oriHash common.Hash, qc *ctypes signsTotal := qc.Len() if signsTotal < threshold { - return authFailedError{err: fmt.Errorf("block qc has small number of signature total:%d, threshold:%d", signsTotal, threshold)} + return authFailedError{err: fmt.Errorf("block qc has small number of signature, total:%d, threshold:%d", signsTotal, threshold)} } - // check if the corresponding block QC + // check whether the corresponding block QC if oriNum != qc.BlockNumber || oriHash != qc.BlockHash { return authFailedError{ err: fmt.Errorf("verify prepare qc failed,not the corresponding qc,oriNum:%d,oriHash:%s,qcNum:%d,qcHash:%s", oriNum, oriHash.String(), qc.BlockNumber, qc.BlockHash.String())} } + if err := cbft.verifyQuorumCert(qc); err != nil { + cbft.log.Error("Verify failed", "qc", qc.String(), "validators", cbft.validatorPool.Validators(qc.Epoch).String()) + return authFailedError{err: fmt.Errorf("verify prepare qc failed: %v", err)} + } + return nil +} + +// This method simply verifies the correctness of the aggregated signature itself +func (cbft *Cbft) verifyQuorumCert(qc *ctypes.QuorumCert) error { + if qc == nil { + return fmt.Errorf("verify QuorumCert failed,qc is nil") + } + if err := cbft.validatorPool.EnableVerifyEpoch(qc.Epoch); err != nil { + return err + } + var cb []byte var err error if cb, err = qc.CannibalizeBytes(); err != nil { return err } - if err = cbft.validatorPool.VerifyAggSigByBA(qc.Epoch, qc.ValidatorSet, cb, qc.Signature.Bytes()); err != nil { - cbft.log.Error("Verify failed", "qc", qc.String(), "validators", cbft.validatorPool.Validators(qc.Epoch).String()) - return authFailedError{err: fmt.Errorf("verify prepare qc failed: %v", err)} - } - return nil + return cbft.validatorPool.VerifyAggSigByBA(qc.Epoch, qc.ValidatorSet, cb, qc.Signature.Bytes()) } -func (cbft *Cbft) validateViewChangeQC(viewChangeQC *ctypes.ViewChangeQC) error { +func (cbft *Cbft) validateViewChangeQC(viewChangeQC *ctypes.ViewChangeQC, validatorLimit int) error { - vcEpoch, _, _, _, _, _ := viewChangeQC.MaxBlock() + //vcEpoch, _, _, _, _, _ := viewChangeQC.MaxBlock() - maxLimit := cbft.validatorPool.Len(vcEpoch) - if len(viewChangeQC.QCs) > maxLimit { - return fmt.Errorf("viewchangeQC exceed validator max limit, total:%d, threshold:%d", len(viewChangeQC.QCs), maxLimit) + //maxLimit := cbft.validatorPool.Len(vcEpoch) + if len(viewChangeQC.QCs) > validatorLimit { + return fmt.Errorf("viewchangeQC exceed validator max limit, total:%d, validatorLimit:%d", len(viewChangeQC.QCs), validatorLimit) } // the threshold of validator on current epoch - threshold := cbft.threshold(maxLimit) + threshold := cbft.threshold(validatorLimit) // check signature number signsTotal := viewChangeQC.Len() if signsTotal < threshold { @@ -1794,6 +1970,24 @@ func (cbft *Cbft) validateViewChangeQC(viewChangeQC *ctypes.ViewChangeQC) error return err } +// This method simply verifies the correctness of the aggregated signature itself +func (cbft *Cbft) verifyViewChangeQuorumCerts(viewChangeQC *ctypes.ViewChangeQC) error { + for _, vc := range viewChangeQC.QCs { + var cb []byte + var err error + if cb, err = vc.CannibalizeBytes(); err != nil { + return fmt.Errorf("get cannibalize bytes failed") + } + if err = cbft.validatorPool.VerifyAggSigByBA(vc.Epoch, vc.ValidatorSet, cb, vc.Signature.Bytes()); err != nil { + cbft.log.Error("verify failed", "qc", vc.String(), "validators", cbft.validatorPool.Validators(vc.Epoch).String()) + + return authFailedError{err: fmt.Errorf("verify viewchange qc failed:number:%d,validators:%s,msg:%s,signature:%s,err:%v", + vc.BlockNumber, vc.ValidatorSet.String(), hexutil.Encode(cb), vc.Signature.String(), err)} + } + } + return nil +} + func (cbft *Cbft) verifyViewChangeQC(viewChangeQC *ctypes.ViewChangeQC) error { vcEpoch, _, _, _, _, _ := viewChangeQC.MaxBlock() @@ -1802,28 +1996,28 @@ func (cbft *Cbft) verifyViewChangeQC(viewChangeQC *ctypes.ViewChangeQC) error { } // check parameter validity - if err := cbft.validateViewChangeQC(viewChangeQC); err != nil { + validatorLimit := cbft.validatorPool.Len(vcEpoch) + if err := cbft.validateViewChangeQC(viewChangeQC, validatorLimit); err != nil { return err } - var err error - for _, vc := range viewChangeQC.QCs { - var cb []byte - if cb, err = vc.CannibalizeBytes(); err != nil { - err = fmt.Errorf("get cannibalize bytes failed") - break - } + return cbft.verifyViewChangeQuorumCerts(viewChangeQC) +} - if err = cbft.validatorPool.VerifyAggSigByBA(vc.Epoch, vc.ValidatorSet, cb, vc.Signature.Bytes()); err != nil { - cbft.log.Debug("verify failed", "qc", vc.String(), "validators", cbft.validatorPool.Validators(vc.Epoch).String()) +func (cbft *Cbft) verifyGroupViewChangeQC(groupID uint32, viewChangeQC *ctypes.ViewChangeQC) error { + vcEpoch, _, _, _, _, _ := viewChangeQC.MaxBlock() - err = authFailedError{err: fmt.Errorf("verify viewchange qc failed:number:%d,validators:%s,msg:%s,signature:%s,err:%v", - vc.BlockNumber, vc.ValidatorSet.String(), hexutil.Encode(cb), vc.Signature.String(), err)} - break - } + if err := cbft.validatorPool.EnableVerifyEpoch(vcEpoch); err != nil { + return err } - return err + // check parameter validity + validatorLimit := cbft.groupLen(vcEpoch, groupID) + if err := cbft.validateViewChangeQC(viewChangeQC, validatorLimit); err != nil { + return err + } + + return cbft.verifyViewChangeQuorumCerts(viewChangeQC) } // NodeID returns the ID value of the current node diff --git a/consensus/cbft/cbft_test.go b/consensus/cbft/cbft_test.go index a1e50a9cfd..672340b48f 100644 --- a/consensus/cbft/cbft_test.go +++ b/consensus/cbft/cbft_test.go @@ -187,7 +187,9 @@ func TestAgg(t *testing.T) { } testPrepareQC(t, cnode) + testCombinePrepareQC(t, cnode) testViewChangeQC(t, cnode) + testCombineViewChangeQC(t, cnode) } func testPrepareQC(t *testing.T, cnode []*Cbft) { @@ -200,12 +202,47 @@ func testPrepareQC(t *testing.T, cnode []*Cbft) { pbs[uint32(i)] = pb } qc := cnode[0].generatePrepareQC(pbs) - fmt.Println(qc) assert.Nil(t, cnode[0].verifyPrepareQC(qc.BlockNumber, qc.BlockHash, qc)) qc.ValidatorSet = nil assert.NotNil(t, cnode[0].verifyPrepareQC(qc.BlockNumber, qc.BlockHash, qc)) +} + +func testCombinePrepareQC(t *testing.T, cnode []*Cbft) { + pbs := make(map[uint32]*protocols.PrepareVote) + for i := 0; i < len(cnode); i++ { + pb := &protocols.PrepareVote{ValidatorIndex: uint32(i)} + assert.NotNil(t, cnode[i]) + cnode[i].signMsgByBls(pb) + pbs[uint32(i)] = pb + } + qc1 := cnode[0].generatePrepareQC(pbs) + //fmt.Println(qc1.String()) + + // combine + qcs := make([]*ctypes.QuorumCert, 0, len(pbs)) + for i, v := range pbs { + vSet := utils.NewBitArray(uint32(len(pbs))) + vSet.SetIndex(i, true) + qcs = append(qcs, &ctypes.QuorumCert{ + Epoch: v.Epoch, + ViewNumber: v.ViewNumber, + BlockHash: v.BlockHash, + BlockNumber: v.BlockNumber, + BlockIndex: v.BlockIndex, + Signature: v.Signature, + ValidatorSet: vSet, + }) + } + qc2 := cnode[0].combinePrepareQC(qcs) + //fmt.Println(qc2.String()) + + assert.Equal(t, qc1.String(), qc2.String()) + assert.Nil(t, cnode[0].verifyPrepareQC(qc1.BlockNumber, qc1.BlockHash, qc1)) + assert.Nil(t, cnode[0].verifyPrepareQC(qc2.BlockNumber, qc2.BlockHash, qc2)) + qc2.ValidatorSet = nil + assert.NotNil(t, cnode[0].verifyPrepareQC(qc2.BlockNumber, qc2.BlockHash, qc2)) } func testViewChangeQC(t *testing.T, cnode []*Cbft) { @@ -225,6 +262,47 @@ func testViewChangeQC(t *testing.T, cnode []*Cbft) { assert.Nil(t, cnode[0].verifyViewChangeQC(qc)) } +func testCombineViewChangeQC(t *testing.T, cnode []*Cbft) { + pbs := make(map[uint32]*protocols.ViewChange) + + for i := 0; i < len(cnode); i++ { + pb := &protocols.ViewChange{BlockHash: common.BigToHash(big.NewInt(int64(i))), BlockNumber: uint64(i), ValidatorIndex: uint32(i)} + assert.NotNil(t, cnode[i]) + cnode[i].signMsgByBls(pb) + pbs[uint32(i)] = pb + } + qc1 := cnode[0].generateViewChangeQC(pbs) + //fmt.Println(qc1.String()) + assert.Len(t, qc1.QCs, len(cnode)) + _, _, _, _, _, num := qc1.MaxBlock() + assert.Equal(t, uint64(len(cnode)-1), num) + + assert.Nil(t, cnode[0].verifyViewChangeQC(qc1)) + + // combine + qcs := make([]*ctypes.ViewChangeQC, 0, len(pbs)) + for i, v := range pbs { + vSet := utils.NewBitArray(uint32(len(pbs))) + vSet.SetIndex(i, true) + qcs = append(qcs, &ctypes.ViewChangeQC{ + QCs: []*ctypes.ViewChangeQuorumCert{ + { + Epoch: v.Epoch, + ViewNumber: v.ViewNumber, + BlockHash: v.BlockHash, + BlockNumber: v.BlockNumber, + Signature: v.Signature, + ValidatorSet: vSet, + }, + }, + }) + } + + qc2 := cnode[0].combineViewChangeQC(qcs) + //fmt.Println(qc2.String()) + assert.Nil(t, cnode[0].verifyViewChangeQC(qc2)) +} + func TestNode(t *testing.T) { pk, sk, nodes := GenerateCbftNode(4) node := MockNode(pk[0], sk[0], nodes, 5000, 10) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index d9a2418846..a55af9baae 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -108,7 +108,7 @@ func (cbft *Cbft) OnPrepareVote(id string, msg *protocols.PrepareVote) error { cbft.log.Debug("Receive PrepareVote", "id", id, "msg", msg.String()) if err := cbft.safetyRules.PrepareVoteRules(msg); err != nil { if err.Common() { - cbft.log.Debug("Preparevote rules fail", "number", msg.BlockHash, "hash", msg.BlockHash, "err", err) + cbft.log.Debug("Preparevote rules fail", "number", msg.BlockNumber, "hash", msg.BlockHash, "err", err) return err } @@ -136,9 +136,11 @@ func (cbft *Cbft) OnPrepareVote(id string, msg *protocols.PrepareVote) error { return err } - cbft.state.AddPrepareVote(uint32(node.Index), msg) + cbft.state.AddPrepareVote(node.Index, msg) + cbft.mergeSelectedQuorumCerts(node, msg) cbft.log.Debug("Receive new prepareVote", "msgHash", msg.MsgHash(), "vote", msg.String(), "votes", cbft.state.PrepareVoteLenByIndex(msg.BlockIndex)) + cbft.trySendRGBlockQuorumCert() cbft.insertPrepareQC(msg.ParentQC) cbft.findQCBlock() return nil @@ -165,8 +167,182 @@ func (cbft *Cbft) OnViewChange(id string, msg *protocols.ViewChange) error { return err } - cbft.state.AddViewChange(uint32(node.Index), msg) + cbft.state.AddViewChange(node.Index, msg) + cbft.mergeSelectedViewChangeQuorumCerts(node, msg) cbft.log.Debug("Receive new viewChange", "msgHash", msg.MsgHash(), "viewChange", msg.String(), "total", cbft.state.ViewChangeLen()) + cbft.trySendRGViewChangeQuorumCert() + + // It is possible to achieve viewchangeQC every time you add viewchange + cbft.tryChangeView() + return nil +} + +// OnRGBlockQuorumCert perform security rule verification,store in viewRGBlockQuorumCerts and selectedRGBlockQuorumCerts, +// Whether to start synchronization +func (cbft *Cbft) OnRGBlockQuorumCert(id string, msg *protocols.RGBlockQuorumCert) error { + cbft.log.Debug("Receive RGBlockQuorumCert", "id", id, "msg", msg.String()) + if err := cbft.safetyRules.RGBlockQuorumCertRules(msg); err != nil { + if err.Common() { + cbft.log.Debug("RGBlockQuorumCert rules fail", "number", msg.BlockNum(), "hash", msg.BHash(), "err", err) + return err + } + + // verify consensus signature + if cbft.verifyConsensusSign(msg) != nil { + signatureCheckFailureMeter.Mark(1) + return err + } + + if err.Fetch() { + if msg.ParentQC != nil { + cbft.log.Info("Epoch or viewNumber higher than local, try to fetch block", "fetchHash", msg.ParentQC.BlockHash, "fetchNumber", msg.ParentQC.BlockNumber) + cbft.fetchBlock(id, msg.ParentQC.BlockHash, msg.ParentQC.BlockNumber, msg.ParentQC) + } + } else if err.FetchPrepare() { + cbft.prepareVoteFetchRules(id, msg) + } + return err + } + + if err := cbft.AllowRGQuorumCert(msg); err != nil { + cbft.log.Error("Failed to verify the aggregate signer information of RGBlockQuorumCert", "RGBlockQuorumCert", msg.String(), "error", err.Error()) + return err + } + + var node *cbfttypes.ValidateNode + var err error + if node, err = cbft.verifyConsensusMsg(msg); err != nil { + cbft.log.Error("Failed to verify RGBlockQuorumCert", "RGBlockQuorumCert", msg.String(), "error", err.Error()) + return err + } + + // VerifyQuorumCert,This method simply verifies the correctness of the aggregated signature itself + // Before this, it is necessary to verify parentqc, whether the number of group signatures is sufficient, whether all signers are group members, whether the message is sent by group members. + if err := cbft.verifyQuorumCert(msg.BlockQC); err != nil { + cbft.log.Error("Failed to verify group prepareQC", "err", err.Error()) + return &authFailedError{err} + } + + cbft.state.AddRGBlockQuorumCert(node.Index, msg) + cbft.richRGBlockQuorumCert(msg) + cbft.state.AddSelectRGQuorumCerts(msg.BlockIndx(), msg.GroupID, msg.BlockQC, msg.ParentQC) + cbft.log.Debug("Receive new RGBlockQuorumCert", "msgHash", msg.MsgHash(), "RGBlockQuorumCert", msg.String(), "total", cbft.state.RGBlockQuorumCertsLen(msg.BlockIndx(), msg.GroupID)) + + cbft.trySendRGBlockQuorumCert() + cbft.insertPrepareQC(msg.ParentQC) + cbft.findQCBlock() + return nil +} + +// Determine whether the total number of RGBlockQuorumCert signatures has reached the minimum threshold for group consensus nodes +func (cbft *Cbft) enoughSigns(epoch uint64, groupID uint32, signs int) bool { + threshold := cbft.groupThreshold(epoch, groupID) + return signs >= threshold +} + +// Determine whether the signer of the RGBlockQuorumCert message is a member of the group +func (cbft *Cbft) isGroupMember(epoch uint64, groupID uint32, nodeIndex uint32) bool { + if v, err := cbft.validatorPool.GetValidatorByGroupIdAndIndex(epoch, groupID, nodeIndex); err != nil || v == nil { + return false + } + return true +} + +// Determine whether the aggregate signers in the RGBlockQuorumCert message are all members of the group +func (cbft *Cbft) allGroupMember(epoch uint64, groupID uint32, validatorSet *utils.BitArray) bool { + var indexes []uint32 // Index collection of the group members + if indexes, err := cbft.validatorPool.GetValidatorIndexesByGroupID(epoch, groupID); err != nil || indexes == nil { + return false + } + total := cbft.validatorPool.Len(epoch) + vSet := utils.NewBitArray(uint32(total)) + for _, index := range indexes { + vSet.SetIndex(index, true) + } + + return vSet.Contains(validatorSet) +} + +// Verify the aggregate signer information of RGQuorumCert +func (cbft *Cbft) AllowRGQuorumCert(msg ctypes.ConsensusMsg) error { + epoch := msg.EpochNum() + nodeIndex := msg.NodeIndex() + var ( + groupID uint32 + validatorSet *utils.BitArray + signsTotal int + ) + + switch rg := msg.(type) { + case *protocols.RGBlockQuorumCert: + groupID = rg.GroupID + signsTotal = rg.BlockQC.Len() + validatorSet = rg.BlockQC.ValidatorSet + case *protocols.RGViewChangeQuorumCert: + groupID = rg.GroupID + signsTotal = rg.ViewChangeQC.Len() + validatorSet = rg.ViewChangeQC.ValidatorSet() + } + + if !cbft.enoughSigns(epoch, groupID, signsTotal) { + return authFailedError{ + err: fmt.Errorf("insufficient signatures"), + } + } + if !cbft.isGroupMember(epoch, groupID, nodeIndex) { + return authFailedError{ + err: fmt.Errorf("the message sender is not a member of the group"), + } + } + if !cbft.allGroupMember(epoch, groupID, validatorSet) { + return authFailedError{ + err: fmt.Errorf("signers include non-group members"), + } + } + return nil +} + +// OnRGViewChangeQuorumCert performs security rule verification, view switching. +func (cbft *Cbft) OnRGViewChangeQuorumCert(id string, msg *protocols.RGViewChangeQuorumCert) error { + cbft.log.Debug("Receive RGViewChangeQuorumCert", "id", id, "msg", msg.String()) + if err := cbft.safetyRules.RGViewChangeQuorumCertRules(msg); err != nil { + if err.Fetch() { + viewChangeQC := msg.ViewChangeQC + _, _, _, _, blockHash, blockNumber := viewChangeQC.MaxBlock() + if msg.PrepareQCs != nil && msg.PrepareQCs[blockHash] != nil { + cbft.log.Info("Epoch or viewNumber higher than local, try to fetch block", "fetchHash", blockHash, "fetchNumber", blockNumber) + cbft.fetchBlock(id, blockHash, blockNumber, msg.PrepareQCs[blockHash]) + } + } + return err + } + + if err := cbft.AllowRGQuorumCert(msg); err != nil { + cbft.log.Error("Failed to verify the aggregate signer information of RGViewChangeQuorumCert", "RGViewChangeQuorumCert", msg.String(), "error", err.Error()) + return err + } + + var node *cbfttypes.ValidateNode + var err error + + if node, err = cbft.verifyConsensusMsg(msg); err != nil { + cbft.log.Error("Failed to verify viewChange", "viewChange", msg.String(), "error", err.Error()) + return err + } + + // VerifyQuorumCert,This method simply verifies the correctness of the aggregated signature itself + // Before this, it is necessary to verify parentqc, whether the number of group signatures is sufficient, whether all signers are group members, whether the message is sent by group members. + if err := cbft.verifyGroupViewChangeQC(msg.GroupID, msg.ViewChangeQC); err != nil { + cbft.log.Error("Failed to verify group viewChangeQC", "err", err.Error()) + return &authFailedError{err} + } + + cbft.state.AddRGViewChangeQuorumCert(node.Index, msg) + cbft.richRGViewChangeQuorumCert(msg) + cbft.state.AddSelectRGViewChangeQuorumCerts(msg.GroupID, msg.ViewChangeQC, msg.PrepareQCs) + cbft.log.Debug("Receive new RGViewChangeQuorumCert", "msgHash", msg.MsgHash(), "RGViewChangeQuorumCert", msg.String(), "total", cbft.state.RGViewChangeQuorumCertsLen(msg.GroupID)) + cbft.trySendRGViewChangeQuorumCert() + // It is possible to achieve viewchangeQC every time you add viewchange cbft.tryChangeView() return nil @@ -337,7 +513,7 @@ func (cbft *Cbft) onAsyncExecuteStatus(s *executor.BlockExecuteStatus) { if cbft.executeFinishHook != nil { cbft.executeFinishHook(index) } - _, err := cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.config.Option.Node.ID()) + _, err := cbft.isCurrentValidator() if err != nil { cbft.log.Debug("Current node is not validator,no need to sign block", "err", err, "hash", s.Hash, "number", s.Number) return @@ -349,7 +525,7 @@ func (cbft *Cbft) onAsyncExecuteStatus(s *executor.BlockExecuteStatus) { cbft.log.Debug("Sign block", "hash", s.Hash, "number", s.Number) if msg := cbft.csPool.GetPrepareQC(cbft.state.Epoch(), cbft.state.ViewNumber(), index); msg != nil { - go cbft.ReceiveMessage(msg) + go cbft.ReceiveSyncMsg(ctypes.NewInnerMsgInfo(msg.Msg, msg.PeerID)) } } } @@ -364,7 +540,7 @@ func (cbft *Cbft) signBlock(hash common.Hash, number uint64, index uint32) error // todo sign vote // parentQC added when sending // Determine if the current consensus node is - node, err := cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.config.Option.Node.ID()) + node, err := cbft.isCurrentValidator() if err != nil { return err } @@ -420,7 +596,7 @@ func (cbft *Cbft) trySendPrepareVote() { p.ParentQC = qc hadSend.Push(p) //Determine if the current consensus node is - node, _ := cbft.validatorPool.GetValidatorByNodeID(cbft.state.Epoch(), cbft.config.Option.Node.ID()) + node, _ := cbft.isCurrentValidator() cbft.log.Info("Add local prepareVote", "vote", p.String()) cbft.state.AddPrepareVote(uint32(node.Index), p) pending.Pop() @@ -437,6 +613,189 @@ func (cbft *Cbft) trySendPrepareVote() { } } +//func (cbft *Cbft) trySendRGBlockQuorumCert() { +// // Check timeout +// if cbft.state.IsDeadline() { +// cbft.log.Debug("Current view had timeout, Refuse to send RGBlockQuorumCert") +// return +// } +// +// node, err := cbft.isCurrentValidator() +// if err != nil || node == nil { +// cbft.log.Debug("Current node is not validator, no need to send RGBlockQuorumCert") +// return +// } +// +// groupID, _, err := cbft.getGroupByValidatorID(cbft.state.Epoch(), cbft.NodeID()) +// if err != nil { +// cbft.log.Debug("Current node is not validator, no need to send RGBlockQuorumCert") +// return +// } +// +// enoughVotes := func(blockIndex, groupID uint32) (bool, *ctypes.QuorumCert, *ctypes.QuorumCert) { +// threshold := cbft.groupThreshold(cbft.state.Epoch(), groupID) +// groupVotes := cbft.groupPrepareVotes(cbft.state.Epoch(), blockIndex, groupID) +// if len(groupVotes) >= threshold { +// // generatePrepareQC by votes +// rgqc := cbft.generatePrepareQC(groupVotes) +// // get parentQC +// var parentQC *ctypes.QuorumCert +// for _, v := range groupVotes { +// parentQC = v.ParentQC +// break +// } +// // Add SelectRGQuorumCerts +// cbft.state.AddSelectRGQuorumCerts(blockIndex, groupID, rgqc, parentQC) +// return true, rgqc, parentQC +// } +// return false, nil, nil +// } +// +// alreadyRGBlockQuorumCerts := func(blockIndex, groupID uint32) (bool, *ctypes.QuorumCert, *ctypes.QuorumCert) { +// blockQC, parentQC := cbft.state.FindMaxGroupRGQuorumCert(blockIndex, groupID) +// if blockQC != nil { +// return true, blockQC, parentQC +// } +// return false, blockQC, parentQC +// } +// +// for i := uint32(0); i < cbft.config.Sys.Amount; i++ { +// if cbft.state.FindRGBlockQuorumCerts(i, groupID, node.Index) != nil { +// cbft.log.Debug("Had async send RGBlockQuorumCert, don't send again", "blockIndex", i, "groupID", groupID, "nodeIndex", node.Index) +// continue +// } +// +// exists, blockQC, parentQC := alreadyRGBlockQuorumCerts(i, groupID) +// if !exists { +// exists, blockQC, parentQC = enoughVotes(i, groupID) +// if !exists { +// continue +// } +// } +// cbft.state.AddRGBlockQuorumCert(node.Index, &protocols.RGBlockQuorumCert{ +// GroupID: groupID, +// BlockQC: blockQC, +// ValidatorIndex: node.Index, +// ParentQC: parentQC, +// }) +// +// cbft.RGBroadcastManager.AsyncSendRGQuorumCert(&awaitingRGBlockQC{ +// groupID: groupID, +// blockIndex: i, +// }) +// cbft.log.Debug("Async send RGBlockQuorumCert", "blockIndex", i, "groupID", groupID, "nodeIndex", node.Index) +// } +//} + +func (cbft *Cbft) trySendRGBlockQuorumCert() { + // Check timeout + if cbft.state.IsDeadline() { + cbft.log.Debug("Current view had timeout, Refuse to send RGBlockQuorumCert") + return + } + + //node, err := cbft.isCurrentValidator() + //if err != nil || node == nil { + // cbft.log.Debug("Current node is not validator, no need to send RGBlockQuorumCert") + // return + //} + + groupID, _, err := cbft.getGroupByValidatorID(cbft.state.Epoch(), cbft.Node().ID()) + if err != nil { + cbft.log.Debug("Current node is not validator, no need to send RGBlockQuorumCert") + return + } + + enoughVotes := func(blockIndex, groupID uint32) bool { + threshold := cbft.groupThreshold(cbft.state.Epoch(), groupID) + groupVotes := cbft.groupPrepareVotes(cbft.state.Epoch(), blockIndex, groupID) + if len(groupVotes) >= threshold { + // generatePrepareQC by votes + rgqc := cbft.generatePrepareQC(groupVotes) + // get parentQC + var parentQC *ctypes.QuorumCert + for _, v := range groupVotes { + parentQC = v.ParentQC + break + } + // Add SelectRGQuorumCerts + cbft.state.AddSelectRGQuorumCerts(blockIndex, groupID, rgqc, parentQC) + return true + } + return false + } + + alreadyRGBlockQuorumCerts := func(blockIndex, groupID uint32) bool { + return cbft.state.SelectRGQuorumCertsLen(blockIndex, groupID) > 0 + } + + for index := uint32(0); index <= cbft.state.MaxViewBlockIndex(); index++ { + if cbft.state.HadSendRGBlockQuorumCerts(index) { + cbft.log.Debug("Had async send RGBlockQuorumCert, don't send again", "blockIndex", index, "groupID", groupID) + continue + } + + if alreadyRGBlockQuorumCerts(index, groupID) || enoughVotes(index, groupID) { + cbft.RGBroadcastManager.AsyncSendRGQuorumCert(&awaitingRGBlockQC{ + groupID: groupID, + blockIndex: index, + }) + cbft.state.AddSendRGBlockQuorumCerts(index) + cbft.log.Debug("Async send RGBlockQuorumCert", "blockIndex", index, "groupID", groupID, "nodeIndex") + } + } +} + +func (cbft *Cbft) trySendRGViewChangeQuorumCert() { + // Check timeout + if cbft.state.IsDeadline() { + cbft.log.Debug("Current view had timeout, Refuse to send RGViewChangeQuorumCert") + return + } + + groupID, _, err := cbft.getGroupByValidatorID(cbft.state.Epoch(), cbft.Node().ID()) + if err != nil { + cbft.log.Debug("Current node is not validator, no need to send RGViewChangeQuorumCert") + return + } + + enoughViewChanges := func(groupID uint32) bool { + threshold := cbft.groupThreshold(cbft.state.Epoch(), groupID) + groupViewChanges := cbft.groupViewChanges(cbft.state.Epoch(), groupID) + if len(groupViewChanges) >= threshold { + // generatePrepareQC by votes + rgqc := cbft.generateViewChangeQC(groupViewChanges) + // get parentQC + prepareQCs := make(map[common.Hash]*ctypes.QuorumCert) + for _, v := range groupViewChanges { + prepareQCs[v.BlockHash] = v.PrepareQC + } + // Add SelectRGViewChangeQuorumCerts + cbft.state.AddSelectRGViewChangeQuorumCerts(groupID, rgqc, prepareQCs) + return true + } + return false + } + + alreadyRGViewChangeQuorumCerts := func(groupID uint32) bool { + return cbft.state.SelectRGViewChangeQuorumCertsLen(groupID) > 0 + } + + if cbft.state.HadSendRGViewChangeQuorumCerts(cbft.state.ViewNumber()) { + cbft.log.Debug("Had async send RGViewChangeQuorumCert, don't send again", "groupID", groupID) + return + } + + if alreadyRGViewChangeQuorumCerts(groupID) || enoughViewChanges(groupID) { + cbft.RGBroadcastManager.AsyncSendRGQuorumCert(&awaitingRGViewQC{ + groupID: groupID, + viewNumber: cbft.state.ViewNumber(), + }) + cbft.state.AddSendRGViewChangeQuorumCerts(cbft.state.ViewNumber()) + cbft.log.Debug("Async send RGBlockQuorumCert", "groupID", groupID) + } +} + // Every time there is a new block or a new executed block result will enter this judgment, find the next executable block func (cbft *Cbft) findExecutableBlock() { qcIndex := cbft.state.MaxQCIndex() @@ -483,19 +842,154 @@ func (cbft *Cbft) findExecutableBlock() { } } +func (cbft *Cbft) mergeSelectedQuorumCerts(node *cbfttypes.ValidateNode, vote *protocols.PrepareVote) { + // Query which group the PrepareVote belongs to according to the information of the node sending the PrepareVote message + groupID, _, err := cbft.getGroupByValidatorID(vote.EpochNum(), node.NodeID) + if err != nil { + cbft.log.Error("Failed to find the group info of the node", "epoch", vote.EpochNum(), "nodeID", node.NodeID.TerminalString(), "error", err) + return + } + cbft.state.MergePrepareVotes(vote.BlockIndex, groupID, []*protocols.PrepareVote{vote}) +} + +func (cbft *Cbft) richRGBlockQuorumCert(rgb *protocols.RGBlockQuorumCert) { + mergeVotes := cbft.groupPrepareVotes(rgb.EpochNum(), rgb.BlockIndx(), rgb.GroupID) + if mergeVotes == nil && len(mergeVotes) > 0 { + for _, v := range mergeVotes { + if !rgb.BlockQC.ValidatorSet.GetIndex(v.NodeIndex()) { + rgb.BlockQC.AddSign(v.Signature, v.NodeIndex()) + } + } + } +} + +func (cbft *Cbft) mergeSelectedViewChangeQuorumCerts(node *cbfttypes.ValidateNode, vc *protocols.ViewChange) { + // Query which group the ViewChange belongs to according to the information of the node sending the ViewChange message + groupID, _, err := cbft.getGroupByValidatorID(vc.EpochNum(), node.NodeID) + if err != nil { + cbft.log.Error("Failed to find the group info of the node", "epoch", vc.EpochNum(), "nodeID", node.NodeID.TerminalString(), "error", err) + return + } + cbft.state.MergeViewChanges(groupID, []*protocols.ViewChange{vc}) +} + +func (cbft *Cbft) richRGViewChangeQuorumCert(rgb *protocols.RGViewChangeQuorumCert) { + mergeVcs := cbft.groupViewChanges(rgb.EpochNum(), rgb.GroupID) + if mergeVcs == nil && len(mergeVcs) > 0 { + for _, vc := range mergeVcs { + if !rgb.ViewChangeQC.ExistViewChange(vc.Epoch, vc.ViewNumber, vc.BlockHash) { + qc := &ctypes.ViewChangeQuorumCert{ + Epoch: vc.Epoch, + ViewNumber: vc.ViewNumber, + BlockHash: vc.BlockHash, + BlockNumber: vc.BlockNumber, + //BlockEpoch: vc.PrepareQC.Epoch, + //BlockViewNumber: vc.PrepareQC.ViewNumber, + ValidatorSet: utils.NewBitArray(vc.PrepareQC.ValidatorSet.Size()), + } + if vc.PrepareQC != nil { + qc.BlockEpoch = vc.PrepareQC.Epoch + qc.BlockViewNumber = vc.PrepareQC.ViewNumber + } + qc.Signature.SetBytes(vc.Signature.Bytes()) + qc.ValidatorSet.SetIndex(vc.ValidatorIndex, true) + rgb.ViewChangeQC.QCs = append(rgb.ViewChangeQC.QCs, qc) + rgb.PrepareQCs[vc.BlockHash] = vc.PrepareQC + } else { + for _, qc := range rgb.ViewChangeQC.QCs { + if !qc.ValidatorSet.GetIndex(vc.NodeIndex()) { + qc.AddSign(vc.Signature, vc.NodeIndex()) + } + } + } + } + } +} + +// Return all votes of the specified group under the current view +func (cbft *Cbft) groupPrepareVotes(epoch uint64, blockIndex, groupID uint32) map[uint32]*protocols.PrepareVote { + indexes, err := cbft.validatorPool.GetValidatorIndexesByGroupID(epoch, groupID) + if err != nil || indexes == nil { + return nil + } + // Find votes corresponding to indexes + votes := cbft.state.AllPrepareVoteByIndex(blockIndex) + if votes != nil && len(votes) > 0 { + groupVotes := make(map[uint32]*protocols.PrepareVote) + for _, index := range indexes { + if vote, ok := votes[index]; ok { + groupVotes[index] = vote + } + } + return groupVotes + } + return nil +} + +// Return all viewChanges of the specified group under the current view +func (cbft *Cbft) groupViewChanges(epoch uint64, groupID uint32) map[uint32]*protocols.ViewChange { + indexes, err := cbft.validatorPool.GetValidatorIndexesByGroupID(epoch, groupID) + if err != nil || indexes == nil { + return nil + } + // Find viewChanges corresponding to indexes + vcs := cbft.state.AllViewChange() + if vcs != nil && len(vcs) > 0 { + groupVcs := make(map[uint32]*protocols.ViewChange) + for _, index := range indexes { + if vc, ok := vcs[index]; ok { + groupVcs[index] = vc + } + } + return groupVcs + } + return nil +} + // Each time a new vote is triggered, a new QC Block will be triggered, and a new one can be found by the commit block. func (cbft *Cbft) findQCBlock() { index := cbft.state.MaxQCIndex() next := index + 1 - size := cbft.state.PrepareVoteLenByIndex(next) + threshold := cbft.threshold(cbft.currentValidatorLen()) + var ( + enoughVotesFlag bool + enoughRGQuorumCertsFlag bool + rgqcs []*ctypes.QuorumCert + ) + + enoughVotes := func() bool { + size := cbft.state.PrepareVoteLenByIndex(next) + enoughVotesFlag = size >= threshold + return enoughVotesFlag + } + + enoughRGQuorumCerts := func() bool { + rgqcs = cbft.state.FindMaxRGQuorumCerts(next) + size := 0 + if rgqcs != nil && len(rgqcs) > 0 { + for _, qc := range rgqcs { + size += qc.ValidatorSet.HasLength() + } + } + enoughRGQuorumCertsFlag = size >= threshold + return enoughRGQuorumCertsFlag + } prepareQC := func() bool { - return size >= cbft.threshold(cbft.currentValidatorLen()) && cbft.state.HadSendPrepareVote().Had(next) + return cbft.state.HadSendPrepareVote().Had(next) && (enoughVotes() || enoughRGQuorumCerts()) } if prepareQC() { block := cbft.state.ViewBlockByIndex(next) - qc := cbft.generatePrepareQC(cbft.state.AllPrepareVoteByIndex(next)) + var qc *ctypes.QuorumCert + if enoughVotesFlag { + qc = cbft.generatePrepareQC(cbft.state.AllPrepareVoteByIndex(next)) + cbft.log.Debug("Enough prepareVote have been received, generate prepareQC", "qc", qc.String()) + } else if enoughRGQuorumCertsFlag { + qc = cbft.combinePrepareQC(rgqcs) + cbft.log.Debug("Enough RGBlockQuorumCerts have been received, combine prepareQC", "qc", qc.String()) + } + if qc != nil { cbft.log.Info("New qc block have been created", "qc", qc.String()) cbft.insertQCBlock(block, qc) @@ -592,17 +1086,42 @@ func (cbft *Cbft) tryChangeView() { return } - viewChangeQC := func() bool { - if cbft.state.ViewChangeLen() >= cbft.threshold(cbft.currentValidatorLen()) { - return true + threshold := cbft.threshold(cbft.currentValidatorLen()) + var ( + enoughViewChangesFlag bool + enoughRGQuorumCertsFlag bool + viewChangeQCs []*ctypes.ViewChangeQC + ) + enoughViewChanges := func() bool { + enoughViewChangesFlag = cbft.state.ViewChangeLen() >= threshold + return enoughViewChangesFlag + } + + enoughRGQuorumCerts := func() bool { + viewChangeQCs = cbft.state.FindMaxRGViewChangeQuorumCerts() + size := 0 + if viewChangeQCs != nil && len(viewChangeQCs) > 0 { + for _, qc := range viewChangeQCs { + size += qc.Len() + } } - cbft.log.Debug("Try change view failed, had receive viewchange", "len", cbft.state.ViewChangeLen(), "view", cbft.state.ViewString()) - return false + enoughRGQuorumCertsFlag = size >= threshold + return enoughRGQuorumCertsFlag + } + + viewChangeQC := func() bool { + return enoughViewChanges() || enoughRGQuorumCerts() } if viewChangeQC() { - viewChangeQC := cbft.generateViewChangeQC(cbft.state.AllViewChange()) - cbft.log.Info("Receive enough viewchange, try change view by viewChangeQC", "view", cbft.state.ViewString(), "viewChangeQC", viewChangeQC.String()) + var viewChangeQC *ctypes.ViewChangeQC + if enoughViewChangesFlag { + viewChangeQC = cbft.generateViewChangeQC(cbft.state.AllViewChange()) + cbft.log.Info("Receive enough viewchange, try change view by viewChangeQC", "view", cbft.state.ViewString(), "viewChangeQC", viewChangeQC.String()) + } else if enoughRGQuorumCertsFlag { + viewChangeQC = cbft.combineViewChangeQC(viewChangeQCs) + cbft.log.Debug("Enough RGViewChangeQuorumCerts have been received, combine ViewChangeQC", "qc", qc.String()) + } cbft.tryChangeViewByViewChange(viewChangeQC) } } @@ -745,6 +1264,7 @@ func (cbft *Cbft) changeView(epoch, viewNumber uint64, block *types.Block, qc *c cbft.csPool.Purge(epoch, viewNumber) cbft.state.ResetView(epoch, viewNumber) + cbft.RGBroadcastManager.Reset(1) // TODO cycleNumber cbft.state.SetViewTimer(interval()) cbft.state.SetLastViewChangeQC(viewChangeQC) diff --git a/consensus/cbft/protocols/protocol.go b/consensus/cbft/protocols/protocol.go index 08ba968d31..99350c6adc 100644 --- a/consensus/cbft/protocols/protocol.go +++ b/consensus/cbft/protocols/protocol.go @@ -38,25 +38,27 @@ const CbftProtocolMaxMsgSize = 10 * 1024 * 1024 const DefaultAvgLatency = 100 const ( - CBFTStatusMsg = 0x00 // Protocol messages belonging to cbft - PrepareBlockMsg = 0x01 - PrepareVoteMsg = 0x02 - ViewChangeMsg = 0x03 - GetPrepareBlockMsg = 0x04 - GetBlockQuorumCertMsg = 0x05 - BlockQuorumCertMsg = 0x06 - GetPrepareVoteMsg = 0x07 - PrepareVotesMsg = 0x08 - GetQCBlockListMsg = 0x09 - QCBlockListMsg = 0x0a - GetLatestStatusMsg = 0x0b - LatestStatusMsg = 0x0c - PrepareBlockHashMsg = 0x0d - GetViewChangeMsg = 0x0e - PingMsg = 0x0f - PongMsg = 0x10 - ViewChangeQuorumCertMsg = 0x11 - ViewChangesMsg = 0x12 + CBFTStatusMsg = 0x00 // Protocol messages belonging to cbft + PrepareBlockMsg = 0x01 + PrepareVoteMsg = 0x02 + ViewChangeMsg = 0x03 + GetPrepareBlockMsg = 0x04 + GetBlockQuorumCertMsg = 0x05 + BlockQuorumCertMsg = 0x06 + GetPrepareVoteMsg = 0x07 + PrepareVotesMsg = 0x08 + GetQCBlockListMsg = 0x09 + QCBlockListMsg = 0x0a + GetLatestStatusMsg = 0x0b + LatestStatusMsg = 0x0c + PrepareBlockHashMsg = 0x0d + GetViewChangeMsg = 0x0e + PingMsg = 0x0f + PongMsg = 0x10 + ViewChangeQuorumCertMsg = 0x11 + ViewChangesMsg = 0x12 + RGBlockQuorumCertMsg = 0x13 + RGViewChangeQuorumCertMsg = 0x14 ) // A is used to convert specific message types according to the message body. @@ -102,6 +104,10 @@ func MessageType(msg interface{}) uint64 { return ViewChangeQuorumCertMsg case *ViewChanges: return ViewChangesMsg + case *RGBlockQuorumCert: + return RGBlockQuorumCertMsg + case *RGViewChangeQuorumCert: + return RGViewChangeQuorumCertMsg default: } panic(fmt.Sprintf("unknown message type [%v}", reflect.TypeOf(msg))) @@ -152,6 +158,14 @@ func (pb *PrepareBlock) NodeIndex() uint32 { return pb.ProposalIndex } +func (pb *PrepareBlock) BlockIndx() uint32 { + return pb.BlockIndex +} + +func (pb *PrepareBlock) CheckQC() *ctypes.QuorumCert { + return pb.PrepareQC +} + func (pb *PrepareBlock) CannibalizeBytes() ([]byte, error) { blockData, err := rlp.EncodeToBytes(pb.Block) if err != nil { @@ -212,6 +226,7 @@ func (pv *PrepareVote) MsgHash() common.Hash { func (pv *PrepareVote) BHash() common.Hash { return pv.BlockHash } + func (pv *PrepareVote) EpochNum() uint64 { return pv.Epoch } @@ -227,6 +242,14 @@ func (pv *PrepareVote) NodeIndex() uint32 { return pv.ValidatorIndex } +func (pv *PrepareVote) BlockIndx() uint32 { + return pv.BlockIndex +} + +func (pv *PrepareVote) CheckQC() *ctypes.QuorumCert { + return pv.ParentQC +} + func (pv *PrepareVote) CannibalizeBytes() ([]byte, error) { buf, err := rlp.EncodeToBytes([]interface{}{ pv.Epoch, @@ -306,6 +329,14 @@ func (vc *ViewChange) NodeIndex() uint32 { return vc.ValidatorIndex } +func (vc *ViewChange) BlockIndx() uint32 { + return vc.PrepareQC.BlockIndex +} + +func (vc *ViewChange) CheckQC() *ctypes.QuorumCert { + return vc.PrepareQC +} + func (vc *ViewChange) CannibalizeBytes() ([]byte, error) { blockEpoch, blockView := uint64(0), uint64(0) if vc.PrepareQC != nil { @@ -449,8 +480,7 @@ func (s *GetBlockQuorumCert) BHash() common.Hash { return s.BlockHash } -// Aggregate signature response message, representing -// aggregated signature information for a block. +// Aggregate signature response message, representing aggregated signature information for a block. type BlockQuorumCert struct { BlockQC *ctypes.QuorumCert `json:"qc"` // Block aggregation signature information. messageHash atomic.Value `json:"-" rlp:"-"` // BlockQuorumCert hash value. @@ -779,3 +809,178 @@ func (v *ViewChangeQuorumCert) BHash() common.Hash { _, _, _, _, hash, _ := v.ViewChangeQC.MaxBlock() return hash } + +// Group Aggregate signature response message. Represents aggregate signature information for a grouping pair block. +type RGBlockQuorumCert struct { + GroupID uint32 `json:"groupID"` // Unique identifier for group. + BlockQC *ctypes.QuorumCert `json:"qc"` // Group Aggregate signature information for a block. + ValidatorIndex uint32 `json:"validatorIndex"` + ParentQC *ctypes.QuorumCert `json:"parentQC" rlp:"nil"` + Signature ctypes.Signature `json:"signature"` // RGBlockQuorumCert signature information + messageHash atomic.Value `json:"-" rlp:"-"` // BlockQuorumCert hash value. +} + +func (rgb *RGBlockQuorumCert) String() string { + return fmt.Sprintf("{GroupID:%d,Epoch:%d,ViewNumber:%d,BlockIndx:%d,Hash:%s,Number:%d,ValidatorIndex:%d}", + rgb.GroupID, rgb.EpochNum(), rgb.ViewNum(), rgb.BlockIndx(), rgb.BHash().TerminalString(), rgb.BlockNum(), rgb.NodeIndex()) +} + +func (rgb *RGBlockQuorumCert) MsgHash() common.Hash { + if mhash := rgb.messageHash.Load(); mhash != nil { + return mhash.(common.Hash) + } + + v := utils.BuildHash(RGBlockQuorumCertMsg, utils.MergeBytes( + common.Uint32ToBytes(rgb.GroupID), + common.Uint32ToBytes(rgb.ValidatorIndex), // To upgrade a slave node to a master node, it is necessary to determine whether the previous marshalling node sends RGBlockQuorumCertMsg, so the ValidatorIndex needs to be added here + rgb.BlockQC.BlockHash.Bytes(), + common.Uint64ToBytes(rgb.BlockQC.BlockNumber), + rgb.BlockQC.Signature.Bytes(), + rgb.BlockQC.ValidatorSet.Bytes())) + + rgb.messageHash.Store(v) + return v +} + +func (rgb *RGBlockQuorumCert) BHash() common.Hash { + return rgb.BlockQC.BlockHash +} + +func (rgb *RGBlockQuorumCert) EpochNum() uint64 { + return rgb.BlockQC.Epoch +} + +func (rgb *RGBlockQuorumCert) ViewNum() uint64 { + return rgb.BlockQC.ViewNumber +} + +func (rgb *RGBlockQuorumCert) BlockNum() uint64 { + return rgb.BlockQC.BlockNumber +} + +func (rgb *RGBlockQuorumCert) NodeIndex() uint32 { + return rgb.ValidatorIndex +} + +func (rgb *RGBlockQuorumCert) BlockIndx() uint32 { + return rgb.BlockQC.BlockIndex +} + +func (rgb *RGBlockQuorumCert) CheckQC() *ctypes.QuorumCert { + return rgb.ParentQC +} + +func (rgb *RGBlockQuorumCert) CannibalizeBytes() ([]byte, error) { + blockQCData, err := rlp.EncodeToBytes(rgb.BlockQC) + if err != nil { + return nil, err + } + buf, err := rlp.EncodeToBytes([]interface{}{ + rgb.GroupID, + rgb.ValidatorIndex, + crypto.Keccak256(blockQCData), + }) + + if err != nil { + return nil, err + } + return crypto.Keccak256(buf), nil +} + +func (rgb *RGBlockQuorumCert) Sign() []byte { + return rgb.Signature.Bytes() +} + +func (rgb *RGBlockQuorumCert) SetSign(sign []byte) { + rgb.Signature.SetBytes(sign) +} + +type RGViewChangeQuorumCert struct { + GroupID uint32 `json:"groupID"` // Unique identifier for group. + ViewChangeQC *ctypes.ViewChangeQC `json:"viewchangeQC"` // viewChange aggregate signature + ValidatorIndex uint32 `json:"validatorIndex"` + PrepareQCs map[common.Hash]*ctypes.QuorumCert `json:"prepareQCs" rlp:"nil"` + Signature ctypes.Signature `json:"signature"` // RGViewChangeQuorumCert signature information + messageHash atomic.Value `rlp:"-"` +} + +func (rgv *RGViewChangeQuorumCert) String() string { + epoch, viewNumber, blockEpoch, blockViewNumber, hash, number := rgv.ViewChangeQC.MaxBlock() + return fmt.Sprintf("{GroupID:%d,Epoch:%d,ViewNumber:%d,BlockEpoch:%d,BlockViewNumber:%d,Hash:%s,Number:%d}", + rgv.GroupID, epoch, viewNumber, blockEpoch, blockViewNumber, hash.TerminalString(), number) +} + +func (rgv *RGViewChangeQuorumCert) MsgHash() common.Hash { + if mhash := rgv.messageHash.Load(); mhash != nil { + return mhash.(common.Hash) + } + epoch, viewNumber, blockEpoch, blockViewNumber, hash, number := rgv.ViewChangeQC.MaxBlock() + mv := utils.BuildHash(RGViewChangeQuorumCertMsg, utils.MergeBytes( + common.Uint32ToBytes(rgv.GroupID), + common.Uint32ToBytes(rgv.ValidatorIndex), // To upgrade a slave node to a master node, it is necessary to determine whether the previous marshalling node sends RGBlockQuorumCertMsg, so the ValidatorIndex needs to be added here + common.Uint64ToBytes(epoch), + common.Uint64ToBytes(viewNumber), + common.Uint64ToBytes(blockEpoch), + common.Uint64ToBytes(blockViewNumber), + hash.Bytes(), + common.Uint64ToBytes(number))) + rgv.messageHash.Store(mv) + return mv +} + +func (rgv *RGViewChangeQuorumCert) BHash() common.Hash { + _, _, _, _, hash, _ := rgv.ViewChangeQC.MaxBlock() + return hash +} + +func (rgv *RGViewChangeQuorumCert) EpochNum() uint64 { + epoch, _, _, _, _, _ := rgv.ViewChangeQC.MaxBlock() + return epoch +} + +func (rgv *RGViewChangeQuorumCert) ViewNum() uint64 { + _, viewNumber, _, _, _, _ := rgv.ViewChangeQC.MaxBlock() + return viewNumber +} + +func (rgv *RGViewChangeQuorumCert) BlockNum() uint64 { + _, _, _, _, _, number := rgv.ViewChangeQC.MaxBlock() + return number +} + +func (rgv *RGViewChangeQuorumCert) NodeIndex() uint32 { + return rgv.ValidatorIndex +} + +func (rgv *RGViewChangeQuorumCert) BlockIndx() uint32 { + return 0 +} + +func (rgv *RGViewChangeQuorumCert) CheckQC() *ctypes.QuorumCert { + return nil +} + +func (rgv *RGViewChangeQuorumCert) CannibalizeBytes() ([]byte, error) { + viewChangeQCData, err := rlp.EncodeToBytes(rgv.ViewChangeQC) + if err != nil { + return nil, err + } + buf, err := rlp.EncodeToBytes([]interface{}{ + rgv.GroupID, + rgv.ValidatorIndex, + crypto.Keccak256(viewChangeQCData), + }) + + if err != nil { + return nil, err + } + return crypto.Keccak256(buf), nil +} + +func (rgv *RGViewChangeQuorumCert) Sign() []byte { + return rgv.Signature.Bytes() +} + +func (rgv *RGViewChangeQuorumCert) SetSign(sign []byte) { + rgv.Signature.SetBytes(sign) +} diff --git a/consensus/cbft/rg_broadcast.go b/consensus/cbft/rg_broadcast.go new file mode 100644 index 0000000000..5d6e91b121 --- /dev/null +++ b/consensus/cbft/rg_broadcast.go @@ -0,0 +1,372 @@ +package cbft + +import ( + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" + "reflect" + "sync" + "time" +) + +const ( + coordinatorWaitTimeout = 200 * time.Millisecond + efficientCoordinatorRatio = 15 // TODO +) + +type awaiting interface { + GroupID() uint32 + Index() uint64 +} + +type awaitingRGBlockQC struct { + groupID uint32 + blockIndex uint32 +} + +func (a *awaitingRGBlockQC) GroupID() uint32 { + return a.groupID +} + +func (a *awaitingRGBlockQC) Index() uint64 { + return uint64(a.blockIndex) +} + +type awaitingRGViewQC struct { + groupID uint32 + viewNumber uint64 +} + +func (a *awaitingRGViewQC) GroupID() uint32 { + return a.groupID +} + +func (a *awaitingRGViewQC) Index() uint64 { + return a.viewNumber +} + +type awaitingJob struct { + jobTimer *time.Timer + awaiting awaiting +} + +type RGBroadcastManager struct { + cbft *Cbft + + cycleNumber int64 + + delayDuration time.Duration + + // A collection of RGBlockQuorumCert messages waiting to be sent + awaitingRGBlockQuorumCerts map[uint64]*awaitingJob + + // A collection of RGBlockQuorumCert messages that have been sent + hadSendRGBlockQuorumCerts map[uint64]*protocols.RGBlockQuorumCert + + // A collection of RGViewChangeQuorumCert messages waiting to be sent + awaitingRGViewChangeQuorumCerts map[uint64]*awaitingJob + + // A collection of RGViewChangeQuorumCert messages that have been sent + hadSendRGViewChangeQuorumCerts map[uint64]*protocols.RGViewChangeQuorumCert + + broadcastCh chan awaiting + + // Termination channel to stop the broadcaster + term chan struct{} + + // global mutex for RGBroadcast operations + mux sync.Mutex +} + +// NewBridge creates a new Bridge to update consensus state and consensus msg. +func NewRGBroadcastManager(cbft *Cbft, cycleNumber int64) *RGBroadcastManager { + m := &RGBroadcastManager{ + cbft: cbft, + cycleNumber: cycleNumber, + delayDuration: time.Duration(cycleNumber) * coordinatorWaitTimeout, + awaitingRGBlockQuorumCerts: make(map[uint64]*awaitingJob), + hadSendRGBlockQuorumCerts: make(map[uint64]*protocols.RGBlockQuorumCert), + awaitingRGViewChangeQuorumCerts: make(map[uint64]*awaitingJob), + hadSendRGViewChangeQuorumCerts: make(map[uint64]*protocols.RGViewChangeQuorumCert), + broadcastCh: make(chan awaiting, 20), + term: make(chan struct{}), + } + go m.broadcastLoop() + return m +} + +func (m *RGBroadcastManager) broadcastLoop() { + for { + select { + case a := <-m.broadcastCh: + m.broadcast(a) + + case <-m.term: + return + } + } +} + +func (m *RGBroadcastManager) hadBroadcastRGBlockQuorumCert(blockIndex uint64) bool { + if _, ok := m.hadSendRGBlockQuorumCerts[blockIndex]; ok { + return true + } + return false +} + +func (m *RGBroadcastManager) awaitingBroadcastRGBlockQuorumCert(blockIndex uint64) bool { + if _, ok := m.awaitingRGBlockQuorumCerts[blockIndex]; ok { + return true + } + return false +} + +func (m *RGBroadcastManager) hadBroadcastRGViewChangeQuorumCert(viewNumber uint64) bool { + if _, ok := m.hadSendRGViewChangeQuorumCerts[viewNumber]; ok { + return true + } + return false +} + +func (m *RGBroadcastManager) awaitingBroadcastRGViewChangeQuorumCert(viewNumber uint64) bool { + if _, ok := m.awaitingRGViewChangeQuorumCerts[viewNumber]; ok { + return true + } + return false +} + +// needBroadcast to check whether the message has been sent or is being sent +func (m *RGBroadcastManager) needBroadcast(a awaiting) bool { + switch msg := a.(type) { + case *awaitingRGBlockQC: + return !m.hadBroadcastRGBlockQuorumCert(msg.Index()) && !m.awaitingBroadcastRGBlockQuorumCert(msg.Index()) + case *awaitingRGViewQC: + return !m.hadBroadcastRGViewChangeQuorumCert(msg.Index()) && !m.awaitingBroadcastRGViewChangeQuorumCert(msg.Index()) + default: + return false + } +} + +// cbft层在调用AsyncSendRGBlockQuorumCert时需判断view和viewTimeout等情况 +func (m *RGBroadcastManager) broadcast(a awaiting) { + m.mux.Lock() + defer m.mux.Unlock() + + if !m.needBroadcast(a) { + return + } + timer := time.AfterFunc(m.delayDuration, func() { + m.cbft.asyncCallCh <- func() { + m.broadcastFunc(a) + } + }) + switch msg := a.(type) { + case *awaitingRGBlockQC: + m.awaitingRGBlockQuorumCerts[msg.Index()] = &awaitingJob{ + jobTimer: timer, + awaiting: a, + } + case *awaitingRGViewQC: + m.awaitingRGViewChangeQuorumCerts[msg.Index()] = &awaitingJob{ + jobTimer: timer, + awaiting: a, + } + default: + m.cbft.log.Error("Unsupported message type") + return + } +} + +//func (m *RGBroadcastManager) allowRGBlockQuorumCert(rgMsg ctypes.ConsensusMsg) bool { +// return rgMsg.EpochNum() == m.cbft.state.Epoch() && rgMsg.ViewNum() == m.cbft.state.ViewNumber() +//} + +func (m *RGBroadcastManager) upgradeCoordinator(a awaiting) bool { + // Check whether the current node is the first group member + groupID, unitID, err := m.cbft.getGroupByValidatorID(m.cbft.state.Epoch(), m.cbft.Node().ID()) + if err != nil || groupID != a.GroupID() { + return false + } + if unitID == uint32(0) { // the first echelon,Send by default + return true + } + + coordinatorIndexes, err := m.cbft.validatorPool.GetCoordinatorIndexesByGroupID(m.cbft.state.Epoch(), groupID) + if err != nil || len(coordinatorIndexes) <= 0 { + m.cbft.log.Error("Get coordinator indexes by groupID error") + return false + } + + var receiveIndexes []uint32 + + switch msg := a.(type) { + case *awaitingRGBlockQC: + receiveIndexes = m.cbft.state.RGBlockQuorumCertsIndexes(msg.blockIndex, groupID) + case *awaitingRGViewQC: + receiveIndexes = m.cbft.state.RGViewChangeQuorumCertsIndexes(groupID) + default: + return false + } + if !m.enoughCoordinator(groupID, unitID, coordinatorIndexes, receiveIndexes) { + m.cbft.log.Warn("Upgrade the current node to Coordinator", "groupID", groupID, "unitID", unitID, "coordinatorIndexes", coordinatorIndexes, "receiveIndexes", receiveIndexes) + return true + } + m.cbft.log.Debug("Enough Coordinator, no need to upgrade to Coordinator", "groupID", groupID, "unitID", unitID, "coordinatorIndexes", coordinatorIndexes, "receiveIndexes", receiveIndexes) + return false +} + +func (m *RGBroadcastManager) enoughCoordinator(groupID, unitID uint32, coordinatorIndexes [][]uint32, receiveIndexes []uint32) bool { + enough := func() int { + // The total number of validators in the current group + total := m.cbft.groupLen(m.cbft.state.Epoch(), groupID) + return total * efficientCoordinatorRatio / 100 + }() + + return m.countCoordinator(unitID, coordinatorIndexes, receiveIndexes) >= enough +} + +func (m *RGBroadcastManager) countCoordinator(unitID uint32, coordinatorIndexes [][]uint32, receiveIndexes []uint32) int { + receiveIndexesMap := make(map[uint32]struct{}) + for i := 0; i < len(receiveIndexes); i++ { + receiveIndexesMap[receiveIndexes[i]] = struct{}{} + } + + c := 0 + for i := 0; i < len(coordinatorIndexes); i++ { + if uint32(i) >= unitID { + break + } + for _, v := range coordinatorIndexes[i] { + if _, ok := receiveIndexesMap[v]; ok { + c++ + } + } + } + return c +} + +func (m *RGBroadcastManager) broadcastFunc(a awaiting) { + if m.cbft.state.IsDeadline() { + m.cbft.log.Debug("Current view had timeout, Refuse to send RGQuorumCert") + return + } + + //alreadyQCBlock := func() bool { + // if _, ok := rgMsg.(*protocols.RGBlockQuorumCert); ok { + // return m.cbft.blockTree.FindBlockByHash(rgMsg.BHash()) != nil || rgMsg.BlockNum() <= m.cbft.state.HighestLockBlock().NumberU64() + // } + // return false + //} + + //if !m.allowRGBlockQuorumCert(rgMsg) || alreadyQCBlock() { + // return + //} + + //findParentQC := func(blockIndex, groupID uint32) *ctypes.QuorumCert { + // votes := m.cbft.state.AllPrepareVoteByIndex(blockIndex) + // if votes != nil && len(votes) > 0 { + // for _, v := range votes { + // return v.ParentQC + // } + // } + // rgqcs := m.cbft.state.FindGroupRGBlockQuorumCerts(blockIndex, groupID) + // if rgqcs != nil && len(rgqcs) > 0 { + // for _, qc := range rgqcs { + // return qc.ParentQC + // } + // } + // return nil + //} + + if !m.upgradeCoordinator(a) { + return + } + + node, err := m.cbft.isCurrentValidator() + if err != nil || node == nil { + m.cbft.log.Debug("Current node is not validator, no need to send RGQuorumCert") + return + } + + m.mux.Lock() + defer m.mux.Unlock() + + // TODO 如果该blockIndex已经qc,是否不用发送分组聚合签名 + switch msg := a.(type) { + case *awaitingRGBlockQC: + // Query the QuorumCert with the largest number of signatures in the current group + blockQC, parentQC := m.cbft.state.FindMaxGroupRGQuorumCert(msg.blockIndex, msg.GroupID()) + if blockQC == nil { + m.cbft.log.Error("Cannot find the RGBlockQuorumCert of the current group", "blockIndex", msg.blockIndex, "groupID", msg.GroupID()) + return + } + //ParentQC := findParentQC(msg.blockIndex, msg.GroupID()) + if blockQC.BlockNumber != 1 && parentQC == nil { + m.cbft.log.Error("Cannot find the ParentQC corresponding to the current blockQC", "blockIndex", msg.blockIndex, "blockNumber", blockQC.BlockNumber, "groupID", msg.GroupID()) + return + } + rg := &protocols.RGBlockQuorumCert{ + GroupID: msg.groupID, + BlockQC: blockQC, + ValidatorIndex: node.Index, + ParentQC: parentQC, + } + if err := m.cbft.signMsgByBls(rg); err != nil { + m.cbft.log.Error("Sign RGBlockQuorumCert failed", "err", err, "rgmsg", rg.String()) + return + } + m.cbft.network.Broadcast(rg) + m.hadSendRGBlockQuorumCerts[msg.Index()] = rg + delete(m.awaitingRGBlockQuorumCerts, msg.Index()) + case *awaitingRGViewQC: + viewChangeQC, prepareQCs := m.cbft.state.FindMaxGroupRGViewChangeQuorumCert(msg.GroupID()) + if viewChangeQC == nil { + m.cbft.log.Error("Cannot find the RGViewChangeQuorumCert of the current group", "groupID", msg.GroupID()) + return + } + rg := &protocols.RGViewChangeQuorumCert{ + GroupID: msg.groupID, + ViewChangeQC: viewChangeQC, + ValidatorIndex: node.Index, + PrepareQCs: prepareQCs, + } + if err := m.cbft.signMsgByBls(rg); err != nil { + m.cbft.log.Error("Sign RGViewChangeQuorumCert failed", "err", err, "rgmsg", rg.String()) + return + } + m.cbft.network.Broadcast(rg) + m.hadSendRGViewChangeQuorumCerts[msg.Index()] = rg + delete(m.awaitingRGViewChangeQuorumCerts, msg.Index()) + } +} + +// AsyncSendRGQuorumCert queues list of RGQuorumCert propagation to a remote peer. +func (m *RGBroadcastManager) AsyncSendRGQuorumCert(a awaiting) { + select { + case m.broadcastCh <- a: + m.cbft.log.Debug("Async send RGQuorumCert", "groupID", a.GroupID(), "index", a.Index(), "type", reflect.TypeOf(a)) + case <-m.term: + m.cbft.log.Debug("Dropping RGQuorumCert propagation") + } +} + +func (m *RGBroadcastManager) Reset(cycleNumber int64) { + m.mux.Lock() + defer m.mux.Unlock() + + for _, await := range m.awaitingRGBlockQuorumCerts { + await.jobTimer.Stop() + } + for _, await := range m.awaitingRGViewChangeQuorumCerts { + await.jobTimer.Stop() + } + m.cycleNumber = cycleNumber + m.delayDuration = time.Duration(cycleNumber) * coordinatorWaitTimeout + m.awaitingRGBlockQuorumCerts = make(map[uint64]*awaitingJob) + m.hadSendRGBlockQuorumCerts = make(map[uint64]*protocols.RGBlockQuorumCert) + m.awaitingRGViewChangeQuorumCerts = make(map[uint64]*awaitingJob) + m.hadSendRGViewChangeQuorumCerts = make(map[uint64]*protocols.RGViewChangeQuorumCert) +} + +// close signals the broadcast goroutine to terminate. +func (m *RGBroadcastManager) Close() { + close(m.term) +} diff --git a/consensus/cbft/rules/safety_rules.go b/consensus/cbft/rules/safety_rules.go index 4b20177be1..e1c4795655 100644 --- a/consensus/cbft/rules/safety_rules.go +++ b/consensus/cbft/rules/safety_rules.go @@ -127,10 +127,16 @@ type SafetyRules interface { PrepareVoteRules(vote *protocols.PrepareVote) SafetyError // Security rules for viewChange - ViewChangeRules(vote *protocols.ViewChange) SafetyError + ViewChangeRules(viewChange *protocols.ViewChange) SafetyError // Security rules for qcblock QCBlockRules(block *types.Block, qc *ctypes.QuorumCert) SafetyError + + // Security rules for RGBlockQuorumCert + RGBlockQuorumCertRules(rgb *protocols.RGBlockQuorumCert) SafetyError + + // Security rules for RGViewChangeQuorumCert + RGViewChangeQuorumCertRules(rgv *protocols.RGViewChangeQuorumCert) SafetyError } type baseSafetyRules struct { @@ -337,7 +343,7 @@ func (r *baseSafetyRules) PrepareVoteRules(vote *protocols.PrepareVote) SafetyEr return newCommonError(fmt.Sprintf("current index block is already qc block,discard msg(index:%d,number:%d,hash:%s)", vote.BlockIndex, vote.BlockNumber, vote.BlockHash.String())) } if r.viewState.Epoch() != vote.Epoch { - return r.changeEpochVoteRules(vote) + return r.changeEpochRules(vote.Epoch) } if r.viewState.ViewNumber() > vote.ViewNumber { return newCommonError(fmt.Sprintf("viewNumber too low(local:%d, msg:%d)", r.viewState.ViewNumber(), vote.ViewNumber)) @@ -358,9 +364,9 @@ func (r *baseSafetyRules) PrepareVoteRules(vote *protocols.PrepareVote) SafetyEr return nil } -func (r *baseSafetyRules) changeEpochVoteRules(vote *protocols.PrepareVote) SafetyError { - if r.viewState.Epoch() > vote.Epoch { - return newCommonError(fmt.Sprintf("epoch too low(local:%d, msg:%d)", r.viewState.Epoch(), vote.Epoch)) +func (r *baseSafetyRules) changeEpochRules(epoch uint64) SafetyError { + if r.viewState.Epoch() > epoch { + return newCommonError(fmt.Sprintf("epoch too low(local:%d, msg:%d)", r.viewState.Epoch(), epoch)) } return newFetchError("new epoch, need fetch blocks") @@ -371,7 +377,7 @@ func (r *baseSafetyRules) changeEpochVoteRules(vote *protocols.PrepareVote) Safe // 2.Synchronization greater than local viewNumber func (r *baseSafetyRules) ViewChangeRules(viewChange *protocols.ViewChange) SafetyError { if r.viewState.Epoch() != viewChange.Epoch { - return r.changeEpochViewChangeRules(viewChange) + return r.changeEpochViewChangeRules(viewChange.Epoch) } if r.viewState.ViewNumber() > viewChange.ViewNumber { return newCommonError(fmt.Sprintf("viewNumber too low(local:%d, msg:%d)", r.viewState.ViewNumber(), viewChange.ViewNumber)) @@ -384,9 +390,9 @@ func (r *baseSafetyRules) ViewChangeRules(viewChange *protocols.ViewChange) Safe return nil } -func (r *baseSafetyRules) changeEpochViewChangeRules(viewChange *protocols.ViewChange) SafetyError { - if r.viewState.Epoch() > viewChange.Epoch { - return newCommonError(fmt.Sprintf("epoch too low(local:%d, msg:%d)", r.viewState.Epoch(), viewChange.Epoch)) +func (r *baseSafetyRules) changeEpochViewChangeRules(epoch uint64) SafetyError { + if r.viewState.Epoch() > epoch { + return newCommonError(fmt.Sprintf("epoch too low(local:%d, msg:%d)", r.viewState.Epoch(), epoch)) } return newFetchError("new epoch, need fetch blocks") @@ -406,6 +412,101 @@ func (r *baseSafetyRules) QCBlockRules(block *types.Block, qc *ctypes.QuorumCert return nil } +func (r *baseSafetyRules) RGBlockQuorumCertRules(rgb *protocols.RGBlockQuorumCert) SafetyError { + alreadyQCBlock := func() bool { + return r.blockTree.FindBlockByHash(rgb.BHash()) != nil || rgb.BlockNum() <= r.viewState.HighestLockBlock().NumberU64() + } + + existsPrepare := func() bool { + prepare := r.viewState.ViewBlockByIndex(rgb.BlockIndx()) + return prepare != nil && prepare.NumberU64() == rgb.BlockNum() && prepare.Hash() == rgb.BHash() + } + + doubtDuplicate := func(exist *protocols.RGBlockQuorumCert) bool { + if exist != nil { + return rgb.MsgHash() != exist.MsgHash() + } + return false + } + + acceptIndexVote := func() SafetyError { + if rgb.BlockIndx() >= r.config.Sys.Amount { + return newCommonError(fmt.Sprintf("RGBlockQuorumCert Index higher than amount(index:%d, amount:%d)", rgb.BlockIndx(), r.config.Sys.Amount)) + } + exist := r.viewState.FindRGBlockQuorumCerts(rgb.BlockIndx(), rgb.GroupID, rgb.ValidatorIndex) + if doubtDuplicate(exist) { + log.Error(fmt.Sprintf("Receive duplicate RGBlockQuorumCert (blockIndex:%d, groupID:%d, validatorIndex:%d)", rgb.BlockIndx(), rgb.GroupID, rgb.ValidatorIndex)) + // TODO do not return authFailedError temporarily + return newCommonError(fmt.Sprintf("Receive duplicate RGBlockQuorumCert (blockIndex:%d, groupID:%d, validatorIndex:%d)", rgb.BlockIndx(), rgb.GroupID, rgb.ValidatorIndex)) + } + if exist != nil { + return newCommonError(fmt.Sprintf("RGBlockQuorumCert has exist(blockIndex:%d, groupID:%d, validatorIndex:%d)", rgb.BlockIndx(), rgb.GroupID, rgb.ValidatorIndex)) + } + if !existsPrepare() { + return newFetchPrepareError(fmt.Sprintf("current index block not existed,discard msg(index:%d)", rgb.BlockIndx())) + } + return nil + } + + if alreadyQCBlock() { + return newCommonError(fmt.Sprintf("current index block is already qc block,discard msg(index:%d,number:%d,hash:%s)", rgb.BlockIndx(), rgb.BlockNum(), rgb.BHash().String())) + } + if r.viewState.Epoch() != rgb.EpochNum() { + return r.changeEpochRules(rgb.EpochNum()) + } + if r.viewState.ViewNumber() > rgb.ViewNum() { + return newCommonError(fmt.Sprintf("viewNumber too low(local:%d, msg:%d)", r.viewState.ViewNumber(), rgb.ViewNum())) + } + + if r.viewState.ViewNumber() < rgb.ViewNum() { + return newFetchError(fmt.Sprintf("viewNumber higher than local(local:%d, msg:%d)", r.viewState.ViewNumber(), rgb.ViewNum())) + } + + // if local epoch and viewNumber is the same with msg + if err := acceptIndexVote(); err != nil { + return err + } + + if r.viewState.IsDeadline() { + return newCommonError(fmt.Sprintf("view's deadline is expire(over:%d)", time.Since(r.viewState.Deadline()))) + } + return nil +} + +func (r *baseSafetyRules) RGViewChangeQuorumCertRules(rgv *protocols.RGViewChangeQuorumCert) SafetyError { + doubtDuplicate := func(exist *protocols.RGViewChangeQuorumCert) bool { + if exist != nil { + return rgv.MsgHash() != exist.MsgHash() + } + return false + } + + exist := r.viewState.FindRGViewChangeQuorumCerts(rgv.GroupID, rgv.ValidatorIndex) + if doubtDuplicate(exist) { + log.Error(fmt.Sprintf("Receive duplicate RGViewChangeQuorumCert (groupID:%d, validatorIndex:%d)", rgv.GroupID, rgv.ValidatorIndex)) + // TODO do not return authFailedError temporarily + return newCommonError(fmt.Sprintf("Receive duplicate RGViewChangeQuorumCert (groupID:%d, validatorIndex:%d)", rgv.GroupID, rgv.ValidatorIndex)) + } + if exist != nil { + return newCommonError(fmt.Sprintf("RGViewChangeQuorumCert has exist (groupID:%d, validatorIndex:%d)", rgv.GroupID, rgv.ValidatorIndex)) + } + + viewChangeQC := rgv.ViewChangeQC + epoch, viewNumber, _, _, _, _ := viewChangeQC.MaxBlock() + if r.viewState.Epoch() != epoch { + return r.changeEpochViewChangeRules(epoch) + } + if r.viewState.ViewNumber() > viewNumber { + return newCommonError(fmt.Sprintf("viewNumber too low(local:%d, msg:%d)", r.viewState.ViewNumber(), viewNumber)) + } + + if r.viewState.ViewNumber() < viewNumber { + return newFetchError(fmt.Sprintf("viewNumber higher than local(local:%d, msg:%d)", r.viewState.ViewNumber(), viewNumber)) + } + + return nil +} + func NewSafetyRules(viewState *state.ViewState, blockTree *ctypes.BlockTree, config *ctypes.Config, validatorPool *validator.ValidatorPool) SafetyRules { return &baseSafetyRules{ viewState: viewState, diff --git a/consensus/cbft/rules/vote_rules.go b/consensus/cbft/rules/vote_rules.go index 56c3601719..44bebba2be 100644 --- a/consensus/cbft/rules/vote_rules.go +++ b/consensus/cbft/rules/vote_rules.go @@ -74,5 +74,4 @@ func NewVoteRules(viewState *state.ViewState) VoteRules { return &baseVoteRules{ viewState: viewState, } - } diff --git a/consensus/cbft/state/state.go b/consensus/cbft/state/state.go index ef22415fc5..c10aefaa1e 100644 --- a/consensus/cbft/state/state.go +++ b/consensus/cbft/state/state.go @@ -31,8 +31,12 @@ import ( "github.com/AlayaNetwork/Alaya-Go/core/types" ) -const DefaultEpoch = 1 -const DefaultViewNumber = 0 +const ( + DefaultEpoch = 1 + DefaultViewNumber = 0 + // maxSelectedRGLimit is the target maximum size of the reservation of the group aggregate signature. + maxSelectedRGLimit = 5 +) type PrepareVoteQueue struct { Votes []*protocols.PrepareVote `json:"votes"` @@ -314,27 +318,53 @@ type view struct { // This view has been sent to other verifiers for voting hadSendPrepareVote *PrepareVoteQueue - //Pending Votes of current view, parent block need receive N-f prepareVotes + // Pending Votes of current view, parent block need receive N-f prepareVotes pendingVote *PrepareVoteQueue - //Current view of the proposed block by the proposer + // Current view of the proposed block by the proposer viewBlocks *viewBlocks viewQCs *viewQCs - //The current view generated by the vote + // The current view generated by the vote viewVotes *viewVotes + + // All RGBlockQuorumCerts in the current view + viewRGBlockQuorumCerts *viewRGBlockQuorumCerts + + // The RGBlockQuorumCerts reserved in the current view + // used to aggregate into a complete signature and later synchronization logic + selectedRGBlockQuorumCerts *selectedRGBlockQuorumCerts + + // This view has been sent to other verifiers for RGBlockQuorumCert + hadSendRGBlockQuorumCerts map[uint32]struct{} + + // All RGViewChangeQuorumCerts in the current view + viewRGViewChangeQuorumCerts *viewRGViewChangeQuorumCerts + + // The RGViewChangeQuorumCerts reserved in the current view + // used to aggregate into a complete signature and later synchronization logic + selectedRGViewChangeQuorumCerts *selectedRGViewChangeQuorumCerts + + // This view has been sent to other verifiers for RGViewChangeQuorumCerts + hadSendRGViewChangeQuorumCerts map[uint64]struct{} } func newView() *view { return &view{ - executing: executing{math.MaxUint32, false}, - viewChanges: newViewChanges(), - hadSendPrepareVote: newPrepareVoteQueue(), - pendingVote: newPrepareVoteQueue(), - viewBlocks: newViewBlocks(), - viewQCs: newViewQCs(), - viewVotes: newViewVotes(), + executing: executing{math.MaxUint32, false}, + viewChanges: newViewChanges(), + hadSendPrepareVote: newPrepareVoteQueue(), + pendingVote: newPrepareVoteQueue(), + viewBlocks: newViewBlocks(), + viewQCs: newViewQCs(), + viewVotes: newViewVotes(), + viewRGBlockQuorumCerts: newViewRGBlockQuorumCerts(), + selectedRGBlockQuorumCerts: newSelectedRGBlockQuorumCerts(), + hadSendRGBlockQuorumCerts: make(map[uint32]struct{}), + hadSendRGViewChangeQuorumCerts: make(map[uint64]struct{}), + viewRGViewChangeQuorumCerts: newViewRGViewChangeQuorumCerts(), + selectedRGViewChangeQuorumCerts: newSelectedRGViewChangeQuorumCerts(), } } @@ -349,6 +379,12 @@ func (v *view) Reset() { v.viewBlocks.clear() v.viewQCs.clear() v.viewVotes.clear() + v.viewRGBlockQuorumCerts.clear() + v.selectedRGBlockQuorumCerts.clear() + v.hadSendRGBlockQuorumCerts = make(map[uint32]struct{}) + v.hadSendRGViewChangeQuorumCerts = make(map[uint64]struct{}) + v.viewRGViewChangeQuorumCerts.clear() + v.selectedRGViewChangeQuorumCerts.clear() } func (v *view) ViewNumber() uint64 { @@ -361,28 +397,40 @@ func (v *view) Epoch() uint64 { func (v *view) MarshalJSON() ([]byte, error) { type view struct { - Epoch uint64 `json:"epoch"` - ViewNumber uint64 `json:"viewNumber"` - Executing executing `json:"executing"` - ViewChanges *viewChanges `json:"viewchange"` - LastViewChangeQC *ctypes.ViewChangeQC `json:"lastViewchange"` - HadSendPrepareVote *PrepareVoteQueue `json:"hadSendPrepareVote"` - PendingVote *PrepareVoteQueue `json:"pendingPrepareVote"` - ViewBlocks *viewBlocks `json:"viewBlocks"` - ViewQCs *viewQCs `json:"viewQcs"` - ViewVotes *viewVotes `json:"viewVotes"` + Epoch uint64 `json:"epoch"` + ViewNumber uint64 `json:"viewNumber"` + Executing executing `json:"executing"` + ViewChanges *viewChanges `json:"viewchange"` + LastViewChangeQC *ctypes.ViewChangeQC `json:"lastViewchange"` + HadSendPrepareVote *PrepareVoteQueue `json:"hadSendPrepareVote"` + PendingVote *PrepareVoteQueue `json:"pendingPrepareVote"` + ViewBlocks *viewBlocks `json:"viewBlocks"` + ViewQCs *viewQCs `json:"viewQcs"` + ViewVotes *viewVotes `json:"viewVotes"` + ViewRGBlockQuorumCerts *viewRGBlockQuorumCerts `json:"viewRGBlockQuorumCerts"` + SelectedRGBlockQuorumCerts *selectedRGBlockQuorumCerts `json:"selectedRGBlockQuorumCerts"` + HadSendRGBlockQuorumCerts map[uint32]struct{} `json:"hadSendRGBlockQuorumCerts"` + HadSendRGViewChangeQuorumCerts map[uint64]struct{} `json:"hadSendRGViewChangeQuorumCerts"` + ViewRGViewChangeQuorumCerts *viewRGViewChangeQuorumCerts `json:"viewRGViewChangeQuorumCerts"` + SelectedRGViewChangeQuorumCerts *selectedRGViewChangeQuorumCerts `json:"selectedRGViewChangeQuorumCerts"` } vv := &view{ - Epoch: atomic.LoadUint64(&v.epoch), - ViewNumber: atomic.LoadUint64(&v.viewNumber), - Executing: v.executing, - ViewChanges: v.viewChanges, - LastViewChangeQC: v.lastViewChangeQC, - HadSendPrepareVote: v.hadSendPrepareVote, - PendingVote: v.pendingVote, - ViewBlocks: v.viewBlocks, - ViewQCs: v.viewQCs, - ViewVotes: v.viewVotes, + Epoch: atomic.LoadUint64(&v.epoch), + ViewNumber: atomic.LoadUint64(&v.viewNumber), + Executing: v.executing, + ViewChanges: v.viewChanges, + LastViewChangeQC: v.lastViewChangeQC, + HadSendPrepareVote: v.hadSendPrepareVote, + PendingVote: v.pendingVote, + ViewBlocks: v.viewBlocks, + ViewQCs: v.viewQCs, + ViewVotes: v.viewVotes, + ViewRGBlockQuorumCerts: v.viewRGBlockQuorumCerts, + SelectedRGBlockQuorumCerts: v.selectedRGBlockQuorumCerts, + HadSendRGBlockQuorumCerts: v.hadSendRGBlockQuorumCerts, + HadSendRGViewChangeQuorumCerts: v.hadSendRGViewChangeQuorumCerts, + ViewRGViewChangeQuorumCerts: v.viewRGViewChangeQuorumCerts, + SelectedRGViewChangeQuorumCerts: v.selectedRGViewChangeQuorumCerts, } return json.Marshal(vv) @@ -390,16 +438,22 @@ func (v *view) MarshalJSON() ([]byte, error) { func (v *view) UnmarshalJSON(input []byte) error { type view struct { - Epoch uint64 `json:"epoch"` - ViewNumber uint64 `json:"viewNumber"` - Executing executing `json:"executing"` - ViewChanges *viewChanges `json:"viewchange"` - LastViewChangeQC *ctypes.ViewChangeQC `json:"lastViewchange"` - HadSendPrepareVote *PrepareVoteQueue `json:"hadSendPrepareVote"` - PendingVote *PrepareVoteQueue `json:"pendingPrepareVote"` - ViewBlocks *viewBlocks `json:"viewBlocks"` - ViewQCs *viewQCs `json:"viewQcs"` - ViewVotes *viewVotes `json:"viewVotes"` + Epoch uint64 `json:"epoch"` + ViewNumber uint64 `json:"viewNumber"` + Executing executing `json:"executing"` + ViewChanges *viewChanges `json:"viewchange"` + LastViewChangeQC *ctypes.ViewChangeQC `json:"lastViewchange"` + HadSendPrepareVote *PrepareVoteQueue `json:"hadSendPrepareVote"` + PendingVote *PrepareVoteQueue `json:"pendingPrepareVote"` + ViewBlocks *viewBlocks `json:"viewBlocks"` + ViewQCs *viewQCs `json:"viewQcs"` + ViewVotes *viewVotes `json:"viewVotes"` + ViewRGBlockQuorumCerts *viewRGBlockQuorumCerts `json:"viewRGBlockQuorumCerts"` + SelectedRGBlockQuorumCerts *selectedRGBlockQuorumCerts `json:"selectedRGBlockQuorumCerts"` + HadSendRGBlockQuorumCerts map[uint32]struct{} `json:"hadSendRGBlockQuorumCerts"` + HadSendRGViewChangeQuorumCerts map[uint64]struct{} `json:"hadSendRGViewChangeQuorumCerts"` + ViewRGViewChangeQuorumCerts *viewRGViewChangeQuorumCerts `json:"viewRGViewChangeQuorumCerts"` + SelectedRGViewChangeQuorumCerts *selectedRGViewChangeQuorumCerts `json:"selectedRGViewChangeQuorumCerts"` } var vv view @@ -418,6 +472,12 @@ func (v *view) UnmarshalJSON(input []byte) error { v.viewBlocks = vv.ViewBlocks v.viewQCs = vv.ViewQCs v.viewVotes = vv.ViewVotes + v.viewRGBlockQuorumCerts = vv.ViewRGBlockQuorumCerts + v.selectedRGBlockQuorumCerts = vv.SelectedRGBlockQuorumCerts + v.hadSendRGBlockQuorumCerts = vv.HadSendRGBlockQuorumCerts + v.hadSendRGViewChangeQuorumCerts = vv.HadSendRGViewChangeQuorumCerts + v.viewRGViewChangeQuorumCerts = vv.ViewRGViewChangeQuorumCerts + v.selectedRGViewChangeQuorumCerts = vv.SelectedRGViewChangeQuorumCerts return nil } @@ -763,6 +823,118 @@ func (vs *ViewState) String() string { return fmt.Sprintf("") } +// viewRGBlockQuorumCerts +func (vs *ViewState) FindRGBlockQuorumCerts(blockIndex, groupID, validatorIndex uint32) *protocols.RGBlockQuorumCert { + return vs.viewRGBlockQuorumCerts.FindRGBlockQuorumCerts(blockIndex, groupID, validatorIndex) +} + +//func (vs *ViewState) FindGroupRGBlockQuorumCerts(blockIndex, groupID uint32) map[uint32]*protocols.RGBlockQuorumCert { +// return vs.viewRGBlockQuorumCerts.FindGroupRGBlockQuorumCerts(blockIndex, groupID) +//} + +func (vs *ViewState) AddRGBlockQuorumCert(nodeIndex uint32, rgb *protocols.RGBlockQuorumCert) { + vs.viewRGBlockQuorumCerts.AddRGBlockQuorumCerts(rgb.BlockIndx(), rgb) +} + +func (vs *ViewState) RGBlockQuorumCertsLen(blockIndex, groupID uint32) int { + return vs.viewRGBlockQuorumCerts.RGBlockQuorumCertsLen(blockIndex, groupID) +} + +func (vs *ViewState) RGBlockQuorumCertsIndexes(blockIndex, groupID uint32) []uint32 { + return vs.viewRGBlockQuorumCerts.RGBlockQuorumCertsIndexes(blockIndex, groupID) +} + +// selectedRGBlockQuorumCerts +func (vs *ViewState) AddSelectRGQuorumCerts(blockIndex, groupID uint32, rgqc *ctypes.QuorumCert, parentQC *ctypes.QuorumCert) { + vs.selectedRGBlockQuorumCerts.AddRGQuorumCerts(blockIndex, groupID, rgqc, parentQC) +} + +func (vs *ViewState) SelectRGQuorumCertsLen(blockIndex, groupID uint32) int { + return vs.selectedRGBlockQuorumCerts.RGQuorumCertsLen(blockIndex, groupID) +} + +func (vs *ViewState) FindMaxRGQuorumCerts(blockIndex uint32) []*ctypes.QuorumCert { + return vs.selectedRGBlockQuorumCerts.FindMaxRGQuorumCerts(blockIndex) +} + +func (vs *ViewState) FindMaxGroupRGQuorumCert(blockIndex, groupID uint32) (*ctypes.QuorumCert, *ctypes.QuorumCert) { + return vs.selectedRGBlockQuorumCerts.FindMaxGroupRGQuorumCert(blockIndex, groupID) +} + +func (vs *ViewState) MergePrepareVotes(blockIndex, groupID uint32, votes []*protocols.PrepareVote) { + for _, v := range votes { + vs.selectedRGBlockQuorumCerts.MergePrepareVote(blockIndex, groupID, v) + } +} + +// viewRGViewChangeQuorumCerts +func (vs *ViewState) AddRGViewChangeQuorumCert(nodeIndex uint32, rgb *protocols.RGViewChangeQuorumCert) { + vs.viewRGViewChangeQuorumCerts.AddRGViewChangeQuorumCerts(rgb) +} + +func (vs *ViewState) FindRGViewChangeQuorumCerts(groupID uint32, validatorIndex uint32) *protocols.RGViewChangeQuorumCert { + return vs.viewRGViewChangeQuorumCerts.FindRGViewChangeQuorumCerts(groupID, validatorIndex) +} + +func (vs *ViewState) RGViewChangeQuorumCertsLen(groupID uint32) int { + return vs.viewRGViewChangeQuorumCerts.RGViewChangeQuorumCertsLen(groupID) +} + +func (vs *ViewState) RGViewChangeQuorumCertsIndexes(groupID uint32) []uint32 { + return vs.viewRGViewChangeQuorumCerts.RGViewChangeQuorumCertsIndexes(groupID) +} + +// selectedRGViewChangeQuorumCerts +func (vs *ViewState) AddSelectRGViewChangeQuorumCerts(groupID uint32, rgqc *ctypes.ViewChangeQC, prepareQCs map[common.Hash]*ctypes.QuorumCert) { + rgqcs := make(map[common.Hash]*ctypes.ViewChangeQuorumCert) + for _, qc := range rgqc.QCs { + rgqcs[qc.BlockHash] = qc + } + vs.selectedRGViewChangeQuorumCerts.AddRGViewChangeQuorumCerts(groupID, rgqcs, prepareQCs) +} + +func (vs *ViewState) MergeViewChanges(groupID uint32, vcs []*protocols.ViewChange) { + for _, vc := range vcs { + vs.selectedRGViewChangeQuorumCerts.MergeViewChange(groupID, vc) + } +} + +func (vs *ViewState) SelectRGViewChangeQuorumCertsLen(groupID uint32) int { + return vs.selectedRGViewChangeQuorumCerts.RGViewChangeQuorumCertsLen(groupID) +} + +func (vs *ViewState) FindMaxGroupRGViewChangeQuorumCert(groupID uint32) (*ctypes.ViewChangeQC, map[common.Hash]*ctypes.QuorumCert) { + return vs.selectedRGViewChangeQuorumCerts.FindMaxGroupRGViewChangeQuorumCert(groupID) +} + +func (vs *ViewState) FindMaxRGViewChangeQuorumCerts() []*ctypes.ViewChangeQC { + return vs.selectedRGViewChangeQuorumCerts.FindMaxRGViewChangeQuorumCert() +} + +// hadSendRGBlockQuorumCerts +func (vs *ViewState) AddSendRGBlockQuorumCerts(blockIndex uint32) { + vs.hadSendRGBlockQuorumCerts[blockIndex] = struct{}{} +} + +func (vs *ViewState) HadSendRGBlockQuorumCerts(blockIndex uint32) bool { + if _, ok := vs.hadSendRGBlockQuorumCerts[blockIndex]; ok { + return true + } + return false +} + +// hadSendRGViewChangeQuorumCerts +func (vs *ViewState) AddSendRGViewChangeQuorumCerts(viewNumber uint64) { + vs.hadSendRGViewChangeQuorumCerts[viewNumber] = struct{}{} +} + +func (vs *ViewState) HadSendRGViewChangeQuorumCerts(viewNumber uint64) bool { + if _, ok := vs.hadSendRGViewChangeQuorumCerts[viewNumber]; ok { + return true + } + return false +} + func (vs *ViewState) MarshalJSON() ([]byte, error) { type hashNumber struct { Hash common.Hash `json:"hash"` @@ -808,3 +980,479 @@ func (vs *ViewState) UnmarshalJSON(input []byte) error { vs.SetHighestCommitBlock(types.NewSimplifiedBlock(s.HighestCommitBlock.Number, s.HighestCommitBlock.Hash)) return nil } + +// viewRGBlockQuorumCerts +type viewRGBlockQuorumCerts struct { + BlockRGBlockQuorumCerts map[uint32]*groupRGBlockQuorumCerts `json:"blockRGBlockQuorumCerts"` // The map key is blockIndex +} + +type groupRGBlockQuorumCerts struct { + GroupRGBlockQuorumCerts map[uint32]*validatorRGBlockQuorumCerts `json:"groupRGBlockQuorumCerts"` // The map key is groupID +} + +type validatorRGBlockQuorumCerts struct { + ValidatorRGBlockQuorumCerts map[uint32]*protocols.RGBlockQuorumCert `json:"validatorRGBlockQuorumCerts"` // The map key is ValidatorIndex +} + +func newViewRGBlockQuorumCerts() *viewRGBlockQuorumCerts { + return &viewRGBlockQuorumCerts{ + BlockRGBlockQuorumCerts: make(map[uint32]*groupRGBlockQuorumCerts), + } +} + +func newGroupRGBlockQuorumCerts() *groupRGBlockQuorumCerts { + return &groupRGBlockQuorumCerts{ + GroupRGBlockQuorumCerts: make(map[uint32]*validatorRGBlockQuorumCerts), + } +} + +func newValidatorRGBlockQuorumCerts() *validatorRGBlockQuorumCerts { + return &validatorRGBlockQuorumCerts{ + ValidatorRGBlockQuorumCerts: make(map[uint32]*protocols.RGBlockQuorumCert), + } +} + +func (vrg *validatorRGBlockQuorumCerts) addRGBlockQuorumCerts(validatorIndex uint32, rg *protocols.RGBlockQuorumCert) bool { + if _, ok := vrg.ValidatorRGBlockQuorumCerts[validatorIndex]; !ok { + vrg.ValidatorRGBlockQuorumCerts[validatorIndex] = rg + return true + } + return false +} + +func (grg *groupRGBlockQuorumCerts) addRGBlockQuorumCerts(groupID uint32, rg *protocols.RGBlockQuorumCert) { + if ps, ok := grg.GroupRGBlockQuorumCerts[groupID]; ok { + ps.addRGBlockQuorumCerts(rg.ValidatorIndex, rg) + } else { + vrg := newValidatorRGBlockQuorumCerts() + vrg.addRGBlockQuorumCerts(rg.ValidatorIndex, rg) + grg.GroupRGBlockQuorumCerts[groupID] = vrg + } +} + +func (brg *viewRGBlockQuorumCerts) AddRGBlockQuorumCerts(blockIndex uint32, rg *protocols.RGBlockQuorumCert) { + groupID := rg.GroupID + if ps, ok := brg.BlockRGBlockQuorumCerts[blockIndex]; ok { + ps.addRGBlockQuorumCerts(groupID, rg) + } else { + grg := newGroupRGBlockQuorumCerts() + grg.addRGBlockQuorumCerts(groupID, rg) + brg.BlockRGBlockQuorumCerts[blockIndex] = grg + } +} + +func (brg *viewRGBlockQuorumCerts) clear() { + brg.BlockRGBlockQuorumCerts = make(map[uint32]*groupRGBlockQuorumCerts) +} + +func (vrg *validatorRGBlockQuorumCerts) findRGBlockQuorumCerts(validatorIndex uint32) *protocols.RGBlockQuorumCert { + if ps, ok := vrg.ValidatorRGBlockQuorumCerts[validatorIndex]; ok { + return ps + } + return nil +} + +func (grg *groupRGBlockQuorumCerts) findRGBlockQuorumCerts(groupID uint32, validatorIndex uint32) *protocols.RGBlockQuorumCert { + if ps, ok := grg.GroupRGBlockQuorumCerts[groupID]; ok { + return ps.findRGBlockQuorumCerts(validatorIndex) + } + return nil +} + +func (brg *viewRGBlockQuorumCerts) FindRGBlockQuorumCerts(blockIndex, groupID, validatorIndex uint32) *protocols.RGBlockQuorumCert { + if ps, ok := brg.BlockRGBlockQuorumCerts[blockIndex]; ok { + return ps.findRGBlockQuorumCerts(groupID, validatorIndex) + } + return nil +} + +//func (brg *viewRGBlockQuorumCerts) FindGroupRGBlockQuorumCerts(blockIndex, groupID uint32) map[uint32]*protocols.RGBlockQuorumCert { +// if ps, ok := brg.BlockRGBlockQuorumCerts[blockIndex]; ok { +// if gs, ok := ps.GroupRGBlockQuorumCerts[groupID]; ok { +// return gs.ValidatorRGBlockQuorumCerts +// } +// } +// return nil +//} + +func (brg *viewRGBlockQuorumCerts) RGBlockQuorumCertsLen(blockIndex, groupID uint32) int { + if ps, ok := brg.BlockRGBlockQuorumCerts[blockIndex]; ok { + if gs, ok := ps.GroupRGBlockQuorumCerts[groupID]; ok { + return len(gs.ValidatorRGBlockQuorumCerts) + } + } + return 0 +} + +func (brg *viewRGBlockQuorumCerts) RGBlockQuorumCertsIndexes(blockIndex, groupID uint32) []uint32 { + if ps, ok := brg.BlockRGBlockQuorumCerts[blockIndex]; ok { + if gs, ok := ps.GroupRGBlockQuorumCerts[groupID]; ok { + indexes := make([]uint32, 0, len(gs.ValidatorRGBlockQuorumCerts)) + for i, _ := range gs.ValidatorRGBlockQuorumCerts { + indexes = append(indexes, i) + } + return indexes + } + } + return nil +} + +// selectedRGBlockQuorumCerts +type selectedRGBlockQuorumCerts struct { + BlockRGBlockQuorumCerts map[uint32]*QuorumCerts `json:"blockRGBlockQuorumCerts"` // The map key is blockIndex +} + +type QuorumCerts struct { + GroupQuorumCerts map[uint32][]*ctypes.QuorumCert `json:"groupQuorumCerts"` // The map key is groupID + ParentQC *ctypes.QuorumCert `json:"parentQC"` +} + +func newSelectedRGBlockQuorumCerts() *selectedRGBlockQuorumCerts { + return &selectedRGBlockQuorumCerts{ + BlockRGBlockQuorumCerts: make(map[uint32]*QuorumCerts), + } +} + +func newQuorumCerts() *QuorumCerts { + return &QuorumCerts{ + GroupQuorumCerts: make(map[uint32][]*ctypes.QuorumCert), + } +} + +func (grg *QuorumCerts) addRGQuorumCerts(groupID uint32, rgqc *ctypes.QuorumCert) { + if ps, ok := grg.GroupQuorumCerts[groupID]; ok { + if len(ps) > 0 { + for i := len(ps) - 1; i >= 0; i-- { + if ps[i].ValidatorSet.Contains(rgqc.ValidatorSet) { + return + } + if rgqc.ValidatorSet.Contains(ps[i].ValidatorSet) { + grg.GroupQuorumCerts[groupID] = append(grg.GroupQuorumCerts[groupID][:i], grg.GroupQuorumCerts[groupID][i+1:]...) + //return + } + } + } + if len(ps) < maxSelectedRGLimit { + grg.GroupQuorumCerts[groupID] = append(grg.GroupQuorumCerts[groupID], rgqc) + } + } else { + qcs := make([]*ctypes.QuorumCert, 0, maxSelectedRGLimit) + qcs = append(qcs, rgqc) + grg.GroupQuorumCerts[groupID] = qcs + } +} + +func (srg *selectedRGBlockQuorumCerts) AddRGQuorumCerts(blockIndex, groupID uint32, rgqc *ctypes.QuorumCert, parentQC *ctypes.QuorumCert) { + if ps, ok := srg.BlockRGBlockQuorumCerts[blockIndex]; ok { + ps.addRGQuorumCerts(groupID, rgqc) + if ps.ParentQC == nil { + ps.ParentQC = parentQC + } + } else { + grg := newQuorumCerts() + grg.addRGQuorumCerts(groupID, rgqc) + grg.ParentQC = parentQC + srg.BlockRGBlockQuorumCerts[blockIndex] = grg + } +} + +func (srg *selectedRGBlockQuorumCerts) FindRGQuorumCerts(blockIndex, groupID uint32) []*ctypes.QuorumCert { + if ps, ok := srg.BlockRGBlockQuorumCerts[blockIndex]; ok { + if gs, ok := ps.GroupQuorumCerts[groupID]; ok { + return gs + } + } + return nil +} + +func (srg *selectedRGBlockQuorumCerts) RGQuorumCertsLen(blockIndex, groupID uint32) int { + if ps, ok := srg.BlockRGBlockQuorumCerts[blockIndex]; ok { + if gs, ok := ps.GroupQuorumCerts[groupID]; ok { + return len(gs) + } + } + return 0 +} + +// Returns the QuorumCert with the most signatures in each group +func (srg *selectedRGBlockQuorumCerts) FindMaxRGQuorumCerts(blockIndex uint32) []*ctypes.QuorumCert { + if ps, ok := srg.BlockRGBlockQuorumCerts[blockIndex]; ok { + var maxs []*ctypes.QuorumCert + if len(ps.GroupQuorumCerts) > 0 { + maxs = make([]*ctypes.QuorumCert, 0, len(ps.GroupQuorumCerts)) + for _, qcs := range ps.GroupQuorumCerts { + m := qcs[0] + for i := 1; i < len(qcs); i++ { + if qcs[i].HigherSign(m) { + m = qcs[i] + } + } + maxs = append(maxs, m) + } + } + return maxs + } + return nil +} + +// Returns the QuorumCert with the most signatures in specified group +func (srg *selectedRGBlockQuorumCerts) FindMaxGroupRGQuorumCert(blockIndex, groupID uint32) (*ctypes.QuorumCert, *ctypes.QuorumCert) { + if ps, ok := srg.BlockRGBlockQuorumCerts[blockIndex]; ok { + parentQC := ps.ParentQC + if gs, ok := ps.GroupQuorumCerts[groupID]; ok { + max := gs[0] + for i := 1; i < len(gs); i++ { + if gs[i].HigherSign(max) { + max = gs[i] + } + } + return max, parentQC + } + } + return nil, nil +} + +func (srg *selectedRGBlockQuorumCerts) MergePrepareVote(blockIndex, groupID uint32, vote *protocols.PrepareVote) { + rgqcs := srg.FindRGQuorumCerts(blockIndex, groupID) + if rgqcs == nil || len(rgqcs) <= 0 { + return + } + + for _, qc := range rgqcs { + if !qc.ValidatorSet.GetIndex(vote.NodeIndex()) { + qc.AddSign(vote.Signature, vote.NodeIndex()) + } + } +} + +func (srg *selectedRGBlockQuorumCerts) clear() { + srg.BlockRGBlockQuorumCerts = make(map[uint32]*QuorumCerts) +} + +// viewRGViewChangeQuorumCerts +type viewRGViewChangeQuorumCerts struct { + GroupRGViewChangeQuorumCerts map[uint32]*validatorRGViewChangeQuorumCerts `json:"groupRGViewChangeQuorumCerts"` // The map key is groupID +} + +type validatorRGViewChangeQuorumCerts struct { + ValidatorRGViewChangeQuorumCerts map[uint32]*protocols.RGViewChangeQuorumCert `json:"validatorRGViewChangeQuorumCerts"` // The map key is ValidatorIndex +} + +func newViewRGViewChangeQuorumCerts() *viewRGViewChangeQuorumCerts { + return &viewRGViewChangeQuorumCerts{ + GroupRGViewChangeQuorumCerts: make(map[uint32]*validatorRGViewChangeQuorumCerts), + } +} + +func newValidatorRGViewChangeQuorumCerts() *validatorRGViewChangeQuorumCerts { + return &validatorRGViewChangeQuorumCerts{ + ValidatorRGViewChangeQuorumCerts: make(map[uint32]*protocols.RGViewChangeQuorumCert), + } +} + +func (vrg *validatorRGViewChangeQuorumCerts) addRGViewChangeQuorumCerts(validatorIndex uint32, rg *protocols.RGViewChangeQuorumCert) bool { + if _, ok := vrg.ValidatorRGViewChangeQuorumCerts[validatorIndex]; !ok { + vrg.ValidatorRGViewChangeQuorumCerts[validatorIndex] = rg + return true + } + return false +} + +func (brg *viewRGViewChangeQuorumCerts) AddRGViewChangeQuorumCerts(rg *protocols.RGViewChangeQuorumCert) { + groupID := rg.GroupID + validatorIndex := rg.ValidatorIndex + if ps, ok := brg.GroupRGViewChangeQuorumCerts[groupID]; ok { + ps.addRGViewChangeQuorumCerts(validatorIndex, rg) + } else { + vrg := newValidatorRGViewChangeQuorumCerts() + vrg.addRGViewChangeQuorumCerts(validatorIndex, rg) + brg.GroupRGViewChangeQuorumCerts[groupID] = vrg + } +} + +func (vrg *validatorRGViewChangeQuorumCerts) FindRGViewChangeQuorumCerts(validatorIndex uint32) *protocols.RGViewChangeQuorumCert { + if ps, ok := vrg.ValidatorRGViewChangeQuorumCerts[validatorIndex]; ok { + return ps + } + return nil +} + +func (brg *viewRGViewChangeQuorumCerts) FindRGViewChangeQuorumCerts(groupID uint32, validatorIndex uint32) *protocols.RGViewChangeQuorumCert { + if ps, ok := brg.GroupRGViewChangeQuorumCerts[groupID]; ok { + return ps.FindRGViewChangeQuorumCerts(validatorIndex) + } + return nil +} + +func (brg *viewRGViewChangeQuorumCerts) RGViewChangeQuorumCertsLen(groupID uint32) int { + if ps, ok := brg.GroupRGViewChangeQuorumCerts[groupID]; ok { + return len(ps.ValidatorRGViewChangeQuorumCerts) + } + return 0 +} + +func (brg *viewRGViewChangeQuorumCerts) RGViewChangeQuorumCertsIndexes(groupID uint32) []uint32 { + if ps, ok := brg.GroupRGViewChangeQuorumCerts[groupID]; ok { + indexes := make([]uint32, 0, len(ps.ValidatorRGViewChangeQuorumCerts)) + for i, _ := range ps.ValidatorRGViewChangeQuorumCerts { + indexes = append(indexes, i) + } + return indexes + } + return nil +} + +func (brg *viewRGViewChangeQuorumCerts) clear() { + brg.GroupRGViewChangeQuorumCerts = make(map[uint32]*validatorRGViewChangeQuorumCerts) +} + +// selectedRGViewChangeQuorumCerts +type selectedRGViewChangeQuorumCerts struct { + GroupRGViewChangeQuorumCerts map[uint32]*ViewChangeQuorumCerts `json:"groupRGViewChangeQuorumCerts"` // The map key is groupID + PrepareQCs map[common.Hash]*ctypes.QuorumCert `json:"prepareQCs"` +} + +type ViewChangeQuorumCerts struct { + QuorumCerts map[common.Hash][]*ctypes.ViewChangeQuorumCert `json:"quorumCerts"` // The map key is blockHash +} + +func newSelectedRGViewChangeQuorumCerts() *selectedRGViewChangeQuorumCerts { + return &selectedRGViewChangeQuorumCerts{ + GroupRGViewChangeQuorumCerts: make(map[uint32]*ViewChangeQuorumCerts), + PrepareQCs: make(map[common.Hash]*ctypes.QuorumCert), + } +} + +func newViewChangeQuorumCerts() *ViewChangeQuorumCerts { + return &ViewChangeQuorumCerts{ + QuorumCerts: make(map[common.Hash][]*ctypes.ViewChangeQuorumCert), + } +} + +func (grg *ViewChangeQuorumCerts) addRGViewChangeQuorumCert(hash common.Hash, rgqc *ctypes.ViewChangeQuorumCert) { + if ps, ok := grg.QuorumCerts[hash]; ok { + if len(ps) > 0 { + for i := len(ps) - 1; i >= 0; i-- { + if ps[i].ValidatorSet.Contains(rgqc.ValidatorSet) { + return + } + if rgqc.ValidatorSet.Contains(ps[i].ValidatorSet) { + grg.QuorumCerts[hash] = append(grg.QuorumCerts[hash][:i], grg.QuorumCerts[hash][i+1:]...) + //return + } + } + } + if len(ps) < maxSelectedRGLimit { + grg.QuorumCerts[hash] = append(grg.QuorumCerts[hash], rgqc) + } + } else { + qcs := make([]*ctypes.ViewChangeQuorumCert, 0, maxSelectedRGLimit) + qcs = append(qcs, rgqc) + grg.QuorumCerts[hash] = qcs + } +} + +func (grg *ViewChangeQuorumCerts) addRGViewChangeQuorumCerts(rgqcs map[common.Hash]*ctypes.ViewChangeQuorumCert) { + for hash, qc := range rgqcs { + grg.addRGViewChangeQuorumCert(hash, qc) + } +} + +func (srg *selectedRGViewChangeQuorumCerts) AddRGViewChangeQuorumCerts(groupID uint32, rgqcs map[common.Hash]*ctypes.ViewChangeQuorumCert, prepareQCs map[common.Hash]*ctypes.QuorumCert) { + if ps, ok := srg.GroupRGViewChangeQuorumCerts[groupID]; ok { + ps.addRGViewChangeQuorumCerts(rgqcs) + } else { + grg := newViewChangeQuorumCerts() + grg.addRGViewChangeQuorumCerts(rgqcs) + srg.GroupRGViewChangeQuorumCerts[groupID] = grg + } + for hash, qc := range prepareQCs { + if srg.PrepareQCs[hash] == nil { + srg.PrepareQCs[hash] = qc + } + } +} + +func (srg *selectedRGViewChangeQuorumCerts) MergeViewChange(groupID uint32, vc *protocols.ViewChange) { + rgqcs := srg.GroupRGViewChangeQuorumCerts[groupID] + if rgqcs == nil || len(rgqcs.QuorumCerts) <= 0 { + // If there is no aggregate signature under the group at this time, then the single ViewChange is not merged + return + } + + if qcs, ok := rgqcs.QuorumCerts[vc.BHash()]; ok { + for _, qc := range qcs { + if !qc.ValidatorSet.GetIndex(vc.NodeIndex()) { + qc.AddSign(vc.Signature, vc.NodeIndex()) + } + } + } else { + qcs := make([]*ctypes.ViewChangeQuorumCert, 0, maxSelectedRGLimit) + qc := &ctypes.ViewChangeQuorumCert{ + Epoch: vc.Epoch, + ViewNumber: vc.ViewNumber, + BlockHash: vc.BlockHash, + BlockNumber: vc.BlockNumber, + //BlockEpoch: vc.PrepareQC.Epoch, + //BlockViewNumber: vc.PrepareQC.ViewNumber, + ValidatorSet: utils.NewBitArray(vc.PrepareQC.ValidatorSet.Size()), + } + if vc.PrepareQC != nil { + qc.BlockEpoch = vc.PrepareQC.Epoch + qc.BlockViewNumber = vc.PrepareQC.ViewNumber + } + qc.Signature.SetBytes(vc.Signature.Bytes()) + qc.ValidatorSet.SetIndex(vc.ValidatorIndex, true) + qcs = append(qcs, qc) + + srg.GroupRGViewChangeQuorumCerts[groupID].QuorumCerts[vc.BHash()] = qcs + srg.PrepareQCs[vc.BHash()] = vc.PrepareQC + } +} + +func (srg *selectedRGViewChangeQuorumCerts) RGViewChangeQuorumCertsLen(groupID uint32) int { + if ps, ok := srg.GroupRGViewChangeQuorumCerts[groupID]; ok { + if ps != nil { + return len(ps.QuorumCerts) + } + } + return 0 +} + +// Returns the QuorumCert with the most signatures in specified group +func (srg *selectedRGViewChangeQuorumCerts) FindMaxGroupRGViewChangeQuorumCert(groupID uint32) (*ctypes.ViewChangeQC, map[common.Hash]*ctypes.QuorumCert) { + rgqcs := srg.GroupRGViewChangeQuorumCerts[groupID] + if rgqcs == nil || len(rgqcs.QuorumCerts) <= 0 { + return nil, nil + } + + viewChangeQC := &ctypes.ViewChangeQC{QCs: make([]*ctypes.ViewChangeQuorumCert, 0)} + prepareQCs := make(map[common.Hash]*ctypes.QuorumCert) + for hash, qcs := range rgqcs.QuorumCerts { + max := qcs[0] + for i := 1; i < len(qcs); i++ { + if qcs[i].HigherSign(max) { + max = qcs[i] + } + } + viewChangeQC.QCs = append(viewChangeQC.QCs, max) + prepareQCs[hash] = srg.PrepareQCs[hash] + } + return viewChangeQC, prepareQCs +} + +func (srg *selectedRGViewChangeQuorumCerts) FindMaxRGViewChangeQuorumCert() []*ctypes.ViewChangeQC { + viewChangeQCs := make([]*ctypes.ViewChangeQC, 0, len(srg.GroupRGViewChangeQuorumCerts)) + for groupID, _ := range srg.GroupRGViewChangeQuorumCerts { + viewChangeQC, _ := srg.FindMaxGroupRGViewChangeQuorumCert(groupID) + if viewChangeQC != nil { + viewChangeQCs = append(viewChangeQCs, viewChangeQC) + } + } + return viewChangeQCs +} + +func (srg *selectedRGViewChangeQuorumCerts) clear() { + srg.GroupRGViewChangeQuorumCerts = make(map[uint32]*ViewChangeQuorumCerts) + srg.PrepareQCs = make(map[common.Hash]*ctypes.QuorumCert) +} diff --git a/consensus/cbft/state/state_test.go b/consensus/cbft/state/state_test.go index e7dd3609d4..8e2a9eb857 100644 --- a/consensus/cbft/state/state_test.go +++ b/consensus/cbft/state/state_test.go @@ -220,5 +220,119 @@ func TestNewViewChanges(t *testing.T) { assert.Equal(t, 10, viewState.ViewChangeLen()) assert.Equal(t, 10, len(viewState.AllViewChange())) assert.Equal(t, uint32(9), viewState.ViewChangeByIndex(9).ValidatorIndex) +} + +func TestViewRGBlockQuorumCerts(t *testing.T) { + v := newViewRGBlockQuorumCerts() + v.AddRGBlockQuorumCerts(0, &protocols.RGBlockQuorumCert{ + GroupID: 1, + BlockQC: &ctypes.QuorumCert{ + BlockIndex: 0, + }, + ValidatorIndex: 11, + }) + v.AddRGBlockQuorumCerts(0, &protocols.RGBlockQuorumCert{ + GroupID: 2, + BlockQC: &ctypes.QuorumCert{ + BlockIndex: 0, + }, + ValidatorIndex: 22, + }) + v.AddRGBlockQuorumCerts(0, &protocols.RGBlockQuorumCert{ + GroupID: 1, + BlockQC: &ctypes.QuorumCert{ + BlockIndex: 0, + }, + ValidatorIndex: 12, + }) + v.AddRGBlockQuorumCerts(1, &protocols.RGBlockQuorumCert{ + GroupID: 3, + BlockQC: &ctypes.QuorumCert{ + BlockIndex: 1, + }, + ValidatorIndex: 33, + }) + v.AddRGBlockQuorumCerts(1, &protocols.RGBlockQuorumCert{ + GroupID: 5, + BlockQC: &ctypes.QuorumCert{ + BlockIndex: 1, + }, + ValidatorIndex: 55, + }) + v.AddRGBlockQuorumCerts(2, &protocols.RGBlockQuorumCert{ + GroupID: 1, + BlockQC: &ctypes.QuorumCert{ + BlockIndex: 2, + }, + ValidatorIndex: 11, + }) + v.AddRGBlockQuorumCerts(2, &protocols.RGBlockQuorumCert{ + GroupID: 2, + BlockQC: &ctypes.QuorumCert{ + BlockIndex: 2, + }, + ValidatorIndex: 22, + }) + v.AddRGBlockQuorumCerts(0, &protocols.RGBlockQuorumCert{ + GroupID: 1, + BlockQC: &ctypes.QuorumCert{ + BlockIndex: 0, + }, + ValidatorIndex: 12, + }) + //c, _ := json.Marshal(v) + //fmt.Println(string(c)) + + assert.Equal(t, 2, v.RGBlockQuorumCertsLen(0, 1)) + assert.Equal(t, 1, v.RGBlockQuorumCertsLen(2, 1)) + assert.Equal(t, 0, v.RGBlockQuorumCertsLen(2, 3)) + assert.Equal(t, 0, v.RGBlockQuorumCertsLen(3, 1)) + + assert.Nil(t, v.FindRGBlockQuorumCerts(3, 1, 1)) + assert.Nil(t, v.FindRGBlockQuorumCerts(0, 3, 1)) + assert.Nil(t, v.FindRGBlockQuorumCerts(0, 1, 13)) + assert.Equal(t, uint32(11), v.FindRGBlockQuorumCerts(0, 1, 11).ValidatorIndex) +} + +func TestSelectedRGBlockQuorumCerts(t *testing.T) { + bitArrayStr := []string{ + `"x"`, + `"xxxx"`, + `"xx"`, + `"x_x_x_"`, + `"xx__x_"`, + `"xxx_x_"`, + } + + bitArray := func(index int) *utils.BitArray { + var ba *utils.BitArray + json.Unmarshal([]byte(bitArrayStr[index]), &ba) + return ba + } + + s := newSelectedRGBlockQuorumCerts() + for i, _ := range bitArrayStr { + s.AddRGQuorumCerts(0, 1, &ctypes.QuorumCert{ + BlockIndex: 0, + ValidatorSet: bitArray(i), + }, nil) + } + for i, _ := range bitArrayStr { + s.AddRGQuorumCerts(0, 2, &ctypes.QuorumCert{ + BlockIndex: 0, + ValidatorSet: bitArray(i), + }, nil) + } + for i, _ := range bitArrayStr { + s.AddRGQuorumCerts(1, 1, &ctypes.QuorumCert{ + BlockIndex: 0, + ValidatorSet: bitArray(i), + }, nil) + } + c, _ := json.Marshal(s) + fmt.Println(string(c)) + assert.Equal(t, 2, len(s.FindRGQuorumCerts(0, 1))) + assert.Equal(t, 2, len(s.FindRGQuorumCerts(0, 2))) + assert.Equal(t, 2, len(s.FindRGQuorumCerts(1, 1))) } diff --git a/consensus/cbft/sync_process.go b/consensus/cbft/sync_process.go index 568ee09d85..a2fbcbc665 100644 --- a/consensus/cbft/sync_process.go +++ b/consensus/cbft/sync_process.go @@ -211,10 +211,10 @@ func (cbft *Cbft) prepareBlockFetchRules(id string, pb *protocols.PrepareBlock) } // Get votes and blocks that are not available locally based on the height of the vote -func (cbft *Cbft) prepareVoteFetchRules(id string, vote *protocols.PrepareVote) { +func (cbft *Cbft) prepareVoteFetchRules(id string, msg ctypes.ConsensusMsg) { // Greater than QC+1 means the vote is behind - if vote.BlockNumber > cbft.state.HighestQCBlock().NumberU64()+1 { - for i := uint32(0); i <= vote.BlockIndex; i++ { + if msg.BlockNum() > cbft.state.HighestQCBlock().NumberU64()+1 { + for i := uint32(0); i <= msg.BlockIndx(); i++ { b, qc := cbft.state.ViewBlockAndQC(i) if b == nil { cbft.SyncPrepareBlock(id, cbft.state.Epoch(), cbft.state.ViewNumber(), i) @@ -704,7 +704,7 @@ func (cbft *Cbft) MissingPrepareVote() (v *protocols.GetPrepareVote, err error) unKnownSet := utils.NewBitArray(uint32(len)) for i := uint32(0); i < unKnownSet.Size(); i++ { if vote := cbft.csPool.GetPrepareVote(cbft.state.Epoch(), cbft.state.ViewNumber(), index, i); vote != nil { - go cbft.ReceiveMessage(vote) + go cbft.ReceiveMessage(ctypes.NewInnerMsgInfo(vote.Msg, vote.PeerID)) continue } if _, ok := knownVotes[i]; !ok { @@ -865,7 +865,7 @@ func calAverage(latencyList *list.List) int64 { func (cbft *Cbft) SyncPrepareBlock(id string, epoch uint64, viewNumber uint64, blockIndex uint32) { if msg := cbft.csPool.GetPrepareBlock(epoch, viewNumber, blockIndex); msg != nil { - go cbft.ReceiveMessage(msg) + go cbft.ReceiveMessage(ctypes.NewInnerMsgInfo(msg.Msg, msg.PeerID)) } if cbft.syncingCache.AddOrReplace(blockIndex) { msg := &protocols.GetPrepareBlock{Epoch: epoch, ViewNumber: viewNumber, BlockIndex: blockIndex} @@ -881,12 +881,11 @@ func (cbft *Cbft) SyncPrepareBlock(id string, epoch uint64, viewNumber uint64, b func (cbft *Cbft) SyncBlockQuorumCert(id string, blockNumber uint64, blockHash common.Hash, blockIndex uint32) { if msg := cbft.csPool.GetPrepareQC(cbft.state.Epoch(), cbft.state.ViewNumber(), blockIndex); msg != nil { - go cbft.ReceiveMessage(msg) + go cbft.ReceiveSyncMsg(ctypes.NewInnerMsgInfo(msg.Msg, msg.PeerID)) } if cbft.syncingCache.AddOrReplace(blockHash) { msg := &protocols.GetBlockQuorumCert{BlockHash: blockHash, BlockNumber: blockNumber} cbft.network.Send(id, msg) cbft.log.Debug("Send GetBlockQuorumCert", "peer", id, "msg", msg.String()) } - } diff --git a/consensus/cbft/types/cache_test.go b/consensus/cbft/types/cache_test.go index e1cbae0a9b..a82a3a2141 100644 --- a/consensus/cbft/types/cache_test.go +++ b/consensus/cbft/types/cache_test.go @@ -61,6 +61,14 @@ func (m mockCSMsg) NodeIndex() uint32 { panic("implement me") } +func (m mockCSMsg) BlockIndx() uint32 { + panic("implement me") +} + +func (m mockCSMsg) CheckQC() *QuorumCert { + panic("implement me") +} + func (m mockCSMsg) CannibalizeBytes() ([]byte, error) { panic("implement me") } diff --git a/consensus/cbft/types/crypto.go b/consensus/cbft/types/crypto.go index e5c47d56a5..dab414803c 100644 --- a/consensus/cbft/types/crypto.go +++ b/consensus/cbft/types/crypto.go @@ -94,6 +94,10 @@ func (q QuorumCert) CannibalizeBytes() ([]byte, error) { } func (q QuorumCert) Len() int { + if q.ValidatorSet == nil { + return 0 + } + length := 0 for i := uint32(0); i < q.ValidatorSet.Size(); i++ { if q.ValidatorSet.GetIndex(i) { @@ -107,7 +111,54 @@ func (q *QuorumCert) String() string { if q == nil { return "" } - return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,Hash:%s,Number:%d,Index:%d,ValidatorSet:%s}", q.Epoch, q.ViewNumber, q.BlockHash.TerminalString(), q.BlockNumber, q.BlockIndex, q.ValidatorSet.String()) + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,Hash:%s,Number:%d,Index:%d,Signature:%s,ValidatorSet:%s}", q.Epoch, q.ViewNumber, q.BlockHash.TerminalString(), q.BlockNumber, q.BlockIndex, q.Signature.String(), q.ValidatorSet.String()) +} + +// Add a new signature to the aggregate signature +// Note: Call this method to ensure that the new signature does not exist in the aggregate signature, otherwise the entire aggregate signature will be wrong +func (q *QuorumCert) AddSign(sign Signature, NodeIndex uint32) bool { + if q == nil { + return false + } + var ( + addSig bls.Sign + blsSig bls.Sign + ) + if err := addSig.Deserialize(sign.Bytes()); err != nil { + return false + } + if err := blsSig.Deserialize(q.Signature.Bytes()); err != nil { + return false + } + blsSig.Add(&addSig) + q.Signature.SetBytes(blsSig.Serialize()) + q.ValidatorSet.SetIndex(NodeIndex, true) + return true +} + +func (q *QuorumCert) HigherSign(c *QuorumCert) bool { + if q == nil && c == nil { + return false + } + if q == nil && c != nil { + return false + } + if c == nil { + return true + } + if !q.EqualState(c) { + return false + } + return q.ValidatorSet.HasLength() >= c.ValidatorSet.HasLength() +} + +func (q *QuorumCert) EqualState(c *QuorumCert) bool { + return q.Epoch == c.Epoch && + q.ViewNumber == c.ViewNumber && + q.BlockHash == c.BlockHash && + q.BlockNumber == c.BlockNumber && + q.BlockIndex == c.BlockIndex && + q.ValidatorSet.Size() == c.ValidatorSet.Size() } // if the two quorumCert have the same blockNumber @@ -151,6 +202,10 @@ func (q ViewChangeQuorumCert) CannibalizeBytes() ([]byte, error) { } func (q ViewChangeQuorumCert) Len() int { + if q.ValidatorSet == nil { + return 0 + } + length := 0 for i := uint32(0); i < q.ValidatorSet.Size(); i++ { if q.ValidatorSet.GetIndex(i) { @@ -189,6 +244,54 @@ func (q *ViewChangeQuorumCert) Copy() *ViewChangeQuorumCert { } } +// Add a new signature to the aggregate signature +// Note: Call this method to ensure that the new signature does not exist in the aggregate signature, otherwise the entire aggregate signature will be wrong +func (q *ViewChangeQuorumCert) AddSign(sign Signature, NodeIndex uint32) bool { + if q == nil { + return false + } + var ( + addSig bls.Sign + blsSig bls.Sign + ) + if err := addSig.Deserialize(sign.Bytes()); err != nil { + return false + } + if err := blsSig.Deserialize(q.Signature.Bytes()); err != nil { + return false + } + blsSig.Add(&addSig) + q.Signature.SetBytes(blsSig.Serialize()) + q.ValidatorSet.SetIndex(NodeIndex, true) + return true +} + +func (q *ViewChangeQuorumCert) HigherSign(c *ViewChangeQuorumCert) bool { + if q == nil && c == nil { + return false + } + if q == nil && c != nil { + return false + } + if c == nil { + return true + } + if !q.EqualState(c) { + return false + } + return q.ValidatorSet.HasLength() >= c.ValidatorSet.HasLength() +} + +func (q *ViewChangeQuorumCert) EqualState(c *ViewChangeQuorumCert) bool { + return q.Epoch == c.Epoch && + q.ViewNumber == c.ViewNumber && + q.BlockHash == c.BlockHash && + q.BlockNumber == c.BlockNumber && + q.BlockEpoch == c.BlockEpoch && + q.BlockViewNumber == c.BlockViewNumber && + q.ValidatorSet.Size() == c.ValidatorSet.Size() +} + func (v ViewChangeQC) EqualAll(epoch uint64, viewNumber uint64) error { for _, v := range v.QCs { if v.ViewNumber != viewNumber || v.Epoch != epoch { @@ -224,6 +327,17 @@ func (v ViewChangeQC) Len() int { return length } +func (v ViewChangeQC) ValidatorSet() *utils.BitArray { + if len(v.QCs) > 0 { + vSet := v.QCs[0].ValidatorSet + for i := 1; i < len(v.QCs); i++ { + vSet = vSet.Or(v.QCs[i].ValidatorSet) + } + return vSet + } + return nil +} + func (v ViewChangeQC) String() string { epoch, view, blockEpoch, blockViewNumber, hash, number := v.MaxBlock() return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,BlockEpoch:%d,BlockViewNumber:%d,Hash:%s,Number:%d}", epoch, view, blockEpoch, blockViewNumber, hash.TerminalString(), number) diff --git a/consensus/cbft/types/crypto_test.go b/consensus/cbft/types/crypto_test.go index 8532273218..fbc4790bdd 100644 --- a/consensus/cbft/types/crypto_test.go +++ b/consensus/cbft/types/crypto_test.go @@ -106,3 +106,64 @@ func Test_ViewChangeQC_MaxBlock(t *testing.T) { epoch, viewNumber, blockEpoch, blockViewNumber, blockHash, blockNumber = viewChangeQC.MaxBlock() assert.Equal(t, uint64(0), epoch) } + +func TestQuorumCertAddSign(t *testing.T) { + bls.Init(int(bls.BLS12_381)) + message := "test merge sign" + var k int = 500 + msk := make([]bls.SecretKey, k) + mpk := make([]bls.PublicKey, k) + msig := make([]bls.Sign, k) + for i := 0; i < k; i++ { + msk[i].SetByCSPRNG() + mpk[i] = *msk[i].GetPublicKey() + msig[i] = *msk[i].Sign(message) + } + + verifyQuorumCert := func(qc *QuorumCert) bool { + var pub bls.PublicKey + for i := uint32(0); i < qc.ValidatorSet.Size(); i++ { + if qc.ValidatorSet.GetIndex(i) { + pub.Add(&mpk[i]) + } + } + var sig bls.Sign + if err := sig.Deserialize(qc.Signature.Bytes()); err != nil { + return false + } + + if sig.Verify(&pub, message) { + return true + } + return false + } + + var sig bls.Sign + vSet := utils.NewBitArray(uint32(k)) + for i := 0; i < len(msig)-2; i++ { + sig.Add(&msig[i]) + vSet.SetIndex(uint32(i), true) + } + + qc := &QuorumCert{ + ValidatorSet: vSet, + } + qc.Signature.SetBytes(sig.Serialize()) + //fmt.Println("qc Signature", qc.Signature.String()) + assert.Equal(t, true, verifyQuorumCert(qc)) + + // add sign and verify sign + for i := len(msig) - 2; i < len(msig); i++ { + var s Signature + s.SetBytes(msig[i].Serialize()) + qc.AddSign(s, uint32(i)) + //fmt.Println("qc Signature", qc.Signature.String()) + assert.Equal(t, true, verifyQuorumCert(qc)) + } + + // The public key does not match and cannot be verified + var s Signature + s.SetBytes(msig[0].Serialize()) + qc.AddSign(s, uint32(0)) + assert.Equal(t, false, verifyQuorumCert(qc)) +} diff --git a/consensus/cbft/types/message.go b/consensus/cbft/types/message.go index db427afaf3..09e03aab83 100644 --- a/consensus/cbft/types/message.go +++ b/consensus/cbft/types/message.go @@ -65,10 +65,13 @@ func ErrResp(code ErrCode, format string, v ...interface{}) error { // Consensus message interface, all consensus message // types must implement this interface. type ConsensusMsg interface { + Message EpochNum() uint64 ViewNum() uint64 BlockNum() uint64 NodeIndex() uint32 + BlockIndx() uint32 + CheckQC() *QuorumCert CannibalizeBytes() ([]byte, error) Sign() []byte SetSign([]byte) diff --git a/consensus/cbft/utils/bit_array.go b/consensus/cbft/utils/bit_array.go index 82dc83c706..ba3f8c6b24 100644 --- a/consensus/cbft/utils/bit_array.go +++ b/consensus/cbft/utils/bit_array.go @@ -114,6 +114,7 @@ func (bA *BitArray) copyBits(bits uint32) *BitArray { } } +/* // Or returns a bit array resulting from a bitwise OR of the two bit arrays. // If the two bit-arrys have different lengths, Or right-pads the smaller of the two bit-arrays with zeroes. // Thus the size of the return value is the maximum of the two provided bit arrays. @@ -134,6 +135,71 @@ func (bA *BitArray) Or(o *BitArray) *BitArray { } return c } +*/ + +// Or returns a bit array resulting from a bitwise OR of the two bit arrays. +// If the two bit-arrys have different lengths, Or right-pads the smaller of the two bit-arrays with zeroes. +// Thus the size of the return value is the maximum of the two provided bit arrays. +func (bA *BitArray) Or(o *BitArray) *BitArray { + if bA == nil && o == nil { + return nil + } + if bA == nil && o != nil { + return o.Copy() + } + if o == nil { + return bA.Copy() + } + + var c *BitArray + smaller := MinInt(len(bA.Elems), len(o.Elems)) + if bA.Size() > o.Size() { + c = bA.Copy() + for i := 0; i < smaller; i++ { + c.Elems[i] |= o.Elems[i] + } + } else { + c = o.copy() + for i := 0; i < smaller; i++ { + c.Elems[i] |= bA.Elems[i] + } + } + return c +} + +// HasLength returns the total number of 1 in the array +func (bA *BitArray) HasLength() int { + if bA == nil { + return 0 + } + + length := 0 + for i := uint32(0); i < bA.Size(); i++ { + if bA.GetIndex(i) { + length++ + } + } + return length +} + +// Determine whether bA contains o +func (bA *BitArray) Contains(o *BitArray) bool { + if bA == nil && o == nil { + return true + } + if bA == nil && o != nil { + return false + } + if o == nil { + return true + } + if bA.Size() < o.Size() { + return false + } + + v := bA.Or(o) + return v.HasLength() == bA.HasLength() +} // And returns a bit array resulting from a bitwise AND of the two bit arrays. // If the two bit-arrys have different lengths, this truncates the larger of the two bit-arrays from the right. diff --git a/consensus/cbft/utils/bit_array_test.go b/consensus/cbft/utils/bit_array_test.go index 06afe60173..064378e9a0 100644 --- a/consensus/cbft/utils/bit_array_test.go +++ b/consensus/cbft/utils/bit_array_test.go @@ -82,6 +82,74 @@ func TestAnd(t *testing.T) { } } +func TestAndIntuitive(t *testing.T) { + testCases := []struct { + initBA string + addBA string + expectedBA string + }{ + {`"x"`, `"x"`, `"x"`}, + {`"xxxxxx"`, `"x_x_x_"`, `"x_x_x_"`}, + {`"x_x_x_"`, `"xxxxxx"`, `"x_x_x_"`}, + {`"xxxxxx"`, `"x_x_x_xxxx"`, `"x_x_x_"`}, + {`"x_x_x_xxxx"`, `"xxxxxx"`, `"x_x_x_"`}, + {`"xxxxxxxxxx"`, `"x_x_x_"`, `"x_x_x_"`}, + {`"x_x_x_"`, `"xxxxxxxxxx"`, `"x_x_x_"`}, + {`"___x__x__x_"`, `"xxxxxxxxxx"`, `"___x__x__x"`}, + {`"___x__x__x___x_x__x__x"`, `"x_x__x_xx________x"`, `"__________________"`}, + {`"x_x__x_xx________x"`, `"___x__x__x___x_x__x__x"`, `"__________________"`}, + {`"_______"`, `"_______xxx_xxx"`, `"_______"`}, + {`"_______xxx_xxx"`, `"_______"`, `"_______"`}, + } + for _, tc := range testCases { + var bA *BitArray + err := json.Unmarshal([]byte(tc.initBA), &bA) + require.Nil(t, err) + + var o *BitArray + err = json.Unmarshal([]byte(tc.addBA), &o) + require.Nil(t, err) + + got, _ := json.Marshal(bA.And(o)) + require.Equal(t, tc.expectedBA, string(got), "%s minus %s doesn't equal %s", tc.initBA, tc.addBA, tc.expectedBA) + } +} + +func TestAndScene(t *testing.T) { + b1 := NewBitArray(1000) + b1.setIndex(uint32(100), true) + b1.setIndex(uint32(666), true) + b1.setIndex(uint32(888), true) + b1.setIndex(uint32(999), true) + + b2 := NewBitArray(500) + b2.setIndex(uint32(0), true) + b2.setIndex(uint32(100), true) + b2.setIndex(uint32(222), true) + b2.setIndex(uint32(333), true) + + got, _ := json.Marshal(b2.And(b1)) + expected := `"____________________________________________________________________________________________________x_______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________"` + assert.Equal(t, expected, string(got)) + + b1 = NewBitArray(25) + b1.setIndex(5, true) + b1.setIndex(15, true) + b1.setIndex(20, true) + + b2 = NewBitArray(200) + b2.setIndex(5, true) + b2.setIndex(15, true) + b2.setIndex(88, true) + b2.setIndex(188, true) + + result := b2.And(b1) + assert.Equal(t, uint32(25), result.Size()) + assert.Equal(t, true, result.getIndex(5)) + assert.Equal(t, true, result.getIndex(15)) + assert.Equal(t, false, result.getIndex(20)) +} + func TestOr(t *testing.T) { bA1, _ := randBitArray(51) @@ -107,6 +175,197 @@ func TestOr(t *testing.T) { } } +func TestOrIntuitive(t *testing.T) { + testCases := []struct { + initBA string + orBA string + expectedBA string + }{ + {"null", `null`, `null`}, + {`"x"`, `null`, `"x"`}, + {`null`, `"x"`, `"x"`}, + {`"x"`, `"x"`, `"x"`}, + {`"xxxxxx"`, `"x_x_x_"`, `"xxxxxx"`}, + {`"x_x_x_"`, `"xxxxxx"`, `"xxxxxx"`}, + {`"xxxxxx"`, `"x_x_x_xxxx"`, `"xxxxxxxxxx"`}, + {`"x_x_x_xxxx"`, `"xxxxxx"`, `"xxxxxxxxxx"`}, + {`"xxxxxxxxxx"`, `"x_x_x_"`, `"xxxxxxxxxx"`}, + {`"x_x_x_"`, `"xxxxxxxxxx"`, `"xxxxxxxxxx"`}, + {`"___x__x__x_"`, `"xxxxxxxxxx"`, `"xxxxxxxxxx_"`}, + {`"___x__x__x___x_x__x__x"`, `"x_x__x_xx________x"`, `"x_xx_xxxxx___x_x_xx__x"`}, + {`"x_x__x_xx________x"`, `"___x__x__x___x_x__x__x"`, `"x_xx_xxxxx___x_x_xx__x"`}, + {`"_______"`, `"_______xxx_xxx"`, `"_______xxx_xxx"`}, + {`"_______xxx_xxx"`, `"_______"`, `"_______xxx_xxx"`}, + {`"_______xxx_xxx"`, `"_______x_______x__"`, `"_______xxx_xxx_x__"`}, + } + for _, tc := range testCases { + var bA *BitArray + err := json.Unmarshal([]byte(tc.initBA), &bA) + require.Nil(t, err) + + var o *BitArray + err = json.Unmarshal([]byte(tc.orBA), &o) + require.Nil(t, err) + + got, _ := json.Marshal(bA.Or(o)) + require.Equal(t, tc.expectedBA, string(got), "%s minus %s doesn't equal %s", tc.initBA, tc.orBA, tc.expectedBA) + } +} + +func TestOrScene(t *testing.T) { + b1 := NewBitArray(1000) + b1.setIndex(uint32(100), true) + b1.setIndex(uint32(666), true) + b1.setIndex(uint32(888), true) + b1.setIndex(uint32(999), true) + + b2 := NewBitArray(500) + b2.setIndex(uint32(0), true) + b2.setIndex(uint32(100), true) + b2.setIndex(uint32(222), true) + b2.setIndex(uint32(333), true) + + got, _ := json.Marshal(b2.Or(b1)) + expected := `"x___________________________________________________________________________________________________x_________________________________________________________________________________________________________________________x______________________________________________________________________________________________________________x____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________x_____________________________________________________________________________________________________________________________________________________________________________________________________________________________x______________________________________________________________________________________________________________x"` + assert.Equal(t, expected, string(got)) + + b1 = NewBitArray(25) + b1.setIndex(5, true) + b1.setIndex(15, true) + + b2 = NewBitArray(200) + b2.setIndex(5, true) + b2.setIndex(15, true) + b2.setIndex(88, true) + b2.setIndex(188, true) + + result := b2.Or(b1) + assert.Equal(t, true, result.getIndex(188)) +} + +func TestContainsIntuitive(t *testing.T) { + testCases := []struct { + initBA string + orBA string + expectedBA bool + }{ + {"null", `null`, true}, + {`"x"`, `null`, true}, + {`null`, `"x"`, false}, + {`"x"`, `"x"`, true}, + {`"xxxxxx"`, `"x_x_x_"`, true}, + {`"x_x_x_"`, `"xxxxxx"`, false}, + {`"xxxxxx"`, `"x_x_x_xxxx"`, false}, + {`"x_x_x_xxxx"`, `"xxxxxx"`, false}, + {`"xxxxxxxxxx"`, `"x_x_x_"`, true}, + {`"x_x_x_"`, `"xxxxxxxxxx"`, false}, + {`"___x__x__x_"`, `"xxxxxxxxxx"`, false}, + {`"___x__x__x___x_x__x__x"`, `"x_x__x_xx________x"`, false}, + {`"_______"`, `"_______xxx_xxx"`, false}, + {`"_______xxx_xxx"`, `"_______"`, true}, + {`"_______xxx_xxx"`, `"_______x_______x__"`, false}, + {`"x_x__x_xx___x_x__x"`, `"x_x__x_xx___x_x__x"`, true}, + {`"x_x__x_xx___x_x__x"`, `"__x__x__x___x_x__x"`, true}, + {`"x_x__x_xx___x_x__x_"`, `"x_x__x_xx___x_x__x"`, true}, + {`"x_x__x_xx___x_x__x"`, `"x_x__x_xx___x_x__x_"`, false}, + {`"x_x__x_xx___x_x__x_x"`, `"x_x__x_xx___x_x__x"`, true}, + {`"x_x__x_xx___x_x__x_x"`, `"__x__x__x___x_x__x"`, true}, + {`"x_x__x_xx___x_x__x_x"`, `"__xx_x__x___x_x__x"`, false}, + {`"x_x__x_xx___x_x__x"`, `"__xx_x__x___x_x__x"`, false}, + } + for _, tc := range testCases { + var bA *BitArray + err := json.Unmarshal([]byte(tc.initBA), &bA) + require.Nil(t, err) + + var o *BitArray + err = json.Unmarshal([]byte(tc.orBA), &o) + require.Nil(t, err) + + b := bA.Contains(o) + assert.Equal(t, tc.expectedBA, b) + } +} + +func TestContainsScene(t *testing.T) { + b1 := NewBitArray(1000) + b1.setIndex(uint32(100), true) + b1.setIndex(uint32(666), true) + b1.setIndex(uint32(888), true) + b1.setIndex(uint32(999), true) + + b2 := NewBitArray(1000) + b2.setIndex(uint32(0), true) + b2.setIndex(uint32(100), true) + b2.setIndex(uint32(222), true) + b2.setIndex(uint32(333), true) + assert.Equal(t, false, b1.Contains(b2)) + + b2.setIndex(uint32(0), false) + b2.setIndex(uint32(222), false) + b2.setIndex(uint32(333), false) + assert.Equal(t, true, b1.Contains(b2)) + + b2.setIndex(uint32(666), true) + b2.setIndex(uint32(888), true) + b2.setIndex(uint32(999), true) + assert.Equal(t, true, b1.Contains(b2)) + + b2.setIndex(uint32(668), true) + assert.Equal(t, false, b1.Contains(b2)) +} + +func TestHasLengthIntuitive(t *testing.T) { + testCases := []struct { + initBA string + expectedBA int + }{ + {"null", 0}, + {`"x"`, 1}, + {`"xxxxxx"`, 6}, + {`"x_x_x_"`, 3}, + {`"x_x_x_xxxx"`, 7}, + {`"xxxxxxxxxx"`, 10}, + {`"___x__x__x_"`, 3}, + {`"___x__x__x___x_x__x__x"`, 7}, + {`"_______"`, 0}, + {`"_______xxx_xxx"`, 6}, + {`"_______x_______x__"`, 2}, + {`"x_x__x_xx___x_x__x"`, 8}, + {`"__x__x__x___x_x__x"`, 6}, + {`"x_x__x_xx___x_x__x_x"`, 9}, + } + for _, tc := range testCases { + var bA *BitArray + err := json.Unmarshal([]byte(tc.initBA), &bA) + require.Nil(t, err) + assert.Equal(t, tc.expectedBA, bA.HasLength()) + } +} + +func TestHasLengthScene(t *testing.T) { + b1 := NewBitArray(1000) + b1.setIndex(uint32(0), true) + b1.setIndex(uint32(100), true) + b1.setIndex(uint32(666), true) + b1.setIndex(uint32(888), true) + b1.setIndex(uint32(999), true) + assert.Equal(t, 5, b1.HasLength()) + + b1.setIndex(uint32(666), true) + b1.setIndex(uint32(888), true) + assert.Equal(t, 5, b1.HasLength()) + + b1.setIndex(uint32(555), false) + assert.Equal(t, 5, b1.HasLength()) + + b1.setIndex(uint32(666), false) + assert.Equal(t, 4, b1.HasLength()) + + var b2 *BitArray + assert.Equal(t, 0, b2.HasLength()) +} + func TestSub(t *testing.T) { testCases := []struct { initBA string diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 531514f9b0..cc0949b0ae 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -714,7 +714,7 @@ func (vp *ValidatorPool) GetGroupID(epoch uint64, nodeID enode.ID) (uint32, erro // GroupID return current node's GroupID according epoch func (vp *ValidatorPool) GroupID(epoch uint64) (uint32, error) { - return vp.GetGroupID(epoch,vp.nodeID) + return vp.GetGroupID(epoch, vp.nodeID) } // GetUnitID return index according epoch & NodeID @@ -730,9 +730,41 @@ func (vp *ValidatorPool) GetUnitID(epoch uint64, nodeID enode.ID) (uint32, error // UnitID return current node's index according epoch func (vp *ValidatorPool) UnitID(epoch uint64) (uint32, error) { - return vp.GetUnitID(epoch,vp.nodeID) + return vp.GetUnitID(epoch, vp.nodeID) } func NextRound(blockNumber uint64) uint64 { return blockNumber + 1 } + +// Len return number of validators by groupID. +// 返回指定epoch和分组下的节点总数 +func (vp *ValidatorPool) LenByGroupID(epoch uint64, groupID uint32) int { + // TODO + return 0 +} + +// 查询指定epoch和分组下对应nodeIndex的节点信息,没有对应信息返回nil +func (vp *ValidatorPool) GetValidatorByGroupIdAndIndex(epoch uint64, groupID, nodeIndex uint32) (*cbfttypes.ValidateNode, error) { + // TODO + return nil, nil +} + +// 返回指定epoch和分组下所有共识节点的index集合,e.g. [25,26,27,28,29,30...49] +func (vp *ValidatorPool) GetValidatorIndexesByGroupID(epoch uint64, groupID uint32) ([]uint32, error) { + // TODO + return nil, nil +} + +// 返回指定epoch和分组下所有共识节点的编组信息,e.g. [[25,26],[27,28],[29,30]...[49]] +// 严格按编组顺序返回 +func (vp *ValidatorPool) GetCoordinatorIndexesByGroupID(epoch uint64, groupID uint32) ([][]uint32, error) { + // TODO + return nil, nil +} + +// 返回指定epoch下,nodeID所在的groupID和unitID(两者都是0开始计数),没有对应信息需返回error +func (vp *ValidatorPool) GetGroupByValidatorID(epoch uint64, nodeID enode.ID) (uint32, uint32, error) { + // TODO + return 0, 0, nil +} From 4240c122e2827dcd7d8307caee6d31fc9af22888 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Fri, 12 Nov 2021 09:54:15 +0800 Subject: [PATCH 042/299] Add a process for sending and processing grouped aggregate messages --- consensus/cbft/rules/safety_rules.go | 1 + consensus/cbft/types/crypto.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/consensus/cbft/rules/safety_rules.go b/consensus/cbft/rules/safety_rules.go index e1c4795655..d0cabed38d 100644 --- a/consensus/cbft/rules/safety_rules.go +++ b/consensus/cbft/rules/safety_rules.go @@ -22,6 +22,7 @@ import ( "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/validator" + "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/state" diff --git a/consensus/cbft/types/crypto.go b/consensus/cbft/types/crypto.go index dab414803c..183fbde814 100644 --- a/consensus/cbft/types/crypto.go +++ b/consensus/cbft/types/crypto.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package types import ( @@ -25,6 +24,7 @@ import ( "github.com/AlayaNetwork/Alaya-Go/common/hexutil" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/crypto/bls" "github.com/AlayaNetwork/Alaya-Go/rlp" ) From abbff5f2f499d9574892a0f9f261c5fdc68b66e6 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Fri, 12 Nov 2021 10:04:10 +0800 Subject: [PATCH 043/299] Add a process for sending and processing grouped aggregate messages --- consensus/cbft/state/state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/cbft/state/state.go b/consensus/cbft/state/state.go index c10aefaa1e..1f40e3de23 100644 --- a/consensus/cbft/state/state.go +++ b/consensus/cbft/state/state.go @@ -14,12 +14,12 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package state import ( "encoding/json" "fmt" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" "sync/atomic" "time" From 5ba36c3898bf918486c0d404e96512cda088c114 Mon Sep 17 00:00:00 2001 From: benbaley Date: Fri, 12 Nov 2021 10:36:20 +0800 Subject: [PATCH 044/299] add gov.Gte0140VersionState --- consensus/cbft/validator/validator.go | 11 +++++++---- params/version_history.go | 1 + x/gov/gov.go | 7 +++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 531514f9b0..f3b9a8b304 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -20,6 +20,7 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/AlayaNetwork/Alaya-Go/x/gov" "sync" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" @@ -312,6 +313,7 @@ type ValidatorPool struct { prevValidators *cbfttypes.Validators // Previous validators currentValidators *cbfttypes.Validators // Current validators + nextValidators *cbfttypes.Validators // Next validators } // NewValidatorPool new a validator pool. @@ -341,9 +343,10 @@ func NewValidatorPool(agency consensus.Agency, blockNumber uint64, epoch uint64, if pool.currentValidators.ValidBlockNumber > 0 { pool.switchPoint = pool.currentValidators.ValidBlockNumber - 1 } - - pool.groupID = pool.currentValidators.GroupID(nodeID) - pool.unitID = pool.currentValidators.UnitID(nodeID) + if gov.Gte0140VersionState(state) { + pool.groupID = pool.currentValidators.GroupID(nodeID) + pool.unitID = pool.currentValidators.UnitID(nodeID) + } log.Debug("Update validator", "validators", pool.currentValidators.String(), "switchpoint", pool.switchPoint, "epoch", pool.epoch, "lastNumber", pool.lastNumber) return pool } @@ -714,7 +717,7 @@ func (vp *ValidatorPool) GetGroupID(epoch uint64, nodeID enode.ID) (uint32, erro // GroupID return current node's GroupID according epoch func (vp *ValidatorPool) GroupID(epoch uint64) (uint32, error) { - return vp.GetGroupID(epoch,vp.nodeID) + return vp.GetGroupID(epoch, vp.nodeID) } // GetUnitID return index according epoch & NodeID diff --git a/params/version_history.go b/params/version_history.go index 66c39ab024..51b7e4e3ac 100644 --- a/params/version_history.go +++ b/params/version_history.go @@ -5,4 +5,5 @@ const ( FORKVERSION_0_14_0 = uint32(0<<16 | 14<<8 | 0) FORKVERSION_0_15_0 = uint32(0<<16 | 15<<8 | 0) FORKVERSION_0_16_0 = uint32(0<<16 | 16<<8 | 0) + FORKVERSION_0_17_0 = uint32(0<<16 | 17<<8 | 0) ) diff --git a/x/gov/gov.go b/x/gov/gov.go index bb3287a331..a559793295 100644 --- a/x/gov/gov.go +++ b/x/gov/gov.go @@ -99,6 +99,13 @@ func Gte0160Version(version uint32) bool { return version >= params.FORKVERSION_0_16_0 } +func Gte0170VersionState(state xcom.StateDB) bool { + return Gte0170Version(GetCurrentActiveVersion(state)) +} + +func Gte0170Version(version uint32) bool { + return version >= params.FORKVERSION_0_17_0 +} func WriteEcHash0140(state xcom.StateDB) error { if data, err := xcom.EcParams0140(); nil != err { return err From 63fbb0efd82b87f83fb4c7d7be6cb4600a173f86 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Fri, 12 Nov 2021 10:43:56 +0800 Subject: [PATCH 045/299] Improve pubsub's p2p.Protocol --- consensus/cbft/cbft.go | 3 +++ consensus/cbft/network/handler.go | 4 ++-- consensus/cbft/network/handler_test.go | 4 ++-- consensus/cbft/network/pubsub.go | 23 +++++++++++++++++++++-- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 15179620fd..5f0e937d05 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -264,6 +264,9 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCacheWriter consensus. // Start the handler to process the message. go cbft.network.Start() + // Data required to initialize pubsub + cbft.pubSub.Init(cbft.config, cbft.network.GetPeer) + if cbft.config.Option.Node == nil { cbft.config.Option.Node = enode.NewV4(&cbft.nodeServiceContext.NodePriKey().PublicKey, nil, 0, 0) cbft.config.Option.NodeID = cbft.config.Option.Node.IDv0() diff --git a/consensus/cbft/network/handler.go b/consensus/cbft/network/handler.go index b620957151..6b18186ecd 100644 --- a/consensus/cbft/network/handler.go +++ b/consensus/cbft/network/handler.go @@ -110,7 +110,7 @@ func NewEngineManger(engine Cbft) *EngineManager { } handler.blacklist, _ = lru.New(maxBlacklist) // init router - handler.router = newRouter(handler.Unregister, handler.getPeer, handler.ConsensusNodes, handler.peerList) + handler.router = newRouter(handler.Unregister, handler.GetPeer, handler.ConsensusNodes, handler.peerList) return handler } @@ -178,7 +178,7 @@ func (h *EngineManager) PeerSetting(peerID string, bType uint64, blockNumber uin } // GetPeer returns the peer with the specified peerID. -func (h *EngineManager) getPeer(peerID string) (*peer, error) { +func (h *EngineManager) GetPeer(peerID string) (*peer, error) { if peerID == "" { return nil, fmt.Errorf("invalid peerID parameter - %v", peerID) } diff --git a/consensus/cbft/network/handler_test.go b/consensus/cbft/network/handler_test.go index 1e88796c26..5908927262 100644 --- a/consensus/cbft/network/handler_test.go +++ b/consensus/cbft/network/handler_test.go @@ -290,14 +290,14 @@ func Test_EngineManager_Synchronize(t *testing.T) { // Verify that registration is successful. checkedPeer := peers[1] - p, err := handle.getPeer(checkedPeer.id) + p, err := handle.GetPeer(checkedPeer.id) if err != nil { t.Error("register peer failed", err) } assert.Equal(t, checkedPeer.id, p.id) // Should return an error if an empty string is passed in. - _, err = handle.getPeer("") + _, err = handle.GetPeer("") assert.NotNil(t, err) // blacklist diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index a1e763dce4..101fed7bb1 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -19,6 +19,8 @@ package network import ( "context" "errors" + "fmt" + ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" @@ -51,7 +53,9 @@ type RGMsg struct { } type PubSub struct { - pss *p2p.PubSubServer + pss *p2p.PubSubServer + config ctypes.Config + getPeerById getByIDFunc // Used to get peer by ID. // Messages of all topics subscribed are sent out from this channel uniformly msgCh chan *RGMsg @@ -72,8 +76,15 @@ func (ps *PubSub) handler(peer *p2p.Peer, rw p2p.MsgReadWriter) error { return handlerErr } +func (ps *PubSub) Config() *ctypes.Config { + return &ps.config +} + func (ps *PubSub) NodeInfo() interface{} { - return nil + cfg := ps.Config() + return &NodeInfo{ + Config: *cfg, + } } //Protocols implemented the Protocols method and returned basic information about the CBFT.pubsub protocol. @@ -90,6 +101,9 @@ func (ps *PubSub) Protocols() []p2p.Protocol { return ps.NodeInfo() }, PeerInfo: func(id enode.ID) interface{} { + if p, err := ps.getPeerById(fmt.Sprintf("%x", id[:8])); err == nil { + return p.Info() + } return nil }, }, @@ -105,6 +119,11 @@ func NewPubSub(server *p2p.PubSubServer) *PubSub { } } +func (ps *PubSub) Init(config ctypes.Config, get getByIDFunc) { + ps.config = config + ps.getPeerById = get +} + //Subscribe subscribe a topic func (ps *PubSub) Subscribe(topic string) error { ps.Lock() From 64a6927d29c2635b0a8470402fa203a57e7ba042 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Fri, 12 Nov 2021 18:25:08 +0800 Subject: [PATCH 046/299] Update message sending and forwarding strategy and message meter --- common/flow_metrics.go | 26 ++++++++-------- consensus/bft_mock.go | 6 ---- consensus/cbft/cbft.go | 2 +- consensus/cbft/consensus_process.go | 34 +++++++++++++++++--- consensus/cbft/network/handler.go | 19 +++++++++++- consensus/cbft/network/handler_test.go | 2 ++ consensus/cbft/network/helper_test.go | 34 ++++++++++++++++++++ consensus/cbft/network/metrics.go | 36 +++++++++++++++++++++- consensus/cbft/network/metrics_test.go | 21 ++++++++++++- consensus/cbft/network/pubsub.go | 21 ++++++------- consensus/cbft/network/router.go | 4 +-- consensus/cbft/network/router_test.go | 7 +++-- consensus/cbft/validator/validator_test.go | 9 +++--- core/cbfttypes/type.go | 30 ++++++++---------- core/events.go | 10 ------ eth/peer.go | 21 ------------- 16 files changed, 185 insertions(+), 97 deletions(-) diff --git a/common/flow_metrics.go b/common/flow_metrics.go index 6f8137b776..99eaf4d814 100644 --- a/common/flow_metrics.go +++ b/common/flow_metrics.go @@ -23,16 +23,18 @@ var ( ReceiptsTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/eth/receipts/OutboundTraffic", nil) // the meter for the protocol of cbft - PrepareBlockCBFTEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/PrepareBlock/OutboundTraffic", nil) - PrepareVoteEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/PrepareVote/OutboundTraffic", nil) - ViewChangeEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/ViewChange/OutboundTraffic", nil) - GetPrepareBlockEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/GetPrepareBlock/OutboundTraffic", nil) - PrepareBlockHashEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/PrepareBlockHash/OutboundTraffic", nil) - GetPrepareVoteEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/GetPrepareVote/OutboundTraffic", nil) - PrepareVotesEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/PrepareVotes/OutboundTraffic", nil) - GetBlockQuorumCertEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/GetBlockQuorumCert/OutboundTraffic", nil) - BlockQuorumCertEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/BlockQuorumCert/OutboundTraffic", nil) - CBFTStatusEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/CBFTStatus/OutboundTraffic", nil) - GetQCBlockListEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/GetQCBlockList/OutboundTraffic", nil) - QCBlockListEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/QCBlockList/OutboundTraffic", nil) + PrepareBlockCBFTEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/PrepareBlock/OutboundTraffic", nil) + PrepareVoteEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/PrepareVote/OutboundTraffic", nil) + ViewChangeEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/ViewChange/OutboundTraffic", nil) + RGBlockQuorumCertEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/RGBlockQuorumCert/OutboundTraffic", nil) + RGViewChangeQuorumCertEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/RGViewChangeQuorumCert/OutboundTraffic", nil) + GetPrepareBlockEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/GetPrepareBlock/OutboundTraffic", nil) + PrepareBlockHashEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/PrepareBlockHash/OutboundTraffic", nil) + GetPrepareVoteEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/GetPrepareVote/OutboundTraffic", nil) + PrepareVotesEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/PrepareVotes/OutboundTraffic", nil) + GetBlockQuorumCertEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/GetBlockQuorumCert/OutboundTraffic", nil) + BlockQuorumCertEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/BlockQuorumCert/OutboundTraffic", nil) + CBFTStatusEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/CBFTStatus/OutboundTraffic", nil) + GetQCBlockListEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/GetQCBlockList/OutboundTraffic", nil) + QCBlockListEgressTrafficMeter = metrics.NewRegisteredMeter("p2p/flow/cbft/QCBlockList/OutboundTraffic", nil) ) diff --git a/consensus/bft_mock.go b/consensus/bft_mock.go index 9b04f9f971..1b9e237e4d 100644 --- a/consensus/bft_mock.go +++ b/consensus/bft_mock.go @@ -288,12 +288,6 @@ func (bm *BftMock) ShouldSeal(curTime time.Time) (bool, error) { return true, nil } -// OnBlockSignature received a new block signature -// Need to verify if the signature is signed by nodeID -func (bm *BftMock) OnBlockSignature(chain ChainReader, nodeID enode.IDv0, sig *cbfttypes.BlockSignature) error { - return nil -} - // OnNewBlock processes the BFT signatures func (bm *BftMock) OnNewBlock(chain ChainReader, block *types.Block) error { return nil diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 81c137359c..9eb83076d7 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -213,7 +213,7 @@ func New(sysConfig *params.CbftConfig, optConfig *ctypes.OptionsConfig, eventMux statQueues: make(map[common.Hash]map[string]int), messageHashCache: mapset.NewSet(), netLatencyMap: make(map[string]*list.List), - pubSub: network.NewPubSub(ctx.PubSubServer()), + //pubSub: network.NewPubSub(ctx.PubSubServer()), } if evPool, err := evidence.NewEvidencePool(ctx, optConfig.EvidenceDir); err == nil { diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index a55af9baae..55ced6e101 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -18,6 +18,8 @@ package cbft import ( "fmt" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/network" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "time" "github.com/pkg/errors" @@ -379,13 +381,19 @@ func (cbft *Cbft) OnViewTimeout() { return } + // send viewChange use pubsub + if err := cbft.publishTopicMsg(viewChange); err != nil { + cbft.log.Error("Publish viewChange failed", "err", err.Error(), "view", cbft.state.ViewString(), "viewChange", viewChange.String()) + return + } + // write sendViewChange info to wal if !cbft.isLoading() { cbft.bridge.SendViewChange(viewChange) } cbft.state.AddViewChange(uint32(node.Index), viewChange) - cbft.network.Broadcast(viewChange) + //cbft.network.Broadcast(viewChange) cbft.log.Info("Local add viewChange", "index", node.Index, "viewChange", viewChange.String(), "total", cbft.state.ViewChangeLen()) cbft.tryChangeView() @@ -590,10 +598,15 @@ func (cbft *Cbft) trySendPrepareVote() { // Only when the view is switched, the block is cleared but the vote is also cleared. // If there is no block, the consensus process is abnormal and should not run. if block == nil { - cbft.log.Crit("Try send PrepareVote failed", "err", "vote corresponding block not found", "view", cbft.state.ViewString(), p.String()) + cbft.log.Crit("Try send PrepareVote failed", "err", "vote corresponding block not found", "view", cbft.state.ViewString(), "vote", p.String()) } if b, qc := cbft.blockTree.FindBlockAndQC(block.ParentHash(), block.NumberU64()-1); b != nil || block.NumberU64() == 0 { p.ParentQC = qc + // send prepareVote use pubsub + if err := cbft.publishTopicMsg(p); err != nil { + cbft.log.Error("Publish PrepareVote failed", "err", err.Error(), "view", cbft.state.ViewString(), "vote", p.String()) + break + } hadSend.Push(p) //Determine if the current consensus node is node, _ := cbft.isCurrentValidator() @@ -601,18 +614,31 @@ func (cbft *Cbft) trySendPrepareVote() { cbft.state.AddPrepareVote(uint32(node.Index), p) pending.Pop() + //cbft.network.Broadcast(p) + // write sendPrepareVote info to wal if !cbft.isLoading() { cbft.bridge.SendPrepareVote(block, p) } - - cbft.network.Broadcast(p) } else { break } } } +func (cbft *Cbft) publishTopicMsg(msg ctypes.ConsensusMsg) error { + groupID, _, err := cbft.getGroupByValidatorID(cbft.state.Epoch(), cbft.Node().ID()) + if err != nil { + return fmt.Errorf("the group info of the current node is not queried, cannot publish the topic message") + } + topic := cbfttypes.ConsensusGroupTopicName(cbft.state.Epoch(), groupID) + return cbft.pubSub.Publish(topic, &network.RGMsg{Code: protocols.MessageType(msg), Data: msg}) +} + +func (cbft *Cbft) OnTopicMsg(peerID enode.ID, msg *network.RGMsg) { + // TODO +} + //func (cbft *Cbft) trySendRGBlockQuorumCert() { // // Check timeout // if cbft.state.IsDeadline() { diff --git a/consensus/cbft/network/handler.go b/consensus/cbft/network/handler.go index b620957151..754fd9c4ab 100644 --- a/consensus/cbft/network/handler.go +++ b/consensus/cbft/network/handler.go @@ -34,6 +34,7 @@ import ( "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p" + //"github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" ) const ( @@ -277,7 +278,7 @@ func (h *EngineManager) Forwarding(nodeID string, msg types.Message) error { } // PrepareBlockMsg does not forward, the message will be forwarded using PrepareBlockHash. switch msgType { - case protocols.PrepareBlockMsg, protocols.PrepareVoteMsg, protocols.ViewChangeMsg: + case protocols.PrepareBlockMsg, protocols.RGBlockQuorumCertMsg, protocols.RGViewChangeQuorumCertMsg: err := forward() if err != nil { messageGossipMeter.Mark(1) @@ -488,6 +489,22 @@ func (h *EngineManager) handleMsg(p *peer) error { p.MarkMessageHash((&request).MsgHash()) return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) + case msg.Code == protocols.RGBlockQuorumCertMsg: + var request protocols.RGBlockQuorumCert + if err := msg.Decode(&request); err != nil { + return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) + } + p.MarkMessageHash((&request).MsgHash()) + return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) + + case msg.Code == protocols.RGViewChangeQuorumCertMsg: + var request protocols.RGViewChangeQuorumCert + if err := msg.Decode(&request); err != nil { + return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) + } + p.MarkMessageHash((&request).MsgHash()) + return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) + case msg.Code == protocols.GetPrepareBlockMsg: var request protocols.GetPrepareBlock if err := msg.Decode(&request); err != nil { diff --git a/consensus/cbft/network/handler_test.go b/consensus/cbft/network/handler_test.go index 1e88796c26..caefce3d5f 100644 --- a/consensus/cbft/network/handler_test.go +++ b/consensus/cbft/network/handler_test.go @@ -194,6 +194,8 @@ func Test_EngineManager_Handle(t *testing.T) { {newFakePrepareBlock(), protocols.PrepareBlockMsg}, {newFakePrepareVote(), protocols.PrepareVoteMsg}, {newFakeViewChange(), protocols.ViewChangeMsg}, + {newFakeRGBlockQuorumCert(), protocols.RGBlockQuorumCertMsg}, + {newFakeRGViewChangeQuorumCert(), protocols.RGViewChangeQuorumCertMsg}, {newFakeGetPrepareBlock(), protocols.GetPrepareBlockMsg}, {newFakeGetBlockQuorumCert(), protocols.GetBlockQuorumCertMsg}, {newFakeBlockQuorumCert(), protocols.BlockQuorumCertMsg}, diff --git a/consensus/cbft/network/helper_test.go b/consensus/cbft/network/helper_test.go index 08f74d8432..fa2f15879e 100644 --- a/consensus/cbft/network/helper_test.go +++ b/consensus/cbft/network/helper_test.go @@ -34,6 +34,7 @@ import ( "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" "github.com/AlayaNetwork/Alaya-Go/core/types" + ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/p2p" ) @@ -74,6 +75,39 @@ func newFakePrepareVote() *protocols.PrepareVote { } } +func newFakeRGBlockQuorumCert() *protocols.RGBlockQuorumCert { + return &protocols.RGBlockQuorumCert{ + GroupID: 1, + BlockQC: newQuorumCert(), + ValidatorIndex: 1, + ParentQC: nil, + Signature: newSignature(), + } +} + +func newFakeRGViewChangeQuorumCert() *protocols.RGViewChangeQuorumCert { + return &protocols.RGViewChangeQuorumCert{ + GroupID: 1, + ViewChangeQC: &ctypes.ViewChangeQC{ + QCs: []*ctypes.ViewChangeQuorumCert{ + { + Epoch: 1, + ViewNumber: 1, + BlockHash: common.BytesToHash([]byte("I'm hash")), + BlockNumber: 1, + BlockEpoch: 1, + BlockViewNumber: 1, + Signature: newSignature(), + ValidatorSet: utils.NewBitArray(25), + }, + }, + }, + ValidatorIndex: 1, + PrepareQCs: nil, + Signature: newSignature(), + } +} + func newQuorumCert() *types2.QuorumCert { return &types2.QuorumCert{ Epoch: 1, diff --git a/consensus/cbft/network/metrics.go b/consensus/cbft/network/metrics.go index 6950f34ebd..0884650b1b 100644 --- a/consensus/cbft/network/metrics.go +++ b/consensus/cbft/network/metrics.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package network import ( @@ -47,6 +46,18 @@ var ( propViewChangeOutPacketsMeter = metrics.NewRegisteredMeter("cbft/prop/view_change/out/packets", nil) propViewChangeOutTrafficMeter = metrics.NewRegisteredMeter("cbft/prop/view_change/out/traffic", nil) + // RGBlockQuorumCertMsg + propRGBlockQuorumCertInPacketsMeter = metrics.NewRegisteredMeter("cbft/prop/rg_block_quorumcert/in/packets", nil) + propRGBlockQuorumCertInTrafficMeter = metrics.NewRegisteredMeter("cbft/prop/rg_block_quorumcert/in/traffic", nil) + propRGBlockQuorumCertOutPacketsMeter = metrics.NewRegisteredMeter("cbft/prop/rg_block_quorumcert/out/packets", nil) + propRGBlockQuorumCertOutTrafficMeter = metrics.NewRegisteredMeter("cbft/prop/rg_block_quorumcert/out/traffic", nil) + + // RGViewChangeQuorumCertMsg + propRGViewChangeQuorumCertInPacketsMeter = metrics.NewRegisteredMeter("cbft/prop/rg_viewchange_quorumcert/in/packets", nil) + propRGViewChangeQuorumCertInTrafficMeter = metrics.NewRegisteredMeter("cbft/prop/rg_viewchange_quorumcert/in/traffic", nil) + propRGViewChangeQuorumCertOutPacketsMeter = metrics.NewRegisteredMeter("cbft/prop/rg_viewchange_quorumcert/out/packets", nil) + propRGViewChangeQuorumCertOutTrafficMeter = metrics.NewRegisteredMeter("cbft/prop/rg_viewchange_quorumcert/out/traffic", nil) + // PrepareBlockHashMsg propPrepareBlockHashInPacketsMeter = metrics.NewRegisteredMeter("cbft/prop/prepare_block_hash/in/packets", nil) propPrepareBlockHashInTrafficMeter = metrics.NewRegisteredMeter("cbft/prop/prepare_block_hash/in/traffic", nil) @@ -139,22 +150,37 @@ func (rw *meteredMsgReadWriter) ReadMsg() (p2p.Msg, error) { switch { case msg.Code == protocols.PrepareBlockMsg: packets, traffic = propPrepareBlockInPacketsMeter, propPrepareBlockInTrafficMeter + case msg.Code == protocols.PrepareVoteMsg: packets, traffic = propPrepareVoteInPacketsMeter, propPrepareVoteInTrafficMeter + case msg.Code == protocols.ViewChangeMsg: packets, traffic = propViewChangeInPacketsMeter, propViewChangeInTrafficMeter + + case msg.Code == protocols.RGBlockQuorumCertMsg: + packets, traffic = propRGBlockQuorumCertInPacketsMeter, propRGBlockQuorumCertInTrafficMeter + + case msg.Code == protocols.RGViewChangeQuorumCertMsg: + packets, traffic = propRGViewChangeQuorumCertInPacketsMeter, propRGViewChangeQuorumCertInTrafficMeter + case msg.Code == protocols.GetPrepareBlockMsg: packets, traffic = reqGetPrepareBlockInPacketsMeter, reqGetPrepareBlockInTrafficMeter + case msg.Code == protocols.GetBlockQuorumCertMsg: packets, traffic = reqGetQuorumCertInPacketsMeter, reqGetQuorumCertInTrafficMeter + case msg.Code == protocols.BlockQuorumCertMsg: packets, traffic = reqBlockQuorumCertInPacketsMeter, reqBlockQuorumCertInTrafficMeter + case msg.Code == protocols.GetPrepareVoteMsg: packets, traffic = reqGetPrepareVoteInPacketsMeter, reqGetPrepareVoteInTrafficMeter + case msg.Code == protocols.PrepareVotesMsg: packets, traffic = reqPrepareVotesInPacketsMeter, reqPrepareVotesInTrafficMeter + case msg.Code == protocols.GetQCBlockListMsg: packets, traffic = reqGetQCBlockListInPacketsMeter, reqGetQCBlockListInTrafficMeter + case msg.Code == protocols.QCBlockListMsg: packets, traffic = reqQCBlockListInPacketsMeter, reqQCBlockListInTrafficMeter } @@ -180,6 +206,14 @@ func (rw *meteredMsgReadWriter) WriteMsg(msg p2p.Msg) error { packets, traffic = propViewChangeOutPacketsMeter, propViewChangeOutTrafficMeter common.ViewChangeEgressTrafficMeter.Mark(int64(msg.Size)) + case msg.Code == protocols.RGBlockQuorumCertMsg: + packets, traffic = propRGBlockQuorumCertOutPacketsMeter, propRGBlockQuorumCertOutTrafficMeter + common.RGBlockQuorumCertEgressTrafficMeter.Mark(int64(msg.Size)) + + case msg.Code == protocols.RGViewChangeQuorumCertMsg: + packets, traffic = propRGViewChangeQuorumCertOutPacketsMeter, propRGViewChangeQuorumCertOutTrafficMeter + common.RGViewChangeQuorumCertEgressTrafficMeter.Mark(int64(msg.Size)) + case msg.Code == protocols.GetPrepareBlockMsg: //packets, traffic = reqGetPrepareBlockOutPacketsMeter, reqGetPrepareBlockOutTrafficMeter common.GetPrepareBlockEgressTrafficMeter.Mark(int64(msg.Size)) diff --git a/consensus/cbft/network/metrics_test.go b/consensus/cbft/network/metrics_test.go index e818fc5eb5..225afd33c1 100644 --- a/consensus/cbft/network/metrics_test.go +++ b/consensus/cbft/network/metrics_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package network import ( @@ -56,6 +55,8 @@ func Test_MeteredMsgReadWriter_ReadMsg(t *testing.T) { {protocols.PrepareBlockMsg, 100, 100}, {protocols.ViewChangeMsg, 111, 111}, {protocols.PrepareVoteMsg, 131, 131}, + {protocols.RGBlockQuorumCertMsg, 111, 111}, + {protocols.RGViewChangeQuorumCertMsg, 131, 131}, {protocols.GetPrepareBlockMsg, 1, 1}, {protocols.GetBlockQuorumCertMsg, 3, 3}, {protocols.BlockQuorumCertMsg, 3, 3}, @@ -80,6 +81,14 @@ func Test_MeteredMsgReadWriter_ReadMsg(t *testing.T) { assert.NotEqual(t, 0, propViewChangeInPacketsMeter.Count()) assert.Equal(t, v.want, propViewChangeInTrafficMeter.Count()) + case v.code == protocols.RGBlockQuorumCertMsg: + assert.NotEqual(t, 0, propRGBlockQuorumCertInPacketsMeter.Count()) + assert.Equal(t, v.want, propRGBlockQuorumCertInTrafficMeter.Count()) + + case v.code == protocols.RGViewChangeQuorumCertMsg: + assert.NotEqual(t, 0, propRGViewChangeQuorumCertInPacketsMeter.Count()) + assert.Equal(t, v.want, propRGViewChangeQuorumCertInTrafficMeter.Count()) + case v.code == protocols.GetPrepareBlockMsg: assert.NotEqual(t, 0, reqGetPrepareBlockInPacketsMeter.Count()) assert.Equal(t, v.want, reqGetPrepareBlockInTrafficMeter.Count()) @@ -129,6 +138,8 @@ func TestMeteredMsgReadWriter_WriteMsg(t *testing.T) { {protocols.PrepareBlockMsg, 100, 100}, {protocols.ViewChangeMsg, 111, 111}, {protocols.PrepareVoteMsg, 131, 131}, + {protocols.RGBlockQuorumCertMsg, 111, 111}, + {protocols.RGViewChangeQuorumCertMsg, 131, 131}, {protocols.GetPrepareBlockMsg, 1, 1}, {protocols.GetBlockQuorumCertMsg, 3, 3}, {protocols.BlockQuorumCertMsg, 3, 3}, @@ -154,6 +165,14 @@ func TestMeteredMsgReadWriter_WriteMsg(t *testing.T) { assert.NotEqual(t, 0, propViewChangeOutPacketsMeter.Count()) assert.Equal(t, v.want, propViewChangeOutTrafficMeter.Count()) + case v.code == protocols.RGBlockQuorumCertMsg: + assert.NotEqual(t, 0, propRGBlockQuorumCertOutPacketsMeter.Count()) + assert.Equal(t, v.want, propRGBlockQuorumCertOutTrafficMeter.Count()) + + case v.code == protocols.RGViewChangeQuorumCertMsg: + assert.NotEqual(t, 0, propRGViewChangeQuorumCertOutPacketsMeter.Count()) + assert.Equal(t, v.want, propRGViewChangeQuorumCertOutTrafficMeter.Count()) + case v.code == protocols.GetPrepareBlockMsg: assert.NotEqual(t, 0, reqGetPrepareBlockOutPacketsMeter.Count()) assert.Equal(t, v.want, reqGetPrepareBlockOutTrafficMeter.Count()) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index a1e763dce4..3de57f7f3b 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -53,8 +53,8 @@ type RGMsg struct { type PubSub struct { pss *p2p.PubSubServer - // Messages of all topics subscribed are sent out from this channel uniformly - msgCh chan *RGMsg + receiveCallback func(peerID enode.ID, msg *RGMsg) + // All topics subscribed topics map[string]*pubsub.Topic // The set of topics we are subscribed to @@ -96,12 +96,12 @@ func (ps *PubSub) Protocols() []p2p.Protocol { } } -func NewPubSub(server *p2p.PubSubServer) *PubSub { +func NewPubSub(server *p2p.PubSubServer, receiveCallback func(peerID enode.ID, msg *RGMsg)) *PubSub { return &PubSub{ - pss: server, - msgCh: make(chan *RGMsg), - topics: make(map[string]*pubsub.Topic), - mySubs: make(map[string]*pubsub.Subscription), + pss: server, + receiveCallback: receiveCallback, + topics: make(map[string]*pubsub.Topic), + mySubs: make(map[string]*pubsub.Subscription), } } @@ -144,7 +144,8 @@ func (ps *PubSub) listen(s *pubsub.Subscription) { ps.Cancel(s.Topic()) return } - ps.msgCh <- &msgData + var pid enode.ID // TODO + ps.receiveCallback(pid, &msgData) } } } @@ -175,7 +176,3 @@ func (ps *PubSub) Publish(topic string, data *RGMsg) error { } return t.Publish(context.Background(), env) } - -func (ps *PubSub) Receive() *RGMsg { - return <-ps.msgCh -} diff --git a/consensus/cbft/network/router.go b/consensus/cbft/network/router.go index 5af0344a78..8801a9f2d5 100644 --- a/consensus/cbft/network/router.go +++ b/consensus/cbft/network/router.go @@ -150,8 +150,8 @@ func (r *router) filteredPeers(msgType uint64, condition common.Hash) ([]*peer, // Test the anchor point, please pay attention to let go. //return r.peers() switch msgType { - case protocols.PrepareBlockMsg, protocols.PrepareVoteMsg, - protocols.ViewChangeMsg, protocols.BlockQuorumCertMsg: + case protocols.PrepareBlockMsg, protocols.RGBlockQuorumCertMsg, + protocols.RGViewChangeQuorumCertMsg, protocols.BlockQuorumCertMsg: return r.kMixingRandomNodes(condition, r.filter) case protocols.PrepareBlockHashMsg, protocols.GetLatestStatusMsg, protocols.GetViewChangeMsg, protocols.GetPrepareVoteMsg, diff --git a/consensus/cbft/network/router_test.go b/consensus/cbft/network/router_test.go index 74d92f7a6b..847c3a3d38 100644 --- a/consensus/cbft/network/router_test.go +++ b/consensus/cbft/network/router_test.go @@ -179,7 +179,8 @@ func Test_Router_FilteredPeers(t *testing.T) { cond common.Hash }{ {protocols.PrepareBlockMsg, common.Hash{}}, - {protocols.PrepareVoteMsg, presetMessageHash}, + {protocols.RGBlockQuorumCertMsg, presetMessageHash}, + {protocols.RGViewChangeQuorumCertMsg, presetMessageHash}, {protocols.PrepareBlockHashMsg, common.Hash{}}, {protocols.PrepareBlockHashMsg, presetMessageHash}, } @@ -190,8 +191,8 @@ func Test_Router_FilteredPeers(t *testing.T) { } t.Logf("filtered len: %d", len(peers)) switch v.msgType { - case protocols.PrepareBlockMsg, protocols.PrepareVoteMsg, - protocols.ViewChangeMsg, protocols.BlockQuorumCertMsg: + case protocols.PrepareBlockMsg, protocols.RGBlockQuorumCertMsg, + protocols.RGViewChangeQuorumCertMsg, protocols.BlockQuorumCertMsg: if v.cond == (common.Hash{}) { //assert.Equal(t, testingPeerCount, len(peers)) t.Log(testingPeerCount) diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index 02797285ce..c7413940cc 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -32,7 +32,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/core/rawdb" - "github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" "github.com/AlayaNetwork/Alaya-Go/core/state" @@ -43,6 +42,7 @@ import ( vm2 "github.com/AlayaNetwork/Alaya-Go/common/vm" "github.com/AlayaNetwork/Alaya-Go/consensus" "github.com/AlayaNetwork/Alaya-Go/core" + "github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" "github.com/AlayaNetwork/Alaya-Go/core/types" "github.com/AlayaNetwork/Alaya-Go/core/vm" "github.com/AlayaNetwork/Alaya-Go/crypto" @@ -143,7 +143,7 @@ func newTestNodeByNum(num int) []params.CbftNode { var sec bls.SecretKey sec.SetByCSPRNG() node := params.CbftNode{ - Node: enode.NewV4(randomPubKey(), nil, 0, 0), + Node: enode.NewV4(randomPubKey(), nil, 0, 0), BlsPubKey: *sec.GetPublicKey(), } nodes = append(nodes, node) @@ -648,7 +648,7 @@ func TestGetGroupID(t *testing.T) { agency := newTestInnerAgency(nodes) vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID()) - grpID,_ := vp.GetGroupID(0, nodes[0].Node.ID()) + grpID, _ := vp.GetGroupID(0, nodes[0].Node.ID()) assert.Equal(t, 0, grpID) } @@ -658,7 +658,6 @@ func TestGetUintID(t *testing.T) { agency := newTestInnerAgency(nodes) vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID()) - untID,_ := vp.GetGroupID(0, nodes[0].Node.ID()) + untID, _ := vp.GetGroupID(0, nodes[0].Node.ID()) assert.Equal(t, 0, untID) } - diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 65ed1f66bd..2ffced8195 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -24,7 +24,6 @@ import ( "fmt" "github.com/AlayaNetwork/Alaya-Go/log" "math" - "math/big" "sort" "github.com/AlayaNetwork/Alaya-Go/common/hexutil" @@ -39,22 +38,17 @@ import ( "github.com/AlayaNetwork/Alaya-Go/crypto/bls" ) -// Block's Signature info -type BlockSignature struct { - SignHash common.Hash // Signature hash,header[0:32] - Hash common.Hash // Block hash,header[:] - Number *big.Int - Signature *common.BlockConfirmSign +const ( + TopicConsensus = "consensus:%d" // consensus:{epoch} + TopicGroup = "consensus:%d:%d" // consensus:{epoch}:{groupID} +) + +func ConsensusTopicName(epoch uint64) string { + return fmt.Sprintf(TopicConsensus, epoch) } -func (bs *BlockSignature) Copy() *BlockSignature { - sign := *bs.Signature - return &BlockSignature{ - SignHash: bs.SignHash, - Hash: bs.Hash, - Number: new(big.Int).Set(bs.Number), - Signature: &sign, - } +func ConsensusGroupTopicName(epoch uint64, groupID uint32) string { + return fmt.Sprintf(TopicGroup, epoch, groupID) } type UpdateChainStateFn func(qcState, lockState, commitState *protocols.State) @@ -270,7 +264,7 @@ func (vs *Validators) GroupID(nodeID enode.ID) uint32 { groupID = groupID + 1 } } - return groupID + return groupID } func (vs *Validators) UnitID(nodeID enode.ID) uint32 { @@ -320,7 +314,7 @@ func (vs *Validators) Grouped(groupValidatorsLimit int, coordinatorLimit int) er groupNum := validatorCount / groupValidatorsLimit mod := validatorCount % groupValidatorsLimit if mod > 0 { - groupNum = groupNum +1 + groupNum = groupNum + 1 } memberMinCount := validatorCount / groupNum @@ -332,7 +326,7 @@ func (vs *Validators) Grouped(groupValidatorsLimit int, coordinatorLimit int) er begin = end if remainder > 0 { end = begin + memberMinCount + 1 - remainder = remainder - 1 + remainder = remainder - 1 } else { end = begin + memberMinCount } diff --git a/core/events.go b/core/events.go index 41c5b0758b..1f47d4230e 100644 --- a/core/events.go +++ b/core/events.go @@ -18,7 +18,6 @@ package core import ( "github.com/AlayaNetwork/Alaya-Go/common" - "github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" "github.com/AlayaNetwork/Alaya-Go/core/types" ) @@ -33,15 +32,6 @@ type PendingLogsEvent struct { // NewMinedBlockEvent is posted when a block has been imported. type NewMinedBlockEvent struct{ Block *types.Block } -type PrepareMinedBlockEvent struct { - Block *types.Block - // ConsensusNodes []discover.NodeID -} -type BlockSignatureEvent struct { - BlockSignature *cbfttypes.BlockSignature - // ConsensusNodes []discover.NodeID -} - // RemovedLogsEvent is posted when a reorg happens type RemovedLogsEvent struct{ Logs []*types.Log } diff --git a/eth/peer.go b/eth/peer.go index 9078e35ff0..c761d9c359 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -24,7 +24,6 @@ import ( "time" "github.com/AlayaNetwork/Alaya-Go/consensus" - "github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" "github.com/AlayaNetwork/Alaya-Go/eth/downloader" "github.com/deckarep/golang-set" @@ -735,26 +734,6 @@ func (ps *peerSet) PeersWithoutConsensus(engine consensus.Engine) []*peer { return list } -type preBlockEvent struct { - block *types.Block -} - -type signatureEvent struct { - SignHash common.Hash // Signature hash,header[0:32] - Hash common.Hash // Block hash,header[:] - Number *big.Int - Signature *common.BlockConfirmSign -} - -// SendPrepareBlock propagates an entire block to a remote peer. -func (p *peer) SendPrepareBlock(block *types.Block) error { - return p2p.Send(p.rw, PrepareBlockMsg, []interface{}{block}) -} - -func (p *peer) SendSignature(signature *cbfttypes.BlockSignature) error { - return p2p.Send(p.rw, BlockSignatureMsg, []interface{}{signature.SignHash, signature.Hash, signature.Number, signature.Signature}) -} - // 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)) From 4dd21ecc90ca551c74c300dff386d165b86ae78a Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Mon, 15 Nov 2021 17:39:54 +0800 Subject: [PATCH 047/299] Improve the publishing and receiving of topic msg --- consensus/cbft/cbft.go | 10 +-- consensus/cbft/consensus_process.go | 32 +++++---- consensus/cbft/network/handler.go | 44 ++++++++----- consensus/cbft/network/helper_test.go | 9 ++- consensus/cbft/network/metrics.go | 69 ++++++++++++------- consensus/cbft/network/metrics_test.go | 91 ++++++++++++++++++-------- consensus/cbft/network/pubsub.go | 19 +++--- consensus/cbft/network/router.go | 9 +-- consensus/cbft/protocols/protocol.go | 13 ++-- consensus/cbft/state/state.go | 8 +-- consensus/cbft/state/state_test.go | 3 + consensus/cbft/types/crypto.go | 21 ++++++ consensus/cbft/types/message.go | 3 +- 13 files changed, 215 insertions(+), 116 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index c013aa8515..280e50d8ac 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -213,7 +213,7 @@ func New(sysConfig *params.CbftConfig, optConfig *ctypes.OptionsConfig, eventMux statQueues: make(map[common.Hash]map[string]int), messageHashCache: mapset.NewSet(), netLatencyMap: make(map[string]*list.List), - //pubSub: network.NewPubSub(ctx.PubSubServer()), + pubSub: network.NewPubSub(ctx.PubSubServer()), } if evPool, err := evidence.NewEvidencePool(ctx, optConfig.EvidenceDir); err == nil { @@ -271,7 +271,7 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCacheWriter consensus. go cbft.network.Start() // Data required to initialize pubsub - cbft.pubSub.Init(cbft.config, cbft.network.GetPeer) + cbft.pubSub.Init(cbft.config, cbft.network.GetPeer, cbft.network.HandleRGMsg) if cbft.config.Option.Node == nil { cbft.config.Option.Node = enode.NewV4(&cbft.nodeServiceContext.NodePriKey().PublicKey, nil, 0, 0) @@ -1516,10 +1516,10 @@ func (cbft *Cbft) checkPrepareQC(msg ctypes.ConsensusMsg) error { viewChangeQC := cm.ViewChangeQC prepareQCs := cm.PrepareQCs for _, qc := range viewChangeQC.QCs { - if qc.BlockNumber == 0 && prepareQCs != nil && prepareQCs[qc.BlockHash] != nil { + if qc.BlockNumber == 0 && prepareQCs != nil && prepareQCs.FindPrepareQC(qc.BlockHash) != nil { return authFailedError{err: fmt.Errorf("RGViewChangeQuorumCert need not take PrepareQC, RGViewChangeQuorumCert:%s", cm.String())} } - if qc.BlockNumber != 0 && (prepareQCs == nil || prepareQCs[qc.BlockHash] == nil) { + if qc.BlockNumber != 0 && (prepareQCs == nil || prepareQCs.FindPrepareQC(qc.BlockHash) == nil) { return authFailedError{err: fmt.Errorf("RGViewChangeQuorumCert need take PrepareQC, RGViewChangeQuorumCert:%s", cm.String())} } } @@ -1647,7 +1647,7 @@ func (cbft *Cbft) verifyConsensusMsg(msg ctypes.ConsensusMsg) (*cbfttypes.Valida prepareQCs := cm.PrepareQCs for _, qc := range viewChangeQC.QCs { if qc.BlockNumber != 0 { - if err := cbft.verifyPrepareQC(qc.BlockNumber, qc.BlockHash, prepareQCs[qc.BlockHash]); err != nil { + if err := cbft.verifyPrepareQC(qc.BlockNumber, qc.BlockHash, prepareQCs.FindPrepareQC(qc.BlockHash)); err != nil { return nil, err } } diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 55ced6e101..cb36f85fa9 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -19,7 +19,6 @@ package cbft import ( "fmt" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/network" - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "time" "github.com/pkg/errors" @@ -311,9 +310,9 @@ func (cbft *Cbft) OnRGViewChangeQuorumCert(id string, msg *protocols.RGViewChang if err.Fetch() { viewChangeQC := msg.ViewChangeQC _, _, _, _, blockHash, blockNumber := viewChangeQC.MaxBlock() - if msg.PrepareQCs != nil && msg.PrepareQCs[blockHash] != nil { + if msg.PrepareQCs != nil && msg.PrepareQCs.FindPrepareQC(blockHash) != nil { cbft.log.Info("Epoch or viewNumber higher than local, try to fetch block", "fetchHash", blockHash, "fetchNumber", blockNumber) - cbft.fetchBlock(id, blockHash, blockNumber, msg.PrepareQCs[blockHash]) + cbft.fetchBlock(id, blockHash, blockNumber, msg.PrepareQCs.FindPrepareQC(blockHash)) } } return err @@ -341,7 +340,7 @@ func (cbft *Cbft) OnRGViewChangeQuorumCert(id string, msg *protocols.RGViewChang cbft.state.AddRGViewChangeQuorumCert(node.Index, msg) cbft.richRGViewChangeQuorumCert(msg) - cbft.state.AddSelectRGViewChangeQuorumCerts(msg.GroupID, msg.ViewChangeQC, msg.PrepareQCs) + cbft.state.AddSelectRGViewChangeQuorumCerts(msg.GroupID, msg.ViewChangeQC, msg.PrepareQCs.FlattenMap()) cbft.log.Debug("Receive new RGViewChangeQuorumCert", "msgHash", msg.MsgHash(), "RGViewChangeQuorumCert", msg.String(), "total", cbft.state.RGViewChangeQuorumCertsLen(msg.GroupID)) cbft.trySendRGViewChangeQuorumCert() @@ -631,12 +630,10 @@ func (cbft *Cbft) publishTopicMsg(msg ctypes.ConsensusMsg) error { if err != nil { return fmt.Errorf("the group info of the current node is not queried, cannot publish the topic message") } + RGMsg := &network.RGMsg{Code: protocols.MessageType(msg), Data: msg} + network.MeteredWriteRGMsg(RGMsg) topic := cbfttypes.ConsensusGroupTopicName(cbft.state.Epoch(), groupID) - return cbft.pubSub.Publish(topic, &network.RGMsg{Code: protocols.MessageType(msg), Data: msg}) -} - -func (cbft *Cbft) OnTopicMsg(peerID enode.ID, msg *network.RGMsg) { - // TODO + return cbft.pubSub.Publish(topic, RGMsg) } //func (cbft *Cbft) trySendRGBlockQuorumCert() { @@ -880,7 +877,7 @@ func (cbft *Cbft) mergeSelectedQuorumCerts(node *cbfttypes.ValidateNode, vote *p func (cbft *Cbft) richRGBlockQuorumCert(rgb *protocols.RGBlockQuorumCert) { mergeVotes := cbft.groupPrepareVotes(rgb.EpochNum(), rgb.BlockIndx(), rgb.GroupID) - if mergeVotes == nil && len(mergeVotes) > 0 { + if len(mergeVotes) > 0 { for _, v := range mergeVotes { if !rgb.BlockQC.ValidatorSet.GetIndex(v.NodeIndex()) { rgb.BlockQC.AddSign(v.Signature, v.NodeIndex()) @@ -901,7 +898,7 @@ func (cbft *Cbft) mergeSelectedViewChangeQuorumCerts(node *cbfttypes.ValidateNod func (cbft *Cbft) richRGViewChangeQuorumCert(rgb *protocols.RGViewChangeQuorumCert) { mergeVcs := cbft.groupViewChanges(rgb.EpochNum(), rgb.GroupID) - if mergeVcs == nil && len(mergeVcs) > 0 { + if len(mergeVcs) > 0 { for _, vc := range mergeVcs { if !rgb.ViewChangeQC.ExistViewChange(vc.Epoch, vc.ViewNumber, vc.BlockHash) { qc := &ctypes.ViewChangeQuorumCert{ @@ -909,22 +906,23 @@ func (cbft *Cbft) richRGViewChangeQuorumCert(rgb *protocols.RGViewChangeQuorumCe ViewNumber: vc.ViewNumber, BlockHash: vc.BlockHash, BlockNumber: vc.BlockNumber, - //BlockEpoch: vc.PrepareQC.Epoch, - //BlockViewNumber: vc.PrepareQC.ViewNumber, - ValidatorSet: utils.NewBitArray(vc.PrepareQC.ValidatorSet.Size()), } if vc.PrepareQC != nil { qc.BlockEpoch = vc.PrepareQC.Epoch qc.BlockViewNumber = vc.PrepareQC.ViewNumber + rgb.PrepareQCs.QCs = append(rgb.PrepareQCs.QCs, vc.PrepareQC) } - qc.Signature.SetBytes(vc.Signature.Bytes()) + total := cbft.validatorPool.Len(cbft.state.Epoch()) + qc.ValidatorSet = utils.NewBitArray(uint32(total)) qc.ValidatorSet.SetIndex(vc.ValidatorIndex, true) + qc.Signature.SetBytes(vc.Signature.Bytes()) + rgb.ViewChangeQC.QCs = append(rgb.ViewChangeQC.QCs, qc) - rgb.PrepareQCs[vc.BlockHash] = vc.PrepareQC } else { for _, qc := range rgb.ViewChangeQC.QCs { - if !qc.ValidatorSet.GetIndex(vc.NodeIndex()) { + if qc.BlockHash == vc.BlockHash && !qc.ValidatorSet.GetIndex(vc.NodeIndex()) { qc.AddSign(vc.Signature, vc.NodeIndex()) + break } } } diff --git a/consensus/cbft/network/handler.go b/consensus/cbft/network/handler.go index 86c8b42e74..eb73899030 100644 --- a/consensus/cbft/network/handler.go +++ b/consensus/cbft/network/handler.go @@ -441,6 +441,34 @@ func (h *EngineManager) handler(p *p2p.Peer, rw p2p.MsgReadWriter) error { } } +func (h *EngineManager) HandleRGMsg(p *peer, msg *RGMsg) error { + // All messages cannot exceed the maximum specified by the agreement. + if msg.Size > protocols.CbftProtocolMaxMsgSize { + return types.ErrResp(types.ErrMsgTooLarge, "%v > %v", msg.Size, protocols.CbftProtocolMaxMsgSize) + } + + switch { + case msg.Code == protocols.RGBlockQuorumCertMsg: + if request, ok := msg.Data.(protocols.RGBlockQuorumCert); ok { + p.MarkMessageHash((&request).MsgHash()) + MeteredReadRGMsg(msg) + return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) + } + return types.ErrResp(types.ErrInvalidRGMsg, "%s: %v", "unmatched code and data", msg.Code) + + case msg.Code == protocols.RGViewChangeQuorumCertMsg: + if request, ok := msg.Data.(protocols.RGViewChangeQuorumCert); ok { + p.MarkMessageHash((&request).MsgHash()) + MeteredReadRGMsg(msg) + return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) + } + return types.ErrResp(types.ErrInvalidRGMsg, "%s: %v", "unmatched code and data", msg.Code) + + default: + return types.ErrResp(types.ErrInvalidMsgCode, "%v", msg.Code) + } +} + // Main logic: Distribute according to message type and // transfer message to CBFT layer func (h *EngineManager) handleMsg(p *peer) error { @@ -489,22 +517,6 @@ func (h *EngineManager) handleMsg(p *peer) error { p.MarkMessageHash((&request).MsgHash()) return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) - case msg.Code == protocols.RGBlockQuorumCertMsg: - var request protocols.RGBlockQuorumCert - if err := msg.Decode(&request); err != nil { - return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) - } - p.MarkMessageHash((&request).MsgHash()) - return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) - - case msg.Code == protocols.RGViewChangeQuorumCertMsg: - var request protocols.RGViewChangeQuorumCert - if err := msg.Decode(&request); err != nil { - return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) - } - p.MarkMessageHash((&request).MsgHash()) - return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) - case msg.Code == protocols.GetPrepareBlockMsg: var request protocols.GetPrepareBlock if err := msg.Decode(&request); err != nil { diff --git a/consensus/cbft/network/helper_test.go b/consensus/cbft/network/helper_test.go index fa2f15879e..723aff39d4 100644 --- a/consensus/cbft/network/helper_test.go +++ b/consensus/cbft/network/helper_test.go @@ -103,8 +103,13 @@ func newFakeRGViewChangeQuorumCert() *protocols.RGViewChangeQuorumCert { }, }, ValidatorIndex: 1, - PrepareQCs: nil, - Signature: newSignature(), + //PrepareQCs: nil, + PrepareQCs: &ctypes.PrepareQCs{ + QCs: []*ctypes.QuorumCert{ + newQuorumCert(), + }, + }, + Signature: newSignature(), } } diff --git a/consensus/cbft/network/metrics.go b/consensus/cbft/network/metrics.go index 0884650b1b..b510bf1cbe 100644 --- a/consensus/cbft/network/metrics.go +++ b/consensus/cbft/network/metrics.go @@ -21,6 +21,7 @@ import ( "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" "github.com/AlayaNetwork/Alaya-Go/metrics" "github.com/AlayaNetwork/Alaya-Go/p2p" + "github.com/AlayaNetwork/Alaya-Go/rlp" ) var ( @@ -140,6 +141,22 @@ func (rw *meteredMsgReadWriter) Init(version int) { rw.version = version } +func MeteredReadRGMsg(msg *RGMsg) { + if !metrics.Enabled { + return + } + packets, traffic := miscInPacketsMeter, miscInTrafficMeter + switch { + case msg.Code == protocols.RGBlockQuorumCertMsg: + packets, traffic = propRGBlockQuorumCertInPacketsMeter, propRGBlockQuorumCertInTrafficMeter + + case msg.Code == protocols.RGViewChangeQuorumCertMsg: + packets, traffic = propRGViewChangeQuorumCertInPacketsMeter, propRGViewChangeQuorumCertInTrafficMeter + } + packets.Mark(1) + traffic.Mark(int64(msg.Size)) +} + func (rw *meteredMsgReadWriter) ReadMsg() (p2p.Msg, error) { // Read the message and short circuit in case of an error msg, err := rw.MsgReadWriter.ReadMsg() @@ -157,12 +174,6 @@ func (rw *meteredMsgReadWriter) ReadMsg() (p2p.Msg, error) { case msg.Code == protocols.ViewChangeMsg: packets, traffic = propViewChangeInPacketsMeter, propViewChangeInTrafficMeter - case msg.Code == protocols.RGBlockQuorumCertMsg: - packets, traffic = propRGBlockQuorumCertInPacketsMeter, propRGBlockQuorumCertInTrafficMeter - - case msg.Code == protocols.RGViewChangeQuorumCertMsg: - packets, traffic = propRGViewChangeQuorumCertInPacketsMeter, propRGViewChangeQuorumCertInTrafficMeter - case msg.Code == protocols.GetPrepareBlockMsg: packets, traffic = reqGetPrepareBlockInPacketsMeter, reqGetPrepareBlockInTrafficMeter @@ -190,6 +201,28 @@ func (rw *meteredMsgReadWriter) ReadMsg() (p2p.Msg, error) { return msg, err } +func MeteredWriteRGMsg(msg *RGMsg) { + if !metrics.Enabled { + return + } + size, _, err := rlp.EncodeToReader(msg.Data) + if err != nil { + return + } + packets, traffic := miscOutPacketsMeter, miscOutTrafficMeter + switch { + case msg.Code == protocols.RGBlockQuorumCertMsg: + packets, traffic = propRGBlockQuorumCertOutPacketsMeter, propRGBlockQuorumCertOutTrafficMeter + common.RGBlockQuorumCertEgressTrafficMeter.Mark(int64(msg.Size)) + + case msg.Code == protocols.RGViewChangeQuorumCertMsg: + packets, traffic = propRGViewChangeQuorumCertOutPacketsMeter, propRGViewChangeQuorumCertOutTrafficMeter + common.RGViewChangeQuorumCertEgressTrafficMeter.Mark(int64(msg.Size)) + } + packets.Mark(1) + traffic.Mark(int64(size)) +} + func (rw *meteredMsgReadWriter) WriteMsg(msg p2p.Msg) error { // Account for the data traffic packets, traffic := miscOutPacketsMeter, miscOutTrafficMeter @@ -206,44 +239,36 @@ func (rw *meteredMsgReadWriter) WriteMsg(msg p2p.Msg) error { packets, traffic = propViewChangeOutPacketsMeter, propViewChangeOutTrafficMeter common.ViewChangeEgressTrafficMeter.Mark(int64(msg.Size)) - case msg.Code == protocols.RGBlockQuorumCertMsg: - packets, traffic = propRGBlockQuorumCertOutPacketsMeter, propRGBlockQuorumCertOutTrafficMeter - common.RGBlockQuorumCertEgressTrafficMeter.Mark(int64(msg.Size)) - - case msg.Code == protocols.RGViewChangeQuorumCertMsg: - packets, traffic = propRGViewChangeQuorumCertOutPacketsMeter, propRGViewChangeQuorumCertOutTrafficMeter - common.RGViewChangeQuorumCertEgressTrafficMeter.Mark(int64(msg.Size)) - case msg.Code == protocols.GetPrepareBlockMsg: - //packets, traffic = reqGetPrepareBlockOutPacketsMeter, reqGetPrepareBlockOutTrafficMeter + packets, traffic = reqGetPrepareBlockOutPacketsMeter, reqGetPrepareBlockOutTrafficMeter common.GetPrepareBlockEgressTrafficMeter.Mark(int64(msg.Size)) case msg.Code == protocols.PrepareBlockHashMsg: - //packets, traffic = propPrepareBlockHashOutPacketsMeter, propPrepareBlockHashOutTrafficMeter + packets, traffic = propPrepareBlockHashOutPacketsMeter, propPrepareBlockHashOutTrafficMeter common.PrepareBlockHashEgressTrafficMeter.Mark(int64(msg.Size)) case msg.Code == protocols.GetPrepareVoteMsg: - //packets, traffic = reqGetPrepareBlockOutPacketsMeter, reqGetPrepareVoteOutTrafficMeter + packets, traffic = reqGetPrepareBlockOutPacketsMeter, reqGetPrepareVoteOutTrafficMeter common.GetPrepareVoteEgressTrafficMeter.Mark(int64(msg.Size)) case msg.Code == protocols.GetBlockQuorumCertMsg: - //packets, traffic = reqGetQuorumCertOutPacketsMeter, reqGetQuorumCertOutTrafficMeter + packets, traffic = reqGetQuorumCertOutPacketsMeter, reqGetQuorumCertOutTrafficMeter common.GetBlockQuorumCertEgressTrafficMeter.Mark(int64(msg.Size)) case msg.Code == protocols.BlockQuorumCertMsg: - //packets, traffic = reqBlockQuorumCertOutPacketsMeter, reqBlockQuorumCertOutTrafficMeter + packets, traffic = reqBlockQuorumCertOutPacketsMeter, reqBlockQuorumCertOutTrafficMeter common.BlockQuorumCertEgressTrafficMeter.Mark(int64(msg.Size)) case msg.Code == protocols.PrepareVotesMsg: - //packets, traffic = reqPrepareVotesOutPacketsMeter, reqPrepareVotesOutTrafficMeter + packets, traffic = reqPrepareVotesOutPacketsMeter, reqPrepareVotesOutTrafficMeter common.PrepareVotesEgressTrafficMeter.Mark(int64(msg.Size)) case msg.Code == protocols.GetQCBlockListMsg: - //packets, traffic = reqGetQCBlockListOutPacketsMeter, reqGetQCBlockListOutTrafficMeter + packets, traffic = reqGetQCBlockListOutPacketsMeter, reqGetQCBlockListOutTrafficMeter common.GetQCBlockListEgressTrafficMeter.Mark(int64(msg.Size)) case msg.Code == protocols.QCBlockListMsg: - //packets, traffic = reqQCBlockListOutPacketsMeter, reqQCBlockListOutTrafficMeter + packets, traffic = reqQCBlockListOutPacketsMeter, reqQCBlockListOutTrafficMeter common.QCBlockListEgressTrafficMeter.Mark(int64(msg.Size)) case msg.Code == protocols.CBFTStatusMsg: diff --git a/consensus/cbft/network/metrics_test.go b/consensus/cbft/network/metrics_test.go index 225afd33c1..182d102ef9 100644 --- a/consensus/cbft/network/metrics_test.go +++ b/consensus/cbft/network/metrics_test.go @@ -17,12 +17,14 @@ package network import ( + "github.com/AlayaNetwork/Alaya-Go/rlp" "testing" "github.com/AlayaNetwork/Alaya-Go/metrics" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/stretchr/testify/assert" ) @@ -55,8 +57,6 @@ func Test_MeteredMsgReadWriter_ReadMsg(t *testing.T) { {protocols.PrepareBlockMsg, 100, 100}, {protocols.ViewChangeMsg, 111, 111}, {protocols.PrepareVoteMsg, 131, 131}, - {protocols.RGBlockQuorumCertMsg, 111, 111}, - {protocols.RGViewChangeQuorumCertMsg, 131, 131}, {protocols.GetPrepareBlockMsg, 1, 1}, {protocols.GetBlockQuorumCertMsg, 3, 3}, {protocols.BlockQuorumCertMsg, 3, 3}, @@ -70,54 +70,76 @@ func Test_MeteredMsgReadWriter_ReadMsg(t *testing.T) { readMsg(v.code, v.size) switch { case v.code == protocols.PrepareBlockMsg: - assert.NotEqual(t, 0, propPrepareBlockInPacketsMeter.Count()) + //assert.NotEqual(t, 0, propPrepareBlockInPacketsMeter.Count()) + assert.Equal(t, int64(1), propPrepareBlockInPacketsMeter.Count()) assert.Equal(t, v.want, propPrepareBlockInTrafficMeter.Count()) case v.code == protocols.PrepareVoteMsg: - assert.NotEqual(t, 0, propPrepareVoteInPacketsMeter.Count()) + //assert.NotEqual(t, 0, propPrepareVoteInPacketsMeter.Count()) + assert.Equal(t, int64(1), propPrepareVoteInPacketsMeter.Count()) assert.Equal(t, v.want, propPrepareVoteInTrafficMeter.Count()) case v.code == protocols.ViewChangeMsg: assert.NotEqual(t, 0, propViewChangeInPacketsMeter.Count()) + assert.Equal(t, int64(1), propViewChangeInPacketsMeter.Count()) assert.Equal(t, v.want, propViewChangeInTrafficMeter.Count()) - case v.code == protocols.RGBlockQuorumCertMsg: - assert.NotEqual(t, 0, propRGBlockQuorumCertInPacketsMeter.Count()) - assert.Equal(t, v.want, propRGBlockQuorumCertInTrafficMeter.Count()) - - case v.code == protocols.RGViewChangeQuorumCertMsg: - assert.NotEqual(t, 0, propRGViewChangeQuorumCertInPacketsMeter.Count()) - assert.Equal(t, v.want, propRGViewChangeQuorumCertInTrafficMeter.Count()) - case v.code == protocols.GetPrepareBlockMsg: - assert.NotEqual(t, 0, reqGetPrepareBlockInPacketsMeter.Count()) + //assert.NotEqual(t, 0, reqGetPrepareBlockInPacketsMeter.Count()) + assert.Equal(t, int64(1), reqGetPrepareBlockInPacketsMeter.Count()) assert.Equal(t, v.want, reqGetPrepareBlockInTrafficMeter.Count()) case v.code == protocols.GetBlockQuorumCertMsg: - assert.NotEqual(t, 0, reqGetQuorumCertInPacketsMeter.Count()) + //assert.NotEqual(t, 0, reqGetQuorumCertInPacketsMeter.Count()) + assert.Equal(t, int64(1), reqGetQuorumCertInPacketsMeter.Count()) assert.Equal(t, v.want, reqGetQuorumCertInTrafficMeter.Count()) case v.code == protocols.BlockQuorumCertMsg: - assert.NotEqual(t, 0, reqBlockQuorumCertInPacketsMeter.Count()) + //assert.NotEqual(t, 0, reqBlockQuorumCertInPacketsMeter.Count()) + assert.Equal(t, int64(1), reqBlockQuorumCertInPacketsMeter.Count()) assert.Equal(t, v.want, reqBlockQuorumCertInTrafficMeter.Count()) case v.code == protocols.GetPrepareVoteMsg: - assert.NotEqual(t, 0, reqGetPrepareVoteInPacketsMeter.Count()) + //assert.NotEqual(t, 0, reqGetPrepareVoteInPacketsMeter.Count()) + assert.Equal(t, int64(1), reqGetPrepareVoteInPacketsMeter.Count()) assert.Equal(t, v.want, reqGetPrepareVoteInTrafficMeter.Count()) case v.code == protocols.PrepareVotesMsg: - assert.NotEqual(t, 0, reqPrepareVotesInPacketsMeter.Count()) + //assert.NotEqual(t, 0, reqPrepareVotesInPacketsMeter.Count()) + assert.Equal(t, int64(1), reqPrepareVotesInPacketsMeter.Count()) assert.Equal(t, v.want, reqPrepareVotesInTrafficMeter.Count()) case v.code == protocols.GetQCBlockListMsg: - assert.NotEqual(t, 0, reqGetQCBlockListInPacketsMeter.Count()) + //assert.NotEqual(t, 0, reqGetQCBlockListInPacketsMeter.Count()) + assert.Equal(t, int64(1), reqGetQCBlockListInPacketsMeter.Count()) assert.Equal(t, v.want, reqGetQCBlockListInTrafficMeter.Count()) case v.code == protocols.QCBlockListMsg: - assert.NotEqual(t, 0, reqQCBlockListInPacketsMeter.Count()) + //assert.NotEqual(t, 0, reqQCBlockListInPacketsMeter.Count()) + assert.Equal(t, int64(1), reqQCBlockListInPacketsMeter.Count()) assert.Equal(t, v.want, reqGetQCBlockListInTrafficMeter.Count()) } } + testRGCases := []struct { + code uint64 + size uint32 + want int64 + }{ + {protocols.RGBlockQuorumCertMsg, 111, 111}, + {protocols.RGViewChangeQuorumCertMsg, 131, 131}, + } + for _, v := range testRGCases { + MeteredReadRGMsg(&RGMsg{Code: v.code, Size: v.size}) + switch { + case v.code == protocols.RGBlockQuorumCertMsg: + assert.Equal(t, int64(1), propRGBlockQuorumCertInPacketsMeter.Count()) + assert.Equal(t, v.want, propRGBlockQuorumCertInTrafficMeter.Count()) + + case v.code == protocols.RGViewChangeQuorumCertMsg: + assert.Equal(t, int64(1), propRGViewChangeQuorumCertInPacketsMeter.Count()) + assert.Equal(t, v.want, propRGViewChangeQuorumCertInTrafficMeter.Count()) + } + } } func TestMeteredMsgReadWriter_WriteMsg(t *testing.T) { @@ -138,8 +160,6 @@ func TestMeteredMsgReadWriter_WriteMsg(t *testing.T) { {protocols.PrepareBlockMsg, 100, 100}, {protocols.ViewChangeMsg, 111, 111}, {protocols.PrepareVoteMsg, 131, 131}, - {protocols.RGBlockQuorumCertMsg, 111, 111}, - {protocols.RGViewChangeQuorumCertMsg, 131, 131}, {protocols.GetPrepareBlockMsg, 1, 1}, {protocols.GetBlockQuorumCertMsg, 3, 3}, {protocols.BlockQuorumCertMsg, 3, 3}, @@ -165,14 +185,6 @@ func TestMeteredMsgReadWriter_WriteMsg(t *testing.T) { assert.NotEqual(t, 0, propViewChangeOutPacketsMeter.Count()) assert.Equal(t, v.want, propViewChangeOutTrafficMeter.Count()) - case v.code == protocols.RGBlockQuorumCertMsg: - assert.NotEqual(t, 0, propRGBlockQuorumCertOutPacketsMeter.Count()) - assert.Equal(t, v.want, propRGBlockQuorumCertOutTrafficMeter.Count()) - - case v.code == protocols.RGViewChangeQuorumCertMsg: - assert.NotEqual(t, 0, propRGViewChangeQuorumCertOutPacketsMeter.Count()) - assert.Equal(t, v.want, propRGViewChangeQuorumCertOutTrafficMeter.Count()) - case v.code == protocols.GetPrepareBlockMsg: assert.NotEqual(t, 0, reqGetPrepareBlockOutPacketsMeter.Count()) assert.Equal(t, v.want, reqGetPrepareBlockOutTrafficMeter.Count()) @@ -202,4 +214,25 @@ func TestMeteredMsgReadWriter_WriteMsg(t *testing.T) { assert.Equal(t, v.want, reqQCBlockListOutTrafficMeter.Count()) } } + testRGCases := []struct { + code uint64 + data types.ConsensusMsg + }{ + {protocols.RGBlockQuorumCertMsg, newFakeRGBlockQuorumCert()}, + {protocols.RGViewChangeQuorumCertMsg, newFakeRGViewChangeQuorumCert()}, + } + for _, v := range testRGCases { + MeteredWriteRGMsg(&RGMsg{Code: v.code, Data: v.data}) + size, _, err := rlp.EncodeToReader(v.data) + assert.Nil(t, err) + switch { + case v.code == protocols.RGBlockQuorumCertMsg: + assert.Equal(t, int64(1), propRGBlockQuorumCertOutPacketsMeter.Count()) + assert.Equal(t, int64(size), propRGBlockQuorumCertOutTrafficMeter.Count()) + + case v.code == protocols.RGViewChangeQuorumCertMsg: + assert.Equal(t, int64(1), propRGViewChangeQuorumCertOutPacketsMeter.Count()) + assert.Equal(t, int64(size), propRGViewChangeQuorumCertOutTrafficMeter.Count()) + } + } } diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index b101587f16..0de4de24e0 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -49,6 +49,7 @@ var ( // Group consensus message type RGMsg struct { Code uint64 + Size uint32 // Size of the raw payload Data interface{} } @@ -57,7 +58,7 @@ type PubSub struct { config ctypes.Config getPeerById getByIDFunc // Used to get peer by ID. - receiveCallback func(peerID enode.ID, msg *RGMsg) + onReceive receiveCallback // All topics subscribed topics map[string]*pubsub.Topic @@ -110,18 +111,18 @@ func (ps *PubSub) Protocols() []p2p.Protocol { } } -func NewPubSub(server *p2p.PubSubServer, receiveCallback func(peerID enode.ID, msg *RGMsg)) *PubSub { +func NewPubSub(server *p2p.PubSubServer) *PubSub { return &PubSub{ - pss: server, - receiveCallback: receiveCallback, - topics: make(map[string]*pubsub.Topic), - mySubs: make(map[string]*pubsub.Subscription), + pss: server, + topics: make(map[string]*pubsub.Topic), + mySubs: make(map[string]*pubsub.Subscription), } } -func (ps *PubSub) Init(config ctypes.Config, get getByIDFunc) { +func (ps *PubSub) Init(config ctypes.Config, get getByIDFunc, onReceive receiveCallback) { ps.config = config ps.getPeerById = get + ps.onReceive = onReceive } //Subscribe subscribe a topic @@ -163,8 +164,8 @@ func (ps *PubSub) listen(s *pubsub.Subscription) { ps.Cancel(s.Topic()) return } - var pid enode.ID // TODO - ps.receiveCallback(pid, &msgData) + var p *peer // TODO + ps.onReceive(p, &msgData) } } } diff --git a/consensus/cbft/network/router.go b/consensus/cbft/network/router.go index 8801a9f2d5..1a399d8b68 100644 --- a/consensus/cbft/network/router.go +++ b/consensus/cbft/network/router.go @@ -37,10 +37,11 @@ import ( // the number of nodes selected per broadcast. const DefaultFanOut = 5 -type unregisterFunc func(id string) error // Unregister peer from peerSet. -type getByIDFunc func(id string) (*peer, error) // Get peer based on ID. -type consensusNodesFunc func() ([]enode.ID, error) // Get a list of consensus nodes. -type peersFunc func() ([]*peer, error) // Get a list of all neighbor nodes. +type unregisterFunc func(id string) error // Unregister peer from peerSet. +type getByIDFunc func(id string) (*peer, error) // Get peer based on ID. +type consensusNodesFunc func() ([]enode.ID, error) // Get a list of consensus nodes. +type peersFunc func() ([]*peer, error) // Get a list of all neighbor nodes. +type receiveCallback func(p *peer, msg *RGMsg) error // Callback function for receiving topic messages // Router implements the message protocol of gossip. // diff --git a/consensus/cbft/protocols/protocol.go b/consensus/cbft/protocols/protocol.go index 99350c6adc..e4a70a9072 100644 --- a/consensus/cbft/protocols/protocol.go +++ b/consensus/cbft/protocols/protocol.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package protocols import ( @@ -896,12 +895,12 @@ func (rgb *RGBlockQuorumCert) SetSign(sign []byte) { } type RGViewChangeQuorumCert struct { - GroupID uint32 `json:"groupID"` // Unique identifier for group. - ViewChangeQC *ctypes.ViewChangeQC `json:"viewchangeQC"` // viewChange aggregate signature - ValidatorIndex uint32 `json:"validatorIndex"` - PrepareQCs map[common.Hash]*ctypes.QuorumCert `json:"prepareQCs" rlp:"nil"` - Signature ctypes.Signature `json:"signature"` // RGViewChangeQuorumCert signature information - messageHash atomic.Value `rlp:"-"` + GroupID uint32 `json:"groupID"` // Unique identifier for group. + ViewChangeQC *ctypes.ViewChangeQC `json:"viewchangeQC"` // viewChange aggregate signature + ValidatorIndex uint32 `json:"validatorIndex"` + PrepareQCs *ctypes.PrepareQCs `json:"prepareQCs" rlp:"nil"` + Signature ctypes.Signature `json:"signature"` // RGViewChangeQuorumCert signature information + messageHash atomic.Value `rlp:"-"` } func (rgv *RGViewChangeQuorumCert) String() string { diff --git a/consensus/cbft/state/state.go b/consensus/cbft/state/state.go index 1f40e3de23..b0b2dba05e 100644 --- a/consensus/cbft/state/state.go +++ b/consensus/cbft/state/state.go @@ -903,7 +903,7 @@ func (vs *ViewState) SelectRGViewChangeQuorumCertsLen(groupID uint32) int { return vs.selectedRGViewChangeQuorumCerts.RGViewChangeQuorumCertsLen(groupID) } -func (vs *ViewState) FindMaxGroupRGViewChangeQuorumCert(groupID uint32) (*ctypes.ViewChangeQC, map[common.Hash]*ctypes.QuorumCert) { +func (vs *ViewState) FindMaxGroupRGViewChangeQuorumCert(groupID uint32) (*ctypes.ViewChangeQC, *ctypes.PrepareQCs) { return vs.selectedRGViewChangeQuorumCerts.FindMaxGroupRGViewChangeQuorumCert(groupID) } @@ -1420,14 +1420,14 @@ func (srg *selectedRGViewChangeQuorumCerts) RGViewChangeQuorumCertsLen(groupID u } // Returns the QuorumCert with the most signatures in specified group -func (srg *selectedRGViewChangeQuorumCerts) FindMaxGroupRGViewChangeQuorumCert(groupID uint32) (*ctypes.ViewChangeQC, map[common.Hash]*ctypes.QuorumCert) { +func (srg *selectedRGViewChangeQuorumCerts) FindMaxGroupRGViewChangeQuorumCert(groupID uint32) (*ctypes.ViewChangeQC, *ctypes.PrepareQCs) { rgqcs := srg.GroupRGViewChangeQuorumCerts[groupID] if rgqcs == nil || len(rgqcs.QuorumCerts) <= 0 { return nil, nil } viewChangeQC := &ctypes.ViewChangeQC{QCs: make([]*ctypes.ViewChangeQuorumCert, 0)} - prepareQCs := make(map[common.Hash]*ctypes.QuorumCert) + prepareQCs := &ctypes.PrepareQCs{QCs: make([]*ctypes.QuorumCert, 0)} for hash, qcs := range rgqcs.QuorumCerts { max := qcs[0] for i := 1; i < len(qcs); i++ { @@ -1436,7 +1436,7 @@ func (srg *selectedRGViewChangeQuorumCerts) FindMaxGroupRGViewChangeQuorumCert(g } } viewChangeQC.QCs = append(viewChangeQC.QCs, max) - prepareQCs[hash] = srg.PrepareQCs[hash] + prepareQCs.QCs = append(prepareQCs.QCs, srg.PrepareQCs[hash]) } return viewChangeQC, prepareQCs } diff --git a/consensus/cbft/state/state_test.go b/consensus/cbft/state/state_test.go index 8e2a9eb857..1e6999ce85 100644 --- a/consensus/cbft/state/state_test.go +++ b/consensus/cbft/state/state_test.go @@ -17,16 +17,19 @@ package state import ( + "fmt" "math/big" "testing" "time" "github.com/stretchr/testify/assert" + "encoding/json" "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/common/math" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" "github.com/AlayaNetwork/Alaya-Go/core/types" ) diff --git a/consensus/cbft/types/crypto.go b/consensus/cbft/types/crypto.go index 183fbde814..555a2b798e 100644 --- a/consensus/cbft/types/crypto.go +++ b/consensus/cbft/types/crypto.go @@ -355,3 +355,24 @@ func (v ViewChangeQC) ExistViewChange(epoch, viewNumber uint64, blockHash common func (v *ViewChangeQC) AppendQuorumCert(viewChangeQC *ViewChangeQuorumCert) { v.QCs = append(v.QCs, viewChangeQC) } + +type PrepareQCs struct { + QCs []*QuorumCert `json:"qcs"` +} + +func (p PrepareQCs) FindPrepareQC(hash common.Hash) *QuorumCert { + for _, qc := range p.QCs { + if qc.BlockHash == hash { + return qc + } + } + return nil +} + +func (p PrepareQCs) FlattenMap() map[common.Hash]*QuorumCert { + m := make(map[common.Hash]*QuorumCert) + for _, qc := range p.QCs { + m[qc.BlockHash] = qc + } + return m +} diff --git a/consensus/cbft/types/message.go b/consensus/cbft/types/message.go index 09e03aab83..161419aa39 100644 --- a/consensus/cbft/types/message.go +++ b/consensus/cbft/types/message.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package types import ( @@ -36,6 +35,7 @@ const ( ErrExtraStatusMsg ErrDecode ErrInvalidMsgCode + ErrInvalidRGMsg ErrCbftProtocolVersionMismatch ErrNoStatusMsg ErrForkedBlock @@ -52,6 +52,7 @@ var errorToString = map[int]string{ ErrMsgTooLarge: "Message too long", ErrDecode: "Invalid message", ErrInvalidMsgCode: "Invalid message code", + ErrInvalidRGMsg: "Invalid RG message", ErrCbftProtocolVersionMismatch: "CBFT Protocol version mismatch", ErrNoStatusMsg: "No status message", ErrForkedBlock: "Forked Block", From daacc817196c18183eb0445bb54b6b594ff68c85 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 16 Nov 2021 10:26:57 +0800 Subject: [PATCH 048/299] update for discover --- p2p/dial.go | 2 +- p2p/discover.go | 31 ++++++++++++++----------------- p2p/server.go | 3 +++ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/p2p/dial.go b/p2p/dial.go index 3975502ff5..60ad902abc 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -540,7 +540,7 @@ func (t *dialTask) run(d *dialScheduler) { } func (t *dialTask) needResolve() bool { - return (t.flags&staticDialedConn != 0 || t.flags&consensusDialedConn != 0) && t.dest.IP() == nil + return (t.flags&staticDialedConn != 0) && t.dest.IP() == nil } // resolve attempts to find the current endpoint for the destination diff --git a/p2p/discover.go b/p2p/discover.go index a0832f24ff..3048b7acb7 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -1,15 +1,10 @@ package p2p import ( - "bytes" "context" "sync" "time" - "github.com/pkg/errors" - - "github.com/prysmaticlabs/go-bitfield" - "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/enr" @@ -57,9 +52,10 @@ func (srv *Server) validPeersExist(subnetTopic string) bool { return len(numOfPeers) >= srv.Config.MinimumPeersPerTopic } +/* func (srv *Server) topicToIdx(topic string) uint64 { return 0 -} +}*/ // FindPeersWithTopic performs a network search for peers // subscribed to a particular subnet. Then we try to connect @@ -74,7 +70,7 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, thresho //topic += s.Encoding().ProtocolSuffix() iterator := srv.DiscV5.RandomNodes() - iterator = filterNodes(ctx, iterator, srv.filterPeerForTopic(srv.topicToIdx(topic))) + iterator = filterNodes(ctx, iterator, srv.filterPeerForTopic(topic)) currNum := len(srv.pubSubServer.pubsub.ListPeers(topic)) wg := new(sync.WaitGroup) @@ -101,23 +97,23 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, thresho } // returns a method with filters peers specifically for a particular attestation subnet. -func (srv *Server) filterPeerForTopic(index uint64) func(node *enode.Node) bool { +func (srv *Server) filterPeerForTopic(topic string) func(node *enode.Node) bool { return func(node *enode.Node) bool { if !srv.filterPeer(node) { return false } - topics, err := srv.getTopics(node.Record()) - if err != nil { + srv.topicSubscriberMu.RLock() + defer srv.topicSubscriberMu.RUnlock() + nodes, ok := srv.topicSubscriber[topic] + if !ok { return false } - indExists := false - for _, comIdx := range topics { - if comIdx == index { - indExists = true - break + for _, peer := range nodes { + if peer == node.ID() { + return true } } - return indExists + return false } } @@ -151,6 +147,7 @@ func (srv *Server) filterPeer(node *enode.Node) bool { return true } +/* func (srv *Server) RefreshTopicENR() { // return early if discv5 isnt running if srv.DiscV5 == nil { @@ -225,4 +222,4 @@ func byteCount(bitCount int) int { numOfBytes++ } return numOfBytes -} +}*/ diff --git a/p2p/server.go b/p2p/server.go index 80fb4bef94..91459a841a 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -226,6 +226,9 @@ type Server struct { removeconsensus chan *enode.Node pubSubServer *PubSubServer + + topicSubscriberMu sync.RWMutex + topicSubscriber map[string][]enode.ID } type peerOpFunc func(map[enode.ID]*Peer) From 44de3a09ce490b5337d86e69f7868644da846825 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Tue, 16 Nov 2021 10:37:59 +0800 Subject: [PATCH 049/299] Increase nodeId and size for grouped messages --- consensus/cbft/network/pubsub.go | 12 +++++++++--- p2p/pubsub/topic.go | 14 +++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index 0de4de24e0..87775cf4dc 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -49,7 +49,7 @@ var ( // Group consensus message type RGMsg struct { Code uint64 - Size uint32 // Size of the raw payload + Size int `rlp:"-"` // Size of the raw payload Data interface{} } @@ -164,8 +164,13 @@ func (ps *PubSub) listen(s *pubsub.Subscription) { ps.Cancel(s.Topic()) return } - var p *peer // TODO - ps.onReceive(p, &msgData) + msgData.Size = len(msg.Data) + fromPeer, err := ps.getPeerById(msg.From.TerminalString()) + if err != nil { + log.Error("Failed to execute getPeerById", "err", err) + } else { + ps.onReceive(fromPeer, &msgData) + } } } } @@ -194,5 +199,6 @@ func (ps *PubSub) Publish(topic string, data *RGMsg) error { if err != nil { return err } + return t.Publish(context.Background(), env) } diff --git a/p2p/pubsub/topic.go b/p2p/pubsub/topic.go index cac8328386..136fd3f44f 100644 --- a/p2p/pubsub/topic.go +++ b/p2p/pubsub/topic.go @@ -218,20 +218,20 @@ func (t *Topic) Publish(ctx context.Context, data []byte, opts ...PubOpt) error m := &message.Message{ Data: data, Topic: &t.topic, - From: enode.ZeroID, + From: t.p.host.ID().ID(), Seqno: nil, } - if t.p.signID != enode.ZeroID { + /*if t.p.signID != enode.ZeroID { m.From = t.p.signID m.Seqno = t.p.nextSeqno() - } - if t.p.signKey != nil { - /*m.From = t.p.signID.Bytes() + }*/ + /*if t.p.signKey != nil { + m.From = t.p.signID.Bytes() err := signMessage(t.p.signID, t.p.signKey, m) if err != nil { return err - }*/ - } + } + }*/ pub := &PublishOptions{} for _, opt := range opts { From b08f06dfbc5c720999dced3f95e3245ab73d17cc Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Tue, 16 Nov 2021 10:52:13 +0800 Subject: [PATCH 050/299] Increase the Seqno of the message --- p2p/pubsub/pubsub.go | 6 +++--- p2p/pubsub/topic.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 48ecf55e90..ed60c610b7 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -1109,7 +1109,7 @@ func (p *PubSub) pushMsg(msg *Message) { func (p *PubSub) checkSigningPolicy(msg *Message) error { // reject unsigned messages when strict before we even process the id - if p.signPolicy.mustVerify() { + /*if p.signPolicy.mustVerify() { if p.signPolicy.mustSign() { if msg.Signature == nil { p.tracer.RejectMessage(msg, RejectMissingSignature) @@ -1134,7 +1134,7 @@ func (p *PubSub) checkSigningPolicy(msg *Message) error { } } } - } + }*/ return nil } @@ -1355,4 +1355,4 @@ type RelayCancelFunc func() type addRelayReq struct { topic string resp chan RelayCancelFunc -} \ No newline at end of file +} diff --git a/p2p/pubsub/topic.go b/p2p/pubsub/topic.go index 136fd3f44f..9d00941f92 100644 --- a/p2p/pubsub/topic.go +++ b/p2p/pubsub/topic.go @@ -219,7 +219,7 @@ func (t *Topic) Publish(ctx context.Context, data []byte, opts ...PubOpt) error Data: data, Topic: &t.topic, From: t.p.host.ID().ID(), - Seqno: nil, + Seqno: t.p.nextSeqno(), } /*if t.p.signID != enode.ZeroID { m.From = t.p.signID From 8246076d70fe6fdc5beabc6f8ddfe687ceec15c6 Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 16 Nov 2021 14:20:09 +0800 Subject: [PATCH 051/299] cbft get acitve version --- consensus/bft_mock.go | 2 +- consensus/cbft/cbft.go | 34 ++++++------ consensus/cbft/cbft_test.go | 12 ++--- consensus/cbft/consensus_process.go | 2 +- consensus/cbft/sync_process.go | 4 +- consensus/cbft/validator/validator.go | 62 +++++++++++++--------- consensus/cbft/validator/validator_test.go | 20 +++---- consensus/cbft/wal_bridge.go | 6 +-- consensus/consensus.go | 11 ++-- core/blockchain_cache.go | 7 +++ core/blockchain_reactor.go | 11 +++- core/cbfttypes/type.go | 9 +++- 12 files changed, 107 insertions(+), 73 deletions(-) diff --git a/consensus/bft_mock.go b/consensus/bft_mock.go index 9b04f9f971..d88e8c3898 100644 --- a/consensus/bft_mock.go +++ b/consensus/bft_mock.go @@ -99,7 +99,7 @@ func (bm *BftMock) FastSyncCommitHead(block *types.Block) error { } // Start is a fake interface, no need to implement. -func (bm *BftMock) Start(chain ChainReader, blockCacheWriter BlockCacheWriter, pool TxPoolReset, agency Agency) error { +func (bm *BftMock) Start(chain ChainReader, blockCacheWriter BlockCache, pool TxPoolReset, agency Agency) error { // todo implement me return nil } diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 81c137359c..274b587a36 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -112,19 +112,19 @@ func (e authFailedError) AuthFailed() bool { // Cbft is the core structure of the consensus engine // and is responsible for handling consensus logic. type Cbft struct { - config ctypes.Config - eventMux *event.TypeMux - closeOnce sync.Once - exitCh chan struct{} - txPool consensus.TxPoolReset - blockChain consensus.ChainReader - blockCacheWriter consensus.BlockCacheWriter - peerMsgCh chan *ctypes.MsgInfo - syncMsgCh chan *ctypes.MsgInfo - evPool evidence.EvidencePool - log log.Logger - network *network.EngineManager - pubSub *network.PubSub + config ctypes.Config + eventMux *event.TypeMux + closeOnce sync.Once + exitCh chan struct{} + txPool consensus.TxPoolReset + blockChain consensus.ChainReader + blockCache consensus.BlockCache + peerMsgCh chan *ctypes.MsgInfo + syncMsgCh chan *ctypes.MsgInfo + evPool evidence.EvidencePool + log log.Logger + network *network.EngineManager + pubSub *network.PubSub start int32 syncing int32 @@ -226,12 +226,12 @@ func New(sysConfig *params.CbftConfig, optConfig *ctypes.OptionsConfig, eventMux } // Start starts consensus engine. -func (cbft *Cbft) Start(chain consensus.ChainReader, blockCacheWriter consensus.BlockCacheWriter, txPool consensus.TxPoolReset, agency consensus.Agency) error { +func (cbft *Cbft) Start(chain consensus.ChainReader, blockCache consensus.BlockCache, txPool consensus.TxPoolReset, agency consensus.Agency) error { cbft.log.Info("~ Start cbft consensus") cbft.blockChain = chain cbft.txPool = txPool - cbft.blockCacheWriter = blockCacheWriter - cbft.asyncExecutor = executor.NewAsyncExecutor(blockCacheWriter.Execute) + cbft.blockCache = blockCache + cbft.asyncExecutor = executor.NewAsyncExecutor(blockCache.Execute) //Initialize block tree block := chain.GetBlock(chain.CurrentHeader().Hash(), chain.CurrentHeader().Number.Uint64()) @@ -937,7 +937,7 @@ func (cbft *Cbft) InsertChain(block *types.Block) error { return errors.New("orphan block") } - err = cbft.blockCacheWriter.Execute(block, parent) + err = cbft.blockCache.Execute(block, parent) if err != nil { cbft.log.Error("Executing block failed", "number", block.Number(), "hash", block.Hash(), "parent", parent.Hash(), "parentHash", block.ParentHash(), "err", err) return errors.New("failed to executed block") diff --git a/consensus/cbft/cbft_test.go b/consensus/cbft/cbft_test.go index 672340b48f..298b7598e6 100644 --- a/consensus/cbft/cbft_test.go +++ b/consensus/cbft/cbft_test.go @@ -80,7 +80,7 @@ func TestBls(t *testing.T) { agency := validator.NewStaticAgency(nodes) cbft := &Cbft{ - validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID()), + validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0), config: ctypes.Config{ Option: &ctypes.OptionsConfig{ BlsPriKey: owner, @@ -105,7 +105,7 @@ func TestPrepareBlockBls(t *testing.T) { agency := validator.NewStaticAgency([]params.CbftNode{node}) cbft := &Cbft{ - validatorPool: validator.NewValidatorPool(agency, 0, 0, node.Node.ID()), + validatorPool: validator.NewValidatorPool(agency, 0, 0, node.Node.ID(),false, 0, 0), config: ctypes.Config{ Option: &ctypes.OptionsConfig{ BlsPriKey: owner, @@ -174,7 +174,7 @@ func TestAgg(t *testing.T) { for i := 0; i < num; i++ { cnode[i] = &Cbft{ - validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID()), + validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0), config: ctypes.Config{ Option: &ctypes.OptionsConfig{ BlsPriKey: sk[i], @@ -518,7 +518,7 @@ func testValidatorSwitch(t *testing.T) { _, qqc := nodes[i].engine.blockTree.FindBlockAndQC(qcBlock.Hash(), qcBlock.NumberU64()) assert.NotNil(t, qqc) p := nodes[ii].engine.state.HighestQCBlock() - assert.Nil(t, nodes[ii].engine.blockCacheWriter.Execute(qcBlock, p), fmt.Sprintf("execute block error, parent: %d block: %d", p.NumberU64(), qcBlock.NumberU64())) + assert.Nil(t, nodes[ii].engine.blockCache.Execute(qcBlock, p), fmt.Sprintf("execute block error, parent: %d block: %d", p.NumberU64(), qcBlock.NumberU64())) assert.Nil(t, nodes[ii].engine.OnInsertQCBlock([]*types.Block{qcBlock}, []*ctypes.QuorumCert{qqc})) } @@ -527,7 +527,7 @@ func testValidatorSwitch(t *testing.T) { _, qqc := nodes[i].engine.blockTree.FindBlockAndQC(qcBlock.Hash(), qcBlock.NumberU64()) assert.NotNil(t, qqc) p := switchNode.engine.state.HighestQCBlock() - assert.Nil(t, switchNode.engine.blockCacheWriter.Execute(qcBlock, p), fmt.Sprintf("execute block error, parent: %d block: %d", p.NumberU64(), qcBlock.NumberU64())) + assert.Nil(t, switchNode.engine.blockCache.Execute(qcBlock, p), fmt.Sprintf("execute block error, parent: %d block: %d", p.NumberU64(), qcBlock.NumberU64())) assert.Nil(t, switchNode.engine.OnInsertQCBlock([]*types.Block{qcBlock}, []*ctypes.QuorumCert{qqc})) } } @@ -577,7 +577,7 @@ func testValidatorSwitch(t *testing.T) { _, qqc := nodes[0].engine.blockTree.FindBlockAndQC(qcBlock.Hash(), qcBlock.NumberU64()) assert.NotNil(t, qqc) p := nodes[3].engine.state.HighestQCBlock() - assert.Nil(t, nodes[3].engine.blockCacheWriter.Execute(qcBlock, p), fmt.Sprintf("execute block error, parent: %d block: %d", p.NumberU64(), qcBlock.NumberU64())) + assert.Nil(t, nodes[3].engine.blockCache.Execute(qcBlock, p), fmt.Sprintf("execute block error, parent: %d block: %d", p.NumberU64(), qcBlock.NumberU64())) assert.Nil(t, nodes[3].engine.OnInsertQCBlock([]*types.Block{qcBlock}, []*ctypes.QuorumCert{qqc})) } diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index a55af9baae..847cb04f27 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -1301,7 +1301,7 @@ func (cbft *Cbft) clearInvalidBlocks(newBlock *types.Block) { rollback = append(rollback, block) } } - cbft.blockCacheWriter.ClearCache(cbft.state.HighestCommitBlock()) + cbft.blockCache.ClearCache(cbft.state.HighestCommitBlock()) //todo proposer is myself cbft.txPool.ForkedReset(newHead, rollback) diff --git a/consensus/cbft/sync_process.go b/consensus/cbft/sync_process.go index a2fbcbc665..132cf8b07b 100644 --- a/consensus/cbft/sync_process.go +++ b/consensus/cbft/sync_process.go @@ -87,7 +87,7 @@ func (cbft *Cbft) fetchBlock(id string, hash common.Hash, number uint64, qc *cty "parentHash", parentBlock.Hash(), "parentNumber", parentBlock.NumberU64()) return } - if err := cbft.blockCacheWriter.Execute(block, parentBlock); err != nil { + if err := cbft.blockCache.Execute(block, parentBlock); err != nil { cbft.log.Error("Execute block failed", "hash", block.Hash(), "number", block.NumberU64(), "error", err) return } @@ -166,7 +166,7 @@ func (cbft *Cbft) fetchBlock(id string, hash common.Hash, number uint64, qc *cty // return //} - if err := cbft.blockCacheWriter.Execute(forkedBlock, parentBlock); err != nil { + if err := cbft.blockCache.Execute(forkedBlock, parentBlock); err != nil { cbft.log.Error("Execute forked block failed", "hash", forkedBlock.Hash(), "number", forkedBlock.NumberU64(), "error", err) return } diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index fe705c05f1..5a38e48cf4 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -20,7 +20,6 @@ import ( "encoding/hex" "errors" "fmt" - "github.com/AlayaNetwork/Alaya-Go/x/gov" "sync" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" @@ -101,7 +100,7 @@ func (d *StaticAgency) GetLastNumber(blockNumber uint64) uint64 { return 0 } -func (d *StaticAgency) GetValidator(uint64) (*cbfttypes.Validators, error) { +func (d *StaticAgency) GetValidators(uint64) (*cbfttypes.Validators, error) { return d.validators, nil } @@ -149,7 +148,7 @@ func (d *MockAgency) GetLastNumber(blockNumber uint64) uint64 { return 0 } -func (d *MockAgency) GetValidator(blockNumber uint64) (*cbfttypes.Validators, error) { +func (d *MockAgency) GetValidators(blockNumber uint64) (*cbfttypes.Validators, error) { if blockNumber > d.interval && blockNumber%d.interval == 1 { d.validators.ValidBlockNumber = d.validators.ValidBlockNumber + d.interval + 1 } @@ -205,7 +204,7 @@ func (ia *InnerAgency) GetLastNumber(blockNumber uint64) uint64 { if blockNumber <= ia.defaultBlocksPerRound { lastBlockNumber = ia.defaultBlocksPerRound } else { - vds, err := ia.GetValidator(blockNumber) + vds, err := ia.GetValidators(blockNumber) if err != nil { log.Error("Get validator fail", "blockNumber", blockNumber) return 0 @@ -234,7 +233,7 @@ func (ia *InnerAgency) GetLastNumber(blockNumber uint64) uint64 { return lastBlockNumber } -func (ia *InnerAgency) GetValidator(blockNumber uint64) (v *cbfttypes.Validators, err error) { +func (ia *InnerAgency) GetValidators(blockNumber uint64) (v *cbfttypes.Validators, err error) { defaultValidators := *ia.defaultValidators baseNumber := blockNumber if blockNumber == 0 { @@ -296,43 +295,54 @@ func (ia *InnerAgency) OnCommit(block *types.Block) error { // ValidatorPool a pool storing validators. type ValidatorPool struct { agency consensus.Agency - lock sync.RWMutex // Current node's public key nodeID enode.ID - // The group ID of the current node - groupID uint32 - // Uint ID of the group where the current node is located - unitID uint32 + // A block number which validators switch to current. switchPoint uint64 lastNumber uint64 + // current epoch epoch uint64 - prevValidators *cbfttypes.Validators // Previous validators - currentValidators *cbfttypes.Validators // Current validators - nextValidators *cbfttypes.Validators // Next validators + // Uint ID of the group where the current node is located + unitID uint32 + + // needGroup indicates if validators need grouped + needGroup bool + // max validators in per group + groupValidatorsLimit int + // coordinator limit + coordinatorLimit int + + prevValidators *cbfttypes.Validators // Previous round validators + currentValidators *cbfttypes.Validators // Current round validators + nextValidators *cbfttypes.Validators // Next round validators } // NewValidatorPool new a validator pool. -func NewValidatorPool(agency consensus.Agency, blockNumber uint64, epoch uint64, nodeID enode.ID) *ValidatorPool { +func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID enode.ID, + needGroup bool, groupValidatorsLimit, coordinatorLimit int) *ValidatorPool { pool := &ValidatorPool{ agency: agency, nodeID: nodeID, epoch: epoch, + needGroup: needGroup, + groupValidatorsLimit: groupValidatorsLimit, + coordinatorLimit: coordinatorLimit, } - // FIXME: Check `GetValidator` return error + // FIXME: Check `GetValidators` return error if agency.GetLastNumber(blockNumber) == blockNumber { - pool.prevValidators, _ = agency.GetValidator(blockNumber) - pool.currentValidators, _ = agency.GetValidator(NextRound(blockNumber)) + pool.prevValidators, _ = agency.GetValidators(blockNumber) + pool.currentValidators, _ = agency.GetValidators(NextRound(blockNumber)) pool.lastNumber = agency.GetLastNumber(NextRound(blockNumber)) if blockNumber != 0 { pool.epoch += 1 } } else { - pool.currentValidators, _ = agency.GetValidator(blockNumber) + pool.currentValidators, _ = agency.GetValidators(blockNumber) pool.prevValidators = pool.currentValidators pool.lastNumber = agency.GetLastNumber(blockNumber) } @@ -343,8 +353,8 @@ func NewValidatorPool(agency consensus.Agency, blockNumber uint64, epoch uint64, if pool.currentValidators.ValidBlockNumber > 0 { pool.switchPoint = pool.currentValidators.ValidBlockNumber - 1 } - if gov.Gte0140VersionState(state) { - pool.groupID = pool.currentValidators.GroupID(nodeID) + if(needGroup) { + pool.prevValidators.Grouped(groupValidatorsLimit,coordinatorLimit) pool.unitID = pool.currentValidators.UnitID(nodeID) } log.Debug("Update validator", "validators", pool.currentValidators.String(), "switchpoint", pool.switchPoint, "epoch", pool.epoch, "lastNumber", pool.lastNumber) @@ -354,12 +364,12 @@ func NewValidatorPool(agency consensus.Agency, blockNumber uint64, epoch uint64, // Reset reset validator pool. func (vp *ValidatorPool) Reset(blockNumber uint64, epoch uint64) { if vp.agency.GetLastNumber(blockNumber) == blockNumber { - vp.prevValidators, _ = vp.agency.GetValidator(blockNumber) - vp.currentValidators, _ = vp.agency.GetValidator(NextRound(blockNumber)) + vp.prevValidators, _ = vp.agency.GetValidators(blockNumber) + vp.currentValidators, _ = vp.agency.GetValidators(NextRound(blockNumber)) vp.lastNumber = vp.agency.GetLastNumber(NextRound(blockNumber)) vp.epoch = epoch + 1 } else { - vp.currentValidators, _ = vp.agency.GetValidator(blockNumber) + vp.currentValidators, _ = vp.agency.GetValidators(blockNumber) vp.prevValidators = vp.currentValidators vp.lastNumber = vp.agency.GetLastNumber(blockNumber) vp.epoch = epoch @@ -400,7 +410,7 @@ func (vp *ValidatorPool) MockSwitchPoint(number uint64) { } // Update switch validators. -func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *event.TypeMux) error { +func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *event.TypeMux, ) error { vp.lock.Lock() defer vp.lock.Unlock() @@ -410,7 +420,7 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *even return errors.New("already updated before") } - nds, err := vp.agency.GetValidator(NextRound(blockNumber)) + nds, err := vp.agency.GetValidators(NextRound(blockNumber)) if err != nil { log.Error("Get validator error", "blockNumber", blockNumber, "err", err) return err @@ -712,7 +722,7 @@ func (vp *ValidatorPool) GetGroupID(epoch uint64, nodeID enode.ID) (uint32, erro if epoch+1 == vp.epoch { return vp.prevValidators.GroupID(nodeID), nil } - return vp.groupID, nil + return vp.currentValidators.GroupID(nodeID), nil } // GroupID return current node's GroupID according epoch diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index 02797285ce..b5e81e3a66 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -215,7 +215,7 @@ func TestStaticAgency(t *testing.T) { vds := newValidators(nodes, 0) agency := NewStaticAgency(nodes) - validators, err := agency.GetValidator(0) + validators, err := agency.GetValidators(0) assert.True(t, err == nil) assert.Equal(t, *vds, *validators) assert.True(t, agency.Sign(nil) == nil) @@ -332,12 +332,12 @@ func TestInnerAgency(t *testing.T) { assert.True(t, agency.GetLastNumber(81) == 120) assert.True(t, agency.GetLastNumber(110) == 120) - validators, err := agency.GetValidator(0) + validators, err := agency.GetValidators(0) assert.True(t, err == nil) assert.Equal(t, *vds, *validators) assert.True(t, blockchain.Genesis() != nil) - newVds, err := agency.GetValidator(81) + newVds, err := agency.GetValidators(81) assert.True(t, err == nil) assert.True(t, newVds.Len() == 4) assert.True(t, newVds.ValidBlockNumber == 81) @@ -351,7 +351,7 @@ func TestInnerAgency(t *testing.T) { assert.True(t, newVds.String() != "") assert.False(t, newVds.Equal(validators)) - defaultVds, _ := agency.GetValidator(60) + defaultVds, _ := agency.GetValidators(60) assert.True(t, defaultVds.Equal(validators)) assert.True(t, agency.GetLastNumber(120) == 120) @@ -447,7 +447,7 @@ func TestValidatorPool(t *testing.T) { nodes := newTestNode() agency := newTestInnerAgency(nodes) - validatorPool := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID()) + validatorPool := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0) assert.False(t, validatorPool.ShouldSwitch(0)) assert.True(t, validatorPool.ShouldSwitch(40)) @@ -533,7 +533,7 @@ func TestValidatorPoolVerify(t *testing.T) { nodes = append(nodes, params.CbftNode{Node: n4, BlsPubKey: *sec4.GetPublicKey()}) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID()) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0) m := "test sig" @@ -594,7 +594,7 @@ func (m *mockAgency) VerifyHeader(*types.Header, *state.StateDB) error { return func (m *mockAgency) GetLastNumber(blockNumber uint64) uint64 { return m.lastNumber } -func (m *mockAgency) GetValidator(blockNumber uint64) (*cbfttypes.Validators, error) { +func (m *mockAgency) GetValidators(blockNumber uint64) (*cbfttypes.Validators, error) { return &cbfttypes.Validators{ ValidBlockNumber: blockNumber, }, nil @@ -606,7 +606,7 @@ func (m *mockAgency) OnCommit(block *types.Block) error { return nil } func TestValidatorPoolReset(t *testing.T) { agency := newMockAgency(100) - vp := NewValidatorPool(agency, 0, 0, enode.ID{}) + vp := NewValidatorPool(agency, 0, 0, enode.ID{},false, 0, 0) vp.Reset(100, 10) assert.Equal(t, vp.switchPoint, uint64(100)) @@ -646,7 +646,7 @@ func TestGetGroupID(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNodeByNum(100) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID()) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0) grpID,_ := vp.GetGroupID(0, nodes[0].Node.ID()) assert.Equal(t, 0, grpID) @@ -656,7 +656,7 @@ func TestGetUintID(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNodeByNum(100) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID()) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0) untID,_ := vp.GetGroupID(0, nodes[0].Node.ID()) assert.Equal(t, 0, untID) diff --git a/consensus/cbft/wal_bridge.go b/consensus/cbft/wal_bridge.go index f5b408ed83..a1b1583008 100644 --- a/consensus/cbft/wal_bridge.go +++ b/consensus/cbft/wal_bridge.go @@ -297,7 +297,7 @@ func (cbft *Cbft) recoveryCommitState(commit *protocols.State, parent *types.Blo return err } commit.Block.SetExtraData(extra) - if err := cbft.blockCacheWriter.WriteBlock(commit.Block); err != nil { + if err := cbft.blockCache.WriteBlock(commit.Block); err != nil { return err } if err := cbft.validatorPool.Commit(commit.Block); err != nil { @@ -459,7 +459,7 @@ func contiguousChainBlock(p *types.Block, s *types.Block) bool { return contiguous } -// executeBlock call blockCacheWriter to execute block. +// executeBlock call blockCache to execute block. func (cbft *Cbft) executeBlock(block *types.Block, parent *types.Block, index uint32) error { if parent == nil { if parent, _ = cbft.blockTree.FindBlockAndQC(block.ParentHash(), block.NumberU64()-1); parent == nil { @@ -468,7 +468,7 @@ func (cbft *Cbft) executeBlock(block *types.Block, parent *types.Block, index ui } } } - if err := cbft.blockCacheWriter.Execute(block, parent); err != nil { + if err := cbft.blockCache.Execute(block, parent); err != nil { return fmt.Errorf("execute block failed, blockNum:%d, blockHash:%s, parentNum:%d, parentHash:%s, err:%s", block.NumberU64(), block.Hash().String(), parent.NumberU64(), parent.Hash().String(), err.Error()) } return nil diff --git a/consensus/consensus.go b/consensus/consensus.go index a1fd4d99fa..184d364f70 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -62,12 +62,15 @@ type TxPoolReset interface { Reset(newBlock *types.Block) } -// BlockCacheWriter executions block, you need to pass in the parent +// BlockCache executions block, you need to pass in the parent // block to find the parent block state -type BlockCacheWriter interface { +type BlockCache interface { Execute(block *types.Block, parent *types.Block) error ClearCache(block *types.Block) WriteBlock(block *types.Block) error + + // CurrentActiveVersion return current gov version + GetActiveVersion(sealhash common.Hash) uint32 } // Engine is an algorithm agnostic consensus engine. @@ -161,7 +164,7 @@ type Agency interface { Flush(header *types.Header) error VerifyHeader(header *types.Header, stateDB *state.StateDB) error GetLastNumber(blockNumber uint64) uint64 - GetValidator(blockNumber uint64) (*cbfttypes.Validators, error) + GetValidators(blockNumber uint64) (*cbfttypes.Validators, error) IsCandidateNode(nodeID enode.IDv0) bool OnCommit(block *types.Block) error } @@ -171,7 +174,7 @@ type Agency interface { type Bft interface { Engine - Start(chain ChainReader, blockCacheWriter BlockCacheWriter, pool TxPoolReset, agency Agency) error + Start(chain ChainReader, blockCache BlockCache, pool TxPoolReset, agency Agency) error // Returns the current consensus node address list. ConsensusNodes() ([]enode.ID, error) diff --git a/core/blockchain_cache.go b/core/blockchain_cache.go index 47fd297f4b..ae566e4491 100644 --- a/core/blockchain_cache.go +++ b/core/blockchain_cache.go @@ -19,6 +19,7 @@ package core import ( "errors" "fmt" + "github.com/AlayaNetwork/Alaya-Go/x/gov" "sort" "sync" "time" @@ -348,6 +349,12 @@ func (bcc *BlockChainCache) WriteBlock(block *types.Block) error { return nil } +// CurrentActiveVersion return current gov version +func (bcc *BlockChainCache) GetActiveVersion(sealHash common.Hash) uint32 { + state := bcc.ReadStateDB(sealHash) + return gov.GetCurrentActiveVersion(state) +} + type sealHashNumber struct { number uint64 hash common.Hash diff --git a/core/blockchain_reactor.go b/core/blockchain_reactor.go index e58c0576f8..880b7f027e 100644 --- a/core/blockchain_reactor.go +++ b/core/blockchain_reactor.go @@ -390,10 +390,19 @@ func (bcr *BlockChainReactor) GetLastNumber(blockNumber uint64) uint64 { return plugin.StakingInstance().GetLastNumber(blockNumber) } -func (bcr *BlockChainReactor) GetValidator(blockNumber uint64) (*cbfttypes.Validators, error) { +func (bcr *BlockChainReactor) GetValidators(blockNumber uint64) (*cbfttypes.Validators, error) { return plugin.StakingInstance().GetValidator(blockNumber) } +func (bcr *BlockChainReactor) GetGroupedValidator(blockNumber uint64, groupValidatorsLimit int, coordinatorLimit int) (*cbfttypes.Validators, error) { + vds, err := plugin.StakingInstance().GetValidator(blockNumber) + if nil != err { + return nil, err + } + vds.Grouped(groupValidatorsLimit,coordinatorLimit) + return vds, nil +} + func (bcr *BlockChainReactor) IsCandidateNode(nodeID enode.IDv0) bool { return plugin.StakingInstance().IsCandidateNode(nodeID) } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 65ed1f66bd..b7a7e38dbe 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -94,8 +94,12 @@ func (sv SortedIndexValidatorNode) Swap(i, j int) { sv[i], sv[j] = sv[j], s func (sv SortedIndexValidatorNode) Less(i, j int) bool { return sv[i].Index < sv[j].Index } type GroupValidators struct { + // all nodes in this group Nodes []*ValidateNode + // Coordinators' index C0>C1>C2>C3... Units [][]uint32 + // The group ID + groupID uint32 } type Validators struct { @@ -104,6 +108,7 @@ type Validators struct { // Sorting based on node index SortedNodes SortedIndexValidatorNode `json:"sortedNodes"` + //// Sorting based on node index // Node grouping info GroupNodes []*GroupValidators `json:"groupNodes"` @@ -294,7 +299,7 @@ func (vs *Validators) UnitID(nodeID enode.ID) uint32 { return unitID } -func (gvs *GroupValidators) GroupedUnits(coordinatorLimit int) error { +func (gvs *GroupValidators) GroupedUnits(coordinatorLimit int) { unit := make([]uint32, 0, coordinatorLimit) for i, n := range gvs.Nodes { unit = append(unit, n.Index) @@ -303,7 +308,6 @@ func (gvs *GroupValidators) GroupedUnits(coordinatorLimit int) error { unit = make([]uint32, 0, coordinatorLimit) } } - return nil } // Grouped fill validators into groups @@ -341,6 +345,7 @@ func (vs *Validators) Grouped(groupValidatorsLimit int, coordinatorLimit int) er } groupValidators := new(GroupValidators) groupValidators.Nodes = vs.SortedNodes[begin:end] + groupValidators.groupID = uint32(i) vs.GroupNodes[i] = groupValidators } From 4ed044dbdbbba67f9ad94b6ac955fd48d3dcdd83 Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 16 Nov 2021 18:50:58 +0800 Subject: [PATCH 052/299] add grouped topic event --- consensus/cbft/cbft.go | 9 ++++-- consensus/cbft/cbft_common_util.go | 2 ++ consensus/cbft/consensus_process.go | 12 +++++++ consensus/cbft/validator/validator.go | 37 ++++++++++++++++++---- consensus/cbft/validator/validator_test.go | 20 ++++++------ core/blockchain_reactor.go | 9 ------ core/cbfttypes/type.go | 24 +++++++++----- params/config.go | 2 ++ 8 files changed, 78 insertions(+), 37 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 1a125fa6ac..2dc765946b 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -278,11 +278,14 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCache consensus.BlockC cbft.config.Option.NodeID = cbft.config.Option.Node.IDv0() cbft.config.Option.NodePriKey = cbft.nodeServiceContext.NodePriKey() } + needGroup := blockCache.GetActiveVersion(block.Header().SealHash()) >= params.FORKVERSION_0_17_0 + groupValidatorsLimit := cbft.config.Sys.GroupValidatorsLimit + coordinatorLimit := cbft.config.Sys.CoordinatorLimit if isGenesis() { - cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), cstate.DefaultEpoch, cbft.config.Option.Node.ID()) + cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), cstate.DefaultEpoch, cbft.config.Option.Node.ID(), needGroup, groupValidatorsLimit, coordinatorLimit, cbft.eventMux) cbft.changeView(cstate.DefaultEpoch, cstate.DefaultViewNumber, block, qc, nil) } else { - cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), qc.Epoch, cbft.config.Option.Node.ID()) + cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), qc.Epoch, cbft.config.Option.Node.ID(), needGroup, groupValidatorsLimit, coordinatorLimit, cbft.eventMux) cbft.changeView(qc.Epoch, qc.ViewNumber, block, qc, nil) } @@ -1086,7 +1089,7 @@ func (cbft *Cbft) FastSyncCommitHead(block *types.Block) error { return } - cbft.validatorPool.Reset(block.NumberU64(), qc.Epoch) + cbft.validatorPool.Reset(block.NumberU64(), qc.Epoch, cbft.eventMux) cbft.blockTree.Reset(block, qc) cbft.changeView(qc.Epoch, qc.ViewNumber, block, qc, nil) diff --git a/consensus/cbft/cbft_common_util.go b/consensus/cbft/cbft_common_util.go index f87589edda..ecccf5104c 100644 --- a/consensus/cbft/cbft_common_util.go +++ b/consensus/cbft/cbft_common_util.go @@ -134,6 +134,8 @@ func CreateCBFT(pk *ecdsa.PrivateKey, sk *bls.SecretKey, period uint64, amount u Period: period, Amount: amount, InitialNodes: []params.CbftNode{}, + GroupValidatorsLimit: 25, + CoordinatorLimit: 5, } pnode := enode.NewV4(&pk.PublicKey, nil, 0, 0) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index cf2926601a..aabc624ab7 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -19,6 +19,7 @@ package cbft import ( "fmt" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/network" + "github.com/AlayaNetwork/Alaya-Go/params" "time" "github.com/pkg/errors" @@ -1091,6 +1092,12 @@ func (cbft *Cbft) tryChangeView() { (qc != nil && qc.Epoch == cbft.state.Epoch() && shouldSwitch) }() + // should grouped according max commit block's state + shouldGroup := func() bool { + activeVersion := cbft.blockCache.GetActiveVersion(cbft.state.HighestCommitBlock().Header().SealHash()) + return cbft.validatorPool.NeedGroup() || activeVersion >= params.FORKVERSION_0_17_0 + } + if shouldSwitch { if err := cbft.validatorPool.Update(block.NumberU64(), cbft.state.Epoch()+1, cbft.eventMux); err == nil { cbft.log.Info("Update validator success", "number", block.NumberU64()) @@ -1110,6 +1117,11 @@ func (cbft *Cbft) tryChangeView() { return } + // TODO: get groupvalidatorslimit and coordinatorlimit from gov + if shouldGroup() { + cbft.validatorPool.SetupGroup(true, 0, 0) + } + threshold := cbft.threshold(cbft.currentValidatorLen()) var ( enoughViewChangesFlag bool diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 5a38e48cf4..711e790425 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -313,9 +313,9 @@ type ValidatorPool struct { // needGroup indicates if validators need grouped needGroup bool // max validators in per group - groupValidatorsLimit int + groupValidatorsLimit uint8 // coordinator limit - coordinatorLimit int + coordinatorLimit uint8 prevValidators *cbfttypes.Validators // Previous round validators currentValidators *cbfttypes.Validators // Current round validators @@ -323,8 +323,8 @@ type ValidatorPool struct { } // NewValidatorPool new a validator pool. -func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID enode.ID, - needGroup bool, groupValidatorsLimit, coordinatorLimit int) *ValidatorPool { +func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID enode.ID, needGroup bool, + groupValidatorsLimit, coordinatorLimit uint8, eventMux *event.TypeMux) *ValidatorPool { pool := &ValidatorPool{ agency: agency, nodeID: nodeID, @@ -354,7 +354,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID pool.switchPoint = pool.currentValidators.ValidBlockNumber - 1 } if(needGroup) { - pool.prevValidators.Grouped(groupValidatorsLimit,coordinatorLimit) + pool.prevValidators.Grouped(groupValidatorsLimit, coordinatorLimit, eventMux, epoch) pool.unitID = pool.currentValidators.UnitID(nodeID) } log.Debug("Update validator", "validators", pool.currentValidators.String(), "switchpoint", pool.switchPoint, "epoch", pool.epoch, "lastNumber", pool.lastNumber) @@ -362,7 +362,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID } // Reset reset validator pool. -func (vp *ValidatorPool) Reset(blockNumber uint64, epoch uint64) { +func (vp *ValidatorPool) Reset(blockNumber uint64, epoch uint64, eventMux *event.TypeMux) { if vp.agency.GetLastNumber(blockNumber) == blockNumber { vp.prevValidators, _ = vp.agency.GetValidators(blockNumber) vp.currentValidators, _ = vp.agency.GetValidators(NextRound(blockNumber)) @@ -377,6 +377,10 @@ func (vp *ValidatorPool) Reset(blockNumber uint64, epoch uint64) { if vp.currentValidators.ValidBlockNumber > 0 { vp.switchPoint = vp.currentValidators.ValidBlockNumber - 1 } + if vp.needGroup { + vp.currentValidators.Grouped(vp.groupValidatorsLimit, vp.coordinatorLimit, eventMux, epoch) + vp.unitID = vp.currentValidators.UnitID(vp.nodeID) + } log.Debug("Update validator", "validators", vp.currentValidators.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) } @@ -410,7 +414,7 @@ func (vp *ValidatorPool) MockSwitchPoint(number uint64) { } // Update switch validators. -func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *event.TypeMux, ) error { +func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *event.TypeMux) error { vp.lock.Lock() defer vp.lock.Unlock() @@ -425,6 +429,10 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *even log.Error("Get validator error", "blockNumber", blockNumber, "err", err) return err } + if vp.needGroup { + nds.Grouped(vp.groupValidatorsLimit, vp.coordinatorLimit, eventMux, epoch) + vp.unitID = nds.UnitID(vp.nodeID) + } vp.prevValidators = vp.currentValidators vp.currentValidators = nds vp.switchPoint = nds.ValidBlockNumber - 1 @@ -479,6 +487,16 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *even return nil } +// SetupGroup update validatorpool group info +func (vp *ValidatorPool) SetupGroup(needGroup bool, groupValidatorsLimit, coordinatorLimit uint8) { + vp.lock.Lock() + defer vp.lock.Unlock() + + vp.needGroup = needGroup + vp.groupValidatorsLimit = groupValidatorsLimit + vp.coordinatorLimit = coordinatorLimit +} + // GetValidatorByNodeID get the validator by node id. func (vp *ValidatorPool) GetValidatorByNodeID(epoch uint64, nodeID enode.ID) (*cbfttypes.ValidateNode, error) { vp.lock.RLock() @@ -714,6 +732,11 @@ func (vp *ValidatorPool) GetGroupNodes(epoch uint64) []*cbfttypes.GroupValidator return vp.currentValidators.GroupNodes } +// NeedGroup return if currentValidators need grouped +func (vp *ValidatorPool) NeedGroup() bool { + return vp.needGroup +} + // GetGroupID return GroupID according epoch & NodeID func (vp *ValidatorPool) GetGroupID(epoch uint64, nodeID enode.ID) (uint32, error) { if epoch > vp.epoch { diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index 99fce043cf..0a8da3c3a8 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -447,7 +447,7 @@ func TestValidatorPool(t *testing.T) { nodes := newTestNode() agency := newTestInnerAgency(nodes) - validatorPool := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0) + validatorPool := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0, nil) assert.False(t, validatorPool.ShouldSwitch(0)) assert.True(t, validatorPool.ShouldSwitch(40)) @@ -533,7 +533,7 @@ func TestValidatorPoolVerify(t *testing.T) { nodes = append(nodes, params.CbftNode{Node: n4, BlsPubKey: *sec4.GetPublicKey()}) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0, nil) m := "test sig" @@ -606,14 +606,14 @@ func (m *mockAgency) OnCommit(block *types.Block) error { return nil } func TestValidatorPoolReset(t *testing.T) { agency := newMockAgency(100) - vp := NewValidatorPool(agency, 0, 0, enode.ID{},false, 0, 0) + vp := NewValidatorPool(agency, 0, 0, enode.ID{},false, 0, 0, nil) - vp.Reset(100, 10) + vp.Reset(100, 10, nil) assert.Equal(t, vp.switchPoint, uint64(100)) assert.Equal(t, vp.epoch, uint64(11)) agency.lastNumber = 200 - vp.Reset(150, 15) + vp.Reset(150, 15, nil) assert.Equal(t, vp.epoch, uint64(15)) assert.Equal(t, vp.switchPoint, uint64(149)) } @@ -621,21 +621,21 @@ func TestValidatorPoolReset(t *testing.T) { func TestValidatorGrouped(t *testing.T) { nodes := newTestNodeByNum(100) vs := newValidators(nodes, 0) - vs.Grouped(25, 5) + vs.Grouped(25, 5, nil, 0) assert.Equal(t, 4, len(vs.GroupNodes)) assert.Equal(t, 25, len(vs.GroupNodes[3].Nodes)) assert.Equal(t, uint32(74), vs.GroupNodes[2].Nodes[24].Index) assert.Equal(t, 5, len(vs.GroupNodes[2].Units)) assert.Equal(t, uint32(74), vs.GroupNodes[2].Units[4][4]) - vs.Grouped(28, 5) + vs.Grouped(28, 5, nil, 0) assert.Equal(t, 4, len(vs.GroupNodes)) assert.Equal(t, 25, len(vs.GroupNodes[0].Nodes)) assert.Equal(t, 25, len(vs.GroupNodes[2].Nodes)) assert.Equal(t, 25, len(vs.GroupNodes[3].Nodes)) assert.Equal(t, uint32(79), vs.GroupNodes[3].Units[0][4]) - vs.Grouped(19, 3) + vs.Grouped(19, 3, nil, 0) assert.Equal(t, 6, len(vs.GroupNodes)) assert.Equal(t, 17, len(vs.GroupNodes[0].Nodes)) assert.Equal(t, 6, len(vs.GroupNodes[2].Units)) @@ -646,7 +646,7 @@ func TestGetGroupID(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNodeByNum(100) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0, nil) grpID, _ := vp.GetGroupID(0, nodes[0].Node.ID()) assert.Equal(t, 0, grpID) @@ -656,7 +656,7 @@ func TestGetUintID(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNodeByNum(100) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0, nil) untID, _ := vp.GetGroupID(0, nodes[0].Node.ID()) assert.Equal(t, 0, untID) diff --git a/core/blockchain_reactor.go b/core/blockchain_reactor.go index 880b7f027e..58021cf9fb 100644 --- a/core/blockchain_reactor.go +++ b/core/blockchain_reactor.go @@ -394,15 +394,6 @@ func (bcr *BlockChainReactor) GetValidators(blockNumber uint64) (*cbfttypes.Vali return plugin.StakingInstance().GetValidator(blockNumber) } -func (bcr *BlockChainReactor) GetGroupedValidator(blockNumber uint64, groupValidatorsLimit int, coordinatorLimit int) (*cbfttypes.Validators, error) { - vds, err := plugin.StakingInstance().GetValidator(blockNumber) - if nil != err { - return nil, err - } - vds.Grouped(groupValidatorsLimit,coordinatorLimit) - return vds, nil -} - func (bcr *BlockChainReactor) IsCandidateNode(nodeID enode.IDv0) bool { return plugin.StakingInstance().IsCandidateNode(nodeID) } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 2f995e1eca..b6a7364cb2 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -22,6 +22,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/log" "math" "sort" @@ -108,6 +109,11 @@ type Validators struct { GroupNodes []*GroupValidators `json:"groupNodes"` } +type NewGroupsEvent struct { + Topic string // consensus:{epoch}:{groupID} + Validators *GroupValidators +} + func (vn *ValidateNode) String() string { b, _ := json.Marshal(vn) return string(b) @@ -293,11 +299,11 @@ func (vs *Validators) UnitID(nodeID enode.ID) uint32 { return unitID } -func (gvs *GroupValidators) GroupedUnits(coordinatorLimit int) { +func (gvs *GroupValidators) GroupedUnits(coordinatorLimit uint8) { unit := make([]uint32, 0, coordinatorLimit) for i, n := range gvs.Nodes { unit = append(unit, n.Index) - if len(unit) >= coordinatorLimit || i == len(gvs.Nodes)-1 { + if uint8(len(unit)) >= coordinatorLimit || i == len(gvs.Nodes)-1 { gvs.Units = append(gvs.Units, unit) unit = make([]uint32, 0, coordinatorLimit) } @@ -308,13 +314,13 @@ func (gvs *GroupValidators) GroupedUnits(coordinatorLimit int) { // groupValidatorsLimit is a factor to determine how many groups are grouped // eg: [validatorCount,groupValidatorsLimit]= // [50,25] = 25,25;[43,25] = 22,21; [101,25] = 21,20,20,20,20 -func (vs *Validators) Grouped(groupValidatorsLimit int, coordinatorLimit int) error { +func (vs *Validators) Grouped(groupValidatorsLimit uint8, coordinatorLimit uint8, eventMux *event.TypeMux, epoch uint64) error { // sort nodes by index if len(vs.SortedNodes) == 0 { vs.Sort() } - validatorCount := vs.SortedNodes.Len() + validatorCount := uint8(vs.SortedNodes.Len()) groupNum := validatorCount / groupValidatorsLimit mod := validatorCount % groupValidatorsLimit if mod > 0 { @@ -324,9 +330,9 @@ func (vs *Validators) Grouped(groupValidatorsLimit int, coordinatorLimit int) er memberMinCount := validatorCount / groupNum remainder := validatorCount % groupNum vs.GroupNodes = make([]*GroupValidators, groupNum, groupNum) - begin := 0 - end := 0 - for i := 0; i < groupNum; i++ { + begin := uint8(0) + end := uint8(0) + for i := uint8(0); i < groupNum; i++ { begin = end if remainder > 0 { end = begin + memberMinCount + 1 @@ -344,8 +350,10 @@ func (vs *Validators) Grouped(groupValidatorsLimit int, coordinatorLimit int) er } // fill group unit - for _, gvs := range vs.GroupNodes { + for i, gvs := range vs.GroupNodes { gvs.GroupedUnits(coordinatorLimit) + topic := ConsensusGroupTopicName(epoch, uint32(i)) + eventMux.Post(NewGroupsEvent{Topic:topic, Validators: gvs}) } return nil } diff --git a/params/config.go b/params/config.go index 8b4f4d2707..0dd5d82223 100644 --- a/params/config.go +++ b/params/config.go @@ -195,6 +195,8 @@ type CbftConfig struct { Amount uint32 `json:"amount,omitempty"` //The maximum number of blocks generated per cycle InitialNodes []CbftNode `json:"initialNodes,omitempty"` //Genesis consensus node ValidatorMode string `json:"validatorMode,omitempty"` //Validator mode for easy testing + GroupValidatorsLimit uint8 `json:"GroupValidatorsLimit,omitempty"` //Max validators per group + CoordinatorLimit uint8 `json:"CoordinatorLimit,omitempty"` //Coordinators Limit C0>C1>C2... } // CliqueConfig is the consensus engine configs for proof-of-authority based sealing. From 20a5d1113473e7acda392e1155cb8d8550b22fd9 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 17 Nov 2021 17:52:55 +0800 Subject: [PATCH 053/299] update validatorspool --- consensus/cbft/consensus_process.go | 2 +- consensus/cbft/validator/validator.go | 139 +++++++++++++------------- core/cbfttypes/type.go | 35 +++++-- params/config.go | 4 +- 4 files changed, 98 insertions(+), 82 deletions(-) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index aabc624ab7..0f39500da9 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -244,7 +244,7 @@ func (cbft *Cbft) enoughSigns(epoch uint64, groupID uint32, signs int) bool { // Determine whether the signer of the RGBlockQuorumCert message is a member of the group func (cbft *Cbft) isGroupMember(epoch uint64, groupID uint32, nodeIndex uint32) bool { - if v, err := cbft.validatorPool.GetValidatorByGroupIdAndIndex(epoch, groupID, nodeIndex); err != nil || v == nil { + if v,err := cbft.validatorPool.GetValidatorByGroupIdAndIndex(epoch, nodeIndex); err != nil || v == nil { return false } return true diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 711e790425..0305ef51b2 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -313,9 +313,9 @@ type ValidatorPool struct { // needGroup indicates if validators need grouped needGroup bool // max validators in per group - groupValidatorsLimit uint8 + groupValidatorsLimit uint32 // coordinator limit - coordinatorLimit uint8 + coordinatorLimit uint32 prevValidators *cbfttypes.Validators // Previous round validators currentValidators *cbfttypes.Validators // Current round validators @@ -323,8 +323,7 @@ type ValidatorPool struct { } // NewValidatorPool new a validator pool. -func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID enode.ID, needGroup bool, - groupValidatorsLimit, coordinatorLimit uint8, eventMux *event.TypeMux) *ValidatorPool { +func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID enode.ID, needGroup bool, groupValidatorsLimit, coordinatorLimit uint32, eventMux *event.TypeMux) *ValidatorPool { pool := &ValidatorPool{ agency: agency, nodeID: nodeID, @@ -439,56 +438,11 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *even vp.lastNumber = vp.agency.GetLastNumber(NextRound(blockNumber)) vp.epoch = epoch log.Info("Update validator", "validators", nds.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) - - isValidatorBefore := vp.isValidator(epoch-1, vp.nodeID) - - isValidatorAfter := vp.isValidator(epoch, vp.nodeID) - - if isValidatorBefore { - // If we are still a consensus node, that adding - // new validators as consensus peer, and removing - // validators. Added as consensus peersis because - // we need to keep connect with other validators - // in the consensus stages. Also we are not needed - // to keep connect with old validators. - if isValidatorAfter { - for nodeID, vnode := range vp.currentValidators.Nodes { - if node, _ := vp.prevValidators.FindNodeByID(nodeID); node == nil { - eventMux.Post(cbfttypes.AddValidatorEvent{Node: enode.NewV4(vnode.PubKey, nil, 0, 0)}) - log.Trace("Post AddValidatorEvent", "nodeID", nodeID.String()) - } - } - - for nodeID, vnode := range vp.prevValidators.Nodes { - if node, _ := vp.currentValidators.FindNodeByID(nodeID); node == nil { - eventMux.Post(cbfttypes.RemoveValidatorEvent{Node: enode.NewV4(vnode.PubKey, nil, 0, 0)}) - log.Trace("Post RemoveValidatorEvent", "nodeID", nodeID.String()) - } - } - } else { - for nodeID, vnode := range vp.prevValidators.Nodes { - eventMux.Post(cbfttypes.RemoveValidatorEvent{Node: enode.NewV4(vnode.PubKey, nil, 0, 0)}) - log.Trace("Post RemoveValidatorEvent", "nodeID", nodeID.String()) - } - } - } else { - // We are become a consensus node, that adding all - // validators as consensus peer except us. Added as - // consensus peers is because we need to keep connecting - // with other validators in the consensus stages. - if isValidatorAfter { - for nodeID, vnode := range vp.currentValidators.Nodes { - eventMux.Post(cbfttypes.AddValidatorEvent{Node: enode.NewV4(vnode.PubKey, nil, 0, 0)}) - log.Trace("Post AddValidatorEvent", "nodeID", nodeID.String()) - } - } - } - return nil } // SetupGroup update validatorpool group info -func (vp *ValidatorPool) SetupGroup(needGroup bool, groupValidatorsLimit, coordinatorLimit uint8) { +func (vp *ValidatorPool) SetupGroup(needGroup bool, groupValidatorsLimit, coordinatorLimit uint32) { vp.lock.Lock() defer vp.lock.Unlock() @@ -587,6 +541,9 @@ func (vp *ValidatorPool) validatorList(epoch uint64) []enode.ID { } func (vp *ValidatorPool) Validators(epoch uint64) *cbfttypes.Validators { + vp.lock.RLock() + defer vp.lock.RUnlock() + if vp.epochToBlockNumber(epoch) <= vp.switchPoint { return vp.prevValidators } @@ -605,9 +562,6 @@ func (vp *ValidatorPool) VerifyHeader(header *types.Header) error { // IsValidator check if the node is validator. func (vp *ValidatorPool) IsValidator(epoch uint64, nodeID enode.ID) bool { - vp.lock.RLock() - defer vp.lock.RUnlock() - return vp.isValidator(epoch, nodeID) } @@ -723,6 +677,9 @@ func (vp *ValidatorPool) Commit(block *types.Block) error { // GetGroupNodes return GroupValidators according epoch func (vp *ValidatorPool) GetGroupNodes(epoch uint64) []*cbfttypes.GroupValidators { + vp.lock.RLock() + defer vp.lock.RUnlock() + if epoch > vp.epoch { panic(fmt.Sprintf("get unknown epoch, current:%d, request:%d", vp.epoch, epoch)) } @@ -734,11 +691,17 @@ func (vp *ValidatorPool) GetGroupNodes(epoch uint64) []*cbfttypes.GroupValidator // NeedGroup return if currentValidators need grouped func (vp *ValidatorPool) NeedGroup() bool { + vp.lock.RLock() + defer vp.lock.RUnlock() + return vp.needGroup } // GetGroupID return GroupID according epoch & NodeID func (vp *ValidatorPool) GetGroupID(epoch uint64, nodeID enode.ID) (uint32, error) { + vp.lock.RLock() + defer vp.lock.RUnlock() + if epoch > vp.epoch { panic(fmt.Sprintf("get unknown epoch, current:%d, request:%d", vp.epoch, epoch)) } @@ -748,13 +711,11 @@ func (vp *ValidatorPool) GetGroupID(epoch uint64, nodeID enode.ID) (uint32, erro return vp.currentValidators.GroupID(nodeID), nil } -// GroupID return current node's GroupID according epoch -func (vp *ValidatorPool) GroupID(epoch uint64) (uint32, error) { - return vp.GetGroupID(epoch, vp.nodeID) -} - // GetUnitID return index according epoch & NodeID func (vp *ValidatorPool) GetUnitID(epoch uint64, nodeID enode.ID) (uint32, error) { + vp.lock.RLock() + defer vp.lock.RUnlock() + if epoch > vp.epoch { panic(fmt.Sprintf("get unknown epoch, current:%d, request:%d", vp.epoch, epoch)) } @@ -776,31 +737,67 @@ func NextRound(blockNumber uint64) uint64 { // Len return number of validators by groupID. // 返回指定epoch和分组下的节点总数 func (vp *ValidatorPool) LenByGroupID(epoch uint64, groupID uint32) int { - // TODO - return 0 + vp.lock.RLock() + defer vp.lock.RUnlock() + + if epoch+1 == vp.epoch { + return vp.prevValidators.MembersCount(groupID) + } + return vp.currentValidators.MembersCount(groupID) } -// 查询指定epoch和分组下对应nodeIndex的节点信息,没有对应信息返回nil -func (vp *ValidatorPool) GetValidatorByGroupIdAndIndex(epoch uint64, groupID, nodeIndex uint32) (*cbfttypes.ValidateNode, error) { - // TODO - return nil, nil +// 查询指定epoch下对应nodeIndex的节点信息,没有对应信息返回nil +func (vp *ValidatorPool) GetValidatorByGroupIdAndIndex(epoch uint64, nodeIndex uint32) (*cbfttypes.ValidateNode,error) { + vp.lock.RLock() + defer vp.lock.RUnlock() + + if epoch+1 == vp.epoch { + return vp.prevValidators.FindNodeByIndex(int(nodeIndex)) + } + return vp.currentValidators.FindNodeByIndex(int(nodeIndex)) } // 返回指定epoch和分组下所有共识节点的index集合,e.g. [25,26,27,28,29,30...49] func (vp *ValidatorPool) GetValidatorIndexesByGroupID(epoch uint64, groupID uint32) ([]uint32, error) { - // TODO - return nil, nil + vp.lock.RLock() + defer vp.lock.RUnlock() + + if epoch+1 == vp.epoch { + return vp.prevValidators.GetValidatorIndexes(groupID) + } + return vp.currentValidators.GetValidatorIndexes(groupID) } -// 返回指定epoch和分组下所有共识节点的编组信息,e.g. [[25,26],[27,28],[29,30]...[49]] +// 返回指定epoch和分组下所有共识节点的协调节点编组信息,e.g. [[25,26],[27,28],[29,30]...[49]] // 严格按编组顺序返回 func (vp *ValidatorPool) GetCoordinatorIndexesByGroupID(epoch uint64, groupID uint32) ([][]uint32, error) { - // TODO - return nil, nil + vp.lock.RLock() + defer vp.lock.RUnlock() + + var validators *cbfttypes.Validators + if epoch+1 == vp.epoch { + validators = vp.prevValidators + } else { + validators = vp.currentValidators + } + if groupID >= uint32(len(validators.GroupNodes)) { + return nil, fmt.Errorf("GetCoordinatorIndexesByGroupID: wrong groupid[%d]!",groupID) + } + return validators.GroupNodes[groupID].Units, nil } // 返回指定epoch下,nodeID所在的groupID和unitID(两者都是0开始计数),没有对应信息需返回error func (vp *ValidatorPool) GetGroupByValidatorID(epoch uint64, nodeID enode.ID) (uint32, uint32, error) { - // TODO - return 0, 0, nil + vp.lock.RLock() + defer vp.lock.RUnlock() + + var validators *cbfttypes.Validators + if epoch+1 == vp.epoch { + validators = vp.prevValidators + } else { + validators = vp.currentValidators + } + groupID := validators.GroupID(nodeID) + unitID := validators.UnitID(nodeID) + return groupID, unitID, nil } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index b6a7364cb2..736ddcc5d0 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -153,6 +153,25 @@ func (vs *Validators) NodeList() []enode.ID { return nodeList } +func (vs *Validators) MembersCount(groupID uint32) int { + if groupID >= uint32(len(vs.GroupNodes)) { + log.Error("MembersCount: wrong groupid","groupID",groupID) + return 0 + } + return len(vs.GroupNodes[groupID].Nodes) +} + +func (vs *Validators) GetValidatorIndexes(groupid uint32) ([]uint32, error) { + if groupid >= uint32(len(vs.GroupNodes)) { + return nil, fmt.Errorf("MembersCount: wrong groupid[%d]",groupid) + } + ids := make([]uint32, 0) + for _,node := range vs.GroupNodes[groupid].Nodes { + ids = append(ids, node.Index) + } + return ids,nil +} + func (vs *Validators) NodeListByIndexes(indexes []uint32) ([]*ValidateNode, error) { if len(vs.SortedNodes) == 0 { vs.Sort() @@ -263,7 +282,7 @@ func (vs *Validators) GroupID(nodeID enode.ID) uint32 { idx, err := vs.Index(nodeID) if err != nil { log.Error("get preValidator index failed!", "err", err) - return idx + return math.MaxUint32 } groupID := uint32(0) @@ -299,11 +318,11 @@ func (vs *Validators) UnitID(nodeID enode.ID) uint32 { return unitID } -func (gvs *GroupValidators) GroupedUnits(coordinatorLimit uint8) { +func (gvs *GroupValidators) GroupedUnits(coordinatorLimit uint32) { unit := make([]uint32, 0, coordinatorLimit) for i, n := range gvs.Nodes { unit = append(unit, n.Index) - if uint8(len(unit)) >= coordinatorLimit || i == len(gvs.Nodes)-1 { + if uint32(len(unit)) >= coordinatorLimit || i == len(gvs.Nodes)-1 { gvs.Units = append(gvs.Units, unit) unit = make([]uint32, 0, coordinatorLimit) } @@ -314,13 +333,13 @@ func (gvs *GroupValidators) GroupedUnits(coordinatorLimit uint8) { // groupValidatorsLimit is a factor to determine how many groups are grouped // eg: [validatorCount,groupValidatorsLimit]= // [50,25] = 25,25;[43,25] = 22,21; [101,25] = 21,20,20,20,20 -func (vs *Validators) Grouped(groupValidatorsLimit uint8, coordinatorLimit uint8, eventMux *event.TypeMux, epoch uint64) error { +func (vs *Validators) Grouped(groupValidatorsLimit uint32, coordinatorLimit uint32, eventMux *event.TypeMux, epoch uint64) error { // sort nodes by index if len(vs.SortedNodes) == 0 { vs.Sort() } - validatorCount := uint8(vs.SortedNodes.Len()) + validatorCount := uint32(vs.SortedNodes.Len()) groupNum := validatorCount / groupValidatorsLimit mod := validatorCount % groupValidatorsLimit if mod > 0 { @@ -330,9 +349,9 @@ func (vs *Validators) Grouped(groupValidatorsLimit uint8, coordinatorLimit uint8 memberMinCount := validatorCount / groupNum remainder := validatorCount % groupNum vs.GroupNodes = make([]*GroupValidators, groupNum, groupNum) - begin := uint8(0) - end := uint8(0) - for i := uint8(0); i < groupNum; i++ { + begin := uint32(0) + end := uint32(0) + for i := uint32(0); i < groupNum; i++ { begin = end if remainder > 0 { end = begin + memberMinCount + 1 diff --git a/params/config.go b/params/config.go index 0dd5d82223..210b2d1ab4 100644 --- a/params/config.go +++ b/params/config.go @@ -195,8 +195,8 @@ type CbftConfig struct { Amount uint32 `json:"amount,omitempty"` //The maximum number of blocks generated per cycle InitialNodes []CbftNode `json:"initialNodes,omitempty"` //Genesis consensus node ValidatorMode string `json:"validatorMode,omitempty"` //Validator mode for easy testing - GroupValidatorsLimit uint8 `json:"GroupValidatorsLimit,omitempty"` //Max validators per group - CoordinatorLimit uint8 `json:"CoordinatorLimit,omitempty"` //Coordinators Limit C0>C1>C2... + GroupValidatorsLimit uint32 `json:"GroupValidatorsLimit,omitempty"` //Max validators per group + CoordinatorLimit uint32 `json:"CoordinatorLimit,omitempty"` //Coordinators Limit C0>C1>C2... } // CliqueConfig is the consensus engine configs for proof-of-authority based sealing. From 18c89526217513ab87eb85237d6e0218ff94cab8 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 17 Nov 2021 17:56:11 +0800 Subject: [PATCH 054/299] update validatorspool --- consensus/cbft/consensus_process.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 0f39500da9..1839759cb9 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -244,7 +244,7 @@ func (cbft *Cbft) enoughSigns(epoch uint64, groupID uint32, signs int) bool { // Determine whether the signer of the RGBlockQuorumCert message is a member of the group func (cbft *Cbft) isGroupMember(epoch uint64, groupID uint32, nodeIndex uint32) bool { - if v,err := cbft.validatorPool.GetValidatorByGroupIdAndIndex(epoch, nodeIndex); err != nil || v == nil { + if v, err := cbft.validatorPool.GetValidatorByGroupIdAndIndex(epoch, nodeIndex); err != nil || v == nil { return false } return true From fa721dc5f7b40dbc723b1ef058cd33aae350db94 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Wed, 17 Nov 2021 18:05:19 +0800 Subject: [PATCH 055/299] Improve state unit testing --- consensus/cbft/cbft.go | 11 +- consensus/cbft/consensus_process.go | 29 +- consensus/cbft/network/helper_test.go | 2 +- consensus/cbft/state/state.go | 262 +++++++++++------ consensus/cbft/state/state_test.go | 405 ++++++++++++++++++++------ consensus/cbft/types/crypto.go | 14 +- consensus/cbft/types/crypto_test.go | 2 +- 7 files changed, 536 insertions(+), 189 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 280e50d8ac..c8264e089b 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -1516,11 +1516,11 @@ func (cbft *Cbft) checkPrepareQC(msg ctypes.ConsensusMsg) error { viewChangeQC := cm.ViewChangeQC prepareQCs := cm.PrepareQCs for _, qc := range viewChangeQC.QCs { - if qc.BlockNumber == 0 && prepareQCs != nil && prepareQCs.FindPrepareQC(qc.BlockHash) != nil { - return authFailedError{err: fmt.Errorf("RGViewChangeQuorumCert need not take PrepareQC, RGViewChangeQuorumCert:%s", cm.String())} + if qc.BlockNumber == 0 && prepareQCs.FindPrepareQC(qc.BlockHash) != nil { + return authFailedError{err: fmt.Errorf("RGViewChangeQuorumCert need not take PrepareQC, blockNumber:%d, blockHash:%s, RGViewChangeQuorumCert:%s", qc.BlockNumber, qc.BlockHash.TerminalString(), cm.String())} } - if qc.BlockNumber != 0 && (prepareQCs == nil || prepareQCs.FindPrepareQC(qc.BlockHash) == nil) { - return authFailedError{err: fmt.Errorf("RGViewChangeQuorumCert need take PrepareQC, RGViewChangeQuorumCert:%s", cm.String())} + if qc.BlockNumber != 0 && prepareQCs.FindPrepareQC(qc.BlockHash) == nil { + return authFailedError{err: fmt.Errorf("RGViewChangeQuorumCert need take PrepareQC, blockNumber:%d, blockHash:%s, RGViewChangeQuorumCert:%s", qc.BlockNumber, qc.BlockHash.TerminalString(), cm.String())} } } default: @@ -1935,6 +1935,9 @@ func (cbft *Cbft) verifyQuorumCert(qc *ctypes.QuorumCert) error { func (cbft *Cbft) validateViewChangeQC(viewChangeQC *ctypes.ViewChangeQC, validatorLimit int) error { + // TODO + // 校验 ValidatorSet 是否重复 + //vcEpoch, _, _, _, _, _ := viewChangeQC.MaxBlock() //maxLimit := cbft.validatorPool.Len(vcEpoch) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index cb36f85fa9..cdd8f13204 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -251,8 +251,9 @@ func (cbft *Cbft) isGroupMember(epoch uint64, groupID uint32, nodeIndex uint32) // Determine whether the aggregate signers in the RGBlockQuorumCert message are all members of the group func (cbft *Cbft) allGroupMember(epoch uint64, groupID uint32, validatorSet *utils.BitArray) bool { - var indexes []uint32 // Index collection of the group members - if indexes, err := cbft.validatorPool.GetValidatorIndexesByGroupID(epoch, groupID); err != nil || indexes == nil { + // Index collection of the group members + indexes, err := cbft.validatorPool.GetValidatorIndexesByGroupID(epoch, groupID) + if err != nil || indexes == nil { return false } total := cbft.validatorPool.Len(epoch) @@ -791,7 +792,9 @@ func (cbft *Cbft) trySendRGViewChangeQuorumCert() { // get parentQC prepareQCs := make(map[common.Hash]*ctypes.QuorumCert) for _, v := range groupViewChanges { - prepareQCs[v.BlockHash] = v.PrepareQC + if v.PrepareQC != nil { + prepareQCs[v.BlockHash] = v.PrepareQC + } } // Add SelectRGViewChangeQuorumCerts cbft.state.AddSelectRGViewChangeQuorumCerts(groupID, rgqc, prepareQCs) @@ -893,30 +896,32 @@ func (cbft *Cbft) mergeSelectedViewChangeQuorumCerts(node *cbfttypes.ValidateNod cbft.log.Error("Failed to find the group info of the node", "epoch", vc.EpochNum(), "nodeID", node.NodeID.TerminalString(), "error", err) return } - cbft.state.MergeViewChanges(groupID, []*protocols.ViewChange{vc}) + validatorLen := cbft.validatorPool.Len(cbft.state.Epoch()) + cbft.state.MergeViewChanges(groupID, []*protocols.ViewChange{vc}, validatorLen) } func (cbft *Cbft) richRGViewChangeQuorumCert(rgb *protocols.RGViewChangeQuorumCert) { mergeVcs := cbft.groupViewChanges(rgb.EpochNum(), rgb.GroupID) if len(mergeVcs) > 0 { + total := cbft.validatorPool.Len(cbft.state.Epoch()) for _, vc := range mergeVcs { if !rgb.ViewChangeQC.ExistViewChange(vc.Epoch, vc.ViewNumber, vc.BlockHash) { qc := &ctypes.ViewChangeQuorumCert{ - Epoch: vc.Epoch, - ViewNumber: vc.ViewNumber, - BlockHash: vc.BlockHash, - BlockNumber: vc.BlockNumber, + Epoch: vc.Epoch, + ViewNumber: vc.ViewNumber, + BlockHash: vc.BlockHash, + BlockNumber: vc.BlockNumber, + ValidatorSet: utils.NewBitArray(uint32(total)), } if vc.PrepareQC != nil { qc.BlockEpoch = vc.PrepareQC.Epoch qc.BlockViewNumber = vc.PrepareQC.ViewNumber - rgb.PrepareQCs.QCs = append(rgb.PrepareQCs.QCs, vc.PrepareQC) + rgb.PrepareQCs.AppendQuorumCert(vc.PrepareQC) } - total := cbft.validatorPool.Len(cbft.state.Epoch()) - qc.ValidatorSet = utils.NewBitArray(uint32(total)) qc.ValidatorSet.SetIndex(vc.ValidatorIndex, true) qc.Signature.SetBytes(vc.Signature.Bytes()) + rgb.ViewChangeQC.AppendQuorumCert(qc) rgb.ViewChangeQC.QCs = append(rgb.ViewChangeQC.QCs, qc) } else { for _, qc := range rgb.ViewChangeQC.QCs { @@ -990,7 +995,7 @@ func (cbft *Cbft) findQCBlock() { enoughRGQuorumCerts := func() bool { rgqcs = cbft.state.FindMaxRGQuorumCerts(next) size := 0 - if rgqcs != nil && len(rgqcs) > 0 { + if len(rgqcs) > 0 { for _, qc := range rgqcs { size += qc.ValidatorSet.HasLength() } diff --git a/consensus/cbft/network/helper_test.go b/consensus/cbft/network/helper_test.go index 723aff39d4..ee4b8df66c 100644 --- a/consensus/cbft/network/helper_test.go +++ b/consensus/cbft/network/helper_test.go @@ -80,7 +80,7 @@ func newFakeRGBlockQuorumCert() *protocols.RGBlockQuorumCert { GroupID: 1, BlockQC: newQuorumCert(), ValidatorIndex: 1, - ParentQC: nil, + ParentQC: newQuorumCert(), Signature: newSignature(), } } diff --git a/consensus/cbft/state/state.go b/consensus/cbft/state/state.go index b0b2dba05e..25dcfb727c 100644 --- a/consensus/cbft/state/state.go +++ b/consensus/cbft/state/state.go @@ -828,10 +828,6 @@ func (vs *ViewState) FindRGBlockQuorumCerts(blockIndex, groupID, validatorIndex return vs.viewRGBlockQuorumCerts.FindRGBlockQuorumCerts(blockIndex, groupID, validatorIndex) } -//func (vs *ViewState) FindGroupRGBlockQuorumCerts(blockIndex, groupID uint32) map[uint32]*protocols.RGBlockQuorumCert { -// return vs.viewRGBlockQuorumCerts.FindGroupRGBlockQuorumCerts(blockIndex, groupID) -//} - func (vs *ViewState) AddRGBlockQuorumCert(nodeIndex uint32, rgb *protocols.RGBlockQuorumCert) { vs.viewRGBlockQuorumCerts.AddRGBlockQuorumCerts(rgb.BlockIndx(), rgb) } @@ -893,9 +889,9 @@ func (vs *ViewState) AddSelectRGViewChangeQuorumCerts(groupID uint32, rgqc *ctyp vs.selectedRGViewChangeQuorumCerts.AddRGViewChangeQuorumCerts(groupID, rgqcs, prepareQCs) } -func (vs *ViewState) MergeViewChanges(groupID uint32, vcs []*protocols.ViewChange) { +func (vs *ViewState) MergeViewChanges(groupID uint32, vcs []*protocols.ViewChange, validatorLen int) { for _, vc := range vcs { - vs.selectedRGViewChangeQuorumCerts.MergeViewChange(groupID, vc) + vs.selectedRGViewChangeQuorumCerts.MergeViewChange(groupID, vc, uint32(validatorLen)) } } @@ -1045,6 +1041,13 @@ func (brg *viewRGBlockQuorumCerts) clear() { brg.BlockRGBlockQuorumCerts = make(map[uint32]*groupRGBlockQuorumCerts) } +func (brg *viewRGBlockQuorumCerts) String() string { + if s, err := json.Marshal(brg); err == nil { + return string(s) + } + return "" +} + func (vrg *validatorRGBlockQuorumCerts) findRGBlockQuorumCerts(validatorIndex uint32) *protocols.RGBlockQuorumCert { if ps, ok := vrg.ValidatorRGBlockQuorumCerts[validatorIndex]; ok { return ps @@ -1066,15 +1069,6 @@ func (brg *viewRGBlockQuorumCerts) FindRGBlockQuorumCerts(blockIndex, groupID, v return nil } -//func (brg *viewRGBlockQuorumCerts) FindGroupRGBlockQuorumCerts(blockIndex, groupID uint32) map[uint32]*protocols.RGBlockQuorumCert { -// if ps, ok := brg.BlockRGBlockQuorumCerts[blockIndex]; ok { -// if gs, ok := ps.GroupRGBlockQuorumCerts[groupID]; ok { -// return gs.ValidatorRGBlockQuorumCerts -// } -// } -// return nil -//} - func (brg *viewRGBlockQuorumCerts) RGBlockQuorumCertsLen(blockIndex, groupID uint32) int { if ps, ok := brg.BlockRGBlockQuorumCerts[blockIndex]; ok { if gs, ok := ps.GroupRGBlockQuorumCerts[groupID]; ok { @@ -1127,13 +1121,16 @@ func (grg *QuorumCerts) addRGQuorumCerts(groupID uint32, rgqc *ctypes.QuorumCert return } if rgqc.ValidatorSet.Contains(ps[i].ValidatorSet) { - grg.GroupQuorumCerts[groupID] = append(grg.GroupQuorumCerts[groupID][:i], grg.GroupQuorumCerts[groupID][i+1:]...) + ps = append(ps[:i], ps[i+1:]...) + //grg.GroupQuorumCerts[groupID] = append(grg.GroupQuorumCerts[groupID][:i], grg.GroupQuorumCerts[groupID][i+1:]...) //return } } } if len(ps) < maxSelectedRGLimit { - grg.GroupQuorumCerts[groupID] = append(grg.GroupQuorumCerts[groupID], rgqc) + ps = append(ps, rgqc) + grg.GroupQuorumCerts[groupID] = ps + //grg.GroupQuorumCerts[groupID] = append(grg.GroupQuorumCerts[groupID], rgqc) } } else { qcs := make([]*ctypes.QuorumCert, 0, maxSelectedRGLimit) @@ -1145,76 +1142,86 @@ func (grg *QuorumCerts) addRGQuorumCerts(groupID uint32, rgqc *ctypes.QuorumCert func (srg *selectedRGBlockQuorumCerts) AddRGQuorumCerts(blockIndex, groupID uint32, rgqc *ctypes.QuorumCert, parentQC *ctypes.QuorumCert) { if ps, ok := srg.BlockRGBlockQuorumCerts[blockIndex]; ok { ps.addRGQuorumCerts(groupID, rgqc) - if ps.ParentQC == nil { + if ps.ParentQC == nil && parentQC != nil { ps.ParentQC = parentQC } } else { grg := newQuorumCerts() grg.addRGQuorumCerts(groupID, rgqc) - grg.ParentQC = parentQC + if parentQC != nil { + grg.ParentQC = parentQC + } srg.BlockRGBlockQuorumCerts[blockIndex] = grg } } +func (grg *QuorumCerts) findRGQuorumCerts(groupID uint32) []*ctypes.QuorumCert { + if gs, ok := grg.GroupQuorumCerts[groupID]; ok { + return gs + } + return nil +} + func (srg *selectedRGBlockQuorumCerts) FindRGQuorumCerts(blockIndex, groupID uint32) []*ctypes.QuorumCert { if ps, ok := srg.BlockRGBlockQuorumCerts[blockIndex]; ok { - if gs, ok := ps.GroupQuorumCerts[groupID]; ok { - return gs - } + return ps.findRGQuorumCerts(groupID) } return nil } func (srg *selectedRGBlockQuorumCerts) RGQuorumCertsLen(blockIndex, groupID uint32) int { if ps, ok := srg.BlockRGBlockQuorumCerts[blockIndex]; ok { - if gs, ok := ps.GroupQuorumCerts[groupID]; ok { - return len(gs) - } + gs := ps.findRGQuorumCerts(groupID) + return len(gs) } return 0 } +func findMaxQuorumCert(qcs []*ctypes.QuorumCert) *ctypes.QuorumCert { + if len(qcs) > 0 { + m := qcs[0] + for i := 1; i < len(qcs); i++ { + if qcs[i].HigherSign(m) { + m = qcs[i] + } + } + return m + } + return nil +} + // Returns the QuorumCert with the most signatures in each group func (srg *selectedRGBlockQuorumCerts) FindMaxRGQuorumCerts(blockIndex uint32) []*ctypes.QuorumCert { if ps, ok := srg.BlockRGBlockQuorumCerts[blockIndex]; ok { - var maxs []*ctypes.QuorumCert + var groupMaxs []*ctypes.QuorumCert // The QuorumCert with the largest number of signatures per group if len(ps.GroupQuorumCerts) > 0 { - maxs = make([]*ctypes.QuorumCert, 0, len(ps.GroupQuorumCerts)) + groupMaxs = make([]*ctypes.QuorumCert, 0, len(ps.GroupQuorumCerts)) for _, qcs := range ps.GroupQuorumCerts { - m := qcs[0] - for i := 1; i < len(qcs); i++ { - if qcs[i].HigherSign(m) { - m = qcs[i] - } + max := findMaxQuorumCert(qcs) + if max != nil { + groupMaxs = append(groupMaxs, max) } - maxs = append(maxs, m) } } - return maxs + return groupMaxs } return nil } // Returns the QuorumCert with the most signatures in specified group func (srg *selectedRGBlockQuorumCerts) FindMaxGroupRGQuorumCert(blockIndex, groupID uint32) (*ctypes.QuorumCert, *ctypes.QuorumCert) { - if ps, ok := srg.BlockRGBlockQuorumCerts[blockIndex]; ok { - parentQC := ps.ParentQC - if gs, ok := ps.GroupQuorumCerts[groupID]; ok { - max := gs[0] - for i := 1; i < len(gs); i++ { - if gs[i].HigherSign(max) { - max = gs[i] - } - } - return max, parentQC - } + gs := srg.FindRGQuorumCerts(blockIndex, groupID) + max := findMaxQuorumCert(gs) + if max != nil { + parentQC := srg.BlockRGBlockQuorumCerts[blockIndex].ParentQC + return max, parentQC } return nil, nil } func (srg *selectedRGBlockQuorumCerts) MergePrepareVote(blockIndex, groupID uint32, vote *protocols.PrepareVote) { rgqcs := srg.FindRGQuorumCerts(blockIndex, groupID) - if rgqcs == nil || len(rgqcs) <= 0 { + if len(rgqcs) <= 0 { return } @@ -1223,12 +1230,46 @@ func (srg *selectedRGBlockQuorumCerts) MergePrepareVote(blockIndex, groupID uint qc.AddSign(vote.Signature, vote.NodeIndex()) } } + // merge again + deleteIndexes := make(map[int]struct{}) + for i := 0; i < len(rgqcs); i++ { + if _, ok := deleteIndexes[i]; ok { + continue + } + for j := 0; j < len(rgqcs); j++ { + if _, ok := deleteIndexes[j]; ok || j == i { + continue + } + if rgqcs[i].ValidatorSet.Contains(rgqcs[j].ValidatorSet) { + deleteIndexes[j] = struct{}{} + } else if rgqcs[j].ValidatorSet.Contains(rgqcs[i].ValidatorSet) { + deleteIndexes[i] = struct{}{} + break + } + } + } + if len(deleteIndexes) > 0 { + merged := make([]*ctypes.QuorumCert, 0) + for i := 0; i < len(rgqcs); i++ { + if _, ok := deleteIndexes[i]; !ok { + merged = append(merged, rgqcs[i]) + } + } + srg.BlockRGBlockQuorumCerts[blockIndex].GroupQuorumCerts[groupID] = merged + } } func (srg *selectedRGBlockQuorumCerts) clear() { srg.BlockRGBlockQuorumCerts = make(map[uint32]*QuorumCerts) } +func (srg *selectedRGBlockQuorumCerts) String() string { + if s, err := json.Marshal(srg); err == nil { + return string(s) + } + return "" +} + // viewRGViewChangeQuorumCerts type viewRGViewChangeQuorumCerts struct { GroupRGViewChangeQuorumCerts map[uint32]*validatorRGViewChangeQuorumCerts `json:"groupRGViewChangeQuorumCerts"` // The map key is groupID @@ -1270,7 +1311,7 @@ func (brg *viewRGViewChangeQuorumCerts) AddRGViewChangeQuorumCerts(rg *protocols } } -func (vrg *validatorRGViewChangeQuorumCerts) FindRGViewChangeQuorumCerts(validatorIndex uint32) *protocols.RGViewChangeQuorumCert { +func (vrg *validatorRGViewChangeQuorumCerts) findRGViewChangeQuorumCerts(validatorIndex uint32) *protocols.RGViewChangeQuorumCert { if ps, ok := vrg.ValidatorRGViewChangeQuorumCerts[validatorIndex]; ok { return ps } @@ -1279,7 +1320,7 @@ func (vrg *validatorRGViewChangeQuorumCerts) FindRGViewChangeQuorumCerts(validat func (brg *viewRGViewChangeQuorumCerts) FindRGViewChangeQuorumCerts(groupID uint32, validatorIndex uint32) *protocols.RGViewChangeQuorumCert { if ps, ok := brg.GroupRGViewChangeQuorumCerts[groupID]; ok { - return ps.FindRGViewChangeQuorumCerts(validatorIndex) + return ps.findRGViewChangeQuorumCerts(validatorIndex) } return nil } @@ -1306,6 +1347,13 @@ func (brg *viewRGViewChangeQuorumCerts) clear() { brg.GroupRGViewChangeQuorumCerts = make(map[uint32]*validatorRGViewChangeQuorumCerts) } +func (brg *viewRGViewChangeQuorumCerts) String() string { + if s, err := json.Marshal(brg); err == nil { + return string(s) + } + return "" +} + // selectedRGViewChangeQuorumCerts type selectedRGViewChangeQuorumCerts struct { GroupRGViewChangeQuorumCerts map[uint32]*ViewChangeQuorumCerts `json:"groupRGViewChangeQuorumCerts"` // The map key is groupID @@ -1337,13 +1385,16 @@ func (grg *ViewChangeQuorumCerts) addRGViewChangeQuorumCert(hash common.Hash, rg return } if rgqc.ValidatorSet.Contains(ps[i].ValidatorSet) { - grg.QuorumCerts[hash] = append(grg.QuorumCerts[hash][:i], grg.QuorumCerts[hash][i+1:]...) + ps = append(ps[:i], ps[i+1:]...) + //grg.QuorumCerts[hash] = append(grg.QuorumCerts[hash][:i], grg.QuorumCerts[hash][i+1:]...) //return } } } if len(ps) < maxSelectedRGLimit { - grg.QuorumCerts[hash] = append(grg.QuorumCerts[hash], rgqc) + ps = append(ps, rgqc) + grg.QuorumCerts[hash] = ps + //grg.QuorumCerts[hash] = append(grg.QuorumCerts[hash], rgqc) } } else { qcs := make([]*ctypes.ViewChangeQuorumCert, 0, maxSelectedRGLimit) @@ -1366,77 +1417,119 @@ func (srg *selectedRGViewChangeQuorumCerts) AddRGViewChangeQuorumCerts(groupID u grg.addRGViewChangeQuorumCerts(rgqcs) srg.GroupRGViewChangeQuorumCerts[groupID] = grg } - for hash, qc := range prepareQCs { - if srg.PrepareQCs[hash] == nil { - srg.PrepareQCs[hash] = qc + if len(prepareQCs) > 0 { + for hash, qc := range prepareQCs { + if srg.PrepareQCs[hash] == nil { + srg.PrepareQCs[hash] = qc + } + } + } +} + +func (srg *selectedRGViewChangeQuorumCerts) findRGQuorumCerts(groupID uint32) map[common.Hash][]*ctypes.ViewChangeQuorumCert { + if ps, ok := srg.GroupRGViewChangeQuorumCerts[groupID]; ok { + if ps != nil && len(ps.QuorumCerts) > 0 { + return ps.QuorumCerts } } + return nil } -func (srg *selectedRGViewChangeQuorumCerts) MergeViewChange(groupID uint32, vc *protocols.ViewChange) { - rgqcs := srg.GroupRGViewChangeQuorumCerts[groupID] - if rgqcs == nil || len(rgqcs.QuorumCerts) <= 0 { +func (srg *selectedRGViewChangeQuorumCerts) MergeViewChange(groupID uint32, vc *protocols.ViewChange, validatorLen uint32) { + rgqcs := srg.findRGQuorumCerts(groupID) + if len(rgqcs) <= 0 { // If there is no aggregate signature under the group at this time, then the single ViewChange is not merged return } - if qcs, ok := rgqcs.QuorumCerts[vc.BHash()]; ok { + if qcs, ok := rgqcs[vc.BHash()]; ok { for _, qc := range qcs { if !qc.ValidatorSet.GetIndex(vc.NodeIndex()) { qc.AddSign(vc.Signature, vc.NodeIndex()) } } + // merge again + deleteIndexes := make(map[int]struct{}) + for i := 0; i < len(qcs); i++ { + if _, ok := deleteIndexes[i]; ok { + continue + } + for j := 0; j < len(qcs); j++ { + if _, ok := deleteIndexes[j]; ok || j == i { + continue + } + if qcs[i].ValidatorSet.Contains(qcs[j].ValidatorSet) { + deleteIndexes[j] = struct{}{} + } else if qcs[j].ValidatorSet.Contains(qcs[i].ValidatorSet) { + deleteIndexes[i] = struct{}{} + break + } + } + } + if len(deleteIndexes) > 0 { + merged := make([]*ctypes.ViewChangeQuorumCert, 0) + for i := 0; i < len(qcs); i++ { + if _, ok := deleteIndexes[i]; !ok { + merged = append(merged, qcs[i]) + } + } + srg.GroupRGViewChangeQuorumCerts[groupID].QuorumCerts[vc.BHash()] = merged + } } else { qcs := make([]*ctypes.ViewChangeQuorumCert, 0, maxSelectedRGLimit) qc := &ctypes.ViewChangeQuorumCert{ - Epoch: vc.Epoch, - ViewNumber: vc.ViewNumber, - BlockHash: vc.BlockHash, - BlockNumber: vc.BlockNumber, - //BlockEpoch: vc.PrepareQC.Epoch, - //BlockViewNumber: vc.PrepareQC.ViewNumber, - ValidatorSet: utils.NewBitArray(vc.PrepareQC.ValidatorSet.Size()), + Epoch: vc.Epoch, + ViewNumber: vc.ViewNumber, + BlockHash: vc.BlockHash, + BlockNumber: vc.BlockNumber, + ValidatorSet: utils.NewBitArray(validatorLen), } if vc.PrepareQC != nil { qc.BlockEpoch = vc.PrepareQC.Epoch qc.BlockViewNumber = vc.PrepareQC.ViewNumber + srg.PrepareQCs[vc.BHash()] = vc.PrepareQC } qc.Signature.SetBytes(vc.Signature.Bytes()) qc.ValidatorSet.SetIndex(vc.ValidatorIndex, true) qcs = append(qcs, qc) srg.GroupRGViewChangeQuorumCerts[groupID].QuorumCerts[vc.BHash()] = qcs - srg.PrepareQCs[vc.BHash()] = vc.PrepareQC } } func (srg *selectedRGViewChangeQuorumCerts) RGViewChangeQuorumCertsLen(groupID uint32) int { - if ps, ok := srg.GroupRGViewChangeQuorumCerts[groupID]; ok { - if ps != nil { - return len(ps.QuorumCerts) - } - } - return 0 + rgqcs := srg.findRGQuorumCerts(groupID) + return len(rgqcs) } // Returns the QuorumCert with the most signatures in specified group func (srg *selectedRGViewChangeQuorumCerts) FindMaxGroupRGViewChangeQuorumCert(groupID uint32) (*ctypes.ViewChangeQC, *ctypes.PrepareQCs) { - rgqcs := srg.GroupRGViewChangeQuorumCerts[groupID] - if rgqcs == nil || len(rgqcs.QuorumCerts) <= 0 { + findMaxQuorumCert := func(qcs []*ctypes.ViewChangeQuorumCert) *ctypes.ViewChangeQuorumCert { + if len(qcs) > 0 { + m := qcs[0] + for i := 1; i < len(qcs); i++ { + if qcs[i].HigherSign(m) { + m = qcs[i] + } + } + return m + } + return nil + } + + rgqcs := srg.findRGQuorumCerts(groupID) + if len(rgqcs) <= 0 { return nil, nil } viewChangeQC := &ctypes.ViewChangeQC{QCs: make([]*ctypes.ViewChangeQuorumCert, 0)} prepareQCs := &ctypes.PrepareQCs{QCs: make([]*ctypes.QuorumCert, 0)} - for hash, qcs := range rgqcs.QuorumCerts { - max := qcs[0] - for i := 1; i < len(qcs); i++ { - if qcs[i].HigherSign(max) { - max = qcs[i] - } - } + for hash, qcs := range rgqcs { + max := findMaxQuorumCert(qcs) viewChangeQC.QCs = append(viewChangeQC.QCs, max) - prepareQCs.QCs = append(prepareQCs.QCs, srg.PrepareQCs[hash]) + if srg.PrepareQCs != nil && srg.PrepareQCs[hash] != nil { + prepareQCs.QCs = append(prepareQCs.QCs, srg.PrepareQCs[hash]) + } } return viewChangeQC, prepareQCs } @@ -1456,3 +1549,10 @@ func (srg *selectedRGViewChangeQuorumCerts) clear() { srg.GroupRGViewChangeQuorumCerts = make(map[uint32]*ViewChangeQuorumCerts) srg.PrepareQCs = make(map[common.Hash]*ctypes.QuorumCert) } + +func (srg *selectedRGViewChangeQuorumCerts) String() string { + if s, err := json.Marshal(srg); err == nil { + return string(s) + } + return "" +} diff --git a/consensus/cbft/state/state_test.go b/consensus/cbft/state/state_test.go index 1e6999ce85..56c8cd2a98 100644 --- a/consensus/cbft/state/state_test.go +++ b/consensus/cbft/state/state_test.go @@ -17,7 +17,6 @@ package state import ( - "fmt" "math/big" "testing" "time" @@ -225,117 +224,347 @@ func TestNewViewChanges(t *testing.T) { assert.Equal(t, uint32(9), viewState.ViewChangeByIndex(9).ValidatorIndex) } +func newQuorumCert(blockIndex uint32) *ctypes.QuorumCert { + return &ctypes.QuorumCert{ + Epoch: 1, + ViewNumber: 1, + BlockHash: common.Hash{}, + BlockNumber: 1, + BlockIndex: blockIndex, + Signature: ctypes.Signature{}, + ValidatorSet: utils.NewBitArray(32), + } +} + func TestViewRGBlockQuorumCerts(t *testing.T) { + testCases := []struct { + blockIndex uint32 + groupID uint32 + validatorIndex uint32 + }{ + {0, 1, 11}, + {0, 2, 22}, + {0, 1, 12}, + {1, 3, 33}, + {1, 5, 55}, + {2, 1, 11}, + {2, 2, 22}, + {2, 2, 23}, + {0, 1, 12}, // duplicate data + } + v := newViewRGBlockQuorumCerts() - v.AddRGBlockQuorumCerts(0, &protocols.RGBlockQuorumCert{ - GroupID: 1, - BlockQC: &ctypes.QuorumCert{ - BlockIndex: 0, - }, - ValidatorIndex: 11, - }) - v.AddRGBlockQuorumCerts(0, &protocols.RGBlockQuorumCert{ - GroupID: 2, - BlockQC: &ctypes.QuorumCert{ - BlockIndex: 0, - }, - ValidatorIndex: 22, - }) - v.AddRGBlockQuorumCerts(0, &protocols.RGBlockQuorumCert{ - GroupID: 1, - BlockQC: &ctypes.QuorumCert{ - BlockIndex: 0, - }, - ValidatorIndex: 12, - }) - v.AddRGBlockQuorumCerts(1, &protocols.RGBlockQuorumCert{ - GroupID: 3, - BlockQC: &ctypes.QuorumCert{ - BlockIndex: 1, - }, - ValidatorIndex: 33, - }) - v.AddRGBlockQuorumCerts(1, &protocols.RGBlockQuorumCert{ - GroupID: 5, - BlockQC: &ctypes.QuorumCert{ - BlockIndex: 1, - }, - ValidatorIndex: 55, - }) - v.AddRGBlockQuorumCerts(2, &protocols.RGBlockQuorumCert{ - GroupID: 1, - BlockQC: &ctypes.QuorumCert{ - BlockIndex: 2, - }, - ValidatorIndex: 11, - }) - v.AddRGBlockQuorumCerts(2, &protocols.RGBlockQuorumCert{ - GroupID: 2, - BlockQC: &ctypes.QuorumCert{ - BlockIndex: 2, - }, - ValidatorIndex: 22, - }) - v.AddRGBlockQuorumCerts(0, &protocols.RGBlockQuorumCert{ - GroupID: 1, - BlockQC: &ctypes.QuorumCert{ - BlockIndex: 0, - }, - ValidatorIndex: 12, - }) - //c, _ := json.Marshal(v) - //fmt.Println(string(c)) + for _, c := range testCases { + v.AddRGBlockQuorumCerts(c.blockIndex, &protocols.RGBlockQuorumCert{ + GroupID: c.groupID, + BlockQC: newQuorumCert(c.blockIndex), + ValidatorIndex: c.validatorIndex, + }) + } + + //fmt.Println(v.String()) assert.Equal(t, 2, v.RGBlockQuorumCertsLen(0, 1)) + assert.Equal(t, 1, v.RGBlockQuorumCertsLen(0, 2)) + assert.Equal(t, 1, v.RGBlockQuorumCertsLen(1, 3)) + assert.Equal(t, 1, v.RGBlockQuorumCertsLen(1, 5)) assert.Equal(t, 1, v.RGBlockQuorumCertsLen(2, 1)) + assert.Equal(t, 2, v.RGBlockQuorumCertsLen(2, 2)) assert.Equal(t, 0, v.RGBlockQuorumCertsLen(2, 3)) assert.Equal(t, 0, v.RGBlockQuorumCertsLen(3, 1)) assert.Nil(t, v.FindRGBlockQuorumCerts(3, 1, 1)) assert.Nil(t, v.FindRGBlockQuorumCerts(0, 3, 1)) assert.Nil(t, v.FindRGBlockQuorumCerts(0, 1, 13)) + assert.NotNil(t, v.FindRGBlockQuorumCerts(1, 3, 33)) assert.Equal(t, uint32(11), v.FindRGBlockQuorumCerts(0, 1, 11).ValidatorIndex) + + assert.Equal(t, []uint32{11, 12}, v.RGBlockQuorumCertsIndexes(0, 1)) } func TestSelectedRGBlockQuorumCerts(t *testing.T) { - bitArrayStr := []string{ - `"x"`, - `"xxxx"`, - `"xx"`, - `"x_x_x_"`, - `"xx__x_"`, - `"xxx_x_"`, + testCases := []struct { + blockIndex uint32 + groupID uint32 + ValidatorSetStr string + }{ + {0, 1, `"x"`}, + {0, 1, `"xxxx__"`}, + {0, 1, `"xx"`}, + {0, 1, `"x_x_x_"`}, + {0, 1, `"xx__x_"`}, + {0, 1, `"xxx_x_"`}, + + {1, 1, `"x"`}, + {1, 1, `"xxxx"`}, + {1, 1, `"xx"`}, + {1, 1, `"x_x_x_"`}, + {1, 1, `"xx__x_"`}, + {1, 1, `"xxx_x_"`}, + {1, 1, `"xxxxx_"`}, // contains all + + {0, 2, `"x_____"`}, + {0, 2, `"_x____"`}, + {0, 2, `"__x___"`}, + {0, 2, `"___x__"`}, + {0, 2, `"____x_"`}, + {0, 2, `"_____x"`}, // exceed the limit + + {1, 2, `"x"`}, + {1, 2, `"xxxxx_"`}, // contains all + {1, 2, `"xx"`}, + {1, 2, `"x_x_x_"`}, + {1, 2, `"xx__x_"`}, + {1, 2, `"xxx_x_"`}, + {1, 2, `"xxxx"`}, + + {2, 2, `"x_x_x_"`}, + {2, 2, `"x_xx_x"`}, + {2, 2, `"_x_xx_"`}, + {2, 2, `"xx__x_"`}, + {2, 2, `"_x__xx"`}, } - bitArray := func(index int) *utils.BitArray { + bitArray := func(bitArrayStr string) *utils.BitArray { var ba *utils.BitArray - json.Unmarshal([]byte(bitArrayStr[index]), &ba) + json.Unmarshal([]byte(bitArrayStr), &ba) return ba + + } + + marshalBitArray := func(arr *utils.BitArray) string { + if b, err := json.Marshal(arr); err == nil { + return string(b) + } + return "" } s := newSelectedRGBlockQuorumCerts() - for i, _ := range bitArrayStr { - s.AddRGQuorumCerts(0, 1, &ctypes.QuorumCert{ - BlockIndex: 0, - ValidatorSet: bitArray(i), - }, nil) + for _, c := range testCases { + s.AddRGQuorumCerts(c.blockIndex, c.groupID, &ctypes.QuorumCert{ + BlockIndex: c.blockIndex, + ValidatorSet: bitArray(c.ValidatorSetStr), + }, newQuorumCert(c.blockIndex)) + } + + //fmt.Println(s.String()) + + assert.Equal(t, 2, len(s.FindRGQuorumCerts(0, 1))) + assert.Equal(t, 1, len(s.FindRGQuorumCerts(1, 1))) + assert.Equal(t, 5, s.RGQuorumCertsLen(0, 2)) + assert.Equal(t, 1, s.RGQuorumCertsLen(1, 2)) + assert.Equal(t, 5, s.RGQuorumCertsLen(2, 2)) + assert.Equal(t, 0, s.RGQuorumCertsLen(0, 3)) + assert.Equal(t, 0, s.RGQuorumCertsLen(3, 0)) + + max, parentQC := s.FindMaxGroupRGQuorumCert(0, 1) + assert.Equal(t, uint32(0), max.BlockIndex) + assert.Equal(t, `"xxx_x_"`, marshalBitArray(max.ValidatorSet)) + assert.Equal(t, uint32(0), parentQC.BlockIndex) + + maxs := s.FindMaxRGQuorumCerts(1) + assert.Equal(t, uint32(1), maxs[1].BlockIndex) + assert.Equal(t, `"xxxxx_"`, marshalBitArray(maxs[1].ValidatorSet)) + + // test merge vote + s.MergePrepareVote(0, 2, &protocols.PrepareVote{ + BlockIndex: 0, + ValidatorIndex: 5, + }) + assert.Equal(t, 5, s.RGQuorumCertsLen(0, 2)) + max, parentQC = s.FindMaxGroupRGQuorumCert(0, 2) + assert.Equal(t, `"____xx"`, marshalBitArray(max.ValidatorSet)) + // test merge vote + s.MergePrepareVote(2, 2, &protocols.PrepareVote{ + BlockIndex: 2, + ValidatorIndex: 2, + }) + assert.Equal(t, 4, s.RGQuorumCertsLen(2, 2)) + // test merge vote + s.MergePrepareVote(2, 2, &protocols.PrepareVote{ + BlockIndex: 2, + ValidatorIndex: 0, + }) + assert.Equal(t, 3, s.RGQuorumCertsLen(2, 2)) + // test merge vote + s.MergePrepareVote(2, 2, &protocols.PrepareVote{ + BlockIndex: 2, + ValidatorIndex: 3, + }) + assert.Equal(t, 1, s.RGQuorumCertsLen(2, 2)) + max, _ = s.FindMaxGroupRGQuorumCert(2, 2) + assert.Equal(t, `"xxxxxx"`, marshalBitArray(max.ValidatorSet)) + //a := s.FindRGQuorumCerts(2, 2) + //for _, v := range a { + // fmt.Println(marshalBitArray(v.ValidatorSet)) + //} +} + +func TestViewRGViewChangeQuorumCerts(t *testing.T) { + testCases := []struct { + groupID uint32 + validatorIndex uint32 + }{ + {1, 11}, + {2, 22}, + {1, 12}, + {3, 33}, + {5, 55}, + {1, 11}, // duplicate data + {2, 22}, // duplicate data + {2, 23}, + {1, 12}, // duplicate data } - for i, _ := range bitArrayStr { - s.AddRGQuorumCerts(0, 2, &ctypes.QuorumCert{ - BlockIndex: 0, - ValidatorSet: bitArray(i), - }, nil) + + v := newViewRGViewChangeQuorumCerts() + for _, c := range testCases { + v.AddRGViewChangeQuorumCerts(&protocols.RGViewChangeQuorumCert{ + GroupID: c.groupID, + ViewChangeQC: &ctypes.ViewChangeQC{}, + ValidatorIndex: c.validatorIndex, + }) } - for i, _ := range bitArrayStr { - s.AddRGQuorumCerts(1, 1, &ctypes.QuorumCert{ - BlockIndex: 0, - ValidatorSet: bitArray(i), - }, nil) + + //fmt.Println(v.String()) + + assert.Equal(t, 2, v.RGViewChangeQuorumCertsLen(1)) + assert.Equal(t, 2, v.RGViewChangeQuorumCertsLen(2)) + assert.Equal(t, 1, v.RGViewChangeQuorumCertsLen(3)) + assert.Equal(t, 0, v.RGViewChangeQuorumCertsLen(4)) + assert.Equal(t, 1, v.RGViewChangeQuorumCertsLen(5)) + + rg := v.FindRGViewChangeQuorumCerts(1, 12) + assert.NotNil(t, rg) + assert.Equal(t, uint32(12), rg.ValidatorIndex) + + assert.Equal(t, []uint32{22, 23}, v.RGViewChangeQuorumCertsIndexes(2)) +} + +func TestSelectedRGViewChangeQuorumCerts(t *testing.T) { + testCases := []struct { + groupID uint32 + blockNumber int64 + ValidatorSetStr string + }{ + {0, 1, `"x___________"`}, + {0, 1, `"xxxx________"`}, + {0, 1, `"xx__________"`}, + {0, 1, `"x_x_x_______"`}, + {0, 1, `"xx__x_______"`}, + {0, 1, `"xxx_x_______"`}, + {0, 2, `"______x_____"`}, + {0, 2, `"______xxxx__"`}, + {0, 2, `"______xx____"`}, + {0, 2, `"______x_x_x_"`}, + {0, 2, `"______xx__x_"`}, + {0, 2, `"______xxx_x_"`}, + + {2, 1, `"x___________"`}, + {2, 1, `"xxxx________"`}, + {2, 1, `"xx__________"`}, + {2, 1, `"x_x_x_______"`}, + {2, 1, `"xx__x_______"`}, + {2, 1, `"xxx_x_______"`}, + {2, 3, `"______x_____"`}, + {2, 3, `"______xxxx__"`}, + {2, 3, `"______xx____"`}, + {2, 3, `"______x_x_x_"`}, + {2, 3, `"______xx__x_"`}, + {2, 3, `"______xxx_x_"`}, } - c, _ := json.Marshal(s) - fmt.Println(string(c)) - assert.Equal(t, 2, len(s.FindRGQuorumCerts(0, 1))) - assert.Equal(t, 2, len(s.FindRGQuorumCerts(0, 2))) - assert.Equal(t, 2, len(s.FindRGQuorumCerts(1, 1))) + bitArray := func(bitArrayStr string) *utils.BitArray { + var ba *utils.BitArray + json.Unmarshal([]byte(bitArrayStr), &ba) + return ba + + } + + marshalBitArray := func(arr *utils.BitArray) string { + if b, err := json.Marshal(arr); err == nil { + return string(b) + } + return "" + } + + s := newSelectedRGViewChangeQuorumCerts() + for _, c := range testCases { + hash := common.BigToHash(big.NewInt(int64(c.blockNumber))) + rgqcs := map[common.Hash]*ctypes.ViewChangeQuorumCert{ + hash: { + BlockNumber: uint64(c.blockNumber), + BlockHash: hash, + Signature: ctypes.Signature{}, + ValidatorSet: bitArray(c.ValidatorSetStr), + }, + } + prepareQCs := map[common.Hash]*ctypes.QuorumCert{ + hash: { + BlockNumber: uint64(c.blockNumber), + BlockHash: hash, + }, + } + s.AddRGViewChangeQuorumCerts(c.groupID, rgqcs, prepareQCs) + } + + //fmt.Println(s.String()) + + assert.Equal(t, 2, len(s.findRGQuorumCerts(0))) + assert.Equal(t, 0, len(s.findRGQuorumCerts(1))) + assert.Equal(t, 0, s.RGViewChangeQuorumCertsLen(1)) + assert.Equal(t, 2, s.RGViewChangeQuorumCertsLen(2)) + viewChangeQC, prepareQCs := s.FindMaxGroupRGViewChangeQuorumCert(0) + assert.Equal(t, 2, len(viewChangeQC.QCs)) + for _, qc := range viewChangeQC.QCs { + if qc.BlockNumber == uint64(1) { + assert.Equal(t, `"xxx_x_______"`, marshalBitArray(qc.ValidatorSet)) + } else if qc.BlockNumber == uint64(2) { + assert.Equal(t, `"______xxx_x_"`, marshalBitArray(qc.ValidatorSet)) + } + } + + assert.Equal(t, 2, len(prepareQCs.QCs)) + //assert.Equal(t, uint64(1), prepareQCs.QCs[0].BlockNumber) + //assert.Equal(t, uint64(2), prepareQCs.QCs[1].BlockNumber) + + maxs := s.FindMaxRGViewChangeQuorumCert() + assert.Equal(t, 2, len(maxs)) + if maxs[1].QCs[0].BlockNumber == uint64(1) { + assert.Equal(t, `"xxx_x_______"`, marshalBitArray(maxs[1].QCs[0].ValidatorSet)) + } else if maxs[1].QCs[0].BlockNumber == uint64(3) { + assert.Equal(t, `"______xxx_x_"`, marshalBitArray(maxs[1].QCs[0].ValidatorSet)) + } + + // merge viewchange + s.MergeViewChange(0, &protocols.ViewChange{ + BlockNumber: 3, + BlockHash: common.BigToHash(big.NewInt(int64(3))), + ValidatorIndex: 6, + }, 12) + assert.Equal(t, 3, s.RGViewChangeQuorumCertsLen(0)) + viewChangeQC, prepareQCs = s.FindMaxGroupRGViewChangeQuorumCert(0) + assert.Equal(t, 3, len(viewChangeQC.QCs)) + for _, qc := range viewChangeQC.QCs { + if qc.BlockNumber == uint64(1) { + assert.Equal(t, `"xxx_x_______"`, marshalBitArray(qc.ValidatorSet)) + } else if qc.BlockNumber == uint64(2) { + assert.Equal(t, `"______xxx_x_"`, marshalBitArray(qc.ValidatorSet)) + } else if qc.BlockNumber == uint64(3) { + assert.Equal(t, `"______x_____"`, marshalBitArray(qc.ValidatorSet)) + } + } + assert.Equal(t, 3, len(prepareQCs.QCs)) + + // merge viewchange + s.MergeViewChange(0, &protocols.ViewChange{ + BlockNumber: 1, + BlockHash: common.BigToHash(big.NewInt(int64(1))), + ValidatorIndex: 3, + }, 12) + //fmt.Println(s.String()) + m := s.findRGQuorumCerts(0) + v, ok := m[common.BigToHash(big.NewInt(int64(1)))] + assert.True(t, true, ok) + assert.Equal(t, 1, len(v)) + assert.Equal(t, `"xxxxx_______"`, marshalBitArray(v[0].ValidatorSet)) } diff --git a/consensus/cbft/types/crypto.go b/consensus/cbft/types/crypto.go index 555a2b798e..f189768027 100644 --- a/consensus/cbft/types/crypto.go +++ b/consensus/cbft/types/crypto.go @@ -360,7 +360,10 @@ type PrepareQCs struct { QCs []*QuorumCert `json:"qcs"` } -func (p PrepareQCs) FindPrepareQC(hash common.Hash) *QuorumCert { +func (p *PrepareQCs) FindPrepareQC(hash common.Hash) *QuorumCert { + if p == nil || len(p.QCs) <= 0 { + return nil + } for _, qc := range p.QCs { if qc.BlockHash == hash { return qc @@ -369,10 +372,17 @@ func (p PrepareQCs) FindPrepareQC(hash common.Hash) *QuorumCert { return nil } -func (p PrepareQCs) FlattenMap() map[common.Hash]*QuorumCert { +func (p *PrepareQCs) FlattenMap() map[common.Hash]*QuorumCert { + if p == nil || len(p.QCs) <= 0 { + return nil + } m := make(map[common.Hash]*QuorumCert) for _, qc := range p.QCs { m[qc.BlockHash] = qc } return m } + +func (p *PrepareQCs) AppendQuorumCert(qc *QuorumCert) { + p.QCs = append(p.QCs, qc) +} diff --git a/consensus/cbft/types/crypto_test.go b/consensus/cbft/types/crypto_test.go index fbc4790bdd..9dedb6a367 100644 --- a/consensus/cbft/types/crypto_test.go +++ b/consensus/cbft/types/crypto_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package types import ( @@ -25,6 +24,7 @@ import ( "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" "github.com/AlayaNetwork/Alaya-Go/common" + "github.com/AlayaNetwork/Alaya-Go/crypto/bls" ) func Test_QuorumCert(t *testing.T) { From 4d54e2f59e9d5d47f54ae78c6bf4f2ad419dee5c Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Thu, 18 Nov 2021 16:22:36 +0800 Subject: [PATCH 056/299] update --- consensus/cbft/cbft.go | 16 +++++------ consensus/cbft/consensus_process.go | 16 ++++++++--- consensus/cbft/rg_broadcast.go | 22 ++++++++++------ consensus/cbft/types/crypto_test.go | 38 +++++++++++++++++++++++++++ consensus/cbft/validator/validator.go | 38 +++++++++++++-------------- core/cbfttypes/type.go | 15 +++++------ 6 files changed, 98 insertions(+), 47 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index f2d254cf79..524baa6a4e 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -261,7 +261,7 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCache consensus.BlockC cbft.state.SetHighestCommitBlock(block) // init RGMsg broadcast manager - cbft.RGBroadcastManager = NewRGBroadcastManager(cbft, 1) // TODO cycleNumber + cbft.RGBroadcastManager = NewRGBroadcastManager(cbft) // init handler and router to process message. // cbft -> handler -> router. @@ -1118,6 +1118,7 @@ func (cbft *Cbft) Close() error { cbft.asyncExecutor.Stop() } cbft.bridge.Close() + cbft.RGBroadcastManager.Close() return nil } @@ -1938,12 +1939,6 @@ func (cbft *Cbft) verifyQuorumCert(qc *ctypes.QuorumCert) error { func (cbft *Cbft) validateViewChangeQC(viewChangeQC *ctypes.ViewChangeQC, validatorLimit int) error { - // TODO - // 校验 ValidatorSet 是否重复 - - //vcEpoch, _, _, _, _, _ := viewChangeQC.MaxBlock() - - //maxLimit := cbft.validatorPool.Len(vcEpoch) if len(viewChangeQC.QCs) > validatorLimit { return fmt.Errorf("viewchangeQC exceed validator max limit, total:%d, validatorLimit:%d", len(viewChangeQC.QCs), validatorLimit) } @@ -1952,7 +1947,12 @@ func (cbft *Cbft) validateViewChangeQC(viewChangeQC *ctypes.ViewChangeQC, valida // check signature number signsTotal := viewChangeQC.Len() if signsTotal < threshold { - return fmt.Errorf("viewchange has small number of signature total:%d, threshold:%d", signsTotal, threshold) + return fmt.Errorf("viewchangeQC has small number of signature, total:%d, threshold:%d", signsTotal, threshold) + } + // check for duplicate signers + anoherTotal := viewChangeQC.ValidatorSet().HasLength() + if signsTotal != anoherTotal { + return fmt.Errorf("viewchangeQC has duplicate signers, signsTotal:%d, anoherTotal:%d", signsTotal, anoherTotal) } var err error diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 7cad6b5c46..e82773072d 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -244,10 +244,17 @@ func (cbft *Cbft) enoughSigns(epoch uint64, groupID uint32, signs int) bool { // Determine whether the signer of the RGBlockQuorumCert message is a member of the group func (cbft *Cbft) isGroupMember(epoch uint64, groupID uint32, nodeIndex uint32) bool { - if v, err := cbft.validatorPool.GetValidatorByGroupIdAndIndex(epoch, nodeIndex); err != nil || v == nil { + // Index collection of the group members + indexes, err := cbft.validatorPool.GetValidatorIndexesByGroupID(epoch, groupID) + if err != nil || indexes == nil { return false } - return true + for _, index := range indexes { + if index == nodeIndex { + return true + } + } + return false } // Determine whether the aggregate signers in the RGBlockQuorumCert message are all members of the group @@ -283,8 +290,9 @@ func (cbft *Cbft) AllowRGQuorumCert(msg ctypes.ConsensusMsg) error { validatorSet = rg.BlockQC.ValidatorSet case *protocols.RGViewChangeQuorumCert: groupID = rg.GroupID - signsTotal = rg.ViewChangeQC.Len() + //signsTotal = rg.ViewChangeQC.Len() validatorSet = rg.ViewChangeQC.ValidatorSet() + signsTotal = validatorSet.HasLength() } if !cbft.enoughSigns(epoch, groupID, signsTotal) { @@ -1305,7 +1313,6 @@ func (cbft *Cbft) changeView(epoch, viewNumber uint64, block *types.Block, qc *c cbft.csPool.Purge(epoch, viewNumber) cbft.state.ResetView(epoch, viewNumber) - cbft.RGBroadcastManager.Reset(1) // TODO cycleNumber cbft.state.SetViewTimer(interval()) cbft.state.SetLastViewChangeQC(viewChangeQC) @@ -1318,6 +1325,7 @@ func (cbft *Cbft) changeView(epoch, viewNumber uint64, block *types.Block, qc *c if !cbft.isLoading() { cbft.bridge.ConfirmViewChange(epoch, viewNumber, block, qc, viewChangeQC, preEpoch, preViewNumber) } + cbft.RGBroadcastManager.Reset() cbft.clearInvalidBlocks(block) cbft.evPool.Clear(epoch, viewNumber) // view change maybe lags behind the other nodes,active sync prepare block diff --git a/consensus/cbft/rg_broadcast.go b/consensus/cbft/rg_broadcast.go index 5d6e91b121..a269c8e275 100644 --- a/consensus/cbft/rg_broadcast.go +++ b/consensus/cbft/rg_broadcast.go @@ -51,8 +51,6 @@ type awaitingJob struct { type RGBroadcastManager struct { cbft *Cbft - cycleNumber int64 - delayDuration time.Duration // A collection of RGBlockQuorumCert messages waiting to be sent @@ -77,11 +75,15 @@ type RGBroadcastManager struct { } // NewBridge creates a new Bridge to update consensus state and consensus msg. -func NewRGBroadcastManager(cbft *Cbft, cycleNumber int64) *RGBroadcastManager { +func NewRGBroadcastManager(cbft *Cbft) *RGBroadcastManager { + _, unitID, err := cbft.getGroupByValidatorID(cbft.state.Epoch(), cbft.Node().ID()) + if err != nil { + cbft.log.Trace("The current node is not a consensus node, no need to start RGBroadcastManager", "epoch", cbft.state.Epoch(), "nodeID", cbft.Node().ID().String()) + unitID = 0 + } m := &RGBroadcastManager{ cbft: cbft, - cycleNumber: cycleNumber, - delayDuration: time.Duration(cycleNumber) * coordinatorWaitTimeout, + delayDuration: time.Duration(unitID) * coordinatorWaitTimeout, awaitingRGBlockQuorumCerts: make(map[uint64]*awaitingJob), hadSendRGBlockQuorumCerts: make(map[uint64]*protocols.RGBlockQuorumCert), awaitingRGViewChangeQuorumCerts: make(map[uint64]*awaitingJob), @@ -348,7 +350,7 @@ func (m *RGBroadcastManager) AsyncSendRGQuorumCert(a awaiting) { } } -func (m *RGBroadcastManager) Reset(cycleNumber int64) { +func (m *RGBroadcastManager) Reset() { m.mux.Lock() defer m.mux.Unlock() @@ -358,8 +360,12 @@ func (m *RGBroadcastManager) Reset(cycleNumber int64) { for _, await := range m.awaitingRGViewChangeQuorumCerts { await.jobTimer.Stop() } - m.cycleNumber = cycleNumber - m.delayDuration = time.Duration(cycleNumber) * coordinatorWaitTimeout + _, unitID, err := m.cbft.getGroupByValidatorID(m.cbft.state.Epoch(), m.cbft.Node().ID()) + if err != nil { + m.cbft.log.Trace("The current node is not a consensus node, no need to start RGBroadcastManager", "epoch", m.cbft.state.Epoch(), "nodeID", m.cbft.Node().ID().String()) + unitID = 0 + } + m.delayDuration = time.Duration(unitID) * coordinatorWaitTimeout m.awaitingRGBlockQuorumCerts = make(map[uint64]*awaitingJob) m.hadSendRGBlockQuorumCerts = make(map[uint64]*protocols.RGBlockQuorumCert) m.awaitingRGViewChangeQuorumCerts = make(map[uint64]*awaitingJob) diff --git a/consensus/cbft/types/crypto_test.go b/consensus/cbft/types/crypto_test.go index 9dedb6a367..f7d393ba62 100644 --- a/consensus/cbft/types/crypto_test.go +++ b/consensus/cbft/types/crypto_test.go @@ -17,6 +17,7 @@ package types import ( + "encoding/json" "testing" "github.com/stretchr/testify/assert" @@ -107,6 +108,43 @@ func Test_ViewChangeQC_MaxBlock(t *testing.T) { assert.Equal(t, uint64(0), epoch) } +func TestValidatorSet(t *testing.T) { + testCases := []struct { + ValidatorSetStr string + }{ + {`"x_x_x_xxxx"`}, + {`"xxxxxx"`}, + {`"xx__________"`}, + {`"x_x_x_______"`}, + {`"xx__x_______"`}, + {`"x_x_x_xxxx"`}, + {`"______x_____"`}, + {`"______xxxx__"`}, + {`"______xx____"`}, + {`"______x_x_x_"`}, + {`"______xx__x_"`}, + {`"______xxx_x____"`}, + } + + bitArray := func(bitArrayStr string) *utils.BitArray { + var ba *utils.BitArray + json.Unmarshal([]byte(bitArrayStr), &ba) + return ba + + } + + viewChangeQC := &ViewChangeQC{QCs: make([]*ViewChangeQuorumCert, 0)} + for _, c := range testCases { + qc := &ViewChangeQuorumCert{ + ValidatorSet: bitArray(c.ValidatorSetStr), + } + viewChangeQC.QCs = append(viewChangeQC.QCs, qc) + } + assert.Equal(t, 45, viewChangeQC.Len()) + assert.Equal(t, uint32(15), viewChangeQC.ValidatorSet().Size()) + assert.Equal(t, 11, viewChangeQC.ValidatorSet().HasLength()) +} + func TestQuorumCertAddSign(t *testing.T) { bls.Init(int(bls.BLS12_381)) message := "test merge sign" diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 0305ef51b2..ba56acc98f 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -295,7 +295,7 @@ func (ia *InnerAgency) OnCommit(block *types.Block) error { // ValidatorPool a pool storing validators. type ValidatorPool struct { agency consensus.Agency - lock sync.RWMutex + lock sync.RWMutex // Current node's public key nodeID enode.ID @@ -319,18 +319,18 @@ type ValidatorPool struct { prevValidators *cbfttypes.Validators // Previous round validators currentValidators *cbfttypes.Validators // Current round validators - nextValidators *cbfttypes.Validators // Next round validators + nextValidators *cbfttypes.Validators // Next round validators } // NewValidatorPool new a validator pool. func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID enode.ID, needGroup bool, groupValidatorsLimit, coordinatorLimit uint32, eventMux *event.TypeMux) *ValidatorPool { pool := &ValidatorPool{ - agency: agency, - nodeID: nodeID, - epoch: epoch, - needGroup: needGroup, + agency: agency, + nodeID: nodeID, + epoch: epoch, + needGroup: needGroup, groupValidatorsLimit: groupValidatorsLimit, - coordinatorLimit: coordinatorLimit, + coordinatorLimit: coordinatorLimit, } // FIXME: Check `GetValidators` return error if agency.GetLastNumber(blockNumber) == blockNumber { @@ -352,7 +352,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID if pool.currentValidators.ValidBlockNumber > 0 { pool.switchPoint = pool.currentValidators.ValidBlockNumber - 1 } - if(needGroup) { + if needGroup { pool.prevValidators.Grouped(groupValidatorsLimit, coordinatorLimit, eventMux, epoch) pool.unitID = pool.currentValidators.UnitID(nodeID) } @@ -746,16 +746,16 @@ func (vp *ValidatorPool) LenByGroupID(epoch uint64, groupID uint32) int { return vp.currentValidators.MembersCount(groupID) } -// 查询指定epoch下对应nodeIndex的节点信息,没有对应信息返回nil -func (vp *ValidatorPool) GetValidatorByGroupIdAndIndex(epoch uint64, nodeIndex uint32) (*cbfttypes.ValidateNode,error) { - vp.lock.RLock() - defer vp.lock.RUnlock() - - if epoch+1 == vp.epoch { - return vp.prevValidators.FindNodeByIndex(int(nodeIndex)) - } - return vp.currentValidators.FindNodeByIndex(int(nodeIndex)) -} +//// 查询指定epoch下对应nodeIndex的节点信息,没有对应信息返回nil +//func (vp *ValidatorPool) GetValidatorByGroupIdAndIndex(epoch uint64, nodeIndex uint32) (*cbfttypes.ValidateNode,error) { +// vp.lock.RLock() +// defer vp.lock.RUnlock() +// +// if epoch+1 == vp.epoch { +// return vp.prevValidators.FindNodeByIndex(int(nodeIndex)) +// } +// return vp.currentValidators.FindNodeByIndex(int(nodeIndex)) +//} // 返回指定epoch和分组下所有共识节点的index集合,e.g. [25,26,27,28,29,30...49] func (vp *ValidatorPool) GetValidatorIndexesByGroupID(epoch uint64, groupID uint32) ([]uint32, error) { @@ -781,7 +781,7 @@ func (vp *ValidatorPool) GetCoordinatorIndexesByGroupID(epoch uint64, groupID ui validators = vp.currentValidators } if groupID >= uint32(len(validators.GroupNodes)) { - return nil, fmt.Errorf("GetCoordinatorIndexesByGroupID: wrong groupid[%d]!",groupID) + return nil, fmt.Errorf("GetCoordinatorIndexesByGroupID: wrong groupid[%d]!", groupID) } return validators.GroupNodes[groupID].Units, nil } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 736ddcc5d0..c6c6660ccf 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -77,7 +77,6 @@ type ValidateNode struct { PubKey *ecdsa.PublicKey `json:"-"` NodeID enode.ID `json:"nodeID"` BlsPubKey *bls.PublicKey `json:"blsPubKey"` - Distance int } type ValidateNodeMap map[enode.ID]*ValidateNode @@ -110,8 +109,8 @@ type Validators struct { } type NewGroupsEvent struct { - Topic string // consensus:{epoch}:{groupID} - Validators *GroupValidators + Topic string // consensus:{epoch}:{groupID} + Validators *GroupValidators } func (vn *ValidateNode) String() string { @@ -155,7 +154,7 @@ func (vs *Validators) NodeList() []enode.ID { func (vs *Validators) MembersCount(groupID uint32) int { if groupID >= uint32(len(vs.GroupNodes)) { - log.Error("MembersCount: wrong groupid","groupID",groupID) + log.Error("MembersCount: wrong groupid", "groupID", groupID) return 0 } return len(vs.GroupNodes[groupID].Nodes) @@ -163,13 +162,13 @@ func (vs *Validators) MembersCount(groupID uint32) int { func (vs *Validators) GetValidatorIndexes(groupid uint32) ([]uint32, error) { if groupid >= uint32(len(vs.GroupNodes)) { - return nil, fmt.Errorf("MembersCount: wrong groupid[%d]",groupid) + return nil, fmt.Errorf("MembersCount: wrong groupid[%d]", groupid) } ids := make([]uint32, 0) - for _,node := range vs.GroupNodes[groupid].Nodes { + for _, node := range vs.GroupNodes[groupid].Nodes { ids = append(ids, node.Index) } - return ids,nil + return ids, nil } func (vs *Validators) NodeListByIndexes(indexes []uint32) ([]*ValidateNode, error) { @@ -372,7 +371,7 @@ func (vs *Validators) Grouped(groupValidatorsLimit uint32, coordinatorLimit uint for i, gvs := range vs.GroupNodes { gvs.GroupedUnits(coordinatorLimit) topic := ConsensusGroupTopicName(epoch, uint32(i)) - eventMux.Post(NewGroupsEvent{Topic:topic, Validators: gvs}) + eventMux.Post(NewGroupsEvent{Topic: topic, Validators: gvs}) } return nil } From 487baa74c8b06c6f2491199ee77716c96c2c93a8 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Thu, 18 Nov 2021 16:59:51 +0800 Subject: [PATCH 057/299] Add pubsub unit test under network --- consensus/cbft/consensus_process.go | 5 +- consensus/cbft/network/handler.go | 8 +- consensus/cbft/network/metrics.go | 15 +-- consensus/cbft/network/metrics_test.go | 4 +- consensus/cbft/network/pubsub.go | 41 +++++--- consensus/cbft/network/pubsub_test.go | 136 +++++++++++++++++++++++++ consensus/cbft/network/router.go | 11 +- p2p/pubsub/message/mesage.go | 9 ++ p2p/pubsub/pubsub.go | 4 +- p2p/pubsub/pubsub_common.go | 16 +-- p2p/pubsub_server.go | 9 +- 11 files changed, 214 insertions(+), 44 deletions(-) create mode 100644 consensus/cbft/network/pubsub_test.go diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index cb36f85fa9..7d94df59ed 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -630,10 +630,9 @@ func (cbft *Cbft) publishTopicMsg(msg ctypes.ConsensusMsg) error { if err != nil { return fmt.Errorf("the group info of the current node is not queried, cannot publish the topic message") } - RGMsg := &network.RGMsg{Code: protocols.MessageType(msg), Data: msg} - network.MeteredWriteRGMsg(RGMsg) + network.MeteredWriteRGMsg(protocols.MessageType(msg), msg) topic := cbfttypes.ConsensusGroupTopicName(cbft.state.Epoch(), groupID) - return cbft.pubSub.Publish(topic, RGMsg) + return cbft.pubSub.Publish(topic, protocols.MessageType(msg), msg) } //func (cbft *Cbft) trySendRGBlockQuorumCert() { diff --git a/consensus/cbft/network/handler.go b/consensus/cbft/network/handler.go index eb73899030..16734ede78 100644 --- a/consensus/cbft/network/handler.go +++ b/consensus/cbft/network/handler.go @@ -441,7 +441,7 @@ func (h *EngineManager) handler(p *p2p.Peer, rw p2p.MsgReadWriter) error { } } -func (h *EngineManager) HandleRGMsg(p *peer, msg *RGMsg) error { +func (h *EngineManager) HandleRGMsg(p *peer, msg *p2p.Msg) error { // All messages cannot exceed the maximum specified by the agreement. if msg.Size > protocols.CbftProtocolMaxMsgSize { return types.ErrResp(types.ErrMsgTooLarge, "%v > %v", msg.Size, protocols.CbftProtocolMaxMsgSize) @@ -449,7 +449,8 @@ func (h *EngineManager) HandleRGMsg(p *peer, msg *RGMsg) error { switch { case msg.Code == protocols.RGBlockQuorumCertMsg: - if request, ok := msg.Data.(protocols.RGBlockQuorumCert); ok { + var request protocols.RGBlockQuorumCert + if err := msg.Decode(&request); err != nil { p.MarkMessageHash((&request).MsgHash()) MeteredReadRGMsg(msg) return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) @@ -457,7 +458,8 @@ func (h *EngineManager) HandleRGMsg(p *peer, msg *RGMsg) error { return types.ErrResp(types.ErrInvalidRGMsg, "%s: %v", "unmatched code and data", msg.Code) case msg.Code == protocols.RGViewChangeQuorumCertMsg: - if request, ok := msg.Data.(protocols.RGViewChangeQuorumCert); ok { + var request protocols.RGViewChangeQuorumCert + if err := msg.Decode(&request); err != nil { p.MarkMessageHash((&request).MsgHash()) MeteredReadRGMsg(msg) return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) diff --git a/consensus/cbft/network/metrics.go b/consensus/cbft/network/metrics.go index b510bf1cbe..41d71e772e 100644 --- a/consensus/cbft/network/metrics.go +++ b/consensus/cbft/network/metrics.go @@ -19,6 +19,7 @@ package network import ( "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" + ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/metrics" "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/rlp" @@ -141,7 +142,7 @@ func (rw *meteredMsgReadWriter) Init(version int) { rw.version = version } -func MeteredReadRGMsg(msg *RGMsg) { +func MeteredReadRGMsg(msg *p2p.Msg) { if !metrics.Enabled { return } @@ -201,23 +202,23 @@ func (rw *meteredMsgReadWriter) ReadMsg() (p2p.Msg, error) { return msg, err } -func MeteredWriteRGMsg(msg *RGMsg) { +func MeteredWriteRGMsg(code uint64, msg ctypes.ConsensusMsg) { if !metrics.Enabled { return } - size, _, err := rlp.EncodeToReader(msg.Data) + size, _, err := rlp.EncodeToReader(msg) if err != nil { return } packets, traffic := miscOutPacketsMeter, miscOutTrafficMeter switch { - case msg.Code == protocols.RGBlockQuorumCertMsg: + case code == protocols.RGBlockQuorumCertMsg: packets, traffic = propRGBlockQuorumCertOutPacketsMeter, propRGBlockQuorumCertOutTrafficMeter - common.RGBlockQuorumCertEgressTrafficMeter.Mark(int64(msg.Size)) + common.RGBlockQuorumCertEgressTrafficMeter.Mark(int64(size)) - case msg.Code == protocols.RGViewChangeQuorumCertMsg: + case code == protocols.RGViewChangeQuorumCertMsg: packets, traffic = propRGViewChangeQuorumCertOutPacketsMeter, propRGViewChangeQuorumCertOutTrafficMeter - common.RGViewChangeQuorumCertEgressTrafficMeter.Mark(int64(msg.Size)) + common.RGViewChangeQuorumCertEgressTrafficMeter.Mark(int64(size)) } packets.Mark(1) traffic.Mark(int64(size)) diff --git a/consensus/cbft/network/metrics_test.go b/consensus/cbft/network/metrics_test.go index 182d102ef9..5be16dde98 100644 --- a/consensus/cbft/network/metrics_test.go +++ b/consensus/cbft/network/metrics_test.go @@ -129,7 +129,7 @@ func Test_MeteredMsgReadWriter_ReadMsg(t *testing.T) { {protocols.RGViewChangeQuorumCertMsg, 131, 131}, } for _, v := range testRGCases { - MeteredReadRGMsg(&RGMsg{Code: v.code, Size: v.size}) + MeteredReadRGMsg(&p2p.Msg{Code: v.code, Size: v.size}) switch { case v.code == protocols.RGBlockQuorumCertMsg: assert.Equal(t, int64(1), propRGBlockQuorumCertInPacketsMeter.Count()) @@ -222,7 +222,7 @@ func TestMeteredMsgReadWriter_WriteMsg(t *testing.T) { {protocols.RGViewChangeQuorumCertMsg, newFakeRGViewChangeQuorumCert()}, } for _, v := range testRGCases { - MeteredWriteRGMsg(&RGMsg{Code: v.code, Data: v.data}) + MeteredWriteRGMsg(v.code, v.data) size, _, err := rlp.EncodeToReader(v.data) assert.Nil(t, err) switch { diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index 87775cf4dc..fad24bca4e 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -17,9 +17,11 @@ package network import ( + "bytes" "context" "errors" "fmt" + "github.com/AlayaNetwork/Alaya-Go/common" ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p" @@ -47,10 +49,9 @@ var ( ) // Group consensus message -type RGMsg struct { +type GMsg struct { Code uint64 - Size int `rlp:"-"` // Size of the raw payload - Data interface{} + Data []byte } type PubSub struct { @@ -149,7 +150,7 @@ func (ps *PubSub) Subscribe(topic string) error { func (ps *PubSub) listen(s *pubsub.Subscription) { for { - msg, err := s.Next(context.Background()) + subMsg, err := s.Next(context.Background()) if err != nil { if err != pubsub.ErrSubscriptionCancelled { ps.Cancel(s.Topic()) @@ -157,19 +158,27 @@ func (ps *PubSub) listen(s *pubsub.Subscription) { log.Error("Failed to listen to topic message", "error", err) return } - if msg != nil { - var msgData RGMsg - if err := rlp.DecodeBytes(msg.Data, &msgData); err != nil { + if subMsg != nil { + var gmsg GMsg + if err := rlp.DecodeBytes(subMsg.Data, &gmsg); err != nil { log.Error("Failed to parse topic message", "error", err) ps.Cancel(s.Topic()) return } - msgData.Size = len(msg.Data) - fromPeer, err := ps.getPeerById(msg.From.TerminalString()) + msg := p2p.Msg{ + Code: gmsg.Code, + Size: uint32(len(subMsg.Data)), + Payload: bytes.NewReader(common.CopyBytes(gmsg.Data)), + } + if ps.pss.Host().ID().ID() == subMsg.From { + log.Trace("Receive a message from myself", "fromId", subMsg.From.TerminalString()) + continue + } + fromPeer, err := ps.getPeerById(subMsg.From.TerminalString()) if err != nil { log.Error("Failed to execute getPeerById", "err", err) } else { - ps.onReceive(fromPeer, &msgData) + ps.onReceive(fromPeer, &msg) } } } @@ -188,14 +197,22 @@ func (ps *PubSub) Cancel(topic string) error { return nil } -func (ps *PubSub) Publish(topic string, data *RGMsg) error { +func (ps *PubSub) Publish(topic string, code uint64, data interface{}) error { ps.Lock() defer ps.Unlock() t := ps.topics[topic] if t == nil { return ErrNotExistsTopic } - env, err := rlp.EncodeToBytes(data) + dataEnv, err := rlp.EncodeToBytes(data) + if err != nil { + return err + } + gmsg := &GMsg{ + Code: code, + Data: dataEnv, + } + env, err := rlp.EncodeToBytes(gmsg) if err != nil { return err } diff --git a/consensus/cbft/network/pubsub_test.go b/consensus/cbft/network/pubsub_test.go new file mode 100644 index 0000000000..3042cd69d4 --- /dev/null +++ b/consensus/cbft/network/pubsub_test.go @@ -0,0 +1,136 @@ +package network + +import ( + ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/node" + "github.com/AlayaNetwork/Alaya-Go/p2p" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/params" + "github.com/stretchr/testify/assert" + "sync" + "testing" + "time" +) + +func makePubSub(handlerMsg func(p *peer, msg *p2p.Msg) error) *PubSub { + sk, err := crypto.GenerateKey() + if nil != err { + panic(err) + } + p2pServer := &p2p.Server{Config: node.DefaultConfig.P2P} + p2pServer.PrivateKey = sk + p2pServer.NoDiscovery = true + p2pServer.ListenAddr = "" + if err := p2pServer.Start(); err != nil { + panic(err) + } + localNode := enode.NewV4(&sk.PublicKey, nil, 0, 0) + psServer := p2p.NewPubSubServer(localNode, p2pServer) + pubSub := NewPubSub(psServer) + + pubSub.Init(ctypes.Config{Sys: params.AlayaChainConfig.Cbft, Option: nil}, func(id string) (p *peer, err error) { + return nil, nil + }, handlerMsg) + return pubSub +} + +type TestPSRW struct { + writeMsgChan chan p2p.Msg + readMsgChan chan p2p.Msg +} + +func (rw *TestPSRW) ReadMsg() (p2p.Msg, error) { + return <-rw.readMsgChan, nil +} + +func (rw *TestPSRW) WriteMsg(msg p2p.Msg) error { + rw.writeMsgChan <- msg + return nil +} + +func TestPubSubPublish(t *testing.T) { + type TestMsg struct { + Title string + } + + expect := []*TestMsg{ + { + Title: "n1_msg", + }, + { + Title: "n2_msg", + }, + } + + wg := sync.WaitGroup{} + wg.Add(len(expect)) + pubSub1 := makePubSub(func(p *peer, msg *p2p.Msg) error { + var tm TestMsg + err := msg.Decode(&tm) + assert.Equal(t, tm.Title, expect[1].Title) + wg.Done() + t.Log("pubSub1 receive:", "data", tm, "err", err) + return nil + }) + pubSub2 := makePubSub(func(p *peer, msg *p2p.Msg) error { + var tm TestMsg + err := msg.Decode(&tm) + assert.Equal(t, tm.Title, expect[0].Title) + wg.Done() + t.Log("pubSub2 receive:", "data", tm, "err", err) + return nil + }) + + n1Chan := make(chan p2p.Msg) + n2Chan := make(chan p2p.Msg) + trw1 := &TestPSRW{ + writeMsgChan: n1Chan, + readMsgChan: n2Chan, + } + trw2 := &TestPSRW{ + writeMsgChan: n2Chan, + readMsgChan: n1Chan, + } + + // connect peer + // peer n1 + go func() { + newPeer := p2p.NewPeer(pubSub2.pss.Host().ID().ID(), "n2", nil) + t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub1.pss.Host().ID().ID().TerminalString(), "name", "n1") + if err := pubSub1.handler(newPeer, trw1); err != nil { + t.Fatal(err) + } + }() + + // peer n2 + go func() { + newPeer := p2p.NewPeer(pubSub1.pss.Host().ID().ID(), "n1", nil) + t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub2.pss.Host().ID().ID().TerminalString(), "name", "n2") + if err := pubSub2.handler(newPeer, trw2); err != nil { + t.Fatal(err) + } + }() + time.Sleep(time.Millisecond * 6) + + // Topics of interest for node registration. + // Send messages between nodes + topic := "test" + go func() { + if err := pubSub1.Subscribe(topic); err != nil { + t.Fatal(err) + } + time.Sleep(time.Millisecond * 3) + pubSub1.Publish(topic, uint64(1), expect[0]) + }() + go func() { + if err := pubSub2.Subscribe(topic); err != nil { + t.Fatal(err) + } + time.Sleep(time.Millisecond * 3) + pubSub2.Publish(topic, uint64(2), expect[1]) + }() + wg.Wait() + pubSub1.Cancel(topic) + pubSub2.Cancel(topic) +} diff --git a/consensus/cbft/network/router.go b/consensus/cbft/network/router.go index 1a399d8b68..cbe7a65266 100644 --- a/consensus/cbft/network/router.go +++ b/consensus/cbft/network/router.go @@ -20,6 +20,7 @@ package network import ( "bytes" "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p" "math" "reflect" "sync" @@ -37,11 +38,11 @@ import ( // the number of nodes selected per broadcast. const DefaultFanOut = 5 -type unregisterFunc func(id string) error // Unregister peer from peerSet. -type getByIDFunc func(id string) (*peer, error) // Get peer based on ID. -type consensusNodesFunc func() ([]enode.ID, error) // Get a list of consensus nodes. -type peersFunc func() ([]*peer, error) // Get a list of all neighbor nodes. -type receiveCallback func(p *peer, msg *RGMsg) error // Callback function for receiving topic messages +type unregisterFunc func(id string) error // Unregister peer from peerSet. +type getByIDFunc func(id string) (*peer, error) // Get peer based on ID. +type consensusNodesFunc func() ([]enode.ID, error) // Get a list of consensus nodes. +type peersFunc func() ([]*peer, error) // Get a list of all neighbor nodes. +type receiveCallback func(p *peer, msg *p2p.Msg) error // Callback function for receiving topic messages // Router implements the message protocol of gossip. // diff --git a/p2p/pubsub/message/mesage.go b/p2p/pubsub/message/mesage.go index 877e25fd25..cb4c703c08 100644 --- a/p2p/pubsub/message/mesage.go +++ b/p2p/pubsub/message/mesage.go @@ -8,6 +8,15 @@ type RPC struct { Control *ControlMessage } +func IsEmpty(rpc *RPC) bool { + if rpc != nil { + if rpc.Subscriptions != nil || rpc.Publish != nil || rpc.Control != nil { + return false + } + } + return true +} + func (m *RPC) Size() (n int) { if m == nil { return 0 diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index ed60c610b7..8dcf8d8dcf 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -637,9 +637,9 @@ func (p *PubSub) handlePendingPeers() { p.newPeersPrioLk.Unlock() for pid := range newPeers { - if p.host.Network().Connectedness(pid) != Connected { + /*if p.host.Network().Connectedness(pid) != Connected { continue - } + }*/ if _, ok := p.peers[pid]; ok { log.Debug("already have connection to peer: ", pid) diff --git a/p2p/pubsub/pubsub_common.go b/p2p/pubsub/pubsub_common.go index d0a56d7484..caf1c6c516 100644 --- a/p2p/pubsub/pubsub_common.go +++ b/p2p/pubsub/pubsub_common.go @@ -2,7 +2,6 @@ package pubsub import ( "context" - "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" @@ -145,9 +144,11 @@ func (p *PubSub) handleSendingMessages(ctx context.Context, s Stream, outgoing < return } - if err := s.Write(&rpc.RPC); err != nil { - log.Error("Send message fail", "err", err) - return + if !message.IsEmpty(&rpc.RPC) { + if err := s.Write(&rpc.RPC); err != nil { + log.Error("Send message fail", "err", err) + return + } } /* err := writeMsg(&rpc.RPC) @@ -166,12 +167,14 @@ func rpcWithSubs(subs ...*message.RPC_SubOpts) *RPC { return &RPC{ RPC: message.RPC{ Subscriptions: subs, + Publish: make([]*message.Message, 0), + Control: &message.ControlMessage{}, }, } } func rpcWithMessages(msgs ...*message.Message) *RPC { - return &RPC{RPC: message.RPC{Publish: msgs}} + return &RPC{RPC: message.RPC{Publish: msgs, Subscriptions: make([]*message.RPC_SubOpts, 0), Control: &message.ControlMessage{}}} } func rpcWithControl(msgs []*message.Message, @@ -181,7 +184,8 @@ func rpcWithControl(msgs []*message.Message, prune []*message.ControlPrune) *RPC { return &RPC{ RPC: message.RPC{ - Publish: msgs, + Subscriptions: make([]*message.RPC_SubOpts, 0), + Publish: msgs, Control: &message.ControlMessage{ Ihave: ihave, Iwant: iwant, diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go index 9850520d2a..8c6d7f8e5c 100644 --- a/p2p/pubsub_server.go +++ b/p2p/pubsub_server.go @@ -3,19 +3,19 @@ package p2p import ( "context" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - pubsub2 "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" ) type PubSubServer struct { p2pServer *Server - pubSub *pubsub2.PubSub + pubSub *pubsub.PubSub host *Host } func NewPubSubServer(localNode *enode.Node, p2pServer *Server) *PubSubServer { network := NewNetwork(p2pServer) host := NewHost(localNode, network) - gossipSub, err := pubsub2.NewGossipSub(context.Background(), host) + gossipSub, err := pubsub.NewGossipSub(context.Background(), host) if err != nil { panic("Failed to NewGossipSub: " + err.Error()) } @@ -23,6 +23,7 @@ func NewPubSubServer(localNode *enode.Node, p2pServer *Server) *PubSubServer { return &PubSubServer{ p2pServer: p2pServer, pubSub: gossipSub, + host: host, } } @@ -30,7 +31,7 @@ func (pss *PubSubServer) Host() *Host { return pss.host } -func (pss *PubSubServer) PubSub() *pubsub2.PubSub { +func (pss *PubSubServer) PubSub() *pubsub.PubSub { return pss.pubSub } From 6b9027eec31727319c7feef82ca9e6127d992922 Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 18 Nov 2021 17:02:29 +0800 Subject: [PATCH 058/299] update validatorpool --- consensus/cbft/validator/validator.go | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 0305ef51b2..3ad42f652c 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -679,11 +679,7 @@ func (vp *ValidatorPool) Commit(block *types.Block) error { func (vp *ValidatorPool) GetGroupNodes(epoch uint64) []*cbfttypes.GroupValidators { vp.lock.RLock() defer vp.lock.RUnlock() - - if epoch > vp.epoch { - panic(fmt.Sprintf("get unknown epoch, current:%d, request:%d", vp.epoch, epoch)) - } - if epoch+1 == vp.epoch { + if vp.epochToBlockNumber(epoch) <= vp.switchPoint { return vp.prevValidators.GroupNodes } return vp.currentValidators.GroupNodes @@ -702,10 +698,7 @@ func (vp *ValidatorPool) GetGroupID(epoch uint64, nodeID enode.ID) (uint32, erro vp.lock.RLock() defer vp.lock.RUnlock() - if epoch > vp.epoch { - panic(fmt.Sprintf("get unknown epoch, current:%d, request:%d", vp.epoch, epoch)) - } - if epoch+1 == vp.epoch { + if vp.epochToBlockNumber(epoch) <= vp.switchPoint { return vp.prevValidators.GroupID(nodeID), nil } return vp.currentValidators.GroupID(nodeID), nil @@ -716,10 +709,7 @@ func (vp *ValidatorPool) GetUnitID(epoch uint64, nodeID enode.ID) (uint32, error vp.lock.RLock() defer vp.lock.RUnlock() - if epoch > vp.epoch { - panic(fmt.Sprintf("get unknown epoch, current:%d, request:%d", vp.epoch, epoch)) - } - if epoch+1 == vp.epoch { + if vp.epochToBlockNumber(epoch) <= vp.switchPoint { return vp.prevValidators.UnitID(nodeID), nil } return vp.unitID, nil @@ -740,7 +730,7 @@ func (vp *ValidatorPool) LenByGroupID(epoch uint64, groupID uint32) int { vp.lock.RLock() defer vp.lock.RUnlock() - if epoch+1 == vp.epoch { + if vp.epochToBlockNumber(epoch) <= vp.switchPoint { return vp.prevValidators.MembersCount(groupID) } return vp.currentValidators.MembersCount(groupID) @@ -751,7 +741,7 @@ func (vp *ValidatorPool) GetValidatorByGroupIdAndIndex(epoch uint64, nodeIndex u vp.lock.RLock() defer vp.lock.RUnlock() - if epoch+1 == vp.epoch { + if vp.epochToBlockNumber(epoch) <= vp.switchPoint { return vp.prevValidators.FindNodeByIndex(int(nodeIndex)) } return vp.currentValidators.FindNodeByIndex(int(nodeIndex)) @@ -762,7 +752,7 @@ func (vp *ValidatorPool) GetValidatorIndexesByGroupID(epoch uint64, groupID uint vp.lock.RLock() defer vp.lock.RUnlock() - if epoch+1 == vp.epoch { + if vp.epochToBlockNumber(epoch) <= vp.switchPoint { return vp.prevValidators.GetValidatorIndexes(groupID) } return vp.currentValidators.GetValidatorIndexes(groupID) @@ -775,11 +765,12 @@ func (vp *ValidatorPool) GetCoordinatorIndexesByGroupID(epoch uint64, groupID ui defer vp.lock.RUnlock() var validators *cbfttypes.Validators - if epoch+1 == vp.epoch { + if vp.epochToBlockNumber(epoch) <= vp.switchPoint { validators = vp.prevValidators } else { validators = vp.currentValidators } + if groupID >= uint32(len(validators.GroupNodes)) { return nil, fmt.Errorf("GetCoordinatorIndexesByGroupID: wrong groupid[%d]!",groupID) } @@ -792,7 +783,7 @@ func (vp *ValidatorPool) GetGroupByValidatorID(epoch uint64, nodeID enode.ID) (u defer vp.lock.RUnlock() var validators *cbfttypes.Validators - if epoch+1 == vp.epoch { + if vp.epochToBlockNumber(epoch) <= vp.switchPoint { validators = vp.prevValidators } else { validators = vp.currentValidators From 7c88c10170c33359835e3af998332854be0a0151 Mon Sep 17 00:00:00 2001 From: benbaley Date: Fri, 19 Nov 2021 10:42:14 +0800 Subject: [PATCH 059/299] getgroupid return err --- consensus/cbft/validator/validator.go | 25 ++++++++++--------------- core/cbfttypes/type.go | 16 +++++++--------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index cc10b2bb46..7c2c6f81c4 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -675,16 +675,6 @@ func (vp *ValidatorPool) Commit(block *types.Block) error { return vp.agency.OnCommit(block) } -// GetGroupNodes return GroupValidators according epoch -func (vp *ValidatorPool) GetGroupNodes(epoch uint64) []*cbfttypes.GroupValidators { - vp.lock.RLock() - defer vp.lock.RUnlock() - if vp.epochToBlockNumber(epoch) <= vp.switchPoint { - return vp.prevValidators.GroupNodes - } - return vp.currentValidators.GroupNodes -} - // NeedGroup return if currentValidators need grouped func (vp *ValidatorPool) NeedGroup() bool { vp.lock.RLock() @@ -699,9 +689,9 @@ func (vp *ValidatorPool) GetGroupID(epoch uint64, nodeID enode.ID) (uint32, erro defer vp.lock.RUnlock() if vp.epochToBlockNumber(epoch) <= vp.switchPoint { - return vp.prevValidators.GroupID(nodeID), nil + return vp.prevValidators.GroupID(nodeID) } - return vp.currentValidators.GroupID(nodeID), nil + return vp.currentValidators.GroupID(nodeID) } // GetUnitID return index according epoch & NodeID @@ -730,10 +720,12 @@ func (vp *ValidatorPool) LenByGroupID(epoch uint64, groupID uint32) int { vp.lock.RLock() defer vp.lock.RUnlock() + grouplen := 0 if vp.epochToBlockNumber(epoch) <= vp.switchPoint { - return vp.prevValidators.MembersCount(groupID) + grouplen, _ = vp.prevValidators.MembersCount(groupID) } - return vp.currentValidators.MembersCount(groupID) + grouplen, _ = vp.currentValidators.MembersCount(groupID) + return grouplen } //// 查询指定epoch下对应nodeIndex的节点信息,没有对应信息返回nil @@ -788,7 +780,10 @@ func (vp *ValidatorPool) GetGroupByValidatorID(epoch uint64, nodeID enode.ID) (u } else { validators = vp.currentValidators } - groupID := validators.GroupID(nodeID) + groupID, err := validators.GroupID(nodeID) + if nil != err { + return 0, 0, err + } unitID := validators.UnitID(nodeID) return groupID, unitID, nil } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index c6c6660ccf..8f62ea92f5 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -152,12 +152,11 @@ func (vs *Validators) NodeList() []enode.ID { return nodeList } -func (vs *Validators) MembersCount(groupID uint32) int { +func (vs *Validators) MembersCount(groupID uint32) (int, error) { if groupID >= uint32(len(vs.GroupNodes)) { - log.Error("MembersCount: wrong groupid", "groupID", groupID) - return 0 + return 0, fmt.Errorf("wrong groupid[%d]", groupID) } - return len(vs.GroupNodes[groupID].Nodes) + return len(vs.GroupNodes[groupID].Nodes), nil } func (vs *Validators) GetValidatorIndexes(groupid uint32) ([]uint32, error) { @@ -273,15 +272,14 @@ func (vs *Validators) Sort() { sort.Sort(vs.SortedNodes) } -func (vs *Validators) GroupID(nodeID enode.ID) uint32 { +func (vs *Validators) GroupID(nodeID enode.ID) (uint32, error) { if len(vs.SortedNodes) == 0 { vs.Sort() } idx, err := vs.Index(nodeID) if err != nil { - log.Error("get preValidator index failed!", "err", err) - return math.MaxUint32 + return math.MaxUint32, err } groupID := uint32(0) @@ -293,7 +291,7 @@ func (vs *Validators) GroupID(nodeID enode.ID) uint32 { groupID = groupID + 1 } } - return groupID + return groupID, nil } func (vs *Validators) UnitID(nodeID enode.ID) uint32 { @@ -307,7 +305,7 @@ func (vs *Validators) UnitID(nodeID enode.ID) uint32 { return idx } - groupID := vs.GroupID(nodeID) + groupID, _ := vs.GroupID(nodeID) unitID := uint32(0) for i, node := range vs.GroupNodes[groupID].Nodes { if idx == node.Index { From f2aa50a016548e609814f0ee539850bb4e89b00a Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Fri, 19 Nov 2021 11:14:56 +0800 Subject: [PATCH 060/299] from node to ReceivedFrom node --- consensus/cbft/network/pubsub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index fad24bca4e..a364cc4e62 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -174,7 +174,7 @@ func (ps *PubSub) listen(s *pubsub.Subscription) { log.Trace("Receive a message from myself", "fromId", subMsg.From.TerminalString()) continue } - fromPeer, err := ps.getPeerById(subMsg.From.TerminalString()) + fromPeer, err := ps.getPeerById(subMsg.ReceivedFrom.ID().TerminalString()) if err != nil { log.Error("Failed to execute getPeerById", "err", err) } else { From c94973f1916f963c9bc5ac420af3bc0c88d01842 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Fri, 19 Nov 2021 15:16:42 +0800 Subject: [PATCH 061/299] Add license --- consensus/cbft/network/pubsub_test.go | 20 ++++++++++++++++++++ p2p/host.go | 16 ++++++++++++++++ p2p/network.go | 16 ++++++++++++++++ p2p/pubsub_server.go | 16 ++++++++++++++++ p2p/stream.go | 16 ++++++++++++++++ 5 files changed, 84 insertions(+) diff --git a/consensus/cbft/network/pubsub_test.go b/consensus/cbft/network/pubsub_test.go index 3042cd69d4..30166fc6b6 100644 --- a/consensus/cbft/network/pubsub_test.go +++ b/consensus/cbft/network/pubsub_test.go @@ -1,3 +1,19 @@ +// Copyright 2021 The Alaya Network Authors +// This file is part of the Alaya-Go library. +// +// The Alaya-Go 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 Alaya-Go 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 Alaya-Go library. If not, see . + package network import ( @@ -134,3 +150,7 @@ func TestPubSubPublish(t *testing.T) { pubSub1.Cancel(topic) pubSub2.Cancel(topic) } + +func TestPubSubPublish_DifferentTopics(t *testing.T) { + +} diff --git a/p2p/host.go b/p2p/host.go index a4408e4439..fb7eba28f6 100644 --- a/p2p/host.go +++ b/p2p/host.go @@ -1,3 +1,19 @@ +// Copyright 2021 The Alaya Network Authors +// This file is part of the Alaya-Go library. +// +// The Alaya-Go 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 Alaya-Go 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 Alaya-Go library. If not, see . + package p2p import ( diff --git a/p2p/network.go b/p2p/network.go index df4b93f9d0..5bc3389ac7 100644 --- a/p2p/network.go +++ b/p2p/network.go @@ -1,3 +1,19 @@ +// Copyright 2021 The Alaya Network Authors +// This file is part of the Alaya-Go library. +// +// The Alaya-Go 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 Alaya-Go 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 Alaya-Go library. If not, see . + package p2p import ( diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go index 8c6d7f8e5c..e0ab81968b 100644 --- a/p2p/pubsub_server.go +++ b/p2p/pubsub_server.go @@ -1,3 +1,19 @@ +// Copyright 2021 The Alaya Network Authors +// This file is part of the Alaya-Go library. +// +// The Alaya-Go 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 Alaya-Go 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 Alaya-Go library. If not, see . + package p2p import ( diff --git a/p2p/stream.go b/p2p/stream.go index f4de13c360..0b99c70a59 100644 --- a/p2p/stream.go +++ b/p2p/stream.go @@ -1,3 +1,19 @@ +// Copyright 2021 The Alaya Network Authors +// This file is part of the Alaya-Go library. +// +// The Alaya-Go 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 Alaya-Go 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 Alaya-Go library. If not, see . + package p2p import ( From 70b72f48ee9e4441e1a56e50a369ac60a9877668 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Fri, 19 Nov 2021 16:18:38 +0800 Subject: [PATCH 062/299] Message sending of different topics between multiple nodes --- consensus/cbft/network/pubsub_test.go | 135 +++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 4 deletions(-) diff --git a/consensus/cbft/network/pubsub_test.go b/consensus/cbft/network/pubsub_test.go index 30166fc6b6..c1e7c600c1 100644 --- a/consensus/cbft/network/pubsub_test.go +++ b/consensus/cbft/network/pubsub_test.go @@ -65,11 +65,11 @@ func (rw *TestPSRW) WriteMsg(msg p2p.Msg) error { return nil } -func TestPubSubPublish(t *testing.T) { - type TestMsg struct { - Title string - } +type TestMsg struct { + Title string +} +func TestPubSubPublish(t *testing.T) { expect := []*TestMsg{ { Title: "n1_msg", @@ -152,5 +152,132 @@ func TestPubSubPublish(t *testing.T) { } func TestPubSubPublish_DifferentTopics(t *testing.T) { + expect := []*TestMsg{ + { + Title: "n1_msg", + }, + { + Title: "n2_msg", + }, + { + Title: "n3_msg", + }, + } + + wg := sync.WaitGroup{} + wg.Add(len(expect)) + pubSub1 := makePubSub(func(p *peer, msg *p2p.Msg) error { + var tm TestMsg + err := msg.Decode(&tm) + if tm.Title != expect[1].Title && tm.Title != expect[2].Title { + t.Fatal("Expected value not received") + } + wg.Done() + t.Log("pubSub1 receive:", "data", tm, "err", err) + return nil + }) + pubSub2 := makePubSub(func(p *peer, msg *p2p.Msg) error { + var tm TestMsg + err := msg.Decode(&tm) + assert.Equal(t, tm.Title, expect[0].Title) + wg.Done() + t.Log("pubSub2 receive:", "data", tm, "err", err) + return nil + }) + pubSub3 := makePubSub(func(p *peer, msg *p2p.Msg) error { + var tm TestMsg + err := msg.Decode(&tm) + assert.Equal(t, tm.Title, expect[0].Title) + wg.Done() + t.Log("pubSub3 receive:", "data", tm, "err", err) + return nil + }) + + n1_2Chan := make(chan p2p.Msg) + n2_1Chan := make(chan p2p.Msg) + trw1 := &TestPSRW{ + writeMsgChan: n1_2Chan, + readMsgChan: n2_1Chan, + } + trw2 := &TestPSRW{ + writeMsgChan: n2_1Chan, + readMsgChan: n1_2Chan, + } + + n1_3Chan := make(chan p2p.Msg) + n3_1Chan := make(chan p2p.Msg) + trw1_3 := &TestPSRW{ + writeMsgChan: n1_3Chan, + readMsgChan: n3_1Chan, + } + trw3_1 := &TestPSRW{ + writeMsgChan: n3_1Chan, + readMsgChan: n1_3Chan, + } + // connect peer + // peer1 <-> peer2 + go func() { + newPeer := p2p.NewPeer(pubSub2.pss.Host().ID().ID(), "n2", nil) + t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub1.pss.Host().ID().ID().TerminalString()) + if err := pubSub1.handler(newPeer, trw1); err != nil { + t.Fatal(err) + } + }() + go func() { + newPeer := p2p.NewPeer(pubSub1.pss.Host().ID().ID(), "n1", nil) + t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub2.pss.Host().ID().ID().TerminalString()) + if err := pubSub2.handler(newPeer, trw2); err != nil { + t.Fatal(err) + } + }() + + // peer1 <-> peer3 + go func() { + newPeer := p2p.NewPeer(pubSub3.pss.Host().ID().ID(), "n3", nil) + t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub1.pss.Host().ID().ID().TerminalString()) + if err := pubSub1.handler(newPeer, trw1_3); err != nil { + t.Fatal(err) + } + }() + go func() { + newPeer := p2p.NewPeer(pubSub1.pss.Host().ID().ID(), "n1", nil) + t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub3.pss.Host().ID().ID().TerminalString()) + if err := pubSub3.handler(newPeer, trw3_1); err != nil { + t.Fatal(err) + } + }() + time.Sleep(time.Millisecond * 6) + + topic1 := "test1" + topic2 := "test2" + go func() { + if err := pubSub1.Subscribe(topic1); err != nil { + t.Fatal(err) + } + if err := pubSub1.Subscribe(topic2); err != nil { + t.Fatal(err) + } + time.Sleep(time.Millisecond * 3) + pubSub1.Publish(topic1, uint64(1), expect[0]) + }() + go func() { + if err := pubSub2.Subscribe(topic1); err != nil { + t.Fatal(err) + } + time.Sleep(time.Millisecond * 3) + pubSub2.Publish(topic1, uint64(2), expect[1]) + }() + go func() { + if err := pubSub3.Subscribe(topic2); err != nil { + t.Fatal(err) + } + time.Sleep(time.Millisecond * 3) + pubSub3.Publish(topic2, uint64(3), expect[2]) + }() + wg.Wait() + pubSub1.Cancel(topic1) + pubSub1.Cancel(topic2) + pubSub2.Cancel(topic1) + pubSub3.Cancel(topic2) } From 0c7a3a5884f6de6d94d69817187dce07966e947a Mon Sep 17 00:00:00 2001 From: benbaley Date: Fri, 19 Nov 2021 16:50:52 +0800 Subject: [PATCH 063/299] commitblock add logic ->validatorpool update nextvalidators --- consensus/cbft/cbft.go | 16 +++++++++++++ consensus/cbft/consensus_process.go | 12 ---------- consensus/cbft/validator/validator.go | 28 +++++++++++++++------- consensus/cbft/validator/validator_test.go | 2 +- core/cbfttypes/type.go | 10 ++++---- miner/worker.go | 3 --- x/xutil/calculate.go | 1 + 7 files changed, 43 insertions(+), 29 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 524baa6a4e..be15dfe88c 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -22,6 +22,7 @@ import ( "crypto/elliptic" "encoding/json" "fmt" + "github.com/AlayaNetwork/Alaya-Go/x/xutil" "strings" "sync/atomic" @@ -1349,6 +1350,21 @@ func (cbft *Cbft) commitBlock(commitBlock *types.Block, commitQC *ctypes.QuorumC SyncState: cbft.commitErrCh, ChainStateUpdateCB: func() { cbft.bridge.UpdateChainState(qcState, lockState, commitState) }, }) + + // should grouped according max commit block's state + shouldGroup := func() bool { + activeVersion := cbft.blockCache.GetActiveVersion(cbft.state.HighestCommitBlock().Header().SealHash()) + return cbft.validatorPool.NeedGroup() || activeVersion >= params.FORKVERSION_0_17_0 + } + + // post NewGroupsEvent to join topic according group info + if xutil.IsElection(cpy.NumberU64()) { + // TODO: get groupvalidatorslimit and coordinatorlimit from gov + if shouldGroup() { + cbft.validatorPool.SetupGroup(true, 0, 0) + } + cbft.validatorPool.Update(cpy.NumberU64(), cbft.state.Epoch()+1, cbft.eventMux) + } } // Evidences implements functions in API. diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index e82773072d..89ca6f4e10 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -19,7 +19,6 @@ package cbft import ( "fmt" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/network" - "github.com/AlayaNetwork/Alaya-Go/params" "time" "github.com/pkg/errors" @@ -1105,12 +1104,6 @@ func (cbft *Cbft) tryChangeView() { (qc != nil && qc.Epoch == cbft.state.Epoch() && shouldSwitch) }() - // should grouped according max commit block's state - shouldGroup := func() bool { - activeVersion := cbft.blockCache.GetActiveVersion(cbft.state.HighestCommitBlock().Header().SealHash()) - return cbft.validatorPool.NeedGroup() || activeVersion >= params.FORKVERSION_0_17_0 - } - if shouldSwitch { if err := cbft.validatorPool.Update(block.NumberU64(), cbft.state.Epoch()+1, cbft.eventMux); err == nil { cbft.log.Info("Update validator success", "number", block.NumberU64()) @@ -1130,11 +1123,6 @@ func (cbft *Cbft) tryChangeView() { return } - // TODO: get groupvalidatorslimit and coordinatorlimit from gov - if shouldGroup() { - cbft.validatorPool.SetupGroup(true, 0, 0) - } - threshold := cbft.threshold(cbft.currentValidatorLen()) var ( enoughViewChangesFlag bool diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 7c2c6f81c4..f0b38c32f5 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -20,6 +20,7 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/AlayaNetwork/Alaya-Go/x/xutil" "sync" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" @@ -417,8 +418,9 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *even vp.lock.Lock() defer vp.lock.Unlock() - // Only updated once - if blockNumber <= vp.switchPoint { + isElection := xutil.IsElection(blockNumber) + // Election block update nextValidators + if blockNumber <= vp.switchPoint && !isElection { log.Debug("Already update validator before", "blockNumber", blockNumber, "switchPoint", vp.switchPoint) return errors.New("already updated before") } @@ -428,21 +430,31 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *even log.Error("Get validator error", "blockNumber", blockNumber, "err", err) return err } + if vp.needGroup { nds.Grouped(vp.groupValidatorsLimit, vp.coordinatorLimit, eventMux, epoch) vp.unitID = nds.UnitID(vp.nodeID) } - vp.prevValidators = vp.currentValidators - vp.currentValidators = nds - vp.switchPoint = nds.ValidBlockNumber - 1 - vp.lastNumber = vp.agency.GetLastNumber(NextRound(blockNumber)) - vp.epoch = epoch - log.Info("Update validator", "validators", nds.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) + + if isElection && vp.needGroup { + vp.nextValidators = nds + log.Info("Update nextValidators", "validators", nds.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) + } else { + vp.prevValidators = vp.currentValidators + vp.currentValidators = nds + vp.switchPoint = nds.ValidBlockNumber - 1 + vp.lastNumber = vp.agency.GetLastNumber(NextRound(blockNumber)) + vp.epoch = epoch + log.Info("Update validator", "validators", nds.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) + } return nil } // SetupGroup update validatorpool group info func (vp *ValidatorPool) SetupGroup(needGroup bool, groupValidatorsLimit, coordinatorLimit uint32) { + if vp.needGroup { + return + } vp.lock.Lock() defer vp.lock.Unlock() diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index 0a8da3c3a8..729524cb3e 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -646,7 +646,7 @@ func TestGetGroupID(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNodeByNum(100) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0, nil) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),true, 25, 5, nil) grpID, _ := vp.GetGroupID(0, nodes[0].Node.ID()) assert.Equal(t, 0, grpID) diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 8f62ea92f5..79f0c5e843 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -69,6 +69,11 @@ type RemoveValidatorEvent struct { Node *enode.Node } +type NewGroupsEvent struct { + Topic string // consensus:{epoch}:{groupID} + Validators *GroupValidators +} + type UpdateValidatorEvent struct{} type ValidateNode struct { @@ -108,11 +113,6 @@ type Validators struct { GroupNodes []*GroupValidators `json:"groupNodes"` } -type NewGroupsEvent struct { - Topic string // consensus:{epoch}:{groupID} - Validators *GroupValidators -} - func (vn *ValidateNode) String() string { b, _ := json.Marshal(vn) return string(b) diff --git a/miner/worker.go b/miner/worker.go index ef0246a2a6..5fcbb3bf4e 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -349,9 +349,6 @@ func (w *worker) newWorkLoop(recommit time.Duration) { timestamp time.Time // timestamp for each round of mining. ) - vdEvent := w.mux.Subscribe(cbfttypes.UpdateValidatorEvent{}) - defer vdEvent.Unsubscribe() - timer := time.NewTimer(0) <-timer.C // discard the initial tick diff --git a/x/xutil/calculate.go b/x/xutil/calculate.go index c3ba19fab4..b2e5cbaced 100644 --- a/x/xutil/calculate.go +++ b/x/xutil/calculate.go @@ -204,6 +204,7 @@ func IsEndOfEpoch(blockNumber uint64) bool { return mod == 0 } +// TODO update this function according group consensus @clearly func IsElection(blockNumber uint64) bool { tmp := blockNumber + xcom.ElectionDistance() mod := tmp % ConsensusSize() From 6ab3d8c59d724ca266d5b0fbfd6fea3f5acdd70d Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Fri, 19 Nov 2021 17:23:31 +0800 Subject: [PATCH 064/299] Complete pubsub unit test --- consensus/cbft/network/pubsub_test.go | 130 ++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/consensus/cbft/network/pubsub_test.go b/consensus/cbft/network/pubsub_test.go index c1e7c600c1..c84857d21a 100644 --- a/consensus/cbft/network/pubsub_test.go +++ b/consensus/cbft/network/pubsub_test.go @@ -281,3 +281,133 @@ func TestPubSubPublish_DifferentTopics(t *testing.T) { pubSub2.Cancel(topic1) pubSub3.Cancel(topic2) } + +func TestPubSubPublish_ForwardMessage(t *testing.T) { + expect := []*TestMsg{ + { + Title: "n1_msg", + }, + { + Title: "n2_msg", + }, + { + Title: "n3_msg", + }, + } + + wg := sync.WaitGroup{} + wg.Add(6) + pubSub1 := makePubSub(func(p *peer, msg *p2p.Msg) error { + var tm TestMsg + err := msg.Decode(&tm) + if tm.Title != expect[1].Title && tm.Title != expect[2].Title { + t.Fatal("Expected value not received") + } + t.Log("pubSub1 receive:", "data", tm, "err", err) + wg.Done() + return nil + }) + pubSub2 := makePubSub(func(p *peer, msg *p2p.Msg) error { + var tm TestMsg + err := msg.Decode(&tm) + if tm.Title != expect[0].Title && tm.Title != expect[2].Title { + t.Fatal("Expected value not received") + } + t.Log("pubSub2 receive:", "data", tm, "err", err) + wg.Done() + return nil + }) + pubSub3 := makePubSub(func(p *peer, msg *p2p.Msg) error { + var tm TestMsg + err := msg.Decode(&tm) + if tm.Title != expect[0].Title && tm.Title != expect[1].Title { + t.Fatal("Expected value not received") + } + t.Log("pubSub3 receive:", "data", tm, "err", err) + wg.Done() + return nil + }) + + n1_2Chan := make(chan p2p.Msg) + n2_1Chan := make(chan p2p.Msg) + trw1 := &TestPSRW{ + writeMsgChan: n1_2Chan, + readMsgChan: n2_1Chan, + } + trw2 := &TestPSRW{ + writeMsgChan: n2_1Chan, + readMsgChan: n1_2Chan, + } + + n2_3Chan := make(chan p2p.Msg) + n3_2Chan := make(chan p2p.Msg) + trw2_3 := &TestPSRW{ + writeMsgChan: n2_3Chan, + readMsgChan: n3_2Chan, + } + trw3_2 := &TestPSRW{ + writeMsgChan: n3_2Chan, + readMsgChan: n2_3Chan, + } + + // connect peer + // peer1 <-> peer2 + go func() { + newPeer := p2p.NewPeer(pubSub2.pss.Host().ID().ID(), "n2", nil) + t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub1.pss.Host().ID().ID().TerminalString(), "n1") + if err := pubSub1.handler(newPeer, trw1); err != nil { + t.Fatal(err) + } + }() + go func() { + newPeer := p2p.NewPeer(pubSub1.pss.Host().ID().ID(), "n1", nil) + t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub2.pss.Host().ID().ID().TerminalString(), "n2") + if err := pubSub2.handler(newPeer, trw2); err != nil { + t.Fatal(err) + } + }() + + // peer2 <-> peer3 + go func() { + newPeer := p2p.NewPeer(pubSub3.pss.Host().ID().ID(), "n3", nil) + t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub2.pss.Host().ID().ID().TerminalString(), "n2") + if err := pubSub2.handler(newPeer, trw2_3); err != nil { + t.Fatal(err) + } + }() + go func() { + newPeer := p2p.NewPeer(pubSub2.pss.Host().ID().ID(), "n2", nil) + t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub3.pss.Host().ID().ID().TerminalString(), "n3") + if err := pubSub3.handler(newPeer, trw3_2); err != nil { + t.Fatal(err) + } + }() + time.Sleep(time.Millisecond * 6) + + topic := "test" + go func() { + if err := pubSub1.Subscribe(topic); err != nil { + t.Fatal(err) + } + time.Sleep(time.Millisecond * 3) + pubSub1.Publish(topic, uint64(1), expect[0]) + }() + go func() { + if err := pubSub2.Subscribe(topic); err != nil { + t.Fatal(err) + } + time.Sleep(time.Millisecond * 3) + pubSub2.Publish(topic, uint64(2), expect[1]) + }() + go func() { + if err := pubSub3.Subscribe(topic); err != nil { + t.Fatal(err) + } + time.Sleep(time.Millisecond * 3) + pubSub3.Publish(topic, uint64(3), expect[2]) + }() + wg.Wait() + pubSub1.Cancel(topic) + pubSub2.Cancel(topic) + pubSub3.Cancel(topic) +} From fc7b4082d29beab736b0be5f0c7b3f43027e269e Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Fri, 19 Nov 2021 18:12:57 +0800 Subject: [PATCH 065/299] fix bug --- consensus/cbft/cbft.go | 2 +- consensus/cbft/consensus_process.go | 169 +++++++++++++++----------- consensus/cbft/sync_process.go | 34 ++++++ consensus/cbft/types/crypto.go | 48 ++++++++ consensus/cbft/types/crypto_test.go | 33 +++++ consensus/cbft/validator/validator.go | 5 + 6 files changed, 222 insertions(+), 69 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 524baa6a4e..b54ef0acef 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -2006,7 +2006,7 @@ func (cbft *Cbft) verifyViewChangeQC(viewChangeQC *ctypes.ViewChangeQC) error { // check parameter validity validatorLimit := cbft.validatorPool.Len(vcEpoch) if err := cbft.validateViewChangeQC(viewChangeQC, validatorLimit); err != nil { - return err + return authFailedError{err} } return cbft.verifyViewChangeQuorumCerts(viewChangeQC) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index e82773072d..87da8dec92 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -914,32 +914,39 @@ func (cbft *Cbft) richRGViewChangeQuorumCert(rgb *protocols.RGViewChangeQuorumCe if len(mergeVcs) > 0 { total := cbft.validatorPool.Len(cbft.state.Epoch()) for _, vc := range mergeVcs { + rgb.ViewChangeQC.MergeViewChange(vc, uint32(total)) if !rgb.ViewChangeQC.ExistViewChange(vc.Epoch, vc.ViewNumber, vc.BlockHash) { - qc := &ctypes.ViewChangeQuorumCert{ - Epoch: vc.Epoch, - ViewNumber: vc.ViewNumber, - BlockHash: vc.BlockHash, - BlockNumber: vc.BlockNumber, - ValidatorSet: utils.NewBitArray(uint32(total)), - } if vc.PrepareQC != nil { - qc.BlockEpoch = vc.PrepareQC.Epoch - qc.BlockViewNumber = vc.PrepareQC.ViewNumber rgb.PrepareQCs.AppendQuorumCert(vc.PrepareQC) } - qc.ValidatorSet.SetIndex(vc.ValidatorIndex, true) - qc.Signature.SetBytes(vc.Signature.Bytes()) - - rgb.ViewChangeQC.AppendQuorumCert(qc) - rgb.ViewChangeQC.QCs = append(rgb.ViewChangeQC.QCs, qc) - } else { - for _, qc := range rgb.ViewChangeQC.QCs { - if qc.BlockHash == vc.BlockHash && !qc.ValidatorSet.GetIndex(vc.NodeIndex()) { - qc.AddSign(vc.Signature, vc.NodeIndex()) - break - } - } } + + //if !rgb.ViewChangeQC.ExistViewChange(vc.Epoch, vc.ViewNumber, vc.BlockHash) { + // qc := &ctypes.ViewChangeQuorumCert{ + // Epoch: vc.Epoch, + // ViewNumber: vc.ViewNumber, + // BlockHash: vc.BlockHash, + // BlockNumber: vc.BlockNumber, + // ValidatorSet: utils.NewBitArray(uint32(total)), + // } + // if vc.PrepareQC != nil { + // qc.BlockEpoch = vc.PrepareQC.Epoch + // qc.BlockViewNumber = vc.PrepareQC.ViewNumber + // rgb.PrepareQCs.AppendQuorumCert(vc.PrepareQC) + // } + // qc.ValidatorSet.SetIndex(vc.ValidatorIndex, true) + // qc.Signature.SetBytes(vc.Signature.Bytes()) + // + // rgb.ViewChangeQC.AppendQuorumCert(qc) + // rgb.ViewChangeQC.QCs = append(rgb.ViewChangeQC.QCs, qc) + //} else { + // for _, qc := range rgb.ViewChangeQC.QCs { + // if qc.BlockHash == vc.BlockHash && !qc.ValidatorSet.GetIndex(vc.NodeIndex()) { + // qc.AddSign(vc.Signature, vc.NodeIndex()) + // break + // } + // } + //} } } } @@ -952,7 +959,7 @@ func (cbft *Cbft) groupPrepareVotes(epoch uint64, blockIndex, groupID uint32) ma } // Find votes corresponding to indexes votes := cbft.state.AllPrepareVoteByIndex(blockIndex) - if votes != nil && len(votes) > 0 { + if len(votes) > 0 { groupVotes := make(map[uint32]*protocols.PrepareVote) for _, index := range indexes { if vote, ok := votes[index]; ok { @@ -989,45 +996,56 @@ func (cbft *Cbft) findQCBlock() { index := cbft.state.MaxQCIndex() next := index + 1 threshold := cbft.threshold(cbft.currentValidatorLen()) - var ( - enoughVotesFlag bool - enoughRGQuorumCertsFlag bool - rgqcs []*ctypes.QuorumCert - ) + + var qc *ctypes.QuorumCert enoughVotes := func() bool { size := cbft.state.PrepareVoteLenByIndex(next) - enoughVotesFlag = size >= threshold - return enoughVotesFlag + if size >= threshold { + qc = cbft.generatePrepareQC(cbft.state.AllPrepareVoteByIndex(next)) + cbft.log.Debug("Enough prepareVote have been received, generate prepareQC", "qc", qc.String()) + } + return size >= threshold } enoughRGQuorumCerts := func() bool { - rgqcs = cbft.state.FindMaxRGQuorumCerts(next) + rgqcs := cbft.state.FindMaxRGQuorumCerts(next) size := 0 if len(rgqcs) > 0 { for _, qc := range rgqcs { size += qc.ValidatorSet.HasLength() } } - enoughRGQuorumCertsFlag = size >= threshold - return enoughRGQuorumCertsFlag - } - - prepareQC := func() bool { - return cbft.state.HadSendPrepareVote().Had(next) && (enoughVotes() || enoughRGQuorumCerts()) + if size >= threshold { + qc = cbft.combinePrepareQC(rgqcs) + cbft.log.Debug("Enough RGBlockQuorumCerts have been received, combine prepareQC", "qc", qc.String()) + } + return size >= threshold } - if prepareQC() { - block := cbft.state.ViewBlockByIndex(next) - var qc *ctypes.QuorumCert - if enoughVotesFlag { - qc = cbft.generatePrepareQC(cbft.state.AllPrepareVoteByIndex(next)) - cbft.log.Debug("Enough prepareVote have been received, generate prepareQC", "qc", qc.String()) - } else if enoughRGQuorumCertsFlag { + enoughCombine := func() bool { + knownIndexes := cbft.KnownVoteIndexes(next) + if len(knownIndexes) >= threshold { + rgqcs := cbft.state.FindMaxRGQuorumCerts(next) qc = cbft.combinePrepareQC(rgqcs) - cbft.log.Debug("Enough RGBlockQuorumCerts have been received, combine prepareQC", "qc", qc.String()) + allVotes := cbft.state.AllPrepareVoteByIndex(next) + + for index, v := range allVotes { + if !qc.HasSign(index) { + qc.AddSign(v.Signature, v.BlockIndex) + } + } + cbft.log.Debug("Enough RGBlockQuorumCerts and prepareVote have been received, combine prepareQC", "qc", qc.String()) } + return len(knownIndexes) >= threshold + } + alreadyQC := func() bool { + return cbft.state.HadSendPrepareVote().Had(next) && (enoughRGQuorumCerts() || enoughVotes() || enoughCombine()) + } + + if alreadyQC() { + block := cbft.state.ViewBlockByIndex(next) if qc != nil { cbft.log.Info("New qc block have been created", "qc", qc.String()) cbft.insertQCBlock(block, qc) @@ -1135,42 +1153,57 @@ func (cbft *Cbft) tryChangeView() { cbft.validatorPool.SetupGroup(true, 0, 0) } - threshold := cbft.threshold(cbft.currentValidatorLen()) - var ( - enoughViewChangesFlag bool - enoughRGQuorumCertsFlag bool - viewChangeQCs []*ctypes.ViewChangeQC - ) + validatorLen := cbft.currentValidatorLen() + threshold := cbft.threshold(validatorLen) + + var viewChangeQC *ctypes.ViewChangeQC + enoughViewChanges := func() bool { - enoughViewChangesFlag = cbft.state.ViewChangeLen() >= threshold - return enoughViewChangesFlag + size := cbft.state.ViewChangeLen() + if size >= threshold { + viewChangeQC = cbft.generateViewChangeQC(cbft.state.AllViewChange()) + cbft.log.Info("Receive enough viewchange, generate viewChangeQC", "view", cbft.state.ViewString(), "viewChangeQC", viewChangeQC.String()) + } + return size >= threshold } enoughRGQuorumCerts := func() bool { - viewChangeQCs = cbft.state.FindMaxRGViewChangeQuorumCerts() + viewChangeQCs := cbft.state.FindMaxRGViewChangeQuorumCerts() size := 0 - if viewChangeQCs != nil && len(viewChangeQCs) > 0 { + if len(viewChangeQCs) > 0 { for _, qc := range viewChangeQCs { - size += qc.Len() + size += qc.ValidatorSet().HasLength() } } - enoughRGQuorumCertsFlag = size >= threshold - return enoughRGQuorumCertsFlag - } - - viewChangeQC := func() bool { - return enoughViewChanges() || enoughRGQuorumCerts() + if size >= threshold { + viewChangeQC = cbft.combineViewChangeQC(viewChangeQCs) + cbft.log.Debug("Enough RGViewChangeQuorumCerts have been received, combine ViewChangeQC", "qc", qc.String()) + } + return size >= threshold } - if viewChangeQC() { - var viewChangeQC *ctypes.ViewChangeQC - if enoughViewChangesFlag { - viewChangeQC = cbft.generateViewChangeQC(cbft.state.AllViewChange()) - cbft.log.Info("Receive enough viewchange, try change view by viewChangeQC", "view", cbft.state.ViewString(), "viewChangeQC", viewChangeQC.String()) - } else if enoughRGQuorumCertsFlag { + enoughCombine := func() bool { + knownIndexes := cbft.KnownViewChangeIndexes() + if len(knownIndexes) >= threshold { + viewChangeQCs := cbft.state.FindMaxRGViewChangeQuorumCerts() viewChangeQC = cbft.combineViewChangeQC(viewChangeQCs) - cbft.log.Debug("Enough RGViewChangeQuorumCerts have been received, combine ViewChangeQC", "qc", qc.String()) + allViewChanges := cbft.state.AllViewChange() + + for index, v := range allViewChanges { + if !viewChangeQC.HasSign(index) { + viewChangeQC.MergeViewChange(v, uint32(validatorLen)) + } + } + cbft.log.Debug("Enough RGViewChangeQuorumCerts and viewchange have been received, combine ViewChangeQC", "qc", qc.String()) } + return len(knownIndexes) >= threshold + } + + alreadyQC := func() bool { + return enoughRGQuorumCerts() || enoughViewChanges() || enoughCombine() + } + + if alreadyQC() { cbft.tryChangeViewByViewChange(viewChangeQC) } } diff --git a/consensus/cbft/sync_process.go b/consensus/cbft/sync_process.go index 132cf8b07b..6faae2ee46 100644 --- a/consensus/cbft/sync_process.go +++ b/consensus/cbft/sync_process.go @@ -679,6 +679,40 @@ func (cbft *Cbft) MissingViewChangeNodes() (v *protocols.GetViewChange, err erro return } +func (cbft *Cbft) KnownVoteIndexes(blockIndex uint32) []uint32 { + groupNodes := cbft.validatorPool.GetGroupByValidator(cbft.state.Epoch()) + allVotes := cbft.state.AllPrepareVoteByIndex(blockIndex) + known := make([]uint32, 0) + for groupID, indexes := range groupNodes { + qc, _ := cbft.state.FindMaxGroupRGQuorumCert(blockIndex, groupID) + for _, index := range indexes { + if _, ok := allVotes[index]; ok { + known = append(known, index) + } else if qc != nil && qc.HasSign(index) { + known = append(known, index) + } + } + } + return known +} + +func (cbft *Cbft) KnownViewChangeIndexes() []uint32 { + groupNodes := cbft.validatorPool.GetGroupByValidator(cbft.state.Epoch()) + allViewChanges := cbft.state.AllViewChange() + known := make([]uint32, 0) + for groupID, indexes := range groupNodes { + qc, _ := cbft.state.FindMaxGroupRGViewChangeQuorumCert(groupID) + for _, index := range indexes { + if _, ok := allViewChanges[index]; ok { + known = append(known, index) + } else if qc != nil && qc.HasSign(index) { + known = append(known, index) + } + } + } + return known +} + // MissingPrepareVote returns missing vote. func (cbft *Cbft) MissingPrepareVote() (v *protocols.GetPrepareVote, err error) { result := make(chan struct{}) diff --git a/consensus/cbft/types/crypto.go b/consensus/cbft/types/crypto.go index f189768027..86ea3caae4 100644 --- a/consensus/cbft/types/crypto.go +++ b/consensus/cbft/types/crypto.go @@ -18,6 +18,7 @@ package types import ( "fmt" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" "reflect" "github.com/AlayaNetwork/Alaya-Go/common" @@ -152,6 +153,10 @@ func (q *QuorumCert) HigherSign(c *QuorumCert) bool { return q.ValidatorSet.HasLength() >= c.ValidatorSet.HasLength() } +func (q *QuorumCert) HasSign(signIndex uint32) bool { + return q.ValidatorSet.GetIndex(signIndex) +} + func (q *QuorumCert) EqualState(c *QuorumCert) bool { return q.Epoch == c.Epoch && q.ViewNumber == c.ViewNumber && @@ -282,6 +287,10 @@ func (q *ViewChangeQuorumCert) HigherSign(c *ViewChangeQuorumCert) bool { return q.ValidatorSet.HasLength() >= c.ValidatorSet.HasLength() } +func (q *ViewChangeQuorumCert) HasSign(signIndex uint32) bool { + return q.ValidatorSet.GetIndex(signIndex) +} + func (q *ViewChangeQuorumCert) EqualState(c *ViewChangeQuorumCert) bool { return q.Epoch == c.Epoch && q.ViewNumber == c.ViewNumber && @@ -338,6 +347,17 @@ func (v ViewChangeQC) ValidatorSet() *utils.BitArray { return nil } +func (v *ViewChangeQC) HasSign(signIndex uint32) bool { + if len(v.QCs) > 0 { + for _, qc := range v.QCs { + if qc.HasSign(signIndex) { + return true + } + } + } + return false +} + func (v ViewChangeQC) String() string { epoch, view, blockEpoch, blockViewNumber, hash, number := v.MaxBlock() return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,BlockEpoch:%d,BlockViewNumber:%d,Hash:%s,Number:%d}", epoch, view, blockEpoch, blockViewNumber, hash.TerminalString(), number) @@ -356,6 +376,34 @@ func (v *ViewChangeQC) AppendQuorumCert(viewChangeQC *ViewChangeQuorumCert) { v.QCs = append(v.QCs, viewChangeQC) } +func (v *ViewChangeQC) MergeViewChange(vc *protocols.ViewChange, validatorLen uint32) { + if !v.ExistViewChange(vc.Epoch, vc.ViewNumber, vc.BlockHash) { + qc := &ViewChangeQuorumCert{ + Epoch: vc.Epoch, + ViewNumber: vc.ViewNumber, + BlockHash: vc.BlockHash, + BlockNumber: vc.BlockNumber, + ValidatorSet: utils.NewBitArray(validatorLen), + } + if vc.PrepareQC != nil { + qc.BlockEpoch = vc.PrepareQC.Epoch + qc.BlockViewNumber = vc.PrepareQC.ViewNumber + //rgb.PrepareQCs.AppendQuorumCert(vc.PrepareQC) + } + qc.ValidatorSet.SetIndex(vc.ValidatorIndex, true) + qc.Signature.SetBytes(vc.Signature.Bytes()) + + v.AppendQuorumCert(qc) + } else { + for _, qc := range v.QCs { + if qc.BlockHash == vc.BlockHash && !qc.HasSign(vc.NodeIndex()) { + qc.AddSign(vc.Signature, vc.NodeIndex()) + break + } + } + } +} + type PrepareQCs struct { QCs []*QuorumCert `json:"qcs"` } diff --git a/consensus/cbft/types/crypto_test.go b/consensus/cbft/types/crypto_test.go index f7d393ba62..c182b90815 100644 --- a/consensus/cbft/types/crypto_test.go +++ b/consensus/cbft/types/crypto_test.go @@ -18,7 +18,9 @@ package types import ( "encoding/json" + "fmt" "testing" + "time" "github.com/stretchr/testify/assert" @@ -205,3 +207,34 @@ func TestQuorumCertAddSign(t *testing.T) { qc.AddSign(s, uint32(0)) assert.Equal(t, false, verifyQuorumCert(qc)) } + +func TestAddSign(t *testing.T) { + bls.Init(int(bls.BLS12_381)) + message := "test merge sign" + var k int = 100000 + msk := make([]bls.SecretKey, k) + mpk := make([]bls.PublicKey, k) + msig := make([]bls.Sign, k) + msignature := make([]Signature, k) + for i := 0; i < 1; i++ { + msk[i].SetByCSPRNG() + mpk[i] = *msk[i].GetPublicKey() + msig[i] = *msk[i].Sign(message) + msignature[i].SetBytes(msig[i].Serialize()) + } + + qc := &QuorumCert{ + Signature: Signature{}, + ValidatorSet: utils.NewBitArray(uint32(300)), + } + qc.Signature.SetBytes(msig[0].Serialize()) + qc.ValidatorSet.SetIndex(0, true) + + start := common.Millis(time.Now()) + fmt.Println("test", "start", start) + for i := 1; i < k; i++ { + qc.AddSign(qc.Signature, uint32(i%300)) + } + end := common.Millis(time.Now()) + fmt.Println("test", "end", end, "v", qc.ValidatorSet.HasLength()) +} diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index ba56acc98f..5c3224da9b 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -801,3 +801,8 @@ func (vp *ValidatorPool) GetGroupByValidatorID(epoch uint64, nodeID enode.ID) (u unitID := validators.UnitID(nodeID) return groupID, unitID, nil } + +// 返回指定epoch下节点的分组信息,key=groupID,value=分组节点index集合 +func (vp *ValidatorPool) GetGroupByValidator(epoch uint64) map[uint32][]uint32 { + return nil +} From 0582e18d85b52a29a4c30b95db91b4bbbe590c1c Mon Sep 17 00:00:00 2001 From: benbaley Date: Mon, 22 Nov 2021 16:46:09 +0800 Subject: [PATCH 066/299] update common_config --- consensus/cbft/cbft.go | 7 +++--- consensus/cbft/validator/validator.go | 3 --- core/blockchain_reactor.go | 2 +- core/genesis.go | 7 ++++++ x/gov/gov.go | 10 ++++++++ x/xcom/common_config.go | 33 +++++++++++++++++++++++++++ x/xcom/common_config_test.go | 10 ++++++++ 7 files changed, 65 insertions(+), 7 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index be15dfe88c..4727676a46 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -22,6 +22,7 @@ import ( "crypto/elliptic" "encoding/json" "fmt" + "github.com/AlayaNetwork/Alaya-Go/x/xcom" "github.com/AlayaNetwork/Alaya-Go/x/xutil" "strings" "sync/atomic" @@ -1351,17 +1352,17 @@ func (cbft *Cbft) commitBlock(commitBlock *types.Block, commitQC *ctypes.QuorumC ChainStateUpdateCB: func() { cbft.bridge.UpdateChainState(qcState, lockState, commitState) }, }) + activeVersion := cbft.blockCache.GetActiveVersion(cpy.Header().SealHash()) // should grouped according max commit block's state shouldGroup := func() bool { - activeVersion := cbft.blockCache.GetActiveVersion(cbft.state.HighestCommitBlock().Header().SealHash()) return cbft.validatorPool.NeedGroup() || activeVersion >= params.FORKVERSION_0_17_0 } // post NewGroupsEvent to join topic according group info - if xutil.IsElection(cpy.NumberU64()) { + if xutil.IsElection(cpy.NumberU64(), activeVersion) { // TODO: get groupvalidatorslimit and coordinatorlimit from gov if shouldGroup() { - cbft.validatorPool.SetupGroup(true, 0, 0) + cbft.validatorPool.SetupGroup(true, xcom.MaxGroupValidators(), xcom.CoordinatorsLimit()) } cbft.validatorPool.Update(cpy.NumberU64(), cbft.state.Epoch()+1, cbft.eventMux) } diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index f0b38c32f5..1c7c1a0ce0 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -452,9 +452,6 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *even // SetupGroup update validatorpool group info func (vp *ValidatorPool) SetupGroup(needGroup bool, groupValidatorsLimit, coordinatorLimit uint32) { - if vp.needGroup { - return - } vp.lock.Lock() defer vp.lock.Unlock() diff --git a/core/blockchain_reactor.go b/core/blockchain_reactor.go index 58021cf9fb..0b43e5a383 100644 --- a/core/blockchain_reactor.go +++ b/core/blockchain_reactor.go @@ -81,7 +81,7 @@ func (bcr *BlockChainReactor) Start(mode string) { if mode == common.PPOS_VALIDATOR_MODE { // Subscribe events for confirmed blocks bcr.bftResultSub = bcr.eventMux.Subscribe(cbfttypes.CbftResult{}) - // start the loop rutine + // start the loop routine go bcr.loop() } } diff --git a/core/genesis.go b/core/genesis.go index 0c3a2a60fc..12f4b7f8d3 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -449,6 +449,13 @@ func (g *Genesis) ToBlock(db ethdb.Database, sdb snapshotdb.BaseDB) *types.Block } } + // 0.17.0 + if gov.Gte0170Version(genesisVersion) { + if err := gov.WriteEcHash0170(statedb); nil != err { + panic("Failed Store EcHash0170: " + err.Error()) + } + } + if g.Config != nil && g.Config.ChainID.Cmp(params.AlayaChainConfig.ChainID) != 0 { if g.Config.AddressHRP != "" { statedb.SetString(vm.StakingContractAddr, rawdb.AddressHRPKey, g.Config.AddressHRP) diff --git a/x/gov/gov.go b/x/gov/gov.go index a559793295..75a72b646d 100644 --- a/x/gov/gov.go +++ b/x/gov/gov.go @@ -106,6 +106,16 @@ func Gte0170VersionState(state xcom.StateDB) bool { func Gte0170Version(version uint32) bool { return version >= params.FORKVERSION_0_17_0 } + +func WriteEcHash0170(state xcom.StateDB) error { + if data, err := xcom.EcParams0170(); nil != err { + return err + } else { + SetEcParametersHash(state, data) + } + return nil +} + func WriteEcHash0140(state xcom.StateDB) error { if data, err := xcom.EcParams0140(); nil != err { return err diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index 5ab1e91baf..6e5bf53fbf 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -162,6 +162,7 @@ type EconomicModel struct { type EconomicModelExtend struct { Reward rewardConfigExtend `json:"reward"` Restricting restrictingConfigExtend `json:"restricting"` + CommonExt commonConfigExtend `json:"CommonExt"` } type rewardConfigExtend struct { @@ -172,6 +173,12 @@ type restrictingConfigExtend struct { MinimumRelease *big.Int `json:"minimumRelease"` //The minimum number of Restricting release in one epoch } +type commonConfigExtend struct { + // add by 0.17.0 + MaxGroupValidators uint32 `json:"MaxGroupValidators"` // max validators count in 1 group + CoordinatorsLimit uint32 `json:"CoordinatorLimit"` // max Coordinators count in 1 group +} + // New parameters added in version 0.14.0 need to be saved on the chain. // Calculate the rlp of the new parameter and return it to the upper storage. func EcParams0140() ([]byte, error) { @@ -189,6 +196,20 @@ func EcParams0140() ([]byte, error) { return bytes, nil } +// New parameters added in version 0.17.0 need to be saved on the chain. +// Calculate the rlp of the new parameter and return it to the upper storage. +func EcParams0170() ([]byte, error) { + params := commonConfigExtend { + MaxGroupValidators: ece.CommonExt.MaxGroupValidators, + CoordinatorsLimit: ece.CommonExt.CoordinatorsLimit, + } + bytes, err := rlp.EncodeToBytes(params) + if err != nil { + return nil, err + } + return bytes, nil +} + var ( modelOnce sync.Once ec *EconomicModel @@ -296,6 +317,10 @@ func getDefaultEMConfig(netId int8) *EconomicModel { Restricting: restrictingConfigExtend{ MinimumRelease: new(big.Int).Mul(oneAtp, new(big.Int).SetInt64(80)), }, + CommonExt: commonConfigExtend{ + MaxGroupValidators: 25, + CoordinatorsLimit: 5, + }, } case DefaultTestNet: @@ -876,3 +901,11 @@ func CalcP(totalWeight float64, sqrtWeight float64) float64 { func CalcPNew(sqrtWeight float64) float64 { return float64(ElectionBase) / sqrtWeight } + +func MaxGroupValidators() uint32 { + return ece.CommonExt.MaxGroupValidators +} + +func CoordinatorsLimit() uint32 { + return ece.CommonExt.CoordinatorsLimit +} diff --git a/x/xcom/common_config_test.go b/x/xcom/common_config_test.go index 6a42c18e02..7841e3fb2b 100644 --- a/x/xcom/common_config_test.go +++ b/x/xcom/common_config_test.go @@ -57,6 +57,16 @@ func TestEcParams0140(t *testing.T) { } } +func TestEcParams0170(t *testing.T) { + eceHash := "0x0000000000000000000000000000000000000000000000000000000000000000" + getDefaultEMConfig(DefaultAlayaNet) + if bytes, err := EcParams0170(); nil != err { + t.Fatal(err) + } else { + assert.True(t, bytes != nil) + assert.True(t, common.RlpHash(bytes).Hex() == eceHash) + } +} func TestAlayaNetHash(t *testing.T) { alayaEc := getDefaultEMConfig(DefaultAlayaNet) bytes, err := rlp.EncodeToBytes(alayaEc) From 260700d1900c007547f65ac228bec31226b181ed Mon Sep 17 00:00:00 2001 From: benbaley Date: Mon, 22 Nov 2021 17:04:59 +0800 Subject: [PATCH 067/299] change EconomicModelExtend.CommonExt to Common --- x/xcom/common_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index 6e5bf53fbf..40f744eb66 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -162,7 +162,7 @@ type EconomicModel struct { type EconomicModelExtend struct { Reward rewardConfigExtend `json:"reward"` Restricting restrictingConfigExtend `json:"restricting"` - CommonExt commonConfigExtend `json:"CommonExt"` + Common commonConfigExtend `json:"Common"` } type rewardConfigExtend struct { From 3124b92515aa52bc9e558957fb0fcf0982962d04 Mon Sep 17 00:00:00 2001 From: benbaley Date: Mon, 22 Nov 2021 17:12:25 +0800 Subject: [PATCH 068/299] change EconomicModelExtend.CommonExt to Common --- x/xcom/common_config.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index 40f744eb66..dfbc8bbe1a 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -200,8 +200,8 @@ func EcParams0140() ([]byte, error) { // Calculate the rlp of the new parameter and return it to the upper storage. func EcParams0170() ([]byte, error) { params := commonConfigExtend { - MaxGroupValidators: ece.CommonExt.MaxGroupValidators, - CoordinatorsLimit: ece.CommonExt.CoordinatorsLimit, + MaxGroupValidators: ece.Common.MaxGroupValidators, + CoordinatorsLimit: ece.Common.CoordinatorsLimit, } bytes, err := rlp.EncodeToBytes(params) if err != nil { @@ -317,7 +317,7 @@ func getDefaultEMConfig(netId int8) *EconomicModel { Restricting: restrictingConfigExtend{ MinimumRelease: new(big.Int).Mul(oneAtp, new(big.Int).SetInt64(80)), }, - CommonExt: commonConfigExtend{ + Common: commonConfigExtend{ MaxGroupValidators: 25, CoordinatorsLimit: 5, }, @@ -903,9 +903,9 @@ func CalcPNew(sqrtWeight float64) float64 { } func MaxGroupValidators() uint32 { - return ece.CommonExt.MaxGroupValidators + return ece.Common.MaxGroupValidators } func CoordinatorsLimit() uint32 { - return ece.CommonExt.CoordinatorsLimit + return ece.Common.CoordinatorsLimit } From d7f3821970e30561c5944f0525003076bf296b32 Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 23 Nov 2021 09:49:13 +0800 Subject: [PATCH 069/299] change EconomicModelExtend.CommonExt to Common --- x/xcom/common_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index dfbc8bbe1a..938e18eab9 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -162,7 +162,7 @@ type EconomicModel struct { type EconomicModelExtend struct { Reward rewardConfigExtend `json:"reward"` Restricting restrictingConfigExtend `json:"restricting"` - Common commonConfigExtend `json:"Common"` + Common commonConfigExtend `json:"common"` } type rewardConfigExtend struct { From e7b9235117de6a0a6122056130b1e0edd155512b Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 23 Nov 2021 11:23:11 +0800 Subject: [PATCH 070/299] change EconomicModelExtend.CommonExt to Common --- consensus/cbft/cbft.go | 2 +- x/xutil/calculate.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 4727676a46..ed81cd92a4 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -1362,7 +1362,7 @@ func (cbft *Cbft) commitBlock(commitBlock *types.Block, commitQC *ctypes.QuorumC if xutil.IsElection(cpy.NumberU64(), activeVersion) { // TODO: get groupvalidatorslimit and coordinatorlimit from gov if shouldGroup() { - cbft.validatorPool.SetupGroup(true, xcom.MaxGroupValidators(), xcom.CoordinatorsLimit()) + cbft.validatorPool.SetupGroup(true, xutil.MaxGroupValidators(), xutil.CoordinatorsLimit()) } cbft.validatorPool.Update(cpy.NumberU64(), cbft.state.Epoch()+1, cbft.eventMux) } diff --git a/x/xutil/calculate.go b/x/xutil/calculate.go index b2e5cbaced..722f2eef08 100644 --- a/x/xutil/calculate.go +++ b/x/xutil/calculate.go @@ -74,6 +74,14 @@ func EpochSize() uint64 { return xcom.EpochSize() } +func MaxGroupValidators() uint32 { + return xcom.MaxGroupValidators() +} + +func CoordinatorsLimit() uint32 { + return xcom.CoordinatorsLimit() +} + // EpochsPerYear returns how many epochs per year func EpochsPerYear() uint64 { epochBlocks := CalcBlocksEachEpoch() From d830b1c88f5fd2894c9b3d9bb2c0cc83ab158f4f Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 23 Nov 2021 11:47:48 +0800 Subject: [PATCH 071/299] update SetupGroup --- consensus/cbft/cbft.go | 2 +- consensus/cbft/validator/validator.go | 7 ++++--- x/xutil/calculate.go | 8 -------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index ed81cd92a4..4d24eb708a 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -1362,7 +1362,7 @@ func (cbft *Cbft) commitBlock(commitBlock *types.Block, commitQC *ctypes.QuorumC if xutil.IsElection(cpy.NumberU64(), activeVersion) { // TODO: get groupvalidatorslimit and coordinatorlimit from gov if shouldGroup() { - cbft.validatorPool.SetupGroup(true, xutil.MaxGroupValidators(), xutil.CoordinatorsLimit()) + cbft.validatorPool.SetupGroup(true) } cbft.validatorPool.Update(cpy.NumberU64(), cbft.state.Epoch()+1, cbft.eventMux) } diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 1c7c1a0ce0..372752962c 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -20,6 +20,7 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/AlayaNetwork/Alaya-Go/x/xcom" "github.com/AlayaNetwork/Alaya-Go/x/xutil" "sync" @@ -451,13 +452,13 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *even } // SetupGroup update validatorpool group info -func (vp *ValidatorPool) SetupGroup(needGroup bool, groupValidatorsLimit, coordinatorLimit uint32) { +func (vp *ValidatorPool) SetupGroup(needGroup bool) { vp.lock.Lock() defer vp.lock.Unlock() vp.needGroup = needGroup - vp.groupValidatorsLimit = groupValidatorsLimit - vp.coordinatorLimit = coordinatorLimit + vp.groupValidatorsLimit = xcom.MaxGroupValidators() + vp.coordinatorLimit = xcom.CoordinatorsLimit() } // GetValidatorByNodeID get the validator by node id. diff --git a/x/xutil/calculate.go b/x/xutil/calculate.go index 722f2eef08..b2e5cbaced 100644 --- a/x/xutil/calculate.go +++ b/x/xutil/calculate.go @@ -74,14 +74,6 @@ func EpochSize() uint64 { return xcom.EpochSize() } -func MaxGroupValidators() uint32 { - return xcom.MaxGroupValidators() -} - -func CoordinatorsLimit() uint32 { - return xcom.CoordinatorsLimit() -} - // EpochsPerYear returns how many epochs per year func EpochsPerYear() uint64 { epochBlocks := CalcBlocksEachEpoch() From a55259ad5ff3194cb8d27ffa2903dd1e0f0a077d Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 23 Nov 2021 11:59:42 +0800 Subject: [PATCH 072/299] update SetupGroup --- consensus/cbft/cbft.go | 1 - 1 file changed, 1 deletion(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 4d24eb708a..41a04d0e7e 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -22,7 +22,6 @@ import ( "crypto/elliptic" "encoding/json" "fmt" - "github.com/AlayaNetwork/Alaya-Go/x/xcom" "github.com/AlayaNetwork/Alaya-Go/x/xutil" "strings" "sync/atomic" From e059a6e160f1076511747870b5a108acb2510ba4 Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 23 Nov 2021 12:06:36 +0800 Subject: [PATCH 073/299] fix import depends cycle --- consensus/cbft/validator/validator.go | 5 ++--- x/xutil/calculate.go | 8 ++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 372752962c..acf4039d17 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -20,7 +20,6 @@ import ( "encoding/hex" "errors" "fmt" - "github.com/AlayaNetwork/Alaya-Go/x/xcom" "github.com/AlayaNetwork/Alaya-Go/x/xutil" "sync" @@ -457,8 +456,8 @@ func (vp *ValidatorPool) SetupGroup(needGroup bool) { defer vp.lock.Unlock() vp.needGroup = needGroup - vp.groupValidatorsLimit = xcom.MaxGroupValidators() - vp.coordinatorLimit = xcom.CoordinatorsLimit() + vp.groupValidatorsLimit = xutil.MaxGroupValidators() + vp.coordinatorLimit = xutil.CoordinatorsLimit() } // GetValidatorByNodeID get the validator by node id. diff --git a/x/xutil/calculate.go b/x/xutil/calculate.go index b2e5cbaced..722f2eef08 100644 --- a/x/xutil/calculate.go +++ b/x/xutil/calculate.go @@ -74,6 +74,14 @@ func EpochSize() uint64 { return xcom.EpochSize() } +func MaxGroupValidators() uint32 { + return xcom.MaxGroupValidators() +} + +func CoordinatorsLimit() uint32 { + return xcom.CoordinatorsLimit() +} + // EpochsPerYear returns how many epochs per year func EpochsPerYear() uint64 { epochBlocks := CalcBlocksEachEpoch() From 3193dca1d7d9f8959441cd4f2d50f92e23e1e2c0 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 24 Nov 2021 10:36:21 +0800 Subject: [PATCH 074/299] Change the field in json to lower case --- x/xcom/common_config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index 938e18eab9..821a4d1af1 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -175,8 +175,8 @@ type restrictingConfigExtend struct { type commonConfigExtend struct { // add by 0.17.0 - MaxGroupValidators uint32 `json:"MaxGroupValidators"` // max validators count in 1 group - CoordinatorsLimit uint32 `json:"CoordinatorLimit"` // max Coordinators count in 1 group + MaxGroupValidators uint32 `json:"caxGroupValidators"` // max validators count in 1 group + CoordinatorsLimit uint32 `json:"coordinatorLimit"` // max Coordinators count in 1 group } // New parameters added in version 0.14.0 need to be saved on the chain. From 399e6cf88b78041a0d70fb21be9849a5009b88cf Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Wed, 24 Nov 2021 14:56:11 +0800 Subject: [PATCH 075/299] Increase the unit test of pubsub --- p2p/host.go | 4 + p2p/pubsub/blacklist_test.go | 127 ++ p2p/pubsub/discovery_test.go | 309 ++++ p2p/pubsub/gossip_tracer_test.go | 98 + p2p/pubsub/gossipsub_feat_test.go | 115 ++ p2p/pubsub/gossipsub_test.go | 2269 ++++++++++++++++++++++++ p2p/pubsub/mcache_test.go | 167 ++ p2p/pubsub/peer_gater_test.go | 128 ++ p2p/pubsub/pubsub_test.go | 261 +++ p2p/pubsub/score_params_test.go | 328 ++++ p2p/pubsub/score_test.go | 1080 +++++++++++ p2p/pubsub/sign_test.go | 43 + p2p/pubsub/subscription_filter_test.go | 210 +++ p2p/pubsub/tag_tracer_test.go | 252 +++ p2p/pubsub/topic_test.go | 782 ++++++++ p2p/pubsub/trace_test.go | 321 ++++ p2p/pubsub/validation_test.go | 333 ++++ p2p/pubsub_server.go | 1 + 18 files changed, 6828 insertions(+) create mode 100644 p2p/pubsub/blacklist_test.go create mode 100644 p2p/pubsub/discovery_test.go create mode 100644 p2p/pubsub/gossip_tracer_test.go create mode 100644 p2p/pubsub/gossipsub_feat_test.go create mode 100644 p2p/pubsub/gossipsub_test.go create mode 100644 p2p/pubsub/mcache_test.go create mode 100644 p2p/pubsub/peer_gater_test.go create mode 100644 p2p/pubsub/pubsub_test.go create mode 100644 p2p/pubsub/score_params_test.go create mode 100644 p2p/pubsub/score_test.go create mode 100644 p2p/pubsub/sign_test.go create mode 100644 p2p/pubsub/subscription_filter_test.go create mode 100644 p2p/pubsub/tag_tracer_test.go create mode 100644 p2p/pubsub/topic_test.go create mode 100644 p2p/pubsub/trace_test.go create mode 100644 p2p/pubsub/validation_test.go diff --git a/p2p/host.go b/p2p/host.go index fb7eba28f6..054f4ab1e8 100644 --- a/p2p/host.go +++ b/p2p/host.go @@ -87,3 +87,7 @@ func (h *Host) ConnManager() connmgr.ConnManager { func (h *Host) NotifyAll(conn pubsub.Conn) { h.network.NotifyAll(conn) } + +func (h *Host) AddConn(p enode.ID, conn pubsub.Conn) { + h.network.SetConn(p, conn) +} diff --git a/p2p/pubsub/blacklist_test.go b/p2p/pubsub/blacklist_test.go new file mode 100644 index 0000000000..38ec33f66e --- /dev/null +++ b/p2p/pubsub/blacklist_test.go @@ -0,0 +1,127 @@ +package pubsub + +import ( + "context" + crand "crypto/rand" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "testing" + "time" +) + +func TestMapBlacklist(t *testing.T) { + b := NewMapBlacklist() + + var p enode.ID + crand.Read(p[:]) + + b.Add(p) + if !b.Contains(p) { + t.Fatal("peer not in the blacklist") + } + +} + +func TestTimeCachedBlacklist(t *testing.T) { + b, err := NewTimeCachedBlacklist(10 * time.Minute) + if err != nil { + t.Fatal(err) + } + + var p enode.ID + crand.Read(p[:]) + + b.Add(p) + if !b.Contains(p) { + t.Fatal("peer not in the blacklist") + } +} + +func TestBlacklist(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 2) + psubs := getPubsubs(ctx, hosts) + connect(t, hosts[0], hosts[1]) + + sub, err := psubs[1].Subscribe("test") + if err != nil { + t.Fatal(err) + } + + time.Sleep(time.Millisecond * 100) + psubs[1].BlacklistPeer(hosts[0].ID()) + time.Sleep(time.Millisecond * 100) + + psubs[0].Publish("test", []byte("message")) + + wctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + _, err = sub.Next(wctx) + + if err == nil { + t.Fatal("got message from blacklisted peer") + } +} + +func TestBlacklist2(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 2) + psubs := getPubsubs(ctx, hosts) + connect(t, hosts[0], hosts[1]) + + _, err := psubs[0].Subscribe("test") + if err != nil { + t.Fatal(err) + } + + sub1, err := psubs[1].Subscribe("test") + if err != nil { + t.Fatal(err) + } + + time.Sleep(time.Millisecond * 100) + psubs[1].BlacklistPeer(hosts[0].ID()) + time.Sleep(time.Millisecond * 100) + + psubs[0].Publish("test", []byte("message")) + + wctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + _, err = sub1.Next(wctx) + + if err == nil { + t.Fatal("got message from blacklisted peer") + } +} + +func TestBlacklist3(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 2) + psubs := getPubsubs(ctx, hosts) + + psubs[1].BlacklistPeer(hosts[0].ID()) + time.Sleep(time.Millisecond * 100) + connect(t, hosts[0], hosts[1]) + + sub, err := psubs[1].Subscribe("test") + if err != nil { + t.Fatal(err) + } + + time.Sleep(time.Millisecond * 100) + + psubs[0].Publish("test", []byte("message")) + + wctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + _, err = sub.Next(wctx) + + if err == nil { + t.Fatal("got message from blacklisted peer") + } +} diff --git a/p2p/pubsub/discovery_test.go b/p2p/pubsub/discovery_test.go new file mode 100644 index 0000000000..7be138e09b --- /dev/null +++ b/p2p/pubsub/discovery_test.go @@ -0,0 +1,309 @@ +package pubsub + +import ( + "bytes" + "context" + "fmt" + "math/rand" + "sync" + "testing" + "time" + + "github.com/libp2p/go-libp2p-core/discovery" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" +) + +type mockDiscoveryServer struct { + mx sync.Mutex + db map[string]map[peer.ID]*discoveryRegistration +} + +type discoveryRegistration struct { + info peer.AddrInfo + ttl time.Duration +} + +func newDiscoveryServer() *mockDiscoveryServer { + return &mockDiscoveryServer{ + db: make(map[string]map[peer.ID]*discoveryRegistration), + } +} + +func (s *mockDiscoveryServer) Advertise(ns string, info peer.AddrInfo, ttl time.Duration) (time.Duration, error) { + s.mx.Lock() + defer s.mx.Unlock() + + peers, ok := s.db[ns] + if !ok { + peers = make(map[peer.ID]*discoveryRegistration) + s.db[ns] = peers + } + peers[info.ID] = &discoveryRegistration{info, ttl} + return ttl, nil +} + +func (s *mockDiscoveryServer) FindPeers(ns string, limit int) (<-chan peer.AddrInfo, error) { + s.mx.Lock() + defer s.mx.Unlock() + + peers, ok := s.db[ns] + if !ok || len(peers) == 0 { + emptyCh := make(chan peer.AddrInfo) + close(emptyCh) + return emptyCh, nil + } + + count := len(peers) + if count > limit { + count = limit + } + ch := make(chan peer.AddrInfo, count) + numSent := 0 + for _, reg := range peers { + if numSent == count { + break + } + numSent++ + ch <- reg.info + } + close(ch) + + return ch, nil +} + +func (s *mockDiscoveryServer) hasPeerRecord(ns string, pid peer.ID) bool { + s.mx.Lock() + defer s.mx.Unlock() + + if peers, ok := s.db[ns]; ok { + _, ok := peers[pid] + return ok + } + return false +} + +type mockDiscoveryClient struct { + host host.Host + server *mockDiscoveryServer +} + +func (d *mockDiscoveryClient) Advertise(ctx context.Context, ns string, opts ...discovery.Option) (time.Duration, error) { + var options discovery.Options + err := options.Apply(opts...) + if err != nil { + return 0, err + } + + return d.server.Advertise(ns, *host.InfoFromHost(d.host), options.Ttl) +} + +func (d *mockDiscoveryClient) FindPeers(ctx context.Context, ns string, opts ...discovery.Option) (<-chan peer.AddrInfo, error) { + var options discovery.Options + err := options.Apply(opts...) + if err != nil { + return nil, err + } + + return d.server.FindPeers(ns, options.Limit) +} + +type dummyDiscovery struct{} + +func (d *dummyDiscovery) Advertise(ctx context.Context, ns string, opts ...discovery.Option) (time.Duration, error) { + return time.Hour, nil +} + +func (d *dummyDiscovery) FindPeers(ctx context.Context, ns string, opts ...discovery.Option) (<-chan peer.AddrInfo, error) { + retCh := make(chan peer.AddrInfo) + go func() { + time.Sleep(time.Second) + close(retCh) + }() + return retCh, nil +} + +func TestSimpleDiscovery(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Setup Discovery server and pubsub clients + const numHosts = 20 + const topic = "foobar" + + server := newDiscoveryServer() + discOpts := []discovery.Option{discovery.Limit(numHosts), discovery.TTL(1 * time.Minute)} + + hosts := getNetHosts(t, ctx, numHosts) + psubs := make([]*PubSub, numHosts) + topicHandlers := make([]*Topic, numHosts) + + for i, h := range hosts { + disc := &mockDiscoveryClient{h, server} + ps := getPubsub(ctx, h, WithDiscovery(disc, WithDiscoveryOpts(discOpts...))) + psubs[i] = ps + topicHandlers[i], _ = ps.Join(topic) + } + + // Subscribe with all but one pubsub instance + msgs := make([]*Subscription, numHosts) + for i, th := range topicHandlers[1:] { + subch, err := th.Subscribe() + if err != nil { + t.Fatal(err) + } + + msgs[i+1] = subch + } + + // Wait for the advertisements to go through then check that they did + for { + server.mx.Lock() + numPeers := len(server.db["floodsub:foobar"]) + server.mx.Unlock() + if numPeers == numHosts-1 { + break + } else { + time.Sleep(time.Millisecond * 100) + } + } + + for i, h := range hosts[1:] { + if !server.hasPeerRecord("floodsub:"+topic, h.ID()) { + t.Fatalf("Server did not register host %d with ID: %s", i+1, h.ID().Pretty()) + } + } + + // Try subscribing followed by publishing a single message + subch, err := topicHandlers[0].Subscribe() + if err != nil { + t.Fatal(err) + } + msgs[0] = subch + + msg := []byte("first message") + if err := topicHandlers[0].Publish(ctx, msg, WithReadiness(MinTopicSize(numHosts-1))); err != nil { + t.Fatal(err) + } + + for _, sub := range msgs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + + // Try random peers sending messages and make sure they are received + for i := 0; i < 100; i++ { + msg := []byte(fmt.Sprintf("%d the flooooooood %d", i, i)) + + owner := rand.Intn(len(psubs)) + + if err := topicHandlers[owner].Publish(ctx, msg, WithReadiness(MinTopicSize(1))); err != nil { + t.Fatal(err) + } + + for _, sub := range msgs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } +} + +func TestGossipSubDiscoveryAfterBootstrap(t *testing.T) { + t.Skip("flaky test disabled") + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Setup Discovery server and pubsub clients + partitionSize := GossipSubDlo - 1 + numHosts := partitionSize * 2 + const ttl = 1 * time.Minute + + const topic = "foobar" + + server1, server2 := newDiscoveryServer(), newDiscoveryServer() + discOpts := []discovery.Option{discovery.Limit(numHosts), discovery.TTL(ttl)} + + // Put the pubsub clients into two partitions + hosts := getNetHosts(t, ctx, numHosts) + psubs := make([]*PubSub, numHosts) + topicHandlers := make([]*Topic, numHosts) + + for i, h := range hosts { + s := server1 + if i >= partitionSize { + s = server2 + } + disc := &mockDiscoveryClient{h, s} + ps := getGossipsub(ctx, h, WithDiscovery(disc, WithDiscoveryOpts(discOpts...))) + psubs[i] = ps + topicHandlers[i], _ = ps.Join(topic) + } + + msgs := make([]*Subscription, numHosts) + for i, th := range topicHandlers { + subch, err := th.Subscribe() + if err != nil { + t.Fatal(err) + } + + msgs[i] = subch + } + + // Wait for network to finish forming then join the partitions via discovery + for _, ps := range psubs { + waitUntilGossipsubMeshCount(ps, topic, partitionSize-1) + } + + for i := 0; i < partitionSize; i++ { + if _, err := server1.Advertise("floodsub:"+topic, *host.InfoFromHost(hosts[i+partitionSize]), ttl); err != nil { + t.Fatal(err) + } + } + + // test the mesh + for i := 0; i < 100; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := rand.Intn(numHosts) + + if err := topicHandlers[owner].Publish(ctx, msg, WithReadiness(MinTopicSize(numHosts-1))); err != nil { + t.Fatal(err) + } + + for _, sub := range msgs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } +} + +//lint:ignore U1000 used only by skipped tests at present +func waitUntilGossipsubMeshCount(ps *PubSub, topic string, count int) { + done := false + doneCh := make(chan bool, 1) + rt := ps.rt.(*GossipSubRouter) + for !done { + ps.eval <- func() { + doneCh <- len(rt.mesh[topic]) == count + } + done = <-doneCh + if !done { + time.Sleep(100 * time.Millisecond) + } + } +} diff --git a/p2p/pubsub/gossip_tracer_test.go b/p2p/pubsub/gossip_tracer_test.go new file mode 100644 index 0000000000..787e753d26 --- /dev/null +++ b/p2p/pubsub/gossip_tracer_test.go @@ -0,0 +1,98 @@ +package pubsub + +import ( + "testing" + "time" + + pb "github.com/libp2p/go-libp2p-pubsub/pb" + + "github.com/libp2p/go-libp2p-core/peer" +) + +func TestBrokenPromises(t *testing.T) { + // tests that unfullfilled promises are tracked correctly + gt := newGossipTracer() + gt.followUpTime = 100 * time.Millisecond + + peerA := peer.ID("A") + peerB := peer.ID("B") + peerC := peer.ID("C") + + var mids []string + for i := 0; i < 100; i++ { + m := makeTestMessage(i) + m.From = []byte(peerA) + mid := DefaultMsgIdFn(m) + mids = append(mids, mid) + } + + gt.AddPromise(peerA, mids) + gt.AddPromise(peerB, mids) + gt.AddPromise(peerC, mids) + + // no broken promises yet + brokenPromises := gt.GetBrokenPromises() + if brokenPromises != nil { + t.Fatal("expected no broken promises") + } + + // throttle one of the peers to save his promises + gt.ThrottlePeer(peerC) + + // make promises break + time.Sleep(GossipSubIWantFollowupTime + 10*time.Millisecond) + + brokenPromises = gt.GetBrokenPromises() + if len(brokenPromises) != 2 { + t.Fatalf("expected 2 broken prmises, got %d", len(brokenPromises)) + } + + brokenPromisesA := brokenPromises[peerA] + if brokenPromisesA != 1 { + t.Fatalf("expected 1 broken promise from A, got %d", brokenPromisesA) + } + + brokenPromisesB := brokenPromises[peerB] + if brokenPromisesB != 1 { + t.Fatalf("expected 1 broken promise from A, got %d", brokenPromisesB) + } +} + +func TestNoBrokenPromises(t *testing.T) { + // like above, but this time we deliver messages to fullfil the promises + originalGossipSubIWantFollowupTime := GossipSubIWantFollowupTime + GossipSubIWantFollowupTime = 100 * time.Millisecond + defer func() { + GossipSubIWantFollowupTime = originalGossipSubIWantFollowupTime + }() + + gt := newGossipTracer() + + peerA := peer.ID("A") + peerB := peer.ID("B") + + var msgs []*pb.Message + var mids []string + for i := 0; i < 100; i++ { + m := makeTestMessage(i) + m.From = []byte(peerA) + msgs = append(msgs, m) + mid := DefaultMsgIdFn(m) + mids = append(mids, mid) + } + + gt.AddPromise(peerA, mids) + gt.AddPromise(peerB, mids) + + for _, m := range msgs { + gt.DeliverMessage(&Message{Message: m}) + } + + time.Sleep(GossipSubIWantFollowupTime + 10*time.Millisecond) + + // there should be no broken promises + brokenPromises := gt.GetBrokenPromises() + if brokenPromises != nil { + t.Fatal("expected no broken promises") + } +} diff --git a/p2p/pubsub/gossipsub_feat_test.go b/p2p/pubsub/gossipsub_feat_test.go new file mode 100644 index 0000000000..668ed86fff --- /dev/null +++ b/p2p/pubsub/gossipsub_feat_test.go @@ -0,0 +1,115 @@ +package pubsub + +import ( + "bytes" + "context" + "fmt" + "math/rand" + "testing" + "time" + + "github.com/libp2p/go-libp2p-core/protocol" +) + +func TestDefaultGossipSubFeatures(t *testing.T) { + if GossipSubDefaultFeatures(GossipSubFeatureMesh, FloodSubID) { + t.Fatal("floodsub should not support Mesh") + } + if !GossipSubDefaultFeatures(GossipSubFeatureMesh, GossipSubID_v10) { + t.Fatal("gossipsub-v1.0 should support Mesh") + } + if !GossipSubDefaultFeatures(GossipSubFeatureMesh, GossipSubID_v11) { + t.Fatal("gossipsub-v1.1 should support Mesh") + } + + if GossipSubDefaultFeatures(GossipSubFeaturePX, FloodSubID) { + t.Fatal("floodsub should not support PX") + } + if GossipSubDefaultFeatures(GossipSubFeaturePX, GossipSubID_v10) { + t.Fatal("gossipsub-v1.0 should not support PX") + } + if !GossipSubDefaultFeatures(GossipSubFeatureMesh, GossipSubID_v11) { + t.Fatal("gossipsub-v1.1 should support PX") + } +} + +func TestGossipSubCustomProtocols(t *testing.T) { + customsub := protocol.ID("customsub/1.0.0") + protos := []protocol.ID{customsub, FloodSubID} + features := func(feat GossipSubFeature, proto protocol.ID) bool { + return proto == customsub + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getNetHosts(t, ctx, 3) + + gsubs := getGossipsubs(ctx, hosts[:2], WithGossipSubProtocols(protos, features)) + fsub := getPubsub(ctx, hosts[2]) + psubs := append(gsubs, fsub) + + connectAll(t, hosts) + + topic := "test" + var subs []*Subscription + for _, ps := range psubs { + subch, err := ps.Subscribe(topic) + if err != nil { + t.Fatal(err) + } + + subs = append(subs, subch) + } + + // wait for heartbeats to build mesh + time.Sleep(time.Second * 2) + + // check the meshes of the gsubs, the gossipsub meshes should include each other but not the + // floddsub peer + gsubs[0].eval <- func() { + gs := gsubs[0].rt.(*GossipSubRouter) + + _, ok := gs.mesh[topic][hosts[1].ID()] + if !ok { + t.Fatal("expected gs0 to have gs1 in its mesh") + } + + _, ok = gs.mesh[topic][hosts[2].ID()] + if ok { + t.Fatal("expected gs0 to not have fs in its mesh") + } + } + + gsubs[1].eval <- func() { + gs := gsubs[1].rt.(*GossipSubRouter) + + _, ok := gs.mesh[topic][hosts[0].ID()] + if !ok { + t.Fatal("expected gs1 to have gs0 in its mesh") + } + + _, ok = gs.mesh[topic][hosts[2].ID()] + if ok { + t.Fatal("expected gs1 to not have fs in its mesh") + } + } + + // send some messages + for i := 0; i < 10; i++ { + msg := []byte(fmt.Sprintf("%d it's not quite a floooooood %d", i, i)) + + owner := rand.Intn(len(psubs)) + + psubs[owner].Publish(topic, msg) + + for _, sub := range subs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } +} diff --git a/p2p/pubsub/gossipsub_test.go b/p2p/pubsub/gossipsub_test.go new file mode 100644 index 0000000000..af0b0d6b11 --- /dev/null +++ b/p2p/pubsub/gossipsub_test.go @@ -0,0 +1,2269 @@ +package pubsub + +import ( + "bytes" + "context" + "fmt" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/peerstore" + "github.com/libp2p/go-libp2p-core/record" + "io" + "math/rand" + "sync" + "testing" + "time" + + "github.com/libp2p/go-msgio/protoio" +) + +func getGossipsub(ctx context.Context, h Host, opts ...Option) *PubSub { + ps, err := NewGossipSub(ctx, h, opts...) + if err != nil { + panic(err) + } + return ps +} + +func getGossipsubs(ctx context.Context, hs []Host, opts ...Option) []*PubSub { + var psubs []*PubSub + for _, h := range hs { + psubs = append(psubs, getGossipsub(ctx, h, opts...)) + } + return psubs +} + +func TestSparseGossipsub(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getNetHosts(t, ctx, 20) + + psubs := getGossipsubs(ctx, hosts) + + var msgs []*Subscription + for _, ps := range psubs { + subch, err := ps.Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + + msgs = append(msgs, subch) + } + + sparseConnect(t, hosts) + + // wait for heartbeats to build mesh + time.Sleep(time.Second * 2) + + for i := 0; i < 100; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := rand.Intn(len(psubs)) + + psubs[owner].Publish("foobar", msg) + + for _, sub := range msgs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } +} + +func TestDenseGossipsub(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getNetHosts(t, ctx, 20) + + psubs := getGossipsubs(ctx, hosts) + + var msgs []*Subscription + for _, ps := range psubs { + subch, err := ps.Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + + msgs = append(msgs, subch) + } + + denseConnect(t, hosts) + + // wait for heartbeats to build mesh + time.Sleep(time.Second * 2) + + for i := 0; i < 100; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := rand.Intn(len(psubs)) + + psubs[owner].Publish("foobar", msg) + + for _, sub := range msgs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } +} + +func TestGossipsubFanout(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getNetHosts(t, ctx, 20) + + psubs := getGossipsubs(ctx, hosts) + + var msgs []*Subscription + for _, ps := range psubs[1:] { + subch, err := ps.Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + + msgs = append(msgs, subch) + } + + denseConnect(t, hosts) + + // wait for heartbeats to build mesh + time.Sleep(time.Second * 2) + + for i := 0; i < 100; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := 0 + + psubs[owner].Publish("foobar", msg) + + for _, sub := range msgs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } + + // subscribe the owner + subch, err := psubs[0].Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + msgs = append(msgs, subch) + + // wait for a heartbeat + time.Sleep(time.Second * 1) + + for i := 0; i < 100; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := 0 + + psubs[owner].Publish("foobar", msg) + + for _, sub := range msgs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } +} + +func TestGossipsubFanoutMaintenance(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getNetHosts(t, ctx, 20) + + psubs := getGossipsubs(ctx, hosts) + + var msgs []*Subscription + for _, ps := range psubs[1:] { + subch, err := ps.Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + + msgs = append(msgs, subch) + } + + denseConnect(t, hosts) + + // wait for heartbeats to build mesh + time.Sleep(time.Second * 2) + + for i := 0; i < 100; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := 0 + + psubs[owner].Publish("foobar", msg) + + for _, sub := range msgs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } + + // unsubscribe all peers to exercise fanout maintenance + for _, sub := range msgs { + sub.Cancel() + } + msgs = nil + + // wait for heartbeats + time.Sleep(time.Second * 2) + + // resubscribe and repeat + for _, ps := range psubs[1:] { + subch, err := ps.Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + + msgs = append(msgs, subch) + } + + time.Sleep(time.Second * 2) + + for i := 0; i < 100; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := 0 + + psubs[owner].Publish("foobar", msg) + + for _, sub := range msgs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } +} + +func TestGossipsubFanoutExpiry(t *testing.T) { + GossipSubFanoutTTL = 1 * time.Second + defer func() { + GossipSubFanoutTTL = 60 * time.Second + }() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getNetHosts(t, ctx, 10) + + psubs := getGossipsubs(ctx, hosts) + + var msgs []*Subscription + for _, ps := range psubs[1:] { + subch, err := ps.Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + + msgs = append(msgs, subch) + } + + denseConnect(t, hosts) + + // wait for heartbeats to build mesh + time.Sleep(time.Second * 2) + + for i := 0; i < 5; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := 0 + + psubs[owner].Publish("foobar", msg) + + for _, sub := range msgs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } + + psubs[0].eval <- func() { + if len(psubs[0].rt.(*GossipSubRouter).fanout) == 0 { + t.Fatal("owner has no fanout") + } + } + + // wait for TTL to expire fanout peers in owner + time.Sleep(time.Second * 2) + + psubs[0].eval <- func() { + if len(psubs[0].rt.(*GossipSubRouter).fanout) > 0 { + t.Fatal("fanout hasn't expired") + } + } + + // wait for it to run in the event loop + time.Sleep(10 * time.Millisecond) +} + +func TestGossipsubGossip(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getNetHosts(t, ctx, 20) + + psubs := getGossipsubs(ctx, hosts) + + var msgs []*Subscription + for _, ps := range psubs { + subch, err := ps.Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + + msgs = append(msgs, subch) + } + + denseConnect(t, hosts) + + // wait for heartbeats to build mesh + time.Sleep(time.Second * 2) + + for i := 0; i < 100; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := rand.Intn(len(psubs)) + + psubs[owner].Publish("foobar", msg) + + for _, sub := range msgs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + + // wait a bit to have some gossip interleaved + time.Sleep(time.Millisecond * 100) + } + + // and wait for some gossip flushing + time.Sleep(time.Second * 2) +} + +func TestGossipsubGossipPiggyback(t *testing.T) { + t.Skip("test no longer relevant; gossip propagation has become eager") + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getNetHosts(t, ctx, 20) + + psubs := getGossipsubs(ctx, hosts) + + var msgs []*Subscription + for _, ps := range psubs { + subch, err := ps.Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + + msgs = append(msgs, subch) + } + + var xmsgs []*Subscription + for _, ps := range psubs { + subch, err := ps.Subscribe("bazcrux") + if err != nil { + t.Fatal(err) + } + + xmsgs = append(xmsgs, subch) + } + + denseConnect(t, hosts) + + // wait for heartbeats to build mesh + time.Sleep(time.Second * 2) + + for i := 0; i < 100; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := rand.Intn(len(psubs)) + + psubs[owner].Publish("foobar", msg) + psubs[owner].Publish("bazcrux", msg) + + for _, sub := range msgs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + + for _, sub := range xmsgs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + + // wait a bit to have some gossip interleaved + time.Sleep(time.Millisecond * 100) + } + + // and wait for some gossip flushing + time.Sleep(time.Second * 2) +} + +func TestGossipsubGossipPropagation(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 20) + psubs := getGossipsubs(ctx, hosts) + + hosts1 := hosts[:GossipSubD+1] + hosts2 := append(hosts[GossipSubD+1:], hosts[0]) + + denseConnect(t, hosts1) + denseConnect(t, hosts2) + + var msgs1 []*Subscription + for _, ps := range psubs[1 : GossipSubD+1] { + subch, err := ps.Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + + msgs1 = append(msgs1, subch) + } + + time.Sleep(time.Second * 1) + + for i := 0; i < 10; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := 0 + + psubs[owner].Publish("foobar", msg) + + for _, sub := range msgs1 { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } + + time.Sleep(time.Millisecond * 100) + + var msgs2 []*Subscription + for _, ps := range psubs[GossipSubD+1:] { + subch, err := ps.Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + + msgs2 = append(msgs2, subch) + } + + var collect [][]byte + for i := 0; i < 10; i++ { + for _, sub := range msgs2 { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + collect = append(collect, got.Data) + } + } + + for i := 0; i < 10; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + gotit := false + for j := 0; j < len(collect); j++ { + if bytes.Equal(msg, collect[j]) { + gotit = true + break + } + } + if !gotit { + t.Fatalf("Didn't get message %s", string(msg)) + } + } +} + +func TestGossipsubPrune(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getNetHosts(t, ctx, 20) + + psubs := getGossipsubs(ctx, hosts) + + var msgs []*Subscription + for _, ps := range psubs { + subch, err := ps.Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + + msgs = append(msgs, subch) + } + + denseConnect(t, hosts) + + // wait for heartbeats to build mesh + time.Sleep(time.Second * 2) + + // disconnect some peers from the mesh to get some PRUNEs + for _, sub := range msgs[:5] { + sub.Cancel() + } + + // wait a bit to take effect + time.Sleep(time.Millisecond * 100) + + for i := 0; i < 10; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := rand.Intn(len(psubs)) + + psubs[owner].Publish("foobar", msg) + + for _, sub := range msgs[5:] { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } +} + +func TestGossipsubGraft(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getNetHosts(t, ctx, 20) + + psubs := getGossipsubs(ctx, hosts) + + sparseConnect(t, hosts) + + time.Sleep(time.Second * 1) + + var msgs []*Subscription + for _, ps := range psubs { + subch, err := ps.Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + + msgs = append(msgs, subch) + + // wait for announce to propagate + time.Sleep(time.Millisecond * 100) + } + + time.Sleep(time.Second * 1) + + for i := 0; i < 100; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := rand.Intn(len(psubs)) + + psubs[owner].Publish("foobar", msg) + + for _, sub := range msgs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } +} + +func TestGossipsubRemovePeer(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getNetHosts(t, ctx, 20) + + psubs := getGossipsubs(ctx, hosts) + + var msgs []*Subscription + for _, ps := range psubs { + subch, err := ps.Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + + msgs = append(msgs, subch) + } + + denseConnect(t, hosts) + + // wait for heartbeats to build mesh + time.Sleep(time.Second * 2) + + // disconnect some peers to exercise RemovePeer paths + for _, host := range hosts[:5] { + host.Close() + } + + // wait a heartbeat + time.Sleep(time.Second * 1) + + for i := 0; i < 10; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := 5 + rand.Intn(len(psubs)-5) + + psubs[owner].Publish("foobar", msg) + + for _, sub := range msgs[5:] { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } +} + +func TestGossipsubGraftPruneRetry(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 10) + psubs := getGossipsubs(ctx, hosts) + denseConnect(t, hosts) + + var topics []string + var msgs [][]*Subscription + for i := 0; i < 35; i++ { + topic := fmt.Sprintf("topic%d", i) + topics = append(topics, topic) + + var subs []*Subscription + for _, ps := range psubs { + subch, err := ps.Subscribe(topic) + if err != nil { + t.Fatal(err) + } + + subs = append(subs, subch) + } + msgs = append(msgs, subs) + } + + // wait for heartbeats to build meshes + time.Sleep(time.Second * 5) + + for i, topic := range topics { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := rand.Intn(len(psubs)) + + psubs[owner].Publish(topic, msg) + + for _, sub := range msgs[i] { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } +} + +func TestGossipsubControlPiggyback(t *testing.T) { + t.Skip("travis regularly fails on this test") + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 10) + psubs := getGossipsubs(ctx, hosts) + denseConnect(t, hosts) + + for _, ps := range psubs { + subch, err := ps.Subscribe("flood") + if err != nil { + t.Fatal(err) + } + go func(sub *Subscription) { + for { + _, err := sub.Next(ctx) + if err != nil { + break + } + } + }(subch) + } + + time.Sleep(time.Second * 1) + + // create a background flood of messages that overloads the queues + done := make(chan struct{}) + go func() { + owner := rand.Intn(len(psubs)) + for i := 0; i < 10000; i++ { + msg := []byte("background flooooood") + psubs[owner].Publish("flood", msg) + } + done <- struct{}{} + }() + + time.Sleep(time.Millisecond * 20) + + // and subscribe to a bunch of topics in the meantime -- this should + // result in some dropped control messages, with subsequent piggybacking + // in the background flood + var topics []string + var msgs [][]*Subscription + for i := 0; i < 5; i++ { + topic := fmt.Sprintf("topic%d", i) + topics = append(topics, topic) + + var subs []*Subscription + for _, ps := range psubs { + subch, err := ps.Subscribe(topic) + if err != nil { + t.Fatal(err) + } + + subs = append(subs, subch) + } + msgs = append(msgs, subs) + } + + // wait for the flood to stop + <-done + + // and test that we have functional overlays + for i, topic := range topics { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := rand.Intn(len(psubs)) + + psubs[owner].Publish(topic, msg) + + for _, sub := range msgs[i] { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } +} + +func TestMixedGossipsub(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getNetHosts(t, ctx, 30) + + gsubs := getGossipsubs(ctx, hosts[:20]) + fsubs := getPubsubs(ctx, hosts[20:]) + psubs := append(gsubs, fsubs...) + + var msgs []*Subscription + for _, ps := range psubs { + subch, err := ps.Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + + msgs = append(msgs, subch) + } + + sparseConnect(t, hosts) + + // wait for heartbeats to build mesh + time.Sleep(time.Second * 2) + + for i := 0; i < 100; i++ { + msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) + + owner := rand.Intn(len(psubs)) + + psubs[owner].Publish("foobar", msg) + + for _, sub := range msgs { + got, err := sub.Next(ctx) + if err != nil { + t.Fatal(sub.err) + } + if !bytes.Equal(msg, got.Data) { + t.Fatal("got wrong message!") + } + } + } +} + +func TestGossipsubMultihops(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 6) + + psubs := getGossipsubs(ctx, hosts) + + connect(t, hosts[0], hosts[1]) + connect(t, hosts[1], hosts[2]) + connect(t, hosts[2], hosts[3]) + connect(t, hosts[3], hosts[4]) + connect(t, hosts[4], hosts[5]) + + var subs []*Subscription + for i := 1; i < 6; i++ { + ch, err := psubs[i].Subscribe("foobar") + if err != nil { + t.Fatal(err) + } + subs = append(subs, ch) + } + + // wait for heartbeats to build mesh + time.Sleep(time.Second * 2) + + msg := []byte("i like cats") + err := psubs[0].Publish("foobar", msg) + if err != nil { + t.Fatal(err) + } + + // last node in the chain should get the message + select { + case out := <-subs[4].ch: + if !bytes.Equal(out.GetData(), msg) { + t.Fatal("got wrong data") + } + case <-time.After(time.Second * 5): + t.Fatal("timed out waiting for message") + } +} + +func TestGossipsubTreeTopology(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 10) + psubs := getGossipsubs(ctx, hosts) + + connect(t, hosts[0], hosts[1]) + connect(t, hosts[1], hosts[2]) + connect(t, hosts[1], hosts[4]) + connect(t, hosts[2], hosts[3]) + connect(t, hosts[0], hosts[5]) + connect(t, hosts[5], hosts[6]) + connect(t, hosts[5], hosts[8]) + connect(t, hosts[6], hosts[7]) + connect(t, hosts[8], hosts[9]) + + /* + [0] -> [1] -> [2] -> [3] + | L->[4] + v + [5] -> [6] -> [7] + | + v + [8] -> [9] + */ + + var chs []*Subscription + for _, ps := range psubs { + ch, err := ps.Subscribe("fizzbuzz") + if err != nil { + t.Fatal(err) + } + + chs = append(chs, ch) + } + + // wait for heartbeats to build mesh + time.Sleep(time.Second * 2) + + assertPeerLists(t, hosts, psubs[0], 1, 5) + assertPeerLists(t, hosts, psubs[1], 0, 2, 4) + assertPeerLists(t, hosts, psubs[2], 1, 3) + + checkMessageRouting(t, "fizzbuzz", []*PubSub{psubs[9], psubs[3]}, chs) +} + +// this tests overlay bootstrapping through px in Gossipsub v1.1 +// we start with a star topology and rely on px through prune to build the mesh +func TestGossipsubStarTopology(t *testing.T) { + originalGossipSubD := GossipSubD + GossipSubD = 4 + originalGossipSubDhi := GossipSubDhi + GossipSubDhi = GossipSubD + 1 + originalGossipSubDlo := GossipSubDlo + GossipSubDlo = GossipSubD - 1 + originalGossipSubDscore := GossipSubDscore + GossipSubDscore = GossipSubDlo + defer func() { + GossipSubD = originalGossipSubD + GossipSubDhi = originalGossipSubDhi + GossipSubDlo = originalGossipSubDlo + GossipSubDscore = originalGossipSubDscore + }() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 20) + psubs := getGossipsubs(ctx, hosts, WithPeerExchange(true)) + + // configure the center of the star with a very low D + psubs[0].eval <- func() { + gs := psubs[0].rt.(*GossipSubRouter) + gs.params.D = 0 + gs.params.Dlo = 0 + gs.params.Dhi = 0 + gs.params.Dscore = 0 + } + + // build the star + for i := 1; i < 20; i++ { + connect(t, hosts[0], hosts[i]) + } + + time.Sleep(time.Second) + + // build the mesh + var subs []*Subscription + for _, ps := range psubs { + sub, err := ps.Subscribe("test") + if err != nil { + t.Fatal(err) + } + subs = append(subs, sub) + } + + // wait a bit for the mesh to build + time.Sleep(10 * time.Second) + + // check that all peers have > 1 connection + for i, h := range hosts { + if len(h.Network().Conns()) == 1 { + t.Errorf("peer %d has ony a single connection", i) + } + } + + // send a message from each peer and assert it was propagated + for i := 0; i < 20; i++ { + msg := []byte(fmt.Sprintf("message %d", i)) + psubs[i].Publish("test", msg) + + for _, sub := range subs { + assertReceive(t, sub, msg) + } + } +} + +// this tests overlay bootstrapping through px in Gossipsub v1.1, with addresses +// exchanged in signed peer records. +// we start with a star topology and rely on px through prune to build the mesh +func TestGossipsubStarTopologyWithSignedPeerRecords(t *testing.T) { + originalGossipSubD := GossipSubD + GossipSubD = 4 + originalGossipSubDhi := GossipSubDhi + GossipSubDhi = GossipSubD + 1 + originalGossipSubDlo := GossipSubDlo + GossipSubDlo = GossipSubD - 1 + originalGossipSubDscore := GossipSubDscore + GossipSubDscore = GossipSubDlo + defer func() { + GossipSubD = originalGossipSubD + GossipSubDhi = originalGossipSubDhi + GossipSubDlo = originalGossipSubDlo + GossipSubDscore = originalGossipSubDscore + }() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 20) + psubs := getGossipsubs(ctx, hosts, WithPeerExchange(true)) + + // configure the center of the star with a very low D + psubs[0].eval <- func() { + gs := psubs[0].rt.(*GossipSubRouter) + gs.params.D = 0 + gs.params.Dlo = 0 + gs.params.Dhi = 0 + gs.params.Dscore = 0 + } + + // manually create signed peer records for each host and add them to the + // peerstore of the center of the star, which is doing the bootstrapping + for i := range hosts[1:] { + privKey := hosts[i].Peerstore().PrivKey(hosts[i].ID()) + if privKey == nil { + t.Fatalf("unable to get private key for host %s", hosts[i].ID().Pretty()) + } + ai := host.InfoFromHost(hosts[i]) + rec := peer.PeerRecordFromAddrInfo(*ai) + signedRec, err := record.Seal(rec, privKey) + if err != nil { + t.Fatalf("error creating signed peer record: %s", err) + } + + cab, ok := peerstore.GetCertifiedAddrBook(hosts[0].Peerstore()) + if !ok { + t.Fatal("peerstore does not implement CertifiedAddrBook") + } + _, err = cab.ConsumePeerRecord(signedRec, peerstore.PermanentAddrTTL) + if err != nil { + t.Fatalf("error adding signed peer record: %s", err) + } + } + + // build the star + for i := 1; i < 20; i++ { + connect(t, hosts[0], hosts[i]) + } + + time.Sleep(time.Second) + + // build the mesh + var subs []*Subscription + for _, ps := range psubs { + sub, err := ps.Subscribe("test") + if err != nil { + t.Fatal(err) + } + subs = append(subs, sub) + } + + // wait a bit for the mesh to build + time.Sleep(10 * time.Second) + + // check that all peers have > 1 connection + for i, h := range hosts { + if len(h.Network().Conns()) == 1 { + t.Errorf("peer %d has ony a single connection", i) + } + } + + // send a message from each peer and assert it was propagated + for i := 0; i < 20; i++ { + msg := []byte(fmt.Sprintf("message %d", i)) + psubs[i].Publish("test", msg) + + for _, sub := range subs { + assertReceive(t, sub, msg) + } + } +} + +func TestGossipsubDirectPeers(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + h := getNetHosts(t, ctx, 3) + psubs := []*PubSub{ + getGossipsub(ctx, h[0], WithDirectConnectTicks(2)), + getGossipsub(ctx, h[1]), + getGossipsub(ctx, h[2]), + } + + connect(t, h[0], h[1]) + connect(t, h[0], h[2]) + + // verify that the direct peers connected + time.Sleep(2 * time.Second) + if len(h[1].Network().ConnsToPeer(h[2].ID().ID())) == 0 { + t.Fatal("expected a connection between direct peers") + } + + // build the mesh + var subs []*Subscription + for _, ps := range psubs { + sub, err := ps.Subscribe("test") + if err != nil { + t.Fatal(err) + } + subs = append(subs, sub) + } + + time.Sleep(time.Second) + + // publish some messages + for i := 0; i < 3; i++ { + msg := []byte(fmt.Sprintf("message %d", i)) + psubs[i].Publish("test", msg) + + for _, sub := range subs { + assertReceive(t, sub, msg) + } + } + + // disconnect the direct peers to test reconnection + for _, c := range h[1].Network().ConnsToPeer(h[2].ID()) { + c.Close() + } + + time.Sleep(5 * time.Second) + + if len(h[1].Network().ConnsToPeer(h[2].ID())) == 0 { + t.Fatal("expected a connection between direct peers") + } + + // publish some messages + for i := 0; i < 3; i++ { + msg := []byte(fmt.Sprintf("message %d", i)) + psubs[i].Publish("test", msg) + + for _, sub := range subs { + assertReceive(t, sub, msg) + } + } +} + +func TestGossipSubPeerFilter(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + h := getNetHosts(t, ctx, 3) + psubs := []*PubSub{ + getGossipsub(ctx, h[0], WithPeerFilter(func(pid peer.ID, topic string) bool { + return pid == h[1].ID() + })), + getGossipsub(ctx, h[1], WithPeerFilter(func(pid peer.ID, topic string) bool { + return pid == h[0].ID() + })), + getGossipsub(ctx, h[2]), + } + + connect(t, h[0], h[1]) + connect(t, h[0], h[2]) + + // Join all peers + var subs []*Subscription + for _, ps := range psubs { + sub, err := ps.Subscribe("test") + if err != nil { + t.Fatal(err) + } + subs = append(subs, sub) + } + + time.Sleep(time.Second) + + msg := []byte("message") + + psubs[0].Publish("test", msg) + assertReceive(t, subs[1], msg) + assertNeverReceives(t, subs[2], time.Second) + + psubs[1].Publish("test", msg) + assertReceive(t, subs[0], msg) + assertNeverReceives(t, subs[2], time.Second) +} + +func TestGossipsubDirectPeersFanout(t *testing.T) { + // regression test for #371 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + h := getNetHosts(t, ctx, 3) + psubs := []*PubSub{ + getGossipsub(ctx, h[0]), + getGossipsub(ctx, h[1], WithDirectPeers([]peer.AddrInfo{{ID: h[2].ID(), Addrs: h[2].Addrs()}})), + getGossipsub(ctx, h[2], WithDirectPeers([]peer.AddrInfo{{ID: h[1].ID(), Addrs: h[1].Addrs()}})), + } + + connect(t, h[0], h[1]) + connect(t, h[0], h[2]) + + // Join all peers except h2 + var subs []*Subscription + for _, ps := range psubs[:2] { + sub, err := ps.Subscribe("test") + if err != nil { + t.Fatal(err) + } + subs = append(subs, sub) + } + + time.Sleep(time.Second) + + // h2 publishes some messages to build a fanout + for i := 0; i < 3; i++ { + msg := []byte(fmt.Sprintf("message %d", i)) + psubs[2].Publish("test", msg) + + for _, sub := range subs { + assertReceive(t, sub, msg) + } + } + + // verify that h0 is in the fanout of h2, but not h1 who is a direct peer + result := make(chan bool, 2) + psubs[2].eval <- func() { + rt := psubs[2].rt.(*GossipSubRouter) + fanout := rt.fanout["test"] + _, ok := fanout[h[0].ID()] + result <- ok + _, ok = fanout[h[1].ID()] + result <- ok + } + + inFanout := <-result + if !inFanout { + t.Fatal("expected peer 0 to be in fanout") + } + + inFanout = <-result + if inFanout { + t.Fatal("expected peer 1 to not be in fanout") + } + + // now subscribe h2 too and verify tht h0 is in the mesh but not h1 + _, err := psubs[2].Subscribe("test") + if err != nil { + t.Fatal(err) + } + + time.Sleep(2 * time.Second) + + psubs[2].eval <- func() { + rt := psubs[2].rt.(*GossipSubRouter) + mesh := rt.mesh["test"] + _, ok := mesh[h[0].ID()] + result <- ok + _, ok = mesh[h[1].ID()] + result <- ok + } + + inMesh := <-result + if !inMesh { + t.Fatal("expected peer 0 to be in mesh") + } + + inMesh = <-result + if inMesh { + t.Fatal("expected peer 1 to not be in mesh") + } +} + +func TestGossipsubFloodPublish(t *testing.T) { + // uses a star topology without PX and publishes from the star to verify that all + // messages get received + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 20) + psubs := getGossipsubs(ctx, hosts, WithFloodPublish(true)) + + // build the star + for i := 1; i < 20; i++ { + connect(t, hosts[0], hosts[i]) + } + + // build the (partial, unstable) mesh + var subs []*Subscription + for _, ps := range psubs { + sub, err := ps.Subscribe("test") + if err != nil { + t.Fatal(err) + } + subs = append(subs, sub) + } + + time.Sleep(time.Second) + + // send a message from the star and assert it was received + for i := 0; i < 20; i++ { + msg := []byte(fmt.Sprintf("message %d", i)) + psubs[0].Publish("test", msg) + + for _, sub := range subs { + assertReceive(t, sub, msg) + } + } +} + +func TestGossipsubEnoughPeers(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 20) + psubs := getGossipsubs(ctx, hosts) + + for _, ps := range psubs { + _, err := ps.Subscribe("test") + if err != nil { + t.Fatal(err) + } + } + + // at this point we have no connections and no mesh, so EnoughPeers should return false + res := make(chan bool, 1) + psubs[0].eval <- func() { + res <- psubs[0].rt.EnoughPeers("test", 0) + } + enough := <-res + if enough { + t.Fatal("should not have enough peers") + } + + // connect them densly to build up the mesh + denseConnect(t, hosts) + + time.Sleep(3 * time.Second) + + psubs[0].eval <- func() { + res <- psubs[0].rt.EnoughPeers("test", 0) + } + enough = <-res + if !enough { + t.Fatal("should have enough peers") + } +} + +func TestGossipsubCustomParams(t *testing.T) { + // in this test we score sinkhole a peer to exercise code paths relative to negative scores + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + params := DefaultGossipSubParams() + + wantedFollowTime := 1 * time.Second + params.IWantFollowupTime = wantedFollowTime + + customGossipFactor := 0.12 + params.GossipFactor = customGossipFactor + + wantedMaxPendingConns := 23 + params.MaxPendingConnections = wantedMaxPendingConns + hosts := getNetHosts(t, ctx, 1) + psubs := getGossipsubs(ctx, hosts, + WithGossipSubParams(params)) + + if len(psubs) != 1 { + t.Fatalf("incorrect number of pusbub objects received: wanted %d but got %d", 1, len(psubs)) + } + + rt, ok := psubs[0].rt.(*GossipSubRouter) + if !ok { + t.Fatal("Did not get gossip sub router from pub sub object") + } + + if rt.params.IWantFollowupTime != wantedFollowTime { + t.Errorf("Wanted %d of param GossipSubIWantFollowupTime but got %d", wantedFollowTime, rt.params.IWantFollowupTime) + } + if rt.params.GossipFactor != customGossipFactor { + t.Errorf("Wanted %f of param GossipSubGossipFactor but got %f", customGossipFactor, rt.params.GossipFactor) + } + if rt.params.MaxPendingConnections != wantedMaxPendingConns { + t.Errorf("Wanted %d of param GossipSubMaxPendingConnections but got %d", wantedMaxPendingConns, rt.params.MaxPendingConnections) + } +} + +func TestGossipsubNegativeScore(t *testing.T) { + // in this test we score sinkhole a peer to exercise code paths relative to negative scores + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 20) + psubs := getGossipsubs(ctx, hosts, + WithPeerScore( + &PeerScoreParams{ + AppSpecificScore: func(p peer.ID) float64 { + if p == hosts[0].ID() { + return -1000 + } else { + return 0 + } + }, + AppSpecificWeight: 1, + DecayInterval: time.Second, + DecayToZero: 0.01, + }, + &PeerScoreThresholds{ + GossipThreshold: -10, + PublishThreshold: -100, + GraylistThreshold: -10000, + })) + + denseConnect(t, hosts) + + var subs []*Subscription + for _, ps := range psubs { + sub, err := ps.Subscribe("test") + if err != nil { + t.Fatal(err) + } + subs = append(subs, sub) + } + + time.Sleep(3 * time.Second) + + for i := 0; i < 20; i++ { + msg := []byte(fmt.Sprintf("message %d", i)) + psubs[i%20].Publish("test", msg) + time.Sleep(20 * time.Millisecond) + } + + // let the sinkholed peer try to emit gossip as well + time.Sleep(2 * time.Second) + + // checks: + // 1. peer 0 should only receive its own message + // 2. peers 1-20 should not receive a message from peer 0, because it's not part of the mesh + // and its gossip is rejected + collectAll := func(sub *Subscription) []*Message { + var res []*Message + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + + for { + msg, err := sub.Next(ctx) + if err != nil { + break + } + + res = append(res, msg) + } + + return res + } + + count := len(collectAll(subs[0])) + if count != 1 { + t.Fatalf("expected 1 message but got %d instead", count) + } + + for _, sub := range subs[1:] { + all := collectAll(sub) + for _, m := range all { + if m.ReceivedFrom == hosts[0].ID() { + t.Fatal("received message from sinkholed peer") + } + } + } +} + +func TestGossipsubScoreValidatorEx(t *testing.T) { + // this is a test that of the two message drop responses from a validator + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 3) + psubs := getGossipsubs(ctx, hosts, + WithPeerScore( + &PeerScoreParams{ + AppSpecificScore: func(p peer.ID) float64 { return 0 }, + DecayInterval: time.Second, + DecayToZero: 0.01, + Topics: map[string]*TopicScoreParams{ + "test": { + TopicWeight: 1, + TimeInMeshQuantum: time.Second, + InvalidMessageDeliveriesWeight: -1, + InvalidMessageDeliveriesDecay: 0.9999, + }, + }, + }, + &PeerScoreThresholds{ + GossipThreshold: -10, + PublishThreshold: -100, + GraylistThreshold: -10000, + })) + + connectAll(t, hosts) + + err := psubs[0].RegisterTopicValidator("test", func(ctx context.Context, p peer.ID, msg *Message) ValidationResult { + // we ignore host1 and reject host2 + if p == hosts[1].ID() { + return ValidationIgnore + } + if p == hosts[2].ID() { + return ValidationReject + } + + return ValidationAccept + }) + if err != nil { + t.Fatal(err) + } + + sub, err := psubs[0].Subscribe("test") + if err != nil { + t.Fatal(err) + } + + time.Sleep(100 * time.Millisecond) + + expectNoMessage := func(sub *Subscription) { + ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) + defer cancel() + + m, err := sub.Next(ctx) + if err == nil { + t.Fatal("expected no message, but got ", string(m.Data)) + } + } + + psubs[1].Publish("test", []byte("i am not a walrus")) + psubs[2].Publish("test", []byte("i am not a walrus either")) + + // assert no messages + expectNoMessage(sub) + + // assert that peer1's score is still 0 (its message was ignored) while peer2 should have + // a negative score (its message got rejected) + res := make(chan float64, 1) + psubs[0].eval <- func() { + res <- psubs[0].rt.(*GossipSubRouter).score.Score(hosts[1].ID()) + } + score := <-res + if score != 0 { + t.Fatalf("expected 0 score for peer1, but got %f", score) + } + + psubs[0].eval <- func() { + res <- psubs[0].rt.(*GossipSubRouter).score.Score(hosts[2].ID()) + } + score = <-res + if score >= 0 { + t.Fatalf("expected negative score for peer2, but got %f", score) + } +} + +func TestGossipsubPiggybackControl(t *testing.T) { + // this is a direct test of the piggybackControl function as we can't reliably + // trigger it on travis + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + h := NewTestHost() + ps := getGossipsub(ctx, h) + + blah := peer.ID("bogotr0n") + + res := make(chan *RPC, 1) + ps.eval <- func() { + gs := ps.rt.(*GossipSubRouter) + test1 := "test1" + test2 := "test2" + test3 := "test3" + gs.mesh[test1] = make(map[peer.ID]struct{}) + gs.mesh[test2] = make(map[peer.ID]struct{}) + gs.mesh[test1][blah] = struct{}{} + + rpc := &RPC{RPC: pb.RPC{}} + gs.piggybackControl(blah, rpc, &pb.ControlMessage{ + Graft: []*pb.ControlGraft{{TopicID: &test1}, {TopicID: &test2}, {TopicID: &test3}}, + Prune: []*pb.ControlPrune{{TopicID: &test1}, {TopicID: &test2}, {TopicID: &test3}}, + }) + res <- rpc + } + + rpc := <-res + if rpc.Control == nil { + t.Fatal("expected non-nil control message") + } + if len(rpc.Control.Graft) != 1 { + t.Fatal("expected 1 GRAFT") + } + if rpc.Control.Graft[0].GetTopicID() != "test1" { + t.Fatal("expected test1 as graft topic ID") + } + if len(rpc.Control.Prune) != 2 { + t.Fatal("expected 2 PRUNEs") + } + if rpc.Control.Prune[0].GetTopicID() != "test2" { + t.Fatal("expected test2 as prune topic ID") + } + if rpc.Control.Prune[1].GetTopicID() != "test3" { + t.Fatal("expected test3 as prune topic ID") + } +} + +func TestGossipsubMultipleGraftTopics(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 2) + psubs := getGossipsubs(ctx, hosts) + sparseConnect(t, hosts) + + time.Sleep(time.Second * 1) + + firstTopic := "topic1" + secondTopic := "topic2" + thirdTopic := "topic3" + + firstPeer := hosts[0].ID() + secondPeer := hosts[1].ID() + + p2Sub := psubs[1] + p1Router := psubs[0].rt.(*GossipSubRouter) + p2Router := psubs[1].rt.(*GossipSubRouter) + + finChan := make(chan struct{}) + + p2Sub.eval <- func() { + // Add topics to second peer + p2Router.mesh[firstTopic] = map[peer.ID]struct{}{} + p2Router.mesh[secondTopic] = map[peer.ID]struct{}{} + p2Router.mesh[thirdTopic] = map[peer.ID]struct{}{} + + finChan <- struct{}{} + } + <-finChan + + // Send multiple GRAFT messages to second peer from + // 1st peer + p1Router.sendGraftPrune(map[peer.ID][]string{ + secondPeer: {firstTopic, secondTopic, thirdTopic}, + }, map[peer.ID][]string{}, map[peer.ID]bool{}) + + time.Sleep(time.Second * 1) + + p2Sub.eval <- func() { + if _, ok := p2Router.mesh[firstTopic][firstPeer]; !ok { + t.Errorf("First peer wasnt added to mesh of the second peer for the topic %s", firstTopic) + } + if _, ok := p2Router.mesh[secondTopic][firstPeer]; !ok { + t.Errorf("First peer wasnt added to mesh of the second peer for the topic %s", secondTopic) + } + if _, ok := p2Router.mesh[thirdTopic][firstPeer]; !ok { + t.Errorf("First peer wasnt added to mesh of the second peer for the topic %s", thirdTopic) + } + finChan <- struct{}{} + } + <-finChan +} + +func TestGossipsubOpportunisticGrafting(t *testing.T) { + originalGossipSubPruneBackoff := GossipSubPruneBackoff + GossipSubPruneBackoff = 500 * time.Millisecond + originalGossipSubGraftFloodThreshold := GossipSubGraftFloodThreshold + GossipSubGraftFloodThreshold = 100 * time.Millisecond + originalGossipSubOpportunisticGraftTicks := GossipSubOpportunisticGraftTicks + GossipSubOpportunisticGraftTicks = 2 + defer func() { + GossipSubPruneBackoff = originalGossipSubPruneBackoff + GossipSubGraftFloodThreshold = originalGossipSubGraftFloodThreshold + GossipSubOpportunisticGraftTicks = originalGossipSubOpportunisticGraftTicks + }() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 50) + // pubsubs for the first 10 hosts + psubs := getGossipsubs(ctx, hosts[:10], + WithFloodPublish(true), + WithPeerScore( + &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificWeight: 0, + DecayInterval: time.Second, + DecayToZero: 0.01, + Topics: map[string]*TopicScoreParams{ + "test": { + TopicWeight: 1, + TimeInMeshWeight: 0.0002777, + TimeInMeshQuantum: time.Second, + TimeInMeshCap: 3600, + FirstMessageDeliveriesWeight: 1, + FirstMessageDeliveriesDecay: 0.9997, + FirstMessageDeliveriesCap: 100, + InvalidMessageDeliveriesDecay: 0.99997, + }, + }, + }, + &PeerScoreThresholds{ + GossipThreshold: -10, + PublishThreshold: -100, + GraylistThreshold: -10000, + OpportunisticGraftThreshold: 1, + })) + + // connect the real hosts with degree 5 + connectSome(t, hosts[:10], 5) + + // sybil squatters for the remaining 40 hosts + for _, h := range hosts[10:] { + squatter := &sybilSquatter{h: h} + h.SetStreamHandler(GossipSubID_v10, squatter.handleStream) + } + + // connect all squatters to every real host + for _, squatter := range hosts[10:] { + for _, real := range hosts[:10] { + connect(t, squatter, real) + } + } + + // wait a bit for the connections to propagate events to the pubsubs + time.Sleep(time.Second) + + // ask the real pubsus to join the topic + for _, ps := range psubs { + sub, err := ps.Subscribe("test") + if err != nil { + t.Fatal(err) + } + // consume the messages + go func(sub *Subscription) { + for { + _, err := sub.Next(ctx) + if err != nil { + return + } + } + }(sub) + } + + // publish a bunch of messages from the real hosts + for i := 0; i < 1000; i++ { + msg := []byte(fmt.Sprintf("message %d", i)) + psubs[i%10].Publish("test", msg) + time.Sleep(20 * time.Millisecond) + } + + // now wait a few of oppgraft cycles + time.Sleep(7 * time.Second) + + // check the honest peer meshes, they should have at least 3 honest peers each + res := make(chan int, 1) + for _, ps := range psubs { + ps.eval <- func() { + gs := ps.rt.(*GossipSubRouter) + count := 0 + for _, h := range hosts[:10] { + _, ok := gs.mesh["test"][h.ID()] + if ok { + count++ + } + } + res <- count + } + + count := <-res + if count < 3 { + t.Fatalf("expected at least 3 honest peers, got %d", count) + } + } +} + +type sybilSquatter struct { + h Host +} + +func (sq *sybilSquatter) handleStream(s network.Stream) { + defer s.Close() + + os, err := sq.h.NewStream(context.Background(), s.Conn().RemotePeer(), GossipSubID_v10) + if err != nil { + panic(err) + } + + // send a subscription for test in the output stream to become candidate for GRAFT + // and then just read and ignore the incoming RPCs + r := protoio.NewDelimitedReader(s, 1<<20) + w := protoio.NewDelimitedWriter(os) + truth := true + topic := "test" + err = w.WriteMsg(&pb.RPC{Subscriptions: []*pb.RPC_SubOpts{{Subscribe: &truth, Topicid: &topic}}}) + if err != nil { + panic(err) + } + + var rpc pb.RPC + for { + rpc.Reset() + err = r.ReadMsg(&rpc) + if err != nil { + if err != io.EOF { + s.Reset() + } + return + } + } +} + +func TestGossipsubPeerScoreInspect(t *testing.T) { + // this test exercises the code path sof peer score inspection + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 2) + + inspector := &mockPeerScoreInspector{} + psub1 := getGossipsub(ctx, hosts[0], + WithPeerScore( + &PeerScoreParams{ + Topics: map[string]*TopicScoreParams{ + "test": { + TopicWeight: 1, + TimeInMeshQuantum: time.Second, + FirstMessageDeliveriesWeight: 1, + FirstMessageDeliveriesDecay: 0.999, + FirstMessageDeliveriesCap: 100, + InvalidMessageDeliveriesWeight: -1, + InvalidMessageDeliveriesDecay: 0.9999, + }, + }, + AppSpecificScore: func(peer.ID) float64 { return 0 }, + DecayInterval: time.Second, + DecayToZero: 0.01, + }, + &PeerScoreThresholds{ + GossipThreshold: -1, + PublishThreshold: -10, + GraylistThreshold: -1000, + }), + WithPeerScoreInspect(inspector.inspect, time.Second)) + psub2 := getGossipsub(ctx, hosts[1]) + psubs := []*PubSub{psub1, psub2} + + connect(t, hosts[0], hosts[1]) + + for _, ps := range psubs { + _, err := ps.Subscribe("test") + if err != nil { + t.Fatal(err) + } + } + + time.Sleep(time.Second) + + for i := 0; i < 20; i++ { + msg := []byte(fmt.Sprintf("message %d", i)) + psubs[i%2].Publish("test", msg) + time.Sleep(20 * time.Millisecond) + } + + time.Sleep(time.Second + 200*time.Millisecond) + + score2 := inspector.score(hosts[1].ID()) + if score2 < 9 { + t.Fatalf("expected score to be at least 9, instead got %f", score2) + } +} + +func TestGossipsubPeerScoreResetTopicParams(t *testing.T) { + // this test exercises the code path sof peer score inspection + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 1) + + ps := getGossipsub(ctx, hosts[0], + WithPeerScore( + &PeerScoreParams{ + Topics: map[string]*TopicScoreParams{ + "test": { + TopicWeight: 1, + TimeInMeshQuantum: time.Second, + FirstMessageDeliveriesWeight: 1, + FirstMessageDeliveriesDecay: 0.999, + FirstMessageDeliveriesCap: 100, + InvalidMessageDeliveriesWeight: -1, + InvalidMessageDeliveriesDecay: 0.9999, + }, + }, + AppSpecificScore: func(peer.ID) float64 { return 0 }, + DecayInterval: time.Second, + DecayToZero: 0.01, + }, + &PeerScoreThresholds{ + GossipThreshold: -1, + PublishThreshold: -10, + GraylistThreshold: -1000, + })) + + topic, err := ps.Join("test") + if err != nil { + t.Fatal(err) + } + + err = topic.SetScoreParams( + &TopicScoreParams{ + TopicWeight: 1, + TimeInMeshQuantum: time.Second, + FirstMessageDeliveriesWeight: 1, + FirstMessageDeliveriesDecay: 0.999, + FirstMessageDeliveriesCap: 200, + InvalidMessageDeliveriesWeight: -1, + InvalidMessageDeliveriesDecay: 0.9999, + }) + if err != nil { + t.Fatal(err) + } +} + +type mockPeerScoreInspector struct { + mx sync.Mutex + scores map[peer.ID]float64 +} + +func (ps *mockPeerScoreInspector) inspect(scores map[peer.ID]float64) { + ps.mx.Lock() + defer ps.mx.Unlock() + ps.scores = scores +} + +func (ps *mockPeerScoreInspector) score(p peer.ID) float64 { + ps.mx.Lock() + defer ps.mx.Unlock() + return ps.scores[p] +} + +func TestGossipsubRPCFragmentation(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 2) + ps := getGossipsub(ctx, hosts[0]) + + // make a fake peer that requests everything through IWANT gossip + iwe := iwantEverything{h: hosts[1]} + iwe.h.SetStreamHandler(GossipSubID_v10, iwe.handleStream) + + connect(t, hosts[0], hosts[1]) + + // have the real pubsub join the test topic + _, err := ps.Subscribe("test") + if err != nil { + t.Fatal(err) + } + + // wait for the real pubsub to connect and try to graft to the faker + time.Sleep(time.Second) + + // publish a bunch of fairly large messages from the real host + nMessages := 1000 + msgSize := 20000 + for i := 0; i < nMessages; i++ { + msg := make([]byte, msgSize) + rand.Read(msg) + ps.Publish("test", msg) + time.Sleep(20 * time.Millisecond) + } + + // wait a bit for them to be received via gossip by the fake peer + time.Sleep(5 * time.Second) + iwe.lk.Lock() + defer iwe.lk.Unlock() + + // we should have received all the messages + if iwe.msgsReceived != nMessages { + t.Fatalf("expected fake gossipsub peer to receive all messages, got %d / %d", iwe.msgsReceived, nMessages) + } + + // and we should have seen an IHAVE message for each of them + if iwe.ihavesReceived != nMessages { + t.Fatalf("expected to get IHAVEs for every message, got %d / %d", iwe.ihavesReceived, nMessages) + } + + // If everything were fragmented with maximum efficiency, we would expect to get + // (nMessages * msgSize) / ps.maxMessageSize total RPCs containing the messages we sent IWANTs for. + // The actual number will probably be larger, since there's some overhead for the RPC itself, and + // we probably aren't packing each RPC to it's maximum size + minExpectedRPCS := (nMessages * msgSize) / ps.maxMessageSize + if iwe.rpcsWithMessages < minExpectedRPCS { + t.Fatalf("expected to receive at least %d RPCs containing messages, got %d", minExpectedRPCS, iwe.rpcsWithMessages) + } +} + +// iwantEverything is a simple gossipsub client that never grafts onto a mesh, +// instead requesting everything through IWANT gossip messages. It is used to +// test that large responses to IWANT requests are fragmented into multiple RPCs. +type iwantEverything struct { + h Host + lk sync.Mutex + rpcsWithMessages int + msgsReceived int + ihavesReceived int +} + +func (iwe *iwantEverything) handleStream(s network.Stream) { + defer s.Close() + + os, err := iwe.h.NewStream(context.Background(), s.Conn().RemotePeer(), GossipSubID_v10) + if err != nil { + panic(err) + } + + msgIdsReceived := make(map[string]struct{}) + gossipMsgIdsReceived := make(map[string]struct{}) + + // send a subscription for test in the output stream to become candidate for gossip + r := protoio.NewDelimitedReader(s, 1<<20) + w := protoio.NewDelimitedWriter(os) + truth := true + topic := "test" + err = w.WriteMsg(&pb.RPC{Subscriptions: []*pb.RPC_SubOpts{{Subscribe: &truth, Topicid: &topic}}}) + if err != nil { + panic(err) + } + + var rpc pb.RPC + for { + rpc.Reset() + err = r.ReadMsg(&rpc) + if err != nil { + if err != io.EOF { + s.Reset() + } + return + } + + iwe.lk.Lock() + if len(rpc.Publish) != 0 { + iwe.rpcsWithMessages++ + } + // keep track of unique message ids received + for _, msg := range rpc.Publish { + id := string(msg.Seqno) + if _, seen := msgIdsReceived[id]; !seen { + iwe.msgsReceived++ + } + msgIdsReceived[id] = struct{}{} + } + + if rpc.Control != nil { + // send a PRUNE for all grafts, so we don't get direct message deliveries + var prunes []*pb.ControlPrune + for _, graft := range rpc.Control.Graft { + prunes = append(prunes, &pb.ControlPrune{TopicID: graft.TopicID}) + } + + var iwants []*pb.ControlIWant + for _, ihave := range rpc.Control.Ihave { + iwants = append(iwants, &pb.ControlIWant{MessageIDs: ihave.MessageIDs}) + for _, msgId := range ihave.MessageIDs { + if _, seen := gossipMsgIdsReceived[msgId]; !seen { + iwe.ihavesReceived++ + } + gossipMsgIdsReceived[msgId] = struct{}{} + } + } + + out := rpcWithControl(nil, nil, iwants, nil, prunes) + err = w.WriteMsg(out) + if err != nil { + panic(err) + } + } + iwe.lk.Unlock() + } +} + +func TestFragmentRPCFunction(t *testing.T) { + p := peer.ID("some-peer") + topic := "test" + rpc := &RPC{from: p} + limit := 1024 + + mkMsg := func(size int) *pb.Message { + msg := &pb.Message{} + msg.Data = make([]byte, size-4) // subtract the protobuf overhead, so msg.Size() returns requested size + rand.Read(msg.Data) + return msg + } + + ensureBelowLimit := func(rpcs []*RPC) { + for _, r := range rpcs { + if r.Size() > limit { + t.Fatalf("expected fragmented RPC to be below %d bytes, was %d", limit, r.Size()) + } + } + } + + // it should not fragment if everything fits in one RPC + rpc.Publish = []*pb.Message{} + rpc.Publish = []*pb.Message{mkMsg(10), mkMsg(10)} + results, err := fragmentRPC(rpc, limit) + if err != nil { + t.Fatal(err) + } + if len(results) != 1 { + t.Fatalf("expected single RPC if input is < limit, got %d", len(results)) + } + + // if there's a message larger than the limit, we should fail + rpc.Publish = []*pb.Message{mkMsg(10), mkMsg(limit * 2)} + results, err = fragmentRPC(rpc, limit) + if err == nil { + t.Fatalf("expected an error if a message exceeds limit, got %d RPCs instead", len(results)) + } + + // if the individual messages are below the limit, but the RPC as a whole is larger, we should fragment + nMessages := 100 + msgSize := 200 + truth := true + rpc.Subscriptions = []*pb.RPC_SubOpts{ + { + Subscribe: &truth, + Topicid: &topic, + }, + } + rpc.Publish = make([]*pb.Message, nMessages) + for i := 0; i < nMessages; i++ { + rpc.Publish[i] = mkMsg(msgSize) + } + results, err = fragmentRPC(rpc, limit) + if err != nil { + t.Fatal(err) + } + ensureBelowLimit(results) + msgsPerRPC := limit / msgSize + expectedRPCs := nMessages / msgsPerRPC + if len(results) != expectedRPCs { + t.Fatalf("expected %d RPC messages in output, got %d", expectedRPCs, len(results)) + } + var nMessagesFragmented int + var nSubscriptions int + for _, r := range results { + nMessagesFragmented += len(r.Publish) + nSubscriptions += len(r.Subscriptions) + } + if nMessagesFragmented != nMessages { + t.Fatalf("expected fragemented RPCs to contain same number of messages as input, got %d / %d", nMessagesFragmented, nMessages) + } + if nSubscriptions != 1 { + t.Fatal("expected subscription to be present in one of the fragmented messages, but not found") + } + + // if we're fragmenting, and the input RPC has control messages, + // the control messages should be in a separate RPC at the end + // reuse RPC from prev test, but add a control message + rpc.Control = &pb.ControlMessage{ + Graft: []*pb.ControlGraft{{TopicID: &topic}}, + Prune: []*pb.ControlPrune{{TopicID: &topic}}, + Ihave: []*pb.ControlIHave{{MessageIDs: []string{"foo"}}}, + Iwant: []*pb.ControlIWant{{MessageIDs: []string{"bar"}}}, + } + results, err = fragmentRPC(rpc, limit) + if err != nil { + t.Fatal(err) + } + ensureBelowLimit(results) + // we expect one more RPC than last time, with the final one containing the control messages + expectedCtrl := 1 + expectedRPCs = (nMessages / msgsPerRPC) + expectedCtrl + if len(results) != expectedRPCs { + t.Fatalf("expected %d RPC messages in output, got %d", expectedRPCs, len(results)) + } + ctl := results[len(results)-1].Control + if ctl == nil { + t.Fatal("expected final fragmented RPC to contain control messages, but .Control was nil") + } + // since it was not altered, the original control message should be identical to the output control message + originalBytes, err := rpc.Control.Marshal() + if err != nil { + t.Fatal(err) + } + receivedBytes, err := ctl.Marshal() + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(originalBytes, receivedBytes) { + t.Fatal("expected control message to be unaltered if it fits within one RPC message") + } + + // if the control message is too large to fit into a single RPC, it should be split into multiple RPCs + nTopics := 5 // pretend we're subscribed to multiple topics and sending IHAVE / IWANTs for each + messageIdSize := 32 + msgsPerTopic := 100 // enough that a single IHAVE or IWANT will exceed the limit + rpc.Control.Ihave = make([]*pb.ControlIHave, nTopics) + rpc.Control.Iwant = make([]*pb.ControlIWant, nTopics) + for i := 0; i < nTopics; i++ { + messageIds := make([]string, msgsPerTopic) + for m := 0; m < msgsPerTopic; m++ { + mid := make([]byte, messageIdSize) + rand.Read(mid) + messageIds[m] = string(mid) + } + rpc.Control.Ihave[i] = &pb.ControlIHave{MessageIDs: messageIds} + rpc.Control.Iwant[i] = &pb.ControlIWant{MessageIDs: messageIds} + } + results, err = fragmentRPC(rpc, limit) + if err != nil { + t.Fatal(err) + } + ensureBelowLimit(results) + minExpectedCtl := rpc.Control.Size() / limit + minExpectedRPCs := (nMessages / msgsPerRPC) + minExpectedCtl + if len(results) < minExpectedRPCs { + t.Fatalf("expected at least %d total RPCs (at least %d with control messages), got %d total", expectedRPCs, expectedCtrl, len(results)) + } + + // Test the pathological case where a single gossip message ID exceeds the limit. + // It should not be present in the fragmented messages, but smaller IDs should be + rpc.Reset() + giantIdBytes := make([]byte, limit*2) + rand.Read(giantIdBytes) + rpc.Control = &pb.ControlMessage{ + Iwant: []*pb.ControlIWant{ + {MessageIDs: []string{"hello", string(giantIdBytes)}}, + }, + } + results, err = fragmentRPC(rpc, limit) + if err != nil { + t.Fatal(err) + } + if len(results) != 1 { + t.Fatalf("expected 1 RPC, got %d", len(results)) + } + if len(results[0].Control.Iwant) != 1 { + t.Fatalf("expected 1 IWANT, got %d", len(results[0].Control.Iwant)) + } + if results[0].Control.Iwant[0].MessageIDs[0] != "hello" { + t.Fatalf("expected small message ID to be included unaltered, got %s instead", + results[0].Control.Iwant[0].MessageIDs[0]) + } +} diff --git a/p2p/pubsub/mcache_test.go b/p2p/pubsub/mcache_test.go new file mode 100644 index 0000000000..e36c6b19f9 --- /dev/null +++ b/p2p/pubsub/mcache_test.go @@ -0,0 +1,167 @@ +package pubsub + +import ( + "encoding/binary" + "fmt" + "testing" + + pb "github.com/libp2p/go-libp2p-pubsub/pb" +) + +func TestMessageCache(t *testing.T) { + mcache := NewMessageCache(3, 5) + msgID := DefaultMsgIdFn + + msgs := make([]*pb.Message, 60) + for i := range msgs { + msgs[i] = makeTestMessage(i) + } + + for i := 0; i < 10; i++ { + mcache.Put(msgs[i]) + } + + for i := 0; i < 10; i++ { + mid := msgID(msgs[i]) + m, ok := mcache.Get(mid) + if !ok { + t.Fatalf("Message %d not in cache", i) + } + + if m != msgs[i] { + t.Fatalf("Message %d does not match cache", i) + } + } + + gids := mcache.GetGossipIDs("test") + if len(gids) != 10 { + t.Fatalf("Expected 10 gossip IDs; got %d", len(gids)) + } + + for i := 0; i < 10; i++ { + mid := msgID(msgs[i]) + if mid != gids[i] { + t.Fatalf("GossipID mismatch for message %d", i) + } + } + + mcache.Shift() + for i := 10; i < 20; i++ { + mcache.Put(msgs[i]) + } + + for i := 0; i < 20; i++ { + mid := msgID(msgs[i]) + m, ok := mcache.Get(mid) + if !ok { + t.Fatalf("Message %d not in cache", i) + } + + if m != msgs[i] { + t.Fatalf("Message %d does not match cache", i) + } + } + + gids = mcache.GetGossipIDs("test") + if len(gids) != 20 { + t.Fatalf("Expected 20 gossip IDs; got %d", len(gids)) + } + + for i := 0; i < 10; i++ { + mid := msgID(msgs[i]) + if mid != gids[10+i] { + t.Fatalf("GossipID mismatch for message %d", i) + } + } + + for i := 10; i < 20; i++ { + mid := msgID(msgs[i]) + if mid != gids[i-10] { + t.Fatalf("GossipID mismatch for message %d", i) + } + } + + mcache.Shift() + for i := 20; i < 30; i++ { + mcache.Put(msgs[i]) + } + + mcache.Shift() + for i := 30; i < 40; i++ { + mcache.Put(msgs[i]) + } + + mcache.Shift() + for i := 40; i < 50; i++ { + mcache.Put(msgs[i]) + } + + mcache.Shift() + for i := 50; i < 60; i++ { + mcache.Put(msgs[i]) + } + + if len(mcache.msgs) != 50 { + t.Fatalf("Expected 50 messages in the cache; got %d", len(mcache.msgs)) + } + + for i := 0; i < 10; i++ { + mid := msgID(msgs[i]) + _, ok := mcache.Get(mid) + if ok { + t.Fatalf("Message %d still in cache", i) + } + } + + for i := 10; i < 60; i++ { + mid := msgID(msgs[i]) + m, ok := mcache.Get(mid) + if !ok { + t.Fatalf("Message %d not in cache", i) + } + + if m != msgs[i] { + t.Fatalf("Message %d does not match cache", i) + } + } + + gids = mcache.GetGossipIDs("test") + if len(gids) != 30 { + t.Fatalf("Expected 30 gossip IDs; got %d", len(gids)) + } + + for i := 0; i < 10; i++ { + mid := msgID(msgs[50+i]) + if mid != gids[i] { + t.Fatalf("GossipID mismatch for message %d", i) + } + } + + for i := 10; i < 20; i++ { + mid := msgID(msgs[30+i]) + if mid != gids[i] { + t.Fatalf("GossipID mismatch for message %d", i) + } + } + + for i := 20; i < 30; i++ { + mid := msgID(msgs[10+i]) + if mid != gids[i] { + t.Fatalf("GossipID mismatch for message %d", i) + } + } + +} + +func makeTestMessage(n int) *pb.Message { + seqno := make([]byte, 8) + binary.BigEndian.PutUint64(seqno, uint64(n)) + data := []byte(fmt.Sprintf("%d", n)) + topic := "test" + return &pb.Message{ + Data: data, + Topic: &topic, + From: []byte("test"), + Seqno: seqno, + } +} diff --git a/p2p/pubsub/peer_gater_test.go b/p2p/pubsub/peer_gater_test.go new file mode 100644 index 0000000000..9ab9d45918 --- /dev/null +++ b/p2p/pubsub/peer_gater_test.go @@ -0,0 +1,128 @@ +package pubsub + +import ( + "context" + "testing" + "time" + + "github.com/libp2p/go-libp2p-core/peer" +) + +func TestPeerGater(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + peerA := peer.ID("A") + peerAip := "1.2.3.4" + + params := NewPeerGaterParams(.1, .9, .999) + err := params.validate() + if err != nil { + t.Fatal(err) + } + + pg := newPeerGater(ctx, nil, params) + pg.getIP = func(p peer.ID) string { + switch p { + case peerA: + return peerAip + default: + return "" + } + } + + pg.AddPeer(peerA, "") + + status := pg.AcceptFrom(peerA) + if status != AcceptAll { + t.Fatal("expected AcceptAll") + } + + msg := &Message{ReceivedFrom: peerA} + + pg.ValidateMessage(msg) + status = pg.AcceptFrom(peerA) + if status != AcceptAll { + t.Fatal("expected AcceptAll") + } + + pg.RejectMessage(msg, RejectValidationQueueFull) + status = pg.AcceptFrom(peerA) + if status != AcceptAll { + t.Fatal("expected AcceptAll") + } + + pg.RejectMessage(msg, RejectValidationThrottled) + status = pg.AcceptFrom(peerA) + if status != AcceptAll { + t.Fatal("expected AcceptAll") + } + + for i := 0; i < 100; i++ { + pg.RejectMessage(msg, RejectValidationIgnored) + pg.RejectMessage(msg, RejectValidationFailed) + } + + accepted := false + for i := 0; !accepted && i < 1000; i++ { + status = pg.AcceptFrom(peerA) + if status == AcceptControl { + accepted = true + } + } + if !accepted { + t.Fatal("expected AcceptControl") + } + + for i := 0; i < 100; i++ { + pg.DeliverMessage(msg) + } + + accepted = false + for i := 0; !accepted && i < 1000; i++ { + status = pg.AcceptFrom(peerA) + if status == AcceptAll { + accepted = true + } + } + if !accepted { + t.Fatal("expected to accept at least once") + } + + for i := 0; i < 100; i++ { + pg.decayStats() + } + + status = pg.AcceptFrom(peerA) + if status != AcceptAll { + t.Fatal("expected AcceptAll") + } + + pg.RemovePeer(peerA) + pg.Lock() + _, ok := pg.peerStats[peerA] + pg.Unlock() + if ok { + t.Fatal("still have a stat record for peerA") + } + + pg.Lock() + _, ok = pg.ipStats[peerAip] + pg.Unlock() + if !ok { + t.Fatal("expected to still have a stat record for peerA's ip") + } + + pg.Lock() + pg.ipStats[peerAip].expire = time.Now() + pg.Unlock() + + time.Sleep(2 * time.Second) + + pg.Lock() + _, ok = pg.ipStats["1.2.3.4"] + pg.Unlock() + if ok { + t.Fatal("still have a stat record for peerA's ip") + } +} diff --git a/p2p/pubsub/pubsub_test.go b/p2p/pubsub/pubsub_test.go new file mode 100644 index 0000000000..534251d716 --- /dev/null +++ b/p2p/pubsub/pubsub_test.go @@ -0,0 +1,261 @@ +package pubsub + +import ( + "bytes" + "context" + crand "crypto/rand" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/libp2p/go-libp2p-core/connmgr" + "math/rand" + "sync" + "testing" + "time" +) + +type TestNetwork struct { + sync.RWMutex + + conns struct { + sync.RWMutex + m map[enode.ID][]Conn + } +} + +func (n *TestNetwork) ConnsToPeer(p enode.ID) []Conn { + return nil +} + +func (n *TestNetwork) Connectedness(enode.ID) Connectedness { + return NotConnected +} + +func (n *TestNetwork) Notify(Notifiee) { + +} + +func (n *TestNetwork) Peers() []enode.ID { + return nil +} + +type TestHost struct { + network *TestNetwork + Node *enode.Node + handlers map[ProtocolID]StreamHandler + handlerLock sync.Mutex +} + +func NewTestHost() *TestHost { + var p enode.ID + crand.Read(p[:]) + netw := &TestNetwork{} + netw.conns.m = make(map[enode.ID][]Conn) + h := &TestHost{ + network: netw, + Node: enode.SignNull(new(enr.Record), p), + handlers: make(map[ProtocolID]StreamHandler), + handlerLock: sync.Mutex{}, + } + return h +} + +func (h *TestHost) ID() *enode.Node { + return h.Node +} + +func (h *TestHost) Network() Network { + return h.network +} + +func (h *TestHost) Connect(ctx context.Context, pi enode.ID) error { + return nil +} + +func (h *TestHost) SetStreamHandler(pid ProtocolID, handler StreamHandler) { + h.handlerLock.Lock() + defer h.handlerLock.Unlock() + h.handlers[pid] = handler +} + +func (h *TestHost) SetStreamHandlerMatch(ProtocolID, func(string) bool, StreamHandler) { +} + +func (h *TestHost) RemoveStreamHandler(pid ProtocolID) { + h.handlerLock.Lock() + defer h.handlerLock.Unlock() + delete(h.handlers, pid) +} + +func (h *TestHost) NewStream(ctx context.Context, p enode.ID, pids ...ProtocolID) (Stream, error) { + return nil, nil +} + +func (h *TestHost) Close() error { + return nil +} + +func (h *TestHost) ConnManager() connmgr.ConnManager { + return nil +} + +func checkMessageRouting(t *testing.T, topic string, pubs []*PubSub, subs []*Subscription) { + data := make([]byte, 16) + rand.Read(data) + + for _, p := range pubs { + err := p.Publish(topic, data) + if err != nil { + t.Fatal(err) + } + + for _, s := range subs { + assertReceive(t, s, data) + } + } +} + +func getNetHosts(t *testing.T, ctx context.Context, n int) []Host { + var out []Host + + for i := 0; i < n; i++ { + out = append(out, NewTestHost()) + } + + return out +} + +func connect(t *testing.T, a, b Host) { + err := b.Connect(context.Background(), a.ID().ID()) + if err != nil { + t.Fatal(err) + } +} + +func sparseConnect(t *testing.T, hosts []Host) { + connectSome(t, hosts, 3) +} + +func denseConnect(t *testing.T, hosts []Host) { + connectSome(t, hosts, 10) +} + +func connectSome(t *testing.T, hosts []Host, d int) { + for i, a := range hosts { + for j := 0; j < d; j++ { + n := rand.Intn(len(hosts)) + if n == i { + j-- + continue + } + + b := hosts[n] + + connect(t, a, b) + } + } +} + +func connectAll(t *testing.T, hosts []Host) { + for i, a := range hosts { + for j, b := range hosts { + if i == j { + continue + } + + connect(t, a, b) + } + } +} + +func getPubsub(ctx context.Context, h Host, opts ...Option) *PubSub { + ps, err := NewGossipSub(ctx, h, opts...) + if err != nil { + panic(err) + } + return ps +} + +func getPubsubs(ctx context.Context, hs []Host, opts ...Option) []*PubSub { + var psubs []*PubSub + for _, h := range hs { + psubs = append(psubs, getPubsub(ctx, h, opts...)) + } + return psubs +} + +func assertReceive(t *testing.T, ch *Subscription, exp []byte) { + select { + case msg := <-ch.ch: + if !bytes.Equal(msg.GetData(), exp) { + t.Fatalf("got wrong message, expected %s but got %s", string(exp), string(msg.GetData())) + } + case <-time.After(time.Second * 5): + t.Logf("%#v\n", ch) + t.Fatal("timed out waiting for message of: ", string(exp)) + } +} + +func assertNeverReceives(t *testing.T, ch *Subscription, timeout time.Duration) { + select { + case msg := <-ch.ch: + t.Logf("%#v\n", ch) + t.Fatal("got unexpected message: ", string(msg.GetData())) + case <-time.After(timeout): + } +} + +func assertPeerLists(t *testing.T, hosts []Host, ps *PubSub, has ...int) { + peers := ps.ListPeers("") + set := make(map[enode.ID]struct{}) + for _, p := range peers { + set[p] = struct{}{} + } + + for _, h := range has { + if _, ok := set[hosts[h].ID().ID()]; !ok { + t.Fatal("expected to have connection to peer: ", h) + } + } +} + +// See https://github.com/libp2p/go-libp2p-pubsub/issues/426 +func TestPubSubRemovesBlacklistedPeer(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + + hosts := getNetHosts(t, ctx, 2) + + bl := NewMapBlacklist() + + psubs0 := getPubsub(ctx, hosts[0]) + psubs1 := getPubsub(ctx, hosts[1], WithBlacklist(bl)) + connect(t, hosts[0], hosts[1]) + + // Bad peer is blacklisted after it has connected. + // Calling p.BlacklistPeer directly does the right thing but we should also clean + // up the peer if it has been added the the blacklist by another means. + bl.Add(hosts[0].ID().ID()) + + _, err := psubs0.Subscribe("test") + if err != nil { + t.Fatal(err) + } + + sub1, err := psubs1.Subscribe("test") + if err != nil { + t.Fatal(err) + } + + time.Sleep(time.Millisecond * 100) + + psubs0.Publish("test", []byte("message")) + + wctx, cancel2 := context.WithTimeout(ctx, 1*time.Second) + defer cancel2() + + _, _ = sub1.Next(wctx) + + // Explicitly cancel context so PubSub cleans up peer channels. + // Issue 426 reports a panic due to a peer channel being closed twice. + cancel() + time.Sleep(time.Millisecond * 100) +} diff --git a/p2p/pubsub/score_params_test.go b/p2p/pubsub/score_params_test.go new file mode 100644 index 0000000000..4493703fec --- /dev/null +++ b/p2p/pubsub/score_params_test.go @@ -0,0 +1,328 @@ +package pubsub + +import ( + "math" + "testing" + "time" + + "github.com/libp2p/go-libp2p-core/peer" +) + +func TestPeerScoreThresholdsValidation(t *testing.T) { + if (&PeerScoreThresholds{GossipThreshold: 1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreThresholds{PublishThreshold: 1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreThresholds{GossipThreshold: -1, PublishThreshold: 0}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreThresholds{GossipThreshold: -1, PublishThreshold: -2, GraylistThreshold: 0}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreThresholds{AcceptPXThreshold: -1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreThresholds{OpportunisticGraftThreshold: -1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreThresholds{GossipThreshold: -1, PublishThreshold: -2, GraylistThreshold: -3, AcceptPXThreshold: 1, OpportunisticGraftThreshold: 2}).validate() != nil { + t.Fatal("expected validation success") + } + if (&PeerScoreThresholds{GossipThreshold: math.Inf(-1), PublishThreshold: -2, GraylistThreshold: -3, AcceptPXThreshold: 1, OpportunisticGraftThreshold: 2}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreThresholds{GossipThreshold: -1, PublishThreshold: math.Inf(-1), GraylistThreshold: -3, AcceptPXThreshold: 1, OpportunisticGraftThreshold: 2}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreThresholds{GossipThreshold: -1, PublishThreshold: -2, GraylistThreshold: math.Inf(-1), AcceptPXThreshold: 1, OpportunisticGraftThreshold: 2}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreThresholds{GossipThreshold: -1, PublishThreshold: -2, GraylistThreshold: -3, AcceptPXThreshold: math.NaN(), OpportunisticGraftThreshold: 2}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreThresholds{GossipThreshold: -1, PublishThreshold: -2, GraylistThreshold: -3, AcceptPXThreshold: 1, OpportunisticGraftThreshold: math.Inf(0)}).validate() == nil { + t.Fatal("expected validation error") + } +} + +func TestTopicScoreParamsValidation(t *testing.T) { + if (&TopicScoreParams{}).validate() == nil { + t.Fatal("expected validation error") + } + + if (&TopicScoreParams{TopicWeight: -1}).validate() == nil { + t.Fatal("expected validation error") + } + + if (&TopicScoreParams{TimeInMeshWeight: -1, TimeInMeshQuantum: time.Second}).validate() == nil { + t.Fatal("expected validation error") + } + if (&TopicScoreParams{TimeInMeshWeight: 1, TimeInMeshQuantum: -1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&TopicScoreParams{TimeInMeshWeight: 1, TimeInMeshQuantum: time.Second, TimeInMeshCap: -1}).validate() == nil { + t.Fatal("expected validation error") + } + + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, FirstMessageDeliveriesWeight: -1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, FirstMessageDeliveriesWeight: 1, FirstMessageDeliveriesDecay: -1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, FirstMessageDeliveriesWeight: 1, FirstMessageDeliveriesDecay: 2}).validate() == nil { + t.Fatal("expected validation error") + } + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, FirstMessageDeliveriesWeight: 1, FirstMessageDeliveriesDecay: .5, FirstMessageDeliveriesCap: -1}).validate() == nil { + t.Fatal("expected validation error") + } + + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: 1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: -1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: 2}).validate() == nil { + t.Fatal("expected validation error") + } + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: .5, MeshMessageDeliveriesCap: -1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: .5, MeshMessageDeliveriesCap: 5, MeshMessageDeliveriesThreshold: -3}).validate() == nil { + t.Fatal("expected validation error") + } + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: .5, MeshMessageDeliveriesCap: 5, MeshMessageDeliveriesThreshold: 3, MeshMessageDeliveriesWindow: -1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: .5, MeshMessageDeliveriesCap: 5, MeshMessageDeliveriesThreshold: 3, MeshMessageDeliveriesWindow: time.Millisecond, MeshMessageDeliveriesActivation: time.Millisecond}).validate() == nil { + t.Fatal("expected validation error") + } + + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshFailurePenaltyWeight: 1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshFailurePenaltyWeight: -1, MeshFailurePenaltyDecay: -1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshFailurePenaltyWeight: -1, MeshFailurePenaltyDecay: 2}).validate() == nil { + t.Fatal("expected validation error") + } + + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, InvalidMessageDeliveriesWeight: 1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, InvalidMessageDeliveriesWeight: -1, InvalidMessageDeliveriesDecay: -1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&TopicScoreParams{TimeInMeshQuantum: time.Second, InvalidMessageDeliveriesWeight: -1, InvalidMessageDeliveriesDecay: 2}).validate() == nil { + t.Fatal("expected validation error") + } + + // Don't use these params in production! + if (&TopicScoreParams{ + TopicWeight: 1, + TimeInMeshWeight: 0.01, + TimeInMeshQuantum: time.Second, + TimeInMeshCap: 10, + FirstMessageDeliveriesWeight: 1, + FirstMessageDeliveriesDecay: 0.5, + FirstMessageDeliveriesCap: 10, + MeshMessageDeliveriesWeight: -1, + MeshMessageDeliveriesDecay: 0.5, + MeshMessageDeliveriesCap: 10, + MeshMessageDeliveriesThreshold: 5, + MeshMessageDeliveriesWindow: time.Millisecond, + MeshMessageDeliveriesActivation: time.Second, + MeshFailurePenaltyWeight: -1, + MeshFailurePenaltyDecay: 0.5, + InvalidMessageDeliveriesWeight: -1, + InvalidMessageDeliveriesDecay: 0.5, + }).validate() != nil { + t.Fatal("expected validation success") + } +} + +func TestPeerScoreParamsValidation(t *testing.T) { + appScore := func(peer.ID) float64 { return 0 } + + if (&PeerScoreParams{TopicScoreCap: -1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreParams{TopicScoreCap: 1, DecayInterval: time.Second, DecayToZero: 0.01}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreParams{TopicScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, IPColocationFactorWeight: 1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreParams{TopicScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, IPColocationFactorWeight: -1, IPColocationFactorThreshold: -1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreParams{TopicScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Millisecond, DecayToZero: 0.01, IPColocationFactorWeight: -1, IPColocationFactorThreshold: 1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreParams{TopicScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: -1, IPColocationFactorWeight: -1, IPColocationFactorThreshold: 1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreParams{TopicScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 2, IPColocationFactorWeight: -1, IPColocationFactorThreshold: 1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreParams{AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, BehaviourPenaltyWeight: 1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreParams{AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, BehaviourPenaltyWeight: -1}).validate() == nil { + t.Fatal("expected validation error") + } + if (&PeerScoreParams{AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, BehaviourPenaltyWeight: -1, BehaviourPenaltyDecay: 2}).validate() == nil { + t.Fatal("expected validation error") + } + + // don't use these params in production! + if (&PeerScoreParams{ + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: 0.01, + IPColocationFactorWeight: -1, + IPColocationFactorThreshold: 1, + BehaviourPenaltyWeight: -1, + BehaviourPenaltyDecay: 0.999, + }).validate() != nil { + t.Fatal("expected validation success") + } + + if (&PeerScoreParams{ + TopicScoreCap: 1, + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: 0.01, + IPColocationFactorWeight: -1, + IPColocationFactorThreshold: 1, + BehaviourPenaltyWeight: -1, + BehaviourPenaltyDecay: 0.999, + }).validate() != nil { + t.Fatal("expected validation success") + } + + if (&PeerScoreParams{ + TopicScoreCap: 1, + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: 0.01, + IPColocationFactorWeight: -1, + IPColocationFactorThreshold: 1, + Topics: map[string]*TopicScoreParams{ + "test": &TopicScoreParams{ + TopicWeight: 1, + TimeInMeshWeight: 0.01, + TimeInMeshQuantum: time.Second, + TimeInMeshCap: 10, + FirstMessageDeliveriesWeight: 1, + FirstMessageDeliveriesDecay: 0.5, + FirstMessageDeliveriesCap: 10, + MeshMessageDeliveriesWeight: -1, + MeshMessageDeliveriesDecay: 0.5, + MeshMessageDeliveriesCap: 10, + MeshMessageDeliveriesThreshold: 5, + MeshMessageDeliveriesWindow: time.Millisecond, + MeshMessageDeliveriesActivation: time.Second, + MeshFailurePenaltyWeight: -1, + MeshFailurePenaltyDecay: 0.5, + InvalidMessageDeliveriesWeight: -1, + InvalidMessageDeliveriesDecay: 0.5, + }, + }, + }).validate() != nil { + t.Fatal("expected validation success") + } + + // don't use these params in production! + if (&PeerScoreParams{ + TopicScoreCap: 1, + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: 0.01, + IPColocationFactorWeight: -1, + IPColocationFactorThreshold: 1, + Topics: map[string]*TopicScoreParams{ + "test": &TopicScoreParams{ + TopicWeight: -1, + TimeInMeshWeight: 0.01, + TimeInMeshQuantum: time.Second, + TimeInMeshCap: 10, + FirstMessageDeliveriesWeight: 1, + FirstMessageDeliveriesDecay: 0.5, + FirstMessageDeliveriesCap: 10, + MeshMessageDeliveriesWeight: -1, + MeshMessageDeliveriesDecay: 0.5, + MeshMessageDeliveriesCap: 10, + MeshMessageDeliveriesThreshold: 5, + MeshMessageDeliveriesWindow: time.Millisecond, + MeshMessageDeliveriesActivation: time.Second, + MeshFailurePenaltyWeight: -1, + MeshFailurePenaltyDecay: 0.5, + InvalidMessageDeliveriesWeight: -1, + InvalidMessageDeliveriesDecay: 0.5, + }, + }, + }).validate() == nil { + t.Fatal("expected validation failure") + } + + // Checks the topic parameters for invalid values such as infinite and + // NaN numbers. + + // Don't use these params in production! + if (&PeerScoreParams{ + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: math.Inf(0), + IPColocationFactorWeight: math.Inf(-1), + IPColocationFactorThreshold: 1, + BehaviourPenaltyWeight: math.Inf(0), + BehaviourPenaltyDecay: math.NaN(), + }).validate() == nil { + t.Fatal("expected validation failure") + } + + if (&PeerScoreParams{ + TopicScoreCap: 1, + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: 0.01, + IPColocationFactorWeight: -1, + IPColocationFactorThreshold: 1, + Topics: map[string]*TopicScoreParams{ + "test": &TopicScoreParams{ + TopicWeight: math.Inf(0), + TimeInMeshWeight: math.NaN(), + TimeInMeshQuantum: time.Second, + TimeInMeshCap: 10, + FirstMessageDeliveriesWeight: math.Inf(1), + FirstMessageDeliveriesDecay: 0.5, + FirstMessageDeliveriesCap: 10, + MeshMessageDeliveriesWeight: math.Inf(-1), + MeshMessageDeliveriesDecay: math.NaN(), + MeshMessageDeliveriesCap: math.Inf(0), + MeshMessageDeliveriesThreshold: 5, + MeshMessageDeliveriesWindow: time.Millisecond, + MeshMessageDeliveriesActivation: time.Second, + MeshFailurePenaltyWeight: -1, + MeshFailurePenaltyDecay: math.NaN(), + InvalidMessageDeliveriesWeight: math.Inf(0), + InvalidMessageDeliveriesDecay: math.NaN(), + }, + }, + }).validate() == nil { + t.Fatal("expected validation failure") + } + +} + +func TestScoreParameterDecay(t *testing.T) { + decay1hr := ScoreParameterDecay(time.Hour) + if decay1hr != .9987216039048303 { + t.Fatalf("expected .9987216039048303, got %f", decay1hr) + } +} diff --git a/p2p/pubsub/score_test.go b/p2p/pubsub/score_test.go new file mode 100644 index 0000000000..849f8be555 --- /dev/null +++ b/p2p/pubsub/score_test.go @@ -0,0 +1,1080 @@ +package pubsub + +import ( + "math" + "net" + "sync" + "testing" + "time" + + "github.com/libp2p/go-libp2p-core/peer" +) + +func TestScoreTimeInMesh(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + Topics: make(map[string]*TopicScoreParams), + } + topicScoreParams := &TopicScoreParams{ + TopicWeight: 0.5, + TimeInMeshWeight: 1, + TimeInMeshQuantum: time.Millisecond, + TimeInMeshCap: 3600, + } + params.Topics[mytopic] = topicScoreParams + + peerA := peer.ID("A") + + // Peer score should start at 0 + ps := newPeerScore(params) + ps.AddPeer(peerA, "myproto") + + aScore := ps.Score(peerA) + if aScore != 0 { + t.Fatal("expected score to start at zero") + } + + // The time in mesh depends on how long the peer has been grafted + ps.Graft(peerA, mytopic) + elapsed := topicScoreParams.TimeInMeshQuantum * 200 + time.Sleep(elapsed) + + ps.refreshScores() + aScore = ps.Score(peerA) + expected := topicScoreParams.TopicWeight * topicScoreParams.TimeInMeshWeight * float64(elapsed/topicScoreParams.TimeInMeshQuantum) + if aScore < expected { + t.Fatalf("Score: %f. Expected >= %f", aScore, expected) + } +} + +func TestScoreTimeInMeshCap(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + Topics: make(map[string]*TopicScoreParams), + } + topicScoreParams := &TopicScoreParams{ + TopicWeight: 0.5, + TimeInMeshWeight: 1, + TimeInMeshQuantum: time.Millisecond, + TimeInMeshCap: 10, + } + + params.Topics[mytopic] = topicScoreParams + + peerA := peer.ID("A") + + ps := newPeerScore(params) + ps.AddPeer(peerA, "myproto") + ps.Graft(peerA, mytopic) + elapsed := topicScoreParams.TimeInMeshQuantum * 40 + time.Sleep(elapsed) + + // The time in mesh score has a cap + ps.refreshScores() + aScore := ps.Score(peerA) + expected := topicScoreParams.TopicWeight * topicScoreParams.TimeInMeshWeight * topicScoreParams.TimeInMeshCap + variance := 0.5 + if !withinVariance(aScore, expected, variance) { + t.Fatalf("Score: %f. Expected %f ± %f", aScore, expected, variance*expected) + } +} + +func TestScoreFirstMessageDeliveries(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + Topics: make(map[string]*TopicScoreParams), + } + topicScoreParams := &TopicScoreParams{ + TopicWeight: 1, + FirstMessageDeliveriesWeight: 1, + FirstMessageDeliveriesDecay: 1.0, // test without decay for now + FirstMessageDeliveriesCap: 2000, + TimeInMeshQuantum: time.Second, + } + + params.Topics[mytopic] = topicScoreParams + peerA := peer.ID("A") + + ps := newPeerScore(params) + ps.AddPeer(peerA, "myproto") + ps.Graft(peerA, mytopic) + + // deliver a bunch of messages from peer A + nMessages := 100 + for i := 0; i < nMessages; i++ { + pbMsg := makeTestMessage(i) + pbMsg.Topic = &mytopic + msg := Message{ReceivedFrom: peerA, Message: pbMsg} + ps.ValidateMessage(&msg) + ps.DeliverMessage(&msg) + } + + ps.refreshScores() + aScore := ps.Score(peerA) + expected := topicScoreParams.TopicWeight * topicScoreParams.FirstMessageDeliveriesWeight * float64(nMessages) + if aScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } +} + +func TestScoreFirstMessageDeliveriesCap(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + Topics: make(map[string]*TopicScoreParams), + } + topicScoreParams := &TopicScoreParams{ + TopicWeight: 1, + FirstMessageDeliveriesWeight: 1, + FirstMessageDeliveriesDecay: 1.0, // test without decay for now + FirstMessageDeliveriesCap: 50, + TimeInMeshQuantum: time.Second, + } + + params.Topics[mytopic] = topicScoreParams + peerA := peer.ID("A") + + ps := newPeerScore(params) + ps.AddPeer(peerA, "myproto") + ps.Graft(peerA, mytopic) + + // deliver a bunch of messages from peer A + nMessages := 100 + for i := 0; i < nMessages; i++ { + pbMsg := makeTestMessage(i) + pbMsg.Topic = &mytopic + msg := Message{ReceivedFrom: peerA, Message: pbMsg} + ps.ValidateMessage(&msg) + ps.DeliverMessage(&msg) + } + + ps.refreshScores() + aScore := ps.Score(peerA) + expected := topicScoreParams.TopicWeight * topicScoreParams.FirstMessageDeliveriesWeight * topicScoreParams.FirstMessageDeliveriesCap + if aScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } +} + +func TestScoreFirstMessageDeliveriesDecay(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + Topics: make(map[string]*TopicScoreParams), + } + topicScoreParams := &TopicScoreParams{ + TopicWeight: 1, + FirstMessageDeliveriesWeight: 1, + FirstMessageDeliveriesDecay: 0.9, // decay 10% per decay interval + FirstMessageDeliveriesCap: 2000, + TimeInMeshQuantum: time.Second, + } + + params.Topics[mytopic] = topicScoreParams + peerA := peer.ID("A") + + ps := newPeerScore(params) + ps.AddPeer(peerA, "myproto") + ps.Graft(peerA, mytopic) + + // deliver a bunch of messages from peer A + nMessages := 100 + for i := 0; i < nMessages; i++ { + pbMsg := makeTestMessage(i) + pbMsg.Topic = &mytopic + msg := Message{ReceivedFrom: peerA, Message: pbMsg} + ps.ValidateMessage(&msg) + ps.DeliverMessage(&msg) + } + + ps.refreshScores() + aScore := ps.Score(peerA) + expected := topicScoreParams.TopicWeight * topicScoreParams.FirstMessageDeliveriesWeight * topicScoreParams.FirstMessageDeliveriesDecay * float64(nMessages) + if aScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } + + // refreshing the scores applies the decay param + decayIntervals := 10 + for i := 0; i < decayIntervals; i++ { + ps.refreshScores() + expected *= topicScoreParams.FirstMessageDeliveriesDecay + } + aScore = ps.Score(peerA) + if aScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } +} + +func TestScoreMeshMessageDeliveries(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + Topics: make(map[string]*TopicScoreParams), + } + topicScoreParams := &TopicScoreParams{ + TopicWeight: 1, + MeshMessageDeliveriesWeight: -1, + MeshMessageDeliveriesActivation: time.Second, + MeshMessageDeliveriesWindow: 10 * time.Millisecond, + MeshMessageDeliveriesThreshold: 20, + MeshMessageDeliveriesCap: 100, + MeshMessageDeliveriesDecay: 1.0, // no decay for this test + + FirstMessageDeliveriesWeight: 0, + TimeInMeshQuantum: time.Second, + } + + params.Topics[mytopic] = topicScoreParams + + // peer A always delivers the message first. + // peer B delivers next (within the delivery window). + // peer C delivers outside the delivery window. + // we expect peers A and B to have a score of zero, since all other parameter weights are zero. + // Peer C should have a negative score. + peerA := peer.ID("A") + peerB := peer.ID("B") + peerC := peer.ID("C") + peers := []peer.ID{peerA, peerB, peerC} + + ps := newPeerScore(params) + for _, p := range peers { + ps.AddPeer(p, "myproto") + ps.Graft(p, mytopic) + } + + // assert that nobody has been penalized yet for not delivering messages before activation time + ps.refreshScores() + for _, p := range peers { + score := ps.Score(p) + if score < 0 { + t.Fatalf("expected no mesh delivery penalty before activation time, got score %f", score) + } + } + // wait for the activation time to kick in + time.Sleep(topicScoreParams.MeshMessageDeliveriesActivation) + + // deliver a bunch of messages from peer A, with duplicates within the window from peer B, + // and duplicates outside the window from peer C. + nMessages := 100 + wg := sync.WaitGroup{} + for i := 0; i < nMessages; i++ { + pbMsg := makeTestMessage(i) + pbMsg.Topic = &mytopic + msg := Message{ReceivedFrom: peerA, Message: pbMsg} + ps.ValidateMessage(&msg) + ps.DeliverMessage(&msg) + + msg.ReceivedFrom = peerB + ps.DuplicateMessage(&msg) + + // deliver duplicate from peerC after the window + wg.Add(1) + time.AfterFunc(topicScoreParams.MeshMessageDeliveriesWindow+(20*time.Millisecond), func() { + msg.ReceivedFrom = peerC + ps.DuplicateMessage(&msg) + wg.Done() + }) + } + wg.Wait() + + ps.refreshScores() + aScore := ps.Score(peerA) + bScore := ps.Score(peerB) + cScore := ps.Score(peerC) + if aScore < 0 { + t.Fatalf("Expected non-negative score for peer A, got %f", aScore) + } + if bScore < 0 { + t.Fatalf("Expected non-negative score for peer B, got %f", aScore) + } + + // the penalty is the difference between the threshold and the actual mesh deliveries, squared. + // since we didn't deliver anything, this is just the value of the threshold + penalty := topicScoreParams.MeshMessageDeliveriesThreshold * topicScoreParams.MeshMessageDeliveriesThreshold + expected := topicScoreParams.TopicWeight * topicScoreParams.MeshMessageDeliveriesWeight * penalty + if cScore != expected { + t.Fatalf("Score: %f. Expected %f", cScore, expected) + } +} + +func TestScoreMeshMessageDeliveriesDecay(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + Topics: make(map[string]*TopicScoreParams), + } + topicScoreParams := &TopicScoreParams{ + TopicWeight: 1, + MeshMessageDeliveriesWeight: -1, + MeshMessageDeliveriesActivation: 0, + MeshMessageDeliveriesWindow: 10 * time.Millisecond, + MeshMessageDeliveriesThreshold: 20, + MeshMessageDeliveriesCap: 100, + MeshMessageDeliveriesDecay: 0.9, + + FirstMessageDeliveriesWeight: 0, + TimeInMeshQuantum: time.Second, + } + + params.Topics[mytopic] = topicScoreParams + + peerA := peer.ID("A") + + ps := newPeerScore(params) + ps.AddPeer(peerA, "myproto") + ps.Graft(peerA, mytopic) + + // deliver messages from peer A + nMessages := 40 + for i := 0; i < nMessages; i++ { + pbMsg := makeTestMessage(i) + pbMsg.Topic = &mytopic + msg := Message{ReceivedFrom: peerA, Message: pbMsg} + ps.ValidateMessage(&msg) + ps.DeliverMessage(&msg) + } + + // we should have a positive score, since we delivered more messages than the threshold + ps.refreshScores() + aScore := ps.Score(peerA) + if aScore < 0 { + t.Fatalf("Expected non-negative score for peer A, got %f", aScore) + } + + // we need to refresh enough times for the decay to bring us below the threshold + decayedDeliveryCount := float64(nMessages) * topicScoreParams.MeshMessageDeliveriesDecay + for i := 0; i < 20; i++ { + ps.refreshScores() + decayedDeliveryCount *= topicScoreParams.MeshMessageDeliveriesDecay + } + aScore = ps.Score(peerA) + // the penalty is the difference between the threshold and the (decayed) mesh deliveries, squared. + deficit := topicScoreParams.MeshMessageDeliveriesThreshold - decayedDeliveryCount + penalty := deficit * deficit + expected := topicScoreParams.TopicWeight * topicScoreParams.MeshMessageDeliveriesWeight * penalty + if aScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } +} + +func TestScoreMeshFailurePenalty(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + Topics: make(map[string]*TopicScoreParams), + } + + // the mesh failure penalty is applied when a peer is pruned while their + // mesh deliveries are under the threshold. + // for this test, we set the mesh delivery threshold, but set + // MeshMessageDeliveriesWeight to zero, so the only affect on the score + // is from the mesh failure penalty + topicScoreParams := &TopicScoreParams{ + TopicWeight: 1, + MeshFailurePenaltyWeight: -1, + MeshFailurePenaltyDecay: 1.0, + + MeshMessageDeliveriesActivation: 0, + MeshMessageDeliveriesWindow: 10 * time.Millisecond, + MeshMessageDeliveriesThreshold: 20, + MeshMessageDeliveriesCap: 100, + MeshMessageDeliveriesDecay: 1.0, + + MeshMessageDeliveriesWeight: 0, + FirstMessageDeliveriesWeight: 0, + TimeInMeshQuantum: time.Second, + } + + params.Topics[mytopic] = topicScoreParams + + peerA := peer.ID("A") + peerB := peer.ID("B") + peers := []peer.ID{peerA, peerB} + + ps := newPeerScore(params) + for _, p := range peers { + ps.AddPeer(p, "myproto") + ps.Graft(p, mytopic) + } + + // deliver messages from peer A. peer B does nothing + nMessages := 100 + for i := 0; i < nMessages; i++ { + pbMsg := makeTestMessage(i) + pbMsg.Topic = &mytopic + msg := Message{ReceivedFrom: peerA, Message: pbMsg} + ps.ValidateMessage(&msg) + ps.DeliverMessage(&msg) + } + + // peers A and B should both have zero scores, since the failure penalty hasn't been applied yet + ps.refreshScores() + aScore := ps.Score(peerA) + bScore := ps.Score(peerB) + if aScore != 0 { + t.Errorf("expected peer A to have score 0.0, got %f", aScore) + } + if bScore != 0 { + t.Errorf("expected peer B to have score 0.0, got %f", bScore) + } + + // prune peer B to apply the penalty + ps.Prune(peerB, mytopic) + ps.refreshScores() + aScore = ps.Score(peerA) + bScore = ps.Score(peerB) + + if aScore != 0 { + t.Errorf("expected peer A to have score 0.0, got %f", aScore) + } + + // penalty calculation is the same as for MeshMessageDeliveries, but multiplied by MeshFailurePenaltyWeight + // instead of MeshMessageDeliveriesWeight + penalty := topicScoreParams.MeshMessageDeliveriesThreshold * topicScoreParams.MeshMessageDeliveriesThreshold + expected := topicScoreParams.TopicWeight * topicScoreParams.MeshFailurePenaltyWeight * penalty + if bScore != expected { + t.Fatalf("Score: %f. Expected %f", bScore, expected) + } +} + +func TestScoreInvalidMessageDeliveries(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + Topics: make(map[string]*TopicScoreParams), + } + topicScoreParams := &TopicScoreParams{ + TopicWeight: 1, + TimeInMeshQuantum: time.Second, + InvalidMessageDeliveriesWeight: -1, + InvalidMessageDeliveriesDecay: 1.0, + } + params.Topics[mytopic] = topicScoreParams + + peerA := peer.ID("A") + + ps := newPeerScore(params) + ps.AddPeer(peerA, "myproto") + ps.Graft(peerA, mytopic) + + nMessages := 100 + for i := 0; i < nMessages; i++ { + pbMsg := makeTestMessage(i) + pbMsg.Topic = &mytopic + msg := Message{ReceivedFrom: peerA, Message: pbMsg} + ps.RejectMessage(&msg, RejectInvalidSignature) + } + + ps.refreshScores() + aScore := ps.Score(peerA) + expected := topicScoreParams.TopicWeight * topicScoreParams.InvalidMessageDeliveriesWeight * float64(nMessages*nMessages) + if aScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } +} + +func TestScoreInvalidMessageDeliveriesDecay(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + Topics: make(map[string]*TopicScoreParams), + } + topicScoreParams := &TopicScoreParams{ + TopicWeight: 1, + TimeInMeshQuantum: time.Second, + InvalidMessageDeliveriesWeight: -1, + InvalidMessageDeliveriesDecay: 0.9, + } + params.Topics[mytopic] = topicScoreParams + + peerA := peer.ID("A") + + ps := newPeerScore(params) + ps.AddPeer(peerA, "myproto") + ps.Graft(peerA, mytopic) + + nMessages := 100 + for i := 0; i < nMessages; i++ { + pbMsg := makeTestMessage(i) + pbMsg.Topic = &mytopic + msg := Message{ReceivedFrom: peerA, Message: pbMsg} + ps.RejectMessage(&msg, RejectInvalidSignature) + } + + ps.refreshScores() + aScore := ps.Score(peerA) + expected := topicScoreParams.TopicWeight * topicScoreParams.InvalidMessageDeliveriesWeight * math.Pow(topicScoreParams.InvalidMessageDeliveriesDecay*float64(nMessages), 2) + if aScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } + + // refresh scores a few times to apply decay + for i := 0; i < 10; i++ { + ps.refreshScores() + expected *= math.Pow(topicScoreParams.InvalidMessageDeliveriesDecay, 2) + } + aScore = ps.Score(peerA) + if aScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } +} + +func TestScoreRejectMessageDeliveries(t *testing.T) { + // this tests adds coverage for the dark corners of rejection tracing + mytopic := "mytopic" + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + Topics: make(map[string]*TopicScoreParams), + } + topicScoreParams := &TopicScoreParams{ + TopicWeight: 1, + TimeInMeshQuantum: time.Second, + InvalidMessageDeliveriesWeight: -1, + InvalidMessageDeliveriesDecay: 1.0, + } + params.Topics[mytopic] = topicScoreParams + + peerA := peer.ID("A") + peerB := peer.ID("B") + + ps := newPeerScore(params) + ps.AddPeer(peerA, "myproto") + ps.AddPeer(peerB, "myproto") + + pbMsg := makeTestMessage(0) + pbMsg.Topic = &mytopic + msg := Message{ReceivedFrom: peerA, Message: pbMsg} + msg2 := Message{ReceivedFrom: peerB, Message: pbMsg} + + // these should have no effect in the score + ps.RejectMessage(&msg, RejectBlacklstedPeer) + ps.RejectMessage(&msg, RejectBlacklistedSource) + ps.RejectMessage(&msg, RejectValidationQueueFull) + + aScore := ps.Score(peerA) + expected := 0.0 + if aScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } + + // insert a record in the message deliveries + ps.ValidateMessage(&msg) + + // this should have no effect in the score, and subsequent duplicate messages should have no + // effect either + ps.RejectMessage(&msg, RejectValidationThrottled) + ps.DuplicateMessage(&msg2) + + aScore = ps.Score(peerA) + expected = 0.0 + if aScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } + + bScore := ps.Score(peerB) + expected = 0.0 + if bScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } + + // now clear the delivery record + ps.deliveries.head.expire = time.Now() + time.Sleep(1 * time.Millisecond) + ps.deliveries.gc() + + // insert a record in the message deliveries + ps.ValidateMessage(&msg) + + // this should have no effect in the score, and subsequent duplicate messages should have no + // effect either + ps.RejectMessage(&msg, RejectValidationIgnored) + ps.DuplicateMessage(&msg2) + + aScore = ps.Score(peerA) + expected = 0.0 + if aScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } + + bScore = ps.Score(peerB) + expected = 0.0 + if bScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } + + // now clear the delivery record + ps.deliveries.head.expire = time.Now() + time.Sleep(1 * time.Millisecond) + ps.deliveries.gc() + + // insert a new record in the message deliveries + ps.ValidateMessage(&msg) + + // and reject the message to make sure duplicates are also penalized + ps.RejectMessage(&msg, RejectValidationFailed) + ps.DuplicateMessage(&msg2) + + aScore = ps.Score(peerA) + expected = -1.0 + if aScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } + + bScore = ps.Score(peerB) + expected = -1.0 + if bScore != expected { + t.Fatalf("Score: %f. Expected %f", bScore, expected) + } + + // now clear the delivery record again + ps.deliveries.head.expire = time.Now() + time.Sleep(1 * time.Millisecond) + ps.deliveries.gc() + + // insert a new record in the message deliveries + ps.ValidateMessage(&msg) + + // and reject the message after a duplciate has arrived + ps.DuplicateMessage(&msg2) + ps.RejectMessage(&msg, RejectValidationFailed) + + aScore = ps.Score(peerA) + expected = -4.0 + if aScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } + + bScore = ps.Score(peerB) + expected = -4.0 + if bScore != expected { + t.Fatalf("Score: %f. Expected %f", bScore, expected) + } +} + +func TestScoreApplicationScore(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + + var appScoreValue float64 + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return appScoreValue }, + AppSpecificWeight: 0.5, + Topics: make(map[string]*TopicScoreParams), + } + + peerA := peer.ID("A") + + ps := newPeerScore(params) + ps.AddPeer(peerA, "myproto") + ps.Graft(peerA, mytopic) + + for i := -100; i < 100; i++ { + appScoreValue = float64(i) + ps.refreshScores() + aScore := ps.Score(peerA) + expected := float64(i) * params.AppSpecificWeight + if aScore != expected { + t.Errorf("expected peer score to equal app-specific score %f, got %f", expected, aScore) + } + } +} + +func TestScoreIPColocation(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + IPColocationFactorThreshold: 1, + IPColocationFactorWeight: -1, + Topics: make(map[string]*TopicScoreParams), + } + + peerA := peer.ID("A") + peerB := peer.ID("B") + peerC := peer.ID("C") + peerD := peer.ID("D") + peers := []peer.ID{peerA, peerB, peerC, peerD} + + ps := newPeerScore(params) + for _, p := range peers { + ps.AddPeer(p, "myproto") + ps.Graft(p, mytopic) + } + + // peerA should have no penalty, but B, C, and D should be penalized for sharing an IP + setIPsForPeer(t, ps, peerA, "1.2.3.4") + setIPsForPeer(t, ps, peerB, "2.3.4.5") + setIPsForPeer(t, ps, peerC, "2.3.4.5", "3.4.5.6") + setIPsForPeer(t, ps, peerD, "2.3.4.5") + + ps.refreshScores() + aScore := ps.Score(peerA) + bScore := ps.Score(peerB) + cScore := ps.Score(peerC) + dScore := ps.Score(peerD) + + if aScore != 0 { + t.Errorf("expected peer A to have score 0.0, got %f", aScore) + } + + nShared := 3 + ipSurplus := nShared - params.IPColocationFactorThreshold + penalty := ipSurplus * ipSurplus + expected := params.IPColocationFactorWeight * float64(penalty) + for _, score := range []float64{bScore, cScore, dScore} { + if score != expected { + t.Fatalf("Score: %f. Expected %f", score, expected) + } + } +} + +func TestScoreIPColocationWhitelist(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + + _, ipNet, err := net.ParseCIDR("2.3.0.0/16") + if err != nil { + t.Fatal(err) + } + + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + IPColocationFactorThreshold: 1, + IPColocationFactorWeight: -1, + IPColocationFactorWhitelist: []*net.IPNet{ipNet}, + Topics: make(map[string]*TopicScoreParams), + } + + peerA := peer.ID("A") + peerB := peer.ID("B") + peerC := peer.ID("C") + peerD := peer.ID("D") + peers := []peer.ID{peerA, peerB, peerC, peerD} + + ps := newPeerScore(params) + for _, p := range peers { + ps.AddPeer(p, "myproto") + ps.Graft(p, mytopic) + } + + // peerA should have no penalty, but B, C, and D should be penalized for sharing an IP + setIPsForPeer(t, ps, peerA, "1.2.3.4") + setIPsForPeer(t, ps, peerB, "2.3.4.5") + setIPsForPeer(t, ps, peerC, "2.3.4.5", "3.4.5.6") + setIPsForPeer(t, ps, peerD, "2.3.4.5") + + ps.refreshScores() + aScore := ps.Score(peerA) + bScore := ps.Score(peerB) + cScore := ps.Score(peerC) + dScore := ps.Score(peerD) + + if aScore != 0 { + t.Errorf("expected peer A to have score 0.0, got %f", aScore) + } + + if bScore != 0 { + t.Errorf("expected peer B to have score 0.0, got %f", aScore) + } + + if cScore != 0 { + t.Errorf("expected peer C to have score 0.0, got %f", aScore) + } + + if dScore != 0 { + t.Errorf("expected peer D to have score 0.0, got %f", aScore) + } + +} + +func TestScoreBehaviourPenalty(t *testing.T) { + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + BehaviourPenaltyWeight: -1, + BehaviourPenaltyDecay: 0.99, + } + + peerA := peer.ID("A") + + var ps *peerScore + + // first check AddPenalty on a nil peerScore + ps.AddPenalty(peerA, 1) + aScore := ps.Score(peerA) + if aScore != 0 { + t.Errorf("expected peer score to be 0, got %f", aScore) + } + + // instantiate the peerScore + ps = newPeerScore(params) + + // next AddPenalty on a non-existent peer + ps.AddPenalty(peerA, 1) + aScore = ps.Score(peerA) + if aScore != 0 { + t.Errorf("expected peer score to be 0, got %f", aScore) + } + + // add the peer and test penalties + ps.AddPeer(peerA, "myproto") + + aScore = ps.Score(peerA) + if aScore != 0 { + t.Errorf("expected peer score to be 0, got %f", aScore) + } + + ps.AddPenalty(peerA, 1) + aScore = ps.Score(peerA) + if aScore != -1 { + t.Errorf("expected peer score to be -1, got %f", aScore) + } + + ps.AddPenalty(peerA, 1) + aScore = ps.Score(peerA) + if aScore != -4 { + t.Errorf("expected peer score to be -4, got %f", aScore) + } + + ps.refreshScores() + + aScore = ps.Score(peerA) + if aScore != -3.9204 { + t.Errorf("expected peer score to be -3.9204, got %f", aScore) + } +} + +func TestScoreRetention(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return -1000 }, + AppSpecificWeight: 1.0, + Topics: make(map[string]*TopicScoreParams), + RetainScore: time.Second, + } + + peerA := peer.ID("A") + + ps := newPeerScore(params) + ps.AddPeer(peerA, "myproto") + ps.Graft(peerA, mytopic) + + // score should equal -1000 (app specific score) + expected := float64(-1000) + ps.refreshScores() + aScore := ps.Score(peerA) + if aScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } + + // disconnect & wait half of RetainScore time. should still have negative score + ps.RemovePeer(peerA) + delay := params.RetainScore / time.Duration(2) + time.Sleep(delay) + ps.refreshScores() + aScore = ps.Score(peerA) + if aScore != expected { + t.Fatalf("Score: %f. Expected %f", aScore, expected) + } + + // wait remaining time (plus a little slop) and the score should reset to zero + time.Sleep(delay + (50 * time.Millisecond)) + ps.refreshScores() + aScore = ps.Score(peerA) + if aScore != 0 { + t.Fatalf("Score: %f. Expected 0.0", aScore) + } +} + +func TestScoreRecapTopicParams(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + Topics: make(map[string]*TopicScoreParams), + } + topicScoreParams := &TopicScoreParams{ + TopicWeight: 1, + + MeshMessageDeliveriesWeight: -1, + MeshMessageDeliveriesActivation: time.Second, + MeshMessageDeliveriesWindow: 10 * time.Millisecond, + MeshMessageDeliveriesThreshold: 20, + MeshMessageDeliveriesCap: 100, + MeshMessageDeliveriesDecay: 1.0, // no decay for this test + + FirstMessageDeliveriesWeight: 10, + FirstMessageDeliveriesDecay: 1.0, // no decay for this test + FirstMessageDeliveriesCap: 100, + + TimeInMeshQuantum: time.Second, + } + + params.Topics[mytopic] = topicScoreParams + + // peer A always delivers the message first. + // peer B delivers next (within the delivery window). + // peer C delivers outside the delivery window. + // we expect peers A and B to have a score of zero, since all other parameter weights are zero. + // Peer C should have a negative score. + peerA := peer.ID("A") + peerB := peer.ID("B") + peers := []peer.ID{peerA, peerB} + + ps := newPeerScore(params) + for _, p := range peers { + ps.AddPeer(p, "myproto") + ps.Graft(p, mytopic) + } + + // deliver a bunch of messages from peer A, with duplicates within the window from peer B, + nMessages := 100 + for i := 0; i < nMessages; i++ { + pbMsg := makeTestMessage(i) + pbMsg.Topic = &mytopic + msg := Message{ReceivedFrom: peerA, Message: pbMsg} + ps.ValidateMessage(&msg) + ps.DeliverMessage(&msg) + + msg.ReceivedFrom = peerB + ps.DuplicateMessage(&msg) + } + + // check that the FirstMessageDeliveries for peerA and MeshMessageDeliveries for PeerB is + // at 100 + if ps.peerStats[peerA].topics[mytopic].firstMessageDeliveries != 100 { + t.Fatalf("expected 100 FirstMessageDeliveries for peerA, but got %f", ps.peerStats[peerA].topics[mytopic].firstMessageDeliveries) + } + // check that the MeshMessageDeliveries for peerB and MeshMessageDeliveries for PeerB is + // at 100 + if ps.peerStats[peerB].topics[mytopic].meshMessageDeliveries != 100 { + t.Fatalf("expected 100 MeshMessageDeliveries for peerB, but got %f", ps.peerStats[peerB].topics[mytopic].meshMessageDeliveries) + } + + // reset the topic paramaters recapping the deliveries counters + newTopicScoreParams := &TopicScoreParams{ + TopicWeight: 1, + + MeshMessageDeliveriesWeight: -1, + MeshMessageDeliveriesActivation: time.Second, + MeshMessageDeliveriesWindow: 10 * time.Millisecond, + MeshMessageDeliveriesThreshold: 20, + MeshMessageDeliveriesCap: 50, + MeshMessageDeliveriesDecay: 1.0, // no decay for this test + + FirstMessageDeliveriesWeight: 10, + FirstMessageDeliveriesDecay: 1.0, // no decay for this test + FirstMessageDeliveriesCap: 50, + + TimeInMeshQuantum: time.Second, + } + + err := ps.SetTopicScoreParams(mytopic, newTopicScoreParams) + if err != nil { + t.Fatal(err) + } + + // verify that the counters got recapped + if ps.peerStats[peerA].topics[mytopic].firstMessageDeliveries != 50 { + t.Fatalf("expected 50 FirstMessageDeliveries for peerA, but got %f", ps.peerStats[peerA].topics[mytopic].firstMessageDeliveries) + } + if ps.peerStats[peerB].topics[mytopic].meshMessageDeliveries != 50 { + t.Fatalf("expected 50 MeshMessageDeliveries for peerB, but got %f", ps.peerStats[peerB].topics[mytopic].meshMessageDeliveries) + } +} + +func TestScoreResetTopicParams(t *testing.T) { + // Create parameters with reasonable default values + mytopic := "mytopic" + params := &PeerScoreParams{ + AppSpecificScore: func(peer.ID) float64 { return 0 }, + Topics: make(map[string]*TopicScoreParams), + } + topicScoreParams := &TopicScoreParams{ + TopicWeight: 1, + TimeInMeshQuantum: time.Second, + InvalidMessageDeliveriesWeight: -1, + InvalidMessageDeliveriesDecay: 1.0, + } + + params.Topics[mytopic] = topicScoreParams + + // peer A always delivers the message first. + // peer B delivers next (within the delivery window). + // peer C delivers outside the delivery window. + // we expect peers A and B to have a score of zero, since all other parameter weights are zero. + // Peer C should have a negative score. + peerA := peer.ID("A") + + ps := newPeerScore(params) + ps.AddPeer(peerA, "myproto") + + // reject a bunch of messages + nMessages := 100 + for i := 0; i < nMessages; i++ { + pbMsg := makeTestMessage(i) + pbMsg.Topic = &mytopic + msg := Message{ReceivedFrom: peerA, Message: pbMsg} + ps.ValidateMessage(&msg) + ps.RejectMessage(&msg, RejectValidationFailed) + } + + // check the topic score + aScore := ps.Score(peerA) + if aScore != -10000 { + t.Fatalf("expected a -10000 score, but got %f instead", aScore) + } + + // reset the topic paramaters recapping the deliveries counters + newTopicScoreParams := &TopicScoreParams{ + TopicWeight: 1, + TimeInMeshQuantum: time.Second, + InvalidMessageDeliveriesWeight: -10, + InvalidMessageDeliveriesDecay: 1.0, + } + + err := ps.SetTopicScoreParams(mytopic, newTopicScoreParams) + if err != nil { + t.Fatal(err) + } + + // verify the topic score was adjusted + aScore = ps.Score(peerA) + if aScore != -100000 { + t.Fatalf("expected a -1000000 score, but got %f instead", aScore) + } +} + +func withinVariance(score float64, expected float64, variance float64) bool { + if expected >= 0 { + return score > expected*(1-variance) && score < expected*(1+variance) + } + return score > expected*(1+variance) && score < expected*(1-variance) +} + +// hack to set IPs for a peer without having to spin up real hosts with shared IPs +func setIPsForPeer(t *testing.T, ps *peerScore, p peer.ID, ips ...string) { + t.Helper() + ps.setIPs(p, ips, []string{}) + pstats, ok := ps.peerStats[p] + if !ok { + t.Fatal("unable to get peerStats") + } + pstats.ips = ips +} diff --git a/p2p/pubsub/sign_test.go b/p2p/pubsub/sign_test.go new file mode 100644 index 0000000000..8ec791d06b --- /dev/null +++ b/p2p/pubsub/sign_test.go @@ -0,0 +1,43 @@ +package pubsub + +import ( + "testing" + + pb "github.com/libp2p/go-libp2p-pubsub/pb" + + "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" +) + +func TestSigning(t *testing.T) { + privk, _, err := crypto.GenerateKeyPair(crypto.RSA, 2048) + if err != nil { + t.Fatal(err) + } + testSignVerify(t, privk) + + privk, _, err = crypto.GenerateKeyPair(crypto.Ed25519, 0) + if err != nil { + t.Fatal(err) + } + testSignVerify(t, privk) +} + +func testSignVerify(t *testing.T, privk crypto.PrivKey) { + id, err := peer.IDFromPublicKey(privk.GetPublic()) + if err != nil { + t.Fatal(err) + } + topic := "foo" + m := pb.Message{ + Data: []byte("abc"), + Topic: &topic, + From: []byte(id), + Seqno: []byte("123"), + } + signMessage(id, privk, &m) + err = verifyMessageSignature(&m) + if err != nil { + t.Fatal(err) + } +} diff --git a/p2p/pubsub/subscription_filter_test.go b/p2p/pubsub/subscription_filter_test.go new file mode 100644 index 0000000000..a2413714ad --- /dev/null +++ b/p2p/pubsub/subscription_filter_test.go @@ -0,0 +1,210 @@ +package pubsub + +import ( + "context" + "regexp" + "testing" + "time" + + pb "github.com/libp2p/go-libp2p-pubsub/pb" + + "github.com/libp2p/go-libp2p-core/peer" +) + +func TestBasicSubscriptionFilter(t *testing.T) { + peerA := peer.ID("A") + + topic1 := "test1" + topic2 := "test2" + topic3 := "test3" + yes := true + subs := []*pb.RPC_SubOpts{ + &pb.RPC_SubOpts{ + Topicid: &topic1, + Subscribe: &yes, + }, + &pb.RPC_SubOpts{ + Topicid: &topic2, + Subscribe: &yes, + }, + &pb.RPC_SubOpts{ + Topicid: &topic3, + Subscribe: &yes, + }, + } + + filter := NewAllowlistSubscriptionFilter(topic1, topic2) + canSubscribe := filter.CanSubscribe(topic1) + if !canSubscribe { + t.Fatal("expected allowed subscription") + } + canSubscribe = filter.CanSubscribe(topic2) + if !canSubscribe { + t.Fatal("expected allowed subscription") + } + canSubscribe = filter.CanSubscribe(topic3) + if canSubscribe { + t.Fatal("expected disallowed subscription") + } + allowedSubs, err := filter.FilterIncomingSubscriptions(peerA, subs) + if err != nil { + t.Fatal(err) + } + if len(allowedSubs) != 2 { + t.Fatalf("expected 2 allowed subscriptions but got %d", len(allowedSubs)) + } + for _, sub := range allowedSubs { + if sub.GetTopicid() == topic3 { + t.Fatal("unpexted subscription to test3") + } + } + + limitFilter := WrapLimitSubscriptionFilter(filter, 2) + _, err = limitFilter.FilterIncomingSubscriptions(peerA, subs) + if err != ErrTooManySubscriptions { + t.Fatal("expected rejection because of too many subscriptions") + } + + filter = NewRegexpSubscriptionFilter(regexp.MustCompile("test[12]")) + canSubscribe = filter.CanSubscribe(topic1) + if !canSubscribe { + t.Fatal("expected allowed subscription") + } + canSubscribe = filter.CanSubscribe(topic2) + if !canSubscribe { + t.Fatal("expected allowed subscription") + } + canSubscribe = filter.CanSubscribe(topic3) + if canSubscribe { + t.Fatal("expected disallowed subscription") + } + allowedSubs, err = filter.FilterIncomingSubscriptions(peerA, subs) + if err != nil { + t.Fatal(err) + } + if len(allowedSubs) != 2 { + t.Fatalf("expected 2 allowed subscriptions but got %d", len(allowedSubs)) + } + for _, sub := range allowedSubs { + if sub.GetTopicid() == topic3 { + t.Fatal("unexpected subscription") + } + } + + limitFilter = WrapLimitSubscriptionFilter(filter, 2) + _, err = limitFilter.FilterIncomingSubscriptions(peerA, subs) + if err != ErrTooManySubscriptions { + t.Fatal("expected rejection because of too many subscriptions") + } + +} + +func TestSubscriptionFilterDeduplication(t *testing.T) { + peerA := peer.ID("A") + + topic1 := "test1" + topic2 := "test2" + topic3 := "test3" + yes := true + no := false + subs := []*pb.RPC_SubOpts{ + &pb.RPC_SubOpts{ + Topicid: &topic1, + Subscribe: &yes, + }, + &pb.RPC_SubOpts{ + Topicid: &topic1, + Subscribe: &yes, + }, + + &pb.RPC_SubOpts{ + Topicid: &topic2, + Subscribe: &yes, + }, + &pb.RPC_SubOpts{ + Topicid: &topic2, + Subscribe: &no, + }, + &pb.RPC_SubOpts{ + Topicid: &topic3, + Subscribe: &yes, + }, + } + + filter := NewAllowlistSubscriptionFilter(topic1, topic2) + allowedSubs, err := filter.FilterIncomingSubscriptions(peerA, subs) + if err != nil { + t.Fatal(err) + } + if len(allowedSubs) != 1 { + t.Fatalf("expected 2 allowed subscriptions but got %d", len(allowedSubs)) + } + for _, sub := range allowedSubs { + if sub.GetTopicid() == topic3 || sub.GetTopicid() == topic2 { + t.Fatal("unexpected subscription") + } + } +} + +func TestSubscriptionFilterRPC(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 2) + ps1 := getPubsub(ctx, hosts[0], WithSubscriptionFilter(NewAllowlistSubscriptionFilter("test1", "test2"))) + ps2 := getPubsub(ctx, hosts[1], WithSubscriptionFilter(NewAllowlistSubscriptionFilter("test2", "test3"))) + + _ = mustSubscribe(t, ps1, "test1") + _ = mustSubscribe(t, ps1, "test2") + _ = mustSubscribe(t, ps2, "test2") + _ = mustSubscribe(t, ps2, "test3") + + // check the rejection as well + _, err := ps1.Join("test3") + if err == nil { + t.Fatal("expected subscription error") + } + + connect(t, hosts[0], hosts[1]) + + time.Sleep(time.Second) + + var sub1, sub2, sub3 bool + ready := make(chan struct{}) + + ps1.eval <- func() { + _, sub1 = ps1.topics["test1"][hosts[1].ID()] + _, sub2 = ps1.topics["test2"][hosts[1].ID()] + _, sub3 = ps1.topics["test3"][hosts[1].ID()] + ready <- struct{}{} + } + <-ready + + if sub1 { + t.Fatal("expected no subscription for test1") + } + if !sub2 { + t.Fatal("expected subscription for test2") + } + if sub3 { + t.Fatal("expected no subscription for test1") + } + + ps2.eval <- func() { + _, sub1 = ps2.topics["test1"][hosts[0].ID()] + _, sub2 = ps2.topics["test2"][hosts[0].ID()] + _, sub3 = ps2.topics["test3"][hosts[0].ID()] + ready <- struct{}{} + } + <-ready + + if sub1 { + t.Fatal("expected no subscription for test1") + } + if !sub2 { + t.Fatal("expected subscription for test1") + } + if sub3 { + t.Fatal("expected no subscription for test1") + } +} diff --git a/p2p/pubsub/tag_tracer_test.go b/p2p/pubsub/tag_tracer_test.go new file mode 100644 index 0000000000..567d211098 --- /dev/null +++ b/p2p/pubsub/tag_tracer_test.go @@ -0,0 +1,252 @@ +package pubsub + +// +//import ( +// crand "crypto/rand" +// "fmt" +// "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +// "testing" +// "time" +// +// "github.com/benbjohnson/clock" +// connmgr "github.com/libp2p/go-libp2p-connmgr" +// connmgri "github.com/libp2p/go-libp2p-core/connmgr" +// "github.com/libp2p/go-libp2p-core/peer" +// +// pb "github.com/libp2p/go-libp2p-pubsub/pb" +//) +// +//func TestTagTracerMeshTags(t *testing.T) { +// // test that tags are applied when the tagTracer sees graft and prune events +// +// cmgr := connmgr.NewConnManager(5, 10, time.Minute) +// tt := newTagTracer(cmgr) +// +// var p enode.ID +// crand.Read(p[:]) +// topic := "a-topic" +// +// tt.Join(topic) +// tt.Graft(p, topic) +// +// tag := "pubsub:" + topic +// if !cmgr.IsProtected(p, tag) { +// t.Fatal("expected the mesh peer to be protected") +// } +// +// tt.Prune(p, topic) +// if cmgr.IsProtected(p, tag) { +// t.Fatal("expected the former mesh peer to be unprotected") +// } +//} +// +//func TestTagTracerDirectPeerTags(t *testing.T) { +// // test that we add a tag to direct peers +// cmgr := connmgr.NewConnManager(5, 10, time.Minute) +// tt := newTagTracer(cmgr) +// +// p1 := peer.ID("1") +// p2 := peer.ID("2") +// p3 := peer.ID("3") +// +// // in the real world, tagTracer.direct is set in the WithDirectPeers option function +// tt.direct = make(map[peer.ID]struct{}) +// tt.direct[p1] = struct{}{} +// +// tt.AddPeer(p1, GossipSubID_v10) +// tt.AddPeer(p2, GossipSubID_v10) +// tt.AddPeer(p3, GossipSubID_v10) +// +// tag := "pubsub:" +// if !cmgr.IsProtected(p1, tag) { +// t.Fatal("expected direct peer to be protected") +// } +// +// for _, p := range []peer.ID{p2, p3} { +// if cmgr.IsProtected(p, tag) { +// t.Fatal("expected non-direct peer to be unprotected") +// } +// } +//} +// +//func TestTagTracerDeliveryTags(t *testing.T) { +// t.Skip("flaky test temporarily disabled; TODO: fixme") +// // test decaying delivery tags +// +// // use fake time to test the tag decay +// clk := clock.NewMock() +// decayCfg := &connmgr.DecayerCfg{ +// Clock: clk, +// Resolution: time.Minute, +// } +// cmgr := connmgr.NewConnManager(5, 10, time.Minute, connmgr.DecayerConfig(decayCfg)) +// +// tt := newTagTracer(cmgr) +// +// topic1 := "topic-1" +// topic2 := "topic-2" +// +// p := peer.ID("a-peer") +// +// tt.Join(topic1) +// tt.Join(topic2) +// +// for i := 0; i < 20; i++ { +// // deliver only 5 messages to topic 2 (less than the cap) +// topic := &topic1 +// if i < 5 { +// topic = &topic2 +// } +// msg := &Message{ +// ReceivedFrom: p, +// Message: &pb.Message{ +// From: []byte(p), +// Data: []byte("hello"), +// Topic: topic, +// }, +// } +// tt.DeliverMessage(msg) +// } +// +// // we have to tick the fake clock once to apply the bump +// clk.Add(time.Minute) +// +// tag1 := "pubsub-deliveries:topic-1" +// tag2 := "pubsub-deliveries:topic-2" +// +// // the tag value for topic-1 should be capped at GossipSubConnTagMessageDeliveryCap (default 15) +// val := getTagValue(cmgr, p, tag1) +// expected := GossipSubConnTagMessageDeliveryCap +// if val != expected { +// t.Errorf("expected delivery tag to be capped at %d, was %d", expected, val) +// } +// +// // the value for topic-2 should equal the number of messages delivered (5), since it was less than the cap +// val = getTagValue(cmgr, p, tag2) +// expected = 5 +// if val != expected { +// t.Errorf("expected delivery tag value = %d, got %d", expected, val) +// } +// +// // if we jump forward a few minutes, we should see the tags decrease by 1 / 10 minutes +// clk.Add(50 * time.Minute) +// time.Sleep(2 * time.Second) +// +// val = getTagValue(cmgr, p, tag1) +// expected = GossipSubConnTagMessageDeliveryCap - 5 +// // the actual expected value should be GossipSubConnTagMessageDeliveryCap - 5, +// // however due to timing issues on Travis, we consistently get GossipSubConnTagMessageDeliveryCap - 4 +// // there instead. So our assertion checks for the expected value +/- 1 +// if val > expected+1 || val < expected-1 { +// t.Errorf("expected delivery tag value = %d ± 1, got %d", expected, val) +// } +// +// // the tag for topic-2 should have reset to zero by now, but again we add one for Travis since it's slow... +// val = getTagValue(cmgr, p, tag2) +// expected = 0 +// if val > expected+1 || val < expected-1 { +// t.Errorf("expected delivery tag value = %d ± 1, got %d", expected, val) +// } +// +// // leaving the topic should remove the tag +// if !tagExists(cmgr, p, tag1) { +// t.Errorf("expected delivery tag %s to be applied to peer %s", tag1, p) +// } +// tt.Leave(topic1) +// // advance the real clock a bit to allow the connmgr to remove the tag async +// time.Sleep(time.Second) +// if tagExists(cmgr, p, tag1) { +// t.Errorf("expected delivery tag %s to be removed after leaving the topic", tag1) +// } +//} +// +//func TestTagTracerDeliveryTagsNearFirst(t *testing.T) { +// // use fake time to test the tag decay +// clk := clock.NewMock() +// decayCfg := &connmgr.DecayerCfg{ +// Clock: clk, +// Resolution: time.Minute, +// } +// cmgr := connmgr.NewConnManager(5, 10, time.Minute, connmgr.DecayerConfig(decayCfg)) +// +// tt := newTagTracer(cmgr) +// +// topic := "test" +// +// p := peer.ID("a-peer") +// p2 := peer.ID("another-peer") +// p3 := peer.ID("slow-peer") +// +// tt.Join(topic) +// +// for i := 0; i < GossipSubConnTagMessageDeliveryCap+5; i++ { +// msg := &Message{ +// ReceivedFrom: p, +// Message: &pb.Message{ +// From: []byte(p), +// Data: []byte(fmt.Sprintf("msg-%d", i)), +// Topic: &topic, +// Seqno: []byte(fmt.Sprintf("%d", i)), +// }, +// } +// +// // a duplicate of the message, received from p2 +// dup := &Message{ +// ReceivedFrom: p2, +// Message: msg.Message, +// } +// +// // the message starts validating as soon as we receive it from p +// tt.ValidateMessage(msg) +// // p2 should get near-first credit for the duplicate message that arrives before +// // validation is complete +// tt.DuplicateMessage(dup) +// // DeliverMessage gets called when validation is complete +// tt.DeliverMessage(msg) +// +// // p3 delivers a duplicate after validation completes & gets no credit +// dup.ReceivedFrom = p3 +// tt.DuplicateMessage(dup) +// } +// +// clk.Add(time.Minute) +// +// // both p and p2 should get delivery tags equal to the cap +// tag := "pubsub-deliveries:test" +// val := getTagValue(cmgr, p, tag) +// if val != GossipSubConnTagMessageDeliveryCap { +// t.Errorf("expected tag %s to have val %d, was %d", tag, GossipSubConnTagMessageDeliveryCap, val) +// } +// val = getTagValue(cmgr, p2, tag) +// if val != GossipSubConnTagMessageDeliveryCap { +// t.Errorf("expected tag %s for near-first peer to have val %d, was %d", tag, GossipSubConnTagMessageDeliveryCap, val) +// } +// +// // p3 should have no delivery tag credit +// val = getTagValue(cmgr, p3, tag) +// if val != 0 { +// t.Errorf("expected tag %s for slow peer to have val %d, was %d", tag, 0, val) +// } +//} +// +//func getTagValue(mgr connmgri.ConnManager, p peer.ID, tag string) int { +// info := mgr.GetTagInfo(p) +// if info == nil { +// return 0 +// } +// val, ok := info.Tags[tag] +// if !ok { +// return 0 +// } +// return val +//} +// +////lint:ignore U1000 used only by skipped tests at present +//func tagExists(mgr connmgri.ConnManager, p peer.ID, tag string) bool { +// info := mgr.GetTagInfo(p) +// if info == nil { +// return false +// } +// _, exists := info.Tags[tag] +// return exists +//} diff --git a/p2p/pubsub/topic_test.go b/p2p/pubsub/topic_test.go new file mode 100644 index 0000000000..2169d35859 --- /dev/null +++ b/p2p/pubsub/topic_test.go @@ -0,0 +1,782 @@ +package pubsub + +import ( + "bytes" + "context" + "fmt" + "math/rand" + "sync" + "testing" + "time" + + "github.com/libp2p/go-libp2p-core/peer" +) + +func getTopics(psubs []*PubSub, topicID string, opts ...TopicOpt) []*Topic { + topics := make([]*Topic, len(psubs)) + + for i, ps := range psubs { + t, err := ps.Join(topicID, opts...) + if err != nil { + panic(err) + } + topics[i] = t + } + + return topics +} + +func getTopicEvts(topics []*Topic, opts ...TopicEventHandlerOpt) []*TopicEventHandler { + handlers := make([]*TopicEventHandler, len(topics)) + + for i, t := range topics { + h, err := t.EventHandler(opts...) + if err != nil { + panic(err) + } + handlers[i] = h + } + + return handlers +} + +func TestTopicCloseWithOpenSubscription(t *testing.T) { + var sub *Subscription + var err error + testTopicCloseWithOpenResource(t, + func(topic *Topic) { + sub, err = topic.Subscribe() + if err != nil { + t.Fatal(err) + } + }, + func() { + sub.Cancel() + }, + ) +} + +func TestTopicCloseWithOpenEventHandler(t *testing.T) { + var evts *TopicEventHandler + var err error + testTopicCloseWithOpenResource(t, + func(topic *Topic) { + evts, err = topic.EventHandler() + if err != nil { + t.Fatal(err) + } + }, + func() { + evts.Cancel() + }, + ) +} + +func TestTopicCloseWithOpenRelay(t *testing.T) { + var relayCancel RelayCancelFunc + var err error + testTopicCloseWithOpenResource(t, + func(topic *Topic) { + relayCancel, err = topic.Relay() + if err != nil { + t.Fatal(err) + } + }, + func() { + relayCancel() + }, + ) +} + +func testTopicCloseWithOpenResource(t *testing.T, openResource func(topic *Topic), closeResource func()) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + const numHosts = 1 + topicID := "foobar" + hosts := getNetHosts(t, ctx, numHosts) + ps := getPubsub(ctx, hosts[0]) + + // Try create and cancel topic + topic, err := ps.Join(topicID) + if err != nil { + t.Fatal(err) + } + + if err := topic.Close(); err != nil { + t.Fatal(err) + } + + // Try create and cancel topic while there's an outstanding subscription/event handler + topic, err = ps.Join(topicID) + if err != nil { + t.Fatal(err) + } + + openResource(topic) + + if err := topic.Close(); err == nil { + t.Fatal("expected an error closing a topic with an open resource") + } + + // Check if the topic closes properly after closing the resource + closeResource() + time.Sleep(time.Millisecond * 100) + + if err := topic.Close(); err != nil { + t.Fatal(err) + } +} + +func TestTopicReuse(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + const numHosts = 2 + topicID := "foobar" + hosts := getNetHosts(t, ctx, numHosts) + + sender := getPubsub(ctx, hosts[0], WithDiscovery(&dummyDiscovery{})) + receiver := getPubsub(ctx, hosts[1]) + + connectAll(t, hosts) + + // Sender creates topic + sendTopic, err := sender.Join(topicID) + if err != nil { + t.Fatal(err) + } + + // Receiver creates and subscribes to the topic + receiveTopic, err := receiver.Join(topicID) + if err != nil { + t.Fatal(err) + } + + sub, err := receiveTopic.Subscribe() + if err != nil { + t.Fatal(err) + } + + firstMsg := []byte("1") + if err := sendTopic.Publish(ctx, firstMsg, WithReadiness(MinTopicSize(1))); err != nil { + t.Fatal(err) + } + + msg, err := sub.Next(ctx) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(msg.GetData(), firstMsg) { + t.Fatal("received incorrect message") + } + + if err := sendTopic.Close(); err != nil { + t.Fatal(err) + } + + // Recreate the same topic + newSendTopic, err := sender.Join(topicID) + if err != nil { + t.Fatal(err) + } + + // Try sending data with original topic + illegalSend := []byte("illegal") + if err := sendTopic.Publish(ctx, illegalSend); err != ErrTopicClosed { + t.Fatal(err) + } + + timeoutCtx, timeoutCancel := context.WithTimeout(ctx, time.Second*2) + defer timeoutCancel() + msg, err = sub.Next(timeoutCtx) + if err != context.DeadlineExceeded { + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(msg.GetData(), illegalSend) { + t.Fatal("received incorrect message from illegal topic") + } + t.Fatal("received message sent by illegal topic") + } + timeoutCancel() + + // Try cancelling the new topic by using the original topic + if err := sendTopic.Close(); err != nil { + t.Fatal(err) + } + + secondMsg := []byte("2") + if err := newSendTopic.Publish(ctx, secondMsg); err != nil { + t.Fatal(err) + } + + timeoutCtx, timeoutCancel = context.WithTimeout(ctx, time.Second*2) + defer timeoutCancel() + msg, err = sub.Next(timeoutCtx) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(msg.GetData(), secondMsg) { + t.Fatal("received incorrect message") + } +} + +func TestTopicEventHandlerCancel(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + const numHosts = 5 + topicID := "foobar" + hosts := getNetHosts(t, ctx, numHosts) + ps := getPubsub(ctx, hosts[0]) + + // Try create and cancel topic + topic, err := ps.Join(topicID) + if err != nil { + t.Fatal(err) + } + + evts, err := topic.EventHandler() + if err != nil { + t.Fatal(err) + } + evts.Cancel() + timeoutCtx, timeoutCancel := context.WithTimeout(ctx, time.Second*2) + defer timeoutCancel() + connectAll(t, hosts) + _, err = evts.NextPeerEvent(timeoutCtx) + if err != context.DeadlineExceeded { + if err != nil { + t.Fatal(err) + } + t.Fatal("received event after cancel") + } +} + +func TestSubscriptionJoinNotification(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + const numLateSubscribers = 10 + const numHosts = 20 + hosts := getNetHosts(t, ctx, numHosts) + topics := getTopics(getPubsubs(ctx, hosts), "foobar") + evts := getTopicEvts(topics) + + subs := make([]*Subscription, numHosts) + topicPeersFound := make([]map[peer.ID]struct{}, numHosts) + + // Have some peers subscribe earlier than other peers. + // This exercises whether we get subscription notifications from + // existing peers. + for i, topic := range topics[numLateSubscribers:] { + subch, err := topic.Subscribe() + if err != nil { + t.Fatal(err) + } + + subs[i] = subch + } + + connectAll(t, hosts) + + time.Sleep(time.Millisecond * 100) + + // Have the rest subscribe + for i, topic := range topics[:numLateSubscribers] { + subch, err := topic.Subscribe() + if err != nil { + t.Fatal(err) + } + + subs[i+numLateSubscribers] = subch + } + + wg := sync.WaitGroup{} + for i := 0; i < numHosts; i++ { + peersFound := make(map[peer.ID]struct{}) + topicPeersFound[i] = peersFound + evt := evts[i] + wg.Add(1) + go func(peersFound map[peer.ID]struct{}) { + defer wg.Done() + for len(peersFound) < numHosts-1 { + event, err := evt.NextPeerEvent(ctx) + if err != nil { + panic(err) + } + if event.Type == PeerJoin { + peersFound[event.Peer] = struct{}{} + } + } + }(peersFound) + } + + wg.Wait() + for _, peersFound := range topicPeersFound { + if len(peersFound) != numHosts-1 { + t.Fatal("incorrect number of peers found") + } + } +} + +func TestSubscriptionLeaveNotification(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + const numHosts = 20 + hosts := getNetHosts(t, ctx, numHosts) + psubs := getPubsubs(ctx, hosts) + topics := getTopics(psubs, "foobar") + evts := getTopicEvts(topics) + + subs := make([]*Subscription, numHosts) + topicPeersFound := make([]map[peer.ID]struct{}, numHosts) + + // Subscribe all peers and wait until they've all been found + for i, topic := range topics { + subch, err := topic.Subscribe() + if err != nil { + t.Fatal(err) + } + + subs[i] = subch + } + + connectAll(t, hosts) + + time.Sleep(time.Millisecond * 100) + + wg := sync.WaitGroup{} + for i := 0; i < numHosts; i++ { + peersFound := make(map[peer.ID]struct{}) + topicPeersFound[i] = peersFound + evt := evts[i] + wg.Add(1) + go func(peersFound map[peer.ID]struct{}) { + defer wg.Done() + for len(peersFound) < numHosts-1 { + event, err := evt.NextPeerEvent(ctx) + if err != nil { + panic(err) + } + if event.Type == PeerJoin { + peersFound[event.Peer] = struct{}{} + } + } + }(peersFound) + } + + wg.Wait() + for _, peersFound := range topicPeersFound { + if len(peersFound) != numHosts-1 { + t.Fatal("incorrect number of peers found") + } + } + + // Test removing peers and verifying that they cause events + subs[1].Cancel() + hosts[2].Close() + psubs[0].BlacklistPeer(hosts[3].ID()) + + leavingPeers := make(map[peer.ID]struct{}) + for len(leavingPeers) < 3 { + event, err := evts[0].NextPeerEvent(ctx) + if err != nil { + t.Fatal(err) + } + if event.Type == PeerLeave { + leavingPeers[event.Peer] = struct{}{} + } + } + + if _, ok := leavingPeers[hosts[1].ID()]; !ok { + t.Fatal(fmt.Errorf("canceling subscription did not cause a leave event")) + } + if _, ok := leavingPeers[hosts[2].ID()]; !ok { + t.Fatal(fmt.Errorf("closing host did not cause a leave event")) + } + if _, ok := leavingPeers[hosts[3].ID()]; !ok { + t.Fatal(fmt.Errorf("blacklisting peer did not cause a leave event")) + } +} + +func TestSubscriptionManyNotifications(t *testing.T) { + t.Skip("flaky test disabled") + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + const topic = "foobar" + + const numHosts = 33 + hosts := getNetHosts(t, ctx, numHosts) + topics := getTopics(getPubsubs(ctx, hosts), topic) + evts := getTopicEvts(topics) + + subs := make([]*Subscription, numHosts) + topicPeersFound := make([]map[peer.ID]struct{}, numHosts) + + // Subscribe all peers except one and wait until they've all been found + for i := 1; i < numHosts; i++ { + subch, err := topics[i].Subscribe() + if err != nil { + t.Fatal(err) + } + + subs[i] = subch + } + + connectAll(t, hosts) + + time.Sleep(time.Millisecond * 100) + + wg := sync.WaitGroup{} + for i := 1; i < numHosts; i++ { + peersFound := make(map[peer.ID]struct{}) + topicPeersFound[i] = peersFound + evt := evts[i] + wg.Add(1) + go func(peersFound map[peer.ID]struct{}) { + defer wg.Done() + for len(peersFound) < numHosts-2 { + event, err := evt.NextPeerEvent(ctx) + if err != nil { + panic(err) + } + if event.Type == PeerJoin { + peersFound[event.Peer] = struct{}{} + } + } + }(peersFound) + } + + wg.Wait() + for _, peersFound := range topicPeersFound[1:] { + if len(peersFound) != numHosts-2 { + t.Fatalf("found %d peers, expected %d", len(peersFound), numHosts-2) + } + } + + // Wait for remaining peer to find other peers + remPeerTopic, remPeerEvts := topics[0], evts[0] + for len(remPeerTopic.ListPeers()) < numHosts-1 { + time.Sleep(time.Millisecond * 100) + } + + // Subscribe the remaining peer and check that all the events came through + sub, err := remPeerTopic.Subscribe() + if err != nil { + t.Fatal(err) + } + + subs[0] = sub + + peerState := readAllQueuedEvents(ctx, t, remPeerEvts) + + if len(peerState) != numHosts-1 { + t.Fatal("incorrect number of peers found") + } + + for _, e := range peerState { + if e != PeerJoin { + t.Fatal("non Join event occurred") + } + } + + // Unsubscribe all peers except one and check that all the events came through + for i := 1; i < numHosts; i++ { + subs[i].Cancel() + } + + // Wait for remaining peer to disconnect from the other peers + for len(topics[0].ListPeers()) != 0 { + time.Sleep(time.Millisecond * 100) + } + + peerState = readAllQueuedEvents(ctx, t, remPeerEvts) + + if len(peerState) != numHosts-1 { + t.Fatal("incorrect number of peers found") + } + + for _, e := range peerState { + if e != PeerLeave { + t.Fatal("non Leave event occurred") + } + } +} + +func TestSubscriptionNotificationSubUnSub(t *testing.T) { + // Resubscribe and Unsubscribe a peers and check the state for consistency + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + const topic = "foobar" + + const numHosts = 35 + hosts := getNetHosts(t, ctx, numHosts) + topics := getTopics(getPubsubs(ctx, hosts), topic) + + for i := 1; i < numHosts; i++ { + connect(t, hosts[0], hosts[i]) + } + time.Sleep(time.Millisecond * 100) + + notifSubThenUnSub(ctx, t, topics) +} + +func TestTopicRelay(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + const topic = "foobar" + const numHosts = 5 + + hosts := getNetHosts(t, ctx, numHosts) + topics := getTopics(getPubsubs(ctx, hosts), topic) + + // [0.Rel] - [1.Rel] - [2.Sub] + // | + // [3.Rel] - [4.Sub] + + connect(t, hosts[0], hosts[1]) + connect(t, hosts[1], hosts[2]) + connect(t, hosts[1], hosts[3]) + connect(t, hosts[3], hosts[4]) + + time.Sleep(time.Millisecond * 100) + + var subs []*Subscription + + for i, topic := range topics { + if i == 2 || i == 4 { + sub, err := topic.Subscribe() + if err != nil { + t.Fatal(err) + } + + subs = append(subs, sub) + } else { + _, err := topic.Relay() + if err != nil { + t.Fatal(err) + } + } + } + + time.Sleep(time.Millisecond * 100) + + for i := 0; i < 100; i++ { + msg := []byte("message") + + owner := rand.Intn(len(topics)) + + err := topics[owner].Publish(ctx, msg) + if err != nil { + t.Fatal(err) + } + + for _, sub := range subs { + received, err := sub.Next(ctx) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(msg, received.Data) { + t.Fatal("received message is other than expected") + } + } + } +} + +func TestTopicRelayReuse(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + const topic = "foobar" + const numHosts = 1 + + hosts := getNetHosts(t, ctx, numHosts) + pubsubs := getPubsubs(ctx, hosts) + topics := getTopics(pubsubs, topic) + + relay1Cancel, err := topics[0].Relay() + if err != nil { + t.Fatal(err) + } + + relay2Cancel, err := topics[0].Relay() + if err != nil { + t.Fatal(err) + } + + relay3Cancel, err := topics[0].Relay() + if err != nil { + t.Fatal(err) + } + + time.Sleep(time.Millisecond * 100) + + res := make(chan bool, 1) + pubsubs[0].eval <- func() { + res <- pubsubs[0].myRelays[topic] == 3 + } + + isCorrectNumber := <-res + if !isCorrectNumber { + t.Fatal("incorrect number of relays") + } + + // only the first invocation should take effect + relay1Cancel() + relay1Cancel() + relay1Cancel() + + pubsubs[0].eval <- func() { + res <- pubsubs[0].myRelays[topic] == 2 + } + + isCorrectNumber = <-res + if !isCorrectNumber { + t.Fatal("incorrect number of relays") + } + + relay2Cancel() + relay3Cancel() + + time.Sleep(time.Millisecond * 100) + + pubsubs[0].eval <- func() { + res <- pubsubs[0].myRelays[topic] == 0 + } + + isCorrectNumber = <-res + if !isCorrectNumber { + t.Fatal("incorrect number of relays") + } +} + +func TestTopicRelayOnClosedTopic(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + const topic = "foobar" + const numHosts = 1 + + hosts := getNetHosts(t, ctx, numHosts) + topics := getTopics(getPubsubs(ctx, hosts), topic) + + err := topics[0].Close() + if err != nil { + t.Fatal(err) + } + + _, err = topics[0].Relay() + if err == nil { + t.Fatalf("error should be returned") + } +} + +func TestProducePanic(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + const numHosts = 5 + topicID := "foobar" + hosts := getNetHosts(t, ctx, numHosts) + ps := getPubsub(ctx, hosts[0]) + + // Create topic + topic, err := ps.Join(topicID) + if err != nil { + t.Fatal(err) + } + + // Create subscription we're going to cancel + s, err := topic.Subscribe() + if err != nil { + t.Fatal(err) + } + // Create second subscription to keep us alive on the subscription map + // after the first one is canceled + s2, err := topic.Subscribe() + if err != nil { + t.Fatal(err) + } + _ = s2 + + s.Cancel() + time.Sleep(time.Second) + s.Cancel() + time.Sleep(time.Second) +} + +func notifSubThenUnSub(ctx context.Context, t *testing.T, topics []*Topic) { + primaryTopic := topics[0] + msgs := make([]*Subscription, len(topics)) + checkSize := len(topics) - 1 + + // Subscribe all peers to the topic + var err error + for i, topic := range topics { + msgs[i], err = topic.Subscribe() + if err != nil { + t.Fatal(err) + } + } + + // Wait for the primary peer to be connected to the other peers + for len(primaryTopic.ListPeers()) < checkSize { + time.Sleep(time.Millisecond * 100) + } + + // Unsubscribe all peers except the primary + for i := 1; i < checkSize+1; i++ { + msgs[i].Cancel() + } + + // Wait for the unsubscribe messages to reach the primary peer + for len(primaryTopic.ListPeers()) < 0 { + time.Sleep(time.Millisecond * 100) + } + + // read all available events and verify that there are no events to process + // this is because every peer that joined also left + primaryEvts, err := primaryTopic.EventHandler() + if err != nil { + t.Fatal(err) + } + peerState := readAllQueuedEvents(ctx, t, primaryEvts) + + if len(peerState) != 0 { + for p, s := range peerState { + fmt.Println(p, s) + } + t.Fatalf("Received incorrect events. %d extra events", len(peerState)) + } +} + +func readAllQueuedEvents(ctx context.Context, t *testing.T, evt *TopicEventHandler) map[peer.ID]EventType { + peerState := make(map[peer.ID]EventType) + for { + ctx, cancel := context.WithTimeout(ctx, time.Millisecond*100) + event, err := evt.NextPeerEvent(ctx) + cancel() + + if err == context.DeadlineExceeded { + break + } else if err != nil { + t.Fatal(err) + } + + e, ok := peerState[event.Peer] + if !ok { + peerState[event.Peer] = event.Type + } else if e != event.Type { + delete(peerState, event.Peer) + } + } + return peerState +} diff --git a/p2p/pubsub/trace_test.go b/p2p/pubsub/trace_test.go new file mode 100644 index 0000000000..e7abb55cfb --- /dev/null +++ b/p2p/pubsub/trace_test.go @@ -0,0 +1,321 @@ +package pubsub + +import ( + "compress/gzip" + "context" + "encoding/json" + "fmt" + "io" + "os" + "sync" + "testing" + "time" + + pb "github.com/libp2p/go-libp2p-pubsub/pb" + + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/peerstore" + + bhost "github.com/libp2p/go-libp2p-blankhost" + swarmt "github.com/libp2p/go-libp2p-swarm/testing" + + "github.com/libp2p/go-msgio/protoio" +) + +func testWithTracer(t *testing.T, tracer EventTracer) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 20) + psubs := getGossipsubs(ctx, hosts, + WithEventTracer(tracer), + // to bootstrap from star topology + WithPeerExchange(true), + // to exercise the score paths in the tracer + WithPeerScore( + &PeerScoreParams{ + TopicScoreCap: 100, + AppSpecificScore: func(peer.ID) float64 { return 0 }, + DecayInterval: time.Second, + DecayToZero: 0.01, + }, + &PeerScoreThresholds{ + GossipThreshold: -1, + PublishThreshold: -2, + GraylistThreshold: -3, + OpportunisticGraftThreshold: 1, + })) + + // add a validator that rejects some messages to exercise those code paths in the tracer + for _, ps := range psubs { + ps.RegisterTopicValidator("test", func(ctx context.Context, p peer.ID, msg *Message) bool { + if string(msg.Data) == "invalid!" { + return false + } else { + return true + } + }) + } + + // this is the star topology test so that we make sure we get some PRUNEs and cover that code path + + // add all peer addresses to the peerstores + // this is necessary because we can't have signed address records witout identify + // pushing them + for i := range hosts { + for j := range hosts { + if i == j { + continue + } + hosts[i].Peerstore().AddAddrs(hosts[j].ID(), hosts[j].Addrs(), peerstore.PermanentAddrTTL) + } + } + + // build the star + for i := 1; i < 20; i++ { + connect(t, hosts[0], hosts[i]) + } + + // build the mesh + var subs []*Subscription + for _, ps := range psubs { + sub, err := ps.Subscribe("test") + if err != nil { + t.Fatal(err) + } + go func(sub *Subscription) { + for { + _, err := sub.Next(ctx) + if err != nil { + return + } + } + }(sub) + subs = append(subs, sub) + } + + // wait for the mesh to build + time.Sleep(5 * time.Second) + + // publish some messages + for i := 0; i < 20; i++ { + if i%7 == 0 { + psubs[i].Publish("test", []byte("invalid!")) + } else { + msg := []byte(fmt.Sprintf("message %d", i)) + psubs[i].Publish("test", msg) + } + } + + // wait a bit for propagation and call it day + time.Sleep(time.Second) + + // close all subscriptions to get some leave events + for _, sub := range subs { + sub.Cancel() + } + + // wait for the leave to take effect + time.Sleep(time.Second) +} + +type traceStats struct { + publish, reject, duplicate, deliver, add, remove, recv, send, drop, join, leave, graft, prune int +} + +func (t *traceStats) process(evt *pb.TraceEvent) { + //fmt.Printf("process event %s\n", evt.GetType()) + switch evt.GetType() { + case pb.TraceEvent_PUBLISH_MESSAGE: + t.publish++ + case pb.TraceEvent_REJECT_MESSAGE: + t.reject++ + case pb.TraceEvent_DUPLICATE_MESSAGE: + t.duplicate++ + case pb.TraceEvent_DELIVER_MESSAGE: + t.deliver++ + case pb.TraceEvent_ADD_PEER: + t.add++ + case pb.TraceEvent_REMOVE_PEER: + t.remove++ + case pb.TraceEvent_RECV_RPC: + t.recv++ + case pb.TraceEvent_SEND_RPC: + t.send++ + case pb.TraceEvent_DROP_RPC: + t.drop++ + case pb.TraceEvent_JOIN: + t.join++ + case pb.TraceEvent_LEAVE: + t.leave++ + case pb.TraceEvent_GRAFT: + t.graft++ + case pb.TraceEvent_PRUNE: + t.prune++ + } +} + +func (ts *traceStats) check(t *testing.T) { + if ts.publish == 0 { + t.Fatal("expected non-zero count") + } + if ts.duplicate == 0 { + t.Fatal("expected non-zero count") + } + if ts.deliver == 0 { + t.Fatal("expected non-zero count") + } + if ts.reject == 0 { + t.Fatal("expected non-zero count") + } + if ts.add == 0 { + t.Fatal("expected non-zero count") + } + if ts.recv == 0 { + t.Fatal("expected non-zero count") + } + if ts.send == 0 { + t.Fatal("expected non-zero count") + } + if ts.join == 0 { + t.Fatal("expected non-zero count") + } + if ts.leave == 0 { + t.Fatal("expected non-zero count") + } + if ts.graft == 0 { + t.Fatal("expected non-zero count") + } + if ts.prune == 0 { + t.Fatal("expected non-zero count") + } +} + +func TestJSONTracer(t *testing.T) { + tracer, err := NewJSONTracer("/tmp/trace.out.json") + if err != nil { + t.Fatal(err) + } + + testWithTracer(t, tracer) + time.Sleep(time.Second) + tracer.Close() + + var stats traceStats + var evt pb.TraceEvent + + f, err := os.Open("/tmp/trace.out.json") + if err != nil { + t.Fatal(err) + } + defer f.Close() + + dec := json.NewDecoder(f) + for { + evt.Reset() + err := dec.Decode(&evt) + if err != nil { + break + } + + stats.process(&evt) + } + + stats.check(t) +} + +func TestPBTracer(t *testing.T) { + tracer, err := NewPBTracer("/tmp/trace.out.pb") + if err != nil { + t.Fatal(err) + } + + testWithTracer(t, tracer) + time.Sleep(time.Second) + tracer.Close() + + var stats traceStats + var evt pb.TraceEvent + + f, err := os.Open("/tmp/trace.out.pb") + if err != nil { + t.Fatal(err) + } + defer f.Close() + + r := protoio.NewDelimitedReader(f, 1<<20) + for { + evt.Reset() + err := r.ReadMsg(&evt) + if err != nil { + break + } + + stats.process(&evt) + } + + stats.check(t) +} + +type mockRemoteTracer struct { + mx sync.Mutex + ts traceStats +} + +func (mrt *mockRemoteTracer) handleStream(s network.Stream) { + defer s.Close() + + gzr, err := gzip.NewReader(s) + if err != nil { + panic(err) + } + + r := protoio.NewDelimitedReader(gzr, 1<<24) + + var batch pb.TraceEventBatch + for { + batch.Reset() + err := r.ReadMsg(&batch) + if err != nil { + if err != io.EOF { + s.Reset() + } + return + } + + mrt.mx.Lock() + for _, evt := range batch.GetBatch() { + mrt.ts.process(evt) + } + mrt.mx.Unlock() + } +} + +func (mrt *mockRemoteTracer) check(t *testing.T) { + mrt.mx.Lock() + defer mrt.mx.Unlock() + mrt.ts.check(t) +} + +func TestRemoteTracer(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + h1 := bhost.NewBlankHost(swarmt.GenSwarm(t, ctx)) + h2 := bhost.NewBlankHost(swarmt.GenSwarm(t, ctx)) + + mrt := &mockRemoteTracer{} + h1.SetStreamHandler(RemoteTracerProtoID, mrt.handleStream) + + tracer, err := NewRemoteTracer(ctx, h2, peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()}) + if err != nil { + t.Fatal(err) + } + + testWithTracer(t, tracer) + time.Sleep(time.Second) + tracer.Close() + + mrt.check(t) +} diff --git a/p2p/pubsub/validation_test.go b/p2p/pubsub/validation_test.go new file mode 100644 index 0000000000..68ed8e3655 --- /dev/null +++ b/p2p/pubsub/validation_test.go @@ -0,0 +1,333 @@ +package pubsub + +import ( + "bytes" + "context" + "fmt" + "sync" + "testing" + "time" + + "github.com/libp2p/go-libp2p-core/peer" +) + +func TestRegisterUnregisterValidator(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 1) + psubs := getPubsubs(ctx, hosts) + + err := psubs[0].RegisterTopicValidator("foo", func(context.Context, peer.ID, *Message) bool { + return true + }) + if err != nil { + t.Fatal(err) + } + + err = psubs[0].UnregisterTopicValidator("foo") + if err != nil { + t.Fatal(err) + } + + err = psubs[0].UnregisterTopicValidator("foo") + if err == nil { + t.Fatal("Unregistered bogus topic validator") + } +} + +func TestRegisterValidatorEx(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 3) + psubs := getPubsubs(ctx, hosts) + + err := psubs[0].RegisterTopicValidator("test", + Validator(func(context.Context, peer.ID, *Message) bool { + return true + })) + if err != nil { + t.Fatal(err) + } + + err = psubs[1].RegisterTopicValidator("test", + ValidatorEx(func(context.Context, peer.ID, *Message) ValidationResult { + return ValidationAccept + })) + if err != nil { + t.Fatal(err) + } + + err = psubs[2].RegisterTopicValidator("test", "bogus") + if err == nil { + t.Fatal("expected error") + } +} + +func TestValidate(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 2) + psubs := getPubsubs(ctx, hosts) + + connect(t, hosts[0], hosts[1]) + topic := "foobar" + + err := psubs[1].RegisterTopicValidator(topic, func(ctx context.Context, from peer.ID, msg *Message) bool { + return !bytes.Contains(msg.Data, []byte("illegal")) + }) + if err != nil { + t.Fatal(err) + } + + sub, err := psubs[1].Subscribe(topic) + if err != nil { + t.Fatal(err) + } + + time.Sleep(time.Millisecond * 50) + + msgs := []struct { + msg []byte + validates bool + }{ + {msg: []byte("this is a legal message"), validates: true}, + {msg: []byte("there also is nothing controversial about this message"), validates: true}, + {msg: []byte("openly illegal content will be censored"), validates: false}, + {msg: []byte("but subversive actors will use leetspeek to spread 1ll3g4l content"), validates: true}, + } + + for _, tc := range msgs { + err := psubs[0].Publish(topic, tc.msg) + if err != nil { + t.Fatal(err) + } + + select { + case msg := <-sub.ch: + if !tc.validates { + t.Log(msg) + t.Error("expected message validation to filter out the message") + } + case <-time.After(333 * time.Millisecond): + if tc.validates { + t.Error("expected message validation to accept the message") + } + } + } +} + +func TestValidate2(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 1) + psubs := getPubsubs(ctx, hosts) + + topic := "foobar" + + err := psubs[0].RegisterTopicValidator(topic, func(ctx context.Context, from peer.ID, msg *Message) bool { + return !bytes.Contains(msg.Data, []byte("illegal")) + }) + if err != nil { + t.Fatal(err) + } + + msgs := []struct { + msg []byte + validates bool + }{ + {msg: []byte("this is a legal message"), validates: true}, + {msg: []byte("there also is nothing controversial about this message"), validates: true}, + {msg: []byte("openly illegal content will be censored"), validates: false}, + {msg: []byte("but subversive actors will use leetspeek to spread 1ll3g4l content"), validates: true}, + } + + for _, tc := range msgs { + err := psubs[0].Publish(topic, tc.msg) + if tc.validates { + if err != nil { + t.Fatal(err) + } + } else { + if err == nil { + t.Fatal("expected validation to fail for this message") + } + } + } +} + +func TestValidateOverload(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + type msg struct { + msg []byte + validates bool + } + + tcs := []struct { + msgs []msg + + maxConcurrency int + }{ + { + maxConcurrency: 10, + msgs: []msg{ + {msg: []byte("this is a legal message"), validates: true}, + {msg: []byte("but subversive actors will use leetspeek to spread 1ll3g4l content"), validates: true}, + {msg: []byte("there also is nothing controversial about this message"), validates: true}, + {msg: []byte("also fine"), validates: true}, + {msg: []byte("still, all good"), validates: true}, + {msg: []byte("this is getting boring"), validates: true}, + {msg: []byte("foo"), validates: true}, + {msg: []byte("foobar"), validates: true}, + {msg: []byte("foofoo"), validates: true}, + {msg: []byte("barfoo"), validates: true}, + {msg: []byte("oh no!"), validates: false}, + }, + }, + { + maxConcurrency: 2, + msgs: []msg{ + {msg: []byte("this is a legal message"), validates: true}, + {msg: []byte("but subversive actors will use leetspeek to spread 1ll3g4l content"), validates: true}, + {msg: []byte("oh no!"), validates: false}, + }, + }, + } + + for _, tc := range tcs { + + hosts := getNetHosts(t, ctx, 2) + psubs := getPubsubs(ctx, hosts) + + connect(t, hosts[0], hosts[1]) + topic := "foobar" + + block := make(chan struct{}) + + err := psubs[1].RegisterTopicValidator(topic, + func(ctx context.Context, from peer.ID, msg *Message) bool { + <-block + return true + }, + WithValidatorConcurrency(tc.maxConcurrency)) + + if err != nil { + t.Fatal(err) + } + + sub, err := psubs[1].Subscribe(topic) + if err != nil { + t.Fatal(err) + } + + time.Sleep(time.Millisecond * 50) + + if len(tc.msgs) != tc.maxConcurrency+1 { + t.Fatalf("expected number of messages sent to be maxConcurrency+1. Got %d, expected %d", len(tc.msgs), tc.maxConcurrency+1) + } + + p := psubs[0] + + var wg sync.WaitGroup + wg.Add(1) + go func() { + for _, tmsg := range tc.msgs { + select { + case msg := <-sub.ch: + if !tmsg.validates { + t.Log(msg) + t.Error("expected message validation to drop the message because all validator goroutines are taken") + } + case <-time.After(time.Second): + if tmsg.validates { + t.Error("expected message validation to accept the message") + } + } + } + wg.Done() + }() + + for _, tmsg := range tc.msgs { + err := p.Publish(topic, tmsg.msg) + if err != nil { + t.Fatal(err) + } + } + + // wait a bit before unblocking the validator goroutines + time.Sleep(500 * time.Millisecond) + close(block) + + wg.Wait() + } +} + +func TestValidateAssortedOptions(t *testing.T) { + // this test adds coverage for various options that are not covered in other tests + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hosts := getNetHosts(t, ctx, 10) + psubs := getPubsubs(ctx, hosts, + WithValidateQueueSize(10), + WithValidateThrottle(10), + WithValidateWorkers(10)) + + sparseConnect(t, hosts) + + for _, psub := range psubs { + err := psub.RegisterTopicValidator("test1", + func(context.Context, peer.ID, *Message) bool { + return true + }, + WithValidatorTimeout(100*time.Millisecond)) + if err != nil { + t.Fatal(err) + } + + err = psub.RegisterTopicValidator("test2", + func(context.Context, peer.ID, *Message) bool { + return true + }, + WithValidatorInline(true)) + if err != nil { + t.Fatal(err) + } + } + + var subs1, subs2 []*Subscription + for _, ps := range psubs { + sub, err := ps.Subscribe("test1") + if err != nil { + t.Fatal(err) + } + subs1 = append(subs1, sub) + + sub, err = ps.Subscribe("test2") + if err != nil { + t.Fatal(err) + } + subs2 = append(subs2, sub) + } + + time.Sleep(time.Second) + + for i := 0; i < 10; i++ { + msg := []byte(fmt.Sprintf("message %d", i)) + + psubs[i].Publish("test1", msg) + for _, sub := range subs1 { + assertReceive(t, sub, msg) + } + + psubs[i].Publish("test2", msg) + for _, sub := range subs2 { + assertReceive(t, sub, msg) + } + } +} diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go index e0ab81968b..717cb4eb44 100644 --- a/p2p/pubsub_server.go +++ b/p2p/pubsub_server.go @@ -61,6 +61,7 @@ func (pss *PubSubServer) NewConn(peer *Peer, rw MsgReadWriter) chan error { conn.SetStream(stream) pss.Host().SetStream(peer.ID(), stream) + pss.Host().AddConn(peer.ID(), conn) pss.Host().NotifyAll(conn) return errCh } From b1c9d492ac3f7139deaffc1a597fa990d6b93b8f Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 24 Nov 2021 15:30:24 +0800 Subject: [PATCH 076/299] update for max consensussize --- core/blockchain_reactor.go | 26 +++-- core/genesis.go | 8 +- core/genesis_data.go | 13 +-- core/types/block.go | 22 +++- core/types/transaction.go | 1 - core/vm/delegate_reward_contract.go | 2 +- core/vm/staking_contract.go | 20 ++-- eth/backend.go | 9 +- p2p/pubsub/discovery.go | 6 +- x/gov/gov_params.go | 19 ++-- x/gov/proposals.go | 15 ++- x/plugin/gov_plugin.go | 10 +- x/plugin/issue1625_patch.go | 10 +- x/plugin/restricting_plugin.go | 13 ++- x/plugin/reward_plugin.go | 41 +++---- x/plugin/slashing_plugin.go | 65 +++++------ x/plugin/staking_plugin.go | 165 +++++++++++++++------------- x/xcom/common_config.go | 26 ++--- x/xutil/calculate.go | 110 +++++++------------ 19 files changed, 297 insertions(+), 284 deletions(-) diff --git a/core/blockchain_reactor.go b/core/blockchain_reactor.go index 58021cf9fb..3c7ac0d890 100644 --- a/core/blockchain_reactor.go +++ b/core/blockchain_reactor.go @@ -25,6 +25,8 @@ import ( "math/big" "sync" + "github.com/AlayaNetwork/Alaya-Go/x/gov" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/common" @@ -45,17 +47,18 @@ import ( ) type BlockChainReactor struct { - vh *handler.VrfHandler - eventMux *event.TypeMux - bftResultSub *event.TypeMuxSubscription - basePluginMap map[int]plugin.BasePlugin // xxPlugin container - beginRule []int // Order rules for xxPlugins called in BeginBlocker - endRule []int // Order rules for xxPlugins called in EndBlocker - validatorMode string // mode: static, inner, ppos - NodeId enode.IDv0 // The nodeId of current node - exitCh chan chan struct{} // Used to receive an exit signal - exitOnce sync.Once - chainID *big.Int + vh *handler.VrfHandler + eventMux *event.TypeMux + bftResultSub *event.TypeMuxSubscription + basePluginMap map[int]plugin.BasePlugin // xxPlugin container + beginRule []int // Order rules for xxPlugins called in BeginBlocker + endRule []int // Order rules for xxPlugins called in EndBlocker + validatorMode string // mode: static, inner, ppos + NodeId enode.IDv0 // The nodeId of current node + exitCh chan chan struct{} // Used to receive an exit signal + exitOnce sync.Once + chainID *big.Int + acurrentVersion uint32 } var ( @@ -267,6 +270,7 @@ func (bcr *BlockChainReactor) BeginBlocker(header *types.Header, state xcom.Stat return err } + header.SetActiveVersion(gov.GetCurrentActiveVersion(state)) for _, pluginRule := range bcr.beginRule { if plugin, ok := bcr.basePluginMap[pluginRule]; ok { if err := plugin.BeginBlock(blockHash, header, state); nil != err { diff --git a/core/genesis.go b/core/genesis.go index 0c3a2a60fc..a24d2c9e30 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -165,7 +165,7 @@ func SetupGenesisBlock(db ethdb.Database, snapshotBaseDB snapshotdb.BaseDB, gene } // check EconomicModel configuration - if err := xcom.CheckEconomicModel(); nil != err { + if err := xcom.CheckEconomicModel(genesis.Config.GenesisVersion); nil != err { log.Error("Failed to check economic config", "err", err) return nil, common.Hash{}, err } @@ -190,7 +190,7 @@ func SetupGenesisBlock(db ethdb.Database, snapshotBaseDB snapshotdb.BaseDB, gene } // check EconomicModel configuration - if err := xcom.CheckEconomicModel(); nil != err { + if err := xcom.CheckEconomicModel(genesis.Config.GenesisVersion); nil != err { log.Error("Failed to check economic config", "err", err) return nil, common.Hash{}, err } @@ -252,6 +252,8 @@ func SetupGenesisBlock(db ethdb.Database, snapshotBaseDB snapshotdb.BaseDB, gene xcom.ResetEconomicDefaultConfig(ecCfg) xcom.ResetEconomicExtendConfig(eceCfg) + //update chain config here + // Special case: don't change the existing config of a non-mainnet chain if no new // config is supplied. These chains would get AllProtocolChanges (and a compat error) // if we just continued here. @@ -352,7 +354,7 @@ func (g *Genesis) InitGenesisAndSetEconomicConfig(path string) error { xcom.SetPerRoundBlocks(uint64(g.Config.Cbft.Amount)) // check EconomicModel configuration - if err := xcom.CheckEconomicModel(); nil != err { + if err := xcom.CheckEconomicModel(g.Config.GenesisVersion); nil != err { return fmt.Errorf("Failed CheckEconomicModel configuration: %v", err) } return nil diff --git a/core/genesis_data.go b/core/genesis_data.go index 6907d9a76c..553d910d03 100644 --- a/core/genesis_data.go +++ b/core/genesis_data.go @@ -29,12 +29,9 @@ func genesisStakingData(prevHash common.Hash, snapdb snapshotdb.BaseDB, g *Genes log.Info("Init staking snapshotdb data, validatorMode is not ppos") return prevHash, nil } - - var length int - - if int(xcom.MaxConsensusVals()) <= len(g.Config.Cbft.InitialNodes) { - length = int(xcom.MaxConsensusVals()) - } else { + activerVersion := gov.GetCurrentActiveVersion(stateDB) + length := int(xcom.MaxConsensusVals(activerVersion)) + if length > len(g.Config.Cbft.InitialNodes) { length = len(g.Config.Cbft.InitialNodes) } @@ -179,7 +176,7 @@ func genesisStakingData(prevHash common.Hash, snapdb snapshotdb.BaseDB, g *Genes // build epoch validators indexInfo verifierIndex := &staking.ValArrIndex{ Start: 1, - End: xutil.CalcBlocksEachEpoch(), + End: xutil.CalcBlocksEachEpoch(activerVersion), } epochIndexArr := make(staking.ValArrIndexQueue, 0) epochIndexArr = append(epochIndexArr, verifierIndex) @@ -212,7 +209,7 @@ func genesisStakingData(prevHash common.Hash, snapdb snapshotdb.BaseDB, g *Genes // build current round validators indexInfo curr_indexInfo := &staking.ValArrIndex{ Start: 1, - End: xutil.ConsensusSize(), + End: xcom.ConsensusSize(activerVersion), } roundIndexArr := make(staking.ValArrIndexQueue, 0) roundIndexArr = append(roundIndexArr, pre_indexInfo) diff --git a/core/types/block.go b/core/types/block.go index 4ca2c8285b..78f73add65 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -89,9 +89,10 @@ type Header struct { Nonce BlockNonce `json:"nonce" gencodec:"required"` // caches - sealHash atomic.Value `json:"-" rlp:"-"` - hash atomic.Value `json:"-" rlp:"-"` - publicKey atomic.Value `json:"-" rlp:"-"` + sealHash atomic.Value `json:"-" rlp:"-"` + hash atomic.Value `json:"-" rlp:"-"` + publicKey atomic.Value `json:"-" rlp:"-"` + activeVersion uint32 `json:"-" rlp:"-"` } // MarshalJSON2 marshals as JSON. @@ -207,6 +208,14 @@ func (h *Header) _sealHash() (hash common.Hash) { return hash } +func (h *Header) SetActiveVersion(version uint32) { + h.activeVersion = version +} + +func (h *Header) GetActiveVersion() uint32 { + return h.activeVersion +} + // Size returns the approximate memory used by all internal contents. It is used // to approximate and limit the memory consumption of various caches. func (h *Header) Size() common.StorageSize { @@ -256,7 +265,7 @@ type Body struct { ExtraData []byte } -// Block represents an entire block in the Ethereum blockchain. +// Block represents an entire block in the Alaya blockchain. type Block struct { header *Header transactions Transactions @@ -351,6 +360,9 @@ func CopyHeader(h *Header) *Header { cpy.Extra = make([]byte, len(h.Extra)) copy(cpy.Extra, h.Extra) } + if h.activeVersion != 0 { + cpy.activeVersion = h.activeVersion + } return &cpy } @@ -416,6 +428,8 @@ func (b *Block) Extra() []byte { return common.CopyBytes(b.header.Ext func (b *Block) Header() *Header { return CopyHeader(b.header) } +func (b *Block) ActiveVersion() uint32 { return b.header.activeVersion } + // Body returns the non-header content of the block. func (b *Block) Body() *Body { return &Body{b.transactions, b.extraData} } diff --git a/core/types/transaction.go b/core/types/transaction.go index 67727b19dd..a10e889fea 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -295,7 +295,6 @@ func (tx *Transaction) FromAddr(signer Signer) common.Address { if err != nil { return common.Address{} } - //log.Debug("Sender cache2", "add", addr, "hash", tx.Hash(), "poi", fmt.Sprintf("%p", tx)) tx.from.Store(sigCache{signer: signer, from: addr}) return addr } diff --git a/core/vm/delegate_reward_contract.go b/core/vm/delegate_reward_contract.go index c1d6807f8f..7f9e964499 100644 --- a/core/vm/delegate_reward_contract.go +++ b/core/vm/delegate_reward_contract.go @@ -119,7 +119,7 @@ func (rc *DelegateRewardContract) withdrawDelegateReward() ([]byte, error) { return nil, ErrOutOfGas } - currentEpoch := xutil.CalculateEpoch(blockNum.Uint64()) + currentEpoch := xutil.CalculateEpoch(blockNum.Uint64(), gov.GetCurrentActiveVersion(state)) unCalEpoch := 0 delegationInfoWithRewardPerList := make([]*plugin.DelegationInfoWithRewardPerList, 0) for _, stakingNode := range list { diff --git a/core/vm/staking_contract.go b/core/vm/staking_contract.go index 689646cda0..fa5e4d42ff 100644 --- a/core/vm/staking_contract.go +++ b/core/vm/staking_contract.go @@ -267,7 +267,7 @@ func (stkc *StakingContract) createStaking(typ uint16, benefitAddress common.Add RestrictingPlanHes: new(big.Int).SetInt64(0), RewardPer: rewardPer, NextRewardPer: rewardPer, - RewardPerChangeEpoch: uint32(xutil.CalculateEpoch(blockNumber.Uint64())), + RewardPerChangeEpoch: uint32(xutil.CalculateEpoch(blockNumber.Uint64(), gov.GetCurrentActiveVersion(state))), DelegateRewardTotal: new(big.Int).SetInt64(0), } @@ -393,7 +393,7 @@ func (stkc *StakingContract) editCandidate(benefitAddress *common.Address, nodeI } } - currentEpoch := uint32(xutil.CalculateEpoch(blockNumber.Uint64())) + currentEpoch := uint32(xutil.CalculateEpoch(blockNumber.Uint64(), gov.GetCurrentActiveVersion(state))) if gov.Gte0140VersionState(state) { if nodeName != nil { @@ -710,7 +710,7 @@ func (stkc *StakingContract) delegate(typ uint16, nodeId enode.IDv0, amount *big } var delegateRewardPerList []*reward.DelegateRewardPer if del.DelegateEpoch > 0 { - delegateRewardPerList, err = plugin.RewardMgrInstance().GetDelegateRewardPerList(blockHash, canBase.NodeId, canBase.StakingBlockNum, uint64(del.DelegateEpoch), xutil.CalculateEpoch(blockNumber.Uint64())-1) + delegateRewardPerList, err = plugin.RewardMgrInstance().GetDelegateRewardPerList(blockHash, canBase.NodeId, canBase.StakingBlockNum, uint64(del.DelegateEpoch), xutil.CalculateEpoch(blockNumber.Uint64(), gov.GetCurrentActiveVersion(state))-1) if snapshotdb.NonDbNotFoundErr(err) { log.Error("Failed to delegate by GetDelegateRewardPerList", "txHash", txHash, "blockNumber", blockNumber, "err", err) return nil, err @@ -800,7 +800,7 @@ func (stkc *StakingContract) withdrewDelegation(stakingBlockNum uint64, nodeId e } } - delegateRewardPerList, err := plugin.RewardMgrInstance().GetDelegateRewardPerList(blockHash, nodeId, stakingBlockNum, uint64(del.DelegateEpoch), xutil.CalculateEpoch(blockNumber.Uint64())-1) + delegateRewardPerList, err := plugin.RewardMgrInstance().GetDelegateRewardPerList(blockHash, nodeId, stakingBlockNum, uint64(del.DelegateEpoch), xutil.CalculateEpoch(blockNumber.Uint64(), gov.GetCurrentActiveVersion(state))-1) if snapshotdb.NonDbNotFoundErr(err) { log.Error("Failed to delegate by GetDelegateRewardPerList", "txHash", txHash, "blockNumber", blockNumber, "err", err) return nil, err @@ -879,8 +879,9 @@ func (stkc *StakingContract) getValidatorList() ([]byte, error) { blockNumber := stkc.Evm.BlockNumber blockHash := stkc.Evm.BlockHash + state := stkc.Evm.StateDB - arr, err := stkc.Plugin.GetValidatorList(blockHash, blockNumber.Uint64(), plugin.CurrentRound, plugin.QueryStartNotIrr) + arr, err := stkc.Plugin.GetValidatorList(blockHash, blockNumber.Uint64(), plugin.CurrentRound, plugin.QueryStartNotIrr, gov.GetCurrentActiveVersion(state)) if snapshotdb.NonDbNotFoundErr(err) { return callResultHandler(stkc.Evm, "getValidatorList", @@ -900,8 +901,9 @@ func (stkc *StakingContract) getCandidateList() ([]byte, error) { blockNumber := stkc.Evm.BlockNumber blockHash := stkc.Evm.BlockHash + state := stkc.Evm.StateDB - arr, err := stkc.Plugin.GetCandidateList(blockHash, blockNumber.Uint64()) + arr, err := stkc.Plugin.GetCandidateList(blockHash, blockNumber.Uint64(), state) if snapshotdb.NonDbNotFoundErr(err) { return callResultHandler(stkc.Evm, "getCandidateList", arr, staking.ErrGetCandidateList.Wrap(err.Error())), nil @@ -939,8 +941,9 @@ func (stkc *StakingContract) getDelegateInfo(stakingBlockNum uint64, delAddr com blockNumber := stkc.Evm.BlockNumber blockHash := stkc.Evm.BlockHash + state := stkc.Evm.StateDB - del, err := stkc.Plugin.GetDelegateExCompactInfo(blockHash, blockNumber.Uint64(), delAddr, nodeId, stakingBlockNum) + del, err := stkc.Plugin.GetDelegateExCompactInfo(blockHash, blockNumber.Uint64(), delAddr, nodeId, stakingBlockNum, state) if snapshotdb.NonDbNotFoundErr(err) { return callResultHandler(stkc.Evm, fmt.Sprintf("getDelegateInfo, delAddr: %s, nodeId: %s, stakingBlockNumber: %d", delAddr, nodeId, stakingBlockNum), @@ -961,13 +964,14 @@ func (stkc *StakingContract) getDelegateInfo(stakingBlockNum uint64, delAddr com func (stkc *StakingContract) getCandidateInfo(nodeId enode.IDv0) ([]byte, error) { blockNumber := stkc.Evm.BlockNumber blockHash := stkc.Evm.BlockHash + state := stkc.Evm.StateDB canAddr, err := xutil.NodeId2Addr(nodeId) if nil != err { return callResultHandler(stkc.Evm, fmt.Sprintf("getCandidateInfo, nodeId: %s", nodeId), nil, staking.ErrQueryCandidateInfo.Wrap(err.Error())), nil } - can, err := stkc.Plugin.GetCandidateCompactInfo(blockHash, blockNumber.Uint64(), canAddr) + can, err := stkc.Plugin.GetCandidateCompactInfo(blockHash, blockNumber.Uint64(), canAddr, state) if snapshotdb.NonDbNotFoundErr(err) { return callResultHandler(stkc.Evm, fmt.Sprintf("getCandidateInfo, nodeId: %s", nodeId), can, staking.ErrQueryCandidateInfo.Wrap(err.Error())), nil diff --git a/eth/backend.go b/eth/backend.go index 3ba9820728..f820a9bafb 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -96,7 +96,7 @@ type Ethereum struct { gasPrice *big.Int networkID uint64 netRPCService *ethapi.PublicNetAPI - lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) + lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) } func (s *Ethereum) AddLesServer(ls LesServer) { @@ -333,8 +333,13 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { handlePlugin(reactor) agency = reactor + currentstate, err := blockChainCache.State() + if err != nil { + return nil, err + } + //register Govern parameter verifiers - gov.RegisterGovernParamVerifiers() + gov.RegisterGovernParamVerifiers(gov.GetCurrentActiveVersion(currentstate)) } if err := recoverSnapshotDB(blockChainCache); err != nil { diff --git a/p2p/pubsub/discovery.go b/p2p/pubsub/discovery.go index 8874aff49b..1962b5e33b 100644 --- a/p2p/pubsub/discovery.go +++ b/p2p/pubsub/discovery.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/prometheus/common/log" + "github.com/AlayaNetwork/Alaya-Go/log" "github.com/libp2p/go-libp2p-core/discovery" "github.com/libp2p/go-libp2p-core/peer" @@ -190,7 +190,7 @@ func (d *discover) Advertise(topic string) { go func() { next, err := d.discovery.Advertise(advertisingCtx, topic) if err != nil { - log.Warnf("bootstrap: error providing rendezvous for %s: %s", topic, err.Error()) + log.Warn("bootstrap: error providing rendezvous for %s: %s", "topic", topic, "err", err) if next == 0 { next = discoveryAdvertiseRetryInterval } @@ -204,7 +204,7 @@ func (d *discover) Advertise(topic string) { case <-t.C: next, err = d.discovery.Advertise(advertisingCtx, topic) if err != nil { - log.Warnf("bootstrap: error providing rendezvous for %s: %s", topic, err.Error()) + log.Warn("bootstrap: error providing rendezvous for %s: %s", "topic", topic, "err", err) if next == 0 { next = discoveryAdvertiseRetryInterval } diff --git a/x/gov/gov_params.go b/x/gov/gov_params.go index 6687e5d645..924d5fb94c 100644 --- a/x/gov/gov_params.go +++ b/x/gov/gov_params.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package gov import ( @@ -38,15 +37,15 @@ var ( var governParam []*GovernParam -func queryInitParam() []*GovernParam { +func queryInitParam(version uint32) []*GovernParam { initGovParam.Do(func() { log.Info("Init Govern parameters ...") - governParam = initParam() + governParam = initParam(version) }) return governParam } -func initParam() []*GovernParam { +func initParam(version uint32) []*GovernParam { return []*GovernParam{ /** @@ -93,7 +92,7 @@ func initParam() []*GovernParam { { ParamItem: &ParamItem{ModuleStaking, KeyMaxValidators, - fmt.Sprintf("maximum amount of validator, range: [%d, %d]", xcom.MaxConsensusVals(), xcom.CeilMaxValidators)}, + fmt.Sprintf("maximum amount of validator, range: [%d, %d]", xcom.MaxConsensusVals(version), xcom.CeilMaxValidators)}, ParamValue: &ParamValue{"", strconv.Itoa(int(xcom.MaxValidators())), 0}, ParamVerifier: func(blockNumber uint64, blockHash common.Hash, value string) error { @@ -248,7 +247,7 @@ func initParam() []*GovernParam { { ParamItem: &ParamItem{ModuleSlashing, KeyZeroProduceCumulativeTime, - fmt.Sprintf("Time range for recording the number of behaviors of zero production blocks, range: [ZeroProduceNumberThreshold, %d]", int(xcom.EpochSize()))}, + fmt.Sprintf("Time range for recording the number of behaviors of zero production blocks, range: [ZeroProduceNumberThreshold, %d]", int(xcom.EpochSize(version)))}, ParamValue: &ParamValue{"", strconv.Itoa(int(xcom.ZeroProduceCumulativeTime())), 0}, ParamVerifier: func(blockNumber uint64, blockHash common.Hash, value string) error { @@ -261,7 +260,7 @@ func initParam() []*GovernParam { if nil != err { return err } - if err := xcom.CheckZeroProduceCumulativeTime(uint16(roundNumber), numberThreshold); nil != err { + if err := xcom.CheckZeroProduceCumulativeTime(uint16(roundNumber), numberThreshold, version); nil != err { return err } return nil @@ -400,7 +399,7 @@ var ParamVerifierMap = make(map[string]ParamVerifier) func InitGenesisGovernParam(prevHash common.Hash, snapDB snapshotdb.BaseDB, genesisVersion uint32) (common.Hash, error) { var paramItemList []*ParamItem - initParamList := queryInitParam() + initParamList := queryInitParam(genesisVersion) if genesisVersion >= params.FORKVERSION_0_14_0 { initParamList = append(initParamList, init0140VersionParam()...) @@ -438,8 +437,8 @@ func InitGenesisGovernParam(prevHash common.Hash, snapDB snapshotdb.BaseDB, gene return lastHash, nil } -func RegisterGovernParamVerifiers() { - for _, param := range queryInitParam() { +func RegisterGovernParamVerifiers(version uint32) { + for _, param := range queryInitParam(version) { RegGovernParamVerifier(param.ParamItem.Module, param.ParamItem.Name, param.ParamVerifier) } if uint32(params.VersionMajor<<16|params.VersionMinor<<8|params.VersionPatch) >= params.FORKVERSION_0_14_0 { diff --git a/x/gov/proposals.go b/x/gov/proposals.go index a38db5448e..d243980ef8 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -149,8 +149,8 @@ func (tp *TextProposal) Verify(submitBlock uint64, blockHash common.Hash, state if err := verifyBasic(tp, blockHash, state); err != nil { return err } - - endVotingBlock := xutil.CalEndVotingBlock(submitBlock, xutil.EstimateConsensusRoundsForGov(xcom.TextProposalVote_DurationSeconds())) + acVersion := GetCurrentActiveVersion(state) + endVotingBlock := xutil.CalEndVotingBlock(submitBlock, xutil.EstimateConsensusRoundsForGov(xcom.TextProposalVote_DurationSeconds(), acVersion), acVersion) if endVotingBlock <= submitBlock { log.Error("the end-voting-block is lower than submit-block. Please check configuration") return common.InternalError @@ -229,7 +229,9 @@ func (vp *VersionProposal) Verify(submitBlock uint64, blockHash common.Hash, sta return EndVotingRoundsTooSmall } - if vp.EndVotingRounds > xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds()) { + acVersion := GetCurrentActiveVersion(state) + + if vp.EndVotingRounds > xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), acVersion) { return EndVotingRoundsTooLarge } @@ -237,7 +239,7 @@ func (vp *VersionProposal) Verify(submitBlock uint64, blockHash common.Hash, sta return err } - endVotingBlock := xutil.CalEndVotingBlock(submitBlock, vp.EndVotingRounds) + endVotingBlock := xutil.CalEndVotingBlock(submitBlock, vp.EndVotingRounds, acVersion) if endVotingBlock <= submitBlock { log.Error("the end-voting-block is lower than submit-block. Please check configuration") return common.InternalError @@ -340,7 +342,7 @@ func (cp *CancelProposal) Verify(submitBlock uint64, blockHash common.Hash, stat return EndVotingRoundsTooSmall } - endVotingBlock := xutil.CalEndVotingBlock(submitBlock, cp.EndVotingRounds) + endVotingBlock := xutil.CalEndVotingBlock(submitBlock, cp.EndVotingRounds, GetCurrentActiveVersion(state)) if endVotingBlock <= submitBlock { log.Error("the end-voting-block is lower than submit-block. Please check configuration") return common.InternalError @@ -474,7 +476,8 @@ func (pp *ParamProposal) Verify(submitBlock uint64, blockHash common.Hash, state var voteDuration = xcom.ParamProposalVote_DurationSeconds() - endVotingBlock := xutil.EstimateEndVotingBlockForParaProposal(submitBlock, voteDuration) + acVersion := GetCurrentActiveVersion(state) + endVotingBlock := xutil.EstimateEndVotingBlockForParaProposal(submitBlock, voteDuration, acVersion) if endVotingBlock <= submitBlock { log.Error("the end-voting-block is lower than submit-block. Please check configuration") return common.InternalError diff --git a/x/plugin/gov_plugin.go b/x/plugin/gov_plugin.go index fa31edeb63..0f77c083ec 100644 --- a/x/plugin/gov_plugin.go +++ b/x/plugin/gov_plugin.go @@ -66,11 +66,11 @@ func (govPlugin *GovPlugin) BeginBlock(blockHash common.Hash, header *types.Head var blockNumber = header.Number.Uint64() //log.Debug("call BeginBlock()", "blockNumber", blockNumber, "blockHash", blockHash) - if !xutil.IsBeginOfConsensus(blockNumber) { + if !xutil.IsBeginOfConsensus(blockNumber, header.GetActiveVersion()) { return nil } - if xutil.IsBeginOfEpoch(blockNumber) { + if xutil.IsBeginOfEpoch(blockNumber, header.GetActiveVersion()) { if err := accuVerifiersAtBeginOfSettlement(blockHash, blockNumber); err != nil { log.Error("accumulates all distinct verifiers for voting proposal failed.", "blockNumber", blockNumber, "err", err) return err @@ -159,7 +159,7 @@ func (govPlugin *GovPlugin) BeginBlock(blockHash common.Hash, header *types.Head } log.Info("Successfully upgraded the new version 0.16.0", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveProposalID", preActiveVersionProposalID) } - + header.SetActiveVersion(versionProposal.NewVersion) log.Info("version proposal is active", "blockNumber", blockNumber, "proposalID", versionProposal.ProposalID, "newVersion", versionProposal.NewVersion, "newVersionString", xutil.ProgramVersion2Str(versionProposal.NewVersion)) } } @@ -175,9 +175,9 @@ func (govPlugin *GovPlugin) EndBlock(blockHash common.Hash, header *types.Header //param proposal's end voting block is end of Epoch isEndOfEpoch := false isElection := false - if xutil.IsElection(blockNumber) { + if xutil.IsElection(blockNumber, header.GetActiveVersion()) { isElection = true - } else if xutil.IsEndOfEpoch(blockNumber) { + } else if xutil.IsEndOfEpoch(blockNumber, header.GetActiveVersion()) { isEndOfEpoch = true } else { return nil diff --git a/x/plugin/issue1625_patch.go b/x/plugin/issue1625_patch.go index d0ccf570a3..55f25b48cf 100644 --- a/x/plugin/issue1625_patch.go +++ b/x/plugin/issue1625_patch.go @@ -22,6 +22,8 @@ import ( "math/big" "sort" + "github.com/AlayaNetwork/Alaya-Go/x/gov" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/params" @@ -167,7 +169,7 @@ func (a *FixIssue1625Plugin) rollBackDel(hash common.Hash, blockNumber *big.Int, dels = append(dels, delInfo) } sort.Sort(dels) - epoch := xutil.CalculateEpoch(blockNumber.Uint64()) + epoch := xutil.CalculateEpoch(blockNumber.Uint64(), gov.GetCurrentActiveVersion(state)) stakingdb := staking.NewStakingDBWithDB(a.sdb) for i := 0; i < len(dels); i++ { if err := dels[i].handleDelegate(hash, blockNumber, epoch, account, amount, state, stakingdb); err != nil { @@ -219,7 +221,7 @@ func (a *FixIssue1625Plugin) rollBackStaking(hash common.Hash, blockNumber *big. stakings = append(stakings, newIssue1625AccountStakingInfo(&candidate, canAddr)) } } - epoch := xutil.CalculateEpoch(blockNumber.Uint64()) + epoch := xutil.CalculateEpoch(blockNumber.Uint64(), gov.GetCurrentActiveVersion(state)) sort.Sort(stakings) for i := 0; i < len(stakings); i++ { @@ -383,7 +385,7 @@ func (a *issue1625AccountStakingInfo) withdrewStaking(hash common.Hash, epoch ui if a.candidate.Released.Cmp(common.Big0) > 0 || a.candidate.RestrictingPlan.Cmp(common.Big0) > 0 { //如果质押处于生效期,需要锁定 - if err := stk.addErrorAccountUnStakeItem(blockNumber.Uint64(), hash, a.candidate.NodeId, a.canAddr, a.candidate.StakingBlockNum); nil != err { + if err := stk.addErrorAccountUnStakeItem(blockNumber.Uint64(), hash, a.candidate.NodeId, a.canAddr, a.candidate.StakingBlockNum, gov.GetCurrentActiveVersion(state)); nil != err { return err } // sub the account staking Reference Count @@ -503,7 +505,7 @@ func (a *issue1625AccountDelInfo) handleDelegate(hash common.Hash, blockNumber * log.Debug("fix issue 1625 for delegate ,can begin info", "account", delAddr, "candidate", a.nodeID.String(), "share", a.candidate.Shares, "candidate.del", a.candidate.DelegateTotal, "candidate.delhes", a.candidate.DelegateTotalHes, "canValid", a.candidate.IsValid()) } //先计算委托收益 - delegateRewardPerList, err := RewardMgrInstance().GetDelegateRewardPerList(hash, a.nodeID, a.stakingBlock, uint64(a.del.DelegateEpoch), xutil.CalculateEpoch(blockNumber.Uint64())-1) + delegateRewardPerList, err := RewardMgrInstance().GetDelegateRewardPerList(hash, a.nodeID, a.stakingBlock, uint64(a.del.DelegateEpoch), xutil.CalculateEpoch(blockNumber.Uint64(), gov.GetCurrentActiveVersion(state))-1) if snapshotdb.NonDbNotFoundErr(err) { return err } diff --git a/x/plugin/restricting_plugin.go b/x/plugin/restricting_plugin.go index 68f996f6f8..7dff1dbcc4 100644 --- a/x/plugin/restricting_plugin.go +++ b/x/plugin/restricting_plugin.go @@ -74,8 +74,8 @@ func (rp *RestrictingPlugin) BeginBlock(blockHash common.Hash, head *types.Heade // EndBlock invoke releaseRestricting func (rp *RestrictingPlugin) EndBlock(blockHash common.Hash, head *types.Header, state xcom.StateDB) error { - if xutil.IsEndOfEpoch(head.Number.Uint64()) { - expect := xutil.CalculateEpoch(head.Number.Uint64()) + if xutil.IsEndOfEpoch(head.Number.Uint64(), head.GetActiveVersion()) { + expect := xutil.CalculateEpoch(head.Number.Uint64(), head.GetActiveVersion()) rp.log.Info("begin to release restricting plan", "currentHash", blockHash, "currBlock", head.Number, "expectBlock", head.Number, "expectEpoch", expect) if err := rp.releaseRestricting(expect, state); err != nil { return err @@ -95,7 +95,7 @@ func (rp *RestrictingPlugin) Confirmed(nodeId enode.IDv0, block *types.Block) er func (rp *RestrictingPlugin) mergeAmount(state xcom.StateDB, blockNum uint64, blockHash common.Hash, plans []restricting.RestrictingPlan) (*big.Int, map[uint64]*big.Int, error) { // latest is the epoch of a settlement block closest to current block - latestEpoch := xutil.CalculateEpoch(blockNum) + latestEpoch := xutil.CalculateEpoch(blockNum, gov.GetCurrentActiveVersion(state)) totalAmount := new(big.Int) @@ -706,10 +706,11 @@ func (rp *RestrictingPlugin) getRestrictingInfoToReturn(account common.Address, result restricting.Result ) totalLeft := new(big.Int) + acVersion := gov.GetCurrentActiveVersion(state) for i := 0; i < len(info.ReleaseList); i++ { epoch := info.ReleaseList[i] _, bAmount := rp.getReleaseAmount(state, epoch, account) - plan.Height = GetBlockNumberByEpoch(epoch) + plan.Height = GetBlockNumberByEpoch(epoch, acVersion) plan.Amount = (*hexutil.Big)(bAmount) totalLeft.Add(totalLeft, bAmount) plans = append(plans, plan) @@ -737,6 +738,6 @@ func (rp *RestrictingPlugin) GetRestrictingInfo(account common.Address, state xc return rp.getRestrictingInfoToReturn(account, state) } -func GetBlockNumberByEpoch(epoch uint64) uint64 { - return epoch * xutil.CalcBlocksEachEpoch() +func GetBlockNumberByEpoch(epoch uint64, version uint32) uint64 { + return epoch * xutil.CalcBlocksEachEpoch(version) } diff --git a/x/plugin/reward_plugin.go b/x/plugin/reward_plugin.go index 49fe0e72c8..db2a2c8dfe 100644 --- a/x/plugin/reward_plugin.go +++ b/x/plugin/reward_plugin.go @@ -120,13 +120,13 @@ func (rmp *RewardMgrPlugin) EndBlock(blockHash common.Hash, head *types.Header, return err } - if xutil.IsEndOfEpoch(blockNumber) { - verifierList, err := rmp.AllocateStakingReward(blockNumber, blockHash, stakingReward, state) + if xutil.IsEndOfEpoch(blockNumber, head.GetActiveVersion()) { + verifierList, err := rmp.AllocateStakingReward(head, blockHash, stakingReward, state) if err != nil { return err } - if err := rmp.HandleDelegatePerReward(blockHash, blockNumber, verifierList, state); err != nil { + if err := rmp.HandleDelegatePerReward(head, blockHash, verifierList, state); err != nil { return err } @@ -213,17 +213,17 @@ func (rmp *RewardMgrPlugin) increaseIssuance(thisYear, lastYear uint32, state xc } // AllocateStakingReward used for reward staking at the settle block -func (rmp *RewardMgrPlugin) AllocateStakingReward(blockNumber uint64, blockHash common.Hash, sreward *big.Int, state xcom.StateDB) ([]*staking.Candidate, error) { +func (rmp *RewardMgrPlugin) AllocateStakingReward(head *types.Header, blockHash common.Hash, sreward *big.Int, state xcom.StateDB) ([]*staking.Candidate, error) { - log.Info("Allocate staking reward start", "blockNumber", blockNumber, "hash", blockHash, - "epoch", xutil.CalculateEpoch(blockNumber), "reward", sreward) - verifierList, err := rmp.stakingPlugin.GetVerifierCandidateInfo(blockHash, blockNumber) + log.Info("Allocate staking reward start", "blockNumber", head.Number.Uint64(), "hash", blockHash, + "epoch", xutil.CalculateEpoch(head.Number.Uint64(), head.GetActiveVersion()), "reward", sreward) + verifierList, err := rmp.stakingPlugin.GetVerifierCandidateInfo(blockHash, head.Number.Uint64()) if err != nil { - log.Error("Failed to AllocateStakingReward: call GetVerifierList is failed", "blockNumber", blockNumber, "hash", blockHash, "err", err) + log.Error("Failed to AllocateStakingReward: call GetVerifierList is failed", "blockNumber", head.Number.Uint64(), "hash", blockHash, "err", err) return nil, err } if err := rmp.rewardStakingByValidatorList(state, verifierList, sreward); err != nil { - log.Error("reward staking by validator list fail", "err", err, "bn", blockNumber, "bh", blockHash) + log.Error("reward staking by validator list fail", "err", err, "bn", head.Number.Uint64(), "bh", blockHash) return nil, err } @@ -245,8 +245,8 @@ func (rmp *RewardMgrPlugin) ReturnDelegateReward(address common.Address, amount return nil } -func (rmp *RewardMgrPlugin) HandleDelegatePerReward(blockHash common.Hash, blockNumber uint64, list []*staking.Candidate, state xcom.StateDB) error { - currentEpoch := xutil.CalculateEpoch(blockNumber) +func (rmp *RewardMgrPlugin) HandleDelegatePerReward(head *types.Header, blockHash common.Hash, list []*staking.Candidate, state xcom.StateDB) error { + currentEpoch := xutil.CalculateEpoch(head.Number.Uint64(), head.GetActiveVersion()) for _, verifier := range list { if verifier.CurrentEpochDelegateReward.Cmp(common.Big0) == 0 { continue @@ -254,13 +254,13 @@ func (rmp *RewardMgrPlugin) HandleDelegatePerReward(blockHash common.Hash, block if verifier.DelegateTotal.Cmp(common.Big0) == 0 { log.Debug("handleDelegatePerReward return delegateReward", "epoch", currentEpoch, "reward", verifier.CurrentEpochDelegateReward, "add", verifier.BenefitAddress) if err := rmp.ReturnDelegateReward(verifier.BenefitAddress, verifier.CurrentEpochDelegateReward, state); err != nil { - log.Error("HandleDelegatePerReward ReturnDelegateReward fail", "err", err, "blockNumber", blockNumber) + log.Error("HandleDelegatePerReward ReturnDelegateReward fail", "err", err, "blockNumber", head.Number.Uint64()) } } else { per := reward.NewDelegateRewardPer(currentEpoch, verifier.CurrentEpochDelegateReward, verifier.DelegateTotal) if err := AppendDelegateRewardPer(blockHash, verifier.NodeId, verifier.StakingBlockNum, per, rmp.db); err != nil { - log.Error("call handleDelegatePerReward fail AppendDelegateRewardPer", "blockNumber", blockNumber, "blockHash", blockHash.TerminalString(), + log.Error("call handleDelegatePerReward fail AppendDelegateRewardPer", "blockNumber", head.Number, "blockHash", blockHash.TerminalString(), "nodeId", verifier.NodeId.TerminalString(), "err", err, "CurrentEpochDelegateReward", verifier.CurrentEpochDelegateReward, "delegateTotal", verifier.DelegateTotal) return err } @@ -270,15 +270,15 @@ func (rmp *RewardMgrPlugin) HandleDelegatePerReward(blockHash common.Hash, block canAddr, err := xutil.NodeId2Addr(verifier.NodeId) if nil != err { log.Error("Failed to handleDelegatePerReward on rewardMgrPlugin: nodeId parse addr failed", - "blockNumber", blockNumber, "blockHash", blockHash, "nodeID", verifier.NodeId.String(), "err", err) + "blockNumber", head.Number, "blockHash", blockHash, "nodeID", verifier.NodeId.String(), "err", err) return err } if err := rmp.stakingPlugin.db.SetCanMutableStore(blockHash, canAddr, verifier.CandidateMutable); err != nil { log.Error("Failed to handleDelegatePerReward on rewardMgrPlugin: setCanMutableStore failed", - "blockNumber", blockNumber, "blockHash", blockHash, "err", err, "mutable", verifier.CandidateMutable) + "blockNumber", head.Number, "blockHash", blockHash, "err", err, "mutable", verifier.CandidateMutable) return err } - log.Debug("handleDelegatePerReward add newDelegateRewardPer", "blockNum", blockNumber, "node_id", verifier.NodeId.TerminalString(), "stakingNum", verifier.StakingBlockNum, + log.Debug("handleDelegatePerReward add newDelegateRewardPer", "blockNum", head.Number, "node_id", verifier.NodeId.TerminalString(), "stakingNum", verifier.StakingBlockNum, "cu_epoch_delegate_reward", currentEpochDelegateReward, "total_delegate_reward", verifier.DelegateRewardTotal, "total_delegate", verifier.DelegateTotal, "epoch", currentEpoch) } @@ -287,13 +287,14 @@ func (rmp *RewardMgrPlugin) HandleDelegatePerReward(blockHash common.Hash, block } func (rmp *RewardMgrPlugin) WithdrawDelegateReward(blockHash common.Hash, blockNum uint64, account common.Address, list []*DelegationInfoWithRewardPerList, state xcom.StateDB) ([]reward.NodeDelegateReward, error) { - log.Debug("Call withdraw delegate reward: begin", "account", account, "list", list, "blockNum", blockNum, "blockHash", blockHash, "epoch", xutil.CalculateEpoch(blockNum)) + acVersion := gov.GetCurrentActiveVersion(state) + log.Debug("Call withdraw delegate reward: begin", "account", account, "list", list, "blockNum", blockNum, "blockHash", blockHash, "epoch", xutil.CalculateEpoch(blockNum, acVersion)) rewards := make([]reward.NodeDelegateReward, 0) if len(list) == 0 { return rewards, nil } - currentEpoch := xutil.CalculateEpoch(blockNum) + currentEpoch := xutil.CalculateEpoch(blockNum, acVersion) receiveReward := new(big.Int) for _, delWithPer := range list { rewardsReceive := calcDelegateIncome(currentEpoch, delWithPer.DelegationInfo.Delegation, delWithPer.RewardPerList) @@ -364,7 +365,7 @@ func (rmp *RewardMgrPlugin) GetDelegateReward(blockHash common.Hash, blockNum ui } } - currentEpoch := xutil.CalculateEpoch(blockNum) + currentEpoch := xutil.CalculateEpoch(blockNum, gov.GetCurrentActiveVersion(state)) delegationInfoWithRewardPerList := make([]*DelegationInfoWithRewardPerList, 0) for _, stakingNode := range dls { delegateRewardPerList, err := rmp.GetDelegateRewardPerList(blockHash, stakingNode.NodeID, stakingNode.StakeBlockNumber, uint64(stakingNode.Delegation.DelegateEpoch), currentEpoch-1) @@ -716,7 +717,7 @@ func (rmp *RewardMgrPlugin) CalcEpochReward(blockHash common.Hash, head *types.H // When the first issuance is completed // Each settlement cycle needs to update the year start time, // which is used to calculate the average annual block production rate - epochBlocks := xutil.CalcBlocksEachEpoch() + epochBlocks := xutil.CalcBlocksEachEpoch(head.GetActiveVersion()) if yearNumber > 0 { incIssuanceNumber, err := xcom.LoadIncIssuanceNumber(blockHash, rmp.db) if nil != err { diff --git a/x/plugin/slashing_plugin.go b/x/plugin/slashing_plugin.go index 8fb6b59582..932941a107 100644 --- a/x/plugin/slashing_plugin.go +++ b/x/plugin/slashing_plugin.go @@ -105,8 +105,8 @@ func (sp *SlashingPlugin) SetDecodeEvidenceFun(f func(dupType consensus.Evidence func (sp *SlashingPlugin) BeginBlock(blockHash common.Hash, header *types.Header, state xcom.StateDB) error { // If it is the first block in each round, Delete old pack amount record. // Do this from the second consensus round - if xutil.IsBeginOfConsensus(header.Number.Uint64()) && header.Number.Uint64() > 1 { - if err := sp.switchEpoch(header.Number.Uint64(), blockHash); nil != err { + if xutil.IsBeginOfConsensus(header.Number.Uint64(), header.GetActiveVersion()) && header.Number.Uint64() > 1 { + if err := sp.switchEpoch(header, blockHash); nil != err { log.Error("Failed to BeginBlock, call switchEpoch is failed", "blockNumber", header.Number.Uint64(), "blockHash", blockHash.TerminalString(), "err", err) return err } @@ -118,10 +118,10 @@ func (sp *SlashingPlugin) BeginBlock(blockHash common.Hash, header *types.Header // If it is the 230th block of each round, // it will punish the node with abnormal block rate. // Do this from the second consensus round - if header.Number.Uint64() > xutil.ConsensusSize() && xutil.IsElection(header.Number.Uint64()) { + if header.Number.Uint64() > xcom.ConsensusSize(header.GetActiveVersion()) && xutil.IsElection(header.Number.Uint64(), header.GetActiveVersion()) { log.Debug("Call GetPrePackAmount", "blockNumber", header.Number.Uint64(), "blockHash", - blockHash.TerminalString(), "consensusSize", xutil.ConsensusSize(), "electionDistance", xcom.ElectionDistance()) - if result, err := sp.GetPrePackAmount(header.Number.Uint64(), header.ParentHash); nil != err { + blockHash.TerminalString(), "consensusSize", xcom.ConsensusSize(header.GetActiveVersion()), "electionDistance", xcom.ElectionDistance()) + if result, err := sp.GetPrePackAmount(header, header.ParentHash); nil != err { return err } else { if nil == result { @@ -129,7 +129,7 @@ func (sp *SlashingPlugin) BeginBlock(blockHash common.Hash, header *types.Header return errors.New("packAmount data not found") } - preRoundVal, err := stk.getPreValList(blockHash, header.Number.Uint64(), QueryStartIrr) + preRoundVal, err := stk.getPreValList(blockHash, header.Number.Uint64(), QueryStartIrr, header.GetActiveVersion()) if nil != err { log.Error("Failed to BeginBlock, query previous round validators is failed", "blockNumber", header.Number.Uint64(), "blockHash", blockHash.TerminalString(), "err", err) return err @@ -204,7 +204,7 @@ func (sp *SlashingPlugin) zeroProduceProcess(blockHash common.Hash, header *type return nil, err } - preRound := xutil.CalculateRound(header.Number.Uint64()) - 1 + preRound := xutil.CalculateRound(header.Number.Uint64(), header.GetActiveVersion()) - 1 log.Info("Call zeroProduceProcess start", "blockNumber", blockNumber, "blockHash", blockHash, "preRound", preRound, "zeroProduceNumberThreshold", zeroProduceNumberThreshold, "zeroProduceCumulativeTime", zeroProduceCumulativeTime, "waitSlashingNodeListSize", waitSlashingNodeList) if len(waitSlashingNodeList) > 0 { for index := 0; index < len(waitSlashingNodeList); index++ { @@ -230,7 +230,7 @@ func (sp *SlashingPlugin) zeroProduceProcess(blockHash common.Hash, header *type if ok && isProduced { isDelete = true } else { - if amount, err := sp.getPackAmount(blockNumber, blockHash, nodeId); nil != err { + if amount, err := sp.getPackAmount(blockNumber, blockHash, nodeId, header.GetActiveVersion()); nil != err { return nil, err } else if amount > 0 { isDelete = true @@ -297,7 +297,7 @@ func (sp *SlashingPlugin) zeroProduceProcess(blockHash common.Hash, header *type continue } - slashItem, err := sp.checkSlashing(header.Number.Uint64(), blockHash, waitSlashingNode, preRound, zeroProduceCumulativeTime, zeroProduceNumberThreshold) + slashItem, err := sp.checkSlashing(header.Number.Uint64(), blockHash, waitSlashingNode, preRound, zeroProduceCumulativeTime, zeroProduceNumberThreshold, header.GetActiveVersion()) if nil != err { return nil, err } @@ -317,7 +317,7 @@ func (sp *SlashingPlugin) zeroProduceProcess(blockHash common.Hash, header *type Round: preRound, CountBit: 1, } - slashItem, err := sp.checkSlashing(header.Number.Uint64(), blockHash, waitSlashingNode, preRound, zeroProduceCumulativeTime, zeroProduceNumberThreshold) + slashItem, err := sp.checkSlashing(header.Number.Uint64(), blockHash, waitSlashingNode, preRound, zeroProduceCumulativeTime, zeroProduceNumberThreshold, header.GetActiveVersion()) if nil != err { return nil, err } @@ -337,7 +337,7 @@ func (sp *SlashingPlugin) zeroProduceProcess(blockHash common.Hash, header *type return slashQueue, nil } -func (sp *SlashingPlugin) checkSlashing(blockNumber uint64, blockHash common.Hash, waitSlashingNode *WaitSlashingNode, preRound uint64, zeroProduceCumulativeTime uint16, zeroProduceNumberThreshold uint16) (*staking.SlashNodeItem, error) { +func (sp *SlashingPlugin) checkSlashing(blockNumber uint64, blockHash common.Hash, waitSlashingNode *WaitSlashingNode, preRound uint64, zeroProduceCumulativeTime uint16, zeroProduceNumberThreshold uint16, version uint32) (*staking.SlashNodeItem, error) { nodeId := waitSlashingNode.NodeId // If the range of the time window is satisfied, and the number of zero blocks is satisfied, a penalty is imposed. if diff := uint16(preRound - waitSlashingNode.Round + 1); diff == zeroProduceCumulativeTime { @@ -376,7 +376,7 @@ func (sp *SlashingPlugin) checkSlashing(blockNumber uint64, blockHash common.Has } slashAmount := new(big.Int).SetUint64(0) - totalBalance := calcCanTotalBalance(blockNumber, canMutable) + totalBalance := calcCanTotalBalance(blockNumber, canMutable, version) blocksReward, err := gov.GovernSlashBlocksReward(blockNumber, blockHash) if nil != err { log.Error("Failed to zeroProduceProcess, query GovernSlashBlocksReward is failed", "blockNumber", blockNumber, "blockHash", blockHash.TerminalString(), "err", err) @@ -435,8 +435,8 @@ func (sp *SlashingPlugin) setWaitSlashingNodeList(blockNumber uint64, blockHash return nil } -func (sp *SlashingPlugin) getPackAmount(blockNumber uint64, blockHash common.Hash, nodeId enode.IDv0) (uint32, error) { - value, err := sp.db.Get(blockHash, buildKey(blockNumber, nodeId.Bytes())) +func (sp *SlashingPlugin) getPackAmount(blockNumber uint64, blockHash common.Hash, nodeId enode.IDv0, version uint32) (uint32, error) { + value, err := sp.db.Get(blockHash, buildKey(blockNumber, nodeId.Bytes(), version)) if snapshotdb.NonDbNotFoundErr(err) { return 0, err } @@ -454,11 +454,11 @@ func (sp *SlashingPlugin) setPackAmount(blockHash common.Hash, header *types.Hea if nil != err { return err } - if value, err := sp.getPackAmount(header.Number.Uint64(), blockHash, nodeId); nil != err { + if value, err := sp.getPackAmount(header.Number.Uint64(), blockHash, nodeId, header.GetActiveVersion()); nil != err { return err } else { value++ - if err := sp.db.Put(blockHash, buildKey(header.Number.Uint64(), nodeId.Bytes()), common.Uint32ToBytes(value)); nil != err { + if err := sp.db.Put(blockHash, buildKey(header.Number.Uint64(), nodeId.Bytes(), header.GetActiveVersion()), common.Uint32ToBytes(value)); nil != err { return err } log.Debug("Call setPackAmount finished", "blockNumber", header.Number.Uint64(), "blockHash", blockHash.TerminalString(), "nodeId", nodeId.TerminalString(), "value", value) @@ -466,9 +466,9 @@ func (sp *SlashingPlugin) setPackAmount(blockHash common.Hash, header *types.Hea return nil } -func (sp *SlashingPlugin) switchEpoch(blockNumber uint64, blockHash common.Hash) error { +func (sp *SlashingPlugin) switchEpoch(header *types.Header, blockHash common.Hash) error { - iter := sp.db.Ranking(blockHash, buildPrefixByRound(xutil.CalculateRound(blockNumber)-2), 0) + iter := sp.db.Ranking(blockHash, buildPrefixByRound(xutil.CalculateRound(header.Number.Uint64(), header.GetActiveVersion())-2), 0) if err := iter.Error(); nil != err { return err } @@ -477,20 +477,20 @@ func (sp *SlashingPlugin) switchEpoch(blockNumber uint64, blockHash common.Hash) for iter.Next() { key := iter.Key() value := iter.Value() - log.Debug("Call switchEpoch ranking old", "blockNumber", blockNumber, "key", hex.EncodeToString(key), "value", common.BytesToUint32(value)) + log.Debug("Call switchEpoch ranking old", "blockNumber", header.Number.Uint64(), "key", hex.EncodeToString(key), "value", common.BytesToUint32(value)) if err := sp.db.Del(blockHash, key); nil != err { return err } count++ } - log.Info("Call switchEpoch finished", "blockNumber", blockNumber, "blockHash", blockHash.TerminalString(), "count", count) + log.Info("Call switchEpoch finished", "blockNumber", header.Number.Uint64(), "blockHash", blockHash.TerminalString(), "count", count) return nil } // Get the consensus rate of all nodes in the previous round -func (sp *SlashingPlugin) GetPrePackAmount(blockNumber uint64, parentHash common.Hash) (map[enode.IDv0]uint32, error) { +func (sp *SlashingPlugin) GetPrePackAmount(header *types.Header, parentHash common.Hash) (map[enode.IDv0]uint32, error) { result := make(map[enode.IDv0]uint32) - prefixKey := buildPrefixByRound(xutil.CalculateRound(blockNumber) - 1) + prefixKey := buildPrefixByRound(xutil.CalculateRound(header.Number.Uint64(), header.GetActiveVersion()) - 1) iter := sp.db.Ranking(parentHash, prefixKey, 0) if err := iter.Error(); nil != err { @@ -528,8 +528,9 @@ func (sp *SlashingPlugin) Slash(evidence consensus.Evidence, blockHash common.Ha "blockNumber", blockNumber, "blockHash", blockHash.TerminalString(), "evidenceBlockNumber", evidence.BlockNumber()) return slashing.ErrBlockNumberTooHigh } - evidenceEpoch := xutil.CalculateEpoch(evidence.BlockNumber()) - blocksOfEpoch := xutil.CalcBlocksEachEpoch() + currentVeriosn := gov.GetCurrentActiveVersion(stateDB) + evidenceEpoch := xutil.CalculateEpoch(evidence.BlockNumber(), currentVeriosn) + blocksOfEpoch := xutil.CalcBlocksEachEpoch(currentVeriosn) invalidNum := evidenceEpoch * blocksOfEpoch if invalidNum < blockNumber { @@ -594,7 +595,7 @@ func (sp *SlashingPlugin) Slash(evidence consensus.Evidence, blockHash common.Ha return slashing.ErrBlsPubKeyMismatch } - if has, err := stk.checkRoundValidatorAddr(blockHash, evidence.BlockNumber(), canAddr); nil != err { + if has, err := stk.checkRoundValidatorAddr(blockHash, evidence.BlockNumber(), canAddr, currentVeriosn); nil != err { log.Error("Failed to Slash, checkRoundValidatorAddr is failed", "blockNumber", blockNumber, "blockHash", blockHash.TerminalString(), "evidenceBlockNum", evidence.BlockNumber(), "canAddr", canAddr.Hex(), "err", err) return slashing.ErrDuplicateSignVerify @@ -625,7 +626,7 @@ func (sp *SlashingPlugin) Slash(evidence consensus.Evidence, blockHash common.Ha return err } - totalBalance := calcCanTotalBalance(blockNumber, canMutable) + totalBalance := calcCanTotalBalance(blockNumber, canMutable, currentVeriosn) slashAmount := calcAmountByRate(totalBalance, uint64(fraction), TenThousandDenominator) log.Info("Call SlashCandidates on executeSlash", "blockNumber", blockNumber, "blockHash", blockHash.TerminalString(), @@ -681,12 +682,12 @@ func duplicateSignKey(nodeId enode.IDv0, blockNumber uint64, dupType consensus.E return append(append(nodeId.Bytes(), common.Uint64ToBytes(blockNumber)...), common.Uint16ToBytes(uint16(dupType))...) } -func buildKey(blockNumber uint64, key []byte) []byte { - return append(buildPrefix(blockNumber), key...) +func buildKey(blockNumber uint64, key []byte, version uint32) []byte { + return append(buildPrefix(blockNumber, version), key...) } -func buildPrefix(blockNumber uint64) []byte { - return buildPrefixByRound(xutil.CalculateRound(blockNumber)) +func buildPrefix(blockNumber uint64, version uint32) []byte { + return buildPrefixByRound(xutil.CalculateRound(blockNumber, version)) } func buildPrefixByRound(round uint64) []byte { @@ -714,9 +715,9 @@ func parseNodeId(header *types.Header) (enode.IDv0, error) { } } -func calcCanTotalBalance(blockNumber uint64, candidate *staking.CandidateMutable) *big.Int { +func calcCanTotalBalance(blockNumber uint64, candidate *staking.CandidateMutable, version uint32) *big.Int { // Recalculate the quality deposit - lazyCalcStakeAmount(xutil.CalculateEpoch(blockNumber), candidate) + lazyCalcStakeAmount(xutil.CalculateEpoch(blockNumber, version), candidate) return new(big.Int).Add(candidate.Released, candidate.RestrictingPlan) } diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index c36d9e2ff6..502946e9b8 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -108,7 +108,8 @@ func (sk *StakingPlugin) SetEventMux(eventMux *event.TypeMux) { func (sk *StakingPlugin) BeginBlock(blockHash common.Hash, header *types.Header, state xcom.StateDB) error { // adjust rewardPer and nextRewardPer blockNumber := header.Number.Uint64() - if xutil.IsBeginOfEpoch(blockNumber) { + acVersion := gov.GetCurrentActiveVersion(state) + if xutil.IsBeginOfEpoch(blockNumber, acVersion) { current, err := sk.getVerifierList(blockHash, blockNumber, QueryStartNotIrr) if err != nil { log.Error("Failed to query current round validators on stakingPlugin BeginBlock", @@ -126,7 +127,7 @@ func (sk *StakingPlugin) BeginBlock(blockHash common.Hash, header *types.Header, continue } var changed bool - changed = lazyCalcNodeTotalDelegateAmount(xutil.CalculateEpoch(blockNumber), canOld) + changed = lazyCalcNodeTotalDelegateAmount(xutil.CalculateEpoch(blockNumber, acVersion), canOld) if canOld.RewardPer != canOld.NextRewardPer { canOld.RewardPer = canOld.NextRewardPer changed = true @@ -149,10 +150,10 @@ func (sk *StakingPlugin) BeginBlock(blockHash common.Hash, header *types.Header, } func (sk *StakingPlugin) EndBlock(blockHash common.Hash, header *types.Header, state xcom.StateDB) error { + acVersion := gov.GetCurrentActiveVersion(state) + epoch := xutil.CalculateEpoch(header.Number.Uint64(), acVersion) - epoch := xutil.CalculateEpoch(header.Number.Uint64()) - - if xutil.IsEndOfEpoch(header.Number.Uint64()) { + if xutil.IsEndOfEpoch(header.Number.Uint64(), acVersion) { // handle UnStaking Item err := sk.HandleUnCandidateItem(state, header.Number.Uint64(), blockHash, epoch) @@ -163,14 +164,14 @@ func (sk *StakingPlugin) EndBlock(blockHash common.Hash, header *types.Header, s } // Election next epoch validators - if err := sk.ElectNextVerifierList(blockHash, header.Number.Uint64(), state); nil != err { + if err := sk.ElectNextVerifierList(blockHash, header, state); nil != err { log.Error("Failed to call ElectNextVerifierList on stakingPlugin EndBlock", "blockNumber", header.Number.Uint64(), "blockHash", blockHash.Hex(), "err", err) return err } } - if xutil.IsElection(header.Number.Uint64()) { + if xutil.IsElection(header.Number.Uint64(), gov.GetCurrentActiveVersion(state)) { // ELection next round validators err := sk.Election(blockHash, header, state) @@ -186,7 +187,7 @@ func (sk *StakingPlugin) EndBlock(blockHash common.Hash, header *types.Header, s func (sk *StakingPlugin) Confirmed(nodeId enode.IDv0, block *types.Block) error { - if xutil.IsElection(block.NumberU64()) { + if xutil.IsElection(block.NumberU64(), block.ActiveVersion()) { next, err := sk.getNextValList(block.Hash(), block.NumberU64(), QueryStartNotIrr) if nil != err { @@ -265,13 +266,13 @@ func (sk *StakingPlugin) GetCanMutable(blockHash common.Hash, addr common.NodeAd return sk.db.GetCanMutableStore(blockHash, addr) } -func (sk *StakingPlugin) GetCandidateCompactInfo(blockHash common.Hash, blockNumber uint64, addr common.NodeAddress) (*staking.CandidateHex, error) { +func (sk *StakingPlugin) GetCandidateCompactInfo(blockHash common.Hash, blockNumber uint64, addr common.NodeAddress, db xcom.StateDB) (*staking.CandidateHex, error) { can, err := sk.GetCandidateInfo(blockHash, addr) if nil != err { return nil, err } - epoch := xutil.CalculateEpoch(blockNumber) + epoch := xutil.CalculateEpoch(blockNumber, gov.GetCurrentActiveVersion(db)) lazyCalcStakeAmount(epoch, can.CandidateMutable) canHex := buildCanHex(can) return canHex, nil @@ -337,7 +338,7 @@ func (sk *StakingPlugin) CreateCandidate(state xcom.StateDB, blockHash common.Ha return staking.ErrWrongVonOptType } - can.StakingEpoch = uint32(xutil.CalculateEpoch(blockNumber.Uint64())) + can.StakingEpoch = uint32(xutil.CalculateEpoch(blockNumber.Uint64(), gov.GetCurrentActiveVersion(state))) if err := sk.db.SetCandidateStore(blockHash, addr, can); nil != err { log.Error("Failed to CreateCandidate on stakingPlugin: Store Candidate info is failed", @@ -443,7 +444,7 @@ func (sk *StakingPlugin) EditCandidate(blockHash common.Hash, blockNumber *big.I func (sk *StakingPlugin) IncreaseStaking(state xcom.StateDB, blockHash common.Hash, blockNumber, amount *big.Int, typ uint16, canAddr common.NodeAddress, can *staking.Candidate) error { - epoch := xutil.CalculateEpoch(blockNumber.Uint64()) + epoch := xutil.CalculateEpoch(blockNumber.Uint64(), gov.GetCurrentActiveVersion(state)) lazyCalcStakeAmount(epoch, can.CandidateMutable) @@ -508,7 +509,7 @@ func (sk *StakingPlugin) IncreaseStaking(state xcom.StateDB, blockHash common.Ha func (sk *StakingPlugin) WithdrewStaking(state xcom.StateDB, blockHash common.Hash, blockNumber *big.Int, canAddr common.NodeAddress, can *staking.Candidate) error { - epoch := xutil.CalculateEpoch(blockNumber.Uint64()) + epoch := xutil.CalculateEpoch(blockNumber.Uint64(), gov.GetCurrentActiveVersion(state)) lazyCalcStakeAmount(epoch, can.CandidateMutable) @@ -800,7 +801,7 @@ func (sk *StakingPlugin) GetDelegateInfo(blockHash common.Hash, delAddr common.A return sk.db.GetDelegateStore(blockHash, delAddr, nodeId, stakeBlockNumber) } -func (sk *StakingPlugin) GetDelegateExInfo(blockHash common.Hash, delAddr common.Address, +/*func (sk *StakingPlugin) GetDelegateExInfo(blockHash common.Hash, delAddr common.Address, nodeId enode.IDv0, stakeBlockNumber uint64) (*staking.DelegationEx, error) { del, err := sk.db.GetDelegateStore(blockHash, delAddr, nodeId, stakeBlockNumber) @@ -819,17 +820,17 @@ func (sk *StakingPlugin) GetDelegateExInfo(blockHash common.Hash, delAddr common CumulativeIncome: (*hexutil.Big)(del.CumulativeIncome), }, }, nil -} +}*/ func (sk *StakingPlugin) GetDelegateExCompactInfo(blockHash common.Hash, blockNumber uint64, delAddr common.Address, - nodeId enode.IDv0, stakeBlockNumber uint64) (*staking.DelegationEx, error) { + nodeId enode.IDv0, stakeBlockNumber uint64, db xcom.StateDB) (*staking.DelegationEx, error) { del, err := sk.db.GetDelegateStore(blockHash, delAddr, nodeId, stakeBlockNumber) if nil != err { return nil, err } - epoch := xutil.CalculateEpoch(blockNumber) + epoch := xutil.CalculateEpoch(blockNumber, gov.GetCurrentActiveVersion(db)) lazyCalcDelegateAmount(epoch, del) return &staking.DelegationEx{ @@ -878,7 +879,7 @@ func (sk *StakingPlugin) Delegate(state xcom.StateDB, blockHash common.Hash, blo delAddr common.Address, del *staking.Delegation, canAddr common.NodeAddress, can *staking.Candidate, typ uint16, amount *big.Int, delegateRewardPerList []*reward.DelegateRewardPer) error { - epoch := xutil.CalculateEpoch(blockNumber.Uint64()) + epoch := xutil.CalculateEpoch(blockNumber.Uint64(), gov.GetCurrentActiveVersion(state)) rewardsReceive := calcDelegateIncome(epoch, del, delegateRewardPerList) @@ -984,7 +985,7 @@ func (sk *StakingPlugin) WithdrewDelegation(state xcom.StateDB, blockHash common return nil, staking.ErrDelegateVonNoEnough } - epoch := xutil.CalculateEpoch(blockNumber.Uint64()) + epoch := xutil.CalculateEpoch(blockNumber.Uint64(), gov.GetCurrentActiveVersion(state)) refundAmount := calcRealRefund(blockNumber.Uint64(), blockHash, total, amount) realSub := refundAmount @@ -1168,27 +1169,27 @@ func rufundDelegateFn(refundBalance, aboutRelease, aboutRestrictingPlan *big.Int return refundTmp, releaseTmp, restrictingPlanTmp, nil } -func (sk *StakingPlugin) ElectNextVerifierList(blockHash common.Hash, blockNumber uint64, state xcom.StateDB) error { +func (sk *StakingPlugin) ElectNextVerifierList(blockHash common.Hash, header *types.Header, state xcom.StateDB) error { - oldIndex, err := sk.getVeriferIndex(blockHash, blockNumber, QueryStartNotIrr) + oldIndex, err := sk.getVeriferIndex(blockHash, header.Number.Uint64(), QueryStartNotIrr) if nil != err { log.Error("Failed to ElectNextVerifierList: Not found the VerifierIndex", "blockNumber", - blockNumber, "blockHash", blockHash.Hex(), "err", err) + header.Number, "blockHash", blockHash.Hex(), "err", err) return err } - log.Debug("Call ElectNextVerifierList old verifiers Index", "blockNumber", blockNumber, "blockHash", blockHash.Hex(), "index", oldIndex) + log.Debug("Call ElectNextVerifierList old verifiers Index", "blockNumber", header.Number, "blockHash", blockHash.Hex(), "index", oldIndex) - if oldIndex.End != blockNumber { + if oldIndex.End != header.Number.Uint64() { log.Error("Failed to ElectNextVerifierList: this blockNumber invalid", "Old Epoch End blockNumber", - oldIndex.End, "Current blockNumber", blockNumber) + oldIndex.End, "Current blockNumber", header.Number) return staking.ErrBlockNumberDisordered } // caculate the new epoch start and end newVerifierArr := &staking.ValidatorArray{ Start: oldIndex.End + 1, - End: oldIndex.End + xutil.CalcBlocksEachEpoch(), + End: oldIndex.End + xutil.CalcBlocksEachEpoch(header.GetActiveVersion()), } currOriginVersion := gov.GetVersionForStaking(blockHash, state) @@ -1196,21 +1197,21 @@ func (sk *StakingPlugin) ElectNextVerifierList(blockHash common.Hash, blockNumbe currentVersion := gov.GetCurrentActiveVersion(state) if currentVersion == 0 { - log.Error("Failed to ElectNextVerifierList, GetCurrentActiveVersion is failed", "blockNumber", blockNumber, "blockHash", blockHash.TerminalString()) + log.Error("Failed to ElectNextVerifierList, GetCurrentActiveVersion is failed", "blockNumber", header.Number, "blockHash", blockHash.TerminalString()) return errors.New("Failed to get CurrentActiveVersion") } - maxvalidators, err := gov.GovernMaxValidators(blockNumber, blockHash) + maxvalidators, err := gov.GovernMaxValidators(header.Number.Uint64(), blockHash) if nil != err { log.Error("Failed to ElectNextVerifierList: query govern params `maxvalidators` is failed", "blockNumber", - blockNumber, "blockHash", blockHash.Hex(), "err", err) + header.Number, "blockHash", blockHash.Hex(), "err", err) return err } iter := sk.db.IteratorCandidatePowerByBlockHash(blockHash, int(maxvalidators)) if err := iter.Error(); nil != err { log.Error("Failed to ElectNextVerifierList: take iter by candidate power is failed", "blockNumber", - blockNumber, "blockHash", blockHash.Hex(), "err", err) + header.Number, "blockHash", blockHash.Hex(), "err", err) return err } defer iter.Release() @@ -1222,7 +1223,7 @@ func (sk *StakingPlugin) ElectNextVerifierList(blockHash common.Hash, blockNumbe addrSuffix := iter.Value() canBase, err := sk.db.GetCanBaseStoreWithSuffix(blockHash, addrSuffix) if nil != err { - log.Error("Failed to ElectNextVerifierList: Query CandidateBase info is failed", "blockNumber", blockNumber, + log.Error("Failed to ElectNextVerifierList: Query CandidateBase info is failed", "blockNumber", header.Number, "blockHash", blockHash.Hex(), "canAddr", common.BytesToNodeAddress(addrSuffix).Hex(), "err", err) if err == snapshotdb.ErrNotFound { if err := sk.db.Del(blockHash, iter.Key()); err != nil { @@ -1236,7 +1237,7 @@ func (sk *StakingPlugin) ElectNextVerifierList(blockHash common.Hash, blockNumbe if xutil.CalcVersion(canBase.ProgramVersion) < currVersion { log.Warn("Warn ElectNextVerifierList: the can ProgramVersion is less than currVersion", - "blockNumber", blockNumber, "blockHash", blockHash.Hex(), "canVersion", + "blockNumber", header.Number, "blockHash", blockHash.Hex(), "canVersion", "nodeId", canBase.NodeId.String(), "canAddr", common.BytesToNodeAddress(addrSuffix).Hex(), canBase.ProgramVersion, "currVersion", currVersion) @@ -1248,7 +1249,7 @@ func (sk *StakingPlugin) ElectNextVerifierList(blockHash common.Hash, blockNumbe canMutable, err := sk.db.GetCanMutableStoreWithSuffix(blockHash, addrSuffix) if nil != err { - log.Error("Failed to ElectNextVerifierList: Query CandidateMutable info is failed", "blockNumber", blockNumber, + log.Error("Failed to ElectNextVerifierList: Query CandidateMutable info is failed", "blockNumber", header.Number, "blockHash", blockHash.Hex(), "canAddr", common.BytesToNodeAddress(addrSuffix).Hex(), "err", err) return err } @@ -1271,14 +1272,14 @@ func (sk *StakingPlugin) ElectNextVerifierList(blockHash common.Hash, blockNumbe } newVerifierArr.Arr = queue - err = sk.setVerifierListAndIndex(blockNumber, blockHash, newVerifierArr) + err = sk.setVerifierListAndIndex(header.Number.Uint64(), blockHash, newVerifierArr) if nil != err { log.Error("Failed to ElectNextVerifierList: Set Next Epoch VerifierList is failed", "blockNumber", - blockNumber, "blockHash", blockHash.Hex(), "err", err) + header.Number, "blockHash", blockHash.Hex(), "err", err) return err } - log.Debug("Call ElectNextVerifierList Next verifiers", "blockNumber", blockNumber, "blockHash", blockHash.Hex(), + log.Debug("Call ElectNextVerifierList Next verifiers", "blockNumber", header.Number, "blockHash", blockHash.Hex(), "list length", len(queue), "list", newVerifierArr) return nil @@ -1456,14 +1457,14 @@ func (sk *StakingPlugin) GetCandidateONEpoch(blockHash common.Hash, blockNumber // 0: Query previous round consensus validator // 1: Query current round consensus validaor // 2: Query next round consensus validator -func (sk *StakingPlugin) GetValidatorList(blockHash common.Hash, blockNumber uint64, flag uint, isCommit bool) ( +func (sk *StakingPlugin) GetValidatorList(blockHash common.Hash, blockNumber uint64, flag uint, isCommit bool, version uint32) ( staking.ValidatorExQueue, error) { var validatorArr *staking.ValidatorArray switch flag { case PreviousRound: - arr, err := sk.getPreValList(blockHash, blockNumber, isCommit) + arr, err := sk.getPreValList(blockHash, blockNumber, isCommit, version) if nil != err { return nil, err } @@ -1536,13 +1537,15 @@ func (sk *StakingPlugin) GetValidatorList(blockHash common.Hash, blockNumber uin } func (sk *StakingPlugin) GetCandidateONRound(blockHash common.Hash, blockNumber uint64, - flag uint, isCommit bool) (staking.CandidateQueue, error) { + flag uint, isCommit bool, db xcom.StateDB) (staking.CandidateQueue, error) { var validatorArr *staking.ValidatorArray + acVersion := gov.GetCurrentActiveVersion(db) + switch flag { case PreviousRound: - arr, err := sk.getPreValList(blockHash, blockNumber, isCommit) + arr, err := sk.getPreValList(blockHash, blockNumber, isCommit, acVersion) if nil != err { return nil, err } @@ -1629,9 +1632,9 @@ func (sk *StakingPlugin) IsCurrValidator(blockHash common.Hash, blockNumber uint return flag, nil } -func (sk *StakingPlugin) GetCandidateList(blockHash common.Hash, blockNumber uint64) (staking.CandidateHexQueue, error) { +func (sk *StakingPlugin) GetCandidateList(blockHash common.Hash, blockNumber uint64, db xcom.StateDB) (staking.CandidateHexQueue, error) { - epoch := xutil.CalculateEpoch(blockNumber) + epoch := xutil.CalculateEpoch(blockNumber, gov.GetCurrentActiveVersion(db)) iter := sk.db.IteratorCandidatePowerByBlockHash(blockHash, 0) if err := iter.Error(); nil != err { @@ -1782,7 +1785,10 @@ func (sk *StakingPlugin) Election(blockHash common.Hash, header *types.Header, s // caculate the next round start and end start := curr.End + 1 - end := curr.End + xutil.ConsensusSize() + + currentActiveVersion := gov.GetCurrentActiveVersion(state) + + end := curr.End + xcom.ConsensusSize(currentActiveVersion) // 记录 被惩罚的 can数目 hasSlashLen := 0 // duplicateSign And lowRatio No enough von @@ -1977,6 +1983,8 @@ func (sk *StakingPlugin) Election(blockHash common.Hash, header *types.Header, s //invalidLen = hasSlashLen + needRMwithdrewLen + needRMLowVersionLen invalidLen = len(invalidCan) + maxConsensusVals := xcom.MaxConsensusVals(currentActiveVersion) + shuffle := func(invalidLen int, currQueue, vrfQueue staking.ValidatorQueue, blockNumber uint64, parentHash common.Hash) (staking.ValidatorQueue, error) { // increase term and use new shares one by one @@ -1992,19 +2000,18 @@ func (sk *StakingPlugin) Election(blockHash common.Hash, header *types.Header, s copyCurrQueue := make(staking.ValidatorQueue, len(currQueue)-invalidLen) // Remove the invalid validators copy(copyCurrQueue, currQueue[invalidLen:]) - return shuffleQueue(copyCurrQueue, vrfQueue, blockNumber, parentHash) + return shuffleQueue(maxConsensusVals, copyCurrQueue, vrfQueue, blockNumber, parentHash) } var vrfQueue staking.ValidatorQueue - var vrfLen int - if len(diffQueue) > int(xcom.MaxConsensusVals()) { - vrfLen = int(xcom.MaxConsensusVals()) - } else { + + vrfLen := int(maxConsensusVals) + if len(diffQueue) <= vrfLen { vrfLen = len(diffQueue) } if vrfLen != 0 { - if queue, err := vrfElection(diffQueue, vrfLen, header.Nonce.Bytes(), header.ParentHash, blockNumber, currentVersion); nil != err { + if queue, err := vrfElection(maxConsensusVals, diffQueue, vrfLen, header.Nonce.Bytes(), header.ParentHash, blockNumber, currentVersion); nil != err { log.Error("Failed to VrfElection on Election", "blockNumber", blockNumber, "blockHash", blockHash.Hex(), "err", err) return err @@ -2017,7 +2024,7 @@ func (sk *StakingPlugin) Election(blockHash common.Hash, header *types.Header, s "has slash count", hasSlashLen, "withdrew and need remove count", needRMwithdrewLen, "low version need remove count", needRMLowVersionLen, "total remove count", invalidLen, "remove map size", len(removeCans), - "current validators Size", len(curr.Arr), "MaxConsensusVals", xcom.MaxConsensusVals(), + "current validators Size", len(curr.Arr), "maxConsensusVals", maxConsensusVals, "ShiftValidatorNum", xcom.ShiftValidatorNum(), "diffQueueLen", len(diffQueue), "vrfQueueLen", len(vrfQueue)) @@ -2066,7 +2073,7 @@ func (sk *StakingPlugin) Election(blockHash common.Hash, header *types.Header, s } } - if err := sk.storeRoundValidatorAddrs(blockNumber, blockHash, start, nextQueue); nil != err { + if err := sk.storeRoundValidatorAddrs(blockNumber, blockHash, start, nextQueue, currentActiveVersion); nil != err { log.Error("Failed to storeRoundValidatorAddrs on Election", "blockNumber", blockNumber, "blockHash", blockHash.TerminalString(), "err", err) return err @@ -2078,18 +2085,18 @@ func (sk *StakingPlugin) Election(blockHash common.Hash, header *types.Header, s return nil } -func shuffleQueue(remainCurrQueue, vrfQueue staking.ValidatorQueue, blockNumber uint64, parentHash common.Hash) (staking.ValidatorQueue, error) { +func shuffleQueue(maxConsensusVals uint64, remainCurrQueue, vrfQueue staking.ValidatorQueue, blockNumber uint64, parentHash common.Hash) (staking.ValidatorQueue, error) { remainLen := len(remainCurrQueue) totalQueue := append(remainCurrQueue, vrfQueue...) - for remainLen > int(xcom.MaxConsensusVals()-xcom.ShiftValidatorNum()) && len(totalQueue) > int(xcom.MaxConsensusVals()) { + for remainLen > int(maxConsensusVals-xcom.ShiftValidatorNum()) && len(totalQueue) > int(maxConsensusVals) { totalQueue = totalQueue[1:] remainLen-- } - if len(totalQueue) > int(xcom.MaxConsensusVals()) { - totalQueue = totalQueue[:xcom.MaxConsensusVals()] + if len(totalQueue) > int(maxConsensusVals) { + totalQueue = totalQueue[:maxConsensusVals] } next := make(staking.ValidatorQueue, len(totalQueue)) @@ -2246,7 +2253,7 @@ func (sk *StakingPlugin) toSlash(state xcom.StateDB, blockNumber uint64, blockHa return needRemove, staking.ErrCanNoExist } - epoch := xutil.CalculateEpoch(blockNumber) + epoch := xutil.CalculateEpoch(blockNumber, gov.GetCurrentActiveVersion(state)) lazyCalcStakeAmount(epoch, can.CandidateMutable) // Balance that can only be effective for Slash @@ -2385,7 +2392,7 @@ func (sk *StakingPlugin) toSlash(state xcom.StateDB, blockNumber uint64, blockHa } } else { // Add a freeze message, after the freeze is over, it can return to normal state - if err := sk.addRecoveryUnStakeItem(blockNumber, blockHash, can.NodeId, canAddr, can.StakingBlockNum); nil != err { + if err := sk.addRecoveryUnStakeItem(blockNumber, blockHash, can.NodeId, canAddr, can.StakingBlockNum, gov.GetCurrentActiveVersion(state)); nil != err { log.Error("Failed to SlashCandidates on stakingPlugin: addRecoveryUnStakeItem failed", "blockNumber", blockNumber, "blockHash", blockHash.Hex(), "nodeId", can.NodeId.String(), "err", err) return needRemove, err @@ -2950,7 +2957,7 @@ func (svs newSortValidatorQueue) Swap(i, j int) { // validatorList:Waiting for the elected node // nonce:Vrf proof of the current block // parentHash:Parent block hash -func vrfElection(validatorList staking.ValidatorQueue, shiftLen int, nonce []byte, parentHash common.Hash, blockNumber uint64, currentVersion uint32) (staking.ValidatorQueue, error) { +func vrfElection(maxConsensusVals uint64, validatorList staking.ValidatorQueue, shiftLen int, nonce []byte, parentHash common.Hash, blockNumber uint64, currentVersion uint32) (staking.ValidatorQueue, error) { preNonces, err := handler.GetVrfHandlerInstance().Load(parentHash) if nil != err { return nil, err @@ -2963,10 +2970,10 @@ func vrfElection(validatorList staking.ValidatorQueue, shiftLen int, nonce []byt if len(preNonces) > len(validatorList) { preNonces = preNonces[len(preNonces)-len(validatorList):] } - return probabilityElection(validatorList, shiftLen, vrf.ProofToHash(nonce), preNonces, blockNumber, currentVersion) + return probabilityElection(maxConsensusVals, validatorList, shiftLen, vrf.ProofToHash(nonce), preNonces, blockNumber, currentVersion) } -func probabilityElection(validatorList staking.ValidatorQueue, shiftLen int, currentNonce []byte, preNonces [][]byte, blockNumber uint64, currentVersion uint32) (staking.ValidatorQueue, error) { +func probabilityElection(maxConsensusVals uint64, validatorList staking.ValidatorQueue, shiftLen int, currentNonce []byte, preNonces [][]byte, blockNumber uint64, currentVersion uint32) (staking.ValidatorQueue, error) { if len(currentNonce) == 0 || len(preNonces) == 0 || len(validatorList) != len(preNonces) { log.Error("Failed to probabilityElection", "blockNumber", blockNumber, "currentVersion", currentVersion, "validators Size", len(validatorList), "currentNonceSize", len(currentNonce), "preNoncesSize", len(preNonces)) @@ -3007,7 +3014,7 @@ func probabilityElection(validatorList staking.ValidatorQueue, shiftLen int, cur } else if gov.Gte0150Version(currentVersion) { p = xcom.CalcP(totalWeightsFloat, totalSqrtWeightsFloat) } else { - p = float64(xcom.ShiftValidatorNum()) * float64(xcom.MaxConsensusVals()) / totalSqrtWeightsFloat + p = float64(xcom.ShiftValidatorNum()) * float64(maxConsensusVals) / totalSqrtWeightsFloat } shuffleSeed := new(big.Int).SetBytes(preNonces[0]).Int64() @@ -3068,9 +3075,9 @@ Internal expansion function */ // previous round validators -func (sk *StakingPlugin) getPreValList(blockHash common.Hash, blockNumber uint64, isCommit bool) (*staking.ValidatorArray, error) { +func (sk *StakingPlugin) getPreValList(blockHash common.Hash, blockNumber uint64, isCommit bool, version uint32) (*staking.ValidatorArray, error) { - targetIndex, err := sk.getPreValIndex(blockHash, blockNumber, isCommit) + targetIndex, err := sk.getPreValIndex(blockHash, blockNumber, isCommit, version) if nil != err { return nil, err } @@ -3106,12 +3113,13 @@ func (sk *StakingPlugin) getPreValList(blockHash common.Hash, blockNumber uint64 }, nil } -func (sk *StakingPlugin) getPreValIndex(blockHash common.Hash, blockNumber uint64, isCommit bool) (*staking.ValArrIndex, error) { +func (sk *StakingPlugin) getPreValIndex(blockHash common.Hash, blockNumber uint64, isCommit bool, version uint32) (*staking.ValArrIndex, error) { var targetIndex *staking.ValArrIndex var preTargetNumber uint64 - if blockNumber > xutil.ConsensusSize() { - preTargetNumber = blockNumber - xutil.ConsensusSize() + consensusSize := xcom.ConsensusSize(version) + if blockNumber > consensusSize { + preTargetNumber = blockNumber - consensusSize } var indexArr staking.ValArrIndexQueue @@ -3575,8 +3583,8 @@ func (sk *StakingPlugin) setVerifierListByIndex(blockNumber uint64, blockHash co return nil } -func (sk *StakingPlugin) addErrorAccountUnStakeItem(blockNumber uint64, blockHash common.Hash, nodeId enode.IDv0, canAddr common.NodeAddress, stakingBlockNum uint64) error { - targetEpoch := xutil.CalculateEpoch(blockNumber) + 1 +func (sk *StakingPlugin) addErrorAccountUnStakeItem(blockNumber uint64, blockHash common.Hash, nodeId enode.IDv0, canAddr common.NodeAddress, stakingBlockNum uint64, version uint32) error { + targetEpoch := xutil.CalculateEpoch(blockNumber, version) + 1 if err := sk.db.AddUnStakeItemStore(blockHash, targetEpoch, canAddr, stakingBlockNum, false); nil != err { return err } @@ -3593,8 +3601,9 @@ func (sk *StakingPlugin) addUnStakeItem(state xcom.StateDB, blockNumber uint64, return err } var refundEpoch, maxEndVoteEpoch, targetEpoch uint64 + acVersion := gov.GetCurrentActiveVersion(state) if endVoteNum != 0 { - maxEndVoteEpoch = xutil.CalculateEpoch(endVoteNum) + maxEndVoteEpoch = xutil.CalculateEpoch(endVoteNum, acVersion) } duration, err := gov.GovernUnStakeFreezeDuration(blockNumber, blockHash) @@ -3602,7 +3611,7 @@ func (sk *StakingPlugin) addUnStakeItem(state xcom.StateDB, blockNumber uint64, return err } - refundEpoch = xutil.CalculateEpoch(blockNumber) + duration + refundEpoch = xutil.CalculateEpoch(blockNumber, acVersion) + duration if maxEndVoteEpoch <= refundEpoch { targetEpoch = refundEpoch @@ -3622,14 +3631,14 @@ func (sk *StakingPlugin) addUnStakeItem(state xcom.StateDB, blockNumber uint64, } func (sk *StakingPlugin) addRecoveryUnStakeItem(blockNumber uint64, blockHash common.Hash, nodeId enode.IDv0, - canAddr common.NodeAddress, stakingBlockNum uint64) error { + canAddr common.NodeAddress, stakingBlockNum uint64, version uint32) error { duration, err := gov.GovernZeroProduceFreezeDuration(blockNumber, blockHash) if nil != err { return err } - targetEpoch := xutil.CalculateEpoch(blockNumber) + duration + targetEpoch := xutil.CalculateEpoch(blockNumber, version) + duration log.Debug("Call addRecoveryUnStakeItem, AddUnStakeItemStore start", "current blockNumber", blockNumber, "duration", duration, "unstake item target Epoch", targetEpoch, @@ -3642,9 +3651,9 @@ func (sk *StakingPlugin) addRecoveryUnStakeItem(blockNumber uint64, blockHash co } // Record the address of the verification node for each consensus round within a certain block range. -func (sk *StakingPlugin) storeRoundValidatorAddrs(blockNumber uint64, blockHash common.Hash, nextStart uint64, array staking.ValidatorQueue) error { - nextRound := xutil.CalculateRound(nextStart) - nextEpoch := xutil.CalculateEpoch(nextStart) +func (sk *StakingPlugin) storeRoundValidatorAddrs(blockNumber uint64, blockHash common.Hash, nextStart uint64, array staking.ValidatorQueue, version uint32) error { + nextRound := xutil.CalculateRound(nextStart, version) + nextEpoch := xutil.CalculateEpoch(nextStart, version) evidenceAge, err := gov.GovernMaxEvidenceAge(blockNumber, blockHash) if nil != err { @@ -3654,7 +3663,7 @@ func (sk *StakingPlugin) storeRoundValidatorAddrs(blockNumber uint64, blockHash } validEpochCount := uint64(evidenceAge + 1) - validRoundCount := xutil.EpochSize() * validEpochCount + validRoundCount := xcom.EpochSize(version) * validEpochCount // Only store the address of last consensus rounds on `validEpochCount` epochs if nextEpoch > validEpochCount { @@ -3702,8 +3711,8 @@ func (sk *StakingPlugin) storeRoundValidatorAddrs(blockNumber uint64, blockHash return nil } -func (sk *StakingPlugin) checkRoundValidatorAddr(blockHash common.Hash, targetBlockNumber uint64, addr common.NodeAddress) (bool, error) { - targetRound := xutil.CalculateRound(targetBlockNumber) +func (sk *StakingPlugin) checkRoundValidatorAddr(blockHash common.Hash, targetBlockNumber uint64, addr common.NodeAddress, version uint32) (bool, error) { + targetRound := xutil.CalculateRound(targetBlockNumber, version) addrList, err := sk.db.LoadRoundValidatorAddrs(blockHash, staking.GetRoundValAddrArrKey(targetRound)) if nil != err { log.Error("Failed to checkRoundValidatorAddr", "blockHash", blockHash.TerminalString(), "targetBlockNumber", targetBlockNumber, diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index 5ab1e91baf..3dbb3ac98c 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package xcom import ( @@ -63,7 +62,7 @@ const ( IncreaseIssuanceRatioLowerLimit = 0 // When electing consensus nodes, it is used to calculate the P value of the binomial distribution - ElectionBase = 25 // New expectations + ElectionBase = 25 // New expectations ElectionBaseL1 = 3000 ElectionBaseL2 = 6000 @@ -485,9 +484,9 @@ func CheckSlashBlocksReward(rewards int) error { return nil } -func CheckZeroProduceCumulativeTime(zeroProduceCumulativeTime uint16, zeroProduceNumberThreshold uint16) error { - if zeroProduceCumulativeTime < zeroProduceNumberThreshold || zeroProduceCumulativeTime > uint16(EpochSize()) { - return common.InvalidParameter.Wrap(fmt.Sprintf("The ZeroProduceCumulativeTime must be [%d, %d]", zeroProduceNumberThreshold, uint16(EpochSize()))) +func CheckZeroProduceCumulativeTime(zeroProduceCumulativeTime uint16, zeroProduceNumberThreshold uint16, version uint32) error { + if zeroProduceCumulativeTime < zeroProduceNumberThreshold || zeroProduceCumulativeTime > uint16(EpochSize(version)) { + return common.InvalidParameter.Wrap(fmt.Sprintf("The ZeroProduceCumulativeTime must be [%d, %d]", zeroProduceNumberThreshold, uint16(EpochSize(version)))) } return nil } @@ -527,7 +526,7 @@ func CheckZeroProduceFreezeDuration(zeroProduceFreezeDuration uint64, unStakeFre return nil } -func CheckEconomicModel() error { +func CheckEconomicModel(version uint32) error { if nil == ec { return errors.New("EconomicModel config is nil") } @@ -607,7 +606,7 @@ func CheckEconomicModel() error { return err } - if uint16(EpochSize()) > maxZeroProduceCumulativeTime { + if uint16(EpochSize(version)) > maxZeroProduceCumulativeTime { return fmt.Errorf("the number of consensus rounds in a settlement cycle cannot be greater than maxZeroProduceCumulativeTime(%d)", maxZeroProduceCumulativeTime) } @@ -615,7 +614,7 @@ func CheckEconomicModel() error { return err } - if err := CheckZeroProduceCumulativeTime(ec.Slashing.ZeroProduceCumulativeTime, ec.Slashing.ZeroProduceNumberThreshold); nil != err { + if err := CheckZeroProduceCumulativeTime(ec.Slashing.ZeroProduceCumulativeTime, ec.Slashing.ZeroProduceNumberThreshold, version); nil != err { return err } @@ -663,7 +662,8 @@ func Interval() uint64 { func BlocksWillCreate() uint64 { return ec.Common.PerRoundBlocks } -func MaxConsensusVals() uint64 { +func MaxConsensusVals(version uint32) uint64 { + return ec.Common.MaxConsensusVals } @@ -671,12 +671,12 @@ func AdditionalCycleTime() uint64 { return ec.Common.AdditionalCycleTime } -func ConsensusSize() uint64 { - return BlocksWillCreate() * MaxConsensusVals() +func ConsensusSize(version uint32) uint64 { + return BlocksWillCreate() * MaxConsensusVals(version) } -func EpochSize() uint64 { - consensusSize := ConsensusSize() +func EpochSize(version uint32) uint64 { + consensusSize := ConsensusSize(version) em := MaxEpochMinutes() i := Interval() diff --git a/x/xutil/calculate.go b/x/xutil/calculate.go index c3ba19fab4..30b9f7df35 100644 --- a/x/xutil/calculate.go +++ b/x/xutil/calculate.go @@ -64,36 +64,19 @@ func ProgramVersion2Str(programVersion uint32) string { return fmt.Sprintf("%d.%d.%d", major, minor, patch) } -// ConsensusSize returns how many blocks per consensus round. -func ConsensusSize() uint64 { - return xcom.ConsensusSize() -} - -// EpochSize returns how many consensus rounds per epoch. -func EpochSize() uint64 { - return xcom.EpochSize() -} - -// EpochsPerYear returns how many epochs per year -func EpochsPerYear() uint64 { - epochBlocks := CalcBlocksEachEpoch() - i := xcom.Interval() - return xcom.AdditionalCycleTime() * 60 / (i * epochBlocks) -} - // CalcBlocksEachEpoch return how many blocks per epoch -func CalcBlocksEachEpoch() uint64 { - return ConsensusSize() * EpochSize() +func CalcBlocksEachEpoch(version uint32) uint64 { + return xcom.ConsensusSize(version) * xcom.EpochSize(version) } -func EstimateConsensusRoundsForGov(seconds uint64) uint64 { +func EstimateConsensusRoundsForGov(seconds uint64, version uint32) uint64 { //v0.7.5, hard code 1 second for block interval for estimating. blockInterval := uint64(1) - return seconds / (blockInterval * ConsensusSize()) + return seconds / (blockInterval * xcom.ConsensusSize(version)) } -func EstimateEndVotingBlockForParaProposal(blockNumber uint64, seconds uint64) uint64 { - consensusSize := ConsensusSize() +func EstimateEndVotingBlockForParaProposal(blockNumber uint64, seconds uint64, version uint32) uint64 { + consensusSize := xcom.ConsensusSize(version) epochMaxDuration := xcom.MaxEpochMinutes() //minutes //estimate how many consensus rounds in a epoch. consensusRoundsEachEpoch := epochMaxDuration * 60 / (xcom.Interval() * consensusSize) @@ -107,50 +90,33 @@ func EstimateEndVotingBlockForParaProposal(blockNumber uint64, seconds uint64) u return blockNumber + blocksEachEpoch - blockNumber%blocksEachEpoch + epochRounds*blocksEachEpoch } -// calculate returns how many blocks per year. -func CalcBlocksEachYear() uint64 { - return EpochsPerYear() * CalcBlocksEachEpoch() -} - // calculate the Epoch number by blockNumber -func CalculateEpoch(blockNumber uint64) uint64 { - size := CalcBlocksEachEpoch() - - var epoch uint64 - div := blockNumber / size - mod := blockNumber % size - - switch { - // first epoch - case div == 0: - epoch = 1 - case div > 0 && mod == 0: - epoch = div - case div > 0 && mod > 0: - epoch = div + 1 - } - - return epoch +func CalculateEpoch(blockNumber uint64, version uint32) uint64 { + size := CalcBlocksEachEpoch(version) + return calculateQuotient(blockNumber, size) } // calculate the Consensus number by blockNumber -func CalculateRound(blockNumber uint64) uint64 { - size := ConsensusSize() +func CalculateRound(blockNumber uint64, version uint32) uint64 { + size := xcom.ConsensusSize(version) + return calculateQuotient(blockNumber, size) +} - var round uint64 +func calculateQuotient(blockNumber, size uint64) uint64 { + var res uint64 div := blockNumber / size mod := blockNumber % size switch { // first consensus round case div == 0: - round = 1 + res = 1 case div > 0 && mod == 0: - round = div + res = div case div > 0 && mod > 0: - round = div + 1 + res = div + 1 } - return round + return res } func InNodeIDList(nodeID enode.IDv0, nodeIDList []enode.IDv0) bool { @@ -172,40 +138,46 @@ func InHashList(hash common.Hash, hashList []common.Hash) bool { } // end-voting-block = the end block of a consensus period - electionDistance, end-voting-block must be a Consensus Election block -func CalEndVotingBlock(blockNumber uint64, endVotingRounds uint64) uint64 { +func CalEndVotingBlock(blockNumber uint64, endVotingRounds uint64, version uint32) uint64 { electionDistance := xcom.ElectionDistance() - consensusSize := ConsensusSize() + consensusSize := xcom.ConsensusSize(version) return blockNumber + consensusSize - blockNumber%consensusSize + endVotingRounds*consensusSize - electionDistance } // active-block = the begin of a consensus period, so, it is possible that active-block also is the begin of a epoch. func CalActiveBlock(endVotingBlock uint64) uint64 { - //return endVotingBlock + xcom.ElectionDistance() + (xcom.VersionProposalActive_ConsensusRounds()-1)*ConsensusSize() + 1 return endVotingBlock + xcom.ElectionDistance() + 1 } // IsBeginOfEpoch returns true if current block is the first block of a Epoch -func IsBeginOfEpoch(blockNumber uint64) bool { - size := CalcBlocksEachEpoch() - mod := blockNumber % size - return mod == 1 +func IsBeginOfEpoch(blockNumber uint64, version uint32) bool { + return isEpochBeginOrEnd(blockNumber, true, version) } -// IsBeginOfConsensus returns true if current block is the first block of a Consensus Cycle -func IsBeginOfConsensus(blockNumber uint64) bool { - size := ConsensusSize() +func IsEndOfEpoch(blockNumber uint64, version uint32) bool { + return isEpochBeginOrEnd(blockNumber, false, version) +} + +func isEpochBeginOrEnd(blockNumber uint64, checkBegin bool, version uint32) bool { + size := CalcBlocksEachEpoch(version) mod := blockNumber % size - return mod == 1 + if checkBegin { + return mod == 1 + } else { + //check end + return mod == 0 + } } -func IsEndOfEpoch(blockNumber uint64) bool { - size := CalcBlocksEachEpoch() +// IsBeginOfConsensus returns true if current block is the first block of a Consensus Cycle +func IsBeginOfConsensus(blockNumber uint64, version uint32) bool { + size := xcom.ConsensusSize(version) mod := blockNumber % size - return mod == 0 + return mod == 1 } -func IsElection(blockNumber uint64) bool { +func IsElection(blockNumber uint64, version uint32) bool { tmp := blockNumber + xcom.ElectionDistance() - mod := tmp % ConsensusSize() + mod := tmp % xcom.ConsensusSize(version) return mod == 0 } From 9c77e1753561dc39bac120f2bb2f21a54e45f6b6 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 26 Nov 2021 09:54:32 +0800 Subject: [PATCH 077/299] fix test --- core/vm/delegate_reward_contract_test.go | 39 ++++----- core/vm/gov_contract_test.go | 34 ++++---- core/vm/platon_contract_test.go | 16 ++-- core/vm/staking_contract_test.go | 18 ++-- x/gov/gov_db_test.go | 2 +- x/gov/gov_test.go | 2 +- x/plugin/gov_plugin_test.go | 41 ++++----- x/plugin/platon_plugin_test.go | 14 +-- x/plugin/restricting_plugin_test.go | 49 +++++------ x/plugin/reward_plugin.go | 30 +++---- x/plugin/reward_plugin_test.go | 54 ++++++------ x/plugin/slashing_plugin_test.go | 28 +++--- x/plugin/staking_plugin.go | 54 ++++++------ x/plugin/staking_plugin_test.go | 104 ++++++++++++----------- x/xcom/common_config.go | 23 +++-- x/xcom/common_config_test.go | 14 +-- 16 files changed, 274 insertions(+), 248 deletions(-) diff --git a/core/vm/delegate_reward_contract_test.go b/core/vm/delegate_reward_contract_test.go index d9f0bc43f5..c64eb0d3dd 100644 --- a/core/vm/delegate_reward_contract_test.go +++ b/core/vm/delegate_reward_contract_test.go @@ -69,24 +69,24 @@ func generateStk(rewardPer uint16, delegateTotal *big.Int, blockNumber uint64) ( var delegation staking.Delegation delegation.Released = delegateTotal - delegation.DelegateEpoch = uint32(xutil.CalculateEpoch(blockNumber)) + delegation.DelegateEpoch = uint32(xutil.CalculateEpoch(blockNumber, params.GenesisVersion)) stakingValIndex := make(staking.ValArrIndexQueue, 0) stakingValIndex = append(stakingValIndex, &staking.ValArrIndex{ Start: 0, - End: xutil.CalcBlocksEachEpoch(), + End: xutil.CalcBlocksEachEpoch(params.GenesisVersion), }) stakingValIndex = append(stakingValIndex, &staking.ValArrIndex{ - Start: xutil.CalcBlocksEachEpoch(), - End: xutil.CalcBlocksEachEpoch() * 2, + Start: xutil.CalcBlocksEachEpoch(params.GenesisVersion), + End: xutil.CalcBlocksEachEpoch(params.GenesisVersion) * 2, }) stakingValIndex = append(stakingValIndex, &staking.ValArrIndex{ - Start: xutil.CalcBlocksEachEpoch() * 2, - End: xutil.CalcBlocksEachEpoch() * 3, + Start: xutil.CalcBlocksEachEpoch(params.GenesisVersion) * 2, + End: xutil.CalcBlocksEachEpoch(params.GenesisVersion) * 3, }) stakingValIndex = append(stakingValIndex, &staking.ValArrIndex{ - Start: xutil.CalcBlocksEachEpoch() * 3, - End: xutil.CalcBlocksEachEpoch() * 4, + Start: xutil.CalcBlocksEachEpoch(params.GenesisVersion) * 3, + End: xutil.CalcBlocksEachEpoch(params.GenesisVersion) * 4, }) validatorQueue := make(staking.ValidatorQueue, 0) validatorQueue = append(validatorQueue, &staking.Validator{ @@ -145,10 +145,10 @@ func TestWithdrawDelegateRewardWithReward(t *testing.T) { contact.Plugin.SetCurrentNodeID(can.NodeId) blockReward, stakingReward := big.NewInt(100000), big.NewInt(200000) - - for i := 0; i < int(xutil.CalcBlocksEachEpoch()); i++ { + acverion := gov.GetCurrentActiveVersion(chain.StateDB) + for i := 0; i < int(xutil.CalcBlocksEachEpoch(acverion)); i++ { if err := chain.AddBlockWithSnapDB(true, func(hash common.Hash, header *types.Header, sdb snapshotdb.DB) error { - if xutil.IsBeginOfEpoch(header.Number.Uint64()) { + if xutil.IsBeginOfEpoch(header.Number.Uint64(), acverion) { can.CandidateMutable.CleanCurrentEpochDelegateReward() if err := stkDB.SetCanMutableStore(hash, queue[0].NodeAddress, can.CandidateMutable); err != nil { return err @@ -158,7 +158,7 @@ func TestWithdrawDelegateRewardWithReward(t *testing.T) { if err := contact.Plugin.AllocatePackageBlock(hash, header, blockReward, chain.StateDB); err != nil { return err } - if xutil.IsEndOfEpoch(header.Number.Uint64()) { + if xutil.IsEndOfEpoch(header.Number.Uint64(), acverion) { verifierList, err := contact.Plugin.AllocateStakingReward(header.Number.Uint64(), hash, stakingReward, chain.StateDB) if err != nil { @@ -168,7 +168,7 @@ func TestWithdrawDelegateRewardWithReward(t *testing.T) { return err } - if err := stkDB.SetEpochValList(hash, index[xutil.CalculateEpoch(header.Number.Uint64())].Start, index[xutil.CalculateEpoch(header.Number.Uint64())].End, queue); err != nil { + if err := stkDB.SetEpochValList(hash, index[xutil.CalculateEpoch(header.Number.Uint64(), acverion)].Start, index[xutil.CalculateEpoch(header.Number.Uint64(), acverion)].End, queue); err != nil { return err } @@ -300,10 +300,11 @@ func TestWithdrawDelegateRewardWithMultiNode(t *testing.T) { }) stkDB := staking.NewStakingDBWithDB(chain.SnapDB) - index, queue, can, delegate := generateStk(1000, big.NewInt(params.ATP*3), xutil.CalcBlocksEachEpoch()*2+10) + acVersion := gov.GetCurrentActiveVersion(chain.StateDB) + index, queue, can, delegate := generateStk(1000, big.NewInt(params.ATP*3), xutil.CalcBlocksEachEpoch(acVersion)*2+10) _, queue2, can2, delegate2 := generateStk(1000, big.NewInt(params.ATP*3), 10) queue = append(queue, queue2...) - _, queue3, can3, delegate3 := generateStk(1000, big.NewInt(params.ATP*3), xutil.CalcBlocksEachEpoch()+10) + _, queue3, can3, delegate3 := generateStk(1000, big.NewInt(params.ATP*3), xutil.CalcBlocksEachEpoch(acVersion)+10) queue = append(queue, queue3...) chain.AddBlockWithSnapDB(true, func(hash common.Hash, header *types.Header, sdb snapshotdb.DB) error { if err := stkDB.SetEpochValIndex(hash, index); err != nil { @@ -358,9 +359,9 @@ func TestWithdrawDelegateRewardWithMultiNode(t *testing.T) { t.Fatal("AddActiveVersion, err", err) } - for i := 0; i < int(xutil.CalcBlocksEachEpoch()*3); i++ { + for i := 0; i < int(xutil.CalcBlocksEachEpoch(acVersion)*3); i++ { if err := chain.AddBlockWithSnapDB(true, func(hash common.Hash, header *types.Header, sdb snapshotdb.DB) error { - if xutil.IsBeginOfEpoch(header.Number.Uint64()) { + if xutil.IsBeginOfEpoch(header.Number.Uint64(), acVersion) { can.CandidateMutable.CleanCurrentEpochDelegateReward() if err := stkDB.SetCanMutableStore(hash, queue[0].NodeAddress, can.CandidateMutable); err != nil { return err @@ -376,7 +377,7 @@ func TestWithdrawDelegateRewardWithMultiNode(t *testing.T) { if err := contact.Plugin.AllocatePackageBlock(hash, header, blockReward, chain.StateDB); err != nil { return err } - if xutil.IsEndOfEpoch(header.Number.Uint64()) { + if xutil.IsEndOfEpoch(header.Number.Uint64(), acVersion) { verifierList, err := contact.Plugin.AllocateStakingReward(header.Number.Uint64(), hash, stakingReward, chain.StateDB) if err != nil { @@ -386,7 +387,7 @@ func TestWithdrawDelegateRewardWithMultiNode(t *testing.T) { return err } - if err := stkDB.SetEpochValList(hash, index[xutil.CalculateEpoch(header.Number.Uint64())].Start, index[xutil.CalculateEpoch(header.Number.Uint64())].End, queue); err != nil { + if err := stkDB.SetEpochValList(hash, index[xutil.CalculateEpoch(header.Number.Uint64(), acVersion)].Start, index[xutil.CalculateEpoch(header.Number.Uint64(), acVersion)].End, queue); err != nil { return err } diff --git a/core/vm/gov_contract_test.go b/core/vm/gov_contract_test.go index 9edf2c964d..e15fb80195 100644 --- a/core/vm/gov_contract_test.go +++ b/core/vm/gov_contract_test.go @@ -22,6 +22,8 @@ import ( "math/big" "testing" + "github.com/AlayaNetwork/Alaya-Go/params" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" //"github.com/AlayaNetwork/Alaya-Go/log" @@ -135,7 +137,7 @@ func buildSubmitVersionInput() []byte { input = append(input, common.MustRlpEncode(nodeIdArr[0])) // param 1 ... input = append(input, common.MustRlpEncode("verionPIPID")) input = append(input, common.MustRlpEncode(promoteVersion)) //new version : 1.1.1 - input = append(input, common.MustRlpEncode(xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds()))) + input = append(input, common.MustRlpEncode(xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion))) return common.MustRlpEncode(input) } @@ -158,7 +160,7 @@ func buildSubmitCancelInput() []byte { input = append(input, common.MustRlpEncode(uint16(2005))) // func type code input = append(input, common.MustRlpEncode(nodeIdArr[0])) // param 1 .. input = append(input, common.MustRlpEncode("cancelPIPID")) - input = append(input, common.MustRlpEncode(xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())-1)) + input = append(input, common.MustRlpEncode(xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion)-1)) input = append(input, common.MustRlpEncode(defaultProposalID)) return common.MustRlpEncode(input) } @@ -303,7 +305,7 @@ func setup(t *testing.T) *mock.Chain { if _, err := gov.InitGenesisGovernParam(common.ZeroHash, chain.SnapDB, 2048); err != nil { t.Error("error", err) } - gov.RegisterGovernParamVerifiers() + gov.RegisterGovernParamVerifiers(params.GenesisVersion) commit_sndb(chain) @@ -601,12 +603,12 @@ func TestGovContract_SubmitVersion_AnotherVoting(t *testing.T) { defer clear(chain, t) //submit a proposal - runGovContract(false, gc, buildSubmitVersion(nodeIdArr[1], "versionPIPID", promoteVersion, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())), t) + runGovContract(false, gc, buildSubmitVersion(nodeIdArr[1], "versionPIPID", promoteVersion, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion)), t) commit_sndb(chain) prepair_sndb(chain, txHashArr[2]) //submit a proposal - runGovContract(false, gc, buildSubmitVersion(nodeIdArr[2], "versionPIPID2", promoteVersion, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())), t, gov.VotingVersionProposalExist) + runGovContract(false, gc, buildSubmitVersion(nodeIdArr[2], "versionPIPID2", promoteVersion, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion)), t, gov.VotingVersionProposalExist) } func TestGovContract_SubmitVersion_Passed(t *testing.T) { @@ -698,7 +700,7 @@ func TestGovContract_SubmitVersion_AnotherPreActive(t *testing.T) { //skip empty blocks, this version proposal is pre-active skip_emptyBlock(chain, p.GetActiveBlock()-1) //submit another version proposal - runGovContract(false, gc, buildSubmitVersion(nodeIdArr[2], "versionPIPID2", promoteVersion, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())), t, gov.PreActiveVersionProposalExist) + runGovContract(false, gc, buildSubmitVersion(nodeIdArr[2], "versionPIPID2", promoteVersion, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion)), t, gov.PreActiveVersionProposalExist) } func TestGovContract_SubmitVersion_Passed_Clear(t *testing.T) { @@ -792,7 +794,7 @@ func TestGovContract_SubmitVersion_Passed_Clear(t *testing.T) { func TestGovContract_SubmitVersion_NewVersionError(t *testing.T) { chain := setup(t) defer clear(chain, t) - runGovContract(false, gc, buildSubmitVersion(nodeIdArr[1], "versionPIPID", uint32(32), xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())), t, gov.NewVersionError) + runGovContract(false, gc, buildSubmitVersion(nodeIdArr[1], "versionPIPID", uint32(32), xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion)), t, gov.NewVersionError) } func TestGovContract_SubmitVersion_EndVotingRoundsTooSmall(t *testing.T) { @@ -806,7 +808,7 @@ func TestGovContract_SubmitVersion_EndVotingRoundsTooLarge(t *testing.T) { defer clear(chain, t) //the default rounds is 6 for developer test net - runGovContract(false, gc, buildSubmitVersion(nodeIdArr[1], "versionPIPID", promoteVersion, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())+1), t, gov.EndVotingRoundsTooLarge) + runGovContract(false, gc, buildSubmitVersion(nodeIdArr[1], "versionPIPID", promoteVersion, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion)+1), t, gov.EndVotingRoundsTooLarge) } func TestGovContract_DeclareVersion_VotingStage_NotVoted_DeclareActiveVersion(t *testing.T) { @@ -981,16 +983,16 @@ func TestGovContract_SubmitCancel_AnotherVoting(t *testing.T) { defer clear(chain, t) //submit a proposal - runGovContract(false, gc, buildSubmitVersion(nodeIdArr[0], "versionPIPID", promoteVersion, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())), t) + runGovContract(false, gc, buildSubmitVersion(nodeIdArr[0], "versionPIPID", promoteVersion, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion)), t) commit_sndb(chain) prepair_sndb(chain, txHashArr[2]) - runGovContract(false, gc, buildSubmitCancel(nodeIdArr[1], "cancelPIPID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())-1, defaultProposalID), t) + runGovContract(false, gc, buildSubmitCancel(nodeIdArr[1], "cancelPIPID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion)-1, defaultProposalID), t) commit_sndb(chain) prepair_sndb(chain, txHashArr[3]) - runGovContract(false, gc, buildSubmitCancel(nodeIdArr[2], "cancelPIPIDAnother", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())-1, defaultProposalID), t, gov.VotingCancelProposalExist) + runGovContract(false, gc, buildSubmitCancel(nodeIdArr[2], "cancelPIPIDAnother", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion)-1, defaultProposalID), t, gov.VotingCancelProposalExist) } func TestGovContract_SubmitCancel_EndVotingRounds_TooLarge(t *testing.T) { @@ -1000,7 +1002,7 @@ func TestGovContract_SubmitCancel_EndVotingRounds_TooLarge(t *testing.T) { commit_sndb(chain) prepair_sndb(chain, txHashArr[2]) - runGovContract(false, gc, buildSubmitCancel(nodeIdArr[0], "cancelPIPID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds()), defaultProposalID), t, gov.EndVotingRoundsTooLarge) + runGovContract(false, gc, buildSubmitCancel(nodeIdArr[0], "cancelPIPID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion), defaultProposalID), t, gov.EndVotingRoundsTooLarge) } func TestGovContract_SubmitCancel_EndVotingRounds_TobeCanceledNotExist(t *testing.T) { @@ -1012,7 +1014,7 @@ func TestGovContract_SubmitCancel_EndVotingRounds_TobeCanceledNotExist(t *testin prepair_sndb(chain, txHashArr[2]) //the version proposal's endVotingRounds=5 - runGovContract(false, gc, buildSubmitCancel(nodeIdArr[0], "cancelPIPID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())-1, txHashArr[3]), t, gov.TobeCanceledProposalNotFound) + runGovContract(false, gc, buildSubmitCancel(nodeIdArr[0], "cancelPIPID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion)-1, txHashArr[3]), t, gov.TobeCanceledProposalNotFound) } func TestGovContract_SubmitCancel_EndVotingRounds_TobeCanceledNotVersionProposal(t *testing.T) { @@ -1024,7 +1026,7 @@ func TestGovContract_SubmitCancel_EndVotingRounds_TobeCanceledNotVersionProposal prepair_sndb(chain, txHashArr[2]) //try to cancel a text proposal - runGovContract(false, gc, buildSubmitCancel(nodeIdArr[0], "cancelPIPID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())-1, defaultProposalID), t, gov.TobeCanceledProposalTypeError) + runGovContract(false, gc, buildSubmitCancel(nodeIdArr[0], "cancelPIPID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion)-1, defaultProposalID), t, gov.TobeCanceledProposalTypeError) } func TestGovContract_SubmitCancel_EndVotingRounds_TobeCanceledNotAtVotingStage(t *testing.T) { @@ -1044,7 +1046,7 @@ func TestGovContract_SubmitCancel_EndVotingRounds_TobeCanceledNotAtVotingStage(t prepair_sndb(chain, txHashArr[3]) //try to cancel a closed version proposal - runGovContract(false, gc, buildSubmitCancel(nodeIdArr[0], "cancelPIPID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())-1, defaultProposalID), t, gov.TobeCanceledProposalNotAtVoting) + runGovContract(false, gc, buildSubmitCancel(nodeIdArr[0], "cancelPIPID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion)-1, defaultProposalID), t, gov.TobeCanceledProposalNotAtVoting) } func TestGovContract_GetCancelProposal(t *testing.T) { @@ -1056,7 +1058,7 @@ func TestGovContract_GetCancelProposal(t *testing.T) { prepair_sndb(chain, txHashArr[2]) //submit a proposal and get it. - runGovContract(false, gc, buildSubmitCancel(nodeIdArr[0], "cancelPIPID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())-1, defaultProposalID), t) + runGovContract(false, gc, buildSubmitCancel(nodeIdArr[0], "cancelPIPID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion)-1, defaultProposalID), t) commit_sndb(chain) prepair_sndb(chain, txHashArr[3]) diff --git a/core/vm/platon_contract_test.go b/core/vm/platon_contract_test.go index 23038acbf1..300a16670c 100644 --- a/core/vm/platon_contract_test.go +++ b/core/vm/platon_contract_test.go @@ -511,7 +511,7 @@ func build_staking_data(genesisHash common.Hash) { epoch_Arr := &staking.ValidatorArray{ Start: 1, - End: uint64(xutil.CalcBlocksEachEpoch()), + End: uint64(xutil.CalcBlocksEachEpoch(params.GenesisVersion)), Arr: queue, } @@ -523,7 +523,7 @@ func build_staking_data(genesisHash common.Hash) { curr_Arr := &staking.ValidatorArray{ Start: 1, - End: uint64(xutil.ConsensusSize()), + End: uint64(xcom.ConsensusSize(params.GenesisVersion)), Arr: queue, } @@ -717,7 +717,7 @@ func build_staking_data_new(chain *mock.Chain) { epoch_Arr := &staking.ValidatorArray{ Start: 1, - End: uint64(xutil.CalcBlocksEachEpoch()), + End: uint64(xutil.CalcBlocksEachEpoch(params.GenesisVersion)), Arr: queue, } @@ -729,7 +729,7 @@ func build_staking_data_new(chain *mock.Chain) { curr_Arr := &staking.ValidatorArray{ Start: 1, - End: uint64(xutil.ConsensusSize()), + End: uint64(xcom.ConsensusSize(params.GenesisVersion)), Arr: queue, } @@ -872,8 +872,8 @@ func build_staking_data_more(chain *mock.Chain) { epoch_Arr := &staking.ValidatorArray{ //Start: ((block-1)/22000)*22000 + 1, //End: ((block-1)/22000)*22000 + 22000, - Start: ((chain.CurrentHeader().Number.Uint64()-1)/uint64(xutil.CalcBlocksEachEpoch()))*uint64(xutil.CalcBlocksEachEpoch()) + 1, - End: ((chain.CurrentHeader().Number.Uint64()-1)/uint64(xutil.CalcBlocksEachEpoch()))*uint64(xutil.CalcBlocksEachEpoch()) + uint64(xutil.CalcBlocksEachEpoch()), + Start: ((chain.CurrentHeader().Number.Uint64()-1)/uint64(xutil.CalcBlocksEachEpoch(params.GenesisVersion)))*uint64(xutil.CalcBlocksEachEpoch(params.GenesisVersion)) + 1, + End: ((chain.CurrentHeader().Number.Uint64()-1)/uint64(xutil.CalcBlocksEachEpoch(params.GenesisVersion)))*uint64(xutil.CalcBlocksEachEpoch(params.GenesisVersion)) + uint64(xutil.CalcBlocksEachEpoch(params.GenesisVersion)), Arr: queue, } @@ -886,8 +886,8 @@ func build_staking_data_more(chain *mock.Chain) { curr_Arr := &staking.ValidatorArray{ //Start: ((block-1)/250)*250 + 1, //End: ((block-1)/250)*250 + 250, - Start: ((chain.CurrentHeader().Number.Uint64()-1)/uint64(xutil.ConsensusSize()))*uint64(xutil.ConsensusSize()) + 1, - End: ((chain.CurrentHeader().Number.Uint64()-1)/uint64(xutil.ConsensusSize()))*uint64(xutil.ConsensusSize()) + uint64(xutil.ConsensusSize()), + Start: ((chain.CurrentHeader().Number.Uint64()-1)/uint64(xcom.ConsensusSize(params.GenesisVersion)))*uint64(xcom.ConsensusSize(params.GenesisVersion)) + 1, + End: ((chain.CurrentHeader().Number.Uint64()-1)/uint64(xcom.ConsensusSize(params.GenesisVersion)))*uint64(xcom.ConsensusSize(params.GenesisVersion)) + uint64(xcom.ConsensusSize(params.GenesisVersion)), Arr: queue, } diff --git a/core/vm/staking_contract_test.go b/core/vm/staking_contract_test.go index 639b570a8c..2e21a85b97 100644 --- a/core/vm/staking_contract_test.go +++ b/core/vm/staking_contract_test.go @@ -287,7 +287,7 @@ func TestStakingContract_editCandidate(t *testing.T) { contract2 := &StakingContract{ Plugin: plugin.StakingInstance(), Contract: newContract(common.Big0, sender), - Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch()*uint64(xcom.RewardPerChangeInterval())*2), blockHash2, state), + Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch(params.GenesisVersion)*uint64(xcom.RewardPerChangeInterval())*2), blockHash2, state), } // get CandidateInfo @@ -450,7 +450,7 @@ func TestStakingContract_editCandidate_updateRewardPer2(t *testing.T) { contract2 := &StakingContract{ Plugin: plugin.StakingInstance(), Contract: newContract(common.Big0, sender), - Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch()*uint64(xcom.RewardPerChangeInterval())*2), blockHash2, state), + Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch(params.GenesisVersion)*uint64(xcom.RewardPerChangeInterval())*2), blockHash2, state), } // get CandidateInfo @@ -535,7 +535,7 @@ func TestStakingContract_editCandidate_updateRewardPer3(t *testing.T) { contract2 := &StakingContract{ Plugin: plugin.StakingInstance(), Contract: newContract(common.Big0, sender), - Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch()*uint64(xcom.RewardPerChangeInterval())*2), blockHash2, state), + Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch(params.GenesisVersion)*uint64(xcom.RewardPerChangeInterval())*2), blockHash2, state), } // get CandidateInfo @@ -620,7 +620,7 @@ func TestStakingContract_editCandidate_continuousUpdateRewardPer(t *testing.T) { contract2 := &StakingContract{ Plugin: plugin.StakingInstance(), Contract: newContract(common.Big0, sender), - Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch()*uint64(xcom.RewardPerChangeInterval())*2), blockHash2, state), + Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch(params.GenesisVersion)*uint64(xcom.RewardPerChangeInterval())*2), blockHash2, state), } // get CandidateInfo @@ -672,7 +672,7 @@ func TestStakingContract_editCandidate_continuousUpdateRewardPer(t *testing.T) { contract2 = &StakingContract{ Plugin: plugin.StakingInstance(), Contract: newContract(common.Big0, sender), - Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch()*uint64(xcom.RewardPerChangeInterval())*4), blockHash2, state), + Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch(params.GenesisVersion)*uint64(xcom.RewardPerChangeInterval())*4), blockHash2, state), } // get CandidateInfo @@ -755,7 +755,7 @@ func TestStakingContract_editCandidate_updateNilRewardPer0140(t *testing.T) { contract2 := &StakingContract{ Plugin: plugin.StakingInstance(), Contract: newContract(common.Big0, sender), - Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch()*uint64(xcom.RewardPerChangeInterval())*4), blockHash2, state), + Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch(params.GenesisVersion)*uint64(xcom.RewardPerChangeInterval())*4), blockHash2, state), } // get CandidateInfo @@ -851,7 +851,7 @@ func TestStakingContract_editCandidate_updateNilRewardPer0140Err(t *testing.T) { contract2 := &StakingContract{ Plugin: plugin.StakingInstance(), Contract: newContract(common.Big0, sender), - Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch()*uint64(xcom.RewardPerChangeInterval())*4), blockHash2, state), + Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch(params.GenesisVersion)*uint64(xcom.RewardPerChangeInterval())*4), blockHash2, state), } // get CandidateInfo @@ -947,7 +947,7 @@ func TestStakingContract_editCandidate_updateNilRewardPer(t *testing.T) { contract2 := &StakingContract{ Plugin: plugin.StakingInstance(), Contract: newContract(common.Big0, sender), - Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch()*uint64(xcom.RewardPerChangeInterval())*4), blockHash2, state), + Evm: newEvm(new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch(params.GenesisVersion)*uint64(xcom.RewardPerChangeInterval())*4), blockHash2, state), } // get CandidateInfo @@ -1592,7 +1592,7 @@ func TestStakingContract_DelegateMerge(t *testing.T) { if _, err := gov.InitGenesisGovernParam(common.ZeroHash, chain.SnapDB, 2048); err != nil { t.Error("error", err) } - gov.RegisterGovernParamVerifiers() + gov.RegisterGovernParamVerifiers(params.GenesisVersion) privateKey, _ := crypto.GenerateKey() stakingAdd := crypto.PubkeyToAddress(privateKey.PublicKey) diff --git a/x/gov/gov_db_test.go b/x/gov/gov_db_test.go index 52b44a23a5..37153628c4 100644 --- a/x/gov/gov_db_test.go +++ b/x/gov/gov_db_test.go @@ -1057,7 +1057,7 @@ func TestSet0140Param(t *testing.T) { var paramItemList []*ParamItem - initParamList := queryInitParam() + initParamList := queryInitParam(params.GenesisVersion) var err error for _, param := range initParamList { diff --git a/x/gov/gov_test.go b/x/gov/gov_test.go index c3214f6188..aa3bc6547f 100644 --- a/x/gov/gov_test.go +++ b/x/gov/gov_test.go @@ -188,7 +188,7 @@ func setup(t *testing.T) *mock.Chain { t.Error("InitGenesisGovernParam, error", err) } - RegisterGovernParamVerifiers() + RegisterGovernParamVerifiers(params.GenesisVersion) if err := AddActiveVersion(params.GenesisVersion, 0, chain.StateDB); err != nil { t.Error("AddActiveVersion, err", err) diff --git a/x/plugin/gov_plugin_test.go b/x/plugin/gov_plugin_test.go index 02615d919d..ad7d7ba2ab 100644 --- a/x/plugin/gov_plugin_test.go +++ b/x/plugin/gov_plugin_test.go @@ -140,7 +140,7 @@ func submitVersion(t *testing.T, pid common.Hash) { ProposalType: gov.Version, PIPID: "versionIPID", SubmitBlock: 1, - EndVotingRounds: xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds()), + EndVotingRounds: xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), currentTestGenesisVersion), Proposer: nodeIdArr[0], NewVersion: promoteVersion, } @@ -172,7 +172,7 @@ func submitCancel(t *testing.T, pid, tobeCanceled common.Hash) { ProposalType: gov.Cancel, PIPID: "CancelPIPID", SubmitBlock: 1, - EndVotingRounds: xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds()) - 1, + EndVotingRounds: xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), currentTestGenesisVersion) - 1, Proposer: nodeIdArr[0], TobeCanceled: tobeCanceled, } @@ -417,7 +417,7 @@ func TestGovPlugin_SubmitVersion(t *testing.T) { func TestGovPlugin_SubmitVersion_PIPID_empty(t *testing.T) { defer setup(t)() - vp := buildVersionProposal(txHashArr[0], "", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds()), uint32(1<<16|2<<8|0)) + vp := buildVersionProposal(txHashArr[0], "", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), currentTestGenesisVersion), uint32(1<<16|2<<8|0)) err := gov.Submit(sender, vp, lastBlockHash, lastBlockNumber, stk, stateDB, chainID) if err != nil { if err == gov.PIPIDEmpty { @@ -433,7 +433,7 @@ func TestGovPlugin_SubmitVersion_PIPID_duplicated(t *testing.T) { t.Log("CurrentActiveVersion", "version", gov.GetCurrentActiveVersion(stateDB)) - vp := buildVersionProposal(txHashArr[0], "pipID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds()), uint32(1<<16|2<<8|0)) + vp := buildVersionProposal(txHashArr[0], "pipID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), currentTestGenesisVersion), uint32(1<<16|2<<8|0)) err := gov.Submit(sender, vp, lastBlockHash, lastBlockNumber, stk, stateDB, chainID) if err != nil { @@ -448,7 +448,7 @@ func TestGovPlugin_SubmitVersion_PIPID_duplicated(t *testing.T) { t.Log("ListPIPID", "p", p) } - vp2 := buildVersionProposal(txHashArr[1], "pipID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds()), uint32(1<<16|3<<8|0)) + vp2 := buildVersionProposal(txHashArr[1], "pipID", xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), currentTestGenesisVersion), uint32(1<<16|3<<8|0)) err = gov.Submit(sender, vp2, lastBlockHash, lastBlockNumber, stk, stateDB, chainID) if err != nil { @@ -468,7 +468,7 @@ func TestGovPlugin_SubmitVersion_invalidEndVotingRounds(t *testing.T) { ProposalType: gov.Version, PIPID: "versionPIPID", SubmitBlock: 1, - EndVotingRounds: xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds()) + 1, //error + EndVotingRounds: xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), currentTestGenesisVersion) + 1, //error Proposer: nodeIdArr[0], NewVersion: promoteVersion, } @@ -530,7 +530,7 @@ func TestGovPlugin_SubmitVersion_NewVersionError(t *testing.T) { ProposalType: gov.Version, PIPID: "versionPIPID", SubmitBlock: 1, - EndVotingRounds: xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds()), + EndVotingRounds: xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), currentTestGenesisVersion), Proposer: nodeIdArr[0], NewVersion: newVersionErr, //error, less than activeVersion } @@ -599,7 +599,7 @@ func TestGovPlugin_SubmitCancel_invalidEndVotingRounds(t *testing.T) { ProposalType: gov.Cancel, PIPID: "CancelPIPID", SubmitBlock: 1, - EndVotingRounds: xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds()), + EndVotingRounds: xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), currentTestGenesisVersion), Proposer: nodeIdArr[1], TobeCanceled: txHashArr[0], } @@ -625,7 +625,7 @@ func TestGovPlugin_SubmitCancel_noVersionProposal(t *testing.T) { ProposalType: gov.Cancel, PIPID: "cancelPIPID", SubmitBlock: 1, - EndVotingRounds: xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds()) - 1, + EndVotingRounds: xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), currentTestGenesisVersion) - 1, Proposer: nodeIdArr[0], TobeCanceled: txHashArr[0], } @@ -982,15 +982,15 @@ func TestGovPlugin_textProposalPassed(t *testing.T) { if err != nil { t.Fatal("find proposal error", "err", err) } - - lastBlockNumber = uint64(xutil.CalcBlocksEachEpoch() - 1) + acversion := gov.GetCurrentActiveVersion(stateDB) + lastBlockNumber = uint64(xutil.CalcBlocksEachEpoch(acversion) - 1) lastHeader = types.Header{ Number: big.NewInt(int64(lastBlockNumber)), } lastBlockHash = lastHeader.Hash() sndb.SetCurrent(lastBlockHash, *big.NewInt(int64(lastBlockNumber)), *big.NewInt(int64(lastBlockNumber))) - build_staking_data_more(uint64(xutil.CalcBlocksEachEpoch())) + build_staking_data_more(uint64(xutil.CalcBlocksEachEpoch(acversion))) beginBlock(t) sndb.Commit(lastBlockHash) sndb.Compaction() @@ -1031,7 +1031,8 @@ func TestGovPlugin_textProposalFailed(t *testing.T) { sndb.Commit(lastBlockHash) sndb.Compaction() - endVotingBlock := xutil.CalEndVotingBlock(1, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())) + acversion := gov.GetCurrentActiveVersion(stateDB) + endVotingBlock := xutil.CalEndVotingBlock(1, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), acversion), acversion) // actvieBlock := xutil.CalActiveBlock(endVotingBlock) buildBlockNoCommit(2) @@ -1040,14 +1041,14 @@ func TestGovPlugin_textProposalFailed(t *testing.T) { sndb.Commit(lastBlockHash) sndb.Compaction() - lastBlockNumber = uint64(xutil.CalcBlocksEachEpoch() - 1) + lastBlockNumber = uint64(xutil.CalcBlocksEachEpoch(acversion) - 1) lastHeader = types.Header{ Number: big.NewInt(int64(lastBlockNumber)), } lastBlockHash = lastHeader.Hash() sndb.SetCurrent(lastBlockHash, *big.NewInt(int64(lastBlockNumber)), *big.NewInt(int64(lastBlockNumber))) - build_staking_data_more(uint64(xutil.CalcBlocksEachEpoch())) + build_staking_data_more(uint64(xutil.CalcBlocksEachEpoch(acversion))) beginBlock(t) sndb.Commit(lastBlockHash) sndb.Compaction() @@ -1086,7 +1087,8 @@ func TestGovPlugin_versionProposalPreActive(t *testing.T) { sndb.Commit(lastBlockHash) sndb.Compaction() - endVotingBlock := xutil.CalEndVotingBlock(1, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())) + acversion := gov.GetCurrentActiveVersion(stateDB) + endVotingBlock := xutil.CalEndVotingBlock(1, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), acversion), acversion) // actvieBlock := xutil.CalActiveBlock(endVotingBlock) buildBlockNoCommit(2) @@ -1096,14 +1098,14 @@ func TestGovPlugin_versionProposalPreActive(t *testing.T) { sndb.Commit(lastBlockHash) sndb.Compaction() - lastBlockNumber = uint64(xutil.CalcBlocksEachEpoch() - 1) + lastBlockNumber = uint64(xutil.CalcBlocksEachEpoch(acversion) - 1) lastHeader = types.Header{ Number: big.NewInt(int64(lastBlockNumber)), } lastBlockHash = lastHeader.Hash() sndb.SetCurrent(lastBlockHash, *big.NewInt(int64(lastBlockNumber)), *big.NewInt(int64(lastBlockNumber))) - build_staking_data_more(uint64(xutil.CalcBlocksEachEpoch())) + build_staking_data_more(uint64(xutil.CalcBlocksEachEpoch(acversion))) beginBlock(t) @@ -1183,7 +1185,8 @@ func TestGovPlugin_versionProposalActive(t *testing.T) { sndb.Commit(lastBlockHash) sndb.Compaction() //flush to LevelDB - endVotingBlock := xutil.CalEndVotingBlock(1, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds())) + acversion := gov.GetCurrentActiveVersion(stateDB) + endVotingBlock := xutil.CalEndVotingBlock(1, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), acversion), acversion) actvieBlock := xutil.CalActiveBlock(endVotingBlock) buildBlockNoCommit(2) diff --git a/x/plugin/platon_plugin_test.go b/x/plugin/platon_plugin_test.go index ce3a607744..fc2b583b3a 100644 --- a/x/plugin/platon_plugin_test.go +++ b/x/plugin/platon_plugin_test.go @@ -56,6 +56,8 @@ func init() { bls.Init(bls.BLS12_381) } +var currentTestGenesisVersion = params.FORKVERSION_0_16_0 + var ( nodeIdArr = []enode.IDv0{ enode.MustHexIDv0("5a942bc607d970259e203f5110887d6105cc787f7433c16ce28390fb39f1e67897b0fb445710cc836b89ed7f951c57a1f26a0940ca308d630448b5bd391a8aa6"), @@ -439,8 +441,8 @@ func build_staking_data_more(block uint64) { epoch_Arr := &staking.ValidatorArray{ //Start: ((block-1)/22000)*22000 + 1, //End: ((block-1)/22000)*22000 + 22000, - Start: ((block-1)/uint64(xutil.CalcBlocksEachEpoch()))*uint64(xutil.CalcBlocksEachEpoch()) + 1, - End: ((block-1)/uint64(xutil.CalcBlocksEachEpoch()))*uint64(xutil.CalcBlocksEachEpoch()) + uint64(xutil.CalcBlocksEachEpoch()), + Start: ((block-1)/uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)))*uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)) + 1, + End: ((block-1)/uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)))*uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)) + uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)), Arr: queue, } @@ -453,8 +455,8 @@ func build_staking_data_more(block uint64) { curr_Arr := &staking.ValidatorArray{ //Start: ((block-1)/250)*250 + 1, //End: ((block-1)/250)*250 + 250, - Start: ((block-1)/uint64(xutil.ConsensusSize()))*uint64(xutil.ConsensusSize()) + 1, - End: ((block-1)/uint64(xutil.ConsensusSize()))*uint64(xutil.ConsensusSize()) + uint64(xutil.ConsensusSize()), + Start: ((block-1)/uint64(xcom.ConsensusSize(currentTestGenesisVersion)))*uint64(xcom.ConsensusSize(currentTestGenesisVersion)) + 1, + End: ((block-1)/uint64(xcom.ConsensusSize(currentTestGenesisVersion)))*uint64(xcom.ConsensusSize(currentTestGenesisVersion)) + uint64(xcom.ConsensusSize(currentTestGenesisVersion)), Arr: queue, } @@ -603,7 +605,7 @@ func build_staking_data(genesisHash common.Hash) { epoch_Arr := &staking.ValidatorArray{ Start: 1, - End: uint64(xutil.CalcBlocksEachEpoch()), + End: uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)), Arr: queue, } @@ -615,7 +617,7 @@ func build_staking_data(genesisHash common.Hash) { curr_Arr := &staking.ValidatorArray{ Start: 1, - End: uint64(xutil.ConsensusSize()), + End: uint64(xcom.ConsensusSize(currentTestGenesisVersion)), Arr: queue, } diff --git a/x/plugin/restricting_plugin_test.go b/x/plugin/restricting_plugin_test.go index aa77c7724d..becc1ac672 100644 --- a/x/plugin/restricting_plugin_test.go +++ b/x/plugin/restricting_plugin_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package plugin import ( @@ -72,8 +71,8 @@ func TestRestrictingPlugin_EndBlock(t *testing.T) { } var count int = 1 for _, entry := range res.Entry { - if entry.Height != uint64(count)*xutil.CalcBlocksEachEpoch() { - t.Errorf("release block number not cmp,want %v ,have %v ", uint64(count)*xutil.CalcBlocksEachEpoch(), entry.Height) + if entry.Height != uint64(count)*xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(chain.StateDB)) { + t.Errorf("release block number not cmp,want %v ,have %v ", uint64(count)*xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(chain.StateDB)), entry.Height) } if entry.Amount.ToInt().Cmp(big.NewInt(int64(1e18))) != 0 { t.Errorf("release amount not cmp,want %v ,have %v ", big.NewInt(int64(1e18)), entry.Amount) @@ -84,7 +83,7 @@ func TestRestrictingPlugin_EndBlock(t *testing.T) { t.Run("blockChain arrived settle block height, restricting plan not exist", func(t *testing.T) { chain := mock.NewChain() - blockNumber := uint64(1) * xutil.CalcBlocksEachEpoch() + blockNumber := uint64(1) * xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(chain.StateDB)) head := types.Header{Number: big.NewInt(int64(blockNumber))} err := RestrictingInstance().EndBlock(common.Hash{}, &head, chain.StateDB) if err != nil { @@ -169,7 +168,7 @@ func TestRestrictingPlugin_AddRestrictingRecord(t *testing.T) { plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e17)}) plans = append(plans, restricting.RestrictingPlan{2, big.NewInt(1e18)}) - if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch()-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { + if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) } _, rAmount := plugin.getReleaseAmount(mockDB, 1, to) @@ -222,7 +221,7 @@ func TestRestrictingPlugin_AddRestrictingRecord(t *testing.T) { plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e17)}) plans = append(plans, restricting.RestrictingPlan{2, big.NewInt(1e18)}) plans = append(plans, restricting.RestrictingPlan{3, big.NewInt(1e18)}) - if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch()-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { + if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) } @@ -273,7 +272,7 @@ func TestRestrictingPlugin_Compose3(t *testing.T) { plugin := NewTestRestrictingPlugin() plans := make([]restricting.RestrictingPlan, 0) plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e18)}) - if err := plugin.AddRestrictingRecord(plugin.from, plugin.to, xutil.CalcBlocksEachEpoch()-10, common.ZeroHash, plans, plugin.mockDB, RestrictingTxHash); err != nil { + if err := plugin.AddRestrictingRecord(plugin.from, plugin.to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(plugin.mockDB))-10, common.ZeroHash, plans, plugin.mockDB, RestrictingTxHash); err != nil { t.Error(err) } if err := plugin.AdvanceLockedFunds(plugin.to, big.NewInt(1e18), plugin.mockDB); err != nil { @@ -305,7 +304,7 @@ func TestRestrictingPlugin_Compose2(t *testing.T) { plans := make([]restricting.RestrictingPlan, 0) plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e18)}) plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e18)}) - if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch()-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { + if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) } if err := plugin.AdvanceLockedFunds(to, big.NewInt(2e18), mockDB); err != nil { @@ -316,7 +315,7 @@ func TestRestrictingPlugin_Compose2(t *testing.T) { } plans2 := []restricting.RestrictingPlan{restricting.RestrictingPlan{1, big.NewInt(3e18)}} - if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch()+10, common.ZeroHash, plans2, mockDB, RestrictingTxHash); err != nil { + if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))+10, common.ZeroHash, plans2, mockDB, RestrictingTxHash); err != nil { t.Error(err) } infoAssertF := func(CachePlanAmount *big.Int, ReleaseList []uint64, StakingAmount *big.Int, NeedRelease *big.Int) { @@ -359,7 +358,7 @@ func TestRestrictingPlugin_Compose(t *testing.T) { plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e18)}) plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e18)}) - if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch()-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { + if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) } assert.Equal(t, mockDB.GetBalance(from), big.NewInt(7e18)) @@ -384,7 +383,7 @@ func TestRestrictingPlugin_Compose(t *testing.T) { infoAssertF(big.NewInt(2e18), []uint64{}, big.NewInt(2e18), big.NewInt(2e18)) plans2 := []restricting.RestrictingPlan{restricting.RestrictingPlan{1, big.NewInt(1e18)}} - if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch()+10, common.ZeroHash, plans2, mockDB, RestrictingTxHash); err != nil { + if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))+10, common.ZeroHash, plans2, mockDB, RestrictingTxHash); err != nil { t.Error(err) } assert.Equal(t, mockDB.GetBalance(from), big.NewInt(6e18)) @@ -417,7 +416,8 @@ func TestRestrictingPlugin_GetRestrictingInfo(t *testing.T) { for _, value := range plans { total.Add(total, value.Amount) } - if err := RestrictingInstance().AddRestrictingRecord(addrArr[1], addrArr[0], xutil.CalcBlocksEachEpoch()-10, common.ZeroHash, plans, chain.StateDB, RestrictingTxHash); err != nil { + acverion := gov.GetCurrentActiveVersion(chain.StateDB) + if err := RestrictingInstance().AddRestrictingRecord(addrArr[1], addrArr[0], xutil.CalcBlocksEachEpoch(acverion)-10, common.ZeroHash, plans, chain.StateDB, RestrictingTxHash); err != nil { t.Error(err) } @@ -436,15 +436,15 @@ func TestRestrictingPlugin_GetRestrictingInfo(t *testing.T) { t.Error("wrong num of RestrictingInfo Entry") } - if res.Entry[0].Height != uint64(1)*xutil.CalcBlocksEachEpoch() { - t.Errorf("release block num is not right,want %v have %v", uint64(1)*xutil.CalcBlocksEachEpoch(), res.Entry[0].Height) + if res.Entry[0].Height != uint64(1)*xutil.CalcBlocksEachEpoch(acverion) { + t.Errorf("release block num is not right,want %v have %v", uint64(1)*xutil.CalcBlocksEachEpoch(acverion), res.Entry[0].Height) } if res.Entry[0].Amount.ToInt().Cmp(big.NewInt(2e18)) != 0 { t.Errorf("release amount not compare ,want %v have %v", big.NewInt(2e18), res.Entry[0].Amount) } - if res.Entry[1].Height != uint64(2)*xutil.CalcBlocksEachEpoch() { - t.Errorf("release block num is not right,want %v have %v", uint64(2)*xutil.CalcBlocksEachEpoch(), res.Entry[1].Height) + if res.Entry[1].Height != uint64(2)*xutil.CalcBlocksEachEpoch(acverion) { + t.Errorf("release block num is not right,want %v have %v", uint64(2)*xutil.CalcBlocksEachEpoch(acverion), res.Entry[1].Height) } if res.Entry[1].Amount.ToInt().Cmp(big.NewInt(1e18)) != 0 { t.Errorf("release amount not compare ,want %v have %v", big.NewInt(1e18), res.Entry[1].Amount) @@ -463,7 +463,7 @@ func TestRestrictingInstance(t *testing.T) { plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(3e18)}) plans = append(plans, restricting.RestrictingPlan{2, big.NewInt(4e18)}) plans = append(plans, restricting.RestrictingPlan{3, big.NewInt(2e18)}) - if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch()-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { + if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) } if err := plugin.releaseRestricting(1, mockDB); err != nil { @@ -483,7 +483,8 @@ func TestRestrictingInstance(t *testing.T) { // SetLatestEpoch(mockDB, 3) plans2 := make([]restricting.RestrictingPlan, 0) plans2 = append(plans2, restricting.RestrictingPlan{1, big.NewInt(1e18)}) - if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch()*3+10, common.ZeroHash, plans2, mockDB, RestrictingTxHash); err != nil { + + if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))*3+10, common.ZeroHash, plans2, mockDB, RestrictingTxHash); err != nil { t.Error(err) } if err := plugin.ReturnLockFunds(to, big.NewInt(5e18), mockDB); err != nil { @@ -522,7 +523,7 @@ func TestNewRestrictingPlugin_MixAdvanceLockedFunds(t *testing.T) { plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(3e18)}) plans = append(plans, restricting.RestrictingPlan{2, big.NewInt(4e18)}) plans = append(plans, restricting.RestrictingPlan{3, big.NewInt(2e18)}) - if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch()-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { + if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) } mockDB.AddBalance(to, big.NewInt(2e18)) @@ -560,7 +561,7 @@ func TestRestrictingInstanceWithSlashing(t *testing.T) { plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(3e18)}) plans = append(plans, restricting.RestrictingPlan{2, big.NewInt(4e18)}) plans = append(plans, restricting.RestrictingPlan{3, big.NewInt(2e18)}) - if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch()-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { + if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) } @@ -590,7 +591,7 @@ func TestRestrictingInstanceWithSlashing(t *testing.T) { plans2 := make([]restricting.RestrictingPlan, 0) plans2 = append(plans2, restricting.RestrictingPlan{1, big.NewInt(1e18)}) - if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch()*3+10, common.ZeroHash, plans2, mockDB, RestrictingTxHash); err != nil { + if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))*3+10, common.ZeroHash, plans2, mockDB, RestrictingTxHash); err != nil { t.Error(err) } if err := plugin.ReturnLockFunds(to, big.NewInt(4e18), mockDB); err != nil { @@ -628,7 +629,7 @@ func TestRestrictingGetRestrictingInfo(t *testing.T) { plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(3e18)}) plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(3e18)}) - if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch()-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { + if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) } res, err := plugin.getRestrictingInfoToReturn(to, mockDB) @@ -670,7 +671,7 @@ func TestRestrictingReturnLockFunds(t *testing.T) { return } - for i := 0; i <= int(xutil.CalcBlocksEachEpoch()+10); i++ { + for i := 0; i <= int(xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(chain.StateDB))+10); i++ { if err := chain.AddBlockWithSnapDB(true, nil, nil, nil); err != nil { t.Error(err) return @@ -759,7 +760,7 @@ func TestRestrictingForkAdvanceLockedFunds(t *testing.T) { return } - for i := 0; i <= int(xutil.CalcBlocksEachEpoch()+10); i++ { + for i := 0; i <= int(xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(chain.StateDB))+10); i++ { if err := chain.AddBlockWithSnapDB(true, nil, nil, nil); err != nil { t.Error(err) return diff --git a/x/plugin/reward_plugin.go b/x/plugin/reward_plugin.go index db2a2c8dfe..2ecc546720 100644 --- a/x/plugin/reward_plugin.go +++ b/x/plugin/reward_plugin.go @@ -121,12 +121,12 @@ func (rmp *RewardMgrPlugin) EndBlock(blockHash common.Hash, head *types.Header, } if xutil.IsEndOfEpoch(blockNumber, head.GetActiveVersion()) { - verifierList, err := rmp.AllocateStakingReward(head, blockHash, stakingReward, state) + verifierList, err := rmp.AllocateStakingReward(head.Number.Uint64(), blockHash, stakingReward, state) if err != nil { return err } - if err := rmp.HandleDelegatePerReward(head, blockHash, verifierList, state); err != nil { + if err := rmp.HandleDelegatePerReward(blockHash, head.Number.Uint64(), verifierList, state); err != nil { return err } @@ -213,17 +213,17 @@ func (rmp *RewardMgrPlugin) increaseIssuance(thisYear, lastYear uint32, state xc } // AllocateStakingReward used for reward staking at the settle block -func (rmp *RewardMgrPlugin) AllocateStakingReward(head *types.Header, blockHash common.Hash, sreward *big.Int, state xcom.StateDB) ([]*staking.Candidate, error) { +func (rmp *RewardMgrPlugin) AllocateStakingReward(blockNumber uint64, blockHash common.Hash, sreward *big.Int, state xcom.StateDB) ([]*staking.Candidate, error) { - log.Info("Allocate staking reward start", "blockNumber", head.Number.Uint64(), "hash", blockHash, - "epoch", xutil.CalculateEpoch(head.Number.Uint64(), head.GetActiveVersion()), "reward", sreward) - verifierList, err := rmp.stakingPlugin.GetVerifierCandidateInfo(blockHash, head.Number.Uint64()) + log.Info("Allocate staking reward start", "blockNumber", blockNumber, "hash", blockHash, + "epoch", xutil.CalculateEpoch(blockNumber, gov.GetCurrentActiveVersion(state)), "reward", sreward) + verifierList, err := rmp.stakingPlugin.GetVerifierCandidateInfo(blockHash, blockNumber) if err != nil { - log.Error("Failed to AllocateStakingReward: call GetVerifierList is failed", "blockNumber", head.Number.Uint64(), "hash", blockHash, "err", err) + log.Error("Failed to AllocateStakingReward: call GetVerifierList is failed", "blockNumber", blockNumber, "hash", blockHash, "err", err) return nil, err } if err := rmp.rewardStakingByValidatorList(state, verifierList, sreward); err != nil { - log.Error("reward staking by validator list fail", "err", err, "bn", head.Number.Uint64(), "bh", blockHash) + log.Error("reward staking by validator list fail", "err", err, "bn", blockNumber, "bh", blockHash) return nil, err } @@ -245,8 +245,8 @@ func (rmp *RewardMgrPlugin) ReturnDelegateReward(address common.Address, amount return nil } -func (rmp *RewardMgrPlugin) HandleDelegatePerReward(head *types.Header, blockHash common.Hash, list []*staking.Candidate, state xcom.StateDB) error { - currentEpoch := xutil.CalculateEpoch(head.Number.Uint64(), head.GetActiveVersion()) +func (rmp *RewardMgrPlugin) HandleDelegatePerReward(blockHash common.Hash, blockNumber uint64, list []*staking.Candidate, state xcom.StateDB) error { + currentEpoch := xutil.CalculateEpoch(blockNumber, gov.GetCurrentActiveVersion(state)) for _, verifier := range list { if verifier.CurrentEpochDelegateReward.Cmp(common.Big0) == 0 { continue @@ -254,13 +254,13 @@ func (rmp *RewardMgrPlugin) HandleDelegatePerReward(head *types.Header, blockHas if verifier.DelegateTotal.Cmp(common.Big0) == 0 { log.Debug("handleDelegatePerReward return delegateReward", "epoch", currentEpoch, "reward", verifier.CurrentEpochDelegateReward, "add", verifier.BenefitAddress) if err := rmp.ReturnDelegateReward(verifier.BenefitAddress, verifier.CurrentEpochDelegateReward, state); err != nil { - log.Error("HandleDelegatePerReward ReturnDelegateReward fail", "err", err, "blockNumber", head.Number.Uint64()) + log.Error("HandleDelegatePerReward ReturnDelegateReward fail", "err", err, "blockNumber", blockNumber) } } else { per := reward.NewDelegateRewardPer(currentEpoch, verifier.CurrentEpochDelegateReward, verifier.DelegateTotal) if err := AppendDelegateRewardPer(blockHash, verifier.NodeId, verifier.StakingBlockNum, per, rmp.db); err != nil { - log.Error("call handleDelegatePerReward fail AppendDelegateRewardPer", "blockNumber", head.Number, "blockHash", blockHash.TerminalString(), + log.Error("call handleDelegatePerReward fail AppendDelegateRewardPer", "blockNumber", blockNumber, "blockHash", blockHash.TerminalString(), "nodeId", verifier.NodeId.TerminalString(), "err", err, "CurrentEpochDelegateReward", verifier.CurrentEpochDelegateReward, "delegateTotal", verifier.DelegateTotal) return err } @@ -270,15 +270,15 @@ func (rmp *RewardMgrPlugin) HandleDelegatePerReward(head *types.Header, blockHas canAddr, err := xutil.NodeId2Addr(verifier.NodeId) if nil != err { log.Error("Failed to handleDelegatePerReward on rewardMgrPlugin: nodeId parse addr failed", - "blockNumber", head.Number, "blockHash", blockHash, "nodeID", verifier.NodeId.String(), "err", err) + "blockNumber", blockNumber, "blockHash", blockHash, "nodeID", verifier.NodeId.String(), "err", err) return err } if err := rmp.stakingPlugin.db.SetCanMutableStore(blockHash, canAddr, verifier.CandidateMutable); err != nil { log.Error("Failed to handleDelegatePerReward on rewardMgrPlugin: setCanMutableStore failed", - "blockNumber", head.Number, "blockHash", blockHash, "err", err, "mutable", verifier.CandidateMutable) + "blockNumber", blockNumber, "blockHash", blockHash, "err", err, "mutable", verifier.CandidateMutable) return err } - log.Debug("handleDelegatePerReward add newDelegateRewardPer", "blockNum", head.Number, "node_id", verifier.NodeId.TerminalString(), "stakingNum", verifier.StakingBlockNum, + log.Debug("handleDelegatePerReward add newDelegateRewardPer", "blockNum", blockNumber, "node_id", verifier.NodeId.TerminalString(), "stakingNum", verifier.StakingBlockNum, "cu_epoch_delegate_reward", currentEpochDelegateReward, "total_delegate_reward", verifier.DelegateRewardTotal, "total_delegate", verifier.DelegateTotal, "epoch", currentEpoch) } diff --git a/x/plugin/reward_plugin_test.go b/x/plugin/reward_plugin_test.go index c9b0ac19a9..d3b179a1ae 100644 --- a/x/plugin/reward_plugin_test.go +++ b/x/plugin/reward_plugin_test.go @@ -147,7 +147,7 @@ func TestRewardPlugin_CalcEpochReward(t *testing.T) { packageReward := new(big.Int) stakingReward := new(big.Int) var err error - + acversion := gov.GetCurrentActiveVersion(chain.StateDB) for i := 0; i < 3200; i++ { if err := chain.AddBlockWithSnapDB(true, func(hash common.Hash, header *types.Header, sdb snapshotdb.DB) error { plugin := new(RewardMgrPlugin) @@ -161,7 +161,7 @@ func TestRewardPlugin_CalcEpochReward(t *testing.T) { return nil } chain.StateDB.SubBalance(vm.RewardManagerPoolAddr, packageReward) - if xutil.IsEndOfEpoch(header.Number.Uint64()) { + if xutil.IsEndOfEpoch(header.Number.Uint64(), acversion) { chain.StateDB.SubBalance(vm.RewardManagerPoolAddr, stakingReward) packageReward, stakingReward, err = plugin.CalcEpochReward(hash, header, chain.StateDB) if err != nil { @@ -210,7 +210,9 @@ func TestRewardMgrPlugin_EndBlock(t *testing.T) { SetYearEndBalance(mockDB, 0, yearBalance) mockDB.AddBalance(vm.RewardManagerPoolAddr, yearBalance) - validatorQueueList, err := buildTestStakingData(1, xutil.CalcBlocksEachEpoch()) + acversion := gov.GetCurrentActiveVersion(chain.StateDB) + + validatorQueueList, err := buildTestStakingData(1, xutil.CalcBlocksEachEpoch(acversion)) if nil != err { t.Fatalf("buildTestStakingData fail: %v", err) } @@ -221,14 +223,14 @@ func TestRewardMgrPlugin_EndBlock(t *testing.T) { // 1. Dynamically adjust the number of settlement cycles according to the average block production time // 2. The block production speed of the last settlement cycle is too fast, leading to the completion of increase issuance in advance // 3. The actual increase issuance time exceeds the expected increase issuance time - for i := 0; i < int(xutil.CalcBlocksEachEpoch()*5); i++ { + for i := 0; i < int(xutil.CalcBlocksEachEpoch(acversion)*5); i++ { var currentHeader *types.Header if err := chain.AddBlockWithSnapDB(true, func(hash common.Hash, header *types.Header, sdb snapshotdb.DB) error { currentHeader = header - if currentHeader.Number.Uint64() < xutil.CalcBlocksEachEpoch() { + if currentHeader.Number.Uint64() < xutil.CalcBlocksEachEpoch(acversion) { currentHeader.Time += uint64(packTime) - } else if currentHeader.Number.Uint64() < xutil.CalcBlocksEachEpoch()*2 { + } else if currentHeader.Number.Uint64() < xutil.CalcBlocksEachEpoch(acversion)*2 { currentHeader.Time -= uint64(rand.Int63n(packTime)) } else { currentHeader.Time += uint64(packTime) @@ -259,7 +261,7 @@ func TestRewardMgrPlugin_EndBlock(t *testing.T) { balance.Add(balance, packageReward) assert.Equal(t, accounts[currentHeader.Coinbase], mockDB.GetBalance(currentHeader.Coinbase)) - if xutil.IsEndOfEpoch(currentHeader.Number.Uint64()) { + if xutil.IsEndOfEpoch(currentHeader.Number.Uint64(), acversion) { everyValidatorReward := new(big.Int).Div(stakingReward, big.NewInt(int64(len(validatorQueueList)))) for _, value := range validatorQueueList { balance := accounts[common.Address(value.NodeAddress)] @@ -271,7 +273,7 @@ func TestRewardMgrPlugin_EndBlock(t *testing.T) { assert.Equal(t, balance, mockDB.GetBalance(common.Address(value.NodeAddress))) } - validatorQueueList, err = buildTestStakingData(currentHeader.Number.Uint64()+1, currentHeader.Number.Uint64()+xutil.CalcBlocksEachEpoch()) + validatorQueueList, err = buildTestStakingData(currentHeader.Number.Uint64()+1, currentHeader.Number.Uint64()+xutil.CalcBlocksEachEpoch(acversion)) if nil != err { t.Fatalf("buildTestStakingData fail: %v", err) } @@ -595,8 +597,8 @@ func TestAllocatePackageBlock(t *testing.T) { log.Debug("reward", "delegateRewardAdd", chain.StateDB.GetBalance(delegateRewardAdd), "delegateReward poll", chain.StateDB.GetBalance(vm.DelegateRewardPoolAddr), "can address", chain.StateDB.GetBalance(can.BenefitAddress), "reward_pool", chain.StateDB.GetBalance(vm.RewardManagerPoolAddr)) - - for i := 0; i < int(xutil.CalcBlocksEachEpoch())-10; i++ { + acVersion := gov.GetCurrentActiveVersion(chain.StateDB) + for i := 0; i < int(xutil.CalcBlocksEachEpoch(acVersion))-10; i++ { if err := chain.AddBlockWithSnapDB(false, func(hash common.Hash, header *types.Header, sdb snapshotdb.DB) error { return nil }, nil, nil); err != nil { @@ -608,7 +610,7 @@ func TestAllocatePackageBlock(t *testing.T) { delegateReward := new(big.Int) for i := 0; i < 9; i++ { if err := chain.AddBlockWithSnapDB(true, func(hash common.Hash, header *types.Header, sdb snapshotdb.DB) error { - if xutil.IsBeginOfEpoch(header.Number.Uint64()) { + if xutil.IsBeginOfEpoch(header.Number.Uint64(), acVersion) { can.CandidateMutable.CleanCurrentEpochDelegateReward() if err := stkDB.SetCanMutableStore(hash, queue[0].NodeAddress, can.CandidateMutable); err != nil { return err @@ -619,7 +621,7 @@ func TestAllocatePackageBlock(t *testing.T) { } dr, _ := rm.CalDelegateRewardAndNodeReward(blockReward, can.RewardPer) delegateReward.Add(delegateReward, dr) - if xutil.IsEndOfEpoch(header.Number.Uint64()) { + if xutil.IsEndOfEpoch(header.Number.Uint64(), acVersion) { verifierList, err := rm.AllocateStakingReward(header.Number.Uint64(), hash, stakingReward, chain.StateDB) if err != nil { return err @@ -638,7 +640,7 @@ func TestAllocatePackageBlock(t *testing.T) { } if err := chain.AddBlockWithSnapDB(true, func(hash common.Hash, header *types.Header, sdb snapshotdb.DB) error { - if xutil.IsBeginOfEpoch(header.Number.Uint64()) { + if xutil.IsBeginOfEpoch(header.Number.Uint64(), acVersion) { can.CandidateMutable.CleanCurrentEpochDelegateReward() if err := stkDB.SetCanMutableStore(hash, queue[0].NodeAddress, can.CandidateMutable); err != nil { return err @@ -655,7 +657,7 @@ func TestAllocatePackageBlock(t *testing.T) { for i := 0; i < 9; i++ { if err := chain.AddBlockWithSnapDB(true, func(hash common.Hash, header *types.Header, sdb snapshotdb.DB) error { - if xutil.IsBeginOfEpoch(header.Number.Uint64()) { + if xutil.IsBeginOfEpoch(header.Number.Uint64(), acVersion) { can.CandidateMutable.CleanCurrentEpochDelegateReward() if err := stkDB.SetCanMutableStore(hash, queue[0].NodeAddress, can.CandidateMutable); err != nil { return err @@ -697,24 +699,24 @@ func generateStk(rewardPer uint16, delegateTotal *big.Int, blockNumber uint64) ( var delegation staking.Delegation delegation.Released = delegateTotal - delegation.DelegateEpoch = uint32(xutil.CalculateEpoch(blockNumber)) + delegation.DelegateEpoch = uint32(xutil.CalculateEpoch(blockNumber, currentTestGenesisVersion)) stakingValIndex := make(staking.ValArrIndexQueue, 0) stakingValIndex = append(stakingValIndex, &staking.ValArrIndex{ Start: 0, - End: xutil.CalcBlocksEachEpoch(), + End: xutil.CalcBlocksEachEpoch(currentTestGenesisVersion), }) stakingValIndex = append(stakingValIndex, &staking.ValArrIndex{ - Start: xutil.CalcBlocksEachEpoch(), - End: xutil.CalcBlocksEachEpoch() * 2, + Start: xutil.CalcBlocksEachEpoch(currentTestGenesisVersion), + End: xutil.CalcBlocksEachEpoch(currentTestGenesisVersion) * 2, }) stakingValIndex = append(stakingValIndex, &staking.ValArrIndex{ - Start: xutil.CalcBlocksEachEpoch() * 2, - End: xutil.CalcBlocksEachEpoch() * 3, + Start: xutil.CalcBlocksEachEpoch(currentTestGenesisVersion) * 2, + End: xutil.CalcBlocksEachEpoch(currentTestGenesisVersion) * 3, }) stakingValIndex = append(stakingValIndex, &staking.ValArrIndex{ - Start: xutil.CalcBlocksEachEpoch() * 3, - End: xutil.CalcBlocksEachEpoch() * 4, + Start: xutil.CalcBlocksEachEpoch(currentTestGenesisVersion) * 3, + End: xutil.CalcBlocksEachEpoch(currentTestGenesisVersion) * 4, }) validatorQueue := make(staking.ValidatorQueue, 0) validatorQueue = append(validatorQueue, &staking.Validator{ @@ -769,9 +771,9 @@ func TestRewardMgrPlugin_GetDelegateReward(t *testing.T) { log.Debug("reward", "delegateRewardAdd", chain.StateDB.GetBalance(delegateRewardAdd), "delegateReward poll", chain.StateDB.GetBalance(vm.DelegateRewardPoolAddr), "can address", chain.StateDB.GetBalance(can.BenefitAddress), "reward_pool", chain.StateDB.GetBalance(vm.RewardManagerPoolAddr)) - for i := 0; i < int(xutil.CalcBlocksEachEpoch()); i++ { + for i := 0; i < int(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)); i++ { if err := chain.AddBlockWithSnapDB(true, func(hash common.Hash, header *types.Header, sdb snapshotdb.DB) error { - if xutil.IsBeginOfEpoch(header.Number.Uint64()) { + if xutil.IsBeginOfEpoch(header.Number.Uint64(), currentTestGenesisVersion) { can.CandidateMutable.CleanCurrentEpochDelegateReward() if err := stkDB.SetCanMutableStore(hash, queue[0].NodeAddress, can.CandidateMutable); err != nil { return err @@ -781,7 +783,7 @@ func TestRewardMgrPlugin_GetDelegateReward(t *testing.T) { if err := rm.AllocatePackageBlock(hash, header, blockReward, chain.StateDB); err != nil { return err } - if xutil.IsEndOfEpoch(header.Number.Uint64()) { + if xutil.IsEndOfEpoch(header.Number.Uint64(), currentTestGenesisVersion) { verifierList, err := rm.AllocateStakingReward(header.Number.Uint64(), hash, stakingReward, chain.StateDB) if err != nil { @@ -791,7 +793,7 @@ func TestRewardMgrPlugin_GetDelegateReward(t *testing.T) { return err } - if err := stkDB.SetEpochValList(hash, index[xutil.CalculateEpoch(header.Number.Uint64())].Start, index[xutil.CalculateEpoch(header.Number.Uint64())].End, queue); err != nil { + if err := stkDB.SetEpochValList(hash, index[xutil.CalculateEpoch(header.Number.Uint64(), currentTestGenesisVersion)].Start, index[xutil.CalculateEpoch(header.Number.Uint64(), currentTestGenesisVersion)].End, queue); err != nil { return err } diff --git a/x/plugin/slashing_plugin_test.go b/x/plugin/slashing_plugin_test.go index e376f7ddb0..8c87522b09 100644 --- a/x/plugin/slashing_plugin_test.go +++ b/x/plugin/slashing_plugin_test.go @@ -252,26 +252,26 @@ func buildStakingData(blockNumber uint64, blockHash common.Hash, pri *ecdsa.Priv epochArr := &staking.ValidatorArray{ Start: 1, - End: uint64(xutil.CalcBlocksEachEpoch()), + End: uint64(xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(stateDb))), Arr: queue, } preArr := &staking.ValidatorArray{ Start: 1, - End: xutil.ConsensusSize(), + End: xcom.ConsensusSize(gov.GetCurrentActiveVersion(stateDb)), Arr: queue, } curArr := &staking.ValidatorArray{ - Start: xutil.ConsensusSize() + 1, - End: xutil.ConsensusSize() * 2, + Start: xcom.ConsensusSize(gov.GetCurrentActiveVersion(stateDb)) + 1, + End: xcom.ConsensusSize(gov.GetCurrentActiveVersion(stateDb)) * 2, Arr: queue, } setVerifierList(blockHash, epochArr) setRoundValList(blockHash, preArr) setRoundValList(blockHash, curArr) - err := stk.storeRoundValidatorAddrs(blockNumber, blockHash, 1, queue) + err := stk.storeRoundValidatorAddrs(blockNumber, blockHash, 1, queue, gov.GetCurrentActiveVersion(stateDb)) assert.Nil(t, err, fmt.Sprintf("Failed to storeRoundValidatorAddrs, err: %v", err)) balance, ok := new(big.Int).SetString("9999999999999999999999999999999999999999999999999", 10) if !ok { @@ -288,8 +288,8 @@ func TestSlashingPlugin_BeginBlock(t *testing.T) { snapshotdb.Instance().Clear() }() - startNumber := xutil.ConsensusSize() - startNumber += xutil.ConsensusSize() - xcom.ElectionDistance() - 2 + startNumber := xcom.ConsensusSize(gov.GetCurrentActiveVersion(stateDB)) + startNumber += xcom.ConsensusSize(gov.GetCurrentActiveVersion(stateDB)) - xcom.ElectionDistance() - 2 pri, phash := buildBlock(t, int(startNumber), stateDB) startNumber++ blockNumber := new(big.Int).SetInt64(int64(startNumber)) @@ -565,7 +565,7 @@ func TestSlashingPlugin_Slash(t *testing.T) { assert.NotNil(t, err) // Report outdated evidence, expected failure - err = si.Slash(normalEvidence, common.ZeroHash, new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch()*uint64(xcom.MaxEvidenceAge())*3).Uint64(), stateDB, anotherSender) + err = si.Slash(normalEvidence, common.ZeroHash, new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(stateDB))*uint64(xcom.MaxEvidenceAge())*3).Uint64(), stateDB, anotherSender) assert.NotNil(t, err) normalEvidence2, err := si.DecodeEvidence(1, normalData2) @@ -592,7 +592,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { _, genesis, _ := newChainState() si, stateDB := initInfo(t) // Starting from the second consensus round - blockNumber := new(big.Int).SetUint64(xutil.ConsensusSize()*2 - xcom.ElectionDistance()) + blockNumber := new(big.Int).SetUint64(xcom.ConsensusSize(gov.GetCurrentActiveVersion(stateDB))*2 - xcom.ElectionDistance()) if err := snapshotdb.Instance().NewBlock(blockNumber, genesis.Hash(), common.ZeroHash); nil != err { t.Fatal(err) } @@ -726,7 +726,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { return } // Third consensus round - blockNumber.Add(blockNumber, new(big.Int).SetUint64(xutil.ConsensusSize())) + blockNumber.Add(blockNumber, new(big.Int).SetUint64(xcom.ConsensusSize(gov.GetCurrentActiveVersion(stateDB)))) validatorMap = make(map[enode.IDv0]bool) validatorQueue = make(staking.ValidatorQueue, 0) validatorMap[nodeIdArr[0]] = false @@ -752,7 +752,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { return } // Fourth consensus round - blockNumber.Add(blockNumber, new(big.Int).SetUint64(xutil.ConsensusSize())) + blockNumber.Add(blockNumber, new(big.Int).SetUint64(xcom.ConsensusSize(gov.GetCurrentActiveVersion(stateDB)))) validatorMap = make(map[enode.IDv0]bool) validatorQueue = make(staking.ValidatorQueue, 0) validatorMap[nodeIdArr[0]] = true @@ -766,7 +766,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { return } // Fifth consensus round - blockNumber.Add(blockNumber, new(big.Int).SetUint64(xutil.ConsensusSize())) + blockNumber.Add(blockNumber, new(big.Int).SetUint64(xcom.ConsensusSize(gov.GetCurrentActiveVersion(stateDB)))) validatorMap = make(map[enode.IDv0]bool) validatorQueue = make(staking.ValidatorQueue, 0) validatorMap[nodeIdArr[2]] = false @@ -798,7 +798,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { if err := gov.SetGovernParam(gov.ModuleSlashing, gov.KeyZeroProduceNumberThreshold, "", "2", 1, common.ZeroHash); nil != err { t.Fatal(err) } - blockNumber.Add(blockNumber, new(big.Int).SetUint64(xutil.ConsensusSize())) + blockNumber.Add(blockNumber, new(big.Int).SetUint64(xcom.ConsensusSize(gov.GetCurrentActiveVersion(stateDB)))) validatorMap = make(map[enode.IDv0]bool) validatorQueue = make(staking.ValidatorQueue, 0) validatorMap[nodeIdArr[1]] = false @@ -826,7 +826,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { if err := gov.SetGovernParam(gov.ModuleSlashing, gov.KeyZeroProduceNumberThreshold, "", "3", 1, common.ZeroHash); nil != err { t.Fatal(err) } - blockNumber.Add(blockNumber, new(big.Int).SetUint64(xutil.ConsensusSize())) + blockNumber.Add(blockNumber, new(big.Int).SetUint64(xcom.ConsensusSize(gov.GetCurrentActiveVersion(stateDB)))) validatorMap = make(map[enode.IDv0]bool) validatorQueue = make(staking.ValidatorQueue, 0) validatorMap[nodeIdArr[5]] = false diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index 502946e9b8..4ebb33e449 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -164,7 +164,7 @@ func (sk *StakingPlugin) EndBlock(blockHash common.Hash, header *types.Header, s } // Election next epoch validators - if err := sk.ElectNextVerifierList(blockHash, header, state); nil != err { + if err := sk.ElectNextVerifierList(blockHash, header.Number.Uint64(), state); nil != err { log.Error("Failed to call ElectNextVerifierList on stakingPlugin EndBlock", "blockNumber", header.Number.Uint64(), "blockHash", blockHash.Hex(), "err", err) return err @@ -1169,27 +1169,27 @@ func rufundDelegateFn(refundBalance, aboutRelease, aboutRestrictingPlan *big.Int return refundTmp, releaseTmp, restrictingPlanTmp, nil } -func (sk *StakingPlugin) ElectNextVerifierList(blockHash common.Hash, header *types.Header, state xcom.StateDB) error { +func (sk *StakingPlugin) ElectNextVerifierList(blockHash common.Hash, blockNumber uint64, state xcom.StateDB) error { - oldIndex, err := sk.getVeriferIndex(blockHash, header.Number.Uint64(), QueryStartNotIrr) + oldIndex, err := sk.getVeriferIndex(blockHash, blockNumber, QueryStartNotIrr) if nil != err { log.Error("Failed to ElectNextVerifierList: Not found the VerifierIndex", "blockNumber", - header.Number, "blockHash", blockHash.Hex(), "err", err) + blockNumber, "blockHash", blockHash.Hex(), "err", err) return err } - log.Debug("Call ElectNextVerifierList old verifiers Index", "blockNumber", header.Number, "blockHash", blockHash.Hex(), "index", oldIndex) + log.Debug("Call ElectNextVerifierList old verifiers Index", "blockNumber", blockNumber, "blockHash", blockHash.Hex(), "index", oldIndex) - if oldIndex.End != header.Number.Uint64() { + if oldIndex.End != blockNumber { log.Error("Failed to ElectNextVerifierList: this blockNumber invalid", "Old Epoch End blockNumber", - oldIndex.End, "Current blockNumber", header.Number) + oldIndex.End, "Current blockNumber", blockNumber) return staking.ErrBlockNumberDisordered } // caculate the new epoch start and end newVerifierArr := &staking.ValidatorArray{ Start: oldIndex.End + 1, - End: oldIndex.End + xutil.CalcBlocksEachEpoch(header.GetActiveVersion()), + End: oldIndex.End + xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(state)), } currOriginVersion := gov.GetVersionForStaking(blockHash, state) @@ -1197,21 +1197,21 @@ func (sk *StakingPlugin) ElectNextVerifierList(blockHash common.Hash, header *ty currentVersion := gov.GetCurrentActiveVersion(state) if currentVersion == 0 { - log.Error("Failed to ElectNextVerifierList, GetCurrentActiveVersion is failed", "blockNumber", header.Number, "blockHash", blockHash.TerminalString()) + log.Error("Failed to ElectNextVerifierList, GetCurrentActiveVersion is failed", "blockNumber", blockNumber, "blockHash", blockHash.TerminalString()) return errors.New("Failed to get CurrentActiveVersion") } - maxvalidators, err := gov.GovernMaxValidators(header.Number.Uint64(), blockHash) + maxvalidators, err := gov.GovernMaxValidators(blockNumber, blockHash) if nil != err { log.Error("Failed to ElectNextVerifierList: query govern params `maxvalidators` is failed", "blockNumber", - header.Number, "blockHash", blockHash.Hex(), "err", err) + blockNumber, "blockHash", blockHash.Hex(), "err", err) return err } iter := sk.db.IteratorCandidatePowerByBlockHash(blockHash, int(maxvalidators)) if err := iter.Error(); nil != err { log.Error("Failed to ElectNextVerifierList: take iter by candidate power is failed", "blockNumber", - header.Number, "blockHash", blockHash.Hex(), "err", err) + blockNumber, "blockHash", blockHash.Hex(), "err", err) return err } defer iter.Release() @@ -1223,7 +1223,7 @@ func (sk *StakingPlugin) ElectNextVerifierList(blockHash common.Hash, header *ty addrSuffix := iter.Value() canBase, err := sk.db.GetCanBaseStoreWithSuffix(blockHash, addrSuffix) if nil != err { - log.Error("Failed to ElectNextVerifierList: Query CandidateBase info is failed", "blockNumber", header.Number, + log.Error("Failed to ElectNextVerifierList: Query CandidateBase info is failed", "blockNumber", blockNumber, "blockHash", blockHash.Hex(), "canAddr", common.BytesToNodeAddress(addrSuffix).Hex(), "err", err) if err == snapshotdb.ErrNotFound { if err := sk.db.Del(blockHash, iter.Key()); err != nil { @@ -1237,7 +1237,7 @@ func (sk *StakingPlugin) ElectNextVerifierList(blockHash common.Hash, header *ty if xutil.CalcVersion(canBase.ProgramVersion) < currVersion { log.Warn("Warn ElectNextVerifierList: the can ProgramVersion is less than currVersion", - "blockNumber", header.Number, "blockHash", blockHash.Hex(), "canVersion", + "blockNumber", blockNumber, "blockHash", blockHash.Hex(), "canVersion", "nodeId", canBase.NodeId.String(), "canAddr", common.BytesToNodeAddress(addrSuffix).Hex(), canBase.ProgramVersion, "currVersion", currVersion) @@ -1249,7 +1249,7 @@ func (sk *StakingPlugin) ElectNextVerifierList(blockHash common.Hash, header *ty canMutable, err := sk.db.GetCanMutableStoreWithSuffix(blockHash, addrSuffix) if nil != err { - log.Error("Failed to ElectNextVerifierList: Query CandidateMutable info is failed", "blockNumber", header.Number, + log.Error("Failed to ElectNextVerifierList: Query CandidateMutable info is failed", "blockNumber", blockNumber, "blockHash", blockHash.Hex(), "canAddr", common.BytesToNodeAddress(addrSuffix).Hex(), "err", err) return err } @@ -1272,14 +1272,14 @@ func (sk *StakingPlugin) ElectNextVerifierList(blockHash common.Hash, header *ty } newVerifierArr.Arr = queue - err = sk.setVerifierListAndIndex(header.Number.Uint64(), blockHash, newVerifierArr) + err = sk.setVerifierListAndIndex(blockNumber, blockHash, newVerifierArr) if nil != err { log.Error("Failed to ElectNextVerifierList: Set Next Epoch VerifierList is failed", "blockNumber", - header.Number, "blockHash", blockHash.Hex(), "err", err) + blockNumber, "blockHash", blockHash.Hex(), "err", err) return err } - log.Debug("Call ElectNextVerifierList Next verifiers", "blockNumber", header.Number, "blockHash", blockHash.Hex(), + log.Debug("Call ElectNextVerifierList Next verifiers", "blockNumber", blockNumber, "blockHash", blockHash.Hex(), "list length", len(queue), "list", newVerifierArr) return nil @@ -2025,7 +2025,7 @@ func (sk *StakingPlugin) Election(blockHash common.Hash, header *types.Header, s needRMwithdrewLen, "low version need remove count", needRMLowVersionLen, "total remove count", invalidLen, "remove map size", len(removeCans), "current validators Size", len(curr.Arr), "maxConsensusVals", maxConsensusVals, - "ShiftValidatorNum", xcom.ShiftValidatorNum(), "diffQueueLen", len(diffQueue), + "ShiftValidatorNum", xcom.ShiftValidatorNum(maxConsensusVals), "diffQueueLen", len(diffQueue), "vrfQueueLen", len(vrfQueue)) nextQueue, err := shuffle(invalidLen, currqueen, vrfQueue, blockNumber, header.ParentHash) @@ -2090,7 +2090,7 @@ func shuffleQueue(maxConsensusVals uint64, remainCurrQueue, vrfQueue staking.Val remainLen := len(remainCurrQueue) totalQueue := append(remainCurrQueue, vrfQueue...) - for remainLen > int(maxConsensusVals-xcom.ShiftValidatorNum()) && len(totalQueue) > int(maxConsensusVals) { + for remainLen > int(maxConsensusVals-xcom.ShiftValidatorNum(maxConsensusVals)) && len(totalQueue) > int(maxConsensusVals) { totalQueue = totalQueue[1:] remainLen-- } @@ -2107,7 +2107,7 @@ func shuffleQueue(maxConsensusVals uint64, remainCurrQueue, vrfQueue staking.Val // but random ordering is performed in each group // The first group: the first f nodes // The second group: the last 2f + 1 nodes - next, err := randomOrderValidatorQueue(blockNumber, parentHash, next) + next, err := randomOrderValidatorQueue(blockNumber, parentHash, next, maxConsensusVals) if nil != err { return nil, err } @@ -2133,7 +2133,7 @@ func (r randomOrderValidatorList) Swap(i, j int) { } // Randomly sort nodes -func randomOrderValidatorQueue(blockNumber uint64, parentHash common.Hash, queue staking.ValidatorQueue) (staking.ValidatorQueue, error) { +func randomOrderValidatorQueue(blockNumber uint64, parentHash common.Hash, queue staking.ValidatorQueue, maxConsensusVals uint64) (staking.ValidatorQueue, error) { preNonces, err := handler.GetVrfHandlerInstance().Load(parentHash) if nil != err { return nil, err @@ -2146,8 +2146,8 @@ func randomOrderValidatorQueue(blockNumber uint64, parentHash common.Hash, queue if len(preNonces) > len(queue) { preNonces = preNonces[len(preNonces)-len(queue):] } - - if len(queue) <= int(xcom.ShiftValidatorNum()) { + shitValudatorNumer := xcom.ShiftValidatorNum(maxConsensusVals) + if len(queue) <= int(shitValudatorNumer) { return queue, nil } @@ -2161,8 +2161,8 @@ func randomOrderValidatorQueue(blockNumber uint64, parentHash common.Hash, queue log.Debug("Call randomOrderValidatorQueue xor", "nodeId", v.NodeId.TerminalString(), "nodeAddress", v.NodeAddress.Hex(), "nonce", hexutil.Encode(preNonces[i]), "xorValue", value) } - frontPart := orderList[:xcom.ShiftValidatorNum()] - backPart := orderList[xcom.ShiftValidatorNum():] + frontPart := orderList[:shitValudatorNumer] + backPart := orderList[shitValudatorNumer:] sort.Sort(frontPart) sort.Sort(backPart) @@ -3014,7 +3014,7 @@ func probabilityElection(maxConsensusVals uint64, validatorList staking.Validato } else if gov.Gte0150Version(currentVersion) { p = xcom.CalcP(totalWeightsFloat, totalSqrtWeightsFloat) } else { - p = float64(xcom.ShiftValidatorNum()) * float64(maxConsensusVals) / totalSqrtWeightsFloat + p = float64(xcom.ShiftValidatorNum(maxConsensusVals)) * float64(maxConsensusVals) / totalSqrtWeightsFloat } shuffleSeed := new(big.Int).SetBytes(preNonces[0]).Int64() diff --git a/x/plugin/staking_plugin_test.go b/x/plugin/staking_plugin_test.go index df1af8e3eb..f693250b6e 100644 --- a/x/plugin/staking_plugin_test.go +++ b/x/plugin/staking_plugin_test.go @@ -250,7 +250,7 @@ func buildPrepareData(genesis *types.Block, t *testing.T) (*types.Header, error) */ verifierIndex := &staking.ValArrIndex{ Start: 1, - End: xutil.CalcBlocksEachEpoch(), + End: xutil.CalcBlocksEachEpoch(currentTestGenesisVersion), } epochIndexArr := make(staking.ValArrIndexQueue, 0) @@ -285,7 +285,7 @@ func buildPrepareData(genesis *types.Block, t *testing.T) (*types.Header, error) */ curr_indexInfo := &staking.ValArrIndex{ Start: 1, - End: xutil.ConsensusSize(), + End: xcom.ConsensusSize(currentTestGenesisVersion), } roundIndexArr := make(staking.ValArrIndexQueue, 0) roundIndexArr = append(roundIndexArr, curr_indexInfo) @@ -301,8 +301,8 @@ func buildPrepareData(genesis *types.Block, t *testing.T) (*types.Header, error) return nil, err } - PrintObject("Test round", validatorQueue[:xcom.MaxConsensusVals()]) - roundArr, err := rlp.EncodeToBytes(validatorQueue[:xcom.MaxConsensusVals()]) + PrintObject("Test round", validatorQueue[:xcom.MaxConsensusVals(currentTestGenesisVersion)]) + roundArr, err := rlp.EncodeToBytes(validatorQueue[:xcom.MaxConsensusVals(currentTestGenesisVersion)]) if nil != err { t.Errorf("Failed to rlp encodeing genesis validators. error:%s", err.Error()) return nil, err @@ -327,7 +327,7 @@ func buildPrepareData(genesis *types.Block, t *testing.T) (*types.Header, error) } nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) currentHash := crypto.Keccak256Hash([]byte(nodeId.String())) - newNumber := big.NewInt(int64(xutil.ConsensusSize() - xcom.ElectionDistance())) // 50 + newNumber := big.NewInt(int64(xcom.ConsensusSize(currentTestGenesisVersion) - xcom.ElectionDistance())) // 50 preNum1 := new(big.Int).Sub(newNumber, big.NewInt(1)) if err := sndb.SetCurrent(currentHash, *preNum1, *preNum1); nil != err { panic(fmt.Errorf("Failed to SetCurrent by snapshotdb. error:%s", err.Error())) @@ -622,7 +622,7 @@ func TestStakingPlugin_EndBlock(t *testing.T) { */ verifierIndex := &staking.ValArrIndex{ Start: 1, - End: xutil.CalcBlocksEachEpoch(), + End: xutil.CalcBlocksEachEpoch(currentTestGenesisVersion), } epochIndexArr := make(staking.ValArrIndexQueue, 0) @@ -657,7 +657,7 @@ func TestStakingPlugin_EndBlock(t *testing.T) { */ curr_indexInfo := &staking.ValArrIndex{ Start: 1, - End: xutil.ConsensusSize(), + End: xcom.ConsensusSize(currentTestGenesisVersion), } roundIndexArr := make(staking.ValArrIndexQueue, 0) roundIndexArr = append(roundIndexArr, curr_indexInfo) @@ -672,8 +672,8 @@ func TestStakingPlugin_EndBlock(t *testing.T) { return } - PrintObject("Test round", validatorQueue[:xcom.MaxConsensusVals()]) - roundArr, err := rlp.EncodeToBytes(validatorQueue[:xcom.MaxConsensusVals()]) + PrintObject("Test round", validatorQueue[:xcom.MaxConsensusVals(currentTestGenesisVersion)]) + roundArr, err := rlp.EncodeToBytes(validatorQueue[:xcom.MaxConsensusVals(currentTestGenesisVersion)]) if !assert.Nil(t, err, fmt.Sprintf("Failed to rlp encodeing genesis validators. error: %v", err)) { return } @@ -690,7 +690,7 @@ func TestStakingPlugin_EndBlock(t *testing.T) { } // SetCurrent to snapshotDB - currentNumber = big.NewInt(int64(xutil.ConsensusSize() - xcom.ElectionDistance())) // 50 + currentNumber = big.NewInt(int64(xcom.ConsensusSize(currentTestGenesisVersion) - xcom.ElectionDistance())) // 50 preNum1 := new(big.Int).Sub(currentNumber, big.NewInt(1)) if err := sndb.SetCurrent(currentHash, *preNum1, *preNum1); nil != err { t.Errorf("Failed to SetCurrent by snapshotdb. error:%s", err.Error()) @@ -701,7 +701,7 @@ func TestStakingPlugin_EndBlock(t *testing.T) { EndBlock to Election() */ // new block - currentNumber = big.NewInt(int64(xutil.ConsensusSize() - xcom.ElectionDistance())) // 50 + currentNumber = big.NewInt(int64(xcom.ConsensusSize(currentTestGenesisVersion) - xcom.ElectionDistance())) // 50 nonce := crypto.Keccak256([]byte(time.Now().Add(time.Duration(1)).String()))[:] header := &types.Header{ @@ -750,7 +750,7 @@ func TestStakingPlugin_EndBlock(t *testing.T) { Elect Epoch validator list == ElectionNextList() */ // new block - currentNumber = big.NewInt(int64(xutil.ConsensusSize() * xutil.EpochSize())) // 600 + currentNumber = big.NewInt(int64(xcom.ConsensusSize(currentTestGenesisVersion) * xcom.EpochSize(currentTestGenesisVersion))) // 600 preNum := new(big.Int).Sub(currentNumber, big.NewInt(1)) // 599 @@ -939,7 +939,7 @@ func TestStakingPlugin_Confirmed(t *testing.T) { */ verifierIndex := &staking.ValArrIndex{ Start: 1, - End: xutil.CalcBlocksEachEpoch(), + End: xutil.CalcBlocksEachEpoch(currentTestGenesisVersion), } epochIndexArr := make(staking.ValArrIndexQueue, 0) @@ -973,7 +973,7 @@ func TestStakingPlugin_Confirmed(t *testing.T) { */ curr_indexInfo := &staking.ValArrIndex{ Start: 1, - End: xutil.ConsensusSize(), + End: xcom.ConsensusSize(currentTestGenesisVersion), } roundIndexArr := make(staking.ValArrIndexQueue, 0) roundIndexArr = append(roundIndexArr, curr_indexInfo) @@ -989,8 +989,8 @@ func TestStakingPlugin_Confirmed(t *testing.T) { return } - PrintObject("Test round", validatorQueue[:xcom.MaxConsensusVals()]) - roundArr, err := rlp.EncodeToBytes(validatorQueue[:xcom.MaxConsensusVals()]) + PrintObject("Test round", validatorQueue[:xcom.MaxConsensusVals(currentTestGenesisVersion)]) + roundArr, err := rlp.EncodeToBytes(validatorQueue[:xcom.MaxConsensusVals(currentTestGenesisVersion)]) if !assert.Nil(t, err, fmt.Sprintf("Failed to rlp encodeing genesis validators. error: %v", err)) { return } @@ -1007,7 +1007,7 @@ func TestStakingPlugin_Confirmed(t *testing.T) { } // SetCurrent to snapshotDB - currentNumber = big.NewInt(int64(xutil.ConsensusSize() - xcom.ElectionDistance())) // 50 + currentNumber = big.NewInt(int64(xcom.ConsensusSize(currentTestGenesisVersion) - xcom.ElectionDistance())) // 50 preNum1 := new(big.Int).Sub(currentNumber, big.NewInt(1)) if err := sndb.SetCurrent(currentHash, *preNum1, *preNum1); nil != err { t.Errorf("Failed to SetCurrent by snapshotdb. error:%s", err.Error()) @@ -1018,7 +1018,7 @@ func TestStakingPlugin_Confirmed(t *testing.T) { EndBlock to Election() */ // new block - currentNumber = big.NewInt(int64(xutil.ConsensusSize() - xcom.ElectionDistance())) // 50 + currentNumber = big.NewInt(int64(xcom.ConsensusSize(currentTestGenesisVersion) - xcom.ElectionDistance())) // 50 nonce := crypto.Keccak256([]byte(time.Now().Add(time.Duration(1)).String()))[:] header := &types.Header{ @@ -1222,7 +1222,7 @@ func TestStakingPlugin_GetCandidateList(t *testing.T) { Start GetCandidateList */ - queue, err := StakingInstance().GetCandidateList(blockHash, blockNumber.Uint64()) + queue, err := StakingInstance().GetCandidateList(blockHash, blockNumber.Uint64(), state) assert.Nil(t, err, fmt.Sprintf("Failed to GetCandidateList: %v", err)) assert.Equal(t, count, len(queue)) queueByte, _ := json.Marshal(queue) @@ -1481,7 +1481,9 @@ func TestStakingPlugin_HandleUnCandidateItem(t *testing.T) { // Add UNStakingItems //stakingDB := staking.NewStakingDB() - epoch := xutil.CalculateEpoch(blockNumber.Uint64()) + currentVersion := gov.GetCurrentActiveVersion(state) + + epoch := xutil.CalculateEpoch(blockNumber.Uint64(), currentVersion) canAddr, _ := xutil.NodeId2Addr(nodeIdArr[index]) if err := StakingInstance().addUnStakeItem(state, blockNumber.Uint64(), blockHash, epoch, nodeIdArr[index], canAddr, blockNumber.Uint64()); nil != err { @@ -1531,11 +1533,11 @@ func TestStakingPlugin_HandleUnCandidateItem(t *testing.T) { t.Fatal(err) } canAddr, _ = xutil.NodeId2Addr(nodeIdArr[index]) - if err := StakingInstance().addRecoveryUnStakeItem(blockNumber2.Uint64(), blockHash2, nodeIdArr[index], canAddr, blockNumber2.Uint64()); nil != err { + if err := StakingInstance().addRecoveryUnStakeItem(blockNumber2.Uint64(), blockHash2, nodeIdArr[index], canAddr, blockNumber2.Uint64(), currentVersion); nil != err { t.Error("Failed to AddUnStakeItemStore:", err) return } - epoch = xutil.CalculateEpoch(blockNumber2.Uint64()) + epoch = xutil.CalculateEpoch(blockNumber2.Uint64(), currentVersion) err = StakingInstance().HandleUnCandidateItem(state, blockNumber2.Uint64(), blockHash2, epoch+xcom.ZeroProduceFreezeDuration()) assert.Nil(t, err) @@ -1560,12 +1562,12 @@ func TestStakingPlugin_HandleUnCandidateItem(t *testing.T) { assert.Nil(t, StakingInstance().EditCandidate(blockHash2, blockNumber2, canAddr, recoveryCan2)) // Handle the lock period of low block rate, and increase the double sign freeze operation - if err := StakingInstance().addRecoveryUnStakeItem(blockNumber2.Uint64(), blockHash2, nodeIdArr[index], canAddr, blockNumber2.Uint64()); nil != err { + if err := StakingInstance().addRecoveryUnStakeItem(blockNumber2.Uint64(), blockHash2, nodeIdArr[index], canAddr, blockNumber2.Uint64(), gov.GetCurrentActiveVersion(state)); nil != err { t.Error("Failed to AddUnStakeItemStore:", err) return } - newBlockNumber := new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch()*xcom.ZeroProduceFreezeDuration() + blockNumber2.Uint64()) - epoch = xutil.CalculateEpoch(newBlockNumber.Uint64()) + newBlockNumber := new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch(currentVersion)*xcom.ZeroProduceFreezeDuration() + blockNumber2.Uint64()) + epoch = xutil.CalculateEpoch(newBlockNumber.Uint64(), currentVersion) err = StakingInstance().HandleUnCandidateItem(state, newBlockNumber.Uint64(), blockHash2, epoch) assert.Nil(t, err) @@ -1577,7 +1579,7 @@ func TestStakingPlugin_HandleUnCandidateItem(t *testing.T) { assert.False(t, recoveryCan2.IsInvalidLowRatio()) // Handle double-signature freeze and release staking, delete nodes - newBlockNumber.Add(newBlockNumber, new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch()*xcom.UnStakeFreezeDuration())) + newBlockNumber.Add(newBlockNumber, new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch(currentVersion)*xcom.UnStakeFreezeDuration())) err = StakingInstance().HandleUnCandidateItem(state, newBlockNumber.Uint64(), blockHash2, xcom.UnStakeFreezeDuration()+epoch) assert.Nil(t, err) recoveryCan2, err = getCandidate(blockHash2, index) @@ -1667,7 +1669,7 @@ func TestStakingPlugin_Delegate(t *testing.T) { canAddr, _ := xutil.NodeId2Addr(can.NodeId) - curBlockNumber := new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch() * 3) + curBlockNumber := new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(state)) * 3) if err := sndb.NewBlock(curBlockNumber, blockHash2, blockHash3); nil != err { t.Error("newBlock 3 err", err) return @@ -1778,7 +1780,7 @@ func TestStakingPlugin_WithdrewDelegation(t *testing.T) { assert.True(t, new(big.Int).Sub(delegateTotalHes, amount).Cmp(can.DelegateTotalHes) == 0) assert.True(t, new(big.Int).Sub(delegateTotalHes, amount).Cmp(del.ReleasedHes) == 0) - curBlockNumber := new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch() * 3) + curBlockNumber := new(big.Int).SetUint64(xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(state)) * 3) if err := sndb.NewBlock(curBlockNumber, blockHash2, blockHash3); nil != err { t.Error("newBlock 3 err", err) return @@ -2160,7 +2162,8 @@ func TestStakingPlugin_ElectNextVerifierList(t *testing.T) { // build genesis VerifierList start := uint64(1) - end := xutil.EpochSize() * xutil.ConsensusSize() + + end := xcom.EpochSize(gov.GetCurrentActiveVersion(state)) * xcom.ConsensusSize(gov.GetCurrentActiveVersion(state)) new_verifierArr := &staking.ValidatorArray{ Start: start, @@ -2225,7 +2228,7 @@ func TestStakingPlugin_ElectNextVerifierList(t *testing.T) { /* Start ElectNextVerifierList */ - targetNum := xutil.EpochSize() * xutil.ConsensusSize() + targetNum := xcom.EpochSize(gov.GetCurrentActiveVersion(state)) * xcom.ConsensusSize(gov.GetCurrentActiveVersion(state)) targetNumInt := big.NewInt(int64(targetNum)) @@ -2348,7 +2351,7 @@ func TestStakingPlugin_Election(t *testing.T) { // build genesis VerifierList start := uint64(1) - end := xutil.EpochSize() * xutil.ConsensusSize() + end := xcom.EpochSize(gov.GetCurrentActiveVersion(state)) * xcom.ConsensusSize(gov.GetCurrentActiveVersion(state)) new_verifierArr := &staking.ValidatorArray{ Start: start, @@ -2406,10 +2409,10 @@ func TestStakingPlugin_Election(t *testing.T) { // build gensis current validatorList new_validatorArr := &staking.ValidatorArray{ Start: start, - End: xutil.ConsensusSize(), + End: xcom.ConsensusSize(gov.GetCurrentActiveVersion(state)), } - new_validatorArr.Arr = queue[:int(xcom.MaxConsensusVals())] + new_validatorArr.Arr = queue[:int(xcom.MaxConsensusVals(gov.GetCurrentActiveVersion(state)))] err = setRoundValList(blockHash, new_validatorArr) if nil != err { @@ -2441,7 +2444,7 @@ func TestStakingPlugin_Election(t *testing.T) { header := &types.Header{ ParentHash: blockHash, - Number: big.NewInt(int64(xutil.ConsensusSize() - xcom.ElectionDistance())), + Number: big.NewInt(int64(xcom.ConsensusSize(gov.GetCurrentActiveVersion(state)) - xcom.ElectionDistance())), Nonce: types.EncodeNonce(currNonce), } @@ -2563,7 +2566,7 @@ func TestStakingPlugin_SlashCandidates(t *testing.T) { // build genesis VerifierList start := uint64(1) - end := xutil.EpochSize() * xutil.ConsensusSize() + end := xcom.EpochSize(gov.GetCurrentActiveVersion(state)) * xcom.ConsensusSize(gov.GetCurrentActiveVersion(state)) new_verifierArr := &staking.ValidatorArray{ Start: start, @@ -3021,13 +3024,13 @@ func TestStakingPlugin_ProposalPassedNotify(t *testing.T) { epoch_Arr := &staking.ValidatorArray{ Start: 1, - End: xutil.CalcBlocksEachEpoch(), + End: xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(state)), Arr: validatorQueue, } curr_Arr := &staking.ValidatorArray{ Start: 1, - End: xutil.ConsensusSize(), + End: xcom.ConsensusSize(gov.GetCurrentActiveVersion(state)), Arr: validatorQueue, } @@ -3130,13 +3133,13 @@ func TestStakingPlugin_GetCandidateONRound(t *testing.T) { /** Start GetCandidateONRound */ - canNotIrrQueue, err := StakingInstance().GetCandidateONRound(header.Hash(), header.Number.Uint64(), CurrentRound, QueryStartNotIrr) + canNotIrrQueue, err := StakingInstance().GetCandidateONRound(header.Hash(), header.Number.Uint64(), CurrentRound, QueryStartNotIrr, state) assert.Nil(t, err, fmt.Sprintf("Failed to GetCandidateONRound by QueryStartNotIrr, err: %v", err)) assert.True(t, 0 != len(canNotIrrQueue)) t.Log("GetCandidateONRound by QueryStartNotIrr:", canNotIrrQueue) - canQueue, err := StakingInstance().GetCandidateONRound(header.Hash(), header.Number.Uint64(), CurrentRound, QueryStartIrr) + canQueue, err := StakingInstance().GetCandidateONRound(header.Hash(), header.Number.Uint64(), CurrentRound, QueryStartIrr, state) assert.Nil(t, err, fmt.Sprintf("Failed to GetCandidateONRound by QueryStartIrr, err: %v", err)) @@ -3169,13 +3172,13 @@ func TestStakingPlugin_GetValidatorList(t *testing.T) { /** Start GetValidatorList */ - validatorNotIrrExQueue, err := StakingInstance().GetValidatorList(header.Hash(), header.Number.Uint64(), CurrentRound, QueryStartNotIrr) + validatorNotIrrExQueue, err := StakingInstance().GetValidatorList(header.Hash(), header.Number.Uint64(), CurrentRound, QueryStartNotIrr, gov.GetCurrentActiveVersion(state)) assert.Nil(t, err, fmt.Sprintf("Failed to GetValidatorList by QueryStartNotIrr, err: %v", err)) assert.True(t, 0 != len(validatorNotIrrExQueue)) t.Log("GetValidatorList by QueryStartNotIrr:", validatorNotIrrExQueue) - validatorExQueue, err := StakingInstance().GetValidatorList(header.Hash(), header.Number.Uint64(), CurrentRound, QueryStartIrr) + validatorExQueue, err := StakingInstance().GetValidatorList(header.Hash(), header.Number.Uint64(), CurrentRound, QueryStartIrr, gov.GetCurrentActiveVersion(state)) if nil != err { t.Errorf("Failed to GetValidatorList by QueryStartIrr, err: %v", err) return @@ -3523,9 +3526,9 @@ func TestStakingPlugin_GetLastNumber(t *testing.T) { Start GetLastNumber */ endNumber := StakingInstance().GetLastNumber(header.Number.Uint64()) - - round := xutil.CalculateRound(header.Number.Uint64()) - blockNum := round * xutil.ConsensusSize() + acVersion := gov.GetCurrentActiveVersion(state) + round := xutil.CalculateRound(header.Number.Uint64(), acVersion) + blockNum := round * xcom.ConsensusSize(acVersion) assert.True(t, endNumber == blockNum, fmt.Sprintf("currentNumber: %d, currentRound: %d endNumber: %d, targetNumber: %d", header.Number, round, endNumber, blockNum)) } @@ -3633,8 +3636,8 @@ func TestStakingPlugin_ProbabilityElection(t *testing.T) { preNonces = append(preNonces, crypto.Keccak256(common.Int64ToBytes(time.Now().UnixNano() + int64(i)))[:]) time.Sleep(time.Microsecond * 10) } - - result, err := probabilityElection(vqList, int(xcom.ShiftValidatorNum()), currentNonce, preNonces, 1, params.GenesisVersion) + maxConsensusVals := xcom.MaxConsensusVals(params.GenesisVersion) + result, err := probabilityElection(maxConsensusVals, vqList, int(xcom.ShiftValidatorNum(maxConsensusVals)), currentNonce, preNonces, 1, params.GenesisVersion) assert.Nil(t, err, fmt.Sprintf("Failed to probabilityElection, err: %v", err)) assert.True(t, nil != result, "the result is nil") @@ -3701,11 +3704,13 @@ func TestStakingPlugin_ProbabilityElectionDifferentWeights(t *testing.T) { } stakeThreshold := 10000 + acVersion := gov.GetCurrentActiveVersion(stateDb) + maxConsensusVals := xcom.MaxConsensusVals(acVersion) for i := 0; i < 3; i++ { vqList, preNonceList, _ := buildCandidate(stakeThreshold) stakeThreshold *= 10 t.Run(fmt.Sprintf("Election_%d", i+1), func(t *testing.T) { - result, err := probabilityElection(vqList, int(xcom.ShiftValidatorNum()), currentNonce, preNonceList, 1, gov.GetCurrentActiveVersion(stateDb)) + result, err := probabilityElection(maxConsensusVals, vqList, int(xcom.ShiftValidatorNum(maxConsensusVals)), currentNonce, preNonceList, 1, acVersion) assert.Nil(t, err, fmt.Sprintf("Failed to probabilityElection, err: %v", err)) assert.True(t, nil != result, "the result is nil") }) @@ -3728,7 +3733,8 @@ func TestStakingPlugin_RandomOrderValidatorQueue(t *testing.T) { if err := slash.db.NewBlock(new(big.Int).SetUint64(1), blockHash, common.ZeroHash); nil != err { t.Fatal(err) } - for i := 0; i < int(xcom.MaxConsensusVals()); i++ { + maxConsensusVals := xcom.MaxConsensusVals(2048) + for i := 0; i < int(maxConsensusVals); i++ { vrfData, err := vrf.Prove(privateKey, data) if nil != err { t.Fatal(err) @@ -3752,7 +3758,7 @@ func TestStakingPlugin_RandomOrderValidatorQueue(t *testing.T) { t.Fatal(err) } } - resultQueue, err := randomOrderValidatorQueue(1, common.ZeroHash, vqList) + resultQueue, err := randomOrderValidatorQueue(1, common.ZeroHash, vqList, maxConsensusVals) if nil != err { t.Fatal(err) } diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index 3dbb3ac98c..728b58b886 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -23,6 +23,8 @@ import ( "math/big" "sync" + "github.com/AlayaNetwork/Alaya-Go/params" + "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/AlayaNetwork/Alaya-Go/log" @@ -438,9 +440,9 @@ func CheckOperatingThreshold(threshold *big.Int) error { return nil } -func CheckMaxValidators(num int) error { - if num < int(ec.Common.MaxConsensusVals) || num > CeilMaxValidators { - return common.InvalidParameter.Wrap(fmt.Sprintf("The MaxValidators must be [%d, %d]", int(ec.Common.MaxConsensusVals), CeilMaxValidators)) +func CheckMaxValidators(num int, version uint32) error { + if num < int(MaxConsensusVals(version)) || num > CeilMaxValidators { + return common.InvalidParameter.Wrap(fmt.Sprintf("The MaxValidators must be [%d, %d]", int(MaxConsensusVals(version)), CeilMaxValidators)) } return nil } @@ -536,7 +538,7 @@ func CheckEconomicModel(version uint32) error { // package perblock duration blockDuration := ec.Common.NodeBlockTimeWindow / ec.Common.PerRoundBlocks // round duration - roundDuration := ec.Common.MaxConsensusVals * ec.Common.PerRoundBlocks * blockDuration + roundDuration := MaxConsensusVals(version) * ec.Common.PerRoundBlocks * blockDuration // epoch Size, how many consensus round epochSize := epochDuration / roundDuration //real epoch duration @@ -562,11 +564,11 @@ func CheckEconomicModel(version uint32) error { return errors.New("The issuance period must be integer multiples of the settlement period and multiples must be greater than or equal to 4") } - if ec.Common.MaxConsensusVals < FloorMaxConsensusVals || ec.Common.MaxConsensusVals > CeilMaxConsensusVals { + if MaxConsensusVals(version) < FloorMaxConsensusVals || MaxConsensusVals(version) > CeilMaxConsensusVals { return fmt.Errorf("The consensus validator num must be [%d, %d]", FloorMaxConsensusVals, CeilMaxConsensusVals) } - if err := CheckMaxValidators(int(ec.Staking.MaxValidators)); nil != err { + if err := CheckMaxValidators(int(ec.Staking.MaxValidators), version); nil != err { return err } @@ -663,7 +665,10 @@ func BlocksWillCreate() uint64 { return ec.Common.PerRoundBlocks } func MaxConsensusVals(version uint32) uint64 { - + if version >= params.FORKVERSION_0_17_0 { + panic("This parameter has not been determined") + return 0 + } return ec.Common.MaxConsensusVals } @@ -699,8 +704,8 @@ func MaxValidators() uint64 { return ec.Staking.MaxValidators } -func ShiftValidatorNum() uint64 { - return (ec.Common.MaxConsensusVals - 1) / 3 +func ShiftValidatorNum(maxConsensusVals uint64) uint64 { + return (maxConsensusVals - 1) / 3 } func HesitateRatio() uint64 { diff --git a/x/xcom/common_config_test.go b/x/xcom/common_config_test.go index 6a42c18e02..9c9de66e18 100644 --- a/x/xcom/common_config_test.go +++ b/x/xcom/common_config_test.go @@ -14,22 +14,26 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package xcom import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/AlayaNetwork/Alaya-Go/common" + "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/rlp" - "github.com/stretchr/testify/assert" - "testing" ) +var currentTestGenesisVersion = params.FORKVERSION_0_16_0 + func TestGetDefaultEMConfig(t *testing.T) { t.Run("DefaultAlayaNet", func(t *testing.T) { if getDefaultEMConfig(DefaultAlayaNet) == nil { t.Error("DefaultAlayaNet can't be nil config") } - if err := CheckEconomicModel(); nil != err { + if err := CheckEconomicModel(currentTestGenesisVersion); nil != err { t.Error(err) } }) @@ -37,7 +41,7 @@ func TestGetDefaultEMConfig(t *testing.T) { if getDefaultEMConfig(DefaultUnitTestNet) == nil { t.Error("DefaultUnitTestNet can't be nil config") } - if err := CheckEconomicModel(); nil != err { + if err := CheckEconomicModel(currentTestGenesisVersion); nil != err { t.Error(err) } }) From 7b16fe8d19793ff76c27d98a51ed791ae9d610f4 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 26 Nov 2021 14:44:15 +0800 Subject: [PATCH 078/299] update for 0170 config --- core/genesis.go | 5 +++-- x/gov/gov_params.go | 2 +- x/plugin/gov_plugin.go | 7 +++++++ x/xcom/common_config.go | 41 ++++++++++++++++++++++++++--------------- 4 files changed, 37 insertions(+), 18 deletions(-) diff --git a/core/genesis.go b/core/genesis.go index f7947041d5..3b8c8a57da 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -237,19 +237,20 @@ func SetupGenesisBlock(db ethdb.Database, snapshotBaseDB snapshotdb.BaseDB, gene // Get the existing EconomicModel configuration. ecCfg := rawdb.ReadEconomicModel(db, stored) - eceCfg := rawdb.ReadEconomicModelExtend(db, stored) if nil == ecCfg { log.Warn("Found genesis block without EconomicModel config") ecCfg = xcom.GetEc(xcom.DefaultAlayaNet) rawdb.WriteEconomicModel(db, stored, ecCfg) } + xcom.ResetEconomicDefaultConfig(ecCfg) + + eceCfg := rawdb.ReadEconomicModelExtend(db, stored) if nil == eceCfg { log.Warn("Found genesis block without EconomicModelExtend config") xcom.GetEc(xcom.DefaultAlayaNet) eceCfg = xcom.GetEce() rawdb.WriteEconomicModelExtend(db, stored, eceCfg) } - xcom.ResetEconomicDefaultConfig(ecCfg) xcom.ResetEconomicExtendConfig(eceCfg) //update chain config here diff --git a/x/gov/gov_params.go b/x/gov/gov_params.go index 924d5fb94c..19c958119d 100644 --- a/x/gov/gov_params.go +++ b/x/gov/gov_params.go @@ -101,7 +101,7 @@ func initParam(version uint32) []*GovernParam { return fmt.Errorf("Parsed MaxValidators is failed: %v", err) } - if err := xcom.CheckMaxValidators(num); nil != err { + if err := xcom.CheckMaxValidators(num, version); nil != err { return err } diff --git a/x/plugin/gov_plugin.go b/x/plugin/gov_plugin.go index 0f77c083ec..088e8ccf8a 100644 --- a/x/plugin/gov_plugin.go +++ b/x/plugin/gov_plugin.go @@ -161,6 +161,13 @@ func (govPlugin *GovPlugin) BeginBlock(blockHash common.Hash, header *types.Head } header.SetActiveVersion(versionProposal.NewVersion) log.Info("version proposal is active", "blockNumber", blockNumber, "proposalID", versionProposal.ProposalID, "newVersion", versionProposal.NewVersion, "newVersionString", xutil.ProgramVersion2Str(versionProposal.NewVersion)) + if versionProposal.NewVersion == params.FORKVERSION_0_17_0 { + if err := gov.WriteEcHash0170(state); nil != err { + log.Error("save EcHash0140 to stateDB failed.", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveProposalID", preActiveVersionProposalID) + return err + } + log.Info("Successfully upgraded the new version 0.14.0", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveProposalID", preActiveVersionProposalID) + } } } return nil diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index e4a516369b..f5aa539339 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -163,7 +163,7 @@ type EconomicModel struct { type EconomicModelExtend struct { Reward rewardConfigExtend `json:"reward"` Restricting restrictingConfigExtend `json:"restricting"` - Common commonConfigExtend `json:"common"` + Extend0170 EconomicModel0170Extend `json:"extend_0170,omitempty"` } type rewardConfigExtend struct { @@ -174,10 +174,14 @@ type restrictingConfigExtend struct { MinimumRelease *big.Int `json:"minimumRelease"` //The minimum number of Restricting release in one epoch } -type commonConfigExtend struct { - // add by 0.17.0 - MaxGroupValidators uint32 `json:"caxGroupValidators"` // max validators count in 1 group - CoordinatorsLimit uint32 `json:"coordinatorLimit"` // max Coordinators count in 1 group +type EconomicModel0170Extend struct { + Common EconomicModel0170CommonConfig `json:"common"` +} + +type EconomicModel0170CommonConfig struct { + MaxGroupValidators uint32 `json:"caxGroupValidators"` // max validators count in 1 group + CoordinatorsLimit uint32 `json:"coordinatorLimit"` // max Coordinators count in 1 group + MaxConsensusVals uint64 `json:"maxConsensusVals"` // The consensus validators count } // New parameters added in version 0.14.0 need to be saved on the chain. @@ -200,9 +204,14 @@ func EcParams0140() ([]byte, error) { // New parameters added in version 0.17.0 need to be saved on the chain. // Calculate the rlp of the new parameter and return it to the upper storage. func EcParams0170() ([]byte, error) { - params := commonConfigExtend { - MaxGroupValidators: ece.Common.MaxGroupValidators, - CoordinatorsLimit: ece.Common.CoordinatorsLimit, + params := struct { + MaxGroupValidators uint32 `json:"caxGroupValidators"` // max validators count in 1 group + CoordinatorsLimit uint32 `json:"coordinatorLimit"` // max Coordinators count in 1 group + MaxConsensusVals uint64 `json:"maxConsensusVals"` // The consensus validators count + }{ + MaxGroupValidators: ece.Extend0170.Common.MaxGroupValidators, + CoordinatorsLimit: ece.Extend0170.Common.CoordinatorsLimit, + MaxConsensusVals: ece.Extend0170.Common.MaxConsensusVals, } bytes, err := rlp.EncodeToBytes(params) if err != nil { @@ -318,9 +327,12 @@ func getDefaultEMConfig(netId int8) *EconomicModel { Restricting: restrictingConfigExtend{ MinimumRelease: new(big.Int).Mul(oneAtp, new(big.Int).SetInt64(80)), }, - Common: commonConfigExtend{ - MaxGroupValidators: 25, - CoordinatorsLimit: 5, + Extend0170: EconomicModel0170Extend{ + Common: EconomicModel0170CommonConfig{ + MaxGroupValidators: 25, + CoordinatorsLimit: 5, + MaxConsensusVals: 215, + }, }, } @@ -691,8 +703,7 @@ func BlocksWillCreate() uint64 { } func MaxConsensusVals(version uint32) uint64 { if version >= params.FORKVERSION_0_17_0 { - panic("This parameter has not been determined") - return 0 + return ece.Extend0170.Common.MaxConsensusVals } return ec.Common.MaxConsensusVals } @@ -908,9 +919,9 @@ func CalcPNew(sqrtWeight float64) float64 { } func MaxGroupValidators() uint32 { - return ece.Common.MaxGroupValidators + return ece.Extend0170.Common.MaxGroupValidators } func CoordinatorsLimit() uint32 { - return ece.Common.CoordinatorsLimit + return ece.Extend0170.Common.CoordinatorsLimit } From 0725346926501c917045c7eb8f93ffba9f910fa5 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Fri, 19 Nov 2021 18:12:57 +0800 Subject: [PATCH 079/299] Improve consensus synchronization --- consensus/cbft/cbft.go | 33 ++- consensus/cbft/consensus_process.go | 80 ++++--- consensus/cbft/protocols/protocol.go | 36 +-- consensus/cbft/rg_broadcast.go | 2 + consensus/cbft/state/state.go | 40 ++++ consensus/cbft/sync_process.go | 301 ++++++++++++++++++++------ consensus/cbft/types/crypto.go | 75 ++++--- consensus/cbft/types/crypto_test.go | 8 +- consensus/cbft/validator/validator.go | 2 +- 9 files changed, 413 insertions(+), 164 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 3a20e050f6..d52f8fce73 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -787,7 +787,7 @@ func (cbft *Cbft) OnSeal(block *types.Block, results chan<- *types.Block, stop < cbft.log.Warn("Can not got the validator, seal fail", "epoch", cbft.state.Epoch(), "nodeID", cbft.Node().ID()) return } - numValidators := cbft.validatorPool.Len(cbft.state.Epoch()) + numValidators := cbft.currentValidatorLen() currentProposer := cbft.state.ViewNumber() % uint64(numValidators) if currentProposer != uint64(me.Index) { cbft.log.Warn("You are not the current proposer", "index", me.Index, "currentProposer", currentProposer) @@ -1181,7 +1181,7 @@ func (cbft *Cbft) OnShouldSeal(result chan error) { return } - numValidators := cbft.validatorPool.Len(cbft.state.Epoch()) + numValidators := cbft.currentValidatorLen() currentProposer := cbft.state.ViewNumber() % uint64(numValidators) validator, err := cbft.isCurrentValidator() if err != nil { @@ -1430,8 +1430,8 @@ func (cbft *Cbft) isCurrentValidator() (*cbfttypes.ValidateNode, error) { } func (cbft *Cbft) currentProposer() *cbfttypes.ValidateNode { - length := cbft.validatorPool.Len(cbft.state.Epoch()) - currentProposer := cbft.state.ViewNumber() % uint64(length) + validatorLen := cbft.currentValidatorLen() + currentProposer := cbft.state.ViewNumber() % uint64(validatorLen) validator, _ := cbft.validatorPool.GetValidatorByIndex(cbft.state.Epoch(), uint32(currentProposer)) return validator } @@ -1709,7 +1709,7 @@ func (cbft *Cbft) generatePrepareQC(votes map[uint32]*protocols.PrepareVote) *ct } // Validator set prepareQC is the same as highestQC - total := cbft.validatorPool.Len(cbft.state.Epoch()) + total := cbft.currentValidatorLen() vSet := utils.NewBitArray(uint32(total)) vSet.SetIndex(vote.NodeIndex(), true) @@ -1757,7 +1757,7 @@ func (cbft *Cbft) combinePrepareQC(qcs []*ctypes.QuorumCert) *ctypes.QuorumCert initqc := qcs[0] // Validator set prepareQC is the same as highestQC - total := cbft.validatorPool.Len(cbft.state.Epoch()) + total := cbft.currentValidatorLen() vSet := utils.NewBitArray(uint32(total)) vSet = vSet.Or(initqc.ValidatorSet) @@ -1805,7 +1805,7 @@ func (cbft *Cbft) generateViewChangeQC(viewChanges map[uint32]*protocols.ViewCha ba *utils.BitArray } - total := uint32(cbft.validatorPool.Len(cbft.state.Epoch())) + total := uint32(cbft.currentValidatorLen()) qcs := make(map[common.Hash]*ViewChangeQC) @@ -1857,7 +1857,7 @@ func (cbft *Cbft) combineViewChangeQC(viewChangeQCs []*ctypes.ViewChangeQC) *cty aggSig *bls.Sign } - total := uint32(cbft.validatorPool.Len(cbft.state.Epoch())) + total := uint32(cbft.currentValidatorLen()) qcs := make(map[common.Hash]*ViewChangeQC) @@ -1944,6 +1944,10 @@ func (cbft *Cbft) verifyQuorumCert(qc *ctypes.QuorumCert) error { if err := cbft.validatorPool.EnableVerifyEpoch(qc.Epoch); err != nil { return err } + validatorLen := cbft.validatorPool.Len(qc.Epoch) + if qc.ValidatorSet.Size() != uint32(validatorLen) { + return fmt.Errorf("verify QuorumCert failed,mismatched validator size,validatorSet:%d,validatorLen:%d", qc.ValidatorSet.Size(), validatorLen) + } var cb []byte var err error @@ -1966,9 +1970,9 @@ func (cbft *Cbft) validateViewChangeQC(viewChangeQC *ctypes.ViewChangeQC, valida return fmt.Errorf("viewchangeQC has small number of signature, total:%d, threshold:%d", signsTotal, threshold) } // check for duplicate signers - anoherTotal := viewChangeQC.ValidatorSet().HasLength() - if signsTotal != anoherTotal { - return fmt.Errorf("viewchangeQC has duplicate signers, signsTotal:%d, anoherTotal:%d", signsTotal, anoherTotal) + aTotal := viewChangeQC.HasLength() + if signsTotal != aTotal { + return fmt.Errorf("viewchangeQC has duplicate signers, signsTotal:%d, aTotal:%d", signsTotal, aTotal) } var err error @@ -1976,6 +1980,11 @@ func (cbft *Cbft) validateViewChangeQC(viewChangeQC *ctypes.ViewChangeQC, valida viewNumber := uint64(0) existHash := make(map[common.Hash]interface{}) for i, vc := range viewChangeQC.QCs { + // check if ValidatorSet size is equal to validatorLimit + validatorLen := cbft.validatorPool.Len(vc.Epoch) + if vc.ValidatorSet.Size() != uint32(validatorLen) { + return fmt.Errorf("verify viewchangeQC failed,mismatched validator size,validatorSet:%d,validatorLen:%d", vc.ValidatorSet.Size(), validatorLimit) + } // Check if it is the same view if i == 0 { epoch = vc.Epoch @@ -2038,7 +2047,7 @@ func (cbft *Cbft) verifyGroupViewChangeQC(groupID uint32, viewChangeQC *ctypes.V // check parameter validity validatorLimit := cbft.groupLen(vcEpoch, groupID) if err := cbft.validateViewChangeQC(viewChangeQC, validatorLimit); err != nil { - return err + return authFailedError{err} } return cbft.verifyViewChangeQuorumCerts(viewChangeQC) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 1929a6707a..9622f9a9dd 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -290,8 +290,7 @@ func (cbft *Cbft) AllowRGQuorumCert(msg ctypes.ConsensusMsg) error { case *protocols.RGViewChangeQuorumCert: groupID = rg.GroupID //signsTotal = rg.ViewChangeQC.Len() - validatorSet = rg.ViewChangeQC.ValidatorSet() - signsTotal = validatorSet.HasLength() + signsTotal = rg.ViewChangeQC.HasLength() } if !cbft.enoughSigns(epoch, groupID, signsTotal) { @@ -741,7 +740,7 @@ func (cbft *Cbft) trySendRGBlockQuorumCert() { threshold := cbft.groupThreshold(cbft.state.Epoch(), groupID) groupVotes := cbft.groupPrepareVotes(cbft.state.Epoch(), blockIndex, groupID) if len(groupVotes) >= threshold { - // generatePrepareQC by votes + // generatePrepareQC by group votes rgqc := cbft.generatePrepareQC(groupVotes) // get parentQC var parentQC *ctypes.QuorumCert @@ -794,7 +793,7 @@ func (cbft *Cbft) trySendRGViewChangeQuorumCert() { threshold := cbft.groupThreshold(cbft.state.Epoch(), groupID) groupViewChanges := cbft.groupViewChanges(cbft.state.Epoch(), groupID) if len(groupViewChanges) >= threshold { - // generatePrepareQC by votes + // generatePrepareQC by group votes rgqc := cbft.generateViewChangeQC(groupViewChanges) // get parentQC prepareQCs := make(map[common.Hash]*ctypes.QuorumCert) @@ -903,16 +902,15 @@ func (cbft *Cbft) mergeSelectedViewChangeQuorumCerts(node *cbfttypes.ValidateNod cbft.log.Error("Failed to find the group info of the node", "epoch", vc.EpochNum(), "nodeID", node.NodeID.TerminalString(), "error", err) return } - validatorLen := cbft.validatorPool.Len(cbft.state.Epoch()) + validatorLen := cbft.currentValidatorLen() cbft.state.MergeViewChanges(groupID, []*protocols.ViewChange{vc}, validatorLen) } func (cbft *Cbft) richRGViewChangeQuorumCert(rgb *protocols.RGViewChangeQuorumCert) { mergeVcs := cbft.groupViewChanges(rgb.EpochNum(), rgb.GroupID) if len(mergeVcs) > 0 { - total := cbft.validatorPool.Len(cbft.state.Epoch()) for _, vc := range mergeVcs { - rgb.ViewChangeQC.MergeViewChange(vc, uint32(total)) + cbft.MergeViewChange(rgb.ViewChangeQC, vc) if !rgb.ViewChangeQC.ExistViewChange(vc.Epoch, vc.ViewNumber, vc.BlockHash) { if vc.PrepareQC != nil { rgb.PrepareQCs.AppendQuorumCert(vc.PrepareQC) @@ -949,6 +947,36 @@ func (cbft *Cbft) richRGViewChangeQuorumCert(rgb *protocols.RGViewChangeQuorumCe } } +func (cbft *Cbft) MergeViewChange(qcs *ctypes.ViewChangeQC, vc *protocols.ViewChange) { + validatorLen := cbft.currentValidatorLen() + + if !qcs.ExistViewChange(vc.Epoch, vc.ViewNumber, vc.BlockHash) { + qc := &ctypes.ViewChangeQuorumCert{ + Epoch: vc.Epoch, + ViewNumber: vc.ViewNumber, + BlockHash: vc.BlockHash, + BlockNumber: vc.BlockNumber, + ValidatorSet: utils.NewBitArray(uint32(validatorLen)), + } + if vc.PrepareQC != nil { + qc.BlockEpoch = vc.PrepareQC.Epoch + qc.BlockViewNumber = vc.PrepareQC.ViewNumber + //rgb.PrepareQCs.AppendQuorumCert(vc.PrepareQC) + } + qc.ValidatorSet.SetIndex(vc.ValidatorIndex, true) + qc.Signature.SetBytes(vc.Signature.Bytes()) + + qcs.AppendQuorumCert(qc) + } else { + for _, qc := range qcs.QCs { + if qc.BlockHash == vc.BlockHash && !qc.HasSign(vc.NodeIndex()) { + qc.AddSign(vc.Signature, vc.NodeIndex()) + break + } + } + } +} + // Return all votes of the specified group under the current view func (cbft *Cbft) groupPrepareVotes(epoch uint64, blockIndex, groupID uint32) map[uint32]*protocols.PrepareVote { indexes, err := cbft.validatorPool.GetValidatorIndexesByGroupID(epoch, groupID) @@ -1003,7 +1031,7 @@ func (cbft *Cbft) findQCBlock() { qc = cbft.generatePrepareQC(cbft.state.AllPrepareVoteByIndex(next)) cbft.log.Debug("Enough prepareVote have been received, generate prepareQC", "qc", qc.String()) } - return size >= threshold + return qc.Len() >= threshold } enoughRGQuorumCerts := func() bool { @@ -1011,14 +1039,14 @@ func (cbft *Cbft) findQCBlock() { size := 0 if len(rgqcs) > 0 { for _, qc := range rgqcs { - size += qc.ValidatorSet.HasLength() + size += qc.Len() } } if size >= threshold { qc = cbft.combinePrepareQC(rgqcs) cbft.log.Debug("Enough RGBlockQuorumCerts have been received, combine prepareQC", "qc", qc.String()) } - return size >= threshold + return qc.Len() >= threshold } enoughCombine := func() bool { @@ -1029,13 +1057,16 @@ func (cbft *Cbft) findQCBlock() { allVotes := cbft.state.AllPrepareVoteByIndex(next) for index, v := range allVotes { + if qc.Len() >= threshold { + break // The merge can be stopped when the threshold is reached + } if !qc.HasSign(index) { qc.AddSign(v.Signature, v.BlockIndex) } } cbft.log.Debug("Enough RGBlockQuorumCerts and prepareVote have been received, combine prepareQC", "qc", qc.String()) } - return len(knownIndexes) >= threshold + return qc.Len() >= threshold } alreadyQC := func() bool { @@ -1140,15 +1171,7 @@ func (cbft *Cbft) tryChangeView() { return } - - // TODO: get groupvalidatorslimit and coordinatorlimit from gov - if shouldGroup() { - cbft.validatorPool.SetupGroup(true, 0, 0) - } - - validatorLen := cbft.currentValidatorLen() - threshold := cbft.threshold(validatorLen) - + threshold := cbft.threshold(cbft.currentValidatorLen()) var viewChangeQC *ctypes.ViewChangeQC enoughViewChanges := func() bool { @@ -1157,22 +1180,22 @@ func (cbft *Cbft) tryChangeView() { viewChangeQC = cbft.generateViewChangeQC(cbft.state.AllViewChange()) cbft.log.Info("Receive enough viewchange, generate viewChangeQC", "view", cbft.state.ViewString(), "viewChangeQC", viewChangeQC.String()) } - return size >= threshold + return viewChangeQC.HasLength() >= threshold } enoughRGQuorumCerts := func() bool { viewChangeQCs := cbft.state.FindMaxRGViewChangeQuorumCerts() size := 0 if len(viewChangeQCs) > 0 { - for _, qc := range viewChangeQCs { - size += qc.ValidatorSet().HasLength() + for _, qcs := range viewChangeQCs { + size += qcs.HasLength() } } if size >= threshold { viewChangeQC = cbft.combineViewChangeQC(viewChangeQCs) cbft.log.Debug("Enough RGViewChangeQuorumCerts have been received, combine ViewChangeQC", "qc", qc.String()) } - return size >= threshold + return viewChangeQC.HasLength() >= threshold } enoughCombine := func() bool { @@ -1183,13 +1206,16 @@ func (cbft *Cbft) tryChangeView() { allViewChanges := cbft.state.AllViewChange() for index, v := range allViewChanges { + if viewChangeQC.HasLength() >= threshold { + break // The merge can be stopped when the threshold is reached + } if !viewChangeQC.HasSign(index) { - viewChangeQC.MergeViewChange(v, uint32(validatorLen)) + cbft.MergeViewChange(viewChangeQC, v) } } cbft.log.Debug("Enough RGViewChangeQuorumCerts and viewchange have been received, combine ViewChangeQC", "qc", qc.String()) } - return len(knownIndexes) >= threshold + return viewChangeQC.HasLength() >= threshold } alreadyQC := func() bool { @@ -1271,7 +1297,7 @@ func (cbft *Cbft) generateViewChangeQuorumCert(v *protocols.ViewChange) (*ctypes if err != nil { return nil, errors.Wrap(err, "local node is not validator") } - total := uint32(cbft.validatorPool.Len(cbft.state.Epoch())) + total := uint32(cbft.currentValidatorLen()) var aggSig bls.Sign if err := aggSig.Deserialize(v.Sign()); err != nil { return nil, err diff --git a/consensus/cbft/protocols/protocol.go b/consensus/cbft/protocols/protocol.go index e4a70a9072..f5a20c53d6 100644 --- a/consensus/cbft/protocols/protocol.go +++ b/consensus/cbft/protocols/protocol.go @@ -365,16 +365,17 @@ func (vc *ViewChange) SetSign(sign []byte) { } type ViewChanges struct { - VCs []*ViewChange - messageHash atomic.Value `rlp:"-"` + VCs []*ViewChange + RGViewChangeQuorumCerts []*RGViewChangeQuorumCert + messageHash atomic.Value `rlp:"-"` } func (v ViewChanges) String() string { if len(v.VCs) != 0 { epoch, viewNumber := v.VCs[0].Epoch, v.VCs[0].ViewNumber - return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,Len:%d}", epoch, viewNumber, len(v.VCs)) + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,VCsLen:%d,,RGLen:%d}", epoch, viewNumber, len(v.VCs), len(v.RGViewChangeQuorumCerts)) } - return fmt.Sprintf("{Len:%d}", len(v.VCs)) + return "" } func (v ViewChanges) MsgHash() common.Hash { @@ -536,12 +537,12 @@ type GetPrepareVote struct { Epoch uint64 ViewNumber uint64 BlockIndex uint32 - UnKnownSet *utils.BitArray + UnKnownSet *ctypes.UnKnownGroups messageHash atomic.Value `json:"-" rlp:"-"` } func (s *GetPrepareVote) String() string { - return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,BlockIndex:%d,UnKnownSet:%s}", s.Epoch, s.ViewNumber, s.BlockIndex, s.UnKnownSet.String()) + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,BlockIndex:%d,UnKnownSetLen:%d}", s.Epoch, s.ViewNumber, s.BlockIndex, s.UnKnownSet.UnKnownSize()) } func (s *GetPrepareVote) MsgHash() common.Hash { @@ -560,15 +561,16 @@ func (s *GetPrepareVote) BHash() common.Hash { // Message used to respond to the number of block votes. type PrepareVotes struct { - Epoch uint64 - ViewNumber uint64 - BlockIndex uint32 - Votes []*PrepareVote // Block voting set. - messageHash atomic.Value `json:"-" rlp:"-"` + Epoch uint64 + ViewNumber uint64 + BlockIndex uint32 + Votes []*PrepareVote // Block voting set. + RGBlockQuorumCerts []*RGBlockQuorumCert + messageHash atomic.Value `json:"-" rlp:"-"` } func (s *PrepareVotes) String() string { - return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,BlockIndex:%d}", s.Epoch, s.ViewNumber, s.BlockIndex) + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,BlockIndex:%d,VotesLen:%d,RGLen:%d}", s.Epoch, s.ViewNumber, s.BlockIndex, len(s.Votes), len(s.RGBlockQuorumCerts)) } func (s *PrepareVotes) MsgHash() common.Hash { @@ -745,14 +747,14 @@ func (s *LatestStatus) BHash() common.Hash { // Used to actively request to get viewChange. type GetViewChange struct { - Epoch uint64 `json:"epoch"` - ViewNumber uint64 `json:"viewNumber"` - ViewChangeBits *utils.BitArray `json:"nodeIndexes"` - messageHash atomic.Value `rlp:"-"` + Epoch uint64 `json:"epoch"` + ViewNumber uint64 `json:"viewNumber"` + UnKnownSet *ctypes.UnKnownGroups `json:"unKnownSet"` + messageHash atomic.Value `rlp:"-"` } func (s *GetViewChange) String() string { - return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,NodeIndexesLen:%s}", s.Epoch, s.ViewNumber, s.ViewChangeBits.String()) + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,UnKnownSetLen:%d}", s.Epoch, s.ViewNumber, s.UnKnownSet.UnKnownSize()) } func (s *GetViewChange) MsgHash() common.Hash { diff --git a/consensus/cbft/rg_broadcast.go b/consensus/cbft/rg_broadcast.go index a269c8e275..2585916bbf 100644 --- a/consensus/cbft/rg_broadcast.go +++ b/consensus/cbft/rg_broadcast.go @@ -318,6 +318,7 @@ func (m *RGBroadcastManager) broadcastFunc(a awaiting) { m.cbft.network.Broadcast(rg) m.hadSendRGBlockQuorumCerts[msg.Index()] = rg delete(m.awaitingRGBlockQuorumCerts, msg.Index()) + m.cbft.state.AddRGBlockQuorumCert(node.Index, rg) case *awaitingRGViewQC: viewChangeQC, prepareQCs := m.cbft.state.FindMaxGroupRGViewChangeQuorumCert(msg.GroupID()) if viewChangeQC == nil { @@ -337,6 +338,7 @@ func (m *RGBroadcastManager) broadcastFunc(a awaiting) { m.cbft.network.Broadcast(rg) m.hadSendRGViewChangeQuorumCerts[msg.Index()] = rg delete(m.awaitingRGViewChangeQuorumCerts, msg.Index()) + m.cbft.state.AddRGViewChangeQuorumCert(node.Index, rg) } } diff --git a/consensus/cbft/state/state.go b/consensus/cbft/state/state.go index 25dcfb727c..b8252de457 100644 --- a/consensus/cbft/state/state.go +++ b/consensus/cbft/state/state.go @@ -840,6 +840,10 @@ func (vs *ViewState) RGBlockQuorumCertsIndexes(blockIndex, groupID uint32) []uin return vs.viewRGBlockQuorumCerts.RGBlockQuorumCertsIndexes(blockIndex, groupID) } +func (vs *ViewState) FindMaxGroupRGBlockQuorumCert(blockIndex, groupID uint32) *protocols.RGBlockQuorumCert { + return vs.viewRGBlockQuorumCerts.FindMaxGroupRGBlockQuorumCert(blockIndex, groupID) +} + // selectedRGBlockQuorumCerts func (vs *ViewState) AddSelectRGQuorumCerts(blockIndex, groupID uint32, rgqc *ctypes.QuorumCert, parentQC *ctypes.QuorumCert) { vs.selectedRGBlockQuorumCerts.AddRGQuorumCerts(blockIndex, groupID, rgqc, parentQC) @@ -880,6 +884,10 @@ func (vs *ViewState) RGViewChangeQuorumCertsIndexes(groupID uint32) []uint32 { return vs.viewRGViewChangeQuorumCerts.RGViewChangeQuorumCertsIndexes(groupID) } +func (vs *ViewState) FindMaxRGViewChangeQuorumCert(groupID uint32) *protocols.RGViewChangeQuorumCert { + return vs.viewRGViewChangeQuorumCerts.FindMaxRGViewChangeQuorumCert(groupID) +} + // selectedRGViewChangeQuorumCerts func (vs *ViewState) AddSelectRGViewChangeQuorumCerts(groupID uint32, rgqc *ctypes.ViewChangeQC, prepareQCs map[common.Hash]*ctypes.QuorumCert) { rgqcs := make(map[common.Hash]*ctypes.ViewChangeQuorumCert) @@ -1069,6 +1077,23 @@ func (brg *viewRGBlockQuorumCerts) FindRGBlockQuorumCerts(blockIndex, groupID, v return nil } +func (brg *viewRGBlockQuorumCerts) FindMaxGroupRGBlockQuorumCert(blockIndex, groupID uint32) *protocols.RGBlockQuorumCert { + if ps, ok := brg.BlockRGBlockQuorumCerts[blockIndex]; ok { + if gs, ok := ps.GroupRGBlockQuorumCerts[groupID]; ok { + var max *protocols.RGBlockQuorumCert + for _, rg := range gs.ValidatorRGBlockQuorumCerts { + if max == nil { + max = rg + } else if rg.BlockQC.HigherSign(max.BlockQC) { + max = rg + } + } + return max + } + } + return nil +} + func (brg *viewRGBlockQuorumCerts) RGBlockQuorumCertsLen(blockIndex, groupID uint32) int { if ps, ok := brg.BlockRGBlockQuorumCerts[blockIndex]; ok { if gs, ok := ps.GroupRGBlockQuorumCerts[groupID]; ok { @@ -1343,6 +1368,21 @@ func (brg *viewRGViewChangeQuorumCerts) RGViewChangeQuorumCertsIndexes(groupID u return nil } +func (brg *viewRGViewChangeQuorumCerts) FindMaxRGViewChangeQuorumCert(groupID uint32) *protocols.RGViewChangeQuorumCert { + if ps, ok := brg.GroupRGViewChangeQuorumCerts[groupID]; ok { + var max *protocols.RGViewChangeQuorumCert + for _, rg := range ps.ValidatorRGViewChangeQuorumCerts { + if max == nil { + max = rg + } else if rg.ViewChangeQC.HigherSign(max.ViewChangeQC) { + max = rg + } + } + return max + } + return nil +} + func (brg *viewRGViewChangeQuorumCerts) clear() { brg.GroupRGViewChangeQuorumCerts = make(map[uint32]*validatorRGViewChangeQuorumCerts) } diff --git a/consensus/cbft/sync_process.go b/consensus/cbft/sync_process.go index 6faae2ee46..8df0081828 100644 --- a/consensus/cbft/sync_process.go +++ b/consensus/cbft/sync_process.go @@ -349,26 +349,58 @@ func (cbft *Cbft) OnGetPrepareVote(id string, msg *protocols.GetPrepareVote) err return nil } - prepareVoteMap := cbft.state.AllPrepareVoteByIndex(msg.BlockIndex) - // Defining an array for receiving PrepareVote. - votes := make([]*protocols.PrepareVote, 0, len(prepareVoteMap)) - if prepareVoteMap != nil { - threshold := cbft.threshold(cbft.currentValidatorLen()) - remain := threshold - (cbft.currentValidatorLen() - int(msg.UnKnownSet.Size())) - for k, v := range prepareVoteMap { - if msg.UnKnownSet.GetIndex(k) { - votes = append(votes, v) - } + if len(msg.UnKnownSet.UnKnown) > 0 { + validatorLen := cbft.currentValidatorLen() + threshold := cbft.threshold(validatorLen) + remain := threshold - (validatorLen - msg.UnKnownSet.UnKnownSize()) + + // Defining an array for receiving PrepareVote. + votes := make([]*protocols.PrepareVote, 0) + RGBlockQuorumCerts := make([]*protocols.RGBlockQuorumCert, 0) + prepareVoteMap := cbft.state.AllPrepareVoteByIndex(msg.BlockIndex) + + for _, un := range msg.UnKnownSet.UnKnown { + if un.UnKnownSet.Size() != uint32(validatorLen) { + cbft.log.Error("Invalid request params,UnKnownSet is not a specified length", "peer", id, "groupID", un.GroupID, "unKnownSet", un.UnKnownSet.String(), "validatorLen", validatorLen) + break // Do not continue processing the request + } // Limit response votes - if remain > 0 && len(votes) >= remain { + if remain <= 0 { break } + groupThreshold := cbft.groupThreshold(cbft.state.Epoch(), un.GroupID) + // the other party has not reached a group consensus, and directly returns the group aggregation signature (if it exists locally) + var rgqc *protocols.RGBlockQuorumCert + if un.UnKnownSet.HasLength() < groupThreshold { + rgqc = cbft.state.FindMaxGroupRGBlockQuorumCert(msg.BlockIndex, un.GroupID) + if rgqc != nil { + RGBlockQuorumCerts = append(RGBlockQuorumCerts, rgqc) + matched := rgqc.BlockQC.ValidatorSet.And(un.UnKnownSet).HasLength() + remain -= matched + } + } + if len(prepareVoteMap) > 0 { + for i := uint32(0); i < un.UnKnownSet.Size(); i++ { + if rgqc != nil && rgqc.BlockQC.HasSign(i) { + continue + } + if v, ok := prepareVoteMap[i]; ok { + votes = append(votes, v) + remain-- + } + // Limit response votes + if remain <= 0 { + break + } + } + } + } + + if len(votes) > 0 || len(RGBlockQuorumCerts) > 0 { + cbft.network.Send(id, &protocols.PrepareVotes{Epoch: msg.Epoch, ViewNumber: msg.ViewNumber, BlockIndex: msg.BlockIndex, Votes: votes, RGBlockQuorumCerts: RGBlockQuorumCerts}) + cbft.log.Debug("Send PrepareVotes", "peer", id, "blockIndex", msg.BlockIndex, "votes length", len(votes), "RGBlockQuorumCerts length", len(RGBlockQuorumCerts)) } - } - if len(votes) > 0 { - cbft.network.Send(id, &protocols.PrepareVotes{Epoch: msg.Epoch, ViewNumber: msg.ViewNumber, BlockIndex: msg.BlockIndex, Votes: votes}) - cbft.log.Debug("Send PrepareVotes", "peer", id, "epoch", msg.Epoch, "viewNumber", msg.ViewNumber, "blockIndex", msg.BlockIndex) } } return nil @@ -377,9 +409,32 @@ func (cbft *Cbft) OnGetPrepareVote(id string, msg *protocols.GetPrepareVote) err // OnPrepareVotes handling response from GetPrepareVote response. func (cbft *Cbft) OnPrepareVotes(id string, msg *protocols.PrepareVotes) error { cbft.log.Debug("Received message on OnPrepareVotes", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) + alreadyQC := func(hash common.Hash, number uint64) bool { + if _, qc := cbft.blockTree.FindBlockAndQC(hash, number); qc != nil { + return true + } + return false + } + + for _, rgqc := range msg.RGBlockQuorumCerts { + if alreadyQC(rgqc.BHash(), rgqc.BlockNum()) { + return nil + } + if !cbft.network.ContainsHistoryMessageHash(rgqc.MsgHash()) { + if err := cbft.OnRGBlockQuorumCert(id, rgqc); err != nil { + if e, ok := err.(HandleError); ok && e.AuthFailed() { + cbft.log.Error("OnRGBlockQuorumCert failed", "peer", id, "err", err) + } + return err + } + } + } + for _, vote := range msg.Votes { - _, qc := cbft.blockTree.FindBlockAndQC(vote.BlockHash, vote.BlockNumber) - if qc == nil && !cbft.network.ContainsHistoryMessageHash(vote.MsgHash()) { + if alreadyQC(vote.BlockHash, vote.BlockNumber) { + return nil + } + if !cbft.network.ContainsHistoryMessageHash(vote.MsgHash()) { if err := cbft.OnPrepareVote(id, vote); err != nil { if e, ok := err.(HandleError); ok && e.AuthFailed() { cbft.log.Error("OnPrepareVotes failed", "peer", id, "err", err) @@ -526,18 +581,56 @@ func (cbft *Cbft) OnGetViewChange(id string, msg *protocols.GetViewChange) error return msg.Epoch == localEpoch && msg.ViewNumber+1 < localViewNumber } - if isLocalView() { - viewChanges := cbft.state.AllViewChange() + if isLocalView() && len(msg.UnKnownSet.UnKnown) > 0 { + validatorLen := cbft.currentValidatorLen() + threshold := cbft.threshold(validatorLen) + remain := threshold - (validatorLen - msg.UnKnownSet.UnKnownSize()) - vcs := &protocols.ViewChanges{} - for k, v := range viewChanges { - if msg.ViewChangeBits.GetIndex(k) { - vcs.VCs = append(vcs.VCs, v) + viewChanges := make([]*protocols.ViewChange, 0) + RGViewChangeQuorumCerts := make([]*protocols.RGViewChangeQuorumCert, 0) + + viewChangeMap := cbft.state.AllViewChange() + + for _, un := range msg.UnKnownSet.UnKnown { + if un.UnKnownSet.Size() != uint32(validatorLen) { + cbft.log.Error("Invalid request params,UnKnownSet is not a specified length", "peer", id, "groupID", un.GroupID, "unKnownSet", un.UnKnownSet.String(), "validatorLen", validatorLen) + break // Do not continue processing the request + } + // Limit response votes + if remain <= 0 { + break + } + groupThreshold := cbft.groupThreshold(cbft.state.Epoch(), un.GroupID) + // the other party has not reached a group consensus, and directly returns the group aggregation signature (if it exists locally) + var rgqc *protocols.RGViewChangeQuorumCert + if un.UnKnownSet.HasLength() < groupThreshold { + rgqc = cbft.state.FindMaxRGViewChangeQuorumCert(un.GroupID) + if rgqc != nil { + RGViewChangeQuorumCerts = append(RGViewChangeQuorumCerts, rgqc) + matched := rgqc.ViewChangeQC.ValidatorSet().And(un.UnKnownSet).HasLength() + remain -= matched + } + } + if len(viewChangeMap) > 0 { + for i := uint32(0); i < un.UnKnownSet.Size(); i++ { + if rgqc != nil && rgqc.ViewChangeQC.HasSign(i) { + continue + } + if v, ok := viewChangeMap[i]; ok { + viewChanges = append(viewChanges, v) + remain-- + } + // Limit response votes + if remain <= 0 { + break + } + } } } - cbft.log.Debug("Send ViewChanges", "peer", id, "len", len(vcs.VCs)) - if len(vcs.VCs) != 0 { - cbft.network.Send(id, vcs) + + if len(viewChanges) > 0 || len(RGViewChangeQuorumCerts) > 0 { + cbft.network.Send(id, &protocols.ViewChanges{VCs: viewChanges, RGViewChangeQuorumCerts: RGViewChangeQuorumCerts}) + cbft.log.Debug("Send ViewChanges", "peer", id, "blockIndex", "viewChanges length", len(viewChanges), "RGViewChangeQuorumCerts length", len(RGViewChangeQuorumCerts)) } return nil } @@ -620,9 +713,9 @@ func (cbft *Cbft) trySyncViewChangeQuorumCert(id string, msg *protocols.ViewChan cbft.log.Debug("Receive future viewChange quorumCert, sync viewChangeQC with fast mode", "localView", cbft.state.ViewString(), "futureView", highestViewChangeQC.String()) // request viewChangeQC for the current view cbft.network.Send(id, &protocols.GetViewChange{ - Epoch: cbft.state.Epoch(), - ViewNumber: cbft.state.ViewNumber(), - ViewChangeBits: utils.NewBitArray(uint32(cbft.currentValidatorLen())), + Epoch: cbft.state.Epoch(), + ViewNumber: cbft.state.ViewNumber(), + UnKnownSet: &ctypes.UnKnownGroups{UnKnown: make([]*ctypes.UnKnownGroup, 0)}, }) } } @@ -631,6 +724,18 @@ func (cbft *Cbft) trySyncViewChangeQuorumCert(id string, msg *protocols.ViewChan // OnViewChanges handles the message type of ViewChangesMsg. func (cbft *Cbft) OnViewChanges(id string, msg *protocols.ViewChanges) error { cbft.log.Debug("Received message on OnViewChanges", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) + + for _, rgqc := range msg.RGViewChangeQuorumCerts { + if !cbft.network.ContainsHistoryMessageHash(rgqc.MsgHash()) { + if err := cbft.OnRGViewChangeQuorumCert(id, rgqc); err != nil { + if e, ok := err.(HandleError); ok && e.AuthFailed() { + cbft.log.Error("OnRGViewChangeQuorumCert failed", "peer", id, "err", err) + } + return err + } + } + } + for _, v := range msg.VCs { if !cbft.network.ContainsHistoryMessageHash(v.MsgHash()) { if err := cbft.OnViewChange(id, v); err != nil { @@ -653,34 +758,36 @@ func (cbft *Cbft) MissingViewChangeNodes() (v *protocols.GetViewChange, err erro cbft.asyncCallCh <- func() { defer func() { result <- struct{}{} }() - allViewChange := cbft.state.AllViewChange() - length := cbft.currentValidatorLen() - vbits := utils.NewBitArray(uint32(length)) - - // enough qc or did not reach deadline - if len(allViewChange) >= cbft.threshold(length) || !cbft.state.IsDeadline() { + if !cbft.state.IsDeadline() { v, err = nil, fmt.Errorf("no need sync viewchange") return } - for i := uint32(0); i < vbits.Size(); i++ { - if _, ok := allViewChange[i]; !ok { - vbits.SetIndex(i, true) + + threshold := cbft.threshold(cbft.currentValidatorLen()) + size := len(cbft.KnownViewChangeIndexes()) + cbft.log.Debug("The length of viewChange", "size", size, "threshold", threshold) + + if size < threshold { + unKnownGroups := cbft.MissGroupViewChanges() + if len(unKnownGroups.UnKnown) > 0 { + v, err = &protocols.GetViewChange{ + Epoch: cbft.state.Epoch(), + ViewNumber: cbft.state.ViewNumber(), + UnKnownSet: unKnownGroups, + }, nil } } - - v, err = &protocols.GetViewChange{ - Epoch: cbft.state.Epoch(), - ViewNumber: cbft.state.ViewNumber(), - ViewChangeBits: vbits, - }, nil + if v == nil { + err = fmt.Errorf("not need sync prepare vote") + } } <-result return } func (cbft *Cbft) KnownVoteIndexes(blockIndex uint32) []uint32 { - groupNodes := cbft.validatorPool.GetGroupByValidator(cbft.state.Epoch()) + groupNodes := cbft.validatorPool.GetGroupIndexes(cbft.state.Epoch()) allVotes := cbft.state.AllPrepareVoteByIndex(blockIndex) known := make([]uint32, 0) for groupID, indexes := range groupNodes { @@ -697,7 +804,7 @@ func (cbft *Cbft) KnownVoteIndexes(blockIndex uint32) []uint32 { } func (cbft *Cbft) KnownViewChangeIndexes() []uint32 { - groupNodes := cbft.validatorPool.GetGroupByValidator(cbft.state.Epoch()) + groupNodes := cbft.validatorPool.GetGroupIndexes(cbft.state.Epoch()) allViewChanges := cbft.state.AllViewChange() known := make([]uint32, 0) for groupID, indexes := range groupNodes { @@ -713,6 +820,68 @@ func (cbft *Cbft) KnownViewChangeIndexes() []uint32 { return known } +func (cbft *Cbft) MissGroupVotes(blockIndex uint32) *ctypes.UnKnownGroups { + groupNodes := cbft.validatorPool.GetGroupIndexes(cbft.state.Epoch()) + allVotes := cbft.state.AllPrepareVoteByIndex(blockIndex) + + validatorLen := cbft.currentValidatorLen() + unKnowns := &ctypes.UnKnownGroups{UnKnown: make([]*ctypes.UnKnownGroup, 0)} + + for groupID, indexes := range groupNodes { + qc, _ := cbft.state.FindMaxGroupRGQuorumCert(blockIndex, groupID) + groupLen := cbft.groupLen(cbft.state.Epoch(), groupID) + if qc == nil || qc.Len() < groupLen { + unKnownSet := utils.NewBitArray(uint32(validatorLen)) + for _, index := range indexes { + if _, ok := allVotes[index]; !ok && !qc.HasSign(index) { + if vote := cbft.csPool.GetPrepareVote(cbft.state.Epoch(), cbft.state.ViewNumber(), blockIndex, index); vote != nil { + go cbft.ReceiveMessage(ctypes.NewInnerMsgInfo(vote.Msg, vote.PeerID)) + continue + } + unKnownSet.SetIndex(index, true) + } + } + if unKnownSet.HasLength() > 0 { + unKnownGroup := &ctypes.UnKnownGroup{ + GroupID: groupID, + UnKnownSet: unKnownSet, + } + unKnowns.UnKnown = append(unKnowns.UnKnown, unKnownGroup) + } + } + } + return unKnowns +} + +func (cbft *Cbft) MissGroupViewChanges() *ctypes.UnKnownGroups { + groupNodes := cbft.validatorPool.GetGroupIndexes(cbft.state.Epoch()) + allViewChange := cbft.state.AllViewChange() + + validatorLen := cbft.currentValidatorLen() + unKnowns := &ctypes.UnKnownGroups{UnKnown: make([]*ctypes.UnKnownGroup, 0)} + + for groupID, indexes := range groupNodes { + qc, _ := cbft.state.FindMaxGroupRGViewChangeQuorumCert(groupID) + groupLen := cbft.groupLen(cbft.state.Epoch(), groupID) + if qc == nil || qc.Len() < groupLen { + unKnownSet := utils.NewBitArray(uint32(validatorLen)) + for _, index := range indexes { + if _, ok := allViewChange[index]; !ok && !qc.HasSign(index) { + unKnownSet.SetIndex(index, true) + } + } + if unKnownSet.HasLength() > 0 { + unKnownGroup := &ctypes.UnKnownGroup{ + GroupID: groupID, + UnKnownSet: unKnownSet, + } + unKnowns.UnKnown = append(unKnowns.UnKnown, unKnownGroup) + } + } + } + return unKnowns +} + // MissingPrepareVote returns missing vote. func (cbft *Cbft) MissingPrepareVote() (v *protocols.GetPrepareVote, err error) { result := make(chan struct{}) @@ -722,37 +891,31 @@ func (cbft *Cbft) MissingPrepareVote() (v *protocols.GetPrepareVote, err error) begin := cbft.state.MaxQCIndex() + 1 end := cbft.state.NextViewBlockIndex() - len := cbft.currentValidatorLen() - cbft.log.Debug("MissingPrepareVote", "epoch", cbft.state.Epoch(), "viewNumber", cbft.state.ViewNumber(), "beginIndex", begin, "endIndex", end, "validatorLen", len) + threshold := cbft.threshold(cbft.currentValidatorLen()) block := cbft.state.HighestQCBlock() blockTime := common.MillisToTime(int64(block.Time())) for index := begin; index < end; index++ { - size := cbft.state.PrepareVoteLenByIndex(index) - cbft.log.Debug("The length of prepare vote", "index", index, "size", size) + if time.Since(blockTime) < syncPrepareVotesInterval { + err = fmt.Errorf("not need sync prepare vote") + break + } + size := len(cbft.KnownVoteIndexes(index)) // We need sync prepare votes when a long time not arrived QC. - if size < cbft.threshold(len) && time.Since(blockTime) >= syncPrepareVotesInterval { // need sync prepare votes - knownVotes := cbft.state.AllPrepareVoteByIndex(index) - unKnownSet := utils.NewBitArray(uint32(len)) - for i := uint32(0); i < unKnownSet.Size(); i++ { - if vote := cbft.csPool.GetPrepareVote(cbft.state.Epoch(), cbft.state.ViewNumber(), index, i); vote != nil { - go cbft.ReceiveMessage(ctypes.NewInnerMsgInfo(vote.Msg, vote.PeerID)) - continue - } - if _, ok := knownVotes[i]; !ok { - unKnownSet.SetIndex(i, true) - } + if size < threshold { // need sync prepare votes + cbft.log.Debug("MissingPrepareVote", "blockIndex", index, "size", size, "threshold", threshold) + unKnownGroups := cbft.MissGroupVotes(index) + if len(unKnownGroups.UnKnown) > 0 { + v, err = &protocols.GetPrepareVote{ + Epoch: cbft.state.Epoch(), + ViewNumber: cbft.state.ViewNumber(), + BlockIndex: index, + UnKnownSet: unKnownGroups, + }, nil + break } - - v, err = &protocols.GetPrepareVote{ - Epoch: cbft.state.Epoch(), - ViewNumber: cbft.state.ViewNumber(), - BlockIndex: index, - UnKnownSet: unKnownSet, - }, nil - break } } if v == nil { diff --git a/consensus/cbft/types/crypto.go b/consensus/cbft/types/crypto.go index 86ea3caae4..ef13a72f5a 100644 --- a/consensus/cbft/types/crypto.go +++ b/consensus/cbft/types/crypto.go @@ -18,15 +18,13 @@ package types import ( "fmt" - "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" - "reflect" - "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/common/hexutil" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" "github.com/AlayaNetwork/Alaya-Go/rlp" + "reflect" ) const ( @@ -154,6 +152,9 @@ func (q *QuorumCert) HigherSign(c *QuorumCert) bool { } func (q *QuorumCert) HasSign(signIndex uint32) bool { + if q == nil || q.ValidatorSet == nil { + return false + } return q.ValidatorSet.GetIndex(signIndex) } @@ -288,6 +289,9 @@ func (q *ViewChangeQuorumCert) HigherSign(c *ViewChangeQuorumCert) bool { } func (q *ViewChangeQuorumCert) HasSign(signIndex uint32) bool { + if q == nil || q.ValidatorSet == nil { + return false + } return q.ValidatorSet.GetIndex(signIndex) } @@ -328,7 +332,7 @@ func (v ViewChangeQC) MaxBlock() (uint64, uint64, uint64, uint64, common.Hash, u return maxQC.Epoch, maxQC.ViewNumber, maxQC.BlockEpoch, maxQC.BlockViewNumber, maxQC.BlockHash, maxQC.BlockNumber } -func (v ViewChangeQC) Len() int { +func (v *ViewChangeQC) Len() int { length := 0 for _, qc := range v.QCs { length += qc.Len() @@ -336,7 +340,11 @@ func (v ViewChangeQC) Len() int { return length } -func (v ViewChangeQC) ValidatorSet() *utils.BitArray { +func (v *ViewChangeQC) HasLength() int { + return v.ValidatorSet().HasLength() +} + +func (v *ViewChangeQC) ValidatorSet() *utils.BitArray { if len(v.QCs) > 0 { vSet := v.QCs[0].ValidatorSet for i := 1; i < len(v.QCs); i++ { @@ -358,6 +366,10 @@ func (v *ViewChangeQC) HasSign(signIndex uint32) bool { return false } +func (v *ViewChangeQC) HigherSign(c *ViewChangeQC) bool { + return v.HasLength() > c.HasLength() +} + func (v ViewChangeQC) String() string { epoch, view, blockEpoch, blockViewNumber, hash, number := v.MaxBlock() return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,BlockEpoch:%d,BlockViewNumber:%d,Hash:%s,Number:%d}", epoch, view, blockEpoch, blockViewNumber, hash.TerminalString(), number) @@ -376,34 +388,6 @@ func (v *ViewChangeQC) AppendQuorumCert(viewChangeQC *ViewChangeQuorumCert) { v.QCs = append(v.QCs, viewChangeQC) } -func (v *ViewChangeQC) MergeViewChange(vc *protocols.ViewChange, validatorLen uint32) { - if !v.ExistViewChange(vc.Epoch, vc.ViewNumber, vc.BlockHash) { - qc := &ViewChangeQuorumCert{ - Epoch: vc.Epoch, - ViewNumber: vc.ViewNumber, - BlockHash: vc.BlockHash, - BlockNumber: vc.BlockNumber, - ValidatorSet: utils.NewBitArray(validatorLen), - } - if vc.PrepareQC != nil { - qc.BlockEpoch = vc.PrepareQC.Epoch - qc.BlockViewNumber = vc.PrepareQC.ViewNumber - //rgb.PrepareQCs.AppendQuorumCert(vc.PrepareQC) - } - qc.ValidatorSet.SetIndex(vc.ValidatorIndex, true) - qc.Signature.SetBytes(vc.Signature.Bytes()) - - v.AppendQuorumCert(qc) - } else { - for _, qc := range v.QCs { - if qc.BlockHash == vc.BlockHash && !qc.HasSign(vc.NodeIndex()) { - qc.AddSign(vc.Signature, vc.NodeIndex()) - break - } - } - } -} - type PrepareQCs struct { QCs []*QuorumCert `json:"qcs"` } @@ -434,3 +418,28 @@ func (p *PrepareQCs) FlattenMap() map[common.Hash]*QuorumCert { func (p *PrepareQCs) AppendQuorumCert(qc *QuorumCert) { p.QCs = append(p.QCs, qc) } + +type UnKnownGroups struct { + UnKnown []*UnKnownGroup `json:"unKnown"` +} + +type UnKnownGroup struct { + GroupID uint32 `json:"groupID"` + UnKnownSet *utils.BitArray +} + +func (unKnowns *UnKnownGroups) UnKnownSize() int { + if unKnowns == nil || len(unKnowns.UnKnown) <= 0 { + return 0 + } + + var unKnownSets *utils.BitArray + for _, un := range unKnowns.UnKnown { + if unKnownSets == nil { + unKnownSets = un.UnKnownSet + } else { + unKnownSets = unKnownSets.Or(un.UnKnownSet) + } + } + return unKnownSets.HasLength() +} diff --git a/consensus/cbft/types/crypto_test.go b/consensus/cbft/types/crypto_test.go index c182b90815..ed0803adce 100644 --- a/consensus/cbft/types/crypto_test.go +++ b/consensus/cbft/types/crypto_test.go @@ -19,13 +19,11 @@ package types import ( "encoding/json" "fmt" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" + "github.com/stretchr/testify/assert" "testing" "time" - "github.com/stretchr/testify/assert" - - "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" - "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" ) @@ -144,7 +142,7 @@ func TestValidatorSet(t *testing.T) { } assert.Equal(t, 45, viewChangeQC.Len()) assert.Equal(t, uint32(15), viewChangeQC.ValidatorSet().Size()) - assert.Equal(t, 11, viewChangeQC.ValidatorSet().HasLength()) + assert.Equal(t, 11, viewChangeQC.HasLength()) } func TestQuorumCertAddSign(t *testing.T) { diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index d509d7f46a..89d1474c63 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -801,6 +801,6 @@ func (vp *ValidatorPool) GetGroupByValidatorID(epoch uint64, nodeID enode.ID) (u } // 返回指定epoch下节点的分组信息,key=groupID,value=分组节点index集合 -func (vp *ValidatorPool) GetGroupByValidator(epoch uint64) map[uint32][]uint32 { +func (vp *ValidatorPool) GetGroupIndexes(epoch uint64) map[uint32][]uint32 { return nil } From b7512a9813ef8796c4df74ea0d8cf5f743024574 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 26 Nov 2021 16:29:55 +0800 Subject: [PATCH 080/299] update --- core/blockchain_reactor.go | 23 +++++++++++------------ x/plugin/gov_plugin.go | 4 ++-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/core/blockchain_reactor.go b/core/blockchain_reactor.go index b891343119..d4d5c4859f 100644 --- a/core/blockchain_reactor.go +++ b/core/blockchain_reactor.go @@ -47,18 +47,17 @@ import ( ) type BlockChainReactor struct { - vh *handler.VrfHandler - eventMux *event.TypeMux - bftResultSub *event.TypeMuxSubscription - basePluginMap map[int]plugin.BasePlugin // xxPlugin container - beginRule []int // Order rules for xxPlugins called in BeginBlocker - endRule []int // Order rules for xxPlugins called in EndBlocker - validatorMode string // mode: static, inner, ppos - NodeId enode.IDv0 // The nodeId of current node - exitCh chan chan struct{} // Used to receive an exit signal - exitOnce sync.Once - chainID *big.Int - acurrentVersion uint32 + vh *handler.VrfHandler + eventMux *event.TypeMux + bftResultSub *event.TypeMuxSubscription + basePluginMap map[int]plugin.BasePlugin // xxPlugin container + beginRule []int // Order rules for xxPlugins called in BeginBlocker + endRule []int // Order rules for xxPlugins called in EndBlocker + validatorMode string // mode: static, inner, ppos + NodeId enode.IDv0 // The nodeId of current node + exitCh chan chan struct{} // Used to receive an exit signal + exitOnce sync.Once + chainID *big.Int } var ( diff --git a/x/plugin/gov_plugin.go b/x/plugin/gov_plugin.go index 088e8ccf8a..b65e80cd8e 100644 --- a/x/plugin/gov_plugin.go +++ b/x/plugin/gov_plugin.go @@ -159,8 +159,6 @@ func (govPlugin *GovPlugin) BeginBlock(blockHash common.Hash, header *types.Head } log.Info("Successfully upgraded the new version 0.16.0", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveProposalID", preActiveVersionProposalID) } - header.SetActiveVersion(versionProposal.NewVersion) - log.Info("version proposal is active", "blockNumber", blockNumber, "proposalID", versionProposal.ProposalID, "newVersion", versionProposal.NewVersion, "newVersionString", xutil.ProgramVersion2Str(versionProposal.NewVersion)) if versionProposal.NewVersion == params.FORKVERSION_0_17_0 { if err := gov.WriteEcHash0170(state); nil != err { log.Error("save EcHash0140 to stateDB failed.", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveProposalID", preActiveVersionProposalID) @@ -168,6 +166,8 @@ func (govPlugin *GovPlugin) BeginBlock(blockHash common.Hash, header *types.Head } log.Info("Successfully upgraded the new version 0.14.0", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveProposalID", preActiveVersionProposalID) } + header.SetActiveVersion(versionProposal.NewVersion) + log.Info("version proposal is active", "blockNumber", blockNumber, "proposalID", versionProposal.ProposalID, "newVersion", versionProposal.NewVersion, "newVersionString", xutil.ProgramVersion2Str(versionProposal.NewVersion)) } } return nil From d854028e25c6ee5f837f2cd4299644042c58b3af Mon Sep 17 00:00:00 2001 From: benbaley Date: Fri, 26 Nov 2021 16:36:27 +0800 Subject: [PATCH 081/299] update validator pool --- consensus/cbft/cbft.go | 7 +++---- consensus/cbft/cbft_common_util.go | 2 -- consensus/cbft/cbft_test.go | 6 +++--- consensus/cbft/consensus_process.go | 6 ------ consensus/cbft/validator/validator.go | 12 ++++++------ consensus/cbft/validator/validator_test.go | 16 ++++++++-------- consensus/consensus.go | 1 + core/cbfttypes/type.go | 12 +++++++----- 8 files changed, 28 insertions(+), 34 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 7c2097eeb0..b60ccc867d 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -279,14 +279,13 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCache consensus.BlockC cbft.config.Option.NodeID = cbft.config.Option.Node.IDv0() cbft.config.Option.NodePriKey = cbft.nodeServiceContext.NodePriKey() } + needGroup := blockCache.GetActiveVersion(block.Header().SealHash()) >= params.FORKVERSION_0_17_0 - groupValidatorsLimit := cbft.config.Sys.GroupValidatorsLimit - coordinatorLimit := cbft.config.Sys.CoordinatorLimit if isGenesis() { - cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), cstate.DefaultEpoch, cbft.config.Option.Node.ID(), needGroup, groupValidatorsLimit, coordinatorLimit, cbft.eventMux) + cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), cstate.DefaultEpoch, cbft.config.Option.Node.ID(), needGroup, cbft.eventMux) cbft.changeView(cstate.DefaultEpoch, cstate.DefaultViewNumber, block, qc, nil) } else { - cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), qc.Epoch, cbft.config.Option.Node.ID(), needGroup, groupValidatorsLimit, coordinatorLimit, cbft.eventMux) + cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), qc.Epoch, cbft.config.Option.Node.ID(), needGroup, cbft.eventMux) cbft.changeView(qc.Epoch, qc.ViewNumber, block, qc, nil) } diff --git a/consensus/cbft/cbft_common_util.go b/consensus/cbft/cbft_common_util.go index ecccf5104c..f87589edda 100644 --- a/consensus/cbft/cbft_common_util.go +++ b/consensus/cbft/cbft_common_util.go @@ -134,8 +134,6 @@ func CreateCBFT(pk *ecdsa.PrivateKey, sk *bls.SecretKey, period uint64, amount u Period: period, Amount: amount, InitialNodes: []params.CbftNode{}, - GroupValidatorsLimit: 25, - CoordinatorLimit: 5, } pnode := enode.NewV4(&pk.PublicKey, nil, 0, 0) diff --git a/consensus/cbft/cbft_test.go b/consensus/cbft/cbft_test.go index 298b7598e6..234ed44d96 100644 --- a/consensus/cbft/cbft_test.go +++ b/consensus/cbft/cbft_test.go @@ -80,7 +80,7 @@ func TestBls(t *testing.T) { agency := validator.NewStaticAgency(nodes) cbft := &Cbft{ - validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0), + validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false), config: ctypes.Config{ Option: &ctypes.OptionsConfig{ BlsPriKey: owner, @@ -105,7 +105,7 @@ func TestPrepareBlockBls(t *testing.T) { agency := validator.NewStaticAgency([]params.CbftNode{node}) cbft := &Cbft{ - validatorPool: validator.NewValidatorPool(agency, 0, 0, node.Node.ID(),false, 0, 0), + validatorPool: validator.NewValidatorPool(agency, 0, 0, node.Node.ID(), false), config: ctypes.Config{ Option: &ctypes.OptionsConfig{ BlsPriKey: owner, @@ -174,7 +174,7 @@ func TestAgg(t *testing.T) { for i := 0; i < num; i++ { cnode[i] = &Cbft{ - validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0), + validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false), config: ctypes.Config{ Option: &ctypes.OptionsConfig{ BlsPriKey: sk[i], diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 1929a6707a..070d053dfb 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -1140,12 +1140,6 @@ func (cbft *Cbft) tryChangeView() { return } - - // TODO: get groupvalidatorslimit and coordinatorlimit from gov - if shouldGroup() { - cbft.validatorPool.SetupGroup(true, 0, 0) - } - validatorLen := cbft.currentValidatorLen() threshold := cbft.threshold(validatorLen) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 4885c57424..15eeb1bf2e 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -324,14 +324,14 @@ type ValidatorPool struct { } // NewValidatorPool new a validator pool. -func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID enode.ID, needGroup bool, groupValidatorsLimit, coordinatorLimit uint32, eventMux *event.TypeMux) *ValidatorPool { +func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID enode.ID, needGroup bool, eventMux *event.TypeMux) *ValidatorPool { pool := &ValidatorPool{ agency: agency, nodeID: nodeID, epoch: epoch, needGroup: needGroup, - groupValidatorsLimit: groupValidatorsLimit, - coordinatorLimit: coordinatorLimit, + groupValidatorsLimit: xutil.MaxGroupValidators(), + coordinatorLimit: xutil.CoordinatorsLimit(), } // FIXME: Check `GetValidators` return error if agency.GetLastNumber(blockNumber) == blockNumber { @@ -354,7 +354,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID pool.switchPoint = pool.currentValidators.ValidBlockNumber - 1 } if needGroup { - pool.prevValidators.Grouped(groupValidatorsLimit, coordinatorLimit, eventMux, epoch) + pool.prevValidators.Grouped(eventMux, epoch) pool.unitID = pool.currentValidators.UnitID(nodeID) } log.Debug("Update validator", "validators", pool.currentValidators.String(), "switchpoint", pool.switchPoint, "epoch", pool.epoch, "lastNumber", pool.lastNumber) @@ -378,7 +378,7 @@ func (vp *ValidatorPool) Reset(blockNumber uint64, epoch uint64, eventMux *event vp.switchPoint = vp.currentValidators.ValidBlockNumber - 1 } if vp.needGroup { - vp.currentValidators.Grouped(vp.groupValidatorsLimit, vp.coordinatorLimit, eventMux, epoch) + vp.currentValidators.Grouped(eventMux, epoch) vp.unitID = vp.currentValidators.UnitID(vp.nodeID) } log.Debug("Update validator", "validators", vp.currentValidators.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) @@ -432,7 +432,7 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *even } if vp.needGroup { - nds.Grouped(vp.groupValidatorsLimit, vp.coordinatorLimit, eventMux, epoch) + nds.Grouped(eventMux, epoch) vp.unitID = nds.UnitID(vp.nodeID) } diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index 729524cb3e..b2537f1c6e 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -447,7 +447,7 @@ func TestValidatorPool(t *testing.T) { nodes := newTestNode() agency := newTestInnerAgency(nodes) - validatorPool := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0, nil) + validatorPool := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, nil) assert.False(t, validatorPool.ShouldSwitch(0)) assert.True(t, validatorPool.ShouldSwitch(40)) @@ -533,7 +533,7 @@ func TestValidatorPoolVerify(t *testing.T) { nodes = append(nodes, params.CbftNode{Node: n4, BlsPubKey: *sec4.GetPublicKey()}) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0, nil) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, nil) m := "test sig" @@ -606,7 +606,7 @@ func (m *mockAgency) OnCommit(block *types.Block) error { return nil } func TestValidatorPoolReset(t *testing.T) { agency := newMockAgency(100) - vp := NewValidatorPool(agency, 0, 0, enode.ID{},false, 0, 0, nil) + vp := NewValidatorPool(agency, 0, 0, enode.ID{}, false, nil) vp.Reset(100, 10, nil) assert.Equal(t, vp.switchPoint, uint64(100)) @@ -621,21 +621,21 @@ func TestValidatorPoolReset(t *testing.T) { func TestValidatorGrouped(t *testing.T) { nodes := newTestNodeByNum(100) vs := newValidators(nodes, 0) - vs.Grouped(25, 5, nil, 0) + vs.Grouped(nil, 0) assert.Equal(t, 4, len(vs.GroupNodes)) assert.Equal(t, 25, len(vs.GroupNodes[3].Nodes)) assert.Equal(t, uint32(74), vs.GroupNodes[2].Nodes[24].Index) assert.Equal(t, 5, len(vs.GroupNodes[2].Units)) assert.Equal(t, uint32(74), vs.GroupNodes[2].Units[4][4]) - vs.Grouped(28, 5, nil, 0) + vs.Grouped(nil, 0) assert.Equal(t, 4, len(vs.GroupNodes)) assert.Equal(t, 25, len(vs.GroupNodes[0].Nodes)) assert.Equal(t, 25, len(vs.GroupNodes[2].Nodes)) assert.Equal(t, 25, len(vs.GroupNodes[3].Nodes)) assert.Equal(t, uint32(79), vs.GroupNodes[3].Units[0][4]) - vs.Grouped(19, 3, nil, 0) + vs.Grouped(nil, 0) assert.Equal(t, 6, len(vs.GroupNodes)) assert.Equal(t, 17, len(vs.GroupNodes[0].Nodes)) assert.Equal(t, 6, len(vs.GroupNodes[2].Units)) @@ -646,7 +646,7 @@ func TestGetGroupID(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNodeByNum(100) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),true, 25, 5, nil) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), true, nil) grpID, _ := vp.GetGroupID(0, nodes[0].Node.ID()) assert.Equal(t, 0, grpID) @@ -656,7 +656,7 @@ func TestGetUintID(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNodeByNum(100) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0, nil) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, nil) untID, _ := vp.GetGroupID(0, nodes[0].Node.ID()) assert.Equal(t, 0, untID) diff --git a/consensus/consensus.go b/consensus/consensus.go index 184d364f70..5810520b4d 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -71,6 +71,7 @@ type BlockCache interface { // CurrentActiveVersion return current gov version GetActiveVersion(sealhash common.Hash) uint32 + } // Engine is an algorithm agnostic consensus engine. diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 79f0c5e843..091f7ce2bd 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -24,6 +24,7 @@ import ( "fmt" "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/log" + "github.com/AlayaNetwork/Alaya-Go/x/xutil" "math" "sort" @@ -315,7 +316,8 @@ func (vs *Validators) UnitID(nodeID enode.ID) uint32 { return unitID } -func (gvs *GroupValidators) GroupedUnits(coordinatorLimit uint32) { +func (gvs *GroupValidators) GroupedUnits() { + coordinatorLimit := xutil.CoordinatorsLimit() unit := make([]uint32, 0, coordinatorLimit) for i, n := range gvs.Nodes { unit = append(unit, n.Index) @@ -330,15 +332,15 @@ func (gvs *GroupValidators) GroupedUnits(coordinatorLimit uint32) { // groupValidatorsLimit is a factor to determine how many groups are grouped // eg: [validatorCount,groupValidatorsLimit]= // [50,25] = 25,25;[43,25] = 22,21; [101,25] = 21,20,20,20,20 -func (vs *Validators) Grouped(groupValidatorsLimit uint32, coordinatorLimit uint32, eventMux *event.TypeMux, epoch uint64) error { +func (vs *Validators) Grouped(eventMux *event.TypeMux, epoch uint64) error { // sort nodes by index if len(vs.SortedNodes) == 0 { vs.Sort() } validatorCount := uint32(vs.SortedNodes.Len()) - groupNum := validatorCount / groupValidatorsLimit - mod := validatorCount % groupValidatorsLimit + groupNum := validatorCount / xutil.MaxGroupValidators() + mod := validatorCount % xutil.CoordinatorsLimit() if mod > 0 { groupNum = groupNum + 1 } @@ -367,7 +369,7 @@ func (vs *Validators) Grouped(groupValidatorsLimit uint32, coordinatorLimit uint // fill group unit for i, gvs := range vs.GroupNodes { - gvs.GroupedUnits(coordinatorLimit) + gvs.GroupedUnits() topic := ConsensusGroupTopicName(epoch, uint32(i)) eventMux.Post(NewGroupsEvent{Topic: topic, Validators: gvs}) } From 687a3a29304e155f1ed31d30ff25ff1ed1ba56cd Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 29 Nov 2021 10:43:38 +0800 Subject: [PATCH 082/299] fix lob --- x/plugin/gov_plugin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/plugin/gov_plugin.go b/x/plugin/gov_plugin.go index b65e80cd8e..ddac018a20 100644 --- a/x/plugin/gov_plugin.go +++ b/x/plugin/gov_plugin.go @@ -161,10 +161,10 @@ func (govPlugin *GovPlugin) BeginBlock(blockHash common.Hash, header *types.Head } if versionProposal.NewVersion == params.FORKVERSION_0_17_0 { if err := gov.WriteEcHash0170(state); nil != err { - log.Error("save EcHash0140 to stateDB failed.", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveProposalID", preActiveVersionProposalID) + log.Error("save EcHash0170 to stateDB failed.", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveProposalID", preActiveVersionProposalID) return err } - log.Info("Successfully upgraded the new version 0.14.0", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveProposalID", preActiveVersionProposalID) + log.Info("Successfully upgraded the new version 0.17.0", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveProposalID", preActiveVersionProposalID) } header.SetActiveVersion(versionProposal.NewVersion) log.Info("version proposal is active", "blockNumber", blockNumber, "proposalID", versionProposal.ProposalID, "newVersion", versionProposal.NewVersion, "newVersionString", xutil.ProgramVersion2Str(versionProposal.NewVersion)) From ed724669bac4891de275908ff47b2c899f01d81b Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 30 Nov 2021 15:38:52 +0800 Subject: [PATCH 083/299] update active block in 0170 --- core/vm/gov_contract.go | 8 ++++++++ core/vm/gov_contract_test.go | 10 +++++----- x/gov/gov.go | 3 +++ x/gov/proposals.go | 7 ++++++- x/plugin/gov_plugin.go | 2 +- 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/core/vm/gov_contract.go b/core/vm/gov_contract.go index 6c8cc595ca..c977eb341c 100644 --- a/core/vm/gov_contract.go +++ b/core/vm/gov_contract.go @@ -342,6 +342,8 @@ func (gc *GovContract) getProposal(proposalID common.Hash) ([]byte, error) { blockNumber := gc.Evm.BlockNumber.Uint64() //blockHash := gc.Evm.BlockHash txHash := gc.Evm.StateDB.TxHash() + state := gc.Evm.StateDB + log.Debug("call getProposal of GovContract", "from", from, "txHash", txHash, @@ -349,6 +351,12 @@ func (gc *GovContract) getProposal(proposalID common.Hash) ([]byte, error) { "proposalID", proposalID) proposal, err := gov.GetExistProposal(proposalID, gc.Evm.StateDB) + if err == nil { + if versionProposal, ok := proposal.(*gov.VersionProposal); ok { + versionProposal.ActiveBlock = versionProposal.GetActiveBlock(gov.GetCurrentActiveVersion(state)) + return gc.callHandler("getProposal", proposal, err) + } + } return gc.callHandler("getProposal", proposal, err) } diff --git a/core/vm/gov_contract_test.go b/core/vm/gov_contract_test.go index e15fb80195..137a1aeb98 100644 --- a/core/vm/gov_contract_test.go +++ b/core/vm/gov_contract_test.go @@ -653,7 +653,7 @@ func TestGovContract_SubmitVersion_Passed(t *testing.T) { } //skip empty blocks, this version proposal is pre-active - skip_emptyBlock(chain, p.GetActiveBlock()-1) + skip_emptyBlock(chain, p.GetActiveBlock(params.GenesisVersion)-1) } func TestGovContract_SubmitVersion_AnotherPreActive(t *testing.T) { @@ -698,7 +698,7 @@ func TestGovContract_SubmitVersion_AnotherPreActive(t *testing.T) { } //skip empty blocks, this version proposal is pre-active - skip_emptyBlock(chain, p.GetActiveBlock()-1) + skip_emptyBlock(chain, p.GetActiveBlock(params.GenesisVersion)-1) //submit another version proposal runGovContract(false, gc, buildSubmitVersion(nodeIdArr[2], "versionPIPID2", promoteVersion, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), params.GenesisVersion)), t, gov.PreActiveVersionProposalExist) } @@ -746,7 +746,7 @@ func TestGovContract_SubmitVersion_Passed_Clear(t *testing.T) { } //skip empty blocks, this version proposal is pre-active - skip_emptyBlock(chain, p.GetActiveBlock()-1) + skip_emptyBlock(chain, p.GetActiveBlock(params.GenesisVersion)-1) prepair_sndb(chain, common.ZeroHash) @@ -1437,7 +1437,7 @@ func TestGovContract_VersionProposal_Active(t *testing.T) { } //skip empty block - skip_emptyBlock(chain, p.GetActiveBlock()-1) + skip_emptyBlock(chain, p.GetActiveBlock(params.GenesisVersion)-1) // build_staking_data_more will build a new block base on chain.SnapDB.Current build_staking_data_more(chain) @@ -1497,7 +1497,7 @@ func TestGovContract_VersionProposal_Active_GetExtraParam_V0_11_0(t *testing.T) } //skip empty block - skip_emptyBlock(chain, p.GetActiveBlock()-1) + skip_emptyBlock(chain, p.GetActiveBlock(params.GenesisVersion)-1) // build_staking_data_more will build a new block base on chain.SnapDB.Current build_staking_data_more(chain) diff --git a/x/gov/gov.go b/x/gov/gov.go index 75a72b646d..1fe7daa292 100644 --- a/x/gov/gov.go +++ b/x/gov/gov.go @@ -460,6 +460,9 @@ func ListProposal(blockHash common.Hash, state xcom.StateDB) ([]Proposal, error) log.Error("find proposal error", "proposalID", proposalID) return nil, err } + if versionProposal, ok := proposal.(*VersionProposal); ok { + versionProposal.ActiveBlock = versionProposal.GetActiveBlock(GetCurrentActiveVersion(state)) + } proposals = append(proposals, proposal) } return proposals, nil diff --git a/x/gov/proposals.go b/x/gov/proposals.go index d243980ef8..f3d425cce6 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -20,6 +20,8 @@ import ( "fmt" "math/big" + "github.com/AlayaNetwork/Alaya-Go/params" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/common" @@ -216,7 +218,10 @@ func (vp *VersionProposal) GetNewVersion() uint32 { return vp.NewVersion } -func (vp *VersionProposal) GetActiveBlock() uint64 { +func (vp *VersionProposal) GetActiveBlock(version uint32) uint64 { + if vp.NewVersion == params.FORKVERSION_0_17_0 { + return xutil.CalculateEpoch(vp.ActiveBlock, version)*xcom.EpochSize(version) + 1 + } return vp.ActiveBlock } diff --git a/x/plugin/gov_plugin.go b/x/plugin/gov_plugin.go index ddac018a20..fa2f3a5d18 100644 --- a/x/plugin/gov_plugin.go +++ b/x/plugin/gov_plugin.go @@ -96,7 +96,7 @@ func (govPlugin *GovPlugin) BeginBlock(blockHash common.Hash, header *types.Head if isVersionProposal { //log.Debug("found pre-active version proposal", "proposalID", preActiveVersionProposalID, "blockNumber", blockNumber, "blockHash", blockHash, "activeBlockNumber", versionProposal.GetActiveBlock()) - if blockNumber == versionProposal.GetActiveBlock() { + if blockNumber == versionProposal.GetActiveBlock(header.GetActiveVersion()) { if params.LtMinorVersion(versionProposal.NewVersion) { panic(fmt.Sprintf("Please upgrade to:%s", params.FormatVersion(versionProposal.NewVersion))) } From 4ea854faa1ea37a3d7998960e5069bc0a1afca56 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 30 Nov 2021 17:13:48 +0800 Subject: [PATCH 084/299] fix round cal --- x/gov/gov.go | 15 +++++++++++++++ x/plugin/slashing_plugin_test.go | 2 +- x/plugin/staking_plugin.go | 20 +++++++++++++++++--- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/x/gov/gov.go b/x/gov/gov.go index 1fe7daa292..b348c7ea3b 100644 --- a/x/gov/gov.go +++ b/x/gov/gov.go @@ -142,6 +142,21 @@ func GetVersionForStaking(blockHash common.Hash, state xcom.StateDB) uint32 { } } +func GetActiveVersion(state xcom.StateDB, version uint32) ActiveVersionValue { + avList, err := ListActiveVersion(state) + if err != nil { + log.Error("Cannot find active version list", "err", err) + return ActiveVersionValue{} + } + + for _, av := range avList { + if av.ActiveVersion == version { + return av + } + } + return ActiveVersionValue{} +} + // Get current active version record func GetCurrentActiveVersion(state xcom.StateDB) uint32 { avList, err := ListActiveVersion(state) diff --git a/x/plugin/slashing_plugin_test.go b/x/plugin/slashing_plugin_test.go index 8c87522b09..f71b54a9c5 100644 --- a/x/plugin/slashing_plugin_test.go +++ b/x/plugin/slashing_plugin_test.go @@ -271,7 +271,7 @@ func buildStakingData(blockNumber uint64, blockHash common.Hash, pri *ecdsa.Priv setVerifierList(blockHash, epochArr) setRoundValList(blockHash, preArr) setRoundValList(blockHash, curArr) - err := stk.storeRoundValidatorAddrs(blockNumber, blockHash, 1, queue, gov.GetCurrentActiveVersion(stateDb)) + err := stk.storeRoundValidatorAddrs(blockNumber, blockHash, 1, queue, gov.GetCurrentActiveVersion(stateDb), stateDb) assert.Nil(t, err, fmt.Sprintf("Failed to storeRoundValidatorAddrs, err: %v", err)) balance, ok := new(big.Int).SetString("9999999999999999999999999999999999999999999999999", 10) if !ok { diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index 4ebb33e449..dba43d2099 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -27,6 +27,8 @@ import ( "strconv" "sync" + "github.com/AlayaNetwork/Alaya-Go/params" + "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" @@ -2073,7 +2075,7 @@ func (sk *StakingPlugin) Election(blockHash common.Hash, header *types.Header, s } } - if err := sk.storeRoundValidatorAddrs(blockNumber, blockHash, start, nextQueue, currentActiveVersion); nil != err { + if err := sk.storeRoundValidatorAddrs(blockNumber, blockHash, start, nextQueue, currentActiveVersion, state); nil != err { log.Error("Failed to storeRoundValidatorAddrs on Election", "blockNumber", blockNumber, "blockHash", blockHash.TerminalString(), "err", err) return err @@ -3651,8 +3653,20 @@ func (sk *StakingPlugin) addRecoveryUnStakeItem(blockNumber uint64, blockHash co } // Record the address of the verification node for each consensus round within a certain block range. -func (sk *StakingPlugin) storeRoundValidatorAddrs(blockNumber uint64, blockHash common.Hash, nextStart uint64, array staking.ValidatorQueue, version uint32) error { - nextRound := xutil.CalculateRound(nextStart, version) +func (sk *StakingPlugin) storeRoundValidatorAddrs(blockNumber uint64, blockHash common.Hash, nextStart uint64, array staking.ValidatorQueue, version uint32, state xcom.StateDB) error { + var nextRound uint64 + if version >= params.FORKVERSION_0_17_0 { + acVersion := gov.GetActiveVersion(state, params.FORKVERSION_0_17_0) + if acVersion.ActiveBlock != 0 { + roundBefore017 := xutil.CalculateRound(acVersion.ActiveBlock-1, params.FORKVERSION_0_16_0) + roundAfter017 := xutil.CalculateRound(nextStart-acVersion.ActiveBlock+1, version) + nextRound = roundBefore017 + roundAfter017 + } else { + nextRound = xutil.CalculateRound(nextStart, version) + } + } else { + nextRound = xutil.CalculateRound(nextStart, version) + } nextEpoch := xutil.CalculateEpoch(nextStart, version) evidenceAge, err := gov.GovernMaxEvidenceAge(blockNumber, blockHash) From 1a4bbb3c98604a32ff06455ac7b85e38560313fc Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 1 Dec 2021 10:20:30 +0800 Subject: [PATCH 085/299] update for calculate round --- x/plugin/slashing_plugin.go | 45 ++++++++++++++++---------------- x/plugin/slashing_plugin_test.go | 14 +++++----- x/plugin/staking_plugin.go | 18 +++---------- x/plugin/staking_plugin_test.go | 2 +- x/xutil/calculate.go | 20 +++++++++++++- 5 files changed, 53 insertions(+), 46 deletions(-) diff --git a/x/plugin/slashing_plugin.go b/x/plugin/slashing_plugin.go index 932941a107..87c402075c 100644 --- a/x/plugin/slashing_plugin.go +++ b/x/plugin/slashing_plugin.go @@ -25,6 +25,8 @@ import ( "math/big" "sync" + "github.com/AlayaNetwork/Alaya-Go/params" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/rlp" @@ -106,12 +108,12 @@ func (sp *SlashingPlugin) BeginBlock(blockHash common.Hash, header *types.Header // If it is the first block in each round, Delete old pack amount record. // Do this from the second consensus round if xutil.IsBeginOfConsensus(header.Number.Uint64(), header.GetActiveVersion()) && header.Number.Uint64() > 1 { - if err := sp.switchEpoch(header, blockHash); nil != err { + if err := sp.switchEpoch(header, blockHash, state); nil != err { log.Error("Failed to BeginBlock, call switchEpoch is failed", "blockNumber", header.Number.Uint64(), "blockHash", blockHash.TerminalString(), "err", err) return err } } - if err := sp.setPackAmount(blockHash, header); nil != err { + if err := sp.setPackAmount(blockHash, header, state); nil != err { log.Error("Failed to BeginBlock, call setPackAmount is failed", "blockNumber", header.Number.Uint64(), "blockHash", blockHash.TerminalString(), "err", err) return err } @@ -121,7 +123,7 @@ func (sp *SlashingPlugin) BeginBlock(blockHash common.Hash, header *types.Header if header.Number.Uint64() > xcom.ConsensusSize(header.GetActiveVersion()) && xutil.IsElection(header.Number.Uint64(), header.GetActiveVersion()) { log.Debug("Call GetPrePackAmount", "blockNumber", header.Number.Uint64(), "blockHash", blockHash.TerminalString(), "consensusSize", xcom.ConsensusSize(header.GetActiveVersion()), "electionDistance", xcom.ElectionDistance()) - if result, err := sp.GetPrePackAmount(header, header.ParentHash); nil != err { + if result, err := sp.GetPrePackAmount(header, header.ParentHash, state); nil != err { return err } else { if nil == result { @@ -154,7 +156,7 @@ func (sp *SlashingPlugin) BeginBlock(blockHash common.Hash, header *types.Header } } - if slashQueue, err = sp.zeroProduceProcess(blockHash, header, validatorMap, preRoundVal.Arr); nil != err { + if slashQueue, err = sp.zeroProduceProcess(blockHash, header, validatorMap, preRoundVal.Arr, state); nil != err { log.Error("Failed to BeginBlock, call zeroProduceProcess is failed", "blockNumber", header.Number.Uint64(), "blockHash", blockHash.TerminalString(), "err", err) return err } @@ -183,7 +185,7 @@ func (sp *SlashingPlugin) Confirmed(nodeId enode.IDv0, block *types.Block) error return nil } -func (sp *SlashingPlugin) zeroProduceProcess(blockHash common.Hash, header *types.Header, validatorMap map[enode.IDv0]bool, validatorQueue staking.ValidatorQueue) (staking.SlashQueue, error) { +func (sp *SlashingPlugin) zeroProduceProcess(blockHash common.Hash, header *types.Header, validatorMap map[enode.IDv0]bool, validatorQueue staking.ValidatorQueue, state xcom.StateDB) (staking.SlashQueue, error) { blockNumber := header.Number.Uint64() slashQueue := make(staking.SlashQueue, 0) waitSlashingNodeList, err := sp.getWaitSlashingNodeList(header.Number.Uint64(), blockHash) @@ -204,7 +206,7 @@ func (sp *SlashingPlugin) zeroProduceProcess(blockHash common.Hash, header *type return nil, err } - preRound := xutil.CalculateRound(header.Number.Uint64(), header.GetActiveVersion()) - 1 + preRound := xutil.CalculateRound(header.Number.Uint64(), header.GetActiveVersion(), gov.GetActiveVersion(state, params.FORKVERSION_0_17_0).ActiveBlock) - 1 log.Info("Call zeroProduceProcess start", "blockNumber", blockNumber, "blockHash", blockHash, "preRound", preRound, "zeroProduceNumberThreshold", zeroProduceNumberThreshold, "zeroProduceCumulativeTime", zeroProduceCumulativeTime, "waitSlashingNodeListSize", waitSlashingNodeList) if len(waitSlashingNodeList) > 0 { for index := 0; index < len(waitSlashingNodeList); index++ { @@ -230,7 +232,7 @@ func (sp *SlashingPlugin) zeroProduceProcess(blockHash common.Hash, header *type if ok && isProduced { isDelete = true } else { - if amount, err := sp.getPackAmount(blockNumber, blockHash, nodeId, header.GetActiveVersion()); nil != err { + if amount, err := sp.getPackAmount(blockNumber, blockHash, nodeId, header.GetActiveVersion(), state); nil != err { return nil, err } else if amount > 0 { isDelete = true @@ -435,8 +437,8 @@ func (sp *SlashingPlugin) setWaitSlashingNodeList(blockNumber uint64, blockHash return nil } -func (sp *SlashingPlugin) getPackAmount(blockNumber uint64, blockHash common.Hash, nodeId enode.IDv0, version uint32) (uint32, error) { - value, err := sp.db.Get(blockHash, buildKey(blockNumber, nodeId.Bytes(), version)) +func (sp *SlashingPlugin) getPackAmount(blockNumber uint64, blockHash common.Hash, nodeId enode.IDv0, version uint32, state xcom.StateDB) (uint32, error) { + value, err := sp.db.Get(blockHash, buildKey(blockNumber, nodeId.Bytes(), version, state)) if snapshotdb.NonDbNotFoundErr(err) { return 0, err } @@ -449,16 +451,16 @@ func (sp *SlashingPlugin) getPackAmount(blockNumber uint64, blockHash common.Has return amount, nil } -func (sp *SlashingPlugin) setPackAmount(blockHash common.Hash, header *types.Header) error { +func (sp *SlashingPlugin) setPackAmount(blockHash common.Hash, header *types.Header, state xcom.StateDB) error { nodeId, err := parseNodeId(header) if nil != err { return err } - if value, err := sp.getPackAmount(header.Number.Uint64(), blockHash, nodeId, header.GetActiveVersion()); nil != err { + if value, err := sp.getPackAmount(header.Number.Uint64(), blockHash, nodeId, header.GetActiveVersion(), state); nil != err { return err } else { value++ - if err := sp.db.Put(blockHash, buildKey(header.Number.Uint64(), nodeId.Bytes(), header.GetActiveVersion()), common.Uint32ToBytes(value)); nil != err { + if err := sp.db.Put(blockHash, buildKey(header.Number.Uint64(), nodeId.Bytes(), header.GetActiveVersion(), state), common.Uint32ToBytes(value)); nil != err { return err } log.Debug("Call setPackAmount finished", "blockNumber", header.Number.Uint64(), "blockHash", blockHash.TerminalString(), "nodeId", nodeId.TerminalString(), "value", value) @@ -466,9 +468,8 @@ func (sp *SlashingPlugin) setPackAmount(blockHash common.Hash, header *types.Hea return nil } -func (sp *SlashingPlugin) switchEpoch(header *types.Header, blockHash common.Hash) error { - - iter := sp.db.Ranking(blockHash, buildPrefixByRound(xutil.CalculateRound(header.Number.Uint64(), header.GetActiveVersion())-2), 0) +func (sp *SlashingPlugin) switchEpoch(header *types.Header, blockHash common.Hash, state xcom.StateDB) error { + iter := sp.db.Ranking(blockHash, buildPrefixByRound(xutil.CalculateRound(header.Number.Uint64(), header.GetActiveVersion(), gov.GetActiveVersion(state, params.FORKVERSION_0_17_0).ActiveBlock)-2), 0) if err := iter.Error(); nil != err { return err } @@ -488,9 +489,9 @@ func (sp *SlashingPlugin) switchEpoch(header *types.Header, blockHash common.Has } // Get the consensus rate of all nodes in the previous round -func (sp *SlashingPlugin) GetPrePackAmount(header *types.Header, parentHash common.Hash) (map[enode.IDv0]uint32, error) { +func (sp *SlashingPlugin) GetPrePackAmount(header *types.Header, parentHash common.Hash, state xcom.StateDB) (map[enode.IDv0]uint32, error) { result := make(map[enode.IDv0]uint32) - prefixKey := buildPrefixByRound(xutil.CalculateRound(header.Number.Uint64(), header.GetActiveVersion()) - 1) + prefixKey := buildPrefixByRound(xutil.CalculateRound(header.Number.Uint64(), header.GetActiveVersion(), gov.GetActiveVersion(state, params.FORKVERSION_0_17_0).ActiveBlock) - 1) iter := sp.db.Ranking(parentHash, prefixKey, 0) if err := iter.Error(); nil != err { @@ -595,7 +596,7 @@ func (sp *SlashingPlugin) Slash(evidence consensus.Evidence, blockHash common.Ha return slashing.ErrBlsPubKeyMismatch } - if has, err := stk.checkRoundValidatorAddr(blockHash, evidence.BlockNumber(), canAddr, currentVeriosn); nil != err { + if has, err := stk.checkRoundValidatorAddr(blockHash, evidence.BlockNumber(), canAddr, currentVeriosn, stateDB); nil != err { log.Error("Failed to Slash, checkRoundValidatorAddr is failed", "blockNumber", blockNumber, "blockHash", blockHash.TerminalString(), "evidenceBlockNum", evidence.BlockNumber(), "canAddr", canAddr.Hex(), "err", err) return slashing.ErrDuplicateSignVerify @@ -682,12 +683,12 @@ func duplicateSignKey(nodeId enode.IDv0, blockNumber uint64, dupType consensus.E return append(append(nodeId.Bytes(), common.Uint64ToBytes(blockNumber)...), common.Uint16ToBytes(uint16(dupType))...) } -func buildKey(blockNumber uint64, key []byte, version uint32) []byte { - return append(buildPrefix(blockNumber, version), key...) +func buildKey(blockNumber uint64, key []byte, version uint32, state xcom.StateDB) []byte { + return append(buildPrefix(blockNumber, version, state), key...) } -func buildPrefix(blockNumber uint64, version uint32) []byte { - return buildPrefixByRound(xutil.CalculateRound(blockNumber, version)) +func buildPrefix(blockNumber uint64, version uint32, state xcom.StateDB) []byte { + return buildPrefixByRound(xutil.CalculateRound(blockNumber, version, gov.GetActiveVersion(state, params.FORKVERSION_0_17_0).ActiveBlock)) } func buildPrefixByRound(round uint64) []byte { diff --git a/x/plugin/slashing_plugin_test.go b/x/plugin/slashing_plugin_test.go index f71b54a9c5..ab3ba72fba 100644 --- a/x/plugin/slashing_plugin_test.go +++ b/x/plugin/slashing_plugin_test.go @@ -719,7 +719,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { Number: blockNumber, Extra: make([]byte, 97), } - if slashingQueue, err := si.zeroProduceProcess(common.ZeroHash, header, validatorMap, validatorQueue); nil != err { + if slashingQueue, err := si.zeroProduceProcess(common.ZeroHash, header, validatorMap, validatorQueue, stateDB); nil != err { t.Fatal(err) } else if len(slashingQueue) > 0 { t.Errorf("zeroProduceProcess amount: have %v, want %v", len(slashingQueue), 0) @@ -742,10 +742,10 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { t.Fatal(err) } copy(header.Extra[len(header.Extra)-common.ExtraSeal:], sign[:]) - if err := si.setPackAmount(common.ZeroHash, header); nil != err { + if err := si.setPackAmount(common.ZeroHash, header, stateDB); nil != err { t.Fatal(err) } - if slashingQueue, err := si.zeroProduceProcess(common.ZeroHash, header, validatorMap, validatorQueue); nil != err { + if slashingQueue, err := si.zeroProduceProcess(common.ZeroHash, header, validatorMap, validatorQueue, stateDB); nil != err { t.Fatal(err) } else if len(slashingQueue) > 0 { t.Errorf("zeroProduceProcess amount: have %v, want %v", len(slashingQueue), 0) @@ -759,7 +759,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { validatorQueue = append(validatorQueue, &staking.Validator{ NodeId: nodeIdArr[0], }) - if slashingQueue, err := si.zeroProduceProcess(common.ZeroHash, header, validatorMap, validatorQueue); nil != err { + if slashingQueue, err := si.zeroProduceProcess(common.ZeroHash, header, validatorMap, validatorQueue, stateDB); nil != err { t.Fatal(err) } else if len(slashingQueue) > 0 { t.Errorf("zeroProduceProcess amount: have %v, want %v", len(slashingQueue), 0) @@ -785,7 +785,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { validatorQueue = append(validatorQueue, &staking.Validator{ NodeId: nodeIdArr[6], }) - if slashingQueue, err := si.zeroProduceProcess(common.ZeroHash, header, validatorMap, validatorQueue); nil != err { + if slashingQueue, err := si.zeroProduceProcess(common.ZeroHash, header, validatorMap, validatorQueue, stateDB); nil != err { t.Fatal(err) } else if len(slashingQueue) != 1 { t.Errorf("zeroProduceProcess amount: have %v, want %v", len(slashingQueue), 1) @@ -813,7 +813,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { validatorQueue = append(validatorQueue, &staking.Validator{ NodeId: nodeIdArr[5], }) - if slashingQueue, err := si.zeroProduceProcess(common.ZeroHash, header, validatorMap, validatorQueue); nil != err { + if slashingQueue, err := si.zeroProduceProcess(common.ZeroHash, header, validatorMap, validatorQueue, stateDB); nil != err { t.Fatal(err) } else if len(slashingQueue) > 0 { t.Errorf("zeroProduceProcess amount: have %v, want %v", len(slashingQueue), 0) @@ -833,7 +833,7 @@ func TestSlashingPlugin_ZeroProduceProcess(t *testing.T) { validatorQueue = append(validatorQueue, &staking.Validator{ NodeId: nodeIdArr[5], }) - if slashingQueue, err := si.zeroProduceProcess(common.ZeroHash, header, validatorMap, validatorQueue); nil != err { + if slashingQueue, err := si.zeroProduceProcess(common.ZeroHash, header, validatorMap, validatorQueue, stateDB); nil != err { t.Fatal(err) } else if len(slashingQueue) > 0 { t.Errorf("zeroProduceProcess amount: have %v, want %v", len(slashingQueue), 0) diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index dba43d2099..bdfe3310a4 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -3654,19 +3654,7 @@ func (sk *StakingPlugin) addRecoveryUnStakeItem(blockNumber uint64, blockHash co // Record the address of the verification node for each consensus round within a certain block range. func (sk *StakingPlugin) storeRoundValidatorAddrs(blockNumber uint64, blockHash common.Hash, nextStart uint64, array staking.ValidatorQueue, version uint32, state xcom.StateDB) error { - var nextRound uint64 - if version >= params.FORKVERSION_0_17_0 { - acVersion := gov.GetActiveVersion(state, params.FORKVERSION_0_17_0) - if acVersion.ActiveBlock != 0 { - roundBefore017 := xutil.CalculateRound(acVersion.ActiveBlock-1, params.FORKVERSION_0_16_0) - roundAfter017 := xutil.CalculateRound(nextStart-acVersion.ActiveBlock+1, version) - nextRound = roundBefore017 + roundAfter017 - } else { - nextRound = xutil.CalculateRound(nextStart, version) - } - } else { - nextRound = xutil.CalculateRound(nextStart, version) - } + nextRound := xutil.CalculateRound(nextStart, version, gov.GetActiveVersion(state, params.FORKVERSION_0_17_0).ActiveBlock) nextEpoch := xutil.CalculateEpoch(nextStart, version) evidenceAge, err := gov.GovernMaxEvidenceAge(blockNumber, blockHash) @@ -3725,8 +3713,8 @@ func (sk *StakingPlugin) storeRoundValidatorAddrs(blockNumber uint64, blockHash return nil } -func (sk *StakingPlugin) checkRoundValidatorAddr(blockHash common.Hash, targetBlockNumber uint64, addr common.NodeAddress, version uint32) (bool, error) { - targetRound := xutil.CalculateRound(targetBlockNumber, version) +func (sk *StakingPlugin) checkRoundValidatorAddr(blockHash common.Hash, targetBlockNumber uint64, addr common.NodeAddress, version uint32, state xcom.StateDB) (bool, error) { + targetRound := xutil.CalculateRound(targetBlockNumber, version, gov.GetActiveVersion(state, params.FORKVERSION_0_17_0).ActiveBlock) addrList, err := sk.db.LoadRoundValidatorAddrs(blockHash, staking.GetRoundValAddrArrKey(targetRound)) if nil != err { log.Error("Failed to checkRoundValidatorAddr", "blockHash", blockHash.TerminalString(), "targetBlockNumber", targetBlockNumber, diff --git a/x/plugin/staking_plugin_test.go b/x/plugin/staking_plugin_test.go index f693250b6e..da10c14ced 100644 --- a/x/plugin/staking_plugin_test.go +++ b/x/plugin/staking_plugin_test.go @@ -3527,7 +3527,7 @@ func TestStakingPlugin_GetLastNumber(t *testing.T) { */ endNumber := StakingInstance().GetLastNumber(header.Number.Uint64()) acVersion := gov.GetCurrentActiveVersion(state) - round := xutil.CalculateRound(header.Number.Uint64(), acVersion) + round := xutil.CalculateRound(header.Number.Uint64(), acVersion, gov.GetActiveVersion(state, params.FORKVERSION_0_17_0).ActiveBlock) blockNum := round * xcom.ConsensusSize(acVersion) assert.True(t, endNumber == blockNum, fmt.Sprintf("currentNumber: %d, currentRound: %d endNumber: %d, targetNumber: %d", header.Number, round, endNumber, blockNum)) diff --git a/x/xutil/calculate.go b/x/xutil/calculate.go index 30b9f7df35..f861622ade 100644 --- a/x/xutil/calculate.go +++ b/x/xutil/calculate.go @@ -20,6 +20,8 @@ import ( "bytes" "fmt" + "github.com/AlayaNetwork/Alaya-Go/params" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/common" @@ -96,12 +98,28 @@ func CalculateEpoch(blockNumber uint64, version uint32) uint64 { return calculateQuotient(blockNumber, size) } +func CalculateRound(blockNumber uint64, version uint32, version0170ActiveBlock uint64) uint64 { + if version >= params.FORKVERSION_0_17_0 { + // 因为主网0.17.0的共识轮需要连续,所以这里需要根据不同的块高计算 + if version0170ActiveBlock > 0 { + return calculateFork0170Round(blockNumber, version, version0170ActiveBlock) + } + } + return calculateRound(blockNumber, version) +} + // calculate the Consensus number by blockNumber -func CalculateRound(blockNumber uint64, version uint32) uint64 { +func calculateRound(blockNumber uint64, version uint32) uint64 { size := xcom.ConsensusSize(version) return calculateQuotient(blockNumber, size) } +func calculateFork0170Round(blockNumber uint64, version uint32, version017ActiveBlock uint64) uint64 { + roundBefore017 := calculateRound(version017ActiveBlock-1, params.FORKVERSION_0_16_0) + roundAfter017 := calculateRound(blockNumber-version017ActiveBlock+1, version) + return roundBefore017 + roundAfter017 +} + func calculateQuotient(blockNumber, size uint64) uint64 { var res uint64 div := blockNumber / size From fc7880b05ebf4650361a43a967a25ebc7ffdcc0d Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Thu, 2 Dec 2021 12:01:13 +0800 Subject: [PATCH 086/299] update use for currentActiveVersion --- x/plugin/staking_plugin.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index bdfe3310a4..71a30c4f38 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -1789,6 +1789,10 @@ func (sk *StakingPlugin) Election(blockHash common.Hash, header *types.Header, s start := curr.End + 1 currentActiveVersion := gov.GetCurrentActiveVersion(state) + if currentActiveVersion == 0 { + log.Error("Failed to Election, GetCurrentActiveVersion is failed", "blockNumber", header.Number.Uint64(), "blockHash", blockHash.TerminalString()) + return errors.New("Failed to get CurrentActiveVersion") + } end := curr.End + xcom.ConsensusSize(currentActiveVersion) @@ -1830,12 +1834,6 @@ func (sk *StakingPlugin) Election(blockHash common.Hash, header *types.Header, s status.IsInvalidDuplicateSign() } - currentVersion := gov.GetCurrentActiveVersion(state) - if currentVersion == 0 { - log.Error("Failed to Election, GetCurrentActiveVersion is failed", "blockNumber", header.Number.Uint64(), "blockHash", blockHash.TerminalString()) - return errors.New("Failed to get CurrentActiveVersion") - } - // 收集当前的 (验证人Id => Power) currMap := make(map[enode.IDv0]*big.Int, len(curr.Arr)) // 验证人信息 (基本上和 curr.Arr 一致) @@ -2013,7 +2011,7 @@ func (sk *StakingPlugin) Election(blockHash common.Hash, header *types.Header, s } if vrfLen != 0 { - if queue, err := vrfElection(maxConsensusVals, diffQueue, vrfLen, header.Nonce.Bytes(), header.ParentHash, blockNumber, currentVersion); nil != err { + if queue, err := vrfElection(maxConsensusVals, diffQueue, vrfLen, header.Nonce.Bytes(), header.ParentHash, blockNumber, currentActiveVersion); nil != err { log.Error("Failed to VrfElection on Election", "blockNumber", blockNumber, "blockHash", blockHash.Hex(), "err", err) return err From ef2ba10da06f814dc466cecc76d208a2e19587d1 Mon Sep 17 00:00:00 2001 From: benbaley Date: Fri, 3 Dec 2021 10:51:56 +0800 Subject: [PATCH 087/299] refactor validatorpool --- consensus/cbft/validator/validator.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 15eeb1bf2e..62f4ad73df 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -20,9 +20,10 @@ import ( "encoding/hex" "errors" "fmt" - "github.com/AlayaNetwork/Alaya-Go/x/xutil" "sync" + "github.com/AlayaNetwork/Alaya-Go/x/xutil" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/core/state" @@ -311,8 +312,8 @@ type ValidatorPool struct { // Uint ID of the group where the current node is located unitID uint32 - // needGroup indicates if validators need grouped - needGroup bool + // grouped indicates if validators need grouped + grouped bool // max validators in per group groupValidatorsLimit uint32 // coordinator limit @@ -329,7 +330,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID agency: agency, nodeID: nodeID, epoch: epoch, - needGroup: needGroup, + grouped: needGroup, groupValidatorsLimit: xutil.MaxGroupValidators(), coordinatorLimit: xutil.CoordinatorsLimit(), } @@ -377,7 +378,7 @@ func (vp *ValidatorPool) Reset(blockNumber uint64, epoch uint64, eventMux *event if vp.currentValidators.ValidBlockNumber > 0 { vp.switchPoint = vp.currentValidators.ValidBlockNumber - 1 } - if vp.needGroup { + if vp.grouped { vp.currentValidators.Grouped(eventMux, epoch) vp.unitID = vp.currentValidators.UnitID(vp.nodeID) } @@ -431,12 +432,12 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, eventMux *even return err } - if vp.needGroup { + if vp.grouped { nds.Grouped(eventMux, epoch) vp.unitID = nds.UnitID(vp.nodeID) } - if isElection && vp.needGroup { + if isElection && vp.grouped { vp.nextValidators = nds log.Info("Update nextValidators", "validators", nds.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) } else { @@ -455,7 +456,7 @@ func (vp *ValidatorPool) SetupGroup(needGroup bool) { vp.lock.Lock() defer vp.lock.Unlock() - vp.needGroup = needGroup + vp.grouped = needGroup vp.groupValidatorsLimit = xutil.MaxGroupValidators() vp.coordinatorLimit = xutil.CoordinatorsLimit() } @@ -689,7 +690,7 @@ func (vp *ValidatorPool) NeedGroup() bool { vp.lock.RLock() defer vp.lock.RUnlock() - return vp.needGroup + return vp.grouped } // GetGroupID return GroupID according epoch & NodeID From 9a6e9cc93ea17afca33dc413ed47d5138fd8dd3e Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 3 Dec 2021 16:11:41 +0800 Subject: [PATCH 088/299] update zeroProduceCumulativeTime,maxValidators --- x/plugin/gov_plugin.go | 8 ++++++ x/xcom/common_config.go | 37 +++++-------------------- x/xcom/common_config_0170.go | 52 ++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 30 deletions(-) create mode 100644 x/xcom/common_config_0170.go diff --git a/x/plugin/gov_plugin.go b/x/plugin/gov_plugin.go index fa2f3a5d18..206c0dadc8 100644 --- a/x/plugin/gov_plugin.go +++ b/x/plugin/gov_plugin.go @@ -164,8 +164,16 @@ func (govPlugin *GovPlugin) BeginBlock(blockHash common.Hash, header *types.Head log.Error("save EcHash0170 to stateDB failed.", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveProposalID", preActiveVersionProposalID) return err } + + if err := gov.UpdateGovernParamValue(gov.ModuleSlashing, gov.KeyZeroProduceCumulativeTime, fmt.Sprint(xcom.ZeroProduceCumulativeTime0170()), blockNumber, blockHash); err != nil { + return err + } + if err := gov.UpdateGovernParamValue(gov.ModuleStaking, gov.KeyMaxValidators, fmt.Sprint(xcom.MaxValidators0170()), blockNumber, blockHash); err != nil { + return err + } log.Info("Successfully upgraded the new version 0.17.0", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveProposalID", preActiveVersionProposalID) } + header.SetActiveVersion(versionProposal.NewVersion) log.Info("version proposal is active", "blockNumber", blockNumber, "proposalID", versionProposal.ProposalID, "newVersion", versionProposal.NewVersion, "newVersionString", xutil.ProgramVersion2Str(versionProposal.NewVersion)) } diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index f5aa539339..eece424d5f 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -49,7 +49,7 @@ const ( Hundred = 100 TenThousand = 10000 CeilBlocksReward = 50000 - CeilMaxValidators = 201 + CeilMaxValidators = 500 FloorMaxConsensusVals = 4 CeilMaxConsensusVals = 25 PositiveInfinity = "+∞" @@ -174,16 +174,6 @@ type restrictingConfigExtend struct { MinimumRelease *big.Int `json:"minimumRelease"` //The minimum number of Restricting release in one epoch } -type EconomicModel0170Extend struct { - Common EconomicModel0170CommonConfig `json:"common"` -} - -type EconomicModel0170CommonConfig struct { - MaxGroupValidators uint32 `json:"caxGroupValidators"` // max validators count in 1 group - CoordinatorsLimit uint32 `json:"coordinatorLimit"` // max Coordinators count in 1 group - MaxConsensusVals uint64 `json:"maxConsensusVals"` // The consensus validators count -} - // New parameters added in version 0.14.0 need to be saved on the chain. // Calculate the rlp of the new parameter and return it to the upper storage. func EcParams0140() ([]byte, error) { @@ -201,25 +191,6 @@ func EcParams0140() ([]byte, error) { return bytes, nil } -// New parameters added in version 0.17.0 need to be saved on the chain. -// Calculate the rlp of the new parameter and return it to the upper storage. -func EcParams0170() ([]byte, error) { - params := struct { - MaxGroupValidators uint32 `json:"caxGroupValidators"` // max validators count in 1 group - CoordinatorsLimit uint32 `json:"coordinatorLimit"` // max Coordinators count in 1 group - MaxConsensusVals uint64 `json:"maxConsensusVals"` // The consensus validators count - }{ - MaxGroupValidators: ece.Extend0170.Common.MaxGroupValidators, - CoordinatorsLimit: ece.Extend0170.Common.CoordinatorsLimit, - MaxConsensusVals: ece.Extend0170.Common.MaxConsensusVals, - } - bytes, err := rlp.EncodeToBytes(params) - if err != nil { - return nil, err - } - return bytes, nil -} - var ( modelOnce sync.Once ec *EconomicModel @@ -333,6 +304,12 @@ func getDefaultEMConfig(netId int8) *EconomicModel { CoordinatorsLimit: 5, MaxConsensusVals: 215, }, + Staking: EconomicModel0170StakingConfig{ + MaxValidators: 215, + }, + Slashing: EconomicModel0170SlashingConfig{ + ZeroProduceCumulativeTime: 4, + }, }, } diff --git a/x/xcom/common_config_0170.go b/x/xcom/common_config_0170.go new file mode 100644 index 0000000000..5ff5323421 --- /dev/null +++ b/x/xcom/common_config_0170.go @@ -0,0 +1,52 @@ +package xcom + +import "github.com/AlayaNetwork/Alaya-Go/rlp" + +// New parameters added in version 0.17.0 need to be saved on the chain. +// Calculate the rlp of the new parameter and return it to the upper storage. +func EcParams0170() ([]byte, error) { + params := struct { + MaxGroupValidators uint32 `json:"caxGroupValidators"` // max validators count in 1 group + CoordinatorsLimit uint32 `json:"coordinatorLimit"` // max Coordinators count in 1 group + MaxConsensusVals uint64 `json:"maxConsensusVals"` // The consensus validators count + }{ + MaxGroupValidators: ece.Extend0170.Common.MaxGroupValidators, + CoordinatorsLimit: ece.Extend0170.Common.CoordinatorsLimit, + MaxConsensusVals: ece.Extend0170.Common.MaxConsensusVals, + } + bytes, err := rlp.EncodeToBytes(params) + if err != nil { + return nil, err + } + return bytes, nil +} + +type EconomicModel0170Extend struct { + Common EconomicModel0170CommonConfig `json:"common"` + Staking EconomicModel0170StakingConfig `json:"staking"` + Slashing EconomicModel0170SlashingConfig `json:"slashing"` +} + +type EconomicModel0170CommonConfig struct { + MaxGroupValidators uint32 `json:"caxGroupValidators"` // max validators count in 1 group + CoordinatorsLimit uint32 `json:"coordinatorLimit"` // max Coordinators count in 1 group + MaxConsensusVals uint64 `json:"maxConsensusVals"` // The consensus validators count +} + +type EconomicModel0170StakingConfig struct { + MaxValidators uint64 `json:"maxValidators"` // The epoch (billing cycle) validators count +} + +type EconomicModel0170SlashingConfig struct { + ZeroProduceCumulativeTime uint16 `json:"zeroProduceCumulativeTime"` // Count the number of zero-production blocks in this time range and check it. If it reaches a certain number of times, it can be punished (unit is consensus round) +} + +// 主网升级0170时需要更新此可治理参数 +func MaxValidators0170() uint64 { + return ece.Extend0170.Staking.MaxValidators +} + +// 主网升级0170时需要更新此可治理参数 +func ZeroProduceCumulativeTime0170() uint16 { + return ece.Extend0170.Slashing.ZeroProduceCumulativeTime +} From c512df3fc67573ec612bd273b48458095ddc2d4f Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 3 Dec 2021 16:21:37 +0800 Subject: [PATCH 089/299] add license --- x/xcom/common_config_0170.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/x/xcom/common_config_0170.go b/x/xcom/common_config_0170.go index 5ff5323421..8b9b1676bf 100644 --- a/x/xcom/common_config_0170.go +++ b/x/xcom/common_config_0170.go @@ -1,3 +1,19 @@ +// Copyright 2021 The Alaya Network Authors +// This file is part of the Alaya-Go library. +// +// The Alaya-Go 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 Alaya-Go 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 Alaya-Go library. If not, see . + package xcom import "github.com/AlayaNetwork/Alaya-Go/rlp" From 162d82b29440d4507b371d8926814524f878f071 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 3 Dec 2021 17:10:41 +0800 Subject: [PATCH 090/299] fix test --- x/plugin/gov_plugin_test.go | 4 ++-- x/xcom/common_config.go | 26 ++++++++++++++++++++++++++ x/xcom/common_config_test.go | 3 +++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/x/plugin/gov_plugin_test.go b/x/plugin/gov_plugin_test.go index ad7d7ba2ab..2ff10148d3 100644 --- a/x/plugin/gov_plugin_test.go +++ b/x/plugin/gov_plugin_test.go @@ -61,7 +61,7 @@ var ( stateDB xcom.StateDB chainID = big.NewInt(100) -// stk *StakingPlugin + // stk *StakingPlugin ) func setup(t *testing.T) func() { @@ -530,7 +530,7 @@ func TestGovPlugin_SubmitVersion_NewVersionError(t *testing.T) { ProposalType: gov.Version, PIPID: "versionPIPID", SubmitBlock: 1, - EndVotingRounds: xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), currentTestGenesisVersion), + EndVotingRounds: xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), version), Proposer: nodeIdArr[0], NewVersion: newVersionErr, //error, less than activeVersion } diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index eece424d5f..91c2522256 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -371,6 +371,19 @@ func getDefaultEMConfig(netId int8) *EconomicModel { Restricting: restrictingConfigExtend{ MinimumRelease: new(big.Int).SetInt64(1), }, + Extend0170: EconomicModel0170Extend{ + Common: EconomicModel0170CommonConfig{ + MaxGroupValidators: 25, + CoordinatorsLimit: 5, + MaxConsensusVals: 25, + }, + Staking: EconomicModel0170StakingConfig{ + MaxValidators: 101, + }, + Slashing: EconomicModel0170SlashingConfig{ + ZeroProduceCumulativeTime: 30, + }, + }, } case DefaultUnitTestNet: ec = &EconomicModel{ @@ -430,6 +443,19 @@ func getDefaultEMConfig(netId int8) *EconomicModel { Restricting: restrictingConfigExtend{ MinimumRelease: new(big.Int).SetInt64(1), }, + Extend0170: EconomicModel0170Extend{ + Common: EconomicModel0170CommonConfig{ + MaxGroupValidators: 25, + CoordinatorsLimit: 5, + MaxConsensusVals: 4, + }, + Staking: EconomicModel0170StakingConfig{ + MaxValidators: 25, + }, + Slashing: EconomicModel0170SlashingConfig{ + ZeroProduceCumulativeTime: 3, + }, + }, } default: log.Error("not support chainID", "netId", netId) diff --git a/x/xcom/common_config_test.go b/x/xcom/common_config_test.go index e5531f7d00..cb034aa4f4 100644 --- a/x/xcom/common_config_test.go +++ b/x/xcom/common_config_test.go @@ -61,7 +61,9 @@ func TestEcParams0140(t *testing.T) { } } +// todo: 这个测试用例需要等版本稳定后再放开 func TestEcParams0170(t *testing.T) { + t.SkipNow() eceHash := "0x0000000000000000000000000000000000000000000000000000000000000000" getDefaultEMConfig(DefaultAlayaNet) if bytes, err := EcParams0170(); nil != err { @@ -71,6 +73,7 @@ func TestEcParams0170(t *testing.T) { assert.True(t, common.RlpHash(bytes).Hex() == eceHash) } } + func TestAlayaNetHash(t *testing.T) { alayaEc := getDefaultEMConfig(DefaultAlayaNet) bytes, err := rlp.EncodeToBytes(alayaEc) From bd07e962e7c4d8d9a2c35bba2b8928a94a20f6cc Mon Sep 17 00:00:00 2001 From: benbaley Date: Fri, 3 Dec 2021 17:23:54 +0800 Subject: [PATCH 091/299] finish GetGroupIndexes --- consensus/cbft/validator/validator.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 9daf40a17e..d617c52314 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -799,5 +799,21 @@ func (vp *ValidatorPool) GetGroupByValidatorID(epoch uint64, nodeID enode.ID) (u // 返回指定epoch下节点的分组信息,key=groupID,value=分组节点index集合 func (vp *ValidatorPool) GetGroupIndexes(epoch uint64) map[uint32][]uint32 { - return nil + vp.lock.RLock() + defer vp.lock.RUnlock() + + validators := vp.currentValidators + if vp.epochToBlockNumber(epoch) <= vp.switchPoint { + validators = vp.prevValidators + } + groupIdxs := make(map[uint32][]uint32, len(validators.GroupNodes)) + var err error + for i, _ := range validators.GroupNodes { + gid := uint32(i) + groupIdxs[gid], err = validators.GetValidatorIndexes(gid) + if nil != err { + log.Error("GetValidatorIndexes failed!", "err", err) + } + } + return groupIdxs } From 91ecfe90843ca3cac0814aad30c723c20d46c441 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Fri, 3 Dec 2021 17:49:27 +0800 Subject: [PATCH 092/299] add test --- consensus/cbft/cbft.go | 7 +- consensus/cbft/cbft_test.go | 6 +- consensus/cbft/consensus_process.go | 29 +--- consensus/cbft/consensus_process_test.go | 93 ++++++++++- consensus/cbft/rg_broadcast.go | 15 +- consensus/cbft/state/state.go | 36 ++-- consensus/cbft/state/state_test.go | 145 ++++++++-------- consensus/cbft/sync_msg_test.go | 61 ++++--- consensus/cbft/sync_process_test.go | 2 +- consensus/cbft/types/crypto.go | 11 +- consensus/cbft/types/crypto_test.go | 203 ++++++++++++++++++++++- 11 files changed, 434 insertions(+), 174 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 3aac27cbc9..4cdc3dbc1c 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -261,9 +261,6 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCache consensus.BlockC cbft.state.SetHighestLockBlock(block) cbft.state.SetHighestCommitBlock(block) - // init RGMsg broadcast manager - cbft.RGBroadcastManager = NewRGBroadcastManager(cbft) - // init handler and router to process message. // cbft -> handler -> router. cbft.network = network.NewEngineManger(cbft) // init engineManager as handler. @@ -284,9 +281,13 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCache consensus.BlockC coordinatorLimit := cbft.config.Sys.CoordinatorLimit if isGenesis() { cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), cstate.DefaultEpoch, cbft.config.Option.Node.ID(), needGroup, groupValidatorsLimit, coordinatorLimit, cbft.eventMux) + // init RGMsg broadcast manager + cbft.RGBroadcastManager = NewRGBroadcastManager(cbft) cbft.changeView(cstate.DefaultEpoch, cstate.DefaultViewNumber, block, qc, nil) } else { cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), qc.Epoch, cbft.config.Option.Node.ID(), needGroup, groupValidatorsLimit, coordinatorLimit, cbft.eventMux) + // init RGMsg broadcast manager + cbft.RGBroadcastManager = NewRGBroadcastManager(cbft) cbft.changeView(qc.Epoch, qc.ViewNumber, block, qc, nil) } diff --git a/consensus/cbft/cbft_test.go b/consensus/cbft/cbft_test.go index 298b7598e6..7ae31f8899 100644 --- a/consensus/cbft/cbft_test.go +++ b/consensus/cbft/cbft_test.go @@ -80,7 +80,7 @@ func TestBls(t *testing.T) { agency := validator.NewStaticAgency(nodes) cbft := &Cbft{ - validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0), + validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, 0, 0, nil), config: ctypes.Config{ Option: &ctypes.OptionsConfig{ BlsPriKey: owner, @@ -105,7 +105,7 @@ func TestPrepareBlockBls(t *testing.T) { agency := validator.NewStaticAgency([]params.CbftNode{node}) cbft := &Cbft{ - validatorPool: validator.NewValidatorPool(agency, 0, 0, node.Node.ID(),false, 0, 0), + validatorPool: validator.NewValidatorPool(agency, 0, 0, node.Node.ID(), false, 0, 0, nil), config: ctypes.Config{ Option: &ctypes.OptionsConfig{ BlsPriKey: owner, @@ -174,7 +174,7 @@ func TestAgg(t *testing.T) { for i := 0; i < num; i++ { cnode[i] = &Cbft{ - validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(),false, 0, 0), + validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, 0, 0, nil), config: ctypes.Config{ Option: &ctypes.OptionsConfig{ BlsPriKey: sk[i], diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 9622f9a9dd..a176bb71a1 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -888,7 +888,7 @@ func (cbft *Cbft) richRGBlockQuorumCert(rgb *protocols.RGBlockQuorumCert) { mergeVotes := cbft.groupPrepareVotes(rgb.EpochNum(), rgb.BlockIndx(), rgb.GroupID) if len(mergeVotes) > 0 { for _, v := range mergeVotes { - if !rgb.BlockQC.ValidatorSet.GetIndex(v.NodeIndex()) { + if !rgb.BlockQC.HasSign(v.NodeIndex()) { rgb.BlockQC.AddSign(v.Signature, v.NodeIndex()) } } @@ -916,33 +916,6 @@ func (cbft *Cbft) richRGViewChangeQuorumCert(rgb *protocols.RGViewChangeQuorumCe rgb.PrepareQCs.AppendQuorumCert(vc.PrepareQC) } } - - //if !rgb.ViewChangeQC.ExistViewChange(vc.Epoch, vc.ViewNumber, vc.BlockHash) { - // qc := &ctypes.ViewChangeQuorumCert{ - // Epoch: vc.Epoch, - // ViewNumber: vc.ViewNumber, - // BlockHash: vc.BlockHash, - // BlockNumber: vc.BlockNumber, - // ValidatorSet: utils.NewBitArray(uint32(total)), - // } - // if vc.PrepareQC != nil { - // qc.BlockEpoch = vc.PrepareQC.Epoch - // qc.BlockViewNumber = vc.PrepareQC.ViewNumber - // rgb.PrepareQCs.AppendQuorumCert(vc.PrepareQC) - // } - // qc.ValidatorSet.SetIndex(vc.ValidatorIndex, true) - // qc.Signature.SetBytes(vc.Signature.Bytes()) - // - // rgb.ViewChangeQC.AppendQuorumCert(qc) - // rgb.ViewChangeQC.QCs = append(rgb.ViewChangeQC.QCs, qc) - //} else { - // for _, qc := range rgb.ViewChangeQC.QCs { - // if qc.BlockHash == vc.BlockHash && !qc.ValidatorSet.GetIndex(vc.NodeIndex()) { - // qc.AddSign(vc.Signature, vc.NodeIndex()) - // break - // } - // } - //} } } } diff --git a/consensus/cbft/consensus_process_test.go b/consensus/cbft/consensus_process_test.go index 37b924445d..09c16a4e75 100644 --- a/consensus/cbft/consensus_process_test.go +++ b/consensus/cbft/consensus_process_test.go @@ -14,19 +14,23 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbft import ( "fmt" + "github.com/AlayaNetwork/Alaya-Go/common" + ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/validator" "io/ioutil" + "math/big" "os" "testing" "time" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/wal" + "encoding/json" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" "github.com/AlayaNetwork/Alaya-Go/core/types" "github.com/stretchr/testify/assert" @@ -388,3 +392,90 @@ func TestViewChangeBySwitchPoint(t *testing.T) { assert.Equal(t, uint64(2), nodes[1].engine.state.Epoch()) assert.Equal(t, uint64(0), nodes[1].engine.state.ViewNumber()) } + +func unmarshalBitArray(bitArrayStr string) *utils.BitArray { + var ba *utils.BitArray + json.Unmarshal([]byte(bitArrayStr), &ba) + return ba +} + +func TestMergeViewChange(t *testing.T) { + pk, sk, cbftnodes := GenerateCbftNode(4) + nodes := make([]*TestCBFT, 0) + for i := 0; i < 4; i++ { + node := MockNode(pk[i], sk[i], cbftnodes, 10000, 10) + assert.Nil(t, node.Start()) + nodes = append(nodes, node) + } + + testCases := []struct { + epoch uint64 + ViewNumber uint64 + blockNumber uint64 + ValidatorSetStr string + }{ + {1, 0, 1, `"x___________"`}, + {1, 0, 2, `"xxxx________"`}, + {1, 0, 3, `"xx__________"`}, + } + + viewChangeQC := &ctypes.ViewChangeQC{QCs: make([]*ctypes.ViewChangeQuorumCert, 0)} + for _, c := range testCases { + viewChangeQC.QCs = append(viewChangeQC.QCs, &ctypes.ViewChangeQuorumCert{ + Epoch: c.epoch, + ViewNumber: c.ViewNumber, + BlockHash: common.BigToHash(big.NewInt(int64(c.blockNumber))), + BlockNumber: c.blockNumber, + Signature: ctypes.Signature{}, + ValidatorSet: unmarshalBitArray(c.ValidatorSetStr), + }) + } + + // ViewChange already exists + vc := &protocols.ViewChange{ + Epoch: testCases[0].epoch, + ViewNumber: testCases[0].ViewNumber, + BlockHash: common.BigToHash(big.NewInt(int64(testCases[0].blockNumber))), + BlockNumber: testCases[0].blockNumber, + ValidatorIndex: 0, + } + nodes[0].engine.MergeViewChange(viewChangeQC, vc) + assert.Equal(t, 3, len(viewChangeQC.QCs)) + for _, qc := range viewChangeQC.QCs { + if qc.BlockHash == vc.BlockHash { + assert.Equal(t, 1, qc.ValidatorSet.HasLength()) + } + } + // blockhash exists but ValidatorIndex not exists + vc = &protocols.ViewChange{ + Epoch: testCases[1].epoch, + ViewNumber: testCases[1].ViewNumber, + BlockHash: common.BigToHash(big.NewInt(int64(testCases[1].blockNumber))), + BlockNumber: testCases[1].blockNumber, + ValidatorIndex: 5, + } + nodes[0].engine.MergeViewChange(viewChangeQC, vc) + assert.Equal(t, 3, len(viewChangeQC.QCs)) + for _, qc := range viewChangeQC.QCs { + if qc.BlockHash == vc.BlockHash { + assert.Equal(t, 5, qc.ValidatorSet.HasLength()) + } + } + // blockhash not exists + blockNumber := uint64(4) + vc = &protocols.ViewChange{ + Epoch: testCases[0].epoch, + ViewNumber: testCases[0].ViewNumber, + BlockHash: common.BigToHash(big.NewInt(int64(blockNumber))), + BlockNumber: blockNumber, + ValidatorIndex: 1, + PrepareQC: &ctypes.QuorumCert{Epoch: testCases[0].epoch, ViewNumber: testCases[0].ViewNumber}, + } + nodes[0].engine.MergeViewChange(viewChangeQC, vc) + assert.Equal(t, 4, len(viewChangeQC.QCs)) + for _, qc := range viewChangeQC.QCs { + if qc.BlockHash == vc.BlockHash { + assert.Equal(t, 1, qc.ValidatorSet.HasLength()) + } + } +} diff --git a/consensus/cbft/rg_broadcast.go b/consensus/cbft/rg_broadcast.go index 2585916bbf..565b9bd6bc 100644 --- a/consensus/cbft/rg_broadcast.go +++ b/consensus/cbft/rg_broadcast.go @@ -10,6 +10,7 @@ import ( const ( coordinatorWaitTimeout = 200 * time.Millisecond efficientCoordinatorRatio = 15 // TODO + defaultUnitID = 0 ) type awaiting interface { @@ -76,14 +77,14 @@ type RGBroadcastManager struct { // NewBridge creates a new Bridge to update consensus state and consensus msg. func NewRGBroadcastManager(cbft *Cbft) *RGBroadcastManager { - _, unitID, err := cbft.getGroupByValidatorID(cbft.state.Epoch(), cbft.Node().ID()) - if err != nil { - cbft.log.Trace("The current node is not a consensus node, no need to start RGBroadcastManager", "epoch", cbft.state.Epoch(), "nodeID", cbft.Node().ID().String()) - unitID = 0 - } + //_, unitID, err := cbft.getGroupByValidatorID(cbft.state.Epoch(), cbft.Node().ID()) + //if err != nil { + // cbft.log.Trace("The current node is not a consensus node, no need to start RGBroadcastManager", "epoch", cbft.state.Epoch(), "nodeID", cbft.Node().ID().String()) + // unitID = 0 + //} m := &RGBroadcastManager{ cbft: cbft, - delayDuration: time.Duration(unitID) * coordinatorWaitTimeout, + delayDuration: time.Duration(defaultUnitID) * coordinatorWaitTimeout, awaitingRGBlockQuorumCerts: make(map[uint64]*awaitingJob), hadSendRGBlockQuorumCerts: make(map[uint64]*protocols.RGBlockQuorumCert), awaitingRGViewChangeQuorumCerts: make(map[uint64]*awaitingJob), @@ -365,7 +366,7 @@ func (m *RGBroadcastManager) Reset() { _, unitID, err := m.cbft.getGroupByValidatorID(m.cbft.state.Epoch(), m.cbft.Node().ID()) if err != nil { m.cbft.log.Trace("The current node is not a consensus node, no need to start RGBroadcastManager", "epoch", m.cbft.state.Epoch(), "nodeID", m.cbft.Node().ID().String()) - unitID = 0 + unitID = defaultUnitID } m.delayDuration = time.Duration(unitID) * coordinatorWaitTimeout m.awaitingRGBlockQuorumCerts = make(map[uint64]*awaitingJob) diff --git a/consensus/cbft/state/state.go b/consensus/cbft/state/state.go index b8252de457..35957f9749 100644 --- a/consensus/cbft/state/state.go +++ b/consensus/cbft/state/state.go @@ -1152,7 +1152,7 @@ func (grg *QuorumCerts) addRGQuorumCerts(groupID uint32, rgqc *ctypes.QuorumCert } } } - if len(ps) < maxSelectedRGLimit { + if len(ps) < maxSelectedRGLimit || rgqc.HigherSign(findMaxQuorumCert(ps)) { ps = append(ps, rgqc) grg.GroupQuorumCerts[groupID] = ps //grg.GroupQuorumCerts[groupID] = append(grg.GroupQuorumCerts[groupID], rgqc) @@ -1251,7 +1251,7 @@ func (srg *selectedRGBlockQuorumCerts) MergePrepareVote(blockIndex, groupID uint } for _, qc := range rgqcs { - if !qc.ValidatorSet.GetIndex(vote.NodeIndex()) { + if !qc.HasSign(vote.NodeIndex()) { qc.AddSign(vote.Signature, vote.NodeIndex()) } } @@ -1417,6 +1417,19 @@ func newViewChangeQuorumCerts() *ViewChangeQuorumCerts { } } +func findMaxViewChangeQuorumCert(qcs []*ctypes.ViewChangeQuorumCert) *ctypes.ViewChangeQuorumCert { + if len(qcs) > 0 { + m := qcs[0] + for i := 1; i < len(qcs); i++ { + if qcs[i].HigherSign(m) { + m = qcs[i] + } + } + return m + } + return nil +} + func (grg *ViewChangeQuorumCerts) addRGViewChangeQuorumCert(hash common.Hash, rgqc *ctypes.ViewChangeQuorumCert) { if ps, ok := grg.QuorumCerts[hash]; ok { if len(ps) > 0 { @@ -1431,7 +1444,7 @@ func (grg *ViewChangeQuorumCerts) addRGViewChangeQuorumCert(hash common.Hash, rg } } } - if len(ps) < maxSelectedRGLimit { + if len(ps) < maxSelectedRGLimit || rgqc.HigherSign(findMaxViewChangeQuorumCert(ps)) { ps = append(ps, rgqc) grg.QuorumCerts[hash] = ps //grg.QuorumCerts[hash] = append(grg.QuorumCerts[hash], rgqc) @@ -1484,7 +1497,7 @@ func (srg *selectedRGViewChangeQuorumCerts) MergeViewChange(groupID uint32, vc * if qcs, ok := rgqcs[vc.BHash()]; ok { for _, qc := range qcs { - if !qc.ValidatorSet.GetIndex(vc.NodeIndex()) { + if !qc.HasSign(vc.NodeIndex()) { qc.AddSign(vc.Signature, vc.NodeIndex()) } } @@ -1544,19 +1557,6 @@ func (srg *selectedRGViewChangeQuorumCerts) RGViewChangeQuorumCertsLen(groupID u // Returns the QuorumCert with the most signatures in specified group func (srg *selectedRGViewChangeQuorumCerts) FindMaxGroupRGViewChangeQuorumCert(groupID uint32) (*ctypes.ViewChangeQC, *ctypes.PrepareQCs) { - findMaxQuorumCert := func(qcs []*ctypes.ViewChangeQuorumCert) *ctypes.ViewChangeQuorumCert { - if len(qcs) > 0 { - m := qcs[0] - for i := 1; i < len(qcs); i++ { - if qcs[i].HigherSign(m) { - m = qcs[i] - } - } - return m - } - return nil - } - rgqcs := srg.findRGQuorumCerts(groupID) if len(rgqcs) <= 0 { return nil, nil @@ -1565,7 +1565,7 @@ func (srg *selectedRGViewChangeQuorumCerts) FindMaxGroupRGViewChangeQuorumCert(g viewChangeQC := &ctypes.ViewChangeQC{QCs: make([]*ctypes.ViewChangeQuorumCert, 0)} prepareQCs := &ctypes.PrepareQCs{QCs: make([]*ctypes.QuorumCert, 0)} for hash, qcs := range rgqcs { - max := findMaxQuorumCert(qcs) + max := findMaxViewChangeQuorumCert(qcs) viewChangeQC.QCs = append(viewChangeQC.QCs, max) if srg.PrepareQCs != nil && srg.PrepareQCs[hash] != nil { prepareQCs.QCs = append(prepareQCs.QCs, srg.PrepareQCs[hash]) diff --git a/consensus/cbft/state/state_test.go b/consensus/cbft/state/state_test.go index 56c8cd2a98..a8cd34e8d3 100644 --- a/consensus/cbft/state/state_test.go +++ b/consensus/cbft/state/state_test.go @@ -224,7 +224,7 @@ func TestNewViewChanges(t *testing.T) { assert.Equal(t, uint32(9), viewState.ViewChangeByIndex(9).ValidatorIndex) } -func newQuorumCert(blockIndex uint32) *ctypes.QuorumCert { +func newQuorumCert(blockIndex uint32, set *utils.BitArray) *ctypes.QuorumCert { return &ctypes.QuorumCert{ Epoch: 1, ViewNumber: 1, @@ -232,32 +232,46 @@ func newQuorumCert(blockIndex uint32) *ctypes.QuorumCert { BlockNumber: 1, BlockIndex: blockIndex, Signature: ctypes.Signature{}, - ValidatorSet: utils.NewBitArray(32), + ValidatorSet: set, } } +func unmarshalBitArray(bitArrayStr string) *utils.BitArray { + var ba *utils.BitArray + json.Unmarshal([]byte(bitArrayStr), &ba) + return ba +} + +func marshalBitArray(arr *utils.BitArray) string { + if b, err := json.Marshal(arr); err == nil { + return string(b) + } + return "" +} + func TestViewRGBlockQuorumCerts(t *testing.T) { testCases := []struct { - blockIndex uint32 - groupID uint32 - validatorIndex uint32 + blockIndex uint32 + groupID uint32 + validatorIndex uint32 + validatorSetStr string }{ - {0, 1, 11}, - {0, 2, 22}, - {0, 1, 12}, - {1, 3, 33}, - {1, 5, 55}, - {2, 1, 11}, - {2, 2, 22}, - {2, 2, 23}, - {0, 1, 12}, // duplicate data + {0, 1, 11, `"x_x_x_"`}, + {0, 2, 22, `"x_x_x_"`}, + {0, 1, 12, `"x_x_x_"`}, + {1, 3, 33, `"x_x_x_"`}, + {1, 5, 55, `"x_x_x_"`}, + {2, 1, 11, `"x_x_x_"`}, + {2, 2, 22, `"x_x_x_"`}, + {2, 2, 23, `"x_xxx_"`}, + {0, 1, 12, `"x_x_x_"`}, // duplicate data } v := newViewRGBlockQuorumCerts() for _, c := range testCases { v.AddRGBlockQuorumCerts(c.blockIndex, &protocols.RGBlockQuorumCert{ GroupID: c.groupID, - BlockQC: newQuorumCert(c.blockIndex), + BlockQC: newQuorumCert(c.blockIndex, unmarshalBitArray(c.validatorSetStr)), ValidatorIndex: c.validatorIndex, }) } @@ -280,6 +294,7 @@ func TestViewRGBlockQuorumCerts(t *testing.T) { assert.Equal(t, uint32(11), v.FindRGBlockQuorumCerts(0, 1, 11).ValidatorIndex) assert.Equal(t, []uint32{11, 12}, v.RGBlockQuorumCertsIndexes(0, 1)) + assert.Equal(t, `"x_xxx_"`, marshalBitArray(v.FindMaxGroupRGBlockQuorumCert(2, 2).BlockQC.ValidatorSet)) } func TestSelectedRGBlockQuorumCerts(t *testing.T) { @@ -303,12 +318,12 @@ func TestSelectedRGBlockQuorumCerts(t *testing.T) { {1, 1, `"xxx_x_"`}, {1, 1, `"xxxxx_"`}, // contains all - {0, 2, `"x_____"`}, - {0, 2, `"_x____"`}, - {0, 2, `"__x___"`}, - {0, 2, `"___x__"`}, - {0, 2, `"____x_"`}, - {0, 2, `"_____x"`}, // exceed the limit + {0, 2, `"x______"`}, + {0, 2, `"_x_____"`}, + {0, 2, `"__x____"`}, + {0, 2, `"___x___"`}, + {0, 2, `"____x__"`}, + {0, 2, `"_____xx"`}, // exceed the limit,but more sign,accept {1, 2, `"x"`}, {1, 2, `"xxxxx_"`}, // contains all @@ -325,33 +340,19 @@ func TestSelectedRGBlockQuorumCerts(t *testing.T) { {2, 2, `"_x__xx"`}, } - bitArray := func(bitArrayStr string) *utils.BitArray { - var ba *utils.BitArray - json.Unmarshal([]byte(bitArrayStr), &ba) - return ba - - } - - marshalBitArray := func(arr *utils.BitArray) string { - if b, err := json.Marshal(arr); err == nil { - return string(b) - } - return "" - } - s := newSelectedRGBlockQuorumCerts() for _, c := range testCases { s.AddRGQuorumCerts(c.blockIndex, c.groupID, &ctypes.QuorumCert{ BlockIndex: c.blockIndex, - ValidatorSet: bitArray(c.ValidatorSetStr), - }, newQuorumCert(c.blockIndex)) + ValidatorSet: unmarshalBitArray(c.ValidatorSetStr), + }, newQuorumCert(c.blockIndex, unmarshalBitArray(c.ValidatorSetStr))) } //fmt.Println(s.String()) assert.Equal(t, 2, len(s.FindRGQuorumCerts(0, 1))) assert.Equal(t, 1, len(s.FindRGQuorumCerts(1, 1))) - assert.Equal(t, 5, s.RGQuorumCertsLen(0, 2)) + assert.Equal(t, 6, s.RGQuorumCertsLen(0, 2)) assert.Equal(t, 1, s.RGQuorumCertsLen(1, 2)) assert.Equal(t, 5, s.RGQuorumCertsLen(2, 2)) assert.Equal(t, 0, s.RGQuorumCertsLen(0, 3)) @@ -359,7 +360,7 @@ func TestSelectedRGBlockQuorumCerts(t *testing.T) { max, parentQC := s.FindMaxGroupRGQuorumCert(0, 1) assert.Equal(t, uint32(0), max.BlockIndex) - assert.Equal(t, `"xxx_x_"`, marshalBitArray(max.ValidatorSet)) + assert.Equal(t, `"xxxx__"`, marshalBitArray(max.ValidatorSet)) assert.Equal(t, uint32(0), parentQC.BlockIndex) maxs := s.FindMaxRGQuorumCerts(1) @@ -369,11 +370,11 @@ func TestSelectedRGBlockQuorumCerts(t *testing.T) { // test merge vote s.MergePrepareVote(0, 2, &protocols.PrepareVote{ BlockIndex: 0, - ValidatorIndex: 5, + ValidatorIndex: 4, }) - assert.Equal(t, 5, s.RGQuorumCertsLen(0, 2)) + assert.Equal(t, 5, s.RGQuorumCertsLen(0, 2)) // after merge, Removes the contained element, changing length from 6 to 5 max, parentQC = s.FindMaxGroupRGQuorumCert(0, 2) - assert.Equal(t, `"____xx"`, marshalBitArray(max.ValidatorSet)) + assert.Equal(t, `"____xxx"`, marshalBitArray(max.ValidatorSet)) // test merge vote s.MergePrepareVote(2, 2, &protocols.PrepareVote{ BlockIndex: 2, @@ -402,25 +403,30 @@ func TestSelectedRGBlockQuorumCerts(t *testing.T) { func TestViewRGViewChangeQuorumCerts(t *testing.T) { testCases := []struct { - groupID uint32 - validatorIndex uint32 + groupID uint32 + validatorIndex uint32 + validatorSetStr string }{ - {1, 11}, - {2, 22}, - {1, 12}, - {3, 33}, - {5, 55}, - {1, 11}, // duplicate data - {2, 22}, // duplicate data - {2, 23}, - {1, 12}, // duplicate data + {1, 11, `"x_x_x_"`}, + {2, 22, `"x_x_x_"`}, + {1, 12, `"x_x_x_"`}, + {3, 33, `"x_x_x_"`}, + {5, 55, `"x_x_x_"`}, + {1, 11, `"x_x_x_"`}, // duplicate data + {2, 22, `"x_x_x_"`}, // duplicate data + {2, 23, `"x_xxx_"`}, + {1, 12, `"x_x_x_"`}, // duplicate data } v := newViewRGViewChangeQuorumCerts() for _, c := range testCases { v.AddRGViewChangeQuorumCerts(&protocols.RGViewChangeQuorumCert{ - GroupID: c.groupID, - ViewChangeQC: &ctypes.ViewChangeQC{}, + GroupID: c.groupID, + ViewChangeQC: &ctypes.ViewChangeQC{ + QCs: []*ctypes.ViewChangeQuorumCert{ + {ValidatorSet: unmarshalBitArray(c.validatorSetStr)}, + }, + }, ValidatorIndex: c.validatorIndex, }) } @@ -438,6 +444,7 @@ func TestViewRGViewChangeQuorumCerts(t *testing.T) { assert.Equal(t, uint32(12), rg.ValidatorIndex) assert.Equal(t, []uint32{22, 23}, v.RGViewChangeQuorumCertsIndexes(2)) + assert.Equal(t, 4, v.FindMaxRGViewChangeQuorumCert(2).ViewChangeQC.HasLength()) } func TestSelectedRGViewChangeQuorumCerts(t *testing.T) { @@ -473,20 +480,6 @@ func TestSelectedRGViewChangeQuorumCerts(t *testing.T) { {2, 3, `"______xxx_x_"`}, } - bitArray := func(bitArrayStr string) *utils.BitArray { - var ba *utils.BitArray - json.Unmarshal([]byte(bitArrayStr), &ba) - return ba - - } - - marshalBitArray := func(arr *utils.BitArray) string { - if b, err := json.Marshal(arr); err == nil { - return string(b) - } - return "" - } - s := newSelectedRGViewChangeQuorumCerts() for _, c := range testCases { hash := common.BigToHash(big.NewInt(int64(c.blockNumber))) @@ -495,7 +488,7 @@ func TestSelectedRGViewChangeQuorumCerts(t *testing.T) { BlockNumber: uint64(c.blockNumber), BlockHash: hash, Signature: ctypes.Signature{}, - ValidatorSet: bitArray(c.ValidatorSetStr), + ValidatorSet: unmarshalBitArray(c.ValidatorSetStr), }, } prepareQCs := map[common.Hash]*ctypes.QuorumCert{ @@ -517,9 +510,9 @@ func TestSelectedRGViewChangeQuorumCerts(t *testing.T) { assert.Equal(t, 2, len(viewChangeQC.QCs)) for _, qc := range viewChangeQC.QCs { if qc.BlockNumber == uint64(1) { - assert.Equal(t, `"xxx_x_______"`, marshalBitArray(qc.ValidatorSet)) + assert.Equal(t, `"xxxx________"`, marshalBitArray(qc.ValidatorSet)) } else if qc.BlockNumber == uint64(2) { - assert.Equal(t, `"______xxx_x_"`, marshalBitArray(qc.ValidatorSet)) + assert.Equal(t, `"______xxxx__"`, marshalBitArray(qc.ValidatorSet)) } } @@ -530,7 +523,7 @@ func TestSelectedRGViewChangeQuorumCerts(t *testing.T) { maxs := s.FindMaxRGViewChangeQuorumCert() assert.Equal(t, 2, len(maxs)) if maxs[1].QCs[0].BlockNumber == uint64(1) { - assert.Equal(t, `"xxx_x_______"`, marshalBitArray(maxs[1].QCs[0].ValidatorSet)) + assert.Equal(t, `"xxxx________"`, marshalBitArray(maxs[1].QCs[0].ValidatorSet)) } else if maxs[1].QCs[0].BlockNumber == uint64(3) { assert.Equal(t, `"______xxx_x_"`, marshalBitArray(maxs[1].QCs[0].ValidatorSet)) } @@ -546,9 +539,9 @@ func TestSelectedRGViewChangeQuorumCerts(t *testing.T) { assert.Equal(t, 3, len(viewChangeQC.QCs)) for _, qc := range viewChangeQC.QCs { if qc.BlockNumber == uint64(1) { - assert.Equal(t, `"xxx_x_______"`, marshalBitArray(qc.ValidatorSet)) + assert.Equal(t, `"xxxx________"`, marshalBitArray(qc.ValidatorSet)) } else if qc.BlockNumber == uint64(2) { - assert.Equal(t, `"______xxx_x_"`, marshalBitArray(qc.ValidatorSet)) + assert.Equal(t, `"______xxxx__"`, marshalBitArray(qc.ValidatorSet)) } else if qc.BlockNumber == uint64(3) { assert.Equal(t, `"______x_____"`, marshalBitArray(qc.ValidatorSet)) } @@ -562,7 +555,7 @@ func TestSelectedRGViewChangeQuorumCerts(t *testing.T) { ValidatorIndex: 3, }, 12) //fmt.Println(s.String()) - m := s.findRGQuorumCerts(0) + m := s.findRGQuorumCerts(0) // after merge, Removes the contained element, changing length from 2 to 1 v, ok := m[common.BigToHash(big.NewInt(int64(1)))] assert.True(t, true, ok) assert.Equal(t, 1, len(v)) diff --git a/consensus/cbft/sync_msg_test.go b/consensus/cbft/sync_msg_test.go index 1d21688d1d..938a664555 100644 --- a/consensus/cbft/sync_msg_test.go +++ b/consensus/cbft/sync_msg_test.go @@ -27,7 +27,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/common/math" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" - "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" "github.com/AlayaNetwork/Alaya-Go/core/types" ) @@ -354,36 +353,36 @@ func (suit *SyncMsgTestSuite) TestOnGetQCBlockListDifNumber() { } // normal -func (suit *SyncMsgTestSuite) TestOnGetPrepareVote() { - votes := make([]*protocols.PrepareVote, 0) - for _, node := range suit.view.allCbft { - index, err := node.validatorPool.GetIndexByNodeID(suit.epoch, node.config.Option.Node.ID()) - if err != nil { - panic(err.Error()) - } - vote := mockPrepareVote(node.config.Option.BlsPriKey, suit.epoch, suit.oldViewNumber, - 0, index, suit.blockOne.Hash(), suit.blockOne.NumberU64(), nil) - votes = append(votes, vote) - suit.view.firstProposer().state.AddPrepareVote(index, vote) - } - unKnownSet := utils.NewBitArray(uint32(len(suit.view.allCbft))) - for i := uint32(0); i < unKnownSet.Size(); i++ { - unKnownSet.SetIndex(i, true) - } - getPrepareVote := &protocols.GetPrepareVote{ - Epoch: suit.epoch, - ViewNumber: suit.oldViewNumber, - BlockIndex: 0, - UnKnownSet: unKnownSet, - } - cleanCh(suit.msgCh) - suit.view.firstProposer().OnGetPrepareVote("", getPrepareVote) - select { - case <-suit.msgCh: - case <-time.After(time.Millisecond * 10): - suit.T().Fatal("timeout") - } -} +//func (suit *SyncMsgTestSuite) TestOnGetPrepareVote() { +// votes := make([]*protocols.PrepareVote, 0) +// for _, node := range suit.view.allCbft { +// index, err := node.validatorPool.GetIndexByNodeID(suit.epoch, node.config.Option.Node.ID()) +// if err != nil { +// panic(err.Error()) +// } +// vote := mockPrepareVote(node.config.Option.BlsPriKey, suit.epoch, suit.oldViewNumber, +// 0, index, suit.blockOne.Hash(), suit.blockOne.NumberU64(), nil) +// votes = append(votes, vote) +// suit.view.firstProposer().state.AddPrepareVote(index, vote) +// } +// unKnownSet := utils.NewBitArray(uint32(len(suit.view.allCbft))) +// for i := uint32(0); i < unKnownSet.Size(); i++ { +// unKnownSet.SetIndex(i, true) +// } +// getPrepareVote := &protocols.GetPrepareVote{ +// Epoch: suit.epoch, +// ViewNumber: suit.oldViewNumber, +// BlockIndex: 0, +// UnKnownSet: unKnownSet, +// } +// cleanCh(suit.msgCh) +// suit.view.firstProposer().OnGetPrepareVote("", getPrepareVote) +// select { +// case <-suit.msgCh: +// case <-time.After(time.Millisecond * 10): +// suit.T().Fatal("timeout") +// } +//} // normal func (suit *SyncMsgTestSuite) TestOnPrepareVotes() { diff --git a/consensus/cbft/sync_process_test.go b/consensus/cbft/sync_process_test.go index e1a92e3529..8bdb67f2ae 100644 --- a/consensus/cbft/sync_process_test.go +++ b/consensus/cbft/sync_process_test.go @@ -18,6 +18,7 @@ package cbft import ( "fmt" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" "strings" "testing" "time" @@ -33,7 +34,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/network" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" types2 "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" - "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" "github.com/AlayaNetwork/Alaya-Go/core/types" ) diff --git a/consensus/cbft/types/crypto.go b/consensus/cbft/types/crypto.go index ef13a72f5a..5457e2cebe 100644 --- a/consensus/cbft/types/crypto.go +++ b/consensus/cbft/types/crypto.go @@ -92,8 +92,8 @@ func (q QuorumCert) CannibalizeBytes() ([]byte, error) { return crypto.Keccak256(buf), nil } -func (q QuorumCert) Len() int { - if q.ValidatorSet == nil { +func (q *QuorumCert) Len() int { + if q == nil || q.ValidatorSet == nil { return 0 } @@ -148,7 +148,7 @@ func (q *QuorumCert) HigherSign(c *QuorumCert) bool { if !q.EqualState(c) { return false } - return q.ValidatorSet.HasLength() >= c.ValidatorSet.HasLength() + return q.ValidatorSet.HasLength() > c.ValidatorSet.HasLength() } func (q *QuorumCert) HasSign(signIndex uint32) bool { @@ -285,7 +285,7 @@ func (q *ViewChangeQuorumCert) HigherSign(c *ViewChangeQuorumCert) bool { if !q.EqualState(c) { return false } - return q.ValidatorSet.HasLength() >= c.ValidatorSet.HasLength() + return q.ValidatorSet.HasLength() > c.ValidatorSet.HasLength() } func (q *ViewChangeQuorumCert) HasSign(signIndex uint32) bool { @@ -341,6 +341,9 @@ func (v *ViewChangeQC) Len() int { } func (v *ViewChangeQC) HasLength() int { + if v == nil { + return 0 + } return v.ValidatorSet().HasLength() } diff --git a/consensus/cbft/types/crypto_test.go b/consensus/cbft/types/crypto_test.go index ed0803adce..85c2ada4da 100644 --- a/consensus/cbft/types/crypto_test.go +++ b/consensus/cbft/types/crypto_test.go @@ -108,7 +108,7 @@ func Test_ViewChangeQC_MaxBlock(t *testing.T) { assert.Equal(t, uint64(0), epoch) } -func TestValidatorSet(t *testing.T) { +func TestViewChangeQC_ValidatorSet(t *testing.T) { testCases := []struct { ValidatorSetStr string }{ @@ -145,7 +145,179 @@ func TestValidatorSet(t *testing.T) { assert.Equal(t, 11, viewChangeQC.HasLength()) } -func TestQuorumCertAddSign(t *testing.T) { +func TestViewChangeQC_HasSign(t *testing.T) { + b1 := utils.NewBitArray(1000) + b1.SetIndex(uint32(100), true) + b1.SetIndex(uint32(666), true) + b1.SetIndex(uint32(888), true) + b1.SetIndex(uint32(999), true) + + b2 := utils.NewBitArray(1000) + b2.SetIndex(uint32(0), true) + b2.SetIndex(uint32(100), true) + b2.SetIndex(uint32(222), true) + b2.SetIndex(uint32(333), true) + + b3 := utils.NewBitArray(1000) + b3.SetIndex(uint32(666), false) + b3.SetIndex(uint32(777), true) + + testCases := []*utils.BitArray{ + b1, b2, b3, + } + + viewChangeQC := &ViewChangeQC{QCs: make([]*ViewChangeQuorumCert, 0)} + for _, c := range testCases { + qc := &ViewChangeQuorumCert{ + ValidatorSet: c, + } + viewChangeQC.QCs = append(viewChangeQC.QCs, qc) + } + assert.True(t, true, viewChangeQC.HasSign(uint32(0))) + assert.True(t, true, viewChangeQC.HasSign(uint32(666))) + assert.False(t, false, viewChangeQC.HasSign(uint32(555))) +} + +func TestViewChangeQC_HigherSign(t *testing.T) { + b1 := utils.NewBitArray(1000) + b1.SetIndex(uint32(100), true) + b1.SetIndex(uint32(666), true) + b1.SetIndex(uint32(888), true) + b1.SetIndex(uint32(999), true) + + b2 := utils.NewBitArray(1000) + b2.SetIndex(uint32(0), true) + b2.SetIndex(uint32(100), true) + b2.SetIndex(uint32(222), true) + b2.SetIndex(uint32(333), true) + + b3 := utils.NewBitArray(1000) + b3.SetIndex(uint32(666), false) + b3.SetIndex(uint32(777), true) + + testCases := []*utils.BitArray{ + b1, b2, b3, + } + + viewChangeQC1 := &ViewChangeQC{QCs: make([]*ViewChangeQuorumCert, 0)} + viewChangeQC2 := &ViewChangeQC{QCs: make([]*ViewChangeQuorumCert, 0)} + for i, c := range testCases { + qc := &ViewChangeQuorumCert{ + ValidatorSet: c, + } + if i <= len(testCases)-2 { + viewChangeQC1.QCs = append(viewChangeQC1.QCs, qc) + viewChangeQC2.QCs = append(viewChangeQC2.QCs, qc) + } + if i == len(testCases)-1 { + viewChangeQC1.QCs = append(viewChangeQC1.QCs, qc) + } + } + assert.Equal(t, 8, viewChangeQC1.HasLength()) + assert.Equal(t, 7, viewChangeQC2.HasLength()) + assert.True(t, true, viewChangeQC1.HigherSign(viewChangeQC2)) +} + +func TestViewChangeQuorumCert_AddSign(t *testing.T) { + bls.Init(int(bls.BLS12_381)) + message := "test merge sign" + var k int = 500 + msk := make([]bls.SecretKey, k) + mpk := make([]bls.PublicKey, k) + msig := make([]bls.Sign, k) + for i := 0; i < k; i++ { + msk[i].SetByCSPRNG() + mpk[i] = *msk[i].GetPublicKey() + msig[i] = *msk[i].Sign(message) + } + + verifyViewChangeQuorumCert := func(qc *ViewChangeQuorumCert) bool { + var pub bls.PublicKey + for i := uint32(0); i < qc.ValidatorSet.Size(); i++ { + if qc.ValidatorSet.GetIndex(i) { + pub.Add(&mpk[i]) + } + } + var sig bls.Sign + if err := sig.Deserialize(qc.Signature.Bytes()); err != nil { + return false + } + + if sig.Verify(&pub, message) { + return true + } + return false + } + + var sig bls.Sign + vSet := utils.NewBitArray(uint32(k)) + for i := 0; i < len(msig)-2; i++ { + sig.Add(&msig[i]) + vSet.SetIndex(uint32(i), true) + } + + qc := &ViewChangeQuorumCert{ + ValidatorSet: vSet, + } + qc.Signature.SetBytes(sig.Serialize()) + //fmt.Println("qc Signature", qc.Signature.String()) + assert.Equal(t, true, verifyViewChangeQuorumCert(qc)) + + // add sign and verify sign + for i := len(msig) - 2; i < len(msig); i++ { + var s Signature + s.SetBytes(msig[i].Serialize()) + qc.AddSign(s, uint32(i)) + //fmt.Println("qc Signature", qc.Signature.String()) + assert.Equal(t, true, verifyViewChangeQuorumCert(qc)) + } + + // The public key does not match and cannot be verified + var s Signature + s.SetBytes(msig[0].Serialize()) + qc.AddSign(s, uint32(0)) + assert.Equal(t, false, verifyViewChangeQuorumCert(qc)) +} + +func TestQuorumCert_HasSign(t *testing.T) { + b1 := utils.NewBitArray(1000) + b1.SetIndex(uint32(100), true) + b1.SetIndex(uint32(666), true) + b1.SetIndex(uint32(888), true) + b1.SetIndex(uint32(999), true) + + b2 := utils.NewBitArray(1000) + b2.SetIndex(uint32(0), true) + b2.SetIndex(uint32(100), true) + b2.SetIndex(uint32(222), true) + b2.SetIndex(uint32(333), true) + + qc1 := &QuorumCert{ + ValidatorSet: b1, + } + + qc2 := &QuorumCert{ + ValidatorSet: b1.Or(b2), + } + assert.Equal(t, 4, qc1.Len()) + assert.Equal(t, 7, qc2.Len()) + assert.True(t, true, qc1.HigherSign(qc2)) +} + +func TestQuorumCert_HigherSign(t *testing.T) { + b1 := utils.NewBitArray(1000) + b1.SetIndex(uint32(100), true) + b1.SetIndex(uint32(666), true) + b1.SetIndex(uint32(888), true) + b1.SetIndex(uint32(999), true) + qc := &QuorumCert{ + ValidatorSet: b1, + } + assert.True(t, true, qc.HasSign(uint32(666))) + assert.False(t, false, qc.HasSign(uint32(777))) +} + +func TestQuorumCert_AddSign(t *testing.T) { bls.Init(int(bls.BLS12_381)) message := "test merge sign" var k int = 500 @@ -236,3 +408,30 @@ func TestAddSign(t *testing.T) { end := common.Millis(time.Now()) fmt.Println("test", "end", end, "v", qc.ValidatorSet.HasLength()) } + +func TestUnKnownGroups_UnKnownSize(t *testing.T) { + testCases := []struct { + groupID uint32 + ValidatorSetStr string + }{ + {1, `"xx__________"`}, + {2, `"______xxxxxx"`}, + {3, `"xx__x___xx__"`}, + } + + bitArray := func(bitArrayStr string) *utils.BitArray { + var ba *utils.BitArray + json.Unmarshal([]byte(bitArrayStr), &ba) + return ba + + } + + unKnownGroups := &UnKnownGroups{UnKnown: make([]*UnKnownGroup, 0)} + for _, c := range testCases { + unKnownGroups.UnKnown = append(unKnownGroups.UnKnown, &UnKnownGroup{ + GroupID: c.groupID, + UnKnownSet: bitArray(c.ValidatorSetStr), + }) + } + assert.Equal(t, 9, unKnownGroups.UnKnownSize()) +} From 55f29ea541c76141bf230dff2e4594b8246698ba Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 6 Dec 2021 16:32:06 +0800 Subject: [PATCH 093/299] update web3.js for getPrepareQC --- internal/jsre/deps/bindata.go | 2 +- internal/jsre/deps/web3.js | 25568 ++++++++++++++++---------------- internal/web3ext/web3ext.go | 10 +- 3 files changed, 12783 insertions(+), 12797 deletions(-) diff --git a/internal/jsre/deps/bindata.go b/internal/jsre/deps/bindata.go index ac7842b8c8..d204486594 100644 --- a/internal/jsre/deps/bindata.go +++ b/internal/jsre/deps/bindata.go @@ -98,7 +98,7 @@ func bignumberJs() (*asset, error) { return a, nil } -var _web3Js = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\xbd\x5b\x6f\x1c\x49\xb2\x30\xf6\xfc\xf1\x57\xa4\xda\xe7\x53\x77\x8b\xa5\xbe\x90\x94\x46\xd3\x14\x35\x4b\x51\xd4\x88\x7b\x24\x52\xe0\x65\x66\x77\x79\xb8\x42\xb1\x2a\xbb\x3b\x47\xd5\x55\x7d\xaa\xb2\x79\x19\x91\x0f\x36\x6c\xc0\x30\x60\xc0\x2f\xc6\xf7\x60\xc0\x2f\x06\xec\x97\xef\x00\x86\x1f\x0c\x03\x86\xe1\x3f\xe3\xf3\x7d\xe7\x67\x18\x19\x79\xcf\xca\xaa\x6e\x5e\x34\x33\x3b\x4b\x61\x77\xd8\x95\x19\x79\x8b\x8c\x8c\x8c\x8c\x8c\x8c\xc8\xf1\x3f\xcf\x48\x8e\x37\x5a\xc3\x59\x1a\x51\x92\xa5\xad\xf6\x17\xf9\x13\xe5\x2d\x1c\xa4\x01\x35\x52\xb2\x16\x09\x86\xed\x2f\x64\xd8\x7a\x94\x1e\x93\x13\xfe\x0b\xc3\xaf\xb3\x30\x47\xd1\x46\x43\x82\x36\x36\x36\xe8\xe5\x14\x67\x43\x24\x9a\x78\xfc\x58\xfc\x58\x67\x65\x86\x8f\x1f\x47\xed\x1c\xd3\x59\x9e\xa2\xa8\x45\x82\x47\xbd\x36\x4b\x9f\xc9\xb4\x99\x48\x63\xb5\x86\x1b\x29\x3e\x47\xdb\x79\x9e\xe5\xad\xc6\x56\x98\xa6\x19\x45\x43\x92\xc6\x68\x92\xc5\xb3\x04\xa3\x66\x63\x99\x2c\x37\x9a\x8d\xf6\x3a\x1d\xe7\xd9\x39\x0a\x3b\x51\x16\xe3\x8d\xc6\x87\xbd\x37\x47\xef\xb7\x3f\xed\xee\x1d\x7e\x7a\xbb\x77\xb4\xfb\xa6\x11\x84\xd7\xac\xbe\xe9\x06\xeb\xfb\xc6\x17\x7c\x31\xcd\x72\x5a\x0c\xbe\x5c\x5f\xaf\xb3\x31\x1c\xf7\x4e\x3a\x51\x98\x24\xad\x69\x47\x64\x05\x0a\x2b\x39\x1f\x60\xba\x01\x80\xfd\x93\xe3\xfc\x64\x5d\x74\x35\x6b\xa5\x57\x57\x79\xfb\x3a\x98\x06\xba\x60\x1e\x70\xd4\x5d\x0b\x20\xd6\xa2\xcc\xbc\x1e\x66\x79\x8b\xd5\x36\x5b\x04\x5d\x01\xd9\xe8\xad\x93\x97\xb4\x93\xe0\x74\x44\xc7\xeb\x64\x79\xb9\x9d\xb5\x28\xc3\xb9\xea\x81\x6c\x25\xbf\x6e\xb7\xda\xad\x2f\xfd\xc1\xb1\xee\xb8\xa8\x85\xe3\x2a\x10\x5d\x68\x7f\x59\xe2\x09\xb2\x4f\x1b\xc7\x4b\x08\x7d\x59\x42\x08\xa1\x46\x94\xa5\x05\x0d\x53\xda\x18\x20\x9a\xcf\x70\xc0\x53\x49\x3a\x9d\xd1\xa2\x31\x40\xc7\xf0\x2d\xa1\x21\x2f\x0d\x27\xb8\x31\x40\x8d\x4f\xd9\x79\x8a\xf3\x46\xa0\x73\xd8\xa0\x58\x4e\x18\xc7\x39\x2e\x8a\x86\xc8\xb9\x86\xbf\x27\xa2\x6a\x59\x1c\xfe\x8a\xb4\x6c\x46\xe7\xb7\x97\x7d\x32\x8a\x58\xed\x9d\x5e\x52\x5c\xac\xae\xf8\xdb\x93\x40\x0a\xf9\x4b\x08\x5d\x07\xf7\x82\x80\x5b\xf5\x47\x0d\xc7\xc0\xde\x62\x08\xb8\x31\xaa\x7f\xab\x43\x8f\xb2\x94\xe2\x94\xde\x79\xf0\x7f\x93\xf3\xce\x66\xec\x37\x33\xed\xc3\x30\x29\x7e\xb9\xa1\xe7\xb8\xc0\xf9\x99\x6f\xd5\xff\xd6\x27\xad\x98\x9d\xee\xe3\x11\x29\x68\x1e\xfe\x1d\x4c\x5e\x50\x57\x07\x3e\xdf\xbb\x13\xdf\xa7\x79\x98\x16\x43\x2f\xeb\xfb\x5b\xc1\x41\xee\x90\xc2\xcd\x91\x50\x60\x7a\x50\x4f\x52\xf7\x86\x0b\xb7\xe9\x5f\xa4\xd1\xaf\x3c\x01\xe1\x22\x88\xaf\xab\x60\x9a\x93\x49\x98\x5f\x7a\xfb\x91\x65\xc9\xdc\xc9\xdb\x14\x6d\xfd\xed\xa2\xd0\xde\x83\x6b\xab\xa9\x42\xc2\x56\xe5\x36\xfe\x5b\x42\x82\xb7\xf7\x31\x29\xb2\xf3\xf4\x0e\x3d\x0f\xd3\x2c\xbd\x9c\x64\xb3\xe2\x06\x5d\x27\x69\x8c\x2f\x70\x6c\xed\x5d\xf7\x36\xb1\xba\x72\xa3\x3b\x76\xed\xe7\x24\xbd\x0b\xe3\xde\x9c\x01\x26\xb6\xd3\x18\xc7\x0d\x07\x4d\xf8\x8c\x11\xc2\xef\x00\x47\xa7\x24\x8e\x17\xc3\xd1\xed\xea\x3f\x0b\x93\x99\xb7\xfb\x33\x92\xd2\x95\x67\xcf\xeb\xa7\x60\x17\x9f\xbf\x26\xbf\x22\xf2\xef\xb4\xe6\xb6\xc6\x61\x3a\xfa\x35\x49\xe7\x5e\x28\xa7\xa2\x6e\x43\xaa\xaf\xa5\x1a\x2f\x66\x3e\xf2\xdd\x68\x2e\x82\x96\x4e\x96\x96\xae\x83\x2f\xd7\x27\xc1\xca\xaf\x76\xe8\xff\x1d\x9d\x79\x7f\x25\xd9\x71\x38\x4b\xe3\x5b\x93\xca\x9d\x37\xae\x87\x63\xef\xdf\xf6\xb1\xf7\xe1\xd0\xf7\x5b\x3e\x73\x78\x07\x2f\xce\x0b\xbf\x35\x69\xf3\xeb\x6e\xe6\x7a\xaf\x5a\xbd\xb7\xbd\xea\xa6\xf3\x3e\xcc\xb3\xc9\x1d\xa7\x9d\x66\x77\x3c\x6a\xde\x4d\xe0\xfb\x75\xd7\xcd\x6f\x01\x7f\x24\x8d\x49\x8e\x23\xba\xe3\xdd\x33\x6f\xd0\x93\xbb\x4d\x04\x89\xc2\xe9\xe1\xaf\x3a\x19\x7e\x4c\x2e\x76\xda\xc5\xd3\xac\x20\x75\x07\xf5\x69\x78\x19\x9e\x26\xd8\x16\x0a\x7e\x15\xae\x54\x45\x73\xf7\x72\xfc\xba\x1b\x0d\x6c\xca\xf1\xbe\xb1\xf1\xf9\xcb\x9f\x64\xee\x05\x49\x15\x75\x2f\x46\x67\xbf\x02\xfa\x7f\xb3\x58\xbf\x8f\xf3\xe3\xad\xf9\xe4\xd7\xc6\xba\xcb\xf4\x1e\xd0\xbe\x20\xda\xef\xbc\x71\x7d\xed\x99\xdd\xf1\x6c\x69\x75\x72\xdc\xda\x22\x72\xdc\x59\x98\xa3\x21\xda\x90\x86\x0d\xad\x66\xa7\x3b\xcc\xf2\x49\x48\x29\xce\x8b\x66\x7b\x1d\x00\x0e\xb2\x84\xc4\x84\x5e\x1e\x5e\x4e\xb1\x0d\xcb\xda\x67\x50\x4b\xdd\x27\x4f\x96\xd0\x13\x0b\x52\xe8\xdc\x11\x29\x50\x88\xa6\x79\x96\x31\x60\x44\xc7\x21\x45\x39\x9e\xb2\x43\x56\x4a\x0b\x24\xe6\x0e\xb1\x4c\x56\xc3\x0e\x45\x93\x90\x46\x63\x5c\x0c\xd8\xa7\xc8\x36\x7e\x1e\x9f\x98\x1f\x6b\xd6\xd7\x89\x9d\xb9\xea\x7c\x9f\x1c\x3f\x3f\x39\x3e\x09\x50\xa7\xd3\x59\x42\x4f\xba\xa5\xb1\xc9\x1e\x6f\x20\x65\x53\xd3\x6a\x8b\x29\xa6\x63\x52\x74\x3e\xc1\xc2\x78\x2b\x11\xc4\x00\x3b\x1c\x5d\x3b\x2c\x63\x27\xa5\xeb\x06\x30\xdf\xb7\x7d\xd0\x7b\x90\x23\x9a\x5b\x5f\xba\x5e\x5f\x5a\xf2\xf4\xa3\x33\xcd\x33\xca\xb1\xb6\x81\x52\x7c\x6e\xf5\xb5\xf5\xe5\xba\xbd\x5e\x5f\xaa\x03\xd2\x4b\x3e\x8b\x68\xc6\x1a\xf7\xc0\xce\x6b\xb7\x43\x0a\x31\xe7\x1a\x21\x8c\x1c\x25\x52\x84\x65\xcb\xa3\x47\x2c\xb1\x03\xf3\xd6\xea\x0a\x6c\xb7\xfe\xe9\xb8\x75\xdc\x7b\xfa\xed\xc9\x93\xf6\x3f\x9d\xb4\xbf\xeb\xb6\xf9\x38\xed\x83\x43\x65\xb7\xae\x83\x2f\x0d\x93\x14\x1b\x83\x6f\x83\x06\xa7\xb7\xc6\xa0\xbf\x76\x7d\x12\x3c\xfb\x95\xc9\xfb\x75\x96\x25\x73\x68\xfb\x94\x81\x54\x10\x36\xcb\x93\x7f\x39\x95\xc2\xaf\x35\xfd\xf3\xc4\x48\x5e\x35\x3f\xe6\x91\x31\xf4\xec\xb6\x34\xcc\x0a\xdf\x84\x88\x39\xbc\x4b\xc1\x2c\xf5\x86\xe4\x6b\x17\xa9\xa1\x5d\xde\x62\x5d\xd9\xdb\x50\xed\x5f\x19\x6a\x6d\x9a\x7d\xf2\x0f\x0b\x11\xad\xe8\xcf\x7c\x8a\x7d\xfe\x6b\x53\x2c\xdb\xc3\x14\xc9\x52\x3f\xcd\xd2\x31\x46\xb0\xd9\x01\xe1\x76\x7c\x94\xcb\x72\xd5\x0f\x41\x97\xf0\x73\xcd\xf8\x7d\x62\x66\xac\x5a\x5f\x36\xfd\x22\xb1\xb5\xaa\x9f\x2f\xac\x7a\x44\x51\x0f\x95\x43\x27\x6f\x4d\xe6\xac\xf4\x8d\xe8\x9c\x17\x28\x11\x3a\x4b\xbe\x29\xa5\xdb\x65\xea\x48\x9d\x37\x5a\x5b\xfa\x76\xc4\xce\x2a\xe1\xa4\xfe\xa5\x1f\x5c\xb7\x6f\x47\xf8\xa2\x77\xf3\x29\xff\x9b\x45\x28\xbf\xfb\x04\x3a\x7c\x38\x26\x05\x1a\x92\x04\x33\x4a\x9d\x86\x39\x45\xd9\x10\x9d\xe3\xd3\xd5\xce\x4f\x45\x67\x09\x40\xc4\x17\x03\x18\xe6\x18\xa3\x22\x1b\xd2\xf3\x30\xc7\x03\x74\x99\xcd\x50\x14\xa6\x28\xc7\x31\x29\x68\x4e\x4e\x67\x14\x23\x42\x51\x98\xc6\xdd\x2c\x47\x93\x2c\x26\xc3\x4b\xa8\x83\x50\x34\x4b\x63\x9c\x03\xc1\x53\x9c\x4f\x0a\xd6\x0e\xfb\xf8\x7e\xf7\x08\xbd\xc7\x45\x81\x73\xf4\x3d\x4e\x71\x1e\x26\xe8\xe3\xec\x34\x21\x11\x7a\x4f\x22\x9c\x16\x18\x85\x05\x9a\xb2\x94\x62\x8c\x63\x74\x7a\x29\xa8\x08\xa3\xb7\xac\x33\x07\xa2\x33\xe8\x6d\x36\x4b\xe3\x90\x8d\x39\x40\x98\xd0\x31\xce\xd1\x19\xce\x0b\x36\x43\xab\xb2\x2d\x51\x63\x80\xb2\x1c\x6a\x69\x85\x94\x8d\x21\x47\xd9\x94\x15\x6c\xa3\x30\xbd\x44\x49\x48\x75\xd9\x32\x0a\xf4\x48\x63\x44\x52\xa8\x76\x9c\xc9\x95\x4d\x28\x3a\x27\x49\x82\x4e\x31\x9a\x15\x78\x38\x4b\xb8\xe0\x78\x3a\xa3\xe8\xc7\x9d\xc3\x77\x7b\x47\x87\x68\x73\xf7\xcf\xe8\xc7\xcd\xfd\xfd\xcd\xdd\xc3\x3f\xaf\xa3\x73\x42\xc7\xd9\x8c\x22\x26\x51\x42\x5d\x64\x32\x4d\x08\x8e\xd1\x79\x98\xe7\x61\x4a\x2f\x51\x36\x84\x2a\x3e\x6c\xef\x6f\xbd\xdb\xdc\x3d\xdc\x7c\xbd\xf3\x7e\xe7\xf0\xcf\x28\xcb\xd1\xdb\x9d\xc3\xdd\xed\x83\x03\xf4\x76\x6f\x1f\x6d\xa2\x8f\x9b\xfb\x87\x3b\x5b\x47\xef\x37\xf7\xd1\xc7\xa3\xfd\x8f\x7b\x07\xdb\x1d\x84\x0e\x30\xeb\x18\x86\x1a\xe6\x23\x7a\x08\x73\x96\x63\x14\x63\x1a\x92\x44\xce\xff\x9f\xb3\x19\x2a\xc6\xd9\x2c\x89\xd1\x38\x3c\xc3\x28\xc7\x11\x26\x67\x38\x46\x21\x8a\xb2\xe9\xe5\xc2\x13\x09\x95\x85\x49\x96\x8e\x60\xd8\x8a\xca\x10\xda\x19\xa2\x34\xa3\x01\x2a\x30\x46\x2f\xc7\x94\x4e\x07\xdd\xee\xf9\xf9\x79\x67\x94\xce\x3a\x59\x3e\xea\x26\xbc\x82\xa2\xfb\xaa\xb3\xf4\xa4\x2b\x99\xed\x1f\x80\x6c\xa3\x2c\xc6\x79\xe7\x27\x60\x91\x7f\x08\x67\x74\x9c\xe5\xe8\x43\x98\xe3\xcf\xe8\x1f\x33\x8a\xcf\x49\xf4\x33\x7a\x39\x61\xdf\x7f\xc0\x74\x1c\xe3\xb3\x4e\x94\x4d\x5e\x01\x70\x1c\x52\x8c\x56\x7a\xfd\x67\xc0\xf0\xe6\x6f\x05\x35\x02\xac\x51\x46\xc8\x63\xbe\xbd\x43\x48\x0a\x06\x30\xdb\x05\x7d\x90\x3b\x29\xb5\x01\x49\x4a\x7d\x70\x47\x25\xc0\x59\x05\xe4\x9b\xcb\x34\x9c\x90\x48\xb2\x71\xa3\x44\xcc\x73\x80\x47\xf9\x4a\x1e\xd0\x9c\xa4\x23\xbb\x4c\x01\x69\x3e\xe8\x7d\x1c\x3a\x63\xcc\x71\xe8\x1d\xe3\x51\x19\x74\x56\x05\xeb\xe9\xb6\xea\x2f\x00\x93\x42\x0c\xd0\xe2\xcc\x85\x51\x45\x00\x3b\xac\xe0\xd3\x82\x4d\x9b\xf9\x1d\x55\x05\x6c\x23\x1c\xf8\xea\x4a\x9d\x1e\x51\x05\xf4\x66\x9e\x87\x97\x1c\x9c\x33\x71\x47\x14\xd8\x62\xf4\x69\x48\x00\x62\x25\x71\x0e\x11\x23\x9a\x21\x9c\x32\x1a\xee\xc6\x98\xfd\x51\xad\x30\x66\x1c\x72\x36\xc9\xb8\x92\x90\x6b\xed\x8d\x99\xd7\x6d\x8e\x98\x81\x15\xf6\xce\x0c\x49\x68\x03\x6a\x28\xac\x2e\x02\xef\x9f\x60\x3a\xce\x62\x4f\xb7\xb8\x72\x3d\xcb\x27\x88\x4b\x2e\x99\x35\x23\x4b\x88\xaf\x41\x51\xfc\x93\x98\x19\x91\x85\xfe\x00\xbd\x47\x5f\x38\xf1\x5c\x2b\xb1\xfc\x0f\x1c\xf3\x05\xfa\x62\x56\x76\x0d\x59\xf0\x62\xa1\x40\x5f\xe0\x75\xc3\x35\x12\x9f\x84\xf1\x06\x2e\x11\x31\x32\x84\xbe\xb0\x9d\x88\xb1\x7b\x40\x88\x85\x0c\x63\xa7\x36\xbb\x54\xc2\x91\x44\x11\xc3\x66\x61\x8b\x77\x06\xd6\x3a\x43\x92\x50\x9c\xb7\x8c\xb2\x6d\x43\x07\x21\xa8\x88\x0a\xa1\x40\x12\x01\xe8\x14\xda\xc7\xbd\x93\x75\xce\x3f\xc9\x10\xb5\x1e\x99\x8d\x98\x75\xf0\x67\x1a\xfc\x41\x47\x93\xa4\x67\x61\x42\x62\x4d\x03\xac\xc6\x47\x03\xd4\x44\xcb\xc8\xac\x7c\xc9\x94\x35\xcc\x9a\x6d\x0a\xac\xa0\x34\x34\x4d\x42\x92\x72\xfa\x72\xa6\x91\x03\x7c\x14\x39\xd5\xb3\x28\xd2\xf7\x4e\x7f\xc2\x11\xbd\x76\x2a\x94\x93\xac\xcb\xf1\x6a\x63\x07\xae\x7a\xea\x8c\x6e\x94\x66\x2e\xe0\xe5\x1d\x81\x0b\x26\xcd\x28\x56\xb4\x8e\x19\xf0\x49\x80\x8e\x01\xfc\xa4\xbd\x18\x6a\x12\x52\x80\x04\xc4\x17\x5f\x35\x76\x0a\x13\x0d\xc0\x02\x38\x76\x7c\xe9\x53\x5d\xa0\x0a\x31\xa5\x66\x17\xc2\x4d\x51\x5e\xfa\x02\x3b\x45\x15\x7d\x17\x92\xc0\x47\x98\x9a\x2b\xb0\x10\x9c\x43\x90\x2c\x2b\x26\xfa\xc6\x4a\x58\x35\x74\x26\xe1\xb4\x55\xc5\x63\x41\x2b\xe7\x59\x23\x16\xef\xe4\x35\xb7\x78\x4f\x8f\xa1\xc8\x09\x67\xcf\xf2\x4b\xad\x22\xa3\x3f\x62\x9f\xda\x1b\x0e\x0b\x4c\x4b\x9d\xca\x71\x3c\x8b\xb0\xd1\xaf\x30\x8a\x02\x34\xa7\x73\x80\x1d\x1a\x52\x12\x7d\x0c\x73\xfa\x1e\x1e\x15\x39\x35\x77\xdc\xfc\x96\xa7\x9f\xb2\xae\x9c\x31\x25\x1c\x1f\x94\xab\xfc\x10\xd2\x71\x67\x98\x64\x59\xde\x6a\x95\x5a\x5c\x46\xab\xfd\x36\xea\xa2\xd5\x95\x36\x7a\x82\x56\x57\xc4\xa0\x0d\xf4\x85\x51\x84\x96\x51\x4b\x6d\x3a\x16\xd6\x2b\x50\x88\xbe\x33\xf6\x2e\x84\x56\x57\xd0\xc0\x4a\xa8\xe8\xac\x44\x7d\x80\x7a\x26\xf6\x73\x5c\xcc\x12\x2a\xa9\x87\xcf\xe0\x87\x59\x42\xc9\x8f\x84\x8e\xf9\x9c\x48\x0a\xb4\xfa\x16\x28\x3a\x0a\xec\x19\x94\x95\xcb\xe7\x5a\x50\xbf\x7d\xe2\xf3\x93\xbe\xd3\xaa\x6f\x0d\x2c\xd8\x03\x63\x8d\xa8\xe1\x35\x1a\xeb\x7a\xe1\xe0\x64\x28\x46\x2c\x3a\x2b\x76\x85\x2c\xdf\x0e\xa3\x71\xcb\x65\x4c\xc4\xa4\x2d\xc6\xf5\x2b\xe7\x4b\xcf\xd5\x49\xdb\x2c\xc4\x11\x02\x5d\x59\x2e\x6b\x3b\x5b\x76\xf7\xe5\x3a\x32\x88\x50\xad\x5d\x46\xc5\x38\x19\x0a\x10\x77\x8e\xa0\x03\xe5\x2e\x49\x3c\xc1\x87\x3b\x59\x66\x13\xf6\x52\x5c\xde\x40\x58\xbc\xc8\x43\x5d\xb4\xa2\x41\xaf\x11\x4e\x0a\xec\x0c\xaf\xdb\x45\x71\x96\x36\x29\x0a\xe3\x18\x89\x52\x34\xb3\xab\xec\x20\x42\x9b\x05\x0a\x93\x1c\x87\xf1\x25\x8a\xb2\x59\x4a\x71\x5c\x81\xa5\xaf\x34\xce\x6b\xbd\x08\xbb\x5d\x74\xb8\xf7\x66\x6f\x80\x86\x64\x34\xcb\x31\x62\x07\xb6\x14\x17\xec\x04\xc8\x4e\x69\x97\x85\xcd\xac\x7e\x09\x22\xf9\xed\x4c\xb2\x3d\x19\xd8\x44\xa0\xc4\xca\xad\x96\x79\xcd\x0a\x77\xf9\xba\x18\x52\x80\x32\x6b\x5d\x77\x9f\xa0\x9f\x8a\x31\x49\x29\x9a\x84\x17\x51\x36\x99\x26\xf8\x82\xd0\xcb\x01\xea\x7f\xc3\x36\xd8\x12\x48\x8c\xa7\x74\x3c\x40\xcf\xe0\x34\xe8\xe5\x01\xc8\xdc\x1a\x49\x3a\xfa\x90\xc5\x78\xe3\x8b\x40\xd4\xa0\x1f\x70\xce\x3e\x58\x09\x32\x3a\xc6\xf9\x60\xf5\xda\x60\xa0\x13\x06\xdb\x9a\x7b\x86\xf8\xce\xac\xbb\x23\xab\x76\xcb\x71\xa6\x5d\x59\x4c\xf4\xc3\x4a\x83\x2e\xb5\xdb\x4a\x26\x6d\xb1\x0e\xa1\x47\x1b\x1b\xc8\x03\x66\x6f\x91\x9c\xd6\x77\xc3\x09\x76\x37\x47\x9d\x63\x0a\xbf\x76\x39\xcf\x6e\x58\xbf\xc1\xea\x4a\xdd\xed\x55\x32\x69\xde\xf7\x0d\xb7\xef\x02\x5b\xe8\x3b\x49\x11\xc7\xbd\x13\x34\x40\xcd\x66\xdb\xd8\x51\xd9\x92\xab\x9b\x05\xa3\x75\xdf\xa2\x9b\xe6\xf8\x8c\x64\xb3\x42\x8d\x65\x6e\x5f\x56\xd0\x40\x6f\xa5\xf2\xdf\x30\xcb\x11\xbc\x7b\x26\x68\x03\xf5\xd6\x11\x41\x2f\x65\xa7\xe5\xe3\x66\x44\x96\x97\xdd\x1e\x08\x6e\x14\x85\x49\x34\x4b\x42\x8a\x25\x03\x65\xa2\xa3\xe8\x18\x22\x14\x4f\x4a\x85\xe4\x7c\x57\xf5\xb3\x5d\x6e\x87\xfd\x73\x46\xbb\xbc\x81\x96\x5b\x12\xb7\x04\x3d\x45\xfd\x13\x76\xce\x41\x57\x57\xa8\xb7\x5e\xaa\xe0\xba\x94\x02\xdb\x41\x65\x57\x38\x21\xdc\xba\x27\xac\x17\xc7\x37\xeb\x4f\xcd\x56\x9b\x09\xb6\x87\x08\x7a\x52\x45\xc8\xcb\x6e\xaf\x98\xf0\x56\xb5\x2d\x5f\xfb\x76\x16\x46\x01\x09\x4e\x17\xa7\x68\x41\x1c\x4f\xfb\x68\xe0\x24\x19\xcd\x29\xe2\x8a\x38\x71\x45\xe8\x25\x6b\x65\x1d\x45\x65\x8a\x02\xa7\x02\x71\x4c\x18\x77\x0d\x13\xc5\x70\x05\x66\x4a\x9c\xfe\x16\x74\x34\x6f\xa7\xd6\x0b\xae\x82\xaf\x1f\x33\xd9\xb7\x7f\x22\xd9\x3b\x5a\x46\xa5\x1e\x57\xe2\xfa\x0e\x54\x77\x0f\xfd\x36\xfb\xbc\x70\x97\xaf\x4b\xd2\xbf\xdc\x34\x51\xe9\xec\x2f\x5a\xe2\xfb\x69\xf5\xe1\x56\xa8\x96\xc4\x45\x53\x56\xa3\x00\xe0\x90\x0b\x2b\x00\x64\xba\xba\xa4\x52\x27\x5c\xbf\x6a\xa0\xfa\x58\x6b\x34\xec\xd9\xef\xa1\x7e\xdf\x91\xdf\x28\xa6\x8f\xfc\x1c\x1a\x14\x30\xb5\x67\x7e\x81\x96\xda\x33\x7f\x05\x42\xaa\x8e\xfc\xd5\x08\x11\x25\x40\x60\x84\xd6\x34\x62\xea\x0f\xfc\xe6\x10\x7d\x87\x1d\x0b\x37\x37\x3f\xef\xcb\x52\x9c\x50\x4d\x78\x4e\xa7\x85\xf7\x50\xe5\x9c\xde\xee\x59\x41\xc0\x87\xdc\x82\x91\xc9\x25\xa4\x4f\xb9\xd6\x31\x57\xd6\xa8\x15\x06\x75\xa2\xa5\x1e\xd5\xdc\x63\xa3\x89\x51\xce\x61\x59\x99\xfb\x1e\xe9\x22\xda\x05\x75\xb0\xb0\x84\x86\x3e\x17\x1a\x44\xd7\x2a\x85\x86\x6e\x17\x15\xb3\x09\xbf\x70\xf1\x08\x0b\x38\xc1\x13\x9c\x52\x05\x2f\xaa\x3b\x26\x27\x8c\xf1\xa9\x2f\xd8\x60\x7d\x0c\x48\x36\x6f\x23\x82\xa7\x7a\x50\xd0\xed\xa2\x1c\x4f\xb2\x33\xb8\x95\x42\xd1\x2c\xcf\x71\x4a\x91\x04\x2f\x32\x48\x16\xdd\x24\x05\xf4\xdc\xd3\xdb\xe2\x26\x0a\x1c\x89\xcc\x85\x15\x39\xd6\xc8\xd0\xd3\x52\xfd\x0b\x53\xda\x81\xb3\x0e\x2b\xb4\xf4\xde\x53\x86\xe4\x71\x1e\x2a\xa3\x8e\x06\x59\x92\x15\xdb\xa4\x4c\x9d\xb7\xad\x8e\xe6\xbd\x05\xa1\x40\x5c\x1a\x98\x37\xe7\xd0\xfb\x16\x37\x3f\x74\x6f\xd4\xc5\x2d\x57\xab\x1d\x78\xb3\x5f\x67\x59\x52\x95\xc7\x04\xa9\x8a\xac\xa3\x9a\x3c\xf3\xae\xaa\xb2\xd9\xba\x4c\xce\x85\xab\x72\xf7\x71\x58\xd9\xe3\x23\x9e\xb9\xc4\x08\xa2\x7c\x1d\x0f\xa8\x53\x57\xf0\xd2\x0e\x72\xb0\x16\x34\xf8\x55\x5e\x63\xf0\x0c\x7e\xb2\xbe\x35\x06\xcf\xd9\x6f\xf3\x76\xad\x31\x78\x11\xf8\xae\xee\x49\x4a\x1b\x83\x7e\x8f\xfd\xcc\x71\x98\x34\x06\xfd\x15\xf6\x9b\x5f\xb2\x35\x06\xfd\x55\xf6\x35\xe3\x50\xd0\xc0\x4c\x80\x3d\xbf\x3e\x09\x5e\xfc\x92\x66\x2e\x73\x6e\x15\x6f\x67\x1c\x62\x56\x72\x13\x1b\x11\xbb\x9c\x6b\x2a\x62\xe6\xde\xd0\x62\xc4\x5f\xb4\xc6\x70\xc4\xee\xc9\x22\x75\xdd\xc1\x8c\xa4\xc2\x76\x64\xa1\x46\x8d\x1b\x4e\xef\x74\x49\xb6\x93\xcf\xf0\x02\x16\x29\xce\xb0\xe7\x1b\xa6\x7c\xfb\x60\x98\xf2\x60\x98\xf2\xf7\x62\x98\xa2\x17\xc2\x7d\x59\xa7\xbc\x26\xa3\xdd\xd9\xe4\x14\x58\xa1\xe2\xce\xa7\x64\x94\x42\x62\xe7\x27\xc5\xc9\x67\x94\x24\xb6\xb9\x44\xa7\x0b\x69\xfc\xbf\x12\x2c\xf2\x82\x44\x59\x3a\x24\x25\xdb\x0e\x79\x32\x33\x76\x05\x38\xbb\x48\xb8\x02\x8f\xce\x09\xfd\xc4\xf6\x44\x6f\xad\x46\xbe\xe8\xa8\x3a\x98\x71\x0e\x5f\x20\xd8\x25\x10\x58\xed\xb3\xd3\x19\xe3\x76\xda\x54\x32\xe4\x3e\x17\x87\x88\x30\x69\xf5\x09\x9b\x19\x0e\x49\x0a\x94\xe2\x51\x48\xc9\x19\x0e\x24\xff\x82\xdb\x03\x7a\x9e\x35\x0b\xc4\xf5\xbe\x5c\xc6\x85\x52\x8c\x22\x54\xc9\x61\x92\x85\x94\xa4\x23\x34\xcd\x48\x4a\x03\x7e\x27\xc6\x16\x4b\x9c\x9d\xa7\xce\x49\xd0\x56\x10\x95\x0f\x7d\x57\x7c\x6e\xae\xd4\x2c\x5d\xcb\xb1\xb0\x05\x98\x62\x1c\xc3\xd9\xfb\x54\x53\x46\xec\xb7\x88\x00\x54\x5f\x2b\x63\x0f\xbb\x5d\x8b\x6d\x43\xfd\x92\x77\xab\x76\x3b\x7c\x06\x5b\x51\x67\xfb\xf0\xdd\xa7\xd7\x3b\xdf\xef\x1e\x7d\x78\xbd\xbd\xff\x69\x7f\xef\x68\xf7\xcd\xce\xee\xf7\x9f\x3e\xec\xbd\xd9\x36\x4e\x7e\x4a\xad\x0a\x33\xd5\x99\x86\xf1\x7b\x3c\xa4\x2d\xfe\x45\xb3\xc3\xf3\xac\xd8\x52\x58\x14\x6d\x76\x68\x26\x84\xac\xfe\xf3\x76\x80\x9e\xaf\xd9\x6a\x7e\x73\x8f\x85\xe1\xb4\x78\x23\xf6\x2d\xbc\x3d\xf1\xf2\xc8\x5c\x81\xf3\xd7\xea\x44\x6d\x1f\xb5\x6f\x8a\xc3\xb2\xac\x62\x61\xd1\x83\x10\x9a\xbd\xc3\x17\x72\xdc\xc5\xec\xb4\xa0\x79\x6b\xc5\xc0\x5f\xe2\xdc\xef\xf2\xe2\xf2\xb2\x64\x19\x3d\x5f\x6d\xa3\xae\x89\x22\x17\xdd\xfb\x64\x34\xa6\xa2\x58\x80\x12\xf4\xe4\x2b\xe3\x53\xec\xdb\xf7\x8a\xd6\x4a\x49\xf0\xce\xd8\x95\x87\x3a\x1b\xad\x4a\x33\xf9\xab\xcd\x80\xa3\x30\x16\x5a\xd8\x0e\x5f\xf3\xcb\x68\xfe\x04\xcd\xe3\x74\x5c\xfe\xaf\x5e\x11\x07\x32\xff\xae\x73\xa7\x2c\xfc\x16\x9f\xb5\x61\x9e\x4d\x8e\xe8\xf0\xc5\xc3\xc4\x79\x26\x4e\x3c\x36\xa9\x62\x64\xe2\x29\x8a\x9c\x34\xf6\x8d\xc3\xf4\xe6\x8c\xcc\x7d\x77\x52\x3d\x67\xcd\xde\xdd\xfe\x35\xd1\xb2\xa8\x1e\x7d\x87\x50\xb3\xdf\x44\x03\xd4\xec\x35\xef\xce\xa3\xe6\x61\x92\x9d\x73\x59\xa9\x1f\x18\x5c\x81\x98\x38\x3d\x99\x25\x94\x70\x51\xf4\xf4\x12\xad\xfc\x75\xc2\x84\x7a\x65\x48\x15\xb2\x9a\x29\x1e\xe1\xbc\x66\x2b\xd9\x17\xb5\xce\xdb\xbf\x6f\x3a\x23\xc2\xa0\xb5\x62\x46\x04\x9a\x1c\xea\x63\x58\x53\x2d\xaa\xcd\x95\x4c\x70\xe1\x64\xad\xb4\x3b\xd3\xec\xbc\xd5\x5f\x79\xd1\x6e\xdb\x28\xdd\x1a\xe3\xe8\x33\x22\x43\x0b\xa7\x86\x58\xe4\x20\xa2\x20\xa3\x14\xc7\x3b\xc5\xae\xce\x2e\xa9\xaf\x55\x1d\x63\x7c\x21\x7a\x6c\x23\x43\x12\x2d\x1c\x15\xa1\x6d\x6a\x4b\x62\x70\x8f\x7c\x4e\x98\xf0\x1e\x26\x85\x36\x5d\x75\x5b\x9f\x8b\x2f\x1f\x86\x24\xbb\xe9\x05\xa8\xdf\x0e\x50\xff\xb9\x21\x8f\xac\xb4\xad\xdc\x36\xdc\xf8\x34\xfb\x4d\x2f\x15\xe6\x8c\x7d\x3c\x0d\x13\xe8\x14\xe2\x0a\x07\x7d\x4d\xc2\x45\xcd\x32\x11\x71\xf5\x83\x2b\x04\x5a\xe4\x51\x32\x86\x64\x38\xd3\x82\x61\x4d\xbb\x4a\x38\x84\x65\x41\x46\x88\x4b\xf7\x0e\xbd\xa9\x2e\x58\xf8\xb3\x2c\x23\x19\x30\x9f\xc7\x0d\xde\x1b\x43\x03\xda\x6a\xa3\xab\x2b\xd4\xe8\x35\x84\x66\xb9\xdb\x45\x91\xa2\x22\x26\x3c\xcb\x89\x54\xad\x73\x20\x42\xf9\x44\x2b\x49\xbb\x2c\x64\xcb\x2b\x76\x67\x9e\xc5\xdc\x7a\x14\x97\x9e\xf9\xe5\x53\x3a\x21\xe9\xcc\x5d\x05\xcd\xe1\x1d\xff\x35\xa1\x6e\x59\x79\x5f\x59\xd4\x2e\xd0\xa1\x5b\x50\xd0\xac\x9e\x84\x8e\x6a\x69\xc8\x47\x3d\xf8\x46\xe4\x23\x9a\x2f\x13\xce\xd1\x7d\x50\xce\xd7\x41\x99\x60\xf9\x55\x28\x2b\xf1\xee\xb9\x28\x03\x8c\x19\x22\xb1\x8d\x22\xd1\x5c\x19\x45\x25\x66\xee\x33\x3b\x76\x16\xa3\x80\xe9\xc4\xe4\x8c\xc4\x38\x7e\x7d\x59\xc3\xc3\x6f\x43\x4d\x73\x70\x73\x74\xdf\xc8\x99\x55\x62\xe7\xe8\xc6\xe8\x39\xba\x0b\x7e\xca\x77\xb7\xbc\x6a\x85\xa2\x2a\x89\x4b\xbf\x9a\x5d\x18\x2f\x72\x67\xb3\xe7\xa2\x12\x47\xa2\xe9\x32\x8a\x4a\xf2\x99\x0f\x43\x9e\xe5\x05\xfb\xd5\x1d\x05\xb6\x7e\x13\x7d\xc7\xb7\x66\xe1\x1e\xe1\x66\xd8\xac\x3c\x39\x1a\x8f\x33\x6b\xf6\xbe\x04\x0f\x35\xe2\x98\x04\x51\x73\xb6\x29\x89\x1e\x69\x38\xc1\xfc\x95\x07\xfb\xe5\x88\x60\x02\x86\xd5\xa9\x6a\xf0\x60\xbe\x74\x08\x85\x36\x02\x64\xaa\xd8\xc1\x26\x8f\xbf\xb3\x45\x1b\xa8\xea\xb9\xe6\x93\x76\xd7\x38\xd2\x14\xe4\x67\xc1\x13\x0b\xb8\xdb\x12\xe5\x8f\xfb\x27\xb6\x28\xdc\xec\x5d\x30\x91\xb9\x3c\xb9\x9d\x22\x21\x11\x66\x92\xc9\x0a\x7a\x02\xd5\xdd\x90\xce\xe7\xcc\x8c\x79\x0a\xbf\xb7\x09\xba\x29\xfa\x2b\x55\x01\xa5\x4d\x46\x1d\x11\x1d\x3e\xc0\x11\x27\xae\xce\x5c\xcc\x3d\x5f\x6b\x8b\x3d\x9c\x66\x02\xbe\x8d\x9e\xc8\x53\xa5\x6f\x06\x9c\x8a\xb8\x74\xf8\x7c\x2d\x40\xca\x72\xea\x06\x53\x50\x73\x2a\xe7\xc3\xf7\x1c\xcb\xef\x15\xfb\x61\x11\x11\x52\x87\x7f\xcf\x71\xfe\x17\xc4\xbc\xd4\xea\x80\x76\x60\x31\xfc\xdf\x6c\x02\xb4\x8f\x92\xaa\x19\xd8\xd4\x5e\x4c\x2a\xa6\xa0\x92\xb7\x57\xa0\x5c\x55\x58\xc6\xb6\xcf\x8b\xc9\x0d\xa4\x29\x0b\x77\x8d\xde\x45\x03\x2d\x23\x71\xc6\x01\xb4\xf3\xdf\xca\x18\x61\xad\x17\x20\x33\x49\x94\xef\xca\xcb\x68\xb4\x71\x97\x4a\x9c\x29\x7c\x8d\xa3\xf1\xea\x8a\xbc\xff\x6f\x24\x21\x6d\x04\x12\x17\x6d\xff\xf5\xe0\x17\x69\xa8\x62\x9c\x71\x07\xce\x77\xe0\xc2\x00\xa7\x18\x94\x52\x4a\x70\x26\x63\x19\x54\x65\x94\x4a\xf1\x49\x1c\x94\x93\xca\xfd\xc8\xb2\x64\xe0\x26\x94\xa0\x98\xe4\x33\x70\x13\x4c\x28\x25\x0e\x0e\xdc\x84\x32\xd4\x51\x09\xec\xc8\x0b\x67\x36\xaa\x53\x3c\xf5\x95\x01\x8f\xfc\x90\xe6\x60\x75\x8a\x07\xce\xc4\xb6\x91\x54\x86\xf4\x4d\x4f\x39\xa7\x5c\xce\x9e\x20\x33\xad\x0c\x2b\x88\x71\xe0\x5d\xef\xd7\xf2\x12\xda\xbe\xca\x6a\x0c\xfa\xdf\x06\x8d\x8a\x9b\xa8\xc6\x60\xa5\x67\x64\xc2\x7f\x1b\x83\x15\x30\xc0\x80\xd5\xda\x18\xf4\xfb\x41\xc3\xbc\x65\x6b\x0c\xec\xcf\xeb\x93\xa0\xdf\xfb\x95\x9d\x8d\xb0\xf3\x5c\xbd\x77\x1c\x92\xd2\x2a\xe7\x38\xe2\x4a\x8d\xa4\x94\xfb\x0d\x61\x3f\xd6\xd4\xaf\x13\x9d\xb8\x6a\xfc\x76\xdc\x8a\x90\x94\x72\xa7\x22\x24\xa5\xcf\xd7\x14\xd8\x0b\x5d\xd1\xca\xb3\xe7\x15\x75\x31\xf8\x39\x4e\x76\xdc\xf3\xea\x57\xf4\x13\x05\xe0\xae\x45\xc5\x4e\x4a\x6f\x68\x44\x62\x95\xa8\xb1\x1d\x81\xe6\x6a\x4a\xde\xca\x52\x84\xa4\x54\xca\xaf\xdf\xdd\xca\xd9\x08\xef\xd5\x7c\x8b\x8e\xfe\x42\xf1\xd5\x1e\x4c\x3a\x1e\x4c\x3a\xfe\x76\x4d\x3a\x90\xb6\xe9\xe0\xf2\xdb\x3d\x99\x73\x2c\x60\xa5\xe1\xb2\x7a\x6e\x85\x91\x81\x6d\xbd\xf6\x29\xd1\xf1\x88\xcd\xe7\x63\x9c\xaa\x87\x0e\x01\x37\x63\x67\xa7\x02\xe5\x5a\x40\x8a\xbb\x5d\xaf\x99\x87\xa3\x93\x77\x9f\xd6\x01\x27\x95\x42\x2d\xff\x7b\x75\x85\x9a\x4d\x83\xcf\x66\xf2\x29\x09\xff\xb1\x8e\xba\x5d\xf9\x18\x82\xa4\xa2\xf5\x85\x7d\x51\x8c\x30\x35\xad\x97\xc1\x16\xbe\x59\xc8\x27\x8a\xc0\x4b\x58\x25\x96\xe1\xbe\x3e\x74\x70\xbb\x5d\x5b\xb4\x97\xba\x2f\x5d\xab\x59\x19\x6a\xc9\x3e\xb6\x2d\xdb\x7c\x40\x8f\x69\x9b\xef\x36\x52\x6b\x55\x07\x0f\x16\xac\xb3\x90\x79\x27\xba\x30\x32\xa2\x1c\x33\x62\x92\xeb\xc1\x76\x18\xc2\xc9\x3d\x26\xc3\x21\x06\xdb\x6a\x8e\x72\xe7\xb0\x74\xae\x5e\xb1\x98\x67\x24\x89\x12\x31\x4b\xd2\x0c\x3b\xf5\x9e\x8c\xec\xf3\x14\xdb\x0e\x7d\xfd\x08\xa7\x9c\xc3\xa8\x5e\x54\xa3\xf2\xdc\xff\xde\xd3\xa6\xbb\xca\xab\x46\x4d\x90\x8a\x54\x6f\x82\xd1\x6c\x72\x4a\xd2\xb2\xef\x15\x9a\x8d\x30\xe3\xee\xac\x06\x3c\xea\xf0\x45\x15\x4e\xa7\x38\x85\xb5\x14\xa6\xfc\x39\x87\x83\x5d\x51\xdb\xbc\xcb\x21\xc1\x98\xc6\x24\x62\xec\x49\xf6\x6a\x7e\x61\x71\xab\x9b\x0d\x05\x2c\xec\x43\xb5\xa8\x95\xc3\x9b\xa7\x8c\xac\x43\xab\x52\xa6\xf0\x7b\x9c\x75\x34\x1f\xbb\x51\x98\x24\x02\xbf\xf2\x6e\x89\x8f\x68\x1c\xea\xa5\x5b\x90\x9f\x85\xdb\x3b\xb8\x43\x1c\x87\x45\xc0\xfe\x4a\x42\x03\xc7\xb4\x9e\xcb\x44\x13\xdf\xca\xac\xd5\xaf\xc8\xad\x45\x8d\xdf\x6f\x90\x6f\xe1\x8a\x55\xf1\x68\x63\x03\xa4\x8b\x21\x49\xe5\xbb\xab\x05\x91\xa0\xfd\xe9\x88\xaa\xc4\xb5\xb7\xab\xc9\xe0\xb9\x9b\xc5\xeb\x6a\x7d\x04\x57\x43\x97\xd5\x46\x0b\x2c\x33\xab\xf6\xba\x41\x3f\x82\x51\xeb\xc7\xe9\x6d\xf4\x1d\x6a\x36\xd1\x60\x31\x2b\x31\x03\x65\x5e\x5b\xb1\x1b\xe0\x8d\xf1\x7e\xae\x31\x51\x32\xa3\xef\xe5\x9a\x56\xaa\xf8\x71\x26\xf7\x1e\x79\x55\x1d\x9a\x0c\x3f\x9c\x98\x44\x06\x24\x5e\x8b\x45\xdd\x98\x17\x85\xe0\xf1\xc7\xc5\x9f\xcf\x73\x90\x5a\x5e\xeb\x88\xdf\x43\xa2\x0a\xba\x13\x13\xd6\xba\x39\xea\xac\x6d\xad\x02\x77\xc6\xa6\xe4\x47\x9e\x4c\x08\xa5\x65\xc0\x02\x58\xc4\x93\x29\xbd\x34\xf5\x94\x0b\x6c\xa2\x73\x57\xa1\x4d\x8f\x06\x7b\x1a\x80\xf4\x71\x03\xdc\x48\x5f\x48\x95\x5e\x90\xbc\x98\xa8\x1d\x88\xa8\x72\xde\x18\xac\xdb\x9e\x65\x8f\x58\x70\x9b\x71\xe8\x77\x85\xd5\x9e\x8b\xde\x93\x82\x96\x1e\x31\x1e\x5b\xa3\x39\xf1\xb8\x2b\xaa\x1d\xbd\xae\xb9\xbc\xbd\xa8\x27\x4e\xd2\x7c\x60\x36\x8d\xb9\xb9\xad\x78\xd2\xa7\xf4\xa3\x34\xa3\xc6\xe3\x63\x5e\x58\x0a\x47\xdc\x23\x0e\xb2\x9e\x29\xaa\xd7\x90\x02\xd4\x7e\x21\x65\xed\x6d\x6a\x3d\xf2\xed\xab\x62\x41\xba\x57\x72\xae\x1b\x0b\xbb\x79\xb4\x61\xf6\x58\xc3\x9a\xc3\x58\xde\x28\xdf\x3e\x88\x87\x67\xe5\xc7\x66\x1c\x52\x8b\x40\xad\xcc\x7c\x23\xac\x1e\x00\x76\xbb\x72\xba\xf1\x19\xce\x2f\xe9\x98\xa4\xa3\x47\x26\x7d\x99\xd8\x29\xbb\x44\x92\x66\x72\x25\xfc\x58\xef\x8c\xfd\xd7\x26\xca\x2b\xd0\xbd\x36\x51\xd6\x84\x5f\x07\xa8\xd9\x94\x37\x02\x35\x4a\x8a\x8f\x7c\x96\x1c\x85\x9f\x52\xdf\x5d\x9f\x04\xfd\x85\xa2\xc0\x7d\x45\x9d\x1c\x5c\x91\xd7\x2b\xe5\x72\x06\x52\xa1\x95\x93\xb6\x6f\xec\x2f\x57\x95\xc1\xaf\x35\xfd\xf3\xc4\x48\x5e\x35\x3f\x1c\xdd\x1c\x4b\xe3\xca\x39\xf6\x4b\x6a\xe7\xd8\xef\x17\x46\x75\x86\x7e\xae\x54\xe3\x02\x1a\xba\x92\x41\xc0\x4d\x54\x74\xac\xf0\x4d\x74\x74\x1c\xde\x55\xd2\xb1\xd4\x1b\x6a\xe9\xec\x22\x35\x6a\x3a\xde\x62\x5d\xd9\xdb\x28\xea\x18\x6e\x2b\x14\x75\x8b\xb9\x70\x17\xdd\x5a\x40\x51\xb7\x50\x9c\xa9\xaf\xf5\x4e\xd0\x73\x25\x79\x13\xf2\xe0\xc5\x6f\x42\x20\xb2\x84\x4b\x22\x3c\xfd\x86\x44\xe2\x16\xaa\x21\x13\xd9\x6e\x7d\xf9\x5b\xe9\x74\xb9\x24\xb5\xc8\xf3\x3f\x4f\x7b\xf7\xfb\xf0\x4f\x8d\x72\x01\xba\xbb\xff\xb8\x18\x75\x4f\x91\x7c\x7c\xd8\x70\xbe\x4a\x8a\xc5\xbd\xae\x46\x38\xa7\x21\x49\xfd\x9e\x57\x4b\x88\xe4\x57\x4d\x73\x88\x9a\x03\x75\xec\xf4\x7a\xb2\x16\x45\x9c\x8c\xb9\x8e\x2d\x28\xce\x27\xec\xc8\x4f\x86\x50\xb3\xdd\xef\x58\xf8\x53\x45\x23\x72\x86\x53\x69\x67\x63\x1f\xa9\xab\x1c\xb9\x3a\x46\x39\xfc\x98\xad\xcd\x80\x01\xcb\xbc\xf2\x52\xbb\x7e\xa3\x60\x13\x62\xf1\x25\xc2\xdd\xa6\x36\xa8\x57\x38\xce\xce\x70\x9e\x9f\xe7\x84\x52\x0c\x36\x68\xbc\x57\x0d\xb4\x0c\xbd\x5f\x18\x77\xe7\xa0\x65\x2f\x4c\x9f\x04\x60\x9a\xa1\x8f\xa2\x24\x15\x28\xa4\x65\x8f\xb8\xae\xdb\x80\x85\x90\x59\xd6\x4a\x3a\xcd\x69\xad\x6d\x05\xde\x3c\xde\x10\xfc\x18\xec\x76\x41\x15\x1e\x4e\xd8\xaa\x00\x7f\x7c\x42\x9b\xc5\xc6\xcb\x38\x01\xe6\x77\x0c\x09\xf9\x8c\x51\x88\x0a\x92\x8e\x12\x2c\x3d\x30\x70\xc8\x8e\x65\xa7\x0d\x14\xcc\x9d\xe2\x70\x0f\x23\xbc\xb5\xab\x2b\x74\xdc\x3c\xee\x9f\x34\x4f\xda\x4a\x18\x9c\xe3\xd1\x40\x74\xcf\xc6\x3b\xfb\x32\x9d\xee\x55\x88\xee\xdc\x30\x8b\xa3\x02\x6c\x1f\xfa\x01\x7a\x0a\x46\xe2\x3d\xe8\x4b\xdf\x90\xec\x8d\x0e\x95\x04\x59\xe9\x73\x22\x90\x5e\x2a\xaa\x4e\x0b\xd2\x37\xc5\x13\x09\xa8\x1b\xe8\x76\x51\x98\x24\xe8\x34\x2c\x48\xc4\x5d\x39\xc0\x0b\x86\xd5\x15\xa1\xc0\x49\x32\x76\x32\x96\xbd\x09\xd0\xea\xca\x3c\x4b\x18\x7b\x61\x0b\x8e\x26\x4f\xe0\x52\x17\x89\xf0\x08\x04\x48\x08\x57\x74\x7c\xd2\x40\x1b\xaf\x60\x7d\xea\xb4\x35\x9e\x58\xab\x4c\xdb\x94\xb5\xdd\x94\x03\x8c\x71\x65\xcf\x6a\x56\xbb\xd9\x6a\x25\xcd\x6a\xa7\x72\x96\x6f\x9b\x12\xd1\xad\x3b\xdb\xa8\x51\xe4\xf1\x63\x64\x7e\x1f\x1b\xbf\xb5\x81\x4c\xff\x44\xee\xba\x2a\x66\x43\x77\x74\xab\xb9\x11\xcb\xb7\x6e\x6a\xe4\x2c\xd8\x73\x23\x26\xcc\x9e\x1a\xc3\xed\xdf\x1d\x67\xc6\xe9\x57\xcd\xc4\x18\x6d\x7e\xed\x79\xb9\xcf\x89\xb1\xbd\xb8\x68\x46\x6a\xcc\x04\x9c\x8d\x1a\x60\x8b\xb0\xc2\x91\xce\x0f\x49\x0d\x61\xac\xd0\x17\x53\xd1\x5f\x53\x80\x2b\x27\xc7\xab\x02\x54\xa6\x71\x10\x05\xd1\x3f\x71\x12\xf4\x77\x79\x77\x00\xac\xde\x62\x7b\x30\xc7\x22\x86\x38\x7f\x4f\x30\x1a\xbb\xa7\x89\x24\x43\xd4\x32\xb2\x0c\x0e\xe9\xf2\xe3\x5b\x4e\x2c\x30\x6c\xdf\x13\x8d\x7e\xcd\x94\x8b\x4d\x42\x9e\xaa\x7d\xf3\x0c\xf3\xe6\x9b\xea\x86\x8a\x0c\x57\x9a\x70\xf1\xd9\xb0\xe6\xdd\xaa\xe8\xd8\xae\xdc\x9c\x6e\xed\x2b\x6e\xa1\x79\x56\x19\x7c\x28\x22\xbf\x72\x7e\x2d\xff\x9c\x95\xbb\xbd\x70\xbc\x94\x84\x05\x45\xc7\x27\x4c\x98\xe0\xf5\xde\x6a\xda\x1f\xf9\xe7\x5d\xcd\x01\xc8\x59\xa8\xe4\x2e\x0a\x0e\x34\xfa\x79\x16\x7c\x2a\x1a\x58\x84\x48\x6a\x2c\x76\x8d\x23\x8c\xe2\xc0\xee\x4d\x13\x3a\xbd\x44\x31\x1e\x86\xb3\x04\x14\xa1\xc5\x8c\xc9\xa9\x6a\x63\x6e\x08\x8f\x3b\x81\x08\x40\xe8\xce\xa2\x75\x8c\xba\x05\x03\xd6\x3b\xe2\x0d\x45\xe1\x05\x4f\x6f\x95\x96\xfe\xd2\x8b\xb8\xd4\x11\x1b\x4b\xa4\x70\xd7\x08\x50\xbc\x20\xe5\xe3\x06\xa3\xf8\x00\x35\xd8\x22\x60\x7f\x4e\x1a\x27\x9a\xda\x05\x84\x91\x06\x85\xd2\x59\xe2\xbe\xc5\x30\x66\x73\x21\xb4\xb9\xbe\xf2\x9c\xfe\xce\x59\x08\x65\x7f\x5b\xa5\x95\xc0\xf7\x06\xe1\xf7\x8f\xcf\x7a\x01\x37\xbc\x6c\x38\xd6\x78\xd9\x7f\x61\xd5\x3b\x44\x2c\xb8\x55\xeb\x9f\x8e\xf9\x69\xfc\x9f\x4e\xda\xf3\x45\x04\xa1\xbc\x55\x2e\x28\xaa\xef\x1d\x9c\x00\x0b\x12\x7a\x71\xd6\x21\x1f\xc4\x96\xef\xb2\x1c\x9c\x79\x2e\x2d\xc4\x3d\xba\xbb\x31\x94\x9c\x94\x31\x46\x60\xdf\xca\x08\x17\xba\xd2\x79\xae\xcb\x16\x5a\x65\xd2\x95\xff\x2c\xa3\x74\xd3\x38\x79\x1d\x55\xfe\xeb\x76\x19\x17\x23\x86\xfb\x3a\xe4\xf5\x58\x8c\x93\xa1\xe9\xa9\x58\xb0\xb2\x52\x21\xf5\x5c\xfa\xf8\xc4\xe7\xde\xba\x33\x9d\x15\xe3\x56\xc9\x51\xac\x7c\x3e\x2e\xbd\xbe\x96\x6b\x66\x63\x29\x7b\x03\x3f\xf3\xf9\xf6\x35\x5b\x32\xbc\x8e\xb6\xce\x02\x64\xfa\x25\x76\x5c\x87\xba\x4d\x7a\xdc\x86\x72\xb8\x96\x84\xbc\x96\x4e\x51\xd5\x04\x9a\x1e\xa5\xef\x3c\x7f\xd0\xf5\x92\xd4\x21\x10\xff\xcb\xcf\x9f\xcf\xd9\xf2\x1c\x27\xcb\x95\x13\xc1\x66\x13\x5c\xa7\xd7\xcc\xc7\x8d\x67\x63\xa9\x74\x47\xe8\xe8\x8e\xac\x25\x69\x38\x18\x5e\xc4\xbd\x29\xbf\x1f\x1d\xe6\xd9\xc4\x6b\x6e\xc0\xa1\x7c\xbc\xe5\xd4\x7d\x45\xe4\x18\x08\x59\x96\x41\x37\x78\xc5\x25\x99\x1a\x6f\x79\x01\x16\x25\x06\x62\xb2\x28\xcb\x35\xe8\x1c\x56\xf5\x55\x78\x15\xec\x4d\xe6\x8d\x25\x17\x74\xc5\xbb\x23\xe8\x9e\x14\x74\x04\xba\x9e\xa0\x15\x30\x7e\x68\xaf\x0b\xc2\x17\xc8\xab\x5a\x44\xb5\x75\x9a\xcd\x97\x2a\xf6\xad\x28\x28\x70\x40\xf9\x1d\xbb\x59\x7a\x19\xad\xae\x40\x41\xbe\xdb\x16\x0c\xa4\x40\xe1\x90\xe2\x5c\x2d\x12\xb3\xbf\xb7\x5a\xab\xfe\x32\x3e\xcf\xf0\x9a\x73\x54\x78\x84\x47\xb5\xd8\x13\x41\x4d\x76\xab\xea\x37\x9d\x8d\x54\x7a\xf5\x76\xa3\xb1\xd4\x32\x9a\x05\x39\x0d\x7a\x52\xdf\x37\x06\xbb\xbc\x51\x0f\xb3\x10\xa3\xb2\x7d\xe7\x1a\xd3\xbe\x8c\xc8\xfd\x72\xad\xdf\xc4\x1e\x62\xfe\x5b\x90\xfa\xa5\x41\xea\x82\xff\x7e\x53\xc4\xff\x40\xfb\xc6\xbf\x5f\x85\xf6\x91\x97\xf4\xcd\xd0\x81\xb7\x25\x7d\x37\xc0\xd5\x0d\x37\x95\x12\xb1\xba\xf5\x2f\xb6\xb3\xd8\xbd\xb8\x49\xfd\x62\xfe\xbc\xf4\x96\x94\xe8\xcb\xbf\xfe\xaa\x97\xf0\x54\xdc\xfa\x95\x8d\x54\xe7\x75\xbf\x85\xfa\x68\xd9\xee\x5d\x9b\x3b\x8a\xe2\x31\xae\x3c\x53\xcf\x9d\x29\x3b\x97\x6e\xd6\x2b\x72\x2f\x59\xe8\x68\x2f\x0a\xd7\x8e\xc5\x97\xc5\xc5\x96\x6e\x38\x36\x73\xce\xd5\xca\x5a\x69\xdb\x6a\x55\xef\x45\xa2\xd3\xf5\xb9\x17\xbc\xd5\x57\xbb\xea\x4d\xdc\xf5\x49\xd0\xff\xb5\x83\xc2\x1f\xcd\x7f\xf6\x36\xab\x79\xf7\x26\xdc\xa3\xc0\x5f\x6e\xeb\x32\xd3\x4f\xdf\x66\xc6\xdb\xb7\x99\xf9\x60\x6d\xe6\x79\xfd\x36\x53\xcf\xdf\x66\xc6\xfb\xb7\x99\xf1\x00\x6e\x66\xbf\x80\x2b\xd5\xb8\x80\x85\x4d\xc9\x69\xcb\x57\x7c\x04\x77\xe4\x7d\x05\x77\x74\xf3\x67\x70\x47\x8b\xbe\x83\x3b\x2a\x3f\x84\x3b\xba\x87\x97\x70\xb3\x3b\x3f\x85\x3b\x5a\xf8\x2d\xdc\xaf\x1d\x71\xfe\x68\x01\x8b\xb3\x59\x9d\xc9\x99\xf4\xf7\xc2\x7f\x08\xe2\x34\xac\xce\x66\xa6\xd9\xd9\xcc\xb2\x12\x9b\xf9\x0c\xcf\x66\xda\xf2\x6c\x66\x9a\x9e\xcd\x4c\xdb\xb3\x99\x63\x7c\xe6\xa9\x77\x91\xc5\xf1\x8b\xda\x9f\x1d\xf9\x0d\xd0\x8e\x6e\x61\x81\x76\xb4\xb0\x09\xda\x91\xc7\x06\xcd\x2d\x7d\xbb\x35\x52\x63\x86\xb6\xe8\x22\x59\xdc\x10\x6d\xb1\xe8\xf4\x5d\xb4\x95\x4d\x2f\xc1\x4f\x03\x6a\x45\x6d\xb4\xd2\xeb\x7f\x83\x3e\x12\xcc\x66\xe3\xc7\x19\x49\x12\xbc\xd4\xed\x32\xb0\x8f\x38\x9f\x90\x02\xde\x5e\x82\xbf\xba\x1c\x9f\x5e\xa2\x51\x1e\xa6\x14\xc7\x01\x7f\x44\x9a\x0d\x51\x34\x0e\xf3\x11\x0e\x10\xcd\xe0\xdd\xe5\x14\xe7\x45\x96\xa2\xec\x94\x86\x24\x85\xa7\x3c\xf0\x9c\x6a\x09\xde\xa8\x71\xcb\x15\xf9\xf2\x14\xdc\x0b\x86\x45\x91\x45\x24\xa4\x38\x46\x71\x16\xcd\x26\xea\xd1\xc5\x90\x24\xb8\x60\x52\x20\x46\x0d\xf9\x3c\xb4\xd1\x0e\xb8\xfa\x25\x4c\x96\xb8\x3c\xc3\xb2\xd5\xe3\x51\xf9\x0c\x33\xc7\x05\xcd\x49\xc4\x5f\x91\x92\x34\x4a\x66\xf0\xa8\x48\x66\x27\x64\x42\x44\x23\xac\x38\x60\xa2\x60\xf5\xd1\x0c\xcd\x0a\x1c\x40\x87\x03\xf1\xee\x35\x40\x13\x0c\xe3\x13\x8f\x57\x03\xe3\xe9\x68\x80\x0a\x78\x1a\xc9\x5f\xa3\x8a\xe7\xb2\x05\x4e\xa0\x73\x51\x36\x25\x58\xbd\x8f\x95\x7d\x04\x30\x08\x14\xc4\x90\x4b\x05\xba\xc0\x07\xd5\xf9\x38\x9b\xd8\xe3\x21\xd0\xab\xe1\x2c\x4f\xf9\xab\x59\x36\xf4\x0c\x15\x19\xb4\x0b\x0f\x15\x29\x8f\xeb\x31\xcc\x92\x24\x3b\x67\x63\x8c\xb2\x94\x07\x43\x2a\x06\x62\x16\x0f\xc7\x18\x85\xa7\xd9\x19\x7f\xd6\xc6\xa7\x3d\xcd\x28\x89\x38\xfe\x61\x46\xa6\x7a\xa6\x45\x56\x31\x0e\xf9\x03\x58\x8e\x3e\x78\x25\xbb\x24\xac\x2e\xe4\xc8\x72\x04\xd2\x53\x98\x52\x12\x26\x88\xd1\x17\x6b\xd7\x1d\x71\x47\xf6\xe3\xdd\x36\x3a\xd8\x7b\x7b\xf8\xe3\xe6\xfe\x36\xda\x39\x40\x1f\xf7\xf7\x7e\xd8\x79\xb3\xfd\x06\x35\x36\x0f\xd0\xce\x41\x23\x50\x6f\x6b\xe5\xbb\x5a\xb4\xf7\x16\xde\xd9\xfe\xe3\xce\xee\x9b\x00\x6d\xff\xe9\xe3\xfe\xf6\xc1\x01\xda\xdb\x67\xb5\xed\x7c\xf8\xf8\x7e\x67\xfb\x4d\x80\x76\x76\xb7\xde\x1f\xbd\xd9\xd9\xfd\x1e\xbd\x3e\x3a\x44\xbb\x7b\x87\xe8\xfd\xce\x87\x9d\xc3\xed\x37\xe8\x70\x0f\xda\x14\xb5\xed\x6c\x1f\xb0\xfa\x9c\x07\xb8\x01\xab\x6b\xde\x0b\x5c\xb4\xb9\xfb\x06\xed\xee\xed\xee\xec\xbe\xdd\xdf\xd9\xfd\x7e\xfb\xc3\xf6\xee\x61\x07\xed\xec\xa2\xdd\x3d\xb4\xfd\xc3\xf6\xee\x21\x3a\x78\xb7\xf9\xfe\x3d\x6b\x8d\x55\xb7\x79\x74\xf8\x6e\x6f\x9f\x75\x14\x6d\xed\x7d\xfc\xf3\xfe\xce\xf7\xef\x0e\xd1\xbb\xbd\xf7\x6f\xb6\xf7\x0f\xd0\xeb\x6d\xf4\x7e\x67\xf3\xf5\xfb\x6d\xde\xda\xee\x9f\xd1\xd6\xfb\xcd\x9d\x0f\x01\x7a\xb3\xf9\x61\xf3\xfb\x6d\x28\xb5\x77\xf8\x6e\x1b\x06\xc9\x20\x79\x37\xd1\x8f\xef\xb6\x59\x2a\x6b\x75\x73\x17\x6d\x6e\x1d\xee\xec\xed\xb2\xf1\x6c\xed\xed\x1e\xee\x6f\x6e\x1d\x06\xe8\x70\x6f\xff\x50\x95\xfe\x71\xe7\x60\x3b\x40\x9b\xfb\x3b\x07\x0c\x33\x6f\xf7\xf7\x3e\xc0\x48\x19\x76\xf7\xde\x32\xa8\x9d\x5d\x56\x74\x77\x9b\x57\xc4\x30\x6f\x4f\xd0\xde\x3e\x7c\x1f\x1d\x6c\xab\x3a\xd1\x9b\xed\xcd\xf7\x3b\xbb\xdf\x1f\xa0\x9d\x5d\x77\x42\x3b\xdc\x30\x75\xeb\xdd\xe6\xfe\xc1\xf6\x21\xda\x40\xcd\x7f\x9e\xfe\x9c\x5f\x7e\x7b\xf1\x62\x34\x5c\xa1\x67\xf1\x79\xaf\x58\xfd\x29\x7d\xb6\xf6\x79\x1c\xe1\xe7\x93\x59\xf8\x4d\xd2\xe4\xdb\xf1\xf7\xdb\xbb\xdb\xfb\x9b\x87\x7b\xfb\xec\xdc\xdb\xbb\x58\x3d\x7d\x1e\x3e\xfb\xe6\x74\x25\x40\xbd\x8b\x95\xe7\xcf\x7a\x2f\xf0\xf3\x98\xfd\xee\xe3\xb0\xdf\xff\x76\x18\xb2\xdf\xab\xf1\xda\xca\xea\x6a\x0c\xe9\x2b\x61\x7f\xed\xf9\xca\xe9\xea\x49\x95\xc7\x14\x7e\x6e\x1d\x88\xbf\xc1\x12\x12\xd7\x37\x32\xc6\x5b\xc0\xad\xe1\x15\x4f\x9f\x66\xc9\xe5\x24\x8b\xc5\x4d\x8d\xb8\x80\x05\x8f\xee\xe3\xcf\x10\x1a\x68\xc9\x38\x8e\x4f\xf9\x71\x7c\x8a\x5e\x72\x4d\xab\x8e\x15\xb4\xbc\x3c\x35\x95\xa6\x34\x63\xa0\xac\x8a\x57\xaf\xd0\xca\x33\xbe\x1f\xf2\x1a\x5b\xec\xcf\x63\x36\x44\xee\x45\xb1\x8d\x5e\xbe\x44\xcf\xd0\x5f\x45\x8d\xc7\x53\xa1\x71\xf7\xe9\x00\x9e\xb1\x86\x8c\xa8\xa7\xec\xd4\xdc\x62\x6d\xbd\x7a\x85\x48\x1b\x3d\x46\x7d\xf3\x30\xcc\x1a\xfa\xeb\x86\xc6\xf8\x31\x51\xca\xfc\x6b\x65\x07\xc0\xfe\x2f\xb6\xb2\x68\xfc\x79\x7d\xe9\xda\xc0\xcd\x38\x9f\x6e\x5f\x4c\x19\xd3\x68\x8d\xf3\xa9\x46\x4d\x8e\xf5\xd5\x00\xa0\x45\x21\xc9\x40\xd0\x38\x9f\xfa\xb0\x93\x63\xa1\x65\x60\xf9\x6c\x2b\xd9\xca\x62\xbc\x49\x5b\xd3\x36\x1b\xc4\x33\x38\x80\x89\x3e\x71\x38\x7e\xac\xbc\x7b\xed\x8f\xd9\x79\x79\xdd\x1e\x71\x8e\xa9\x3d\xe2\x33\x9c\x93\xe1\x25\x78\x81\x2d\x66\x13\x18\x76\x80\xe2\x90\x86\xbc\x01\x69\x8b\xc1\x69\xa6\xa5\xf0\x03\xe8\x61\xe2\x46\x14\xd2\x16\x80\x73\xdf\x73\x7d\xbb\x76\xfe\xda\xb8\xb2\x76\xe5\xdd\x88\xd1\x72\x4d\xdd\xf2\xe3\xb8\x17\x20\xeb\x7f\xdc\x75\x9a\x88\x90\x8a\x36\x54\x3f\x25\x69\xff\x95\x93\xb3\x3b\x85\x3e\xf2\x7e\xee\x47\x6b\x8b\x55\xcc\xe6\x09\x3d\x41\xad\x67\xe8\x29\x9a\xb6\x17\xc4\xac\xb8\x1b\xf5\x8d\x59\xbc\xa9\x65\x3d\x66\x19\x72\x78\x36\xb6\x8c\x82\x6d\x7b\x10\xe3\x7c\x8a\x96\xb9\x5b\x5a\xff\x58\x64\xfd\x15\x04\x83\x96\x37\x24\x1f\x03\x92\xd9\xa4\x2d\x59\xe2\x78\x7a\x32\x77\x60\xe2\x46\xa5\x75\x8a\xa3\x31\xbf\xb2\xd1\x23\x9b\xca\x9e\x8e\xc3\xe2\x53\x92\x9d\x73\xf1\x97\x9b\x16\xe8\x8c\xd9\x74\x6a\x67\xb8\xc4\xae\xab\xf6\x0d\x81\x31\x01\x03\xc2\xa6\xfa\x97\x68\x75\x15\x5d\x5d\xa1\x4a\x80\x57\xa8\xbf\xf2\x5c\x73\x0d\x79\x2f\x3f\x4b\x12\xd3\x56\xa8\xb6\x8d\x57\x1b\xe8\xdb\x6f\xd0\xe3\xc7\xd5\x8d\xbc\xdc\x40\xfd\x95\x15\x93\x37\x99\x08\xe1\x96\xf2\x8b\x37\xf6\xfc\xd9\xbc\xc6\xbe\xed\xb9\x6d\x49\x1c\xdb\x6d\xb1\xff\xb3\xd6\x74\x6f\x1e\x3f\xd6\xe0\xee\x93\x6e\x81\x13\x56\x48\x37\x8e\x36\xcc\x9e\xd0\xec\x3d\xab\x66\x2b\x2c\x44\xd0\x52\xa0\x82\xac\xb0\xa1\x92\xb0\xa0\x3b\x69\x8c\x2f\xf6\x86\xad\x66\x9f\xfb\xfe\x66\xdd\x60\x80\x2f\x51\x9f\xcd\x17\xfb\xb9\x8c\xbe\x41\xaf\xca\x93\xef\x4c\xa7\x48\x7c\x65\x0c\xda\xd3\x61\xa0\xb5\x7c\x6a\xf7\x83\xeb\xc2\x48\x3a\x6a\xf5\x02\xd6\xa2\xea\x31\x5b\x66\x36\x7b\x98\x02\x3b\x61\x7d\xea\x2f\x42\x93\x50\x09\xd2\xeb\x8a\x88\xd1\x3a\x93\x06\x13\x66\x98\x42\xc5\xc0\x35\x9f\xf6\xe7\xd2\x23\xf0\x09\xe0\x48\xb1\x5a\xa0\x60\x4b\x65\x33\x70\x93\x69\x54\xe3\x46\xda\x7b\x8c\xf3\xe9\x00\xa9\x12\x03\xde\x88\xf2\xd8\x07\x5f\xca\x7e\xea\x79\xfb\x1a\xd8\xc0\x75\xf0\x85\x1d\xc4\x16\x8a\x1c\xd6\x9c\x15\x18\xf1\x83\x4a\x73\x7d\x89\x49\x54\xa3\x0c\xe1\xf4\x0c\xc5\x19\x06\xb3\x71\x70\xd3\xc2\xb6\xdb\x14\xe3\x18\xfd\xe9\xc3\xfb\x77\x94\x4e\xf7\xf1\x3f\xcf\x70\x41\x97\x40\x43\x7e\x39\xc5\xd9\xd0\xc9\xe1\x5e\x4e\xd5\x43\xfa\xa6\x1c\xa9\x68\xb8\xe3\x42\xa3\x2f\xac\xeb\x66\x3c\xf9\x4a\x48\x3b\x01\xae\x4c\x44\x8c\x6f\x32\x4a\xb3\x1c\x0f\x12\x92\x62\x86\x06\x89\x87\x87\xa8\x54\x0f\x2e\x8c\x7e\xef\x2e\x8c\xb8\xfb\x22\xf1\x98\xc8\xf2\x5f\xc4\x35\x7f\xe8\x76\x6e\x8c\x84\x12\xf2\x88\x92\x04\xea\xe4\x86\x25\xb0\x76\xb8\x9f\x88\x12\x18\xa1\x97\x4a\x37\xa5\x8b\x44\x49\x58\x14\xe8\x18\x8a\x9c\x88\x6e\xf2\x0c\xad\x0d\xe3\x55\x2d\x75\xc1\x80\x0f\x56\xa9\xb0\x72\x61\x1c\x04\xd5\x84\x3a\x5a\x3c\x76\x16\x40\xb2\x9a\x8e\x76\x77\x0e\x0f\xd8\x66\x02\x93\xd0\x3c\xcb\xd2\x26\x27\xcd\xe6\x67\xe3\xf7\x07\xe3\xf7\xf7\xc6\xef\xe2\xe7\xf0\x34\x93\x1f\x43\x92\xa6\xf8\x52\x7d\xe1\x09\xcd\x92\x90\xca\xef\x29\x89\xcc\xcf\x34\x4c\xcd\xcf\x09\x89\x72\xfb\x3b\x49\x88\x03\x6e\xc1\x5a\x80\xf2\xc3\x28\x30\xca\xc3\x34\x56\xdd\x37\x32\xbe\x37\x7e\x1f\x1a\xbf\x3f\x1a\xbf\xb7\x8d\xdf\x7f\x31\x7e\xff\xd9\xf8\xbd\x6b\xfc\x7e\x63\xfc\xfe\xc1\xf8\x7d\xc4\x7e\x2f\x55\x1e\x86\x11\xcc\xc0\xc7\xcd\x37\x6c\x02\x07\x68\x75\x25\x50\x89\x07\x3b\xdf\xef\x6e\x1e\x1e\xed\x6f\x7f\x7a\xbf\xbd\xfb\xfd\xe1\xbb\x01\x5a\xd3\x99\x30\x67\x03\xfd\x53\xe7\x54\xd0\xc5\x00\x7d\x41\x4e\x82\x8e\xa1\x05\x19\x9f\xde\xec\xfd\xb8\x8b\xae\x75\x4d\x1f\xf7\xde\xbf\x67\xd0\x87\x3b\x1f\xb6\xf7\x8e\x0e\x07\xa8\xdf\xeb\xf5\xba\xa2\x87\xc2\xb0\xf8\x75\x92\x45\x9f\x07\x80\x76\x5c\xc8\x41\x8b\xbc\xcd\x28\xca\x66\x29\x1d\x68\x17\x32\xfc\x64\x7f\x1d\x7c\x99\xe7\x62\x72\x65\x21\x17\x93\x0f\x7a\xd3\x07\xbd\xe9\x83\xde\xf4\x41\x6f\xfa\x35\xf4\xa6\xa7\xe0\x0e\xda\xbe\x8e\xe4\x69\x4d\x6f\xb4\xe4\x2f\x1e\x3d\xa6\x4f\xd7\xf9\x06\x7e\x49\xcf\xbe\xd6\x17\xcb\xde\x4e\xcd\x6c\xeb\x8b\xeb\x45\xb5\xaa\x2a\x4b\xcf\x70\x4e\x4f\x09\x2d\x10\x68\x9d\x02\x30\x7b\x65\xdf\x8c\xb3\xf0\xbf\xd3\x30\xd6\xba\x8e\x30\x8a\x40\x63\x21\x3e\xa1\xa4\xf1\xed\xea\x0c\x27\xe1\xc5\x19\xda\x40\xad\x3e\x7a\xf9\x52\x54\xd8\x46\x4f\xab\xf5\xad\xc6\xf1\xab\x74\xd2\x94\xde\x13\x19\x8c\xd2\x9f\xb2\xf3\x12\xcf\x78\x89\x7a\xec\xcc\x2c\xbe\x5e\xbd\x52\x23\x69\x83\x0b\xb2\xde\xdc\xd3\x26\x1f\x5a\x8b\xfd\x79\xf9\xd2\x28\x7d\xc5\x5b\xe6\xa0\x30\xe0\xe5\x0d\x95\xcd\x53\xcf\xc7\x4c\x58\x6c\x41\xe6\xab\x0d\x35\x50\xd9\x20\xa4\x3f\x95\xe9\xeb\xba\x1b\x42\xdd\xc6\x9a\x7c\xf5\x0a\xf1\x42\x8f\x01\x69\xed\xb2\xfa\x42\xcd\x83\xd0\x9d\x40\x63\xce\xb8\x8c\x0a\x5f\xbe\x44\x2d\xde\x20\x7a\xca\xab\xf6\xd5\xad\xed\xb2\x64\xe7\xe5\xc8\x00\x99\xf3\x6a\x9a\x7f\xde\xae\xd4\xa4\xc1\x11\x3c\x8c\xe3\x5c\x13\x57\x8c\x23\xa1\xba\x58\x5d\x91\xa6\x69\x00\x21\x55\x27\x00\xb0\xb1\x01\xed\xb0\xfe\xc5\x38\xea\x8c\xf3\x29\x4c\x30\xfb\x2b\x92\xcc\x43\xfc\x4b\xd4\x47\xdd\x27\x46\xc6\x71\xef\x04\xbd\x42\xfd\xe7\x4f\xba\x73\x14\x29\x39\xe6\x11\xcc\xd5\x12\x69\xa9\xba\x85\xba\xa0\x1d\xa0\x67\x01\x7a\x11\x70\x9d\x9e\xea\x25\x14\x34\x7a\x99\xeb\x27\x59\x2f\xd1\x8a\x93\xf2\x0a\xad\xd5\x69\x74\xc4\xa8\x55\xcf\x59\xbd\x3d\xf4\xf8\xb1\x59\x05\x1b\xfd\x8a\x2f\x71\x75\x65\x51\x7d\x88\x40\x64\x80\xa6\x79\x36\xca\xc3\xc9\x80\x55\x75\x5d\xa3\xdb\x15\x70\xee\x6d\x81\x98\x3b\x61\x6f\x0f\x90\x26\x02\x45\x29\xc0\xd9\xb3\x00\xd4\x74\x6d\x73\x72\x55\xa1\x1c\xd3\xb6\xc2\xe1\xe2\x44\xd6\xed\xfe\xeb\x7f\xff\x5f\xfe\xeb\x7f\xf3\x2f\xff\xf6\xff\xfe\x4f\xff\xfa\xdf\xfe\x9f\xff\xfa\x2f\xff\xe1\x3f\xff\xc7\xff\xf5\xff\xfb\xbf\xfe\x8f\x7f\xfb\x7f\xfe\xe3\xbf\xfe\xcb\x7f\xf8\xb7\xff\xee\xbf\xfa\x4f\xff\xe3\xff\xfe\x9f\xff\xef\xff\x5a\x0f\xe9\x80\xe6\x2b\xdc\x4d\x5c\x41\x0d\x3a\xe4\xaa\x3c\xc5\xd4\x12\x9c\xa2\x0d\x54\xd0\x5c\x72\x26\xe8\x70\x8b\x25\xff\x7b\xb4\x82\x1e\x31\xe6\xb2\x84\xe4\x32\x34\x7b\x89\x44\x37\x19\x68\x77\x83\xc7\x7d\x00\x1d\x1d\xbe\xd8\x14\xb6\x0e\xdc\xf8\x57\xde\x7e\xf0\xab\xa0\x0d\x6d\x0c\xca\xdf\x5b\xe8\xda\xc1\x56\x5f\xf4\x46\x18\xb9\x4d\xb3\x22\x40\x32\x8e\x24\xeb\x18\xa3\x3e\x1e\xd4\xe0\xea\x0a\x89\x8f\x3f\x35\xc4\xdb\x7d\xc3\x8a\x12\x34\x7d\x1b\xc8\xb4\x59\x8d\xb2\x94\x92\x54\x32\x3b\xe9\xca\x0b\xb8\xaf\x69\x06\x59\x88\x58\x55\x5c\xf1\x8a\x2f\x36\x39\xdf\x91\x9c\xc5\xaa\xd9\x98\x26\x06\x29\xe7\xc9\x98\x0f\x36\x3d\xbe\x69\xd3\xf3\x04\x73\xb4\x72\xc0\xcd\xf8\xf5\x3c\x15\x94\x1d\x4e\x1b\x8d\x32\xf2\x5e\x86\xb9\x9a\x2d\x0f\x06\xe9\x64\xca\x8d\xfa\x8f\xc9\x89\xe5\x6c\x51\x23\x91\x4e\xe4\xd5\x13\x43\x60\xbf\x8c\x3d\x5e\x47\xa3\xd7\x40\xcb\xec\xb7\x8d\x31\xd6\xb3\xe5\x0d\x99\x6e\xa0\xa0\xa0\xb9\xc0\x00\xfa\xb7\xff\xed\x7f\xf9\x4f\xff\xf3\xff\xa0\x07\x69\xed\xe3\x9a\x3d\xe2\x82\x1d\x53\xcc\x45\x66\x2e\x14\x09\xa1\x79\x0f\x2d\xad\x1b\xcf\xca\xd1\x1d\xd2\xa8\x85\xed\x43\x2c\xeb\x25\x84\xba\xdd\x28\x4b\x8b\x2c\xc1\x9d\x24\x1b\xb5\x1a\x82\x6d\x4b\x6b\xe6\x8d\x57\xe0\x1e\xc4\x5f\xba\x2d\x87\xa8\x07\x67\x49\x21\xbc\xeb\x05\xcd\xc5\x37\xef\xa8\x28\x8d\x36\xec\x65\x29\x61\x2c\x14\x98\x0c\x46\xb6\xba\x5e\xee\xb4\x60\x59\x4e\xa7\x73\xe1\x27\xce\x61\x1e\xdc\x12\x87\x73\xb0\xc6\xa0\xff\x0d\x3b\x42\x3e\x38\x6b\x7f\xd0\x74\xfe\xde\x35\x9d\x4b\xca\x59\x7b\x31\x0e\x57\xef\xcb\x57\xfb\x56\x7e\x39\xa5\xd9\x1f\x0f\xcc\xd3\x4f\x04\x69\x4f\x75\xd8\x7d\xd6\xa0\x17\xa0\xcb\x72\xfc\x87\xa4\xb2\xa3\x75\xa0\xb8\xc2\x94\x8d\x45\x12\x13\x89\xc4\xcf\x8e\xf4\xec\xce\xef\x51\xc6\xf8\xa2\xe9\xfa\xbb\xb4\x62\xfe\xbc\x42\x2b\xac\xb4\x1b\x3c\x75\x45\x46\x9b\xb3\x8b\x23\xf9\x6c\x55\x3d\xc5\x75\xc2\x3b\x23\xc7\x2f\x26\x07\x95\x38\x62\x9d\xeb\xbc\xc3\x17\x1d\xd8\x60\x45\xe0\x4f\x9f\x37\x00\x86\x15\x39\x6c\xdd\x3a\xb7\xbd\xe4\x86\xf2\x03\xb4\xf2\xec\x39\x2f\x69\xb8\x11\x96\xde\x21\x19\x8b\x53\x38\x6e\x0c\x9e\xbd\x08\x1a\x36\xca\x1b\x83\x6f\xbe\x65\x7c\x6f\x21\x4f\x90\x0f\x7c\xef\x81\xef\xfd\xed\xf2\x3d\xcd\xf6\xb8\x47\xee\x7b\xe0\x7b\xce\xe5\xce\xcd\xef\x76\x3c\x57\x3b\xb2\xa0\xef\x66\x67\xe1\x6b\x1a\x1f\xaf\xed\x74\x05\xbb\x97\x00\x33\x3a\x7c\x61\x02\xb0\x6f\x55\x58\x87\x80\x72\xea\xb0\x62\x43\xa9\x2b\xa1\x59\x4a\xe8\x87\x70\xaa\x2e\x28\x9a\x69\x16\xd2\x69\x73\xc0\x19\xd6\xbf\x6b\xf6\xe4\xd5\xc6\x59\x96\xca\x54\xf8\xee\x5b\xd7\x46\x03\x23\xa3\xd7\x53\x85\xfe\xb1\x26\xef\x34\x3c\x3d\x0d\x47\x58\x66\x5b\x79\x70\x97\x24\xfb\xf1\xef\xac\xac\x89\xa7\x4a\x23\xfb\x43\x7d\x76\x92\x9d\xe1\x24\x8c\x64\xab\x6e\xf6\x94\x44\x7a\xf8\xff\xce\xcd\x1d\xf9\xeb\x36\x20\xbe\x9f\x0b\x51\x8c\xc3\x34\xd5\x40\x1e\x88\x34\x4c\x3d\x7d\x70\x00\xe6\x34\x02\xb7\x74\x03\x0f\x84\x89\x48\x12\xe5\x0e\x8e\xfd\x30\xf3\x2a\x12\xd7\x80\x03\x2f\x90\x55\x59\x92\x90\x8a\x06\x5d\xb0\xaa\x36\x0d\x38\x83\x4c\x7d\xd5\x19\x90\x9f\x4d\x50\x2f\xa4\x39\xc9\x70\x8f\x58\xdd\xbc\xd3\xd9\x45\xaa\x36\x6b\x5f\x10\xde\x28\x42\x17\x2f\xc2\x4a\xf9\x1d\x27\x68\xa7\xea\xd3\x30\x16\x6e\x61\xc0\x6d\xcb\xc5\x14\x47\x6c\x7b\x53\xae\xb6\x4c\x07\x0a\x22\x12\x82\xcf\x0b\x82\xae\xe2\x14\x33\xb8\x18\xc7\x3e\x7f\x0a\xd1\x38\xcc\xc3\x88\xe2\xbc\x90\xcf\x75\xe0\x80\x27\x4a\x1b\x3b\x8d\xb7\x0d\x32\x4a\x03\xc3\xaf\x11\xea\x2d\xf9\x5d\xf8\xc3\xdd\x90\x0c\x79\xe9\x84\x0f\x15\x63\xb0\xe4\x51\x0e\x12\x40\xef\x8a\x00\xda\xf1\xc4\x0b\xe1\x0a\x1f\x80\x41\x4f\x45\xb5\x1d\xf5\xda\xb3\xdf\xee\xfc\x94\x91\x14\xa2\xc1\xa3\xef\xa0\x0e\x34\x40\x8d\x5e\xa3\x8d\x96\x05\x70\x85\x13\x8b\x5b\xcf\xc5\x3e\x1b\xe7\xdf\xfa\x64\xc0\x20\x6e\x38\x1b\xa2\x87\xcb\x3c\xa4\xeb\x6d\xe7\xa5\xca\xa9\x88\x1d\x54\x02\x36\x57\x70\x29\x42\x68\xb3\xf0\x04\xb1\x75\xe6\x85\x87\x83\xad\x99\x15\x02\xea\x34\x3f\xb2\xcc\x80\xb7\x9e\xa0\x27\xac\x71\xed\x42\x84\x1b\xb2\x1f\xf1\xcd\x5f\x09\xfd\x63\x7c\xc1\xb0\xd5\xed\xa2\xb7\x24\x8d\x11\x77\xd4\x28\x3a\x8a\xca\xda\x37\x9e\x02\x36\xec\x01\x4a\xd0\x06\x6b\xc3\xd0\x96\x0a\xfb\x4b\x7c\x61\x1b\x23\x56\x9c\xa6\x58\x35\x2b\xe6\xcd\x0c\xdc\x14\x09\x95\xe8\x3a\x22\xcb\x1b\x96\x91\x29\x37\x2e\x8e\xad\x38\xd3\xba\xa9\x16\x61\xed\x98\xfa\x4a\xd1\x1b\x5e\x64\x43\x28\x6f\xf5\xbf\xd3\x1c\x87\x9f\x35\xa8\xd0\xe4\x09\x73\x46\x36\x83\x5b\xc2\x0e\x15\x6a\xf0\x1e\xd3\xd8\x64\xcb\xfb\x8b\x82\xe6\x8b\x51\x08\x9f\xb4\x1b\x91\xc8\x26\x2b\xf2\xcb\xd1\x08\x34\xf7\xf7\x43\x24\xb7\x99\x79\xae\xde\x9d\x3f\xd9\x9e\x20\xd6\xad\x69\x8e\x87\xe4\x02\x0e\xa8\xa8\x77\xd1\x66\xb3\x00\x5c\xc3\x1f\x3f\x3b\xcf\x26\xf5\x1c\xc2\xcc\x51\x21\xfa\xc3\x24\xc9\xce\xff\x82\xf3\x8c\x07\xf1\x82\x9b\x19\x8e\x97\x69\x46\x52\x8a\x7e\x16\x59\xbd\x1e\x22\x69\x41\x71\x18\xb3\x6e\xe0\x34\x5e\x24\xc2\x79\x99\x8a\x78\x9c\x68\x37\xf2\xb6\xe8\xba\x49\x49\x05\xcd\x03\xdd\x39\x39\x71\x9c\x78\x60\x35\x09\x85\x2f\x5f\x4d\x72\x42\x59\xa3\x9a\xb8\xd4\x0d\x80\x7e\x49\x63\xdc\xd9\xb8\x7e\xab\x0c\x82\x60\x50\x86\x71\x39\xa9\x66\x14\x8e\xca\x87\xe5\x96\x3a\x6d\xfe\x63\x1d\x5c\x66\xb4\xdb\x6b\x3a\xfe\x33\x4c\xc3\xdc\x1a\xee\x83\x2c\xbd\x91\xb7\x14\x38\x48\x81\x8b\xca\x18\xfb\xee\x32\xec\xae\xa4\xe6\x4d\xe4\x77\xa8\xd9\x6b\xa2\x65\xc4\xb6\xb2\xd4\x55\x54\xd9\x84\x2d\x62\x64\x8f\xf1\xc5\x7d\xd2\xb7\x13\x99\xdd\x25\xf0\x39\xfc\xcd\xeb\x69\x0b\xd4\x30\x61\x02\xf2\xca\x7d\x51\x6b\x89\xf1\xa9\xdb\xc2\x5f\x8a\x0e\x17\x98\xe4\x05\x27\xf8\x86\xd3\xea\x84\x50\x1c\xce\x92\x44\xa1\xa1\x8b\xcf\x70\x4a\xb9\xe7\x32\xd8\xb7\x7e\x2a\xb2\x14\x85\xa7\xc4\xdd\xa8\x64\x1c\xb7\xc3\xec\xed\x2c\x49\x5c\xa7\xae\xd2\xbb\x19\x2b\xfd\x94\x97\x2e\x7b\x67\xe4\x0d\x97\xda\xd5\xbb\x53\xb9\x0d\x4b\x1a\x64\x95\x9b\x1a\x62\xf6\xdd\x81\x37\xdd\xf2\xd1\x42\xb3\xd5\xe4\x96\x22\x4f\xfb\x1e\xff\xac\x0a\xbe\xe4\xb8\x84\x5e\x4e\xb1\x68\x0e\x80\x80\x8a\xec\x00\x4b\x2d\xd2\xfe\x22\xea\x21\x1d\x06\xbf\x8e\xae\x85\x3c\x69\xbb\x22\x51\xad\xa0\x65\xd4\x6c\xb1\x99\x53\xb5\x2f\xa3\x66\xbb\xb9\xd0\xda\x8b\x49\x31\x4d\xc2\x4b\x3e\x2f\x10\xf4\x30\xa5\x4c\x40\x57\xd8\x70\x9d\x68\x5e\x40\xf6\x1b\x5e\xac\xce\xe5\x2e\xab\xcd\x9e\x7c\xff\xf2\xb2\x7a\xc0\xf6\x65\x87\x62\xf0\xa8\x83\x26\xb3\x84\x92\x69\x72\xd9\x12\x7e\x3e\xda\xe8\xe9\x2b\x95\xa8\xa6\xb5\xdc\xb7\x5a\x4f\xcc\x05\x7d\x9d\x87\xd1\x67\xb8\x22\x2c\x4d\xae\xde\x37\x70\x1a\x57\xc1\xb5\x9b\xf6\x8c\xb4\x74\x95\x9c\x36\xd0\xe3\xc7\x66\x79\x49\x30\xdf\xb9\x9e\x7a\x55\xb9\x36\x5b\x7b\xd5\x51\x14\xec\xb9\x53\x0e\x00\x6f\x30\x71\x87\x82\x44\xee\x3a\x6b\xba\xed\x9b\x4c\x99\xf8\xed\xce\xd8\xa1\x5e\x15\xbf\xfd\xe9\xd2\x85\x96\x51\x3f\x30\xe1\x9f\x1a\x7d\x7c\xca\x0e\x81\x39\x9e\x26\x61\x84\x5b\x4d\xd4\x84\xb8\x5b\xec\x10\xd8\xb0\xa6\x76\x8b\x4b\x55\x85\xb8\xe5\xa1\x19\x17\xe7\x63\x1c\x91\x49\x98\xb8\xdb\x0e\x49\xfd\xdb\x1f\xcd\xde\xf0\x02\xe5\x59\xbd\xe2\x7b\xdd\x95\xd2\xfc\x5e\xfb\x38\xa6\x21\xb7\x8b\x9a\xca\x77\x67\x6e\x2c\x9b\x4c\x55\x29\xf2\x3b\x34\x13\xdf\xed\x45\xc6\x38\xf7\xc8\xc2\x76\x89\xfb\x1a\x97\x51\x57\xf5\xc8\x60\xdf\x94\xea\xf1\xf2\xf8\x34\x8d\x29\x2f\x6d\x42\x6f\x6e\xef\xb1\x96\x0e\x87\x03\x24\xb8\x28\x0e\xc7\x61\xda\xea\x41\x9c\xcf\xa7\xdc\x31\x98\x70\xb0\x26\xe8\xaa\xcf\xc8\xa3\x69\xe6\x58\x58\xdc\x9c\x69\x21\xbc\x80\x8b\x23\x1e\x1b\x45\x44\x8c\x4c\xab\xd1\xda\x11\x78\xdd\x4c\x63\x7e\xef\xc3\x69\x88\x0c\x2f\x0b\x11\xeb\xba\x40\xa7\x78\x98\xe5\xb8\x53\xa2\xab\x77\xe2\x44\x58\x8f\xfb\x2b\xb1\x2b\xcf\x21\xad\x77\x20\xf9\x58\xc8\x57\x6e\xa3\x9f\x88\x47\x64\x93\xf0\x22\x82\x38\xa7\x17\x84\x5e\x0e\xd0\x0b\xb8\xbb\x90\xfb\x30\x29\xc4\xd9\x04\x8a\xb6\xdd\x6d\xd7\x98\xe4\xd6\x32\x83\x58\xb7\x8a\x9a\xd3\x59\x5f\xd8\x29\x2b\x9e\xd8\x9d\x31\xda\x61\x87\x4b\x8e\xb4\xa6\xb7\x0a\x76\x56\x69\xfd\xf1\x60\x6f\xb7\xa3\xb0\xcc\xdb\xd3\x31\x06\x21\xba\x67\x81\x42\x94\xe2\x51\x48\xc9\x19\x16\x54\x12\xa0\x69\x58\x14\x88\x50\x08\xbf\x33\x1b\x8d\xed\x15\xa0\x06\x22\x68\x0d\x6a\x2d\xdf\x4a\x6b\x76\xf7\x14\x0e\xc1\x1e\xa5\x45\xe5\x88\x25\x80\x30\xf3\xb4\xab\x9b\x53\xdb\x99\x74\xf1\x63\x54\x80\x5a\x8f\x48\xf1\x96\xa4\x84\x62\x07\xe9\x3e\xac\x9d\x85\x49\xd0\xf7\x1e\x94\xcb\x3d\x35\x16\xc6\xbe\xd8\x9a\xf8\x42\x60\xe7\xe0\x94\x80\xf7\xdc\xb3\x12\x47\x19\x61\xfa\x03\x83\xda\x1b\x1e\xa5\xc4\xab\xc5\x84\xb2\x74\x8c\xc5\x0f\x7d\xec\xa5\x59\xa0\xb4\x8e\x21\x9d\xda\x9b\xa2\x66\x42\xba\x17\xaa\x92\x16\xef\x4a\x1b\x8a\x40\x60\xa5\x02\xe1\x3c\xcf\x72\x19\x34\x84\xf7\xb7\x40\x69\xc6\x4e\xd7\x79\x8e\x23\x3a\x38\x57\xcb\xc6\xee\xb3\xb5\x7e\x58\x41\x49\x01\x33\x9e\x09\x7f\xbe\x83\x3f\xf6\xc3\x63\xc6\x5b\x42\x3a\x6d\x6a\x26\xc6\x60\x7e\x10\xb6\x05\xe2\x06\xee\x98\xfd\x35\x6c\xa5\x0d\x10\x33\x2e\xb3\x49\x75\x3c\x52\x54\x8a\xcf\xd1\x36\x1b\x53\xab\x09\x17\xfc\xd0\x0d\x78\xc2\xfa\x4f\x4d\x8a\xf0\x05\x29\xc0\x36\x3c\xc1\x61\x01\xa7\x04\x18\x77\x96\x2a\x44\xe9\x37\x18\xac\x64\xc1\x58\xa0\xb3\x86\x44\x0f\x03\x30\x4b\x0b\xb4\x5b\xbe\x6b\x57\x63\xae\x97\xb9\xea\x3d\x44\x27\xb2\xa3\xbe\x86\x9f\x31\xac\x40\xce\xed\xd9\xe2\x66\x87\x91\x34\xd6\x1c\x96\x4f\x3f\xe3\xb3\x10\x4b\x80\x4d\x3a\x47\xab\xa0\xa9\x8f\x59\x51\x90\xd3\x84\x4f\x1f\x04\x38\x10\x2f\xfd\x0e\x76\x98\xc0\x96\x53\xfe\x93\x9d\x2f\x24\xae\xb6\x87\x43\x32\xba\x14\x1f\x7b\x32\xf2\xf9\x53\xf4\x99\x35\xce\xff\xc9\x4b\x47\xf8\x10\xb7\x93\x1c\x68\xa2\x81\xc4\x0d\x21\xff\x90\xb7\x89\x1c\x6a\xa4\xa1\xc4\x1d\x1e\xff\x10\x57\x7e\x3a\x87\x83\x3f\x7d\xaa\x56\xa3\xbc\x8a\xe3\xe0\x3f\x87\xa7\x99\x95\xe3\x81\xe7\x37\x69\xbc\xdb\x70\xfd\x66\xe6\x70\x78\xd5\xbe\x51\x90\x7f\x8a\x91\x2b\x00\xe4\xe4\x1b\x1f\x70\x1b\x26\x90\x20\x96\xde\x53\x34\x52\xbf\x28\xff\xe5\x4a\x0e\x3f\xc0\xea\x77\x8e\xfc\x57\x4a\xd4\xe5\x13\x1f\x85\x29\x13\xab\x43\xc5\x81\x45\xba\xd0\x74\x66\x39\x0a\xd1\xbb\xed\x3f\x81\xf6\x41\x0a\x65\xf7\xc4\x36\xd4\x66\x2a\x4f\xb4\x3f\x8e\xb1\x8c\x77\x16\x1a\x17\xf5\x7c\xb3\x31\x83\xb6\xb3\x75\x13\x16\xe8\x1c\xb3\x85\xa0\x03\x5d\xc8\x41\x2c\x19\x22\xcf\x0f\x59\x6a\x6a\x22\xe4\x28\x4d\xc6\x21\xcc\x24\x67\x79\x2a\x99\x81\x29\xf9\xf0\x12\xed\x4e\x4c\xce\x48\x8c\xe3\xd7\x97\x2d\x9b\x1f\x71\x1e\xe4\x08\x3d\xe6\x5e\x2b\x2a\x40\xdf\x59\x8d\x0c\xcc\x2f\x43\x7c\x9a\xbb\x4c\x43\x8e\x61\xcf\x4a\x3d\xd3\x82\xce\xc3\xd2\xfc\xed\x2e\x4d\x9a\xd5\x2d\x4c\x43\x9c\xff\xc5\x96\x28\x5b\x27\xbf\xda\x22\x05\x74\xdc\xcf\x12\xa5\x64\x82\x8b\x5f\x63\x79\xa6\x42\x17\x0a\x2f\x22\xa5\x66\x0d\x96\x25\x9c\x4a\xd8\xfc\x49\x0b\xa3\x12\x31\xa8\x9e\x2c\x44\x12\x9a\x08\xe4\x7d\x2d\x9b\x79\x4d\x09\x76\x6d\x2a\x4a\x90\xae\x40\x03\x28\xfc\x9b\x26\x51\xce\x2c\xcc\x39\x9c\x3c\x63\x22\x1a\x2c\x5a\x59\x5e\xfc\xb8\xba\xe2\xcf\x41\xca\x27\x0f\x51\x6f\xe9\xe4\xc0\xd3\xad\xe3\x8a\xc0\xb9\xec\xc9\xe3\xc7\x48\xfc\xf6\x49\xe4\xac\x49\x37\xd7\x14\xff\x7d\x31\xa4\x2c\x59\x49\x14\x56\x0a\x8b\xde\x45\x33\x68\x36\xcd\x4b\x2e\x27\xd2\x94\xaf\xb4\x49\x28\x95\x32\x57\x1a\x0b\x72\xf1\x90\x8a\xa2\x13\x0e\x36\x44\x42\x30\x96\xe7\x24\x61\xcc\x0a\xb0\xf4\x3c\x6b\x16\x88\x4f\xc7\x84\xab\xa9\x6c\xd2\x3a\x3c\xcf\x8a\x2d\x33\x7b\x2e\x7d\x55\x50\x8d\x71\x82\xb5\xeb\xb4\xd5\x55\x16\xc9\xf0\xe7\x8e\x9a\xb2\x7c\x0b\x16\x5c\x66\xb7\x0c\x57\x37\xaa\x80\xa9\x2f\x98\x3b\x75\x8d\xe1\x1d\xff\x35\x60\x96\x3b\xd3\x64\x56\xe8\x2e\x88\x6f\x6f\x78\x38\x05\x64\xab\x79\xc0\xc1\x8e\x3c\xd4\x70\x1e\x29\xaf\x96\x0b\xe1\xe3\x26\xb9\x84\x38\xa8\xfc\x91\x86\x27\xfe\x22\x83\xd9\x54\xb9\x25\x1e\x2e\x0a\x1a\x0d\xb0\xf5\x1f\x6a\x78\x33\x30\x18\x0e\xd3\x6b\xb9\xcc\x9d\xba\xad\x89\x0b\xcd\x67\x25\x6a\x8c\xdd\xbf\xf6\x2e\x8e\x7b\x4f\xbf\x0d\x9f\x0e\x4f\xbe\xac\xf5\xae\xff\xa1\x4b\x3a\x14\x17\xb4\xa5\xdf\xd0\x2c\x3c\xf6\x9a\x21\x7f\x9d\xc1\x2e\x30\x4c\x38\x9c\x77\xff\xda\xea\x5d\xb4\xbf\xab\x1d\xa7\x49\x7f\xdd\x2e\x8a\xd8\x48\xc5\xd2\x1c\x87\xbc\x77\x3c\x02\xab\xb0\xf3\x9c\x80\x1f\x64\xb6\x1b\x1b\xa3\xb6\xe9\x57\x39\x35\xb3\x9e\x94\x7a\x3b\x63\xf7\x85\xb1\x39\x03\x6c\xf3\xe9\x5b\x2f\x98\xd3\xe5\x9d\x21\xd7\x8c\x85\x49\x82\x8a\x09\x3c\xc2\x0f\xa7\xf0\x04\x9f\xfd\x96\xdf\x81\x19\xe1\xce\xed\xb1\xe1\x22\xcc\xbd\x55\xed\x76\xb9\xbc\x08\xdb\x3b\xc7\x0e\x0e\xa3\x31\x8a\xc2\xa2\x54\x0d\x29\xa4\x17\x2a\xf9\xb6\xc9\x7c\x91\xc5\x56\xd9\x4d\x68\x4a\x3c\x18\xaf\x22\xad\xd7\x90\x7d\x23\x02\x53\x35\x2e\x40\x63\x56\xf5\xf5\x94\x26\x53\xf4\x6b\x53\xb4\xb6\xc2\xf7\xc0\x12\x6f\x33\x69\x43\x5f\x99\x82\x97\x32\x59\x8b\xba\x36\x59\xb5\x54\xb2\x14\x42\x7c\x68\x03\xe3\xaa\x87\x6f\x75\x4f\xdf\xaa\xbb\x61\x11\x82\xb9\xab\xe9\x5d\xcc\x9a\x22\xb0\x83\x33\xe6\xa7\x2c\xf9\xcc\x9b\x9e\x71\x3e\xad\x99\x35\xff\xbd\xa7\x70\x0c\x1d\x97\x69\x43\x89\x3b\x95\xd3\x66\x21\xca\xe0\x12\x0e\xef\xb4\x16\x63\x35\x41\x7b\x70\x6a\x4e\x8e\xe7\x85\x9f\xbb\x14\x4c\xb4\x37\x9b\xb6\xc6\xaf\xc8\x92\x33\xae\x44\xaa\x5d\x04\x9c\x04\xe6\x62\xfa\xb4\x04\x50\x8d\x58\x17\xa3\x9e\x26\xec\x48\x5a\x66\x8e\x8d\x56\xab\x90\x03\xe8\x5e\xfa\x6b\x3f\x7d\x0a\xa6\xbc\x0e\xd8\xbf\x50\xf5\xc1\x44\xb7\xe7\xb5\xa8\xdd\x9e\x6d\xc4\x22\x2b\x79\xe4\x59\x20\xa8\x64\x0f\xe0\x72\x79\x9f\x45\x88\x98\xc0\x05\x79\x1b\xe7\xa3\xc5\x6c\x32\xb1\x30\x6e\xf3\x37\x87\xf2\xee\x79\x0b\x75\x6a\xaf\x63\x70\xdd\x2e\x1f\x8f\xc3\xf7\xf5\x4c\x48\xc6\xe5\x4a\xd7\x9a\x7b\x09\x88\x77\x61\x01\x81\x8a\xc6\xe1\xaa\xe2\x99\x96\x16\x57\x9e\xe4\x7c\x8e\x80\xd7\x7a\x60\x2e\x82\x9c\xdd\x0f\x02\xbb\xd3\x31\x4a\x30\xb8\xda\xd7\x61\xfc\xc1\x9f\x25\xeb\xad\x9c\x06\x06\x15\x93\x11\x81\x27\x59\x2c\x63\x12\x4e\xd9\x74\xf4\x2d\xda\x68\x29\x8b\x38\xa3\xd3\x10\xb2\xaf\xff\xbc\x8d\x5e\x21\xf0\xfa\x29\xb2\xf8\x83\xe9\x23\xd6\x90\xd0\x42\x33\x8a\xd2\x99\xc0\x43\xea\x2b\x7c\xb9\xe1\xa9\xd1\xd4\x6b\x3b\x35\x56\x90\xaa\xc1\xcf\x91\xe5\xeb\xd8\xc3\xd9\x15\x95\x7e\x10\xea\x27\x49\x8d\x15\xa4\x48\xb3\xfb\x24\x45\x79\x6d\xa6\x38\xf6\x0d\x28\xd1\xb8\xac\x52\xd4\xe7\x3a\x82\x54\x0b\x52\x90\x52\x99\x4c\x2d\xfc\xde\x8a\x68\x0d\xa0\xa8\xd4\x7b\x56\x51\x0d\x19\xdb\xb2\x82\x97\xa4\x41\xa0\x1b\x6b\x53\x72\x46\xa4\xdf\xb2\x1d\x17\xe8\x38\x45\x93\xf0\x33\x3c\x0b\x54\x14\x6e\x11\xf0\x3c\xfa\xb5\x29\xc8\xed\xff\xf2\x46\x25\x71\xaf\xd7\x9b\xde\xd5\xd6\x54\x4b\x9a\x4e\xc9\x45\xe5\x8f\xb3\x30\x21\x31\x5a\xe9\xf1\x78\x52\x4f\x85\xf0\xc5\xdb\xe4\x2f\xe2\x7a\x17\x88\xdb\xd9\x95\x8d\x85\xe7\x52\xf2\x0d\xf7\x49\x55\xe5\x3c\xfa\xad\x12\x37\x3c\x02\x45\x28\xf1\x61\xec\x37\xfc\x24\x51\x77\x88\xf0\x54\x24\x6e\xdd\x7d\xf5\xd9\x10\xe2\x21\x47\x0b\xee\xb2\x55\x8d\xfa\xfd\x6e\x80\xd6\x7a\xfe\xdb\x49\x19\xa4\x5a\x6a\x0a\x0b\x7d\x82\xf7\x45\xab\x76\x44\x79\x9f\x96\xcc\x77\xe7\xae\xf6\x31\xa4\xe5\x74\x8f\x7e\x0b\xb5\x78\x37\x9c\x33\xaf\xec\x5b\x5a\xd0\x30\x8d\x18\x17\xd1\x85\xaf\xae\x14\xd2\x44\x61\x78\x2f\x0d\xbf\xac\xa0\x2a\xde\x54\x6e\xa6\x06\xbc\x48\x55\xd9\x5c\x14\x51\x52\xd7\x37\x0f\x4b\x07\x25\x3b\xcf\x05\x51\xa4\xbc\x36\xcf\xc1\x8f\x60\xad\xa2\x67\x30\x1a\xde\xb7\xe6\xfd\xa1\x87\xf7\x65\x61\xdc\xc8\x1e\xcf\xc7\xce\x5b\x6d\x16\x76\x53\xfc\xc8\xa2\xb7\xc2\x90\x2c\xd1\x5c\x70\x44\xac\x4f\x74\xfe\x70\x78\xd7\x6f\x31\x98\x3d\xd1\xb7\x05\x17\x83\x14\x81\xd9\xc4\x3d\x6a\xc1\xb3\x9f\x0e\x29\xf8\xf3\x1f\x51\x0e\x94\xb3\x9e\x91\x0b\x33\x91\x05\xc7\x7d\xca\xbb\xba\x00\x33\xe0\x80\xb7\x61\x05\x3c\xe9\x56\xd3\x28\xba\xb7\xe8\x68\x20\x38\xe4\xfc\xb1\x00\x1e\x6f\x31\x12\x28\x37\x7f\x1c\xde\xe9\xaa\x1d\x81\x7b\x14\xe1\x7b\xe8\x4f\xdc\x2b\xa7\xa0\x38\x7b\x08\x7f\x2c\xec\x35\xa5\xcd\x9e\x6a\x47\xc0\x8a\xb9\x2f\xd0\x54\x48\xad\xfc\xb2\xbc\x47\x3d\x7a\x04\x16\x11\xdc\xe9\x83\x7e\xd9\x70\x8d\xa2\x90\x46\x63\xd4\xc2\xf3\xb4\x18\x37\x18\x75\x28\xc6\xbd\xcd\xa6\x0f\xcf\x26\xe8\x34\xc9\xa2\xcf\x68\x8c\xc3\x18\xe7\xec\x23\x9b\xb8\xa6\x62\xa4\x78\xcd\x92\xbd\x8a\x0c\x7c\xa1\xe2\xe5\x43\x59\x34\x24\x09\x75\x2f\x69\x3c\xe4\x0a\xb0\xd6\x61\x9a\xa5\xd4\x6a\x30\x9f\xf5\x57\xb4\x0a\x93\x83\xcf\xc1\x4b\xb5\x02\x92\xd7\x65\x56\xe5\xd3\x3f\x96\xa1\x3c\x0d\xba\xe1\x0c\xec\x7e\xdc\x62\x66\x92\x8c\x89\x78\x53\x12\x95\xe7\xe1\x90\x25\xcf\x9b\x07\xaa\x80\x6a\x26\x00\x6a\xb2\x26\x00\x8a\xd5\x4e\xc0\xf3\x35\x8d\x7f\x0e\x7d\x6b\xfc\x43\x55\x66\x4d\x3e\xf4\x97\x80\x6e\x85\xfd\x0a\x4f\xc9\x42\xdc\x1b\xc8\x1f\x81\x4c\x85\x07\xa4\x03\xf5\x8b\xa7\x83\x50\x38\xe0\x7f\x64\x8a\xb0\x5a\x1b\xe8\x9f\x3c\xc7\xb0\x68\x1b\x98\x1f\xb2\xdc\x11\x1d\xbe\x18\x88\xbf\x32\x0d\x1e\x96\x0c\xe4\x0f\x5d\x0f\x87\x95\xbf\x74\xba\x80\x57\x3f\x45\x3d\xe5\xb7\x0f\x03\x5f\x22\x87\x2e\x9b\xd4\x0f\x3c\x69\x16\xac\x34\xe6\x1e\xb8\x09\x72\x1c\x3f\x64\xe9\x80\xff\xd1\x7d\x85\x34\xf1\x43\xc2\x29\x49\x71\x60\x7e\xc8\x5c\xfb\x2a\x6e\x50\x4a\x51\x58\x13\x9e\x4d\xf5\x4f\x9e\x63\x48\xc6\x03\xf3\x43\xe6\x5a\xa7\x90\x81\x9b\x20\xa1\x8c\x7c\x27\xc7\x39\xb6\x0f\xca\x49\xb2\x87\x25\xc8\x52\x92\xea\xb1\xa9\xb3\x1b\x38\xdf\x0a\x6d\x16\x90\xf3\x2d\xdd\x65\x97\x94\x96\x03\x4f\x9a\x6c\x56\xca\x7f\x03\xe3\xb7\x89\xa6\x74\x34\x50\xbf\x64\x3a\xdf\xc8\x07\xea\x97\x1a\x02\x67\x33\x03\xfd\x53\xa1\x92\x6d\xd2\x03\xf9\x43\xa6\xb2\x7d\x72\x20\xfe\xaa\x3a\x18\x9b\x1d\xc8\x1f\x32\x15\xb8\xd5\x40\xfe\xe0\xbe\x6b\xb9\x97\x36\xdb\xed\x48\x63\xb0\xd2\x0b\x1a\xca\xa1\x49\x63\xb0\xd2\x0f\x6a\xbd\x83\x07\x8d\x19\x1d\xbe\x68\x0c\x5e\xac\x5d\x9f\x04\x2b\xab\x8b\x38\x3b\xb2\xf9\xc9\x06\xe7\x26\x0d\xe1\xe3\xa7\x31\x40\x8d\x5e\xa7\xdf\xe9\x35\x74\xb4\x90\x95\xb5\x07\x1f\x4a\x0f\x3e\x94\xfe\x5e\x7c\x28\x89\x5a\x96\xca\x61\x32\xfe\x88\x87\xc3\x1c\x5f\xa2\x1f\x49\x12\x7d\xc6\xe8\xe5\x4f\x78\x38\x74\x1d\x29\x2d\x18\x4c\x03\xc0\x48\x98\xa2\xbd\x30\x8d\x70\x08\x50\x24\x4c\xcb\x60\x6f\xc3\x53\x06\xf6\x43\x36\xc2\x49\x41\x71\x92\xe0\x1c\xbd\x1c\x42\x62\x19\xf8\xfb\xf0\x0c\xfd\x98\x65\x31\x7a\x39\xaa\x74\xf0\xb4\xa6\x1d\xdb\x89\x30\x39\x1f\xc2\x34\x1c\x61\xc7\x1b\x12\xc3\x42\x37\xe7\x00\x13\x0e\x20\x3d\x28\xed\x9c\xc2\x31\xcd\x05\x26\xa7\x61\x2a\x41\xb6\x21\xfe\xb9\x0b\xc1\xa5\xc0\xa2\x8b\xe9\x58\x02\xbe\x79\x5d\x03\x17\x9f\x32\xb0\x6e\x17\x74\xb5\x07\xe3\xba\x2a\x8b\xb1\xaa\x72\x17\x2e\x5b\xab\x00\x53\x4c\x25\xe0\x47\xf0\xd0\x0f\x6f\x6e\xaa\xa0\xa7\x02\xc4\xec\xc7\x79\x98\x4f\xea\x7a\xc2\xf2\x65\x13\x92\x61\x58\xe0\x92\x13\xb0\x83\x9b\xf6\x58\x45\x92\xc2\x06\x83\x24\xfe\xdf\x6a\xc7\x57\x1c\x48\xba\x18\x14\xaf\xd6\x70\x1a\x7b\x3a\xc8\x33\x24\xd8\x6b\x38\x98\x95\xa1\x4e\x59\xba\xc2\x50\x9e\x4d\x71\x4e\x2f\x3d\x70\x53\x91\x25\x41\xdf\x51\x3a\xfd\x98\x67\x67\x24\xf6\x52\x12\x5b\x83\x53\x91\xad\xe8\x68\x1a\xd5\x94\x20\xd3\xc8\x2d\xb0\x58\x1c\x17\x75\x20\xf8\x11\x9f\xae\xa2\x96\xac\xc4\x0e\x09\x9d\xbb\xa4\x9f\xe2\x73\x67\x3d\xe8\x92\xeb\xba\x60\x34\xcb\x73\x9c\x52\xa3\xdf\x12\xca\x00\x9a\x26\x21\x85\x49\x87\x97\x07\x74\xdc\x62\xa9\x66\x2d\xf1\xa9\xc8\x7c\xf3\xda\xcc\x83\x0b\x31\x52\x74\x0a\x20\x75\x08\x1f\x3d\x2e\x17\x4e\xf9\xe3\x41\x7c\xce\x28\xbd\x94\x3b\xd5\x44\xcd\x40\x24\x8d\xfb\x9a\x39\xfd\xf9\x67\xd9\x0c\xa3\xd9\x52\x55\x9a\x78\xf5\xa1\x25\x9c\x92\x81\xda\xca\xc4\x5f\x7e\x78\x31\xfb\x20\x50\x52\x58\x45\x4d\x0a\x19\x58\x5f\x81\x82\x31\x48\x62\x60\x7e\x94\x9a\xf8\xa4\xc8\x9c\xff\x28\x75\x5e\x00\xb4\x74\xfb\x82\x62\x09\x2e\x06\xc6\xef\x16\x37\xf5\xb8\x56\x8e\x7e\x99\x40\x53\x60\xa4\xc7\x20\x82\x89\x80\x2f\xbb\x25\x46\x53\x9e\xf1\xcd\x1b\x5b\xe5\xb8\x98\x1c\x28\xeb\x14\x61\xbc\x0b\x6c\x92\x97\x3e\xde\x2e\x42\xc8\x66\xe1\x1b\xd3\xaf\xa7\x2f\x39\x2e\xb0\xad\x8e\xfc\x8c\xf1\x74\xa7\x38\xb8\x4c\x23\x15\x38\xb2\xa2\x2b\x50\xd6\x81\x37\x7a\x52\x60\x4a\x49\x3a\x2a\x24\x09\x8a\x4f\xf9\x3c\xd3\xe9\x88\xb9\xf4\x5f\x6b\x0b\x3f\x07\x4a\xbe\xe2\xe3\x6e\x10\xe1\xcb\x03\x23\x7d\x20\x48\x28\xf8\xf6\xc0\x09\xcf\x1e\x12\x8c\x7d\x96\xa0\x4c\x9f\x0a\x1c\x50\xa5\x78\x61\xad\x3a\x65\x82\xa7\x6d\xfd\x1c\x54\x36\x2f\x52\xbc\xb5\xba\xd0\x46\x9a\xa7\x6e\x13\x97\xb2\xf6\x3a\x9c\x72\x23\x70\x09\xf9\x43\x96\x7a\xfb\x60\x42\x89\xef\x12\x9c\x69\xfc\xc7\x21\x55\x8a\x07\xb6\x7c\xb7\x2c\xcb\x6c\xb9\x77\x8e\xa5\x3e\x57\x95\x2d\xe5\x78\xda\x75\xed\x65\x64\xab\x56\xba\x0f\xaf\xde\x72\x4e\x7a\xa9\x9c\xdf\x46\x87\x97\xf5\xe4\x95\xca\x6b\xf7\x6d\xbc\x8c\xf8\xf6\xc1\x49\xcf\x62\x0a\x10\x12\xd6\x97\xca\x2c\x88\xcb\x19\xa6\x93\x10\x7e\xc9\x65\xfb\x27\xb6\xaf\x1f\xe1\x8e\xdd\x85\xac\xba\x0e\x8e\x49\x0e\xca\xf8\x28\x9c\xc2\x4b\x36\xe3\x42\xd6\x43\x5b\x3b\x5b\x9b\x1f\x2d\x2e\xc4\xca\x99\xd6\xcc\x84\x8b\xa4\x8c\x91\x30\xe9\x94\xe7\x5b\xcf\xe9\x19\x44\x47\x34\x23\x79\x0c\x38\x5c\x53\x3b\x81\xd5\x80\x33\xc4\x63\xb5\x8b\xc0\xa6\x2a\x64\x9f\x96\x7d\x87\x9e\x82\x5e\xa9\x29\x77\xc7\x34\x8b\x71\x33\xb0\x20\x46\x60\xe6\x32\x40\x4d\x26\xe1\x7c\x8a\x12\x82\x53\xfa\x03\x07\x6f\xea\x3b\xf6\x76\x70\x9b\xd6\x30\x3d\xcf\xf2\xcf\x55\x0d\xa6\x98\x7e\x12\xa0\x0e\x08\xd8\xbb\xbf\x15\x57\xe3\xf9\xc0\xe5\x37\x77\xec\x16\x17\x85\xaa\x7a\xc5\x73\x3f\xc1\x64\x47\x59\xf2\xc3\x1d\x3b\x68\x9a\x61\x54\x74\xb1\xdb\xf5\xf5\xf2\x7c\x4c\x8a\x29\xce\x8d\x86\x05\x9c\xea\x69\x31\x1e\x7b\xf0\x27\xa0\x16\xed\x61\xb7\x2b\x3b\x79\xe2\xdd\xe4\x48\xb1\x95\xa5\x29\x77\x71\x68\xac\xbe\xb6\x45\x8a\x2d\xef\x3e\xfe\xf8\xb1\x77\x7f\x37\xab\x6c\xb5\xfd\x5b\x2b\xf7\x87\x23\xcf\x03\x95\x4b\x00\xec\xfa\x19\x90\x14\xb5\xbc\xaa\x69\x56\xb7\x74\xcf\x6d\x9e\x4e\xb8\x22\xcb\x3a\xd4\x34\x06\x2b\x2b\x2c\xc9\x3c\x0e\x35\x06\x2b\xab\x2c\x4d\x1f\x45\x1a\x83\x95\xe7\x2a\x85\xcb\x74\x8d\xc1\x6a\x4f\x25\x99\x07\x8b\xc6\x60\x75\x4d\x65\xb0\x05\xdf\x18\xac\x3e\xd3\x09\xfa\x40\xd1\x18\xac\xea\x4a\xf5\x69\xb3\x31\x58\xfd\xb6\x94\x8c\xe9\xb8\x31\x58\xeb\x95\xd2\x53\x4c\x1b\x83\xb5\x7e\x29\x5d\x0a\xe0\x8d\xc1\xda\x8a\xca\x94\xa7\xa5\xc6\x60\x4d\x77\xc8\x3e\x5d\x37\x06\x6b\xcf\xeb\x55\x7d\xd7\x27\xc1\xca\xb3\x07\x4d\xdc\x83\x26\xee\xef\x45\x13\x17\x26\x09\xb8\xec\xb9\x9b\x47\x73\x43\xe1\x55\xd2\x9f\xf8\x14\x28\x07\x59\x42\x62\x42\x2f\xb7\xcf\xf8\x2b\x2a\x43\x31\x01\xbd\x91\x70\xd2\x9c\x2c\x77\xf4\x36\x3a\xbd\x5a\xb9\xe3\xd5\xee\xbc\x85\x8b\x67\xa7\x32\x48\x93\x10\xe7\x8c\x23\x62\xa7\x3d\xc9\x78\x44\xa6\x72\x5c\xbe\x99\x24\xd6\x50\x6c\x01\xc7\x3e\xac\x05\x60\x45\x50\xb2\xb0\xaf\xd0\x95\xd8\x09\xe6\xa1\xfb\x27\x6e\x38\xc0\xfe\x98\xc9\x25\x33\x4e\xbe\x7b\x94\x7a\x68\xec\x4a\x22\xde\x8d\x65\x45\x61\x4b\x9f\x32\x32\xc6\x86\xfa\x75\x75\x05\xa1\xc2\x91\xeb\x08\x87\x25\x42\xea\x71\x93\x49\x93\x10\xa4\xb5\x19\xa0\x26\xcd\xf8\xcf\x93\x0e\x47\x73\x4b\xb7\x35\xf4\xdc\xd4\x8a\x66\x8e\x87\x27\x60\x78\xa3\xec\x56\xc5\xed\x6d\xbb\x33\xcc\xf2\xed\x30\x1a\x57\x57\xc3\xfa\xc3\x8a\x6f\x18\xc4\xc3\xbd\xac\x41\x47\xf8\x09\x4c\x09\x12\x2d\xdd\xa0\xb4\xa7\xd0\x8f\x65\xc1\x39\x8f\xc6\xab\x85\x67\xfb\x45\xad\xe9\xae\xa7\x0e\xe3\x22\x2c\x92\x89\x71\x08\x44\x2f\x46\x00\xd1\xe2\x44\x9c\x7d\xfd\xfb\xea\x8a\x1b\xeb\x2a\xa4\xc3\x12\x91\x77\xf3\x2d\x75\xc8\x03\x1b\x17\x28\x06\x17\xe1\x05\x98\x25\xc9\x5c\xf1\x30\xd3\xc8\x3e\xee\x9d\xb4\xdb\xe8\x3b\x64\x27\x89\x60\x75\x2b\xe0\x2f\xc4\x70\x16\x32\x11\xb2\x8b\xa6\xc1\xf2\x84\xfe\xe4\x99\x50\xb3\xaf\x1b\xc2\x48\x58\xc8\x6e\xe5\x2b\xe7\xd6\x4f\xca\xab\x47\xfb\xb8\x77\x62\x3c\x87\x7d\x04\xed\xb7\xd1\x17\x78\x82\x16\xa6\x69\x46\xd1\x90\xa4\x31\xef\x17\x49\x47\xbc\xa1\xef\xdc\xe6\x0d\x1a\x10\x71\x4f\xb2\x91\x9e\x7c\xc0\x7d\xc9\x45\x1f\x16\x4c\x09\x54\x26\xe6\x4c\xb6\x7c\x6b\x35\xe0\x5d\x08\x6c\xf2\xb0\x0f\x46\x50\xa3\x7c\x8f\x24\x1a\x9d\xbb\x38\x2f\x70\xc4\xe4\x01\xcf\xea\x0c\xc0\x0d\xdc\x69\x18\x7d\x66\x08\x57\x38\x92\x53\x2d\x6f\x68\x5b\x61\x3e\x82\x90\xc0\xc5\xb1\xfa\x25\x5f\x60\x3d\x45\x7d\xdb\x54\x5e\xd6\x08\x81\xce\x6a\x8b\x99\x2f\x45\x5a\x25\x88\x0d\x23\xfc\x99\xf8\xa7\x99\x88\x19\x43\x53\xa1\x3b\x93\x54\x25\x9c\xb6\xea\x63\xad\x84\x50\x93\x28\x21\x39\x26\x3b\xa7\xc4\x56\x4a\x1a\x52\x34\xe7\xf3\xad\x2c\x40\x4d\x4c\xc7\xcd\xc0\xcb\x66\x03\xc9\xec\x3b\x98\x8e\x5b\xed\x40\x37\x64\x60\x78\xfe\x4c\x85\x94\x86\xd1\xf8\x30\xdb\x92\x0e\xf8\xcc\x29\x93\x5e\xf9\xcc\x93\xb4\x9e\x5a\x3e\x6e\xfe\x59\x1a\x8e\x2c\xda\x09\x93\x44\xed\x2e\x02\xb8\xe2\x68\x50\xea\xa6\x3a\x27\x78\x0e\x0a\xde\x93\x02\x90\x6a\x63\xb0\x02\x42\x3a\x5f\xe1\x8d\xc1\x2a\x88\xe0\x7a\x2d\x35\x06\xab\x00\xec\x6c\x8b\x8d\xc1\xda\x2a\x13\xa0\x9f\x3f\x08\xd0\x0f\x02\xf4\xef\x5b\x80\x36\xc2\xa0\xc1\x11\xfa\xbe\xe2\xa0\xfd\xb1\xc8\xd2\x7c\x1a\xd9\xd2\xe7\x4f\x3c\x51\xdd\x3e\xe6\x79\xe6\x0a\xc4\x3c\x4d\xc9\xa5\x65\x4d\x03\x1b\xac\x25\x72\x96\x24\x4e\x40\xc7\xa7\x4a\xb9\x53\x64\x14\x22\x60\xf3\x1c\xef\xa4\x61\x1c\x4b\x27\xc0\x8c\x1d\x8b\xc2\xf0\x28\x16\xba\x26\x12\x1c\xdb\xc5\x30\x8e\x3d\xb6\xc0\x48\x8c\x9f\x17\xa2\xda\x0e\xb7\xbb\x04\xe3\x34\x59\x71\x1c\xfb\x24\x70\xdf\xc0\x0b\x1e\x84\x54\x42\xcc\x1d\x91\x64\xda\x75\xfd\x17\x30\xde\xae\xf9\xf6\x73\xdb\xbf\x8b\xc2\xaf\xd5\xcd\xf2\x14\xe8\x7b\xa8\x34\xe6\xda\x22\x09\x1b\x18\x75\xe3\x3c\x0f\x84\x5c\x5a\x94\x25\x64\x4e\x3e\xfc\xd7\xd5\x95\x08\xc1\x2d\x01\x04\x7e\x4c\x9f\xc4\x0a\x95\x01\x02\xff\x22\x15\xaf\xec\x44\x95\xc7\x00\x73\x62\x1e\x16\x2a\xc5\x77\x5e\xa4\xa2\x5a\xfe\x50\x9b\xf7\x47\x54\xda\x29\xc9\x3e\x6e\x81\x47\x62\x11\x75\x48\xf1\x43\x98\x90\x78\x1f\x17\xd3\x2c\x2d\xb0\x68\xaa\xf4\x42\xb0\x34\x06\x7f\x7b\x2d\xbe\xc6\x3a\x3b\xe9\x99\xb7\xd6\xf5\x52\xa5\xd7\xe5\xfe\x55\x56\xce\xbd\xda\x95\x06\xcb\xf7\x5c\xf0\xce\xe3\xcb\x10\x8d\x77\x44\x1f\xc0\x6f\x8f\x99\xd0\xae\xf4\x0d\xaf\x4f\x3b\xc8\x2f\x4a\x00\x65\x69\x35\x23\x1f\x7c\x63\xb0\xf2\x82\x6d\xfc\x62\x45\x36\x06\xab\x10\xf7\xf4\x9b\x87\x0d\xff\x61\xc3\xff\x7d\x6f\xf8\x7a\xbf\x57\x62\xf9\x3d\x29\xcc\x16\xd4\x5c\xb1\x13\x4f\xee\x80\x15\x42\xd6\xef\x42\xe6\x4d\x95\x6b\x12\xce\x78\xdd\x65\x6a\xc5\xe4\xb3\x2d\x01\xbd\x69\x1c\x42\x0c\x30\xa5\x3f\x04\xd9\xa3\x6e\xff\xe4\xca\x26\xf3\x29\x1c\x6c\xf3\xee\x63\x6a\x0e\xb7\xa5\xc1\x3e\x4a\x28\x25\x17\x80\xa6\xe3\x1a\x49\xe7\xff\x76\xaa\xb7\x01\xe1\x6c\xdc\x5f\xb5\xfd\x20\xf5\x94\x04\xa2\x9c\x73\x39\x13\x9e\x12\x8f\xca\xc3\x08\x14\x80\x1c\xcf\xfc\xe0\x21\x1d\xde\x4e\x19\x7d\x6a\xa2\xc7\x8f\x2d\xcf\xff\xc6\xb9\x99\x37\x6b\xc5\xb8\xb9\x6e\x3b\xdb\x70\x5d\x83\x9e\x58\x02\xa8\x05\x89\x15\xdb\x35\xe4\xf1\x10\x03\x9e\x9d\xc1\xa9\x88\x81\x15\x9e\xa6\x81\xf6\xc4\xe1\x9d\x23\x94\x83\x2a\x34\x1a\x1a\x1d\xa9\xcb\x5a\x40\x7a\x54\x81\x2e\x85\xab\x3e\x7e\xb4\xf6\xbe\xc1\x0a\xe3\x58\xd2\x70\xa1\x8f\xe1\x26\x6d\xc8\xb4\x6b\x55\x53\x25\x3d\x71\x52\xf1\x57\x59\x7b\xb2\xd7\xc7\xf5\xdb\x13\x8a\xf1\x56\xf2\x26\xb3\x6f\x6a\xaa\x94\xf6\x47\xf5\xe7\x13\xa6\x63\xa9\x7c\xd6\x9d\xb4\x9d\xad\xcc\x55\xac\x0e\x4b\x6a\x0e\x8d\x00\xad\x38\x5d\x60\x5e\x39\xb7\x58\x68\x52\x39\xbf\xb9\xbf\x19\x75\xeb\x9b\xaf\xa8\x91\x0c\xef\x3e\xe6\x96\xf3\x5e\x47\xc9\x2c\x38\xab\xd0\x36\x2a\x1e\x6b\x4f\x5e\x59\x6f\xc5\x3b\x56\x39\x9d\x9b\x49\x52\x3b\x5d\x00\x24\xae\x7d\x6e\x4c\x60\x5c\x15\x3a\xa7\x83\x37\xa7\x36\xeb\xf9\x62\x59\xa9\x56\x45\x6d\x75\xe4\xa6\x5c\x95\x81\x6d\xa1\x9c\xf4\x11\xa6\x85\x30\x49\x49\x2e\x51\x8c\xa7\x49\x76\x89\x63\x69\x82\x08\x0f\x1d\xa3\x71\x48\x52\xf7\x61\x1d\xd4\xf6\x36\xcb\x65\x8f\x3c\x1e\x12\xe4\x81\xd5\x47\x92\x72\x5d\x5e\x2b\xd5\xe2\x92\xe5\x42\xfc\x48\x5e\x75\xe8\x07\x78\x15\x2d\x9a\x76\x0b\xb2\x25\x43\x61\xa9\xc9\x37\xca\x66\x20\x00\xf4\x82\x92\x9a\xf7\x2d\xc9\xb9\x01\x04\x7f\x02\x07\xf9\xdd\x2e\x3a\x0f\x09\x05\xa9\x0c\x44\xae\x29\xd5\x2a\x58\x79\x6f\x66\xcf\xbb\x58\x0a\x4d\x26\xc2\x16\xb4\xa9\xcf\x9f\x2d\xdc\xb6\x1d\xd3\x3f\xc2\xf0\x80\xda\x6a\xdf\xdd\x95\xa0\xbf\xcb\xcb\xeb\xf6\xb1\xa9\xdb\x45\x05\xcd\xa6\x5c\x57\x4b\xd2\x11\x0a\x87\xac\x2b\xcf\x7a\x7c\xae\x0a\xd4\xa2\x64\x82\xb3\x19\x6d\x97\x8e\x8e\x1c\x01\xaf\xd0\xb3\x9e\xf7\xb0\xc8\x7b\xdf\x61\xb5\xff\x28\x2a\xd7\x41\x78\xda\xe8\xcb\xb5\xe7\x4c\xe7\x22\x90\x3f\x2d\xf4\x9e\x43\xd5\x8c\x78\x4f\x9b\xfa\xe4\xa7\x5d\xa7\x2b\xc6\x04\x77\x23\x21\x5f\x19\x11\x23\x6c\x70\xa3\x7e\xca\x24\xe6\x59\x1a\xbb\x18\x68\xfa\x0e\x9f\x38\x31\xdc\x9e\x98\xff\x4a\x0e\xdb\x6f\xdd\xaa\xbc\x93\x92\xff\x94\xd7\x93\x32\xd6\x2c\xaa\x19\x61\x7a\xa8\x9b\xda\xe7\xa4\xa6\x39\x8a\xd1\x8d\x77\x61\x31\x36\x89\x2a\x90\x84\xd9\xf6\x1f\xe1\xc9\xb0\x25\x00\x18\xb5\x89\x9f\x1d\xe8\x2f\x78\xa9\xf1\x92\xa0\xb7\x26\x6f\xaf\x21\x9a\x9f\xac\x35\xb2\x57\xa5\xdd\x4d\x08\xf9\xe7\xef\xa3\xfc\x57\x15\xf1\xe2\xb9\x8a\x78\x51\xf5\x8f\x0c\x5b\x36\x19\x5e\x5d\xa1\x47\xd0\x62\x6d\x31\xa4\xf8\xb9\x87\x60\xcd\x7f\xb7\x59\x17\xe6\xbf\x05\xd7\x88\x3b\xa4\x2c\xc6\x52\x1a\x7e\x85\x56\x6b\x67\x46\xfe\xeb\x76\x41\xde\xcf\x12\xdc\x49\xb2\x91\x41\xca\x70\xd6\x90\xbc\xfd\x51\xb3\x3d\xa7\x79\x64\xc9\x6f\x8a\xcd\x56\x4c\xf5\x02\x95\x75\xbb\x88\xef\x60\x52\x86\x08\xd3\x18\x89\xeb\x12\x14\x8e\x42\x92\x8a\xe5\x74\x8e\x45\xb0\xdb\x39\xff\xfc\x02\xa9\xbb\x2b\xce\xa9\xc1\x15\x80\x4a\x32\xc1\x9c\x21\x45\xdc\x57\xa6\xb8\x29\x64\xfb\x04\xdb\x32\x0b\x1c\x65\x69\x8c\x18\x17\x9e\x5b\x89\x41\xba\xf3\x89\x15\x59\x6c\x12\x14\x64\x8b\x76\xb8\x32\x62\xe2\x3d\x76\xc8\x0c\x79\x61\xc8\x17\xe2\x98\x6b\xb0\xcf\x82\x66\x39\x8e\x55\xf8\x0b\x2e\x96\x80\x1a\x68\x14\x16\x28\x9c\xb0\x5d\xaa\xe3\x65\xe2\xee\xbf\x4a\xa6\xee\xfe\xf3\x44\xe5\xb8\x8f\x2e\xd6\xf7\xf0\xba\x32\xb7\x8a\x63\x94\x4b\xb8\x90\x86\xca\x72\xd1\x53\x86\x71\xef\x20\x09\xfd\x6d\xc8\xce\xde\x97\xca\xb3\x91\x23\xda\x39\x60\x73\x4e\x12\x6e\xa5\xe6\x29\x02\x8e\x5a\xe1\x29\xb1\x6e\x1c\x30\xbc\xd4\xc3\xf2\x4c\x0f\x69\xe1\x29\x41\x1b\x0c\x52\x0a\x5f\x4f\xb8\x7a\x74\xfe\x98\xf4\xb1\x29\x45\xa1\x21\xaf\x8a\xe2\xb2\x16\x39\xb6\x14\x9f\xab\x24\x39\xa6\xf4\xf2\xda\x24\x06\x47\x61\xd2\x97\x86\x06\x61\xd2\xbe\x61\xd1\x95\x9a\xa2\xae\x70\x6c\x89\x1b\xc2\x71\x8c\xd4\x1e\xc7\xa5\xd2\x3e\xf1\x59\x40\x28\x11\x5a\x9f\x09\x65\x92\x6a\x4b\x4f\x3c\x14\xea\xea\x89\x60\x28\xf5\xdd\xc9\xa0\x9a\x2d\xfa\x1b\x6b\x8b\xe6\xa2\xb6\x3a\x52\x9a\x74\xd7\x92\xf3\x1f\xa8\x59\x37\x39\x57\xb7\xcb\xc3\x0b\x6b\x93\x0b\xab\x52\x6d\x38\xf1\xe5\x7a\x9d\x01\x4b\xec\x3c\xb2\xdb\x16\x08\x59\xb7\xbb\x14\xe6\xf0\xf0\x87\xbb\xc0\x31\xde\x1d\x24\x24\xc2\x70\xef\xa0\x8d\x37\x1c\xef\x9f\x3e\x43\x12\x30\x06\xa9\x36\x1f\x41\x25\x13\x92\xa2\x33\xcd\xa6\x96\x4f\x3c\xbb\x7b\x49\x58\x50\x01\x59\xaa\xda\xdf\x1d\x4e\x40\x2d\x56\x10\x4c\x9b\x1e\xd9\x96\x4f\x90\xee\xf6\x49\xa3\x70\x4e\x97\x58\x43\x02\xb8\xc3\x43\x38\xbd\x2a\x47\xf0\x15\x33\x2d\x69\x7e\x53\xae\xe1\xf9\x2a\x01\xf9\xef\x4e\xfa\x41\x03\x4d\x0e\x93\x94\xba\x35\x65\x85\x67\x0e\xeb\x91\xdd\xe5\xce\x34\xbc\x0c\x4f\x13\xec\xeb\x5e\xf9\x6c\xc0\x4d\xad\x0a\x9c\xc6\x3a\x7a\x5f\x9a\xa5\x4f\x45\x25\x26\x3a\xdc\xed\xe1\xba\x6a\xea\xc1\x1d\x21\xe3\x88\x7e\x7d\x6c\x4b\x2e\x95\x00\x46\x6c\xd4\x2a\x27\x08\xcc\xe2\x96\x37\x78\x45\xeb\xf6\x24\x56\x5e\x0f\x9a\xc7\x5b\x4b\x21\x20\xe5\x3d\x88\x29\x63\xab\x0c\x62\x8c\xce\xc3\x42\x09\x92\x4b\x36\xae\xf8\xd2\x86\x7b\x58\xe3\x3c\xa3\xad\xb4\x9c\xcb\xd8\x71\x58\x8c\x7d\x48\x67\xbd\xc6\x79\x5e\x75\x2d\x69\xde\x3f\xfa\x2e\x19\xeb\x24\x1d\x26\x86\xc6\x31\xbf\x03\x33\xb8\x2d\xeb\x89\xbf\xad\x8a\x33\x18\xda\x80\x32\x15\x42\x57\xa5\x64\x38\x24\x79\x41\xab\x05\xc3\x1b\xca\x76\x15\xea\x10\x9f\x0e\xc4\x77\x17\x6b\x7d\xcd\xf3\xd5\x09\x2e\x97\xf9\xc0\xe7\xcd\xb3\xd3\xd8\xa2\x28\x9f\x8b\xea\x9b\x0c\xdd\xcf\xd3\x94\x82\x9e\x03\x09\xc5\x99\xc0\xae\xdc\xfc\xd8\xb9\xf6\x0d\x37\x39\x85\x24\xf1\x69\x59\xa3\xdd\x5a\xe0\x72\x55\x68\xf3\x54\xb5\x9e\xcd\x5c\xd4\x74\x6d\xcb\x3e\x43\x55\x7f\x77\xa9\xdb\x75\x76\x60\xeb\x36\x47\x3b\x69\x36\x74\x99\x4e\xe5\x2d\xbe\x2f\x77\xbb\x96\xf7\xdf\xaa\xa8\xbb\x61\x14\x81\x23\xdf\x8c\xc7\xb5\x23\xe9\xa8\x46\x26\xb3\x75\xda\xf6\xc8\xf9\x24\x5e\x97\x39\x91\x29\x06\xd5\x89\x40\xe8\x8b\x21\x6d\x2d\x2a\x09\x0d\x51\x9a\xe9\x1a\x18\x7b\x9b\x86\x45\x81\xe3\x80\x55\xa1\xfd\xf5\x31\x88\xc2\x58\xd2\x36\x2f\x53\x84\x07\x33\xe0\xa0\xd3\xb2\x8d\xf4\xb9\x7b\xb5\x0d\x58\xd1\x8d\x65\x28\xd3\x57\xbc\x54\x5d\xd2\xb1\xe1\x0d\x13\x62\x7e\x41\x3c\x45\x69\x61\xa0\xba\x14\xc8\x02\xa7\x38\x0a\x67\x05\x66\x27\xf0\x38\x4b\x29\x3a\x0f\x53\x30\x50\x2a\xa6\x19\x49\xf8\xd5\x78\x4a\x71\x3e\x0c\x23\xe5\xce\x7b\x81\x13\xf8\x22\xa7\x6c\x77\x9b\x9a\xcf\x0f\x51\xc9\x23\xb0\x5a\xd3\xc6\xda\xfc\x1e\x53\xee\x5f\x9a\xed\x8f\x01\x3a\x1f\x93\x68\x0c\x16\x04\x6c\x79\xd3\x4c\x6c\x63\x68\x9a\xcc\x8a\xf9\xf7\xb0\x82\x0f\xcc\x99\x5f\xcd\x3c\xfc\x56\x4d\x73\x44\xd8\x9b\xcb\xa9\xaa\xd8\x7c\xf9\xf1\x2e\xb2\x63\xb5\xdc\x68\x58\x2e\xdf\x4a\x8e\xa9\x93\x61\xec\x47\x10\x00\x72\x13\xe6\xeb\x39\xed\x78\x8f\xb9\x0b\xdc\xa5\x57\xb1\xa6\xd2\x21\xd8\x7b\xfa\xad\x78\xe4\xe2\x3b\x07\x97\x77\x3f\x6d\x24\xce\xf1\x57\x7e\xd7\x82\x3c\x87\x68\xaf\x0d\x93\x43\xb7\xeb\xca\x16\xda\x36\x9c\x68\x0c\xbe\xa9\x32\x87\x56\xe6\x0d\x8d\xc1\xca\xb3\xb2\x7d\xb4\x18\x79\x63\xb0\x0a\x86\xcf\x2f\x1e\xec\xa0\x1e\xec\xa0\x7e\xdf\x76\x50\x86\xe1\xb3\xb0\x87\xbc\x07\xcb\xe7\x0a\xef\x9b\xc2\xd2\x92\x3f\xd9\xda\x1b\xca\x0b\xe8\xcd\x7c\x54\x0c\x2a\x34\x36\x86\x8c\x27\x4e\xb0\xa2\x12\x33\x84\xa8\xdc\x4e\x18\xfb\x94\x95\x22\xd3\xde\x09\xf8\x7c\xe0\xeb\xc3\xfe\xc7\x2d\xce\xdc\xef\xd2\x01\x1e\xfe\x0e\x58\x2d\x86\xb7\x8d\x34\x43\xfb\x1f\xb7\xc4\xfd\x80\xbf\x03\xe2\x89\x39\x38\x61\x34\x42\xf6\x64\x85\x79\x15\x56\x6e\x7c\x6b\x6f\x77\x77\x7b\xeb\x70\x67\x6f\x17\x6d\xef\xef\xef\xed\x0f\xd0\x96\x52\xfb\x46\xbc\x4a\x7e\xa2\x8f\x31\x6a\x2e\x23\x56\x1f\x5a\x6e\x76\xfc\x7d\xd0\xee\x75\x16\x1d\xbb\x7a\x3b\xcf\x95\x08\x94\xad\x1e\xc2\x2b\xf3\x37\x21\xad\x6a\x07\xc8\xb5\x10\x6e\xdb\xf1\x70\x26\xb8\x28\xc2\x11\x46\x1b\xe8\xd1\x23\xf1\x08\x91\x6d\xeb\xe2\x77\x87\xc7\xd7\x2e\xa5\x74\x64\xb1\xef\x90\x37\x79\x80\xd4\x74\xfd\xf1\x60\x6f\x17\x66\x25\x57\x5d\xf2\x44\xa5\x16\x7d\x5b\xaf\xc6\x81\xa8\xda\x1e\xad\x9e\xcd\x43\x7e\x77\x6d\x8e\x77\x52\x2c\x3a\xa5\x87\x3b\x1f\xb6\xf7\x8e\x0e\x07\x48\xdc\x80\x33\xe2\x62\x9d\x9c\x14\x68\x19\x35\xd9\x9f\x30\x1a\x33\x8e\xd1\xb4\xe2\x8b\x09\x8f\x93\xdf\x3e\xec\x56\x0f\xbb\xd5\xef\x7b\xb7\x32\x36\x2b\x78\x82\xf9\x5b\x35\xd9\x5d\xfc\x9d\xfb\x42\xcf\xeb\xef\xf1\x95\xbb\x74\x2b\xc4\xd6\xbf\x3a\x9c\x99\x41\xa4\x0a\xeb\x18\xc2\xdf\xce\xf2\xb6\xf4\x61\xc1\xb5\x48\xfe\xda\x4f\xe4\xa7\xd2\xae\xd7\x50\xa4\x9b\xf9\x3c\xda\x06\xaa\x79\x87\x5c\x64\x69\x7b\xce\xeb\x7a\x23\x33\xcd\xd2\xcb\x49\x36\x53\x2d\xaa\x84\x8a\xd3\x9b\x44\xda\x08\x4b\x5c\xe1\x98\xcb\x03\x10\x79\xa1\x14\x5f\x4a\xa4\xa9\xe3\xd9\xeb\x2c\x4b\xae\x21\xd6\x75\x0c\x9e\xd3\xf9\x26\x81\x39\x64\x6c\xcc\x0e\x3c\x16\xc1\xb1\xe5\xe7\x5d\x9e\xf8\x20\x82\x02\x5b\x94\xa2\xf6\xee\x92\x35\x4d\xa6\xdb\x2d\x06\x61\xfb\x86\xe2\xf5\xba\x51\x23\x0c\xe4\x97\xaf\x44\x88\x47\x54\x20\x1d\x51\x13\xdc\x6f\x88\xdf\x15\xb6\xaf\xfe\xf2\xda\x78\xb9\xf2\x66\xd8\xc4\x36\xa7\xcf\x98\x7b\x2e\x07\x17\x45\x6e\xc4\x40\xed\xd4\xdc\x1b\xe0\xc8\x5b\x41\x35\xea\x8c\xea\xea\x1c\xff\x70\x4a\x2c\xfb\x55\x6f\x69\x7a\x5d\x7c\x74\xb0\x42\x3d\x43\xa3\xc2\x0d\x7b\xcd\xb8\xcc\xa2\xf5\xf4\x70\xa3\x11\x49\x07\xf0\xb7\x1a\x4e\x41\x46\x69\x48\x67\xb9\x3b\x1c\x33\xbd\x6a\x3c\x26\x4c\xf5\x78\x14\x54\xdd\x80\xc0\x73\xc1\xe2\xfd\x17\xcf\x1d\x24\x79\x0b\x8e\x14\xa6\xb1\x52\x2d\xd1\x0c\x62\xc4\x0f\x49\x1a\x26\x7e\x13\x68\x5e\x87\xcf\xc0\xd4\x5c\xd7\x4e\x96\xa8\xde\x42\x8a\xcc\xc3\x67\x38\xbf\xa4\x63\xae\xb1\x9e\x9c\x12\x60\x19\xd9\x08\x83\x18\xc4\xfa\x26\xa2\x43\xd4\x62\xcb\xe3\x35\x44\x74\xa7\xe4\xbc\x4e\x2d\x6e\xf5\xcb\x78\x11\xf8\xe0\x5b\x44\x78\xdd\x10\x2f\x6a\xcd\x8c\x4a\xa7\x23\x52\xaf\xf9\xc8\xd9\x79\xca\xfd\x14\x95\xf3\x57\xae\xc2\x7d\x20\x77\x0a\x2a\xa9\xbd\xd5\x2e\xab\x34\xc5\x3c\x1d\xca\x8e\xdd\x95\xa5\x1b\x91\x37\x6a\xf9\x39\x38\x72\x86\xfb\x65\x45\x2b\xc7\x04\xa2\x62\x39\xc6\x00\x02\xc8\x9c\x3f\x46\x37\x3a\xb9\x26\x5c\xa9\xd6\x2c\xa2\xf2\xe5\xbf\xad\x10\x86\xfa\xaa\x62\x49\x42\xa6\x33\x9a\xb3\x9a\xdb\x7e\x81\xfd\xd2\x43\x99\x16\xdf\xbe\x02\x74\xe6\xde\x9c\xb7\xdd\x70\x80\x37\xaa\x0d\xfa\x3e\x87\xe2\x6c\x22\x89\xb2\x34\x0a\x69\xcb\x9a\xfd\x76\xb5\x8b\x9b\x4a\xae\x27\xfc\xdb\x54\x73\x3d\x6f\x7c\x5e\x1f\x1b\x8b\xf9\xdd\x43\x99\x87\x09\xae\x20\x0c\x86\xe0\x84\xc0\x6b\x89\x55\xb3\x8f\x1f\x83\xbe\xc1\xee\x45\xfd\x36\x5d\xed\x97\x07\x70\x50\xeb\x98\x47\x01\x28\xe4\x9a\x5f\xe2\x75\xf5\x92\x79\xdf\xe1\x2c\x2f\x2d\x3f\x5a\xde\x78\xd0\xc0\xfc\x12\x8e\x79\xfa\x86\x5f\x14\x31\x40\x71\x01\xa3\xea\x75\xdf\x6d\x09\x67\x40\x5f\x94\x98\x66\xd6\xb7\xd2\x5e\x67\x4b\xf3\xa7\x8c\xa4\xad\x46\xa3\x5c\xb9\x7a\x22\xc7\xe9\x8d\x23\xca\xbc\xdf\x00\xe1\xb0\xc5\xd6\x79\x3b\x30\x7b\x64\xde\xd5\xa4\x19\xdd\xb1\xfa\xaa\x70\xa8\x7b\xe1\x01\x5e\xb0\x6d\x78\xc3\xd0\x0e\x9c\x56\xcc\xf6\xd5\x4e\xb2\xb0\x3b\x20\xa9\x1d\x12\x07\x16\xee\x75\xc6\x90\xcb\x6c\x30\xcd\xae\x61\x4c\xae\x8b\x28\x0e\xc3\xaf\xb9\x2c\x86\x9a\xe3\x78\x16\x61\x63\xae\xc2\x28\x0a\x90\xf0\x2f\x69\x32\x94\x30\x8a\x8e\x45\x32\x67\x8a\x0c\x29\xe2\x5b\x92\xf9\x77\xf6\x94\x75\x8a\x31\x19\xd2\x56\x1b\x0d\x4a\x18\x95\x59\x25\xad\x55\x18\x45\x52\x4d\xc5\xad\xbc\x39\x6d\xe3\x04\x53\x2c\xc7\xc1\x2f\xa5\xca\xe9\x9c\xaa\x6e\xc1\x33\x8c\x0b\x2c\xf1\xca\x44\xac\x6d\xb6\xd7\x73\x39\xae\xd2\xcd\xc2\x7d\x09\x46\x56\xc3\xf5\xd2\x8f\xc7\x8d\x83\x2b\xfe\xfc\xd6\xfd\x33\xad\xcc\xf7\xcf\x54\x57\xbc\x5f\x6d\x64\x67\x57\x80\x2c\x11\xd0\xf2\x04\xe1\xca\x1d\xae\x13\xa8\x92\x44\xf9\x9b\xf2\x06\x55\x49\xcb\xa6\xcd\xff\x02\x4f\x1c\x9d\x37\x6e\xde\xf7\x8d\xb5\xd4\xf8\xb5\x5c\x4f\x41\x60\x7b\xeb\xe8\x5a\xc5\x01\xa5\x81\x21\xb4\x74\x6c\x00\x9c\x58\xd4\xeb\x03\x30\xbd\x59\x69\xa2\xf0\x82\x1e\x2b\x76\xcf\xdb\x3e\xa9\x1c\x80\x65\x36\xe1\xbd\x14\xb6\x70\x69\xf8\xc7\xaa\xbb\x13\x9e\xe7\x35\xeb\x76\x8e\xb2\x56\x7b\x8b\x68\xe0\xef\xea\x07\xb3\xeb\x57\x4e\x7e\xe0\x34\xe8\x51\x00\xd6\x47\x26\x31\x83\x92\x2c\x75\xbb\xe8\x70\xef\xcd\xde\x00\xe5\x98\x5b\x64\x05\xa8\xc8\x84\xed\x8c\xba\xe3\xd2\xc6\x38\x21\x57\x75\x75\x58\x39\x42\x9b\x05\x4a\x71\x84\x8b\x22\xcc\x2f\xd9\x62\x81\xa8\xdd\x05\x23\xb7\x26\x38\x21\x06\x47\xd0\xe8\x3c\xcb\x3f\x73\x31\x6f\x32\x4b\x28\x99\x26\x46\xdc\x08\x3b\x18\x8b\xdf\xd9\x51\xf7\x09\xf2\x1a\x71\x3f\x93\x56\xe0\xbc\x0e\xdb\x8e\x41\x36\x6f\x19\x91\xea\xc6\x70\xcc\x37\x0e\xfb\x68\xa2\x4a\x75\x24\x8e\x7c\xee\x6c\x1e\xb1\xce\x1d\x97\x61\x4f\x7c\x07\x88\x2a\x58\x87\x9d\xa2\x92\x81\xa3\xdd\x4f\xe1\xdd\xc9\x57\xd3\x1c\x3b\x48\x6f\x3d\x95\x67\x28\x5d\xbf\x20\x78\x77\x4c\x25\x00\xcf\x99\x9b\xe7\xf8\xb0\xc1\x73\x14\xd3\x13\x36\x3d\x76\x17\x3d\xa6\x9f\xa2\x58\x81\xa9\x13\x7c\xc6\xe7\xf1\xaa\xf6\x24\x56\x3f\xdd\x04\xd7\x8c\x57\xc3\x78\x86\x5c\x45\x2f\x48\x87\x54\xb9\xbc\xf1\xb0\x55\xc1\x7b\x18\x38\x32\x0c\x47\xf1\x45\xc7\x62\x47\xfa\x63\x1d\x49\x00\xc9\x85\xe0\xef\xba\x4c\x55\x2c\x87\xff\x50\xe9\x06\xa3\x91\x3f\x6d\x39\x12\x5f\x88\xc7\xde\x65\x6e\xce\xd1\xa0\xfd\x14\x55\xf0\xe7\x0a\x8e\xdc\x18\xac\x82\x3f\x23\xd3\x77\x38\x63\xcc\xfd\x87\xab\xd1\x87\xab\xd1\xdf\xf7\xd5\xa8\xb8\x17\x15\x6f\x7d\xff\x2e\x62\xf1\xdd\xab\x37\x71\x38\x04\x3c\x41\x5b\x59\x7a\x86\x19\x2b\x0a\x45\xa8\x66\x38\x07\xc3\x59\x00\xe2\x2d\xcb\x60\x2d\x8c\x80\xc3\xa4\xc8\x50\x98\x24\xd9\x79\xc1\x43\xca\x83\xa6\xae\xe8\x2c\xb1\x8a\xa4\xe0\xff\x81\x5c\xe0\xf8\x9a\x67\x2d\x95\x2f\x36\x96\xc4\x95\x2a\xcd\xdc\xe0\xcc\x42\x67\xa9\x0e\x9c\x2d\x5b\x27\x8a\xae\xae\x64\x58\x77\x9d\xd1\x54\x4a\xd4\x66\xdb\x55\x06\xf0\xb3\x9c\x10\x91\xb8\x66\x56\x38\x91\x96\x0a\x46\xab\x21\xae\x87\xd8\x1b\x82\xae\xb9\x0d\xb5\xf7\x4a\x75\x02\xa4\xe0\xfb\xe6\x53\x56\xeb\xd2\x48\x86\x6c\x52\xed\xc0\x91\x8b\x89\x9a\x8c\xd3\xca\xdb\x1f\xd7\x14\x36\x53\x0a\x7e\x71\xd8\x0a\xd8\x24\x9c\xe1\x9c\x0c\xc1\xcb\x47\x8e\xa3\x90\x71\x1c\x23\x18\xcd\xe3\xc7\x28\x09\x7f\xbe\x44\x49\x16\xc6\x28\xbe\x4c\xc3\x09\x89\x50\x96\xe2\x02\x5a\x13\x13\xa2\x1b\x12\x41\xb8\x33\xa5\x27\x00\x28\x69\x60\x2f\x1b\x2f\x41\xb1\xd9\x1a\x61\xba\xa7\x0e\xc9\x1e\x7f\xce\x6c\x62\xb4\xc0\x3a\xcf\x2f\x80\xa9\x4c\x10\x53\x22\x8f\xc9\xd5\xd7\x1e\x96\xaa\xbf\xf2\xee\x85\x67\x17\xe7\x04\x82\x90\x18\xbd\x42\xa0\x83\x28\x30\x3f\x41\x0f\x4a\x4f\xab\xb8\xf0\x3e\xcb\xb1\x50\x2f\x06\x70\x83\x17\xf1\xd5\xc1\x0f\xc7\x63\x7c\xe1\x53\x1b\x68\xb5\xa9\x93\xe0\xf8\xa5\x5c\xa0\x88\xa5\xf9\x14\xe1\xc0\x2b\xb5\xf2\x8e\xc6\x5f\x06\x0f\xff\x4e\x84\x55\x67\x55\x89\x45\xd6\x46\x03\xb9\xde\x04\x58\x59\x56\xf2\xdd\x13\x78\xde\xd7\x41\x37\x07\x4e\xb7\x83\x12\x1c\x5b\x02\x1a\x8a\x7d\x95\x61\x68\x66\xd6\x53\xce\x0f\x65\x34\x69\x09\x14\x9a\x41\x9d\xdd\xd6\xe0\x02\x6b\x70\xa3\x7b\xad\x8e\x2a\xd7\xf6\xf5\xee\x36\x35\x8a\x52\xb6\x7e\xca\x38\xe8\x70\x0a\x2c\xc6\x8c\x02\x3d\x08\x77\x48\x5d\xaa\x6a\x36\xe3\x98\xbf\x8b\x50\x4a\xb4\x30\x8d\x51\x81\x69\x81\x66\x53\xc8\x10\xa7\x11\x60\x19\x84\xe2\x9c\xed\x1d\xd9\x99\x10\xb6\x84\x53\xd3\xce\xd2\x92\xf1\x36\xe2\x7d\x36\x2a\x36\xe9\x01\x0d\x73\xba\xe4\x6a\x1a\x0b\x9c\x0c\x55\xe2\xb0\xfc\x70\x59\xb0\x70\xbb\x16\x2b\x16\x18\x4e\x86\x25\x8f\x3e\xf2\x95\xdd\x08\x53\xae\xcf\x62\x85\x9d\xa7\x76\xa0\x5f\xd0\xc3\x2c\xa0\x7b\x48\x9e\x3c\x1d\x9e\xc1\x5a\xe9\xf8\x18\x07\x64\x8c\x30\x6d\x39\x8f\x7e\x84\x29\x63\xe9\x94\xd3\xed\xa2\x38\x4b\x9b\xe2\x99\x28\xeb\xa3\x40\x1b\xd8\x4d\xc2\x4d\xb7\x4c\x94\x76\x47\xe0\x02\xa3\xd3\xe9\xa0\x9f\x66\xdc\x2d\x30\x6b\x93\xf1\xde\xd2\x79\xb9\xe2\x65\x64\xcd\xab\xc8\x6b\xf7\x09\xac\xb3\xd2\xd5\x30\xfc\x67\x4c\x9e\xe9\x3d\x98\x72\x4b\xce\x79\xef\x34\xf9\xeb\x1d\xdb\xee\xd3\xea\xdf\x7c\xaf\xac\x5f\x8f\x74\xa7\x59\x92\x70\xf2\xf1\x93\xad\xa0\x4d\x0d\xe6\xd2\xa5\x52\x89\x80\xda\x36\xfd\xa0\xec\x70\x2d\x62\xc9\x2a\xc8\x45\xcc\x68\x56\x9a\x53\x69\x65\xc1\x48\x4f\x8e\xd5\x37\x09\xbe\x77\x53\x3e\x9a\xc8\x16\x3e\xc9\x2f\x4a\x1d\xb7\xa3\x0c\x6d\xa7\x0c\x43\xd3\xca\xeb\xef\x9c\x04\x5d\xc9\x40\x16\x2a\x93\xce\x9d\xd0\x73\x37\x22\xad\xd4\x07\x40\x9f\x5c\xd7\xd4\x8c\xf1\x7c\xcc\x92\x84\xf1\x19\xdd\x13\x4e\x83\x03\x5e\x84\x9d\xd3\xf0\x04\xa7\x14\x8e\x9c\x1d\x46\x71\x30\x34\xbd\x97\x4c\x85\xa5\xfd\x31\xc7\x14\x90\xe3\x4e\x7c\x12\xc8\x2b\x2a\x2b\x39\xd0\xc4\x28\x72\x4c\xff\x45\x5c\x41\x0c\xf4\xcb\x36\x6b\x19\x99\xb0\x44\xe2\x8e\x4c\x16\xa0\x52\xb0\x0f\xb9\xdc\x3c\xb7\x03\x81\x38\x4d\x6d\xe5\x18\xc6\x04\x06\xdb\x5b\x9e\x97\x8e\xc0\xec\xb8\x06\xdb\xb8\x70\xb5\xf0\x61\x68\xf8\x6e\xa2\x2a\xab\xd4\x75\x55\x2a\x7b\xfc\x4a\x35\xbb\x33\x86\x31\x01\xaa\xf4\x1f\x5f\x69\x8e\xa9\x85\x4d\x2d\x06\x3b\xa2\xaf\x01\x5d\x42\x83\x9d\x60\x40\xca\x99\x2f\xbf\x19\x53\x2b\x44\x98\xd6\xa8\x0c\xb1\xe5\x6e\x55\xe5\x6b\xb6\x57\xca\x32\xaf\x9d\xd4\xef\xca\x7e\x77\x52\x7c\x2e\x6e\x9d\x4c\x1c\x98\x4e\xc2\x38\x93\x24\xb1\xe5\x28\xcf\xcf\x1c\xe7\x2c\xfb\x56\x74\xea\x11\x73\xa3\xd3\xb9\x7c\x10\x09\x8e\x2c\xce\x85\x35\xd4\xeb\xb8\x27\x2d\xb3\x97\x9a\xb2\xfe\x6e\xd4\xeb\x9d\xad\xa5\xcd\x88\x20\x2e\x7b\x05\x34\x7d\x35\xe4\x18\x2e\x19\xd8\x99\x63\x8a\xd3\x18\x2c\xdc\xd4\x24\x87\x05\x28\x5a\xd2\x82\x51\xa8\x72\x02\xa3\x2b\xca\x86\x00\xcc\x0a\x31\xa9\xa7\xcd\x95\x2b\xaa\xf5\x59\x1a\x16\x05\x19\xa5\x38\xee\x94\xfb\xe8\x52\x94\x8f\x27\xfb\x66\x47\xc9\x58\xd1\xe9\x9c\x09\xf2\x36\x63\xda\x8c\x19\x23\xd1\x06\xc5\x28\x31\xa5\xc3\x30\xc9\x71\x18\x5f\xea\x07\xeb\x5a\x50\x2c\xee\x4e\x69\xb6\x20\x2b\xa5\xd7\x79\xe3\x22\xc3\x96\xd3\x9a\x72\xfe\xd6\x2b\xbb\xe2\xd2\x2b\x93\x73\x71\x9f\x93\x48\x26\x45\xd3\x4c\x8c\x8d\x4c\x26\x38\x26\x21\xc5\xc9\xa5\xdb\xac\x20\xf7\x68\x51\xda\xb6\xa5\x13\xa8\xbe\x55\xe1\x6a\xc2\xe7\xae\xca\xd4\x64\x73\x96\xcf\xb6\x1f\x3e\x18\xe3\x2e\xf7\xbc\x14\xb3\xc3\xdd\xcc\x6d\xde\xc6\x2d\xfb\x8c\x7e\x48\x75\x8c\xc5\x1c\xcd\xd1\x38\xf3\x24\xae\x4b\xcb\x03\x31\x70\x6d\xdc\x09\x5f\x2f\x3a\x10\x73\xdf\x9d\x3f\x9e\x92\xe4\x90\x4d\xa5\xe0\x60\x0f\x64\x0e\x7f\x87\xb7\xe5\x93\xec\x4c\xaa\x34\x51\x58\x5c\xa6\x91\x3a\xfc\xf8\x04\x23\x1f\xdf\x9e\xa5\xf0\x78\xda\x42\x80\x21\x63\x38\xd8\x2a\xf1\x2e\xd3\x12\xfe\x26\x35\x5b\x82\x7f\x09\xa3\x23\x27\x40\xbc\xcf\x7b\x42\x49\xa6\xf0\x9a\x9c\xa8\x92\xae\x50\xee\xec\xa8\x15\x86\x94\xdd\x2e\xda\x19\x6a\xce\x48\x0a\xf5\xb0\xef\x12\x0b\xff\x2b\x88\x50\xa4\xdd\x73\xe9\x72\xe7\x63\x0c\xc6\x18\x62\xf4\x6d\xc4\x99\x6a\x81\x08\xb5\xd9\xaa\x77\xa3\x2e\x11\xbb\x5a\x66\xbe\xdd\xc3\x87\x7e\x51\xa3\x3b\xa1\xe6\x7e\x0c\xf1\x52\x3c\xfc\xed\x2b\x3a\xe8\x71\xe4\xf1\x1c\xbb\xd6\x7b\x49\x36\xaa\x6a\x17\x39\x8c\xa9\xe6\x6c\x01\xb5\x0c\xd8\x9e\x50\xe1\x87\xcf\x1f\xbe\xc4\x06\x29\x1d\x00\xdc\x81\x2d\x4e\x47\x25\x3f\x53\x42\x10\xdf\x79\xc3\x13\x06\x82\xc6\x5a\xed\x0e\xdf\x91\xa3\x50\x7a\x2a\x04\xbf\x2a\x38\x46\x6c\x75\x8f\xf3\x2c\xcd\x66\x85\x72\x5b\x28\x0c\x03\xd8\x6e\xef\xba\x22\xe2\xd5\x08\x61\xb7\xe9\xb5\xaf\x05\xaf\x12\x99\xb6\xd2\x5b\x84\x80\xca\x36\x74\xac\x86\xf9\x73\x78\x87\x79\xbb\x9e\xc3\x8f\x4b\x57\xa4\x1c\xb7\xa5\x48\x70\x35\x17\xa4\xd7\x27\xc1\xea\xca\xc3\x15\xe8\xc3\x15\xe8\xef\xfb\x0a\x54\xbf\x0e\x35\x94\xd4\xb7\x79\x22\x2a\x80\x6f\x70\x77\xe9\x7a\x40\x68\xce\x0a\x7e\xc1\x14\x51\x08\xf5\x7a\xa3\x57\xa6\xe9\x90\x8c\xbc\x70\x3c\x4b\x02\xee\x9c\x86\x4e\x8c\x17\x72\x1a\xaa\xf8\x2e\x05\x1e\x9d\x13\x0a\x2f\x4d\xfc\xef\x4b\x75\x7e\xe7\x27\x5f\xb4\x17\xd0\x42\xf3\x70\x55\xdc\xb6\x9a\x5f\x82\x9e\x92\x91\x70\x7d\xe0\x58\x3f\x72\xa0\xd7\x64\xe4\x5c\x06\x98\x56\x90\x5c\x53\x7d\xc5\x21\xae\x14\xec\xb5\xed\xed\x4a\xa7\x9b\x16\xbc\xa0\x9f\xac\x68\xc3\x92\xa6\x78\xef\xbc\xef\xd7\x68\xa6\xca\x4a\xb0\x75\xa5\x3c\x22\xc5\xc7\x1c\x8b\xeb\x53\xe3\x56\xc3\xaa\xfb\x54\xa7\x3b\x0d\x9c\x9a\x25\xd8\x01\x44\xf8\xd0\x47\x57\x57\xe5\x3c\x71\xa6\xf5\x67\xe2\x30\x4f\x08\x2b\x6a\x74\x2d\x9d\xce\xe8\x1b\x3c\x0c\x67\x89\xf7\xc2\x65\x5e\x1f\xd9\x4e\xee\xb6\xa3\xae\x8a\xbd\x31\x60\x18\xa1\x75\x62\xa3\x45\x8f\xd3\xaa\xea\xfb\x1f\xb3\x0b\xce\x28\x7e\x89\xee\xbb\x4f\xc1\xb8\x60\xc3\x6a\xa9\x98\x63\xab\x51\x4f\x85\x46\xb6\x07\x0f\x92\xb6\xde\xe1\x0b\xcf\xc8\xc5\xaa\xe2\x83\x2d\x0c\x63\xca\x6c\x88\x42\xcb\xa7\x20\x38\xac\x54\xf6\x07\xca\x9e\x80\xad\xbb\x77\xdb\x7f\x72\x96\x1b\xd4\xc1\xe4\x69\xef\x42\x93\x77\x00\x96\x4f\xde\x92\xc1\xbc\xbc\x5a\x97\xb7\x04\xe5\x3a\xbd\xc1\xc4\xbf\x58\x97\xce\x70\x6d\x68\x5c\xdd\xc2\xe7\xd5\x95\x43\x43\x9b\x11\x04\x6f\x30\x3c\xa6\x99\xf0\x1e\x57\x5d\xb2\x5a\xe8\x93\xf0\x5f\xe5\xbf\xb2\xb4\xe5\x27\xb3\x6a\x9a\x89\x08\xd3\x84\xa2\x09\x19\x8d\xb9\xc0\xa9\xbc\x1d\x0b\xe5\x56\xa9\x65\x9a\xcd\x6d\x97\x66\x76\xab\xc7\xcd\x51\x58\x7c\xcc\x49\x84\x9b\x01\x62\xbf\xd9\x1f\x98\x3e\xf6\x23\xcd\xd2\x08\xfb\x9e\x35\x7e\xc6\x97\x35\x0f\x1b\x3f\xe3\xcb\x45\x9f\x36\x42\x4d\x25\x1c\xf2\x1a\x36\x0c\x3b\x8c\x37\x38\x22\x93\x30\x69\x99\x00\xe5\xa7\x65\xf6\xe5\xeb\xd7\x26\x62\xc3\x07\xe7\x7d\xd3\xb2\xaf\xea\xfb\x27\xe9\xdb\x52\xed\x03\xbd\xfe\x92\xf4\x2a\x84\x98\x12\xc1\xc2\x3d\xab\x8c\x20\x24\xa8\xd5\x2b\xda\x2c\x4c\xa7\x17\xb6\x38\x23\xd2\x97\x2c\x19\x66\x2e\x65\xd2\x8b\xf6\x17\xa5\x01\xbc\xe8\x98\xdb\xdf\x23\x79\x0a\xd7\x9a\x2f\x1b\x40\xb9\xc2\xc8\x24\xfe\x6c\x00\xf5\xd6\x83\xa5\x1b\xb8\x80\x57\x65\xfe\xea\x4b\x50\xde\x36\x5c\x28\xa9\xe4\xbb\xe8\x00\x49\xf9\x0b\x41\x96\x86\x1c\x85\x85\x1f\x6e\x14\x16\x16\x14\x90\xaf\x01\xaa\x45\x3b\x23\x5f\x97\x90\x46\x6f\x5e\x70\xf3\x8d\xad\x3c\x81\x5f\xdc\x98\x94\x64\xac\x9a\xdb\x90\x94\x08\x9b\x53\x4b\x59\x2a\x8a\xd3\x4d\xc8\xcb\xad\xd8\xf1\xd7\x62\x86\xdf\xd1\xe1\x76\xe6\xd0\x9b\x0f\xaa\x3c\x67\x1e\x28\x4d\x79\x32\x73\x01\xf2\xab\x04\xad\x6e\xb2\x82\x10\x55\x00\x98\xd9\x64\x96\x84\x94\x9c\xe1\xef\xc3\xe2\xa8\x80\xf7\x6c\x55\x55\x95\x60\x9d\xba\x46\x73\x6b\x18\xa9\x72\x72\xf0\xb6\x89\x82\x84\x4b\xb2\x91\x6b\xf1\xa7\x33\x8c\xf0\x35\x25\x95\x14\xe8\x95\xbc\x0a\x29\xcf\xab\x58\x06\x3b\x4f\xfb\x24\x5a\x5a\x68\x01\xc0\xec\x2e\x4e\xf2\x20\x75\xd7\x52\x39\x54\xb8\x00\x8d\xdb\x35\x99\x76\x2d\x50\x83\x32\x6c\xe9\x76\x91\xf2\x09\x04\xce\xf1\xc4\xe9\x15\x21\xde\x14\x9b\x9f\xf7\x64\x42\xa8\x67\x0a\x6d\x00\x81\x2b\x95\x58\x31\xef\x56\xbe\x55\xa6\x20\x3f\xfb\x98\xa0\xce\xb4\xa0\x29\x99\xe0\x82\x86\x93\x69\x65\x11\x05\xa1\xd7\x15\xcf\x48\xab\x56\xae\x95\x5d\x55\xad\x3a\x1a\x97\x2e\x1e\x38\xc1\x8a\xc6\xf5\x1a\xb3\xc9\xb6\x9c\x5d\xde\xff\x09\xc5\x93\xb6\xfb\x9a\xaa\x64\xd2\x06\x50\xe5\xcb\x43\x8b\xb8\x7d\x3b\x29\x2f\x38\x8f\xca\x4f\xf9\xb9\x76\x01\x1a\x4f\xb2\x91\x97\x98\xcd\x85\xe5\x23\xe5\x24\x1b\x69\x15\x46\x99\x9e\xa1\x5e\x8b\xa6\xcd\x0a\x4d\x8a\x36\x54\xce\x64\xc8\xbe\x2c\xa6\xaa\xc6\xe9\x64\x78\x26\xd8\x2d\xba\x6e\xd6\x59\xe2\x9a\x56\xc5\x0b\xb0\x5f\x6f\x25\x56\x13\x49\x36\xf2\x54\x2d\x53\x2b\xaa\x54\x85\x6c\x89\x0f\xd4\xe8\xf3\x4f\x27\xe7\x63\x52\x30\xa6\x34\xcd\x0a\x7a\x8b\xe3\xc9\xc7\xac\xa8\xdf\x95\xcb\x71\x6f\x6a\x99\x57\xb9\x52\x73\xa2\x59\x27\x4d\xce\xc5\xbe\x3b\xd3\xf0\x12\x6c\xda\x37\x2c\x55\x83\x99\x25\x90\x0c\x49\x94\x26\x5e\x19\x5a\x66\x9a\xb0\xe7\x59\xfe\xf9\x30\xfb\x98\x67\x67\xb8\xba\x8c\x01\x64\x96\x9d\xe6\x24\xcb\x09\x3c\xf3\xaa\x28\x28\x21\x0c\xb7\xee\x43\x33\xda\x8f\x65\xc5\xca\x59\x0b\xef\x24\x77\x71\x61\xf2\x14\x23\x1d\x6d\x58\x5f\xdf\xa1\x63\xe3\xf3\x04\x0d\xd4\x15\xf2\xb5\x6e\x95\xeb\x31\xb9\x4a\x33\x49\xb2\x73\x30\xe9\x97\x67\xc8\xba\xea\xeb\x4d\xd0\x79\xbc\x3a\x46\x4c\x28\x4b\x93\x4b\xee\x8c\x9f\x5a\x96\xf1\xd2\x3a\x9d\x5b\xa1\xfb\x1e\x55\x48\x13\x75\x34\x70\x1f\x4c\x98\xc6\xe9\xee\x71\x87\xf5\x71\x21\xde\xa5\x34\xf3\x40\xff\xc2\x42\xd0\xcb\xcd\xe6\x51\xba\x9f\xac\x6d\xdd\x6c\x3d\x61\x0b\xba\x06\xfc\xe2\x8b\x29\xc9\x2f\x3d\x2b\xde\xc8\x35\xc9\xad\xe0\xae\x3b\xbc\xd0\x2c\xaf\x6a\x09\x38\xa0\x9e\x05\x00\x94\xed\xdb\xc8\x1d\x88\xf6\xba\x6f\x55\xee\x87\xe7\x92\x64\x44\x8a\x17\xcc\xa8\x7e\xb3\x88\x08\x71\x97\xaf\x2c\x63\xee\xb6\x7f\x2c\x04\xe2\x14\x9c\x8e\x7e\xa1\x57\x85\x6e\x00\x9c\xd9\x42\x04\x2a\x1f\x73\xe8\x76\x6f\xb2\x22\x60\x6d\x9a\xab\xb1\x72\x31\xea\xe5\x76\x87\x95\xe4\xa8\x52\x39\x8a\x16\xa3\x7f\xc5\x54\x5d\xed\x87\x2f\x54\x85\xa9\x52\x96\x68\x7e\x8d\xa3\xf1\xea\x8a\x8c\xc1\x22\x01\x3d\x8a\x0e\xcb\x6d\x97\xa1\x55\x56\x2c\x6c\x7e\x15\xc2\xdb\x93\x80\xe8\xd0\xec\x7d\x76\x8e\xf3\xad\xb0\xc0\xad\x76\x27\xc7\xd3\x24\x8c\x30\x70\x87\x00\x35\x9b\x56\xa4\x88\xd2\x1d\xbb\x70\xbc\x2c\xeb\x6a\x2a\x7d\xf3\xed\x30\x02\x85\xf8\x95\x12\x6b\x64\xe7\x34\x4c\x5b\x76\x9c\xe7\x71\x3e\x54\xb8\x63\x90\x1d\x52\xfc\xc0\x7a\xd0\x82\x10\x0c\x22\xe5\x0d\xc9\x71\x44\x5b\x35\x63\x07\x40\x51\x73\xab\x5d\x89\xcc\x03\xb8\x3d\xfb\x35\x66\xe5\xe6\x58\x97\x2f\x12\x39\x0f\x3c\xb8\x4c\x23\x92\xfa\x05\x39\xe1\x60\xda\xa0\xc2\x47\x76\x12\x2a\xbb\xdb\xb1\x24\x57\x70\xce\x03\xb6\x71\x24\x1d\xc1\x11\xc8\x7b\xf2\x2c\x83\xd9\x2e\x8e\x84\xb7\xa1\x39\x15\x98\x50\x76\xf9\x31\x19\x8d\x71\x31\xaf\xbc\x09\x65\x38\x18\x11\xb9\x9f\xd3\xec\x3c\x3d\xa0\x21\xc5\x3e\x7f\x77\x46\x6e\x75\x03\x66\x15\xeb\x6e\x0d\xd3\x59\x92\xe0\x78\x5e\x15\x26\x54\xc5\x61\x58\x7b\x3d\xaa\xf0\x6c\x3f\xef\x7e\x6e\x30\x17\x22\xd0\xf5\xd4\x54\x30\xa7\xa4\x75\x75\x33\xf0\xa4\x19\xb0\xbe\x63\xd3\xa0\x3a\xcb\x28\xe9\x32\x95\x81\x3f\xd9\x28\x61\x09\x05\x03\x4f\x1a\x87\xad\xba\xdd\x1d\x54\xe6\x98\xe5\xfc\x03\xaa\xce\xab\x28\xeb\xaa\xcb\x3c\x55\xb8\x20\x56\xef\x2d\xa5\xc4\xc0\x9b\x6a\xc2\x9b\x07\xbe\x81\x27\xcd\x84\x75\xd0\xe8\x49\x34\xa1\x5d\x2e\x34\xa8\x48\xe7\x9e\x18\x2c\x53\x22\x7e\xcf\xd1\x18\xf4\xbf\x35\x7d\xe6\x58\x06\x03\x8d\xc1\x4a\xaf\xca\x7b\x0e\x63\xf0\x8d\xc1\x2a\x78\x5e\x58\x7d\x30\x3b\x7a\x30\x3b\xfa\xbb\x31\x3b\x12\x94\x7e\x1f\x31\x54\x6e\xe6\x70\x7e\x41\xdb\x22\x1e\xe2\xc5\x36\x1a\xe2\x69\x5f\xc1\x73\xfd\xe2\xbe\xe6\xc3\x24\xe9\x3a\xd1\x18\xe1\x0d\xa9\x1b\xcb\xa5\xec\x81\x5e\x1a\x42\x97\xc3\x56\xd5\x78\x9e\xf7\xc5\xad\xfa\xc4\x77\x3e\xe1\x19\xdd\x0c\xd8\x7a\x73\xaf\xe5\xba\x52\xb1\x75\x98\xb5\xf2\xa4\xbb\x55\x0b\xc1\xde\x42\x38\x0e\x43\x9d\xf2\xdb\x84\x91\x01\x5d\x05\x88\xf8\x34\x21\xee\xc5\x6b\x3e\xdb\x3c\xdc\xc9\xb0\xdc\x2b\xc2\xdd\xb5\x7e\x76\x64\x9e\x3f\xf2\x91\xf7\x38\x76\x83\x30\xc5\xf2\x04\xa0\x63\xba\x81\xef\x02\xe0\xf5\xfc\xa1\x4c\x3e\x2a\xb8\x6b\xfc\x47\x42\xb2\x5b\xac\xc3\xa6\xa4\x56\xdb\x69\xb3\x7b\xaf\x4a\xa4\x24\x73\xcc\x08\x75\xe2\x45\x5e\x79\x70\xfe\xb1\xb9\xef\xed\x6b\x64\xc8\x96\xc6\xc3\x82\x88\xa8\x8b\x43\x67\x46\xce\xf5\x05\x5b\x22\x05\x8a\xb2\x3c\x2f\xbb\x41\x84\xf3\x51\x48\xf1\x66\x3e\x2a\x7c\xa1\xe9\x74\x6c\xec\x27\xe8\x0f\x70\xbe\x2a\xd0\x17\x38\x5d\x5d\xb3\xf6\x08\x15\xef\x10\x2c\x4f\x89\x9e\xa9\x32\xdb\xa9\x9c\x23\x7d\x14\xe6\x50\x80\xa2\x92\x5d\x43\xa8\x11\xdf\xed\xca\x07\x27\xa0\x48\xb3\x5c\x90\xc0\xe6\x09\x8e\xef\x74\xc8\x29\xb6\xd5\x86\xf0\x5c\x2d\x0f\x2f\xe5\xe3\x2b\x31\x77\x8f\x5a\xa5\x88\x85\x61\x5b\xf9\xd1\x66\xa7\xe6\xd2\xd5\x86\x73\xd3\x22\xc0\xb9\x4b\xdc\x5a\x78\x7d\x9d\x6d\x95\x71\x0a\x38\x2f\x3f\xe1\x24\x2f\xb1\x23\x09\xf1\xd1\xc6\x46\x15\x21\xdb\xaf\x6d\xd8\xc9\x58\x04\x0d\xab\x09\xed\xd5\x2a\x3d\x82\xaf\x73\x1b\x2b\x95\x59\x60\x47\x63\x92\x8a\x15\xf9\x20\xfb\xc8\xc1\x3c\xe4\x55\xda\x34\xb4\xbf\xe8\x0a\x17\xb2\x61\xa2\x5a\xd5\x66\x45\xb5\x94\xa7\xda\xaf\x25\x3b\x2b\x56\xe6\xcd\x19\xc6\x4d\xf9\x85\x1d\xe2\xb2\x22\x86\xe6\xb5\xe6\xe6\xe6\xf2\x69\x79\x02\x5a\xd2\xcc\xef\xae\xde\x0a\x78\xb9\x81\x2a\x5c\xd1\xfb\x3c\x9a\x8b\xb7\x16\xc6\x70\xad\x48\x9a\x35\x76\x3c\x15\xa1\x56\x24\xea\x6f\x17\x52\xc2\x5b\xbc\x76\xde\x6f\x15\x58\x42\xb8\xc4\xee\x05\xe8\x85\xd0\xd0\xd5\x35\x31\x4b\xa7\x61\xf4\x79\x8f\xeb\xef\x2d\x4b\x35\x48\xb2\x14\x47\x76\x92\xee\x82\xed\x1a\x48\x56\xc5\x7f\x28\xd2\xdb\x40\x2b\xe8\x3b\x99\x28\xbd\x76\x23\x79\x48\xd4\xcf\xd8\x95\xaf\xed\x2a\xa7\xdd\xa6\x90\x13\x88\xe2\xf6\x8c\x0a\x45\x8b\xe9\x72\x58\x05\x6c\x3b\xee\x9d\xa0\x81\xcf\xb1\xf4\x16\xc4\x2b\x0e\x8d\x10\xd1\x12\x59\x6e\x10\xea\x30\x49\xcc\xc5\xdd\xe9\x74\xe4\xfa\xde\x72\xcb\x3a\x9b\x4f\xc9\xa5\xcb\x0e\xdf\xee\x20\x16\xad\x04\x65\xbb\x51\xa8\x6a\x08\xec\x58\xf3\x32\x99\xfb\x2f\x83\xf7\x72\xf2\xd0\x15\x5a\x2f\x9a\xc2\x34\xb6\xfd\x7e\x48\x30\x1e\xac\x99\x9f\x8c\x58\x1d\x3c\xd2\x1d\x03\x17\x68\xf3\xd2\xae\x98\x55\x08\x36\x3b\x8f\x6a\xa1\x57\x55\x01\x7d\x6f\x12\xad\xd7\xbf\x6f\x4a\x19\xcc\xb1\xfc\x53\x7b\x0c\x1c\x64\xb4\xfc\x27\xdc\xfc\x5a\x62\xa1\xc9\x7e\xc0\x55\xb1\x2d\x7d\x99\x45\x4c\xf1\xc7\x2d\xa6\x2f\x42\xb8\x7b\x61\xc9\xa5\x25\x9c\x36\x67\x78\xe4\x7b\x3e\xaa\x15\xb8\x8a\xf1\x61\x3a\xe6\x48\x10\x55\x07\x56\xd7\xca\x8f\xc5\xa0\x94\xb9\x84\x5b\xd6\x7a\x30\x1c\x64\x97\x9e\x77\x2e\xd2\x60\x50\x76\xca\x52\xe6\x01\x86\x4b\x16\x69\xcd\x6f\x3d\xdc\x0f\xb8\x45\xc8\xba\xed\x08\x97\x77\x1a\xc7\x25\xa7\xdf\x34\xbf\x74\xde\xba\x19\xa0\xf0\xbc\xad\x7a\xbc\xc8\x7a\x8f\x17\xc1\x83\xe8\x56\xc9\xad\x0a\xa7\xf8\x0d\x84\xbd\x0e\x5d\xdc\xce\xcb\xd6\x0d\x49\xa6\x76\xa3\x58\xe4\x5c\xe9\x6e\x1b\x76\x91\xb9\xbb\x82\xd3\xc2\xdf\xd4\x52\x9b\xbb\x66\x24\x49\x09\x40\x61\x46\xf9\x0a\xf5\xe0\x50\x63\x9d\x35\xcb\xd2\xa1\x19\xe6\x32\x4c\xf9\xdb\xe2\x34\x16\xfe\x07\x21\x4e\x69\xfa\x54\x1e\x54\x4b\x01\x5b\xe7\x2c\x57\x2b\x48\x19\x5b\x37\xce\x3c\xb4\xec\xdb\x4f\x51\xdd\x5c\xf0\xc5\x9d\xc3\xe3\x82\x92\x49\x48\xf1\xf7\x21\x28\x10\xe7\x51\x95\x01\x3e\x8f\xa2\xcc\x9a\xef\x83\x9a\xbe\x3e\x75\x2c\x36\x43\xc6\xb8\xe6\xcd\x8e\x07\xb4\x6a\x66\xf6\x65\x33\x58\xc6\xbb\xe2\x11\xf2\x85\x2e\x50\xc8\x07\xfe\x58\xf8\xe5\x9d\xd5\xad\x66\xde\x5c\xcd\x8b\x98\x7f\xbb\x79\x5a\x08\xf1\xf2\x26\x5c\xac\xcc\x9a\xf0\xf2\x5e\xa9\xf9\x0e\x51\xf3\xcc\xa2\x12\xcf\x06\xd9\xd7\x22\xec\x97\x0d\xa1\xa7\xea\xbf\x55\x14\x3d\x55\xe8\xa6\x83\xfc\x9a\x21\xf5\xb4\x8e\x86\x0d\x30\x9f\x46\xd2\x55\x53\xc1\x4f\xcd\xf3\x18\x91\x80\xae\xb6\xe6\xa9\x19\x97\x28\xfb\xdb\xe6\x4a\xc8\x8a\x2c\x22\xc1\x4c\x31\xc5\x0a\x90\x61\xce\x49\xd9\xb7\x99\xa3\x71\xfd\x0e\x35\xa7\x49\x48\xb3\xf4\x13\xeb\x74\x13\x0d\xd4\xb7\xb3\x9f\x34\x83\x92\x08\x33\xd0\x9e\xc5\x54\x9e\xf2\x89\x26\x06\x75\xa2\xb3\x78\xf7\xa5\xc7\x4d\xce\x26\xe7\x92\x84\x0c\x08\x52\xb5\x09\xa9\x50\x3b\xf5\x1b\x90\x27\x2a\x8e\x39\xcd\xd4\x12\x77\x1e\xd9\x38\xd2\xb6\x4c\xb0\xd9\x53\x53\x92\x92\x5e\x25\x74\x5a\xd5\x86\xe1\xa0\xb3\x13\x4e\xa7\xc9\xa5\xf0\x71\xb3\x10\x79\xb5\x5d\x23\x40\xbe\x11\x38\xcd\xb0\xc4\x5b\xd5\x3d\x67\x1e\x44\xe4\x18\xcd\x7e\x74\xf0\x98\x3b\x47\x8d\xf1\x4c\xd8\xd7\x0a\x1c\x23\xd3\xf5\xba\x37\x9d\xfc\x54\x82\x8b\x23\xa7\xc6\x70\x15\x60\x59\x76\xf6\x4e\x7e\x55\x71\x5b\x50\xe2\x23\xd1\x49\x95\xc5\xf4\x9e\x2d\x9d\xd3\xb0\xcf\xbf\xc9\xa8\x39\xb2\x2c\x10\x38\xc9\xa3\x59\x12\xe6\x8f\x1e\x3d\x7a\x54\x1f\x2b\x47\x52\xd0\xfa\xbd\x44\xcb\xe1\x3a\xe0\xc6\x60\xe5\x85\xdf\xf5\xc8\xda\x83\x0d\xc0\x83\x0d\xc0\xef\xdb\x06\x40\x18\x00\x30\x58\x19\xcd\xc8\x1f\x83\xe1\x57\x8b\xae\xe0\xb3\x2f\xa8\x37\x07\x58\xea\x76\x21\x5a\x53\x98\x33\x52\x66\x3b\xd8\xac\xb0\x87\xc8\x08\x2e\x26\xc3\x21\xce\x71\x4a\x11\x4e\xcf\x0a\x28\x74\x9a\x67\xe7\x05\xce\x97\x0c\x57\x94\xe7\x24\x8d\xb3\x73\xd0\x5b\x18\x31\x0a\xd0\xe3\xc7\x22\xa7\xf3\xa7\x0f\xef\xdf\x51\x3a\x15\x5e\x4e\x39\xd7\xb4\xd3\xd0\x86\x1f\x16\x58\x9f\x70\xb1\x4f\x46\x69\xc6\x18\x41\x42\x52\xcc\x7a\x92\x66\x31\x5e\x32\xfc\x5a\x95\x6a\x54\x03\xbf\x98\x24\x6c\x64\x62\x63\x6b\xb6\x17\x6d\xe4\x9a\x63\xf2\x4f\xef\xf6\x57\xac\xea\xc6\xf9\x4a\xb3\x5d\x59\x4a\x4a\x0e\xac\x85\x8f\x12\x99\x65\xc3\x08\x90\x9f\x98\x80\x0f\x8e\x1d\xb9\x1b\x68\xd6\x4b\x65\x06\x61\x95\x37\xb7\xfc\x71\x56\xd0\x00\x51\x32\xc1\xd9\x8c\x06\xac\xc2\x3c\x00\x55\xf3\x79\x96\xc7\x01\x1a\xe3\x30\xc6\xb9\xb8\xad\x82\x5d\x85\x15\x40\x1b\x08\xfe\x5c\x5d\xa1\xa6\xa0\xfa\x24\x8b\xc2\x84\x25\x0e\x5e\x3c\x5b\x7b\x06\xc1\x51\xf9\x26\xc4\x6b\x66\x5b\xa2\xf8\x75\x75\x85\x7a\x2a\x9b\xb5\x87\x36\xa0\x59\x95\x26\x5b\x47\x1b\xaa\x23\x2a\x4f\xf4\x87\x75\x80\xff\x9a\x2b\x5f\x4d\x73\x3c\x85\x30\x64\xf8\xdc\x99\x55\x47\xbc\x12\x80\xfb\xc6\xa1\xc7\x88\xba\x8f\xc3\xf4\x1a\x2e\x63\x79\xd8\x7d\x4e\x16\x7a\x22\x84\xcf\x41\xe3\x64\x68\xb7\x65\x39\xa8\x31\x27\xc3\x10\xcd\xec\x0e\xd8\x37\x86\xac\x7a\x3e\x09\xfc\xaa\x85\xd3\x1a\x63\x73\x70\x55\xa8\xb3\x11\xd2\x12\x18\x0c\xf9\xdd\xfe\x8a\x0e\x5a\xca\x85\x31\x63\x4e\x8c\x29\x62\x30\x96\x67\x37\xa7\x22\x6b\x3c\xbc\x4a\x10\x99\x65\xad\xd9\x14\xa7\xad\xe6\xc7\xbd\x83\x43\x19\x67\x91\xd3\x16\xef\xdc\xfa\x92\xe1\x26\x0e\x66\xfd\xf1\x63\x7b\xba\xad\xd3\xe1\x0c\x2c\x6f\x9a\xaf\xc3\x82\x44\xa8\x89\x96\xa1\x0b\xaf\x67\x8c\x83\x18\x55\x2c\xa3\xe6\x40\xdd\x29\xaa\x7a\x3a\x34\x13\xaf\x02\x9b\xa7\x61\x81\x9f\xaf\x35\x9d\xf1\x6b\x27\xc9\xef\x80\x88\x5a\xcd\x4d\x60\xbd\xe4\xe7\x90\x1f\xc8\xa0\x7d\x3e\xc2\x9a\x42\x4c\x84\xc6\x29\x7d\xca\x4e\xe4\xcd\x00\x35\xd9\xe1\x80\x44\x50\x45\xf7\xa7\x42\xea\x27\xa5\x5f\x4e\x6b\x25\x29\x15\x96\x48\x2d\x3f\x7b\xe4\x19\xb6\x06\xbc\xb2\x2b\x1c\x18\x02\xc4\xca\x25\x6b\x3d\x54\x97\xf7\xf8\xd7\x4b\xc6\x55\x9b\x20\xa1\x39\xcb\x87\x87\xf7\xb8\x4c\x23\x43\x1b\xe0\xaa\xc0\x7d\x37\xe6\x53\xe3\xde\xdd\x1f\xc9\xb9\x6e\x29\x14\x6e\x60\x3f\xa9\x04\x74\x97\x80\xa4\x61\x7b\xed\x88\xc8\xc0\xb0\x3e\xf4\xb5\x84\x46\x5f\x1a\xb7\xe0\x05\x0c\x0f\xd9\x42\x86\x97\xaa\x7e\x31\xe9\xf2\x06\xc2\xf4\xc9\xec\x33\x63\xd8\xca\xd2\x14\x8b\x77\x2a\x92\xe2\xf5\xca\xb0\x6e\x45\x65\xeb\x32\x3a\xc2\x21\xbe\xa0\xa5\x0e\x0a\x58\xe3\x79\x8e\xb0\xf9\xb7\xbb\x55\xd7\xa5\x7d\x51\x7f\xcb\xd7\xa0\xb9\x6a\x17\x0f\xd4\x0b\x34\x10\xce\x21\x82\x4d\xc5\x01\x6b\x28\x41\x64\xbd\x2d\x85\xc6\x40\x34\x27\xa3\x11\xce\x79\x3c\x1f\x36\xfb\x20\x69\x29\xe7\x9c\x0c\x07\xf3\x08\x06\x7a\xe0\xa3\x1a\x3b\x2c\xef\x22\xf4\x03\x56\x37\xeb\x16\x77\x4b\xc1\x91\x72\x41\x43\x8a\xa3\x71\x98\x8e\xfc\x3a\x0f\xfe\x68\x41\x22\x3e\x8c\x2f\xe1\xb9\x00\x5c\x65\xaf\x31\x8e\xe7\xb2\xe0\x47\x76\x58\xdd\x05\x28\x46\x03\xca\xeb\x30\x23\x5e\x93\x7b\x0b\x37\x87\xa2\x80\x33\x78\xaf\xdb\xd4\x55\xdb\x0d\x69\x0b\x99\x77\x72\xee\x8d\x9c\xd5\x32\xe3\x45\xd7\x0e\x8a\xf5\xc6\x54\xc6\xac\x5d\x59\xd1\xd1\x4b\xef\x90\x97\x6a\x99\x9b\x99\x58\xc8\xf7\xcb\x01\xdc\x2e\xd4\x31\x01\xb1\xd2\xcc\x75\xa5\x6f\xf9\x93\x4b\x46\xef\xfc\x31\x31\x2c\x2e\x46\xd5\x15\x6b\x8b\x14\xa2\x7e\x6c\x33\x53\x25\x94\x48\xcd\xac\x2b\x9c\xb0\x23\x4f\xda\xa4\x68\x18\x92\x04\xc7\x1d\xb4\xc7\x8e\x96\xe7\x84\x1d\x97\x42\x08\xc1\x55\xbd\x9a\x8c\x36\x3d\x73\xa1\xf1\xa9\x54\x30\x2a\xd4\xc3\x29\xbc\x90\x1b\x40\x67\xa4\x82\x92\xc4\x03\xf4\xad\xfa\x27\x53\xd9\x0e\x99\x4f\xa3\x01\x6a\xae\x74\x7a\x4a\x07\x2a\x95\xa9\xcd\x14\xd3\x4f\x09\x29\x28\x4e\x49\x3a\x52\xd9\x4a\x09\x7a\xb2\x84\x0c\xeb\x34\xa9\xa7\xcb\x79\x8c\xb1\x32\x49\x4b\x13\x49\x36\x70\x3d\x31\x25\xad\x87\x89\x0e\xa9\xf1\xb0\x94\x16\x4c\x5a\x6f\x0c\x5e\x3c\x0b\x1a\xf6\x31\xa0\x31\xe8\xbf\xb8\x3e\x09\x56\x9f\x3d\x28\x30\x1e\x14\x18\xbf\x73\x05\x86\x7e\xc5\x00\xef\x33\xef\xe9\x05\x83\xb2\x4a\x35\x0f\xc7\xa7\x64\xc4\x0d\x8a\xa5\xe3\x51\x7e\xa9\x13\xbf\xc7\x43\x7b\x93\x50\xc1\x16\x2f\x8d\x27\x82\xd6\x66\xca\x21\x38\xbb\x38\x1f\xb3\xde\xb7\x6c\x6b\xb3\x97\xbc\x30\x7a\x82\x56\xca\x6f\x0c\xc1\x7c\x91\x9d\x34\xec\x97\x96\xc8\xff\xf6\x4f\x30\xfc\x8f\xe2\xe4\x19\xa6\x68\xe7\xf5\xe6\xae\x98\xe4\x18\x7d\xfb\x0d\x8a\xb2\xc9\x74\x26\x02\x9d\x9c\x5e\xa2\x49\x76\x46\xd2\x91\x11\xce\x6b\x0d\x45\xe3\x30\x87\xfd\x81\x5f\x33\xc7\xdc\x2e\x4c\xda\xde\x4b\xe8\x04\xf3\x17\x18\x34\x63\x0d\x72\x5c\x15\xa8\xb5\x89\x36\x50\xbf\x17\xa0\xd7\xec\x6f\x3f\x40\x9d\x4e\x27\x40\x7f\x41\x1b\x68\xf5\x59\x9b\x1d\xc8\x50\x31\xc5\x11\x19\x12\xbe\x90\x76\x0e\xf6\xfa\xab\xcf\xfb\xcf\x5d\x7b\x39\x52\x64\x90\x2e\xc6\x51\x76\xcf\x7a\xcd\x9f\x01\xb3\x8e\xb0\x01\xda\xf7\x84\xe6\x35\xb9\x90\xae\x62\x09\x26\xfc\x5b\xd8\xf5\x5b\x13\xca\x2a\x36\xe7\x91\x8d\xa8\xb9\xd9\xec\x30\xb4\x6c\x65\x31\xde\xa4\xad\x9e\xa1\x7c\x67\x63\x6b\xfe\xa5\x94\xcd\x19\x20\x7f\xaa\x0c\xc4\x4a\xb3\xa3\xe9\x54\xbe\xa2\x5e\x2f\x65\x17\xb3\xd3\x82\xe6\xad\xb5\xb6\x7c\x7c\x2c\x12\x7a\xc1\x9a\x73\xfd\xc7\x73\xa7\x09\xa1\xad\x66\xb3\x6d\xbf\x58\x4f\xdb\xb6\xa9\x58\x94\xc5\x6c\x70\xa9\xaf\xf3\x52\x66\x04\x98\x57\x1b\x68\x93\x09\x89\xf0\xf1\x72\x03\xfd\xa5\x5d\x72\xc2\xef\x99\x59\x31\xb1\x16\xa4\xf2\xce\x1a\x63\xf4\x14\x6d\xa2\x65\xd4\xef\x19\x46\x53\x3e\xc7\xf4\x32\xf8\xa7\x6b\x90\x75\xdd\xee\xfc\x94\x91\xb4\xd5\x6c\xb6\x5d\xb3\xcb\x68\x06\xae\x6b\x61\x8a\x3f\xec\xbd\x61\x84\xdd\xef\x49\xa6\x24\xcc\x15\x81\xf2\x3d\x14\xf7\x4d\xef\xf9\x9a\x4b\x70\x93\x2c\xfe\xf6\x9b\x7e\xaf\x8a\xd0\x6c\xfa\xd2\x0e\x81\x39\x35\x89\xc2\xb5\x54\x94\xe3\x49\x48\x52\xae\x02\x63\x79\xfa\x0a\x55\x78\xf8\xb1\xd9\x83\x00\xd6\x46\xd8\x2b\x6d\xc7\x77\x11\x30\x2b\x09\xa6\xcc\x77\xbf\xb5\xec\xfd\x74\x93\x20\x7f\xef\xa4\x94\xbb\x45\x0a\x50\xbf\xd7\x46\xff\x9e\x61\x6d\xb9\x54\x0b\xf7\x8c\x24\x6c\xe9\x7d\x6f\x8d\x55\x5d\xaa\xa4\xae\xcf\x9a\xa7\xf9\x8f\xaa\xb8\x3d\x3d\xac\x03\xf1\x7a\x01\xac\x03\x20\x41\x3c\xdc\x10\xec\x53\xce\x97\x7f\x72\xba\xa6\xb3\x69\xff\x24\x08\xb3\x6e\xbd\xe4\xca\x5d\x2d\x85\x79\x9d\xd7\x4f\x0c\x51\x80\x66\x13\xf9\xd4\xc8\x21\x2a\x06\xb3\xa9\x72\x4a\x7d\x0f\x8d\x2c\x29\x5a\xb3\x21\x5c\x2b\xb6\x66\x6a\xee\x58\x4e\xc7\xa8\x71\x9e\x9b\x84\xb0\x78\x2d\xdd\x24\x18\xcf\x2e\x44\x38\x4e\xd4\x7f\x6e\xb0\xb0\xd3\xb0\xc0\xab\xcf\xd1\x06\x94\xd1\x2a\xac\xd5\xe7\x96\x3d\x43\x1c\x63\xae\x17\x85\x3d\xb0\xc5\x0b\x05\xa8\xff\xcc\x96\x84\x55\x3f\x5f\x9f\x86\x69\x8b\x17\xb3\x99\x9f\xb3\x98\x85\x6f\x1a\x63\xe1\xbe\x66\x43\xa7\x99\xb5\x7b\xb1\xe9\x43\xe0\xcb\x34\xbf\x94\x2b\x9a\x2b\xbc\xc0\xf8\xf8\x23\x0f\xc6\x90\x66\x54\x08\x65\x2f\xc9\xab\xc6\x08\x24\x12\xee\xf3\x68\xa8\x91\x5a\x8c\x43\x2e\xad\xc1\xfe\x76\x11\x25\xb3\x82\x9c\xa9\xd8\x91\xe4\x94\x24\x84\x2a\x01\xe7\x34\x4c\x3f\x77\x4f\xf3\x30\x8d\xc6\xa8\xc0\xf9\x19\x89\xe4\x06\x18\x72\xd7\xaa\x8d\x97\x5d\xf2\xaa\xe3\xd2\x90\x8a\xe3\x50\xc8\x5d\x68\x88\x73\xb6\x0d\x85\xc9\x28\xcb\x09\x1d\x4f\x50\x8c\x8b\x28\x27\xa7\x9c\x2d\x09\xf9\x07\xa7\x9d\x73\xf2\x99\x4c\x71\x4c\x42\x10\x82\xd8\x57\x77\x27\xa5\x38\x4f\x43\xfe\x0e\xe4\xd3\xeb\x30\xfd\xfc\x49\xf8\x75\xfd\xc4\xe7\xf5\xbf\xf8\x5e\x8c\x34\x1d\x7d\x62\x43\xfc\x04\x0f\xa3\x3e\xc5\x64\x44\x4a\xef\x52\xe4\xd4\xf8\x28\xf2\x54\xee\xa9\x72\x06\xa4\xe3\x20\x9a\x79\xb6\xd9\x05\x68\xf5\xb5\xbb\x22\x4f\x1d\xb6\x28\x66\x74\x8b\xef\x53\xcd\x3f\x6d\x37\xd7\x97\xbc\x3c\x53\xf0\xd8\x96\xb3\x73\xb7\xcc\x0a\x96\x51\xb3\x07\xa2\x12\xb4\x62\xda\xee\x30\x74\xbc\x61\xd8\x40\x1b\xa8\xc5\xc5\xa9\xd6\xb7\x2f\xd0\x53\xdd\x44\x5b\xbe\x81\x78\xba\xe2\xec\xb7\xca\xd1\x88\xdd\x94\x51\xa7\x68\x70\x8e\x2a\x4b\x30\x11\x03\x57\x40\xd8\x3c\x82\x37\x49\x0b\x4a\xe8\x8c\x4a\xef\xc4\x24\xc6\x29\x65\x9b\x96\xeb\xc1\x9e\xd7\xb2\x93\xc6\x24\xc7\xb6\x1d\x86\xfd\x60\xa8\x08\xa4\x2c\xab\x5e\x0c\xc1\x13\xb0\x86\xd1\x52\x03\x9a\x6a\xe8\xb6\x1a\x37\xe1\x45\x76\x4f\xbc\x1e\x8b\xed\x23\xb0\xcd\x19\x9a\xdb\x87\xef\xd8\x3c\xc8\xa7\x3a\x26\x06\x8c\x54\xd5\xb7\xb6\xc3\xaf\xb3\x3a\x7e\x2d\xdf\x05\x72\xe4\x8a\xf0\xd8\xa4\xe0\xcf\xfe\x4c\x3e\x5e\x92\x3b\xc1\x59\x4c\xa5\xbc\xa9\xf6\x22\x8f\x32\x44\x2a\x41\xf8\xdb\x40\xde\x92\x12\x3a\x77\x0c\x57\x34\xb5\x72\x02\x31\x5d\xdb\x10\x4e\x56\xa6\xf0\xa7\xfd\xd9\xcc\xd5\x9f\x2b\x2c\x40\xd7\x2b\x9f\x42\x9a\x63\xd6\xe1\x0d\xbc\xa3\x0a\x50\xe6\xd1\xe4\xc0\xd8\x4c\x0d\x4e\x79\x94\x0e\x25\x74\xff\xfa\xa7\xed\xe3\xde\xd3\x6f\x4f\xbe\xac\x5c\xb7\xb6\x0f\xdf\xb1\xdf\x9b\x4f\xff\x72\xf2\xa5\xbf\x7a\x7d\xa5\x3e\x56\x7b\xc1\x6a\xff\xba\xfd\x0f\xdd\x0e\x05\xc5\xa8\xda\xc0\xad\x47\x86\x55\x8c\xc1\x00\xe7\x6f\x0d\xfb\x37\x44\x98\x78\x4f\x0a\xa7\x7f\x2f\xda\xde\xe8\x25\x78\x3f\x78\x7b\x53\x5e\x49\x0e\xe2\xf4\xa0\xcc\x97\x4a\xab\x6b\x10\x68\xdf\x9f\xf7\xec\x96\xc3\x1e\x22\x92\x56\x0c\xdc\xe2\x3e\xf7\x33\x74\x2f\x1b\x59\x68\xf0\x2b\xbd\x85\x4c\x50\xb9\x48\xc9\x46\x5a\xcc\x26\x0c\xf0\xa8\x10\xc7\x87\x49\x16\x3f\xfd\xf6\x9b\xa7\xfd\x9e\xca\x86\x33\x2e\xf4\x2e\xca\x12\xd4\xda\x39\xd8\xeb\xee\x6c\x6f\x21\x76\x6e\x18\xac\xf4\x7a\xab\x6d\x97\x27\x1b\xd5\x96\x4f\xa1\x46\x6e\x69\xe0\x32\x6f\xc1\x61\x8b\x33\xe1\x4a\x80\x56\x16\x33\xbc\x35\x99\xaa\xb5\xa5\x20\x3c\xea\xa0\x3f\xed\x6f\x7f\x5f\xf2\x26\xa9\x0a\xf8\x47\x53\x59\x63\x79\x52\x0d\xc8\x79\xc3\xd3\x04\xd0\x02\x57\x83\xa5\x21\x7f\x13\xa0\xb5\x36\x1a\xa0\x66\x73\xa1\x71\x47\x09\x81\x57\x71\xaa\x83\xa0\x7c\x22\xa9\x3b\x3e\x86\x85\xef\x37\x7f\xd8\x7b\xfb\xe7\xbd\xfd\x7f\x74\x67\x15\xea\xa8\x98\x53\xb7\x7e\xef\xe4\x72\xa0\x3b\x8f\xbd\xdf\xbf\xf9\xc8\xc5\x6a\xf2\x9f\x4b\xca\x07\x8f\xf2\xd0\x4a\x15\x94\x86\x17\x7a\xce\x21\xe6\x5d\x94\x18\x9c\xcf\xd7\x9a\x75\xe8\x28\x0f\x78\xcd\x38\xc4\x56\x1e\x65\xe4\xf9\x43\x9d\x52\xac\x13\x2a\x3f\xa3\xd8\xe7\x99\xfe\xf3\x76\x80\x56\x7a\xca\x9b\x9b\x25\xe5\x49\xf4\x3a\x83\x94\x85\x17\x5b\xa0\x15\xfe\xb7\x76\x20\x8b\x2b\xf5\x4d\xbd\x62\x63\x60\x7f\x5e\x9f\x04\xab\xcf\x1f\xd4\xf8\x0f\x6a\xfc\xdf\xb9\x1a\x5f\xa8\xf0\xa7\x51\xbd\x19\xe2\x1d\x0c\x07\x1b\x3a\xb4\x59\x63\xfd\x46\x91\xcd\xe6\x98\x1b\x72\x3d\xd3\x34\xf2\x1a\xb4\x4d\x43\x3a\x0e\x50\x8a\x2d\x23\xf6\x4f\xa0\xb9\x28\xbd\xa2\x95\xd7\xd7\x66\x74\x65\xe9\x82\x41\xd8\x14\x81\x1d\x12\xfb\xc3\x53\x75\x56\xa4\x6e\x7d\x81\x2b\x52\x99\xd0\xfa\xc2\xa0\x07\xba\xbc\xf2\x47\xea\x14\xeb\x64\x69\xab\x09\xa3\x6a\x9a\xd1\x48\xdb\x96\x19\x78\x91\x31\x26\xc6\x1f\x46\xee\x7c\xdc\x42\xfa\xae\x99\x3f\x97\x6c\x06\xc8\x0c\xe2\xff\x89\xb3\x41\x71\x09\xde\x72\x3d\xa2\x7a\x7b\x90\xc6\x66\xfb\x46\xf3\x95\x95\xa1\x25\xf5\x54\xe2\xfd\xce\xc1\xe1\xf6\x2e\xac\xa0\xad\xbd\xdd\xdd\xed\xad\xc3\x9d\xbd\x5d\xb4\xbf\x7d\xf0\x71\x6f\xf7\x60\xfb\xa0\xb2\xd5\x38\xa4\xa1\xd9\x2c\xfb\x36\x37\xa7\xee\x13\x61\x0d\x39\x09\x2f\xa2\x6c\x32\x4d\xf0\x05\xa1\x97\x03\xf4\x1c\x28\xcb\xe9\x21\xe8\x42\x95\x6d\x02\xab\x4a\xef\x37\x6d\x4f\x48\x19\x61\x87\xf0\xc5\x8e\x24\x0d\x07\xbf\xd8\xb5\xa7\x10\xdd\xe1\x11\xb6\x81\xbf\xc4\xe8\x7c\x4c\xa2\x31\x9a\x84\x34\x1a\x0b\xf1\x95\x6f\x42\x8c\xa1\xc5\x56\x39\x4f\x04\x07\x68\xda\x1f\x6a\x1a\xae\xa3\x4a\xbd\x05\xab\x04\x7f\x20\x58\x32\x6c\x7d\xf2\x13\xf2\x31\x3c\xf4\x23\xf1\x49\xd9\x27\xbe\x2a\xcc\xc6\x2a\xc0\xd6\x4b\x50\x6e\xd0\xee\xca\x60\xb6\x50\x8d\xe8\xbb\x5b\xd1\x75\x09\x8b\x43\x92\x63\xcb\xbd\x81\x8b\xae\xaa\xf1\xb0\xa1\x78\x5a\xaf\x01\xd7\x91\x75\x4d\x73\x17\xfd\x2f\xc6\x09\xa6\xb8\xae\x06\x77\x30\x2e\x6e\xcc\x27\xe5\x3f\xb2\x5d\x0b\x08\x51\x10\x04\xaf\x0f\x94\x3b\xdc\x7e\x2a\xe5\x9e\x7f\x50\xc6\xfd\x6d\x13\xda\x59\x5a\x92\xc2\xa0\x4d\xc2\x4b\xae\xda\x03\x9e\x97\x32\xe1\x4f\xf3\x3c\x43\x3c\xb2\x0b\x9b\x3e\x64\xcd\x55\xe5\xb2\xc1\xc0\x91\xd7\x7e\xe0\xee\xed\xb5\x97\x66\xb9\xc4\xdf\x6c\x3f\xdd\x7a\x77\xb4\xfb\x8f\xdb\xfb\xaa\x9e\x18\x47\xe3\x59\xfa\x19\xc7\xe2\x71\xcc\xff\xcf\xde\xbf\xef\xb5\x91\x2b\x8b\xe3\xe8\xdf\xc9\x53\x28\x39\x67\x0d\x66\x30\xc6\x37\x0c\x71\x86\x59\x9b\x18\x12\xd8\x09\x21\x5f\x20\x33\xb3\xbe\x6c\x26\x9f\xb6\x2d\x43\x27\x76\xb7\x77\x77\x9b\xcb\x9a\xf0\x7b\x9f\xf3\x1c\xe7\xc5\x7e\x1f\x95\xee\xb7\xee\xb6\x81\x4c\x66\x16\xac\xbd\x33\xee\x6e\xa9\x54\x92\x4a\xa5\x52\xa9\x2e\xd4\xfd\x95\xfd\x89\xe0\xc4\x6b\xff\x73\x7b\xfa\x3f\xd1\xff\x24\x67\xff\xfc\x9f\x3f\xd6\xce\xab\x4b\xb7\x5f\x57\x57\xbf\xfe\xb1\xb4\x0c\xf1\xa5\xff\x70\x96\xff\x9f\x33\x5e\xe3\x94\xd5\x39\x23\x95\x4e\x79\xad\xb3\x53\x77\x3d\xb3\x96\x56\xc9\x53\x47\xb6\xa5\xb4\x24\x1a\x52\xea\xb0\x6b\x3e\xf2\x79\x89\x73\x52\x6d\x04\xec\x35\x0b\x03\xaf\xf0\x97\xb5\x35\xb8\x03\xc5\x2c\xb6\x07\x84\x0d\x01\x00\x4f\x2d\xd2\x27\xdf\x7a\xe4\x93\xbe\x72\x59\x10\x1d\xa3\x0c\x5a\x41\xd4\x99\x57\x13\xd5\xc5\x9d\xb5\xc1\xc9\xec\x08\x0d\x2e\xfb\x30\xc0\x5d\xa9\xc5\xac\x69\xa8\x31\xcd\x53\x7b\xf1\x49\x64\xb6\x4c\x64\x90\x96\xa0\x9f\x19\x77\x41\x1c\xbf\x68\x80\xc7\x90\x55\x81\x7b\xa4\x6a\x75\x06\x63\x1c\x24\xdc\xac\xcb\x68\x85\xbd\x36\x16\xb4\xbb\x10\x04\x9e\xc8\x38\x20\xd7\x1e\xa7\xd7\x37\xf7\x3a\xf5\x2f\xd7\xd6\x92\x8d\x99\x5a\xfe\xb6\x8a\x1a\xf5\x7a\x1d\xfd\x48\x2f\x67\x1c\x77\xad\xce\x28\x16\xe0\x7e\x08\xa3\xc3\xc7\x8b\x70\x90\x14\x33\x7a\xa1\x69\x69\x98\x7b\xe2\xfc\x43\x65\xcf\x98\x41\x22\x90\x6f\x89\x59\x5b\x5a\x08\x53\x16\x41\x33\xb2\xeb\xb6\x7c\x86\xb6\x5e\x2d\xce\x22\x19\x11\x1e\xf9\x86\x6d\xa1\xc1\x70\x98\xaa\xa9\xba\x99\x95\x83\x2d\x8d\x51\xf5\x70\xf5\x29\xdd\x70\xf9\xc1\x80\x9d\xb5\x43\x9a\xb3\x80\x71\x3d\xd8\x8d\xf9\xad\x90\x78\xbb\x3f\xa4\xb5\x82\x24\x09\x2f\xb1\xca\x70\x83\xa1\x98\x3d\xde\x5e\x0e\x87\x75\x94\xd6\x52\x16\xb8\xed\x4c\x15\x99\x82\xbb\xde\x87\x43\xf4\xf5\x2b\x7f\x3a\xad\x9f\x89\x2d\x13\xae\xb0\x29\x6e\xb2\x34\x7b\xa1\xd7\xa0\x2f\x55\x89\xce\xb9\x79\xa1\x2d\x81\x4d\x9e\xc4\x4b\x8a\xd6\x44\xc3\x1c\x36\xdf\xd5\xf8\x3a\x52\x57\x2a\x4d\xe8\xcf\xcc\x52\xa8\x58\x0e\xa7\x2f\xd0\xb8\x93\xfd\x7d\xa8\x0c\x33\x23\xe2\x9c\xa1\xb5\x6d\x3e\xc5\xd1\x7e\x14\x27\x15\x32\x2e\x5f\xf0\x0d\x3d\x29\xba\x3a\xa0\x47\xb4\xa9\xb8\x0b\xd5\x2e\x82\xf4\xf0\x2a\xfa\x00\x49\xa7\xb2\x1b\x48\x2a\x68\x70\x01\xcf\xf0\x7c\xc1\x37\x67\x7e\x7b\xcf\xa5\x38\x42\xfb\x1f\x7a\x4b\x66\x16\x7d\x26\x5b\xe4\xc0\xb4\xcc\x2c\xe4\x32\xe9\xa9\x01\x15\x59\x60\x75\xa4\x1c\x37\xc2\x14\xa5\x59\x48\x33\xd2\x84\x43\x85\xa8\x55\x53\x51\xef\x80\xbb\x6d\x3b\x2b\xfe\xd3\x12\x97\x03\xc8\xee\x91\x60\x86\x47\x95\x52\x81\x8e\xd5\x79\x1c\x61\xa6\x79\xaa\x3c\xfb\x64\x8a\xfd\x57\x49\x98\x41\xf0\x17\x83\x1b\x29\x45\x8c\x23\xd4\x27\xfb\x0c\xc5\xe3\x25\x3e\xf3\x41\x67\x0a\x24\x67\xd7\xf3\xbc\x0a\x9e\xaa\xf4\x63\xc0\xc5\xc7\x8a\x7b\x0d\xd2\x36\xd9\x97\x1a\xe7\x9e\x63\x88\xf8\x30\x15\x8f\x12\x72\x1c\xa1\x0a\xcf\x9a\x1c\xcb\xa1\xe2\xbd\xec\xea\x55\x8e\x15\xf4\x62\x62\x83\xa0\x6a\x55\x6a\xd0\xbb\x52\xb8\x8f\x22\xe3\xcf\xb5\x4f\x1a\x66\x77\x4c\x9b\x68\x9c\x51\xac\xd8\x42\xff\x8a\x67\x60\x79\x2d\x9d\xe6\x9e\x2f\xad\x98\xec\x76\x65\xe9\x39\x4a\x55\x63\xed\x9a\x6a\xbb\x2d\xf9\x20\x78\x95\xc5\x19\x4a\x67\xd3\x69\x9c\x64\xa0\x5b\xa3\x37\xb5\x1f\x7a\x48\x68\x55\x96\xb4\xa8\x96\x7e\xc2\x2c\xed\xbd\xb0\xc8\x62\x2c\xa6\xb2\xb9\x28\xcc\x79\xac\x07\x9a\xca\xb5\xaa\x57\xa2\x03\x9b\xbb\xa9\x37\x74\x8f\xad\xc7\x95\x23\x68\x85\xa3\xf7\x38\x9b\xdf\x9e\x55\x5b\x1b\x8f\x2a\xdd\x47\x95\xee\x7f\x84\x4a\x97\x39\x56\xdc\xc9\xab\x7c\x3b\x48\xe2\x08\xbd\x9d\x4d\x82\xcb\x30\x45\x3f\x05\xe4\xf1\xbf\xbe\xd0\xc7\xda\x04\x3b\xd5\xbd\x6b\x6b\x68\x3f\x0a\xb3\x30\x18\x87\xff\xc6\xe8\xbf\x29\x16\x84\x50\x03\x94\x82\x25\x16\x37\xb8\x81\x8e\x92\xa5\xaa\x45\x6c\xaf\x81\x56\x97\x57\xe3\xf9\x52\x58\x16\xb2\xfd\x61\x17\xd5\x8b\x6e\xde\xa8\xb5\x07\xe9\xbe\x19\x23\xd8\x69\x66\xe2\x8c\x0d\x2c\x3d\xd3\x78\xc2\xb3\x11\x1f\x50\x68\x49\x1a\xf4\x38\x22\x0a\x53\xef\x94\xaa\xa2\xea\x19\x71\x30\xfc\x13\x0f\x24\xec\x8c\xa9\xa4\xf4\x80\xb4\x67\x61\xbf\xf6\x94\x0d\x8f\x27\xae\x30\x6d\xab\xca\x1a\xd3\x22\xc0\xd2\x4f\xcb\x3e\xd5\x32\x6f\xd2\x32\xe6\x11\x66\xbb\xcf\x96\x94\xd4\x63\xc1\xf0\x92\x9c\x51\xf9\xec\xa0\xfd\x1d\xf8\xc2\xb1\x13\x93\xb6\xb2\xe2\x0b\x9a\xe4\xf2\x13\xf2\xf9\x04\x51\x0f\x22\x0b\xb8\xfc\xcc\x9d\x86\x58\xff\xc5\x7b\xee\x2d\x44\xff\x4b\x8e\x51\xdc\x6f\xa8\xbc\x39\x06\x1f\x19\x71\xa4\xe4\x66\x45\x6e\x03\x1e\xa1\xc2\x2b\x4a\x8a\xe7\xf0\x29\x37\x1a\x70\x1b\x6d\xf0\x61\x30\xda\x33\x72\x35\xd3\x97\xc6\x15\x29\x0d\x58\xa4\x68\xa2\x59\xa1\x7f\x8a\xb6\x6b\xf8\x12\x27\x37\x15\x1e\xf0\xf9\x38\x8c\xce\xc7\xf8\x80\x0e\xf8\x32\xea\x22\xe7\x07\x09\x89\x4d\xb6\x40\xc4\x5d\x9c\x4d\xa0\x38\x7e\x09\x91\x9e\x53\x93\x62\x57\xc4\xdf\x51\x3a\xd5\x2c\x8e\xd8\x07\x3e\x3f\x5b\x5b\x5b\x94\x6a\xd4\x42\x2c\xa6\x04\x2f\x4b\x4e\xe2\xa4\x18\xbd\xec\x5d\x72\x81\x63\x92\xad\x11\x36\x73\x6d\x8d\x66\x6d\x14\xaf\x58\xe0\x79\x95\xe5\xf0\x55\x5a\x2a\x64\x3e\x65\x4f\x7d\xf0\x52\x2b\x60\x52\xaf\x48\x21\x7f\x14\x73\x86\xb8\xc1\x75\x28\xac\x94\xb2\x5a\x06\x8a\x8b\x3d\xc3\x65\x50\x29\xb1\x45\x51\xe1\x1c\x69\x19\x8d\x42\x3c\x1e\x1a\x06\x09\xac\x15\x0d\x53\x83\x13\xa9\x08\x1a\xec\x88\xa2\x66\x90\x21\x7f\x6d\x24\xb6\xe0\x64\x61\x47\xf9\xb2\x98\x1e\x2f\x5b\xe3\x0c\x8f\x3f\x33\xc6\xa7\x6b\xe7\x2d\x09\x92\xc1\x63\xd2\x23\x11\x0e\x37\x1f\x85\xc3\x47\xe1\xf0\xef\x2d\x1c\x4a\xaf\x3d\xba\x68\xee\xcb\x6f\xef\x7e\x6e\xf4\x49\x91\x03\xae\x84\xf4\x9a\x30\xc3\xe9\x91\x25\xa3\xe1\xb6\xc9\xe4\x51\x39\x5b\xb2\x78\x72\xfc\x2b\x79\xd4\xae\xf3\x81\xe7\xc9\xef\x52\x32\xa8\x2b\xc5\x20\xed\x80\x9a\x94\x51\x9a\x47\xab\x1f\x94\x1a\x34\x98\xb5\xab\x8a\xf1\x45\x53\xc5\x82\x1e\xe1\x20\x88\x82\x73\x2c\xfd\xf8\x09\xcb\xa2\x43\xa1\x29\x08\x78\xc4\x0f\x59\x5c\xd9\xef\x27\xda\x08\x59\x80\x93\x49\x81\x55\xfc\x10\x13\x0e\x13\x46\x7a\x08\x53\x43\x28\xec\x07\x29\x8d\xce\xe0\x4b\xb6\x71\x8e\x21\x10\xa7\x63\x93\xd2\x83\xe9\x9b\xb1\x53\x79\x9b\x7a\x7b\x40\x62\xd6\x40\xb0\x36\xbc\xe9\x34\xb4\x88\xa9\x4a\x86\x20\x41\x1c\xc2\xba\x8f\x07\x6a\x4c\x89\x60\x23\xde\x14\x45\x72\xa6\xf1\x46\xe5\xd5\x05\xcb\x3e\xc2\xb6\x5d\xd1\xcf\x1a\x3a\x20\xac\x3c\xc4\x29\xcb\x2f\x0e\xe3\x61\x85\xd8\xd4\x82\x97\x96\x1e\x37\xde\xa9\xaf\xef\x67\xe3\xb1\x0c\xa1\x51\x25\x52\x24\xbe\x0e\xe1\x32\xcd\x35\x76\xdf\x67\x8a\xa5\x7b\xcb\x2c\x84\x9e\x3e\x54\x6a\x21\x6b\x24\x4b\x25\x14\x32\x73\xb8\x88\x6c\x39\xfa\xf1\x0c\x15\x64\xc8\x39\xfa\xd0\x63\x49\x34\xf2\xd3\xe3\xc8\x61\x63\xd1\x6c\x29\xe1\xc1\xa0\x8b\x73\xa8\x39\x4c\xb4\xe8\x3e\x29\x69\x8f\x92\x46\x74\xe2\x95\x63\x38\x14\x28\xb9\xa3\xf1\xcc\xc1\x5d\x3d\x14\xe4\x72\x79\x74\xd4\x36\x64\x48\x11\x51\xb2\x8a\x20\x7f\x7a\x5e\x96\x2c\xf4\x4f\xf9\x9b\x52\x37\x54\x39\x23\xec\x40\x79\x2c\x48\x1e\x60\x8d\x3a\xe5\xf7\x22\x7b\xaf\x3a\xf2\xf6\xc0\xd3\x18\xc3\xf9\x67\x45\x3d\xe2\xb2\x67\xec\x1d\x69\x60\xf4\x64\xc1\xea\x10\x9a\x9b\x15\x84\xa5\x81\x63\xcf\x3f\x9d\x05\x04\x28\x67\xfa\x15\x36\x16\xae\xac\x4c\xdc\xab\x8a\xd3\xe1\x1c\xc9\x99\x02\xbe\x6e\x4b\x0d\x42\xb9\x34\x4b\x6a\xac\x59\xb6\x8f\x98\x81\xa0\xe7\x8e\x2f\xcd\xe4\x09\x28\xac\x2c\x03\xb5\x20\x7c\xd3\x72\x28\x89\x63\xca\x3c\xba\x11\xae\xe4\x00\x2d\x94\x47\xc5\x91\x17\x49\x5a\x55\x7c\x58\x03\xc8\xa3\x02\xd3\x51\xd6\x65\xb0\xfe\x67\x75\x10\xc9\x07\xde\xe1\xfe\x2c\x1c\x0f\x61\x18\x59\x57\xc9\x47\x2b\xa4\x2f\x6c\x03\x27\x87\x3b\x87\xcf\x9e\x3d\x03\xa1\x7f\x29\x45\xb3\xf3\xf1\x4d\x8d\xa5\x4f\x23\xc7\x84\x59\x4a\x76\xca\x4c\xb4\x12\x29\x51\x74\xc9\x6f\x6e\x83\x23\x6e\x53\x08\x3b\x81\x0f\xc2\x37\xbb\xa1\xa5\xb8\xea\x7f\x3e\x25\x9f\x4f\xeb\x67\x67\x44\x18\x53\x1f\xbf\x7e\x15\x36\x9e\x66\x51\xfa\xa3\x01\x75\x48\x5f\x5e\xba\xaf\x95\x0c\xe8\x50\x12\x29\xbc\xd9\x1a\x5e\x31\x50\xbe\x1b\x57\x71\x01\x2f\x0d\x54\x69\xee\x97\xc8\xed\xc2\x85\xac\x00\xe7\x4c\xaa\x97\x67\xe4\x79\x42\x5f\x23\x23\x75\x87\x2b\x47\x83\x76\x82\x87\xb6\x74\x39\x53\x5e\x62\xc9\xbb\x1c\x2d\x09\x8d\x30\xc5\x73\x98\xf1\x99\x0d\xd3\xf2\xac\x1d\x95\xc5\x09\x03\x4a\xc3\xd8\xc6\x99\x47\x46\x47\x57\x03\xe3\xef\x82\x79\x65\xc5\x48\x85\x7c\x2a\x1b\xdc\x9a\xe7\xf7\xc2\xd1\xb0\x58\xd1\xc8\xf8\xe6\x2c\xc1\x70\x9b\x7a\xf4\xa1\x27\x22\x38\x51\xb3\x97\x41\x10\x09\x11\x34\x8c\x98\x2a\xc6\x1d\x2b\x2a\xb1\x63\x57\xd6\x6a\xb5\x5b\x35\xb1\x9d\x19\x8b\x4f\xea\x37\x79\x55\x07\x7f\x2d\x0a\xe6\xef\x8d\xb3\x3f\x0f\x11\x72\x1a\xd0\x83\xf1\xab\xb3\xe6\x1b\x68\x56\xd3\xa3\xee\x39\xe0\xf6\x32\x65\xaf\x0a\x5f\x3c\x6a\x83\x1e\xb5\x41\x7f\x6f\x6d\x10\x53\x05\x0d\xfb\x77\xb8\x25\x74\x69\x83\x84\x0e\x47\x55\x07\x51\xe6\x24\xf4\x3c\x3b\xaf\x34\x3e\x42\xba\xa1\x87\x07\xb1\xf4\x17\xd0\x53\xe3\xf5\xcb\x9c\x0c\x69\x4f\xa5\xbc\x94\x56\x1c\xf6\xff\xec\x32\x0c\xfd\x81\x0c\xe9\xca\x90\x83\x2a\x04\xb2\xb2\xb3\xb0\x42\x96\x32\xa6\xe2\x42\xd0\xd0\x42\xcb\x5d\xde\x9f\xa9\x69\x3a\xcb\x84\xa3\x5b\x84\xaf\xd8\x68\x56\xe4\x76\x49\x84\x8e\x2e\x5a\x12\xe5\x8c\xd4\x21\x5d\xb4\x34\xec\x7f\x72\x7d\xe5\x62\x62\x4b\xe0\x24\x1a\x3d\xc7\xe5\x1a\x15\xe5\x9c\x8d\xba\xbe\xf2\x46\x9b\x76\xa3\xd3\x59\xb6\x87\xaf\x8b\xbb\xb9\x87\xaf\x7d\x7d\xd4\x3f\xe5\x77\xb0\xb8\x2d\x5a\xc8\xd7\x35\x77\x5b\x46\xbf\xd8\x66\x74\x2a\xcb\xf1\x89\xa8\xca\x41\xae\xb2\xae\x57\x19\x5a\x50\xf8\xcc\xb3\x73\xed\xbc\x92\xbb\x16\xa5\x9d\xe7\xdd\x16\xd9\xa2\xda\xf5\xc7\x2d\xea\x71\x8b\xfa\x7b\x6f\x51\xf2\xc2\x02\x67\x17\x0b\xdd\x56\xb0\xc2\xf7\xeb\xc1\x28\x93\x96\x18\xbb\x9c\xfc\xc0\xef\x3c\x5c\x57\x23\xee\xdb\x91\x82\x6d\x93\x14\xe1\xa6\xcb\x46\xa1\x29\x7b\xcd\x8b\x0d\x7c\xed\x0d\xe2\x68\x14\x9e\xf3\x62\x4a\xf6\x1f\xb5\x34\x4f\x26\xc3\x8b\x5d\x31\x17\x37\xed\xda\x86\xbd\xe4\x65\x5e\xd3\x1c\xe8\xc6\x60\xc0\x4b\x5e\x64\x3f\x3d\xbe\x89\x06\x74\x8b\x51\x4b\xa5\xf4\x2d\x2f\x46\x58\x71\x82\xcd\x42\xec\xad\x80\x45\x83\x19\xa9\x25\xc2\x7e\x10\xf1\xcf\x34\x42\xa2\x85\x0f\x7f\x2d\x84\x10\x88\xaf\x56\xe6\x26\x41\x68\xc2\x99\x3f\x21\xa8\x06\xeb\x67\xcb\xe8\x87\x1f\x10\xfb\x5d\x03\x4d\xe1\xe1\xa8\xb2\x54\xbf\x5e\xa2\x61\x4e\xea\xcb\xe8\x9f\xe8\x39\xcb\xf7\x75\x8e\x69\x52\xd6\x57\x37\x7b\x41\x7a\xf1\x1c\x75\x1d\x5f\xa8\xb6\xf7\xb9\x94\x15\x94\xc4\x57\xaf\x93\x78\xf2\xea\x1b\xe0\xbb\x24\xb1\x52\x52\x2a\xbd\xba\x81\xb6\x09\xea\xdb\xd1\x70\x9f\x54\x55\x93\x99\x39\x0b\xd3\xde\x88\xe2\xb2\x57\xbc\xc3\x4a\x95\x5e\x3c\x8b\x4a\xdd\xe9\xdc\x4f\xcf\x9c\xcd\xd3\x99\x31\xba\xe5\x29\x49\xfb\xc6\xfa\x24\x10\xde\xcd\x2e\xee\x5b\x92\x2d\x2d\xc5\xde\x93\x10\x0b\x29\x7a\xfd\x42\x2c\x3d\xa3\x53\x76\x13\x62\x27\x46\x53\x15\x99\x69\x01\x1e\xd3\xf9\x51\xb8\x7d\xfa\x94\xc2\xaa\x51\x33\x1c\xe1\xce\xb1\x9b\x5d\x48\x25\x43\x15\x2d\xa9\xa9\x90\x97\xaa\x0c\xa9\x73\x9c\x75\x3d\xaa\x34\x1e\x2e\xb3\xa6\x56\x64\xed\x56\x99\xfa\x47\xab\x7c\x19\x8c\xb5\x7c\x5d\x35\x23\x11\xf5\x65\x30\xb6\xe2\x61\x88\x77\xb7\x4f\x61\x3c\xe7\xea\x0a\x0b\x35\xb7\x48\x67\x58\xd5\x45\xba\xc3\xaa\x96\xec\x50\x99\x23\xce\x39\xce\x58\xe8\xc4\xbd\x64\x5a\x2c\x93\xcb\xb2\xb6\x68\x2e\x57\xaa\xb3\x14\x97\xd2\xeb\x55\xe7\x91\xe0\x55\x30\x06\x13\xc6\x42\x14\x58\xc1\xbc\xf6\xed\x22\xe2\x88\xa0\xd8\x30\x6a\x17\x57\x5d\x74\x2a\x05\x06\x7a\xa9\xc5\x7a\xf1\x5a\xde\x61\x99\x25\xd4\x94\xe6\x94\x0d\x89\xc2\x4a\x36\x48\xe3\xf6\xa6\xab\x82\xa1\xdf\x44\xfc\x16\x51\xc8\x73\x2c\x8c\x93\xe0\x1c\x6f\x67\x65\x4e\x86\xac\x68\xde\x30\xb9\x0a\x89\x73\x5b\xce\x40\x51\x3f\x6e\xca\xfd\xb3\x18\x0e\x4f\xf3\x8c\x8c\xb3\x6f\x2c\xd6\x61\x41\xb7\x48\xa9\xbc\x1e\x19\xdf\xbf\xfd\xac\xbb\x49\x9b\x31\xa1\x22\xc2\xa6\xdc\xd1\xe8\x9d\x10\x8c\xee\xd6\x2d\x17\xb6\x55\x8b\xe3\x48\xdb\x4f\xc2\x4b\xd0\xed\x7c\x54\x4c\x9a\xc8\xa7\x60\xe7\x16\x5e\x72\x64\xcc\x6a\xd6\x48\xe5\xc9\x32\xf6\xe0\x35\xee\x38\x78\x79\x43\xc3\x97\xc6\x0e\x1e\x84\x93\x60\xec\x1c\x0a\x3d\x77\x66\xc1\x00\xf8\xb3\xd3\xfa\x05\x3f\x90\xa0\x72\xfb\x5d\x3c\xa5\x0a\xc0\xfc\x89\x3d\x71\x48\xc7\x73\x75\x4b\xd4\xb2\xfa\xe7\x13\xbc\x1f\x62\x3d\x28\x3c\x6d\x2e\xd2\x5f\x64\x9c\x8e\xc8\x99\x7e\x5a\x82\xfa\xed\x3a\x25\x69\xc0\x2e\x7d\x57\x22\x60\x10\x4b\xf7\xb1\xe4\xfa\x2e\x5c\xda\xee\xfe\x99\x65\xcb\xd0\x01\xdd\xbb\xee\x69\x23\x2f\x5c\xe7\x29\x8e\x86\x47\xc1\x55\xf9\xb5\x6e\x57\xf0\x8e\x45\x6e\xd1\x32\x5c\x8e\x0c\x85\x63\xc3\xb2\x13\xfb\x16\x21\x5c\x16\xdb\xc5\x51\x35\xe7\xcb\xb5\xde\x5c\x5d\x09\xcf\xa3\x39\xba\xa2\x97\xf6\x77\xc5\x5b\xee\x81\xbb\x52\x02\xff\x5c\xa4\xef\x5d\x32\xf2\x50\x10\x33\x38\xc9\xc3\x15\x52\xb0\xfb\x70\x35\x3e\x2e\x82\x2b\xd9\x20\xee\x4d\x84\xd3\x73\x55\xe7\x75\x4b\x29\xe9\xed\x9d\xbb\xcc\x22\xa4\xa3\x75\xf2\xae\xf2\x08\x8b\x10\xfc\x7f\x7a\xc5\xec\x5a\x14\xcd\xe3\xd3\xae\x42\x46\x27\x75\xa2\x89\xa3\x14\x47\xe9\x2c\x3d\xce\x82\x6c\x56\x34\xd0\x46\x69\xc7\x3d\x11\xee\xcf\xce\x3f\xf9\x8b\x89\xd3\xa8\x8e\x89\x75\x67\xa4\x9d\x66\xab\xea\x6b\x76\xc8\xd4\xde\x89\x13\x95\xf6\x96\x9c\x4a\xf4\xaa\x90\x6e\xc2\x7a\x63\x6e\x6d\x5a\x09\xe5\xa3\xef\xbd\x10\x8f\x7c\x05\xd8\xf6\xed\xfb\x6c\x34\xaa\x1b\xb6\x29\x64\x2b\x5f\xda\x7b\x90\xf2\x4d\x67\x93\x7a\x25\x6f\x0d\xa5\x79\x7d\xee\x34\xa4\x05\x71\xc1\x4b\x76\x5d\x47\x93\x11\x09\x85\x58\x5e\x70\x52\x39\xbf\x84\xc8\x84\xae\x47\xb7\x83\xe2\x6c\x95\x29\xc5\xab\xda\xc7\x73\x4c\x97\x96\xe0\xaf\xce\x52\xc5\xc2\x15\x53\xc4\xeb\x12\x15\x02\x92\xac\xce\x81\xe4\x79\x90\x7e\x48\x42\x4d\xe5\xe1\xc2\xd2\x53\x6c\x61\x0d\xc5\xfc\x88\xb2\xfc\x10\x69\x01\xa2\xa2\xd8\x82\xcd\xf4\x25\x67\x2f\x68\xc9\x5f\xb2\x14\x1b\x9d\x1f\x35\x1e\x42\xfb\x17\x7a\xdd\x59\x80\x9e\x59\x5a\x69\x54\xa5\x7d\x4d\x4b\x59\x1b\xc8\xdb\x24\x45\x83\xdf\x0f\x35\xbb\xd1\x60\x90\xc5\xc9\x0d\xe3\xb3\xfc\xfe\x09\x8c\xb9\xaa\x88\x94\xd5\x2c\xba\x58\x69\x57\x63\x23\x7e\xdf\x64\xb9\x09\x55\x15\x57\x08\x5a\xaa\x97\x60\xb8\x2f\x06\xc3\xfe\x9e\x61\xd4\xa8\x64\x96\xa0\x77\x58\x12\xce\x12\xce\x2e\x78\x12\x63\x43\x1d\x5d\xe5\x97\x62\x35\x9c\x5d\x54\x96\xab\x36\xed\xbe\x8b\xcf\x15\x51\xa0\x1c\x4a\xae\x8e\x86\xca\xad\x99\xec\xab\x69\x9a\xa9\xe6\xc7\xe0\x15\x2a\x6e\xc4\x8b\x22\x6b\xec\x66\x17\xd2\xde\xc0\xb8\x38\x7c\xde\x6d\xbc\xa8\x3e\xb7\x2e\x30\x99\x01\x9d\xbc\x39\x7c\xde\x6d\x6e\xc0\x0b\xda\xdb\xe7\xdd\x56\x83\x3e\x8a\x51\x7a\xde\x6d\xd1\x2a\x61\x3f\x88\x9e\x77\x5b\xeb\x55\xdd\xbc\x01\x1e\xd9\xa5\xdf\xf3\x6e\xbb\x0d\xcf\xfc\x9a\xf3\x79\xb7\x4d\xcb\x33\xfe\xf7\xbc\xdb\xa6\xcd\xf1\x0b\xbe\xe7\xdd\x36\x81\xc0\x2f\x29\x9f\x77\xdb\xad\xdb\xb3\x6a\xbb\xf1\x68\x2f\xf1\x68\x2f\xf1\xf7\xb6\x97\xf0\x19\x4b\xdc\xd9\xa6\xaf\xbc\x19\x43\x09\x1b\x05\x28\xf7\x1e\x67\x0f\x69\x02\x08\x6f\x8b\x6f\x2a\xa5\xf1\xdf\x22\x57\x95\x25\x4c\xfe\xd6\xd6\xd6\xa4\x27\xbd\xcb\x3b\x9f\x25\x9f\x22\x2c\x1e\xc0\xe1\xec\x02\x05\xd3\x50\xc1\xfd\x81\x64\x4c\x3b\xb1\xae\x21\x0d\xe8\xd9\x77\x17\x95\x3c\x30\x4e\x4c\x9d\x99\xd5\x8a\xaf\xd0\x1c\xd2\x90\x2a\x98\x58\x9b\xda\x7b\x9c\x39\x36\x35\x7d\xf3\x52\x77\x17\xb2\x5b\x34\x1f\x77\x8b\xc7\xdd\xe2\xef\xbd\x5b\x7c\xa7\xd6\x75\xf7\x67\x08\x57\xd2\x4e\x4f\x5a\xf0\x7c\xc0\x49\x1a\x47\xc1\xf8\xd1\x8c\xe7\x1b\x98\xf1\x94\x31\x15\x89\xf0\x95\xb4\x3f\xc9\x53\xce\xc9\x82\x0e\x05\x21\x9b\xd5\x4f\xce\x42\x77\xb8\xb6\x08\x27\x64\x23\x38\x0a\xae\xde\xe2\x9b\x02\xfc\xd4\xa2\x4b\xd5\xa7\x4f\x9e\x98\xb8\x59\x05\x72\x2c\xe7\xcb\x2b\xe6\xed\x76\xc4\x07\xc5\xea\xe2\xc9\x93\x92\x77\x56\xa5\xd5\xf1\x78\x70\x84\x07\xf1\x25\x0d\x69\x91\xa7\xbc\xe6\xe5\x9c\xb8\xea\x5f\x73\x06\x64\x16\x8d\xe3\xc1\x97\x72\x94\xa2\x95\xcd\x21\x16\x5f\xb9\x32\xd6\x2a\xe5\xc6\xcd\x3b\x7a\xf7\x7d\x1d\x26\xe6\xbe\xf0\x42\x6c\x9e\xfb\x0e\xd7\x2d\x92\xb7\x4b\xe5\xe7\xa7\xdc\xec\xe4\xcf\xcd\x3c\x37\x1a\xe6\xdc\x18\xc8\xbb\x24\x6b\xd6\xb0\xd2\x88\xb2\x78\xe5\x5b\x8d\x82\x14\xad\x36\xa7\x6a\x9f\x16\xda\xab\xac\x56\xc0\xa9\xf2\xee\xc3\x9d\x0f\xb6\x8b\x34\xa7\x62\x3a\xd4\xc2\x1e\xb1\xdc\x96\xcb\xf9\x76\x2b\x85\x73\x87\x8a\xc8\xd0\x0a\x99\x72\x7a\xeb\x51\x4e\x7f\x94\xd3\xff\xde\x72\x3a\x13\xd2\xb9\xce\xf9\xdb\x7a\x6b\x2e\xa2\xc0\xe0\xfa\xf9\x60\x1a\x72\x09\x8f\x06\x00\x83\x44\x5a\x5e\x49\x8f\x6a\xe0\x73\xdd\x00\x78\x69\x48\x63\xbf\xc5\x4d\xfe\x95\x38\xe5\xe9\x55\x98\x0d\x2e\x2a\xe4\xbb\x19\x63\x60\x10\xa4\x18\x2d\x11\x8a\x4f\xb3\xa5\xae\xf6\x09\x26\x2b\x39\x4f\x6b\xe9\x45\x38\xf2\x64\xfe\x50\x43\x87\xd5\xed\x02\x3c\x7b\x21\xbb\x43\x89\xf0\x15\xb5\x4d\xa4\x4e\x30\x2f\x1d\x98\x4c\x71\x34\x0c\xa3\xf3\x6f\x81\xca\x07\xda\x94\xba\x63\xbb\xf0\x62\x16\xdf\x36\x42\x36\x44\xab\x3e\xcb\x60\xa0\x6c\xf9\xa2\x54\xb1\xe8\xce\xa0\x99\xdb\xbd\x46\x13\x8e\x5d\xde\x25\x08\x86\x51\x9a\x05\xe3\x71\xa9\x96\x8d\xd2\x5e\xd3\x02\x7f\xb9\x1c\x54\xce\x71\xf6\x2e\x3e\x2f\x32\x2a\x60\xa5\xf2\xac\x1a\x68\xa3\x46\xa9\x9c\x86\xa7\x71\xa1\x29\x0c\x29\x52\xdc\x64\xef\x22\x88\xce\x71\x89\x56\x5d\xd2\x05\x05\xa1\x0a\x43\xda\x18\x6a\x57\xeb\xa4\x6f\x4a\x23\x31\x4b\x6d\x73\xb6\xa0\x0a\x35\xbd\xb8\xa8\x01\xb3\xb4\x18\x50\x7a\x61\x33\x20\xbf\x7c\x64\x8d\x5e\x01\xd1\x2a\x43\x99\x5e\x5c\x90\x55\xc2\xc2\xdd\x3a\x4b\xea\x63\x89\x1c\x9a\xd5\xbc\xf6\xfd\x24\x69\x60\x41\xb3\x90\x3c\x1c\x22\x36\x01\x1b\x08\x08\x72\x62\x38\x98\x45\xef\xd6\xbc\x41\xc9\xf7\xd0\xb6\x4a\x7a\x96\xc4\x4a\x71\xc0\xd9\x45\x97\xfc\x43\x81\xa5\x17\x17\x5d\xf2\x0f\x15\x72\x9d\xae\xda\xed\x47\x21\xf5\x51\x48\xfd\x9b\x0b\xa9\x52\x9b\xcc\x2e\xee\xef\x2b\xba\xec\xf9\x38\xee\x07\xe3\x23\x7c\x4e\xe6\x39\x48\xb6\xfb\xa1\xc7\x63\x39\x5d\x7b\xa3\x17\x85\xc0\xe0\x5c\x19\x1c\x0e\x82\xa9\x0a\xc4\x07\x63\xbf\xb7\xfd\xc1\x86\xa0\x60\xf2\x9e\x76\x6f\x5b\x64\xdd\x5e\xaa\x5f\x0f\x3a\xc3\x17\xc3\xe6\x60\xd8\x6e\xbf\x08\x36\xd6\xdb\x83\xf6\x8b\x76\xb3\xd3\xc6\x8d\xcd\xfa\x8b\xc1\x7a\x1d\xb7\xda\xc3\x4e\x7b\xbd\xd3\xec\x2f\x49\x5c\x5c\x60\x82\x46\xd0\x68\x34\xfa\x83\xfa\x46\x7b\xf0\x62\x30\x0a\x36\x36\x1b\xa3\xfa\xa0\xb5\x89\x3b\xad\xfe\x70\xbd\x31\x78\xd1\xe8\x6f\x06\xa3\x7a\x7d\xc9\xcf\x9b\x28\x8e\x5d\x45\xf6\x0d\xfa\x61\xd7\x31\x88\x92\x13\xb2\xfc\xe1\x5d\x67\xff\xe8\x4e\x4f\x0b\x13\xb4\x2d\xc8\xe6\xb8\x3a\xe0\xda\xdd\xa5\x50\x35\x8e\x99\x3f\x8b\xcf\xbb\x8d\xea\xf3\x82\x79\x7a\xde\x6d\x12\x66\xbb\xfe\xc8\x6c\x1f\x99\xed\xdf\x9b\xd9\x4a\x5e\xcb\xd5\x61\x06\xb3\xcd\xbb\x91\x1b\x25\xf1\xbf\xf1\x24\x88\x6a\x43\xfc\xf3\xb7\x0a\xff\x6d\xde\xda\xdd\x25\xbc\x37\xd5\x3e\x2a\xdf\xe9\x0b\xa5\x44\x6a\x96\x48\xcd\x12\x8b\xc4\xea\x9e\x3f\x22\x78\x4e\x74\x6f\x3e\x16\x0f\x1f\xdf\xbb\x6c\x4c\xe0\x3b\x87\x04\x76\x74\x29\x27\x28\xb0\x2b\x28\xad\x31\xc2\xff\x74\xbd\xa5\x75\x21\x3c\xef\x77\x12\x8e\xd7\xdb\xef\x7b\x0a\xc8\xfb\x6c\x8b\x12\x8e\xf1\x4a\xe6\x21\xb9\x43\xc4\xde\xbf\x68\x58\x73\xc7\x90\x7f\x9f\x81\xcd\x8d\xe1\xa6\xd7\xe8\x12\xb3\x2c\x76\x93\xa2\x7e\xdd\xee\x23\x46\x33\x5d\x95\x6f\x68\xca\x07\xf6\x85\x3a\x42\x94\x44\x3c\xc6\x84\x0c\xf4\xfb\x06\x67\x15\xe5\x74\x8e\xa3\xd9\x04\x27\x41\x7f\x8c\x69\xa4\x62\x5b\x07\x18\x4c\x70\x9a\x1b\xc4\x57\x89\xf4\x0b\x85\x41\x9d\x8b\x94\xe8\xbe\x69\x41\x78\xdf\xd4\x88\xef\x9b\x7a\x02\xfc\x9a\x45\x5e\x6a\x4a\x31\xd1\x7c\xe3\x4c\x0b\x3b\xee\x0e\xd0\x11\xf7\x3f\x57\xa1\x7c\x95\x0e\x19\xeb\x0b\x81\x1f\xa4\x37\xd1\xe0\x0d\xec\x37\x44\xe4\x85\x2e\x2c\x9f\x69\xd1\x92\xb7\x59\x91\x8a\x12\x16\xd0\xa8\xa6\x4d\x12\x80\xd0\x59\xc6\xd2\x39\xce\x96\xd0\x0a\xe0\x50\x1b\x5c\x04\xc9\x76\x56\xa9\x2f\xd7\xb2\xf8\xe3\x74\x8a\x93\x5e\x90\xe2\xca\x32\xff\x0c\xf1\x56\x2b\x8d\x65\xef\xc6\xc3\x67\xd6\x1f\x93\x50\x6e\xdc\x32\xc0\x22\xb7\xd2\xe7\x35\xce\x49\x87\xec\x15\x23\x04\x14\x2d\xf4\xae\x78\xeb\x0a\xbe\xab\x2b\x7a\x78\xcc\x6b\x51\x85\x6e\xf7\x8a\xc6\x46\x06\xe9\xcd\xeb\x20\x1f\xf5\xf9\x7a\xc9\x34\xb9\xb9\xe6\xf7\x48\x5a\xd3\x38\x3b\x44\x03\x22\xcf\xd9\xab\x92\x81\x93\xf5\x60\xc9\xee\xc1\xb6\xc3\x25\xdf\xea\x71\x8d\xcf\x71\x36\x67\x58\xe3\x73\xec\xdb\x4e\xbe\xef\xa8\xc6\x0e\xe2\x28\x1f\xd7\xd8\xb4\xce\xea\xaa\xf2\xa8\xad\x24\x3f\x3d\xd3\x35\xec\x34\x14\xbb\x33\xa4\x7c\xb9\x10\xc9\xea\x94\x3d\x54\xa0\x64\x3e\x40\xfe\xb4\xaa\xe4\x88\xdd\x79\x3c\x62\x3f\x1e\xb1\xff\xde\x47\x6c\x45\x9f\xc9\x38\xc4\x84\xb1\x74\xfd\xa4\xfd\xdf\x78\x34\x4a\xf0\x0d\xfa\x35\x1c\x0f\xbe\x60\xf4\xd3\x67\x3c\x1a\xf9\xec\x64\xe7\x32\xaa\x3d\x08\x12\x72\x84\x3f\x0c\xa2\x01\x0e\xa0\xac\xcb\x9c\x76\x01\x0b\x5c\x56\xe5\x4d\x70\x89\x7e\x8d\xe3\x21\xfa\xe9\xdc\x7b\xc8\x6f\xcb\x43\xbe\x4c\xca\xaa\x84\x81\x64\x2c\x36\x2f\xd8\xa5\xc3\x45\xc4\x8c\x4f\xe9\x0a\x4e\x59\x90\x2b\x8c\x6d\x3b\xfb\xd9\x52\xca\x13\x57\x86\xfd\x31\x25\xb0\x69\x90\xa6\x61\x74\x2e\x72\x0c\x92\xbd\x88\x67\xe2\x4e\x45\xad\x60\x9c\xc6\x76\xd5\x78\x3c\x26\x55\x09\xb5\x61\xb2\xc2\xf1\x6c\x82\xa2\x78\x48\xbf\x86\xd1\x20\x9e\xa8\x90\x09\x30\xe6\xd6\x4f\x6f\x5d\xb3\x70\x82\xc9\x62\x0b\x53\xd4\x40\x29\x1e\xc4\x11\xe4\x35\xa1\xb9\x2f\xb3\x38\x82\xe1\x24\xdd\xcb\x39\xe8\x73\x54\xb5\xe3\x3e\x7f\x89\xb6\x44\x57\xd4\x94\x66\xf1\x18\xc6\x9d\x4b\xbc\x34\x00\x3f\xc3\x45\xd5\x3a\x78\x0f\x7f\x5a\xaa\x73\x9a\x72\xc2\xbd\x0f\x93\x4f\x8e\xf3\x08\xe4\x98\xf7\x9e\x90\xf5\xde\xe6\x25\xe3\x87\xdc\xef\x56\xb6\x20\x73\x48\x90\x95\xbc\x56\x4f\x9b\xc4\x73\x83\x57\x54\x99\x87\x3b\x49\xce\xc6\x63\x4d\xc2\xd7\xf7\x78\x3b\xcf\x24\x41\x49\x24\x99\x84\x07\x2d\xc3\x24\xa9\x2e\x52\xe1\x6b\xd8\xea\x99\x24\xd8\xa1\x08\x3a\xe5\xc9\xdf\x2a\x04\xb5\x82\xac\x50\x66\x79\x57\x9e\x24\xfa\xa9\x56\xea\xe4\x1f\xcc\x35\xfb\x20\xcd\xe6\x92\x80\x95\x46\x59\xa6\x9f\x29\x22\x06\x3b\x01\x3e\x81\x6a\x67\xbf\xcf\xa7\x0d\x1e\xfd\x50\x91\x8d\xf3\xa8\xe3\x7e\x08\xc1\x9e\xfb\x79\x13\xa1\x90\x4e\xe1\x24\xf1\x24\x5c\x51\x7b\xe3\xcc\x74\xa2\xc1\x99\x87\xc6\x8a\x87\xcc\xaa\xad\xb6\x6f\x09\xb9\x15\x6a\xdc\xac\x91\xa0\xe9\x16\xe7\x3f\x36\xb8\xa8\x31\xef\x64\x40\x0a\x40\xfa\x59\x87\x92\x89\xd6\xbb\x0f\xc2\x84\x16\xbe\x33\xc2\x54\x93\xee\x52\xce\xc9\x98\xcc\x62\xa4\x98\xde\x03\x2d\xea\x34\xc8\xf5\x6c\x22\xd9\x34\xb4\x72\x27\xd2\x4b\x8b\x68\x4f\xeb\x90\x20\x3a\x54\xb0\xfd\xe1\x4c\xec\xab\x44\xda\xe4\x67\x42\x26\xf2\x59\x14\x97\xf1\xa9\x72\xab\xe6\x72\x69\x49\xd4\xd5\x77\x7d\xef\x76\x5f\xb4\x73\x67\xe4\x48\xc5\x04\x17\x13\x51\xf2\xed\x83\xf8\x54\xc8\xb1\x69\x48\x8a\x5b\x80\xb6\xef\x49\x89\x3f\x70\xa8\x74\xe5\x57\x61\x96\xc4\x31\xc9\xe2\x61\x8c\x06\x63\x1c\x44\xb3\xa9\x92\xbe\x51\x1c\xdb\xf3\x86\x4a\xc1\xde\xb1\xf2\x28\x92\x6a\x70\x05\xd1\xb8\x3e\x96\x44\x38\x3a\xa5\xa5\xcf\x88\x90\x44\xaa\x77\x11\x05\x12\x0e\xbb\x16\xa0\xae\x0b\x64\x57\xfe\x04\xbd\x2e\xa2\x49\xf0\x8d\xd1\xd7\x18\x00\x13\xc0\xf4\xdd\x9c\x21\x54\x90\xc7\x51\x4e\x6e\x3c\x15\x42\x29\x11\x41\x99\x61\x2d\x9c\x6e\x58\x66\x3d\x53\x77\x4c\xea\x38\xe6\xdc\x9a\xdb\xdc\x91\x17\x20\x74\x22\x85\xba\xbc\x43\xd4\xb2\xcc\x31\xc8\x2f\x95\xe1\x91\xf8\xb3\xd1\xa9\x30\x8d\xea\x17\x7c\x93\x56\x64\xdd\x65\xae\xe5\x85\x78\xdd\xe8\x87\x1f\x90\x6f\x0c\x09\x31\x25\x27\xf4\x7d\x45\x2b\xf4\x52\x1f\x67\x53\x00\xce\x19\x6f\xb9\xfb\x24\x98\xf0\x02\x22\xff\xf3\x61\x9f\xe0\xc1\x45\x10\x85\xe9\x84\x1f\x43\xf3\x99\x03\x00\xc8\x1f\x5e\xda\x86\x3a\xb0\x5f\x30\x9e\x8a\xc8\x1d\xbc\xb3\x6b\x3f\x7e\x4e\x2f\xc2\x88\x34\x74\x3d\x88\x27\xd3\x31\xbe\x0e\xb3\x9b\xee\x3a\x1c\xc9\x48\x01\x42\x10\x15\xb2\x39\x7c\xc1\x37\x54\x53\x20\x46\x53\x19\xaf\xb5\x35\x94\xe0\x49\x7c\x89\x51\x30\x1e\x43\xaf\xd2\x2a\xc2\xd7\x03\x3c\xcd\x40\xec\x67\xaf\xd4\xf2\xd9\x05\xbe\x41\x11\xa6\x23\xd2\xc7\xac\xfe\x90\xf4\x78\x16\x8c\xc7\x37\xa8\x7f\x03\x43\x46\x86\x87\x05\xe1\x00\x9a\xf9\x95\x6c\x48\x61\x74\x5e\x59\x56\xf6\x81\xca\x33\xad\x77\xe8\xeb\x57\x82\xaf\x8c\xd2\x4e\x00\x10\x62\xfb\xc4\x82\xb5\xaf\x36\xcc\x0d\x42\xa1\xb0\x2f\xf8\xe6\xac\x26\x56\xa2\x69\x1d\x6d\x53\x24\x29\x6f\xd9\x28\xff\x85\xc9\x13\x4e\x99\x64\xde\x07\xd4\x36\x17\xc5\x51\x19\x9e\x40\x4d\x6a\xf3\x68\x92\x19\x0d\xdb\x2a\x50\x0f\x15\xa2\x0e\x01\xe7\xe8\x4c\x8a\x33\xad\xf7\x04\xb0\xa2\x8a\xac\xa2\x41\x6d\xf7\x64\xef\xd3\x87\xc3\x77\xef\xf6\xdf\xbf\xf9\x74\xb2\x7f\xb0\x7b\xf8\xf1\x44\x3d\x1e\x95\x99\x01\x5b\xa8\xd2\x24\xa6\x07\x39\x3a\xda\x32\x19\xc1\x6b\x27\xc8\x02\xb4\x85\x4e\xcf\x5e\xea\xef\xf7\xc1\xab\x95\xbf\x2e\xb7\x54\x05\xc0\xda\x74\x96\x5e\x54\x4c\xba\x67\x22\x9e\x56\x7a\x7f\x98\xd2\xc2\x5f\xf0\xcd\xb2\x35\x06\x12\xe0\x1c\x83\x57\x4a\xdc\x14\x90\x97\x95\x74\x5b\x6b\x6b\x68\x12\x4c\x35\x26\x19\x02\xd9\x02\x43\x01\x12\x23\xa4\xa9\x0f\xd3\x41\x30\x55\x54\x17\x8a\x5e\x5b\x77\x48\xa6\x82\x2b\xcd\xe8\xfb\x87\x39\x06\x07\xc1\xf4\x14\xaa\x85\xb0\xc5\xf3\x91\xb1\xd2\xf7\xe6\xe4\x0b\x9b\x4f\x66\x8e\x75\xa9\xf9\xa9\xca\x3d\x4f\x0e\x77\x0e\xbb\x9c\xc8\xd0\x38\x3e\xff\xa7\x29\x55\xc7\x1e\xb9\xfa\xae\x92\x74\x09\x65\x41\xea\x49\x92\x09\xdf\x8c\x4c\xca\xee\x1b\x27\xb0\x5f\x1c\xca\x51\x26\x63\xcf\x8e\x7a\xe1\x50\x75\xc5\x11\x14\xf1\x05\xa3\x74\x96\x80\x9e\x98\x33\xab\x30\x45\x69\x16\x12\x7a\xa0\x9c\x1c\x0f\x51\x30\x02\x97\xa1\x24\x09\x2f\x83\xb1\xb1\xd7\x6a\x30\xc9\x80\x80\x77\x39\x5d\x1a\xe1\xf0\xcc\x95\x4d\x94\xa1\xa4\x24\xf3\xd5\xeb\x88\x2f\x5e\xff\x19\xae\x3b\x51\x3f\xd1\x34\xb0\xbe\x1a\xa3\x60\x9c\x62\xf5\x96\x8d\x39\x42\x15\x8e\xa9\x08\x81\xce\xda\x44\x0b\xc0\x20\xf3\x02\x33\xae\x2c\x5a\xcf\xe1\xff\xa5\x35\x9e\xcf\xa0\x66\x89\x71\x2c\xaf\x18\x40\x1a\x85\x21\x2d\x65\x44\x38\xd4\x47\xc9\x58\xec\xfe\x61\xd2\x71\xf1\xeb\x19\x90\x7a\xc9\xe9\x8b\x75\x76\x64\xc6\x6e\xd0\x6f\xbc\x8c\xa0\x67\xb9\x29\x43\xc9\x13\x53\x86\x3f\xef\xb6\x36\x6e\xcf\xaa\xed\x8d\xc7\x3b\xb2\xc7\x3b\xb2\xbf\xf9\x1d\x19\xbd\x1f\xe3\x47\x00\xa7\x63\xea\x1d\x62\xc3\xe4\x84\x70\x29\x99\x69\xcd\x65\x85\xca\x63\x10\x34\x98\xa4\xbd\x3d\x1c\xa6\x30\x74\x62\xab\x08\x22\xd0\xf1\xa4\x68\x46\x65\x19\xe6\x41\x56\x25\x92\x4c\x98\xe1\x84\x60\x09\xb1\x2d\xe8\x0e\x45\xb7\xce\xa7\x4f\x55\x61\x9b\x1d\x76\x9e\x9a\x1a\x17\xb2\x07\x3d\x65\x77\x40\x4a\x39\x35\x25\x38\xc4\x56\xe1\x7e\x69\xa4\x5c\x1c\x31\xef\x25\x4d\x3e\x67\x42\x08\x19\x7b\x87\xde\xce\x27\x61\xd0\x4d\x90\xf7\x34\x75\x70\x5e\x5f\xda\x6f\x64\x28\xd9\xe2\x24\x37\xfd\xb6\x7a\x00\xe4\xc2\x0b\x0f\x1e\x03\x28\xff\xf0\x03\xe0\x4e\xb5\x3c\x61\x74\x0e\xde\xb2\xcb\x1a\x44\x7e\x17\x50\x14\xbf\x96\x42\x54\x9d\x80\x17\xed\xa4\x10\x2d\xc6\x41\x0a\xcd\x1c\x67\x64\xb2\x9f\x6d\x6d\x59\x03\xcd\xff\xac\x17\x6b\x6b\x34\x18\xb9\x46\x52\xb0\xd4\xb2\x64\x46\x04\xa0\x24\xcd\x50\x1a\x53\xa3\xc1\xe9\x14\x58\x37\x1c\x44\x83\xe8\x26\xbb\x80\xf4\xa0\x7d\x3c\x22\x0c\x80\x2e\x71\x7e\x1f\x09\xa3\x41\x35\x76\xc6\x1f\x39\x63\x3b\xb0\xfe\xe1\x07\xe4\x1a\xf9\x65\xab\x3e\xb2\x75\xf7\x04\x55\x87\xeb\xb1\xb7\xb3\x31\xe5\x9b\x11\xbe\xce\x50\xef\xc3\x47\x34\xb8\x19\x8c\x71\x55\x74\x13\x86\x5d\x6c\x36\xd0\x13\xe8\x32\x33\x00\x9a\x26\xf1\x80\xf0\xac\x94\x8e\x8e\xd5\x8a\x72\xa6\xb4\x32\xe7\x17\x74\x84\x91\x86\x59\xea\xb6\x8a\xea\x65\xfa\xe7\x18\x56\x4a\x0a\x3e\x39\x47\xb1\xac\x7a\x2a\x00\x98\x36\x61\x8a\x62\xb3\x62\x1b\x15\xf9\xc3\x3b\x9b\x96\x45\xb7\x55\x29\xd4\xee\x0f\xab\x92\x53\xb0\xf7\xaa\x4e\x86\x9e\xc1\x81\xb9\x90\xa5\xc2\xed\x0e\xf7\x9d\x01\x66\xbd\x51\x63\xf3\x4d\xd0\x0d\x79\x0a\xa9\xba\x9b\x7d\x22\x97\x2a\xca\x9e\x15\xca\x88\x57\x56\x94\x72\x62\xc9\x2a\x27\x67\xf8\x10\x0c\x87\xc2\x56\x4a\x89\x60\x2b\xbe\x9b\x33\xa4\x08\xe2\x0a\x97\xe5\xc6\x50\xf0\x5e\xb1\xbd\xa6\xe2\xa1\x18\x09\xd5\x72\x56\xb6\x9b\x6b\x01\x18\x8e\xe4\x2b\x5d\xcb\x23\xb9\x10\x9c\xd2\x0d\xe4\x4b\x21\xa1\xce\xa2\x53\x45\xe3\x98\x0d\x55\x85\x5b\x51\xe6\x60\x39\x67\xb4\x9d\x32\xb2\x40\xc8\x6f\x10\x46\x64\x5f\x3d\x6e\x0b\x11\x7f\x37\x1f\xc5\xdf\x47\xf1\xf7\x6f\x2e\xfe\x4a\x13\x31\x1e\x7a\xfa\xbe\x7c\x5e\xad\x0c\xb7\x5a\x82\x5b\x7c\x4d\xd5\xbf\x79\xae\xb0\xc7\x93\x20\xc9\x76\x59\x41\xe9\xc6\xea\xbd\x6a\x02\x35\x0d\x34\xcb\xfb\x62\xe8\x90\x95\xd7\xe2\x52\x89\x66\x61\xbd\x05\x57\x11\xd7\x7b\x22\x30\xf7\x83\x48\xfd\xf4\x4b\x30\x9e\xe1\x5b\x74\x49\xfe\xc3\xae\x17\x08\xe4\x11\x4e\x70\xc1\x8d\x63\x55\xbf\xae\x37\x32\xfd\x2a\x92\x71\x76\x51\x05\x8c\x88\xc0\x5e\xa5\x2d\xd9\x5b\x18\xa8\xb1\xe8\x28\x43\xdc\xf4\x7e\x10\x55\xb2\x78\x99\xa9\x5e\x40\x27\x42\x3e\x73\x15\x4a\xc5\x61\x95\x43\xea\x41\x08\xf7\xca\x52\x48\xd5\x21\x14\x22\xf3\x7b\x5d\xb2\xf5\xb1\x0c\xe2\x4e\x98\x10\x71\xcc\xe5\x60\xc2\x7b\x74\x12\x33\x4f\x59\xb5\x3b\x50\x9d\x41\xaf\x2c\xdb\x5d\xe3\xed\x09\x51\x06\xba\xe1\x12\x76\xc1\xe5\x40\x78\x1e\xe3\xec\xa2\xa6\xf8\xe8\x56\x96\xa1\x11\x86\x6d\x94\x66\x61\x36\xa3\x32\x97\x6d\x4e\x35\xc4\xd3\x38\x0d\x33\x15\x4b\x06\x57\xa0\x07\x60\x06\xe3\x10\x47\x99\x69\xd9\x50\xba\x61\xcb\x64\x81\x35\xe2\x18\xc1\x79\x31\xb2\xc7\x8f\xab\xb4\x73\xaf\x66\x05\xe9\x8d\x66\xd1\x10\x6c\x0c\x07\x38\xc9\x82\x50\x4c\xbf\x67\xf9\x88\x89\x9d\x6f\x1d\x3d\xf8\x12\x12\x78\x2d\xb0\x96\xd8\xc8\x93\xd9\x34\x62\xd7\x29\xe2\xad\xf0\x06\xcf\x62\x29\xd1\x12\xd0\x5d\xda\x80\x42\x9b\xe3\x19\xee\xd2\xff\x70\x31\xd7\x48\x5b\xe0\x9d\x15\x36\xf9\x72\x52\xce\xc9\x2e\x10\x0e\x10\xe7\x84\x48\xe4\xa9\xa8\x4c\x66\x69\x06\x5b\x1d\x9e\xe0\x28\x13\x74\xd3\xbf\xc9\x70\xda\x6a\x2e\x33\x79\xfc\xd9\xb2\x31\x91\xac\xdc\xbd\x4f\x5f\x6a\xcd\x1f\xaf\x4e\x29\x15\xcd\xa2\xf0\x7f\x67\x18\x85\x43\x1c\x65\xe1\x28\xd4\x39\x71\xa9\xb9\xe6\xa3\x53\x62\x86\xa1\x49\x37\xd7\x0c\x60\xd7\x51\xf6\xa0\x97\x26\x11\xf0\x31\x86\xfc\x1f\xb5\x20\x23\x8c\xb5\xc6\xc7\x97\x83\xfe\xe3\xae\x44\x60\xc9\xaa\x7c\x14\x9d\x41\x05\xec\xfd\xf0\x79\xb7\x45\x44\x57\x9e\x82\x82\x08\xad\x2f\xca\x08\xad\x04\xf6\xed\x59\x75\xbd\x5e\xa6\xf0\x4b\x55\xa9\x1d\x13\xf9\x82\xa5\x35\xa9\x52\x07\x4e\x32\xb0\x4f\x08\x9b\x26\xa2\x7e\x3c\x42\xa2\x37\x5b\x5b\xe8\x39\x0d\x70\xf4\x1c\xca\x3c\x59\x5b\x43\xbd\x78\x32\x89\xa3\xff\x3e\x7e\xfa\xe4\x89\xd5\x79\xf9\x8b\x35\xc0\x71\xaa\x3c\x27\xc3\x90\xe0\xe7\xcb\x55\xa4\xbc\xc2\xd1\x60\xb5\x1f\xa4\xb8\xd3\x36\x3e\x4c\x86\xeb\x66\xd1\xcb\xe9\x97\xe1\xc8\x78\x39\x08\xa7\x17\x38\x59\xa5\x90\x97\x5f\x3e\x7d\x72\xfb\xf4\x09\x1e\xa7\x18\x29\x9d\xa1\x5e\x73\xb4\x2f\x7c\x18\x9e\xa3\x1f\x7e\x60\x1f\x6a\xc1\x64\x28\xfa\xb6\x7d\xb0\xf3\xf4\xc9\x13\xfa\xa1\x72\xca\x71\xae\x22\x1d\x55\x78\x26\x18\xd2\x0f\x14\x31\xf8\xad\xe2\x73\x26\x46\x59\x45\x8c\x35\x44\xa3\x4b\xa0\x4a\x3f\x89\xaf\x52\x9c\x2c\x3f\x7d\xf2\x44\x8c\x58\x1c\x67\xb5\x5e\x72\x33\xcd\xe2\xff\x3e\xa6\x55\x6f\x59\x76\x1a\x39\x8b\xe2\x3b\xfa\xe3\xe9\xd3\x27\x15\xfd\x38\xf6\x04\x51\xa5\xc8\xf1\x45\x9c\x64\x83\x59\x96\xd2\x37\x64\xd9\xf4\xd0\x16\xe2\x75\x5f\x2a\xaf\x3f\x8d\xc3\x3e\xf9\x54\x1b\x87\x7d\xe5\x3d\xcd\x39\x0a\x9d\x22\x5f\x49\xa9\x9a\xf2\x4e\x83\x10\x8c\xcf\x63\x00\x41\x7e\xbc\x7c\x2a\xb0\x78\x17\xc7\x5f\x66\x53\x94\x05\xfd\x31\x56\x30\x39\x7e\x75\xf8\x1b\x3b\xf3\x89\x77\xfb\xef\x7f\xf9\xe4\x7a\x7f\xfc\xf1\xd5\xa7\x83\xfd\xdf\x3e\xd5\x7d\x1f\x1a\xbe\x0f\x4d\xdf\x87\x96\xb3\x6d\x5f\x3b\xea\x47\xab\x2d\xf5\xa3\xd5\x9e\xfa\x91\xb7\x29\x86\xa6\x17\x4f\xa6\xe4\xa0\x38\xb6\x87\xc8\x35\xa5\x46\xad\x61\x3c\xeb\x13\xa9\x9f\xd4\x92\x05\x80\xc5\xaa\x58\x20\xf5\xe6\x3f\x84\x60\x7d\x28\x44\x3f\xa1\xe6\x7a\xe7\x25\x0a\x57\x56\x34\xf0\x42\x46\x44\x3f\xa1\x46\x73\xd3\xfa\x46\xfe\x86\xa7\xe1\x19\xda\x22\x30\x7e\x42\x8d\x97\xfa\x77\x7a\x35\x99\x53\xab\x42\xab\x2d\xa3\xdf\x51\xfd\xba\xd1\xe8\x9b\xf5\xe5\xe3\xed\x53\xad\xd7\xbf\x06\xe3\x2f\xe8\xcd\xeb\x4a\xf3\xf7\xcd\x65\xbd\xb7\xd7\x34\x00\xa1\xfe\x2e\x34\x5e\xce\x35\x02\xca\x20\xa7\xfd\xf8\x5a\xff\x08\x17\xf7\xa4\xcd\xeb\x10\xfd\x8e\x2a\xd7\xb2\x43\xec\x77\x53\xf9\xdd\x52\x7e\xb7\x97\x8d\xce\x02\x94\x4a\x7a\x8d\x7e\xfe\xf9\x67\xb4\x09\x25\xd3\x6b\xf4\x03\xaa\x5f\x8f\x46\x74\x80\x3a\x2d\xa3\x0a\x59\x1d\xa7\xd7\x64\x20\xd3\x6b\xe3\x13\x5f\x3c\xa7\x29\x7c\xbf\x7e\xf9\xd4\xdb\xa9\xc9\x6c\x9c\x85\xd3\x71\x38\x00\x2d\x81\xdd\xbd\x6b\x42\xc6\xc3\xd3\xeb\xb3\x97\x8e\x6f\x6d\xfa\xad\xe9\xfc\xb8\x49\x3f\xb6\xcf\x72\x5a\x4f\x67\x7d\x04\xf2\x4d\x15\x4d\xc2\x6b\x34\x88\xc7\xb3\x49\x94\x6a\xd4\xaf\xc2\x24\x92\x42\x65\x08\xbd\xfa\x91\xd0\x4c\xbd\xc1\x47\x8a\x3d\xd6\x1b\xf5\xba\x39\xb4\x62\x25\xd3\xc1\xaa\x64\x30\x31\xed\x65\xf4\x95\xfc\xa6\xe3\xed\xa9\xd2\x50\xab\x34\x3a\x4a\x95\x46\xc7\x57\xa7\xa9\xd6\xd9\x5c\x46\xb2\x4e\xd3\x9a\x75\xc1\x0d\x68\x9d\x2c\x67\xa4\xc2\xe8\x52\x1d\x2d\xf2\x58\x7a\xc4\xae\x37\x95\xf1\x61\xe4\xd9\x66\xaf\xea\xfc\x45\x53\x1b\xd2\xdc\x11\xd5\xf8\x23\xa3\xb1\x32\xc3\xaa\xb1\x4e\xad\x5e\xc1\xd8\x6a\x6c\x55\xab\x58\x30\xc0\x1a\xcb\x65\x15\xf3\x46\x19\xee\x0b\x40\x0f\x8c\x13\x9b\x13\x3e\xbb\x76\x32\x41\xc6\x00\xb6\xe6\xe0\x80\x50\xa5\x89\x7e\x47\xc3\x53\xf2\xbf\xeb\x4d\xf4\x3b\xba\x6e\x9e\x9d\x99\x0b\x09\xca\x86\xe8\xf7\x2d\x28\x78\x1d\x5a\x05\x34\x26\x09\x3f\x6f\xe1\x4c\x2b\xf6\x95\x0f\x09\x1e\xd0\xce\x0d\xd1\xd1\x20\x8e\xd8\x06\x23\x77\xa5\xa3\xde\xe1\x7b\xb2\x47\xd4\xaf\xeb\xf5\x2a\xaa\x5f\xd7\x1b\xf0\x6f\x13\xfe\x6d\xc3\xbf\x9b\x55\xa0\x05\xf2\x6f\x13\xfe\x6d\xc3\xbf\x9b\xf0\x6f\xa3\x4f\xfe\x6d\x75\xe4\x66\xf6\xe3\x8f\x0c\xa9\x1f\xd1\xf6\xee\x31\x4d\xde\x8f\xa8\x38\x84\x88\x40\x90\x84\xd9\xc5\xa4\xc6\xcb\xac\x49\x54\x48\xe9\x2d\x26\x3e\xd4\xe8\x83\x22\x61\xd4\xf0\x75\x46\xbd\xf1\x45\x97\x3f\x0d\xe3\x23\x9c\xe2\xac\x8b\x3c\x5b\x24\x1b\x84\xe3\x2f\xe1\x94\x59\xd2\xc6\x23\x14\x1d\xc5\x70\x1a\xbb\x08\x52\xd4\xc7\x38\x02\x6b\x7b\x76\xc5\x15\x44\x43\x30\x89\x1b\x86\x43\x14\xc5\x19\x33\x6b\xb4\x49\x81\xe6\xe0\xe0\x90\xb8\xf9\xe5\xa7\x2f\xf8\xe6\x43\x12\xc6\xc9\x11\xb5\xa8\xdd\xda\x92\xef\x9d\xa4\xc3\xcd\xac\x8c\x39\xb5\x3b\xa0\x8b\x6f\xfc\x8f\x1b\xf0\x6d\xb9\x9b\x97\x6f\x1d\xfc\xf9\x0b\xbe\xf9\x35\x4e\xc0\x28\xf0\x0b\xbe\xa9\x5d\x91\xdf\xee\x62\xc7\xe1\xbf\x31\x2b\x95\x86\xe7\xaf\x08\x03\x42\x6b\xa8\x9d\xb7\x8c\x84\x5d\x7d\x02\x03\x64\x83\xe5\x23\xc7\x71\x94\xcf\xbc\xc1\x15\xd4\x29\xd5\x02\xe9\x7f\x3a\xb8\xc0\xe4\xf8\x81\x88\x08\xed\xe8\x43\x7a\x14\x5f\x11\xd8\x15\xde\xcc\x0a\xd9\xa5\x7f\xcc\xed\x83\x0a\xd7\x3d\x2c\xbc\x51\x65\x9c\x95\x77\xa7\xe6\x52\x95\x26\x97\x04\x1d\x2a\x7a\xd0\x9f\x3f\x31\x0c\xd9\xb3\x43\x0a\x41\x8c\xec\x44\x79\x3a\x48\xce\x72\xe4\x4f\x41\xe5\x14\xea\x9c\xd1\x91\x85\x19\x67\x6f\x1c\xac\xc6\xcf\xb0\x90\xb2\x9f\x58\xc0\x21\x3a\x8d\x39\x94\x2a\xda\xcf\x18\xe2\xff\x10\x88\x7b\x31\x67\xb3\x70\x14\x67\x88\x90\xa4\xbf\x50\xa6\xee\x01\xfa\x16\x90\x0b\xf9\x78\xd6\x2f\x03\x19\xc4\x27\x0e\xf3\x4c\xd9\xdb\xe0\x83\xdc\xa9\x98\x8c\x76\xa6\xec\x62\x6a\x89\x4d\xad\x00\x60\xca\x20\xb3\xd7\x05\xd8\x1e\x84\xd7\xc0\xb6\xf3\xb0\xfd\x7d\x0b\x98\xf8\x29\x1b\xe4\x35\x49\x1d\x5f\x51\x9d\xa1\xee\x98\x6c\x24\x27\x1c\x48\x8b\xad\xbb\x9f\x51\x87\xf0\x33\x63\xc2\xd0\xd6\x16\x6a\x17\x4d\xda\x77\x37\xb4\xee\x3e\x7b\x46\xdc\xb7\x66\x2c\x5a\x67\x43\x72\x86\x7e\x27\xb2\x84\xbd\x88\x0a\xb9\xb9\x2a\xd3\xe5\xb3\x99\x30\xba\x7c\xeb\xe0\x34\xd6\x6b\x3f\xb3\x21\x45\x25\xbf\x11\x4f\x92\xe5\xf0\x57\x1e\xae\xa3\x32\x2c\xc6\x47\x57\x45\x1d\x17\xf1\xc2\x91\x91\x37\xf3\x8f\x1c\xa2\xf1\xb2\x93\xfb\xe5\x4c\x6d\x27\xb8\x22\xc4\x7f\x42\x6d\x70\x0c\xa1\x0f\x79\xb4\xaf\xcf\xc5\x29\x87\xc0\x24\xcd\x39\x3b\x92\x03\x4c\x17\xba\xf5\x35\x44\x48\x51\x17\xae\x3d\x4b\xe9\x0c\xfd\xee\x5f\x9c\x9e\x3f\x5d\xf8\x76\xaf\x40\x13\x81\xd6\xa9\xbe\x14\xdd\x73\xe0\x95\x64\xab\xca\xf4\xe0\x68\x90\xdc\x4c\x69\x72\x78\x55\xce\x3b\xa8\xa2\x78\x34\x4a\x71\x66\xcd\x0c\x5d\x23\xc3\xb8\x27\xea\xc9\xc2\x55\x7b\xaf\xae\xca\x13\xa2\xfc\xd9\x90\x3f\x9b\xf2\x67\xab\x0a\x2c\x46\x3d\x65\x68\xb8\x0e\xf1\xbc\xb8\x12\xae\x79\x15\x4c\x51\x33\x1a\x82\xec\xd9\xce\x2e\x3c\x42\x0c\xa1\xef\x83\x53\x0a\x86\xc8\x2f\xe6\x90\x6a\xdf\xf4\xb2\xad\x9c\xb2\x2d\xe7\x91\xa8\xcc\x10\xea\xb4\x5a\xd5\x09\x54\x7f\x6c\xe8\x8f\x4d\xfd\xb1\x55\x15\x0a\x0b\x6b\xf3\x5e\x5b\x43\xfb\xe4\xe4\xfb\x5d\x8c\x91\x7b\xd2\xb5\x61\x72\xce\x7a\x15\xdd\x8d\xdc\x5c\x44\xc3\x0e\x04\xa5\x25\x6b\xc7\xc0\xbe\xc1\x2c\xf6\x26\x5c\x48\x52\x51\x9d\x60\xea\xd0\x71\xd5\x95\xc1\x3a\x83\xd7\xbf\x6b\xcc\xb6\xee\xd2\x00\xa5\x0d\x73\x3a\x8c\x5a\xd6\xfc\x40\xad\xa6\x5e\xab\x69\xd6\x72\x6a\x9b\xd2\x96\x39\x9d\x46\xad\x96\x4b\x0d\xf5\xd6\x38\x3b\xb8\x8f\xfe\xea\x16\xe8\x3a\x31\x1c\x39\xce\x38\x62\xff\xa5\xa3\xba\x85\x1a\x2f\xd9\xcf\x9f\xf8\x0c\xb1\x17\x9e\x7d\x17\xe6\x38\x1c\x65\x40\xe9\x55\x8f\xa2\x2c\x77\xe2\x38\xea\x19\x99\x3c\x45\x5d\x53\x17\x92\xd7\xef\x8a\xa2\xab\x92\x36\x2c\xb9\xeb\x77\x45\xa9\x55\x49\x9b\xa6\xd4\xf5\xbb\xa2\xbf\x4a\x5b\xca\x6b\x6b\x1b\x5e\x59\x71\x6d\x00\x80\x5c\x43\x47\xae\xe1\x41\xae\x59\x80\x5c\x2b\x17\xb9\xfa\x82\xc8\x35\x75\xe4\x9a\x1e\xe4\x5a\x05\xc8\xd5\x73\x91\x6b\x2c\x88\x5c\x4b\x47\xae\xe5\x41\xae\x5e\x80\x5c\x23\x17\xb9\x66\x21\x72\x4e\xd2\xfd\x38\x05\x1b\xa2\x34\x0b\x32\x6c\x17\x00\x76\x92\xd5\x1d\x1d\x03\x96\x91\x99\x7a\x34\xf8\x42\xe6\x22\x6b\xba\xbe\x90\x81\xc8\x4c\xed\xb8\x53\x89\xe2\x5c\x4f\x05\xbc\x0f\x96\x4f\x85\x9e\x3c\x94\xb5\x63\x9e\x5a\x1c\xcb\xc7\x3c\xb6\xd8\x2b\x48\x3b\xb7\xc8\x25\xb4\x5c\x8e\x12\xc4\xfa\xe1\xd8\x35\xfc\xd8\xd9\xeb\xc7\xc2\xce\x5a\x42\x3a\x76\xf5\x45\xb0\x6b\x2a\xd8\x35\xfd\xd8\xd9\x0b\xc8\xc2\xce\x5a\x43\x3a\x76\x8d\x45\xb0\x6b\x29\xd8\xb5\xfc\xd8\xd9\x2b\xc8\xc2\xce\x5a\x44\x3a\x76\xcd\x62\xec\x6c\x6a\xc5\x3c\x50\xb4\x5b\x2e\xa1\xdb\xb0\x63\x1d\x99\x42\x8e\xb5\x9c\xf4\xcd\xd5\xb1\xaa\x2c\xd1\xa7\xe5\x93\x7d\xd8\x51\xb8\x8b\x9a\xeb\x9d\xb5\x56\x93\x69\xa0\x97\x5d\xaa\x60\x2e\xb1\x08\x01\x29\x65\x8e\xb8\x4c\x35\xbc\x94\xb2\xfc\x49\x28\x24\x5b\xf0\x28\x18\x60\xa1\x23\x16\x40\xfe\x0b\x5f\x07\x93\xa9\x38\x29\xcb\x0f\x7c\x4e\x29\xac\x0c\x5f\x67\xca\xed\x76\x6d\x7b\xf7\xb8\xc6\xce\x11\x95\x09\xb7\x48\xff\x82\x6f\xaa\x68\x30\x3a\x17\xd2\xbc\x84\x32\x1d\x07\x04\x89\xeb\x0c\x99\x50\x98\x84\x5f\x91\xed\xb8\x00\x31\x9d\x76\xcf\xa1\xc4\xfe\x44\xa3\x90\xee\xe1\xf1\x14\x27\x95\xed\x5d\x7a\xad\x4f\x75\xf6\x4f\x9f\x30\x9b\x15\xb5\xc9\x97\x4f\x9f\x42\x44\x59\x30\x20\xd1\xac\x0a\xba\xeb\x8d\x2a\xb7\x4b\xe8\xae\x53\x07\x40\x69\x99\xd0\x5d\x07\x6b\x12\x66\x92\xd0\x5d\x5f\xaf\x32\x4b\x85\x6e\xa7\x7e\x7b\x56\x5d\x6f\xfc\xb5\xad\x45\x98\x09\xc8\x03\x1b\x7a\xd0\x56\xbe\xa1\xf9\x06\x5d\x31\x3f\x22\x66\x68\x41\x50\x41\x83\x78\x32\x8d\x23\x08\x75\x4e\xbe\xad\x3d\x7d\x22\xe8\x63\x1c\xf6\x6b\xac\xe8\xd7\xaf\xaa\xa1\x80\x08\xc6\x7e\xcf\x46\x20\x41\x8a\xa5\xf5\x47\x90\x62\xe5\xdb\xaf\x71\x32\x04\x77\x70\x51\x40\xbc\x51\x21\xcc\x46\x60\x57\x06\x6b\x62\x9b\xdf\x06\x49\x98\xce\xcf\x1a\x66\x18\x9c\xb0\x7a\x64\x41\x2b\xef\x3f\x66\xa3\x4d\x80\x82\xa3\x41\x8d\x3c\x18\x58\x77\xda\xe2\x2b\x7d\xcc\x33\x58\x11\x5f\x76\x2f\xa7\x6f\x77\x5e\xcb\x4b\x29\xfa\xec\xbc\xe9\xea\xa7\xd4\x8c\x8f\x2c\x3f\x7e\xdb\x95\xe1\xc9\x74\x1c\x64\x2e\x46\x26\x82\x3b\xff\x11\xb1\x40\x38\x5c\xd3\x0a\xce\x07\x82\x27\x82\x7e\x30\xfc\x37\xae\xf1\xc0\x8e\x5d\xd4\x46\x95\x46\x73\x13\xf5\xc3\x2c\x5d\xce\x03\x18\x5e\x3a\xe0\xed\xff\xb2\x28\xb8\x4f\xbb\xef\x7b\x9f\x7e\x7b\x7d\x78\x74\xf0\xe9\xe0\x70\x67\x17\x6d\x43\x48\x81\x2c\x88\x32\x94\xe0\x69\x82\x53\x1c\x65\x61\x74\xce\x15\x36\x84\x0c\x27\xf1\x50\xf6\xdd\x09\x73\x67\xb7\x14\x4c\xc6\x76\x2d\x98\xca\xe5\xa1\x61\x9a\x24\x1e\xdd\x14\xe5\xb8\x4c\x94\xb3\x49\xd1\xed\x81\xbb\xf5\x2c\x01\xc3\x08\x91\x5b\x43\x2d\xa2\x15\x57\x7a\x27\xe8\x9e\xcc\x01\x3a\xb9\xc0\x64\xd4\xb3\x18\xcd\x98\x3b\x01\x61\x01\x88\x14\x06\xd0\x1a\xc8\x35\xf9\x30\x18\x9d\x77\x81\x74\x39\xae\xcb\xea\xce\x6b\x61\x0b\xdb\x4a\x4a\x61\x33\xf2\x0b\x23\xdf\x64\xb8\xd0\xa7\x76\x9b\x0a\xee\x84\xf4\x08\xf2\x5f\xf0\x4d\xcd\x59\x96\x3b\x91\x0e\x46\xe7\xa8\x72\x08\xad\x04\xe3\x65\xa8\x33\x70\x0d\x5e\xc9\x31\xd0\xdb\xe2\xf1\x3b\xe9\x84\xde\x12\x12\xe1\xbd\x23\x84\x32\xc8\xeb\x13\x39\x7f\x84\x03\xff\x77\x5d\x9a\xb0\x0b\x20\x4d\xaa\x50\x65\x01\x7e\x45\x5d\xa3\xdb\xf9\x2e\x1d\xe6\x38\xa9\xb0\x4b\x36\x18\xc2\x2a\xfa\x03\x85\x97\x5d\x14\x5e\x4a\xde\x78\xab\x99\x28\x68\xf3\xad\x43\xea\x6a\xe1\x98\x98\x84\x61\x6a\x0a\xd4\x84\x1d\xb4\x3e\xbb\x99\xd6\xd7\xaa\x43\x46\x31\x25\xb9\x92\xf4\xe4\x59\x88\x8f\xf4\x74\xbf\xf4\xb4\x83\xef\x8b\x9e\x04\xa4\xbb\xd1\x93\xce\xa7\x17\xa0\xa7\xfd\x28\xcc\xc2\x60\x1c\xfe\x1b\xa7\x28\x40\x11\xbe\x1a\xdf\x30\x0c\x87\x6c\x38\x8a\x69\x89\xef\x1a\xd7\xa3\x38\x99\x1c\xc4\x43\x8c\x76\xa9\x4f\x1b\x84\x47\x96\x9c\x2e\x4e\x54\x3a\x05\x2b\x6c\x70\x07\xe4\x54\x2b\x36\x19\x37\x19\x7e\x77\x24\x7b\x6f\x64\x55\xb1\x3f\xb8\x38\xc5\x82\x04\x17\x46\xa1\x66\x89\x23\xa6\x49\x21\x17\x87\x2a\x7b\x7b\x3a\x25\xb4\x00\xa3\xc5\x33\x3e\xa7\x8e\xeb\x08\x32\xc4\x5b\xe2\x27\xdf\x14\x29\x0d\xda\xa7\xe7\x8c\x48\xce\xd4\x00\x3f\x4e\x26\x74\xda\x03\x97\x8e\x87\xd2\xb7\x24\xa9\x2d\x49\x5e\x2f\x5d\x25\xa9\xbd\x0d\xd8\xd4\x38\xcf\xec\x21\x25\x74\xea\x29\xe0\xea\x07\xd8\x21\x55\x4a\x2f\x1c\xb0\xe5\xd1\xf9\x30\x84\x7d\x48\x45\x4b\xa0\x65\xbb\x23\xf9\xb0\x25\x68\xe3\xa6\xcd\x70\x52\xc6\xd8\x8a\x1a\x1f\x0d\x83\x2c\x40\x7d\x90\xbd\xf4\x12\x1e\x79\x0c\x40\xd3\x0c\x13\xdc\x2b\xda\x04\xfc\x01\x27\x30\x97\x83\x38\x1a\x24\x38\xc3\xab\x6c\x38\xc6\xf1\xb9\xc6\x94\x95\xfb\xab\xa3\xf9\xc6\x1a\x42\x6f\x00\xe6\xd4\x0d\x86\xf1\x14\x3c\x54\x58\x0a\x1e\xce\xb1\xe9\x7d\x4d\x99\xcb\x0c\x01\xca\x94\xa2\x84\x37\xf0\x36\x58\x03\x0a\xf8\x12\x3b\x97\xc2\x9f\x04\x2c\x1a\xac\x8a\xc5\x2d\x08\xa3\xf3\x7b\xe0\x26\xb2\xf3\x5b\x9c\x3c\x18\xfc\xca\x12\x69\x73\x49\x27\x93\x32\xf5\xae\x38\xe6\x5e\x0a\x63\x25\xbb\x46\x74\x55\x3a\x74\x1e\xee\x81\xa3\xa1\x6b\xf6\x03\xf8\xa2\x56\x77\xd1\x14\x6d\x0f\x05\x97\x41\x38\x0e\xfa\x63\x4c\xcd\x15\x53\xff\xb6\xf8\x89\x77\xa6\x34\x55\xbd\x0e\x23\xb6\xf1\xe5\xee\x53\x0c\xae\xbe\xcf\xbc\x8f\x33\xe6\x45\x4d\x83\x95\x51\x48\x72\xd7\x40\x61\x8a\xf0\x68\x84\x07\x59\x78\x89\xc7\x37\x28\x40\x43\x9c\x66\xc9\x0c\x9e\xab\x28\xc1\xc1\x70\x35\x8e\x06\xb8\xd4\x3e\x53\x96\x7a\x01\x8d\x87\xa2\x61\x0a\xfc\xa1\x29\x99\x8f\x64\xa5\x3c\x11\x8b\x2a\xf3\x52\xbf\xa8\x58\x4c\xfe\xbc\x68\x79\xfa\x7f\x2d\xe7\x62\x06\x85\xf4\x12\xe1\x28\x17\x00\xca\x5d\x2d\x5a\x51\xc7\x85\xca\x1c\x0c\x19\x42\x27\x11\x54\xd9\x82\xc3\x43\x16\xa7\x92\x73\xea\xd7\xca\x84\x38\x17\x9f\x5d\xbb\x50\x29\xdd\x68\x6e\xae\xb5\x9a\xea\x27\xaa\x12\x71\x7d\x31\xe4\xa0\x2e\x6a\x68\x5f\x75\xf9\xb7\x8b\x9a\x65\xce\x4e\xa9\x53\xe5\x1d\x14\x2b\xbc\x91\x77\x6d\xf2\x53\x0b\x1b\xe9\x93\x0b\xac\x08\x05\x2c\xc1\x55\x80\x2e\x40\xbb\x4c\x84\xcc\x12\x4b\x91\x8b\xb0\xdb\x11\xc7\x07\x02\x11\xf0\x65\x4d\x84\x26\xb6\xae\x1d\x1d\xfa\x06\x87\x25\x66\x15\x6e\xab\x3c\x0d\x5d\xba\x25\xdb\x7a\x57\x99\x56\xaf\xeb\xf5\xaf\x22\x7f\xe2\x53\x8a\xc7\x78\x90\xd1\x86\x8f\xb3\x24\xc8\xf0\xf9\x4d\xc5\x67\xd6\xad\x68\xa2\x41\x5c\xdc\x42\x4b\x94\x95\x2e\x79\xcd\xc8\xd8\x6c\x7c\x08\xd2\x94\xb0\x89\x57\x41\x8a\x87\x9a\x67\x9d\xfa\x97\x6f\x44\xc6\x40\x1d\xe3\x04\x0e\x5c\x64\x57\xf3\x43\xca\x5f\xe4\x66\x4e\x3d\x76\xef\x91\x63\xcb\xee\x43\x8a\x91\x93\xca\xd8\xec\x9b\x98\x3c\xfb\x52\x19\x2f\xcc\x3d\x0f\xe2\x82\x87\xa2\x58\x45\xfe\x8b\x1e\x73\x0c\xaa\x1e\x8b\x50\x46\xf6\x5d\xab\xff\xc6\xbd\xcf\x9d\xd0\xd6\x6f\x94\xaa\x28\xf7\x66\xc9\xc4\xdc\x31\xa1\x26\xdb\x56\xb9\x64\x65\x99\x69\x78\xdd\x57\x74\xba\x0e\x3b\xcd\x12\x1c\x4c\x16\x52\x65\x83\x0c\xc5\x94\xcf\xaa\xad\x7e\xab\xb9\xda\x0f\xa9\x61\xb7\x7e\xa2\xa1\xd2\x09\x84\x8f\x56\x34\xd3\x0d\x54\x69\x35\x75\xc5\xb4\xa2\xf0\x3d\x06\xfc\x0c\xb5\xaf\xf9\x32\xc7\x73\xe4\xb5\x63\xaf\x75\xed\xb0\x5c\x44\x9c\x06\x09\x1c\xb7\x5c\x02\xa2\xbd\xbd\xc1\xf1\x46\x5a\x61\x71\xa1\xf1\xd9\xb3\xa5\xd1\x78\x96\x5e\x2c\x95\xdb\xe6\x28\x14\xdf\x46\x27\x86\xb9\x8b\x1a\x79\xf3\x0a\xe7\x5a\xc8\x26\x3a\x55\x6f\x55\x55\xe5\xf9\xa7\x09\x3d\xfb\xf6\x6a\xec\xc7\x1f\xb7\xc5\x14\xa2\x79\xf6\x40\x3d\x8b\x4a\x94\x36\x94\x5b\x50\x76\xd0\xb6\x9c\x88\xd9\x7b\x55\xe9\x9d\xa7\xa0\x57\x55\x94\x13\x9e\x14\x4b\xca\xd7\x73\xef\xa6\xdb\x7a\x8f\x9c\x0a\x41\x33\xa3\x8b\x54\xf0\x03\x55\x7f\x83\xfd\x90\xcf\x14\xdf\xee\x40\x0f\xdb\x7b\xd5\xb3\x54\xd1\x9c\xa3\x84\x97\xd4\xbb\x67\x11\xcd\xb3\x84\x91\xab\x2b\x14\x75\xb9\xa2\x49\xa9\xb7\x90\xc6\x59\x4c\xa7\x3c\x20\xfd\x67\x4e\xa7\xd4\x04\xcf\x39\x9d\x4e\xc5\x6f\xc9\xe9\x14\x75\xef\x30\x9d\x79\x0a\xdf\x72\x57\x07\xdf\x74\x3a\xef\x3c\x5d\x39\x4b\xa0\x60\xbe\x4c\xbd\x69\xce\x24\xd1\xcd\x44\xe8\x79\x07\x2e\xb1\x8e\x59\x67\x5f\xa2\x2d\x14\x5e\xaa\xb3\x95\xb7\x45\xb0\x1d\x13\x78\x31\xea\x5d\x04\x61\x04\xa9\x46\x7c\x77\xad\xaf\xc0\x6e\xe0\x13\xef\x3c\xda\xf2\x07\x29\x30\x55\x6c\xda\x0e\x42\xea\x5a\xc4\xa0\x0c\x8d\x6c\xcc\xd8\x25\xc4\x9d\xe8\xcb\x3c\x8e\xf2\xaa\xc7\xb7\x03\xe3\x24\xa4\x34\xa1\xcd\x1d\xe9\xd5\xab\x9e\x63\xef\xb1\xc1\xd3\x26\x3e\x88\x48\xa1\x19\x57\x63\x50\x2a\x0d\x32\x66\xfc\x5d\x33\xeb\x58\x30\x0c\x9a\xa5\xd2\x91\xd0\x8a\x30\x61\x29\xe6\x32\x12\xd2\x39\x21\x72\xde\x90\x30\xfb\x2d\x02\x84\xfd\xbc\xba\xc0\x2c\xe2\x3d\xc5\x0f\x62\x7e\xa6\x25\x90\xb3\x17\x86\xbb\x20\xf9\x83\xa9\x64\xa2\x0e\xf5\x1a\x80\xb4\x74\xd0\x05\xe1\x02\x61\xca\xb2\xea\x64\x20\xa9\x02\xb4\xcc\xe9\x75\x28\x5e\x9b\x69\xa7\xa3\x2c\x32\x6f\x48\xd4\x85\xe4\x31\xb0\x95\x42\xac\xd0\xe4\x88\x57\x1e\xb3\xd7\xdf\x0e\x8f\xe0\xbc\xcc\x88\xce\x2e\x73\x1d\x27\xd0\x2f\xa9\xe8\xae\x22\xad\x5f\x55\xd9\xac\x4b\xe8\x67\x78\xa8\x3e\x31\x15\x73\x74\x9d\x98\x1d\xe1\x09\x06\x29\x1c\x76\x57\x4a\x02\xec\x2a\x0a\x4e\xfb\xe0\xf8\x0e\xaf\xed\xea\x5c\x82\xc5\x97\x94\x3d\x6c\x31\x66\x4a\xf3\xb8\x73\xbc\x85\xc9\xa0\xb7\x03\xaa\x87\x4f\xe1\xba\x1d\xe2\x12\xeb\x56\xec\x53\x8f\xeb\xf6\x71\xdd\xa2\xc5\xd7\xed\x5d\x56\x07\x58\x12\x5f\x84\xe9\xdc\x6b\xc3\x89\x09\xa3\x68\xe0\x22\xbf\x1d\x1e\x79\x39\x80\xea\x69\x66\x71\x80\xbb\xb2\x1d\x27\x66\x27\x72\x68\xfa\x78\x10\x4f\xd8\xd2\x21\x6c\x21\x8c\x67\x69\x79\xe6\x21\x06\xab\x2c\x7b\x10\xa4\xc4\xbb\x51\xf1\xe2\x3e\x97\xa7\x14\x88\x48\x5c\x5a\x72\x45\x02\xb8\x88\xe3\x14\xa3\x49\x78\x4d\x64\x21\x47\xff\xc0\x63\xd4\x16\xd2\x90\x4a\x88\x4c\x0a\xf3\x91\x5d\x7c\x09\xd2\x29\x39\xe9\xa4\xb3\x7e\x8a\xff\x77\x86\xa3\xcc\xa9\x62\x40\xba\x68\x27\x0c\x3c\x4b\xc7\x1c\x51\x51\x32\x66\xa5\x58\xd5\x4f\x76\x36\x17\x56\xae\x58\x4a\x72\xb5\x39\x23\x2a\x91\x3f\x98\x40\x69\x65\x1e\x9e\xa1\xdf\xb7\x68\xbd\xd3\x30\x37\xc4\x89\xfc\xcd\x4d\xa5\x5f\xf5\x58\x79\x2d\xf0\x89\x22\xda\x7e\x08\x86\x43\x32\x81\x05\x0a\x90\x29\x64\x97\xea\xd5\xe8\x7f\xdd\xea\x8f\x0f\x6f\x7b\xc7\xe8\xff\xb3\xbe\xb6\x81\xa6\x0c\x68\xca\x74\x79\x2e\x98\x1f\xbe\x0c\xd2\x0d\x90\x93\xa7\xc1\xb0\xc6\x9f\x72\x64\xe3\x0f\x01\xbf\x7e\x9e\xa5\x3c\xca\xbe\x08\x98\xc2\x4c\x97\x21\xbe\xb2\xc0\x63\x2e\xfb\x2b\x80\xac\xde\x3e\x13\xb4\x9c\x95\xdc\x7a\x3c\x16\x2a\x4a\xb9\x8f\x04\x40\xa9\x08\x7a\x49\x06\x05\xc2\x5e\x3e\xf0\xb1\x59\x1c\xbe\xc4\xb8\x92\x5f\xf2\x7a\xad\x6a\xc4\xd7\xd2\x2e\x98\x83\xa1\x79\xb9\xb6\x30\x03\x11\xd5\x68\x4c\x94\x2d\x65\xbc\x7c\xb1\x45\x66\x51\x26\x68\x07\xfc\x8f\x6c\xa8\x11\x23\x58\x0b\x28\x7d\xb1\x4a\x53\x3d\x8b\x48\x2c\xff\x30\x0a\xb8\x9a\xa5\xf7\x42\xbc\x5d\x3b\x44\x03\xcd\xb0\x06\x5f\x09\xbd\x40\xa4\x14\x0d\x0b\xe9\x13\x63\xbd\x67\x8e\x30\xd6\x7b\x70\x7f\xd1\xde\x16\x62\x96\x8b\x54\x9a\x1f\xe6\x40\xb2\x1f\xbd\x4d\x14\xa2\x15\x9f\xfb\xbe\x0a\x9d\x86\xc3\x57\xde\xe4\x48\xaf\x06\x76\x68\x4b\xda\xbe\xf3\xc3\xbf\x0a\xba\xaa\xa3\xe4\x32\x43\xd8\x1e\x0e\xdd\x83\x00\x73\x3d\x88\xa3\x41\x90\x71\x98\xa5\x35\x30\x1f\xa3\xa9\x60\x28\xb0\x64\x2f\x82\x21\x0d\x78\xc4\x16\xea\xb7\xe1\x32\xb3\xc8\xe4\x33\xdf\x84\x23\x40\xb3\x25\xae\xdc\xa1\x9c\xc9\x12\x5c\x7c\xe0\x0d\xce\xb4\x84\xc1\xca\x22\x86\x58\xb1\x68\x1c\xa4\x19\x3c\x17\xaf\x69\x29\x5e\x9f\x56\xf4\xe5\xbc\x8a\x1a\xcb\xd4\x15\xed\x8c\x39\x8d\xb9\x3c\x8e\xa9\xe0\xe0\xa7\x18\x09\x6e\xcb\x5c\x83\xca\x66\x4a\xb7\xcd\x39\xf5\xfc\x7f\xc6\x45\x90\xcb\x45\xc1\x7d\xb3\xe0\xba\x55\xc8\xbb\x07\xba\x3f\xa3\xff\x83\x78\x88\x6f\xa9\x7a\xf0\x44\x9c\xd6\xe8\xa5\x08\x9c\x24\x94\xee\xf4\x5e\xf5\x7c\x50\xd8\x5c\xdd\x0a\xfa\x22\xb0\x4c\x61\xc3\x86\x08\x24\xef\x21\x70\xf0\x23\x60\x03\xa0\x18\x4e\x1a\x04\x4e\x30\x05\xcc\xaa\xc6\xa9\x8e\xb6\x6d\x35\x71\xab\x79\x23\xcc\x61\x18\x48\x27\x5a\xff\xd8\x53\xac\x0f\xf3\x6d\x00\x73\x02\xa1\xe9\xf6\xa1\x0e\x7f\x4f\x90\x9b\xc9\x08\x68\x6a\x51\xa4\x2b\x76\xc9\xf7\x09\xd8\x7e\x7a\xf0\x97\x13\x6b\x1f\x06\x2c\x5b\x52\x2e\x69\xeb\xc6\x25\xde\x13\x03\x81\x0a\x5b\x22\x68\x34\xe0\x54\x6e\xdc\xcd\xb8\xa5\xfd\xb5\x1f\xf3\x9b\xd7\xad\x57\x96\xd1\x8f\x6b\x73\x63\x20\x54\x2d\x8e\xe3\x25\x44\x52\xc0\x53\x14\x64\x68\x8c\x09\x13\x8c\x23\xbe\x00\x58\x32\x10\x6a\x08\x0a\xdb\x35\xf0\x5b\x9b\x6d\x21\x71\xbc\x99\x84\x11\xb5\x11\x65\x67\x78\x2b\xaa\xe2\x53\xdf\xc0\xc3\x14\x8a\x58\x7c\x80\xfc\x27\x6a\xeb\x41\x7a\xb3\xa5\x76\x2e\xc7\x12\x0a\xea\x85\x51\x98\xb1\x83\x72\x78\x49\x60\x3a\xb5\xf8\xb9\xe7\x2d\x05\x27\xad\x69\x4a\xe0\x13\xb0\x8b\x2e\xd3\x82\x81\x99\xd2\x23\x15\x6a\xce\xa1\xc9\x08\x56\xf2\x61\x21\x3d\x8d\x82\x81\xa6\x28\xf2\xea\x1e\xcc\x66\xcb\x5e\xb7\xfb\xc5\x6d\x29\x6e\x09\x23\x70\xf6\xca\xb1\x58\x79\x73\xf7\xbb\x48\xd7\xd6\xc8\x21\x8d\x66\x3d\xf3\x89\x93\x70\x16\x61\x66\x6d\x70\xba\x80\xdf\x85\xfa\x15\x6e\x66\x40\xed\x1f\x7d\x1a\x80\x85\x0c\x0d\x1e\x8e\x65\x3c\x2c\xde\xce\xf6\x40\xaa\x2d\x98\x01\x2a\xfb\xb9\xda\x2b\xb2\x6a\xbc\xa3\x15\x06\x98\x15\x32\xb9\xca\x6d\x62\x83\x06\xf1\x78\x8c\x29\xfd\xc7\x23\x2e\xf6\x80\x18\x8d\x21\xa5\x60\x9e\x58\xa5\x88\xd9\x8a\xa3\x3b\x11\x11\x92\xe0\x4a\x79\xe5\xf4\xb9\x74\xbb\xb5\x50\x27\x7c\x21\x80\x95\xa9\x2d\x2f\x55\x21\xcb\x05\x63\xf1\xb4\x3e\x59\x9f\x96\xa9\xb1\x0f\x50\x1a\x8c\x29\xf6\xf0\x03\x80\x81\xba\x35\xa0\x21\x58\x71\x12\x5e\x52\x39\x8c\x73\x0c\x27\x40\x7e\x4d\x2c\xcf\x30\x8a\x55\xa4\x1d\x6f\xb6\x9c\xcc\xb6\x48\xcf\xf2\x65\xb7\xc1\x05\x9e\x2c\x06\xd7\x2d\x4c\xb3\xeb\x00\xb0\x06\x1f\x29\xf0\x9c\x20\x68\x6a\xca\x5b\x99\xba\x92\x9e\xd0\xa8\xf8\xc8\xdf\x9a\x22\xe6\x20\x8e\x2e\x71\x92\x69\xf2\x39\xcd\xf8\xc7\x0d\x45\xc1\x9a\x95\x5a\x36\xfa\x5d\x72\x3f\xd0\x2a\xba\x63\xae\x78\x59\xd2\xd6\xa7\xd8\x7d\x4c\x47\xad\xf8\xc8\x2a\x3c\xb7\x54\x7c\x0c\x1b\xd8\x20\x12\x89\xbc\xa6\x71\x9a\x86\xfd\x31\xf6\xaf\x58\x47\x53\xf3\x39\x6e\xc9\x81\xb2\x6d\x5d\xe9\x37\xae\x5d\xf8\xc3\x82\x82\xc4\xd5\x00\x59\xc1\x5d\xe5\xb7\x74\xe6\x72\x56\xfa\x82\x6f\xba\xba\xcf\x97\xb3\x98\xe1\x05\xe6\x2e\x44\x96\x71\x17\xfe\x2d\x28\x28\x56\x65\xd7\x76\x55\x73\xd7\x60\xc7\x13\xd3\xea\xc2\x5d\x58\x9c\x59\x74\xb5\xc0\xdb\xde\xf1\x86\xbb\x82\xc2\xc2\xdb\xee\x12\x62\xe1\x28\x40\xe9\xbb\xda\xe1\x14\x47\xc7\xc7\xef\xac\x6a\xe5\x1d\xe5\xd4\xe9\x77\x0b\x5e\x93\xf0\x7a\x3f\xd2\xcb\x95\x36\xab\xa2\xab\x38\x9d\x6f\x19\x23\xef\xba\xb1\x59\x89\xe1\xf7\xe8\xe1\x26\xe4\xc0\xea\x07\xce\x8d\x87\xb9\xc7\x09\xec\x0a\xf0\x3b\x1c\x85\xe6\x1a\xcf\x81\x03\x89\xd0\x52\x9a\x05\x0d\x32\xe8\x61\xe9\x21\x2a\x31\x8e\x62\xfa\xc6\x60\x80\x38\x41\x61\x8a\x8c\xbb\xdf\x05\x97\x34\x45\x5e\x5c\x41\xb2\xb5\xbd\x82\x96\x96\xdc\x7e\x23\xce\xf2\xb5\x2c\xa6\x39\x97\x7c\x6e\x2a\x05\xb5\x3c\xa4\xea\x25\x4c\x5e\x51\x25\x4e\x31\x36\x3e\x8b\x31\x59\x02\x7d\xfd\x4a\xc9\x55\xd6\xa9\xf1\x49\xbc\xe1\x47\x7a\x4b\xff\xe4\x94\x93\x28\x95\x15\xdd\xd9\xd0\x76\xe0\xda\x46\xfc\x74\xdf\xdc\xb0\x9e\xbb\x88\xd3\x05\x9a\x15\x17\x19\x9d\x61\xf7\xd2\x07\x31\xff\x2a\x47\xac\xba\xc0\xbf\xe4\x22\xde\xcc\xea\x20\x9e\x4c\x83\x0c\xb6\x97\xb2\xcb\x50\xdd\x16\x8c\x4d\x4c\x11\x7f\xca\xee\x89\xae\xe5\xb7\x08\x72\xf7\x65\x14\x19\xd3\xb6\x8f\x39\x79\x7b\x08\x59\xa1\x2e\x1f\x6f\xd4\xe8\x5b\x14\x2f\xcd\x7d\x0b\x54\x4e\x6a\xb4\x29\x6d\x09\xca\x2f\xae\x60\x95\x44\xdc\x35\x2a\x90\x77\xae\x63\x2c\xf4\xd7\x3e\xc4\x92\xe2\x5e\x35\xd2\x95\x12\x89\xc7\xba\x99\x38\xad\x5f\xaf\xb7\x3a\x8d\xce\x60\x03\x92\x3b\x74\xd6\x3b\xed\xf5\xd1\xfa\xe8\x6c\x99\x5f\x33\x00\x68\xfe\x20\xfb\x51\x5e\xf1\x61\xa2\xe0\x1d\x0b\xcf\xe1\x4b\xd4\x95\x8c\x8c\x86\xec\x99\x7f\xcf\xcb\x5b\x63\xaa\x2f\xd6\xbc\xc2\x23\x5f\x27\x92\x4e\x17\x5e\x32\x7a\x3c\x0a\xbe\xa0\x17\x58\xc3\xf7\x1b\x9c\xc2\x16\x46\x8d\xa5\x37\x0d\x92\x14\x57\xb4\x85\x9a\x73\xe9\x9a\xa4\x9a\xe2\x47\x56\x73\x7a\x5c\x90\xe2\x88\xc6\x31\x2b\x58\x74\x94\x30\x2c\x64\xf2\x54\xc7\x79\x10\xf9\x45\xa3\x72\x18\x66\x89\x31\x2c\x70\x27\x38\xcd\xa8\xdd\x46\x30\x76\x2c\x50\x03\xe6\x69\xfd\x0c\x6d\x6d\x21\xb9\xf6\xd0\x0f\x3f\x98\xed\x9e\x36\x58\x19\xbe\x26\x7d\x2a\xa8\xdd\x6b\x7a\x39\x63\xb7\x8c\x74\x0e\x63\x2d\x7e\xa3\x45\x66\xa6\xd4\xac\xa2\xf6\x72\x8e\xe5\x60\x7c\xc9\x8e\xe8\x70\xcd\x25\x61\xd8\xe5\x2d\xf8\x53\x68\xa0\x6e\xde\xc8\x5b\xc5\xb5\x1b\xab\x46\xa7\x1c\xa3\x70\x1e\x8d\x3c\xc7\xa0\xaa\x72\x3a\xd1\x45\xf3\xdc\x7b\x30\xbe\x08\xaf\x92\x60\x3a\x05\x39\x32\xc8\x58\xf3\xaa\xca\x04\x05\x64\xa7\x4f\x15\x8f\xbb\xdc\xd5\xab\xb9\x31\x59\x6e\x7a\xd2\x99\xc9\xf5\xa9\xac\x73\xcc\xc2\x17\x59\x42\xe8\xe1\x32\x7e\x9e\x54\xcf\x75\x04\x6a\x6f\x59\x67\xa9\xb3\x6b\x34\xa4\x54\x23\x0e\x18\xf2\xd2\xca\x71\x70\xca\x0b\x7f\x65\x7a\x66\x02\x42\x5d\x4b\x54\x53\x26\x36\x37\x60\x16\xbb\x52\x21\xf3\xc6\x3c\x05\xef\xe2\x7d\x2b\x95\x4f\x8e\xa3\x4e\x8e\x67\x3d\x6b\x9a\xda\xd7\xb0\xdf\xd2\xa7\xfe\x2f\x12\x9f\xc6\xbd\x85\x6d\xff\xb9\x1b\x18\x59\x96\x6e\x8d\x8a\xbd\xac\x84\xef\xa8\xad\x8d\xd0\xdc\x48\x3d\xa7\xb0\x87\x6b\x50\x06\xe0\x31\xd5\x09\xdf\xb4\xf1\xaa\x58\x6d\x1e\x69\x20\x47\xd9\xe1\x70\xfc\xf5\x7b\xe8\x2e\x16\x1e\x68\xae\xc8\x40\xbb\x2e\xdb\x7c\x25\x72\x43\x2c\x1d\x6b\x5c\xc1\xdd\x9c\xfe\xb8\x96\x58\x22\xbd\x4e\x44\xf0\x07\xd6\xd9\x45\x24\x93\x1e\xf4\x4f\xc2\x84\xeb\x6d\x0b\x0a\xb3\xa5\x22\x1c\x57\x38\x1d\xfb\xf6\xa3\xf2\xdb\x69\xde\xa6\xad\xed\xaf\x76\x41\xae\x5a\x74\x7c\x22\x64\x25\xfa\x56\x0b\x2f\x1d\x45\x14\x1d\x21\xa3\x17\xbb\x0c\xd5\x0a\x4a\x40\x70\xa9\x6a\x17\x13\xfa\x40\x59\x92\xbd\x72\x14\x56\x74\x81\xa6\xf5\xb8\xa3\xb4\xa2\x17\x24\xa4\x37\x72\x1c\xd7\x6e\x4b\x1f\x5b\xd8\x1d\x7b\x2a\x26\x4e\x28\xbe\xd4\x6b\x19\xf4\x60\xdb\x93\x4a\x00\x62\x87\x32\x2e\x9a\x94\x47\x48\x6f\xfe\x57\xdc\xa7\x8c\xe0\x33\x22\xda\xf3\x37\xd8\x9b\x64\x64\xe9\x62\x36\xcd\x23\x03\x38\xd8\x34\x27\x3b\x16\x22\x42\x89\x16\xb0\x30\xcb\xbe\x6f\x14\xcd\x7d\xe9\x1e\xb7\x14\x6f\x5c\x06\x4f\xf4\x84\x6f\xb0\xab\x30\x8d\x83\xa2\x5a\x50\x17\x93\x01\x58\xdd\x29\xd8\xed\x37\x9c\x5f\x55\xe4\x25\x37\x71\x35\xc7\x38\x85\xbd\x61\xa8\x93\xa7\x6d\x62\x5a\x44\x49\x3a\x2c\x72\x6f\x52\x98\x8c\xa6\xf0\x71\x6e\x13\xa2\x89\xb9\xb5\x31\x4e\xb6\x66\x8e\x95\x7e\xff\x02\x3a\xa6\x20\x4d\x67\x13\x3c\xd4\xef\x13\x83\x71\x82\x83\xe1\x8d\xb2\xdf\x69\x07\xb2\x59\x44\x53\x77\x96\x88\xd6\x36\x1f\xdb\x73\xf3\xaf\xb9\x0e\x4d\x84\x71\x81\xf9\x7d\x92\xe2\xb9\x79\xbd\x5f\x5f\x34\x8b\xe6\x85\xf5\x27\x4a\xdc\x16\xc9\x53\x15\xd2\x21\xa7\x02\x24\x88\xdf\xcd\x03\x3e\x59\x3a\x25\x75\xf5\xb0\xca\xae\x74\xe6\x2c\x2e\x8f\xba\x08\x0b\xc2\xf3\x70\x9b\x10\xca\x9e\xec\xa5\x6a\x5e\x6c\xa0\x5c\xed\x28\x83\x96\xa3\x14\xb5\x34\x13\xce\x1b\x92\xb7\x6e\x13\x89\xa2\x2b\x93\x2f\xc3\x11\xdc\x97\xd0\xff\xe6\x5f\x96\x14\x59\x61\xd8\x17\x26\x6f\x29\x74\xd2\x4a\xb9\x7b\x92\x1d\x02\x1e\xee\xf4\x49\x63\x64\x2d\xef\xff\xc2\x15\x06\x53\x16\x0b\xa9\xbc\x3a\x96\xd7\x60\x96\x17\xec\x01\xe4\x14\xd2\x0c\x00\xce\xf7\x78\x91\x41\xd8\x31\xb5\xad\x08\x23\x66\xa5\xcc\xec\x00\x98\xc9\xcc\x39\x8e\xc0\x50\x39\x1f\x9a\x88\xc0\xee\x01\x46\xc3\x82\xe7\xc3\xb2\x75\x06\xa0\xc2\x52\x84\xa4\x6d\xd4\x69\x83\x39\x35\x7c\xe0\xb6\xc1\xfb\x23\x14\x4f\x42\x22\x23\x54\x51\x40\x3f\x5d\x85\xe3\x31\xea\x63\xd1\xe0\x10\x25\x41\x34\x8c\x27\xe3\x9b\x7b\x3a\xdc\x53\xab\x09\x36\x4c\x55\xb4\xff\x4b\x15\xa6\x94\x34\xfe\x0d\xb8\x10\x9d\xe4\xa1\xcd\x82\x14\x6a\xac\xe1\x6b\x3c\x98\x65\xb8\xb2\xc4\x23\x6d\x2d\x55\x59\xf2\x92\x2a\x33\xdf\xf2\x88\x45\xf7\x04\xbd\x8a\x96\xc8\x70\x90\xff\x5f\xf2\x9f\x99\x29\x18\x95\xbb\x71\x6a\xae\x72\x12\xad\x32\xea\xa2\x8a\x4d\xb7\xc3\x02\x9d\x66\x36\xcb\x1e\x45\xf5\x33\xef\x55\x92\xa5\x44\xa6\x70\x2a\x9d\xf6\x9a\x95\xda\xdd\xe1\x32\x48\x97\xb6\xb2\xae\x6d\x69\x85\xc6\xd2\xa5\x49\x15\xa4\x02\x57\xc4\xef\x93\xa9\xa0\xd9\x42\xba\x5d\xae\xb1\x64\xe6\xca\x78\x00\xfe\xce\x60\xbc\x84\x36\xb3\x7c\x0c\xc0\x26\xdc\x52\x93\x8b\x84\xd8\x4c\x41\xce\x13\xea\xf2\x31\x47\x3f\xda\xfa\x6c\x2d\x3d\xb6\x4c\x63\xed\x76\x04\x3b\x62\xa2\xd4\x9c\x87\x71\x79\xa4\x16\x52\xf4\x62\x5a\x6d\x97\x66\x40\x53\x71\x0f\x19\x5f\xe6\x2c\xcf\x60\xc9\x55\x01\xcb\x23\x7e\x2d\xae\x0f\x77\x44\xc0\x13\x0a\x71\xf7\x37\x97\x86\xeb\x01\xf5\xe3\x6f\x77\x5e\xdf\x22\xb2\x7d\x72\x0b\x4a\xd7\x2e\x2c\xa5\x3c\xce\x6c\xf3\xb7\xb8\xb9\xb4\xe2\x8e\x0e\xfb\x1d\x3b\xbe\x0c\x47\x5d\x65\x7b\x56\x28\x64\x4e\xf5\x38\x73\x17\x9b\x67\x5f\xfe\x3e\xf4\xe5\xb9\xd2\xc1\x77\xa0\x8e\xf8\x8b\xa8\xcd\x1d\x8b\xaf\x94\x26\x79\x89\x0f\xb5\x2f\x64\xee\xc3\x37\x5c\x45\x7f\x3c\xb0\x06\x5b\x6e\x47\xdf\x48\xe1\x60\xec\xae\x71\xe6\x53\xee\xba\x64\x17\x02\x9e\x88\x2d\x5c\x5c\x51\xb0\xa7\xc3\x2b\x64\x0c\xf6\x4c\xb7\x3d\x9f\xe7\x2a\x15\x63\x69\xdf\xac\x2e\xd5\x60\x8b\xd5\x30\xa8\x39\xc3\x2d\x78\x15\xf3\x9a\xbe\xc4\x7f\x9d\xa1\x06\xb7\xb0\xe6\x47\x6f\x5f\xd1\xe3\x5b\x68\x1c\x84\xd7\x34\xd1\x09\x54\x70\x0e\xa9\x72\xb6\xa6\x86\x99\x1a\x74\x9f\xde\xc4\x79\xe2\xbb\x83\x3e\xf8\x4f\xe0\xc7\xf7\xac\x20\xfe\xde\x19\xf3\xf7\xa8\x27\x76\x31\xc3\x79\x15\xc5\x77\x62\x8c\xf7\x8e\xa2\xad\x28\xbe\x2f\xc6\x5d\x52\x4f\xfc\xcd\x79\xf7\x37\x57\x16\x7f\xfb\xad\xa2\xaa\xd9\xf6\x78\x4e\x68\xf7\xb7\x77\x94\xd2\x87\xfb\xef\x2f\x5c\x5b\x87\x3a\xbe\x25\x77\x8f\x3c\x05\xb9\x54\xe5\x89\x6c\x9f\x6a\x5a\x4f\x35\x87\xa7\xc8\xd8\x79\x7b\x56\x5d\x6f\xde\x29\x4d\xe7\x03\xe6\xe7\xbc\xf7\xcc\x9b\xf3\x66\xd9\xd4\xb2\x6b\x5a\x88\xd9\x89\x36\x8d\x04\x9b\xbc\xa2\x27\xc5\x26\x68\x4b\x25\x70\xf1\x53\x4f\xb3\x79\x10\x64\x17\x55\xe4\x48\xb6\x29\x0f\xdb\xef\xe2\x41\x30\x46\xd3\x78\x7c\x33\x0a\xc7\x28\x1e\x21\xba\x85\xb1\x33\xbd\xe3\x00\xcc\xa2\xf8\x6f\xe9\x05\x8d\x86\x35\x36\x25\x5e\xbf\x26\xef\x6f\x5f\xda\x51\x92\x14\xcb\xcb\xfe\x67\x8b\xc5\x81\xc5\xe0\xac\x4f\x66\xd0\x24\xe9\xd7\xb5\x69\x12\x67\x31\xf9\x84\xb6\xc8\x59\xc4\x2c\xc0\xea\xa1\x2d\x14\xe1\x2b\x82\x40\x3e\x84\x68\x36\x1e\x7b\x96\x8d\xc0\x40\x2e\x1a\x25\xb2\x93\x2b\x66\x29\x9f\x93\x7c\x95\xb7\x57\xcd\xfd\x2e\xec\x27\x41\x72\x53\xa4\x31\x57\x32\xa1\x7a\x41\x41\x5e\x54\xa6\x03\x25\xa2\x06\xef\x72\x30\x46\x61\x74\x81\x93\x50\x0b\x55\xab\xc5\xae\x30\x33\xaa\xda\xb1\x54\xed\xe9\x2c\x11\xe0\x90\x47\x53\x06\x67\x39\xe1\x75\x70\x11\x64\x1c\x21\x16\xb4\x84\x0a\x45\xd6\x19\x13\xa1\xbc\x88\x87\x5c\x0a\x8b\x2f\x71\x92\x84\x43\x9c\xa2\x0f\x54\x3d\x12\xe2\x94\xb2\xf3\xe9\x0d\x0a\x23\x96\xdf\x59\x22\x50\xa2\x05\x33\x2b\xc5\xc9\xbc\x00\x2c\x09\xcc\x53\x6e\x9e\xf8\x88\x64\xa2\x0e\x6e\x4e\x28\x09\x6b\xb2\x4e\x8e\x81\xa2\xea\x3d\x16\xe2\xf1\xb0\x8b\x96\x20\x27\xd8\x92\x69\x46\xe2\x6e\x93\xfc\x4d\x70\x76\x11\x0f\x73\x3d\xe6\x95\xd2\x66\x36\x00\x97\x1b\x1a\x42\x76\xe0\x46\x8a\xbe\x66\x9e\xcd\xe7\xd5\x1b\xae\x71\x1a\x5c\x45\xf6\x17\x85\x91\x10\xd1\x41\x26\x10\xf4\x19\x17\x6f\xcf\xce\x27\x38\x72\x18\x12\x93\x1d\x25\x1f\x0b\x24\x99\x0f\x3b\x85\xc9\xf2\xce\x44\x17\x4e\x04\x98\xd1\x14\x77\x04\x0b\x85\x9b\x69\xe2\xc6\xe9\x19\x6f\xf2\x22\x48\x0f\xaf\x22\x46\xf6\x37\x95\x25\x52\x73\x69\x59\x78\x40\x91\x47\xd8\x04\x79\x79\xf2\xa2\xb0\x1f\xb4\x56\xee\x74\x3b\x6a\xfd\x7f\xd3\xd9\x94\x08\x5e\x51\x98\xd5\x02\x22\xaa\xb2\xad\x2f\x48\xce\x67\x64\x74\x9d\xe3\x81\x1c\xb9\x22\x72\xc6\x49\xfa\xdf\x26\x4b\x29\x92\x1c\x3d\xa4\x2a\x62\x3e\xe9\x74\x95\xda\x10\xd4\x0e\x6a\xfb\x81\x67\xdb\x41\x5c\x4d\x3e\xc2\x09\x8e\x06\xa4\x01\x18\xe7\xa9\xb9\x5e\xad\x61\x60\x52\xb2\x0b\xa0\x77\x9f\x41\xae\x24\x20\x2e\xa6\xba\x0b\x2b\x25\x55\x99\x26\x55\x80\xcf\x22\x3a\x0e\x30\x81\x74\xd5\xda\xc1\x5e\xb7\xf9\x7c\xc8\x5c\x3d\x95\x65\x71\x29\x47\x04\x6b\x08\x9e\x07\x40\x2a\xcb\x7f\x65\x5e\xc9\x63\xb3\xa3\x2d\xc6\x36\xf9\x0d\x46\x21\x2f\xa2\xe5\xf2\x39\x9e\xdd\x08\x2c\x39\x19\x11\xdc\x5e\xb9\x3c\x56\xbc\xb2\x46\xf8\x3b\x7d\x9d\x78\xa9\x86\x17\x5f\x84\x6c\xf2\x9c\xd7\x3d\x73\x85\x0e\x19\x33\x63\xe9\x10\x80\xa4\xc0\xa0\x7e\x38\x44\x69\x3c\xc1\x34\xc9\x16\xba\xba\xc0\x11\xba\x89\x67\x89\x30\xba\x0f\x88\x38\x4b\x81\xdf\x73\x94\xe0\xbb\xee\x82\xa6\xdb\x73\xde\x5e\x86\x28\x03\xa8\xd5\xec\x91\x11\x43\xbf\xe0\x76\x57\x88\x46\xa9\x39\xed\xc5\x53\x22\xec\x4c\xa5\xdc\xc3\xe4\x9d\x3b\x88\x53\x0a\x30\xd0\x37\x69\x32\xd5\x04\xf4\x92\xf7\x3c\xa5\x6c\x75\xd2\xfd\xb3\xac\xfc\xb2\xe0\xb8\x43\x23\xda\x95\xb6\xe8\x9f\x73\x8d\x8b\xd8\x8e\xfc\xea\xed\x7d\x30\x01\x13\x8a\x82\x7a\x88\x6d\xd5\xb2\x98\xb9\x59\xab\x00\x97\x73\xb7\x58\x32\x9d\xa7\x6a\xf1\x33\xb4\xa5\xb4\xaf\x7f\x9a\x23\x49\x93\x67\x93\xdd\x45\x57\x71\xb4\x94\x51\xf9\x99\x3b\x3f\x2a\x61\x1a\xc7\x71\x3c\x45\x41\x3f\xbe\x74\x6c\x83\xf9\x5d\x5e\xe2\xd0\x96\xfc\x1d\x06\x2e\x2a\x5a\x55\xfb\x29\xde\x96\xc8\x20\x56\x6a\xf1\x88\xc3\x09\xf4\x14\xac\x61\xe6\x59\x37\xae\x8d\x6f\x30\x8e\x23\xfc\x00\x1c\x0f\xe0\xa2\x2d\xb9\x87\xc0\x8b\x12\x3b\x19\x29\x56\xb8\x91\xa9\x59\x57\x74\xe1\x88\xf3\x53\xa7\x75\x99\xfb\x8c\xec\xbc\xeb\x8f\x50\x00\x7e\xb8\x46\xd4\xc5\xdc\x38\x43\x56\x44\xfb\x7c\x10\xae\x60\x35\xc2\x14\x42\x0f\xfc\x98\x86\xe7\x51\x38\x0a\x07\x41\x94\xb1\xd0\x99\x21\xed\x3d\x80\xa4\xed\xb8\x8e\xc9\xbf\x2a\xfe\xc4\xf4\xac\xac\xbe\xb9\x87\x20\x32\x76\xf3\x26\x59\x78\x02\xfe\xab\x86\x58\x05\x63\x8d\x9c\x46\x62\x62\xa4\x8c\xfb\x8c\xc2\x41\xc3\xf7\x96\xd4\x46\xf5\xd6\xd6\x36\x76\xcb\x32\xc6\x73\x17\x50\x1e\xc0\x69\xfd\xba\x5e\xaf\x37\xea\xcd\x7a\xab\x8a\xea\xd7\xf5\x76\x7d\xbd\xde\xa9\x6f\x9c\x3d\x18\xe0\x2a\xea\x94\x0e\xc4\xc2\x82\xd9\xf1\x19\xb1\x56\xec\x15\x73\x0f\x86\xe5\xca\x1f\xe8\x7f\xbf\x7e\x85\xe8\xc4\x86\xa8\x31\x42\x15\x31\xbd\xcf\xb6\x1c\x8a\x42\xf5\x0f\xa0\x2a\x26\x44\xfc\xe7\x7c\x11\x07\x15\x00\x94\x3c\xc6\x38\x3a\xcf\x2e\xa8\x21\x92\x97\x8b\x94\x8f\x20\x23\x17\xca\x7c\x71\x63\x76\xa3\x41\x3c\x24\xf4\x8e\xe9\x0f\x93\xdc\xe1\x75\x7e\x94\x53\x41\x00\x38\x1a\xd4\xf6\xf0\xb5\xbf\xcd\xa2\x70\x32\xa5\x56\xfb\xdc\xa1\x5e\x24\xb1\x96\x88\xf3\xe2\x88\x72\x50\x14\xe4\xc5\x51\x45\x1b\x92\x8f\xd9\x68\x73\xae\xd8\x2e\x6c\x2a\xbc\x91\x5d\xf8\x54\x7d\xfd\x8a\xf6\xf0\x75\x6e\x30\x97\x02\x02\x1a\x04\x19\x8e\xd8\x9e\xaf\x53\x90\x87\xf9\xfb\x09\x49\xb9\x95\x95\x03\x7e\xc2\xb8\xa1\x42\x99\x90\xd0\x78\xde\x5b\xde\xb2\xb8\x94\xa1\x0d\x81\x5d\x83\x47\xd3\x10\x6f\x9a\xfe\xe4\x6d\x50\xd2\x64\x4a\x34\x84\xf5\x7c\xc1\x49\xc8\xc0\xfe\x6a\x31\x2c\x87\xe7\x62\x76\x11\x88\xc0\x07\x92\xc4\xfc\xa5\xc3\xf4\x58\xf2\x18\x8d\xe7\x78\x80\x1f\xeb\x2c\x89\xc2\x97\x75\xac\x4e\xf5\xc6\xc1\x64\x8a\xf0\x35\xc4\x95\xec\x87\x66\xe7\xe8\x2d\x2b\x29\x63\xdf\x36\xd0\xdb\xd5\x81\x2b\x64\x8a\x86\xf8\x3f\x3c\x21\xe1\xa1\x3e\x11\x49\x23\x0c\x5b\x2d\x0a\x32\x14\xa0\x2c\x9c\x38\x24\x6e\x57\xf0\x79\xb5\xbb\xfe\xf4\x17\xea\xe0\x90\xa2\x68\x8b\xa0\xc7\x66\xe1\x34\xe4\xf1\xbf\xc9\x7f\x2a\xcd\x36\x5a\x45\x95\x90\x62\xfc\x23\xda\x5c\x5e\x16\x71\xc1\xbd\x52\x3c\x85\xa3\xf7\x78\x05\x85\x22\xb0\xf8\xd7\x2d\xd9\xf4\x4f\x3f\xf1\x36\x1c\xe5\x45\xa3\x25\x04\x7f\xef\xb6\xa4\x8e\x29\x5d\x5c\x77\x1a\x53\x7f\x3c\xff\xb2\xdd\xdf\x42\xf6\x60\x97\xc9\x8d\x6c\x53\xa1\xd8\x6c\x57\xb6\x74\x34\x5d\xd9\x64\x82\x30\x0a\xfa\xe6\xc9\x43\x39\x00\x94\x65\xa7\x34\x22\x07\x11\x02\x35\xc1\x30\xcc\xee\x2a\x0a\xca\xc5\x29\x56\x97\x87\x49\x91\xcf\x65\x03\xf9\x3a\x58\x93\x2d\x47\xb9\xa2\x24\x79\x99\x8c\x9b\x61\x38\x44\xb5\x53\x01\x83\x47\xd4\xdf\x82\xa5\x43\xff\x80\xf4\x5b\x4d\x42\xfa\xa9\xc6\x17\x1c\x04\xaf\x89\x52\x5b\xe8\x20\xc8\x2e\x6a\x03\x1c\x8e\x65\xcd\x35\x34\x47\x7c\x22\xf7\xf9\xb7\xd4\xce\xe3\x31\x4e\xb2\x8e\xbf\x8b\x5a\x81\xb2\xe3\xae\x4a\x0b\xd6\x79\x57\xa7\x85\xa2\x73\xae\x0a\x16\x4e\x6a\x14\x57\x35\xce\xbb\x7d\x72\xae\xd9\x34\xc2\x8c\xf1\xeb\x5e\x03\x3b\x52\x6f\xfe\x29\x50\xc4\x86\x51\x38\x1e\xf3\x20\xb4\xcc\x69\x02\xce\x5b\xc5\x42\x09\x3f\xcc\x45\xae\x43\xaf\x0a\xca\xeb\xf0\x53\x6a\x96\x19\xa4\x52\x84\x72\x5f\xa6\x68\x25\x8e\x60\xcc\x31\xa4\xe1\x3f\x69\xd1\x12\x2a\x99\x44\xee\x23\x96\xca\x1e\xec\x03\x15\xf9\x9a\xe8\x37\xe4\x93\x4f\x57\xfe\x78\xfa\x9f\xae\xd0\x16\xf9\xd7\x93\x2a\x6e\xf2\xe9\xdf\x64\x9b\xb9\x6e\x05\x43\xdc\xd9\xec\x9b\x91\xe6\x45\xb1\x20\xfd\x82\x54\xce\x91\x73\x4f\x50\xe2\xee\x8e\xb6\x5a\xa9\x5f\xbf\xa8\x77\x5e\xa0\x1f\x49\x17\xfe\x0d\x7b\xfa\xeb\xd7\xaf\x5f\x2f\xa3\x15\xfa\xe2\xe7\x9f\x51\xfd\xba\x51\x87\xed\x9e\x20\xe0\xd9\xee\x69\x17\x2b\xf5\xeb\x76\x67\xbd\x4e\x81\x5d\x99\xc0\xae\xca\x02\x83\xe1\xc5\xe9\x0c\xfc\x7e\x2a\x80\xc6\x4f\x3f\xd1\x9a\x68\x05\xc1\x48\xe7\xd6\x67\x75\xd7\xb6\xa0\x0e\xfb\xcb\x2f\xbb\xb2\x85\xea\xb5\x75\x6f\x19\x18\x53\x56\xf4\x47\x6a\x6f\xc3\xa9\x6d\x19\xfd\x8c\x6a\xeb\xe8\x9f\xa8\x81\xba\x68\xb5\x51\x46\x44\xb1\x38\x87\x2e\x6e\x54\x51\x32\x08\x06\x17\x98\xe5\x11\x2a\x16\x38\x48\xcd\x4f\x84\x1e\x93\x4a\x85\x56\x25\x47\x25\x0d\x49\xb2\x9b\x28\x83\xe1\xbe\x62\xa2\x55\xb7\xd0\xa7\xa4\x42\xcb\x03\x41\x6e\xf4\x37\x1c\x7d\xba\x92\xd9\x8a\x2a\xa2\xbc\x84\x8f\xbe\xa2\x7a\xc9\x20\xe7\x11\xbe\x52\x5c\x9f\xe0\xd6\x91\x29\x40\x22\x9e\xa8\xe8\x89\x31\x92\x6e\x57\x54\x76\xb4\x2f\x32\xa4\xc1\xd1\x00\x0c\x69\xe8\x7f\xdd\x86\x34\x7b\xf8\xda\xd6\x04\xb8\xc0\x91\x82\x5b\x14\x68\x8d\xfe\x2e\x17\x8d\xd3\x54\x5f\x5c\xe0\xeb\xd2\x2a\x8c\x12\x27\xcf\x39\x63\x6c\x96\x6a\xfd\xbe\x18\xf9\x05\xbe\xb6\x03\x6a\xb2\xf1\x53\x8e\xf6\xc5\x29\x93\x9c\x61\x34\x17\x3d\xa6\x5e\x95\x3e\x79\xa6\xf3\x1e\x23\xe9\xac\xdb\x80\x2e\xf0\x75\xef\x22\x48\x4a\x67\x14\x4b\x0b\x0f\x74\x90\x0d\x2e\xa4\x07\xb9\xab\x3b\x1e\xe2\x38\x76\x6c\x8d\x03\x58\x02\xa4\xbd\x2c\xd5\x3e\x8d\xce\xb2\x8b\xdf\xb9\xaa\x92\x76\xea\xa3\xfc\xba\x1e\x06\x21\xc0\x7d\x8e\xc3\xa8\xb2\xb4\xb4\x40\xfc\x4d\x85\xc2\xe9\x7a\x9b\x47\xd3\xc3\x57\x0a\x25\xdc\xf2\x0b\xc6\x23\x3c\xfd\xf9\x52\x13\x5f\x6c\xd4\x82\x5b\xac\xc7\xf2\x71\x33\x69\x95\xf9\xd2\xa6\xd0\x3a\xef\xf8\xd1\x85\x3e\xb2\xa3\xcc\x3c\xab\xe6\x6a\x9e\x24\x7c\x6a\xa3\x6c\x0b\x6d\xe6\x64\x02\xa5\xab\xa5\x05\x9a\x09\xe8\xf4\x7e\x94\xb1\xce\xd6\xd2\x59\x3f\xcd\x92\x4a\x58\x45\xcd\xe5\x2a\xa4\x1b\x94\x2a\x0b\xb2\xa2\x36\x97\x5d\xee\xb8\x73\xef\x79\xda\x30\xad\xa1\x66\x59\x67\xda\x77\x41\x16\x46\x8d\x72\x9b\x16\x2b\xcb\xf7\x2d\xf1\xb8\xd8\xd6\xc5\xaa\xff\x79\xbb\x57\x59\x04\xee\x6b\x4d\x8d\xa1\x3d\xf7\x1e\x46\x71\xf9\x8f\xda\xc6\xe8\x70\x7c\xc7\x3b\x99\x82\x20\xdd\x91\xe8\xd4\xd5\x46\x49\x3c\x21\x6f\x7b\xf1\x10\xc3\x26\x55\x76\x43\x52\x01\xde\x61\x4f\xd2\xe8\x76\xf1\x6d\x49\x90\xe3\x5c\x8b\xe1\xbb\xde\x9c\xd8\x2a\xa2\xfb\x93\xba\xdc\xca\x6f\x51\xa2\xd6\x7c\xbb\x94\xa8\x26\x36\x2a\xf1\xe6\xa1\xf7\x2a\xa3\xe9\xa2\xac\xd5\xa1\xa2\x45\x97\xbd\xad\x0d\x18\x41\x6f\x67\x95\x90\xaf\x09\x73\xab\x72\xeb\x16\xe7\xde\xaa\x0c\x84\xcb\xee\x54\x1f\x4f\x5e\xaf\x6e\x96\xdb\xa8\x3e\x66\xa3\x4d\xb1\x4d\xb1\x87\xc5\x36\x29\xda\xe8\x9f\xb7\x47\x95\x6c\xff\xbe\x56\xd6\x2c\x1b\x6d\xba\x37\x28\x32\x8a\x0f\xb9\x3d\x65\xc9\x4d\x8e\x81\xd1\x10\x93\x23\xfa\xc7\xa3\xfd\x1e\xf7\x74\xaa\xe0\x74\x10\x4c\x71\x25\x67\xe3\xb4\xd9\x32\x1a\x04\xd9\xe0\x02\x55\xec\x44\xd9\x80\xc2\x45\x12\x5f\x01\xdd\x42\xfe\x95\xca\xd2\x41\x30\x1e\xc5\xc9\x04\x0f\xd9\x34\x0c\x83\x2c\xb0\x13\xd2\xcd\xcf\xc0\xd5\x49\x5d\x9c\x7f\xb3\xb9\x9a\x87\x4c\xbe\x6b\xe6\x0d\x14\x46\x59\xb7\x24\xc3\xf2\x8c\x9b\xd5\xf1\x19\x03\x68\x5b\xc3\x2c\x62\xd4\x43\x2d\x04\x34\xba\xe2\x70\x96\x4b\x87\xa3\x11\xc9\x86\x21\xed\x27\x1e\xb2\xdc\x66\x8a\x4f\xba\x37\xe7\xb0\x62\x27\x7b\xa3\x24\x48\x9b\xcc\xd2\x0c\xf5\x31\x0a\xc9\x88\x4e\x70\x94\xd1\xac\x6b\x01\x5c\xaf\x27\x38\x13\x1e\x0b\xa5\xb2\x18\x1b\x29\x49\x75\xe5\x3e\xcd\x78\x48\x5d\xab\x64\x2a\xfc\x2f\x78\x9a\xa1\x59\x34\xe5\x29\x04\xf5\x44\xa8\x8a\x4d\x4b\xdd\xc1\x7d\x5f\xb1\x71\x80\xbc\x83\xdb\x62\x14\x84\x97\x98\xef\x73\x49\x33\x38\xc8\x63\xcb\xac\x79\x8c\x91\x5e\x62\xe9\xc2\x59\xbe\xd6\x2c\x46\x61\x96\x72\xaf\x18\x44\x28\xf8\xae\x77\x4c\x7d\x27\xf2\x34\xf5\xaf\xff\x92\xa9\x54\x7e\x61\x66\xde\x87\xc0\x4a\xd9\x65\x33\x00\xf9\x38\x99\xa7\xa2\xb1\xb3\x9a\x4c\x89\x96\x8f\x76\x82\x2c\xe0\xc2\x7a\xbd\xac\xa4\xb9\x3d\x1c\xa6\xd0\x06\xcf\x80\xee\x19\x69\x46\x0b\xe5\x37\x45\x11\x72\xc1\xca\xb1\xce\x8c\x5d\x10\x5d\xf3\xcc\x09\x80\xf2\x4b\xea\x53\x12\x28\x16\x94\xd4\x9e\x18\x38\xde\xc3\x4c\xe6\x27\x8a\x4e\x65\xc9\xe6\xf7\xa5\xea\x15\xef\x8d\xac\x64\x99\xb4\xed\xb6\xb3\xbd\x4c\x4e\xa7\x86\x17\x55\x06\x88\x85\x16\xd5\x41\xa9\x3e\xce\x40\x46\x05\x51\x23\x19\xad\x29\x4c\x19\x30\x2c\x8e\x9b\xb6\x0d\x5d\xf3\x91\x2f\x37\x25\x72\x01\xb3\x88\x76\x65\x4b\x4f\x07\x5f\x96\x82\x79\xe6\xd3\x14\x05\x97\x41\x38\x86\xf8\x5d\x94\x2f\x00\xb3\xf3\x53\xcd\x89\xe2\xac\x12\x46\x97\xf1\x17\x9c\x9a\x29\x87\x2b\x2c\x55\x70\x15\x5d\x5d\x84\x83\x0b\x27\xab\xee\xdf\xe4\xb0\x6a\xbb\x55\xbe\x50\xfa\x71\x3c\xc6\x41\x74\x8b\x86\xf1\xeb\xf1\x2c\xbd\x40\xbf\x5e\xe0\x8c\x46\x37\xe1\x99\x69\xc1\x5d\x6b\x1a\x24\xc0\x28\xd8\x2b\xc9\xb5\x05\xbb\x5e\x20\x38\x88\xe0\xf4\x30\xe2\x77\xdf\xe6\x05\xc0\x1d\x4a\x48\xbe\x35\xc3\x13\xe7\xfa\xa2\x74\xcc\x09\xc6\x9d\x37\x58\x8f\xbc\x4a\x8b\x6a\x8b\x8f\x0e\xf8\x9c\x3a\x13\xb6\x44\x24\x71\x3b\xb4\x25\xe4\x35\x37\x4e\x83\x91\xf5\xa9\x55\xc8\x47\xc5\xd0\xcc\x47\xf7\xbc\xb8\x94\x15\xb6\x8c\x04\xcd\x79\x85\x39\x74\x59\xdb\x1d\xdf\xaf\x17\xcf\xa2\x8c\xd3\x97\x83\x99\x10\xa0\x11\x4d\x2b\x7c\x04\x51\x8c\xb7\x74\xfc\xd7\x8c\x26\x5f\xda\xbc\xc8\x37\xe4\x0c\x83\xa3\x78\x16\x0d\xd1\x6c\x4a\x1d\x0a\x07\xe3\xd9\x10\x1b\x74\x6f\x57\x33\x30\x92\x46\x2e\xea\x87\xf2\x91\x6e\x05\x16\xc3\xf8\x2a\x52\xf1\x88\xa3\xf1\x0d\x1a\xcd\xc4\xa2\x74\xc4\xd5\x5f\x5b\x43\x63\x9c\x52\xa7\x4a\xb7\xac\x05\x7c\x23\xc1\x93\x20\x8c\x74\xe1\xaa\x5c\xbf\x26\xc1\x75\x45\xeb\x17\x5c\x9c\xa2\x55\x57\x12\xfa\x6a\xf1\x95\xaa\x98\x73\xaa\x79\xf0\x4d\x39\x50\x32\xc7\x43\x6b\xfd\x47\xa4\x10\xa0\x8f\x9e\x80\x36\xbc\xe4\x44\xbe\xea\x7d\x0c\xa3\x8a\xda\xe4\x8f\xa8\x5d\xd5\xe8\xcc\x65\x3e\xc9\xf3\x79\xbb\x88\x84\xd0\x9d\x02\x30\xdf\x6d\x8b\xf2\x79\xaa\x66\x61\xbf\x7f\x52\x47\x40\xbc\x5d\x51\xd6\x93\xd7\x68\x82\x60\x86\x13\x72\x9a\x14\x1b\xc3\xaa\x3c\x20\x80\x33\xa4\xbb\x22\xe3\x2e\xfa\x1e\x24\xb8\x8a\x2b\x73\xbd\x6f\x8e\x91\x96\x10\x4b\x32\x7c\x98\x72\xbb\xa8\xc6\x7d\x55\x16\x66\xa6\xc6\x52\x47\xd4\x81\x86\xc6\xc9\xd0\xea\x96\x3a\xd3\xc5\x54\xc9\x23\x8d\xe6\x61\xeb\x57\x38\xe9\xf8\x57\xd5\xa6\xef\x6a\xec\x56\x3a\x27\x65\xae\x93\xd7\x1d\xad\xdc\x3c\xbb\xe1\x9f\x64\xf2\xf6\xc9\xda\x10\x25\x26\xce\x19\xcb\xb5\x78\xd3\x79\x98\x38\x69\x7a\xf2\xd2\xf3\x33\xf8\x45\x90\x42\xbe\x5c\xef\x89\xbb\x30\x31\xb9\x64\xd7\xaa\x0f\x14\x9d\x74\x06\x9d\x06\x61\xc3\x29\x8a\x23\xe5\x28\xdc\xe8\xa0\xca\x7a\xa3\x09\x96\xac\xcb\x8e\x63\xf1\x1e\xad\xcc\x8f\xc1\xe2\xd1\x7d\x1e\xbe\x97\x18\xb0\x79\xf9\xc8\x72\xc3\xa7\xe6\xb9\x9a\xd1\x41\x98\x23\x43\xf9\xa2\xb1\xee\x48\x43\x88\x06\x4c\x2e\x0a\x79\x57\xda\x86\x44\xcc\x81\x16\xc8\xed\x78\x6f\xbb\xb9\xde\x71\x3b\x89\xe5\x25\xbe\x5e\x38\xde\x1a\x8f\xb4\x56\x3e\xe8\xda\x31\x16\xe1\x3d\xfc\x1a\x02\x5b\x0d\x51\x60\x89\x2d\x35\x29\x7c\xe1\xdc\xbf\xca\x84\xd1\xcb\x7d\xa8\x48\x00\x61\x55\xc5\xa3\x97\xf0\xac\x24\x00\xad\x31\x2f\x5b\x6a\x30\xf7\x66\x36\x1c\x8e\x8d\x99\x6f\xc8\x47\xf3\x8d\xf5\xc7\xe9\x10\x58\x86\x3a\xd8\x34\x49\x7f\xf9\xfc\x7d\xde\x78\xc2\x14\xb8\x19\x55\xb8\xb4\x0b\x11\x65\x45\xcc\x7f\xa8\x70\x79\xcf\x31\xe7\x33\xc0\xab\xb2\xc4\x90\x72\xe9\x52\xf4\x92\xc5\xaa\x13\x5a\x50\x25\x14\x6d\x0c\x3c\xeb\xd1\xa3\x91\x60\x0a\x1b\x1d\x82\x83\x3c\xd8\xf8\x12\x21\x9d\xe0\xeb\x02\xa5\x9c\x63\x6d\xf1\xf7\xde\x7c\x27\x5e\xb3\x94\x37\xa9\xc0\xc5\xcb\x20\xd1\xfb\x18\x50\x0e\x32\x9a\x3d\x9e\xd5\x94\x11\x44\x51\x98\x22\x3c\x1a\xe1\x41\x16\x5e\xe2\xf1\x0d\x0a\xd0\x10\xa7\x59\x32\x83\xe7\x2a\xc8\xe9\xab\x71\x34\xc0\xa5\x62\x8e\x96\xa4\x50\x2d\xed\x03\xa0\x24\xc3\x73\x43\x89\xf9\x35\x17\x64\x10\xee\x69\x67\x40\x5b\x9c\x1c\x45\x6a\x21\x8f\x5a\xc2\x53\x3a\x8f\xd0\x73\xaa\x15\x53\x3d\x2f\x3a\x17\xdd\xbf\x76\x8c\xaf\x7d\x20\xca\x07\x83\x8a\xd6\x4a\x91\x00\x3f\x07\x67\x55\x46\x88\x33\xd9\xd7\xca\x3c\x38\x17\x0f\x29\xef\x5b\x3c\x4a\x2a\xbc\xf5\x46\x73\xad\xd5\x2c\x27\xe6\xa7\x4c\xe3\xa3\x45\xc3\x0f\xd8\xa4\x2d\x89\xc0\x49\x61\x94\xe1\x64\xa4\x58\x0b\x23\xef\xaa\xe0\xfc\x95\x75\x9d\x53\x2d\xdd\x6e\x59\x7c\xc4\x00\x5d\xe0\xf1\x14\x27\x44\xfc\x29\xb1\x08\x5e\x33\xdc\x98\x6f\xb0\x89\xf2\x37\xb8\xc7\xa3\x32\x93\xe9\x54\x41\xbb\x5a\xfb\x44\x7b\xb5\x07\x5d\xaa\xb8\x84\x2d\xbf\x7e\x4e\xad\xaa\x19\x0f\x02\x68\xdf\xfd\x9e\xb5\x2e\xdc\xe1\x70\x91\x7e\x5e\x64\x2b\x11\x0e\x8b\x7a\x4e\x31\x99\xef\x52\xa7\xf0\xf9\x8f\x8d\x4e\x7a\x22\x2c\x79\xef\x60\xbb\x77\xff\xf4\x44\x44\x68\x1e\x94\x82\xb4\xc0\xe8\xea\x2f\x41\x53\x7b\x93\x60\x50\x8a\xae\x26\xc1\xe0\x2e\xb4\x25\xaa\xdf\x89\xbe\xbe\x60\xb7\x0a\x49\xa1\xaf\xde\x27\x40\x8b\xcc\x03\x25\x32\xda\x08\xad\x3b\x1f\xb1\xe5\x1e\x7f\x85\x26\xa9\xc0\x87\x81\x60\x03\x4e\x0c\xec\x87\xf4\x62\xe0\x79\x5b\x20\xc0\xef\x41\x90\x5d\xd0\x20\xbf\x4f\xf8\x7b\x36\xcc\x2f\x65\xdc\xdf\xdb\xb3\xea\x7a\xeb\x7b\x0d\xef\xcb\x90\xa9\xf0\xe0\xc4\xcb\xf7\x1e\xef\x97\x43\x9e\x37\xee\xaf\xc0\x50\x8d\xff\xeb\x0b\xfa\x2b\xbe\x43\xf0\x5f\x57\x00\x5d\xfb\x8a\x82\x47\x8d\x95\x53\xa6\x10\x80\x12\x0d\x56\x79\x9f\x13\x9e\x46\xab\xad\xb8\xc0\xf8\xc2\xc8\x76\xda\xe5\x4c\xb4\x58\x59\x6e\xa4\x25\x1e\x17\x33\xd3\x62\xd5\xff\x3c\x3b\xad\xb2\x08\xdc\x17\xa7\xec\x43\x7b\x6e\x53\x2d\x8a\xcb\xdf\xc0\x96\xd8\x2a\x3f\x09\xa6\x42\x38\x9c\x04\xd3\xf9\x63\x2f\x38\x5c\xc4\x6d\x10\x3e\xab\x4c\x3a\xe6\x8b\x1a\x2c\xa3\x95\x2d\xd4\xf2\xdb\x2c\xdf\x64\xb8\xe1\x30\x5a\xa6\x7f\x3e\xd3\x65\xfa\xe7\x35\x60\xe6\x80\x9b\x12\x70\x25\x44\x2b\xa8\xb1\xec\xb0\x89\xe6\x5f\xca\x58\x46\x73\xc0\x2d\x03\x70\xd3\x0b\xb8\xe9\x04\xec\x86\x9c\x25\xe1\x74\x0c\x57\x2f\x15\x3a\x2c\x3f\xfd\x04\x7e\x13\x5f\xe9\x73\x93\x3c\x6f\x92\x47\x40\xc1\x05\x45\x4c\xc5\x67\x3a\x15\x95\xcf\xe8\x27\xd2\xfa\x0f\x3f\x20\xc0\xe6\x33\xfa\x11\xd5\x6b\x1b\xeb\xca\x0c\x2d\xbf\x44\x9f\x73\xc2\x5d\x28\x73\x4f\x6d\xc1\x27\xc1\x14\x6c\x66\xb7\xb3\x4a\x85\x23\x0c\x9d\xee\xa0\x1f\x51\xa5\x85\x56\xd1\xe7\x65\xd6\xd3\xd6\xc8\xe9\xed\x64\xc5\x67\xb0\x15\x17\xc3\x21\x4f\xfe\x6d\x53\x23\xfb\x40\x50\x42\x5b\x48\x41\xa7\x63\x39\x93\x40\x6c\x3d\x59\xdc\x6d\x1c\x7c\x11\x8e\x31\xaa\xa8\xfd\x64\xe1\x02\x7c\xb1\x46\x9c\xc3\xa2\x36\x33\x7f\x9f\x19\x67\x55\xa1\xde\xc1\x4e\x5e\xe3\xc9\x8b\xdb\x59\x0a\x56\x3b\x17\xa3\xff\xae\x4d\x2d\xd9\x0e\x41\xed\x7a\xd4\xad\xa4\xbc\xb9\xa5\xa8\x35\xe7\xe6\x20\xea\x09\x43\x79\xf1\x46\x18\xca\x17\xf3\x7d\xab\x44\x82\x2f\x71\x92\xe2\x03\xa5\xa0\x7c\xe5\x8a\x6b\xf6\x4c\x7e\xf6\x52\x77\x2e\x50\xd7\x16\xc0\xff\x4c\xfe\x43\xd8\x0f\x59\xa1\xac\x83\xb9\x9c\x46\x6f\xf8\x94\x2f\x6c\x66\x9b\xff\x79\xf9\x0c\x6d\xa1\xcf\xe5\x62\x75\x3a\x58\xca\xfe\x79\x14\x27\xf8\x9b\x71\x15\x05\xe4\x7e\x34\x04\x3f\x67\x39\xdd\x21\x79\x73\x38\x2a\xe2\x19\x4a\x3b\x14\xc6\xb3\xad\x2d\xb4\xda\x28\xe0\x49\x2a\x85\xa9\xb5\x17\x62\xc4\x4e\x91\x20\x11\x49\x30\x53\xfc\x2e\x8e\xa7\x72\x49\x54\x4d\x1c\xaa\xca\x8c\x6a\x22\x87\x71\xe3\x19\x4c\xbb\x68\x69\xfb\x55\x6f\x67\xf7\xf5\x9b\xbd\xfd\xff\x7e\xfb\xee\xe0\xfd\xe1\x87\xff\x73\x74\x7c\xf2\xf1\x97\x5f\x7f\xfb\xd7\xff\x0d\xfa\x83\x21\x1e\x9d\x5f\x84\x9f\xbf\x8c\x27\x51\x3c\xfd\xdf\x24\xcd\x66\x97\x57\xd7\x37\xff\xae\x37\x9a\xad\xf6\x7a\x67\x63\xf3\xc5\xca\xda\x16\x8b\x70\x2b\x8e\x76\x62\xd1\xce\x8d\xaa\x1c\x62\x8f\x57\x8a\xb4\xdc\xd0\x2c\x4c\x5d\xa2\x90\xd1\x8e\xcb\x4d\x85\xcc\x74\xe8\xd9\x6f\x98\x63\x57\x4a\x84\x24\x65\x79\x48\x6a\x52\x1d\x58\xd0\x2a\x6a\x2c\x9f\x81\xf7\x8a\x14\x98\x9a\x36\x71\x71\xa0\xcd\x32\x40\x97\xcf\xf8\x06\xaf\x8a\x61\x0e\xa8\x54\x20\x8a\xb4\xc8\x3d\x5f\x89\x30\x03\xe8\x7f\xa5\x2d\xaa\xbe\x35\x51\x7e\xf0\x1e\xc4\x86\x78\x65\x45\xfb\x20\xc8\x56\xfc\x60\x14\x69\xc5\x96\x74\x86\x45\xb8\x95\x99\x7c\xcc\x43\xbe\xb2\x47\xbc\x74\xe6\xf9\x21\x47\xff\xf6\xe3\xd1\xff\xf1\xe8\x2f\x8e\xfe\x1f\x4f\x5e\xaf\x36\x3a\xe8\xd5\x6e\x69\x07\xad\x46\xe7\xd5\xae\xea\xa3\xd5\xe8\xe8\x4f\xf0\x75\x71\xa7\x2d\x8a\xcc\x9f\xeb\xb8\x55\x12\x87\x7b\x74\xde\x6a\x74\xbc\xde\x5b\x8d\xce\xdf\x40\x23\x50\xfe\xb0\x0e\x83\x71\x97\xb3\xba\xdb\xdf\x1f\x2c\xa3\xe2\x21\xfe\x10\x87\x51\xe6\x73\x32\x6e\x74\x3c\x4e\xc6\xce\xc3\xb4\xc4\xd4\xef\x65\x2c\x9a\x2c\xeb\x6a\xac\x00\xbd\xc3\x09\xca\x24\xe2\x3b\x39\xab\x01\x6d\xce\xbb\x36\xbe\xeb\x63\x14\x5d\x55\xc2\x65\x8d\x2f\xbe\xb9\x7c\xd6\xa0\xd2\x7c\xbe\xc6\xbc\x96\x90\x6f\xf9\x8b\x87\xf6\x34\xd6\x1b\x2e\xe7\x68\xdc\x00\xd9\x47\x60\xa8\xbb\x19\x13\x11\x48\x2e\x96\x26\x59\x2c\x46\x10\x36\x3f\x85\xfb\xa4\x1c\x63\x74\x7e\x2c\x1f\x0a\x83\x91\xe5\xbb\x12\x7b\x98\xb2\x4f\xbd\xbb\xf3\x3e\xf5\xee\x3b\xd8\xa7\xca\xe0\x70\xdf\xfb\x94\x73\x39\xbd\xdb\x7d\xdc\xa6\xc4\xdf\xbd\x6d\x53\xe9\x55\x30\xdd\x8d\x86\x61\x10\x55\xe6\xdd\xb1\x5c\x47\xf2\xef\x7f\xcb\x7a\xf7\x30\x5b\x56\x99\x65\xf2\xfd\x6f\x59\xef\x76\x8d\x4d\xeb\x71\xc7\xb2\x76\x2c\x65\xc5\xcc\xb5\x79\x7d\xd3\xdd\x4b\xcc\x8b\x82\x2d\x01\xa4\xf5\x91\x47\xc3\x87\x2f\xec\xee\x84\x2e\xee\x7a\x9d\xfc\x3f\x5c\xac\xd0\x8f\xa4\xfb\xec\x2b\xfd\x26\x97\x7f\x91\xba\x00\x08\xcb\xaf\x2d\x58\xbf\x93\xb6\x80\xe5\xa8\xfd\x96\x4a\x83\x2a\x52\x5e\xa5\x17\x41\xc3\x78\x75\x31\x09\x06\x0f\xa8\x5a\xa8\x22\xde\x2c\xfc\x82\xd6\xfe\x0e\xea\x06\x2b\x5f\xec\x02\xaa\x08\xcd\x88\x45\xf9\x72\xb0\xb3\x0e\x35\xc1\xe4\xe6\x60\x67\xdd\x25\xe3\x81\x89\xf3\x17\x7c\x43\x73\x62\x53\x3b\x58\xd1\x57\x70\xfe\x0d\xa2\x8c\xa7\xf4\x8e\x93\x09\xb5\xd1\xde\xfd\xe5\xc3\x27\xd8\x74\x4f\xe2\xb7\x58\x0a\x83\xe8\xea\xea\xaa\x16\x4f\x71\x94\xa6\xe3\x5a\x9c\x9c\xaf\x0d\xe3\x41\xba\x06\x29\xb9\xe3\x35\xa3\xce\x45\x36\x19\x3b\x14\x21\xbb\x97\xd3\xb7\x3b\xaf\x25\xda\xe2\xb9\x64\x30\x84\x62\x1f\x10\x63\x8f\xb3\xbc\x5f\x58\x02\x74\xd8\xa3\xc8\xc0\xa4\xe4\x21\x8c\xb8\xdb\x8b\x12\xee\x59\xba\xba\xb4\x51\xa5\xd1\xdc\xd4\x3c\x5d\x2c\xf8\x1e\x23\x35\x35\x2c\x86\x99\x20\xe5\x60\x67\xbd\x08\xdb\x30\x63\xb6\xc8\x66\x90\x6a\xe5\x43\x16\xa3\x29\xb5\x3a\x55\xbd\x73\x3c\x3b\x9c\xe5\x17\x63\xec\x0e\x6c\x78\xba\xa8\xd1\xdc\x04\x13\x52\xed\x2b\xed\x1c\x60\x6e\x7c\x91\xf8\x68\x6d\xdf\x2e\xec\x76\xe3\x21\xda\x87\xf6\xc3\xc1\x4a\xa3\xf7\x60\x66\xfd\x65\x38\xb2\xbc\x6f\x28\xcd\x17\xa4\x68\x2a\xae\xf8\x87\x9c\xab\x4d\x23\x9f\xdf\xa2\x60\xaa\xfa\x34\xd6\xeb\x75\x13\xf0\x9c\xde\x41\x85\x7e\x3f\xe5\xe4\xdd\x1d\x48\xe8\x4f\x68\x84\x50\x05\x24\xc2\x0e\x20\x03\x2b\x59\xb4\x8b\x58\xe9\xf3\xba\x34\x16\x80\x0b\x50\x4e\xe5\x34\x18\x67\x68\x1b\xfe\x33\xbf\x58\x0c\xd4\x45\xc9\xfb\x3e\xc8\x0b\x93\xcd\xe3\xcb\x70\x54\xa3\x6e\x11\xb8\xc2\x3b\x53\x05\xfc\x72\xf2\xd6\x40\x71\x2d\xbf\xa3\x5e\x73\x2e\x81\x57\x9f\x62\x87\x78\x4b\x56\x3a\xe3\x1e\x76\x6d\xe1\xa5\x46\xc8\x83\x99\x28\xcb\xd5\xe1\x84\xe5\x73\x0b\x83\xd0\x02\x74\x88\xdf\xc2\xd8\xb8\x52\xa2\xcd\x73\x46\x96\xc0\x84\x4f\xb0\x78\xe3\x3d\x2e\xf3\x3d\x86\xf6\x88\x3d\x39\xca\x29\x4c\x9c\x16\x95\x2f\x1c\x58\xbe\x61\x1b\x13\x01\xaf\x7f\x64\xc6\x2c\x06\xae\xdc\xa0\xe5\x27\x8e\x8f\xf3\x28\x40\xc4\x38\xf0\x1c\xf0\x5e\x30\xeb\x2e\x4b\xb4\x6c\xf1\xb5\x32\x52\x83\x31\x48\x27\x10\x06\x85\x13\x9b\x62\x14\x6c\xd1\xab\xde\xbc\xf0\xa7\xb3\x4b\x10\x9a\x10\x03\x67\x7f\xd6\x0e\x4a\x0d\x7a\x50\x52\x06\x3a\x37\xed\x8f\x81\xbd\x40\xd6\x3b\x0a\x2e\x8c\x1d\x43\x65\xbf\x53\xc8\x8a\xc5\x8c\x71\xb6\x61\x8c\xb2\x52\x4b\xd1\xd1\x70\xfa\x73\x44\xbb\x10\x01\xe6\x78\xbd\xb2\x36\xd7\xa5\x78\xb0\xea\x77\xbc\x10\xef\x9d\x93\xef\xde\xa3\xf7\xad\xc3\xaf\x4c\xe9\x4d\x79\x6e\xae\x54\xd2\xb4\x1b\xca\x7b\x9d\xbb\xcb\x0f\x48\xe3\xea\x62\xd3\xa6\xfb\xb5\x8f\xb3\xcf\x57\xad\x8a\x3c\x62\xc3\x5d\xc0\xe4\x8a\x0d\x42\x85\x2c\x65\x7d\xdf\x9e\x63\xbb\xb0\xb0\x61\xd7\x25\x16\x70\x5c\xc9\xdf\xef\x6e\x5f\xe6\x1c\xdf\x29\x34\xf7\xd9\xbd\xca\x0f\x9f\xdd\xf5\x8d\x2a\x3f\x92\x76\x37\x3a\xe4\x4c\xdf\xf9\x4b\x9f\xe9\x07\xe1\xf4\x02\x27\xab\x0f\x6c\x22\x00\xa7\x77\xb5\xa9\x3f\xe7\x10\x6f\x67\xee\xbc\x97\xd3\x7c\x0f\x3a\xf6\x81\x70\x9c\x54\x1c\xda\xd5\x97\x7e\x13\x02\xf1\xde\xc8\x84\xa1\xd5\x20\x67\xb8\x20\x83\x4a\xf4\x27\x67\xc4\xac\xe2\x6b\x78\x99\xb1\xa8\x0a\xb4\xc8\x1c\xe9\x34\xc8\xe9\x86\xce\x4d\x86\xaf\x33\x72\x8a\x0c\xd8\x33\x9a\xd2\x3e\x31\xdf\x2c\x9e\x6a\x23\x18\xe2\x41\x38\x09\xc6\xe3\x1b\x96\x06\x74\x58\xfa\xe6\x46\x1d\x95\x5b\xd6\x0a\x1b\xb8\x13\x81\x86\xde\xec\xfc\xc9\x38\x16\xc1\xef\x41\xd3\x73\xc8\x29\x51\x6e\x75\xd4\xce\xcf\x77\xb1\xa3\xd5\xf4\x38\x6a\xa9\x65\x6a\x72\x76\x65\x02\x89\x3d\x7c\xbd\x60\x26\x08\xc7\xf0\x2a\xe4\xa3\xde\x37\xcc\x39\x9d\xc6\xcd\x43\x18\x4d\x67\xd9\x5d\xe6\x94\x93\x87\x4e\x74\x0b\xd0\xd9\x7d\x11\xc7\xc0\x60\x14\x0e\xfa\x58\x38\xa9\x04\x8c\x96\x3b\x84\x8d\x9c\x9c\x2d\x24\xdb\xa0\x15\x5e\x3a\xa9\xa7\xa7\x51\x0f\xd7\x08\x48\x40\x5d\x15\xe8\xad\x5b\x37\xef\xdf\x69\x65\x77\x8d\xdd\x56\xd9\x20\xba\xeb\x8d\xaa\xa1\x3c\xdf\x78\x34\xb5\xfb\xbb\xeb\xbe\x7d\xbb\xa3\x15\xc9\x3c\x4f\x13\x6e\x1f\x52\xc0\x01\x58\x68\x5c\x9d\x89\xa8\x48\x89\x2d\xd5\x51\xf5\x7e\x12\xd2\x83\xcb\x6b\x21\xc7\x2b\xad\x24\x2e\xa9\x8a\x22\xb2\x3a\x38\x2f\xe3\x41\x82\xb3\x7b\x52\x2a\x11\xf9\x77\xcf\x1d\x38\x08\x7a\xc9\xd8\x84\xcb\x13\x99\x3a\xfa\x96\xd5\x18\xaa\xce\xc1\x9e\x00\xc1\x4e\x9d\x91\xd0\x17\x51\x1f\x05\xf1\x68\x7a\xb8\xe7\x78\xbb\xdd\x67\x7c\x59\x38\x30\x15\x84\x97\xa5\x87\x2a\x25\xba\xac\x39\x4e\x6e\x43\xfc\x1c\xc5\x14\xed\xe8\x2b\x25\x2e\x26\xeb\x7a\x5e\x64\x4c\xa3\x12\xd7\x17\x98\xb0\xdc\x51\x32\xb7\xc7\xe3\xf8\x0a\x05\x49\x3f\xcc\x92\x20\xb9\x41\x4c\xbd\xf4\x05\xdf\x38\xe2\x0e\x7e\x51\x35\x12\x3f\x3b\x1b\xce\x19\x28\x53\xdd\x52\x6e\xb4\x0a\x9c\x21\x09\x4a\x39\x6e\x90\x10\xff\x0d\x74\x1b\x71\x82\xc2\x28\xc2\x09\x44\x9f\x8d\x67\x19\x08\x10\x66\x14\x3e\x88\x99\x48\x75\x8c\x94\x0c\xd9\x03\x6d\xc5\x0a\x48\xc7\x35\x7e\x6a\x8d\xd0\x51\x63\x1e\x12\x88\x15\xad\x64\x9c\xa7\x8f\x0c\x95\x82\xa1\x52\xd0\x6a\xec\xb7\xc3\x23\x98\x4f\x7a\x0d\x38\x0d\x86\x68\x10\x47\x69\x16\x44\x66\xf3\xce\x24\x52\xfa\x1c\xfb\x15\x6b\x02\xef\xd3\xf0\x0c\xfd\xbe\x85\xea\xd7\xeb\x03\xfa\x3f\x97\x3b\x8c\x55\xb8\xd5\xa1\xff\xcb\xd7\x8c\xc5\x86\x4e\x2c\x34\x9e\x5d\x14\xf9\x27\xc4\x21\x83\x1d\xe8\x21\xa2\x90\x09\x26\x7e\x2f\x91\xc8\x72\xf2\x95\xb9\x98\xb1\x63\x20\xa1\xd3\x2e\x3e\xee\xd1\x93\xea\xfa\x62\xb9\x60\x16\x8b\x40\x06\xc3\xfc\xdd\xc4\x1f\x3b\xd8\xee\xb1\xe8\x63\x80\x57\x08\x4b\x2c\x37\x12\xca\x9c\x53\x5e\x26\x10\x99\x55\xfa\xfe\x83\x91\xa9\x24\xc1\x5b\x29\x0c\x3e\xf6\x50\xd1\xc3\x60\xa8\xff\xd3\xa3\x87\x15\x88\xa9\xf3\x88\x88\x84\x87\x4a\x1a\x2a\x8c\x20\xe6\xaf\x51\x18\x45\xcc\x5f\xf5\x81\x22\x89\xdd\x9d\xdb\xf5\xa8\x7a\x1a\xc6\xdb\xb1\x1f\x13\xe9\x62\xcf\x1d\x1c\x2d\x37\xe0\x58\x2e\xc7\x54\xc7\xca\x00\x2a\x25\x14\x2e\x69\xf0\x4b\x26\x81\xca\xb2\x37\xe4\xd8\x24\x18\xb8\x2f\x89\xc4\xc1\xdf\x63\x04\xb7\xf9\x97\x56\x98\x5f\x77\xda\xab\x8e\xd7\xe3\xb0\xbf\x4a\x50\x19\x82\x6d\x6b\x6a\x7c\xc5\xd1\x60\x15\x6c\x1a\x1d\xef\xa9\x9b\xa5\xf1\x61\x32\x5c\x2f\x36\xbe\x4b\x2f\x82\xe6\xba\x09\x92\xbc\x6c\x9a\xe0\xd2\x8b\x60\xbd\xd1\xb4\x5f\xb6\x36\x1d\x25\x5b\xc6\xab\x24\x9c\xe2\xc9\xb0\xd1\xa9\x3b\x6d\xff\xb4\x57\xd3\xfe\x97\xe1\xc8\x6c\x07\x5f\x4e\xbf\x0c\x47\x79\xf7\x0e\x7a\xd7\xe3\x21\x5e\x1d\x8c\xfa\xce\xd7\x59\xe2\x79\xbd\x7a\x3e\x0e\x86\x93\x20\x72\x7d\x8e\xdd\xc0\xf0\xc0\x7c\x3d\x0d\x86\xab\x41\x94\x86\xd7\x2f\x9a\xe6\x20\x90\x4f\x61\x1a\x37\xea\x8d\xa6\x39\xe2\xec\xd3\x8b\x8d\x17\x1b\xe6\x0c\x91\x4f\xff\xc6\x49\xcc\x5c\xaf\x1d\x5f\x23\xcf\x37\xaa\x23\x5b\xbd\xc0\xd7\xc6\x87\x00\x9b\xc4\x45\xe3\x6e\x0c\xad\xf7\xc9\xc0\x9c\xdc\x24\xe8\xf7\xc3\xcc\xf9\x72\x75\x8c\xcf\x83\xc1\xcd\x43\xdf\x01\x89\xd5\x03\x4f\xe6\xa2\x81\x97\x72\xad\x88\x47\xb6\x44\xe0\x99\xac\x0c\xc3\x2c\x94\xad\x03\xf1\xbb\xd9\x16\xbf\x09\xd5\xf3\xdf\x84\xd8\xc5\x6f\xfa\x4b\x92\xb6\xb4\x2f\x85\x5f\x8c\x90\x29\x06\x94\x7e\xad\x3b\x2c\x8a\x0e\xa7\x56\xe5\x29\x4b\xf4\x27\x41\x9b\xf2\x6d\xac\xd5\x20\x94\x48\x9b\x55\x09\x50\xbc\x11\x74\xa7\xbe\xa1\xe4\x26\xde\xa8\x54\x26\x5e\x46\xfa\x2b\x85\xa6\xe0\x99\x90\x12\xfc\x90\x14\x44\x47\x65\xc0\x06\x8a\xd1\x8b\xf2\x9b\x93\xc9\xbc\x8a\x48\x4d\x01\xa9\xf2\xda\xf9\x15\x93\xfe\x50\x6c\xac\x4b\xdd\xf5\x7a\x35\x5f\x9b\x5c\xd5\xe9\xaa\xbb\xde\xaa\x6a\x84\xd7\x5d\x6f\x57\xe5\xc4\x77\xd7\xd7\xab\xfa\xe8\x75\xd7\x3b\xe6\x8d\xb0\x49\xca\xdd\xf5\x17\x55\x46\xad\xdd\x0e\xe0\x23\x28\xa5\xdb\x69\x54\x55\x5a\xe9\x76\x5a\x55\x17\xb5\x74\x3b\xcd\xaa\x4a\x21\xdd\x4e\xbb\xaa\xd2\x4f\xb7\x03\x78\x69\x34\xd3\xed\x74\xaa\x26\xd5\x74\x3b\x1b\x55\x93\x6e\xba\x9d\xcd\xaa\x45\x24\xdd\xce\x8b\xaa\x83\x9c\xba\x1b\x80\x3f\x5b\x12\xdd\x0d\xc0\x9e\x91\x46\x77\xa3\x55\xb5\x88\xa3\xbb\x01\x88\x13\x32\xea\x6e\x00\xce\x72\x9d\x75\x37\xd6\xd5\x0b\xf4\xaa\x5c\xb2\xdd\x0d\x7e\xb5\x4e\x16\x73\x77\x63\xb3\xca\x97\x6a\x77\xe3\x45\x55\x2e\xe1\xee\x66\xbd\x2a\x17\x77\x77\x13\xd0\x91\x14\xdc\xdd\x84\xc6\x05\xa3\xe9\x6e\xb6\x88\xd0\xf1\xe2\xf1\xf2\xe0\xcf\xbf\x3c\xe8\x5d\xe0\xc1\x17\xd2\x29\x58\x29\xd4\x0d\x88\xa6\x39\x4b\x67\x53\x32\x30\x98\xc5\xa7\x56\xfa\x0d\x72\x3c\x0d\x69\x8e\x9e\x6d\xa1\x25\x0e\x79\xc9\x61\x11\x22\x9c\x34\xee\xf1\xba\x22\xd7\x1c\x5f\xb4\x73\x84\x47\x38\xc1\x70\xd0\x4b\xc2\x73\x38\x93\x85\x51\x98\x49\x30\xe9\x6c\x8a\x13\x50\x5d\x6f\x19\xe9\x39\x14\x28\xdb\xb3\xf3\x09\x8e\x32\xa3\x00\xca\x62\x74\x11\x44\xc3\x31\xd6\xc6\x4d\x85\xdd\x77\x42\xd6\x6c\x6a\xa0\xaa\xed\x0e\xa8\xe8\xbe\x69\x2c\x79\x6a\x02\x15\x46\xd9\xa6\xa2\xa1\x1f\xa9\xf5\x85\x62\x42\x9f\x1d\xfb\x98\x2f\x6b\x50\x25\xfc\x47\x02\x15\x5e\xa8\xd8\x68\x87\x08\x27\x62\x31\x4d\xff\x05\x90\x2e\x43\x7c\xe5\x43\xd1\xdb\xbc\x82\xf0\x3e\x47\x01\x7d\xfd\xaa\x97\xe7\x04\x07\x58\x82\xce\x98\x57\x7f\x46\xd6\x9c\xb0\x1d\x81\x45\xe7\x06\x6e\x55\x5d\xb6\x5a\xf1\x62\xd5\xe8\xb8\xd1\xf2\xb7\x34\x5f\x8d\xfd\x28\x6b\x35\xe7\x6d\x62\xbe\x1a\xaf\xc7\x71\xb0\x48\x95\x4e\x1b\xde\xcb\xf2\x0b\x92\x52\x8d\x52\x70\x15\xa9\xaf\x6e\x32\x7c\x08\xc9\x81\xac\xd7\xae\xbc\xcb\x1a\xfd\xed\xd1\x45\x27\xdb\x2a\xb3\x22\x64\xe9\xf9\x54\x08\x12\xda\x2b\x81\x1b\xda\x72\xe3\xec\xd0\x2c\xec\x5e\xb3\xec\xab\x37\x99\xcb\xf8\x79\x2e\x77\x41\x17\x2a\xf3\xe4\xd3\x96\xf5\x4f\xc3\xb3\x85\x92\x67\x4b\x73\xee\xf0\xdf\x98\xaa\x6a\xa5\xe3\xa8\x5e\x54\x30\x56\x99\xda\xa2\x8a\x98\x1b\xa1\xab\x23\xda\x7c\x3b\xb3\x9e\x91\xd1\x24\xaf\x09\x3c\x14\x11\xa9\x4f\x65\xe6\x76\xbb\xc1\x74\x3a\xbe\x61\x0d\x07\xc9\xf9\x8c\xb0\xf0\x34\xcf\x5f\x91\xf1\xeb\xda\x34\x89\xb3\x98\xe0\xa8\x72\xee\x3c\xc3\x09\x73\xf7\x71\x2b\x58\x3a\xf5\x47\x59\xe7\xcf\x91\x75\x20\x60\xf4\x9f\x10\x97\xc8\x99\x53\xa9\x84\x89\x04\x6c\xb1\xf4\x1e\x0f\x65\x52\xb7\x4e\xaa\x9c\x30\x66\xa1\x94\xa4\xaa\x4b\xe3\xe6\xcf\x25\xe9\xf9\xf8\x4a\xa7\xed\xe6\x22\x27\x84\x4d\x6c\xd1\xe1\xab\x05\xfd\x94\xfe\x48\xc3\x88\x05\x63\x25\x2c\xa3\x7e\xdd\xa8\xb3\xbf\x65\xf4\x55\x4f\xe3\xcb\x96\x57\x65\xd9\x69\xa1\x7e\xb0\xb3\x6e\x58\x53\xb8\x0c\x40\x4c\xaf\x49\xb4\xc5\x46\xd5\x61\x00\xc2\xd3\xde\xe4\xde\x8e\x49\x4d\xb0\x3b\x57\xf1\xa9\xcd\x49\xeb\xd7\x9d\x8d\xf6\x7a\xb3\x55\x6f\x54\x51\xfd\x1a\x8f\x06\xc3\xa0\xbf\xf9\xc2\x91\x57\xb1\x7e\xfd\x62\xb3\x1f\x0c\x07\x23\x5c\x85\x81\x69\x35\xd7\xdb\x1b\x1d\xbd\xdc\x99\xf7\x46\xcc\x48\xa3\xa7\xf6\xe2\x40\x64\xd2\x73\xed\x5d\x57\xc1\x14\x61\x70\xaf\x2e\xde\x43\x1a\x1d\xff\x8e\xe1\xbf\xbe\xe6\xb3\x41\x91\xf8\x44\xe0\xf1\xf4\x82\x28\xf4\x44\xe0\x3d\xf8\xa4\x94\x3e\x38\xe5\x0f\x67\x2e\x97\x10\xe5\x33\x21\x38\xbb\x00\xf9\xab\x54\x2a\x0a\x4c\xea\x29\x8e\xbe\x22\xf5\x25\xec\x75\xed\x65\xc3\x47\x1c\x7d\x2d\x09\xb0\xd9\x5e\x76\x00\x84\x50\xc6\x9a\x4b\xba\x0d\xee\x6e\xc6\x21\x7b\xda\x0d\x85\xfb\xba\x5f\x1b\xd2\x3a\x52\xc6\x14\xad\xa0\xba\x29\x3e\x68\xa5\x1b\x46\xe9\x46\x6e\xe9\xa6\x51\xba\x99\x5b\xba\x65\x94\x6e\xe5\x96\x6e\x1b\xa5\xdb\xb9\xa5\xd7\x8d\xd2\xeb\xb9\xa5\x3b\x46\xe9\x4e\x6e\xe9\x0d\xa3\xf4\x46\x6e\xe9\x4d\xa3\xf4\x66\x6e\xe9\x17\x46\xe9\x17\xf9\xb3\x53\x37\x66\xa7\x60\x32\x1b\x46\xf1\xfc\xd9\x6c\x34\x8d\xe2\xf9\xd3\xd9\x68\x19\xc5\xf3\xe7\xb3\xd1\x36\x8a\xe7\x4f\x68\x63\xdd\x28\xbe\x6e\x71\x83\xb5\x35\xc2\x90\xbf\x84\xd1\x39\xa9\x1a\x06\xe3\xbe\x4b\x6c\x0e\xc8\x36\x70\xea\x1c\xa8\x3e\x7c\x72\x0e\xca\x00\x3e\x39\x07\x60\x08\x9f\x5a\x2e\x74\x7a\xf2\x0e\x5a\xff\x46\x90\x78\xfd\xba\x12\x54\x51\xbf\x8a\x06\x55\x34\xac\x2a\x0b\xb4\x8a\xd0\x46\x95\x6c\xa1\xf5\x33\x93\x37\x0c\x69\xbd\x61\x15\x89\xaa\x72\x84\xaa\x08\x35\x9a\x55\x74\x72\xda\xb0\xea\x0d\x68\x3d\xda\x12\xad\x2a\x17\x2d\xa9\xb7\x41\xea\x35\xad\x7a\x7d\x5a\x4f\x20\x19\x28\xf5\x5a\x55\x84\x9a\xd0\x5e\xcb\xaa\x97\xd7\xbf\xb6\xe8\x5f\x7b\xae\xfe\xad\x8b\xfe\xad\xcf\xd5\xbf\x8e\xe8\x5f\x67\xae\xfe\x6d\x88\xfe\x6d\xcc\xd5\xbf\x4d\xd1\xbf\xcd\xb9\xfa\xf7\x42\xf4\xef\xc5\x5c\xfd\x6b\xd4\xab\xac\x7f\x0d\x9b\x60\xf2\x3a\xd8\x68\x54\x59\x07\x1b\x36\xc5\xe4\xf5\x90\x60\x49\x7b\xd8\xb0\x49\x26\x97\x44\x5b\x55\x4e\xa2\x36\xcd\xe4\xf6\xb1\x2d\xfa\x68\x13\x4d\x6e\x1f\xd7\x45\x1f\x81\x6a\xec\x4e\xbe\x79\xe3\xe9\x64\x15\xa1\x75\xda\x49\x9b\x6e\x86\xb4\xa2\xb3\x93\x84\xde\x5e\xd0\x8a\x36\xe1\x0c\x68\x45\x77\x27\x1b\x55\x44\x3a\x7a\x72\xda\xb0\x29\xa7\x4f\x2b\x3a\x3b\x49\x38\x46\xb3\x0e\x15\x6d\xd2\xc9\xeb\xe3\xba\xe8\x63\xd3\xcd\x6b\x7c\x7d\x24\x34\x47\xfb\xd8\x74\x33\x1b\x6f\x1f\xd7\x79\x1f\x9b\x6e\x6e\xe3\xeb\x63\x5b\xf4\xb1\xe9\x66\x37\xbe\x3e\xbe\x90\x7d\x74\xf3\x1b\x6f\x1f\xdb\xa2\x8f\x6e\x86\xe3\xeb\x23\x61\x8c\xac\x8f\x6e\x8e\xe3\xeb\xe3\xa6\xec\xa3\x9b\xe5\x78\x69\xb5\x55\xe5\x7d\x74\xf3\x1c\x5f\x1f\x9b\x82\x56\x9b\x6e\xa6\xe3\xeb\xe3\x86\xe8\x63\xcb\xcd\x74\x7c\x7d\x24\xcb\x9f\xf6\xb1\xd5\x70\x2f\xc8\xbd\x3d\x3f\xb1\xb6\x01\xd7\x96\x9b\xeb\xec\xed\xb9\x3b\x49\x86\x95\xac\xad\x93\xd3\x96\x9b\xeb\xec\xed\xe5\x2c\xc8\x0e\x54\x74\x73\x9d\xbd\x3d\x4f\x27\xdb\x55\xd4\x6c\x41\x45\x9b\x74\xf2\xfa\xd8\x90\x7d\x74\x33\x1d\x5f\x1f\xdb\xb2\x8f\x6e\xa6\xe3\xeb\x23\x4c\x24\xed\xa3\x9b\xe9\x78\xfb\x58\x17\x7d\x74\x33\x1d\x6f\x1f\x5b\x55\xd6\xc7\xb6\x9b\xe9\xf8\xfa\x58\x17\x7d\x6c\xbb\x99\x8e\xaf\x8f\x2d\xd1\xc7\xb6\x9b\xe9\xf8\xfa\x48\x58\x39\xed\x63\xdb\xcd\x74\x7c\x7d\x7c\x21\xe6\xb1\xed\x66\x3a\xbe\x3e\x92\xe5\xc1\xfa\xe8\x66\x3a\x5e\x5a\x5d\xe7\xb4\xda\x76\x33\x1d\x5f\x1f\x9b\xb2\x8f\x1b\xee\x05\xb9\xbf\xef\x17\x54\x3b\xb4\x93\x6e\xae\xb3\xbf\xef\xee\x24\xd0\x1c\xf0\x80\xb6\x9b\xeb\xec\xef\xe7\x88\x01\xeb\x20\x02\xba\xb9\xce\xfe\xbe\xbb\x93\x84\x77\x34\x61\x58\xd7\xdd\xa2\x8e\xaf\x8f\x64\x3e\x68\x1f\xd7\xdd\x4c\xc7\xd7\xc7\x96\xe8\xe3\xba\x9b\xe9\x78\xfb\x58\x17\x7d\x74\x33\x1d\x5f\x1f\x1b\xb2\x8f\x6e\xa6\xe3\xeb\xe3\xa6\x98\xc7\x75\x37\xd3\xf1\xf5\x11\x68\x8e\xf6\xd1\xcd\x74\x7c\x7d\x04\x91\x9c\xf6\xd1\xcd\x74\xbc\x7d\x6c\x55\x79\x1f\xdd\x4c\xc7\xd7\xc7\xb6\xe8\x63\xc7\xcd\x74\xbc\x7d\x6c\xf0\x3e\x76\xdc\x4c\xc7\xd7\xc7\xa6\xe8\x63\xc7\xcd\x74\x7c\x7d\x7c\x21\xe6\xb1\xd3\xb2\x17\x24\x5c\xa3\x64\x38\x99\xe0\x61\x18\x64\xcc\xa9\x0c\xdc\x15\xf4\x72\xe4\x88\x8b\xb6\x50\x05\xfe\xbb\x82\x02\x53\xc3\x4a\xcb\x34\x58\x99\x06\x29\xd3\x77\x97\x69\xb2\x32\x4d\x52\x66\xe0\x2e\xd3\x62\x65\x5a\xa4\xcc\xd0\xd2\xe6\x1a\xaa\xca\xd7\x0e\x4b\xdd\x39\x03\xda\x42\xa6\x74\x91\x4d\x37\xc8\x02\xd7\xc1\x3c\xc8\x02\x11\xca\x27\xc8\x02\xbf\x72\x2c\x7a\x15\x66\xe9\x49\x9c\x05\x63\x01\x33\xda\x09\xb2\x80\x7a\x90\xfc\x88\x36\x1d\xd0\xa1\xce\x3b\x3c\xca\x38\x74\xe1\x71\x02\xe5\xad\xce\x78\x53\x5e\x09\x34\x4f\x25\xc8\x9f\x7f\xfe\x19\xad\xc3\xc5\x5b\xfd\x7a\xb3\x2e\xef\xdb\x64\x89\x7f\xa0\x56\xd3\x22\x0e\xbd\x2f\x7b\x68\x0b\x81\xda\x7d\x34\x8e\xe3\xa4\xa2\x74\x72\x4d\xd3\xbd\xfb\x3a\x07\x65\xdf\xa1\x2d\xe5\xc9\x5c\x38\x02\xf5\x4a\xa5\x22\x71\x5b\x41\x9d\x36\xcd\x97\xf6\x02\x82\x89\xb6\x97\xa9\xc2\xc6\xad\x9f\xe5\x55\x19\xce\x52\x39\xab\xbe\x2d\xaf\x9d\xb5\xc1\x31\xd5\xac\x09\xae\x48\x37\x6b\x71\x89\x79\x3a\xdb\x2e\xd3\xd9\x77\xce\xce\xbe\x5b\xb4\xb3\xef\x9c\x9d\x7d\x57\xb6\xb3\x76\x6f\x55\x27\xaa\x8a\xe8\x3e\x0f\x36\x05\x39\xf5\xdc\xfe\x83\x60\xf0\x4e\xdd\x18\xc0\x47\xd1\xe5\x49\x95\x9b\x57\xbe\xc0\x1b\x52\xd3\x79\x3b\xc8\x77\x8f\x19\xc6\x7b\xbd\xdf\xe6\xba\xf7\xf0\x5c\x71\xa1\xbc\xeb\x7f\x81\x09\x5c\x61\xec\x9d\xba\xef\x2e\xf6\xd8\x2d\x59\xa5\xb2\xa7\x5d\x4b\xec\xcd\x7d\x1f\x41\x69\x61\x4f\xbb\x8b\xd8\xf3\x5e\x42\x14\xdf\x38\x1c\xb1\xdc\xc0\x30\x87\x2c\x02\xcf\x10\xc6\x54\x2f\x5a\x22\x59\x39\xb8\x21\xe4\xb2\x7a\x50\xb0\x82\x53\xa6\xb8\xa1\x83\x47\x79\xfd\x6f\x6d\xbc\xf0\xf9\x93\x45\x0b\x3e\xef\x4a\x1e\x41\x83\x7c\x75\x7b\x38\xd0\x5f\x02\x49\x43\xf5\x75\x5d\x45\x69\x15\xe9\x57\x68\xc0\x27\xd1\x16\x0a\xd0\x0a\xaa\x54\xfa\xe8\x07\xba\x39\x56\xfe\x1f\xf2\x73\xb8\x4c\xd8\xc0\x35\x5a\x41\x99\xd2\x9e\x08\x58\x1c\x91\x69\x4a\xe9\x4a\xa5\x71\xca\x5b\x4d\xb4\x8a\xd2\x65\xa8\xd6\x37\x8c\xde\x04\x56\xc6\xf9\xbf\x1c\x56\xb0\x1d\x57\x06\xe8\x07\xf4\xff\x3c\x0c\x56\xc6\x21\xa8\x10\xab\x3e\xfa\x1d\x0d\xd0\xef\x04\xb1\xfb\x47\xc6\x10\x00\x0b\x91\x21\x88\x54\xfa\xe8\xeb\x3d\x0f\x8e\x7a\x5b\x7d\xec\x4b\x93\x5e\x98\x78\xbf\x4c\x90\x35\xee\x27\x66\xb8\x28\xc2\x6a\xb0\xc1\x78\x9c\xc5\x3c\xa5\x17\x0d\x6b\xc6\xd6\xa5\x30\x72\x39\xd8\x59\x77\xf8\x7e\xe5\x97\xb7\x1d\xbe\x64\x7c\x31\xed\x32\x5f\xcf\xc8\x7f\xb0\xb3\xee\x34\x19\xf0\x4e\x42\x41\xae\xfa\xfb\x9a\x82\x85\x42\x3b\x14\x4f\x9c\xea\xe5\x77\x1f\x13\x47\x9d\xca\xc4\x44\xec\x4d\x82\x01\x99\x0c\x2d\x33\xbc\x3d\x1f\xac\x98\x3d\x27\x32\x9b\x3d\x9d\x97\xdc\x0c\xec\x2c\xb2\xb5\xc7\x02\xaa\xf1\x97\x76\x31\xfb\xfb\xc7\x64\xa3\x8b\xed\x47\x16\x67\x08\xbd\xc6\x78\xd8\x0f\x06\x5f\x58\x5c\xcd\x49\x3c\x84\x25\x45\x68\x46\xcc\x37\xbc\xec\xbd\x7e\x45\x44\x20\x87\x78\x00\x66\x4e\xf0\x55\xb3\x96\x03\x0b\x17\xda\xca\x01\x01\xc0\x8c\x79\xc4\xaa\xef\xbd\x7e\x55\xdb\x8d\x68\xac\x72\x30\xa0\x7a\xfd\xca\x61\xf0\x33\xf5\x98\xcb\x30\x33\xc3\x1c\x93\x19\xbf\x68\xca\x42\x50\x71\x81\x84\x3e\xba\xee\x99\x95\x50\x1e\xb4\x90\x1a\xca\x43\x2f\xcf\x63\x94\xbf\xc5\x37\x69\x96\xe0\x60\xb2\x1d\x0d\x59\xef\x1c\xd6\x91\x31\x33\x8b\x15\xe0\xaa\xac\x01\x97\x90\x7d\x84\x27\x18\x82\x8c\x83\x31\x26\x9d\x27\x16\x2b\x13\xfc\xe7\x23\x7c\x9d\xd1\xd7\x6e\xf1\x1d\x5f\xbe\x62\x31\x53\xa1\xf5\x5a\x3a\x0e\x07\xb8\xc2\x51\x10\x37\xf5\x02\x17\x97\xfd\xa4\x36\x6b\x3b\xf8\xef\x32\x6b\x77\x18\x5d\x30\x1c\xbe\x08\xd3\xb9\xc7\xf6\x9b\xd1\xcd\x89\xec\x50\x1f\x0f\xe2\x09\xf3\xba\x27\x04\x11\xc6\xb3\xb4\x1c\xc9\x88\x2e\x96\x12\xc7\x73\x7a\x53\x29\xec\x82\xe1\x1b\x61\x1f\xd8\xe0\xbc\x77\x29\x83\xb5\x5c\xbe\xd4\x8d\xc6\xd5\x70\xcc\xb4\x79\xf9\x19\x32\xbb\x5e\x3a\x8f\x34\xa2\x34\xda\x42\xe1\x25\x9b\xc2\xba\x67\x25\xc6\x97\x18\xed\xff\x02\xe7\xcf\x74\xd6\x4f\xf1\xff\xce\x70\x94\xe5\x9c\x9e\x01\x5f\xe1\xc0\x50\x68\x00\x6d\xe2\x63\x4c\x88\x3d\x09\xe4\x8f\x51\x39\xa6\x03\x0d\x05\x2b\x02\x48\x15\xe9\x5d\x59\x5b\x43\x6c\x46\xe4\x3b\x67\xb6\xdc\xfc\xa8\x31\xd4\xf4\x5c\x5a\x08\x42\x24\x18\xd1\x28\x9c\xa3\x2d\x7a\x61\x58\x70\x71\xe2\xf5\xab\x3c\x83\x6b\xbe\xe9\xcc\x13\xa7\xae\xd3\x7c\x14\x3e\xbe\x77\xe1\x03\xfd\xd7\x34\xc1\x29\x4e\x2e\x31\x15\x43\xe2\x19\x11\xe5\x15\xf1\x03\xd4\x18\x41\x16\xf6\xc7\x8c\x03\xa3\x9d\x04\xbd\x4a\xc2\x20\x42\x6f\xa8\x7b\x26\x1a\x85\x63\x8c\xa3\x41\x6d\x00\x20\x78\xc8\x67\x88\x80\x6d\xd0\xcf\xc9\x11\x14\xf9\xef\x20\x42\x7b\xc9\xac\x7f\x83\x3e\x5f\x90\xff\xd4\xae\x70\xff\xbf\xce\x27\x41\x38\xae\x0d\xe2\x89\x5b\xde\x39\x39\xe2\xcd\xe5\x88\x3d\x6a\xa1\xd2\xd2\xcf\x13\x99\xef\x25\x1a\x90\x83\x02\x4d\x99\xf4\xf4\xc9\x13\x32\xe8\x40\x7a\x22\x1d\x12\x28\x89\xa8\x52\x68\x19\x66\x9d\xfe\xfa\x03\xad\xad\xc5\x97\x38\x19\x8d\xe3\x2b\x52\x07\x36\xbe\x06\x4f\x07\x4a\xea\x35\x3a\xcb\x3f\x90\xb2\x2f\xc5\xe7\xa6\xfa\x79\xd3\xfc\xda\x62\x7b\x18\x6b\x0c\xf0\x04\x54\x08\x58\xd1\xee\xda\x1a\xe2\xcd\xa2\x7e\x83\x14\x01\x94\xa1\xe9\xfa\x4b\x51\xa5\x29\xab\x88\x32\x4f\x00\x01\x5a\x88\x96\x6a\xe9\xa5\x58\xb1\x27\x80\x0a\x2b\x77\x0b\xff\x12\x82\x54\x4b\xac\xac\xf4\x5b\xca\x77\xf8\x87\x97\xa1\x45\x56\x56\xfa\xcd\x97\x4f\xfd\x05\x56\x56\xfa\x0d\xf6\x9d\xfc\x0b\x1d\xe7\x8d\xc2\xc3\xca\x16\xf4\xfc\xa7\x9f\x58\x3e\x48\xf5\x75\x93\xaa\x00\xb5\xb7\x0c\x21\xbb\x25\x51\xad\x7e\x5d\x6f\x30\xad\x9f\x2c\xca\xb8\x1e\x29\x44\x5e\xde\x9a\xd4\xc1\x96\x47\x65\x40\xff\xab\xd3\x08\x7b\x49\x6f\x90\x38\x29\xc9\x97\xcb\x8c\x60\x94\x29\x58\x5b\x43\x64\x97\x80\x9b\x18\x14\x2a\x0b\x89\x2e\x1e\x6b\xa5\x2d\xa5\x08\xe0\xa5\x28\x8e\xc6\x37\x74\x39\xee\xfc\x7a\x78\xb4\x83\x3e\xa3\x9f\xd0\x26\xc0\xe4\x0d\x36\x5c\x58\xd0\xbb\x38\xbd\xb3\xec\x1b\xef\x2f\x5f\x4b\xda\x59\x40\xac\xab\x9a\xe7\xf5\x9f\x28\x73\xce\x2b\x72\x5a\xc5\x0d\x19\xc6\x6e\x95\xf1\x44\xd1\x2c\x1f\x30\x0b\xf5\x3c\x89\x07\xf9\xa5\x1e\x10\x1a\xdc\x8d\xe4\xcb\x40\x68\x01\x39\x08\x15\xcb\x42\x5c\x3a\x20\x84\x6d\xd3\x3c\x65\x45\x4f\x4c\xd1\x88\x7d\x56\x70\xd5\x55\xcf\xf3\x08\x45\xc8\x23\x18\xa1\xc5\x84\x23\x34\xa7\x80\x84\x74\x79\xce\x3e\x74\x49\xba\x57\xcf\x5e\x62\x69\xbc\x34\x24\x2b\x51\x5c\x11\xb0\xbc\x22\x96\x52\x78\x0e\x49\xab\xf5\x28\x69\x7d\xef\x92\x96\x47\xbe\xf2\xa8\x77\x4e\x8e\xf2\xe5\x9c\x79\xd5\x3b\x0e\x96\x6e\xf2\xf2\x47\x26\xfe\xf7\x63\xe2\xb9\xa7\xd9\x07\x60\xd9\xfb\xd1\x20\xc1\x10\xb9\x81\x01\x37\x40\x32\x39\x44\x4e\xee\x2a\xa2\xc6\x34\x9e\x2f\x70\x5b\xfe\x15\xd5\xff\x52\x9b\x43\xd9\x5d\xa1\xf8\xbc\x4d\xca\xcc\xb1\x0b\xb4\x1f\x77\x81\xbf\xc4\x2e\xb0\x3b\xc6\x83\x2c\x89\xa3\x70\x80\x7a\xf1\x10\xf7\xe3\xb8\x58\xe1\xbf\xdb\xcb\x53\xf8\xd3\xaf\x73\xed\x08\xbb\x3d\x5d\xe1\x4f\x9e\xef\x6b\x07\x50\x59\xbb\xce\x40\xf4\x7a\x79\x5a\x4c\x82\x8f\xb6\x90\x1e\x0a\xbf\x21\x5e\x08\x3f\x9e\x7a\xa9\x57\xac\x37\x83\x32\x73\xac\xe3\xbf\x76\x72\xe4\xff\x9c\x75\x7c\x38\xcb\xa6\xb3\xac\xfc\xa5\xdd\x61\xee\xa5\xdd\xe1\xfc\x97\x76\xa6\x54\x77\x68\x5c\xe2\x1d\xfe\xb9\xd7\x41\x0f\x2e\xd5\xd9\xba\x79\xf1\xe6\x7e\x25\xbb\x9c\x86\xbe\x17\xe9\xee\xef\x74\xc2\x3e\x34\xae\x35\x7d\x42\xd4\x61\x89\x4b\x8b\xc3\x39\x2f\x2d\x1e\xb3\xd8\xfd\x35\x98\xef\xf6\xfb\xe3\x7d\xf4\x5b\xed\x45\xb3\xc5\x0d\xc4\x51\x9a\x91\xe5\x7d\x7e\x63\x71\xdf\x69\x30\xac\x6d\x47\x69\xf8\x1b\x29\x2d\x72\xc1\x4d\x83\xa1\xca\xfe\x86\x41\x16\x28\x17\xa1\xbe\x0b\xd0\x54\xbf\x01\x25\xb5\x8e\xa5\xc1\xaf\x66\x00\xfc\x52\x2f\xda\x37\xd3\x8a\xf4\x7d\x09\x45\x80\x28\x66\x51\x26\x7a\x66\x04\xb3\x02\x5b\xbc\x0f\xf4\x9b\x05\x8c\xbe\x58\xd5\x31\xfb\x87\xf1\xdd\x6a\x8d\xc6\xb4\x19\x07\x29\x8d\x9c\x85\xa6\x71\x1a\xea\x1e\xf8\xa4\x51\xf2\x9d\xd4\xff\x10\xf3\xce\x8a\x16\x56\x0c\x8c\x56\x51\xc3\x68\xe4\x43\x30\x94\xcf\x30\x50\x22\xdb\x88\xfe\x9a\xb2\x12\xb5\x2d\x19\x52\x4b\x6f\x44\x86\xd4\x52\x4b\xbb\x82\x6b\xe9\x96\xd9\x2b\x06\x20\x6e\x87\xc8\x2d\x70\x67\x91\x83\x38\x4c\x8a\x78\x83\x33\x25\xe1\xbc\x36\x55\x54\x81\x2f\x46\x33\x7f\xe6\x94\x3e\x57\x74\x34\x57\xc9\xf1\x97\xf5\x5d\x5e\x04\x29\x28\xb0\x7d\xc5\xf2\x90\x30\xc0\x78\x7a\xfb\xf4\xc9\xad\x93\x6f\xf2\xe5\x72\xfd\xa2\xd9\x9a\x8b\x77\xde\x2d\x31\xd9\x23\xef\xfc\x56\xbc\x73\xff\xf8\x10\x41\x48\xdc\x72\xac\x73\x9f\x05\xd0\xbd\x2b\xeb\xfc\xd3\xd9\xa1\x5c\x12\x05\xfc\xd0\xc1\xaa\x68\x3a\x00\x77\x04\xba\x5a\x12\x44\xc3\x78\x52\xb1\x38\xe0\xf2\x72\xcd\x90\x94\xf2\xe1\xb0\xd4\x61\xa7\x16\x97\x6b\xb6\xcf\xaa\x04\xdc\x23\xa3\x32\x19\x15\x27\xce\xb9\x18\xd5\x5f\x3b\xf3\xc2\x7f\x14\xa3\x5a\xdb\xdf\xed\xa1\x17\x1b\x2f\x36\x56\x1b\x88\xd1\x06\x3a\xc0\xd9\x45\x3c\x44\x4d\x1f\xb7\x82\xd0\xde\x8b\x72\xab\xed\xe1\x90\xfa\x0f\xea\x0b\xa2\x04\x17\xe0\xab\x97\xd4\xa6\x7f\x7c\xd1\x6a\x0d\xfc\x5f\x9c\xc4\x90\x3b\x2c\xbb\xc0\x28\xc1\xa9\xc2\x17\xb5\x8e\x90\x72\xac\xc7\xe4\xd9\xc2\x7b\x21\x5e\xc0\x16\xe2\xbf\x19\x0e\xfa\x6a\xf4\x36\x0f\xa0\x29\x3c\xf7\xc2\x8e\x23\x8c\x26\x71\x82\xa9\xf0\xb8\xba\x0a\x7d\xf3\x8d\x22\x5f\xef\xab\xab\x25\x17\x38\xcc\xe7\x5c\x0b\xfc\x6e\x51\xce\x1f\x17\xf8\x37\x3b\xc5\xa1\x28\x8e\xa7\xe5\xc4\x90\xf7\x9c\x1c\xbd\x2b\x5b\x10\xbb\x7f\x4d\xc8\x22\x79\x34\x27\x9a\x9a\x87\xe8\x36\xee\x16\x6e\xf6\x91\xe8\xbe\x15\xd1\xfd\x5f\x85\xf9\xe5\x93\x9c\xc2\x03\xff\x44\xe1\xb7\xf4\xc1\x59\x3d\xdf\x5a\x02\x70\xa5\x92\x2f\x02\x2f\xa3\xaf\x5f\xcd\x57\x0b\x6d\x31\xee\x1e\x17\xc7\x15\x58\x5b\x43\x1f\x09\x7c\xbd\x5e\x68\x45\x0a\x00\xcd\x82\x28\x73\x75\x11\x8e\x31\xaa\x3c\xab\x48\x5f\x6b\x19\x83\x1b\x3c\x0e\xad\x98\xdb\xc2\x84\xd3\x52\x64\x86\x62\x4b\x42\xba\x8a\xd2\x74\xec\x86\x78\xbc\x65\x76\x2f\x85\x82\xe6\xe2\x25\x7f\x6d\xc7\x2d\x47\x8e\x2e\x9a\x24\xeb\x61\xf9\x8a\xcc\x84\x04\xad\xfd\xf9\x79\x3e\x1e\x36\x49\x78\xb9\x98\xd8\x56\xcc\x6b\xf1\xe5\x78\x6f\xbb\x21\x63\x3d\x93\x27\xe5\xa3\x9d\x08\xdc\xe5\x20\xfa\x21\x48\x53\xb2\x90\x57\x09\x6a\x43\xf4\x16\xdf\xa0\x1d\x9c\x84\x97\x34\x27\xe4\x6b\x3e\x28\xcd\xfc\x98\xd3\x1f\x5e\xbd\xdd\x79\xdd\x94\xad\x89\xe7\x92\x89\xc7\x7b\x71\x34\x0a\xcf\x67\x2c\x13\x65\x0c\x59\x21\xd3\xbc\xfc\x92\x49\x3c\xc5\x49\x76\x83\xfe\xa0\xc7\x62\xf0\x26\x05\xe6\x7b\x72\x41\x73\x1c\xa7\xe4\x21\x8c\x58\xba\x80\x2c\x16\xbe\x34\x35\xb4\x83\x47\xc1\x6c\x9c\x75\x51\x1b\x55\x1a\xcd\x4d\x48\xa4\xbc\xec\x83\xef\x49\x68\x8e\x13\x9e\xc8\x5c\x82\x23\xe3\x5f\x84\x66\x98\xb1\xe4\x99\x29\x80\x92\x87\x7a\xe5\x43\x16\xa3\x29\x4e\x46\x71\x32\x51\x80\x6b\x90\x95\xf4\x8f\x83\xd1\x79\xd7\x37\xca\x88\x5e\x7c\x1d\x43\xcc\x99\x46\x73\x73\xad\xd5\x34\x42\x70\xd3\xae\x50\xd4\x8d\x4f\x12\x21\xad\xf1\xdb\xe5\xbc\x84\xa4\x79\x09\xe4\xc9\xac\x0c\x25\x69\xf1\xf5\x56\x9c\x45\xf4\x10\xf8\xdc\x2d\xe9\xaa\x9a\x31\x94\x8c\xdf\xc0\x45\x37\xdc\xdf\x6c\x14\x27\x70\x8a\x91\x8d\xde\x43\x62\xd0\x2f\xc3\x91\x95\x34\x9e\x52\x3b\x3f\x3d\x6a\x66\x58\xf3\x54\xfc\x43\x4e\xd6\x26\x4d\x3f\x79\x67\x30\x55\x7d\x1a\xeb\xf5\xba\x09\x38\x27\x7b\xfd\x60\x74\xee\x36\xbc\x20\x13\xb1\x25\x7e\x72\xc2\x23\xc5\x7d\xc1\x30\xec\xf5\x0e\xd7\x15\xd4\x83\xae\x2c\x0b\x5a\x24\xdf\xec\x94\xc1\x06\x6a\xe1\x0f\xb5\x92\x95\xd3\x60\x9c\xa1\x6d\xf8\xcf\xfc\x89\x68\xb9\x1b\x8d\xe2\xd7\x7e\x17\xb2\xa3\x89\xd4\x87\xa3\x1a\x8b\x4a\x52\xe1\x9d\xa9\x02\x7e\xde\x49\x65\xc5\xd5\x79\x35\x6a\xce\x95\xdb\x45\x9f\x7a\xa7\x01\x61\x98\x79\x92\xc2\x32\x2f\x7b\xf0\xdd\x67\xb4\x4a\xc8\x87\xf2\xa0\xaa\x98\x1d\xb7\x59\xa2\x3f\x41\x39\xc8\xa6\x74\xb0\x69\xba\x79\x4b\x9f\xe3\x0a\xf5\x04\x72\xf2\x7e\x34\xc4\xd7\xae\x1a\xa7\xf5\x6b\xa6\x00\x72\x44\xeb\x2c\x08\xd1\x25\x50\x11\xc2\xb2\x78\xe3\xcd\x5f\x2f\xb1\xe1\x95\xe4\x1b\x6f\x25\xbe\xe5\x6d\x91\x59\xa9\xb1\x27\x97\x11\x86\xdc\x5a\x68\x51\xf9\xa2\xc0\xc8\x42\xff\xc8\x04\x75\xa3\x83\x3c\x2e\xd2\x4f\x1c\x1f\xa7\x71\x81\xe8\x24\xcb\x73\xcc\x93\x65\x03\x05\xca\x34\xbe\xb2\xd7\xe6\x9c\x21\x96\xd1\x5b\xa6\x06\xb6\xbf\x17\x67\x63\x00\xf8\xda\x10\x3b\x47\xd7\x2e\x2e\xb2\x18\xc9\x57\xac\xe3\x1e\x44\xf6\xc5\x18\xbb\x41\x87\x6a\x34\x3b\x06\xd6\x81\x85\x66\xcb\xd1\xa0\xb6\x1c\xca\xf4\x79\x8d\x39\x10\xf0\x73\xad\x09\x18\x3d\x31\xd2\xea\x47\xd7\x58\x97\x19\x6f\x54\x14\x0a\xca\xd5\x59\x3e\xfa\xea\x3b\x77\xc0\x2a\xa5\x89\xdf\x0e\x8f\xf4\xee\x80\xeb\x94\xc3\xe3\xda\x1a\xb7\xcf\xd4\x06\xe6\x33\xb7\x81\x51\x66\xf3\x25\xfa\x9c\x33\x7a\xe4\x4f\xd6\x38\xfd\x0c\xe6\x30\x56\x47\x4e\x3f\x9b\x66\x31\xfc\xef\xd6\x7e\x6d\x06\x9c\x22\x7f\x0a\x73\x60\xba\x69\x68\xd4\x35\x25\x06\x93\x38\xad\x9f\xad\xac\xe4\x9b\x14\x29\xc0\x95\xa3\x2f\xe7\x1b\x8e\x20\x66\x6c\x2f\x93\xf5\xf2\x0c\x28\xd5\x63\xc4\x9d\x36\xf4\x32\xc1\x66\x72\x37\xf2\x39\x37\xf1\xfb\x12\x2d\xc3\xd4\x95\x6e\xbf\x38\x7a\x8d\x43\x34\xb8\x87\x20\x36\x54\x44\x10\x92\x21\x15\x0a\x7d\x62\xc2\x7c\xd5\xaa\xc8\x23\x9b\xde\x05\x4c\xae\x6c\x2a\x83\xec\x88\xa3\xa4\x4f\x80\xa9\x22\x53\x50\x65\xc3\xae\x8b\xc5\xa4\x50\x81\xf0\x74\x9b\x67\x8b\x46\xa1\xb9\x03\xf5\x98\x29\x74\x79\x4e\xd8\xdb\xb3\xea\xc6\x5f\xdb\x87\x7e\x8e\xb4\xee\xc5\xc9\xd1\x1f\x56\x77\xe4\x4d\xaf\xed\xcb\x7a\xfd\x77\xd0\x2e\x1d\x83\x71\x66\x8f\x1b\xef\x52\x25\x92\xfa\x32\x4f\x8f\x24\xf0\x38\xc2\xb3\x34\xe8\x8f\x31\x0b\x07\xa6\xa0\x73\x8c\xd4\x54\x8b\x14\x8a\xf9\xe6\x0d\xd2\x33\xac\x29\xdb\xc2\x11\x64\x53\x46\xcc\xd0\x96\xd9\x18\xdb\x9a\x24\x51\x1e\x62\xac\x84\x29\x0a\x10\x4d\xc0\x8c\x2e\x71\x92\x42\xd4\xb2\x8b\x20\x43\x11\x3e\x1f\xe3\x41\x86\x87\x84\x0d\x0f\x58\x4a\xd5\x8c\x29\x7c\xb2\x18\x8d\xc3\x2c\x1b\xe3\x55\x1a\xe0\xb2\xa6\x03\xc5\x49\x12\x27\x68\x18\xe3\x34\x5a\xca\x50\x30\x1a\xe1\x01\xad\x4b\x91\x5a\x4a\x51\x8a\x07\xb3\x24\xcc\x6e\xaa\xa2\x62\x7f\x96\xa1\x30\x83\x4a\xbc\x46\x98\xa5\x22\xa0\x42\x38\x0e\x33\xe6\xc4\x4d\xf3\xba\x86\x84\x3f\x4f\x70\x44\xf7\x83\xd4\xa5\x28\xa3\x03\xf2\x8e\x76\x4e\xa8\xcb\x8c\xb7\xea\xfc\x2d\x9a\xb4\x2d\xff\x90\xf2\x56\x35\x83\xf6\x1e\x30\xa4\xf5\x36\x9c\x1a\x2e\xf3\x4e\x0b\x21\x3b\xa1\x91\xdd\x0b\x7b\xcf\x69\xbf\x89\x76\xc9\x2f\x47\xe2\xb8\xb7\xa7\xf5\xb3\x2a\xaa\xbc\x3d\x6d\x9d\xb1\x60\x01\xe8\x2b\x79\x64\x57\x01\x8d\xce\xb2\x23\x89\xdc\xdb\xd3\x06\xad\x54\xd7\x2b\xb5\xf2\x2b\x35\x69\xa5\x86\x5e\xa9\x9e\x5f\xa9\x45\x2b\x35\xf5\x4a\x0d\x51\x49\xaf\xe3\xca\x8e\x64\x0d\x19\xf7\x32\xf4\x0d\x5a\x4f\x0c\x5a\xcf\x3d\x68\x36\x3e\xca\x70\xb1\x3e\xd1\x0b\x93\xd1\x88\xa7\x1d\xa4\x48\xd3\x20\xab\xf5\x3a\xf9\xe2\xea\xaf\x3d\x11\x2d\x1d\x72\xc3\x09\xb9\x59\x0a\x72\xdd\x3b\xf0\x0a\x0c\x03\x72\xab\x14\xe4\x86\x6f\x76\xaa\x0a\x0c\x03\x72\xdd\x80\x5c\x3c\x91\xbd\x20\x49\x6e\x50\xdf\x4c\xa7\x4a\xa7\xaa\x4f\xe3\x5f\xd8\x9a\x8c\x8c\x4e\x3e\x61\x3d\xe9\x4d\x9a\xe1\x09\x1a\xc5\xb3\x04\x65\xe1\xc4\x9c\xfb\x39\x83\xf2\x46\xf8\x3a\x3b\x26\xab\xcf\x1f\x3f\xd6\x11\xf1\xf6\x20\x1e\x86\xa3\x1b\xca\x09\x29\x1d\x96\xc0\x62\xd3\x8f\x45\xef\x94\x3a\x0e\xfc\x76\x0a\x29\x2f\x21\xda\x8a\x95\x29\xce\x95\x24\xf7\x17\x94\xe2\x6c\x36\xd5\x3f\xe4\x78\x74\x14\x1f\xf6\xf7\x7f\xa1\xae\x1d\x79\x27\xfc\xfd\x5f\x3e\xd5\xd1\x16\xda\xff\xc5\x4e\x8d\xa6\x14\x69\xd0\x22\x0d\x67\x34\x63\x75\x49\xc3\x54\xa6\xb3\xfe\x25\x26\xa2\x82\xef\xe8\x5f\xa7\xc1\x8f\xa1\x6d\x1a\xfd\xf8\x2b\xa2\x4f\xbe\xe8\xc7\x6a\x71\x16\xe6\x58\x94\x97\xd7\xa1\xee\x30\xc7\xa2\xd9\xa6\x68\xb6\xa1\x35\xdb\x28\x6a\xb6\xa1\x37\xdb\x98\xaf\x59\x08\xa3\x13\xd6\xf9\x12\x24\x40\xc2\xa6\xbe\x02\x7d\x55\x5b\x50\xb5\xc9\x17\x33\x54\xad\xeb\xcb\xd4\x33\x23\x8c\xac\xf3\x58\x2b\x02\x6a\xad\xd3\x73\xbd\x19\xdb\x9f\x7e\x6c\xd0\x8f\x0d\xe7\xc7\x26\xfd\xd8\x74\x7e\x6c\xd1\x8f\x2d\xe7\xc7\x76\x5e\x9b\xeb\x79\x6d\x76\xf2\xda\xdc\x10\x6d\xe6\x68\xa4\x4a\x71\x1e\x34\x3f\xf7\x41\xe5\x38\x10\xb2\x95\x14\xaa\x1f\xd1\xbd\x24\x77\xf5\x2a\xaf\x15\xe9\xa3\x14\x67\xd6\x8b\xb8\x7b\xe7\xdf\xde\x61\x70\xa5\x97\x19\x70\x21\xbd\xf4\x31\x0d\x35\xf4\x1b\x10\x21\xaa\xfc\x46\xe6\x9e\xaf\x12\x78\x16\x7b\xef\x4b\xb3\x62\x83\x56\x6c\xb2\x8a\x1b\x46\xc5\x75\x6f\xc5\x26\xad\xd8\x66\x15\x1b\x46\xc5\x0d\x6f\xc5\x16\xad\xd8\x39\x13\xa8\x69\x15\x1b\xb2\xe2\x9d\x76\xb1\xbc\x28\xf5\x14\x11\x1e\x3b\xfe\x98\xa5\x64\x67\xc1\xe3\xe1\x71\x91\xe8\xf1\x1c\x0e\x63\x70\x02\x8e\x2b\x7e\xbc\x13\x5f\xa7\x13\x1e\x52\x72\xf4\x0a\x6f\xba\xe3\x7c\x2f\x3a\x95\xfa\x85\x1d\x8f\xbc\xb9\x95\x1f\xc3\x4b\xfa\xa5\xd3\x5e\x6b\x35\x4d\xb5\x9c\x58\x26\x82\x60\x2b\x25\x5d\xa1\xb4\xf5\xa1\x7d\x51\x44\x50\xc3\xe0\xe7\x38\xb8\xc4\x28\x1e\x0f\xbd\xac\x76\x0e\xf9\xa1\xf7\x89\x4e\x6e\xcf\x8c\x77\xa8\xb5\xd8\x0b\xc6\x83\xd9\x98\xac\xb0\x08\x5f\x79\x9b\xed\xb1\x44\x30\x3d\x9a\x08\xa6\x7e\xdd\x1e\xb6\xe0\xff\xd0\x0a\x97\xd0\xcc\x7c\x2d\x3d\x96\x17\xa6\x47\xf3\xc2\xd4\xaf\x59\x8d\x16\xc4\x94\xef\x71\x01\xb5\xbe\x8c\x7e\x42\x95\xde\x27\xe5\xf9\x9f\xa8\x81\xba\xa8\xbe\x6c\x43\x6c\x32\x88\x4d\x0a\x91\x01\x6c\x33\x88\x0d\x03\x62\xa3\x04\xc4\x16\x83\xd8\xb2\xba\x55\xa1\xed\x68\x10\x9b\x25\x20\xb6\x19\xc4\xb6\xb3\xd7\x2d\x03\x62\xab\x04\xc4\x75\x06\x71\xdd\xd9\xeb\xb6\x01\xb1\x5d\x02\x62\x87\x41\xec\x38\x7b\xbd\x6e\x40\x5c\x2f\x01\x71\x83\x41\xdc\x70\xf6\xba\x63\x40\xec\x14\x42\x94\x62\x3f\x05\xaa\x55\xdf\x30\xab\x9b\xde\x31\x82\xa6\xc9\xee\x73\xbe\x7a\x87\x45\x44\x4a\x9d\x5f\x03\xaf\x0e\x49\xd7\x7a\x8e\x24\x1c\x3c\x5d\x7e\x32\x1b\x64\xe8\x22\x3c\xbf\x40\x41\x34\x44\xe3\xf8\x0a\x05\xc9\xf9\x0c\xc2\xbf\x80\x9b\xf3\xff\xce\x82\xc4\x4a\xdc\x03\x0d\x04\x68\x8b\xb4\xc2\xa5\x38\x87\xf2\xe0\xbc\x4f\x8b\xd0\x5d\xc2\x79\x7c\xe2\x7d\xd6\x30\x48\x70\x3a\x1b\x67\x28\x1e\xe5\x35\x7f\x41\xb7\x80\xca\x79\x80\x7e\x44\xe7\x01\x75\x5d\x69\x6c\x2c\xa3\x15\x44\x5f\xf5\xd9\xab\x75\x78\xd5\x87\x57\x2e\x24\xc7\x14\x90\xd2\x15\x7a\x24\xfc\x11\x9d\x5f\xc3\x0c\x2f\x03\x41\xf0\x02\x42\xec\x54\x0a\xb8\x12\xc1\x90\x0e\xfd\x76\x78\x84\x20\x9c\xa4\xfa\xf1\x0d\xe5\x70\xe7\x17\xe8\x77\x74\x3e\x2e\xcb\xe4\xdc\x4a\x95\xdf\x18\x8b\x7b\x43\x59\x5c\xa5\xf2\x46\x6e\xdf\x64\x27\x7b\xa3\x88\x05\xcb\xac\x40\x47\x2f\xd0\x91\x05\x4c\x7a\xfe\x8d\x71\xc3\x37\x94\x1b\x56\x68\x33\x72\xbf\x7d\xc3\xf9\x1f\xec\xb7\x2b\x88\xb4\x66\xc3\x68\x32\x18\x4d\x0e\xa3\xa1\x23\xd0\xb0\x30\xac\xeb\x05\xea\x79\x18\xb6\x18\xf4\x16\x87\xde\xd4\x31\x6c\x1a\x18\x36\x1c\x18\xb6\x19\x8c\x36\x87\xd1\xd2\x11\x68\x59\x18\x36\xf5\x02\xcd\x3c\x0c\xd7\x19\xf4\x75\x0e\xbd\xad\x63\xd8\x36\x30\x6c\x39\x30\xec\x30\x18\x1d\x0e\x63\x5d\x47\x60\xdd\xc2\xb0\xad\x17\x68\xe7\x61\xb8\xc1\xa0\x6f\x9c\x69\x24\x22\x30\xec\x18\x18\xae\x6b\x18\x96\x4a\xfc\x91\xf2\xa4\x13\x42\xd7\x5a\x22\xed\x44\xd1\x75\x17\x85\x95\xe1\xeb\x4c\xbd\x77\x52\x35\xa9\x3c\x94\x82\x96\xc6\x81\xde\x16\xd9\xf7\x57\xd3\x71\x40\xb0\xb9\xce\x90\x17\x1c\x8b\x33\x53\x91\x2d\xbb\x20\x8a\x8b\xab\x3c\xa5\xae\x9e\xbc\x43\x2d\xb9\x9c\x77\x07\xa5\x16\x2c\x6d\x8c\x5c\xd5\xef\x46\xba\xeb\xad\xaa\xbc\x14\xe9\xae\xaf\x57\xd9\x5d\x49\xb7\x53\xbf\x3d\xab\x6e\xfc\xb5\x23\x11\x3e\xde\x57\x3d\xde\x57\x3d\xd8\x7d\x95\xb1\xc4\xe5\x7d\x8e\x79\x93\xf3\xd7\xba\xc3\xb9\xaf\xac\x70\x6f\xc5\xd1\xfc\xad\x7e\x34\x7f\xbb\xe8\xd1\xfc\xad\x7e\x34\x7f\x9b\x77\x34\x2f\x52\x30\x3f\xde\x54\x3d\xde\x54\x3d\xde\x54\x69\x5f\x1e\x6f\xaa\x1e\x6f\xaa\x1e\x6f\xaa\x64\xb3\x8f\x37\x55\xe6\xc7\xc7\x9b\x2a\xcf\xe3\xe3\x4d\xd5\xe3\x4d\xd5\xe3\x4d\x15\xfc\x3d\xde\x54\x95\x53\xe2\x3e\xde\x54\x3d\xde\x54\x3d\xde\x54\x29\x7f\x8f\x37\x55\x8f\x37\x55\x8f\x37\x55\x8f\x37\x55\xff\xc9\x37\x55\xf7\x76\x47\xb5\xd8\xed\x54\x99\x7b\xa9\x12\x37\x52\x0f\x75\x17\xf5\xd7\xce\x87\xf2\x78\x17\xf5\xf7\xbf\x8b\x52\xef\x8e\x7a\xed\x42\x47\x27\xf5\xe6\xa8\xd7\x56\xae\x8d\xe0\xe1\xe1\xef\x8c\xa8\x97\xa6\xb8\x35\x72\x07\x15\xe0\x1e\xda\x79\xd7\x4a\xe0\xc6\xa9\x7a\x14\x2b\x31\xd3\x6d\x7d\x45\x14\x66\x28\xed\xc7\xd7\x36\x9c\x63\x81\xce\xb1\x7a\x4d\xc7\xff\x5c\xd2\x64\x73\xbd\xe3\x3f\x94\xb3\x43\x77\x58\xac\xc6\x7d\x8b\x6f\x5c\x7a\x5c\xbd\xc5\x2a\xf7\x1f\x2f\x6c\x98\x0d\x0a\x19\x02\x1e\x55\x22\x44\xff\x50\xc7\xc9\xa3\x3a\x64\x95\xc8\xd6\xc6\xc7\xfe\x54\x03\x64\x47\x42\xd3\x3e\x5b\x41\xd1\x5c\x67\x7f\xd2\x8b\xca\x67\xb4\x42\xc7\x67\x85\x37\xba\x8c\xfe\x01\xbd\xf2\xc4\x52\xb8\x0a\xa6\x6e\x9c\x61\xdf\xb0\x35\x04\xca\x04\x1c\xbb\x1d\xe3\xc9\x6b\x32\xe3\xc5\xd3\xd3\x73\xaa\xf8\x59\x56\x0d\x41\x34\x9f\x59\x96\x59\x01\xe8\xce\x6a\x39\xae\x09\x01\x2d\x88\x95\x7f\x9d\x4c\x8f\x5b\x65\xa8\xb5\x2c\x9c\x9c\x9b\xeb\x1d\x8f\x42\xa4\xee\x55\x86\x38\x1b\x2d\xab\x18\x51\xd6\x93\xa1\x18\x91\x83\x16\x1a\x5f\x3e\xcb\xe1\x2c\xcc\x00\x0f\xca\x41\xbd\xfa\x17\x15\x4f\x63\x3e\xc4\x6a\x8a\xe8\x32\x8a\xa8\x4a\x2d\x72\x2c\xa2\x10\x34\xe8\x34\x61\x1c\xa3\x4a\xed\xbb\x46\xc2\x1e\xc2\x75\x12\x6d\x0e\xc1\xfa\x89\x55\x12\xaa\xfe\x5e\xef\xec\x57\x52\xb7\xc2\xd6\x14\xa9\xc2\xf0\x3a\x93\x79\x0d\x22\x33\x8f\x81\x71\x7c\xfa\x08\x71\x50\x1c\x37\x5a\x92\xd4\x43\xeb\xec\x4e\xc6\x42\x9b\x2b\x26\x96\x69\xd8\x7d\xaf\x72\x6f\xaf\x7d\x1f\x42\x6f\xaf\x3d\xb7\xc4\x6b\xef\xb1\x86\xb8\xdb\x6b\x3b\x63\x5b\xc0\x0d\x4d\x88\x87\x0b\xec\xf0\x3b\x49\x3c\xd5\x76\x79\xf6\x02\x06\xe1\x1b\x44\xc5\x1b\x92\xe6\xf4\x40\x73\x86\x9e\x9f\x4c\x3c\x29\x25\x42\xcd\xa1\xc6\x8b\xa6\x0a\xd6\x8c\x35\x47\x50\x57\xa2\x7e\x19\xab\x98\x80\xea\xea\x20\xf4\x88\x71\xa5\x84\x18\xd2\x06\x2f\x98\x7f\x87\x41\xc6\x33\x67\x03\x17\x86\x2f\x04\x2f\xb2\x8b\xff\x0c\x9b\xf9\xea\xaa\x73\x0f\x9f\x83\xdd\xa3\x82\x04\x48\xdf\xd1\x6a\x23\x43\x74\x3f\x2b\x0e\x20\xcd\xbf\xea\x18\xcd\xe7\xaf\x3c\x52\x28\xff\xa4\xd9\x6b\x3f\xd4\x31\xf3\x6e\xe9\xfa\xbe\xe5\xf9\xf2\xc1\x4e\x81\xdf\x36\x88\x33\x61\x55\x38\xc5\xc9\x25\x7e\xfa\xa4\x32\x58\x46\xcd\x7a\xa3\x89\xfa\x37\xa8\xf7\xff\xff\xff\x0d\x93\x70\x80\x0e\x70\x1a\x85\xe3\x1a\xda\x1e\x8f\x51\x12\x9e\x5f\x64\x29\x62\xe5\x87\xb5\xa7\x4f\x9f\x1c\xe1\x61\x98\x66\x49\xd8\x9f\x01\xfc\x20\x1a\x42\x50\x9e\x30\x42\x69\x3c\x4b\x06\x18\xde\xf4\xc3\x28\x48\x6e\x08\x3b\x98\xa4\x55\x16\xa5\x21\x81\xff\xc6\xb3\x0c\x4d\x80\xa7\x0f\x80\xb3\x56\x51\x90\x60\x34\xc5\xc9\x24\xcc\x32\x3c\x44\xd3\x24\xbe\x0c\x87\x78\x48\x83\x4e\x90\x75\x3a\x8a\xc7\xe3\xf8\x2a\x8c\xce\xd1\x20\x8e\x86\x21\x5d\xc3\xa4\xd2\x04\x67\x5d\xb6\xe2\x57\x91\x8e\x56\x0a\x8a\x61\x8a\xcf\x20\x1e\x62\x34\x99\xa5\x19\xd9\xa8\x83\x30\x02\xa0\x41\x3f\xbe\x24\x9f\xa6\x37\xd0\x45\x14\xc5\x59\x38\xc0\x55\x1a\x57\x68\x1c\xa6\xa0\x59\x56\xdb\x8b\x86\x06\x32\xc3\x30\x1d\x8c\x83\x70\x82\x93\x9a\x0f\x87\x30\x52\x07\x82\xe3\x30\x4d\xe2\xe1\x6c\x80\xef\x1d\x0d\xc4\xba\x36\x8c\x07\x33\x11\x07\x83\xd4\x58\x8b\x13\x16\x23\x63\x12\x64\x38\x09\x83\x71\x2a\x87\x19\xe6\x06\xaa\x29\xa8\x93\x79\x3e\xd9\xdb\x3f\x46\xc7\x87\xaf\x4f\x7e\xdd\x3e\xda\x45\xfb\xc7\xe8\xc3\xd1\xe1\x2f\xfb\x3b\xbb\x3b\xe8\xd5\xbf\xd0\xc9\xde\x2e\xea\x1d\x7e\xf8\xd7\xd1\xfe\x9b\xbd\x13\xb4\x77\xf8\x6e\x67\xf7\xe8\x18\x6d\xbf\xdf\x41\xbd\xc3\xf7\x27\x47\xfb\xaf\x3e\x9e\x1c\x1e\x1d\xa3\xe7\xdb\xc7\x68\xff\xf8\x39\x7c\xd8\x7e\xff\x2f\xb4\xfb\xdb\x87\xa3\xdd\xe3\x63\x74\x78\x84\xf6\x0f\x3e\xbc\xdb\xdf\xdd\x41\xbf\x6e\x1f\x1d\x6d\xbf\x3f\xd9\xdf\x3d\xae\xa2\xfd\xf7\xbd\x77\x1f\x77\xf6\xdf\xbf\xa9\xa2\x57\x1f\x4f\xd0\xfb\xc3\x13\xf4\x6e\xff\x60\xff\x64\x77\x07\x9d\x1c\x56\xa1\x51\xbb\x1a\x3a\x7c\x8d\x0e\x76\x8f\x7a\x7b\xdb\xef\x4f\xb6\x5f\xed\xbf\xdb\x3f\xf9\x17\xb4\xf7\x7a\xff\xe4\x3d\x69\xeb\xf5\xe1\x11\xda\x46\x1f\xb6\x8f\x4e\xf6\x7b\x1f\xdf\x6d\x1f\xa1\x0f\x1f\x8f\x3e\x1c\x1e\xef\x22\xd2\xad\x9d\xfd\xe3\xde\xbb\xed\xfd\x83\xdd\x9d\x1a\xda\x7f\x8f\xde\x1f\xa2\xdd\x5f\x76\xdf\x9f\xa0\xe3\xbd\xed\x77\xef\x9c\xbd\x24\xb8\x6b\x7d\x7c\xb5\x8b\xde\xed\x6f\xbf\x7a\xb7\x4b\x5b\x7a\xff\x2f\xb4\xb3\x7f\xb4\xdb\x3b\x21\xdd\x91\xbf\x7a\xfb\x3b\xbb\xef\x4f\xb6\xdf\x55\xd1\xf1\x87\xdd\xde\x3e\xf9\xb1\xfb\xdb\xee\xc1\x87\x77\xdb\x47\xff\xaa\x32\x98\xc7\xbb\xff\xe7\xe3\xee\xfb\x93\xfd\xed\x77\x68\x67\xfb\x60\xfb\xcd\xee\x31\xaa\x14\x0c\xc9\x87\xa3\xc3\xde\xc7\xa3\xdd\x03\x82\xf3\xe1\x6b\x74\xfc\xf1\xd5\xf1\xc9\xfe\xc9\xc7\x93\x5d\xf4\xe6\xf0\x70\x07\x06\xfa\x78\xf7\xe8\x97\xfd\xde\xee\xf1\x4b\xf4\xee\xf0\x18\x46\xeb\xe3\xf1\x6e\x15\xed\x6c\x9f\x6c\x43\xc3\x1f\x8e\x0e\x5f\xef\x9f\x1c\xbf\x24\xbf\x5f\x7d\x3c\xde\x87\x41\xdb\x7f\x7f\xb2\x7b\x74\xf4\xf1\xc3\xc9\xfe\xe1\xfb\x65\xb4\x77\xf8\xeb\xee\x2f\xbb\x47\xa8\xb7\xfd\xf1\x78\x77\x07\x46\xf7\xf0\x3d\x74\xf5\x64\x6f\xf7\xf0\xe8\x5f\x04\x28\x19\x03\x18\xfc\x2a\xfa\x75\x6f\xf7\x64\xef\xff\x65\xef\x6b\xbb\xa3\xc6\xb1\x06\x3f\x37\xbf\x42\x33\x7b\x06\x2a\xc4\x24\x96\xfc\x26\x03\xe9\x5d\x08\x61\xc2\x36\x34\x9c\x90\x7e\xe8\xe7\x70\x80\xf1\x8b\x9c\x72\x53\xa9\xca\x53\xe5\x90\x4a\x4f\x33\x67\xff\xc6\xfe\xbd\xfd\x25\x7b\x74\x25\xdb\xb2\x2d\xc9\x95\x10\xfa\x99\x9e\x21\x73\x86\xae\x2a\x5d\xdd\x7b\x75\xdf\xf4\x7e\x75\x70\xc4\x05\x0a\x92\x7a\xc4\x45\xf0\xfa\xf8\xe8\xd9\xfe\xb1\x0a\xf6\xf2\x08\x1d\xbf\x3c\x3a\x56\xda\x88\x7e\x3c\xf8\xeb\xf3\x67\x7f\x3d\xf8\x71\xff\x80\x97\xbe\xe4\x58\xde\x3c\x7b\x7d\xb0\x85\x1e\x1d\x3d\x7b\xcd\x01\x9e\x09\xb2\x6f\x1e\xfd\x27\x7a\xf9\x13\x34\x99\xeb\xe8\xa7\xd7\x07\xe2\xa3\x62\xb1\x0e\x68\x12\x3d\x7b\x8a\x1e\x3d\xf9\x8f\x67\x9c\x6d\x09\xfc\xea\xe5\xeb\xd7\xcf\xa4\x9d\x80\xc8\xf6\x0f\xa5\xb8\x77\x6e\x7d\x77\x77\xb7\xbb\xe6\xf5\x22\xa9\xa6\x37\xbb\xee\xb5\x59\xd6\x69\x91\xf8\xb8\x01\x11\x5f\x37\x3a\x9d\x0d\x1b\x76\xc9\xbc\x5a\xa1\x2a\x49\xeb\x11\x0b\xaf\xf2\xe1\xd7\x99\x36\xd9\x66\x3b\x8e\x72\x1d\x84\xb0\x83\x10\x71\x10\xf2\x1c\x84\x7c\x07\xa1\xc0\x41\x28\x74\x10\x8a\x1c\x84\xa8\x83\x50\xec\x20\xec\x3a\x08\x63\x07\x61\xe2\x20\xec\x39\x08\xfb\x0e\xc2\x81\x72\xc2\x32\x12\x75\x79\x19\xc7\xc7\xe1\x39\x0e\x1c\x08\xbc\xbc\x1e\xd0\x8a\x25\x7e\x22\x71\x60\xa0\xd1\xe2\xf1\x24\x2d\x5f\xf2\x12\x4b\x1c\x54\xe1\x33\x92\xb8\x42\xc9\x0b\x16\x38\xb1\x9a\x6b\x19\xcb\xba\x35\x2f\xae\xc0\x01\x7c\xd4\x7c\x7a\x02\x17\xc7\x8f\xd5\x76\xab\x78\x7c\x59\x37\x90\xbc\x47\x12\x07\x51\xf8\xc4\x12\x17\x95\xbc\xc8\x76\x63\xef\xdd\xd6\x03\x55\x17\xcb\x11\x5d\xd4\x7c\x44\x8a\xac\x88\xc4\x59\xf3\x1c\x76\xe5\x01\x6d\xf3\x7a\x6d\x0f\x65\x1d\xaf\xc5\x05\x75\x83\x96\xe7\x1a\x47\x2d\x0f\xa0\x85\x7b\x6d\x07\xa0\x40\x69\x60\x24\x19\x0c\x5b\xe1\x36\x48\x88\x22\x68\xc1\x6c\x8b\x88\xca\x3a\x8a\xb0\x40\x31\x41\x2b\xdc\x06\x87\xa7\x08\x5a\x30\xab\x30\x44\xa4\x64\x5d\x05\x59\x2d\x8d\xa8\xd1\x5e\x63\xa1\x48\x4a\x47\x30\xdb\xd5\xc6\x6a\xcc\x33\x04\x8b\x52\x56\xc0\x9e\x4a\xa9\xb6\x2d\x4f\x91\x67\xd8\x96\x75\x6c\x3a\x72\xa0\x08\x44\x55\xdb\x6b\xdc\xda\x5e\x6d\x53\x38\x50\xc4\x1a\x49\xd8\x8e\x3e\x70\x6b\x04\x8d\x2e\x62\x09\x58\x1b\x0f\x55\x60\x6a\xc6\x23\xf8\xac\xce\xa5\x1a\x5f\xf6\xdb\xaa\x35\xfd\xc6\xe7\x55\x9f\xa0\x1d\x94\x2d\xaa\xda\x3f\x83\xd6\xf6\x9b\x76\x92\x96\x85\x46\x4e\xd2\x64\x04\x5e\xd4\x53\xc9\x98\x83\x48\xc6\x3a\xc2\x0f\x5a\x06\xa0\x9d\x51\xeb\x88\x40\xd0\x97\x8c\x84\x3d\xa6\xbd\xae\xf0\x9b\x46\xe3\x16\x4f\x23\xbb\xc6\xa1\xa1\xbc\x83\xa7\x09\x20\x58\x11\x52\xd8\xd2\x6d\x1c\x4f\x3a\x30\xf6\x34\xfa\x68\x1a\xd0\x63\xbc\x46\xd4\x38\x1c\x51\xfe\x1b\x34\x5e\xdc\x15\x50\xa0\x81\xf3\xbb\x9a\x69\x34\xd9\x36\x0a\x61\x8c\xde\x75\x5e\xc9\xfe\x30\xe5\x1e\xa2\xd1\x07\x6a\x52\x35\xbb\x0e\x72\xd7\xc1\x23\x4a\xa2\x38\x8e\xf9\xe7\xf0\xe0\x49\x7c\xf0\xf8\x11\xe6\x9f\xe9\x53\xfc\xf8\xf1\xfe\x93\x7d\xfe\xf9\x51\x1c\x78\x4f\x9f\xf8\x07\x5d\x7d\x4f\x97\x46\x02\x81\xfb\x88\xd0\xc7\x07\x21\x10\xd8\xf7\x9f\x3c\xc1\xc4\x07\x02\x4f\x22\xd7\x3b\x78\xea\xf1\xcf\xd1\xa3\xf0\x49\x14\x1e\x00\xe1\x9a\xa1\x77\xda\xf5\x80\xa3\x67\xaf\x0e\x5e\x3c\xc1\xa1\x0b\xe9\xf7\x47\xd6\x90\x1a\xd8\x76\x15\x49\xf9\x49\xf4\xca\xd7\xdd\x2b\x12\x8b\x89\xc0\x84\x21\x09\x76\x18\xf9\x01\xf1\x5c\x90\xe0\xc1\xd3\xfd\x27\x8f\x1e\x53\x68\x60\x4c\x1f\x3f\x7a\xb2\xff\xf4\x80\x7f\xc6\xae\x47\x02\x3f\x02\xe1\xec\x7b\x4f\xc8\x01\x7e\xea\xbe\x33\x2e\x8d\x6f\xba\x28\xaf\x5d\xd8\xdd\xf8\x96\x12\xb6\xec\xd4\x8c\x1f\xc7\x17\x5c\xc0\xda\x6b\x7b\x2c\xd2\xb0\x7d\xf3\xe2\x83\x02\x5d\x6f\x1e\x7c\x18\x1e\x64\x42\xb6\x3d\x15\xa5\x1e\xda\x43\x93\x21\x00\x12\x07\x40\x15\x62\xed\xc1\x07\xe5\xc7\xab\x1d\x2a\x1d\x20\x94\xe7\x4a\x7b\x08\x87\xa7\x4b\x87\xe8\x2c\x4b\x63\x68\xec\xac\xf3\x21\x6a\xf6\x1f\xb8\x29\x1a\xb7\x1c\x21\x00\x7c\x98\xce\xcc\x00\x4b\x00\x58\x1a\x01\x60\xf8\xf9\xe1\x57\x33\x06\x18\x13\x7d\xf8\xd5\x8c\x01\xba\xe9\x0f\x2b\x33\x06\xe8\x34\x3e\xac\x96\xfa\x8c\xd6\xbb\xbb\xdc\xcb\x3e\xf2\x49\xf3\xa7\x64\x59\xf2\xd1\xb1\x66\x93\x36\x99\x39\x28\x9d\x39\x28\x9b\x39\x28\x9f\x39\x88\xcd\x34\x84\x92\xa5\x83\xd2\xa5\x83\xb2\xa5\x83\xf2\xa5\x83\xd8\xb2\x4f\x2c\xe1\xac\x24\x9c\xe1\xc3\xe1\x95\x91\x74\x09\x49\xc7\xa1\x10\xf7\x0b\x33\x5e\x98\x89\x42\xd2\x2f\xcc\x79\x61\x2e\x0a\xbd\x7e\x21\x4c\x18\x98\x28\xf4\xfb\x85\xcd\x33\xd5\x49\xf7\x5d\xea\xba\x49\xfd\x5d\x41\xed\x51\x42\xf8\xef\xf6\x1e\xc2\x5a\xdf\xae\xb8\xfb\x24\x33\xb4\xdd\xfa\xd4\xf6\xaf\xb3\xb7\xe5\xbb\x77\x5b\xbf\xe9\x2e\x31\xc0\xad\x9d\x87\x38\xdc\xfa\xfb\xad\xef\xba\xa1\x91\xd3\x40\x05\x9e\xa4\x33\x27\x9b\x39\xf9\x6c\x0b\x6d\xa3\xe9\x4c\x7f\xf7\xe6\x33\x6a\x16\xe4\xca\x87\x1e\x11\x4b\x6d\x1a\x6c\xa4\x8f\x6d\x20\xf9\x01\x36\x9f\x1a\xb1\x79\x7d\x6c\x03\x55\x0d\xb0\x85\xbe\x11\x9b\xdf\xc7\x36\xd0\xad\x82\xed\xef\xbb\xbb\x12\x23\x75\x8d\x18\x83\x3e\xc6\x81\x41\x20\x7d\x9a\x74\xae\xc4\x4a\xab\x2e\x5e\x82\x96\x8b\x6a\x36\xa9\x9c\x15\x57\xab\xee\xd2\x06\xd8\x40\xb5\xcd\x66\x7a\x95\x83\x47\x0c\x5c\x8a\xff\x81\xdd\xe6\xba\x12\x30\x77\xa0\xcb\x7d\x12\x6b\xaf\x01\x81\xbf\xa4\xba\xda\xe0\x66\x83\x9d\xc4\x86\xd5\x64\x89\xb6\x15\x6b\x5d\x5e\xdf\x5a\x83\x49\xba\x74\xb2\xa5\x93\x2f\x41\xe2\xcb\x2f\xb3\x56\xbf\x8f\xed\x4b\xad\xb5\x8b\xed\x8b\xac\x95\xf4\xb1\x7d\xb1\xb5\xe2\x3e\xc6\x1b\xb6\xd6\x25\xec\x5a\x5b\xcc\x75\x69\x30\x57\x88\xa8\x4b\x9d\xb9\x42\x20\xd6\x95\x40\x88\x16\xe6\xba\x34\x9a\x2b\x74\x00\xba\xda\xd0\x35\x0c\x4f\x68\xf4\x43\xf9\x33\xf5\x39\x06\x18\x43\xc2\xa9\xdf\xde\x08\x93\xff\xb3\x87\x26\x87\xe2\x68\x6e\xc6\x23\x73\xae\x69\xe9\xa1\x3c\xc2\x7b\x28\x8e\xdf\xe6\x1c\x4e\x27\x91\x43\x79\x4c\xf7\x50\x1c\xa4\x65\x1c\x2e\xd1\xc2\x79\x12\x0e\x0e\xcb\x42\x8f\x90\x6a\xe1\x7c\x09\x07\x07\x93\x53\x0e\x97\x69\xe1\xe0\x00\x73\x47\x2c\xfd\x61\xed\x53\xf9\xb4\xc6\x17\x1c\xcf\xca\x93\x2a\x69\x06\x43\xfc\x8b\xa6\xe3\x1f\x7f\x86\xb1\x86\x9c\x3f\x2e\xab\xd5\xf1\xa2\x82\x88\x27\x70\xce\x9f\x24\x55\x22\x4e\x6d\xdd\x45\x54\x83\x1d\xea\x3c\x67\x45\x35\x78\xb4\x11\xe0\x07\x8d\x79\x94\xe7\xc3\x57\x88\x91\x7c\x6f\x51\x1c\x66\x6a\x51\xf2\xd1\x64\xf0\x0e\xfd\xb6\x27\x1e\x16\x6e\xcf\x48\x34\x10\x7f\x41\x1e\xe9\x5b\x6b\x8b\x69\x32\x99\xb4\xa0\xdb\x88\xc7\x07\x8e\x32\xde\xe2\xa8\x7c\xee\xb7\xd8\x37\x0c\xa0\xeb\xaa\x42\x1c\xed\xe0\x59\xf9\x71\xf3\xc1\xf3\x00\x99\x1c\x38\xf7\x90\x8d\x0d\x9c\x75\x0d\x55\xdf\xe9\x68\xdf\xc3\xac\xdf\xd8\x81\xc3\x31\x9a\x67\x3b\x76\x77\x61\x26\x88\xe0\x75\x17\x71\x21\x4b\x7b\x70\xea\x4c\xcc\xbc\x86\xaf\xb9\xd8\xcd\x12\xbc\x5b\x1d\xa3\x6b\x0c\xe7\x10\xed\x21\x75\xf8\xfe\x65\xf3\xb7\x60\xa3\xe9\x9b\x7e\x46\x76\x08\x53\xb1\x43\xcd\x65\x12\x64\x9b\x83\x1d\x36\xd7\xf5\x0e\x3b\xd3\xab\xc3\x2b\xcf\xab\x84\x85\x1c\x76\xe6\x54\x87\xc6\xc9\xd4\xf8\x51\xb8\x23\xb1\x13\x2e\x94\x2b\x5f\xb0\xc8\x41\xd8\x5d\x50\xb9\x63\xde\x53\x50\x27\x4c\x65\xb3\xc5\xdc\x1e\xa0\xe0\x28\x01\x87\x6a\x57\x17\xe0\xab\xf9\x18\x84\x28\xfe\x30\x30\x12\x51\x6f\x68\x6b\x92\x4d\x28\xed\x9c\x8b\x82\x8f\x9f\xc5\xee\x3f\x52\x4f\xc4\x15\x78\xb2\x76\xd0\xa5\x83\x7e\xd5\x3d\xf3\x31\x99\xac\xe1\x66\xe7\x25\xfc\xfb\x6b\xfb\x5a\xfb\xe7\x01\x1e\x62\xc7\x33\x59\x6f\xdd\x9e\x5c\x6e\x89\xeb\xe4\xff\xe0\x5f\x7e\xdd\xda\xda\x7a\x60\xc2\xe6\x8d\x62\xe3\x88\xfe\xc1\x31\xb6\xac\x19\x70\xf9\xe3\xb8\x6e\x03\x06\xe0\xed\x72\xeb\xf6\xe4\x1f\xc0\x9c\x19\x63\xb0\x89\xcc\xb8\xd0\x7e\x6b\x51\x19\x70\xc1\x50\x62\xed\xcc\xb5\x98\xd6\x0f\x1f\xce\x81\xab\xf5\xf7\xdf\x7f\x3f\xf1\xc8\xbd\xb9\xca\x94\xf8\x60\x3d\x0d\x53\x1f\x86\x11\xef\xc0\x6d\x76\x18\xc6\xf8\xda\x8f\x3c\xdf\x02\x67\x9e\xea\xcf\xd5\x42\x44\xa6\x21\x1a\xc3\xfb\x3c\x06\xe8\xeb\x3e\xcc\x23\x3d\xa3\x3d\xc9\x52\x2f\xe0\x4d\xee\x48\x16\xef\x68\x4e\xe1\x98\x6b\x5d\xd4\xdc\xea\x8e\xdb\x0c\x17\x07\x7b\x47\x6d\x6a\x80\xcd\x8e\x2a\xd5\xca\x39\x7c\xf1\x68\xff\x77\x50\x8d\xa5\xfa\x47\x76\x09\x55\x57\x2c\x5b\xb2\xca\xf0\x76\x92\x41\xa1\xf0\xe4\xe0\x0d\x2a\x54\x3c\x64\xd8\xa8\xe6\xf0\x34\xc9\x5a\xf5\xa8\x47\xac\x34\x1a\xea\x00\x0f\xb5\x74\x9a\x64\x1a\x4d\x7d\xf7\x59\xec\x03\x1b\x8e\x46\xd5\x90\xfa\xd7\x89\x3e\xbf\x73\xa2\xf0\xdb\x11\xa7\x7f\x85\x2b\x2b\x5f\x7b\xeb\xbe\x97\x58\x4d\x21\x6c\x4c\x99\xf6\xfa\xf0\xd1\x3d\xbc\xc1\x4e\xc6\xf0\xad\xea\x9b\xdc\xbf\xd8\x83\xdb\xa7\xed\x16\x46\x39\x2f\xab\x89\x26\x01\x55\x77\x4b\x83\x15\x59\x9e\xa4\x34\xd6\xe4\x66\x72\xd7\x31\x4d\x93\x3c\x2b\x58\x67\x8f\x43\x07\x98\x79\x39\x61\xb8\x70\xbb\x65\x5f\xbe\x05\x62\x1a\xa1\xeb\x07\xdf\xc3\x15\xf4\x01\x82\x4d\xd6\x9e\xf5\xcb\xc5\xbc\x28\xd5\x2f\x16\xc3\x80\x51\xbf\x54\x0c\xd3\x55\xfd\x42\x31\x2f\x62\xcd\x32\xf1\x80\x53\xe3\x3a\xb1\x71\x4d\xd8\x30\x5b\x80\x75\x1f\x24\x6e\x98\x1a\x72\xc1\xbc\x11\x03\xff\x6e\x0a\x8c\xee\xdd\xd3\xfa\xaf\x5e\x50\xd2\x23\xaa\xef\x39\xbc\x79\x5b\xa2\x7b\xc8\x7b\x87\xde\xcb\x8f\xb4\xfd\x88\x7d\xe5\x73\x68\x7a\x3b\x52\xb2\x34\x99\xc3\xe5\x58\x31\xb7\x84\xe9\x83\x87\xf5\x69\x6a\xf4\x33\x21\x58\x5a\x9a\x24\x1c\x49\x00\x48\x12\x31\x93\x89\xe0\x82\x2c\x43\xdb\x40\xc8\xb4\xd0\x88\x1e\x22\xe2\x1a\xa5\x06\xcb\x66\x93\x49\x8a\x6e\xa3\x4c\x8c\x73\xf9\xc7\x1c\x30\xbb\xeb\x20\x11\xbb\xb0\x23\x4b\x7c\xe8\x21\xf2\xc7\x48\xa4\xe8\x3d\xca\xd0\x7b\x94\x0b\xcc\x21\xcb\x63\x96\x26\xba\xa4\x43\x3d\xcc\xe1\x15\x98\x17\xbc\xf3\x4f\x99\x6c\xc5\x3d\xe4\xae\x23\x97\xf9\xbe\x47\x7c\x33\xad\xdd\xbb\x0d\x39\xea\x6e\xa1\xbb\xbb\x1b\xb7\x85\xe3\xf7\x82\x38\xf7\x18\xe9\xaf\xf2\x20\x83\x4a\xb9\xbf\xe4\xba\xe5\x3e\xb4\x87\x32\xdd\x12\x1f\x02\x92\x0f\x1f\x22\xcf\x95\xad\x04\xf5\x6b\xdf\x16\x45\x7b\x48\xc7\x47\xb2\xd9\x6d\xad\x8d\x16\x03\xe5\x22\x5a\xbd\xd8\x96\xf4\x6f\x78\xa3\xce\x42\x20\x2c\x18\x0e\x32\x9f\xa0\xce\x22\x20\x2c\x16\x66\x7a\x18\x4f\x5d\x28\xcc\xf5\x30\xbe\xba\x48\xc8\xfa\x30\xdf\x16\xf8\xfe\x59\x17\xf8\xf8\x58\x78\xa7\x98\x2d\x16\x4b\x75\xcd\x6d\x17\x3a\x6a\xf9\xf7\x45\x44\x20\x17\x42\x8b\x79\x64\x9d\x6e\xb0\x4c\xf7\x95\x56\xe8\xae\xb8\x0e\xa4\x5d\xae\xfb\x23\xae\x06\x7d\x5b\x42\x18\x2c\x06\xf0\xe1\xf3\x95\x56\x0f\xa0\x82\x6d\xe1\xa0\x3b\x20\xef\xae\x19\xf0\xb2\x6f\xcb\x05\x37\xba\x5c\x00\xfa\xd8\x60\xa5\x40\xaf\x96\x76\x91\x40\xaa\xc6\x7c\x6d\x8a\x03\x98\x97\x05\xa2\x3f\x74\x82\x8d\xd5\x34\x21\x41\xf8\xb5\x73\x63\x48\x2a\xff\x3e\xcb\x07\x83\xe5\x01\x75\x0e\x4f\x82\xb0\x33\x8b\x57\x6e\x61\xf7\x57\x05\x08\xf1\x37\x5b\x17\xe0\x80\x1d\x9c\xf0\x5d\x20\xff\x5d\xd7\x06\x32\xec\x06\x31\xcb\x29\x9f\xf2\x7b\x61\x94\xe5\x81\x1b\xc1\x67\x37\x72\xf3\x1c\xc3\xe7\x22\x72\x59\x10\x7b\xfa\x35\x83\xa2\xc8\x5c\x37\xf5\x60\x71\x21\xa4\x01\xc5\x01\x16\x9f\xfd\x22\xa6\x45\x02\x08\x52\x56\x24\x7e\x91\xf8\x57\x58\x2e\xd8\x68\xe4\xa9\x84\x7d\x29\x3a\xa5\xa6\xe5\x16\x2d\x44\xd4\x66\x38\x73\x6f\x6f\x38\x78\x31\x6c\x2c\x7d\xeb\xa2\x47\x7a\x5c\x42\xfc\xab\x76\xd2\xbc\xca\x48\x37\xdd\xf1\x8e\x41\x47\x4d\x88\xfe\x12\xfb\xb7\xae\xfa\x0b\xba\x6a\xae\x95\xcd\x3a\x6b\xad\x72\x3a\xdd\xb5\x50\x90\xb5\xc3\x26\xa4\x7f\xd5\x59\xb9\xd7\x2c\xbb\xa3\xfb\x11\xe5\x1d\x38\xfd\xb6\xae\xff\xdf\xd3\x31\xff\xf1\xae\xe5\x3d\x13\x8f\x38\x94\xbf\x36\xb7\x72\xd1\x72\x71\x3e\xcf\x51\xd6\xbd\xaf\xa7\xb4\xe0\xb0\xff\x74\xca\x0f\xdd\x6d\x80\x7a\xa1\x96\xb5\x38\x44\x89\x6e\x04\x83\xd4\x2d\xe5\x72\xf5\x6a\x59\x9e\xb2\xc9\x5c\xdb\x8d\xad\xfe\x6b\x59\xfd\x58\xcf\xf3\xf9\x97\xc9\xbc\x3f\xcf\x6c\x16\x82\x85\x3a\xd1\x1e\x22\x0f\xea\xcf\x0f\xf7\x04\x86\xfa\x07\xcb\xda\xf0\x9f\x26\x73\xf4\x17\x09\xb6\x65\x5c\x2f\x94\x3e\x5a\x24\xb3\x15\x1b\x3f\x15\xd8\x5f\x1f\xab\xe7\xe3\xcb\xf3\xee\x0c\x57\x23\x96\x13\x56\x3d\x5d\x26\xf0\x39\x99\x3d\x2e\xab\x95\x46\x40\xcd\x16\xfe\x1c\xdd\x43\x93\x39\x64\xf6\xdc\x42\x77\x3b\x8b\x1f\xfd\x95\x2c\x85\x56\xbd\x4a\xad\x66\x66\x87\xdf\x40\x21\xbd\xfc\x3d\x17\xd3\x72\xc6\xd0\x44\x96\x3d\x44\xf2\x48\x66\x5f\x8a\xad\x36\x8d\x82\x6e\x50\x50\xa3\x94\x0f\xdf\x0a\x20\x48\x3b\x3a\x10\x04\xd8\xc2\xd9\xe2\x62\x32\x77\x10\x46\xbb\x88\x6c\x6d\x90\xb1\x1d\xc1\x4b\x28\x57\x41\xeb\x6d\x69\x93\x67\x0b\x14\xdb\xdb\x23\x4b\xa1\xf3\x0e\x44\x3d\x42\x9a\xb4\x38\xaf\xbf\xc7\xc6\x07\xde\x9b\x8d\xa6\x87\x23\xf4\xaf\xbe\xd3\x76\xb8\xb3\x9a\x95\x19\x9b\xb8\x5b\xdf\x76\xbd\x36\xde\xf5\x1a\x14\x15\x50\x14\xe8\x8a\x4e\xa0\x68\xb0\x61\x04\x63\x16\x28\x8a\xbe\x78\x1b\x2d\xb4\xe4\xba\xff\xbd\xb7\xd1\x4e\x92\xd3\xd3\xc4\x5d\x37\x9b\x69\x78\x20\x94\x21\x34\x1c\x34\x9e\xd4\x35\x1f\x3e\x44\x44\x6c\x7a\xd5\xbf\x7c\xff\xfd\xf7\x28\xda\xda\x42\xe8\xbd\x1e\x53\xf7\xaf\x83\x09\xfb\x03\x4c\x98\x6e\x6d\x6d\x86\xa9\x5b\xcf\xd3\x86\x97\x4e\x4b\x70\xdb\x6e\xed\x21\xf9\x2e\xb0\xd2\x6c\x2c\x98\x55\x9a\x8d\xd7\x75\xbe\xe9\x0d\x99\xed\x62\xf2\x86\x98\xe2\x2b\x36\xbb\xae\xa7\x7f\x93\x00\xb5\x86\x23\x95\xb8\x2d\x5b\x0e\x49\x7e\x65\x0b\xb7\xad\x1b\xa6\xa6\xdd\xcf\x0c\x6e\x35\x4e\x18\xba\x8d\x0a\x38\xec\xf6\x0f\xfe\xf1\xc4\xf4\x84\xcb\x69\x02\x19\xe6\x12\x74\x1b\xa5\x00\x9e\x88\xdd\xc1\xf7\x48\xee\x13\xea\xf8\x87\xc1\x4a\x79\xc2\x19\x6f\xb6\x5a\xe5\x66\x9b\xdc\x6b\x15\x47\xff\x44\x09\x8e\x95\x12\xec\x75\x8a\x3a\x95\xf4\x7d\x5b\x43\x0c\xde\xa9\x99\x30\xb0\x71\x91\x39\x99\x41\xbd\x50\x62\x14\x25\x58\x29\xc1\x58\x2d\x8a\xc4\xc9\x56\x51\x44\x02\x7d\x8f\x07\x1b\xc8\x9c\xd2\x14\x6d\xd7\x64\xb7\xb9\x50\xb7\xc5\x43\x6f\xc6\xcd\x63\xa8\x48\xd0\x5e\x2d\x98\x6d\x2e\x5a\x1d\x05\x1e\xb8\x4e\x34\x08\x78\xac\xeb\xe7\x69\xe7\x7f\x3c\x3c\xea\x86\x5f\x90\x3b\x13\x5e\x4b\xc0\xba\x6d\x3e\x34\xb2\x45\xda\xcf\xb6\x8e\x46\xb6\x43\x27\x15\x17\x44\x45\x74\xb4\xfe\x5d\xb6\x46\x05\x4c\x20\x61\x20\x65\x78\xa1\x87\x09\x25\x0c\xa4\x04\x3f\xd1\xc3\x44\x12\x06\x7c\x7e\xfa\x6d\x1b\xf6\xdb\x36\xec\xb7\x6d\xd8\xe1\x68\xf3\xdb\x36\xec\x3f\xe5\x1a\x6f\x10\x5e\x79\x8d\x37\x08\x47\xd7\x78\xd5\x39\xdb\x70\x8d\x37\x08\xbf\xad\xf1\xde\xf8\x1a\x6f\x10\x6e\xba\xc6\xab\x53\x4e\x77\x8d\x17\x14\x64\x3f\xb4\xdd\xec\x9d\x19\xb6\x66\xe3\x3f\xf4\xd6\xec\x3a\xf4\x7f\x97\x87\x0b\x1a\x3a\xdf\x56\x81\xbb\xab\xc0\xeb\x10\xf6\x54\x77\xd6\xa1\xaf\xfc\xfe\x73\xe8\xcb\x2c\xdd\x00\xb1\xa3\xe4\x89\xbe\x52\x4e\x37\xa5\x7d\x47\x87\x2f\x3f\xbc\x7c\xfa\xf4\xf5\xc1\xf1\xeb\xfe\x6a\xf1\xab\x67\x1f\x9e\xfd\xf8\xe4\xe0\xe7\x83\xe1\xab\xdc\x47\x2f\x7f\xfa\xf1\xc9\x87\xfd\x97\x3f\xbe\x3e\x7e\xf4\x63\x53\x53\x21\x27\x96\x95\xf7\x37\x5b\x56\x56\x6a\x2c\xa7\x8b\x3a\x69\x4b\x6f\x4d\xba\x26\xcd\x67\xd7\xd8\x41\x97\xa6\x54\xe5\x95\x58\x12\xa9\xd0\x43\x44\xfc\x07\xa8\xd2\x2c\x89\x28\x6d\x7e\xbb\x46\xdb\x28\x40\x77\xd1\xa5\xb8\x3d\x58\xd5\x97\x34\xe1\x13\xd9\x82\x95\x4a\xf4\x17\x14\x0e\xc6\x22\x30\x0c\x64\x17\x3f\xa3\x3d\x74\x89\xfe\x82\x02\xdd\x28\x91\x5d\xfc\x27\xc7\x4a\xd0\x5d\xc4\xe9\x78\x9c\xce\x96\x06\x78\x2d\x96\xe5\x7e\xee\xfd\x7c\x29\x7e\xfe\x4f\xc3\x52\xb0\x22\xb6\xb3\x12\x95\xf0\x9c\x80\x46\x68\x8d\x64\xd6\x42\x32\x6b\x71\x41\x73\xad\x11\x4c\x03\x2a\xa4\x8b\x2e\x05\xe8\xa5\x61\x59\xa9\x35\x90\xae\x18\x2f\xe1\x81\x9f\x61\xab\xb9\x5c\xfb\x4d\xff\x3c\xda\xb6\xde\x2e\x47\xd7\x1a\x9e\x3f\x7d\x7d\xc4\x79\x5d\xbb\x58\x67\x0c\xea\xbb\x13\x86\xf5\x31\x0e\x06\x24\x6a\x63\x7d\xb1\xba\xe8\xd9\x96\x16\xec\x79\x0d\x66\x10\xa1\x7c\x79\xe2\x17\xf4\x10\x45\x0f\xd0\x2f\x96\x95\x39\x68\x03\x5c\x4d\xd5\x67\x45\xa9\xc9\xa7\x65\xf5\x6a\xb1\x82\x3c\xae\xdc\xaa\xe0\xb1\xdc\x5f\xb6\xd0\x3d\xa4\x3b\x4d\x5d\x23\x57\x2b\x3d\x44\x32\x5f\x84\x0e\x98\xff\x0d\x1a\xf8\x7e\x0f\x01\x19\x05\x8b\x81\x56\xf7\x44\xb5\x4a\xf5\xfb\x3d\x20\x6b\x3e\x5c\x3d\xa0\xfc\x42\xa1\xdc\x41\x75\x4f\x33\xef\x69\x18\xd8\x6c\x69\x49\x31\xac\x39\x5b\x57\x60\x40\x23\x6a\xa1\xe6\x93\xe8\xbb\xbb\xe8\xd5\xb2\x3c\x2d\xab\xf2\x13\x43\x67\x8b\xd9\xe5\x7c\x71\x5a\x26\x33\xb4\xf8\xc4\x96\xe8\xaf\x4f\x27\x64\xeb\x3e\x5a\xbf\xa7\x68\x1b\xad\xdf\x87\xf0\x6f\x00\xff\xfa\x3c\xcc\xe8\x51\x4a\x8b\x16\xe4\xc5\xfd\x81\xf7\xc8\x5d\x47\x96\x23\xf3\x06\xe6\x24\x86\x3d\xad\x7d\x8c\x6c\x7a\xf5\x02\xbc\x58\xe3\x93\xdd\x4f\x9d\x60\xac\xaf\x33\x67\x60\x3f\x5b\x57\xdd\x4d\x59\x81\xff\x54\xec\xf4\x6c\xb1\x4c\x96\x97\x9d\x97\xe8\xb8\x0b\x1c\xab\x1d\x91\x71\x97\x52\xfb\xea\x8c\xde\xfb\x8f\xb5\x2d\x1b\xe3\xbb\xb7\xb7\xe3\x6d\xb6\xb3\xe3\x75\xf6\x75\x3c\xdb\xae\xce\xcd\x3f\x25\xb0\x38\xaf\xce\xce\xab\xe7\x30\xb5\xee\xc0\x22\x18\xa4\xe7\x6c\x55\x2e\x59\xae\x3c\x34\x90\x96\xd5\xaa\x4e\x08\x2d\x2a\x77\x66\x0b\x75\xe5\x97\xf3\x59\xad\x26\x25\x07\x77\xb2\x64\xf7\x11\x21\xbe\x83\x48\x10\x3a\xc8\xa3\xbe\x83\x02\x4c\xfa\x95\xe5\x9b\x05\xf7\x79\x99\x5a\xd4\x7f\xb4\xa0\x9e\x34\x1b\xdf\x2d\x50\x5b\xd7\xc3\x76\x8d\xf7\x0b\x60\xa5\x16\x5e\x42\xac\xe7\xde\xf5\xb7\xb7\xef\x0c\xd1\x7e\x03\x53\xe3\x7f\x80\x47\x98\xdc\x9c\x5d\x34\x66\x07\x9b\x70\x63\xa9\x04\x80\x93\xe6\xb5\x5e\xe8\x01\x42\xd7\x45\xf7\x10\xef\x68\x9b\x97\x12\x54\x49\xf0\xd1\x8b\x47\xbe\xd6\x8e\x9e\x66\x61\x4e\x23\x34\xed\xe2\x59\xdd\x88\xe7\xc9\x1c\xd6\x7e\x7a\x4d\xdb\x45\x44\xb7\x86\x96\xae\x16\xcb\x74\x5c\xfe\x3d\xf4\x5f\x92\x49\xf0\x05\x29\x51\x77\x53\x8c\xcb\xda\xb8\x6c\xfe\x82\xc0\x1b\xf4\x7d\x70\x1e\xeb\x6d\xc9\x2c\x8c\x4f\x50\x73\xf2\xd6\x7c\x82\xa4\x93\x48\x90\x5c\x27\x83\x20\xe9\xa4\x0e\x24\xd7\xcf\x19\x28\x19\xc6\x63\x1c\xe3\x2e\xcb\xf8\x5a\x3c\xe3\x2e\xd3\xf8\x2a\x5c\x6b\xf5\x20\x8c\xab\x59\x1a\x29\xe7\xd5\x42\x58\xb3\x5e\xd3\xb3\x04\x16\xf3\x6a\x77\xd6\x88\x82\x43\xec\xc0\x7b\xb3\xef\xf7\x40\x2e\x26\x98\xd9\xe2\x02\x49\x98\xf1\xdd\x88\x23\xde\xc1\xae\x0c\x3e\x20\x06\xca\xe0\x07\xe2\xa3\x18\xf4\xc2\x67\xb3\x0b\x1c\x4f\x59\x95\x0c\x4b\xae\x30\x6b\x50\x90\xbd\x28\xf9\x14\x64\x76\x7e\x3a\x87\xc6\x69\xdc\xaa\x96\x60\x3d\xcc\x76\x50\x3b\x92\xd6\x02\x5f\x71\x4e\xa2\xd2\xe8\x68\xa9\x9d\xa1\x18\x08\xf1\xbf\x7a\xe8\xd9\x68\xcf\x06\xfb\x5c\x81\x9d\x2d\x2e\x8c\xe3\x52\xa3\xb4\x8e\xb5\xe3\x1c\x5d\x4b\x8e\xb9\x16\x8e\xdf\xae\x4d\xbc\x1f\xaf\x85\xad\xed\x41\x0b\xcc\x40\x60\x6c\x7b\xc0\xfa\x66\xa7\x6f\xae\x67\x06\x96\xb0\xda\xb6\xc8\x87\x26\x4d\xb8\x5e\x7c\x98\x1e\xda\x36\xcb\x8f\xd7\xb8\x05\xc7\x9b\x80\x0b\xfb\x3a\x5e\x63\x9b\x1e\x25\xec\xf3\x06\x16\xf4\x68\x35\xef\xd5\xf9\x12\x3c\x4a\x3c\x27\xc2\x4d\x7d\xdc\xca\x8f\xd7\xbe\x8c\x05\x68\x32\x91\xbc\x35\x57\x83\x25\x7d\x79\x3f\xd8\x34\xbd\x01\x6c\xcf\x1b\x6c\x22\x6a\x08\x6c\xcf\x7b\xd8\x5e\x8c\x63\xfb\x5d\x9d\xaa\x13\x0a\x2d\xf6\x89\xfa\x21\xd1\x60\xa6\xe8\x4a\xb3\xbd\xa3\xe9\x02\xbd\x2a\x2d\x96\xcd\x49\xd6\x6f\x3e\xe2\x07\xca\x57\x31\x94\x6b\xbe\x7f\xb1\xc9\x77\x24\xd7\xa0\xb5\x99\x31\x07\x12\x16\x34\x16\x90\x6a\xe8\xe7\x2d\xb4\x39\x24\x41\x67\x31\x5d\xbc\x14\xa3\x94\xbd\xce\x7a\x98\xca\x97\xb1\xb1\x47\x0b\x18\xe8\x59\x42\x3c\x9f\x40\xb7\x24\x46\x17\x1e\x14\x5f\x99\xd4\x8d\x7e\xf8\xb0\x65\x12\x4c\xbb\x6e\x1f\x3c\xa5\xe9\x11\x74\x4f\x29\x37\x19\x3a\xea\xba\x4e\x83\x43\x8b\xfc\xc5\x15\x91\x77\xd7\x3c\xda\xe6\x6e\xb4\xe2\xd1\x6f\xb2\xe4\x4a\x41\x03\xab\x1d\x43\xe6\x42\xff\xda\x2d\x7f\x31\x42\xe3\xf9\x15\x69\xd8\xfa\xb6\x65\x32\x5f\x9d\x2d\x56\x56\x2b\x81\xf0\xfb\xaa\x7c\x2e\x1c\xe3\xf8\xad\xb2\xa0\xd8\xda\xa1\xb1\xcf\x13\x15\x37\xe9\xf8\x24\xe4\x58\xef\x67\x8c\x1f\x67\x25\x4a\x2a\xe8\x02\x21\x5e\xea\xc7\x84\xc7\x2e\xb4\x41\x9b\xb4\xb5\x99\x1c\xb9\x4d\x00\xd0\xc2\x1d\xbb\x75\x73\x04\xb4\xc9\xe5\x8f\xdd\xba\x31\x12\xce\xd0\x6f\xed\xee\xa2\xfd\xa9\x2d\xf8\x6d\xde\xad\x5f\xb3\xcb\x18\x0f\x8d\x48\x09\x5f\x75\x1c\x6e\xfa\x95\x11\xe3\xbe\x5a\x48\xad\x6b\x1d\x37\x06\xb7\x79\x95\x35\x6e\x2a\x4d\x94\x41\xc8\xd6\x26\x1d\xa0\x40\x40\x7a\x08\xc8\x00\x81\x55\x8a\x7c\xec\xb1\x5c\x5c\x58\x84\x38\x53\xbc\xe1\xb8\x75\x8d\xf7\x68\xf2\x0f\xc9\xbe\xf8\xe1\x76\xcd\x0c\x7c\xb5\xc5\x8f\x99\xe2\x35\xc7\xad\x0b\xa9\x18\xe1\x87\x16\xe3\x6c\x71\xf1\xe5\x0b\xb4\xcf\x16\xba\x19\xc9\x40\xdf\x46\x4f\xeb\x2c\x43\xf2\xfe\xad\xb7\x98\x09\xe5\xa3\x93\xb6\x0e\x16\x93\x23\x76\xc6\x2b\xdd\x1a\xdc\x25\x2d\x9b\x1d\xff\x5c\xc7\xa2\x34\x8b\x34\x37\x7e\x2a\x6a\x00\xdf\xac\xf8\xf0\x7a\xc3\x65\xa0\xbb\xb0\x78\x35\x5c\x07\xba\xee\x59\x2a\x7c\x9d\xa3\x54\x70\x48\x2a\x63\xe5\xac\x7b\xde\x09\x6f\xa1\xdd\x2e\xff\x5b\xe8\x6e\xff\x07\x20\x0e\x1b\x34\xcd\x69\xae\x7f\x92\x43\x50\x5f\xbc\x86\xa7\x2e\x33\xd6\xcc\x6b\xd7\x20\xd1\xae\x56\xf5\x2a\x48\xbd\x0a\x38\xc4\xb9\xab\x3d\x4c\xf7\xfa\xbf\xce\x19\xfb\x95\x0d\x91\x4e\x93\xd5\xb4\x36\xee\x8d\xde\xa2\x1f\x70\xf1\x25\x8b\x85\xe3\x6b\x42\x9b\x0f\xe9\x4d\xc3\xf9\xab\xaf\x21\xb6\xf4\xcc\xab\x72\xca\xd0\x50\x2e\xcc\xa9\x03\xce\x2b\xad\xcd\x29\xa8\xe4\xf2\x9c\x8a\xea\xba\xeb\x8a\xad\x28\xec\x8d\x78\x3e\x68\xc4\xf3\xeb\x36\xe2\xf9\xa0\x11\xcf\xaf\xd6\x08\xbd\xaa\x84\xe9\x4a\x27\xab\x16\x68\xc9\xaa\x65\xc9\x3e\x31\xcd\x01\x44\x24\x2f\x77\x8b\x78\x70\x76\xbe\x9a\xd6\x6c\xe8\x44\xa4\x81\x7c\x31\x84\xfc\xf2\xf4\xc4\x9a\xdb\x43\x0d\x69\x67\xe8\xc2\xc6\xfb\x44\x37\x74\x6a\xd2\x1c\xbf\xe4\x11\x4a\x4d\x38\x6b\x2e\x3b\x6d\x10\x21\x36\xdc\xcc\xa9\x3f\xb6\xe7\x33\xad\x62\xff\x76\x5c\xf3\x9a\xc7\x35\xbd\xab\x1e\xd6\xf4\xc6\x8e\x6a\x7a\x96\x83\x9a\xde\xb7\x63\x9a\x37\x7d\x4c\xd3\xdb\xf0\x90\xa6\x46\x2d\x9d\x23\x9a\xde\x26\x07\x34\x3d\xf3\x35\xfc\xe6\xe0\xe1\x7d\xea\x7d\x7e\xe7\x50\xf7\x5f\xe4\xb8\x66\x3f\xc1\x4e\x80\xc9\xef\x76\x86\xb3\x4e\xb7\xc3\x69\xfe\xb1\xd2\xed\x5c\xeb\xb4\xa5\x2c\x6e\x4f\x7b\xd6\x30\x57\x4a\xc8\x13\x60\xd2\x39\x16\x12\x60\x62\x3c\x66\x42\x37\x4c\xc8\xc3\x01\x3b\x47\x4d\xa8\xcc\x6a\x11\x60\x72\x63\x57\x88\xd5\xe6\x1b\x73\xf2\x0c\x0e\x39\xb8\xeb\x2c\x4d\xd3\x38\x0f\x72\x47\x49\xd8\xb3\xe5\xe8\x20\x43\x12\x27\x24\x26\x89\x9a\xce\x67\x4b\x93\xb7\x47\x53\x35\xc6\x41\xec\xe2\x20\x51\xb3\xff\xe8\x89\xe0\x80\x14\x2c\x13\x39\x83\xea\xdc\x40\x1b\x12\x09\x23\xcf\x23\x61\x28\xd2\x0a\xc9\xcc\x41\x7a\x22\x94\xa5\xbe\x9f\xd0\x48\xcd\x2b\xb4\x21\x91\x3c\x75\x33\xc2\xdc\x5c\x4d\x43\xa4\x27\xe2\x47\x69\xe0\x53\x9c\xab\x49\x8a\x7a\x43\xd3\x9b\xce\x52\xc4\xed\xe9\x9a\x59\x8a\x70\xf8\x2d\x4d\xd1\x0d\x8d\x89\xe8\x95\xd3\x14\xf1\x2a\x63\xe3\x22\x35\x66\x0c\x47\x46\xf4\x5b\x9a\xa2\x9b\x1f\x1b\xd1\x4d\xd3\x14\x69\x95\xd3\x1d\x1f\xd1\xd1\x34\x45\x1e\xb5\xa7\x29\xe2\xdd\xf8\x7d\x8a\x75\xa3\x25\xfc\x2f\x32\x5a\xfa\x97\xbe\xdc\x72\xb3\x17\x5b\xbe\xd2\x95\x95\xeb\x0f\xa2\x44\x51\xd3\x5c\x89\xe8\x43\x7d\x82\x57\xf3\xd6\x4d\xf7\x90\xef\x4e\x72\x76\x36\xbb\x9c\xc8\x1f\x1d\x94\x2c\x4f\xce\x4f\xd9\xbc\x5a\xf5\xdf\xe4\x51\xaf\xcf\xb4\xfc\x40\x2a\xa5\x96\x44\x8f\xbc\xbb\xf6\x09\x4d\x48\x11\xc3\xb8\x22\x8f\x08\x4d\x18\x21\x5b\xce\x10\x2e\xc2\x5e\xe4\xfb\x31\xa4\x19\x24\x1e\x2b\xc2\x20\xcb\xd5\xa1\xc1\xa0\x42\x1a\x64\x6e\x91\x66\x05\x3c\x80\x90\xf9\xb9\x97\x92\x42\x87\x98\xc5\x69\x90\xa7\x49\x00\xaf\x67\x63\x1a\xe7\x69\x9a\x59\x11\x7b\x71\x10\x66\x24\x48\x61\x38\xe3\xf9\x34\x0d\x3c\xaa\x43\x1c\xc4\x05\xc6\xb8\x00\x8e\xd3\xd0\x0d\x72\x17\xc7\x56\xc4\x31\xf1\x0a\x4a\x12\x78\x72\x3b\x29\x70\xec\x17\x71\xaa\x43\x9c\xa4\x38\x0b\x58\x0e\x1c\xe7\x49\x98\x53\x8c\xa9\x15\x71\x4e\xdd\x28\x49\x84\x8c\x13\xcf\xf5\x5c\xe2\x6b\x65\x8c\x09\xf5\x82\x54\xbc\x19\xe1\x07\x91\x1b\x16\x29\xb3\x22\x26\xbe\x87\x69\x90\xc2\xdb\x11\x3e\x63\x7e\x4a\x68\xa6\x15\x45\xe0\x66\x51\x9e\xc1\x03\xe2\x79\x50\x14\xa9\xcf\x88\x15\x71\x44\x52\x16\xe4\x11\x88\xa2\x20\x51\x4a\xe3\x50\xab\x3c\xea\xe6\x2c\xc5\xe2\xf1\x0a\x2f\xc5\x61\x1c\xa6\xd8\x2e\xe3\x34\xcf\xdc\x50\x64\xa8\x24\x41\x16\x61\xe2\x05\x3a\xc4\x19\x8e\xd3\x02\x0b\x06\xb2\x22\x8c\x49\x18\xfb\x56\xc4\xcc\x8f\xd3\x30\xce\x40\x76\x31\x2b\xb0\x9f\xe4\x5a\x19\xb3\x22\x65\x7e\x44\xe1\x19\x71\x8f\xfa\x05\x09\x98\x67\x45\xec\x16\x19\x8e\xf3\x0c\x2a\xd0\x94\x66\x79\x90\x6a\x39\x26\xbe\x9b\x25\x38\xcb\xe0\x91\xf6\x28\xc9\xe2\x2c\x0c\xec\xca\xcb\x59\x4c\xb2\x10\x1c\x24\x88\x49\xea\x92\x48\x8b\xd8\x4f\x22\x9f\xfa\x09\xcc\x11\x42\x96\x84\xcc\xa7\x76\x8e\x83\x2c\x75\x93\x38\x07\x4e\xd2\xdc\xc7\x45\x9a\xfb\x5a\x97\x0e\x8b\x98\xd2\x1c\x10\x53\x0f\xe3\xc0\x4b\xed\x1c\xc7\xd4\x63\x01\x0e\x08\xb8\x34\x0b\xc3\xbc\x48\xf4\x0e\x42\x3d\x9c\x85\x21\x8c\xf0\x49\x9e\xfa\x1e\xc1\xae\x3d\x56\xb8\xae\x47\xa2\x8c\x8a\x37\xdf\x8b\x94\x60\x4f\x6b\x6e\x69\x11\xc4\x51\x91\xc9\xfc\xa6\xac\x70\x19\xb3\x5b\x45\x16\x32\xd7\x4d\x0b\x30\x7c\x2f\x4f\x28\x2d\x32\xad\x55\xe4\x41\x12\xc5\xd8\x07\xc4\xb1\xe7\x26\x49\x44\xec\xa2\x70\xc3\x2c\x09\xbd\x40\x3c\xef\xe2\xba\x1e\x25\x7a\x07\xc1\x3e\x89\x49\x2c\xe6\x5e\x6e\xe2\xb2\x90\x45\x76\x51\x90\x28\x8d\xdc\x84\x42\x70\xf1\xc3\x9c\x90\xa2\xd0\xba\x34\x61\x98\x8b\x09\x44\x16\x64\x24\xcc\x62\x12\x5a\x11\xfb\x39\xc9\xc2\xbc\x00\xab\x08\x92\xcc\x27\x09\xcb\xb5\xb1\xc2\xf3\xa8\x9b\x63\x10\x59\x9c\xc7\x41\xea\xe5\x85\x15\x71\x18\xb8\x49\xe4\x05\xbe\x70\x90\xa4\x08\xbd\x9c\xe9\xcd\x2d\x4c\xdc\x24\x85\xb8\xed\x65\x51\x94\x92\xc4\x1e\x36\x29\xce\x48\x16\x13\x11\xdd\x22\x96\x27\x8c\x85\x3a\xc4\x31\x89\x08\xc9\x84\xc8\xb0\x4f\x89\x17\x78\xa9\x15\x71\x42\xd2\x82\xd1\x44\xc4\xd9\xac\xc0\xae\x17\x6a\x1d\x24\xa1\x38\x09\x43\x1f\x38\x4e\x33\x9f\x78\xae\x6b\x8f\x6e\x19\xf1\x53\x9a\x46\x2e\xc4\x59\xb7\xa0\x71\x14\x63\x6d\x74\x8b\xc2\x2c\xc0\x09\xc8\xd8\x0d\x03\x3f\x65\x9e\xdd\x2a\x72\x1c\x13\x46\x71\x0c\x88\x43\x56\x04\x04\x6b\xfb\xbc\x3c\x8c\x63\x37\x24\xa0\x8b\x20\x08\x83\x24\x1e\xf1\xbc\xc2\x77\x99\x17\x08\xd9\x05\x51\x84\x89\x4b\x12\xad\x1d\xbb\x61\x92\xb8\xa2\x65\x1e\x49\xd3\x1c\xa7\x76\xe5\xe1\x38\xf1\x33\x8c\x21\x6c\xa6\x34\x27\xb9\x9b\x69\x39\xc6\xcc\x8b\xc2\xcc\x15\x76\x8c\x7d\x9c\xa4\x81\x3d\xba\x91\xc8\xa7\x51\xe4\x83\x1d\xe7\x05\x65\x2c\x8d\x63\x1d\x62\xcf\x4f\xdd\x34\x4b\xa1\x65\x0c\xc7\xa9\x4f\x47\xcc\xcd\x8b\x71\xe6\x66\x29\x28\x25\x0b\xb2\x38\x48\x42\x4f\x1b\x8f\x59\x4e\x93\xc4\x87\xb0\xc9\x3c\x1f\xd3\x24\xb3\x9b\x5b\x90\xc6\x59\x96\xf8\x85\xe8\x19\x42\x8f\x79\x91\x16\x71\x48\x09\x0b\x0b\x11\xac\xf2\x30\x25\x29\x4d\xec\xa2\x88\x7c\x5a\x50\xc2\xc0\x41\x82\x9c\x15\x29\xd1\xc7\x8a\x88\x26\x41\xe8\x89\x9e\xc6\xf7\x70\x44\x8a\xd0\x6e\x15\xd4\xcf\x68\x44\xb1\x18\x09\xe1\xc2\x4d\xd2\x48\x1b\x36\x69\x96\x45\x2e\x11\xca\xc3\x49\xe8\x7b\x31\xb3\x8f\xdd\x62\x37\x65\x45\x51\x24\x62\x14\x19\x7a\x98\x11\xad\x55\x24\x7e\xe0\x86\x19\x03\xcf\xcb\x19\x25\x69\xce\xec\x63\xb7\x94\x15\x71\xe2\x15\xa2\x67\x20\x59\x18\xc5\x58\x3f\xae\x08\x23\x1c\xd1\x42\x74\x61\x5e\x44\x02\x8f\xd8\x95\x97\x25\x24\xf2\x58\x06\x32\x66\x09\x09\x43\x1c\x6b\x65\x9c\x63\x1a\xa6\x54\x74\x4d\x84\x1b\x12\xe9\x2e\x02\x0e\x07\x22\x49\x9e\x44\x79\x0e\x0e\x92\xe5\xcc\x65\x29\xd6\x86\xcd\x22\x88\x72\xbf\x88\x0a\xd9\xe9\xb2\x1c\x47\x76\x3b\x76\xc3\xc2\x0d\x23\x31\x5e\x88\x08\x8e\xc2\x22\xd5\xba\xb4\x9b\x84\x5e\x94\x67\xe0\x20\x09\xc9\x68\x4c\x13\x7b\x0f\x82\xb1\x57\xc4\xd4\xf5\xe5\xc2\x5d\xec\xe6\x89\x96\x63\x9c\x46\xd8\x4d\x3d\x11\x8f\x3d\x9c\xf9\x11\xb6\xcb\x98\xd0\x3c\x8d\xa2\x22\x10\x56\xe1\xfa\x51\x4e\xb5\xf1\xd8\x23\x59\x92\xa4\x11\x58\x85\xef\x66\x11\xf1\x63\xbb\x83\x78\x59\xcc\x52\xe6\x82\x28\x70\x90\xc5\x29\x4b\xb5\xca\xf3\x3d\x9c\x87\x51\x06\x2d\x8b\x33\xec\xba\xb9\x6f\xb7\x63\x3f\xcb\x82\xdc\x17\x03\xef\x2c\xf5\x98\x4f\x52\x6d\xd7\xc4\x87\x2b\x24\x8e\x21\x58\x15\x59\x18\x44\x8c\x87\x57\x5b\xac\x28\xb2\x34\x2c\x12\xd1\x49\x26\x79\x58\x24\x4c\xcb\x71\x98\xf9\x3e\x8e\x29\x20\xf6\x13\x3f\x0a\x28\x8e\xe4\x22\xea\x3b\xcb\xb5\xd5\x76\x5e\xf8\xe6\xba\x37\x54\x4d\xcf\xa0\xbd\xe9\xdc\x50\xfd\x70\xbd\x1b\xaa\x01\x26\x9b\x6d\x1d\x68\xb6\x23\x6e\x3e\xfb\xe8\x75\xb7\x0e\xc2\xc4\x8d\x59\xbd\xe0\xee\xa5\x59\x16\xbb\x86\xad\x83\x34\x0d\xa3\x84\x89\xee\x97\xfa\x59\x92\x44\xdd\xa1\x8b\x85\x88\x97\x85\xac\xf0\x22\x88\x64\x05\x8b\xfd\x82\xf2\x48\xa6\x83\x4c\x02\xbf\x28\x02\x0f\xbc\x20\x28\x70\xee\x85\xc5\xa6\xab\xfa\x01\x76\x59\x40\x44\xf0\x49\x72\x16\x52\x92\x1b\xb6\x0e\xe2\xd4\x0d\x42\x2a\x0c\x92\xa4\x1e\x0b\x33\x5c\x6c\x48\x04\x17\xd4\xcb\x63\x61\xf3\x45\xea\xe3\x34\x0f\x0d\x2d\x09\x52\xe6\x66\xb9\x18\x06\x61\x2f\x62\x04\x47\xf1\x55\xb6\x0e\x6e\xfa\x1e\xe9\x26\xa9\x61\x01\xce\x35\x67\x7e\x3d\xc4\xe6\xd4\xaf\x87\xc4\x9c\xfb\xf5\xd0\x33\x27\x7f\x3d\xf4\xcd\xd9\x5f\x0f\x03\x73\xfa\xd7\xc3\xd0\x9c\xff\xf5\x30\x32\x24\x80\x15\x0d\x84\xf4\xb0\xda\x73\xe0\xa2\x7c\x26\xca\x87\x97\x3d\x84\x0c\xa0\xba\xf6\x0a\x94\x28\x9f\x89\x72\x43\x75\x02\xd5\x89\xb1\x3a\x99\x89\x72\x43\x75\x0f\xaa\x7b\xc6\xea\xde\x4c\x94\x1b\xaa\xfb\x50\xdd\x37\x56\xf7\x67\xa2\xdc\x50\x3d\x80\xea\x81\xb1\x7a\x30\x13\xe5\x86\xea\x21\x54\x0f\x8d\xd5\xc3\x99\x28\x37\x54\x8f\xa0\x7a\x64\xac\x1e\xcd\x44\xb9\xe6\x58\xdf\x86\x49\x8f\x85\x65\xe8\x90\x27\xc2\x28\xfa\x19\xf7\xe0\xc8\xad\x30\x08\x5d\xad\x54\xd8\x82\xae\x56\x26\xec\x40\x57\x2b\x13\x26\xa0\xab\x95\x0b\xf5\xeb\x6a\xe5\x42\xf3\xba\x5a\x4c\x68\x5d\x57\x8b\x09\x85\xeb\x6a\x15\x42\xd9\xba\x5a\x85\xd0\xb3\xae\xd6\x89\xd0\xb1\xae\xd6\x89\x50\xaf\xae\xd6\x54\xa8\x56\x57\x6b\x2a\xb4\x3a\xd3\xe5\x1d\xb4\x5d\xdd\xdd\xf0\x39\x54\x63\x3e\xed\x9a\xfe\x9b\x52\xe4\x1e\x36\x5d\x37\x3f\x80\x1e\xbc\xde\x3e\x1b\x82\x6c\x90\x28\x5a\x90\xe1\x22\x78\x53\xd6\xb7\x0d\xd4\xac\xd1\xe8\x2e\x22\xef\x00\x52\x9f\xcb\xb5\xc5\x31\x13\x38\xe4\xfd\x82\x3e\x0e\xb8\x35\x7f\xad\x0c\xd4\xbb\xbb\xe8\xaf\x90\x8d\xd8\x4c\xbc\x4e\xe9\x7c\xa5\x0c\xd5\xeb\x69\x93\xe7\x78\x3d\x76\x17\x4f\x82\xcd\x94\x1a\xf6\xfb\x78\x02\x6a\xda\xc9\x82\x3d\x15\xc9\x7f\xd5\xe4\xd5\x33\x48\x51\x5c\xa7\x03\xee\xc0\xd1\x01\x1c\x1c\x7a\x7d\x8f\xba\x60\x91\xed\x86\xa9\x80\x9c\x75\xb8\x98\x0d\xb9\x98\xea\xb8\x98\x0d\xb9\x98\xaa\x5c\x74\xe1\xa2\x21\x9c\x21\x93\xb1\xaa\x52\x43\xce\x9c\x4f\x4a\xee\xed\xab\x24\xdf\x6e\x35\x8a\x37\xd3\x28\x6e\x35\x8a\x37\xd2\x28\x9e\x76\x12\x7c\x4f\xeb\x2c\xdc\x4a\x62\xee\x99\xcc\xd5\xad\x08\x09\x4b\x09\x77\xc1\xe0\x1c\x73\xac\xa8\xb4\xc6\x17\x8e\xaa\x14\xcf\x3a\x6c\xcc\x34\x6c\x4c\x75\x6c\xcc\x06\x6c\x4c\x3b\x6c\x74\x11\x86\x03\x7c\x24\xb4\xea\xf4\x4a\xb9\xc3\x6d\xa1\x24\x6a\xd5\x1e\xd9\xd4\xfe\xa6\x8c\x44\xe4\xd2\x76\xcc\x3d\xc8\x99\x84\xb4\xdc\x09\x17\x90\x38\x54\x02\x89\xf1\x55\xe8\x1a\x56\x30\x80\xb5\x23\x8b\x3e\xec\xac\x86\x1d\xe5\xa1\x8d\x34\x33\x2e\xb4\x32\xea\xf7\x5c\x5d\xf0\x36\x94\x4d\x05\xf8\x14\x72\xb6\x71\x3c\x5c\x93\xee\x16\x7a\x58\x7b\x67\xf3\xcb\xff\x44\x18\xdd\x47\x83\x63\xd3\x43\x3e\xf8\xbf\xb5\x06\xc7\xd9\xe0\xff\x6e\x37\xde\x62\xe0\x02\x5f\x97\x0b\x90\xe2\x86\x3c\x08\xed\x0c\x39\x10\x9a\x18\xd0\xd7\x23\x6d\x7b\xc5\x37\xa5\x49\xbd\x6d\xaf\xf7\xa6\xd4\x31\x67\xce\x89\x2f\x93\xe2\x4f\xd1\x6d\x54\x4c\x65\x5a\x7c\xfe\x45\x7f\x8f\x4f\xd4\x11\xbe\xcf\x66\xbc\xce\x4c\xd6\xe1\x5f\x4e\x66\x96\x64\xfa\x53\xc8\xa6\xcf\x51\xa7\x82\x0e\x7c\xce\xc4\xe7\x54\x7e\x36\x57\x9f\x41\x75\x4e\x25\x15\x24\xe1\x73\x26\x3e\xa7\xf2\xb3\x3d\x25\xff\x54\xe4\xe4\x97\x01\x47\xf4\x2b\xc9\x4c\xa4\x97\xde\x12\xc9\x0f\x92\x69\x9d\xb1\x5f\x16\x76\x72\xf6\x4f\x95\x57\x24\x92\xba\xd7\xb1\x66\xe6\x87\xd9\xd4\xa4\x41\x24\x69\x4e\xbb\x34\x67\x1d\x9a\xd3\x2e\xcd\x99\x4a\x73\xba\x09\x4d\x2c\xda\xc9\x64\xd7\x20\xee\x9b\x30\xd1\x29\xd0\x3a\xed\xff\xb4\x7e\xb4\x42\x29\xf4\xdb\x42\x4e\xd3\xab\xcb\x44\x1a\x6e\x3b\x4d\xd1\x4e\x09\x5c\xd3\x9c\x76\x69\xce\x3a\x34\xa7\x5d\x9a\x33\x95\xe6\xb4\xa5\xa9\x1d\x75\x8e\xbf\x43\xa0\xe7\xf5\x07\xc8\xbe\xf4\x83\xf9\x32\xd5\x0f\xe0\xbc\x3f\x94\xb6\x6b\x54\x3f\x40\x30\xf8\xa1\x34\x85\xd0\x4f\xf0\x50\x02\x87\x99\xce\x1a\x16\x75\x4e\x29\x00\x39\xc1\x69\xdb\x16\x11\x2e\x2a\xac\x86\x8b\xe9\x26\xb1\xaa\x25\xcb\xff\xe5\x12\xb1\xd3\xac\x80\x54\x36\xd5\x11\xcc\xae\x45\xf1\x07\x6d\xe8\xe9\x53\xfc\xa1\xd4\x51\xfc\xa1\xbc\x0e\x45\x7d\xb0\xeb\x53\x7c\xa3\xa5\xf8\x46\x47\x51\x6f\x6d\xfd\xc7\x2b\x0c\x24\x61\xf1\xa2\x76\x7b\x00\x34\x72\x07\xeb\x20\x75\x54\xda\x16\xe1\x11\x58\x24\x2a\x8b\x35\xae\xcd\xd8\xfc\xe9\x2c\x4f\x2a\x86\x2e\xec\x33\x7d\xfe\x07\xf3\x4d\xad\x7d\xc3\x74\xf3\x44\xc7\x36\x74\x40\x85\xae\x0e\x4c\x6c\x0b\x5d\x1d\x98\x43\x33\x5d\x1d\x98\x42\x33\x5d\x1d\x98\x92\x4f\xf2\x19\x3c\xdf\x31\x33\xbd\xdf\x01\x73\xfa\x49\x3e\x05\x28\x21\x3a\xa6\x4a\x2e\x1f\x08\xcd\xf8\x12\x08\xc7\x94\xe9\x78\x84\x25\x85\x4c\xc7\x23\xac\x5e\xa4\xba\x3a\xb0\x78\x91\xea\xea\xc0\x3a\x49\xa2\xab\x03\xcb\x24\x83\xd7\x0c\xf8\x1f\x2c\xbb\x4c\x84\xa9\x57\xc4\x28\x0c\x58\xb8\x99\x08\x39\x70\xcb\xda\x6e\x7b\x1c\x21\x8d\x6a\x38\xd8\xb9\xd1\xc7\x4a\x94\x35\x43\x18\x19\x1c\x82\xfd\x27\x83\xd1\xc0\x61\x93\x8c\x62\x72\x08\x76\x9f\x08\x66\x0f\x5d\x95\xdb\x64\xc8\x6c\x1f\x8f\xb2\xca\x28\x08\x82\x88\xd2\x21\x41\xdc\x12\x04\xf1\xa4\x92\x60\x27\x12\xa4\xe3\x04\x95\x75\x49\x41\x90\x40\x88\x1d\x12\x24\x2d\x41\x32\xad\xfb\xa5\x09\xc0\x2b\xe1\x75\x9c\xa0\xb2\x92\x29\x08\x7a\x9c\x60\x3e\x24\xe8\xb5\x04\x3d\x4e\x2b\x97\x04\xbd\x11\x77\xe8\xe3\x51\xd6\x3e\x05\x41\x9f\x13\x64\x43\x82\x7e\x4b\xd0\xe7\xb4\x98\x24\xe8\xab\x04\xd9\x38\x41\x65\xb5\x54\x10\x0c\x38\xc1\x62\x48\x30\x68\x09\x06\x9c\x56\x21\x09\x06\x2a\xc1\x62\x9c\xa0\xb2\xbe\x2a\x08\x86\x30\xa9\x18\x12\x0c\x5b\x82\x30\x7a\x3f\x91\x04\xc3\xce\x24\x62\x9c\xa0\xb2\x22\x2b\x08\x46\x9c\xe0\x74\x48\x30\x6a\x09\xc2\xb4\x49\xf6\xc9\x1c\xde\x36\x08\xf8\xe2\xbb\x17\xdf\x1e\xc5\xb9\xb9\x47\x71\x30\x1f\xdc\xcb\x97\xcd\x38\x32\xc8\xc3\xe2\xb9\x37\xfd\x2c\x8e\x9e\x0c\xfe\xa7\x7c\x18\x67\x7f\x31\xff\xc4\x96\x22\xcb\x2f\xaa\x16\xc8\x23\xf7\xd2\xb2\xe2\x03\x94\x1c\x25\x70\x3e\x3b\x65\xc5\x62\xc9\xe4\x71\xea\x81\xd6\x94\xbb\x26\xca\xde\x5d\xb5\xf8\xd9\x23\x37\xf1\x10\xcf\x1f\xf5\x09\x1e\x95\xcf\x26\x3f\xc8\x7d\x84\x5d\xe2\xef\x7a\x32\x4f\xf1\xb7\xdb\x4d\xc6\xab\x4a\x01\x26\x57\xbd\xdd\xc4\xab\x8c\xdc\x6e\xea\x1c\x6b\x18\xdc\x6e\x0a\x30\xf9\x76\xbb\xe9\xa6\x6f\x37\x71\xad\x6c\x76\xbb\x49\xab\x9c\xce\xed\x26\xa1\x20\xeb\xed\x26\x71\x8f\x76\xc3\xdb\xdf\xe4\x0f\x7d\x9f\x89\xcd\xb3\x7b\x69\xb2\x62\xa1\xdf\x2b\x38\xcd\x83\x3e\xe8\xa7\xb3\x8f\x79\xd1\xfb\x31\x2b\xcf\xa6\x6c\xf9\xbb\x5c\x89\x52\x58\x85\xef\x9c\x43\x51\x20\x18\x83\xcf\x2a\x3f\xff\x0a\x57\xa7\xde\x6c\xf4\x26\x10\x1c\x9e\xd9\x87\xa6\x37\x70\xca\x6f\xe3\x57\xa1\x76\x77\xd1\x2b\xb6\x3c\x85\x5e\x74\x7f\xba\x28\x33\x86\x70\xff\xd9\x14\x5e\xfd\xd5\x3e\xee\xde\x5d\x0a\x22\x07\xf9\xb1\x83\x7c\xec\x20\xcf\x73\x10\x09\x1c\x84\x23\x07\xc5\x0e\x42\x58\x39\x6a\x14\x50\x07\x05\xae\x83\x7c\xe2\x20\xcf\x77\x10\x09\x1d\x84\xa9\x83\xb0\xeb\x20\xa2\xc2\xc5\x0e\x0a\xb0\x83\x7c\xcf\x41\x5e\xe0\x20\x12\x39\x08\xc7\x0e\xc2\x1c\xbf\x02\x17\xba\x0e\x0a\x88\x83\x7c\xdf\x41\x5e\xe8\xa0\xd0\x73\x50\x10\x38\xc8\x8f\x1c\xe4\xc5\x0a\xa0\x87\x1d\x44\x3c\x07\xe1\xc0\x41\x91\x83\x50\x48\x1c\x14\xf8\x0e\xf2\xe1\x69\x01\x15\x90\x73\x42\x1c\x84\x7d\x07\x85\x1c\x10\x3b\x28\xf0\x1c\xe4\x07\x0e\xf2\x22\x05\x90\xc4\x0e\x22\xd8\x41\x98\x93\x74\x10\x22\xd4\x41\xc4\x75\x10\xe6\xec\x08\xb0\x77\x16\xb9\x12\xbd\x5c\x49\x57\xae\x9c\x0b\x2e\x47\xde\x6e\xc2\x3f\x3b\x08\x05\x2a\xb7\x92\x30\x6f\x16\xe7\x16\x18\x72\x55\x2e\x3d\x29\x38\xce\x15\x07\x08\x1d\xa4\x36\x17\x87\x42\x1e\x5c\xc0\xc0\xbd\xd7\x55\x04\x57\x28\x17\x30\x97\x9f\x17\x09\xc1\x06\x41\x4f\x5e\xbe\x2b\xb5\x15\x08\xed\xfb\x2a\x05\xae\x1a\x6e\x1a\x1e\x57\x69\x28\xd4\x1e\xa8\x3a\xe4\x2a\xe0\xf6\xc0\xed\x82\xeb\x90\x0b\xb6\x1e\xd5\x74\x5e\x84\x3a\x3f\x3d\x9f\x25\xf0\x4c\x0a\x1f\x54\xae\xa6\x65\x31\x78\xe1\x09\xbc\xe0\xd9\xf1\x87\xd7\x87\xcf\x9e\x8a\x37\xa5\xb8\xc4\x88\x83\xa0\xf1\x5c\x42\x94\x5b\xa4\x54\x13\x48\x57\x5a\x2a\x96\xea\x24\xd2\x7a\x41\x20\x54\xa5\xff\xfa\xf1\xcb\x9f\xd9\x0a\x25\xf3\x5c\xe6\x46\x3f\x03\x95\x8a\xf7\x34\x34\x7c\x70\xf8\x0f\xaf\xba\xfa\xec\x0d\x29\xdd\xb5\x7b\x1f\x26\x23\x94\xb8\xae\xd3\x2f\xab\xe7\x0a\x02\x44\x03\x40\x3a\x00\xd4\x75\xc9\x00\xc4\x53\x40\x86\xa5\xbe\x5a\xaa\x21\x10\x74\x09\x10\x0d\x81\xb0\xcb\xa4\x0e\x24\xea\xb5\x43\x43\x88\x76\x18\x19\xa2\x88\xfb\x54\x86\x28\x12\x15\x44\x07\x90\xf6\xa5\x35\x04\xc9\x7a\x64\x06\x00\x79\xbf\x29\x43\x10\xa6\x80\x0c\x29\x14\x5d\x2e\x87\xd5\xa9\xad\x36\xa6\xa3\xfa\x20\x74\x84\x80\x47\x47\xac\xca\xef\x13\xd1\xd8\x05\xb5\xdb\x4d\x48\x47\x0d\x33\xa2\x36\xc3\xa4\x74\x54\xdf\x31\x1d\xd1\x77\xd2\x67\x42\x63\x12\x7d\x32\x43\x4e\x32\x3a\xaa\xf1\x9c\x8e\x58\x0d\xa3\x76\xeb\x2e\xfa\x34\x34\x9a\x37\xaa\x4b\x46\x09\xac\x17\x24\x51\x4a\x0d\xca\xf4\x3a\x20\x5a\xea\x7e\x17\x8b\xae\x8d\x81\x0a\xa2\xb5\x09\x95\x4f\x4d\x79\xd4\x65\xc3\xe2\x1b\xd8\x62\xfe\x71\x9f\x53\x63\xa0\xc0\x16\x8d\xa6\xdd\xc6\x68\xac\xa2\xd3\x18\x63\x9c\xc0\x16\xfb\x65\x3d\x10\x53\xa8\xc0\xfa\x50\x40\x47\x45\x81\xe9\xa8\x28\x08\x1d\x55\xbd\x47\xed\x6a\xf3\x7b\x28\x4c\xb1\xc2\x26\xee\x90\xda\x4c\x38\xa2\x23\xca\xa0\x74\x44\x92\x31\x1d\x35\xad\x84\xda\x15\x9a\xf6\xe5\xad\xe9\x3c\xfa\x54\x86\x20\x39\xb5\xa9\x94\xd1\x11\x17\x2a\xfa\x1a\x55\xdf\xa8\x72\xc6\x46\x19\xbe\xeb\x52\xdf\xc5\xc6\x08\x22\x61\x8c\xc3\x8c\x46\x81\xa6\x08\x52\x13\x71\x75\x44\xfc\x2e\x11\x2d\x4c\xd0\xc5\xa3\x65\x26\xec\xe2\xd1\xc2\x44\x2d\x8c\x86\x8a\x1a\x6c\xb5\xd5\xe3\x3e\x09\x0d\x92\xa4\xdf\x1c\xf3\x80\x43\x12\xd2\x20\xc9\x3a\x82\xd5\x00\xe4\x2d\x80\x31\x80\x08\x16\x34\x95\x8b\xbe\x56\x8c\xe3\x2e\xab\x30\x31\x1d\x69\x05\xa1\x36\x69\x7b\x7d\x12\x3a\xdb\xa0\x3d\xbd\xeb\x6c\x83\x8e\x0b\x3c\xa4\x23\x86\x1a\xd1\x71\x43\xa5\x74\x44\x29\x31\xb5\x28\x25\xa1\x76\x5f\x4a\xfb\x1c\x98\x03\x89\xd5\x55\x72\x3a\x62\xc4\xac\x2f\x53\x73\x3c\x31\x5a\x90\x3a\x01\xd1\x94\xe2\x0d\xdc\x1e\x93\x0d\x9c\x09\x7b\x1b\x38\x3e\xf6\x37\xb0\x67\x1c\x58\x5d\x1f\x87\x63\x2e\x89\xa3\x91\x60\xa8\x0e\xc1\xf5\x18\xe2\xb1\x70\x89\x93\x31\xbf\xc7\xe9\x06\xd1\x12\x67\x63\x81\x0c\xe7\x1b\x04\x4b\xcc\x36\x08\x65\xb8\xe8\x6b\x48\x6b\x2e\x63\xa1\x02\xe3\x31\x0f\xc5\x64\x03\x07\xc1\xde\x88\x97\x61\x7f\x93\xc0\x16\x6c\x10\x76\x70\x68\x8d\x6e\x38\xda\x20\x2c\x61\xba\x81\x2f\xe2\x78\x03\xaf\xc7\xc9\x06\xd1\x14\xa7\x63\x11\x0c\x67\xb6\x10\x86\xf3\xb1\xb0\xc0\x36\x08\xa3\xb8\xe8\x45\xa8\xab\x0c\x55\xb0\xeb\x1b\x82\x91\x9e\x65\xd2\x91\x0a\x36\x0e\x51\x04\x6e\x1d\x76\x5f\x29\x77\x35\xe5\x41\x4f\x39\x43\x88\xb0\x23\x34\x1d\x8d\xa8\x03\x31\xde\x1d\x9b\xc7\x26\x2d\x15\xd3\xc8\xa4\x6e\xa9\x69\x54\xd2\x72\x31\xe4\x33\xeb\x49\x73\x08\x91\x77\xa4\x65\x1a\x9a\x00\x06\xc3\xb0\x44\xd6\xd5\x4b\xc0\xd6\x3c\x4c\xc7\xd8\x27\xd4\x6c\x28\x1e\x1d\x33\x14\x9f\x8e\x29\x3a\xa0\xf6\xc6\x87\xd4\x6e\x4a\x91\x52\x3e\x2c\xa5\xd4\x2c\xba\x98\xda\x44\x97\xd0\x31\xf3\x4a\xa9\xdd\x09\x32\x6a\x37\x9d\x9c\x8e\x19\x06\xa3\x63\x4e\x50\xd0\x31\x13\xef\x0c\x2b\x0c\x46\x80\x47\xdc\x15\x93\x11\x0b\xc5\xde\x68\xc8\xc0\xbe\xd5\x52\x71\x30\xea\xf0\x38\x1c\x8d\x1a\x38\xb2\x45\x62\x3a\xea\x89\x38\x1e\x0d\x19\x38\xb1\x78\x23\x4e\x47\xc2\x05\xce\x46\xa3\x16\x56\xc3\x81\x86\x04\x1b\x89\xbd\xb8\x18\x0d\x49\x72\x68\x61\x6d\x26\xb6\xfa\x15\x26\xe3\xa1\xc5\xb3\x44\x0e\xec\x8f\xb8\x35\x0e\x46\x63\x0b\x0e\xad\x0e\x8c\xa3\xd1\xd8\x86\xe9\x48\xf0\xc1\xf1\xa8\x07\xe2\x64\x24\x0c\xe0\x74\x34\x06\xe2\x6c\x34\x14\xe0\x7c\x34\x1e\x61\x66\x09\x76\xb8\xe8\x46\xa3\xab\x8c\x1f\xa8\x2b\x48\xea\x63\x4b\x3d\xfa\xc4\xae\x6f\x18\x4a\xd4\x4c\x6b\xca\xbd\x16\x83\xaf\x37\x44\xdf\x6c\x44\x41\x57\x22\xfa\x31\x44\x33\x38\xd6\x91\x8f\xdc\xce\xf0\xcf\xdc\x7f\xd6\x3b\x2a\xfa\x11\x44\xab\x5b\xfd\xf8\x41\x94\xeb\xc7\x0e\xad\xf8\x4c\x3b\x28\xad\x78\x34\x38\x72\xc5\x4b\x0d\x23\x87\xda\xbc\xf5\x63\x87\x56\xc1\x86\xf6\x5b\xf5\x8b\xa9\xb9\x79\x84\x8e\x31\xef\xd1\x31\x01\xf8\xd4\xae\xe2\x80\x8e\x35\x21\xa4\x46\xfb\x89\xe8\x98\xf1\x51\x6a\x93\x5f\xdc\x25\x6e\x1a\x44\x58\xac\x23\xa5\x36\xed\x65\x74\xcc\xfa\x72\x6a\xb7\x5f\x46\xed\xee\x57\xd0\x31\x0f\xc1\xee\x88\x8b\x60\x3c\xe2\x85\x98\x8c\xba\x21\xf6\x6c\x3d\x85\xd5\xc2\x71\x30\xea\x22\x38\x74\xc7\xf4\x84\xa3\xd1\x48\x86\xe9\xa8\xb7\xe0\x78\x34\x5c\xe0\x64\x34\xe0\xe1\x74\x24\x66\xe2\x6c\x34\x6e\xe0\x7c\x24\x2c\x61\x66\x89\x4b\xb8\xb0\x86\x0d\x31\x7a\xb0\xb7\x01\x8f\xfa\x25\x26\x66\xc7\xc4\xde\x88\xdb\x63\x7f\xc4\xf0\x71\x30\xea\x3b\x38\x1c\x8f\x6e\x91\x25\xbc\x61\x3a\xee\x3c\xb1\x35\x7e\xe0\x64\x34\xfe\xe1\x74\x34\x88\xe2\xcc\x1a\x44\x70\x3e\x1a\xa5\x30\x1b\x09\x53\xb8\xe8\xc6\x91\xab\x0d\x1e\xb4\x31\xa5\xe6\xd7\xb4\x43\xd2\x70\xa3\x1d\x32\xdc\x57\x8e\x6b\x68\x47\x0c\x12\x00\xd6\x53\xb4\xe3\x86\x66\xcc\xa7\x29\x0f\x6b\x04\x26\x80\xa8\x65\x50\x53\xaa\xea\xdc\x34\x64\x68\xf9\x33\x8c\x19\xda\x16\x6a\x28\xa4\x2d\x83\x7a\x16\xb2\x0e\x80\xae\xe3\x30\xfa\x1e\x53\x95\xa3\x41\x5d\x74\x84\xa3\x5f\x73\xb0\xd5\xc7\x74\x44\xb8\x84\xba\x26\xc3\xf1\xa8\xdd\x70\x7c\x6a\x33\x9c\x80\x8e\xd8\x45\x48\x47\xa4\x16\xd1\x11\xd3\xa3\x74\x44\xb5\x31\x35\xc9\x3d\xa1\x23\x3a\x4d\xa9\xdd\x6a\x33\x3a\x62\x35\x39\x1d\xd1\x1c\xa3\x76\xc3\x2d\xa8\xcd\xec\xb1\x6b\x75\x5b\x8c\x5d\xa3\x5e\x31\x19\xf3\x69\xec\x8d\xf9\x24\xf6\x47\xbc\x1a\x07\x63\x4e\x81\xc3\xb1\xc8\x81\xa3\x11\xdf\x6e\xfa\x3d\xa3\x1a\x71\x3c\xe6\x40\x38\x19\x89\x8f\x38\x1d\x8b\x20\x38\xb3\x46\x28\x9c\x8f\x45\x18\xcc\xcc\x9d\x73\x31\x12\x21\x60\x7c\x60\xd7\x15\x1e\xb1\x34\x4c\x46\x3c\x1d\x7b\x63\xce\x8c\xfd\x31\x67\xc5\xc1\x58\xa8\x0a\xcd\xa1\x08\x47\x63\xc1\x02\x53\xbb\xbb\xc4\x63\x0e\x8f\x13\x63\xb0\xc0\xe9\x98\x2f\xe3\x6c\x24\x5c\xe0\xdc\x1a\x2c\x31\x1b\x0b\x65\xb8\xe8\x05\x9c\xab\x8c\x0a\x24\xdb\x54\x17\x45\x6a\x9c\xba\x71\x81\xa8\x4b\xf4\x6d\xf6\xda\x72\xa2\xc3\xed\xb7\x12\xd1\xe2\x0f\xd4\xf6\xe8\x46\x05\x4d\xe9\x10\x77\xd4\x31\x68\x63\xaf\xa8\x1d\x0d\x28\x4c\x0d\x11\x27\x35\x59\x2d\xcb\xa9\x34\x50\xdd\x08\x40\x91\xd5\xb0\x3c\x57\xd0\x0e\x4b\x59\xd3\xd6\x61\x59\xd1\x91\xb2\xae\xa5\x56\x25\x61\x6a\x57\x12\xa1\x86\x16\x79\xd4\xa6\x1d\x9f\xda\xda\x13\x50\xbb\xd5\x85\xd4\x6e\x19\x11\x35\xcb\x83\x52\x9b\x5d\xc4\xd4\x6c\xcf\x09\xb5\xab\x3e\xa5\x76\x1d\x66\xd4\x60\x53\x39\xb5\xab\x88\x51\x9b\x4d\x15\xd4\x6e\xca\xd8\x1d\xf1\x23\x8c\x47\x8c\x0f\x93\x11\x4f\xc5\x9e\xc5\x00\xb1\x6f\xf5\x53\x1c\x8c\xb8\x22\x0e\xdd\x91\x18\x14\x59\x7d\xae\x19\xc1\x1a\x78\x8f\x8d\x51\x3b\x31\x79\x2b\x4e\x47\x42\x1b\xce\x2c\x71\x11\xe7\x23\x31\x04\xb3\x11\x9f\xc5\x85\x35\xb8\xf1\x1e\xdd\xc0\x38\xb6\x9a\x12\x26\x56\xa7\xc5\xde\x88\x5f\x62\x7f\xc4\x31\x71\x60\xf1\x4c\x1c\x8e\xc4\x1a\x1c\x8d\x06\xab\x11\x4f\xc2\xf1\x88\x8f\xe2\xc4\x12\x00\x70\x6a\x8d\x5a\x38\xb3\x86\x16\x9c\x9b\xfc\x1f\xb3\x31\x17\x2e\xba\xa1\xe7\xea\x5d\xb7\xc6\x46\x6a\x56\x7d\x17\x6b\xba\x6e\x39\xd4\xd0\x74\xda\x12\xa9\xae\x9a\xdf\x0c\x72\x74\xa5\x81\xa1\xf9\xa1\x40\xa9\xe9\xa3\xdb\x21\xd3\xb0\x94\x2a\x0d\xd0\x75\xd3\x4d\xdb\x87\x55\x13\xc5\xc8\x87\xa5\xa9\xd2\x08\xdd\x54\x5d\x19\xc7\x69\xba\x69\x21\xb7\x21\x56\xd6\xca\x4d\x37\x49\x57\x46\xbe\xc3\x96\xda\xc4\x80\xa9\x5e\xa8\x84\xda\xf4\xeb\x51\x5b\x1b\x7d\x6a\x31\x9c\x80\xda\x84\x17\x52\x5b\x4b\x22\x6a\x12\x0f\xa5\x16\xb3\x8a\xa9\x4d\xd5\x09\xb5\x69\x24\xa5\x16\x43\xc8\xa8\xc9\xcc\x73\x6a\xb3\x64\x46\xf5\x16\x5b\x50\x8b\x92\xb1\x6b\xd5\x32\xc6\x56\x77\x25\x56\x7f\xc5\x9e\xd5\x57\xb0\x6f\x73\x07\x1c\x58\x5d\x09\x87\x56\x87\xc0\x91\x2d\x22\xc8\xfe\x46\x5b\x14\x5b\xa3\x05\x4e\x6c\x1e\x83\x53\x43\xd0\xc0\x99\x29\xc8\xe6\x56\xcf\xc5\xcc\x1a\x14\x70\x61\x8c\x88\xd8\xb5\x6a\x1d\x5b\x1d\x11\x13\xbb\x77\x7b\x06\x4b\xc3\xbe\xd5\xd1\x70\x60\x73\x61\x1c\x1a\xfd\x10\x47\xd6\xc8\x80\xa9\xd5\xfb\x71\x6c\xf5\x45\x9c\x18\x82\x15\x4e\xad\xee\x86\x33\x5b\x74\xc0\xb9\xd1\x8b\x31\xb3\x46\x0e\x5c\x28\xc1\xe1\x2a\x7d\x2a\xe5\x1d\x3c\xd1\x20\x6c\x84\x33\x8c\xc7\xf7\xdb\xcd\x8d\x61\x38\x16\xf5\x86\x81\x58\xe2\xd3\x14\x05\x02\x1f\xd1\xf2\x11\x36\x85\xba\x20\x2c\x39\xd1\xf7\x33\xd4\xd5\xf3\x1f\x37\xed\xd6\x85\x60\xc1\xa7\xae\x28\x6d\x90\x6a\xf8\xcc\xee\x8b\xcb\x1e\xc3\xf0\xab\xb7\x13\xd6\x08\x51\x53\xa7\x90\x4c\x68\x8a\xea\x4d\x25\x63\xcb\x45\x31\xb6\xc9\x54\xc2\x10\x9b\xfe\x25\x8c\x67\xd3\xb5\xfc\xdd\xb7\x09\x5b\xc2\x04\x66\xb1\x4a\x88\x70\xb4\xcd\x91\xc1\xb4\x64\x31\xb5\x49\x54\xc2\xc4\x26\x2d\xc9\xf2\xc4\x6c\xa5\x12\x22\xb5\xd9\xa3\x84\xc9\xf4\x2a\x97\xa5\xb9\xcd\x8c\x24\x0c\xb3\x99\xa8\x84\x29\xcc\x1e\x5a\x8f\x88\xb5\x8e\x8d\x6d\x2d\xc0\xc4\x20\x64\xec\x99\x2c\x0e\xfb\x36\x66\x71\x60\x53\x0b\x0e\x6d\xc2\xc0\x91\xa5\x89\xa6\xf8\x1b\x9b\x55\x88\x13\x9b\xa5\xe2\xd4\x1a\x0f\x33\x9b\x47\xe1\xdc\x6c\xdf\x98\x99\x8c\x0e\x17\xe3\xde\xd5\x4e\x6e\x8c\x10\xd8\x1e\x0b\x30\x19\x37\x38\xec\x8d\x79\x1f\xf6\xad\xde\x87\x83\xf1\x20\x50\x2b\xdb\xda\xdc\x68\x3c\x28\x61\x3a\x1e\xdc\x70\x3c\x1e\x0d\x6a\x73\xb0\x79\x99\x30\x0a\x63\x69\x36\x16\xd6\x84\x61\x58\xf8\x64\x63\x11\xa7\x36\x12\xa0\xa2\xf4\xec\xe2\xa3\x9a\xd7\xe0\x45\xb2\xfa\xb8\x42\xd5\x34\xa9\xd0\x8a\xcd\x58\x56\x41\x3e\xa2\xd7\x8f\x5f\xfe\x8c\xca\xf9\x59\xfd\x4c\x44\x93\xd1\xe0\xc5\xa3\xd7\xbd\x87\x8b\xdb\x8b\x89\x0e\x6a\x0f\xfe\xc3\x03\x8a\xf2\x0b\x7c\x96\x5f\x1c\xb5\xa2\x2b\x7f\x15\x00\xe2\x4b\xfd\x99\x7f\x71\x94\xf6\xf4\x39\x57\xb2\x2a\x3d\x39\x78\x2d\x12\x63\x21\x91\xf8\xc5\xfe\x46\x15\x87\x6e\x1e\xa8\x12\x5f\x94\x2c\x29\xd7\x7d\xa2\xca\x9e\x5a\xef\x23\xbb\x6c\x52\x80\x7d\x64\x97\x9a\xd4\x77\x1f\xd9\x65\x9d\x57\xef\x23\xbb\xd4\xa7\xd5\xe3\x34\x84\x8a\x82\x10\xa5\x65\xb5\x42\x49\x96\x2d\x96\x79\x39\x3f\x41\xd5\x02\xbd\xda\xc7\x5a\xbc\x8f\x4b\x48\x05\xf4\xb6\x9f\x03\x59\xf7\x76\x48\x10\x9a\xdf\x0e\x69\xd1\xbd\x5a\x70\x84\xaf\xf6\xf1\xdb\xf2\x1d\xba\x87\xb0\x26\x47\xa9\xa4\x2b\xd2\xf3\x4f\xea\xd6\xbd\x6d\xeb\xcb\x74\x7c\xfc\x3f\x13\x0f\xa3\x7b\x0a\x6a\xc8\xc3\xb7\x85\x6e\x0f\x10\x6b\x12\x96\x3e\x5a\xad\xd8\x69\x3a\x63\x08\x87\x68\x75\x9e\x7e\x64\x97\x1a\xf1\xaf\xce\xd3\x1f\xd8\xe5\xaa\x51\x41\xfb\xdd\x2c\x94\xf9\x6b\x00\x12\xa2\xa9\xbf\x3c\x44\x38\x6c\xbe\x99\x9f\x58\xd9\x87\x8c\x53\x92\x1f\xbd\x20\x57\x35\x76\xc9\xcb\x5b\x89\xf4\x9d\x64\x4a\x8b\xd7\xfe\x74\x4b\x5a\x56\xaf\x21\x2b\xca\x9e\x92\x04\xa5\xc1\x6b\x42\x29\x0c\xca\xa7\x5a\x83\x22\xc3\x3a\x3a\xab\x21\xbe\xd9\x6a\xba\x74\x8a\xe5\xe2\x14\x02\xcc\x8c\x15\x15\x22\x14\x3c\x83\x53\xd6\x57\x14\xc2\x79\x3b\x29\xd1\xae\x78\x1b\xc2\x85\x04\x8e\xb5\x71\x4d\x26\xaf\xf6\x89\xb4\xc1\x2d\xb4\xdd\x48\x60\x0b\xfd\x05\x11\xfa\x0e\x72\x3c\x82\x6d\x95\xe8\x2f\xf0\xc6\xc5\xc6\xec\x2d\xcb\x93\xe9\xe6\xfc\xf9\x90\xbe\xb3\x65\x72\xab\xc3\x25\xa1\x50\x2c\x78\x45\xdb\x88\xf8\x06\x86\xb7\x34\x1c\x0f\xc8\xea\x32\xfb\xf3\x06\x94\xf3\x8c\x21\x96\x64\x53\x69\x76\xa8\x5c\xa1\xe4\xec\x6c\x56\xb2\x9c\xeb\x32\x99\x23\xb6\x3e\x4b\xe6\x39\xcb\xeb\xbc\x8c\x10\xde\x1d\x2d\x36\x2e\x02\x89\x26\x4b\xe6\x28\x65\x28\x5d\x2e\x3e\xb2\x39\x2a\xe7\xd5\x02\x51\x91\x14\x78\x85\x56\x59\x32\x13\xe8\x05\xca\x95\x1e\xdb\xc5\xb4\xcc\xa6\x28\x99\xcd\x16\x17\x2b\x40\xcd\xf1\x56\x0b\x8e\xf6\x7c\xc5\x72\x74\x51\x56\xd3\xc5\x79\x25\x18\x5c\x95\x8b\xf9\x10\x8b\x14\x34\xa4\xd7\x9c\xb4\x5f\x1e\x3e\x94\xcf\xca\xb4\x3f\xf1\x80\xe2\x61\x9d\xe4\x3a\x96\x8b\x85\xe5\x46\x76\xc3\x95\x68\x21\x88\xb5\x9f\x21\x66\x4d\x4a\xa1\xc4\xbb\x88\x6b\xdf\xd3\xab\xca\xd4\x8e\x48\x6d\x47\xf4\x4e\x26\xf6\xfc\x4d\xfd\x09\x1e\x05\x18\x3c\xb5\xa3\x89\x80\xfb\x22\xf1\x25\x2a\xe7\x9f\xd8\x72\xc5\xcc\x51\xb0\x9c\x7f\x7a\xdd\x0b\x84\x9d\x9f\x36\xea\x20\xb0\xa5\x83\x68\xb1\xa9\x12\x5b\xbd\xc5\x01\x37\xe8\x3e\xf6\xcf\x9d\x05\x87\xf6\x0b\x9b\x67\xcb\xcb\xb3\xea\x0a\x4f\x01\xca\x8c\xb5\x8b\xfd\xa6\x5e\x0b\xec\x74\x43\xbe\x31\x85\x6e\xce\xbe\x06\xd5\x56\x22\xb6\xdc\xbd\xfb\x76\xca\x4e\x2d\x48\xdd\xa0\xe3\xaf\xac\x52\xc7\x69\x5d\xe6\x66\x80\x54\x79\x1a\xab\xaf\x03\x01\xb6\xec\x83\xc1\xcb\x59\x9a\xd1\xc7\xb3\x79\x59\x95\xc9\x4c\x4d\x7d\xd5\x85\x61\xeb\x6c\x9a\xcc\x4f\xd8\xf3\xa3\x36\x2d\xaa\xc8\x3c\xe6\xae\xdd\x42\xfc\xaf\x6f\xd2\xfa\x3a\xe2\x7d\x6a\x98\xb1\x16\x85\xb1\xce\xd1\x73\xb5\x0e\x01\x3a\x9e\xfc\xdb\xac\x0e\x15\xbc\xb9\x45\xc1\xff\xbf\x21\x6f\x50\x27\x90\x7f\xda\xcc\xb4\xb6\xa7\xda\x44\xfa\x30\xf0\x28\xf1\x51\x78\x15\x7c\x1e\x7f\xb6\x4d\xd3\x13\x69\xc7\x13\x80\xce\xf4\xec\x45\x63\x18\xaa\x9d\x18\x60\x97\x5d\xd8\xa5\x84\xd5\x32\xf9\x94\x95\xab\x8a\xcd\x1a\x2b\xd6\x63\x2c\xa0\xf1\x9b\x0d\x2d\xa8\x3d\x40\x17\xbc\xa3\x15\xa9\xd6\xde\x96\xef\xde\x4e\x26\x92\xdb\xf7\x6d\xb8\xe6\x03\xc9\x66\xea\x02\xdf\x21\xad\xb6\x4e\x34\x9a\x80\xdd\x73\xa4\xa5\x49\x52\x3d\x4f\x9a\xd5\x6c\x14\xe3\x01\xfc\xa7\x79\xbe\x40\xab\x8b\xe4\x4c\x0c\x3f\x66\xc9\xaa\x12\xc6\x30\x0c\xe1\x95\x5d\x65\x3d\x66\xbb\x0a\xb3\x39\x7e\xa5\xb1\x61\xc8\x28\x7e\x55\x57\x1f\xb8\xc6\x8d\xb9\xe0\x75\x5c\xfd\x3a\x21\x65\x24\x74\x69\x66\x64\x15\x5a\x9c\x57\x83\x08\xdc\x84\x5c\xbb\xca\x3a\x21\xd7\xac\xb3\x4e\x97\xf1\x91\x5d\x8a\x14\xd0\xa1\xbf\xeb\x11\xb5\xa4\xfc\x64\x28\x50\xf2\x46\x87\xda\xac\xd1\xbb\xe8\x35\xb7\x40\x39\x09\x58\x2e\x56\xab\x76\x98\x0e\x39\x0f\x61\x40\x0c\xd3\x52\x51\xa3\xe9\xa8\x5a\xc1\x4d\xea\xfe\xea\x34\x59\x7d\xec\xb8\x6c\x6d\xbb\x93\x49\xc7\x44\xb9\x23\xd6\xbd\xeb\xfb\x4e\xd3\xb9\xd3\x72\x2c\x8a\x08\x3a\x26\xfb\x1e\x6c\xf6\x3b\xad\xe1\xf3\x32\x3e\xa2\x12\x98\x25\x54\xed\x77\x03\xb6\x8f\x9e\x6f\xce\xf6\xd2\xcc\xf6\xcc\xce\xf6\xcc\xc2\xf6\x72\x03\xb6\xad\x49\xa4\x57\x75\x16\x69\xb1\xfc\xb1\x59\x1e\xe9\xb1\x24\xcc\x02\x57\xc5\xd6\x95\x9a\x8a\xf9\xc9\xc1\xeb\x1d\x39\x40\xeb\xe4\x62\x76\x50\x56\x9c\x68\x92\x6b\x9f\xcd\x12\xce\xc4\xba\x42\x7d\x2c\x72\xc0\x35\x69\xe9\xe8\x10\x35\x99\x9d\x87\x0b\x35\xdd\xa4\xdb\x4f\x0e\x5e\x6b\x33\x6e\x1f\x2f\xcb\xb3\x19\xbb\x77\xb5\x25\x22\x51\xa9\xb3\x50\xa4\xfe\xf4\xc7\x59\x2e\x92\x0b\x11\x9c\xed\x12\x32\x94\x66\xfd\xe7\x81\xe4\x28\x96\xad\x30\xda\xe3\x70\x3b\x42\xaa\x07\x42\xc7\x8b\xe5\xa4\x7d\x67\x5d\x3e\x1c\x5f\x93\xde\x59\xcd\xca\x8c\x4d\x5c\x07\x91\xad\xc1\x5b\x18\x0d\x5a\x72\x4d\xb4\xc4\x41\xbe\x05\xad\x77\x4d\xb4\xbe\x83\xc2\x2d\xf3\x43\x1a\xd7\x9e\x7b\xb0\x15\xde\x51\x2b\x2b\x35\x8c\x92\xd9\x51\xe7\x1c\x1b\x54\xf0\x36\xa0\x70\x33\x73\x1a\x4e\xeb\x8a\xcc\x91\xab\x36\x1f\x6f\x40\x41\xdf\xeb\xe1\x98\xdc\x58\xb7\xf7\x4f\x12\x56\x9b\xe8\x72\x03\xc1\xb5\xc5\x75\xc5\x10\x6b\x0a\x71\xdd\x40\xdb\x40\x59\xf3\xe7\x37\x50\xbd\x14\xfa\x4a\x62\xf6\xfb\x01\x76\x7a\x59\xf5\x95\xe4\xee\xf7\x03\xcf\x69\xb3\xba\xdf\x0f\x02\x47\x26\x7b\xbf\x1f\xba\x9f\xdf\x39\xd4\xfb\xa2\x84\xfb\xbf\x67\xa6\xfd\xaf\x96\x0f\xff\xbf\x27\xb3\x3d\xbc\x54\x50\xce\x59\x7e\xb3\x29\xee\x1f\x27\x2b\xd6\x66\xad\x4f\x56\x4c\x29\xfb\xd9\x23\xd6\x0c\xf8\x43\x5f\x5e\x87\x3e\x9a\x27\xa7\x6c\x75\xa6\x7a\xe9\xae\xca\x06\x07\xe1\x6c\x88\xff\xfe\xfd\xb3\x0e\xcd\x23\x14\xfa\xcd\x13\x36\x3a\x34\x3f\x87\x3e\xe7\x03\x98\x5a\x87\xfe\x8e\xfc\xc2\xf9\xd7\x8c\x0c\x5a\xd4\x02\xbd\x5c\x4e\x29\x7f\x65\x2b\x94\xa0\x39\xbb\x98\x5d\x22\xe1\x6b\xb9\x8e\xb0\x1a\x50\x50\xe7\x35\x8f\xf9\xf9\x69\xca\x96\x9f\x11\xbc\x2a\x05\xaf\xaa\xf0\x0f\x1e\x81\xe1\xfc\x8e\xb5\xca\x6c\x71\x01\x35\xf8\x7f\x75\x15\xba\x95\xbb\xd1\x6d\x08\x50\xcb\x65\xdd\xca\xa5\x8e\x08\xb5\x78\xea\x8e\x59\xec\xfe\xb9\xc4\xf5\x60\x56\xe6\xbb\x81\x1b\xba\xdd\xf5\xce\x5a\xd2\x10\xe2\xe7\x65\x67\x44\xc5\x5b\xe8\x70\xae\xf5\x7d\x98\x7c\x5f\x4b\xf3\xaa\x27\x14\xf3\xd6\xee\xa1\xee\xeb\xdb\xea\xcc\xbc\xaf\xa9\xc7\x65\x75\x51\xae\x18\xfa\xf1\xe5\xf1\x0a\x30\x8c\x29\xa6\x7e\x28\x45\x1a\xc8\x67\xf4\x88\xeb\x97\xcb\xe5\x1e\x08\x46\xf6\x24\x49\x51\xb1\x25\x9a\xb3\x93\xa4\x2a\xe7\x27\x37\x20\x78\x40\xc5\xb8\xe0\xa5\x0a\x76\xe6\x8b\x6a\x62\x94\xea\xee\x2e\x9a\x2f\x46\x47\xaa\xf0\x26\x8b\x10\xe8\x3f\x1a\xe9\x3e\xd0\x82\x09\xc1\xfe\xa3\x16\xb2\x66\x48\x2a\x25\x23\x05\x53\x5b\x43\xab\xce\x07\x1d\xee\x3a\x23\x00\x93\x56\x1e\xfd\xf8\x44\xd1\x0a\x6c\x27\x40\xbf\x7d\x96\xac\x60\x7b\x61\x23\x1f\x6a\x34\x05\x38\xb8\x4b\x34\xca\xaa\x16\x9c\x44\x8d\xf7\x86\x95\xff\xe8\xc7\x27\x37\xa3\x7a\xb1\xb7\xd3\x2a\x3e\x99\xe7\x93\x64\xbe\xa8\xa6\x6c\x29\x19\xb1\x99\x41\x32\xcf\x55\x33\xe0\x2d\x1c\x31\x85\xd6\xcf\x6e\x0b\x81\x8c\x59\x45\xe3\x79\x12\xfe\x77\xb3\x8f\x97\x47\x5f\xdb\x3c\x5e\x1e\x7d\x25\xeb\x78\x79\x74\x33\xc6\xb1\x58\x76\x6c\x63\xb1\xbc\x82\x69\x2c\x96\xd7\xb6\x8c\xdf\xae\x68\x19\xbf\xfd\xce\x96\xf1\xf3\xd7\x37\x8d\x9f\xbf\x9a\x6d\xfc\x7c\x53\xc6\xb1\xee\x59\xc7\xfa\x4a\xe6\xb1\xfe\x02\xfb\x78\x7f\x45\xfb\x78\xff\x3b\xd9\x07\x6c\xca\xab\x96\x31\x17\x2b\xa3\x72\x42\x38\x63\x45\xb5\xf9\xa8\x6c\x0e\x36\x21\xbe\xa1\x45\xd1\x60\x82\x27\x6c\x6e\xca\x18\x00\xd9\xcd\x98\x03\xa0\xea\x18\x04\xfc\xf2\x7c\x42\x02\x9b\x1d\x08\x20\xd5\x14\xe6\x3a\x3b\xe0\x53\xa0\x39\x7a\x88\x3c\x62\xda\xe9\x52\x2c\x65\xd2\x9a\xca\xc3\x87\x68\x0e\x5b\xe4\x8d\x31\x88\xa3\x43\x04\xdd\x43\x73\xed\x63\xf5\x7a\x13\xe2\x78\x86\xb6\xf6\x19\xd5\x93\x27\x3b\x43\x2a\x9a\xc9\x1c\xdd\xd3\xbc\x18\x3a\x20\xdd\xdf\xea\xe2\xe4\xfe\x3b\xad\x17\x96\xf2\xff\xed\xcc\xf7\x68\x62\x9e\x5c\xd4\xd6\x7b\x74\x43\xd6\x2b\xf4\xde\xb5\x54\xc5\x78\x6b\x7b\xde\xc0\x78\x07\x11\x13\x50\x5d\xc3\x7e\x15\x2f\x68\xf0\x8c\x1b\xb0\x24\xff\xbb\x5b\xf0\xd1\xa2\x4a\x2a\xf6\xb5\x03\xf0\x12\xa8\xdc\x94\x09\x03\xb6\x9b\x31\x61\xc1\x98\x6a\xc2\xcb\xc5\x68\xfc\xe5\x20\xa3\xf6\x2b\x5b\x04\x76\x20\xa3\xfa\x7c\x8b\x0f\x07\xdb\x5f\x8e\x26\xa1\x3f\x30\xcb\x2f\x55\xd8\x0d\xc5\x9c\x3f\x96\xc6\x46\x42\x0e\x87\xb8\xba\xc2\x8e\x06\x0a\x7b\x7e\x1d\x85\x3d\xca\xf3\xaf\x3d\xf2\x4d\xf2\xfc\x2b\x8d\x7c\xc5\x93\xdf\x37\x31\x67\xce\x7b\x73\xe6\xfc\x4a\x73\xe6\x7c\xe3\x39\x73\xbf\x47\xd8\x6e\x06\xb2\x70\x60\x54\x3f\xf8\xcd\x92\xe5\xf2\x92\x57\xab\xfb\x10\xf1\x30\x7c\xa7\x5b\x69\x9f\x87\xd7\xe3\x18\x0e\xa4\xb6\xdb\x31\x37\xda\x16\x34\x24\x0f\x5f\x1a\xd1\xc5\x37\xfd\xee\xca\xa3\xb9\x7c\x02\x7c\x51\xa8\x6b\x9b\x2b\xdd\x0b\xc7\xcb\xc5\x19\x5b\x56\x97\xe8\xef\xf2\x89\x61\x00\x04\xf3\x6a\x50\x0c\x96\x15\xa5\x81\xac\x76\x74\x78\xea\xb0\xd2\xbc\x89\xde\x8d\x2e\xab\xf2\x64\x5e\x16\x65\x96\xcc\x2b\x94\x42\x79\x39\x57\x7c\x03\x88\x5a\x56\x7f\xdb\x75\xe9\x9a\x99\xfa\x97\x1b\x58\x07\x1e\x72\x60\x76\xc7\x8e\xb8\x26\x2f\xcf\xb8\x59\x26\xb3\xad\x8e\xec\x47\x05\x87\xb4\x01\xb9\x91\x9c\x82\x76\x23\x21\xb2\xae\x9a\xbf\xc0\x57\x2f\x54\x51\xf7\x5b\xd1\xd9\xf3\xed\xfa\xec\x17\x22\x7b\x3b\xa8\xcf\xff\xae\xba\xac\xed\x5c\x15\x0b\xa6\x38\xc6\x09\x4e\xe1\x4e\x4d\x86\x73\xcc\x70\xb1\x35\x40\xf2\xee\xdf\xa8\xa9\x0e\xc2\xee\xc6\xdb\x03\x60\x74\x4e\x63\xb6\x83\xb0\x7c\x21\x0f\x4f\x40\x58\xac\xbf\x88\xff\xfe\xf6\x9b\xe6\x02\x06\x1f\xf7\x37\x3e\xf0\xa7\x3d\x34\xdc\x05\x53\xff\x44\xdf\x5c\x83\xef\x35\x6c\xf4\xcf\x02\x1a\x07\xed\x7d\x04\xc2\x87\x66\x6c\x7e\x52\x4d\xd1\x5d\x44\x37\x3c\x4a\xdd\x0f\x34\xfb\x8b\xf9\x27\xb6\xac\xa7\x86\x4a\x18\x96\xf1\x81\x77\xda\xf5\xed\x80\x8d\x02\x4f\xdd\x6b\x37\xda\xed\xec\xcc\x7d\x46\xc7\xdd\x20\x7a\x67\x85\xf2\xa4\x4a\x50\xb2\xba\x22\x9d\x8d\x57\xb2\xba\x3b\x85\x6b\x25\x40\xef\x54\x8b\x9f\x3d\x62\xde\x0a\x81\xe2\x2f\x38\xb3\x23\x69\x75\x8d\x4a\x73\x72\xa7\x86\x7b\x2e\x94\xd9\x30\x59\xab\x57\x77\x8a\x47\xa8\x4d\x83\x4b\x34\x77\xe3\xc3\xfb\x5d\xda\xf6\x9b\x5e\xed\x16\x5e\x5d\xeb\xed\xe0\x08\x3f\xff\xab\x79\xd8\x39\x3b\x5f\x4d\x27\xf5\x40\x8a\x8f\x11\x74\xf3\x4a\x3d\x74\x6f\x2c\x81\x34\xe7\x64\xeb\xa1\x88\xa2\xe0\x3a\x82\xd4\x38\x9d\xae\xdb\x18\x0f\x92\x0c\xbc\x02\xd0\x70\x93\xcc\x16\x67\xd0\x49\x1a\xfa\x7e\x34\x3a\x6c\x6d\xcc\x9e\xa1\x6c\xb6\x98\xdb\x66\x2a\x9b\x9a\x34\xe0\xe9\xdb\x32\xfc\x68\xb6\x65\x28\xb6\xda\xb2\x8a\x19\x46\x29\x82\xdd\xe6\xe4\xab\xee\xa4\xeb\x3e\xc0\xff\x19\x0c\xfb\xcf\x42\x32\x43\xa4\x75\x2c\x15\xf8\x86\x61\xb6\x3e\x35\x66\x26\x00\x77\x98\xea\x8d\x75\x31\x38\x31\x90\x69\x5c\xe8\xa2\xe3\x3f\xa3\x6e\x70\xb1\x89\x0f\x5c\x48\x93\xaf\xd1\xbf\x2d\xdf\xe9\xc4\x6e\x36\x55\x00\xee\xec\x2f\x37\xc3\x63\xe3\xb9\x99\xde\x69\x19\x79\x34\xe6\xf3\x3b\x87\xfa\x9b\x9c\x77\xd9\xbd\xfb\x27\x34\xad\xaa\xb3\xd5\xfd\xdd\xdd\xd3\x6a\xba\xda\x49\xd9\xee\x79\x55\xd0\x5f\x56\xe8\x13\xd9\xc1\x3b\x04\xa5\x97\xe8\x7f\x9d\x26\xd5\xb4\x4c\x56\xdc\x62\xda\x03\x32\x70\x2a\x44\x1c\xf6\xd8\xdd\x45\x4f\x58\x25\xae\xc3\x31\xc6\xc5\x5d\x26\xe9\x8c\xad\xd0\xdf\x24\xa5\xbf\xdd\xfa\x0e\x8e\xf1\x2f\x19\x3b\x68\xce\xbf\x0c\x4e\xd2\xa0\x3b\x42\x79\x77\xd0\xed\xdb\xf5\xcf\x0f\xcc\xe8\xd1\xdf\x44\x73\x14\xe4\x2f\xe0\x87\x16\xf7\xa9\xfc\xde\x45\x2d\x7f\xbd\x7d\x5b\x73\x3e\x67\xaf\xc3\x64\x03\x6c\x65\xe3\x04\x4e\xce\xfc\xcd\x11\xa7\xf1\x7f\x5c\xe4\x6c\xe7\x97\x15\x5a\x2c\xd1\x63\x71\x94\xa6\x2c\x4a\x96\xa3\x6c\x91\x33\x07\xb0\x24\xf3\x1c\x9d\xaf\x18\x2a\x2b\xde\xaf\xfd\x8d\xcb\x51\x69\x83\x3c\x87\xd3\xb4\xe1\x44\x7e\xef\xb6\x41\xfc\xfa\x40\x9c\x49\x6a\xab\xed\x34\xd0\x7b\x2a\xb2\xdf\x7e\x53\xbe\xed\x5c\x94\xf3\x9c\xcf\x2e\x3b\x30\xe2\xe8\x10\xe7\x05\xa9\x3f\xc3\x61\x9f\x5b\xdf\xed\xde\xbd\x77\x63\x7f\x77\x77\x6f\x89\xd6\xae\xaa\x65\x39\x3f\x79\xba\x5c\x9c\xee\x4f\x93\xe5\xfe\x22\xe7\x9a\x7b\x0d\x3f\xee\x14\xca\xaf\x52\xf8\xc7\xc9\x47\x36\x17\x32\xee\x9b\xec\xd9\xf9\xfc\x92\xcb\xf7\xd6\x77\x4d\x04\x3b\xcf\x56\x24\x67\xfc\xc7\x89\xa0\x23\x1a\x08\x5b\x9b\x70\xf8\xbe\xee\x02\xe1\xa7\x6c\x71\x3e\xaf\xd8\x52\xae\x5c\xc2\x4f\xb3\x3a\x56\x88\xea\x6d\xb0\x80\x52\xb8\xcf\x58\x7f\x61\xeb\x6a\x99\xf0\x2f\x17\xd3\x72\xc6\xd0\xa4\xc6\xf6\x50\x22\x11\xa4\xbf\x83\x3a\x2d\xc2\x4c\x36\xef\x51\x55\x57\xd8\xde\xe6\xae\xfe\x1d\xe8\x54\x00\x7f\xbf\x87\xdc\xf5\x13\xea\xba\x5c\xe7\xe2\xa7\x87\xf0\xd3\xe3\xa7\x4f\xf9\x4f\x06\x4a\x5c\x5c\x30\x5d\x5f\x9d\x2f\x97\x8b\x93\xa4\x62\x0e\x58\x5d\x35\x65\x4b\x06\xf7\x3c\xd1\x9c\xad\x2b\xc4\x59\x48\xb2\x8a\x2d\xa1\x12\x34\x63\x13\xfe\x80\xc1\x89\x00\xbf\x8d\xdc\xf5\xd3\x7d\xd7\xdd\xe2\x16\xea\xae\x9f\xc0\xc7\xbf\xf3\xe0\x3c\x5b\x5c\xb4\xf4\xa1\xda\x77\x42\xf2\xa2\x2b\x9f\xc8\x26\x72\x04\xde\xd3\xa7\x5b\x70\x35\xd3\xdd\x42\xdb\x48\xc1\x0c\x05\xdb\x75\xc6\x21\x49\xbd\x1d\x05\xcb\xa6\x9e\xcf\x4f\x93\x2a\x9b\xb2\xbc\xa5\xf7\x00\x2d\xe6\xb3\x4b\x94\x9c\x9d\x31\x68\x77\xb9\x02\x07\x44\xe7\xf3\xb2\x72\xf8\x44\x33\x4b\x56\x0c\x66\x9b\x5c\x10\x0d\xa6\x06\x86\x0b\xa9\xaa\xcf\x45\x35\x58\x79\x57\x9f\x28\x5f\xcf\x92\x72\x39\x6c\x19\xb4\x4b\xf2\xfa\x9d\x14\xdd\xbd\x7b\x92\xf7\x5b\xfd\x06\x18\x6a\x72\x40\xfe\x7f\x19\xef\x05\x54\xed\x8d\xd7\x71\x06\x36\x07\x67\x80\x5e\xb8\xf5\x85\xc6\xca\xc5\xb8\xa5\x6b\xe4\xe5\x3c\x67\x6b\xb4\x87\xee\x61\xad\xd9\x37\x7e\x74\xe7\x8e\x62\xfc\xdb\xdb\xa2\x9a\xc1\xf8\x81\xce\x5b\x00\x79\xd7\x37\x76\x6e\x4a\x4f\xb9\xc6\x85\x64\xc4\xaf\xf7\xf6\x6a\xf5\x3f\x50\xe4\x85\xb6\xf7\x34\xf1\xa3\x46\xf4\xfd\xf7\x08\xbb\xb5\x01\xa1\xdf\xa4\x0f\x49\x95\xd4\x9c\x08\x63\x45\xbf\xa1\x8e\x1d\x36\xc2\xdf\x80\x10\x20\x34\x29\xa9\x11\x7e\x36\x65\xd9\xc7\xd7\x59\x32\x4b\x96\xff\xc1\x6b\x4d\xb8\x1e\x5e\x2d\xca\xb9\x38\x4d\x0d\x02\x68\x7e\xea\x7a\x7c\xfb\xb3\xf0\xfa\x56\x38\xd5\x74\xb9\xb8\x40\x07\xcb\xe5\x62\x39\x81\x56\xdd\x79\xce\x87\x42\xad\x69\xfe\xb4\x7d\x07\x6d\xb7\x08\x76\xaa\x85\x88\xac\x13\x1c\x6e\xed\x54\x8b\x9f\xce\xce\xd8\x72\x3f\x59\xb1\xc9\x16\xda\x16\x08\xb8\xc9\xcf\x17\x15\x37\x70\x60\x56\xc8\xe5\x0e\x2f\xac\x1b\xfa\xf9\x2b\xf4\x04\xad\x9c\x60\x54\xcd\x47\xe2\xad\x38\x1c\xb1\xcd\x26\x3b\x27\x21\x65\x8d\x36\x26\xaa\x00\xbf\xaf\xeb\x08\x8d\xc2\x52\xe5\x9a\xba\x5b\x7d\xbd\x08\x87\xd8\xaf\x2b\xea\xd4\xa2\xa0\xbd\x2d\x8d\xf3\xe9\x53\x2a\x63\x9d\x0c\x73\xf8\x5e\x7a\x59\x31\xb4\x62\xff\x75\xce\xe6\x19\x04\x3a\x33\xa3\x2d\x8d\xda\x74\xa0\x23\xbc\x3c\x4d\x17\xb3\xc6\x91\x4c\x94\xa9\xdb\xa5\x4c\x86\x94\x1b\x4c\xe3\x42\x0a\x85\x80\xb0\x14\xd0\xbe\xdb\xb0\xd4\x1c\x3c\xd6\x30\x01\x61\x58\x65\xc2\x1b\x32\x61\x31\xf8\x07\x57\x64\x12\x13\xc1\xa5\x2b\xb9\x3c\x70\x3b\x28\xb6\xf7\x0c\x56\x13\x6e\xd0\x98\x03\x77\xd0\x18\xff\x8b\x24\x8a\xa9\x64\x36\x12\xcc\x3e\xdd\x90\x59\x4c\xae\xda\xa8\x16\x52\xc7\x55\xb7\xa1\x5d\x0f\x68\x6c\x13\x30\xf4\x5d\x82\x0f\xfd\x65\x3f\xd1\x1f\x34\x35\x48\xf9\xd0\x7d\x38\xb8\x1a\x8c\x9a\x5a\xf8\xd1\x4e\xa5\x01\xad\x7f\xe0\x4a\x10\xd1\x6a\xc3\xce\xa5\x6d\xb1\x4a\x58\xed\x65\x14\x94\xdb\x7b\x16\xd7\xef\x45\xf4\xb6\xda\xd7\x1a\x08\x37\xb2\x5f\xb2\x24\xdf\x5f\xcc\xab\x72\x7e\x0e\x97\x67\x41\xfb\x6d\x28\xe2\x9c\x3c\x83\xb6\x7f\xbf\x07\x6c\xed\xf3\x81\x85\xa6\x37\xb8\xf3\x6c\xfe\x29\x99\x95\x39\x00\x09\x69\xdf\x91\xcd\x6a\xe4\xdd\xa5\x82\x04\x42\x58\x28\x78\xdb\xd0\x79\x27\xdd\x84\x57\x6d\x7e\xdc\xde\xe6\x83\xf1\x3a\x42\xf5\xd0\xdc\x16\x61\x44\x0c\x04\x79\x94\xfc\xbb\x12\x0c\xb5\xd0\xde\xd3\x86\xb1\xdd\x5d\xf4\xac\x40\x17\x0c\xf1\xf1\xda\xf9\x19\xe2\x23\x55\x07\x95\xd5\xff\xfb\x3f\xff\xb7\xee\x96\x54\x14\xc0\xf1\x2d\x43\xcb\x07\x80\x77\x06\xc1\x5f\x58\xef\x6b\xf0\x82\x49\x6b\xe5\x1c\x18\xab\x66\x48\xd4\x2f\x9e\xfa\xc5\xd7\x98\xef\x50\x57\x5f\xa0\xaa\x2e\xa6\xbd\xa1\xd6\xa5\x64\x8b\x64\x06\x97\x1f\x1a\x39\x1e\xb1\x24\x47\x45\xb9\x5c\x55\xb5\x94\xa0\x59\x57\x57\xf3\xb0\x77\x43\x93\xf9\x62\x28\xde\xd5\x56\x6d\x13\x82\xd0\x6d\xa9\x7f\x19\x59\x15\x5e\x1b\xf9\xd6\xbc\x0e\xfb\xb0\x1e\x9e\x83\xda\xa0\xf6\x6b\x54\xa0\x16\xb4\x67\x70\x98\x07\xfd\x78\xa0\x22\xc3\x62\x9a\x01\x39\x77\x1a\xed\xea\x06\x60\x8d\xf5\xb6\xe6\xab\xc4\xa8\xee\x00\xfe\x0a\x26\x58\x0f\xeb\x45\xdb\xbd\xbe\x6c\x4f\x93\x4b\x54\xce\xb3\xd9\x39\x4c\x42\xf8\xe4\x42\x9d\xd2\xe8\xa4\xfc\xb4\x96\xce\xc1\x15\xa4\x03\xa6\x7c\x3d\x01\xba\x72\x9e\x46\xe0\x6c\x92\xa0\xa5\x0a\xd4\x33\x09\xd4\x85\xc1\x8b\x10\xd8\xd8\xf8\xe0\x6b\xca\x7c\xd8\xc3\xf7\x25\x4a\xa5\x44\x9f\xde\xac\x44\x21\x64\x5c\x53\xe8\x11\x08\xdd\x5d\xf7\xc5\xee\xae\xdd\xfd\x2d\xf4\x1b\x48\x64\x22\x78\x10\xbf\x36\xfa\xf0\x8d\xfa\x80\x19\x95\x66\x8e\x81\x5d\x75\x0a\xa6\xd7\x44\x2d\x4f\xad\x16\x7e\x3a\x7e\x7a\x8f\xa2\x1c\x56\xca\x58\xde\x44\xde\x3a\x6c\xca\x1b\x58\xcd\x77\x08\x68\xca\x77\x88\x3f\x0f\x7a\x63\x12\x39\xd6\x68\x7b\x63\xc1\x5f\x83\xaf\x3b\x24\x51\xc0\xea\xa8\x06\x54\xd4\x00\xa8\x0c\x4a\x94\x18\xdb\xae\xfe\x74\x86\x3b\xed\x3a\x51\x75\x7a\xa6\x8c\x46\x26\xd5\xe9\x19\xda\xeb\xf5\x25\x5b\xe8\x4f\x7b\x7b\x22\x28\xf7\x47\x27\x72\x13\xa3\x3a\x3d\xeb\x8f\x33\x94\x09\x7a\x0b\xbd\xf5\x35\x17\xdf\xb8\x58\xd1\x1e\x30\x78\xe7\x13\x5b\xae\xca\xc5\xfc\xce\x7d\x74\x07\x16\x7d\xef\x38\xfc\x57\xc1\xcf\x9d\xfb\xca\xa8\x10\x7e\x17\xcd\x95\xbf\x8b\x2f\xb7\xbe\xfb\x2c\x17\xe9\x5e\x2f\x4e\x19\x7a\xf4\xe2\x09\x4a\xcf\xcb\x59\x8e\x16\x67\x55\x79\x5a\xfe\xca\x96\x2b\x07\xcd\xca\x8f\x0c\x2d\x77\x7e\x59\x39\x62\x4a\x0c\x2b\xed\xab\x33\x96\x95\x45\x99\x71\xe7\xcd\x4b\x50\xf8\x59\x52\x55\x6c\x39\x5f\x01\x3e\xa8\x54\x4d\x19\x2a\x16\xb3\xd9\xe2\xa2\x9c\x9f\xdc\x17\x6b\x9e\xdc\xfc\x7a\xf7\x22\xd1\x9d\xda\x68\xee\x88\xc5\xdd\x0e\xc0\x4e\x72\x9a\xf7\x56\x51\x9b\x2b\x92\xbc\xec\xd6\x77\x42\x5d\xf2\xd2\x64\xb3\xcc\xdd\xed\xc0\x78\x9b\x41\x77\xa0\x9c\x76\x76\xd1\x5b\x35\xfe\x93\xf2\x7d\x67\xbe\xc8\xd9\xf1\xe5\x19\x6b\x07\x73\xed\x5a\xb5\x9c\x78\x94\x73\x75\xdd\xf8\xa8\x9c\x9f\x2c\xfe\xf7\x6b\xf4\xc9\xdd\xa1\x3b\x2e\x4c\xcf\xdb\x1a\xca\x5d\xd2\x86\x19\x19\x1a\x6b\x4c\xc9\xf2\x62\x9a\xcc\x7a\x98\xa2\x1d\xf7\x9e\x58\x88\x59\xd6\x67\xa3\xc4\x2d\x46\xf9\xdb\x34\x59\xbd\xbc\x98\xbf\xaa\x8f\xc0\xec\x49\xa0\x9d\xee\xef\x00\xde\x6c\x91\x40\xd6\x38\x21\x94\x3a\x62\x74\xc1\xc5\xfe\x10\x2f\x87\x8b\xc4\x5b\x5c\x36\xaa\xac\xde\x7e\x14\x09\x0c\x39\x04\x7c\xee\x2c\x7e\xf5\xda\x75\x34\x2d\xe7\x0b\xde\xaa\x04\x5d\xb0\x14\xc9\x8b\xaa\x72\xd5\x7a\x47\x1a\xb4\x94\xc9\xe7\x5b\xf2\x8a\x2a\x6c\x9b\x7c\x76\xfe\xfe\xf9\x9d\x43\x83\x4d\xb6\x44\x06\x37\x76\x7f\x7e\xf1\xfc\xb0\xaa\xce\x8e\x78\x97\xb1\xaa\x1a\x6c\x7f\x4e\xcb\x13\x71\x98\x65\xe7\x97\xd5\x9f\x37\xc1\x7c\xe7\x7c\xc5\x60\xc2\x96\x55\x77\x1e\xdc\x1a\x12\x7a\x5c\x9e\xfc\x08\x08\x1f\xf0\x06\xff\xb2\x9a\xf2\xa0\x5c\x9e\xcc\x17\x4b\x76\x7f\x56\xce\xd9\xad\x86\xf4\x05\x4b\xbd\x8d\x48\x72\x25\xbd\x61\xa9\xe8\x9b\xc4\x35\xe3\x3b\x3b\xbb\xb3\x32\xdd\xe5\x28\x78\x70\xbe\xb5\xbb\x8b\xf2\xc5\xbc\x42\x8b\x4f\x6c\xb9\x2c\x73\x56\x6f\x38\xd4\xfb\x1b\xb7\x94\x2b\xc8\x72\xe7\x80\x07\xb8\x3b\xcd\x81\x06\xd8\x8f\xe8\x00\xec\x08\x92\x5d\x28\x6e\x20\xb0\x4d\xa6\x82\x00\x73\x0f\x6e\x7d\xd6\x48\x43\x94\xc8\x8d\xad\x9a\xe3\x3f\xdf\x27\xfe\xe7\x77\x5c\x0a\xce\x5b\x21\x85\x77\x5b\xb7\x76\x77\xff\x07\x5a\x2d\xce\x97\x19\x7b\x91\x9c\x9d\x95\xf3\x93\x9f\x8e\x9e\xef\xf1\xc2\x7b\x33\x38\x44\xfa\xcb\x6a\xe7\x34\x39\xbb\xf5\xff\x03\x00\x00\xff\xff\x3e\x28\x38\x1f\x2f\x1f\x06\x00") +var _web3Js = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\xbd\x5b\x6f\x1c\x49\xb3\x20\xf6\xbc\xfa\x15\xa9\xf6\xae\xba\x7b\x58\xea\x1b\x29\x8d\xa6\x29\x4a\x87\xa2\xa8\x11\xbf\x23\x91\x02\x2f\x33\xdf\x77\x78\xf8\x09\xc5\xaa\xec\xee\x1a\x55\x57\xf5\x56\x65\xf3\x32\x22\x1f\x6c\xd8\x80\x61\xc0\x80\x5f\x8c\x7d\x30\xe0\x17\x03\xf6\xcb\x1e\xc0\xf0\x83\x61\xc0\x30\xfc\x67\x7c\x76\xcf\xcf\x30\xf2\x7e\xaf\xae\xe6\x65\x46\xdf\x19\x0a\x83\x61\x57\x55\x64\x66\x64\x64\x64\x64\x64\x64\x64\x44\x01\xff\xfd\x3c\x29\xe0\x46\x6b\x34\xcf\x22\x94\xe4\x59\xab\xfd\x95\xff\x04\x45\x0b\x06\x59\x80\x94\x37\x79\x2b\x09\x46\xed\xaf\xc9\xa8\xf5\x38\x3b\x4e\x4e\xe8\x2f\x48\x7e\x9d\x85\x05\x88\x36\x1a\x1c\xb4\xb1\xb1\x81\x2e\x67\x30\x1f\x01\xd6\xc4\x93\x27\xec\xc7\x3a\x2e\x33\x7a\xf2\x24\x6a\x17\x10\xcd\x8b\x0c\x44\xad\x24\x78\xdc\x6b\xe3\xf7\x73\xfe\x6e\xce\xde\xe1\x5a\xc3\x8d\x0c\x9e\x83\xed\xa2\xc8\x8b\x56\x63\x2b\xcc\xb2\x1c\x81\x51\x92\xc5\x60\x9a\xc7\xf3\x14\x82\x66\x63\x25\x59\x69\x34\x1b\xed\x75\x34\x29\xf2\x73\x10\x76\xa2\x3c\x86\x1b\x8d\x8f\x7b\x6f\x8f\x3e\x6c\x7f\xde\xdd\x3b\xfc\xfc\x6e\xef\x68\xf7\x6d\x23\x08\xaf\x71\x7d\xb3\x0d\x8c\xfb\xc6\x57\x78\x31\xcb\x0b\x54\x0e\xbf\x5e\x5f\xaf\xe3\x3e\x1c\xf7\x4e\x3a\x51\x98\xa6\xad\x59\x87\x7d\x0a\x04\x55\x0a\xda\xc1\x6c\x83\x00\xf6\x4f\x8e\x8b\x93\x75\x86\x6a\xde\xca\xae\xae\x8a\xf6\x75\x30\x0b\x64\xc1\x22\xa0\xa4\xbb\x66\x40\xb8\x45\xfe\xf1\x7a\x94\x17\x2d\x5c\xdb\xbc\x0e\xb9\x82\x64\xa3\xb7\x9e\xbc\x44\x9d\x14\x66\x63\x34\x59\x4f\x56\x56\xda\x79\x0b\x61\x9a\x0b\x0c\x78\x2b\xc5\x75\xbb\xd5\x6e\x7d\xed\x0f\x8f\x25\xe2\xac\x16\x4a\xab\x80\xa1\xd0\xfe\xfa\x08\x00\xc0\x08\xc8\xf1\xda\x38\x26\x2f\x01\xf8\xca\xfe\x02\xd0\x88\xf2\xac\x44\x61\x86\x1a\x43\x80\x8a\x39\x0c\xe4\x97\x24\x9b\xcd\x51\xd9\x18\x82\x63\xf1\x4e\x2d\x49\x60\xb2\x70\x0a\x1b\x43\xd0\xf8\x9c\x9f\x67\xb0\x68\x04\xfa\x57\xdc\x61\xfc\x35\x8c\xe3\x02\x96\x65\x43\xf9\x7a\x2d\x7e\x9f\x28\x4d\xf2\xea\xc8\x5f\xe5\x7d\x3e\x47\xf5\x71\xc9\x3f\x1b\xc5\x35\x5c\x4e\x2f\x11\x2c\x57\x07\x8b\x71\xe1\x05\xc4\x00\xb2\x4f\xd7\xc1\xbd\x11\xf1\x4e\xf0\x16\x64\x30\x46\x64\x39\x22\xde\x7a\x28\xff\xd6\xc9\x17\xe5\x19\x82\x19\xba\x73\x02\xfe\x61\xf8\x0f\x73\xca\xdf\x1c\xfb\x8d\xc2\xb4\xfc\x36\xc8\x57\xc0\x12\x16\x67\x3e\x29\xf8\xaf\x89\x51\xca\xf9\xe9\x3e\x1c\x27\x25\x2a\xc2\x07\x86\xa9\x24\x63\x50\xa7\x4e\x78\xbe\x77\xa7\x6b\x31\x2a\xc2\xac\x1c\x79\x97\x92\x7f\x8d\x34\x2c\x1c\xec\x78\x3b\x22\x96\x10\x1d\x2c\x66\xf3\x7b\xa5\xa5\x0b\xad\xdf\x15\xa1\xdf\x69\x70\xc3\x65\x06\xb5\x4e\x85\xb3\x22\x99\x86\xc5\xa5\x17\xcf\x3c\x4f\x97\x62\x94\x4d\x86\xc7\x1f\x67\x48\x6c\x5d\x6b\x61\xb5\x8b\x88\xb8\x55\xa9\xbe\xfd\xad\x11\xb1\xb2\xb7\x71\x52\xe6\xe7\xd9\x1d\xf7\x34\xcc\xf2\xec\x72\x9a\xcf\xcb\x1b\x76\x35\xc9\x62\x78\x01\x63\x4b\xcf\xb8\x1d\x1d\x2a\x98\x49\x36\x68\xa0\xab\xb7\x78\x9e\x64\x77\xb9\x38\x6e\xce\x09\x25\xb7\xb3\x18\xc6\x0d\x07\xa9\xe1\x19\x66\xc2\x3f\x20\x9d\x4f\x93\x38\x5e\x8e\xce\xb7\x6f\xf3\x2c\x4c\xe7\xde\x6e\xce\x93\x0c\x0d\x9e\x3d\xaf\x3f\xb4\xbb\xf0\xfc\x4d\xf2\x0d\x0f\xea\x9d\xca\x90\xad\x49\x98\x8d\xbf\x65\x16\xbe\x17\x0e\xae\x68\xcf\xd8\xbd\x82\x5b\x49\x89\x4f\x54\x43\xa8\x4d\x64\xf2\xf7\xe4\xd1\x23\xdc\x8f\xaf\xd7\x27\xc1\xe0\x9b\x33\xf7\x3d\x58\xaa\xbe\x3d\x2d\xaa\x80\xa3\x79\x16\xdf\x19\xd3\xde\x8b\x5a\xf1\x2d\xb2\xdf\x83\xa1\xea\x56\xe4\xbb\x77\x43\xd5\x37\x37\xd1\x1e\x4c\x2c\xbf\xf1\x2e\xbc\x92\x78\x6c\xc7\xfc\xb7\xb8\xff\xf9\x7d\x54\x38\x53\xbb\x58\xbd\x17\xed\xe2\x36\x1c\x37\x2a\xf2\xe9\x1d\x33\x1c\xca\xef\xd8\xec\x73\xb7\x9b\x8d\x6f\x5b\x02\xfc\x2d\x8c\x47\x92\xc5\x49\x01\x23\xb4\xe3\xd5\x80\x6e\x88\xe9\xdd\x0e\x74\x12\x85\xb3\xc3\x6f\x7a\xb0\xfd\x23\xb3\xbc\x65\x0a\xce\xf2\x32\xa9\x63\x84\x9b\x85\x97\xe1\x69\x0a\x6d\xb5\xf0\x9b\x93\xd9\x55\x73\xe1\x5e\xcc\x1a\x77\xcb\x7f\x9b\x9c\x46\x6f\xed\xb1\xf9\xb6\x76\xfc\xf7\x42\xe8\x8a\xf6\x96\xe3\xfb\x6f\x6c\x58\xff\xa6\x47\xf3\x3e\xec\x37\x77\xb6\xbe\xfc\x1e\xa3\xe9\x5a\x20\x1e\x86\xf3\x37\x1c\xce\x3b\x57\x26\x7e\x0f\x2e\xda\xf1\xa8\x1a\xf5\x76\x05\x6b\x75\x77\x05\x67\x61\x01\x46\x60\x83\xbb\x35\xb6\x9a\x9d\xee\x28\x2f\xa6\x21\x42\xb0\x28\x9b\xed\x75\x01\x74\x90\xa7\x49\x9c\xa0\xcb\xc3\xcb\x19\xd4\xe1\x31\x46\x18\x92\x80\x76\xbf\xfb\x8e\xe2\xf4\x9d\x56\x82\x9d\x8a\x82\xa4\x04\x21\x98\x15\x79\x8e\x0b\x01\x34\x09\x11\x28\xe0\xac\x80\x25\xcc\x50\x09\x18\x37\x00\xfc\x91\xd7\xb2\x83\xc0\x34\x44\xd1\x04\x96\x43\xfe\x8a\x81\x19\x8f\xc7\x27\xe6\x8b\x35\xeb\xcd\x89\x0d\xb4\xea\x78\x77\x72\xfc\xfc\xe4\xf8\x24\x00\x9d\x4e\x87\x7d\xea\x3a\xe9\xc0\x7b\xb5\x01\x84\x07\x6e\xab\x2d\x98\x07\x4d\x92\xb2\xf3\x99\x4c\xda\x77\x9c\xa4\x18\xb4\x43\x09\xbc\x83\x3f\xec\x64\x68\x5d\x03\xa7\x1a\x96\x0b\x7e\x8f\x7c\x61\x4d\xd2\x42\xd7\x8c\xe8\x0e\x9c\x3a\xb3\x22\x47\x94\xca\x1b\x20\x83\xe7\x1a\x4c\xeb\xeb\x35\x1b\xd9\xca\x92\x1d\xa2\x93\x16\xf3\x08\xe5\x18\x11\x07\x6c\x9d\xf6\x3b\x49\xc9\x78\x46\x12\x09\x33\xb9\x24\x14\xf3\x8e\x7d\xfc\x18\xbf\xee\x90\xd1\x6e\x75\xd9\x58\xb4\xfe\xf1\xb8\x75\xdc\x7b\xfa\xc3\xc9\x77\xed\x7f\x3c\x69\xbf\xee\xb6\xf5\x9e\xeb\x9b\xda\x0a\x24\xaf\x83\xaf\x0d\x95\xb5\x1b\xc3\x1f\x82\x06\xe5\xdd\xc6\xb0\xbf\x76\x7d\x12\x3c\xfb\xc6\xa6\xcc\x9b\x3c\x4f\x17\xcc\x97\x53\x0c\xb2\x60\xb2\x60\x18\xf5\xb7\xe4\x76\xf2\xb4\xa6\x3f\x9e\x18\x9f\x57\xcd\x17\x75\xa7\x06\xc1\xfe\xe6\xf3\x02\x17\x5f\x6e\x62\xc8\x12\xae\x59\x81\xbf\xde\x60\x4a\xe8\xc5\x2a\xe6\x03\x6d\x7d\x51\xf9\x9b\xcd\x84\xbf\x62\xc2\xeb\xf3\xe0\xbb\x7f\xbb\xd4\x44\x10\xd8\x2d\x9e\x05\xcf\xbf\xb5\x59\x80\x57\x6a\x31\x0d\x90\x7b\x1e\xa0\x09\x04\x64\x49\x27\x93\xa1\x53\x35\x1b\x30\x94\xf6\xa0\xf0\x37\x79\x5c\x33\x9e\x4f\x4c\x80\x55\xeb\x8d\x3d\x27\x00\x53\x30\xb4\xc7\x17\x56\xdd\x4a\x55\x9e\x59\x44\x3a\x75\x8b\x69\x84\xcb\x2f\x39\x8f\x64\x11\xe7\x44\xc2\x9f\x6f\x32\x93\xf4\x72\x55\x53\x89\x22\xb0\xb0\x86\x9b\x4e\x26\x5c\x0d\x9d\x4a\x5f\xfb\xc1\x75\xfb\x76\x13\x4b\xe0\xba\x78\x66\x7d\x5f\x77\x66\x75\xe9\x44\x38\x9c\x24\x25\x18\x25\x29\xc4\xdc\x3f\x0b\x0b\x04\xf2\x11\x38\x87\xa7\xab\x9d\x5f\xca\x0e\xc5\x8d\x3d\x61\x80\x51\x01\x21\x28\xf3\x11\x3a\x0f\x0b\x38\x04\x97\xf9\x1c\x44\x61\x06\x0a\x18\x27\x25\x2a\x92\xd3\x39\x82\x20\x41\x20\xcc\xe2\x6e\x5e\xe0\x4e\x25\xa3\x4b\x52\x47\x82\xc0\x3c\x8b\x61\x41\x26\x11\x82\xc5\xb4\xc4\xed\xe0\x87\x1f\x77\x8f\xc0\x07\x58\x96\xb0\x00\x3f\xc2\x0c\x16\x61\x0a\x3e\xcd\x4f\xd3\x24\x02\x1f\x92\x08\x66\x25\x04\x61\x09\x66\xf8\x4d\x39\x81\x31\x38\xa5\xf5\xe1\x92\xef\x30\x32\x07\x0c\x19\xf0\x2e\x9f\x67\x71\x88\xfb\x1d\x00\x98\xa0\x09\x2c\xc0\x19\x2c\x4a\x3c\x62\xab\xbc\x2d\x56\x63\x00\xf2\x82\xd4\xd2\x0a\x11\xee\x43\x01\xf2\x19\x2e\xd8\x06\x61\x76\x09\xd2\x10\xc9\xb2\x36\x09\x64\x4f\x63\x90\x64\xa4\xda\x49\xce\xa5\x45\x82\xc0\x79\x92\xa6\xe0\x14\x82\x79\x09\x47\xf3\x94\xaa\xd4\xa7\x73\x04\x7e\xde\x39\x7c\xbf\x77\x74\x08\x36\x77\xff\x02\x7e\xde\xdc\xdf\xdf\xdc\x3d\xfc\xcb\x3a\x38\x4f\xd0\x24\x9f\x23\x80\xf5\x6c\x52\x57\x32\x9d\xa5\x09\x8c\xc1\x79\x58\x14\x61\x86\x2e\x41\x3e\x22\x55\x7c\xdc\xde\xdf\x7a\xbf\xb9\x7b\xb8\xf9\x66\xe7\xc3\xce\xe1\x5f\x40\x5e\x80\x77\x3b\x87\xbb\xdb\x07\x07\xe0\xdd\xde\x3e\xd8\x04\x9f\x36\xf7\x0f\x77\xb6\x8e\x3e\x6c\xee\x83\x4f\x47\xfb\x9f\xf6\x0e\xb6\x3b\x00\x1c\x40\x8c\x18\x5d\xb8\x17\x13\x7a\x44\xc6\xac\x80\x20\x86\x28\x4c\x52\x3e\xfe\x7f\xc9\xe7\xa0\x9c\xe4\xf3\x34\x06\x93\xf0\x0c\x82\x02\x46\x30\x39\x83\x31\x08\x41\x94\xcf\x2e\x6b\x0f\x24\xa9\x2c\x4c\xf3\x6c\x4c\xba\x2d\xb8\x0c\x80\x9d\x11\xc8\x72\x14\x80\x12\x42\xf0\x72\x82\xd0\x6c\xd8\xed\x9e\x9f\x9f\x77\xc6\xd9\xbc\x93\x17\xe3\x6e\x4a\x2b\x28\xbb\xaf\x3a\x8f\x98\x0c\x53\x04\xf9\xdf\x11\xf6\x8d\xf2\x18\x16\x9d\x5f\x84\xd8\xfd\xbb\x70\x8e\x26\x79\x01\x3e\x86\x05\xfc\x02\xfe\x3e\x47\xf0\x3c\x89\x7e\x05\x2f\xa7\xf8\xf9\xef\x20\x9a\xc4\xf0\xac\x13\xe5\xd3\x57\xa2\x40\x1c\x22\x08\x06\xbd\xfe\x33\x21\x2c\xeb\x2d\x41\x0b\x94\x76\xa5\x1c\xd3\x36\x7d\xeb\x16\xd3\x66\x94\x02\x78\x4d\xf6\x41\xef\x64\x48\x07\x4e\x32\xe4\x83\x3d\xb2\x80\xe7\x15\xd0\x6f\x2f\xb3\x70\x9a\x44\x7c\x59\x50\x4a\xc5\xf4\x0b\x91\x6e\xbe\xd2\x07\xa8\x48\xb2\xb1\x5e\xae\x24\xef\x7c\x25\xf6\x61\x68\xf4\xbb\x80\xa1\xb7\xdf\x47\x36\xf8\xbc\x0a\xde\xd1\x0d\x81\xbf\x28\x90\x94\xac\xd3\x9a\xa4\x2f\x95\x6a\x02\xb2\xea\x5b\x72\x5f\x05\xe9\x88\x5a\xc8\x0a\x45\xe1\xaf\xae\x94\xcd\xb8\x1b\x7a\xb3\x28\xc2\x4b\x0a\xae\xaf\x0a\x0e\x6d\x65\x0b\xb3\xb9\xa2\xa4\xb0\x89\x49\x05\x4e\x0c\x50\x0e\x60\x86\xa7\x42\x37\x86\xf8\x8f\x68\x11\xcb\xf6\x90\x4a\x5d\x2c\xe4\x14\x75\xde\xd6\x09\x68\x1b\x2a\x21\x30\x78\x69\x2a\x05\xe4\x25\xd8\x20\x75\x95\x5e\xc4\xc9\x02\x33\x85\x68\x92\xc7\x0e\x64\xe9\x99\x56\x5e\x4c\x01\x55\xb9\x72\x6d\xe8\x58\x1d\x62\x86\xb2\x6a\x3e\xb3\xa1\x54\x40\xc0\xdf\x91\xfe\x81\xaf\x94\xfb\xae\xb5\xfd\xca\xdf\xd1\xb1\x2a\xc1\x57\xb5\xf2\x6b\xf1\x99\x5c\x18\x2d\xc1\x57\x72\xb9\xf4\x1a\xb0\xc7\x04\x0b\x25\xaa\xda\x61\x7e\x26\xf8\xe1\x25\x10\xaf\x33\x1a\xe9\x34\xb2\x29\xaa\x83\x8a\xa6\x45\x4d\x49\x4c\x4c\xf9\x52\xd7\x5e\x15\xfa\x76\x46\x49\x8a\x60\xd1\x52\x4a\xb7\x15\xc3\x11\xe3\x42\xc4\xf4\x14\x95\x89\x00\xb8\x6e\x1f\xf7\x4e\xd8\x88\x00\xdc\x9f\xd6\x63\xb5\x21\xb5\x1e\x7a\x67\x96\xde\xae\x6d\x26\xd9\x59\x98\x26\xb1\xe4\x1d\x5c\xeb\xe3\x21\x68\x82\x15\xa0\x37\xf0\xc8\x3f\x19\xfc\x9c\xec\xe1\x58\x30\x4b\xc3\x24\xa3\x7c\xea\x19\x78\x0a\xf8\x49\x81\xa8\x1e\x77\xf6\x6d\xef\xf4\x17\x18\xa1\x6b\x47\x03\x9c\x35\x64\x79\xda\x44\xec\x80\xad\x1e\x6c\x05\x35\x6b\xac\x03\x5a\x8f\x25\x3b\xc8\x30\x2b\x05\xcb\xd6\x31\x06\x3f\x09\xc0\x31\x29\x70\x52\x21\x0e\x7c\x44\x4c\x93\x92\xa8\x70\x74\xba\x2f\xa6\x63\x69\x12\x8b\x08\x23\x4a\x47\xdf\xb7\x99\x5e\xd0\x47\x42\x27\x2a\xb5\xa9\x58\xda\x02\x88\xd1\xb1\xf4\xcf\x9d\x92\x4f\x9e\x31\x44\xea\x5c\x2f\x99\x04\x13\x93\x01\x17\x64\x78\xe2\x32\x5a\x1d\x9d\x69\x38\x6b\xf9\x16\x01\x62\xa4\x75\xcc\x40\x4d\xb2\xd3\x9a\x5b\x14\xdb\x63\x52\xe4\x84\xae\x1f\xfc\x49\x99\xa3\x1a\x4e\x6c\x81\xdd\x1b\x8d\x4a\x88\x2c\xc4\x0a\x18\xcf\x23\xa8\xe0\x16\x46\x51\x00\x16\x20\x48\x68\x84\x42\x94\x44\x9f\xc2\x02\x7d\x20\xf7\xc7\x8d\x9a\x3b\xe6\xf7\x96\x13\x57\x5a\x57\x81\x05\x20\x8c\x0f\xec\x2a\x3f\x86\x68\xd2\x19\xa5\x79\x5e\xb4\x5a\x56\x8b\x2b\x60\xb5\xdf\x06\x5d\xb0\x3a\x68\x83\xef\xc0\xea\x40\x74\x5b\x90\x30\x8c\x22\xb0\x02\x5a\x62\x59\xd4\x28\xef\x21\x23\x78\xad\xac\xae\x00\xac\x0e\xc0\x50\x7b\xe1\x41\x56\x92\x3f\x00\x3d\x7d\x04\x0a\x58\xce\x53\xc4\xf9\x88\x8e\xe4\xc7\x79\x8a\x92\x9f\x13\x34\xa1\xe3\xc2\xb9\x51\xc3\x2f\x10\xfc\x14\xe8\xa3\x28\xab\xe7\xf7\xf3\x49\x0b\xee\xcd\xaf\x7b\x42\x18\xed\xbb\x66\x46\x4d\x5c\xb4\x99\x23\xba\xda\x68\xac\xab\x13\x0a\xa6\x23\xd6\x7f\x81\x3a\x5b\x8b\xf2\x62\x3b\x8c\x26\x2d\x53\xbc\x25\x2a\xbf\xe1\x75\xc6\x3b\x86\x72\xfc\x4e\xda\x6d\xed\x00\x84\x21\xb3\x62\x9b\xae\x5b\x7a\x17\xf8\xec\x52\xd8\x92\xcd\x67\xcc\xd5\x30\x1d\x31\x00\x73\xbc\x48\xe3\x36\x3a\x9c\x52\xe4\xc1\x1c\x38\xd9\x80\x3e\x31\x57\x36\x00\x64\xa1\x18\x40\x17\x0c\x24\xe0\x35\x80\x69\x09\xb5\x8e\x75\xbb\x20\xce\xb3\x26\x02\x61\x1c\x03\x56\x06\xe5\x7a\x85\x1d\x90\xa0\x66\x09\xc2\xb4\x80\x61\x7c\x09\xa2\x7c\x9e\x21\x18\x3b\xa9\x73\x4f\x3d\xbc\x96\xd3\xb1\xdb\x05\x87\x7b\x6f\xf7\x86\x60\x94\x8c\xe7\x05\x04\x78\x7f\x9a\xc1\x12\x6f\x78\xf1\xa6\xf4\xb2\xb4\x45\xd7\xfd\xb3\xc7\xb7\x32\xc0\xea\x50\x40\x95\x7c\x92\x26\xb7\x9c\xec\x15\xf3\xdc\x94\xf5\xac\x6b\x01\xc8\x8d\xd9\xdd\xfd\x0e\xfc\x52\x4e\x92\x0c\x81\x69\x78\x11\xe5\xd3\x59\x0a\x2f\x12\x74\x39\x04\xfd\xef\xf9\x12\x6c\x00\xc5\x70\x86\x26\x43\xf0\x4c\x6c\x7b\x1d\xb2\x40\x19\x09\xdc\x70\x92\x8d\x3f\xe6\x31\xdc\xf8\xca\x48\x37\xec\x07\x54\xea\x0f\x07\x41\x8e\x26\xb0\x18\xae\x5e\x6b\xa2\x75\x8a\xa1\x5b\x0b\xf7\x40\xaf\xd5\xda\x3b\xbc\x72\xb3\x1c\x15\xe9\xde\x62\x0c\x13\xed\x1d\x41\xaa\xdd\x56\x34\xe2\x16\x46\x09\x3c\xde\xd8\x00\x0e\x40\x7d\x09\xa5\x33\x60\x37\x9c\x42\x73\xf1\x94\x5f\x74\xf5\x5b\x2d\xe7\x58\x2d\xab\x17\x60\x59\xa9\xb9\xfc\x72\xa1\x4d\x71\xdf\x30\x71\x67\xf4\x02\xaf\x39\x77\x1c\xf7\x4e\xc0\x10\x34\x9b\x6d\x65\xc5\xc5\x93\xb0\x6a\x1c\x94\xd6\xed\x69\x38\x2b\xe0\x59\x92\xcf\x4b\xd1\x93\x85\x98\x0c\xc0\x50\x5d\x66\xf1\xbf\x51\x5e\x00\x12\xfe\x26\x01\x1b\xa0\xb7\x0e\x12\xf0\x92\x23\xcc\x63\xdc\x80\x64\x65\xa5\x6d\x1c\x92\x77\xbb\x20\x0a\xd3\x68\x9e\x86\x08\x72\x61\x8a\x55\x4c\x86\x12\x48\x10\x9c\x6a\x05\xf8\x18\xfb\xb0\x6b\xeb\xf5\x03\xb3\x77\x2b\x1b\x60\xa5\xc5\x29\x99\x80\xa7\xa0\x7f\x82\x77\x55\xe0\xea\x0a\xf4\xd6\xb5\xa2\xd7\xda\x13\x59\x06\xbc\x8d\xd3\xe1\xbe\x41\xdb\xb8\xdd\xe3\x7a\x18\x54\x2c\xa8\x39\x13\x72\x20\x01\xdf\xf9\x18\x74\xc5\xc4\x06\x2b\x6d\xee\xc5\xf7\xda\xb5\x8a\xe0\xb1\x4d\x61\x56\x9f\x4f\xd9\xb0\x3f\xed\x83\xa1\xf1\x4a\x69\x4c\xb0\x4d\x44\xd9\x26\x02\x2f\x71\x2b\xeb\x20\x32\x79\x85\xc4\x8c\x8a\xe3\x04\x4b\xcf\x30\x15\x02\x95\x51\xc5\x90\xe9\x4b\x72\xc9\xa2\xd5\x58\x4e\x1f\x8f\xc4\x3e\xc6\x9a\x6e\xff\x84\x0b\x6e\xb0\x02\x2c\x5c\x3d\x14\xbe\x11\x67\xdd\x01\xbe\x2a\xae\x35\x51\xbd\xb6\x34\x7c\x75\x31\xb4\xad\x07\xac\x2d\xb9\x56\x2e\xde\xf4\x32\x23\x17\x3b\x95\xcb\x6b\x98\x10\x68\x89\xa5\x4d\x08\xfc\x9b\x76\xb2\x27\x76\xbe\x7e\x03\x43\xf5\x76\x57\x41\xc6\xb1\xd6\x93\xb6\xdc\x46\x03\xa5\xa0\x34\x1a\x50\x78\x62\xf2\x01\x75\xad\x06\x8c\x80\xb5\xac\x06\x15\xa4\xab\x32\x1a\x54\x93\x8e\x95\x24\x8a\x25\xc1\x40\x92\xb0\x9e\xc9\x40\x25\x85\x6b\x63\x64\x50\x71\x79\x8b\x81\x2c\x47\x27\x80\x5a\x82\xf2\x7f\xe9\xdc\x84\x59\xfb\xbe\x3b\x36\x32\xd0\x8e\xb7\x48\xff\xf8\xe4\x94\xbb\x64\x6d\x9b\xcc\x6b\x5c\x37\xb4\xd4\x45\x0a\xa9\xec\xe1\xc2\x2d\xa7\x4e\x61\x2a\xb7\x71\xa9\xbb\xee\x75\x3d\x4b\x85\xb2\x35\xd1\x14\x8d\x3e\x55\x34\x18\x7a\x3e\x45\xa3\xdb\x05\xe5\x7c\x4a\x0f\xa9\x1c\x3a\x06\x4c\xe1\x14\x66\x48\x80\xb3\xda\x8e\x93\x13\x2c\x61\xc5\x13\x59\xaf\x7d\xb2\x8e\x63\xa0\xd3\x83\xbe\x75\x50\xa2\xdb\x05\x05\x9c\xe6\x67\xe4\x34\x0f\x44\xf3\xa2\x80\x19\x02\x1c\xbc\xcc\xc9\x6b\x86\x6a\x52\x12\xec\x1d\x18\x97\xcb\xd8\x84\x38\x4d\x6b\xdb\x86\xb4\x9e\x81\xa7\x56\xfd\x37\x60\xbe\x03\x63\xa2\x7a\x0f\x24\x3c\x5b\x15\x2e\x32\x1d\xac\x87\x0c\x03\x38\xe7\x35\xbc\x3e\xaa\x86\x7b\xd3\xa2\xae\xe3\x4e\x34\x11\x76\x5a\xa2\x7a\x28\x90\xbe\xb4\xb8\x03\xad\xe9\xbb\xc0\xce\x06\x5b\xed\xc0\x03\xf0\x26\xcf\x53\xff\x57\xac\xc1\x79\x3f\x1e\x55\x7e\x55\x4f\xf6\x2a\x9a\xaf\xfe\x4c\x05\xba\xff\xfb\x3e\x0c\x2b\xb0\x3f\xa2\x9f\xc9\xd7\x93\xb6\xcf\xfd\x81\x10\x55\x71\x79\xe0\x1e\xbc\xc3\xb5\xa0\x41\x8f\x45\x1b\xc3\x67\xe4\x27\xc6\xb5\x31\x7c\x8e\x7f\xab\xa7\x93\x8d\xe1\x8b\xc0\xe5\x2a\x91\x64\xa8\x31\xec\xf7\xf0\xcf\x02\x86\x69\x63\xd8\x1f\xe0\xdf\xf4\x80\xb2\x31\xec\xaf\xe2\xa7\x39\x85\x22\x0d\xcc\x19\xd8\xf3\xeb\x93\xe0\xc5\xef\xe5\xb6\xb4\xe0\x84\xf6\xa6\x8e\x3b\x6a\x35\xcb\xf9\xef\xd8\x25\x5d\x6e\x3c\x2a\xd4\x0d\xbc\x79\xdc\xc5\x2b\x9c\x7a\x74\xac\xea\xd6\x77\x2b\x17\x9f\x05\x7e\x3d\xb5\x9a\x57\x0e\x8d\x3d\x83\xc9\x05\x59\x31\x87\x4b\xf8\x0d\x59\xe4\x58\xec\x3e\xf4\xc3\x83\xfb\xd0\x83\xfb\xd0\x1f\xd1\x7d\x48\x4e\x8a\xfb\xf2\x21\x7a\x93\x8c\x77\xe7\xd3\x53\x22\x4c\x85\x9c\x3f\x4d\xc6\x19\x79\xd9\xf9\x45\x5b\x17\xe6\x28\x49\x75\xa7\x95\x4e\x97\xbc\xa3\xff\x57\x41\x23\x27\x58\x94\x67\xa3\xc4\xe9\x71\xc3\xb7\x9b\xca\x5a\x43\xb6\x5b\x2a\x6c\x09\xc7\xe7\x09\xfa\x8c\x57\x5d\x67\xed\xca\x77\x86\xb8\xb5\xd7\xa4\x6b\x47\x09\xc8\x0a\x04\xc8\xad\x15\xbc\xe1\xc4\x52\x53\x3a\xd5\x86\x34\x9e\xf8\x08\x24\x5c\x9f\xfe\x0e\x8f\x26\x85\x4e\x4a\x90\xc1\x71\x88\x92\x33\x18\x70\x09\x48\x8e\x49\xd0\x79\xde\x2c\x01\x35\x6b\x4b\x4d\x9c\x94\xc4\x9c\x24\x4a\x8f\xd2\x3c\x44\x49\x36\x06\xb3\x3c\xc9\x50\x40\x0f\x03\xf1\x24\x8b\xf3\xf3\xcc\xb3\xc9\xd5\x2d\x65\xee\xbd\xec\x15\x1d\xc7\x2b\x31\xa2\xd7\xbc\x7f\x78\x12\x67\x10\xc6\xc4\x18\x71\x2a\xb9\x2a\x36\xf6\xbd\x8a\x2b\x0a\x19\x8e\x6b\x6d\xab\x4b\xb4\x07\x0d\x0f\x6d\x59\x20\x6d\xc9\xb5\x41\x60\xd1\xa1\x63\xde\x8a\x3a\xdb\x87\xef\x3f\xbf\xd9\xf9\x71\xf7\xe8\xe3\x9b\xed\xfd\xcf\xfb\x7b\x47\xbb\x6f\x77\x76\x7f\xfc\xfc\x71\xef\xed\xb6\xb6\xa1\x15\xb6\x64\x32\xaa\x9d\x59\x18\x7f\x80\x23\xd4\xa2\x4f\x28\x3f\x3c\xcf\xcb\x2d\x41\x65\xd6\x6e\x07\xe5\x4c\x01\xec\x3f\x6f\x07\xe0\xf9\x9a\x79\xe2\xa1\xae\xed\xa4\x73\x2d\xda\x8c\xdf\x99\x41\x67\x15\xd5\x4e\x50\x31\x3a\x6f\x34\x73\x82\x6e\x6b\xb8\x0d\xb5\x6d\x9d\xca\xa0\xb7\x83\x70\x28\x7f\x0f\x2f\x38\x7d\xca\xf9\x69\x89\x8a\xd6\x40\xa3\x74\x6a\x1c\x8f\xd3\x0a\xf8\x09\xd3\x0a\x78\xbe\xda\x06\x5d\x9d\x98\xe6\xd0\xec\x27\xe3\x09\x62\x05\x03\x90\x82\xef\x7e\x17\xda\x33\x9d\xe2\x5e\x87\xc0\xab\xdd\xde\xc9\x48\xf0\xcd\xae\x3e\x04\x8a\x41\xf8\x77\x1d\x2f\xc3\x52\xcf\x0c\xe0\x1d\x2a\x5f\x56\xc0\x72\xc3\xb9\x48\xea\xd2\x5d\xcf\xe2\xb9\x76\xa0\xc2\xdd\xe5\x48\x0b\x5f\xd1\x65\xc6\x78\x54\xe4\xd3\x23\x34\x7a\xf1\x30\xcc\x35\x87\x59\xb9\x9c\x55\x25\x50\x95\x2b\x5c\x7c\x88\xf1\x3b\x18\x66\xb7\x13\xa8\xe6\x1d\xad\xaa\x11\x6e\xf6\x6e\xf7\xaf\x09\x56\x58\x03\xe0\x35\x00\xcd\x7e\x13\x0c\x41\xb3\xd7\xbc\x6b\x39\xb9\x88\xe2\x05\x0c\x05\x2d\x7f\xc2\xb0\x25\xc0\x9b\x8c\xe9\x3c\x45\x09\x55\xd0\x4f\x2f\xc1\xe0\xaf\x53\xbc\xd5\x11\xfe\x72\x21\xae\x1d\xc1\x31\x2c\x6a\x2c\x7f\xfb\x4a\x0b\x8b\xb4\x93\xdb\x8c\x1e\x73\xb6\xf6\x8e\x1e\x23\xa8\xc1\xcf\x98\xbe\xa2\x7d\xa1\x38\x24\x53\x58\x1a\x9f\x06\xed\xce\x2c\x3f\x6f\xf5\x07\x2f\xda\x6d\x3f\xf1\xb7\x26\x30\xfa\x02\x92\x91\x46\x7d\x45\x3d\xf4\x90\xab\x4c\xc6\x19\x8c\x77\xca\x5d\x1d\xcc\x3a\x99\x10\xf5\x4d\xe0\x05\xeb\x89\x4d\x32\x3e\x15\xc8\x26\x9c\xe0\x82\x74\x0d\x95\x38\x0d\x9c\x27\x78\x23\x14\xa6\xa5\xed\x5d\x6d\x62\x53\x83\xaa\x2e\x3a\x72\x71\xd7\x0b\x40\xbf\x1d\x80\xfe\x73\x45\x23\x1b\xb4\xb5\xaf\x6d\x72\x3c\xd8\xec\x37\x17\x72\x75\x81\x85\xd8\xd3\x30\x25\x28\x02\x6a\xfc\x91\xa7\x6a\x52\x39\x77\x33\x24\x35\x09\xb9\xd4\x65\x8d\xcd\x9c\x9e\xb7\x98\xb2\x52\x8d\xae\xc0\x43\xa8\xd2\x64\xda\x25\x63\x40\xf7\x4d\x1e\xde\x15\x28\x69\x54\x36\xdc\x70\x31\x38\x1d\xfb\x0d\x8a\x9b\x62\xeb\x6e\xb5\xc1\xd5\x15\x68\xf4\x1a\xe2\x20\xa1\xdb\x05\x91\xe0\x43\xbc\x0d\xe1\x43\xaf\x61\x82\xc1\x12\x44\x99\x43\xec\x5a\xdc\x1b\x16\xea\x85\x61\x70\x05\xe3\x04\x87\x69\xda\xc1\x0b\x74\xf8\xa7\x49\x36\x37\xe7\x55\x73\x74\xcb\x7f\x4d\x52\x37\xaf\xbc\xaf\xb8\x7c\xd7\x42\xea\x96\x1c\x37\xaf\xc7\x72\x47\x0b\x79\xce\xc7\x6d\x70\x29\x76\x53\xd0\x71\x33\xda\xd1\xdd\x70\xda\x6f\x41\x5a\x65\x69\xaa\x22\xad\x73\x7d\xa9\x45\x5a\x42\x59\x65\x3b\xa1\x93\x52\x69\xde\x4d\x4a\x6b\xc1\x71\xfb\xce\x1b\xd3\x9c\x41\x75\xe2\xe4\x2c\x89\x61\xfc\xe6\xb2\x62\x9d\xb9\x2d\x6f\xd6\xa4\xe0\xd1\x7d\x91\x70\xbe\x90\x86\x47\x37\x20\xe2\xd1\x5d\x51\xd1\x76\x32\xa0\xcd\x08\x42\x2e\xd2\x46\xe5\x4d\xfb\xa5\xa9\xc7\xd7\x68\x7d\x04\xbd\x94\x54\x50\x71\x13\xd2\xd2\x5d\xdd\x74\x74\x4c\x69\xb2\xf2\xde\x52\x99\xed\x37\xc1\x6b\xaa\x6e\xb0\x90\x30\x37\xa7\xf9\xc2\x7d\xbd\x72\x2d\x7b\xc1\x2a\x9e\xc2\x91\x24\x2d\xd6\x98\x16\xec\x2b\x2d\x75\x2b\x0b\xa7\x90\x5e\xa7\xc2\xbf\x1c\xca\x29\x83\xc3\x75\x6b\x35\x79\xc6\xc8\x32\x13\x90\xf6\x02\xa0\x1f\xe2\x10\x07\x54\x7a\x4f\x1f\x6c\x00\xdf\x65\xed\xef\xda\x5d\x6d\x1b\x59\x26\xbf\x32\x99\x5d\x92\x73\x57\x56\xc3\x71\xff\xc4\xdc\x52\x34\x7b\x17\x78\xf3\x61\xb3\x42\xa7\x4c\x93\x08\x62\x8d\x6c\x00\xbe\x23\x15\xde\x62\xee\xd4\x1c\x47\xd5\x82\x72\xe7\xc3\x79\xd3\x81\xf2\x9a\x75\x1c\x8b\xa5\xd8\xbe\x1b\x12\x88\x92\x97\x1d\xf4\x9a\xf4\x7d\xbe\xd6\x66\x5a\x0b\xca\x19\x7c\x1b\x7c\x27\x77\xfc\xae\x91\x32\xaa\xa2\xda\xf3\xf3\xb5\x00\x68\xae\x87\x37\x18\xaa\x1a\xb6\x15\x4a\x16\x8f\x71\xe5\xce\x47\x29\x2c\xa3\x24\xa9\x33\x4e\x0e\xa3\xcc\x6f\x3c\x42\xdc\x96\x47\xac\x3c\xf5\xc6\xe9\xe6\x03\xa5\xc7\x79\xaa\x1a\xa9\x4d\x3d\x22\x94\x67\xa8\xbc\x6b\x4f\xc5\xd0\x68\x15\xbb\x47\xc5\x15\x05\x6a\x29\x2d\xd3\xa0\x71\xa3\x77\xd1\x00\x2b\x80\xed\x29\xc9\x00\xd1\xdf\xc2\x81\x67\xad\x17\x00\xf5\x95\xa8\xa1\xcb\xdd\x31\xc0\xc6\xed\xaa\x31\x86\xfb\x0d\x8c\x26\xab\x03\xee\x1d\xd3\x48\x43\xd4\x08\x38\x6d\xda\xda\xf0\x5a\xc7\xde\x9c\x04\xba\x1d\x62\x68\x3c\x07\x36\x14\x91\x46\x43\xeb\x8d\x03\x52\x15\x5f\x43\xdf\x07\x47\x39\x3a\xc8\x43\xfb\x95\x0b\x9b\x3c\x4f\x87\xe6\x0b\x07\x1c\xd6\xef\x86\xe6\x0b\x1d\x4e\xa8\xc6\x43\xf3\x85\x0b\xee\xc8\x02\x3c\xf2\x40\xaa\x4d\xcb\x37\xce\x3a\x6d\xd0\x23\x1f\xac\xda\x71\xf9\xc6\x09\xa9\xd2\x5f\x79\xe5\x82\x75\x0d\x99\xfd\xc5\x55\x52\x1f\x34\xf5\x9d\x0b\x9a\xb1\xec\xd0\x2b\x2d\xae\xa5\xfb\x85\x7e\x40\xdb\x18\xf6\x7f\x08\x1a\x9e\x73\xd5\xc6\x70\xd0\x53\x3e\x92\xff\x37\x86\x03\xe2\xb0\x44\xe6\x77\x63\xd8\xef\x07\x0d\xf5\x0c\xb9\x31\xd4\x1f\xaf\x4f\x82\x7e\xef\x1b\x0b\xb6\x84\xf7\xca\xd5\x11\xc7\x92\x0c\x2d\x0a\x38\xa6\x1c\x18\x27\x19\x92\xb1\x92\xf0\xc3\x9a\xf6\x74\xa2\x7f\x5c\x35\x9e\x1d\x61\x95\x92\x0c\xc9\xa0\x4a\x49\x86\x9e\xaf\x69\x45\x5e\xe8\x0d\x0c\x9e\x3d\x5f\xd0\x06\x2e\x5f\x33\xa0\x99\x69\x47\xb8\xe7\x38\x7f\xa2\x80\xcb\x5f\x69\x27\x43\x37\x70\xda\xd2\x4a\x55\xf8\x6a\x91\xa6\x17\x94\xbe\xa1\x67\x56\x92\x21\xae\xcd\xbf\xbe\x55\xe0\x25\x8e\xe3\x62\xbf\xa9\x7e\xed\x6c\xdb\x0f\x8e\x53\x0f\x8e\x53\xff\x9a\x1c\xa7\xa8\xa2\x79\x4f\x3e\x53\x35\xdc\xa0\x7c\x2b\x0d\x75\x73\xca\xc9\xcd\x1c\x19\x2f\xa7\xe3\xd9\x15\x9c\x4f\x60\x26\x2e\x52\x05\xf4\xaa\x0b\xde\x04\x89\x10\x28\xaa\x06\x6f\x8b\x6e\xfb\x0a\x0f\xb3\xd7\x9a\x17\x73\x89\x58\xe6\x3a\x3a\xfd\x7b\x75\x05\x9a\x4d\x4d\x6c\xe7\xfc\xb2\x1a\xfd\xb1\x0e\xba\x5d\x7e\xf9\x2a\xc9\x14\x4c\x96\x8e\xb6\x33\x86\x48\xbd\xd0\x40\xee\xcc\x34\x4b\x7e\xf5\x99\x88\x21\xcf\x36\x48\xd9\x7e\x51\x6f\x7e\x7b\x33\xc3\xed\x96\xb2\x01\xb5\x5e\xd0\xe2\xa8\xb7\x35\x2a\x6a\x14\x54\xaf\xf5\x98\x0d\x2e\xf0\x8c\x25\xb7\xa2\xb4\x3d\xa2\x79\xe2\xbf\x34\xb5\xa2\x02\x62\xce\xe4\x73\x4d\x8f\xa4\x44\xa7\x52\x9c\x8c\x46\x90\xdc\xc5\xa0\xe3\xe3\xa1\xde\xb9\xb8\x6a\x67\xee\x1d\x39\xd1\xd8\xf0\xf2\x2b\x1c\x99\x77\xc7\xa8\xef\x35\xf1\x92\xec\xc2\x2b\x9c\x51\x69\xa6\x61\x55\x4d\xf0\x73\xf7\xb5\x73\x93\x81\xbd\x07\xe8\x92\xb3\x05\xd7\xdf\x94\xee\xf9\xf4\x34\xc9\xec\xd0\x55\x28\x1f\x43\xbc\xbe\xf0\x5a\xe0\xb8\x43\xe7\x6d\x38\x9b\xc1\x8c\x4c\xd7\x30\xa3\x37\xc9\x3c\xe3\xc0\x6a\x5e\xb0\x81\x57\xe8\x78\x3e\x49\x22\x2c\x30\x39\x96\xf5\x2a\x60\xde\x0c\xf9\x88\xc1\x93\xd5\xb1\xd6\x20\xf0\xae\x2f\x36\x39\x57\x0d\x80\x30\x6e\xd1\xb3\xc4\x25\x87\x21\x0a\xd3\x94\x0d\x04\x3f\xee\xa4\xdd\x9c\x84\x52\x54\x94\xc9\xaf\x4a\x04\x54\x72\x18\x3e\x09\xcb\x00\xff\xe5\xbc\x4a\x22\xb5\x7b\x4e\xc5\xcd\x81\x11\x3e\xf0\x36\xc7\x1b\x2e\x28\xd5\xf4\x73\x87\x71\x73\x4b\x0b\x36\xe5\x1e\x6f\x6c\x10\x15\x69\x94\x64\xea\xdd\xd3\x25\x49\x25\xe3\x98\xb1\x6a\x99\x6f\x88\xcf\xac\x44\xa1\x36\xcb\x37\xd5\x86\x21\x7a\x66\x61\xdb\xfa\x96\x98\xd7\x5a\x4b\xd5\x64\x79\x4c\xe8\x22\x83\x73\xb4\xc1\x6b\xd0\x6c\x82\x61\x3d\xf7\x50\x85\xa8\x5e\x27\xd1\x1b\x50\x16\xaf\x59\xd4\x8c\x25\xd4\xe4\xaa\xfb\xbd\xd2\xe2\xe5\xa7\x2a\x5f\x4b\xb9\x0f\x47\xa8\xae\x54\xe1\x54\x65\x5a\x32\x83\x6a\xd1\x59\x36\xec\x21\x32\x89\xbf\x66\x52\xd8\x15\xcb\x4d\xcc\x63\xcf\x99\xbb\x42\x68\x36\xac\xad\xdb\x91\x57\x5b\xa7\x17\xd0\x57\x59\x63\xfd\x04\xe6\x2f\x42\xee\x5a\x53\x83\xd2\x70\x3a\x43\x97\x2e\xf3\x74\x0d\x0d\xa1\xc6\x6c\xd7\xb9\x5a\x91\x95\x43\xa1\x7d\xdd\x80\x7a\x3c\x72\xdd\xc2\x98\x75\x5e\x5a\xd5\xea\x2a\x6b\x66\x71\x2f\xb5\x23\xc8\x15\x87\x56\x74\xdb\x9e\xca\x7b\xda\x8b\xe3\xcb\x7d\x48\x4a\x4b\xdf\x21\xd7\xbd\x8f\xb5\x7e\x9e\x78\x02\xca\xd5\xa2\x91\x6c\xc9\x5e\x33\xc9\xfd\x50\x46\xa4\x6e\x17\xcc\x67\x31\xbd\x0d\xc0\xae\x4a\xb3\x2f\x58\x8d\x47\x39\x52\x42\x44\xd0\xc2\x5c\x7f\xa4\xb1\xca\x24\x2c\x2d\x2d\xee\x9b\x33\x60\xfd\x72\xa9\xb1\x64\xb3\xb9\x4f\xd7\x64\xcf\xe4\xd7\xcf\x92\xed\xc0\x42\x6a\xe3\x60\x43\xc5\x58\xc2\xaa\xdd\x58\xd9\xb0\x0f\xb8\xd8\xfd\x5d\xfb\xce\x2e\x85\x94\x1a\x60\x2b\xd7\xe3\x3a\x28\xd7\xa9\xbb\x5d\xce\x0a\xf0\x0c\x16\x97\x68\x92\x64\xe3\xc7\x3a\x27\xaa\x14\xb2\x43\xd6\x71\x4f\x59\x8b\x46\x5a\x8c\x08\xf7\xf9\x9c\x12\xb5\xed\x4e\x1b\x71\x1d\x93\x5c\x07\xa0\xd9\x54\x8f\x95\x16\xd8\x8b\x3e\xd1\x71\xb3\xec\xbc\xc2\x6a\x7b\x7d\x12\xf4\x6b\x27\x69\xfe\x8d\x4c\xb1\xc4\x4f\xa4\xda\x16\x5b\x60\x90\x05\xc6\x58\xd5\xdd\x15\xff\x96\xd6\x4f\xf2\xb4\xa6\x3f\x9e\x18\x9f\x57\xcd\x17\x0e\x93\x2c\x7e\x2f\x6d\xb2\xf8\x49\x35\xca\xe2\xe7\x17\x46\x33\x86\x59\xd6\xd9\xd2\x12\x86\x59\xcb\xa3\x66\x39\xcb\x2c\x2e\xbe\x9c\x69\x56\x96\x70\xd9\x66\xf1\xd7\x1b\x18\x67\xf5\x62\x15\xd6\x59\xda\xfa\xa2\xf2\x37\xb3\xcf\x62\xca\x7b\xec\xb3\xcb\x65\x5e\x11\x48\xd6\xb0\xcf\xd6\x4e\x60\x7a\xdf\x57\xb7\x1d\xa7\xeb\xcb\xb1\x12\xad\x60\x39\x66\x52\xcb\xb8\xd8\x89\x7e\xbf\x01\x43\x99\x05\x2b\x58\x8a\xe3\xb0\xb8\x8e\x1b\x9a\xfd\xa9\xee\xb8\xcc\x8d\x6c\x47\xcb\xf7\x75\x17\x5b\xe9\x7d\x0d\x6e\xbd\xbf\xc4\x5a\x55\xb7\x3f\xab\x56\x0a\x25\xe4\x78\x52\xd6\x8f\x35\x1e\xc1\x02\x85\x49\x56\x1d\x6f\xdc\x22\x36\x3d\x1b\x5d\x38\x2d\x28\x58\x47\x7f\xbf\x68\x62\xb0\x42\xc6\x87\xa5\x42\x26\x21\x58\x4c\x93\x8c\x6c\x6b\x70\x2b\x7a\x3f\x62\x16\x33\x1c\x8c\x93\x33\x98\xa9\x2e\x6e\xb6\xd1\xa3\x2a\x80\xb9\xc3\x37\x8e\x1a\x43\xe4\x0d\x04\x32\x1a\xb4\x31\x0b\x8f\xea\xfb\x08\x2a\xe4\x32\x13\x8f\x86\x09\x6f\x20\xe7\x66\x21\x3f\x83\x45\x71\x5e\x24\x08\x41\xe2\x5a\x4a\xf1\x6b\x80\x15\xd2\x97\x1b\x51\xf8\x9c\x1c\xee\x94\x6a\x78\x1b\xe2\xe9\x24\xcd\x01\x49\xc6\x08\x8d\xfc\xf1\xe2\xcd\x48\x34\x4b\x91\xdc\x36\x6b\x1b\xcd\xdb\x27\x03\x1e\xea\x3a\x02\xee\xf8\xe8\xdc\xed\x92\xb3\x99\x70\x8a\x67\x1b\x89\x1d\xcb\x0c\x9b\x98\x12\x58\xf6\x40\x7a\xe8\x95\x26\x5f\x20\x08\x41\x99\x64\xe3\x14\xf2\x30\x3f\x14\xb2\xa3\x8b\x2b\x6a\x9e\xa1\x81\xde\x68\x7c\x2b\xda\xe2\xd5\x15\x38\x6e\x1e\xf7\x4f\x9a\x27\x6d\x65\x2f\xb2\x20\x58\x0e\x43\x51\x1f\x15\xfc\xa4\x86\x89\xd5\xa0\xe5\x46\x83\xfa\x51\x52\x82\x10\x77\xa1\x7e\x00\x9e\x92\x9b\x2c\x3d\x82\x4d\x5f\x0b\x26\xa7\x21\x65\xa9\xf9\x3c\xc0\x51\xc0\x43\x22\x99\x48\xf2\x66\x79\x20\xa4\xef\x38\xe0\xba\xd9\x97\x30\x4d\xc1\x69\x58\x26\x11\x8d\x16\x44\xae\x6b\xad\x0e\x98\x01\x2e\xcd\xb9\x4d\x82\x62\x15\x80\xd5\xc1\x32\x0e\x67\xba\xc8\x60\x32\x94\x5b\x42\x54\x83\x35\x80\x63\xa1\x54\x93\x1c\x8c\xc7\x27\x0d\xb0\xf1\x8a\xcc\x78\xfd\xfd\x1a\xfd\x50\xcb\xa8\xba\xa9\xb6\x70\x13\x39\x33\x81\x95\x58\x2f\x90\x29\x2a\x16\x15\x3c\x2f\x83\xa5\x6a\x71\xd9\x2c\x96\x5d\xb7\x94\x00\xa5\xd0\x93\x27\x40\x7d\x3e\x56\x7e\x4b\x8f\xb4\xfe\x09\xd7\x19\x44\x82\xa6\xee\xf8\xd6\xe3\xc9\xc4\x42\x9d\xe1\x54\x47\x4d\x1f\x4f\x65\xa0\xed\xe1\x54\x42\xde\xde\xd1\x68\x3a\x70\x5e\x30\x98\x0a\x0e\xbf\xc5\x58\xde\xd7\x60\xea\xc1\xca\xa4\x50\x37\x46\x4f\xec\x3f\x1b\xc4\xad\x67\x40\x07\x49\x6e\x44\x1b\xcc\xff\xa7\xcf\x86\xaf\xbf\xa6\x15\x18\x9c\x1c\xaf\xb2\x22\xea\x7b\x0a\xaa\x41\xf6\x4f\x1c\x2f\xf5\x77\xee\x55\x8d\x8c\xc4\x2d\x96\x35\xb5\xdf\x8c\x1c\xf5\xd7\x32\xa5\xf1\x3b\x64\x86\x64\x04\x5a\xca\x47\x87\xf9\x46\x2c\x23\x37\xe4\x0e\xb2\xce\xf8\xae\xb4\xf5\x6b\xf2\x0e\x5b\xeb\x54\xd3\x88\x8f\x61\xc8\xa0\xfb\x78\xa6\xa1\xe5\xec\xb5\x38\x87\x3d\x36\x2c\x06\xb2\x2a\x3d\xb6\x1b\x33\x79\x46\x06\x7a\x5d\x8a\x59\xc4\x47\xda\x65\x05\xa6\x92\x49\xb4\x90\xd9\x15\xaa\x0e\x0b\x6d\x98\x86\x25\x02\xc7\x27\x58\xa7\x92\x2d\xdc\x88\x7b\x1e\x57\xb3\x8f\x54\x47\xed\xc0\x8c\x64\x47\x29\x2f\xd1\x92\x47\xc1\x4d\x75\xd8\xad\xe6\xbd\x01\x65\x17\x29\x16\x11\xf3\x80\x15\x9c\x5e\x82\x18\x8e\xc2\x79\x2a\xec\xe8\xe5\x1c\x2b\xfb\x42\x43\x69\xb0\xb8\x75\x01\x4b\x36\xed\x1b\x77\x6d\x5f\x7b\x8b\xb5\x43\x2a\x00\x37\xdc\x5f\xd4\xde\x5e\xd7\xba\xef\xc4\x13\x87\xf0\x43\x09\x65\x4a\x96\xbe\x39\x49\x66\x16\x9b\x2a\xc7\x0d\x3c\xb3\x02\xd0\xc0\x93\x0d\xff\x39\x69\x9c\xe8\x33\x8a\x41\x19\xef\x49\xe1\x6c\x9e\xfa\xee\xb1\x29\xbc\xb1\x14\xb1\xcd\x60\xb8\x8e\xfe\xd4\x98\x70\x76\xb4\x4c\xc7\x8c\xa3\xcb\x1f\x0b\xfb\x4b\xb9\xa8\x24\x1e\x15\xb8\xb3\x1a\x45\xf0\xff\x85\xfc\xd1\xa6\x09\x93\xac\xad\x7f\x3c\xa6\x06\x97\x7f\x3c\x69\x2f\xa7\x47\xb1\x93\x04\x11\xb4\xc9\x43\x4e\xe3\x08\xcd\xc8\xea\xa4\x96\x5c\x4e\x9c\xf1\xc0\x0b\xfe\x43\x5e\x83\xbe\x8e\x73\x38\xe6\xf1\x62\xaf\x78\x8e\x70\xa4\x58\x34\xe9\x87\x91\x2c\xda\x3e\x8f\xb3\x6f\x0a\xaa\x96\x6b\x82\x00\xf3\x02\x8e\x7a\xb9\x62\x1d\x78\xff\x75\xbb\x58\xb2\x26\x4a\xe0\x5a\x57\x62\x03\x98\x8e\xd4\x84\x06\x4c\xb4\x1a\x45\x44\xb8\x8e\xe3\x13\x75\x23\xc5\xa2\xa7\xcc\xe6\xe5\xa4\x65\xc5\x9c\xe7\x21\x4e\x78\x00\x79\xb3\x56\xdc\x07\x3b\x81\xc8\x99\xbd\xa3\x93\x6d\x28\xc1\xcc\x5b\x67\x01\x50\x93\x16\xe8\x9b\x48\xad\x29\x67\x2c\x72\x0c\xd5\x92\x70\xd7\x3c\xca\xba\x18\x30\x35\xdd\xc4\x2d\xc7\x8b\xa0\x6d\x29\x50\x8c\xd4\xbf\xe5\x78\xb9\x72\x30\x54\xe4\x5e\xf0\x92\x1e\x8f\x1d\xc9\xae\xe2\x1d\x81\xa5\xe9\x6f\xac\xca\x2e\x03\xa0\x36\xf5\x8c\xbc\x04\xcb\xc4\x3e\xa7\x6e\x00\xa3\x22\x9f\x56\x7a\xec\x50\x68\x9f\x9c\x39\x75\xdd\xb4\x34\x9c\xfb\x2c\xaf\xbe\x25\xef\xc5\x72\x81\x47\x31\x59\x42\x6c\xb1\x8e\xaa\x62\x4b\x8b\x1a\xbe\x50\x7c\xdd\x8b\xfc\x22\xab\x9c\x7a\xe8\x4e\x55\x7a\x76\x3b\x93\x20\xc8\x15\x31\x46\xc0\xef\xc0\x80\xf8\x12\xb5\xd7\xd9\xe4\x50\xc8\x69\x4f\xb3\xca\x1a\xd5\xc6\xad\x6a\xed\x39\x47\xc0\x0f\x10\x75\x35\x51\xcb\xae\x80\xd5\x01\x29\x46\xd7\xec\x12\x83\x94\x20\x1c\x21\x58\x88\xa9\xa4\xe2\x7a\x83\xb9\xec\x2a\xe1\x4a\x27\x23\xa5\x8a\x27\x8d\x0c\xa8\xa0\x1a\xcb\x93\xb6\xeb\xab\x5d\x0d\x92\xe5\x4d\x18\x62\x26\x78\xab\x10\x42\xb5\xa4\x10\xf8\xae\x1a\x2f\x0c\xbb\xb2\x51\x0d\xb3\x50\x88\xe9\x61\xf4\x95\x81\x5e\x01\xc9\x5d\x4a\xb4\x6f\x60\x45\x51\xff\xd5\xe2\x74\xee\x7f\x5e\xf3\xdf\x37\xc3\xe8\x0f\x7c\xfe\xdb\xf3\x39\xfb\x6d\xb1\xb9\x9a\x11\xf9\x66\x6c\x6e\xe6\xc4\x5c\x72\xa9\xb0\x58\x53\xaf\xbd\xde\x7a\xa1\xe3\x50\xbf\x76\x36\x66\x4e\xfe\x4a\x2d\x7e\x72\xcf\x34\xdf\x54\x9d\xb1\xa3\x5c\xdb\x15\x7c\x11\xea\x2d\xd0\x07\x2b\x3a\x6e\x6d\x1a\xb8\x90\xa6\xc3\xb4\x86\x9c\xa6\x45\x30\xce\x4c\xb5\x38\x1b\x1e\x76\x50\xd3\xbf\x09\x4a\x1b\xae\x8a\x9a\xb4\x7a\xb4\x74\xff\xd4\x11\x17\xf3\x69\xd0\x36\x6d\xcb\xce\xf3\x60\x47\x17\x6a\x9d\xe6\x57\x9d\xe3\x8b\x9b\xb9\xd7\x27\x41\xff\xd9\x37\xe6\xf0\x75\xb4\xf8\xf2\xed\xbc\xc6\xed\x5b\x25\x20\x16\xf9\x2d\xdd\xaa\xe6\xfa\x05\xdc\xb9\x71\x03\x77\x6e\x5e\x8f\x9d\x7b\xee\xe0\xce\xb5\x4b\xb8\x73\xe3\x16\xee\xdc\xb8\x86\x3b\xb7\xef\xe1\x3a\x5b\x5a\xc2\xe1\xcb\x0a\xe9\x75\xcf\x57\x71\x8f\x2a\xef\xe2\x1e\xdd\xec\x32\xee\x51\xdd\xdb\xb8\x47\xee\xeb\xb8\x47\x77\x72\x1f\x77\x7e\x67\x17\x72\x8f\x96\xb8\x91\xfb\xfc\x5b\x9b\x7a\x35\x9c\x2d\xe7\x75\xbc\x2d\xd5\xf8\x5f\xf4\x41\x61\x72\xc3\xe1\x72\x6e\x7a\x5c\xce\x2d\x47\xc8\xb9\xcf\xe7\x72\xae\x3b\x5d\xce\x4d\xaf\xcb\xb9\xe9\x76\x39\x77\xf8\x5d\x7a\xda\x5b\x66\x22\xfe\xe6\xae\x97\x47\xd5\xbe\x97\x47\x37\x74\xbe\x3c\xaa\xed\x7d\x79\xe4\x71\xbf\x34\x6b\xb8\xe9\x7c\xac\xf0\xc0\x5c\x76\x42\x2e\xe3\x83\xf9\x7d\x9d\x19\xd9\xed\x82\xad\x7c\x76\x49\xa2\xe8\x80\x56\xd4\x06\x83\x5e\xff\x7b\xf0\x29\x81\x78\xa4\x7e\x9e\x27\x69\x0a\x1f\x75\xbb\x18\xec\x13\x2c\xa6\x49\x49\x6e\x9a\x93\xd8\xad\x05\x3c\xbd\x04\xe3\x22\xcc\x10\x8c\x03\x7a\x65\x3e\x1f\x81\x68\x12\x16\x63\x18\x00\x94\x93\x5b\xe6\x33\x58\x94\x79\x06\xf2\x53\x14\x26\x19\xb9\x32\x48\x2e\x78\x3e\x22\x57\x6c\xa9\x1b\x15\xbf\x67\x4f\x82\xf3\x86\x65\x99\x47\x49\x88\x60\x0c\xe2\x3c\x9a\x4f\xc5\x2d\xac\x51\x92\xc2\x12\x6b\xb9\x10\x34\xf8\x65\xf8\x46\x3b\xa0\x86\xa5\x30\x7d\x44\xb5\x36\xfc\x59\x5c\x95\xe7\x97\xce\x0b\x58\xa2\x22\x89\xe8\x9d\xf9\x24\x8b\xd2\x39\xb9\xbc\xc8\x3f\xa7\xc9\x34\x61\x8d\xe0\xe2\x84\x12\x25\xae\x0f\xe5\x60\x5e\xc2\x80\x20\x1c\xb0\x5b\xfe\x01\x98\x42\xd2\x3f\x76\x55\x3f\x50\x2e\xca\x07\xa0\x24\x17\xc1\xe9\xdd\x7b\x16\x1c\xa0\x84\x29\x41\x2e\xca\x67\x09\x14\xd1\x00\x38\x8e\x04\x8c\x64\x52\xc4\xc4\x45\x8c\x5c\x24\xee\xe1\xf9\x24\x9f\xea\xfd\x49\x08\x56\xa3\x79\x91\xd1\x18\x01\xb8\xeb\x39\x28\x73\xd2\x2e\xb9\x77\x8d\x68\x66\xb2\x51\x9e\xa6\xf9\x39\xee\x63\x94\x67\x34\x77\x64\x39\x64\xa3\x78\x38\x81\x20\x3c\xcd\xcf\xe8\x45\x5b\x3a\xec\x59\x8e\x92\x88\xd2\x9f\x8c\xc8\x4c\x8e\x34\xfb\x54\x4e\x42\x7a\xdd\x9f\x92\x8f\xc4\x04\x78\xc4\xdc\x78\x78\xcf\x0a\x40\x34\xc4\x30\x43\x49\x98\x02\xcc\x5f\xb8\x5d\xb3\xc7\x1d\x8e\xc7\xfb\x6d\x70\xb0\xf7\xee\xf0\xe7\xcd\xfd\x6d\xb0\x73\x00\x3e\xed\xef\xfd\xb4\xf3\x76\xfb\x2d\x68\x6c\x1e\x80\x9d\x83\x46\x20\x22\x09\xf0\x28\x02\x60\xef\x1d\x89\x2a\xf0\xf7\x3b\xbb\x6f\x03\xb0\xfd\xe7\x4f\xfb\xdb\x07\x07\x60\x6f\x1f\xd7\xb6\xf3\xf1\xd3\x87\x9d\xed\xb7\x01\xd8\xd9\xdd\xfa\x70\xf4\x76\x67\xf7\x47\xf0\xe6\xe8\x10\xec\xee\x1d\x82\x0f\x3b\x1f\x77\x0e\xb7\xdf\x82\xc3\x3d\xd2\x26\xab\x6d\x67\xfb\x00\xd7\x67\x84\x1b\x08\x70\x5d\x8b\xe2\x0d\x80\xcd\xdd\xb7\x60\x77\x6f\x77\x67\xf7\xdd\xfe\xce\xee\x8f\xdb\x1f\xb7\x77\x0f\x3b\x60\x67\x17\xec\xee\x81\xed\x9f\xb6\x77\x0f\xc1\xc1\xfb\xcd\x0f\x1f\x70\x6b\xb8\xba\xcd\xa3\xc3\xf7\x7b\xfb\x18\x51\xb0\xb5\xf7\xe9\x2f\xfb\x3b\x3f\xbe\x3f\x04\xef\xf7\x3e\xbc\xdd\xde\x3f\x00\x6f\xb6\xc1\x87\x9d\xcd\x37\x1f\xb6\x69\x6b\xbb\x7f\x01\x5b\x1f\x36\x77\x3e\x06\xe0\xed\xe6\xc7\xcd\x1f\xb7\x49\xa9\xbd\xc3\xf7\xdb\xa4\x93\x18\x92\xa2\x09\x7e\x7e\xbf\x8d\xdf\xe2\x56\x37\x77\xc1\xe6\xd6\xe1\xce\xde\x2e\xee\xcf\xd6\xde\xee\xe1\xfe\xe6\xd6\x61\x00\x0e\xf7\xf6\x0f\x45\xe9\x9f\x77\x0e\xb6\x03\xb0\xb9\xbf\x73\x80\x29\xf3\x6e\x7f\xef\x23\xe9\x29\xa6\xee\xde\x3b\x0c\xb5\xb3\x8b\x8b\xee\x6e\xd3\x8a\x30\xe5\xf5\x01\xda\xdb\x27\xcf\x47\x07\xdb\xa2\x4e\xf0\x76\x7b\xf3\xc3\xce\xee\x8f\x07\x60\x67\xd7\x1c\xd0\x8e\xf4\xc5\xde\x7a\xbf\xb9\x7f\xb0\x7d\x08\x36\x40\xf3\xdf\xcf\x7e\x2d\x2e\x7f\xb8\x78\x31\x1e\x0d\xd0\x59\x7c\xde\x2b\x57\x7f\xc9\x9e\xad\x7d\x99\x44\xf0\xf9\x74\x1e\x7e\x9f\x36\xe5\xd2\xff\xe3\xf6\xee\xf6\xfe\xe6\xe1\xde\x3e\xde\xd7\xf7\x2e\x56\x4f\x9f\x87\xcf\xbe\x3f\x1d\x04\xa0\x77\x31\x78\xfe\xac\xf7\x02\x3e\x8f\xf1\xef\x3e\x0c\xfb\xfd\x1f\x46\x21\xfe\xbd\x1a\xaf\x0d\x56\x57\x63\xf2\x7e\x10\xf6\xd7\x9e\x0f\x4e\x57\x4f\x16\xc5\xaf\xa2\xbb\xf4\x21\xfb\xcb\x63\x0d\x51\xe3\x36\x4f\x8b\x1b\x08\xf1\x4c\x7e\x08\xd9\x3f\xcb\xd3\xcb\x69\x1e\xb3\x33\x29\x23\x3b\x5e\x34\xf9\x42\xf2\x1f\xb2\x57\xc2\x18\x31\xa3\xc6\x88\x19\x78\x49\xed\xc9\x32\x29\xe2\xca\xca\x4c\xdd\x8b\xd3\x7b\x52\x18\x1c\x57\xf5\xea\x15\x18\x3c\x93\x7b\x3e\x5a\x7b\x0b\xff\x79\x82\xc9\x40\x63\x08\xb7\xc1\xcb\x97\xe0\x19\xf8\x2b\xab\xf9\x78\xa6\x9c\x3e\xb8\xac\x21\xcf\x70\xa3\x89\x6e\x00\x48\x46\xa0\xd5\xc2\xed\xbe\x7a\x05\x92\x36\x78\x02\xfa\xa6\xa5\x03\x37\xfa\xd7\x0d\x39\x42\xc7\xc9\x89\x6e\xc1\x30\x7f\x19\xfe\x1e\xd1\xe4\x0b\x5b\xf0\x0c\x7a\x4e\x8a\xd9\xf6\xc5\x0c\x0b\xa1\xd6\xa4\x98\x99\x01\xeb\xf5\xe3\x14\x42\x4a\x8d\xb8\x0a\x61\x27\xc5\xcc\x47\xd5\x02\x32\x1b\x0d\x86\xc1\x4b\xd5\x56\x1e\xc3\x4d\xd4\x9a\xb5\x71\x87\x9f\xb9\x7c\x54\x28\xbc\xdc\xa6\xdf\x4d\x6b\x4f\xc0\xaa\xd7\x23\xa6\xe0\x97\xcc\x4c\x0a\x9d\xc1\x22\x19\x5d\x92\xe8\xec\xe5\x7c\x4a\xc8\x14\x80\x38\x44\xa1\x1d\x21\x95\xf2\x66\x4b\xd0\x94\x90\x14\xab\x40\x51\x88\x5a\xa4\x08\x8d\x99\xda\x77\xb7\x44\x63\x31\x54\xb6\x24\xa2\xdf\xe1\xf9\x54\xd1\x0e\x7f\x38\xee\x05\x40\xfb\xef\x44\xb3\x3e\xe0\x99\xb4\x21\xf0\xe6\x53\xea\xaf\x72\x0a\xb9\xd8\xc0\x35\xad\x9e\xfb\x87\xa1\x85\x1b\xc1\xe3\x0c\xbe\x03\xad\x67\xe0\x29\x98\xb5\x6f\x38\x12\xec\x64\xda\x47\x17\x16\x57\x00\xf7\x08\x7f\xe4\x24\xd0\xa9\xaa\x14\x6e\xdb\x9d\x9c\x14\x33\xb0\x22\x83\xc9\xbb\xfb\xca\xdb\xa9\x60\x40\xb0\xb2\xc1\x65\x30\x61\xc1\x4d\xd4\xe2\xa5\x8e\x67\x27\x4b\x77\x9c\x9d\x6d\xb5\x4e\x61\x34\xa1\x07\x69\x7a\xcf\x67\x6a\x4f\x26\x61\xf9\x39\xcd\xcf\xa9\xca\x2f\x1d\x4c\xe4\xc7\xf9\x6c\x66\x7f\x34\x27\x98\x6c\xca\xd7\x4d\x2c\xb4\x14\x28\x7d\xa6\xbd\x04\xab\xab\xe0\xea\x0a\x78\x01\x5e\x81\xfe\xe0\xb9\xf3\x6e\x68\x36\x4f\x53\xd7\xd5\xd0\xca\xf6\x5e\x6d\x80\x1f\xbe\x07\x4f\x9e\xf8\x1b\x7c\xb9\x01\xfa\x83\x81\xde\xa2\x4a\x2a\x79\xd5\x65\xb9\x66\x9f\x3f\x5b\xd4\xec\x0f\x3d\xbb\x55\x3e\x06\xee\x56\xf9\x5f\xdc\xb6\xc4\xf1\xc9\x13\x59\xd4\xe5\xf0\xa5\xd0\x8d\x57\x20\xd1\x02\x1b\x2a\x8e\x28\xff\x80\xab\xdc\x0a\x4b\x25\xcb\x3d\xe1\xa4\xbc\xd4\x21\xd3\xb0\x44\x3b\x59\x0c\x2f\xf6\x46\xad\x66\xbf\xa9\x79\xa0\x61\xe0\x97\xa0\x8f\xc7\x19\xff\x5c\x01\xdf\x83\x57\x36\xe3\x18\x6c\xc0\x5e\xbe\x32\xc8\x52\xd1\x09\xc2\xb7\xc5\x4c\xc7\x8b\xda\x45\x93\x6c\xdc\xea\x05\xb8\x75\xad\x17\x78\x7a\xdb\x22\x6b\x46\x44\x1d\xc6\xb3\x5f\x97\xc7\x49\x65\x40\xce\xe5\x84\x51\xc2\x18\x6e\x32\xd4\x8a\x75\x18\x13\x27\x26\x92\xfe\x69\x7f\x29\x1e\x27\x72\x8b\x48\xcd\xd8\x12\x12\xc4\xe7\x4f\x5f\x8c\x54\x61\x56\x8f\x96\xdc\x3b\x68\x52\xcc\x86\x40\x94\x1e\xd2\x86\x45\x04\x5b\xf2\x24\xfc\xfd\x9e\xb7\xaf\x15\xb1\x74\x1d\x7c\xc5\xdb\xdc\xda\x59\x62\x9b\xf3\x12\x02\xba\x15\x6c\xae\x3f\xc2\x3a\xeb\x38\x07\x30\x3b\x03\x71\x0e\xc9\xad\x0f\x12\xf6\x0b\x2b\x20\x19\x84\x31\xf8\xf3\xc7\x0f\xef\x11\x9a\xed\xc3\x7f\x3f\x87\xec\x66\x3d\x39\x67\xb9\x9c\xc1\x7c\x64\x7c\xa5\xf1\xc7\x45\x5c\x93\xa6\xa4\x01\x43\xa1\x63\xc2\x83\xaf\xbc\x2b\xf4\x08\x67\x21\xbc\xfe\x82\x1c\xba\xfd\x52\x4e\x92\x0c\x81\x64\x9c\xe5\x05\x1c\xa6\x3c\x52\xce\xf5\x23\x85\x3a\x0f\x19\x46\x1f\x02\xe5\xfd\x41\x02\xe5\xb1\x08\x79\xec\xc6\xa1\x15\x22\x4f\x5a\x74\xc1\xed\xa3\xe5\x29\x46\xe7\x23\x94\xa4\xb6\x17\x13\x99\x62\x34\x92\x4f\x65\xc9\x04\x5d\x0a\xed\xca\xaa\x25\x4a\xc3\xb2\x04\xc7\xa4\x96\x13\xd6\x31\xf9\x51\xda\x33\x65\x0b\x8f\xba\xc4\xdd\x94\xcc\x73\xe6\x75\x85\x25\x12\xa8\x48\x51\x29\x36\xdd\xb5\x32\xa6\x0a\x68\x5c\xe3\xd1\xee\xce\xe1\x01\x5e\xde\x98\xf0\x6a\x9e\xe5\x59\x93\xef\xa2\x9b\x5f\xb4\xa7\x8f\xda\xd3\x8f\xda\x53\xf9\x6b\x78\x9a\xcb\xc7\x51\x92\x65\xf0\x52\x79\x86\x53\x94\xa7\x21\x92\x6f\x66\x49\xa4\xbf\xc8\xc2\x4c\x7f\x31\x4d\xa2\xc2\x7c\x93\xa6\x89\x55\xc8\x28\x61\x80\xcb\x47\xad\xe0\xb8\x08\xb3\x58\xe9\x9a\xf6\xf1\x47\xed\xe9\x50\x7b\xfa\xa4\x3d\x6d\x6b\x4f\xff\xa0\x3d\xfd\x45\x7b\xda\xd5\x9e\xde\x6a\x4f\x3f\x69\x4f\x47\xf8\x89\x3c\x2c\x34\x7e\xe0\x11\xfc\xb4\xf9\x16\x33\xc2\x10\xac\x0e\x02\xe5\xf5\xc1\xce\x8f\xbb\x9b\x87\x47\xfb\xdb\x9f\x3f\x6c\xef\xfe\x78\xf8\x7e\x08\xd6\xd4\xcf\x64\xdc\x87\xf2\xa7\xfa\xcd\xc3\x65\x43\xf0\x15\x18\x2f\x64\x26\x55\xf2\xe1\xf3\xdb\xbd\x9f\x77\xc1\xb5\x5a\xd7\xa7\xbd\x0f\x1f\x30\xfc\xe1\xce\xc7\xed\xbd\xa3\xc3\x21\xe8\xf7\x7a\xbd\xae\xc0\x94\x39\xeb\xbf\x49\xf3\xe8\xcb\x90\x0c\x11\x2c\x25\x29\xd8\xd7\xcd\x28\xca\xe7\x19\x1a\xca\xa8\x63\x74\x91\x94\xb1\x43\x16\x04\x74\x1e\xd4\x0a\xe8\xfc\x60\x47\x7f\xb0\xa3\x3f\xd8\xd1\x1f\xec\xe8\xf7\x65\x47\x3f\x25\xc9\x1a\xf4\x63\x70\xfa\x4e\xac\xca\x5e\x41\xef\xb2\x67\xfb\x6c\xdf\x6f\xc9\x13\x8f\xb0\xaf\x3d\x09\xc9\x9c\xa9\x20\xda\x93\xb4\x95\x03\xcd\x9c\x98\x67\x67\xb0\x40\xa7\x09\x2a\x01\xb1\x0a\x06\xc4\x01\x1c\x3f\x63\xc9\x43\xff\xce\xc2\x58\xb7\x25\x85\x51\x44\xac\x40\xca\x2b\x52\x83\xf1\xce\x65\x1b\x9e\x86\x17\x67\x60\x03\xb4\xfa\xe0\xe5\x4b\xd6\x40\x1b\x3c\xad\xb6\xc7\x2b\x5b\x4d\xe7\xee\x9b\x87\x11\xc6\x70\x9a\x6d\x1d\xef\x0a\xe9\xc7\x97\xa0\x07\xae\xae\xf8\xd3\xab\x57\xa2\x97\x6d\x12\xf7\xb2\xb7\xd4\x0e\x9c\x76\xbf\x85\xff\xbc\x7c\xa9\xd4\x74\x05\xb4\x68\x85\x80\x12\x65\x65\x43\x80\xc8\x2f\xe7\x13\xac\x10\xb7\x08\xc0\xab\x0d\x41\x08\x15\x09\xf2\xed\x29\xff\x66\xf8\xa2\x31\xb3\x29\x46\xe1\xd5\x2b\x40\x0b\x3f\x21\xc4\x6d\x57\x9b\x8a\xb4\xb1\x64\x96\x2b\x82\x84\x83\x06\x4a\x23\x2f\x5f\x82\x16\x45\x04\x3c\xa5\xcd\x55\xb5\x27\x7d\x1f\x79\x07\x39\x05\xc8\x20\x2c\xaa\x71\x39\x3b\xc5\x42\x8b\x28\x31\x61\x84\x71\x5c\xe8\x4c\x1c\xc3\x88\x99\x8b\x56\x07\xdc\x29\x94\x40\xa9\xe6\x2b\x02\xb4\xb1\x41\xda\xc7\xb8\xc7\x30\xea\x4c\x8a\x19\x61\x1a\xfc\x97\xbd\x52\x8d\x21\x2f\x41\x1f\x74\xbf\x53\x3e\x1c\xf7\x4e\xc0\x2b\xd0\x7f\xfe\x5d\x77\x09\x63\x56\x41\xac\xf7\xca\xf4\x6c\x89\x76\x98\x09\xa6\x1d\x80\x67\x01\x78\x11\x50\x1b\xad\x86\x35\x29\xac\x60\x5d\xc8\xab\x99\x2f\xc1\xc0\x78\xf3\x0a\xac\xd5\xb5\xb2\x31\x8a\x88\x5e\xe1\x36\x7a\xe0\xc9\x13\xb5\x3a\x4c\x99\x81\xeb\xe5\xea\xe0\x26\xf6\x27\x46\xf0\x00\xcc\x8a\x7c\x5c\x84\xd3\x21\xae\xf6\xba\x86\xed\x9f\xc1\xbb\x4e\xab\xd8\x98\xb3\x9b\x32\x04\x5a\x25\x34\x2b\x49\x68\xfb\x2c\x20\xe6\xd7\xb6\xc9\x14\xa2\x60\x01\x51\x5b\xd0\xfa\xe6\x8c\xdb\xed\xfe\xf3\x7f\xff\x5f\xfe\xf3\x7f\xf3\x4f\xff\xf2\xff\xfe\x4f\xff\xfc\xdf\xfe\x9f\xff\xfc\x4f\xff\xe1\x3f\xff\xc7\xff\xf5\xff\xfb\xbf\xfe\x8f\x7f\xf9\x7f\xfe\xe3\x3f\xff\xd3\x7f\xf8\x97\xff\xee\xbf\xfa\x4f\xff\xe3\xff\xfe\x9f\xff\xef\xff\x5a\xef\xee\x01\x2a\x06\x34\xea\x69\x89\x0c\xde\xa6\x26\x5a\x4d\x18\xa7\x30\x03\x1b\xa0\x44\x05\x97\xa4\xa2\x43\x2d\xfc\xe9\xdf\x81\x01\x78\x8c\x05\x21\x7b\x5d\xab\x23\xb8\x60\x77\x43\xcf\x24\x3e\x81\x17\x9b\xcc\x13\x88\xba\xf4\xab\xe7\x73\xf4\xa0\x73\x43\x3a\x7d\xd3\xfb\x55\x56\x9b\xe4\xe2\x0d\x43\x97\x79\xb7\xce\xf2\x32\x00\x03\xcd\x80\xda\xc2\x5c\x4e\x13\x2f\x5d\x5d\x01\xf6\xf0\xe7\x86\x0c\x95\xa2\xca\x33\x62\xd5\x55\x70\xc5\xff\xa2\x3c\x43\x49\xe6\xb6\xe9\x93\x85\x45\xf5\x86\x2e\x95\xa4\xa3\xf8\x1f\xee\x28\x95\x90\xaa\x0c\x34\x9b\x31\xc6\x1c\x17\x52\x07\x5d\x19\x5c\x3c\xd6\x2e\x1e\xd0\x07\x9d\x0c\xf8\xe0\x80\xde\xdc\x31\x6e\x2a\xa1\x02\x6c\x80\x46\xc3\x4d\xef\x97\x61\x21\x86\xde\x4f\x74\x34\x9d\xd1\x8b\x3d\xc7\xc9\x89\x16\xbb\x58\xa3\x3b\x9a\xf2\x93\x55\x4c\xf3\xbe\x9b\xe0\xb4\xaa\x46\xaf\x01\x56\xf0\x6f\x17\x8d\x31\xc6\x2b\x1b\xea\x57\x83\x5c\x25\x2a\x14\x6a\x81\x7f\xf9\xdf\xfe\x97\xff\xf4\x3f\xff\x0f\x3a\x41\x34\x45\x48\xca\x7a\x58\x72\xf3\xa9\x3a\xf3\xd5\x99\xcb\xa1\x74\xc1\x89\xea\x4f\x66\x1d\x55\x39\x30\x64\xe5\x64\xd2\x87\xc1\x74\xbb\x51\x9e\x95\x79\x0a\x3b\x69\x3e\x6e\x35\xd8\xca\xc4\x2f\x48\x6c\xbc\x22\xc1\xa1\xdc\x35\xb4\x15\x02\xe8\x3d\xd7\xf4\x3b\xda\xa7\x12\x15\xec\x59\x62\xcf\x2a\x02\x1b\xba\xc0\xd8\x54\xd3\x89\xa9\x34\x52\xc5\x22\x47\x62\xdd\xdd\x0f\x26\x70\x8d\x7e\x14\x4a\xc0\x54\xe7\x1a\x4d\x3d\xe4\xa8\x14\x6e\x0c\xfb\xdf\xe3\xad\xfc\x43\xda\x98\x07\x6b\xf8\x1f\xc4\x1a\x4e\xa7\x06\x37\x8a\x97\x93\x70\xf5\xbe\xb2\xc6\x6c\x15\x97\x33\x94\xff\xe9\x40\xdd\x9c\x46\xe4\xdd\xd3\x5f\x34\x87\x6e\x8c\x84\x13\xa8\x8b\xbf\xf8\xf7\xb1\x76\x9a\x17\xc2\x90\xa5\x1e\xec\x9d\xbd\xc4\x3a\x21\xfb\xd9\xe1\x19\x66\xe8\xf1\xdc\x04\x5e\x34\xcd\x5d\x89\x96\x3c\xf1\x15\x18\xe0\xd2\x66\xde\xff\x01\x4f\x2f\xac\x17\x07\x40\x4b\x2c\xc3\x0b\x0c\x1c\x5b\x15\x09\xca\x69\x85\x51\xeb\xbc\x87\x17\x1d\xb2\xf0\xb7\xd4\xcc\x11\xb6\xd0\xc7\xd4\xe1\x5d\x97\xed\x53\xb7\x6a\x7a\x9b\x67\x08\x06\xcf\x9e\xf3\xd2\x4a\x3a\x00\x35\x9e\x32\x95\x88\x82\xe6\x8d\xe1\xb3\x17\x41\x43\x1f\x82\xc6\xf0\xfb\x1f\xb0\x98\xac\x1d\x37\xf9\x41\x4c\x3e\x88\xc9\x7f\x4d\x62\x92\xe6\xda\xb8\x07\x39\xf9\x7b\x9c\x17\xaa\x95\xf9\x8e\x0b\xc1\x32\x67\x7f\x3e\x39\xde\xe9\xb2\xe5\x45\x05\x9a\xa3\xd1\x0b\x15\x08\x3f\x6b\x95\xc8\xdc\x9b\x46\x5d\x5a\x52\x4e\xed\xcc\x71\x9e\x25\xe8\x63\x38\x53\x4c\x9b\xcd\x2c\x0f\xd1\xac\x39\xa4\x4f\xff\xa6\xd9\x93\x27\x61\x67\x79\xc6\xdf\x93\xe7\xbe\x71\x32\x39\x54\x3e\xf5\x7a\x4a\xc1\xbf\xaf\xfc\x7a\x1a\x9e\x9e\x86\x63\xc8\x01\x8c\xaf\xe4\xa8\x92\x63\xf4\x6f\x8c\x8f\x53\x47\xc5\x1a\xc0\xc7\x45\x00\x69\x7e\x06\xd3\x30\xe2\xad\xdb\x00\xb3\x24\x92\x04\xf9\x37\xf6\xf7\xb1\xbb\x05\x0d\xe6\xc7\x1a\x30\xe5\x24\xcc\x32\x09\xe6\x84\xc9\xc2\xcc\x81\x8b\x05\xb2\xb0\x29\x72\x38\x3c\x74\xc0\xe8\xc4\x4d\xa2\xc2\xa0\xbc\x0f\x6a\x71\x65\xec\x04\x7a\xe8\x04\x33\x2a\x4c\xd3\xc4\xd3\xac\x0d\xe8\x6b\x59\x83\x54\x18\xda\x55\xa5\x06\xfb\x45\x05\x76\xc2\xea\xc3\x4f\x0e\xae\xfd\x48\x58\x48\xd7\xa9\x5e\x6f\xa1\x66\x09\xad\x10\xaa\x5f\x08\x97\x13\x3a\x0d\xf0\xc5\x9c\x91\x89\x54\x66\x61\xcc\xe2\x6d\x91\x08\x58\x17\x33\x18\xe1\x45\x57\x0b\xbd\x28\xa5\x30\x8d\x3d\xc3\x32\x2f\x89\xd7\x46\xe0\x18\x59\xdd\x29\xc4\xb0\x31\x3b\x52\x76\x84\xa2\x89\x26\x61\x11\x46\x08\x16\x25\xbf\x7b\x48\xf6\xb0\xac\x06\x65\x3d\xf4\xb6\x95\x8c\xb3\x40\x09\x4c\x07\x7a\x02\xd2\x4e\x03\x44\x0e\x21\x79\x26\x74\x4f\xe6\x79\xd6\x37\x4d\xab\xa6\xa0\x01\xc1\xb6\x0c\x48\x9b\xce\x04\x68\xd4\xd2\x46\xa0\xc0\x53\xd6\x40\x47\x5c\xab\xef\xb7\x3b\xbf\xe4\x49\xd6\xc2\xc5\xc1\x6b\x52\x0b\x18\x82\x46\xaf\xd1\x06\x2b\x0c\xb8\x46\xb4\xa0\x5b\x8f\xdc\x3e\xa6\xc2\xbf\xd6\xa1\x23\x9d\x5b\x7a\xec\x18\xce\x2b\xa0\x75\x9b\x51\xac\x13\xeb\x49\x4f\x74\x45\x54\x00\x12\xe9\x29\x41\xcd\x12\x4c\xe0\x85\x91\x5b\xcd\x33\x8a\x28\x3f\x42\xa3\x17\x0b\xc6\x30\x21\x66\x4e\x3f\x49\xc3\x32\x4a\x12\x0e\x6c\xa7\x74\xc3\xc8\xd8\x11\x9d\xe8\xad\x9b\x23\xaa\xba\x88\x2d\xd0\x04\x5e\x60\x9a\x76\xbb\xe0\x5d\x92\xc5\x80\x06\x23\x56\x3a\xe0\xb4\x8b\x8a\xcb\x36\x01\x48\xc1\x06\x6e\xcf\x32\x89\x03\x5c\xb3\xee\x3f\xec\xd9\x6b\xe2\x8a\x2c\x63\x2f\x39\xc4\x64\x36\xee\x75\x90\xac\x6c\x0c\xcc\x23\x4b\x16\x87\x4a\x18\x97\x65\x73\xad\x04\xb7\x65\xd8\x9a\x31\x46\xb4\xc8\x86\x62\xa3\xc7\xff\x4e\x0b\x18\x7e\x91\x80\xcc\xa2\xca\xfc\x8e\xf1\x08\x6f\x31\x57\x73\x52\xde\xbb\x91\xc5\x0c\xc1\x0f\xc1\x4a\x54\x2c\xcf\x51\x74\x50\x6f\xc4\x52\x9b\xb8\xe8\xef\xc7\x53\xa4\xf9\x3f\x22\x53\xdd\x94\x57\xa4\x51\xbe\x3e\x7b\xd8\xec\x40\x82\xd6\x8f\x92\x0b\xb2\xe9\x07\xbd\x8b\x36\x1e\x27\x22\x97\x34\x21\x6b\xb2\x0b\x46\x73\xb1\x0c\x32\xbf\xf2\xdc\x99\x20\x4c\xd3\xfc\xfc\x1f\x60\x91\xd3\xfc\xaa\xe4\xd0\x8f\xd2\x6d\x96\x27\x19\x02\xbf\xb2\x4f\xbd\x1e\x48\xb2\x12\xc1\x30\xc6\x68\xc1\x2c\x96\xd1\x41\xfd\x4c\xe8\xe8\x64\x3e\x02\xe4\x52\xbe\x6f\xe5\xe0\xdd\x51\xf9\xaf\x44\x45\x20\x11\x95\x83\x4c\x59\x8e\xcc\x54\x66\xa3\x97\x33\x55\x9c\xbd\xb9\x4f\x80\xe4\xd5\x42\xe5\x00\xd0\x0c\x6d\xa8\xb0\x10\x86\x52\xee\xa8\x24\x7e\x51\x64\xdd\x51\x74\x20\x4e\xff\x61\xe4\x56\x30\xaf\xf7\x9a\xda\xb5\x44\xdd\xcb\x9e\xfe\x33\xa4\x9a\x76\x79\xd1\x86\x27\x51\xb2\xc8\x69\x79\x0c\x3d\x87\x56\xa2\xf9\x4c\x3d\x08\x7f\x0d\x9a\xbd\x26\x58\x01\x78\x21\xcd\x5c\x7e\x13\x3a\xe3\x93\x93\xc6\x15\x5c\xd3\x7d\xf1\xbf\x2a\xc6\x2a\x26\x40\x0d\x89\xe9\x53\x9e\xa8\x29\x2c\x4c\x89\x8e\x75\x1f\xdc\x6c\x89\x53\xeb\x70\xfa\xb7\xe0\xd2\x1a\x2c\x51\x9b\x21\x6e\xc5\x06\x46\x0e\xed\xd1\x3c\x4d\x05\x69\xba\xf0\x0c\x66\x88\x86\xbb\x24\x2b\xe7\x2f\x65\x9e\x81\xf0\x34\xf1\x2d\x95\x3c\xb7\xee\x61\xfe\x6e\x9e\xa6\xae\xe8\xe5\x3c\x34\x26\xae\xe9\xa9\xac\xc9\x0e\xf9\x4b\x11\xb1\xf0\xb0\xd7\x47\xbb\x4d\x4d\xcb\xc5\x0d\xe9\xd6\x7e\xfc\xa6\x43\xc2\x6c\xf0\x9b\x50\xcd\x56\x93\xba\x59\xe9\x37\x9d\x18\x4a\x02\xde\xa2\x37\x69\xff\x72\x06\x59\xa3\x04\x90\xf0\x9f\x9e\x66\xb2\x95\xb4\xbf\xb2\xba\x92\x0e\x86\x5f\x07\xd7\x4c\x57\x36\xcf\x21\x45\x5b\x60\x05\x34\x5b\x78\xa4\x45\xfd\x2b\xa0\xd9\xf6\xe7\x3f\xf5\xcd\xeb\x38\x29\x67\x69\x78\x49\xc7\x90\xe4\xb9\xce\x10\xde\x9c\x08\x0a\x79\x86\x12\x5e\x10\xb0\xb7\xb4\xf8\xa2\x38\xf4\xb8\x66\x9d\x69\xfc\xd3\x56\xc3\x08\xeb\x0c\x06\xb7\xc1\x71\x07\x4c\xe7\x29\x4a\x66\xe9\x65\x8b\x85\x8b\x6a\x83\xa7\xaf\xc4\x4b\x8b\x05\x6c\x5c\x17\xe4\x38\x28\xd1\x9b\x22\x8c\xbe\x90\xd3\x64\x8b\x11\xd4\xf5\x0a\x66\xb1\x0f\xb2\xdd\x34\xc7\xae\x25\xab\xa5\xbc\x04\x9e\x3c\x51\x6b\xe0\x0c\xf6\xda\x0c\x5d\x2f\xca\xb5\xf1\xac\xae\x97\x81\x49\x1f\x65\x11\x8b\xf6\x06\x43\x7c\xc8\x18\xec\xae\xc6\x57\xe2\xb2\xcc\xe0\xb2\xdf\xbe\xb1\x3d\x94\xb3\xec\x6f\x65\x60\x65\xa1\x15\xd0\x0f\x54\xf8\xa7\x0a\x9e\x4f\xf1\xb6\xb9\x80\xb3\x34\x8c\x60\xab\x09\x9a\x24\x97\x29\xde\x36\x37\xbc\x4c\xb0\x45\xb5\xc2\x92\x9d\x0c\xa2\x9c\x6e\x68\x62\x18\x25\xd3\x30\x35\x97\xc4\x24\xab\x5e\xae\x51\xfe\x96\x16\x74\x8f\xff\x15\x5d\x97\xaf\x84\x95\xff\xda\x27\xb1\x1d\x3b\x17\x56\xb3\x7d\x0e\x6b\xe7\xe8\xcb\x45\x03\x0c\xa2\x83\x72\xf6\xec\xdf\xeb\x79\x28\x51\x7b\x6b\x87\xd7\xb4\xfb\xea\xbd\x52\x77\x55\xff\x89\x1e\xc0\x8f\x4f\x6c\x2a\xe8\xde\xca\x2c\x5c\x29\x3b\x59\xd1\xb5\x06\x9d\x9e\x0c\x24\x85\x65\x79\x38\x09\xb3\x56\x8f\x64\x88\x7f\x4a\xe3\x65\xb2\x88\xa3\x8c\x4f\xfb\x98\xdd\x9a\xea\x17\x2f\xbd\x37\xe7\x72\x53\x52\x92\xc3\x4a\x9a\xd1\x8d\xe5\x0e\xcf\xfc\x03\xd0\x31\x46\x60\x33\x8b\xe9\x99\x23\xe5\xcd\x64\x74\x59\x82\x9c\xa8\x06\x25\x38\x85\xa3\xbc\x80\x66\x09\xc9\xaf\xef\x95\x7d\x76\xf5\x68\x5d\x31\x6d\x63\x09\x96\x7d\x4f\x74\x3f\x6d\xb8\x94\x7c\x0c\xdf\xb1\x7b\xb3\xd3\xf0\x22\x22\xf9\xf4\x2f\x12\x74\x39\x04\x2f\xc4\x91\x1b\xd5\x30\x92\x92\xed\xe7\x48\xf1\xb6\xa9\x50\x28\xac\xd1\x5a\xc1\x10\xeb\x46\x61\x95\x09\xaa\x8b\x5b\xa5\xd9\x1d\xe3\x33\xcc\x75\x78\xe3\x4e\x89\xda\x74\x56\x82\xf7\x75\xad\x3f\x1d\xec\xed\x76\xc4\x28\xd0\x16\xd5\x1c\xd2\x24\x23\x7c\x09\x42\x90\xc1\x71\x88\x92\x33\xc8\x78\x2b\x00\xb3\xb0\x2c\x41\x82\x48\xaa\xc1\xf9\x78\x62\xcf\x26\xd6\x1d\xc6\xa5\xa4\x66\xdb\x87\x42\x0a\xde\xa7\xc4\xc8\x60\x19\x90\xbc\xbd\xe6\x00\xcc\x97\x5b\xaf\xac\xb2\xae\xb3\x30\xb5\x8a\x83\xd6\xe3\xa4\x7c\x97\x64\x09\x82\x06\xd9\x6d\x9a\x9d\x85\x69\xd0\xf7\x9a\x1f\xdc\x98\x3a\xa6\xd3\x3e\x5b\x3a\xe9\xf4\xc9\x47\xe4\x90\x12\x0b\xec\x33\xaf\xc4\x1a\x43\xf4\x13\x86\xde\x1b\x1d\x65\x89\xd7\x56\x4d\xea\x41\x13\xc8\x7e\x48\x43\x02\xca\x03\x61\x53\x0e\xd1\xcc\x5e\xc4\xa5\xa0\x93\x58\x89\x8a\x5a\x14\xb5\xb6\x28\x46\x92\x4c\x96\x00\x16\x45\x5e\xf0\x84\x65\xb4\x0f\x25\xc8\x72\x04\xa2\xbc\x28\x60\x84\x86\xe7\xd6\x44\xd3\xfb\xa1\xcd\x38\x5c\x81\xe4\x93\x39\xfd\x4c\xfe\xbc\x26\x7f\xf4\xb8\x0f\x58\x76\x85\x68\xd6\x54\x05\x25\x86\xfa\x89\x79\xcd\xb0\x73\xdf\x63\xfc\xf7\x44\x35\x75\x29\x40\x1b\x1b\xf2\x86\x9f\xca\xa1\x34\x87\x66\x06\xcf\xc1\x36\xee\x61\xab\x49\x9c\x55\x08\x2a\xe4\xde\xff\x3f\x36\x11\x80\x17\x49\x49\xae\xa0\xa4\x30\x2c\xc9\x6e\x8a\x50\x21\xcf\x04\xe9\xe4\x55\x30\x5c\xb2\xc4\x62\xd6\x98\x73\x0c\xc7\x80\xb8\x6f\x06\x6a\x44\x5c\xc3\xdb\x14\xa3\x22\x85\x83\xe8\x01\xc9\xc8\xe8\xe5\xb2\xc3\xf0\x0b\x24\xf3\x97\xae\x32\x58\x38\xe0\x4d\x5c\x16\x4b\x49\x4e\x99\x04\xcb\x73\x92\x2a\x08\xb3\x06\x25\xb5\xc1\x85\x9f\xf2\xb2\x4c\x4e\x53\x3a\xc8\x24\xaf\x91\x72\x31\xfa\x60\x07\xab\xa7\x05\xa2\x3f\xf1\x5e\x8c\xd3\x71\x7b\x34\x4a\xc6\x97\xec\x61\x0f\xb7\xc0\x4b\x3d\x05\x5f\x30\x32\xf4\x1f\x3f\x00\x27\x0f\xec\xac\x5c\x02\x4e\x25\x20\x3b\xa9\xa6\x0f\xfc\x5c\x5b\x42\x8e\x25\x24\x3b\x47\xa6\x0f\xec\xe0\x59\x7e\x91\x45\x9e\x3e\x15\x73\x9d\x1f\x06\xd3\x22\xbf\x86\xa7\xb9\xf6\xc5\x53\x86\x9e\xe4\xd2\x6e\x90\x03\x60\xf5\x8b\x2c\x23\x70\x51\x0a\xd3\x47\x85\x22\x02\x08\x18\x30\xca\x03\x39\x8d\x55\x88\xa3\x4c\xe6\xa7\x60\xac\x3d\x21\xf9\xe4\xd2\x7f\x7e\x12\xb2\xc6\x30\xc5\x5c\x89\x0d\x00\x65\x9c\x28\xcc\xf0\xc6\x23\x14\xf2\x9f\xbd\x67\x36\xed\xbc\x00\x21\x78\xbf\xfd\x67\x62\x25\xe2\xca\xe8\x1d\x0b\x28\xb1\xd0\x73\x2b\xc2\xcf\x13\xc8\xb3\xca\x86\x8a\x23\x0a\x5d\xf4\x30\x5b\x73\xc1\x86\xe7\x63\x58\x82\x73\x88\x27\x98\xcc\x87\xc5\x3b\xe3\xd4\xde\x7e\xca\x33\xd5\x4c\xc4\xbb\xad\xcb\x27\xe6\x96\x3c\x2f\x32\x2e\x71\x54\x25\x8e\x96\x69\x77\xe2\xe4\x2c\x89\x61\xfc\xe6\xb2\xa5\x0b\x3e\x2a\xec\x2c\xed\x4d\x55\x01\x58\x15\xe0\xb5\xd6\xcc\x50\x7d\x52\x34\xc1\xa5\xe4\x40\x48\xc9\xef\x10\x05\x67\xb6\xc6\xf6\x30\xf7\xff\x75\xcd\x7d\x94\x2f\x9a\xf9\xca\xae\xe7\x37\x95\x01\x78\xee\x7d\x1b\x52\x80\xd0\xe8\xae\x64\x00\x4a\xa6\xb0\xfc\xfd\xe7\x7f\xc6\x2c\xe4\xe4\xaa\x39\xb7\x9d\x92\x79\x4f\xf6\x6f\x78\x80\xb9\xef\x9e\x97\x73\x04\x76\xb5\xf9\x47\x72\x0c\xf7\x3a\xc0\x6c\x22\xd9\xc6\xae\x51\xa4\x35\x94\x95\x98\x40\xea\x40\xa9\x5e\x88\xc6\x70\x2d\xdc\xbf\x3d\xe3\x55\x01\xb9\x15\x67\x3f\xae\xae\xe4\x25\x35\x73\x73\xc6\x6a\xb7\xb6\x56\xf4\xbd\xb1\xa7\x63\x03\xc4\x31\x7a\xf2\x04\xb0\xdf\xae\x2d\x0b\x6e\xd6\xfc\xaa\xee\x8e\x5c\x37\x7f\x34\xd5\x90\x15\x16\x76\xa5\xde\x45\x33\x68\x36\xf5\xf3\x55\x2b\x9f\xa6\xab\x06\x95\xb3\x6a\xa9\x99\x59\xcc\xf8\xcb\xc1\x5b\x82\xb1\x28\xd8\x08\xb0\x9d\x02\xdf\x58\x32\x3f\x75\x02\x8b\xce\xf3\x66\x09\xe8\x20\x4d\xa5\xbd\xd1\xe6\xc5\xc3\xf3\xbc\xdc\x32\xc1\x16\x32\x64\x05\x9b\x39\x98\x4b\x6f\x43\xb7\x41\x1a\x2c\x46\xaf\x9a\x4b\x5e\x74\x49\x02\x92\xce\xa3\xa5\xdd\xeb\x12\x45\x54\x93\xcd\xc2\x61\x6e\x8c\x6e\xf9\xaf\x41\x38\xa2\x33\x4b\xe7\xa5\x44\x81\x3d\x7b\x43\x5b\x0a\x40\xbf\x5d\x8e\x84\x76\xe3\x3b\x42\x2a\x99\xb9\x9f\x44\xc9\xe2\xa8\xa5\x97\x24\x55\x3e\xbd\xe4\xe5\x19\x5d\x3a\x6d\x22\xb4\xa9\x41\x59\x2b\x09\xab\x44\x69\x0c\x0b\x97\x50\x2f\xa3\xa6\x49\x85\x61\x66\x0f\xb3\xd1\x98\x36\xca\xa1\x79\x67\x8d\xd5\xd6\xfd\x6b\xef\xe2\xb8\xf7\xf4\x87\xf0\xe9\xe8\xe4\xeb\x5a\xef\xfa\xdf\x76\x93\x0e\x82\x25\x6a\xe9\x77\xf7\x96\x26\x51\x0d\xca\xfc\x46\x34\xa9\x45\x0d\x62\x3d\xe9\xfe\xb5\xd5\xbb\x68\xbf\xae\x24\x87\xca\xd1\xdd\x2e\x88\x30\x11\x98\x20\x98\x84\x14\x59\x9a\xda\x9f\x39\x75\x4f\x49\xae\x05\xac\x4d\x18\x04\x71\xa6\x9f\xd5\x2e\xf6\x3b\xd1\xd1\xb1\xc1\x62\x56\x01\xdb\x7c\xfa\xce\x09\x66\x20\xbd\x33\xa2\x46\xce\x30\x4d\x41\x39\x25\xe1\x57\xc2\x19\x09\xbe\x82\x7f\xf3\xe7\x40\xcd\x1b\x6c\xe2\xac\x06\xcc\xb4\xdc\x05\xba\x5d\xaa\x27\x13\xd5\x84\x52\x08\x86\xd1\x04\x44\x61\x69\x55\x94\x94\x3c\x8a\x22\xbf\x65\x69\x5e\x1a\xbd\xbe\x31\x03\xb2\xb0\x21\x8b\xf8\xf0\x0d\x01\x5b\x9a\x1b\x45\xed\xcb\x32\xa4\xd6\xde\x22\xb6\xe4\xef\xe4\x35\x7f\xb0\x36\xf0\xde\x20\xd7\x59\x49\x11\xe8\x34\x70\x27\xaf\x4b\x1c\xdc\xad\x3a\x82\xef\xaa\x57\x10\x6e\x79\x99\xd7\x89\x8e\xc5\x41\xae\xa5\x58\x2e\xbd\xda\xb8\x12\xdf\x55\x65\x50\xfd\xfa\x5d\x9d\x31\x9d\x14\xb3\x05\xc3\xed\xd1\xdb\xaf\x01\xcf\x3d\x11\xbb\x99\x4c\xd3\xeb\xbc\xc3\xad\x91\x55\x13\x45\x86\x24\xd7\xe6\xbb\x7f\xc6\x38\x46\x40\x1d\x4c\xc7\x65\x66\x7b\xae\xe9\x83\xd4\xf4\xba\x9b\xef\xc3\x32\x4f\xcf\xa8\xa9\xb0\xd6\x3c\xa3\xac\x54\x6b\x5c\x4e\x9d\x40\xfe\x21\xf0\xd1\xde\xd1\xa4\x9e\x98\x54\xfd\x62\x0e\x80\x56\xcc\x00\x35\x9d\x65\x64\x58\x5c\x01\xe3\x9a\x63\x40\x48\x11\x7d\x96\x39\xee\xd7\xeb\xed\xe9\xae\x61\xbc\x92\xc7\x8e\x89\x67\x7a\xd0\x98\x4b\x8e\xcf\xef\x4a\x19\xe8\x25\xc5\x2c\x15\xf0\xe5\x7c\x3a\xb5\x46\xc2\x16\xb5\x06\xe7\xde\xf7\xd2\x6f\x34\x57\x2d\x6b\xbb\x5d\xda\x55\x6b\xad\x92\x83\xc6\xe5\xa7\xb9\x2b\x51\x85\x28\x83\x79\x1f\x96\x24\x81\xe4\x24\x5c\x15\x22\x5c\x33\xfb\xcb\x2d\xb3\x2b\x3d\xc1\x5a\x8f\x78\x65\x01\x63\xe5\xc6\x04\xc9\xd0\x04\xa4\x90\x24\x13\x2a\xa5\x0f\xd4\x6c\x06\x0b\x8c\x33\x1f\x27\x0c\x15\x27\xe3\x84\xdc\x46\xc5\x1f\xa6\xe1\x0c\x8f\x57\x5f\x63\xa5\x96\x70\x64\x55\xd0\x26\xe9\x95\xfb\xcf\xdb\xe0\x15\x20\xb1\xbc\xd9\x27\x1a\xae\xe2\x08\x37\xc4\x0e\x2e\x30\x03\xca\x8f\x44\x38\x55\x57\xf8\x72\xc3\x51\xa3\x7a\x14\x62\xd4\xe8\xe4\x6c\x6d\x51\xf1\x66\x5a\xd0\x97\x17\x8b\xa9\x3f\x32\x53\x22\x67\xde\x05\x9c\x8b\xf2\xfb\xe0\x5c\xff\x19\xee\x52\x8c\xab\x1c\x9a\x0a\x56\x35\x23\x32\x8b\x69\x2e\xb8\xce\xe6\x6a\x6d\x28\x6e\xc8\xe3\x1a\x58\x64\xf5\x02\x57\x56\xc9\xf7\xba\xb6\xe3\x9c\x03\x44\x7b\x9d\xc8\x2b\x2a\x98\xab\x7f\xc0\x9a\x01\x61\xfc\x0c\x4c\xc3\x2f\xe4\x0a\xb5\x98\x12\x1a\xc7\x2f\x62\x78\x9d\xe5\xcc\x1e\xac\x6c\x78\x67\x83\xc2\x91\xb6\xfb\x6c\x65\x3d\x0b\x79\xd9\x28\x7d\x13\xad\xe9\x2c\x4c\x93\x18\x0c\x7a\x34\x71\xe8\x53\xa6\x4c\x52\x1c\xe8\x5d\xe2\xde\x05\xa0\xde\xb2\xde\x29\x50\x8f\xf5\x6f\xb1\x5e\x8b\x26\x16\xf3\xbb\x4f\x41\x72\xa8\x40\xa1\x4a\x35\x65\xe5\xa3\x5b\xac\xaa\xdd\x95\xa3\x32\xe6\x65\xe2\xab\x53\x87\x62\xd7\xcd\x5a\xc4\x27\x43\xd4\x2a\x63\x25\x04\x60\xad\xc2\x1c\xc5\xcf\xd6\xb1\x24\xc3\xc8\x72\x63\x70\x29\xcd\x27\xaa\x09\x98\x88\x45\xff\x1e\xc7\x67\xef\xf4\xf9\x97\x54\xed\x5f\x1c\x86\x4a\xd0\xa2\xe8\x59\xf6\x05\x8e\x75\x56\xa2\x30\x8b\xb0\x8c\x92\xc5\xaf\xae\x94\x29\xc2\x2a\x20\x11\x2c\xc8\x2f\x2d\x67\x9d\xf3\x2d\x75\x33\x25\xb2\x4e\x54\xda\xbc\x09\x41\xb9\x45\xb7\x2e\x35\x0f\x9c\xfe\xdf\x37\x20\xa5\x48\x0a\xb1\x90\x8e\x4c\xc0\x33\x9c\x49\x9f\x29\xd6\xcd\xbb\x24\x23\xc5\xe7\x46\x34\xe4\x3d\xa8\x4f\xc5\x77\xba\x73\xe7\x6d\xe8\xc8\xab\xba\x21\x25\x79\x19\xbf\x73\xb2\xbf\xdf\x18\x5b\x54\xbf\xd3\xb4\x73\x77\xd0\xe5\x3d\x86\x7f\xed\x29\xc8\xb7\x0b\x98\x09\x1e\xb7\xc8\x45\xc8\x4e\x52\xd2\x0b\x91\xac\x24\xb1\xea\x3b\xe8\xc3\x5c\xb0\x6e\x40\x9d\x53\xda\x81\x25\x04\x15\x2d\x70\x17\x62\x8a\x7e\xba\x21\x4b\x30\xc4\x6f\xd2\x67\x92\xef\xbc\x7e\x8f\xc9\x08\xdc\x41\x7f\x49\x3d\x75\x7a\xeb\x1c\xfa\xda\xfd\x34\x37\x80\x54\xb3\xf8\x85\x06\xe8\x56\x78\xdb\xee\xe8\x9f\x4a\x7b\xae\xeb\x2a\x70\xad\x7e\xe2\x6a\xcc\x5b\xc0\x4a\x96\xd6\xe2\xd2\x5e\xb5\x1f\x3f\x26\xfe\x4a\x34\xdc\x90\x7a\xa3\xeb\x1a\x44\x21\x8a\x26\xa0\x05\xeb\x59\xaf\x6e\x40\x9f\x90\x51\x68\x1b\xb3\x03\x9c\x4f\xc1\x69\x9a\x47\x5f\xc0\x04\x86\x31\x2c\xf0\x43\x3e\xf5\x39\x8a\x26\xe5\x1b\xfc\xd9\xa7\x70\x4d\xe0\x05\x8b\x24\x17\xd3\x7a\xc0\x28\x49\x91\xeb\x00\xb2\x62\x92\x90\x72\x9a\x51\x04\xbf\x59\x60\x18\x7f\xd6\x1f\x48\xcb\x38\x2d\xb0\x90\x7e\x7e\xbb\x36\xad\x4d\xad\xcc\x65\xd6\xb6\xa1\x1c\x4d\x6a\x66\xe9\x47\x3e\x6c\x6e\x31\x8a\x69\x8e\xd5\xe9\x59\x12\xf9\xc7\xec\x10\x7f\xae\x33\x66\x48\x03\xac\x31\x58\xa4\x66\x6d\xb0\x48\x15\x0b\x06\xeb\xf9\x9a\x1c\x2b\x0a\x7f\x8b\xb1\x22\x95\xa9\x75\xb9\x86\xca\x02\xba\xf5\x48\x79\xa3\x7b\x33\x15\x7b\xc8\x7f\x04\xf2\x3d\x09\x17\x30\x14\xbf\xf8\x17\xa2\x8c\x0f\xe9\x1f\xf9\x8e\x79\xbb\x0e\xe5\x4f\xfe\x4d\xf1\x85\x1d\xaa\x0f\xb2\xec\x11\x1a\xbd\x18\xb2\xbf\xf2\x2d\xb9\xa4\x37\xe4\x3f\xd4\xda\x28\x3c\xff\xa5\x7e\x61\x65\xc4\x4f\x51\x9b\x7d\x43\x6c\xe8\x7a\x29\x72\x78\x5a\xd7\x89\x86\x8e\x77\x06\x34\xbf\xa0\x32\x34\x5f\xc8\x3e\xfd\x94\x67\x43\xfa\x47\xc5\x9a\xbc\x65\x3f\x24\xac\xd0\xc8\x87\xea\x83\xfc\xae\x9f\x48\x0f\xad\x37\x0a\x25\x59\xb0\x75\xf9\x93\x7f\x53\xf6\x22\x43\xf5\x41\x7e\xd7\x76\x89\x43\xf3\x85\x84\x53\x20\xac\x6f\x86\x31\x66\x68\xbf\x92\xb8\x5a\xb0\xd6\x2b\x05\x77\xd5\xc6\x3b\x34\x9e\x15\x42\x6a\x60\xc6\xb3\x1e\xcf\x5e\x07\x75\xbc\x93\x8d\x73\x8d\x79\xa8\xfc\xd6\xc9\x96\x8d\x87\xe2\x97\xfc\x42\x15\x93\xa1\xf8\xa5\x74\x87\x0a\xae\xa1\xfc\xa9\x90\x17\xab\x1a\x43\xfe\x43\xbe\xc7\x6b\xf9\x90\xfd\x55\x6a\xc2\xe2\x7d\xc8\x7f\xc8\xf7\x44\xfe\x0d\xf9\x0f\x35\xdc\x3e\x0d\x77\xaa\x07\xcf\x6a\x0c\x07\xbd\xa0\x21\xc2\x73\x35\x86\x83\x7e\x50\x99\xee\x24\x68\xcc\xd1\xe8\x45\x63\xf8\x62\xed\xfa\x24\x18\xac\xd6\x0d\x03\xa8\xcb\xa6\x0d\x2e\x99\x1a\x2c\xfe\x5d\x63\x08\x1a\xbd\x4e\xbf\xd3\x6b\x50\x7c\x65\x7a\xb2\xc1\xda\x43\xa4\xc1\x87\x48\x83\x7f\xc4\x48\x83\xac\x36\xf1\xd2\xce\x51\xf6\x27\x38\x1a\x15\xf0\x12\xfc\x9c\xa4\xd1\x17\x08\x5e\xfe\x02\x47\x23\x57\xc8\xc1\x25\xb2\x99\x11\xd0\x24\xcc\xc0\x5e\x98\x45\x30\x24\x90\x49\x98\xb9\x41\xdf\x85\xa7\x18\xf4\xa7\x7c\x0c\xd3\x12\xc1\x34\x85\x05\x78\x39\x22\x2f\xdd\x05\x7e\x0c\xcf\xc0\xcf\x79\x1e\x83\x97\xe3\xca\xd0\x88\x6b\x42\xaf\x13\x8a\x1d\xcb\x6a\xf8\x31\xcc\xc2\x31\x34\xe2\x01\x62\x4a\x75\x0b\x0a\x30\xa5\x00\x6a\x1c\xc1\x9d\x53\xb2\xc1\x35\x0b\x24\xa7\x61\xa6\x82\x6d\xa3\x89\x03\x8a\x6a\xac\x65\x17\xa2\x89\x0a\xfc\xf6\x4d\x05\x6c\x7c\x8a\x41\xbb\x5d\x02\x79\x30\xa9\xaa\xb6\x9c\x68\xd5\xee\x12\xe7\x02\x1f\x70\x06\x91\x0a\xfc\x89\xa4\x30\x22\xf7\x0e\x7d\x25\x66\x0c\x44\xc5\xe7\x3c\x2c\xa6\x55\x18\xe1\xef\x6a\x33\x5c\xe8\x68\x45\xb8\x34\xc1\x5b\x5a\x3d\xa6\x63\x92\x96\x3a\x28\x79\x45\xff\x5f\x1d\x22\x92\x02\xf2\x40\xbf\x40\xde\x0c\x86\x59\xec\x40\x98\x7e\x50\x41\xdf\x90\x6d\xaa\x0d\x79\x8a\xdf\x6b\x94\x2b\xf2\x19\x2c\xd0\xa5\x03\x76\xc6\x3e\xa9\xe0\xef\x11\x9a\x7d\x2a\xf2\xb3\x24\x76\x72\x1e\x9e\xdf\x33\xf6\x59\xe3\xbb\x59\x54\x51\x2a\x99\x45\xae\x42\xf5\x53\xec\x89\x0d\xce\xcf\xf0\x74\x15\xb4\x78\x65\xca\x2e\x7f\x92\x94\x9d\xcf\x85\x39\x71\x32\x78\x6e\xcc\x26\x59\x76\x5d\x2d\x1a\xcd\x8b\x02\x66\x48\xe9\x03\x87\xd3\xc0\x66\x69\x88\x08\x7b\x90\x1b\x54\x68\xd2\xc2\x6f\xf5\x9a\xe2\x53\xf6\xf9\xed\x1b\xfd\x2b\x39\xad\x4d\xca\x4e\x49\x26\x09\x86\x38\x98\xb8\x2a\xc8\xe8\x05\x6e\x78\x8e\x67\x88\xe3\xfb\x4c\x4e\x05\x0c\xc4\x67\x86\xbb\xb1\xd3\x5f\x7f\xe5\x8d\x61\x5e\x77\x54\x27\x59\x5e\x6e\xc9\xc2\x59\x32\x14\x8b\x28\xfb\xcb\xb7\x66\x3a\x2e\x8c\x48\xa5\x56\x5c\xe5\xa1\xa1\xf6\x14\x08\x18\x85\x61\x86\xea\x83\xb3\x99\xcf\x62\x62\xd0\x1f\x8e\x6e\x30\x90\x96\xc4\x82\x71\x77\x02\xcb\xa1\xf2\xbb\xc5\x5d\xa5\xae\xd5\x20\xff\x24\xd1\x6c\x09\x81\xec\x0f\xcb\xdb\x46\x62\xc8\x12\x38\xcc\x79\xce\xfe\x2e\xee\x6d\x65\x5f\xf9\x8e\x96\xd7\x8f\x72\x74\x39\x83\x9d\x12\xaa\xcc\x28\x37\xf8\xf5\x58\x5f\x2d\x7e\x43\x8e\xf7\xe0\x55\xc0\x12\xea\x46\xe6\x2f\x10\xce\x76\xca\x83\xcb\x2c\xd2\xf2\x9f\x3b\xd1\x22\xa5\x8d\x12\x1a\x56\x25\x44\x28\xc9\xc6\x25\x67\x5a\xf6\x68\x5e\x98\x37\x90\x52\x05\xc9\x1b\xdd\x95\xd7\x80\xe4\xb7\xa1\x69\x68\x63\xf2\xe4\x81\xe3\x31\x75\x38\x24\x79\xf6\xc0\xb2\x68\x52\x1c\x14\x3f\x3a\x21\xd5\x58\x3d\x14\x58\xbc\xf1\xc2\x6b\x75\xf3\x17\x1e\x3c\xe4\xe5\x7c\x8e\x0a\x7b\xe3\xad\xdd\x2c\xa1\xbc\xf3\xb4\xa1\xd2\x9a\xb7\xb2\x88\xe6\xf4\xca\x09\x87\xfe\x29\xcf\xbc\xf8\xa8\x90\xec\xd9\x09\xab\x7a\xf1\x52\x68\xf1\xc6\x03\x6f\x7b\x50\xf0\x72\x5b\xae\xd3\x74\xab\x0f\xbe\xf2\xd6\x17\x4f\xfb\xa6\xd7\x19\x6f\x5d\x7b\xef\xa3\xb9\xb3\xac\xf1\xde\x59\xd6\xed\xf1\x46\xcb\x3b\xbe\x39\xeb\x90\x41\x52\x69\x39\xf6\xec\x83\xe5\x51\x39\x05\x30\x79\xc1\xfd\xc8\x4c\x31\x47\x35\x23\x35\x70\x15\x3d\x78\x35\x73\x1a\xe8\x07\xe9\xc4\xc3\xc4\x84\xad\xe3\x06\x11\x27\x05\x39\x94\x89\xc2\x19\xb9\x1f\xec\x70\x3c\x70\x70\xe5\xce\xd6\xe6\x27\x4d\xe6\xe1\xf2\xfa\x05\x89\x84\x2a\xde\x58\x68\x61\x1d\x9c\x42\x18\x41\x57\x30\x4c\x87\x35\x69\x4a\x34\x12\xd2\x54\xac\x52\x5a\x63\x16\x11\x8e\xc5\x1a\x47\x14\x00\xa6\xc5\xb5\x54\xff\x92\x8c\xd8\xed\x9a\x7c\x0d\xcf\xf2\x18\x36\x03\xe5\xfb\x98\xf8\x8b\x0d\x41\x13\xeb\x67\x9f\xa3\x34\x81\x19\xfa\x89\x02\x37\x05\xd8\x75\x3b\x58\xbe\x25\x88\xce\xf3\xe2\x8b\xbb\xb1\x0c\xa2\xcf\x0c\x50\x03\x20\xd7\x6b\xde\x31\x77\x90\x62\x68\x4a\xaf\x5b\x21\x44\x55\x36\x37\x3e\xf4\xdb\x67\x32\xd8\x51\x9e\xfe\x74\x2b\xd4\xc4\xcf\x6e\xd7\x87\x5c\xb7\xeb\xc2\xf0\x7c\x92\x94\x33\x58\x28\xcd\x32\x38\x81\x67\x39\x99\x38\xe8\xc6\xa0\xea\x62\xd8\xed\x4a\x24\x4f\x2a\x97\xd3\xa4\xdc\xca\xb3\x8c\x06\x18\x56\xe6\x66\xdb\x60\xc3\x96\x53\x8f\x78\xf2\xc4\xa9\x5f\xa8\x95\xb6\xcc\xe8\xb9\x46\xfb\x34\x6e\x1b\xdf\xe1\x54\x4c\x04\x72\x75\x08\x83\xa9\x0a\xa1\xf7\x88\x00\xb7\x22\x53\x7f\xa8\x7b\x30\x6a\x0a\xd4\xb6\x6f\x8d\xe1\x60\x80\x5f\xa9\x9b\xbf\xc6\x70\xb0\x8a\xdf\xc9\x8d\x56\x63\x38\x78\x2e\xde\x50\x0d\xb4\x31\x5c\xed\x89\x57\xea\x76\xa9\x31\x5c\x5d\x13\x1f\xb0\x28\x68\x0c\x57\x9f\xc9\x17\x72\x8b\xd4\x18\xae\xca\x4a\xe5\x3e\xbb\x31\x5c\xfd\xc1\x7a\x0d\xd1\xa4\x31\x5c\xeb\x59\xef\x33\x88\x1a\xc3\xb5\xbe\xf5\x9e\x6f\x1e\x1a\xc3\xb5\x81\xf8\xc8\xf7\x81\x8d\xe1\x9a\x44\x48\xb7\x31\x34\x86\x6b\xcf\xab\x8d\xa5\xd7\x27\xc1\xe0\xd9\x83\xfd\xf2\xc1\x7e\xf9\x47\xb4\x5f\x86\x69\x4a\x82\xc1\xdd\x4d\xb6\x14\x87\x49\xd0\xb2\x20\xf9\x4c\x48\x07\x79\x9a\xc4\x09\xba\xdc\x3e\xa3\xd7\x3a\x15\x53\x0c\xc1\x50\x85\xe5\x0e\x98\x85\x61\xc5\x92\xef\xab\xcd\x5d\x5e\x7b\xd7\x3b\xe2\x76\x60\x54\x4a\xde\xa9\x50\xe7\x58\x82\x42\xa3\x6d\x2e\xa8\xd8\x47\x2d\xe1\xc9\x66\x9a\x6a\xdd\xd3\xd5\x24\x7d\xab\x19\x10\x0f\x14\xc7\x6d\x1a\x8f\xa5\x48\x7f\xa1\x9b\x16\x7e\xa1\x0e\x26\xf8\x8f\xfe\xc1\x72\x99\xd6\x57\x20\x0b\x5f\x65\x8d\x63\x69\xfc\x34\xef\x1c\x53\xe7\xe5\x39\xbc\x36\xc4\xaf\xab\x2b\xf0\xf5\xda\x19\x40\xed\xeb\xb5\x70\xa3\x3e\x6e\x62\x9d\xf5\x4d\x9a\x47\x5f\x9a\x01\x68\xa2\x9c\xfe\x3c\xe9\xd0\x21\x68\xc9\x16\x47\x8e\x73\x78\xd6\xd4\xf1\xe8\x84\x38\x88\x09\x9f\x71\x71\x36\xdf\xee\x8c\xf2\x62\x3b\x8c\x26\xfe\x8a\x30\x4e\xb8\x82\x0d\x85\xc5\x68\x14\x51\x82\x0a\xdd\x2b\x0a\x95\xa5\x25\x9b\x94\x7e\x38\x6a\x00\x01\x12\xda\x4d\xd2\x5a\xa3\xbd\x19\x67\xc0\x15\xee\xad\x6a\x1c\x58\x5a\x48\x75\x1c\xe2\x10\x85\xb2\x3f\x24\xfd\x2f\xfe\x1f\x4b\x76\x4d\x7f\x5f\x5d\x51\xd7\x79\x65\x28\xc8\xf4\xe2\x1e\x19\x2d\xb1\x29\x25\x3e\x55\xa4\x20\x71\x7d\x28\x89\x4b\x1d\xff\xca\x6e\xa2\x2b\x9f\x8f\x7b\x27\xed\x36\x78\x0d\xf4\x57\x2c\xff\xf0\x80\xc4\x86\xd2\x02\x43\x4d\x99\x96\x24\x39\xd5\x1e\xe6\x5f\x1c\xc3\xac\x62\xbb\xc1\x5c\xf6\x99\xe6\x68\xbb\x11\xb4\x7e\x51\xe2\x37\xb5\x8f\x7b\x27\x5a\x24\x80\xc7\x04\x87\x36\xf8\x4a\xee\xbf\x86\x59\x96\x23\x30\x4a\xb2\x98\xe2\x96\x64\x63\xda\xd8\x6b\x13\x05\x85\x37\x58\xf6\xb6\x7c\x2c\x99\x82\x8c\x82\xe5\x4f\x2d\x08\xcd\x0d\x44\xea\xb8\xb6\x5c\x73\x3b\xa0\x68\x04\x3a\xdb\x98\x5b\x33\x52\x27\xbf\xdd\xa8\x34\x5d\x6b\x2a\x5f\xc0\x08\xeb\x1e\x8e\xb9\x1c\x90\xa0\xa6\xa7\x61\xf4\x05\x0f\x81\x42\x33\xce\x00\xfc\xa4\xbd\x15\x16\xe3\x39\xb9\x1a\x7c\x2c\x7e\xf1\x1b\x9e\x4f\x41\x5f\xbf\xf9\xc2\xeb\x24\x59\x63\x2b\x8b\xa9\xf7\xc4\x5a\x16\xc4\x86\x9e\x48\x56\x15\x38\xce\xac\xab\x98\xf8\x39\xe7\x35\x16\x0e\x5d\xdf\x7c\x1b\xeb\x0a\x87\xa5\x74\xed\x9c\x26\xa6\xe9\x56\xd1\xe3\xe9\x9a\xd1\xca\x03\xd0\x84\x68\xd2\x0c\x9c\x82\x3a\xe0\x8b\x46\x07\xa2\x49\xab\x1d\xc8\xa6\x14\x4a\xd7\x1f\xb9\x10\xa1\x30\x9a\x1c\xe6\x5b\x3c\xbc\xac\x3a\x84\x3c\xe6\xac\xbe\xcf\x97\x83\x4d\xa9\x40\x1f\x1d\x5d\xe3\xc5\x3b\x61\x9a\x8a\xd5\x8a\x81\x2f\xd8\xa8\x58\x28\x2b\xbb\x16\xc7\xb6\xc5\xb9\x6f\x21\x0c\xdd\x18\x0e\xc8\x96\x81\x4a\x84\xc6\x70\x95\x6c\x08\xe4\xbc\x6b\x0c\x57\x09\xb0\xb1\xe8\x36\x86\x6b\xab\x58\x9d\x7f\xfe\xa0\xce\x3f\xa8\xf3\x7f\x44\x75\x9e\xec\xed\xef\x2b\x41\xec\x9f\xca\x3c\x2b\x66\x91\xae\xf6\xfe\x42\x5f\x6a\x87\xc1\x45\x91\x9b\x9a\x39\x7d\xa7\x29\xc5\xb6\x99\x04\xd3\xc2\xd0\x77\x2d\x75\x97\xd0\xeb\x73\x85\xd2\xcb\x3e\xe1\xf6\x8f\x4f\xbc\x96\x55\x57\xf0\xee\x30\x8e\x79\xe4\x7d\x2c\xd7\x59\x45\xe4\xa6\x3e\x41\x95\xbd\x60\x55\x08\x8a\x31\x77\xd9\x30\xb6\x72\x50\xa9\xf1\xf5\x31\xe1\x48\x05\x48\x77\x2e\xa7\x03\x49\x68\xa1\xca\xf7\x38\x76\x6d\x11\xdc\xc4\x29\x69\x62\x79\x0e\xb3\x54\x9f\xf9\xaa\x50\xa7\x87\x0c\xb6\x12\x71\x97\x4a\x61\x06\xe7\x12\xe3\xa3\x75\xc2\x35\x88\xf2\x50\x30\x8b\xa9\xb9\x8c\x43\x07\x4a\xfd\xb0\x28\x02\xa6\x3e\x97\xb6\x3a\x4f\x19\x91\xfe\xba\xba\x12\x3c\x41\x01\x18\xfd\xd4\x04\x01\x82\xd8\x01\x20\xf1\x9e\x9c\x37\x77\x59\x85\xc7\x04\xe2\x44\xdf\xdd\x78\x76\x1a\xb4\x88\xb3\x52\x1a\x7f\x82\xe2\xc2\xaa\xec\x38\xd4\x2f\x09\xfc\x98\x4d\xc5\x4e\x52\xfe\x14\xa6\x49\xbc\x0f\xcb\x59\x9e\x95\x90\x35\xd2\x36\xb3\xa9\x28\x78\xbb\x5b\x69\xd1\xf9\xd9\xd9\xc9\xce\x9c\xf5\xad\x6b\xd5\x5d\xeb\x18\x79\x2b\xa5\x21\x54\xad\xae\xd1\x65\x9c\x04\x51\x73\x7d\x60\x8d\x76\x58\xdb\x24\xbc\x9a\xfa\x42\x43\x46\x49\xf5\xa2\x6d\xc1\x40\x95\x9a\x42\x58\x49\x35\xa8\xd2\xde\x37\x86\x83\x17\x58\xa9\x60\xd3\xb5\x31\x5c\x25\xc9\xe6\xbf\x7f\x50\x26\x1e\x94\x89\x3f\xa2\x32\x21\x36\x02\xf7\x64\x1a\x5c\xc2\x2e\x87\x77\x61\x85\x01\x5a\xb2\x9d\x46\x97\x7c\xbc\xa9\x29\x91\xc3\x2a\xf7\x2e\x55\xdb\x1f\xbf\x4e\xa9\x94\xd8\x54\xb6\x45\x0a\xa8\xb0\xa4\x0a\x25\x67\xd1\xd2\x4b\x0d\x69\xea\xa5\x56\xa2\x37\xf8\x22\x37\x50\xf8\x2d\x09\xfe\x49\x85\x16\x4a\x07\xb1\xdb\x5c\x03\x35\x9d\x8f\xfe\xc5\xdb\xa0\x92\xe8\xc3\xdd\x94\x7e\x99\xfd\x34\x09\x58\x79\xeb\x78\x2b\x3c\x4d\x1c\xc6\x1c\x2d\xf9\x8f\x9e\x65\x87\xe4\x29\x21\xb7\x15\x15\xec\x9a\xe0\xc9\x13\x2d\x8f\x8f\xb2\xfb\xa7\x0d\x1b\x59\xf3\xae\xdb\xc6\x4a\x5e\xd5\xa4\x23\x37\x10\x68\x91\x97\xce\x15\x9f\x7c\xa1\x09\x83\x9c\x4b\x8e\x51\x11\x06\x2c\x1d\x4d\x13\x5e\x65\x26\x08\x4a\x54\x0a\x2a\x48\xa9\x59\xab\xb8\xb5\x0e\x2c\xa7\xc4\x8a\xc4\xe2\x2c\x92\x2b\x35\x11\x54\xde\x69\x0c\xe3\x98\xcf\x80\x52\x9a\x15\x4c\x0e\xe2\xef\xaf\x45\xad\x0b\xb9\x4f\x32\x95\xbb\x89\x05\x96\x0b\x69\x8c\xb8\x39\x4b\x19\x77\xa2\xeb\xf3\x89\x6a\xa7\x13\xf6\x2e\x81\xd1\x67\x88\x26\xdc\x58\x2f\xd1\x34\xc3\x50\x2d\x34\x3a\x8f\x2c\x63\x8e\x24\x83\x4f\xa3\xa9\xc1\x03\x54\x16\x2d\xc5\x00\x54\xaa\xdd\xdf\xe8\x9b\xf5\xd7\x31\x5a\x71\x41\x7b\x17\x7c\x40\x65\xbf\xc3\x50\xcf\xa4\x3a\xb3\xcc\x0a\xf9\xae\x0f\xb5\x6d\xd1\xa3\xc8\x55\x0c\xfe\x66\x9a\x56\x0e\x2e\x03\x63\x07\x70\x4b\xb3\x24\x35\x1d\x2f\x40\xf2\x26\xfc\xa9\x5d\x21\xb6\x8d\x8d\xd5\xfc\x59\x87\x41\x45\xac\x49\xe2\xa9\xca\xd9\x61\x0c\x51\xc9\x5c\x89\xd2\x4b\x10\xc3\x59\x9a\x5f\xc2\x98\x3b\xb4\x92\xcb\xc6\xd1\x24\x4c\xac\x68\xe8\x9c\x85\x49\xad\xef\xf2\x82\xe3\xe9\x89\xf2\xc2\x77\xe5\x3e\x26\xe6\x33\xfd\x5a\x18\x66\x05\x84\x48\xcd\x71\xc4\x0f\x97\xec\xcb\xad\x1e\x2c\x54\x7f\x14\xde\xb2\x62\xfa\x55\x99\x3e\xca\xe7\x44\x6d\xe9\xa9\xee\x3c\x1c\xf2\x5d\x52\x50\xe7\x16\x7a\xb5\x94\x41\x74\xbb\xe0\x3c\x4c\x10\xd1\x37\x89\x32\x39\x43\x4a\x85\x23\x7e\xaa\xa9\xf3\x0a\x9b\x46\x4d\xac\x9e\x97\xa8\x29\xb7\xd4\x2d\xd8\xd6\x53\xc3\x3c\x86\x24\xd4\x82\x86\x83\x11\x5e\x08\xe3\xbc\xb2\xb2\xae\xee\x0c\xbb\x5d\x50\xa2\x7c\x46\x2d\xdf\x49\x36\x06\xe1\x08\xa3\xf1\xac\x47\xc7\xb2\x04\x2d\x94\x4c\x61\x3e\x47\x6d\x63\x2f\x4c\x09\xf0\x0a\x3c\xeb\x59\xbb\x5f\x8a\x73\x07\xd7\xfb\x33\xab\x56\xa6\xf6\x6b\x83\xaf\xd7\xc6\x56\xd5\x24\x1b\xbd\xac\x6b\x6d\xa8\xc5\x38\x18\xdb\x66\xb9\x95\x95\x89\x47\x84\xf8\x22\xe7\x4d\x21\x9d\x33\x11\x66\x72\x92\x84\xe4\x14\x6b\xfd\xf3\x2c\x36\x7b\xdb\x34\x77\xd1\x30\x55\x02\x3a\xd1\x7f\x56\x92\x93\x1b\xb7\x25\x4f\xf8\x80\x1a\xc7\x49\xa7\x8d\xc6\x0d\x63\x88\x0e\x65\x23\xfb\x94\x89\xa4\x74\x51\x10\x78\x1f\x96\x13\x95\x59\x02\xce\x72\x6d\xd3\xea\x90\x8c\x5a\xec\x13\xe6\x1f\xf6\xb3\x43\x70\x24\x11\xb7\x9c\x4c\x65\xd4\xe1\xc0\x93\x64\x1d\xe6\xb5\x45\xfa\x3c\xd3\x11\x23\xa9\x89\x4d\xac\x40\x45\xf6\xa8\xe7\x4a\xf6\x28\xa3\x27\x3a\x2b\x5d\x5d\x81\xc7\xa4\x76\x07\x30\x97\xd6\xeb\xae\x9a\x96\xe5\x60\x46\x83\xc5\x7c\x2c\x11\xcd\x63\xc8\x75\xe5\x57\x60\xd5\x49\x53\xc0\x82\xfe\xe6\x59\x99\xa7\xb0\x93\xe6\x63\x85\xd9\xc8\xbe\x84\xcb\xdf\xc7\xcd\xb6\xb3\x21\x60\x2d\x2a\x64\x3f\xe4\x1c\x14\x4f\x05\xdd\x2e\xa0\x6b\x0b\x5f\xe7\xc3\x2c\x06\xec\x48\x07\x84\xe3\x30\xc9\x18\x63\x9f\x43\x25\x45\xbf\xf1\xcf\xad\x56\xda\x6b\x94\x55\xca\x54\x47\x1c\x2b\xb3\x4d\x2f\x1a\x52\x98\x9d\x62\x62\x69\x8b\x97\xa6\x12\x46\x79\x16\x03\x2c\xcd\x9c\x05\x15\xe6\x71\xb3\x8b\x2a\x68\x88\xcd\xac\x1a\x15\x67\x16\xe4\x1b\x35\xa6\xa6\x53\x52\x56\x62\xb6\x31\x55\xc4\x4c\x89\xf2\x02\xc6\x22\xb5\x12\x5d\xc0\x89\xa1\x67\x1c\x96\x20\x9c\x62\x89\xdd\xb1\x44\x1c\xff\xe7\x10\x75\xfc\x9f\x23\xaf\xd3\x5d\x20\xe2\xc2\xe3\xda\x7a\x67\xcf\x35\x1d\xe6\xda\x67\x6b\xbc\x99\x5e\xae\x1c\x2e\x70\xa6\x7b\x17\xe2\x9d\xee\xa5\x88\x2a\xe6\x51\x6d\x0c\xf0\x25\xf4\x6e\xb3\x21\x55\xe7\x26\x1b\x98\xf0\x34\x31\x8e\x15\x20\xb9\x63\x09\xe5\xbe\x9a\xbc\x0d\x4f\x13\xb0\x81\xa1\xa5\xd2\xc1\x7b\x5b\xa3\xbf\x72\x13\x92\x81\x50\xd1\xe5\x44\x05\xb2\x26\xde\xe7\x0c\x9e\x2b\x2f\x79\x4f\xb3\xcb\x6b\x95\x3d\x0c\x53\x46\x9f\x3b\x30\x84\x69\x7b\xe9\xc2\x83\xca\xc2\xa6\xda\xa8\x2d\xc9\x2c\x64\x12\xb7\x0f\xc7\x8e\xf2\x3e\xb5\x52\x57\x2c\xe5\x1e\x8b\xbf\x12\xed\xe9\x2c\x02\x64\x9e\x0c\x76\xf9\xee\xdc\x73\xe8\xe2\x5f\xec\xbe\xd7\x16\x3b\xaa\x7a\x8a\xad\x99\xca\xa5\x2d\xce\x17\x81\xe0\x05\x55\x32\x75\xbb\x80\x04\x24\x92\x4e\x1d\x5a\xa5\xd2\x39\xe3\xeb\xf5\x3a\x06\xe6\x54\x7a\xac\xb7\xcd\xc8\xb2\xae\xa3\x14\x16\xe4\x52\x15\x0d\xf5\xa4\xdc\xbf\x48\x93\x08\x92\xa3\x06\xe9\x1e\x62\x44\x17\x76\xb9\xaa\x10\x77\x13\xbf\x83\x8a\xe9\xa2\x52\x76\x66\xf9\x4c\x8b\x88\xa9\x23\x97\x86\x25\x62\x90\x56\xc5\x6e\x64\x28\x1b\xb5\x70\x41\xe2\x50\xf5\x58\xf7\xb7\x22\xef\x75\x8c\x24\xf9\x16\x20\x84\x9b\x61\xc0\x1d\x9a\x30\xf0\x95\x99\x73\x9f\x8d\x31\xe7\xfb\x4d\x3e\xa7\x17\x6f\xa9\xc1\x6d\x2d\x76\x0a\x79\x34\xa1\xca\xed\x5c\x8a\x1f\x20\xef\xcc\x63\x1d\xd5\xce\x2c\xbc\x0c\x4f\x53\x68\xa2\x65\x6b\xcd\xd4\x95\xab\x84\x59\x2c\xb3\xd1\x66\x79\xf6\x94\x55\xa0\x92\xa0\xe9\x15\xf1\xda\x40\x93\x60\xa3\x58\x2a\xba\x2d\xa3\x2d\x3e\x2d\x02\xd2\x4f\xa5\x4e\x3e\x20\xc4\xf9\x6e\x65\x83\x56\xb4\xae\x0f\x9a\xe7\xac\x4f\xdd\xd4\x69\x1b\x65\xae\x6f\x91\x64\x61\xfa\x56\x3a\x86\xe0\x3c\x2c\x85\xf2\xf6\x48\xa5\x12\x9d\xc2\xe4\x30\x55\xd1\xf4\xa5\xc7\x97\x71\xa2\x3a\x09\xcb\x89\x49\x6a\x8c\x2d\x2c\x0a\xfb\x7c\x51\x3d\x48\x34\x4f\x0b\xdd\xda\x0a\x56\xfe\xe2\x98\x9e\x5e\x29\x92\x14\xb7\x6a\xd6\xed\xd9\x83\x80\x0d\x02\x6d\xa9\x48\x5e\x3d\x6d\x94\x14\x25\x72\xa9\x69\xb5\xb5\x2f\xcf\xe6\xde\xb5\xa3\x37\x8f\x4c\x95\xdf\xd5\x11\x76\x49\x18\x76\xda\xbd\x45\x63\xa6\x35\x52\x8f\xa0\x0b\x89\x59\xbf\xb3\x6e\x29\x24\x0c\xdd\x14\x48\x98\x88\x04\x2d\xf9\x72\x85\x77\x72\x6f\xa9\x7b\x2a\x79\xc5\x1e\x2d\x8f\xb4\x5b\x2b\x57\x3e\x73\x51\x5d\x23\xa6\x63\x79\x66\x35\x5f\xeb\xba\xcd\x48\x6b\xaf\xfb\xa8\xdb\x35\xd6\x54\xed\xf4\x84\x4f\xa2\x6e\x57\xb1\xed\x19\x0d\xb4\xe8\x4a\xdb\xed\x3a\x43\x7f\xfb\x32\xd0\x87\x51\x44\x22\x77\xe7\x34\x23\x6a\x92\x8d\x17\xe8\x5f\xb6\x35\xd8\xa6\x0a\x1d\xfc\x6b\x5b\x0a\x99\x4a\x4f\x95\xca\x03\xbe\x1a\x1a\x56\x5d\xdd\x67\x04\xb2\x5c\xd6\x82\x45\xdc\x2c\x2c\x4b\x18\x07\xbc\x1a\x19\xa3\x12\x43\x95\xca\xe4\xd7\xa5\x9a\x60\x5e\x4d\x53\x36\xc8\xae\x79\x5a\xba\x42\x38\xeb\xee\xb1\x72\xde\xd6\xd7\x9f\xd4\x8c\x15\xd2\x88\x87\x26\x4a\xfc\x59\x92\xd8\x91\xe4\xee\xe5\x5e\x04\x02\xad\x40\x16\x39\x85\x51\x38\x2f\x21\xde\x23\xc7\x79\x86\xc0\x79\x98\x11\x9f\xa5\x72\x96\x27\x29\x3d\x00\xcf\x10\x2c\x46\x22\x35\x63\xed\x1d\x73\xbd\x3d\xb2\xb9\x80\x09\xe9\xe1\x95\xa9\xba\x44\x74\xca\x0b\xc7\x9c\xff\x11\x22\x1a\x8c\x1e\xaf\xa4\x01\x38\x9f\x24\xd1\x84\x78\x0f\x60\x11\x82\x72\xb6\xe4\x81\x59\x3a\x2f\xeb\x9f\xa9\x32\x99\x53\x93\x17\xa4\xc0\xf2\x39\x37\x2d\x54\x75\x97\xd7\x68\x95\x82\x75\xb4\xcd\xdb\xe8\x9a\x55\x9a\xa6\xe6\x4b\x7d\x43\x6d\xa8\x5a\x17\xd2\xaf\x71\x10\xa0\x9b\x2e\x00\x9e\x3d\x93\x77\x5b\x5d\x63\x09\x58\x24\x06\xbd\x1b\x6f\xe7\x8e\xdb\x7b\xbd\xc7\xbd\xf7\xb6\xd7\x6a\xd5\xcd\x9d\x52\xdb\x7d\xa3\xc7\xde\xbc\x83\x2a\xcf\x28\x83\xf7\xd7\x15\xef\x6d\xdd\xdd\xa2\x31\xfc\xde\xe7\xc0\x2d\x1c\x21\x1a\xc3\xc1\x33\xdb\xa3\x9b\xd1\xa2\x31\x5c\x25\xae\xda\x2f\x1e\xbc\xab\x1e\xbc\xab\xfe\x88\xde\x55\xcc\xe3\xf2\x1e\x7c\xb5\xbd\x21\x66\x99\x77\x27\xbd\xc5\xb6\x37\xe2\xe7\xc8\x9b\xc5\xb8\x1c\x7a\x4c\x47\x8a\x6a\xca\x36\xd7\xac\x12\x35\x75\x35\x5f\xad\xb0\x14\xe6\x95\x02\xd3\x75\x0a\x0b\x12\x37\x1e\xfb\x9f\xb6\xe8\xba\x71\x1b\x24\x68\xa2\x53\x22\xa5\x21\xb9\x18\x8a\x72\xb0\xff\x69\x8b\x1d\x27\xf8\x91\x60\x77\xfb\x49\xfc\x50\x25\x9f\x59\x5e\xaa\xa7\x59\x36\x02\x5b\x7b\xbb\xbb\xdb\x5b\x87\x3b\x7b\xbb\x60\x7b\x7f\x7f\x6f\x7f\x08\xb6\x84\x95\x3a\xa2\x55\x52\xa3\x43\x0c\x41\x73\x05\xe0\xfa\xc0\x4a\xb3\xe3\xc7\x43\xc6\x59\xaa\x4b\x03\x11\xb8\x80\xda\x3a\x10\x9e\x62\x09\xad\xcc\xdf\x0c\xf7\xec\x1d\x02\xd3\x33\xb9\xad\x27\x02\x9b\xc2\xb2\x0c\xc7\x10\x6c\x80\xc7\x8f\xd9\x5d\x4d\xac\x41\xb0\xdf\x1d\xc2\xbd\xf6\x9b\x0e\x2f\xf6\x1a\x38\x5f\x0f\x81\x18\xba\x3f\x1d\xec\xed\x92\x11\x2a\x04\x4a\x4d\xb0\x42\x5e\x77\x68\x80\x94\x64\x74\xc9\x71\x5b\xf7\xd3\x81\x55\x6d\xf7\x58\x8e\xec\x21\x3d\x66\x56\xfb\x3c\x2d\xeb\x0e\xef\xe1\xce\xc7\xed\xbd\xa3\xc3\x21\x60\x87\xd5\x98\xd9\x30\xa2\xd3\x12\xac\x80\x26\xfe\x13\x46\x13\x2c\x5e\x9a\xce\x3c\x8f\x2c\x40\xea\x0f\x0f\xcb\xdc\xc3\x32\xf7\x87\x5c\xe6\xc8\xfd\xd4\xbf\x35\x0f\xe2\xe5\x02\x0c\xd4\x8e\x71\x70\x0f\xe1\x05\xd4\x68\x51\x58\x66\x88\xfd\xa4\x9a\x52\xaf\xd4\x76\x48\xf4\x1a\x32\x6d\xd7\xde\xb7\x98\xce\xd3\xbf\x45\x9c\x82\x19\x77\x2f\x56\xce\x0e\x74\x08\x9a\xd8\x07\x54\x5c\xf4\x2e\xf3\xac\x5d\x2b\xc8\x81\xf8\x9c\xe5\xd9\xe5\x34\x9f\x8b\x76\xc5\x8b\x1a\xdb\x4e\x4e\xd2\x31\xe4\x94\x84\x31\xd5\x3e\x48\x0a\x16\x6f\xf6\x3d\xe5\x9b\xd8\x53\xbe\xc9\xf3\xf4\x1a\xc4\x30\x4a\x62\x92\xaa\x80\x2e\x47\x90\x42\xc7\xca\x38\x92\x6b\x32\x30\x36\x2c\x67\x72\xbb\x4a\xd2\xa6\xe0\x69\xaf\xb4\x42\x07\x56\x1b\x54\x35\x42\x1b\x86\xd4\x03\x84\xd1\x36\xec\xc4\x32\xca\x40\xd9\xa7\x45\x89\x43\x55\x49\x3a\xac\x2e\x72\xfc\xc3\x7e\x7b\x5d\x70\xdd\x35\xa8\xfe\xd6\xb5\x0e\xdb\xd5\x71\xa1\x7c\x1e\xd3\x34\x01\x24\x76\x95\x67\x54\x62\x99\x49\xc0\xa2\x2d\x4f\x77\xe1\xad\xac\x9a\xc0\x4a\xd5\xd5\x11\xa1\x28\x67\xdb\xa9\x0d\x5a\x92\xff\x6f\xd6\x7f\x22\x0b\x2a\x3a\x8f\x58\x6e\x84\x05\x3d\x37\xab\x59\xcc\x57\x4b\xf6\x99\xe7\x68\xb8\x75\x87\xcb\x64\x9c\x85\x68\x5e\xf8\x3a\x6c\x7e\xf7\xf5\xd8\x84\xab\xee\xb1\x80\xae\xee\x32\x09\x57\x71\xb3\x1e\xb2\xfb\x22\x7c\x52\x31\x99\x19\x66\xb1\xb0\xdb\xa1\x1c\xe4\x19\x04\xa3\x24\x0b\xd3\x6a\x6f\x6f\x5a\x97\x29\x88\xb8\x8b\x83\x29\x67\x8c\xcf\xac\x39\x8b\x7c\xfc\x3b\x3c\x83\xc5\x25\x9a\xd0\x63\x87\xe9\x69\x42\x44\x59\x3e\x86\x44\xf9\xc3\xf8\xb2\xb4\x31\xb5\xe8\xea\x08\x37\xc3\xd0\x73\xc4\x5a\x14\x02\x47\xfc\xd2\x6e\x66\x3e\x84\xa5\x91\xd5\xb1\x10\x2d\xec\x96\xb4\xfe\xc9\x1b\xb1\x86\x51\x79\x04\x5a\x8f\x8d\x35\xd4\xc6\x97\x35\x40\xef\x26\xb3\xe8\x97\x34\x52\x2e\x9f\x29\xad\xb6\xdb\xba\xcc\xc6\xee\x90\x23\x78\xdb\xa5\x47\x4b\xd0\x53\xb9\xee\x90\x48\xea\xc4\x61\x40\x70\xd0\x71\x42\x92\x0a\x1a\xde\x1c\x0c\x48\x1d\x4f\xcc\x4b\xf2\xb5\x37\x9b\xb5\x6a\xb8\xb5\xbc\x37\x74\xfb\x3c\xa9\xcd\x9d\x39\x98\x7c\x32\x7a\x72\xe6\x71\xd7\x60\xb4\xb7\x6e\x1e\xb5\xe8\xf2\x1a\x80\x33\xdd\x05\x42\x3b\xd6\x5d\xba\x2e\x82\x73\x0d\xae\xd3\x99\x24\xca\xb3\x28\x44\x2d\x6d\xe4\xdb\x75\x22\x24\x55\x4a\x4c\x16\x22\xc9\x2f\x31\x7d\xfa\x00\xac\x92\x8e\xb1\x3c\x30\xb2\x65\x1f\x93\x1f\xcc\x1f\x8c\xec\xa7\x68\x6d\xb1\x40\xe3\xc9\x13\x62\xb1\xd1\xb1\xaa\xa7\x4c\xf8\x43\x3e\x31\x4a\x55\xc6\x7c\x52\x40\xc4\x30\xa8\x4f\xec\x76\xbc\x3a\x15\xc3\x62\x6c\x4c\x43\xa9\x35\x6b\xa1\x9e\xc0\x50\x7d\x62\x51\x9f\xfa\x5a\x70\x1d\xd6\x61\x76\x82\x26\x6a\x36\x2f\xce\xb1\x58\x53\x5f\x85\xe2\xa9\xd6\x38\x68\xaf\xe3\x29\xfc\x4b\x9e\x64\xad\x46\xc3\x55\xbd\xb8\xa9\x48\x79\x94\x92\x4c\x3d\x96\x22\x2a\x6f\x0b\xcb\x84\x76\xa0\xe2\xa4\x1f\xb5\x65\x39\xda\xd1\xf0\x15\xd4\x94\x98\x38\xc1\x6b\xb6\x4f\xae\x6f\xb4\x03\xa3\x1d\x1d\x07\xb1\x16\x2d\x11\x6f\x8a\x5b\xdd\xd8\xe6\x8d\x06\x31\x52\xb4\x49\x13\x50\x8a\x7a\xd2\x37\x3b\x36\x19\x85\xa2\x27\x96\x9a\x20\x2e\x60\x3c\x8f\xa0\x32\x72\x61\x14\x05\x80\x05\x4f\x55\x45\x51\x18\x45\xc7\xec\x35\x15\xa5\x98\x38\xec\x99\x4f\x82\xd7\xfa\xf0\x75\xca\x49\x32\x42\xad\x36\x18\x5a\x94\xe5\x9f\x2c\x8b\x60\x18\x45\xd2\x04\x48\xdd\xe8\x39\xc7\xc3\x14\x22\xc8\xfb\x22\xcf\x15\xcd\x2f\x94\xd3\x6e\x2d\x75\x94\x73\x48\x76\xf1\x86\x49\x03\xac\x45\x50\xdd\xd2\xa7\x95\x69\x01\x36\xee\x5a\x2d\xd3\x90\xa9\xa7\x7b\x39\x82\x79\x98\xca\xd7\xb7\x1f\x28\x6c\xb0\x38\x50\x58\x55\x94\x31\x8f\x2f\xa6\x5e\x5c\x55\x3d\xb5\x48\x20\x2e\xfd\xc6\x8c\x44\x66\xe9\xb3\xdf\x6c\x48\xb2\x4a\xbe\x57\xaf\x72\x2c\x71\xef\xd4\xb8\x5c\x68\x72\xb7\x70\x0a\xaa\xc5\xb1\xf7\x19\x17\x0d\x03\xeb\xdb\x79\xbf\x74\xe5\xde\xab\xa4\xbd\x63\x05\xe4\x44\xe3\x72\x17\x80\x19\x6c\x4d\x32\x91\x13\xfc\x58\x2c\x2a\x14\x83\x13\x6f\x47\x2c\x1f\x1c\xaf\x17\x80\x46\x5d\x2d\x84\x5b\x95\x13\xc0\xa2\xc0\x6e\x37\x8b\xe5\xb6\xda\xab\x7b\x72\x72\x17\x81\x61\xbb\x7e\x03\xf2\x47\xca\xaf\x0e\xa3\xec\xe2\xa4\x45\x6a\xbe\xa2\x47\xdd\x2e\x38\xdc\x7b\xbb\x37\x04\x05\xa4\x0e\x83\x01\x28\x73\xe6\xa8\x25\xce\x30\xa5\xef\x57\x48\x8d\x8b\x1d\x5c\x2e\x41\xcd\x12\x64\x30\x82\x65\x19\x16\x97\x78\x92\x4d\xc3\x2f\x10\x94\x98\x1d\x9b\x24\x8a\x37\x89\xb4\x0e\xce\xf3\xe2\x0b\x55\x3e\xa7\xf3\x14\x25\xb3\x54\x49\x10\xf3\x08\x98\x39\x9b\x7c\x01\xb8\xba\xdf\x01\xe7\xbd\x81\x67\xf2\xea\x01\xad\x47\x77\x71\xe1\x68\x68\xde\xcc\xb2\x41\x18\xd3\x05\x49\xdf\x50\x89\x52\x1d\x4e\x2b\x3b\x44\xd2\x63\x8c\xde\xb1\x0d\x79\x62\x6e\x7c\x7c\x70\x86\x70\xd6\xbd\x72\x75\xec\x58\xa4\x31\x57\x2d\x95\xce\xbb\xce\x5a\xbc\xfb\x3d\x59\x3b\x63\xfe\xb6\x5d\x99\xfa\xd9\x61\x29\xa0\x5f\x4c\x0a\xd0\xb7\x42\x1c\x32\x47\x30\x1d\x39\xc3\x43\x99\x15\x29\x21\x32\xf2\x4e\xb9\x22\xae\x55\xec\x18\xab\x87\x35\x81\xde\x3e\x4a\x08\x47\x37\xdd\x5c\x21\x72\x25\x5d\x2e\xd5\x55\x51\xe8\xd6\x9d\x65\x7f\xe5\x55\xfe\x8b\x8e\x26\x82\xe4\xc3\xba\x84\xe0\x62\x87\xfc\x55\xde\x0b\x09\x43\x7f\x28\x5f\x14\xa9\xc2\x7f\x9a\x6a\x2a\x4f\x07\x24\x1d\x85\x2d\xb1\x4e\x69\xa2\xc6\xbd\xf2\x08\x6a\x8f\x68\x6e\x0c\x57\x49\x7c\x2c\x35\xea\x3e\x96\xd0\xfd\x87\xb3\xed\x87\xb3\xed\x3f\xc6\xd9\x36\x3b\xd4\x66\x77\xb8\x1f\xf2\x7e\xde\x57\x3c\x7e\x73\xb7\xb1\x95\x67\x67\x10\x4b\xb1\x90\xe5\xb3\x27\x7b\x75\xb2\xeb\x20\x89\xe8\x79\x46\x25\xcc\xf5\x61\x5a\xe6\x20\x4c\xd3\xfc\xbc\x24\xbb\x34\x6a\x93\x2c\xad\xa8\x21\x6c\x83\xf1\x31\xb9\x80\xf1\x35\x05\xb2\xf6\xce\xec\x44\x48\xdb\x7b\xe0\x2e\xa0\xdc\xcc\x60\xcf\xac\xb5\xca\x16\xb8\xa5\x5b\x83\xc1\xd5\x15\x39\x44\xcb\x47\x8a\x99\xb8\x29\xcc\xc7\xcd\xb6\x69\xd0\xa0\x3b\x4c\xa1\x68\x51\xab\x34\x8b\xbb\xce\xcd\xac\x46\x63\xd4\x9e\xb2\x37\x22\xf6\xf6\x36\x69\xa1\x67\xd5\x4b\x20\xc5\xe2\xa2\xde\xcc\xd6\x8e\xe5\x78\xc6\xb6\x96\x6a\xd2\xbd\x26\xfa\x6b\x97\x8a\x70\x7e\x9e\x66\xfa\x62\xe7\xe2\xd0\x83\x6d\xfd\x02\x3c\x50\x67\xb0\x48\x46\x24\xac\x4b\x01\xa3\x10\x8b\x32\x25\x6b\xd4\x93\x27\x20\x0d\x7f\xbd\x04\x69\x1e\xc6\x20\xbe\xcc\xc2\x69\x12\x81\x3c\x83\x25\x69\x8d\x0d\x95\x6c\x88\x0e\x37\x16\x48\x6c\x6f\x49\xa0\xf8\xad\x11\xde\xb8\x05\xc5\x47\x6f\x0c\xd1\x9e\xd8\xc8\x3b\x42\x9f\xe3\x61\x52\xd5\xe0\xc5\xa1\x22\x54\x83\x07\x1b\x22\xbe\x99\xf7\x1f\x09\x59\x87\x1f\xde\xf3\x29\x0e\x50\x9e\x27\x24\x67\x90\x86\x1f\x00\x51\x58\x42\xba\xd3\x1f\x1a\xf7\x07\xe9\xf6\x60\x5e\x40\x66\x46\x0d\xc8\x39\x69\x44\x67\x13\xdd\xb6\x4f\xe0\x85\x6d\xe0\x90\x06\x62\xe3\x85\x11\x41\x75\x61\x01\xcd\xba\x4b\x8d\xbb\x6d\xe7\x29\x85\x71\xf6\x41\x21\xdb\xe0\x35\x45\x9c\x54\xc3\x26\x5d\x1b\x0c\xf9\xfc\x63\x60\xa6\x7e\xf6\xc8\xaa\xdd\xb8\x30\x4a\x50\x1b\x1a\xa8\x06\x1a\x0c\x66\x7f\x09\x81\x9f\x02\xa3\x0e\xb5\xbc\xfe\x2d\xe4\x29\xf3\x39\x40\xa8\xe7\xac\x97\x2d\x90\xe3\xbb\xe1\x52\xa7\x7a\x1d\x51\xae\x6d\x62\x74\x93\xda\x58\x29\xd5\x46\xa6\x6c\x91\x28\x67\x95\x13\xcc\x59\x16\x51\x2d\x36\x06\x2e\x53\xd1\x66\x1c\xd3\x7b\x3b\xc2\xa6\x17\x66\x31\x28\x21\x2a\xc1\x7c\x46\x3e\xb0\xcd\x0d\x91\x12\x09\x82\x05\x5e\x67\xf2\x33\xa6\xb8\xb1\x48\xbb\x4c\x2f\x50\x2f\xee\x7c\xc8\xc7\xe5\x26\x3a\x40\x61\xc1\x8c\x46\xa6\x41\xb4\x84\xe9\x48\xff\x32\xf2\x85\x76\xea\xaa\xb2\x41\xa9\x58\xcb\xf0\x07\xd3\x91\x23\x62\x13\xbf\x6d\x3a\x86\x88\x9a\xdd\x70\x71\xe3\xca\x29\x31\x69\x48\x02\x94\x12\x67\xba\xb1\x35\x44\x07\x6e\xa9\xe3\x92\x1f\xe4\xc3\x18\xa2\x96\x71\xa9\x8d\xf9\xc0\x1a\x5b\xab\x6e\x17\xc4\x79\xd6\x64\x17\xa1\x31\x8e\x8c\xa0\xc4\xdd\x96\xb8\x0b\xf0\x97\xdc\xe5\x8c\x04\x5d\xe9\x74\x3a\xe0\x97\x39\x8d\x7c\x8d\x5b\xc4\x82\xd8\xd8\x8a\x3b\xee\x00\x7b\x6f\x00\x5f\xeb\xd7\xba\x8d\x89\x2e\x50\xb7\xb7\xb0\xf4\x83\x73\xcf\x4b\x5d\x7e\xfd\xb7\x90\xe9\x9d\x33\xd3\x35\x98\xe1\xb3\x38\x80\xf0\x6f\xc9\xc9\xb3\x3c\x4d\x29\xe3\x2c\xe2\x62\x85\x4b\x65\x21\x93\x43\x15\xab\x0b\xb1\x35\x67\x1f\x85\x23\xb7\xc6\x34\xb9\x87\x6d\xd8\xe8\xe6\x85\xf3\xdc\x99\x30\x20\xef\xbe\x6b\x68\x5c\x37\x01\x4d\xee\xc8\x6b\x9a\x0f\xea\x71\xca\xf2\x7c\x22\x5d\xdb\x49\x77\xa4\x85\xfd\xb5\xf1\x42\x56\x30\xe4\x85\x74\x56\xba\x31\x39\x6e\xc6\xaa\x95\xa6\x07\x82\x8b\x19\x37\x1d\x0b\x9a\x4f\x79\x9a\x62\xa9\x22\x31\xa0\x9c\x37\xa4\x45\xf0\xee\x0f\x4e\x61\x86\xc8\x46\xb6\x83\x39\x8b\x74\x49\x2e\x2e\x33\x76\x39\xe3\x98\x52\x87\xb0\xdd\x4e\x7c\xc2\x01\xae\x03\xa0\x7d\x08\x24\xd3\xb1\x2f\x6a\xac\x2b\x6a\x8f\x26\x9c\xca\x57\x6a\x35\x93\xa8\xc5\xd4\x86\x5a\x16\x00\x2b\x49\x0e\x9f\x73\x8e\xe3\x8b\x80\xed\xd4\xb6\x0a\x48\x7a\x47\xbc\xfb\xb7\x9c\x37\x77\x89\xa0\xa3\xa6\x73\xed\x54\x59\xa3\x8e\x66\x40\x5c\xc6\x26\xe7\x35\xae\x79\x2d\x4d\x6e\x0b\x9e\x8e\x8e\x15\x7f\xd1\x97\x34\xa1\xc2\xf7\x56\xea\x9c\x52\x2b\x36\x34\x61\x0d\xde\x22\x88\xfe\x42\x83\xe5\x1c\xe1\xba\xaf\x28\x66\x8d\x92\xa7\x01\x88\x40\x00\x78\x0d\xdd\xf2\x43\x48\xf1\xe7\xf8\xa8\x9e\x98\x89\xdf\x15\x7d\xe8\x64\xf0\x9c\x1d\x98\xa9\x14\x51\x03\xc9\x51\x71\x99\xc4\x5a\x58\x44\x97\x98\x5c\x20\x10\x5a\xd1\xa9\xa1\xfd\x46\xa7\x0b\x24\x22\x95\xc8\x6c\xe3\x58\xc1\xcd\x46\xa8\x5b\x53\xe0\x54\x94\x74\x21\x20\xe5\x8c\x6d\xe2\xd6\x26\x3b\x66\x80\xd8\x8c\xfa\xa8\xc6\x24\x29\x20\x39\xc9\xc0\xdb\x8e\x19\xcc\x62\xe2\x14\x28\x06\x36\x2c\x89\x41\x27\x2b\x31\x7f\x2a\xa1\x8d\x68\x35\xf9\x88\x80\xe2\x22\x58\xe7\x69\x53\x13\x8e\x68\x79\x9e\x85\x65\x99\x8c\x33\x18\x77\x4c\xec\x4c\x0e\x72\x49\x66\x73\x24\x84\x6e\x15\x9d\x56\x0c\x86\xb3\x7a\xdd\x89\x8e\x61\x2f\x3d\xc3\x41\xaa\x6a\x84\x61\x5a\xc0\x30\xbe\x94\xa1\x19\xa4\x72\x58\xde\x86\x93\x74\xc5\x95\x6b\xab\x55\x7d\x49\x46\x2d\xa3\x15\x11\x34\xb0\xa7\x87\x8e\x93\xf3\x8d\x4a\x6e\x3b\xcc\x27\xd6\x95\x51\xce\xfa\x92\x4c\xa7\x30\x4e\x42\x04\xd3\x4b\xbd\x39\xc6\xc6\x51\x3d\x9e\x55\x75\x0e\x52\x71\xcb\x19\x5c\xc5\x1d\x96\xed\x91\x56\x87\x14\xeb\x7c\xb1\xa1\xdd\x51\x8e\x8e\xcf\xad\x74\x34\xf6\xe2\xad\x4b\x2e\xea\xda\xa8\xe0\x24\xad\x31\x9a\xf8\x53\xfb\x65\x8c\x12\x3b\x97\xb5\xbb\xa5\x50\x5c\x3b\x84\xbe\x5e\xbe\x63\xea\xea\x5b\xa7\x7f\x96\x0e\x91\xcf\xb8\x0a\xa1\x77\xab\x86\x3c\x27\x11\x10\xa6\xf9\x19\x37\x99\x82\xb0\xbc\xcc\x22\x65\xeb\xe3\x52\x90\x5c\x32\x7a\x9e\x91\xab\xfc\x1a\x51\x14\x8d\xc3\xa2\xa0\x25\xb7\xd4\x6b\x0c\xcb\xd4\xed\xd9\x14\x58\x54\xc6\xd3\xbd\x9a\xb8\x1e\x2d\xc3\xe9\x2b\x23\x4a\x9b\xaa\xb9\xb1\xae\x3a\x3d\x4d\xbb\x5d\xb0\x33\x92\x72\x32\x29\xc5\x0d\xd1\x4b\xc8\x22\x14\x81\x04\x01\x35\x04\x1d\x8b\x36\x35\x81\xc4\x7f\x84\xd1\xa1\x0d\xa8\x80\x2d\x41\x82\x54\x11\xeb\x5c\xa4\xad\xc9\x20\x26\xa5\xbd\x82\xb8\x06\x81\xd5\x67\x0e\xac\xba\x06\x93\x24\x3f\xed\x7b\x0b\x48\x65\x68\xe4\x05\x34\x5d\x13\xd3\x7c\x6c\xb7\xa3\x8b\xa9\x8a\xbd\x04\x29\x3d\xc4\x6b\xc1\xba\x51\xc5\xb5\x9d\x5f\x47\xfd\xe8\x50\xfa\xcd\x0e\xd4\xe5\x0c\x2b\x52\x1a\x53\xb7\x77\xde\xd2\x17\x43\xc6\x35\xad\x76\x87\xae\xb8\x51\xc8\xe3\x6e\x92\x68\x41\x30\x06\x78\xfe\x4e\x8a\x3c\xcb\xe7\xa5\x08\xc2\xc9\x7c\x0b\xf0\x5a\x6e\x86\xdc\xa2\xd5\x30\x65\xb6\xe9\x74\x2d\x26\x91\x4d\x72\xe9\x60\x58\x87\x31\x6c\xf7\x3f\x5c\xc3\xa2\xf1\xba\xc5\x48\x5d\x2f\x21\x81\xad\x83\x57\x4a\x65\x47\x2e\xc4\x8a\x63\xd7\xeb\x93\x60\x75\xf0\x70\xb0\xfa\x70\xb0\xfa\xc7\x38\x58\x65\x16\x2e\x7e\xbe\x2a\x2d\xd8\xb7\xb9\x39\xcc\x0a\x2c\x79\x12\xea\x0b\xab\xd1\x9c\x97\xf4\xe4\x29\x42\x22\x81\xf2\xd2\x97\x90\xb3\x51\x32\x76\xc2\xd2\x4f\x2a\xf0\xce\x69\x68\x64\x23\x4a\x4e\x43\x2d\x13\x51\x09\xc7\xe7\x09\x22\xd7\x73\xdc\xd7\x8f\xe5\xf7\xce\x2f\x55\x79\x89\x88\xa5\x9a\x66\x65\xa3\x0e\xe6\xf4\x64\xf5\x34\x19\xb3\x08\x1b\x0c\x5e\x90\x88\xd9\x2e\x29\xf0\x9b\x64\x6c\x9c\x2e\x08\x38\x66\xcb\xa4\x76\xed\x2b\x0a\x75\x25\xe0\xaf\x05\x9c\x88\x01\x27\xbf\xb9\x1c\x95\x89\x45\xd3\xd3\xa6\xa6\x7d\x51\xac\x3d\x17\x0e\x51\x2e\x4a\x73\xc0\x75\xcb\x10\x95\x94\x9f\x0a\xc8\xce\x66\x95\x03\x14\xad\x95\x53\xf9\xde\x6a\xea\x54\x2d\x83\xb7\x39\x2c\xcb\x02\xb8\xba\xb2\xbf\xb1\x9d\xb1\xfb\x23\x0c\x8b\x34\xc1\x45\x1d\x48\x66\xb3\x39\x7a\x0b\x47\xe1\x3c\x75\x9e\xf2\x2c\xc6\x16\x6b\x0e\x66\x8b\xe2\x4c\xda\x99\xbf\x08\xb3\x69\x27\x56\xda\x34\xdd\x54\xd5\x84\x49\xce\xa3\x27\x15\x11\x4f\x9f\x7e\x9b\xce\xd8\x77\xf1\xa8\x62\x85\xeb\xf1\x8c\xbf\xd6\xb0\xa3\x4a\xe5\xb3\x87\x2e\x9c\x07\xdf\xc3\x8b\x0a\x4a\x28\x73\x94\x12\xa0\x54\x3c\x44\xf3\x11\x08\xb5\x18\x9f\x24\x10\xac\x70\x91\x10\x2e\x0f\x78\x16\xbf\xdf\xfe\xb3\x67\xf2\x92\xba\xb0\xfe\xee\x9d\xb6\xfc\x08\x42\x8b\x74\xed\xbc\x65\x50\xfa\x66\xab\xdd\x88\xcb\xb6\x2b\xdd\x26\xd4\x43\x4e\xe5\x30\x99\x3c\x5e\x5d\x19\xec\xb7\x19\x91\x4c\x21\x5a\x28\x42\xb5\x84\x23\xb6\x1d\xaf\x98\xe0\xc5\x82\xb9\xb9\x8f\x58\x6d\x85\x4f\xad\x1e\xe5\x2c\x6d\x7c\x82\xc0\x34\x19\x4f\xa8\x8e\x2c\x42\x8b\x33\x3b\x9b\xd5\x3a\xca\x17\xb6\x8d\x72\xbb\xe5\xe3\xe6\x38\x2c\x3f\x15\x49\x04\x9b\x01\xc0\xbf\xf1\x1f\x32\xc6\xf8\x47\x96\x67\x11\x74\x5d\x47\xfd\x02\x2f\x2b\x2e\xa4\x7e\x81\x97\xf5\xaf\xa4\x92\xba\x2c\x6a\xd2\x3a\x36\x14\x8f\x91\xb7\x30\x4a\xa6\x61\xda\x52\x01\xdc\xd7\x01\xed\x23\xe4\xdf\x9a\xf7\x95\x80\xba\xf7\x3e\x05\x5c\x6d\xdd\xc7\x4c\xb8\x39\xb3\x3f\x30\xf9\xb7\xc0\xe4\x4c\xfd\xb2\xb8\x9c\x1c\x28\xf3\xec\x58\x8c\xc5\x2b\x95\xb2\xa5\x98\xfb\xc2\x56\xc4\xd8\x37\x8f\xfa\xb5\x90\x9d\xd1\x45\x5b\xae\xca\x2d\x74\xd1\x51\xd7\xe5\xc7\xdc\x38\x21\x48\x6d\x00\x88\x40\x2f\x39\x27\xb5\x0e\xa0\xdc\xb3\xc1\x5f\x14\x42\x91\xbb\x82\xee\x06\x2c\x28\x67\x2b\x26\x94\xb4\x72\x5e\x74\x08\x0f\xba\x8b\x91\x4f\x2a\xec\x38\x2c\xdd\x90\xe3\xb0\x34\xe0\x08\xcf\x2b\xc0\x52\x3b\x55\xbe\xab\x65\xb8\x33\xa0\xb3\x80\x7e\x03\x9b\x1b\x29\x2e\x6e\xc5\x7d\x3c\x89\xd3\x6d\xb8\x90\x65\x94\x5a\xc8\x8c\x5a\xfa\xb2\x1b\x71\xa4\xd9\x92\x11\xc4\x48\x4f\x55\x25\x53\x54\x2d\x60\x51\x17\x94\x3d\xc0\x0e\x28\x95\x59\xf9\xe7\x1a\x1c\xeb\x05\xf5\x37\xea\xe5\x5d\x91\x96\x69\x3e\x9d\xa7\x21\x4a\xce\xe0\x8f\x61\x79\x54\x92\xeb\x8a\xbe\xca\x2c\x58\xab\xb6\xf1\xc2\x3a\xc6\xa2\xa4\x24\x81\xee\xd4\xc1\x21\xd3\x7c\x6c\xba\x4a\xca\x0f\x4a\x62\x29\xcb\xcc\x47\xac\x75\x0e\x23\x9f\xe3\xa2\x34\x86\xac\x63\xd1\x63\x6d\x2d\x3d\x63\xc8\x98\x2f\x3f\x47\xc8\xee\x62\xe1\xb4\x20\x95\x2f\x3b\x29\xf4\xaa\x55\x57\x21\x52\x9d\xe2\x2b\xd4\xed\x02\x11\x55\x8b\x04\xae\x54\xb6\xfd\xb4\x6d\x3c\x98\x1f\x92\x69\x82\x1c\xe3\xad\x03\x08\xc2\x8a\xd7\x1e\x36\xd1\xbe\x1b\xa5\xca\xe4\x57\x97\xa8\x95\x1f\x0d\x78\x94\x4c\x61\x89\xc2\xe9\xcc\x5b\x48\x40\xa8\x13\x92\x7e\xca\x7c\x93\x5e\xfb\xec\xab\x58\xd8\x10\x14\x15\x4a\xe7\x72\x86\x80\x9c\x9e\x3a\xaf\xdb\x9f\x6d\xd5\x24\x41\x70\xda\xd6\x6f\xc4\x59\x7e\x83\x04\x46\x3f\xb9\xd5\x66\x83\x6b\xd5\xa6\x85\xea\x4c\x8b\x53\xb9\xd9\x5f\x62\x52\xa4\xf9\xb8\x92\xfb\xd5\x19\xea\xe3\xfd\x34\x1f\xeb\xca\xb5\x3d\x01\x94\x76\xcc\x49\xa0\xb6\xa0\x4e\x01\xed\x78\x20\x19\xe1\x67\x4d\x6c\x0b\x92\x18\x1f\x1c\x7c\x60\x16\x5d\xd7\x6b\xb5\xe4\xb2\x56\x75\x0d\x01\xef\xac\xc4\x68\x24\xcd\xc7\x8e\xca\xf9\x5b\x4f\xa5\xa2\x90\xa9\xb3\x8a\xc3\x8f\xfa\x9b\xb2\xf3\x49\x52\x62\x91\x37\xcb\x4b\x74\x8b\x5d\xd9\xa7\xbc\x5c\xac\x24\xd8\xb9\xb7\xea\x8b\x46\xbb\x15\x95\x2b\x30\xf6\xba\x5c\xc4\x6f\x3a\xb3\xf0\x92\xdc\x4d\xd8\xd0\xec\x37\xea\x27\x31\x1e\xe4\x25\x42\xa9\x73\xcb\xc0\x3f\xea\xd0\xe7\x79\xf1\xe5\x30\xff\x54\xe4\x67\xd0\x5f\x4a\x01\xd2\x4b\xcf\x8a\x24\x2f\x12\x72\x3b\xd0\x53\x94\x43\x68\xb9\x26\x46\x7a\x1a\x32\xcd\x11\x99\x8a\x2d\x8a\x2c\x0d\xc8\xa2\xca\x2b\xe5\x3d\xd8\xd0\x9e\x5e\x83\x63\xe5\xf1\x04\x0c\x15\x5f\x80\x6b\xb5\x6d\x6a\x6c\xa6\x76\xe7\x34\xcd\xcf\xc9\x45\x0d\x75\x5f\xed\x6f\xa4\xfa\x4a\x01\x4d\x6a\x89\xf9\x0e\xe4\x59\x7a\x49\x33\x87\x20\xed\x96\x03\xbf\x71\x40\x6f\x16\xb8\xae\xcc\xf0\x6b\x07\x60\x68\x5e\x86\xd1\x2f\x1c\xd8\x7b\x3d\x8c\xe7\xd2\x32\x52\x1c\xc2\x90\xa9\xc3\x5c\x41\x2b\xa5\x66\x9d\x49\xb2\xac\xb2\x50\x3d\x25\xc4\x8c\x20\x23\x01\x2f\x66\x49\x71\xe9\x10\x2a\xca\x57\x9d\x49\x4b\x1a\x90\xc6\x09\x8f\xbf\xf9\xa7\x8f\x01\xec\x9c\x3c\x64\x56\xb8\x54\x0c\x03\xa2\xbd\xee\x9e\xd7\xfb\xe1\x39\x67\x32\xf6\xc6\x03\xa8\x34\xb1\x59\x46\x49\x62\x8a\x00\x59\x4a\xd5\x02\xfe\x54\x32\x32\x0a\x48\xe5\x7a\x51\x57\x99\xc9\xa2\x11\x12\x06\x9b\x24\xd5\x73\x0b\x99\x6e\x77\xd9\xf9\x44\xe6\xb7\x3a\xa3\xbd\x13\x5a\x9d\xb2\xb7\x9a\x8b\x86\xc5\x9b\x12\x6c\xf9\x19\x24\x84\xb6\x69\x4e\x72\x65\xe6\xd1\x4f\x03\xf8\x00\xbc\x81\xd1\x64\x75\xc0\x13\x58\x71\x50\x87\xdd\xc8\x08\x75\xa7\x1c\x08\x08\xd1\xb8\xb8\x12\x16\x25\x8d\x41\x74\x50\xfe\x21\x3f\x87\xc5\x56\x58\xc2\x56\xbb\x53\xc0\x59\x1a\x46\x90\xc8\x9b\x00\x34\xcd\xa8\xe1\x0e\x27\x0d\x16\xd4\x9d\xd7\xd7\xb4\x8e\x0a\x1e\xdd\x88\x42\xa4\x10\x3d\x63\xc4\x8d\xed\x9c\x86\x59\x4b\x4f\x0f\x3f\x29\xe8\x79\x35\x39\x0f\x39\x0d\xb3\x4e\x52\xfe\x84\x31\x69\x91\x6c\x32\xec\xcd\xdb\xa4\x80\x11\x6a\x55\xd0\x81\x00\xb2\x9a\x79\x88\x14\x07\x61\x0f\xc8\xd1\xea\xb2\x63\x74\x17\x23\xc4\x6a\x7a\x54\x97\xfa\xd7\xeb\xea\xe5\x57\x2a\x3d\x0f\x2e\xb3\x28\xc9\xdc\x9a\x26\x0b\x62\xaf\x71\xe6\x63\xf3\xa5\x33\xe0\x94\xa6\x85\x93\x20\x55\xc4\xd5\x32\xc9\xc6\x64\x6b\xe7\xdc\x7a\xdb\x60\x66\xd0\x2f\x16\x7d\x6b\x41\x15\x2a\x94\x59\xc3\x24\x19\x4f\x60\xb9\xa8\x06\x15\x4a\x0b\x8d\xc3\xbe\x7f\xc9\xf2\xf3\xec\x00\x85\x08\xba\xe2\x47\x2a\x5f\xfd\x4d\xa8\x55\xac\x9b\x35\xcc\xe6\x69\x0a\xe3\x45\x55\xa8\x50\x15\x36\x01\x3b\x0a\x98\x37\xcf\xc6\xa2\x63\xd9\xe1\x42\x88\x40\xad\xa9\xa2\x8a\x85\x65\xb5\xa3\xb7\xa1\xe3\x9d\x06\xed\xda\x20\x0e\xfd\x9f\xb4\xb2\xa6\xe8\x19\xba\x5f\x6b\x65\x34\x95\x63\xe8\x78\xc7\xa1\x7d\x87\xfe\x43\xef\x17\xbd\xa4\xbb\x63\xfe\x6f\xde\xd2\xa6\x6d\xd1\x51\x89\x09\x62\xf4\x41\x33\xc9\x0c\x9d\x6f\xf5\x12\xea\xfe\x75\xe8\x78\xa7\x43\x1b\x24\x75\xbc\xd4\xe1\x4d\xb9\x35\xf4\xbc\x17\x9c\x6f\xfa\xb0\xd1\x83\xa8\xc6\xb0\xff\x83\x1a\x0b\x4a\xf3\x3c\x69\x0c\x07\x3d\x5f\x54\x28\xbc\x38\x34\x86\xab\x24\x90\xc8\xea\x83\xbf\xdb\x83\xbf\xdb\x1f\xd2\xdf\x8d\x71\xfd\x7d\x64\x83\x5a\x3e\xf9\xc5\x12\x0e\x6d\x34\x89\x95\xee\xa5\x46\xdf\xdd\x63\x36\x0d\x93\x86\x8b\xf3\x5e\x84\x69\xda\x35\x52\xe8\x92\xcb\xcd\x66\xae\x2a\x7f\x36\x0c\xee\x99\x6f\x67\xf3\xab\xcc\x82\xe1\x4e\xe7\xf7\x99\xae\xa8\x2c\xf7\x82\x9a\x2f\xfb\xa6\x39\x11\x78\xc5\x6c\x31\x52\x6b\xa6\xaf\x6e\x5f\x35\xc9\xa9\x19\x92\x4d\x3c\xa9\x97\x3f\xeb\x50\x3c\xc3\x36\x03\x62\x8f\x3a\xcc\x1d\xe6\xf2\xe0\xaa\x98\x39\x4c\x5a\x10\x53\xe2\xae\x20\xef\xc9\xa9\x3b\xa2\x62\xec\xd9\x30\x2e\x91\x33\x9e\xef\x4a\x64\xca\x4c\x12\x79\x83\xac\x1f\xf4\xc6\x57\x31\x2e\x69\x72\x8e\xc7\xda\x56\xaf\x3e\xf2\xaa\xa6\xb8\xa0\x03\x2a\xaa\xaf\x2c\x76\xe3\x5f\xd4\x64\xa0\xec\x6a\xa9\xdd\x51\x77\x3f\xcd\xe8\x12\x15\x5a\x6c\x4b\xd2\x64\x69\xa2\x2c\x4a\xf6\xa9\xa6\x3d\x77\xa5\xa2\x4b\x4a\x10\xe5\x45\xe1\x0f\x3c\x4a\x76\x75\x21\x82\x9b\xc5\xd8\xca\xc8\x22\x12\xaa\xb0\xda\xc4\x77\xb2\x3b\x2c\xc1\x57\xb2\x37\xbc\xc6\xed\x27\x88\x5d\xb0\xd1\x44\x47\xc5\x90\xaa\xed\x56\x8c\xa5\xdc\xd2\x53\x38\x42\x4a\xcb\xf9\x25\x54\x07\xa8\xdb\xe5\xb7\xac\x88\x81\x51\x0b\xc1\x43\x96\x71\x12\x4e\x52\x26\xeb\xc3\x8b\x7e\x48\xee\x67\x16\xe1\x65\x69\x8e\xf3\xe3\x96\x95\x48\x36\x6c\x8b\x78\xfa\x6d\xf0\xe4\x89\x28\x41\xd4\x11\xdd\x74\xcc\x40\x69\x98\x6b\x2f\xac\x74\x5c\xd0\xe0\x05\xb0\xe3\x82\x33\xb1\x45\x70\xba\x70\x56\x7d\xbc\xb1\xe1\x63\x75\xfd\x62\x19\xde\xdb\xb3\xc4\x8b\x15\xc9\x10\x5b\x8b\x22\x41\x54\x84\x7d\xe6\x26\x3c\xe2\x7a\x65\x32\x90\xc9\x83\x28\xff\x44\xc1\x3d\x0c\x68\x2d\x50\x7a\xb4\x78\x4f\x08\xe8\x30\x15\x58\xe8\xae\x6a\x0b\x79\x53\xe0\xb3\x80\x31\xb5\x34\xc7\x37\x11\x3e\xcb\xca\x1e\x3d\x33\xb1\x37\xf9\xf1\xb5\xba\x66\xa8\x13\xad\xe5\xcc\x43\x8c\x72\x5f\x8a\x0b\x2d\x53\xf1\x06\xf0\xa4\xaf\x70\x65\x42\x60\x17\x95\x94\x6e\x1b\x49\x90\x6b\x3a\x85\x79\x52\x4d\xf1\x41\xb9\x7d\x92\x1b\x6f\x55\x0b\xb9\xe4\x86\xa9\x6e\x58\xe0\xfc\x5e\x00\x5e\x28\x16\xcc\x45\x8d\xcd\xb3\x59\x18\x7d\xd9\xa3\xa7\x25\x9a\xc3\x24\x79\x65\x18\xd5\xcc\x97\x1c\x19\xcb\xac\x93\xf3\x0a\xe9\x0f\xc1\xb4\x1b\x60\x00\x5e\xf3\x97\x3c\xd2\x3f\xe0\x9b\xe2\x75\x65\x0e\x88\xe8\xfc\xbe\x40\xff\xaa\x32\x16\xb0\x0a\x4c\x2e\x60\x26\x28\x35\xe0\xb8\x48\x9d\x79\xdc\x3b\x01\xc3\x45\xe1\xe7\xb7\x48\xb2\xfb\x50\x7a\x65\x8a\xad\x84\x87\x31\xf0\x12\x6a\x8a\x8f\x4e\xa7\xc3\x25\xc8\x96\x59\x8f\x63\x11\xb4\x82\x29\xed\xd0\x65\x98\xa4\x27\xe7\xe0\x78\x55\x0c\x45\x2d\x01\x8d\x9b\x63\x56\xce\xab\xa4\x97\x2f\xc5\xf6\x33\xd4\x2e\x19\x86\x59\xac\x47\xdd\xe1\x60\xb3\xb0\x24\x53\x63\x02\x65\x3d\x34\x2f\x29\x2e\xc2\x48\xeb\xe5\x7f\xc6\x01\x24\x07\x79\x5d\xce\x27\x58\xfa\xb3\xbf\x2f\x93\xd8\xdd\xbd\x96\x73\x5d\xd2\x70\x59\x55\xd6\x3f\xb2\x9d\x93\xba\x2c\x0b\xf5\xad\xa9\xb8\xba\xb0\x23\x61\xcb\x75\xfd\x51\x2d\xa4\xaa\x6f\x76\x41\x79\x20\x45\xc3\x8c\xf3\xf5\x81\x43\xaa\x2e\x2f\x8f\x5d\x77\xb9\xa5\x81\x5c\x88\x5a\x88\x26\x94\x18\xac\xf2\x40\x43\xcf\xbe\xd9\x49\x4a\xa9\x62\xa0\x65\xcc\x25\x2d\xb8\xbe\xe3\x86\x75\x9d\x66\x03\x3b\x3a\x92\x2d\x4b\x94\xd8\x48\xfc\x3a\x8c\x16\x43\x23\xa0\xde\x43\xeb\x7a\xd8\x6b\x8a\x3a\x34\xa3\xa0\x00\x80\x8a\x4b\xed\x7a\xaa\x02\x48\x6e\xa4\x56\xf5\x59\xbd\x40\x1b\x91\xc8\x05\x2d\x23\xce\x11\x9d\x09\x1b\x00\xaa\x45\x94\x32\x3a\xe2\xbc\x6d\x4b\xe3\xaa\xb5\x34\xd5\xd9\x5b\xfb\x16\x2a\xbd\x68\xed\x35\xc8\x68\xf1\x6f\x70\x52\x2e\x9c\x5b\x92\xe9\x18\x28\x73\xfc\x7d\x05\x7a\x64\x03\xa7\xed\xb8\x6d\x3d\x57\x4d\x67\x1c\x66\x34\x04\x40\x16\xb3\x48\xa2\x24\x37\x75\xf6\x94\x6f\xd7\x1d\xc9\xba\x17\x4e\x6d\x2d\x31\x24\x9e\x5d\xc6\x98\xb4\xcc\x33\x6b\x51\xe5\xc2\x22\x37\x4b\x40\x01\x4b\x94\x4c\x43\x04\x7f\x0c\x89\x81\xb6\x2e\x07\x2a\xc5\xea\x72\x9f\xda\xd2\xdd\x70\xde\x6f\xc5\x49\xf5\x46\x52\xe9\x5f\x9d\x51\x74\x80\xd7\x19\xc1\x7d\xde\x2c\xe4\x19\x01\x49\x12\x22\x6e\x93\x55\x34\x15\x73\xc8\xc6\x10\xbd\x75\x65\xe1\xe2\x6b\xbb\x59\x65\xdd\x71\x65\xf5\xde\xc3\x98\xd6\x1c\x22\xee\x03\xc1\xe6\xbb\xcc\x55\xb4\xfc\x0e\xe1\x8e\xf2\x9e\xaa\xd5\xf0\x91\x31\x26\xd4\x42\xb2\xfe\x9e\x49\x50\x45\x6b\xb7\xce\x83\x2a\x2a\xb8\x29\x19\x7e\xbb\xa4\xa8\xd2\x4e\x86\x49\x50\xcc\x22\x1e\x01\xae\xa4\x76\x89\xba\xa2\x91\x95\x32\x48\xe2\xf1\x24\xab\xe8\x39\xab\xe7\x6f\x41\x56\x4a\x48\xe6\x53\xb5\x61\x2b\xa0\x5a\x72\x20\x7d\xf4\xec\x20\x8b\x86\xf5\xfc\x35\x68\xce\xd2\x10\xe5\xd9\x67\x8c\x7a\x13\x0c\xc5\xb3\xb1\x16\x36\x03\x4b\x59\x1b\xca\xc0\x86\xe2\x9b\x08\xce\xc8\x3a\x76\x22\x3f\xd1\x0e\xf0\x78\xc0\x5c\x7c\x2f\xc5\x3e\x3c\x39\xd2\xa2\x25\x54\x4b\x5c\xb6\x78\xf9\x74\xe4\x15\xd3\xd9\x01\x69\xaa\xdd\x63\x9d\x86\xaa\x97\x1d\x51\x69\x90\xa9\x3f\xd2\x80\x38\xf2\xad\x6f\xb1\x33\x48\xde\x09\x67\xb3\xf4\x92\x05\xe5\xaa\xc5\x8a\xf6\xaa\xc8\xc2\x01\x3f\xb2\x1b\xc3\xaf\x6f\xdc\x42\xcd\xf1\x62\x39\xb8\xa4\xd0\x93\x69\xb8\x3c\x43\x77\xbb\xfc\x5b\x8e\xc1\xbd\xcf\x14\x5c\xfc\x8b\x94\x27\x6a\xc4\xb2\x8a\x02\x6c\xe3\x2e\xc7\xc1\x0f\x6a\xef\x2c\x9c\xac\xe2\xaf\x40\x57\x0e\x69\x8f\xe4\xab\x8a\x82\x52\xfb\xe0\x11\xb6\xf0\xe3\xdf\x74\x26\x32\x5e\x9e\x4c\x8b\xa4\x88\xe6\x69\x58\x3c\x7e\xfc\xf8\xb1\xc6\xd3\xde\xfc\x63\x9c\xbb\xd6\xef\x28\x03\x19\xb5\xfc\x37\x86\x83\x17\xee\x78\x4b\x6b\x0f\xfe\x27\x0f\xfe\x27\x7f\x0c\xff\x13\xe6\x78\x82\xe1\x79\x66\xb8\xea\x74\x36\xdf\x44\x92\x9a\x2a\x1f\x97\x6a\x57\x94\x47\xdd\x2e\xc9\x8a\x17\x16\x98\xd5\xf1\xba\x38\x2f\xf5\xee\x63\x86\x8c\x93\xd1\x08\x16\x30\x43\x00\x66\x67\x25\x29\x74\x5a\xe4\xe7\x25\x73\x7f\x53\x22\xf1\x9e\x27\x59\x9c\x9f\x13\x8b\x90\x92\xc3\x05\x3c\x79\xc2\xbe\x74\xfe\xfc\xf1\xc3\x7b\x84\x66\x2c\xdc\xb3\x94\xb5\xfa\x7b\xb0\xe1\x86\x27\x02\x93\xa5\x1d\x49\xc6\x59\x8e\x05\x46\x9a\x64\x10\x63\x94\xf1\xdc\xd4\x46\x18\x40\xab\x66\x41\x88\x8b\x69\x8a\x7b\xca\x96\xc9\x66\xbb\x6e\x63\x8a\x62\x83\x29\xfc\xe7\xf7\xfb\x03\xad\xda\x49\x31\x68\xb6\xbd\xa5\x4d\x7d\x05\xb7\xf8\x89\x13\xdb\x76\xd2\x21\x9a\x1c\xde\xaa\x90\x48\xb8\x34\x6c\x3e\xc6\x5a\xd3\x3b\x30\x1a\x5a\x3d\xaa\x72\x31\xc9\x4b\x14\x00\x94\x4c\x61\x3e\x47\x01\xae\xb8\x08\xc8\x21\xc1\x79\x5e\xc4\x01\x98\xc0\x30\x86\x85\xe9\xa2\x83\x0b\x81\x0d\x40\xfe\x5c\x5d\x81\x26\x9b\x41\x69\x1e\x85\x29\x7e\x39\x7c\xf1\x6c\xed\x59\x53\x33\x25\xb3\x16\xf0\xb2\xcb\x7e\x5d\x5d\x81\x9e\x06\x82\xdb\x06\x1b\x04\x05\xed\x3d\xc7\x06\x6c\x08\xc4\xb4\xef\x0c\x47\x8c\x10\xfd\xb5\x94\xf6\x37\x2b\xe0\x8c\xa4\x8d\x84\xe7\x06\x37\x78\x94\x3f\x56\x60\xdf\xd8\xe6\x31\xd5\xef\x4d\x9e\xa7\x30\xcc\xae\xc9\x81\x3f\x66\x7e\xae\x76\xc9\xc1\x53\xc2\xb9\x2a\x3b\x66\xbd\x6d\x67\x24\x2f\x75\x10\x15\xe5\x51\x47\x48\x3f\x85\xc6\x4d\xe9\x7a\x23\xc3\x47\x33\x70\x19\x60\x00\x48\x5d\x91\x90\xe5\xfd\xfe\x40\x4f\x1e\x4d\x55\x47\x65\x4c\x95\x21\x36\xae\x81\x78\x2b\xd5\xfa\xdb\x72\x39\x91\xd3\x56\xf2\x19\xcc\x5a\xcd\x4f\x7b\x07\x87\x3c\x27\x2f\xe5\x59\x8a\xb4\xaa\xb2\x49\x2e\x7a\xf2\x44\x67\x1d\xf3\x74\x05\x8b\x6a\xb0\x01\x9a\x6f\xc2\x32\x89\x40\x13\xac\x10\x94\xde\xcc\xb1\x24\x53\xaa\x59\x01\xcd\xa1\x38\xc3\x16\x75\x75\x50\xce\xee\x0e\x37\x4f\xc3\x12\x3e\x5f\x6b\x3a\x68\x23\xa3\xd6\xbf\x27\x4c\xd9\x6a\x6e\x92\xe5\x21\xf9\x35\xa4\x1b\x55\x82\x83\x12\x7a\xd7\x5f\x10\x6f\x0a\x60\x86\x9e\x1e\x5e\xce\x48\x54\x17\xbc\x1d\x4a\x22\x52\x4d\xf7\x97\x52\xb5\x3f\xf3\x20\xca\xd6\xac\xd5\x67\x8a\x7d\x51\x9a\x7e\x30\x03\x7f\x7a\x10\xa2\xc0\x24\x2d\x39\x17\x10\x46\x00\x0d\x3b\x2f\x88\x72\x78\xcb\xd8\x6f\x89\x29\x4a\x13\x32\x5d\x66\x91\x61\x5b\x31\x67\x26\x96\x86\xe6\x7c\xe4\x67\xb4\x33\xc3\x67\xc4\x4c\xf4\x4d\x4f\x3e\xeb\x4c\xb7\xd2\x4c\x00\xcb\x8d\xbd\xae\x69\xc6\xe7\x86\x3e\x47\x59\x26\x7b\x31\x07\xf5\x43\x2d\x49\xf8\x2c\x6e\x91\x3b\x6d\x34\x29\x57\x32\xba\x14\x6d\x29\x8c\xc3\x4f\xb2\xcc\xc0\xfa\x2e\xc7\x9d\xad\x3c\xcb\x20\xbb\x77\xc6\x67\x92\xfb\x48\x50\x9c\xd4\x73\x6c\x78\x0a\x9c\x43\x78\x81\xbc\x88\xb3\x32\xca\x45\x3c\x76\x63\xc7\x46\x77\x11\xaa\xfb\xac\xbd\x96\x0b\x81\xa5\xae\x9c\x54\x9a\x3b\x09\x6f\x85\x35\x99\x6b\x53\x93\xdc\x0b\x38\x8c\x7d\x7e\x67\xa5\x4f\x02\xa8\x48\xc6\x63\x58\xd0\xac\x6f\x98\x9b\x88\x76\x2a\xa2\x35\x63\x7a\xd5\x65\x44\x82\x91\x8b\x1b\xf5\x14\xf3\x75\xf9\x92\x78\xa9\xad\x5b\x12\x38\x23\xd1\xf7\x4b\x14\x22\x18\x4d\xc2\x6c\xec\xb3\x42\xf1\x8b\x4a\x7c\xc0\xc2\xf8\x92\x5c\x11\x22\x4e\x1a\x6b\x58\x2a\x9b\x4b\xc7\x63\x3b\x5d\x7c\x0d\xee\xd3\x81\xf9\xa1\xad\x96\xfb\xcf\xe6\xce\x9a\x1c\xea\xe7\x52\xa0\x1e\x10\xdf\x80\x57\x01\x30\xf2\x3a\x99\xe7\xc9\x16\x26\x76\xc2\x1a\x39\x24\x72\xf1\x75\x8f\x84\x5e\x79\xd9\x91\x53\xff\x90\x96\x6c\xa9\x8b\xb6\x22\x50\xee\x57\x2a\x99\x68\x55\x09\xa6\x1a\x1e\x85\xd2\xe7\x25\xbd\xc4\xf3\x8a\x46\x4b\x20\x93\x1a\xcf\x9a\x05\x73\x3a\x29\x59\xbb\xd0\x5e\x18\x84\x22\xc7\xed\xf7\xa6\x42\x87\xb7\xa6\x59\x13\x81\x51\x98\xa4\x30\xee\x80\x3d\x34\x81\xc5\x79\x82\xb7\xb5\xa1\x48\x10\x59\x3d\x83\x95\xf6\x3d\xe3\xa8\x8f\x81\x30\xaa\xb5\xb4\xa0\x26\xf1\x10\xfc\x20\xfe\xa9\xf9\xed\xb0\x76\x50\xcc\xa2\x21\x68\x0e\x3a\xbd\xa6\xfa\x85\x1b\xda\x9b\x19\x44\x9f\xd3\xa4\x44\x30\x4b\xb2\xb1\x06\x22\x8c\xe4\x27\x8e\x55\x5d\x5a\x69\x0b\x99\x17\xd3\x3d\x6d\xb8\x0b\x34\x26\x8a\x7b\x40\x2d\x5b\x96\x4a\x2e\x69\xc7\xd2\x4c\x51\x78\x1f\xd5\x18\xbe\x78\x16\x34\xf4\x8d\x5a\x63\xd8\x7f\x71\x7d\x12\xac\x3e\x7b\x30\x4b\x3d\x98\xa5\xfe\x18\x66\x29\x21\xba\x28\xfb\x9e\x86\xf7\x76\x25\x4a\xb8\x9e\xab\x16\x8d\xd3\x64\x4c\xef\x12\xa8\xe1\xb2\xe9\xa9\x61\xfc\x01\x8e\xf4\x15\x4a\xe4\x1c\xbe\xd4\xee\x2f\x6b\x6b\x3e\x85\xe1\xa2\xe2\x7c\x82\x7b\xd5\xd2\x9d\x3e\x5f\xd2\x0a\xc0\x77\x60\x60\x5f\x82\x26\xbe\xc7\x78\xdb\x66\x5e\x0a\xd7\xe4\xd1\x02\x4d\xf1\x13\x33\x0b\x84\x19\xd8\x79\xb3\xb9\xcb\x78\x22\x06\x3f\x7c\x0f\xa2\x7c\x3a\x9b\xb3\x54\x5e\xa7\x97\x60\x9a\x9f\x25\xd9\x58\x49\x5c\xb9\x06\xa2\x49\x58\x90\x85\x88\xfa\x54\xc4\xd4\x1d\x93\x5f\xda\xe1\xd0\x29\xa4\xd7\xbb\x50\xce\x1b\xa5\x74\x2c\x41\x6b\x13\x6c\x80\x7e\x2f\x00\x6f\xf0\xdf\x7e\x00\x3a\x9d\x4e\x00\xfe\x01\x6c\x80\xd5\x67\x6d\xbc\xf3\x05\xe5\x0c\x46\xc9\x28\xa1\x73\x6f\xe7\x60\xaf\xbf\xfa\xbc\xff\xdc\xe7\xbe\x9a\x94\x39\xf9\xce\xfa\x64\x6a\xa8\xfc\xac\x9e\xc4\x35\xc0\x88\xe1\x0e\x1b\xeb\x61\xa9\xfb\x89\x30\xb5\x31\x56\x41\x95\x60\x41\x7a\x7b\x1a\x03\xe0\x46\xf4\x71\xc7\x7d\x6d\x6e\x36\x3b\x98\x68\x5b\x79\x0c\x37\x51\xab\xa7\x9d\xda\xe0\x7e\x37\xff\xc1\x02\xe0\x7b\x5e\x1a\x8d\x81\xb0\x3d\xca\x8f\x66\x33\x1e\x34\x62\xdd\x01\x50\xce\x4f\x4b\x54\xb4\xd6\xda\x3c\xc2\x02\x7b\xd1\x0b\xd6\xac\x53\x69\xfa\x7d\x96\x26\xa8\xd5\x6c\xb6\xf5\xa0\x1d\x59\x5b\xb7\x27\x44\x79\x8c\xbb\x99\xb9\x3b\xc1\xb2\xf6\x60\x98\x57\x1b\x60\x13\xeb\xc1\xe4\xe1\xe5\x06\xf8\x87\xb6\x91\x84\xc6\x31\xf2\x6c\xe0\xb5\xad\x39\x8b\x2b\x1e\x43\xf0\x14\x6c\x82\x15\xd0\xef\x29\x5a\xa3\x9d\xaa\x85\x67\xc7\x76\xa8\x96\xed\xce\x2f\x79\x92\xb5\x9a\x76\x84\x0c\xd5\x5f\x3a\x9a\x93\x50\xec\x64\xf0\x3f\xee\xbd\xc5\xd3\xa0\xdf\xe3\x12\x8f\xf9\x14\x93\x79\xe2\xe0\xcd\xef\x7b\xcf\xd7\x7c\xac\x39\xcd\xe3\x1f\xbe\xef\xf7\xaa\x58\xd2\xe6\x44\x3d\x10\xbe\xe4\x3b\x56\xd9\x02\x7e\x2b\xe0\x34\x4c\x32\x6a\x07\xc5\x5f\x55\x9d\x87\x85\x6b\x33\x45\x0f\x2b\x20\x6f\x69\x0c\xda\x46\x20\x3a\x22\x10\x39\x98\xf0\xd9\xff\x41\xd3\x97\x64\xb3\x64\xe3\xb1\x93\x21\x1a\xe3\x2e\x00\xfd\x5e\x1b\xfc\x3b\x4c\xd3\x15\xab\x16\x1a\xe6\x8e\x5d\xcb\xf1\xed\x7a\x45\x7d\xa2\xb4\xac\xd3\x3b\xaa\x8b\x6f\x80\xd2\xcb\x39\x64\xfe\xb0\x0b\x51\xc4\x11\x86\xbc\x60\xf7\xc4\x98\xb0\x36\x47\xd7\x3f\x8c\x72\xb4\x58\xaa\x06\xdf\x50\xb1\x1b\x21\x72\xf2\x56\x77\xc4\xca\xab\xbe\xa8\x17\x90\xe4\xd9\x9b\x4f\xf9\xdd\x48\x0f\x83\x62\xd8\x4d\x0d\xc2\xea\x5d\x68\x7c\xe6\x3b\x08\xdc\xc1\x6b\x21\x50\x5d\x06\x5d\x0c\xd1\x51\x5a\xa8\x8e\xcd\x43\x0c\x97\xe5\x1b\x1e\x7b\x46\xb9\x07\xc6\x32\x65\x83\xfe\x73\x4d\x70\x9e\x86\x25\x5c\x7d\x0e\x36\x48\x29\x69\xb5\x5c\x7d\x6e\x38\xf7\xc4\x31\xa4\xc6\x75\xb2\x5e\xb7\x68\xb1\x00\xf4\x9f\x99\x37\x2f\x04\xbe\x6f\x4e\xc3\xac\x45\x0b\xea\x42\xb7\x42\x88\xb0\x00\x63\x8a\xc0\x78\x83\xc9\x82\x72\x6d\x8d\xc5\xc3\x0e\x48\x5c\xee\xe2\x92\x4b\x12\x6a\xeb\x14\xb7\x12\x3e\xd1\x04\x4a\x59\x8e\x98\xb6\xf9\x32\x79\xd5\x18\x13\x55\x8b\xc6\xb6\x1b\x49\xa2\x97\x93\x90\xaa\xa1\x64\x25\xbe\x88\xd2\x79\x99\x9c\x89\x7c\xce\xc9\x69\x92\x26\x48\x68\x6e\xa7\x61\xf6\xa5\x7b\x5a\x84\x59\x34\x01\x25\x2c\xce\x92\x88\x2f\xd5\x21\x0d\x15\xde\x78\xd9\x4d\x5e\x75\x5c\x7c\x27\xf2\x2f\x95\x7c\x7d\x1c\xc1\x02\x2f\x90\x61\x3a\xce\x8b\x04\x4d\xa6\x20\x86\x65\x54\x24\xa7\x54\x2c\x32\xe5\x0e\x66\x9d\xf3\xe4\x4b\x32\x83\x71\x12\x12\x0d\x0f\x3f\x75\x77\x32\x04\x8b\x2c\xa4\xd7\xd2\x3e\xbf\x09\xb3\x2f\x9f\x59\xac\xf2\xcf\x74\xc4\xff\x8b\x1f\x59\x6f\xb3\xf1\x67\xdc\xcd\xcf\xe4\x66\xe7\xe7\x38\x19\x27\xde\x6b\x73\x7c\xd8\x7c\x5c\x7c\xca\x57\x7f\x3e\x2a\x3c\x22\x1c\xca\x3d\x0a\xc1\x12\xfc\xfd\xc6\x9c\xeb\xa7\x96\x58\x66\x63\xbe\x45\x57\xd2\xe6\x9f\xb7\x9b\xeb\x8f\x3c\x52\x9b\xc9\xf9\x96\xa1\x67\xb4\xd4\x2a\x56\x40\xb3\x47\x94\x40\xd2\x92\xee\xfe\x86\x89\xf5\x16\xd3\x0a\x6c\x80\x16\x55\x15\x5b\x3f\xbc\x00\x4f\x65\x23\x6d\x7e\xf1\xea\xe9\xc0\xd2\x0b\x44\xdc\x27\xbd\x39\xa5\x56\xd6\xe8\x12\x56\x48\x26\xa4\x14\x3a\x92\xc9\x30\x4e\xf0\x76\x28\xc9\x4a\x94\xa0\x39\xe2\x81\xfb\x93\x18\x66\x08\x2f\xb0\xbe\x0c\x33\xb4\xb6\x9d\x2c\x4e\x0a\x68\x3b\x27\xe9\xf7\x1e\xcb\x80\x6b\xf2\xe2\xe2\x23\xb9\xdf\xda\x50\x5a\x6d\x90\x66\x1b\xb2\xdd\xc6\x4d\x65\x9d\x8e\x99\x33\x96\xbf\xe9\xda\xa9\xcb\x9c\xe6\xf6\xe1\x7b\x3c\x5e\xfc\xae\xa1\x4a\x1b\xe5\xad\xc0\xd4\x3f\x08\x87\x93\xbc\x6a\xd5\xe0\x57\xa5\xe9\x10\xd0\x55\x0e\x6f\x7b\x69\x5c\x2b\x65\x35\xf1\xea\xdc\x24\xea\x57\xa5\xae\xad\xad\x9d\x0e\xcb\x13\xb7\x38\xd1\xeb\xd2\xb4\x65\x4b\xe1\xde\x51\x62\x8c\x2d\xd0\x7c\x12\x35\x6a\x59\x22\x59\x54\x09\x66\x20\x82\x95\x2d\x75\x40\x23\xa8\x45\xba\xb5\xf0\x16\xb9\x49\x1b\x99\xc8\xc8\xd9\xf3\x00\xe4\x15\xa6\x35\xd2\x7f\xd5\xa4\x66\x53\xc2\xe2\xa9\xee\x5f\xff\xbc\x7d\xdc\x7b\xfa\xc3\xc9\xd7\xc1\x75\x6b\xfb\xf0\x3d\xfe\xbd\xf9\xf4\x1f\x4e\xbe\xf6\x57\xaf\xaf\xc4\xc3\x6a\x2f\x58\xed\x5f\xb7\xff\x6d\xb7\x83\x88\x6d\x5c\x28\x24\xc6\xdd\x6c\x9f\x38\x52\x0a\xd0\x8b\xda\xfd\x5b\x10\x95\x5d\xd7\x27\x86\x97\x4a\xd2\xbe\xd5\x27\xfd\xdd\xd2\xf6\xad\x3d\x6f\x2d\xe2\xca\x6e\xab\x57\x34\x57\xd7\xc0\xd5\x95\xef\xdb\xb3\x3b\x20\xcc\x08\x24\xd9\x02\xd2\x58\x12\xf1\x6e\x89\xe3\x14\x6b\x35\xc9\x33\xe8\x2d\xed\xa1\x4e\xd5\x70\x4c\x8b\x72\x3e\xe5\xc0\x47\x25\xdb\xa4\x4d\xf3\xf8\xe9\x0f\xdf\x3f\xed\xf7\x04\x08\xb1\x3b\x10\x7c\xa3\x3c\x05\xad\x9d\x83\xbd\xee\xce\xf6\x16\xc0\xbb\xb3\xe1\xa0\xd7\x5b\x6d\xfb\x56\x13\xa3\x09\xdb\x22\x60\x40\x38\xc9\xc3\x61\x6a\x13\x87\xed\xcc\x07\x01\x18\x2c\xef\xbd\xaf\x2e\x0b\xd6\x72\x09\xe0\xb8\x03\xfe\xbc\xbf\xfd\xa3\x8f\x51\x64\x61\x7f\x9f\x2b\x5b\x70\x33\x88\x52\x62\x31\x11\x24\x3b\xb5\x48\x64\x5d\x8b\x30\xdf\x07\x60\xad\x0d\x86\xa0\xd9\x5c\x9a\x3a\x51\x9a\x90\xcb\xc6\x02\x6d\x62\x87\x4c\x32\x57\xcf\x31\xad\x7e\xdc\xfc\x69\xef\xdd\x5f\xf6\xf6\xff\xde\xc7\x21\xa4\xbe\x0a\xfe\x30\xdb\xab\x64\x14\x0a\x7c\x07\x14\xea\xf7\x6f\x47\x1f\x36\x9b\xab\xf7\x87\xee\xcd\x9f\xdd\x79\x67\x65\x4e\x02\x84\x8e\xbd\xa0\x7e\x63\x9f\x75\xdf\x15\x44\x54\xdb\xf6\xd9\x24\x59\x53\x0c\x12\xde\xed\x24\xdf\xff\x29\xfb\x44\xc5\xd2\x40\xf7\x88\xfa\x8e\xb2\xff\xbc\x1d\x80\x41\xcf\x6b\x9f\x30\xc7\xc0\xe8\x31\xaf\xa7\xae\x6c\x58\x10\x51\x72\x87\x80\xf0\x63\x23\xd5\x3c\xdd\x18\xea\x8f\xd7\x27\xc1\xea\xf3\x87\x83\xa2\x87\x83\xa2\x3f\xc6\x41\x11\x3f\x21\x9a\x45\xf5\xdc\x97\xef\xc8\xd1\xb8\x21\x73\xc0\x36\x94\xe3\xa1\x3b\x8b\x98\x27\xab\xdc\x99\x45\x4e\x27\xd7\x59\x88\x26\x01\xc8\xa0\x71\x85\xe6\x33\x31\x36\x39\xe2\x1e\x70\xf7\x8d\x2d\x25\x3f\xbf\x0c\xda\xc3\xbc\x01\x89\x07\x21\xfe\xb3\xae\x5d\xee\x8d\x84\x27\x03\x11\xac\x88\xbf\x68\x7d\xc5\xb0\x43\x59\x5a\x89\xe2\x6d\x14\xec\xe4\x59\xab\x49\xfa\xd7\x54\x93\xb9\xb7\xb5\xab\x27\x65\x8e\x85\x1e\xbd\xaa\xbe\xf3\x69\x0b\x48\x0f\x0a\x7a\x81\xbd\x19\x00\xd5\x21\xef\x33\x15\x9f\xcc\xdd\xa3\xe5\x8a\x25\xee\xc4\x22\x8b\x55\x1c\x14\x14\x2a\x2b\x64\xbf\xbb\x5d\xf0\x61\xe7\xe0\x70\x7b\x97\x4c\xb9\xad\xbd\xdd\xdd\xed\xad\xc3\x9d\xbd\x5d\xb0\xbf\x7d\xf0\x69\x6f\xf7\x60\xfb\xa0\xa2\xe9\x38\x44\xa1\xda\x36\x7e\xd6\x22\x77\x7d\xc7\x1c\xa8\xa7\xe1\x45\x94\x4f\x67\x29\xbc\x48\xd0\xe5\x10\x3c\x17\x8c\xa7\xa0\x49\x2c\xe4\xc2\x4d\x07\x57\x25\x57\xaf\xb6\x23\xa9\x1d\x73\xc5\xf9\xaa\xba\xeb\x90\x2d\x74\xec\x72\x34\xea\x76\x89\xde\x40\xc4\x51\x0c\xce\x27\x49\x34\x01\xd3\x10\x45\x13\xa6\x8c\xd3\x15\x0c\xcb\xbf\x58\x29\xe5\xc8\xeb\x44\x1a\x35\x7d\x8e\xd8\xd1\xa7\x85\x23\x71\xb9\xd1\x41\x49\xa5\x9f\xdd\x3c\x7c\x4c\x6e\x47\x27\xf1\x49\xdb\x28\x02\x68\xaf\xd8\xe7\x75\xed\xeb\xb5\xe1\x17\x65\x9d\x2c\xb1\xc2\x0c\x47\xbd\xf8\xb5\x41\xa3\x51\x52\x40\x2d\x2a\xcd\xa3\xff\x9f\xbd\x77\xef\x6b\x1b\x49\x1a\x46\xff\x3e\xfb\x29\x3a\x39\x67\x07\x33\x18\xf0\x0d\x43\x9c\x61\xf7\x21\x86\x24\x6c\x42\xc8\x0b\x64\x66\xf6\xe5\x61\xf2\x93\xad\x36\x28\xb1\x25\x3f\x92\xcc\x65\x27\xbc\xdf\xe7\x7c\x8e\xf3\xc5\xce\xaf\xab\xef\x37\x59\x32\x90\x4c\x66\xc8\xec\x26\x96\xd4\x5d\x5d\xdd\x5d\x5d\x5d\x5d\x5d\x97\x52\x78\x13\x94\x8d\x36\x0b\x8a\xd6\xc8\xec\x38\x6c\xa9\xc8\x9f\x10\x8f\x71\x8e\x8b\x6a\xeb\xe8\xdb\xa3\xe0\x8d\xf0\xf1\x0b\xd9\xc5\x80\xca\xd8\x8c\x53\xe0\xa0\x39\xa3\x36\x86\x31\x8d\x19\x87\x12\x9a\xc1\x22\xca\xd9\x6e\x21\x44\x49\x9d\x48\xd9\x37\x43\x6f\x64\x39\xe6\x2b\x2c\x4f\x91\xa8\x74\x50\x6a\x58\x75\x7d\x15\x99\x5c\x50\xbd\x0b\xa3\x44\xfb\x33\x4d\x41\x43\x13\x1c\xc8\x65\xbd\xbb\xb7\xda\x7f\xfd\xe1\xdd\x9b\xbd\x23\x05\x52\x88\x87\x17\xb3\xf8\x33\x0e\x99\x47\x9e\x44\x16\xfe\x88\x28\xfe\xeb\xff\x7d\x7b\xfa\xdf\xf1\x7f\xa7\x67\xff\xfc\xef\xdf\xd7\xcf\xeb\x4b\xb7\x5f\x56\x57\xbf\xfc\xbe\xb4\x0c\x09\x1a\x7e\xf7\xd4\xf8\xef\x33\x5e\xe7\x94\xd5\x3a\x23\xd5\x4e\x79\xbd\xb3\x53\x5f\x4d\xb3\x9e\x56\xcd\x5b\x4b\xb6\xa7\xb4\x26\x1a\xd3\x6a\xb1\x4b\x62\x52\x60\x49\xb2\x53\x6d\x34\xec\xd5\x0b\x13\xa1\xf0\x97\xf5\x75\xb8\x55\xc7\x2c\x70\x13\xc4\x85\x02\x00\x7f\xb3\x96\x08\xf9\xd6\x27\x9f\xd4\x95\xcc\xa2\xae\x19\x25\xd0\x0a\xa2\xf1\x10\x34\xb1\x5f\xd8\x49\x18\x9c\xcc\x34\x98\x74\x99\x4b\x02\xde\x4a\x1d\x66\xf4\x45\x6d\xbe\x94\xba\x26\x22\xdb\x26\x22\xd4\x15\x39\x27\x12\x34\xb7\x65\x84\xb0\xb3\xf1\x10\x8f\x21\xfb\x11\x77\xbf\x57\x6a\x0c\xc7\x38\x48\xb9\xfd\xa2\xd1\x02\x7b\xad\x2d\x79\x77\x11\x88\x15\x94\x73\x30\xae\xcd\x0d\x15\x6c\x70\x6c\xa8\x8a\x4c\x96\xd9\x18\xc9\xd2\xb7\x75\xd4\x6c\x34\x1a\xe8\x47\x7a\xa9\x66\xad\x32\x67\xb8\x21\xf0\x8a\x86\xd1\xe0\xe3\x43\x98\x49\x86\x19\x5d\xd0\x8c\x73\xc2\x6b\xba\xea\xe0\xd8\xf3\x63\x10\x03\x64\x5e\x54\x8c\x91\x15\x74\x29\x53\x58\x9b\xce\xb2\x0b\xcb\xc0\xdb\xba\x2f\x51\xab\x18\x21\xec\x04\xf7\x7c\xc5\x76\xcf\x20\x0c\x21\x30\x9b\xb0\x94\x4e\x34\x6e\x2a\xe5\x31\xaa\x7b\x67\xec\x8a\x6e\xb8\xfc\x1c\xc1\xce\xf2\x11\x4d\x12\xc4\x38\x20\xec\xc6\xfc\x36\x4f\xbc\xdd\x0f\x69\xad\x20\x4d\xa3\x4b\x6c\xf1\xe3\x20\x14\x53\xda\xd7\x76\xac\x62\xde\xeb\xa8\xa6\x65\x06\x2a\x36\xd0\x8e\xe8\x55\xec\x0d\xdb\x8e\xd1\x97\x2f\xfc\xe9\xb4\x71\xa6\x6c\xb0\x60\xf0\x40\x11\x95\xe5\xd9\x0b\xbd\x0e\x7d\xa9\x8b\x79\xce\xcd\x0f\x6d\x0b\x9c\x8a\xc5\x61\x52\x78\x4d\x34\xce\xe1\x23\xd7\xbe\xc8\x97\x9c\xba\xa0\xd1\xd5\x05\xe6\x21\x6c\x42\x2a\xc8\xc3\x79\x0e\xae\x39\x88\x90\x10\x9a\x33\xc1\x16\x40\x99\xd1\xb7\xed\xa2\x15\x55\xc2\x28\x49\x6b\x64\xe0\x3e\xe3\x1b\x7a\x16\x75\xf5\x4e\xb7\x67\xaf\xb9\x0b\xad\x5d\x04\xd9\xe1\x55\xfc\x1e\x32\x56\xe6\x37\x90\x05\x59\x63\x20\x9e\x91\xfb\x8c\x6f\xce\xfc\xb6\xcf\x4b\x49\x8c\xf6\xdf\xf7\x97\x96\x35\xee\xc1\xc4\x96\x02\x88\x0e\xbb\x1e\xcf\x4a\xeb\xab\x31\x80\x59\xb6\x12\xa4\x9c\x59\xa2\x0c\x65\x79\x44\x73\xcf\x45\xa1\xb9\x24\x6c\x83\xe9\xa2\xb9\x70\x9b\x37\xd7\x8a\x0e\x61\x52\xc2\x20\x7b\x51\x8a\x19\x66\x75\x4a\x32\x3a\x9e\xe7\x09\x73\xb7\xa4\x79\x1d\x3f\x9a\x87\x88\xab\x34\xca\x21\xca\x97\xc1\xf3\x94\x22\xc6\xd9\xec\xa3\xeb\x70\xc6\x43\xfb\x3e\xf1\xc1\x37\x74\x5b\xce\xa1\x28\x72\x1a\xfa\x9b\x4e\x6a\x46\x0b\xf8\xd8\xf0\xcd\x93\x1b\xf8\x73\x6d\xaf\xa8\x34\x60\x74\xc8\xe6\x8f\x98\x7d\x34\x2b\x71\x9c\x65\xd2\xa0\x12\x96\xc1\xd5\xa7\x02\x27\x82\x45\x04\x12\xb1\x00\x54\x79\x44\xed\x46\xf9\xfd\x5a\xd9\x83\x0d\x63\x51\xbe\xb8\xcc\x73\x90\x15\x3c\xee\xdf\xc9\x0c\xdc\x11\xa4\x97\xee\xd3\xa5\x15\x93\x4d\xaf\x2c\x3d\x45\x99\xea\xc1\xb0\xa6\x3a\x34\x48\x76\x09\xee\xa9\x49\x8e\xb2\xd9\x74\x9a\xa4\x39\x28\xf5\xe8\x5d\xfb\xfb\x3e\x12\xaa\x1c\x33\x29\xd6\x7c\x82\xac\xe4\x3c\xb4\xc8\x92\x9c\x4f\x5f\x95\xa8\xcb\xa3\x2c\x00\x8a\x9a\xeb\x92\x92\xf3\x90\xc5\xe6\xde\x3c\x37\xfa\x9a\xad\x60\x96\x23\xea\xc8\xed\xe2\x89\x9e\x71\x7b\x56\x6f\x6f\x3e\xea\x9a\x1f\x75\xcd\x7f\x29\x5d\x33\xf3\x40\xba\xb7\x30\x19\x3b\x41\x9a\xc4\xe8\xcd\x6c\x12\x5c\x46\x19\xfa\x29\x20\x8f\xff\xf5\x99\x3e\xae\x4d\x70\xa1\x2e\x7a\x7d\x1d\xed\xc7\x51\x1e\x05\xe3\xe8\x3f\x18\xfd\x8b\x62\x46\x08\x38\x40\x19\xd8\xef\x71\xd3\x2b\x18\x00\xb2\x8c\xb5\x34\x27\xd4\xee\x8f\xec\x6c\xbc\xaa\x4c\x5e\xc6\xd2\x8f\xee\x87\x3d\xd4\x70\xcb\xc1\xee\xdb\x46\x6a\xd3\x43\x06\xc9\x0c\x85\x5f\x68\x5c\xe4\x0d\x81\x2f\x9d\x4d\x79\xe6\xd3\x11\x9f\x02\x68\x55\x1a\x7b\x99\x35\x59\xf0\x7c\xea\xf4\x55\x57\xb4\x55\x23\x0e\x8a\x7f\xe2\xf1\xf2\x05\x04\x2b\xad\xb4\xec\x15\x69\xd7\xd9\x23\x4a\x27\x6c\x20\x3d\x21\xf4\x69\xbb\x75\xd6\xb0\x11\xb4\x9c\x7e\x94\x1b\x89\xa1\x11\xe7\x8d\x5b\x06\x5e\xc2\x38\xfd\xc9\x92\x96\x79\x34\x08\x2f\xc9\x59\x9b\xcf\x24\xda\xdf\x65\xdf\x38\x8e\x62\x8a\x57\x56\xfc\xf1\xeb\x7c\x0e\x77\x51\xd8\xb3\x01\xc9\xcf\xdc\x1b\x8f\xf5\x58\xbc\xe7\x2e\x78\xf4\x5f\x72\xac\x13\xce\x78\xd5\x42\xd2\x09\x93\x19\x3e\x2e\xe2\x08\xcc\x0d\xcb\x3c\x94\xc6\xcc\xa9\x74\xa5\x64\x89\xac\xb9\x8e\x30\x18\x46\x83\xc5\xc6\x35\x7c\xb0\x8c\xf6\x35\xfa\xe0\x20\xad\x9b\x62\x1a\x23\x4e\xd1\xaa\xb3\x62\xff\x14\x58\xac\xe1\x4b\x9c\xde\xd4\x78\xb6\x83\xe3\x28\x3e\x1f\xe3\x03\x3a\x31\xcb\xa8\x87\x9c\x1f\x24\x24\x41\x00\x02\x19\x77\x05\x36\xd5\x76\x76\xd7\x27\x4f\x38\x9d\x19\x59\x3e\xf8\x7b\x4a\xc7\xe6\x57\xfe\x91\xcf\xe2\xf6\xf6\x36\xa5\x34\xb3\x20\x0b\xb8\xc3\xcb\x47\x21\x2d\x4a\x6f\xbe\xad\xd2\xbc\x18\x93\xae\x8d\x88\xcd\xeb\xeb\x34\x29\xb4\x78\xc5\x72\xb7\xa8\xcc\x4b\x5d\xdb\x15\x73\xd2\x50\xb6\x37\x00\xc7\xd1\x92\xcc\xef\x05\x29\x5c\x9c\x04\x84\x75\xc9\xe0\x64\x14\x6e\x46\x19\x3c\x03\xc7\x05\xb1\x70\x19\xd4\x6b\x6c\xa9\xd5\x38\x97\x5b\x46\xa3\x08\x8f\x43\x87\x81\x07\x6b\x49\xc3\xdc\xc3\xdd\x54\x84\x0d\x16\x47\xd1\xb4\x48\x98\x7f\x30\x32\x50\x71\x82\xb2\xdd\x6b\x2d\x56\xca\xcb\xae\x71\x36\xca\x9f\x19\x3b\xf5\xdd\x68\x58\xd2\x2e\x83\x2c\x24\x5d\x22\xc8\x6e\x3d\x0a\xb2\x8f\x82\xec\x5f\x43\x90\x15\x0b\x1f\xc8\x97\x2e\xa6\x87\xf2\xaf\xbd\x3f\x93\x08\x5e\xec\x80\x2b\x69\x0b\x0c\xed\xe1\x64\xcc\xf2\xcb\x71\x0b\x7a\xf2\xa8\x9d\x9c\x59\x30\x50\xfe\x9d\x3c\x1a\xf6\x10\xc0\x37\x65\x09\x29\xb3\xe8\x51\xbe\x20\xc7\x8f\x9a\xf1\x59\x9a\xf2\xab\x1f\xb4\x3a\x34\x8f\x83\xab\x92\xf1\xc5\x50\x5b\x83\xfe\xe4\x20\x88\x83\x73\x9c\x6a\x99\x24\x38\xb3\xa3\xc3\xa3\xa9\x45\x78\xb0\x23\x59\x4d\x91\x38\x26\xc6\xa8\x59\x4d\xa4\x93\x0a\x5e\x1f\x21\x26\x3c\x2a\x8a\xf5\x08\xd9\x86\xe8\x3a\x08\x32\x1a\x28\x66\x5e\x5e\xac\x73\x0c\x11\x9c\x3d\x1b\xa2\x9d\x91\xc6\x0c\xd5\xcd\x71\xd0\xdb\xd7\x76\x34\x6b\xb8\x58\x9b\x05\x99\xaf\xb4\x00\xdd\x4a\x8a\x40\x41\x54\xc2\x76\x93\xc7\xf0\xcd\x88\x00\x26\xde\x54\x49\x65\x40\xc3\x58\xcb\xdb\x23\x96\x4e\x8c\x6d\xfe\x62\x04\xd6\xd0\x01\xd9\x32\x22\x9c\xd1\x64\x71\x74\xcf\xf7\x8c\xaa\x11\x1b\xbb\xf2\xe8\xf2\x0e\x7f\x79\x37\x1b\x8f\x65\x0c\xa0\x3a\x91\x89\xf1\x75\x24\xae\x37\x7d\x23\xfc\xc7\xcf\xc4\xf8\x7d\x27\x1d\xb4\x06\xbc\x64\xaa\x41\x33\x73\x9b\xc8\xa6\x67\x9e\x56\x8b\x33\xe8\x1d\xbd\xef\xb3\xf4\x57\x55\xd2\xe7\xc9\xd1\x65\xf1\xd6\x29\x1d\xc3\x4c\x89\x03\xbb\x67\x34\x69\x8d\x7d\x52\xc1\x3d\x98\x16\x09\x6b\xaf\x3d\xa3\xa6\x40\x9d\x33\x68\x4f\x1c\xbb\x80\x87\x1e\x7d\xe6\xc4\x0e\x08\x86\x9c\x2c\xc2\x14\xd7\x51\x14\x87\xf8\xba\x28\x25\x27\xfa\xa7\xfc\x4d\xd7\x0b\x54\x39\x23\x6c\x48\x79\x9c\x6b\x01\x54\x38\x41\x74\x9f\xe2\x97\xe5\x25\x27\x89\x06\xce\x2f\x77\xe6\x76\xa7\x17\xf0\xcc\x93\x23\x33\x1c\xc7\xcc\x65\x74\x6d\x6e\xbf\x10\xc7\x0b\x8e\x8a\xff\x74\x16\x10\xc0\xe6\x66\x62\x63\x23\xe6\x4a\x03\xc9\x7d\x1f\x39\x61\x7b\x4f\x83\xc5\x07\xc1\x40\x65\x1a\x95\x86\xaa\x6c\x5e\x47\x35\x4c\x3a\xdb\x0f\xed\xbc\x08\x0b\xa4\x5c\x60\xf2\x14\x14\x57\x96\x97\x5e\x14\xbe\x6a\x49\x1b\xc5\xf1\x4e\x9f\x48\x3b\xc7\x02\xe8\x03\xc5\x5b\x5d\xd5\x54\x94\x46\x81\xaf\x83\x39\x92\x14\x0f\x69\x4f\xc7\x5a\x97\x40\x07\x9f\xf4\x21\x24\x9f\x78\x57\x07\xb3\x68\x1c\xc2\x20\x8a\x4e\x92\xcf\x56\x34\x7a\xd8\xa6\x4e\x0e\x77\x0f\x9f\x3c\x79\x02\xc7\xa2\xa5\x0c\xcd\xce\xc7\x37\x6b\x2c\x9f\x2b\x39\x48\xcd\x32\xb2\xc7\xe7\x4a\x4b\xb1\x12\xfa\x9d\xfc\xe6\xb6\x5b\x6b\x4b\x5a\x24\x51\xf8\x24\xa2\x3d\x68\x01\xea\x92\xc1\xa7\x53\xf2\xf9\xb4\x71\x76\x46\x04\x51\xf5\xf1\xcb\x17\xc5\x4c\x58\x2f\x4a\x7f\x34\xa1\x0e\xe9\x8f\x37\x4a\xaa\x01\x5f\x2b\x5b\x3c\xe2\x62\xe4\xfc\x97\xed\xc2\x66\x43\x35\x79\xa6\x19\xe0\x62\x9f\xcf\xa4\x99\x02\x84\x1d\x81\xa4\xa2\xa1\x5a\xd2\x07\x3d\x39\x97\x2b\xb3\x92\xa2\x04\x81\x96\x74\x21\x5b\xde\x5c\xca\x8b\x3b\x2d\x0d\x9d\xb0\xfd\x34\x6c\x46\xcd\x26\x69\x59\xd6\x86\xca\x17\x85\x1d\xae\x66\xc1\xe5\xcc\x22\xa7\x23\xaa\x01\xf1\x23\xef\x08\x97\xc7\x7f\x90\xcf\xe5\x13\x35\xf0\x9c\xa1\x38\xf6\x59\xdf\x14\xea\xdc\xa6\xb3\x14\xc3\x45\xfa\xd1\xfb\xbe\x88\x6a\x47\x6d\xa9\x86\x41\x2c\x24\xeb\x28\x66\x3a\xae\xe2\x58\x7a\x66\xda\x1b\x99\x26\xf3\xd6\x4c\xc0\x6b\xc6\x5e\x95\x4a\x69\x15\x84\x87\x2d\xcf\x4f\x8b\xe3\xcd\x56\x53\x8d\x50\x39\xbd\xe8\x29\x6d\xd4\x39\xf6\x4f\x09\xab\x3b\x47\xb3\x76\x20\x0d\xb3\xca\xde\x20\x3f\x7b\x54\xbc\x3d\x2a\xde\xfe\x1a\x8a\x37\xa6\x71\x0b\x07\xf7\x74\x79\x5c\xa4\x78\x13\x6a\x32\x55\xf3\x46\x19\x9b\xa6\x4e\xdb\x7d\xa1\xf1\x1d\xd2\x45\x33\x62\x91\xa5\x12\x82\x71\x30\x5e\x6b\xa1\x4d\xb4\xec\xab\xec\x3d\x6d\x3b\xab\x39\x5c\x53\xd8\xcd\xa7\x25\xd1\x19\x12\x57\x8d\x80\x55\x36\x2d\x56\xc8\x52\x70\xd5\x5c\xf8\x79\x6f\x07\xa4\x10\x51\x94\xc4\x71\x3a\xcb\x85\x47\x67\x8c\xaf\xd8\xf0\x2a\x41\x4b\x89\x80\xd3\x43\x4b\xa2\x9c\x91\xb7\xab\x87\x96\xc2\xc1\x47\xd7\x57\x2e\xa6\xb6\x15\xfc\x94\x86\xcf\x71\xb9\x86\x45\x39\x67\xc3\xae\xaf\xbc\xe1\x96\xbb\xe1\xe9\x2c\x7f\x8d\xaf\xe7\x77\xf7\x35\xbe\xf6\xf5\x55\xff\x34\xbf\xa3\xf3\xdb\xa3\x85\x7c\x5d\x74\xb7\xe7\xe8\x1f\xdb\xd3\x4e\x65\x59\x3e\x31\x75\x39\xe0\x75\x36\x04\x75\x86\x1a\x2b\x7c\x36\x67\x0b\xdc\x7d\xa1\x6e\x7f\x94\xb2\x9e\xf6\xda\x64\xaf\xeb\x34\x1e\xf7\xba\xc7\xbd\xee\xaf\xb1\xd7\x89\x3d\x0a\xc8\x17\xe7\x17\x77\xba\x61\x62\x15\x1e\xd6\x7d\x57\x66\x02\x33\xb6\x4d\xf9\x41\xbd\xaf\x72\x5d\x6d\xf9\x6f\xb7\xe6\xec\xc7\xbc\x18\xb7\xbe\x37\x0a\x4e\xd9\x6b\xb5\xe8\xd0\xd7\xf6\x30\x89\x47\xd1\xb9\x5a\x54\x49\xc2\xa7\xd6\xe0\xf9\xdb\xd4\xa2\x57\xcc\xc9\x53\xbb\x86\x63\x2f\xd5\x72\x2f\x41\x25\x61\x0e\x14\xbc\x54\x8b\xed\x67\xc7\x37\xf1\x90\x6e\x5f\x6a\xc9\x8c\xbe\x55\x8b\x12\x16\x9f\x62\xb3\x20\x7b\xab\xc1\xa4\xa1\xd7\xd4\x52\xd1\x20\x88\xd5\x22\x34\x32\xad\x85\x1f\x7f\xad\x49\x40\x10\x8f\xb2\xdc\x9d\x8f\xb8\x8d\x60\x7e\xb6\xa0\x4c\x6d\x9c\x2d\xa3\x1f\x7e\x40\xec\xf7\x1a\xe8\x56\x0f\x47\xb5\xa5\xc6\xf5\x12\x0d\x92\xd4\x58\x46\xff\x44\x4f\x59\x7a\xcf\x73\x4c\x33\xcd\xbf\xb8\x79\x1d\x64\x17\x4f\x51\xcf\xf1\x85\xaa\xd2\x9f\xda\x72\x8a\x92\xcb\xf2\x65\x9a\x4c\x5e\x7c\x15\xcc\x97\x24\x7e\x4a\xe6\xc3\x17\x37\xd0\x3a\xe9\xc4\x4e\x1c\xee\x93\xaa\x6a\x16\x53\x67\x61\xda\x2f\x51\xdc\xee\x1f\x1f\x02\xa5\x6a\x3f\x99\xc5\x25\x6f\xe4\xee\xa7\x8f\x4e\x04\xe8\x6c\x19\x1d\xf4\x94\xa4\xbd\x34\x7a\x27\x50\xdf\xcb\x2f\xfe\x1c\x42\x36\x29\x57\x28\x64\x73\xa5\x03\x65\x5d\x11\x76\x62\x35\x55\x11\x9a\xce\xc1\x65\xba\x08\x1a\x48\x5e\xb7\x50\xa8\x6b\xd4\xe8\x4b\xf8\x39\xed\xe5\x17\x52\x83\x52\x47\x4b\x21\x1e\x05\xb3\x31\x9d\xdd\xa5\xba\x40\xef\x1c\xe7\x3d\x8f\x36\x91\x07\x2f\x5e\x53\xab\x0a\x1c\xea\x42\x13\xa6\x01\xb8\x0c\xc6\x5a\x16\x09\xad\x32\xda\x46\x97\xc1\xd8\x8a\x55\xa3\xbc\x63\x9a\x53\x3e\xce\x95\xba\xc6\x42\x6f\x2e\xd6\x39\x56\x79\xd1\xee\xb1\xea\x95\x3b\x58\xee\x98\x76\x8e\x73\x16\x78\xf6\x75\x3a\x9d\x7f\x96\x90\x65\xed\x23\x85\x5c\xe7\xce\x52\xfc\x74\xd1\xa8\x7b\x8f\x33\x2f\x82\x31\x18\xe0\xce\x45\x83\x15\x2c\xc2\xc1\x2e\x22\x8e\x37\x8a\x4d\xae\x76\x81\xd8\x43\xa7\x52\x70\xa1\x97\x8b\xac\x27\x2f\xe5\x5d\xa2\x59\x62\x57\x21\x42\xca\xc8\x44\x61\x25\xa5\xb4\x71\x3b\xd6\x53\xc1\xd0\x6f\x22\xf6\x92\x28\x54\x70\xbc\x4d\xd2\xe0\x1c\xef\xe4\x65\x4e\xb8\xac\x68\xd1\x50\xb9\x0a\x89\xb3\x67\xc1\x60\xd1\x28\x0b\x74\x0f\xc9\x13\x38\xf8\x55\x19\x1d\x6f\xff\x58\xfc\xd7\x39\x5d\x23\xa5\x8a\x7a\x65\x7c\xff\xfa\xb3\xef\x27\x73\xc6\xb0\xe6\x11\x39\xe5\xa7\x46\x0f\x85\xf0\x75\xb7\xae\xb9\x30\xae\x5b\xdc\x48\xda\x2a\x13\x3e\x83\x6e\xab\x51\x34\x69\x62\x3e\x35\x3b\x85\x82\x92\xa3\x63\x56\xb3\x46\xab\x48\x3e\xb2\x07\xb0\x79\xc7\x01\x2c\x1a\x1e\xbe\x4c\x76\xf1\x30\x9a\x70\xcf\x09\xc7\x70\xe8\x29\xb4\xe7\x0c\x82\x3f\xed\xbd\x5f\xb0\x04\xb9\xac\xb0\xef\xf3\xa7\x56\x01\x38\x7f\x82\x4f\x1c\x32\x78\xa5\xae\x89\x5a\x56\x1f\x7d\xe2\xfd\x43\xac\x0d\x85\xcf\x55\x5a\x06\x8b\x8e\xd5\x11\x1e\xe2\x68\x5a\x62\x25\xd8\x75\x4a\xd2\x82\x5d\xfa\xae\xc4\xc0\x20\x56\xea\x67\xc9\xf5\x3e\x77\xa9\xbb\xfb\x68\x96\x2d\x43\x0f\x74\x5f\xbb\xa7\x8d\xbe\xd4\xba\xcf\x70\x1c\x1e\x05\x57\xe5\xd7\xbe\x5d\xc1\x3b\x1e\x85\x45\xcb\x70\x3e\x32\x1c\x9e\xcd\xcc\xce\xf9\x3f\x0f\xe9\xb2\x18\x2f\x8e\xae\x39\x6f\xae\xf5\xe7\xeb\x4e\x74\x1e\x57\xe8\x8e\x5e\xda\xdf\x1d\x6f\xb9\xaf\xd0\x9d\x12\x7d\x28\x44\xfc\xde\x25\xa8\x02\x6a\x62\xe6\x3d\x45\xf8\x92\x22\x5e\x7c\x8d\x8f\x8b\xe0\x4b\x36\x8f\x7b\x15\xf7\x70\x96\x47\x93\x20\xc7\xaf\x82\x6c\x4e\xd7\x94\x92\xde\x1e\xba\xcb\x2c\x42\x46\x5a\x47\x17\xe4\x5f\x6a\x37\x2f\xa3\x10\xc7\x43\x3c\xb7\x93\xbc\x9c\xbf\x8b\x8e\x12\xe2\xe8\x68\x8f\xb1\x75\x31\xa5\x1d\x3f\xeb\xea\x6b\x76\x22\xd4\xde\x89\xa3\x8f\xf6\x96\x1c\x1d\xf4\xaa\x90\xa3\xc7\x7a\x63\xee\x33\x5a\x09\xe5\xa3\xef\xbd\x90\x59\x7c\x05\xd8\x7e\xea\xfb\x6c\x34\xaa\x5b\xf3\x29\xb4\x22\x5f\xda\x9b\x81\xf2\x4d\xe7\x53\x7a\x25\x6f\x0d\xa5\x41\x3e\x73\xfc\xd5\x99\x11\xea\x09\x2e\x49\x85\x72\xab\x38\xd2\xaf\x9c\x50\x42\x4e\x42\x3f\xa3\x9a\x73\x71\x16\xc6\x14\xe3\x6a\xdc\xba\x73\x4c\x89\x57\x70\x32\x47\x99\xf9\x82\x0d\x53\xc4\x9b\xd2\x0c\xa1\xbf\x7a\x69\xf4\xce\x83\xec\x7d\x1a\x69\x8a\x08\x1b\x3f\x67\xa1\x3b\xe8\x0c\xaa\xa1\xc8\x32\xdb\x64\x85\x28\x8a\x42\x0b\x35\x31\x90\x1c\xb3\xb0\x15\x5f\xb9\x92\x6c\xa9\x1a\x52\x3c\x10\xfe\xcf\xf4\x72\xb4\x10\x31\xb3\xac\xd2\xa0\x4d\xed\xe4\x5f\x4d\x93\xb8\x36\x94\xf7\x49\x8a\x4e\x7e\x10\x19\x36\xb0\xc1\x30\x4f\xd2\x1b\xc6\x43\xf9\x1d\x14\x58\x97\xd5\x11\x29\x6d\x98\x98\xb1\xf2\x45\x0d\x8f\xf8\xad\x93\xe5\xfc\x55\x57\x1c\x51\x68\xa9\x7e\x8a\xe1\xc6\x19\xfc\x25\xfa\x96\x55\xa6\x92\xfb\x86\xde\x65\x49\x48\x4b\x38\xbf\xe0\x99\xf9\x0d\xd5\x72\x9d\x5f\x90\xad\xe1\xfc\xa2\xb6\x5c\xb7\xa9\xf8\x6d\x72\xae\x6c\xb9\xe5\x90\x2a\xea\x72\xa4\xdc\xa0\xc9\x5e\xdb\x56\xa6\x6a\x2e\x1f\x5e\xa5\xe6\xee\x42\xd9\xa8\x30\x7b\x2c\x12\x30\xb3\x65\x30\x2e\x18\x9f\xf6\x9a\xcf\xea\x4f\xad\x4b\x4f\x66\xe5\x27\x6f\x17\x9f\xf6\x5a\x9b\xf0\x82\x8e\xc0\xd3\x5e\xbb\x49\x1f\xc5\xc8\x3d\xed\xb5\x69\x95\x68\x10\xc4\x4f\x7b\xed\x8d\xba\x6e\x3a\x01\x8f\xec\x22\xf0\x69\xaf\xd3\x81\x67\x7e\x25\xfa\xb4\xd7\xa1\xe5\x19\x57\x7c\xda\xeb\xd0\xe6\xf8\x65\xdf\xd3\x5e\x87\x40\xe0\x97\x98\x4f\x7b\x9d\xf6\xed\x59\xbd\xd3\x7c\xb4\xc5\x78\xb4\xc5\xf8\x6b\xd8\x62\x14\x19\x61\xdc\xbb\xe1\x61\x35\xb3\x88\x12\xf6\x0e\xa2\xec\x3b\x9c\x7f\x25\x5b\xc5\x07\xbe\xb3\x2c\x6d\x9f\xb8\xbe\xbe\x2e\xc3\x33\xb8\xc2\x3e\xb0\x0c\x7c\x64\x77\x00\xa0\x38\xbf\x40\xc1\x34\x52\x3a\xf0\x80\xa2\xaa\x3b\xc9\xba\x10\x35\xf4\x3c\xec\x8b\x89\x34\x18\xa7\xa6\xca\xcb\x68\xc1\x5d\xa4\x82\x80\xe5\x96\x77\xac\xbd\xf0\x1d\xce\x9d\x7b\xa1\xbe\xe7\xa9\x9b\x12\xd9\x64\x5a\x8f\x9b\xcc\xe3\x26\xf3\xd7\xd8\x64\xc4\xde\xf0\xfd\x18\xfc\xdd\xbf\x4d\x5e\x49\x13\x42\x28\x2f\x78\xf0\x7b\x9c\x66\x49\x1c\x8c\x1f\xed\x82\xbe\x91\x5d\x50\x39\xdb\x92\x18\x5f\x49\xe3\x95\x22\xad\xa0\x2c\xe8\x50\x0b\xb2\xb9\xfe\xe8\x2c\x74\xc7\xfb\x8b\x68\x42\xb6\x94\xa3\xe0\xea\x0d\xbe\x99\x83\xa3\x5a\xb4\x00\x4b\x4f\xb1\x39\xae\x03\x8b\x6b\xea\x79\xbb\x1e\x5d\xfd\x7c\xf3\x8d\x3b\x69\xec\xf1\xf0\x08\x0f\x93\x4b\x1a\x59\xa5\x48\xed\xcb\xcb\x15\xf4\xc0\x55\x66\xce\xb0\xcd\xe2\x71\x32\xfc\x5c\x8e\xc6\xb4\xb2\x05\x68\xf8\xca\x95\x19\xd1\x72\x63\x59\x38\xa2\xf7\x7d\xa3\x26\xe8\x63\xee\x9d\x5a\x95\x6b\x12\xd7\x25\x54\x61\xb7\xca\xcf\x53\xb9\x59\x2a\x9e\xa3\x2a\x17\x21\xe6\x1c\x39\x3a\xe0\x92\xf3\x59\xe3\x4a\x43\xca\xc2\x97\x6f\x35\x6a\x52\x54\xe5\x9c\xd6\x7d\xba\x74\xaf\xca\x5d\x01\xe7\x96\xc3\x1f\xee\xe4\xb2\x53\xac\x26\x16\x53\xa3\x16\x2d\x3c\x36\xb8\xcf\x0d\x7c\x73\x57\x0f\x0f\x0e\xcd\x97\xa1\xec\x32\xcf\x11\xed\xc7\x73\xc4\xe3\x39\xe2\xaf\x71\x8e\x60\x07\x08\xae\x66\xff\xf6\x9e\xb2\x8b\x28\x61\xf8\x45\x45\x30\x8d\xb8\x64\x29\xda\xc3\x90\x66\xaf\x40\xca\xa4\xd7\x11\x73\x7c\x1e\x98\x43\xc8\xcd\x14\xa3\x6d\xee\xdd\xa0\xa4\x19\xc8\xae\xa2\x7c\x78\x51\x23\xdf\xf5\xa8\x11\xc3\x20\xc3\x68\x89\xac\x86\x2c\x5f\xea\x29\x1f\x10\x0d\x98\x95\x5d\x44\x23\x47\x6a\x20\x35\x1e\x5e\x43\xff\xc8\x33\xa2\xb2\xab\xa5\x18\x5f\x51\x53\x49\xea\x0b\xf4\xdc\x6a\x7b\x8a\xe3\x30\x8a\xcf\x1f\xaa\xf1\xf7\x14\xbc\xba\xa7\xdb\x98\x30\x63\xf4\xde\x1c\x58\x56\x4d\x91\x8b\x44\x13\x07\x44\xc9\xf9\x07\x03\x06\xd1\x14\x05\xb4\x59\x77\x48\x00\x3e\x81\x31\x8a\xb3\x3c\x18\x8f\x4b\xb5\x6e\x94\xf6\x9a\x2c\xf8\xcb\xcd\x41\xe7\x1c\xe7\x6f\x93\xf3\x79\x36\x13\xac\x54\x91\xc1\x1b\x6d\xd8\x28\x35\xa7\xf1\x69\x32\xd7\xda\x86\x14\x99\xdf\x6c\xff\x22\x88\xcf\x9d\xf6\x1a\x8e\x96\x5d\x92\x07\x05\xa3\x0a\x4d\xda\x78\x6a\xd6\x0f\xa4\x8f\x4a\x43\x89\xc8\x7d\x75\x76\x07\x25\x70\x76\x71\xb1\x06\xec\xd3\xc9\x7e\xb2\x0b\x9b\xfd\x14\xc9\x51\xd6\x68\x16\x92\xb3\x18\xd6\xec\xe2\x82\xac\x20\x16\x37\xda\x51\xce\x1c\x55\x5b\x33\xec\x6f\xd9\x4f\xa2\x5a\xfb\x34\xcb\xd0\xc3\xa0\x60\x93\xb1\xd6\xb4\x20\x27\xd6\x7a\x76\x6f\x0d\x1b\x54\x7c\xc7\x56\xdd\x52\xb7\x25\xc5\x72\x3c\x70\x7e\xd1\x23\x7f\x71\xb0\xd9\xc5\x45\x8f\xfc\xa5\x0a\xc1\x4e\xcf\xf7\xce\xa3\x00\xfb\x28\xc0\xfe\x35\x04\x58\x21\x73\x02\xf9\x32\xb3\x85\x87\x8a\xaf\x7c\x3e\x4e\x06\xc1\xf8\x08\x9f\x93\xe9\x0f\xd2\x9d\x41\xe4\xf1\xf1\xce\xd6\x5f\xe9\x45\x21\xe4\xbe\xaa\xbb\x8e\x86\xc1\x54\x05\xe4\x83\xb3\xdf\xdf\x79\x6f\x43\x31\x30\x7a\x47\xbb\xbd\x23\x32\xff\x2f\x35\xae\x87\xdd\xf0\x59\xd8\x1a\x86\x9d\xce\xb3\x60\x73\xa3\x33\xec\x3c\xeb\xb4\xba\x1d\xdc\xdc\x6a\x3c\x1b\x6e\x34\x70\xbb\x13\x76\x3b\x1b\xdd\xd6\x60\x49\xc7\xc9\x05\x2a\x68\x06\xcd\x66\x73\x30\x6c\x6c\x76\x86\xcf\x86\xa3\x60\x73\xab\x39\x6a\x0c\xdb\x5b\xb8\xdb\x1e\x84\x1b\xcd\xe1\xb3\xe6\x60\x2b\x18\x35\x1a\x4b\xf3\x18\x1a\xc5\xb6\xa7\x88\xc8\xc1\x20\xea\x39\x86\x55\x32\xd2\x80\x22\xd2\x73\xf6\x94\x8b\x08\xbc\x38\xe9\x80\x05\xdd\x1c\x69\x07\x6c\xbb\xe3\x1c\xb2\x83\xd5\x16\xcf\xf1\xd3\x5e\xb3\xfe\x74\xce\x0c\x3e\xed\xb5\x08\x97\xde\x78\xe4\xd2\x8f\x5c\xfa\xaf\xc8\xa5\xb9\xbe\xcd\xc1\xa6\x8b\xae\x20\x47\x69\xf2\x1f\x3c\x09\xe2\xb5\x10\x5b\x57\x97\x5f\x2b\x7c\xbe\x28\xab\x5c\x53\xde\x35\x34\x3e\x55\x83\x2a\x25\xe8\x0b\xad\x4c\x66\x96\xc9\xec\x32\x8b\x45\xb9\x5f\x24\x9e\x7e\x89\xc8\xf8\x7c\x7c\xbe\x76\x6c\xfc\x6f\x1e\xda\xda\xd1\xf1\x82\xe0\xd6\xee\x80\xc9\xc6\x9c\xfc\xd3\xf5\x96\xd6\x86\x10\xd3\xdf\x51\x40\x69\xef\xe8\xdc\x5b\x48\xe9\x27\xdb\x94\x1c\x8d\x57\x32\xfb\xd0\x3d\xc5\x9c\xfe\x93\x25\x0e\x70\x4c\xcc\x1f\x3b\x75\x80\x67\x5a\xa8\xed\x82\xc4\x34\x4f\x8a\x09\x5b\xb7\x75\x98\x47\xda\x66\x9a\xbc\x79\x43\x58\x25\x88\x35\xd4\x52\x84\x65\x16\x27\x44\x06\xb5\x7e\x85\xf3\x9a\xa2\xb8\xc0\xf1\x6c\x82\xd3\x60\x30\xc6\x3d\xc8\x92\xc6\x47\x48\x57\x96\x06\x13\x9c\xcd\x09\x58\xad\xc4\xb5\x86\xe2\xa0\xdb\x96\x13\x0b\xef\xe6\x04\xb3\xce\x8c\x68\xd6\x99\x27\x9c\xb5\x59\x44\x7e\xd4\x10\x68\x9e\x59\xa1\xfb\xdd\x21\x57\x92\xc1\xa7\x3a\xd4\xa9\xd3\xe1\x13\x7d\x22\xad\x04\xd9\x4d\x3c\x7c\x05\xfb\x24\x11\xee\xa1\x23\xcb\x67\x5a\x9c\xf0\x1d\x56\xa4\xe6\x08\x53\x69\x54\xd7\x26\x0f\x40\x99\x8c\x69\xe9\x1c\xe7\x4b\x68\x05\xf0\x59\x1b\x5e\x04\xe9\x4e\x5e\x6b\x2c\xaf\xe5\xc9\x87\xe9\x14\xa7\xfd\x20\xc3\xb5\x65\xfe\x19\x42\x07\xd7\x9a\xcb\x73\x37\x4d\x3e\xf3\x45\xd1\x32\xa5\x28\xa2\x86\xe5\xe6\x7e\x1e\xbc\xd6\x39\xe9\xa6\xbd\xfa\x84\x30\xa6\x45\x9f\x16\x6f\x5d\xf1\xa7\x55\x3d\x19\x8f\x06\x2f\x2a\x50\xe1\x45\x94\xb8\xb5\xa2\x54\xcf\xeb\x2c\x9f\x93\xea\x3d\x66\x6a\xf1\x39\xae\x1b\x08\x15\x75\x8e\x46\x06\xaf\xd4\xc3\x52\xf1\xc3\xf5\xa8\xe1\xee\x41\xb7\xe3\x86\xdf\xda\x21\xbe\xcf\x71\x5e\x39\xc2\xf7\x39\x2e\xb3\x9d\x7d\x9f\x01\xbe\x1d\x64\x54\x25\xc4\xb7\x9d\xd7\x40\x91\xc9\xed\x5b\x89\xd3\x33\xfd\x4a\x83\xa6\x36\x70\xa6\x64\x28\x17\x2d\xdc\x9c\xde\x87\x8f\x1a\xce\x07\xac\x28\xf5\xf4\xed\x59\xbd\xd3\x7d\x54\x4f\x3c\xaa\x27\xfe\x8a\xea\x09\xc6\x3e\x26\x6c\x57\xb0\x95\x14\xff\xc2\xa3\x51\x8a\x6f\xd0\x2f\xd1\x78\xf8\x19\xa3\x9f\x3e\xe1\xd1\xa8\xc8\xae\xba\xb2\x21\xf6\x41\x90\x46\x41\x8c\x0e\x83\x78\x88\x03\x28\xef\x33\xc1\x5e\xd0\x72\x9b\x55\x7b\x15\x5c\xa2\x5f\x92\x24\x44\x3f\x9d\x17\xea\x4a\x3a\xb6\xae\x44\xe6\xb0\x56\x22\x97\x32\x3e\x3d\x2f\x6e\xab\xc7\x3b\xc9\x0c\xb1\xea\x8b\xaf\x5a\x22\x75\xa1\x32\xa7\xfb\xf9\x52\xc6\xf3\xf6\x46\x83\x31\xa5\xcd\x69\x90\x65\x51\x7c\x2e\xd2\xa4\x92\x4d\x6f\xca\x52\xf2\x67\x5a\xcd\x60\x9c\x25\x76\xf5\x64\x3c\x26\xd5\x09\xb1\x62\xc2\x20\xf0\x6c\x82\xe2\x24\xa4\x5f\xa3\x78\x98\x4c\x54\xe8\x1c\x20\x8b\x00\x41\x6f\xcf\xf3\x68\x82\xc9\x7a\x8d\x32\xd4\x44\x19\x1e\x26\xb1\x48\x41\x44\x93\x00\xe7\x49\xac\x6d\x74\xa4\xeb\x05\xfa\x13\x8e\xbe\xa1\x45\xe1\xaf\xd1\xb6\xe8\xa0\x9e\x77\x31\x19\xc3\xfc\x48\x41\x9d\x26\xbd\x60\xd8\xb9\x54\x3b\x85\x67\x61\x22\x3e\x5d\xa4\x49\x9c\xcc\xb2\xf1\x0d\x4d\x0b\x53\x2c\x16\x90\x22\x9e\x63\x17\x0a\x83\x3c\x28\xa5\x56\xd0\xc7\x45\xd3\x39\x19\x79\x69\x08\x48\x57\x42\x31\x7b\xf8\xcc\x9c\xe0\x7a\x02\xb6\xf7\xac\x7c\x4d\x15\xd7\xb8\x57\xf0\x6c\x3c\xb6\x0e\x30\xba\xd8\x61\xa7\xdb\x25\x88\x89\x5c\xbb\xf0\x60\x24\xda\x25\x00\x98\x42\x63\x5b\x9f\x5b\x3d\x2b\x8c\x38\xff\x41\xe7\x3c\x89\xb0\x85\xac\x39\x27\xcf\x9c\x59\xde\x97\x52\x8d\x7e\x5e\xab\xac\x3a\x09\x16\xa2\x17\x10\xd2\x4b\x11\x8d\x95\xd7\x5e\xcf\x42\x55\x86\x7c\xa0\x31\x8b\x86\x14\xbf\xee\x92\xd4\xc4\x43\x7b\x2a\x07\x81\x62\x7a\xba\x3f\xd2\xb1\xa9\xa5\x6a\x02\x24\xd2\x35\x9c\xa6\xce\x34\x4b\x6a\x8f\xb4\x2c\x47\x5a\xed\x2a\x94\x38\x6f\xb0\xac\xba\xce\x56\x45\x65\x6a\x38\xaf\x91\xa8\x37\x41\xc5\x9c\x93\x91\x8b\x5e\xcb\x1c\x7e\x48\x41\xc8\xe2\x6d\x52\xa6\x96\x05\xfc\xbe\x49\x17\x5a\xfc\x43\x92\xae\x9a\xd1\x9c\xf2\x64\xc1\xb6\x16\x23\xd6\xec\x9e\xa9\x95\xab\x33\x61\x7e\x6a\xbc\x8d\x3b\x10\x69\x36\x8f\x4a\xb5\xce\x54\x22\x4f\xb1\xf7\xe2\x5c\x6c\xf2\x44\x96\xe6\xe7\x60\x26\xc9\x7a\x49\x33\xe7\x33\xe8\x61\xa9\x65\x09\x4e\xc0\xd1\x05\x93\x02\x89\xa4\x4a\xe7\x72\x72\xa4\x64\x92\x97\xaf\x2b\xa4\xcc\x7b\xad\x48\xa9\xed\x81\x06\x8d\xb9\x05\xe8\xfb\x96\x34\xe2\x5d\x81\x8e\x12\xc2\x56\xce\xc4\x30\x4f\xc2\x04\x0d\xc7\x38\x88\x67\x53\x25\x8d\xad\xa6\xf8\x98\x37\xc0\x4a\xef\x1c\x8b\x9a\x22\xaf\x06\x41\x11\xc8\x98\x33\x40\x64\xbe\x53\x5a\xfe\x8c\xc8\x7e\x04\x40\x0f\x51\x30\x51\xd8\xb3\x40\xf5\x5c\x40\x7b\xf2\xe7\xad\x0c\xe8\xb5\xbe\xee\x9a\x29\x85\xbf\x30\xc9\x52\x17\x3c\x18\x52\x95\x92\xd8\x4a\xc2\x48\xa6\x42\x22\x27\xf2\x37\xb3\x06\x87\x93\x21\xcb\x02\xea\xd3\xfe\x93\xba\x1e\x7a\x71\xd2\xc4\xdc\x19\x12\xe0\xf4\x25\x00\x30\x64\x97\xa9\xa9\xa4\x63\x32\x9e\x6b\xc3\x28\xfb\x25\x46\xb1\xc6\xf4\xe0\x9f\xf1\x4d\x56\x93\xf5\x97\xb9\x7e\x1e\x62\xec\xa3\x1f\x7e\x40\xbe\xb1\x26\x24\x98\x9e\xd0\xf7\x35\xad\xd0\x73\x7d\x3e\x5c\x27\x80\x8a\xc9\x85\x53\x4c\x38\x12\x39\x24\xf1\xe9\x99\xe0\xe1\x45\x10\x47\xd9\x84\x1f\xf5\xcb\xb1\x28\x00\x54\x6e\x0a\x68\x9b\xea\xe0\x7f\xc6\x78\x2a\x62\xf4\xc8\xa1\x58\xff\xf1\x53\x76\x11\xc5\xa4\xe9\xeb\x61\x32\x99\x8e\xf1\x75\x94\xdf\xf4\x36\xc4\x29\x17\x01\x31\xd5\xc8\x0e\xf6\x19\xdf\x50\x0d\x8d\x18\x6f\x65\x44\xd7\xd7\x51\x8a\x27\xc9\x25\x46\xc1\x78\x0c\x3d\xcd\xea\x08\x5f\x0f\xf1\x34\x87\xd3\x10\x7b\xa5\x96\xcf\x2f\xf0\x0d\x8a\x31\x1d\xa5\x01\x66\xf5\x43\x32\x0a\xb3\x60\x3c\xbe\x41\x83\x1b\x18\x46\x32\x64\x2c\xd0\x0e\xd0\xd6\x2f\x64\xd7\x8c\xe2\xf3\xda\xb2\xb2\x59\xd5\x9e\x68\x3d\x44\x5f\xbe\x10\x7c\x65\xee\x05\x02\x80\x10\xe5\x47\x96\x82\x61\xb5\xa9\xef\x62\x0a\x15\x7e\xc6\x37\x67\x6b\x62\x55\xeb\xae\x01\x36\xcd\x92\xd2\x0e\x63\xfd\x3f\x15\x09\xc3\x51\x9d\xd0\xc1\x90\x1a\xaa\xa3\x24\xae\xc2\x5f\xa4\x8d\xf9\x3c\xba\x65\x16\xf5\x2e\x85\xb5\x87\x52\x51\x97\x03\xb5\xba\x9b\xe1\x5c\x1b\x1f\x02\x5c\x51\x1b\xd7\xd1\x70\x6d\xef\xe4\xf5\xc7\xf7\x87\x6f\xdf\xee\xbf\x7b\xf5\xf1\x64\xff\x60\xef\xf0\xc3\x89\x7e\x72\x2c\x33\x4b\xb6\x8c\x68\x09\x80\x0f\x72\xc2\x76\x8b\x99\x04\xbf\xdd\x20\x0f\xd0\x36\x3a\x3d\x7b\x6e\x7e\xd9\x07\x77\x73\xf9\xa1\xdc\xe2\x16\x40\xd7\xa6\xb3\xec\xa2\x66\xae\x15\x26\xb9\x6a\xa5\xf7\xc3\x8c\x16\xfe\x8c\x6f\x6c\x81\x18\x92\xd7\x0a\xa0\x15\x07\xb3\x94\x34\x2d\xa0\x2f\xab\xbb\xc9\x24\x98\x6a\x0c\x37\x02\xf2\x06\x46\x04\xa4\xc7\x49\x58\x1d\xb0\x83\x60\xaa\x69\x86\x94\xbb\x09\x3d\x7e\x00\x95\xcb\x69\xa6\xf4\xdf\xcd\xd1\x38\x08\xa6\xa7\x50\x2d\x02\x61\x83\x8f\x91\x23\x2d\xba\x37\xae\x82\x42\xe4\xa5\x0f\x06\x89\x7e\x34\xf8\x9b\xca\x7f\x4f\x0e\x77\x0f\x7b\x9c\xf8\xd0\x38\x39\xff\xa7\x79\x74\x48\x9c\x87\x87\xbb\x1d\x17\x4a\xe8\x57\x32\x4f\x96\x60\xf8\x66\xe4\xa8\x77\xdd\x32\x82\x15\x6f\x28\x47\x99\x8c\x3d\x3b\xf7\x46\xa1\xea\xb1\xc6\x68\xe2\x33\x46\xd9\x2c\x05\xcd\x3e\x67\x6b\x51\x86\xb2\x3c\x22\x14\x41\x77\x01\x1c\xa2\x60\x04\xde\x75\x69\x1a\x5d\x06\x63\x63\xe7\x56\x20\x92\x81\x80\x40\x10\x74\x81\x44\xe1\x99\x99\x42\x99\xa1\xa2\x24\x33\xd7\xcb\x8b\x2f\x4e\xb7\x33\x55\xb9\xc4\x86\x08\x92\x5f\xbb\xca\x8e\x82\x71\x86\xd5\x7b\x54\xe6\x23\x38\x67\xfc\x44\xca\x02\xb3\xad\xb2\x00\xc8\x04\xc0\xc4\x2a\xeb\xd3\xa3\xf0\x78\x6e\x0c\xdd\x13\xa8\x37\x67\xc8\xca\x2b\x42\x54\x02\x52\x33\xbe\x44\xa1\xde\x2b\x63\x2d\xfb\xc6\x45\xc7\xa2\x48\xa3\xa2\xdd\x5a\xcf\x0b\x7a\x78\x64\x46\x56\x31\xaf\x24\x8d\xe8\x87\x85\x09\x8e\xc9\x13\xbb\x82\x78\xda\x6b\x6f\xde\x9e\xd5\x3b\x9b\x8f\x97\x98\x8f\x97\x98\x7f\x8d\x4b\x4c\x76\x7b\xc9\xcf\x0b\x85\xae\xdc\xf7\x14\xe9\xa9\x20\x08\x53\x85\x34\x8e\x3e\x13\x6b\x1e\xf1\xa3\x69\x8a\xea\x3b\x61\x98\xc1\x18\x8b\x8d\x24\x88\x41\x8f\x95\xa1\x19\x95\x72\x98\x1b\x66\x9d\xc8\x38\x51\x8e\x53\x82\x3e\x84\x9a\xa1\x3b\x17\xdd\x4e\xd9\x6e\xa8\xca\xea\xec\x0c\xc5\x3e\x18\x9a\x23\xb2\x57\x69\xc2\x3c\x17\x95\xec\x90\xb1\x34\x66\x92\xf4\xf5\x24\x25\x93\x98\xf9\x09\x6a\x62\x3e\x93\x54\xc8\xac\x39\x34\x98\x6e\x31\x84\xee\x9a\xbc\xf3\x99\x83\x87\xbb\xac\x9e\x90\xa1\x6c\x4c\x52\x7d\xb7\xf0\x29\x43\x85\x65\x27\x0f\x07\x05\xa8\xfe\xf0\x03\xe0\x4c\x95\x52\x51\x7c\x0e\x5e\xe8\xcb\x0a\x3c\x7e\x6b\x32\x2f\x30\x36\x85\xa7\x3a\xd3\x2f\xd6\x39\x21\x7d\x8c\x83\x0c\x1a\x39\xce\xc9\xac\x3f\xd9\xde\x16\x83\xab\x0d\xc5\xfa\x3a\xcd\x19\xa0\xd1\x11\x2c\xc4\x3c\x9d\x11\x69\x28\xcd\x72\x94\x25\xd4\x0e\x75\x3a\x05\xc6\x0e\xe7\xd9\x20\xbe\xc9\x2f\x20\x63\xf1\x00\x8f\x08\x7b\xa0\x0c\x80\xdf\x07\x43\xbf\xa5\x92\x51\x0e\xe3\x13\x07\x76\x3f\xfc\x80\x5c\xa3\xbb\xac\xd5\xb5\x6e\x32\x08\x82\xcb\xcf\xdd\xdd\x49\x28\xdf\x8c\xf1\x75\x8e\xfa\xef\x3f\xa0\xe1\xcd\x70\x8c\xeb\xa2\x23\x30\x84\x62\xb3\x01\x5c\xa1\x53\xcc\x9e\x6b\x9a\x26\x43\xc2\xb3\x32\xda\x7f\xad\x05\xe5\x50\x29\xc8\xdc\xa4\x2f\x0b\x55\x36\xb9\x6a\x89\xdb\x3a\x6a\x98\xd8\x3b\x86\x86\x4e\x9b\x5b\x9e\xd1\xcc\xe2\xfe\xa6\x80\x30\x0d\xfa\x14\x8d\x69\xcd\xb6\xf4\xf2\x47\x78\xb7\xcd\xbd\x6e\xeb\x52\x56\xdd\x0f\xeb\x72\x35\xb3\xf7\xaa\x7a\x86\x1e\xb5\x81\x05\xf0\x8e\xaa\x86\xa5\xfb\xce\x18\xd3\xde\xb0\xd1\xf3\xbc\x22\x0c\x09\x4a\x29\x4b\x71\x45\xdb\x48\xd1\x01\xad\x50\xb6\xba\xb2\xa2\x95\x14\xcb\x4d\x3b\x22\xc3\xa7\x20\x0c\x85\x49\x9b\x11\xca\x9a\x95\x30\xe7\x4d\x93\xbd\x15\xfe\xc8\x6d\xd6\xd8\x17\xc5\x9c\x5f\x17\x12\xc5\xf8\xa8\x86\xd4\x12\x8b\x39\xe6\x9d\xd1\x48\xbe\xd4\xd5\x41\x92\xa7\xc0\xf1\xdc\xea\x4c\x25\x84\xd4\xf9\xf6\xe8\x6d\x1c\xf3\xa5\x6a\x88\x6b\xca\x1c\x2d\xcf\x9d\x0d\xaf\x04\x2d\x90\x2b\xb2\xe7\x23\x92\xb1\x1e\x07\x89\x08\xc7\x5b\x8f\xc2\xf1\xa3\x70\xfc\xd7\x10\x8e\x85\x24\x0b\xe4\xcb\x83\xd5\x3f\x94\x9f\xb8\x95\x33\xdb\x4a\x99\x8d\xaf\xa9\x42\xb9\xc8\x85\xfc\x78\x12\xa4\xf9\x1e\x2b\xa8\xbb\x7e\x17\x5e\x88\x81\x5a\x07\x50\xe0\xfd\x64\x65\x05\xea\x4c\xe2\x35\x3e\x0b\x81\x97\x66\x74\xbe\x05\x37\x26\xdf\x37\x22\x58\x0f\x82\xd8\xfc\xfc\x73\x30\x9e\xe1\x5b\x74\x49\xfe\x61\x97\x1b\xa4\x95\x11\x4e\x71\x89\x7b\xd5\xba\xdb\xc6\xc1\xc8\x31\xae\x08\xcf\xf9\x45\x1d\xb0\x24\x82\x7e\x9d\xb6\xea\xda\x39\x41\x2d\x46\xe7\x04\x32\x36\x0c\x82\xb8\xa6\x38\x8b\x80\xf6\x85\x7c\xe7\xaa\x9a\x9a\xc3\x2e\x8a\x54\x84\x3c\x12\xb5\xa5\x88\x2a\x5f\x28\x48\xe6\x44\xbe\xe4\x56\xf5\x32\xa8\xbb\x51\x4a\x84\x3b\x97\xf7\x13\xef\xd8\x49\xc2\x5c\xcf\xd5\x5e\x41\x75\xd6\x42\x6d\xd9\xee\xa1\x5b\xdd\xee\x12\x90\xc1\xa3\x45\x38\xf5\xe3\xfc\x62\x4d\x71\x7a\xaf\x2d\x43\x23\x0c\xdb\x38\xcb\xa3\x7c\x46\xe5\x3b\xdb\xb8\x2d\xc4\xd3\x24\x8b\x72\x15\x4b\x06\x57\xa0\x07\x60\x86\xe3\x08\xc7\xb9\xcb\x26\xa4\x74\xe3\x96\xa9\x07\x6b\xc8\x31\x8a\x55\xb1\x72\x8d\xa1\x29\xab\x15\x2e\x32\x41\x8f\xa3\x59\x1c\x82\xfd\xe8\x10\xa7\x79\x10\x09\x82\x98\xb3\xe6\xc4\x74\x2f\xb6\xf8\xbe\xfa\xba\x13\xf8\x2e\xb4\x00\xd9\x2c\x91\x99\x37\xa2\x53\x2a\x62\xb9\x08\xc7\x90\x27\x52\x12\x27\xc0\x7b\xb4\x09\x85\x96\xc7\x33\xdc\xa3\xff\x48\xf1\xdc\x93\x67\xa5\x70\x16\x19\xd9\xc8\x49\x3c\x27\xbb\x51\x34\x44\x9c\xed\x22\x91\x7c\xa7\x36\x99\x65\x39\x6c\xb9\x78\x82\xe3\x5c\x50\xdc\xe0\x26\xc7\x59\xbb\xb5\xcc\x4e\x15\x4f\x96\x3d\x13\xcf\xca\x3f\xe8\x74\x67\xce\xf9\xe6\x60\x28\xed\xa3\x59\x1c\xfd\xcf\x0c\xa3\x28\xc4\x71\x1e\x8d\x22\x9b\xfd\x57\xa2\x0f\x3e\x82\x25\xa8\x02\x9a\xf7\xb1\xe7\x00\x36\x42\x65\x5b\x7c\x6e\x93\x0e\x9f\x0b\x48\x7e\xb4\x16\xe4\x84\x87\xaf\xf1\x79\xe0\xe0\x7f\xbf\x3f\xd2\xb1\x64\x6d\x3e\xc6\x9e\x78\x20\xf6\x86\xfd\xb4\xd7\x26\xa2\x37\xcf\xb4\x43\x84\xee\x67\x65\x84\x6e\x0a\xfd\xf6\xac\xbe\xd1\x28\x2b\xa3\x3f\x57\x55\xf8\x09\x91\x90\x58\x86\xa7\x3a\xf5\x7d\xd6\x6d\xfd\xc8\x19\x26\x19\x21\xd1\xb5\xed\x6d\xf4\x94\x46\x38\x7b\x6a\xd8\x36\xf4\x93\xc9\x24\x89\xff\x75\x2c\x4f\xcf\xe6\xa8\xc8\x5f\xac\x45\x8e\x68\xed\x29\x19\x9d\x14\x3f\x5d\xae\x23\xe5\x15\x8e\x87\xab\x83\x20\xc3\xdd\x8e\xf1\x61\x12\x6e\x98\x45\x2f\xa7\x9f\xc3\x91\xf1\x72\x18\x4d\x2f\x70\xba\x4a\x21\x1b\x16\x43\x08\x6e\x84\xd4\x2e\x52\x8f\x51\xda\x43\x3e\x42\x4f\xd1\x0f\x3f\xb0\x0f\x6b\xc1\x24\x34\x7a\xbc\x73\xb0\x2b\x1e\x69\xa1\xda\x29\xef\x49\x1d\xe9\x1d\x80\x67\x82\x37\xfd\x40\xd1\x85\xdf\x2a\x96\x67\x62\x32\xdc\xe8\x6a\xcd\xd3\xa8\x32\xa8\x36\x48\x93\xab\x0c\xa7\xf2\x10\x2b\x46\x37\x49\xf2\xb5\x7e\x7a\x33\xcd\x93\x7f\x1d\x9b\x16\x53\x2c\xe1\x97\xa4\x05\x51\x50\x2a\xc0\x6a\xae\xd3\x2a\xb4\x7d\x7c\x91\xa4\xf9\x70\xc6\x7d\xc3\xd9\xda\xec\xa3\x6d\xc4\xc1\x3c\xd7\xbf\x7c\x1c\x47\x03\xf2\x75\x6d\x1c\x0d\xf4\x4f\x34\x9f\x32\x0c\x02\x29\x40\x0a\xae\x29\xef\x4c\x38\xc1\xf8\x3c\x01\x40\xe4\xc7\x73\xed\xb6\xf6\x6d\x92\x7c\x9e\x4d\x51\x1e\x0c\xc6\x58\x47\xec\xf8\xc5\xe1\xaf\x9a\xc6\x82\x89\xdf\xef\x7e\xfe\xe8\xf9\x74\xfc\xe1\xc5\xc7\x83\xfd\x5f\x3f\x36\x0a\xbe\x35\x0b\xbe\xb5\x0a\xbe\xb5\x7d\xa8\x14\xb4\xa9\x7e\x77\xb5\xab\x7e\x77\xb5\xad\x7e\xe7\xed\x1b\xab\x77\x4a\x0e\xda\x63\xe7\x18\x7a\x08\x41\xab\x19\x26\xb3\x01\x39\x32\x91\x9a\x4a\x09\xe0\xfd\x06\x42\x8a\x51\x45\x04\xc1\x43\x51\x84\x7e\x42\xad\x8d\xee\x73\x14\xad\xac\x98\xba\x42\x10\x8f\xd1\x4f\xa8\xd9\xda\xb2\xd5\x88\xe1\x69\x74\x86\xb6\x49\xfd\x9f\x50\xd3\xd0\x1f\x9a\x6b\x46\xab\x51\xa3\x55\x96\xd1\x6f\xa8\x71\xdd\x6c\x0e\x8c\xba\x7f\x53\x7f\xeb\x1d\xfe\x25\x18\x7f\x46\xaf\x5e\xd6\x5a\xbf\x6d\x2d\x1b\x1d\xbd\x36\x43\xa1\xc2\xcb\xc8\x7c\x5b\xa5\xf7\xca\x08\x67\x83\xe4\x5a\xfb\x06\xf6\x0f\xa4\xcd\xeb\x08\xfd\x86\x6a\xd7\xb2\x4b\xec\x77\x4b\xf9\xdd\x56\x7e\x77\x0c\x55\x2b\x00\xa9\x65\xd7\xe8\x1f\xff\xf8\x07\xda\x82\x82\xd9\x35\xfa\x01\x35\xae\x47\x23\x3a\x42\xdd\xb6\x5e\x83\x2c\x9b\xd3\x6b\x32\x90\xd9\xb5\xfe\x85\x2f\xaa\xd3\x0c\x3e\x5f\xdb\xfa\x67\xde\x9f\xc9\x6c\x9c\x47\xd3\x71\x34\x04\x0d\x8b\xd5\xb3\x6b\x42\xc7\xe1\xe9\xf5\xd9\x73\xfb\x53\x87\x7e\x6a\xb9\xbe\x6d\xd1\x6f\x9d\x33\x7f\xcb\xd9\x6c\x80\x40\x1e\xab\xa3\x49\x74\x8d\x86\xc9\x78\x36\x89\x33\x93\xea\x39\x44\x22\xb4\xd4\x42\xe8\xcf\x8f\x84\x58\x1a\x4d\x3e\x42\xec\xb1\xd1\x6c\x34\x8c\x11\x15\x6b\x99\x0e\x52\x2d\x87\xe9\xe8\x2c\xa3\x2f\xe4\x37\x1d\x66\x77\x8d\xa6\x5a\xa3\xd9\x55\x6a\x34\xbb\x9e\x2a\x2d\xb5\xca\xd6\x32\x92\x55\x5a\xe6\x4c\x0b\x1e\x40\xab\xe4\xfe\x31\x8a\xe2\x4b\x75\x9c\xc8\x63\xd9\xb1\xba\xde\x52\x46\x86\xd1\x63\x87\xbd\x6a\xf0\x17\x2d\x6d\x30\x8b\xc6\x52\xe3\x8d\x8c\xac\x4a\x0c\xa8\xc6\x32\xb5\x6a\xc5\xa3\xaa\xb1\x52\xad\x5e\xf1\xd0\x6a\x2c\x96\xd5\x2b\x18\x5f\xb8\x79\x01\x1d\xbb\x12\x23\x00\xf1\xa3\xf9\xb5\xcd\xea\xd8\x42\xdf\x2e\xc9\xe7\xa0\x78\x0b\xfd\x86\xc2\x53\xf2\xdf\xf5\x16\xfa\x0d\x5d\xb7\xce\xce\x8c\x25\x83\x80\x79\x6c\x43\xa9\xeb\xc8\xfc\xaa\xf3\x41\xf1\xab\xb6\xbc\xac\x6f\x1f\xef\x53\x3c\xa4\xfd\x0a\xd1\xd1\x30\x89\xd9\x3e\xa2\x6d\x41\x47\xfd\xc3\x77\x64\x23\x68\x5c\x37\x1a\x75\xd4\xb8\x6e\x34\xe1\xef\x16\xfc\xdd\x81\xbf\xb7\xea\x40\x04\xe4\xef\x16\xfc\xdd\x81\xbf\xb7\xe0\xef\xe6\x80\xfc\xdd\xee\x6a\x3b\x17\x3f\xb4\x21\x38\x99\xec\xec\x1d\x23\xc8\xee\x8a\xa8\x54\x85\x88\x90\x90\x46\xf9\xc5\x64\x4d\x29\xb6\xae\xa1\x45\xea\x6c\x33\xc1\x62\x8d\x3e\x28\xe2\xc7\x1a\xbe\xce\xcd\x50\x16\x1f\xc3\xe4\x08\x67\x38\xef\x39\xb5\xf9\x62\x4c\x8e\x3f\x47\x53\x66\xda\x9c\x8c\x50\x7c\x94\xc0\xd9\xf1\x22\xc8\xd0\x00\xe3\x18\x9c\x30\xd8\xc5\x60\x10\x87\x60\x5d\x18\x46\x21\x8a\x93\x9c\xd9\x90\x5a\x44\x41\x53\x0f\x71\x40\xdc\xf0\xf5\xe3\x67\x7c\xf3\x3e\x8d\x92\xf4\x88\x5a\x34\x6f\x6f\xcb\xf7\x36\x11\x99\xf6\x69\xc8\xdc\xe0\x3c\xe2\x1d\x1f\xad\xcf\x98\x07\xd3\xb0\x1a\x96\x6f\x6d\xa6\xfc\x19\xdf\xfc\x92\xa4\x60\x59\xf9\x19\xdf\xac\x5d\x91\xdf\xce\x52\xc7\xd1\x7f\x30\x2b\x94\x45\xe7\x2f\x08\xef\x41\xeb\xa8\x53\xb0\x8c\x84\x8b\x44\x0a\xe3\x62\x01\xe5\xe3\xc5\xf1\x93\xcf\xbc\xb9\x15\xd4\x2d\x03\x9f\x74\x3d\x1b\x5e\x60\x72\xaa\x41\x44\xe6\xb6\xf1\xcf\x8e\x92\x2b\x02\xb9\xc6\x1b\x59\x21\xdb\xf1\x8f\x45\xf8\xab\x50\x9d\x03\xc2\x5b\x54\xc6\x57\x79\x77\x6a\x2c\x58\x69\xad\x4a\x70\xa1\xe2\x05\xfd\xf9\x13\x43\x8f\x3d\xdb\x92\x06\x25\x32\x51\x98\x0e\x8e\x5d\x08\xa9\x58\x9d\x42\xf1\x33\x3a\x98\x30\xc3\xec\x8d\xc9\x64\x3c\x4c\x4a\x6e\x1a\x16\x50\x08\x03\x65\x8c\x1b\x47\xf2\x09\x43\xf3\xef\x02\x4d\x17\x9e\x30\xce\x47\x49\x8e\x08\xb9\x39\xbe\xe6\x2a\x63\xd7\xf9\xba\x1b\xd6\xf1\x6c\x50\x08\x0b\xc4\x1e\x0e\xe5\x4c\xd9\xa0\xe0\x83\xdc\x6f\x98\x64\x75\xa6\xec\x45\x6a\x89\x2d\xad\x00\xe0\xc6\x20\xb3\xd7\x3e\xfc\x0e\xa2\x6b\xe0\xbe\x4e\xfc\x7e\xdb\x06\x0e\x7c\xca\x86\x6e\x5d\xce\xf0\x17\xd4\x60\xc8\x9a\xb3\x26\xe6\x0d\x28\x83\x2d\x97\x7f\xa0\x2e\x61\x3e\xc6\x0c\xa0\xed\x6d\xd4\xf1\xce\xc2\x1f\x67\xe4\xac\x0e\xda\x63\xe9\xa3\x6f\x8b\x3e\x59\xd7\xcf\xd0\x6f\x28\xb7\x08\x7e\x0e\x8b\x55\xc5\xac\x42\x1e\x10\xc5\x97\x6f\x1c\x6c\xc0\x7a\xed\xe5\x04\xa4\xa4\x64\x06\xe2\x49\xf2\x03\xfe\xca\xc5\x12\x54\x56\xc2\xd8\xdb\xaa\xa8\x60\xd1\x21\x1c\xd5\x78\x03\x7f\x77\xd3\x83\x77\xc1\xdf\x0f\xcf\xe8\xd8\x60\x0a\xb1\xfc\x09\x75\xc0\x13\x86\x3e\x78\x68\x58\x1f\xec\x53\x5e\x99\x49\x78\x25\xb1\x2e\x00\xa2\x4b\xb8\xfa\x62\x20\xd4\xa5\x8b\xb2\x9e\x35\x71\x86\x7e\x73\x2c\x2f\x53\x9e\x75\x2f\x17\xb3\x91\xf6\xa9\xbe\x6e\x1c\x83\xea\x7b\xba\xad\xab\xe3\x8d\xe3\x61\x7a\x33\xcd\x41\xa2\x52\xe5\xa5\x83\x3a\x4a\x46\xa3\x0c\xe7\xe6\x70\x53\xea\x0e\x93\xbe\xa8\x26\xcb\xd6\xed\x2d\xb0\x2e\x4f\x5a\xf2\x67\x53\xfe\x6c\xc9\x9f\xed\x3a\x30\x03\xdd\x98\x4d\x43\x36\xc4\x15\x91\x25\x9c\xed\x2a\x98\xa2\x56\x1c\x82\x10\xd7\xc9\x2f\xdc\x92\x01\xa1\xd6\x83\x53\x0a\x84\x08\x05\xc6\x78\x6a\x9f\xf4\xa2\x6d\x7f\xd1\xb6\xeb\x84\x51\x66\xfc\x74\x4a\xac\xeb\xe4\xa7\x3f\x36\xf5\xc7\x96\xfe\xd8\xae\x8b\xe3\xbe\xc3\xce\x6c\x9f\x9c\x20\xbf\xfd\xf0\xf8\x66\x5b\x1b\x24\xe7\x74\xd7\xd1\xdd\x28\xcd\x41\x2d\x4c\xb0\x2e\x2b\xa5\xda\x83\xfa\x0a\xb3\x50\xb5\x70\x71\x49\x85\x5e\x82\xa6\xad\x18\x6a\x28\x23\x75\x06\xaf\x7f\xd3\x38\x66\xc3\xa1\x3c\xc9\x9a\xe6\x4c\x18\x95\xcc\xa9\x81\x4a\x2d\xbd\x52\xcb\xac\xe4\x52\xd3\x64\x6d\x73\x22\x8d\x4a\x6d\x87\xfe\xe6\x8d\x21\x82\x3b\xcf\xcf\xea\x96\xe5\x10\xbc\x8f\xec\x63\x82\xd8\x2a\xe9\x68\x92\x13\x36\xfb\xf9\x13\x9f\x18\xf6\xc2\xb5\x45\xc2\xb4\x46\xa3\x1c\x48\xbb\xee\xd1\x2d\x15\xcd\x16\x5b\x03\x64\xbe\x14\x35\x47\x43\x48\x43\xbf\x29\xba\xa1\x5a\xd6\xb4\x64\xa1\xdf\x14\x45\x50\x2d\x6b\x99\x92\xd0\x6f\x8a\xd2\x27\x6b\x2b\xaf\xad\xed\x73\x65\xc5\xe2\xf3\x80\x59\x53\xc7\xac\xe9\xc1\xac\x35\x07\xb3\x76\x21\x66\x8d\x45\x30\x6b\xe9\x98\xb5\x3c\x98\xb5\xe7\x60\xd6\x28\xc4\xac\xb9\x08\x66\x6d\x1d\xb3\xb6\x07\xb3\xc6\x1c\xcc\x9a\x85\x98\xb5\xe6\x62\x66\xd3\xea\x87\x29\x18\x24\x65\x79\x90\x63\xe3\x2b\xb0\x8c\xbc\x61\xf6\x07\x98\x42\xde\xb4\x5e\x93\xc1\xcf\x5b\xd6\x6b\xd2\xf3\xbc\x3d\x5f\xc5\xe0\x5c\x32\xc5\x3c\x0d\xd6\x48\x8d\x8a\xfc\xca\x02\x31\x8f\x0b\x8e\x35\x62\x9e\x17\xec\x65\xa2\x1d\x18\xe4\x3a\x59\x2e\x35\xe9\x62\x99\x70\xe4\x9a\x7e\xe4\xec\x65\x62\x21\x67\xad\x14\x1d\xb9\xc6\x02\xc8\xb5\x14\xe4\x5a\x7e\xe4\xec\x95\x62\x21\x67\x2d\x16\x1d\xb9\xe6\x02\xc8\xb5\x15\xe4\xda\x7e\xe4\xec\xc5\x62\x21\x67\xad\x17\x1d\xb9\xd6\x7c\xe4\x2c\x42\xc5\x3c\x08\xba\x53\xd0\xa0\x3b\xab\xbd\x6c\x4c\x99\xc5\x5c\x3f\xfa\x7e\x69\x2f\x24\x4b\x90\x69\xfb\x25\x19\x76\x0e\xed\xa1\xd6\x46\x77\xbd\xdd\x92\x7a\xda\x65\xbf\xaa\x94\x4b\x22\x42\xee\xc9\x98\x9f\x2f\x53\x9d\x2e\x65\x2c\x93\x19\x8a\xc8\x0e\x3b\x0a\x86\x58\xd5\xa1\xaa\xa0\xfe\x0b\x5f\x07\x93\xa9\x7a\x74\xd5\x3e\xf3\x59\xa6\x70\x73\x7c\x9d\x2b\xd7\xc4\x6b\x3b\x7b\xc7\x6b\xec\x90\x50\x9b\x70\x4b\xf9\xcf\xf8\xa6\x8e\x86\xa3\x73\x55\x56\x97\x80\xa6\xe3\x80\xe0\x74\x9d\x23\x13\x10\x13\xe0\x6b\xb2\x29\x0f\x2c\xa9\x02\xee\x3b\x74\xbe\x1f\x69\x5c\xdb\xd7\x78\x3c\xc5\x69\x6d\x67\x4f\xb9\x3c\xa7\x9a\x6f\xf6\xc4\x4c\x4e\x54\x14\xb8\x69\x18\xb5\x37\x00\xcb\x0f\xed\x86\xbf\xb7\xd1\xac\x73\x1b\x81\xde\x06\xf5\x4b\x94\x56\x02\xbd\x0d\x30\x03\x61\xe6\x01\xbd\x8d\x8d\x3a\xb3\x1a\xe8\x75\x1b\xb7\x67\xf5\x8d\xe6\x9d\x8d\x3c\xbe\xa9\x75\xc7\x57\xb6\xc1\xf8\x83\x59\x53\x28\x0b\xf0\x47\xc4\x0c\x1e\x08\x9e\x68\x98\x4c\xa6\x49\x0c\x81\xff\xff\x66\x12\xa8\xa0\xad\x71\x34\x58\x63\x95\xbe\x7c\x51\x2f\xe4\x45\xc2\x82\x07\x34\xd1\x08\x32\x2c\x6d\x33\x02\xd5\x61\x9a\x7c\xfe\x25\x49\x43\xf0\x67\x17\x65\xc4\x1b\x03\xce\x6c\x04\x86\x67\xb0\xd6\x76\xf8\xd5\x8c\x84\xec\xfc\x6c\x62\x89\xc1\x5d\xac\x4f\x98\x86\xfe\xe9\x43\x3e\xda\x02\x58\x38\x1e\xae\x91\x07\xbb\x13\xdd\x8e\x28\x40\x1f\xe7\x58\x97\xa8\x1f\xf7\x2e\xa7\x6f\x76\x5f\xca\xab\x22\xfa\x5c\x70\x17\x35\xc8\xa8\x41\x20\x59\xdb\xfc\x3e\x2a\xc7\x93\xe9\x38\xc8\xfd\xac\x54\x44\x2c\xff\x3d\x66\x71\x85\xb8\x7e\x15\x9c\x2a\x04\x6f\x06\x95\x61\xf4\x1f\xbc\xc6\x83\x85\xf6\x50\x07\xd5\x9a\xad\x2d\x34\x88\xf2\x6c\x79\x0e\xcc\xe8\xd2\x01\x72\xff\xe7\x3b\x40\xfc\xb8\xf7\xae\xff\xf1\xd7\x97\x87\x47\x07\x1f\x0f\x0e\x77\xf7\xd0\x0e\x44\x4d\xc8\x83\x38\x47\x29\x9e\xa6\x38\xc3\x71\x1e\xc5\xe7\x5c\x1f\x44\x28\x77\x92\x84\xda\x38\x38\xc1\xee\xee\x95\x02\xcb\x18\xbf\x0b\xac\x7e\xdb\x67\x18\x1a\x89\x47\x37\xed\xb9\x6e\xff\xb4\x69\x86\xa5\x0c\x9e\xe7\xb3\x14\xac\x18\x44\x56\x1c\xad\x8c\x5e\x41\xe9\xa7\x58\x28\x64\x4e\xd0\xc9\x05\x26\xb3\x90\x27\x68\xc6\x3c\x27\x08\x5b\x41\xa4\x30\x00\xd7\x81\xae\x2b\x4f\xc3\xd1\x79\x0f\x28\x9c\x63\xbc\xac\x09\x08\x36\xce\xb0\xc1\x65\x14\x3e\xa3\xcd\x28\x2e\x98\x1d\x47\x27\xa8\x11\xa8\xd2\x03\x42\x93\xa4\x0b\x9f\xf1\xcd\x9a\xbb\x30\xf7\x90\x1d\x8e\xce\x51\xed\x10\x1a\x0a\xc6\xcb\x50\x69\xe8\x1a\xc5\xb2\x43\x61\xb4\xc6\x23\xc0\xd2\xd9\xbd\x25\x54\xc3\xbb\x48\x68\x67\x58\xdc\x31\x72\x32\x8a\x86\x45\x25\x2c\x79\xc7\x2a\x82\x34\x99\x47\x15\x53\xf8\x35\xf3\x1a\x95\x31\xf6\xe8\x88\x27\x69\x8d\x5d\xa0\xc1\x58\xd6\xd1\xef\x28\xba\xec\xa1\xe8\x52\xf2\xd6\x5b\xdd\xdc\x40\x9f\x7e\x1d\x56\x4f\x8b\x6a\xc5\xc4\x1f\x2b\x5e\x84\x4c\x92\x43\xab\xb3\xfb\x65\x7d\x21\xbb\xe5\x27\x43\xfe\x2c\x4b\x5e\xfe\x55\xfa\x48\x5e\x4a\x89\x7b\x24\xaf\x5d\x7c\x7f\xe4\x25\x60\xdd\x89\xbc\x74\x86\xbe\x18\x79\xed\xc7\x51\x1e\x05\xe3\xe8\x3f\x38\x43\x01\x8a\xf1\xd5\xf8\x86\xe1\x18\xb2\x21\x29\x43\x5a\x7c\x8f\xb9\x1e\x25\xe9\xe4\x20\x09\x31\xda\xa3\x2e\x7e\x10\xad\x5b\x72\xc2\x24\x55\x09\x17\x4c\xbb\xc1\x43\x92\x93\xb1\xd8\x92\x3c\x54\xf9\x47\x24\xe1\x7b\x24\xb1\x9a\xfd\xc1\xc5\x43\x16\x26\xbe\x28\x8e\x34\x4b\x1b\x31\x5d\x0a\xed\xd8\x8a\xf6\x9d\xe9\x94\x10\x05\x0c\x19\xcf\x2b\xaf\x2b\x7d\x29\x69\x8e\xce\xb9\xca\x7d\x38\x3a\xe7\x9b\x27\x25\x47\x4b\x11\x90\x13\x61\x9d\x9a\xf6\x27\xe9\x84\xce\x7d\xe0\xd0\x54\x51\x42\x97\x64\xb5\x2d\x49\xec\xb9\xa3\x20\x35\xa9\x01\xb3\x19\x97\xee\x21\xa2\xc4\x4e\x5d\x11\x1c\x5d\x00\x0b\xa3\x5a\x95\xe5\x03\xf6\x3a\x3a\x73\x86\xf0\x17\x99\x68\x0b\x94\x83\xf7\x40\x40\x6c\x31\x3a\x50\xd4\xe7\x38\x2d\x61\x4e\x45\xad\x8c\xc2\x20\x0f\xd0\x00\x84\x35\xad\x80\x47\x7e\x03\xc0\x34\x97\x8a\xee\x54\x2e\xc0\xbe\xc7\x29\xcc\xe6\x30\x89\x87\x29\xce\xf1\x2a\x1b\x91\x71\x72\xae\x73\x69\x71\xb3\x76\x54\x79\xc0\x21\x0e\x09\x60\x4e\x5d\x70\x18\x73\xc1\xa1\xc2\x5b\x70\x58\x69\x3b\xfc\x92\x31\x67\x1d\x02\x96\xa9\x74\x09\x93\xe0\xad\xb0\x26\x94\x06\x4a\x6d\x69\x0a\xaf\x12\xd0\x68\x54\x2f\x16\xf1\x21\x8a\xcf\xef\x89\xb1\xc8\x41\xd8\xe6\x74\xc2\xda\xa8\x2d\x91\x76\x97\x0c\x7a\x29\x53\xf1\x8a\xa3\x5f\x40\x6b\xac\x6c\xcf\x88\x8b\x4b\xc7\xd0\xcd\x49\x44\xe6\x01\xf6\x87\x52\x42\x00\x1f\xd4\xca\x0e\xea\xa2\x8d\xa1\xe0\x32\x88\xc6\xc1\x60\x8c\xa9\x79\x62\xe6\xdd\x2b\x3f\xf2\xae\x54\xa1\xaf\x97\x51\xcc\x76\xc3\xc2\xad\x8b\x81\x36\x76\x9e\x77\x49\xce\x1c\xcd\x69\x70\x37\x0a\x4a\xee\x23\x28\xca\x10\x1e\x8d\xf0\x30\x8f\x2e\xf1\xf8\x06\x05\x28\xc4\x59\x9e\xce\xe0\xb9\x8e\x52\x1c\x84\xab\x49\x3c\xc4\x25\x77\x9e\xb2\x84\x0c\x88\x3c\x1c\x39\x53\xf0\x5f\x83\xa8\xf9\x88\x9a\xfc\xaf\x54\x9d\xca\x2b\x41\xd4\x2c\xb3\x14\x78\xe1\xd2\x6b\xe1\xa5\x9c\x94\x19\x94\xd1\x0a\x44\xa3\xa2\xea\x45\xcb\x46\x2d\x65\x5f\x15\x95\x67\xd1\x10\x67\x8a\xe0\xc8\xd6\x1d\x0e\x59\x9c\x4f\xce\xbb\x5f\x2a\x93\xe1\x5a\x83\x76\xe5\x12\x5a\xf7\x66\x6b\x6b\xbd\xdd\xd2\xbe\x51\x05\x8b\xf3\x93\x21\x1c\xf5\x50\x53\xff\xac\x0b\xc8\x3d\xd4\x2a\x77\xd4\xca\x9c\xfa\xfc\xa0\x94\x36\xdf\xbb\x4a\xf9\x09\x87\x0d\xf8\xc9\x05\x56\x04\x06\x96\xfc\x2d\x40\x17\xa0\x27\x27\x22\x68\xa9\x45\xc9\x45\xdc\x9d\x98\xe3\x04\x91\x1b\xf8\x12\x27\x12\x15\x5b\xe3\x8e\x4e\x7d\xb5\xa3\x15\x33\x0b\xb7\xd5\xaf\xc6\xed\x80\x25\xfd\x16\xac\x37\xad\x66\xcf\xef\x55\x85\xe4\x7a\xcc\xf0\x18\x0f\x73\xda\xf2\x71\x9e\x06\x39\x3e\xbf\xa9\x39\x2d\xbb\x15\x35\x3a\x48\x94\xdb\x68\x89\xb2\xd6\x25\x97\x45\x1b\x9b\x8c\xf7\x41\x96\x11\x4e\xf1\x22\xc8\x70\x68\x3a\xdc\xd1\x3f\x5e\x7b\x36\x06\xe2\x18\xa7\x70\x16\x23\xbb\x9b\x07\x42\xd1\xfa\x36\xb3\x4f\xb2\xbb\x1b\x9f\xe5\xba\x03\x0f\x46\x38\x2a\x13\xb3\x2f\x92\x9c\x86\xa9\x32\x4a\x9a\x7b\x8c\xc5\xc5\x14\xc5\xa9\x8e\x0a\x2f\xa8\x78\xff\xea\xb6\x45\x29\xa3\xe7\x9e\xd5\x4d\xe3\x8a\x6a\x31\x2c\xf5\x5b\xaf\x3a\x9a\x77\xfb\xe5\x9c\x16\x25\xb5\xa0\xe3\xf1\xb6\xa6\xc4\x1a\x2b\xba\x56\xd4\xb5\xde\x59\x9e\xe2\x60\x72\x07\xe5\x37\x48\x4b\x4c\x57\xad\xda\xe2\xb7\x5b\xab\x83\x88\x1a\x76\xeb\x07\x19\x2a\x84\x40\xfc\x6d\x45\x91\xdd\x44\xb5\x76\xcb\xd2\x63\xeb\x9a\xe1\x63\xc0\xd5\xd0\x0f\x9b\x2f\x8b\x7c\x42\x5e\x3a\x76\x53\xc7\x1e\xca\x45\xc2\x69\x90\xc2\x59\xcb\x21\x10\xda\xdb\x18\x1c\x6b\xa4\x75\x18\x17\x12\x9f\x3c\x59\x1a\x8d\x67\xd9\xc5\x52\xa9\xed\x8c\x02\xf1\x6f\x68\x62\xb0\x7b\xa8\x59\x6a\xb2\xe1\x74\x0b\x99\x78\xa7\xc6\xf5\xb0\xa1\x74\xff\x38\xa1\xe7\xe0\xfe\x1a\xfb\xf1\xfb\x6d\x59\x12\xd2\xdc\x79\xa0\xb6\x8b\x8c\xf4\xf6\x94\xbb\x5c\x76\x00\xb7\xdc\x8a\xd9\x7b\x55\x65\x5e\xac\xe4\x57\x35\x9a\x13\x9e\xc1\x4d\x8a\xda\x0b\xec\xa6\x3b\x7a\xdf\xdc\xca\x43\x33\xff\x8f\xbc\x25\x00\xda\xff\x6a\xdb\x21\x9f\x40\xbe\xdb\x81\xfe\xb6\xff\xa2\x6f\xa9\xb1\x39\x13\x8a\x2e\xa9\x97\xcf\x62\x5a\x6b\x09\xa5\x48\xb1\x28\xaa\x72\x55\x94\x52\x6d\x51\x6d\xb5\x98\x5b\x79\x70\xfa\x8b\xcf\xad\xd4\x21\x57\x9e\x5b\xa7\xca\xb8\xdc\xdc\x8a\xaa\x77\x9b\xdb\x22\x55\x71\xd9\x3b\x88\xaf\x3d\xb7\xf7\x30\x73\x05\x2b\x63\xee\xd4\x99\x0a\x57\xff\x7c\xd1\xcd\x48\x68\x88\x87\x0e\xe9\x8f\x19\x9c\x5f\xa2\x6d\x14\x5d\x6a\xf3\x56\x6a\x87\x61\x3b\x2f\x30\x6e\xd4\xbf\x08\xa2\x18\xb2\xc3\x14\x5f\xf1\xbe\x00\x23\x87\x8f\x7c\x30\xd0\x76\x41\xfc\x02\x4b\x55\xa7\x6d\x3e\xa4\xb6\x4d\x25\xea\x60\xc9\x06\x8d\xcd\x45\x5c\xc4\x3e\x2f\xe6\x3d\x2f\xfa\x7c\x17\x31\x8f\x4f\x6a\x33\xda\x9c\x92\xde\xbd\xe8\xbb\xb6\x2d\xab\x01\xd2\xc4\x7b\x11\xab\x35\xe7\x3a\x10\x4a\xc0\x41\xce\xec\xdc\xd7\x8c\x2a\x26\x04\x83\x96\xa9\xd4\x25\x14\x2a\x4c\x08\x4b\xb8\xec\x85\x0c\x8e\xe9\xba\x73\x61\xb6\x6d\x04\x06\xfb\x79\x75\x81\x59\x7e\x01\x8a\x1c\x84\x64\xcd\xe6\x62\xe6\x58\x2b\x8e\x62\x88\x1a\x2c\x61\xae\x28\xa4\x7e\x11\x90\x01\x11\xb0\x17\xfe\x1d\x86\x94\xac\xcd\x81\x50\x1d\x5a\x0e\x03\x3a\x0c\xb7\x81\xb8\xcb\xa1\xd6\xb8\x61\x51\x97\x93\xcb\xa6\x58\x0a\xc3\x42\xdd\x23\x5e\xb9\x2c\x7d\x7f\x3d\x3c\x82\xd3\x34\xa3\x2f\xa3\xc0\x75\x92\x42\x47\xa4\x76\xbc\x8e\xb4\x8e\xd4\x65\x83\xd6\xe9\x81\x35\xaf\x7a\xf7\xd4\xcc\x91\xb4\x11\x3a\xc2\x13\x0c\x12\x3c\xec\xb4\x74\x9e\xd9\xfd\x15\xa8\x00\xc0\x25\x1e\x5e\x1b\x75\xb9\xd4\x8b\x2f\x29\x27\xd8\x66\x5c\x14\x52\xa5\xd7\x38\xba\xc2\x60\xd2\x87\xb7\xe6\xa6\x54\x62\x61\x86\xb8\xd4\xc2\x14\x5b\xd5\xe3\xc2\x94\x70\x1f\x17\xa6\x7f\x61\x2e\xbc\x0e\xc0\x58\xfa\x22\xca\x2a\xaf\x02\x1b\x07\x46\xb4\xc0\x20\x7e\x3d\x3c\x72\xaf\x6f\xd5\x21\xce\x5a\xdf\x7a\x8d\x8a\xec\xc4\x46\xe8\x44\x8e\xc5\x00\x0f\x93\x09\x5b\x16\x64\xd1\x47\xc9\x2c\x2b\xc9\x17\xc4\xe8\x94\x5e\xf9\x82\x5a\x78\x0f\x6a\x5e\xb4\x2b\xf8\x73\x81\xc4\xc3\x85\x1f\x87\xf7\xff\x45\x92\x64\x18\x4d\xa2\x6b\x22\xcf\xd8\x5d\x03\x17\x55\x4b\xde\x52\xa8\x8d\xc9\x53\x4e\xda\x4a\x2e\x41\xd0\x24\x87\x98\x6c\x36\xc8\xf0\xff\xcc\x70\x9c\xbb\xf4\x0c\x9a\x78\x26\xec\x48\x4b\x45\x17\x51\x11\x31\x66\x61\x9e\x3a\x9f\x6c\x4b\x0e\x64\x5c\x41\x91\xe4\x2a\x72\x85\x46\x42\x94\xc0\xa4\xa1\x7c\x74\x86\x7e\xdb\xa6\x95\x4e\xa3\xc2\x48\x26\xca\x03\x37\xe2\x7e\xd1\x97\x15\xcc\x00\x27\xba\x50\xfa\x3e\x08\x43\x32\x6d\xa5\x34\x1f\x53\x48\xc1\xd5\x5f\xa3\xff\x16\xe9\x3d\xde\xbf\xe9\x1f\xa3\xff\x7b\x63\x7d\x13\x4d\x59\x03\x19\x53\xf3\xf9\xe1\xbf\xff\x3c\xcc\x36\x41\xda\x9d\x06\xe1\x1a\x7f\x2a\x94\x70\xdf\x07\xfc\x32\x7a\x96\xf1\x5c\x04\x22\x4c\x0a\xb3\xa1\x86\x28\xd4\x02\x9f\x8a\x76\x5a\x00\x5b\xbd\x8b\x26\xa8\xb9\x6b\xb9\xd5\x7c\x2c\x2e\x94\x72\x2b\x09\x90\x32\x11\x95\x93\x8c\x0f\xc4\xe5\xfc\x2a\x47\x64\x71\xba\x12\x43\x4c\x7e\xc9\x0b\xb6\xba\x19\x4e\x4b\xbf\x6e\x0e\x42\xf3\x7a\x6d\x41\x96\x22\x2a\xd1\xa8\x28\xdb\xca\xa8\x79\x22\x8c\xcc\xe2\x5c\xd0\x12\xf8\x57\x59\x30\x63\x46\xcb\x16\x48\xfa\x62\x95\x66\x09\x17\x91\x58\xfe\x6e\x14\x70\x34\x4a\x2f\x85\x78\xab\x56\xcc\x07\x9a\x99\x0e\x3e\x12\x92\x81\x58\x29\x1a\x0e\xd2\xe9\xc7\x7a\xcf\x3c\x7d\xac\xf7\xe0\xdf\xa3\xbd\x9d\x87\x57\x11\x4a\x59\x61\x2c\x05\xc9\x9b\xf4\x06\x51\x84\x56\x9c\xc1\x03\x54\xc8\x34\x9f\x80\xf2\xa6\x40\x3e\xd5\xf0\x42\xdb\xd2\xc8\x9e\x9f\xe8\x55\xc0\x75\x1d\x1b\x87\x21\xc2\x4e\x18\x3a\x3b\x0f\xf3\x3b\x4c\xe2\x61\x90\x73\x88\x55\xd4\x2b\x1f\xe2\xa9\x60\x27\xb0\x5a\x2f\x82\x90\x86\x37\x62\x6b\xf4\x6b\xf1\x98\x59\x6c\x71\x99\xaf\xc7\x0e\xa0\xf1\x52\x37\xee\x50\xd2\xe4\x07\x0e\x26\xf0\x0a\xe7\x5a\xf2\x67\x65\x0d\x43\xa0\x5a\x34\x0e\xb2\x1c\x9e\xe7\x2e\x69\x29\x4c\x9f\xd6\xf4\xd5\xbc\x8a\x9a\xcb\xd4\xd9\xee\x8c\xb9\xc5\x39\xbc\xa5\xa9\x38\xe1\x25\x1d\x09\x6c\xdb\x5c\x82\xb2\xa4\xa2\xeb\x59\x48\xed\xff\xed\x2e\x8e\x3c\x1e\x10\xde\xeb\x06\xd7\x55\x43\xe1\xbd\xd1\xbd\x7a\x13\x1c\x24\x21\xbe\xa5\x4a\xc1\x13\x71\x5e\xa3\x77\x26\x70\xba\x50\x3a\xd6\x7f\xd1\xf7\x82\x61\xd3\x78\x2b\xe8\x8e\x00\x33\x65\x12\x1b\x24\xac\x05\x2f\xe5\x83\x87\x02\x1b\x07\xc5\xd4\x52\xa7\x7c\x82\x2b\xe0\x56\xd7\x8f\x77\xb4\x6d\xbb\x89\x5b\xdd\xcd\xa1\xbc\x19\x21\x9d\x74\xed\x5b\x5f\xb1\x55\x2c\xb4\x18\xf4\x07\x47\xd3\xad\x49\x6d\x3f\x57\x90\xb3\x49\xe7\x75\x55\xa8\xa6\xcb\x25\x9f\x27\x60\x29\xea\xc6\x5c\x4e\xaa\x75\x60\xb0\xec\x4e\xb9\x68\xae\x5b\x9c\xb8\x4f\x15\x04\x22\x6c\x92\xa0\xc6\x80\x33\xb9\x71\x3f\xe3\x3c\x17\xac\xff\x58\xdc\xb4\x6e\xcd\xb2\x8c\x7e\x5c\xaf\xd6\xba\xd0\xad\xb8\xce\x3a\x6f\x30\x9e\xa2\x20\x47\x63\x4c\xb8\x61\x12\x73\x92\x67\x29\x54\xa8\x99\x28\x6c\xde\xc0\x77\x2d\x0e\xc6\xcf\x3f\x93\x28\xa6\xf6\xa3\xec\x0c\x6f\x06\x58\xfc\x9b\xa3\x0a\x9d\x2d\x05\x6f\x4a\x33\x13\x30\x4c\xa6\xc7\xdf\xe8\x12\xfd\xf0\x83\x5b\xe3\x6e\x45\x22\x79\xbf\x88\x06\x43\xa2\xa2\xeb\x4f\xfc\x27\x75\xab\xdd\x92\x97\xd6\x5e\xa1\x54\x0a\x28\xc2\x88\x9a\xbd\xb2\x09\x98\xb7\x75\x7f\x84\xbb\xbe\x4e\x8e\x33\x76\x16\x35\xc1\x32\x40\x52\x67\xc6\x5f\x20\x79\xc3\xef\x62\x45\x04\xbf\xa0\xa7\x26\x82\xce\x63\xf3\x42\x17\xf4\x2a\x80\xfb\x5b\x3d\x0f\x88\xb0\xdd\x12\x88\x7b\x45\xc3\x4d\x25\x21\x57\x4b\xc5\x56\x7e\x77\xb6\x57\x00\x3b\x3b\x29\x66\x14\x59\xa8\xa0\x61\x32\x1e\x63\x4a\xeb\xc9\x88\xef\xff\x20\x63\x62\xc8\x3f\x38\x5f\xca\x50\x24\x51\xc5\xab\x9d\x6c\x93\x69\x70\xa5\xbc\xf2\xb9\x37\xba\x5d\x42\xa8\x03\xbe\x10\x49\x4a\x02\x90\x37\x8b\x90\xac\x82\xf1\x3e\x0a\x82\xac\x4c\x97\x59\xae\x0f\x56\x16\x8c\x69\x37\xe0\x07\xc0\x03\x85\x64\x40\xc3\x93\xe2\x34\xba\xa4\x72\x09\x67\x16\x3e\x98\xfc\xca\x54\x0a\xfd\x8a\xed\xa0\x33\x28\x6b\x39\x39\x66\xc1\x2e\x16\x4b\x34\xc3\x0b\x3c\x59\x18\xb4\x5b\xe0\x64\xaa\x72\x30\xa4\x1e\x29\x20\x7d\x50\x68\x96\xcb\x5b\x99\x05\x93\x1e\x6e\xa8\x68\xc5\xdf\x9a\xe2\xd7\x30\x89\x2f\x71\x9a\x6b\x62\x2c\x4d\x48\xc8\x0d\x2b\xc1\x02\x94\x5a\x01\xce\x73\x89\x7d\x4f\x2b\xea\x8e\xb1\xe2\x65\x69\x3b\x99\xf9\xae\x59\x3a\x8a\x65\xce\x7d\xc2\x27\x4a\xc5\xc9\xb0\x1e\x0d\x62\x91\x33\x6c\x9a\x64\x59\x34\x18\xe3\xc2\x95\x7d\x77\x97\x28\x39\x60\xb6\x8d\x28\xfd\xc6\x4f\xea\xbf\xdb\x60\x90\xd0\xb1\x93\x55\xde\x53\x7e\x4b\x3f\x29\x77\xad\xcf\xf8\xa6\xa7\xfb\x53\xb9\xcb\x19\x2e\x56\x9e\x52\x64\x9d\xf7\xe0\xef\x79\x25\xc5\xa2\xed\xd9\x9e\x60\x9e\x2a\x4c\x9a\x37\x8d\x13\x3c\xa5\x85\x90\xaf\x1f\xb0\xdf\xf4\x8f\x37\x3d\x35\x14\xfe\xdf\xf1\x14\x11\xeb\x49\x01\x4b\xdf\xad\x1d\x4e\x71\x7c\x7c\xfc\xd6\xae\x57\xc5\x19\x4d\x25\x05\xa7\x74\x36\x89\xae\xf7\x63\xbd\x58\x15\xcb\x24\xba\xc2\xb3\xca\x4b\xdc\xbb\x96\x6c\x46\x63\x78\x19\x7a\x78\x0d\x39\xea\x15\x81\xe7\xd6\xb9\xdc\x9b\x03\x36\x11\xf8\x1d\x8d\x22\x73\xed\x17\x42\x82\x2c\x6a\x19\x4d\xa1\x06\x49\xfc\xb0\xf4\xca\x94\x58\xc7\x09\x7d\x63\xb0\x48\x9c\xa2\x28\x43\xe6\xcd\xe9\xe2\x4b\x9d\x76\x41\x5c\xeb\xb1\x35\xbf\x82\x96\x96\x3c\x2e\x19\xce\x0a\x6b\x79\x42\x73\x2b\x79\x7d\x40\xe6\x54\xf3\xd0\x6e\x01\xa1\xf2\xaa\x2a\xb1\x8a\x31\xf2\xd8\x5d\xc9\x02\xe8\xcb\x17\x4a\xbe\xb2\xca\x1a\x9f\xcd\x1b\x7e\x2e\x96\xf5\xcb\xd9\xf2\x50\xd2\x2b\x77\x6b\x42\xdb\x85\x8b\x13\xf1\xb3\xe8\xee\x84\x0d\x88\x8b\x7a\xfd\xcd\xb0\x4a\x22\xc3\x34\x6c\x7e\xfa\x08\xcf\xbb\x4c\x11\x0b\x34\xf0\xaf\xce\x98\x37\xb4\x3a\x4c\x26\xd3\x20\x87\xbd\xa9\xfc\x8a\x55\x77\x14\x63\x0f\x54\x64\xaa\xf2\x9b\xaa\x6b\xa5\x2e\x86\xe0\x7d\xda\x20\x26\x14\x83\x63\xbe\x0e\x3c\x14\xaf\x50\xa1\x97\xa7\xea\x0b\x41\x54\x28\xcb\xb6\x8b\x35\x3c\x6a\x0c\x2b\x6d\xad\xca\x2f\x8e\x58\x97\x44\x9a\x36\xca\x93\x77\x8e\x23\x32\xf4\xd6\x3a\x20\x93\xc2\x6e\xb5\xcd\x95\x12\x7b\xc7\xba\x22\x38\x6d\x5c\x6f\xb4\xbb\xcd\xee\x70\x13\xd2\x2b\x74\x37\xba\x9d\x8d\xd1\xc6\xe8\x6c\x99\x6b\xfc\x01\x2e\x7f\x90\x3d\x70\x1f\x56\xe7\x35\xef\x1b\x01\xf7\x51\x4f\xd4\x94\x5c\x8e\x86\xe4\x59\x68\x7f\x2c\x5a\x64\xaa\x1f\x54\x75\xe1\x93\x2f\x13\x49\xa0\x77\x58\x31\x7a\xe4\x08\xbe\xa6\x17\x5a\xc6\xf7\x1f\x46\xc2\x16\x68\x8d\xb5\x37\x0d\xd2\x0c\xd7\xb4\x95\x5a\x78\xf1\x99\x66\x9a\x5a\x49\x56\x74\x79\x45\x90\xd2\x88\x86\x41\x2b\x5e\x75\x94\x4a\x2c\x64\x0a\x14\xb5\x05\xf0\xf8\x85\x9f\x72\xe2\x66\xb9\x29\x4c\x60\x27\x38\xcb\xa9\x49\x45\x30\xb6\x17\xa8\x01\xf1\xb4\x71\x86\xb6\xb7\x91\x5c\x7e\xe8\x87\x1f\xcc\x56\x4f\x9b\xac\x0c\x5f\x96\x4e\xf5\xd6\xde\x35\xbd\x14\xb1\x5a\xd5\x38\x8b\xb5\xf2\x8d\xb6\x98\x89\x50\xab\x8e\x3a\xcb\x3e\xcb\xbc\xe4\x92\x1d\xfb\xe1\x7e\x49\x02\x30\x0a\x5b\x90\xa7\x00\xba\x61\xdd\x82\x6b\x65\xb5\x7b\xa2\x66\xb7\x0c\x83\x70\x9e\xa4\x3c\x87\xa6\xba\x72\x90\x31\xe4\xf7\x72\x77\x4f\x7c\xfd\x5d\xa5\xc1\x74\x0a\xc2\x66\x90\x33\x44\x54\x75\x0c\x0a\xc8\x4e\x9f\x29\x1e\x6f\xf3\x16\xae\xe9\x64\x64\x39\xcb\x49\x57\x23\xd7\xa7\xf2\xfe\x29\x77\xb8\x36\x12\x02\x10\x3f\x16\x14\x1d\x04\xb8\xd2\x41\xed\x37\xeb\x36\xf5\x38\x8d\x43\x4a\x41\xe2\x54\x22\x6f\x88\x5c\x47\xae\xc2\x28\x56\x86\x53\x24\x60\xd4\xb3\xe4\x36\x75\xb6\xe7\xc4\xbd\x62\x97\x19\x64\x16\x99\x2f\xdf\xdd\xdc\x60\xa5\x7e\xcb\x75\x46\x2a\xf0\x76\x67\xad\x53\x7b\x17\xf6\x5b\xfa\xb9\x7f\x47\x91\x64\xdc\x5b\xda\xce\x1f\x61\x43\x23\x2b\xd6\xad\xa5\xb1\xd7\x9a\x70\xf5\xb4\x95\x1b\x9a\xd7\xa7\xef\x00\xf7\x70\x2d\xca\x78\x39\x96\x66\xe2\xab\xb6\x5e\x17\xeb\xcf\x23\x1f\x14\x2a\x4e\x1c\x4e\xba\x7e\xa7\xda\x45\xc2\xf9\x54\x89\xe4\xb3\xe7\x30\x88\x57\xe2\x29\x24\xd2\x99\xc5\x15\xa5\xcd\xe5\x4f\x6b\x49\x29\xd2\xa7\x43\x44\x64\x60\xfd\xac\x2e\xa8\xf4\xa1\x6b\x12\x22\xdc\x2d\x9b\x30\x98\x81\x13\x61\xbf\xc2\x61\xd8\xb7\x4d\x95\xde\x6e\xbd\xfb\xb9\xb6\xf9\x1a\xa5\xb8\x86\xd2\x7c\x4f\xc8\x48\x74\x68\x2d\xba\x34\xbf\x2b\x1a\x46\x46\x1a\x46\x01\xaa\x4f\x94\x20\xe0\xae\xd5\x28\x23\xb4\x88\xb2\x18\x7b\x65\x96\x54\xb4\x87\xa6\xcd\xb6\x59\x54\xd1\x22\x12\xda\x1a\xd9\x87\xb4\xdb\x2a\xa7\x16\x76\xa7\x9d\x89\x19\x12\xfa\x31\xe3\xca\xe7\xe1\x36\x24\x75\xb6\xc5\x9e\x64\x5c\x65\x29\x8f\x90\x01\xfd\x7b\xdd\x99\x8c\x20\x30\x22\x7a\xf4\x57\xdb\x8d\x64\xbc\xea\xf9\x6c\x99\xfb\xf2\x3b\xd8\x32\xa7\x42\x16\xa7\x41\xf1\xef\x5f\x9c\x45\xdf\x37\x8e\xe6\x46\x74\xaf\x7b\x88\x37\x8a\x82\x27\xe4\xc1\x43\x6f\x23\x4c\xfb\xa0\xa8\x19\xd4\x65\xa5\x83\x55\xf7\x06\x76\xe3\x0e\x07\x58\x15\x71\xc9\x5b\x1c\x8d\x31\xa6\x61\x6d\x11\xea\xc4\x69\x7b\x96\x16\xfa\x91\x0e\x88\xdc\x8b\x14\x76\xa3\xa9\x7d\x5c\x3b\x83\x68\x61\x11\xc5\x8c\x93\xc5\x99\x43\x65\x5c\xde\x80\xb6\x29\xc8\xb2\xd9\x04\x87\xfa\xed\x64\x30\x4e\x71\x10\xde\x28\xbb\x9c\x76\x3a\x9b\xc5\x34\x73\x66\xa9\x50\x6a\xd5\x58\xa0\x87\x95\x55\x3a\x3a\x11\x1e\x06\xe6\xf0\x69\x86\x17\xe0\xfd\x7e\xe5\xd1\x2c\xae\x0e\xed\x1b\x8b\xdb\x16\xf9\x53\x7d\xd2\x21\xa7\x08\x24\x16\x82\xda\xa2\x16\x4c\xc7\x52\x30\xa9\x6b\x89\x55\x77\xa4\x12\x67\x31\x72\xd4\x15\x59\x14\x2a\x87\x5b\xa6\x50\x36\x65\x2f\x5a\xeb\x32\xc4\xab\x2f\x65\x90\xfc\x6a\x52\xd7\xef\xa2\x0b\x95\x37\x6e\x9b\x8c\x72\x37\x2c\x9f\xc3\x11\x5c\xaf\xd0\x7f\xcb\xdc\xad\x94\x30\x01\x71\xde\xaf\xbc\xa1\x2d\x91\x16\xcb\x5e\xab\xec\x92\x66\xc0\x82\x80\x34\x4a\x56\xfb\xfe\xcf\x5c\xab\x30\x65\x21\x8b\xaa\x28\x6f\x79\x1d\x66\xf7\xc1\x1e\x40\xaa\x21\x0d\x01\xe8\x39\x2e\x2a\x32\x0c\x3b\xa6\x76\x1d\x51\xcc\xac\x88\x99\xd9\x01\x33\xe0\x39\xc7\x31\x18\x12\xcf\x01\x27\x22\xb0\x7b\xa0\xd1\x30\xe0\x73\x80\xd9\x6a\x05\x50\x80\x29\x32\xd5\x0e\xea\x76\xc0\xe0\x19\x3e\x70\x93\xdd\xfd\x11\x4a\x26\x11\x11\x25\xea\x28\xa0\x9f\xae\xa2\xf1\x18\x0d\xb0\x68\x31\x44\x69\x10\x87\xc9\x64\x7c\x73\x6f\xa7\x7f\x6a\xa8\xc1\x86\xaa\x8e\xf6\x7f\xae\xc3\xcc\x92\xe6\xbf\x1a\xa7\xa2\xf3\x1d\xda\x6c\x4a\x21\xce\x35\x7c\x8d\x87\xb3\x1c\xd7\x96\x78\x74\xac\xa5\x3a\xcb\xac\x52\x67\x86\x66\x3e\x29\xea\x9e\xc0\xd7\xd1\x12\x19\x15\xf2\x7f\x33\xf6\x9f\x76\xa6\xa6\x80\x54\x16\xc8\x69\xbb\xce\xe9\xb5\xce\x28\x8d\x6a\x48\x9d\x9e\x06\x74\xc2\xd9\x7c\xbb\xd5\xdb\x4f\xdc\x17\x50\x96\xf6\x99\xc2\xa8\x75\x3b\xeb\x66\x32\x76\xdb\x0d\x90\xae\x71\x65\x81\x5b\xa2\x0d\x0d\x8c\x4b\xf3\x2c\x48\x0d\xb0\x08\xbb\x27\xd3\x37\xb3\xc5\x74\xbb\xbc\xc6\x92\x90\x2b\xe3\x00\x98\xbb\x22\xeb\x12\xda\xcc\x0b\xdb\x07\x73\x6d\x4b\xb5\x2e\xf2\x57\x33\xa5\x3a\x4f\xa3\xcb\x47\x1a\xfd\x68\x69\xc2\xb5\x6c\xd6\x32\xef\xb4\xd3\x6b\xeb\x88\x09\x5d\xd5\x0e\xea\xf2\xd0\x2d\x24\xed\xbb\x6a\xc4\x5d\xea\x03\x4d\x3d\x1e\x32\x26\xcd\x99\x9f\xc1\x9f\xeb\x2a\x38\x8f\xc0\x76\x57\x75\xba\x23\x7c\x9d\xd0\xa7\xbb\xbf\xb9\x34\x61\x0f\xac\x5e\x7f\xb3\xfb\xf2\x16\x91\x6d\x96\x9b\x79\xba\xf6\x6c\x29\x1e\x72\x0e\x5c\xbc\xf9\x55\x54\xaa\x3b\xba\xed\x75\xc2\xf8\x1c\x8e\x7a\xca\xde\xad\x52\x4e\x65\xed\x3a\xf3\xff\xaa\xb6\x6b\xff\x51\xd4\xed\x85\xd2\xc3\x1f\x43\xb7\xf1\x5d\x69\xdd\x1d\x2b\xb2\x94\x1a\x7a\x89\x8f\xb9\x37\x10\xee\xc3\xb7\x5c\x47\xbf\x3f\xb8\xfe\x5b\x6e\x5a\x5f\x43\x7b\x61\xec\xbf\x49\xee\xd1\x0f\xbb\xe4\x1a\x02\x9c\x88\x34\x5c\x94\x51\x30\xa7\x83\x2b\xa4\x0f\xf6\x4c\xb7\x46\x8f\x5f\x2a\x95\x75\x69\xbf\xcc\xee\xac\xc1\x26\xac\xb5\xbf\xe6\x0a\xa8\xe0\x53\xe9\x6b\x8a\x17\xff\x35\x88\x1a\xb5\xc2\x9a\x17\xbd\x75\xe5\x06\xc0\x44\xe2\x20\xba\xa6\x69\x4e\xa0\xbc\x6b\x30\x95\xc3\x39\xb5\x0c\xd5\x60\x7b\x14\x30\xee\x13\xe3\x9d\x94\xcc\xdf\x86\x2f\xdf\xb7\xd6\xf9\x3b\x60\xd0\x7f\x5c\xe5\xb3\x8b\x27\x56\xd5\x3e\xdf\x8d\x3f\xde\x3b\x8e\xb6\xf6\xf9\xfe\x18\x78\x49\xe5\xf3\xd7\xe5\xe1\x5f\x59\x03\xfd\xd5\x37\x8c\xba\x66\x42\xe4\x3e\xc9\xdd\xd7\x0e\x52\x46\xc1\xee\xbf\x0c\x71\x6d\x20\xea\xc8\x96\xdb\x43\x0a\x35\xee\x9a\x4e\x90\xfd\x52\xd2\x91\xea\x29\x47\x59\x7e\xd1\xdb\xb3\xfa\x46\xeb\xbb\x4a\x1a\xfa\x95\x72\x84\xde\x4f\x6e\x50\x2d\x27\xa8\xbf\x13\x76\x7a\x50\x6f\x5a\x50\x0e\x6b\x6e\x62\x50\xd0\xe2\xca\x96\xc5\x4f\x3d\x39\xe8\x41\x90\x5f\xd4\x91\x3b\x45\xe8\x8f\x7f\xfb\xbf\x58\xab\x6f\x93\x61\x30\x46\xd3\x64\x7c\x33\x8a\xc6\x28\x19\x21\xba\x49\x32\xed\x02\x2f\xa6\x1f\xbd\x59\xf4\xff\x6d\xbd\xac\xd1\xbc\xce\x00\xc5\xfb\x97\xe4\xc3\xed\x73\x47\x14\x26\xc5\x5a\x74\xf0\xc9\x64\x9f\x60\xf0\x38\x1b\x10\x3a\x30\xd6\xcd\xcb\xb5\x69\x9a\xe4\x09\xf9\x82\xb6\xc9\xb1\xc7\xf8\xce\x6a\xa1\x6d\x14\xe3\x2b\xd2\x7c\x61\xfd\x78\x36\x1e\xbb\x17\xa6\x68\x5d\x7e\xb9\xd5\xa3\x46\xf9\x23\x99\xf2\x09\x2a\xa3\x9d\x9f\xa3\x91\x7f\x1b\x0d\xd2\x20\xbd\x29\xa7\xe8\x57\xb2\xba\xce\x01\x0b\x69\x5e\x99\xae\x96\x88\x3b\x7c\x4c\x82\x31\x8a\xe2\x0b\x9c\x46\x66\x9c\x5b\x33\x0e\x86\x99\x26\xd6\x8e\xbc\xea\x98\xf2\xb9\x61\x12\x79\x78\x66\x70\x27\x14\x8e\x15\x17\x41\xce\xb1\x62\x21\x51\xa8\x74\x66\x1f\x79\x0b\xe2\x26\x72\x61\x30\xb9\xc4\x69\x1a\x85\x38\x43\xef\xa9\xda\x26\xc2\x19\xdd\x51\xa6\x37\x28\x8a\x59\x62\x6c\xd9\xfc\x5c\xf8\x66\xae\x8b\x93\x6a\xd5\x6d\x01\xf0\x6e\x21\x16\xc9\xec\x1c\xdc\x9c\x50\xf2\xd6\x24\x2c\xbf\xb5\xa5\xea\x45\x17\xe1\x71\xd8\x43\x4b\x90\x82\x6c\xa9\x5e\xa2\x41\xf2\x67\x82\xf3\x8b\x24\x2c\x0a\x2c\xa0\x14\xbe\x75\x7d\xb8\x9d\x13\xf6\x91\xe2\xad\xd9\x98\xf3\x89\x74\x07\x7b\x9c\x06\x57\xb1\xf1\x5a\x61\x2a\x44\x4e\x91\xa9\x0a\x9d\x76\xd2\x3b\xb3\xf3\x09\x8e\x4d\x9b\x68\xb2\x3b\x15\xb4\x2c\xf8\x0f\x3b\xf3\xc9\xa2\x76\xa6\x0c\xbb\x49\x66\xd8\xc5\x1d\xdd\x22\xe1\x60\x9b\x3a\xb0\x78\xc2\x5b\xba\x08\xb2\xc3\xab\x98\x51\xf3\x4d\x6d\x89\x54\x5b\x5a\x16\x7e\x5d\xe4\x11\xb6\x50\x5e\x9e\xbc\x28\xc2\x9c\x56\x28\x9a\x4b\xad\xf8\xff\x93\xcd\xa6\x44\x8a\x8b\xa3\x7c\x2d\x20\x22\x2f\xdb\x01\x83\xf4\x7c\x46\x06\xd0\xee\xba\x95\x7f\xc2\x3d\x1a\xd2\xbf\x38\x5d\xca\x90\xe4\xda\x11\xd5\x43\xf3\x99\xa4\xeb\xec\x6f\xfe\xbe\x68\x0c\xdf\xbd\xa9\x30\x75\xfc\x08\xa7\x38\x1e\x12\xd0\x30\x94\x53\xec\x81\x4a\xbb\xcc\x84\x6b\x0b\x94\x7f\x03\xb1\x33\x86\x38\x78\xe0\x1e\x50\x7a\xa6\xf2\x38\xaa\x53\x9f\xc5\xb4\xe3\x30\x3f\x74\xc5\x59\x31\x5e\x77\xf8\xa0\xcb\xac\x3d\xb5\x65\x71\xe3\x47\x44\x71\x08\xa6\x07\x30\x6a\xcb\xdf\x23\x6f\xe3\x31\xd8\xd1\x36\x63\x73\xfc\x62\x64\x0e\x03\xa1\xa5\x0a\x59\x94\x09\x1f\x96\x8f\x8c\xed\xad\x97\x15\xb1\xe0\x15\xc2\xe7\xef\x74\xe2\x77\x93\x07\x2f\x5b\x99\x3e\x8a\xdc\xee\xdd\xf3\x82\x0e\x19\x17\x62\x69\x0f\x80\x74\xc0\x80\x3f\x0c\x51\x96\x4c\x30\x4d\xaf\x85\xae\x2e\x70\x8c\x6e\x92\x59\x2a\x8c\xfc\x03\x22\x99\x52\xd8\xf7\x18\x05\xf8\x8e\x5b\x94\xe9\x98\x5d\xb0\xd5\x20\xba\xb2\xd7\xd6\xac\x21\x11\x03\xbe\xc8\x66\x34\x0f\x83\x12\xb3\xd8\x4f\xa6\x44\xf0\x98\x4a\x19\x84\xc9\x1e\x0b\x0b\x36\x0a\x28\x50\x3f\x69\xd2\xcd\x04\xd4\x95\xf7\x38\x87\x6c\xed\xd1\x5d\xae\xa4\x34\xb1\xc8\x50\x43\x03\xda\xb5\xb7\xe8\x97\xbd\x7e\x45\xec\x46\x7e\x23\xf7\x2e\x98\x80\xbd\x45\x51\x25\xba\x9b\xca\x12\xe6\x7e\xaa\xc2\x5a\x76\x6f\x85\x64\xd6\x4e\xd5\x72\x67\x68\x5b\x69\x53\xff\x54\x22\x3b\x93\x6b\x33\xdc\x43\x57\x49\xbc\x94\x53\x39\x95\xfb\x51\x2a\x31\x17\xc7\x49\x32\x45\xc1\x20\xb9\x34\x37\xad\xe2\xde\x2d\x71\x50\x4b\xce\xbe\x01\x0b\x14\xad\xa9\x9d\x12\x6f\xe7\xa4\x00\x2b\xb1\x14\x84\xd0\x0f\x7d\x03\xab\x98\xf2\xab\xc0\xb5\x45\x0d\xc7\x49\x8c\xef\x99\x61\x01\x4c\xb4\x2d\xf9\x3e\xbc\x98\xbb\xeb\x90\x42\xc5\x9b\x8e\x9a\x09\x45\x17\x57\x38\x27\x74\xd9\x9d\xf9\x8f\xa5\x05\x17\xfd\x31\x0a\xc0\x81\xd7\x08\x8e\x58\x22\x0a\x92\x15\x75\x7e\x2e\x20\x57\xe0\x1c\x61\x18\xa1\x07\x6a\xcc\xa2\xf3\x38\x1a\x45\xc3\x20\xce\x59\xdc\xcb\x88\x8e\x07\x40\xa5\x4d\xf9\x8f\xa2\xbf\x28\x7e\xc9\xf4\x3c\xaa\xbe\xb9\x97\x68\x36\x4e\x34\xbc\x77\x27\xda\x58\xa9\x06\x5a\xf3\x87\xdf\x65\x3e\x26\x06\xcd\xb8\xc2\x98\x3b\x7e\xf8\x5e\xf3\xcf\xa8\xee\xdf\xda\x9e\x6d\x59\xcd\xf8\x54\xff\xe5\x21\x9c\x36\xae\x1b\x8d\x46\xb3\xd1\x6a\xb4\xeb\xa8\x71\xdd\xe8\x34\x36\x1a\xdd\xc6\xe6\xd9\xc3\x41\xae\xa3\x6e\x85\x50\x30\x2c\xe8\x1e\x9f\x19\x73\x4d\x5f\x31\x4f\x63\x58\xd2\xfc\x81\xfe\xfb\xe5\x0b\x04\x1c\xd6\xa5\x89\x11\xaa\x89\x49\x7e\xb2\xed\x56\xe1\xd1\x3f\x00\x51\x31\x2f\xe2\x3f\x4b\x19\xa5\x9a\x95\x29\x75\x8c\x71\x7c\x9e\x5f\x50\x03\x25\x0d\x88\xca\x61\xaa\x84\xae\x91\x8b\xa5\x6a\xc0\x9a\xbd\x78\x98\x84\x84\xe4\x31\xfd\x61\x52\x3c\xbc\x2e\x0e\x4c\x2a\x66\x1f\xc7\xc3\xb5\xd7\xf8\xba\xa8\xd5\x79\x71\x6c\x4a\xae\xfa\x05\x22\xcc\x48\x7a\x2d\x13\x5e\xc6\x11\x3e\x61\x6e\x6c\x19\x47\x1d\x6d\x68\x3e\xe4\xa3\xad\x8a\x21\x65\xd8\xa4\xf8\x02\xca\xf0\x39\xfb\xf2\x05\xbd\xc6\xd7\xc5\x31\x64\xe6\x92\xd3\x30\xc8\x71\xcc\x84\x02\x9d\x9e\xfc\xdb\x42\x99\x58\xd2\x72\xe0\x4f\x18\x7b\x54\x28\x15\x92\x15\x57\xbf\xe9\x2d\x8f\x4f\x39\x4a\x11\x38\x36\x79\xb4\x0e\xf1\xc6\x34\x46\xd5\x6c\xbf\xa0\xac\xc9\xa5\x68\x90\xea\x2a\x61\x4f\xc8\x00\xff\x62\x31\x30\xdb\x05\x32\xbf\x08\x44\x48\x05\x49\x6d\xde\xc2\x51\x76\x2c\x39\x8f\xc6\x89\xdc\xa0\x8f\x75\x3e\x45\xa1\xcb\x2a\x66\x7f\xfa\xe3\x60\x32\x45\xf8\x1a\x82\x63\x0e\x22\xa3\x5f\xf4\x86\x95\x14\xb1\xae\x03\xe8\xcd\xea\xd0\x11\x88\x45\xc3\xf9\xef\xae\x78\xef\x50\x97\x48\xac\x31\x86\x7d\x17\x05\x39\x0a\x50\x1e\x4d\x4c\x11\xdc\x15\x53\x5e\xed\xa4\x27\xe5\x85\x1c\x0d\x52\x0a\x6d\x13\x9c\xd8\xa0\x9f\x46\x3c\x9a\x37\xf9\xa7\xd6\xea\xa0\x55\x54\x8b\x28\x9a\x3f\xa2\xad\xe5\x65\x11\xe5\xdb\x04\x29\xe6\xf7\x54\xef\xe1\x0a\x8a\x44\x84\xf0\x2f\xdb\xb2\xd5\x9f\x7e\xe2\xe0\x1d\xe5\x45\x7b\x73\x0e\x01\xee\x0d\x49\x1d\x3f\xba\x84\x16\x1f\x3f\x4f\x48\xfe\x72\xfd\xdd\x46\xf6\xc0\xce\xcb\x6c\x6c\xb6\x21\x77\xd6\x95\x6d\x1d\x39\x47\x46\x98\x20\x8a\x83\x81\x71\xf4\x50\xce\x01\x15\x98\x25\x8d\xe6\x41\x24\x3e\x4d\x0a\x8c\xf2\xfb\x90\xfb\xe4\xca\x13\x8b\x47\x2d\xa3\xb1\x1f\x52\xa0\x64\x64\x61\x9b\xe9\xd8\x12\x93\x23\xb2\x92\x97\x7f\x38\x99\x81\x2d\x91\x9d\x0a\x08\x3c\x10\xfe\x36\xac\x11\xfa\x07\x08\xbd\xdd\x22\x84\x9e\x69\xab\xde\x26\x6f\x4d\x70\xda\x46\x07\x41\x7e\xb1\x36\xc4\xd1\x58\x56\x5c\x47\xd5\x62\x1b\xb9\x4f\xc0\x25\xb7\x15\x8f\xfd\x91\x7d\x08\xbe\x83\xb9\x27\x3b\xfc\xaa\x04\x61\x9f\x7e\x0d\x82\x98\x73\xf2\x55\xe1\xc2\x29\x8d\x22\xac\x86\x63\x57\x8b\xd3\xaf\x36\xa1\x30\xd3\xfc\x86\xcf\xa0\x8e\x54\x5b\x68\x2a\x14\xe9\x60\x14\x8d\xc7\x3c\x3e\x2e\xf3\x9d\x80\x73\x56\x19\xe9\x83\x1f\xe3\x62\xd7\xc9\x57\x05\xe6\x77\x02\x2a\x37\xdf\x0c\x56\x49\xa2\xb9\x4f\xbb\xb3\x12\xc7\x2f\xe6\x2d\xd2\x2c\x3a\x65\xd1\x32\x2a\xc9\xc4\xce\xe3\x95\xca\x30\xac\xc3\x14\xf9\x98\xea\x97\xd1\x93\x8f\x57\x9e\x38\xf8\x1f\xaf\xd0\x36\xf9\xdb\x95\xe8\x6d\xf2\xf1\x3f\x64\xb3\xb9\x6e\x07\x21\xee\x6e\x0d\x9a\xce\x32\x41\xf6\x19\xa9\x4c\xc4\xa7\xec\x2f\xbe\x4b\xa3\x4d\xd5\x1a\xd7\xcf\x1a\xdd\x67\xe8\x47\x82\xf1\x7f\x60\xff\x7e\xf9\xf2\xe5\xcb\x65\xb4\x42\x5f\xfc\xe3\x1f\xa8\x71\xdd\x6c\xc0\xd6\x4e\x1a\xb6\xb7\x76\xda\x9f\x5a\xe3\xba\xd3\xdd\x68\x50\x38\x57\x26\x9c\xab\x12\x70\x60\x0c\x71\x36\x03\x8f\x9f\x1a\x34\xfe\xd3\x4f\xb4\x12\x5a\x41\x30\x9c\xbe\xaa\xac\xda\xfa\x36\x14\x67\x7f\xbc\xc5\x56\xb6\x51\x63\x6d\xc3\xf5\x19\x46\x8d\x95\xfa\x91\xda\xb6\x70\x1a\x5a\x46\xff\x40\x6b\x1b\xe8\x9f\xa8\x89\x7a\x68\xb5\x39\x4f\xf4\x30\xf9\x82\x2e\x4b\xd4\x51\x3a\x0c\x86\x17\x98\xe5\xf9\x99\x23\x4d\x90\x6a\x1f\x09\x75\xa5\xb5\x1a\xad\x47\x8e\x3b\x1a\x72\x64\xbf\x50\xba\xee\xb8\xfe\xa1\xf5\xb6\xd1\xc7\xb4\x46\x0b\x03\x85\x6d\x0e\x36\xcd\x8e\x5c\xc9\x3c\x42\x35\x51\x58\x42\x46\x5f\x50\xa3\x54\xa4\xf5\x18\x5f\x29\xae\x4d\x70\xf3\xc7\x14\x18\x31\x4f\x21\xe4\x1a\xbd\x22\x77\x54\x76\x52\x2f\x67\x95\x82\xe3\x21\x58\xa5\xd0\x7f\x8b\xac\x52\x5e\xe3\x6b\xfb\x90\xef\x07\x4d\x8a\x6f\xd3\x06\xd6\xe8\xef\xb2\x01\x3c\x4d\x1d\xc5\x05\xbe\xae\xa0\xa7\x28\x71\xa0\xac\x1c\x96\xb3\x24\x06\xf7\xc9\xb6\x2f\xf0\xb5\x1d\x83\x93\x8d\xa5\x72\x7c\x2f\x93\xeb\xc8\x19\x79\x73\xb1\xf3\xe7\x55\xd9\x33\x65\x56\xf1\x84\x48\x67\xdf\x02\x73\x81\xaf\xfb\x17\x41\x5a\x36\x15\x58\x56\x7c\x64\x83\xe4\x6d\x11\x3d\xac\x5d\xdd\xe5\xa0\xc6\xd1\x62\xeb\x1f\x60\x12\x08\x9d\x65\xa9\xd1\x69\x76\x97\x2d\xee\xe7\xaa\x47\x5a\x68\x8c\x0a\x2b\xba\x39\x87\x00\xf6\x29\x89\xe2\xda\xd2\xd2\x62\xf1\x3a\x15\xda\xa6\xab\xad\x9a\xfa\x86\xaf\x12\x4a\xae\x55\x16\x8b\x47\x50\xfa\xa3\x48\x48\x7c\xa9\x51\xdb\x6c\xb1\x1a\xab\xc4\xd9\xa4\x95\xaa\x24\x71\xa1\x35\xde\xf2\xb3\x0b\x7d\x64\x67\x99\xf2\x8b\xe6\xaa\x42\xf2\x3c\xb5\x45\xb6\xb5\xb6\x7c\xe9\x3d\xe9\x62\x69\x83\x06\x02\x3a\xbb\x1f\xe7\xac\x93\x6b\xd9\x6c\x90\xe5\x69\x2d\xaa\xa3\xd6\x72\x1d\x12\x04\x4a\xd5\x04\x59\x50\x5b\xcb\x0e\x77\xdc\xaa\x3b\xa2\x36\x3c\xeb\xa8\x55\xdd\x9b\xf6\x6d\x90\x47\x71\xb3\xca\x36\xc6\x6a\xf0\x9d\x4c\x3c\x2e\xba\x99\x31\x00\xdf\x76\x3f\x2b\x8f\xc4\x7d\xae\xb3\x31\xb4\xea\xde\xd5\x28\x46\x7f\x9d\x8d\x8d\x8e\xc5\x1f\x71\x6f\x53\x30\xa3\xdb\x14\x9d\xb0\xb5\x51\x9a\x4c\xc8\xdb\x7e\x12\x62\xd8\xb9\xca\xed\x52\x2a\xb8\xbb\x6d\x54\x1a\xd1\xde\x65\xaf\x12\x74\x58\x71\x2d\x7c\x07\x3b\x16\x5b\x46\x74\xd3\x52\xd7\x5b\x95\x7d\x4b\xd4\xab\xb2\x75\x89\x4a\x62\xf7\x12\x6f\x1e\x74\x03\x33\xda\x2d\xcc\x4f\x1d\x29\x2a\x74\xd9\xcb\xb5\x21\xa3\xea\x9d\xbc\x16\xf1\x55\x61\xee\x5f\x4e\x55\x63\xd5\xfd\xcb\xc0\xb5\xfa\xf6\xf5\xe1\xe4\xe5\xea\x56\x95\xdd\xeb\x43\x3e\xda\x12\x7b\x17\x7b\x58\x74\xe7\xa2\x8d\x7f\xdb\x8d\xab\x34\x0e\xf7\xb9\xda\x66\xf9\x68\xcb\xbd\x6b\x91\x11\x7d\xd0\x3d\x2b\x4f\x6f\x7c\x26\x49\x21\x26\xa7\xfc\x0f\x47\xfb\x7d\xee\x9c\x54\xc3\xd9\x30\x98\xe2\x5a\xc1\x5e\x6a\xf1\x6c\x34\x0c\xf2\xe1\x05\xaa\x59\x09\xb0\x11\x82\xfc\x2f\x40\xcd\x90\x02\xa6\xb6\x74\x10\x8c\x47\x49\x3a\xc1\x21\x9b\x86\x30\xc8\x03\x2b\x9f\xde\x42\xac\x5d\x9d\xd6\xbb\x70\x76\x36\x53\xd5\x48\xe5\x3b\x60\xeb\x40\x67\x94\xa9\x4b\x62\xac\xc2\xd2\x59\x2d\x8f\x79\x80\xb6\x6b\xcc\x62\x46\x46\xd4\x66\x40\x23\x30\x0e\x66\x79\x81\x38\x36\x22\xab\x30\x24\xf8\xc4\x21\x4b\xcd\xa6\x78\xac\xcf\x49\x2e\xac\xd8\xcd\xde\x28\x59\xde\x26\xb3\x2c\x47\x03\x8c\x22\x32\xc8\x13\x1c\xe7\x34\x7b\x5c\x00\x77\xee\x29\xce\x85\x2f\x42\x85\xa4\xc5\x46\x22\x52\xfd\x5a\x80\xe6\x75\xa4\x8e\x4f\x32\x15\xfe\x67\x3c\xcd\xd1\x2c\x9e\xf2\x9c\x89\x7a\xee\x53\xc5\x08\xa6\xe1\x65\xd6\x2f\xd8\xc8\x40\xba\xc5\x1d\x31\x2e\xc2\x91\xcb\xf7\xb9\xb4\x15\x1d\x24\xae\x65\x86\x40\xc6\xe8\x2f\xb1\xdc\xe1\x2c\x55\x6b\x9e\xa0\x28\xcf\xb8\x8f\x0b\x22\x44\x7e\x1f\xb7\x55\x03\x67\x17\x68\xaa\xdf\xa2\xeb\xaa\x32\xf9\x84\x99\x89\x20\x02\x6b\x66\x87\x85\x01\xe4\x1d\x65\x8e\x87\xc6\xf6\x6c\xf0\x30\x5a\x3c\xde\x0d\xf2\x80\x8b\xfa\x8d\x0a\x22\xeb\x4e\x18\x66\xd0\x0a\x4f\x89\xee\x19\x6f\x46\x19\x55\x76\x52\x11\xab\xc1\x4a\xbb\xce\xec\x63\x10\x65\x0d\xcc\x37\x80\x32\x58\xea\x53\x12\x28\xa6\x98\xd4\x2e\x19\x18\xe4\xc3\x4d\xea\x47\x8a\x52\x6d\xc9\xb1\x4d\x94\xaa\x58\x66\x37\x65\x65\x4b\x24\x72\xb7\x3d\xf4\x65\xa2\x3d\x35\xd0\xa9\x32\x4c\x2c\xc8\xa9\x06\x49\xf5\x7f\x06\x7a\x2a\x0a\x59\xc9\x28\x4e\xe1\xde\x80\xdc\xbc\x80\x6c\x3b\xd0\x27\x0f\x05\x73\xb3\x23\x07\x24\x8b\x6e\x57\xb6\xf5\xdc\xf0\x15\x88\x98\xe7\x7e\xcd\x50\x70\x19\x44\x63\x08\x09\x46\x19\x04\x70\xbf\x22\xb2\x39\x51\xdc\x58\xa2\xf8\x32\xf9\x8c\x33\x33\xb1\x72\x8d\x65\x44\xae\xa3\xab\x8b\x68\x78\xe1\xe4\xe1\x83\x9b\x62\x1e\xee\x5d\x2d\x83\x24\x19\xe3\x20\xbe\x45\x61\xf2\x72\x3c\xcb\x2e\xd0\x2f\x17\x38\xa7\xc1\x51\x78\x52\x5e\x70\xd9\x9a\x06\x29\x30\x0c\xf6\x4a\xb2\x72\xc1\xc3\x17\x0a\x2c\x22\x36\x00\x18\xfa\xfb\x11\x0c\x04\xd0\x5d\x4a\x51\xbe\x95\xc3\x93\x07\x7b\xe3\x7b\x54\x84\xe3\xc9\x9a\x6c\x84\x7f\xa5\x85\xb5\x35\x48\x47\xbe\x92\x06\x86\x2d\x16\x49\xe9\xb6\xee\x85\xbc\xe5\x56\x6d\x30\xba\x1e\x1d\x0d\xf9\xa6\x98\xa8\x79\xd7\x00\xd3\x9a\x08\x51\x62\xdb\xc8\x4a\x5d\x50\x96\xc3\x96\x95\x9d\x41\x04\xfb\xc9\x2c\xce\x39\x85\xd9\xdc\x84\x80\x8c\x69\x76\xe5\x23\x88\xa7\xbc\xad\xe3\xbe\x6e\x34\xf8\xdc\xe2\x45\x9e\x91\xa6\x2e\x93\xc9\x2c\x0e\xd1\x6c\x4a\x5d\x0a\x87\xe3\x59\x88\x0d\xa2\x37\xea\x18\xa8\x48\xd3\x19\xf5\x43\xb9\x18\xbb\xa2\xf9\x30\xb9\x8a\x55\x04\x92\x78\x7c\x83\x46\x33\xb1\x14\xcd\x10\xff\xeb\xeb\x68\x8c\x33\xea\x4b\xe9\x96\xb9\x80\x59\xa4\x78\x12\x44\xb1\x2e\x64\x95\xe8\xcd\x24\xb8\xae\x69\xbd\x81\x7b\x5a\xb4\xea\xca\x3f\x5f\x9f\x77\x83\x2b\xe6\x97\xea\x2b\x3c\xd3\x0b\x04\xcb\x91\xd0\x9a\xfe\x11\x29\x94\xe6\xa1\x1c\x20\x03\x1f\xe1\x90\x8f\x7a\xf7\xa2\xb8\xa6\x36\xf8\x23\xea\xd4\x35\x8a\x72\x18\x58\xf2\xa4\xe5\x0e\x8a\x20\x04\xa6\x80\x2b\x70\xe9\xa2\xfc\x9c\x6a\x65\xd8\xef\x9f\xd4\xae\x8b\xb7\x2b\xca\x92\x71\x59\x5b\x10\x84\x70\x4a\x8e\x99\x82\xf7\xaf\xca\x23\x02\x78\x42\x5a\x75\x18\xcb\xd0\x77\x18\xc1\x2b\x9c\xe9\xf7\x1d\xb3\xa9\xa6\xdf\x92\x4c\x1c\xa6\xd6\x28\xa7\x71\x52\x95\x1f\x99\x89\xb8\xd4\xb1\x33\x5b\xd7\x98\x12\x5a\xdd\x56\xa7\x73\x1e\xd5\xf1\xd0\xa4\x05\x58\xfa\x75\x51\x3a\xe6\x75\xb5\xdd\x7b\xb0\x8a\xab\x90\xf7\xb2\xd8\x27\xec\xee\xe6\x70\x9e\xcd\xed\x1b\xd9\xc6\x7d\xb4\xf6\x37\x89\x89\x6b\xe6\xe6\x98\xc6\xe9\xac\x4a\x3d\x55\x16\xa6\xdc\xe7\x87\xf0\x8b\x20\x83\x54\xbe\x73\x8e\xdc\x73\xd3\xab\x4b\x0e\xad\xfa\x4c\x51\x52\x60\x6d\xd0\x30\x6d\x38\x43\x49\xac\x9c\x82\x9b\x5d\x54\xdb\x68\xb6\xc0\x16\x76\xd9\x7b\x22\x7e\x4d\x41\xf0\x13\xb0\x78\x74\x1f\x85\xef\x2b\x80\x6c\x61\x26\xb4\x39\x91\x57\x8b\x1c\xd5\xe8\x80\x54\x4a\xb4\xbe\x68\x6c\x3c\xd2\x14\xa2\x31\x98\xe7\x87\xc8\xab\x60\x94\x22\xe6\x43\x8b\xf9\x76\xfc\x7a\xa7\xb5\xd1\xf5\x78\x98\x15\x66\xed\x5e\x30\x34\x1b\x0f\xca\x56\x3a\x3e\xdb\x31\x16\x01\x3b\xbc\x0a\x02\x87\x16\x62\xae\x79\xb7\xd4\xa7\xf0\x15\xf5\x30\x8a\x13\x46\x3a\xf7\xa2\x28\x01\xac\x55\x6d\x8f\x56\xc0\xb3\xb4\x00\xb0\xc6\xe2\x2c\x39\xc2\xdc\xb6\xd9\x88\xd8\x7b\x36\xdf\xaf\x8f\x2a\x0f\xf8\x87\x69\x08\xac\x44\x1d\x71\xb0\xfb\xad\x94\x45\xd0\x1b\x95\x98\x82\x37\x63\x13\x57\x70\x3d\xa2\x0c\x8a\xf9\x1d\x95\x58\xee\x95\x26\x7f\x06\xd8\xd5\x96\x18\x6a\x4e\x95\x8a\x5e\xb4\x8c\x06\x85\x16\x55\x49\x46\x1b\x0c\xf7\xf2\x74\xab\x27\x98\xde\x46\xaf\x6f\x13\x0a\x1b\x65\x22\xb7\x13\x74\x1d\x80\x94\x63\xac\x25\x16\xdf\xa3\x77\xc6\x4b\x96\x84\x27\x13\xb8\xf8\x59\x26\x7a\x97\x00\xce\x41\x4e\xf3\xdf\xb3\xaa\x32\x08\x29\x8a\x32\x84\x47\x23\x3c\xcc\xa3\x4b\x3c\xbe\x41\x01\x0a\x71\x96\xa7\x33\x78\xae\x83\x08\xbf\x9a\xc4\x43\x5c\x32\x6c\x69\x49\x52\xd5\x32\x4c\x00\x52\x32\xe0\x37\x94\x58\x44\x85\x41\x46\xe2\x1e\x37\x0c\xb4\xcd\xc9\x52\x64\x3d\xf2\xe9\x27\x3c\xc5\x0b\x49\xbe\xa0\x5e\x19\xfa\xe7\x85\xab\xac\x80\x97\x8e\x81\xb6\x0e\x4e\x85\x40\xe6\x2c\x19\xb5\xa0\x2d\xfe\x97\xe7\xb4\xca\xd8\x70\xa6\xfb\x52\x99\x03\xd7\x1a\x22\xc5\xfd\x6b\x48\xc9\xd5\xb7\xd1\x6c\xad\xb7\x5b\x65\x0f\x09\x19\xd3\xfb\x68\x81\xf6\x03\x36\x63\x4b\x22\x94\x52\x14\xe7\x38\x1d\xe9\x26\xcb\xde\xc5\xc1\xb9\x2d\xeb\x3e\x27\x5d\xba\x0b\xb3\x30\x88\x01\xba\xc0\xe3\x29\x4e\x89\x68\x54\x6a\x2d\xbc\x64\xf8\x31\x8f\x63\x13\xed\xaf\x76\x17\x48\x65\x2a\xd3\x67\x83\x76\x79\xed\x23\xed\xdd\x6b\xe8\x5a\xcd\x25\x8c\x15\x29\xec\xd4\xca\x9a\xbd\x22\x00\xf7\xdc\x11\x5a\xab\xc3\x19\x58\x57\x3b\x78\xb2\xe5\x08\xa7\x4e\x3d\xeb\x99\xcc\xc0\xa9\x56\xbd\x5d\xe8\xf8\xe9\xa4\x2c\xc2\xa3\x5f\x1f\xec\xf4\x1f\x82\xb2\x88\xa0\xcd\x43\x60\x90\x36\x18\x85\x7d\x57\xd4\xf5\x7a\x12\x0c\x4b\x51\xd8\x24\x18\xde\x8d\xca\x04\x80\xbb\x50\xda\x67\xec\x50\x3e\x29\x94\xd6\xff\x08\x48\x91\xd9\xa0\xe4\x46\x1b\xa0\x15\xab\x91\x5d\xb9\x73\xb4\xd0\x48\x95\xf2\xb1\x20\xc8\x81\x93\x05\xfb\xa1\x79\x59\xf0\x6c\x32\x32\x94\xf0\x41\x90\x5f\xc8\x70\xc2\xb2\x04\x9b\x80\xe7\x66\x98\xe1\xdb\xb3\xfa\x46\xfb\xbb\x8a\x2d\xcc\x30\xac\xf1\x00\xc9\xcb\x5f\x29\xd8\x30\x6f\xef\x7e\x82\x0e\x8b\xde\xa8\xc1\x87\xe7\x46\x1c\x16\x05\x65\xe4\x61\x4f\xa0\x5e\xe7\x75\x09\x0f\x4b\x2b\xa9\x41\xa7\x35\x25\xca\xac\xfe\xa9\x20\x12\x8f\x09\x43\xf1\x09\x2a\x0e\x52\xdb\xed\x54\x31\x46\x63\x35\xb8\x39\x9a\x78\x5c\xd4\x20\x8d\x01\xf8\xb6\x16\x69\xe5\x91\xb8\x4f\xfe\x3d\x80\x56\xdd\x46\x69\x14\xa3\xef\xdf\x94\xda\x2c\x3e\x09\xa6\x42\x8e\x9d\x04\xd3\xaa\x61\x26\x1c\xee\xf2\x16\x00\x8f\x3d\x2a\x1d\xed\x05\x8d\xb5\xd1\xca\x36\x6a\x7b\xec\xb5\x6f\x72\xdc\x74\x18\x6c\xd3\x3f\x3e\xb3\x6d\xfa\xc7\x6d\xbc\xcd\xa1\xb6\x24\xd4\x5a\x84\x56\x50\x73\xd9\x61\x0c\xce\xbf\xcc\x35\x09\xe7\x50\xdb\x06\xd4\x96\x17\x6a\xcb\x09\xd5\x01\x36\x4f\xa3\xe9\x18\x6e\x8e\x6a\x74\x34\x7e\xfa\x09\xbc\x47\xbe\xd0\xe7\x16\x79\xde\x22\x8f\xd0\xbe\x05\x42\x8c\xfd\x27\x3a\xf6\xb5\x4f\xe8\x27\xd2\xee\x0f\x3f\x20\xc0\xe3\x13\xfa\x11\x35\xd6\x36\x37\x94\x29\x59\x7e\x8e\x3e\xb9\xe3\x79\x28\xd3\x4c\x2d\xdf\x27\xc1\x14\x8c\x83\x77\xf2\x5a\x8d\x23\x0a\x3d\xed\xa2\x1f\x51\xad\x8d\x56\xd1\xa7\x65\xd6\xbd\xf6\xc8\xf6\xf6\x32\x83\x52\x98\xe4\xb6\x13\x86\x3c\x4d\xba\x45\x72\xec\x3d\x41\x06\x6d\x23\x05\x91\xae\xe9\x44\x03\x11\x04\x65\x69\x87\xed\xf3\x45\x34\xc6\xa8\xa6\xf6\x8e\x45\x47\x70\xc6\x4d\x71\x8c\x83\x0a\xbe\x52\x27\x19\xc3\x54\x01\xde\xcd\x07\x40\xe3\xb6\x77\xb1\x14\x15\x0c\xb4\x22\x13\xff\x0e\x8c\x45\x19\xff\xa7\x06\x47\xea\x46\x51\xc5\x60\x54\xd4\xab\xc4\xfa\x45\x2d\xe1\x04\x20\xde\x08\x27\x00\xb3\x8e\xc5\xd6\xcd\x02\x29\xbe\xc4\x69\x86\x0f\x94\x72\xf2\x95\x23\x5a\xdb\x13\xf9\xd5\x75\x68\x28\x00\xe6\x60\xee\x36\x8b\x21\x1c\x86\xac\x47\xd6\x1f\x1f\x33\x91\x50\x4f\xf9\xea\x65\x3e\x06\x9f\x96\xcf\xd0\x36\xfa\x54\x95\x5b\xec\x9f\xc7\x49\x8a\xbf\x06\xc3\x50\xc0\xed\xc7\x21\xb8\x6e\xcb\x69\x8c\xc8\x9b\xc3\x51\x21\x4f\x50\x5a\xa0\x00\x9e\x6c\x6f\xa3\xd5\xa6\x9f\xd7\xa8\x14\xa3\x56\xac\x3a\x46\xae\x0d\x3c\x15\x79\x3c\x33\xfc\x36\x49\xa6\x92\xb4\xeb\x66\xeb\x75\x65\xd6\x74\xf1\xc0\xbc\x7c\x0d\xa6\x3d\xb4\xb4\xf3\xa2\xbf\xbb\xf7\xf2\xd5\xeb\xfd\x7f\xbd\x79\x7b\xf0\xee\xf0\xfd\xff\x3a\x3a\x3e\xf9\xf0\xf3\x2f\xbf\xfe\xfb\x7f\x07\x83\x61\x88\x47\xe7\x17\xd1\xa7\xcf\xe3\x49\x9c\x4c\xff\x27\xcd\xf2\xd9\xe5\xd5\xf5\xcd\x7f\x1a\xcd\x56\xbb\xb3\xd1\xdd\xdc\x7a\xb6\xb2\xbe\x2d\x63\xee\xaa\x07\x42\xb1\x08\x2b\xa3\xac\x8d\xaf\xdf\x91\x46\x1a\x8f\x98\x96\xad\x2e\x51\xc6\x68\xd0\xe9\x60\x43\x26\x3c\x72\x6f\x27\xcc\x2f\x2d\x23\x72\x8e\xb2\x26\x24\x41\xa9\xde\x37\x68\x15\x35\x97\xcf\xc0\xf5\x46\xca\x3c\x2d\x8b\xbe\x38\xcc\x56\x19\x98\xcb\x67\x7c\xcf\x56\x05\x29\x1b\x28\x95\x6b\x62\x2d\x06\xd1\x17\x22\x96\x00\xf2\x5f\x68\x83\xaa\x5b\x50\x5c\x18\x86\x08\xb1\x21\x5e\x59\x29\x70\x90\xf8\x9b\x45\xa6\x56\x4c\x4c\x5f\x30\x88\x5b\xa1\x2e\x28\x50\x15\x28\xfb\x81\xa5\x35\x30\x92\x13\x75\x1e\x15\x08\x8f\x0a\x84\xaf\xaa\x40\xf8\x70\xf2\x72\xb5\xd9\x45\x2f\xf6\x2a\x3a\xb4\x35\xbb\x2f\xf6\x54\x9f\xb6\x66\x57\x7f\x82\xaf\x77\x71\x72\xa3\x48\x7d\x7b\x47\xb7\xd2\x78\xdc\xb3\xb3\x5b\xb3\xeb\xf5\x76\x6b\x76\xbf\x7f\xbd\x42\xd9\x53\x3f\x8c\xc4\x1d\x0e\xfd\x8e\x38\x0a\x60\x39\x96\x84\xf8\x7d\x12\xc5\xb9\xcf\x53\xbb\xd9\xf5\x78\x6a\xdb\x07\x73\x89\xa2\xdf\x55\x5b\xb4\x57\xce\x5f\x5b\x01\x79\xb7\xa3\x9a\x49\xc1\x77\xf4\xeb\x03\xa2\xac\xbe\x34\xbe\x83\xf3\x1a\x5d\x56\xc2\xbb\x8f\xaf\xbe\x8a\xee\x7d\x50\xad\x8a\xc3\x36\xaf\x23\x04\x6f\xfe\xe2\x41\xdd\xb5\xf5\x56\x4b\x78\x6b\x37\x41\x0a\x13\xb8\xe9\xbe\xda\x44\x18\x93\xcb\xa5\x45\x96\x8b\x19\xc4\xce\x43\xe6\x3e\x39\xcb\x18\x15\x53\x50\x2c\xed\xa5\xdd\xec\xa2\xb7\xa5\x37\x36\x65\xf3\x7a\x7b\x0f\x9b\xd7\xdb\x3f\xc8\xe6\x55\x0e\x8f\x87\xd8\xbc\x9c\x4b\xec\xed\xde\xe3\xde\x05\x7f\xee\x67\xef\xca\xae\x82\xe9\x5e\x1c\x46\x41\x5c\xab\xba\x8d\x59\x47\xa5\xef\x60\x1f\x7b\xfb\x50\xfb\x58\xb9\x55\xf2\xbd\xec\x63\x6f\xf7\x8c\x9d\xec\x71\x1b\x53\xb6\x31\x65\xcd\x54\xda\xd1\xbe\xfe\x96\x26\x26\x45\x41\x99\xc0\xd3\x7b\xc9\xd3\x0b\xc0\x27\x76\x55\x43\x17\x79\xa3\x41\xfe\x0f\xf7\x38\xf4\x23\x19\x04\xf6\x95\x7e\xd3\xd8\x40\x79\x6d\x06\x50\xd9\x3c\x65\xc6\xc6\x9d\x95\x19\x2c\x57\xef\x37\xd5\x69\xd4\x91\xf2\x2a\xbb\x08\x9a\xc6\xab\x8b\x49\x30\xfc\xea\x9a\x8f\x3a\xe2\xc8\xc0\x2f\xc0\xe1\xaf\xa8\x0d\xb1\x72\xec\x2e\xac\x2c\xd1\xac\x83\xf4\x8f\x07\xbb\x1b\x50\x1f\xac\x9b\x0e\x76\x37\xfc\x32\x27\x58\x9c\x7f\xc6\x37\x34\xbf\x39\xb5\x48\x16\xc3\x00\xfe\xd8\x41\x9c\xf3\xdc\xec\x49\x3a\xa1\x66\xf3\x7b\x3f\xbf\xff\x08\xdb\xfe\x49\xf2\x06\x6b\xc2\x29\xba\xba\xba\x5a\x4b\xa6\x38\xce\xb2\xf1\x5a\x92\x9e\xaf\x87\xc9\x30\x5b\x87\x0c\xeb\xc9\xba\x51\xed\x22\x9f\x8c\x9d\x72\x2d\xe9\xc2\xde\xe5\xf4\xcd\xee\x4b\xd9\x0b\xf1\x5c\x3a\x6a\x45\x09\x8f\x1d\x73\xd7\xb5\xbc\x96\x58\x56\x7b\xd8\x33\xc9\x28\x65\xe4\x21\x8a\xb9\xbb\x92\x12\xdb\x5b\xba\x28\x75\x50\xad\xd9\xda\x32\x3d\x94\x8c\x06\x3c\x96\x82\x6a\x58\x13\x33\x0b\xce\xc1\xee\xc6\x5c\x7c\xa3\x9c\x19\x89\x9b\x31\xc9\x95\x0f\x79\x82\xa6\xd4\x12\x58\xf5\xab\xf2\xee\xb6\x96\x27\x93\xbe\x5d\xb1\x11\xea\xa1\x66\x6b\x0b\xec\x7a\xd5\x8f\xb4\x77\x80\xba\xfe\x41\xa2\xa3\x37\x7d\x7b\x07\x37\x29\x0f\x15\x3f\xbc\xdf\x14\x56\x9a\xbd\x27\xfb\xf7\xcf\xe1\xc8\xf2\x96\xa2\x2b\x60\x5e\x3e\xae\xf9\x35\x7f\x97\x73\xb6\x65\x66\x6a\x5c\x14\x4e\x5d\x9f\xd1\x46\xa3\x61\x41\xae\xe8\xcf\x35\xd7\x55\x4b\x21\x9a\x62\x9a\xd9\x25\x93\x03\xf4\x42\x28\x04\x32\x8d\x07\x90\x34\x97\x2c\xe2\xc5\x9c\x29\x78\x6d\x1a\xb9\xc1\x09\xaa\xa0\x76\x16\x8c\x73\xb4\x03\xff\x2c\x22\xbb\x03\xb1\x51\x7a\xbf\x2f\x6a\xc3\x64\xbb\xf9\x1c\x8e\xd6\xa8\x1f\x0b\xae\xf1\x3e\xd5\x01\xcb\xc2\x04\x45\x50\x41\xcb\xe5\xa9\xd7\xad\x20\x93\xeb\xd3\x6d\x4b\xe0\x64\xf5\x33\x9e\x62\xd5\x15\x2e\x86\x84\x50\x98\x05\xb9\x5c\x2a\x2e\x48\x1e\x9f\x3e\x08\x04\x41\x87\xf8\x0d\x8c\x8b\x2b\x15\x5e\xf9\x13\xbc\x04\x25\x3c\xbe\xc5\x1b\xdf\x61\x9e\xef\x3a\xb4\x33\xec\xc9\x2e\xa6\xf0\x74\x5a\x52\xbe\xb0\x31\x7c\xc5\x36\x2a\x02\x5c\xfb\xc6\xcc\x7c\x0c\x3c\xb9\xa9\xcf\x4f\x1c\x19\xfb\x98\x42\x44\x45\xf0\xeb\x70\xdd\xcf\xeb\x0e\x65\xb4\x58\xf1\xad\xbc\x12\x28\x43\xba\xe4\xb0\xfa\x9c\xa4\x14\x9b\x6c\x9b\x2e\x1d\xee\x8e\xc6\x67\x32\xf1\x62\x88\x8c\x6f\xda\x69\xad\x49\x4f\x6b\xca\x78\xfa\x72\x39\x19\xf8\x0a\xf4\xdc\x3d\x2e\x74\xc8\x74\x85\x16\x50\x68\x85\xc5\xef\x71\x01\xd6\x07\x52\xa9\xa3\x68\x85\x38\x4d\xd9\xe1\x46\x44\x5c\x40\x5e\xad\xba\x5d\x7b\x59\x16\x6b\x38\x89\x2f\xca\x5e\xab\x33\xd7\x7b\x74\x91\x76\xbb\xfa\xe9\x3d\xab\xc4\xb9\xf5\xaa\xa6\xca\x45\xff\x6a\xf1\x74\xed\x33\xd2\xf8\xb9\xd8\xbb\xe9\xb6\x5d\xc0\xd3\xab\xd5\xac\x23\xbf\x04\x71\x17\x48\xf3\x64\x08\x55\xf1\x2d\x8f\x03\xbe\x7d\xc7\xe1\x66\xc4\x66\x45\x97\x62\xc0\xc1\x68\xee\xb6\x77\xfb\xbc\x94\xa2\x81\x82\x2e\xd2\x32\xd4\xf9\xd9\xb7\xb7\xb1\x59\xe7\x27\xe2\xde\x66\xf7\xf6\xac\xbe\xd1\xfd\xf3\x69\x1f\x86\xd1\xf4\x02\xa7\xab\xdf\xc4\xd6\x02\xf4\x0c\x2a\x02\x7f\x5c\x75\x83\x33\x39\xec\xfd\xea\x1d\xfa\x30\x10\xef\x09\x13\xcc\x84\x6e\x41\x7d\x59\x68\x8b\xa1\x7e\x32\x12\xb4\x98\xf5\xc8\xc1\x32\xc8\xa1\x2a\xfd\xa9\x6c\x1b\xac\xfa\x4b\x78\x9f\xb3\x08\x1d\xb4\x54\xa5\x5c\x2f\xe4\xcc\x45\x67\x36\xc7\xd7\x39\x39\xdf\x06\xec\x19\x4d\x69\x0f\x99\xf7\x1e\xcf\x03\x13\x84\x78\x18\x4d\x82\xf1\xf8\x86\xa5\xa1\x0d\x2b\x5c\x75\xa9\xa3\x74\xcb\xda\x61\x03\x79\x22\x10\xd1\x1b\x5e\x24\x53\xcc\x62\x38\x3e\x78\xee\x18\x39\x3d\xca\x35\x98\x3a\x08\x55\x6f\xc2\xb4\xba\x6e\x57\x3e\xb5\xc8\x9a\x9c\x68\x99\xeb\xe4\x35\xbe\x5e\x3c\x6d\x89\x63\xa0\x15\x62\x52\x6f\x6b\x2a\x4f\xad\x71\x6f\x13\xc5\xd3\x59\x7e\xb7\xf9\xe5\xc4\xa2\x13\xe1\x42\x74\x77\x9f\xa4\x32\x34\xd8\x89\x83\x5a\xee\x90\x01\x05\xc6\xcd\x19\x3e\x49\xce\xd3\x36\x92\x8d\xd0\xf2\xcf\x5d\xa4\xd4\xd7\x48\x89\x6b\x2e\x24\x9c\x9e\x0a\xf3\xd6\x7f\xc3\xc1\x7e\x14\x6e\xff\xb2\xf3\x4e\x11\x40\xd9\x8a\x7a\x1b\xcd\xba\x71\xf7\xb0\xf9\x68\x48\xf9\x68\x48\x79\x97\xab\x83\x82\x2d\xdb\x0a\xe7\x3f\xe7\x2e\xc1\x77\xce\x03\xb7\x75\xa1\xa2\x2e\xc8\xe1\x46\xca\x6d\xab\xee\xd5\x55\x14\xf7\x45\x4a\x5e\x70\xd5\x2e\xc1\x81\x4b\xeb\xd7\xdd\xd5\xed\x73\x25\x39\xcf\x80\xef\x3d\x1e\xa6\x38\xbf\x47\xe5\x1b\x39\x0d\xbc\x76\xc7\xc7\x82\xce\x32\x9e\xe5\x72\xa3\xa7\x7e\xea\xe5\xd5\xac\xaa\x77\xbb\x3b\x10\xb6\x4b\xbb\x26\x34\x6b\xd4\xed\x45\x3c\x9a\xa1\x1a\xfc\x8e\x91\xf7\x18\x3c\x19\x8e\x95\x45\xb1\x93\xe9\xb9\x53\x09\x9d\x6c\x0d\x91\xc3\xff\xc3\xaf\xbe\xa3\x1d\x7c\xa1\x84\x7a\x65\x5d\x2e\x08\xf6\x6a\xd4\xe1\x1a\x18\x13\x94\x33\xf0\xeb\xce\x78\x9c\x5c\xa1\x20\x1d\x44\x79\x1a\xa4\x37\x88\xa9\xe1\x3e\xe3\x1b\x3b\xc4\xe6\x67\x55\xc3\xf3\x0f\x67\xb3\xbe\xf1\x31\xd5\x55\x65\x06\xa9\xd8\x4d\x96\x20\xe3\x77\x90\x85\xa8\x87\xa0\x1b\x4a\x52\x14\xc5\x31\x4e\x21\x94\x72\x32\xcb\x41\x82\x09\xed\x81\x4f\xa8\xf2\x95\xd2\x1c\x7b\xa0\x6d\xd8\x61\x18\xa9\x36\x54\xad\x10\x39\x2a\x94\x9f\xf4\x44\xd1\xd6\x26\x05\x7a\xda\x48\x29\x17\x29\xe5\xcc\x96\x7e\x3d\x3c\x82\x19\xa4\x97\xa7\xd3\x20\x44\xc3\x24\xce\xf2\x20\x36\xda\x76\x26\x5a\xd3\x67\xd5\xa3\x88\x14\x18\x9f\x46\x67\xe8\xb7\x6d\xd4\xb8\xde\x18\xd2\xff\x2c\x1f\x2b\xab\x64\xbb\x4b\xff\x2b\xd2\x29\x26\x86\x36\x31\x32\x9e\x5d\xb4\xf7\x2d\xa2\xe9\xc1\x56\xf3\x50\xb1\xf4\x04\xa3\xbe\x9f\x78\x7a\xfe\x6c\x7e\x2e\x86\x6b\x8f\x26\xf4\xdb\xc1\xa9\xdd\xca\x65\x5d\xa1\x2e\x57\xc9\xc2\x61\xf4\x60\xa8\xff\x50\x41\xf4\x0e\x76\xfa\x2c\x84\x1e\xe0\x16\xc1\x12\x9b\x13\xb8\xa7\xf2\xd4\x97\x8a\xa6\x67\x15\x7f\x80\x88\x7a\x2a\x71\xf0\x66\xe6\x85\xd0\x7b\xb8\x28\x78\x30\xe2\x8f\x51\xf0\xd8\x48\xdc\xa3\x68\x48\x98\xab\x24\xa7\xf9\x91\xf0\xfc\x55\xe6\x47\xc3\xf3\xd7\x7d\xa8\x88\x78\x77\xe5\x81\x7d\xaa\xb8\x87\x41\xb7\xb7\x67\x22\x67\xbc\x76\x87\xf8\x2b\x8a\x9c\x57\xc4\x46\xd5\x71\x32\x40\x4a\x51\x85\xcb\x1c\xfc\xce\x4e\x20\xb2\xec\x0b\x9e\x37\x09\x86\xfe\x5b\x37\xf6\x4b\x51\x3d\x78\x0d\x18\xb7\xfe\x7c\x57\x08\xd7\xdd\xce\xaa\xe3\xf5\x38\x1a\xac\x12\xb4\x42\xb0\x62\xce\x8c\xaf\x38\x1e\xae\x82\xfd\xaa\xe3\x3d\x75\xf7\x35\x3e\x4c\xc2\x8d\xf9\x86\x93\xd9\x45\xd0\xda\x30\x41\x92\x97\x2d\x13\x5c\x76\x11\x6c\x34\x5b\xf6\xcb\xf6\x96\xa3\x64\xdb\x78\x95\x46\x53\x3c\x09\x9b\xdd\x86\xd3\x6e\x53\x7b\x35\x1d\x7c\x0e\x47\x66\x3b\xf8\x72\xfa\x39\x1c\x15\xdd\xc4\xe8\x5d\x4f\x42\xbc\x3a\x1c\x0d\x9c\xaf\xf3\xd4\xf3\x7a\xf5\x7c\x1c\x84\x93\x20\x76\x7d\x4e\xdc\xc0\xf0\xd0\x7c\x3d\x0d\xc2\xd5\x20\xce\xa2\xeb\x67\x2d\x73\x10\xc8\xa7\x28\x4b\x9a\x8d\x66\xcb\x1c\x71\xf6\xe9\xd9\xe6\xb3\x4d\x73\x86\xc8\xa7\xff\xe0\x34\x61\xc1\x00\x1c\x5f\x63\xcf\x37\xaa\xbb\x5b\xbd\xc0\xd7\xc6\x87\x00\x9b\xc4\x45\x43\xba\x84\xd6\xfb\x74\x68\x4e\x6e\x1a\x0c\x06\x51\xee\x7c\xb9\x3a\xc6\xe7\xc1\xf0\xe6\xdb\xdc\x8a\x89\x35\x05\x4f\xe6\x52\x82\x97\x72\x05\x89\x47\xb6\x70\xe0\x99\xac\x17\xc3\xa4\x97\xad\x0e\xf1\xbb\xd5\x11\xbf\xc9\x5a\xe0\xbf\xc9\x12\x10\xbf\xe9\x2f\x49\xf0\xd2\x36\x18\x7e\x31\xf2\xa6\x18\x50\xaa\xb6\x6e\xf5\x28\x3a\x9c\x86\x95\xa7\x3c\xd5\x9f\x04\xc5\xca\xb7\x89\x56\x83\xd0\x27\x6d\x56\x25\x4b\xf1\x46\x50\xa3\xfa\x86\x12\xa1\x78\xa3\xd2\x9e\x78\x19\xeb\xaf\x14\x4a\x83\x67\x42\x60\xf0\x43\xd2\x15\x1d\x95\x21\x1b\x28\x46\x45\xca\x6f\x4e\x3c\xf7\xa3\x05\xd5\xb4\x9f\x2a\x5f\xbe\xab\x56\x74\x5e\x00\x42\xd6\xf9\xde\x46\xa3\x5e\xac\x1f\xaf\xeb\x14\xd8\xdb\x68\xd7\x35\x12\xed\x6d\x74\xea\x92\x44\x7a\x1b\x1b\x75\x7d\x9c\x7b\x1b\x5d\xf3\xe2\xdd\x24\xfa\xde\xc6\xb3\x3a\xa3\xeb\x5e\x17\xf0\x11\x34\xd5\xeb\x36\xeb\x2a\x55\xf5\xba\xed\xba\x8b\xae\x7a\xdd\x56\x5d\xa5\xa5\x5e\xb7\x53\x57\x29\xad\xd7\x05\xbc\x34\xea\xea\x75\xbb\x75\x93\xbe\x7a\xdd\xcd\xba\x49\x61\xbd\xee\x56\xdd\x22\xa7\x5e\xf7\x59\xdd\x41\x78\xbd\x4d\xc0\x9f\x2d\x9e\xde\x26\x60\xcf\x88\xa8\xb7\xd9\xae\x5b\x64\xd4\xdb\x04\xc4\x09\xc1\xf5\x36\x01\x67\xb9\x22\x7b\x9b\x1b\xaa\x9d\x42\x5d\x2e\xee\xde\x26\xb7\x60\x20\xcb\xbe\xb7\xb9\x55\xe7\x8b\xba\xb7\xf9\xac\x2e\x17\x7b\x6f\xab\x51\x97\x6c\xa0\xb7\x05\xe8\x48\x5a\xef\x6d\x41\xe3\x82\x25\xf5\xb6\xda\x44\x92\x79\xf6\x78\x1d\xf2\x67\xbc\x0e\xe9\x5f\xe0\xe1\x67\x32\x0e\xb0\xf6\xa8\x0b\x1a\xcd\x3a\x98\xcd\xa6\x64\x54\xb1\x8c\x10\xaf\x8c\x16\x9c\x3d\x68\x8a\x01\xf4\x64\x1b\x2d\x71\xf8\x4b\x2e\x0b\x1f\xd5\x3d\x48\x6d\xfb\x6b\xc5\xb4\x50\xdb\x3c\xc2\x23\x9c\x62\x38\xb9\xa6\xd1\x39\x1c\x31\xa3\x38\xca\x35\x60\xd9\x6c\x8a\x53\xd0\xbe\x6f\x1b\x49\x77\x74\x58\x3b\xb3\xf3\x09\x8e\x73\xa3\x0c\xca\x13\x74\x11\xc4\xe1\x18\x6b\x63\x6a\xb4\x30\x70\xc2\xd7\xec\xa7\xa0\xb6\xc3\x45\x55\xd1\xe3\xd3\xbc\x0f\xd4\x26\x2e\x8a\xf3\x2d\xa5\x14\x9f\x2c\x36\x2c\x5c\xf9\xa2\xcf\x9d\xa5\xc3\x90\x15\xe8\x7d\xc2\x07\x02\x15\x5e\xa8\xe8\xe8\x47\x23\x37\x6a\x09\x4d\xd5\x07\xb0\x2e\x23\x7c\xe5\x45\xd2\xd0\x87\xba\x71\xde\xe7\x68\xa0\x2f\x5f\x8c\x0a\x9c\x24\x01\x55\xd0\x89\xf3\xfa\x4f\xc8\x5a\x16\xa6\x3c\xb0\x98\xdd\xd0\xad\xaa\xcb\x76\x33\x5e\xbc\x9a\x5d\x0f\x62\xfe\xb6\x2a\x56\xd9\x8f\xf3\x76\xab\x72\x23\x15\xab\xbc\x1c\x27\xc1\x42\x75\xba\x1d\x78\xaf\x54\x58\x8c\xaa\xd6\x28\x31\xd7\x91\xfa\xea\x26\xc7\x87\x90\xf5\xcb\x7a\xed\x4c\xbd\xae\x93\xe2\x6b\xba\x0a\x65\x6b\xa5\x96\x87\x2c\x5e\x45\x3f\x22\x61\xbd\x10\xc8\xa1\x6d\x37\xd2\xb6\xda\x64\xef\x9a\xe5\x54\xbe\xc9\x1d\xd6\xef\x55\x1c\x5a\x5d\x78\x94\xce\xa5\x2f\x2b\x9f\x46\x67\x0b\x24\xce\x97\x86\xfc\xd1\x7f\x30\xd5\x4a\x4b\xc7\x66\xad\xa4\x60\xb1\x32\x15\x4d\x1d\x31\x3f\x57\x57\x17\xf4\x79\x76\xa5\x2a\x24\xa3\x48\xde\x12\x78\x28\x26\xa2\xa6\xc1\xd9\xf5\x66\x83\xe9\x74\x7c\xc3\xda\x0d\xd2\xf3\x19\x61\xe4\x59\x09\x7f\x5a\xc6\xb9\xd7\xa6\x69\x92\x27\x04\x53\x95\x87\x97\xb3\x4a\x71\xed\x4e\x3e\x05\x52\xb7\xf1\x28\x76\xfd\x29\xc4\x2e\x88\x05\xff\xed\x43\x7a\x39\x13\xb1\x95\x36\x3e\x81\xdd\x9d\xde\x93\xa2\x5c\xbb\xb6\x20\x15\x4f\x18\x8b\x32\x88\x08\xf4\xc1\xf6\xfd\xaa\x47\x24\x75\x73\xb5\x6e\xc7\xc9\xc3\x4e\x08\x9b\xda\xa6\x83\xbb\x16\x0c\x32\xfa\x23\x8b\x62\x16\x11\x99\xb0\xac\xc6\x75\xb3\xc1\xfe\x2c\xa3\x2f\x46\x02\x70\xb9\xc4\x6b\xcb\x05\xee\x14\x07\xbb\x1b\x86\xb5\x8a\xdf\xd4\xc6\x74\xf5\x45\xdb\x6c\xd0\x5d\xa6\x36\x3c\x79\x56\xd1\xcd\xa4\x54\xb8\xbb\x93\x9e\x9f\x1a\x6c\xbd\x71\xdd\xdd\xec\x6c\xb4\xda\x8d\x66\x1d\x35\xae\xf1\x68\x18\x06\x83\xad\x67\x66\x7a\xd6\xc6\xf5\xb3\xad\x41\x10\x0e\x47\xb8\x0e\x43\xd4\x6e\x6d\x74\x36\xbb\x5a\xa1\xb3\x82\xab\x48\x23\x4b\xa7\x8a\xff\x81\x48\xd4\xe9\xd8\x3e\xaf\x82\x29\xc2\x10\x85\x60\xee\x4e\xd6\xec\x7a\xf6\x2d\xaf\xa9\x00\x33\x16\x80\xc6\x3f\x12\x48\x3c\x57\x29\x8a\xcc\x5b\x77\x98\xa9\x8f\x4a\xd1\x83\x53\xfe\x70\x66\xb9\x29\x29\xdf\x08\xb5\x19\x5f\xc9\x9f\x5a\xad\xa6\x40\xa3\xf1\x13\xd0\x17\xa4\xbe\x84\x5d\xb6\xb3\x6c\x44\x4e\x40\xa6\xa0\xe5\x84\xd6\xea\x2c\x3b\xa0\x41\x0c\x71\x2d\x4a\x83\x01\xeb\x0e\x66\x36\xaf\xb5\x9b\x1e\xa7\x1d\x85\x36\x84\x0d\xa4\x8c\x21\x5a\x41\x8d\x33\xfb\x9a\x46\x14\x6e\x1a\x85\x9b\x45\x85\x5b\x46\xe1\x56\x51\xe1\xb6\x51\xb8\x5d\x54\xb8\x63\x14\xee\x14\x15\xde\x30\x0a\x6f\x14\x15\xee\x1a\x85\xbb\x45\x85\x37\x8d\xc2\x9b\x45\x85\xb7\x8c\xc2\x5b\x45\x85\x9f\x19\x85\x9f\x15\x4e\x4a\xc3\x98\x94\xe2\x29\x6c\x1a\xa5\x0b\xe7\xb0\xd9\x32\x4a\x17\x4e\x62\xb3\x6d\x94\x2e\x9c\xc5\x66\xc7\x28\x5d\x38\x8d\xcd\x0d\xa3\xf4\x86\xb9\xdc\xd7\xd7\x09\x83\xfd\x1c\xc5\xe7\xa4\x66\x14\x8c\x07\x0e\x89\x3c\x20\x5c\xfd\xd4\x35\x42\x03\xf8\xe2\x1a\x8d\x21\x7c\x71\xf5\x3c\x84\x2f\x6d\x07\x26\x7d\x79\x81\xaf\x7d\x22\xed\xbf\x7c\x59\x0b\xea\x68\x50\x47\xc3\x3a\x0a\xeb\xca\x52\xac\x23\xb4\x59\x27\x9b\x63\xe3\xcc\xe0\x00\x21\xad\x16\xd6\x91\xa8\x29\x47\xa6\x8e\x50\xb3\x55\x47\x27\xa7\x4d\xb3\xda\x90\x56\xa3\xed\xd0\x9a\x72\x79\x92\x6a\x9b\xa4\x5a\xcb\xac\x36\xa0\xd5\x04\x86\x81\x52\xad\x5d\x47\xa8\x05\xad\xb5\xcd\x6a\x45\x7d\xeb\x88\xbe\x75\xaa\xf4\x6d\x43\xf4\x6d\xa3\x4a\xdf\xba\xa2\x6f\xdd\x2a\x7d\xdb\x14\x7d\xdb\xac\xd2\xb7\x2d\xd1\xb7\xad\x2a\x7d\x7b\x26\xfa\xf6\xac\x4a\xdf\x9a\x8d\x3a\xeb\x5b\xd3\x22\x93\xa2\xce\x35\x9b\x75\xd6\xb9\xa6\x45\x27\x45\xbd\x23\x28\xd2\xde\x35\x2d\x42\x29\x24\xcb\x76\x9d\x93\xa5\x45\x29\x85\xfd\xeb\x88\xfe\x59\xa4\x52\xd8\xbf\x0d\xd1\x3f\xa0\x15\xab\x83\xaf\x5e\x79\x3a\x58\x47\x68\x83\x76\xd0\xa2\x96\x90\xd6\x73\x76\x90\x10\xd9\x33\x5a\xcf\x22\x97\x21\xad\xe7\xee\x60\xb3\x8e\x48\x27\x4f\x4e\x9b\x16\xbd\x0c\x68\x3d\x67\x07\x09\x7b\x68\x35\xa0\x9e\x45\x30\x45\xfd\xdb\x10\xfd\x6b\x39\xf9\x8a\xaf\x7f\x84\xd0\x68\xff\x5a\x4e\xc6\xe2\xed\xdf\x06\xef\x5f\xcb\xc9\x59\x7c\xfd\xeb\x88\xfe\xb5\x9c\xac\xc5\xd7\xbf\x67\xb2\x7f\x4e\xde\xe2\xed\x5f\x47\xf4\xcf\xc9\x5c\x7c\xfd\x23\x1c\x90\xf5\xcf\xc9\x5d\x7c\xfd\xdb\x92\xfd\x73\xb2\x17\x2f\x7d\xb6\xeb\xbc\x7f\x4e\xfe\xe2\xeb\x5f\x4b\xd0\x67\xcb\xc9\x60\x7c\xfd\xdb\x14\xfd\x6b\x3b\x19\x8c\xaf\x7f\x64\xb1\xd3\xfe\xb5\x9b\xce\x05\xf8\xfa\xb5\x9f\x40\x3b\x80\x68\xdb\xc9\x61\x5e\xbf\x76\x77\x90\x0c\x28\x59\x4c\x27\xa7\x6d\x27\x87\x79\xfd\xba\x60\x01\x76\xa1\x9e\x93\xc3\xbc\x7e\xed\xe9\x60\xa7\x8e\x5a\x6d\xa8\x67\x11\x4c\x51\xff\x9a\xb2\x7f\x4e\x06\xe3\xeb\x5f\x47\xf6\xcf\xc9\x60\x7c\xfd\x83\x09\xa4\xfd\x73\x32\x18\x6f\xff\x1a\xa2\x7f\x4e\x06\xe3\xed\x5f\xbb\xce\xfa\xd7\x71\x32\x18\x5f\xff\x1a\xa2\x7f\x1d\x27\x83\xf1\xf5\xaf\x2d\xfa\xd7\x71\x32\x18\x5f\xff\x08\xc3\xa6\xfd\xeb\x38\x19\x8c\xaf\x7f\xcf\xc4\xfc\x75\x9c\x0c\xc6\xd7\x3f\xb2\x20\x58\xff\x9c\x0c\xc6\x4b\x9f\x1b\x9c\x3e\x3b\x4e\x06\xe3\xeb\x5f\x4b\xf6\x6f\xd3\xb9\x00\xf7\xf7\xfd\x82\x67\x97\x76\xd0\xc9\x61\xf6\xf7\xdd\x1d\x04\x42\x83\x15\xdf\x71\x72\x98\xfd\xfd\x82\x2d\x7e\x03\xa4\x3a\x27\x87\xd9\xdf\x77\x77\x90\x30\x8a\x16\x0c\xe8\x86\x53\x84\xf1\xf5\x8f\x4c\x04\xed\xdf\x86\x93\xc1\xf8\xfa\xd7\x16\xfd\xdb\x70\x32\x18\x6f\xff\x1a\xa2\x7f\x4e\x06\xe3\xeb\x5f\x53\xf6\xcf\xc9\x60\x7c\xfd\xdb\x12\xf3\xb7\xe1\x64\x30\xbe\xfe\x01\xa1\xd1\xfe\x39\x19\x8c\xaf\x7f\x20\x5e\xd3\xfe\x39\x19\x8c\xb7\x7f\xed\x3a\xef\x9f\x93\xc1\xf8\xfa\xd7\x11\xfd\xeb\x3a\x19\x8c\xb7\x7f\x4d\xde\xbf\xae\x93\xc1\xf8\xfa\xd7\x12\xfd\xeb\x3a\x19\x8c\xaf\x7f\xcf\xc4\xfc\x75\xdb\xd6\x02\x84\x2b\x96\x1c\xa7\x13\x1c\x46\x41\xce\x9c\xe8\xc0\x77\x43\x2b\x46\x0e\xa7\x68\x1b\xd5\xe0\xdf\x15\x14\x58\xaa\x4f\x04\xa7\x54\x5a\xa4\x49\x8a\x0c\x9c\x45\x5a\xac\x48\x8b\x14\x19\x3a\x8b\xb4\x59\x91\x36\x29\x12\xda\x3a\x56\x53\x6f\xf8\xd2\x61\x96\x5c\x29\x06\x73\x18\xe4\x81\x4c\x84\x1d\xe4\x81\xe3\x44\x1d\xe4\x81\x08\xef\x14\xe4\x81\x57\x81\x15\xbf\x88\xf2\xec\x24\xc9\x83\xb1\x80\x18\xef\x06\x79\x40\xfd\x68\x7e\x44\x5b\x36\x6c\xa8\xf2\x16\x8f\x72\x0e\x5b\xb8\xdd\x40\x71\xb3\x1f\xbe\xe4\x70\x02\xc5\x53\x09\xf0\x1f\xff\xf8\x07\xda\x80\x9b\xb8\xc6\xf5\x56\x43\x5e\xc0\xc9\x12\x7f\x47\xed\x96\x49\x12\x7a\x3f\x5e\xa3\x6d\x04\x6a\xf0\xd1\x38\x49\xd2\x9a\xd2\xc1\x75\x4d\x17\xee\xe9\x18\x14\x7d\x8b\xb6\x95\xa7\xe7\x1e\xbc\x6b\xb5\x9a\x44\x6c\x05\x75\x3b\x34\x8b\xe0\x33\x08\x7e\xdb\x59\xa6\xda\x15\xa7\xc2\x94\xd7\x64\x08\x4b\x7d\xa9\xfa\xb6\xb4\xc2\xd4\x86\xc6\xf4\xa5\x26\xb4\x39\x0a\x53\x93\x25\x54\xe9\x68\xa7\x44\x47\xdf\x3a\x3b\xfa\x76\xc1\x8e\xbe\x75\x76\xf4\x6d\xc9\x8e\x5a\x3d\x55\x9d\xc7\x6a\xa2\xeb\x3c\xea\x18\xa4\x97\x74\xfa\x47\x82\x39\x3f\x75\xd5\x00\x0f\x4c\x87\x07\xd9\xc7\x29\xbd\x29\xa8\xe6\xf3\xa7\xa5\xbb\x37\x93\xd9\x4b\x25\xb5\x57\x3d\x5d\xf1\xb6\xc1\x7d\xc1\xe4\xbf\xf3\x67\x18\xc0\xdd\xc1\xeb\x53\xc7\xa5\xc1\x6b\x76\x37\x55\xab\xbd\xd6\xae\x04\x5e\x57\xbe\x0b\x78\xad\x5d\x02\xbc\xf6\x6a\xff\xe7\x69\xfb\x8f\x58\x5a\x6e\x98\x2c\x16\x61\x29\x84\x01\xd4\x4a\x72\x17\x08\x63\xc8\x75\x3e\x0e\x0e\x15\x45\x1c\x1c\x34\x9e\xe0\x65\x2a\xee\xbf\xe0\x51\xde\xf7\x9b\x3b\x29\x7c\xfd\x68\x4d\xbb\xc7\x5f\x94\x07\x26\x21\x1f\xfd\x9e\x1a\xe2\x41\x20\x6a\xe8\xa7\xae\xeb\x28\xab\xa3\xdc\xce\xb8\x16\xa3\x6d\x14\xa0\x15\x54\xab\x0d\xd0\x0f\x74\xe7\xab\xfd\x1f\xf2\x33\x5c\x26\x0b\xfe\x1a\xad\xa0\x5c\x6d\x57\x44\xd0\x8e\xc9\x5c\x65\x74\x59\xd2\x00\xfa\xed\x16\x5a\x45\xd9\x32\xd4\x1b\x38\x6d\xe1\x04\x76\xc6\xe1\xbd\x24\x76\xb0\xe9\xd6\x86\xe8\x07\xf4\x7f\x1e\x14\x3b\xe3\x64\x33\x1f\xbb\x01\xfa\x0d\x0d\xd1\x6f\x04\xc1\x07\x43\xca\x90\xf6\xe6\x23\x45\x10\xaa\x0d\xd0\x97\x87\x19\x2c\xe3\xe6\x98\xf3\x0e\x89\x6f\x9e\x80\xcb\x1d\x75\x56\x5a\x12\x81\x91\x22\x22\xd4\x8d\x8c\xfc\xf3\x2a\xa4\xb9\x3e\x74\xa6\xa7\x26\x2c\x1e\x1f\x30\x8f\xaf\x5c\x61\x9d\xfb\x88\x84\xc7\x96\xb7\xb0\x85\x39\xd8\xdd\x70\xfb\xc1\x15\x57\x71\xba\xbf\x69\xf1\xe8\xb4\x9b\xf7\x8f\x34\x66\xc3\x6b\x3c\x9e\xe2\xb4\x76\xb0\xbb\x51\x70\xd7\xef\x9d\xb1\xd7\x07\x3b\xfd\xaf\x39\x5f\x8b\xc6\xbe\x28\x35\xd1\x86\x53\xe4\x03\x4c\x34\xf5\xc9\x13\xb3\xf6\x7a\x12\x0c\xc9\xcc\xb1\x0e\x5b\x91\x32\xb4\xc9\x63\x85\xed\x09\x9c\x04\x43\x6d\x12\x69\x85\x5b\x6a\x5d\x53\x60\x72\x25\x43\xae\x7b\x0d\xad\x9a\x7f\x3e\x4f\xbd\xc7\x60\x7f\x7e\xd3\x2c\x65\xd5\xff\xc8\x62\x52\xa1\x97\x18\x87\x83\x60\xf8\x99\x05\x91\x9d\x24\xa1\x5c\xdd\x82\x3e\x05\x4d\xc1\xe7\xfe\xcb\x17\x44\xc8\x73\xcb\x41\x60\x4d\x05\x05\x34\xdb\x3f\xb0\x98\xa1\x4d\x1e\x10\x18\xcc\x2e\x48\xe5\x49\xfd\x97\x2f\xd6\xf6\x62\x1a\xa5\x1f\xac\xb5\x5e\xbe\x70\x99\x0f\x4d\x3d\x26\x38\xcc\x90\xd2\x6f\x86\xe3\x95\xba\x59\x0c\x33\x2e\x80\xd1\x47\xc7\x2d\xb7\x12\x7f\x85\x96\x51\xe3\xaf\x68\xc5\x79\x40\xfe\x37\xf8\x26\xcb\x53\x1c\x4c\x76\xe2\x90\x75\xcd\x61\xfb\x99\x30\x63\x5f\x01\xad\xce\xe0\x3b\xce\x0e\x47\x78\x82\x21\x9c\x3e\x18\x9a\xd2\x39\x63\xc1\x61\x21\x0c\x42\x8c\xaf\x73\xfa\xda\x79\x26\xc1\x97\x2f\x58\xac\x60\x68\x7b\x2d\x1b\x47\x43\x5c\xe3\x08\x08\x0b\x01\x81\x89\xcf\x38\xd4\x9c\xb7\x5d\xfc\xa7\x98\xb7\xc5\x07\x18\xcc\xa1\x2f\xa2\xac\xf2\xf0\x7e\x25\xba\x39\x91\xbd\x19\xe0\x61\x32\x61\x11\x13\x08\x45\x44\xc9\x2c\x2b\x45\x32\xa2\x7f\x55\xce\x1e\x05\x5d\xaa\xcd\xed\x87\xe9\x02\xe2\x38\x95\xc2\x91\xf6\x52\xc6\xdc\xb9\x7c\x6e\x18\xc4\xab\x31\xc7\x29\x06\xca\x77\xc8\xfd\x7c\xe9\x3a\xc7\x89\xd2\x68\x1b\x45\x97\x6c\x26\x1b\xee\x15\x99\x5c\x62\xb4\xff\x33\x1c\xb1\xb3\xd9\x20\xc3\xff\x33\xc3\x71\xee\x57\x0d\x00\xba\xc2\x45\x63\xae\x91\xb7\x89\x8d\x31\x31\xee\xc9\x40\x9c\xd4\x31\x1d\x6b\x28\x5a\x13\x60\xea\xc8\xe8\xca\xfa\x3a\x62\xb3\xa2\xbc\x74\x66\xd4\x2e\x0c\x02\x44\xcd\xeb\xa5\x09\x22\x44\xf7\x11\xad\x82\xca\xc0\x49\x3a\x12\x15\x2e\xc5\xbc\x7c\x51\xce\xb0\x9c\xef\x48\xd5\x83\x1d\x76\x5b\x8f\xd2\xcf\x5f\x4c\xfa\x41\xff\x35\x4d\x71\x86\xd3\x4b\xac\xc8\x41\xc9\x8c\x1c\x6b\x14\xf9\x07\xf4\x44\x41\x1e\x0d\xc6\x8c\xe7\xa3\xdd\x14\xbd\x48\xa3\x20\x46\xaf\xa8\x97\x2d\x1a\x45\x63\x8c\xe3\xe1\xda\x50\xc2\xe1\x71\xd5\x21\xf0\xbc\x41\x9e\x27\x47\xb2\xdc\xbf\x82\x18\xbd\x4e\x67\x83\x1b\xf4\xe9\x82\xfc\xb3\x76\x85\x07\xff\x75\x3e\x09\xa2\xf1\xda\x30\x99\xcc\x13\xbd\x4e\x8e\x38\x0a\xc5\x12\x98\x5a\xae\xaa\x20\x26\x53\x2f\xc5\x43\x72\x84\xa2\xb9\xd3\xc4\xe7\xdf\x0d\xfe\x29\x72\xa4\x81\xb6\x8e\x6a\xe7\x96\x81\xa8\xe8\xaf\xdf\xd1\xfa\x7a\x72\x89\xd3\xd1\x38\xb9\xb2\x37\xe7\x26\xcf\x24\x4c\x00\x34\xbb\xcb\x3f\x90\x4a\x8e\x4d\xbc\xa5\x96\xdb\xf2\x16\x6b\xb3\xbd\x97\xe1\x61\xb0\x6b\xc8\x16\xd1\x54\x70\x5b\x5f\x47\x1c\x35\x34\xd0\xd2\x0f\x59\x4a\x51\xc0\xb4\x61\xda\x54\x03\xc4\x96\x84\x68\x7c\x76\xe4\x6a\x68\xb9\xc0\x30\x40\x6d\x2f\x20\x17\x28\x44\xbb\xeb\x00\x66\x26\xb5\xa0\x0b\xae\x14\xc8\x95\x95\x41\x7b\x3e\x40\xf3\xd9\x01\xde\x06\xbe\xb2\x32\x68\x99\xa0\x75\x40\xfa\x93\x05\xd4\x04\xb9\xb2\x32\x68\x16\x2a\x80\x81\x12\xac\x11\x82\xb7\x2b\xdb\x40\x0a\x3f\xfd\x64\x27\xdd\x95\xdf\x5b\x54\x87\xed\xfe\xac\x8f\x93\x8a\xba\x81\xb8\xbd\x45\x02\xf4\xc6\x75\xa3\xc9\x74\xdc\x3e\x40\x6c\xb3\x23\x55\x8a\x15\x82\x51\x3c\x64\x7c\xac\x36\xa4\xff\x16\xac\x57\x56\x82\xde\x96\xf2\x35\x2e\x5f\x2e\xb3\xc5\xbb\xec\xed\xc2\xfa\x3a\x22\x62\x05\xdc\x43\xa2\x48\xe1\x86\x94\xf9\x59\xec\x72\x29\x43\x00\x3e\x43\x49\x3c\xbe\xa1\x3c\x75\xf7\x97\xc3\xa3\x5d\xf4\x09\xfd\x84\xb6\x34\xd8\x1c\x8f\xa6\x0b\x39\xe3\x2a\xda\x31\x54\xac\xa4\x73\xb4\x20\xa3\x9b\x7a\xc0\x14\x4c\x72\xcd\xf3\xfa\xdb\x1d\x63\x2a\x9e\x62\xcc\xd2\x86\x44\x6c\x35\xc9\xf6\x3c\xd1\x26\x1f\x35\x13\xeb\x42\xe1\xd9\x27\x3e\x23\x1f\xfc\x42\x51\xba\xb2\x30\x3d\x5f\x9c\xb6\xf8\x81\x63\x99\x38\xee\x95\x55\x61\x9b\x15\x53\x90\xd6\xfa\x59\x49\xc6\xf6\x48\xd9\x8b\xc8\xd9\x15\x25\x6d\x7d\xa9\xf8\xce\xf1\x92\xee\xd5\xe3\xbc\x58\x1a\xcf\x6d\x21\x5d\xd4\x30\x65\xf5\x62\x69\x5d\xa9\x56\x59\x68\x6f\x3f\x0a\xed\x7f\x31\xa1\xbd\x48\x54\x9f\xab\xaa\x3c\x39\x9a\x2b\x28\x2f\x20\x21\x3b\x76\x12\x73\x0b\x79\xdc\x3b\xbe\xff\xbd\xc3\xec\xfa\x83\x6e\x0c\xfb\xf1\x30\xc5\x10\x5a\x85\x81\x76\x8a\x45\x72\x2a\x57\x11\x35\x65\xf3\x7c\x01\xeb\x95\x2f\xa8\xf1\x7d\x6d\x40\x15\x76\x9e\x2a\xea\x21\x52\xba\xf2\x4e\xd3\x79\xdc\x69\xfe\xb2\x3b\xcd\xde\x18\x0f\xf3\x34\x89\xa3\x21\xea\x27\x21\x1e\x24\x49\x95\x0b\xb2\xbd\xfe\x9c\x0b\x32\x5a\xa0\xea\xae\xb3\xd7\xd7\x2f\xc8\xc8\xf3\x7d\xed\x32\xea\xfe\xa1\xb3\x2d\xbd\x5a\x09\xd5\x3f\x41\x4b\x5b\xc6\x0f\x85\x66\x88\xef\x82\x26\x4f\xc4\xd7\xaf\xa2\x68\x86\xd2\x95\x39\xc9\x9f\x30\xa3\xff\x23\x27\x29\xc9\x49\x0e\x67\xf9\x74\x96\x2f\x72\xcd\x7e\x38\xef\x9a\xfd\x70\xa1\x6b\x76\x53\x76\x3d\x34\xae\xdd\x0f\xbf\xe9\xf5\xed\xc3\xca\xae\xf6\x35\x9a\x78\x73\x6f\xf2\x6b\x41\x1b\xdf\x5a\x86\xfd\x53\xa9\x2b\x0e\x0d\xb3\x83\x02\x69\xf1\xb0\xd2\x65\xe2\xe1\x42\x97\x89\x8f\x79\x53\xff\xba\x3c\x7e\xe7\xdd\xf1\x3e\xfa\x75\xed\x59\xab\xcd\xbd\x5e\x50\x96\x13\xd6\x71\x7e\x53\xc0\xe4\xa7\x41\xb8\xb6\x13\x67\xd1\xaf\xa4\x9e\x9a\x74\x74\x1a\x84\x2a\x87\x0d\x83\x3c\x50\x8c\x23\xbc\x46\x11\x2a\x77\xe0\xfe\x40\xc7\xd2\xc1\x41\x73\x78\x78\x6e\x94\x1d\x98\x79\xa2\x06\xde\x0c\x51\x40\x6a\xb3\x38\x17\x5d\x35\xe3\xf7\x81\xe1\xf1\x7b\xfa\xd1\x02\x47\x5f\xac\xea\xc8\xfd\xdd\xf8\x6e\xb7\x47\x03\x6a\x8d\x83\x8c\x86\x0b\x44\xd3\x24\x8b\x8c\x00\x21\xa4\x59\x52\x80\x40\x78\x9f\xf0\x1e\x8b\x36\x56\x0c\x9c\x56\x51\xd3\x6c\xe6\x7d\x10\x2a\x2f\x60\xbc\x44\x32\x29\xe3\x3d\x65\x67\x6a\x73\x32\xa0\xa0\xde\x8e\x0c\x28\xa8\x96\x76\x86\x16\xd4\x5d\x52\x56\x0c\x48\xca\x95\x86\xe2\x95\x30\x8b\x1d\xd4\x62\x91\xc8\x2b\x9c\xa3\x78\x06\x66\x4d\xc9\x48\x9f\x38\x7a\x65\x23\x46\x76\xce\x3c\x2a\x9d\xaf\xe9\xe8\xae\xa2\xe6\x32\x1f\x04\xc7\xdd\xab\xdc\xdb\x6c\xbf\x30\x03\x90\xbf\xdf\x7c\x99\xfa\x19\x38\x5f\x53\xd7\xcf\x5a\xed\x05\x98\xf8\xdd\xd3\x5f\x3e\x32\xf1\xef\x95\x89\xef\x1f\x1f\x22\x08\x7d\x5e\x95\x87\xef\xb3\x90\xe9\xf7\xc1\xc3\xff\x08\x6c\x59\x2e\xc5\x79\x7c\xd9\xc5\x30\x69\xb2\x19\x77\xdc\xcf\xb5\x34\x88\xc3\x64\x52\xb3\x38\xf1\xf2\xb2\x9a\xac\xa8\x18\x06\x4b\x45\x79\x6a\xb1\xd9\x56\xe7\xac\x4e\x40\x3d\x32\xca\x92\x8c\x92\x13\xee\x02\x8c\xf2\x4f\x98\xe2\xe7\x91\x51\x96\x67\x94\xeb\xfb\x7b\x7d\xf4\x6c\xf3\xd9\xe6\x6a\x13\x31\x12\x44\x07\x38\xbf\x48\x42\xd4\x9a\xcf\x2d\x21\x99\xc4\x5d\xb8\xe5\x4e\x18\x52\x67\x71\x63\x21\x96\xe0\x40\x9c\x7b\x90\xea\xf4\x0f\x67\x1a\x7a\x13\xff\x1b\xa7\x09\x24\xc9\xcc\x2f\x30\x4a\x71\xa6\x72\x66\xad\x3f\xa4\x20\x1b\x02\xf2\x6c\xe1\x7e\x17\x6e\xc4\x18\xc1\x7f\x18\x2e\x06\x37\xf0\xa2\x01\xc0\x29\x44\x0f\x6b\x49\x62\x8c\x26\x49\x8a\xa9\x30\xbd\xba\x0a\xbd\xf4\x8e\x28\xe7\x38\xab\xab\x8b\xb0\x18\x98\xed\x05\x58\xcc\xdd\x73\x6f\x3c\xb2\x98\xef\x95\xc5\xec\xa0\x38\x49\xa6\x55\x05\xb1\x77\x7c\x05\x14\xf1\x16\x75\xc8\x0a\x57\xa4\x56\xb0\x1c\xb5\x0b\x04\xaa\x93\xfb\xe6\xdd\x63\x9e\x3f\x92\xfb\xf7\x4a\xee\xff\x5b\x61\xf0\x65\x89\x5d\x61\xf8\xdf\xfc\xe0\x51\x41\x71\xa2\xaa\x37\xac\xd3\x47\xad\x56\x7c\xfe\x58\x46\x5f\xbe\x98\xaf\xee\xb2\xbf\x7a\x3a\x5f\x22\x90\xce\xfa\x3a\xfa\x40\x9a\x30\x6a\x46\x56\x78\x1c\x50\x33\xc9\x42\x57\x17\xd1\x18\xa3\xda\x93\x9a\x0c\x37\x22\x93\x51\x80\xb7\xbd\x95\x7c\x42\x98\xd4\x9b\x4a\xf5\x48\xdd\x8f\x75\x95\xb9\x19\xdc\x04\x22\xc3\x57\xde\xbc\x15\x12\x5b\x80\xa1\xfd\x09\x7d\x8b\x1d\xd9\x38\x69\x3a\xcc\x6f\xc1\xdc\x64\xce\x43\xc0\xe1\x0f\xc6\xdf\xfc\xc9\xb7\xee\x31\x03\xc4\x8b\x20\xc3\x22\xad\x03\x79\xd0\x3f\x97\x4e\x10\x61\x65\x7f\x50\x3f\x1e\xbf\xde\x69\xca\xe4\x06\xe4\x49\xff\x0e\x49\xa7\xc5\x77\xf2\xe4\x8f\xb3\xf0\x3e\xc8\x32\xc2\x4c\x56\x09\xb2\x21\x7a\x83\x6f\xd0\x2e\x4e\xa3\x4b\x9a\x7e\xfa\x25\x1f\xb2\x56\x99\x9c\x0b\xef\x5f\xbc\xd9\x7d\xd9\x92\x2d\x8b\x67\x02\xdb\x75\x83\x6b\x25\xcf\xee\x27\xf1\x28\x3a\x9f\xb1\xe4\xd7\x09\xa4\xa1\xce\x8a\x53\x5a\xa7\xc9\x14\xa7\xf9\x0d\xfa\x9d\x2a\x49\x20\x2a\x03\xec\x0a\x27\x17\x70\x4f\x8a\x32\xf2\x10\xc5\x2c\x7d\x4f\x9e\x08\xff\xcf\x35\xb4\x8b\x47\xc1\x6c\x9c\xf7\x50\x07\xd5\x9a\xad\x2d\x34\x88\xf2\x6c\xd9\xdb\x00\x0d\x82\x70\xcb\x73\x30\x9f\x88\xf0\x21\xcc\x1b\x57\x81\x47\xe6\x64\x2e\xa2\x51\xce\x72\x76\x67\x00\x4b\x2a\x79\x94\x0f\x79\x82\xa6\x38\x1d\x25\xe9\x44\x81\xae\x83\x56\x73\x4d\x0f\x47\xe7\x3d\xef\x68\x23\x3e\x34\x3d\xd4\x6c\x6d\xad\xb7\x5b\x7a\x32\x0a\xda\x17\x8a\xbb\xfe\x45\xe2\xa3\xb7\x7d\xbb\x5c\x9c\x0a\x5d\x26\x21\xca\x50\x80\x62\x7c\x35\xbe\x41\xf4\xb4\x1b\xc2\xcc\x84\x92\xd0\xf8\xda\x2c\x93\xbe\xfc\x10\x78\xeb\x2d\xe9\xac\x9a\xaa\x9c\x8c\xe1\xd0\x45\x3f\xdc\x59\x7a\x94\xa4\x70\xaa\x94\xcd\xde\x53\x3e\xf2\xcf\xe1\x48\xb5\x7a\x50\x68\x9f\x1f\xee\x75\x5b\xcc\x2a\x35\x7f\x97\x93\xb6\xa5\x64\xbd\xbe\x13\x9c\xba\x3e\xa3\x8d\x46\xc3\x82\xac\x12\x55\x14\x47\x5a\x66\x94\xe1\xe8\xdc\x69\x04\x45\xe6\x63\x5b\xfc\xe4\x14\x48\x4a\xfb\x03\x58\x39\x38\x00\x5c\xac\x51\x5f\xf0\xb2\xcc\x69\xb1\xa4\xf7\x53\x06\x1d\x28\x87\x3f\xac\x95\xad\x9d\x05\xe3\x1c\xed\xc0\x3f\x8b\xa4\xc3\xe7\xbe\x99\x7a\xd0\x98\x3b\x51\x21\x26\xbb\xca\xe7\x70\xb4\xc6\x82\x8a\xd5\x78\x9f\xea\x80\x65\xc1\x0c\xb3\x0a\xea\x24\x1b\x75\x2b\xa4\x60\xd3\xc9\xc0\x65\x55\x1c\xe5\xee\xc4\xf4\x2c\x58\x0d\x04\xc5\x61\x64\x4b\x08\x89\x32\xa6\xba\x98\x22\xa7\xa5\x32\x70\x1a\x1a\x81\xd3\x11\xaa\x92\x0e\xf6\x1b\x18\x21\x4b\xf3\xe6\x88\xc1\x08\x12\xfd\x7e\x1c\xe2\x6b\x57\x85\xd3\xc6\x35\xd3\xd4\xd9\xa1\xb1\x8b\x43\x66\x0a\x3c\x84\x30\x2f\xde\x70\x91\xde\x8f\x0a\xaf\x23\xdf\xf8\xea\xf0\x2d\x70\x9b\x4c\xc6\x1a\x7b\x72\x98\x26\xc9\x7d\x86\x96\x94\x2f\x8a\x6d\x8f\xb4\x6f\xec\x00\x61\xf4\x8d\x87\x2c\xfc\x89\x23\x63\x1b\xe0\x88\xde\xa1\x6d\x98\xfa\xb5\xd9\x34\x24\xc3\x0b\x04\xb7\x36\x62\xf1\x49\x6b\xb2\xbb\xcb\xa6\xab\x26\xd4\x4a\x71\x86\x73\x2b\x30\xdd\xdc\xfc\x44\x00\x56\x1b\x52\xd7\x68\x9a\x65\x45\x46\x41\xf9\x8a\xf5\xd4\xd5\xfe\xbe\x18\x4f\x07\xd0\x48\x8d\x1f\xcb\x00\x9a\x8d\x6b\x76\x4d\x4d\x6a\xd7\xa4\x4c\x92\xdb\xb0\x09\x99\xa0\x61\x98\xc4\x78\xaa\x1f\xad\x11\x9d\x33\xa6\x45\x81\x18\xed\x5e\xf1\xd1\x55\xdf\x39\x83\x44\x32\xc0\xbf\x1e\x1e\xe9\xa8\x83\x5b\xa6\x1d\x07\x44\x1b\x99\x4f\xd4\xe2\xeb\x13\xb7\xf8\x52\xa6\xe9\x39\xfa\xe4\x1e\x1f\xa4\x14\x3d\xfd\x04\x86\x5f\x16\xde\xa7\x9f\xce\xca\x38\x1d\x1b\x2f\x94\xf5\xcc\xb4\xfe\xd0\x92\x35\xce\xc6\xaa\x3e\x6d\x9c\xad\xac\x14\xd9\xc9\x29\x70\x95\x43\x34\x5f\xe7\x76\x48\x50\xb6\xeb\xc8\x6a\x25\xcc\x8f\x8d\x93\xc1\x5d\x77\xe2\x92\x61\xd8\xe6\xed\xc2\xd5\xf7\xe0\xfb\x92\x14\xa3\x8c\x6d\x8c\xa6\xac\x58\x2a\xb4\x9b\x7b\x83\xbf\xff\x08\x6f\x74\xdb\x17\xa2\x1f\x95\xfa\x0a\xb6\xfe\x6a\x35\xeb\xc8\x2f\x80\xde\x05\xd2\x3c\x11\x54\x8b\x43\x27\xce\x91\x3e\xf1\xa4\x8e\x2c\x99\x94\xcd\x8a\x2e\x04\x93\x52\xf3\xa5\xa3\xdb\x72\x96\x99\x14\x74\x51\x3c\x3b\x33\xb7\x3e\x4f\x16\x7f\x7b\x56\xdf\xfc\x13\x46\x7a\xc1\xf1\x70\x75\x10\x64\xb8\xdb\x31\x3e\x4c\xc2\x0d\xb3\xe8\xe5\xf4\x73\x38\xfa\xa3\x29\xe7\x95\x0e\xc0\x33\xc1\x9b\x7e\xa0\xe8\xfe\xd1\x35\xf8\x5f\x45\xc3\x75\x0c\xe6\xd1\x7d\x6e\x29\x4f\xb5\x58\xea\xcb\x39\x8a\x2c\x15\xad\x23\x3c\xcb\x82\xc1\x18\xb3\x68\x9e\x3a\x76\xc7\xc8\xc8\xb5\x4c\xc1\x39\x5e\xbe\x42\x56\xca\x53\x7d\x2b\x3b\x0a\x06\x83\x28\x47\xcc\xe4\x9d\x59\xf9\x3b\x15\x5a\x6a\x2d\x08\x50\x16\x65\x28\x40\x63\x7c\x1e\x0c\x6f\xd0\x25\x4e\x33\x88\x44\x7a\x11\xe4\x28\xc6\xe7\x63\x3c\xcc\x71\x48\x36\x8d\x21\xcb\xb6\x9e\x33\x9d\x53\x9e\xa0\x71\x94\xe7\x63\xbc\x4a\xe3\x5e\xaf\x59\x70\x71\x9a\x26\x29\x0a\x13\x9c\xc5\x4b\x39\x0a\x46\x23\x3c\xa4\xd5\x29\x76\x4b\x19\xca\xf0\x70\x96\x46\xf9\x4d\x5d\xad\x3b\x98\xe5\x28\xca\xa1\x1e\xaf\x14\xe5\x99\x08\x0f\x14\x8d\xa3\x9c\x45\xb3\xa0\x89\xdf\x23\xb2\x83\x4c\x70\x4c\xf7\xb0\xcc\xaf\xbb\xa3\x43\xf4\x96\x76\x54\x68\xf0\x8c\xb7\xea\x34\x2f\x9a\x47\xb5\xf0\x80\xf4\x46\xf5\x50\xf0\x9d\x6e\xa4\x47\x05\x9c\x59\x2e\x0b\xce\x2a\x11\x3b\x17\x92\x8d\x16\xfb\x4e\x87\xbf\x8a\x46\xc9\x2f\x33\x8f\xeb\x9b\xd3\xc6\x59\x1d\xd5\xde\x9c\xb6\xcf\x58\x68\x16\xf4\x85\x3c\xb2\xdb\x91\x66\x77\xd9\xcc\xe9\xfa\xe6\xb4\x49\x6b\x34\xf4\x1a\xed\x82\x1a\x2d\x5a\xa3\xa9\xd7\x68\x14\xd4\x68\xd3\x1a\x2d\xbd\x46\x53\xd4\xd0\x2a\x38\x72\x1a\x5a\x63\xc4\xbd\x9a\x3d\xa3\xd4\x17\xa3\xd4\x77\x8c\x92\x8d\x89\x32\x3e\xac\x2b\xf4\xd2\x68\x34\xe2\x39\x80\x29\xba\x34\xf6\x7a\xa3\x41\xbe\x58\xdd\xb4\x87\xbd\xad\x83\x6d\x3a\xc1\xb6\xe6\x83\x6d\x78\x47\x5a\x01\x60\x80\x6d\xcf\x07\xdb\xf4\x4d\x47\x5d\x01\x60\x80\x6d\x18\x60\xe7\xcd\x5c\x3f\x48\xd3\x1b\x34\x30\x72\xaa\xd3\xb9\x19\xd0\xd8\x42\x96\x7e\x24\xa7\x73\x4d\x18\x4c\x76\x93\xe5\x78\x82\x46\xc9\x2c\x45\x79\x34\x31\xa6\xba\x4a\x50\xfe\x18\x5f\xe7\xc7\x64\x65\x79\xa3\xc9\xdb\xd1\xef\x0f\x92\x30\x1a\xdd\x50\x4e\x47\x09\x6e\x7e\xfb\x5b\x9e\xf6\xfb\xa7\xd4\x69\xe7\xd7\x53\x48\x34\x0d\xa1\xbe\xcc\xb4\xad\x8e\xd4\xf8\x3f\xa3\x0c\xe7\xb3\xa9\xf6\xde\xe7\x3a\x35\x47\x89\xb0\xff\x33\x75\xa0\xf2\x6a\x0e\xf6\x7f\xfe\xd8\x40\xdb\x68\xff\x67\x2b\x4b\xa9\xf8\xde\xa4\xdf\x9b\x76\xee\x02\x75\x95\xc2\x74\x65\xb3\xc1\x25\x26\xc2\x82\x53\x9f\xd0\xa0\x79\x0e\xa0\x49\x9a\xe8\xe0\x0b\xa2\x4f\xbe\x44\x07\x6a\x71\x96\xd7\x40\x94\x97\xd7\xbc\xce\xbc\x06\xac\xcd\x96\x68\xb3\xa9\xb5\xd9\x9c\xd7\x66\x53\x6f\xb3\x59\xa1\x4d\x88\xcf\x16\x35\xf8\xca\x22\x10\xa2\x96\xbe\xb0\x9c\xf5\xda\x50\xaf\xc5\x17\x28\xd4\x6b\xe8\x4b\xcf\x35\x05\x8c\x62\x0b\xd8\x23\xa1\xc4\x06\xd5\x22\x58\x91\xcf\xfa\x84\x07\x90\x2f\x4d\xfb\x4b\x8b\x7e\xb1\x22\x90\xf5\x09\xa3\x20\x5f\xac\xb0\x67\xfd\xd3\x8e\xb7\x9d\x0d\x6f\x3b\x5d\x6f\x3b\x9b\xa2\x1d\x9f\xf2\xaa\x0c\xcf\xa8\xc6\x35\xca\xf0\x8d\xa2\x28\x6c\xf7\x93\x01\xdd\xa7\xbd\x56\x84\x81\x32\x6c\xf4\x6f\xf3\xfb\xe5\xdd\x78\x61\x34\xa5\x27\x26\xf0\x10\xad\xf0\x31\x8d\x87\xf6\x2b\x10\x17\xaa\xfd\x4a\xa6\x98\x13\x3d\x3c\x8b\xbd\xf1\xb9\x51\xaf\x49\xeb\xb5\x58\xbd\x4d\xa3\xde\x86\xaf\x5e\x8b\xd6\xeb\xb0\x7a\x4d\xa3\xde\xa6\xaf\x5e\x9b\xd6\xeb\x9e\x09\xbc\xb4\x7a\x4d\x59\x6f\xe1\xcd\xa6\x20\x91\x0c\xc1\x80\x67\x79\x81\x5f\x32\xcd\x0b\x3c\x56\xcb\xf3\xc2\x21\x30\xe6\x24\x20\xb8\x32\xbd\xd8\x38\xba\xbc\x53\x91\x9a\xea\x9a\x6e\x59\xc7\xc5\xfe\xa5\x1a\x89\x0b\xf3\x22\x79\x6f\xab\x7c\x8d\x2e\xe9\xa7\x6e\x67\xbd\xdd\x92\x3a\x0c\x67\xf4\x4c\x41\xa0\xb5\xd2\x2e\x7b\xda\x7a\xd0\x3f\x29\x12\xa1\x69\x89\x74\x1c\x5c\x62\x94\x8c\x43\x3f\xd7\x2c\xbf\xcf\xf7\x3f\xd2\xc9\xed\x5b\xb1\x7a\xf5\x36\xfb\xc1\x78\x38\x1b\x93\x85\x15\xe3\x2b\x7f\xc3\x7d\x96\x92\xad\x4f\x53\xb2\x35\xae\x3b\x61\x1b\xfe\x87\x56\xb8\x0c\x65\x25\x4f\xeb\xb3\x1c\x6d\x7d\x9a\xa3\xad\x71\xcd\xaa\xb4\x21\xf7\x4b\x9f\x4b\x8f\x8d\x65\xf4\x13\xaa\xf5\x3f\x2a\xcf\xff\x44\x4d\xd4\x43\x8d\x65\x07\xc8\x16\x03\xd9\xa2\x20\x19\xc4\x0e\x03\xd9\x34\x40\x36\xcb\x80\x6c\x33\x90\x6d\xab\x67\x35\xda\x90\x06\xb2\x55\x06\x64\x87\x81\xec\x38\x3b\xde\x36\x40\xb6\xcb\x80\xdc\x60\x20\x37\x9c\x1d\xef\x18\x20\x3b\x65\x40\x76\x19\xc8\xae\xb3\xe3\x1b\x06\xc8\x8d\x32\x20\x37\x19\xc8\x4d\x67\xc7\xbb\x06\xc8\xee\x7c\x90\x52\x40\xa7\x50\xb5\xfa\x9b\x66\x7d\xcb\x93\x4a\x50\x37\xd9\x7c\xce\x57\xef\xb0\xa0\x48\xa1\xf3\x6b\x60\xd8\x11\xe9\x5c\xdf\xce\x9b\x05\xfa\xc1\x38\xcb\xd3\xd9\x30\x47\x17\xd1\xf9\x05\x0a\xe2\x10\x8d\x93\x2b\x14\xa4\xe7\x33\x08\x09\x05\xd1\x00\xfe\x67\x16\xa4\x66\x46\x3d\x00\x1f\xa0\x6d\xd2\x06\x17\xca\xec\x43\xfc\xf9\x80\x96\xa0\xfb\x84\xeb\x90\xc3\xfb\xab\x35\x9f\xe2\x6c\x36\xce\x51\x32\x2a\x68\xfb\x82\xee\x04\xb5\xf3\x00\xfd\x88\xce\x03\xea\xda\xd4\xdc\x5c\x46\x2b\x88\xbe\x1a\xb0\x57\x1b\xf0\x6a\x00\xaf\x1c\x18\x8e\x29\x1c\xa5\x1b\xf4\xd0\xf6\x23\x3a\xbf\x86\xd9\x5d\x06\x62\xe0\x05\x84\x10\xa9\x14\x70\x24\x6a\x23\xbd\xf9\xf5\xf0\x08\x99\xa1\x87\x5f\x51\x2e\x77\x7e\x81\x7e\x43\xe7\xe3\xf2\x8c\xce\xa3\xda\xf8\x95\xb1\xb9\x57\x94\xcd\xd5\x6a\xaf\xe4\xee\x4d\xb6\xb5\x57\x8a\x50\xb0\xcc\x0a\x74\xf5\x02\x5d\x59\xc0\x22\xe6\x5f\x19\x47\x7c\x45\x39\x62\x8d\xb6\x23\xf7\xdd\x57\x9c\x07\xc2\xbe\xbb\x82\x48\x73\x0e\x20\x2d\x06\xa4\xc5\x81\x34\x75\x14\x9a\x16\x8e\x0d\xbd\x40\xa3\x10\xc7\x36\x03\xdf\xe6\xe0\x5b\x3a\x8e\x2d\x03\xc7\xa6\x0b\xc7\x0e\x03\xd2\xe1\x40\xda\x3a\x0a\x6d\x0b\xc7\x96\x5e\xa0\x55\x88\xe3\x06\x03\xbf\xc1\xc1\x77\x74\x1c\x3b\x06\x8e\x6d\x17\x8e\x5d\x06\xa4\xcb\x81\x6c\xe8\x28\x6c\x58\x38\x76\xf4\x02\x9d\x42\x1c\x37\x19\xf8\xcd\x33\x8d\x52\x04\x8e\x5d\x03\xc7\x0d\x13\xc7\x0a\x99\xb9\x32\x9e\xe8\x49\x28\x42\x4b\xa7\x7a\x2a\x77\x61\x46\xe1\xe6\xf8\x3a\x57\xef\xac\x54\x4d\x27\x0f\x47\xa2\x65\x49\xa2\x77\x4c\xce\xeb\xaf\xe9\x38\x20\xc8\x5d\xe7\xc8\x0b\x91\x05\x86\xaa\xc9\xc6\x3d\x40\xd5\x7b\xaf\x22\xed\xab\x9e\x4b\x4b\x2d\xb9\x5c\xee\xfa\x4a\xad\x52\xd1\x96\xba\xae\xdf\x95\xf4\x36\xda\x75\x79\x49\xd2\xdb\xd8\xa8\xb3\xbb\x93\x5e\xb7\x71\x7b\x56\xdf\xfc\x13\xc6\x47\x7d\xbc\xea\x7a\xbc\xea\xfa\x1e\xaf\xba\x9c\x8c\x46\x5e\xff\x98\x17\x3f\xdf\xd1\x95\xcf\x7d\x24\x99\x7d\x23\xb4\x08\x6f\x74\x2d\xc2\x9b\xea\x5a\x84\x37\xba\x16\xe1\x4d\x91\x16\x41\xad\x6a\xeb\xab\x1f\x2f\xb3\x1e\x2f\xb3\x1e\x2f\xb3\x94\x2a\x8f\x97\x59\xca\x58\x3c\x5e\x66\x3d\x5e\x66\x3d\x5e\x66\x3d\x5e\x66\x3d\x5e\x66\x3d\x5e\x66\x99\x7f\x1e\x2f\xb3\x94\xd7\x8f\x97\x59\xa2\xce\xe3\x65\xd6\xe3\x65\xd6\xe3\x65\xd6\xe3\x65\xd6\xe3\x65\x16\x07\xff\x78\x99\xf5\x78\x99\xf5\xa7\xbb\xcc\xba\xcf\x6b\xac\xbb\x5c\x60\x95\xb9\xba\xaa\x74\x69\xf5\xb0\xd7\x55\x7f\xc2\x24\x4b\x8f\xd7\x55\x8f\xd7\x55\x55\xae\xab\x8c\x2b\xa5\x7e\xa7\x8c\xeb\x94\x71\xa1\xd4\xef\x28\xb7\x49\xf0\xf0\xe0\x57\x49\xd4\x0d\x55\x5c\x26\xd9\x52\xd0\x67\x19\x78\xa1\xe0\xb2\x09\xbc\x54\x55\x1f\x6b\x25\x47\x81\xa5\x44\x89\xa3\x1c\x65\x83\xe4\xda\x82\x72\x2c\x50\x39\x36\xee\xf1\x90\x47\xc0\x6d\x6d\x74\x3d\xea\x02\xa6\x11\x88\xe6\x69\x83\xdf\xe0\x1b\x87\x3a\x58\x6f\xab\xce\x3d\xe6\x8b\x9b\x64\x03\x41\xba\xcd\xe3\x60\x44\xe8\xef\xea\xd8\xb8\xd4\x92\xac\x06\xd9\x60\xf9\x58\x9f\x6a\x50\xec\xb8\x72\xda\x67\x2b\xc4\x9c\xa5\x95\x20\xc8\xd7\x3e\xa1\x15\x3a\x26\x2b\xbc\xc5\x65\xf4\x77\xe8\x8c\x2b\x08\xc4\x55\x30\x75\xa0\x0a\xdb\x92\xa5\xb8\x10\x63\x7d\xec\xf0\xff\x27\xef\xc8\xac\xce\x9b\x86\xbe\xeb\x2e\x80\x65\xc6\x11\x54\xf1\xc9\xcc\x02\x7e\x77\x55\x20\x57\xcc\x80\x52\x86\x47\xb9\x7a\xc3\x75\x72\x64\x36\x3c\x3a\x4a\xbd\x69\xe1\xa5\xdd\xda\xe8\xfa\x14\x34\x8d\x39\xca\x19\x67\xeb\xe5\x15\x35\xca\xe2\x31\x15\x35\x72\x04\x23\xf3\xd3\x27\x39\xb8\xe6\x89\xd3\xce\x9c\x04\x2a\x4a\x03\xc0\x67\x15\x5b\x73\x7e\xc4\x32\x8a\xe9\xfa\x89\xa9\x7e\x2f\xb6\x57\x4f\x04\xba\x79\x9a\xef\x92\x91\xa5\xfa\x59\x23\x61\x37\xe1\xba\xc8\xd6\x4f\xb4\x5e\x92\x95\x04\xab\xbd\xd6\xbb\xf9\x85\xd4\xac\xb1\xe5\x44\x6a\x30\x94\xce\x64\xa2\x90\xd8\x4a\x0c\x62\x1e\xe3\x3e\x40\xe8\x16\xd7\x2d\x98\xa4\xfb\xc8\x56\x20\x90\xa1\xd0\xe7\x8a\x49\x7c\x1a\x8a\xdf\x95\xe8\xdd\xef\xdc\x93\xdc\xdd\xef\x2c\x28\x74\xdb\x9b\xad\x21\x71\xf7\x3b\x05\x61\x3f\xe0\x86\x28\xc2\xe1\xc2\xdb\xfe\x6e\x9a\x4c\xb5\xad\x9f\xbd\x80\x91\xf9\x4a\x31\x00\x43\xd2\xa4\x1e\x54\xcf\xb8\x9b\x20\xd4\x41\x4a\x89\xb0\x7a\xa8\xf9\xac\xa5\xc1\xb5\xe2\xea\x91\x0e\x28\x81\xcd\xf4\x35\x4f\x60\xf5\x0c\x18\x46\x7c\xbc\x32\xf2\x0d\x69\x83\x97\x2b\xbc\x78\x21\x83\xea\xdf\xe5\x85\xa9\x0c\x41\x8b\x6c\xf5\xff\x80\x1d\x7f\x75\xd5\xde\xe8\xab\xec\x13\xa5\xb2\xa0\xfd\x71\x17\x26\x19\xb3\x7b\x5b\x9c\x00\x6c\xd1\x05\xca\x96\x44\xf1\x22\x25\x85\xca\x9e\x8b\xfb\x9d\x87\x3d\x14\xdf\x3d\x5f\xe8\x37\x3d\x0d\x7f\xe5\x33\xeb\x1f\xec\xf4\xb9\xfe\x23\x30\x49\x9c\xe1\xf4\x52\x2c\x9e\xda\x70\x19\xb5\x1a\xcd\x16\x1a\xdc\xa0\xfe\xff\xf7\xff\x86\x69\x34\x44\x07\x38\x8b\xa3\xf1\x1a\xda\x19\x8f\x51\x1a\x9d\x5f\xe4\x19\x62\xb5\xc2\x35\xb1\xc0\x8f\x70\x18\x65\x79\x1a\x0d\x66\xd0\x60\x10\x87\x10\x33\x29\x8a\x51\x96\xcc\xd2\x21\x86\x37\x83\x28\x0e\xd2\x1b\xc2\x94\x26\x59\x9d\x05\xa4\x48\xe1\xdf\x64\x96\x93\x29\x8c\x46\xd1\x10\x38\x7c\x1d\x05\x29\x46\x53\x9c\x4e\xa2\x3c\xc7\x21\x9a\xa6\xc9\x65\x14\xe2\x90\x46\xd9\x20\x7c\x62\x94\x8c\xc7\xc9\x55\x14\x9f\xa3\x61\x12\x87\x11\xe5\x21\xa4\xd2\x04\xe7\x3d\x81\xd6\xaa\x81\x58\x06\x7a\x75\x8a\xd1\x30\x09\x31\x9a\xcc\xb2\x9c\x2c\x9c\x20\x8a\x01\x6c\x30\x48\x2e\xc9\xa7\xe9\x0d\x74\x15\xc5\x49\x1e\x0d\x71\x9d\x06\x7e\x1a\x47\x19\x28\xe6\xd5\x16\xe3\xd0\x40\x27\x8c\xb2\xe1\x38\x88\x26\x38\x5d\xf3\x63\x11\xc5\xea\x60\x70\x2c\xa6\x69\x12\xce\x86\xf8\xde\x11\x41\xac\x73\x61\x32\x9c\x89\xb0\x1f\xa4\xc6\x7a\x92\xb2\x90\x20\x93\x20\xc7\x69\x14\x8c\x33\x39\xd4\x30\x3f\x50\x4d\x41\x5d\xce\xf8\xc9\xeb\xfd\x63\x74\x7c\xf8\xf2\xe4\x97\x9d\xa3\x3d\xb4\x7f\x8c\xde\x1f\x1d\xfe\xbc\xbf\xbb\xb7\x8b\x5e\xfc\x1b\x9d\xbc\xde\x43\xfd\xc3\xf7\xff\x3e\xda\x7f\xf5\xfa\x04\xbd\x3e\x7c\xbb\xbb\x77\x74\x8c\x76\xde\xed\xa2\xfe\xe1\xbb\x93\xa3\xfd\x17\x1f\x4e\x0e\x8f\x8e\xd1\xd3\x9d\x63\xb4\x7f\xfc\x14\x3e\xec\xbc\xfb\x37\xda\xfb\xf5\xfd\xd1\xde\xf1\x31\x3a\x3c\x42\xfb\x07\xef\xdf\xee\xef\xed\xa2\x5f\x76\x8e\x8e\x76\xde\x9d\xec\xef\x1d\xd7\xd1\xfe\xbb\xfe\xdb\x0f\xbb\xfb\xef\x5e\xd5\xd1\x8b\x0f\x27\xe8\xdd\xe1\x09\x7a\xbb\x7f\xb0\x7f\xb2\xb7\x8b\x4e\x0e\xeb\xd0\xa8\x5d\x0d\x1d\xbe\x44\x07\x7b\x47\xfd\xd7\x3b\xef\x4e\x76\x5e\xec\xbf\xdd\x3f\xf9\x37\xb4\xf7\x72\xff\xe4\x1d\x69\xeb\xe5\xe1\x11\xda\x41\xef\x77\x8e\x4e\xf6\xfb\x1f\xde\xee\x1c\xa1\xf7\x1f\x8e\xde\x1f\x1e\xef\x21\xd2\xad\xdd\xfd\xe3\xfe\xdb\x9d\xfd\x83\xbd\xdd\x35\xb4\xff\x0e\xbd\x3b\x44\x7b\x3f\xef\xbd\x3b\x41\xc7\xaf\x77\xde\xbe\x75\xf6\x92\xe0\xae\xf5\xf1\xc5\x1e\x7a\xbb\xbf\xf3\xe2\xed\x1e\x6d\xe9\xdd\xbf\xd1\xee\xfe\xd1\x5e\xff\x84\x74\x47\xfe\xea\xef\xef\xee\xbd\x3b\xd9\x79\x5b\x47\xc7\xef\xf7\xfa\xfb\xe4\xc7\xde\xaf\x7b\x07\xef\xdf\xee\x1c\xfd\xbb\xce\x60\x1e\xef\xfd\xaf\x0f\x7b\xef\x4e\xf6\x77\xde\xa2\xdd\x9d\x83\x9d\x57\x7b\xc7\xa8\x36\x67\x48\xde\x1f\x1d\xf6\x3f\x1c\xed\x1d\x10\x9c\x0f\x5f\xa2\xe3\x0f\x2f\x8e\x4f\xf6\x4f\x3e\x9c\xec\xa1\x57\x87\x87\xbb\x30\xd0\xc7\x7b\x47\x3f\xef\xf7\xf7\xfe\x7f\xf6\xde\x7d\x3b\x8a\x1b\x7b\x18\xfd\xfb\xe4\x29\x34\x73\xd6\x40\x77\x5c\xd8\x25\xd5\x4d\x45\x70\xce\x01\x63\xc6\x9c\x40\x60\xd9\xce\x8f\xfc\x16\x0b\x98\xba\xa8\xe8\x0a\xed\x6e\xff\xba\xcb\xd8\xce\x84\x59\xe7\x35\xbe\xd7\xfb\x9e\xe4\x5b\xda\x52\x55\xa9\x4a\x2a\x55\xb7\x31\xc9\x5c\x20\x2b\xd0\xdd\xda\xda\x7b\x6b\xdf\x74\xdf\x3a\xf9\x0e\x3d\x7b\x71\x02\xd2\xfa\xe9\xe4\xd0\x41\x8f\x1f\x9e\x3e\x04\xc2\x2f\x8f\x5f\x3c\x79\x7a\x7a\xf2\x1d\xff\xfc\xe8\xa7\x93\xa7\x20\xb4\xa7\x3f\x9e\x1e\x1e\x1f\xff\xf4\xf2\xf4\xe9\x8b\x1f\xa7\xe8\xe8\xc5\xab\xc3\xff\x3a\x3c\x46\x07\x0f\x7f\x3a\x39\x7c\x0c\xd2\x7d\xf1\x23\x34\xf5\xf4\xe8\xf0\xc5\xf1\x7f\x73\xa4\x5c\x06\x20\x7c\x07\xbd\x3a\x3a\x3c\x3d\x3a\x3c\xe6\x02\x05\x49\x3d\xe4\x22\x38\x39\x3d\x7e\x7a\x70\xaa\x82\xbd\x38\x46\xa7\x2f\x8e\x4f\x95\x36\xa2\x1f\x0f\xff\xfa\xec\xe9\x5f\x0f\x7f\x3c\x38\xe4\xa5\x2f\x38\x96\x57\x4f\x4f\x0e\xa7\xe8\xe1\xf1\xd3\x13\x0e\xf0\x54\x90\x7d\xf5\xf0\xbf\xd1\x8b\x9f\xa0\xc9\x5c\x47\x3f\x9d\x1c\x8a\x8f\x8a\xc5\x3a\xa0\x49\xf4\xf4\x09\x7a\xf8\xf8\xbf\x9e\x72\xb6\x25\xf0\xcb\x17\x27\x27\x4f\xa5\x9d\x80\xc8\x0e\x8e\xa4\xb8\xd5\x97\x0e\xf4\x25\xbc\xe7\x49\x35\xfb\x72\xcb\x78\x1b\xe7\x08\x17\xf9\xa8\x1b\x28\xf1\x75\x8b\x23\xe9\xb0\x29\x9a\x2c\xaa\x35\xaa\x92\x54\x19\x55\xf1\x8a\xef\x7e\x9d\x1b\x33\x9f\x2a\xc3\x3f\xd7\x41\x08\x3b\x08\x11\x07\x21\xcf\x41\xc8\x77\x10\x0a\x1c\x84\x42\x07\xa1\xc8\x41\x88\x3a\x08\xc5\x0e\xc2\xae\x83\x30\x76\x10\x26\x0e\xc2\x9e\x83\xb0\xef\x20\x1c\xa8\xa7\x4d\x23\x51\x99\x17\x72\x84\xbc\x02\x47\x82\x03\x81\x98\x57\x04\x62\xb1\x24\x40\x24\x12\x0c\x44\x14\x44\x9e\xa4\xe6\x4b\x6e\x62\x89\x84\x2a\x9c\x46\x12\x59\x28\xb9\xc1\x02\x29\xee\x24\xc3\xc6\xb2\x72\xcd\x8d\x2b\x90\x00\x27\x35\xa7\x9e\x40\xc6\x09\x60\xb5\xe9\x1d\x44\xbe\xac\x1c\x48\xf6\x23\x89\x84\x28\x9c\x62\x89\x8c\x4a\x6e\x64\xd3\xb1\xf7\x66\xfa\x5d\x4f\x27\xab\x31\x9d\xd4\xcc\x44\x8a\xc8\x88\xc4\x5b\x33\x1e\x76\xa5\x02\x0d\xf4\xfa\x12\x08\x65\x25\xaf\x45\x06\x95\x83\x96\xf1\x1a\x49\x2d\x15\x20\x86\xfb\x12\x00\xa8\x40\x69\x66\x24\x59\x0c\x5b\x19\x37\x58\x88\x22\x6f\xc1\xae\x82\x89\xca\x4a\x8a\xcc\x40\x41\x41\x2b\xe3\x06\x89\xa7\xc8\x5b\xb0\xab\xb2\x44\xa4\x84\x5d\x05\x5b\x2d\x91\xa8\x51\x63\x63\xad\x48\x4a\x48\xb0\xab\x69\x65\x3d\xea\x29\xc0\xa6\x94\x18\xb0\xa8\x12\xab\xed\xcc\x53\xa4\x1a\xb6\x65\x5d\x0b\x8f\x1c\x28\x03\x81\xd5\xd6\x1b\xb7\x86\x58\xdb\x17\x0e\x14\xe1\x46\x12\xb6\xab\x16\xdc\x5a\x43\xa3\x92\x58\x42\xd6\x56\x44\x15\x98\x9a\xf5\x08\x3e\xf7\x31\x11\xa5\x81\x7e\x4b\xbf\x89\x01\xaa\x83\xd0\x0e\x46\x05\x53\xed\xae\x41\xeb\x08\x4d\x43\x49\xcb\x42\x23\x29\x69\x39\x02\x31\xd2\xf5\x32\xea\x2d\x92\xbb\x8e\x0a\x82\x96\x09\x68\x6b\xd4\x7a\x26\x10\xf5\x25\x33\x61\x9f\x73\xaf\xab\x82\xa6\xe9\xb8\x45\xd4\x08\xb0\x71\x71\x28\xef\x22\x6a\x82\x0a\x56\x64\x15\xb6\x94\x1b\x37\x94\xfe\x8c\x3d\xa3\x56\x82\xd6\xcc\x54\xde\x6b\x4c\x8d\xfb\x11\xe5\xdf\xa0\x71\xea\x9e\x90\x02\x03\xa0\xdf\xd5\x50\xa3\xd2\xb6\x5d\x08\x63\xf4\xa6\xff\xce\xff\xbb\x19\x77\x17\x83\x5e\x50\x93\x54\xdb\x75\x90\x7b\x15\x3c\xa4\x24\x8a\xe3\x98\x7f\x0e\x0f\x1f\xc7\x87\x8f\x1e\x62\xfe\x99\x3e\xc1\x8f\x1e\x1d\x3c\x3e\xe0\x9f\x1f\xc6\x81\xf7\xe4\xb1\x7f\xa8\xa9\x7e\xb6\x1a\xa4\x11\xb8\x0f\x09\x7d\x74\x18\x02\x8d\x03\xff\xf1\x63\x4c\x7c\xa0\xf1\x38\x72\xbd\xc3\x27\x1e\xff\x1c\x3d\x0c\x1f\x47\xe1\x21\xd0\xae\x79\x7a\x63\x59\xe2\x38\x7e\xfa\xf2\xf0\xf9\x63\x1c\xba\xf0\x86\xc2\x46\xcb\x64\x4d\x8d\x76\xa1\x4c\xf9\x49\x74\xe5\x37\xdd\x23\x13\x4b\xab\xc0\xca\x40\xf6\xf2\x30\xf2\x03\xe2\xb9\x20\xd0\xc3\x27\x07\x8f\x1f\x3e\xa2\xd0\xd8\x98\x3e\x7a\xf8\xf8\xe0\xc9\x21\xff\x8c\x5d\x8f\x04\x7e\x04\x82\x3a\xf0\x1e\x93\x43\xfc\xc4\x7d\x63\xd9\x35\xd8\x74\xc3\xc2\xb4\xce\xbd\xe9\xc5\x2d\x3c\xb4\x5b\x35\x72\xb3\x41\x50\x87\x45\xe8\xf6\xe8\xaa\x69\xff\xea\xf9\x3b\x05\xb4\xde\x4e\x79\xa7\x9d\x28\x1b\xda\x57\x52\x6a\xa0\x7d\x34\xe9\x95\x22\x71\x24\x57\xa1\xd1\x1e\x3b\x51\x7e\xdc\xee\x80\x6f\x17\x9b\x3c\xe6\xdb\xc3\xa6\x1f\xf6\xed\xe1\xb2\x2c\xf2\xd9\x0f\x98\x1f\xa1\x66\xcb\x85\xdb\xdb\xd0\x8e\x2a\xf8\xfc\xbb\xd9\x7c\xb0\x7c\x05\xe5\xab\xa1\x72\x18\x8c\xbe\xfb\x75\xb0\x3e\x0c\x8c\xde\xfd\x3a\x58\x1f\xba\xe8\x77\xeb\xc1\xfa\xd0\x55\xbc\x5b\xaf\x8c\xc9\xc9\xf7\xf6\xb8\x0f\x7d\xe0\xf3\xec\x8f\xc9\xaa\xe4\x43\x65\x7d\xeb\x39\x99\x3b\x28\x9d\x3b\x28\x9b\x3b\x28\x9f\x3b\x88\xcd\x75\x2a\xc9\xca\x41\xe9\xca\x41\xd9\xca\x41\xf9\xca\x41\x7c\xb0\xde\x01\x4a\x38\x1b\x09\xe7\xf5\x48\xbb\x61\x93\xae\x20\x47\x3c\x94\xe1\x5e\x59\xc6\xcb\x32\x51\x46\x7a\x65\x39\x2f\xcb\x45\x99\xd7\x2b\x83\xc9\x03\x13\x65\x7e\xaf\xac\x79\xe3\x3e\xe9\x3d\x6a\x2f\x37\xa2\xc6\xb7\xb3\x29\xfc\xbb\xb3\x8f\xb0\xee\xaf\x15\x77\x8f\x64\x8e\x76\x5a\x9f\xd9\xf9\x75\xfe\xba\x7c\xf3\x66\xfa\x9b\x76\x1b\x04\xae\x33\x3d\xc0\xe1\x54\xbf\x7c\x51\x71\xfc\xa8\xc0\x93\x74\xee\x64\x73\x27\x9f\x4f\xd1\x0e\x9a\xcd\x0d\xf7\x93\x3e\xb5\x4b\x7e\xe5\x03\x8f\x98\x6e\x72\x48\x64\xa4\x8f\xac\x2f\xee\x1e\x32\x9f\x5a\x90\x79\x7d\x64\x7d\xfd\xf4\x90\x85\xbe\x05\x99\xdf\x47\xd6\x57\x68\x83\xec\xef\x7b\x7b\x12\x21\x75\x2d\x08\x83\x3e\xc2\xbe\x15\xe8\x19\xef\xb9\xe6\x2a\x5d\x47\xfc\x67\xb4\x5a\x56\xf3\x49\xe5\xac\xb9\x22\xb5\x3b\x30\xa0\xf2\x6a\x87\xcd\x0d\x1a\x06\x9b\xef\xbb\x0c\x12\xa6\x99\x6b\x3f\x83\x35\x03\x2d\xee\x6d\x58\xbf\x20\x05\xae\x90\x6a\xf5\xc0\x7b\xfa\x7b\xa1\xb5\x2d\xae\xd0\x8e\x62\x8a\xab\x1b\x9b\x62\x30\x49\x57\x4e\xb6\x72\xf2\x15\x88\x74\xf5\x59\xa6\xe8\xf7\x91\x7d\xa6\x29\x76\x91\x7d\x8e\x29\x92\x3e\xb2\xcf\x35\x45\xdc\x47\x78\x3b\xa6\xb8\x82\x2d\xf6\x21\x5b\x5c\x99\x6c\x11\x82\xe2\x4a\xb3\x45\x88\xa3\xda\xcf\x10\x5e\x85\x2d\xae\xcc\xb6\x08\x71\x5b\xab\x07\xe1\x5c\x3b\x48\xa2\x7e\x83\xc3\x45\xca\x5b\x18\x30\x9e\x83\x93\xd1\xdd\xc1\x1e\xff\x6b\x1f\x4d\x8e\xc4\xd9\xe5\x8c\xc7\xd4\x5c\x6f\xd7\x91\x3c\xe2\x7c\x24\x4e\x27\xe7\x1c\xcc\xd0\xfc\x23\x79\x88\xf9\x48\x9c\x32\x66\x1c\x2c\x31\x81\x79\x12\x0c\xce\x11\x43\x20\x4f\x4d\x60\xbe\x04\x83\x63\xdb\x29\x07\xcb\x4c\x60\x70\xba\xbb\x2b\x0e\x6d\x70\xf9\x44\xbe\x5f\x72\xe3\xd3\x61\x79\x52\x25\xcd\x68\x85\x7f\xd1\x7b\xe8\x0d\x9e\xd9\x14\x80\x8b\x47\x65\xb5\x3e\x5d\x56\x10\xb8\x04\xc6\xc5\xe3\xa4\x4a\xc4\xa1\xb1\x6f\x11\xd5\x71\x43\x95\x67\xac\xa8\xb4\x07\x39\x01\xbc\xdf\x8e\x87\x79\x6e\x78\x60\x1b\xb5\x2c\xbe\x6e\x11\xf2\x31\x5e\xf0\x06\xfd\xb6\x2f\x1e\xcc\x6e\x8f\x6d\x34\x10\x7f\x41\x1e\xe9\x99\x66\x8b\x68\x32\x99\xb4\x90\x3b\x88\xfb\x3d\xc7\x18\x4f\x39\x26\x9f\xbb\x23\xf6\xcd\xe3\xd9\xba\xa6\x90\x44\x3b\x9a\x55\x7e\xdc\x78\x34\xab\xe1\x92\x63\xd9\x1e\xae\x91\xb1\xac\xa1\x91\xea\x83\x29\xed\x1b\xa7\xf5\xfb\x44\x70\x48\x47\x7f\x3f\x65\x6f\x0f\x26\x61\x08\x5e\xcd\x11\x97\xd6\x4c\xc7\xb9\xce\xc5\x8c\x47\x7b\x29\xc7\x6a\x88\xe0\xc9\xea\xb0\x59\x37\x96\x23\xb4\x8f\xd4\x01\xf5\xe7\x4c\x9a\x82\xf1\x39\x93\x61\x26\x74\x04\x53\xa0\x23\xfd\x36\xcd\xd0\xdc\xe7\xa8\xb9\xb5\x78\xd4\x99\xd9\x1c\x6d\x3d\xa5\x39\xea\xcc\x65\x8e\x06\x27\x31\x63\xc7\xf0\x8e\xc5\x26\xb9\x50\xa1\x7c\xf5\x23\x07\xb1\x76\x20\xe5\x5e\x7a\x5f\x11\xdd\x00\x94\xcd\x97\x0b\x6b\xe8\x81\x83\x07\x1c\xa8\x9d\xbe\xc3\xd7\xc1\x93\x14\xa2\xf4\x9d\x66\x0c\xa2\x9a\x66\x51\x92\x4b\x28\xec\x1e\xc4\x6a\x3e\x29\xe7\x03\x94\x93\x78\x05\x9e\x5c\x39\xe8\xda\x41\xbf\x1a\x1f\x48\x99\x4c\xae\xe0\x5e\xeb\x35\xfc\xfd\xeb\x54\xa5\xfb\xc9\x84\x8e\x8c\xa0\x9b\x5c\x4d\xef\x4c\xae\xa7\xe2\x72\xfc\x3f\xf8\x97\x5f\xa7\xd3\xa9\xf1\x08\x57\x8b\xd4\x1b\x47\xca\xf1\xfd\x83\x23\x6e\x19\xb5\xa2\xf4\x37\x40\x79\x07\x10\x01\xa7\xd7\xd3\x3b\x93\x7f\x00\xab\x63\x88\x83\x8d\xe4\xc9\x05\xfa\x5b\x8b\xd1\x8a\x12\xc6\x0f\x57\xce\xc2\x8c\xf0\xea\xc1\x83\x05\xf0\x78\xf5\xfd\xf7\xdf\x4f\x3c\x72\x6f\xd1\x63\xb1\xf9\x3c\x76\xf2\xa6\x3e\x78\x23\x1e\xd4\xdb\xe6\xe0\xcd\xe0\x0b\x4c\xf2\x14\x0d\x9c\xb5\xaa\x3f\x57\x4b\x11\xbb\x86\x90\x0d\x3c\x98\x64\xad\x73\x1b\x2f\x25\x49\x57\x6b\x0f\xcd\xd4\x2b\x6e\x93\xbb\x92\xf5\xbb\xe6\x33\x40\xc3\x15\x2f\xeb\x26\x0c\x1f\xf6\xd1\x97\xf5\x7a\x07\x7d\x6a\x80\x6d\xce\x50\xd5\x9a\x3c\x7a\xfe\xf0\xe0\xf7\xd4\xa3\x05\xc3\x07\x76\x0d\xb5\xd7\x2c\x5b\xb1\xca\xfa\xec\xd5\x80\x01\xc0\xbb\x8f\x5f\xd4\x00\xc4\xfb\x92\x8d\x1e\x8f\xce\x92\xac\xd5\xa5\x7a\x20\x6c\x50\x9d\x9d\x2a\xba\x4a\xcf\x92\xcc\xa0\x56\x51\xf9\x93\xd8\x37\xb6\x9d\xe2\xaa\xeb\xd8\x1e\x95\xfa\xf4\xc6\x89\xc2\xaf\xa7\xb1\xfe\x75\x4f\x63\xfd\x2e\x77\x81\x7e\xb7\x43\x04\xbd\xbc\x76\x5d\x26\x46\x72\xd5\x9d\x1c\x3d\xbc\x87\x37\xde\x33\xd1\x5f\x3d\xbf\xcd\x9d\x92\x7d\xb8\x7f\xdc\x6e\x96\x94\x8b\xb2\x9a\xf4\x13\x83\x75\x77\x4e\x58\x91\xe5\x49\x4a\xe3\x7e\xe6\x2c\xf7\x2a\xa6\x69\x92\x67\x05\xeb\xec\xa3\x68\x50\x99\x97\x13\x86\x0b\xb7\x53\x70\x1b\x5b\x2c\x2a\x3e\xfb\xea\xbd\x69\xf1\xbe\x5f\x7d\x83\xc5\x6f\xe3\x8a\x35\x2f\x49\x8d\xeb\xd5\x30\x56\x36\xae\x56\xc3\x24\xdc\xb8\x56\xcd\x4b\x58\xb3\x52\xdd\xe7\x71\x68\xa9\x7a\x70\x5d\xda\x34\x21\x82\x55\x2a\x24\xee\x11\xeb\xeb\x54\xaf\xc4\xfc\xa6\x9b\xfc\xa4\x77\xb9\x18\x29\xcb\x5e\x1a\x82\xfa\xea\xc8\xab\xd7\x25\xba\x87\xbc\x37\xe8\xad\xfc\x48\xdb\x8f\xd8\x57\x3e\x87\x86\x07\x48\x25\x17\x93\x05\x5c\x78\x16\x13\x64\x98\x1e\x79\xd8\x90\x75\xc8\x30\xaf\x83\xa5\xaf\x49\xc2\xab\x07\x50\x3d\x11\xb3\xb3\x08\x6e\x3b\x33\xb4\x03\x24\x8c\xeb\x9e\xe8\x01\x22\xc3\x4b\x78\x93\x49\x8a\xee\xa0\x4c\x8c\xd3\xf9\xc7\x1c\x30\xba\x57\x41\x22\x76\x72\x6d\x8b\x8d\xe8\x01\xf2\x2d\xa8\x53\xf4\x16\x65\xe8\x2d\xca\x05\xc6\x90\xe5\x31\x4b\x13\x2d\x39\x54\x0f\x63\xb8\x19\xb3\x82\x57\xfe\x29\x93\x5c\xdf\x43\xee\x55\xe4\x32\xdf\xf7\x88\x3f\x40\x63\xef\xdb\x86\x0c\x75\xa7\xe8\xdb\xbd\x4d\x78\xe7\x78\xbd\x20\xce\x3d\x46\x92\x51\x65\x71\x63\xcf\xb5\xe5\x46\xb4\x8f\x32\x6d\x89\x11\x01\x99\x07\x0f\x90\xe7\xca\x16\x81\x4a\xf5\x47\x67\xd1\x3e\xd2\x08\x27\x9b\xdc\x68\xdb\x64\x25\x52\x2e\xe3\xd5\xab\x7d\x89\x76\xf5\x5e\x5d\x85\x84\xc5\x4a\x3d\x37\x8d\xba\x02\x09\x0b\x95\x99\x11\xc4\x53\x17\x29\x73\x23\x88\xaf\x2e\x50\x32\x0d\xe4\xeb\xf2\xe2\x3f\xdd\xf2\x22\x1f\x1d\xef\x16\xf3\xe5\x72\xa5\xae\xf9\xed\x41\x17\x2a\xff\x7c\x0e\x0d\x48\x4d\xd1\x22\xb6\xaf\x13\x6a\xcb\x84\x5f\x62\x85\x70\xbb\xf5\x29\xf3\x72\xe1\xbf\xe6\x2a\x55\x23\x84\xaf\x0b\x16\xa6\x62\x34\xb0\xee\xc0\xc7\xbc\xdb\xae\x55\x40\x9d\xf1\x65\x8a\xee\x70\xba\xbb\x42\xc1\xcb\xbe\x2e\x4e\xfc\x11\x8b\x13\xa0\xbc\x8d\xd7\x25\xcc\x3a\x6c\x97\x24\xa4\x1e\x45\x15\xfb\x7d\x32\x0e\x3a\xb6\x08\x11\xfd\xfb\xe5\x49\x59\xcf\x12\x12\x84\x7f\x4c\x8a\x13\x49\xfb\xeb\x62\x85\x65\xb1\x42\x5b\x89\x50\x0b\x4f\x8e\x1e\x92\x20\xec\xac\x10\x74\x2f\xd6\x1b\x56\x1f\x08\xf1\xb7\x59\x7f\xe0\xe0\x1d\xfc\xf0\x5d\x10\xfa\xfd\xd6\x20\x32\xec\x06\x31\xcb\xa9\xc3\xa7\x13\x61\x94\xe5\x81\x1b\xc1\x67\x37\x72\xf3\x1c\xc3\xe7\x22\x72\x59\x10\x7b\x86\xb5\x89\xa2\xc8\x5c\x37\xf5\x60\x05\x23\xa4\x01\xc5\x01\x16\x9f\xfd\x22\xa6\x45\x02\xb5\x53\x56\x24\x7e\x91\xf8\xdb\xac\x4c\x6c\x32\x80\x56\x3a\x28\x29\x34\xa5\xe2\xf0\x75\x67\x08\xf4\xcd\xe8\xec\xde\xbe\x36\x16\x1b\xda\xc2\xfb\x3a\xea\xb8\x9d\x51\x07\x21\xfe\x0d\xc6\x1d\xbc\xd6\x46\x23\x8f\x8e\x1b\x69\x63\x0f\x42\x6c\xe9\x0a\xbe\x8e\x3e\xbe\xe4\xe8\x83\xab\x70\x9b\xf1\x87\x51\x93\x9d\x11\x88\xd0\xa6\xa8\x36\x3a\x06\x21\xc4\x7c\xad\x5d\xb9\xc3\x2e\xbb\xcd\xfb\x11\xe5\x63\x12\xfa\x75\x63\xe4\xdf\x62\x63\xe4\xdf\xe4\x86\xe5\x53\xf1\x1a\x49\xf9\x6b\x73\x0d\x1b\xad\x96\x17\x8b\x1c\x65\xdd\xab\x97\xdd\x36\x1d\x19\xde\xfd\xf9\x41\xdb\x4a\xa9\x17\xbd\x59\x8b\xac\x29\x1c\x18\xb5\x29\x47\x0e\xca\xf5\xcb\x55\x79\xc6\x26\x0b\x53\x17\xbd\xfe\x9f\x55\xf5\x63\xbd\x22\xc3\xbf\x4c\x16\xd3\x81\xf3\xde\x42\xf9\x68\x1f\x91\xef\xea\xcf\x0f\xf6\x05\x82\xfa\x87\xa1\xa5\xf6\x3f\x4d\x16\xe8\x2f\x12\x66\x6a\x5a\xa9\x95\x01\xa1\x48\xe6\x6b\x66\x3f\x0b\xfa\xc9\x38\x1c\xa8\x56\x17\xbd\x45\x08\x93\x24\xde\xb3\xea\xc9\x2a\x81\xcf\xc9\xfc\x51\x59\xad\x75\x99\x34\x47\x3b\x16\xe8\x1e\x9a\x2c\x20\x19\xee\x14\x7d\xdb\x59\x99\xd2\x16\x18\x55\x62\xf5\xd2\x7f\xe7\xd1\x02\xf8\x11\xb4\xd0\xcf\x34\x75\x39\x2b\xe7\x0c\x4d\x64\xe1\x03\xa4\x9f\xc4\x85\xb5\xe7\x46\x85\x66\xf9\x36\xd5\x8d\x87\x82\x8f\x5e\x8b\x72\x48\xd3\xab\x89\x00\x34\x7f\xbe\xbc\x9c\x2c\x1c\x84\xd1\x1e\x22\xd3\x91\x37\x0d\x10\xfa\x61\x2b\x84\xde\x54\x4f\x38\x2f\xea\xef\xec\x58\x17\xa3\x17\xdd\x72\x65\x88\x37\xe9\xa0\xbc\x85\xad\x49\x3e\x9b\xd8\x66\x72\xa0\x4f\x3e\xbe\xf8\x06\xe5\xd1\xee\x7a\x5e\x66\x6c\xe2\x4e\xbf\x6e\x1b\x6e\xb0\x6d\xd8\x2f\x29\xa0\x24\x30\x94\xbc\x87\x92\xfe\x0e\x1c\x8c\x94\xa0\x24\xfa\xcc\x4d\xc8\x70\xe8\x65\x88\xdf\x61\x13\xf2\x7d\x72\x76\x96\xb8\x57\xcd\x56\x24\xee\x0b\xa0\x0b\x08\xc7\xcb\x27\x75\xa5\x07\x0f\x10\x11\x1b\x87\xf5\x2f\xdf\x7f\xff\x3d\x8a\xa6\x53\x84\xde\x6a\x48\x10\x1c\xe8\x54\xaa\x62\x5f\xab\x8a\xe9\x74\x3a\x54\x55\x85\xf3\xf4\x80\xa1\x70\x89\xdb\xe6\xe8\xd7\x1a\x54\x38\xa5\x35\x58\xb0\xa4\xb4\x06\x5f\xd5\xe9\xd5\x07\x59\xea\x54\xf5\xf4\xaa\xf1\x48\x6b\x6a\x38\xc3\x83\x1b\x8d\x7a\xa5\xdc\x77\x64\x83\x20\x51\xb5\xe4\x7e\x67\x78\x73\xd8\xb8\xdd\x9b\xc1\x1d\xd0\x09\x43\x77\x50\x01\xe7\x12\xff\xc1\x3f\xbe\x37\xbe\x29\x74\x96\x40\x66\xc2\x04\xdd\x41\x29\xc0\x26\x62\x8f\xf4\x2d\x92\xbb\xa5\x1a\xc7\x30\x58\x28\xdf\x73\x66\x9b\x5d\x65\xb9\x07\x29\xb7\x95\xc5\x69\x4d\x51\x82\x63\xa5\x04\x7b\x9d\xa2\x4e\x25\x43\x6f\xd3\x50\x82\xf7\x92\x26\x0c\xec\x50\xe4\xfb\x66\x50\x29\x94\xe8\x44\x09\x56\x4a\x30\x56\x8b\x22\x71\x2a\x59\x14\x91\xc0\xd0\x0d\xc1\x2e\x39\x27\x33\x43\x3b\x35\xcd\x1d\x2e\xc8\x1d\xf1\x40\xa0\x79\x87\x1c\x6a\x11\xb4\x5f\xcb\x63\x87\x8b\x53\xc3\xcd\x83\xc7\xfb\x7e\x55\x1e\x6b\x8a\xfe\x8f\x3c\x34\x69\xa3\x1e\x48\xac\x0a\x6f\x7b\x60\xc3\xa6\xa7\x65\x7b\x38\xdd\x78\x2b\x78\x52\xf1\xd6\x56\xc4\x80\xff\x3f\x60\x5b\x18\x41\x77\x20\x40\x20\x87\x7d\x61\x04\x09\x25\x08\x64\xa8\x7f\x6f\x04\x89\x24\x08\x38\xef\xec\xeb\xfe\x73\xaf\x1d\x5f\xf7\x9f\x35\x4c\x5f\xf7\x9f\xbf\xee\x3f\x9b\x91\xfd\x33\xad\x04\x07\xe1\x4d\x56\x82\x83\x70\xc3\x95\x60\x75\xce\xa4\xaf\x04\x07\xe1\xd7\x95\xe0\x3f\x6a\x25\x38\x08\xb7\x5b\x09\x36\x69\xb2\xbb\x12\x1c\x84\x5b\x9c\x8d\xef\xec\xe6\x0d\xee\x49\xc7\xff\x7e\x7b\xd2\x57\xa1\xff\x07\x3e\xbc\xd1\x50\xff\xba\x56\x7c\x1b\x6b\xc5\x57\x21\x6c\x20\xef\x5e\x85\x7e\xb7\xe8\xe7\xd0\x97\x39\xe7\x01\x68\xb7\x9b\xf6\x7c\xdb\x54\x7e\xdd\x76\x1f\x1f\xbd\x78\xf7\xe2\xc9\x93\x93\xc3\xd3\xfe\x4b\x14\xbc\xf4\xe5\xd3\x77\x4f\x7f\x7c\x7c\xf8\xf3\xa1\xf1\x11\xfa\xe3\x17\x3f\xfd\xf8\xf8\xdd\xc1\x8b\x1f\x4f\x4e\x1f\xfe\xd8\xd4\xef\x59\x3c\xac\x42\x1f\x6c\xbe\x0a\xad\xd4\x5a\xcd\x96\x75\x4a\x1e\x7d\x19\x5b\xb0\xc0\x67\xf6\xd8\x41\xd7\x83\xf9\xf8\x2b\xb1\xac\x52\xa1\x07\x88\xf8\xdf\xa1\x4a\x5f\x56\x51\x24\xf0\xfa\x0a\xed\xa0\x00\x7d\x8b\xae\xc5\x6d\xd5\xaa\xbe\x01\x0c\x9f\xc8\x14\x16\x3b\xd1\x5f\x50\xd8\x1f\x6f\xc1\x20\x97\x5d\xfe\x8c\xf6\xd1\x35\xfa\x0b\x0a\x0c\x43\x60\x76\xf9\xdf\x1c\x27\x41\xdf\x22\x4e\xc5\xe3\x54\xa6\x3a\xec\x95\x58\xcb\xfb\xb9\xfb\xeb\xb5\xf8\xf5\xbf\x07\x17\x91\x15\xa9\x9d\x97\xa8\x84\xf7\x32\x4c\x32\x6b\xe4\x72\x25\xe4\x72\x25\xee\x00\x5f\xe9\x62\x69\x20\x85\x6c\xd1\xb5\x80\xbc\x36\xad\x4b\xb5\x66\xd2\x95\xe0\x35\x3c\xa2\xa5\x37\x99\x8b\x34\xe8\xcf\x1f\xc7\x5b\xd6\xdb\x1e\xe9\x99\xc2\xb3\x27\x27\xc7\x9c\xd5\x2b\x17\x1b\x2d\x41\x7d\x4c\xc5\xbc\xc0\xc6\xa1\x80\x46\x6d\xae\xcf\xd7\x97\x7d\xc3\x32\x40\x3d\xab\xa1\xcc\xf2\x93\x2f\xaa\xfc\x82\x1e\xa0\xe8\x3b\xf4\xcb\xd0\xba\x1e\xb0\x0f\x57\xa0\x0d\x29\x71\xe4\xca\x67\x59\xbd\x5c\xae\x21\xd1\x2f\x37\x25\x78\x58\xfa\x97\x29\xba\x87\xb4\xc3\xee\x02\xa5\x0a\xff\x00\x99\x93\x89\x20\xbd\x2d\x6f\xf7\x11\xa0\x56\xaa\xeb\xf8\xbb\x47\xdd\x55\x4a\xdf\xef\x03\x29\xe3\xa9\x77\xa4\xcb\xb7\xa6\xd6\xc1\x71\x4f\x9f\xb3\x21\xc3\xb6\x82\xb6\xaa\xa5\x18\xcb\x82\x5d\x55\x60\x13\x36\x59\x9b\xb3\x8e\xec\xed\xa1\x97\xab\xf2\xac\xac\xca\x8f\x0c\x9d\x2f\xe7\xd7\x8b\xe5\x59\x99\xcc\xd1\xf2\x23\x5b\xa1\xbf\x3e\x99\x90\xe9\x7d\x74\xf5\x96\xa2\x1d\x74\xf5\x36\x84\xbf\x03\xf8\xdb\xe7\xe1\x42\xc3\x26\xed\x52\x10\x15\x17\x33\xde\x22\xf7\x2a\x1a\xba\xa1\xa0\xf3\x23\x6b\xee\xeb\x7a\xb6\xee\x74\xf5\xc2\xb3\x58\x3e\x94\x7d\x49\x9d\xef\xad\xaf\x0e\x47\x33\x87\xe9\x0d\x77\x54\xd6\xe0\x01\x15\x3b\x3b\x5f\xae\x92\xd5\x75\xff\xdd\x46\x6e\xcf\xa7\xbd\x3e\x65\x78\x67\xd2\xf8\x20\x92\xd1\x87\x4f\x8d\xcd\xdc\xb0\x11\xfa\x36\x8f\xb7\xcd\x26\x8f\xd7\xd9\xe2\xf1\xac\x1b\x3c\x5f\xe4\x65\x8b\xe5\x45\x75\x7e\x51\x3d\x83\x55\x81\x2e\x30\x82\xf9\x41\xce\xd6\xe5\x8a\xe5\xca\xc3\x17\x69\x59\xad\xeb\xb4\xe0\xa2\x76\x7f\xc6\x22\x6a\xbf\x58\xcc\x6b\x15\x2a\xe9\xd8\x93\x15\xbb\x8f\x08\xf1\x1d\x44\x82\xd0\x41\x1e\xf5\x1d\x14\x60\xa2\xd5\x96\xaf\x68\xdc\xe7\x85\x9d\x32\xed\x19\x8d\x7a\xde\x3f\xf4\x92\x86\xda\xc2\x3e\xba\x1b\xbc\xa8\x01\x2b\xc7\xf0\xac\x68\xbd\x7e\x50\x7f\x7b\xfd\xc6\x1c\xcd\xc7\x2d\x10\x09\x8c\xc2\x0e\x17\xec\xb2\xb1\x45\xd8\x94\xb3\x27\xa6\x00\x1e\x9a\x57\xb0\x21\xc8\x87\xae\x8b\xee\x21\xde\x87\x36\xef\x76\xa8\x42\xe0\x63\x12\x8f\x7c\xb1\xfd\x3d\x7d\x29\xd1\x20\x2e\xd3\x82\x5f\xdd\x86\x67\xc9\x02\x16\xad\x7a\x2d\xdb\x43\xc4\xb0\xee\x97\xae\x97\xab\x74\x54\xee\x3d\xe4\x37\xcb\xfa\xf8\x9c\x94\xa8\xbb\x59\xc6\x45\x6c\x5e\xba\x7f\x4e\x4a\x6c\x80\xe5\xd1\x7e\x30\x01\x8a\xf9\xf5\x76\x4e\x75\x38\xf7\x23\xe9\x24\x7d\x24\x5b\x67\x7b\x24\x9d\x34\x8f\xe4\x66\xf9\x1d\x51\xdd\xdc\x61\x2e\x71\x97\x4d\xbc\x3d\x9f\xb8\xcb\x28\xde\x98\x53\x5d\xd8\xc2\x68\x9a\x25\x97\x72\x51\x2d\x85\x8d\x1a\x14\x39\x4f\x60\x71\xb1\xf6\xcf\x7e\xc3\x79\xf1\x2e\x3c\xc2\xfc\x76\x1f\xa4\x60\x04\x98\x2f\x2f\x91\x04\x18\xdb\x02\x39\xe6\x7d\xe9\xda\x6c\xd0\x62\x30\x0b\x46\x2d\x3e\x8a\x61\x29\x7c\x1e\xb0\xe7\xd3\x19\xab\x92\xde\xcf\x9b\x0f\xe8\x25\x92\xe7\x25\x9f\x19\xcc\x2f\xce\x16\xd0\x9c\xbe\x6f\xc8\xcd\x2a\x39\xf2\x75\x50\x3b\xba\xed\xc3\x6d\x33\x41\x30\x2a\xa0\x9d\x2a\xe8\xc8\x11\xaa\x07\x87\x8d\x52\x8c\x40\xcf\x14\xa0\xf9\xf2\xd2\x34\x64\x34\x09\xe1\x74\x68\x58\xa2\x32\x7b\xca\x45\x7a\xfa\xfa\xca\xc0\xde\xe9\x95\xb0\x93\x7d\x60\xd3\x58\x0e\x66\xb2\x0f\x1c\x8e\x9d\x8e\xd9\x5a\x87\x43\x81\xad\xe6\xdb\x07\xc6\x27\x5c\xc0\x3e\x4c\xb8\x06\x76\xbd\x4f\xaf\x70\x0b\x89\x47\x20\x85\x49\x9c\x5e\xe1\x01\x6d\x48\xb0\x67\x0d\x18\xe8\x63\xc8\x04\xd7\x17\x2b\x30\x75\xf1\x72\x0b\x37\x47\xab\x25\x9e\x5e\xf9\xd2\x33\xd1\x64\x22\x99\x69\x2e\x39\x4b\xaa\xf2\xa6\xb3\x61\xde\x00\x88\x9e\x35\x88\x84\x07\x0b\x44\xcf\x7a\x88\x9e\x5b\x11\x7d\x61\x9b\xef\x04\x20\xb3\x65\xf5\x82\x90\xc9\xbc\x36\x9b\x27\x1d\xcf\x96\xe8\x65\x39\x64\x89\x9c\x48\xfd\xb4\x27\xfe\x4e\xf9\x2a\x86\x3d\xcd\xf7\x1b\x9a\x68\x47\x26\x0d\xb2\x01\xdb\xe3\xe5\xc2\x0a\x2c\xb1\xa0\x06\x7c\xd6\x02\x1a\xe3\x01\x84\xde\xd9\xf2\x85\xe8\xc9\xf7\x3b\xab\x3f\x2a\x23\xa6\x36\x1d\x2f\x61\xec\x63\x0e\x9b\x7c\x5e\xd9\x22\x1e\x9e\x6e\x2b\x46\x3d\xa9\x5b\xf6\xe0\x41\xcb\x13\x18\x62\xdd\x12\x78\x0a\xd5\x23\xe8\x9e\x52\x6e\x30\xcb\x8e\x85\x37\x95\x8d\x58\x9f\x6f\x8e\xb5\x3b\xc5\x6f\xdb\x66\x9f\xe0\xf7\xdb\x27\x39\x51\xea\xc3\xe4\x5e\x67\x28\xf4\xb7\x6f\xe6\xf3\x11\xe4\xcf\x36\x47\x3e\xd0\x51\xac\x92\xc5\xfa\x7c\xb9\x1e\xd2\x3a\x04\xbd\x97\xe5\x33\x61\xcf\xa7\xaf\x95\x95\xb0\xd6\x9a\x4c\x1d\x88\xa8\x33\xd2\x8b\x48\x20\x6b\x57\x62\xf6\xec\xf3\x12\x25\x15\xf4\x27\x10\xa9\x0c\x63\xa1\x53\x17\x18\xd6\x33\xd0\x36\x83\x7c\xb7\xf1\x50\x1d\xe8\xd4\xad\x59\x17\xa0\x46\xaf\x3c\x75\x6b\xd6\x25\x90\xa9\x47\xd8\xdb\x43\x07\xb3\xc1\x38\xb4\x51\x8f\xb8\x6d\x54\xb6\x86\xa7\x36\x94\xd4\xf1\xaf\x89\xd9\x43\x16\xb9\x71\x34\x93\x92\x6f\xac\x65\x03\xd8\x2b\xdc\x40\x4f\x94\x6e\x7a\x6a\xed\x47\x44\x4d\xd2\xab\x49\xb4\x9a\x66\xd9\xf0\xee\x79\xb5\xbc\x34\x89\x66\xae\xd8\xec\x69\x6b\xc0\x6f\xd1\xe4\x1f\x92\x53\xf1\xc3\x9d\x9a\x3c\x7c\x35\xba\xf2\x5c\xb1\xec\xd3\xd6\xcc\x55\x54\xf0\x43\x8b\x6a\xbe\xbc\xbc\xe1\x9a\xe0\xd3\xa5\x36\x6e\xd6\xf4\x66\x76\x84\xce\x6a\x18\xef\x2e\x7a\x6b\x6a\x50\x6e\x9f\x48\x74\x50\x18\xfd\xa4\xd3\xa3\x77\xc1\xb9\xc7\x58\x16\xca\xff\x99\x0e\x0a\xf5\xe1\x6e\xff\x9c\x50\x1f\xbc\x59\x4e\xe0\xd5\xf4\x35\x86\x6f\x61\x61\x44\x5b\x64\xb8\xe1\xe1\x22\x7c\x83\xb3\x45\x70\x6a\x28\x63\xe5\xbc\x7b\x02\x08\x4f\xd1\x5e\x97\xf9\x29\xfa\xb6\xff\x03\xd0\x86\xc5\xfd\xe6\x74\xd3\x3f\xc1\xb1\xa0\xcf\x5c\x17\x52\x57\xae\x6a\xbe\x8d\xcb\x5a\x68\xcf\xa4\x70\x15\xa2\x5e\x58\xd2\x51\xee\x99\xce\x94\x9d\xfc\xcf\x05\x63\xbf\x32\x0d\xe5\x2c\x59\xcf\x6a\x6b\xde\xe4\x1d\x7e\x8d\x85\x9b\x2d\x3f\x8d\xac\x44\x6c\x38\xec\x1d\x1a\xf4\x6e\xb3\x24\xd5\x92\x19\x58\xf0\x51\xc6\x59\x72\xcd\x47\x1d\xb6\x6d\xbe\xec\xa3\xe0\x91\x2b\x3f\x2a\x9e\xed\x97\xa9\xda\x86\x5b\x18\x7f\xa6\x31\xfe\xec\x46\x8c\x3f\xd3\x18\x7f\xb6\x05\xe3\x06\x7d\x08\x73\x94\x5e\x53\x2d\xd1\x8a\x55\xab\x92\x7d\x64\xfa\x11\x3b\xd4\xda\xe8\xee\xf9\xc5\x7a\x56\x53\xd7\x04\x62\x00\x7b\xae\x81\x7d\x6e\x26\x64\xc3\x3d\x93\x86\xae\xa3\x3b\xa3\xe5\xe6\xc9\xed\x9c\x07\x1c\x8c\x43\xf2\x70\xa0\x21\x2c\x35\xd7\x62\xc6\xbd\x7d\x93\x25\xfe\xfa\x63\x7b\xea\xd0\x22\xef\xaf\x67\x10\xeb\x3a\x5f\xe8\x0c\xa2\x77\x83\x13\x88\xde\x66\xe7\x0f\x3d\xcb\xe9\x43\xef\xeb\xd9\xc3\x3f\xe8\xec\xa1\xb7\xd5\xc9\x43\x83\x0e\x3b\xe7\x0e\xbd\xed\x4e\x1d\x7a\x63\x37\xd0\x9b\x23\x72\xf7\xa9\xf7\xe9\x8d\x43\xdd\x7f\xe7\x33\x88\xfd\x74\x39\x01\x26\x7f\xf0\xc1\xc4\x3a\x79\x0e\xe7\xe4\x9f\xf7\x90\xe2\x67\x1f\x50\xfc\x8c\x93\x83\x12\xa2\x3d\xc5\x58\x83\xdd\x20\xc3\x4e\x80\x49\xe7\x04\x44\x80\xc9\xc8\xe9\x0a\xba\x55\x86\x1d\x0e\xde\x39\x61\x41\x65\x42\x89\x00\x93\x5b\xbb\x44\xab\x0a\xc4\x9c\x64\x47\xdb\xd3\x77\xaf\xb2\x34\x4d\xe3\x3c\xc8\x1d\x25\x03\xcf\xd4\x31\x41\x86\x24\x4e\x48\x4c\x12\x35\x3f\xcf\xb4\x9f\x88\xc7\x50\x2f\xc6\x41\xec\xe2\x20\x51\x73\xf9\x98\x29\xe0\x80\x14\x2c\x13\x19\x80\xea\x4c\x3f\x9b\x50\x08\x23\xcf\x23\x61\x28\x32\x04\xc9\x3c\x40\x66\x0a\x94\xa5\xbe\x9f\xd0\x48\xcd\x12\xb4\x09\x85\x3c\x75\x33\xc2\xdc\x5c\xcd\x28\x64\xa6\xe0\x47\x69\xe0\x53\x9c\xab\xf9\x86\xba\xe3\xea\x5b\x4f\x38\xc4\x6d\xe8\x66\x09\x87\x70\xf8\x35\xe3\x90\xb9\xce\x97\x1a\xe3\xd1\x9b\x64\x1c\xe2\xb5\x36\x1b\xe7\xa9\x61\x45\x1f\xe9\xd1\xaf\x19\x87\xfe\xb0\xb1\x1e\xdd\x2e\xe3\x90\x51\x93\xdd\xf1\x1e\xdd\x22\xe3\x90\x47\x37\xc9\x38\xc4\xc7\x1a\xf7\x29\x36\x8d\xfe\xf0\xbf\xf3\xe8\xef\xeb\x0d\x94\xdf\x39\x33\xe2\x97\xbe\x54\xf2\x59\x43\xc3\x56\xa6\xb5\x24\x24\xba\x77\xf5\x91\x5c\xd3\x7b\x46\xdd\x63\xbb\xbb\xc9\xf9\xf9\xfc\x7a\x22\x7f\x74\x50\xb2\x7a\x7f\x71\xc6\x16\xd5\xda\xfc\x0a\x93\x7a\xd5\xa5\xc3\x1c\xe4\x47\x52\x88\xf5\x38\x71\xaf\x7c\x42\x13\x52\xc4\x30\x64\xca\x23\x42\x13\x46\xc8\xd4\xd1\xe1\x22\xec\x45\xbe\x1f\x43\x4a\x44\xe2\xb1\x22\x0c\xb2\xbc\x33\xf0\xd1\x6a\xa4\x41\xe6\x16\x69\x56\xc0\x93\x10\x99\x9f\x7b\x29\x29\x4c\x98\x59\x9c\x06\x79\x9a\x04\xf0\x8a\x39\xa6\x71\x9e\xa6\x99\x1d\xb3\x17\x07\x61\x46\x82\x14\x86\x6b\x9e\x4f\xd3\xc0\xa3\x26\xcc\x41\x5c\x60\x8c\x0b\xe0\x39\x0d\xdd\x20\x77\x71\x6c\xc7\x1c\x13\xaf\xa0\x24\x81\x97\xcf\x93\x02\xc7\x7e\x11\xa7\x26\xcc\x49\x8a\xb3\x80\xe5\xc0\x73\x9e\x84\x39\xc5\x98\xda\x31\xe7\xd4\x8d\x92\x44\xc8\x39\xf1\x5c\xcf\x25\xbe\x51\xce\x98\x50\x2f\x48\xc5\x43\x1a\x7e\x10\xb9\x61\x91\x32\x3b\x66\xe2\x7b\x98\x06\x29\x3c\xaa\xe1\x33\xe6\xa7\x84\x66\x46\x69\x04\x6e\x16\xe5\x19\xbc\xe4\x9e\x07\x45\x91\xfa\x8c\xd8\x31\x47\x24\x65\x41\x1e\x81\x34\x0a\x12\xa5\x34\x0e\x8d\x1a\xa4\x6e\xce\x52\x2c\x9e\xf5\xf0\x52\x1c\xc6\x61\x8a\x47\xe4\x9c\xe6\x99\x1b\x8a\xa4\x9a\x24\xc8\x22\x4c\xbc\xc0\x84\x39\xc3\x71\x5a\x60\xc1\x41\x56\x84\x31\x09\x63\xdf\x8e\x99\xf9\x71\x1a\xc6\x19\xc8\x2f\x66\x05\xf6\x93\xdc\x28\x67\x56\xa4\xcc\x8f\x28\x3c\xe3\xee\x51\xbf\x20\x01\xf3\xec\x98\xdd\x22\xc3\x71\x9e\x41\x0d\x9a\xd2\x2c\x0f\x52\x23\xcf\xc4\x77\xb3\x04\x67\x19\xbc\x98\x1f\x25\x59\x9c\x85\xc1\x88\x06\x73\x16\x93\x2c\x04\x4f\x09\x62\x92\xba\x24\x32\x62\xf6\x93\xc8\xa7\x7e\x02\x13\xa1\x90\x25\x21\xf3\xe9\x08\xcf\x41\x96\xba\x49\x9c\x03\x2f\x69\xee\xe3\x22\xcd\x7d\xa3\x77\x87\x45\x4c\x69\x0e\x98\xa9\x87\x71\xe0\xa5\x23\x3c\xc7\xd4\x63\x01\x0e\x08\x78\x37\x0b\xc3\xbc\x48\xcc\x9e\x42\x3d\x9c\x85\x21\x4c\x67\x48\x9e\xfa\x1e\xc1\xee\x48\xdc\x70\x5d\x8f\x44\x19\x15\x0f\xef\x17\x29\xc1\x9e\xd1\xea\xd2\x22\x88\xa3\x22\x93\x89\x59\x59\xe1\x32\x36\x62\x1b\x59\xc8\x5c\x37\x2d\xc0\x03\xbc\x3c\xa1\xb4\xc8\x8c\xb6\x91\x07\x49\x14\x63\x1f\x30\xc7\x9e\x9b\x24\x11\x19\x91\x86\x1b\x66\x49\xe8\x05\xe2\xf9\x1b\xd7\xf5\x28\x31\x7b\x0a\xf6\x49\x4c\x62\x31\xd1\x74\x13\x97\x85\x2c\x1a\x91\x06\x89\xd2\xc8\x4d\x28\x44\x1a\x3f\xcc\x09\x29\x0a\xa3\x77\x13\x86\xb9\xa4\x40\x6a\x41\x46\xc2\x2c\x26\xa1\x1d\xb3\x9f\x93\x2c\xcc\x0b\xb0\x8d\x20\xc9\x7c\x92\xb0\xdc\x18\x37\x3c\x8f\xba\x39\x06\xa9\xc5\x79\x1c\xa4\x5e\x5e\xd8\x31\x87\x81\x9b\x44\x5e\xe0\x0b\x4f\x49\x8a\xd0\xcb\x99\xd9\xea\xc2\xc4\x4d\x52\x88\xe3\x5e\x16\x45\x29\x49\x46\xa2\x28\xc5\x19\xc9\x62\x22\x62\x5d\xc4\xf2\x84\xb1\xd0\x84\x39\x26\x11\x21\x99\x90\x1a\xf6\x29\xf1\x02\x2f\xb5\x63\x4e\x48\x5a\x30\x9a\x88\xb8\x9b\x15\xd8\xf5\x42\xa3\xa7\x24\x14\x27\x61\xe8\x03\xcf\x69\xe6\x13\xcf\x75\x47\x62\x5d\x46\xfc\x94\xa6\x91\x0b\x71\xd7\x2d\x68\x1c\xc5\xd8\x18\xeb\xa2\x30\x0b\x70\x02\x72\x76\xc3\xc0\x4f\x99\x37\x62\x1b\x39\x8e\x09\xa3\x38\x06\xcc\x21\x2b\x02\x82\x8d\xfd\x60\x1e\xc6\xb1\x1b\x12\xd0\x47\x10\x84\x41\x12\x8f\xf9\x60\xe1\xbb\xcc\x0b\x84\xfc\x82\x28\xc2\xc4\x25\x89\xd1\x9e\xdd\x30\x49\x5c\xd1\x36\x8f\xa4\x69\x8e\xd3\x11\x0d\xe2\x38\xf1\x33\x8c\x21\x8a\xa6\x34\x27\xb9\x9b\x19\x79\xc6\xcc\x8b\xc2\xcc\x15\xf6\x8c\x7d\x9c\xa4\xc1\x48\xac\x23\x91\x4f\xa3\xc8\x07\x7b\xce\x0b\xca\x58\x1a\xc7\x26\xcc\x9e\x9f\xba\x69\x96\x42\xdb\x18\x8e\x53\x9f\x8e\x59\x9d\x17\xe3\xcc\xcd\x52\xd0\x4c\x16\x64\x71\x90\x84\x9e\x31\x3e\xb3\x9c\x26\x89\x0f\x51\x94\x79\x3e\xa6\x49\x36\x62\x75\x41\x1a\x67\x59\xe2\x17\xa2\xaf\x08\x3d\xe6\x45\x46\xcc\x21\x25\x2c\x2c\x44\xe4\xca\xc3\x94\xa4\x34\x19\x91\x46\xe4\xd3\x82\x12\x06\x9e\x12\xe4\xac\x48\x89\x39\x6e\x44\x34\x09\x42\x4f\xf4\x3d\xbe\x87\x23\x52\x84\x23\xb6\x41\xfd\x8c\x46\x14\x8b\x31\x12\x2e\xdc\x24\x8d\x8c\x51\x94\x66\x59\xe4\x12\xa1\x41\x9c\x84\xbe\x17\xb3\x91\x71\x5d\xec\xa6\xac\x28\x8a\x44\x8c\x31\x43\x0f\x33\x62\xb4\x8d\xc4\x0f\xdc\x30\x63\xe0\x83\x39\xa3\x24\xcd\xd9\xc8\xb8\x2e\x65\x45\x9c\x78\x85\xe8\x2b\x48\x16\x46\x31\x36\x8f\x37\xc2\x08\x47\xb4\x10\xbd\x9a\x17\x91\xc0\x23\x23\x1a\xcc\x12\x12\x79\x2c\x03\x39\xb3\x84\x84\x21\x8e\x8d\x72\xce\x31\x0d\x53\x2a\x7a\x2b\xc2\xcd\x89\xf4\x16\x3f\xf5\x11\x4a\x92\x27\x51\x9e\x83\xa7\x64\x39\x73\x59\x8a\x8d\x51\xb4\x08\xa2\xdc\x2f\xa2\x42\xf6\xc4\x2c\xc7\xd1\x88\x3d\xbb\x61\xe1\x86\x91\x18\x47\x44\x04\x47\x61\x91\x1a\xbd\xdb\x4d\x42\x2f\xca\x33\xf0\x94\x84\x64\x34\xa6\xc9\x48\x9f\x82\xb1\x57\xc4\xd4\xf5\xe5\xe2\x65\xec\xe6\x89\x91\x67\x9c\x46\xd8\x4d\x3d\x11\x9f\x3d\x9c\xf9\x11\x1e\x91\x33\xa1\x79\x1a\x45\x45\x20\x6c\xc3\xf5\xa3\x9c\x1a\xe3\xb3\x47\xb2\x24\x49\x23\xb0\x0d\xdf\xcd\x22\xe2\xc7\x23\x9e\xe2\x65\x31\x4b\x99\x0b\xd2\xc0\x41\x16\xa7\x2c\x35\x6a\xd0\xf7\x70\x1e\x46\x19\xb4\x2d\xce\xb0\xeb\xe6\xfe\x88\x3d\xfb\x59\x16\xe4\xbe\x18\x99\x67\xa9\xc7\x7c\x92\x1a\x7b\x2b\x3e\x8e\x21\x71\x0c\x91\xab\xc8\xc2\x20\x62\x3c\xda\x5a\xe3\x46\x91\xa5\x61\x91\x88\x9e\x33\xc9\xc3\x22\x61\x46\x9e\xc3\xcc\xf7\x71\x4c\x01\xb3\x9f\xf8\x51\x40\x71\xd4\xce\xf9\xdf\xd8\x6f\xb1\x76\x26\x92\xaf\x3e\xe7\xc2\xea\xc0\xd3\x72\xaf\x3a\x17\x56\xdf\x7d\xee\x85\xd5\x00\x93\x6d\xb6\x54\x0c\x5b\x36\xb7\x9f\x97\xf4\x46\x5b\x2a\x61\xe2\xc6\xac\xde\x8e\xf0\xd2\x2c\x8b\xdd\x81\x2d\x95\x34\x0d\xa3\x84\x89\x7e\x9a\xfa\x59\x92\x44\xbd\x71\xce\x00\x05\x2f\x0b\x59\xe1\x45\x10\xe7\x0a\x16\xfb\x05\xe5\x71\xce\x04\x99\x04\x7e\x51\x04\x1e\x78\x47\x50\xe0\xdc\x0b\x8b\x8d\x36\x3c\x02\xec\xb2\x80\x88\xa8\x94\xe4\x2c\xa4\x24\x1f\xd8\x52\x89\x53\x37\x08\xa9\xb0\x51\x92\x7a\x2c\xcc\x70\xb1\x09\x05\x5c\x50\x2f\x8f\x85\x0f\x14\xa9\x8f\xd3\x3c\x1c\x68\x43\x90\x32\x37\xcb\xc5\x38\x09\x7b\x11\x23\x38\x8a\xb7\xda\x52\xb9\xdd\x6b\xa4\x1b\xe4\x89\x05\x30\x77\x30\x0f\xec\x11\x1e\x4c\x04\x7b\x44\x06\x33\xc1\x1e\x79\x83\xa9\x60\x8f\xfc\xc1\x5c\xb0\x47\xc1\x60\x32\xd8\xa3\x70\x30\x1b\xec\x51\x64\x4e\x07\x2b\x1a\x06\xb9\x62\x4d\x07\xe7\x45\xf1\x5c\x14\x6b\x97\x58\x44\xd3\xa1\xb2\xe9\x26\x96\x28\x9e\x8b\x62\x73\x65\x02\x95\xc9\x50\x65\x32\x17\xc5\xe6\xca\x1e\x54\xf6\x86\x2a\x7b\x73\x51\x6c\xae\xec\x43\x65\x7f\xa8\xb2\x3f\x17\xc5\xe6\xca\x01\x54\x0e\x86\x2a\x07\x73\x51\x6c\xae\x1c\x42\xe5\x70\xa8\x72\x38\x17\xc5\xe6\xca\x11\x54\x8e\x86\x2a\x47\x73\x51\xac\x1f\xbd\xdc\x2c\xd3\xb1\x30\x04\x03\xe6\x44\xd8\xc0\xdc\x70\xf2\x59\xe8\xdf\x50\x27\x15\xaa\x37\xd4\xc9\x84\xda\x0d\x75\x32\xa1\x71\x43\x9d\x5c\x68\xdb\x50\x27\x17\x8a\x36\xd4\x61\x42\xc9\x86\x3a\x4c\xe8\xd7\x50\xa7\x10\xba\x35\xd4\x29\x84\x5a\x0d\x75\xde\x0b\x95\x1a\xea\xbc\x17\xda\x34\xd4\x99\x09\x4d\x1a\xea\xcc\x84\x12\xe7\x86\x4c\x88\x96\x4b\xbc\x9b\xbc\x1c\x3b\x94\x2d\x5b\x0e\x29\x4a\x91\x9b\xd8\x78\x91\xfc\x10\x3a\xe0\x7a\x9b\xaf\x57\x6e\x4f\x06\x2d\x70\xf3\xe6\xbe\x2a\xeb\x1b\x1b\x6a\x66\x68\xf4\x2d\x22\x6f\x00\xd2\x90\x2a\xb6\xae\x3e\x17\xd5\xe5\x2d\x8d\x7e\x75\xb8\xff\xbe\x55\x82\xe9\xbd\x3d\xf4\x57\x48\x5f\x6c\xa4\x57\x27\x74\xde\x34\xf7\xf4\xd5\xac\x49\x87\x7c\x65\xb9\x15\x28\x21\xe6\x0a\xf0\xe0\xcd\x40\x01\x30\xeb\x64\xb5\x9e\x89\x44\xc1\x6a\x6e\xea\x39\xe4\x32\xae\x53\x07\x77\xe0\xa8\x06\x07\x07\x8d\xdf\xa2\x2e\x58\x34\x70\x4b\x55\x00\xcd\x3b\x0c\xcc\x75\x06\x66\x26\x06\xe6\x3a\x03\x33\x95\x81\x2e\x5c\xa4\xc3\x99\x12\x1e\xb7\x4a\xd3\x33\xdb\x7c\x54\x12\x6c\x6f\x98\x61\xbb\xd5\x19\x1e\xd5\x19\x6e\x75\x86\xc7\x74\x86\x67\x9d\xdc\xdd\xb3\x3a\xdf\xb6\x92\x82\x7b\x2e\xb3\x72\x2b\xb2\xc0\x52\x90\x5d\x30\x38\x1d\x1e\x2b\x4a\xab\xf1\x85\x36\xa5\xe1\x79\x87\x83\xb9\x81\x83\x99\x89\x83\xb9\xc6\xc1\xac\xc3\x41\x17\x61\xa8\xe1\x23\xe1\x90\xd6\xb6\x4a\x18\x3e\x10\x01\xa2\x56\xb1\xd1\x80\x62\x5f\x95\x91\x08\x33\xa6\x0e\x52\x01\x9a\x4b\x20\xf3\xb5\x70\x01\x84\x43\xc5\xfb\x4d\x4f\x5c\xd7\x60\x82\x22\x36\x75\xe8\x2a\xd8\xbc\x06\xb3\x11\x6d\x23\xc3\x9c\x4b\xa4\x8c\xe6\x43\x08\xdb\x80\x33\x13\x90\x33\x48\x70\xc6\x51\x70\xe5\xb8\x53\xf4\xa0\x76\xa9\xe6\x97\xff\x07\x61\x74\x1f\xf5\x0f\x9b\xa3\x5e\x8c\x9d\x37\x4a\xb1\x12\xe7\x7f\xef\x34\x16\x3f\x40\x1b\xdf\x80\x36\x08\x6b\x9c\xb2\x10\xbd\x4e\x57\xc8\x5a\xa3\xaa\xe1\x6b\xfb\xa2\x57\xa5\x41\x6d\x6d\x5f\xf3\xaa\xd4\xb8\x19\x48\x61\x2f\x73\xd8\xcf\xd0\x1d\x54\xcc\x64\x16\x7b\xfe\xc5\x70\xed\x50\x54\x10\x7e\xca\xe6\xbc\xc2\x5c\x56\xe0\x5f\xde\xcf\x87\x12\xdf\xcf\x20\xf3\x3d\x47\x9a\x0a\x0a\xf0\x39\x13\x9f\x53\xf9\x79\xa0\xee\x1c\xea\x72\xfc\xa9\x20\x06\x9f\x33\xf1\x39\x95\x9f\x2d\xb9\xf3\x67\x22\x79\xbe\x8c\x09\x22\xb8\x27\x73\x91\x39\x7a\x2a\xb2\x18\x24\xb3\x3a\xb5\xbe\x2c\xec\x24\xd7\x9f\x29\xaf\x32\x24\x75\xe8\x1f\x4e\xa1\x0f\xf3\x8e\x49\x83\x45\x12\x9c\x75\x09\xce\x3b\x04\x67\x5d\x82\x73\x95\xe0\x6c\x94\x20\x16\x2d\x64\x32\x64\x8b\x0b\x36\x4c\x04\x6b\x5a\x27\xe7\x9f\xd5\xaf\x41\x28\x85\x7e\x5b\xc8\x09\x7a\x75\x99\xc8\xad\x6d\x21\x28\x5a\x28\x21\x6b\x82\xb3\x2e\xc1\x79\x87\xe0\xac\x4b\x70\xae\x12\x9c\xb5\x04\xf5\x01\xdc\xf8\x53\x01\x06\x2e\x7f\x80\xcc\x44\x3f\x0c\x5c\x0b\xfb\x01\xdc\xf1\x87\x72\xf0\x42\xd8\x0f\xe0\xd6\x3f\x94\xc6\x98\xf7\x11\xde\x2f\xe0\x00\xb3\x79\xc3\x96\xe6\x6a\x02\x8a\xd3\x99\xb5\xcc\x0b\xbf\xaf\xb0\xea\xf7\xb3\xd1\x50\xd3\x12\xe4\x7f\xf3\xf6\x5b\xa8\x55\x40\x24\x9b\x99\x48\x65\xdb\xd3\xfa\x41\x0f\x22\x7d\x5a\x3f\x94\x26\x5a\x3f\x94\x5b\xd3\x32\x04\xac\x3e\xad\x57\x46\x5a\xaf\x4c\xb4\x0c\x96\xd4\x7f\x3e\xc2\x44\x0c\xa6\xee\xb5\x1b\x03\x94\x99\x29\x58\x01\xa8\xe3\xcb\x8e\x08\x71\xc0\x19\x51\x39\xab\x11\x6d\xc0\xdd\x4f\xe7\x79\x52\x31\x74\x69\x9d\xec\x22\x31\xfd\xd2\xad\x16\xe6\x5e\xef\x35\x56\xa1\x9f\x28\x34\x68\x98\xdd\x15\x1a\x34\x4c\x21\x99\x06\x0d\xf3\x47\xa6\x41\xc3\x54\x74\x92\xcf\xe1\xb9\x8c\xb9\xf1\xbd\x0c\x98\xc7\x4e\xf2\x19\x80\x08\xf9\x30\x55\x3c\xb9\x26\x19\xf3\xb3\x1b\x1c\x4d\xa6\xf1\x05\xf3\xe7\x4c\xe3\x0b\x26\xe9\xa9\x06\x0d\x33\xf4\x54\x83\x86\x65\x80\x44\x83\x86\x35\x80\xfe\x2b\x02\x48\x2c\x27\x4c\x84\xb9\x56\xc4\xdc\x68\x58\x8c\x98\x88\xf6\x72\x33\xd9\x69\xfb\x02\xd1\xea\x4a\x1f\x5d\xdc\xde\x6b\x20\xca\xa2\x17\x74\xd2\x47\x60\xc6\x49\xbf\x63\x3e\x6a\x92\x4f\x4c\x8e\xc0\x80\x13\xc1\xe8\x91\xab\x72\x9a\xe8\x8c\xf6\xd0\x28\xcb\x64\x82\x1c\x88\x26\xd5\xc8\xe1\x96\x1c\x08\x26\x95\xe4\x3a\x7e\x9c\x8e\x92\x53\x16\xd6\x04\x39\x02\x01\x51\x23\x47\x5a\x72\x64\x56\x77\x19\x13\x00\x57\xc2\xe1\x28\x39\x65\x29\x4e\x90\xf3\x38\xb9\x5c\x23\xe7\xb5\xe4\x3c\x4e\x29\x97\xe4\xbc\x11\x63\xef\xa1\x51\x16\xef\x04\x39\x9f\x93\x63\x1a\x39\xbf\x25\xe7\x73\x4a\x4c\x92\xf3\x55\x72\x6c\x94\x9c\xb2\xdc\x27\xc8\x05\x9c\x5c\xa1\x91\x0b\x5a\x72\x01\xa7\x54\x48\x72\x81\x4a\xae\x18\x25\xa7\x2c\x10\x0a\x72\x21\x8c\xd8\x35\x72\x61\x4b\x0e\x06\xca\xef\x25\xb9\xb0\x33\x4a\x1f\x25\xa7\x2c\x29\x0a\x72\x11\x27\x37\xd3\xc8\x45\x2d\x39\x98\x8b\xc8\x9e\x93\x83\xdb\x3a\xe9\xcf\xbf\x3b\xf1\xf5\xb5\x99\x5b\x78\x6d\x06\xf3\xe1\xb5\x7c\xf0\x8b\xe3\x82\xdc\x2c\x9e\x7b\xcb\xef\xcd\x98\xa9\xe0\x7f\xba\x17\x67\x0e\x96\x8b\x8f\x6c\x25\xf2\xcf\xa2\x6a\x89\x3c\x72\x2f\x2d\x2b\x3e\xa6\xc8\x51\x02\x67\x90\x53\x56\x2c\x57\x4c\x9e\x15\xee\xab\x4b\xb9\xf5\xa1\xec\x2b\x55\xcb\x9f\x3d\xf2\xf9\xef\xdb\xfc\xab\xbe\x6c\xd3\x61\xb3\x49\x30\x72\x1f\x61\x97\xf8\x7b\x5e\x9b\x3c\xf7\xeb\x85\xa4\xcf\xba\x90\x14\x60\x72\x83\x0b\x49\xbc\xd6\x46\x17\x92\x3a\x9b\xf2\xda\x85\xa4\x00\x93\xaf\x17\x92\xfe\xa0\x0b\x49\x5c\x85\xdb\x5c\x48\x32\x6a\xb2\x73\x21\x49\x68\x53\x54\x1b\xbd\x90\xd4\x5e\xb0\xdd\xf8\x02\x3a\xf9\xf7\xbb\x82\xc4\x16\xd9\xbd\x34\x59\xb3\xd0\xef\x15\x9c\xe5\x41\x1f\xf4\xe3\xf9\x87\xbc\xe8\xfd\x98\x95\xe7\x33\xb6\xfa\x03\x6f\x31\x29\x0d\x80\xef\x9c\x6f\x51\x20\xd8\x85\xcf\x2a\x97\xff\x89\xb7\x9d\x5e\x6d\xfa\xd6\x0e\x1c\x50\x39\x00\x69\x35\xa0\xca\x6f\x7d\x82\x96\x87\x73\x5e\xb2\xd5\x19\x0c\x0b\x0e\x66\xcb\x32\x63\x08\x1b\xde\x1e\xe1\x48\x5e\x1e\xe0\xde\x0d\xa3\x20\x72\x90\x1f\x3b\xc8\xc7\x0e\xf2\x3c\x07\x91\xc0\x41\x38\x72\x50\xec\x20\x84\xd5\x63\x3d\x01\x75\x50\xe0\x3a\xc8\x27\x0e\xf2\x7c\x07\x91\xd0\x41\x98\x3a\x08\xbb\x0e\x22\x1d\xc0\xd8\x41\x01\x76\x90\xef\x39\xc8\x0b\x1c\x44\x22\x07\xe1\xd8\x41\x98\x53\x50\x01\x43\xd7\x41\x01\x71\x90\xef\x3b\xc8\x0b\x1d\x14\x7a\x0e\x0a\x02\x07\xf9\x91\x83\xbc\x58\x85\xf4\xb0\x83\x88\xe7\x20\x1c\x38\x28\x72\x10\x0a\x89\x83\x02\xdf\x41\x3e\xa4\xf2\xef\x40\x72\x6e\x88\x83\xb0\xef\xa0\x90\x43\x62\x07\x05\x9e\x83\xfc\xc0\x41\x5e\xa4\x42\x92\xd8\x41\x04\x3b\x08\x73\xaa\x0e\x42\x84\x3a\x88\xb8\x0e\xc2\x9c\xa3\x06\xee\x8d\x5d\xce\x64\x50\xce\xa4\x27\x67\xce\x10\x97\x2b\x97\x02\xe1\x9f\x1d\x84\x82\x0e\xe7\x92\x07\xde\x46\xce\x39\xf0\xe6\x76\x38\xf6\xa4\x20\x39\x87\x1c\x22\x74\x50\xa7\xf1\x38\x14\xe2\xe1\x12\x87\xa6\x78\x3d\xd5\x70\x25\x73\x89\x73\x79\x7a\x91\x90\x74\x10\xf4\xe5\xe7\xbb\x52\x81\x81\x30\x09\xbf\x43\x84\x6b\x8b\x1b\x8c\xc7\xd5\x1c\x0a\x5b\x08\x3a\x7a\xe5\x5a\xe1\x56\xc2\xad\x85\xeb\x95\x4b\x5a\x19\xc1\xf5\x1f\x5e\xba\x38\xbb\x98\x27\xf0\x80\x09\x1f\x44\xaf\x67\x65\x61\x7a\x44\x09\xfc\xe5\xe9\xe9\xbb\x93\xa3\xa7\x4f\xc4\x03\x4e\x5c\x82\xc4\x41\x20\x0a\x2e\x30\xca\x2d\x56\x2a\x10\xa4\x2d\x2d\x19\x4b\x45\x13\x69\xdd\x20\x1d\xda\xe3\xe2\xe4\xd1\x8b\x9f\xd9\x1a\x25\x8b\x5c\xe6\x38\x3f\x07\x4d\x8b\x27\x2e\xcc\xdc\xf0\x2a\xef\x5e\xf6\xd4\xdc\x1d\x4d\xbb\x57\xee\x7d\x98\x7f\x51\xe2\xba\x4e\xaf\xa8\x9e\x1e\x09\x08\xbd\x9c\x74\xca\xa9\xeb\x92\x3e\x84\xa7\x40\x68\x85\xbe\x5a\xa8\x63\x0f\xba\xd8\x89\x8e\x3d\xec\xf2\x67\x80\x88\x7a\x2d\xd0\xa9\xd0\x0e\x13\x1a\x82\xb8\x4f\x42\x43\x90\xa8\x10\x86\xf2\xb4\x2f\x24\x0d\x22\xeb\xd1\xe8\x97\xe7\xfd\x46\x68\x10\x4c\x81\xd0\xd0\x17\x5d\x06\xb5\xca\xd4\x52\x17\xd3\x31\x1d\x10\x6a\xc7\xee\x51\xbb\x0d\xf9\x7d\x0a\xba\x1d\x50\xab\x99\x84\x74\xcc\x08\x23\x6a\x31\x42\x4a\xc7\x14\x1c\x53\xbb\x82\x93\x3e\x03\xba\x09\xf4\x69\x68\x5c\x64\x74\x4c\xc5\x39\xb5\x1b\x09\xa3\x56\x3b\x2e\xfa\x04\x74\x55\x0f\x69\x48\x46\x01\x6c\x14\x1f\x51\x0a\xcd\xea\xf3\x3a\x10\x26\xca\x7e\x17\x87\xa1\x71\x81\x0a\x61\xb2\x01\x95\x45\xbd\x38\xea\xb2\x30\xec\x03\x78\xd8\xce\xe3\x3e\x93\x43\x81\x00\x0f\xeb\x30\xed\x36\x43\xb7\x82\x4e\x33\x86\xe2\x00\x1e\x36\x55\xd6\x83\x18\x08\x05\xd8\xe8\xeb\x74\x4c\x04\x98\x8e\x89\x80\xd0\x31\x5d\x7b\xd4\xaa\x29\xbf\x87\x60\x20\x16\x58\x64\x1c\x52\x8b\xb5\x46\xd4\xae\x00\x4a\xed\xf2\x8b\xe9\x98\x21\x25\xd4\xaa\xc2\xb4\x2f\x64\xbd\x33\xe8\x93\xd0\x20\x72\x6a\x51\x22\xa3\x76\x4f\x29\xfa\x3a\xec\x3c\xfa\xe4\x8c\x8d\x13\x7c\xd7\xa5\xbe\x8b\x87\x62\x84\x04\x19\x1a\x28\x34\x5a\x1b\x88\x11\x35\x05\xd7\x40\xc1\xef\x52\x30\x81\x04\x5d\x2c\x26\x46\xc2\x2e\x16\x13\x48\xd4\x82\xe8\x24\xd4\x28\x6a\xaa\x1c\xf7\xf1\xeb\x28\x92\x7e\x43\x06\x87\x0c\x92\x8a\x8e\x22\xeb\x88\x53\x2f\xcf\xdb\xf2\xa1\x10\x21\xc8\xeb\x55\x8b\xbe\x26\x86\x46\x4c\x36\x11\x62\x6a\xe7\x9f\x50\x8b\x88\xbd\x3e\x7e\x83\x2d\xd0\x9e\xa2\x0d\xb6\x40\x47\xa5\x1c\x52\xbb\x51\x46\x74\xd4\x28\x29\xb5\x2b\x22\xa6\xc3\x8a\x48\xa8\xd5\x65\xd2\x3e\xf5\xc1\x50\x61\x73\x89\x9c\xda\xed\x95\xf5\x25\x39\x18\x31\x86\x0c\x46\x9d\x23\xe8\x85\x78\xdc\xb1\x31\x19\xf7\x19\xec\x8d\xbb\x36\xf6\xc7\x4d\x17\x07\x36\xe7\xc6\xe1\x88\xdf\xe1\xc8\x1e\xe6\xd4\xd1\xb2\xb1\x7e\x3c\x12\x07\x71\x32\xe2\xd9\x38\x1d\x0f\x83\x38\x1b\x09\x52\x38\x1f\x8f\x82\x98\x8d\x87\x29\x5c\xf4\xb5\x62\x32\x8f\x91\x58\x80\xf1\x88\x1b\x62\x32\xee\x09\xd8\xb3\x3b\x13\xf6\x37\x08\x5a\xc1\x78\x54\xc1\xa1\x2d\x72\xe1\x68\x3c\xe8\x60\x3a\xee\x71\x38\x1e\x77\x6c\x9c\x8c\x87\x49\x9c\x8e\x84\x27\x9c\x59\xe2\x13\xce\x47\x1c\x9f\x8d\xc7\x47\x5c\xf4\xe2\xcf\x56\x83\x0d\xec\xfa\xe6\x68\x63\xe4\x97\x74\xe4\x81\x87\x06\x19\x02\xb1\x01\xb5\xaf\x14\xbb\x7a\x71\xd0\x53\x89\x06\x10\x76\xa4\x65\x20\x10\x75\x00\x46\x3b\xd6\xc1\xd1\x45\x4b\x62\x60\x6c\x51\x37\x71\x60\x5c\xd1\x72\xa0\xb1\x98\xf5\x64\xa8\x01\xe4\x1d\x21\x0d\x0c\x2e\xa0\xbe\x79\x60\x21\x6b\x1a\x5b\x6e\x69\x17\xa6\x23\x8c\x13\x3a\x68\x17\x1e\x1d\xb1\x0b\x9f\x8e\x68\x36\xa0\xd6\x46\x87\xd4\x6a\x38\x91\x52\xac\x15\x52\x3a\x28\xb0\x98\x5a\x04\x96\xd0\x11\x5b\x4a\xa9\xd5\xd8\x33\x6a\x35\x94\x9c\x8e\xd8\x01\xa3\x23\xc6\x5e\xd0\x11\x5b\xee\x8c\x0c\xcc\x4a\xc7\x76\x8f\xc4\xc4\x6e\x8c\xd8\x1b\x8b\x08\xd8\xb7\x19\x25\x0e\xc6\x3c\x1a\x87\x63\x41\x01\x47\x96\x00\x4b\xc7\xbc\x0d\xc7\x63\x11\x01\x27\xc3\x1e\x87\x53\x7b\x34\xc0\xd9\x58\x40\xc2\xaa\xbb\xeb\xf8\x99\x3d\xa0\xe2\x62\x2c\xdc\xc8\xc1\x81\xad\x7d\xd8\xe6\x3d\x98\x8c\x06\x0e\x6f\x38\x30\x60\xdf\xee\xb8\x38\x18\x8b\x1c\x38\xb4\xf9\x28\x8e\xc6\xa2\x16\xa6\xf6\xc8\x82\xe3\x31\x37\xc3\x89\xdd\xcf\x71\x3a\x16\xdc\x70\x36\xe6\xeb\x38\x1f\x0b\x36\x98\x0d\x87\x31\x5c\x74\x43\xcd\x56\x23\x00\xea\x0a\x8a\xc6\xe0\x51\x8f\x18\xb1\xeb\x9b\x07\x03\x35\xc7\x7a\xb1\xd7\xd6\xf7\x8d\x76\xe7\x0f\x5a\x4d\xd0\x95\x85\x71\x14\xd0\x8c\x65\x0d\xa4\x23\xb7\x33\x6a\x1b\xec\x09\xeb\x7d\x09\xe3\x18\xa0\xd5\xa7\x71\x04\x20\x8a\x8d\xbd\x7f\x2b\xb4\x81\x7d\x88\x56\x2a\x3a\x86\x5c\xf1\x45\x73\xdf\x5f\x9b\xb2\xb1\xf7\x6f\x35\x6a\x6e\xb7\x4d\xa1\x98\x0e\xb6\x8b\xd0\x11\xb6\x3d\x3a\xd2\x70\x9f\x5a\x75\x1a\xd0\x11\xe6\x43\x3a\x64\x2d\x11\x1d\x31\x34\x4a\x2d\x52\x8b\xbb\x84\x07\x86\x01\xc3\xc6\x90\x52\x8b\xbe\x32\x3a\x62\x69\x39\xb5\x5a\x2a\xa3\x56\x17\x2b\xe8\x88\x23\x60\xd7\xee\x09\x18\xdb\x3d\x0d\x93\x31\x57\xc3\x9e\x25\xf6\xdb\x4c\x19\x07\x63\x9e\x80\x43\x77\x44\x35\x38\x1a\x8b\x51\x98\x8e\x39\x05\x8e\xc7\xa2\x01\x4e\xc6\x22\x19\x4e\xed\xa1\x10\x67\x63\x61\x01\xe7\xf6\x90\x83\xd9\x70\xcc\xc1\x85\x2d\x2a\x88\xfe\xdf\xca\x3d\x1e\xf3\x3d\x4c\x06\x9d\x0f\x7b\x76\xbf\xc6\xbe\xdd\xc2\x71\x30\xe6\x22\x38\x1c\x8d\x5b\xd1\x70\xe0\xc2\x74\xd4\x47\x62\x5b\x78\xc0\xc9\x58\x60\xc3\xe9\x58\x6c\xc4\x99\x2d\x46\xe0\x7c\x2c\x02\x61\x66\x0f\x41\xb8\xe8\x86\x89\x2d\xbb\x7f\x53\xd0\xa8\x99\x1d\xd8\x67\x68\x78\x31\x75\xfa\xf7\x95\x53\x0b\xa6\x3e\x5f\x96\xc3\x7a\x86\xa9\xe7\x6f\x86\x6a\x7a\x71\x58\x57\x1f\x28\x8f\x5a\xde\xf4\x42\x55\xcd\x03\x9d\x7e\xcb\x9a\xb9\xd7\x6f\x9b\xa6\xa3\x4f\x5b\xde\x8c\xe4\xb3\x4e\xb9\xa1\x2b\x18\x72\x31\xa6\xea\x43\xc7\x5b\x74\x64\x62\x9c\xf7\x5b\x6a\x63\x6a\x97\x28\xa1\xee\x80\x99\x78\xd4\x6a\x26\x3e\xb5\x98\x49\x40\xed\x66\x10\x52\xbb\xac\x22\x6a\x37\x33\x4a\xed\xba\x8c\xe9\x80\xac\x13\x6a\x57\x62\x4a\xad\xf6\x99\x51\xbb\x8d\xe4\xd4\xae\x2b\x46\xad\x26\x5a\x50\x8b\x79\x63\xd7\xe6\x98\x18\xbb\x43\x8a\xc4\x64\xc4\x69\xb1\x37\xe2\x76\xd8\xb7\xbb\x2d\x0e\x46\x6c\x1f\x87\x23\x71\x01\x47\x76\xe7\x6d\xba\xb0\x21\xc5\xe1\x78\xc4\x4d\x70\x62\x0f\x7b\x38\x1d\x89\x0f\x38\xb3\x05\x1f\x9c\x8f\x84\x0f\xcc\x06\xbb\xd8\xc2\x1e\x00\xa0\x87\xb7\xaa\x07\xdb\xcd\x0a\x13\xbb\x2b\x63\x6f\xc4\x5d\xb1\x3f\xe2\x8f\x38\x18\x89\x42\xe1\x60\x98\xc1\xd1\x48\x2c\xc0\xd4\xea\x15\xf1\x88\x47\xe3\x64\x28\x16\xe0\x74\xc4\x5b\x71\x66\x8f\x06\x38\xb7\xc5\x40\xcc\x46\x82\x14\x2e\x7a\xd1\x64\xab\x7e\x5d\xf2\x4c\x0d\x61\xa2\xc6\x69\xe8\xd9\x45\x4d\x62\x6c\xae\xd7\x16\x13\x03\x62\xbf\x95\x85\x09\x79\xa0\xb6\xc5\xd0\xaf\x37\x85\x1a\xe2\xa8\x63\xbc\x43\xfd\x9b\xa9\x3f\x57\xf8\xd1\xb0\x26\x35\x49\x13\xb3\xa9\xb4\x46\x43\x1f\xae\x48\x48\x2b\xce\x15\x9c\x5a\x21\x6b\xda\xa8\x15\x15\x1d\xc9\x1a\x5a\x68\x53\x0b\xa6\x56\xb5\x10\x6a\x6e\x8a\x47\x2d\xfa\xf0\xa9\xa5\x21\x01\xb5\x1a\x58\x48\xad\x76\x10\xd1\x41\x31\x50\x6a\xb1\x82\x98\x0e\xda\x6d\x42\xad\x9a\x4e\xa9\x55\x69\x19\x35\xdb\x4f\x4e\xad\x4a\x61\xd4\x62\x3f\x05\xb5\x9a\x2c\x76\xed\xbe\x82\xb1\xdd\xce\x30\xb1\xbb\x22\xf6\x86\x6d\x0d\xfb\x36\x47\xc4\x81\xdd\xd9\x70\xe8\xda\xa3\x4b\x64\x73\xab\x66\xb0\x69\xe6\x3a\x1e\x0a\xc4\xc9\x80\x3b\xe2\xd4\x1e\xb1\x70\x36\x1c\xec\x70\x6e\x8f\x0f\x98\xd9\x9d\x12\x17\xb6\x98\xc5\xfb\x64\x33\xcb\xd8\x66\x37\x98\xd8\xbc\x12\x7b\x76\xcf\xc3\xbe\xdd\xf5\x70\x30\xec\x7b\x38\xb4\x87\x11\x1c\x8d\x45\x21\xbb\xbb\xe0\xd8\xee\x85\x38\x19\xf6\x6f\x9c\xda\xc2\x11\xce\x6c\x61\x03\xe7\x03\xee\x8d\xd9\x88\x8f\x16\xdd\xa8\x72\x83\xce\x57\x37\x8a\x9a\x4f\xdf\xc5\x7a\xe7\x2b\x47\x0a\x7a\xb7\x2b\x31\x1a\x2a\xf9\xcd\xf8\xc4\x50\x18\x98\x1b\x1e\x0a\x7c\x7a\x2f\xdb\x0e\x75\xb4\x42\xaa\x70\x6e\xe8\x68\x9b\x26\x6b\x15\x13\xc5\x9c\xb5\xc2\x54\xe1\xde\x30\x5d\x56\x86\x5e\x7a\x47\x2b\x84\xa5\xa1\x64\xad\xb0\x0c\x13\x65\x65\x88\xaa\xb5\xd0\xd2\x7a\x4c\x8d\x82\x24\xd4\xa2\x4e\x8f\x5a\xda\xe6\xd3\x61\x23\x09\xa8\x45\x62\x21\xb5\x34\x21\xa2\x03\x42\xa1\x74\xd8\x82\x62\x6a\xd1\x6c\x42\x2d\x3a\x48\xe9\xb0\xda\x33\x3a\x60\xcc\x39\xb5\x18\x2c\xa3\x46\xcb\x2c\xe8\xb0\x4e\xb1\x6b\x53\x2a\xc6\x36\x6f\x24\x36\x77\xc4\x9e\xcd\x1f\xb0\x6f\xb1\x79\x1c\xd8\xbc\x05\x87\x36\xab\xc7\x91\xc5\xdb\x65\xd7\x61\x2a\x89\x6d\x81\x00\x27\x16\xaf\xc0\xa9\x39\x1e\xe0\x6c\x20\x6e\xe6\x36\xc7\xc4\xcc\xe6\xf0\xb8\x18\x0a\x72\xd8\xb5\x29\x19\xdb\x3c\x0d\x13\xab\xef\x7a\x66\xa3\xc2\xbe\xcd\x95\x70\x60\xf1\x50\x1c\x0e\x39\x1a\x8e\x6c\x5e\x8f\xa9\xcd\xb5\x71\x6c\x73\x36\x9c\x98\x63\x10\x4e\x6d\x0e\x85\x33\x8b\xe7\xe3\x7c\xc8\x49\x31\xb3\x05\x05\x5c\x28\x8e\xbf\x55\xaf\x48\x79\x07\x4d\x74\x84\x8d\x54\xb4\x08\x7b\xbf\xdd\x17\xd0\x02\xac\xa8\xa5\x85\x56\x89\x4c\x2f\x09\x04\x32\x62\x62\x21\x6c\xca\x0c\x61\x55\x32\x61\xec\x31\xa8\x6b\x64\x3c\x6e\x9a\x6b\x08\xaa\x82\x43\x43\x49\xda\x60\xd4\x39\xcc\xee\x8b\xcb\x06\x5a\x40\x35\x9a\x05\x6b\x04\xa7\xd7\x28\x24\x7d\xbd\xa4\xde\x81\x19\x6a\xb0\x28\xc5\x16\x39\x4a\x10\x62\xd1\xb6\x04\xf1\x2c\xaa\x95\x3f\xfb\x16\x01\x4b\x90\x60\x50\x96\x12\x20\x1c\x6b\x6c\x64\x36\x23\x59\x4a\x2d\x72\x94\x20\xf1\x80\x62\x64\x71\x32\x68\x8f\x12\x20\xb5\x98\x9e\x04\xc9\x8c\x3a\x96\x85\xb9\xc5\x68\x24\x08\xb3\x58\xa3\x04\x29\x06\x7d\xb0\x1e\xb4\x9a\x1c\x17\x5b\x78\xc7\xc4\x2c\x59\xec\x0d\x98\x17\xf6\x2d\x6c\xe2\xc0\xa2\x09\x1c\x5a\x64\x80\xa3\xe1\xa6\x0d\xc4\xd4\x78\x50\x67\x38\xb1\xd8\x24\x4e\x6d\x41\x2e\xb3\xb8\x0d\xce\x07\xed\x18\xb3\x01\xfb\xc2\xc5\xa8\x07\xb5\xf3\x8d\x21\x00\x6c\x75\x75\x4c\x46\x6d\x0b\x7b\x23\x0e\x86\x7d\x9b\x83\xe1\x60\xd4\xc7\x6b\xe5\xda\xda\x19\x8d\xc6\x1b\x4c\x47\xa3\x16\x8e\x47\x9d\xbd\x56\xbf\xc5\x93\x84\x11\x0c\x15\x66\x23\xf1\x4a\x18\xc2\x30\x8b\x6c\x24\x9a\xd4\x46\x01\x24\xd4\xbe\xb9\xf9\xdc\xbb\xf0\xfe\x3c\x59\x7f\x58\xa3\x6a\x96\x54\x68\xcd\xe6\x2c\xab\x20\xd5\xce\xc9\xa3\x17\x3f\xa3\x72\x71\xae\x3c\x00\xd0\x5c\x75\x7f\xfe\xf0\xa4\xff\x3c\x6d\x7b\xe3\xcd\x41\xed\x79\x74\x78\x11\x4f\x7e\x81\xcf\xf2\x8b\xd3\xa9\xe9\xca\x9f\x05\x84\xf8\x52\x7f\xe6\x5f\x1c\xa5\x6d\xe6\x46\x74\xd3\x07\x3d\x3e\x3c\x11\x79\xa3\x90\xc8\x3f\xb2\xc9\xb3\x42\xbc\x4e\xf3\xa6\x90\xf8\xa2\xe4\xdf\xb8\xe9\xab\x42\xd6\x8c\x73\x1f\xd8\x75\x93\x27\xeb\x03\xbb\xd6\x73\xc2\x7d\x60\xd7\x75\xba\xb9\x0f\xec\xda\x98\x6d\x8e\x13\x10\x3a\x0b\x42\x94\x96\xd5\x1a\x25\x59\xb6\x5c\xe5\xe5\xe2\x3d\xaa\x96\xe8\xe5\x01\x36\x61\x7d\x54\x42\xa6\x9a\xd7\xbd\x5c\xbd\xa6\x17\x22\x82\x70\xe0\x85\x88\x16\xd5\xcb\x25\x47\xf6\xf2\x00\xbf\x2e\xdf\xa0\x7b\x08\xf7\xd3\x73\x4a\x82\x22\xb1\xfb\xa4\x6e\xd4\xeb\xb6\xb2\x4c\x50\xc7\xff\x99\x78\x18\xdd\x53\xf0\x42\x66\xba\x29\xba\xd3\xc7\xaa\xe7\xea\x7c\xb8\x5e\xb3\xb3\x74\xce\x10\x0e\xd1\xfa\x22\xfd\xc0\xae\x75\x89\xaf\x2f\xd2\x1f\xd8\xf5\xba\x91\x7a\xfb\x7d\x50\x14\x8b\x13\x80\x11\x02\xa9\xbf\x3c\x40\x38\x6c\xbe\x0d\x3c\x9f\x71\x00\x79\x92\x24\x2b\x06\xd9\xad\x6b\xbc\x92\x89\xd7\x12\xdd\x1b\xc9\x8d\x8e\xd1\xf2\x20\x47\x5a\x56\x27\x90\x2d\x63\x5f\xc9\x8c\xd1\x60\x34\x22\x13\x56\xe3\x53\xa3\xd5\x90\x5e\x05\x93\x65\x10\x7f\xc0\x32\x54\xfc\xc5\x6a\x79\x06\xe1\x64\xce\x8a\x0a\x11\x0a\x46\xcf\x29\x6a\x75\x84\x18\x5e\x4f\x4a\xb4\x27\x1e\x0c\x70\x21\x65\x61\x6d\x3c\x93\xc9\xcb\x03\x22\x0d\x6c\x8a\x76\x9a\x16\x4f\xd1\x5f\x10\xa1\x6f\x20\xab\x21\xd8\x4e\x89\xfe\x02\x6f\x1e\x6c\xc2\xd4\xaa\x7c\x3f\xdb\x88\x2b\x1f\x32\x54\xb6\xac\x4d\x3b\xbc\x11\x0a\xc5\x82\x43\xb4\x83\x88\x3f\xc0\xe6\xd4\xc0\x67\x97\x9e\x96\x2f\x9e\x73\x5d\x2e\x32\x86\x58\x92\xcd\xa4\x35\xa1\x72\x8d\x92\xf3\xf3\x79\xc9\x72\xae\xae\x64\x81\xd8\xd5\x79\xb2\xc8\x59\x5e\xe7\x1f\x84\xb8\xed\xe8\xa8\x78\xa3\x25\x8e\x2c\x59\xa0\x94\xa1\x74\xb5\xfc\xc0\x16\xa8\x5c\x54\x4b\x44\x45\x7a\xdb\x35\x5a\x67\xc9\x5c\xe0\x16\xf8\xd6\x06\x54\x97\xb3\x32\x9b\xa1\x64\x3e\x5f\x5e\xae\x01\x2f\x47\x5a\x2d\x39\xce\x8b\x35\xcb\xd1\x65\x59\xcd\x96\x17\x95\x60\x6d\x5d\x2e\x17\xdf\x18\x25\x0b\x89\x23\x27\xed\x97\x07\x0f\xe4\xa3\x21\xed\x4f\x3c\x2e\x78\x58\x13\x55\xc7\x26\xb1\xb0\xc9\x68\xd0\x24\x25\x3a\x88\x41\xed\x67\x08\x39\x93\x52\x68\xeb\x5b\xc4\xd5\xec\x19\x74\x62\x44\x15\xa9\x9c\x47\x6f\x64\x96\xca\xdf\xd4\x9f\x20\xc5\x7c\xff\xd5\x14\x3d\x74\x1d\x88\x4c\x8e\xa8\x5c\x7c\x64\xab\x35\x1b\x0c\x5f\xe5\xe2\xe3\x49\x2f\x82\x75\x7e\xda\x24\x9e\xe3\xa1\x78\xde\x62\x52\x85\xb4\x7e\x8d\x03\x6e\xac\x6f\xfa\x6d\x50\x3e\x77\xf2\x35\xb2\x45\xb6\xba\x3e\xaf\x36\x7f\x70\x4d\x26\x5c\x5d\x1e\x34\xd5\x5a\x58\xa7\x1b\xa7\x2d\x19\x60\x73\xf6\x05\xc8\xb6\x22\xb1\xe7\x9e\x3d\xb0\x93\x76\x6a\x59\x1a\x46\x07\x7f\x65\x55\x6f\x90\xd5\x32\x37\x07\x94\xca\x1b\x46\x3d\x25\x08\xa8\x55\x1f\x0a\x5e\x38\xd2\x87\x09\x4f\x17\x65\x55\x26\x73\x35\x9b\x51\x07\x84\x5d\x65\xb3\x64\xf1\x9e\x3d\x3b\x6e\x13\x7c\x8a\xe4\x52\xee\x95\x5b\x88\xff\x7a\x96\x6c\xae\x22\x5e\x08\x86\x09\x65\x51\x0c\x55\x39\x7e\xa6\x56\x21\x40\xc5\x93\x7f\x36\xaa\x42\x05\x63\x6e\x51\xf0\xff\x37\x63\x0c\xaa\x04\xf2\x8f\x29\xbd\xaa\xe5\xd1\x2c\x91\x0a\x0a\xdc\x48\x7c\x14\xae\x04\x9f\x47\x1e\xd0\xea\xf7\x2a\xc6\xde\x1f\x10\x19\xdf\x4e\x68\xcc\x40\xb5\x0a\x13\xe0\xaa\x0b\xb8\x92\x80\x3a\x63\x4f\x58\xb9\xae\xd8\xbc\xb1\x55\x03\xae\x02\x9a\xba\xc1\x28\x80\x0e\x46\xdc\x82\x77\x91\x22\x33\xd6\xeb\xf2\xcd\xeb\xc9\x44\xf2\xf7\xb6\x8d\xbf\x7c\x60\xd7\x4c\x28\xe0\x3b\xe4\x7c\xd6\xc4\xd0\x8f\xc0\x3d\xff\x58\x19\x45\xd2\x73\x8f\x79\x4d\xbd\x18\x0b\xc7\x3f\x2d\xf2\x25\x5a\x5f\x26\xe7\x62\xa0\x30\x4f\xd6\x95\xd0\xb3\x16\x90\x2b\xab\x56\x7a\x5c\x76\xb5\x62\xe1\xb4\xd2\x2d\x13\xb2\x5b\x6f\xe9\xba\x9a\xb9\x9b\xaa\xdc\xc0\xa9\x6e\xe0\xba\x37\x08\x10\x23\x61\x48\x9f\x04\x55\x68\x79\x51\xf5\x03\x69\x13\x39\xad\x8a\xea\x44\x4e\x8b\xa6\xba\x81\xff\x03\xbb\x16\x79\x89\x43\x7f\xcf\x23\x9d\xa2\xf2\xe3\x50\x89\x92\xce\x38\xb4\x24\x33\xde\x43\x27\xdc\xfe\xe4\xd0\x7c\xb5\x5c\xaf\xdb\x41\x34\x24\xa6\x83\x81\x2b\x4c\x08\x9b\x4a\x4d\xcf\xd3\x8a\x70\x52\x77\x40\x67\xc9\xfa\x43\xd7\x47\x6b\xf3\x9d\x4c\x3a\x66\xca\xfd\xaf\xee\x30\xdf\x76\x04\xc1\x7d\x95\xa3\x51\x05\xd2\x31\xdc\xb7\x60\xb9\xfd\xc2\x79\x5b\xc8\x47\x48\x02\x77\x0b\xa6\x78\x9f\xc6\xff\xf1\xb3\x2d\xf8\x5f\x0d\xf3\x3f\x1f\xe1\x7f\x6e\xe3\x7f\xb5\x31\xff\x63\x39\x8c\xd7\x75\x12\x63\xb1\x28\xb1\x4d\x1a\xe3\xcd\xd2\xfd\x0a\xbc\x15\xbb\xaa\xd4\xa4\xbf\x8f\x0f\x4f\x76\xe5\xa0\xac\x93\xf5\xd7\x41\x59\xf1\xde\x9c\xf6\xf9\x7c\x9e\x70\x9e\xae\x2a\xd4\x47\x24\x87\x59\x93\x96\xd4\x00\x2e\x35\x8d\xb0\xbe\x9a\xd2\x4d\x07\xfd\xf8\xf0\xc4\x92\x0b\xfa\x74\x55\x9e\xcf\xd9\xbd\x9b\xac\xe9\x88\xaa\x9d\x95\x1d\xf5\xa7\x7f\x91\xf5\x1d\xb9\x90\xc0\x79\x2e\x21\xf9\x64\xc6\x4c\x49\xea\x73\xb6\xc6\x68\x9f\x83\xed\x0a\xe9\x1e\x0a\xa5\x2f\x57\x93\xf6\x1d\x6b\xf9\x50\x77\x4d\x78\x77\x3d\x2f\x33\x36\x71\x1d\x44\xa6\xfd\xe7\x1c\x1a\xac\xe4\x86\x58\x89\x83\xfc\x61\xac\xde\x0d\xb1\xfa\x0e\x0a\xa7\xb6\xb7\x20\x6e\x3a\x01\x61\x6b\xbc\xab\xd6\x55\x2a\x0c\x09\x66\x57\x9d\x75\x8c\xc3\x7b\x9b\xe0\xbf\x9d\x69\x0d\x27\xb6\x1d\x73\x64\xcb\xc6\xe3\x4d\xf0\x0f\x74\x98\x38\x26\xb7\xde\x63\xfe\x73\x06\xdf\x26\xdc\xdc\x4e\x08\x6e\xd1\xdd\x28\x10\x0f\x05\xbf\x6e\x38\x6e\xa0\x36\x4c\xe9\xde\xc0\x1b\xb3\xba\x2b\x09\xc0\xef\x07\xd8\xe9\x25\x7a\x57\x92\x88\xdf\x0f\x3c\xa7\xcd\x1e\x7e\x3f\x08\x1c\x99\x54\xfc\x7e\xe8\x7e\x7a\xe3\x50\xef\xb3\x73\xc0\xff\xa1\xc9\xdf\x7f\xe7\x14\xed\xff\xbc\xc9\xd6\x21\x0f\x7f\xb9\x60\xfd\xd6\xdc\x62\xd6\xf5\x47\xc9\x9a\xb5\x59\xd4\x93\x35\xeb\x16\xff\xec\x11\x6b\x5e\xf6\xa1\xa0\x72\x15\xfa\x68\x91\x9c\xb1\xf5\x79\x2f\x5c\xec\xf5\x18\xe3\x80\x9c\x31\xf1\xef\xdf\x3f\x0d\xa3\x7c\x88\x42\xbf\x79\x30\x66\x18\xe5\xcf\xa1\xcf\xf9\x03\x66\xaf\x42\x7f\x57\x7e\xe1\x4d\x33\x8d\x5c\x3a\x44\x38\x19\xb9\xf6\x53\xfe\xca\xd6\x28\x41\x0b\x76\x39\xbf\x46\xc2\xe7\xf3\x01\x06\x3a\x51\x4e\x7d\x08\x63\x71\x71\x96\xb2\xd5\x27\x04\x4f\x38\xc1\xd3\x25\xfc\x83\x47\x60\xae\xb2\x6b\xaf\x33\x5f\x5e\x42\x15\xfe\xaf\xb1\x46\xaf\xba\x16\x79\x35\x90\x5a\x40\x57\xad\x80\xea\xb0\x54\xcb\xa9\x1e\x47\x88\x6d\x46\x97\xb8\x1e\xcc\x3e\x7d\x37\x70\x43\xb7\xb7\x36\xab\x88\x1d\xa1\x72\x51\x76\xc6\x7e\xbc\xa1\x0e\xe7\xdd\xd8\xe7\xca\x27\xad\xf4\xf7\x2d\xa1\x94\xb7\x78\x1f\xf5\x5e\x83\xee\xac\x3c\x68\x3a\x7b\x54\x56\x97\xe5\x9a\xa1\x1f\x5f\x9c\xae\x01\xc9\xb8\x8a\xea\x77\x46\xa4\xb9\x7c\x42\x0f\xb9\xae\xb9\x70\xee\x81\x74\x64\x97\x97\x14\x15\x5b\xa1\x05\x7b\x9f\x54\xe5\xe2\xfd\x2d\x69\x00\xd0\x31\xae\x01\xa9\x8b\xdd\xc5\xb2\x9a\x58\xc4\xbb\xb7\x87\x16\x4b\xcb\xe0\x7a\x6f\x4f\xbc\x61\x22\xe4\xfa\x8f\x46\xc8\xdf\xe9\x30\x42\xba\xff\xa8\x25\xdd\x19\x42\xef\xed\xd5\x1d\x96\x94\x4a\x6d\x0f\xad\x3a\x7b\x18\xbb\x83\x96\x41\xbd\x3c\xfc\xf1\xb1\xa2\x17\xd8\xee\x80\x01\xc6\x79\xb2\x86\xed\x8f\x0d\xfd\xa9\xd1\x15\x60\xe1\xde\xd1\xa8\xab\x5a\x72\x22\x35\xe6\x5b\x37\x80\x87\x3f\x3e\xbe\x3d\xf5\x8b\xdd\xa7\x56\xf9\xc9\x22\x9f\x24\x8b\x65\x35\x63\x2b\xc9\x8c\xdd\x14\x92\x45\xae\x9a\x02\x6f\xa9\xcd\x1c\x5a\x97\xbb\x23\xa4\x62\xb5\x8c\xc6\x05\x25\xf0\xef\x66\x23\x2f\x8e\xbf\xbc\x89\xbc\x38\xfe\x62\x16\xf2\xe2\xf8\xf6\x0c\x64\xb9\xea\xd8\xc7\x72\xb5\x95\x79\x2c\x57\x37\xb3\x8e\xdf\xb6\xb1\x8e\xdf\x7e\x67\xeb\xf8\xf9\xf7\x30\x8f\x9f\xbf\xa0\x7d\xfc\x7c\x9b\x06\x72\xd5\xb3\x90\xab\x2d\x4d\xe4\xea\xa6\x36\xf2\x76\x1b\x1b\x79\xfb\x3b\xd9\x08\x1c\x1a\x50\xad\x63\x21\x16\x83\xe5\x2c\x76\xce\x8a\x6a\x9b\xd1\xda\x02\xec\x42\x7c\x43\xcb\xa2\xc1\x05\x4f\xac\xdc\x9e\x41\x00\xba\xdb\x33\x09\x40\xd7\x31\x0a\xf8\xe5\xd9\x84\x04\x76\x5b\x10\x60\xaa\x39\x2c\x34\x5b\xe0\x53\xaf\x05\x7a\x80\x3c\x62\x33\x93\x49\x6b\x27\x0f\x1e\xa0\x05\xec\xef\x37\xc6\x20\x0e\x2c\x11\x74\x0f\x2d\xa6\x7d\xfd\x1a\x6c\x87\x23\xe8\x5b\x41\x7f\x36\x66\x34\x53\x59\x79\xb2\x40\xf7\xfa\x0f\x75\x76\x28\xb9\x7d\xec\x7f\xa4\x89\xc2\x16\xc5\x7f\xac\x8d\x1e\x4f\x6c\x53\x8b\xda\x44\x8f\x3f\xcb\x44\x85\xd2\xbb\xe6\xa8\x58\x68\x6d\xb4\x36\x0b\xd5\x82\x21\xe0\xd8\xd8\x48\x15\x03\x6f\x6a\x5b\xac\x54\x52\xfb\x7d\xcd\xf4\x78\x59\x25\x15\xfb\xf2\xa1\x74\x05\x74\x6e\xcf\x4e\x01\xdf\xed\xd9\xa9\x60\x4f\xb5\xd3\xd5\x72\x83\x48\xca\x81\xec\x46\x2a\x1b\x05\x46\x20\xc3\xf3\x62\xca\xc7\x77\xed\x2f\xc7\x93\xd0\x37\x9a\xe0\x67\xaa\xee\xd6\x42\xcc\xbf\xa2\xee\x46\x23\x0c\x87\xd9\x52\x75\xc7\x9a\xea\x9e\xdd\x50\x75\x0f\xf3\xfc\xcb\x8f\x6d\x93\x3c\xff\x62\x63\x5b\xf1\xb6\xf6\x6d\xcd\x8e\xf3\xde\xec\x38\xdf\x72\x76\x9c\x6f\x36\x3b\xee\xf7\x08\x3b\xcd\x68\x15\x4e\xad\x1a\x42\x72\x96\xac\x56\xd7\xbc\x4e\xdd\x81\x88\xd7\xd7\x3b\x7d\x4a\xfb\x06\xfb\x70\x4c\x57\xba\x9c\x9d\x76\x3c\x8d\x76\x04\x01\x49\xfd\xf3\xc2\x7c\xf3\xd5\xb6\xf1\xf3\x70\x21\x9f\xdd\x5e\x16\xea\xca\xe6\x7a\xf8\x85\xe0\xd5\xf2\x9c\xad\xaa\x6b\xf4\x77\xf9\x32\x2f\x80\x83\xa9\x35\x88\xb4\x35\x45\x69\x2a\xeb\xdd\x01\x54\x75\xbc\x69\x5e\x23\xef\x86\x9d\x75\xf9\x7e\x51\x16\x65\x96\x2c\x2a\x94\x42\x79\xb9\x50\x9c\x05\xe8\x8e\x2e\x06\xb7\xcb\xd7\x35\x57\xf5\x2f\xb7\xb2\x2c\x6c\xe4\x64\xd0\x4f\x3b\xb2\x9b\xbc\x38\xe7\x76\x9a\xcc\xa7\x1d\x75\x6c\x22\x45\x53\xd0\x6e\x84\xa8\xe0\xdd\x48\x9e\x4c\x53\xfd\xe7\xf8\xf0\xa5\x2a\xf3\x7e\x53\x3a\x3b\xd6\x3d\x5f\xfe\x5c\x74\xaf\x75\x04\xfc\xcf\xb6\x0b\xdd\xce\xd6\x68\x30\xc5\x31\x4e\x70\x0a\x17\x7a\x32\x9c\x63\x86\x8b\xa9\x8e\xe5\xcd\x7f\x5a\x7b\x1d\x84\xdd\x2d\xf6\x0d\xc0\x08\x9d\xc6\x8c\xfb\xbb\x07\x97\xf2\x04\x08\x04\xd0\xfa\x8b\xf8\xf7\xb7\xdf\xf4\x9b\x20\x7c\x4e\xd0\x78\xc4\x9f\xf6\x91\x71\x3b\x4d\xfc\x11\xbd\x78\x0d\xbb\xdf\x70\xd0\x3b\xff\xa8\x8f\xee\x4d\x95\x85\x2b\xcd\xd9\xe2\x7d\x35\x43\xdf\x22\xba\xe9\x79\x70\x2d\xe8\x1c\x2c\x17\x1f\xd9\xaa\x9e\x38\x2a\x31\x5a\x46\x0a\xde\xaf\xd7\xd7\x18\x36\x0c\x42\x75\xc7\xde\xe8\xb6\xb3\xa1\xf7\x09\x9d\x76\x43\xeb\xdd\x35\xca\x93\x2a\x41\xc9\x7a\x6b\x4a\x5b\x2c\x67\x75\x37\x19\xaf\x94\xa0\xbd\x5b\x2d\x7f\xf6\x88\x6d\x6f\x04\x00\x6e\x7c\xee\x48\xd2\xea\x1a\x95\x7e\xfa\xa8\x06\x7b\x26\x74\xda\xf0\x58\x6b\xd9\x70\x12\x49\xa8\x4e\xc7\x24\xda\xba\xe9\x15\x84\x2e\x61\xcb\xf5\xb2\x76\x57\xaf\xae\xf2\xba\x7f\x11\x01\x35\xc4\x77\xcf\x2f\xd6\xb3\x49\x3d\xc2\xe2\xc3\x08\xed\x22\x87\x19\xb4\x3f\xd6\xd0\x8e\x04\xd7\x03\x15\x45\xa3\x75\xd0\xa8\x31\x3a\x5d\x7f\xb1\x1c\x82\xd1\xfd\x01\x30\x71\x53\xcc\x96\xe7\xd0\x51\x0e\x8f\x04\x46\xc7\xb4\x8d\xc1\x33\x94\xcd\x97\x0b\xfb\x94\x66\x73\x63\x06\x5c\x7d\x2b\x86\x1f\x6d\x56\x0c\x00\x36\x2b\x56\x51\xc3\xa0\x45\xf0\xdc\x9c\xfb\x35\x9c\xf3\x3d\x00\xf0\x3f\x83\x49\xff\x59\xc8\x47\x43\x59\x07\x51\x81\x4d\x8f\xaf\xf5\xa9\xb7\x41\xf4\x70\xd5\xaa\xde\x73\x17\x83\x14\x33\x91\xc6\x75\x2e\x3b\x7e\xa3\xc2\x9a\x1c\xe0\x72\xd4\xfa\x2f\xa5\xb1\xd7\xa8\x5f\x97\x6f\x8c\xf2\x1e\x30\x55\x00\xed\x6e\x35\x37\x9f\x36\x3b\xd5\x63\x3c\xcb\x23\x0f\xee\x7c\x7a\xe3\x50\x7f\xd3\xd3\x38\x7b\xdf\xfe\x09\xcd\xaa\xea\x7c\x7d\x7f\x6f\xef\xac\x9a\xad\x77\x53\xb6\x77\x51\x15\xf4\x97\x35\xfa\x48\x76\xf1\x2e\x41\xe9\x35\xfa\x7f\xcf\x92\x6a\x56\x26\xeb\xda\x7e\xda\x23\x3c\x70\xf4\x44\x39\x48\xb2\xb7\x87\x1e\xb3\x4a\x5c\xe2\x63\x8c\x6b\xa2\x4c\xd2\x39\x5b\xa3\xbf\x49\xb2\x7f\xfb\xa6\xd5\x11\x07\x39\x6c\x8e\xe6\x68\x07\x7e\xd0\x5d\xa1\xdb\xbb\xe8\xce\x9d\xfa\xe7\xef\x46\x08\xa1\xbf\x89\x56\xf6\xc9\x3c\x87\x5f\x5b\x2a\x67\xf2\x7b\x97\x88\xfc\xf5\xce\x1d\x45\x35\xfd\xc3\x44\xfb\x1d\xb6\x9b\x4a\xe3\x8c\xbd\x87\x53\x3c\x7f\x73\xc4\xad\x85\x1f\x97\x39\xdb\xfd\x65\x8d\x96\x2b\xf4\x48\x1c\xeb\x29\x8b\x92\xe5\x28\x5b\xe6\xcc\x69\x51\x25\x8b\x1c\x5d\xac\x19\x2a\x2b\xde\x1d\xfe\x8d\x4b\xbb\xdf\x34\x79\x3a\xa8\x69\xda\x7b\xf9\xbd\xdb\x34\xf1\x6b\x6d\x5b\x7c\x94\xd2\xd6\xdd\x6d\xaa\xec\xab\x18\x7f\xfb\x4d\xf9\xb6\x7b\x59\x2e\x72\x3e\x7d\xed\xc0\xa8\x9e\xc1\x79\x43\x6a\x61\x7b\x0e\xa9\x6e\xcf\xb7\xf7\x6e\xed\xcf\xb7\x7b\xdf\x28\x72\x58\x57\xab\x72\xf1\xfe\xc9\x6a\x79\x76\x30\x4b\x56\x07\xcb\x9c\xab\xfa\x04\x7e\xdc\x2d\x94\x5f\x55\x2d\x9d\x26\x1f\xd8\x42\x28\xa3\xef\x00\xe7\x17\x8b\x6b\xae\x08\x09\xdc\x44\xc8\x8b\x6c\x4d\x72\xc6\x4b\x26\x82\xa2\xda\x7e\xd8\x45\x85\x2b\x0e\xbd\x7e\x16\xc2\xe8\xf2\x62\x51\xb1\x55\x77\x75\x15\x96\x04\xea\x10\x25\x10\x6a\x31\x8a\xc3\xc0\xad\xcf\xee\x4f\xec\xaa\x5a\x25\xed\x4f\x97\xb3\x72\xce\xd0\xa4\xa6\xf2\x40\xa2\xed\x1f\xc9\x9f\x5f\xb0\x96\x52\x26\x65\xf2\xb0\xaa\xeb\xed\xec\x74\xa2\x17\x37\x12\x51\xe7\xfb\x7d\xe4\x5e\x3d\xa6\xae\xcb\x2d\x49\xfc\xf4\x00\x7e\x7a\xf4\xe4\x09\xff\xc9\x4e\x17\xa4\x0d\xcb\x0d\xeb\x8b\xd5\x6a\xf9\x3e\xa9\x98\x03\x86\x5d\xcd\xd8\x8a\xc1\x65\x59\xb4\x60\x57\x15\xe2\x1c\x25\x59\xc5\x56\x9d\xca\xd0\xd4\x2d\xd8\x16\x8c\x4f\x44\xb5\x3b\xc8\xbd\x7a\x72\xe0\xba\x53\xee\x0f\xee\xd5\x63\xf8\xf8\x77\xce\xd0\x7c\x79\xd9\xf2\xd3\x0b\xef\x42\x8d\x62\x18\x32\x91\x32\xe0\x98\xbc\x27\x4f\xa6\x70\x03\xd6\x9d\xa2\x1d\xa4\x90\x80\x82\x9d\x3a\xe3\x52\xbf\x0f\x30\x8f\xe0\xf7\xf6\xd0\xc5\xe2\x2c\xa9\xb2\x19\xcb\x5b\x4e\xbe\x43\xcb\xc5\xfc\x1a\x25\xe7\xe7\x0c\x24\x54\xae\x21\x24\xa0\x8b\x45\x59\x39\x7c\xde\x9c\x25\x6b\x06\x93\x67\x2e\x32\x1d\x65\x03\xcc\xe5\x5a\xd5\xe7\xbf\x1a\xf4\x7c\xfc\x92\x28\x5f\xcf\x93\x72\x65\x69\x3c\x34\x5d\x1b\xa7\x49\xb9\xdf\xbb\x67\x99\x64\x98\xda\x6c\xc7\xac\xf4\x81\x6d\x4c\x11\xbd\x9e\xa8\xa8\xc7\x93\x1b\x3b\x31\x5b\x80\x13\xc3\xf8\xa4\xef\xc3\x8d\x47\x8a\xd1\x9d\xc9\x21\xcb\x45\xce\xae\xd0\x3e\xba\x87\x47\x1d\xb5\x89\x08\x77\xef\x6a\xee\xba\xb3\x23\x10\xd9\xdd\x15\xd8\x78\x0d\x90\x6f\x06\xdc\x93\x1b\xf9\x13\x6e\x82\xfd\xf1\x1b\x2f\xbd\xb7\x5f\xdb\xe5\x77\x06\x6d\xa0\x9d\x7d\x43\xf4\xac\x11\x7f\xff\x3d\xc2\x6e\x6d\xe1\xe8\x37\x19\x05\xa6\xfd\xc9\x8a\x60\x54\xb8\x17\xfa\x0d\x75\x1c\xc6\xac\xe2\x4d\xc8\x4f\xbf\xdb\xda\x22\x1a\x25\x67\x33\x96\x7d\x38\xc9\x92\x79\xb2\xfa\x2f\x8e\x6b\xc2\xf5\xfd\x72\x59\x2e\x3a\x27\xf2\xb9\x08\x9b\x82\x6e\x94\x6b\x7f\x16\x91\x4e\x13\x6f\x35\x5b\x2d\x2f\xd1\xe1\x6a\xb5\x5c\x4d\x7a\xfe\x71\xf7\x19\x1f\xa5\xb6\x4e\xf6\xd3\xce\x5d\xb4\xd3\x62\xdc\xad\x96\xa2\x6b\x9a\xe0\x70\xba\x5b\x2d\x7f\x3a\x3f\x67\xab\x83\x64\xcd\x26\x53\xb4\xd3\x47\xc5\xdd\x78\xb1\xac\xb8\xd3\x42\x6b\x84\x70\xef\x2a\x60\x06\x31\x7d\xfa\xa2\x1d\x6e\x2b\x64\x98\x1a\xf1\x19\x55\x2b\x45\x47\xec\x9c\x76\x06\x06\x42\x65\x06\x35\x4f\x54\xe9\x7f\x5f\xd7\x14\x86\x03\x6b\xd2\x57\xd4\x9d\x0e\x2b\x59\x78\xf1\x41\x8d\x62\x58\xc7\x0a\x99\x3b\xd2\x57\x9e\x3c\xa1\xb2\x53\x90\xfd\x01\xbe\x97\x5e\x57\x0c\xad\xd9\xff\x5c\xb0\x45\xa6\xf6\x08\xc3\xfc\xb7\x04\x4d\xa6\x0a\x03\x93\xeb\xb3\x74\x39\xef\xb9\xff\x10\x4b\xd4\xed\xb2\x44\x06\x59\x6a\xd0\x8e\x0b\x35\x14\x02\xc5\x52\xa0\x07\xae\x91\xd7\xe6\xb4\xbb\x81\x2f\xe8\xd2\x54\xbe\xbc\x41\xbe\x2c\x6e\xf7\xdd\xcd\xd8\xc7\x44\xf0\xef\x4a\xfe\x0f\x5d\x13\xa6\x9d\xfd\x01\x63\x0c\xb7\x6c\xed\xa1\xab\xb5\xd6\xbf\x0d\x2d\x60\x2a\x9b\x11\x89\x66\x3c\xd9\xae\x19\x98\xdc\x42\xab\xdb\x5a\x26\x7e\xbb\x92\x30\xf8\x5f\xeb\x08\x80\x67\xd8\x2d\xf9\xf4\x51\x76\xb0\xe6\x51\x72\x43\x8a\x4f\xfb\xf4\x31\xf5\xc0\x00\xb9\xad\xb5\x55\x9f\xdc\x54\xeb\xfe\xcc\x75\x2a\xa2\xf0\x4d\xfa\xe6\x56\x58\x2a\x5f\x86\x4e\x5a\x21\xb3\xb3\x6f\x09\x58\x96\xae\xae\x45\xf1\x3b\x4d\xa8\x1a\x45\xae\x58\x92\x1f\x2c\x17\x55\xb9\xb8\x80\x4b\xed\x60\x66\xfd\xd8\xca\xd9\x7b\x0a\xa2\xfa\x7e\x1f\x78\x3d\xe0\xa3\xc3\xe1\xbe\xf2\xee\xd3\xc5\xc7\x64\x5e\xe6\x00\x2b\xb4\x76\x77\x6a\xbc\xb0\x2b\xb4\xd7\x25\x8f\x04\x0d\x58\xe1\x7a\xdd\x90\x7e\x23\xbd\xb7\x45\xd3\x14\xed\xec\x28\xab\x48\xd2\xe5\x7b\x28\xef\x88\xb0\x28\x66\x08\xbc\x57\xf8\xbb\x1e\xfc\x8d\x95\xbc\x27\xe6\x8b\xc6\x7b\xe8\x69\x81\x2e\x19\xe2\x23\xf8\x8b\x73\xc4\x67\x39\x0e\x2a\xab\xff\xfd\xff\xff\xaf\xba\x2f\x57\xd1\x01\xaf\xdf\x58\x45\xa5\x81\xdf\xb5\xf4\x8a\xc2\x99\x4e\xc0\x49\x27\x7d\xd7\xe3\x95\xb1\xee\x09\x44\xff\xc9\xd3\x7f\xf2\x07\x3d\x6b\xc8\x22\x6e\xd7\x20\xba\xc8\xf7\x07\xcd\x4d\xea\xac\x48\xe6\xea\xd5\xa2\xae\x86\x8e\x59\x92\xa3\xa2\x5c\xad\xab\xae\xfc\x41\x40\x9f\x67\x65\xfa\x60\x02\x4d\x16\x4b\x5d\x89\xeb\x69\xd7\x30\x05\xe9\x3b\xd2\x08\x65\x0f\xa4\x37\xab\xa7\xc2\x6e\xb3\x86\x86\x0c\x3d\x02\x87\xb5\xb9\x1f\xf4\x68\x80\x31\xa0\xfd\x01\xd7\xff\x6e\x20\x04\xaa\xa8\xb1\x98\x22\x43\x1e\xae\x9e\x69\x19\x07\xdc\x7d\x87\x53\x5c\x4e\x0b\xdc\x03\xf3\xca\x6d\x7d\x06\xf5\x66\x9b\xaa\x00\xbd\xbe\xe6\xce\x92\x6b\x54\x2e\xb2\xf9\x05\xcc\xab\xf9\x7c\x59\x9d\xae\x0f\xeb\xf0\x49\x2d\xe2\xc3\x1b\x8b\x18\xdc\xf0\xb3\x94\xe1\xca\xf5\x0a\x02\xc7\x0e\x05\x65\x55\x39\xde\x88\x72\x5c\x18\x92\x76\xa5\xbd\xe1\x18\xef\x0f\xd6\xe3\xd0\xa8\xad\xaf\x25\x2a\xb5\xf4\xe4\x77\xd0\x12\xc4\xd0\xcf\xd3\x67\x04\xfa\x74\xaf\xfa\x1a\x75\xaf\xdc\x83\x29\xfa\xad\x37\x7b\x9c\x08\xde\x44\x79\xa3\x74\x7f\x4c\xe9\xb0\x58\x60\x98\x04\x63\xd7\xb4\xca\x60\x55\xb3\x49\x3b\x46\x35\xff\x74\xfa\xe4\x1e\x45\x39\xac\x9c\xb3\xdc\xd0\xbf\xd5\x9d\x90\xbc\x04\xda\xfd\x11\x3a\x81\xfe\x8f\x10\x99\xbf\x33\x8d\x4e\xe5\x78\xb3\x1d\x59\xa9\x6d\x6a\x88\x74\x07\xa7\x0a\x70\xb7\x03\x00\xda\x6a\x8f\xa1\x0d\x4f\x95\x3e\xab\xbf\xfc\xdb\x19\x0a\xf7\x17\x8d\xab\xb3\x73\x6d\x5c\x3a\xa9\xce\xce\xd1\x7e\xaf\x93\x9f\xa2\x3f\xed\xef\x8b\xfe\x6e\x60\x9c\x2a\xf7\x54\xab\xb3\x73\xdb\x50\x53\x59\x1a\x6b\x6b\xea\xaa\xf8\x72\xeb\xf8\x5c\x39\x68\x5f\x69\xc2\xdd\x8f\x6c\xb5\x2e\x97\x8b\xbb\xf7\xd1\x5d\xd8\x8e\xba\xdb\x9e\x1b\xb9\x2b\x58\xbd\x7b\x5f\x99\x71\x28\xa5\x42\x44\xb2\x54\x7c\xa9\x1b\xa2\x6e\x03\x9c\x2c\xcf\x18\x7a\xf8\xfc\x31\x4a\x2f\xca\x79\x8e\x96\xe7\x55\x79\x56\xfe\xca\x56\x6b\x07\xcd\xcb\x0f\x0c\xad\x76\x7f\x59\x3b\x22\xe8\xc1\x66\xe1\xfa\x9c\x65\x65\x51\x66\x3c\x30\xe5\x25\x98\xd4\x79\x52\x55\x6c\xb5\x58\xb7\x48\xa1\x66\x35\x63\xa8\x58\xce\xe7\xcb\xcb\x72\xf1\xfe\xfe\x37\xad\xbf\xa9\x03\xa1\xde\xe5\x72\x74\xb7\x36\xd4\xbb\xdd\x6d\xa8\x0e\xe0\x6e\x72\x96\xf7\x36\x78\x14\xd0\x16\x46\xfe\xa8\xda\x84\xbc\x88\xde\xec\xdf\x19\x87\x18\x5c\x62\x8a\x99\xb4\x16\xf0\x7f\x35\x33\xe7\xde\x26\xd8\x9f\x94\xef\xbb\x8b\x65\xce\x4e\xaf\xcf\x59\x7f\xaa\xd0\x6e\xc4\xc9\xa9\x75\xb9\x50\xf7\xc1\x8e\xcb\xc5\xfb\xe5\xff\x77\x82\x3e\xba\xbb\x74\xd7\x55\x57\xc2\xda\x8a\xca\xa5\xfe\x1e\x93\xb2\x43\xa9\xd1\x26\xab\xcb\x59\x32\xef\xa1\x8d\x76\xdd\x7b\x0a\x5a\x58\x9f\x15\x87\x4a\xc5\x35\xf0\x6e\xd1\x2c\x59\xbf\xb8\x5c\xbc\xac\x4f\x0d\xee\x4b\xd8\xdd\xee\xef\x6a\xad\x66\x33\x19\x92\x80\x0a\x39\xf6\xc3\x65\xb7\xb6\xd8\x4c\xe7\x70\x90\x1f\x62\xca\x65\xa9\xca\xf6\xf5\x07\x91\x71\x96\x43\xc0\xe7\x91\x25\xf3\x9e\x18\x8e\x67\xe5\x62\xc9\x85\x90\xa0\x4b\x96\x22\x99\x40\xa0\x75\xfb\xe5\xb2\xda\x95\x3e\xa7\x8a\x53\x7a\xba\x48\x1d\x30\x15\xfb\xf0\x9f\x9c\xbf\x7f\x7a\xe3\xd0\x60\xd3\x8d\x65\x2d\x09\xc3\xcf\xcf\x9f\x1d\x55\xd5\xf9\x31\xef\x91\xd7\x95\x82\xf3\xcf\x69\xf9\x5e\x1c\x16\xdc\xfd\x65\xfd\xe7\x4d\xf1\xdf\xbd\x58\x33\x58\xc8\xc8\xaa\xbb\xd2\x9f\x35\x92\x8f\xca\xf7\x3f\x02\xe2\xef\xb8\x3c\x7e\x59\xcf\x78\x57\x56\xbe\x5f\x2c\x57\xec\xfe\xbc\x5c\xb0\x6f\x14\x26\x2e\x59\xea\x6d\x4c\x9c\xeb\xf8\x15\x4b\x45\xb7\x2f\x32\x49\xdc\xdd\xdd\x9b\x97\xe9\x1e\x47\xc3\x7b\xaf\x6f\xf6\xf6\x50\xbe\x5c\x54\x68\xf9\x91\xad\x56\x65\xce\xea\x7d\xd9\x7a\x43\xf8\x1b\xd4\x4d\x74\x21\xf7\x57\x79\x20\xbf\xdb\x1c\x1a\x83\xad\xdb\x0e\xc0\xae\x20\xdb\x85\x6a\x6d\xac\x03\x04\x2c\x0a\x9d\x7e\x1a\x90\x90\x80\x68\x8e\x10\xd4\x2d\xf8\xf3\x7d\xe2\x7f\x7a\xc3\x25\xe3\xbc\x16\x92\x79\x33\xfd\x66\x6f\xef\xff\x46\xeb\xe5\xc5\x2a\x63\xcf\x93\xf3\xf3\x72\xf1\xfe\xa7\xe3\x67\xfb\xbc\xf0\xde\x1c\x0e\xfa\xff\xb2\xde\x3d\x4b\xce\xbf\xf9\x3f\x01\x00\x00\xff\xff\xab\x38\x4d\x9a\x2f\x66\x06\x00") func web3JsBytes() ([]byte, error) { return bindataRead( diff --git a/internal/jsre/deps/web3.js b/internal/jsre/deps/web3.js index bf139453c0..c53c45163c 100644 --- a/internal/jsre/deps/web3.js +++ b/internal/jsre/deps/web3.js @@ -1,607 +1,607 @@ require=(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i. */ -/** - * @file coder.js - * @author Marek Kotewicz - * @date 2015 - */ - -var f = require('./formatters'); - -var SolidityTypeAddress = require('./address'); -var SolidityTypeBool = require('./bool'); -var SolidityTypeInt = require('./int'); -var SolidityTypeUInt = require('./uint'); -var SolidityTypeDynamicBytes = require('./dynamicbytes'); -var SolidityTypeString = require('./string'); -var SolidityTypeReal = require('./real'); -var SolidityTypeUReal = require('./ureal'); -var SolidityTypeBytes = require('./bytes'); - -var isDynamic = function (solidityType, type) { - return solidityType.isDynamicType(type) || + /** + * @file coder.js + * @author Marek Kotewicz + * @date 2015 + */ + + var f = require('./formatters'); + + var SolidityTypeAddress = require('./address'); + var SolidityTypeBool = require('./bool'); + var SolidityTypeInt = require('./int'); + var SolidityTypeUInt = require('./uint'); + var SolidityTypeDynamicBytes = require('./dynamicbytes'); + var SolidityTypeString = require('./string'); + var SolidityTypeReal = require('./real'); + var SolidityTypeUReal = require('./ureal'); + var SolidityTypeBytes = require('./bytes'); + + var isDynamic = function (solidityType, type) { + return solidityType.isDynamicType(type) || solidityType.isDynamicArray(type); -}; - -/** - * SolidityCoder prototype should be used to encode/decode solidity params of any type - */ -var SolidityCoder = function (types) { - this._types = types; -}; - -/** - * This method should be used to transform type to SolidityType - * - * @method _requireType - * @param {String} type - * @returns {SolidityType} - * @throws {Error} throws if no matching type is found - */ -SolidityCoder.prototype._requireType = function (type) { - var solidityType = this._types.filter(function (t) { + }; + + /** + * SolidityCoder prototype should be used to encode/decode solidity params of any type + */ + var SolidityCoder = function (types) { + this._types = types; + }; + + /** + * This method should be used to transform type to SolidityType + * + * @method _requireType + * @param {String} type + * @returns {SolidityType} + * @throws {Error} throws if no matching type is found + */ + SolidityCoder.prototype._requireType = function (type) { + var solidityType = this._types.filter(function (t) { return t.isType(type); - })[0]; + })[0]; - if (!solidityType) { + if (!solidityType) { throw Error('invalid solidity type!: ' + type); - } + } - return solidityType; -}; - -/** - * Should be used to encode plain param - * - * @method encodeParam - * @param {String} type - * @param {Object} plain param - * @return {String} encoded plain param - */ -SolidityCoder.prototype.encodeParam = function (type, param) { - return this.encodeParams([type], [param]); -}; - -/** - * Should be used to encode list of params - * - * @method encodeParams - * @param {Array} types - * @param {Array} params - * @return {String} encoded list of params - */ -SolidityCoder.prototype.encodeParams = function (types, params) { - var solidityTypes = this.getSolidityTypes(types); - - var encodeds = solidityTypes.map(function (solidityType, index) { + return solidityType; + }; + + /** + * Should be used to encode plain param + * + * @method encodeParam + * @param {String} type + * @param {Object} plain param + * @return {String} encoded plain param + */ + SolidityCoder.prototype.encodeParam = function (type, param) { + return this.encodeParams([type], [param]); + }; + + /** + * Should be used to encode list of params + * + * @method encodeParams + * @param {Array} types + * @param {Array} params + * @return {String} encoded list of params + */ + SolidityCoder.prototype.encodeParams = function (types, params) { + var solidityTypes = this.getSolidityTypes(types); + + var encodeds = solidityTypes.map(function (solidityType, index) { return solidityType.encode(params[index], types[index]); - }); + }); - var dynamicOffset = solidityTypes.reduce(function (acc, solidityType, index) { + var dynamicOffset = solidityTypes.reduce(function (acc, solidityType, index) { var staticPartLength = solidityType.staticPartLength(types[index]); var roundedStaticPartLength = Math.floor((staticPartLength + 31) / 32) * 32; return acc + (isDynamic(solidityTypes[index], types[index]) ? 32 : roundedStaticPartLength); - }, 0); + }, 0); - var result = this.encodeMultiWithOffset(types, solidityTypes, encodeds, dynamicOffset); + var result = this.encodeMultiWithOffset(types, solidityTypes, encodeds, dynamicOffset); - return result; -}; + return result; + }; -SolidityCoder.prototype.encodeMultiWithOffset = function (types, solidityTypes, encodeds, dynamicOffset) { - var result = ""; - var self = this; + SolidityCoder.prototype.encodeMultiWithOffset = function (types, solidityTypes, encodeds, dynamicOffset) { + var result = ""; + var self = this; - types.forEach(function (type, i) { + types.forEach(function (type, i) { if (isDynamic(solidityTypes[i], types[i])) { - result += f.formatInputInt(dynamicOffset).encode(); - var e = self.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset); - dynamicOffset += e.length / 2; + result += f.formatInputInt(dynamicOffset).encode(); + var e = self.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset); + dynamicOffset += e.length / 2; } else { - // don't add length to dynamicOffset. it's already counted - result += self.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset); + // don't add length to dynamicOffset. it's already counted + result += self.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset); } // TODO: figure out nested arrays - }); + }); - types.forEach(function (type, i) { + types.forEach(function (type, i) { if (isDynamic(solidityTypes[i], types[i])) { - var e = self.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset); - dynamicOffset += e.length / 2; - result += e; + var e = self.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset); + dynamicOffset += e.length / 2; + result += e; } - }); - return result; -}; + }); + return result; + }; -SolidityCoder.prototype.encodeWithOffset = function (type, solidityType, encoded, offset) { - /* jshint maxcomplexity: 17 */ - /* jshint maxdepth: 5 */ + SolidityCoder.prototype.encodeWithOffset = function (type, solidityType, encoded, offset) { + /* jshint maxcomplexity: 17 */ + /* jshint maxdepth: 5 */ - var self = this; - var encodingMode={dynamic:1,static:2,other:3}; + var self = this; + var encodingMode={dynamic:1,static:2,other:3}; - var mode=(solidityType.isDynamicArray(type)?encodingMode.dynamic:(solidityType.isStaticArray(type)?encodingMode.static:encodingMode.other)); + var mode=(solidityType.isDynamicArray(type)?encodingMode.dynamic:(solidityType.isStaticArray(type)?encodingMode.static:encodingMode.other)); - if(mode !== encodingMode.other){ + if(mode !== encodingMode.other){ var nestedName = solidityType.nestedName(type); var nestedStaticPartLength = solidityType.staticPartLength(nestedName); var result = (mode === encodingMode.dynamic ? encoded[0] : ''); if (solidityType.isDynamicArray(nestedName)) { - var previousLength = (mode === encodingMode.dynamic ? 2 : 0); + var previousLength = (mode === encodingMode.dynamic ? 2 : 0); - for (var i = 0; i < encoded.length; i++) { - // calculate length of previous item - if(mode === encodingMode.dynamic){ - previousLength += +(encoded[i - 1])[0] || 0; - } - else if(mode === encodingMode.static){ - previousLength += +(encoded[i - 1] || [])[0] || 0; - } - result += f.formatInputInt(offset + i * nestedStaticPartLength + previousLength * 32).encode(); + for (var i = 0; i < encoded.length; i++) { + // calculate length of previous item + if(mode === encodingMode.dynamic){ + previousLength += +(encoded[i - 1])[0] || 0; + } + else if(mode === encodingMode.static){ + previousLength += +(encoded[i - 1] || [])[0] || 0; } + result += f.formatInputInt(offset + i * nestedStaticPartLength + previousLength * 32).encode(); + } } var len= (mode === encodingMode.dynamic ? encoded.length-1 : encoded.length); for (var c = 0; c < len; c++) { - var additionalOffset = result / 2; - if(mode === encodingMode.dynamic){ - result += self.encodeWithOffset(nestedName, solidityType, encoded[c + 1], offset + additionalOffset); - } - else if(mode === encodingMode.static){ - result += self.encodeWithOffset(nestedName, solidityType, encoded[c], offset + additionalOffset); - } + var additionalOffset = result / 2; + if(mode === encodingMode.dynamic){ + result += self.encodeWithOffset(nestedName, solidityType, encoded[c + 1], offset + additionalOffset); + } + else if(mode === encodingMode.static){ + result += self.encodeWithOffset(nestedName, solidityType, encoded[c], offset + additionalOffset); + } } return result; - } + } + + return encoded; + }; - return encoded; -}; - - -/** - * Should be used to decode bytes to plain param - * - * @method decodeParam - * @param {String} type - * @param {String} bytes - * @return {Object} plain param - */ -SolidityCoder.prototype.decodeParam = function (type, bytes) { - return this.decodeParams([type], bytes)[0]; -}; - -/** - * Should be used to decode list of params - * - * @method decodeParam - * @param {Array} types - * @param {String} bytes - * @return {Array} array of plain params - */ -SolidityCoder.prototype.decodeParams = function (types, bytes) { - var solidityTypes = this.getSolidityTypes(types); - var offsets = this.getOffsets(types, solidityTypes); - - return solidityTypes.map(function (solidityType, index) { + + /** + * Should be used to decode bytes to plain param + * + * @method decodeParam + * @param {String} type + * @param {String} bytes + * @return {Object} plain param + */ + SolidityCoder.prototype.decodeParam = function (type, bytes) { + return this.decodeParams([type], bytes)[0]; + }; + + /** + * Should be used to decode list of params + * + * @method decodeParam + * @param {Array} types + * @param {String} bytes + * @return {Array} array of plain params + */ + SolidityCoder.prototype.decodeParams = function (types, bytes) { + var solidityTypes = this.getSolidityTypes(types); + var offsets = this.getOffsets(types, solidityTypes); + + return solidityTypes.map(function (solidityType, index) { return solidityType.decode(bytes, offsets[index], types[index], index); - }); -}; + }); + }; -SolidityCoder.prototype.getOffsets = function (types, solidityTypes) { - var lengths = solidityTypes.map(function (solidityType, index) { + SolidityCoder.prototype.getOffsets = function (types, solidityTypes) { + var lengths = solidityTypes.map(function (solidityType, index) { return solidityType.staticPartLength(types[index]); - }); + }); - for (var i = 1; i < lengths.length; i++) { - // sum with length of previous element + for (var i = 1; i < lengths.length; i++) { + // sum with length of previous element lengths[i] += lengths[i - 1]; - } + } - return lengths.map(function (length, index) { + return lengths.map(function (length, index) { // remove the current length, so the length is sum of previous elements var staticPartLength = solidityTypes[index].staticPartLength(types[index]); return length - staticPartLength; - }); -}; + }); + }; -SolidityCoder.prototype.getSolidityTypes = function (types) { - var self = this; - return types.map(function (type) { + SolidityCoder.prototype.getSolidityTypes = function (types) { + var self = this; + return types.map(function (type) { return self._requireType(type); - }); -}; - -var coder = new SolidityCoder([ - new SolidityTypeAddress(), - new SolidityTypeBool(), - new SolidityTypeInt(), - new SolidityTypeUInt(), - new SolidityTypeDynamicBytes(), - new SolidityTypeBytes(), - new SolidityTypeString(), - new SolidityTypeReal(), - new SolidityTypeUReal() -]); - -module.exports = coder; - -},{"./address":4,"./bool":5,"./bytes":6,"./dynamicbytes":8,"./formatters":9,"./int":10,"./real":12,"./string":13,"./uint":15,"./ureal":16}],8:[function(require,module,exports){ -var f = require('./formatters'); -var SolidityType = require('./type'); - -var SolidityTypeDynamicBytes = function () { - this._inputFormatter = f.formatInputDynamicBytes; - this._outputFormatter = f.formatOutputDynamicBytes; -}; - -SolidityTypeDynamicBytes.prototype = new SolidityType({}); -SolidityTypeDynamicBytes.prototype.constructor = SolidityTypeDynamicBytes; - -SolidityTypeDynamicBytes.prototype.isType = function (name) { - return !!name.match(/^bytes(\[([0-9]*)\])*$/); -}; - -SolidityTypeDynamicBytes.prototype.isDynamicType = function () { - return true; -}; - -module.exports = SolidityTypeDynamicBytes; - -},{"./formatters":9,"./type":14}],9:[function(require,module,exports){ -/* - This file is part of web3.js. + }); + }; - web3.js 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. + var coder = new SolidityCoder([ + new SolidityTypeAddress(), + new SolidityTypeBool(), + new SolidityTypeInt(), + new SolidityTypeUInt(), + new SolidityTypeDynamicBytes(), + new SolidityTypeBytes(), + new SolidityTypeString(), + new SolidityTypeReal(), + new SolidityTypeUReal() + ]); + + module.exports = coder; + + },{"./address":4,"./bool":5,"./bytes":6,"./dynamicbytes":8,"./formatters":9,"./int":10,"./real":12,"./string":13,"./uint":15,"./ureal":16}],8:[function(require,module,exports){ + var f = require('./formatters'); + var SolidityType = require('./type'); + + var SolidityTypeDynamicBytes = function () { + this._inputFormatter = f.formatInputDynamicBytes; + this._outputFormatter = f.formatOutputDynamicBytes; + }; - web3.js 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. + SolidityTypeDynamicBytes.prototype = new SolidityType({}); + SolidityTypeDynamicBytes.prototype.constructor = SolidityTypeDynamicBytes; - You should have received a copy of the GNU Lesser General Public License - along with web3.js. If not, see . -*/ -/** - * @file formatters.js - * @author Marek Kotewicz - * @date 2015 - */ - -var BigNumber = require('bignumber.js'); -var utils = require('../utils/utils'); -var c = require('../utils/config'); -var SolidityParam = require('./param'); -var segwit_addr = require('../utils/segwit_addr.js'); - - -/** - * Formats input value to byte representation of int - * If value is negative, return it's two's complement - * If the value is floating point, round it down - * - * @method formatInputInt - * @param {String|Number|BigNumber} value that needs to be formatted - * @returns {SolidityParam} - */ -var formatInputInt = function (value) { - BigNumber.config(c.ETH_BIGNUMBER_ROUNDING_MODE); - var result = utils.padLeft(utils.toTwosComplement(value).toString(16), 64); - return new SolidityParam(result); -}; - -/** - * Formats input bytes - * - * @method formatInputBytes - * @param {String} - * @returns {SolidityParam} - */ -var formatInputBytes = function (value) { - var result = utils.toHex(value).substr(2); - var l = Math.floor((result.length + 63) / 64); - result = utils.padRight(result, l * 64); - return new SolidityParam(result); -}; - -/** - * Formats input bytes - * - * @method formatDynamicInputBytes - * @param {String} - * @returns {SolidityParam} - */ -var formatInputDynamicBytes = function (value) { - var result = utils.toHex(value).substr(2); - var length = result.length / 2; - var l = Math.floor((result.length + 63) / 64); - result = utils.padRight(result, l * 64); - return new SolidityParam(formatInputInt(length).value + result); -}; - -/** - * Formats input value to byte representation of string - * - * @method formatInputString - * @param {String} - * @returns {SolidityParam} - */ -var formatInputString = function (value) { - var result = utils.fromUtf8(value).substr(2); - var length = result.length / 2; - var l = Math.floor((result.length + 63) / 64); - result = utils.padRight(result, l * 64); - return new SolidityParam(formatInputInt(length).value + result); -}; - -/** - * Formats input value to byte representation of bool - * - * @method formatInputBool - * @param {Boolean} - * @returns {SolidityParam} - */ -var formatInputBool = function (value) { - var result = '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0'); - return new SolidityParam(result); -}; - -/** - * Formats input value to byte representation of real - * Values are multiplied by 2^m and encoded as integers - * - * @method formatInputReal - * @param {String|Number|BigNumber} - * @returns {SolidityParam} - */ -var formatInputReal = function (value) { - return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))); -}; - -/** - * Check if input value is negative - * - * @method signedIsNegative - * @param {String} value is hex format - * @returns {Boolean} true if it is negative, otherwise false - */ -var signedIsNegative = function (value) { - return (new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1)) === '1'; -}; - -/** - * Formats right-aligned output bytes to int - * - * @method formatOutputInt - * @param {SolidityParam} param - * @returns {BigNumber} right-aligned output bytes formatted to big number - */ -var formatOutputInt = function (param) { - var value = param.staticPart() || "0"; - - // check if it's negative number - // it it is, return two's complement - if (signedIsNegative(value)) { - return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1); - } - return new BigNumber(value, 16); -}; - -/** - * Formats right-aligned output bytes to uint - * - * @method formatOutputUInt - * @param {SolidityParam} - * @returns {BigNumeber} right-aligned output bytes formatted to uint - */ -var formatOutputUInt = function (param) { - var value = param.staticPart() || "0"; - return new BigNumber(value, 16); -}; - -/** - * Formats right-aligned output bytes to real - * - * @method formatOutputReal - * @param {SolidityParam} - * @returns {BigNumber} input bytes formatted to real - */ -var formatOutputReal = function (param) { - return formatOutputInt(param).dividedBy(new BigNumber(2).pow(128)); -}; - -/** - * Formats right-aligned output bytes to ureal - * - * @method formatOutputUReal - * @param {SolidityParam} - * @returns {BigNumber} input bytes formatted to ureal - */ -var formatOutputUReal = function (param) { - return formatOutputUInt(param).dividedBy(new BigNumber(2).pow(128)); -}; - -/** - * Should be used to format output bool - * - * @method formatOutputBool - * @param {SolidityParam} - * @returns {Boolean} right-aligned input bytes formatted to bool - */ -var formatOutputBool = function (param) { - return param.staticPart() === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false; -}; - -/** - * Should be used to format output bytes - * - * @method formatOutputBytes - * @param {SolidityParam} left-aligned hex representation of string - * @param {String} name type name - * @returns {String} hex string - */ -var formatOutputBytes = function (param, name) { - var matches = name.match(/^bytes([0-9]*)/); - var size = parseInt(matches[1]); - return '0x' + param.staticPart().slice(0, 2 * size); -}; - -/** - * Should be used to format output bytes - * - * @method formatOutputDynamicBytes - * @param {SolidityParam} left-aligned hex representation of string - * @returns {String} hex string - */ -var formatOutputDynamicBytes = function (param) { - var length = (new BigNumber(param.dynamicPart().slice(0, 64), 16)).toNumber() * 2; - return '0x' + param.dynamicPart().substr(64, length); -}; - -/** - * Should be used to format output string - * - * @method formatOutputString - * @param {SolidityParam} left-aligned hex representation of string - * @returns {String} ascii string - */ -var formatOutputString = function (param) { - var length = (new BigNumber(param.dynamicPart().slice(0, 64), 16)).toNumber() * 2; - return utils.toUtf8(param.dynamicPart().substr(64, length)); -}; - -/** - * Should be used to format output address - * - * @method formatOutputAddress - * @param {SolidityParam} right-aligned input bytes - * @returns {String} address - */ -var formatOutputAddress = function (param) { - var value = param.staticPart(); - return "0x" + value.slice(value.length - 40, value.length); - //address = "0x" + value.slice(value.length - 40, value.length); - //return utils.toBech32Address("lat", address) -}; - -module.exports = { - formatInputInt: formatInputInt, - formatInputBytes: formatInputBytes, - formatInputDynamicBytes: formatInputDynamicBytes, - formatInputString: formatInputString, - formatInputBool: formatInputBool, - formatInputReal: formatInputReal, - formatOutputInt: formatOutputInt, - formatOutputUInt: formatOutputUInt, - formatOutputReal: formatOutputReal, - formatOutputUReal: formatOutputUReal, - formatOutputBool: formatOutputBool, - formatOutputBytes: formatOutputBytes, - formatOutputDynamicBytes: formatOutputDynamicBytes, - formatOutputString: formatOutputString, - formatOutputAddress: formatOutputAddress -}; - -},{"../utils/config":19,"../utils/segwit_addr.js":20,"../utils/utils":22,"./param":11,"bignumber.js":"bignumber.js"}],10:[function(require,module,exports){ -var f = require('./formatters'); -var SolidityType = require('./type'); - -/** - * SolidityTypeInt is a prootype that represents int type - * It matches: - * int - * int[] - * int[4] - * int[][] - * int[3][] - * int[][6][], ... - * int32 - * int64[] - * int8[4] - * int256[][] - * int[3][] - * int64[][6][], ... - */ -var SolidityTypeInt = function () { - this._inputFormatter = f.formatInputInt; - this._outputFormatter = f.formatOutputInt; -}; - -SolidityTypeInt.prototype = new SolidityType({}); -SolidityTypeInt.prototype.constructor = SolidityTypeInt; - -SolidityTypeInt.prototype.isType = function (name) { - return !!name.match(/^int([0-9]*)?(\[([0-9]*)\])*$/); -}; - -module.exports = SolidityTypeInt; - -},{"./formatters":9,"./type":14}],11:[function(require,module,exports){ -/* + SolidityTypeDynamicBytes.prototype.isType = function (name) { + return !!name.match(/^bytes(\[([0-9]*)\])*$/); + }; + + SolidityTypeDynamicBytes.prototype.isDynamicType = function () { + return true; + }; + + module.exports = SolidityTypeDynamicBytes; + + },{"./formatters":9,"./type":14}],9:[function(require,module,exports){ + /* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify @@ -1179,655 +888,282 @@ module.exports = SolidityTypeInt; You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -/** - * @file param.js - * @author Marek Kotewicz - * @date 2015 - */ - -var utils = require('../utils/utils'); - -/** - * SolidityParam object prototype. - * Should be used when encoding, decoding solidity bytes - */ -var SolidityParam = function (value, offset) { - this.value = value || ''; - this.offset = offset; // offset in bytes -}; - -/** - * This method should be used to get length of params's dynamic part - * - * @method dynamicPartLength - * @returns {Number} length of dynamic part (in bytes) - */ -SolidityParam.prototype.dynamicPartLength = function () { - return this.dynamicPart().length / 2; -}; - -/** - * This method should be used to create copy of solidity param with different offset - * - * @method withOffset - * @param {Number} offset length in bytes - * @returns {SolidityParam} new solidity param with applied offset - */ -SolidityParam.prototype.withOffset = function (offset) { - return new SolidityParam(this.value, offset); -}; - -/** - * This method should be used to combine solidity params together - * eg. when appending an array - * - * @method combine - * @param {SolidityParam} param with which we should combine - * @param {SolidityParam} result of combination - */ -SolidityParam.prototype.combine = function (param) { - return new SolidityParam(this.value + param.value); -}; - -/** - * This method should be called to check if param has dynamic size. - * If it has, it returns true, otherwise false - * - * @method isDynamic - * @returns {Boolean} - */ -SolidityParam.prototype.isDynamic = function () { - return this.offset !== undefined; -}; - -/** - * This method should be called to transform offset to bytes - * - * @method offsetAsBytes - * @returns {String} bytes representation of offset - */ -SolidityParam.prototype.offsetAsBytes = function () { - return !this.isDynamic() ? '' : utils.padLeft(utils.toTwosComplement(this.offset).toString(16), 64); -}; - -/** - * This method should be called to get static part of param - * - * @method staticPart - * @returns {String} offset if it is a dynamic param, otherwise value - */ -SolidityParam.prototype.staticPart = function () { - if (!this.isDynamic()) { - return this.value; - } - return this.offsetAsBytes(); -}; - -/** - * This method should be called to get dynamic part of param - * - * @method dynamicPart - * @returns {String} returns a value if it is a dynamic param, otherwise empty string - */ -SolidityParam.prototype.dynamicPart = function () { - return this.isDynamic() ? this.value : ''; -}; - -/** - * This method should be called to encode param - * - * @method encode - * @returns {String} - */ -SolidityParam.prototype.encode = function () { - return this.staticPart() + this.dynamicPart(); -}; - -/** - * This method should be called to encode array of params - * - * @method encodeList - * @param {Array[SolidityParam]} params - * @returns {String} - */ -SolidityParam.encodeList = function (params) { - - // updating offsets - var totalOffset = params.length * 32; - var offsetParams = params.map(function (param) { - if (!param.isDynamic()) { - return param; - } - var offset = totalOffset; - totalOffset += param.dynamicPartLength(); - return param.withOffset(offset); - }); - - // encode everything! - return offsetParams.reduce(function (result, param) { - return result + param.dynamicPart(); - }, offsetParams.reduce(function (result, param) { - return result + param.staticPart(); - }, '')); -}; - - - -module.exports = SolidityParam; - - -},{"../utils/utils":22}],12:[function(require,module,exports){ -var f = require('./formatters'); -var SolidityType = require('./type'); - -/** - * SolidityTypeReal is a prootype that represents real type - * It matches: - * real - * real[] - * real[4] - * real[][] - * real[3][] - * real[][6][], ... - * real32 - * real64[] - * real8[4] - * real256[][] - * real[3][] - * real64[][6][], ... - */ -var SolidityTypeReal = function () { - this._inputFormatter = f.formatInputReal; - this._outputFormatter = f.formatOutputReal; -}; - -SolidityTypeReal.prototype = new SolidityType({}); -SolidityTypeReal.prototype.constructor = SolidityTypeReal; - -SolidityTypeReal.prototype.isType = function (name) { - return !!name.match(/real([0-9]*)?(\[([0-9]*)\])?/); -}; - -module.exports = SolidityTypeReal; - -},{"./formatters":9,"./type":14}],13:[function(require,module,exports){ -var f = require('./formatters'); -var SolidityType = require('./type'); - -var SolidityTypeString = function () { - this._inputFormatter = f.formatInputString; - this._outputFormatter = f.formatOutputString; -}; - -SolidityTypeString.prototype = new SolidityType({}); -SolidityTypeString.prototype.constructor = SolidityTypeString; - -SolidityTypeString.prototype.isType = function (name) { - return !!name.match(/^string(\[([0-9]*)\])*$/); -}; - -SolidityTypeString.prototype.isDynamicType = function () { - return true; -}; - -module.exports = SolidityTypeString; - -},{"./formatters":9,"./type":14}],14:[function(require,module,exports){ -var f = require('./formatters'); -var SolidityParam = require('./param'); - -/** - * SolidityType prototype is used to encode/decode solidity params of certain type - */ -var SolidityType = function (config) { - this._inputFormatter = config.inputFormatter; - this._outputFormatter = config.outputFormatter; -}; - -/** - * Should be used to determine if this SolidityType do match given name - * - * @method isType - * @param {String} name - * @return {Bool} true if type match this SolidityType, otherwise false - */ -SolidityType.prototype.isType = function (name) { - throw "this method should be overrwritten for type " + name; -}; - -/** - * Should be used to determine what is the length of static part in given type - * - * @method staticPartLength - * @param {String} name - * @return {Number} length of static part in bytes - */ -SolidityType.prototype.staticPartLength = function (name) { - // If name isn't an array then treat it like a single element array. - return (this.nestedTypes(name) || ['[1]']) - .map(function (type) { - // the length of the nested array - return parseInt(type.slice(1, -1), 10) || 1; - }) - .reduce(function (previous, current) { - return previous * current; - // all basic types are 32 bytes long - }, 32); -}; - -/** - * Should be used to determine if type is dynamic array - * eg: - * "type[]" => true - * "type[4]" => false - * - * @method isDynamicArray - * @param {String} name - * @return {Bool} true if the type is dynamic array - */ -SolidityType.prototype.isDynamicArray = function (name) { - var nestedTypes = this.nestedTypes(name); - return !!nestedTypes && !nestedTypes[nestedTypes.length - 1].match(/[0-9]{1,}/g); -}; - -/** - * Should be used to determine if type is static array - * eg: - * "type[]" => false - * "type[4]" => true - * - * @method isStaticArray - * @param {String} name - * @return {Bool} true if the type is static array - */ -SolidityType.prototype.isStaticArray = function (name) { - var nestedTypes = this.nestedTypes(name); - return !!nestedTypes && !!nestedTypes[nestedTypes.length - 1].match(/[0-9]{1,}/g); -}; - -/** - * Should return length of static array - * eg. - * "int[32]" => 32 - * "int256[14]" => 14 - * "int[2][3]" => 3 - * "int" => 1 - * "int[1]" => 1 - * "int[]" => 1 - * - * @method staticArrayLength - * @param {String} name - * @return {Number} static array length - */ -SolidityType.prototype.staticArrayLength = function (name) { - var nestedTypes = this.nestedTypes(name); - if (nestedTypes) { - return parseInt(nestedTypes[nestedTypes.length - 1].match(/[0-9]{1,}/g) || 1); - } - return 1; -}; - -/** - * Should return nested type - * eg. - * "int[32]" => "int" - * "int256[14]" => "int256" - * "int[2][3]" => "int[2]" - * "int" => "int" - * "int[]" => "int" - * - * @method nestedName - * @param {String} name - * @return {String} nested name - */ -SolidityType.prototype.nestedName = function (name) { - // remove last [] in name - var nestedTypes = this.nestedTypes(name); - if (!nestedTypes) { - return name; - } + /** + * @file formatters.js + * @author Marek Kotewicz + * @date 2015 + */ - return name.substr(0, name.length - nestedTypes[nestedTypes.length - 1].length); -}; - -/** - * Should return true if type has dynamic size by default - * such types are "string", "bytes" - * - * @method isDynamicType - * @param {String} name - * @return {Bool} true if is dynamic, otherwise false - */ -SolidityType.prototype.isDynamicType = function () { - return false; -}; - -/** - * Should return array of nested types - * eg. - * "int[2][3][]" => ["[2]", "[3]", "[]"] - * "int[] => ["[]"] - * "int" => null - * - * @method nestedTypes - * @param {String} name - * @return {Array} array of nested types - */ -SolidityType.prototype.nestedTypes = function (name) { - // return list of strings eg. "[]", "[3]", "[]", "[2]" - return name.match(/(\[[0-9]*\])/g); -}; - -/** - * Should be used to encode the value - * - * @method encode - * @param {Object} value - * @param {String} name - * @return {String} encoded value - */ -SolidityType.prototype.encode = function (value, name) { - var self = this; - if (this.isDynamicArray(name)) { + var BigNumber = require('bignumber.js'); + var utils = require('../utils/utils'); + var c = require('../utils/config'); + var SolidityParam = require('./param'); + var segwit_addr = require('../utils/segwit_addr.js'); - return (function () { - var length = value.length; // in int - var nestedName = self.nestedName(name); - var result = []; - result.push(f.formatInputInt(length).encode()); + /** + * Formats input value to byte representation of int + * If value is negative, return it's two's complement + * If the value is floating point, round it down + * + * @method formatInputInt + * @param {String|Number|BigNumber} value that needs to be formatted + * @returns {SolidityParam} + */ + var formatInputInt = function (value) { + BigNumber.config(c.ETH_BIGNUMBER_ROUNDING_MODE); + var result = utils.padLeft(utils.toTwosComplement(value).toString(16), 64); + return new SolidityParam(result); + }; - value.forEach(function (v) { - result.push(self.encode(v, nestedName)); - }); + /** + * Formats input bytes + * + * @method formatInputBytes + * @param {String} + * @returns {SolidityParam} + */ + var formatInputBytes = function (value) { + var result = utils.toHex(value).substr(2); + var l = Math.floor((result.length + 63) / 64); + result = utils.padRight(result, l * 64); + return new SolidityParam(result); + }; - return result; - })(); + /** + * Formats input bytes + * + * @method formatDynamicInputBytes + * @param {String} + * @returns {SolidityParam} + */ + var formatInputDynamicBytes = function (value) { + var result = utils.toHex(value).substr(2); + var length = result.length / 2; + var l = Math.floor((result.length + 63) / 64); + result = utils.padRight(result, l * 64); + return new SolidityParam(formatInputInt(length).value + result); + }; - } else if (this.isStaticArray(name)) { + /** + * Formats input value to byte representation of string + * + * @method formatInputString + * @param {String} + * @returns {SolidityParam} + */ + var formatInputString = function (value) { + var result = utils.fromUtf8(value).substr(2); + var length = result.length / 2; + var l = Math.floor((result.length + 63) / 64); + result = utils.padRight(result, l * 64); + return new SolidityParam(formatInputInt(length).value + result); + }; - return (function () { - var length = self.staticArrayLength(name); // in int - var nestedName = self.nestedName(name); + /** + * Formats input value to byte representation of bool + * + * @method formatInputBool + * @param {Boolean} + * @returns {SolidityParam} + */ + var formatInputBool = function (value) { + var result = '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0'); + return new SolidityParam(result); + }; - var result = []; - for (var i = 0; i < length; i++) { - result.push(self.encode(value[i], nestedName)); - } + /** + * Formats input value to byte representation of real + * Values are multiplied by 2^m and encoded as integers + * + * @method formatInputReal + * @param {String|Number|BigNumber} + * @returns {SolidityParam} + */ + var formatInputReal = function (value) { + return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))); + }; - return result; - })(); + /** + * Check if input value is negative + * + * @method signedIsNegative + * @param {String} value is hex format + * @returns {Boolean} true if it is negative, otherwise false + */ + var signedIsNegative = function (value) { + return (new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1)) === '1'; + }; - } + /** + * Formats right-aligned output bytes to int + * + * @method formatOutputInt + * @param {SolidityParam} param + * @returns {BigNumber} right-aligned output bytes formatted to big number + */ + var formatOutputInt = function (param) { + var value = param.staticPart() || "0"; - return this._inputFormatter(value, name).encode(); -}; + // check if it's negative number + // it it is, return two's complement + if (signedIsNegative(value)) { + return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1); + } + return new BigNumber(value, 16); + }; -/** - * Should be used to decode value from bytes - * - * @method decode - * @param {String} bytes - * @param {Number} offset in bytes - * @param {String} name type name - * @returns {Object} decoded value - */ -SolidityType.prototype.decode = function (bytes, offset, name) { - var self = this; + /** + * Formats right-aligned output bytes to uint + * + * @method formatOutputUInt + * @param {SolidityParam} + * @returns {BigNumeber} right-aligned output bytes formatted to uint + */ + var formatOutputUInt = function (param) { + var value = param.staticPart() || "0"; + return new BigNumber(value, 16); + }; - if (this.isDynamicArray(name)) { + /** + * Formats right-aligned output bytes to real + * + * @method formatOutputReal + * @param {SolidityParam} + * @returns {BigNumber} input bytes formatted to real + */ + var formatOutputReal = function (param) { + return formatOutputInt(param).dividedBy(new BigNumber(2).pow(128)); + }; - return (function () { - var arrayOffset = parseInt('0x' + bytes.substr(offset * 2, 64)); // in bytes - var length = parseInt('0x' + bytes.substr(arrayOffset * 2, 64)); // in int - var arrayStart = arrayOffset + 32; // array starts after length; // in bytes + /** + * Formats right-aligned output bytes to ureal + * + * @method formatOutputUReal + * @param {SolidityParam} + * @returns {BigNumber} input bytes formatted to ureal + */ + var formatOutputUReal = function (param) { + return formatOutputUInt(param).dividedBy(new BigNumber(2).pow(128)); + }; - var nestedName = self.nestedName(name); - var nestedStaticPartLength = self.staticPartLength(nestedName); // in bytes - var roundedNestedStaticPartLength = Math.floor((nestedStaticPartLength + 31) / 32) * 32; - var result = []; + /** + * Should be used to format output bool + * + * @method formatOutputBool + * @param {SolidityParam} + * @returns {Boolean} right-aligned input bytes formatted to bool + */ + var formatOutputBool = function (param) { + return param.staticPart() === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false; + }; - for (var i = 0; i < length * roundedNestedStaticPartLength; i += roundedNestedStaticPartLength) { - result.push(self.decode(bytes, arrayStart + i, nestedName)); - } + /** + * Should be used to format output bytes + * + * @method formatOutputBytes + * @param {SolidityParam} left-aligned hex representation of string + * @param {String} name type name + * @returns {String} hex string + */ + var formatOutputBytes = function (param, name) { + var matches = name.match(/^bytes([0-9]*)/); + var size = parseInt(matches[1]); + return '0x' + param.staticPart().slice(0, 2 * size); + }; - return result; - })(); - - } else if (this.isStaticArray(name)) { - - return (function () { - var length = self.staticArrayLength(name); // in int - var arrayStart = offset; // in bytes - - var nestedName = self.nestedName(name); - var nestedStaticPartLength = self.staticPartLength(nestedName); // in bytes - var roundedNestedStaticPartLength = Math.floor((nestedStaticPartLength + 31) / 32) * 32; - var result = []; - - for (var i = 0; i < length * roundedNestedStaticPartLength; i += roundedNestedStaticPartLength) { - result.push(self.decode(bytes, arrayStart + i, nestedName)); - } - - return result; - })(); - } else if (this.isDynamicType(name)) { - - return (function () { - var dynamicOffset = parseInt('0x' + bytes.substr(offset * 2, 64)); // in bytes - var length = parseInt('0x' + bytes.substr(dynamicOffset * 2, 64)); // in bytes - var roundedLength = Math.floor((length + 31) / 32); // in int - var param = new SolidityParam(bytes.substr(dynamicOffset * 2, ( 1 + roundedLength) * 64), 0); - return self._outputFormatter(param, name); - })(); - } + /** + * Should be used to format output bytes + * + * @method formatOutputDynamicBytes + * @param {SolidityParam} left-aligned hex representation of string + * @returns {String} hex string + */ + var formatOutputDynamicBytes = function (param) { + var length = (new BigNumber(param.dynamicPart().slice(0, 64), 16)).toNumber() * 2; + return '0x' + param.dynamicPart().substr(64, length); + }; - var length = this.staticPartLength(name); - var param = new SolidityParam(bytes.substr(offset * 2, length * 2)); - return this._outputFormatter(param, name); -}; - -module.exports = SolidityType; - -},{"./formatters":9,"./param":11}],15:[function(require,module,exports){ -var f = require('./formatters'); -var SolidityType = require('./type'); - -/** - * SolidityTypeUInt is a prootype that represents uint type - * It matches: - * uint - * uint[] - * uint[4] - * uint[][] - * uint[3][] - * uint[][6][], ... - * uint32 - * uint64[] - * uint8[4] - * uint256[][] - * uint[3][] - * uint64[][6][], ... - */ -var SolidityTypeUInt = function () { - this._inputFormatter = f.formatInputInt; - this._outputFormatter = f.formatOutputUInt; -}; - -SolidityTypeUInt.prototype = new SolidityType({}); -SolidityTypeUInt.prototype.constructor = SolidityTypeUInt; - -SolidityTypeUInt.prototype.isType = function (name) { - return !!name.match(/^uint([0-9]*)?(\[([0-9]*)\])*$/); -}; - -module.exports = SolidityTypeUInt; - -},{"./formatters":9,"./type":14}],16:[function(require,module,exports){ -var f = require('./formatters'); -var SolidityType = require('./type'); - -/** - * SolidityTypeUReal is a prootype that represents ureal type - * It matches: - * ureal - * ureal[] - * ureal[4] - * ureal[][] - * ureal[3][] - * ureal[][6][], ... - * ureal32 - * ureal64[] - * ureal8[4] - * ureal256[][] - * ureal[3][] - * ureal64[][6][], ... - */ -var SolidityTypeUReal = function () { - this._inputFormatter = f.formatInputReal; - this._outputFormatter = f.formatOutputUReal; -}; - -SolidityTypeUReal.prototype = new SolidityType({}); -SolidityTypeUReal.prototype.constructor = SolidityTypeUReal; - -SolidityTypeUReal.prototype.isType = function (name) { - return !!name.match(/^ureal([0-9]*)?(\[([0-9]*)\])*$/); -}; - -module.exports = SolidityTypeUReal; - -},{"./formatters":9,"./type":14}],17:[function(require,module,exports){ -// Copyright (c) 2017 Pieter Wuille -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. + /** + * Should be used to format output string + * + * @method formatOutputString + * @param {SolidityParam} left-aligned hex representation of string + * @returns {String} ascii string + */ + var formatOutputString = function (param) { + var length = (new BigNumber(param.dynamicPart().slice(0, 64), 16)).toNumber() * 2; + return utils.toUtf8(param.dynamicPart().substr(64, length)); + }; -var CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; -var GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; + /** + * Should be used to format output address + * + * @method formatOutputAddress + * @param {SolidityParam} right-aligned input bytes + * @returns {String} address + */ + var formatOutputAddress = function (param) { + var value = param.staticPart(); + return "0x" + value.slice(value.length - 40, value.length); + //address = "0x" + value.slice(value.length - 40, value.length); + //return utils.toBech32Address("lat", address) + }; -module.exports = { - decode: decode, - encode: encode, -}; + module.exports = { + formatInputInt: formatInputInt, + formatInputBytes: formatInputBytes, + formatInputDynamicBytes: formatInputDynamicBytes, + formatInputString: formatInputString, + formatInputBool: formatInputBool, + formatInputReal: formatInputReal, + formatOutputInt: formatOutputInt, + formatOutputUInt: formatOutputUInt, + formatOutputReal: formatOutputReal, + formatOutputUReal: formatOutputUReal, + formatOutputBool: formatOutputBool, + formatOutputBytes: formatOutputBytes, + formatOutputDynamicBytes: formatOutputDynamicBytes, + formatOutputString: formatOutputString, + formatOutputAddress: formatOutputAddress + }; + },{"../utils/config":19,"../utils/segwit_addr.js":20,"../utils/utils":22,"./param":11,"bignumber.js":"bignumber.js"}],10:[function(require,module,exports){ + var f = require('./formatters'); + var SolidityType = require('./type'); -function polymod (values) { - var chk = 1; - for (var p = 0; p < values.length; ++p) { - var top = chk >> 25; - chk = (chk & 0x1ffffff) << 5 ^ values[p]; - for (var i = 0; i < 5; ++i) { - if ((top >> i) & 1) { - chk ^= GENERATOR[i]; - } - } - } - return chk; -} - -function hrpExpand (hrp) { - var ret = []; - var p; - for (p = 0; p < hrp.length; ++p) { - ret.push(hrp.charCodeAt(p) >> 5); - } - ret.push(0); - for (p = 0; p < hrp.length; ++p) { - ret.push(hrp.charCodeAt(p) & 31); - } - return ret; -} - -function verifyChecksum (hrp, data) { - return polymod(hrpExpand(hrp).concat(data)) === 1; -} - -function createChecksum (hrp, data) { - var values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]); - var mod = polymod(values) ^ 1; - var ret = []; - for (var p = 0; p < 6; ++p) { - ret.push((mod >> 5 * (5 - p)) & 31); - } - return ret; -} - -function encode (hrp, data) { - var combined = data.concat(createChecksum(hrp, data)); - var ret = hrp + '1'; - for (var p = 0; p < combined.length; ++p) { - ret += CHARSET.charAt(combined[p]); - } - return ret; -} - -function decode (bechString) { - var p; - var has_lower = false; - var has_upper = false; - for (p = 0; p < bechString.length; ++p) { - if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { - return null; - } - if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { - has_lower = true; - } - if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { - has_upper = true; - } - } - if (has_lower && has_upper) { - return null; - } - bechString = bechString.toLowerCase(); - var pos = bechString.lastIndexOf('1'); - if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { - return null; - } - var hrp = bechString.substring(0, pos); - var data = []; - for (p = pos + 1; p < bechString.length; ++p) { - var d = CHARSET.indexOf(bechString.charAt(p)); - if (d === -1) { - return null; - } - data.push(d); - } - if (!verifyChecksum(hrp, data)) { - return null; - } - return {hrp: hrp, data: data.slice(0, data.length - 6)}; -} + /** + * SolidityTypeInt is a prootype that represents int type + * It matches: + * int + * int[] + * int[4] + * int[][] + * int[3][] + * int[][6][], ... + * int32 + * int64[] + * int8[4] + * int256[][] + * int[3][] + * int64[][6][], ... + */ + var SolidityTypeInt = function () { + this._inputFormatter = f.formatInputInt; + this._outputFormatter = f.formatOutputInt; + }; -},{}],18:[function(require,module,exports){ -'use strict'; + SolidityTypeInt.prototype = new SolidityType({}); + SolidityTypeInt.prototype.constructor = SolidityTypeInt; -// go env doesn't have and need XMLHttpRequest -if (typeof XMLHttpRequest === 'undefined') { - exports.XMLHttpRequest = {}; -} else { - exports.XMLHttpRequest = XMLHttpRequest; // jshint ignore:line -} + SolidityTypeInt.prototype.isType = function (name) { + return !!name.match(/^int([0-9]*)?(\[([0-9]*)\])*$/); + }; + module.exports = SolidityTypeInt; -},{}],19:[function(require,module,exports){ -/* + },{"./formatters":9,"./type":14}],11:[function(require,module,exports){ + /* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify @@ -1843,1268 +1179,655 @@ if (typeof XMLHttpRequest === 'undefined') { You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -/** @file config.js - * @authors: - * Marek Kotewicz - * @date 2015 - */ - -/** - * Utils - * - * @module utils - */ - -/** - * Utility functions - * - * @class [utils] config - * @constructor - */ + /** + * @file param.js + * @author Marek Kotewicz + * @date 2015 + */ + var utils = require('../utils/utils'); -/// required to define ETH_BIGNUMBER_ROUNDING_MODE -var BigNumber = require('bignumber.js'); - -var ETH_UNITS = [ - 'von', - 'kvon', - 'Mvon', - 'Gvon', - 'szabo', - 'finney', - 'femtolat', - 'picolat', - 'nanolat', - 'microlat', - 'millilat', - 'nano', - 'micro', - 'milli', - 'lat', - 'grand', - 'Mlat', - 'Glat', - 'Tlat', - 'Plat', - 'Elat', - 'Zlat', - 'Ylat', - 'Nlat', - 'Dlat', - 'Vlat', - 'Ulat' -]; - -module.exports = { - ETH_PADDING: 32, - ETH_SIGNATURE_LENGTH: 4, - ETH_UNITS: ETH_UNITS, - ETH_BIGNUMBER_ROUNDING_MODE: { ROUNDING_MODE: BigNumber.ROUND_DOWN }, - ETH_POLLING_TIMEOUT: 1000/2, - defaultBlock: 'latest', - defaultAccount: undefined -}; - - -},{"bignumber.js":"bignumber.js"}],20:[function(require,module,exports){ -// Copyright (c) 2017 Pieter Wuille -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. + /** + * SolidityParam object prototype. + * Should be used when encoding, decoding solidity bytes + */ + var SolidityParam = function (value, offset) { + this.value = value || ''; + this.offset = offset; // offset in bytes + }; -var bech32 = require('./bech32'); - -module.exports = { - encode: encode, - decode: decode, - DecodeAddress:DecodeAddress, - EncodeAddress:EncodeAddress, -}; - -function convertbits (data, frombits, tobits, pad) { - var acc = 0; - var bits = 0; - var ret = []; - var maxv = (1 << tobits) - 1; - for (var p = 0; p < data.length; ++p) { - var value = data[p]; - if (value < 0 || (value >> frombits) !== 0) { - return null; - } - acc = (acc << frombits) | value; - bits += frombits; - while (bits >= tobits) { - bits -= tobits; - ret.push((acc >> bits) & maxv); - } - } - if (pad) { - if (bits > 0) { - ret.push((acc << (tobits - bits)) & maxv); - } - } else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) { - return null; - } - return ret; -} - -function decode (hrp, addr) { - var dec = bech32.decode(addr); - if (dec === null || dec.hrp !== hrp || dec.data.length < 1 /*|| dec.data[0] > 16*/) { - return null; - } - var res = convertbits(dec.data.slice(0), 5, 8, false); - if (res === null || res.length < 2 || res.length > 40) { - return null; - } - if (dec.data[0] === 0 && res.length !== 20 && res.length !== 32) { - return null; - } - return {hrp: dec.hrp, program: res}; -} - -function encode (hrp, program) { - var ret = bech32.encode(hrp, convertbits(program, 8, 5, true)); - if (decode(hrp, ret) === null) { - return null; - } - return ret; -} + /** + * This method should be used to get length of params's dynamic part + * + * @method dynamicPartLength + * @returns {Number} length of dynamic part (in bytes) + */ + SolidityParam.prototype.dynamicPartLength = function () { + return this.dynamicPart().length / 2; + }; -//十六进制字符串转字节数组 -function Str2Bytes(str) { - var pos = 0; - var len = str.length; - if(len % 2 != 0) - { - return null; - } - len /= 2; - var hexA = new Array(); - for(var i=0; i < len; i++) - { - var s = str.substr(pos, 2); - if(s == "0x" || s == "0X") - { - pos += 2; - continue; - } - var v = parseInt(s, 16); - hexA.push(v); - pos += 2; - } - return hexA; -} + /** + * This method should be used to create copy of solidity param with different offset + * + * @method withOffset + * @param {Number} offset length in bytes + * @returns {SolidityParam} new solidity param with applied offset + */ + SolidityParam.prototype.withOffset = function (offset) { + return new SolidityParam(this.value, offset); + }; -//字节数组转十六进制字符串 -function Bytes2Str(arr) { - var str = ""; - for(var i=0; i " + Bytes2Str(ret.program)); -} - -// -function EncodeAddress(hrp, strAddress) { - program = Str2Bytes(strAddress) - var ret = encode(hrp, program); - //console.log("encode result ==> " + ret); - return ret; -} - -},{"./bech32":17}],21:[function(require,module,exports){ -/* - This file is part of web3.js. + /** + * This method should be called to check if param has dynamic size. + * If it has, it returns true, otherwise false + * + * @method isDynamic + * @returns {Boolean} + */ + SolidityParam.prototype.isDynamic = function () { + return this.offset !== undefined; + }; - web3.js 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. + /** + * This method should be called to transform offset to bytes + * + * @method offsetAsBytes + * @returns {String} bytes representation of offset + */ + SolidityParam.prototype.offsetAsBytes = function () { + return !this.isDynamic() ? '' : utils.padLeft(utils.toTwosComplement(this.offset).toString(16), 64); + }; - web3.js 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. + /** + * This method should be called to get static part of param + * + * @method staticPart + * @returns {String} offset if it is a dynamic param, otherwise value + */ + SolidityParam.prototype.staticPart = function () { + if (!this.isDynamic()) { + return this.value; + } + return this.offsetAsBytes(); + }; - You should have received a copy of the GNU Lesser General Public License - along with web3.js. If not, see . -*/ -/** - * @file sha3.js - * @author Marek Kotewicz - * @date 2015 - */ + /** + * This method should be called to get dynamic part of param + * + * @method dynamicPart + * @returns {String} returns a value if it is a dynamic param, otherwise empty string + */ + SolidityParam.prototype.dynamicPart = function () { + return this.isDynamic() ? this.value : ''; + }; -var CryptoJS = require('crypto-js'); -var sha3 = require('crypto-js/sha3'); + /** + * This method should be called to encode param + * + * @method encode + * @returns {String} + */ + SolidityParam.prototype.encode = function () { + return this.staticPart() + this.dynamicPart(); + }; -module.exports = function (value, options) { - if (options && options.encoding === 'hex') { - if (value.length > 2 && value.substr(0, 2) === '0x') { - value = value.substr(2); + /** + * This method should be called to encode array of params + * + * @method encodeList + * @param {Array[SolidityParam]} params + * @returns {String} + */ + SolidityParam.encodeList = function (params) { + + // updating offsets + var totalOffset = params.length * 32; + var offsetParams = params.map(function (param) { + if (!param.isDynamic()) { + return param; } - value = CryptoJS.enc.Hex.parse(value); - } + var offset = totalOffset; + totalOffset += param.dynamicPartLength(); + return param.withOffset(offset); + }); - return sha3(value, { - outputLength: 256 - }).toString(); -}; + // encode everything! + return offsetParams.reduce(function (result, param) { + return result + param.dynamicPart(); + }, offsetParams.reduce(function (result, param) { + return result + param.staticPart(); + }, '')); + }; -},{"crypto-js":58,"crypto-js/sha3":79}],22:[function(require,module,exports){ -/* - This file is part of web3.js. - web3.js 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. + module.exports = SolidityParam; - web3.js 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 web3.js. If not, see . -*/ -/** - * @file utils.js - * @author Marek Kotewicz - * @date 2015 - */ - -/** - * Utils - * - * @module utils - */ - -/** - * Utility functions - * - * @class [utils] utils - * @constructor - */ - - -var BigNumber = require('bignumber.js'); -var sha3 = require('./sha3.js'); -var utf8 = require('utf8'); -var segwit_addr = require('./segwit_addr.js'); - -var unitMap = { - 'noatp': '0', - 'von': '1', - 'kvon': '1000', - 'Kvon': '1000', - 'babbage': '1000', - 'femtoatp': '1000', - 'mvon': '1000000', - 'Mvon': '1000000', - 'lovelace': '1000000', - 'picoatp': '1000000', - 'gvon': '1000000000', - 'Gvon': '1000000000', - 'shannon': '1000000000', - 'nanoatp': '1000000000', - 'nano': '1000000000', - 'szabo': '1000000000000', - 'microatp': '1000000000000', - 'micro': '1000000000000', - 'finney': '1000000000000000', - 'milliatp': '1000000000000000', - 'milli': '1000000000000000', - 'atp': '1000000000000000000', - 'katp': '1000000000000000000000', - 'grand': '1000000000000000000000', - 'matp': '1000000000000000000000000', - 'gatp': '1000000000000000000000000000', - 'tatp': '1000000000000000000000000000000' -}; - -/** - * Should be called to pad string to expected length - * - * @method padLeft - * @param {String} string to be padded - * @param {Number} characters that result string should have - * @param {String} sign, by default 0 - * @returns {String} right aligned string - */ -var padLeft = function (string, chars, sign) { - return new Array(chars - string.length + 1).join(sign ? sign : "0") + string; -}; - -/** - * Should be called to pad string to expected length - * - * @method padRight - * @param {String} string to be padded - * @param {Number} characters that result string should have - * @param {String} sign, by default 0 - * @returns {String} right aligned string - */ -var padRight = function (string, chars, sign) { - return string + (new Array(chars - string.length + 1).join(sign ? sign : "0")); -}; - -/** - * Should be called to get utf8 from it's hex representation - * - * @method toUtf8 - * @param {String} string in hex - * @returns {String} ascii string representation of hex value - */ -var toUtf8 = function(hex) { -// Find termination - var str = ""; - var i = 0, l = hex.length; - if (hex.substring(0, 2) === '0x') { - i = 2; - } - for (; i < l; i+=2) { - var code = parseInt(hex.substr(i, 2), 16); - if (code === 0) - break; - str += String.fromCharCode(code); - } + },{"../utils/utils":22}],12:[function(require,module,exports){ + var f = require('./formatters'); + var SolidityType = require('./type'); - return utf8.decode(str); -}; - -/** - * Should be called to get ascii from it's hex representation - * - * @method toAscii - * @param {String} string in hex - * @returns {String} ascii string representation of hex value - */ -var toAscii = function(hex) { -// Find termination - var str = ""; - var i = 0, l = hex.length; - if (hex.substring(0, 2) === '0x') { - i = 2; - } - for (; i < l; i+=2) { - var code = parseInt(hex.substr(i, 2), 16); - str += String.fromCharCode(code); - } + /** + * SolidityTypeReal is a prootype that represents real type + * It matches: + * real + * real[] + * real[4] + * real[][] + * real[3][] + * real[][6][], ... + * real32 + * real64[] + * real8[4] + * real256[][] + * real[3][] + * real64[][6][], ... + */ + var SolidityTypeReal = function () { + this._inputFormatter = f.formatInputReal; + this._outputFormatter = f.formatOutputReal; + }; - return str; -}; - -/** - * Should be called to get hex representation (prefixed by 0x) of utf8 string - * - * @method fromUtf8 - * @param {String} string - * @param {Boolean} allowZero to convert code point zero to 00 instead of end of string - * @returns {String} hex representation of input string - */ -var fromUtf8 = function(str, allowZero) { - str = utf8.encode(str); - var hex = ""; - for(var i = 0; i < str.length; i++) { - var code = str.charCodeAt(i); - if (code === 0) { - if (allowZero) { - hex += '00'; - } else { - break; - } - } else { - var n = code.toString(16); - hex += n.length < 2 ? '0' + n : n; - } - } + SolidityTypeReal.prototype = new SolidityType({}); + SolidityTypeReal.prototype.constructor = SolidityTypeReal; - return "0x" + hex; -}; - -/** - * Should be called to get hex representation (prefixed by 0x) of ascii string - * - * @method fromAscii - * @param {String} string - * @param {Number} optional padding - * @returns {String} hex representation of input string - */ -var fromAscii = function(str) { - var hex = ""; - for(var i = 0; i < str.length; i++) { - var code = str.charCodeAt(i); - var n = code.toString(16); - hex += n.length < 2 ? '0' + n : n; - } + SolidityTypeReal.prototype.isType = function (name) { + return !!name.match(/real([0-9]*)?(\[([0-9]*)\])?/); + }; - return "0x" + hex; -}; - -/** - * Should be used to create full function/event name from json abi - * - * @method transformToFullName - * @param {Object} json-abi - * @return {String} full fnction/event name - */ -var transformToFullName = function (json) { - if (json.name.indexOf('(') !== -1) { - return json.name; - } + module.exports = SolidityTypeReal; - var typeName = json.inputs.map(function(i){return i.type; }).join(); - return json.name + '(' + typeName + ')'; -}; - -/** - * Should be called to get display name of contract function - * - * @method extractDisplayName - * @param {String} name of function/event - * @returns {String} display name for function/event eg. multiply(uint256) -> multiply - */ -var extractDisplayName = function (name) { - var stBracket = name.indexOf('('); - var endBracket = name.indexOf(')'); - return (stBracket !== -1 && endBracket !== -1) ? name.substr(0, stBracket) : name; -}; - -/** - * Should be called to get type name of contract function - * - * @method extractTypeName - * @param {String} name of function/event - * @returns {String} type name for function/event eg. multiply(uint256) -> uint256 - */ -var extractTypeName = function (name) { - var stBracket = name.indexOf('('); - var endBracket = name.indexOf(')'); - return (stBracket !== -1 && endBracket !== -1) ? name.substr(stBracket + 1, endBracket - stBracket - 1).replace(' ', '') : ""; -}; - -/** - * Converts value to it's decimal representation in string - * - * @method toDecimal - * @param {String|Number|BigNumber} - * @return {String} - */ -var toDecimal = function (value) { - return toBigNumber(value).toNumber(); -}; - -/** - * Converts value to it's hex representation - * - * @method fromDecimal - * @param {String|Number|BigNumber} - * @return {String} - */ -var fromDecimal = function (value) { - var number = toBigNumber(value); - var result = number.toString(16); - - return number.lessThan(0) ? '-0x' + result.substr(1) : '0x' + result; -}; - -/** - * Auto converts any given value into it's hex representation. - * - * And even stringifys objects before. - * - * @method toHex - * @param {String|Number|BigNumber|Object} - * @return {String} - */ -var toHex = function (val) { - /*jshint maxcomplexity: 8 */ - - if (isBoolean(val)) - return fromDecimal(+val); + },{"./formatters":9,"./type":14}],13:[function(require,module,exports){ + var f = require('./formatters'); + var SolidityType = require('./type'); - if (isBigNumber(val)) - return fromDecimal(val); + var SolidityTypeString = function () { + this._inputFormatter = f.formatInputString; + this._outputFormatter = f.formatOutputString; + }; - if (typeof val === 'object') - return fromUtf8(JSON.stringify(val)); + SolidityTypeString.prototype = new SolidityType({}); + SolidityTypeString.prototype.constructor = SolidityTypeString; - // if its a negative number, pass it through fromDecimal - if (isString(val)) { - if (val.indexOf('-0x') === 0) - return fromDecimal(val); - else if(val.indexOf('0x') === 0) - return val; - else if (!isFinite(val)) - return fromUtf8(val,1); - } + SolidityTypeString.prototype.isType = function (name) { + return !!name.match(/^string(\[([0-9]*)\])*$/); + }; - return fromDecimal(val); -}; - -/** - * Returns value of unit in von - * - * @method getValueOfUnit - * @param {String} unit the unit to convert to, default atp - * @returns {BigNumber} value of the unit (in von) - * @throws error if the unit is not correct:w - */ -var getValueOfUnit = function (unit) { - unit = unit ? unit.toLowerCase() : 'atp'; - var unitValue = unitMap[unit]; - if (unitValue === undefined) { - throw new Error('This unit doesn\'t exists, please use the one of the following units' + JSON.stringify(unitMap, null, 2)); - } - return new BigNumber(unitValue, 10); -}; - -/** - * Takes a number of von and converts it to any other atp unit. - * - * Possible units are: - * SI Short SI Full Effigy Other - * - kvon femtoatp babbage - * - mvon picoatp lovelace - * - gvon nanoatp shannon nano - * - -- microatp szabo micro - * - -- milliatp finney milli - * - atp -- -- - * - katp -- grand - * - matp - * - gatp - * - tatp - * - * @method fromVon - * @param {Number|String} number can be a number, number string or a HEX of a decimal - * @param {String} unit the unit to convert to, default atp - * @return {String|Object} When given a BigNumber object it returns one as well, otherwise a number -*/ -var fromVon = function(number, unit) { - var returnValue = toBigNumber(number).dividedBy(getValueOfUnit(unit)); - - return isBigNumber(number) ? returnValue : returnValue.toString(10); -}; - -/** - * Takes a number of a unit and converts it to von. - * - * Possible units are: - * SI Short SI Full Effigy Other - * - kvon femtoatp babbage - * - mvon picoatp lovelace - * - gvon nanoatp shannon nano - * - -- microatp szabo micro - * - -- milliatp finney milli - * - atp -- -- - * - katp -- grand - * - matp - * - gatp - * - tatp - * - * @method toVon - * @param {Number|String|BigNumber} number can be a number, number string or a HEX of a decimal - * @param {String} unit the unit to convert from, default atp - * @return {String|Object} When given a BigNumber object it returns one as well, otherwise a number -*/ -var toVon = function(number, unit) { - var returnValue = toBigNumber(number).times(getValueOfUnit(unit)); - - return isBigNumber(number) ? returnValue : returnValue.toString(10); -}; - -/** - * Takes an input and transforms it into an bignumber - * - * @method toBigNumber - * @param {Number|String|BigNumber} a number, string, HEX string or BigNumber - * @return {BigNumber} BigNumber -*/ -var toBigNumber = function(number) { - /*jshint maxcomplexity:5 */ - number = number || 0; - if (isBigNumber(number)) - return number; + SolidityTypeString.prototype.isDynamicType = function () { + return true; + }; - if (isString(number) && (number.indexOf('0x') === 0 || number.indexOf('-0x') === 0)) { - return new BigNumber(number.replace('0x',''), 16); - } + module.exports = SolidityTypeString; - return new BigNumber(number.toString(10), 10); -}; - -/** - * Takes and input transforms it into bignumber and if it is negative value, into two's complement - * - * @method toTwosComplement - * @param {Number|String|BigNumber} - * @return {BigNumber} - */ -var toTwosComplement = function (number) { - var bigNumber = toBigNumber(number).round(); - if (bigNumber.lessThan(0)) { - return new BigNumber("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16).plus(bigNumber).plus(1); - } - return bigNumber; -}; - -/** - * Checks if the given string is strictly an address - * - * @method isStrictAddress - * @param {String} address the given HEX adress - * @return {Boolean} -*/ -var isStrictAddress = function (address) { - return /^0x[0-9a-f]{40}$/i.test(address); -}; - -/** - * Checks if the given string is an address - * - * @method isAddress - * @param {String} address the given HEX adress - * @return {Boolean} -*/ -var isAddress = function (address) { - if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) { - // check if it has the basic requirements of an address - return false; - } else if (/^(0x)?[0-9a-f]{40}$/.test(address) || /^(0x)?[0-9A-F]{40}$/.test(address)) { - // If it's all small caps or all all caps, return true - return true; - } else { - // Otherwise check each case - return isChecksumAddress(address); - } -}; - -/** - * Checks if the given string is an bech32 address - * - * @method isBech32Address - * @param {String} address the given bech32 adress - * @return {Boolean} -*/ -var isBech32Address = function (address) { - if(address.length != 42) - { - return false; - } - var hrp = address.substr(0,3); - var ret = segwit_addr.decode(hrp, address); - if (ret === null) { - return false; - } - return true; -}; - -/** - * Transforms given string to bech32 addres - * - * @method toBech32Address - * @param {String} hrp - * @param {String} address - * @return {String} formatted bech32 address - */ -var toBech32Address = function (hrp, address) { - if (isStrictAddress(address) || isChecksumAddress(address)) { - return segwit_addr.EncodeAddress(hrp, address); - } + },{"./formatters":9,"./type":14}],14:[function(require,module,exports){ + var f = require('./formatters'); + var SolidityParam = require('./param'); - return '' -}; - -/** - * Resolve the bech32 address - * - * @method decodeBech32Address - * @param {String} bech32Address - * @return {String} formatted address - */ -var decodeBech32Address = function (bech32Address) { - if (isBech32Address(bech32Address)) { - var hrp = bech32Address.substr(0,3); - address = segwit_addr.DecodeAddress(hrp, bech32Address); - if (address !== null) { - return "0x" + address - } - } + /** + * SolidityType prototype is used to encode/decode solidity params of certain type + */ + var SolidityType = function (config) { + this._inputFormatter = config.inputFormatter; + this._outputFormatter = config.outputFormatter; + }; - return '' -}; + /** + * Should be used to determine if this SolidityType do match given name + * + * @method isType + * @param {String} name + * @return {Bool} true if type match this SolidityType, otherwise false + */ + SolidityType.prototype.isType = function (name) { + throw "this method should be overrwritten for type " + name; + }; + /** + * Should be used to determine what is the length of static part in given type + * + * @method staticPartLength + * @param {String} name + * @return {Number} length of static part in bytes + */ + SolidityType.prototype.staticPartLength = function (name) { + // If name isn't an array then treat it like a single element array. + return (this.nestedTypes(name) || ['[1]']) + .map(function (type) { + // the length of the nested array + return parseInt(type.slice(1, -1), 10) || 1; + }) + .reduce(function (previous, current) { + return previous * current; + // all basic types are 32 bytes long + }, 32); + }; -/** - * Checks if the given string is a checksummed address - * - * @method isChecksumAddress - * @param {String} address the given HEX adress - * @return {Boolean} -*/ -var isChecksumAddress = function (address) { - // Check each case - address = address.replace('0x',''); - var addressHash = sha3(address.toLowerCase()); + /** + * Should be used to determine if type is dynamic array + * eg: + * "type[]" => true + * "type[4]" => false + * + * @method isDynamicArray + * @param {String} name + * @return {Bool} true if the type is dynamic array + */ + SolidityType.prototype.isDynamicArray = function (name) { + var nestedTypes = this.nestedTypes(name); + return !!nestedTypes && !nestedTypes[nestedTypes.length - 1].match(/[0-9]{1,}/g); + }; - for (var i = 0; i < 40; i++ ) { - // the nth letter should be uppercase if the nth digit of casemap is 1 - if ((parseInt(addressHash[i], 16) > 7 && address[i].toUpperCase() !== address[i]) || (parseInt(addressHash[i], 16) <= 7 && address[i].toLowerCase() !== address[i])) { - return false; - } - } - return true; -}; + /** + * Should be used to determine if type is static array + * eg: + * "type[]" => false + * "type[4]" => true + * + * @method isStaticArray + * @param {String} name + * @return {Bool} true if the type is static array + */ + SolidityType.prototype.isStaticArray = function (name) { + var nestedTypes = this.nestedTypes(name); + return !!nestedTypes && !!nestedTypes[nestedTypes.length - 1].match(/[0-9]{1,}/g); + }; + /** + * Should return length of static array + * eg. + * "int[32]" => 32 + * "int256[14]" => 14 + * "int[2][3]" => 3 + * "int" => 1 + * "int[1]" => 1 + * "int[]" => 1 + * + * @method staticArrayLength + * @param {String} name + * @return {Number} static array length + */ + SolidityType.prototype.staticArrayLength = function (name) { + var nestedTypes = this.nestedTypes(name); + if (nestedTypes) { + return parseInt(nestedTypes[nestedTypes.length - 1].match(/[0-9]{1,}/g) || 1); + } + return 1; + }; + /** + * Should return nested type + * eg. + * "int[32]" => "int" + * "int256[14]" => "int256" + * "int[2][3]" => "int[2]" + * "int" => "int" + * "int[]" => "int" + * + * @method nestedName + * @param {String} name + * @return {String} nested name + */ + SolidityType.prototype.nestedName = function (name) { + // remove last [] in name + var nestedTypes = this.nestedTypes(name); + if (!nestedTypes) { + return name; + } -/** - * Makes a checksum address - * - * @method toChecksumAddress - * @param {String} address the given HEX adress - * @return {String} -*/ -var toChecksumAddress = function (address) { - if (typeof address === 'undefined') return ''; + return name.substr(0, name.length - nestedTypes[nestedTypes.length - 1].length); + }; - address = address.toLowerCase().replace('0x',''); - var addressHash = sha3(address); - var checksumAddress = '0x'; + /** + * Should return true if type has dynamic size by default + * such types are "string", "bytes" + * + * @method isDynamicType + * @param {String} name + * @return {Bool} true if is dynamic, otherwise false + */ + SolidityType.prototype.isDynamicType = function () { + return false; + }; - for (var i = 0; i < address.length; i++ ) { - // If ith character is 9 to f then make it uppercase - if (parseInt(addressHash[i], 16) > 7) { - checksumAddress += address[i].toUpperCase(); - } else { - checksumAddress += address[i]; - } - } - return checksumAddress; -}; - -/** - * Transforms given string to valid 20 bytes-length addres with 0x prefix - * - * @method toAddress - * @param {String} address - * @return {String} formatted address - */ -var toAddress = function (address) { - if (isStrictAddress(address)) { - return address; - } + /** + * Should return array of nested types + * eg. + * "int[2][3][]" => ["[2]", "[3]", "[]"] + * "int[] => ["[]"] + * "int" => null + * + * @method nestedTypes + * @param {String} name + * @return {Array} array of nested types + */ + SolidityType.prototype.nestedTypes = function (name) { + // return list of strings eg. "[]", "[3]", "[]", "[2]" + return name.match(/(\[[0-9]*\])/g); + }; - if (/^[0-9a-f]{40}$/.test(address)) { - return '0x' + address; - } + /** + * Should be used to encode the value + * + * @method encode + * @param {Object} value + * @param {String} name + * @return {String} encoded value + */ + SolidityType.prototype.encode = function (value, name) { + var self = this; + if (this.isDynamicArray(name)) { - return '0x' + padLeft(toHex(address).substr(2), 40); -}; - -/** - * Returns true if object is BigNumber, otherwise false - * - * @method isBigNumber - * @param {Object} - * @return {Boolean} - */ -var isBigNumber = function (object) { - return object instanceof BigNumber || - (object && object.constructor && object.constructor.name === 'BigNumber'); -}; - -/** - * Returns true if object is string, otherwise false - * - * @method isString - * @param {Object} - * @return {Boolean} - */ -var isString = function (object) { - return typeof object === 'string' || - (object && object.constructor && object.constructor.name === 'String'); -}; - -/** - * Returns true if object is function, otherwise false - * - * @method isFunction - * @param {Object} - * @return {Boolean} - */ -var isFunction = function (object) { - return typeof object === 'function'; -}; - -/** - * Returns true if object is Objet, otherwise false - * - * @method isObject - * @param {Object} - * @return {Boolean} - */ -var isObject = function (object) { - return object !== null && !(Array.isArray(object)) && typeof object === 'object'; -}; - -/** - * Returns true if object is boolean, otherwise false - * - * @method isBoolean - * @param {Object} - * @return {Boolean} - */ -var isBoolean = function (object) { - return typeof object === 'boolean'; -}; - -/** - * Returns true if object is array, otherwise false - * - * @method isArray - * @param {Object} - * @return {Boolean} - */ -var isArray = function (object) { - return Array.isArray(object); -}; - -/** - * Returns true if given string is valid json object - * - * @method isJson - * @param {String} - * @return {Boolean} - */ -var isJson = function (str) { - try { - return !!JSON.parse(str); - } catch (e) { - return false; - } -}; - -/** - * Returns true if given string is a valid Ethereum block header bloom. - * - * @method isBloom - * @param {String} hex encoded bloom filter - * @return {Boolean} - */ -var isBloom = function (bloom) { - if (!/^(0x)?[0-9a-f]{512}$/i.test(bloom)) { - return false; - } else if (/^(0x)?[0-9a-f]{512}$/.test(bloom) || /^(0x)?[0-9A-F]{512}$/.test(bloom)) { - return true; - } - return false; -}; - -/** - * Returns true if given string is a valid log topic. - * - * @method isTopic - * @param {String} hex encoded topic - * @return {Boolean} - */ -var isTopic = function (topic) { - if (!/^(0x)?[0-9a-f]{64}$/i.test(topic)) { - return false; - } else if (/^(0x)?[0-9a-f]{64}$/.test(topic) || /^(0x)?[0-9A-F]{64}$/.test(topic)) { - return true; - } - return false; -}; - -module.exports = { - padLeft: padLeft, - padRight: padRight, - toHex: toHex, - toDecimal: toDecimal, - fromDecimal: fromDecimal, - toUtf8: toUtf8, - toAscii: toAscii, - fromUtf8: fromUtf8, - fromAscii: fromAscii, - transformToFullName: transformToFullName, - extractDisplayName: extractDisplayName, - extractTypeName: extractTypeName, - toVon: toVon, - fromVon: fromVon, - toBigNumber: toBigNumber, - toTwosComplement: toTwosComplement, - toAddress: toAddress, - isBigNumber: isBigNumber, - isStrictAddress: isStrictAddress, - isAddress: isAddress, - isChecksumAddress: isChecksumAddress, - toChecksumAddress: toChecksumAddress, - isBech32Address:isBech32Address, - toBech32Address:toBech32Address, - decodeBech32Address:decodeBech32Address, - isFunction: isFunction, - isString: isString, - isObject: isObject, - isBoolean: isBoolean, - isArray: isArray, - isJson: isJson, - isBloom: isBloom, - isTopic: isTopic, -}; - -},{"./segwit_addr.js":20,"./sha3.js":21,"bignumber.js":"bignumber.js","utf8":84}],23:[function(require,module,exports){ -module.exports={ - "version": "0.1.0" -} - -},{}],24:[function(require,module,exports){ -/* - This file is part of web3.js. + return (function () { + var length = value.length; // in int + var nestedName = self.nestedName(name); - web3.js 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. + var result = []; + result.push(f.formatInputInt(length).encode()); - web3.js 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. + value.forEach(function (v) { + result.push(self.encode(v, nestedName)); + }); - You should have received a copy of the GNU Lesser General Public License - along with web3.js. If not, see . -*/ -/** - * @file web3.js - * @authors: - * Jeffrey Wilcke - * Marek Kotewicz - * Marian Oancea - * Fabian Vogelsteller - * Gav Wood - * @date 2014 - */ - -var RequestManager = require('./web3/requestmanager'); -var Iban = require('./web3/iban'); -var Eth = require('./web3/methods/eth'); -var DB = require('./web3/methods/db'); -// var Shh = require('./web3/methods/shh'); -var Net = require('./web3/methods/net'); -var Personal = require('./web3/methods/personal'); -// var Swarm = require('./web3/methods/swarm'); -var version = require('./version.json'); -var utils = require('./utils/utils'); -var sha3 = require('./utils/sha3'); -var extend = require('./web3/extend'); -var Batch = require('./web3/batch'); -var Property = require('./web3/property'); -var HttpProvider = require('./web3/httpprovider'); -var IpcProvider = require('./web3/ipcprovider'); -var BigNumber = require('bignumber.js'); - -function Web3 (provider) { - this._requestManager = new RequestManager(provider); - this.currentProvider = provider; - this.platon = new Eth(this); - this.db = new DB(this); - // this.shh = new Shh(this); - this.net = new Net(this); - this.personal = new Personal(this); - // this.bzz = new Swarm(this); - this.version = { - api: version.version - }; - this.providers = { - HttpProvider: HttpProvider, - IpcProvider: IpcProvider + return result; + })(); + + } else if (this.isStaticArray(name)) { + + return (function () { + var length = self.staticArrayLength(name); // in int + var nestedName = self.nestedName(name); + + var result = []; + for (var i = 0; i < length; i++) { + result.push(self.encode(value[i], nestedName)); + } + + return result; + })(); + + } + + return this._inputFormatter(value, name).encode(); }; - this._extend = extend(this); - this._extend({ - properties: properties() - }); -} -// expose providers on the class -Web3.providers = { - HttpProvider: HttpProvider, - IpcProvider: IpcProvider -}; - -Web3.prototype.setProvider = function (provider) { - this._requestManager.setProvider(provider); - this.currentProvider = provider; -}; - -Web3.prototype.reset = function (keepIsSyncing) { - this._requestManager.reset(keepIsSyncing); - this.settings = new Settings(); -}; - -Web3.prototype.BigNumber = BigNumber; -Web3.prototype.toHex = utils.toHex; -Web3.prototype.toAscii = utils.toAscii; -Web3.prototype.toUtf8 = utils.toUtf8; -Web3.prototype.fromAscii = utils.fromAscii; -Web3.prototype.fromUtf8 = utils.fromUtf8; -Web3.prototype.toDecimal = utils.toDecimal; -Web3.prototype.fromDecimal = utils.fromDecimal; -Web3.prototype.toBigNumber = utils.toBigNumber; -Web3.prototype.toVon = utils.toVon; -Web3.prototype.fromVon = utils.fromVon; -Web3.prototype.isAddress = utils.isAddress; -Web3.prototype.isChecksumAddress = utils.isChecksumAddress; -Web3.prototype.toChecksumAddress = utils.toChecksumAddress; -Web3.prototype.isBech32Address = utils.isBech32Address; -Web3.prototype.toBech32Address = utils.toBech32Address; -Web3.prototype.decodeBech32Address = utils.decodeBech32Address; -Web3.prototype.padLeft = utils.padLeft; -Web3.prototype.padRight = utils.padRight; - - -Web3.prototype.sha3 = function(string, options) { - return '0x' + sha3(string, options); -}; - -/** - * Transforms direct icap to address - */ -Web3.prototype.fromICAP = function (icap) { - var iban = new Iban(icap); - return iban.address(); -}; - -var properties = function () { - return [ - new Property({ - name: 'version.node', - getter: 'web3_clientVersion' - }), - new Property({ - name: 'version.network', - getter: 'net_version', - inputFormatter: utils.toDecimal - }), - new Property({ - name: 'version.platon', - getter: 'platon_protocolVersion', - inputFormatter: utils.toDecimal - }) - // new Property({ - // name: 'version.whisper', - // getter: 'shh_version', - // inputFormatter: utils.toDecimal - // }) - ]; -}; + /** + * Should be used to decode value from bytes + * + * @method decode + * @param {String} bytes + * @param {Number} offset in bytes + * @param {String} name type name + * @returns {Object} decoded value + */ + SolidityType.prototype.decode = function (bytes, offset, name) { + var self = this; -Web3.prototype.isConnected = function(){ - return (this.currentProvider && this.currentProvider.isConnected()); -}; + if (this.isDynamicArray(name)) { -Web3.prototype.createBatch = function () { - return new Batch(this); -}; + return (function () { + var arrayOffset = parseInt('0x' + bytes.substr(offset * 2, 64)); // in bytes + var length = parseInt('0x' + bytes.substr(arrayOffset * 2, 64)); // in int + var arrayStart = arrayOffset + 32; // array starts after length; // in bytes -module.exports = Web3; + var nestedName = self.nestedName(name); + var nestedStaticPartLength = self.staticPartLength(nestedName); // in bytes + var roundedNestedStaticPartLength = Math.floor((nestedStaticPartLength + 31) / 32) * 32; + var result = []; + for (var i = 0; i < length * roundedNestedStaticPartLength; i += roundedNestedStaticPartLength) { + result.push(self.decode(bytes, arrayStart + i, nestedName)); + } -},{"./utils/sha3":21,"./utils/utils":22,"./version.json":23,"./web3/batch":26,"./web3/extend":30,"./web3/httpprovider":34,"./web3/iban":35,"./web3/ipcprovider":36,"./web3/methods/db":39,"./web3/methods/eth":40,"./web3/methods/net":41,"./web3/methods/personal":42,"./web3/property":45,"./web3/requestmanager":46,"bignumber.js":"bignumber.js"}],25:[function(require,module,exports){ -/* - This file is part of web3.js. + return result; + })(); - web3.js 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. + } else if (this.isStaticArray(name)) { - web3.js 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. + return (function () { + var length = self.staticArrayLength(name); // in int + var arrayStart = offset; // in bytes - You should have received a copy of the GNU Lesser General Public License - along with web3.js. If not, see . -*/ -/** - * @file allevents.js - * @author Marek Kotewicz - * @date 2014 - */ - -var sha3 = require('../utils/sha3'); -var SolidityEvent = require('./event'); -var formatters = require('./formatters'); -var utils = require('../utils/utils'); -var Filter = require('./filter'); -var watches = require('./methods/watches'); - -var AllSolidityEvents = function (requestManager, json, address) { - this._requestManager = requestManager; - this._json = json; - this._address = address; -}; - -AllSolidityEvents.prototype.encode = function (options) { - options = options || {}; - var result = {}; - - ['fromBlock', 'toBlock'].filter(function (f) { - return options[f] !== undefined; - }).forEach(function (f) { - result[f] = formatters.inputBlockNumberFormatter(options[f]); - }); + var nestedName = self.nestedName(name); + var nestedStaticPartLength = self.staticPartLength(nestedName); // in bytes + var roundedNestedStaticPartLength = Math.floor((nestedStaticPartLength + 31) / 32) * 32; + var result = []; - result.address = this._address; + for (var i = 0; i < length * roundedNestedStaticPartLength; i += roundedNestedStaticPartLength) { + result.push(self.decode(bytes, arrayStart + i, nestedName)); + } - return result; -}; + return result; + })(); + } else if (this.isDynamicType(name)) { -AllSolidityEvents.prototype.decode = function (data) { - data.data = data.data || ''; + return (function () { + var dynamicOffset = parseInt('0x' + bytes.substr(offset * 2, 64)); // in bytes + var length = parseInt('0x' + bytes.substr(dynamicOffset * 2, 64)); // in bytes + var roundedLength = Math.floor((length + 31) / 32); // in int + var param = new SolidityParam(bytes.substr(dynamicOffset * 2, ( 1 + roundedLength) * 64), 0); + return self._outputFormatter(param, name); + })(); + } + var length = this.staticPartLength(name); + var param = new SolidityParam(bytes.substr(offset * 2, length * 2)); + return this._outputFormatter(param, name); + }; - var eventTopic = (utils.isArray(data.topics) && utils.isString(data.topics[0])) ? data.topics[0].slice(2) : ''; - var match = this._json.filter(function (j) { - return eventTopic === sha3(utils.transformToFullName(j)); - })[0]; + module.exports = SolidityType; - if (!match) { // cannot find matching event? - return formatters.outputLogFormatter(data); - } + },{"./formatters":9,"./param":11}],15:[function(require,module,exports){ + var f = require('./formatters'); + var SolidityType = require('./type'); - var event = new SolidityEvent(this._requestManager, match, this._address); - return event.decode(data); -}; + /** + * SolidityTypeUInt is a prootype that represents uint type + * It matches: + * uint + * uint[] + * uint[4] + * uint[][] + * uint[3][] + * uint[][6][], ... + * uint32 + * uint64[] + * uint8[4] + * uint256[][] + * uint[3][] + * uint64[][6][], ... + */ + var SolidityTypeUInt = function () { + this._inputFormatter = f.formatInputInt; + this._outputFormatter = f.formatOutputUInt; + }; -AllSolidityEvents.prototype.execute = function (options, callback) { + SolidityTypeUInt.prototype = new SolidityType({}); + SolidityTypeUInt.prototype.constructor = SolidityTypeUInt; - if (utils.isFunction(arguments[arguments.length - 1])) { - callback = arguments[arguments.length - 1]; - if(arguments.length === 1) - options = null; - } + SolidityTypeUInt.prototype.isType = function (name) { + return !!name.match(/^uint([0-9]*)?(\[([0-9]*)\])*$/); + }; + + module.exports = SolidityTypeUInt; - var o = this.encode(options); - var formatter = this.decode.bind(this); - return new Filter(o, 'eth', this._requestManager, watches.eth(), formatter, callback); -}; + },{"./formatters":9,"./type":14}],16:[function(require,module,exports){ + var f = require('./formatters'); + var SolidityType = require('./type'); -AllSolidityEvents.prototype.attachToContract = function (contract) { - var execute = this.execute.bind(this); - contract.allEvents = execute; -}; + /** + * SolidityTypeUReal is a prootype that represents ureal type + * It matches: + * ureal + * ureal[] + * ureal[4] + * ureal[][] + * ureal[3][] + * ureal[][6][], ... + * ureal32 + * ureal64[] + * ureal8[4] + * ureal256[][] + * ureal[3][] + * ureal64[][6][], ... + */ + var SolidityTypeUReal = function () { + this._inputFormatter = f.formatInputReal; + this._outputFormatter = f.formatOutputUReal; + }; -module.exports = AllSolidityEvents; + SolidityTypeUReal.prototype = new SolidityType({}); + SolidityTypeUReal.prototype.constructor = SolidityTypeUReal; + SolidityTypeUReal.prototype.isType = function (name) { + return !!name.match(/^ureal([0-9]*)?(\[([0-9]*)\])*$/); + }; -},{"../utils/sha3":21,"../utils/utils":22,"./event":29,"./filter":31,"./formatters":32,"./methods/watches":43}],26:[function(require,module,exports){ -/* - This file is part of web3.js. + module.exports = SolidityTypeUReal; - web3.js 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. + },{"./formatters":9,"./type":14}],17:[function(require,module,exports){ +// Copyright (c) 2017 Pieter Wuille +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. - web3.js 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. + var CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; + var GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; - You should have received a copy of the GNU Lesser General Public License - along with web3.js. If not, see . -*/ -/** - * @file batch.js - * @author Marek Kotewicz - * @date 2015 - */ - -var Jsonrpc = require('./jsonrpc'); -var errors = require('./errors'); - -var Batch = function (web3) { - this.requestManager = web3._requestManager; - this.requests = []; -}; - -/** - * Should be called to add create new request to batch request - * - * @method add - * @param {Object} jsonrpc requet object - */ -Batch.prototype.add = function (request) { - this.requests.push(request); -}; - -/** - * Should be called to execute batch request - * - * @method execute - */ -Batch.prototype.execute = function () { - var requests = this.requests; - this.requestManager.sendBatch(requests, function (err, results) { - results = results || []; - requests.map(function (request, index) { - return results[index] || {}; - }).forEach(function (result, index) { - if (requests[index].callback) { + module.exports = { + decode: decode, + encode: encode, + }; - if (!Jsonrpc.isValidResponse(result)) { - return requests[index].callback(errors.InvalidResponse(result)); - } - requests[index].callback(null, (requests[index].format ? requests[index].format(result.result) : result.result)); - } - }); - }); -}; + function polymod (values) { + var chk = 1; + for (var p = 0; p < values.length; ++p) { + var top = chk >> 25; + chk = (chk & 0x1ffffff) << 5 ^ values[p]; + for (var i = 0; i < 5; ++i) { + if ((top >> i) & 1) { + chk ^= GENERATOR[i]; + } + } + } + return chk; + } + + function hrpExpand (hrp) { + var ret = []; + var p; + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) >> 5); + } + ret.push(0); + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) & 31); + } + return ret; + } + + function verifyChecksum (hrp, data) { + return polymod(hrpExpand(hrp).concat(data)) === 1; + } + + function createChecksum (hrp, data) { + var values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]); + var mod = polymod(values) ^ 1; + var ret = []; + for (var p = 0; p < 6; ++p) { + ret.push((mod >> 5 * (5 - p)) & 31); + } + return ret; + } + + function encode (hrp, data) { + var combined = data.concat(createChecksum(hrp, data)); + var ret = hrp + '1'; + for (var p = 0; p < combined.length; ++p) { + ret += CHARSET.charAt(combined[p]); + } + return ret; + } + + function decode (bechString) { + var p; + var has_lower = false; + var has_upper = false; + for (p = 0; p < bechString.length; ++p) { + if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { + return null; + } + if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { + has_lower = true; + } + if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { + has_upper = true; + } + } + if (has_lower && has_upper) { + return null; + } + bechString = bechString.toLowerCase(); + var pos = bechString.lastIndexOf('1'); + if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { + return null; + } + var hrp = bechString.substring(0, pos); + var data = []; + for (p = pos + 1; p < bechString.length; ++p) { + var d = CHARSET.indexOf(bechString.charAt(p)); + if (d === -1) { + return null; + } + data.push(d); + } + if (!verifyChecksum(hrp, data)) { + return null; + } + return {hrp: hrp, data: data.slice(0, data.length - 6)}; + } + + },{}],18:[function(require,module,exports){ + 'use strict'; -module.exports = Batch; +// go env doesn't have and need XMLHttpRequest + if (typeof XMLHttpRequest === 'undefined') { + exports.XMLHttpRequest = {}; + } else { + exports.XMLHttpRequest = XMLHttpRequest; // jshint ignore:line + } -},{"./errors":28,"./jsonrpc":37}],27:[function(require,module,exports){ -/* + },{}],19:[function(require,module,exports){ + /* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify @@ -3120,303 +1843,211 @@ module.exports = Batch; You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -/** - * @file contract.js - * @author Marek Kotewicz - * @date 2014 - */ - -var utils = require('../utils/utils'); -var coder = require('../solidity/coder'); -var SolidityEvent = require('./event'); -var SolidityFunction = require('./function'); -var AllEvents = require('./allevents'); - -/** - * Should be called to encode constructor params - * - * @method encodeConstructorParams - * @param {Array} abi - * @param {Array} constructor params - */ -var encodeConstructorParams = function (abi, params) { - return abi.filter(function (json) { - return json.type === 'constructor' && json.inputs.length === params.length; - }).map(function (json) { - return json.inputs.map(function (input) { - return input.type; - }); - }).map(function (types) { - return coder.encodeParams(types, params); - })[0] || ''; -}; - -/** - * Should be called to add functions to contract object - * - * @method addFunctionsToContract - * @param {Contract} contract - * @param {Array} abi - */ -var addFunctionsToContract = function (contract) { - contract.abi.filter(function (json) { - return json.type === 'function'; - }).map(function (json) { - return new SolidityFunction(contract._eth, json, contract.address); - }).forEach(function (f) { - f.attachToContract(contract); - }); -}; - -/** - * Should be called to add events to contract object - * - * @method addEventsToContract - * @param {Contract} contract - * @param {Array} abi - */ -var addEventsToContract = function (contract) { - var events = contract.abi.filter(function (json) { - return json.type === 'event'; - }); + /** @file config.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ - var All = new AllEvents(contract._eth._requestManager, events, contract.address); - All.attachToContract(contract); + /** + * Utils + * + * @module utils + */ - events.map(function (json) { - return new SolidityEvent(contract._eth._requestManager, json, contract.address); - }).forEach(function (e) { - e.attachToContract(contract); - }); -}; - - -/** - * Should be called to check if the contract gets properly deployed on the blockchain. - * - * @method checkForContractAddress - * @param {Object} contract - * @param {Function} callback - * @returns {Undefined} - */ -var checkForContractAddress = function(contract, callback){ - var count = 0, - callbackFired = false; - - // wait for receipt - var filter = contract._eth.filter('latest', function(e){ - if (!e && !callbackFired) { - count++; + /** + * Utility functions + * + * @class [utils] config + * @constructor + */ - // stop watching after 50 blocks (timeout) - if (count > 50) { - filter.stopWatching(function() {}); - callbackFired = true; +/// required to define ETH_BIGNUMBER_ROUNDING_MODE + var BigNumber = require('bignumber.js'); + + var ETH_UNITS = [ + 'von', + 'kvon', + 'Mvon', + 'Gvon', + 'szabo', + 'finney', + 'femtolat', + 'picolat', + 'nanolat', + 'microlat', + 'millilat', + 'nano', + 'micro', + 'milli', + 'lat', + 'grand', + 'Mlat', + 'Glat', + 'Tlat', + 'Plat', + 'Elat', + 'Zlat', + 'Ylat', + 'Nlat', + 'Dlat', + 'Vlat', + 'Ulat' + ]; - if (callback) - callback(new Error('Contract transaction couldn\'t be found after 50 blocks')); - else - throw new Error('Contract transaction couldn\'t be found after 50 blocks'); + module.exports = { + ETH_PADDING: 32, + ETH_SIGNATURE_LENGTH: 4, + ETH_UNITS: ETH_UNITS, + ETH_BIGNUMBER_ROUNDING_MODE: { ROUNDING_MODE: BigNumber.ROUND_DOWN }, + ETH_POLLING_TIMEOUT: 1000/2, + defaultBlock: 'latest', + defaultAccount: undefined + }; - } else { + },{"bignumber.js":"bignumber.js"}],20:[function(require,module,exports){ +// Copyright (c) 2017 Pieter Wuille +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. - contract._eth.getTransactionReceipt(contract.transactionHash, function(e, receipt){ - if(receipt && receipt.blockHash && !callbackFired) { + var bech32 = require('./bech32'); - contract._eth.getCode(receipt.contractAddress, function(e, code){ - /*jshint maxcomplexity: 6 */ + module.exports = { + encode: encode, + decode: decode, + DecodeAddress:DecodeAddress, + EncodeAddress:EncodeAddress, + }; - if(callbackFired || !code) - return; + function convertbits (data, frombits, tobits, pad) { + var acc = 0; + var bits = 0; + var ret = []; + var maxv = (1 << tobits) - 1; + for (var p = 0; p < data.length; ++p) { + var value = data[p]; + if (value < 0 || (value >> frombits) !== 0) { + return null; + } + acc = (acc << frombits) | value; + bits += frombits; + while (bits >= tobits) { + bits -= tobits; + ret.push((acc >> bits) & maxv); + } + } + if (pad) { + if (bits > 0) { + ret.push((acc << (tobits - bits)) & maxv); + } + } else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) { + return null; + } + return ret; + } - filter.stopWatching(function() {}); - callbackFired = true; - - if(code.length > 3) { - - // console.log('Contract code deployed!'); - - contract.address = receipt.contractAddress; - - // attach events and methods again after we have - addFunctionsToContract(contract); - addEventsToContract(contract); - - // call callback for the second time - if(callback) - callback(null, contract); - - } else { - if(callback) - callback(new Error('The contract code couldn\'t be stored, please check your gas amount.')); - else - throw new Error('The contract code couldn\'t be stored, please check your gas amount.'); - } - }); - } - }); - } - } - }); -}; - -/** - * Should be called to create new ContractFactory instance - * - * @method ContractFactory - * @param {Array} abi - */ -var ContractFactory = function (eth, abi) { - this.eth = eth; - this.abi = abi; - - /** - * Should be called to create new contract on a blockchain - * - * @method new - * @param {Any} contract constructor param1 (optional) - * @param {Any} contract constructor param2 (optional) - * @param {Object} contract transaction object (required) - * @param {Function} callback - * @returns {Contract} returns contract instance - */ - this.new = function () { - /*jshint maxcomplexity: 7 */ - - var contract = new Contract(this.eth, this.abi); - - // parse arguments - var options = {}; // required! - var callback; - - var args = Array.prototype.slice.call(arguments); - if (utils.isFunction(args[args.length - 1])) { - callback = args.pop(); - } - - var last = args[args.length - 1]; - if (utils.isObject(last) && !utils.isArray(last)) { - options = args.pop(); - } + function decode (hrp, addr) { + var dec = bech32.decode(addr); + if (dec === null || dec.hrp !== hrp || dec.data.length < 1 /*|| dec.data[0] > 16*/) { + return null; + } + var res = convertbits(dec.data.slice(0), 5, 8, false); + if (res === null || res.length < 2 || res.length > 40) { + return null; + } + if (dec.data[0] === 0 && res.length !== 20 && res.length !== 32) { + return null; + } + return {hrp: dec.hrp, program: res}; + } - if (options.value > 0) { - var constructorAbi = abi.filter(function (json) { - return json.type === 'constructor' && json.inputs.length === args.length; - })[0] || {}; + function encode (hrp, program) { + var ret = bech32.encode(hrp, convertbits(program, 8, 5, true)); + if (decode(hrp, ret) === null) { + return null; + } + return ret; + } - if (!constructorAbi.payable) { - throw new Error('Cannot send value to non-payable constructor'); - } +//十六进制字符串转字节数组 + function Str2Bytes(str) { + var pos = 0; + var len = str.length; + if(len % 2 != 0) + { + return null; + } + len /= 2; + var hexA = new Array(); + for(var i=0; i < len; i++) + { + var s = str.substr(pos, 2); + if(s == "0x" || s == "0X") + { + pos += 2; + continue; } + var v = parseInt(s, 16); + hexA.push(v); + pos += 2; + } + return hexA; + } - var bytes = encodeConstructorParams(this.abi, args); - options.data += bytes; - - if (callback) { - - // wait for the contract address and check if the code was deployed - this.eth.sendTransaction(options, function (err, hash) { - if (err) { - callback(err); - } else { - // add the transaction hash - contract.transactionHash = hash; - - // call callback for the first time - callback(null, contract); - - checkForContractAddress(contract, callback); - } - }); - } else { - var hash = this.eth.sendTransaction(options); - // add the transaction hash - contract.transactionHash = hash; - checkForContractAddress(contract); +//字节数组转十六进制字符串 + function Bytes2Str(arr) { + var str = ""; + for(var i=0; i " + Bytes2Str(ret.program)); } - return contract; -}; - -/** - * Gets the data, which is data to deploy plus constructor params - * - * @method getData - */ -ContractFactory.prototype.getData = function () { - var options = {}; // required! - var args = Array.prototype.slice.call(arguments); - - var last = args[args.length - 1]; - if (utils.isObject(last) && !utils.isArray(last)) { - options = args.pop(); + +// + function EncodeAddress(hrp, strAddress) { + program = Str2Bytes(strAddress) + var ret = encode(hrp, program); + //console.log("encode result ==> " + ret); + return ret; } - var bytes = encodeConstructorParams(this.abi, args); - options.data += bytes; - - return options.data; -}; - -/** - * Should be called to create new contract instance - * - * @method Contract - * @param {Array} abi - * @param {Address} contract address - */ -var Contract = function (eth, abi, address) { - this._eth = eth; - this.transactionHash = null; - this.address = address; - this.abi = abi; -}; - -module.exports = ContractFactory; - -},{"../solidity/coder":7,"../utils/utils":22,"./allevents":25,"./event":29,"./function":33}],28:[function(require,module,exports){ -/* + },{"./bech32":17}],21:[function(require,module,exports){ + /* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify @@ -3432,36 +2063,31 @@ module.exports = ContractFactory; You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -/** - * @file errors.js - * @author Marek Kotewicz - * @date 2015 - */ - -module.exports = { - InvalidNumberOfSolidityArgs: function () { - return new Error('Invalid number of arguments to Solidity function'); - }, - InvalidNumberOfRPCParams: function () { - return new Error('Invalid number of input parameters to RPC method'); - }, - InvalidConnection: function (host){ - return new Error('CONNECTION ERROR: Couldn\'t connect to node '+ host +'.'); - }, - InvalidProvider: function () { - return new Error('Provider not set or invalid'); - }, - InvalidResponse: function (result){ - var message = !!result && !!result.error && !!result.error.message ? result.error.message : 'Invalid JSON RPC response: ' + JSON.stringify(result); - return new Error(message); - }, - ConnectionTimeout: function (ms){ - return new Error('CONNECTION TIMEOUT: timeout of ' + ms + ' ms achived'); - } -}; + /** + * @file sha3.js + * @author Marek Kotewicz + * @date 2015 + */ + + var CryptoJS = require('crypto-js'); + var sha3 = require('crypto-js/sha3'); + + module.exports = function (value, options) { + if (options && options.encoding === 'hex') { + if (value.length > 2 && value.substr(0, 2) === '0x') { + value = value.substr(2); + } + value = CryptoJS.enc.Hex.parse(value); + } + + return sha3(value, { + outputLength: 256 + }).toString(); + }; + -},{}],29:[function(require,module,exports){ -/* + },{"crypto-js":58,"crypto-js/sha3":79}],22:[function(require,module,exports){ + /* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify @@ -3477,499 +2103,699 @@ module.exports = { You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -/** - * @file event.js - * @author Marek Kotewicz - * @date 2014 - */ - -var utils = require('../utils/utils'); -var coder = require('../solidity/coder'); -var formatters = require('./formatters'); -var sha3 = require('../utils/sha3'); -var Filter = require('./filter'); -var watches = require('./methods/watches'); - -/** - * This prototype should be used to create event filters - */ -var SolidityEvent = function (requestManager, json, address) { - this._requestManager = requestManager; - this._params = json.inputs; - this._name = utils.transformToFullName(json); - this._address = address; - this._anonymous = json.anonymous; -}; - -/** - * Should be used to get filtered param types - * - * @method types - * @param {Bool} decide if returned typed should be indexed - * @return {Array} array of types - */ -SolidityEvent.prototype.types = function (indexed) { - return this._params.filter(function (i) { - return i.indexed === indexed; - }).map(function (i) { - return i.type; - }); -}; - -/** - * Should be used to get event display name - * - * @method displayName - * @return {String} event display name - */ -SolidityEvent.prototype.displayName = function () { - return utils.extractDisplayName(this._name); -}; - -/** - * Should be used to get event type name - * - * @method typeName - * @return {String} event type name - */ -SolidityEvent.prototype.typeName = function () { - return utils.extractTypeName(this._name); -}; - -/** - * Should be used to get event signature - * - * @method signature - * @return {String} event signature - */ -SolidityEvent.prototype.signature = function () { - return sha3(this._name); -}; - -/** - * Should be used to encode indexed params and options to one final object - * - * @method encode - * @param {Object} indexed - * @param {Object} options - * @return {Object} everything combined together and encoded - */ -SolidityEvent.prototype.encode = function (indexed, options) { - indexed = indexed || {}; - options = options || {}; - var result = {}; - - ['fromBlock', 'toBlock'].filter(function (f) { - return options[f] !== undefined; - }).forEach(function (f) { - result[f] = formatters.inputBlockNumberFormatter(options[f]); - }); + /** + * @file utils.js + * @author Marek Kotewicz + * @date 2015 + */ - result.topics = []; + /** + * Utils + * + * @module utils + */ - result.address = this._address; - if (!this._anonymous) { - result.topics.push('0x' + this.signature()); - } + /** + * Utility functions + * + * @class [utils] utils + * @constructor + */ - var indexedTopics = this._params.filter(function (i) { - return i.indexed === true; - }).map(function (i) { - var value = indexed[i.name]; - if (value === undefined || value === null) { - return null; - } - if (utils.isArray(value)) { - return value.map(function (v) { - return '0x' + coder.encodeParam(i.type, v); - }); - } - return '0x' + coder.encodeParam(i.type, value); - }); + var BigNumber = require('bignumber.js'); + var sha3 = require('./sha3.js'); + var utf8 = require('utf8'); + var segwit_addr = require('./segwit_addr.js'); + + var unitMap = { + 'noatp': '0', + 'von': '1', + 'kvon': '1000', + 'Kvon': '1000', + 'babbage': '1000', + 'femtoatp': '1000', + 'mvon': '1000000', + 'Mvon': '1000000', + 'lovelace': '1000000', + 'picoatp': '1000000', + 'gvon': '1000000000', + 'Gvon': '1000000000', + 'shannon': '1000000000', + 'nanoatp': '1000000000', + 'nano': '1000000000', + 'szabo': '1000000000000', + 'microatp': '1000000000000', + 'micro': '1000000000000', + 'finney': '1000000000000000', + 'milliatp': '1000000000000000', + 'milli': '1000000000000000', + 'atp': '1000000000000000000', + 'katp': '1000000000000000000000', + 'grand': '1000000000000000000000', + 'matp': '1000000000000000000000000', + 'gatp': '1000000000000000000000000000', + 'tatp': '1000000000000000000000000000000' + }; - result.topics = result.topics.concat(indexedTopics); + /** + * Should be called to pad string to expected length + * + * @method padLeft + * @param {String} string to be padded + * @param {Number} characters that result string should have + * @param {String} sign, by default 0 + * @returns {String} right aligned string + */ + var padLeft = function (string, chars, sign) { + return new Array(chars - string.length + 1).join(sign ? sign : "0") + string; + }; - return result; -}; + /** + * Should be called to pad string to expected length + * + * @method padRight + * @param {String} string to be padded + * @param {Number} characters that result string should have + * @param {String} sign, by default 0 + * @returns {String} right aligned string + */ + var padRight = function (string, chars, sign) { + return string + (new Array(chars - string.length + 1).join(sign ? sign : "0")); + }; -/** - * Should be used to decode indexed params and options - * - * @method decode - * @param {Object} data - * @return {Object} result object with decoded indexed && not indexed params - */ -SolidityEvent.prototype.decode = function (data) { + /** + * Should be called to get utf8 from it's hex representation + * + * @method toUtf8 + * @param {String} string in hex + * @returns {String} ascii string representation of hex value + */ + var toUtf8 = function(hex) { +// Find termination + var str = ""; + var i = 0, l = hex.length; + if (hex.substring(0, 2) === '0x') { + i = 2; + } + for (; i < l; i+=2) { + var code = parseInt(hex.substr(i, 2), 16); + if (code === 0) + break; + str += String.fromCharCode(code); + } - data.data = data.data || ''; - data.topics = data.topics || []; + return utf8.decode(str); + }; + /** + * Should be called to get ascii from it's hex representation + * + * @method toAscii + * @param {String} string in hex + * @returns {String} ascii string representation of hex value + */ + var toAscii = function(hex) { +// Find termination + var str = ""; + var i = 0, l = hex.length; + if (hex.substring(0, 2) === '0x') { + i = 2; + } + for (; i < l; i+=2) { + var code = parseInt(hex.substr(i, 2), 16); + str += String.fromCharCode(code); + } - var argTopics = this._anonymous ? data.topics : data.topics.slice(1); - var indexedData = argTopics.map(function (topics) { return topics.slice(2); }).join(""); - var indexedParams = coder.decodeParams(this.types(true), indexedData); + return str; + }; - var notIndexedData = data.data.slice(2); - var notIndexedParams = coder.decodeParams(this.types(false), notIndexedData); + /** + * Should be called to get hex representation (prefixed by 0x) of utf8 string + * + * @method fromUtf8 + * @param {String} string + * @param {Boolean} allowZero to convert code point zero to 00 instead of end of string + * @returns {String} hex representation of input string + */ + var fromUtf8 = function(str, allowZero) { + str = utf8.encode(str); + var hex = ""; + for(var i = 0; i < str.length; i++) { + var code = str.charCodeAt(i); + if (code === 0) { + if (allowZero) { + hex += '00'; + } else { + break; + } + } else { + var n = code.toString(16); + hex += n.length < 2 ? '0' + n : n; + } + } - var result = formatters.outputLogFormatter(data); - result.event = this.displayName(); - result.address = data.address; + return "0x" + hex; + }; - result.args = this._params.reduce(function (acc, current) { - acc[current.name] = current.indexed ? indexedParams.shift() : notIndexedParams.shift(); - return acc; - }, {}); + /** + * Should be called to get hex representation (prefixed by 0x) of ascii string + * + * @method fromAscii + * @param {String} string + * @param {Number} optional padding + * @returns {String} hex representation of input string + */ + var fromAscii = function(str) { + var hex = ""; + for(var i = 0; i < str.length; i++) { + var code = str.charCodeAt(i); + var n = code.toString(16); + hex += n.length < 2 ? '0' + n : n; + } - delete result.data; - delete result.topics; + return "0x" + hex; + }; - return result; -}; + /** + * Should be used to create full function/event name from json abi + * + * @method transformToFullName + * @param {Object} json-abi + * @return {String} full fnction/event name + */ + var transformToFullName = function (json) { + if (json.name.indexOf('(') !== -1) { + return json.name; + } -/** - * Should be used to create new filter object from event - * - * @method execute - * @param {Object} indexed - * @param {Object} options - * @return {Object} filter object - */ -SolidityEvent.prototype.execute = function (indexed, options, callback) { + var typeName = json.inputs.map(function(i){return i.type; }).join(); + return json.name + '(' + typeName + ')'; + }; - if (utils.isFunction(arguments[arguments.length - 1])) { - callback = arguments[arguments.length - 1]; - if(arguments.length === 2) - options = null; - if(arguments.length === 1) { - options = null; - indexed = {}; - } - } + /** + * Should be called to get display name of contract function + * + * @method extractDisplayName + * @param {String} name of function/event + * @returns {String} display name for function/event eg. multiply(uint256) -> multiply + */ + var extractDisplayName = function (name) { + var stBracket = name.indexOf('('); + var endBracket = name.indexOf(')'); + return (stBracket !== -1 && endBracket !== -1) ? name.substr(0, stBracket) : name; + }; - var o = this.encode(indexed, options); - var formatter = this.decode.bind(this); - return new Filter(o, 'eth', this._requestManager, watches.eth(), formatter, callback); -}; - -/** - * Should be used to attach event to contract object - * - * @method attachToContract - * @param {Contract} - */ -SolidityEvent.prototype.attachToContract = function (contract) { - var execute = this.execute.bind(this); - var displayName = this.displayName(); - if (!contract[displayName]) { - contract[displayName] = execute; - } - contract[displayName][this.typeName()] = this.execute.bind(this, contract); -}; + /** + * Should be called to get type name of contract function + * + * @method extractTypeName + * @param {String} name of function/event + * @returns {String} type name for function/event eg. multiply(uint256) -> uint256 + */ + var extractTypeName = function (name) { + var stBracket = name.indexOf('('); + var endBracket = name.indexOf(')'); + return (stBracket !== -1 && endBracket !== -1) ? name.substr(stBracket + 1, endBracket - stBracket - 1).replace(' ', '') : ""; + }; -module.exports = SolidityEvent; + /** + * Converts value to it's decimal representation in string + * + * @method toDecimal + * @param {String|Number|BigNumber} + * @return {String} + */ + var toDecimal = function (value) { + return toBigNumber(value).toNumber(); + }; + /** + * Converts value to it's hex representation + * + * @method fromDecimal + * @param {String|Number|BigNumber} + * @return {String} + */ + var fromDecimal = function (value) { + var number = toBigNumber(value); + var result = number.toString(16); -},{"../solidity/coder":7,"../utils/sha3":21,"../utils/utils":22,"./filter":31,"./formatters":32,"./methods/watches":43}],30:[function(require,module,exports){ -var formatters = require('./formatters'); -var utils = require('./../utils/utils'); -var Method = require('./method'); -var Property = require('./property'); + return number.lessThan(0) ? '-0x' + result.substr(1) : '0x' + result; + }; -// TODO: refactor, so the input params are not altered. -// it's necessary to make same 'extension' work with multiple providers -var extend = function (web3) { - /* jshint maxcomplexity:5 */ - var ex = function (extension) { + /** + * Auto converts any given value into it's hex representation. + * + * And even stringifys objects before. + * + * @method toHex + * @param {String|Number|BigNumber|Object} + * @return {String} + */ + var toHex = function (val) { + /*jshint maxcomplexity: 8 */ - var extendedObject; - if (extension.property) { - if (!web3[extension.property]) { - web3[extension.property] = {}; - } - extendedObject = web3[extension.property]; - } else { - extendedObject = web3; - } + if (isBoolean(val)) + return fromDecimal(+val); - if (extension.methods) { - extension.methods.forEach(function (method) { - method.attachToObject(extendedObject); - method.setRequestManager(web3._requestManager); - }); - } + if (isBigNumber(val)) + return fromDecimal(val); - if (extension.properties) { - extension.properties.forEach(function (property) { - property.attachToObject(extendedObject); - property.setRequestManager(web3._requestManager); - }); - } + if (typeof val === 'object') + return fromUtf8(JSON.stringify(val)); + + // if its a negative number, pass it through fromDecimal + if (isString(val)) { + if (val.indexOf('-0x') === 0) + return fromDecimal(val); + else if(val.indexOf('0x') === 0) + return val; + else if (!isFinite(val)) + return fromUtf8(val,1); + } + + return fromDecimal(val); }; - ex.formatters = formatters; - ex.utils = utils; - ex.Method = Method; - ex.Property = Property; + /** + * Returns value of unit in von + * + * @method getValueOfUnit + * @param {String} unit the unit to convert to, default atp + * @returns {BigNumber} value of the unit (in von) + * @throws error if the unit is not correct:w + */ + var getValueOfUnit = function (unit) { + unit = unit ? unit.toLowerCase() : 'atp'; + var unitValue = unitMap[unit]; + if (unitValue === undefined) { + throw new Error('This unit doesn\'t exists, please use the one of the following units' + JSON.stringify(unitMap, null, 2)); + } + return new BigNumber(unitValue, 10); + }; - return ex; -}; + /** + * Takes a number of von and converts it to any other atp unit. + * + * Possible units are: + * SI Short SI Full Effigy Other + * - kvon femtoatp babbage + * - mvon picoatp lovelace + * - gvon nanoatp shannon nano + * - -- microatp szabo micro + * - -- milliatp finney milli + * - atp -- -- + * - katp -- grand + * - matp + * - gatp + * - tatp + * + * @method fromVon + * @param {Number|String} number can be a number, number string or a HEX of a decimal + * @param {String} unit the unit to convert to, default atp + * @return {String|Object} When given a BigNumber object it returns one as well, otherwise a number + */ + var fromVon = function(number, unit) { + var returnValue = toBigNumber(number).dividedBy(getValueOfUnit(unit)); + return isBigNumber(number) ? returnValue : returnValue.toString(10); + }; + /** + * Takes a number of a unit and converts it to von. + * + * Possible units are: + * SI Short SI Full Effigy Other + * - kvon femtoatp babbage + * - mvon picoatp lovelace + * - gvon nanoatp shannon nano + * - -- microatp szabo micro + * - -- milliatp finney milli + * - atp -- -- + * - katp -- grand + * - matp + * - gatp + * - tatp + * + * @method toVon + * @param {Number|String|BigNumber} number can be a number, number string or a HEX of a decimal + * @param {String} unit the unit to convert from, default atp + * @return {String|Object} When given a BigNumber object it returns one as well, otherwise a number + */ + var toVon = function(number, unit) { + var returnValue = toBigNumber(number).times(getValueOfUnit(unit)); -module.exports = extend; + return isBigNumber(number) ? returnValue : returnValue.toString(10); + }; + /** + * Takes an input and transforms it into an bignumber + * + * @method toBigNumber + * @param {Number|String|BigNumber} a number, string, HEX string or BigNumber + * @return {BigNumber} BigNumber + */ + var toBigNumber = function(number) { + /*jshint maxcomplexity:5 */ + number = number || 0; + if (isBigNumber(number)) + return number; -},{"./../utils/utils":22,"./formatters":32,"./method":38,"./property":45}],31:[function(require,module,exports){ -/* - This file is part of web3.js. + if (isString(number) && (number.indexOf('0x') === 0 || number.indexOf('-0x') === 0)) { + return new BigNumber(number.replace('0x',''), 16); + } - web3.js 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. - - web3.js 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 web3.js. If not, see . -*/ -/** @file filter.js - * @authors: - * Jeffrey Wilcke - * Marek Kotewicz - * Marian Oancea - * Fabian Vogelsteller - * Gav Wood - * @date 2014 - */ - -var formatters = require('./formatters'); -var utils = require('../utils/utils'); - -/** -* Converts a given topic to a hex string, but also allows null values. -* -* @param {Mixed} value -* @return {String} -*/ -var toTopic = function(value){ - - if(value === null || typeof value === 'undefined') - return null; + return new BigNumber(number.toString(10), 10); + }; - value = String(value); + /** + * Takes and input transforms it into bignumber and if it is negative value, into two's complement + * + * @method toTwosComplement + * @param {Number|String|BigNumber} + * @return {BigNumber} + */ + var toTwosComplement = function (number) { + var bigNumber = toBigNumber(number).round(); + if (bigNumber.lessThan(0)) { + return new BigNumber("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16).plus(bigNumber).plus(1); + } + return bigNumber; + }; - if(value.indexOf('0x') === 0) - return value; - else - return utils.fromUtf8(value); -}; + /** + * Checks if the given string is strictly an address + * + * @method isStrictAddress + * @param {String} address the given HEX adress + * @return {Boolean} + */ + var isStrictAddress = function (address) { + return /^0x[0-9a-f]{40}$/i.test(address); + }; -/// This method should be called on options object, to verify deprecated properties && lazy load dynamic ones -/// @param should be string or object -/// @returns options string or object -var getOptions = function (options, type) { - /*jshint maxcomplexity: 6 */ + /** + * Checks if the given string is an address + * + * @method isAddress + * @param {String} address the given HEX adress + * @return {Boolean} + */ + var isAddress = function (address) { + if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) { + // check if it has the basic requirements of an address + return false; + } else if (/^(0x)?[0-9a-f]{40}$/.test(address) || /^(0x)?[0-9A-F]{40}$/.test(address)) { + // If it's all small caps or all all caps, return true + return true; + } else { + // Otherwise check each case + return isChecksumAddress(address); + } + }; - if (utils.isString(options)) { - return options; - } + /** + * Checks if the given string is an bech32 address + * + * @method isBech32Address + * @param {String} address the given bech32 adress + * @return {Boolean} + */ + var isBech32Address = function (address) { + if(address.length != 42) + { + return false; + } + var hrp = address.substr(0,3); + var ret = segwit_addr.decode(hrp, address); + if (ret === null) { + return false; + } + return true; + }; - options = options || {}; + /** + * Transforms given string to bech32 addres + * + * @method toBech32Address + * @param {String} hrp + * @param {String} address + * @return {String} formatted bech32 address + */ + var toBech32Address = function (hrp, address) { + if (isStrictAddress(address) || isChecksumAddress(address)) { + return segwit_addr.EncodeAddress(hrp, address); + } + return '' + }; - switch(type) { - case 'eth': + /** + * Resolve the bech32 address + * + * @method decodeBech32Address + * @param {String} bech32Address + * @return {String} formatted address + */ + var decodeBech32Address = function (bech32Address) { + if (isBech32Address(bech32Address)) { + var hrp = bech32Address.substr(0,3); + address = segwit_addr.DecodeAddress(hrp, bech32Address); + if (address !== null) { + return "0x" + address + } + } - // make sure topics, get converted to hex - options.topics = options.topics || []; - options.topics = options.topics.map(function(topic){ - return (utils.isArray(topic)) ? topic.map(toTopic) : toTopic(topic); - }); + return '' + }; - return { - topics: options.topics, - from: options.from, - to: options.to, - address: options.address, - fromBlock: formatters.inputBlockNumberFormatter(options.fromBlock), - toBlock: formatters.inputBlockNumberFormatter(options.toBlock) - }; - case 'shh': - return options; - } -}; -/** -Adds the callback and sets up the methods, to iterate over the results. + /** + * Checks if the given string is a checksummed address + * + * @method isChecksumAddress + * @param {String} address the given HEX adress + * @return {Boolean} + */ + var isChecksumAddress = function (address) { + // Check each case + address = address.replace('0x',''); + var addressHash = sha3(address.toLowerCase()); -@method getLogsAtStart -@param {Object} self -@param {function} callback -*/ -var getLogsAtStart = function(self, callback){ - // call getFilterLogs for the first watch callback start - if (!utils.isString(self.options)) { - self.get(function (err, messages) { - // don't send all the responses to all the watches again... just to self one - if (err) { - callback(err); - } + for (var i = 0; i < 40; i++ ) { + // the nth letter should be uppercase if the nth digit of casemap is 1 + if ((parseInt(addressHash[i], 16) > 7 && address[i].toUpperCase() !== address[i]) || (parseInt(addressHash[i], 16) <= 7 && address[i].toLowerCase() !== address[i])) { + return false; + } + } + return true; + }; - if(utils.isArray(messages)) { - messages.forEach(function (message) { - callback(null, message); - }); - } - }); - } -}; -/** -Adds the callback and sets up the methods, to iterate over the results. -@method pollFilter -@param {Object} self -*/ -var pollFilter = function(self) { + /** + * Makes a checksum address + * + * @method toChecksumAddress + * @param {String} address the given HEX adress + * @return {String} + */ + var toChecksumAddress = function (address) { + if (typeof address === 'undefined') return ''; - var onMessage = function (error, messages) { - if (error) { - return self.callbacks.forEach(function (callback) { - callback(error); - }); - } + address = address.toLowerCase().replace('0x',''); + var addressHash = sha3(address); + var checksumAddress = '0x'; - if(utils.isArray(messages)) { - messages.forEach(function (message) { - message = self.formatter ? self.formatter(message) : message; - self.callbacks.forEach(function (callback) { - callback(null, message); - }); - }); + for (var i = 0; i < address.length; i++ ) { + // If ith character is 9 to f then make it uppercase + if (parseInt(addressHash[i], 16) > 7) { + checksumAddress += address[i].toUpperCase(); + } else { + checksumAddress += address[i]; } + } + return checksumAddress; }; - self.requestManager.startPolling({ - method: self.implementation.poll.call, - params: [self.filterId], - }, self.filterId, onMessage, self.stopWatching.bind(self)); - -}; + /** + * Transforms given string to valid 20 bytes-length addres with 0x prefix + * + * @method toAddress + * @param {String} address + * @return {String} formatted address + */ + var toAddress = function (address) { + if (isStrictAddress(address)) { + return address; + } -var Filter = function (options, type, requestManager, methods, formatter, callback, filterCreationErrorCallback) { - var self = this; - var implementation = {}; - methods.forEach(function (method) { - method.setRequestManager(requestManager); - method.attachToObject(implementation); - }); - this.requestManager = requestManager; - this.options = getOptions(options, type); - this.implementation = implementation; - this.filterId = null; - this.callbacks = []; - this.getLogsCallbacks = []; - this.pollFilters = []; - this.formatter = formatter; - this.implementation.newFilter(this.options, function(error, id){ - if(error) { - self.callbacks.forEach(function(cb){ - cb(error); - }); - if (typeof filterCreationErrorCallback === 'function') { - filterCreationErrorCallback(error); - } - } else { - self.filterId = id; + if (/^[0-9a-f]{40}$/.test(address)) { + return '0x' + address; + } - // check if there are get pending callbacks as a consequence - // of calling get() with filterId unassigned. - self.getLogsCallbacks.forEach(function (cb){ - self.get(cb); - }); - self.getLogsCallbacks = []; + return '0x' + padLeft(toHex(address).substr(2), 40); + }; - // get filter logs for the already existing watch calls - self.callbacks.forEach(function(cb){ - getLogsAtStart(self, cb); - }); - if(self.callbacks.length > 0) - pollFilter(self); + /** + * Returns true if object is BigNumber, otherwise false + * + * @method isBigNumber + * @param {Object} + * @return {Boolean} + */ + var isBigNumber = function (object) { + return object instanceof BigNumber || + (object && object.constructor && object.constructor.name === 'BigNumber'); + }; - // start to watch immediately - if(typeof callback === 'function') { - return self.watch(callback); - } - } - }); + /** + * Returns true if object is string, otherwise false + * + * @method isString + * @param {Object} + * @return {Boolean} + */ + var isString = function (object) { + return typeof object === 'string' || + (object && object.constructor && object.constructor.name === 'String'); + }; - return this; -}; + /** + * Returns true if object is function, otherwise false + * + * @method isFunction + * @param {Object} + * @return {Boolean} + */ + var isFunction = function (object) { + return typeof object === 'function'; + }; -Filter.prototype.watch = function (callback) { - this.callbacks.push(callback); + /** + * Returns true if object is Objet, otherwise false + * + * @method isObject + * @param {Object} + * @return {Boolean} + */ + var isObject = function (object) { + return object !== null && !(Array.isArray(object)) && typeof object === 'object'; + }; - if(this.filterId) { - getLogsAtStart(this, callback); - pollFilter(this); - } + /** + * Returns true if object is boolean, otherwise false + * + * @method isBoolean + * @param {Object} + * @return {Boolean} + */ + var isBoolean = function (object) { + return typeof object === 'boolean'; + }; - return this; -}; + /** + * Returns true if object is array, otherwise false + * + * @method isArray + * @param {Object} + * @return {Boolean} + */ + var isArray = function (object) { + return Array.isArray(object); + }; -Filter.prototype.stopWatching = function (callback) { - this.requestManager.stopPolling(this.filterId); - this.callbacks = []; - // remove filter async - if (callback) { - this.implementation.uninstallFilter(this.filterId, callback); - } else { - return this.implementation.uninstallFilter(this.filterId); - } -}; + /** + * Returns true if given string is valid json object + * + * @method isJson + * @param {String} + * @return {Boolean} + */ + var isJson = function (str) { + try { + return !!JSON.parse(str); + } catch (e) { + return false; + } + }; -Filter.prototype.get = function (callback) { - var self = this; - if (utils.isFunction(callback)) { - if (this.filterId === null) { - // If filterId is not set yet, call it back - // when newFilter() assigns it. - this.getLogsCallbacks.push(callback); - } else { - this.implementation.getLogs(this.filterId, function(err, res){ - if (err) { - callback(err); - } else { - callback(null, res.map(function (log) { - return self.formatter ? self.formatter(log) : log; - })); - } - }); - } - } else { - if (this.filterId === null) { - throw new Error('Filter ID Error: filter().get() can\'t be chained synchronous, please provide a callback for the get() method.'); - } - var logs = this.implementation.getLogs(this.filterId); - return logs.map(function (log) { - return self.formatter ? self.formatter(log) : log; - }); - } + /** + * Returns true if given string is a valid Ethereum block header bloom. + * + * @method isBloom + * @param {String} hex encoded bloom filter + * @return {Boolean} + */ + var isBloom = function (bloom) { + if (!/^(0x)?[0-9a-f]{512}$/i.test(bloom)) { + return false; + } else if (/^(0x)?[0-9a-f]{512}$/.test(bloom) || /^(0x)?[0-9A-F]{512}$/.test(bloom)) { + return true; + } + return false; + }; - return this; -}; + /** + * Returns true if given string is a valid log topic. + * + * @method isTopic + * @param {String} hex encoded topic + * @return {Boolean} + */ + var isTopic = function (topic) { + if (!/^(0x)?[0-9a-f]{64}$/i.test(topic)) { + return false; + } else if (/^(0x)?[0-9a-f]{64}$/.test(topic) || /^(0x)?[0-9A-F]{64}$/.test(topic)) { + return true; + } + return false; + }; -module.exports = Filter; + module.exports = { + padLeft: padLeft, + padRight: padRight, + toHex: toHex, + toDecimal: toDecimal, + fromDecimal: fromDecimal, + toUtf8: toUtf8, + toAscii: toAscii, + fromUtf8: fromUtf8, + fromAscii: fromAscii, + transformToFullName: transformToFullName, + extractDisplayName: extractDisplayName, + extractTypeName: extractTypeName, + toVon: toVon, + fromVon: fromVon, + toBigNumber: toBigNumber, + toTwosComplement: toTwosComplement, + toAddress: toAddress, + isBigNumber: isBigNumber, + isStrictAddress: isStrictAddress, + isAddress: isAddress, + isChecksumAddress: isChecksumAddress, + toChecksumAddress: toChecksumAddress, + isBech32Address:isBech32Address, + toBech32Address:toBech32Address, + decodeBech32Address:decodeBech32Address, + isFunction: isFunction, + isString: isString, + isObject: isObject, + isBoolean: isBoolean, + isArray: isArray, + isJson: isJson, + isBloom: isBloom, + isTopic: isTopic, + }; + },{"./segwit_addr.js":20,"./sha3.js":21,"bignumber.js":"bignumber.js","utf8":84}],23:[function(require,module,exports){ + module.exports={ + "version": "0.1.0" + } -},{"../utils/utils":22,"./formatters":32}],32:[function(require,module,exports){ -/* + },{}],24:[function(require,module,exports){ + /* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify @@ -3985,311 +2811,144 @@ module.exports = Filter; You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -/** - * @file formatters.js - * @author Marek Kotewicz - * @author Fabian Vogelsteller - * @date 2015 - */ - -'use strict'; - - -var utils = require('../utils/utils'); -var config = require('../utils/config'); -var Iban = require('./iban'); -var segwit_addr = require('../utils/segwit_addr.js'); - -/** - * Should the format output to a big number - * - * @method outputBigNumberFormatter - * @param {String|Number|BigNumber} - * @returns {BigNumber} object - */ -var outputBigNumberFormatter = function (number) { - return utils.toBigNumber(number); -}; - -var isPredefinedBlockNumber = function (blockNumber) { - return blockNumber === 'latest' || blockNumber === 'pending' || blockNumber === 'earliest'; -}; - -var inputDefaultBlockNumberFormatter = function (blockNumber) { - if (blockNumber === undefined) { - return config.defaultBlock; - } - return inputBlockNumberFormatter(blockNumber); -}; + /** + * @file web3.js + * @authors: + * Jeffrey Wilcke + * Marek Kotewicz + * Marian Oancea + * Fabian Vogelsteller + * Gav Wood + * @date 2014 + */ -var inputBlockNumberFormatter = function (blockNumber) { - if (blockNumber === undefined) { - return undefined; - } else if (isPredefinedBlockNumber(blockNumber)) { - return blockNumber; + var RequestManager = require('./web3/requestmanager'); + var Iban = require('./web3/iban'); + var Eth = require('./web3/methods/eth'); + var DB = require('./web3/methods/db'); +// var Shh = require('./web3/methods/shh'); + var Net = require('./web3/methods/net'); + var Personal = require('./web3/methods/personal'); +// var Swarm = require('./web3/methods/swarm'); + var version = require('./version.json'); + var utils = require('./utils/utils'); + var sha3 = require('./utils/sha3'); + var extend = require('./web3/extend'); + var Batch = require('./web3/batch'); + var Property = require('./web3/property'); + var HttpProvider = require('./web3/httpprovider'); + var IpcProvider = require('./web3/ipcprovider'); + var BigNumber = require('bignumber.js'); + + function Web3 (provider) { + this._requestManager = new RequestManager(provider); + this.currentProvider = provider; + this.platon = new Eth(this); + this.db = new DB(this); + // this.shh = new Shh(this); + this.net = new Net(this); + this.personal = new Personal(this); + // this.bzz = new Swarm(this); + this.version = { + api: version.version + }; + this.providers = { + HttpProvider: HttpProvider, + IpcProvider: IpcProvider + }; + this._extend = extend(this); + this._extend({ + properties: properties() + }); } - return utils.toHex(blockNumber); -}; - -/** - * Formats the input of a transaction and converts all values to HEX - * - * @method inputCallFormatter - * @param {Object} transaction options - * @returns object -*/ -var inputCallFormatter = function (options){ - options.from = options.from || config.defaultAccount; - - if (options.from) { - options.from = inputAddressFormatter(options.from); - } +// expose providers on the class + Web3.providers = { + HttpProvider: HttpProvider, + IpcProvider: IpcProvider + }; - if (options.to) { // it might be contract creation - options.to = inputAddressFormatter(options.to); - } + Web3.prototype.setProvider = function (provider) { + this._requestManager.setProvider(provider); + this.currentProvider = provider; + }; - ['gasPrice', 'gas', 'value', 'nonce'].filter(function (key) { - return options[key] !== undefined; - }).forEach(function(key){ - options[key] = utils.fromDecimal(options[key]); - }); + Web3.prototype.reset = function (keepIsSyncing) { + this._requestManager.reset(keepIsSyncing); + this.settings = new Settings(); + }; - return options; -}; + Web3.prototype.BigNumber = BigNumber; + Web3.prototype.toHex = utils.toHex; + Web3.prototype.toAscii = utils.toAscii; + Web3.prototype.toUtf8 = utils.toUtf8; + Web3.prototype.fromAscii = utils.fromAscii; + Web3.prototype.fromUtf8 = utils.fromUtf8; + Web3.prototype.toDecimal = utils.toDecimal; + Web3.prototype.fromDecimal = utils.fromDecimal; + Web3.prototype.toBigNumber = utils.toBigNumber; + Web3.prototype.toVon = utils.toVon; + Web3.prototype.fromVon = utils.fromVon; + Web3.prototype.isAddress = utils.isAddress; + Web3.prototype.isChecksumAddress = utils.isChecksumAddress; + Web3.prototype.toChecksumAddress = utils.toChecksumAddress; + Web3.prototype.isBech32Address = utils.isBech32Address; + Web3.prototype.toBech32Address = utils.toBech32Address; + Web3.prototype.decodeBech32Address = utils.decodeBech32Address; + Web3.prototype.padLeft = utils.padLeft; + Web3.prototype.padRight = utils.padRight; + + + Web3.prototype.sha3 = function(string, options) { + return '0x' + sha3(string, options); + }; -/** - * Formats the input of a transaction and converts all values to HEX - * - * @method inputTransactionFormatter - * @param {Object} transaction options - * @returns object -*/ -var inputTransactionFormatter = function (options){ + /** + * Transforms direct icap to address + */ + Web3.prototype.fromICAP = function (icap) { + var iban = new Iban(icap); + return iban.address(); + }; - options.from = options.from || config.defaultAccount; - options.from = inputAddressFormatter(options.from); + var properties = function () { + return [ + new Property({ + name: 'version.node', + getter: 'web3_clientVersion' + }), + new Property({ + name: 'version.network', + getter: 'net_version', + inputFormatter: utils.toDecimal + }), + new Property({ + name: 'version.platon', + getter: 'platon_protocolVersion', + inputFormatter: utils.toDecimal + }) + // new Property({ + // name: 'version.whisper', + // getter: 'shh_version', + // inputFormatter: utils.toDecimal + // }) + ]; + }; - if (options.to) { // it might be contract creation - options.to = inputAddressFormatter(options.to); - } + Web3.prototype.isConnected = function(){ + return (this.currentProvider && this.currentProvider.isConnected()); + }; - ['gasPrice', 'gas', 'value', 'nonce'].filter(function (key) { - return options[key] !== undefined; - }).forEach(function(key){ - options[key] = utils.fromDecimal(options[key]); - }); + Web3.prototype.createBatch = function () { + return new Batch(this); + }; - return options; -}; + module.exports = Web3; -/** - * Formats the output of a transaction to its proper values - * - * @method outputTransactionFormatter - * @param {Object} tx - * @returns {Object} -*/ -var outputTransactionFormatter = function (tx){ - if(tx.blockNumber !== null) - tx.blockNumber = utils.toDecimal(tx.blockNumber); - if(tx.transactionIndex !== null) - tx.transactionIndex = utils.toDecimal(tx.transactionIndex); - tx.nonce = utils.toDecimal(tx.nonce); - tx.gas = utils.toDecimal(tx.gas); - tx.gasPrice = utils.toBigNumber(tx.gasPrice); - tx.value = utils.toBigNumber(tx.value); - return tx; -}; - -/** - * Formats the output of a transaction receipt to its proper values - * - * @method outputTransactionReceiptFormatter - * @param {Object} receipt - * @returns {Object} -*/ -var outputTransactionReceiptFormatter = function (receipt){ - if(receipt.blockNumber !== null) - receipt.blockNumber = utils.toDecimal(receipt.blockNumber); - if(receipt.transactionIndex !== null) - receipt.transactionIndex = utils.toDecimal(receipt.transactionIndex); - receipt.cumulativeGasUsed = utils.toDecimal(receipt.cumulativeGasUsed); - receipt.gasUsed = utils.toDecimal(receipt.gasUsed); - if(utils.isArray(receipt.logs)) { - receipt.logs = receipt.logs.map(function(log){ - return outputLogFormatter(log); - }); - } - - return receipt; -}; - -/** - * Formats the output of a block to its proper values - * - * @method outputBlockFormatter - * @param {Object} block - * @returns {Object} -*/ -var outputBlockFormatter = function(block) { - - // transform to number - block.gasLimit = utils.toDecimal(block.gasLimit); - block.gasUsed = utils.toDecimal(block.gasUsed); - block.size = utils.toDecimal(block.size); - block.timestamp = utils.toDecimal(block.timestamp); - if(block.number !== null) - block.number = utils.toDecimal(block.number); - - if (utils.isArray(block.transactions)) { - block.transactions.forEach(function(item){ - if(!utils.isString(item)) - return outputTransactionFormatter(item); - }); - } - - return block; -}; - -/** - * Formats the output of a log - * - * @method outputLogFormatter - * @param {Object} log object - * @returns {Object} log -*/ -var outputLogFormatter = function(log) { - if(log.blockNumber) - log.blockNumber = utils.toDecimal(log.blockNumber); - if(log.transactionIndex) - log.transactionIndex = utils.toDecimal(log.transactionIndex); - if(log.logIndex) - log.logIndex = utils.toDecimal(log.logIndex); - - return log; -}; - -/** - * Formats the input of a whisper post and converts all values to HEX - * - * @method inputPostFormatter - * @param {Object} transaction object - * @returns {Object} -*/ -var inputPostFormatter = function(post) { - - // post.payload = utils.toHex(post.payload); - post.ttl = utils.fromDecimal(post.ttl); - post.workToProve = utils.fromDecimal(post.workToProve); - post.priority = utils.fromDecimal(post.priority); - - // fallback - if (!utils.isArray(post.topics)) { - post.topics = post.topics ? [post.topics] : []; - } - - // format the following options - post.topics = post.topics.map(function(topic){ - // convert only if not hex - return (topic.indexOf('0x') === 0) ? topic : utils.fromUtf8(topic); - }); - - return post; -}; - -/** - * Formats the output of a received post message - * - * @method outputPostFormatter - * @param {Object} - * @returns {Object} - */ -var outputPostFormatter = function(post){ - - post.expiry = utils.toDecimal(post.expiry); - post.sent = utils.toDecimal(post.sent); - post.ttl = utils.toDecimal(post.ttl); - post.workProved = utils.toDecimal(post.workProved); - // post.payloadRaw = post.payload; - // post.payload = utils.toAscii(post.payload); - - // if (utils.isJson(post.payload)) { - // post.payload = JSON.parse(post.payload); - // } - - // format the following options - if (!post.topics) { - post.topics = []; - } - post.topics = post.topics.map(function(topic){ - return utils.toAscii(topic); - }); - - return post; -}; - -var inputAddressFormatter = function (address) { - if (utils.isBech32Address(address)) { - return address; - } else if (utils.isAddress(address)) { - return '0x' + address.toLowerCase().replace('0x', ''); - } - throw new Error('invalid address'); -}; - -/* -var inputAddressFormatter = function (address) { - var iban = new Iban(address); - hrf - if (iban.isValid() && iban.isDirect()) { - return '0x' + iban.address(); - } else if (utils.isStrictAddress(address)) { - return address; - } else if (utils.isAddress(address)) { - return '0x' + address; - } - throw new Error('invalid address'); -};*/ - -var outputSyncingFormatter = function(result) { - if (!result) { - return result; - } - - result.startingBlock = utils.toDecimal(result.startingBlock); - result.currentBlock = utils.toDecimal(result.currentBlock); - result.highestBlock = utils.toDecimal(result.highestBlock); - if (result.knownStates) { - result.knownStates = utils.toDecimal(result.knownStates); - result.pulledStates = utils.toDecimal(result.pulledStates); - } - - return result; -}; - -module.exports = { - inputDefaultBlockNumberFormatter: inputDefaultBlockNumberFormatter, - inputBlockNumberFormatter: inputBlockNumberFormatter, - inputCallFormatter: inputCallFormatter, - inputTransactionFormatter: inputTransactionFormatter, - inputAddressFormatter: inputAddressFormatter, - inputPostFormatter: inputPostFormatter, - outputBigNumberFormatter: outputBigNumberFormatter, - outputTransactionFormatter: outputTransactionFormatter, - outputTransactionReceiptFormatter: outputTransactionReceiptFormatter, - outputBlockFormatter: outputBlockFormatter, - outputLogFormatter: outputLogFormatter, - outputPostFormatter: outputPostFormatter, - outputSyncingFormatter: outputSyncingFormatter -}; - - -},{"../utils/config":19,"../utils/segwit_addr.js":20,"../utils/utils":22,"./iban":35}],33:[function(require,module,exports){ -/* - This file is part of web3.js. + },{"./utils/sha3":21,"./utils/utils":22,"./version.json":23,"./web3/batch":26,"./web3/extend":30,"./web3/httpprovider":34,"./web3/iban":35,"./web3/ipcprovider":36,"./web3/methods/db":39,"./web3/methods/eth":40,"./web3/methods/net":41,"./web3/methods/personal":42,"./web3/property":45,"./web3/requestmanager":46,"bignumber.js":"bignumber.js"}],25:[function(require,module,exports){ + /* + This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by @@ -4304,276 +2963,80 @@ module.exports = { You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -/** - * @file function.js - * @author Marek Kotewicz - * @date 2015 - */ - -var coder = require('../solidity/coder'); -var utils = require('../utils/utils'); -var errors = require('./errors'); -var formatters = require('./formatters'); -var sha3 = require('../utils/sha3'); - -/** - * This prototype should be used to call/sendTransaction to solidity functions - */ -var SolidityFunction = function (eth, json, address) { - this._eth = eth; - this._inputTypes = json.inputs.map(function (i) { - return i.type; - }); - this._outputTypes = json.outputs.map(function (i) { - return i.type; - }); - this._constant = json.constant; - this._payable = json.payable; - this._name = utils.transformToFullName(json); - this._address = address; -}; - -SolidityFunction.prototype.extractCallback = function (args) { - if (utils.isFunction(args[args.length - 1])) { - return args.pop(); // modify the args array! - } -}; + /** + * @file allevents.js + * @author Marek Kotewicz + * @date 2014 + */ -SolidityFunction.prototype.extractDefaultBlock = function (args) { - if (args.length > this._inputTypes.length && !utils.isObject(args[args.length -1])) { - return formatters.inputDefaultBlockNumberFormatter(args.pop()); // modify the args array! - } -}; - -/** - * Should be called to check if the number of arguments is correct - * - * @method validateArgs - * @param {Array} arguments - * @throws {Error} if it is not - */ -SolidityFunction.prototype.validateArgs = function (args) { - var inputArgs = args.filter(function (a) { - // filter the options object but not arguments that are arrays - return !( (utils.isObject(a) === true) && - (utils.isArray(a) === false) && - (utils.isBigNumber(a) === false) - ); - }); - if (inputArgs.length !== this._inputTypes.length) { - throw errors.InvalidNumberOfSolidityArgs(); - } -}; - -/** - * Should be used to create payload from arguments - * - * @method toPayload - * @param {Array} solidity function params - * @param {Object} optional payload options - */ -SolidityFunction.prototype.toPayload = function (args) { - var options = {}; - if (args.length > this._inputTypes.length && utils.isObject(args[args.length -1])) { - options = args[args.length - 1]; - } - this.validateArgs(args); - options.to = this._address; - options.data = '0x' + this.signature() + coder.encodeParams(this._inputTypes, args); - return options; -}; - -/** - * Should be used to get function signature - * - * @method signature - * @return {String} function signature - */ -SolidityFunction.prototype.signature = function () { - return sha3(this._name).slice(0, 8); -}; - - -SolidityFunction.prototype.unpackOutput = function (output) { - if (!output) { - return; - } + var sha3 = require('../utils/sha3'); + var SolidityEvent = require('./event'); + var formatters = require('./formatters'); + var utils = require('../utils/utils'); + var Filter = require('./filter'); + var watches = require('./methods/watches'); + + var AllSolidityEvents = function (requestManager, json, address) { + this._requestManager = requestManager; + this._json = json; + this._address = address; + }; - output = output.length >= 2 ? output.slice(2) : output; - var result = coder.decodeParams(this._outputTypes, output); - return result.length === 1 ? result[0] : result; -}; - -/** - * Calls a contract function. - * - * @method call - * @param {...Object} Contract function arguments - * @param {function} If the last argument is a function, the contract function - * call will be asynchronous, and the callback will be passed the - * error and result. - * @return {String} output bytes - */ -SolidityFunction.prototype.call = function () { - var args = Array.prototype.slice.call(arguments).filter(function (a) {return a !== undefined; }); - var callback = this.extractCallback(args); - var defaultBlock = this.extractDefaultBlock(args); - var payload = this.toPayload(args); - - - if (!callback) { - var output = this._eth.call(payload, defaultBlock); - return this.unpackOutput(output); - } + AllSolidityEvents.prototype.encode = function (options) { + options = options || {}; + var result = {}; - var self = this; - this._eth.call(payload, defaultBlock, function (error, output) { - if (error) return callback(error, null); + ['fromBlock', 'toBlock'].filter(function (f) { + return options[f] !== undefined; + }).forEach(function (f) { + result[f] = formatters.inputBlockNumberFormatter(options[f]); + }); - var unpacked = null; - try { - unpacked = self.unpackOutput(output); - } - catch (e) { - error = e; - } + result.address = this._address; - callback(error, unpacked); - }); -}; - -/** - * Should be used to sendTransaction to solidity function - * - * @method sendTransaction - */ -SolidityFunction.prototype.sendTransaction = function () { - var args = Array.prototype.slice.call(arguments).filter(function (a) {return a !== undefined; }); - var callback = this.extractCallback(args); - var payload = this.toPayload(args); - - if (payload.value > 0 && !this._payable) { - throw new Error('Cannot send value to non-payable function'); - } + return result; + }; - if (!callback) { - return this._eth.sendTransaction(payload); - } + AllSolidityEvents.prototype.decode = function (data) { + data.data = data.data || ''; - this._eth.sendTransaction(payload, callback); -}; -/** - * Should be used to estimateGas of solidity function - * - * @method estimateGas - */ -SolidityFunction.prototype.estimateGas = function () { - var args = Array.prototype.slice.call(arguments); - var callback = this.extractCallback(args); - var payload = this.toPayload(args); + var eventTopic = (utils.isArray(data.topics) && utils.isString(data.topics[0])) ? data.topics[0].slice(2) : ''; + var match = this._json.filter(function (j) { + return eventTopic === sha3(utils.transformToFullName(j)); + })[0]; - if (!callback) { - return this._eth.estimateGas(payload); - } + if (!match) { // cannot find matching event? + return formatters.outputLogFormatter(data); + } - this._eth.estimateGas(payload, callback); -}; - -/** - * Return the encoded data of the call - * - * @method getData - * @return {String} the encoded data - */ -SolidityFunction.prototype.getData = function () { - var args = Array.prototype.slice.call(arguments); - var payload = this.toPayload(args); - - return payload.data; -}; - -/** - * Should be used to get function display name - * - * @method displayName - * @return {String} display name of the function - */ -SolidityFunction.prototype.displayName = function () { - return utils.extractDisplayName(this._name); -}; - -/** - * Should be used to get function type name - * - * @method typeName - * @return {String} type name of the function - */ -SolidityFunction.prototype.typeName = function () { - return utils.extractTypeName(this._name); -}; - -/** - * Should be called to get rpc requests from solidity function - * - * @method request - * @returns {Object} - */ -SolidityFunction.prototype.request = function () { - var args = Array.prototype.slice.call(arguments); - var callback = this.extractCallback(args); - var payload = this.toPayload(args); - var format = this.unpackOutput.bind(this); - - return { - method: this._constant ? 'platon_call' : 'platon_sendTransaction', - callback: callback, - params: [payload], - format: format + var event = new SolidityEvent(this._requestManager, match, this._address); + return event.decode(data); }; -}; -/** - * Should be called to execute function - * - * @method execute - */ -SolidityFunction.prototype.execute = function () { - var transaction = !this._constant; + AllSolidityEvents.prototype.execute = function (options, callback) { - // send transaction - if (transaction) { - return this.sendTransaction.apply(this, Array.prototype.slice.call(arguments)); - } + if (utils.isFunction(arguments[arguments.length - 1])) { + callback = arguments[arguments.length - 1]; + if(arguments.length === 1) + options = null; + } - // call - return this.call.apply(this, Array.prototype.slice.call(arguments)); -}; - -/** - * Should be called to attach function to contract - * - * @method attachToContract - * @param {Contract} - */ -SolidityFunction.prototype.attachToContract = function (contract) { - var execute = this.execute.bind(this); - execute.request = this.request.bind(this); - execute.call = this.call.bind(this); - execute.sendTransaction = this.sendTransaction.bind(this); - execute.estimateGas = this.estimateGas.bind(this); - execute.getData = this.getData.bind(this); - var displayName = this.displayName(); - if (!contract[displayName]) { - contract[displayName] = execute; - } - contract[displayName][this.typeName()] = execute; // circular!!!! -}; + var o = this.encode(options); + var formatter = this.decode.bind(this); + return new Filter(o, 'eth', this._requestManager, watches.eth(), formatter, callback); + }; + + AllSolidityEvents.prototype.attachToContract = function (contract) { + var execute = this.execute.bind(this); + contract.allEvents = execute; + }; + + module.exports = AllSolidityEvents; -module.exports = SolidityFunction; -},{"../solidity/coder":7,"../utils/sha3":21,"../utils/utils":22,"./errors":28,"./formatters":32}],34:[function(require,module,exports){ -/* + },{"../utils/sha3":21,"../utils/utils":22,"./event":29,"./filter":31,"./formatters":32,"./methods/watches":43}],26:[function(require,module,exports){ + /* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify @@ -4589,157 +3052,59 @@ module.exports = SolidityFunction; You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -/** @file httpprovider.js - * @authors: - * Marek Kotewicz - * Marian Oancea - * Fabian Vogelsteller - * @date 2015 - */ + /** + * @file batch.js + * @author Marek Kotewicz + * @date 2015 + */ -var errors = require('./errors'); + var Jsonrpc = require('./jsonrpc'); + var errors = require('./errors'); -// workaround to use httpprovider in different envs + var Batch = function (web3) { + this.requestManager = web3._requestManager; + this.requests = []; + }; -// browser -if (typeof window !== 'undefined' && window.XMLHttpRequest) { - XMLHttpRequest = window.XMLHttpRequest; // jshint ignore: line -// node -} else { - XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; // jshint ignore: line -} - -var XHR2 = require('xhr2'); // jshint ignore: line - -/** - * HttpProvider should be used to send rpc calls over http - */ -var HttpProvider = function (host, timeout, user, password, headers) { - this.host = host || 'http://localhost:8545'; - this.timeout = timeout || 0; - this.user = user; - this.password = password; - this.headers = headers; -}; - -/** - * Should be called to prepare new XMLHttpRequest - * - * @method prepareRequest - * @param {Boolean} true if request should be async - * @return {XMLHttpRequest} object - */ -HttpProvider.prototype.prepareRequest = function (async) { - var request; - - if (async) { - request = new XHR2(); - request.timeout = this.timeout; - } else { - request = new XMLHttpRequest(); - } - - request.open('POST', this.host, async); - if (this.user && this.password) { - var auth = 'Basic ' + new Buffer(this.user + ':' + this.password).toString('base64'); - request.setRequestHeader('Authorization', auth); - } request.setRequestHeader('Content-Type', 'application/json'); - if(this.headers) { - this.headers.forEach(function(header) { - request.setRequestHeader(header.name, header.value); - }); - } - return request; -}; - -/** - * Should be called to make sync request - * - * @method send - * @param {Object} payload - * @return {Object} result - */ -HttpProvider.prototype.send = function (payload) { - var request = this.prepareRequest(false); - - try { - request.send(JSON.stringify(payload)); - } catch (error) { - throw errors.InvalidConnection(this.host); - } - - var result = request.responseText; - - try { - result = JSON.parse(result); - } catch (e) { - throw errors.InvalidResponse(request.responseText); - } - - return result; -}; - -/** - * Should be used to make async request - * - * @method sendAsync - * @param {Object} payload - * @param {Function} callback triggered on end with (err, result) - */ -HttpProvider.prototype.sendAsync = function (payload, callback) { - var request = this.prepareRequest(true); - - request.onreadystatechange = function () { - if (request.readyState === 4 && request.timeout !== 1) { - var result = request.responseText; - var error = null; + /** + * Should be called to add create new request to batch request + * + * @method add + * @param {Object} jsonrpc requet object + */ + Batch.prototype.add = function (request) { + this.requests.push(request); + }; - try { - result = JSON.parse(result); - } catch (e) { - error = errors.InvalidResponse(request.responseText); - } + /** + * Should be called to execute batch request + * + * @method execute + */ + Batch.prototype.execute = function () { + var requests = this.requests; + this.requestManager.sendBatch(requests, function (err, results) { + results = results || []; + requests.map(function (request, index) { + return results[index] || {}; + }).forEach(function (result, index) { + if (requests[index].callback) { - callback(error, result); - } - }; - - request.ontimeout = function () { - callback(errors.ConnectionTimeout(this.timeout)); - }; - - try { - request.send(JSON.stringify(payload)); - } catch (error) { - callback(errors.InvalidConnection(this.host)); - } -}; - -/** - * Synchronously tries to make Http request - * - * @method isConnected - * @return {Boolean} returns true if request haven't failed. Otherwise false - */ -HttpProvider.prototype.isConnected = function () { - try { - this.send({ - bech32: true, - id: 9999999999, - jsonrpc: '2.0', - method: 'net_listening', - params: [] - }); - return true; - } catch (e) { - return false; - } -}; + if (!Jsonrpc.isValidResponse(result)) { + return requests[index].callback(errors.InvalidResponse(result)); + } -module.exports = HttpProvider; + requests[index].callback(null, (requests[index].format ? requests[index].format(result.result) : result.result)); + } + }); + }); + }; + + module.exports = Batch; -},{"./errors":28,"xhr2":85,"xmlhttprequest":18}],35:[function(require,module,exports){ -/* + + },{"./errors":28,"./jsonrpc":37}],27:[function(require,module,exports){ + /* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify @@ -4755,429 +3120,303 @@ module.exports = HttpProvider; You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -/** - * @file iban.js - * @author Marek Kotewicz - * @date 2015 - */ - -var BigNumber = require('bignumber.js'); + /** + * @file contract.js + * @author Marek Kotewicz + * @date 2014 + */ -var padLeft = function (string, bytes) { - var result = string; - while (result.length < bytes * 2) { - result = '0' + result; - } - return result; -}; - -/** - * Prepare an IBAN for mod 97 computation by moving the first 4 chars to the end and transforming the letters to - * numbers (A = 10, B = 11, ..., Z = 35), as specified in ISO13616. - * - * @method iso13616Prepare - * @param {String} iban the IBAN - * @returns {String} the prepared IBAN - */ -var iso13616Prepare = function (iban) { - var A = 'A'.charCodeAt(0); - var Z = 'Z'.charCodeAt(0); - - iban = iban.toUpperCase(); - iban = iban.substr(4) + iban.substr(0,4); - - return iban.split('').map(function(n){ - var code = n.charCodeAt(0); - if (code >= A && code <= Z){ - // A = 10, B = 11, ... Z = 35 - return code - A + 10; - } else { - return n; - } - }).join(''); -}; - -/** - * Calculates the MOD 97 10 of the passed IBAN as specified in ISO7064. - * - * @method mod9710 - * @param {String} iban - * @returns {Number} - */ -var mod9710 = function (iban) { - var remainder = iban, - block; - - while (remainder.length > 2){ - block = remainder.slice(0, 9); - remainder = parseInt(block, 10) % 97 + remainder.slice(block.length); - } + var utils = require('../utils/utils'); + var coder = require('../solidity/coder'); + var SolidityEvent = require('./event'); + var SolidityFunction = require('./function'); + var AllEvents = require('./allevents'); - return parseInt(remainder, 10) % 97; -}; - -/** - * This prototype should be used to create iban object from iban correct string - * - * @param {String} iban - */ -var Iban = function (iban) { - this._iban = iban; -}; - -/** - * This method should be used to create iban object from ethereum address - * - * @method fromAddress - * @param {String} address - * @return {Iban} the IBAN object - */ -Iban.fromAddress = function (address) { - var asBn = new BigNumber(address, 16); - var base36 = asBn.toString(36); - var padded = padLeft(base36, 15); - return Iban.fromBban(padded.toUpperCase()); -}; - -/** - * Convert the passed BBAN to an IBAN for this country specification. - * Please note that "generation of the IBAN shall be the exclusive responsibility of the bank/branch servicing the account". - * This method implements the preferred algorithm described in http://en.wikipedia.org/wiki/International_Bank_Account_Number#Generating_IBAN_check_digits - * - * @method fromBban - * @param {String} bban the BBAN to convert to IBAN - * @returns {Iban} the IBAN object - */ -Iban.fromBban = function (bban) { - var countryCode = 'XE'; - - var remainder = mod9710(iso13616Prepare(countryCode + '00' + bban)); - var checkDigit = ('0' + (98 - remainder)).slice(-2); - - return new Iban(countryCode + checkDigit + bban); -}; - -/** - * Should be used to create IBAN object for given institution and identifier - * - * @method createIndirect - * @param {Object} options, required options are "institution" and "identifier" - * @return {Iban} the IBAN object - */ -Iban.createIndirect = function (options) { - return Iban.fromBban('ETH' + options.institution + options.identifier); -}; - -/** - * Thos method should be used to check if given string is valid iban object - * - * @method isValid - * @param {String} iban string - * @return {Boolean} true if it is valid IBAN - */ -Iban.isValid = function (iban) { - var i = new Iban(iban); - return i.isValid(); -}; - -/** - * Should be called to check if iban is correct - * - * @method isValid - * @returns {Boolean} true if it is, otherwise false - */ -Iban.prototype.isValid = function () { - return /^XE[0-9]{2}(ETH[0-9A-Z]{13}|[0-9A-Z]{30,31})$/.test(this._iban) && - mod9710(iso13616Prepare(this._iban)) === 1; -}; - -/** - * Should be called to check if iban number is direct - * - * @method isDirect - * @returns {Boolean} true if it is, otherwise false - */ -Iban.prototype.isDirect = function () { - return this._iban.length === 34 || this._iban.length === 35; -}; - -/** - * Should be called to check if iban number if indirect - * - * @method isIndirect - * @returns {Boolean} true if it is, otherwise false - */ -Iban.prototype.isIndirect = function () { - return this._iban.length === 20; -}; - -/** - * Should be called to get iban checksum - * Uses the mod-97-10 checksumming protocol (ISO/IEC 7064:2003) - * - * @method checksum - * @returns {String} checksum - */ -Iban.prototype.checksum = function () { - return this._iban.substr(2, 2); -}; - -/** - * Should be called to get institution identifier - * eg. XREG - * - * @method institution - * @returns {String} institution identifier - */ -Iban.prototype.institution = function () { - return this.isIndirect() ? this._iban.substr(7, 4) : ''; -}; - -/** - * Should be called to get client identifier within institution - * eg. GAVOFYORK - * - * @method client - * @returns {String} client identifier - */ -Iban.prototype.client = function () { - return this.isIndirect() ? this._iban.substr(11) : ''; -}; - -/** - * Should be called to get client direct address - * - * @method address - * @returns {String} client direct address - */ -Iban.prototype.address = function () { - if (this.isDirect()) { - var base36 = this._iban.substr(4); - var asBn = new BigNumber(base36, 36); - return padLeft(asBn.toString(16), 20); - } + /** + * Should be called to encode constructor params + * + * @method encodeConstructorParams + * @param {Array} abi + * @param {Array} constructor params + */ + var encodeConstructorParams = function (abi, params) { + return abi.filter(function (json) { + return json.type === 'constructor' && json.inputs.length === params.length; + }).map(function (json) { + return json.inputs.map(function (input) { + return input.type; + }); + }).map(function (types) { + return coder.encodeParams(types, params); + })[0] || ''; + }; - return ''; -}; + /** + * Should be called to add functions to contract object + * + * @method addFunctionsToContract + * @param {Contract} contract + * @param {Array} abi + */ + var addFunctionsToContract = function (contract) { + contract.abi.filter(function (json) { + return json.type === 'function'; + }).map(function (json) { + return new SolidityFunction(contract._eth, json, contract.address); + }).forEach(function (f) { + f.attachToContract(contract); + }); + }; -Iban.prototype.toString = function () { - return this._iban; -}; + /** + * Should be called to add events to contract object + * + * @method addEventsToContract + * @param {Contract} contract + * @param {Array} abi + */ + var addEventsToContract = function (contract) { + var events = contract.abi.filter(function (json) { + return json.type === 'event'; + }); -module.exports = Iban; + var All = new AllEvents(contract._eth._requestManager, events, contract.address); + All.attachToContract(contract); + events.map(function (json) { + return new SolidityEvent(contract._eth._requestManager, json, contract.address); + }).forEach(function (e) { + e.attachToContract(contract); + }); + }; -},{"bignumber.js":"bignumber.js"}],36:[function(require,module,exports){ -/* - This file is part of web3.js. - web3.js 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. + /** + * Should be called to check if the contract gets properly deployed on the blockchain. + * + * @method checkForContractAddress + * @param {Object} contract + * @param {Function} callback + * @returns {Undefined} + */ + var checkForContractAddress = function(contract, callback){ + var count = 0, + callbackFired = false; - web3.js 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. + // wait for receipt + var filter = contract._eth.filter('latest', function(e){ + if (!e && !callbackFired) { + count++; - You should have received a copy of the GNU Lesser General Public License - along with web3.js. If not, see . -*/ -/** @file ipcprovider.js - * @authors: - * Fabian Vogelsteller - * @date 2015 - */ + // stop watching after 50 blocks (timeout) + if (count > 50) { -"use strict"; + filter.stopWatching(function() {}); + callbackFired = true; -var utils = require('../utils/utils'); -var errors = require('./errors'); + if (callback) + callback(new Error('Contract transaction couldn\'t be found after 50 blocks')); + else + throw new Error('Contract transaction couldn\'t be found after 50 blocks'); -var IpcProvider = function (path, net) { - var _this = this; - this.responseCallbacks = {}; - this.path = path; - - this.connection = net.connect({path: this.path}); + } else { - this.connection.on('error', function(e){ - console.error('IPC Connection Error', e); - _this._timeout(); - }); + contract._eth.getTransactionReceipt(contract.transactionHash, function(e, receipt){ + if(receipt && receipt.blockHash && !callbackFired) { - this.connection.on('end', function(){ - _this._timeout(); - }); + contract._eth.getCode(receipt.contractAddress, function(e, code){ + /*jshint maxcomplexity: 6 */ + if(callbackFired || !code) + return; - // LISTEN FOR CONNECTION RESPONSES - this.connection.on('data', function(data) { - /*jshint maxcomplexity: 6 */ + filter.stopWatching(function() {}); + callbackFired = true; - _this._parseResponse(data.toString()).forEach(function(result){ + if(code.length > 3) { - var id = null; + // console.log('Contract code deployed!'); - // get the id which matches the returned id - if(utils.isArray(result)) { - result.forEach(function(load){ - if(_this.responseCallbacks[load.id]) - id = load.id; - }); - } else { - id = result.id; - } + contract.address = receipt.contractAddress; - // fire the callback - if(_this.responseCallbacks[id]) { - _this.responseCallbacks[id](null, result); - delete _this.responseCallbacks[id]; - } - }); - }); -}; + // attach events and methods again after we have + addFunctionsToContract(contract); + addEventsToContract(contract); -/** -Will parse the response and make an array out of it. + // call callback for the second time + if(callback) + callback(null, contract); -@method _parseResponse -@param {String} data -*/ -IpcProvider.prototype._parseResponse = function(data) { - var _this = this, - returnValues = []; - - // DE-CHUNKER - var dechunkedData = data - .replace(/\}[\n\r]?\{/g,'}|--|{') // }{ - .replace(/\}\][\n\r]?\[\{/g,'}]|--|[{') // }][{ - .replace(/\}[\n\r]?\[\{/g,'}|--|[{') // }[{ - .replace(/\}\][\n\r]?\{/g,'}]|--|{') // }]{ - .split('|--|'); - - dechunkedData.forEach(function(data){ + } else { + if(callback) + callback(new Error('The contract code couldn\'t be stored, please check your gas amount.')); + else + throw new Error('The contract code couldn\'t be stored, please check your gas amount.'); + } + }); + } + }); + } + } + }); + }; - // prepend the last chunk - if(_this.lastChunk) - data = _this.lastChunk + data; + /** + * Should be called to create new ContractFactory instance + * + * @method ContractFactory + * @param {Array} abi + */ + var ContractFactory = function (eth, abi) { + this.eth = eth; + this.abi = abi; + + /** + * Should be called to create new contract on a blockchain + * + * @method new + * @param {Any} contract constructor param1 (optional) + * @param {Any} contract constructor param2 (optional) + * @param {Object} contract transaction object (required) + * @param {Function} callback + * @returns {Contract} returns contract instance + */ + this.new = function () { + /*jshint maxcomplexity: 7 */ - var result = null; + var contract = new Contract(this.eth, this.abi); - try { - result = JSON.parse(data); + // parse arguments + var options = {}; // required! + var callback; - } catch(e) { + var args = Array.prototype.slice.call(arguments); + if (utils.isFunction(args[args.length - 1])) { + callback = args.pop(); + } - _this.lastChunk = data; + var last = args[args.length - 1]; + if (utils.isObject(last) && !utils.isArray(last)) { + options = args.pop(); + } - // start timeout to cancel all requests - clearTimeout(_this.lastChunkTimeout); - _this.lastChunkTimeout = setTimeout(function(){ - _this._timeout(); - throw errors.InvalidResponse(data); - }, 1000 * 15); + if (options.value > 0) { + var constructorAbi = abi.filter(function (json) { + return json.type === 'constructor' && json.inputs.length === args.length; + })[0] || {}; - return; + if (!constructorAbi.payable) { + throw new Error('Cannot send value to non-payable constructor'); + } } - // cancel timeout and set chunk to null - clearTimeout(_this.lastChunkTimeout); - _this.lastChunk = null; - - if(result) - returnValues.push(result); - }); + var bytes = encodeConstructorParams(this.abi, args); + options.data += bytes; - return returnValues; -}; + if (callback) { + // wait for the contract address and check if the code was deployed + this.eth.sendTransaction(options, function (err, hash) { + if (err) { + callback(err); + } else { + // add the transaction hash + contract.transactionHash = hash; -/** -Get the adds a callback to the responseCallbacks object, -which will be called if a response matching the response Id will arrive. + // call callback for the first time + callback(null, contract); -@method _addResponseCallback -*/ -IpcProvider.prototype._addResponseCallback = function(payload, callback) { - var id = payload.id || payload[0].id; - var method = payload.method || payload[0].method; - - this.responseCallbacks[id] = callback; - this.responseCallbacks[id].method = method; -}; - -/** -Timeout all requests when the end/error event is fired - -@method _timeout -*/ -IpcProvider.prototype._timeout = function() { - for(var key in this.responseCallbacks) { - if(this.responseCallbacks.hasOwnProperty(key)){ - this.responseCallbacks[key](errors.InvalidConnection('on IPC')); - delete this.responseCallbacks[key]; + checkForContractAddress(contract, callback); + } + }); + } else { + var hash = this.eth.sendTransaction(options); + // add the transaction hash + contract.transactionHash = hash; + checkForContractAddress(contract); } - } -}; + return contract; + }; -/** -Check if the current connection is still valid. - -@method isConnected -*/ -IpcProvider.prototype.isConnected = function() { - var _this = this; - - // try reconnect, when connection is gone - if(!_this.connection.writable) - _this.connection.connect({path: _this.path}); - - return !!this.connection.writable; -}; + this.new.getData = this.getData.bind(this); + }; -IpcProvider.prototype.send = function (payload) { + /** + * Should be called to create new ContractFactory + * + * @method contract + * @param {Array} abi + * @returns {ContractFactory} new contract factory + */ +//var contract = function (abi) { + //return new ContractFactory(abi); +//}; - if(this.connection.writeSync) { - var result; - // try reconnect, when connection is gone - if(!this.connection.writable) - this.connection.connect({path: this.path}); - var data = this.connection.writeSync(JSON.stringify(payload)); + /** + * Should be called to get access to existing contract on a blockchain + * + * @method at + * @param {Address} contract address (required) + * @param {Function} callback {optional) + * @returns {Contract} returns contract if no callback was passed, + * otherwise calls callback function (err, contract) + */ + ContractFactory.prototype.at = function (address, callback) { + var contract = new Contract(this.eth, this.abi, address); - try { - result = JSON.parse(data); - } catch(e) { - throw errors.InvalidResponse(data); - } + // this functions are not part of prototype, + // because we dont want to spoil the interface + addFunctionsToContract(contract); + addEventsToContract(contract); - return result; + if (callback) { + callback(null, contract); + } + return contract; + }; - } else { - throw new Error('You tried to send "'+ payload.method +'" synchronously. Synchronous requests are not supported by the IPC provider.'); - } -}; + /** + * Gets the data, which is data to deploy plus constructor params + * + * @method getData + */ + ContractFactory.prototype.getData = function () { + var options = {}; // required! + var args = Array.prototype.slice.call(arguments); -IpcProvider.prototype.sendAsync = function (payload, callback) { - // try reconnect, when connection is gone - if(!this.connection.writable) - this.connection.connect({path: this.path}); + var last = args[args.length - 1]; + if (utils.isObject(last) && !utils.isArray(last)) { + options = args.pop(); + } + var bytes = encodeConstructorParams(this.abi, args); + options.data += bytes; - this.connection.write(JSON.stringify(payload)); - this._addResponseCallback(payload, callback); -}; + return options.data; + }; -module.exports = IpcProvider; + /** + * Should be called to create new contract instance + * + * @method Contract + * @param {Array} abi + * @param {Address} contract address + */ + var Contract = function (eth, abi, address) { + this._eth = eth; + this.transactionHash = null; + this.address = address; + this.abi = abi; + }; + module.exports = ContractFactory; -},{"../utils/utils":22,"./errors":28}],37:[function(require,module,exports){ -/* + },{"../solidity/coder":7,"../utils/utils":22,"./allevents":25,"./event":29,"./function":33}],28:[function(require,module,exports){ + /* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify @@ -5193,79 +3432,36 @@ module.exports = IpcProvider; You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -/** @file jsonrpc.js - * @authors: - * Marek Kotewicz - * Aaron Kumavis - * @date 2015 - */ - -// Initialize Jsonrpc as a simple object with utility functions. -var Jsonrpc = { - messageId: 0 -}; - -/** - * Should be called to valid json create payload object - * - * @method toPayload - * @param {Function} method of jsonrpc call, required - * @param {Array} params, an array of method params, optional - * @returns {Object} valid jsonrpc payload object - */ -Jsonrpc.toPayload = function (method, params) { - if (!method) - console.error('jsonrpc method should be specified!'); - - // advance message ID - Jsonrpc.messageId++; + /** + * @file errors.js + * @author Marek Kotewicz + * @date 2015 + */ - return { - bech32: true, - jsonrpc: '2.0', - id: Jsonrpc.messageId, - method: method, - params: params || [] + module.exports = { + InvalidNumberOfSolidityArgs: function () { + return new Error('Invalid number of arguments to Solidity function'); + }, + InvalidNumberOfRPCParams: function () { + return new Error('Invalid number of input parameters to RPC method'); + }, + InvalidConnection: function (host){ + return new Error('CONNECTION ERROR: Couldn\'t connect to node '+ host +'.'); + }, + InvalidProvider: function () { + return new Error('Provider not set or invalid'); + }, + InvalidResponse: function (result){ + var message = !!result && !!result.error && !!result.error.message ? result.error.message : 'Invalid JSON RPC response: ' + JSON.stringify(result); + return new Error(message); + }, + ConnectionTimeout: function (ms){ + return new Error('CONNECTION TIMEOUT: timeout of ' + ms + ' ms achived'); + } }; -}; - -/** - * Should be called to check if jsonrpc response is valid - * - * @method isValidResponse - * @param {Object} - * @returns {Boolean} true if response is valid, otherwise false - */ -Jsonrpc.isValidResponse = function (response) { - return Array.isArray(response) ? response.every(validateSingleMessage) : validateSingleMessage(response); - - function validateSingleMessage(message){ - return !!message && - !message.error && - message.jsonrpc === '2.0' && - typeof message.id === 'number' && - message.result !== undefined; // only undefined is not valid json object - } -}; - -/** - * Should be called to create batch payload object - * - * @method toBatchPayload - * @param {Array} messages, an array of objects with method (required) and params (optional) fields - * @returns {Array} batch payload - */ -Jsonrpc.toBatchPayload = function (messages) { - return messages.map(function (message) { - return Jsonrpc.toPayload(message.method, message.params); - }); -}; - -module.exports = Jsonrpc; - -},{}],38:[function(require,module,exports){ -/* + },{}],29:[function(require,module,exports){ + /* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify @@ -5281,226 +3477,252 @@ module.exports = Jsonrpc; You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -/** - * @file method.js - * @author Marek Kotewicz - * @date 2015 - */ - -var utils = require('../utils/utils'); -var errors = require('./errors'); - -var Method = function (options) { - this.name = options.name; - this.call = options.call; - this.params = options.params || 0; - this.inputFormatter = options.inputFormatter; - this.outputFormatter = options.outputFormatter; - this.requestManager = null; -}; - -Method.prototype.setRequestManager = function (rm) { - this.requestManager = rm; -}; - -/** - * Should be used to determine name of the jsonrpc method based on arguments - * - * @method getCall - * @param {Array} arguments - * @return {String} name of jsonrpc method - */ -Method.prototype.getCall = function (args) { - return utils.isFunction(this.call) ? this.call(args) : this.call; -}; - -/** - * Should be used to extract callback from array of arguments. Modifies input param - * - * @method extractCallback - * @param {Array} arguments - * @return {Function|Null} callback, if exists - */ -Method.prototype.extractCallback = function (args) { - if (utils.isFunction(args[args.length - 1])) { - return args.pop(); // modify the args array! - } -}; - -/** - * Should be called to check if the number of arguments is correct - * - * @method validateArgs - * @param {Array} arguments - * @throws {Error} if it is not - */ -Method.prototype.validateArgs = function (args) { - if (args.length !== this.params) { - throw errors.InvalidNumberOfRPCParams(); - } -}; - -/** - * Should be called to format input args of method - * - * @method formatInput - * @param {Array} - * @return {Array} - */ -Method.prototype.formatInput = function (args) { - if (!this.inputFormatter) { - return args; - } + /** + * @file event.js + * @author Marek Kotewicz + * @date 2014 + */ - return this.inputFormatter.map(function (formatter, index) { - return formatter ? formatter(args[index]) : args[index]; - }); -}; - -/** - * Should be called to format output(result) of method - * - * @method formatOutput - * @param {Object} - * @return {Object} - */ -Method.prototype.formatOutput = function (result) { - return this.outputFormatter && result ? this.outputFormatter(result) : result; -}; - -/** - * Should create payload from given input args - * - * @method toPayload - * @param {Array} args - * @return {Object} - */ -Method.prototype.toPayload = function (args) { - var call = this.getCall(args); - var callback = this.extractCallback(args); - var params = this.formatInput(args); - this.validateArgs(params); - - return { - bech32: true, - method: call, - params: params, - callback: callback + var utils = require('../utils/utils'); + var coder = require('../solidity/coder'); + var formatters = require('./formatters'); + var sha3 = require('../utils/sha3'); + var Filter = require('./filter'); + var watches = require('./methods/watches'); + + /** + * This prototype should be used to create event filters + */ + var SolidityEvent = function (requestManager, json, address) { + this._requestManager = requestManager; + this._params = json.inputs; + this._name = utils.transformToFullName(json); + this._address = address; + this._anonymous = json.anonymous; }; -}; -Method.prototype.attachToObject = function (obj) { - var func = this.buildCall(); - func.call = this.call; // TODO!!! that's ugly. filter.js uses it - var name = this.name.split('.'); - if (name.length > 1) { - obj[name[0]] = obj[name[0]] || {}; - obj[name[0]][name[1]] = func; - } else { - obj[name[0]] = func; - } -}; + /** + * Should be used to get filtered param types + * + * @method types + * @param {Bool} decide if returned typed should be indexed + * @return {Array} array of types + */ + SolidityEvent.prototype.types = function (indexed) { + return this._params.filter(function (i) { + return i.indexed === indexed; + }).map(function (i) { + return i.type; + }); + }; -Method.prototype.buildCall = function() { - var method = this; - var send = function () { - var payload = method.toPayload(Array.prototype.slice.call(arguments)); - if (payload.callback) { - return method.requestManager.sendAsync(payload, function (err, result) { - payload.callback(err, method.formatOutput(result)); - }); + /** + * Should be used to get event display name + * + * @method displayName + * @return {String} event display name + */ + SolidityEvent.prototype.displayName = function () { + return utils.extractDisplayName(this._name); + }; + + /** + * Should be used to get event type name + * + * @method typeName + * @return {String} event type name + */ + SolidityEvent.prototype.typeName = function () { + return utils.extractTypeName(this._name); + }; + + /** + * Should be used to get event signature + * + * @method signature + * @return {String} event signature + */ + SolidityEvent.prototype.signature = function () { + return sha3(this._name); + }; + + /** + * Should be used to encode indexed params and options to one final object + * + * @method encode + * @param {Object} indexed + * @param {Object} options + * @return {Object} everything combined together and encoded + */ + SolidityEvent.prototype.encode = function (indexed, options) { + indexed = indexed || {}; + options = options || {}; + var result = {}; + + ['fromBlock', 'toBlock'].filter(function (f) { + return options[f] !== undefined; + }).forEach(function (f) { + result[f] = formatters.inputBlockNumberFormatter(options[f]); + }); + + result.topics = []; + + result.address = this._address; + if (!this._anonymous) { + result.topics.push('0x' + this.signature()); + } + + var indexedTopics = this._params.filter(function (i) { + return i.indexed === true; + }).map(function (i) { + var value = indexed[i.name]; + if (value === undefined || value === null) { + return null; } - return method.formatOutput(method.requestManager.send(payload)); + + if (utils.isArray(value)) { + return value.map(function (v) { + return '0x' + coder.encodeParam(i.type, v); + }); + } + return '0x' + coder.encodeParam(i.type, value); + }); + + result.topics = result.topics.concat(indexedTopics); + + return result; }; - send.request = this.request.bind(this); - return send; -}; - -/** - * Should be called to create pure JSONRPC request which can be used in batch request - * - * @method request - * @param {...} params - * @return {Object} jsonrpc request - */ -Method.prototype.request = function () { - var payload = this.toPayload(Array.prototype.slice.call(arguments)); - payload.format = this.formatOutput.bind(this); - return payload; -}; - -module.exports = Method; - -},{"../utils/utils":22,"./errors":28}],39:[function(require,module,exports){ -/* - This file is part of web3.js. - web3.js 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. + /** + * Should be used to decode indexed params and options + * + * @method decode + * @param {Object} data + * @return {Object} result object with decoded indexed && not indexed params + */ + SolidityEvent.prototype.decode = function (data) { - web3.js 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. + data.data = data.data || ''; + data.topics = data.topics || []; - You should have received a copy of the GNU Lesser General Public License - along with web3.js. If not, see . -*/ -/** @file db.js - * @authors: - * Marek Kotewicz - * @date 2015 - */ -var Method = require('../method'); + var argTopics = this._anonymous ? data.topics : data.topics.slice(1); + var indexedData = argTopics.map(function (topics) { return topics.slice(2); }).join(""); + var indexedParams = coder.decodeParams(this.types(true), indexedData); -var DB = function (web3) { - this._requestManager = web3._requestManager; + var notIndexedData = data.data.slice(2); + var notIndexedParams = coder.decodeParams(this.types(false), notIndexedData); - var self = this; - - methods().forEach(function(method) { - method.attachToObject(self); - method.setRequestManager(web3._requestManager); - }); -}; + var result = formatters.outputLogFormatter(data); + result.event = this.displayName(); + result.address = data.address; -var methods = function () { - var putString = new Method({ - name: 'putString', - call: 'db_putString', - params: 3 - }); + result.args = this._params.reduce(function (acc, current) { + acc[current.name] = current.indexed ? indexedParams.shift() : notIndexedParams.shift(); + return acc; + }, {}); - var getString = new Method({ - name: 'getString', - call: 'db_getString', - params: 2 - }); + delete result.data; + delete result.topics; - var putHex = new Method({ - name: 'putHex', - call: 'db_putHex', - params: 3 - }); + return result; + }; - var getHex = new Method({ - name: 'getHex', - call: 'db_getHex', - params: 2 - }); + /** + * Should be used to create new filter object from event + * + * @method execute + * @param {Object} indexed + * @param {Object} options + * @return {Object} filter object + */ + SolidityEvent.prototype.execute = function (indexed, options, callback) { - return [ - putString, getString, putHex, getHex - ]; -}; + if (utils.isFunction(arguments[arguments.length - 1])) { + callback = arguments[arguments.length - 1]; + if(arguments.length === 2) + options = null; + if(arguments.length === 1) { + options = null; + indexed = {}; + } + } + + var o = this.encode(indexed, options); + var formatter = this.decode.bind(this); + return new Filter(o, 'eth', this._requestManager, watches.eth(), formatter, callback); + }; + + /** + * Should be used to attach event to contract object + * + * @method attachToContract + * @param {Contract} + */ + SolidityEvent.prototype.attachToContract = function (contract) { + var execute = this.execute.bind(this); + var displayName = this.displayName(); + if (!contract[displayName]) { + contract[displayName] = execute; + } + contract[displayName][this.typeName()] = this.execute.bind(this, contract); + }; + + module.exports = SolidityEvent; + + + },{"../solidity/coder":7,"../utils/sha3":21,"../utils/utils":22,"./filter":31,"./formatters":32,"./methods/watches":43}],30:[function(require,module,exports){ + var formatters = require('./formatters'); + var utils = require('./../utils/utils'); + var Method = require('./method'); + var Property = require('./property'); + +// TODO: refactor, so the input params are not altered. +// it's necessary to make same 'extension' work with multiple providers + var extend = function (web3) { + /* jshint maxcomplexity:5 */ + var ex = function (extension) { + + var extendedObject; + if (extension.property) { + if (!web3[extension.property]) { + web3[extension.property] = {}; + } + extendedObject = web3[extension.property]; + } else { + extendedObject = web3; + } + + if (extension.methods) { + extension.methods.forEach(function (method) { + method.attachToObject(extendedObject); + method.setRequestManager(web3._requestManager); + }); + } + + if (extension.properties) { + extension.properties.forEach(function (property) { + property.attachToObject(extendedObject); + property.setRequestManager(web3._requestManager); + }); + } + }; + + ex.formatters = formatters; + ex.utils = utils; + ex.Method = Method; + ex.Property = Property; + + return ex; + }; + + + + module.exports = extend; -module.exports = DB; -},{"../method":38}],40:[function(require,module,exports){ -/* + },{"./../utils/utils":22,"./formatters":32,"./method":38,"./property":45}],31:[function(require,module,exports){ + /* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify @@ -5516,274 +3738,238 @@ module.exports = DB; You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -/** - * @file eth.js - * @author Marek Kotewicz - * @author Fabian Vogelsteller - * @date 2015 - */ - -"use strict"; - -var formatters = require('../formatters'); -var utils = require('../../utils/utils'); -var Method = require('../method'); -var Property = require('../property'); -var c = require('../../utils/config'); -var Contract = require('../contract'); -var watches = require('./watches'); -var Filter = require('../filter'); -var IsSyncing = require('../syncing'); -var namereg = require('../namereg'); -var Iban = require('../iban'); -var transfer = require('../transfer'); - -var blockCall = function (args) { - return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? "platon_getBlockByHash" : "platon_getBlockByNumber"; -}; - -var transactionFromBlockCall = function (args) { - return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'platon_getTransactionByBlockHashAndIndex' : 'platon_getTransactionByBlockNumberAndIndex'; -}; - -var getBlockTransactionCountCall = function (args) { - return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'platon_getBlockTransactionCountByHash' : 'platon_getBlockTransactionCountByNumber'; -}; - -function Eth(web3) { - this._requestManager = web3._requestManager; - - var self = this; - - methods().forEach(function(method) { - method.attachToObject(self); - method.setRequestManager(self._requestManager); - }); + /** @file filter.js + * @authors: + * Jeffrey Wilcke + * Marek Kotewicz + * Marian Oancea + * Fabian Vogelsteller + * Gav Wood + * @date 2014 + */ - properties().forEach(function(p) { - p.attachToObject(self); - p.setRequestManager(self._requestManager); - }); -} + var formatters = require('./formatters'); + var utils = require('../utils/utils'); -Object.defineProperty(Eth.prototype, 'defaultBlock', { - get: function () { - return c.defaultBlock; - }, - set: function (val) { - c.defaultBlock = val; - return val; - } -}); + /** + * Converts a given topic to a hex string, but also allows null values. + * + * @param {Mixed} value + * @return {String} + */ + var toTopic = function(value){ -Object.defineProperty(Eth.prototype, 'defaultAccount', { - get: function () { - return c.defaultAccount; - }, - set: function (val) { - c.defaultAccount = val; - return val; - } -}); + if(value === null || typeof value === 'undefined') + return null; -var methods = function () { - var getAddressHrp = new Method({ - name: 'getAddressHrp', - call: 'platon_getAddressHrp', - params: 0, - }); + value = String(value); - var getBalance = new Method({ - name: 'getBalance', - call: 'platon_getBalance', - params: 2, - inputFormatter: [formatters.inputAddressFormatter, formatters.inputDefaultBlockNumberFormatter], - outputFormatter: formatters.outputBigNumberFormatter - }); + if(value.indexOf('0x') === 0) + return value; + else + return utils.fromUtf8(value); + }; - var getStorageAt = new Method({ - name: 'getStorageAt', - call: 'platon_getStorageAt', - params: 3, - inputFormatter: [null, utils.toHex, formatters.inputDefaultBlockNumberFormatter] - }); +/// This method should be called on options object, to verify deprecated properties && lazy load dynamic ones +/// @param should be string or object +/// @returns options string or object + var getOptions = function (options, type) { + /*jshint maxcomplexity: 6 */ - var getCode = new Method({ - name: 'getCode', - call: 'platon_getCode', - params: 2, - inputFormatter: [formatters.inputAddressFormatter, formatters.inputDefaultBlockNumberFormatter] - }); + if (utils.isString(options)) { + return options; + } - var getBlock = new Method({ - name: 'getBlock', - call: blockCall, - params: 2, - inputFormatter: [formatters.inputBlockNumberFormatter, function (val) { return !!val; }], - outputFormatter: formatters.outputBlockFormatter - }); + options = options || {}; - var getBlockTransactionCount = new Method({ - name: 'getBlockTransactionCount', - call: getBlockTransactionCountCall, - params: 1, - inputFormatter: [formatters.inputBlockNumberFormatter], - outputFormatter: utils.toDecimal - }); - var getTransaction = new Method({ - name: 'getTransaction', - call: 'platon_getTransactionByHash', - params: 1, - outputFormatter: formatters.outputTransactionFormatter - }); + switch(type) { + case 'eth': - var getTransactionFromBlock = new Method({ - name: 'getTransactionFromBlock', - call: transactionFromBlockCall, - params: 2, - inputFormatter: [formatters.inputBlockNumberFormatter, utils.toHex], - outputFormatter: formatters.outputTransactionFormatter - }); + // make sure topics, get converted to hex + options.topics = options.topics || []; + options.topics = options.topics.map(function(topic){ + return (utils.isArray(topic)) ? topic.map(toTopic) : toTopic(topic); + }); + + return { + topics: options.topics, + from: options.from, + to: options.to, + address: options.address, + fromBlock: formatters.inputBlockNumberFormatter(options.fromBlock), + toBlock: formatters.inputBlockNumberFormatter(options.toBlock) + }; + case 'shh': + return options; + } + }; - var getTransactionReceipt = new Method({ - name: 'getTransactionReceipt', - call: 'platon_getTransactionReceipt', - params: 1, - outputFormatter: formatters.outputTransactionReceiptFormatter - }); + /** + Adds the callback and sets up the methods, to iterate over the results. - var getTransactionCount = new Method({ - name: 'getTransactionCount', - call: 'platon_getTransactionCount', - params: 2, - inputFormatter: [null, formatters.inputDefaultBlockNumberFormatter], - outputFormatter: utils.toDecimal - }); + @method getLogsAtStart + @param {Object} self + @param {function} callback + */ + var getLogsAtStart = function(self, callback){ + // call getFilterLogs for the first watch callback start + if (!utils.isString(self.options)) { + self.get(function (err, messages) { + // don't send all the responses to all the watches again... just to self one + if (err) { + callback(err); + } - var sendRawTransaction = new Method({ - name: 'sendRawTransaction', - call: 'platon_sendRawTransaction', - params: 1, - inputFormatter: [null] - }); + if(utils.isArray(messages)) { + messages.forEach(function (message) { + callback(null, message); + }); + } + }); + } + }; - var sendTransaction = new Method({ - name: 'sendTransaction', - call: 'platon_sendTransaction', - params: 1, - inputFormatter: [formatters.inputTransactionFormatter] - }); + /** + Adds the callback and sets up the methods, to iterate over the results. - var signTransaction = new Method({ - name: 'signTransaction', - call: 'platon_signTransaction', - params: 1, - inputFormatter: [formatters.inputTransactionFormatter] - }); + @method pollFilter + @param {Object} self + */ + var pollFilter = function(self) { - var sign = new Method({ - name: 'sign', - call: 'platon_sign', - params: 2, - inputFormatter: [formatters.inputAddressFormatter, null] - }); + var onMessage = function (error, messages) { + if (error) { + return self.callbacks.forEach(function (callback) { + callback(error); + }); + } - var call = new Method({ - name: 'call', - call: 'platon_call', - params: 2, - inputFormatter: [formatters.inputCallFormatter, formatters.inputDefaultBlockNumberFormatter] - }); + if(utils.isArray(messages)) { + messages.forEach(function (message) { + message = self.formatter ? self.formatter(message) : message; + self.callbacks.forEach(function (callback) { + callback(null, message); + }); + }); + } + }; - var estimateGas = new Method({ - name: 'estimateGas', - call: 'platon_estimateGas', - params: 1, - inputFormatter: [formatters.inputCallFormatter], - outputFormatter: utils.toDecimal - }); + self.requestManager.startPolling({ + method: self.implementation.poll.call, + params: [self.filterId], + }, self.filterId, onMessage, self.stopWatching.bind(self)); - var getPrepareQC = new Method({ - name: 'getPrepareQC', - call: 'platon_getPrepareQC', - params: 1, - }); + }; - var consensusStatus = new Method({ - name: 'consensusStatus', - call: 'debug_consensusStatus', - params: 0 - }); + var Filter = function (options, type, requestManager, methods, formatter, callback, filterCreationErrorCallback) { + var self = this; + var implementation = {}; + methods.forEach(function (method) { + method.setRequestManager(requestManager); + method.attachToObject(implementation); + }); + this.requestManager = requestManager; + this.options = getOptions(options, type); + this.implementation = implementation; + this.filterId = null; + this.callbacks = []; + this.getLogsCallbacks = []; + this.pollFilters = []; + this.formatter = formatter; + this.implementation.newFilter(this.options, function(error, id){ + if(error) { + self.callbacks.forEach(function(cb){ + cb(error); + }); + if (typeof filterCreationErrorCallback === 'function') { + filterCreationErrorCallback(error); + } + } else { + self.filterId = id; + + // check if there are get pending callbacks as a consequence + // of calling get() with filterId unassigned. + self.getLogsCallbacks.forEach(function (cb){ + self.get(cb); + }); + self.getLogsCallbacks = []; + + // get filter logs for the already existing watch calls + self.callbacks.forEach(function(cb){ + getLogsAtStart(self, cb); + }); + if(self.callbacks.length > 0) + pollFilter(self); + + // start to watch immediately + if(typeof callback === 'function') { + return self.watch(callback); + } + } + }); - return [ - getAddressHrp, - getBalance, - getStorageAt, - getCode, - getBlock, - getBlockTransactionCount, - getTransaction, - getTransactionFromBlock, - getTransactionReceipt, - getTransactionCount, - call, - estimateGas, - sendRawTransaction, - signTransaction, - sendTransaction, - sign, - consensusStatus, - getPrepareQC - ]; -}; + return this; + }; + Filter.prototype.watch = function (callback) { + this.callbacks.push(callback); -var properties = function () { - return [ - new Property({ - name: 'syncing', - getter: 'platon_syncing', - outputFormatter: formatters.outputSyncingFormatter - }), - new Property({ - name: 'gasPrice', - getter: 'platon_gasPrice', - outputFormatter: formatters.outputBigNumberFormatter - }), - new Property({ - name: 'accounts', - getter: 'platon_accounts' - }), - new Property({ - name: 'blockNumber', - getter: 'platon_blockNumber', - outputFormatter: utils.toDecimal - }), - new Property({ - name: 'protocolVersion', - getter: 'platon_protocolVersion' - }) - ]; -}; + if(this.filterId) { + getLogsAtStart(this, callback); + pollFilter(this); + } + + return this; + }; + + Filter.prototype.stopWatching = function (callback) { + this.requestManager.stopPolling(this.filterId); + this.callbacks = []; + // remove filter async + if (callback) { + this.implementation.uninstallFilter(this.filterId, callback); + } else { + return this.implementation.uninstallFilter(this.filterId); + } + }; -Eth.prototype.contract = function (abi) { - var factory = new Contract(this, abi); - return factory; -}; + Filter.prototype.get = function (callback) { + var self = this; + if (utils.isFunction(callback)) { + if (this.filterId === null) { + // If filterId is not set yet, call it back + // when newFilter() assigns it. + this.getLogsCallbacks.push(callback); + } else { + this.implementation.getLogs(this.filterId, function(err, res){ + if (err) { + callback(err); + } else { + callback(null, res.map(function (log) { + return self.formatter ? self.formatter(log) : log; + })); + } + }); + } + } else { + if (this.filterId === null) { + throw new Error('Filter ID Error: filter().get() can\'t be chained synchronous, please provide a callback for the get() method.'); + } + var logs = this.implementation.getLogs(this.filterId); + return logs.map(function (log) { + return self.formatter ? self.formatter(log) : log; + }); + } -Eth.prototype.filter = function (options, callback, filterCreationErrorCallback) { - return new Filter(options, 'eth', this._requestManager, watches.eth(), formatters.outputLogFormatter, callback, filterCreationErrorCallback); -}; + return this; + }; -Eth.prototype.isSyncing = function (callback) { - return new IsSyncing(this._requestManager, callback); -}; + module.exports = Filter; -module.exports = Eth; -},{"../../utils/config":19,"../../utils/utils":22,"../contract":27,"../filter":31,"../formatters":32,"../iban":35,"../method":38,"../namereg":44,"../property":45,"../syncing":47,"../transfer":48,"./watches":43}],41:[function(require,module,exports){ -/* + },{"../utils/utils":22,"./formatters":32}],32:[function(require,module,exports){ + /* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify @@ -5796,7793 +3982,9593 @@ module.exports = Eth; 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 web3.js. If not, see . -*/ -/** @file eth.js - * @authors: - * Marek Kotewicz - * @date 2015 - */ + You should have received a copy of the GNU Lesser General Public License + along with web3.js. If not, see . +*/ + /** + * @file formatters.js + * @author Marek Kotewicz + * @author Fabian Vogelsteller + * @date 2015 + */ + + 'use strict'; + + + var utils = require('../utils/utils'); + var config = require('../utils/config'); + var Iban = require('./iban'); + var segwit_addr = require('../utils/segwit_addr.js'); + + /** + * Should the format output to a big number + * + * @method outputBigNumberFormatter + * @param {String|Number|BigNumber} + * @returns {BigNumber} object + */ + var outputBigNumberFormatter = function (number) { + return utils.toBigNumber(number); + }; + + var isPredefinedBlockNumber = function (blockNumber) { + return blockNumber === 'latest' || blockNumber === 'pending' || blockNumber === 'earliest'; + }; + + var inputDefaultBlockNumberFormatter = function (blockNumber) { + if (blockNumber === undefined) { + return config.defaultBlock; + } + return inputBlockNumberFormatter(blockNumber); + }; + + var inputBlockNumberFormatter = function (blockNumber) { + if (blockNumber === undefined) { + return undefined; + } else if (isPredefinedBlockNumber(blockNumber)) { + return blockNumber; + } + return utils.toHex(blockNumber); + }; + + /** + * Formats the input of a transaction and converts all values to HEX + * + * @method inputCallFormatter + * @param {Object} transaction options + * @returns object + */ + var inputCallFormatter = function (options){ + + options.from = options.from || config.defaultAccount; + + if (options.from) { + options.from = inputAddressFormatter(options.from); + } + + if (options.to) { // it might be contract creation + options.to = inputAddressFormatter(options.to); + } + + ['gasPrice', 'gas', 'value', 'nonce'].filter(function (key) { + return options[key] !== undefined; + }).forEach(function(key){ + options[key] = utils.fromDecimal(options[key]); + }); + + return options; + }; + + /** + * Formats the input of a transaction and converts all values to HEX + * + * @method inputTransactionFormatter + * @param {Object} transaction options + * @returns object + */ + var inputTransactionFormatter = function (options){ + + options.from = options.from || config.defaultAccount; + options.from = inputAddressFormatter(options.from); + + if (options.to) { // it might be contract creation + options.to = inputAddressFormatter(options.to); + } + + ['gasPrice', 'gas', 'value', 'nonce'].filter(function (key) { + return options[key] !== undefined; + }).forEach(function(key){ + options[key] = utils.fromDecimal(options[key]); + }); + + return options; + }; + + /** + * Formats the output of a transaction to its proper values + * + * @method outputTransactionFormatter + * @param {Object} tx + * @returns {Object} + */ + var outputTransactionFormatter = function (tx){ + if(tx.blockNumber !== null) + tx.blockNumber = utils.toDecimal(tx.blockNumber); + if(tx.transactionIndex !== null) + tx.transactionIndex = utils.toDecimal(tx.transactionIndex); + tx.nonce = utils.toDecimal(tx.nonce); + tx.gas = utils.toDecimal(tx.gas); + tx.gasPrice = utils.toBigNumber(tx.gasPrice); + tx.value = utils.toBigNumber(tx.value); + return tx; + }; + + /** + * Formats the output of a transaction receipt to its proper values + * + * @method outputTransactionReceiptFormatter + * @param {Object} receipt + * @returns {Object} + */ + var outputTransactionReceiptFormatter = function (receipt){ + if(receipt.blockNumber !== null) + receipt.blockNumber = utils.toDecimal(receipt.blockNumber); + if(receipt.transactionIndex !== null) + receipt.transactionIndex = utils.toDecimal(receipt.transactionIndex); + receipt.cumulativeGasUsed = utils.toDecimal(receipt.cumulativeGasUsed); + receipt.gasUsed = utils.toDecimal(receipt.gasUsed); + + if(utils.isArray(receipt.logs)) { + receipt.logs = receipt.logs.map(function(log){ + return outputLogFormatter(log); + }); + } + + return receipt; + }; + + /** + * Formats the output of a block to its proper values + * + * @method outputBlockFormatter + * @param {Object} block + * @returns {Object} + */ + var outputBlockFormatter = function(block) { + + // transform to number + block.gasLimit = utils.toDecimal(block.gasLimit); + block.gasUsed = utils.toDecimal(block.gasUsed); + block.size = utils.toDecimal(block.size); + block.timestamp = utils.toDecimal(block.timestamp); + if(block.number !== null) + block.number = utils.toDecimal(block.number); + + if (utils.isArray(block.transactions)) { + block.transactions.forEach(function(item){ + if(!utils.isString(item)) + return outputTransactionFormatter(item); + }); + } + + return block; + }; + + /** + * Formats the output of a log + * + * @method outputLogFormatter + * @param {Object} log object + * @returns {Object} log + */ + var outputLogFormatter = function(log) { + if(log.blockNumber) + log.blockNumber = utils.toDecimal(log.blockNumber); + if(log.transactionIndex) + log.transactionIndex = utils.toDecimal(log.transactionIndex); + if(log.logIndex) + log.logIndex = utils.toDecimal(log.logIndex); + + return log; + }; + + /** + * Formats the input of a whisper post and converts all values to HEX + * + * @method inputPostFormatter + * @param {Object} transaction object + * @returns {Object} + */ + var inputPostFormatter = function(post) { + + // post.payload = utils.toHex(post.payload); + post.ttl = utils.fromDecimal(post.ttl); + post.workToProve = utils.fromDecimal(post.workToProve); + post.priority = utils.fromDecimal(post.priority); + + // fallback + if (!utils.isArray(post.topics)) { + post.topics = post.topics ? [post.topics] : []; + } + + // format the following options + post.topics = post.topics.map(function(topic){ + // convert only if not hex + return (topic.indexOf('0x') === 0) ? topic : utils.fromUtf8(topic); + }); + + return post; + }; + + /** + * Formats the output of a received post message + * + * @method outputPostFormatter + * @param {Object} + * @returns {Object} + */ + var outputPostFormatter = function(post){ + + post.expiry = utils.toDecimal(post.expiry); + post.sent = utils.toDecimal(post.sent); + post.ttl = utils.toDecimal(post.ttl); + post.workProved = utils.toDecimal(post.workProved); + // post.payloadRaw = post.payload; + // post.payload = utils.toAscii(post.payload); + + // if (utils.isJson(post.payload)) { + // post.payload = JSON.parse(post.payload); + // } + + // format the following options + if (!post.topics) { + post.topics = []; + } + post.topics = post.topics.map(function(topic){ + return utils.toAscii(topic); + }); + + return post; + }; + + var inputAddressFormatter = function (address) { + if (utils.isBech32Address(address)) { + return address; + } else if (utils.isAddress(address)) { + return '0x' + address.toLowerCase().replace('0x', ''); + } + throw new Error('invalid address'); + }; + + /* +var inputAddressFormatter = function (address) { + var iban = new Iban(address); + hrf + if (iban.isValid() && iban.isDirect()) { + return '0x' + iban.address(); + } else if (utils.isStrictAddress(address)) { + return address; + } else if (utils.isAddress(address)) { + return '0x' + address; + } + throw new Error('invalid address'); +};*/ + + var outputSyncingFormatter = function(result) { + if (!result) { + return result; + } + + result.startingBlock = utils.toDecimal(result.startingBlock); + result.currentBlock = utils.toDecimal(result.currentBlock); + result.highestBlock = utils.toDecimal(result.highestBlock); + if (result.knownStates) { + result.knownStates = utils.toDecimal(result.knownStates); + result.pulledStates = utils.toDecimal(result.pulledStates); + } + + return result; + }; + + module.exports = { + inputDefaultBlockNumberFormatter: inputDefaultBlockNumberFormatter, + inputBlockNumberFormatter: inputBlockNumberFormatter, + inputCallFormatter: inputCallFormatter, + inputTransactionFormatter: inputTransactionFormatter, + inputAddressFormatter: inputAddressFormatter, + inputPostFormatter: inputPostFormatter, + outputBigNumberFormatter: outputBigNumberFormatter, + outputTransactionFormatter: outputTransactionFormatter, + outputTransactionReceiptFormatter: outputTransactionReceiptFormatter, + outputBlockFormatter: outputBlockFormatter, + outputLogFormatter: outputLogFormatter, + outputPostFormatter: outputPostFormatter, + outputSyncingFormatter: outputSyncingFormatter + }; + + + },{"../utils/config":19,"../utils/segwit_addr.js":20,"../utils/utils":22,"./iban":35}],33:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** + * @file function.js + * @author Marek Kotewicz + * @date 2015 + */ + + var coder = require('../solidity/coder'); + var utils = require('../utils/utils'); + var errors = require('./errors'); + var formatters = require('./formatters'); + var sha3 = require('../utils/sha3'); + + /** + * This prototype should be used to call/sendTransaction to solidity functions + */ + var SolidityFunction = function (eth, json, address) { + this._eth = eth; + this._inputTypes = json.inputs.map(function (i) { + return i.type; + }); + this._outputTypes = json.outputs.map(function (i) { + return i.type; + }); + this._constant = json.constant; + this._payable = json.payable; + this._name = utils.transformToFullName(json); + this._address = address; + }; + + SolidityFunction.prototype.extractCallback = function (args) { + if (utils.isFunction(args[args.length - 1])) { + return args.pop(); // modify the args array! + } + }; + + SolidityFunction.prototype.extractDefaultBlock = function (args) { + if (args.length > this._inputTypes.length && !utils.isObject(args[args.length -1])) { + return formatters.inputDefaultBlockNumberFormatter(args.pop()); // modify the args array! + } + }; + + /** + * Should be called to check if the number of arguments is correct + * + * @method validateArgs + * @param {Array} arguments + * @throws {Error} if it is not + */ + SolidityFunction.prototype.validateArgs = function (args) { + var inputArgs = args.filter(function (a) { + // filter the options object but not arguments that are arrays + return !( (utils.isObject(a) === true) && + (utils.isArray(a) === false) && + (utils.isBigNumber(a) === false) + ); + }); + if (inputArgs.length !== this._inputTypes.length) { + throw errors.InvalidNumberOfSolidityArgs(); + } + }; + + /** + * Should be used to create payload from arguments + * + * @method toPayload + * @param {Array} solidity function params + * @param {Object} optional payload options + */ + SolidityFunction.prototype.toPayload = function (args) { + var options = {}; + if (args.length > this._inputTypes.length && utils.isObject(args[args.length -1])) { + options = args[args.length - 1]; + } + this.validateArgs(args); + options.to = this._address; + options.data = '0x' + this.signature() + coder.encodeParams(this._inputTypes, args); + return options; + }; + + /** + * Should be used to get function signature + * + * @method signature + * @return {String} function signature + */ + SolidityFunction.prototype.signature = function () { + return sha3(this._name).slice(0, 8); + }; + + + SolidityFunction.prototype.unpackOutput = function (output) { + if (!output) { + return; + } + + output = output.length >= 2 ? output.slice(2) : output; + var result = coder.decodeParams(this._outputTypes, output); + return result.length === 1 ? result[0] : result; + }; + + /** + * Calls a contract function. + * + * @method call + * @param {...Object} Contract function arguments + * @param {function} If the last argument is a function, the contract function + * call will be asynchronous, and the callback will be passed the + * error and result. + * @return {String} output bytes + */ + SolidityFunction.prototype.call = function () { + var args = Array.prototype.slice.call(arguments).filter(function (a) {return a !== undefined; }); + var callback = this.extractCallback(args); + var defaultBlock = this.extractDefaultBlock(args); + var payload = this.toPayload(args); + + + if (!callback) { + var output = this._eth.call(payload, defaultBlock); + return this.unpackOutput(output); + } + + var self = this; + this._eth.call(payload, defaultBlock, function (error, output) { + if (error) return callback(error, null); + + var unpacked = null; + try { + unpacked = self.unpackOutput(output); + } + catch (e) { + error = e; + } + + callback(error, unpacked); + }); + }; + + /** + * Should be used to sendTransaction to solidity function + * + * @method sendTransaction + */ + SolidityFunction.prototype.sendTransaction = function () { + var args = Array.prototype.slice.call(arguments).filter(function (a) {return a !== undefined; }); + var callback = this.extractCallback(args); + var payload = this.toPayload(args); + + if (payload.value > 0 && !this._payable) { + throw new Error('Cannot send value to non-payable function'); + } + + if (!callback) { + return this._eth.sendTransaction(payload); + } + + this._eth.sendTransaction(payload, callback); + }; + + /** + * Should be used to estimateGas of solidity function + * + * @method estimateGas + */ + SolidityFunction.prototype.estimateGas = function () { + var args = Array.prototype.slice.call(arguments); + var callback = this.extractCallback(args); + var payload = this.toPayload(args); + + if (!callback) { + return this._eth.estimateGas(payload); + } + + this._eth.estimateGas(payload, callback); + }; + + /** + * Return the encoded data of the call + * + * @method getData + * @return {String} the encoded data + */ + SolidityFunction.prototype.getData = function () { + var args = Array.prototype.slice.call(arguments); + var payload = this.toPayload(args); + + return payload.data; + }; + + /** + * Should be used to get function display name + * + * @method displayName + * @return {String} display name of the function + */ + SolidityFunction.prototype.displayName = function () { + return utils.extractDisplayName(this._name); + }; + + /** + * Should be used to get function type name + * + * @method typeName + * @return {String} type name of the function + */ + SolidityFunction.prototype.typeName = function () { + return utils.extractTypeName(this._name); + }; + + /** + * Should be called to get rpc requests from solidity function + * + * @method request + * @returns {Object} + */ + SolidityFunction.prototype.request = function () { + var args = Array.prototype.slice.call(arguments); + var callback = this.extractCallback(args); + var payload = this.toPayload(args); + var format = this.unpackOutput.bind(this); + + return { + method: this._constant ? 'platon_call' : 'platon_sendTransaction', + callback: callback, + params: [payload], + format: format + }; + }; + + /** + * Should be called to execute function + * + * @method execute + */ + SolidityFunction.prototype.execute = function () { + var transaction = !this._constant; + + // send transaction + if (transaction) { + return this.sendTransaction.apply(this, Array.prototype.slice.call(arguments)); + } + + // call + return this.call.apply(this, Array.prototype.slice.call(arguments)); + }; + + /** + * Should be called to attach function to contract + * + * @method attachToContract + * @param {Contract} + */ + SolidityFunction.prototype.attachToContract = function (contract) { + var execute = this.execute.bind(this); + execute.request = this.request.bind(this); + execute.call = this.call.bind(this); + execute.sendTransaction = this.sendTransaction.bind(this); + execute.estimateGas = this.estimateGas.bind(this); + execute.getData = this.getData.bind(this); + var displayName = this.displayName(); + if (!contract[displayName]) { + contract[displayName] = execute; + } + contract[displayName][this.typeName()] = execute; // circular!!!! + }; + + module.exports = SolidityFunction; + + },{"../solidity/coder":7,"../utils/sha3":21,"../utils/utils":22,"./errors":28,"./formatters":32}],34:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** @file httpprovider.js + * @authors: + * Marek Kotewicz + * Marian Oancea + * Fabian Vogelsteller + * @date 2015 + */ + + var errors = require('./errors'); + +// workaround to use httpprovider in different envs + +// browser + if (typeof window !== 'undefined' && window.XMLHttpRequest) { + XMLHttpRequest = window.XMLHttpRequest; // jshint ignore: line +// node + } else { + XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; // jshint ignore: line + } + + var XHR2 = require('xhr2'); // jshint ignore: line + + /** + * HttpProvider should be used to send rpc calls over http + */ + var HttpProvider = function (host, timeout, user, password, headers) { + this.host = host || 'http://localhost:8545'; + this.timeout = timeout || 0; + this.user = user; + this.password = password; + this.headers = headers; + }; + + /** + * Should be called to prepare new XMLHttpRequest + * + * @method prepareRequest + * @param {Boolean} true if request should be async + * @return {XMLHttpRequest} object + */ + HttpProvider.prototype.prepareRequest = function (async) { + var request; + + if (async) { + request = new XHR2(); + request.timeout = this.timeout; + } else { + request = new XMLHttpRequest(); + } + + request.open('POST', this.host, async); + if (this.user && this.password) { + var auth = 'Basic ' + new Buffer(this.user + ':' + this.password).toString('base64'); + request.setRequestHeader('Authorization', auth); + } request.setRequestHeader('Content-Type', 'application/json'); + if(this.headers) { + this.headers.forEach(function(header) { + request.setRequestHeader(header.name, header.value); + }); + } + return request; + }; + + /** + * Should be called to make sync request + * + * @method send + * @param {Object} payload + * @return {Object} result + */ + HttpProvider.prototype.send = function (payload) { + var request = this.prepareRequest(false); + + try { + request.send(JSON.stringify(payload)); + } catch (error) { + throw errors.InvalidConnection(this.host); + } + + var result = request.responseText; + + try { + result = JSON.parse(result); + } catch (e) { + throw errors.InvalidResponse(request.responseText); + } + + return result; + }; + + /** + * Should be used to make async request + * + * @method sendAsync + * @param {Object} payload + * @param {Function} callback triggered on end with (err, result) + */ + HttpProvider.prototype.sendAsync = function (payload, callback) { + var request = this.prepareRequest(true); + + request.onreadystatechange = function () { + if (request.readyState === 4 && request.timeout !== 1) { + var result = request.responseText; + var error = null; + + try { + result = JSON.parse(result); + } catch (e) { + error = errors.InvalidResponse(request.responseText); + } + + callback(error, result); + } + }; + + request.ontimeout = function () { + callback(errors.ConnectionTimeout(this.timeout)); + }; + + try { + request.send(JSON.stringify(payload)); + } catch (error) { + callback(errors.InvalidConnection(this.host)); + } + }; + + /** + * Synchronously tries to make Http request + * + * @method isConnected + * @return {Boolean} returns true if request haven't failed. Otherwise false + */ + HttpProvider.prototype.isConnected = function () { + try { + this.send({ + id: 9999999999, + jsonrpc: '2.0', + method: 'net_listening', + params: [] + }); + return true; + } catch (e) { + return false; + } + }; + + module.exports = HttpProvider; + + },{"./errors":28,"xhr2":85,"xmlhttprequest":18}],35:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** + * @file iban.js + * @author Marek Kotewicz + * @date 2015 + */ + + var BigNumber = require('bignumber.js'); + + var padLeft = function (string, bytes) { + var result = string; + while (result.length < bytes * 2) { + result = '0' + result; + } + return result; + }; + + /** + * Prepare an IBAN for mod 97 computation by moving the first 4 chars to the end and transforming the letters to + * numbers (A = 10, B = 11, ..., Z = 35), as specified in ISO13616. + * + * @method iso13616Prepare + * @param {String} iban the IBAN + * @returns {String} the prepared IBAN + */ + var iso13616Prepare = function (iban) { + var A = 'A'.charCodeAt(0); + var Z = 'Z'.charCodeAt(0); + + iban = iban.toUpperCase(); + iban = iban.substr(4) + iban.substr(0,4); + + return iban.split('').map(function(n){ + var code = n.charCodeAt(0); + if (code >= A && code <= Z){ + // A = 10, B = 11, ... Z = 35 + return code - A + 10; + } else { + return n; + } + }).join(''); + }; + + /** + * Calculates the MOD 97 10 of the passed IBAN as specified in ISO7064. + * + * @method mod9710 + * @param {String} iban + * @returns {Number} + */ + var mod9710 = function (iban) { + var remainder = iban, + block; + + while (remainder.length > 2){ + block = remainder.slice(0, 9); + remainder = parseInt(block, 10) % 97 + remainder.slice(block.length); + } + + return parseInt(remainder, 10) % 97; + }; + + /** + * This prototype should be used to create iban object from iban correct string + * + * @param {String} iban + */ + var Iban = function (iban) { + this._iban = iban; + }; + + /** + * This method should be used to create iban object from ethereum address + * + * @method fromAddress + * @param {String} address + * @return {Iban} the IBAN object + */ + Iban.fromAddress = function (address) { + var asBn = new BigNumber(address, 16); + var base36 = asBn.toString(36); + var padded = padLeft(base36, 15); + return Iban.fromBban(padded.toUpperCase()); + }; + + /** + * Convert the passed BBAN to an IBAN for this country specification. + * Please note that "generation of the IBAN shall be the exclusive responsibility of the bank/branch servicing the account". + * This method implements the preferred algorithm described in http://en.wikipedia.org/wiki/International_Bank_Account_Number#Generating_IBAN_check_digits + * + * @method fromBban + * @param {String} bban the BBAN to convert to IBAN + * @returns {Iban} the IBAN object + */ + Iban.fromBban = function (bban) { + var countryCode = 'XE'; + + var remainder = mod9710(iso13616Prepare(countryCode + '00' + bban)); + var checkDigit = ('0' + (98 - remainder)).slice(-2); + + return new Iban(countryCode + checkDigit + bban); + }; + + /** + * Should be used to create IBAN object for given institution and identifier + * + * @method createIndirect + * @param {Object} options, required options are "institution" and "identifier" + * @return {Iban} the IBAN object + */ + Iban.createIndirect = function (options) { + return Iban.fromBban('ETH' + options.institution + options.identifier); + }; + + /** + * Thos method should be used to check if given string is valid iban object + * + * @method isValid + * @param {String} iban string + * @return {Boolean} true if it is valid IBAN + */ + Iban.isValid = function (iban) { + var i = new Iban(iban); + return i.isValid(); + }; + + /** + * Should be called to check if iban is correct + * + * @method isValid + * @returns {Boolean} true if it is, otherwise false + */ + Iban.prototype.isValid = function () { + return /^XE[0-9]{2}(ETH[0-9A-Z]{13}|[0-9A-Z]{30,31})$/.test(this._iban) && + mod9710(iso13616Prepare(this._iban)) === 1; + }; + + /** + * Should be called to check if iban number is direct + * + * @method isDirect + * @returns {Boolean} true if it is, otherwise false + */ + Iban.prototype.isDirect = function () { + return this._iban.length === 34 || this._iban.length === 35; + }; + + /** + * Should be called to check if iban number if indirect + * + * @method isIndirect + * @returns {Boolean} true if it is, otherwise false + */ + Iban.prototype.isIndirect = function () { + return this._iban.length === 20; + }; + + /** + * Should be called to get iban checksum + * Uses the mod-97-10 checksumming protocol (ISO/IEC 7064:2003) + * + * @method checksum + * @returns {String} checksum + */ + Iban.prototype.checksum = function () { + return this._iban.substr(2, 2); + }; + + /** + * Should be called to get institution identifier + * eg. XREG + * + * @method institution + * @returns {String} institution identifier + */ + Iban.prototype.institution = function () { + return this.isIndirect() ? this._iban.substr(7, 4) : ''; + }; + + /** + * Should be called to get client identifier within institution + * eg. GAVOFYORK + * + * @method client + * @returns {String} client identifier + */ + Iban.prototype.client = function () { + return this.isIndirect() ? this._iban.substr(11) : ''; + }; + + /** + * Should be called to get client direct address + * + * @method address + * @returns {String} client direct address + */ + Iban.prototype.address = function () { + if (this.isDirect()) { + var base36 = this._iban.substr(4); + var asBn = new BigNumber(base36, 36); + return padLeft(asBn.toString(16), 20); + } + + return ''; + }; + + Iban.prototype.toString = function () { + return this._iban; + }; + + module.exports = Iban; + + + },{"bignumber.js":"bignumber.js"}],36:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** @file ipcprovider.js + * @authors: + * Fabian Vogelsteller + * @date 2015 + */ + + "use strict"; + + var utils = require('../utils/utils'); + var errors = require('./errors'); + + + var IpcProvider = function (path, net) { + var _this = this; + this.responseCallbacks = {}; + this.path = path; + + this.connection = net.connect({path: this.path}); + + this.connection.on('error', function(e){ + console.error('IPC Connection Error', e); + _this._timeout(); + }); + + this.connection.on('end', function(){ + _this._timeout(); + }); + + + // LISTEN FOR CONNECTION RESPONSES + this.connection.on('data', function(data) { + /*jshint maxcomplexity: 6 */ + + _this._parseResponse(data.toString()).forEach(function(result){ + + var id = null; + + // get the id which matches the returned id + if(utils.isArray(result)) { + result.forEach(function(load){ + if(_this.responseCallbacks[load.id]) + id = load.id; + }); + } else { + id = result.id; + } + + // fire the callback + if(_this.responseCallbacks[id]) { + _this.responseCallbacks[id](null, result); + delete _this.responseCallbacks[id]; + } + }); + }); + }; + + /** + Will parse the response and make an array out of it. + + @method _parseResponse + @param {String} data + */ + IpcProvider.prototype._parseResponse = function(data) { + var _this = this, + returnValues = []; + + // DE-CHUNKER + var dechunkedData = data + .replace(/\}[\n\r]?\{/g,'}|--|{') // }{ + .replace(/\}\][\n\r]?\[\{/g,'}]|--|[{') // }][{ + .replace(/\}[\n\r]?\[\{/g,'}|--|[{') // }[{ + .replace(/\}\][\n\r]?\{/g,'}]|--|{') // }]{ + .split('|--|'); + + dechunkedData.forEach(function(data){ + + // prepend the last chunk + if(_this.lastChunk) + data = _this.lastChunk + data; + + var result = null; + + try { + result = JSON.parse(data); + + } catch(e) { + + _this.lastChunk = data; + + // start timeout to cancel all requests + clearTimeout(_this.lastChunkTimeout); + _this.lastChunkTimeout = setTimeout(function(){ + _this._timeout(); + throw errors.InvalidResponse(data); + }, 1000 * 15); + + return; + } + + // cancel timeout and set chunk to null + clearTimeout(_this.lastChunkTimeout); + _this.lastChunk = null; + + if(result) + returnValues.push(result); + }); + + return returnValues; + }; + + + /** + Get the adds a callback to the responseCallbacks object, + which will be called if a response matching the response Id will arrive. + + @method _addResponseCallback + */ + IpcProvider.prototype._addResponseCallback = function(payload, callback) { + var id = payload.id || payload[0].id; + var method = payload.method || payload[0].method; + + this.responseCallbacks[id] = callback; + this.responseCallbacks[id].method = method; + }; + + /** + Timeout all requests when the end/error event is fired + + @method _timeout + */ + IpcProvider.prototype._timeout = function() { + for(var key in this.responseCallbacks) { + if(this.responseCallbacks.hasOwnProperty(key)){ + this.responseCallbacks[key](errors.InvalidConnection('on IPC')); + delete this.responseCallbacks[key]; + } + } + }; + + + /** + Check if the current connection is still valid. + + @method isConnected + */ + IpcProvider.prototype.isConnected = function() { + var _this = this; + + // try reconnect, when connection is gone + if(!_this.connection.writable) + _this.connection.connect({path: _this.path}); + + return !!this.connection.writable; + }; + + IpcProvider.prototype.send = function (payload) { + + if(this.connection.writeSync) { + var result; + + // try reconnect, when connection is gone + if(!this.connection.writable) + this.connection.connect({path: this.path}); + + var data = this.connection.writeSync(JSON.stringify(payload)); + + try { + result = JSON.parse(data); + } catch(e) { + throw errors.InvalidResponse(data); + } + + return result; + + } else { + throw new Error('You tried to send "'+ payload.method +'" synchronously. Synchronous requests are not supported by the IPC provider.'); + } + }; + + IpcProvider.prototype.sendAsync = function (payload, callback) { + // try reconnect, when connection is gone + if(!this.connection.writable) + this.connection.connect({path: this.path}); + + + this.connection.write(JSON.stringify(payload)); + this._addResponseCallback(payload, callback); + }; + + module.exports = IpcProvider; + + + },{"../utils/utils":22,"./errors":28}],37:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** @file jsonrpc.js + * @authors: + * Marek Kotewicz + * Aaron Kumavis + * @date 2015 + */ + +// Initialize Jsonrpc as a simple object with utility functions. + var Jsonrpc = { + messageId: 0 + }; + + /** + * Should be called to valid json create payload object + * + * @method toPayload + * @param {Function} method of jsonrpc call, required + * @param {Array} params, an array of method params, optional + * @returns {Object} valid jsonrpc payload object + */ + Jsonrpc.toPayload = function (method, params) { + if (!method) + console.error('jsonrpc method should be specified!'); + + // advance message ID + Jsonrpc.messageId++; + + return { + jsonrpc: '2.0', + id: Jsonrpc.messageId, + method: method, + params: params || [] + }; + }; + + /** + * Should be called to check if jsonrpc response is valid + * + * @method isValidResponse + * @param {Object} + * @returns {Boolean} true if response is valid, otherwise false + */ + Jsonrpc.isValidResponse = function (response) { + return Array.isArray(response) ? response.every(validateSingleMessage) : validateSingleMessage(response); + + function validateSingleMessage(message){ + return !!message && + !message.error && + message.jsonrpc === '2.0' && + typeof message.id === 'number' && + message.result !== undefined; // only undefined is not valid json object + } + }; + + /** + * Should be called to create batch payload object + * + * @method toBatchPayload + * @param {Array} messages, an array of objects with method (required) and params (optional) fields + * @returns {Array} batch payload + */ + Jsonrpc.toBatchPayload = function (messages) { + return messages.map(function (message) { + return Jsonrpc.toPayload(message.method, message.params); + }); + }; + + module.exports = Jsonrpc; + + + },{}],38:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** + * @file method.js + * @author Marek Kotewicz + * @date 2015 + */ + + var utils = require('../utils/utils'); + var errors = require('./errors'); + + var Method = function (options) { + this.name = options.name; + this.call = options.call; + this.params = options.params || 0; + this.inputFormatter = options.inputFormatter; + this.outputFormatter = options.outputFormatter; + this.requestManager = null; + }; + + Method.prototype.setRequestManager = function (rm) { + this.requestManager = rm; + }; + + /** + * Should be used to determine name of the jsonrpc method based on arguments + * + * @method getCall + * @param {Array} arguments + * @return {String} name of jsonrpc method + */ + Method.prototype.getCall = function (args) { + return utils.isFunction(this.call) ? this.call(args) : this.call; + }; + + /** + * Should be used to extract callback from array of arguments. Modifies input param + * + * @method extractCallback + * @param {Array} arguments + * @return {Function|Null} callback, if exists + */ + Method.prototype.extractCallback = function (args) { + if (utils.isFunction(args[args.length - 1])) { + return args.pop(); // modify the args array! + } + }; + + /** + * Should be called to check if the number of arguments is correct + * + * @method validateArgs + * @param {Array} arguments + * @throws {Error} if it is not + */ + Method.prototype.validateArgs = function (args) { + if (args.length !== this.params) { + throw errors.InvalidNumberOfRPCParams(); + } + }; + + /** + * Should be called to format input args of method + * + * @method formatInput + * @param {Array} + * @return {Array} + */ + Method.prototype.formatInput = function (args) { + if (!this.inputFormatter) { + return args; + } + + return this.inputFormatter.map(function (formatter, index) { + return formatter ? formatter(args[index]) : args[index]; + }); + }; + + /** + * Should be called to format output(result) of method + * + * @method formatOutput + * @param {Object} + * @return {Object} + */ + Method.prototype.formatOutput = function (result) { + return this.outputFormatter && result ? this.outputFormatter(result) : result; + }; + + /** + * Should create payload from given input args + * + * @method toPayload + * @param {Array} args + * @return {Object} + */ + Method.prototype.toPayload = function (args) { + var call = this.getCall(args); + var callback = this.extractCallback(args); + var params = this.formatInput(args); + this.validateArgs(params); + + return { + method: call, + params: params, + callback: callback + }; + }; + + Method.prototype.attachToObject = function (obj) { + var func = this.buildCall(); + func.call = this.call; // TODO!!! that's ugly. filter.js uses it + var name = this.name.split('.'); + if (name.length > 1) { + obj[name[0]] = obj[name[0]] || {}; + obj[name[0]][name[1]] = func; + } else { + obj[name[0]] = func; + } + }; + + Method.prototype.buildCall = function() { + var method = this; + var send = function () { + var payload = method.toPayload(Array.prototype.slice.call(arguments)); + if (payload.callback) { + return method.requestManager.sendAsync(payload, function (err, result) { + payload.callback(err, method.formatOutput(result)); + }); + } + return method.formatOutput(method.requestManager.send(payload)); + }; + send.request = this.request.bind(this); + return send; + }; + + /** + * Should be called to create pure JSONRPC request which can be used in batch request + * + * @method request + * @param {...} params + * @return {Object} jsonrpc request + */ + Method.prototype.request = function () { + var payload = this.toPayload(Array.prototype.slice.call(arguments)); + payload.format = this.formatOutput.bind(this); + return payload; + }; + + module.exports = Method; + + },{"../utils/utils":22,"./errors":28}],39:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** @file db.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + + var Method = require('../method'); + + var DB = function (web3) { + this._requestManager = web3._requestManager; + + var self = this; + + methods().forEach(function(method) { + method.attachToObject(self); + method.setRequestManager(web3._requestManager); + }); + }; + + var methods = function () { + var putString = new Method({ + name: 'putString', + call: 'db_putString', + params: 3 + }); + + var getString = new Method({ + name: 'getString', + call: 'db_getString', + params: 2 + }); + + var putHex = new Method({ + name: 'putHex', + call: 'db_putHex', + params: 3 + }); + + var getHex = new Method({ + name: 'getHex', + call: 'db_getHex', + params: 2 + }); + + return [ + putString, getString, putHex, getHex + ]; + }; + + module.exports = DB; + + },{"../method":38}],40:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** + * @file eth.js + * @author Marek Kotewicz + * @author Fabian Vogelsteller + * @date 2015 + */ + + "use strict"; + + var formatters = require('../formatters'); + var utils = require('../../utils/utils'); + var Method = require('../method'); + var Property = require('../property'); + var c = require('../../utils/config'); + var Contract = require('../contract'); + var watches = require('./watches'); + var Filter = require('../filter'); + var IsSyncing = require('../syncing'); + var namereg = require('../namereg'); + var Iban = require('../iban'); + var transfer = require('../transfer'); + + var blockCall = function (args) { + return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? "platon_getBlockByHash" : "platon_getBlockByNumber"; + }; + + var transactionFromBlockCall = function (args) { + return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'platon_getTransactionByBlockHashAndIndex' : 'platon_getTransactionByBlockNumberAndIndex'; + }; + + var getBlockTransactionCountCall = function (args) { + return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'platon_getBlockTransactionCountByHash' : 'platon_getBlockTransactionCountByNumber'; + }; + + function Eth(web3) { + this._requestManager = web3._requestManager; + + var self = this; + + methods().forEach(function(method) { + method.attachToObject(self); + method.setRequestManager(self._requestManager); + }); + + properties().forEach(function(p) { + p.attachToObject(self); + p.setRequestManager(self._requestManager); + }); + } + + Object.defineProperty(Eth.prototype, 'defaultBlock', { + get: function () { + return c.defaultBlock; + }, + set: function (val) { + c.defaultBlock = val; + return val; + } + }); + + Object.defineProperty(Eth.prototype, 'defaultAccount', { + get: function () { + return c.defaultAccount; + }, + set: function (val) { + c.defaultAccount = val; + return val; + } + }); + + var methods = function () { + var getAddressHrp = new Method({ + name: 'getAddressHrp', + call: 'platon_getAddressHrp', + params: 0, + }); + + var getBalance = new Method({ + name: 'getBalance', + call: 'platon_getBalance', + params: 2, + inputFormatter: [formatters.inputAddressFormatter, formatters.inputDefaultBlockNumberFormatter], + outputFormatter: formatters.outputBigNumberFormatter + }); + + var getStorageAt = new Method({ + name: 'getStorageAt', + call: 'platon_getStorageAt', + params: 3, + inputFormatter: [null, utils.toHex, formatters.inputDefaultBlockNumberFormatter] + }); + + var getCode = new Method({ + name: 'getCode', + call: 'platon_getCode', + params: 2, + inputFormatter: [formatters.inputAddressFormatter, formatters.inputDefaultBlockNumberFormatter] + }); + + var getBlock = new Method({ + name: 'getBlock', + call: blockCall, + params: 2, + inputFormatter: [formatters.inputBlockNumberFormatter, function (val) { return !!val; }], + outputFormatter: formatters.outputBlockFormatter + }); + + var getBlockTransactionCount = new Method({ + name: 'getBlockTransactionCount', + call: getBlockTransactionCountCall, + params: 1, + inputFormatter: [formatters.inputBlockNumberFormatter], + outputFormatter: utils.toDecimal + }); + + var getTransaction = new Method({ + name: 'getTransaction', + call: 'platon_getTransactionByHash', + params: 1, + outputFormatter: formatters.outputTransactionFormatter + }); + + var getTransactionFromBlock = new Method({ + name: 'getTransactionFromBlock', + call: transactionFromBlockCall, + params: 2, + inputFormatter: [formatters.inputBlockNumberFormatter, utils.toHex], + outputFormatter: formatters.outputTransactionFormatter + }); + + var getTransactionReceipt = new Method({ + name: 'getTransactionReceipt', + call: 'platon_getTransactionReceipt', + params: 1, + outputFormatter: formatters.outputTransactionReceiptFormatter + }); + + var getTransactionCount = new Method({ + name: 'getTransactionCount', + call: 'platon_getTransactionCount', + params: 2, + inputFormatter: [null, formatters.inputDefaultBlockNumberFormatter], + outputFormatter: utils.toDecimal + }); + + var sendRawTransaction = new Method({ + name: 'sendRawTransaction', + call: 'platon_sendRawTransaction', + params: 1, + inputFormatter: [null] + }); + + var sendTransaction = new Method({ + name: 'sendTransaction', + call: 'platon_sendTransaction', + params: 1, + inputFormatter: [formatters.inputTransactionFormatter] + }); + + var signTransaction = new Method({ + name: 'signTransaction', + call: 'platon_signTransaction', + params: 1, + inputFormatter: [formatters.inputTransactionFormatter] + }); + + var sign = new Method({ + name: 'sign', + call: 'platon_sign', + params: 2, + inputFormatter: [formatters.inputAddressFormatter, null] + }); + + var call = new Method({ + name: 'call', + call: 'platon_call', + params: 2, + inputFormatter: [formatters.inputCallFormatter, formatters.inputDefaultBlockNumberFormatter] + }); + + var estimateGas = new Method({ + name: 'estimateGas', + call: 'platon_estimateGas', + params: 1, + inputFormatter: [formatters.inputCallFormatter], + outputFormatter: utils.toDecimal + }); + + + var evidences = new Method({ + name: 'evidences', + call: 'platon_evidences', + params: 0 + }); + + return [ + getAddressHrp, + getBalance, + getStorageAt, + getCode, + getBlock, + getBlockTransactionCount, + getTransaction, + getTransactionFromBlock, + getTransactionReceipt, + getTransactionCount, + call, + estimateGas, + sendRawTransaction, + signTransaction, + sendTransaction, + sign, + evidences, + ]; + }; + + + var properties = function () { + return [ + new Property({ + name: 'syncing', + getter: 'platon_syncing', + outputFormatter: formatters.outputSyncingFormatter + }), + new Property({ + name: 'gasPrice', + getter: 'platon_gasPrice', + outputFormatter: formatters.outputBigNumberFormatter + }), + new Property({ + name: 'accounts', + getter: 'platon_accounts' + }), + new Property({ + name: 'blockNumber', + getter: 'platon_blockNumber', + outputFormatter: utils.toDecimal + }), + new Property({ + name: 'protocolVersion', + getter: 'platon_protocolVersion' + }) + ]; + }; + + Eth.prototype.contract = function (abi) { + var factory = new Contract(this, abi); + return factory; + }; + + Eth.prototype.filter = function (options, callback, filterCreationErrorCallback) { + return new Filter(options, 'eth', this._requestManager, watches.eth(), formatters.outputLogFormatter, callback, filterCreationErrorCallback); + }; + + Eth.prototype.isSyncing = function (callback) { + return new IsSyncing(this._requestManager, callback); + }; + + module.exports = Eth; + + },{"../../utils/config":19,"../../utils/utils":22,"../contract":27,"../filter":31,"../formatters":32,"../iban":35,"../method":38,"../namereg":44,"../property":45,"../syncing":47,"../transfer":48,"./watches":43}],41:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** @file eth.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + + var utils = require('../../utils/utils'); + var Property = require('../property'); + + var Net = function (web3) { + this._requestManager = web3._requestManager; + + var self = this; + + properties().forEach(function(p) { + p.attachToObject(self); + p.setRequestManager(web3._requestManager); + }); + }; + +/// @returns an array of objects describing web3.eth api properties + var properties = function () { + return [ + new Property({ + name: 'listening', + getter: 'net_listening' + }), + new Property({ + name: 'peerCount', + getter: 'net_peerCount', + outputFormatter: utils.toDecimal + }) + ]; + }; + + module.exports = Net; + + },{"../../utils/utils":22,"../property":45}],42:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** + * @file eth.js + * @author Marek Kotewicz + * @author Fabian Vogelsteller + * @date 2015 + */ + + "use strict"; + + var Method = require('../method'); + var Property = require('../property'); + var formatters = require('../formatters'); + + function Personal(web3) { + this._requestManager = web3._requestManager; + + var self = this; + + methods().forEach(function(method) { + method.attachToObject(self); + method.setRequestManager(self._requestManager); + }); + + properties().forEach(function(p) { + p.attachToObject(self); + p.setRequestManager(self._requestManager); + }); + } + + var methods = function () { + var newAccount = new Method({ + name: 'newAccount', + call: 'personal_newAccount', + params: 1, + inputFormatter: [null] + }); + + var importRawKey = new Method({ + name: 'importRawKey', + call: 'personal_importRawKey', + params: 2 + }); + + var sign = new Method({ + name: 'sign', + call: 'personal_sign', + params: 3, + inputFormatter: [null, formatters.inputAddressFormatter, null] + }); + + var ecRecover = new Method({ + name: 'ecRecover', + call: 'personal_ecRecover', + params: 2 + }); + + var unlockAccount = new Method({ + name: 'unlockAccount', + call: 'personal_unlockAccount', + params: 3, + inputFormatter: [formatters.inputAddressFormatter, null, null] + }); + + var sendTransaction = new Method({ + name: 'sendTransaction', + call: 'personal_sendTransaction', + params: 2, + inputFormatter: [formatters.inputTransactionFormatter, null] + }); + + var lockAccount = new Method({ + name: 'lockAccount', + call: 'personal_lockAccount', + params: 1, + inputFormatter: [formatters.inputAddressFormatter] + }); + + return [ + newAccount, + importRawKey, + unlockAccount, + ecRecover, + sign, + sendTransaction, + lockAccount + ]; + }; + + var properties = function () { + return [ + new Property({ + name: 'listAccounts', + getter: 'personal_listAccounts' + }) + ]; + }; + + + module.exports = Personal; + + },{"../formatters":32,"../method":38,"../property":45}],43:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** @file watches.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + + var Method = require('../method'); + +/// @returns an array of objects describing web3.eth.filter api methods + var eth = function () { + var newFilterCall = function (args) { + var type = args[0]; + + switch(type) { + case 'latest': + args.shift(); + this.params = 0; + return 'platon_newBlockFilter'; + case 'pending': + args.shift(); + this.params = 0; + return 'platon_newPendingTransactionFilter'; + default: + return 'platon_newFilter'; + } + }; + + var newFilter = new Method({ + name: 'newFilter', + call: newFilterCall, + params: 1 + }); + + var uninstallFilter = new Method({ + name: 'uninstallFilter', + call: 'platon_uninstallFilter', + params: 1 + }); + + var getLogs = new Method({ + name: 'getLogs', + call: 'platon_getFilterLogs', + params: 1 + }); + + var poll = new Method({ + name: 'poll', + call: 'platon_getFilterChanges', + params: 1 + }); + + return [ + newFilter, + uninstallFilter, + getLogs, + poll + ]; + }; + +/// @returns an array of objects describing web3.shh.watch api methods + var shh = function () { + + return [ + new Method({ + name: 'newFilter', + call: 'shh_newMessageFilter', + params: 1 + }), + new Method({ + name: 'uninstallFilter', + call: 'shh_deleteMessageFilter', + params: 1 + }), + new Method({ + name: 'getLogs', + call: 'shh_getFilterMessages', + params: 1 + }), + new Method({ + name: 'poll', + call: 'shh_getFilterMessages', + params: 1 + }) + ]; + }; + + module.exports = { + eth: eth, + shh: shh + }; + + + },{"../method":38}],44:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** + * @file namereg.js + * @author Marek Kotewicz + * @date 2015 + */ + + var globalRegistrarAbi = require('../contracts/GlobalRegistrar.json'); + var icapRegistrarAbi= require('../contracts/ICAPRegistrar.json'); + + var globalNameregAddress = '0xc6d9d2cd449a754c494264e1809c50e34d64562b'; + var icapNameregAddress = '0xa1a111bc074c9cfa781f0c38e63bd51c91b8af00'; + + module.exports = { + global: { + abi: globalRegistrarAbi, + address: globalNameregAddress + }, + icap: { + abi: icapRegistrarAbi, + address: icapNameregAddress + } + }; + + + },{"../contracts/GlobalRegistrar.json":1,"../contracts/ICAPRegistrar.json":2}],45:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** + * @file property.js + * @author Fabian Vogelsteller + * @author Marek Kotewicz + * @date 2015 + */ + + var utils = require('../utils/utils'); + + var Property = function (options) { + this.name = options.name; + this.getter = options.getter; + this.setter = options.setter; + this.outputFormatter = options.outputFormatter; + this.inputFormatter = options.inputFormatter; + this.requestManager = null; + }; + + Property.prototype.setRequestManager = function (rm) { + this.requestManager = rm; + }; + + /** + * Should be called to format input args of method + * + * @method formatInput + * @param {Array} + * @return {Array} + */ + Property.prototype.formatInput = function (arg) { + return this.inputFormatter ? this.inputFormatter(arg) : arg; + }; + + /** + * Should be called to format output(result) of method + * + * @method formatOutput + * @param {Object} + * @return {Object} + */ + Property.prototype.formatOutput = function (result) { + return this.outputFormatter && result !== null && result !== undefined ? this.outputFormatter(result) : result; + }; + + /** + * Should be used to extract callback from array of arguments. Modifies input param + * + * @method extractCallback + * @param {Array} arguments + * @return {Function|Null} callback, if exists + */ + Property.prototype.extractCallback = function (args) { + if (utils.isFunction(args[args.length - 1])) { + return args.pop(); // modify the args array! + } + }; + + + /** + * Should attach function to method + * + * @method attachToObject + * @param {Object} + * @param {Function} + */ + Property.prototype.attachToObject = function (obj) { + var proto = { + get: this.buildGet(), + enumerable: true + }; + + var names = this.name.split('.'); + var name = names[0]; + if (names.length > 1) { + obj[names[0]] = obj[names[0]] || {}; + obj = obj[names[0]]; + name = names[1]; + } + + Object.defineProperty(obj, name, proto); + obj[asyncGetterName(name)] = this.buildAsyncGet(); + }; + + var asyncGetterName = function (name) { + return 'get' + name.charAt(0).toUpperCase() + name.slice(1); + }; + + Property.prototype.buildGet = function () { + var property = this; + return function get() { + return property.formatOutput(property.requestManager.send({ + method: property.getter + })); + }; + }; + + Property.prototype.buildAsyncGet = function () { + var property = this; + var get = function (callback) { + property.requestManager.sendAsync({ + method: property.getter + }, function (err, result) { + callback(err, property.formatOutput(result)); + }); + }; + get.request = this.request.bind(this); + return get; + }; + + /** + * Should be called to create pure JSONRPC request which can be used in batch request + * + * @method request + * @param {...} params + * @return {Object} jsonrpc request + */ + Property.prototype.request = function () { + var payload = { + method: this.getter, + params: [], + callback: this.extractCallback(Array.prototype.slice.call(arguments)) + }; + payload.format = this.formatOutput.bind(this); + return payload; + }; + + module.exports = Property; + + + },{"../utils/utils":22}],46:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** + * @file requestmanager.js + * @author Jeffrey Wilcke + * @author Marek Kotewicz + * @author Marian Oancea + * @author Fabian Vogelsteller + * @author Gav Wood + * @date 2014 + */ + + var Jsonrpc = require('./jsonrpc'); + var utils = require('../utils/utils'); + var c = require('../utils/config'); + var errors = require('./errors'); + + /** + * It's responsible for passing messages to providers + * It's also responsible for polling the ethereum node for incoming messages + * Default poll timeout is 1 second + * Singleton + */ + var RequestManager = function (provider) { + this.provider = provider; + this.polls = {}; + this.timeout = null; + }; + + /** + * Should be used to synchronously send request + * + * @method send + * @param {Object} data + * @return {Object} + */ + RequestManager.prototype.send = function (data) { + if (!this.provider) { + console.error(errors.InvalidProvider()); + return null; + } + + var payload = Jsonrpc.toPayload(data.method, data.params); + var result = this.provider.send(payload); + + if (!Jsonrpc.isValidResponse(result)) { + throw errors.InvalidResponse(result); + } + + return result.result; + }; + + /** + * Should be used to asynchronously send request + * + * @method sendAsync + * @param {Object} data + * @param {Function} callback + */ + RequestManager.prototype.sendAsync = function (data, callback) { + if (!this.provider) { + return callback(errors.InvalidProvider()); + } + + var payload = Jsonrpc.toPayload(data.method, data.params); + this.provider.sendAsync(payload, function (err, result) { + if (err) { + return callback(err); + } + + if (!Jsonrpc.isValidResponse(result)) { + return callback(errors.InvalidResponse(result)); + } + + callback(null, result.result); + }); + }; + + /** + * Should be called to asynchronously send batch request + * + * @method sendBatch + * @param {Array} batch data + * @param {Function} callback + */ + RequestManager.prototype.sendBatch = function (data, callback) { + if (!this.provider) { + return callback(errors.InvalidProvider()); + } + + var payload = Jsonrpc.toBatchPayload(data); + + this.provider.sendAsync(payload, function (err, results) { + if (err) { + return callback(err); + } + + if (!utils.isArray(results)) { + return callback(errors.InvalidResponse(results)); + } + + callback(err, results); + }); + }; + + /** + * Should be used to set provider of request manager + * + * @method setProvider + * @param {Object} + */ + RequestManager.prototype.setProvider = function (p) { + this.provider = p; + }; + + /** + * Should be used to start polling + * + * @method startPolling + * @param {Object} data + * @param {Number} pollId + * @param {Function} callback + * @param {Function} uninstall + * + * @todo cleanup number of params + */ + RequestManager.prototype.startPolling = function (data, pollId, callback, uninstall) { + this.polls[pollId] = {data: data, id: pollId, callback: callback, uninstall: uninstall}; + + + // start polling + if (!this.timeout) { + this.poll(); + } + }; + + /** + * Should be used to stop polling for filter with given id + * + * @method stopPolling + * @param {Number} pollId + */ + RequestManager.prototype.stopPolling = function (pollId) { + delete this.polls[pollId]; + + // stop polling + if(Object.keys(this.polls).length === 0 && this.timeout) { + clearTimeout(this.timeout); + this.timeout = null; + } + }; + + /** + * Should be called to reset the polling mechanism of the request manager + * + * @method reset + */ + RequestManager.prototype.reset = function (keepIsSyncing) { + /*jshint maxcomplexity:5 */ + + for (var key in this.polls) { + // remove all polls, except sync polls, + // they need to be removed manually by calling syncing.stopWatching() + if(!keepIsSyncing || key.indexOf('syncPoll_') === -1) { + this.polls[key].uninstall(); + delete this.polls[key]; + } + } + + // stop polling + if(Object.keys(this.polls).length === 0 && this.timeout) { + clearTimeout(this.timeout); + this.timeout = null; + } + }; + + /** + * Should be called to poll for changes on filter with given id + * + * @method poll + */ + RequestManager.prototype.poll = function () { + /*jshint maxcomplexity: 6 */ + this.timeout = setTimeout(this.poll.bind(this), c.ETH_POLLING_TIMEOUT); + + if (Object.keys(this.polls).length === 0) { + return; + } + + if (!this.provider) { + console.error(errors.InvalidProvider()); + return; + } + + var pollsData = []; + var pollsIds = []; + for (var key in this.polls) { + pollsData.push(this.polls[key].data); + pollsIds.push(key); + } + + if (pollsData.length === 0) { + return; + } + + var payload = Jsonrpc.toBatchPayload(pollsData); + + // map the request id to they poll id + var pollsIdMap = {}; + payload.forEach(function(load, index){ + pollsIdMap[load.id] = pollsIds[index]; + }); + + + var self = this; + this.provider.sendAsync(payload, function (error, results) { + + + // TODO: console log? + if (error) { + return; + } + + if (!utils.isArray(results)) { + throw errors.InvalidResponse(results); + } + results.map(function (result) { + var id = pollsIdMap[result.id]; + + // make sure the filter is still installed after arrival of the request + if (self.polls[id]) { + result.callback = self.polls[id].callback; + return result; + } else + return false; + }).filter(function (result) { + return !!result; + }).filter(function (result) { + var valid = Jsonrpc.isValidResponse(result); + if (!valid) { + result.callback(errors.InvalidResponse(result)); + } + return valid; + }).forEach(function (result) { + result.callback(null, result.result); + }); + }); + }; + + module.exports = RequestManager; + + + },{"../utils/config":19,"../utils/utils":22,"./errors":28,"./jsonrpc":37}],47:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** @file syncing.js + * @authors: + * Fabian Vogelsteller + * @date 2015 + */ + + var formatters = require('./formatters'); + var utils = require('../utils/utils'); + + var count = 1; + + /** + Adds the callback and sets up the methods, to iterate over the results. + + @method pollSyncing + @param {Object} self + */ + var pollSyncing = function(self) { + + var onMessage = function (error, sync) { + if (error) { + return self.callbacks.forEach(function (callback) { + callback(error); + }); + } + + if(utils.isObject(sync) && sync.startingBlock) + sync = formatters.outputSyncingFormatter(sync); + + self.callbacks.forEach(function (callback) { + if (self.lastSyncState !== sync) { + + // call the callback with true first so the app can stop anything, before receiving the sync data + if(!self.lastSyncState && utils.isObject(sync)) + callback(null, true); + + // call on the next CPU cycle, so the actions of the sync stop can be processes first + setTimeout(function() { + callback(null, sync); + }, 0); + + self.lastSyncState = sync; + } + }); + }; + + self.requestManager.startPolling({ + method: 'platon_syncing', + params: [], + }, self.pollId, onMessage, self.stopWatching.bind(self)); + + }; + + var IsSyncing = function (requestManager, callback) { + this.requestManager = requestManager; + this.pollId = 'syncPoll_'+ count++; + this.callbacks = []; + this.addCallback(callback); + this.lastSyncState = false; + pollSyncing(this); + + return this; + }; + + IsSyncing.prototype.addCallback = function (callback) { + if(callback) + this.callbacks.push(callback); + return this; + }; + + IsSyncing.prototype.stopWatching = function () { + this.requestManager.stopPolling(this.pollId); + this.callbacks = []; + }; + + module.exports = IsSyncing; + + + },{"../utils/utils":22,"./formatters":32}],48:[function(require,module,exports){ + /* + This file is part of web3.js. + + web3.js 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. + + web3.js 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 web3.js. If not, see . +*/ + /** + * @file transfer.js + * @author Marek Kotewicz + * @date 2015 + */ + + var Iban = require('./iban'); + var exchangeAbi = require('../contracts/SmartExchange.json'); + + /** + * Should be used to make Iban transfer + * + * @method transfer + * @param {String} from + * @param {String} to iban + * @param {Value} value to be tranfered + * @param {Function} callback, callback + */ + var transfer = function (eth, from, to, value, callback) { + var iban = new Iban(to); + if (!iban.isValid()) { + throw new Error('invalid iban address'); + } + + if (iban.isDirect()) { + return transferToAddress(eth, from, iban.address(), value, callback); + } + + if (!callback) { + var address = eth.icapNamereg().addr(iban.institution()); + return deposit(eth, from, address, value, iban.client()); + } + + eth.icapNamereg().addr(iban.institution(), function (err, address) { + return deposit(eth, from, address, value, iban.client(), callback); + }); + + }; + + /** + * Should be used to transfer funds to certain address + * + * @method transferToAddress + * @param {String} from + * @param {String} to + * @param {Value} value to be tranfered + * @param {Function} callback, callback + */ + var transferToAddress = function (eth, from, to, value, callback) { + return eth.sendTransaction({ + address: to, + from: from, + value: value + }, callback); + }; + + /** + * Should be used to deposit funds to generic Exchange contract (must implement deposit(bytes32) method!) + * + * @method deposit + * @param {String} from + * @param {String} to + * @param {Value} value to be transfered + * @param {String} client unique identifier + * @param {Function} callback, callback + */ + var deposit = function (eth, from, to, value, client, callback) { + var abi = exchangeAbi; + return eth.contract(abi).at(to).deposit(client, { + from: from, + value: value + }, callback); + }; + + module.exports = transfer; + + + },{"../contracts/SmartExchange.json":3,"./iban":35}],49:[function(require,module,exports){ + + },{}],50:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var BlockCipher = C_lib.BlockCipher; + var C_algo = C.algo; + + // Lookup tables + var SBOX = []; + var INV_SBOX = []; + var SUB_MIX_0 = []; + var SUB_MIX_1 = []; + var SUB_MIX_2 = []; + var SUB_MIX_3 = []; + var INV_SUB_MIX_0 = []; + var INV_SUB_MIX_1 = []; + var INV_SUB_MIX_2 = []; + var INV_SUB_MIX_3 = []; + + // Compute lookup tables + (function () { + // Compute double table + var d = []; + for (var i = 0; i < 256; i++) { + if (i < 128) { + d[i] = i << 1; + } else { + d[i] = (i << 1) ^ 0x11b; + } + } + + // Walk GF(2^8) + var x = 0; + var xi = 0; + for (var i = 0; i < 256; i++) { + // Compute sbox + var sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4); + sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63; + SBOX[x] = sx; + INV_SBOX[sx] = x; + + // Compute multiplication + var x2 = d[x]; + var x4 = d[x2]; + var x8 = d[x4]; + + // Compute sub bytes, mix columns tables + var t = (d[sx] * 0x101) ^ (sx * 0x1010100); + SUB_MIX_0[x] = (t << 24) | (t >>> 8); + SUB_MIX_1[x] = (t << 16) | (t >>> 16); + SUB_MIX_2[x] = (t << 8) | (t >>> 24); + SUB_MIX_3[x] = t; + + // Compute inv sub bytes, inv mix columns tables + var t = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100); + INV_SUB_MIX_0[sx] = (t << 24) | (t >>> 8); + INV_SUB_MIX_1[sx] = (t << 16) | (t >>> 16); + INV_SUB_MIX_2[sx] = (t << 8) | (t >>> 24); + INV_SUB_MIX_3[sx] = t; + + // Compute next counter + if (!x) { + x = xi = 1; + } else { + x = x2 ^ d[d[d[x8 ^ x2]]]; + xi ^= d[d[xi]]; + } + } + }()); + + // Precomputed Rcon lookup + var RCON = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; + + /** + * AES block cipher algorithm. + */ + var AES = C_algo.AES = BlockCipher.extend({ + _doReset: function () { + // Skip reset of nRounds has been set before and key did not change + if (this._nRounds && this._keyPriorReset === this._key) { + return; + } + + // Shortcuts + var key = this._keyPriorReset = this._key; + var keyWords = key.words; + var keySize = key.sigBytes / 4; + + // Compute number of rounds + var nRounds = this._nRounds = keySize + 6; + + // Compute number of key schedule rows + var ksRows = (nRounds + 1) * 4; + + // Compute key schedule + var keySchedule = this._keySchedule = []; + for (var ksRow = 0; ksRow < ksRows; ksRow++) { + if (ksRow < keySize) { + keySchedule[ksRow] = keyWords[ksRow]; + } else { + var t = keySchedule[ksRow - 1]; + + if (!(ksRow % keySize)) { + // Rot word + t = (t << 8) | (t >>> 24); + + // Sub word + t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; + + // Mix Rcon + t ^= RCON[(ksRow / keySize) | 0] << 24; + } else if (keySize > 6 && ksRow % keySize == 4) { + // Sub word + t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; + } + + keySchedule[ksRow] = keySchedule[ksRow - keySize] ^ t; + } + } + + // Compute inv key schedule + var invKeySchedule = this._invKeySchedule = []; + for (var invKsRow = 0; invKsRow < ksRows; invKsRow++) { + var ksRow = ksRows - invKsRow; + + if (invKsRow % 4) { + var t = keySchedule[ksRow]; + } else { + var t = keySchedule[ksRow - 4]; + } + + if (invKsRow < 4 || ksRow <= 4) { + invKeySchedule[invKsRow] = t; + } else { + invKeySchedule[invKsRow] = INV_SUB_MIX_0[SBOX[t >>> 24]] ^ INV_SUB_MIX_1[SBOX[(t >>> 16) & 0xff]] ^ + INV_SUB_MIX_2[SBOX[(t >>> 8) & 0xff]] ^ INV_SUB_MIX_3[SBOX[t & 0xff]]; + } + } + }, + + encryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX); + }, + + decryptBlock: function (M, offset) { + // Swap 2nd and 4th rows + var t = M[offset + 1]; + M[offset + 1] = M[offset + 3]; + M[offset + 3] = t; + + this._doCryptBlock(M, offset, this._invKeySchedule, INV_SUB_MIX_0, INV_SUB_MIX_1, INV_SUB_MIX_2, INV_SUB_MIX_3, INV_SBOX); + + // Inv swap 2nd and 4th rows + var t = M[offset + 1]; + M[offset + 1] = M[offset + 3]; + M[offset + 3] = t; + }, + + _doCryptBlock: function (M, offset, keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX) { + // Shortcut + var nRounds = this._nRounds; + + // Get input, add round key + var s0 = M[offset] ^ keySchedule[0]; + var s1 = M[offset + 1] ^ keySchedule[1]; + var s2 = M[offset + 2] ^ keySchedule[2]; + var s3 = M[offset + 3] ^ keySchedule[3]; + + // Key schedule row counter + var ksRow = 4; + + // Rounds + for (var round = 1; round < nRounds; round++) { + // Shift rows, sub bytes, mix columns, add round key + var t0 = SUB_MIX_0[s0 >>> 24] ^ SUB_MIX_1[(s1 >>> 16) & 0xff] ^ SUB_MIX_2[(s2 >>> 8) & 0xff] ^ SUB_MIX_3[s3 & 0xff] ^ keySchedule[ksRow++]; + var t1 = SUB_MIX_0[s1 >>> 24] ^ SUB_MIX_1[(s2 >>> 16) & 0xff] ^ SUB_MIX_2[(s3 >>> 8) & 0xff] ^ SUB_MIX_3[s0 & 0xff] ^ keySchedule[ksRow++]; + var t2 = SUB_MIX_0[s2 >>> 24] ^ SUB_MIX_1[(s3 >>> 16) & 0xff] ^ SUB_MIX_2[(s0 >>> 8) & 0xff] ^ SUB_MIX_3[s1 & 0xff] ^ keySchedule[ksRow++]; + var t3 = SUB_MIX_0[s3 >>> 24] ^ SUB_MIX_1[(s0 >>> 16) & 0xff] ^ SUB_MIX_2[(s1 >>> 8) & 0xff] ^ SUB_MIX_3[s2 & 0xff] ^ keySchedule[ksRow++]; + + // Update state + s0 = t0; + s1 = t1; + s2 = t2; + s3 = t3; + } + + // Shift rows, sub bytes, add round key + var t0 = ((SBOX[s0 >>> 24] << 24) | (SBOX[(s1 >>> 16) & 0xff] << 16) | (SBOX[(s2 >>> 8) & 0xff] << 8) | SBOX[s3 & 0xff]) ^ keySchedule[ksRow++]; + var t1 = ((SBOX[s1 >>> 24] << 24) | (SBOX[(s2 >>> 16) & 0xff] << 16) | (SBOX[(s3 >>> 8) & 0xff] << 8) | SBOX[s0 & 0xff]) ^ keySchedule[ksRow++]; + var t2 = ((SBOX[s2 >>> 24] << 24) | (SBOX[(s3 >>> 16) & 0xff] << 16) | (SBOX[(s0 >>> 8) & 0xff] << 8) | SBOX[s1 & 0xff]) ^ keySchedule[ksRow++]; + var t3 = ((SBOX[s3 >>> 24] << 24) | (SBOX[(s0 >>> 16) & 0xff] << 16) | (SBOX[(s1 >>> 8) & 0xff] << 8) | SBOX[s2 & 0xff]) ^ keySchedule[ksRow++]; + + // Set output + M[offset] = t0; + M[offset + 1] = t1; + M[offset + 2] = t2; + M[offset + 3] = t3; + }, + + keySize: 256/32 + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.AES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.AES.decrypt(ciphertext, key, cfg); + */ + C.AES = BlockCipher._createHelper(AES); + }()); + + + return CryptoJS.AES; + + })); + },{"./cipher-core":51,"./core":52,"./enc-base64":53,"./evpkdf":55,"./md5":60}],51:[function(require,module,exports){ + ;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + /** + * Cipher core components. + */ + CryptoJS.lib.Cipher || (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm; + var C_enc = C.enc; + var Utf8 = C_enc.Utf8; + var Base64 = C_enc.Base64; + var C_algo = C.algo; + var EvpKDF = C_algo.EvpKDF; + + /** + * Abstract base cipher template. + * + * @property {number} keySize This cipher's key size. Default: 4 (128 bits) + * @property {number} ivSize This cipher's IV size. Default: 4 (128 bits) + * @property {number} _ENC_XFORM_MODE A constant representing encryption mode. + * @property {number} _DEC_XFORM_MODE A constant representing decryption mode. + */ + var Cipher = C_lib.Cipher = BufferedBlockAlgorithm.extend({ + /** + * Configuration options. + * + * @property {WordArray} iv The IV to use for this operation. + */ + cfg: Base.extend(), + + /** + * Creates this cipher in encryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createEncryptor(keyWordArray, { iv: ivWordArray }); + */ + createEncryptor: function (key, cfg) { + return this.create(this._ENC_XFORM_MODE, key, cfg); + }, + + /** + * Creates this cipher in decryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createDecryptor(keyWordArray, { iv: ivWordArray }); + */ + createDecryptor: function (key, cfg) { + return this.create(this._DEC_XFORM_MODE, key, cfg); + }, + + /** + * Initializes a newly created cipher. + * + * @param {number} xformMode Either the encryption or decryption transormation mode constant. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @example + * + * var cipher = CryptoJS.algo.AES.create(CryptoJS.algo.AES._ENC_XFORM_MODE, keyWordArray, { iv: ivWordArray }); + */ + init: function (xformMode, key, cfg) { + // Apply config defaults + this.cfg = this.cfg.extend(cfg); + + // Store transform mode and key + this._xformMode = xformMode; + this._key = key; + + // Set initial values + this.reset(); + }, + + /** + * Resets this cipher to its initial state. + * + * @example + * + * cipher.reset(); + */ + reset: function () { + // Reset data buffer + BufferedBlockAlgorithm.reset.call(this); + + // Perform concrete-cipher logic + this._doReset(); + }, + + /** + * Adds data to be encrypted or decrypted. + * + * @param {WordArray|string} dataUpdate The data to encrypt or decrypt. + * + * @return {WordArray} The data after processing. + * + * @example + * + * var encrypted = cipher.process('data'); + * var encrypted = cipher.process(wordArray); + */ + process: function (dataUpdate) { + // Append + this._append(dataUpdate); + + // Process available blocks + return this._process(); + }, + + /** + * Finalizes the encryption or decryption process. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} dataUpdate The final data to encrypt or decrypt. + * + * @return {WordArray} The data after final processing. + * + * @example + * + * var encrypted = cipher.finalize(); + * var encrypted = cipher.finalize('data'); + * var encrypted = cipher.finalize(wordArray); + */ + finalize: function (dataUpdate) { + // Final data update + if (dataUpdate) { + this._append(dataUpdate); + } + + // Perform concrete-cipher logic + var finalProcessedData = this._doFinalize(); + + return finalProcessedData; + }, + + keySize: 128/32, + + ivSize: 128/32, + + _ENC_XFORM_MODE: 1, + + _DEC_XFORM_MODE: 2, + + /** + * Creates shortcut functions to a cipher's object interface. + * + * @param {Cipher} cipher The cipher to create a helper for. + * + * @return {Object} An object with encrypt and decrypt shortcut functions. + * + * @static + * + * @example + * + * var AES = CryptoJS.lib.Cipher._createHelper(CryptoJS.algo.AES); + */ + _createHelper: (function () { + function selectCipherStrategy(key) { + if (typeof key == 'string') { + return PasswordBasedCipher; + } else { + return SerializableCipher; + } + } + + return function (cipher) { + return { + encrypt: function (message, key, cfg) { + return selectCipherStrategy(key).encrypt(cipher, message, key, cfg); + }, + + decrypt: function (ciphertext, key, cfg) { + return selectCipherStrategy(key).decrypt(cipher, ciphertext, key, cfg); + } + }; + }; + }()) + }); + + /** + * Abstract base stream cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 1 (32 bits) + */ + var StreamCipher = C_lib.StreamCipher = Cipher.extend({ + _doFinalize: function () { + // Process partial blocks + var finalProcessedBlocks = this._process(!!'flush'); + + return finalProcessedBlocks; + }, + + blockSize: 1 + }); + + /** + * Mode namespace. + */ + var C_mode = C.mode = {}; + + /** + * Abstract base block cipher mode template. + */ + var BlockCipherMode = C_lib.BlockCipherMode = Base.extend({ + /** + * Creates this mode for encryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createEncryptor(cipher, iv.words); + */ + createEncryptor: function (cipher, iv) { + return this.Encryptor.create(cipher, iv); + }, + + /** + * Creates this mode for decryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createDecryptor(cipher, iv.words); + */ + createDecryptor: function (cipher, iv) { + return this.Decryptor.create(cipher, iv); + }, + + /** + * Initializes a newly created mode. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @example + * + * var mode = CryptoJS.mode.CBC.Encryptor.create(cipher, iv.words); + */ + init: function (cipher, iv) { + this._cipher = cipher; + this._iv = iv; + } + }); + + /** + * Cipher Block Chaining mode. + */ + var CBC = C_mode.CBC = (function () { + /** + * Abstract base CBC mode. + */ + var CBC = BlockCipherMode.extend(); + + /** + * CBC encryptor. + */ + CBC.Encryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // XOR and encrypt + xorBlock.call(this, words, offset, blockSize); + cipher.encryptBlock(words, offset); + + // Remember this block to use with next block + this._prevBlock = words.slice(offset, offset + blockSize); + } + }); + + /** + * CBC decryptor. + */ + CBC.Decryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // Remember this block to use with next block + var thisBlock = words.slice(offset, offset + blockSize); + + // Decrypt and XOR + cipher.decryptBlock(words, offset); + xorBlock.call(this, words, offset, blockSize); + + // This block becomes the previous block + this._prevBlock = thisBlock; + } + }); + + function xorBlock(words, offset, blockSize) { + // Shortcut + var iv = this._iv; + + // Choose mixing block + if (iv) { + var block = iv; + + // Remove IV for subsequent blocks + this._iv = undefined; + } else { + var block = this._prevBlock; + } + + // XOR blocks + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= block[i]; + } + } + + return CBC; + }()); + + /** + * Padding namespace. + */ + var C_pad = C.pad = {}; + + /** + * PKCS #5/7 padding strategy. + */ + var Pkcs7 = C_pad.Pkcs7 = { + /** + * Pads data using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to pad. + * @param {number} blockSize The multiple that the data should be padded to. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.pad(wordArray, 4); + */ + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Create padding word + var paddingWord = (nPaddingBytes << 24) | (nPaddingBytes << 16) | (nPaddingBytes << 8) | nPaddingBytes; + + // Create padding + var paddingWords = []; + for (var i = 0; i < nPaddingBytes; i += 4) { + paddingWords.push(paddingWord); + } + var padding = WordArray.create(paddingWords, nPaddingBytes); + + // Add padding + data.concat(padding); + }, + + /** + * Unpads data that had been padded using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to unpad. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.unpad(wordArray); + */ + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + /** + * Abstract base block cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 4 (128 bits) + */ + var BlockCipher = C_lib.BlockCipher = Cipher.extend({ + /** + * Configuration options. + * + * @property {Mode} mode The block mode to use. Default: CBC + * @property {Padding} padding The padding strategy to use. Default: Pkcs7 + */ + cfg: Cipher.cfg.extend({ + mode: CBC, + padding: Pkcs7 + }), + + reset: function () { + // Reset cipher + Cipher.reset.call(this); + + // Shortcuts + var cfg = this.cfg; + var iv = cfg.iv; + var mode = cfg.mode; + + // Reset block mode + if (this._xformMode == this._ENC_XFORM_MODE) { + var modeCreator = mode.createEncryptor; + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + var modeCreator = mode.createDecryptor; + + // Keep at least one block in the buffer for unpadding + this._minBufferSize = 1; + } + this._mode = modeCreator.call(mode, this, iv && iv.words); + }, + + _doProcessBlock: function (words, offset) { + this._mode.processBlock(words, offset); + }, + + _doFinalize: function () { + // Shortcut + var padding = this.cfg.padding; + + // Finalize + if (this._xformMode == this._ENC_XFORM_MODE) { + // Pad data + padding.pad(this._data, this.blockSize); + + // Process final blocks + var finalProcessedBlocks = this._process(!!'flush'); + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + // Process final blocks + var finalProcessedBlocks = this._process(!!'flush'); + + // Unpad data + padding.unpad(finalProcessedBlocks); + } + + return finalProcessedBlocks; + }, + + blockSize: 128/32 + }); + + /** + * A collection of cipher parameters. + * + * @property {WordArray} ciphertext The raw ciphertext. + * @property {WordArray} key The key to this ciphertext. + * @property {WordArray} iv The IV used in the ciphering operation. + * @property {WordArray} salt The salt used with a key derivation function. + * @property {Cipher} algorithm The cipher algorithm. + * @property {Mode} mode The block mode used in the ciphering operation. + * @property {Padding} padding The padding scheme used in the ciphering operation. + * @property {number} blockSize The block size of the cipher. + * @property {Format} formatter The default formatting strategy to convert this cipher params object to a string. + */ + var CipherParams = C_lib.CipherParams = Base.extend({ + /** + * Initializes a newly created cipher params object. + * + * @param {Object} cipherParams An object with any of the possible cipher parameters. + * + * @example + * + * var cipherParams = CryptoJS.lib.CipherParams.create({ + * ciphertext: ciphertextWordArray, + * key: keyWordArray, + * iv: ivWordArray, + * salt: saltWordArray, + * algorithm: CryptoJS.algo.AES, + * mode: CryptoJS.mode.CBC, + * padding: CryptoJS.pad.PKCS7, + * blockSize: 4, + * formatter: CryptoJS.format.OpenSSL + * }); + */ + init: function (cipherParams) { + this.mixIn(cipherParams); + }, + + /** + * Converts this cipher params object to a string. + * + * @param {Format} formatter (Optional) The formatting strategy to use. + * + * @return {string} The stringified cipher params. + * + * @throws Error If neither the formatter nor the default formatter is set. + * + * @example + * + * var string = cipherParams + ''; + * var string = cipherParams.toString(); + * var string = cipherParams.toString(CryptoJS.format.OpenSSL); + */ + toString: function (formatter) { + return (formatter || this.formatter).stringify(this); + } + }); + + /** + * Format namespace. + */ + var C_format = C.format = {}; + + /** + * OpenSSL formatting strategy. + */ + var OpenSSLFormatter = C_format.OpenSSL = { + /** + * Converts a cipher params object to an OpenSSL-compatible string. + * + * @param {CipherParams} cipherParams The cipher params object. + * + * @return {string} The OpenSSL-compatible string. + * + * @static + * + * @example + * + * var openSSLString = CryptoJS.format.OpenSSL.stringify(cipherParams); + */ + stringify: function (cipherParams) { + // Shortcuts + var ciphertext = cipherParams.ciphertext; + var salt = cipherParams.salt; + + // Format + if (salt) { + var wordArray = WordArray.create([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext); + } else { + var wordArray = ciphertext; + } + + return wordArray.toString(Base64); + }, + + /** + * Converts an OpenSSL-compatible string to a cipher params object. + * + * @param {string} openSSLStr The OpenSSL-compatible string. + * + * @return {CipherParams} The cipher params object. + * + * @static + * + * @example + * + * var cipherParams = CryptoJS.format.OpenSSL.parse(openSSLString); + */ + parse: function (openSSLStr) { + // Parse base64 + var ciphertext = Base64.parse(openSSLStr); + + // Shortcut + var ciphertextWords = ciphertext.words; + + // Test for salt + if (ciphertextWords[0] == 0x53616c74 && ciphertextWords[1] == 0x65645f5f) { + // Extract salt + var salt = WordArray.create(ciphertextWords.slice(2, 4)); + + // Remove salt from ciphertext + ciphertextWords.splice(0, 4); + ciphertext.sigBytes -= 16; + } + + return CipherParams.create({ ciphertext: ciphertext, salt: salt }); + } + }; + + /** + * A cipher wrapper that returns ciphertext as a serializable cipher params object. + */ + var SerializableCipher = C_lib.SerializableCipher = Base.extend({ + /** + * Configuration options. + * + * @property {Formatter} format The formatting strategy to convert cipher param objects to and from a string. Default: OpenSSL + */ + cfg: Base.extend({ + format: OpenSSLFormatter + }), + + /** + * Encrypts a message. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv }); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Encrypt + var encryptor = cipher.createEncryptor(key, cfg); + var ciphertext = encryptor.finalize(message); + + // Shortcut + var cipherCfg = encryptor.cfg; + + // Create and return serializable cipher params + return CipherParams.create({ + ciphertext: ciphertext, + key: key, + iv: cipherCfg.iv, + algorithm: cipher, + mode: cipherCfg.mode, + padding: cipherCfg.padding, + blockSize: cipher.blockSize, + formatter: cfg.format + }); + }, + + /** + * Decrypts serialized ciphertext. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Decrypt + var plaintext = cipher.createDecryptor(key, cfg).finalize(ciphertext.ciphertext); + + return plaintext; + }, + + /** + * Converts serialized ciphertext to CipherParams, + * else assumed CipherParams already and returns ciphertext unchanged. + * + * @param {CipherParams|string} ciphertext The ciphertext. + * @param {Formatter} format The formatting strategy to use to parse serialized ciphertext. + * + * @return {CipherParams} The unserialized ciphertext. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher._parse(ciphertextStringOrParams, format); + */ + _parse: function (ciphertext, format) { + if (typeof ciphertext == 'string') { + return format.parse(ciphertext, this); + } else { + return ciphertext; + } + } + }); + + /** + * Key derivation function namespace. + */ + var C_kdf = C.kdf = {}; + + /** + * OpenSSL key derivation function. + */ + var OpenSSLKdf = C_kdf.OpenSSL = { + /** + * Derives a key and IV from a password. + * + * @param {string} password The password to derive from. + * @param {number} keySize The size in words of the key to generate. + * @param {number} ivSize The size in words of the IV to generate. + * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be generated randomly. + * + * @return {CipherParams} A cipher params object with the key, IV, and salt. + * + * @static + * + * @example + * + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32); + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt'); + */ + execute: function (password, keySize, ivSize, salt) { + // Generate random salt + if (!salt) { + salt = WordArray.random(64/8); + } + + // Derive key and IV + var key = EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt); + + // Separate key and IV + var iv = WordArray.create(key.words.slice(keySize), ivSize * 4); + key.sigBytes = keySize * 4; + + // Return params + return CipherParams.create({ key: key, iv: iv, salt: salt }); + } + }; + + /** + * A serializable cipher wrapper that derives the key from a password, + * and returns ciphertext as a serializable cipher params object. + */ + var PasswordBasedCipher = C_lib.PasswordBasedCipher = SerializableCipher.extend({ + /** + * Configuration options. + * + * @property {KDF} kdf The key derivation function to use to generate a key and IV from a password. Default: OpenSSL + */ + cfg: SerializableCipher.cfg.extend({ + kdf: OpenSSLKdf + }), + + /** + * Encrypts a message using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password'); + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password', { format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Encrypt + var ciphertext = SerializableCipher.encrypt.call(this, cipher, message, derivedParams.key, cfg); + + // Mix in derived params + ciphertext.mixIn(derivedParams); + + return ciphertext; + }, + + /** + * Decrypts serialized ciphertext using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, 'password', { format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, 'password', { format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize, ciphertext.salt); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Decrypt + var plaintext = SerializableCipher.decrypt.call(this, cipher, ciphertext, derivedParams.key, cfg); + + return plaintext; + } + }); + }()); + + + })); + },{"./core":52}],52:[function(require,module,exports){ + ;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(); + } + else if (typeof define === "function" && define.amd) { + // AMD + define([], factory); + } + else { + // Global (browser) + root.CryptoJS = factory(); + } + }(this, function () { + + /** + * CryptoJS core components. + */ + var CryptoJS = CryptoJS || (function (Math, undefined) { + /* + * Local polyfil of Object.create + */ + var create = Object.create || (function () { + function F() {}; + + return function (obj) { + var subtype; + + F.prototype = obj; + + subtype = new F(); + + F.prototype = null; + + return subtype; + }; + }()) + + /** + * CryptoJS namespace. + */ + var C = {}; + + /** + * Library namespace. + */ + var C_lib = C.lib = {}; + + /** + * Base object for prototypal inheritance. + */ + var Base = C_lib.Base = (function () { + + + return { + /** + * Creates a new object that inherits from this object. + * + * @param {Object} overrides Properties to copy into the new object. + * + * @return {Object} The new object. + * + * @static + * + * @example + * + * var MyType = CryptoJS.lib.Base.extend({ + * field: 'value', + * + * method: function () { + * } + * }); + */ + extend: function (overrides) { + // Spawn + var subtype = create(this); + + // Augment + if (overrides) { + subtype.mixIn(overrides); + } + + // Create default initializer + if (!subtype.hasOwnProperty('init') || this.init === subtype.init) { + subtype.init = function () { + subtype.$super.init.apply(this, arguments); + }; + } + + // Initializer's prototype is the subtype object + subtype.init.prototype = subtype; + + // Reference supertype + subtype.$super = this; + + return subtype; + }, + + /** + * Extends this object and runs the init method. + * Arguments to create() will be passed to init(). + * + * @return {Object} The new object. + * + * @static + * + * @example + * + * var instance = MyType.create(); + */ + create: function () { + var instance = this.extend(); + instance.init.apply(instance, arguments); + + return instance; + }, + + /** + * Initializes a newly created object. + * Override this method to add some logic when your objects are created. + * + * @example + * + * var MyType = CryptoJS.lib.Base.extend({ + * init: function () { + * // ... + * } + * }); + */ + init: function () { + }, + + /** + * Copies properties into this object. + * + * @param {Object} properties The properties to mix in. + * + * @example + * + * MyType.mixIn({ + * field: 'value' + * }); + */ + mixIn: function (properties) { + for (var propertyName in properties) { + if (properties.hasOwnProperty(propertyName)) { + this[propertyName] = properties[propertyName]; + } + } + + // IE won't copy toString using the loop above + if (properties.hasOwnProperty('toString')) { + this.toString = properties.toString; + } + }, + + /** + * Creates a copy of this object. + * + * @return {Object} The clone. + * + * @example + * + * var clone = instance.clone(); + */ + clone: function () { + return this.init.prototype.extend(this); + } + }; + }()); + + /** + * An array of 32-bit words. + * + * @property {Array} words The array of 32-bit words. + * @property {number} sigBytes The number of significant bytes in this word array. + */ + var WordArray = C_lib.WordArray = Base.extend({ + /** + * Initializes a newly created word array. + * + * @param {Array} words (Optional) An array of 32-bit words. + * @param {number} sigBytes (Optional) The number of significant bytes in the words. + * + * @example + * + * var wordArray = CryptoJS.lib.WordArray.create(); + * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]); + * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6); + */ + init: function (words, sigBytes) { + words = this.words = words || []; + + if (sigBytes != undefined) { + this.sigBytes = sigBytes; + } else { + this.sigBytes = words.length * 4; + } + }, + + /** + * Converts this word array to a string. + * + * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex + * + * @return {string} The stringified word array. + * + * @example + * + * var string = wordArray + ''; + * var string = wordArray.toString(); + * var string = wordArray.toString(CryptoJS.enc.Utf8); + */ + toString: function (encoder) { + return (encoder || Hex).stringify(this); + }, + + /** + * Concatenates a word array to this word array. + * + * @param {WordArray} wordArray The word array to append. + * + * @return {WordArray} This word array. + * + * @example + * + * wordArray1.concat(wordArray2); + */ + concat: function (wordArray) { + // Shortcuts + var thisWords = this.words; + var thatWords = wordArray.words; + var thisSigBytes = this.sigBytes; + var thatSigBytes = wordArray.sigBytes; + + // Clamp excess bits + this.clamp(); + + // Concat + if (thisSigBytes % 4) { + // Copy one byte at a time + for (var i = 0; i < thatSigBytes; i++) { + var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8); + } + } else { + // Copy one word at a time + for (var i = 0; i < thatSigBytes; i += 4) { + thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2]; + } + } + this.sigBytes += thatSigBytes; + + // Chainable + return this; + }, + + /** + * Removes insignificant bits. + * + * @example + * + * wordArray.clamp(); + */ + clamp: function () { + // Shortcuts + var words = this.words; + var sigBytes = this.sigBytes; + + // Clamp + words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8); + words.length = Math.ceil(sigBytes / 4); + }, + + /** + * Creates a copy of this word array. + * + * @return {WordArray} The clone. + * + * @example + * + * var clone = wordArray.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + clone.words = this.words.slice(0); + + return clone; + }, + + /** + * Creates a word array filled with random bytes. + * + * @param {number} nBytes The number of random bytes to generate. + * + * @return {WordArray} The random word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.lib.WordArray.random(16); + */ + random: function (nBytes) { + var words = []; + + var r = (function (m_w) { + var m_w = m_w; + var m_z = 0x3ade68b1; + var mask = 0xffffffff; + + return function () { + m_z = (0x9069 * (m_z & 0xFFFF) + (m_z >> 0x10)) & mask; + m_w = (0x4650 * (m_w & 0xFFFF) + (m_w >> 0x10)) & mask; + var result = ((m_z << 0x10) + m_w) & mask; + result /= 0x100000000; + result += 0.5; + return result * (Math.random() > .5 ? 1 : -1); + } + }); + + for (var i = 0, rcache; i < nBytes; i += 4) { + var _r = r((rcache || Math.random()) * 0x100000000); + + rcache = _r() * 0x3ade67b7; + words.push((_r() * 0x100000000) | 0); + } + + return new WordArray.init(words, nBytes); + } + }); + + /** + * Encoder namespace. + */ + var C_enc = C.enc = {}; + + /** + * Hex encoding strategy. + */ + var Hex = C_enc.Hex = { + /** + * Converts a word array to a hex string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The hex string. + * + * @static + * + * @example + * + * var hexString = CryptoJS.enc.Hex.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var hexChars = []; + for (var i = 0; i < sigBytes; i++) { + var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + hexChars.push((bite >>> 4).toString(16)); + hexChars.push((bite & 0x0f).toString(16)); + } + + return hexChars.join(''); + }, + + /** + * Converts a hex string to a word array. + * + * @param {string} hexStr The hex string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Hex.parse(hexString); + */ + parse: function (hexStr) { + // Shortcut + var hexStrLength = hexStr.length; + + // Convert + var words = []; + for (var i = 0; i < hexStrLength; i += 2) { + words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); + } + + return new WordArray.init(words, hexStrLength / 2); + } + }; + + /** + * Latin1 encoding strategy. + */ + var Latin1 = C_enc.Latin1 = { + /** + * Converts a word array to a Latin1 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The Latin1 string. + * + * @static + * + * @example + * + * var latin1String = CryptoJS.enc.Latin1.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var latin1Chars = []; + for (var i = 0; i < sigBytes; i++) { + var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + latin1Chars.push(String.fromCharCode(bite)); + } + + return latin1Chars.join(''); + }, + + /** + * Converts a Latin1 string to a word array. + * + * @param {string} latin1Str The Latin1 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Latin1.parse(latin1String); + */ + parse: function (latin1Str) { + // Shortcut + var latin1StrLength = latin1Str.length; + + // Convert + var words = []; + for (var i = 0; i < latin1StrLength; i++) { + words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8); + } + + return new WordArray.init(words, latin1StrLength); + } + }; + + /** + * UTF-8 encoding strategy. + */ + var Utf8 = C_enc.Utf8 = { + /** + * Converts a word array to a UTF-8 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-8 string. + * + * @static + * + * @example + * + * var utf8String = CryptoJS.enc.Utf8.stringify(wordArray); + */ + stringify: function (wordArray) { + try { + return decodeURIComponent(escape(Latin1.stringify(wordArray))); + } catch (e) { + throw new Error('Malformed UTF-8 data'); + } + }, + + /** + * Converts a UTF-8 string to a word array. + * + * @param {string} utf8Str The UTF-8 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf8.parse(utf8String); + */ + parse: function (utf8Str) { + return Latin1.parse(unescape(encodeURIComponent(utf8Str))); + } + }; + + /** + * Abstract buffered block algorithm template. + * + * The property blockSize must be implemented in a concrete subtype. + * + * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0 + */ + var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({ + /** + * Resets this block algorithm's data buffer to its initial state. + * + * @example + * + * bufferedBlockAlgorithm.reset(); + */ + reset: function () { + // Initial values + this._data = new WordArray.init(); + this._nDataBytes = 0; + }, + + /** + * Adds new data to this block algorithm's buffer. + * + * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8. + * + * @example + * + * bufferedBlockAlgorithm._append('data'); + * bufferedBlockAlgorithm._append(wordArray); + */ + _append: function (data) { + // Convert string to WordArray, else assume WordArray already + if (typeof data == 'string') { + data = Utf8.parse(data); + } + + // Append + this._data.concat(data); + this._nDataBytes += data.sigBytes; + }, + + /** + * Processes available data blocks. + * + * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype. + * + * @param {boolean} doFlush Whether all blocks and partial blocks should be processed. + * + * @return {WordArray} The processed data. + * + * @example + * + * var processedData = bufferedBlockAlgorithm._process(); + * var processedData = bufferedBlockAlgorithm._process(!!'flush'); + */ + _process: function (doFlush) { + // Shortcuts + var data = this._data; + var dataWords = data.words; + var dataSigBytes = data.sigBytes; + var blockSize = this.blockSize; + var blockSizeBytes = blockSize * 4; + + // Count blocks ready + var nBlocksReady = dataSigBytes / blockSizeBytes; + if (doFlush) { + // Round up to include partial blocks + nBlocksReady = Math.ceil(nBlocksReady); + } else { + // Round down to include only full blocks, + // less the number of blocks that must remain in the buffer + nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0); + } + + // Count words ready + var nWordsReady = nBlocksReady * blockSize; + + // Count bytes ready + var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes); + + // Process blocks + if (nWordsReady) { + for (var offset = 0; offset < nWordsReady; offset += blockSize) { + // Perform concrete-algorithm logic + this._doProcessBlock(dataWords, offset); + } + + // Remove processed words + var processedWords = dataWords.splice(0, nWordsReady); + data.sigBytes -= nBytesReady; + } + + // Return processed words + return new WordArray.init(processedWords, nBytesReady); + }, + + /** + * Creates a copy of this object. + * + * @return {Object} The clone. + * + * @example + * + * var clone = bufferedBlockAlgorithm.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + clone._data = this._data.clone(); + + return clone; + }, + + _minBufferSize: 0 + }); + + /** + * Abstract hasher template. + * + * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits) + */ + var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({ + /** + * Configuration options. + */ + cfg: Base.extend(), + + /** + * Initializes a newly created hasher. + * + * @param {Object} cfg (Optional) The configuration options to use for this hash computation. + * + * @example + * + * var hasher = CryptoJS.algo.SHA256.create(); + */ + init: function (cfg) { + // Apply config defaults + this.cfg = this.cfg.extend(cfg); + + // Set initial values + this.reset(); + }, + + /** + * Resets this hasher to its initial state. + * + * @example + * + * hasher.reset(); + */ + reset: function () { + // Reset data buffer + BufferedBlockAlgorithm.reset.call(this); + + // Perform concrete-hasher logic + this._doReset(); + }, + + /** + * Updates this hasher with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {Hasher} This hasher. + * + * @example + * + * hasher.update('message'); + * hasher.update(wordArray); + */ + update: function (messageUpdate) { + // Append + this._append(messageUpdate); + + // Update the hash + this._process(); + + // Chainable + return this; + }, + + /** + * Finalizes the hash computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The hash. + * + * @example + * + * var hash = hasher.finalize(); + * var hash = hasher.finalize('message'); + * var hash = hasher.finalize(wordArray); + */ + finalize: function (messageUpdate) { + // Final message update + if (messageUpdate) { + this._append(messageUpdate); + } + + // Perform concrete-hasher logic + var hash = this._doFinalize(); + + return hash; + }, + + blockSize: 512/32, + + /** + * Creates a shortcut function to a hasher's object interface. + * + * @param {Hasher} hasher The hasher to create a helper for. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256); + */ + _createHelper: function (hasher) { + return function (message, cfg) { + return new hasher.init(cfg).finalize(message); + }; + }, + + /** + * Creates a shortcut function to the HMAC's object interface. + * + * @param {Hasher} hasher The hasher to use in this HMAC helper. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256); + */ + _createHmacHelper: function (hasher) { + return function (message, key) { + return new C_algo.HMAC.init(hasher, key).finalize(message); + }; + } + }); + + /** + * Algorithm namespace. + */ + var C_algo = C.algo = {}; + + return C; + }(Math)); + + + return CryptoJS; + + })); + },{}],53:[function(require,module,exports){ + ;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_enc = C.enc; + + /** + * Base64 encoding strategy. + */ + var Base64 = C_enc.Base64 = { + /** + * Converts a word array to a Base64 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The Base64 string. + * + * @static + * + * @example + * + * var base64String = CryptoJS.enc.Base64.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + var map = this._map; + + // Clamp excess bits + wordArray.clamp(); + + // Convert + var base64Chars = []; + for (var i = 0; i < sigBytes; i += 3) { + var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff; + var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff; + + var triplet = (byte1 << 16) | (byte2 << 8) | byte3; + + for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) { + base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); + } + } + + // Add padding + var paddingChar = map.charAt(64); + if (paddingChar) { + while (base64Chars.length % 4) { + base64Chars.push(paddingChar); + } + } + + return base64Chars.join(''); + }, + + /** + * Converts a Base64 string to a word array. + * + * @param {string} base64Str The Base64 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Base64.parse(base64String); + */ + parse: function (base64Str) { + // Shortcuts + var base64StrLength = base64Str.length; + var map = this._map; + var reverseMap = this._reverseMap; + + if (!reverseMap) { + reverseMap = this._reverseMap = []; + for (var j = 0; j < map.length; j++) { + reverseMap[map.charCodeAt(j)] = j; + } + } + + // Ignore padding + var paddingChar = map.charAt(64); + if (paddingChar) { + var paddingIndex = base64Str.indexOf(paddingChar); + if (paddingIndex !== -1) { + base64StrLength = paddingIndex; + } + } + + // Convert + return parseLoop(base64Str, base64StrLength, reverseMap); + + }, + + _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' + }; + + function parseLoop(base64Str, base64StrLength, reverseMap) { + var words = []; + var nBytes = 0; + for (var i = 0; i < base64StrLength; i++) { + if (i % 4) { + var bits1 = reverseMap[base64Str.charCodeAt(i - 1)] << ((i % 4) * 2); + var bits2 = reverseMap[base64Str.charCodeAt(i)] >>> (6 - (i % 4) * 2); + words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8); + nBytes++; + } + } + return WordArray.create(words, nBytes); + } + }()); + + + return CryptoJS.enc.Base64; + + })); + },{"./core":52}],54:[function(require,module,exports){ + ;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_enc = C.enc; + + /** + * UTF-16 BE encoding strategy. + */ + var Utf16BE = C_enc.Utf16 = C_enc.Utf16BE = { + /** + * Converts a word array to a UTF-16 BE string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-16 BE string. + * + * @static + * + * @example + * + * var utf16String = CryptoJS.enc.Utf16.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var utf16Chars = []; + for (var i = 0; i < sigBytes; i += 2) { + var codePoint = (words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff; + utf16Chars.push(String.fromCharCode(codePoint)); + } + + return utf16Chars.join(''); + }, + + /** + * Converts a UTF-16 BE string to a word array. + * + * @param {string} utf16Str The UTF-16 BE string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf16.parse(utf16String); + */ + parse: function (utf16Str) { + // Shortcut + var utf16StrLength = utf16Str.length; + + // Convert + var words = []; + for (var i = 0; i < utf16StrLength; i++) { + words[i >>> 1] |= utf16Str.charCodeAt(i) << (16 - (i % 2) * 16); + } + + return WordArray.create(words, utf16StrLength * 2); + } + }; + + /** + * UTF-16 LE encoding strategy. + */ + C_enc.Utf16LE = { + /** + * Converts a word array to a UTF-16 LE string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-16 LE string. + * + * @static + * + * @example + * + * var utf16Str = CryptoJS.enc.Utf16LE.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var utf16Chars = []; + for (var i = 0; i < sigBytes; i += 2) { + var codePoint = swapEndian((words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff); + utf16Chars.push(String.fromCharCode(codePoint)); + } + + return utf16Chars.join(''); + }, + + /** + * Converts a UTF-16 LE string to a word array. + * + * @param {string} utf16Str The UTF-16 LE string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf16LE.parse(utf16Str); + */ + parse: function (utf16Str) { + // Shortcut + var utf16StrLength = utf16Str.length; + + // Convert + var words = []; + for (var i = 0; i < utf16StrLength; i++) { + words[i >>> 1] |= swapEndian(utf16Str.charCodeAt(i) << (16 - (i % 2) * 16)); + } + + return WordArray.create(words, utf16StrLength * 2); + } + }; + + function swapEndian(word) { + return ((word << 8) & 0xff00ff00) | ((word >>> 8) & 0x00ff00ff); + } + }()); + + + return CryptoJS.enc.Utf16; + + })); + },{"./core":52}],55:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./sha1"), require("./hmac")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./sha1", "./hmac"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var MD5 = C_algo.MD5; + + /** + * This key derivation function is meant to conform with EVP_BytesToKey. + * www.openssl.org/docs/crypto/EVP_BytesToKey.html + */ + var EvpKDF = C_algo.EvpKDF = Base.extend({ + /** + * Configuration options. + * + * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) + * @property {Hasher} hasher The hash algorithm to use. Default: MD5 + * @property {number} iterations The number of iterations to perform. Default: 1 + */ + cfg: Base.extend({ + keySize: 128/32, + hasher: MD5, + iterations: 1 + }), + + /** + * Initializes a newly created key derivation function. + * + * @param {Object} cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.EvpKDF.create(); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8, iterations: 1000 }); + */ + init: function (cfg) { + this.cfg = this.cfg.extend(cfg); + }, + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * + * @return {WordArray} The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute: function (password, salt) { + // Shortcut + var cfg = this.cfg; + + // Init hasher + var hasher = cfg.hasher.create(); + + // Initial values + var derivedKey = WordArray.create(); + + // Shortcuts + var derivedKeyWords = derivedKey.words; + var keySize = cfg.keySize; + var iterations = cfg.iterations; + + // Generate key + while (derivedKeyWords.length < keySize) { + if (block) { + hasher.update(block); + } + var block = hasher.update(password).finalize(salt); + hasher.reset(); + + // Iterations + for (var i = 1; i < iterations; i++) { + block = hasher.finalize(block); + hasher.reset(); + } + + derivedKey.concat(block); + } + derivedKey.sigBytes = keySize * 4; + + return derivedKey; + } + }); + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * @param {Object} cfg (Optional) The configuration options to use for this computation. + * + * @return {WordArray} The derived key. + * + * @static + * + * @example + * + * var key = CryptoJS.EvpKDF(password, salt); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8 }); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8, iterations: 1000 }); + */ + C.EvpKDF = function (password, salt, cfg) { + return EvpKDF.create(cfg).compute(password, salt); + }; + }()); + + + return CryptoJS.EvpKDF; + + })); + },{"./core":52,"./hmac":57,"./sha1":76}],56:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var CipherParams = C_lib.CipherParams; + var C_enc = C.enc; + var Hex = C_enc.Hex; + var C_format = C.format; + + var HexFormatter = C_format.Hex = { + /** + * Converts the ciphertext of a cipher params object to a hexadecimally encoded string. + * + * @param {CipherParams} cipherParams The cipher params object. + * + * @return {string} The hexadecimally encoded string. + * + * @static + * + * @example + * + * var hexString = CryptoJS.format.Hex.stringify(cipherParams); + */ + stringify: function (cipherParams) { + return cipherParams.ciphertext.toString(Hex); + }, + + /** + * Converts a hexadecimally encoded ciphertext string to a cipher params object. + * + * @param {string} input The hexadecimally encoded string. + * + * @return {CipherParams} The cipher params object. + * + * @static + * + * @example + * + * var cipherParams = CryptoJS.format.Hex.parse(hexString); + */ + parse: function (input) { + var ciphertext = Hex.parse(input); + return CipherParams.create({ ciphertext: ciphertext }); + } + }; + }()); + + + return CryptoJS.format.Hex; + + })); + },{"./cipher-core":51,"./core":52}],57:[function(require,module,exports){ + ;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var C_enc = C.enc; + var Utf8 = C_enc.Utf8; + var C_algo = C.algo; + + /** + * HMAC algorithm. + */ + var HMAC = C_algo.HMAC = Base.extend({ + /** + * Initializes a newly created HMAC. + * + * @param {Hasher} hasher The hash algorithm to use. + * @param {WordArray|string} key The secret key. + * + * @example + * + * var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key); + */ + init: function (hasher, key) { + // Init hasher + hasher = this._hasher = new hasher.init(); + + // Convert string to WordArray, else assume WordArray already + if (typeof key == 'string') { + key = Utf8.parse(key); + } + + // Shortcuts + var hasherBlockSize = hasher.blockSize; + var hasherBlockSizeBytes = hasherBlockSize * 4; + + // Allow arbitrary length keys + if (key.sigBytes > hasherBlockSizeBytes) { + key = hasher.finalize(key); + } + + // Clamp excess bits + key.clamp(); + + // Clone key for inner and outer pads + var oKey = this._oKey = key.clone(); + var iKey = this._iKey = key.clone(); + + // Shortcuts + var oKeyWords = oKey.words; + var iKeyWords = iKey.words; + + // XOR keys with pad constants + for (var i = 0; i < hasherBlockSize; i++) { + oKeyWords[i] ^= 0x5c5c5c5c; + iKeyWords[i] ^= 0x36363636; + } + oKey.sigBytes = iKey.sigBytes = hasherBlockSizeBytes; + + // Set initial values + this.reset(); + }, + + /** + * Resets this HMAC to its initial state. + * + * @example + * + * hmacHasher.reset(); + */ + reset: function () { + // Shortcut + var hasher = this._hasher; + + // Reset + hasher.reset(); + hasher.update(this._iKey); + }, + + /** + * Updates this HMAC with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {HMAC} This HMAC instance. + * + * @example + * + * hmacHasher.update('message'); + * hmacHasher.update(wordArray); + */ + update: function (messageUpdate) { + this._hasher.update(messageUpdate); + + // Chainable + return this; + }, + + /** + * Finalizes the HMAC computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The HMAC. + * + * @example + * + * var hmac = hmacHasher.finalize(); + * var hmac = hmacHasher.finalize('message'); + * var hmac = hmacHasher.finalize(wordArray); + */ + finalize: function (messageUpdate) { + // Shortcut + var hasher = this._hasher; + + // Compute HMAC + var innerHash = hasher.finalize(messageUpdate); + hasher.reset(); + var hmac = hasher.finalize(this._oKey.clone().concat(innerHash)); + + return hmac; + } + }); + }()); + + + })); + },{"./core":52}],58:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./x64-core"), require("./lib-typedarrays"), require("./enc-utf16"), require("./enc-base64"), require("./md5"), require("./sha1"), require("./sha256"), require("./sha224"), require("./sha512"), require("./sha384"), require("./sha3"), require("./ripemd160"), require("./hmac"), require("./pbkdf2"), require("./evpkdf"), require("./cipher-core"), require("./mode-cfb"), require("./mode-ctr"), require("./mode-ctr-gladman"), require("./mode-ofb"), require("./mode-ecb"), require("./pad-ansix923"), require("./pad-iso10126"), require("./pad-iso97971"), require("./pad-zeropadding"), require("./pad-nopadding"), require("./format-hex"), require("./aes"), require("./tripledes"), require("./rc4"), require("./rabbit"), require("./rabbit-legacy")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./x64-core", "./lib-typedarrays", "./enc-utf16", "./enc-base64", "./md5", "./sha1", "./sha256", "./sha224", "./sha512", "./sha384", "./sha3", "./ripemd160", "./hmac", "./pbkdf2", "./evpkdf", "./cipher-core", "./mode-cfb", "./mode-ctr", "./mode-ctr-gladman", "./mode-ofb", "./mode-ecb", "./pad-ansix923", "./pad-iso10126", "./pad-iso97971", "./pad-zeropadding", "./pad-nopadding", "./format-hex", "./aes", "./tripledes", "./rc4", "./rabbit", "./rabbit-legacy"], factory); + } + else { + // Global (browser) + root.CryptoJS = factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + return CryptoJS; + + })); + },{"./aes":50,"./cipher-core":51,"./core":52,"./enc-base64":53,"./enc-utf16":54,"./evpkdf":55,"./format-hex":56,"./hmac":57,"./lib-typedarrays":59,"./md5":60,"./mode-cfb":61,"./mode-ctr":63,"./mode-ctr-gladman":62,"./mode-ecb":64,"./mode-ofb":65,"./pad-ansix923":66,"./pad-iso10126":67,"./pad-iso97971":68,"./pad-nopadding":69,"./pad-zeropadding":70,"./pbkdf2":71,"./rabbit":73,"./rabbit-legacy":72,"./rc4":74,"./ripemd160":75,"./sha1":76,"./sha224":77,"./sha256":78,"./sha3":79,"./sha384":80,"./sha512":81,"./tripledes":82,"./x64-core":83}],59:[function(require,module,exports){ + ;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function () { + // Check if typed arrays are supported + if (typeof ArrayBuffer != 'function') { + return; + } + + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + + // Reference original init + var superInit = WordArray.init; + + // Augment WordArray.init to handle typed arrays + var subInit = WordArray.init = function (typedArray) { + // Convert buffers to uint8 + if (typedArray instanceof ArrayBuffer) { + typedArray = new Uint8Array(typedArray); + } + + // Convert other array views to uint8 + if ( + typedArray instanceof Int8Array || + (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray) || + typedArray instanceof Int16Array || + typedArray instanceof Uint16Array || + typedArray instanceof Int32Array || + typedArray instanceof Uint32Array || + typedArray instanceof Float32Array || + typedArray instanceof Float64Array + ) { + typedArray = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); + } + + // Handle Uint8Array + if (typedArray instanceof Uint8Array) { + // Shortcut + var typedArrayByteLength = typedArray.byteLength; + + // Extract bytes + var words = []; + for (var i = 0; i < typedArrayByteLength; i++) { + words[i >>> 2] |= typedArray[i] << (24 - (i % 4) * 8); + } + + // Initialize this word array + superInit.call(this, words, typedArrayByteLength); + } else { + // Else call normal init + superInit.apply(this, arguments); + } + }; + + subInit.prototype = WordArray; + }()); + + + return CryptoJS.lib.WordArray; + + })); + },{"./core":52}],60:[function(require,module,exports){ + ;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Constants table + var T = []; + + // Compute constants + (function () { + for (var i = 0; i < 64; i++) { + T[i] = (Math.abs(Math.sin(i + 1)) * 0x100000000) | 0; + } + }()); + + /** + * MD5 hash algorithm. + */ + var MD5 = C_algo.MD5 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0x67452301, 0xefcdab89, + 0x98badcfe, 0x10325476 + ]); + }, + + _doProcessBlock: function (M, offset) { + // Swap endian + for (var i = 0; i < 16; i++) { + // Shortcuts + var offset_i = offset + i; + var M_offset_i = M[offset_i]; + + M[offset_i] = ( + (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | + (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + + // Shortcuts + var H = this._hash.words; + + var M_offset_0 = M[offset + 0]; + var M_offset_1 = M[offset + 1]; + var M_offset_2 = M[offset + 2]; + var M_offset_3 = M[offset + 3]; + var M_offset_4 = M[offset + 4]; + var M_offset_5 = M[offset + 5]; + var M_offset_6 = M[offset + 6]; + var M_offset_7 = M[offset + 7]; + var M_offset_8 = M[offset + 8]; + var M_offset_9 = M[offset + 9]; + var M_offset_10 = M[offset + 10]; + var M_offset_11 = M[offset + 11]; + var M_offset_12 = M[offset + 12]; + var M_offset_13 = M[offset + 13]; + var M_offset_14 = M[offset + 14]; + var M_offset_15 = M[offset + 15]; + + // Working varialbes + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + + // Computation + a = FF(a, b, c, d, M_offset_0, 7, T[0]); + d = FF(d, a, b, c, M_offset_1, 12, T[1]); + c = FF(c, d, a, b, M_offset_2, 17, T[2]); + b = FF(b, c, d, a, M_offset_3, 22, T[3]); + a = FF(a, b, c, d, M_offset_4, 7, T[4]); + d = FF(d, a, b, c, M_offset_5, 12, T[5]); + c = FF(c, d, a, b, M_offset_6, 17, T[6]); + b = FF(b, c, d, a, M_offset_7, 22, T[7]); + a = FF(a, b, c, d, M_offset_8, 7, T[8]); + d = FF(d, a, b, c, M_offset_9, 12, T[9]); + c = FF(c, d, a, b, M_offset_10, 17, T[10]); + b = FF(b, c, d, a, M_offset_11, 22, T[11]); + a = FF(a, b, c, d, M_offset_12, 7, T[12]); + d = FF(d, a, b, c, M_offset_13, 12, T[13]); + c = FF(c, d, a, b, M_offset_14, 17, T[14]); + b = FF(b, c, d, a, M_offset_15, 22, T[15]); + + a = GG(a, b, c, d, M_offset_1, 5, T[16]); + d = GG(d, a, b, c, M_offset_6, 9, T[17]); + c = GG(c, d, a, b, M_offset_11, 14, T[18]); + b = GG(b, c, d, a, M_offset_0, 20, T[19]); + a = GG(a, b, c, d, M_offset_5, 5, T[20]); + d = GG(d, a, b, c, M_offset_10, 9, T[21]); + c = GG(c, d, a, b, M_offset_15, 14, T[22]); + b = GG(b, c, d, a, M_offset_4, 20, T[23]); + a = GG(a, b, c, d, M_offset_9, 5, T[24]); + d = GG(d, a, b, c, M_offset_14, 9, T[25]); + c = GG(c, d, a, b, M_offset_3, 14, T[26]); + b = GG(b, c, d, a, M_offset_8, 20, T[27]); + a = GG(a, b, c, d, M_offset_13, 5, T[28]); + d = GG(d, a, b, c, M_offset_2, 9, T[29]); + c = GG(c, d, a, b, M_offset_7, 14, T[30]); + b = GG(b, c, d, a, M_offset_12, 20, T[31]); + + a = HH(a, b, c, d, M_offset_5, 4, T[32]); + d = HH(d, a, b, c, M_offset_8, 11, T[33]); + c = HH(c, d, a, b, M_offset_11, 16, T[34]); + b = HH(b, c, d, a, M_offset_14, 23, T[35]); + a = HH(a, b, c, d, M_offset_1, 4, T[36]); + d = HH(d, a, b, c, M_offset_4, 11, T[37]); + c = HH(c, d, a, b, M_offset_7, 16, T[38]); + b = HH(b, c, d, a, M_offset_10, 23, T[39]); + a = HH(a, b, c, d, M_offset_13, 4, T[40]); + d = HH(d, a, b, c, M_offset_0, 11, T[41]); + c = HH(c, d, a, b, M_offset_3, 16, T[42]); + b = HH(b, c, d, a, M_offset_6, 23, T[43]); + a = HH(a, b, c, d, M_offset_9, 4, T[44]); + d = HH(d, a, b, c, M_offset_12, 11, T[45]); + c = HH(c, d, a, b, M_offset_15, 16, T[46]); + b = HH(b, c, d, a, M_offset_2, 23, T[47]); + + a = II(a, b, c, d, M_offset_0, 6, T[48]); + d = II(d, a, b, c, M_offset_7, 10, T[49]); + c = II(c, d, a, b, M_offset_14, 15, T[50]); + b = II(b, c, d, a, M_offset_5, 21, T[51]); + a = II(a, b, c, d, M_offset_12, 6, T[52]); + d = II(d, a, b, c, M_offset_3, 10, T[53]); + c = II(c, d, a, b, M_offset_10, 15, T[54]); + b = II(b, c, d, a, M_offset_1, 21, T[55]); + a = II(a, b, c, d, M_offset_8, 6, T[56]); + d = II(d, a, b, c, M_offset_15, 10, T[57]); + c = II(c, d, a, b, M_offset_6, 15, T[58]); + b = II(b, c, d, a, M_offset_13, 21, T[59]); + a = II(a, b, c, d, M_offset_4, 6, T[60]); + d = II(d, a, b, c, M_offset_11, 10, T[61]); + c = II(c, d, a, b, M_offset_2, 15, T[62]); + b = II(b, c, d, a, M_offset_9, 21, T[63]); + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + + var nBitsTotalH = Math.floor(nBitsTotal / 0x100000000); + var nBitsTotalL = nBitsTotal; + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = ( + (((nBitsTotalH << 8) | (nBitsTotalH >>> 24)) & 0x00ff00ff) | + (((nBitsTotalH << 24) | (nBitsTotalH >>> 8)) & 0xff00ff00) + ); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotalL << 8) | (nBitsTotalL >>> 24)) & 0x00ff00ff) | + (((nBitsTotalL << 24) | (nBitsTotalL >>> 8)) & 0xff00ff00) + ); + + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var hash = this._hash; + var H = hash.words; + + // Swap endian + for (var i = 0; i < 4; i++) { + // Shortcut + var H_i = H[i]; + + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | + (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + function FF(a, b, c, d, x, s, t) { + var n = a + ((b & c) | (~b & d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function GG(a, b, c, d, x, s, t) { + var n = a + ((b & d) | (c & ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function HH(a, b, c, d, x, s, t) { + var n = a + (b ^ c ^ d) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function II(a, b, c, d, x, s, t) { + var n = a + (c ^ (b | ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.MD5('message'); + * var hash = CryptoJS.MD5(wordArray); + */ + C.MD5 = Hasher._createHelper(MD5); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacMD5(message, key); + */ + C.HmacMD5 = Hasher._createHmacHelper(MD5); + }(Math)); + + + return CryptoJS.MD5; + + })); + },{"./core":52}],61:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + /** + * Cipher Feedback block mode. + */ + CryptoJS.mode.CFB = (function () { + var CFB = CryptoJS.lib.BlockCipherMode.extend(); + + CFB.Encryptor = CFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); + + // Remember this block to use with next block + this._prevBlock = words.slice(offset, offset + blockSize); + } + }); + + CFB.Decryptor = CFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // Remember this block to use with next block + var thisBlock = words.slice(offset, offset + blockSize); + + generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); + + // This block becomes the previous block + this._prevBlock = thisBlock; + } + }); + + function generateKeystreamAndEncrypt(words, offset, blockSize, cipher) { + // Shortcut + var iv = this._iv; + + // Generate keystream + if (iv) { + var keystream = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } else { + var keystream = this._prevBlock; + } + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + + return CFB; + }()); + + + return CryptoJS.mode.CFB; + + })); + },{"./cipher-core":51,"./core":52}],62:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + /** @preserve + * Counter block mode compatible with Dr Brian Gladman fileenc.c + * derived from CryptoJS.mode.CTR + * Jan Hruby jhruby.web@gmail.com + */ + CryptoJS.mode.CTRGladman = (function () { + var CTRGladman = CryptoJS.lib.BlockCipherMode.extend(); + + function incWord(word) + { + if (((word >> 24) & 0xff) === 0xff) { //overflow + var b1 = (word >> 16)&0xff; + var b2 = (word >> 8)&0xff; + var b3 = word & 0xff; + + if (b1 === 0xff) // overflow b1 + { + b1 = 0; + if (b2 === 0xff) + { + b2 = 0; + if (b3 === 0xff) + { + b3 = 0; + } + else + { + ++b3; + } + } + else + { + ++b2; + } + } + else + { + ++b1; + } + + word = 0; + word += (b1 << 16); + word += (b2 << 8); + word += b3; + } + else + { + word += (0x01 << 24); + } + return word; + } + + function incCounter(counter) + { + if ((counter[0] = incWord(counter[0])) === 0) + { + // encr_data in fileenc.c from Dr Brian Gladman's counts only with DWORD j < 8 + counter[1] = incWord(counter[1]); + } + return counter; + } + + var Encryptor = CTRGladman.Encryptor = CTRGladman.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var counter = this._counter; + + // Generate keystream + if (iv) { + counter = this._counter = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + + incCounter(counter); + + var keystream = counter.slice(0); + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + CTRGladman.Decryptor = Encryptor; + + return CTRGladman; + }()); + + + + + return CryptoJS.mode.CTRGladman; + + })); + },{"./cipher-core":51,"./core":52}],63:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + /** + * Counter block mode. + */ + CryptoJS.mode.CTR = (function () { + var CTR = CryptoJS.lib.BlockCipherMode.extend(); + + var Encryptor = CTR.Encryptor = CTR.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var counter = this._counter; + + // Generate keystream + if (iv) { + counter = this._counter = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + var keystream = counter.slice(0); + cipher.encryptBlock(keystream, 0); + + // Increment counter + counter[blockSize - 1] = (counter[blockSize - 1] + 1) | 0 + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + CTR.Decryptor = Encryptor; + + return CTR; + }()); + + + return CryptoJS.mode.CTR; + + })); + },{"./cipher-core":51,"./core":52}],64:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + /** + * Electronic Codebook block mode. + */ + CryptoJS.mode.ECB = (function () { + var ECB = CryptoJS.lib.BlockCipherMode.extend(); + + ECB.Encryptor = ECB.extend({ + processBlock: function (words, offset) { + this._cipher.encryptBlock(words, offset); + } + }); + + ECB.Decryptor = ECB.extend({ + processBlock: function (words, offset) { + this._cipher.decryptBlock(words, offset); + } + }); + + return ECB; + }()); + + + return CryptoJS.mode.ECB; + + })); + },{"./cipher-core":51,"./core":52}],65:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + /** + * Output Feedback block mode. + */ + CryptoJS.mode.OFB = (function () { + var OFB = CryptoJS.lib.BlockCipherMode.extend(); + + var Encryptor = OFB.Encryptor = OFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var keystream = this._keystream; + + // Generate keystream + if (iv) { + keystream = this._keystream = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + OFB.Decryptor = Encryptor; + + return OFB; + }()); + + + return CryptoJS.mode.OFB; + + })); + },{"./cipher-core":51,"./core":52}],66:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + /** + * ANSI X.923 padding strategy. + */ + CryptoJS.pad.AnsiX923 = { + pad: function (data, blockSize) { + // Shortcuts + var dataSigBytes = data.sigBytes; + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - dataSigBytes % blockSizeBytes; + + // Compute last byte position + var lastBytePos = dataSigBytes + nPaddingBytes - 1; + + // Pad + data.clamp(); + data.words[lastBytePos >>> 2] |= nPaddingBytes << (24 - (lastBytePos % 4) * 8); + data.sigBytes += nPaddingBytes; + }, + + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + + return CryptoJS.pad.Ansix923; + + })); + },{"./cipher-core":51,"./core":52}],67:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + /** + * ISO 10126 padding strategy. + */ + CryptoJS.pad.Iso10126 = { + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Pad + data.concat(CryptoJS.lib.WordArray.random(nPaddingBytes - 1)). + concat(CryptoJS.lib.WordArray.create([nPaddingBytes << 24], 1)); + }, + + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + + return CryptoJS.pad.Iso10126; + + })); + },{"./cipher-core":51,"./core":52}],68:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + /** + * ISO/IEC 9797-1 Padding Method 2. + */ + CryptoJS.pad.Iso97971 = { + pad: function (data, blockSize) { + // Add 0x80 byte + data.concat(CryptoJS.lib.WordArray.create([0x80000000], 1)); + + // Zero pad the rest + CryptoJS.pad.ZeroPadding.pad(data, blockSize); + }, + + unpad: function (data) { + // Remove zero padding + CryptoJS.pad.ZeroPadding.unpad(data); + + // Remove one more byte -- the 0x80 byte + data.sigBytes--; + } + }; + + + return CryptoJS.pad.Iso97971; -var utils = require('../../utils/utils'); -var Property = require('../property'); + })); + },{"./cipher-core":51,"./core":52}],69:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { -var Net = function (web3) { - this._requestManager = web3._requestManager; + /** + * A noop padding strategy. + */ + CryptoJS.pad.NoPadding = { + pad: function () { + }, - var self = this; + unpad: function () { + } + }; - properties().forEach(function(p) { - p.attachToObject(self); - p.setRequestManager(web3._requestManager); - }); -}; -/// @returns an array of objects describing web3.eth api properties -var properties = function () { - return [ - new Property({ - name: 'listening', - getter: 'net_listening' - }), - new Property({ - name: 'peerCount', - getter: 'net_peerCount', - outputFormatter: utils.toDecimal - }) - ]; -}; + return CryptoJS.pad.NoPadding; -module.exports = Net; + })); + },{"./cipher-core":51,"./core":52}],70:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + /** + * Zero padding strategy. + */ + CryptoJS.pad.ZeroPadding = { + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Pad + data.clamp(); + data.sigBytes += blockSizeBytes - ((data.sigBytes % blockSizeBytes) || blockSizeBytes); + }, + + unpad: function (data) { + // Shortcut + var dataWords = data.words; + + // Unpad + var i = data.sigBytes - 1; + while (!((dataWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff)) { + i--; + } + data.sigBytes = i + 1; + } + }; -},{"../../utils/utils":22,"../property":45}],42:[function(require,module,exports){ -/* - This file is part of web3.js. - web3.js 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. + return CryptoJS.pad.ZeroPadding; - web3.js 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. + })); + },{"./cipher-core":51,"./core":52}],71:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./sha1"), require("./hmac")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./sha1", "./hmac"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var SHA1 = C_algo.SHA1; + var HMAC = C_algo.HMAC; + + /** + * Password-Based Key Derivation Function 2 algorithm. + */ + var PBKDF2 = C_algo.PBKDF2 = Base.extend({ + /** + * Configuration options. + * + * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) + * @property {Hasher} hasher The hasher to use. Default: SHA1 + * @property {number} iterations The number of iterations to perform. Default: 1 + */ + cfg: Base.extend({ + keySize: 128/32, + hasher: SHA1, + iterations: 1 + }), + + /** + * Initializes a newly created key derivation function. + * + * @param {Object} cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.PBKDF2.create(); + * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8, iterations: 1000 }); + */ + init: function (cfg) { + this.cfg = this.cfg.extend(cfg); + }, + + /** + * Computes the Password-Based Key Derivation Function 2. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * + * @return {WordArray} The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute: function (password, salt) { + // Shortcut + var cfg = this.cfg; + + // Init HMAC + var hmac = HMAC.create(cfg.hasher, password); + + // Initial values + var derivedKey = WordArray.create(); + var blockIndex = WordArray.create([0x00000001]); + + // Shortcuts + var derivedKeyWords = derivedKey.words; + var blockIndexWords = blockIndex.words; + var keySize = cfg.keySize; + var iterations = cfg.iterations; + + // Generate key + while (derivedKeyWords.length < keySize) { + var block = hmac.update(salt).finalize(blockIndex); + hmac.reset(); + + // Shortcuts + var blockWords = block.words; + var blockWordsLength = blockWords.length; + + // Iterations + var intermediate = block; + for (var i = 1; i < iterations; i++) { + intermediate = hmac.finalize(intermediate); + hmac.reset(); + + // Shortcut + var intermediateWords = intermediate.words; + + // XOR intermediate with block + for (var j = 0; j < blockWordsLength; j++) { + blockWords[j] ^= intermediateWords[j]; + } + } - You should have received a copy of the GNU Lesser General Public License - along with web3.js. If not, see . -*/ -/** - * @file eth.js - * @author Marek Kotewicz - * @author Fabian Vogelsteller - * @date 2015 - */ + derivedKey.concat(block); + blockIndexWords[0]++; + } + derivedKey.sigBytes = keySize * 4; -"use strict"; + return derivedKey; + } + }); -var Method = require('../method'); -var Property = require('../property'); -var formatters = require('../formatters'); + /** + * Computes the Password-Based Key Derivation Function 2. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * @param {Object} cfg (Optional) The configuration options to use for this computation. + * + * @return {WordArray} The derived key. + * + * @static + * + * @example + * + * var key = CryptoJS.PBKDF2(password, salt); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8 }); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8, iterations: 1000 }); + */ + C.PBKDF2 = function (password, salt, cfg) { + return PBKDF2.create(cfg).compute(password, salt); + }; + }()); + + + return CryptoJS.PBKDF2; + + })); + },{"./core":52,"./hmac":57,"./sha1":76}],72:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + // Reusable objects + var S = []; + var C_ = []; + var G = []; + + /** + * Rabbit stream cipher algorithm. + * + * This is a legacy version that neglected to convert the key to little-endian. + * This error doesn't affect the cipher's security, + * but it does affect its compatibility with other implementations. + */ + var RabbitLegacy = C_algo.RabbitLegacy = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var K = this._key.words; + var iv = this.cfg.iv; + + // Generate initial state values + var X = this._X = [ + K[0], (K[3] << 16) | (K[2] >>> 16), + K[1], (K[0] << 16) | (K[3] >>> 16), + K[2], (K[1] << 16) | (K[0] >>> 16), + K[3], (K[2] << 16) | (K[1] >>> 16) + ]; + + // Generate initial counter values + var C = this._C = [ + (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), + (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), + (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), + (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff) + ]; + + // Carry bit + this._b = 0; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } -function Personal(web3) { - this._requestManager = web3._requestManager; + // Modify the counters + for (var i = 0; i < 8; i++) { + C[i] ^= X[(i + 4) & 7]; + } - var self = this; + // IV setup + if (iv) { + // Shortcuts + var IV = iv.words; + var IV_0 = IV[0]; + var IV_1 = IV[1]; + + // Generate four subvectors + var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); + var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); + var i1 = (i0 >>> 16) | (i2 & 0xffff0000); + var i3 = (i2 << 16) | (i0 & 0x0000ffff); + + // Modify counter values + C[0] ^= i0; + C[1] ^= i1; + C[2] ^= i2; + C[3] ^= i3; + C[4] ^= i0; + C[5] ^= i1; + C[6] ^= i2; + C[7] ^= i3; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + } + }, - methods().forEach(function(method) { - method.attachToObject(self); - method.setRequestManager(self._requestManager); - }); + _doProcessBlock: function (M, offset) { + // Shortcut + var X = this._X; - properties().forEach(function(p) { - p.attachToObject(self); - p.setRequestManager(self._requestManager); - }); -} + // Iterate the system + nextState.call(this); -var methods = function () { - var newAccount = new Method({ - name: 'newAccount', - call: 'personal_newAccount', - params: 1, - inputFormatter: [null] - }); + // Generate four keystream words + S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); + S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); + S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); + S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); - var importRawKey = new Method({ - name: 'importRawKey', - call: 'personal_importRawKey', - params: 2 - }); + for (var i = 0; i < 4; i++) { + // Swap endian + S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | + (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); - var sign = new Method({ - name: 'sign', - call: 'personal_sign', - params: 3, - inputFormatter: [null, formatters.inputAddressFormatter, null] - }); + // Encrypt + M[offset + i] ^= S[i]; + } + }, - var ecRecover = new Method({ - name: 'ecRecover', - call: 'personal_ecRecover', - params: 2 - }); + blockSize: 128/32, - var unlockAccount = new Method({ - name: 'unlockAccount', - call: 'personal_unlockAccount', - params: 3, - inputFormatter: [formatters.inputAddressFormatter, null, null] - }); + ivSize: 64/32 + }); - var sendTransaction = new Method({ - name: 'sendTransaction', - call: 'personal_sendTransaction', - params: 2, - inputFormatter: [formatters.inputTransactionFormatter, null] - }); + function nextState() { + // Shortcuts + var X = this._X; + var C = this._C; + + // Save old counter values + for (var i = 0; i < 8; i++) { + C_[i] = C[i]; + } + + // Calculate new counter values + C[0] = (C[0] + 0x4d34d34d + this._b) | 0; + C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; + C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; + C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; + C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; + C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; + C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; + C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; + this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; + + // Calculate the g-values + for (var i = 0; i < 8; i++) { + var gx = X[i] + C[i]; + + // Construct high and low argument for squaring + var ga = gx & 0xffff; + var gb = gx >>> 16; + + // Calculate high and low result of squaring + var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; + var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); + + // High XOR low + G[i] = gh ^ gl; + } + + // Calculate new state values + X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; + X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; + X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; + X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; + X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; + X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; + X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; + X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; + } - var lockAccount = new Method({ - name: 'lockAccount', - call: 'personal_lockAccount', - params: 1, - inputFormatter: [formatters.inputAddressFormatter] - }); + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RabbitLegacy.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RabbitLegacy.decrypt(ciphertext, key, cfg); + */ + C.RabbitLegacy = StreamCipher._createHelper(RabbitLegacy); + }()); + + + return CryptoJS.RabbitLegacy; + + })); + },{"./cipher-core":51,"./core":52,"./enc-base64":53,"./evpkdf":55,"./md5":60}],73:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + // Reusable objects + var S = []; + var C_ = []; + var G = []; + + /** + * Rabbit stream cipher algorithm + */ + var Rabbit = C_algo.Rabbit = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var K = this._key.words; + var iv = this.cfg.iv; + + // Swap endian + for (var i = 0; i < 4; i++) { + K[i] = (((K[i] << 8) | (K[i] >>> 24)) & 0x00ff00ff) | + (((K[i] << 24) | (K[i] >>> 8)) & 0xff00ff00); + } - return [ - newAccount, - importRawKey, - unlockAccount, - ecRecover, - sign, - sendTransaction, - lockAccount - ]; -}; + // Generate initial state values + var X = this._X = [ + K[0], (K[3] << 16) | (K[2] >>> 16), + K[1], (K[0] << 16) | (K[3] >>> 16), + K[2], (K[1] << 16) | (K[0] >>> 16), + K[3], (K[2] << 16) | (K[1] >>> 16) + ]; + + // Generate initial counter values + var C = this._C = [ + (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), + (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), + (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), + (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff) + ]; + + // Carry bit + this._b = 0; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } -var properties = function () { - return [ - new Property({ - name: 'listAccounts', - getter: 'personal_listAccounts' - }) - ]; -}; + // Modify the counters + for (var i = 0; i < 8; i++) { + C[i] ^= X[(i + 4) & 7]; + } + // IV setup + if (iv) { + // Shortcuts + var IV = iv.words; + var IV_0 = IV[0]; + var IV_1 = IV[1]; + + // Generate four subvectors + var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); + var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); + var i1 = (i0 >>> 16) | (i2 & 0xffff0000); + var i3 = (i2 << 16) | (i0 & 0x0000ffff); + + // Modify counter values + C[0] ^= i0; + C[1] ^= i1; + C[2] ^= i2; + C[3] ^= i3; + C[4] ^= i0; + C[5] ^= i1; + C[6] ^= i2; + C[7] ^= i3; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + } + }, -module.exports = Personal; + _doProcessBlock: function (M, offset) { + // Shortcut + var X = this._X; -},{"../formatters":32,"../method":38,"../property":45}],43:[function(require,module,exports){ -/* - This file is part of web3.js. + // Iterate the system + nextState.call(this); - web3.js 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. + // Generate four keystream words + S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); + S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); + S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); + S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); - web3.js 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. + for (var i = 0; i < 4; i++) { + // Swap endian + S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | + (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); - You should have received a copy of the GNU Lesser General Public License - along with web3.js. If not, see . -*/ -/** @file watches.js - * @authors: - * Marek Kotewicz - * @date 2015 - */ + // Encrypt + M[offset + i] ^= S[i]; + } + }, -var Method = require('../method'); + blockSize: 128/32, -/// @returns an array of objects describing web3.eth.filter api methods -var eth = function () { - var newFilterCall = function (args) { - var type = args[0]; + ivSize: 64/32 + }); - switch(type) { - case 'latest': - args.shift(); - this.params = 0; - return 'platon_newBlockFilter'; - case 'pending': - args.shift(); - this.params = 0; - return 'platon_newPendingTransactionFilter'; - default: - return 'platon_newFilter'; + function nextState() { + // Shortcuts + var X = this._X; + var C = this._C; + + // Save old counter values + for (var i = 0; i < 8; i++) { + C_[i] = C[i]; + } + + // Calculate new counter values + C[0] = (C[0] + 0x4d34d34d + this._b) | 0; + C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; + C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; + C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; + C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; + C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; + C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; + C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; + this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; + + // Calculate the g-values + for (var i = 0; i < 8; i++) { + var gx = X[i] + C[i]; + + // Construct high and low argument for squaring + var ga = gx & 0xffff; + var gb = gx >>> 16; + + // Calculate high and low result of squaring + var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; + var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); + + // High XOR low + G[i] = gh ^ gl; + } + + // Calculate new state values + X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; + X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; + X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; + X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; + X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; + X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; + X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; + X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; } - }; - var newFilter = new Method({ - name: 'newFilter', - call: newFilterCall, - params: 1 - }); + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.Rabbit.encrypt(message, key, cfg); + * var plaintext = CryptoJS.Rabbit.decrypt(ciphertext, key, cfg); + */ + C.Rabbit = StreamCipher._createHelper(Rabbit); + }()); + + + return CryptoJS.Rabbit; + + })); + },{"./cipher-core":51,"./core":52,"./enc-base64":53,"./evpkdf":55,"./md5":60}],74:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + /** + * RC4 stream cipher algorithm. + */ + var RC4 = C_algo.RC4 = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + var keySigBytes = key.sigBytes; + + // Init sbox + var S = this._S = []; + for (var i = 0; i < 256; i++) { + S[i] = i; + } - var uninstallFilter = new Method({ - name: 'uninstallFilter', - call: 'platon_uninstallFilter', - params: 1 - }); + // Key setup + for (var i = 0, j = 0; i < 256; i++) { + var keyByteIndex = i % keySigBytes; + var keyByte = (keyWords[keyByteIndex >>> 2] >>> (24 - (keyByteIndex % 4) * 8)) & 0xff; - var getLogs = new Method({ - name: 'getLogs', - call: 'platon_getFilterLogs', - params: 1 - }); + j = (j + S[i] + keyByte) % 256; - var poll = new Method({ - name: 'poll', - call: 'platon_getFilterChanges', - params: 1 - }); + // Swap + var t = S[i]; + S[i] = S[j]; + S[j] = t; + } - return [ - newFilter, - uninstallFilter, - getLogs, - poll - ]; -}; + // Counters + this._i = this._j = 0; + }, -/// @returns an array of objects describing web3.shh.watch api methods -var shh = function () { + _doProcessBlock: function (M, offset) { + M[offset] ^= generateKeystreamWord.call(this); + }, - return [ - new Method({ - name: 'newFilter', - call: 'shh_newMessageFilter', - params: 1 - }), - new Method({ - name: 'uninstallFilter', - call: 'shh_deleteMessageFilter', - params: 1 - }), - new Method({ - name: 'getLogs', - call: 'shh_getFilterMessages', - params: 1 - }), - new Method({ - name: 'poll', - call: 'shh_getFilterMessages', - params: 1 - }) - ]; -}; + keySize: 256/32, -module.exports = { - eth: eth, - shh: shh -}; + ivSize: 0 + }); + function generateKeystreamWord() { + // Shortcuts + var S = this._S; + var i = this._i; + var j = this._j; -},{"../method":38}],44:[function(require,module,exports){ -/* - This file is part of web3.js. + // Generate keystream word + var keystreamWord = 0; + for (var n = 0; n < 4; n++) { + i = (i + 1) % 256; + j = (j + S[i]) % 256; - web3.js 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. + // Swap + var t = S[i]; + S[i] = S[j]; + S[j] = t; - web3.js 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. + keystreamWord |= S[(S[i] + S[j]) % 256] << (24 - n * 8); + } - You should have received a copy of the GNU Lesser General Public License - along with web3.js. If not, see . -*/ -/** - * @file namereg.js - * @author Marek Kotewicz - * @date 2015 - */ + // Update counters + this._i = i; + this._j = j; -var globalRegistrarAbi = require('../contracts/GlobalRegistrar.json'); -var icapRegistrarAbi= require('../contracts/ICAPRegistrar.json'); + return keystreamWord; + } -var globalNameregAddress = '0xc6d9d2cd449a754c494264e1809c50e34d64562b'; -var icapNameregAddress = '0xa1a111bc074c9cfa781f0c38e63bd51c91b8af00'; + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4.decrypt(ciphertext, key, cfg); + */ + C.RC4 = StreamCipher._createHelper(RC4); + + /** + * Modified RC4 stream cipher algorithm. + */ + var RC4Drop = C_algo.RC4Drop = RC4.extend({ + /** + * Configuration options. + * + * @property {number} drop The number of keystream words to drop. Default 192 + */ + cfg: RC4.cfg.extend({ + drop: 192 + }), + + _doReset: function () { + RC4._doReset.call(this); + + // Drop + for (var i = this.cfg.drop; i > 0; i--) { + generateKeystreamWord.call(this); + } + } + }); -module.exports = { - global: { - abi: globalRegistrarAbi, - address: globalNameregAddress - }, - icap: { - abi: icapRegistrarAbi, - address: icapNameregAddress - } -}; + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4Drop.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4Drop.decrypt(ciphertext, key, cfg); + */ + C.RC4Drop = StreamCipher._createHelper(RC4Drop); + }()); + + + return CryptoJS.RC4; + + })); + },{"./cipher-core":51,"./core":52,"./enc-base64":53,"./evpkdf":55,"./md5":60}],75:[function(require,module,exports){ + ;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + /** @preserve + (c) 2012 by Cédric Mesnil. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Constants table + var _zl = WordArray.create([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13]); + var _zr = WordArray.create([ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11]); + var _sl = WordArray.create([ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 ]); + var _sr = WordArray.create([ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 ]); + + var _hl = WordArray.create([ 0x00000000, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E]); + var _hr = WordArray.create([ 0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000]); + + /** + * RIPEMD160 hash algorithm. + */ + var RIPEMD160 = C_algo.RIPEMD160 = Hasher.extend({ + _doReset: function () { + this._hash = WordArray.create([0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]); + }, + + _doProcessBlock: function (M, offset) { + + // Swap endian + for (var i = 0; i < 16; i++) { + // Shortcuts + var offset_i = offset + i; + var M_offset_i = M[offset_i]; + + // Swap + M[offset_i] = ( + (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | + (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + // Shortcut + var H = this._hash.words; + var hl = _hl.words; + var hr = _hr.words; + var zl = _zl.words; + var zr = _zr.words; + var sl = _sl.words; + var sr = _sr.words; + + // Working variables + var al, bl, cl, dl, el; + var ar, br, cr, dr, er; + + ar = al = H[0]; + br = bl = H[1]; + cr = cl = H[2]; + dr = dl = H[3]; + er = el = H[4]; + // Computation + var t; + for (var i = 0; i < 80; i += 1) { + t = (al + M[offset+zl[i]])|0; + if (i<16){ + t += f1(bl,cl,dl) + hl[0]; + } else if (i<32) { + t += f2(bl,cl,dl) + hl[1]; + } else if (i<48) { + t += f3(bl,cl,dl) + hl[2]; + } else if (i<64) { + t += f4(bl,cl,dl) + hl[3]; + } else {// if (i<80) { + t += f5(bl,cl,dl) + hl[4]; + } + t = t|0; + t = rotl(t,sl[i]); + t = (t+el)|0; + al = el; + el = dl; + dl = rotl(cl, 10); + cl = bl; + bl = t; + + t = (ar + M[offset+zr[i]])|0; + if (i<16){ + t += f5(br,cr,dr) + hr[0]; + } else if (i<32) { + t += f4(br,cr,dr) + hr[1]; + } else if (i<48) { + t += f3(br,cr,dr) + hr[2]; + } else if (i<64) { + t += f2(br,cr,dr) + hr[3]; + } else {// if (i<80) { + t += f1(br,cr,dr) + hr[4]; + } + t = t|0; + t = rotl(t,sr[i]) ; + t = (t+er)|0; + ar = er; + er = dr; + dr = rotl(cr, 10); + cr = br; + br = t; + } + // Intermediate hash value + t = (H[1] + cl + dr)|0; + H[1] = (H[2] + dl + er)|0; + H[2] = (H[3] + el + ar)|0; + H[3] = (H[4] + al + br)|0; + H[4] = (H[0] + bl + cr)|0; + H[0] = t; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotal << 8) | (nBitsTotal >>> 24)) & 0x00ff00ff) | + (((nBitsTotal << 24) | (nBitsTotal >>> 8)) & 0xff00ff00) + ); + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var hash = this._hash; + var H = hash.words; + + // Swap endian + for (var i = 0; i < 5; i++) { + // Shortcut + var H_i = H[i]; + + // Swap + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | + (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + // Return final computed hash + return hash; + }, -},{"../contracts/GlobalRegistrar.json":1,"../contracts/ICAPRegistrar.json":2}],45:[function(require,module,exports){ -/* - This file is part of web3.js. + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); - web3.js 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. + return clone; + } + }); - web3.js 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 web3.js. If not, see . -*/ -/** - * @file property.js - * @author Fabian Vogelsteller - * @author Marek Kotewicz - * @date 2015 - */ - -var utils = require('../utils/utils'); - -var Property = function (options) { - this.name = options.name; - this.getter = options.getter; - this.setter = options.setter; - this.outputFormatter = options.outputFormatter; - this.inputFormatter = options.inputFormatter; - this.requestManager = null; -}; - -Property.prototype.setRequestManager = function (rm) { - this.requestManager = rm; -}; - -/** - * Should be called to format input args of method - * - * @method formatInput - * @param {Array} - * @return {Array} - */ -Property.prototype.formatInput = function (arg) { - return this.inputFormatter ? this.inputFormatter(arg) : arg; -}; - -/** - * Should be called to format output(result) of method - * - * @method formatOutput - * @param {Object} - * @return {Object} - */ -Property.prototype.formatOutput = function (result) { - return this.outputFormatter && result !== null && result !== undefined ? this.outputFormatter(result) : result; -}; - -/** - * Should be used to extract callback from array of arguments. Modifies input param - * - * @method extractCallback - * @param {Array} arguments - * @return {Function|Null} callback, if exists - */ -Property.prototype.extractCallback = function (args) { - if (utils.isFunction(args[args.length - 1])) { - return args.pop(); // modify the args array! - } -}; - - -/** - * Should attach function to method - * - * @method attachToObject - * @param {Object} - * @param {Function} - */ -Property.prototype.attachToObject = function (obj) { - var proto = { - get: this.buildGet(), - enumerable: true - }; + function f1(x, y, z) { + return ((x) ^ (y) ^ (z)); - var names = this.name.split('.'); - var name = names[0]; - if (names.length > 1) { - obj[names[0]] = obj[names[0]] || {}; - obj = obj[names[0]]; - name = names[1]; - } + } - Object.defineProperty(obj, name, proto); - obj[asyncGetterName(name)] = this.buildAsyncGet(); -}; + function f2(x, y, z) { + return (((x)&(y)) | ((~x)&(z))); + } -var asyncGetterName = function (name) { - return 'get' + name.charAt(0).toUpperCase() + name.slice(1); -}; + function f3(x, y, z) { + return (((x) | (~(y))) ^ (z)); + } -Property.prototype.buildGet = function () { - var property = this; - return function get() { - return property.formatOutput(property.requestManager.send({ - method: property.getter - })); - }; -}; + function f4(x, y, z) { + return (((x) & (z)) | ((y)&(~(z)))); + } -Property.prototype.buildAsyncGet = function () { - var property = this; - var get = function (callback) { - property.requestManager.sendAsync({ - method: property.getter - }, function (err, result) { - callback(err, property.formatOutput(result)); - }); - }; - get.request = this.request.bind(this); - return get; -}; - -/** - * Should be called to create pure JSONRPC request which can be used in batch request - * - * @method request - * @param {...} params - * @return {Object} jsonrpc request - */ -Property.prototype.request = function () { - var payload = { - method: this.getter, - params: [], - callback: this.extractCallback(Array.prototype.slice.call(arguments)) - }; - payload.format = this.formatOutput.bind(this); - return payload; -}; + function f5(x, y, z) { + return ((x) ^ ((y) |(~(z)))); -module.exports = Property; + } + function rotl(x,n) { + return (x<>>(32-n)); + } -},{"../utils/utils":22}],46:[function(require,module,exports){ -/* - This file is part of web3.js. - web3.js 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. + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.RIPEMD160('message'); + * var hash = CryptoJS.RIPEMD160(wordArray); + */ + C.RIPEMD160 = Hasher._createHelper(RIPEMD160); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacRIPEMD160(message, key); + */ + C.HmacRIPEMD160 = Hasher._createHmacHelper(RIPEMD160); + }(Math)); + + + return CryptoJS.RIPEMD160; + + })); + },{"./core":52}],76:[function(require,module,exports){ + ;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Reusable object + var W = []; + + /** + * SHA-1 hash algorithm. + */ + var SHA1 = C_algo.SHA1 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0x67452301, 0xefcdab89, + 0x98badcfe, 0x10325476, + 0xc3d2e1f0 + ]); + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var H = this._hash.words; + + // Working variables + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + var e = H[4]; + + // Computation + for (var i = 0; i < 80; i++) { + if (i < 16) { + W[i] = M[offset + i] | 0; + } else { + var n = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]; + W[i] = (n << 1) | (n >>> 31); + } + + var t = ((a << 5) | (a >>> 27)) + e + W[i]; + if (i < 20) { + t += ((b & c) | (~b & d)) + 0x5a827999; + } else if (i < 40) { + t += (b ^ c ^ d) + 0x6ed9eba1; + } else if (i < 60) { + t += ((b & c) | (b & d) | (c & d)) - 0x70e44324; + } else /* if (i < 80) */ { + t += (b ^ c ^ d) - 0x359d3e2a; + } + + e = d; + d = c; + c = (b << 30) | (b >>> 2); + b = a; + a = t; + } - web3.js 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. + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + H[4] = (H[4] + e) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Return final computed hash + return this._hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); - You should have received a copy of the GNU Lesser General Public License - along with web3.js. If not, see . -*/ -/** - * @file requestmanager.js - * @author Jeffrey Wilcke - * @author Marek Kotewicz - * @author Marian Oancea - * @author Fabian Vogelsteller - * @author Gav Wood - * @date 2014 - */ - -var Jsonrpc = require('./jsonrpc'); -var utils = require('../utils/utils'); -var c = require('../utils/config'); -var errors = require('./errors'); - -/** - * It's responsible for passing messages to providers - * It's also responsible for polling the ethereum node for incoming messages - * Default poll timeout is 1 second - * Singleton - */ -var RequestManager = function (provider) { - this.provider = provider; - this.polls = {}; - this.timeout = null; -}; - -/** - * Should be used to synchronously send request - * - * @method send - * @param {Object} data - * @return {Object} - */ -RequestManager.prototype.send = function (data) { - if (!this.provider) { - console.error(errors.InvalidProvider()); - return null; - } + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA1('message'); + * var hash = CryptoJS.SHA1(wordArray); + */ + C.SHA1 = Hasher._createHelper(SHA1); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA1(message, key); + */ + C.HmacSHA1 = Hasher._createHmacHelper(SHA1); + }()); + + + return CryptoJS.SHA1; + + })); + },{"./core":52}],77:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./sha256")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./sha256"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var SHA256 = C_algo.SHA256; + + /** + * SHA-224 hash algorithm. + */ + var SHA224 = C_algo.SHA224 = SHA256.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, + 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 + ]); + }, + + _doFinalize: function () { + var hash = SHA256._doFinalize.call(this); + + hash.sigBytes -= 4; + + return hash; + } + }); - var payload = Jsonrpc.toPayload(data.method, data.params); - var result = this.provider.send(payload); + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA224('message'); + * var hash = CryptoJS.SHA224(wordArray); + */ + C.SHA224 = SHA256._createHelper(SHA224); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA224(message, key); + */ + C.HmacSHA224 = SHA256._createHmacHelper(SHA224); + }()); + + + return CryptoJS.SHA224; + + })); + },{"./core":52,"./sha256":78}],78:[function(require,module,exports){ + ;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Initialization and round constants tables + var H = []; + var K = []; + + // Compute constants + (function () { + function isPrime(n) { + var sqrtN = Math.sqrt(n); + for (var factor = 2; factor <= sqrtN; factor++) { + if (!(n % factor)) { + return false; + } + } - if (!Jsonrpc.isValidResponse(result)) { - throw errors.InvalidResponse(result); - } + return true; + } - return result.result; -}; - -/** - * Should be used to asynchronously send request - * - * @method sendAsync - * @param {Object} data - * @param {Function} callback - */ -RequestManager.prototype.sendAsync = function (data, callback) { - if (!this.provider) { - return callback(errors.InvalidProvider()); - } + function getFractionalBits(n) { + return ((n - (n | 0)) * 0x100000000) | 0; + } + + var n = 2; + var nPrime = 0; + while (nPrime < 64) { + if (isPrime(n)) { + if (nPrime < 8) { + H[nPrime] = getFractionalBits(Math.pow(n, 1 / 2)); + } + K[nPrime] = getFractionalBits(Math.pow(n, 1 / 3)); + + nPrime++; + } + + n++; + } + }()); + + // Reusable object + var W = []; + + /** + * SHA-256 hash algorithm. + */ + var SHA256 = C_algo.SHA256 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init(H.slice(0)); + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var H = this._hash.words; + + // Working variables + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + var e = H[4]; + var f = H[5]; + var g = H[6]; + var h = H[7]; + + // Computation + for (var i = 0; i < 64; i++) { + if (i < 16) { + W[i] = M[offset + i] | 0; + } else { + var gamma0x = W[i - 15]; + var gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^ + ((gamma0x << 14) | (gamma0x >>> 18)) ^ + (gamma0x >>> 3); + + var gamma1x = W[i - 2]; + var gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^ + ((gamma1x << 13) | (gamma1x >>> 19)) ^ + (gamma1x >>> 10); + + W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16]; + } + + var ch = (e & f) ^ (~e & g); + var maj = (a & b) ^ (a & c) ^ (b & c); + + var sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22)); + var sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7) | (e >>> 25)); + + var t1 = h + sigma1 + ch + K[i] + W[i]; + var t2 = sigma0 + maj; + + h = g; + g = f; + f = e; + e = (d + t1) | 0; + d = c; + c = b; + b = a; + a = (t1 + t2) | 0; + } - var payload = Jsonrpc.toPayload(data.method, data.params); - this.provider.sendAsync(payload, function (err, result) { - if (err) { - return callback(err); - } - - if (!Jsonrpc.isValidResponse(result)) { - return callback(errors.InvalidResponse(result)); - } + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + H[4] = (H[4] + e) | 0; + H[5] = (H[5] + f) | 0; + H[6] = (H[6] + g) | 0; + H[7] = (H[7] + h) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Return final computed hash + return this._hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); - callback(null, result.result); - }); -}; - -/** - * Should be called to asynchronously send batch request - * - * @method sendBatch - * @param {Array} batch data - * @param {Function} callback - */ -RequestManager.prototype.sendBatch = function (data, callback) { - if (!this.provider) { - return callback(errors.InvalidProvider()); - } + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA256('message'); + * var hash = CryptoJS.SHA256(wordArray); + */ + C.SHA256 = Hasher._createHelper(SHA256); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA256(message, key); + */ + C.HmacSHA256 = Hasher._createHmacHelper(SHA256); + }(Math)); + + + return CryptoJS.SHA256; + + })); + },{"./core":52}],79:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./x64-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./x64-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var C_algo = C.algo; + + // Constants tables + var RHO_OFFSETS = []; + var PI_INDEXES = []; + var ROUND_CONSTANTS = []; + + // Compute Constants + (function () { + // Compute rho offset constants + var x = 1, y = 0; + for (var t = 0; t < 24; t++) { + RHO_OFFSETS[x + 5 * y] = ((t + 1) * (t + 2) / 2) % 64; + + var newX = y % 5; + var newY = (2 * x + 3 * y) % 5; + x = newX; + y = newY; + } + + // Compute pi index constants + for (var x = 0; x < 5; x++) { + for (var y = 0; y < 5; y++) { + PI_INDEXES[x + 5 * y] = y + ((2 * x + 3 * y) % 5) * 5; + } + } + + // Compute round constants + var LFSR = 0x01; + for (var i = 0; i < 24; i++) { + var roundConstantMsw = 0; + var roundConstantLsw = 0; + + for (var j = 0; j < 7; j++) { + if (LFSR & 0x01) { + var bitPosition = (1 << j) - 1; + if (bitPosition < 32) { + roundConstantLsw ^= 1 << bitPosition; + } else /* if (bitPosition >= 32) */ { + roundConstantMsw ^= 1 << (bitPosition - 32); + } + } + + // Compute next LFSR + if (LFSR & 0x80) { + // Primitive polynomial over GF(2): x^8 + x^6 + x^5 + x^4 + 1 + LFSR = (LFSR << 1) ^ 0x71; + } else { + LFSR <<= 1; + } + } - var payload = Jsonrpc.toBatchPayload(data); + ROUND_CONSTANTS[i] = X64Word.create(roundConstantMsw, roundConstantLsw); + } + }()); + + // Reusable objects for temporary values + var T = []; + (function () { + for (var i = 0; i < 25; i++) { + T[i] = X64Word.create(); + } + }()); + + /** + * SHA-3 hash algorithm. + */ + var SHA3 = C_algo.SHA3 = Hasher.extend({ + /** + * Configuration options. + * + * @property {number} outputLength + * The desired number of bits in the output hash. + * Only values permitted are: 224, 256, 384, 512. + * Default: 512 + */ + cfg: Hasher.cfg.extend({ + outputLength: 512 + }), + + _doReset: function () { + var state = this._state = [] + for (var i = 0; i < 25; i++) { + state[i] = new X64Word.init(); + } - this.provider.sendAsync(payload, function (err, results) { - if (err) { - return callback(err); - } + this.blockSize = (1600 - 2 * this.cfg.outputLength) / 32; + }, - if (!utils.isArray(results)) { - return callback(errors.InvalidResponse(results)); - } + _doProcessBlock: function (M, offset) { + // Shortcuts + var state = this._state; + var nBlockSizeLanes = this.blockSize / 2; - callback(err, results); - }); -}; - -/** - * Should be used to set provider of request manager - * - * @method setProvider - * @param {Object} - */ -RequestManager.prototype.setProvider = function (p) { - this.provider = p; -}; - -/** - * Should be used to start polling - * - * @method startPolling - * @param {Object} data - * @param {Number} pollId - * @param {Function} callback - * @param {Function} uninstall - * - * @todo cleanup number of params - */ -RequestManager.prototype.startPolling = function (data, pollId, callback, uninstall) { - this.polls[pollId] = {data: data, id: pollId, callback: callback, uninstall: uninstall}; - - - // start polling - if (!this.timeout) { - this.poll(); - } -}; - -/** - * Should be used to stop polling for filter with given id - * - * @method stopPolling - * @param {Number} pollId - */ -RequestManager.prototype.stopPolling = function (pollId) { - delete this.polls[pollId]; - - // stop polling - if(Object.keys(this.polls).length === 0 && this.timeout) { - clearTimeout(this.timeout); - this.timeout = null; - } -}; + // Absorb + for (var i = 0; i < nBlockSizeLanes; i++) { + // Shortcuts + var M2i = M[offset + 2 * i]; + var M2i1 = M[offset + 2 * i + 1]; -/** - * Should be called to reset the polling mechanism of the request manager - * - * @method reset - */ -RequestManager.prototype.reset = function (keepIsSyncing) { - /*jshint maxcomplexity:5 */ + // Swap endian + M2i = ( + (((M2i << 8) | (M2i >>> 24)) & 0x00ff00ff) | + (((M2i << 24) | (M2i >>> 8)) & 0xff00ff00) + ); + M2i1 = ( + (((M2i1 << 8) | (M2i1 >>> 24)) & 0x00ff00ff) | + (((M2i1 << 24) | (M2i1 >>> 8)) & 0xff00ff00) + ); - for (var key in this.polls) { - // remove all polls, except sync polls, - // they need to be removed manually by calling syncing.stopWatching() - if(!keepIsSyncing || key.indexOf('syncPoll_') === -1) { - this.polls[key].uninstall(); - delete this.polls[key]; - } - } + // Absorb message into state + var lane = state[i]; + lane.high ^= M2i1; + lane.low ^= M2i; + } - // stop polling - if(Object.keys(this.polls).length === 0 && this.timeout) { - clearTimeout(this.timeout); - this.timeout = null; - } -}; - -/** - * Should be called to poll for changes on filter with given id - * - * @method poll - */ -RequestManager.prototype.poll = function () { - /*jshint maxcomplexity: 6 */ - this.timeout = setTimeout(this.poll.bind(this), c.ETH_POLLING_TIMEOUT); - - if (Object.keys(this.polls).length === 0) { - return; - } + // Rounds + for (var round = 0; round < 24; round++) { + // Theta + for (var x = 0; x < 5; x++) { + // Mix column lanes + var tMsw = 0, tLsw = 0; + for (var y = 0; y < 5; y++) { + var lane = state[x + 5 * y]; + tMsw ^= lane.high; + tLsw ^= lane.low; + } - if (!this.provider) { - console.error(errors.InvalidProvider()); - return; - } + // Temporary values + var Tx = T[x]; + Tx.high = tMsw; + Tx.low = tLsw; + } + for (var x = 0; x < 5; x++) { + // Shortcuts + var Tx4 = T[(x + 4) % 5]; + var Tx1 = T[(x + 1) % 5]; + var Tx1Msw = Tx1.high; + var Tx1Lsw = Tx1.low; + + // Mix surrounding columns + var tMsw = Tx4.high ^ ((Tx1Msw << 1) | (Tx1Lsw >>> 31)); + var tLsw = Tx4.low ^ ((Tx1Lsw << 1) | (Tx1Msw >>> 31)); + for (var y = 0; y < 5; y++) { + var lane = state[x + 5 * y]; + lane.high ^= tMsw; + lane.low ^= tLsw; + } + } + + // Rho Pi + for (var laneIndex = 1; laneIndex < 25; laneIndex++) { + // Shortcuts + var lane = state[laneIndex]; + var laneMsw = lane.high; + var laneLsw = lane.low; + var rhoOffset = RHO_OFFSETS[laneIndex]; + + // Rotate lanes + if (rhoOffset < 32) { + var tMsw = (laneMsw << rhoOffset) | (laneLsw >>> (32 - rhoOffset)); + var tLsw = (laneLsw << rhoOffset) | (laneMsw >>> (32 - rhoOffset)); + } else /* if (rhoOffset >= 32) */ { + var tMsw = (laneLsw << (rhoOffset - 32)) | (laneMsw >>> (64 - rhoOffset)); + var tLsw = (laneMsw << (rhoOffset - 32)) | (laneLsw >>> (64 - rhoOffset)); + } - var pollsData = []; - var pollsIds = []; - for (var key in this.polls) { - pollsData.push(this.polls[key].data); - pollsIds.push(key); - } + // Transpose lanes + var TPiLane = T[PI_INDEXES[laneIndex]]; + TPiLane.high = tMsw; + TPiLane.low = tLsw; + } + + // Rho pi at x = y = 0 + var T0 = T[0]; + var state0 = state[0]; + T0.high = state0.high; + T0.low = state0.low; + + // Chi + for (var x = 0; x < 5; x++) { + for (var y = 0; y < 5; y++) { + // Shortcuts + var laneIndex = x + 5 * y; + var lane = state[laneIndex]; + var TLane = T[laneIndex]; + var Tx1Lane = T[((x + 1) % 5) + 5 * y]; + var Tx2Lane = T[((x + 2) % 5) + 5 * y]; + + // Mix rows + lane.high = TLane.high ^ (~Tx1Lane.high & Tx2Lane.high); + lane.low = TLane.low ^ (~Tx1Lane.low & Tx2Lane.low); + } + } - if (pollsData.length === 0) { - return; - } + // Iota + var lane = state[0]; + var roundConstant = ROUND_CONSTANTS[round]; + lane.high ^= roundConstant.high; + lane.low ^= roundConstant.low;; + } + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + var blockSizeBits = this.blockSize * 32; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x1 << (24 - nBitsLeft % 32); + dataWords[((Math.ceil((nBitsLeft + 1) / blockSizeBits) * blockSizeBits) >>> 5) - 1] |= 0x80; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var state = this._state; + var outputLengthBytes = this.cfg.outputLength / 8; + var outputLengthLanes = outputLengthBytes / 8; + + // Squeeze + var hashWords = []; + for (var i = 0; i < outputLengthLanes; i++) { + // Shortcuts + var lane = state[i]; + var laneMsw = lane.high; + var laneLsw = lane.low; + + // Swap endian + laneMsw = ( + (((laneMsw << 8) | (laneMsw >>> 24)) & 0x00ff00ff) | + (((laneMsw << 24) | (laneMsw >>> 8)) & 0xff00ff00) + ); + laneLsw = ( + (((laneLsw << 8) | (laneLsw >>> 24)) & 0x00ff00ff) | + (((laneLsw << 24) | (laneLsw >>> 8)) & 0xff00ff00) + ); - var payload = Jsonrpc.toBatchPayload(pollsData); - - // map the request id to they poll id - var pollsIdMap = {}; - payload.forEach(function(load, index){ - pollsIdMap[load.id] = pollsIds[index]; - }); + // Squeeze state to retrieve hash + hashWords.push(laneLsw); + hashWords.push(laneMsw); + } + // Return final computed hash + return new WordArray.init(hashWords, outputLengthBytes); + }, - var self = this; - this.provider.sendAsync(payload, function (error, results) { + clone: function () { + var clone = Hasher.clone.call(this); + var state = clone._state = this._state.slice(0); + for (var i = 0; i < 25; i++) { + state[i] = state[i].clone(); + } - // TODO: console log? - if (error) { - return; - } + return clone; + } + }); - if (!utils.isArray(results)) { - throw errors.InvalidResponse(results); + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA3('message'); + * var hash = CryptoJS.SHA3(wordArray); + */ + C.SHA3 = Hasher._createHelper(SHA3); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA3(message, key); + */ + C.HmacSHA3 = Hasher._createHmacHelper(SHA3); + }(Math)); + + + return CryptoJS.SHA3; + + })); + },{"./core":52,"./x64-core":83}],80:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./x64-core"), require("./sha512")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./x64-core", "./sha512"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var X64WordArray = C_x64.WordArray; + var C_algo = C.algo; + var SHA512 = C_algo.SHA512; + + /** + * SHA-384 hash algorithm. + */ + var SHA384 = C_algo.SHA384 = SHA512.extend({ + _doReset: function () { + this._hash = new X64WordArray.init([ + new X64Word.init(0xcbbb9d5d, 0xc1059ed8), new X64Word.init(0x629a292a, 0x367cd507), + new X64Word.init(0x9159015a, 0x3070dd17), new X64Word.init(0x152fecd8, 0xf70e5939), + new X64Word.init(0x67332667, 0xffc00b31), new X64Word.init(0x8eb44a87, 0x68581511), + new X64Word.init(0xdb0c2e0d, 0x64f98fa7), new X64Word.init(0x47b5481d, 0xbefa4fa4) + ]); + }, + + _doFinalize: function () { + var hash = SHA512._doFinalize.call(this); + + hash.sigBytes -= 16; + + return hash; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA384('message'); + * var hash = CryptoJS.SHA384(wordArray); + */ + C.SHA384 = SHA512._createHelper(SHA384); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA384(message, key); + */ + C.HmacSHA384 = SHA512._createHmacHelper(SHA384); + }()); + + + return CryptoJS.SHA384; + + })); + },{"./core":52,"./sha512":81,"./x64-core":83}],81:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./x64-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./x64-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Hasher = C_lib.Hasher; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var X64WordArray = C_x64.WordArray; + var C_algo = C.algo; + + function X64Word_create() { + return X64Word.create.apply(X64Word, arguments); } - results.map(function (result) { - var id = pollsIdMap[result.id]; - // make sure the filter is still installed after arrival of the request - if (self.polls[id]) { - result.callback = self.polls[id].callback; - return result; - } else - return false; - }).filter(function (result) { - return !!result; - }).filter(function (result) { - var valid = Jsonrpc.isValidResponse(result); - if (!valid) { - result.callback(errors.InvalidResponse(result)); + // Constants + var K = [ + X64Word_create(0x428a2f98, 0xd728ae22), X64Word_create(0x71374491, 0x23ef65cd), + X64Word_create(0xb5c0fbcf, 0xec4d3b2f), X64Word_create(0xe9b5dba5, 0x8189dbbc), + X64Word_create(0x3956c25b, 0xf348b538), X64Word_create(0x59f111f1, 0xb605d019), + X64Word_create(0x923f82a4, 0xaf194f9b), X64Word_create(0xab1c5ed5, 0xda6d8118), + X64Word_create(0xd807aa98, 0xa3030242), X64Word_create(0x12835b01, 0x45706fbe), + X64Word_create(0x243185be, 0x4ee4b28c), X64Word_create(0x550c7dc3, 0xd5ffb4e2), + X64Word_create(0x72be5d74, 0xf27b896f), X64Word_create(0x80deb1fe, 0x3b1696b1), + X64Word_create(0x9bdc06a7, 0x25c71235), X64Word_create(0xc19bf174, 0xcf692694), + X64Word_create(0xe49b69c1, 0x9ef14ad2), X64Word_create(0xefbe4786, 0x384f25e3), + X64Word_create(0x0fc19dc6, 0x8b8cd5b5), X64Word_create(0x240ca1cc, 0x77ac9c65), + X64Word_create(0x2de92c6f, 0x592b0275), X64Word_create(0x4a7484aa, 0x6ea6e483), + X64Word_create(0x5cb0a9dc, 0xbd41fbd4), X64Word_create(0x76f988da, 0x831153b5), + X64Word_create(0x983e5152, 0xee66dfab), X64Word_create(0xa831c66d, 0x2db43210), + X64Word_create(0xb00327c8, 0x98fb213f), X64Word_create(0xbf597fc7, 0xbeef0ee4), + X64Word_create(0xc6e00bf3, 0x3da88fc2), X64Word_create(0xd5a79147, 0x930aa725), + X64Word_create(0x06ca6351, 0xe003826f), X64Word_create(0x14292967, 0x0a0e6e70), + X64Word_create(0x27b70a85, 0x46d22ffc), X64Word_create(0x2e1b2138, 0x5c26c926), + X64Word_create(0x4d2c6dfc, 0x5ac42aed), X64Word_create(0x53380d13, 0x9d95b3df), + X64Word_create(0x650a7354, 0x8baf63de), X64Word_create(0x766a0abb, 0x3c77b2a8), + X64Word_create(0x81c2c92e, 0x47edaee6), X64Word_create(0x92722c85, 0x1482353b), + X64Word_create(0xa2bfe8a1, 0x4cf10364), X64Word_create(0xa81a664b, 0xbc423001), + X64Word_create(0xc24b8b70, 0xd0f89791), X64Word_create(0xc76c51a3, 0x0654be30), + X64Word_create(0xd192e819, 0xd6ef5218), X64Word_create(0xd6990624, 0x5565a910), + X64Word_create(0xf40e3585, 0x5771202a), X64Word_create(0x106aa070, 0x32bbd1b8), + X64Word_create(0x19a4c116, 0xb8d2d0c8), X64Word_create(0x1e376c08, 0x5141ab53), + X64Word_create(0x2748774c, 0xdf8eeb99), X64Word_create(0x34b0bcb5, 0xe19b48a8), + X64Word_create(0x391c0cb3, 0xc5c95a63), X64Word_create(0x4ed8aa4a, 0xe3418acb), + X64Word_create(0x5b9cca4f, 0x7763e373), X64Word_create(0x682e6ff3, 0xd6b2b8a3), + X64Word_create(0x748f82ee, 0x5defb2fc), X64Word_create(0x78a5636f, 0x43172f60), + X64Word_create(0x84c87814, 0xa1f0ab72), X64Word_create(0x8cc70208, 0x1a6439ec), + X64Word_create(0x90befffa, 0x23631e28), X64Word_create(0xa4506ceb, 0xde82bde9), + X64Word_create(0xbef9a3f7, 0xb2c67915), X64Word_create(0xc67178f2, 0xe372532b), + X64Word_create(0xca273ece, 0xea26619c), X64Word_create(0xd186b8c7, 0x21c0c207), + X64Word_create(0xeada7dd6, 0xcde0eb1e), X64Word_create(0xf57d4f7f, 0xee6ed178), + X64Word_create(0x06f067aa, 0x72176fba), X64Word_create(0x0a637dc5, 0xa2c898a6), + X64Word_create(0x113f9804, 0xbef90dae), X64Word_create(0x1b710b35, 0x131c471b), + X64Word_create(0x28db77f5, 0x23047d84), X64Word_create(0x32caab7b, 0x40c72493), + X64Word_create(0x3c9ebe0a, 0x15c9bebc), X64Word_create(0x431d67c4, 0x9c100d4c), + X64Word_create(0x4cc5d4be, 0xcb3e42b6), X64Word_create(0x597f299c, 0xfc657e2a), + X64Word_create(0x5fcb6fab, 0x3ad6faec), X64Word_create(0x6c44198c, 0x4a475817) + ]; + + // Reusable objects + var W = []; + (function () { + for (var i = 0; i < 80; i++) { + W[i] = X64Word_create(); + } + }()); + + /** + * SHA-512 hash algorithm. + */ + var SHA512 = C_algo.SHA512 = Hasher.extend({ + _doReset: function () { + this._hash = new X64WordArray.init([ + new X64Word.init(0x6a09e667, 0xf3bcc908), new X64Word.init(0xbb67ae85, 0x84caa73b), + new X64Word.init(0x3c6ef372, 0xfe94f82b), new X64Word.init(0xa54ff53a, 0x5f1d36f1), + new X64Word.init(0x510e527f, 0xade682d1), new X64Word.init(0x9b05688c, 0x2b3e6c1f), + new X64Word.init(0x1f83d9ab, 0xfb41bd6b), new X64Word.init(0x5be0cd19, 0x137e2179) + ]); + }, + + _doProcessBlock: function (M, offset) { + // Shortcuts + var H = this._hash.words; + + var H0 = H[0]; + var H1 = H[1]; + var H2 = H[2]; + var H3 = H[3]; + var H4 = H[4]; + var H5 = H[5]; + var H6 = H[6]; + var H7 = H[7]; + + var H0h = H0.high; + var H0l = H0.low; + var H1h = H1.high; + var H1l = H1.low; + var H2h = H2.high; + var H2l = H2.low; + var H3h = H3.high; + var H3l = H3.low; + var H4h = H4.high; + var H4l = H4.low; + var H5h = H5.high; + var H5l = H5.low; + var H6h = H6.high; + var H6l = H6.low; + var H7h = H7.high; + var H7l = H7.low; + + // Working variables + var ah = H0h; + var al = H0l; + var bh = H1h; + var bl = H1l; + var ch = H2h; + var cl = H2l; + var dh = H3h; + var dl = H3l; + var eh = H4h; + var el = H4l; + var fh = H5h; + var fl = H5l; + var gh = H6h; + var gl = H6l; + var hh = H7h; + var hl = H7l; + + // Rounds + for (var i = 0; i < 80; i++) { + // Shortcut + var Wi = W[i]; + + // Extend message + if (i < 16) { + var Wih = Wi.high = M[offset + i * 2] | 0; + var Wil = Wi.low = M[offset + i * 2 + 1] | 0; + } else { + // Gamma0 + var gamma0x = W[i - 15]; + var gamma0xh = gamma0x.high; + var gamma0xl = gamma0x.low; + var gamma0h = ((gamma0xh >>> 1) | (gamma0xl << 31)) ^ ((gamma0xh >>> 8) | (gamma0xl << 24)) ^ (gamma0xh >>> 7); + var gamma0l = ((gamma0xl >>> 1) | (gamma0xh << 31)) ^ ((gamma0xl >>> 8) | (gamma0xh << 24)) ^ ((gamma0xl >>> 7) | (gamma0xh << 25)); + + // Gamma1 + var gamma1x = W[i - 2]; + var gamma1xh = gamma1x.high; + var gamma1xl = gamma1x.low; + var gamma1h = ((gamma1xh >>> 19) | (gamma1xl << 13)) ^ ((gamma1xh << 3) | (gamma1xl >>> 29)) ^ (gamma1xh >>> 6); + var gamma1l = ((gamma1xl >>> 19) | (gamma1xh << 13)) ^ ((gamma1xl << 3) | (gamma1xh >>> 29)) ^ ((gamma1xl >>> 6) | (gamma1xh << 26)); + + // W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16] + var Wi7 = W[i - 7]; + var Wi7h = Wi7.high; + var Wi7l = Wi7.low; + + var Wi16 = W[i - 16]; + var Wi16h = Wi16.high; + var Wi16l = Wi16.low; + + var Wil = gamma0l + Wi7l; + var Wih = gamma0h + Wi7h + ((Wil >>> 0) < (gamma0l >>> 0) ? 1 : 0); + var Wil = Wil + gamma1l; + var Wih = Wih + gamma1h + ((Wil >>> 0) < (gamma1l >>> 0) ? 1 : 0); + var Wil = Wil + Wi16l; + var Wih = Wih + Wi16h + ((Wil >>> 0) < (Wi16l >>> 0) ? 1 : 0); + + Wi.high = Wih; + Wi.low = Wil; + } + + var chh = (eh & fh) ^ (~eh & gh); + var chl = (el & fl) ^ (~el & gl); + var majh = (ah & bh) ^ (ah & ch) ^ (bh & ch); + var majl = (al & bl) ^ (al & cl) ^ (bl & cl); + + var sigma0h = ((ah >>> 28) | (al << 4)) ^ ((ah << 30) | (al >>> 2)) ^ ((ah << 25) | (al >>> 7)); + var sigma0l = ((al >>> 28) | (ah << 4)) ^ ((al << 30) | (ah >>> 2)) ^ ((al << 25) | (ah >>> 7)); + var sigma1h = ((eh >>> 14) | (el << 18)) ^ ((eh >>> 18) | (el << 14)) ^ ((eh << 23) | (el >>> 9)); + var sigma1l = ((el >>> 14) | (eh << 18)) ^ ((el >>> 18) | (eh << 14)) ^ ((el << 23) | (eh >>> 9)); + + // t1 = h + sigma1 + ch + K[i] + W[i] + var Ki = K[i]; + var Kih = Ki.high; + var Kil = Ki.low; + + var t1l = hl + sigma1l; + var t1h = hh + sigma1h + ((t1l >>> 0) < (hl >>> 0) ? 1 : 0); + var t1l = t1l + chl; + var t1h = t1h + chh + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0); + var t1l = t1l + Kil; + var t1h = t1h + Kih + ((t1l >>> 0) < (Kil >>> 0) ? 1 : 0); + var t1l = t1l + Wil; + var t1h = t1h + Wih + ((t1l >>> 0) < (Wil >>> 0) ? 1 : 0); + + // t2 = sigma0 + maj + var t2l = sigma0l + majl; + var t2h = sigma0h + majh + ((t2l >>> 0) < (sigma0l >>> 0) ? 1 : 0); + + // Update working variables + hh = gh; + hl = gl; + gh = fh; + gl = fl; + fh = eh; + fl = el; + el = (dl + t1l) | 0; + eh = (dh + t1h + ((el >>> 0) < (dl >>> 0) ? 1 : 0)) | 0; + dh = ch; + dl = cl; + ch = bh; + cl = bl; + bh = ah; + bl = al; + al = (t1l + t2l) | 0; + ah = (t1h + t2h + ((al >>> 0) < (t1l >>> 0) ? 1 : 0)) | 0; } - return valid; - }).forEach(function (result) { - result.callback(null, result.result); + + // Intermediate hash value + H0l = H0.low = (H0l + al); + H0.high = (H0h + ah + ((H0l >>> 0) < (al >>> 0) ? 1 : 0)); + H1l = H1.low = (H1l + bl); + H1.high = (H1h + bh + ((H1l >>> 0) < (bl >>> 0) ? 1 : 0)); + H2l = H2.low = (H2l + cl); + H2.high = (H2h + ch + ((H2l >>> 0) < (cl >>> 0) ? 1 : 0)); + H3l = H3.low = (H3l + dl); + H3.high = (H3h + dh + ((H3l >>> 0) < (dl >>> 0) ? 1 : 0)); + H4l = H4.low = (H4l + el); + H4.high = (H4h + eh + ((H4l >>> 0) < (el >>> 0) ? 1 : 0)); + H5l = H5.low = (H5l + fl); + H5.high = (H5h + fh + ((H5l >>> 0) < (fl >>> 0) ? 1 : 0)); + H6l = H6.low = (H6l + gl); + H6.high = (H6h + gh + ((H6l >>> 0) < (gl >>> 0) ? 1 : 0)); + H7l = H7.low = (H7l + hl); + H7.high = (H7h + hh + ((H7l >>> 0) < (hl >>> 0) ? 1 : 0)); + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 30] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 31] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Convert hash to 32-bit word array before returning + var hash = this._hash.toX32(); + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + }, + + blockSize: 1024/32 }); - }); -}; -module.exports = RequestManager; + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA512('message'); + * var hash = CryptoJS.SHA512(wordArray); + */ + C.SHA512 = Hasher._createHelper(SHA512); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA512(message, key); + */ + C.HmacSHA512 = Hasher._createHmacHelper(SHA512); + }()); + + + return CryptoJS.SHA512; + + })); + },{"./core":52,"./x64-core":83}],82:[function(require,module,exports){ + ;(function (root, factory, undef) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var BlockCipher = C_lib.BlockCipher; + var C_algo = C.algo; + + // Permuted Choice 1 constants + var PC1 = [ + 57, 49, 41, 33, 25, 17, 9, 1, + 58, 50, 42, 34, 26, 18, 10, 2, + 59, 51, 43, 35, 27, 19, 11, 3, + 60, 52, 44, 36, 63, 55, 47, 39, + 31, 23, 15, 7, 62, 54, 46, 38, + 30, 22, 14, 6, 61, 53, 45, 37, + 29, 21, 13, 5, 28, 20, 12, 4 + ]; + + // Permuted Choice 2 constants + var PC2 = [ + 14, 17, 11, 24, 1, 5, + 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, + 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, + 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, + 46, 42, 50, 36, 29, 32 + ]; + + // Cumulative bit shift constants + var BIT_SHIFTS = [1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28]; + + // SBOXes and round permutation constants + var SBOX_P = [ + { + 0x0: 0x808200, + 0x10000000: 0x8000, + 0x20000000: 0x808002, + 0x30000000: 0x2, + 0x40000000: 0x200, + 0x50000000: 0x808202, + 0x60000000: 0x800202, + 0x70000000: 0x800000, + 0x80000000: 0x202, + 0x90000000: 0x800200, + 0xa0000000: 0x8200, + 0xb0000000: 0x808000, + 0xc0000000: 0x8002, + 0xd0000000: 0x800002, + 0xe0000000: 0x0, + 0xf0000000: 0x8202, + 0x8000000: 0x0, + 0x18000000: 0x808202, + 0x28000000: 0x8202, + 0x38000000: 0x8000, + 0x48000000: 0x808200, + 0x58000000: 0x200, + 0x68000000: 0x808002, + 0x78000000: 0x2, + 0x88000000: 0x800200, + 0x98000000: 0x8200, + 0xa8000000: 0x808000, + 0xb8000000: 0x800202, + 0xc8000000: 0x800002, + 0xd8000000: 0x8002, + 0xe8000000: 0x202, + 0xf8000000: 0x800000, + 0x1: 0x8000, + 0x10000001: 0x2, + 0x20000001: 0x808200, + 0x30000001: 0x800000, + 0x40000001: 0x808002, + 0x50000001: 0x8200, + 0x60000001: 0x200, + 0x70000001: 0x800202, + 0x80000001: 0x808202, + 0x90000001: 0x808000, + 0xa0000001: 0x800002, + 0xb0000001: 0x8202, + 0xc0000001: 0x202, + 0xd0000001: 0x800200, + 0xe0000001: 0x8002, + 0xf0000001: 0x0, + 0x8000001: 0x808202, + 0x18000001: 0x808000, + 0x28000001: 0x800000, + 0x38000001: 0x200, + 0x48000001: 0x8000, + 0x58000001: 0x800002, + 0x68000001: 0x2, + 0x78000001: 0x8202, + 0x88000001: 0x8002, + 0x98000001: 0x800202, + 0xa8000001: 0x202, + 0xb8000001: 0x808200, + 0xc8000001: 0x800200, + 0xd8000001: 0x0, + 0xe8000001: 0x8200, + 0xf8000001: 0x808002 + }, + { + 0x0: 0x40084010, + 0x1000000: 0x4000, + 0x2000000: 0x80000, + 0x3000000: 0x40080010, + 0x4000000: 0x40000010, + 0x5000000: 0x40084000, + 0x6000000: 0x40004000, + 0x7000000: 0x10, + 0x8000000: 0x84000, + 0x9000000: 0x40004010, + 0xa000000: 0x40000000, + 0xb000000: 0x84010, + 0xc000000: 0x80010, + 0xd000000: 0x0, + 0xe000000: 0x4010, + 0xf000000: 0x40080000, + 0x800000: 0x40004000, + 0x1800000: 0x84010, + 0x2800000: 0x10, + 0x3800000: 0x40004010, + 0x4800000: 0x40084010, + 0x5800000: 0x40000000, + 0x6800000: 0x80000, + 0x7800000: 0x40080010, + 0x8800000: 0x80010, + 0x9800000: 0x0, + 0xa800000: 0x4000, + 0xb800000: 0x40080000, + 0xc800000: 0x40000010, + 0xd800000: 0x84000, + 0xe800000: 0x40084000, + 0xf800000: 0x4010, + 0x10000000: 0x0, + 0x11000000: 0x40080010, + 0x12000000: 0x40004010, + 0x13000000: 0x40084000, + 0x14000000: 0x40080000, + 0x15000000: 0x10, + 0x16000000: 0x84010, + 0x17000000: 0x4000, + 0x18000000: 0x4010, + 0x19000000: 0x80000, + 0x1a000000: 0x80010, + 0x1b000000: 0x40000010, + 0x1c000000: 0x84000, + 0x1d000000: 0x40004000, + 0x1e000000: 0x40000000, + 0x1f000000: 0x40084010, + 0x10800000: 0x84010, + 0x11800000: 0x80000, + 0x12800000: 0x40080000, + 0x13800000: 0x4000, + 0x14800000: 0x40004000, + 0x15800000: 0x40084010, + 0x16800000: 0x10, + 0x17800000: 0x40000000, + 0x18800000: 0x40084000, + 0x19800000: 0x40000010, + 0x1a800000: 0x40004010, + 0x1b800000: 0x80010, + 0x1c800000: 0x0, + 0x1d800000: 0x4010, + 0x1e800000: 0x40080010, + 0x1f800000: 0x84000 + }, + { + 0x0: 0x104, + 0x100000: 0x0, + 0x200000: 0x4000100, + 0x300000: 0x10104, + 0x400000: 0x10004, + 0x500000: 0x4000004, + 0x600000: 0x4010104, + 0x700000: 0x4010000, + 0x800000: 0x4000000, + 0x900000: 0x4010100, + 0xa00000: 0x10100, + 0xb00000: 0x4010004, + 0xc00000: 0x4000104, + 0xd00000: 0x10000, + 0xe00000: 0x4, + 0xf00000: 0x100, + 0x80000: 0x4010100, + 0x180000: 0x4010004, + 0x280000: 0x0, + 0x380000: 0x4000100, + 0x480000: 0x4000004, + 0x580000: 0x10000, + 0x680000: 0x10004, + 0x780000: 0x104, + 0x880000: 0x4, + 0x980000: 0x100, + 0xa80000: 0x4010000, + 0xb80000: 0x10104, + 0xc80000: 0x10100, + 0xd80000: 0x4000104, + 0xe80000: 0x4010104, + 0xf80000: 0x4000000, + 0x1000000: 0x4010100, + 0x1100000: 0x10004, + 0x1200000: 0x10000, + 0x1300000: 0x4000100, + 0x1400000: 0x100, + 0x1500000: 0x4010104, + 0x1600000: 0x4000004, + 0x1700000: 0x0, + 0x1800000: 0x4000104, + 0x1900000: 0x4000000, + 0x1a00000: 0x4, + 0x1b00000: 0x10100, + 0x1c00000: 0x4010000, + 0x1d00000: 0x104, + 0x1e00000: 0x10104, + 0x1f00000: 0x4010004, + 0x1080000: 0x4000000, + 0x1180000: 0x104, + 0x1280000: 0x4010100, + 0x1380000: 0x0, + 0x1480000: 0x10004, + 0x1580000: 0x4000100, + 0x1680000: 0x100, + 0x1780000: 0x4010004, + 0x1880000: 0x10000, + 0x1980000: 0x4010104, + 0x1a80000: 0x10104, + 0x1b80000: 0x4000004, + 0x1c80000: 0x4000104, + 0x1d80000: 0x4010000, + 0x1e80000: 0x4, + 0x1f80000: 0x10100 + }, + { + 0x0: 0x80401000, + 0x10000: 0x80001040, + 0x20000: 0x401040, + 0x30000: 0x80400000, + 0x40000: 0x0, + 0x50000: 0x401000, + 0x60000: 0x80000040, + 0x70000: 0x400040, + 0x80000: 0x80000000, + 0x90000: 0x400000, + 0xa0000: 0x40, + 0xb0000: 0x80001000, + 0xc0000: 0x80400040, + 0xd0000: 0x1040, + 0xe0000: 0x1000, + 0xf0000: 0x80401040, + 0x8000: 0x80001040, + 0x18000: 0x40, + 0x28000: 0x80400040, + 0x38000: 0x80001000, + 0x48000: 0x401000, + 0x58000: 0x80401040, + 0x68000: 0x0, + 0x78000: 0x80400000, + 0x88000: 0x1000, + 0x98000: 0x80401000, + 0xa8000: 0x400000, + 0xb8000: 0x1040, + 0xc8000: 0x80000000, + 0xd8000: 0x400040, + 0xe8000: 0x401040, + 0xf8000: 0x80000040, + 0x100000: 0x400040, + 0x110000: 0x401000, + 0x120000: 0x80000040, + 0x130000: 0x0, + 0x140000: 0x1040, + 0x150000: 0x80400040, + 0x160000: 0x80401000, + 0x170000: 0x80001040, + 0x180000: 0x80401040, + 0x190000: 0x80000000, + 0x1a0000: 0x80400000, + 0x1b0000: 0x401040, + 0x1c0000: 0x80001000, + 0x1d0000: 0x400000, + 0x1e0000: 0x40, + 0x1f0000: 0x1000, + 0x108000: 0x80400000, + 0x118000: 0x80401040, + 0x128000: 0x0, + 0x138000: 0x401000, + 0x148000: 0x400040, + 0x158000: 0x80000000, + 0x168000: 0x80001040, + 0x178000: 0x40, + 0x188000: 0x80000040, + 0x198000: 0x1000, + 0x1a8000: 0x80001000, + 0x1b8000: 0x80400040, + 0x1c8000: 0x1040, + 0x1d8000: 0x80401000, + 0x1e8000: 0x400000, + 0x1f8000: 0x401040 + }, + { + 0x0: 0x80, + 0x1000: 0x1040000, + 0x2000: 0x40000, + 0x3000: 0x20000000, + 0x4000: 0x20040080, + 0x5000: 0x1000080, + 0x6000: 0x21000080, + 0x7000: 0x40080, + 0x8000: 0x1000000, + 0x9000: 0x20040000, + 0xa000: 0x20000080, + 0xb000: 0x21040080, + 0xc000: 0x21040000, + 0xd000: 0x0, + 0xe000: 0x1040080, + 0xf000: 0x21000000, + 0x800: 0x1040080, + 0x1800: 0x21000080, + 0x2800: 0x80, + 0x3800: 0x1040000, + 0x4800: 0x40000, + 0x5800: 0x20040080, + 0x6800: 0x21040000, + 0x7800: 0x20000000, + 0x8800: 0x20040000, + 0x9800: 0x0, + 0xa800: 0x21040080, + 0xb800: 0x1000080, + 0xc800: 0x20000080, + 0xd800: 0x21000000, + 0xe800: 0x1000000, + 0xf800: 0x40080, + 0x10000: 0x40000, + 0x11000: 0x80, + 0x12000: 0x20000000, + 0x13000: 0x21000080, + 0x14000: 0x1000080, + 0x15000: 0x21040000, + 0x16000: 0x20040080, + 0x17000: 0x1000000, + 0x18000: 0x21040080, + 0x19000: 0x21000000, + 0x1a000: 0x1040000, + 0x1b000: 0x20040000, + 0x1c000: 0x40080, + 0x1d000: 0x20000080, + 0x1e000: 0x0, + 0x1f000: 0x1040080, + 0x10800: 0x21000080, + 0x11800: 0x1000000, + 0x12800: 0x1040000, + 0x13800: 0x20040080, + 0x14800: 0x20000000, + 0x15800: 0x1040080, + 0x16800: 0x80, + 0x17800: 0x21040000, + 0x18800: 0x40080, + 0x19800: 0x21040080, + 0x1a800: 0x0, + 0x1b800: 0x21000000, + 0x1c800: 0x1000080, + 0x1d800: 0x40000, + 0x1e800: 0x20040000, + 0x1f800: 0x20000080 + }, + { + 0x0: 0x10000008, + 0x100: 0x2000, + 0x200: 0x10200000, + 0x300: 0x10202008, + 0x400: 0x10002000, + 0x500: 0x200000, + 0x600: 0x200008, + 0x700: 0x10000000, + 0x800: 0x0, + 0x900: 0x10002008, + 0xa00: 0x202000, + 0xb00: 0x8, + 0xc00: 0x10200008, + 0xd00: 0x202008, + 0xe00: 0x2008, + 0xf00: 0x10202000, + 0x80: 0x10200000, + 0x180: 0x10202008, + 0x280: 0x8, + 0x380: 0x200000, + 0x480: 0x202008, + 0x580: 0x10000008, + 0x680: 0x10002000, + 0x780: 0x2008, + 0x880: 0x200008, + 0x980: 0x2000, + 0xa80: 0x10002008, + 0xb80: 0x10200008, + 0xc80: 0x0, + 0xd80: 0x10202000, + 0xe80: 0x202000, + 0xf80: 0x10000000, + 0x1000: 0x10002000, + 0x1100: 0x10200008, + 0x1200: 0x10202008, + 0x1300: 0x2008, + 0x1400: 0x200000, + 0x1500: 0x10000000, + 0x1600: 0x10000008, + 0x1700: 0x202000, + 0x1800: 0x202008, + 0x1900: 0x0, + 0x1a00: 0x8, + 0x1b00: 0x10200000, + 0x1c00: 0x2000, + 0x1d00: 0x10002008, + 0x1e00: 0x10202000, + 0x1f00: 0x200008, + 0x1080: 0x8, + 0x1180: 0x202000, + 0x1280: 0x200000, + 0x1380: 0x10000008, + 0x1480: 0x10002000, + 0x1580: 0x2008, + 0x1680: 0x10202008, + 0x1780: 0x10200000, + 0x1880: 0x10202000, + 0x1980: 0x10200008, + 0x1a80: 0x2000, + 0x1b80: 0x202008, + 0x1c80: 0x200008, + 0x1d80: 0x0, + 0x1e80: 0x10000000, + 0x1f80: 0x10002008 + }, + { + 0x0: 0x100000, + 0x10: 0x2000401, + 0x20: 0x400, + 0x30: 0x100401, + 0x40: 0x2100401, + 0x50: 0x0, + 0x60: 0x1, + 0x70: 0x2100001, + 0x80: 0x2000400, + 0x90: 0x100001, + 0xa0: 0x2000001, + 0xb0: 0x2100400, + 0xc0: 0x2100000, + 0xd0: 0x401, + 0xe0: 0x100400, + 0xf0: 0x2000000, + 0x8: 0x2100001, + 0x18: 0x0, + 0x28: 0x2000401, + 0x38: 0x2100400, + 0x48: 0x100000, + 0x58: 0x2000001, + 0x68: 0x2000000, + 0x78: 0x401, + 0x88: 0x100401, + 0x98: 0x2000400, + 0xa8: 0x2100000, + 0xb8: 0x100001, + 0xc8: 0x400, + 0xd8: 0x2100401, + 0xe8: 0x1, + 0xf8: 0x100400, + 0x100: 0x2000000, + 0x110: 0x100000, + 0x120: 0x2000401, + 0x130: 0x2100001, + 0x140: 0x100001, + 0x150: 0x2000400, + 0x160: 0x2100400, + 0x170: 0x100401, + 0x180: 0x401, + 0x190: 0x2100401, + 0x1a0: 0x100400, + 0x1b0: 0x1, + 0x1c0: 0x0, + 0x1d0: 0x2100000, + 0x1e0: 0x2000001, + 0x1f0: 0x400, + 0x108: 0x100400, + 0x118: 0x2000401, + 0x128: 0x2100001, + 0x138: 0x1, + 0x148: 0x2000000, + 0x158: 0x100000, + 0x168: 0x401, + 0x178: 0x2100400, + 0x188: 0x2000001, + 0x198: 0x2100000, + 0x1a8: 0x0, + 0x1b8: 0x2100401, + 0x1c8: 0x100401, + 0x1d8: 0x400, + 0x1e8: 0x2000400, + 0x1f8: 0x100001 + }, + { + 0x0: 0x8000820, + 0x1: 0x20000, + 0x2: 0x8000000, + 0x3: 0x20, + 0x4: 0x20020, + 0x5: 0x8020820, + 0x6: 0x8020800, + 0x7: 0x800, + 0x8: 0x8020000, + 0x9: 0x8000800, + 0xa: 0x20800, + 0xb: 0x8020020, + 0xc: 0x820, + 0xd: 0x0, + 0xe: 0x8000020, + 0xf: 0x20820, + 0x80000000: 0x800, + 0x80000001: 0x8020820, + 0x80000002: 0x8000820, + 0x80000003: 0x8000000, + 0x80000004: 0x8020000, + 0x80000005: 0x20800, + 0x80000006: 0x20820, + 0x80000007: 0x20, + 0x80000008: 0x8000020, + 0x80000009: 0x820, + 0x8000000a: 0x20020, + 0x8000000b: 0x8020800, + 0x8000000c: 0x0, + 0x8000000d: 0x8020020, + 0x8000000e: 0x8000800, + 0x8000000f: 0x20000, + 0x10: 0x20820, + 0x11: 0x8020800, + 0x12: 0x20, + 0x13: 0x800, + 0x14: 0x8000800, + 0x15: 0x8000020, + 0x16: 0x8020020, + 0x17: 0x20000, + 0x18: 0x0, + 0x19: 0x20020, + 0x1a: 0x8020000, + 0x1b: 0x8000820, + 0x1c: 0x8020820, + 0x1d: 0x20800, + 0x1e: 0x820, + 0x1f: 0x8000000, + 0x80000010: 0x20000, + 0x80000011: 0x800, + 0x80000012: 0x8020020, + 0x80000013: 0x20820, + 0x80000014: 0x20, + 0x80000015: 0x8020000, + 0x80000016: 0x8000000, + 0x80000017: 0x8000820, + 0x80000018: 0x8020820, + 0x80000019: 0x8000020, + 0x8000001a: 0x8000800, + 0x8000001b: 0x0, + 0x8000001c: 0x20800, + 0x8000001d: 0x820, + 0x8000001e: 0x20020, + 0x8000001f: 0x8020800 + } + ]; + + // Masks that select the SBOX input + var SBOX_MASK = [ + 0xf8000001, 0x1f800000, 0x01f80000, 0x001f8000, + 0x0001f800, 0x00001f80, 0x000001f8, 0x8000001f + ]; + + /** + * DES block cipher algorithm. + */ + var DES = C_algo.DES = BlockCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + + // Select 56 bits according to PC1 + var keyBits = []; + for (var i = 0; i < 56; i++) { + var keyBitPos = PC1[i] - 1; + keyBits[i] = (keyWords[keyBitPos >>> 5] >>> (31 - keyBitPos % 32)) & 1; + } + // Assemble 16 subkeys + var subKeys = this._subKeys = []; + for (var nSubKey = 0; nSubKey < 16; nSubKey++) { + // Create subkey + var subKey = subKeys[nSubKey] = []; + + // Shortcut + var bitShift = BIT_SHIFTS[nSubKey]; + + // Select 48 bits according to PC2 + for (var i = 0; i < 24; i++) { + // Select from the left 28 key bits + subKey[(i / 6) | 0] |= keyBits[((PC2[i] - 1) + bitShift) % 28] << (31 - i % 6); + + // Select from the right 28 key bits + subKey[4 + ((i / 6) | 0)] |= keyBits[28 + (((PC2[i + 24] - 1) + bitShift) % 28)] << (31 - i % 6); + } + + // Since each subkey is applied to an expanded 32-bit input, + // the subkey can be broken into 8 values scaled to 32-bits, + // which allows the key to be used without expansion + subKey[0] = (subKey[0] << 1) | (subKey[0] >>> 31); + for (var i = 1; i < 7; i++) { + subKey[i] = subKey[i] >>> ((i - 1) * 4 + 3); + } + subKey[7] = (subKey[7] << 5) | (subKey[7] >>> 27); + } -},{"../utils/config":19,"../utils/utils":22,"./errors":28,"./jsonrpc":37}],47:[function(require,module,exports){ -/* - This file is part of web3.js. + // Compute inverse subkeys + var invSubKeys = this._invSubKeys = []; + for (var i = 0; i < 16; i++) { + invSubKeys[i] = subKeys[15 - i]; + } + }, + + encryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._subKeys); + }, + + decryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._invSubKeys); + }, + + _doCryptBlock: function (M, offset, subKeys) { + // Get input + this._lBlock = M[offset]; + this._rBlock = M[offset + 1]; + + // Initial permutation + exchangeLR.call(this, 4, 0x0f0f0f0f); + exchangeLR.call(this, 16, 0x0000ffff); + exchangeRL.call(this, 2, 0x33333333); + exchangeRL.call(this, 8, 0x00ff00ff); + exchangeLR.call(this, 1, 0x55555555); + + // Rounds + for (var round = 0; round < 16; round++) { + // Shortcuts + var subKey = subKeys[round]; + var lBlock = this._lBlock; + var rBlock = this._rBlock; + + // Feistel function + var f = 0; + for (var i = 0; i < 8; i++) { + f |= SBOX_P[i][((rBlock ^ subKey[i]) & SBOX_MASK[i]) >>> 0]; + } + this._lBlock = rBlock; + this._rBlock = lBlock ^ f; + } - web3.js 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. + // Undo swap from last round + var t = this._lBlock; + this._lBlock = this._rBlock; + this._rBlock = t; - web3.js 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. + // Final permutation + exchangeLR.call(this, 1, 0x55555555); + exchangeRL.call(this, 8, 0x00ff00ff); + exchangeRL.call(this, 2, 0x33333333); + exchangeLR.call(this, 16, 0x0000ffff); + exchangeLR.call(this, 4, 0x0f0f0f0f); - You should have received a copy of the GNU Lesser General Public License - along with web3.js. If not, see . -*/ -/** @file syncing.js - * @authors: - * Fabian Vogelsteller - * @date 2015 - */ + // Set output + M[offset] = this._lBlock; + M[offset + 1] = this._rBlock; + }, -var formatters = require('./formatters'); -var utils = require('../utils/utils'); + keySize: 64/32, -var count = 1; + ivSize: 64/32, -/** -Adds the callback and sets up the methods, to iterate over the results. + blockSize: 64/32 + }); -@method pollSyncing -@param {Object} self -*/ -var pollSyncing = function(self) { + // Swap bits across the left and right words + function exchangeLR(offset, mask) { + var t = ((this._lBlock >>> offset) ^ this._rBlock) & mask; + this._rBlock ^= t; + this._lBlock ^= t << offset; + } - var onMessage = function (error, sync) { - if (error) { - return self.callbacks.forEach(function (callback) { - callback(error); - }); + function exchangeRL(offset, mask) { + var t = ((this._rBlock >>> offset) ^ this._lBlock) & mask; + this._lBlock ^= t; + this._rBlock ^= t << offset; } - if(utils.isObject(sync) && sync.startingBlock) - sync = formatters.outputSyncingFormatter(sync); + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.DES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.DES.decrypt(ciphertext, key, cfg); + */ + C.DES = BlockCipher._createHelper(DES); + + /** + * Triple-DES block cipher algorithm. + */ + var TripleDES = C_algo.TripleDES = BlockCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + + // Create DES instances + this._des1 = DES.createEncryptor(WordArray.create(keyWords.slice(0, 2))); + this._des2 = DES.createEncryptor(WordArray.create(keyWords.slice(2, 4))); + this._des3 = DES.createEncryptor(WordArray.create(keyWords.slice(4, 6))); + }, + + encryptBlock: function (M, offset) { + this._des1.encryptBlock(M, offset); + this._des2.decryptBlock(M, offset); + this._des3.encryptBlock(M, offset); + }, + + decryptBlock: function (M, offset) { + this._des3.decryptBlock(M, offset); + this._des2.encryptBlock(M, offset); + this._des1.decryptBlock(M, offset); + }, + + keySize: 192/32, + + ivSize: 64/32, + + blockSize: 64/32 + }); - self.callbacks.forEach(function (callback) { - if (self.lastSyncState !== sync) { - - // call the callback with true first so the app can stop anything, before receiving the sync data - if(!self.lastSyncState && utils.isObject(sync)) - callback(null, true); - - // call on the next CPU cycle, so the actions of the sync stop can be processes first - setTimeout(function() { - callback(null, sync); - }, 0); - - self.lastSyncState = sync; + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.TripleDES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.TripleDES.decrypt(ciphertext, key, cfg); + */ + C.TripleDES = BlockCipher._createHelper(TripleDES); + }()); + + + return CryptoJS.TripleDES; + + })); + },{"./cipher-core":51,"./core":52,"./enc-base64":53,"./evpkdf":55,"./md5":60}],83:[function(require,module,exports){ + ;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(require("./core")); + } + else if (typeof define === "function" && define.amd) { + // AMD + define(["./core"], factory); + } + else { + // Global (browser) + factory(root.CryptoJS); + } + }(this, function (CryptoJS) { + + (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var X32WordArray = C_lib.WordArray; + + /** + * x64 namespace. + */ + var C_x64 = C.x64 = {}; + + /** + * A 64-bit word. + */ + var X64Word = C_x64.Word = Base.extend({ + /** + * Initializes a newly created 64-bit word. + * + * @param {number} high The high 32 bits. + * @param {number} low The low 32 bits. + * + * @example + * + * var x64Word = CryptoJS.x64.Word.create(0x00010203, 0x04050607); + */ + init: function (high, low) { + this.high = high; + this.low = low; + } + + /** + * Bitwise NOTs this word. + * + * @return {X64Word} A new x64-Word object after negating. + * + * @example + * + * var negated = x64Word.not(); + */ + // not: function () { + // var high = ~this.high; + // var low = ~this.low; + + // return X64Word.create(high, low); + // }, + + /** + * Bitwise ANDs this word with the passed word. + * + * @param {X64Word} word The x64-Word to AND with this word. + * + * @return {X64Word} A new x64-Word object after ANDing. + * + * @example + * + * var anded = x64Word.and(anotherX64Word); + */ + // and: function (word) { + // var high = this.high & word.high; + // var low = this.low & word.low; + + // return X64Word.create(high, low); + // }, + + /** + * Bitwise ORs this word with the passed word. + * + * @param {X64Word} word The x64-Word to OR with this word. + * + * @return {X64Word} A new x64-Word object after ORing. + * + * @example + * + * var ored = x64Word.or(anotherX64Word); + */ + // or: function (word) { + // var high = this.high | word.high; + // var low = this.low | word.low; + + // return X64Word.create(high, low); + // }, + + /** + * Bitwise XORs this word with the passed word. + * + * @param {X64Word} word The x64-Word to XOR with this word. + * + * @return {X64Word} A new x64-Word object after XORing. + * + * @example + * + * var xored = x64Word.xor(anotherX64Word); + */ + // xor: function (word) { + // var high = this.high ^ word.high; + // var low = this.low ^ word.low; + + // return X64Word.create(high, low); + // }, + + /** + * Shifts this word n bits to the left. + * + * @param {number} n The number of bits to shift. + * + * @return {X64Word} A new x64-Word object after shifting. + * + * @example + * + * var shifted = x64Word.shiftL(25); + */ + // shiftL: function (n) { + // if (n < 32) { + // var high = (this.high << n) | (this.low >>> (32 - n)); + // var low = this.low << n; + // } else { + // var high = this.low << (n - 32); + // var low = 0; + // } + + // return X64Word.create(high, low); + // }, + + /** + * Shifts this word n bits to the right. + * + * @param {number} n The number of bits to shift. + * + * @return {X64Word} A new x64-Word object after shifting. + * + * @example + * + * var shifted = x64Word.shiftR(7); + */ + // shiftR: function (n) { + // if (n < 32) { + // var low = (this.low >>> n) | (this.high << (32 - n)); + // var high = this.high >>> n; + // } else { + // var low = this.high >>> (n - 32); + // var high = 0; + // } + + // return X64Word.create(high, low); + // }, + + /** + * Rotates this word n bits to the left. + * + * @param {number} n The number of bits to rotate. + * + * @return {X64Word} A new x64-Word object after rotating. + * + * @example + * + * var rotated = x64Word.rotL(25); + */ + // rotL: function (n) { + // return this.shiftL(n).or(this.shiftR(64 - n)); + // }, + + /** + * Rotates this word n bits to the right. + * + * @param {number} n The number of bits to rotate. + * + * @return {X64Word} A new x64-Word object after rotating. + * + * @example + * + * var rotated = x64Word.rotR(7); + */ + // rotR: function (n) { + // return this.shiftR(n).or(this.shiftL(64 - n)); + // }, + + /** + * Adds this word with the passed word. + * + * @param {X64Word} word The x64-Word to add with this word. + * + * @return {X64Word} A new x64-Word object after adding. + * + * @example + * + * var added = x64Word.add(anotherX64Word); + */ + // add: function (word) { + // var low = (this.low + word.low) | 0; + // var carry = (low >>> 0) < (this.low >>> 0) ? 1 : 0; + // var high = (this.high + word.high + carry) | 0; + + // return X64Word.create(high, low); + // } + }); + + /** + * An array of 64-bit words. + * + * @property {Array} words The array of CryptoJS.x64.Word objects. + * @property {number} sigBytes The number of significant bytes in this word array. + */ + var X64WordArray = C_x64.WordArray = Base.extend({ + /** + * Initializes a newly created word array. + * + * @param {Array} words (Optional) An array of CryptoJS.x64.Word objects. + * @param {number} sigBytes (Optional) The number of significant bytes in the words. + * + * @example + * + * var wordArray = CryptoJS.x64.WordArray.create(); + * + * var wordArray = CryptoJS.x64.WordArray.create([ + * CryptoJS.x64.Word.create(0x00010203, 0x04050607), + * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) + * ]); + * + * var wordArray = CryptoJS.x64.WordArray.create([ + * CryptoJS.x64.Word.create(0x00010203, 0x04050607), + * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) + * ], 10); + */ + init: function (words, sigBytes) { + words = this.words = words || []; + + if (sigBytes != undefined) { + this.sigBytes = sigBytes; + } else { + this.sigBytes = words.length * 8; + } + }, + + /** + * Converts this 64-bit word array to a 32-bit word array. + * + * @return {CryptoJS.lib.WordArray} This word array's data as a 32-bit word array. + * + * @example + * + * var x32WordArray = x64WordArray.toX32(); + */ + toX32: function () { + // Shortcuts + var x64Words = this.words; + var x64WordsLength = x64Words.length; + + // Convert + var x32Words = []; + for (var i = 0; i < x64WordsLength; i++) { + var x64Word = x64Words[i]; + x32Words.push(x64Word.high); + x32Words.push(x64Word.low); } + + return X32WordArray.create(x32Words, this.sigBytes); + }, + + /** + * Creates a copy of this word array. + * + * @return {X64WordArray} The clone. + * + * @example + * + * var clone = x64WordArray.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + + // Clone "words" array + var words = clone.words = this.words.slice(0); + + // Clone each X64Word object + var wordsLength = words.length; + for (var i = 0; i < wordsLength; i++) { + words[i] = words[i].clone(); + } + + return clone; + } }); - }; + }()); - self.requestManager.startPolling({ - method: 'platon_syncing', - params: [], - }, self.pollId, onMessage, self.stopWatching.bind(self)); -}; + return CryptoJS; -var IsSyncing = function (requestManager, callback) { - this.requestManager = requestManager; - this.pollId = 'syncPoll_'+ count++; - this.callbacks = []; - this.addCallback(callback); - this.lastSyncState = false; - pollSyncing(this); + })); + },{"./core":52}],84:[function(require,module,exports){ + /*! https://mths.be/utf8js v2.1.2 by @mathias */ + ;(function(root) { - return this; -}; + // Detect free variables `exports` + var freeExports = typeof exports == 'object' && exports; -IsSyncing.prototype.addCallback = function (callback) { - if(callback) - this.callbacks.push(callback); - return this; -}; + // Detect free variable `module` + var freeModule = typeof module == 'object' && module && + module.exports == freeExports && module; -IsSyncing.prototype.stopWatching = function () { - this.requestManager.stopPolling(this.pollId); - this.callbacks = []; -}; + // Detect free variable `global`, from Node.js or Browserified code, + // and use it as `root` + var freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { + root = freeGlobal; + } -module.exports = IsSyncing; + /*--------------------------------------------------------------------------*/ + + var stringFromCharCode = String.fromCharCode; + + // Taken from https://mths.be/punycode + function ucs2decode(string) { + var output = []; + var counter = 0; + var length = string.length; + var value; + var extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + // Taken from https://mths.be/punycode + function ucs2encode(array) { + var length = array.length; + var index = -1; + var value; + var output = ''; + while (++index < length) { + value = array[index]; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + } + return output; + } -},{"../utils/utils":22,"./formatters":32}],48:[function(require,module,exports){ -/* - This file is part of web3.js. + function checkScalarValue(codePoint) { + if (codePoint >= 0xD800 && codePoint <= 0xDFFF) { + throw Error( + 'Lone surrogate U+' + codePoint.toString(16).toUpperCase() + + ' is not a scalar value' + ); + } + } + /*--------------------------------------------------------------------------*/ - web3.js 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. + function createByte(codePoint, shift) { + return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80); + } - web3.js 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. + function encodeCodePoint(codePoint) { + if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence + return stringFromCharCode(codePoint); + } + var symbol = ''; + if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence + symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0); + } + else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence + checkScalarValue(codePoint); + symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0); + symbol += createByte(codePoint, 6); + } + else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence + symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0); + symbol += createByte(codePoint, 12); + symbol += createByte(codePoint, 6); + } + symbol += stringFromCharCode((codePoint & 0x3F) | 0x80); + return symbol; + } - You should have received a copy of the GNU Lesser General Public License - along with web3.js. If not, see . -*/ -/** - * @file transfer.js - * @author Marek Kotewicz - * @date 2015 - */ - -var Iban = require('./iban'); -var exchangeAbi = require('../contracts/SmartExchange.json'); - -/** - * Should be used to make Iban transfer - * - * @method transfer - * @param {String} from - * @param {String} to iban - * @param {Value} value to be tranfered - * @param {Function} callback, callback - */ -var transfer = function (eth, from, to, value, callback) { - var iban = new Iban(to); - if (!iban.isValid()) { - throw new Error('invalid iban address'); - } + function utf8encode(string) { + var codePoints = ucs2decode(string); + var length = codePoints.length; + var index = -1; + var codePoint; + var byteString = ''; + while (++index < length) { + codePoint = codePoints[index]; + byteString += encodeCodePoint(codePoint); + } + return byteString; + } - if (iban.isDirect()) { - return transferToAddress(eth, from, iban.address(), value, callback); - } - - if (!callback) { - var address = eth.icapNamereg().addr(iban.institution()); - return deposit(eth, from, address, value, iban.client()); - } + /*--------------------------------------------------------------------------*/ - eth.icapNamereg().addr(iban.institution(), function (err, address) { - return deposit(eth, from, address, value, iban.client(), callback); - }); - -}; - -/** - * Should be used to transfer funds to certain address - * - * @method transferToAddress - * @param {String} from - * @param {String} to - * @param {Value} value to be tranfered - * @param {Function} callback, callback - */ -var transferToAddress = function (eth, from, to, value, callback) { - return eth.sendTransaction({ - address: to, - from: from, - value: value - }, callback); -}; - -/** - * Should be used to deposit funds to generic Exchange contract (must implement deposit(bytes32) method!) - * - * @method deposit - * @param {String} from - * @param {String} to - * @param {Value} value to be transfered - * @param {String} client unique identifier - * @param {Function} callback, callback - */ -var deposit = function (eth, from, to, value, client, callback) { - var abi = exchangeAbi; - return eth.contract(abi).at(to).deposit(client, { - from: from, - value: value - }, callback); -}; - -module.exports = transfer; - - -},{"../contracts/SmartExchange.json":3,"./iban":35}],49:[function(require,module,exports){ - -},{}],50:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var BlockCipher = C_lib.BlockCipher; - var C_algo = C.algo; - - // Lookup tables - var SBOX = []; - var INV_SBOX = []; - var SUB_MIX_0 = []; - var SUB_MIX_1 = []; - var SUB_MIX_2 = []; - var SUB_MIX_3 = []; - var INV_SUB_MIX_0 = []; - var INV_SUB_MIX_1 = []; - var INV_SUB_MIX_2 = []; - var INV_SUB_MIX_3 = []; - - // Compute lookup tables - (function () { - // Compute double table - var d = []; - for (var i = 0; i < 256; i++) { - if (i < 128) { - d[i] = i << 1; - } else { - d[i] = (i << 1) ^ 0x11b; - } - } - - // Walk GF(2^8) - var x = 0; - var xi = 0; - for (var i = 0; i < 256; i++) { - // Compute sbox - var sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4); - sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63; - SBOX[x] = sx; - INV_SBOX[sx] = x; - - // Compute multiplication - var x2 = d[x]; - var x4 = d[x2]; - var x8 = d[x4]; - - // Compute sub bytes, mix columns tables - var t = (d[sx] * 0x101) ^ (sx * 0x1010100); - SUB_MIX_0[x] = (t << 24) | (t >>> 8); - SUB_MIX_1[x] = (t << 16) | (t >>> 16); - SUB_MIX_2[x] = (t << 8) | (t >>> 24); - SUB_MIX_3[x] = t; - - // Compute inv sub bytes, inv mix columns tables - var t = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100); - INV_SUB_MIX_0[sx] = (t << 24) | (t >>> 8); - INV_SUB_MIX_1[sx] = (t << 16) | (t >>> 16); - INV_SUB_MIX_2[sx] = (t << 8) | (t >>> 24); - INV_SUB_MIX_3[sx] = t; - - // Compute next counter - if (!x) { - x = xi = 1; - } else { - x = x2 ^ d[d[d[x8 ^ x2]]]; - xi ^= d[d[xi]]; - } - } - }()); - - // Precomputed Rcon lookup - var RCON = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; - - /** - * AES block cipher algorithm. - */ - var AES = C_algo.AES = BlockCipher.extend({ - _doReset: function () { - // Skip reset of nRounds has been set before and key did not change - if (this._nRounds && this._keyPriorReset === this._key) { - return; - } - - // Shortcuts - var key = this._keyPriorReset = this._key; - var keyWords = key.words; - var keySize = key.sigBytes / 4; - - // Compute number of rounds - var nRounds = this._nRounds = keySize + 6; - - // Compute number of key schedule rows - var ksRows = (nRounds + 1) * 4; - - // Compute key schedule - var keySchedule = this._keySchedule = []; - for (var ksRow = 0; ksRow < ksRows; ksRow++) { - if (ksRow < keySize) { - keySchedule[ksRow] = keyWords[ksRow]; - } else { - var t = keySchedule[ksRow - 1]; - - if (!(ksRow % keySize)) { - // Rot word - t = (t << 8) | (t >>> 24); - - // Sub word - t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; - - // Mix Rcon - t ^= RCON[(ksRow / keySize) | 0] << 24; - } else if (keySize > 6 && ksRow % keySize == 4) { - // Sub word - t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; - } - - keySchedule[ksRow] = keySchedule[ksRow - keySize] ^ t; - } - } - - // Compute inv key schedule - var invKeySchedule = this._invKeySchedule = []; - for (var invKsRow = 0; invKsRow < ksRows; invKsRow++) { - var ksRow = ksRows - invKsRow; - - if (invKsRow % 4) { - var t = keySchedule[ksRow]; - } else { - var t = keySchedule[ksRow - 4]; - } - - if (invKsRow < 4 || ksRow <= 4) { - invKeySchedule[invKsRow] = t; - } else { - invKeySchedule[invKsRow] = INV_SUB_MIX_0[SBOX[t >>> 24]] ^ INV_SUB_MIX_1[SBOX[(t >>> 16) & 0xff]] ^ - INV_SUB_MIX_2[SBOX[(t >>> 8) & 0xff]] ^ INV_SUB_MIX_3[SBOX[t & 0xff]]; - } - } - }, - - encryptBlock: function (M, offset) { - this._doCryptBlock(M, offset, this._keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX); - }, - - decryptBlock: function (M, offset) { - // Swap 2nd and 4th rows - var t = M[offset + 1]; - M[offset + 1] = M[offset + 3]; - M[offset + 3] = t; - - this._doCryptBlock(M, offset, this._invKeySchedule, INV_SUB_MIX_0, INV_SUB_MIX_1, INV_SUB_MIX_2, INV_SUB_MIX_3, INV_SBOX); - - // Inv swap 2nd and 4th rows - var t = M[offset + 1]; - M[offset + 1] = M[offset + 3]; - M[offset + 3] = t; - }, - - _doCryptBlock: function (M, offset, keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX) { - // Shortcut - var nRounds = this._nRounds; - - // Get input, add round key - var s0 = M[offset] ^ keySchedule[0]; - var s1 = M[offset + 1] ^ keySchedule[1]; - var s2 = M[offset + 2] ^ keySchedule[2]; - var s3 = M[offset + 3] ^ keySchedule[3]; - - // Key schedule row counter - var ksRow = 4; - - // Rounds - for (var round = 1; round < nRounds; round++) { - // Shift rows, sub bytes, mix columns, add round key - var t0 = SUB_MIX_0[s0 >>> 24] ^ SUB_MIX_1[(s1 >>> 16) & 0xff] ^ SUB_MIX_2[(s2 >>> 8) & 0xff] ^ SUB_MIX_3[s3 & 0xff] ^ keySchedule[ksRow++]; - var t1 = SUB_MIX_0[s1 >>> 24] ^ SUB_MIX_1[(s2 >>> 16) & 0xff] ^ SUB_MIX_2[(s3 >>> 8) & 0xff] ^ SUB_MIX_3[s0 & 0xff] ^ keySchedule[ksRow++]; - var t2 = SUB_MIX_0[s2 >>> 24] ^ SUB_MIX_1[(s3 >>> 16) & 0xff] ^ SUB_MIX_2[(s0 >>> 8) & 0xff] ^ SUB_MIX_3[s1 & 0xff] ^ keySchedule[ksRow++]; - var t3 = SUB_MIX_0[s3 >>> 24] ^ SUB_MIX_1[(s0 >>> 16) & 0xff] ^ SUB_MIX_2[(s1 >>> 8) & 0xff] ^ SUB_MIX_3[s2 & 0xff] ^ keySchedule[ksRow++]; - - // Update state - s0 = t0; - s1 = t1; - s2 = t2; - s3 = t3; - } - - // Shift rows, sub bytes, add round key - var t0 = ((SBOX[s0 >>> 24] << 24) | (SBOX[(s1 >>> 16) & 0xff] << 16) | (SBOX[(s2 >>> 8) & 0xff] << 8) | SBOX[s3 & 0xff]) ^ keySchedule[ksRow++]; - var t1 = ((SBOX[s1 >>> 24] << 24) | (SBOX[(s2 >>> 16) & 0xff] << 16) | (SBOX[(s3 >>> 8) & 0xff] << 8) | SBOX[s0 & 0xff]) ^ keySchedule[ksRow++]; - var t2 = ((SBOX[s2 >>> 24] << 24) | (SBOX[(s3 >>> 16) & 0xff] << 16) | (SBOX[(s0 >>> 8) & 0xff] << 8) | SBOX[s1 & 0xff]) ^ keySchedule[ksRow++]; - var t3 = ((SBOX[s3 >>> 24] << 24) | (SBOX[(s0 >>> 16) & 0xff] << 16) | (SBOX[(s1 >>> 8) & 0xff] << 8) | SBOX[s2 & 0xff]) ^ keySchedule[ksRow++]; - - // Set output - M[offset] = t0; - M[offset + 1] = t1; - M[offset + 2] = t2; - M[offset + 3] = t3; - }, - - keySize: 256/32 - }); - - /** - * Shortcut functions to the cipher's object interface. - * - * @example - * - * var ciphertext = CryptoJS.AES.encrypt(message, key, cfg); - * var plaintext = CryptoJS.AES.decrypt(ciphertext, key, cfg); - */ - C.AES = BlockCipher._createHelper(AES); - }()); - - - return CryptoJS.AES; - -})); -},{"./cipher-core":51,"./core":52,"./enc-base64":53,"./evpkdf":55,"./md5":60}],51:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./evpkdf")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./evpkdf"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - /** - * Cipher core components. - */ - CryptoJS.lib.Cipher || (function (undefined) { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var Base = C_lib.Base; - var WordArray = C_lib.WordArray; - var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm; - var C_enc = C.enc; - var Utf8 = C_enc.Utf8; - var Base64 = C_enc.Base64; - var C_algo = C.algo; - var EvpKDF = C_algo.EvpKDF; - - /** - * Abstract base cipher template. - * - * @property {number} keySize This cipher's key size. Default: 4 (128 bits) - * @property {number} ivSize This cipher's IV size. Default: 4 (128 bits) - * @property {number} _ENC_XFORM_MODE A constant representing encryption mode. - * @property {number} _DEC_XFORM_MODE A constant representing decryption mode. - */ - var Cipher = C_lib.Cipher = BufferedBlockAlgorithm.extend({ - /** - * Configuration options. - * - * @property {WordArray} iv The IV to use for this operation. - */ - cfg: Base.extend(), - - /** - * Creates this cipher in encryption mode. - * - * @param {WordArray} key The key. - * @param {Object} cfg (Optional) The configuration options to use for this operation. - * - * @return {Cipher} A cipher instance. - * - * @static - * - * @example - * - * var cipher = CryptoJS.algo.AES.createEncryptor(keyWordArray, { iv: ivWordArray }); - */ - createEncryptor: function (key, cfg) { - return this.create(this._ENC_XFORM_MODE, key, cfg); - }, - - /** - * Creates this cipher in decryption mode. - * - * @param {WordArray} key The key. - * @param {Object} cfg (Optional) The configuration options to use for this operation. - * - * @return {Cipher} A cipher instance. - * - * @static - * - * @example - * - * var cipher = CryptoJS.algo.AES.createDecryptor(keyWordArray, { iv: ivWordArray }); - */ - createDecryptor: function (key, cfg) { - return this.create(this._DEC_XFORM_MODE, key, cfg); - }, - - /** - * Initializes a newly created cipher. - * - * @param {number} xformMode Either the encryption or decryption transormation mode constant. - * @param {WordArray} key The key. - * @param {Object} cfg (Optional) The configuration options to use for this operation. - * - * @example - * - * var cipher = CryptoJS.algo.AES.create(CryptoJS.algo.AES._ENC_XFORM_MODE, keyWordArray, { iv: ivWordArray }); - */ - init: function (xformMode, key, cfg) { - // Apply config defaults - this.cfg = this.cfg.extend(cfg); - - // Store transform mode and key - this._xformMode = xformMode; - this._key = key; - - // Set initial values - this.reset(); - }, - - /** - * Resets this cipher to its initial state. - * - * @example - * - * cipher.reset(); - */ - reset: function () { - // Reset data buffer - BufferedBlockAlgorithm.reset.call(this); - - // Perform concrete-cipher logic - this._doReset(); - }, - - /** - * Adds data to be encrypted or decrypted. - * - * @param {WordArray|string} dataUpdate The data to encrypt or decrypt. - * - * @return {WordArray} The data after processing. - * - * @example - * - * var encrypted = cipher.process('data'); - * var encrypted = cipher.process(wordArray); - */ - process: function (dataUpdate) { - // Append - this._append(dataUpdate); - - // Process available blocks - return this._process(); - }, - - /** - * Finalizes the encryption or decryption process. - * Note that the finalize operation is effectively a destructive, read-once operation. - * - * @param {WordArray|string} dataUpdate The final data to encrypt or decrypt. - * - * @return {WordArray} The data after final processing. - * - * @example - * - * var encrypted = cipher.finalize(); - * var encrypted = cipher.finalize('data'); - * var encrypted = cipher.finalize(wordArray); - */ - finalize: function (dataUpdate) { - // Final data update - if (dataUpdate) { - this._append(dataUpdate); - } - - // Perform concrete-cipher logic - var finalProcessedData = this._doFinalize(); - - return finalProcessedData; - }, - - keySize: 128/32, - - ivSize: 128/32, - - _ENC_XFORM_MODE: 1, - - _DEC_XFORM_MODE: 2, - - /** - * Creates shortcut functions to a cipher's object interface. - * - * @param {Cipher} cipher The cipher to create a helper for. - * - * @return {Object} An object with encrypt and decrypt shortcut functions. - * - * @static - * - * @example - * - * var AES = CryptoJS.lib.Cipher._createHelper(CryptoJS.algo.AES); - */ - _createHelper: (function () { - function selectCipherStrategy(key) { - if (typeof key == 'string') { - return PasswordBasedCipher; - } else { - return SerializableCipher; - } - } - - return function (cipher) { - return { - encrypt: function (message, key, cfg) { - return selectCipherStrategy(key).encrypt(cipher, message, key, cfg); - }, - - decrypt: function (ciphertext, key, cfg) { - return selectCipherStrategy(key).decrypt(cipher, ciphertext, key, cfg); - } - }; - }; - }()) - }); - - /** - * Abstract base stream cipher template. - * - * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 1 (32 bits) - */ - var StreamCipher = C_lib.StreamCipher = Cipher.extend({ - _doFinalize: function () { - // Process partial blocks - var finalProcessedBlocks = this._process(!!'flush'); + function readContinuationByte() { + if (byteIndex >= byteCount) { + throw Error('Invalid byte index'); + } - return finalProcessedBlocks; - }, + var continuationByte = byteArray[byteIndex] & 0xFF; + byteIndex++; - blockSize: 1 - }); + if ((continuationByte & 0xC0) == 0x80) { + return continuationByte & 0x3F; + } - /** - * Mode namespace. - */ - var C_mode = C.mode = {}; + // If we end up here, it’s not a continuation byte + throw Error('Invalid continuation byte'); + } - /** - * Abstract base block cipher mode template. - */ - var BlockCipherMode = C_lib.BlockCipherMode = Base.extend({ - /** - * Creates this mode for encryption. - * - * @param {Cipher} cipher A block cipher instance. - * @param {Array} iv The IV words. - * - * @static - * - * @example - * - * var mode = CryptoJS.mode.CBC.createEncryptor(cipher, iv.words); - */ - createEncryptor: function (cipher, iv) { - return this.Encryptor.create(cipher, iv); - }, - - /** - * Creates this mode for decryption. - * - * @param {Cipher} cipher A block cipher instance. - * @param {Array} iv The IV words. - * - * @static - * - * @example - * - * var mode = CryptoJS.mode.CBC.createDecryptor(cipher, iv.words); - */ - createDecryptor: function (cipher, iv) { - return this.Decryptor.create(cipher, iv); - }, - - /** - * Initializes a newly created mode. - * - * @param {Cipher} cipher A block cipher instance. - * @param {Array} iv The IV words. - * - * @example - * - * var mode = CryptoJS.mode.CBC.Encryptor.create(cipher, iv.words); - */ - init: function (cipher, iv) { - this._cipher = cipher; - this._iv = iv; - } - }); - - /** - * Cipher Block Chaining mode. - */ - var CBC = C_mode.CBC = (function () { - /** - * Abstract base CBC mode. - */ - var CBC = BlockCipherMode.extend(); - - /** - * CBC encryptor. - */ - CBC.Encryptor = CBC.extend({ - /** - * Processes the data block at offset. - * - * @param {Array} words The data words to operate on. - * @param {number} offset The offset where the block starts. - * - * @example - * - * mode.processBlock(data.words, offset); - */ - processBlock: function (words, offset) { - // Shortcuts - var cipher = this._cipher; - var blockSize = cipher.blockSize; - - // XOR and encrypt - xorBlock.call(this, words, offset, blockSize); - cipher.encryptBlock(words, offset); - - // Remember this block to use with next block - this._prevBlock = words.slice(offset, offset + blockSize); - } - }); - - /** - * CBC decryptor. - */ - CBC.Decryptor = CBC.extend({ - /** - * Processes the data block at offset. - * - * @param {Array} words The data words to operate on. - * @param {number} offset The offset where the block starts. - * - * @example - * - * mode.processBlock(data.words, offset); - */ - processBlock: function (words, offset) { - // Shortcuts - var cipher = this._cipher; - var blockSize = cipher.blockSize; - - // Remember this block to use with next block - var thisBlock = words.slice(offset, offset + blockSize); - - // Decrypt and XOR - cipher.decryptBlock(words, offset); - xorBlock.call(this, words, offset, blockSize); - - // This block becomes the previous block - this._prevBlock = thisBlock; - } - }); - - function xorBlock(words, offset, blockSize) { - // Shortcut - var iv = this._iv; - - // Choose mixing block - if (iv) { - var block = iv; - - // Remove IV for subsequent blocks - this._iv = undefined; - } else { - var block = this._prevBlock; - } - - // XOR blocks - for (var i = 0; i < blockSize; i++) { - words[offset + i] ^= block[i]; - } - } - - return CBC; - }()); - - /** - * Padding namespace. - */ - var C_pad = C.pad = {}; + function decodeSymbol() { + var byte1; + var byte2; + var byte3; + var byte4; + var codePoint; - /** - * PKCS #5/7 padding strategy. - */ - var Pkcs7 = C_pad.Pkcs7 = { - /** - * Pads data using the algorithm defined in PKCS #5/7. - * - * @param {WordArray} data The data to pad. - * @param {number} blockSize The multiple that the data should be padded to. - * - * @static - * - * @example - * - * CryptoJS.pad.Pkcs7.pad(wordArray, 4); - */ - pad: function (data, blockSize) { - // Shortcut - var blockSizeBytes = blockSize * 4; - - // Count padding bytes - var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; - - // Create padding word - var paddingWord = (nPaddingBytes << 24) | (nPaddingBytes << 16) | (nPaddingBytes << 8) | nPaddingBytes; - - // Create padding - var paddingWords = []; - for (var i = 0; i < nPaddingBytes; i += 4) { - paddingWords.push(paddingWord); - } - var padding = WordArray.create(paddingWords, nPaddingBytes); - - // Add padding - data.concat(padding); - }, - - /** - * Unpads data that had been padded using the algorithm defined in PKCS #5/7. - * - * @param {WordArray} data The data to unpad. - * - * @static - * - * @example - * - * CryptoJS.pad.Pkcs7.unpad(wordArray); - */ - unpad: function (data) { - // Get number of padding bytes from last byte - var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; - - // Remove padding - data.sigBytes -= nPaddingBytes; - } - }; - - /** - * Abstract base block cipher template. - * - * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 4 (128 bits) - */ - var BlockCipher = C_lib.BlockCipher = Cipher.extend({ - /** - * Configuration options. - * - * @property {Mode} mode The block mode to use. Default: CBC - * @property {Padding} padding The padding strategy to use. Default: Pkcs7 - */ - cfg: Cipher.cfg.extend({ - mode: CBC, - padding: Pkcs7 - }), - - reset: function () { - // Reset cipher - Cipher.reset.call(this); - - // Shortcuts - var cfg = this.cfg; - var iv = cfg.iv; - var mode = cfg.mode; - - // Reset block mode - if (this._xformMode == this._ENC_XFORM_MODE) { - var modeCreator = mode.createEncryptor; - } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { - var modeCreator = mode.createDecryptor; - // Keep at least one block in the buffer for unpadding - this._minBufferSize = 1; - } - - if (this._mode && this._mode.__creator == modeCreator) { - this._mode.init(this, iv && iv.words); - } else { - this._mode = modeCreator.call(mode, this, iv && iv.words); - this._mode.__creator = modeCreator; - } - }, - - _doProcessBlock: function (words, offset) { - this._mode.processBlock(words, offset); - }, - - _doFinalize: function () { - // Shortcut - var padding = this.cfg.padding; - - // Finalize - if (this._xformMode == this._ENC_XFORM_MODE) { - // Pad data - padding.pad(this._data, this.blockSize); - - // Process final blocks - var finalProcessedBlocks = this._process(!!'flush'); - } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { - // Process final blocks - var finalProcessedBlocks = this._process(!!'flush'); - - // Unpad data - padding.unpad(finalProcessedBlocks); - } - - return finalProcessedBlocks; - }, - - blockSize: 128/32 - }); - - /** - * A collection of cipher parameters. - * - * @property {WordArray} ciphertext The raw ciphertext. - * @property {WordArray} key The key to this ciphertext. - * @property {WordArray} iv The IV used in the ciphering operation. - * @property {WordArray} salt The salt used with a key derivation function. - * @property {Cipher} algorithm The cipher algorithm. - * @property {Mode} mode The block mode used in the ciphering operation. - * @property {Padding} padding The padding scheme used in the ciphering operation. - * @property {number} blockSize The block size of the cipher. - * @property {Format} formatter The default formatting strategy to convert this cipher params object to a string. - */ - var CipherParams = C_lib.CipherParams = Base.extend({ - /** - * Initializes a newly created cipher params object. - * - * @param {Object} cipherParams An object with any of the possible cipher parameters. - * - * @example - * - * var cipherParams = CryptoJS.lib.CipherParams.create({ - * ciphertext: ciphertextWordArray, - * key: keyWordArray, - * iv: ivWordArray, - * salt: saltWordArray, - * algorithm: CryptoJS.algo.AES, - * mode: CryptoJS.mode.CBC, - * padding: CryptoJS.pad.PKCS7, - * blockSize: 4, - * formatter: CryptoJS.format.OpenSSL - * }); - */ - init: function (cipherParams) { - this.mixIn(cipherParams); - }, - - /** - * Converts this cipher params object to a string. - * - * @param {Format} formatter (Optional) The formatting strategy to use. - * - * @return {string} The stringified cipher params. - * - * @throws Error If neither the formatter nor the default formatter is set. - * - * @example - * - * var string = cipherParams + ''; - * var string = cipherParams.toString(); - * var string = cipherParams.toString(CryptoJS.format.OpenSSL); - */ - toString: function (formatter) { - return (formatter || this.formatter).stringify(this); - } - }); - - /** - * Format namespace. - */ - var C_format = C.format = {}; + if (byteIndex > byteCount) { + throw Error('Invalid byte index'); + } - /** - * OpenSSL formatting strategy. - */ - var OpenSSLFormatter = C_format.OpenSSL = { - /** - * Converts a cipher params object to an OpenSSL-compatible string. - * - * @param {CipherParams} cipherParams The cipher params object. - * - * @return {string} The OpenSSL-compatible string. - * - * @static - * - * @example - * - * var openSSLString = CryptoJS.format.OpenSSL.stringify(cipherParams); - */ - stringify: function (cipherParams) { - // Shortcuts - var ciphertext = cipherParams.ciphertext; - var salt = cipherParams.salt; - - // Format - if (salt) { - var wordArray = WordArray.create([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext); - } else { - var wordArray = ciphertext; - } - - return wordArray.toString(Base64); - }, - - /** - * Converts an OpenSSL-compatible string to a cipher params object. - * - * @param {string} openSSLStr The OpenSSL-compatible string. - * - * @return {CipherParams} The cipher params object. - * - * @static - * - * @example - * - * var cipherParams = CryptoJS.format.OpenSSL.parse(openSSLString); - */ - parse: function (openSSLStr) { - // Parse base64 - var ciphertext = Base64.parse(openSSLStr); - - // Shortcut - var ciphertextWords = ciphertext.words; - - // Test for salt - if (ciphertextWords[0] == 0x53616c74 && ciphertextWords[1] == 0x65645f5f) { - // Extract salt - var salt = WordArray.create(ciphertextWords.slice(2, 4)); - - // Remove salt from ciphertext - ciphertextWords.splice(0, 4); - ciphertext.sigBytes -= 16; - } - - return CipherParams.create({ ciphertext: ciphertext, salt: salt }); - } - }; - - /** - * A cipher wrapper that returns ciphertext as a serializable cipher params object. - */ - var SerializableCipher = C_lib.SerializableCipher = Base.extend({ - /** - * Configuration options. - * - * @property {Formatter} format The formatting strategy to convert cipher param objects to and from a string. Default: OpenSSL - */ - cfg: Base.extend({ - format: OpenSSLFormatter - }), - - /** - * Encrypts a message. - * - * @param {Cipher} cipher The cipher algorithm to use. - * @param {WordArray|string} message The message to encrypt. - * @param {WordArray} key The key. - * @param {Object} cfg (Optional) The configuration options to use for this operation. - * - * @return {CipherParams} A cipher params object. - * - * @static - * - * @example - * - * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key); - * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv }); - * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv, format: CryptoJS.format.OpenSSL }); - */ - encrypt: function (cipher, message, key, cfg) { - // Apply config defaults - cfg = this.cfg.extend(cfg); - - // Encrypt - var encryptor = cipher.createEncryptor(key, cfg); - var ciphertext = encryptor.finalize(message); - - // Shortcut - var cipherCfg = encryptor.cfg; - - // Create and return serializable cipher params - return CipherParams.create({ - ciphertext: ciphertext, - key: key, - iv: cipherCfg.iv, - algorithm: cipher, - mode: cipherCfg.mode, - padding: cipherCfg.padding, - blockSize: cipher.blockSize, - formatter: cfg.format - }); - }, - - /** - * Decrypts serialized ciphertext. - * - * @param {Cipher} cipher The cipher algorithm to use. - * @param {CipherParams|string} ciphertext The ciphertext to decrypt. - * @param {WordArray} key The key. - * @param {Object} cfg (Optional) The configuration options to use for this operation. - * - * @return {WordArray} The plaintext. - * - * @static - * - * @example - * - * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, key, { iv: iv, format: CryptoJS.format.OpenSSL }); - * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, key, { iv: iv, format: CryptoJS.format.OpenSSL }); - */ - decrypt: function (cipher, ciphertext, key, cfg) { - // Apply config defaults - cfg = this.cfg.extend(cfg); - - // Convert string to CipherParams - ciphertext = this._parse(ciphertext, cfg.format); - - // Decrypt - var plaintext = cipher.createDecryptor(key, cfg).finalize(ciphertext.ciphertext); - - return plaintext; - }, - - /** - * Converts serialized ciphertext to CipherParams, - * else assumed CipherParams already and returns ciphertext unchanged. - * - * @param {CipherParams|string} ciphertext The ciphertext. - * @param {Formatter} format The formatting strategy to use to parse serialized ciphertext. - * - * @return {CipherParams} The unserialized ciphertext. - * - * @static - * - * @example - * - * var ciphertextParams = CryptoJS.lib.SerializableCipher._parse(ciphertextStringOrParams, format); - */ - _parse: function (ciphertext, format) { - if (typeof ciphertext == 'string') { - return format.parse(ciphertext, this); - } else { - return ciphertext; - } - } - }); - - /** - * Key derivation function namespace. - */ - var C_kdf = C.kdf = {}; + if (byteIndex == byteCount) { + return false; + } - /** - * OpenSSL key derivation function. - */ - var OpenSSLKdf = C_kdf.OpenSSL = { - /** - * Derives a key and IV from a password. - * - * @param {string} password The password to derive from. - * @param {number} keySize The size in words of the key to generate. - * @param {number} ivSize The size in words of the IV to generate. - * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be generated randomly. - * - * @return {CipherParams} A cipher params object with the key, IV, and salt. - * - * @static - * - * @example - * - * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32); - * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt'); - */ - execute: function (password, keySize, ivSize, salt) { - // Generate random salt - if (!salt) { - salt = WordArray.random(64/8); - } - - // Derive key and IV - var key = EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt); - - // Separate key and IV - var iv = WordArray.create(key.words.slice(keySize), ivSize * 4); - key.sigBytes = keySize * 4; - - // Return params - return CipherParams.create({ key: key, iv: iv, salt: salt }); - } - }; - - /** - * A serializable cipher wrapper that derives the key from a password, - * and returns ciphertext as a serializable cipher params object. - */ - var PasswordBasedCipher = C_lib.PasswordBasedCipher = SerializableCipher.extend({ - /** - * Configuration options. - * - * @property {KDF} kdf The key derivation function to use to generate a key and IV from a password. Default: OpenSSL - */ - cfg: SerializableCipher.cfg.extend({ - kdf: OpenSSLKdf - }), - - /** - * Encrypts a message using a password. - * - * @param {Cipher} cipher The cipher algorithm to use. - * @param {WordArray|string} message The message to encrypt. - * @param {string} password The password. - * @param {Object} cfg (Optional) The configuration options to use for this operation. - * - * @return {CipherParams} A cipher params object. - * - * @static - * - * @example - * - * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password'); - * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password', { format: CryptoJS.format.OpenSSL }); - */ - encrypt: function (cipher, message, password, cfg) { - // Apply config defaults - cfg = this.cfg.extend(cfg); - - // Derive key and other params - var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize); - - // Add IV to config - cfg.iv = derivedParams.iv; - - // Encrypt - var ciphertext = SerializableCipher.encrypt.call(this, cipher, message, derivedParams.key, cfg); - - // Mix in derived params - ciphertext.mixIn(derivedParams); - - return ciphertext; - }, - - /** - * Decrypts serialized ciphertext using a password. - * - * @param {Cipher} cipher The cipher algorithm to use. - * @param {CipherParams|string} ciphertext The ciphertext to decrypt. - * @param {string} password The password. - * @param {Object} cfg (Optional) The configuration options to use for this operation. - * - * @return {WordArray} The plaintext. - * - * @static - * - * @example - * - * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, 'password', { format: CryptoJS.format.OpenSSL }); - * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, 'password', { format: CryptoJS.format.OpenSSL }); - */ - decrypt: function (cipher, ciphertext, password, cfg) { - // Apply config defaults - cfg = this.cfg.extend(cfg); - - // Convert string to CipherParams - ciphertext = this._parse(ciphertext, cfg.format); - - // Derive key and other params - var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize, ciphertext.salt); - - // Add IV to config - cfg.iv = derivedParams.iv; - - // Decrypt - var plaintext = SerializableCipher.decrypt.call(this, cipher, ciphertext, derivedParams.key, cfg); - - return plaintext; - } - }); - }()); - - -})); -},{"./core":52,"./evpkdf":55}],52:[function(require,module,exports){ -;(function (root, factory) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(); - } - else if (typeof define === "function" && define.amd) { - // AMD - define([], factory); - } - else { - // Global (browser) - root.CryptoJS = factory(); - } -}(this, function () { - - /** - * CryptoJS core components. - */ - var CryptoJS = CryptoJS || (function (Math, undefined) { - /* - * Local polyfil of Object.create - */ - var create = Object.create || (function () { - function F() {}; + // Read first byte + byte1 = byteArray[byteIndex] & 0xFF; + byteIndex++; - return function (obj) { - var subtype; + // 1-byte sequence (no continuation bytes) + if ((byte1 & 0x80) == 0) { + return byte1; + } - F.prototype = obj; + // 2-byte sequence + if ((byte1 & 0xE0) == 0xC0) { + byte2 = readContinuationByte(); + codePoint = ((byte1 & 0x1F) << 6) | byte2; + if (codePoint >= 0x80) { + return codePoint; + } else { + throw Error('Invalid continuation byte'); + } + } - subtype = new F(); + // 3-byte sequence (may include unpaired surrogates) + if ((byte1 & 0xF0) == 0xE0) { + byte2 = readContinuationByte(); + byte3 = readContinuationByte(); + codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3; + if (codePoint >= 0x0800) { + checkScalarValue(codePoint); + return codePoint; + } else { + throw Error('Invalid continuation byte'); + } + } - F.prototype = null; + // 4-byte sequence + if ((byte1 & 0xF8) == 0xF0) { + byte2 = readContinuationByte(); + byte3 = readContinuationByte(); + byte4 = readContinuationByte(); + codePoint = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0C) | + (byte3 << 0x06) | byte4; + if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) { + return codePoint; + } + } - return subtype; - }; - }()) + throw Error('Invalid UTF-8 detected'); + } - /** - * CryptoJS namespace. - */ - var C = {}; + var byteArray; + var byteCount; + var byteIndex; + function utf8decode(byteString) { + byteArray = ucs2decode(byteString); + byteCount = byteArray.length; + byteIndex = 0; + var codePoints = []; + var tmp; + while ((tmp = decodeSymbol()) !== false) { + codePoints.push(tmp); + } + return ucs2encode(codePoints); + } - /** - * Library namespace. - */ - var C_lib = C.lib = {}; + /*--------------------------------------------------------------------------*/ + + var utf8 = { + 'version': '2.1.2', + 'encode': utf8encode, + 'decode': utf8decode + }; + + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define(function() { + return utf8; + }); + } else if (freeExports && !freeExports.nodeType) { + if (freeModule) { // in Node.js or RingoJS v0.8.0+ + freeModule.exports = utf8; + } else { // in Narwhal or RingoJS v0.7.0- + var object = {}; + var hasOwnProperty = object.hasOwnProperty; + for (var key in utf8) { + hasOwnProperty.call(utf8, key) && (freeExports[key] = utf8[key]); + } + } + } else { // in Rhino or a web browser + root.utf8 = utf8; + } - /** - * Base object for prototypal inheritance. - */ - var Base = C_lib.Base = (function () { - - - return { - /** - * Creates a new object that inherits from this object. - * - * @param {Object} overrides Properties to copy into the new object. - * - * @return {Object} The new object. - * - * @static - * - * @example - * - * var MyType = CryptoJS.lib.Base.extend({ - * field: 'value', - * - * method: function () { - * } - * }); - */ - extend: function (overrides) { - // Spawn - var subtype = create(this); - - // Augment - if (overrides) { - subtype.mixIn(overrides); - } - - // Create default initializer - if (!subtype.hasOwnProperty('init') || this.init === subtype.init) { - subtype.init = function () { - subtype.$super.init.apply(this, arguments); - }; - } - - // Initializer's prototype is the subtype object - subtype.init.prototype = subtype; - - // Reference supertype - subtype.$super = this; - - return subtype; - }, - - /** - * Extends this object and runs the init method. - * Arguments to create() will be passed to init(). - * - * @return {Object} The new object. - * - * @static - * - * @example - * - * var instance = MyType.create(); - */ - create: function () { - var instance = this.extend(); - instance.init.apply(instance, arguments); - - return instance; - }, - - /** - * Initializes a newly created object. - * Override this method to add some logic when your objects are created. - * - * @example - * - * var MyType = CryptoJS.lib.Base.extend({ - * init: function () { - * // ... - * } - * }); - */ - init: function () { - }, - - /** - * Copies properties into this object. - * - * @param {Object} properties The properties to mix in. - * - * @example - * - * MyType.mixIn({ - * field: 'value' - * }); - */ - mixIn: function (properties) { - for (var propertyName in properties) { - if (properties.hasOwnProperty(propertyName)) { - this[propertyName] = properties[propertyName]; - } - } - - // IE won't copy toString using the loop above - if (properties.hasOwnProperty('toString')) { - this.toString = properties.toString; - } - }, - - /** - * Creates a copy of this object. - * - * @return {Object} The clone. - * - * @example - * - * var clone = instance.clone(); - */ - clone: function () { - return this.init.prototype.extend(this); - } - }; - }()); - - /** - * An array of 32-bit words. - * - * @property {Array} words The array of 32-bit words. - * @property {number} sigBytes The number of significant bytes in this word array. - */ - var WordArray = C_lib.WordArray = Base.extend({ - /** - * Initializes a newly created word array. - * - * @param {Array} words (Optional) An array of 32-bit words. - * @param {number} sigBytes (Optional) The number of significant bytes in the words. - * - * @example - * - * var wordArray = CryptoJS.lib.WordArray.create(); - * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]); - * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6); - */ - init: function (words, sigBytes) { - words = this.words = words || []; - - if (sigBytes != undefined) { - this.sigBytes = sigBytes; - } else { - this.sigBytes = words.length * 4; - } - }, - - /** - * Converts this word array to a string. - * - * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex - * - * @return {string} The stringified word array. - * - * @example - * - * var string = wordArray + ''; - * var string = wordArray.toString(); - * var string = wordArray.toString(CryptoJS.enc.Utf8); - */ - toString: function (encoder) { - return (encoder || Hex).stringify(this); - }, - - /** - * Concatenates a word array to this word array. - * - * @param {WordArray} wordArray The word array to append. - * - * @return {WordArray} This word array. - * - * @example - * - * wordArray1.concat(wordArray2); - */ - concat: function (wordArray) { - // Shortcuts - var thisWords = this.words; - var thatWords = wordArray.words; - var thisSigBytes = this.sigBytes; - var thatSigBytes = wordArray.sigBytes; - - // Clamp excess bits - this.clamp(); - - // Concat - if (thisSigBytes % 4) { - // Copy one byte at a time - for (var i = 0; i < thatSigBytes; i++) { - var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8); - } - } else { - // Copy one word at a time - for (var i = 0; i < thatSigBytes; i += 4) { - thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2]; - } - } - this.sigBytes += thatSigBytes; - - // Chainable - return this; - }, - - /** - * Removes insignificant bits. - * - * @example - * - * wordArray.clamp(); - */ - clamp: function () { - // Shortcuts - var words = this.words; - var sigBytes = this.sigBytes; - - // Clamp - words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8); - words.length = Math.ceil(sigBytes / 4); - }, - - /** - * Creates a copy of this word array. - * - * @return {WordArray} The clone. - * - * @example - * - * var clone = wordArray.clone(); - */ - clone: function () { - var clone = Base.clone.call(this); - clone.words = this.words.slice(0); - - return clone; - }, - - /** - * Creates a word array filled with random bytes. - * - * @param {number} nBytes The number of random bytes to generate. - * - * @return {WordArray} The random word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.lib.WordArray.random(16); - */ - random: function (nBytes) { - var words = []; - - var r = (function (m_w) { - var m_w = m_w; - var m_z = 0x3ade68b1; - var mask = 0xffffffff; - - return function () { - m_z = (0x9069 * (m_z & 0xFFFF) + (m_z >> 0x10)) & mask; - m_w = (0x4650 * (m_w & 0xFFFF) + (m_w >> 0x10)) & mask; - var result = ((m_z << 0x10) + m_w) & mask; - result /= 0x100000000; - result += 0.5; - return result * (Math.random() > .5 ? 1 : -1); - } - }); - - for (var i = 0, rcache; i < nBytes; i += 4) { - var _r = r((rcache || Math.random()) * 0x100000000); - - rcache = _r() * 0x3ade67b7; - words.push((_r() * 0x100000000) | 0); - } - - return new WordArray.init(words, nBytes); - } - }); - - /** - * Encoder namespace. - */ - var C_enc = C.enc = {}; + }(this)); - /** - * Hex encoding strategy. - */ - var Hex = C_enc.Hex = { - /** - * Converts a word array to a hex string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The hex string. - * - * @static - * - * @example - * - * var hexString = CryptoJS.enc.Hex.stringify(wordArray); - */ - stringify: function (wordArray) { - // Shortcuts - var words = wordArray.words; - var sigBytes = wordArray.sigBytes; - - // Convert - var hexChars = []; - for (var i = 0; i < sigBytes; i++) { - var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - hexChars.push((bite >>> 4).toString(16)); - hexChars.push((bite & 0x0f).toString(16)); - } - - return hexChars.join(''); - }, - - /** - * Converts a hex string to a word array. - * - * @param {string} hexStr The hex string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.enc.Hex.parse(hexString); - */ - parse: function (hexStr) { - // Shortcut - var hexStrLength = hexStr.length; - - // Convert - var words = []; - for (var i = 0; i < hexStrLength; i += 2) { - words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); - } - - return new WordArray.init(words, hexStrLength / 2); - } - }; - - /** - * Latin1 encoding strategy. - */ - var Latin1 = C_enc.Latin1 = { - /** - * Converts a word array to a Latin1 string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The Latin1 string. - * - * @static - * - * @example - * - * var latin1String = CryptoJS.enc.Latin1.stringify(wordArray); - */ - stringify: function (wordArray) { - // Shortcuts - var words = wordArray.words; - var sigBytes = wordArray.sigBytes; - - // Convert - var latin1Chars = []; - for (var i = 0; i < sigBytes; i++) { - var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - latin1Chars.push(String.fromCharCode(bite)); - } - - return latin1Chars.join(''); - }, - - /** - * Converts a Latin1 string to a word array. - * - * @param {string} latin1Str The Latin1 string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.enc.Latin1.parse(latin1String); - */ - parse: function (latin1Str) { - // Shortcut - var latin1StrLength = latin1Str.length; - - // Convert - var words = []; - for (var i = 0; i < latin1StrLength; i++) { - words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8); - } - - return new WordArray.init(words, latin1StrLength); - } - }; - - /** - * UTF-8 encoding strategy. - */ - var Utf8 = C_enc.Utf8 = { - /** - * Converts a word array to a UTF-8 string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The UTF-8 string. - * - * @static - * - * @example - * - * var utf8String = CryptoJS.enc.Utf8.stringify(wordArray); - */ - stringify: function (wordArray) { - try { - return decodeURIComponent(escape(Latin1.stringify(wordArray))); - } catch (e) { - throw new Error('Malformed UTF-8 data'); - } - }, - - /** - * Converts a UTF-8 string to a word array. - * - * @param {string} utf8Str The UTF-8 string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.enc.Utf8.parse(utf8String); - */ - parse: function (utf8Str) { - return Latin1.parse(unescape(encodeURIComponent(utf8Str))); - } - }; - - /** - * Abstract buffered block algorithm template. - * - * The property blockSize must be implemented in a concrete subtype. - * - * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0 - */ - var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({ - /** - * Resets this block algorithm's data buffer to its initial state. - * - * @example - * - * bufferedBlockAlgorithm.reset(); - */ - reset: function () { - // Initial values - this._data = new WordArray.init(); - this._nDataBytes = 0; - }, - - /** - * Adds new data to this block algorithm's buffer. - * - * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8. - * - * @example - * - * bufferedBlockAlgorithm._append('data'); - * bufferedBlockAlgorithm._append(wordArray); - */ - _append: function (data) { - // Convert string to WordArray, else assume WordArray already - if (typeof data == 'string') { - data = Utf8.parse(data); - } - - // Append - this._data.concat(data); - this._nDataBytes += data.sigBytes; - }, - - /** - * Processes available data blocks. - * - * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype. - * - * @param {boolean} doFlush Whether all blocks and partial blocks should be processed. - * - * @return {WordArray} The processed data. - * - * @example - * - * var processedData = bufferedBlockAlgorithm._process(); - * var processedData = bufferedBlockAlgorithm._process(!!'flush'); - */ - _process: function (doFlush) { - // Shortcuts - var data = this._data; - var dataWords = data.words; - var dataSigBytes = data.sigBytes; - var blockSize = this.blockSize; - var blockSizeBytes = blockSize * 4; - - // Count blocks ready - var nBlocksReady = dataSigBytes / blockSizeBytes; - if (doFlush) { - // Round up to include partial blocks - nBlocksReady = Math.ceil(nBlocksReady); - } else { - // Round down to include only full blocks, - // less the number of blocks that must remain in the buffer - nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0); - } - - // Count words ready - var nWordsReady = nBlocksReady * blockSize; - - // Count bytes ready - var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes); - - // Process blocks - if (nWordsReady) { - for (var offset = 0; offset < nWordsReady; offset += blockSize) { - // Perform concrete-algorithm logic - this._doProcessBlock(dataWords, offset); - } - - // Remove processed words - var processedWords = dataWords.splice(0, nWordsReady); - data.sigBytes -= nBytesReady; - } - - // Return processed words - return new WordArray.init(processedWords, nBytesReady); - }, - - /** - * Creates a copy of this object. - * - * @return {Object} The clone. - * - * @example - * - * var clone = bufferedBlockAlgorithm.clone(); - */ - clone: function () { - var clone = Base.clone.call(this); - clone._data = this._data.clone(); - - return clone; - }, - - _minBufferSize: 0 - }); - - /** - * Abstract hasher template. - * - * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits) - */ - var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({ - /** - * Configuration options. - */ - cfg: Base.extend(), - - /** - * Initializes a newly created hasher. - * - * @param {Object} cfg (Optional) The configuration options to use for this hash computation. - * - * @example - * - * var hasher = CryptoJS.algo.SHA256.create(); - */ - init: function (cfg) { - // Apply config defaults - this.cfg = this.cfg.extend(cfg); - - // Set initial values - this.reset(); - }, - - /** - * Resets this hasher to its initial state. - * - * @example - * - * hasher.reset(); - */ - reset: function () { - // Reset data buffer - BufferedBlockAlgorithm.reset.call(this); - - // Perform concrete-hasher logic - this._doReset(); - }, - - /** - * Updates this hasher with a message. - * - * @param {WordArray|string} messageUpdate The message to append. - * - * @return {Hasher} This hasher. - * - * @example - * - * hasher.update('message'); - * hasher.update(wordArray); - */ - update: function (messageUpdate) { - // Append - this._append(messageUpdate); - - // Update the hash - this._process(); - - // Chainable - return this; - }, - - /** - * Finalizes the hash computation. - * Note that the finalize operation is effectively a destructive, read-once operation. - * - * @param {WordArray|string} messageUpdate (Optional) A final message update. - * - * @return {WordArray} The hash. - * - * @example - * - * var hash = hasher.finalize(); - * var hash = hasher.finalize('message'); - * var hash = hasher.finalize(wordArray); - */ - finalize: function (messageUpdate) { - // Final message update - if (messageUpdate) { - this._append(messageUpdate); - } - - // Perform concrete-hasher logic - var hash = this._doFinalize(); - - return hash; - }, - - blockSize: 512/32, - - /** - * Creates a shortcut function to a hasher's object interface. - * - * @param {Hasher} hasher The hasher to create a helper for. - * - * @return {Function} The shortcut function. - * - * @static - * - * @example - * - * var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256); - */ - _createHelper: function (hasher) { - return function (message, cfg) { - return new hasher.init(cfg).finalize(message); - }; - }, - - /** - * Creates a shortcut function to the HMAC's object interface. - * - * @param {Hasher} hasher The hasher to use in this HMAC helper. - * - * @return {Function} The shortcut function. - * - * @static - * - * @example - * - * var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256); - */ - _createHmacHelper: function (hasher) { - return function (message, key) { - return new C_algo.HMAC.init(hasher, key).finalize(message); - }; - } - }); - - /** - * Algorithm namespace. - */ - var C_algo = C.algo = {}; - - return C; - }(Math)); - - - return CryptoJS; - -})); -},{}],53:[function(require,module,exports){ -;(function (root, factory) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var WordArray = C_lib.WordArray; - var C_enc = C.enc; - - /** - * Base64 encoding strategy. - */ - var Base64 = C_enc.Base64 = { - /** - * Converts a word array to a Base64 string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The Base64 string. - * - * @static - * - * @example - * - * var base64String = CryptoJS.enc.Base64.stringify(wordArray); - */ - stringify: function (wordArray) { - // Shortcuts - var words = wordArray.words; - var sigBytes = wordArray.sigBytes; - var map = this._map; - - // Clamp excess bits - wordArray.clamp(); - - // Convert - var base64Chars = []; - for (var i = 0; i < sigBytes; i += 3) { - var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff; - var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff; - - var triplet = (byte1 << 16) | (byte2 << 8) | byte3; - - for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) { - base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); - } - } - - // Add padding - var paddingChar = map.charAt(64); - if (paddingChar) { - while (base64Chars.length % 4) { - base64Chars.push(paddingChar); - } - } - - return base64Chars.join(''); - }, - - /** - * Converts a Base64 string to a word array. - * - * @param {string} base64Str The Base64 string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.enc.Base64.parse(base64String); - */ - parse: function (base64Str) { - // Shortcuts - var base64StrLength = base64Str.length; - var map = this._map; - var reverseMap = this._reverseMap; - - if (!reverseMap) { - reverseMap = this._reverseMap = []; - for (var j = 0; j < map.length; j++) { - reverseMap[map.charCodeAt(j)] = j; - } - } - - // Ignore padding - var paddingChar = map.charAt(64); - if (paddingChar) { - var paddingIndex = base64Str.indexOf(paddingChar); - if (paddingIndex !== -1) { - base64StrLength = paddingIndex; - } - } - - // Convert - return parseLoop(base64Str, base64StrLength, reverseMap); - - }, - - _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' - }; - - function parseLoop(base64Str, base64StrLength, reverseMap) { - var words = []; - var nBytes = 0; - for (var i = 0; i < base64StrLength; i++) { - if (i % 4) { - var bits1 = reverseMap[base64Str.charCodeAt(i - 1)] << ((i % 4) * 2); - var bits2 = reverseMap[base64Str.charCodeAt(i)] >>> (6 - (i % 4) * 2); - words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8); - nBytes++; - } - } - return WordArray.create(words, nBytes); - } - }()); - - - return CryptoJS.enc.Base64; - -})); -},{"./core":52}],54:[function(require,module,exports){ -;(function (root, factory) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var WordArray = C_lib.WordArray; - var C_enc = C.enc; - - /** - * UTF-16 BE encoding strategy. - */ - var Utf16BE = C_enc.Utf16 = C_enc.Utf16BE = { - /** - * Converts a word array to a UTF-16 BE string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The UTF-16 BE string. - * - * @static - * - * @example - * - * var utf16String = CryptoJS.enc.Utf16.stringify(wordArray); - */ - stringify: function (wordArray) { - // Shortcuts - var words = wordArray.words; - var sigBytes = wordArray.sigBytes; - - // Convert - var utf16Chars = []; - for (var i = 0; i < sigBytes; i += 2) { - var codePoint = (words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff; - utf16Chars.push(String.fromCharCode(codePoint)); - } - - return utf16Chars.join(''); - }, - - /** - * Converts a UTF-16 BE string to a word array. - * - * @param {string} utf16Str The UTF-16 BE string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.enc.Utf16.parse(utf16String); - */ - parse: function (utf16Str) { - // Shortcut - var utf16StrLength = utf16Str.length; - - // Convert - var words = []; - for (var i = 0; i < utf16StrLength; i++) { - words[i >>> 1] |= utf16Str.charCodeAt(i) << (16 - (i % 2) * 16); - } - - return WordArray.create(words, utf16StrLength * 2); - } - }; - - /** - * UTF-16 LE encoding strategy. - */ - C_enc.Utf16LE = { - /** - * Converts a word array to a UTF-16 LE string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The UTF-16 LE string. - * - * @static - * - * @example - * - * var utf16Str = CryptoJS.enc.Utf16LE.stringify(wordArray); - */ - stringify: function (wordArray) { - // Shortcuts - var words = wordArray.words; - var sigBytes = wordArray.sigBytes; - - // Convert - var utf16Chars = []; - for (var i = 0; i < sigBytes; i += 2) { - var codePoint = swapEndian((words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff); - utf16Chars.push(String.fromCharCode(codePoint)); - } - - return utf16Chars.join(''); - }, - - /** - * Converts a UTF-16 LE string to a word array. - * - * @param {string} utf16Str The UTF-16 LE string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.enc.Utf16LE.parse(utf16Str); - */ - parse: function (utf16Str) { - // Shortcut - var utf16StrLength = utf16Str.length; - - // Convert - var words = []; - for (var i = 0; i < utf16StrLength; i++) { - words[i >>> 1] |= swapEndian(utf16Str.charCodeAt(i) << (16 - (i % 2) * 16)); - } - - return WordArray.create(words, utf16StrLength * 2); - } - }; - - function swapEndian(word) { - return ((word << 8) & 0xff00ff00) | ((word >>> 8) & 0x00ff00ff); - } - }()); - - - return CryptoJS.enc.Utf16; - -})); -},{"./core":52}],55:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./sha1"), require("./hmac")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./sha1", "./hmac"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var Base = C_lib.Base; - var WordArray = C_lib.WordArray; - var C_algo = C.algo; - var MD5 = C_algo.MD5; - - /** - * This key derivation function is meant to conform with EVP_BytesToKey. - * www.openssl.org/docs/crypto/EVP_BytesToKey.html - */ - var EvpKDF = C_algo.EvpKDF = Base.extend({ - /** - * Configuration options. - * - * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) - * @property {Hasher} hasher The hash algorithm to use. Default: MD5 - * @property {number} iterations The number of iterations to perform. Default: 1 - */ - cfg: Base.extend({ - keySize: 128/32, - hasher: MD5, - iterations: 1 - }), - - /** - * Initializes a newly created key derivation function. - * - * @param {Object} cfg (Optional) The configuration options to use for the derivation. - * - * @example - * - * var kdf = CryptoJS.algo.EvpKDF.create(); - * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8 }); - * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8, iterations: 1000 }); - */ - init: function (cfg) { - this.cfg = this.cfg.extend(cfg); - }, - - /** - * Derives a key from a password. - * - * @param {WordArray|string} password The password. - * @param {WordArray|string} salt A salt. - * - * @return {WordArray} The derived key. - * - * @example - * - * var key = kdf.compute(password, salt); - */ - compute: function (password, salt) { - // Shortcut - var cfg = this.cfg; - - // Init hasher - var hasher = cfg.hasher.create(); - - // Initial values - var derivedKey = WordArray.create(); - - // Shortcuts - var derivedKeyWords = derivedKey.words; - var keySize = cfg.keySize; - var iterations = cfg.iterations; - - // Generate key - while (derivedKeyWords.length < keySize) { - if (block) { - hasher.update(block); - } - var block = hasher.update(password).finalize(salt); - hasher.reset(); - - // Iterations - for (var i = 1; i < iterations; i++) { - block = hasher.finalize(block); - hasher.reset(); - } - - derivedKey.concat(block); - } - derivedKey.sigBytes = keySize * 4; - - return derivedKey; - } - }); - - /** - * Derives a key from a password. - * - * @param {WordArray|string} password The password. - * @param {WordArray|string} salt A salt. - * @param {Object} cfg (Optional) The configuration options to use for this computation. - * - * @return {WordArray} The derived key. - * - * @static - * - * @example - * - * var key = CryptoJS.EvpKDF(password, salt); - * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8 }); - * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8, iterations: 1000 }); - */ - C.EvpKDF = function (password, salt, cfg) { - return EvpKDF.create(cfg).compute(password, salt); - }; - }()); - - - return CryptoJS.EvpKDF; - -})); -},{"./core":52,"./hmac":57,"./sha1":76}],56:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function (undefined) { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var CipherParams = C_lib.CipherParams; - var C_enc = C.enc; - var Hex = C_enc.Hex; - var C_format = C.format; - - var HexFormatter = C_format.Hex = { - /** - * Converts the ciphertext of a cipher params object to a hexadecimally encoded string. - * - * @param {CipherParams} cipherParams The cipher params object. - * - * @return {string} The hexadecimally encoded string. - * - * @static - * - * @example - * - * var hexString = CryptoJS.format.Hex.stringify(cipherParams); - */ - stringify: function (cipherParams) { - return cipherParams.ciphertext.toString(Hex); - }, - - /** - * Converts a hexadecimally encoded ciphertext string to a cipher params object. - * - * @param {string} input The hexadecimally encoded string. - * - * @return {CipherParams} The cipher params object. - * - * @static - * - * @example - * - * var cipherParams = CryptoJS.format.Hex.parse(hexString); - */ - parse: function (input) { - var ciphertext = Hex.parse(input); - return CipherParams.create({ ciphertext: ciphertext }); - } - }; - }()); - - - return CryptoJS.format.Hex; - -})); -},{"./cipher-core":51,"./core":52}],57:[function(require,module,exports){ -;(function (root, factory) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var Base = C_lib.Base; - var C_enc = C.enc; - var Utf8 = C_enc.Utf8; - var C_algo = C.algo; - - /** - * HMAC algorithm. - */ - var HMAC = C_algo.HMAC = Base.extend({ - /** - * Initializes a newly created HMAC. - * - * @param {Hasher} hasher The hash algorithm to use. - * @param {WordArray|string} key The secret key. - * - * @example - * - * var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key); - */ - init: function (hasher, key) { - // Init hasher - hasher = this._hasher = new hasher.init(); - - // Convert string to WordArray, else assume WordArray already - if (typeof key == 'string') { - key = Utf8.parse(key); - } - - // Shortcuts - var hasherBlockSize = hasher.blockSize; - var hasherBlockSizeBytes = hasherBlockSize * 4; - - // Allow arbitrary length keys - if (key.sigBytes > hasherBlockSizeBytes) { - key = hasher.finalize(key); - } - - // Clamp excess bits - key.clamp(); - - // Clone key for inner and outer pads - var oKey = this._oKey = key.clone(); - var iKey = this._iKey = key.clone(); - - // Shortcuts - var oKeyWords = oKey.words; - var iKeyWords = iKey.words; - - // XOR keys with pad constants - for (var i = 0; i < hasherBlockSize; i++) { - oKeyWords[i] ^= 0x5c5c5c5c; - iKeyWords[i] ^= 0x36363636; - } - oKey.sigBytes = iKey.sigBytes = hasherBlockSizeBytes; - - // Set initial values - this.reset(); - }, - - /** - * Resets this HMAC to its initial state. - * - * @example - * - * hmacHasher.reset(); - */ - reset: function () { - // Shortcut - var hasher = this._hasher; - - // Reset - hasher.reset(); - hasher.update(this._iKey); - }, - - /** - * Updates this HMAC with a message. - * - * @param {WordArray|string} messageUpdate The message to append. - * - * @return {HMAC} This HMAC instance. - * - * @example - * - * hmacHasher.update('message'); - * hmacHasher.update(wordArray); - */ - update: function (messageUpdate) { - this._hasher.update(messageUpdate); - - // Chainable - return this; - }, - - /** - * Finalizes the HMAC computation. - * Note that the finalize operation is effectively a destructive, read-once operation. - * - * @param {WordArray|string} messageUpdate (Optional) A final message update. - * - * @return {WordArray} The HMAC. - * - * @example - * - * var hmac = hmacHasher.finalize(); - * var hmac = hmacHasher.finalize('message'); - * var hmac = hmacHasher.finalize(wordArray); - */ - finalize: function (messageUpdate) { - // Shortcut - var hasher = this._hasher; - - // Compute HMAC - var innerHash = hasher.finalize(messageUpdate); - hasher.reset(); - var hmac = hasher.finalize(this._oKey.clone().concat(innerHash)); - - return hmac; - } - }); - }()); - - -})); -},{"./core":52}],58:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./x64-core"), require("./lib-typedarrays"), require("./enc-utf16"), require("./enc-base64"), require("./md5"), require("./sha1"), require("./sha256"), require("./sha224"), require("./sha512"), require("./sha384"), require("./sha3"), require("./ripemd160"), require("./hmac"), require("./pbkdf2"), require("./evpkdf"), require("./cipher-core"), require("./mode-cfb"), require("./mode-ctr"), require("./mode-ctr-gladman"), require("./mode-ofb"), require("./mode-ecb"), require("./pad-ansix923"), require("./pad-iso10126"), require("./pad-iso97971"), require("./pad-zeropadding"), require("./pad-nopadding"), require("./format-hex"), require("./aes"), require("./tripledes"), require("./rc4"), require("./rabbit"), require("./rabbit-legacy")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./x64-core", "./lib-typedarrays", "./enc-utf16", "./enc-base64", "./md5", "./sha1", "./sha256", "./sha224", "./sha512", "./sha384", "./sha3", "./ripemd160", "./hmac", "./pbkdf2", "./evpkdf", "./cipher-core", "./mode-cfb", "./mode-ctr", "./mode-ctr-gladman", "./mode-ofb", "./mode-ecb", "./pad-ansix923", "./pad-iso10126", "./pad-iso97971", "./pad-zeropadding", "./pad-nopadding", "./format-hex", "./aes", "./tripledes", "./rc4", "./rabbit", "./rabbit-legacy"], factory); - } - else { - // Global (browser) - root.CryptoJS = factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - return CryptoJS; - -})); -},{"./aes":50,"./cipher-core":51,"./core":52,"./enc-base64":53,"./enc-utf16":54,"./evpkdf":55,"./format-hex":56,"./hmac":57,"./lib-typedarrays":59,"./md5":60,"./mode-cfb":61,"./mode-ctr":63,"./mode-ctr-gladman":62,"./mode-ecb":64,"./mode-ofb":65,"./pad-ansix923":66,"./pad-iso10126":67,"./pad-iso97971":68,"./pad-nopadding":69,"./pad-zeropadding":70,"./pbkdf2":71,"./rabbit":73,"./rabbit-legacy":72,"./rc4":74,"./ripemd160":75,"./sha1":76,"./sha224":77,"./sha256":78,"./sha3":79,"./sha384":80,"./sha512":81,"./tripledes":82,"./x64-core":83}],59:[function(require,module,exports){ -;(function (root, factory) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function () { - // Check if typed arrays are supported - if (typeof ArrayBuffer != 'function') { - return; - } - - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var WordArray = C_lib.WordArray; - - // Reference original init - var superInit = WordArray.init; - - // Augment WordArray.init to handle typed arrays - var subInit = WordArray.init = function (typedArray) { - // Convert buffers to uint8 - if (typedArray instanceof ArrayBuffer) { - typedArray = new Uint8Array(typedArray); - } - - // Convert other array views to uint8 - if ( - typedArray instanceof Int8Array || - (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray) || - typedArray instanceof Int16Array || - typedArray instanceof Uint16Array || - typedArray instanceof Int32Array || - typedArray instanceof Uint32Array || - typedArray instanceof Float32Array || - typedArray instanceof Float64Array - ) { - typedArray = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); - } - - // Handle Uint8Array - if (typedArray instanceof Uint8Array) { - // Shortcut - var typedArrayByteLength = typedArray.byteLength; - - // Extract bytes - var words = []; - for (var i = 0; i < typedArrayByteLength; i++) { - words[i >>> 2] |= typedArray[i] << (24 - (i % 4) * 8); - } - - // Initialize this word array - superInit.call(this, words, typedArrayByteLength); - } else { - // Else call normal init - superInit.apply(this, arguments); - } - }; - - subInit.prototype = WordArray; - }()); - - - return CryptoJS.lib.WordArray; - -})); -},{"./core":52}],60:[function(require,module,exports){ -;(function (root, factory) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function (Math) { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var WordArray = C_lib.WordArray; - var Hasher = C_lib.Hasher; - var C_algo = C.algo; - - // Constants table - var T = []; - - // Compute constants - (function () { - for (var i = 0; i < 64; i++) { - T[i] = (Math.abs(Math.sin(i + 1)) * 0x100000000) | 0; - } - }()); - - /** - * MD5 hash algorithm. - */ - var MD5 = C_algo.MD5 = Hasher.extend({ - _doReset: function () { - this._hash = new WordArray.init([ - 0x67452301, 0xefcdab89, - 0x98badcfe, 0x10325476 - ]); - }, - - _doProcessBlock: function (M, offset) { - // Swap endian - for (var i = 0; i < 16; i++) { - // Shortcuts - var offset_i = offset + i; - var M_offset_i = M[offset_i]; - - M[offset_i] = ( - (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | - (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) - ); - } - - // Shortcuts - var H = this._hash.words; - - var M_offset_0 = M[offset + 0]; - var M_offset_1 = M[offset + 1]; - var M_offset_2 = M[offset + 2]; - var M_offset_3 = M[offset + 3]; - var M_offset_4 = M[offset + 4]; - var M_offset_5 = M[offset + 5]; - var M_offset_6 = M[offset + 6]; - var M_offset_7 = M[offset + 7]; - var M_offset_8 = M[offset + 8]; - var M_offset_9 = M[offset + 9]; - var M_offset_10 = M[offset + 10]; - var M_offset_11 = M[offset + 11]; - var M_offset_12 = M[offset + 12]; - var M_offset_13 = M[offset + 13]; - var M_offset_14 = M[offset + 14]; - var M_offset_15 = M[offset + 15]; - - // Working varialbes - var a = H[0]; - var b = H[1]; - var c = H[2]; - var d = H[3]; - - // Computation - a = FF(a, b, c, d, M_offset_0, 7, T[0]); - d = FF(d, a, b, c, M_offset_1, 12, T[1]); - c = FF(c, d, a, b, M_offset_2, 17, T[2]); - b = FF(b, c, d, a, M_offset_3, 22, T[3]); - a = FF(a, b, c, d, M_offset_4, 7, T[4]); - d = FF(d, a, b, c, M_offset_5, 12, T[5]); - c = FF(c, d, a, b, M_offset_6, 17, T[6]); - b = FF(b, c, d, a, M_offset_7, 22, T[7]); - a = FF(a, b, c, d, M_offset_8, 7, T[8]); - d = FF(d, a, b, c, M_offset_9, 12, T[9]); - c = FF(c, d, a, b, M_offset_10, 17, T[10]); - b = FF(b, c, d, a, M_offset_11, 22, T[11]); - a = FF(a, b, c, d, M_offset_12, 7, T[12]); - d = FF(d, a, b, c, M_offset_13, 12, T[13]); - c = FF(c, d, a, b, M_offset_14, 17, T[14]); - b = FF(b, c, d, a, M_offset_15, 22, T[15]); - - a = GG(a, b, c, d, M_offset_1, 5, T[16]); - d = GG(d, a, b, c, M_offset_6, 9, T[17]); - c = GG(c, d, a, b, M_offset_11, 14, T[18]); - b = GG(b, c, d, a, M_offset_0, 20, T[19]); - a = GG(a, b, c, d, M_offset_5, 5, T[20]); - d = GG(d, a, b, c, M_offset_10, 9, T[21]); - c = GG(c, d, a, b, M_offset_15, 14, T[22]); - b = GG(b, c, d, a, M_offset_4, 20, T[23]); - a = GG(a, b, c, d, M_offset_9, 5, T[24]); - d = GG(d, a, b, c, M_offset_14, 9, T[25]); - c = GG(c, d, a, b, M_offset_3, 14, T[26]); - b = GG(b, c, d, a, M_offset_8, 20, T[27]); - a = GG(a, b, c, d, M_offset_13, 5, T[28]); - d = GG(d, a, b, c, M_offset_2, 9, T[29]); - c = GG(c, d, a, b, M_offset_7, 14, T[30]); - b = GG(b, c, d, a, M_offset_12, 20, T[31]); - - a = HH(a, b, c, d, M_offset_5, 4, T[32]); - d = HH(d, a, b, c, M_offset_8, 11, T[33]); - c = HH(c, d, a, b, M_offset_11, 16, T[34]); - b = HH(b, c, d, a, M_offset_14, 23, T[35]); - a = HH(a, b, c, d, M_offset_1, 4, T[36]); - d = HH(d, a, b, c, M_offset_4, 11, T[37]); - c = HH(c, d, a, b, M_offset_7, 16, T[38]); - b = HH(b, c, d, a, M_offset_10, 23, T[39]); - a = HH(a, b, c, d, M_offset_13, 4, T[40]); - d = HH(d, a, b, c, M_offset_0, 11, T[41]); - c = HH(c, d, a, b, M_offset_3, 16, T[42]); - b = HH(b, c, d, a, M_offset_6, 23, T[43]); - a = HH(a, b, c, d, M_offset_9, 4, T[44]); - d = HH(d, a, b, c, M_offset_12, 11, T[45]); - c = HH(c, d, a, b, M_offset_15, 16, T[46]); - b = HH(b, c, d, a, M_offset_2, 23, T[47]); - - a = II(a, b, c, d, M_offset_0, 6, T[48]); - d = II(d, a, b, c, M_offset_7, 10, T[49]); - c = II(c, d, a, b, M_offset_14, 15, T[50]); - b = II(b, c, d, a, M_offset_5, 21, T[51]); - a = II(a, b, c, d, M_offset_12, 6, T[52]); - d = II(d, a, b, c, M_offset_3, 10, T[53]); - c = II(c, d, a, b, M_offset_10, 15, T[54]); - b = II(b, c, d, a, M_offset_1, 21, T[55]); - a = II(a, b, c, d, M_offset_8, 6, T[56]); - d = II(d, a, b, c, M_offset_15, 10, T[57]); - c = II(c, d, a, b, M_offset_6, 15, T[58]); - b = II(b, c, d, a, M_offset_13, 21, T[59]); - a = II(a, b, c, d, M_offset_4, 6, T[60]); - d = II(d, a, b, c, M_offset_11, 10, T[61]); - c = II(c, d, a, b, M_offset_2, 15, T[62]); - b = II(b, c, d, a, M_offset_9, 21, T[63]); - - // Intermediate hash value - H[0] = (H[0] + a) | 0; - H[1] = (H[1] + b) | 0; - H[2] = (H[2] + c) | 0; - H[3] = (H[3] + d) | 0; - }, - - _doFinalize: function () { - // Shortcuts - var data = this._data; - var dataWords = data.words; - - var nBitsTotal = this._nDataBytes * 8; - var nBitsLeft = data.sigBytes * 8; - - // Add padding - dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); - - var nBitsTotalH = Math.floor(nBitsTotal / 0x100000000); - var nBitsTotalL = nBitsTotal; - dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = ( - (((nBitsTotalH << 8) | (nBitsTotalH >>> 24)) & 0x00ff00ff) | - (((nBitsTotalH << 24) | (nBitsTotalH >>> 8)) & 0xff00ff00) - ); - dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( - (((nBitsTotalL << 8) | (nBitsTotalL >>> 24)) & 0x00ff00ff) | - (((nBitsTotalL << 24) | (nBitsTotalL >>> 8)) & 0xff00ff00) - ); - - data.sigBytes = (dataWords.length + 1) * 4; - - // Hash final blocks - this._process(); - - // Shortcuts - var hash = this._hash; - var H = hash.words; - - // Swap endian - for (var i = 0; i < 4; i++) { - // Shortcut - var H_i = H[i]; - - H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | - (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); - } - - // Return final computed hash - return hash; - }, - - clone: function () { - var clone = Hasher.clone.call(this); - clone._hash = this._hash.clone(); - - return clone; - } - }); - - function FF(a, b, c, d, x, s, t) { - var n = a + ((b & c) | (~b & d)) + x + t; - return ((n << s) | (n >>> (32 - s))) + b; - } - - function GG(a, b, c, d, x, s, t) { - var n = a + ((b & d) | (c & ~d)) + x + t; - return ((n << s) | (n >>> (32 - s))) + b; - } - - function HH(a, b, c, d, x, s, t) { - var n = a + (b ^ c ^ d) + x + t; - return ((n << s) | (n >>> (32 - s))) + b; - } - - function II(a, b, c, d, x, s, t) { - var n = a + (c ^ (b | ~d)) + x + t; - return ((n << s) | (n >>> (32 - s))) + b; - } - - /** - * Shortcut function to the hasher's object interface. - * - * @param {WordArray|string} message The message to hash. - * - * @return {WordArray} The hash. - * - * @static - * - * @example - * - * var hash = CryptoJS.MD5('message'); - * var hash = CryptoJS.MD5(wordArray); - */ - C.MD5 = Hasher._createHelper(MD5); - - /** - * Shortcut function to the HMAC's object interface. - * - * @param {WordArray|string} message The message to hash. - * @param {WordArray|string} key The secret key. - * - * @return {WordArray} The HMAC. - * - * @static - * - * @example - * - * var hmac = CryptoJS.HmacMD5(message, key); - */ - C.HmacMD5 = Hasher._createHmacHelper(MD5); - }(Math)); - - - return CryptoJS.MD5; - -})); -},{"./core":52}],61:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - /** - * Cipher Feedback block mode. - */ - CryptoJS.mode.CFB = (function () { - var CFB = CryptoJS.lib.BlockCipherMode.extend(); - - CFB.Encryptor = CFB.extend({ - processBlock: function (words, offset) { - // Shortcuts - var cipher = this._cipher; - var blockSize = cipher.blockSize; - - generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); - - // Remember this block to use with next block - this._prevBlock = words.slice(offset, offset + blockSize); - } - }); - - CFB.Decryptor = CFB.extend({ - processBlock: function (words, offset) { - // Shortcuts - var cipher = this._cipher; - var blockSize = cipher.blockSize; - - // Remember this block to use with next block - var thisBlock = words.slice(offset, offset + blockSize); - - generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); - - // This block becomes the previous block - this._prevBlock = thisBlock; - } - }); - - function generateKeystreamAndEncrypt(words, offset, blockSize, cipher) { - // Shortcut - var iv = this._iv; - - // Generate keystream - if (iv) { - var keystream = iv.slice(0); - - // Remove IV for subsequent blocks - this._iv = undefined; - } else { - var keystream = this._prevBlock; - } - cipher.encryptBlock(keystream, 0); - - // Encrypt - for (var i = 0; i < blockSize; i++) { - words[offset + i] ^= keystream[i]; - } - } - - return CFB; - }()); - - - return CryptoJS.mode.CFB; - -})); -},{"./cipher-core":51,"./core":52}],62:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - /** @preserve - * Counter block mode compatible with Dr Brian Gladman fileenc.c - * derived from CryptoJS.mode.CTR - * Jan Hruby jhruby.web@gmail.com - */ - CryptoJS.mode.CTRGladman = (function () { - var CTRGladman = CryptoJS.lib.BlockCipherMode.extend(); - - function incWord(word) - { - if (((word >> 24) & 0xff) === 0xff) { //overflow - var b1 = (word >> 16)&0xff; - var b2 = (word >> 8)&0xff; - var b3 = word & 0xff; - - if (b1 === 0xff) // overflow b1 - { - b1 = 0; - if (b2 === 0xff) - { - b2 = 0; - if (b3 === 0xff) - { - b3 = 0; - } - else - { - ++b3; - } - } - else - { - ++b2; - } - } - else - { - ++b1; - } - - word = 0; - word += (b1 << 16); - word += (b2 << 8); - word += b3; - } - else - { - word += (0x01 << 24); - } - return word; - } - - function incCounter(counter) - { - if ((counter[0] = incWord(counter[0])) === 0) - { - // encr_data in fileenc.c from Dr Brian Gladman's counts only with DWORD j < 8 - counter[1] = incWord(counter[1]); - } - return counter; - } - - var Encryptor = CTRGladman.Encryptor = CTRGladman.extend({ - processBlock: function (words, offset) { - // Shortcuts - var cipher = this._cipher - var blockSize = cipher.blockSize; - var iv = this._iv; - var counter = this._counter; - - // Generate keystream - if (iv) { - counter = this._counter = iv.slice(0); - - // Remove IV for subsequent blocks - this._iv = undefined; - } - - incCounter(counter); - - var keystream = counter.slice(0); - cipher.encryptBlock(keystream, 0); - - // Encrypt - for (var i = 0; i < blockSize; i++) { - words[offset + i] ^= keystream[i]; - } - } - }); - - CTRGladman.Decryptor = Encryptor; - - return CTRGladman; - }()); - - - - - return CryptoJS.mode.CTRGladman; - -})); -},{"./cipher-core":51,"./core":52}],63:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - /** - * Counter block mode. - */ - CryptoJS.mode.CTR = (function () { - var CTR = CryptoJS.lib.BlockCipherMode.extend(); - - var Encryptor = CTR.Encryptor = CTR.extend({ - processBlock: function (words, offset) { - // Shortcuts - var cipher = this._cipher - var blockSize = cipher.blockSize; - var iv = this._iv; - var counter = this._counter; - - // Generate keystream - if (iv) { - counter = this._counter = iv.slice(0); - - // Remove IV for subsequent blocks - this._iv = undefined; - } - var keystream = counter.slice(0); - cipher.encryptBlock(keystream, 0); - - // Increment counter - counter[blockSize - 1] = (counter[blockSize - 1] + 1) | 0 - - // Encrypt - for (var i = 0; i < blockSize; i++) { - words[offset + i] ^= keystream[i]; - } - } - }); - - CTR.Decryptor = Encryptor; - - return CTR; - }()); - - - return CryptoJS.mode.CTR; - -})); -},{"./cipher-core":51,"./core":52}],64:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - /** - * Electronic Codebook block mode. - */ - CryptoJS.mode.ECB = (function () { - var ECB = CryptoJS.lib.BlockCipherMode.extend(); - - ECB.Encryptor = ECB.extend({ - processBlock: function (words, offset) { - this._cipher.encryptBlock(words, offset); - } - }); - - ECB.Decryptor = ECB.extend({ - processBlock: function (words, offset) { - this._cipher.decryptBlock(words, offset); - } - }); - - return ECB; - }()); - - - return CryptoJS.mode.ECB; - -})); -},{"./cipher-core":51,"./core":52}],65:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - /** - * Output Feedback block mode. - */ - CryptoJS.mode.OFB = (function () { - var OFB = CryptoJS.lib.BlockCipherMode.extend(); - - var Encryptor = OFB.Encryptor = OFB.extend({ - processBlock: function (words, offset) { - // Shortcuts - var cipher = this._cipher - var blockSize = cipher.blockSize; - var iv = this._iv; - var keystream = this._keystream; - - // Generate keystream - if (iv) { - keystream = this._keystream = iv.slice(0); - - // Remove IV for subsequent blocks - this._iv = undefined; - } - cipher.encryptBlock(keystream, 0); - - // Encrypt - for (var i = 0; i < blockSize; i++) { - words[offset + i] ^= keystream[i]; - } - } - }); - - OFB.Decryptor = Encryptor; - - return OFB; - }()); - - - return CryptoJS.mode.OFB; - -})); -},{"./cipher-core":51,"./core":52}],66:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - /** - * ANSI X.923 padding strategy. - */ - CryptoJS.pad.AnsiX923 = { - pad: function (data, blockSize) { - // Shortcuts - var dataSigBytes = data.sigBytes; - var blockSizeBytes = blockSize * 4; - - // Count padding bytes - var nPaddingBytes = blockSizeBytes - dataSigBytes % blockSizeBytes; - - // Compute last byte position - var lastBytePos = dataSigBytes + nPaddingBytes - 1; - - // Pad - data.clamp(); - data.words[lastBytePos >>> 2] |= nPaddingBytes << (24 - (lastBytePos % 4) * 8); - data.sigBytes += nPaddingBytes; - }, - - unpad: function (data) { - // Get number of padding bytes from last byte - var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; - - // Remove padding - data.sigBytes -= nPaddingBytes; - } - }; - - - return CryptoJS.pad.Ansix923; - -})); -},{"./cipher-core":51,"./core":52}],67:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - /** - * ISO 10126 padding strategy. - */ - CryptoJS.pad.Iso10126 = { - pad: function (data, blockSize) { - // Shortcut - var blockSizeBytes = blockSize * 4; - - // Count padding bytes - var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; - - // Pad - data.concat(CryptoJS.lib.WordArray.random(nPaddingBytes - 1)). - concat(CryptoJS.lib.WordArray.create([nPaddingBytes << 24], 1)); - }, - - unpad: function (data) { - // Get number of padding bytes from last byte - var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; - - // Remove padding - data.sigBytes -= nPaddingBytes; - } - }; - - - return CryptoJS.pad.Iso10126; - -})); -},{"./cipher-core":51,"./core":52}],68:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - /** - * ISO/IEC 9797-1 Padding Method 2. - */ - CryptoJS.pad.Iso97971 = { - pad: function (data, blockSize) { - // Add 0x80 byte - data.concat(CryptoJS.lib.WordArray.create([0x80000000], 1)); - - // Zero pad the rest - CryptoJS.pad.ZeroPadding.pad(data, blockSize); - }, - - unpad: function (data) { - // Remove zero padding - CryptoJS.pad.ZeroPadding.unpad(data); - - // Remove one more byte -- the 0x80 byte - data.sigBytes--; - } - }; - - - return CryptoJS.pad.Iso97971; - -})); -},{"./cipher-core":51,"./core":52}],69:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - /** - * A noop padding strategy. - */ - CryptoJS.pad.NoPadding = { - pad: function () { - }, - - unpad: function () { - } - }; - - - return CryptoJS.pad.NoPadding; - -})); -},{"./cipher-core":51,"./core":52}],70:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - /** - * Zero padding strategy. - */ - CryptoJS.pad.ZeroPadding = { - pad: function (data, blockSize) { - // Shortcut - var blockSizeBytes = blockSize * 4; - - // Pad - data.clamp(); - data.sigBytes += blockSizeBytes - ((data.sigBytes % blockSizeBytes) || blockSizeBytes); - }, - - unpad: function (data) { - // Shortcut - var dataWords = data.words; - - // Unpad - var i = data.sigBytes - 1; - while (!((dataWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff)) { - i--; - } - data.sigBytes = i + 1; - } - }; - - - return CryptoJS.pad.ZeroPadding; - -})); -},{"./cipher-core":51,"./core":52}],71:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./sha1"), require("./hmac")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./sha1", "./hmac"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var Base = C_lib.Base; - var WordArray = C_lib.WordArray; - var C_algo = C.algo; - var SHA1 = C_algo.SHA1; - var HMAC = C_algo.HMAC; - - /** - * Password-Based Key Derivation Function 2 algorithm. - */ - var PBKDF2 = C_algo.PBKDF2 = Base.extend({ - /** - * Configuration options. - * - * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) - * @property {Hasher} hasher The hasher to use. Default: SHA1 - * @property {number} iterations The number of iterations to perform. Default: 1 - */ - cfg: Base.extend({ - keySize: 128/32, - hasher: SHA1, - iterations: 1 - }), - - /** - * Initializes a newly created key derivation function. - * - * @param {Object} cfg (Optional) The configuration options to use for the derivation. - * - * @example - * - * var kdf = CryptoJS.algo.PBKDF2.create(); - * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8 }); - * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8, iterations: 1000 }); - */ - init: function (cfg) { - this.cfg = this.cfg.extend(cfg); - }, - - /** - * Computes the Password-Based Key Derivation Function 2. - * - * @param {WordArray|string} password The password. - * @param {WordArray|string} salt A salt. - * - * @return {WordArray} The derived key. - * - * @example - * - * var key = kdf.compute(password, salt); - */ - compute: function (password, salt) { - // Shortcut - var cfg = this.cfg; - - // Init HMAC - var hmac = HMAC.create(cfg.hasher, password); - - // Initial values - var derivedKey = WordArray.create(); - var blockIndex = WordArray.create([0x00000001]); - - // Shortcuts - var derivedKeyWords = derivedKey.words; - var blockIndexWords = blockIndex.words; - var keySize = cfg.keySize; - var iterations = cfg.iterations; - - // Generate key - while (derivedKeyWords.length < keySize) { - var block = hmac.update(salt).finalize(blockIndex); - hmac.reset(); - - // Shortcuts - var blockWords = block.words; - var blockWordsLength = blockWords.length; - - // Iterations - var intermediate = block; - for (var i = 1; i < iterations; i++) { - intermediate = hmac.finalize(intermediate); - hmac.reset(); - - // Shortcut - var intermediateWords = intermediate.words; - - // XOR intermediate with block - for (var j = 0; j < blockWordsLength; j++) { - blockWords[j] ^= intermediateWords[j]; - } - } - - derivedKey.concat(block); - blockIndexWords[0]++; - } - derivedKey.sigBytes = keySize * 4; - - return derivedKey; - } - }); - - /** - * Computes the Password-Based Key Derivation Function 2. - * - * @param {WordArray|string} password The password. - * @param {WordArray|string} salt A salt. - * @param {Object} cfg (Optional) The configuration options to use for this computation. - * - * @return {WordArray} The derived key. - * - * @static - * - * @example - * - * var key = CryptoJS.PBKDF2(password, salt); - * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8 }); - * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8, iterations: 1000 }); - */ - C.PBKDF2 = function (password, salt, cfg) { - return PBKDF2.create(cfg).compute(password, salt); - }; - }()); - - - return CryptoJS.PBKDF2; - -})); -},{"./core":52,"./hmac":57,"./sha1":76}],72:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var StreamCipher = C_lib.StreamCipher; - var C_algo = C.algo; - - // Reusable objects - var S = []; - var C_ = []; - var G = []; - - /** - * Rabbit stream cipher algorithm. - * - * This is a legacy version that neglected to convert the key to little-endian. - * This error doesn't affect the cipher's security, - * but it does affect its compatibility with other implementations. - */ - var RabbitLegacy = C_algo.RabbitLegacy = StreamCipher.extend({ - _doReset: function () { - // Shortcuts - var K = this._key.words; - var iv = this.cfg.iv; - - // Generate initial state values - var X = this._X = [ - K[0], (K[3] << 16) | (K[2] >>> 16), - K[1], (K[0] << 16) | (K[3] >>> 16), - K[2], (K[1] << 16) | (K[0] >>> 16), - K[3], (K[2] << 16) | (K[1] >>> 16) - ]; - - // Generate initial counter values - var C = this._C = [ - (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), - (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), - (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), - (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff) - ]; - - // Carry bit - this._b = 0; - - // Iterate the system four times - for (var i = 0; i < 4; i++) { - nextState.call(this); - } - - // Modify the counters - for (var i = 0; i < 8; i++) { - C[i] ^= X[(i + 4) & 7]; - } - - // IV setup - if (iv) { - // Shortcuts - var IV = iv.words; - var IV_0 = IV[0]; - var IV_1 = IV[1]; - - // Generate four subvectors - var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); - var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); - var i1 = (i0 >>> 16) | (i2 & 0xffff0000); - var i3 = (i2 << 16) | (i0 & 0x0000ffff); - - // Modify counter values - C[0] ^= i0; - C[1] ^= i1; - C[2] ^= i2; - C[3] ^= i3; - C[4] ^= i0; - C[5] ^= i1; - C[6] ^= i2; - C[7] ^= i3; - - // Iterate the system four times - for (var i = 0; i < 4; i++) { - nextState.call(this); - } - } - }, - - _doProcessBlock: function (M, offset) { - // Shortcut - var X = this._X; - - // Iterate the system - nextState.call(this); - - // Generate four keystream words - S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); - S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); - S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); - S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); - - for (var i = 0; i < 4; i++) { - // Swap endian - S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | - (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); - - // Encrypt - M[offset + i] ^= S[i]; - } - }, - - blockSize: 128/32, - - ivSize: 64/32 - }); - - function nextState() { - // Shortcuts - var X = this._X; - var C = this._C; - - // Save old counter values - for (var i = 0; i < 8; i++) { - C_[i] = C[i]; - } - - // Calculate new counter values - C[0] = (C[0] + 0x4d34d34d + this._b) | 0; - C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; - C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; - C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; - C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; - C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; - C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; - C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; - this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; - - // Calculate the g-values - for (var i = 0; i < 8; i++) { - var gx = X[i] + C[i]; - - // Construct high and low argument for squaring - var ga = gx & 0xffff; - var gb = gx >>> 16; - - // Calculate high and low result of squaring - var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; - var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); - - // High XOR low - G[i] = gh ^ gl; - } - - // Calculate new state values - X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; - X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; - X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; - X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; - X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; - X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; - X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; - X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; - } - - /** - * Shortcut functions to the cipher's object interface. - * - * @example - * - * var ciphertext = CryptoJS.RabbitLegacy.encrypt(message, key, cfg); - * var plaintext = CryptoJS.RabbitLegacy.decrypt(ciphertext, key, cfg); - */ - C.RabbitLegacy = StreamCipher._createHelper(RabbitLegacy); - }()); - - - return CryptoJS.RabbitLegacy; - -})); -},{"./cipher-core":51,"./core":52,"./enc-base64":53,"./evpkdf":55,"./md5":60}],73:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var StreamCipher = C_lib.StreamCipher; - var C_algo = C.algo; - - // Reusable objects - var S = []; - var C_ = []; - var G = []; - - /** - * Rabbit stream cipher algorithm - */ - var Rabbit = C_algo.Rabbit = StreamCipher.extend({ - _doReset: function () { - // Shortcuts - var K = this._key.words; - var iv = this.cfg.iv; - - // Swap endian - for (var i = 0; i < 4; i++) { - K[i] = (((K[i] << 8) | (K[i] >>> 24)) & 0x00ff00ff) | - (((K[i] << 24) | (K[i] >>> 8)) & 0xff00ff00); - } - - // Generate initial state values - var X = this._X = [ - K[0], (K[3] << 16) | (K[2] >>> 16), - K[1], (K[0] << 16) | (K[3] >>> 16), - K[2], (K[1] << 16) | (K[0] >>> 16), - K[3], (K[2] << 16) | (K[1] >>> 16) - ]; - - // Generate initial counter values - var C = this._C = [ - (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), - (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), - (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), - (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff) - ]; - - // Carry bit - this._b = 0; - - // Iterate the system four times - for (var i = 0; i < 4; i++) { - nextState.call(this); - } - - // Modify the counters - for (var i = 0; i < 8; i++) { - C[i] ^= X[(i + 4) & 7]; - } - - // IV setup - if (iv) { - // Shortcuts - var IV = iv.words; - var IV_0 = IV[0]; - var IV_1 = IV[1]; - - // Generate four subvectors - var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); - var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); - var i1 = (i0 >>> 16) | (i2 & 0xffff0000); - var i3 = (i2 << 16) | (i0 & 0x0000ffff); - - // Modify counter values - C[0] ^= i0; - C[1] ^= i1; - C[2] ^= i2; - C[3] ^= i3; - C[4] ^= i0; - C[5] ^= i1; - C[6] ^= i2; - C[7] ^= i3; - - // Iterate the system four times - for (var i = 0; i < 4; i++) { - nextState.call(this); - } - } - }, - - _doProcessBlock: function (M, offset) { - // Shortcut - var X = this._X; - - // Iterate the system - nextState.call(this); - - // Generate four keystream words - S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); - S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); - S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); - S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); - - for (var i = 0; i < 4; i++) { - // Swap endian - S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | - (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); - - // Encrypt - M[offset + i] ^= S[i]; - } - }, - - blockSize: 128/32, - - ivSize: 64/32 - }); - - function nextState() { - // Shortcuts - var X = this._X; - var C = this._C; - - // Save old counter values - for (var i = 0; i < 8; i++) { - C_[i] = C[i]; - } - - // Calculate new counter values - C[0] = (C[0] + 0x4d34d34d + this._b) | 0; - C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; - C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; - C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; - C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; - C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; - C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; - C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; - this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; - - // Calculate the g-values - for (var i = 0; i < 8; i++) { - var gx = X[i] + C[i]; - - // Construct high and low argument for squaring - var ga = gx & 0xffff; - var gb = gx >>> 16; - - // Calculate high and low result of squaring - var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; - var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); - - // High XOR low - G[i] = gh ^ gl; - } - - // Calculate new state values - X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; - X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; - X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; - X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; - X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; - X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; - X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; - X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; - } - - /** - * Shortcut functions to the cipher's object interface. - * - * @example - * - * var ciphertext = CryptoJS.Rabbit.encrypt(message, key, cfg); - * var plaintext = CryptoJS.Rabbit.decrypt(ciphertext, key, cfg); - */ - C.Rabbit = StreamCipher._createHelper(Rabbit); - }()); - - - return CryptoJS.Rabbit; - -})); -},{"./cipher-core":51,"./core":52,"./enc-base64":53,"./evpkdf":55,"./md5":60}],74:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var StreamCipher = C_lib.StreamCipher; - var C_algo = C.algo; - - /** - * RC4 stream cipher algorithm. - */ - var RC4 = C_algo.RC4 = StreamCipher.extend({ - _doReset: function () { - // Shortcuts - var key = this._key; - var keyWords = key.words; - var keySigBytes = key.sigBytes; - - // Init sbox - var S = this._S = []; - for (var i = 0; i < 256; i++) { - S[i] = i; - } - - // Key setup - for (var i = 0, j = 0; i < 256; i++) { - var keyByteIndex = i % keySigBytes; - var keyByte = (keyWords[keyByteIndex >>> 2] >>> (24 - (keyByteIndex % 4) * 8)) & 0xff; - - j = (j + S[i] + keyByte) % 256; - - // Swap - var t = S[i]; - S[i] = S[j]; - S[j] = t; - } - - // Counters - this._i = this._j = 0; - }, - - _doProcessBlock: function (M, offset) { - M[offset] ^= generateKeystreamWord.call(this); - }, - - keySize: 256/32, - - ivSize: 0 - }); - - function generateKeystreamWord() { - // Shortcuts - var S = this._S; - var i = this._i; - var j = this._j; - - // Generate keystream word - var keystreamWord = 0; - for (var n = 0; n < 4; n++) { - i = (i + 1) % 256; - j = (j + S[i]) % 256; - - // Swap - var t = S[i]; - S[i] = S[j]; - S[j] = t; - - keystreamWord |= S[(S[i] + S[j]) % 256] << (24 - n * 8); - } - - // Update counters - this._i = i; - this._j = j; - - return keystreamWord; - } - - /** - * Shortcut functions to the cipher's object interface. - * - * @example - * - * var ciphertext = CryptoJS.RC4.encrypt(message, key, cfg); - * var plaintext = CryptoJS.RC4.decrypt(ciphertext, key, cfg); - */ - C.RC4 = StreamCipher._createHelper(RC4); + },{}],85:[function(require,module,exports){ + module.exports = XMLHttpRequest; - /** - * Modified RC4 stream cipher algorithm. - */ - var RC4Drop = C_algo.RC4Drop = RC4.extend({ - /** - * Configuration options. - * - * @property {number} drop The number of keystream words to drop. Default 192 - */ - cfg: RC4.cfg.extend({ - drop: 192 - }), - - _doReset: function () { - RC4._doReset.call(this); - - // Drop - for (var i = this.cfg.drop; i > 0; i--) { - generateKeystreamWord.call(this); - } - } - }); - - /** - * Shortcut functions to the cipher's object interface. - * - * @example - * - * var ciphertext = CryptoJS.RC4Drop.encrypt(message, key, cfg); - * var plaintext = CryptoJS.RC4Drop.decrypt(ciphertext, key, cfg); - */ - C.RC4Drop = StreamCipher._createHelper(RC4Drop); - }()); - - - return CryptoJS.RC4; - -})); -},{"./cipher-core":51,"./core":52,"./enc-base64":53,"./evpkdf":55,"./md5":60}],75:[function(require,module,exports){ -;(function (root, factory) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - /** @preserve - (c) 2012 by Cédric Mesnil. All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - (function (Math) { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var WordArray = C_lib.WordArray; - var Hasher = C_lib.Hasher; - var C_algo = C.algo; - - // Constants table - var _zl = WordArray.create([ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, - 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, - 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, - 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13]); - var _zr = WordArray.create([ - 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, - 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, - 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, - 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, - 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11]); - var _sl = WordArray.create([ - 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, - 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, - 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, - 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, - 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 ]); - var _sr = WordArray.create([ - 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, - 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, - 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, - 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, - 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 ]); - - var _hl = WordArray.create([ 0x00000000, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E]); - var _hr = WordArray.create([ 0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000]); - - /** - * RIPEMD160 hash algorithm. - */ - var RIPEMD160 = C_algo.RIPEMD160 = Hasher.extend({ - _doReset: function () { - this._hash = WordArray.create([0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]); - }, - - _doProcessBlock: function (M, offset) { - - // Swap endian - for (var i = 0; i < 16; i++) { - // Shortcuts - var offset_i = offset + i; - var M_offset_i = M[offset_i]; - - // Swap - M[offset_i] = ( - (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | - (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) - ); - } - // Shortcut - var H = this._hash.words; - var hl = _hl.words; - var hr = _hr.words; - var zl = _zl.words; - var zr = _zr.words; - var sl = _sl.words; - var sr = _sr.words; - - // Working variables - var al, bl, cl, dl, el; - var ar, br, cr, dr, er; - - ar = al = H[0]; - br = bl = H[1]; - cr = cl = H[2]; - dr = dl = H[3]; - er = el = H[4]; - // Computation - var t; - for (var i = 0; i < 80; i += 1) { - t = (al + M[offset+zl[i]])|0; - if (i<16){ - t += f1(bl,cl,dl) + hl[0]; - } else if (i<32) { - t += f2(bl,cl,dl) + hl[1]; - } else if (i<48) { - t += f3(bl,cl,dl) + hl[2]; - } else if (i<64) { - t += f4(bl,cl,dl) + hl[3]; - } else {// if (i<80) { - t += f5(bl,cl,dl) + hl[4]; - } - t = t|0; - t = rotl(t,sl[i]); - t = (t+el)|0; - al = el; - el = dl; - dl = rotl(cl, 10); - cl = bl; - bl = t; - - t = (ar + M[offset+zr[i]])|0; - if (i<16){ - t += f5(br,cr,dr) + hr[0]; - } else if (i<32) { - t += f4(br,cr,dr) + hr[1]; - } else if (i<48) { - t += f3(br,cr,dr) + hr[2]; - } else if (i<64) { - t += f2(br,cr,dr) + hr[3]; - } else {// if (i<80) { - t += f1(br,cr,dr) + hr[4]; - } - t = t|0; - t = rotl(t,sr[i]) ; - t = (t+er)|0; - ar = er; - er = dr; - dr = rotl(cr, 10); - cr = br; - br = t; - } - // Intermediate hash value - t = (H[1] + cl + dr)|0; - H[1] = (H[2] + dl + er)|0; - H[2] = (H[3] + el + ar)|0; - H[3] = (H[4] + al + br)|0; - H[4] = (H[0] + bl + cr)|0; - H[0] = t; - }, - - _doFinalize: function () { - // Shortcuts - var data = this._data; - var dataWords = data.words; - - var nBitsTotal = this._nDataBytes * 8; - var nBitsLeft = data.sigBytes * 8; - - // Add padding - dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); - dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( - (((nBitsTotal << 8) | (nBitsTotal >>> 24)) & 0x00ff00ff) | - (((nBitsTotal << 24) | (nBitsTotal >>> 8)) & 0xff00ff00) - ); - data.sigBytes = (dataWords.length + 1) * 4; - - // Hash final blocks - this._process(); - - // Shortcuts - var hash = this._hash; - var H = hash.words; - - // Swap endian - for (var i = 0; i < 5; i++) { - // Shortcut - var H_i = H[i]; - - // Swap - H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | - (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); - } - - // Return final computed hash - return hash; - }, - - clone: function () { - var clone = Hasher.clone.call(this); - clone._hash = this._hash.clone(); - - return clone; - } - }); - - - function f1(x, y, z) { - return ((x) ^ (y) ^ (z)); - - } - - function f2(x, y, z) { - return (((x)&(y)) | ((~x)&(z))); - } - - function f3(x, y, z) { - return (((x) | (~(y))) ^ (z)); - } - - function f4(x, y, z) { - return (((x) & (z)) | ((y)&(~(z)))); - } - - function f5(x, y, z) { - return ((x) ^ ((y) |(~(z)))); - - } - - function rotl(x,n) { - return (x<>>(32-n)); - } - - - /** - * Shortcut function to the hasher's object interface. - * - * @param {WordArray|string} message The message to hash. - * - * @return {WordArray} The hash. - * - * @static - * - * @example - * - * var hash = CryptoJS.RIPEMD160('message'); - * var hash = CryptoJS.RIPEMD160(wordArray); - */ - C.RIPEMD160 = Hasher._createHelper(RIPEMD160); - - /** - * Shortcut function to the HMAC's object interface. - * - * @param {WordArray|string} message The message to hash. - * @param {WordArray|string} key The secret key. - * - * @return {WordArray} The HMAC. - * - * @static - * - * @example - * - * var hmac = CryptoJS.HmacRIPEMD160(message, key); - */ - C.HmacRIPEMD160 = Hasher._createHmacHelper(RIPEMD160); - }(Math)); - - - return CryptoJS.RIPEMD160; - -})); -},{"./core":52}],76:[function(require,module,exports){ -;(function (root, factory) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var WordArray = C_lib.WordArray; - var Hasher = C_lib.Hasher; - var C_algo = C.algo; - - // Reusable object - var W = []; - - /** - * SHA-1 hash algorithm. - */ - var SHA1 = C_algo.SHA1 = Hasher.extend({ - _doReset: function () { - this._hash = new WordArray.init([ - 0x67452301, 0xefcdab89, - 0x98badcfe, 0x10325476, - 0xc3d2e1f0 - ]); - }, - - _doProcessBlock: function (M, offset) { - // Shortcut - var H = this._hash.words; - - // Working variables - var a = H[0]; - var b = H[1]; - var c = H[2]; - var d = H[3]; - var e = H[4]; - - // Computation - for (var i = 0; i < 80; i++) { - if (i < 16) { - W[i] = M[offset + i] | 0; - } else { - var n = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]; - W[i] = (n << 1) | (n >>> 31); - } - - var t = ((a << 5) | (a >>> 27)) + e + W[i]; - if (i < 20) { - t += ((b & c) | (~b & d)) + 0x5a827999; - } else if (i < 40) { - t += (b ^ c ^ d) + 0x6ed9eba1; - } else if (i < 60) { - t += ((b & c) | (b & d) | (c & d)) - 0x70e44324; - } else /* if (i < 80) */ { - t += (b ^ c ^ d) - 0x359d3e2a; - } - - e = d; - d = c; - c = (b << 30) | (b >>> 2); - b = a; - a = t; - } - - // Intermediate hash value - H[0] = (H[0] + a) | 0; - H[1] = (H[1] + b) | 0; - H[2] = (H[2] + c) | 0; - H[3] = (H[3] + d) | 0; - H[4] = (H[4] + e) | 0; - }, - - _doFinalize: function () { - // Shortcuts - var data = this._data; - var dataWords = data.words; - - var nBitsTotal = this._nDataBytes * 8; - var nBitsLeft = data.sigBytes * 8; - - // Add padding - dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); - dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); - dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; - data.sigBytes = dataWords.length * 4; - - // Hash final blocks - this._process(); - - // Return final computed hash - return this._hash; - }, - - clone: function () { - var clone = Hasher.clone.call(this); - clone._hash = this._hash.clone(); - - return clone; - } - }); - - /** - * Shortcut function to the hasher's object interface. - * - * @param {WordArray|string} message The message to hash. - * - * @return {WordArray} The hash. - * - * @static - * - * @example - * - * var hash = CryptoJS.SHA1('message'); - * var hash = CryptoJS.SHA1(wordArray); - */ - C.SHA1 = Hasher._createHelper(SHA1); - - /** - * Shortcut function to the HMAC's object interface. - * - * @param {WordArray|string} message The message to hash. - * @param {WordArray|string} key The secret key. - * - * @return {WordArray} The HMAC. - * - * @static - * - * @example - * - * var hmac = CryptoJS.HmacSHA1(message, key); - */ - C.HmacSHA1 = Hasher._createHmacHelper(SHA1); - }()); - - - return CryptoJS.SHA1; - -})); -},{"./core":52}],77:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./sha256")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./sha256"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var WordArray = C_lib.WordArray; - var C_algo = C.algo; - var SHA256 = C_algo.SHA256; - - /** - * SHA-224 hash algorithm. - */ - var SHA224 = C_algo.SHA224 = SHA256.extend({ - _doReset: function () { - this._hash = new WordArray.init([ - 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, - 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 - ]); - }, - - _doFinalize: function () { - var hash = SHA256._doFinalize.call(this); - - hash.sigBytes -= 4; - - return hash; - } - }); - - /** - * Shortcut function to the hasher's object interface. - * - * @param {WordArray|string} message The message to hash. - * - * @return {WordArray} The hash. - * - * @static - * - * @example - * - * var hash = CryptoJS.SHA224('message'); - * var hash = CryptoJS.SHA224(wordArray); - */ - C.SHA224 = SHA256._createHelper(SHA224); - - /** - * Shortcut function to the HMAC's object interface. - * - * @param {WordArray|string} message The message to hash. - * @param {WordArray|string} key The secret key. - * - * @return {WordArray} The HMAC. - * - * @static - * - * @example - * - * var hmac = CryptoJS.HmacSHA224(message, key); - */ - C.HmacSHA224 = SHA256._createHmacHelper(SHA224); - }()); - - - return CryptoJS.SHA224; - -})); -},{"./core":52,"./sha256":78}],78:[function(require,module,exports){ -;(function (root, factory) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function (Math) { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var WordArray = C_lib.WordArray; - var Hasher = C_lib.Hasher; - var C_algo = C.algo; - - // Initialization and round constants tables - var H = []; - var K = []; - - // Compute constants - (function () { - function isPrime(n) { - var sqrtN = Math.sqrt(n); - for (var factor = 2; factor <= sqrtN; factor++) { - if (!(n % factor)) { - return false; - } - } - - return true; - } - - function getFractionalBits(n) { - return ((n - (n | 0)) * 0x100000000) | 0; - } - - var n = 2; - var nPrime = 0; - while (nPrime < 64) { - if (isPrime(n)) { - if (nPrime < 8) { - H[nPrime] = getFractionalBits(Math.pow(n, 1 / 2)); - } - K[nPrime] = getFractionalBits(Math.pow(n, 1 / 3)); - - nPrime++; - } - - n++; - } - }()); - - // Reusable object - var W = []; - - /** - * SHA-256 hash algorithm. - */ - var SHA256 = C_algo.SHA256 = Hasher.extend({ - _doReset: function () { - this._hash = new WordArray.init(H.slice(0)); - }, - - _doProcessBlock: function (M, offset) { - // Shortcut - var H = this._hash.words; - - // Working variables - var a = H[0]; - var b = H[1]; - var c = H[2]; - var d = H[3]; - var e = H[4]; - var f = H[5]; - var g = H[6]; - var h = H[7]; - - // Computation - for (var i = 0; i < 64; i++) { - if (i < 16) { - W[i] = M[offset + i] | 0; - } else { - var gamma0x = W[i - 15]; - var gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^ - ((gamma0x << 14) | (gamma0x >>> 18)) ^ - (gamma0x >>> 3); - - var gamma1x = W[i - 2]; - var gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^ - ((gamma1x << 13) | (gamma1x >>> 19)) ^ - (gamma1x >>> 10); - - W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16]; - } - - var ch = (e & f) ^ (~e & g); - var maj = (a & b) ^ (a & c) ^ (b & c); - - var sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22)); - var sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7) | (e >>> 25)); - - var t1 = h + sigma1 + ch + K[i] + W[i]; - var t2 = sigma0 + maj; - - h = g; - g = f; - f = e; - e = (d + t1) | 0; - d = c; - c = b; - b = a; - a = (t1 + t2) | 0; - } - - // Intermediate hash value - H[0] = (H[0] + a) | 0; - H[1] = (H[1] + b) | 0; - H[2] = (H[2] + c) | 0; - H[3] = (H[3] + d) | 0; - H[4] = (H[4] + e) | 0; - H[5] = (H[5] + f) | 0; - H[6] = (H[6] + g) | 0; - H[7] = (H[7] + h) | 0; - }, - - _doFinalize: function () { - // Shortcuts - var data = this._data; - var dataWords = data.words; - - var nBitsTotal = this._nDataBytes * 8; - var nBitsLeft = data.sigBytes * 8; - - // Add padding - dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); - dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); - dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; - data.sigBytes = dataWords.length * 4; - - // Hash final blocks - this._process(); - - // Return final computed hash - return this._hash; - }, - - clone: function () { - var clone = Hasher.clone.call(this); - clone._hash = this._hash.clone(); - - return clone; - } - }); - - /** - * Shortcut function to the hasher's object interface. - * - * @param {WordArray|string} message The message to hash. - * - * @return {WordArray} The hash. - * - * @static - * - * @example - * - * var hash = CryptoJS.SHA256('message'); - * var hash = CryptoJS.SHA256(wordArray); - */ - C.SHA256 = Hasher._createHelper(SHA256); - - /** - * Shortcut function to the HMAC's object interface. - * - * @param {WordArray|string} message The message to hash. - * @param {WordArray|string} key The secret key. - * - * @return {WordArray} The HMAC. - * - * @static - * - * @example - * - * var hmac = CryptoJS.HmacSHA256(message, key); - */ - C.HmacSHA256 = Hasher._createHmacHelper(SHA256); - }(Math)); - - - return CryptoJS.SHA256; - -})); -},{"./core":52}],79:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./x64-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./x64-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function (Math) { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var WordArray = C_lib.WordArray; - var Hasher = C_lib.Hasher; - var C_x64 = C.x64; - var X64Word = C_x64.Word; - var C_algo = C.algo; - - // Constants tables - var RHO_OFFSETS = []; - var PI_INDEXES = []; - var ROUND_CONSTANTS = []; - - // Compute Constants - (function () { - // Compute rho offset constants - var x = 1, y = 0; - for (var t = 0; t < 24; t++) { - RHO_OFFSETS[x + 5 * y] = ((t + 1) * (t + 2) / 2) % 64; - - var newX = y % 5; - var newY = (2 * x + 3 * y) % 5; - x = newX; - y = newY; - } - - // Compute pi index constants - for (var x = 0; x < 5; x++) { - for (var y = 0; y < 5; y++) { - PI_INDEXES[x + 5 * y] = y + ((2 * x + 3 * y) % 5) * 5; - } - } - - // Compute round constants - var LFSR = 0x01; - for (var i = 0; i < 24; i++) { - var roundConstantMsw = 0; - var roundConstantLsw = 0; - - for (var j = 0; j < 7; j++) { - if (LFSR & 0x01) { - var bitPosition = (1 << j) - 1; - if (bitPosition < 32) { - roundConstantLsw ^= 1 << bitPosition; - } else /* if (bitPosition >= 32) */ { - roundConstantMsw ^= 1 << (bitPosition - 32); - } - } - - // Compute next LFSR - if (LFSR & 0x80) { - // Primitive polynomial over GF(2): x^8 + x^6 + x^5 + x^4 + 1 - LFSR = (LFSR << 1) ^ 0x71; - } else { - LFSR <<= 1; - } - } - - ROUND_CONSTANTS[i] = X64Word.create(roundConstantMsw, roundConstantLsw); - } - }()); - - // Reusable objects for temporary values - var T = []; - (function () { - for (var i = 0; i < 25; i++) { - T[i] = X64Word.create(); - } - }()); - - /** - * SHA-3 hash algorithm. - */ - var SHA3 = C_algo.SHA3 = Hasher.extend({ - /** - * Configuration options. - * - * @property {number} outputLength - * The desired number of bits in the output hash. - * Only values permitted are: 224, 256, 384, 512. - * Default: 512 - */ - cfg: Hasher.cfg.extend({ - outputLength: 512 - }), - - _doReset: function () { - var state = this._state = [] - for (var i = 0; i < 25; i++) { - state[i] = new X64Word.init(); - } - - this.blockSize = (1600 - 2 * this.cfg.outputLength) / 32; - }, - - _doProcessBlock: function (M, offset) { - // Shortcuts - var state = this._state; - var nBlockSizeLanes = this.blockSize / 2; - - // Absorb - for (var i = 0; i < nBlockSizeLanes; i++) { - // Shortcuts - var M2i = M[offset + 2 * i]; - var M2i1 = M[offset + 2 * i + 1]; - - // Swap endian - M2i = ( - (((M2i << 8) | (M2i >>> 24)) & 0x00ff00ff) | - (((M2i << 24) | (M2i >>> 8)) & 0xff00ff00) - ); - M2i1 = ( - (((M2i1 << 8) | (M2i1 >>> 24)) & 0x00ff00ff) | - (((M2i1 << 24) | (M2i1 >>> 8)) & 0xff00ff00) - ); - - // Absorb message into state - var lane = state[i]; - lane.high ^= M2i1; - lane.low ^= M2i; - } - - // Rounds - for (var round = 0; round < 24; round++) { - // Theta - for (var x = 0; x < 5; x++) { - // Mix column lanes - var tMsw = 0, tLsw = 0; - for (var y = 0; y < 5; y++) { - var lane = state[x + 5 * y]; - tMsw ^= lane.high; - tLsw ^= lane.low; - } - - // Temporary values - var Tx = T[x]; - Tx.high = tMsw; - Tx.low = tLsw; - } - for (var x = 0; x < 5; x++) { - // Shortcuts - var Tx4 = T[(x + 4) % 5]; - var Tx1 = T[(x + 1) % 5]; - var Tx1Msw = Tx1.high; - var Tx1Lsw = Tx1.low; - - // Mix surrounding columns - var tMsw = Tx4.high ^ ((Tx1Msw << 1) | (Tx1Lsw >>> 31)); - var tLsw = Tx4.low ^ ((Tx1Lsw << 1) | (Tx1Msw >>> 31)); - for (var y = 0; y < 5; y++) { - var lane = state[x + 5 * y]; - lane.high ^= tMsw; - lane.low ^= tLsw; - } - } - - // Rho Pi - for (var laneIndex = 1; laneIndex < 25; laneIndex++) { - // Shortcuts - var lane = state[laneIndex]; - var laneMsw = lane.high; - var laneLsw = lane.low; - var rhoOffset = RHO_OFFSETS[laneIndex]; - - // Rotate lanes - if (rhoOffset < 32) { - var tMsw = (laneMsw << rhoOffset) | (laneLsw >>> (32 - rhoOffset)); - var tLsw = (laneLsw << rhoOffset) | (laneMsw >>> (32 - rhoOffset)); - } else /* if (rhoOffset >= 32) */ { - var tMsw = (laneLsw << (rhoOffset - 32)) | (laneMsw >>> (64 - rhoOffset)); - var tLsw = (laneMsw << (rhoOffset - 32)) | (laneLsw >>> (64 - rhoOffset)); - } - - // Transpose lanes - var TPiLane = T[PI_INDEXES[laneIndex]]; - TPiLane.high = tMsw; - TPiLane.low = tLsw; - } - - // Rho pi at x = y = 0 - var T0 = T[0]; - var state0 = state[0]; - T0.high = state0.high; - T0.low = state0.low; - - // Chi - for (var x = 0; x < 5; x++) { - for (var y = 0; y < 5; y++) { - // Shortcuts - var laneIndex = x + 5 * y; - var lane = state[laneIndex]; - var TLane = T[laneIndex]; - var Tx1Lane = T[((x + 1) % 5) + 5 * y]; - var Tx2Lane = T[((x + 2) % 5) + 5 * y]; - - // Mix rows - lane.high = TLane.high ^ (~Tx1Lane.high & Tx2Lane.high); - lane.low = TLane.low ^ (~Tx1Lane.low & Tx2Lane.low); - } - } - - // Iota - var lane = state[0]; - var roundConstant = ROUND_CONSTANTS[round]; - lane.high ^= roundConstant.high; - lane.low ^= roundConstant.low;; - } - }, - - _doFinalize: function () { - // Shortcuts - var data = this._data; - var dataWords = data.words; - var nBitsTotal = this._nDataBytes * 8; - var nBitsLeft = data.sigBytes * 8; - var blockSizeBits = this.blockSize * 32; - - // Add padding - dataWords[nBitsLeft >>> 5] |= 0x1 << (24 - nBitsLeft % 32); - dataWords[((Math.ceil((nBitsLeft + 1) / blockSizeBits) * blockSizeBits) >>> 5) - 1] |= 0x80; - data.sigBytes = dataWords.length * 4; - - // Hash final blocks - this._process(); - - // Shortcuts - var state = this._state; - var outputLengthBytes = this.cfg.outputLength / 8; - var outputLengthLanes = outputLengthBytes / 8; - - // Squeeze - var hashWords = []; - for (var i = 0; i < outputLengthLanes; i++) { - // Shortcuts - var lane = state[i]; - var laneMsw = lane.high; - var laneLsw = lane.low; - - // Swap endian - laneMsw = ( - (((laneMsw << 8) | (laneMsw >>> 24)) & 0x00ff00ff) | - (((laneMsw << 24) | (laneMsw >>> 8)) & 0xff00ff00) - ); - laneLsw = ( - (((laneLsw << 8) | (laneLsw >>> 24)) & 0x00ff00ff) | - (((laneLsw << 24) | (laneLsw >>> 8)) & 0xff00ff00) - ); - - // Squeeze state to retrieve hash - hashWords.push(laneLsw); - hashWords.push(laneMsw); - } - - // Return final computed hash - return new WordArray.init(hashWords, outputLengthBytes); - }, - - clone: function () { - var clone = Hasher.clone.call(this); - - var state = clone._state = this._state.slice(0); - for (var i = 0; i < 25; i++) { - state[i] = state[i].clone(); - } - - return clone; - } - }); - - /** - * Shortcut function to the hasher's object interface. - * - * @param {WordArray|string} message The message to hash. - * - * @return {WordArray} The hash. - * - * @static - * - * @example - * - * var hash = CryptoJS.SHA3('message'); - * var hash = CryptoJS.SHA3(wordArray); - */ - C.SHA3 = Hasher._createHelper(SHA3); - - /** - * Shortcut function to the HMAC's object interface. - * - * @param {WordArray|string} message The message to hash. - * @param {WordArray|string} key The secret key. - * - * @return {WordArray} The HMAC. - * - * @static - * - * @example - * - * var hmac = CryptoJS.HmacSHA3(message, key); - */ - C.HmacSHA3 = Hasher._createHmacHelper(SHA3); - }(Math)); - - - return CryptoJS.SHA3; - -})); -},{"./core":52,"./x64-core":83}],80:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./x64-core"), require("./sha512")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./x64-core", "./sha512"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function () { - // Shortcuts - var C = CryptoJS; - var C_x64 = C.x64; - var X64Word = C_x64.Word; - var X64WordArray = C_x64.WordArray; - var C_algo = C.algo; - var SHA512 = C_algo.SHA512; - - /** - * SHA-384 hash algorithm. - */ - var SHA384 = C_algo.SHA384 = SHA512.extend({ - _doReset: function () { - this._hash = new X64WordArray.init([ - new X64Word.init(0xcbbb9d5d, 0xc1059ed8), new X64Word.init(0x629a292a, 0x367cd507), - new X64Word.init(0x9159015a, 0x3070dd17), new X64Word.init(0x152fecd8, 0xf70e5939), - new X64Word.init(0x67332667, 0xffc00b31), new X64Word.init(0x8eb44a87, 0x68581511), - new X64Word.init(0xdb0c2e0d, 0x64f98fa7), new X64Word.init(0x47b5481d, 0xbefa4fa4) - ]); - }, - - _doFinalize: function () { - var hash = SHA512._doFinalize.call(this); - - hash.sigBytes -= 16; - - return hash; - } - }); - - /** - * Shortcut function to the hasher's object interface. - * - * @param {WordArray|string} message The message to hash. - * - * @return {WordArray} The hash. - * - * @static - * - * @example - * - * var hash = CryptoJS.SHA384('message'); - * var hash = CryptoJS.SHA384(wordArray); - */ - C.SHA384 = SHA512._createHelper(SHA384); - - /** - * Shortcut function to the HMAC's object interface. - * - * @param {WordArray|string} message The message to hash. - * @param {WordArray|string} key The secret key. - * - * @return {WordArray} The HMAC. - * - * @static - * - * @example - * - * var hmac = CryptoJS.HmacSHA384(message, key); - */ - C.HmacSHA384 = SHA512._createHmacHelper(SHA384); - }()); - - - return CryptoJS.SHA384; - -})); -},{"./core":52,"./sha512":81,"./x64-core":83}],81:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./x64-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./x64-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var Hasher = C_lib.Hasher; - var C_x64 = C.x64; - var X64Word = C_x64.Word; - var X64WordArray = C_x64.WordArray; - var C_algo = C.algo; - - function X64Word_create() { - return X64Word.create.apply(X64Word, arguments); - } - - // Constants - var K = [ - X64Word_create(0x428a2f98, 0xd728ae22), X64Word_create(0x71374491, 0x23ef65cd), - X64Word_create(0xb5c0fbcf, 0xec4d3b2f), X64Word_create(0xe9b5dba5, 0x8189dbbc), - X64Word_create(0x3956c25b, 0xf348b538), X64Word_create(0x59f111f1, 0xb605d019), - X64Word_create(0x923f82a4, 0xaf194f9b), X64Word_create(0xab1c5ed5, 0xda6d8118), - X64Word_create(0xd807aa98, 0xa3030242), X64Word_create(0x12835b01, 0x45706fbe), - X64Word_create(0x243185be, 0x4ee4b28c), X64Word_create(0x550c7dc3, 0xd5ffb4e2), - X64Word_create(0x72be5d74, 0xf27b896f), X64Word_create(0x80deb1fe, 0x3b1696b1), - X64Word_create(0x9bdc06a7, 0x25c71235), X64Word_create(0xc19bf174, 0xcf692694), - X64Word_create(0xe49b69c1, 0x9ef14ad2), X64Word_create(0xefbe4786, 0x384f25e3), - X64Word_create(0x0fc19dc6, 0x8b8cd5b5), X64Word_create(0x240ca1cc, 0x77ac9c65), - X64Word_create(0x2de92c6f, 0x592b0275), X64Word_create(0x4a7484aa, 0x6ea6e483), - X64Word_create(0x5cb0a9dc, 0xbd41fbd4), X64Word_create(0x76f988da, 0x831153b5), - X64Word_create(0x983e5152, 0xee66dfab), X64Word_create(0xa831c66d, 0x2db43210), - X64Word_create(0xb00327c8, 0x98fb213f), X64Word_create(0xbf597fc7, 0xbeef0ee4), - X64Word_create(0xc6e00bf3, 0x3da88fc2), X64Word_create(0xd5a79147, 0x930aa725), - X64Word_create(0x06ca6351, 0xe003826f), X64Word_create(0x14292967, 0x0a0e6e70), - X64Word_create(0x27b70a85, 0x46d22ffc), X64Word_create(0x2e1b2138, 0x5c26c926), - X64Word_create(0x4d2c6dfc, 0x5ac42aed), X64Word_create(0x53380d13, 0x9d95b3df), - X64Word_create(0x650a7354, 0x8baf63de), X64Word_create(0x766a0abb, 0x3c77b2a8), - X64Word_create(0x81c2c92e, 0x47edaee6), X64Word_create(0x92722c85, 0x1482353b), - X64Word_create(0xa2bfe8a1, 0x4cf10364), X64Word_create(0xa81a664b, 0xbc423001), - X64Word_create(0xc24b8b70, 0xd0f89791), X64Word_create(0xc76c51a3, 0x0654be30), - X64Word_create(0xd192e819, 0xd6ef5218), X64Word_create(0xd6990624, 0x5565a910), - X64Word_create(0xf40e3585, 0x5771202a), X64Word_create(0x106aa070, 0x32bbd1b8), - X64Word_create(0x19a4c116, 0xb8d2d0c8), X64Word_create(0x1e376c08, 0x5141ab53), - X64Word_create(0x2748774c, 0xdf8eeb99), X64Word_create(0x34b0bcb5, 0xe19b48a8), - X64Word_create(0x391c0cb3, 0xc5c95a63), X64Word_create(0x4ed8aa4a, 0xe3418acb), - X64Word_create(0x5b9cca4f, 0x7763e373), X64Word_create(0x682e6ff3, 0xd6b2b8a3), - X64Word_create(0x748f82ee, 0x5defb2fc), X64Word_create(0x78a5636f, 0x43172f60), - X64Word_create(0x84c87814, 0xa1f0ab72), X64Word_create(0x8cc70208, 0x1a6439ec), - X64Word_create(0x90befffa, 0x23631e28), X64Word_create(0xa4506ceb, 0xde82bde9), - X64Word_create(0xbef9a3f7, 0xb2c67915), X64Word_create(0xc67178f2, 0xe372532b), - X64Word_create(0xca273ece, 0xea26619c), X64Word_create(0xd186b8c7, 0x21c0c207), - X64Word_create(0xeada7dd6, 0xcde0eb1e), X64Word_create(0xf57d4f7f, 0xee6ed178), - X64Word_create(0x06f067aa, 0x72176fba), X64Word_create(0x0a637dc5, 0xa2c898a6), - X64Word_create(0x113f9804, 0xbef90dae), X64Word_create(0x1b710b35, 0x131c471b), - X64Word_create(0x28db77f5, 0x23047d84), X64Word_create(0x32caab7b, 0x40c72493), - X64Word_create(0x3c9ebe0a, 0x15c9bebc), X64Word_create(0x431d67c4, 0x9c100d4c), - X64Word_create(0x4cc5d4be, 0xcb3e42b6), X64Word_create(0x597f299c, 0xfc657e2a), - X64Word_create(0x5fcb6fab, 0x3ad6faec), X64Word_create(0x6c44198c, 0x4a475817) - ]; - - // Reusable objects - var W = []; - (function () { - for (var i = 0; i < 80; i++) { - W[i] = X64Word_create(); - } - }()); - - /** - * SHA-512 hash algorithm. - */ - var SHA512 = C_algo.SHA512 = Hasher.extend({ - _doReset: function () { - this._hash = new X64WordArray.init([ - new X64Word.init(0x6a09e667, 0xf3bcc908), new X64Word.init(0xbb67ae85, 0x84caa73b), - new X64Word.init(0x3c6ef372, 0xfe94f82b), new X64Word.init(0xa54ff53a, 0x5f1d36f1), - new X64Word.init(0x510e527f, 0xade682d1), new X64Word.init(0x9b05688c, 0x2b3e6c1f), - new X64Word.init(0x1f83d9ab, 0xfb41bd6b), new X64Word.init(0x5be0cd19, 0x137e2179) - ]); - }, - - _doProcessBlock: function (M, offset) { - // Shortcuts - var H = this._hash.words; - - var H0 = H[0]; - var H1 = H[1]; - var H2 = H[2]; - var H3 = H[3]; - var H4 = H[4]; - var H5 = H[5]; - var H6 = H[6]; - var H7 = H[7]; - - var H0h = H0.high; - var H0l = H0.low; - var H1h = H1.high; - var H1l = H1.low; - var H2h = H2.high; - var H2l = H2.low; - var H3h = H3.high; - var H3l = H3.low; - var H4h = H4.high; - var H4l = H4.low; - var H5h = H5.high; - var H5l = H5.low; - var H6h = H6.high; - var H6l = H6.low; - var H7h = H7.high; - var H7l = H7.low; - - // Working variables - var ah = H0h; - var al = H0l; - var bh = H1h; - var bl = H1l; - var ch = H2h; - var cl = H2l; - var dh = H3h; - var dl = H3l; - var eh = H4h; - var el = H4l; - var fh = H5h; - var fl = H5l; - var gh = H6h; - var gl = H6l; - var hh = H7h; - var hl = H7l; - - // Rounds - for (var i = 0; i < 80; i++) { - // Shortcut - var Wi = W[i]; - - // Extend message - if (i < 16) { - var Wih = Wi.high = M[offset + i * 2] | 0; - var Wil = Wi.low = M[offset + i * 2 + 1] | 0; - } else { - // Gamma0 - var gamma0x = W[i - 15]; - var gamma0xh = gamma0x.high; - var gamma0xl = gamma0x.low; - var gamma0h = ((gamma0xh >>> 1) | (gamma0xl << 31)) ^ ((gamma0xh >>> 8) | (gamma0xl << 24)) ^ (gamma0xh >>> 7); - var gamma0l = ((gamma0xl >>> 1) | (gamma0xh << 31)) ^ ((gamma0xl >>> 8) | (gamma0xh << 24)) ^ ((gamma0xl >>> 7) | (gamma0xh << 25)); - - // Gamma1 - var gamma1x = W[i - 2]; - var gamma1xh = gamma1x.high; - var gamma1xl = gamma1x.low; - var gamma1h = ((gamma1xh >>> 19) | (gamma1xl << 13)) ^ ((gamma1xh << 3) | (gamma1xl >>> 29)) ^ (gamma1xh >>> 6); - var gamma1l = ((gamma1xl >>> 19) | (gamma1xh << 13)) ^ ((gamma1xl << 3) | (gamma1xh >>> 29)) ^ ((gamma1xl >>> 6) | (gamma1xh << 26)); - - // W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16] - var Wi7 = W[i - 7]; - var Wi7h = Wi7.high; - var Wi7l = Wi7.low; - - var Wi16 = W[i - 16]; - var Wi16h = Wi16.high; - var Wi16l = Wi16.low; - - var Wil = gamma0l + Wi7l; - var Wih = gamma0h + Wi7h + ((Wil >>> 0) < (gamma0l >>> 0) ? 1 : 0); - var Wil = Wil + gamma1l; - var Wih = Wih + gamma1h + ((Wil >>> 0) < (gamma1l >>> 0) ? 1 : 0); - var Wil = Wil + Wi16l; - var Wih = Wih + Wi16h + ((Wil >>> 0) < (Wi16l >>> 0) ? 1 : 0); - - Wi.high = Wih; - Wi.low = Wil; - } - - var chh = (eh & fh) ^ (~eh & gh); - var chl = (el & fl) ^ (~el & gl); - var majh = (ah & bh) ^ (ah & ch) ^ (bh & ch); - var majl = (al & bl) ^ (al & cl) ^ (bl & cl); - - var sigma0h = ((ah >>> 28) | (al << 4)) ^ ((ah << 30) | (al >>> 2)) ^ ((ah << 25) | (al >>> 7)); - var sigma0l = ((al >>> 28) | (ah << 4)) ^ ((al << 30) | (ah >>> 2)) ^ ((al << 25) | (ah >>> 7)); - var sigma1h = ((eh >>> 14) | (el << 18)) ^ ((eh >>> 18) | (el << 14)) ^ ((eh << 23) | (el >>> 9)); - var sigma1l = ((el >>> 14) | (eh << 18)) ^ ((el >>> 18) | (eh << 14)) ^ ((el << 23) | (eh >>> 9)); - - // t1 = h + sigma1 + ch + K[i] + W[i] - var Ki = K[i]; - var Kih = Ki.high; - var Kil = Ki.low; - - var t1l = hl + sigma1l; - var t1h = hh + sigma1h + ((t1l >>> 0) < (hl >>> 0) ? 1 : 0); - var t1l = t1l + chl; - var t1h = t1h + chh + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0); - var t1l = t1l + Kil; - var t1h = t1h + Kih + ((t1l >>> 0) < (Kil >>> 0) ? 1 : 0); - var t1l = t1l + Wil; - var t1h = t1h + Wih + ((t1l >>> 0) < (Wil >>> 0) ? 1 : 0); - - // t2 = sigma0 + maj - var t2l = sigma0l + majl; - var t2h = sigma0h + majh + ((t2l >>> 0) < (sigma0l >>> 0) ? 1 : 0); - - // Update working variables - hh = gh; - hl = gl; - gh = fh; - gl = fl; - fh = eh; - fl = el; - el = (dl + t1l) | 0; - eh = (dh + t1h + ((el >>> 0) < (dl >>> 0) ? 1 : 0)) | 0; - dh = ch; - dl = cl; - ch = bh; - cl = bl; - bh = ah; - bl = al; - al = (t1l + t2l) | 0; - ah = (t1h + t2h + ((al >>> 0) < (t1l >>> 0) ? 1 : 0)) | 0; - } - - // Intermediate hash value - H0l = H0.low = (H0l + al); - H0.high = (H0h + ah + ((H0l >>> 0) < (al >>> 0) ? 1 : 0)); - H1l = H1.low = (H1l + bl); - H1.high = (H1h + bh + ((H1l >>> 0) < (bl >>> 0) ? 1 : 0)); - H2l = H2.low = (H2l + cl); - H2.high = (H2h + ch + ((H2l >>> 0) < (cl >>> 0) ? 1 : 0)); - H3l = H3.low = (H3l + dl); - H3.high = (H3h + dh + ((H3l >>> 0) < (dl >>> 0) ? 1 : 0)); - H4l = H4.low = (H4l + el); - H4.high = (H4h + eh + ((H4l >>> 0) < (el >>> 0) ? 1 : 0)); - H5l = H5.low = (H5l + fl); - H5.high = (H5h + fh + ((H5l >>> 0) < (fl >>> 0) ? 1 : 0)); - H6l = H6.low = (H6l + gl); - H6.high = (H6h + gh + ((H6l >>> 0) < (gl >>> 0) ? 1 : 0)); - H7l = H7.low = (H7l + hl); - H7.high = (H7h + hh + ((H7l >>> 0) < (hl >>> 0) ? 1 : 0)); - }, - - _doFinalize: function () { - // Shortcuts - var data = this._data; - var dataWords = data.words; - - var nBitsTotal = this._nDataBytes * 8; - var nBitsLeft = data.sigBytes * 8; - - // Add padding - dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); - dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 30] = Math.floor(nBitsTotal / 0x100000000); - dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 31] = nBitsTotal; - data.sigBytes = dataWords.length * 4; - - // Hash final blocks - this._process(); - - // Convert hash to 32-bit word array before returning - var hash = this._hash.toX32(); - - // Return final computed hash - return hash; - }, - - clone: function () { - var clone = Hasher.clone.call(this); - clone._hash = this._hash.clone(); - - return clone; - }, - - blockSize: 1024/32 - }); - - /** - * Shortcut function to the hasher's object interface. - * - * @param {WordArray|string} message The message to hash. - * - * @return {WordArray} The hash. - * - * @static - * - * @example - * - * var hash = CryptoJS.SHA512('message'); - * var hash = CryptoJS.SHA512(wordArray); - */ - C.SHA512 = Hasher._createHelper(SHA512); - - /** - * Shortcut function to the HMAC's object interface. - * - * @param {WordArray|string} message The message to hash. - * @param {WordArray|string} key The secret key. - * - * @return {WordArray} The HMAC. - * - * @static - * - * @example - * - * var hmac = CryptoJS.HmacSHA512(message, key); - */ - C.HmacSHA512 = Hasher._createHmacHelper(SHA512); - }()); - - - return CryptoJS.SHA512; - -})); -},{"./core":52,"./x64-core":83}],82:[function(require,module,exports){ -;(function (root, factory, undef) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var WordArray = C_lib.WordArray; - var BlockCipher = C_lib.BlockCipher; - var C_algo = C.algo; - - // Permuted Choice 1 constants - var PC1 = [ - 57, 49, 41, 33, 25, 17, 9, 1, - 58, 50, 42, 34, 26, 18, 10, 2, - 59, 51, 43, 35, 27, 19, 11, 3, - 60, 52, 44, 36, 63, 55, 47, 39, - 31, 23, 15, 7, 62, 54, 46, 38, - 30, 22, 14, 6, 61, 53, 45, 37, - 29, 21, 13, 5, 28, 20, 12, 4 - ]; - - // Permuted Choice 2 constants - var PC2 = [ - 14, 17, 11, 24, 1, 5, - 3, 28, 15, 6, 21, 10, - 23, 19, 12, 4, 26, 8, - 16, 7, 27, 20, 13, 2, - 41, 52, 31, 37, 47, 55, - 30, 40, 51, 45, 33, 48, - 44, 49, 39, 56, 34, 53, - 46, 42, 50, 36, 29, 32 - ]; - - // Cumulative bit shift constants - var BIT_SHIFTS = [1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28]; - - // SBOXes and round permutation constants - var SBOX_P = [ - { - 0x0: 0x808200, - 0x10000000: 0x8000, - 0x20000000: 0x808002, - 0x30000000: 0x2, - 0x40000000: 0x200, - 0x50000000: 0x808202, - 0x60000000: 0x800202, - 0x70000000: 0x800000, - 0x80000000: 0x202, - 0x90000000: 0x800200, - 0xa0000000: 0x8200, - 0xb0000000: 0x808000, - 0xc0000000: 0x8002, - 0xd0000000: 0x800002, - 0xe0000000: 0x0, - 0xf0000000: 0x8202, - 0x8000000: 0x0, - 0x18000000: 0x808202, - 0x28000000: 0x8202, - 0x38000000: 0x8000, - 0x48000000: 0x808200, - 0x58000000: 0x200, - 0x68000000: 0x808002, - 0x78000000: 0x2, - 0x88000000: 0x800200, - 0x98000000: 0x8200, - 0xa8000000: 0x808000, - 0xb8000000: 0x800202, - 0xc8000000: 0x800002, - 0xd8000000: 0x8002, - 0xe8000000: 0x202, - 0xf8000000: 0x800000, - 0x1: 0x8000, - 0x10000001: 0x2, - 0x20000001: 0x808200, - 0x30000001: 0x800000, - 0x40000001: 0x808002, - 0x50000001: 0x8200, - 0x60000001: 0x200, - 0x70000001: 0x800202, - 0x80000001: 0x808202, - 0x90000001: 0x808000, - 0xa0000001: 0x800002, - 0xb0000001: 0x8202, - 0xc0000001: 0x202, - 0xd0000001: 0x800200, - 0xe0000001: 0x8002, - 0xf0000001: 0x0, - 0x8000001: 0x808202, - 0x18000001: 0x808000, - 0x28000001: 0x800000, - 0x38000001: 0x200, - 0x48000001: 0x8000, - 0x58000001: 0x800002, - 0x68000001: 0x2, - 0x78000001: 0x8202, - 0x88000001: 0x8002, - 0x98000001: 0x800202, - 0xa8000001: 0x202, - 0xb8000001: 0x808200, - 0xc8000001: 0x800200, - 0xd8000001: 0x0, - 0xe8000001: 0x8200, - 0xf8000001: 0x808002 - }, - { - 0x0: 0x40084010, - 0x1000000: 0x4000, - 0x2000000: 0x80000, - 0x3000000: 0x40080010, - 0x4000000: 0x40000010, - 0x5000000: 0x40084000, - 0x6000000: 0x40004000, - 0x7000000: 0x10, - 0x8000000: 0x84000, - 0x9000000: 0x40004010, - 0xa000000: 0x40000000, - 0xb000000: 0x84010, - 0xc000000: 0x80010, - 0xd000000: 0x0, - 0xe000000: 0x4010, - 0xf000000: 0x40080000, - 0x800000: 0x40004000, - 0x1800000: 0x84010, - 0x2800000: 0x10, - 0x3800000: 0x40004010, - 0x4800000: 0x40084010, - 0x5800000: 0x40000000, - 0x6800000: 0x80000, - 0x7800000: 0x40080010, - 0x8800000: 0x80010, - 0x9800000: 0x0, - 0xa800000: 0x4000, - 0xb800000: 0x40080000, - 0xc800000: 0x40000010, - 0xd800000: 0x84000, - 0xe800000: 0x40084000, - 0xf800000: 0x4010, - 0x10000000: 0x0, - 0x11000000: 0x40080010, - 0x12000000: 0x40004010, - 0x13000000: 0x40084000, - 0x14000000: 0x40080000, - 0x15000000: 0x10, - 0x16000000: 0x84010, - 0x17000000: 0x4000, - 0x18000000: 0x4010, - 0x19000000: 0x80000, - 0x1a000000: 0x80010, - 0x1b000000: 0x40000010, - 0x1c000000: 0x84000, - 0x1d000000: 0x40004000, - 0x1e000000: 0x40000000, - 0x1f000000: 0x40084010, - 0x10800000: 0x84010, - 0x11800000: 0x80000, - 0x12800000: 0x40080000, - 0x13800000: 0x4000, - 0x14800000: 0x40004000, - 0x15800000: 0x40084010, - 0x16800000: 0x10, - 0x17800000: 0x40000000, - 0x18800000: 0x40084000, - 0x19800000: 0x40000010, - 0x1a800000: 0x40004010, - 0x1b800000: 0x80010, - 0x1c800000: 0x0, - 0x1d800000: 0x4010, - 0x1e800000: 0x40080010, - 0x1f800000: 0x84000 - }, - { - 0x0: 0x104, - 0x100000: 0x0, - 0x200000: 0x4000100, - 0x300000: 0x10104, - 0x400000: 0x10004, - 0x500000: 0x4000004, - 0x600000: 0x4010104, - 0x700000: 0x4010000, - 0x800000: 0x4000000, - 0x900000: 0x4010100, - 0xa00000: 0x10100, - 0xb00000: 0x4010004, - 0xc00000: 0x4000104, - 0xd00000: 0x10000, - 0xe00000: 0x4, - 0xf00000: 0x100, - 0x80000: 0x4010100, - 0x180000: 0x4010004, - 0x280000: 0x0, - 0x380000: 0x4000100, - 0x480000: 0x4000004, - 0x580000: 0x10000, - 0x680000: 0x10004, - 0x780000: 0x104, - 0x880000: 0x4, - 0x980000: 0x100, - 0xa80000: 0x4010000, - 0xb80000: 0x10104, - 0xc80000: 0x10100, - 0xd80000: 0x4000104, - 0xe80000: 0x4010104, - 0xf80000: 0x4000000, - 0x1000000: 0x4010100, - 0x1100000: 0x10004, - 0x1200000: 0x10000, - 0x1300000: 0x4000100, - 0x1400000: 0x100, - 0x1500000: 0x4010104, - 0x1600000: 0x4000004, - 0x1700000: 0x0, - 0x1800000: 0x4000104, - 0x1900000: 0x4000000, - 0x1a00000: 0x4, - 0x1b00000: 0x10100, - 0x1c00000: 0x4010000, - 0x1d00000: 0x104, - 0x1e00000: 0x10104, - 0x1f00000: 0x4010004, - 0x1080000: 0x4000000, - 0x1180000: 0x104, - 0x1280000: 0x4010100, - 0x1380000: 0x0, - 0x1480000: 0x10004, - 0x1580000: 0x4000100, - 0x1680000: 0x100, - 0x1780000: 0x4010004, - 0x1880000: 0x10000, - 0x1980000: 0x4010104, - 0x1a80000: 0x10104, - 0x1b80000: 0x4000004, - 0x1c80000: 0x4000104, - 0x1d80000: 0x4010000, - 0x1e80000: 0x4, - 0x1f80000: 0x10100 - }, - { - 0x0: 0x80401000, - 0x10000: 0x80001040, - 0x20000: 0x401040, - 0x30000: 0x80400000, - 0x40000: 0x0, - 0x50000: 0x401000, - 0x60000: 0x80000040, - 0x70000: 0x400040, - 0x80000: 0x80000000, - 0x90000: 0x400000, - 0xa0000: 0x40, - 0xb0000: 0x80001000, - 0xc0000: 0x80400040, - 0xd0000: 0x1040, - 0xe0000: 0x1000, - 0xf0000: 0x80401040, - 0x8000: 0x80001040, - 0x18000: 0x40, - 0x28000: 0x80400040, - 0x38000: 0x80001000, - 0x48000: 0x401000, - 0x58000: 0x80401040, - 0x68000: 0x0, - 0x78000: 0x80400000, - 0x88000: 0x1000, - 0x98000: 0x80401000, - 0xa8000: 0x400000, - 0xb8000: 0x1040, - 0xc8000: 0x80000000, - 0xd8000: 0x400040, - 0xe8000: 0x401040, - 0xf8000: 0x80000040, - 0x100000: 0x400040, - 0x110000: 0x401000, - 0x120000: 0x80000040, - 0x130000: 0x0, - 0x140000: 0x1040, - 0x150000: 0x80400040, - 0x160000: 0x80401000, - 0x170000: 0x80001040, - 0x180000: 0x80401040, - 0x190000: 0x80000000, - 0x1a0000: 0x80400000, - 0x1b0000: 0x401040, - 0x1c0000: 0x80001000, - 0x1d0000: 0x400000, - 0x1e0000: 0x40, - 0x1f0000: 0x1000, - 0x108000: 0x80400000, - 0x118000: 0x80401040, - 0x128000: 0x0, - 0x138000: 0x401000, - 0x148000: 0x400040, - 0x158000: 0x80000000, - 0x168000: 0x80001040, - 0x178000: 0x40, - 0x188000: 0x80000040, - 0x198000: 0x1000, - 0x1a8000: 0x80001000, - 0x1b8000: 0x80400040, - 0x1c8000: 0x1040, - 0x1d8000: 0x80401000, - 0x1e8000: 0x400000, - 0x1f8000: 0x401040 - }, - { - 0x0: 0x80, - 0x1000: 0x1040000, - 0x2000: 0x40000, - 0x3000: 0x20000000, - 0x4000: 0x20040080, - 0x5000: 0x1000080, - 0x6000: 0x21000080, - 0x7000: 0x40080, - 0x8000: 0x1000000, - 0x9000: 0x20040000, - 0xa000: 0x20000080, - 0xb000: 0x21040080, - 0xc000: 0x21040000, - 0xd000: 0x0, - 0xe000: 0x1040080, - 0xf000: 0x21000000, - 0x800: 0x1040080, - 0x1800: 0x21000080, - 0x2800: 0x80, - 0x3800: 0x1040000, - 0x4800: 0x40000, - 0x5800: 0x20040080, - 0x6800: 0x21040000, - 0x7800: 0x20000000, - 0x8800: 0x20040000, - 0x9800: 0x0, - 0xa800: 0x21040080, - 0xb800: 0x1000080, - 0xc800: 0x20000080, - 0xd800: 0x21000000, - 0xe800: 0x1000000, - 0xf800: 0x40080, - 0x10000: 0x40000, - 0x11000: 0x80, - 0x12000: 0x20000000, - 0x13000: 0x21000080, - 0x14000: 0x1000080, - 0x15000: 0x21040000, - 0x16000: 0x20040080, - 0x17000: 0x1000000, - 0x18000: 0x21040080, - 0x19000: 0x21000000, - 0x1a000: 0x1040000, - 0x1b000: 0x20040000, - 0x1c000: 0x40080, - 0x1d000: 0x20000080, - 0x1e000: 0x0, - 0x1f000: 0x1040080, - 0x10800: 0x21000080, - 0x11800: 0x1000000, - 0x12800: 0x1040000, - 0x13800: 0x20040080, - 0x14800: 0x20000000, - 0x15800: 0x1040080, - 0x16800: 0x80, - 0x17800: 0x21040000, - 0x18800: 0x40080, - 0x19800: 0x21040080, - 0x1a800: 0x0, - 0x1b800: 0x21000000, - 0x1c800: 0x1000080, - 0x1d800: 0x40000, - 0x1e800: 0x20040000, - 0x1f800: 0x20000080 - }, - { - 0x0: 0x10000008, - 0x100: 0x2000, - 0x200: 0x10200000, - 0x300: 0x10202008, - 0x400: 0x10002000, - 0x500: 0x200000, - 0x600: 0x200008, - 0x700: 0x10000000, - 0x800: 0x0, - 0x900: 0x10002008, - 0xa00: 0x202000, - 0xb00: 0x8, - 0xc00: 0x10200008, - 0xd00: 0x202008, - 0xe00: 0x2008, - 0xf00: 0x10202000, - 0x80: 0x10200000, - 0x180: 0x10202008, - 0x280: 0x8, - 0x380: 0x200000, - 0x480: 0x202008, - 0x580: 0x10000008, - 0x680: 0x10002000, - 0x780: 0x2008, - 0x880: 0x200008, - 0x980: 0x2000, - 0xa80: 0x10002008, - 0xb80: 0x10200008, - 0xc80: 0x0, - 0xd80: 0x10202000, - 0xe80: 0x202000, - 0xf80: 0x10000000, - 0x1000: 0x10002000, - 0x1100: 0x10200008, - 0x1200: 0x10202008, - 0x1300: 0x2008, - 0x1400: 0x200000, - 0x1500: 0x10000000, - 0x1600: 0x10000008, - 0x1700: 0x202000, - 0x1800: 0x202008, - 0x1900: 0x0, - 0x1a00: 0x8, - 0x1b00: 0x10200000, - 0x1c00: 0x2000, - 0x1d00: 0x10002008, - 0x1e00: 0x10202000, - 0x1f00: 0x200008, - 0x1080: 0x8, - 0x1180: 0x202000, - 0x1280: 0x200000, - 0x1380: 0x10000008, - 0x1480: 0x10002000, - 0x1580: 0x2008, - 0x1680: 0x10202008, - 0x1780: 0x10200000, - 0x1880: 0x10202000, - 0x1980: 0x10200008, - 0x1a80: 0x2000, - 0x1b80: 0x202008, - 0x1c80: 0x200008, - 0x1d80: 0x0, - 0x1e80: 0x10000000, - 0x1f80: 0x10002008 - }, - { - 0x0: 0x100000, - 0x10: 0x2000401, - 0x20: 0x400, - 0x30: 0x100401, - 0x40: 0x2100401, - 0x50: 0x0, - 0x60: 0x1, - 0x70: 0x2100001, - 0x80: 0x2000400, - 0x90: 0x100001, - 0xa0: 0x2000001, - 0xb0: 0x2100400, - 0xc0: 0x2100000, - 0xd0: 0x401, - 0xe0: 0x100400, - 0xf0: 0x2000000, - 0x8: 0x2100001, - 0x18: 0x0, - 0x28: 0x2000401, - 0x38: 0x2100400, - 0x48: 0x100000, - 0x58: 0x2000001, - 0x68: 0x2000000, - 0x78: 0x401, - 0x88: 0x100401, - 0x98: 0x2000400, - 0xa8: 0x2100000, - 0xb8: 0x100001, - 0xc8: 0x400, - 0xd8: 0x2100401, - 0xe8: 0x1, - 0xf8: 0x100400, - 0x100: 0x2000000, - 0x110: 0x100000, - 0x120: 0x2000401, - 0x130: 0x2100001, - 0x140: 0x100001, - 0x150: 0x2000400, - 0x160: 0x2100400, - 0x170: 0x100401, - 0x180: 0x401, - 0x190: 0x2100401, - 0x1a0: 0x100400, - 0x1b0: 0x1, - 0x1c0: 0x0, - 0x1d0: 0x2100000, - 0x1e0: 0x2000001, - 0x1f0: 0x400, - 0x108: 0x100400, - 0x118: 0x2000401, - 0x128: 0x2100001, - 0x138: 0x1, - 0x148: 0x2000000, - 0x158: 0x100000, - 0x168: 0x401, - 0x178: 0x2100400, - 0x188: 0x2000001, - 0x198: 0x2100000, - 0x1a8: 0x0, - 0x1b8: 0x2100401, - 0x1c8: 0x100401, - 0x1d8: 0x400, - 0x1e8: 0x2000400, - 0x1f8: 0x100001 - }, - { - 0x0: 0x8000820, - 0x1: 0x20000, - 0x2: 0x8000000, - 0x3: 0x20, - 0x4: 0x20020, - 0x5: 0x8020820, - 0x6: 0x8020800, - 0x7: 0x800, - 0x8: 0x8020000, - 0x9: 0x8000800, - 0xa: 0x20800, - 0xb: 0x8020020, - 0xc: 0x820, - 0xd: 0x0, - 0xe: 0x8000020, - 0xf: 0x20820, - 0x80000000: 0x800, - 0x80000001: 0x8020820, - 0x80000002: 0x8000820, - 0x80000003: 0x8000000, - 0x80000004: 0x8020000, - 0x80000005: 0x20800, - 0x80000006: 0x20820, - 0x80000007: 0x20, - 0x80000008: 0x8000020, - 0x80000009: 0x820, - 0x8000000a: 0x20020, - 0x8000000b: 0x8020800, - 0x8000000c: 0x0, - 0x8000000d: 0x8020020, - 0x8000000e: 0x8000800, - 0x8000000f: 0x20000, - 0x10: 0x20820, - 0x11: 0x8020800, - 0x12: 0x20, - 0x13: 0x800, - 0x14: 0x8000800, - 0x15: 0x8000020, - 0x16: 0x8020020, - 0x17: 0x20000, - 0x18: 0x0, - 0x19: 0x20020, - 0x1a: 0x8020000, - 0x1b: 0x8000820, - 0x1c: 0x8020820, - 0x1d: 0x20800, - 0x1e: 0x820, - 0x1f: 0x8000000, - 0x80000010: 0x20000, - 0x80000011: 0x800, - 0x80000012: 0x8020020, - 0x80000013: 0x20820, - 0x80000014: 0x20, - 0x80000015: 0x8020000, - 0x80000016: 0x8000000, - 0x80000017: 0x8000820, - 0x80000018: 0x8020820, - 0x80000019: 0x8000020, - 0x8000001a: 0x8000800, - 0x8000001b: 0x0, - 0x8000001c: 0x20800, - 0x8000001d: 0x820, - 0x8000001e: 0x20020, - 0x8000001f: 0x8020800 - } - ]; - - // Masks that select the SBOX input - var SBOX_MASK = [ - 0xf8000001, 0x1f800000, 0x01f80000, 0x001f8000, - 0x0001f800, 0x00001f80, 0x000001f8, 0x8000001f - ]; - - /** - * DES block cipher algorithm. - */ - var DES = C_algo.DES = BlockCipher.extend({ - _doReset: function () { - // Shortcuts - var key = this._key; - var keyWords = key.words; - - // Select 56 bits according to PC1 - var keyBits = []; - for (var i = 0; i < 56; i++) { - var keyBitPos = PC1[i] - 1; - keyBits[i] = (keyWords[keyBitPos >>> 5] >>> (31 - keyBitPos % 32)) & 1; - } - - // Assemble 16 subkeys - var subKeys = this._subKeys = []; - for (var nSubKey = 0; nSubKey < 16; nSubKey++) { - // Create subkey - var subKey = subKeys[nSubKey] = []; - - // Shortcut - var bitShift = BIT_SHIFTS[nSubKey]; - - // Select 48 bits according to PC2 - for (var i = 0; i < 24; i++) { - // Select from the left 28 key bits - subKey[(i / 6) | 0] |= keyBits[((PC2[i] - 1) + bitShift) % 28] << (31 - i % 6); - - // Select from the right 28 key bits - subKey[4 + ((i / 6) | 0)] |= keyBits[28 + (((PC2[i + 24] - 1) + bitShift) % 28)] << (31 - i % 6); - } - - // Since each subkey is applied to an expanded 32-bit input, - // the subkey can be broken into 8 values scaled to 32-bits, - // which allows the key to be used without expansion - subKey[0] = (subKey[0] << 1) | (subKey[0] >>> 31); - for (var i = 1; i < 7; i++) { - subKey[i] = subKey[i] >>> ((i - 1) * 4 + 3); - } - subKey[7] = (subKey[7] << 5) | (subKey[7] >>> 27); - } - - // Compute inverse subkeys - var invSubKeys = this._invSubKeys = []; - for (var i = 0; i < 16; i++) { - invSubKeys[i] = subKeys[15 - i]; - } - }, - - encryptBlock: function (M, offset) { - this._doCryptBlock(M, offset, this._subKeys); - }, - - decryptBlock: function (M, offset) { - this._doCryptBlock(M, offset, this._invSubKeys); - }, - - _doCryptBlock: function (M, offset, subKeys) { - // Get input - this._lBlock = M[offset]; - this._rBlock = M[offset + 1]; - - // Initial permutation - exchangeLR.call(this, 4, 0x0f0f0f0f); - exchangeLR.call(this, 16, 0x0000ffff); - exchangeRL.call(this, 2, 0x33333333); - exchangeRL.call(this, 8, 0x00ff00ff); - exchangeLR.call(this, 1, 0x55555555); - - // Rounds - for (var round = 0; round < 16; round++) { - // Shortcuts - var subKey = subKeys[round]; - var lBlock = this._lBlock; - var rBlock = this._rBlock; - - // Feistel function - var f = 0; - for (var i = 0; i < 8; i++) { - f |= SBOX_P[i][((rBlock ^ subKey[i]) & SBOX_MASK[i]) >>> 0]; - } - this._lBlock = rBlock; - this._rBlock = lBlock ^ f; - } - - // Undo swap from last round - var t = this._lBlock; - this._lBlock = this._rBlock; - this._rBlock = t; - - // Final permutation - exchangeLR.call(this, 1, 0x55555555); - exchangeRL.call(this, 8, 0x00ff00ff); - exchangeRL.call(this, 2, 0x33333333); - exchangeLR.call(this, 16, 0x0000ffff); - exchangeLR.call(this, 4, 0x0f0f0f0f); - - // Set output - M[offset] = this._lBlock; - M[offset + 1] = this._rBlock; - }, - - keySize: 64/32, - - ivSize: 64/32, - - blockSize: 64/32 - }); - - // Swap bits across the left and right words - function exchangeLR(offset, mask) { - var t = ((this._lBlock >>> offset) ^ this._rBlock) & mask; - this._rBlock ^= t; - this._lBlock ^= t << offset; - } - - function exchangeRL(offset, mask) { - var t = ((this._rBlock >>> offset) ^ this._lBlock) & mask; - this._lBlock ^= t; - this._rBlock ^= t << offset; - } - - /** - * Shortcut functions to the cipher's object interface. - * - * @example - * - * var ciphertext = CryptoJS.DES.encrypt(message, key, cfg); - * var plaintext = CryptoJS.DES.decrypt(ciphertext, key, cfg); - */ - C.DES = BlockCipher._createHelper(DES); + },{}],"bignumber.js":[function(require,module,exports){ + 'use strict'; - /** - * Triple-DES block cipher algorithm. - */ - var TripleDES = C_algo.TripleDES = BlockCipher.extend({ - _doReset: function () { - // Shortcuts - var key = this._key; - var keyWords = key.words; - - // Create DES instances - this._des1 = DES.createEncryptor(WordArray.create(keyWords.slice(0, 2))); - this._des2 = DES.createEncryptor(WordArray.create(keyWords.slice(2, 4))); - this._des3 = DES.createEncryptor(WordArray.create(keyWords.slice(4, 6))); - }, - - encryptBlock: function (M, offset) { - this._des1.encryptBlock(M, offset); - this._des2.decryptBlock(M, offset); - this._des3.encryptBlock(M, offset); - }, - - decryptBlock: function (M, offset) { - this._des3.decryptBlock(M, offset); - this._des2.encryptBlock(M, offset); - this._des1.decryptBlock(M, offset); - }, - - keySize: 192/32, - - ivSize: 64/32, - - blockSize: 64/32 - }); - - /** - * Shortcut functions to the cipher's object interface. - * - * @example - * - * var ciphertext = CryptoJS.TripleDES.encrypt(message, key, cfg); - * var plaintext = CryptoJS.TripleDES.decrypt(ciphertext, key, cfg); - */ - C.TripleDES = BlockCipher._createHelper(TripleDES); - }()); - - - return CryptoJS.TripleDES; - -})); -},{"./cipher-core":51,"./core":52,"./enc-base64":53,"./evpkdf":55,"./md5":60}],83:[function(require,module,exports){ -;(function (root, factory) { - if (typeof exports === "object") { - // CommonJS - module.exports = exports = factory(require("./core")); - } - else if (typeof define === "function" && define.amd) { - // AMD - define(["./core"], factory); - } - else { - // Global (browser) - factory(root.CryptoJS); - } -}(this, function (CryptoJS) { - - (function (undefined) { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var Base = C_lib.Base; - var X32WordArray = C_lib.WordArray; - - /** - * x64 namespace. - */ - var C_x64 = C.x64 = {}; + module.exports = BigNumber; // jshint ignore:line - /** - * A 64-bit word. - */ - var X64Word = C_x64.Word = Base.extend({ - /** - * Initializes a newly created 64-bit word. - * - * @param {number} high The high 32 bits. - * @param {number} low The low 32 bits. - * - * @example - * - * var x64Word = CryptoJS.x64.Word.create(0x00010203, 0x04050607); - */ - init: function (high, low) { - this.high = high; - this.low = low; - } - - /** - * Bitwise NOTs this word. - * - * @return {X64Word} A new x64-Word object after negating. - * - * @example - * - * var negated = x64Word.not(); - */ - // not: function () { - // var high = ~this.high; - // var low = ~this.low; - - // return X64Word.create(high, low); - // }, - - /** - * Bitwise ANDs this word with the passed word. - * - * @param {X64Word} word The x64-Word to AND with this word. - * - * @return {X64Word} A new x64-Word object after ANDing. - * - * @example - * - * var anded = x64Word.and(anotherX64Word); - */ - // and: function (word) { - // var high = this.high & word.high; - // var low = this.low & word.low; - - // return X64Word.create(high, low); - // }, - - /** - * Bitwise ORs this word with the passed word. - * - * @param {X64Word} word The x64-Word to OR with this word. - * - * @return {X64Word} A new x64-Word object after ORing. - * - * @example - * - * var ored = x64Word.or(anotherX64Word); - */ - // or: function (word) { - // var high = this.high | word.high; - // var low = this.low | word.low; - - // return X64Word.create(high, low); - // }, - - /** - * Bitwise XORs this word with the passed word. - * - * @param {X64Word} word The x64-Word to XOR with this word. - * - * @return {X64Word} A new x64-Word object after XORing. - * - * @example - * - * var xored = x64Word.xor(anotherX64Word); - */ - // xor: function (word) { - // var high = this.high ^ word.high; - // var low = this.low ^ word.low; - - // return X64Word.create(high, low); - // }, - - /** - * Shifts this word n bits to the left. - * - * @param {number} n The number of bits to shift. - * - * @return {X64Word} A new x64-Word object after shifting. - * - * @example - * - * var shifted = x64Word.shiftL(25); - */ - // shiftL: function (n) { - // if (n < 32) { - // var high = (this.high << n) | (this.low >>> (32 - n)); - // var low = this.low << n; - // } else { - // var high = this.low << (n - 32); - // var low = 0; - // } - - // return X64Word.create(high, low); - // }, - - /** - * Shifts this word n bits to the right. - * - * @param {number} n The number of bits to shift. - * - * @return {X64Word} A new x64-Word object after shifting. - * - * @example - * - * var shifted = x64Word.shiftR(7); - */ - // shiftR: function (n) { - // if (n < 32) { - // var low = (this.low >>> n) | (this.high << (32 - n)); - // var high = this.high >>> n; - // } else { - // var low = this.high >>> (n - 32); - // var high = 0; - // } - - // return X64Word.create(high, low); - // }, - - /** - * Rotates this word n bits to the left. - * - * @param {number} n The number of bits to rotate. - * - * @return {X64Word} A new x64-Word object after rotating. - * - * @example - * - * var rotated = x64Word.rotL(25); - */ - // rotL: function (n) { - // return this.shiftL(n).or(this.shiftR(64 - n)); - // }, - - /** - * Rotates this word n bits to the right. - * - * @param {number} n The number of bits to rotate. - * - * @return {X64Word} A new x64-Word object after rotating. - * - * @example - * - * var rotated = x64Word.rotR(7); - */ - // rotR: function (n) { - // return this.shiftR(n).or(this.shiftL(64 - n)); - // }, - - /** - * Adds this word with the passed word. - * - * @param {X64Word} word The x64-Word to add with this word. - * - * @return {X64Word} A new x64-Word object after adding. - * - * @example - * - * var added = x64Word.add(anotherX64Word); - */ - // add: function (word) { - // var low = (this.low + word.low) | 0; - // var carry = (low >>> 0) < (this.low >>> 0) ? 1 : 0; - // var high = (this.high + word.high + carry) | 0; - - // return X64Word.create(high, low); - // } - }); - - /** - * An array of 64-bit words. - * - * @property {Array} words The array of CryptoJS.x64.Word objects. - * @property {number} sigBytes The number of significant bytes in this word array. - */ - var X64WordArray = C_x64.WordArray = Base.extend({ - /** - * Initializes a newly created word array. - * - * @param {Array} words (Optional) An array of CryptoJS.x64.Word objects. - * @param {number} sigBytes (Optional) The number of significant bytes in the words. - * - * @example - * - * var wordArray = CryptoJS.x64.WordArray.create(); - * - * var wordArray = CryptoJS.x64.WordArray.create([ - * CryptoJS.x64.Word.create(0x00010203, 0x04050607), - * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) - * ]); - * - * var wordArray = CryptoJS.x64.WordArray.create([ - * CryptoJS.x64.Word.create(0x00010203, 0x04050607), - * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) - * ], 10); - */ - init: function (words, sigBytes) { - words = this.words = words || []; - - if (sigBytes != undefined) { - this.sigBytes = sigBytes; - } else { - this.sigBytes = words.length * 8; - } - }, - - /** - * Converts this 64-bit word array to a 32-bit word array. - * - * @return {CryptoJS.lib.WordArray} This word array's data as a 32-bit word array. - * - * @example - * - * var x32WordArray = x64WordArray.toX32(); - */ - toX32: function () { - // Shortcuts - var x64Words = this.words; - var x64WordsLength = x64Words.length; - - // Convert - var x32Words = []; - for (var i = 0; i < x64WordsLength; i++) { - var x64Word = x64Words[i]; - x32Words.push(x64Word.high); - x32Words.push(x64Word.low); - } - - return X32WordArray.create(x32Words, this.sigBytes); - }, - - /** - * Creates a copy of this word array. - * - * @return {X64WordArray} The clone. - * - * @example - * - * var clone = x64WordArray.clone(); - */ - clone: function () { - var clone = Base.clone.call(this); - - // Clone "words" array - var words = clone.words = this.words.slice(0); - - // Clone each X64Word object - var wordsLength = words.length; - for (var i = 0; i < wordsLength; i++) { - words[i] = words[i].clone(); - } - - return clone; - } - }); - }()); - - - return CryptoJS; - -})); -},{"./core":52}],84:[function(require,module,exports){ -/*! https://mths.be/utf8js v2.1.2 by @mathias */ -;(function(root) { - - // Detect free variables `exports` - var freeExports = typeof exports == 'object' && exports; - - // Detect free variable `module` - var freeModule = typeof module == 'object' && module && - module.exports == freeExports && module; - - // Detect free variable `global`, from Node.js or Browserified code, - // and use it as `root` - var freeGlobal = typeof global == 'object' && global; - if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { - root = freeGlobal; - } - - /*--------------------------------------------------------------------------*/ - - var stringFromCharCode = String.fromCharCode; - - // Taken from https://mths.be/punycode - function ucs2decode(string) { - var output = []; - var counter = 0; - var length = string.length; - var value; - var extra; - while (counter < length) { - value = string.charCodeAt(counter++); - if (value >= 0xD800 && value <= 0xDBFF && counter < length) { - // high surrogate, and there is a next character - extra = string.charCodeAt(counter++); - if ((extra & 0xFC00) == 0xDC00) { // low surrogate - output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); - } else { - // unmatched surrogate; only append this code unit, in case the next - // code unit is the high surrogate of a surrogate pair - output.push(value); - counter--; - } - } else { - output.push(value); - } - } - return output; - } - - // Taken from https://mths.be/punycode - function ucs2encode(array) { - var length = array.length; - var index = -1; - var value; - var output = ''; - while (++index < length) { - value = array[index]; - if (value > 0xFFFF) { - value -= 0x10000; - output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); - value = 0xDC00 | value & 0x3FF; - } - output += stringFromCharCode(value); - } - return output; - } - - function checkScalarValue(codePoint) { - if (codePoint >= 0xD800 && codePoint <= 0xDFFF) { - throw Error( - 'Lone surrogate U+' + codePoint.toString(16).toUpperCase() + - ' is not a scalar value' - ); - } - } - /*--------------------------------------------------------------------------*/ - - function createByte(codePoint, shift) { - return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80); - } - - function encodeCodePoint(codePoint) { - if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence - return stringFromCharCode(codePoint); - } - var symbol = ''; - if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence - symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0); - } - else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence - checkScalarValue(codePoint); - symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0); - symbol += createByte(codePoint, 6); - } - else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence - symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0); - symbol += createByte(codePoint, 12); - symbol += createByte(codePoint, 6); - } - symbol += stringFromCharCode((codePoint & 0x3F) | 0x80); - return symbol; - } - - function utf8encode(string) { - var codePoints = ucs2decode(string); - var length = codePoints.length; - var index = -1; - var codePoint; - var byteString = ''; - while (++index < length) { - codePoint = codePoints[index]; - byteString += encodeCodePoint(codePoint); - } - return byteString; - } - - /*--------------------------------------------------------------------------*/ - - function readContinuationByte() { - if (byteIndex >= byteCount) { - throw Error('Invalid byte index'); - } - - var continuationByte = byteArray[byteIndex] & 0xFF; - byteIndex++; - - if ((continuationByte & 0xC0) == 0x80) { - return continuationByte & 0x3F; - } - - // If we end up here, it’s not a continuation byte - throw Error('Invalid continuation byte'); - } - - function decodeSymbol() { - var byte1; - var byte2; - var byte3; - var byte4; - var codePoint; - - if (byteIndex > byteCount) { - throw Error('Invalid byte index'); - } - - if (byteIndex == byteCount) { - return false; - } - - // Read first byte - byte1 = byteArray[byteIndex] & 0xFF; - byteIndex++; - - // 1-byte sequence (no continuation bytes) - if ((byte1 & 0x80) == 0) { - return byte1; - } - - // 2-byte sequence - if ((byte1 & 0xE0) == 0xC0) { - byte2 = readContinuationByte(); - codePoint = ((byte1 & 0x1F) << 6) | byte2; - if (codePoint >= 0x80) { - return codePoint; - } else { - throw Error('Invalid continuation byte'); - } - } - - // 3-byte sequence (may include unpaired surrogates) - if ((byte1 & 0xF0) == 0xE0) { - byte2 = readContinuationByte(); - byte3 = readContinuationByte(); - codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3; - if (codePoint >= 0x0800) { - checkScalarValue(codePoint); - return codePoint; - } else { - throw Error('Invalid continuation byte'); - } - } - - // 4-byte sequence - if ((byte1 & 0xF8) == 0xF0) { - byte2 = readContinuationByte(); - byte3 = readContinuationByte(); - byte4 = readContinuationByte(); - codePoint = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0C) | - (byte3 << 0x06) | byte4; - if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) { - return codePoint; - } - } - - throw Error('Invalid UTF-8 detected'); - } - - var byteArray; - var byteCount; - var byteIndex; - function utf8decode(byteString) { - byteArray = ucs2decode(byteString); - byteCount = byteArray.length; - byteIndex = 0; - var codePoints = []; - var tmp; - while ((tmp = decodeSymbol()) !== false) { - codePoints.push(tmp); - } - return ucs2encode(codePoints); - } - - /*--------------------------------------------------------------------------*/ - - var utf8 = { - 'version': '2.1.2', - 'encode': utf8encode, - 'decode': utf8decode - }; - - // Some AMD build optimizers, like r.js, check for specific condition patterns - // like the following: - if ( - typeof define == 'function' && - typeof define.amd == 'object' && - define.amd - ) { - define(function() { - return utf8; - }); - } else if (freeExports && !freeExports.nodeType) { - if (freeModule) { // in Node.js or RingoJS v0.8.0+ - freeModule.exports = utf8; - } else { // in Narwhal or RingoJS v0.7.0- - var object = {}; - var hasOwnProperty = object.hasOwnProperty; - for (var key in utf8) { - hasOwnProperty.call(utf8, key) && (freeExports[key] = utf8[key]); - } - } - } else { // in Rhino or a web browser - root.utf8 = utf8; - } - -}(this)); - -},{}],85:[function(require,module,exports){ -module.exports = XMLHttpRequest; - -},{}],"bignumber.js":[function(require,module,exports){ -'use strict'; - -module.exports = BigNumber; // jshint ignore:line - - -},{}],"web3":[function(require,module,exports){ -var Web3 = require('./lib/web3'); + + },{}],"web3":[function(require,module,exports){ + var Web3 = require('./lib/web3'); // dont override global variable -if (typeof window !== 'undefined' && typeof window.Web3 === 'undefined') { - window.Web3 = Web3; -} + if (typeof window !== 'undefined' && typeof window.Web3 === 'undefined') { + window.Web3 = Web3; + } -module.exports = Web3; + module.exports = Web3; -},{"./lib/web3":24}]},{},["web3"]) + },{"./lib/web3":24}]},{},["web3"]) //# sourceMappingURL=web3-light.js.map diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index af1f6c0b28..d4649d2bd8 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -356,6 +356,11 @@ web3._extend({ name: 'disableDBGC', call: 'debug_disableDBGC', }), + new web3._extend.Method({ + name: 'getPrepareQC', + call: 'debug_getPrepareQC', + params: 1 + }), ], properties: [] }); @@ -413,11 +418,6 @@ web3._extend({ params: 2, inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, web3._extend.utils.toHex] }), - new web3._extend.Method({ - name: 'getPrepareQC', - call: 'platon_getPrepareQC', - params: 1 - }), ], properties: [ new web3._extend.Property({ From d2da10afde5283bd21fe4ae6fd24aad65aa9c93e Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 7 Dec 2021 14:46:15 +0800 Subject: [PATCH 094/299] add Adjust0170RoundValidators logic --- x/gov/gov_db.go | 18 ++++++++++++++++++ x/plugin/gov_plugin.go | 19 +++++++------------ x/plugin/staking_plugin.go | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/x/gov/gov_db.go b/x/gov/gov_db.go index 16910db9c2..4dc6f0e942 100644 --- a/x/gov/gov_db.go +++ b/x/gov/gov_db.go @@ -202,6 +202,24 @@ func GetVotedVerifierMap(proposalID common.Hash, blockHash common.Hash) (map[eno return votedMap, nil } +// update tally result status to Active +func UpdateTallyResult(proposalID common.Hash, state xcom.StateDB) error { + //log.Debug("it's time to active the pre-active version proposal") + tallyResult, err := GetTallyResult(proposalID, state) + if err != nil || tallyResult == nil { + log.Error("find pre-active version proposal tally result failed.", "proposalID", proposalID) + return err + } + //update tally status to "active" + tallyResult.Status = Active + + if err := SetTallyResult(*tallyResult, state); err != nil { + log.Error("update version proposal tally result failed.", "proposalID", proposalID) + return err + } + return nil +} + func SetTallyResult(tallyResult TallyResult, state xcom.StateDB) error { value, err := json.Marshal(tallyResult) if err != nil { diff --git a/x/plugin/gov_plugin.go b/x/plugin/gov_plugin.go index 206c0dadc8..929ed8ad46 100644 --- a/x/plugin/gov_plugin.go +++ b/x/plugin/gov_plugin.go @@ -100,20 +100,10 @@ func (govPlugin *GovPlugin) BeginBlock(blockHash common.Hash, header *types.Head if params.LtMinorVersion(versionProposal.NewVersion) { panic(fmt.Sprintf("Please upgrade to:%s", params.FormatVersion(versionProposal.NewVersion))) } - //log.Debug("it's time to active the pre-active version proposal") - tallyResult, err := gov.GetTallyResult(preActiveVersionProposalID, state) - if err != nil || tallyResult == nil { - log.Error("find pre-active version proposal tally result failed.", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveVersionProposalID", preActiveVersionProposalID) + if err = gov.UpdateTallyResult(preActiveVersionProposalID, state); err != nil { + log.Error("UpdateTallyResult failed.", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveVersionProposalID", preActiveVersionProposalID) return err } - //update tally status to "active" - tallyResult.Status = gov.Active - - if err := gov.SetTallyResult(*tallyResult, state); err != nil { - log.Error("update version proposal tally result failed.", "blockNumber", blockNumber, "preActiveVersionProposalID", preActiveVersionProposalID) - return err - } - if err = gov.MovePreActiveProposalIDToEnd(blockHash, preActiveVersionProposalID); err != nil { log.Error("move version proposal ID to EndProposalID list failed.", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveVersionProposalID", preActiveVersionProposalID) return err @@ -172,6 +162,11 @@ func (govPlugin *GovPlugin) BeginBlock(blockHash common.Hash, header *types.Head return err } log.Info("Successfully upgraded the new version 0.17.0", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveProposalID", preActiveVersionProposalID) + + if err := StakingInstance().Adjust0170RoundValidators(blockHash, blockNumber); err != nil { + log.Error("Adjust 0.17.0 validators failed!", "blockNumber", blockNumber, "blockHash", blockHash, "preActiveProposalID", preActiveVersionProposalID) + return err + } } header.SetActiveVersion(versionProposal.NewVersion) diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index 71a30c4f38..21af98172f 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -3733,6 +3733,42 @@ func (sk *StakingPlugin) HasStake(blockHash common.Hash, addr common.Address) (b return sk.db.HasAccountStakeRc(blockHash, addr) } +// 0170分组共识在Epoch第一个块生效,但新Epoch的轮值共识节点是旧版本选出的 +// Adjust0170Validators调整共识节点的index以便在新Epoch按新Range出块 +func (sk *StakingPlugin) Adjust0170RoundValidators(blockHash common.Hash, blockNumber uint64) error { + oldIndex := &staking.ValArrIndex{ + Start: blockNumber, + End: xcom.ConsensusSize(params.FORKVERSION_0_16_0), + } + //获取旧ValidatorQueue + queue, err := sk.db.GetRoundValListByIrr(oldIndex.Start, oldIndex.End) + if nil != err { + log.Error("Adjust0170RoundValidators: Query round validators failed", + "Start", blockNumber, "End", oldIndex.End, "err", err) + return err + } + + // 根据新ConsensusSize更新index和RoundValList + newQueue := &staking.ValidatorArray{ + Start: oldIndex.Start, + End: oldIndex.Start + xcom.ConsensusSize(params.FORKVERSION_0_17_0), + Arr: queue, + } + if err := sk.setRoundValListAndIndex(blockNumber, blockHash, newQueue); nil != err { + log.Error("Failed to SetNextValidatorList on Election", "blockNumber", blockNumber, + "blockHash", blockHash.Hex(), "err", err) + return err + } + // 删除旧ValidatorQueue + if err := sk.db.DelRoundValListByBlockHash(blockHash, oldIndex.Start, oldIndex.End); nil != err { + log.Error("Adjust0170RoundValidators: delete oldIndex validators failed", + "oldIndex start", oldIndex.Start, "oldIndex end", oldIndex.End, + "blockNumber", blockNumber, "blockHash", blockHash.Hex(), "err", err) + return err + } + return nil +} + func calcCandidateTotalAmount(can *staking.Candidate) *big.Int { release := new(big.Int).Add(can.Released, can.ReleasedHes) restrictingPlan := new(big.Int).Add(can.RestrictingPlan, can.RestrictingPlanHes) From 76fb3c82101297f6cff45aa6ba9a8947f9da9111 Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 7 Dec 2021 14:58:33 +0800 Subject: [PATCH 095/299] add Adjust0170RoundValidators logic --- consensus/cbft/cbft_test.go | 8 +++++--- consensus/cbft/validator/validator_test.go | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/consensus/cbft/cbft_test.go b/consensus/cbft/cbft_test.go index 234ed44d96..df0a41d622 100644 --- a/consensus/cbft/cbft_test.go +++ b/consensus/cbft/cbft_test.go @@ -24,6 +24,8 @@ import ( "testing" "time" + "github.com/AlayaNetwork/Alaya-Go/event" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/core/rawdb" @@ -80,7 +82,7 @@ func TestBls(t *testing.T) { agency := validator.NewStaticAgency(nodes) cbft := &Cbft{ - validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false), + validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, new(event.TypeMux)), config: ctypes.Config{ Option: &ctypes.OptionsConfig{ BlsPriKey: owner, @@ -105,7 +107,7 @@ func TestPrepareBlockBls(t *testing.T) { agency := validator.NewStaticAgency([]params.CbftNode{node}) cbft := &Cbft{ - validatorPool: validator.NewValidatorPool(agency, 0, 0, node.Node.ID(), false), + validatorPool: validator.NewValidatorPool(agency, 0, 0, node.Node.ID(), false, new(event.TypeMux)), config: ctypes.Config{ Option: &ctypes.OptionsConfig{ BlsPriKey: owner, @@ -174,7 +176,7 @@ func TestAgg(t *testing.T) { for i := 0; i < num; i++ { cnode[i] = &Cbft{ - validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false), + validatorPool: validator.NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, new(event.TypeMux)), config: ctypes.Config{ Option: &ctypes.OptionsConfig{ BlsPriKey: sk[i], diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index dffa6163f1..25140b7caf 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -621,7 +621,7 @@ func TestValidatorPoolReset(t *testing.T) { func TestValidatorGrouped(t *testing.T) { nodes := newTestNodeByNum(100) vs := newValidators(nodes, 0) - vs.Grouped(nil, 0) + vs.Grouped(new(event.TypeMux), 0) assert.Equal(t, 4, len(vs.GroupNodes)) assert.Equal(t, 25, len(vs.GroupNodes[3].Nodes)) assert.Equal(t, uint32(74), vs.GroupNodes[2].Nodes[24].Index) From 14431717ce7d45dcd080a1b99cecff8e491ba9b1 Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 7 Dec 2021 16:00:44 +0800 Subject: [PATCH 096/299] update Adjust0170RoundValidators oldIndex.End --- x/plugin/staking_plugin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index 21af98172f..1661a1f15e 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -3738,7 +3738,7 @@ func (sk *StakingPlugin) HasStake(blockHash common.Hash, addr common.Address) (b func (sk *StakingPlugin) Adjust0170RoundValidators(blockHash common.Hash, blockNumber uint64) error { oldIndex := &staking.ValArrIndex{ Start: blockNumber, - End: xcom.ConsensusSize(params.FORKVERSION_0_16_0), + End: blockNumber + xcom.ConsensusSize(params.FORKVERSION_0_16_0), } //获取旧ValidatorQueue queue, err := sk.db.GetRoundValListByIrr(oldIndex.Start, oldIndex.End) From e53cad79844d8b77ce443e9a3db3a63be5f8fa4e Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Tue, 7 Dec 2021 17:45:44 +0800 Subject: [PATCH 097/299] Perfect unit test --- consensus/cbft/network/pubsub.go | 3 +- p2p/host.go | 43 +- p2p/network.go | 24 + p2p/pubsub/blacklist_test.go | 6 +- p2p/pubsub/discovery.go | 14 +- p2p/pubsub/discovery_test.go | 374 ++++---- p2p/pubsub/gossip_tracer_test.go | 28 +- p2p/pubsub/{gossippub.go => gossipsub.go} | 8 +- p2p/pubsub/gossipsub_feat_test.go | 175 ++-- p2p/pubsub/gossipsub_test.go | 1006 +++++++++------------ p2p/pubsub/interfaces.go | 13 +- p2p/pubsub/mcache_test.go | 15 +- p2p/pubsub/message/mesage.go | 33 +- p2p/pubsub/peer_gater_test.go | 18 +- p2p/pubsub/pubsub.go | 29 +- p2p/pubsub/pubsub_common.go | 7 +- p2p/pubsub/pubsub_test.go | 212 ++++- p2p/pubsub/score_params_test.go | 5 +- p2p/pubsub/score_test.go | 497 +++++----- p2p/pubsub/sign_test.go | 82 +- p2p/pubsub/subscription_filter_test.go | 422 ++++----- p2p/pubsub/tag_tracer_test.go | 5 - p2p/pubsub/topic_test.go | 246 ++--- p2p/pubsub/trace_test.go | 318 +++---- p2p/pubsub/validation_test.go | 31 +- p2p/pubsub_server.go | 2 +- p2p/stream.go | 2 +- 27 files changed, 1869 insertions(+), 1749 deletions(-) rename p2p/pubsub/{gossippub.go => gossipsub.go} (99%) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index a364cc4e62..4ce5e2dc43 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -70,11 +70,12 @@ type PubSub struct { // Protocol.Run() func (ps *PubSub) handler(peer *p2p.Peer, rw p2p.MsgReadWriter) error { - errCh := ps.pss.NewConn(peer, rw) + defer ps.pss.Host().DisConn(peer.ID()) handlerErr := <-errCh log.Info("pubsub's handler ends", "err", handlerErr) + return handlerErr } diff --git a/p2p/host.go b/p2p/host.go index 054f4ab1e8..ba9dd259c5 100644 --- a/p2p/host.go +++ b/p2p/host.go @@ -18,6 +18,7 @@ package p2p import ( "context" + "errors" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "github.com/libp2p/go-libp2p-core/connmgr" @@ -25,17 +26,19 @@ import ( ) type Host struct { - node *enode.Node - network *Network - streams map[enode.ID]pubsub.Stream sync.Mutex + node *enode.Node + network *Network + streams map[enode.ID]pubsub.Stream + handlers map[pubsub.ProtocolID]pubsub.StreamHandler } func NewHost(localNode *enode.Node, network *Network) *Host { host := &Host{ - node: localNode, - streams: make(map[enode.ID]pubsub.Stream), - network: network, + node: localNode, + streams: make(map[enode.ID]pubsub.Stream), + network: network, + handlers: map[pubsub.ProtocolID]pubsub.StreamHandler{}, } return host } @@ -53,21 +56,34 @@ func (h *Host) Connect(ctx context.Context, pi enode.ID) error { } func (h *Host) SetStreamHandler(pid pubsub.ProtocolID, handler pubsub.StreamHandler) { - + h.Lock() + defer h.Unlock() + h.handlers[pid] = handler } func (h *Host) SetStreamHandlerMatch(pubsub.ProtocolID, func(string) bool, pubsub.StreamHandler) { } -func (h *Host) RemoveStreamHandler(pid pubsub.ProtocolID) { +func (h *Host) StreamHandler(pid pubsub.ProtocolID) pubsub.StreamHandler { + h.Lock() + defer h.Unlock() + return h.handlers[pid] +} +func (h *Host) RemoveStreamHandler(pid pubsub.ProtocolID) { + h.Lock() + defer h.Unlock() + delete(h.handlers, pid) } func (h *Host) NewStream(ctx context.Context, nodeId enode.ID, pids ...pubsub.ProtocolID) (pubsub.Stream, error) { h.Lock() defer h.Unlock() - return h.streams[nodeId], nil + if s, ok := h.streams[nodeId]; ok { + return s, nil + } + return nil, errors.New("no stream exists for this node") } func (h *Host) SetStream(nodeId enode.ID, stream pubsub.Stream) { @@ -81,7 +97,7 @@ func (h *Host) Close() error { } func (h *Host) ConnManager() connmgr.ConnManager { - return nil + return &connmgr.NullConnMgr{} } func (h *Host) NotifyAll(conn pubsub.Conn) { @@ -91,3 +107,10 @@ func (h *Host) NotifyAll(conn pubsub.Conn) { func (h *Host) AddConn(p enode.ID, conn pubsub.Conn) { h.network.SetConn(p, conn) } + +func (h *Host) DisConn(p enode.ID) { + h.Lock() + defer h.Unlock() + delete(h.streams, p) + h.network.RemoveConn(p) +} diff --git a/p2p/network.go b/p2p/network.go index 5bc3389ac7..85fc40382d 100644 --- a/p2p/network.go +++ b/p2p/network.go @@ -59,6 +59,12 @@ func (n *Network) SetConn(p enode.ID, conn pubsub.Conn) { n.conns.m[p] = conns } +func (n *Network) RemoveConn(p enode.ID) { + n.conns.RLock() + defer n.conns.RUnlock() + delete(n.conns.m, p) +} + func (n *Network) ConnsToPeer(p enode.ID) []pubsub.Conn { n.conns.RLock() defer n.conns.RUnlock() @@ -79,6 +85,16 @@ func (n *Network) Connectedness(id enode.ID) pubsub.Connectedness { return pubsub.NotConnected } +func (n *Network) Conns() []pubsub.Conn { + n.conns.RLock() + defer n.conns.RUnlock() + connList := make([]pubsub.Conn, 0, len(n.conns.m)) + for _, cs := range n.conns.m { + connList = append(connList, cs...) + } + return connList +} + func (n *Network) Notify(f pubsub.Notifiee) { n.Lock() n.m[f] = struct{}{} @@ -116,6 +132,10 @@ func (n *Network) Peers() []enode.ID { return eids } +func (n *Network) Close() error { + return nil +} + type Conn struct { remote *enode.Node stat pubsub.Stat @@ -171,3 +191,7 @@ func (c *Conn) Stat() pubsub.Stat { func (c *Conn) RemotePeer() *enode.Node { return c.remote } + +func (c *Conn) Close() error { + return nil +} diff --git a/p2p/pubsub/blacklist_test.go b/p2p/pubsub/blacklist_test.go index 38ec33f66e..c5b6095cdb 100644 --- a/p2p/pubsub/blacklist_test.go +++ b/p2p/pubsub/blacklist_test.go @@ -41,7 +41,7 @@ func TestBlacklist(t *testing.T) { defer cancel() hosts := getNetHosts(t, ctx, 2) - psubs := getPubsubs(ctx, hosts) + psubs := getGossipsubs(ctx, hosts) connect(t, hosts[0], hosts[1]) sub, err := psubs[1].Subscribe("test") @@ -69,7 +69,7 @@ func TestBlacklist2(t *testing.T) { defer cancel() hosts := getNetHosts(t, ctx, 2) - psubs := getPubsubs(ctx, hosts) + psubs := getGossipsubs(ctx, hosts) connect(t, hosts[0], hosts[1]) _, err := psubs[0].Subscribe("test") @@ -102,7 +102,7 @@ func TestBlacklist3(t *testing.T) { defer cancel() hosts := getNetHosts(t, ctx, 2) - psubs := getPubsubs(ctx, hosts) + psubs := getGossipsubs(ctx, hosts) psubs[1].BlacklistPeer(hosts[0].ID()) time.Sleep(time.Millisecond * 100) diff --git a/p2p/pubsub/discovery.go b/p2p/pubsub/discovery.go index 8874aff49b..5587e07877 100644 --- a/p2p/pubsub/discovery.go +++ b/p2p/pubsub/discovery.go @@ -32,19 +32,9 @@ type discoverOptions struct { } func defaultDiscoverOptions() *discoverOptions { - /*rngSrc := rand.NewSource(rand.Int63()) - minBackoff, maxBackoff := time.Second*10, time.Hour - cacheSize := 100 - dialTimeout := time.Minute * 2 - discoverOpts := &discoverOptions{ - connFactory: func(host host.Host) (*discimpl.BackoffConnector, error) { - backoff := discimpl.NewExponentialBackoff(minBackoff, maxBackoff, discimpl.FullJitter, time.Second, 5.0, 0, rand.New(rngSrc)) - return discimpl.NewBackoffConnector(host, cacheSize, dialTimeout, backoff) - }, - } + discoverOpts := &discoverOptions{} - return discoverOpts*/ - return nil + return discoverOpts } // discover represents the discovery pipeline. diff --git a/p2p/pubsub/discovery_test.go b/p2p/pubsub/discovery_test.go index 7be138e09b..b245cdf4b6 100644 --- a/p2p/pubsub/discovery_test.go +++ b/p2p/pubsub/discovery_test.go @@ -1,22 +1,18 @@ package pubsub import ( - "bytes" "context" - "fmt" - "math/rand" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "sync" - "testing" "time" "github.com/libp2p/go-libp2p-core/discovery" - "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" ) type mockDiscoveryServer struct { mx sync.Mutex - db map[string]map[peer.ID]*discoveryRegistration + db map[string]map[enode.ID]*discoveryRegistration } type discoveryRegistration struct { @@ -26,20 +22,20 @@ type discoveryRegistration struct { func newDiscoveryServer() *mockDiscoveryServer { return &mockDiscoveryServer{ - db: make(map[string]map[peer.ID]*discoveryRegistration), + db: make(map[string]map[enode.ID]*discoveryRegistration), } } func (s *mockDiscoveryServer) Advertise(ns string, info peer.AddrInfo, ttl time.Duration) (time.Duration, error) { - s.mx.Lock() - defer s.mx.Unlock() - - peers, ok := s.db[ns] - if !ok { - peers = make(map[peer.ID]*discoveryRegistration) - s.db[ns] = peers - } - peers[info.ID] = &discoveryRegistration{info, ttl} + //s.mx.Lock() + //defer s.mx.Unlock() + // + //peers, ok := s.db[ns] + //if !ok { + // peers = make(map[enode.ID]*discoveryRegistration) + // s.db[ns] = peers + //} + //peers[info.ID] = &discoveryRegistration{info, ttl} return ttl, nil } @@ -72,7 +68,7 @@ func (s *mockDiscoveryServer) FindPeers(ns string, limit int) (<-chan peer.AddrI return ch, nil } -func (s *mockDiscoveryServer) hasPeerRecord(ns string, pid peer.ID) bool { +func (s *mockDiscoveryServer) hasPeerRecord(ns string, pid enode.ID) bool { s.mx.Lock() defer s.mx.Unlock() @@ -83,8 +79,8 @@ func (s *mockDiscoveryServer) hasPeerRecord(ns string, pid peer.ID) bool { return false } -type mockDiscoveryClient struct { - host host.Host +/*type mockDiscoveryClient struct { + host Host server *mockDiscoveryServer } @@ -106,7 +102,7 @@ func (d *mockDiscoveryClient) FindPeers(ctx context.Context, ns string, opts ... } return d.server.FindPeers(ns, options.Limit) -} +}*/ type dummyDiscovery struct{} @@ -123,174 +119,176 @@ func (d *dummyDiscovery) FindPeers(ctx context.Context, ns string, opts ...disco return retCh, nil } -func TestSimpleDiscovery(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // Setup Discovery server and pubsub clients - const numHosts = 20 - const topic = "foobar" - - server := newDiscoveryServer() - discOpts := []discovery.Option{discovery.Limit(numHosts), discovery.TTL(1 * time.Minute)} - - hosts := getNetHosts(t, ctx, numHosts) - psubs := make([]*PubSub, numHosts) - topicHandlers := make([]*Topic, numHosts) - - for i, h := range hosts { - disc := &mockDiscoveryClient{h, server} - ps := getPubsub(ctx, h, WithDiscovery(disc, WithDiscoveryOpts(discOpts...))) - psubs[i] = ps - topicHandlers[i], _ = ps.Join(topic) - } - - // Subscribe with all but one pubsub instance - msgs := make([]*Subscription, numHosts) - for i, th := range topicHandlers[1:] { - subch, err := th.Subscribe() - if err != nil { - t.Fatal(err) - } - - msgs[i+1] = subch - } - - // Wait for the advertisements to go through then check that they did - for { - server.mx.Lock() - numPeers := len(server.db["floodsub:foobar"]) - server.mx.Unlock() - if numPeers == numHosts-1 { - break - } else { - time.Sleep(time.Millisecond * 100) - } - } - - for i, h := range hosts[1:] { - if !server.hasPeerRecord("floodsub:"+topic, h.ID()) { - t.Fatalf("Server did not register host %d with ID: %s", i+1, h.ID().Pretty()) - } - } - - // Try subscribing followed by publishing a single message - subch, err := topicHandlers[0].Subscribe() - if err != nil { - t.Fatal(err) - } - msgs[0] = subch - - msg := []byte("first message") - if err := topicHandlers[0].Publish(ctx, msg, WithReadiness(MinTopicSize(numHosts-1))); err != nil { - t.Fatal(err) - } - - for _, sub := range msgs { - got, err := sub.Next(ctx) - if err != nil { - t.Fatal(sub.err) - } - if !bytes.Equal(msg, got.Data) { - t.Fatal("got wrong message!") - } - } - - // Try random peers sending messages and make sure they are received - for i := 0; i < 100; i++ { - msg := []byte(fmt.Sprintf("%d the flooooooood %d", i, i)) - - owner := rand.Intn(len(psubs)) - - if err := topicHandlers[owner].Publish(ctx, msg, WithReadiness(MinTopicSize(1))); err != nil { - t.Fatal(err) - } - - for _, sub := range msgs { - got, err := sub.Next(ctx) - if err != nil { - t.Fatal(sub.err) - } - if !bytes.Equal(msg, got.Data) { - t.Fatal("got wrong message!") - } - } - } -} - -func TestGossipSubDiscoveryAfterBootstrap(t *testing.T) { - t.Skip("flaky test disabled") - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // Setup Discovery server and pubsub clients - partitionSize := GossipSubDlo - 1 - numHosts := partitionSize * 2 - const ttl = 1 * time.Minute - - const topic = "foobar" - - server1, server2 := newDiscoveryServer(), newDiscoveryServer() - discOpts := []discovery.Option{discovery.Limit(numHosts), discovery.TTL(ttl)} - - // Put the pubsub clients into two partitions - hosts := getNetHosts(t, ctx, numHosts) - psubs := make([]*PubSub, numHosts) - topicHandlers := make([]*Topic, numHosts) - - for i, h := range hosts { - s := server1 - if i >= partitionSize { - s = server2 - } - disc := &mockDiscoveryClient{h, s} - ps := getGossipsub(ctx, h, WithDiscovery(disc, WithDiscoveryOpts(discOpts...))) - psubs[i] = ps - topicHandlers[i], _ = ps.Join(topic) - } - - msgs := make([]*Subscription, numHosts) - for i, th := range topicHandlers { - subch, err := th.Subscribe() - if err != nil { - t.Fatal(err) - } - - msgs[i] = subch - } - - // Wait for network to finish forming then join the partitions via discovery - for _, ps := range psubs { - waitUntilGossipsubMeshCount(ps, topic, partitionSize-1) - } - - for i := 0; i < partitionSize; i++ { - if _, err := server1.Advertise("floodsub:"+topic, *host.InfoFromHost(hosts[i+partitionSize]), ttl); err != nil { - t.Fatal(err) - } - } - - // test the mesh - for i := 0; i < 100; i++ { - msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) - - owner := rand.Intn(numHosts) - - if err := topicHandlers[owner].Publish(ctx, msg, WithReadiness(MinTopicSize(numHosts-1))); err != nil { - t.Fatal(err) - } - - for _, sub := range msgs { - got, err := sub.Next(ctx) - if err != nil { - t.Fatal(sub.err) - } - if !bytes.Equal(msg, got.Data) { - t.Fatal("got wrong message!") - } - } - } -} +// TODO pubSub check +//func TestSimpleDiscovery(t *testing.T) { +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// +// // Setup Discovery server and pubsub clients +// const numHosts = 20 +// const topic = "foobar" +// +// server := newDiscoveryServer() +// //discOpts := []discovery.Option{discovery.Limit(numHosts), discovery.TTL(1 * time.Minute)} +// +// hosts := getNetHosts(t, ctx, numHosts) +// psubs := make([]*PubSub, numHosts) +// topicHandlers := make([]*Topic, numHosts) +// +// for i, h := range hosts { +// //disc := &mockDiscoveryClient{h, server} +// //ps := getPubsub(ctx, h, WithDiscovery(disc, WithDiscoveryOpts(discOpts...))) +// ps := getGossipsub(ctx, h) +// psubs[i] = ps +// topicHandlers[i], _ = ps.Join(topic) +// } +// +// // Subscribe with all but one pubsub instance +// msgs := make([]*Subscription, numHosts) +// for i, th := range topicHandlers[1:] { +// subch, err := th.Subscribe() +// if err != nil { +// t.Fatal(err) +// } +// +// msgs[i+1] = subch +// } +// +// // Wait for the advertisements to go through then check that they did +// for { +// server.mx.Lock() +// numPeers := len(server.db["floodsub:foobar"]) +// server.mx.Unlock() +// if numPeers == numHosts-1 { +// break +// } else { +// time.Sleep(time.Millisecond * 100) +// } +// } +// +// for i, h := range hosts[1:] { +// if !server.hasPeerRecord("floodsub:"+topic, h.ID().ID()) { +// t.Fatalf("Server did not register host %d with ID: %s", i+1, h.ID().ID().TerminalString()) +// } +// } +// +// // Try subscribing followed by publishing a single message +// subch, err := topicHandlers[0].Subscribe() +// if err != nil { +// t.Fatal(err) +// } +// msgs[0] = subch +// +// msg := []byte("first message") +// if err := topicHandlers[0].Publish(ctx, msg, WithReadiness(MinTopicSize(numHosts-1))); err != nil { +// t.Fatal(err) +// } +// +// for _, sub := range msgs { +// got, err := sub.Next(ctx) +// if err != nil { +// t.Fatal(sub.err) +// } +// if !bytes.Equal(msg, got.Data) { +// t.Fatal("got wrong message!") +// } +// } +// +// // Try random peers sending messages and make sure they are received +// for i := 0; i < 100; i++ { +// msg := []byte(fmt.Sprintf("%d the flooooooood %d", i, i)) +// +// owner := rand.Intn(len(psubs)) +// +// if err := topicHandlers[owner].Publish(ctx, msg, WithReadiness(MinTopicSize(1))); err != nil { +// t.Fatal(err) +// } +// +// for _, sub := range msgs { +// got, err := sub.Next(ctx) +// if err != nil { +// t.Fatal(sub.err) +// } +// if !bytes.Equal(msg, got.Data) { +// t.Fatal("got wrong message!") +// } +// } +// } +//} + +//func TestGossipSubDiscoveryAfterBootstrap(t *testing.T) { +// t.Skip("flaky test disabled") +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// +// // Setup Discovery server and pubsub clients +// partitionSize := GossipSubDlo - 1 +// numHosts := partitionSize * 2 +// const ttl = 1 * time.Minute +// +// const topic = "foobar" +// +// server1, server2 := newDiscoveryServer(), newDiscoveryServer() +// discOpts := []discovery.Option{discovery.Limit(numHosts), discovery.TTL(ttl)} +// +// // Put the pubsub clients into two partitions +// hosts := getNetHosts(t, ctx, numHosts) +// psubs := make([]*PubSub, numHosts) +// topicHandlers := make([]*Topic, numHosts) +// +// for i, h := range hosts { +// s := server1 +// if i >= partitionSize { +// s = server2 +// } +// disc := &mockDiscoveryClient{h, s} +// ps := getGossipsub(ctx, h, WithDiscovery(disc, WithDiscoveryOpts(discOpts...))) +// psubs[i] = ps +// topicHandlers[i], _ = ps.Join(topic) +// } +// +// msgs := make([]*Subscription, numHosts) +// for i, th := range topicHandlers { +// subch, err := th.Subscribe() +// if err != nil { +// t.Fatal(err) +// } +// +// msgs[i] = subch +// } +// +// // Wait for network to finish forming then join the partitions via discovery +// for _, ps := range psubs { +// waitUntilGossipsubMeshCount(ps, topic, partitionSize-1) +// } +// +// for i := 0; i < partitionSize; i++ { +// if _, err := server1.Advertise("floodsub:"+topic, *host.InfoFromHost(hosts[i+partitionSize]), ttl); err != nil { +// t.Fatal(err) +// } +// } +// +// // test the mesh +// for i := 0; i < 100; i++ { +// msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) +// +// owner := rand.Intn(numHosts) +// +// if err := topicHandlers[owner].Publish(ctx, msg, WithReadiness(MinTopicSize(numHosts-1))); err != nil { +// t.Fatal(err) +// } +// +// for _, sub := range msgs { +// got, err := sub.Next(ctx) +// if err != nil { +// t.Fatal(sub.err) +// } +// if !bytes.Equal(msg, got.Data) { +// t.Fatal("got wrong message!") +// } +// } +// } +//} //lint:ignore U1000 used only by skipped tests at present func waitUntilGossipsubMeshCount(ps *PubSub, topic string, count int) { diff --git a/p2p/pubsub/gossip_tracer_test.go b/p2p/pubsub/gossip_tracer_test.go index 787e753d26..38ae708f1d 100644 --- a/p2p/pubsub/gossip_tracer_test.go +++ b/p2p/pubsub/gossip_tracer_test.go @@ -1,12 +1,11 @@ package pubsub import ( + crand "crypto/rand" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" "testing" "time" - - pb "github.com/libp2p/go-libp2p-pubsub/pb" - - "github.com/libp2p/go-libp2p-core/peer" ) func TestBrokenPromises(t *testing.T) { @@ -14,14 +13,17 @@ func TestBrokenPromises(t *testing.T) { gt := newGossipTracer() gt.followUpTime = 100 * time.Millisecond - peerA := peer.ID("A") - peerB := peer.ID("B") - peerC := peer.ID("C") + var peerA enode.ID + crand.Read(peerA[:]) + var peerB enode.ID + crand.Read(peerB[:]) + var peerC enode.ID + crand.Read(peerC[:]) var mids []string for i := 0; i < 100; i++ { m := makeTestMessage(i) - m.From = []byte(peerA) + m.From = peerA mid := DefaultMsgIdFn(m) mids = append(mids, mid) } @@ -68,14 +70,16 @@ func TestNoBrokenPromises(t *testing.T) { gt := newGossipTracer() - peerA := peer.ID("A") - peerB := peer.ID("B") + var peerA enode.ID + crand.Read(peerA[:]) + var peerB enode.ID + crand.Read(peerB[:]) - var msgs []*pb.Message + var msgs []*message.Message var mids []string for i := 0; i < 100; i++ { m := makeTestMessage(i) - m.From = []byte(peerA) + m.From = peerA msgs = append(msgs, m) mid := DefaultMsgIdFn(m) mids = append(mids, mid) diff --git a/p2p/pubsub/gossippub.go b/p2p/pubsub/gossipsub.go similarity index 99% rename from p2p/pubsub/gossippub.go rename to p2p/pubsub/gossipsub.go index 922a3817ab..730bda4474 100644 --- a/p2p/pubsub/gossippub.go +++ b/p2p/pubsub/gossipsub.go @@ -331,7 +331,6 @@ func WithPeerExchange(doPX bool) Option { direct := make(map[enode.ID]struct{}) for _, pi := range pis { direct[pi.ID()] = struct{}{} - ps.host.Peerstore().AddAddrs(pi.ID(), PermanentAddrTTL) } gs.direct = direct @@ -998,8 +997,9 @@ func (gs *GossipSubRouter) Publish(msg *Message) { } out := rpcWithMessages(msg.Message) + for pid := range tosend { - if pid == from.ID() || pid == enode.ID(msg.GetFrom()) { + if pid == from.ID() || pid == msg.GetFrom() { continue } @@ -1190,12 +1190,12 @@ func fragmentRPC(rpc *RPC, limit int) ([]*RPC, error) { if s > limit { return nil, fmt.Errorf("message with len=%d exceeds limit %d", s, limit) } - out := outRPC(s, false) + out := outRPC(s, true) out.Publish = append(out.Publish, msg) } for _, sub := range rpc.GetSubscriptions() { - out := outRPC(sub.Size(), false) + out := outRPC(sub.Size(), true) out.Subscriptions = append(out.Subscriptions, sub) } diff --git a/p2p/pubsub/gossipsub_feat_test.go b/p2p/pubsub/gossipsub_feat_test.go index 668ed86fff..e87ae6fdcd 100644 --- a/p2p/pubsub/gossipsub_feat_test.go +++ b/p2p/pubsub/gossipsub_feat_test.go @@ -1,20 +1,10 @@ package pubsub import ( - "bytes" - "context" - "fmt" - "math/rand" "testing" - "time" - - "github.com/libp2p/go-libp2p-core/protocol" ) func TestDefaultGossipSubFeatures(t *testing.T) { - if GossipSubDefaultFeatures(GossipSubFeatureMesh, FloodSubID) { - t.Fatal("floodsub should not support Mesh") - } if !GossipSubDefaultFeatures(GossipSubFeatureMesh, GossipSubID_v10) { t.Fatal("gossipsub-v1.0 should support Mesh") } @@ -22,9 +12,6 @@ func TestDefaultGossipSubFeatures(t *testing.T) { t.Fatal("gossipsub-v1.1 should support Mesh") } - if GossipSubDefaultFeatures(GossipSubFeaturePX, FloodSubID) { - t.Fatal("floodsub should not support PX") - } if GossipSubDefaultFeatures(GossipSubFeaturePX, GossipSubID_v10) { t.Fatal("gossipsub-v1.0 should not support PX") } @@ -33,83 +20,85 @@ func TestDefaultGossipSubFeatures(t *testing.T) { } } -func TestGossipSubCustomProtocols(t *testing.T) { - customsub := protocol.ID("customsub/1.0.0") - protos := []protocol.ID{customsub, FloodSubID} - features := func(feat GossipSubFeature, proto protocol.ID) bool { - return proto == customsub - } - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - hosts := getNetHosts(t, ctx, 3) - - gsubs := getGossipsubs(ctx, hosts[:2], WithGossipSubProtocols(protos, features)) - fsub := getPubsub(ctx, hosts[2]) - psubs := append(gsubs, fsub) - - connectAll(t, hosts) - - topic := "test" - var subs []*Subscription - for _, ps := range psubs { - subch, err := ps.Subscribe(topic) - if err != nil { - t.Fatal(err) - } - - subs = append(subs, subch) - } - - // wait for heartbeats to build mesh - time.Sleep(time.Second * 2) - - // check the meshes of the gsubs, the gossipsub meshes should include each other but not the - // floddsub peer - gsubs[0].eval <- func() { - gs := gsubs[0].rt.(*GossipSubRouter) - - _, ok := gs.mesh[topic][hosts[1].ID()] - if !ok { - t.Fatal("expected gs0 to have gs1 in its mesh") - } - - _, ok = gs.mesh[topic][hosts[2].ID()] - if ok { - t.Fatal("expected gs0 to not have fs in its mesh") - } - } - - gsubs[1].eval <- func() { - gs := gsubs[1].rt.(*GossipSubRouter) - - _, ok := gs.mesh[topic][hosts[0].ID()] - if !ok { - t.Fatal("expected gs1 to have gs0 in its mesh") - } - - _, ok = gs.mesh[topic][hosts[2].ID()] - if ok { - t.Fatal("expected gs1 to not have fs in its mesh") - } - } - - // send some messages - for i := 0; i < 10; i++ { - msg := []byte(fmt.Sprintf("%d it's not quite a floooooood %d", i, i)) - - owner := rand.Intn(len(psubs)) - - psubs[owner].Publish(topic, msg) - - for _, sub := range subs { - got, err := sub.Next(ctx) - if err != nil { - t.Fatal(sub.err) - } - if !bytes.Equal(msg, got.Data) { - t.Fatal("got wrong message!") - } - } - } -} +// TODO pubSub check +// No other protocols are supported at this time +//func TestGossipSubCustomProtocols(t *testing.T) { +// customsub := ProtocolID("customsub/1.0.0") +// protos := []ProtocolID{customsub} +// features := func(feat GossipSubFeature, proto ProtocolID) bool { +// return proto == customsub +// } +// +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// hosts := getNetHosts(t, ctx, 3) +// +// gsubs := getGossipsubs(ctx, hosts[:2], WithGossipSubProtocols(protos, features)) +// fsub := getPubsub(ctx, hosts[2]) +// psubs := append(gsubs, fsub) +// +// connectAll(t, hosts) +// +// topic := "test" +// var subs []*Subscription +// for _, ps := range psubs { +// subch, err := ps.Subscribe(topic) +// if err != nil { +// t.Fatal(err) +// } +// +// subs = append(subs, subch) +// } +// +// // wait for heartbeats to build mesh +// time.Sleep(time.Second * 2) +// +// // check the meshes of the gsubs, the gossipsub meshes should include each other but not the +// // floddsub peer +// gsubs[0].eval <- func() { +// gs := gsubs[0].rt.(*GossipSubRouter) +// +// _, ok := gs.mesh[topic][hosts[1].ID().ID()] +// if !ok { +// t.Fatal("expected gs0 to have gs1 in its mesh") +// } +// +// _, ok = gs.mesh[topic][hosts[2].ID().ID()] +// if ok { +// t.Fatal("expected gs0 to not have fs in its mesh") +// } +// } +// +// gsubs[1].eval <- func() { +// gs := gsubs[1].rt.(*GossipSubRouter) +// +// _, ok := gs.mesh[topic][hosts[0].ID().ID()] +// if !ok { +// t.Fatal("expected gs1 to have gs0 in its mesh") +// } +// +// _, ok = gs.mesh[topic][hosts[2].ID().ID()] +// if ok { +// t.Fatal("expected gs1 to not have fs in its mesh") +// } +// } +// +// // send some messages +// for i := 0; i < 10; i++ { +// msg := []byte(fmt.Sprintf("%d it's not quite a floooooood %d", i, i)) +// +// owner := rand.Intn(len(psubs)) +// +// psubs[owner].Publish(topic, msg) +// +// for _, sub := range subs { +// got, err := sub.Next(ctx) +// if err != nil { +// t.Fatal(sub.err) +// } +// if !bytes.Equal(msg, got.Data) { +// t.Fatal("got wrong message!") +// } +// } +// } +//} diff --git a/p2p/pubsub/gossipsub_test.go b/p2p/pubsub/gossipsub_test.go index af0b0d6b11..8a57927d14 100644 --- a/p2p/pubsub/gossipsub_test.go +++ b/p2p/pubsub/gossipsub_test.go @@ -3,19 +3,16 @@ package pubsub import ( "bytes" "context" + crand "crypto/rand" "fmt" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/network" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/peerstore" - "github.com/libp2p/go-libp2p-core/record" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" "io" "math/rand" "sync" "testing" "time" - - "github.com/libp2p/go-msgio/protoio" ) func getGossipsub(ctx context.Context, h Host, opts ...Option) *PubSub { @@ -620,54 +617,55 @@ func TestGossipsubGraft(t *testing.T) { } } -func TestGossipsubRemovePeer(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - hosts := getNetHosts(t, ctx, 20) - - psubs := getGossipsubs(ctx, hosts) - - var msgs []*Subscription - for _, ps := range psubs { - subch, err := ps.Subscribe("foobar") - if err != nil { - t.Fatal(err) - } - - msgs = append(msgs, subch) - } - - denseConnect(t, hosts) - - // wait for heartbeats to build mesh - time.Sleep(time.Second * 2) - - // disconnect some peers to exercise RemovePeer paths - for _, host := range hosts[:5] { - host.Close() - } - - // wait a heartbeat - time.Sleep(time.Second * 1) - - for i := 0; i < 10; i++ { - msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) - - owner := 5 + rand.Intn(len(psubs)-5) - - psubs[owner].Publish("foobar", msg) - - for _, sub := range msgs[5:] { - got, err := sub.Next(ctx) - if err != nil { - t.Fatal(sub.err) - } - if !bytes.Equal(msg, got.Data) { - t.Fatal("got wrong message!") - } - } - } -} +// TODO pubSub check +//func TestGossipsubRemovePeer(t *testing.T) { +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// hosts := getNetHosts(t, ctx, 20) +// +// psubs := getGossipsubs(ctx, hosts) +// +// var msgs []*Subscription +// for _, ps := range psubs { +// subch, err := ps.Subscribe("foobar") +// if err != nil { +// t.Fatal(err) +// } +// +// msgs = append(msgs, subch) +// } +// +// denseConnect(t, hosts) +// +// // wait for heartbeats to build mesh +// time.Sleep(time.Second * 2) +// +// // disconnect some peers to exercise RemovePeer paths +// for _, host := range hosts[:5] { +// host.Close() +// } +// +// // wait a heartbeat +// time.Sleep(time.Second * 1) +// +// for i := 0; i < 10; i++ { +// msg := []byte(fmt.Sprintf("%d it's not a floooooood %d", i, i)) +// +// owner := 5 + rand.Intn(len(psubs)-5) +// +// psubs[owner].Publish("foobar", msg) +// +// for _, sub := range msgs[5:] { +// got, err := sub.Next(ctx) +// if err != nil { +// t.Fatal(sub.err) +// } +// if !bytes.Equal(msg, got.Data) { +// t.Fatal("got wrong message!") +// } +// } +// } +//} func TestGossipsubGraftPruneRetry(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) @@ -807,7 +805,7 @@ func TestMixedGossipsub(t *testing.T) { hosts := getNetHosts(t, ctx, 30) gsubs := getGossipsubs(ctx, hosts[:20]) - fsubs := getPubsubs(ctx, hosts[20:]) + fsubs := getGossipsubs(ctx, hosts[20:]) psubs := append(gsubs, fsubs...) var msgs []*Subscription @@ -936,234 +934,211 @@ func TestGossipsubTreeTopology(t *testing.T) { // this tests overlay bootstrapping through px in Gossipsub v1.1 // we start with a star topology and rely on px through prune to build the mesh -func TestGossipsubStarTopology(t *testing.T) { - originalGossipSubD := GossipSubD - GossipSubD = 4 - originalGossipSubDhi := GossipSubDhi - GossipSubDhi = GossipSubD + 1 - originalGossipSubDlo := GossipSubDlo - GossipSubDlo = GossipSubD - 1 - originalGossipSubDscore := GossipSubDscore - GossipSubDscore = GossipSubDlo - defer func() { - GossipSubD = originalGossipSubD - GossipSubDhi = originalGossipSubDhi - GossipSubDlo = originalGossipSubDlo - GossipSubDscore = originalGossipSubDscore - }() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - hosts := getNetHosts(t, ctx, 20) - psubs := getGossipsubs(ctx, hosts, WithPeerExchange(true)) - - // configure the center of the star with a very low D - psubs[0].eval <- func() { - gs := psubs[0].rt.(*GossipSubRouter) - gs.params.D = 0 - gs.params.Dlo = 0 - gs.params.Dhi = 0 - gs.params.Dscore = 0 - } - - // build the star - for i := 1; i < 20; i++ { - connect(t, hosts[0], hosts[i]) - } - - time.Sleep(time.Second) - - // build the mesh - var subs []*Subscription - for _, ps := range psubs { - sub, err := ps.Subscribe("test") - if err != nil { - t.Fatal(err) - } - subs = append(subs, sub) - } - - // wait a bit for the mesh to build - time.Sleep(10 * time.Second) - - // check that all peers have > 1 connection - for i, h := range hosts { - if len(h.Network().Conns()) == 1 { - t.Errorf("peer %d has ony a single connection", i) - } - } - - // send a message from each peer and assert it was propagated - for i := 0; i < 20; i++ { - msg := []byte(fmt.Sprintf("message %d", i)) - psubs[i].Publish("test", msg) - - for _, sub := range subs { - assertReceive(t, sub, msg) - } - } -} +//func TestGossipsubStarTopology(t *testing.T) { +// originalGossipSubD := GossipSubD +// GossipSubD = 4 +// originalGossipSubDhi := GossipSubDhi +// GossipSubDhi = GossipSubD + 1 +// originalGossipSubDlo := GossipSubDlo +// GossipSubDlo = GossipSubD - 1 +// originalGossipSubDscore := GossipSubDscore +// GossipSubDscore = GossipSubDlo +// defer func() { +// GossipSubD = originalGossipSubD +// GossipSubDhi = originalGossipSubDhi +// GossipSubDlo = originalGossipSubDlo +// GossipSubDscore = originalGossipSubDscore +// }() +// +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// +// hosts := getNetHosts(t, ctx, 20) +// psubs := getGossipsubs(ctx, hosts, WithPeerExchange(true), WithFloodPublish(true)) +// +// // configure the center of the star with a very low D +// psubs[0].eval <- func() { +// gs := psubs[0].rt.(*GossipSubRouter) +// gs.params.D = 0 +// gs.params.Dlo = 0 +// gs.params.Dhi = 0 +// gs.params.Dscore = 0 +// } +// +// // build the star +// for i := 1; i < 20; i++ { +// connect(t, hosts[0], hosts[i]) +// } +// +// time.Sleep(time.Second) +// +// // build the mesh +// var subs []*Subscription +// for _, ps := range psubs { +// sub, err := ps.Subscribe("test") +// if err != nil { +// t.Fatal(err) +// } +// subs = append(subs, sub) +// } +// +// // wait a bit for the mesh to build +// time.Sleep(10 * time.Second) +// +// // check that all peers have > 1 connection +// for i, h := range hosts { +// if len(h.Network().Conns()) == 1 { +// t.Errorf("peer %d has ony a single connection", i) +// } +// } +// +// // send a message from each peer and assert it was propagated +// for i := 0; i < 20; i++ { +// msg := []byte(fmt.Sprintf("message %d", i)) +// psubs[i].Publish("test", msg) +// +// for _, sub := range subs { +// assertReceive(t, sub, msg) +// } +// } +//} // this tests overlay bootstrapping through px in Gossipsub v1.1, with addresses // exchanged in signed peer records. // we start with a star topology and rely on px through prune to build the mesh -func TestGossipsubStarTopologyWithSignedPeerRecords(t *testing.T) { - originalGossipSubD := GossipSubD - GossipSubD = 4 - originalGossipSubDhi := GossipSubDhi - GossipSubDhi = GossipSubD + 1 - originalGossipSubDlo := GossipSubDlo - GossipSubDlo = GossipSubD - 1 - originalGossipSubDscore := GossipSubDscore - GossipSubDscore = GossipSubDlo - defer func() { - GossipSubD = originalGossipSubD - GossipSubDhi = originalGossipSubDhi - GossipSubDlo = originalGossipSubDlo - GossipSubDscore = originalGossipSubDscore - }() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - hosts := getNetHosts(t, ctx, 20) - psubs := getGossipsubs(ctx, hosts, WithPeerExchange(true)) - - // configure the center of the star with a very low D - psubs[0].eval <- func() { - gs := psubs[0].rt.(*GossipSubRouter) - gs.params.D = 0 - gs.params.Dlo = 0 - gs.params.Dhi = 0 - gs.params.Dscore = 0 - } - - // manually create signed peer records for each host and add them to the - // peerstore of the center of the star, which is doing the bootstrapping - for i := range hosts[1:] { - privKey := hosts[i].Peerstore().PrivKey(hosts[i].ID()) - if privKey == nil { - t.Fatalf("unable to get private key for host %s", hosts[i].ID().Pretty()) - } - ai := host.InfoFromHost(hosts[i]) - rec := peer.PeerRecordFromAddrInfo(*ai) - signedRec, err := record.Seal(rec, privKey) - if err != nil { - t.Fatalf("error creating signed peer record: %s", err) - } - - cab, ok := peerstore.GetCertifiedAddrBook(hosts[0].Peerstore()) - if !ok { - t.Fatal("peerstore does not implement CertifiedAddrBook") - } - _, err = cab.ConsumePeerRecord(signedRec, peerstore.PermanentAddrTTL) - if err != nil { - t.Fatalf("error adding signed peer record: %s", err) - } - } - - // build the star - for i := 1; i < 20; i++ { - connect(t, hosts[0], hosts[i]) - } - - time.Sleep(time.Second) - - // build the mesh - var subs []*Subscription - for _, ps := range psubs { - sub, err := ps.Subscribe("test") - if err != nil { - t.Fatal(err) - } - subs = append(subs, sub) - } - - // wait a bit for the mesh to build - time.Sleep(10 * time.Second) - - // check that all peers have > 1 connection - for i, h := range hosts { - if len(h.Network().Conns()) == 1 { - t.Errorf("peer %d has ony a single connection", i) - } - } - - // send a message from each peer and assert it was propagated - for i := 0; i < 20; i++ { - msg := []byte(fmt.Sprintf("message %d", i)) - psubs[i].Publish("test", msg) - - for _, sub := range subs { - assertReceive(t, sub, msg) - } - } -} - -func TestGossipsubDirectPeers(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - h := getNetHosts(t, ctx, 3) - psubs := []*PubSub{ - getGossipsub(ctx, h[0], WithDirectConnectTicks(2)), - getGossipsub(ctx, h[1]), - getGossipsub(ctx, h[2]), - } - - connect(t, h[0], h[1]) - connect(t, h[0], h[2]) - - // verify that the direct peers connected - time.Sleep(2 * time.Second) - if len(h[1].Network().ConnsToPeer(h[2].ID().ID())) == 0 { - t.Fatal("expected a connection between direct peers") - } - - // build the mesh - var subs []*Subscription - for _, ps := range psubs { - sub, err := ps.Subscribe("test") - if err != nil { - t.Fatal(err) - } - subs = append(subs, sub) - } - - time.Sleep(time.Second) - - // publish some messages - for i := 0; i < 3; i++ { - msg := []byte(fmt.Sprintf("message %d", i)) - psubs[i].Publish("test", msg) - - for _, sub := range subs { - assertReceive(t, sub, msg) - } - } - - // disconnect the direct peers to test reconnection - for _, c := range h[1].Network().ConnsToPeer(h[2].ID()) { - c.Close() - } - - time.Sleep(5 * time.Second) - - if len(h[1].Network().ConnsToPeer(h[2].ID())) == 0 { - t.Fatal("expected a connection between direct peers") - } - - // publish some messages - for i := 0; i < 3; i++ { - msg := []byte(fmt.Sprintf("message %d", i)) - psubs[i].Publish("test", msg) - - for _, sub := range subs { - assertReceive(t, sub, msg) - } - } -} +//func TestGossipsubStarTopologyWithSignedPeerRecords(t *testing.T) { +// originalGossipSubD := GossipSubD +// GossipSubD = 4 +// originalGossipSubDhi := GossipSubDhi +// GossipSubDhi = GossipSubD + 1 +// originalGossipSubDlo := GossipSubDlo +// GossipSubDlo = GossipSubD - 1 +// originalGossipSubDscore := GossipSubDscore +// GossipSubDscore = GossipSubDlo +// defer func() { +// GossipSubD = originalGossipSubD +// GossipSubDhi = originalGossipSubDhi +// GossipSubDlo = originalGossipSubDlo +// GossipSubDscore = originalGossipSubDscore +// }() +// +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// +// hosts := getNetHosts(t, ctx, 20) +// psubs := getGossipsubs(ctx, hosts, WithPeerExchange(true), WithFloodPublish(true)) +// +// // configure the center of the star with a very low D +// psubs[0].eval <- func() { +// gs := psubs[0].rt.(*GossipSubRouter) +// gs.params.D = 0 +// gs.params.Dlo = 0 +// gs.params.Dhi = 0 +// gs.params.Dscore = 0 +// } +// +// // build the star +// for i := 1; i < 20; i++ { +// connect(t, hosts[0], hosts[i]) +// } +// +// time.Sleep(time.Second) +// +// // build the mesh +// var subs []*Subscription +// for _, ps := range psubs { +// sub, err := ps.Subscribe("test") +// if err != nil { +// t.Fatal(err) +// } +// subs = append(subs, sub) +// } +// +// // wait a bit for the mesh to build +// time.Sleep(10 * time.Second) +// +// // check that all peers have > 1 connection +// for i, h := range hosts { +// if len(h.Network().Conns()) == 1 { +// t.Errorf("peer %d has ony a single connection", i) +// } +// } +// +// // send a message from each peer and assert it was propagated +// for i := 0; i < 20; i++ { +// msg := []byte(fmt.Sprintf("message %d", i)) +// psubs[i].Publish("test", msg) +// +// for _, sub := range subs { +// assertReceive(t, sub, msg) +// } +// } +//} + +//func TestGossipsubDirectPeers(t *testing.T) { +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// +// h := getNetHosts(t, ctx, 3) +// psubs := []*PubSub{ +// getGossipsub(ctx, h[0], WithDirectConnectTicks(2)), +// getGossipsub(ctx, h[1], WithDirectPeers([]*enode.Node{h[2].ID()}), WithDirectConnectTicks(2)), +// getGossipsub(ctx, h[2], WithDirectPeers([]*enode.Node{h[1].ID()}), WithDirectConnectTicks(2)), +// } +// +// connect(t, h[0], h[1]) +// connect(t, h[0], h[2]) +// +// // verify that the direct peers connected +// time.Sleep(2 * time.Second) +// if len(h[1].Network().ConnsToPeer(h[2].ID().ID())) == 0 { +// t.Fatal("expected a connection between direct peers") +// } +// +// // build the mesh +// var subs []*Subscription +// for _, ps := range psubs { +// sub, err := ps.Subscribe("test") +// if err != nil { +// t.Fatal(err) +// } +// subs = append(subs, sub) +// } +// +// time.Sleep(time.Second) +// +// // publish some messages +// for i := 0; i < 3; i++ { +// msg := []byte(fmt.Sprintf("message %d", i)) +// psubs[i].Publish("test", msg) +// +// for _, sub := range subs { +// assertReceive(t, sub, msg) +// } +// } +// +// // disconnect the direct peers to test reconnection +// // TODO pubSub check +// /*for _, c := range h[1].Network().ConnsToPeer(h[2].ID().ID()) { +// c.Close() +// }*/ +// +// time.Sleep(5 * time.Second) +// +// if len(h[1].Network().ConnsToPeer(h[2].ID().ID())) == 0 { +// t.Fatal("expected a connection between direct peers") +// } +// +// // publish some messages +// for i := 0; i < 3; i++ { +// msg := []byte(fmt.Sprintf("message %d", i)) +// psubs[i].Publish("test", msg) +// +// for _, sub := range subs { +// assertReceive(t, sub, msg) +// } +// } +//} func TestGossipSubPeerFilter(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) @@ -1171,11 +1146,11 @@ func TestGossipSubPeerFilter(t *testing.T) { h := getNetHosts(t, ctx, 3) psubs := []*PubSub{ - getGossipsub(ctx, h[0], WithPeerFilter(func(pid peer.ID, topic string) bool { - return pid == h[1].ID() + getGossipsub(ctx, h[0], WithPeerFilter(func(pid enode.ID, topic string) bool { + return pid == h[1].ID().ID() })), - getGossipsub(ctx, h[1], WithPeerFilter(func(pid peer.ID, topic string) bool { - return pid == h[0].ID() + getGossipsub(ctx, h[1], WithPeerFilter(func(pid enode.ID, topic string) bool { + return pid == h[0].ID().ID() })), getGossipsub(ctx, h[2]), } @@ -1206,129 +1181,6 @@ func TestGossipSubPeerFilter(t *testing.T) { assertNeverReceives(t, subs[2], time.Second) } -func TestGossipsubDirectPeersFanout(t *testing.T) { - // regression test for #371 - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - h := getNetHosts(t, ctx, 3) - psubs := []*PubSub{ - getGossipsub(ctx, h[0]), - getGossipsub(ctx, h[1], WithDirectPeers([]peer.AddrInfo{{ID: h[2].ID(), Addrs: h[2].Addrs()}})), - getGossipsub(ctx, h[2], WithDirectPeers([]peer.AddrInfo{{ID: h[1].ID(), Addrs: h[1].Addrs()}})), - } - - connect(t, h[0], h[1]) - connect(t, h[0], h[2]) - - // Join all peers except h2 - var subs []*Subscription - for _, ps := range psubs[:2] { - sub, err := ps.Subscribe("test") - if err != nil { - t.Fatal(err) - } - subs = append(subs, sub) - } - - time.Sleep(time.Second) - - // h2 publishes some messages to build a fanout - for i := 0; i < 3; i++ { - msg := []byte(fmt.Sprintf("message %d", i)) - psubs[2].Publish("test", msg) - - for _, sub := range subs { - assertReceive(t, sub, msg) - } - } - - // verify that h0 is in the fanout of h2, but not h1 who is a direct peer - result := make(chan bool, 2) - psubs[2].eval <- func() { - rt := psubs[2].rt.(*GossipSubRouter) - fanout := rt.fanout["test"] - _, ok := fanout[h[0].ID()] - result <- ok - _, ok = fanout[h[1].ID()] - result <- ok - } - - inFanout := <-result - if !inFanout { - t.Fatal("expected peer 0 to be in fanout") - } - - inFanout = <-result - if inFanout { - t.Fatal("expected peer 1 to not be in fanout") - } - - // now subscribe h2 too and verify tht h0 is in the mesh but not h1 - _, err := psubs[2].Subscribe("test") - if err != nil { - t.Fatal(err) - } - - time.Sleep(2 * time.Second) - - psubs[2].eval <- func() { - rt := psubs[2].rt.(*GossipSubRouter) - mesh := rt.mesh["test"] - _, ok := mesh[h[0].ID()] - result <- ok - _, ok = mesh[h[1].ID()] - result <- ok - } - - inMesh := <-result - if !inMesh { - t.Fatal("expected peer 0 to be in mesh") - } - - inMesh = <-result - if inMesh { - t.Fatal("expected peer 1 to not be in mesh") - } -} - -func TestGossipsubFloodPublish(t *testing.T) { - // uses a star topology without PX and publishes from the star to verify that all - // messages get received - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - hosts := getNetHosts(t, ctx, 20) - psubs := getGossipsubs(ctx, hosts, WithFloodPublish(true)) - - // build the star - for i := 1; i < 20; i++ { - connect(t, hosts[0], hosts[i]) - } - - // build the (partial, unstable) mesh - var subs []*Subscription - for _, ps := range psubs { - sub, err := ps.Subscribe("test") - if err != nil { - t.Fatal(err) - } - subs = append(subs, sub) - } - - time.Sleep(time.Second) - - // send a message from the star and assert it was received - for i := 0; i < 20; i++ { - msg := []byte(fmt.Sprintf("message %d", i)) - psubs[0].Publish("test", msg) - - for _, sub := range subs { - assertReceive(t, sub, msg) - } - } -} - func TestGossipsubEnoughPeers(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -1415,8 +1267,8 @@ func TestGossipsubNegativeScore(t *testing.T) { psubs := getGossipsubs(ctx, hosts, WithPeerScore( &PeerScoreParams{ - AppSpecificScore: func(p peer.ID) float64 { - if p == hosts[0].ID() { + AppSpecificScore: func(p enode.ID) float64 { + if p == hosts[0].ID().ID() { return -1000 } else { return 0 @@ -1490,92 +1342,93 @@ func TestGossipsubNegativeScore(t *testing.T) { } } -func TestGossipsubScoreValidatorEx(t *testing.T) { - // this is a test that of the two message drop responses from a validator - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - hosts := getNetHosts(t, ctx, 3) - psubs := getGossipsubs(ctx, hosts, - WithPeerScore( - &PeerScoreParams{ - AppSpecificScore: func(p peer.ID) float64 { return 0 }, - DecayInterval: time.Second, - DecayToZero: 0.01, - Topics: map[string]*TopicScoreParams{ - "test": { - TopicWeight: 1, - TimeInMeshQuantum: time.Second, - InvalidMessageDeliveriesWeight: -1, - InvalidMessageDeliveriesDecay: 0.9999, - }, - }, - }, - &PeerScoreThresholds{ - GossipThreshold: -10, - PublishThreshold: -100, - GraylistThreshold: -10000, - })) - - connectAll(t, hosts) - - err := psubs[0].RegisterTopicValidator("test", func(ctx context.Context, p peer.ID, msg *Message) ValidationResult { - // we ignore host1 and reject host2 - if p == hosts[1].ID() { - return ValidationIgnore - } - if p == hosts[2].ID() { - return ValidationReject - } - - return ValidationAccept - }) - if err != nil { - t.Fatal(err) - } - - sub, err := psubs[0].Subscribe("test") - if err != nil { - t.Fatal(err) - } - - time.Sleep(100 * time.Millisecond) - - expectNoMessage := func(sub *Subscription) { - ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) - defer cancel() - - m, err := sub.Next(ctx) - if err == nil { - t.Fatal("expected no message, but got ", string(m.Data)) - } - } - - psubs[1].Publish("test", []byte("i am not a walrus")) - psubs[2].Publish("test", []byte("i am not a walrus either")) - - // assert no messages - expectNoMessage(sub) - - // assert that peer1's score is still 0 (its message was ignored) while peer2 should have - // a negative score (its message got rejected) - res := make(chan float64, 1) - psubs[0].eval <- func() { - res <- psubs[0].rt.(*GossipSubRouter).score.Score(hosts[1].ID()) - } - score := <-res - if score != 0 { - t.Fatalf("expected 0 score for peer1, but got %f", score) - } - - psubs[0].eval <- func() { - res <- psubs[0].rt.(*GossipSubRouter).score.Score(hosts[2].ID()) - } - score = <-res - if score >= 0 { - t.Fatalf("expected negative score for peer2, but got %f", score) - } -} +// TODO pubSub check +//func TestGossipsubScoreValidatorEx(t *testing.T) { +// // this is a test that of the two message drop responses from a validator +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// +// hosts := getNetHosts(t, ctx, 3) +// psubs := getGossipsubs(ctx, hosts, +// WithPeerScore( +// &PeerScoreParams{ +// AppSpecificScore: func(p enode.ID) float64 { return 0 }, +// DecayInterval: time.Second, +// DecayToZero: 0.01, +// Topics: map[string]*TopicScoreParams{ +// "test": { +// TopicWeight: 1, +// TimeInMeshQuantum: time.Second, +// InvalidMessageDeliveriesWeight: -1, +// InvalidMessageDeliveriesDecay: 0.9999, +// }, +// }, +// }, +// &PeerScoreThresholds{ +// GossipThreshold: -10, +// PublishThreshold: -100, +// GraylistThreshold: -10000, +// })) +// +// connectAll(t, hosts) +// +// err := psubs[0].RegisterTopicValidator("test", func(ctx context.Context, p enode.ID, msg *Message) ValidationResult { +// // we ignore host1 and reject host2 +// if p == hosts[1].ID().ID() { +// return ValidationIgnore +// } +// if p == hosts[2].ID().ID() { +// return ValidationReject +// } +// +// return ValidationAccept +// }) +// if err != nil { +// t.Fatal(err) +// } +// +// sub, err := psubs[0].Subscribe("test") +// if err != nil { +// t.Fatal(err) +// } +// +// time.Sleep(100 * time.Millisecond) +// +// expectNoMessage := func(sub *Subscription) { +// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) +// defer cancel() +// +// m, err := sub.Next(ctx) +// if err == nil { +// t.Fatal("expected no message, but got ", string(m.Data)) +// } +// } +// +// psubs[1].Publish("test", []byte("i am not a walrus")) +// psubs[2].Publish("test", []byte("i am not a walrus either")) +// +// // assert no messages +// expectNoMessage(sub) +// +// // assert that peer1's score is still 0 (its message was ignored) while peer2 should have +// // a negative score (its message got rejected) +// res := make(chan float64, 1) +// psubs[0].eval <- func() { +// res <- psubs[0].rt.(*GossipSubRouter).score.Score(hosts[1].ID().ID()) +// } +// score := <-res +// if score != 0 { +// t.Fatalf("expected 0 score for peer1, but got %f", score) +// } +// +// psubs[0].eval <- func() { +// res <- psubs[0].rt.(*GossipSubRouter).score.Score(hosts[2].ID().ID()) +// } +// score = <-res +// if score >= 0 { +// t.Fatalf("expected negative score for peer2, but got %f", score) +// } +//} func TestGossipsubPiggybackControl(t *testing.T) { // this is a direct test of the piggybackControl function as we can't reliably @@ -1586,7 +1439,8 @@ func TestGossipsubPiggybackControl(t *testing.T) { h := NewTestHost() ps := getGossipsub(ctx, h) - blah := peer.ID("bogotr0n") + var blah enode.ID + crand.Read(blah[:]) res := make(chan *RPC, 1) ps.eval <- func() { @@ -1594,14 +1448,14 @@ func TestGossipsubPiggybackControl(t *testing.T) { test1 := "test1" test2 := "test2" test3 := "test3" - gs.mesh[test1] = make(map[peer.ID]struct{}) - gs.mesh[test2] = make(map[peer.ID]struct{}) + gs.mesh[test1] = make(map[enode.ID]struct{}) + gs.mesh[test2] = make(map[enode.ID]struct{}) gs.mesh[test1][blah] = struct{}{} - rpc := &RPC{RPC: pb.RPC{}} - gs.piggybackControl(blah, rpc, &pb.ControlMessage{ - Graft: []*pb.ControlGraft{{TopicID: &test1}, {TopicID: &test2}, {TopicID: &test3}}, - Prune: []*pb.ControlPrune{{TopicID: &test1}, {TopicID: &test2}, {TopicID: &test3}}, + rpc := &RPC{RPC: message.RPC{}} + gs.piggybackControl(blah, rpc, &message.ControlMessage{ + Graft: []*message.ControlGraft{{TopicID: &test1}, {TopicID: &test2}, {TopicID: &test3}}, + Prune: []*message.ControlPrune{{TopicID: &test1}, {TopicID: &test2}, {TopicID: &test3}}, }) res <- rpc } @@ -1641,8 +1495,8 @@ func TestGossipsubMultipleGraftTopics(t *testing.T) { secondTopic := "topic2" thirdTopic := "topic3" - firstPeer := hosts[0].ID() - secondPeer := hosts[1].ID() + firstPeer := hosts[0].ID().ID() + secondPeer := hosts[1].ID().ID() p2Sub := psubs[1] p1Router := psubs[0].rt.(*GossipSubRouter) @@ -1652,9 +1506,9 @@ func TestGossipsubMultipleGraftTopics(t *testing.T) { p2Sub.eval <- func() { // Add topics to second peer - p2Router.mesh[firstTopic] = map[peer.ID]struct{}{} - p2Router.mesh[secondTopic] = map[peer.ID]struct{}{} - p2Router.mesh[thirdTopic] = map[peer.ID]struct{}{} + p2Router.mesh[firstTopic] = map[enode.ID]struct{}{} + p2Router.mesh[secondTopic] = map[enode.ID]struct{}{} + p2Router.mesh[thirdTopic] = map[enode.ID]struct{}{} finChan <- struct{}{} } @@ -1662,9 +1516,9 @@ func TestGossipsubMultipleGraftTopics(t *testing.T) { // Send multiple GRAFT messages to second peer from // 1st peer - p1Router.sendGraftPrune(map[peer.ID][]string{ + p1Router.sendGraftPrune(map[enode.ID][]string{ secondPeer: {firstTopic, secondTopic, thirdTopic}, - }, map[peer.ID][]string{}, map[peer.ID]bool{}) + }, map[enode.ID][]string{}, map[enode.ID]bool{}) time.Sleep(time.Second * 1) @@ -1702,10 +1556,9 @@ func TestGossipsubOpportunisticGrafting(t *testing.T) { hosts := getNetHosts(t, ctx, 50) // pubsubs for the first 10 hosts psubs := getGossipsubs(ctx, hosts[:10], - WithFloodPublish(true), WithPeerScore( &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, AppSpecificWeight: 0, DecayInterval: time.Second, DecayToZero: 0.01, @@ -1732,11 +1585,13 @@ func TestGossipsubOpportunisticGrafting(t *testing.T) { // connect the real hosts with degree 5 connectSome(t, hosts[:10], 5) - // sybil squatters for the remaining 40 hosts + // TODO check + /*// sybil squatters for the remaining 40 hosts for _, h := range hosts[10:] { squatter := &sybilSquatter{h: h} + // TODO pubSub check h.SetStreamHandler(GossipSubID_v10, squatter.handleStream) - } + }*/ // connect all squatters to every real host for _, squatter := range hosts[10:] { @@ -1782,7 +1637,7 @@ func TestGossipsubOpportunisticGrafting(t *testing.T) { gs := ps.rt.(*GossipSubRouter) count := 0 for _, h := range hosts[:10] { - _, ok := gs.mesh["test"][h.ID()] + _, ok := gs.mesh["test"][h.ID().ID()] if ok { count++ } @@ -1797,14 +1652,15 @@ func TestGossipsubOpportunisticGrafting(t *testing.T) { } } -type sybilSquatter struct { +// TODO pubSub check +/*type sybilSquatter struct { h Host } -func (sq *sybilSquatter) handleStream(s network.Stream) { - defer s.Close() +func (sq *sybilSquatter) handleStream(s Stream) { + defer s.Close(nil) - os, err := sq.h.NewStream(context.Background(), s.Conn().RemotePeer(), GossipSubID_v10) + os, err := sq.h.NewStream(context.Background(), s.Conn().RemotePeer().ID(), GossipSubID_v10) if err != nil { panic(err) } @@ -1831,7 +1687,7 @@ func (sq *sybilSquatter) handleStream(s network.Stream) { return } } -} +}*/ func TestGossipsubPeerScoreInspect(t *testing.T) { // this test exercises the code path sof peer score inspection @@ -1855,7 +1711,7 @@ func TestGossipsubPeerScoreInspect(t *testing.T) { InvalidMessageDeliveriesDecay: 0.9999, }, }, - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, DecayInterval: time.Second, DecayToZero: 0.01, }, @@ -1887,7 +1743,7 @@ func TestGossipsubPeerScoreInspect(t *testing.T) { time.Sleep(time.Second + 200*time.Millisecond) - score2 := inspector.score(hosts[1].ID()) + score2 := inspector.score(hosts[1].ID().ID()) if score2 < 9 { t.Fatalf("expected score to be at least 9, instead got %f", score2) } @@ -1914,7 +1770,7 @@ func TestGossipsubPeerScoreResetTopicParams(t *testing.T) { InvalidMessageDeliveriesDecay: 0.9999, }, }, - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, DecayInterval: time.Second, DecayToZero: 0.01, }, @@ -1946,16 +1802,16 @@ func TestGossipsubPeerScoreResetTopicParams(t *testing.T) { type mockPeerScoreInspector struct { mx sync.Mutex - scores map[peer.ID]float64 + scores map[enode.ID]float64 } -func (ps *mockPeerScoreInspector) inspect(scores map[peer.ID]float64) { +func (ps *mockPeerScoreInspector) inspect(scores map[enode.ID]float64) { ps.mx.Lock() defer ps.mx.Unlock() ps.scores = scores } -func (ps *mockPeerScoreInspector) score(p peer.ID) float64 { +func (ps *mockPeerScoreInspector) score(p enode.ID) float64 { ps.mx.Lock() defer ps.mx.Unlock() return ps.scores[p] @@ -1968,18 +1824,25 @@ func TestGossipsubRPCFragmentation(t *testing.T) { hosts := getNetHosts(t, ctx, 2) ps := getGossipsub(ctx, hosts[0]) + ps1 := getGossipsub(ctx, hosts[1]) + // make a fake peer that requests everything through IWANT gossip - iwe := iwantEverything{h: hosts[1]} - iwe.h.SetStreamHandler(GossipSubID_v10, iwe.handleStream) + iwe := iwantEverything{h: hosts[1], pubSub: ps1} + iwe.h.SetStreamHandler(GossipSubID_v11, iwe.handleStream) connect(t, hosts[0], hosts[1]) + time.Sleep(time.Second) // have the real pubsub join the test topic - _, err := ps.Subscribe("test") + _, err := ps1.Subscribe("test") + if err != nil { + t.Fatal(err) + } + time.Sleep(time.Millisecond * 20) + _, err = ps.Subscribe("test") if err != nil { t.Fatal(err) } - // wait for the real pubsub to connect and try to graft to the faker time.Sleep(time.Second) @@ -2023,44 +1886,28 @@ func TestGossipsubRPCFragmentation(t *testing.T) { // test that large responses to IWANT requests are fragmented into multiple RPCs. type iwantEverything struct { h Host + pubSub *PubSub lk sync.Mutex rpcsWithMessages int msgsReceived int ihavesReceived int } -func (iwe *iwantEverything) handleStream(s network.Stream) { - defer s.Close() - - os, err := iwe.h.NewStream(context.Background(), s.Conn().RemotePeer(), GossipSubID_v10) - if err != nil { - panic(err) - } +func (iwe *iwantEverything) handleStream(s Stream) { msgIdsReceived := make(map[string]struct{}) gossipMsgIdsReceived := make(map[string]struct{}) - // send a subscription for test in the output stream to become candidate for gossip - r := protoio.NewDelimitedReader(s, 1<<20) - w := protoio.NewDelimitedWriter(os) - truth := true - topic := "test" - err = w.WriteMsg(&pb.RPC{Subscriptions: []*pb.RPC_SubOpts{{Subscribe: &truth, Topicid: &topic}}}) - if err != nil { - panic(err) - } - - var rpc pb.RPC for { - rpc.Reset() - err = r.ReadMsg(&rpc) + var rpc message.RPC + err := s.Read(&rpc) if err != nil { + panic(err) if err != io.EOF { - s.Reset() + s.Close(err) } return } - iwe.lk.Lock() if len(rpc.Publish) != 0 { iwe.rpcsWithMessages++ @@ -2076,14 +1923,14 @@ func (iwe *iwantEverything) handleStream(s network.Stream) { if rpc.Control != nil { // send a PRUNE for all grafts, so we don't get direct message deliveries - var prunes []*pb.ControlPrune + var prunes []*message.ControlPrune for _, graft := range rpc.Control.Graft { - prunes = append(prunes, &pb.ControlPrune{TopicID: graft.TopicID}) + prunes = append(prunes, &message.ControlPrune{TopicID: graft.TopicID}) } - var iwants []*pb.ControlIWant + var iwants []*message.ControlIWant for _, ihave := range rpc.Control.Ihave { - iwants = append(iwants, &pb.ControlIWant{MessageIDs: ihave.MessageIDs}) + iwants = append(iwants, &message.ControlIWant{MessageIDs: ihave.MessageIDs}) for _, msgId := range ihave.MessageIDs { if _, seen := gossipMsgIdsReceived[msgId]; !seen { iwe.ihavesReceived++ @@ -2093,24 +1940,23 @@ func (iwe *iwantEverything) handleStream(s network.Stream) { } out := rpcWithControl(nil, nil, iwants, nil, prunes) - err = w.WriteMsg(out) - if err != nil { - panic(err) - } + iwe.pubSub.rt.(*GossipSubRouter).sendRPC(s.Conn().RemotePeer().ID(), out) } iwe.lk.Unlock() } } func TestFragmentRPCFunction(t *testing.T) { - p := peer.ID("some-peer") + var nid enode.ID + crand.Read(nid[:]) + p := enode.SignNull(new(enr.Record), nid) topic := "test" rpc := &RPC{from: p} limit := 1024 - mkMsg := func(size int) *pb.Message { - msg := &pb.Message{} - msg.Data = make([]byte, size-4) // subtract the protobuf overhead, so msg.Size() returns requested size + mkMsg := func(size int) *message.Message { + msg := &message.Message{} + msg.Data = make([]byte, size) // subtract the protobuf overhead, so msg.Size() returns requested size rand.Read(msg.Data) return msg } @@ -2124,8 +1970,8 @@ func TestFragmentRPCFunction(t *testing.T) { } // it should not fragment if everything fits in one RPC - rpc.Publish = []*pb.Message{} - rpc.Publish = []*pb.Message{mkMsg(10), mkMsg(10)} + rpc.Publish = []*message.Message{} + rpc.Publish = []*message.Message{mkMsg(10), mkMsg(10)} results, err := fragmentRPC(rpc, limit) if err != nil { t.Fatal(err) @@ -2135,7 +1981,7 @@ func TestFragmentRPCFunction(t *testing.T) { } // if there's a message larger than the limit, we should fail - rpc.Publish = []*pb.Message{mkMsg(10), mkMsg(limit * 2)} + rpc.Publish = []*message.Message{mkMsg(10), mkMsg(limit * 2)} results, err = fragmentRPC(rpc, limit) if err == nil { t.Fatalf("expected an error if a message exceeds limit, got %d RPCs instead", len(results)) @@ -2145,13 +1991,13 @@ func TestFragmentRPCFunction(t *testing.T) { nMessages := 100 msgSize := 200 truth := true - rpc.Subscriptions = []*pb.RPC_SubOpts{ + rpc.Subscriptions = []*message.RPC_SubOpts{ { Subscribe: &truth, Topicid: &topic, }, } - rpc.Publish = make([]*pb.Message, nMessages) + rpc.Publish = make([]*message.Message, nMessages) for i := 0; i < nMessages; i++ { rpc.Publish[i] = mkMsg(msgSize) } @@ -2160,7 +2006,7 @@ func TestFragmentRPCFunction(t *testing.T) { t.Fatal(err) } ensureBelowLimit(results) - msgsPerRPC := limit / msgSize + msgsPerRPC := limit / (msgSize + len(enode.ZeroID)) expectedRPCs := nMessages / msgsPerRPC if len(results) != expectedRPCs { t.Fatalf("expected %d RPC messages in output, got %d", expectedRPCs, len(results)) @@ -2181,11 +2027,11 @@ func TestFragmentRPCFunction(t *testing.T) { // if we're fragmenting, and the input RPC has control messages, // the control messages should be in a separate RPC at the end // reuse RPC from prev test, but add a control message - rpc.Control = &pb.ControlMessage{ - Graft: []*pb.ControlGraft{{TopicID: &topic}}, - Prune: []*pb.ControlPrune{{TopicID: &topic}}, - Ihave: []*pb.ControlIHave{{MessageIDs: []string{"foo"}}}, - Iwant: []*pb.ControlIWant{{MessageIDs: []string{"bar"}}}, + rpc.Control = &message.ControlMessage{ + Graft: []*message.ControlGraft{{TopicID: &topic}}, + Prune: []*message.ControlPrune{{TopicID: &topic}}, + Ihave: []*message.ControlIHave{{MessageIDs: []string{"foo"}}}, + Iwant: []*message.ControlIWant{{MessageIDs: []string{"bar"}}}, } results, err = fragmentRPC(rpc, limit) if err != nil { @@ -2219,8 +2065,8 @@ func TestFragmentRPCFunction(t *testing.T) { nTopics := 5 // pretend we're subscribed to multiple topics and sending IHAVE / IWANTs for each messageIdSize := 32 msgsPerTopic := 100 // enough that a single IHAVE or IWANT will exceed the limit - rpc.Control.Ihave = make([]*pb.ControlIHave, nTopics) - rpc.Control.Iwant = make([]*pb.ControlIWant, nTopics) + rpc.Control.Ihave = make([]*message.ControlIHave, nTopics) + rpc.Control.Iwant = make([]*message.ControlIWant, nTopics) for i := 0; i < nTopics; i++ { messageIds := make([]string, msgsPerTopic) for m := 0; m < msgsPerTopic; m++ { @@ -2228,8 +2074,8 @@ func TestFragmentRPCFunction(t *testing.T) { rand.Read(mid) messageIds[m] = string(mid) } - rpc.Control.Ihave[i] = &pb.ControlIHave{MessageIDs: messageIds} - rpc.Control.Iwant[i] = &pb.ControlIWant{MessageIDs: messageIds} + rpc.Control.Ihave[i] = &message.ControlIHave{MessageIDs: messageIds} + rpc.Control.Iwant[i] = &message.ControlIWant{MessageIDs: messageIds} } results, err = fragmentRPC(rpc, limit) if err != nil { @@ -2247,8 +2093,8 @@ func TestFragmentRPCFunction(t *testing.T) { rpc.Reset() giantIdBytes := make([]byte, limit*2) rand.Read(giantIdBytes) - rpc.Control = &pb.ControlMessage{ - Iwant: []*pb.ControlIWant{ + rpc.Control = &message.ControlMessage{ + Iwant: []*message.ControlIWant{ {MessageIDs: []string{"hello", string(giantIdBytes)}}, }, } diff --git a/p2p/pubsub/interfaces.go b/p2p/pubsub/interfaces.go index 06c431cbf0..86accc7ace 100644 --- a/p2p/pubsub/interfaces.go +++ b/p2p/pubsub/interfaces.go @@ -2,6 +2,7 @@ package pubsub import ( "context" + "io" "time" "github.com/libp2p/go-libp2p-core/connmgr" @@ -36,7 +37,7 @@ type ProtocolID string // It is called Host because it is both Server and Client (and Peer // may be confusing). type Host interface { - // ID returns the (local) peer.ID associated with this Host + // ID returns the (local) enode.ID associated with this Host ID() *enode.Node // Peerstore returns the Host's repository of Peer Addresses and Keys. @@ -46,7 +47,7 @@ type Host interface { Network() Network // Connect ensures there is a connection between this host and the peer with - // given peer.ID. Connect will absorb the addresses in pi into its internal + // given enode.ID. Connect will absorb the addresses in pi into its internal // peerstore. If there is not an active connection, Connect will issue a // h.Network.Dial, and block until a connection is open, or an error is // returned. // TODO: Relay + NAT. @@ -63,6 +64,9 @@ type Host interface { // using a matching function for protocol selection. SetStreamHandlerMatch(ProtocolID, func(string) bool, StreamHandler) + // Get a handler + StreamHandler(pid ProtocolID) StreamHandler + // RemoveStreamHandler removes a handler on the mux that was set by // SetStreamHandler RemoveStreamHandler(pid ProtocolID) @@ -89,12 +93,16 @@ type StreamHandler func(Stream) // connections (see swarm pkg, and peerstream.Swarm). Connections // are encrypted with a TLS-like protocol. type Network interface { + io.Closer // ConnsToPeer returns the connections in this Netowrk for given peer. ConnsToPeer(p enode.ID) []Conn // Connectedness returns a state signaling connection capabilities Connectedness(enode.ID) Connectedness + // Conns returns the connections in this Netowrk + Conns() []Conn + // Notify/StopNotify register and unregister a notifiee for signals Notify(Notifiee) @@ -129,6 +137,7 @@ type Stream interface { // be useful to get information about the peer on the other side: // stream.Conn().RemotePeer() type Conn interface { + io.Closer // ID returns an identifier that uniquely identifies this Conn within this // host, during this run. Connection IDs may repeat across restarts. ID() string diff --git a/p2p/pubsub/mcache_test.go b/p2p/pubsub/mcache_test.go index e36c6b19f9..a987173e98 100644 --- a/p2p/pubsub/mcache_test.go +++ b/p2p/pubsub/mcache_test.go @@ -1,18 +1,19 @@ package pubsub import ( + crand "crypto/rand" "encoding/binary" "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" "testing" - - pb "github.com/libp2p/go-libp2p-pubsub/pb" ) func TestMessageCache(t *testing.T) { mcache := NewMessageCache(3, 5) msgID := DefaultMsgIdFn - msgs := make([]*pb.Message, 60) + msgs := make([]*message.Message, 60) for i := range msgs { msgs[i] = makeTestMessage(i) } @@ -153,15 +154,17 @@ func TestMessageCache(t *testing.T) { } -func makeTestMessage(n int) *pb.Message { +func makeTestMessage(n int) *message.Message { seqno := make([]byte, 8) binary.BigEndian.PutUint64(seqno, uint64(n)) data := []byte(fmt.Sprintf("%d", n)) topic := "test" - return &pb.Message{ + var from enode.ID + crand.Read(from[:]) + return &message.Message{ Data: data, Topic: &topic, - From: []byte("test"), + From: from, Seqno: seqno, } } diff --git a/p2p/pubsub/message/mesage.go b/p2p/pubsub/message/mesage.go index cb4c703c08..30bcac5199 100644 --- a/p2p/pubsub/message/mesage.go +++ b/p2p/pubsub/message/mesage.go @@ -1,6 +1,10 @@ package message -import "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +import ( + "errors" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/rlp" +) type RPC struct { Subscriptions []*RPC_SubOpts @@ -17,6 +21,26 @@ func IsEmpty(rpc *RPC) bool { return true } +func Filling(rpc *RPC) { + if rpc != nil { + if rpc.Subscriptions == nil { + rpc.Subscriptions = make([]*RPC_SubOpts, 0) + } + if rpc.Publish == nil { + rpc.Publish = make([]*Message, 0) + } + if rpc.Control == nil { + rpc.Control = &ControlMessage{} + } + } +} + +func (m *RPC) Reset() { + m.Subscriptions = make([]*RPC_SubOpts, 0) + m.Publish = make([]*Message, 0) + m.Control = &ControlMessage{} +} + func (m *RPC) Size() (n int) { if m == nil { return 0 @@ -226,6 +250,13 @@ func (m *ControlMessage) Size() (n int) { return n } +func (m *ControlMessage) Marshal() ([]byte, error) { + if m != nil { + return rlp.EncodeToBytes(m) + } + return nil, errors.New("serialized object is empty") +} + type ControlIHave struct { TopicID *string MessageIDs []string diff --git a/p2p/pubsub/peer_gater_test.go b/p2p/pubsub/peer_gater_test.go index 9ab9d45918..53d2680761 100644 --- a/p2p/pubsub/peer_gater_test.go +++ b/p2p/pubsub/peer_gater_test.go @@ -2,17 +2,21 @@ package pubsub import ( "context" + crand "crypto/rand" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" "testing" "time" - - "github.com/libp2p/go-libp2p-core/peer" ) func TestPeerGater(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - peerA := peer.ID("A") + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) + peerAip := "1.2.3.4" params := NewPeerGaterParams(.1, .9, .999) @@ -22,9 +26,9 @@ func TestPeerGater(t *testing.T) { } pg := newPeerGater(ctx, nil, params) - pg.getIP = func(p peer.ID) string { + pg.getIP = func(p enode.ID) string { switch p { - case peerA: + case peerAId: return peerAip default: return "" @@ -98,9 +102,9 @@ func TestPeerGater(t *testing.T) { t.Fatal("expected AcceptAll") } - pg.RemovePeer(peerA) + pg.RemovePeer(peerAId) pg.Lock() - _, ok := pg.peerStats[peerA] + _, ok := pg.peerStats[peerAId] pg.Unlock() if ok { t.Fatal("still have a stat record for peerA") diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 8dcf8d8dcf..2c02f0be3f 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "errors" "fmt" + "github.com/libp2p/go-libp2p-core/discovery" "math/rand" "sync" "sync/atomic" @@ -431,20 +432,20 @@ func WithBlacklist(b Blacklist) Option { } // WithDiscovery provides a discovery mechanism used to bootstrap and provide peers into PubSub -//func WithDiscovery(d discovery.Discovery, opts ...DiscoverOpt) Option { -// return func(p *PubSub) error { -// discoverOpts := defaultDiscoverOptions() -// for _, opt := range opts { -// err := opt(discoverOpts) -// if err != nil { -// return err -// } -// } -// p.disc.discovery = &pubSubDiscovery{Discovery: d, opts: discoverOpts.opts} -// p.disc.options = discoverOpts -// return nil -// } -//} +func WithDiscovery(d discovery.Discovery, opts ...DiscoverOpt) Option { + return func(p *PubSub) error { + discoverOpts := defaultDiscoverOptions() + for _, opt := range opts { + err := opt(discoverOpts) + if err != nil { + return err + } + } + p.disc.discovery = &pubSubDiscovery{Discovery: d, opts: discoverOpts.opts} + p.disc.options = discoverOpts + return nil + } +} // WithEventTracer provides a tracer for the pubsub system func WithEventTracer(tracer EventTracer) Option { diff --git a/p2p/pubsub/pubsub_common.go b/p2p/pubsub/pubsub_common.go index caf1c6c516..1baa254c27 100644 --- a/p2p/pubsub/pubsub_common.go +++ b/p2p/pubsub/pubsub_common.go @@ -54,9 +54,7 @@ func (p *PubSub) handleNewStream(s Stream) { p.inboundStreamsMx.Unlock() }() - //r := protoio.NewDelimitedReader(s, p.maxMessageSize) for { - rpc := new(RPC) if err := s.Read(&rpc.RPC); err != nil { log.Error("Read message error", "err", err) @@ -64,11 +62,11 @@ func (p *PubSub) handleNewStream(s Stream) { s.Close(err) return } - rpc.from = peer select { case p.incoming <- rpc: case <-p.ctx.Done(): + s.Close(nil) return } } @@ -100,7 +98,7 @@ func (p *PubSub) handleNewPeer(ctx context.Context, pid enode.ID, outgoing <-cha return } - go p.handleNewStream(s) + go p.host.StreamHandler(s.Protocol())(s) go p.handleSendingMessages(ctx, s, outgoing) //go p.handlePeerEOF(ctx, s) select { @@ -145,6 +143,7 @@ func (p *PubSub) handleSendingMessages(ctx context.Context, s Stream, outgoing < } if !message.IsEmpty(&rpc.RPC) { + message.Filling(&rpc.RPC) if err := s.Write(&rpc.RPC); err != nil { log.Error("Send message fail", "err", err) return diff --git a/p2p/pubsub/pubsub_test.go b/p2p/pubsub/pubsub_test.go index 534251d716..82fd4e2372 100644 --- a/p2p/pubsub/pubsub_test.go +++ b/p2p/pubsub/pubsub_test.go @@ -6,6 +6,7 @@ import ( crand "crypto/rand" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "github.com/AlayaNetwork/Alaya-Go/rlp" "github.com/libp2p/go-libp2p-core/connmgr" "math/rand" "sync" @@ -13,8 +14,83 @@ import ( "time" ) +type TestStream struct { + conn Conn + read chan []byte + write chan []byte +} + +func (s *TestStream) Protocol() ProtocolID { + return GossipSubID_v11 +} + +// Conn returns the connection this stream is part of. +func (s *TestStream) Conn() Conn { + return s.conn +} + +func (s *TestStream) Read(data interface{}) error { + outData := <-s.read + return rlp.DecodeBytes(outData, data) +} + +func (s *TestStream) Write(data interface{}) error { + enVal, err := rlp.EncodeToBytes(data) + if err != nil { + return err + } + s.write <- enVal + return nil +} + +func (s *TestStream) Close(err error) { +} + +type TestConn struct { + remote *enode.Node + stat Stat + + streams struct { + sync.Mutex + m map[Stream]struct{} + } +} + +func (c *TestConn) ID() string { + return c.remote.ID().String() +} + +func (c *TestConn) GetStreams() []Stream { + c.streams.Lock() + defer c.streams.Unlock() + streams := make([]Stream, 0, len(c.streams.m)) + for s := range c.streams.m { + streams = append(streams, s) + } + return streams +} + +func (c *TestConn) Stat() Stat { + return c.stat +} + +func (c *TestConn) RemotePeer() *enode.Node { + return c.remote +} + +func (c *TestConn) Close() error { + c.streams.Lock() + defer c.streams.Unlock() + for s := range c.streams.m { + s.Close(nil) + } + c.streams.m = nil + return nil +} + type TestNetwork struct { sync.RWMutex + m map[Notifiee]struct{} conns struct { sync.RWMutex @@ -23,21 +99,77 @@ type TestNetwork struct { } func (n *TestNetwork) ConnsToPeer(p enode.ID) []Conn { - return nil + n.conns.Lock() + defer n.conns.Unlock() + return n.conns.m[p] } func (n *TestNetwork) Connectedness(enode.ID) Connectedness { return NotConnected } -func (n *TestNetwork) Notify(Notifiee) { +func (n *TestNetwork) Notify(nf Notifiee) { + n.Lock() + n.m[nf] = struct{}{} + n.Unlock() +} + +// notifyAll sends a signal to all Notifiees +func (n *TestNetwork) NotifyAll(conn Conn) { + var wg sync.WaitGroup + + n.RLock() + wg.Add(len(n.m)) + for f := range n.m { + go func(f Notifiee) { + defer wg.Done() + f.Connected(n, conn) + }(f) + } + wg.Wait() + n.RUnlock() } func (n *TestNetwork) Peers() []enode.ID { + n.conns.Lock() + defer n.conns.Unlock() + var peers []enode.ID + for pid := range n.conns.m { + peers = append(peers, pid) + } + return peers +} + +func (n *TestNetwork) Close() error { + n.conns.Lock() + defer n.conns.Unlock() + for _, c := range n.conns.m { + if err := c[0].Close(); err != nil { + return err + } + } return nil } +func (n *TestNetwork) SetConn(p enode.ID, conn Conn) { + n.conns.RLock() + defer n.conns.RUnlock() + conns := make([]Conn, 0, 1) + conns = append(conns, conn) + n.conns.m[p] = conns +} + +func (n *TestNetwork) Conns() []Conn { + n.conns.RLock() + defer n.conns.RUnlock() + var connList []Conn + for _, cs := range n.conns.m { + connList = append(connList, cs...) + } + return connList +} + type TestHost struct { network *TestNetwork Node *enode.Node @@ -48,7 +180,9 @@ type TestHost struct { func NewTestHost() *TestHost { var p enode.ID crand.Read(p[:]) - netw := &TestNetwork{} + netw := &TestNetwork{ + m: make(map[Notifiee]struct{}), + } netw.conns.m = make(map[enode.ID][]Conn) h := &TestHost{ network: netw, @@ -80,6 +214,12 @@ func (h *TestHost) SetStreamHandler(pid ProtocolID, handler StreamHandler) { func (h *TestHost) SetStreamHandlerMatch(ProtocolID, func(string) bool, StreamHandler) { } +func (h *TestHost) StreamHandler(pid ProtocolID) StreamHandler { + h.handlerLock.Lock() + defer h.handlerLock.Unlock() + return h.handlers[pid] +} + func (h *TestHost) RemoveStreamHandler(pid ProtocolID) { h.handlerLock.Lock() defer h.handlerLock.Unlock() @@ -87,15 +227,38 @@ func (h *TestHost) RemoveStreamHandler(pid ProtocolID) { } func (h *TestHost) NewStream(ctx context.Context, p enode.ID, pids ...ProtocolID) (Stream, error) { - return nil, nil + return h.newStream(p, make(chan []byte), make(chan []byte)) +} + +func (h *TestHost) newStream(p enode.ID, read chan []byte, write chan []byte) (Stream, error) { + if conns := h.network.ConnsToPeer(p); len(conns) > 0 { + if streams := conns[0].GetStreams(); len(streams) > 0 { + return streams[0], nil + } + } + conn := &TestConn{ + remote: enode.SignNull(new(enr.Record), p), + stat: Stat{}, + } + conn.streams.m = make(map[Stream]struct{}) + + stream := &TestStream{ + conn: conn, + read: read, + write: write, + } + conn.streams.m[stream] = struct{}{} + + h.network.SetConn(p, conn) + return stream, nil } func (h *TestHost) Close() error { - return nil + return h.network.Close() } func (h *TestHost) ConnManager() connmgr.ConnManager { - return nil + return &connmgr.NullConnMgr{} } func checkMessageRouting(t *testing.T, topic string, pubs []*PubSub, subs []*Subscription) { @@ -125,9 +288,20 @@ func getNetHosts(t *testing.T, ctx context.Context, n int) []Host { } func connect(t *testing.T, a, b Host) { - err := b.Connect(context.Background(), a.ID().ID()) - if err != nil { + hostA := a.(*TestHost) + hostB := b.(*TestHost) + chan1 := make(chan []byte, 100) + chan2 := make(chan []byte, 100) + if stream, err := hostA.newStream(b.ID().ID(), chan2, chan1); err != nil { + t.Fatal(err) + } else { + hostA.network.NotifyAll(stream.Conn()) + } + + if stream, err := hostB.newStream(a.ID().ID(), chan1, chan2); err != nil { t.Fatal(err) + } else { + hostB.network.NotifyAll(stream.Conn()) } } @@ -167,22 +341,6 @@ func connectAll(t *testing.T, hosts []Host) { } } -func getPubsub(ctx context.Context, h Host, opts ...Option) *PubSub { - ps, err := NewGossipSub(ctx, h, opts...) - if err != nil { - panic(err) - } - return ps -} - -func getPubsubs(ctx context.Context, hs []Host, opts ...Option) []*PubSub { - var psubs []*PubSub - for _, h := range hs { - psubs = append(psubs, getPubsub(ctx, h, opts...)) - } - return psubs -} - func assertReceive(t *testing.T, ch *Subscription, exp []byte) { select { case msg := <-ch.ch: @@ -218,7 +376,7 @@ func assertPeerLists(t *testing.T, hosts []Host, ps *PubSub, has ...int) { } } -// See https://github.com/libp2p/go-libp2p-pubsub/issues/426 +//See https://github.com/libp2p/go-libp2p-pubsub/issues/426 func TestPubSubRemovesBlacklistedPeer(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) @@ -226,8 +384,8 @@ func TestPubSubRemovesBlacklistedPeer(t *testing.T) { bl := NewMapBlacklist() - psubs0 := getPubsub(ctx, hosts[0]) - psubs1 := getPubsub(ctx, hosts[1], WithBlacklist(bl)) + psubs0 := getGossipsub(ctx, hosts[0]) + psubs1 := getGossipsub(ctx, hosts[1], WithBlacklist(bl)) connect(t, hosts[0], hosts[1]) // Bad peer is blacklisted after it has connected. diff --git a/p2p/pubsub/score_params_test.go b/p2p/pubsub/score_params_test.go index 4493703fec..69fc189f42 100644 --- a/p2p/pubsub/score_params_test.go +++ b/p2p/pubsub/score_params_test.go @@ -1,11 +1,10 @@ package pubsub import ( + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "math" "testing" "time" - - "github.com/libp2p/go-libp2p-core/peer" ) func TestPeerScoreThresholdsValidation(t *testing.T) { @@ -146,7 +145,7 @@ func TestTopicScoreParamsValidation(t *testing.T) { } func TestPeerScoreParamsValidation(t *testing.T) { - appScore := func(peer.ID) float64 { return 0 } + appScore := func(enode.ID) float64 { return 0 } if (&PeerScoreParams{TopicScoreCap: -1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01}).validate() == nil { t.Fatal("expected validation error") diff --git a/p2p/pubsub/score_test.go b/p2p/pubsub/score_test.go index 849f8be555..2c64e3ad82 100644 --- a/p2p/pubsub/score_test.go +++ b/p2p/pubsub/score_test.go @@ -1,20 +1,21 @@ package pubsub import ( + crand "crypto/rand" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" "math" "net" "sync" "testing" "time" - - "github.com/libp2p/go-libp2p-core/peer" ) func TestScoreTimeInMesh(t *testing.T) { // Create parameters with reasonable default values mytopic := "mytopic" params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, Topics: make(map[string]*TopicScoreParams), } topicScoreParams := &TopicScoreParams{ @@ -25,24 +26,26 @@ func TestScoreTimeInMesh(t *testing.T) { } params.Topics[mytopic] = topicScoreParams - peerA := peer.ID("A") + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) // Peer score should start at 0 ps := newPeerScore(params) ps.AddPeer(peerA, "myproto") - aScore := ps.Score(peerA) + aScore := ps.Score(peerAId) if aScore != 0 { t.Fatal("expected score to start at zero") } // The time in mesh depends on how long the peer has been grafted - ps.Graft(peerA, mytopic) + ps.Graft(peerAId, mytopic) elapsed := topicScoreParams.TimeInMeshQuantum * 200 time.Sleep(elapsed) ps.refreshScores() - aScore = ps.Score(peerA) + aScore = ps.Score(peerAId) expected := topicScoreParams.TopicWeight * topicScoreParams.TimeInMeshWeight * float64(elapsed/topicScoreParams.TimeInMeshQuantum) if aScore < expected { t.Fatalf("Score: %f. Expected >= %f", aScore, expected) @@ -53,7 +56,7 @@ func TestScoreTimeInMeshCap(t *testing.T) { // Create parameters with reasonable default values mytopic := "mytopic" params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, Topics: make(map[string]*TopicScoreParams), } topicScoreParams := &TopicScoreParams{ @@ -65,17 +68,19 @@ func TestScoreTimeInMeshCap(t *testing.T) { params.Topics[mytopic] = topicScoreParams - peerA := peer.ID("A") + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) ps := newPeerScore(params) ps.AddPeer(peerA, "myproto") - ps.Graft(peerA, mytopic) + ps.Graft(peerAId, mytopic) elapsed := topicScoreParams.TimeInMeshQuantum * 40 time.Sleep(elapsed) // The time in mesh score has a cap ps.refreshScores() - aScore := ps.Score(peerA) + aScore := ps.Score(peerAId) expected := topicScoreParams.TopicWeight * topicScoreParams.TimeInMeshWeight * topicScoreParams.TimeInMeshCap variance := 0.5 if !withinVariance(aScore, expected, variance) { @@ -87,7 +92,7 @@ func TestScoreFirstMessageDeliveries(t *testing.T) { // Create parameters with reasonable default values mytopic := "mytopic" params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, Topics: make(map[string]*TopicScoreParams), } topicScoreParams := &TopicScoreParams{ @@ -99,11 +104,13 @@ func TestScoreFirstMessageDeliveries(t *testing.T) { } params.Topics[mytopic] = topicScoreParams - peerA := peer.ID("A") + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) ps := newPeerScore(params) ps.AddPeer(peerA, "myproto") - ps.Graft(peerA, mytopic) + ps.Graft(peerAId, mytopic) // deliver a bunch of messages from peer A nMessages := 100 @@ -116,7 +123,7 @@ func TestScoreFirstMessageDeliveries(t *testing.T) { } ps.refreshScores() - aScore := ps.Score(peerA) + aScore := ps.Score(peerAId) expected := topicScoreParams.TopicWeight * topicScoreParams.FirstMessageDeliveriesWeight * float64(nMessages) if aScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) @@ -127,7 +134,7 @@ func TestScoreFirstMessageDeliveriesCap(t *testing.T) { // Create parameters with reasonable default values mytopic := "mytopic" params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, Topics: make(map[string]*TopicScoreParams), } topicScoreParams := &TopicScoreParams{ @@ -139,11 +146,13 @@ func TestScoreFirstMessageDeliveriesCap(t *testing.T) { } params.Topics[mytopic] = topicScoreParams - peerA := peer.ID("A") + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) ps := newPeerScore(params) ps.AddPeer(peerA, "myproto") - ps.Graft(peerA, mytopic) + ps.Graft(peerAId, mytopic) // deliver a bunch of messages from peer A nMessages := 100 @@ -156,7 +165,7 @@ func TestScoreFirstMessageDeliveriesCap(t *testing.T) { } ps.refreshScores() - aScore := ps.Score(peerA) + aScore := ps.Score(peerAId) expected := topicScoreParams.TopicWeight * topicScoreParams.FirstMessageDeliveriesWeight * topicScoreParams.FirstMessageDeliveriesCap if aScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) @@ -167,7 +176,7 @@ func TestScoreFirstMessageDeliveriesDecay(t *testing.T) { // Create parameters with reasonable default values mytopic := "mytopic" params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, Topics: make(map[string]*TopicScoreParams), } topicScoreParams := &TopicScoreParams{ @@ -179,11 +188,13 @@ func TestScoreFirstMessageDeliveriesDecay(t *testing.T) { } params.Topics[mytopic] = topicScoreParams - peerA := peer.ID("A") + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) ps := newPeerScore(params) ps.AddPeer(peerA, "myproto") - ps.Graft(peerA, mytopic) + ps.Graft(peerAId, mytopic) // deliver a bunch of messages from peer A nMessages := 100 @@ -196,7 +207,7 @@ func TestScoreFirstMessageDeliveriesDecay(t *testing.T) { } ps.refreshScores() - aScore := ps.Score(peerA) + aScore := ps.Score(peerAId) expected := topicScoreParams.TopicWeight * topicScoreParams.FirstMessageDeliveriesWeight * topicScoreParams.FirstMessageDeliveriesDecay * float64(nMessages) if aScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) @@ -208,7 +219,7 @@ func TestScoreFirstMessageDeliveriesDecay(t *testing.T) { ps.refreshScores() expected *= topicScoreParams.FirstMessageDeliveriesDecay } - aScore = ps.Score(peerA) + aScore = ps.Score(peerAId) if aScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) } @@ -218,7 +229,7 @@ func TestScoreMeshMessageDeliveries(t *testing.T) { // Create parameters with reasonable default values mytopic := "mytopic" params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, Topics: make(map[string]*TopicScoreParams), } topicScoreParams := &TopicScoreParams{ @@ -241,21 +252,27 @@ func TestScoreMeshMessageDeliveries(t *testing.T) { // peer C delivers outside the delivery window. // we expect peers A and B to have a score of zero, since all other parameter weights are zero. // Peer C should have a negative score. - peerA := peer.ID("A") - peerB := peer.ID("B") - peerC := peer.ID("C") - peers := []peer.ID{peerA, peerB, peerC} + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) + var peerBId enode.ID + crand.Read(peerBId[:]) + peerB := enode.SignNull(new(enr.Record), peerBId) + var peerCId enode.ID + crand.Read(peerCId[:]) + peerC := enode.SignNull(new(enr.Record), peerCId) + peers := []*enode.Node{peerA, peerB, peerC} ps := newPeerScore(params) for _, p := range peers { ps.AddPeer(p, "myproto") - ps.Graft(p, mytopic) + ps.Graft(p.ID(), mytopic) } // assert that nobody has been penalized yet for not delivering messages before activation time ps.refreshScores() for _, p := range peers { - score := ps.Score(p) + score := ps.Score(p.ID()) if score < 0 { t.Fatalf("expected no mesh delivery penalty before activation time, got score %f", score) } @@ -288,9 +305,9 @@ func TestScoreMeshMessageDeliveries(t *testing.T) { wg.Wait() ps.refreshScores() - aScore := ps.Score(peerA) - bScore := ps.Score(peerB) - cScore := ps.Score(peerC) + aScore := ps.Score(peerAId) + bScore := ps.Score(peerBId) + cScore := ps.Score(peerCId) if aScore < 0 { t.Fatalf("Expected non-negative score for peer A, got %f", aScore) } @@ -307,11 +324,12 @@ func TestScoreMeshMessageDeliveries(t *testing.T) { } } -func TestScoreMeshMessageDeliveriesDecay(t *testing.T) { +// TODO pubSub check +/*func TestScoreMeshMessageDeliveriesDecay(t *testing.T) { // Create parameters with reasonable default values mytopic := "mytopic" params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, Topics: make(map[string]*TopicScoreParams), } topicScoreParams := &TopicScoreParams{ @@ -329,11 +347,13 @@ func TestScoreMeshMessageDeliveriesDecay(t *testing.T) { params.Topics[mytopic] = topicScoreParams - peerA := peer.ID("A") + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) ps := newPeerScore(params) ps.AddPeer(peerA, "myproto") - ps.Graft(peerA, mytopic) + ps.Graft(peerAId, mytopic) // deliver messages from peer A nMessages := 40 @@ -347,7 +367,7 @@ func TestScoreMeshMessageDeliveriesDecay(t *testing.T) { // we should have a positive score, since we delivered more messages than the threshold ps.refreshScores() - aScore := ps.Score(peerA) + aScore := ps.Score(peerAId) if aScore < 0 { t.Fatalf("Expected non-negative score for peer A, got %f", aScore) } @@ -358,7 +378,7 @@ func TestScoreMeshMessageDeliveriesDecay(t *testing.T) { ps.refreshScores() decayedDeliveryCount *= topicScoreParams.MeshMessageDeliveriesDecay } - aScore = ps.Score(peerA) + aScore = ps.Score(peerAId) // the penalty is the difference between the threshold and the (decayed) mesh deliveries, squared. deficit := topicScoreParams.MeshMessageDeliveriesThreshold - decayedDeliveryCount penalty := deficit * deficit @@ -366,94 +386,99 @@ func TestScoreMeshMessageDeliveriesDecay(t *testing.T) { if aScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) } -} - -func TestScoreMeshFailurePenalty(t *testing.T) { - // Create parameters with reasonable default values - mytopic := "mytopic" - params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, - Topics: make(map[string]*TopicScoreParams), - } - - // the mesh failure penalty is applied when a peer is pruned while their - // mesh deliveries are under the threshold. - // for this test, we set the mesh delivery threshold, but set - // MeshMessageDeliveriesWeight to zero, so the only affect on the score - // is from the mesh failure penalty - topicScoreParams := &TopicScoreParams{ - TopicWeight: 1, - MeshFailurePenaltyWeight: -1, - MeshFailurePenaltyDecay: 1.0, - - MeshMessageDeliveriesActivation: 0, - MeshMessageDeliveriesWindow: 10 * time.Millisecond, - MeshMessageDeliveriesThreshold: 20, - MeshMessageDeliveriesCap: 100, - MeshMessageDeliveriesDecay: 1.0, - - MeshMessageDeliveriesWeight: 0, - FirstMessageDeliveriesWeight: 0, - TimeInMeshQuantum: time.Second, - } - - params.Topics[mytopic] = topicScoreParams - - peerA := peer.ID("A") - peerB := peer.ID("B") - peers := []peer.ID{peerA, peerB} - - ps := newPeerScore(params) - for _, p := range peers { - ps.AddPeer(p, "myproto") - ps.Graft(p, mytopic) - } - - // deliver messages from peer A. peer B does nothing - nMessages := 100 - for i := 0; i < nMessages; i++ { - pbMsg := makeTestMessage(i) - pbMsg.Topic = &mytopic - msg := Message{ReceivedFrom: peerA, Message: pbMsg} - ps.ValidateMessage(&msg) - ps.DeliverMessage(&msg) - } - - // peers A and B should both have zero scores, since the failure penalty hasn't been applied yet - ps.refreshScores() - aScore := ps.Score(peerA) - bScore := ps.Score(peerB) - if aScore != 0 { - t.Errorf("expected peer A to have score 0.0, got %f", aScore) - } - if bScore != 0 { - t.Errorf("expected peer B to have score 0.0, got %f", bScore) - } - - // prune peer B to apply the penalty - ps.Prune(peerB, mytopic) - ps.refreshScores() - aScore = ps.Score(peerA) - bScore = ps.Score(peerB) - - if aScore != 0 { - t.Errorf("expected peer A to have score 0.0, got %f", aScore) - } - - // penalty calculation is the same as for MeshMessageDeliveries, but multiplied by MeshFailurePenaltyWeight - // instead of MeshMessageDeliveriesWeight - penalty := topicScoreParams.MeshMessageDeliveriesThreshold * topicScoreParams.MeshMessageDeliveriesThreshold - expected := topicScoreParams.TopicWeight * topicScoreParams.MeshFailurePenaltyWeight * penalty - if bScore != expected { - t.Fatalf("Score: %f. Expected %f", bScore, expected) - } -} +}*/ + +// TODO pubSub check +//func TestScoreMeshFailurePenalty(t *testing.T) { +// // Create parameters with reasonable default values +// mytopic := "mytopic" +// params := &PeerScoreParams{ +// AppSpecificScore: func(enode.ID) float64 { return 0 }, +// Topics: make(map[string]*TopicScoreParams), +// } +// +// // the mesh failure penalty is applied when a peer is pruned while their +// // mesh deliveries are under the threshold. +// // for this test, we set the mesh delivery threshold, but set +// // MeshMessageDeliveriesWeight to zero, so the only affect on the score +// // is from the mesh failure penalty +// topicScoreParams := &TopicScoreParams{ +// TopicWeight: 1, +// MeshFailurePenaltyWeight: -1, +// MeshFailurePenaltyDecay: 1.0, +// +// MeshMessageDeliveriesActivation: 0, +// MeshMessageDeliveriesWindow: 10 * time.Millisecond, +// MeshMessageDeliveriesThreshold: 20, +// MeshMessageDeliveriesCap: 100, +// MeshMessageDeliveriesDecay: 1.0, +// +// MeshMessageDeliveriesWeight: 0, +// FirstMessageDeliveriesWeight: 0, +// TimeInMeshQuantum: time.Second, +// } +// +// params.Topics[mytopic] = topicScoreParams +// +// var peerAId enode.ID +// crand.Read(peerAId[:]) +// peerA := enode.SignNull(new(enr.Record), peerAId) +// var peerBId enode.ID +// crand.Read(peerBId[:]) +// peerB := enode.SignNull(new(enr.Record), peerBId) +// peers := []*enode.Node{peerA, peerB} +// +// ps := newPeerScore(params) +// for _, p := range peers { +// ps.AddPeer(p, "myproto") +// ps.Graft(p.ID(), mytopic) +// } +// +// // deliver messages from peer A. peer B does nothing +// nMessages := 100 +// for i := 0; i < nMessages; i++ { +// pbMsg := makeTestMessage(i) +// pbMsg.Topic = &mytopic +// msg := Message{ReceivedFrom: peerA, Message: pbMsg} +// ps.ValidateMessage(&msg) +// ps.DeliverMessage(&msg) +// } +// +// // peers A and B should both have zero scores, since the failure penalty hasn't been applied yet +// ps.refreshScores() +// aScore := ps.Score(peerAId) +// bScore := ps.Score(peerBId) +// if aScore != 0 { +// t.Errorf("expected peer A to have score 0.0, got %f", aScore) +// } +// if bScore != 0 { +// t.Errorf("expected peer B to have score 0.0, got %f", bScore) +// } +// +// // prune peer B to apply the penalty +// ps.Prune(peerBId, mytopic) +// ps.refreshScores() +// aScore = ps.Score(peerAId) +// bScore = ps.Score(peerBId) +// +// if aScore != 0 { +// t.Errorf("expected peer A to have score 0.0, got %f", aScore) +// } +// +// // penalty calculation is the same as for MeshMessageDeliveries, but multiplied by MeshFailurePenaltyWeight +// // instead of MeshMessageDeliveriesWeight +// penalty := topicScoreParams.MeshMessageDeliveriesThreshold * topicScoreParams.MeshMessageDeliveriesThreshold +// expected := topicScoreParams.TopicWeight * topicScoreParams.MeshFailurePenaltyWeight * penalty +// if bScore != expected { +// t.Fatalf("Score: %f. Expected %f", bScore, expected) +// } +//} func TestScoreInvalidMessageDeliveries(t *testing.T) { // Create parameters with reasonable default values mytopic := "mytopic" params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, Topics: make(map[string]*TopicScoreParams), } topicScoreParams := &TopicScoreParams{ @@ -464,11 +489,13 @@ func TestScoreInvalidMessageDeliveries(t *testing.T) { } params.Topics[mytopic] = topicScoreParams - peerA := peer.ID("A") + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) ps := newPeerScore(params) ps.AddPeer(peerA, "myproto") - ps.Graft(peerA, mytopic) + ps.Graft(peerAId, mytopic) nMessages := 100 for i := 0; i < nMessages; i++ { @@ -479,7 +506,7 @@ func TestScoreInvalidMessageDeliveries(t *testing.T) { } ps.refreshScores() - aScore := ps.Score(peerA) + aScore := ps.Score(peerAId) expected := topicScoreParams.TopicWeight * topicScoreParams.InvalidMessageDeliveriesWeight * float64(nMessages*nMessages) if aScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) @@ -490,7 +517,7 @@ func TestScoreInvalidMessageDeliveriesDecay(t *testing.T) { // Create parameters with reasonable default values mytopic := "mytopic" params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, Topics: make(map[string]*TopicScoreParams), } topicScoreParams := &TopicScoreParams{ @@ -501,11 +528,13 @@ func TestScoreInvalidMessageDeliveriesDecay(t *testing.T) { } params.Topics[mytopic] = topicScoreParams - peerA := peer.ID("A") + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) ps := newPeerScore(params) ps.AddPeer(peerA, "myproto") - ps.Graft(peerA, mytopic) + ps.Graft(peerAId, mytopic) nMessages := 100 for i := 0; i < nMessages; i++ { @@ -516,7 +545,7 @@ func TestScoreInvalidMessageDeliveriesDecay(t *testing.T) { } ps.refreshScores() - aScore := ps.Score(peerA) + aScore := ps.Score(peerAId) expected := topicScoreParams.TopicWeight * topicScoreParams.InvalidMessageDeliveriesWeight * math.Pow(topicScoreParams.InvalidMessageDeliveriesDecay*float64(nMessages), 2) if aScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) @@ -527,7 +556,7 @@ func TestScoreInvalidMessageDeliveriesDecay(t *testing.T) { ps.refreshScores() expected *= math.Pow(topicScoreParams.InvalidMessageDeliveriesDecay, 2) } - aScore = ps.Score(peerA) + aScore = ps.Score(peerAId) if aScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) } @@ -537,7 +566,7 @@ func TestScoreRejectMessageDeliveries(t *testing.T) { // this tests adds coverage for the dark corners of rejection tracing mytopic := "mytopic" params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, Topics: make(map[string]*TopicScoreParams), } topicScoreParams := &TopicScoreParams{ @@ -548,8 +577,13 @@ func TestScoreRejectMessageDeliveries(t *testing.T) { } params.Topics[mytopic] = topicScoreParams - peerA := peer.ID("A") - peerB := peer.ID("B") + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) + + var peerBId enode.ID + crand.Read(peerBId[:]) + peerB := enode.SignNull(new(enr.Record), peerBId) ps := newPeerScore(params) ps.AddPeer(peerA, "myproto") @@ -565,7 +599,7 @@ func TestScoreRejectMessageDeliveries(t *testing.T) { ps.RejectMessage(&msg, RejectBlacklistedSource) ps.RejectMessage(&msg, RejectValidationQueueFull) - aScore := ps.Score(peerA) + aScore := ps.Score(peerAId) expected := 0.0 if aScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) @@ -579,13 +613,13 @@ func TestScoreRejectMessageDeliveries(t *testing.T) { ps.RejectMessage(&msg, RejectValidationThrottled) ps.DuplicateMessage(&msg2) - aScore = ps.Score(peerA) + aScore = ps.Score(peerAId) expected = 0.0 if aScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) } - bScore := ps.Score(peerB) + bScore := ps.Score(peerBId) expected = 0.0 if bScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) @@ -604,13 +638,13 @@ func TestScoreRejectMessageDeliveries(t *testing.T) { ps.RejectMessage(&msg, RejectValidationIgnored) ps.DuplicateMessage(&msg2) - aScore = ps.Score(peerA) + aScore = ps.Score(peerAId) expected = 0.0 if aScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) } - bScore = ps.Score(peerB) + bScore = ps.Score(peerBId) expected = 0.0 if bScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) @@ -628,13 +662,13 @@ func TestScoreRejectMessageDeliveries(t *testing.T) { ps.RejectMessage(&msg, RejectValidationFailed) ps.DuplicateMessage(&msg2) - aScore = ps.Score(peerA) + aScore = ps.Score(peerAId) expected = -1.0 if aScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) } - bScore = ps.Score(peerB) + bScore = ps.Score(peerBId) expected = -1.0 if bScore != expected { t.Fatalf("Score: %f. Expected %f", bScore, expected) @@ -652,13 +686,13 @@ func TestScoreRejectMessageDeliveries(t *testing.T) { ps.DuplicateMessage(&msg2) ps.RejectMessage(&msg, RejectValidationFailed) - aScore = ps.Score(peerA) + aScore = ps.Score(peerAId) expected = -4.0 if aScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) } - bScore = ps.Score(peerB) + bScore = ps.Score(peerBId) expected = -4.0 if bScore != expected { t.Fatalf("Score: %f. Expected %f", bScore, expected) @@ -671,21 +705,23 @@ func TestScoreApplicationScore(t *testing.T) { var appScoreValue float64 params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return appScoreValue }, + AppSpecificScore: func(enode.ID) float64 { return appScoreValue }, AppSpecificWeight: 0.5, Topics: make(map[string]*TopicScoreParams), } - peerA := peer.ID("A") + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) ps := newPeerScore(params) ps.AddPeer(peerA, "myproto") - ps.Graft(peerA, mytopic) + ps.Graft(peerAId, mytopic) for i := -100; i < 100; i++ { appScoreValue = float64(i) ps.refreshScores() - aScore := ps.Score(peerA) + aScore := ps.Score(peerAId) expected := float64(i) * params.AppSpecificWeight if aScore != expected { t.Errorf("expected peer score to equal app-specific score %f, got %f", expected, aScore) @@ -698,35 +734,46 @@ func TestScoreIPColocation(t *testing.T) { mytopic := "mytopic" params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, IPColocationFactorThreshold: 1, IPColocationFactorWeight: -1, Topics: make(map[string]*TopicScoreParams), } - peerA := peer.ID("A") - peerB := peer.ID("B") - peerC := peer.ID("C") - peerD := peer.ID("D") - peers := []peer.ID{peerA, peerB, peerC, peerD} + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) + + var peerBId enode.ID + crand.Read(peerBId[:]) + peerB := enode.SignNull(new(enr.Record), peerBId) + + var peerCId enode.ID + crand.Read(peerCId[:]) + peerC := enode.SignNull(new(enr.Record), peerCId) + + var peerDId enode.ID + crand.Read(peerDId[:]) + peerD := enode.SignNull(new(enr.Record), peerDId) + peers := []*enode.Node{peerA, peerB, peerC, peerD} ps := newPeerScore(params) for _, p := range peers { ps.AddPeer(p, "myproto") - ps.Graft(p, mytopic) + ps.Graft(p.ID(), mytopic) } // peerA should have no penalty, but B, C, and D should be penalized for sharing an IP - setIPsForPeer(t, ps, peerA, "1.2.3.4") - setIPsForPeer(t, ps, peerB, "2.3.4.5") - setIPsForPeer(t, ps, peerC, "2.3.4.5", "3.4.5.6") - setIPsForPeer(t, ps, peerD, "2.3.4.5") + setIPsForPeer(t, ps, peerAId, "1.2.3.4") + setIPsForPeer(t, ps, peerBId, "2.3.4.5") + setIPsForPeer(t, ps, peerCId, "2.3.4.5", "3.4.5.6") + setIPsForPeer(t, ps, peerDId, "2.3.4.5") ps.refreshScores() - aScore := ps.Score(peerA) - bScore := ps.Score(peerB) - cScore := ps.Score(peerC) - dScore := ps.Score(peerD) + aScore := ps.Score(peerAId) + bScore := ps.Score(peerBId) + cScore := ps.Score(peerCId) + dScore := ps.Score(peerDId) if aScore != 0 { t.Errorf("expected peer A to have score 0.0, got %f", aScore) @@ -753,36 +800,47 @@ func TestScoreIPColocationWhitelist(t *testing.T) { } params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, IPColocationFactorThreshold: 1, IPColocationFactorWeight: -1, IPColocationFactorWhitelist: []*net.IPNet{ipNet}, Topics: make(map[string]*TopicScoreParams), } - peerA := peer.ID("A") - peerB := peer.ID("B") - peerC := peer.ID("C") - peerD := peer.ID("D") - peers := []peer.ID{peerA, peerB, peerC, peerD} + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) + + var peerBId enode.ID + crand.Read(peerBId[:]) + peerB := enode.SignNull(new(enr.Record), peerBId) + + var peerCId enode.ID + crand.Read(peerCId[:]) + peerC := enode.SignNull(new(enr.Record), peerCId) + + var peerDId enode.ID + crand.Read(peerDId[:]) + peerD := enode.SignNull(new(enr.Record), peerDId) + peers := []*enode.Node{peerA, peerB, peerC, peerD} ps := newPeerScore(params) for _, p := range peers { ps.AddPeer(p, "myproto") - ps.Graft(p, mytopic) + ps.Graft(p.ID(), mytopic) } // peerA should have no penalty, but B, C, and D should be penalized for sharing an IP - setIPsForPeer(t, ps, peerA, "1.2.3.4") - setIPsForPeer(t, ps, peerB, "2.3.4.5") - setIPsForPeer(t, ps, peerC, "2.3.4.5", "3.4.5.6") - setIPsForPeer(t, ps, peerD, "2.3.4.5") + setIPsForPeer(t, ps, peerAId, "1.2.3.4") + setIPsForPeer(t, ps, peerBId, "2.3.4.5") + setIPsForPeer(t, ps, peerCId, "2.3.4.5", "3.4.5.6") + setIPsForPeer(t, ps, peerDId, "2.3.4.5") ps.refreshScores() - aScore := ps.Score(peerA) - bScore := ps.Score(peerB) - cScore := ps.Score(peerC) - dScore := ps.Score(peerD) + aScore := ps.Score(peerAId) + bScore := ps.Score(peerBId) + cScore := ps.Score(peerCId) + dScore := ps.Score(peerDId) if aScore != 0 { t.Errorf("expected peer A to have score 0.0, got %f", aScore) @@ -804,18 +862,20 @@ func TestScoreIPColocationWhitelist(t *testing.T) { func TestScoreBehaviourPenalty(t *testing.T) { params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, BehaviourPenaltyWeight: -1, BehaviourPenaltyDecay: 0.99, } - peerA := peer.ID("A") + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) var ps *peerScore // first check AddPenalty on a nil peerScore - ps.AddPenalty(peerA, 1) - aScore := ps.Score(peerA) + ps.AddPenalty(peerAId, 1) + aScore := ps.Score(peerAId) if aScore != 0 { t.Errorf("expected peer score to be 0, got %f", aScore) } @@ -824,8 +884,8 @@ func TestScoreBehaviourPenalty(t *testing.T) { ps = newPeerScore(params) // next AddPenalty on a non-existent peer - ps.AddPenalty(peerA, 1) - aScore = ps.Score(peerA) + ps.AddPenalty(peerAId, 1) + aScore = ps.Score(peerAId) if aScore != 0 { t.Errorf("expected peer score to be 0, got %f", aScore) } @@ -833,26 +893,26 @@ func TestScoreBehaviourPenalty(t *testing.T) { // add the peer and test penalties ps.AddPeer(peerA, "myproto") - aScore = ps.Score(peerA) + aScore = ps.Score(peerAId) if aScore != 0 { t.Errorf("expected peer score to be 0, got %f", aScore) } - ps.AddPenalty(peerA, 1) - aScore = ps.Score(peerA) + ps.AddPenalty(peerAId, 1) + aScore = ps.Score(peerAId) if aScore != -1 { t.Errorf("expected peer score to be -1, got %f", aScore) } - ps.AddPenalty(peerA, 1) - aScore = ps.Score(peerA) + ps.AddPenalty(peerAId, 1) + aScore = ps.Score(peerAId) if aScore != -4 { t.Errorf("expected peer score to be -4, got %f", aScore) } ps.refreshScores() - aScore = ps.Score(peerA) + aScore = ps.Score(peerAId) if aScore != -3.9204 { t.Errorf("expected peer score to be -3.9204, got %f", aScore) } @@ -863,32 +923,34 @@ func TestScoreRetention(t *testing.T) { mytopic := "mytopic" params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return -1000 }, + AppSpecificScore: func(enode.ID) float64 { return -1000 }, AppSpecificWeight: 1.0, Topics: make(map[string]*TopicScoreParams), RetainScore: time.Second, } - peerA := peer.ID("A") + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) ps := newPeerScore(params) ps.AddPeer(peerA, "myproto") - ps.Graft(peerA, mytopic) + ps.Graft(peerAId, mytopic) // score should equal -1000 (app specific score) expected := float64(-1000) ps.refreshScores() - aScore := ps.Score(peerA) + aScore := ps.Score(peerAId) if aScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) } // disconnect & wait half of RetainScore time. should still have negative score - ps.RemovePeer(peerA) + ps.RemovePeer(peerAId) delay := params.RetainScore / time.Duration(2) time.Sleep(delay) ps.refreshScores() - aScore = ps.Score(peerA) + aScore = ps.Score(peerAId) if aScore != expected { t.Fatalf("Score: %f. Expected %f", aScore, expected) } @@ -896,7 +958,7 @@ func TestScoreRetention(t *testing.T) { // wait remaining time (plus a little slop) and the score should reset to zero time.Sleep(delay + (50 * time.Millisecond)) ps.refreshScores() - aScore = ps.Score(peerA) + aScore = ps.Score(peerAId) if aScore != 0 { t.Fatalf("Score: %f. Expected 0.0", aScore) } @@ -906,7 +968,7 @@ func TestScoreRecapTopicParams(t *testing.T) { // Create parameters with reasonable default values mytopic := "mytopic" params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, Topics: make(map[string]*TopicScoreParams), } topicScoreParams := &TopicScoreParams{ @@ -933,14 +995,19 @@ func TestScoreRecapTopicParams(t *testing.T) { // peer C delivers outside the delivery window. // we expect peers A and B to have a score of zero, since all other parameter weights are zero. // Peer C should have a negative score. - peerA := peer.ID("A") - peerB := peer.ID("B") - peers := []peer.ID{peerA, peerB} + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) + + var peerBId enode.ID + crand.Read(peerBId[:]) + peerB := enode.SignNull(new(enr.Record), peerBId) + peers := []*enode.Node{peerA, peerB} ps := newPeerScore(params) for _, p := range peers { ps.AddPeer(p, "myproto") - ps.Graft(p, mytopic) + ps.Graft(p.ID(), mytopic) } // deliver a bunch of messages from peer A, with duplicates within the window from peer B, @@ -958,13 +1025,13 @@ func TestScoreRecapTopicParams(t *testing.T) { // check that the FirstMessageDeliveries for peerA and MeshMessageDeliveries for PeerB is // at 100 - if ps.peerStats[peerA].topics[mytopic].firstMessageDeliveries != 100 { - t.Fatalf("expected 100 FirstMessageDeliveries for peerA, but got %f", ps.peerStats[peerA].topics[mytopic].firstMessageDeliveries) + if ps.peerStats[peerAId].topics[mytopic].firstMessageDeliveries != 100 { + t.Fatalf("expected 100 FirstMessageDeliveries for peerA, but got %f", ps.peerStats[peerAId].topics[mytopic].firstMessageDeliveries) } // check that the MeshMessageDeliveries for peerB and MeshMessageDeliveries for PeerB is // at 100 - if ps.peerStats[peerB].topics[mytopic].meshMessageDeliveries != 100 { - t.Fatalf("expected 100 MeshMessageDeliveries for peerB, but got %f", ps.peerStats[peerB].topics[mytopic].meshMessageDeliveries) + if ps.peerStats[peerBId].topics[mytopic].meshMessageDeliveries != 100 { + t.Fatalf("expected 100 MeshMessageDeliveries for peerB, but got %f", ps.peerStats[peerBId].topics[mytopic].meshMessageDeliveries) } // reset the topic paramaters recapping the deliveries counters @@ -991,11 +1058,11 @@ func TestScoreRecapTopicParams(t *testing.T) { } // verify that the counters got recapped - if ps.peerStats[peerA].topics[mytopic].firstMessageDeliveries != 50 { - t.Fatalf("expected 50 FirstMessageDeliveries for peerA, but got %f", ps.peerStats[peerA].topics[mytopic].firstMessageDeliveries) + if ps.peerStats[peerAId].topics[mytopic].firstMessageDeliveries != 50 { + t.Fatalf("expected 50 FirstMessageDeliveries for peerA, but got %f", ps.peerStats[peerAId].topics[mytopic].firstMessageDeliveries) } - if ps.peerStats[peerB].topics[mytopic].meshMessageDeliveries != 50 { - t.Fatalf("expected 50 MeshMessageDeliveries for peerB, but got %f", ps.peerStats[peerB].topics[mytopic].meshMessageDeliveries) + if ps.peerStats[peerBId].topics[mytopic].meshMessageDeliveries != 50 { + t.Fatalf("expected 50 MeshMessageDeliveries for peerB, but got %f", ps.peerStats[peerBId].topics[mytopic].meshMessageDeliveries) } } @@ -1003,7 +1070,7 @@ func TestScoreResetTopicParams(t *testing.T) { // Create parameters with reasonable default values mytopic := "mytopic" params := &PeerScoreParams{ - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, Topics: make(map[string]*TopicScoreParams), } topicScoreParams := &TopicScoreParams{ @@ -1020,7 +1087,9 @@ func TestScoreResetTopicParams(t *testing.T) { // peer C delivers outside the delivery window. // we expect peers A and B to have a score of zero, since all other parameter weights are zero. // Peer C should have a negative score. - peerA := peer.ID("A") + var peerAId enode.ID + crand.Read(peerAId[:]) + peerA := enode.SignNull(new(enr.Record), peerAId) ps := newPeerScore(params) ps.AddPeer(peerA, "myproto") @@ -1036,7 +1105,7 @@ func TestScoreResetTopicParams(t *testing.T) { } // check the topic score - aScore := ps.Score(peerA) + aScore := ps.Score(peerAId) if aScore != -10000 { t.Fatalf("expected a -10000 score, but got %f instead", aScore) } @@ -1055,7 +1124,7 @@ func TestScoreResetTopicParams(t *testing.T) { } // verify the topic score was adjusted - aScore = ps.Score(peerA) + aScore = ps.Score(peerAId) if aScore != -100000 { t.Fatalf("expected a -1000000 score, but got %f instead", aScore) } @@ -1069,7 +1138,7 @@ func withinVariance(score float64, expected float64, variance float64) bool { } // hack to set IPs for a peer without having to spin up real hosts with shared IPs -func setIPsForPeer(t *testing.T, ps *peerScore, p peer.ID, ips ...string) { +func setIPsForPeer(t *testing.T, ps *peerScore, p enode.ID, ips ...string) { t.Helper() ps.setIPs(p, ips, []string{}) pstats, ok := ps.peerStats[p] diff --git a/p2p/pubsub/sign_test.go b/p2p/pubsub/sign_test.go index 8ec791d06b..eb3395df4a 100644 --- a/p2p/pubsub/sign_test.go +++ b/p2p/pubsub/sign_test.go @@ -1,43 +1,43 @@ package pubsub -import ( - "testing" - - pb "github.com/libp2p/go-libp2p-pubsub/pb" - - "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/peer" -) - -func TestSigning(t *testing.T) { - privk, _, err := crypto.GenerateKeyPair(crypto.RSA, 2048) - if err != nil { - t.Fatal(err) - } - testSignVerify(t, privk) - - privk, _, err = crypto.GenerateKeyPair(crypto.Ed25519, 0) - if err != nil { - t.Fatal(err) - } - testSignVerify(t, privk) -} - -func testSignVerify(t *testing.T, privk crypto.PrivKey) { - id, err := peer.IDFromPublicKey(privk.GetPublic()) - if err != nil { - t.Fatal(err) - } - topic := "foo" - m := pb.Message{ - Data: []byte("abc"), - Topic: &topic, - From: []byte(id), - Seqno: []byte("123"), - } - signMessage(id, privk, &m) - err = verifyMessageSignature(&m) - if err != nil { - t.Fatal(err) - } -} +//import ( +// "crypto/ecdsa" +// "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +// "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" +// "testing" +// +// pb "github.com/libp2p/go-libp2p-pubsub/pb" +// +// "github.com/libp2p/go-libp2p-core/crypto" +// "github.com/libp2p/go-libp2p-core/peer" +//) +// +//func TestSigning(t *testing.T) { +// privk, _, err := crypto.GenerateKeyPair(crypto.RSA, 2048) +// if err != nil { +// t.Fatal(err) +// } +// testSignVerify(t, privk) +// +// privk, _, err = crypto.GenerateKeyPair(crypto.Ed25519, 0) +// if err != nil { +// t.Fatal(err) +// } +// testSignVerify(t, privk) +//} +// +//func testSignVerify(t *testing.T, privk *ecdsa.PublicKey) { +// id := enode.PubkeyToIDV4(privk) +// topic := "foo" +// m := message.Message{ +// Data: []byte("abc"), +// Topic: &topic, +// From: id, +// Seqno: []byte("123"), +// } +// signMessage(id, privk, &m) +// err = verifyMessageSignature(&m) +// if err != nil { +// t.Fatal(err) +// } +//} diff --git a/p2p/pubsub/subscription_filter_test.go b/p2p/pubsub/subscription_filter_test.go index a2413714ad..0310498310 100644 --- a/p2p/pubsub/subscription_filter_test.go +++ b/p2p/pubsub/subscription_filter_test.go @@ -1,210 +1,216 @@ package pubsub -import ( - "context" - "regexp" - "testing" - "time" - - pb "github.com/libp2p/go-libp2p-pubsub/pb" - - "github.com/libp2p/go-libp2p-core/peer" -) - -func TestBasicSubscriptionFilter(t *testing.T) { - peerA := peer.ID("A") - - topic1 := "test1" - topic2 := "test2" - topic3 := "test3" - yes := true - subs := []*pb.RPC_SubOpts{ - &pb.RPC_SubOpts{ - Topicid: &topic1, - Subscribe: &yes, - }, - &pb.RPC_SubOpts{ - Topicid: &topic2, - Subscribe: &yes, - }, - &pb.RPC_SubOpts{ - Topicid: &topic3, - Subscribe: &yes, - }, - } - - filter := NewAllowlistSubscriptionFilter(topic1, topic2) - canSubscribe := filter.CanSubscribe(topic1) - if !canSubscribe { - t.Fatal("expected allowed subscription") - } - canSubscribe = filter.CanSubscribe(topic2) - if !canSubscribe { - t.Fatal("expected allowed subscription") - } - canSubscribe = filter.CanSubscribe(topic3) - if canSubscribe { - t.Fatal("expected disallowed subscription") - } - allowedSubs, err := filter.FilterIncomingSubscriptions(peerA, subs) - if err != nil { - t.Fatal(err) - } - if len(allowedSubs) != 2 { - t.Fatalf("expected 2 allowed subscriptions but got %d", len(allowedSubs)) - } - for _, sub := range allowedSubs { - if sub.GetTopicid() == topic3 { - t.Fatal("unpexted subscription to test3") - } - } - - limitFilter := WrapLimitSubscriptionFilter(filter, 2) - _, err = limitFilter.FilterIncomingSubscriptions(peerA, subs) - if err != ErrTooManySubscriptions { - t.Fatal("expected rejection because of too many subscriptions") - } - - filter = NewRegexpSubscriptionFilter(regexp.MustCompile("test[12]")) - canSubscribe = filter.CanSubscribe(topic1) - if !canSubscribe { - t.Fatal("expected allowed subscription") - } - canSubscribe = filter.CanSubscribe(topic2) - if !canSubscribe { - t.Fatal("expected allowed subscription") - } - canSubscribe = filter.CanSubscribe(topic3) - if canSubscribe { - t.Fatal("expected disallowed subscription") - } - allowedSubs, err = filter.FilterIncomingSubscriptions(peerA, subs) - if err != nil { - t.Fatal(err) - } - if len(allowedSubs) != 2 { - t.Fatalf("expected 2 allowed subscriptions but got %d", len(allowedSubs)) - } - for _, sub := range allowedSubs { - if sub.GetTopicid() == topic3 { - t.Fatal("unexpected subscription") - } - } - - limitFilter = WrapLimitSubscriptionFilter(filter, 2) - _, err = limitFilter.FilterIncomingSubscriptions(peerA, subs) - if err != ErrTooManySubscriptions { - t.Fatal("expected rejection because of too many subscriptions") - } - -} - -func TestSubscriptionFilterDeduplication(t *testing.T) { - peerA := peer.ID("A") - - topic1 := "test1" - topic2 := "test2" - topic3 := "test3" - yes := true - no := false - subs := []*pb.RPC_SubOpts{ - &pb.RPC_SubOpts{ - Topicid: &topic1, - Subscribe: &yes, - }, - &pb.RPC_SubOpts{ - Topicid: &topic1, - Subscribe: &yes, - }, - - &pb.RPC_SubOpts{ - Topicid: &topic2, - Subscribe: &yes, - }, - &pb.RPC_SubOpts{ - Topicid: &topic2, - Subscribe: &no, - }, - &pb.RPC_SubOpts{ - Topicid: &topic3, - Subscribe: &yes, - }, - } - - filter := NewAllowlistSubscriptionFilter(topic1, topic2) - allowedSubs, err := filter.FilterIncomingSubscriptions(peerA, subs) - if err != nil { - t.Fatal(err) - } - if len(allowedSubs) != 1 { - t.Fatalf("expected 2 allowed subscriptions but got %d", len(allowedSubs)) - } - for _, sub := range allowedSubs { - if sub.GetTopicid() == topic3 || sub.GetTopicid() == topic2 { - t.Fatal("unexpected subscription") - } - } -} - -func TestSubscriptionFilterRPC(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - hosts := getNetHosts(t, ctx, 2) - ps1 := getPubsub(ctx, hosts[0], WithSubscriptionFilter(NewAllowlistSubscriptionFilter("test1", "test2"))) - ps2 := getPubsub(ctx, hosts[1], WithSubscriptionFilter(NewAllowlistSubscriptionFilter("test2", "test3"))) - - _ = mustSubscribe(t, ps1, "test1") - _ = mustSubscribe(t, ps1, "test2") - _ = mustSubscribe(t, ps2, "test2") - _ = mustSubscribe(t, ps2, "test3") - - // check the rejection as well - _, err := ps1.Join("test3") - if err == nil { - t.Fatal("expected subscription error") - } - - connect(t, hosts[0], hosts[1]) - - time.Sleep(time.Second) - - var sub1, sub2, sub3 bool - ready := make(chan struct{}) - - ps1.eval <- func() { - _, sub1 = ps1.topics["test1"][hosts[1].ID()] - _, sub2 = ps1.topics["test2"][hosts[1].ID()] - _, sub3 = ps1.topics["test3"][hosts[1].ID()] - ready <- struct{}{} - } - <-ready - - if sub1 { - t.Fatal("expected no subscription for test1") - } - if !sub2 { - t.Fatal("expected subscription for test2") - } - if sub3 { - t.Fatal("expected no subscription for test1") - } - - ps2.eval <- func() { - _, sub1 = ps2.topics["test1"][hosts[0].ID()] - _, sub2 = ps2.topics["test2"][hosts[0].ID()] - _, sub3 = ps2.topics["test3"][hosts[0].ID()] - ready <- struct{}{} - } - <-ready - - if sub1 { - t.Fatal("expected no subscription for test1") - } - if !sub2 { - t.Fatal("expected subscription for test1") - } - if sub3 { - t.Fatal("expected no subscription for test1") - } -} +//import ( +// "context" +// crand "crypto/rand" +// "github.com/AlayaNetwork/Alaya-Go/p2p/enode" +// "github.com/AlayaNetwork/Alaya-Go/p2p/enr" +// "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" +// "regexp" +// "testing" +// "time" +// +// pb "github.com/libp2p/go-libp2p-pubsub/pb" +// +// "github.com/libp2p/go-libp2p-core/peer" +//) +// +//func TestBasicSubscriptionFilter(t *testing.T) { +// var peerAId enode.ID +// crand.Read(peerAId[:]) +// peerA := enode.SignNull(new(enr.Record), peerAId) +// +// topic1 := "test1" +// topic2 := "test2" +// topic3 := "test3" +// yes := true +// subs := []*message.RPC_SubOpts{ +// &pb.RPC_SubOpts{ +// Topicid: &topic1, +// Subscribe: &yes, +// }, +// &pb.RPC_SubOpts{ +// Topicid: &topic2, +// Subscribe: &yes, +// }, +// &pb.RPC_SubOpts{ +// Topicid: &topic3, +// Subscribe: &yes, +// }, +// } +// +// filter := NewAllowlistSubscriptionFilter(topic1, topic2) +// canSubscribe := filter.CanSubscribe(topic1) +// if !canSubscribe { +// t.Fatal("expected allowed subscription") +// } +// canSubscribe = filter.CanSubscribe(topic2) +// if !canSubscribe { +// t.Fatal("expected allowed subscription") +// } +// canSubscribe = filter.CanSubscribe(topic3) +// if canSubscribe { +// t.Fatal("expected disallowed subscription") +// } +// allowedSubs, err := filter.FilterIncomingSubscriptions(peerA, subs) +// if err != nil { +// t.Fatal(err) +// } +// if len(allowedSubs) != 2 { +// t.Fatalf("expected 2 allowed subscriptions but got %d", len(allowedSubs)) +// } +// for _, sub := range allowedSubs { +// if sub.GetTopicid() == topic3 { +// t.Fatal("unpexted subscription to test3") +// } +// } +// +// limitFilter := WrapLimitSubscriptionFilter(filter, 2) +// _, err = limitFilter.FilterIncomingSubscriptions(peerA, subs) +// if err != ErrTooManySubscriptions { +// t.Fatal("expected rejection because of too many subscriptions") +// } +// +// filter = NewRegexpSubscriptionFilter(regexp.MustCompile("test[12]")) +// canSubscribe = filter.CanSubscribe(topic1) +// if !canSubscribe { +// t.Fatal("expected allowed subscription") +// } +// canSubscribe = filter.CanSubscribe(topic2) +// if !canSubscribe { +// t.Fatal("expected allowed subscription") +// } +// canSubscribe = filter.CanSubscribe(topic3) +// if canSubscribe { +// t.Fatal("expected disallowed subscription") +// } +// allowedSubs, err = filter.FilterIncomingSubscriptions(peerA, subs) +// if err != nil { +// t.Fatal(err) +// } +// if len(allowedSubs) != 2 { +// t.Fatalf("expected 2 allowed subscriptions but got %d", len(allowedSubs)) +// } +// for _, sub := range allowedSubs { +// if sub.GetTopicid() == topic3 { +// t.Fatal("unexpected subscription") +// } +// } +// +// limitFilter = WrapLimitSubscriptionFilter(filter, 2) +// _, err = limitFilter.FilterIncomingSubscriptions(peerA, subs) +// if err != ErrTooManySubscriptions { +// t.Fatal("expected rejection because of too many subscriptions") +// } +// +//} +// +//func TestSubscriptionFilterDeduplication(t *testing.T) { +// peerA := peer.ID("A") +// +// topic1 := "test1" +// topic2 := "test2" +// topic3 := "test3" +// yes := true +// no := false +// subs := []*pb.RPC_SubOpts{ +// &pb.RPC_SubOpts{ +// Topicid: &topic1, +// Subscribe: &yes, +// }, +// &pb.RPC_SubOpts{ +// Topicid: &topic1, +// Subscribe: &yes, +// }, +// +// &pb.RPC_SubOpts{ +// Topicid: &topic2, +// Subscribe: &yes, +// }, +// &pb.RPC_SubOpts{ +// Topicid: &topic2, +// Subscribe: &no, +// }, +// &pb.RPC_SubOpts{ +// Topicid: &topic3, +// Subscribe: &yes, +// }, +// } +// +// filter := NewAllowlistSubscriptionFilter(topic1, topic2) +// allowedSubs, err := filter.FilterIncomingSubscriptions(peerA, subs) +// if err != nil { +// t.Fatal(err) +// } +// if len(allowedSubs) != 1 { +// t.Fatalf("expected 2 allowed subscriptions but got %d", len(allowedSubs)) +// } +// for _, sub := range allowedSubs { +// if sub.GetTopicid() == topic3 || sub.GetTopicid() == topic2 { +// t.Fatal("unexpected subscription") +// } +// } +//} +// +//func TestSubscriptionFilterRPC(t *testing.T) { +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// +// hosts := getNetHosts(t, ctx, 2) +// ps1 := getPubsub(ctx, hosts[0], WithSubscriptionFilter(NewAllowlistSubscriptionFilter("test1", "test2"))) +// ps2 := getPubsub(ctx, hosts[1], WithSubscriptionFilter(NewAllowlistSubscriptionFilter("test2", "test3"))) +// +// _ = mustSubscribe(t, ps1, "test1") +// _ = mustSubscribe(t, ps1, "test2") +// _ = mustSubscribe(t, ps2, "test2") +// _ = mustSubscribe(t, ps2, "test3") +// +// // check the rejection as well +// _, err := ps1.Join("test3") +// if err == nil { +// t.Fatal("expected subscription error") +// } +// +// connect(t, hosts[0], hosts[1]) +// +// time.Sleep(time.Second) +// +// var sub1, sub2, sub3 bool +// ready := make(chan struct{}) +// +// ps1.eval <- func() { +// _, sub1 = ps1.topics["test1"][hosts[1].ID()] +// _, sub2 = ps1.topics["test2"][hosts[1].ID()] +// _, sub3 = ps1.topics["test3"][hosts[1].ID()] +// ready <- struct{}{} +// } +// <-ready +// +// if sub1 { +// t.Fatal("expected no subscription for test1") +// } +// if !sub2 { +// t.Fatal("expected subscription for test2") +// } +// if sub3 { +// t.Fatal("expected no subscription for test1") +// } +// +// ps2.eval <- func() { +// _, sub1 = ps2.topics["test1"][hosts[0].ID()] +// _, sub2 = ps2.topics["test2"][hosts[0].ID()] +// _, sub3 = ps2.topics["test3"][hosts[0].ID()] +// ready <- struct{}{} +// } +// <-ready +// +// if sub1 { +// t.Fatal("expected no subscription for test1") +// } +// if !sub2 { +// t.Fatal("expected subscription for test1") +// } +// if sub3 { +// t.Fatal("expected no subscription for test1") +// } +//} diff --git a/p2p/pubsub/tag_tracer_test.go b/p2p/pubsub/tag_tracer_test.go index 567d211098..a75449b775 100644 --- a/p2p/pubsub/tag_tracer_test.go +++ b/p2p/pubsub/tag_tracer_test.go @@ -1,6 +1,5 @@ package pubsub -// //import ( // crand "crypto/rand" // "fmt" @@ -10,10 +9,6 @@ package pubsub // // "github.com/benbjohnson/clock" // connmgr "github.com/libp2p/go-libp2p-connmgr" -// connmgri "github.com/libp2p/go-libp2p-core/connmgr" -// "github.com/libp2p/go-libp2p-core/peer" -// -// pb "github.com/libp2p/go-libp2p-pubsub/pb" //) // //func TestTagTracerMeshTags(t *testing.T) { diff --git a/p2p/pubsub/topic_test.go b/p2p/pubsub/topic_test.go index 2169d35859..55f59f636b 100644 --- a/p2p/pubsub/topic_test.go +++ b/p2p/pubsub/topic_test.go @@ -4,12 +4,11 @@ import ( "bytes" "context" "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "math/rand" "sync" "testing" "time" - - "github.com/libp2p/go-libp2p-core/peer" ) func getTopics(psubs []*PubSub, topicID string, opts ...TopicOpt) []*Topic { @@ -95,7 +94,7 @@ func testTopicCloseWithOpenResource(t *testing.T, openResource func(topic *Topic const numHosts = 1 topicID := "foobar" hosts := getNetHosts(t, ctx, numHosts) - ps := getPubsub(ctx, hosts[0]) + ps := getGossipsub(ctx, hosts[0]) // Try create and cancel topic topic, err := ps.Join(topicID) @@ -128,99 +127,100 @@ func testTopicCloseWithOpenResource(t *testing.T, openResource func(topic *Topic } } -func TestTopicReuse(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - const numHosts = 2 - topicID := "foobar" - hosts := getNetHosts(t, ctx, numHosts) - - sender := getPubsub(ctx, hosts[0], WithDiscovery(&dummyDiscovery{})) - receiver := getPubsub(ctx, hosts[1]) - - connectAll(t, hosts) - - // Sender creates topic - sendTopic, err := sender.Join(topicID) - if err != nil { - t.Fatal(err) - } - - // Receiver creates and subscribes to the topic - receiveTopic, err := receiver.Join(topicID) - if err != nil { - t.Fatal(err) - } - - sub, err := receiveTopic.Subscribe() - if err != nil { - t.Fatal(err) - } - - firstMsg := []byte("1") - if err := sendTopic.Publish(ctx, firstMsg, WithReadiness(MinTopicSize(1))); err != nil { - t.Fatal(err) - } - - msg, err := sub.Next(ctx) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(msg.GetData(), firstMsg) { - t.Fatal("received incorrect message") - } - - if err := sendTopic.Close(); err != nil { - t.Fatal(err) - } - - // Recreate the same topic - newSendTopic, err := sender.Join(topicID) - if err != nil { - t.Fatal(err) - } - - // Try sending data with original topic - illegalSend := []byte("illegal") - if err := sendTopic.Publish(ctx, illegalSend); err != ErrTopicClosed { - t.Fatal(err) - } - - timeoutCtx, timeoutCancel := context.WithTimeout(ctx, time.Second*2) - defer timeoutCancel() - msg, err = sub.Next(timeoutCtx) - if err != context.DeadlineExceeded { - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(msg.GetData(), illegalSend) { - t.Fatal("received incorrect message from illegal topic") - } - t.Fatal("received message sent by illegal topic") - } - timeoutCancel() - - // Try cancelling the new topic by using the original topic - if err := sendTopic.Close(); err != nil { - t.Fatal(err) - } - - secondMsg := []byte("2") - if err := newSendTopic.Publish(ctx, secondMsg); err != nil { - t.Fatal(err) - } - - timeoutCtx, timeoutCancel = context.WithTimeout(ctx, time.Second*2) - defer timeoutCancel() - msg, err = sub.Next(timeoutCtx) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(msg.GetData(), secondMsg) { - t.Fatal("received incorrect message") - } -} +// TODO discovery +//func TestTopicReuse(t *testing.T) { +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// +// const numHosts = 2 +// topicID := "foobar" +// hosts := getNetHosts(t, ctx, numHosts) +// +// sender := getGossipsub(ctx, hosts[0], WithDiscovery(&dummyDiscovery{})) +// receiver := getGossipsub(ctx, hosts[1]) +// +// connectAll(t, hosts) +// +// // Sender creates topic +// sendTopic, err := sender.Join(topicID) +// if err != nil { +// t.Fatal(err) +// } +// +// // Receiver creates and subscribes to the topic +// receiveTopic, err := receiver.Join(topicID) +// if err != nil { +// t.Fatal(err) +// } +// +// sub, err := receiveTopic.Subscribe() +// if err != nil { +// t.Fatal(err) +// } +// +// firstMsg := []byte("1") +// if err := sendTopic.Publish(ctx, firstMsg, WithReadiness(MinTopicSize(1))); err != nil { +// t.Fatal(err) +// } +// +// msg, err := sub.Next(ctx) +// if err != nil { +// t.Fatal(err) +// } +// if !bytes.Equal(msg.GetData(), firstMsg) { +// t.Fatal("received incorrect message") +// } +// +// if err := sendTopic.Close(); err != nil { +// t.Fatal(err) +// } +// +// // Recreate the same topic +// newSendTopic, err := sender.Join(topicID) +// if err != nil { +// t.Fatal(err) +// } +// +// // Try sending data with original topic +// illegalSend := []byte("illegal") +// if err := sendTopic.Publish(ctx, illegalSend); err != ErrTopicClosed { +// t.Fatal(err) +// } +// +// timeoutCtx, timeoutCancel := context.WithTimeout(ctx, time.Second*2) +// defer timeoutCancel() +// msg, err = sub.Next(timeoutCtx) +// if err != context.DeadlineExceeded { +// if err != nil { +// t.Fatal(err) +// } +// if !bytes.Equal(msg.GetData(), illegalSend) { +// t.Fatal("received incorrect message from illegal topic") +// } +// t.Fatal("received message sent by illegal topic") +// } +// timeoutCancel() +// +// // Try cancelling the new topic by using the original topic +// if err := sendTopic.Close(); err != nil { +// t.Fatal(err) +// } +// +// secondMsg := []byte("2") +// if err := newSendTopic.Publish(ctx, secondMsg); err != nil { +// t.Fatal(err) +// } +// +// timeoutCtx, timeoutCancel = context.WithTimeout(ctx, time.Second*2) +// defer timeoutCancel() +// msg, err = sub.Next(timeoutCtx) +// if err != nil { +// t.Fatal(err) +// } +// if !bytes.Equal(msg.GetData(), secondMsg) { +// t.Fatal("received incorrect message") +// } +//} func TestTopicEventHandlerCancel(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) @@ -229,7 +229,7 @@ func TestTopicEventHandlerCancel(t *testing.T) { const numHosts = 5 topicID := "foobar" hosts := getNetHosts(t, ctx, numHosts) - ps := getPubsub(ctx, hosts[0]) + ps := getGossipsub(ctx, hosts[0]) // Try create and cancel topic topic, err := ps.Join(topicID) @@ -261,11 +261,11 @@ func TestSubscriptionJoinNotification(t *testing.T) { const numLateSubscribers = 10 const numHosts = 20 hosts := getNetHosts(t, ctx, numHosts) - topics := getTopics(getPubsubs(ctx, hosts), "foobar") + topics := getTopics(getGossipsubs(ctx, hosts), "foobar") evts := getTopicEvts(topics) subs := make([]*Subscription, numHosts) - topicPeersFound := make([]map[peer.ID]struct{}, numHosts) + topicPeersFound := make([]map[enode.ID]struct{}, numHosts) // Have some peers subscribe earlier than other peers. // This exercises whether we get subscription notifications from @@ -295,11 +295,11 @@ func TestSubscriptionJoinNotification(t *testing.T) { wg := sync.WaitGroup{} for i := 0; i < numHosts; i++ { - peersFound := make(map[peer.ID]struct{}) + peersFound := make(map[enode.ID]struct{}) topicPeersFound[i] = peersFound evt := evts[i] wg.Add(1) - go func(peersFound map[peer.ID]struct{}) { + go func(peersFound map[enode.ID]struct{}) { defer wg.Done() for len(peersFound) < numHosts-1 { event, err := evt.NextPeerEvent(ctx) @@ -327,12 +327,12 @@ func TestSubscriptionLeaveNotification(t *testing.T) { const numHosts = 20 hosts := getNetHosts(t, ctx, numHosts) - psubs := getPubsubs(ctx, hosts) + psubs := getGossipsubs(ctx, hosts) topics := getTopics(psubs, "foobar") evts := getTopicEvts(topics) subs := make([]*Subscription, numHosts) - topicPeersFound := make([]map[peer.ID]struct{}, numHosts) + topicPeersFound := make([]map[enode.ID]struct{}, numHosts) // Subscribe all peers and wait until they've all been found for i, topic := range topics { @@ -350,11 +350,11 @@ func TestSubscriptionLeaveNotification(t *testing.T) { wg := sync.WaitGroup{} for i := 0; i < numHosts; i++ { - peersFound := make(map[peer.ID]struct{}) + peersFound := make(map[enode.ID]struct{}) topicPeersFound[i] = peersFound evt := evts[i] wg.Add(1) - go func(peersFound map[peer.ID]struct{}) { + go func(peersFound map[enode.ID]struct{}) { defer wg.Done() for len(peersFound) < numHosts-1 { event, err := evt.NextPeerEvent(ctx) @@ -377,10 +377,10 @@ func TestSubscriptionLeaveNotification(t *testing.T) { // Test removing peers and verifying that they cause events subs[1].Cancel() - hosts[2].Close() + subs[2].Cancel() psubs[0].BlacklistPeer(hosts[3].ID()) - leavingPeers := make(map[peer.ID]struct{}) + leavingPeers := make(map[enode.ID]struct{}) for len(leavingPeers) < 3 { event, err := evts[0].NextPeerEvent(ctx) if err != nil { @@ -391,13 +391,13 @@ func TestSubscriptionLeaveNotification(t *testing.T) { } } - if _, ok := leavingPeers[hosts[1].ID()]; !ok { + if _, ok := leavingPeers[hosts[1].ID().ID()]; !ok { t.Fatal(fmt.Errorf("canceling subscription did not cause a leave event")) } - if _, ok := leavingPeers[hosts[2].ID()]; !ok { + if _, ok := leavingPeers[hosts[2].ID().ID()]; !ok { t.Fatal(fmt.Errorf("closing host did not cause a leave event")) } - if _, ok := leavingPeers[hosts[3].ID()]; !ok { + if _, ok := leavingPeers[hosts[3].ID().ID()]; !ok { t.Fatal(fmt.Errorf("blacklisting peer did not cause a leave event")) } } @@ -412,11 +412,11 @@ func TestSubscriptionManyNotifications(t *testing.T) { const numHosts = 33 hosts := getNetHosts(t, ctx, numHosts) - topics := getTopics(getPubsubs(ctx, hosts), topic) + topics := getTopics(getGossipsubs(ctx, hosts), topic) evts := getTopicEvts(topics) subs := make([]*Subscription, numHosts) - topicPeersFound := make([]map[peer.ID]struct{}, numHosts) + topicPeersFound := make([]map[enode.ID]struct{}, numHosts) // Subscribe all peers except one and wait until they've all been found for i := 1; i < numHosts; i++ { @@ -434,11 +434,11 @@ func TestSubscriptionManyNotifications(t *testing.T) { wg := sync.WaitGroup{} for i := 1; i < numHosts; i++ { - peersFound := make(map[peer.ID]struct{}) + peersFound := make(map[enode.ID]struct{}) topicPeersFound[i] = peersFound evt := evts[i] wg.Add(1) - go func(peersFound map[peer.ID]struct{}) { + go func(peersFound map[enode.ID]struct{}) { defer wg.Done() for len(peersFound) < numHosts-2 { event, err := evt.NextPeerEvent(ctx) @@ -517,7 +517,7 @@ func TestSubscriptionNotificationSubUnSub(t *testing.T) { const numHosts = 35 hosts := getNetHosts(t, ctx, numHosts) - topics := getTopics(getPubsubs(ctx, hosts), topic) + topics := getTopics(getGossipsubs(ctx, hosts), topic) for i := 1; i < numHosts; i++ { connect(t, hosts[0], hosts[i]) @@ -535,7 +535,7 @@ func TestTopicRelay(t *testing.T) { const numHosts = 5 hosts := getNetHosts(t, ctx, numHosts) - topics := getTopics(getPubsubs(ctx, hosts), topic) + topics := getTopics(getGossipsubs(ctx, hosts), topic) // [0.Rel] - [1.Rel] - [2.Sub] // | @@ -546,7 +546,7 @@ func TestTopicRelay(t *testing.T) { connect(t, hosts[1], hosts[3]) connect(t, hosts[3], hosts[4]) - time.Sleep(time.Millisecond * 100) + time.Sleep(time.Millisecond * 500) var subs []*Subscription @@ -566,7 +566,7 @@ func TestTopicRelay(t *testing.T) { } } - time.Sleep(time.Millisecond * 100) + time.Sleep(time.Second * 1) for i := 0; i < 100; i++ { msg := []byte("message") @@ -599,7 +599,7 @@ func TestTopicRelayReuse(t *testing.T) { const numHosts = 1 hosts := getNetHosts(t, ctx, numHosts) - pubsubs := getPubsubs(ctx, hosts) + pubsubs := getGossipsubs(ctx, hosts) topics := getTopics(pubsubs, topic) relay1Cancel, err := topics[0].Relay() @@ -666,7 +666,7 @@ func TestTopicRelayOnClosedTopic(t *testing.T) { const numHosts = 1 hosts := getNetHosts(t, ctx, numHosts) - topics := getTopics(getPubsubs(ctx, hosts), topic) + topics := getTopics(getGossipsubs(ctx, hosts), topic) err := topics[0].Close() if err != nil { @@ -686,7 +686,7 @@ func TestProducePanic(t *testing.T) { const numHosts = 5 topicID := "foobar" hosts := getNetHosts(t, ctx, numHosts) - ps := getPubsub(ctx, hosts[0]) + ps := getGossipsub(ctx, hosts[0]) // Create topic topic, err := ps.Join(topicID) @@ -758,8 +758,8 @@ func notifSubThenUnSub(ctx context.Context, t *testing.T, topics []*Topic) { } } -func readAllQueuedEvents(ctx context.Context, t *testing.T, evt *TopicEventHandler) map[peer.ID]EventType { - peerState := make(map[peer.ID]EventType) +func readAllQueuedEvents(ctx context.Context, t *testing.T, evt *TopicEventHandler) map[enode.ID]EventType { + peerState := make(map[enode.ID]EventType) for { ctx, cancel := context.WithTimeout(ctx, time.Millisecond*100) event, err := evt.NextPeerEvent(ctx) diff --git a/p2p/pubsub/trace_test.go b/p2p/pubsub/trace_test.go index e7abb55cfb..33f0156c37 100644 --- a/p2p/pubsub/trace_test.go +++ b/p2p/pubsub/trace_test.go @@ -1,26 +1,12 @@ package pubsub import ( - "compress/gzip" "context" - "encoding/json" "fmt" - "io" - "os" - "sync" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" "testing" "time" - - pb "github.com/libp2p/go-libp2p-pubsub/pb" - - "github.com/libp2p/go-libp2p-core/network" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/peerstore" - - bhost "github.com/libp2p/go-libp2p-blankhost" - swarmt "github.com/libp2p/go-libp2p-swarm/testing" - - "github.com/libp2p/go-msgio/protoio" ) func testWithTracer(t *testing.T, tracer EventTracer) { @@ -36,7 +22,7 @@ func testWithTracer(t *testing.T, tracer EventTracer) { WithPeerScore( &PeerScoreParams{ TopicScoreCap: 100, - AppSpecificScore: func(peer.ID) float64 { return 0 }, + AppSpecificScore: func(enode.ID) float64 { return 0 }, DecayInterval: time.Second, DecayToZero: 0.01, }, @@ -49,7 +35,7 @@ func testWithTracer(t *testing.T, tracer EventTracer) { // add a validator that rejects some messages to exercise those code paths in the tracer for _, ps := range psubs { - ps.RegisterTopicValidator("test", func(ctx context.Context, p peer.ID, msg *Message) bool { + ps.RegisterTopicValidator("test", func(ctx context.Context, p enode.ID, msg *Message) bool { if string(msg.Data) == "invalid!" { return false } else { @@ -58,20 +44,6 @@ func testWithTracer(t *testing.T, tracer EventTracer) { }) } - // this is the star topology test so that we make sure we get some PRUNEs and cover that code path - - // add all peer addresses to the peerstores - // this is necessary because we can't have signed address records witout identify - // pushing them - for i := range hosts { - for j := range hosts { - if i == j { - continue - } - hosts[i].Peerstore().AddAddrs(hosts[j].ID(), hosts[j].Addrs(), peerstore.PermanentAddrTTL) - } - } - // build the star for i := 1; i < 20; i++ { connect(t, hosts[0], hosts[i]) @@ -124,34 +96,34 @@ type traceStats struct { publish, reject, duplicate, deliver, add, remove, recv, send, drop, join, leave, graft, prune int } -func (t *traceStats) process(evt *pb.TraceEvent) { +func (t *traceStats) process(evt *message.TraceEvent) { //fmt.Printf("process event %s\n", evt.GetType()) switch evt.GetType() { - case pb.TraceEvent_PUBLISH_MESSAGE: + case message.TraceEvent_PUBLISH_MESSAGE: t.publish++ - case pb.TraceEvent_REJECT_MESSAGE: + case message.TraceEvent_REJECT_MESSAGE: t.reject++ - case pb.TraceEvent_DUPLICATE_MESSAGE: + case message.TraceEvent_DUPLICATE_MESSAGE: t.duplicate++ - case pb.TraceEvent_DELIVER_MESSAGE: + case message.TraceEvent_DELIVER_MESSAGE: t.deliver++ - case pb.TraceEvent_ADD_PEER: + case message.TraceEvent_ADD_PEER: t.add++ - case pb.TraceEvent_REMOVE_PEER: + case message.TraceEvent_REMOVE_PEER: t.remove++ - case pb.TraceEvent_RECV_RPC: + case message.TraceEvent_RECV_RPC: t.recv++ - case pb.TraceEvent_SEND_RPC: + case message.TraceEvent_SEND_RPC: t.send++ - case pb.TraceEvent_DROP_RPC: + case message.TraceEvent_DROP_RPC: t.drop++ - case pb.TraceEvent_JOIN: + case message.TraceEvent_JOIN: t.join++ - case pb.TraceEvent_LEAVE: + case message.TraceEvent_LEAVE: t.leave++ - case pb.TraceEvent_GRAFT: + case message.TraceEvent_GRAFT: t.graft++ - case pb.TraceEvent_PRUNE: + case message.TraceEvent_PRUNE: t.prune++ } } @@ -192,130 +164,130 @@ func (ts *traceStats) check(t *testing.T) { } } -func TestJSONTracer(t *testing.T) { - tracer, err := NewJSONTracer("/tmp/trace.out.json") - if err != nil { - t.Fatal(err) - } - - testWithTracer(t, tracer) - time.Sleep(time.Second) - tracer.Close() - - var stats traceStats - var evt pb.TraceEvent - - f, err := os.Open("/tmp/trace.out.json") - if err != nil { - t.Fatal(err) - } - defer f.Close() - - dec := json.NewDecoder(f) - for { - evt.Reset() - err := dec.Decode(&evt) - if err != nil { - break - } - - stats.process(&evt) - } - - stats.check(t) -} - -func TestPBTracer(t *testing.T) { - tracer, err := NewPBTracer("/tmp/trace.out.pb") - if err != nil { - t.Fatal(err) - } - - testWithTracer(t, tracer) - time.Sleep(time.Second) - tracer.Close() - - var stats traceStats - var evt pb.TraceEvent - - f, err := os.Open("/tmp/trace.out.pb") - if err != nil { - t.Fatal(err) - } - defer f.Close() - - r := protoio.NewDelimitedReader(f, 1<<20) - for { - evt.Reset() - err := r.ReadMsg(&evt) - if err != nil { - break - } - - stats.process(&evt) - } - - stats.check(t) -} - -type mockRemoteTracer struct { - mx sync.Mutex - ts traceStats -} - -func (mrt *mockRemoteTracer) handleStream(s network.Stream) { - defer s.Close() - - gzr, err := gzip.NewReader(s) - if err != nil { - panic(err) - } - - r := protoio.NewDelimitedReader(gzr, 1<<24) - - var batch pb.TraceEventBatch - for { - batch.Reset() - err := r.ReadMsg(&batch) - if err != nil { - if err != io.EOF { - s.Reset() - } - return - } - - mrt.mx.Lock() - for _, evt := range batch.GetBatch() { - mrt.ts.process(evt) - } - mrt.mx.Unlock() - } -} - -func (mrt *mockRemoteTracer) check(t *testing.T) { - mrt.mx.Lock() - defer mrt.mx.Unlock() - mrt.ts.check(t) -} - -func TestRemoteTracer(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - h1 := bhost.NewBlankHost(swarmt.GenSwarm(t, ctx)) - h2 := bhost.NewBlankHost(swarmt.GenSwarm(t, ctx)) - - mrt := &mockRemoteTracer{} - h1.SetStreamHandler(RemoteTracerProtoID, mrt.handleStream) - - tracer, err := NewRemoteTracer(ctx, h2, peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()}) - if err != nil { - t.Fatal(err) - } - - testWithTracer(t, tracer) - time.Sleep(time.Second) - tracer.Close() - - mrt.check(t) -} +//func TestJSONTracer(t *testing.T) { +// tracer, err := NewJSONTracer("/tmp/trace.out.json") +// if err != nil { +// t.Fatal(err) +// } +// +// testWithTracer(t, tracer) +// time.Sleep(time.Second) +// tracer.Close() +// +// var stats traceStats +// var evt message.TraceEvent +// +// f, err := os.Open("/tmp/trace.out.json") +// if err != nil { +// t.Fatal(err) +// } +// defer f.Close() +// +// dec := json.NewDecoder(f) +// for { +// evt.Reset() +// err := dec.Decode(&evt) +// if err != nil { +// break +// } +// +// stats.process(&evt) +// } +// +// stats.check(t) +//} +// +//func TestPBTracer(t *testing.T) { +// tracer, err := NewPBTracer("/tmp/trace.out.pb") +// if err != nil { +// t.Fatal(err) +// } +// +// testWithTracer(t, tracer) +// time.Sleep(time.Second) +// tracer.Close() +// +// var stats traceStats +// var evt message.TraceEvent +// +// f, err := os.Open("/tmp/trace.out.pb") +// if err != nil { +// t.Fatal(err) +// } +// defer f.Close() +// +// r := protoio.NewDelimitedReader(f, 1<<20) +// for { +// evt.Reset() +// err := r.ReadMsg(&evt) +// if err != nil { +// break +// } +// +// stats.process(&evt) +// } +// +// stats.check(t) +//} + +//type mockRemoteTracer struct { +// mx sync.Mutex +// ts traceStats +//} +// +//func (mrt *mockRemoteTracer) handleStream(s network.Stream) { +// defer s.Close() +// +// gzr, err := gzip.NewReader(s) +// if err != nil { +// panic(err) +// } +// +// r := protoio.NewDelimitedReader(gzr, 1<<24) +// +// var batch message.TraceEventBatch +// for { +// batch.Reset() +// err := r.ReadMsg(&batch) +// if err != nil { +// if err != io.EOF { +// s.Reset() +// } +// return +// } +// +// mrt.mx.Lock() +// for _, evt := range batch.GetBatch() { +// mrt.ts.process(evt) +// } +// mrt.mx.Unlock() +// } +//} +// +//func (mrt *mockRemoteTracer) check(t *testing.T) { +// mrt.mx.Lock() +// defer mrt.mx.Unlock() +// mrt.ts.check(t) +//} +// +//func TestRemoteTracer(t *testing.T) { +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// +// h1 := NewTestHost() +// h2 := NewTestHost() +// +// mrt := &mockRemoteTracer{} +// h1.SetStreamHandler(RemoteTracerProtoID, mrt.handleStream) +// +// tracer, err := NewRemoteTracer(ctx, h2, peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()}) +// if err != nil { +// t.Fatal(err) +// } +// +// testWithTracer(t, tracer) +// time.Sleep(time.Second) +// tracer.Close() +// +// mrt.check(t) +//} diff --git a/p2p/pubsub/validation_test.go b/p2p/pubsub/validation_test.go index 68ed8e3655..862b78c8e8 100644 --- a/p2p/pubsub/validation_test.go +++ b/p2p/pubsub/validation_test.go @@ -4,11 +4,10 @@ import ( "bytes" "context" "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "sync" "testing" "time" - - "github.com/libp2p/go-libp2p-core/peer" ) func TestRegisterUnregisterValidator(t *testing.T) { @@ -16,9 +15,9 @@ func TestRegisterUnregisterValidator(t *testing.T) { defer cancel() hosts := getNetHosts(t, ctx, 1) - psubs := getPubsubs(ctx, hosts) + psubs := getGossipsubs(ctx, hosts) - err := psubs[0].RegisterTopicValidator("foo", func(context.Context, peer.ID, *Message) bool { + err := psubs[0].RegisterTopicValidator("foo", func(context.Context, enode.ID, *Message) bool { return true }) if err != nil { @@ -41,10 +40,10 @@ func TestRegisterValidatorEx(t *testing.T) { defer cancel() hosts := getNetHosts(t, ctx, 3) - psubs := getPubsubs(ctx, hosts) + psubs := getGossipsubs(ctx, hosts) err := psubs[0].RegisterTopicValidator("test", - Validator(func(context.Context, peer.ID, *Message) bool { + Validator(func(context.Context, enode.ID, *Message) bool { return true })) if err != nil { @@ -52,7 +51,7 @@ func TestRegisterValidatorEx(t *testing.T) { } err = psubs[1].RegisterTopicValidator("test", - ValidatorEx(func(context.Context, peer.ID, *Message) ValidationResult { + ValidatorEx(func(context.Context, enode.ID, *Message) ValidationResult { return ValidationAccept })) if err != nil { @@ -70,12 +69,12 @@ func TestValidate(t *testing.T) { defer cancel() hosts := getNetHosts(t, ctx, 2) - psubs := getPubsubs(ctx, hosts) + psubs := getGossipsubs(ctx, hosts) connect(t, hosts[0], hosts[1]) topic := "foobar" - err := psubs[1].RegisterTopicValidator(topic, func(ctx context.Context, from peer.ID, msg *Message) bool { + err := psubs[1].RegisterTopicValidator(topic, func(ctx context.Context, from enode.ID, msg *Message) bool { return !bytes.Contains(msg.Data, []byte("illegal")) }) if err != nil { @@ -124,11 +123,11 @@ func TestValidate2(t *testing.T) { defer cancel() hosts := getNetHosts(t, ctx, 1) - psubs := getPubsubs(ctx, hosts) + psubs := getGossipsubs(ctx, hosts) topic := "foobar" - err := psubs[0].RegisterTopicValidator(topic, func(ctx context.Context, from peer.ID, msg *Message) bool { + err := psubs[0].RegisterTopicValidator(topic, func(ctx context.Context, from enode.ID, msg *Message) bool { return !bytes.Contains(msg.Data, []byte("illegal")) }) if err != nil { @@ -202,7 +201,7 @@ func TestValidateOverload(t *testing.T) { for _, tc := range tcs { hosts := getNetHosts(t, ctx, 2) - psubs := getPubsubs(ctx, hosts) + psubs := getGossipsubs(ctx, hosts) connect(t, hosts[0], hosts[1]) topic := "foobar" @@ -210,7 +209,7 @@ func TestValidateOverload(t *testing.T) { block := make(chan struct{}) err := psubs[1].RegisterTopicValidator(topic, - func(ctx context.Context, from peer.ID, msg *Message) bool { + func(ctx context.Context, from enode.ID, msg *Message) bool { <-block return true }, @@ -273,7 +272,7 @@ func TestValidateAssortedOptions(t *testing.T) { defer cancel() hosts := getNetHosts(t, ctx, 10) - psubs := getPubsubs(ctx, hosts, + psubs := getGossipsubs(ctx, hosts, WithValidateQueueSize(10), WithValidateThrottle(10), WithValidateWorkers(10)) @@ -282,7 +281,7 @@ func TestValidateAssortedOptions(t *testing.T) { for _, psub := range psubs { err := psub.RegisterTopicValidator("test1", - func(context.Context, peer.ID, *Message) bool { + func(context.Context, enode.ID, *Message) bool { return true }, WithValidatorTimeout(100*time.Millisecond)) @@ -291,7 +290,7 @@ func TestValidateAssortedOptions(t *testing.T) { } err = psub.RegisterTopicValidator("test2", - func(context.Context, peer.ID, *Message) bool { + func(context.Context, enode.ID, *Message) bool { return true }, WithValidatorInline(true)) diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go index 717cb4eb44..6ecc7d9301 100644 --- a/p2p/pubsub_server.go +++ b/p2p/pubsub_server.go @@ -57,7 +57,7 @@ func (pss *PubSubServer) NewConn(peer *Peer, rw MsgReadWriter) chan error { // Wait for the connection to exit errCh := make(chan error) - stream := NewStream(conn, rw, errCh, "") + stream := NewStream(conn, rw, errCh, pubsub.GossipSubID_v11) conn.SetStream(stream) pss.Host().SetStream(peer.ID(), stream) diff --git a/p2p/stream.go b/p2p/stream.go index 0b99c70a59..5d98de3ba0 100644 --- a/p2p/stream.go +++ b/p2p/stream.go @@ -68,7 +68,7 @@ func (s *Stream) SetProtocol(p pubsub.ProtocolID) { func (s *Stream) Read(data interface{}) error { msg, err := s.rw.ReadMsg() if err != nil { - log.Debug("Failed to read PubSub message", "err", err) + log.Error("Failed to read PubSub message", "err", err) return err } From a40b7dd51f51ba4c76892e49c9e9b15f266c5177 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 8 Dec 2021 18:22:27 +0800 Subject: [PATCH 098/299] refactor validatorpool->Update func --- consensus/cbft/cbft.go | 10 ++-- consensus/cbft/consensus_process.go | 2 +- consensus/cbft/validator/validator.go | 69 ++++++++++++++-------- consensus/cbft/validator/validator_test.go | 2 +- consensus/cbft/wal_bridge.go | 6 +- 5 files changed, 53 insertions(+), 36 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 5bb1540db1..cea3e8631c 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -280,12 +280,12 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCache consensus.BlockC needGroup := blockCache.GetActiveVersion(block.Header().SealHash()) >= params.FORKVERSION_0_17_0 if isGenesis() { - cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), cstate.DefaultEpoch, cbft.config.Option.Node.ID(), needGroup, cbft.eventMux) + cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), cstate.DefaultEpoch, cbft.config.Option.Node.ID(), needGroup, cbft.eventMux) // init RGMsg broadcast manager cbft.RGBroadcastManager = NewRGBroadcastManager(cbft) cbft.changeView(cstate.DefaultEpoch, cstate.DefaultViewNumber, block, qc, nil) } else { - cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), qc.Epoch, cbft.config.Option.Node.ID(), needGroup, cbft.eventMux) + cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), qc.Epoch, cbft.config.Option.Node.ID(), needGroup, cbft.eventMux) // init RGMsg broadcast manager cbft.RGBroadcastManager = NewRGBroadcastManager(cbft) cbft.changeView(qc.Epoch, qc.ViewNumber, block, qc, nil) @@ -1352,7 +1352,7 @@ func (cbft *Cbft) commitBlock(commitBlock *types.Block, commitQC *ctypes.QuorumC ChainStateUpdateCB: func() { cbft.bridge.UpdateChainState(qcState, lockState, commitState) }, }) - activeVersion := cbft.blockCache.GetActiveVersion(cpy.Header().SealHash()) + activeVersion := cpy.Header().GetActiveVersion() // should grouped according max commit block's state shouldGroup := func() bool { return cbft.validatorPool.NeedGroup() || activeVersion >= params.FORKVERSION_0_17_0 @@ -1360,11 +1360,9 @@ func (cbft *Cbft) commitBlock(commitBlock *types.Block, commitQC *ctypes.QuorumC // post NewGroupsEvent to join topic according group info if xutil.IsElection(cpy.NumberU64(), activeVersion) { - // TODO: get groupvalidatorslimit and coordinatorlimit from gov if shouldGroup() { - cbft.validatorPool.SetupGroup(true) + cbft.validatorPool.Update(cpy.NumberU64(), cbft.state.Epoch()+1, true, activeVersion, cbft.eventMux) } - cbft.validatorPool.Update(cpy.NumberU64(), cbft.state.Epoch()+1, true, cbft.eventMux) } } diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index a884d0243f..827c582aa5 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -1127,7 +1127,7 @@ func (cbft *Cbft) tryChangeView() { }() if shouldSwitch { - if err := cbft.validatorPool.Update(block.NumberU64(), cbft.state.Epoch()+1, false, cbft.eventMux); err == nil { + if err := cbft.validatorPool.Update(block.NumberU64(), cbft.state.Epoch()+1, false, block.Header().GetActiveVersion(), cbft.eventMux); err == nil { cbft.log.Info("Update validator success", "number", block.NumberU64()) } } diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index d617c52314..4586a1f4ab 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -321,7 +321,7 @@ type ValidatorPool struct { prevValidators *cbfttypes.Validators // Previous round validators currentValidators *cbfttypes.Validators // Current round validators - nextValidators *cbfttypes.Validators // Next round validators + nextValidators *cbfttypes.Validators // Next round validators, to Post Pub event } // NewValidatorPool new a validator pool. @@ -415,51 +415,70 @@ func (vp *ValidatorPool) MockSwitchPoint(number uint64) { } // Update switch validators. -func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection bool, eventMux *event.TypeMux) error { +func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection bool, version uint32, eventMux *event.TypeMux) error { vp.lock.Lock() defer vp.lock.Unlock() - // Election block update nextValidators + // 生效后第一个共识周期的Election block已经是新值(2130)所以第一次触发update是cbft.tryChangeView->shouldSwitch if blockNumber <= vp.switchPoint && !isElection { log.Debug("Already update validator before", "blockNumber", blockNumber, "switchPoint", vp.switchPoint) return errors.New("already updated before") } - nds, err := vp.agency.GetValidators(NextRound(blockNumber)) - if err != nil { - log.Error("Get validator error", "blockNumber", blockNumber, "err", err) - return err - } + var err error + var nds *cbfttypes.Validators - if vp.grouped { - nds.Grouped(eventMux, epoch) - vp.unitID = nds.UnitID(vp.nodeID) + //生效后第一个共识周期的switchpoint是旧值,此时不能切换 + //判断依据是新validators和current完全相同且nextValidators为空 + if !isElection && vp.nextValidators == nil { + nds, err = vp.agency.GetValidators(NextRound(blockNumber)) + if err != nil { + log.Error("Get validator error", "blockNumber", blockNumber, "err", err) + return err + } + if vp.currentValidators.Equal(nds) { + vp.currentValidators = nds + vp.switchPoint = nds.ValidBlockNumber - 1 + vp.lastNumber = vp.agency.GetLastNumber(blockNumber) + //不切换,所以epoch不增 + vp.grouped = false + return nil + } else { + return fmt.Errorf("ValidatorPool update failed!", "currentValidators", vp.currentValidators.String(), "nds", nds.String()) + } + } + //分组提案生效后第一个共识round到ElectionPoint时初始化分组信息 + if !vp.grouped { + if version >= params.FORKVERSION_0_17_0 { + vp.grouped = true + vp.groupValidatorsLimit = xcom.MaxGroupValidators() + vp.coordinatorLimit = xcom.CoordinatorsLimit() + } } - if isElection && vp.grouped { + if isElection { + nds, err = vp.agency.GetValidators(NextRound(blockNumber)) + if err != nil { + log.Error("Get validator error", "blockNumber", blockNumber, "err", err) + return err + } vp.nextValidators = nds - log.Info("Update nextValidators", "validators", nds.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) + if vp.grouped { + nds.Grouped(eventMux, epoch) + } } else { vp.prevValidators = vp.currentValidators - vp.currentValidators = nds - vp.switchPoint = nds.ValidBlockNumber - 1 + vp.currentValidators = vp.nextValidators + vp.switchPoint = vp.currentValidators.ValidBlockNumber - 1 vp.lastNumber = vp.agency.GetLastNumber(NextRound(blockNumber)) vp.epoch = epoch + vp.unitID = vp.currentValidators.UnitID(vp.nodeID) + vp.nextValidators = nil log.Info("Update validator", "validators", nds.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) } return nil } -// SetupGroup update validatorpool group info -func (vp *ValidatorPool) SetupGroup(needGroup bool) { - vp.lock.Lock() - defer vp.lock.Unlock() - - vp.grouped = needGroup - vp.groupValidatorsLimit = xcom.MaxGroupValidators() - vp.coordinatorLimit = xcom.CoordinatorsLimit() -} - // GetValidatorByNodeID get the validator by node id. func (vp *ValidatorPool) GetValidatorByNodeID(epoch uint64, nodeID enode.ID) (*cbfttypes.ValidateNode, error) { vp.lock.RLock() diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index 25140b7caf..f371e7c2ed 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -497,7 +497,7 @@ func TestValidatorPool(t *testing.T) { eventMux := &event.TypeMux{} - validatorPool.Update(80, 1, false, eventMux) + validatorPool.Update(80, 1, false, 0, eventMux) assert.True(t, validatorPool.IsValidator(0, nodes[0].Node.ID())) assert.False(t, validatorPool.IsValidator(1, nodes[0].Node.ID())) } diff --git a/consensus/cbft/wal_bridge.go b/consensus/cbft/wal_bridge.go index aba1d1053e..e3bad8ef37 100644 --- a/consensus/cbft/wal_bridge.go +++ b/consensus/cbft/wal_bridge.go @@ -330,7 +330,7 @@ func (cbft *Cbft) recoveryQCState(qcs []*protocols.State, parent *types.Block) e // recoveryChainStateProcess tries to recovery the corresponding state to cbft consensus. func (cbft *Cbft) recoveryChainStateProcess(stateType uint16, s *protocols.State) { - cbft.trySwitchValidator(s.Block.NumberU64()) + cbft.trySwitchValidator(s.Block.NumberU64(), s.Block.ActiveVersion()) cbft.tryWalChangeView(s.QuorumCert.Epoch, s.QuorumCert.ViewNumber, s.Block, s.QuorumCert, nil) cbft.state.AddQCBlock(s.Block, s.QuorumCert) cbft.state.AddQC(s.QuorumCert) @@ -360,9 +360,9 @@ func (cbft *Cbft) recoveryChainStateProcess(stateType uint16, s *protocols.State } // trySwitch tries to switch next validator. -func (cbft *Cbft) trySwitchValidator(blockNumber uint64) { +func (cbft *Cbft) trySwitchValidator(blockNumber uint64, version uint32) { if cbft.validatorPool.ShouldSwitch(blockNumber) { - if err := cbft.validatorPool.Update(blockNumber, cbft.state.Epoch()+1, false, cbft.eventMux); err != nil { + if err := cbft.validatorPool.Update(blockNumber, cbft.state.Epoch()+1, false, version, cbft.eventMux); err != nil { cbft.log.Debug("Update validator error", "err", err.Error()) } } From 8cec2c8503648a8660e471f43beacd7f14bb0eec Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 8 Dec 2021 18:49:02 +0800 Subject: [PATCH 099/299] add debug log in Adjust0170RoundValidators --- x/plugin/staking_plugin.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index 1661a1f15e..73391343d1 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -3766,6 +3766,8 @@ func (sk *StakingPlugin) Adjust0170RoundValidators(blockHash common.Hash, blockN "blockNumber", blockNumber, "blockHash", blockHash.Hex(), "err", err) return err } + log.Debug("Adjust0170RoundValidators OK!", "queue", queue.String(), + "oldIndex.Start", oldIndex.Start, "oldIndex.End", oldIndex.End, "newQueue.Start", newQueue.Start, "newQueue.End", newQueue.End) return nil } From e18ef4a7504ba7820fd1e97393315b3c2cbe1989 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 8 Dec 2021 19:04:54 +0800 Subject: [PATCH 100/299] fix vp.nextValidators is nil --- consensus/cbft/validator/validator.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 4586a1f4ab..be8c0094a4 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -467,6 +467,17 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo nds.Grouped(eventMux, epoch) } } else { + // 节点中间重启过, nextValidators没有赋值 + if vp.nextValidators == nil { + vp.nextValidators, err = vp.agency.GetValidators(NextRound(blockNumber)) + if err != nil { + log.Error("Get nextValidators error", "blockNumber", blockNumber, "err", err) + return err + } + if vp.grouped { + nds.Grouped(eventMux, epoch) + } + } vp.prevValidators = vp.currentValidators vp.currentValidators = vp.nextValidators vp.switchPoint = vp.currentValidators.ValidBlockNumber - 1 From 75697c4601d23a89dce710005be1b612d016deca Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 8 Dec 2021 19:07:32 +0800 Subject: [PATCH 101/299] fix vp.nextValidators is nil --- consensus/cbft/validator/validator.go | 1 + 1 file changed, 1 insertion(+) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index be8c0094a4..47fbd57346 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -457,6 +457,7 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo } if isElection { + // 提前更新nextValidators,为了p2p早一步订阅分组事件以便建链接 nds, err = vp.agency.GetValidators(NextRound(blockNumber)) if err != nil { log.Error("Get validator error", "blockNumber", blockNumber, "err", err) From bbc2d5ce7c1c40bb31cb7e77ab81ca4422cb8c39 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 8 Dec 2021 19:14:44 +0800 Subject: [PATCH 102/299] fix vp.nextValidators is nil bug --- consensus/cbft/validator/validator.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 47fbd57346..14b7095b61 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -463,21 +463,22 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo log.Error("Get validator error", "blockNumber", blockNumber, "err", err) return err } - vp.nextValidators = nds if vp.grouped { nds.Grouped(eventMux, epoch) } + vp.nextValidators = nds } else { // 节点中间重启过, nextValidators没有赋值 if vp.nextValidators == nil { - vp.nextValidators, err = vp.agency.GetValidators(NextRound(blockNumber)) + nds, err = vp.agency.GetValidators(NextRound(blockNumber)) if err != nil { - log.Error("Get nextValidators error", "blockNumber", blockNumber, "err", err) + log.Error("Get validator error", "blockNumber", blockNumber, "err", err) return err } if vp.grouped { nds.Grouped(eventMux, epoch) } + vp.nextValidators = nds } vp.prevValidators = vp.currentValidators vp.currentValidators = vp.nextValidators @@ -486,7 +487,7 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo vp.epoch = epoch vp.unitID = vp.currentValidators.UnitID(vp.nodeID) vp.nextValidators = nil - log.Info("Update validator", "validators", nds.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) + log.Info("Update validator", "validators", vp.currentValidators.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) } return nil } From 4aab41ff21ccbbeb2589b19d9ae177f05686ec94 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Thu, 9 Dec 2021 11:05:30 +0800 Subject: [PATCH 103/299] Unit test to supplement trace --- p2p/pubsub/trace_test.go | 70 ++++++++++++++++++++++------------------ p2p/pubsub/validation.go | 15 +++++++++ 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/p2p/pubsub/trace_test.go b/p2p/pubsub/trace_test.go index 33f0156c37..f6af64f605 100644 --- a/p2p/pubsub/trace_test.go +++ b/p2p/pubsub/trace_test.go @@ -2,9 +2,11 @@ package pubsub import ( "context" + "encoding/json" "fmt" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" + "os" "testing" "time" ) @@ -48,6 +50,9 @@ func testWithTracer(t *testing.T, tracer EventTracer) { for i := 1; i < 20; i++ { connect(t, hosts[0], hosts[i]) } + connectSome(t, hosts, 6) + + time.Sleep(time.Second) // build the mesh var subs []*Subscription @@ -164,38 +169,39 @@ func (ts *traceStats) check(t *testing.T) { } } -//func TestJSONTracer(t *testing.T) { -// tracer, err := NewJSONTracer("/tmp/trace.out.json") -// if err != nil { -// t.Fatal(err) -// } -// -// testWithTracer(t, tracer) -// time.Sleep(time.Second) -// tracer.Close() -// -// var stats traceStats -// var evt message.TraceEvent -// -// f, err := os.Open("/tmp/trace.out.json") -// if err != nil { -// t.Fatal(err) -// } -// defer f.Close() -// -// dec := json.NewDecoder(f) -// for { -// evt.Reset() -// err := dec.Decode(&evt) -// if err != nil { -// break -// } -// -// stats.process(&evt) -// } -// -// stats.check(t) -//} +func TestJSONTracer(t *testing.T) { + tracer, err := NewJSONTracer("D:\\trace.out.json") + if err != nil { + t.Fatal(err) + } + + testWithTracer(t, tracer) + time.Sleep(time.Second) + tracer.Close() + + var stats traceStats + var evt message.TraceEvent + + f, err := os.Open("D:\\trace.out.json") + if err != nil { + t.Fatal(err) + } + defer f.Close() + + dec := json.NewDecoder(f) + for { + evt.Reset() + err := dec.Decode(&evt) + if err != nil { + break + } + + stats.process(&evt) + } + + stats.check(t) +} + // //func TestPBTracer(t *testing.T) { // tracer, err := NewPBTracer("/tmp/trace.out.pb") diff --git a/p2p/pubsub/validation.go b/p2p/pubsub/validation.go index eccacf3372..40c26c0392 100644 --- a/p2p/pubsub/validation.go +++ b/p2p/pubsub/validation.go @@ -67,6 +67,8 @@ type ValidatorOpt func(addVal *addValReq) error type validation struct { p *PubSub + tracer *pubsubTracer + // mx protects the validator map mx sync.Mutex // topicVals tracks per topic validators @@ -128,6 +130,7 @@ func newValidation() *validation { // workers func (v *validation) Start(p *PubSub) { v.p = p + v.tracer = p.tracer for i := 0; i < v.validateWorkers; i++ { go v.validateWorker() } @@ -213,6 +216,7 @@ func (v *validation) RemoveValidator(req *rmValReq) { // validations. // Returns an error if validation fails func (v *validation) PushLocal(msg *Message) error { + v.p.tracer.PublishMessage(msg) err := v.p.checkSigningPolicy(msg) if err != nil { @@ -233,6 +237,7 @@ func (v *validation) Push(src enode.ID, msg *Message) bool { case v.validateQ <- &validateReq{vals, src, msg}: default: log.Debug("message validation throttled: queue full; dropping message from %s", "from", src) + v.tracer.RejectMessage(msg, RejectValidationQueueFull) } return false } @@ -274,6 +279,7 @@ func (v *validation) validate(vals []*topicVal, src enode.ID, msg *Message, sync if msg.Signature != nil { if !v.validateSignature(msg) { log.Debug("message signature validation failed; dropping message from %s", "src", src) + v.tracer.RejectMessage(msg, RejectInvalidSignature) return ValidationError{Reason: RejectInvalidSignature} } } @@ -282,7 +288,10 @@ func (v *validation) validate(vals []*topicVal, src enode.ID, msg *Message, sync // and avoid invoking user validators more than once id := v.p.msgID(msg.Message) if !v.p.markSeen(id) { + v.tracer.DuplicateMessage(msg) return nil + } else { + v.tracer.ValidateMessage(msg) } var inline, async []*topicVal @@ -310,6 +319,7 @@ loop: if result == ValidationReject { log.Debug("message validation failed; dropping message from %s", "src", src) + v.tracer.RejectMessage(msg, RejectValidationFailed) return ValidationError{Reason: RejectValidationFailed} } @@ -323,11 +333,13 @@ loop: }() default: log.Debug("message validation throttled; dropping message from %s", "src", src) + v.tracer.RejectMessage(msg, RejectValidationThrottled) } return nil } if result == ValidationIgnore { + v.tracer.RejectMessage(msg, RejectValidationIgnored) return ValidationError{Reason: RejectValidationIgnored} } @@ -362,12 +374,15 @@ func (v *validation) doValidateTopic(vals []*topicVal, src enode.ID, msg *Messag v.p.sendMsg <- msg case ValidationReject: log.Debug("message validation failed; dropping message from %s", "src", src) + v.tracer.RejectMessage(msg, RejectValidationFailed) return case ValidationIgnore: log.Debug("message validation punted; ignoring message from %s", "src", src) + v.tracer.RejectMessage(msg, RejectValidationIgnored) return case validationThrottled: log.Debug("message validation throttled; ignoring message from %s", "src", src) + v.tracer.RejectMessage(msg, RejectValidationThrottled) default: // BUG: this would be an internal programming error, so a panic seems appropiate. From c08329441878eeb34dd9b47ee7d6b7f27b6205c8 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Thu, 9 Dec 2021 11:08:40 +0800 Subject: [PATCH 104/299] improve discover --- consensus/cbft/network/pubsub.go | 14 +++++- core/cbfttypes/type.go | 6 ++- p2p/discover.go | 81 +------------------------------- p2p/pubsub_server.go | 5 ++ p2p/server.go | 34 +++++++------- 5 files changed, 41 insertions(+), 99 deletions(-) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index a364cc4e62..6cde59b2f5 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -21,6 +21,8 @@ import ( "context" "errors" "fmt" + "sync" + "github.com/AlayaNetwork/Alaya-Go/common" ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/log" @@ -28,7 +30,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "github.com/AlayaNetwork/Alaya-Go/rlp" - "sync" ) const ( @@ -62,7 +63,8 @@ type PubSub struct { onReceive receiveCallback // All topics subscribed - topics map[string]*pubsub.Topic + topics map[string]*pubsub.Topic + topicCtx map[string]context.Context // The set of topics we are subscribed to mySubs map[string]*pubsub.Subscription sync.Mutex @@ -137,12 +139,16 @@ func (ps *PubSub) Subscribe(topic string) error { if err != nil { return err } + ctx := context.Background() + ps.pss.DiscoverTopic(ctx, topic) + subscription, err := t.Subscribe() if err != nil { return err } ps.topics[topic] = t ps.mySubs[topic] = subscription + ps.topicCtx[topic] = ctx go ps.listen(subscription) return nil @@ -194,6 +200,10 @@ func (ps *PubSub) Cancel(topic string) error { delete(ps.mySubs, topic) delete(ps.topics, topic) } + if ctx, ok := ps.topicCtx[topic]; ok { + ctx.Done() + delete(ps.topicCtx, topic) + } return nil } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 03d55d063c..dc8078e53d 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -75,7 +75,11 @@ type NewGroupsEvent struct { Validators *GroupValidators } -type UpdateValidatorEvent struct{} +type ExpiredTopicEvent struct { + Topic string // consensus:{epoch}:{groupID} +} + +//type UpdateValidatorEvent struct{} type ValidateNode struct { Index uint32 `json:"index"` diff --git a/p2p/discover.go b/p2p/discover.go index eb5ad6daed..7424644267 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -10,9 +10,9 @@ import ( "github.com/AlayaNetwork/Alaya-Go/p2p/enr" ) -// SubscribeTopic to the given topic.A given validator and subscription handler is +// DiscoverTopic to the given topic.A given validator and subscription handler is // used to handle messages from the subnet. The base protobuf message is used to initialize new messages for decoding. -func (srv *Server) SubscribeTopic(ctx context.Context, topic string) { +func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { ticker := time.NewTicker(time.Second * 1) @@ -146,80 +146,3 @@ func (srv *Server) filterPeer(node *enode.Node) bool { } return true } - -/* -func (srv *Server) RefreshTopicENR() { - // return early if discv5 isnt running - if srv.DiscV5 == nil { - return - } - bitTopics := bitfield.NewBitvector64() - committees := srv.GetAllTopics() - for _, idx := range committees { - bitTopics.SetBitAt(idx, true) - } - currentBitTopics, err := getBitvector(srv.Config.TopicKey, srv.DiscV5.Self().Record()) - if err != nil { - log.Error("Could not retrieve att bitfield", "err", err) - return - } - - if bytes.Equal(bitTopics, currentBitTopics) { - // return early if bitfields haven't changed - return - } - srv.updateTopicRecord(bitTopics) - // ping all peers to inform them of new metadata - //todo not sure ping here - //s.pingPeers() -} - -func (srv *Server) updateTopicRecord(bitVAtt bitfield.Bitvector64) { - entry := enr.WithEntry(srv.Config.TopicKey, &bitVAtt) - srv.DiscV5.LocalNode().Set(entry) -} - -func (srv *Server) GetAllTopics() []uint64 { - return nil -} - -// Reads the attestation subnets entry from a node's ENR and determines -// the committee indices of the attestation subnets the node is subscribed to. -func (srv *Server) getTopics(record *enr.Record) ([]uint64, error) { - bitV, err := getBitvector(srv.Config.TopicKey, record) - if err != nil { - return nil, err - } - if len(bitV) != byteCount(int(srv.Config.TopicCount)) { - return []uint64{}, errors.Errorf("invalid bitvector provided, it has a size of %d", len(bitV)) - } - var topicIdxs []uint64 - for i := uint64(0); i < srv.Config.TopicCount; i++ { - if bitV.BitAt(i) { - topicIdxs = append(topicIdxs, i) - } - } - return topicIdxs, nil -} - -// Parses the attestation subnets ENR entry in a node and extracts its value -// as a bitvector for further manipulation. -func getBitvector(key string, record *enr.Record) (bitfield.Bitvector64, error) { - bitV := bitfield.NewBitvector64() - entry := enr.WithEntry(key, &bitV) - err := record.Load(entry) - if err != nil { - return nil, err - } - return bitV, nil -} - -// Determines the number of bytes that are used -// to represent the provided number of bits. -func byteCount(bitCount int) int { - numOfBytes := bitCount / 8 - if bitCount%8 != 0 { - numOfBytes++ - } - return numOfBytes -}*/ diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go index e0ab81968b..ec2cdfd6c0 100644 --- a/p2p/pubsub_server.go +++ b/p2p/pubsub_server.go @@ -18,6 +18,7 @@ package p2p import ( "context" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" ) @@ -64,3 +65,7 @@ func (pss *PubSubServer) NewConn(peer *Peer, rw MsgReadWriter) chan error { pss.Host().NotifyAll(conn) return errCh } + +func (pss *PubSubServer) DiscoverTopic(ctx context.Context, topic string) { + pss.p2pServer.DiscoverTopic(ctx, topic) +} diff --git a/p2p/server.go b/p2p/server.go index 91459a841a..97817c5cf6 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -1277,7 +1277,7 @@ func (srv *Server) StartWatching(eventMux *event.TypeMux) { } func (srv *Server) watching() { - events := srv.eventMux.Subscribe(cbfttypes.AddValidatorEvent{}, cbfttypes.RemoveValidatorEvent{}) + events := srv.eventMux.Subscribe(cbfttypes.AddValidatorEvent{}, cbfttypes.RemoveValidatorEvent{}, cbfttypes.NewGroupsEvent{}, cbfttypes.ExpiredTopicEvent{}) defer events.Unsubscribe() for { @@ -1286,28 +1286,28 @@ func (srv *Server) watching() { if ev == nil { continue } - - switch ev.Data.(type) { + switch data := ev.Data.(type) { case cbfttypes.AddValidatorEvent: - addEv, ok := ev.Data.(cbfttypes.AddValidatorEvent) - if !ok { - log.Error("Received add validator event type error") - continue - } - log.Trace("Received AddValidatorEvent", "nodeID", addEv.Node.ID().String()) - srv.AddConsensusPeer(addEv.Node) + log.Trace("Received AddValidatorEvent", "nodeID", data.Node.ID().String()) + srv.AddConsensusPeer(data.Node) case cbfttypes.RemoveValidatorEvent: - removeEv, ok := ev.Data.(cbfttypes.RemoveValidatorEvent) - if !ok { - log.Error("Received remove validator event type error") - continue + log.Trace("Received RemoveValidatorEvent", "nodeID", data.Node.ID().String()) + srv.RemoveConsensusPeer(data.Node) + case cbfttypes.NewGroupsEvent: + srv.topicSubscriberMu.Lock() + topicSubscriber := make([]enode.ID, 0) + for _, node := range data.Validators.Nodes { + topicSubscriber = append(topicSubscriber, node.NodeID) } - log.Trace("Received RemoveValidatorEvent", "nodeID", removeEv.Node.ID().String()) - srv.RemoveConsensusPeer(removeEv.Node) + srv.topicSubscriber[data.Topic] = topicSubscriber + srv.topicSubscriberMu.Unlock() + case cbfttypes.ExpiredTopicEvent: + srv.topicSubscriberMu.Lock() + delete(srv.topicSubscriber, data.Topic) + srv.topicSubscriberMu.Unlock() default: log.Error("Received unexcepted event") } - case <-srv.quit: return } From 0121f9a1aed01893872929e8292466c5ffd79ed4 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Thu, 9 Dec 2021 11:22:10 +0800 Subject: [PATCH 105/299] Optimize pubsub --- p2p/network.go | 18 +++++++++--------- p2p/pubsub_server.go | 12 +++++------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/p2p/network.go b/p2p/network.go index 85fc40382d..23a456ea8d 100644 --- a/p2p/network.go +++ b/p2p/network.go @@ -23,12 +23,12 @@ import ( "time" ) -type PeerManager interface { -} +// Get a list of connected nodes +type Peers func() []*Peer type Network struct { sync.RWMutex - server *Server + GetPeers Peers m map[pubsub.Notifiee]struct{} @@ -38,11 +38,11 @@ type Network struct { } } -func NewNetwork(server *Server) *Network { +func NewNetwork(getPeers Peers) *Network { n := &Network{ - RWMutex: sync.RWMutex{}, - server: server, - m: make(map[pubsub.Notifiee]struct{}), + RWMutex: sync.RWMutex{}, + GetPeers: getPeers, + m: make(map[pubsub.Notifiee]struct{}), } n.conns.m = make(map[enode.ID][]pubsub.Conn) return n @@ -77,7 +77,7 @@ func (n *Network) ConnsToPeer(p enode.ID) []pubsub.Conn { } func (n *Network) Connectedness(id enode.ID) pubsub.Connectedness { - for _, p := range n.server.Peers() { + for _, p := range n.GetPeers() { if p.ID() == id { return pubsub.Connected } @@ -126,7 +126,7 @@ func (n *Network) NotifyAll(conn pubsub.Conn) { func (n *Network) Peers() []enode.ID { var eids []enode.ID - for _, p := range n.server.Peers() { + for _, p := range n.GetPeers() { eids = append(eids, p.ID()) } return eids diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go index 6ecc7d9301..ad4c8de8a1 100644 --- a/p2p/pubsub_server.go +++ b/p2p/pubsub_server.go @@ -23,13 +23,12 @@ import ( ) type PubSubServer struct { - p2pServer *Server - pubSub *pubsub.PubSub - host *Host + pubSub *pubsub.PubSub + host *Host } func NewPubSubServer(localNode *enode.Node, p2pServer *Server) *PubSubServer { - network := NewNetwork(p2pServer) + network := NewNetwork(p2pServer.Peers) host := NewHost(localNode, network) gossipSub, err := pubsub.NewGossipSub(context.Background(), host) if err != nil { @@ -37,9 +36,8 @@ func NewPubSubServer(localNode *enode.Node, p2pServer *Server) *PubSubServer { } return &PubSubServer{ - p2pServer: p2pServer, - pubSub: gossipSub, - host: host, + pubSub: gossipSub, + host: host, } } From e241311ef20f9beb48aee821e7b7ed1b2e54cd54 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Thu, 9 Dec 2021 12:05:38 +0800 Subject: [PATCH 106/299] change --- p2p/pubsub_server.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go index ad4c8de8a1..934e85afa8 100644 --- a/p2p/pubsub_server.go +++ b/p2p/pubsub_server.go @@ -23,8 +23,9 @@ import ( ) type PubSubServer struct { - pubSub *pubsub.PubSub - host *Host + p2pServer *Server + pubSub *pubsub.PubSub + host *Host } func NewPubSubServer(localNode *enode.Node, p2pServer *Server) *PubSubServer { @@ -36,8 +37,9 @@ func NewPubSubServer(localNode *enode.Node, p2pServer *Server) *PubSubServer { } return &PubSubServer{ - pubSub: gossipSub, - host: host, + p2pServer: p2pServer, + pubSub: gossipSub, + host: host, } } From 5e86b8157d73328d0d5e57cdd6b757d73e7b6f4b Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 9 Dec 2021 15:27:41 +0800 Subject: [PATCH 107/299] add validatorpool update test --- consensus/cbft/validator/validator.go | 15 +++++++-- consensus/cbft/validator/validator_test.go | 39 +++++++++++++++++++--- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 14b7095b61..b43d169547 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -355,8 +355,17 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID pool.switchPoint = pool.currentValidators.ValidBlockNumber - 1 } if needGroup { - pool.prevValidators.Grouped(eventMux, epoch) + pool.currentValidators.Grouped(eventMux, epoch) pool.unitID = pool.currentValidators.UnitID(nodeID) + if pool.nextValidators == nil { + nds, err := pool.agency.GetValidators(NextRound(pool.currentValidators.ValidBlockNumber)) + if err != nil { + log.Debug("Get nextValidators error", "blockNumber", blockNumber, "err", err) + return pool + } + nds.Grouped(eventMux, epoch+1) + pool.nextValidators = nds + } } log.Debug("Update validator", "validators", pool.currentValidators.String(), "switchpoint", pool.switchPoint, "epoch", pool.epoch, "lastNumber", pool.lastNumber) return pool @@ -444,7 +453,7 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo vp.grouped = false return nil } else { - return fmt.Errorf("ValidatorPool update failed!", "currentValidators", vp.currentValidators.String(), "nds", nds.String()) + return fmt.Errorf("ValidatorPool update failed, currentValidators:%s, nds:%s", vp.currentValidators.String(), nds.String()) } } //分组提案生效后第一个共识round到ElectionPoint时初始化分组信息 @@ -464,7 +473,7 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo return err } if vp.grouped { - nds.Grouped(eventMux, epoch) + nds.Grouped(eventMux, epoch+1) } vp.nextValidators = nds } else { diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index f371e7c2ed..03cf0e6573 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -447,7 +447,7 @@ func TestValidatorPool(t *testing.T) { nodes := newTestNode() agency := newTestInnerAgency(nodes) - validatorPool := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, nil) + validatorPool := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, new(event.TypeMux)) assert.False(t, validatorPool.ShouldSwitch(0)) assert.True(t, validatorPool.ShouldSwitch(40)) @@ -533,7 +533,7 @@ func TestValidatorPoolVerify(t *testing.T) { nodes = append(nodes, params.CbftNode{Node: n4, BlsPubKey: *sec4.GetPublicKey()}) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, nil) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, new(event.TypeMux)) m := "test sig" @@ -606,7 +606,7 @@ func (m *mockAgency) OnCommit(block *types.Block) error { return nil } func TestValidatorPoolReset(t *testing.T) { agency := newMockAgency(100) - vp := NewValidatorPool(agency, 0, 0, enode.ID{}, false, nil) + vp := NewValidatorPool(agency, 0, 0, enode.ID{}, false, new(event.TypeMux)) vp.Reset(100, 10, nil) assert.Equal(t, vp.switchPoint, uint64(100)) @@ -646,7 +646,7 @@ func TestGetGroupID(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNodeByNum(100) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), true, nil) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), true, new(event.TypeMux)) grpID, _ := vp.GetGroupID(0, nodes[0].Node.ID()) assert.Equal(t, 0, grpID) @@ -656,8 +656,37 @@ func TestGetUintID(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNodeByNum(100) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, nil) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, new(event.TypeMux)) untID, _ := vp.GetGroupID(0, nodes[0].Node.ID()) assert.Equal(t, 0, untID) } + +func TestUpdate(t *testing.T) { + bls.Init(bls.BLS12_381) + nodes := newTestNodeByNum(100) + agency := newTestInnerAgency(nodes) + eventMux := new(event.TypeMux) + blockNum := uint64(0) + lastNumber := agency.GetLastNumber(blockNum) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), true, eventMux) + + assert.Equal(t, true, vp.NeedGroup()) + assert.Equal(t, lastNumber, vp.lastNumber) + assert.Equal(t, vp.prevValidators, vp.currentValidators) + next, err := agency.GetValidators(lastNumber + 1) + if err != nil { + t.Log("agency.GetValidators", "err", err) + } + next.Grouped(eventMux, 1) + assert.NotEqual(t, vp.currentValidators, next) + + t.Log("TestUpdate", "vp.lastNumber", vp.lastNumber) + t.Log("TestUpdate", "next.lastNumber", next.ValidBlockNumber) + vp.Update(250, 0, false, 0, eventMux) + assert.Nil(t, vp.nextValidators) + vp.Update(vp.lastNumber, 0, true, 0, eventMux) + assert.Equal(t, vp.nextValidators, next) + vp.Update(vp.lastNumber+1, 0, false, 0, eventMux) + assert.Nil(t, vp.nextValidators) +} From c4667b706dd8b95bca69db5456362050d1c519ae Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 9 Dec 2021 16:02:22 +0800 Subject: [PATCH 108/299] update validator.getUnitID --- consensus/cbft/validator/validator.go | 14 +++++++------- core/cbfttypes/type.go | 10 ++++------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index b43d169547..3b5400152f 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -356,7 +356,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID } if needGroup { pool.currentValidators.Grouped(eventMux, epoch) - pool.unitID = pool.currentValidators.UnitID(nodeID) + pool.unitID, _ = pool.currentValidators.UnitID(nodeID) if pool.nextValidators == nil { nds, err := pool.agency.GetValidators(NextRound(pool.currentValidators.ValidBlockNumber)) if err != nil { @@ -389,7 +389,7 @@ func (vp *ValidatorPool) Reset(blockNumber uint64, epoch uint64, eventMux *event } if vp.grouped { vp.currentValidators.Grouped(eventMux, epoch) - vp.unitID = vp.currentValidators.UnitID(vp.nodeID) + vp.unitID, _ = vp.currentValidators.UnitID(vp.nodeID) } log.Debug("Update validator", "validators", vp.currentValidators.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) } @@ -494,7 +494,7 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo vp.switchPoint = vp.currentValidators.ValidBlockNumber - 1 vp.lastNumber = vp.agency.GetLastNumber(NextRound(blockNumber)) vp.epoch = epoch - vp.unitID = vp.currentValidators.UnitID(vp.nodeID) + vp.unitID, _ = vp.currentValidators.UnitID(vp.nodeID) vp.nextValidators = nil log.Info("Update validator", "validators", vp.currentValidators.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) } @@ -750,9 +750,9 @@ func (vp *ValidatorPool) GetUnitID(epoch uint64, nodeID enode.ID) (uint32, error defer vp.lock.RUnlock() if vp.epochToBlockNumber(epoch) <= vp.switchPoint { - return vp.prevValidators.UnitID(nodeID), nil + return vp.prevValidators.UnitID(nodeID) } - return vp.unitID, nil + return vp.currentValidators.UnitID(nodeID) } // UnitID return current node's index according epoch @@ -834,8 +834,8 @@ func (vp *ValidatorPool) GetGroupByValidatorID(epoch uint64, nodeID enode.ID) (u if nil != err { return 0, 0, err } - unitID := validators.UnitID(nodeID) - return groupID, unitID, nil + unitID, err := validators.UnitID(nodeID) + return groupID, unitID, err } // 返回指定epoch下节点的分组信息,key=groupID,value=分组节点index集合 diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 03d55d063c..bd0f55c95f 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -27,7 +27,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/common/hexutil" "github.com/AlayaNetwork/Alaya-Go/event" - "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/x/xcom" @@ -295,25 +294,24 @@ func (vs *Validators) GroupID(nodeID enode.ID) (uint32, error) { return groupID, nil } -func (vs *Validators) UnitID(nodeID enode.ID) uint32 { +func (vs *Validators) UnitID(nodeID enode.ID) (uint32, error) { if len(vs.SortedNodes) == 0 { vs.Sort() } idx, err := vs.Index(nodeID) if err != nil { - log.Error("get preValidator index failed!", "err", err) - return idx + return idx, err } groupID, _ := vs.GroupID(nodeID) unitID := uint32(0) for i, node := range vs.GroupNodes[groupID].Nodes { if idx == node.Index { - unitID = uint32(i) + return uint32(i), nil } } - return unitID + return unitID, errors.New("not found the specified nodeID") } func (gvs *GroupValidators) GroupedUnits() { From d132a19e53d7501dfaf4326d9e61b996a3ae7680 Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 9 Dec 2021 16:48:57 +0800 Subject: [PATCH 109/299] update validator.getUnitID --- consensus/cbft/validator/validator.go | 44 +++++++++++++++++----- consensus/cbft/validator/validator_test.go | 8 ++-- core/cbfttypes/type.go | 42 +++++++++++---------- 3 files changed, 61 insertions(+), 33 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 3b5400152f..c50aeb4e90 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -355,7 +355,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID pool.switchPoint = pool.currentValidators.ValidBlockNumber - 1 } if needGroup { - pool.currentValidators.Grouped(eventMux, epoch) + pool.currentValidators.Grouped() pool.unitID, _ = pool.currentValidators.UnitID(nodeID) if pool.nextValidators == nil { nds, err := pool.agency.GetValidators(NextRound(pool.currentValidators.ValidBlockNumber)) @@ -363,7 +363,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID log.Debug("Get nextValidators error", "blockNumber", blockNumber, "err", err) return pool } - nds.Grouped(eventMux, epoch+1) + nds.Grouped() pool.nextValidators = nds } } @@ -388,7 +388,7 @@ func (vp *ValidatorPool) Reset(blockNumber uint64, epoch uint64, eventMux *event vp.switchPoint = vp.currentValidators.ValidBlockNumber - 1 } if vp.grouped { - vp.currentValidators.Grouped(eventMux, epoch) + vp.currentValidators.Grouped() vp.unitID, _ = vp.currentValidators.UnitID(vp.nodeID) } log.Debug("Update validator", "validators", vp.currentValidators.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) @@ -473,7 +473,7 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo return err } if vp.grouped { - nds.Grouped(eventMux, epoch+1) + nds.Grouped() } vp.nextValidators = nds } else { @@ -485,7 +485,7 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo return err } if vp.grouped { - nds.Grouped(eventMux, epoch) + nds.Grouped() } vp.nextValidators = nds } @@ -738,10 +738,17 @@ func (vp *ValidatorPool) GetGroupID(epoch uint64, nodeID enode.ID) (uint32, erro vp.lock.RLock() defer vp.lock.RUnlock() + var validators *cbfttypes.Validators if vp.epochToBlockNumber(epoch) <= vp.switchPoint { - return vp.prevValidators.GroupID(nodeID) + validators = vp.prevValidators + } else { + validators = vp.currentValidators } - return vp.currentValidators.GroupID(nodeID) + gvs, err := validators.GetGroupValidators(nodeID) + if err != nil { + return 0, err + } + return gvs.GetGroupID(), nil } // GetUnitID return index according epoch & NodeID @@ -830,12 +837,12 @@ func (vp *ValidatorPool) GetGroupByValidatorID(epoch uint64, nodeID enode.ID) (u } else { validators = vp.currentValidators } - groupID, err := validators.GroupID(nodeID) + gvs, err := validators.GetGroupValidators(nodeID) if nil != err { return 0, 0, err } unitID, err := validators.UnitID(nodeID) - return groupID, unitID, err + return gvs.GetGroupID(), unitID, err } // 返回指定epoch下节点的分组信息,key=groupID,value=分组节点index集合 @@ -858,3 +865,22 @@ func (vp *ValidatorPool) GetGroupIndexes(epoch uint64) map[uint32][]uint32 { } return groupIdxs } + +// organize validators into groups +func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64, eventMux *event.TypeMux) error { + if validators == nil { + return errors.New("validators is nil") + } + err := validators.Grouped() + if err != nil { + return err + } + + gvs, err := validators.GetGroupValidators(vp.nodeID) + if nil != err { + return err + } + topic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) + eventMux.Post(cbfttypes.NewGroupsEvent{Topic: topic, Validators: gvs}) + return nil +} diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index 03cf0e6573..941a37cc0a 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -621,21 +621,21 @@ func TestValidatorPoolReset(t *testing.T) { func TestValidatorGrouped(t *testing.T) { nodes := newTestNodeByNum(100) vs := newValidators(nodes, 0) - vs.Grouped(new(event.TypeMux), 0) + vs.Grouped() assert.Equal(t, 4, len(vs.GroupNodes)) assert.Equal(t, 25, len(vs.GroupNodes[3].Nodes)) assert.Equal(t, uint32(74), vs.GroupNodes[2].Nodes[24].Index) assert.Equal(t, 5, len(vs.GroupNodes[2].Units)) assert.Equal(t, uint32(74), vs.GroupNodes[2].Units[4][4]) - vs.Grouped(nil, 0) + vs.Grouped() assert.Equal(t, 4, len(vs.GroupNodes)) assert.Equal(t, 25, len(vs.GroupNodes[0].Nodes)) assert.Equal(t, 25, len(vs.GroupNodes[2].Nodes)) assert.Equal(t, 25, len(vs.GroupNodes[3].Nodes)) assert.Equal(t, uint32(79), vs.GroupNodes[3].Units[0][4]) - vs.Grouped(nil, 0) + vs.Grouped() assert.Equal(t, 6, len(vs.GroupNodes)) assert.Equal(t, 17, len(vs.GroupNodes[0].Nodes)) assert.Equal(t, 6, len(vs.GroupNodes[2].Units)) @@ -678,7 +678,7 @@ func TestUpdate(t *testing.T) { if err != nil { t.Log("agency.GetValidators", "err", err) } - next.Grouped(eventMux, 1) + next.Grouped() assert.NotEqual(t, vp.currentValidators, next) t.Log("TestUpdate", "vp.lastNumber", vp.lastNumber) diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index bd0f55c95f..bdb0401ded 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -26,7 +26,6 @@ import ( "sort" "github.com/AlayaNetwork/Alaya-Go/common/hexutil" - "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/x/xcom" @@ -146,7 +145,7 @@ func (vs *Validators) String() string { func (vs *Validators) NodeList() []enode.ID { nodeList := make([]enode.ID, 0) - for id, _ := range vs.Nodes { + for id := range vs.Nodes { nodeList = append(nodeList, id) } return nodeList @@ -272,26 +271,25 @@ func (vs *Validators) Sort() { sort.Sort(vs.SortedNodes) } -func (vs *Validators) GroupID(nodeID enode.ID) (uint32, error) { +func (vs *Validators) GetGroupValidators(nodeID enode.ID) (*GroupValidators, error) { if len(vs.SortedNodes) == 0 { vs.Sort() } idx, err := vs.Index(nodeID) if err != nil { - return math.MaxUint32, err + return nil, err } - groupID := uint32(0) - for _, group := range vs.GroupNodes { - goupLen := len(group.Nodes) - if idx <= group.Nodes[goupLen-1].Index { + var ret *GroupValidators + for _, gvs := range vs.GroupNodes { + goupLen := len(gvs.Nodes) + if idx <= gvs.Nodes[goupLen-1].Index { + ret = gvs break - } else { - groupID = groupID + 1 } } - return groupID, nil + return ret, nil } func (vs *Validators) UnitID(nodeID enode.ID) (uint32, error) { @@ -304,14 +302,16 @@ func (vs *Validators) UnitID(nodeID enode.ID) (uint32, error) { return idx, err } - groupID, _ := vs.GroupID(nodeID) - unitID := uint32(0) - for i, node := range vs.GroupNodes[groupID].Nodes { + gvs, err := vs.GetGroupValidators(nodeID) + if err != nil { + return 0, err + } + for i, node := range gvs.Nodes { if idx == node.Index { return uint32(i), nil } } - return unitID, errors.New("not found the specified nodeID") + return 0, errors.New("not found the specified nodeID") } func (gvs *GroupValidators) GroupedUnits() { @@ -326,11 +326,15 @@ func (gvs *GroupValidators) GroupedUnits() { } } +func (gvs *GroupValidators) GetGroupID() uint32 { + return gvs.groupID +} + // Grouped fill validators into groups // groupValidatorsLimit is a factor to determine how many groups are grouped // eg: [validatorCount,groupValidatorsLimit]= // [50,25] = 25,25;[43,25] = 22,21; [101,25] = 21,20,20,20,20 -func (vs *Validators) Grouped(eventMux *event.TypeMux, epoch uint64) error { +func (vs *Validators) Grouped() error { // sort nodes by index if len(vs.SortedNodes) == 0 { vs.Sort() @@ -361,15 +365,13 @@ func (vs *Validators) Grouped(eventMux *event.TypeMux, epoch uint64) error { } groupValidators := new(GroupValidators) groupValidators.Nodes = vs.SortedNodes[begin:end] - groupValidators.groupID = uint32(i) + groupValidators.groupID = i vs.GroupNodes[i] = groupValidators } // fill group unit - for i, gvs := range vs.GroupNodes { + for _, gvs := range vs.GroupNodes { gvs.GroupedUnits() - topic := ConsensusGroupTopicName(epoch, uint32(i)) - eventMux.Post(NewGroupsEvent{Topic: topic, Validators: gvs}) } return nil } From c048c534fea8a438837eb707e6abe9706b8eb6d7 Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 9 Dec 2021 16:59:31 +0800 Subject: [PATCH 110/299] add organize func --- consensus/cbft/validator/validator.go | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index c50aeb4e90..2156f5e423 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -309,9 +309,6 @@ type ValidatorPool struct { // current epoch epoch uint64 - // Uint ID of the group where the current node is located - unitID uint32 - // grouped indicates if validators need grouped grouped bool // max validators in per group @@ -355,16 +352,15 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID pool.switchPoint = pool.currentValidators.ValidBlockNumber - 1 } if needGroup { - pool.currentValidators.Grouped() - pool.unitID, _ = pool.currentValidators.UnitID(nodeID) + pool.organize(pool.currentValidators, epoch, eventMux) if pool.nextValidators == nil { nds, err := pool.agency.GetValidators(NextRound(pool.currentValidators.ValidBlockNumber)) if err != nil { log.Debug("Get nextValidators error", "blockNumber", blockNumber, "err", err) return pool } - nds.Grouped() pool.nextValidators = nds + pool.organize(pool.nextValidators, epoch+1, eventMux) } } log.Debug("Update validator", "validators", pool.currentValidators.String(), "switchpoint", pool.switchPoint, "epoch", pool.epoch, "lastNumber", pool.lastNumber) @@ -388,8 +384,7 @@ func (vp *ValidatorPool) Reset(blockNumber uint64, epoch uint64, eventMux *event vp.switchPoint = vp.currentValidators.ValidBlockNumber - 1 } if vp.grouped { - vp.currentValidators.Grouped() - vp.unitID, _ = vp.currentValidators.UnitID(vp.nodeID) + vp.organize(vp.currentValidators, epoch, eventMux) } log.Debug("Update validator", "validators", vp.currentValidators.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) } @@ -472,10 +467,10 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo log.Error("Get validator error", "blockNumber", blockNumber, "err", err) return err } + vp.nextValidators = nds if vp.grouped { - nds.Grouped() + vp.organize(vp.nextValidators, epoch, eventMux) } - vp.nextValidators = nds } else { // 节点中间重启过, nextValidators没有赋值 if vp.nextValidators == nil { @@ -484,17 +479,16 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo log.Error("Get validator error", "blockNumber", blockNumber, "err", err) return err } + vp.nextValidators = nds if vp.grouped { - nds.Grouped() + vp.organize(vp.nextValidators, epoch, eventMux) } - vp.nextValidators = nds } vp.prevValidators = vp.currentValidators vp.currentValidators = vp.nextValidators vp.switchPoint = vp.currentValidators.ValidBlockNumber - 1 vp.lastNumber = vp.agency.GetLastNumber(NextRound(blockNumber)) vp.epoch = epoch - vp.unitID, _ = vp.currentValidators.UnitID(vp.nodeID) vp.nextValidators = nil log.Info("Update validator", "validators", vp.currentValidators.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) } From a948f57922fd64413f05a8086a9056b06bd3d5b6 Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 9 Dec 2021 17:29:26 +0800 Subject: [PATCH 111/299] add dissolve --- consensus/cbft/validator/validator.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 2156f5e423..34d6c7843a 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -878,3 +878,16 @@ func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64 eventMux.Post(cbfttypes.NewGroupsEvent{Topic: topic, Validators: gvs}) return nil } + +// dissolve prevValidators group +func (vp *ValidatorPool) dissolve(epoch uint64, eventMux *event.TypeMux) { + if !vp.grouped { + return + } + gvs, err := vp.prevValidators.GetGroupValidators(vp.nodeID) + if nil != err { + return + } + topic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) + eventMux.Post(cbfttypes.ExpiredTopicEvent{Topic: topic}) +} From 769d4ab350d0dcfb55e518cfd00f9443dca75bdb Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 9 Dec 2021 17:29:42 +0800 Subject: [PATCH 112/299] add dissolve --- consensus/cbft/validator/validator.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 34d6c7843a..3b18e399ae 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -491,6 +491,8 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo vp.epoch = epoch vp.nextValidators = nil log.Info("Update validator", "validators", vp.currentValidators.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) + //切换共识轮时需要将上一轮分组的topic取消订阅 + vp.dissolve(epoch, eventMux) } return nil } From 5379c68cc8406a5a00ff311a39ee0b3a5f6a7148 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Thu, 9 Dec 2021 17:32:36 +0800 Subject: [PATCH 113/299] Improve log printing --- p2p/pubsub/gossipsub.go | 52 ++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/p2p/pubsub/gossipsub.go b/p2p/pubsub/gossipsub.go index 730bda4474..a0500bb961 100644 --- a/p2p/pubsub/gossipsub.go +++ b/p2p/pubsub/gossipsub.go @@ -489,7 +489,7 @@ func (gs *GossipSubRouter) Attach(p *PubSub) { } func (gs *GossipSubRouter) AddPeer(p *enode.Node, proto ProtocolID) { - log.Debug("PEERUP: Add new peer %s using %s", "peer", p, "proto", proto) + log.Debug("PEERUP: Add new peer", "peer", p.ID().TerminalString(), "proto", proto) gs.tracer.AddPeer(p, proto) gs.peers[p.ID()] = proto @@ -518,7 +518,7 @@ loop: } func (gs *GossipSubRouter) RemovePeer(p enode.ID) { - log.Debug("PEERDOWN: Remove disconnected peer %s", "peer", p) + log.Debug("PEERDOWN: Remove disconnected peer", "peer", p.TerminalString()) gs.tracer.RemovePeer(p) delete(gs.peers, p) for _, peers := range gs.mesh { @@ -597,19 +597,19 @@ func (gs *GossipSubRouter) handleIHave(p enode.ID, ctl *message.ControlMessage) // we ignore IHAVE gossip from any peer whose score is below the gossip threshold score := gs.score.Score(p) if score < gs.gossipThreshold { - log.Debug("IHAVE: ignoring peer %s with score below threshold [score = %f]", "peer", p, "score", score) + log.Debug("IHAVE: ignoring peer with score below threshold", "peer", p.TerminalString(), "score", score) return nil } // IHAVE flood protection gs.peerhave[p]++ if gs.peerhave[p] > gs.params.MaxIHaveMessages { - log.Debug("IHAVE: peer %s has advertised too many times (%d) within this heartbeat interval; ignoring", "peer", "messages", p, gs.peerhave[p]) + log.Debug("IHAVE: peer has advertised too many times within this heartbeat interval; ignoring", "peer", p.TerminalString(), "messages", gs.peerhave[p]) return nil } if gs.iasked[p] >= gs.params.MaxIHaveLength { - log.Debug("IHAVE: peer %s has already advertised too many messages (%d); ignoring", "peer", p, "messages", gs.iasked[p]) + log.Debug("IHAVE: peer has already advertised too many messages; ignoring", "peer", p.TerminalString(), "messages", gs.iasked[p]) return nil } @@ -642,7 +642,7 @@ func (gs *GossipSubRouter) handleIHave(p enode.ID, ctl *message.ControlMessage) iask = gs.params.MaxIHaveLength - gs.iasked[p] } - log.Debug("IHAVE: Asking for %d out of %d messages from %s", "iask", iask, "iwant", len(iwant), "peer", p) + log.Debug("IHAVE: request node to get messages", "iask", iask, "iwant", len(iwant), "peer", p.TerminalString()) iwantlst := make([]string, 0, len(iwant)) for mid := range iwant { @@ -665,7 +665,7 @@ func (gs *GossipSubRouter) handleIWant(p enode.ID, ctl *message.ControlMessage) // we don't respond to IWANT requests from any peer whose score is below the gossip threshold score := gs.score.Score(p) if score < gs.gossipThreshold { - log.Debug("IWANT: ignoring peer %s with score below threshold [score = %f]", "peer", p, "score", score) + log.Debug("IWANT: ignoring peer with score below threshold", "peer", p.TerminalString(), "score", score) return nil } @@ -682,7 +682,7 @@ func (gs *GossipSubRouter) handleIWant(p enode.ID, ctl *message.ControlMessage) } if count > gs.params.GossipRetransmission { - log.Debug("IWANT: Peer %s has asked for message %s too many times; ignoring request", "peer", p, "mid", mid) + log.Debug("IWANT: Peer has asked for message too many times; ignoring request", "peer", p.TerminalString(), "mid", mid) continue } @@ -694,7 +694,7 @@ func (gs *GossipSubRouter) handleIWant(p enode.ID, ctl *message.ControlMessage) return nil } - log.Debug("IWANT: Sending %d messages to %s", "ihave", len(ihave), "peer", p) + log.Debug("IWANT: Sending some messages to node", "ihave", len(ihave), "peer", p.TerminalString()) msgs := make([]*message.Message, 0, len(ihave)) for _, msg := range ihave { @@ -735,7 +735,7 @@ func (gs *GossipSubRouter) handleGraft(p enode.ID, ctl *message.ControlMessage) // we don't GRAFT to/from direct peers; complain loudly if this happens _, direct := gs.direct[p] if direct { - log.Warn("GRAFT: ignoring request from direct peer %s", "peer", p) + log.Warn("GRAFT: ignoring request from direct peer", "peer", p.TerminalString()) // this is possibly a bug from non-reciprocal configuration; send a PRUNE prune = append(prune, topic) // but don't PX @@ -746,7 +746,7 @@ func (gs *GossipSubRouter) handleGraft(p enode.ID, ctl *message.ControlMessage) // make sure we are not backing off that peer expire, backoff := gs.backoff[topic][p] if backoff && now.Before(expire) { - log.Debug("GRAFT: ignoring backed off peer %s", "peer", p) + log.Debug("GRAFT: ignoring backed off peer", "peer", p) // add behavioural penalty gs.score.AddPenalty(p, 1) // no PX @@ -766,7 +766,7 @@ func (gs *GossipSubRouter) handleGraft(p enode.ID, ctl *message.ControlMessage) // check the score if score < 0 { // we don't GRAFT peers with negative score - log.Debug("GRAFT: ignoring peer %s with negative score [score = %f, topic = %s]", "peer", p, "score", score, "topic", topic) + log.Debug("GRAFT: ignoring peer with negative score", "peer", p.TerminalString(), "score", score, "topic", topic) // we do send them PRUNE however, because it's a matter of protocol correctness prune = append(prune, topic) // but we won't PX to them @@ -785,7 +785,7 @@ func (gs *GossipSubRouter) handleGraft(p enode.ID, ctl *message.ControlMessage) continue } - log.Debug("GRAFT: add mesh link from %s in %s", "peer", p, "topic", topic) + log.Debug("GRAFT: add mesh", "peer", p.TerminalString(), "topic", topic) gs.tracer.Graft(p, topic) peers[p] = struct{}{} } @@ -812,7 +812,7 @@ func (gs *GossipSubRouter) handlePrune(p enode.ID, ctl *message.ControlMessage) continue } - log.Debug("PRUNE: Remove mesh link to %s in %s", "peer", p, "topic", topic) + log.Debug("PRUNE: Remove mesh", "peer", p.TerminalString(), "topic", topic) gs.tracer.Prune(p, topic) delete(peers, p) // is there a backoff specified by the peer? if so obey it. @@ -827,7 +827,7 @@ func (gs *GossipSubRouter) handlePrune(p enode.ID, ctl *message.ControlMessage) if len(px) > 0 { // we ignore PX from peers with insufficient score if score < gs.acceptPXThreshold { - log.Debug("PRUNE: ignoring PX from peer %s with insufficient score [score = %f, topic = %s]", "peer", p, "score", score, "topic", topic) + log.Debug("PRUNE: ignoring PX from peer with insufficient score", "peer", p.TerminalString(), "score", score, "topic", topic) continue } @@ -912,7 +912,7 @@ func (gs *GossipSubRouter) connector() { continue } - log.Debug("connecting to peer", "peer", ci.p) + log.Debug("connecting to peer", "peer", ci.p.TerminalString()) /* cab, ok := peerstore.GetCertifiedAddrBook(gs.p.host.Peerstore()) if ok && ci.spr != nil { _, err := cab.ConsumePeerRecord(ci.spr, peerstore.TempAddrTTL) @@ -925,7 +925,7 @@ func (gs *GossipSubRouter) connector() { err := gs.p.host.Connect(ctx, ci.p) cancel() if err != nil { - log.Debug("error connecting to peer", "peer", ci.p, "err", err) + log.Debug("error connecting to peer", "peer", ci.p.TerminalString(), "err", err) } case <-gs.p.ctx.Done(): @@ -1052,7 +1052,7 @@ func (gs *GossipSubRouter) Join(topic string) { } for p := range gmap { - log.Debug("JOIN: Add mesh link to %s in %s", "peer", p, "topic", topic) + log.Debug("JOIN: Add mesh", "peer", p.TerminalString(), "topic", topic) gs.tracer.Graft(p, topic) gs.sendGraft(p, topic) } @@ -1070,7 +1070,7 @@ func (gs *GossipSubRouter) Leave(topic string) { delete(gs.mesh, topic) for p := range gmap { - log.Debug("LEAVE: Remove mesh link to %s in %s", "peer", p, "topic", topic) + log.Debug("LEAVE: Remove mesh", "peer", p.TerminalString(), "topic", topic) gs.tracer.Prune(p, topic) gs.sendPrune(p, topic) } @@ -1256,7 +1256,7 @@ func fragmentMessageIds(msgIds []string, limit int) [][]string { size := len(msgIds[i]) + protobufOverhead if size > limit { // pathological case where a single message ID exceeds the limit. - log.Warn("message ID length %d exceeds limit %d, removing from outgoing gossip", "size", size, "limit", limit) + log.Warn("message ID length exceeds limit, removing from outgoing gossip", "size", size, "limit", limit) continue } bucketLen += size @@ -1346,7 +1346,7 @@ func (gs *GossipSubRouter) heartbeat() { } graftPeer := func(p enode.ID) { - log.Debug("HEARTBEAT: Add mesh link to %s in %s", "peer", p, "topic", topic) + log.Debug("HEARTBEAT: Add mesh", "peer", p.TerminalString(), "topic", topic) gs.tracer.Graft(p, topic) peers[p] = struct{}{} topics := tograft[p] @@ -1356,7 +1356,7 @@ func (gs *GossipSubRouter) heartbeat() { // drop all peers with negative score, without PX for p := range peers { if score(p) < 0 { - log.Debug("HEARTBEAT: Prune peer %s with negative score [score = %f, topic = %s]", "peer", p, "score", score(p), "topic", topic) + log.Debug("HEARTBEAT: Prune peer with negative score", "peer", p.TerminalString(), "score", score(p), "topic", topic) prunePeer(p) noPX[p] = true } @@ -1437,7 +1437,7 @@ func (gs *GossipSubRouter) heartbeat() { // prune the excess peers for _, p := range plst[gs.params.D:] { - log.Debug("HEARTBEAT: Remove mesh link to %s in %s", "peer", p, "topic", topic) + log.Debug("HEARTBEAT: Remove mesh", "peer", p.TerminalString(), "topic", topic) prunePeer(p) } } @@ -1498,7 +1498,7 @@ func (gs *GossipSubRouter) heartbeat() { }) for _, p := range plst { - log.Debug("HEARTBEAT: Opportunistically graft peer %s on topic %s", "peer", p, "topic", topic) + log.Debug("HEARTBEAT: Opportunistically graft peer on topic", "peer", p.TerminalString(), "topic", topic) graftPeer(p) } } @@ -1572,7 +1572,7 @@ func (gs *GossipSubRouter) clearIHaveCounters() { func (gs *GossipSubRouter) applyIwantPenalties() { for p, count := range gs.gossipTracer.GetBrokenPromises() { - log.Info("peer %s didn't follow up in %d IWANT requests; adding penalty", "peer", p, "count", count) + log.Info("peer didn't follow up in IWANT requests; adding penalty", "peer", p.TerminalString(), "count", count) gs.score.AddPenalty(p, count) } } @@ -1673,7 +1673,7 @@ func (gs *GossipSubRouter) emitGossip(topic string, exclude map[enode.ID]struct{ // if we are emitting more than GossipSubMaxIHaveLength mids, truncate the list if len(mids) > gs.params.MaxIHaveLength { // we do the truncation (with shuffling) per peer below - log.Debug("too many messages for gossip; will truncate IHAVE list (%d messages)", "mids", len(mids)) + log.Debug("too many messages for gossip; will truncate IHAVE list", "mids", len(mids)) } // Send gossip to GossipFactor peers above threshold, with a minimum of D_lazy. From 0867013cb2fe419ff036df609f01cf8368b11cc3 Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 9 Dec 2021 17:44:57 +0800 Subject: [PATCH 114/299] add ConsensusTopicEvent --- consensus/cbft/validator/validator.go | 15 ++++++++++----- core/cbfttypes/type.go | 4 ++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 3b18e399ae..017790cd90 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -488,11 +488,12 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo vp.currentValidators = vp.nextValidators vp.switchPoint = vp.currentValidators.ValidBlockNumber - 1 vp.lastNumber = vp.agency.GetLastNumber(NextRound(blockNumber)) + currEpoch := vp.epoch vp.epoch = epoch vp.nextValidators = nil log.Info("Update validator", "validators", vp.currentValidators.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) //切换共识轮时需要将上一轮分组的topic取消订阅 - vp.dissolve(epoch, eventMux) + vp.dissolve(currEpoch, eventMux) } return nil } @@ -876,8 +877,10 @@ func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64 if nil != err { return err } - topic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) - eventMux.Post(cbfttypes.NewGroupsEvent{Topic: topic, Validators: gvs}) + consensusTopic := cbfttypes.ConsensusTopicName(epoch) + eventMux.Post(cbfttypes.ConsensusTopicEvent{Topic: consensusTopic}) + groupTopic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) + eventMux.Post(cbfttypes.NewGroupsEvent{Topic: groupTopic, Validators: gvs}) return nil } @@ -890,6 +893,8 @@ func (vp *ValidatorPool) dissolve(epoch uint64, eventMux *event.TypeMux) { if nil != err { return } - topic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) - eventMux.Post(cbfttypes.ExpiredTopicEvent{Topic: topic}) + consensusTopic := cbfttypes.ConsensusTopicName(epoch) + eventMux.Post(cbfttypes.ConsensusTopicEvent{Topic: consensusTopic}) + groupTopic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) + eventMux.Post(cbfttypes.ExpiredTopicEvent{Topic: groupTopic}) } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 627e90dc7b..6377573a4e 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -68,6 +68,10 @@ type RemoveValidatorEvent struct { Node *enode.Node } +type ConsensusTopicEvent struct { + Topic string // consensus:{epoch} +} + type NewGroupsEvent struct { Topic string // consensus:{epoch}:{groupID} Validators *GroupValidators From 953afed28ebc9714531621334b3b17991b7bf3e9 Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 9 Dec 2021 17:56:10 +0800 Subject: [PATCH 115/299] add ExpiredConsensusTopicEvent --- consensus/cbft/validator/validator.go | 4 ++-- core/cbfttypes/type.go | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 017790cd90..dbb16833bb 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -886,7 +886,7 @@ func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64 // dissolve prevValidators group func (vp *ValidatorPool) dissolve(epoch uint64, eventMux *event.TypeMux) { - if !vp.grouped { + if !vp.grouped || vp.prevValidators == nil { return } gvs, err := vp.prevValidators.GetGroupValidators(vp.nodeID) @@ -894,7 +894,7 @@ func (vp *ValidatorPool) dissolve(epoch uint64, eventMux *event.TypeMux) { return } consensusTopic := cbfttypes.ConsensusTopicName(epoch) - eventMux.Post(cbfttypes.ConsensusTopicEvent{Topic: consensusTopic}) + eventMux.Post(cbfttypes.ExpiredConsensusTopicEvent{Topic: consensusTopic}) groupTopic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) eventMux.Post(cbfttypes.ExpiredTopicEvent{Topic: groupTopic}) } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 6377573a4e..8b2e358e02 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -77,6 +77,10 @@ type NewGroupsEvent struct { Validators *GroupValidators } +type ExpiredConsensusTopicEvent struct { + Topic string // consensus:{epoch} +} + type ExpiredTopicEvent struct { Topic string // consensus:{epoch}:{groupID} } From a0e2c75dd528f5ab5837bd59870fd68709f1e23a Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Thu, 9 Dec 2021 18:35:18 +0800 Subject: [PATCH 116/299] Adjust log location --- consensus/cbft/network/pubsub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index baa6f66b0a..b70ff9b1cc 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -161,8 +161,8 @@ func (ps *PubSub) listen(s *pubsub.Subscription) { if err != nil { if err != pubsub.ErrSubscriptionCancelled { ps.Cancel(s.Topic()) + log.Error("Failed to listen to topic message", "topic", s.Topic(), "error", err) } - log.Error("Failed to listen to topic message", "error", err) return } if subMsg != nil { From 4352d3ae1b0fa3f811d94072ef8e9dd1c59962d0 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 10 Dec 2021 10:19:38 +0800 Subject: [PATCH 117/299] update p2p topic event --- consensus/cbft/cbft.go | 2 +- consensus/cbft/validator/validator.go | 4 ++-- core/cbfttypes/type.go | 15 +++++++++++---- p2p/server.go | 8 ++++---- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index cea3e8631c..2aabcb459d 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -1358,7 +1358,7 @@ func (cbft *Cbft) commitBlock(commitBlock *types.Block, commitQC *ctypes.QuorumC return cbft.validatorPool.NeedGroup() || activeVersion >= params.FORKVERSION_0_17_0 } - // post NewGroupsEvent to join topic according group info + // post GroupsTopicEvent to join topic according group info if xutil.IsElection(cpy.NumberU64(), activeVersion) { if shouldGroup() { cbft.validatorPool.Update(cpy.NumberU64(), cbft.state.Epoch()+1, true, activeVersion, cbft.eventMux) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index dbb16833bb..ff1665bbce 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -880,7 +880,7 @@ func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64 consensusTopic := cbfttypes.ConsensusTopicName(epoch) eventMux.Post(cbfttypes.ConsensusTopicEvent{Topic: consensusTopic}) groupTopic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) - eventMux.Post(cbfttypes.NewGroupsEvent{Topic: groupTopic, Validators: gvs}) + eventMux.Post(cbfttypes.GroupsTopicEvent{Topic: groupTopic, Validators: gvs}) return nil } @@ -896,5 +896,5 @@ func (vp *ValidatorPool) dissolve(epoch uint64, eventMux *event.TypeMux) { consensusTopic := cbfttypes.ConsensusTopicName(epoch) eventMux.Post(cbfttypes.ExpiredConsensusTopicEvent{Topic: consensusTopic}) groupTopic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) - eventMux.Post(cbfttypes.ExpiredTopicEvent{Topic: groupTopic}) + eventMux.Post(cbfttypes.ExpiredGroupsTopicEvent{Topic: groupTopic}) } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 8b2e358e02..e62ba4e550 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -72,17 +72,24 @@ type ConsensusTopicEvent struct { Topic string // consensus:{epoch} } -type NewGroupsEvent struct { +type GroupsTopicEvent struct { Topic string // consensus:{epoch}:{groupID} Validators *GroupValidators } -type ExpiredConsensusTopicEvent struct { - Topic string // consensus:{epoch} +type ExpiredConsensusTopicEvent ExpiredTopicEvent // consensus:{epoch} + +type ExpiredGroupsTopicEvent ExpiredTopicEvent // consensus:{epoch}:{groupID} + +// NewTopicEvent use for p2p,Nodes under this topic will be discovered +type NewTopicEvent struct { + Topic string + Nodes []enode.ID } +// ExpiredTopicEvent use for p2p,Nodes under this topic may be disconnected type ExpiredTopicEvent struct { - Topic string // consensus:{epoch}:{groupID} + Topic string } //type UpdateValidatorEvent struct{} diff --git a/p2p/server.go b/p2p/server.go index 97817c5cf6..435f1e6f46 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -1277,7 +1277,7 @@ func (srv *Server) StartWatching(eventMux *event.TypeMux) { } func (srv *Server) watching() { - events := srv.eventMux.Subscribe(cbfttypes.AddValidatorEvent{}, cbfttypes.RemoveValidatorEvent{}, cbfttypes.NewGroupsEvent{}, cbfttypes.ExpiredTopicEvent{}) + events := srv.eventMux.Subscribe(cbfttypes.AddValidatorEvent{}, cbfttypes.RemoveValidatorEvent{}, cbfttypes.NewTopicEvent{}, cbfttypes.ExpiredTopicEvent{}) defer events.Unsubscribe() for { @@ -1293,11 +1293,11 @@ func (srv *Server) watching() { case cbfttypes.RemoveValidatorEvent: log.Trace("Received RemoveValidatorEvent", "nodeID", data.Node.ID().String()) srv.RemoveConsensusPeer(data.Node) - case cbfttypes.NewGroupsEvent: + case cbfttypes.NewTopicEvent: srv.topicSubscriberMu.Lock() topicSubscriber := make([]enode.ID, 0) - for _, node := range data.Validators.Nodes { - topicSubscriber = append(topicSubscriber, node.NodeID) + for _, node := range data.Nodes { + topicSubscriber = append(topicSubscriber, node) } srv.topicSubscriber[data.Topic] = topicSubscriber srv.topicSubscriberMu.Unlock() From 9be9296f6ea2364a281c58d23f92acf2cb6a2f58 Mon Sep 17 00:00:00 2001 From: benbaley Date: Fri, 10 Dec 2021 11:51:31 +0800 Subject: [PATCH 118/299] rename GroupTopicEvent --- consensus/cbft/validator/validator.go | 17 ++++++++++++---- core/cbfttypes/type.go | 28 ++++++++++++++------------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index ff1665bbce..2a443b8c70 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -875,12 +875,18 @@ func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64 gvs, err := validators.GetGroupValidators(vp.nodeID) if nil != err { + // 当前节点不是共识节点 return err } + + consensusNodeIDs := validators.NodeList() + groupNodeIDs := gvs.NodeList() consensusTopic := cbfttypes.ConsensusTopicName(epoch) - eventMux.Post(cbfttypes.ConsensusTopicEvent{Topic: consensusTopic}) groupTopic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) - eventMux.Post(cbfttypes.GroupsTopicEvent{Topic: groupTopic, Validators: gvs}) + + eventMux.Post(cbfttypes.NewTopicEvent{Topic: consensusTopic, Nodes: consensusNodeIDs}) + eventMux.Post(cbfttypes.NewTopicEvent{Topic: groupTopic, Nodes: groupNodeIDs}) + eventMux.Post(cbfttypes.GroupTopicEvent{Topic: groupTopic}) return nil } @@ -893,8 +899,11 @@ func (vp *ValidatorPool) dissolve(epoch uint64, eventMux *event.TypeMux) { if nil != err { return } + consensusTopic := cbfttypes.ConsensusTopicName(epoch) - eventMux.Post(cbfttypes.ExpiredConsensusTopicEvent{Topic: consensusTopic}) groupTopic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) - eventMux.Post(cbfttypes.ExpiredGroupsTopicEvent{Topic: groupTopic}) + + eventMux.Post(cbfttypes.ExpiredTopicEvent{Topic: consensusTopic}) // for p2p + eventMux.Post(cbfttypes.ExpiredTopicEvent{Topic: groupTopic}) // for p2p + eventMux.Post(cbfttypes.ExpiredGroupTopicEvent{Topic: groupTopic}) // for cbft } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index e62ba4e550..5e1afb40ee 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -68,19 +68,6 @@ type RemoveValidatorEvent struct { Node *enode.Node } -type ConsensusTopicEvent struct { - Topic string // consensus:{epoch} -} - -type GroupsTopicEvent struct { - Topic string // consensus:{epoch}:{groupID} - Validators *GroupValidators -} - -type ExpiredConsensusTopicEvent ExpiredTopicEvent // consensus:{epoch} - -type ExpiredGroupsTopicEvent ExpiredTopicEvent // consensus:{epoch}:{groupID} - // NewTopicEvent use for p2p,Nodes under this topic will be discovered type NewTopicEvent struct { Topic string @@ -92,6 +79,12 @@ type ExpiredTopicEvent struct { Topic string } +type GroupTopicEvent struct { + Topic string // consensus:{epoch}:{groupID} +} + +type ExpiredGroupTopicEvent ExpiredTopicEvent // consensus:{epoch}:{groupID} + //type UpdateValidatorEvent struct{} type ValidateNode struct { @@ -349,6 +342,15 @@ func (gvs *GroupValidators) GetGroupID() uint32 { return gvs.groupID } +// return all NodeIDs in the group +func (gvs *GroupValidators) NodeList() []enode.ID { + nodeList := make([]enode.ID, 0) + for _, id := range gvs.Nodes { + nodeList = append(nodeList, id.NodeID) + } + return nodeList +} + // Grouped fill validators into groups // groupValidatorsLimit is a factor to determine how many groups are grouped // eg: [validatorCount,groupValidatorsLimit]= From 5f7bd59fc7548ede79207a0a135bdf5bf2211f86 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Fri, 10 Dec 2021 14:55:17 +0800 Subject: [PATCH 119/299] Improve log printing and part of the code --- p2p/pubsub/peer_gater.go | 2 +- p2p/pubsub/pubsub.go | 32 +++++++++++++------------------- p2p/pubsub/score.go | 4 ++-- p2p/pubsub/validation.go | 18 +++++++++--------- 4 files changed, 25 insertions(+), 31 deletions(-) diff --git a/p2p/pubsub/peer_gater.go b/p2p/pubsub/peer_gater.go index 2f12b4c881..9873a19923 100644 --- a/p2p/pubsub/peer_gater.go +++ b/p2p/pubsub/peer_gater.go @@ -357,7 +357,7 @@ func (pg *peerGater) AcceptFrom(p *enode.Node) AcceptStatus { return AcceptAll } - log.Debug("throttling peer %s with threshold %f", "peer", p, "threshold", threshold) + log.Debug("throttling peer with threshold", "peer", p.ID().TerminalString(), "threshold", threshold) return AcceptControl } diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 2c02f0be3f..d30c872945 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -214,8 +214,6 @@ type Message struct { } func (m *Message) GetFrom() enode.ID { - //var id enode.ID - //copy(id[:], m.Message.GetFrom()) return m.Message.GetFrom() } @@ -532,12 +530,12 @@ func (p *PubSub) processLoop(ctx context.Context) { ch, ok := p.peers[pid.ID()] if !ok { - log.Warn("new stream for unknown peer: ", pid) + log.Warn("new stream for unknown peer: ", pid.ID().TerminalString()) continue } if p.blacklist.Contains(pid.ID()) { - log.Warn("closing stream for blacklisted peer: ", pid) + log.Warn("closing stream for blacklisted peer: ", pid.ID().TerminalString()) close(ch) delete(p.peers, pid.ID()) continue @@ -602,7 +600,7 @@ func (p *PubSub) processLoop(ctx context.Context) { thunk() case pid := <-p.blacklistPeer: - log.Info("Blacklisting peer", "peer", pid) + log.Info("Blacklisting peer", "peer", pid.ID().TerminalString()) p.blacklist.Add(pid.ID()) ch, ok := p.peers[pid.ID()] @@ -638,10 +636,6 @@ func (p *PubSub) handlePendingPeers() { p.newPeersPrioLk.Unlock() for pid := range newPeers { - /*if p.host.Network().Connectedness(pid) != Connected { - continue - }*/ - if _, ok := p.peers[pid]; ok { log.Debug("already have connection to peer: ", pid) continue @@ -865,7 +859,7 @@ func (p *PubSub) announce(topic string, sub bool) { case peer <- out: p.tracer.SendRPC(out, pid) default: - log.Info("Can't send announce message to peer %s: queue full; scheduling retry", "peer", pid) + log.Info("Can't send announce message to peer: queue full; scheduling retry", "peer", pid.TerminalString()) p.tracer.DropRPC(out, pid) go p.announceRetry(pid, topic, sub) } @@ -908,7 +902,7 @@ func (p *PubSub) doAnnounceRetry(pid enode.ID, topic string, sub bool) { case peer <- out: p.tracer.SendRPC(out, pid) default: - log.Info("Can't send announce message to peer %s: queue full; scheduling retry", "peer", pid) + log.Info("Can't send announce message to peer: queue full; scheduling retry", "peer", pid.TerminalString()) p.tracer.DropRPC(out, pid) go p.announceRetry(pid, topic, sub) } @@ -924,7 +918,7 @@ func (p *PubSub) notifySubs(msg *Message) { case f.ch <- msg: default: p.tracer.UndeliverableMessage(msg) - log.Info("Can't deliver message to subscription for topic %s; subscriber too slow", "topic", topic) + log.Info("Can't deliver message to subscription; subscriber too slow", "topic", topic) } } } @@ -989,7 +983,7 @@ func (p *PubSub) handleIncomingRPC(rpc *RPC) { var err error subs, err = p.subFilter.FilterIncomingSubscriptions(rpc.from.ID(), subs) if err != nil { - log.Debug("subscription filter error: %s; ignoring RPC", "err", err) + log.Debug("subscription filter error; ignoring RPC", "err", err) return } } @@ -1027,12 +1021,12 @@ func (p *PubSub) handleIncomingRPC(rpc *RPC) { // ask the router to vet the peer before commiting any processing resources switch p.rt.AcceptFrom(rpc.from) { case AcceptNone: - log.Debug("received RPC from router graylisted peer %s; dropping RPC", "from", rpc.from) + log.Debug("received RPC from router graylisted peer; dropping RPC", "from", rpc.from.ID().TerminalString()) return case AcceptControl: if len(rpc.GetPublish()) > 0 { - log.Debug("peer %s was throttled by router; ignoring %d payload messages", "from", rpc.from, "lengths", len(rpc.GetPublish())) + log.Debug("peer was throttled by router; ignoring payload messages", "from", rpc.from.ID().TerminalString(), "lengths", len(rpc.GetPublish())) } p.tracer.ThrottlePeer(rpc.from.ID()) @@ -1066,28 +1060,28 @@ func (p *PubSub) pushMsg(msg *Message) { src := msg.ReceivedFrom // reject messages from blacklisted peers if p.blacklist.Contains(src.ID()) { - log.Debug("dropping message from blacklisted peer %s", "peer", src) + log.Debug("dropping message from blacklisted peer", "peer", src.ID().TerminalString()) p.tracer.RejectMessage(msg, RejectBlacklstedPeer) return } // even if they are forwarded by good peers if p.blacklist.Contains(msg.GetFrom()) { - log.Debug("dropping message from blacklisted source %s", "peer", src) + log.Debug("dropping message from blacklisted source", "peer", src.ID().TerminalString()) p.tracer.RejectMessage(msg, RejectBlacklistedSource) return } err := p.checkSigningPolicy(msg) if err != nil { - log.Debug("dropping message from %s: %s", "peer", src, "err", err) + log.Debug("dropping message", "fromPeer", src.ID().TerminalString(), "err", err) return } // reject messages claiming to be from ourselves but not locally published self := p.host.ID() if msg.GetFrom() == self.ID() && src.ID() != self.ID() { - log.Debug("dropping message claiming to be from self but forwarded from %s", "peer", src) + log.Debug("dropping message claiming to be from self but forwarded", "peer", src) p.tracer.RejectMessage(msg, RejectSelfOrigin) return } diff --git a/p2p/pubsub/score.go b/p2p/pubsub/score.go index 95962bda58..de0f77bec9 100644 --- a/p2p/pubsub/score.go +++ b/p2p/pubsub/score.go @@ -700,7 +700,7 @@ func (ps *peerScore) DeliverMessage(msg *Message) { // defensive check that this is the first delivery trace -- delivery status should be unknown if drec.status != deliveryUnknown { - log.Debug("unexpected delivery trace: message from %s was first seen %s ago and has delivery status %d", "from", msg.ReceivedFrom, "seen", time.Since(drec.firstSeen), "status", drec.status) + log.Debug("unexpected delivery trace", "from", msg.ReceivedFrom.ID().TerminalString(), "seen", time.Since(drec.firstSeen), "deliveryStatus", drec.status) return } @@ -751,7 +751,7 @@ func (ps *peerScore) RejectMessage(msg *Message, reason string) { // defensive check that this is the first rejection trace -- delivery status should be unknown if drec.status != deliveryUnknown { - log.Debug("unexpected rejection trace: message from %s was first seen %s ago and has delivery status %d", "from", msg.ReceivedFrom, "seen", time.Since(drec.firstSeen), "status", drec.status) + log.Debug("unexpected rejection trace", "from", msg.ReceivedFrom.ID().TerminalString(), "seen", time.Since(drec.firstSeen), "deliveryStatus", drec.status) return } diff --git a/p2p/pubsub/validation.go b/p2p/pubsub/validation.go index 40c26c0392..8b6fa023ad 100644 --- a/p2p/pubsub/validation.go +++ b/p2p/pubsub/validation.go @@ -236,7 +236,7 @@ func (v *validation) Push(src enode.ID, msg *Message) bool { select { case v.validateQ <- &validateReq{vals, src, msg}: default: - log.Debug("message validation throttled: queue full; dropping message from %s", "from", src) + log.Debug("message validation throttled: queue full; drop the message", "from", src.TerminalString()) v.tracer.RejectMessage(msg, RejectValidationQueueFull) } return false @@ -278,7 +278,7 @@ func (v *validation) validate(vals []*topicVal, src enode.ID, msg *Message, sync // the Signature is required to be nil upon receiving the message in PubSub.pushMsg. if msg.Signature != nil { if !v.validateSignature(msg) { - log.Debug("message signature validation failed; dropping message from %s", "src", src) + log.Debug("message signature validation failed; drop the message", "src", src.TerminalString()) v.tracer.RejectMessage(msg, RejectInvalidSignature) return ValidationError{Reason: RejectInvalidSignature} } @@ -318,7 +318,7 @@ loop: } if result == ValidationReject { - log.Debug("message validation failed; dropping message from %s", "src", src) + log.Debug("message validation failed; drop the message", "src", src.TerminalString()) v.tracer.RejectMessage(msg, RejectValidationFailed) return ValidationError{Reason: RejectValidationFailed} } @@ -332,7 +332,7 @@ loop: <-v.validateThrottle }() default: - log.Debug("message validation throttled; dropping message from %s", "src", src) + log.Debug("message validation throttled; drop the message", "src", src.TerminalString()) v.tracer.RejectMessage(msg, RejectValidationThrottled) } return nil @@ -373,15 +373,15 @@ func (v *validation) doValidateTopic(vals []*topicVal, src enode.ID, msg *Messag case ValidationAccept: v.p.sendMsg <- msg case ValidationReject: - log.Debug("message validation failed; dropping message from %s", "src", src) + log.Debug("message validation failed; drop the message", "src", src.TerminalString()) v.tracer.RejectMessage(msg, RejectValidationFailed) return case ValidationIgnore: - log.Debug("message validation punted; ignoring message from %s", "src", src) + log.Debug("message validation punted; ignore the message", "src", src.TerminalString()) v.tracer.RejectMessage(msg, RejectValidationIgnored) return case validationThrottled: - log.Debug("message validation throttled; ignoring message from %s", "src", src) + log.Debug("message validation throttled; ignore the message", "src", src.TerminalString()) v.tracer.RejectMessage(msg, RejectValidationThrottled) default: @@ -456,7 +456,7 @@ func (v *validation) validateSingleTopic(val *topicVal, src enode.ID, msg *Messa func (val *topicVal) validateMsg(ctx context.Context, src enode.ID, msg *Message) ValidationResult { start := time.Now() defer func() { - log.Debug("validation done; took %s", "time", time.Since(start)) + log.Debug("validation done", "time", time.Since(start)) }() if val.validateTimeout > 0 { @@ -475,7 +475,7 @@ func (val *topicVal) validateMsg(ctx context.Context, src enode.ID, msg *Message return r default: - log.Warn("Unexpected result from validator: %d; ignoring message", "r", r) + log.Warn("Unexpected result from validator; ignoring message", "r", r) return ValidationIgnore } } From b377b4b4de6d9136799fbb834b23dcea1e817924 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 10 Dec 2021 16:40:04 +0800 Subject: [PATCH 120/299] improve discover,Limit the number of attempts discover --- p2p/discover.go | 44 +++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/p2p/discover.go b/p2p/discover.go index 7424644267..9aacd586c5 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -29,13 +29,15 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { // Check there are enough peers if !srv.validPeersExist(topic) { + srv.topicSubscriberMu.RLock() + nodes, ok := srv.topicSubscriber[topic] + if !ok { + srv.topicSubscriberMu.RUnlock() + continue + } + srv.topicSubscriberMu.RUnlock() log.Debug("No peers found subscribed gossip topic . Searching network for peers subscribed to the topic.", "topic", topic) - _, err := srv.FindPeersWithTopic( - ctx, - topic, - srv.Config.MinimumPeersPerTopic, - ) - if err != nil { + if err := srv.FindPeersWithTopic(ctx, topic, nodes, srv.Config.MinimumPeersPerTopic); err != nil { log.Error("Could not search for peers", "err", err) return } @@ -52,33 +54,30 @@ func (srv *Server) validPeersExist(subnetTopic string) bool { return len(numOfPeers) >= srv.Config.MinimumPeersPerTopic } -/* -func (srv *Server) topicToIdx(topic string) uint64 { - return 0 -}*/ - // FindPeersWithTopic performs a network search for peers // subscribed to a particular subnet. Then we try to connect // with those peers. This method will block until the required amount of // peers are found, the method only exits in the event of context timeouts. -func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, threshold int) (bool, error) { +func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes []enode.ID, threshold int) error { if srv.DiscV5 == nil { // return if discovery isn't set - return false, nil + return nil } - //topic += s.Encoding().ProtocolSuffix() iterator := srv.DiscV5.RandomNodes() - iterator = filterNodes(ctx, iterator, srv.filterPeerForTopic(topic)) + iterator = filterNodes(ctx, iterator, srv.filterPeerForTopic(nodes)) currNum := len(srv.pubSubServer.PubSub().ListPeers(topic)) wg := new(sync.WaitGroup) + + try := 0 for { if err := ctx.Err(); err != nil { - return false, err + return err } - if currNum >= threshold { + // Retry at most 3 times + if try >= 3 || currNum >= threshold { break } nodes := enode.ReadNodes(iterator, int(srv.Config.MinimumPeersInTopicSearch)) @@ -92,22 +91,17 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, thresho // Wait for all dials to be completed. wg.Wait() currNum = len(srv.pubSubServer.PubSub().ListPeers(topic)) + try++ } - return true, nil + return nil } // returns a method with filters peers specifically for a particular attestation subnet. -func (srv *Server) filterPeerForTopic(topic string) func(node *enode.Node) bool { +func (srv *Server) filterPeerForTopic(nodes []enode.ID) func(node *enode.Node) bool { return func(node *enode.Node) bool { if !srv.filterPeer(node) { return false } - srv.topicSubscriberMu.RLock() - defer srv.topicSubscriberMu.RUnlock() - nodes, ok := srv.topicSubscriber[topic] - if !ok { - return false - } for _, peer := range nodes { if peer == node.ID() { return true From 426f567faf351fe9a7495d1b9fccaef6c866df32 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 13 Dec 2021 11:28:35 +0800 Subject: [PATCH 121/299] update discover,add check is ready to dial first --- p2p/dial.go | 45 +++++++++++++++++++++++++++++++++------------ p2p/discover.go | 4 ++++ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/p2p/dial.go b/p2p/dial.go index 60ad902abc..e443c5e846 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -107,6 +107,9 @@ type dialScheduler struct { addPeerCh chan *conn remPeerCh chan *conn + checkReadyToDial chan *enode.Node + checkReadyToDialDone chan bool + // Everything below here belongs to loop and // should only be accessed by code on the loop goroutine. dialing map[enode.ID]*dialTask // active tasks @@ -166,18 +169,20 @@ func (cfg dialConfig) withDefaults() dialConfig { func newDialScheduler(config dialConfig, it enode.Iterator, setupFunc dialSetupFunc) *dialScheduler { d := &dialScheduler{ - dialConfig: config.withDefaults(), - setupFunc: setupFunc, - dialing: make(map[enode.ID]*dialTask), - static: make(map[enode.ID]*dialTask), - peers: make(map[enode.ID]struct{}), - doneCh: make(chan *dialTask), - nodesIn: make(chan *enode.Node), - consensusNodesIn: make(chan *dialTask), - addStaticCh: make(chan *enode.Node), - remStaticCh: make(chan *enode.Node), - addPeerCh: make(chan *conn), - remPeerCh: make(chan *conn), + dialConfig: config.withDefaults(), + setupFunc: setupFunc, + dialing: make(map[enode.ID]*dialTask), + static: make(map[enode.ID]*dialTask), + peers: make(map[enode.ID]struct{}), + doneCh: make(chan *dialTask), + nodesIn: make(chan *enode.Node), + consensusNodesIn: make(chan *dialTask), + addStaticCh: make(chan *enode.Node), + remStaticCh: make(chan *enode.Node), + addPeerCh: make(chan *conn), + remPeerCh: make(chan *conn), + checkReadyToDial: make(chan *enode.Node), + checkReadyToDialDone: make(chan bool), } d.lastStatsLog = d.clock.Now() d.ctx, d.cancel = context.WithCancel(context.Background()) @@ -193,6 +198,15 @@ func (d *dialScheduler) stop() { d.wg.Wait() } +func (d *dialScheduler) IsReadyToDial(n *enode.Node) bool { + select { + case d.checkReadyToDial <- n: + return <-d.checkReadyToDialDone + case <-d.ctx.Done(): + } + return false +} + // addStatic adds a static dial candidate. func (d *dialScheduler) addStatic(n *enode.Node) { select { @@ -266,6 +280,13 @@ loop: d.startDial(task) } continue + case node := <-d.checkReadyToDial: + if err := d.checkDial(node); err == nil { + d.checkReadyToDialDone <- true + } else { + d.checkReadyToDialDone <- false + } + continue default: } // Launch new dials if slots are available. diff --git a/p2p/discover.go b/p2p/discover.go index 9aacd586c5..9edbce8c89 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -102,6 +102,10 @@ func (srv *Server) filterPeerForTopic(nodes []enode.ID) func(node *enode.Node) b if !srv.filterPeer(node) { return false } + if !srv.dialsched.IsReadyToDial(node) { + return false + } + for _, peer := range nodes { if peer == node.ID() { return true From 09b5cf3887261240709e84e690de236d54ff6f85 Mon Sep 17 00:00:00 2001 From: benbaley Date: Mon, 13 Dec 2021 14:26:51 +0800 Subject: [PATCH 122/299] fix ut errors --- consensus/cbft/validator/validator.go | 5 +++-- consensus/cbft/validator/validator_test.go | 21 +++++++-------------- core/cbfttypes/type.go | 1 + 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 2a443b8c70..129a0c456f 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -431,10 +431,11 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo var err error var nds *cbfttypes.Validators + needGroup := version >= params.FORKVERSION_0_17_0 //生效后第一个共识周期的switchpoint是旧值,此时不能切换 //判断依据是新validators和current完全相同且nextValidators为空 - if !isElection && vp.nextValidators == nil { + if !isElection && vp.nextValidators == nil && needGroup { nds, err = vp.agency.GetValidators(NextRound(blockNumber)) if err != nil { log.Error("Get validator error", "blockNumber", blockNumber, "err", err) @@ -453,7 +454,7 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo } //分组提案生效后第一个共识round到ElectionPoint时初始化分组信息 if !vp.grouped { - if version >= params.FORKVERSION_0_17_0 { + if needGroup { vp.grouped = true vp.groupValidatorsLimit = xcom.MaxGroupValidators() vp.coordinatorLimit = xcom.CoordinatorsLimit() diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index 941a37cc0a..4ad514acd3 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -619,27 +619,22 @@ func TestValidatorPoolReset(t *testing.T) { } func TestValidatorGrouped(t *testing.T) { + nodes := newTestNodeByNum(100) vs := newValidators(nodes, 0) vs.Grouped() assert.Equal(t, 4, len(vs.GroupNodes)) - assert.Equal(t, 25, len(vs.GroupNodes[3].Nodes)) assert.Equal(t, uint32(74), vs.GroupNodes[2].Nodes[24].Index) assert.Equal(t, 5, len(vs.GroupNodes[2].Units)) assert.Equal(t, uint32(74), vs.GroupNodes[2].Units[4][4]) - vs.Grouped() - assert.Equal(t, 4, len(vs.GroupNodes)) assert.Equal(t, 25, len(vs.GroupNodes[0].Nodes)) assert.Equal(t, 25, len(vs.GroupNodes[2].Nodes)) assert.Equal(t, 25, len(vs.GroupNodes[3].Nodes)) assert.Equal(t, uint32(79), vs.GroupNodes[3].Units[0][4]) - vs.Grouped() - assert.Equal(t, 6, len(vs.GroupNodes)) - assert.Equal(t, 17, len(vs.GroupNodes[0].Nodes)) - assert.Equal(t, 6, len(vs.GroupNodes[2].Units)) - assert.Equal(t, uint32(48), vs.GroupNodes[2].Units[4][2]) + assert.Equal(t, 5, len(vs.GroupNodes[2].Units)) + assert.Equal(t, uint32(72), vs.GroupNodes[2].Units[4][2]) } func TestGetGroupID(t *testing.T) { @@ -649,17 +644,17 @@ func TestGetGroupID(t *testing.T) { vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), true, new(event.TypeMux)) grpID, _ := vp.GetGroupID(0, nodes[0].Node.ID()) - assert.Equal(t, 0, grpID) + assert.Equal(t, uint32(0), grpID) } func TestGetUintID(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNodeByNum(100) agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, new(event.TypeMux)) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), true, new(event.TypeMux)) - untID, _ := vp.GetGroupID(0, nodes[0].Node.ID()) - assert.Equal(t, 0, untID) + unitID, _ := vp.GetGroupID(0, nodes[0].Node.ID()) + assert.Equal(t, uint32(0), unitID) } func TestUpdate(t *testing.T) { @@ -681,8 +676,6 @@ func TestUpdate(t *testing.T) { next.Grouped() assert.NotEqual(t, vp.currentValidators, next) - t.Log("TestUpdate", "vp.lastNumber", vp.lastNumber) - t.Log("TestUpdate", "next.lastNumber", next.ValidBlockNumber) vp.Update(250, 0, false, 0, eventMux) assert.Nil(t, vp.nextValidators) vp.Update(vp.lastNumber, 0, true, 0, eventMux) diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 5e1afb40ee..806e7024d7 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -338,6 +338,7 @@ func (gvs *GroupValidators) GroupedUnits() { } } +// return groupID func (gvs *GroupValidators) GetGroupID() uint32 { return gvs.groupID } From bbd9bf150bdf6ba0a9809599ece211027637f429 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Mon, 13 Dec 2021 16:46:49 +0800 Subject: [PATCH 123/299] Perfect unit test --- p2p/pubsub/gossipsub_test.go | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/p2p/pubsub/gossipsub_test.go b/p2p/pubsub/gossipsub_test.go index 8a57927d14..36021b7a07 100644 --- a/p2p/pubsub/gossipsub_test.go +++ b/p2p/pubsub/gossipsub_test.go @@ -1585,13 +1585,12 @@ func TestGossipsubOpportunisticGrafting(t *testing.T) { // connect the real hosts with degree 5 connectSome(t, hosts[:10], 5) - // TODO check - /*// sybil squatters for the remaining 40 hosts + // sybil squatters for the remaining 40 hosts + getGossipsubs(ctx, hosts[10:]) for _, h := range hosts[10:] { squatter := &sybilSquatter{h: h} - // TODO pubSub check - h.SetStreamHandler(GossipSubID_v10, squatter.handleStream) - }*/ + h.SetStreamHandler(GossipSubID_v11, squatter.handleStream) + } // connect all squatters to every real host for _, squatter := range hosts[10:] { @@ -1652,42 +1651,34 @@ func TestGossipsubOpportunisticGrafting(t *testing.T) { } } -// TODO pubSub check -/*type sybilSquatter struct { +type sybilSquatter struct { h Host } func (sq *sybilSquatter) handleStream(s Stream) { defer s.Close(nil) - - os, err := sq.h.NewStream(context.Background(), s.Conn().RemotePeer().ID(), GossipSubID_v10) + os, err := sq.h.NewStream(context.Background(), s.Conn().RemotePeer().ID(), GossipSubID_v11) if err != nil { panic(err) } // send a subscription for test in the output stream to become candidate for GRAFT // and then just read and ignore the incoming RPCs - r := protoio.NewDelimitedReader(s, 1<<20) - w := protoio.NewDelimitedWriter(os) truth := true topic := "test" - err = w.WriteMsg(&pb.RPC{Subscriptions: []*pb.RPC_SubOpts{{Subscribe: &truth, Topicid: &topic}}}) + err = os.Write(&message.RPC{Subscriptions: []*message.RPC_SubOpts{{Subscribe: &truth, Topicid: &topic}}}) if err != nil { panic(err) } - var rpc pb.RPC for { - rpc.Reset() - err = r.ReadMsg(&rpc) + var rpc message.RPC + err = os.Read(&rpc) if err != nil { - if err != io.EOF { - s.Reset() - } return } } -}*/ +} func TestGossipsubPeerScoreInspect(t *testing.T) { // this test exercises the code path sof peer score inspection From 598c8c6946b8fb957ce8f9905ed12ed4db3c632d Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Mon, 13 Dec 2021 17:46:40 +0800 Subject: [PATCH 124/299] 1.Add topic subscription, create subscription channel 2.Add metrics 3.Optimize the process --- consensus/bft_mock.go | 7 -- consensus/cbft/cbft.go | 8 +- consensus/cbft/consensus_process.go | 125 +++++++------------------- consensus/cbft/metrics.go | 33 ++++++- consensus/cbft/network/pubsub.go | 39 +++++++- consensus/cbft/protocols/protocol.go | 19 ++-- consensus/cbft/rg_broadcast.go | 60 ++++++------- consensus/cbft/sync_process.go | 42 +++++++-- consensus/cbft/types/crypto.go | 11 +-- consensus/cbft/utils/bit_array.go | 2 - consensus/cbft/validator/validator.go | 1 - 11 files changed, 188 insertions(+), 159 deletions(-) diff --git a/consensus/bft_mock.go b/consensus/bft_mock.go index 501be4613e..aa67cb3c35 100644 --- a/consensus/bft_mock.go +++ b/consensus/bft_mock.go @@ -94,13 +94,11 @@ func (bm *BftMock) GetPrepareQC(number uint64) *ctypes.QuorumCert { // FastSyncCommitHead is a fake interface, no need to implement. func (bm *BftMock) FastSyncCommitHead(block *types.Block) error { - // todo implement me return nil } // Start is a fake interface, no need to implement. func (bm *BftMock) Start(chain ChainReader, blockCacheWriter BlockCache, pool TxPoolReset, agency Agency) error { - // todo implement me return nil } @@ -117,31 +115,26 @@ func (bm *BftMock) CalcBlockDeadline(timePoint time.Time) time.Time { // CalcNextBlockTime is a fake interface, no need to implement. func (bm *BftMock) CalcNextBlockTime(timePoint time.Time) time.Time { - // todo implement me return time.Now() } // GetBlockWithoutLock is a fake interface, no need to implement. func (bm *BftMock) GetBlockWithoutLock(hash common.Hash, number uint64) *types.Block { - // todo implement me return nil } // IsSignedBySelf is a fake interface, no need to implement. func (bm *BftMock) IsSignedBySelf(sealHash common.Hash, header *types.Header) bool { - // todo implement me return true } // Evidences is a fake interface, no need to implement. func (bm *BftMock) Evidences() string { - // todo implement me return "" } // UnmarshalEvidence is a fake interface, no need to implement. func (bm *BftMock) UnmarshalEvidence(data []byte) (consensus.Evidences, error) { - // todo implement me return nil, nil } diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 2aabcb459d..3ab2c25ad4 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -270,7 +270,7 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCache consensus.BlockC go cbft.network.Start() // Data required to initialize pubsub - cbft.pubSub.Init(cbft.config, cbft.network.GetPeer, cbft.network.HandleRGMsg) + cbft.pubSub.Init(cbft.config, cbft.network.GetPeer, cbft.network.HandleRGMsg, cbft.eventMux) if cbft.config.Option.Node == nil { cbft.config.Option.Node = enode.NewV4(&cbft.nodeServiceContext.NodePriKey().PublicKey, nil, 0, 0) @@ -381,7 +381,6 @@ func (cbft *Cbft) recordMessage(msg *ctypes.MsgInfo) error { if int64(count) > cbft.config.Option.MaxQueuesLimit { log.Warn("Discarded message, exceeded allowance for the layer of cbft", "peer", msg.PeerID, "msgHash", msg.Msg.MsgHash().TerminalString()) // Need further confirmation. - // todo: Is the program exiting or dropping the message here? return fmt.Errorf("execeed max queues limit") } cbft.queues[msg.PeerID] = count @@ -1121,6 +1120,7 @@ func (cbft *Cbft) Close() error { } cbft.bridge.Close() cbft.RGBroadcastManager.Close() + cbft.pubSub.Stop() return nil } @@ -1439,6 +1439,10 @@ func (cbft *Cbft) getGroupByValidatorID(epoch uint64, nodeID enode.ID) (uint32, return cbft.validatorPool.GetGroupByValidatorID(epoch, nodeID) } +func (cbft *Cbft) getGroupIndexes(epoch uint64) map[uint32][]uint32 { + return cbft.validatorPool.GetGroupIndexes(epoch) +} + func (cbft *Cbft) isProposer(epoch, viewNumber uint64, nodeIndex uint32) bool { if err := cbft.validatorPool.EnableVerifyEpoch(epoch); err != nil { return false diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 827c582aa5..182100c9a2 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -412,10 +412,8 @@ func (cbft *Cbft) OnInsertQCBlock(blocks []*types.Block, qcs []*ctypes.QuorumCer if len(blocks) != len(qcs) { return fmt.Errorf("block qc is inconsistent") } - //todo insert tree, update view for i := 0; i < len(blocks); i++ { block, qc := blocks[i], qcs[i] - //todo verify qc if err := cbft.safetyRules.QCBlockRules(block, qc); err != nil { if err.NewView() { @@ -553,7 +551,6 @@ func (cbft *Cbft) onAsyncExecuteStatus(s *executor.BlockExecuteStatus) { // Sign the block that has been executed // Every time try to trigger a send PrepareVote func (cbft *Cbft) signBlock(hash common.Hash, number uint64, index uint32) error { - // todo sign vote // parentQC added when sending // Determine if the current consensus node is node, err := cbft.isCurrentValidator() @@ -566,7 +563,7 @@ func (cbft *Cbft) signBlock(hash common.Hash, number uint64, index uint32) error BlockHash: hash, BlockNumber: number, BlockIndex: index, - ValidatorIndex: uint32(node.Index), + ValidatorIndex: node.Index, } if err := cbft.signMsgByBls(prepareVote); err != nil { @@ -644,84 +641,10 @@ func (cbft *Cbft) publishTopicMsg(msg ctypes.ConsensusMsg) error { return cbft.pubSub.Publish(topic, protocols.MessageType(msg), msg) } -//func (cbft *Cbft) trySendRGBlockQuorumCert() { -// // Check timeout -// if cbft.state.IsDeadline() { -// cbft.log.Debug("Current view had timeout, Refuse to send RGBlockQuorumCert") -// return -// } -// -// node, err := cbft.isCurrentValidator() -// if err != nil || node == nil { -// cbft.log.Debug("Current node is not validator, no need to send RGBlockQuorumCert") -// return -// } -// -// groupID, _, err := cbft.getGroupByValidatorID(cbft.state.Epoch(), cbft.NodeID()) -// if err != nil { -// cbft.log.Debug("Current node is not validator, no need to send RGBlockQuorumCert") -// return -// } -// -// enoughVotes := func(blockIndex, groupID uint32) (bool, *ctypes.QuorumCert, *ctypes.QuorumCert) { -// threshold := cbft.groupThreshold(cbft.state.Epoch(), groupID) -// groupVotes := cbft.groupPrepareVotes(cbft.state.Epoch(), blockIndex, groupID) -// if len(groupVotes) >= threshold { -// // generatePrepareQC by votes -// rgqc := cbft.generatePrepareQC(groupVotes) -// // get parentQC -// var parentQC *ctypes.QuorumCert -// for _, v := range groupVotes { -// parentQC = v.ParentQC -// break -// } -// // Add SelectRGQuorumCerts -// cbft.state.AddSelectRGQuorumCerts(blockIndex, groupID, rgqc, parentQC) -// return true, rgqc, parentQC -// } -// return false, nil, nil -// } -// -// alreadyRGBlockQuorumCerts := func(blockIndex, groupID uint32) (bool, *ctypes.QuorumCert, *ctypes.QuorumCert) { -// blockQC, parentQC := cbft.state.FindMaxGroupRGQuorumCert(blockIndex, groupID) -// if blockQC != nil { -// return true, blockQC, parentQC -// } -// return false, blockQC, parentQC -// } -// -// for i := uint32(0); i < cbft.config.Sys.Amount; i++ { -// if cbft.state.FindRGBlockQuorumCerts(i, groupID, node.Index) != nil { -// cbft.log.Debug("Had async send RGBlockQuorumCert, don't send again", "blockIndex", i, "groupID", groupID, "nodeIndex", node.Index) -// continue -// } -// -// exists, blockQC, parentQC := alreadyRGBlockQuorumCerts(i, groupID) -// if !exists { -// exists, blockQC, parentQC = enoughVotes(i, groupID) -// if !exists { -// continue -// } -// } -// cbft.state.AddRGBlockQuorumCert(node.Index, &protocols.RGBlockQuorumCert{ -// GroupID: groupID, -// BlockQC: blockQC, -// ValidatorIndex: node.Index, -// ParentQC: parentQC, -// }) -// -// cbft.RGBroadcastManager.AsyncSendRGQuorumCert(&awaitingRGBlockQC{ -// groupID: groupID, -// blockIndex: i, -// }) -// cbft.log.Debug("Async send RGBlockQuorumCert", "blockIndex", i, "groupID", groupID, "nodeIndex", node.Index) -// } -//} - func (cbft *Cbft) trySendRGBlockQuorumCert() { // Check timeout if cbft.state.IsDeadline() { - cbft.log.Debug("Current view had timeout, Refuse to send RGBlockQuorumCert") + cbft.log.Debug("Current view had timeout, refuse to send RGBlockQuorumCert") return } @@ -751,18 +674,24 @@ func (cbft *Cbft) trySendRGBlockQuorumCert() { } // Add SelectRGQuorumCerts cbft.state.AddSelectRGQuorumCerts(blockIndex, groupID, rgqc, parentQC) + blockGroupQCBySelfCounter.Inc(1) return true } return false } alreadyRGBlockQuorumCerts := func(blockIndex, groupID uint32) bool { - return cbft.state.SelectRGQuorumCertsLen(blockIndex, groupID) > 0 + len := cbft.state.SelectRGQuorumCertsLen(blockIndex, groupID) + if len > 0 { + blockGroupQCByOtherCounter.Inc(1) + return true + } + return false } for index := uint32(0); index <= cbft.state.MaxViewBlockIndex(); index++ { if cbft.state.HadSendRGBlockQuorumCerts(index) { - cbft.log.Debug("Had async send RGBlockQuorumCert, don't send again", "blockIndex", index, "groupID", groupID) + cbft.log.Debug("RGBlockQuorumCert has been sent, no need to send again", "blockIndex", index, "groupID", groupID) continue } @@ -772,18 +701,15 @@ func (cbft *Cbft) trySendRGBlockQuorumCert() { blockIndex: index, }) cbft.state.AddSendRGBlockQuorumCerts(index) - cbft.log.Debug("Async send RGBlockQuorumCert", "blockIndex", index, "groupID", groupID, "nodeIndex") + cbft.log.Debug("Send RGBlockQuorumCert asynchronously", "blockIndex", index, "groupID", groupID) + // record metrics + block := cbft.state.ViewBlockByIndex(index) + blockGroupQCTimer.UpdateSince(time.Unix(int64(block.Time()), 0)) } } } func (cbft *Cbft) trySendRGViewChangeQuorumCert() { - // Check timeout - if cbft.state.IsDeadline() { - cbft.log.Debug("Current view had timeout, Refuse to send RGViewChangeQuorumCert") - return - } - groupID, _, err := cbft.getGroupByValidatorID(cbft.state.Epoch(), cbft.Node().ID()) if err != nil { cbft.log.Debug("Current node is not validator, no need to send RGViewChangeQuorumCert") @@ -805,17 +731,23 @@ func (cbft *Cbft) trySendRGViewChangeQuorumCert() { } // Add SelectRGViewChangeQuorumCerts cbft.state.AddSelectRGViewChangeQuorumCerts(groupID, rgqc, prepareQCs) + viewGroupQCBySelfCounter.Inc(1) return true } return false } alreadyRGViewChangeQuorumCerts := func(groupID uint32) bool { - return cbft.state.SelectRGViewChangeQuorumCertsLen(groupID) > 0 + len := cbft.state.SelectRGViewChangeQuorumCertsLen(groupID) + if len > 0 { + viewGroupQCByOtherCounter.Inc(1) + return true + } + return false } if cbft.state.HadSendRGViewChangeQuorumCerts(cbft.state.ViewNumber()) { - cbft.log.Debug("Had async send RGViewChangeQuorumCert, don't send again", "groupID", groupID) + cbft.log.Debug("RGViewChangeQuorumCert has been sent, no need to send again", "groupID", groupID) return } @@ -825,7 +757,8 @@ func (cbft *Cbft) trySendRGViewChangeQuorumCert() { viewNumber: cbft.state.ViewNumber(), }) cbft.state.AddSendRGViewChangeQuorumCerts(cbft.state.ViewNumber()) - cbft.log.Debug("Async send RGBlockQuorumCert", "groupID", groupID) + cbft.log.Debug("Send RGViewChangeQuorumCert asynchronously", "groupID", groupID) + viewGroupQCTimer.UpdateSince(cbft.state.Deadline()) } } @@ -1003,6 +936,7 @@ func (cbft *Cbft) findQCBlock() { size := cbft.state.PrepareVoteLenByIndex(next) if size >= threshold { qc = cbft.generatePrepareQC(cbft.state.AllPrepareVoteByIndex(next)) + blockWholeQCByVotesCounter.Inc(1) cbft.log.Debug("Enough prepareVote have been received, generate prepareQC", "qc", qc.String()) } return qc.Len() >= threshold @@ -1018,6 +952,7 @@ func (cbft *Cbft) findQCBlock() { } if size >= threshold { qc = cbft.combinePrepareQC(rgqcs) + blockWholeQCByRGQCCounter.Inc(1) cbft.log.Debug("Enough RGBlockQuorumCerts have been received, combine prepareQC", "qc", qc.String()) } return qc.Len() >= threshold @@ -1038,6 +973,7 @@ func (cbft *Cbft) findQCBlock() { qc.AddSign(v.Signature, v.BlockIndex) } } + blockWholeQCByCombineCounter.Inc(1) cbft.log.Debug("Enough RGBlockQuorumCerts and prepareVote have been received, combine prepareQC", "qc", qc.String()) } return qc.Len() >= threshold @@ -1055,6 +991,7 @@ func (cbft *Cbft) findQCBlock() { cbft.network.Broadcast(&protocols.BlockQuorumCert{BlockQC: qc}) // metrics blockQCCollectedGauage.Update(int64(block.Time())) + blockWholeQCTimer.UpdateSince(time.Unix(int64(block.Time()), 0)) cbft.trySendPrepareVote() } } @@ -1152,6 +1089,7 @@ func (cbft *Cbft) tryChangeView() { size := cbft.state.ViewChangeLen() if size >= threshold { viewChangeQC = cbft.generateViewChangeQC(cbft.state.AllViewChange()) + viewWholeQCByVcsCounter.Inc(1) cbft.log.Info("Receive enough viewchange, generate viewChangeQC", "view", cbft.state.ViewString(), "viewChangeQC", viewChangeQC.String()) } return viewChangeQC.HasLength() >= threshold @@ -1167,6 +1105,7 @@ func (cbft *Cbft) tryChangeView() { } if size >= threshold { viewChangeQC = cbft.combineViewChangeQC(viewChangeQCs) + viewWholeQCByRGQCCounter.Inc(1) cbft.log.Debug("Enough RGViewChangeQuorumCerts have been received, combine ViewChangeQC", "qc", qc.String()) } return viewChangeQC.HasLength() >= threshold @@ -1187,6 +1126,7 @@ func (cbft *Cbft) tryChangeView() { cbft.MergeViewChange(viewChangeQC, v) } } + viewWholeQCByCombineCounter.Inc(1) cbft.log.Debug("Enough RGViewChangeQuorumCerts and viewchange have been received, combine ViewChangeQC", "qc", qc.String()) } return viewChangeQC.HasLength() >= threshold @@ -1197,6 +1137,7 @@ func (cbft *Cbft) tryChangeView() { } if alreadyQC() { + viewWholeQCTimer.UpdateSince(cbft.state.Deadline()) cbft.tryChangeViewByViewChange(viewChangeQC) } } @@ -1342,7 +1283,7 @@ func (cbft *Cbft) changeView(epoch, viewNumber uint64, block *types.Block, qc *c cbft.state.SetViewTimer(interval()) cbft.state.SetLastViewChangeQC(viewChangeQC) - // metrics. + // record metrics viewNumberGauage.Update(int64(viewNumber)) epochNumberGauage.Update(int64(epoch)) viewChangedTimer.UpdateSince(time.Unix(int64(block.Time()), 0)) diff --git a/consensus/cbft/metrics.go b/consensus/cbft/metrics.go index 8ebb0ddbb8..857871e2ca 100644 --- a/consensus/cbft/metrics.go +++ b/consensus/cbft/metrics.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbft import ( @@ -43,4 +42,36 @@ var ( highestQCNumberGauage = metrics.NewRegisteredGauge("cbft/gauage/block/qc/number", nil) highestLockedNumberGauage = metrics.NewRegisteredGauge("cbft/gauage/block/locked/number", nil) highestCommitNumberGauage = metrics.NewRegisteredGauge("cbft/gauage/block/commit/number", nil) + + // for rand-grouped-consensus block + upgradeCoordinatorBlockCounter = metrics.NewRegisteredCounter("cbft/counter/block/upgradeCoordinator/count", nil) + + blockGroupQCBySelfCounter = metrics.NewRegisteredCounter("cbft/counter/block/groupqc/self/count", nil) // Own group + blockGroupQCByOtherCounter = metrics.NewRegisteredCounter("cbft/counter/block/groupqc/other/count", nil) // Own group + + blockWholeQCByVotesCounter = metrics.NewRegisteredCounter("cbft/counter/block/wholeqc/votes/count", nil) + blockWholeQCByRGQCCounter = metrics.NewRegisteredCounter("cbft/counter/block/wholeqc/rgqc/count", nil) + blockWholeQCByCombineCounter = metrics.NewRegisteredCounter("cbft/counter/block/wholeqc/combine/count", nil) + + blockGroupQCTimer = metrics.NewRegisteredTimer("cbft/timer/block/group/qc", nil) // Own group + blockWholeQCTimer = metrics.NewRegisteredTimer("cbft/timer/block/whole/qc", nil) + + missRGBlockQuorumCertsGauage = metrics.NewRegisteredGauge("cbft/gauage/block/miss/rgqc", nil) + missVotesGauage = metrics.NewRegisteredGauge("cbft/gauage/block/miss/vote", nil) + + // for rand-grouped-consensus viewChange + upgradeCoordinatorViewCounter = metrics.NewRegisteredCounter("cbft/counter/view/upgradeCoordinator/count", nil) + + viewGroupQCBySelfCounter = metrics.NewRegisteredCounter("cbft/counter/view/groupqc/self/count", nil) + viewGroupQCByOtherCounter = metrics.NewRegisteredCounter("cbft/counter/view/groupqc/other/count", nil) // Own group + + viewWholeQCByVcsCounter = metrics.NewRegisteredCounter("cbft/counter/view/wholeqc/vcs/count", nil) + viewWholeQCByRGQCCounter = metrics.NewRegisteredCounter("cbft/counter/view/wholeqc/rgqc/count", nil) + viewWholeQCByCombineCounter = metrics.NewRegisteredCounter("cbft/counter/view/wholeqc/combine/count", nil) + + viewGroupQCTimer = metrics.NewRegisteredTimer("cbft/timer/view/group/qc", nil) // Own group + viewWholeQCTimer = metrics.NewRegisteredTimer("cbft/timer/view/whole/qc", nil) + + missRGViewQuorumCertsGauage = metrics.NewRegisteredGauge("cbft/gauage/view/miss/rgqc", nil) + missVcsGauage = metrics.NewRegisteredGauge("cbft/gauage/view/miss/vote", nil) ) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index b70ff9b1cc..7e23ea93ea 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -21,6 +21,8 @@ import ( "context" "errors" "fmt" + "github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" + "github.com/AlayaNetwork/Alaya-Go/event" "sync" "github.com/AlayaNetwork/Alaya-Go/common" @@ -68,6 +70,8 @@ type PubSub struct { // The set of topics we are subscribed to mySubs map[string]*pubsub.Subscription sync.Mutex + + quit chan struct{} } // Protocol.Run() @@ -120,13 +124,42 @@ func NewPubSub(server *p2p.PubSubServer) *PubSub { pss: server, topics: make(map[string]*pubsub.Topic), mySubs: make(map[string]*pubsub.Subscription), + quit: make(chan struct{}), } } -func (ps *PubSub) Init(config ctypes.Config, get getByIDFunc, onReceive receiveCallback) { +func (ps *PubSub) Init(config ctypes.Config, get getByIDFunc, onReceive receiveCallback, eventMux *event.TypeMux) { ps.config = config ps.getPeerById = get ps.onReceive = onReceive + + go ps.watching(eventMux) +} + +func (ps *PubSub) watching(eventMux *event.TypeMux) { + events := eventMux.Subscribe(cbfttypes.GroupTopicEvent{}, cbfttypes.ExpiredGroupTopicEvent{}) + defer events.Unsubscribe() + + for { + select { + case ev := <-events.Chan(): + if ev == nil { + continue + } + switch data := ev.Data.(type) { + case cbfttypes.GroupTopicEvent: + log.Trace("Received GroupTopicEvent", "topic", data.Topic) + ps.Subscribe(data.Topic) + case cbfttypes.ExpiredGroupTopicEvent: + log.Trace("Received ExpiredGroupTopicEvent", "topic", data.Topic) + ps.Cancel(data.Topic) + default: + log.Error("Received unexcepted event") + } + case <-ps.quit: + return + } + } } //Subscribe subscribe a topic @@ -230,3 +263,7 @@ func (ps *PubSub) Publish(topic string, code uint64, data interface{}) error { return t.Publish(context.Background(), env) } + +func (ps *PubSub) Stop() { + close(ps.quit) +} diff --git a/consensus/cbft/protocols/protocol.go b/consensus/cbft/protocols/protocol.go index f5a20c53d6..5c856f107e 100644 --- a/consensus/cbft/protocols/protocol.go +++ b/consensus/cbft/protocols/protocol.go @@ -456,7 +456,6 @@ func (s *GetPrepareBlock) BHash() common.Hash { } // GetBlockQuorumCert is the protocol message for obtaining an aggregated signature. -// todo: Need to determine the attribute field - ParentQC. type GetBlockQuorumCert struct { BlockHash common.Hash `json:"blockHash"` // The hash of the block to be acquired. BlockNumber uint64 `json:"blockNumber"` // The number of the block to be acquired. @@ -833,11 +832,16 @@ func (rgb *RGBlockQuorumCert) MsgHash() common.Hash { v := utils.BuildHash(RGBlockQuorumCertMsg, utils.MergeBytes( common.Uint32ToBytes(rgb.GroupID), - common.Uint32ToBytes(rgb.ValidatorIndex), // To upgrade a slave node to a master node, it is necessary to determine whether the previous marshalling node sends RGBlockQuorumCertMsg, so the ValidatorIndex needs to be added here + common.Uint32ToBytes(rgb.ValidatorIndex), + common.Uint64ToBytes(rgb.BlockQC.Epoch), + common.Uint64ToBytes(rgb.BlockQC.ViewNumber), rgb.BlockQC.BlockHash.Bytes(), common.Uint64ToBytes(rgb.BlockQC.BlockNumber), - rgb.BlockQC.Signature.Bytes(), - rgb.BlockQC.ValidatorSet.Bytes())) + common.Uint32ToBytes(rgb.BlockQC.BlockIndex), + rgb.Signature.Bytes(), + //rgb.BlockQC.Signature.Bytes(), + //rgb.BlockQC.ValidatorSet.Bytes() + )) rgb.messageHash.Store(v) return v @@ -915,16 +919,19 @@ func (rgv *RGViewChangeQuorumCert) MsgHash() common.Hash { if mhash := rgv.messageHash.Load(); mhash != nil { return mhash.(common.Hash) } + epoch, viewNumber, blockEpoch, blockViewNumber, hash, number := rgv.ViewChangeQC.MaxBlock() mv := utils.BuildHash(RGViewChangeQuorumCertMsg, utils.MergeBytes( common.Uint32ToBytes(rgv.GroupID), - common.Uint32ToBytes(rgv.ValidatorIndex), // To upgrade a slave node to a master node, it is necessary to determine whether the previous marshalling node sends RGBlockQuorumCertMsg, so the ValidatorIndex needs to be added here + common.Uint32ToBytes(rgv.ValidatorIndex), common.Uint64ToBytes(epoch), common.Uint64ToBytes(viewNumber), common.Uint64ToBytes(blockEpoch), common.Uint64ToBytes(blockViewNumber), hash.Bytes(), - common.Uint64ToBytes(number))) + common.Uint64ToBytes(number), + rgv.Signature.Bytes(), + )) rgv.messageHash.Store(mv) return mv } diff --git a/consensus/cbft/rg_broadcast.go b/consensus/cbft/rg_broadcast.go index 565b9bd6bc..f97626edb4 100644 --- a/consensus/cbft/rg_broadcast.go +++ b/consensus/cbft/rg_broadcast.go @@ -148,7 +148,6 @@ func (m *RGBroadcastManager) needBroadcast(a awaiting) bool { } } -// cbft层在调用AsyncSendRGBlockQuorumCert时需判断view和viewTimeout等情况 func (m *RGBroadcastManager) broadcast(a awaiting) { m.mux.Lock() defer m.mux.Unlock() @@ -178,9 +177,18 @@ func (m *RGBroadcastManager) broadcast(a awaiting) { } } -//func (m *RGBroadcastManager) allowRGBlockQuorumCert(rgMsg ctypes.ConsensusMsg) bool { -// return rgMsg.EpochNum() == m.cbft.state.Epoch() && rgMsg.ViewNum() == m.cbft.state.ViewNumber() -//} +func (m *RGBroadcastManager) allowRGBlockQuorumCert(a awaiting) bool { + switch a.(type) { + case *awaitingRGBlockQC: + if m.cbft.state.IsDeadline() { + m.cbft.log.Debug("Current view had timeout, refuse to send RGBlockQuorumCert") + return false + } + case *awaitingRGViewQC: + return true + } + return true +} func (m *RGBroadcastManager) upgradeCoordinator(a awaiting) bool { // Check whether the current node is the first group member @@ -210,12 +218,23 @@ func (m *RGBroadcastManager) upgradeCoordinator(a awaiting) bool { } if !m.enoughCoordinator(groupID, unitID, coordinatorIndexes, receiveIndexes) { m.cbft.log.Warn("Upgrade the current node to Coordinator", "groupID", groupID, "unitID", unitID, "coordinatorIndexes", coordinatorIndexes, "receiveIndexes", receiveIndexes) + m.recordMetrics(a) return true } m.cbft.log.Debug("Enough Coordinator, no need to upgrade to Coordinator", "groupID", groupID, "unitID", unitID, "coordinatorIndexes", coordinatorIndexes, "receiveIndexes", receiveIndexes) return false } +func (m *RGBroadcastManager) recordMetrics(a awaiting) { + switch a.(type) { + case *awaitingRGBlockQC: + upgradeCoordinatorBlockCounter.Inc(1) + case *awaitingRGViewQC: + upgradeCoordinatorViewCounter.Inc(1) + default: + } +} + func (m *RGBroadcastManager) enoughCoordinator(groupID, unitID uint32, coordinatorIndexes [][]uint32, receiveIndexes []uint32) bool { enough := func() int { // The total number of validators in the current group @@ -247,38 +266,10 @@ func (m *RGBroadcastManager) countCoordinator(unitID uint32, coordinatorIndexes } func (m *RGBroadcastManager) broadcastFunc(a awaiting) { - if m.cbft.state.IsDeadline() { - m.cbft.log.Debug("Current view had timeout, Refuse to send RGQuorumCert") + if !m.allowRGBlockQuorumCert(a) { return } - //alreadyQCBlock := func() bool { - // if _, ok := rgMsg.(*protocols.RGBlockQuorumCert); ok { - // return m.cbft.blockTree.FindBlockByHash(rgMsg.BHash()) != nil || rgMsg.BlockNum() <= m.cbft.state.HighestLockBlock().NumberU64() - // } - // return false - //} - - //if !m.allowRGBlockQuorumCert(rgMsg) || alreadyQCBlock() { - // return - //} - - //findParentQC := func(blockIndex, groupID uint32) *ctypes.QuorumCert { - // votes := m.cbft.state.AllPrepareVoteByIndex(blockIndex) - // if votes != nil && len(votes) > 0 { - // for _, v := range votes { - // return v.ParentQC - // } - // } - // rgqcs := m.cbft.state.FindGroupRGBlockQuorumCerts(blockIndex, groupID) - // if rgqcs != nil && len(rgqcs) > 0 { - // for _, qc := range rgqcs { - // return qc.ParentQC - // } - // } - // return nil - //} - if !m.upgradeCoordinator(a) { return } @@ -292,7 +283,6 @@ func (m *RGBroadcastManager) broadcastFunc(a awaiting) { m.mux.Lock() defer m.mux.Unlock() - // TODO 如果该blockIndex已经qc,是否不用发送分组聚合签名 switch msg := a.(type) { case *awaitingRGBlockQC: // Query the QuorumCert with the largest number of signatures in the current group @@ -301,7 +291,6 @@ func (m *RGBroadcastManager) broadcastFunc(a awaiting) { m.cbft.log.Error("Cannot find the RGBlockQuorumCert of the current group", "blockIndex", msg.blockIndex, "groupID", msg.GroupID()) return } - //ParentQC := findParentQC(msg.blockIndex, msg.GroupID()) if blockQC.BlockNumber != 1 && parentQC == nil { m.cbft.log.Error("Cannot find the ParentQC corresponding to the current blockQC", "blockIndex", msg.blockIndex, "blockNumber", blockQC.BlockNumber, "groupID", msg.GroupID()) return @@ -344,6 +333,7 @@ func (m *RGBroadcastManager) broadcastFunc(a awaiting) { } // AsyncSendRGQuorumCert queues list of RGQuorumCert propagation to a remote peer. +// Before calling this function, it will be judged whether the current node is validator func (m *RGBroadcastManager) AsyncSendRGQuorumCert(a awaiting) { select { case m.broadcastCh <- a: diff --git a/consensus/cbft/sync_process.go b/consensus/cbft/sync_process.go index 8df0081828..a7bf5b2372 100644 --- a/consensus/cbft/sync_process.go +++ b/consensus/cbft/sync_process.go @@ -787,7 +787,7 @@ func (cbft *Cbft) MissingViewChangeNodes() (v *protocols.GetViewChange, err erro } func (cbft *Cbft) KnownVoteIndexes(blockIndex uint32) []uint32 { - groupNodes := cbft.validatorPool.GetGroupIndexes(cbft.state.Epoch()) + groupNodes := cbft.getGroupIndexes(cbft.state.Epoch()) allVotes := cbft.state.AllPrepareVoteByIndex(blockIndex) known := make([]uint32, 0) for groupID, indexes := range groupNodes { @@ -795,7 +795,7 @@ func (cbft *Cbft) KnownVoteIndexes(blockIndex uint32) []uint32 { for _, index := range indexes { if _, ok := allVotes[index]; ok { known = append(known, index) - } else if qc != nil && qc.HasSign(index) { + } else if qc.HasSign(index) { known = append(known, index) } } @@ -804,7 +804,7 @@ func (cbft *Cbft) KnownVoteIndexes(blockIndex uint32) []uint32 { } func (cbft *Cbft) KnownViewChangeIndexes() []uint32 { - groupNodes := cbft.validatorPool.GetGroupIndexes(cbft.state.Epoch()) + groupNodes := cbft.getGroupIndexes(cbft.state.Epoch()) allViewChanges := cbft.state.AllViewChange() known := make([]uint32, 0) for groupID, indexes := range groupNodes { @@ -812,7 +812,7 @@ func (cbft *Cbft) KnownViewChangeIndexes() []uint32 { for _, index := range indexes { if _, ok := allViewChanges[index]; ok { known = append(known, index) - } else if qc != nil && qc.HasSign(index) { + } else if qc.HasSign(index) { known = append(known, index) } } @@ -821,16 +821,25 @@ func (cbft *Cbft) KnownViewChangeIndexes() []uint32 { } func (cbft *Cbft) MissGroupVotes(blockIndex uint32) *ctypes.UnKnownGroups { - groupNodes := cbft.validatorPool.GetGroupIndexes(cbft.state.Epoch()) + groupNodes := cbft.getGroupIndexes(cbft.state.Epoch()) allVotes := cbft.state.AllPrepareVoteByIndex(blockIndex) validatorLen := cbft.currentValidatorLen() unKnowns := &ctypes.UnKnownGroups{UnKnown: make([]*ctypes.UnKnownGroup, 0)} + // just for record metrics + missGroups := 0 + missVotes := 0 + for groupID, indexes := range groupNodes { qc, _ := cbft.state.FindMaxGroupRGQuorumCert(blockIndex, groupID) groupLen := cbft.groupLen(cbft.state.Epoch(), groupID) - if qc == nil || qc.Len() < groupLen { + // just for record metrics + groupThreshold := cbft.groupThreshold(cbft.state.Epoch(), groupID) + if qc.Len() < groupThreshold { + missGroups++ + } + if qc.Len() < groupLen { unKnownSet := utils.NewBitArray(uint32(validatorLen)) for _, index := range indexes { if _, ok := allVotes[index]; !ok && !qc.HasSign(index) { @@ -847,22 +856,36 @@ func (cbft *Cbft) MissGroupVotes(blockIndex uint32) *ctypes.UnKnownGroups { UnKnownSet: unKnownSet, } unKnowns.UnKnown = append(unKnowns.UnKnown, unKnownGroup) + // just for record metrics + missVotes += unKnownSet.HasLength() } } } + // just for record metrics + missRGBlockQuorumCertsGauage.Update(int64(missGroups)) + missVotesGauage.Update(int64(missVotes)) return unKnowns } func (cbft *Cbft) MissGroupViewChanges() *ctypes.UnKnownGroups { - groupNodes := cbft.validatorPool.GetGroupIndexes(cbft.state.Epoch()) + groupNodes := cbft.getGroupIndexes(cbft.state.Epoch()) allViewChange := cbft.state.AllViewChange() validatorLen := cbft.currentValidatorLen() unKnowns := &ctypes.UnKnownGroups{UnKnown: make([]*ctypes.UnKnownGroup, 0)} + // just for record metrics + missGroups := 0 + missViewChanges := 0 + for groupID, indexes := range groupNodes { qc, _ := cbft.state.FindMaxGroupRGViewChangeQuorumCert(groupID) groupLen := cbft.groupLen(cbft.state.Epoch(), groupID) + // just for record metrics + groupThreshold := cbft.groupThreshold(cbft.state.Epoch(), groupID) + if qc.Len() < groupThreshold { + missGroups++ + } if qc == nil || qc.Len() < groupLen { unKnownSet := utils.NewBitArray(uint32(validatorLen)) for _, index := range indexes { @@ -876,9 +899,14 @@ func (cbft *Cbft) MissGroupViewChanges() *ctypes.UnKnownGroups { UnKnownSet: unKnownSet, } unKnowns.UnKnown = append(unKnowns.UnKnown, unKnownGroup) + // just for record metrics + missViewChanges += unKnownSet.HasLength() } } } + // just for record metrics + missRGViewQuorumCertsGauage.Update(int64(missGroups)) + missVcsGauage.Update(int64(missViewChanges)) return unKnowns } diff --git a/consensus/cbft/types/crypto.go b/consensus/cbft/types/crypto.go index 5457e2cebe..5f9bcfbcb2 100644 --- a/consensus/cbft/types/crypto.go +++ b/consensus/cbft/types/crypto.go @@ -359,11 +359,12 @@ func (v *ViewChangeQC) ValidatorSet() *utils.BitArray { } func (v *ViewChangeQC) HasSign(signIndex uint32) bool { - if len(v.QCs) > 0 { - for _, qc := range v.QCs { - if qc.HasSign(signIndex) { - return true - } + if v == nil || len(v.QCs) <= 0 { + return false + } + for _, qc := range v.QCs { + if qc.HasSign(signIndex) { + return true } } return false diff --git a/consensus/cbft/utils/bit_array.go b/consensus/cbft/utils/bit_array.go index ba3f8c6b24..420a7a89ef 100644 --- a/consensus/cbft/utils/bit_array.go +++ b/consensus/cbft/utils/bit_array.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package utils import ( @@ -241,7 +240,6 @@ func (bA *BitArray) not() *BitArray { // If bA is longer than o, o is right padded with zeroes func (bA *BitArray) Sub(o *BitArray) *BitArray { if bA == nil || o == nil { - // TODO: Decide if we should do 1's complement here? return nil } // output is the same size as bA diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 2a443b8c70..e2fd2f1461 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -603,7 +603,6 @@ func (vp *ValidatorPool) VerifyHeader(header *types.Header) error { if err != nil { return err } - // todo: need confirmed. return vp.agency.VerifyHeader(header, nil) } From cbbbaa78d65ab159f1dc727b1887445c069d05bf Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 13 Dec 2021 18:10:05 +0800 Subject: [PATCH 125/299] update pubsub server context --- consensus/cbft/network/pubsub.go | 10 +++++----- node/node.go | 9 ++++++--- p2p/pubsub_server.go | 4 ++-- p2p/server.go | 8 ++++++-- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index b70ff9b1cc..0e82a81550 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -64,7 +64,7 @@ type PubSub struct { // All topics subscribed topics map[string]*pubsub.Topic - topicCtx map[string]context.Context + topicCtx map[string]context.CancelFunc // The set of topics we are subscribed to mySubs map[string]*pubsub.Subscription sync.Mutex @@ -140,7 +140,7 @@ func (ps *PubSub) Subscribe(topic string) error { if err != nil { return err } - ctx := context.Background() + ctx, cancel := context.WithCancel(context.Background()) ps.pss.DiscoverTopic(ctx, topic) subscription, err := t.Subscribe() @@ -149,7 +149,7 @@ func (ps *PubSub) Subscribe(topic string) error { } ps.topics[topic] = t ps.mySubs[topic] = subscription - ps.topicCtx[topic] = ctx + ps.topicCtx[topic] = cancel go ps.listen(subscription) return nil @@ -201,8 +201,8 @@ func (ps *PubSub) Cancel(topic string) error { delete(ps.mySubs, topic) delete(ps.topics, topic) } - if ctx, ok := ps.topicCtx[topic]; ok { - ctx.Done() + if cancel, ok := ps.topicCtx[topic]; ok { + cancel() delete(ps.topicCtx, topic) } return nil diff --git a/node/node.go b/node/node.go index 44548668b1..84d735e43a 100644 --- a/node/node.go +++ b/node/node.go @@ -17,9 +17,9 @@ package node import ( + "context" "errors" "fmt" - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "math/big" "net" "os" @@ -28,6 +28,8 @@ import ( "strings" "sync" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/core/rawdb" "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" @@ -204,8 +206,9 @@ func (n *Node) Start() error { running := &p2p.Server{Config: n.serverConfig} localNode := enode.NewV4(&n.serverConfig.PrivateKey.PublicKey, nil, 0, 0) - pubSubServer := p2p.NewPubSubServer(localNode, running) - running.SetPubSubServer(pubSubServer) + ctx, cancel := context.WithCancel(context.Background()) + pubSubServer := p2p.NewPubSubServer(ctx, localNode, running) + running.SetPubSubServer(pubSubServer, cancel) n.log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name) // Otherwise copy and specialize the P2P configuration diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go index 7d65fa1a2a..c726d052b0 100644 --- a/p2p/pubsub_server.go +++ b/p2p/pubsub_server.go @@ -29,10 +29,10 @@ type PubSubServer struct { host *Host } -func NewPubSubServer(localNode *enode.Node, p2pServer *Server) *PubSubServer { +func NewPubSubServer(ctx context.Context, localNode *enode.Node, p2pServer *Server) *PubSubServer { network := NewNetwork(p2pServer.Peers) host := NewHost(localNode, network) - gossipSub, err := pubsub.NewGossipSub(context.Background(), host) + gossipSub, err := pubsub.NewGossipSub(ctx, host) if err != nil { panic("Failed to NewGossipSub: " + err.Error()) } diff --git a/p2p/server.go b/p2p/server.go index 435f1e6f46..729e0494d2 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -19,6 +19,7 @@ package p2p import ( "bytes" + "context" "crypto/ecdsa" "encoding/hex" "errors" @@ -225,7 +226,8 @@ type Server struct { addconsensus chan *dialTask removeconsensus chan *enode.Node - pubSubServer *PubSubServer + pubSubServer *PubSubServer + cancelPubSubServer context.CancelFunc topicSubscriberMu sync.RWMutex topicSubscriber map[string][]enode.ID @@ -461,6 +463,7 @@ func (srv *Server) Stop() { // this unblocks listener Accept srv.listener.Close() } + srv.cancelPubSubServer() close(srv.quit) srv.lock.Unlock() srv.loopWG.Wait() @@ -1314,6 +1317,7 @@ func (srv *Server) watching() { } } -func (srv *Server) SetPubSubServer(pss *PubSubServer) { +func (srv *Server) SetPubSubServer(pss *PubSubServer, cancel context.CancelFunc) { srv.pubSubServer = pss + srv.cancelPubSubServer = cancel } From 26da402af8e912ab44e465c2004a9ac9f332d808 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Tue, 14 Dec 2021 10:27:32 +0800 Subject: [PATCH 126/299] Update unit test --- consensus/cbft/network/pubsub_test.go | 37 +++++++++++++++++---------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/consensus/cbft/network/pubsub_test.go b/consensus/cbft/network/pubsub_test.go index c84857d21a..5dfeec7b1d 100644 --- a/consensus/cbft/network/pubsub_test.go +++ b/consensus/cbft/network/pubsub_test.go @@ -17,8 +17,10 @@ package network import ( + "context" ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" @@ -29,11 +31,12 @@ import ( "time" ) -func makePubSub(handlerMsg func(p *peer, msg *p2p.Msg) error) *PubSub { +func makePubSub(handlerMsg func(p *peer, msg *p2p.Msg) error) (*PubSub, *p2p.Server) { sk, err := crypto.GenerateKey() if nil != err { panic(err) } + ctx, cancel := context.WithCancel(context.Background()) p2pServer := &p2p.Server{Config: node.DefaultConfig.P2P} p2pServer.PrivateKey = sk p2pServer.NoDiscovery = true @@ -42,13 +45,13 @@ func makePubSub(handlerMsg func(p *peer, msg *p2p.Msg) error) *PubSub { panic(err) } localNode := enode.NewV4(&sk.PublicKey, nil, 0, 0) - psServer := p2p.NewPubSubServer(localNode, p2pServer) + psServer := p2p.NewPubSubServer(ctx, localNode, p2pServer) + p2pServer.SetPubSubServer(psServer, cancel) pubSub := NewPubSub(psServer) - pubSub.Init(ctypes.Config{Sys: params.AlayaChainConfig.Cbft, Option: nil}, func(id string) (p *peer, err error) { return nil, nil - }, handlerMsg) - return pubSub + }, handlerMsg, new(event.TypeMux)) + return pubSub, p2pServer } type TestPSRW struct { @@ -81,7 +84,7 @@ func TestPubSubPublish(t *testing.T) { wg := sync.WaitGroup{} wg.Add(len(expect)) - pubSub1 := makePubSub(func(p *peer, msg *p2p.Msg) error { + pubSub1, p2pServer1 := makePubSub(func(p *peer, msg *p2p.Msg) error { var tm TestMsg err := msg.Decode(&tm) assert.Equal(t, tm.Title, expect[1].Title) @@ -89,7 +92,8 @@ func TestPubSubPublish(t *testing.T) { t.Log("pubSub1 receive:", "data", tm, "err", err) return nil }) - pubSub2 := makePubSub(func(p *peer, msg *p2p.Msg) error { + defer p2pServer1.Stop() + pubSub2, p2pServer2 := makePubSub(func(p *peer, msg *p2p.Msg) error { var tm TestMsg err := msg.Decode(&tm) assert.Equal(t, tm.Title, expect[0].Title) @@ -97,6 +101,7 @@ func TestPubSubPublish(t *testing.T) { t.Log("pubSub2 receive:", "data", tm, "err", err) return nil }) + defer p2pServer2.Stop() n1Chan := make(chan p2p.Msg) n2Chan := make(chan p2p.Msg) @@ -166,7 +171,7 @@ func TestPubSubPublish_DifferentTopics(t *testing.T) { wg := sync.WaitGroup{} wg.Add(len(expect)) - pubSub1 := makePubSub(func(p *peer, msg *p2p.Msg) error { + pubSub1, p2pServer1 := makePubSub(func(p *peer, msg *p2p.Msg) error { var tm TestMsg err := msg.Decode(&tm) if tm.Title != expect[1].Title && tm.Title != expect[2].Title { @@ -176,7 +181,8 @@ func TestPubSubPublish_DifferentTopics(t *testing.T) { t.Log("pubSub1 receive:", "data", tm, "err", err) return nil }) - pubSub2 := makePubSub(func(p *peer, msg *p2p.Msg) error { + defer p2pServer1.Stop() + pubSub2, p2pServer2 := makePubSub(func(p *peer, msg *p2p.Msg) error { var tm TestMsg err := msg.Decode(&tm) assert.Equal(t, tm.Title, expect[0].Title) @@ -184,7 +190,8 @@ func TestPubSubPublish_DifferentTopics(t *testing.T) { t.Log("pubSub2 receive:", "data", tm, "err", err) return nil }) - pubSub3 := makePubSub(func(p *peer, msg *p2p.Msg) error { + defer p2pServer2.Stop() + pubSub3, p2pServer3 := makePubSub(func(p *peer, msg *p2p.Msg) error { var tm TestMsg err := msg.Decode(&tm) assert.Equal(t, tm.Title, expect[0].Title) @@ -192,6 +199,7 @@ func TestPubSubPublish_DifferentTopics(t *testing.T) { t.Log("pubSub3 receive:", "data", tm, "err", err) return nil }) + defer p2pServer3.Stop() n1_2Chan := make(chan p2p.Msg) n2_1Chan := make(chan p2p.Msg) @@ -297,7 +305,7 @@ func TestPubSubPublish_ForwardMessage(t *testing.T) { wg := sync.WaitGroup{} wg.Add(6) - pubSub1 := makePubSub(func(p *peer, msg *p2p.Msg) error { + pubSub1, p2pServer1 := makePubSub(func(p *peer, msg *p2p.Msg) error { var tm TestMsg err := msg.Decode(&tm) if tm.Title != expect[1].Title && tm.Title != expect[2].Title { @@ -307,7 +315,8 @@ func TestPubSubPublish_ForwardMessage(t *testing.T) { wg.Done() return nil }) - pubSub2 := makePubSub(func(p *peer, msg *p2p.Msg) error { + defer p2pServer1.Stop() + pubSub2, p2pServer2 := makePubSub(func(p *peer, msg *p2p.Msg) error { var tm TestMsg err := msg.Decode(&tm) if tm.Title != expect[0].Title && tm.Title != expect[2].Title { @@ -317,7 +326,8 @@ func TestPubSubPublish_ForwardMessage(t *testing.T) { wg.Done() return nil }) - pubSub3 := makePubSub(func(p *peer, msg *p2p.Msg) error { + defer p2pServer2.Stop() + pubSub3, p2pServer3 := makePubSub(func(p *peer, msg *p2p.Msg) error { var tm TestMsg err := msg.Decode(&tm) if tm.Title != expect[0].Title && tm.Title != expect[1].Title { @@ -327,6 +337,7 @@ func TestPubSubPublish_ForwardMessage(t *testing.T) { wg.Done() return nil }) + defer p2pServer3.Stop() n1_2Chan := make(chan p2p.Msg) n2_1Chan := make(chan p2p.Msg) From 2d5ee14218ce325030f3c0cf349ce08def1bd7bd Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 14 Dec 2021 11:57:58 +0800 Subject: [PATCH 127/299] pubsub use discoverv4 --- p2p/discover.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/p2p/discover.go b/p2p/discover.go index 9edbce8c89..ec34149dfd 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -7,7 +7,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - "github.com/AlayaNetwork/Alaya-Go/p2p/enr" ) // DiscoverTopic to the given topic.A given validator and subscription handler is @@ -60,12 +59,12 @@ func (srv *Server) validPeersExist(subnetTopic string) bool { // peers are found, the method only exits in the event of context timeouts. func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes []enode.ID, threshold int) error { - if srv.DiscV5 == nil { + if srv.ntab == nil { // return if discovery isn't set return nil } - iterator := srv.DiscV5.RandomNodes() + iterator := srv.ntab.RandomNodes() iterator = filterNodes(ctx, iterator, srv.filterPeerForTopic(nodes)) currNum := len(srv.pubSubServer.PubSub().ListPeers(topic)) @@ -136,11 +135,11 @@ func (srv *Server) filterPeer(node *enode.Node) bool { return false } // do not dial nodes with their tcp ports not set - if err := node.Record().Load(enr.WithEntry("tcp", new(enr.TCP))); err != nil { + /*if err := node.Record().Load(enr.WithEntry("tcp", new(enr.TCP))); err != nil { if !enr.IsNotFound(err) { log.Error("Could not retrieve tcp port", err) } return false - } + }*/ return true } From 8f4e4dd70fe3eef7e1d685844fa3354834d7b69b Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Tue, 14 Dec 2021 12:24:40 +0800 Subject: [PATCH 128/299] Perfect code --- consensus/cbft/cbft.go | 7 ++++++- consensus/consensus.go | 3 +-- core/blockchain_cache.go | 9 ++++++--- p2p/pubsub/pubsub.go | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 3ab2c25ad4..75ca2176ef 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -278,7 +278,12 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCache consensus.BlockC cbft.config.Option.NodePriKey = cbft.nodeServiceContext.NodePriKey() } - needGroup := blockCache.GetActiveVersion(block.Header().SealHash()) >= params.FORKVERSION_0_17_0 + version, err := blockCache.GetActiveVersion(block.Header()) + if err != nil { + log.Error("GetActiveVersion failed during startup of cbft", "err", err) + return err + } + needGroup := version >= params.FORKVERSION_0_17_0 if isGenesis() { cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), cstate.DefaultEpoch, cbft.config.Option.Node.ID(), needGroup, cbft.eventMux) // init RGMsg broadcast manager diff --git a/consensus/consensus.go b/consensus/consensus.go index 5810520b4d..1812dcc6a1 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -70,8 +70,7 @@ type BlockCache interface { WriteBlock(block *types.Block) error // CurrentActiveVersion return current gov version - GetActiveVersion(sealhash common.Hash) uint32 - + GetActiveVersion(header *types.Header) (uint32, error) } // Engine is an algorithm agnostic consensus engine. diff --git a/core/blockchain_cache.go b/core/blockchain_cache.go index ae566e4491..74908b4d06 100644 --- a/core/blockchain_cache.go +++ b/core/blockchain_cache.go @@ -350,9 +350,12 @@ func (bcc *BlockChainCache) WriteBlock(block *types.Block) error { } // CurrentActiveVersion return current gov version -func (bcc *BlockChainCache) GetActiveVersion(sealHash common.Hash) uint32 { - state := bcc.ReadStateDB(sealHash) - return gov.GetCurrentActiveVersion(state) +func (bcc *BlockChainCache) GetActiveVersion(header *types.Header) (uint32, error) { + if state, err := bcc.GetState(header); err != nil { + return 0, err + } else { + return gov.GetCurrentActiveVersion(state), nil + } } type sealHashNumber struct { diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index d30c872945..1c8b252efa 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -308,7 +308,7 @@ func NewPubSub(ctx context.Context, h Host, rt PubSubRouter, opts ...Option) (*P go ps.processLoop(ctx) - (*PubSubNotif)(ps).Initialize() + //(*PubSubNotif)(ps).Initialize() return ps, nil } From c6e5fd96a912e71399c6137cb18304651f432677 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 14 Dec 2021 12:31:06 +0800 Subject: [PATCH 129/299] fix init genesis not work --- core/genesis.go | 54 ++++++++++++++++++++++++++++++++++++----- x/xcom/common_config.go | 20 +++++++-------- 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/core/genesis.go b/core/genesis.go index 3b8c8a57da..e146457a42 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -290,15 +290,58 @@ func (g *Genesis) UnmarshalAddressHRP(r io.Reader) (string, error) { return genesisAddressHRP.Config.AddressHRP, nil } -func (g *Genesis) UnmarshalEconomicConfigExtend(r io.Reader) error { +func (g *Genesis) UnmarshalEconomicConfigExtend(file *os.File) error { + newEce := xcom.GetEce() var genesisEcConfig struct { - EconomicModel *xcom.EconomicModelExtend `json:"economicModel"` + EconomicModel *struct { + Reward xcom.RewardConfigExtend `json:"reward"` + Restricting xcom.RestrictingConfigExtend `json:"restricting"` + } `json:"economicModel"` } - genesisEcConfig.EconomicModel = xcom.GetEce() - if err := json.NewDecoder(r).Decode(&genesisEcConfig); err != nil { + file.Seek(0, io.SeekStart) + if err := json.NewDecoder(file).Decode(&genesisEcConfig); err != nil { return fmt.Errorf("invalid genesis file economicModel: %v", err) } - xcom.ResetEconomicExtendConfig(genesisEcConfig.EconomicModel) + + if genesisEcConfig.EconomicModel != nil { + if genesisEcConfig.EconomicModel.Reward.TheNumberOfDelegationsReward != 0 { + newEce.Reward.TheNumberOfDelegationsReward = genesisEcConfig.EconomicModel.Reward.TheNumberOfDelegationsReward + } + + if genesisEcConfig.EconomicModel.Restricting.MinimumRelease != nil { + newEce.Restricting.MinimumRelease = genesisEcConfig.EconomicModel.Restricting.MinimumRelease + } + } + + file.Seek(0, io.SeekStart) + var genesis0170EcConfig struct { + EconomicModel *xcom.EconomicModel0170Extend `json:"economicModel"` + } + if err := json.NewDecoder(file).Decode(&genesis0170EcConfig); err != nil { + return fmt.Errorf("invalid genesis file for genesis0170EcConfig: %v", err) + } + if genesis0170EcConfig.EconomicModel != nil { + if genesis0170EcConfig.EconomicModel.Common.MaxGroupValidators != 0 { + newEce.Extend0170.Common.MaxGroupValidators = genesis0170EcConfig.EconomicModel.Common.MaxGroupValidators + } + if genesis0170EcConfig.EconomicModel.Common.CoordinatorsLimit != 0 { + newEce.Extend0170.Common.CoordinatorsLimit = genesis0170EcConfig.EconomicModel.Common.CoordinatorsLimit + } + if genesis0170EcConfig.EconomicModel.Common.MaxConsensusVals != 0 { + newEce.Extend0170.Common.MaxConsensusVals = genesis0170EcConfig.EconomicModel.Common.MaxConsensusVals + } + + if genesis0170EcConfig.EconomicModel.Staking.MaxValidators != 0 { + newEce.Extend0170.Staking.MaxValidators = genesis0170EcConfig.EconomicModel.Staking.MaxValidators + } + + if genesis0170EcConfig.EconomicModel.Slashing.ZeroProduceCumulativeTime != 0 { + newEce.Extend0170.Slashing.ZeroProduceCumulativeTime = genesis0170EcConfig.EconomicModel.Slashing.ZeroProduceCumulativeTime + } + + } + + xcom.ResetEconomicExtendConfig(newEce) return nil } @@ -320,7 +363,6 @@ func (g *Genesis) InitGenesisAndSetEconomicConfig(path string) error { g.EconomicModel = xcom.GetEc(xcom.DefaultAlayaNet) - file.Seek(0, io.SeekStart) if err := g.UnmarshalEconomicConfigExtend(file); nil != err { return err } diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index 91c2522256..763c1e6597 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -161,16 +161,16 @@ type EconomicModel struct { // When the chain is started, if new parameters are added, add them to this structure type EconomicModelExtend struct { - Reward rewardConfigExtend `json:"reward"` - Restricting restrictingConfigExtend `json:"restricting"` + Reward RewardConfigExtend `json:"reward"` + Restricting RestrictingConfigExtend `json:"restricting"` Extend0170 EconomicModel0170Extend `json:"extend_0170,omitempty"` } -type rewardConfigExtend struct { +type RewardConfigExtend struct { TheNumberOfDelegationsReward uint16 `json:"theNumberOfDelegationsReward"` // The maximum number of delegates that can receive rewards at a time } -type restrictingConfigExtend struct { +type RestrictingConfigExtend struct { MinimumRelease *big.Int `json:"minimumRelease"` //The minimum number of Restricting release in one epoch } @@ -292,10 +292,10 @@ func getDefaultEMConfig(netId int8) *EconomicModel { }, } ece = &EconomicModelExtend{ - Reward: rewardConfigExtend{ + Reward: RewardConfigExtend{ TheNumberOfDelegationsReward: 20, }, - Restricting: restrictingConfigExtend{ + Restricting: RestrictingConfigExtend{ MinimumRelease: new(big.Int).Mul(oneAtp, new(big.Int).SetInt64(80)), }, Extend0170: EconomicModel0170Extend{ @@ -365,10 +365,10 @@ func getDefaultEMConfig(netId int8) *EconomicModel { }, } ece = &EconomicModelExtend{ - Reward: rewardConfigExtend{ + Reward: RewardConfigExtend{ TheNumberOfDelegationsReward: 20, }, - Restricting: restrictingConfigExtend{ + Restricting: RestrictingConfigExtend{ MinimumRelease: new(big.Int).SetInt64(1), }, Extend0170: EconomicModel0170Extend{ @@ -437,10 +437,10 @@ func getDefaultEMConfig(netId int8) *EconomicModel { }, } ece = &EconomicModelExtend{ - Reward: rewardConfigExtend{ + Reward: RewardConfigExtend{ TheNumberOfDelegationsReward: 2, }, - Restricting: restrictingConfigExtend{ + Restricting: RestrictingConfigExtend{ MinimumRelease: new(big.Int).SetInt64(1), }, Extend0170: EconomicModel0170Extend{ From b9da0b758892707b90ccacf455a8932b28a8708b Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 14 Dec 2021 14:21:45 +0800 Subject: [PATCH 130/299] update CeilMaxConsensusVals to 500 --- core/cbfttypes/type.go | 2 +- x/xcom/common_config.go | 2 +- x/xcom/common_config_0170.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 806e7024d7..865f5f0ce6 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -364,7 +364,7 @@ func (vs *Validators) Grouped() error { validatorCount := uint32(vs.SortedNodes.Len()) groupNum := validatorCount / xcom.MaxGroupValidators() - mod := validatorCount % xcom.CoordinatorsLimit() + mod := validatorCount % xcom.MaxGroupValidators() if mod > 0 { groupNum = groupNum + 1 } diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index 91c2522256..562d96633f 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -51,7 +51,7 @@ const ( CeilBlocksReward = 50000 CeilMaxValidators = 500 FloorMaxConsensusVals = 4 - CeilMaxConsensusVals = 25 + CeilMaxConsensusVals = 500 PositiveInfinity = "+∞" CeilUnStakeFreezeDuration = 168 * 2 CeilMaxEvidenceAge = CeilUnStakeFreezeDuration - 1 diff --git a/x/xcom/common_config_0170.go b/x/xcom/common_config_0170.go index 8b9b1676bf..d59cb72d0c 100644 --- a/x/xcom/common_config_0170.go +++ b/x/xcom/common_config_0170.go @@ -44,7 +44,7 @@ type EconomicModel0170Extend struct { } type EconomicModel0170CommonConfig struct { - MaxGroupValidators uint32 `json:"caxGroupValidators"` // max validators count in 1 group + MaxGroupValidators uint32 `json:"maxGroupValidators"` // max validators count in 1 group CoordinatorsLimit uint32 `json:"coordinatorLimit"` // max Coordinators count in 1 group MaxConsensusVals uint64 `json:"maxConsensusVals"` // The consensus validators count } From afec5551f29cb16c690453f4d968c9918502f924 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 14 Dec 2021 15:07:47 +0800 Subject: [PATCH 131/299] fix geneis check --- x/xcom/common_config.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index 763c1e6597..313670cbc3 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -49,7 +49,8 @@ const ( Hundred = 100 TenThousand = 10000 CeilBlocksReward = 50000 - CeilMaxValidators = 500 + CeilMaxValidators = 201 + CeilMaxValidators0170 = 500 FloorMaxConsensusVals = 4 CeilMaxConsensusVals = 25 PositiveInfinity = "+∞" @@ -481,8 +482,14 @@ func CheckOperatingThreshold(threshold *big.Int) error { } func CheckMaxValidators(num int, version uint32) error { - if num < int(MaxConsensusVals(version)) || num > CeilMaxValidators { - return common.InvalidParameter.Wrap(fmt.Sprintf("The MaxValidators must be [%d, %d]", int(MaxConsensusVals(version)), CeilMaxValidators)) + if version >= params.FORKVERSION_0_17_0 { + if num < int(MaxConsensusVals(version)) || num > CeilMaxValidators0170 { + return common.InvalidParameter.Wrap(fmt.Sprintf("The MaxValidators must be [%d, %d]", int(MaxConsensusVals(version)), CeilMaxValidators)) + } + } else { + if num < int(MaxConsensusVals(version)) || num > CeilMaxValidators { + return common.InvalidParameter.Wrap(fmt.Sprintf("The MaxValidators must be [%d, %d]", int(MaxConsensusVals(version)), CeilMaxValidators)) + } } return nil } @@ -604,9 +611,9 @@ func CheckEconomicModel(version uint32) error { return errors.New("The issuance period must be integer multiples of the settlement period and multiples must be greater than or equal to 4") } - if MaxConsensusVals(version) < FloorMaxConsensusVals || MaxConsensusVals(version) > CeilMaxConsensusVals { + /*if MaxConsensusVals(version) < FloorMaxConsensusVals || MaxConsensusVals(version) > CeilMaxConsensusVals { return fmt.Errorf("The consensus validator num must be [%d, %d]", FloorMaxConsensusVals, CeilMaxConsensusVals) - } + }*/ if err := CheckMaxValidators(int(ec.Staking.MaxValidators), version); nil != err { return err From f64be643a918097a3c7877707827a80c3cad64eb Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 14 Dec 2021 15:15:34 +0800 Subject: [PATCH 132/299] fix --- x/xcom/common_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index e9a6e208f1..11e797ae06 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -484,7 +484,7 @@ func CheckOperatingThreshold(threshold *big.Int) error { func CheckMaxValidators(num int, version uint32) error { if version >= params.FORKVERSION_0_17_0 { if num < int(MaxConsensusVals(version)) || num > CeilMaxValidators0170 { - return common.InvalidParameter.Wrap(fmt.Sprintf("The MaxValidators must be [%d, %d]", int(MaxConsensusVals(version)), CeilMaxValidators)) + return common.InvalidParameter.Wrap(fmt.Sprintf("The MaxValidators must be [%d, %d]", int(MaxConsensusVals(version)), CeilMaxValidators0170)) } } else { if num < int(MaxConsensusVals(version)) || num > CeilMaxValidators { From d63f915f93c9c0fb1895fb707af2abb99e949766 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 14 Dec 2021 15:31:49 +0800 Subject: [PATCH 133/299] add MinimumPeersInTopicSearch and MinimumPeersPerTopic config --- cmd/utils/flags.go | 3 ++- p2p/server.go | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e1bc5c8042..c039882317 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -895,7 +895,8 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { if ctx.GlobalIsSet(NoDiscoverFlag.Name) || lightClient { cfg.NoDiscovery = true } - + cfg.MinimumPeersInTopicSearch = 6 + cfg.MinimumPeersPerTopic = 6 // if we're running a light client or server, force enable the v5 peer discovery // unless it is explicitly disabled with --nodiscover note that explicitly specifying // --v5disc overrides --nodiscover, in which case the later only disables v4 discovery diff --git a/p2p/server.go b/p2p/server.go index 729e0494d2..cec4754211 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -173,9 +173,6 @@ type Config struct { clock mclock.Clock - TopicKey string // TopicKey is the ENR key of the topic bitfield in the enr. - TopicCount uint64 - MinimumPeersInTopicSearch uint MinimumPeersPerTopic int From 3b79224cb46593827b6c2d6f4f9f3f7d76f0200f Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 14 Dec 2021 16:39:12 +0800 Subject: [PATCH 134/299] fix ping pong Rest --- p2p/discover/v4wire/v4wire.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/p2p/discover/v4wire/v4wire.go b/p2p/discover/v4wire/v4wire.go index 4ff65f5dc5..6d12aa3022 100644 --- a/p2p/discover/v4wire/v4wire.go +++ b/p2p/discover/v4wire/v4wire.go @@ -50,10 +50,9 @@ type ( Version uint From, To Endpoint Expiration uint64 - ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` + Rest []rlp.RawValue `rlp:"tail"` + ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. } // Pong is the reply to ping. @@ -64,10 +63,9 @@ type ( To Endpoint ReplyTok []byte // This contains the hash of the ping packet. Expiration uint64 // Absolute timestamp at which the packet becomes invalid. - ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` + Rest []rlp.RawValue `rlp:"tail"` + ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. } // Findnode is a query for nodes close to the given target. From 63f37014a164d1b31a9fab42af6122db8c71a734 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Tue, 14 Dec 2021 17:19:15 +0800 Subject: [PATCH 135/299] fix bug --- consensus/cbft/consensus_process.go | 2 +- consensus/cbft/network/pubsub.go | 9 +++++---- consensus/cbft/sync_process.go | 4 ++-- consensus/cbft/types/crypto.go | 7 +++++-- p2p/stream.go | 2 +- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 182100c9a2..03782c5c86 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -1148,7 +1148,7 @@ func (cbft *Cbft) richViewChangeQC(viewChangeQC *ctypes.ViewChangeQC) { cbft.log.Info("Local node is not validator") return } - hadSend := cbft.state.ViewChangeByIndex(uint32(node.Index)) + hadSend := cbft.state.ViewChangeByIndex(node.Index) if hadSend != nil && !viewChangeQC.ExistViewChange(hadSend.Epoch, hadSend.ViewNumber, hadSend.BlockHash) { cert, err := cbft.generateViewChangeQuorumCert(hadSend) if err != nil { diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index 95d229a251..35ae6aca99 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -121,10 +121,11 @@ func (ps *PubSub) Protocols() []p2p.Protocol { func NewPubSub(server *p2p.PubSubServer) *PubSub { return &PubSub{ - pss: server, - topics: make(map[string]*pubsub.Topic), - mySubs: make(map[string]*pubsub.Subscription), - quit: make(chan struct{}), + pss: server, + topics: make(map[string]*pubsub.Topic), + topicCtx: make(map[string]context.CancelFunc), + mySubs: make(map[string]*pubsub.Subscription), + quit: make(chan struct{}), } } diff --git a/consensus/cbft/sync_process.go b/consensus/cbft/sync_process.go index a7bf5b2372..ed7a6d0eed 100644 --- a/consensus/cbft/sync_process.go +++ b/consensus/cbft/sync_process.go @@ -630,7 +630,7 @@ func (cbft *Cbft) OnGetViewChange(id string, msg *protocols.GetViewChange) error if len(viewChanges) > 0 || len(RGViewChangeQuorumCerts) > 0 { cbft.network.Send(id, &protocols.ViewChanges{VCs: viewChanges, RGViewChangeQuorumCerts: RGViewChangeQuorumCerts}) - cbft.log.Debug("Send ViewChanges", "peer", id, "blockIndex", "viewChanges length", len(viewChanges), "RGViewChangeQuorumCerts length", len(RGViewChangeQuorumCerts)) + cbft.log.Debug("Send ViewChanges", "peer", id, "viewChanges length", len(viewChanges), "RGViewChangeQuorumCerts length", len(RGViewChangeQuorumCerts)) } return nil } @@ -886,7 +886,7 @@ func (cbft *Cbft) MissGroupViewChanges() *ctypes.UnKnownGroups { if qc.Len() < groupThreshold { missGroups++ } - if qc == nil || qc.Len() < groupLen { + if qc.Len() < groupLen { unKnownSet := utils.NewBitArray(uint32(validatorLen)) for _, index := range indexes { if _, ok := allViewChange[index]; !ok && !qc.HasSign(index) { diff --git a/consensus/cbft/types/crypto.go b/consensus/cbft/types/crypto.go index 5f9bcfbcb2..32abfe5b9f 100644 --- a/consensus/cbft/types/crypto.go +++ b/consensus/cbft/types/crypto.go @@ -207,7 +207,7 @@ func (q ViewChangeQuorumCert) CannibalizeBytes() ([]byte, error) { return crypto.Keccak256(buf), nil } -func (q ViewChangeQuorumCert) Len() int { +func (q *ViewChangeQuorumCert) Len() int { if q.ValidatorSet == nil { return 0 } @@ -333,6 +333,9 @@ func (v ViewChangeQC) MaxBlock() (uint64, uint64, uint64, uint64, common.Hash, u } func (v *ViewChangeQC) Len() int { + if v == nil || len(v.QCs) <= 0 { + return 0 + } length := 0 for _, qc := range v.QCs { length += qc.Len() @@ -341,7 +344,7 @@ func (v *ViewChangeQC) Len() int { } func (v *ViewChangeQC) HasLength() int { - if v == nil { + if v == nil || len(v.QCs) <= 0 { return 0 } return v.ValidatorSet().HasLength() diff --git a/p2p/stream.go b/p2p/stream.go index 5d98de3ba0..cf0e42107b 100644 --- a/p2p/stream.go +++ b/p2p/stream.go @@ -26,7 +26,7 @@ import ( const ( // Msg code of PubSub's message - PubSubMsgCode = 0xff + PubSubMsgCode = 0x00 ) // Stream is the stream type used by pubsub. In general From 7b22965228d499199a1d0f798caa44c0e84edc9f Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 14 Dec 2021 18:26:01 +0800 Subject: [PATCH 136/299] fix no discover --- p2p/discover/v4_udp.go | 11 ++++++----- p2p/discover/v4wire/v4wire.go | 8 ++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go index aff6c29ad5..d8e356768c 100644 --- a/p2p/discover/v4_udp.go +++ b/p2p/discover/v4_udp.go @@ -231,7 +231,8 @@ func (t *UDPv4) Ping(n *enode.Node) error { func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) { rm := t.sendPing(n.ID(), &net.UDPAddr{IP: n.IP(), Port: n.UDP()}, nil) if err = <-rm.errc; err == nil { - seq = rm.reply.(*v4wire.Pong).ENRSeq + //seq = rm.reply.(*v4wire.Pong).ENRSeq + return seq, nil } return seq, err } @@ -267,8 +268,8 @@ func (t *UDPv4) makePing(toaddr *net.UDPAddr) *v4wire.Ping { From: t.ourEndpoint(), To: v4wire.NewEndpoint(toaddr, 0), Expiration: uint64(time.Now().Add(expiration).Unix()), - ENRSeq: t.localNode.Node().Seq(), - Rest: cRest, + //ENRSeq: t.localNode.Node().Seq(), + Rest: cRest, } } @@ -679,8 +680,8 @@ func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.I To: v4wire.NewEndpoint(from, req.From.TCP), ReplyTok: mac, Expiration: uint64(time.Now().Add(expiration).Unix()), - ENRSeq: t.localNode.Node().Seq(), - Rest: cRest, + // ENRSeq: t.localNode.Node().Seq(), + Rest: cRest, }) // Ping back if our last pong on file is too far in the past. diff --git a/p2p/discover/v4wire/v4wire.go b/p2p/discover/v4wire/v4wire.go index 6d12aa3022..93e2b2087f 100644 --- a/p2p/discover/v4wire/v4wire.go +++ b/p2p/discover/v4wire/v4wire.go @@ -51,8 +51,8 @@ type ( From, To Endpoint Expiration uint64 // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. + Rest []rlp.RawValue `rlp:"tail"` + // ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. } // Pong is the reply to ping. @@ -64,8 +64,8 @@ type ( ReplyTok []byte // This contains the hash of the ping packet. Expiration uint64 // Absolute timestamp at which the packet becomes invalid. // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. + Rest []rlp.RawValue `rlp:"tail"` + // ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. } // Findnode is a query for nodes close to the given target. From 5f3b3ca38b3fe75bd5c4c74a2b847615d41a7567 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 14 Dec 2021 18:29:08 +0800 Subject: [PATCH 137/299] fix test --- p2p/discover/v4_udp_test.go | 5 ++--- p2p/discover/v4wire/v4wire.go | 2 +- p2p/discover/v4wire/v4wire_test.go | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go index 422ddc5ec4..e7eea63338 100644 --- a/p2p/discover/v4_udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -37,7 +37,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/p2p/discover/v4wire" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - "github.com/AlayaNetwork/Alaya-Go/p2p/enr" ) // shared test variables @@ -460,7 +459,7 @@ func TestUDPv4_successfulPing(t *testing.T) { } // This test checks that EIP-868 requests work. -func TestUDPv4_EIP868(t *testing.T) { +/*func TestUDPv4_EIP868(t *testing.T) { test := newUDPTest(t) defer test.close() @@ -495,7 +494,7 @@ func TestUDPv4_EIP868(t *testing.T) { t.Fatalf("wrong node in enrResponse: %v", n) } }) -} +}*/ // This test verifies that a small network of nodes can boot up into a healthy state. func TestUDPv4_smallNetConvergence(t *testing.T) { diff --git a/p2p/discover/v4wire/v4wire.go b/p2p/discover/v4wire/v4wire.go index 93e2b2087f..de35615562 100644 --- a/p2p/discover/v4wire/v4wire.go +++ b/p2p/discover/v4wire/v4wire.go @@ -65,7 +65,7 @@ type ( Expiration uint64 // Absolute timestamp at which the packet becomes invalid. // Ignore additional fields (for forward compatibility). Rest []rlp.RawValue `rlp:"tail"` - // ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. + //ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. } // Findnode is a query for nodes close to the given target. diff --git a/p2p/discover/v4wire/v4wire_test.go b/p2p/discover/v4wire/v4wire_test.go index 228c12995e..8623d99e53 100644 --- a/p2p/discover/v4wire/v4wire_test.go +++ b/p2p/discover/v4wire/v4wire_test.go @@ -49,8 +49,8 @@ var testPackets = []struct { From: Endpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, To: Endpoint{net.ParseIP("::1"), 2222, 3333}, Expiration: 1136239445, - ENRSeq: 1, - Rest: []rlp.RawValue{{0x02}}, + //ENRSeq: 1, + Rest: []rlp.RawValue{{0x02}}, }, }, { From 54841c8bdd4f521e4336aefa928fc90afb9956ec Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 15 Dec 2021 11:14:54 +0800 Subject: [PATCH 138/299] fix p2p server addconsensus removeconsensus topicSubscriber not init --- p2p/server.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/p2p/server.go b/p2p/server.go index cec4754211..f9d3155f5d 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -530,6 +530,9 @@ func (srv *Server) Start() (err error) { srv.removetrusted = make(chan *enode.Node) srv.peerOp = make(chan peerOpFunc) srv.peerOpDone = make(chan struct{}) + srv.addconsensus = make(chan *dialTask) + srv.removeconsensus = make(chan *enode.Node) + srv.topicSubscriber = make(map[string][]enode.ID) if err := srv.setupLocalNode(); err != nil { return err From 93112ca1257a2e05cc3e8d7fa5a9c81e47198756 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 15 Dec 2021 11:31:24 +0800 Subject: [PATCH 139/299] fix duplicate TopicEvent --- consensus/cbft/validator/validator.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index e49694b60d..62b2d5e9bf 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -359,8 +359,10 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID log.Debug("Get nextValidators error", "blockNumber", blockNumber, "err", err) return pool } - pool.nextValidators = nds - pool.organize(pool.nextValidators, epoch+1, eventMux) + if nds != nil && !pool.currentValidators.Equal(nds) { + pool.nextValidators = nds + pool.organize(pool.nextValidators, epoch+1, eventMux) + } } } log.Debug("Update validator", "validators", pool.currentValidators.String(), "switchpoint", pool.switchPoint, "epoch", pool.epoch, "lastNumber", pool.lastNumber) From ca0ab61f0b10a929f00ab8f6345361fbe2bb63f1 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Wed, 15 Dec 2021 11:37:43 +0800 Subject: [PATCH 140/299] Rename and modify Context --- consensus/cbft/cbft.go | 2 +- consensus/cbft/network/pubsub.go | 26 +++++++++++++++----------- consensus/cbft/network/pubsub_test.go | 2 +- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 75ca2176ef..c48b319a34 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -270,7 +270,7 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCache consensus.BlockC go cbft.network.Start() // Data required to initialize pubsub - cbft.pubSub.Init(cbft.config, cbft.network.GetPeer, cbft.network.HandleRGMsg, cbft.eventMux) + cbft.pubSub.Start(cbft.config, cbft.network.GetPeer, cbft.network.HandleRGMsg, cbft.eventMux) if cbft.config.Option.Node == nil { cbft.config.Option.Node = enode.NewV4(&cbft.nodeServiceContext.NodePriKey().PublicKey, nil, 0, 0) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index 35ae6aca99..ccdbfea649 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -65,8 +65,9 @@ type PubSub struct { onReceive receiveCallback // All topics subscribed - topics map[string]*pubsub.Topic - topicCtx map[string]context.CancelFunc + topics map[string]*pubsub.Topic + topicCtx map[string]context.Context + topicCancel map[string]context.CancelFunc // The set of topics we are subscribed to mySubs map[string]*pubsub.Subscription sync.Mutex @@ -121,15 +122,16 @@ func (ps *PubSub) Protocols() []p2p.Protocol { func NewPubSub(server *p2p.PubSubServer) *PubSub { return &PubSub{ - pss: server, - topics: make(map[string]*pubsub.Topic), - topicCtx: make(map[string]context.CancelFunc), - mySubs: make(map[string]*pubsub.Subscription), - quit: make(chan struct{}), + pss: server, + topics: make(map[string]*pubsub.Topic), + topicCtx: make(map[string]context.Context), + topicCancel: make(map[string]context.CancelFunc), + mySubs: make(map[string]*pubsub.Subscription), + quit: make(chan struct{}), } } -func (ps *PubSub) Init(config ctypes.Config, get getByIDFunc, onReceive receiveCallback, eventMux *event.TypeMux) { +func (ps *PubSub) Start(config ctypes.Config, get getByIDFunc, onReceive receiveCallback, eventMux *event.TypeMux) { ps.config = config ps.getPeerById = get ps.onReceive = onReceive @@ -183,7 +185,8 @@ func (ps *PubSub) Subscribe(topic string) error { } ps.topics[topic] = t ps.mySubs[topic] = subscription - ps.topicCtx[topic] = cancel + ps.topicCtx[topic] = ctx + ps.topicCancel[topic] = cancel go ps.listen(subscription) return nil @@ -235,9 +238,10 @@ func (ps *PubSub) Cancel(topic string) error { delete(ps.mySubs, topic) delete(ps.topics, topic) } - if cancel, ok := ps.topicCtx[topic]; ok { + if cancel, ok := ps.topicCancel[topic]; ok { cancel() delete(ps.topicCtx, topic) + delete(ps.topicCancel, topic) } return nil } @@ -262,7 +266,7 @@ func (ps *PubSub) Publish(topic string, code uint64, data interface{}) error { return err } - return t.Publish(context.Background(), env) + return t.Publish(ps.topicCtx[topic], env) } func (ps *PubSub) Stop() { diff --git a/consensus/cbft/network/pubsub_test.go b/consensus/cbft/network/pubsub_test.go index 5dfeec7b1d..4ce8b24430 100644 --- a/consensus/cbft/network/pubsub_test.go +++ b/consensus/cbft/network/pubsub_test.go @@ -48,7 +48,7 @@ func makePubSub(handlerMsg func(p *peer, msg *p2p.Msg) error) (*PubSub, *p2p.Ser psServer := p2p.NewPubSubServer(ctx, localNode, p2pServer) p2pServer.SetPubSubServer(psServer, cancel) pubSub := NewPubSub(psServer) - pubSub.Init(ctypes.Config{Sys: params.AlayaChainConfig.Cbft, Option: nil}, func(id string) (p *peer, err error) { + pubSub.Start(ctypes.Config{Sys: params.AlayaChainConfig.Cbft, Option: nil}, func(id string) (p *peer, err error) { return nil, nil }, handlerMsg, new(event.TypeMux)) return pubSub, p2pServer From d984c30a3faba48e4b8423a4674c0dbab60fa1c8 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 15 Dec 2021 11:47:40 +0800 Subject: [PATCH 141/299] update nextRoundBlockNumber --- consensus/cbft/validator/validator.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 62b2d5e9bf..8f5dc757bd 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -434,11 +434,12 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo var err error var nds *cbfttypes.Validators needGroup := version >= params.FORKVERSION_0_17_0 + nextRoundBlockNumber := blockNumber + xcom.ElectionDistance() + 1 //生效后第一个共识周期的switchpoint是旧值,此时不能切换 //判断依据是新validators和current完全相同且nextValidators为空 if !isElection && vp.nextValidators == nil && needGroup { - nds, err = vp.agency.GetValidators(NextRound(blockNumber)) + nds, err = vp.agency.GetValidators(nextRoundBlockNumber) if err != nil { log.Error("Get validator error", "blockNumber", blockNumber, "err", err) return err @@ -465,7 +466,7 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo if isElection { // 提前更新nextValidators,为了p2p早一步订阅分组事件以便建链接 - nds, err = vp.agency.GetValidators(NextRound(blockNumber)) + nds, err = vp.agency.GetValidators(nextRoundBlockNumber) if err != nil { log.Error("Get validator error", "blockNumber", blockNumber, "err", err) return err @@ -477,6 +478,7 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo } else { // 节点中间重启过, nextValidators没有赋值 if vp.nextValidators == nil { + // 此时blockNumber==vp.lastNumber blockNumber+1即为下一轮 nds, err = vp.agency.GetValidators(NextRound(blockNumber)) if err != nil { log.Error("Get validator error", "blockNumber", blockNumber, "err", err) From 165559fd8d5d6468094903932cae09d16fc35c72 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 15 Dec 2021 14:13:57 +0800 Subject: [PATCH 142/299] fix discover --- p2p/discover.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/p2p/discover.go b/p2p/discover.go index ec34149dfd..fc3cc4972d 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -22,7 +22,7 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { ticker.Stop() return case <-ticker.C: - if srv.running { + if !srv.running { continue } @@ -92,6 +92,8 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes [ currNum = len(srv.pubSubServer.PubSub().ListPeers(topic)) try++ } + log.Trace(" Searching network for peers subscribed to the topic done.", "topic", topic, "peers", currNum, "try", try) + return nil } From 42f21149956fbc1c2590eb337437d32eb6652932 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 15 Dec 2021 14:18:27 +0800 Subject: [PATCH 143/299] update --- p2p/discover.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/p2p/discover.go b/p2p/discover.go index fc3cc4972d..2893789223 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -30,11 +30,10 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { if !srv.validPeersExist(topic) { srv.topicSubscriberMu.RLock() nodes, ok := srv.topicSubscriber[topic] + srv.topicSubscriberMu.RUnlock() if !ok { - srv.topicSubscriberMu.RUnlock() continue } - srv.topicSubscriberMu.RUnlock() log.Debug("No peers found subscribed gossip topic . Searching network for peers subscribed to the topic.", "topic", topic) if err := srv.FindPeersWithTopic(ctx, topic, nodes, srv.Config.MinimumPeersPerTopic); err != nil { log.Error("Could not search for peers", "err", err) From b2118c3553453ec2228c302105ac1361cc7c0e89 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 15 Dec 2021 15:31:05 +0800 Subject: [PATCH 144/299] update MinimumPeersPerTopic --- cmd/utils/flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c039882317..c15d943b99 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -896,7 +896,7 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { cfg.NoDiscovery = true } cfg.MinimumPeersInTopicSearch = 6 - cfg.MinimumPeersPerTopic = 6 + cfg.MinimumPeersPerTopic = 12 // if we're running a light client or server, force enable the v5 peer discovery // unless it is explicitly disabled with --nodiscover note that explicitly specifying // --v5disc overrides --nodiscover, in which case the later only disables v4 discovery From 156d52cba7e7dc74688c576331b91fab14d9fa98 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Wed, 15 Dec 2021 16:01:51 +0800 Subject: [PATCH 145/299] Fix problems and add logs --- consensus/cbft/network/pubsub.go | 7 ++++--- p2p/network.go | 9 ++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index ccdbfea649..a4abb13a42 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -205,7 +205,7 @@ func (ps *PubSub) listen(s *pubsub.Subscription) { if subMsg != nil { var gmsg GMsg if err := rlp.DecodeBytes(subMsg.Data, &gmsg); err != nil { - log.Error("Failed to parse topic message", "error", err) + log.Error("Failed to parse topic message", "topic", s.Topic(), "error", err) ps.Cancel(s.Topic()) return } @@ -215,13 +215,14 @@ func (ps *PubSub) listen(s *pubsub.Subscription) { Payload: bytes.NewReader(common.CopyBytes(gmsg.Data)), } if ps.pss.Host().ID().ID() == subMsg.From { - log.Trace("Receive a message from myself", "fromId", subMsg.From.TerminalString()) + log.Trace("Receive a message from myself", "fromId", subMsg.From.TerminalString(), "topic", s.Topic(), "msgCode", gmsg.Code) continue } fromPeer, err := ps.getPeerById(subMsg.ReceivedFrom.ID().TerminalString()) if err != nil { - log.Error("Failed to execute getPeerById", "err", err) + log.Error("Failed to execute getPeerById", "receivedFrom", subMsg.ReceivedFrom.ID().TerminalString(), "topic", s.Topic(), "err", err) } else { + log.Trace("Receive a message", "topic", s.Topic(), "receivedFrom", fromPeer.ID().TerminalString(), "msgFrom", subMsg.From.TerminalString(), "msgCode", gmsg.Code) ps.onReceive(fromPeer, &msg) } } diff --git a/p2p/network.go b/p2p/network.go index 23a456ea8d..97d503e320 100644 --- a/p2p/network.go +++ b/p2p/network.go @@ -40,7 +40,6 @@ type Network struct { func NewNetwork(getPeers Peers) *Network { n := &Network{ - RWMutex: sync.RWMutex{}, GetPeers: getPeers, m: make(map[pubsub.Notifiee]struct{}), } @@ -49,8 +48,8 @@ func NewNetwork(getPeers Peers) *Network { } func (n *Network) SetConn(p enode.ID, conn pubsub.Conn) { - n.conns.RLock() - defer n.conns.RUnlock() + n.conns.Lock() + defer n.conns.Unlock() conns := n.conns.m[p] if conns == nil { conns = make([]pubsub.Conn, 0) @@ -60,8 +59,8 @@ func (n *Network) SetConn(p enode.ID, conn pubsub.Conn) { } func (n *Network) RemoveConn(p enode.ID) { - n.conns.RLock() - defer n.conns.RUnlock() + n.conns.Lock() + defer n.conns.Unlock() delete(n.conns.m, p) } From a22bbe4c9620ee1874ac2702b0cac60f0acd8698 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 15 Dec 2021 17:27:04 +0800 Subject: [PATCH 146/299] MinimumPeersPerTopic can't greater than MaxGroupValidators --- cmd/utils/flags.go | 3 +-- p2p/server.go | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c15d943b99..e1bc5c8042 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -895,8 +895,7 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { if ctx.GlobalIsSet(NoDiscoverFlag.Name) || lightClient { cfg.NoDiscovery = true } - cfg.MinimumPeersInTopicSearch = 6 - cfg.MinimumPeersPerTopic = 12 + // if we're running a light client or server, force enable the v5 peer discovery // unless it is explicitly disabled with --nodiscover note that explicitly specifying // --v5disc overrides --nodiscover, in which case the later only disables v4 discovery diff --git a/p2p/server.go b/p2p/server.go index f9d3155f5d..a8225bfe9f 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -31,6 +31,8 @@ import ( "sync/atomic" "time" + "github.com/AlayaNetwork/Alaya-Go/x/xcom" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" "github.com/AlayaNetwork/Alaya-Go/crypto" @@ -534,6 +536,12 @@ func (srv *Server) Start() (err error) { srv.removeconsensus = make(chan *enode.Node) srv.topicSubscriber = make(map[string][]enode.ID) + srv.MinimumPeersInTopicSearch = 6 + srv.MinimumPeersPerTopic = 10 + if xcom.MaxGroupValidators() <= 10 { + srv.MinimumPeersPerTopic = int(xcom.MaxGroupValidators()) + } + if err := srv.setupLocalNode(); err != nil { return err } From e876f2ad02290e8a9bfe7b0bf844f79a5a718433 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 15 Dec 2021 17:27:55 +0800 Subject: [PATCH 147/299] update --- p2p/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/server.go b/p2p/server.go index a8225bfe9f..692696ddcd 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -538,7 +538,7 @@ func (srv *Server) Start() (err error) { srv.MinimumPeersInTopicSearch = 6 srv.MinimumPeersPerTopic = 10 - if xcom.MaxGroupValidators() <= 10 { + if int(xcom.MaxGroupValidators()) <= srv.MinimumPeersPerTopic { srv.MinimumPeersPerTopic = int(xcom.MaxGroupValidators()) } From 23f6f3b1ae33cf45cdaa1eee98705a1671522507 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 15 Dec 2021 18:21:31 +0800 Subject: [PATCH 148/299] fix unitid err --- consensus/cbft/validator/validator.go | 11 ++--- consensus/cbft/validator/validator_test.go | 9 +++- core/cbfttypes/type.go | 55 +++++++++++++++------- 3 files changed, 49 insertions(+), 26 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 8f5dc757bd..7abbc238ae 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -412,7 +412,7 @@ func (vp *ValidatorPool) EnableVerifyEpoch(epoch uint64) error { if epoch+1 == vp.epoch || epoch == vp.epoch { return nil } - return fmt.Errorf("enable verify epoch:%d,%d, request:%d", vp.epoch-1, vp.epoch, epoch) + return fmt.Errorf("unable verify epoch:%d,%d, request:%d", vp.epoch-1, vp.epoch, epoch) } func (vp *ValidatorPool) MockSwitchPoint(number uint64) { @@ -454,6 +454,7 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo } else { return fmt.Errorf("ValidatorPool update failed, currentValidators:%s, nds:%s", vp.currentValidators.String(), nds.String()) } + log.Debug("update currentValidators success!", "lastNumber", vp.lastNumber, "grouped", vp.grouped, "switchPoint", vp.switchPoint) } //分组提案生效后第一个共识round到ElectionPoint时初始化分组信息 if !vp.grouped { @@ -496,10 +497,11 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo currEpoch := vp.epoch vp.epoch = epoch vp.nextValidators = nil - log.Info("Update validator", "validators", vp.currentValidators.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) + log.Info("Update validator", "validators.len", vp.currentValidators.Len(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) //切换共识轮时需要将上一轮分组的topic取消订阅 vp.dissolve(currEpoch, eventMux) } + log.Debug("Update OK", "blockNumber", blockNumber, "epoch", epoch, "isElection", isElection, "version", version) return nil } @@ -763,11 +765,6 @@ func (vp *ValidatorPool) GetUnitID(epoch uint64, nodeID enode.ID) (uint32, error return vp.currentValidators.UnitID(nodeID) } -// UnitID return current node's index according epoch -func (vp *ValidatorPool) UnitID(epoch uint64) (uint32, error) { - return vp.GetUnitID(epoch, vp.nodeID) -} - func NextRound(blockNumber uint64) uint64 { return blockNumber + 1 } diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index 4ad514acd3..20e0630e90 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -647,14 +647,19 @@ func TestGetGroupID(t *testing.T) { assert.Equal(t, uint32(0), grpID) } -func TestGetUintID(t *testing.T) { +func TestGetUnitID(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNodeByNum(100) agency := newTestInnerAgency(nodes) vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), true, new(event.TypeMux)) - unitID, _ := vp.GetGroupID(0, nodes[0].Node.ID()) + gvs, _ := vp.currentValidators.GetGroupValidators(nodes[50].Node.ID()) + unitID, _ := gvs.GetUnitID(nodes[50].Node.ID()) assert.Equal(t, uint32(0), unitID) + + gvs, _ = vp.currentValidators.GetGroupValidators(nodes[55].Node.ID()) + unitID, _ = gvs.GetUnitID(nodes[55].Node.ID()) + assert.Equal(t, uint32(1), unitID) } func TestUpdate(t *testing.T) { diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 865f5f0ce6..a0e3d68711 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -103,13 +103,21 @@ func (sv SortedIndexValidatorNode) Len() int { return len(sv) } func (sv SortedIndexValidatorNode) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] } func (sv SortedIndexValidatorNode) Less(i, j int) bool { return sv[i].Index < sv[j].Index } +type GroupCoordinate struct { + groupID uint32 + unitID uint32 +} + +type IDCoordinateMap map[enode.ID]*GroupCoordinate + type GroupValidators struct { // all nodes in this group Nodes []*ValidateNode // Coordinators' index C0>C1>C2>C3... Units [][]uint32 // The group ID - groupID uint32 + groupID uint32 + nodesMap IDCoordinateMap } type Validators struct { @@ -295,8 +303,8 @@ func (vs *Validators) GetGroupValidators(nodeID enode.ID) (*GroupValidators, err var ret *GroupValidators for _, gvs := range vs.GroupNodes { - goupLen := len(gvs.Nodes) - if idx <= gvs.Nodes[goupLen-1].Index { + groupLen := len(gvs.Nodes) + if idx <= gvs.Nodes[groupLen-1].Index { ret = gvs break } @@ -318,26 +326,37 @@ func (vs *Validators) UnitID(nodeID enode.ID) (uint32, error) { if err != nil { return 0, err } - for i, node := range gvs.Nodes { - if idx == node.Index { - return uint32(i), nil - } - } - return 0, errors.New("not found the specified nodeID") + return gvs.GetUnitID(nodeID) } -func (gvs *GroupValidators) GroupedUnits() { +func (gvs *GroupValidators) GroupOrganized() { + gvs.nodesMap = make(IDCoordinateMap, len(gvs.Nodes)) coordinatorLimit := xcom.CoordinatorsLimit() - unit := make([]uint32, 0, coordinatorLimit) + idsSeq := make([]uint32, 0, coordinatorLimit) + unitID := uint32(0) for i, n := range gvs.Nodes { - unit = append(unit, n.Index) - if uint32(len(unit)) >= coordinatorLimit || i == len(gvs.Nodes)-1 { - gvs.Units = append(gvs.Units, unit) - unit = make([]uint32, 0, coordinatorLimit) + gvs.nodesMap[n.NodeID] = &GroupCoordinate{ + unitID: unitID, + groupID: gvs.groupID, + } + idsSeq = append(idsSeq, n.Index) + if uint32(len(idsSeq)) >= coordinatorLimit || i == len(gvs.Nodes)-1 { + gvs.Units = append(gvs.Units, idsSeq) + idsSeq = make([]uint32, 0, coordinatorLimit) + unitID = unitID + 1 } } } +// return node's unitID +func (gvs *GroupValidators) GetUnitID(id enode.ID) (uint32, error) { + pos, ok := gvs.nodesMap[id] + if ok { + return pos.unitID, nil + } + return uint32(0), errors.New("not found the specified validator") +} + // return groupID func (gvs *GroupValidators) GetGroupID() uint32 { return gvs.groupID @@ -361,7 +380,9 @@ func (vs *Validators) Grouped() error { if len(vs.SortedNodes) == 0 { vs.Sort() } - + if uint32(len(vs.SortedNodes)) <= xcom.MaxGroupValidators() { + return errors.New("no need grouped!") + } validatorCount := uint32(vs.SortedNodes.Len()) groupNum := validatorCount / xcom.MaxGroupValidators() mod := validatorCount % xcom.MaxGroupValidators() @@ -393,7 +414,7 @@ func (vs *Validators) Grouped() error { // fill group unit for _, gvs := range vs.GroupNodes { - gvs.GroupedUnits() + gvs.GroupOrganized() } return nil } From 5b22a24573e3ac47aa8ab3161ab0a6bfa054ab34 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 15 Dec 2021 18:38:27 +0800 Subject: [PATCH 149/299] fix unitid err --- consensus/cbft/consensus_process.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 03782c5c86..8b140e8112 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -1066,6 +1066,8 @@ func (cbft *Cbft) tryChangeView() { if shouldSwitch { if err := cbft.validatorPool.Update(block.NumberU64(), cbft.state.Epoch()+1, false, block.Header().GetActiveVersion(), cbft.eventMux); err == nil { cbft.log.Info("Update validator success", "number", block.NumberU64()) + } else { + cbft.log.Error("Update validator failed!", "number", block.NumberU64(), "cbft.state.Epoch()", cbft.state.Epoch()) } } From 6201f50f88945dd972ede382520838544bf0084b Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 15 Dec 2021 19:17:41 +0800 Subject: [PATCH 150/299] update log --- consensus/cbft/consensus_process.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 8b140e8112..04c1f3457a 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -1067,7 +1067,7 @@ func (cbft *Cbft) tryChangeView() { if err := cbft.validatorPool.Update(block.NumberU64(), cbft.state.Epoch()+1, false, block.Header().GetActiveVersion(), cbft.eventMux); err == nil { cbft.log.Info("Update validator success", "number", block.NumberU64()) } else { - cbft.log.Error("Update validator failed!", "number", block.NumberU64(), "cbft.state.Epoch()", cbft.state.Epoch()) + cbft.log.Error("Update validator failed!", "number", block.NumberU64(), "err", err) } } From a0d73370eddd5cdd3f29c072de5645911ab18ab7 Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 16 Dec 2021 09:51:59 +0800 Subject: [PATCH 151/299] add debug log --- consensus/cbft/validator/validator.go | 1 + 1 file changed, 1 insertion(+) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 7abbc238ae..9f7ddb4ce3 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -425,6 +425,7 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo vp.lock.Lock() defer vp.lock.Unlock() + log.Debug("Update", "blockNumber", blockNumber, "epoch", epoch, "isElection", isElection, "version", version) // 生效后第一个共识周期的Election block已经是新值(2130)所以第一次触发update是cbft.tryChangeView->shouldSwitch if blockNumber <= vp.switchPoint && !isElection { log.Debug("Already update validator before", "blockNumber", blockNumber, "switchPoint", vp.switchPoint) From 9da94ecf759fe45637cc362ef85a46daab9ba1d9 Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 16 Dec 2021 11:07:56 +0800 Subject: [PATCH 152/299] add logs --- consensus/cbft/validator/validator.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 9f7ddb4ce3..2c32236aa3 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -352,7 +352,9 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID pool.switchPoint = pool.currentValidators.ValidBlockNumber - 1 } if needGroup { - pool.organize(pool.currentValidators, epoch, eventMux) + if err := pool.organize(pool.currentValidators, epoch, eventMux); err != nil { + log.Error("ValidatorPool organized failed!", "error", err) + } if pool.nextValidators == nil { nds, err := pool.agency.GetValidators(NextRound(pool.currentValidators.ValidBlockNumber)) if err != nil { From 371df42d81c05649f157aa3635e74977292883bc Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 16 Dec 2021 11:12:33 +0800 Subject: [PATCH 153/299] add logs --- consensus/cbft/validator/validator.go | 1 + 1 file changed, 1 insertion(+) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 2c32236aa3..9d19aa2622 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -882,6 +882,7 @@ func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64 // 当前节点不是共识节点 return err } + log.Debug("ValidatorPool organized OK!", "epoch", epoch, "validators", validators.String()) consensusNodeIDs := validators.NodeList() groupNodeIDs := gvs.NodeList() From b0b8711c6d56da09732f9697187ad0c2749733cc Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 16 Dec 2021 14:14:45 +0800 Subject: [PATCH 154/299] change GetActiveVersion method --- consensus/cbft/cbft.go | 5 ++++- consensus/cbft/consensus_process.go | 7 ++++++- core/cbfttypes/type.go | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index c48b319a34..c12f668e7d 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -1357,7 +1357,10 @@ func (cbft *Cbft) commitBlock(commitBlock *types.Block, commitQC *ctypes.QuorumC ChainStateUpdateCB: func() { cbft.bridge.UpdateChainState(qcState, lockState, commitState) }, }) - activeVersion := cpy.Header().GetActiveVersion() + activeVersion, err := cbft.blockCache.GetActiveVersion(commitBlock.Header()) + if err != nil { + log.Error("GetActiveVersion failed", "err", err) + } // should grouped according max commit block's state shouldGroup := func() bool { return cbft.validatorPool.NeedGroup() || activeVersion >= params.FORKVERSION_0_17_0 diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 04c1f3457a..0ca76ed46f 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -1063,8 +1063,13 @@ func (cbft *Cbft) tryChangeView() { (qc != nil && qc.Epoch == cbft.state.Epoch() && shouldSwitch) }() + activeVersion, err := cbft.blockCache.GetActiveVersion(block.Header()) + if err != nil { + log.Error("GetActiveVersion failed", "err", err) + } + if shouldSwitch { - if err := cbft.validatorPool.Update(block.NumberU64(), cbft.state.Epoch()+1, false, block.Header().GetActiveVersion(), cbft.eventMux); err == nil { + if err := cbft.validatorPool.Update(block.NumberU64(), cbft.state.Epoch()+1, false, activeVersion, cbft.eventMux); err == nil { cbft.log.Info("Update validator success", "number", block.NumberU64()) } else { cbft.log.Error("Update validator failed!", "number", block.NumberU64(), "err", err) diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index a0e3d68711..f8add3d165 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -381,7 +381,7 @@ func (vs *Validators) Grouped() error { vs.Sort() } if uint32(len(vs.SortedNodes)) <= xcom.MaxGroupValidators() { - return errors.New("no need grouped!") + return errors.New("no need grouped") } validatorCount := uint32(vs.SortedNodes.Len()) groupNum := validatorCount / xcom.MaxGroupValidators() From 28062f2d66949702f464ac306ba6bf993726a93f Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Thu, 16 Dec 2021 16:19:17 +0800 Subject: [PATCH 155/299] Added RPC interface for obtaining PubSub status information --- consensus/cbft/api.go | 7 +++++ consensus/cbft/cbft.go | 5 +++ consensus/cbft/network/pubsub.go | 7 +++-- internal/web3ext/web3ext.go | 4 +++ p2p/pubsub/pubsub.go | 54 ++++++++++++++++++++++++++++++++ p2p/pubsub_server.go | 4 +++ 6 files changed, 79 insertions(+), 2 deletions(-) diff --git a/consensus/cbft/api.go b/consensus/cbft/api.go index 500a91ef6b..234d3580a3 100644 --- a/consensus/cbft/api.go +++ b/consensus/cbft/api.go @@ -18,6 +18,7 @@ package cbft import ( "encoding/json" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/state" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" @@ -36,6 +37,7 @@ type API interface { Evidences() string GetPrepareQC(number uint64) *types.QuorumCert GetSchnorrNIZKProve() (*bls.SchnorrProof, error) + PubSubStatus() *pubsub.Status } // PublicDebugConsensusAPI provides an API to access the Alaya blockchain. @@ -66,6 +68,11 @@ func (s *PublicDebugConsensusAPI) GetPrepareQC(number uint64) *types.QuorumCert return s.engine.GetPrepareQC(number) } +// Get information about the nodes in PubSub +func (s *PublicDebugConsensusAPI) PubSubStatus() *pubsub.Status { + return s.engine.PubSubStatus() +} + // PublicPlatonConsensusAPI provides an API to access the Alaya blockchain. // It offers only methods that operate on public data that // is freely available to anyone. diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index c48b319a34..bb6ebea929 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -22,6 +22,7 @@ import ( "crypto/elliptic" "encoding/json" "fmt" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "strings" "sync/atomic" @@ -1009,6 +1010,10 @@ func (cbft *Cbft) Status() []byte { return <-status } +func (cbft *Cbft) PubSubStatus() *pubsub.Status { + return cbft.pubSub.GetAllPubSubStatus() +} + // GetPrepareQC returns the QC data of the specified block height. func (cbft *Cbft) GetPrepareQC(number uint64) *ctypes.QuorumCert { cbft.log.Debug("get prepare QC") diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index a4abb13a42..42ebdc183e 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -61,8 +61,7 @@ type PubSub struct { pss *p2p.PubSubServer config ctypes.Config getPeerById getByIDFunc // Used to get peer by ID. - - onReceive receiveCallback + onReceive receiveCallback // All topics subscribed topics map[string]*pubsub.Topic @@ -273,3 +272,7 @@ func (ps *PubSub) Publish(topic string, code uint64, data interface{}) error { func (ps *PubSub) Stop() { close(ps.quit) } + +func (ps *PubSub) GetAllPubSubStatus() *pubsub.Status { + return ps.pss.GetAllPubSubStatus() +} diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index d4649d2bd8..8ba1bf0e2e 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -340,6 +340,10 @@ web3._extend({ name: 'consensusStatus', call: 'debug_consensusStatus', }), + new web3._extend.Method({ + name: 'pubSubStatus', + call: 'debug_pubSubStatus', + }), new web3._extend.Method({ name: 'economicConfig', call: 'debug_economicConfig', diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 1c8b252efa..d8555d5c0c 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -1351,3 +1351,57 @@ type addRelayReq struct { topic string resp chan RelayCancelFunc } + +type Status struct { + Peers map[enode.ID]ProtocolID `json:"peers"` + Mesh map[string][]enode.ID `json:"mesh"` + OutMesh map[string][]enode.ID `json:"outMesh"` + Topics []string `json:"topics"` +} + +func (p *PubSub) GetAllPubSubStatus() *Status { + status := &Status{} + getInfo := func() { + gsr, ok := p.rt.(*GossipSubRouter) + if !ok { + return + } + newPeers := gsr.peers + status.Peers = newPeers + + newMesh := make(map[string][]enode.ID, len(gsr.mesh)) + for topic, nodeIdMap := range gsr.mesh { + ids := make([]enode.ID, 0) + for nodeId, _ := range nodeIdMap { + ids = append(ids, nodeId) + } + newMesh[topic] = ids + } + status.Mesh = newMesh + + outMesh := make(map[string][]enode.ID) + for k, v := range p.topics { + nodeList := make([]enode.ID, 0) + tmpMap := gsr.mesh[k] + for n := range v { + if _, ok := tmpMap[n]; !ok { + nodeList = append(nodeList, n) + } + } + outMesh[k] = nodeList + } + status.OutMesh = outMesh + + myTopics := make([]string, 0) + for t := range p.myTopics { + myTopics = append(myTopics, t) + } + status.Topics = myTopics + } + + select { + case p.eval <- getInfo: + case <-p.ctx.Done(): + } + return status +} diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go index c726d052b0..c9e3c2c687 100644 --- a/p2p/pubsub_server.go +++ b/p2p/pubsub_server.go @@ -70,3 +70,7 @@ func (pss *PubSubServer) NewConn(peer *Peer, rw MsgReadWriter) chan error { func (pss *PubSubServer) DiscoverTopic(ctx context.Context, topic string) { pss.p2pServer.DiscoverTopic(ctx, topic) } + +func (pss *PubSubServer) GetAllPubSubStatus() *pubsub.Status { + return pss.pubSub.GetAllPubSubStatus() +} From e8c1b971564c28c2f982db78f6e665927385958d Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 16 Dec 2021 17:28:50 +0800 Subject: [PATCH 156/299] fix panic caused by gvs is nil --- consensus/cbft/validator/validator.go | 10 +++++-- consensus/cbft/validator/validator_test.go | 33 +++++++++++----------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 9d19aa2622..0a4b561b9d 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -437,7 +437,12 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo var err error var nds *cbfttypes.Validators needGroup := version >= params.FORKVERSION_0_17_0 - nextRoundBlockNumber := blockNumber + xcom.ElectionDistance() + 1 + var nextRoundBlockNumber uint64 + if isElection { + nextRoundBlockNumber = blockNumber + xcom.ElectionDistance() + 1 + } else { + nextRoundBlockNumber = blockNumber + 1 + } //生效后第一个共识周期的switchpoint是旧值,此时不能切换 //判断依据是新validators和current完全相同且nextValidators为空 @@ -901,7 +906,8 @@ func (vp *ValidatorPool) dissolve(epoch uint64, eventMux *event.TypeMux) { return } gvs, err := vp.prevValidators.GetGroupValidators(vp.nodeID) - if nil != err { + if nil != err || gvs == nil { + // nil != err 说明当前节点上一轮不是共识节点,gvs == nil意味着上一轮共识没分组 return } diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index 20e0630e90..595b21bd23 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -444,12 +444,14 @@ func newTestInnerAgency(nodes []params.CbftNode) consensus.Agency { func TestValidatorPool(t *testing.T) { bls.Init(bls.BLS12_381) - nodes := newTestNode() + nodes := newTestNodeByNum(100) agency := newTestInnerAgency(nodes) + eventMux := &event.TypeMux{} - validatorPool := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, new(event.TypeMux)) + validatorPool := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), true, eventMux) assert.False(t, validatorPool.ShouldSwitch(0)) - assert.True(t, validatorPool.ShouldSwitch(40)) + assert.True(t, validatorPool.ShouldSwitch(1000)) + assert.Equal(t, uint64(1000), agency.GetLastNumber(2)) node, err := validatorPool.GetValidatorByNodeID(0, nodes[0].Node.ID()) assert.Nil(t, err) @@ -462,7 +464,7 @@ func TestValidatorPool(t *testing.T) { assert.Nil(t, err) assert.Equal(t, node.NodeID, nodes[1].Node.ID()) - _, err = validatorPool.GetValidatorByIndex(0, 5) + _, err = validatorPool.GetValidatorByIndex(0, 100) assert.Equal(t, err, errors.New("not found the specified validator")) vds := newValidators(nodes, 0) @@ -474,7 +476,7 @@ func TestValidatorPool(t *testing.T) { _, err = validatorPool.GetValidatorByAddr(0, common.NodeAddress{}) assert.Equal(t, err, errors.New("invalid address")) - nodeID := validatorPool.GetNodeIDByIndex(0, 4) + nodeID := validatorPool.GetNodeIDByIndex(0, 100) assert.Equal(t, nodeID, enode.ID{}) nodeID = validatorPool.GetNodeIDByIndex(0, 0) @@ -494,12 +496,6 @@ func TestValidatorPool(t *testing.T) { assert.True(t, validatorPool.IsValidator(0, nodes[0].Node.ID())) assert.True(t, validatorPool.Len(0) == len(nodes)) assert.True(t, validatorPool.IsCandidateNode(enode.ZeroIDv0)) - - eventMux := &event.TypeMux{} - - validatorPool.Update(80, 1, false, 0, eventMux) - assert.True(t, validatorPool.IsValidator(0, nodes[0].Node.ID())) - assert.False(t, validatorPool.IsValidator(1, nodes[0].Node.ID())) } func TestValidatorPoolVerify(t *testing.T) { @@ -674,17 +670,22 @@ func TestUpdate(t *testing.T) { assert.Equal(t, true, vp.NeedGroup()) assert.Equal(t, lastNumber, vp.lastNumber) assert.Equal(t, vp.prevValidators, vp.currentValidators) - next, err := agency.GetValidators(lastNumber + 1) + + nextNodes := newTestNodeByNum(100) + nextAgency := newTestInnerAgency(nextNodes) + + next, err := nextAgency.GetValidators(lastNumber + 1) if err != nil { t.Log("agency.GetValidators", "err", err) } next.Grouped() assert.NotEqual(t, vp.currentValidators, next) - vp.Update(250, 0, false, 0, eventMux) + vp.Update(1000, 0, false, 4352, eventMux) assert.Nil(t, vp.nextValidators) - vp.Update(vp.lastNumber, 0, true, 0, eventMux) - assert.Equal(t, vp.nextValidators, next) - vp.Update(vp.lastNumber+1, 0, false, 0, eventMux) + vp.Update(vp.lastNumber, 0, true, 4352, eventMux) + assert.False(t, vp.nextValidators.Equal(next)) + vp.nextValidators = next + vp.Update(vp.lastNumber+1, 0, false, 4352, eventMux) assert.Nil(t, vp.nextValidators) } From 81a4f2792337451152a115b736cf182cab2f3936 Mon Sep 17 00:00:00 2001 From: benbaley Date: Fri, 17 Dec 2021 15:45:56 +0800 Subject: [PATCH 157/299] update tests --- consensus/cbft/validator/validator_test.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index 595b21bd23..b9fcca3851 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -671,21 +671,27 @@ func TestUpdate(t *testing.T) { assert.Equal(t, lastNumber, vp.lastNumber) assert.Equal(t, vp.prevValidators, vp.currentValidators) + vp.Update(250, 0, false, 4352, eventMux) + assert.Nil(t, vp.nextValidators) + assert.False(t, vp.NeedGroup()) + assert.Equal(t, vp.epoch, uint64(0)) + + vp.Update(980, 0, true, 4352, eventMux) + assert.True(t, vp.NeedGroup()) + assert.Equal(t, vp.epoch, uint64(0)) + nextNodes := newTestNodeByNum(100) nextAgency := newTestInnerAgency(nextNodes) - next, err := nextAgency.GetValidators(lastNumber + 1) if err != nil { t.Log("agency.GetValidators", "err", err) } next.Grouped() assert.NotEqual(t, vp.currentValidators, next) - - vp.Update(1000, 0, false, 4352, eventMux) - assert.Nil(t, vp.nextValidators) - vp.Update(vp.lastNumber, 0, true, 4352, eventMux) assert.False(t, vp.nextValidators.Equal(next)) vp.nextValidators = next - vp.Update(vp.lastNumber+1, 0, false, 4352, eventMux) + vp.Update(vp.lastNumber+1, 1, false, 4352, eventMux) + assert.True(t, vp.NeedGroup()) + assert.Equal(t, vp.epoch, uint64(1)) assert.Nil(t, vp.nextValidators) } From b974cbfea069f645e6fe6befadecbd3eed73acae Mon Sep 17 00:00:00 2001 From: benbaley Date: Fri, 17 Dec 2021 16:42:38 +0800 Subject: [PATCH 158/299] fix epoch switch failed err --- consensus/cbft/validator/validator.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 0a4b561b9d..bcbcbeab3c 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -458,11 +458,9 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo vp.lastNumber = vp.agency.GetLastNumber(blockNumber) //不切换,所以epoch不增 vp.grouped = false + log.Debug("update currentValidators success!", "lastNumber", vp.lastNumber, "grouped", vp.grouped, "switchPoint", vp.switchPoint) return nil - } else { - return fmt.Errorf("ValidatorPool update failed, currentValidators:%s, nds:%s", vp.currentValidators.String(), nds.String()) } - log.Debug("update currentValidators success!", "lastNumber", vp.lastNumber, "grouped", vp.grouped, "switchPoint", vp.switchPoint) } //分组提案生效后第一个共识round到ElectionPoint时初始化分组信息 if !vp.grouped { From ff37db427a456ac902b06b5cc824bfbf0a70e792 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Fri, 17 Dec 2021 16:51:15 +0800 Subject: [PATCH 159/299] Increase log printing --- consensus/cbft/network/pubsub.go | 3 ++- p2p/network.go | 2 +- p2p/stream.go | 15 +++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index 42ebdc183e..0d580d8efa 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -76,11 +76,12 @@ type PubSub struct { // Protocol.Run() func (ps *PubSub) handler(peer *p2p.Peer, rw p2p.MsgReadWriter) error { + log.Debug("Start PubSub's processors", "id", peer.ID().TerminalString()) errCh := ps.pss.NewConn(peer, rw) defer ps.pss.Host().DisConn(peer.ID()) handlerErr := <-errCh - log.Info("pubsub's handler ends", "err", handlerErr) + log.Info("pubsub's handler ends", "id", peer.ID().TerminalString(), "err", handlerErr) return handlerErr } diff --git a/p2p/network.go b/p2p/network.go index 97d503e320..d6e50f0a00 100644 --- a/p2p/network.go +++ b/p2p/network.go @@ -23,7 +23,7 @@ import ( "time" ) -// Get a list of connected nodes +// Get a list of connected nodes from p2pServer type Peers func() []*Peer type Network struct { diff --git a/p2p/stream.go b/p2p/stream.go index cf0e42107b..e7e8aea05a 100644 --- a/p2p/stream.go +++ b/p2p/stream.go @@ -39,12 +39,12 @@ type Stream struct { func (s *Stream) String() string { return fmt.Sprintf( - " %s>", - s.conn.RemotePeer(), + " %s>", + s.conn.RemotePeer().ID().TerminalString(), ) } -// Conn returns the Conn associated with this stream, as an network.Conn +// Conn returns the Conn associated with this stream, as an pubsub.Conn func (s *Stream) Conn() pubsub.Conn { return s.conn } @@ -59,8 +59,7 @@ func (s *Stream) Protocol() pubsub.ProtocolID { // SetProtocol sets the protocol for this stream. // // This doesn't actually *do* anything other than record the fact that we're -// speaking the given protocol over this stream. It's still up to the user to -// negotiate the protocol. This is usually done by the Host. +// speaking the given protocol over this stream. func (s *Stream) SetProtocol(p pubsub.ProtocolID) { s.protocol.Store(p) } @@ -68,12 +67,12 @@ func (s *Stream) SetProtocol(p pubsub.ProtocolID) { func (s *Stream) Read(data interface{}) error { msg, err := s.rw.ReadMsg() if err != nil { - log.Error("Failed to read PubSub message", "err", err) + log.Error("Failed to read PubSub message", "id", s.conn.ID(), "err", err) return err } if err := msg.Decode(data); err != nil { - log.Error("Decode PubSub message fail", "err", err) + log.Error("Decode PubSub message fail", "id", s.conn.ID(), "err", err) return err } return nil @@ -81,7 +80,7 @@ func (s *Stream) Read(data interface{}) error { func (s *Stream) Write(data interface{}) error { if err := Send(s.rw, PubSubMsgCode, data); err != nil { - log.Error("Failed to send PubSub message", "err", err) + log.Error("Failed to send PubSub message", "id", s.conn.ID(), "err", err) return err } return nil From 33172fc7c353d569019108ea8c9c411d6ddf7728 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Fri, 17 Dec 2021 16:59:49 +0800 Subject: [PATCH 160/299] Update log --- p2p/pubsub/pubsub_common.go | 43 ++++--------------------------------- 1 file changed, 4 insertions(+), 39 deletions(-) diff --git a/p2p/pubsub/pubsub_common.go b/p2p/pubsub/pubsub_common.go index 1baa254c27..5bf0b87499 100644 --- a/p2p/pubsub/pubsub_common.go +++ b/p2p/pubsub/pubsub_common.go @@ -41,7 +41,7 @@ func (p *PubSub) handleNewStream(s Stream) { p.inboundStreamsMx.Lock() _, dup := p.inboundStreams[peer.ID()] if dup { - log.Debug("duplicate inbound stream , resetting other stream", "from", peer) + log.Debug("duplicate inbound stream , resetting other stream", "from", peer.ID().TerminalString()) } p.inboundStreams[peer.ID()] = s p.inboundStreamsMx.Unlock() @@ -57,7 +57,7 @@ func (p *PubSub) handleNewStream(s Stream) { for { rpc := new(RPC) if err := s.Read(&rpc.RPC); err != nil { - log.Error("Read message error", "err", err) + log.Error("Read message error", "id", peer.ID().TerminalString(), "err", err) p.notifyPeerDead(peer.ID()) s.Close(err) return @@ -88,7 +88,7 @@ func (p *PubSub) notifyPeerDead(pid enode.ID) { func (p *PubSub) handleNewPeer(ctx context.Context, pid enode.ID, outgoing <-chan *RPC) { s, err := p.host.NewStream(p.ctx, pid, p.rt.Protocols()...) if err != nil || s == nil { - log.Debug("opening new stream to peer: ", err, pid) + log.Debug("opening new stream to peer: ", "id", pid.TerminalString(), "err", err) select { case p.newPeerError <- pid: @@ -100,41 +100,13 @@ func (p *PubSub) handleNewPeer(ctx context.Context, pid enode.ID, outgoing <-cha go p.host.StreamHandler(s.Protocol())(s) go p.handleSendingMessages(ctx, s, outgoing) - //go p.handlePeerEOF(ctx, s) select { case p.newPeerStream <- s: case <-ctx.Done(): } } -/*func (p *PubSub) handlePeerEOF(ctx context.Context, s Stream) { - pid := s.Conn().RemotePeer() - r := protoio.NewDelimitedReader(s, p.maxMessageSize) - rpc := new(RPC) - for { - err := r.ReadMsg(&rpc.RPC) - if err != nil { - p.notifyPeerDead(pid.ID()) - return - } - - log.Debugf("unexpected message from %s", pid) - } -}*/ - func (p *PubSub) handleSendingMessages(ctx context.Context, s Stream, outgoing <-chan *RPC) { - //bufw := bufio.NewWriter(s) - //wc := protoio.NewDelimitedWriter(bufw) - - /*writeMsg := func(msg proto.Message) error { - err := wc.WriteMsg(msg) - if err != nil { - return err - } - - return bufw.Flush() - }*/ - for { select { case rpc, ok := <-outgoing: @@ -145,17 +117,10 @@ func (p *PubSub) handleSendingMessages(ctx context.Context, s Stream, outgoing < if !message.IsEmpty(&rpc.RPC) { message.Filling(&rpc.RPC) if err := s.Write(&rpc.RPC); err != nil { - log.Error("Send message fail", "err", err) + log.Error("Send message fail", "id", s.Conn().ID(), "err", err) return } } - - /* err := writeMsg(&rpc.RPC) - if err != nil { - s.Reset() - log.Debugf("writing message to %s: %s", s.Conn().RemotePeer(), err) - return - }*/ case <-ctx.Done(): return } From d17a18d9eaf16550b199be98e568da93434d5194 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Fri, 17 Dec 2021 17:15:05 +0800 Subject: [PATCH 161/299] Update log --- p2p/pubsub/pubsub.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index d8555d5c0c..0eb85b504f 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -530,12 +530,12 @@ func (p *PubSub) processLoop(ctx context.Context) { ch, ok := p.peers[pid.ID()] if !ok { - log.Warn("new stream for unknown peer: ", pid.ID().TerminalString()) + log.Warn("new stream for unknown peer", "id", pid.ID().TerminalString()) continue } if p.blacklist.Contains(pid.ID()) { - log.Warn("closing stream for blacklisted peer: ", pid.ID().TerminalString()) + log.Warn("closing stream for blacklisted peer", "id", pid.ID().TerminalString()) close(ch) delete(p.peers, pid.ID()) continue @@ -637,12 +637,12 @@ func (p *PubSub) handlePendingPeers() { for pid := range newPeers { if _, ok := p.peers[pid]; ok { - log.Debug("already have connection to peer: ", pid) + log.Debug("already have connection to peer", "id", pid.TerminalString()) continue } if p.blacklist.Contains(pid) { - log.Warn("ignoring connection from blacklisted peer: ", pid) + log.Warn("ignoring connection from blacklisted peer", "id", pid.TerminalString()) continue } @@ -1081,7 +1081,7 @@ func (p *PubSub) pushMsg(msg *Message) { // reject messages claiming to be from ourselves but not locally published self := p.host.ID() if msg.GetFrom() == self.ID() && src.ID() != self.ID() { - log.Debug("dropping message claiming to be from self but forwarded", "peer", src) + log.Debug("dropping message claiming to be from self but forwarded", "peer", src.ID().TerminalString()) p.tracer.RejectMessage(msg, RejectSelfOrigin) return } From bfba4de5d39b6e549fe719bf8e0a34894ba22d38 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Fri, 17 Dec 2021 17:26:23 +0800 Subject: [PATCH 162/299] Update log --- p2p/pubsub/discovery.go | 4 ++-- p2p/pubsub/gossipsub.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/p2p/pubsub/discovery.go b/p2p/pubsub/discovery.go index 0bf1ccc602..fa0df846c9 100644 --- a/p2p/pubsub/discovery.go +++ b/p2p/pubsub/discovery.go @@ -180,7 +180,7 @@ func (d *discover) Advertise(topic string) { go func() { next, err := d.discovery.Advertise(advertisingCtx, topic) if err != nil { - log.Warn("bootstrap: error providing rendezvous for %s: %s", "topic", topic, "err", err) + log.Warn("bootstrap: error providing rendezvous for topic", "topic", topic, "err", err) if next == 0 { next = discoveryAdvertiseRetryInterval } @@ -194,7 +194,7 @@ func (d *discover) Advertise(topic string) { case <-t.C: next, err = d.discovery.Advertise(advertisingCtx, topic) if err != nil { - log.Warn("bootstrap: error providing rendezvous for %s: %s", "topic", topic, "err", err) + log.Warn("bootstrap: error providing rendezvous for topic", "topic", topic, "err", err) if next == 0 { next = discoveryAdvertiseRetryInterval } diff --git a/p2p/pubsub/gossipsub.go b/p2p/pubsub/gossipsub.go index a0500bb961..7b725d58f5 100644 --- a/p2p/pubsub/gossipsub.go +++ b/p2p/pubsub/gossipsub.go @@ -746,7 +746,7 @@ func (gs *GossipSubRouter) handleGraft(p enode.ID, ctl *message.ControlMessage) // make sure we are not backing off that peer expire, backoff := gs.backoff[topic][p] if backoff && now.Before(expire) { - log.Debug("GRAFT: ignoring backed off peer", "peer", p) + log.Debug("GRAFT: ignoring backed off peer", "peer", p.TerminalString()) // add behavioural penalty gs.score.AddPenalty(p, 1) // no PX From b80a0ee443272c15a8f115c00308c26c73e1b319 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Fri, 17 Dec 2021 17:47:02 +0800 Subject: [PATCH 163/299] Add log --- p2p/pubsub/pubsub.go | 1 + 1 file changed, 1 insertion(+) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 0eb85b504f..445f16aa8a 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -1397,6 +1397,7 @@ func (p *PubSub) GetAllPubSubStatus() *Status { myTopics = append(myTopics, t) } status.Topics = myTopics + log.Debug("Get PubSub status information", "peers", gsr.peers, "mesh", gsr.mesh, "myTopics", p.myTopics) } select { From a2c837fd9bd5c06a2342e371b9610e1ae349bf37 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Fri, 17 Dec 2021 17:58:15 +0800 Subject: [PATCH 164/299] fix bug --- consensus/cbft/cbft.go | 7 ++++- consensus/cbft/consensus_process.go | 31 +++++++++++------- consensus/cbft/network/handler.go | 42 +++++++++++++++++++------ consensus/cbft/network/interface.go | 3 ++ consensus/cbft/protocols/protocol.go | 8 ++--- consensus/cbft/rg_broadcast.go | 47 +++++++++++++++++++++++++--- consensus/cbft/state/state.go | 8 ++--- consensus/cbft/types/crypto.go | 33 +++++++++++++++++++ 8 files changed, 143 insertions(+), 36 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 3fcae41f44..5a3290786d 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -1452,6 +1452,11 @@ func (cbft *Cbft) getGroupByValidatorID(epoch uint64, nodeID enode.ID) (uint32, return cbft.validatorPool.GetGroupByValidatorID(epoch, nodeID) } +// TODO just for log +func (cbft *Cbft) GetGroupByValidatorID(nodeID enode.ID) (uint32, uint32, error) { + return cbft.getGroupByValidatorID(cbft.state.Epoch(), nodeID) +} + func (cbft *Cbft) getGroupIndexes(epoch uint64) map[uint32][]uint32 { return cbft.validatorPool.GetGroupIndexes(epoch) } @@ -1483,7 +1488,7 @@ func (cbft *Cbft) verifyConsensusSign(msg ctypes.ConsensusMsg) error { // Verify consensus msg signature if err := cbft.validatorPool.Verify(msg.EpochNum(), msg.NodeIndex(), digest, msg.Sign()); err != nil { - return authFailedError{err: err} + return authFailedError{err: fmt.Errorf("verify consensus sign failed: %v", err)} } return nil } diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 0ca76ed46f..c4c256541a 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -207,7 +207,7 @@ func (cbft *Cbft) OnRGBlockQuorumCert(id string, msg *protocols.RGBlockQuorumCer } if err := cbft.AllowRGQuorumCert(msg); err != nil { - cbft.log.Error("Failed to verify the aggregate signer information of RGBlockQuorumCert", "RGBlockQuorumCert", msg.String(), "error", err.Error()) + cbft.log.Error("Failed to allow RGBlockQuorumCert", "RGBlockQuorumCert", msg.String(), "error", err.Error()) return err } @@ -221,7 +221,7 @@ func (cbft *Cbft) OnRGBlockQuorumCert(id string, msg *protocols.RGBlockQuorumCer // VerifyQuorumCert,This method simply verifies the correctness of the aggregated signature itself // Before this, it is necessary to verify parentqc, whether the number of group signatures is sufficient, whether all signers are group members, whether the message is sent by group members. if err := cbft.verifyQuorumCert(msg.BlockQC); err != nil { - cbft.log.Error("Failed to verify group prepareQC", "err", err.Error()) + cbft.log.Error("Failed to verify RGBlockQuorumCert blockQC", "blockQC", msg.BlockQC.String(), "err", err.Error()) return &authFailedError{err} } @@ -328,7 +328,7 @@ func (cbft *Cbft) OnRGViewChangeQuorumCert(id string, msg *protocols.RGViewChang } if err := cbft.AllowRGQuorumCert(msg); err != nil { - cbft.log.Error("Failed to verify the aggregate signer information of RGViewChangeQuorumCert", "RGViewChangeQuorumCert", msg.String(), "error", err.Error()) + cbft.log.Error("Failed to allow RGViewChangeQuorumCert", "RGViewChangeQuorumCert", msg.String(), "error", err.Error()) return err } @@ -336,14 +336,14 @@ func (cbft *Cbft) OnRGViewChangeQuorumCert(id string, msg *protocols.RGViewChang var err error if node, err = cbft.verifyConsensusMsg(msg); err != nil { - cbft.log.Error("Failed to verify viewChange", "viewChange", msg.String(), "error", err.Error()) + cbft.log.Error("Failed to verify RGViewChangeQuorumCert", "viewChange", msg.String(), "error", err.Error()) return err } // VerifyQuorumCert,This method simply verifies the correctness of the aggregated signature itself // Before this, it is necessary to verify parentqc, whether the number of group signatures is sufficient, whether all signers are group members, whether the message is sent by group members. if err := cbft.verifyGroupViewChangeQC(msg.GroupID, msg.ViewChangeQC); err != nil { - cbft.log.Error("Failed to verify group viewChangeQC", "err", err.Error()) + cbft.log.Error("Failed to verify RGViewChangeQuorumCert viewChangeQC", "err", err.Error()) return &authFailedError{err} } @@ -380,7 +380,7 @@ func (cbft *Cbft) OnViewTimeout() { ViewNumber: cbft.state.ViewNumber(), BlockHash: hash, BlockNumber: number, - ValidatorIndex: uint32(node.Index), + ValidatorIndex: node.Index, PrepareQC: qc, } @@ -699,6 +699,8 @@ func (cbft *Cbft) trySendRGBlockQuorumCert() { cbft.RGBroadcastManager.AsyncSendRGQuorumCert(&awaitingRGBlockQC{ groupID: groupID, blockIndex: index, + epoch: cbft.state.Epoch(), + viewNumber: cbft.state.ViewNumber(), }) cbft.state.AddSendRGBlockQuorumCerts(index) cbft.log.Debug("Send RGBlockQuorumCert asynchronously", "blockIndex", index, "groupID", groupID) @@ -754,6 +756,7 @@ func (cbft *Cbft) trySendRGViewChangeQuorumCert() { if alreadyRGViewChangeQuorumCerts(groupID) || enoughViewChanges(groupID) { cbft.RGBroadcastManager.AsyncSendRGQuorumCert(&awaitingRGViewQC{ groupID: groupID, + epoch: cbft.state.Epoch(), viewNumber: cbft.state.ViewNumber(), }) cbft.state.AddSendRGViewChangeQuorumCerts(cbft.state.ViewNumber()) @@ -963,14 +966,16 @@ func (cbft *Cbft) findQCBlock() { if len(knownIndexes) >= threshold { rgqcs := cbft.state.FindMaxRGQuorumCerts(next) qc = cbft.combinePrepareQC(rgqcs) - allVotes := cbft.state.AllPrepareVoteByIndex(next) + cbft.log.Trace("enoughCombine rgqcs", "qc", qc.String()) + allVotes := cbft.state.AllPrepareVoteByIndex(next) for index, v := range allVotes { if qc.Len() >= threshold { break // The merge can be stopped when the threshold is reached } if !qc.HasSign(index) { - qc.AddSign(v.Signature, v.BlockIndex) + cbft.log.Trace("enoughCombine add vote", "index", v.NodeIndex(), "vote", v.String()) + qc.AddSign(v.Signature, v.NodeIndex()) } } blockWholeQCByCombineCounter.Inc(1) @@ -1097,7 +1102,7 @@ func (cbft *Cbft) tryChangeView() { if size >= threshold { viewChangeQC = cbft.generateViewChangeQC(cbft.state.AllViewChange()) viewWholeQCByVcsCounter.Inc(1) - cbft.log.Info("Receive enough viewchange, generate viewChangeQC", "view", cbft.state.ViewString(), "viewChangeQC", viewChangeQC.String()) + cbft.log.Info("Receive enough viewchange, generate viewChangeQC", "viewChangeQC", viewChangeQC.String()) } return viewChangeQC.HasLength() >= threshold } @@ -1113,7 +1118,7 @@ func (cbft *Cbft) tryChangeView() { if size >= threshold { viewChangeQC = cbft.combineViewChangeQC(viewChangeQCs) viewWholeQCByRGQCCounter.Inc(1) - cbft.log.Debug("Enough RGViewChangeQuorumCerts have been received, combine ViewChangeQC", "qc", qc.String()) + cbft.log.Debug("Enough RGViewChangeQuorumCerts have been received, combine ViewChangeQC", "viewChangeQC", viewChangeQC.String()) } return viewChangeQC.HasLength() >= threshold } @@ -1123,18 +1128,20 @@ func (cbft *Cbft) tryChangeView() { if len(knownIndexes) >= threshold { viewChangeQCs := cbft.state.FindMaxRGViewChangeQuorumCerts() viewChangeQC = cbft.combineViewChangeQC(viewChangeQCs) - allViewChanges := cbft.state.AllViewChange() + cbft.log.Trace("enoughCombine viewChangeQCs", "viewChangeQC", viewChangeQC.String()) + allViewChanges := cbft.state.AllViewChange() for index, v := range allViewChanges { if viewChangeQC.HasLength() >= threshold { break // The merge can be stopped when the threshold is reached } if !viewChangeQC.HasSign(index) { + cbft.log.Trace("enoughCombine add viewChange", "index", v.NodeIndex(), "viewChange", v.String()) cbft.MergeViewChange(viewChangeQC, v) } } viewWholeQCByCombineCounter.Inc(1) - cbft.log.Debug("Enough RGViewChangeQuorumCerts and viewchange have been received, combine ViewChangeQC", "qc", qc.String()) + cbft.log.Debug("Enough RGViewChangeQuorumCerts and viewchange have been received, combine ViewChangeQC", "viewChangeQC", viewChangeQC.String()) } return viewChangeQC.HasLength() >= threshold } diff --git a/consensus/cbft/network/handler.go b/consensus/cbft/network/handler.go index 16734ede78..4ef4fb8917 100644 --- a/consensus/cbft/network/handler.go +++ b/consensus/cbft/network/handler.go @@ -18,6 +18,7 @@ package network import ( "fmt" + "github.com/AlayaNetwork/Alaya-Go/internal/debug" "math/big" "math/rand" "reflect" @@ -442,26 +443,47 @@ func (h *EngineManager) handler(p *p2p.Peer, rw p2p.MsgReadWriter) error { } func (h *EngineManager) HandleRGMsg(p *peer, msg *p2p.Msg) error { + // TODO just for log + logVerbosity := debug.GetLogVerbosity() + if logVerbosity == log.LvlTrace { + myGroupID, _, err1 := h.engine.GetGroupByValidatorID(h.engine.Node().ID()) + if err1 != nil { + p.Log().Error("GetGroupByValidatorID error", "nodeID", h.engine.Node().ID().String()) + } + herGroupID, _, err2 := h.engine.GetGroupByValidatorID(p.Node().ID()) + if err2 != nil { + p.Log().Error("GetGroupByValidatorID error", "nodeID", p.Node().ID().String()) + } + if err1 == nil && err2 == nil { + p.Log().Debug("HandleRGMsg", "myGroupID", myGroupID, "myNodeID", h.engine.Node().ID().String(), "herGroupID", herGroupID, "herNodeID", p.Node().ID().String()) + if myGroupID != herGroupID { + p.Log().Error("Invalidate rg msg", "myGroupID", myGroupID, "myNodeID", h.engine.Node().ID().String(), "herGroupID", herGroupID, "herNodeID", p.Node().ID().String()) + } + } + } + // All messages cannot exceed the maximum specified by the agreement. if msg.Size > protocols.CbftProtocolMaxMsgSize { return types.ErrResp(types.ErrMsgTooLarge, "%v > %v", msg.Size, protocols.CbftProtocolMaxMsgSize) } switch { - case msg.Code == protocols.RGBlockQuorumCertMsg: - var request protocols.RGBlockQuorumCert - if err := msg.Decode(&request); err != nil { + case msg.Code == protocols.PrepareVoteMsg: + var request protocols.PrepareVote + if err := msg.Decode(&request); err == nil { p.MarkMessageHash((&request).MsgHash()) MeteredReadRGMsg(msg) + p.Log().Debug("Receive PrepareVoteMsg", "msg", request.String()) return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) } return types.ErrResp(types.ErrInvalidRGMsg, "%s: %v", "unmatched code and data", msg.Code) - case msg.Code == protocols.RGViewChangeQuorumCertMsg: - var request protocols.RGViewChangeQuorumCert - if err := msg.Decode(&request); err != nil { + case msg.Code == protocols.ViewChangeMsg: + var request protocols.ViewChange + if err := msg.Decode(&request); err == nil { p.MarkMessageHash((&request).MsgHash()) MeteredReadRGMsg(msg) + p.Log().Debug("Receive ViewChangeMsg", "msg", request.String()) return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) } return types.ErrResp(types.ErrInvalidRGMsg, "%s: %v", "unmatched code and data", msg.Code) @@ -503,16 +525,16 @@ func (h *EngineManager) handleMsg(p *peer) error { // Message transfer to cbft message queue. return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) - case msg.Code == protocols.PrepareVoteMsg: - var request protocols.PrepareVote + case msg.Code == protocols.RGBlockQuorumCertMsg: + var request protocols.RGBlockQuorumCert if err := msg.Decode(&request); err != nil { return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) } p.MarkMessageHash((&request).MsgHash()) return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) - case msg.Code == protocols.ViewChangeMsg: - var request protocols.ViewChange + case msg.Code == protocols.RGViewChangeQuorumCertMsg: + var request protocols.RGViewChangeQuorumCert if err := msg.Decode(&request); err != nil { return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) } diff --git a/consensus/cbft/network/interface.go b/consensus/cbft/network/interface.go index 0d13cd675a..44f7beaa37 100644 --- a/consensus/cbft/network/interface.go +++ b/consensus/cbft/network/interface.go @@ -67,4 +67,7 @@ type Cbft interface { // BlockExists determines if a block exists. BlockExists(blockNumber uint64, blockHash common.Hash) error + + // TODO just for log + GetGroupByValidatorID(nodeID enode.ID) (uint32, uint32, error) } diff --git a/consensus/cbft/protocols/protocol.go b/consensus/cbft/protocols/protocol.go index 5c856f107e..cd2dab935b 100644 --- a/consensus/cbft/protocols/protocol.go +++ b/consensus/cbft/protocols/protocol.go @@ -208,8 +208,8 @@ type PrepareVote struct { } func (pv *PrepareVote) String() string { - return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,Hash:%s,Number:%d,BlockIndex:%d,ValidatorIndex:%d}", - pv.Epoch, pv.ViewNumber, pv.BlockHash.TerminalString(), pv.BlockNumber, pv.BlockIndex, pv.ValidatorIndex) + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,Hash:%s,Number:%d,BlockIndex:%d,Signature:%s,ValidatorIndex:%d}", + pv.Epoch, pv.ViewNumber, pv.BlockHash.TerminalString(), pv.BlockNumber, pv.BlockIndex, pv.Signature.String(), pv.ValidatorIndex) } func (pv *PrepareVote) MsgHash() common.Hash { @@ -821,8 +821,8 @@ type RGBlockQuorumCert struct { } func (rgb *RGBlockQuorumCert) String() string { - return fmt.Sprintf("{GroupID:%d,Epoch:%d,ViewNumber:%d,BlockIndx:%d,Hash:%s,Number:%d,ValidatorIndex:%d}", - rgb.GroupID, rgb.EpochNum(), rgb.ViewNum(), rgb.BlockIndx(), rgb.BHash().TerminalString(), rgb.BlockNum(), rgb.NodeIndex()) + return fmt.Sprintf("{GroupID:%d,Epoch:%d,ViewNumber:%d,BlockIndx:%d,Hash:%s,Number:%d,ValidatorIndex:%d,Signature:%s,ValidatorSet:%s}", + rgb.GroupID, rgb.EpochNum(), rgb.ViewNum(), rgb.BlockIndx(), rgb.BHash().TerminalString(), rgb.BlockNum(), rgb.NodeIndex(), rgb.Signature.String(), rgb.BlockQC.ValidatorSet.String()) } func (rgb *RGBlockQuorumCert) MsgHash() common.Hash { diff --git a/consensus/cbft/rg_broadcast.go b/consensus/cbft/rg_broadcast.go index f97626edb4..5aaecc5402 100644 --- a/consensus/cbft/rg_broadcast.go +++ b/consensus/cbft/rg_broadcast.go @@ -1,6 +1,7 @@ package cbft import ( + "github.com/AlayaNetwork/Alaya-Go/common/hexutil" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" "reflect" "sync" @@ -16,11 +17,15 @@ const ( type awaiting interface { GroupID() uint32 Index() uint64 + Epoch() uint64 + ViewNumber() uint64 } type awaitingRGBlockQC struct { groupID uint32 blockIndex uint32 + epoch uint64 + viewNumber uint64 } func (a *awaitingRGBlockQC) GroupID() uint32 { @@ -31,8 +36,17 @@ func (a *awaitingRGBlockQC) Index() uint64 { return uint64(a.blockIndex) } +func (a *awaitingRGBlockQC) Epoch() uint64 { + return a.epoch +} + +func (a *awaitingRGBlockQC) ViewNumber() uint64 { + return a.viewNumber +} + type awaitingRGViewQC struct { groupID uint32 + epoch uint64 viewNumber uint64 } @@ -44,6 +58,14 @@ func (a *awaitingRGViewQC) Index() uint64 { return a.viewNumber } +func (a *awaitingRGViewQC) Epoch() uint64 { + return a.epoch +} + +func (a *awaitingRGViewQC) ViewNumber() uint64 { + return a.viewNumber +} + type awaitingJob struct { jobTimer *time.Timer awaiting awaiting @@ -136,6 +158,11 @@ func (m *RGBroadcastManager) awaitingBroadcastRGViewChangeQuorumCert(viewNumber return false } +// equalsState checks if the message is currently CBFT status +func (m *RGBroadcastManager) equalsState(a awaiting) bool { + return a.Epoch() == m.cbft.state.Epoch() && a.ViewNumber() == m.cbft.state.ViewNumber() +} + // needBroadcast to check whether the message has been sent or is being sent func (m *RGBroadcastManager) needBroadcast(a awaiting) bool { switch msg := a.(type) { @@ -152,9 +179,11 @@ func (m *RGBroadcastManager) broadcast(a awaiting) { m.mux.Lock() defer m.mux.Unlock() - if !m.needBroadcast(a) { + if !m.equalsState(a) || !m.needBroadcast(a) { return } + m.cbft.log.Debug("Begin broadcast rg msg", "type", reflect.TypeOf(a), "groupID", a.GroupID(), "index", a.Index(), "delayDuration", m.delayDuration.String()) + timer := time.AfterFunc(m.delayDuration, func() { m.cbft.asyncCallCh <- func() { m.broadcastFunc(a) @@ -217,7 +246,7 @@ func (m *RGBroadcastManager) upgradeCoordinator(a awaiting) bool { return false } if !m.enoughCoordinator(groupID, unitID, coordinatorIndexes, receiveIndexes) { - m.cbft.log.Warn("Upgrade the current node to Coordinator", "groupID", groupID, "unitID", unitID, "coordinatorIndexes", coordinatorIndexes, "receiveIndexes", receiveIndexes) + m.cbft.log.Warn("Upgrade the current node to Coordinator", "type", reflect.TypeOf(a), "groupID", groupID, "index", a.Index(), "unitID", unitID, "coordinatorIndexes", coordinatorIndexes, "receiveIndexes", receiveIndexes) m.recordMetrics(a) return true } @@ -266,7 +295,8 @@ func (m *RGBroadcastManager) countCoordinator(unitID uint32, coordinatorIndexes } func (m *RGBroadcastManager) broadcastFunc(a awaiting) { - if !m.allowRGBlockQuorumCert(a) { + m.cbft.log.Debug("Broadcast rg msg", "type", reflect.TypeOf(a), "groupID", a.GroupID(), "index", a.Index()) + if !m.equalsState(a) || !m.allowRGBlockQuorumCert(a) { return } @@ -306,6 +336,11 @@ func (m *RGBroadcastManager) broadcastFunc(a awaiting) { return } m.cbft.network.Broadcast(rg) + // todo just for log + digest, _ := rg.CannibalizeBytes() + pubKey := m.cbft.config.Option.BlsPriKey.GetPublicKey() + blsPublicKey := hexutil.Encode(pubKey.Serialize()) + m.cbft.log.Debug("Success to broadcast RGBlockQuorumCert", "msg", rg.String(), "data", hexutil.Encode(digest), "sign", hexutil.Encode(rg.Sign()), "pubkey", blsPublicKey) m.hadSendRGBlockQuorumCerts[msg.Index()] = rg delete(m.awaitingRGBlockQuorumCerts, msg.Index()) m.cbft.state.AddRGBlockQuorumCert(node.Index, rg) @@ -326,6 +361,7 @@ func (m *RGBroadcastManager) broadcastFunc(a awaiting) { return } m.cbft.network.Broadcast(rg) + m.cbft.log.Debug("Success to broadcast RGViewChangeQuorumCert", "msg", rg.String()) m.hadSendRGViewChangeQuorumCerts[msg.Index()] = rg delete(m.awaitingRGViewChangeQuorumCerts, msg.Index()) m.cbft.state.AddRGViewChangeQuorumCert(node.Index, rg) @@ -348,12 +384,13 @@ func (m *RGBroadcastManager) Reset() { defer m.mux.Unlock() for _, await := range m.awaitingRGBlockQuorumCerts { - await.jobTimer.Stop() + await.jobTimer.Stop() // Some JobTimers are already running and may fail to stop } for _, await := range m.awaitingRGViewChangeQuorumCerts { - await.jobTimer.Stop() + await.jobTimer.Stop() // Some JobTimers are already running and may fail to stop } _, unitID, err := m.cbft.getGroupByValidatorID(m.cbft.state.Epoch(), m.cbft.Node().ID()) + m.cbft.log.Debug("RGBroadcastManager Reset", "unitID", unitID) if err != nil { m.cbft.log.Trace("The current node is not a consensus node, no need to start RGBroadcastManager", "epoch", m.cbft.state.Epoch(), "nodeID", m.cbft.Node().ID().String()) unitID = defaultUnitID diff --git a/consensus/cbft/state/state.go b/consensus/cbft/state/state.go index 35957f9749..ef97edd63a 100644 --- a/consensus/cbft/state/state.go +++ b/consensus/cbft/state/state.go @@ -1224,7 +1224,7 @@ func (srg *selectedRGBlockQuorumCerts) FindMaxRGQuorumCerts(blockIndex uint32) [ for _, qcs := range ps.GroupQuorumCerts { max := findMaxQuorumCert(qcs) if max != nil { - groupMaxs = append(groupMaxs, max) + groupMaxs = append(groupMaxs, max.DeepCopyQuorumCert()) } } } @@ -1239,7 +1239,7 @@ func (srg *selectedRGBlockQuorumCerts) FindMaxGroupRGQuorumCert(blockIndex, grou max := findMaxQuorumCert(gs) if max != nil { parentQC := srg.BlockRGBlockQuorumCerts[blockIndex].ParentQC - return max, parentQC + return max.DeepCopyQuorumCert(), parentQC.DeepCopyQuorumCert() } return nil, nil } @@ -1566,9 +1566,9 @@ func (srg *selectedRGViewChangeQuorumCerts) FindMaxGroupRGViewChangeQuorumCert(g prepareQCs := &ctypes.PrepareQCs{QCs: make([]*ctypes.QuorumCert, 0)} for hash, qcs := range rgqcs { max := findMaxViewChangeQuorumCert(qcs) - viewChangeQC.QCs = append(viewChangeQC.QCs, max) + viewChangeQC.QCs = append(viewChangeQC.QCs, max.DeepCopyViewChangeQuorumCert()) if srg.PrepareQCs != nil && srg.PrepareQCs[hash] != nil { - prepareQCs.QCs = append(prepareQCs.QCs, srg.PrepareQCs[hash]) + prepareQCs.QCs = append(prepareQCs.QCs, srg.PrepareQCs[hash].DeepCopyQuorumCert()) } } return viewChangeQC, prepareQCs diff --git a/consensus/cbft/types/crypto.go b/consensus/cbft/types/crypto.go index 32abfe5b9f..1f93920adb 100644 --- a/consensus/cbft/types/crypto.go +++ b/consensus/cbft/types/crypto.go @@ -78,6 +78,22 @@ type QuorumCert struct { ValidatorSet *utils.BitArray `json:"validatorSet"` } +func (q *QuorumCert) DeepCopyQuorumCert() *QuorumCert { + if q == nil { + return nil + } + qc := &QuorumCert{ + Epoch: q.Epoch, + ViewNumber: q.ViewNumber, + BlockHash: q.BlockHash, + BlockNumber: q.BlockNumber, + BlockIndex: q.BlockIndex, + Signature: q.Signature, + ValidatorSet: q.ValidatorSet.Copy(), + } + return qc +} + func (q QuorumCert) CannibalizeBytes() ([]byte, error) { buf, err := rlp.EncodeToBytes([]interface{}{ q.Epoch, @@ -192,6 +208,23 @@ type ViewChangeQuorumCert struct { ValidatorSet *utils.BitArray `json:"validatorSet"` } +func (q *ViewChangeQuorumCert) DeepCopyViewChangeQuorumCert() *ViewChangeQuorumCert { + if q == nil { + return nil + } + qc := &ViewChangeQuorumCert{ + Epoch: q.Epoch, + ViewNumber: q.ViewNumber, + BlockHash: q.BlockHash, + BlockNumber: q.BlockNumber, + BlockEpoch: q.BlockEpoch, + BlockViewNumber: q.BlockViewNumber, + Signature: q.Signature, + ValidatorSet: q.ValidatorSet.Copy(), + } + return qc +} + func (q ViewChangeQuorumCert) CannibalizeBytes() ([]byte, error) { buf, err := rlp.EncodeToBytes([]interface{}{ q.Epoch, From a93bde0d528267d969341c3d697faa107fbf95b3 Mon Sep 17 00:00:00 2001 From: benbaley Date: Mon, 20 Dec 2021 11:44:52 +0800 Subject: [PATCH 165/299] update validatorpool update func --- consensus/cbft/cbft.go | 6 ++-- consensus/cbft/consensus_process.go | 2 +- consensus/cbft/validator/validator.go | 32 ++++++++++++++++++---- consensus/cbft/validator/validator_test.go | 10 +++++-- consensus/cbft/wal_bridge.go | 8 ++++-- consensus/consensus.go | 1 + core/blockchain_reactor.go | 7 +++-- x/plugin/staking_plugin.go | 19 +++++++++++-- x/plugin/staking_plugin_test.go | 8 +++--- 9 files changed, 70 insertions(+), 23 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 5a3290786d..a679f32fda 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -22,10 +22,11 @@ import ( "crypto/elliptic" "encoding/json" "fmt" - "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" "strings" "sync/atomic" + "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub" + "github.com/AlayaNetwork/Alaya-Go/x/xutil" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" @@ -1367,6 +1368,7 @@ func (cbft *Cbft) commitBlock(commitBlock *types.Block, commitQC *ctypes.QuorumC log.Error("GetActiveVersion failed", "err", err) } // should grouped according max commit block's state + // TODO 升级后shouldGroup用块高判断,避免每次都通过cbft.blockCache.GetActiveVersion获取 shouldGroup := func() bool { return cbft.validatorPool.NeedGroup() || activeVersion >= params.FORKVERSION_0_17_0 } @@ -1374,7 +1376,7 @@ func (cbft *Cbft) commitBlock(commitBlock *types.Block, commitQC *ctypes.QuorumC // post GroupsTopicEvent to join topic according group info if xutil.IsElection(cpy.NumberU64(), activeVersion) { if shouldGroup() { - cbft.validatorPool.Update(cpy.NumberU64(), cbft.state.Epoch()+1, true, activeVersion, cbft.eventMux) + cbft.validatorPool.Update(cpy.Hash(), cpy.NumberU64(), cbft.state.Epoch()+1, true, activeVersion, cbft.eventMux) } } } diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index c4c256541a..4565139cb2 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -1074,7 +1074,7 @@ func (cbft *Cbft) tryChangeView() { } if shouldSwitch { - if err := cbft.validatorPool.Update(block.NumberU64(), cbft.state.Epoch()+1, false, activeVersion, cbft.eventMux); err == nil { + if err := cbft.validatorPool.Update(block.Hash(), block.NumberU64(), cbft.state.Epoch()+1, false, activeVersion, cbft.eventMux); err == nil { cbft.log.Info("Update validator success", "number", block.NumberU64()) } else { cbft.log.Error("Update validator failed!", "number", block.NumberU64(), "err", err) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index bcbcbeab3c..720f26a7f9 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -106,6 +106,10 @@ func (d *StaticAgency) GetValidators(uint64) (*cbfttypes.Validators, error) { return d.validators, nil } +func (d *StaticAgency) GetComingValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { + return d.GetValidators(blockNumber) +} + func (d *StaticAgency) IsCandidateNode(nodeID enode.IDv0) bool { return false } @@ -157,6 +161,10 @@ func (d *MockAgency) GetValidators(blockNumber uint64) (*cbfttypes.Validators, e return d.validators, nil } +func (d *MockAgency) GetComingValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { + return d.GetValidators(blockNumber) +} + func (d *MockAgency) IsCandidateNode(nodeID enode.IDv0) bool { return false } @@ -286,6 +294,10 @@ func (ia *InnerAgency) GetValidators(blockNumber uint64) (v *cbfttypes.Validator return &validators, nil } +func (ia *InnerAgency) GetComingValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { + return ia.GetValidators(blockNumber) +} + func (ia *InnerAgency) IsCandidateNode(nodeID enode.IDv0) bool { return true } @@ -423,7 +435,7 @@ func (vp *ValidatorPool) MockSwitchPoint(number uint64) { } // Update switch validators. -func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection bool, version uint32, eventMux *event.TypeMux) error { +func (vp *ValidatorPool) Update(blockHash common.Hash, blockNumber uint64, epoch uint64, isElection bool, version uint32, eventMux *event.TypeMux) error { vp.lock.Lock() defer vp.lock.Unlock() @@ -473,10 +485,18 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo if isElection { // 提前更新nextValidators,为了p2p早一步订阅分组事件以便建链接 - nds, err = vp.agency.GetValidators(nextRoundBlockNumber) - if err != nil { - log.Error("Get validator error", "blockNumber", blockNumber, "err", err) - return err + if blockHash != common.ZeroHash { + nds, err = vp.agency.GetComingValidators(blockHash, nextRoundBlockNumber) + if err != nil { + log.Error("Get coming validators error", "blockNumber", blockNumber, "err", err) + return err + } + } else { + nds, err = vp.agency.GetValidators(nextRoundBlockNumber) + if err != nil { + log.Error("Get validators error", "blockNumber", blockNumber, "err", err) + return err + } } vp.nextValidators = nds if vp.grouped { @@ -503,7 +523,7 @@ func (vp *ValidatorPool) Update(blockNumber uint64, epoch uint64, isElection boo currEpoch := vp.epoch vp.epoch = epoch vp.nextValidators = nil - log.Info("Update validator", "validators.len", vp.currentValidators.Len(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) + log.Info("Update validators", "validators.len", vp.currentValidators.Len(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) //切换共识轮时需要将上一轮分组的topic取消订阅 vp.dissolve(currEpoch, eventMux) } diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index b9fcca3851..39cd36efb0 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -596,6 +596,10 @@ func (m *mockAgency) GetValidators(blockNumber uint64) (*cbfttypes.Validators, e }, nil } +func (m *mockAgency) GetComingValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { + return m.GetValidators(blockNumber) +} + func (m *mockAgency) IsCandidateNode(dv0 enode.IDv0) bool { return false } func (m *mockAgency) OnCommit(block *types.Block) error { return nil } @@ -671,12 +675,12 @@ func TestUpdate(t *testing.T) { assert.Equal(t, lastNumber, vp.lastNumber) assert.Equal(t, vp.prevValidators, vp.currentValidators) - vp.Update(250, 0, false, 4352, eventMux) + vp.Update(common.ZeroHash, 250, 0, false, 4352, eventMux) assert.Nil(t, vp.nextValidators) assert.False(t, vp.NeedGroup()) assert.Equal(t, vp.epoch, uint64(0)) - vp.Update(980, 0, true, 4352, eventMux) + vp.Update(common.ZeroHash, 980, 0, true, 4352, eventMux) assert.True(t, vp.NeedGroup()) assert.Equal(t, vp.epoch, uint64(0)) @@ -690,7 +694,7 @@ func TestUpdate(t *testing.T) { assert.NotEqual(t, vp.currentValidators, next) assert.False(t, vp.nextValidators.Equal(next)) vp.nextValidators = next - vp.Update(vp.lastNumber+1, 1, false, 4352, eventMux) + vp.Update(common.ZeroHash, vp.lastNumber+1, 1, false, 4352, eventMux) assert.True(t, vp.NeedGroup()) assert.Equal(t, vp.epoch, uint64(1)) assert.Nil(t, vp.nextValidators) diff --git a/consensus/cbft/wal_bridge.go b/consensus/cbft/wal_bridge.go index e3bad8ef37..d4a531cb55 100644 --- a/consensus/cbft/wal_bridge.go +++ b/consensus/cbft/wal_bridge.go @@ -22,6 +22,8 @@ import ( "reflect" "time" + "github.com/AlayaNetwork/Alaya-Go/common" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/state" "github.com/AlayaNetwork/Alaya-Go/common/math" @@ -330,7 +332,7 @@ func (cbft *Cbft) recoveryQCState(qcs []*protocols.State, parent *types.Block) e // recoveryChainStateProcess tries to recovery the corresponding state to cbft consensus. func (cbft *Cbft) recoveryChainStateProcess(stateType uint16, s *protocols.State) { - cbft.trySwitchValidator(s.Block.NumberU64(), s.Block.ActiveVersion()) + cbft.trySwitchValidator(s.Block.Hash(), s.Block.NumberU64(), s.Block.ActiveVersion()) cbft.tryWalChangeView(s.QuorumCert.Epoch, s.QuorumCert.ViewNumber, s.Block, s.QuorumCert, nil) cbft.state.AddQCBlock(s.Block, s.QuorumCert) cbft.state.AddQC(s.QuorumCert) @@ -360,9 +362,9 @@ func (cbft *Cbft) recoveryChainStateProcess(stateType uint16, s *protocols.State } // trySwitch tries to switch next validator. -func (cbft *Cbft) trySwitchValidator(blockNumber uint64, version uint32) { +func (cbft *Cbft) trySwitchValidator(blockHash common.Hash, blockNumber uint64, version uint32) { if cbft.validatorPool.ShouldSwitch(blockNumber) { - if err := cbft.validatorPool.Update(blockNumber, cbft.state.Epoch()+1, false, version, cbft.eventMux); err != nil { + if err := cbft.validatorPool.Update(blockHash, blockNumber, cbft.state.Epoch()+1, false, version, cbft.eventMux); err != nil { cbft.log.Debug("Update validator error", "err", err.Error()) } } diff --git a/consensus/consensus.go b/consensus/consensus.go index 1812dcc6a1..df31341b15 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -165,6 +165,7 @@ type Agency interface { VerifyHeader(header *types.Header, stateDB *state.StateDB) error GetLastNumber(blockNumber uint64) uint64 GetValidators(blockNumber uint64) (*cbfttypes.Validators, error) + GetComingValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) IsCandidateNode(nodeID enode.IDv0) bool OnCommit(block *types.Block) error } diff --git a/core/blockchain_reactor.go b/core/blockchain_reactor.go index d4d5c4859f..84625017cf 100644 --- a/core/blockchain_reactor.go +++ b/core/blockchain_reactor.go @@ -147,7 +147,6 @@ func (bcr *BlockChainReactor) commit(block *types.Block) error { if err := plugin.Confirmed(bcr.NodeId, block); nil != err { log.Error("Failed to call Staking Confirmed", "blockNumber", block.Number(), "blockHash", block.Hash().Hex(), "err", err.Error()) } - } log.Info("Call snapshotdb commit on blockchain_reactor", "blockNumber", block.Number(), "blockHash", block.Hash()) @@ -394,7 +393,11 @@ func (bcr *BlockChainReactor) GetLastNumber(blockNumber uint64) uint64 { } func (bcr *BlockChainReactor) GetValidators(blockNumber uint64) (*cbfttypes.Validators, error) { - return plugin.StakingInstance().GetValidator(blockNumber) + return plugin.StakingInstance().GetValidators(blockNumber) +} + +func (bcr *BlockChainReactor) GetComingValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { + return plugin.StakingInstance().GetComingValidators(blockHash, blockNumber) } func (bcr *BlockChainReactor) IsCandidateNode(nodeID enode.IDv0) bool { diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index 73391343d1..68cb557f0a 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -2693,7 +2693,7 @@ func (sk *StakingPlugin) GetLastNumber(blockNumber uint64) uint64 { return 0 } -func (sk *StakingPlugin) GetValidator(blockNumber uint64) (*cbfttypes.Validators, error) { +func (sk *StakingPlugin) GetValidators(blockNumber uint64) (*cbfttypes.Validators, error) { valArr, err := sk.getCurrValList(common.ZeroHash, blockNumber, QueryStartIrr) if snapshotdb.NonDbNotFoundErr(err) { @@ -2703,7 +2703,22 @@ func (sk *StakingPlugin) GetValidator(blockNumber uint64) (*cbfttypes.Validators if nil == err && nil != valArr { return buildCbftValidators(valArr.Start, valArr.Arr), nil } - return nil, fmt.Errorf("Not Found Validators by blockNumber: %d", blockNumber) + return nil, fmt.Errorf("can not found validators by blockNumber: %d", blockNumber) +} + +// validatorpool 在选举块更新nextValidators,触发条件是选举块commit完成,此时block已经实际上不可逆 +// 不从QueryStartIrr查询的原因是此时blockchain_reactor订阅的cbftResult还没处理完,snapshotdb还没有更新最高不可逆区块,查不到 +func (sk *StakingPlugin) GetComingValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { + + valArr, err := sk.getCurrValList(blockHash, blockNumber, QueryStartNotIrr) + if snapshotdb.NonDbNotFoundErr(err) { + return nil, err + } + + if nil == err && nil != valArr { + return buildCbftValidators(valArr.Start, valArr.Arr), nil + } + return nil, fmt.Errorf("can not found validators by blockNumber: %d", blockNumber) } // NOTE: Verify that it is the validator of the current Epoch diff --git a/x/plugin/staking_plugin_test.go b/x/plugin/staking_plugin_test.go index da10c14ced..3e106c8419 100644 --- a/x/plugin/staking_plugin_test.go +++ b/x/plugin/staking_plugin_test.go @@ -3555,13 +3555,13 @@ func TestStakingPlugin_GetValidator(t *testing.T) { } /** - Start GetValidator + Start GetValidators */ - valArr, err := StakingInstance().GetValidator(header.Number.Uint64()) + valArr, err := StakingInstance().GetValidators(header.Number.Uint64()) - assert.Nil(t, err, fmt.Sprintf("Failed to GetValidator, err: %v", err)) + assert.Nil(t, err, fmt.Sprintf("Failed to GetValidators, err: %v", err)) assert.True(t, nil != valArr) - t.Log("GetValidator the validators is:", valArr) + t.Log("GetValidators the validators is:", valArr) } From 2cec955a3ecd70a517e73374ea83cf2bef58a0fc Mon Sep 17 00:00:00 2001 From: benbaley Date: Mon, 20 Dec 2021 12:08:54 +0800 Subject: [PATCH 166/299] rm BlockChainReactor->GetComingValidators --- consensus/cbft/validator/validator.go | 54 +++++++--------------- consensus/cbft/validator/validator_test.go | 16 +++---- consensus/consensus.go | 3 +- core/blockchain_reactor.go | 8 +--- x/plugin/staking_plugin.go | 26 ++++------- 5 files changed, 35 insertions(+), 72 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 720f26a7f9..405def0a66 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -102,14 +102,10 @@ func (d *StaticAgency) GetLastNumber(blockNumber uint64) uint64 { return 0 } -func (d *StaticAgency) GetValidators(uint64) (*cbfttypes.Validators, error) { +func (d *StaticAgency) GetValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { return d.validators, nil } -func (d *StaticAgency) GetComingValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { - return d.GetValidators(blockNumber) -} - func (d *StaticAgency) IsCandidateNode(nodeID enode.IDv0) bool { return false } @@ -154,17 +150,13 @@ func (d *MockAgency) GetLastNumber(blockNumber uint64) uint64 { return 0 } -func (d *MockAgency) GetValidators(blockNumber uint64) (*cbfttypes.Validators, error) { +func (d *MockAgency) GetValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { if blockNumber > d.interval && blockNumber%d.interval == 1 { d.validators.ValidBlockNumber = d.validators.ValidBlockNumber + d.interval + 1 } return d.validators, nil } -func (d *MockAgency) GetComingValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { - return d.GetValidators(blockNumber) -} - func (d *MockAgency) IsCandidateNode(nodeID enode.IDv0) bool { return false } @@ -214,7 +206,7 @@ func (ia *InnerAgency) GetLastNumber(blockNumber uint64) uint64 { if blockNumber <= ia.defaultBlocksPerRound { lastBlockNumber = ia.defaultBlocksPerRound } else { - vds, err := ia.GetValidators(blockNumber) + vds, err := ia.GetValidators(common.ZeroHash, blockNumber) if err != nil { log.Error("Get validator fail", "blockNumber", blockNumber) return 0 @@ -243,7 +235,7 @@ func (ia *InnerAgency) GetLastNumber(blockNumber uint64) uint64 { return lastBlockNumber } -func (ia *InnerAgency) GetValidators(blockNumber uint64) (v *cbfttypes.Validators, err error) { +func (ia *InnerAgency) GetValidators(blockHash common.Hash, blockNumber uint64) (v *cbfttypes.Validators, err error) { defaultValidators := *ia.defaultValidators baseNumber := blockNumber if blockNumber == 0 { @@ -294,10 +286,6 @@ func (ia *InnerAgency) GetValidators(blockNumber uint64) (v *cbfttypes.Validator return &validators, nil } -func (ia *InnerAgency) GetComingValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { - return ia.GetValidators(blockNumber) -} - func (ia *InnerAgency) IsCandidateNode(nodeID enode.IDv0) bool { return true } @@ -345,14 +333,14 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID } // FIXME: Check `GetValidators` return error if agency.GetLastNumber(blockNumber) == blockNumber { - pool.prevValidators, _ = agency.GetValidators(blockNumber) - pool.currentValidators, _ = agency.GetValidators(NextRound(blockNumber)) + pool.prevValidators, _ = agency.GetValidators(common.ZeroHash, blockNumber) + pool.currentValidators, _ = agency.GetValidators(common.ZeroHash, NextRound(blockNumber)) pool.lastNumber = agency.GetLastNumber(NextRound(blockNumber)) if blockNumber != 0 { pool.epoch += 1 } } else { - pool.currentValidators, _ = agency.GetValidators(blockNumber) + pool.currentValidators, _ = agency.GetValidators(common.ZeroHash, blockNumber) pool.prevValidators = pool.currentValidators pool.lastNumber = agency.GetLastNumber(blockNumber) } @@ -368,7 +356,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID log.Error("ValidatorPool organized failed!", "error", err) } if pool.nextValidators == nil { - nds, err := pool.agency.GetValidators(NextRound(pool.currentValidators.ValidBlockNumber)) + nds, err := pool.agency.GetValidators(common.ZeroHash, NextRound(pool.currentValidators.ValidBlockNumber)) if err != nil { log.Debug("Get nextValidators error", "blockNumber", blockNumber, "err", err) return pool @@ -386,12 +374,12 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID // Reset reset validator pool. func (vp *ValidatorPool) Reset(blockNumber uint64, epoch uint64, eventMux *event.TypeMux) { if vp.agency.GetLastNumber(blockNumber) == blockNumber { - vp.prevValidators, _ = vp.agency.GetValidators(blockNumber) - vp.currentValidators, _ = vp.agency.GetValidators(NextRound(blockNumber)) + vp.prevValidators, _ = vp.agency.GetValidators(common.ZeroHash, blockNumber) + vp.currentValidators, _ = vp.agency.GetValidators(common.ZeroHash, NextRound(blockNumber)) vp.lastNumber = vp.agency.GetLastNumber(NextRound(blockNumber)) vp.epoch = epoch + 1 } else { - vp.currentValidators, _ = vp.agency.GetValidators(blockNumber) + vp.currentValidators, _ = vp.agency.GetValidators(common.ZeroHash, blockNumber) vp.prevValidators = vp.currentValidators vp.lastNumber = vp.agency.GetLastNumber(blockNumber) vp.epoch = epoch @@ -459,7 +447,7 @@ func (vp *ValidatorPool) Update(blockHash common.Hash, blockNumber uint64, epoch //生效后第一个共识周期的switchpoint是旧值,此时不能切换 //判断依据是新validators和current完全相同且nextValidators为空 if !isElection && vp.nextValidators == nil && needGroup { - nds, err = vp.agency.GetValidators(nextRoundBlockNumber) + nds, err = vp.agency.GetValidators(blockHash, nextRoundBlockNumber) if err != nil { log.Error("Get validator error", "blockNumber", blockNumber, "err", err) return err @@ -485,18 +473,10 @@ func (vp *ValidatorPool) Update(blockHash common.Hash, blockNumber uint64, epoch if isElection { // 提前更新nextValidators,为了p2p早一步订阅分组事件以便建链接 - if blockHash != common.ZeroHash { - nds, err = vp.agency.GetComingValidators(blockHash, nextRoundBlockNumber) - if err != nil { - log.Error("Get coming validators error", "blockNumber", blockNumber, "err", err) - return err - } - } else { - nds, err = vp.agency.GetValidators(nextRoundBlockNumber) - if err != nil { - log.Error("Get validators error", "blockNumber", blockNumber, "err", err) - return err - } + nds, err = vp.agency.GetValidators(blockHash, nextRoundBlockNumber) + if err != nil { + log.Error("Get validators error", "blockNumber", blockNumber, "err", err) + return err } vp.nextValidators = nds if vp.grouped { @@ -506,7 +486,7 @@ func (vp *ValidatorPool) Update(blockHash common.Hash, blockNumber uint64, epoch // 节点中间重启过, nextValidators没有赋值 if vp.nextValidators == nil { // 此时blockNumber==vp.lastNumber blockNumber+1即为下一轮 - nds, err = vp.agency.GetValidators(NextRound(blockNumber)) + nds, err = vp.agency.GetValidators(blockHash, NextRound(blockNumber)) if err != nil { log.Error("Get validator error", "blockNumber", blockNumber, "err", err) return err diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index 39cd36efb0..fd2f22bf7b 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -215,7 +215,7 @@ func TestStaticAgency(t *testing.T) { vds := newValidators(nodes, 0) agency := NewStaticAgency(nodes) - validators, err := agency.GetValidators(0) + validators, err := agency.GetValidators(common.ZeroHash, 0) assert.True(t, err == nil) assert.Equal(t, *vds, *validators) assert.True(t, agency.Sign(nil) == nil) @@ -332,12 +332,12 @@ func TestInnerAgency(t *testing.T) { assert.True(t, agency.GetLastNumber(81) == 120) assert.True(t, agency.GetLastNumber(110) == 120) - validators, err := agency.GetValidators(0) + validators, err := agency.GetValidators(common.ZeroHash, 0) assert.True(t, err == nil) assert.Equal(t, *vds, *validators) assert.True(t, blockchain.Genesis() != nil) - newVds, err := agency.GetValidators(81) + newVds, err := agency.GetValidators(common.ZeroHash, 81) assert.True(t, err == nil) assert.True(t, newVds.Len() == 4) assert.True(t, newVds.ValidBlockNumber == 81) @@ -351,7 +351,7 @@ func TestInnerAgency(t *testing.T) { assert.True(t, newVds.String() != "") assert.False(t, newVds.Equal(validators)) - defaultVds, _ := agency.GetValidators(60) + defaultVds, _ := agency.GetValidators(common.ZeroHash, 60) assert.True(t, defaultVds.Equal(validators)) assert.True(t, agency.GetLastNumber(120) == 120) @@ -590,16 +590,12 @@ func (m *mockAgency) VerifyHeader(*types.Header, *state.StateDB) error { return func (m *mockAgency) GetLastNumber(blockNumber uint64) uint64 { return m.lastNumber } -func (m *mockAgency) GetValidators(blockNumber uint64) (*cbfttypes.Validators, error) { +func (m *mockAgency) GetValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { return &cbfttypes.Validators{ ValidBlockNumber: blockNumber, }, nil } -func (m *mockAgency) GetComingValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { - return m.GetValidators(blockNumber) -} - func (m *mockAgency) IsCandidateNode(dv0 enode.IDv0) bool { return false } func (m *mockAgency) OnCommit(block *types.Block) error { return nil } @@ -686,7 +682,7 @@ func TestUpdate(t *testing.T) { nextNodes := newTestNodeByNum(100) nextAgency := newTestInnerAgency(nextNodes) - next, err := nextAgency.GetValidators(lastNumber + 1) + next, err := nextAgency.GetValidators(common.ZeroHash, lastNumber+1) if err != nil { t.Log("agency.GetValidators", "err", err) } diff --git a/consensus/consensus.go b/consensus/consensus.go index df31341b15..b9e7bc008b 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -164,8 +164,7 @@ type Agency interface { Flush(header *types.Header) error VerifyHeader(header *types.Header, stateDB *state.StateDB) error GetLastNumber(blockNumber uint64) uint64 - GetValidators(blockNumber uint64) (*cbfttypes.Validators, error) - GetComingValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) + GetValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) IsCandidateNode(nodeID enode.IDv0) bool OnCommit(block *types.Block) error } diff --git a/core/blockchain_reactor.go b/core/blockchain_reactor.go index 84625017cf..15ebcc4d2a 100644 --- a/core/blockchain_reactor.go +++ b/core/blockchain_reactor.go @@ -392,12 +392,8 @@ func (bcr *BlockChainReactor) GetLastNumber(blockNumber uint64) uint64 { return plugin.StakingInstance().GetLastNumber(blockNumber) } -func (bcr *BlockChainReactor) GetValidators(blockNumber uint64) (*cbfttypes.Validators, error) { - return plugin.StakingInstance().GetValidators(blockNumber) -} - -func (bcr *BlockChainReactor) GetComingValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { - return plugin.StakingInstance().GetComingValidators(blockHash, blockNumber) +func (bcr *BlockChainReactor) GetValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { + return plugin.StakingInstance().GetValidators(blockHash, blockNumber) } func (bcr *BlockChainReactor) IsCandidateNode(nodeID enode.IDv0) bool { diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index 68cb557f0a..b5b3f11372 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -2693,24 +2693,16 @@ func (sk *StakingPlugin) GetLastNumber(blockNumber uint64) uint64 { return 0 } -func (sk *StakingPlugin) GetValidators(blockNumber uint64) (*cbfttypes.Validators, error) { - - valArr, err := sk.getCurrValList(common.ZeroHash, blockNumber, QueryStartIrr) - if snapshotdb.NonDbNotFoundErr(err) { - return nil, err - } - - if nil == err && nil != valArr { - return buildCbftValidators(valArr.Start, valArr.Arr), nil +func (sk *StakingPlugin) GetValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { + // validatorpool 在选举块更新nextValidators,触发条件是选举块commit完成,此时block已经实际上不可逆 + // 不从QueryStartIrr查询的原因是此时blockchain_reactor订阅的cbftResult还没处理完,snapshotdb还没有更新最高不可逆区块,查不到 + var isCommit bool + if blockHash == common.ZeroHash { + isCommit = true + } else { + isCommit = false } - return nil, fmt.Errorf("can not found validators by blockNumber: %d", blockNumber) -} - -// validatorpool 在选举块更新nextValidators,触发条件是选举块commit完成,此时block已经实际上不可逆 -// 不从QueryStartIrr查询的原因是此时blockchain_reactor订阅的cbftResult还没处理完,snapshotdb还没有更新最高不可逆区块,查不到 -func (sk *StakingPlugin) GetComingValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { - - valArr, err := sk.getCurrValList(blockHash, blockNumber, QueryStartNotIrr) + valArr, err := sk.getCurrValList(blockHash, blockNumber, isCommit) if snapshotdb.NonDbNotFoundErr(err) { return nil, err } From e4497e5d01e14f1d1ac64de22e0b04d4ed1331ee Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 20 Dec 2021 15:03:37 +0800 Subject: [PATCH 167/299] fix 0170 add consenousnode --- x/plugin/staking_plugin.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index 73391343d1..461dbc6a1b 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -188,8 +188,11 @@ func (sk *StakingPlugin) EndBlock(blockHash common.Hash, header *types.Header, s } func (sk *StakingPlugin) Confirmed(nodeId enode.IDv0, block *types.Block) error { - - if xutil.IsElection(block.NumberU64(), block.ActiveVersion()) { + acVersion := block.ActiveVersion() + if acVersion >= params.FORKVERSION_0_17_0 { + return nil + } + if xutil.IsElection(block.NumberU64(), acVersion) { next, err := sk.getNextValList(block.Hash(), block.NumberU64(), QueryStartNotIrr) if nil != err { From 8c3529eb3420eeb3687b46d443d98ba65fc4c13f Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 20 Dec 2021 15:23:27 +0800 Subject: [PATCH 168/299] fix ActiveVersion --- core/parallel_state_processor.go | 2 +- core/types/block.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 20a35e452e..c49798c54d 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -85,7 +85,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat } log.Debug("Process end blocker cost time", "blockNumber", block.Number(), "blockHash", block.Hash()) } - + block.SetActiveVersion(header.GetActiveVersion()) // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) //p.engine.Finalize(p.bc, header, statedb, block.Transactions(), receipts) statedb.IntermediateRoot(true) diff --git a/core/types/block.go b/core/types/block.go index 78f73add65..6982a03a68 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -428,7 +428,8 @@ func (b *Block) Extra() []byte { return common.CopyBytes(b.header.Ext func (b *Block) Header() *Header { return CopyHeader(b.header) } -func (b *Block) ActiveVersion() uint32 { return b.header.activeVersion } +func (b *Block) ActiveVersion() uint32 { return b.header.activeVersion } +func (b *Block) SetActiveVersion(version uint32) { b.header.activeVersion = version } // Body returns the non-header content of the block. func (b *Block) Body() *Body { return &Body{b.transactions, b.extraData} } From 1819deebc5638141d2baa25fa5c141fcba198b6f Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 20 Dec 2021 15:30:35 +0800 Subject: [PATCH 169/299] fix --- core/state_processor.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/state_processor.go b/core/state_processor.go index 1b3064fc3d..fac20c8561 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -102,6 +102,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg return nil, nil, 0, err } } + block.SetActiveVersion(header.GetActiveVersion()) // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) p.engine.Finalize(p.bc, header, statedb, block.Transactions(), receipts) From 364eff1d2118d2546138347ea64229dd6a4b5eef Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Mon, 20 Dec 2021 15:37:09 +0800 Subject: [PATCH 170/299] Fix the problem of not getting data --- consensus/cbft/network/pubsub.go | 9 +++++++-- p2p/pubsub/pubsub.go | 6 ++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index 0d580d8efa..853239bea7 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -233,12 +233,17 @@ func (ps *PubSub) listen(s *pubsub.Subscription) { func (ps *PubSub) Cancel(topic string) error { ps.Lock() defer ps.Unlock() - sb := ps.mySubs[topic] - if sb != nil { + sb, ok := ps.mySubs[topic] + if ok && sb != nil { sb.Cancel() delete(ps.mySubs, topic) + } + t, ok := ps.topics[topic] + if ok && t != nil { + t.Close() delete(ps.topics, topic) } + if cancel, ok := ps.topicCancel[topic]; ok { cancel() delete(ps.topicCtx, topic) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 445f16aa8a..79b6b74e37 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -1360,8 +1360,9 @@ type Status struct { } func (p *PubSub) GetAllPubSubStatus() *Status { - status := &Status{} + result := make(chan *Status, 1) getInfo := func() { + status := &Status{} gsr, ok := p.rt.(*GossipSubRouter) if !ok { return @@ -1398,11 +1399,12 @@ func (p *PubSub) GetAllPubSubStatus() *Status { } status.Topics = myTopics log.Debug("Get PubSub status information", "peers", gsr.peers, "mesh", gsr.mesh, "myTopics", p.myTopics) + result <- status } select { case p.eval <- getInfo: case <-p.ctx.Done(): } - return status + return <-result } From b4adc9105cd9d35394ae6157b522ecd12730349a Mon Sep 17 00:00:00 2001 From: benbaley Date: Mon, 20 Dec 2021 15:57:50 +0800 Subject: [PATCH 171/299] recover validatorpool old version logic --- consensus/cbft/validator/validator.go | 54 ++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 405def0a66..c2584bb174 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -503,14 +503,66 @@ func (vp *ValidatorPool) Update(blockHash common.Hash, blockNumber uint64, epoch currEpoch := vp.epoch vp.epoch = epoch vp.nextValidators = nil - log.Info("Update validators", "validators.len", vp.currentValidators.Len(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) + //切换共识轮时需要将上一轮分组的topic取消订阅 vp.dissolve(currEpoch, eventMux) + + //旧版本(非分组共识)需要发events断开落选的共识节点 + if !vp.grouped { + vp.dealWithOldVersionEvents(epoch, eventMux) + } + log.Info("Update validators", "validators.len", vp.currentValidators.Len(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) } log.Debug("Update OK", "blockNumber", blockNumber, "epoch", epoch, "isElection", isElection, "version", version) return nil } +// dealWithOldVersionEvents process version <= 0.16.0 logics +func (vp *ValidatorPool) dealWithOldVersionEvents(epoch uint64, eventMux *event.TypeMux) { + isValidatorBefore := vp.isValidator(epoch-1, vp.nodeID) + isValidatorAfter := vp.isValidator(epoch, vp.nodeID) + + if isValidatorBefore { + // If we are still a consensus node, that adding + // new validators as consensus peer, and removing + // validators. Added as consensus peersis because + // we need to keep connect with other validators + // in the consensus stages. Also we are not needed + // to keep connect with old validators. + if isValidatorAfter { + for _, n := range vp.currentValidators.SortedNodes { + if node, _ := vp.prevValidators.FindNodeByID(n.NodeID); node == nil { + eventMux.Post(cbfttypes.AddValidatorEvent{Node: enode.NewV4(node.PubKey, nil, 0, 0)}) + log.Trace("Post AddValidatorEvent", "node", node.String()) + } + } + + for _, n := range vp.prevValidators.SortedNodes { + if node, _ := vp.currentValidators.FindNodeByID(n.NodeID); node == nil { + eventMux.Post(cbfttypes.RemoveValidatorEvent{Node: enode.NewV4(node.PubKey, nil, 0, 0)}) + log.Trace("Post RemoveValidatorEvent", "node", node.String()) + } + } + } else { + for _, node := range vp.prevValidators.SortedNodes { + eventMux.Post(cbfttypes.RemoveValidatorEvent{Node: enode.NewV4(node.PubKey, nil, 0, 0)}) + log.Trace("Post RemoveValidatorEvent", "nodeID", node.String()) + } + } + } else { + // We are become a consensus node, that adding all + // validators as consensus peer except us. Added as + // consensus peers is because we need to keep connecting + // with other validators in the consensus stages. + if isValidatorAfter { + for _, node := range vp.currentValidators.SortedNodes { + eventMux.Post(cbfttypes.AddValidatorEvent{Node: enode.NewV4(node.PubKey, nil, 0, 0)}) + log.Trace("Post AddValidatorEvent", "nodeID", node.String()) + } + } + } +} + // GetValidatorByNodeID get the validator by node id. func (vp *ValidatorPool) GetValidatorByNodeID(epoch uint64, nodeID enode.ID) (*cbfttypes.ValidateNode, error) { vp.lock.RLock() From cf547d1d74e92f81afffb4be6614ce6f24183962 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Mon, 20 Dec 2021 16:07:31 +0800 Subject: [PATCH 172/299] Clean up redundant data --- p2p/pubsub/pubsub.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 79b6b74e37..4c8bf2af08 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -1013,6 +1013,9 @@ func (p *PubSub) handleIncomingRPC(rpc *RPC) { if _, ok := tmap[rpc.from.ID()]; ok { delete(tmap, rpc.from.ID()) + if len(tmap) == 0 { + delete(p.topics, t) + } p.notifyLeave(t, rpc.from.ID()) } } From 301eee55e75c0bd40c6480e8a846368f0ad91c7a Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Mon, 20 Dec 2021 16:19:58 +0800 Subject: [PATCH 173/299] Increase security check --- p2p/pubsub/pubsub.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 4c8bf2af08..49386bd70b 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -1388,9 +1388,12 @@ func (p *PubSub) GetAllPubSubStatus() *Status { nodeList := make([]enode.ID, 0) tmpMap := gsr.mesh[k] for n := range v { - if _, ok := tmpMap[n]; !ok { - nodeList = append(nodeList, n) + if tmpMap != nil { + if _, ok := tmpMap[n]; ok { + continue + } } + nodeList = append(nodeList, n) } outMesh[k] = nodeList } From 598cfb646ff5423d2c5d303d3fbf8be2fdc075ab Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 20 Dec 2021 18:00:33 +0800 Subject: [PATCH 174/299] fix test TestServerListen --- p2p/server.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/p2p/server.go b/p2p/server.go index 692696ddcd..1a154b974f 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -462,7 +462,9 @@ func (srv *Server) Stop() { // this unblocks listener Accept srv.listener.Close() } - srv.cancelPubSubServer() + if srv.cancelPubSubServer != nil { + srv.cancelPubSubServer() + } close(srv.quit) srv.lock.Unlock() srv.loopWG.Wait() From d455fc0ec1d0d2c91e02f614519f94e7231fc6ca Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 20 Dec 2021 18:29:23 +0800 Subject: [PATCH 175/299] fix test --- p2p/discover/v4wire/v4wire_test.go | 18 +++++++++--------- x/plugin/staking_plugin_test.go | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/p2p/discover/v4wire/v4wire_test.go b/p2p/discover/v4wire/v4wire_test.go index 8623d99e53..0c33a5183d 100644 --- a/p2p/discover/v4wire/v4wire_test.go +++ b/p2p/discover/v4wire/v4wire_test.go @@ -31,19 +31,19 @@ import ( // EIP-8 test vectors. var testPackets = []struct { input string - wantPacket interface{} + wantPacket Packet }{ - { - input: "71dbda3a79554728d4f94411e42ee1f8b0d561c10e1e5f5893367948c6a7d70bb87b235fa28a77070271b6c164a2dce8c7e13a5739b53b5e96f2e5acb0e458a02902f5965d55ecbeb2ebb6cabb8b2b232896a36b737666c55265ad0a68412f250001ea04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a355", + /* { + input: "96e7a55f265b738379447058e63d7bad33b05b1e3e93d9537f05be9975090bd72dbe25ab1d57c3de65e0b089f6b884c2273d29b343211483557f7c46f3df0b2650779f3459fabbf34261f113b4f29f6d5f540ea7d3fe5e35b24264517f8529110001ea04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a355", wantPacket: &Ping{ Version: 4, From: Endpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, To: Endpoint{net.ParseIP("::1"), 2222, 3333}, Expiration: 1136239445, }, - }, + },*/ { - input: "e9614ccfd9fc3e74360018522d30e1419a143407ffcce748de3e22116b7e8dc92ff74788c0b6663aaa3d67d641936511c8f8d6ad8698b820a7cf9e1be7155e9a241f556658c55428ec0563514365799a4be2be5a685a80971ddcfa80cb422cdd0101ec04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a3550102", + input: "61bf25845953d099c52ea2c6b09b723a18dde1f5ee60f7daeb9d16ac45e8d9a29cc8eb8b55cf6c13ef34dfda47bb673a0566a2d659e062ec9ab49c7ffcc584ca0485acdf7ffbf20e9ff4756004ec8f3998fe6e4e33343b431ad774dd1fc1fcba0001eb04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a35502", wantPacket: &Ping{ Version: 4, From: Endpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, @@ -54,7 +54,7 @@ var testPackets = []struct { }, }, { - input: "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260add7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396", + input: "0cc5db6098d5d5a5d6af93731aee4629ebbdd967899e42e073dfee48f36162d3fee71339958ee7859a936d61e6e4e43f74f5dc119fffcd6b424df1929f55197b159aaef76f9bac9fed4f35677e85b049a618cdb62d5cdb70a3b238439c79bce30103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999", wantPacket: &Findnode{ Target: hexPubkey("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"), Expiration: 1136239445, @@ -62,7 +62,7 @@ var testPackets = []struct { }, }, { - input: "c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db8403155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d313198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df738443b9a355010203b525a138aa34383fec3d2719a0", + input: "de3cd768c1b49fd8e7e4e0b50e28c371f4683181550d877a4ce6d32580ccd72c56b8409b9176e182b36ebc0715d6197a69b0eb806d6a7b7aa8615677891e15705c4cf3849f0ff477db229126dc4c0715e11f3ee9172659726dbb3eff8a64a1590004f9015bf90150f84d846321163782115c82115db8403155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d313198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df738443b9a355010203", wantPacket: &Neighbors{ Nodes: []Node{ { @@ -101,7 +101,7 @@ func TestForwardCompatibility(t *testing.T) { testkey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") wantNodeKey := EncodePubkey(&testkey.PublicKey) - for _, test := range testPackets { + for i, test := range testPackets { input, err := hex.DecodeString(test.input) if err != nil { t.Fatalf("invalid hex: %s", test.input) @@ -112,7 +112,7 @@ func TestForwardCompatibility(t *testing.T) { continue } 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,index:%d", spew.Sdump(packet), spew.Sdump(test.wantPacket), i) } if nodekey != wantNodeKey { t.Errorf("got id %v\nwant id %v", nodekey, wantNodeKey) diff --git a/x/plugin/staking_plugin_test.go b/x/plugin/staking_plugin_test.go index 3e106c8419..adca7e3872 100644 --- a/x/plugin/staking_plugin_test.go +++ b/x/plugin/staking_plugin_test.go @@ -3557,7 +3557,7 @@ func TestStakingPlugin_GetValidator(t *testing.T) { /** Start GetValidators */ - valArr, err := StakingInstance().GetValidators(header.Number.Uint64()) + valArr, err := StakingInstance().GetValidators(header.Hash(), header.Number.Uint64()) assert.Nil(t, err, fmt.Sprintf("Failed to GetValidators, err: %v", err)) assert.True(t, nil != valArr) From 4d0d23c06713dbd10e7c27ac529f085bac105f21 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Tue, 21 Dec 2021 11:46:53 +0800 Subject: [PATCH 176/299] fix bug --- consensus/cbft/cbft.go | 2 +- consensus/cbft/consensus_process.go | 45 ++++++++------- consensus/cbft/network/handler_test.go | 4 ++ consensus/cbft/network/helper_test.go | 4 ++ consensus/cbft/protocols/protocol.go | 4 +- consensus/cbft/rg_broadcast.go | 25 +++++---- consensus/cbft/sync_process.go | 76 +++++++++++++------------- consensus/cbft/types/crypto.go | 34 +++++++++++- 8 files changed, 119 insertions(+), 75 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 5a3290786d..6185f64231 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -1634,7 +1634,7 @@ func (cbft *Cbft) verifyConsensusMsg(msg ctypes.ConsensusMsg) (*cbfttypes.Valida switch cm := msg.(type) { case *protocols.PrepareBlock: proposer := cbft.currentProposer() - if uint32(proposer.Index) != msg.NodeIndex() { + if proposer.Index != msg.NodeIndex() { return nil, fmt.Errorf("current proposer index:%d, prepare block author index:%d", proposer.Index, msg.NodeIndex()) } // BlockNum equal 1, the parent's block is genesis, doesn't has prepareQC diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index c4c256541a..09e2c22150 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -139,7 +139,7 @@ func (cbft *Cbft) OnPrepareVote(id string, msg *protocols.PrepareVote) error { } cbft.state.AddPrepareVote(node.Index, msg) - cbft.mergeSelectedQuorumCerts(node, msg) + cbft.mergeVoteToQuorumCerts(node, msg) cbft.log.Debug("Receive new prepareVote", "msgHash", msg.MsgHash(), "vote", msg.String(), "votes", cbft.state.PrepareVoteLenByIndex(msg.BlockIndex)) cbft.trySendRGBlockQuorumCert() @@ -170,7 +170,7 @@ func (cbft *Cbft) OnViewChange(id string, msg *protocols.ViewChange) error { } cbft.state.AddViewChange(node.Index, msg) - cbft.mergeSelectedViewChangeQuorumCerts(node, msg) + cbft.mergeViewChangeToViewChangeQuorumCerts(node, msg) cbft.log.Debug("Receive new viewChange", "msgHash", msg.MsgHash(), "viewChange", msg.String(), "total", cbft.state.ViewChangeLen()) cbft.trySendRGViewChangeQuorumCert() @@ -226,12 +226,13 @@ func (cbft *Cbft) OnRGBlockQuorumCert(id string, msg *protocols.RGBlockQuorumCer } cbft.state.AddRGBlockQuorumCert(node.Index, msg) - cbft.richRGBlockQuorumCert(msg) - cbft.state.AddSelectRGQuorumCerts(msg.BlockIndx(), msg.GroupID, msg.BlockQC, msg.ParentQC) + blockQC, ParentQC := msg.BlockQC.DeepCopyQuorumCert(), msg.ParentQC + cbft.richBlockQuorumCert(msg.EpochNum(), msg.BlockIndx(), msg.GroupID, blockQC) + cbft.state.AddSelectRGQuorumCerts(msg.BlockIndx(), msg.GroupID, blockQC, ParentQC) cbft.log.Debug("Receive new RGBlockQuorumCert", "msgHash", msg.MsgHash(), "RGBlockQuorumCert", msg.String(), "total", cbft.state.RGBlockQuorumCertsLen(msg.BlockIndx(), msg.GroupID)) cbft.trySendRGBlockQuorumCert() - cbft.insertPrepareQC(msg.ParentQC) + cbft.insertPrepareQC(ParentQC) cbft.findQCBlock() return nil } @@ -348,8 +349,9 @@ func (cbft *Cbft) OnRGViewChangeQuorumCert(id string, msg *protocols.RGViewChang } cbft.state.AddRGViewChangeQuorumCert(node.Index, msg) - cbft.richRGViewChangeQuorumCert(msg) - cbft.state.AddSelectRGViewChangeQuorumCerts(msg.GroupID, msg.ViewChangeQC, msg.PrepareQCs.FlattenMap()) + viewChangeQC, prepareQCs := msg.ViewChangeQC.DeepCopyViewChangeQC(), msg.PrepareQCs.DeepCopyPrepareQCs() + cbft.richViewChangeQuorumCert(msg.EpochNum(), msg.GroupID, viewChangeQC, prepareQCs) + cbft.state.AddSelectRGViewChangeQuorumCerts(msg.GroupID, viewChangeQC, prepareQCs.FlattenMap()) cbft.log.Debug("Receive new RGViewChangeQuorumCert", "msgHash", msg.MsgHash(), "RGViewChangeQuorumCert", msg.String(), "total", cbft.state.RGViewChangeQuorumCertsLen(msg.GroupID)) cbft.trySendRGViewChangeQuorumCert() @@ -506,6 +508,7 @@ func (cbft *Cbft) insertPrepareQC(qc *ctypes.QuorumCert) { return false } if block != nil && hasExecuted() { + cbft.log.Trace("Insert prepareQC", "qc", qc.String()) cbft.insertQCBlock(block, qc) } } @@ -811,7 +814,7 @@ func (cbft *Cbft) findExecutableBlock() { } } -func (cbft *Cbft) mergeSelectedQuorumCerts(node *cbfttypes.ValidateNode, vote *protocols.PrepareVote) { +func (cbft *Cbft) mergeVoteToQuorumCerts(node *cbfttypes.ValidateNode, vote *protocols.PrepareVote) { // Query which group the PrepareVote belongs to according to the information of the node sending the PrepareVote message groupID, _, err := cbft.getGroupByValidatorID(vote.EpochNum(), node.NodeID) if err != nil { @@ -821,18 +824,18 @@ func (cbft *Cbft) mergeSelectedQuorumCerts(node *cbfttypes.ValidateNode, vote *p cbft.state.MergePrepareVotes(vote.BlockIndex, groupID, []*protocols.PrepareVote{vote}) } -func (cbft *Cbft) richRGBlockQuorumCert(rgb *protocols.RGBlockQuorumCert) { - mergeVotes := cbft.groupPrepareVotes(rgb.EpochNum(), rgb.BlockIndx(), rgb.GroupID) +func (cbft *Cbft) richBlockQuorumCert(epoch uint64, blockIndex, groupID uint32, blockQC *ctypes.QuorumCert) { + mergeVotes := cbft.groupPrepareVotes(epoch, blockIndex, groupID) if len(mergeVotes) > 0 { for _, v := range mergeVotes { - if !rgb.BlockQC.HasSign(v.NodeIndex()) { - rgb.BlockQC.AddSign(v.Signature, v.NodeIndex()) + if !blockQC.HasSign(v.NodeIndex()) { + blockQC.AddSign(v.Signature, v.NodeIndex()) } } } } -func (cbft *Cbft) mergeSelectedViewChangeQuorumCerts(node *cbfttypes.ValidateNode, vc *protocols.ViewChange) { +func (cbft *Cbft) mergeViewChangeToViewChangeQuorumCerts(node *cbfttypes.ValidateNode, vc *protocols.ViewChange) { // Query which group the ViewChange belongs to according to the information of the node sending the ViewChange message groupID, _, err := cbft.getGroupByValidatorID(vc.EpochNum(), node.NodeID) if err != nil { @@ -843,14 +846,14 @@ func (cbft *Cbft) mergeSelectedViewChangeQuorumCerts(node *cbfttypes.ValidateNod cbft.state.MergeViewChanges(groupID, []*protocols.ViewChange{vc}, validatorLen) } -func (cbft *Cbft) richRGViewChangeQuorumCert(rgb *protocols.RGViewChangeQuorumCert) { - mergeVcs := cbft.groupViewChanges(rgb.EpochNum(), rgb.GroupID) +func (cbft *Cbft) richViewChangeQuorumCert(epoch uint64, groupID uint32, viewChangeQC *ctypes.ViewChangeQC, prepareQCs *ctypes.PrepareQCs) { + mergeVcs := cbft.groupViewChanges(epoch, groupID) if len(mergeVcs) > 0 { for _, vc := range mergeVcs { - cbft.MergeViewChange(rgb.ViewChangeQC, vc) - if !rgb.ViewChangeQC.ExistViewChange(vc.Epoch, vc.ViewNumber, vc.BlockHash) { + cbft.MergeViewChange(viewChangeQC, vc) + if !viewChangeQC.ExistViewChange(vc.Epoch, vc.ViewNumber, vc.BlockHash) { if vc.PrepareQC != nil { - rgb.PrepareQCs.AppendQuorumCert(vc.PrepareQC) + prepareQCs.AppendQuorumCert(vc.PrepareQC) } } } @@ -915,7 +918,7 @@ func (cbft *Cbft) groupViewChanges(epoch uint64, groupID uint32) map[uint32]*pro } // Find viewChanges corresponding to indexes vcs := cbft.state.AllViewChange() - if vcs != nil && len(vcs) > 0 { + if len(vcs) > 0 { groupVcs := make(map[uint32]*protocols.ViewChange) for _, index := range indexes { if vc, ok := vcs[index]; ok { @@ -1102,7 +1105,7 @@ func (cbft *Cbft) tryChangeView() { if size >= threshold { viewChangeQC = cbft.generateViewChangeQC(cbft.state.AllViewChange()) viewWholeQCByVcsCounter.Inc(1) - cbft.log.Info("Receive enough viewchange, generate viewChangeQC", "viewChangeQC", viewChangeQC.String()) + cbft.log.Info("Receive enough viewChange, generate viewChangeQC", "viewChangeQC", viewChangeQC.String()) } return viewChangeQC.HasLength() >= threshold } @@ -1141,7 +1144,7 @@ func (cbft *Cbft) tryChangeView() { } } viewWholeQCByCombineCounter.Inc(1) - cbft.log.Debug("Enough RGViewChangeQuorumCerts and viewchange have been received, combine ViewChangeQC", "viewChangeQC", viewChangeQC.String()) + cbft.log.Debug("Enough RGViewChangeQuorumCerts and viewChange have been received, combine ViewChangeQC", "viewChangeQC", viewChangeQC.String()) } return viewChangeQC.HasLength() >= threshold } diff --git a/consensus/cbft/network/handler_test.go b/consensus/cbft/network/handler_test.go index 2e713dcfa4..417911a7ac 100644 --- a/consensus/cbft/network/handler_test.go +++ b/consensus/cbft/network/handler_test.go @@ -141,6 +141,10 @@ func (s *fakeCbft) BlockExists(blockNumber uint64, blockHash common.Hash) error return nil } +func (s *fakeCbft) GetGroupByValidatorID(nodeID enode.ID) (uint32, uint32, error) { + return 0, 0, nil +} + // Create a new EngineManager. func newHandle(t *testing.T) (*EngineManager, *fakeCbft) { // init local peer and engineManager. diff --git a/consensus/cbft/network/helper_test.go b/consensus/cbft/network/helper_test.go index ee4b8df66c..c8335dfe54 100644 --- a/consensus/cbft/network/helper_test.go +++ b/consensus/cbft/network/helper_test.go @@ -435,3 +435,7 @@ func (s *mockCbft) OnPong(nodeID string, netLatency int64) error { func (s *mockCbft) BlockExists(blockNumber uint64, blockHash common.Hash) error { return nil } + +func (s *mockCbft) GetGroupByValidatorID(nodeID enode.ID) (uint32, uint32, error) { + return 0, 0, nil +} diff --git a/consensus/cbft/protocols/protocol.go b/consensus/cbft/protocols/protocol.go index cd2dab935b..13354ce4e1 100644 --- a/consensus/cbft/protocols/protocol.go +++ b/consensus/cbft/protocols/protocol.go @@ -541,7 +541,7 @@ type GetPrepareVote struct { } func (s *GetPrepareVote) String() string { - return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,BlockIndex:%d,UnKnownSetLen:%d}", s.Epoch, s.ViewNumber, s.BlockIndex, s.UnKnownSet.UnKnownSize()) + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,BlockIndex:%d,UnKnownSetLen:%d,UnKnownSet:%s}", s.Epoch, s.ViewNumber, s.BlockIndex, s.UnKnownSet.UnKnownSize(), s.UnKnownSet.String()) } func (s *GetPrepareVote) MsgHash() common.Hash { @@ -753,7 +753,7 @@ type GetViewChange struct { } func (s *GetViewChange) String() string { - return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,UnKnownSetLen:%d}", s.Epoch, s.ViewNumber, s.UnKnownSet.UnKnownSize()) + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,UnKnownSetLen:%d,UnKnownSet:%s}", s.Epoch, s.ViewNumber, s.UnKnownSet.UnKnownSize(), s.UnKnownSet.String()) } func (s *GetViewChange) MsgHash() common.Hash { diff --git a/consensus/cbft/rg_broadcast.go b/consensus/cbft/rg_broadcast.go index 5aaecc5402..96f9d5e347 100644 --- a/consensus/cbft/rg_broadcast.go +++ b/consensus/cbft/rg_broadcast.go @@ -1,7 +1,6 @@ package cbft import ( - "github.com/AlayaNetwork/Alaya-Go/common/hexutil" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" "reflect" "sync" @@ -225,7 +224,7 @@ func (m *RGBroadcastManager) upgradeCoordinator(a awaiting) bool { if err != nil || groupID != a.GroupID() { return false } - if unitID == uint32(0) { // the first echelon,Send by default + if unitID == defaultUnitID { // the first echelon, Send by default return true } @@ -234,6 +233,7 @@ func (m *RGBroadcastManager) upgradeCoordinator(a awaiting) bool { m.cbft.log.Error("Get coordinator indexes by groupID error") return false } + m.cbft.log.Trace("CoordinatorIndexes", "groupID", groupID, "unitID", unitID, "coordinatorIndexes", coordinatorIndexes) var receiveIndexes []uint32 @@ -246,15 +246,15 @@ func (m *RGBroadcastManager) upgradeCoordinator(a awaiting) bool { return false } if !m.enoughCoordinator(groupID, unitID, coordinatorIndexes, receiveIndexes) { - m.cbft.log.Warn("Upgrade the current node to Coordinator", "type", reflect.TypeOf(a), "groupID", groupID, "index", a.Index(), "unitID", unitID, "coordinatorIndexes", coordinatorIndexes, "receiveIndexes", receiveIndexes) - m.recordMetrics(a) + m.cbft.log.Warn("Upgrade the current node to Coordinator", "type", reflect.TypeOf(a), "groupID", groupID, "unitID", unitID, "blockIndex", a.Index(), "coordinatorIndexes", coordinatorIndexes, "receiveIndexes", receiveIndexes) + m.recordUpgradeCoordinatorMetrics(a) return true } m.cbft.log.Debug("Enough Coordinator, no need to upgrade to Coordinator", "groupID", groupID, "unitID", unitID, "coordinatorIndexes", coordinatorIndexes, "receiveIndexes", receiveIndexes) return false } -func (m *RGBroadcastManager) recordMetrics(a awaiting) { +func (m *RGBroadcastManager) recordUpgradeCoordinatorMetrics(a awaiting) { switch a.(type) { case *awaitingRGBlockQC: upgradeCoordinatorBlockCounter.Inc(1) @@ -265,10 +265,17 @@ func (m *RGBroadcastManager) recordMetrics(a awaiting) { } func (m *RGBroadcastManager) enoughCoordinator(groupID, unitID uint32, coordinatorIndexes [][]uint32, receiveIndexes []uint32) bool { + if len(receiveIndexes) == 0 { + return false + } enough := func() int { // The total number of validators in the current group total := m.cbft.groupLen(m.cbft.state.Epoch(), groupID) - return total * efficientCoordinatorRatio / 100 + threshold := total * efficientCoordinatorRatio / 100 + if threshold <= 0 { + threshold = 1 + } + return threshold }() return m.countCoordinator(unitID, coordinatorIndexes, receiveIndexes) >= enough @@ -336,11 +343,7 @@ func (m *RGBroadcastManager) broadcastFunc(a awaiting) { return } m.cbft.network.Broadcast(rg) - // todo just for log - digest, _ := rg.CannibalizeBytes() - pubKey := m.cbft.config.Option.BlsPriKey.GetPublicKey() - blsPublicKey := hexutil.Encode(pubKey.Serialize()) - m.cbft.log.Debug("Success to broadcast RGBlockQuorumCert", "msg", rg.String(), "data", hexutil.Encode(digest), "sign", hexutil.Encode(rg.Sign()), "pubkey", blsPublicKey) + m.cbft.log.Debug("Success to broadcast RGBlockQuorumCert", "msg", rg.String()) m.hadSendRGBlockQuorumCerts[msg.Index()] = rg delete(m.awaitingRGBlockQuorumCerts, msg.Index()) m.cbft.state.AddRGBlockQuorumCert(node.Index, rg) diff --git a/consensus/cbft/sync_process.go b/consensus/cbft/sync_process.go index ed7a6d0eed..a038a98d4c 100644 --- a/consensus/cbft/sync_process.go +++ b/consensus/cbft/sync_process.go @@ -749,43 +749,6 @@ func (cbft *Cbft) OnViewChanges(id string, msg *protocols.ViewChanges) error { return nil } -// MissingViewChangeNodes returns the node ID of the missing vote. -// -// Notes: -// Use the channel to complete serial execution to prevent concurrency. -func (cbft *Cbft) MissingViewChangeNodes() (v *protocols.GetViewChange, err error) { - result := make(chan struct{}) - - cbft.asyncCallCh <- func() { - defer func() { result <- struct{}{} }() - - if !cbft.state.IsDeadline() { - v, err = nil, fmt.Errorf("no need sync viewchange") - return - } - - threshold := cbft.threshold(cbft.currentValidatorLen()) - size := len(cbft.KnownViewChangeIndexes()) - cbft.log.Debug("The length of viewChange", "size", size, "threshold", threshold) - - if size < threshold { - unKnownGroups := cbft.MissGroupViewChanges() - if len(unKnownGroups.UnKnown) > 0 { - v, err = &protocols.GetViewChange{ - Epoch: cbft.state.Epoch(), - ViewNumber: cbft.state.ViewNumber(), - UnKnownSet: unKnownGroups, - }, nil - } - } - if v == nil { - err = fmt.Errorf("not need sync prepare vote") - } - } - <-result - return -} - func (cbft *Cbft) KnownVoteIndexes(blockIndex uint32) []uint32 { groupNodes := cbft.getGroupIndexes(cbft.state.Epoch()) allVotes := cbft.state.AllPrepareVoteByIndex(blockIndex) @@ -933,7 +896,6 @@ func (cbft *Cbft) MissingPrepareVote() (v *protocols.GetPrepareVote, err error) size := len(cbft.KnownVoteIndexes(index)) // We need sync prepare votes when a long time not arrived QC. if size < threshold { // need sync prepare votes - cbft.log.Debug("MissingPrepareVote", "blockIndex", index, "size", size, "threshold", threshold) unKnownGroups := cbft.MissGroupVotes(index) if len(unKnownGroups.UnKnown) > 0 { v, err = &protocols.GetPrepareVote{ @@ -942,6 +904,7 @@ func (cbft *Cbft) MissingPrepareVote() (v *protocols.GetPrepareVote, err error) BlockIndex: index, UnKnownSet: unKnownGroups, }, nil + cbft.log.Debug("MissingPrepareVote", "blockIndex", index, "size", size, "threshold", threshold, "request", v.String()) break } } @@ -954,6 +917,43 @@ func (cbft *Cbft) MissingPrepareVote() (v *protocols.GetPrepareVote, err error) return } +// MissingViewChangeNodes returns the node ID of the missing vote. +// +// Notes: +// Use the channel to complete serial execution to prevent concurrency. +func (cbft *Cbft) MissingViewChangeNodes() (v *protocols.GetViewChange, err error) { + result := make(chan struct{}) + + cbft.asyncCallCh <- func() { + defer func() { result <- struct{}{} }() + + if !cbft.state.IsDeadline() { + v, err = nil, fmt.Errorf("no need sync viewchange") + return + } + + threshold := cbft.threshold(cbft.currentValidatorLen()) + size := len(cbft.KnownViewChangeIndexes()) + + if size < threshold { + unKnownGroups := cbft.MissGroupViewChanges() + if len(unKnownGroups.UnKnown) > 0 { + v, err = &protocols.GetViewChange{ + Epoch: cbft.state.Epoch(), + ViewNumber: cbft.state.ViewNumber(), + UnKnownSet: unKnownGroups, + }, nil + cbft.log.Debug("MissingViewChangeNodes", "size", size, "threshold", threshold, "request", v.String()) + } + } + if v == nil { + err = fmt.Errorf("not need sync prepare vote") + } + } + <-result + return +} + // LatestStatus returns latest status. func (cbft *Cbft) LatestStatus() (v *protocols.GetLatestStatus) { result := make(chan struct{}) diff --git a/consensus/cbft/types/crypto.go b/consensus/cbft/types/crypto.go index 1f93920adb..cffc1c4002 100644 --- a/consensus/cbft/types/crypto.go +++ b/consensus/cbft/types/crypto.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/common/hexutil" + "github.com/AlayaNetwork/Alaya-Go/common/json" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/utils" "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" @@ -428,6 +429,17 @@ func (v *ViewChangeQC) AppendQuorumCert(viewChangeQC *ViewChangeQuorumCert) { v.QCs = append(v.QCs, viewChangeQC) } +func (v *ViewChangeQC) DeepCopyViewChangeQC() *ViewChangeQC { + if v == nil || len(v.QCs) <= 0 { + return nil + } + cpy := &ViewChangeQC{QCs: make([]*ViewChangeQuorumCert, 0, len(v.QCs))} + for _, qc := range v.QCs { + cpy.AppendQuorumCert(qc.DeepCopyViewChangeQuorumCert()) + } + return cpy +} + type PrepareQCs struct { QCs []*QuorumCert `json:"qcs"` } @@ -459,13 +471,24 @@ func (p *PrepareQCs) AppendQuorumCert(qc *QuorumCert) { p.QCs = append(p.QCs, qc) } +func (p *PrepareQCs) DeepCopyPrepareQCs() *PrepareQCs { + if p == nil || len(p.QCs) <= 0 { + return nil + } + cpy := &PrepareQCs{QCs: make([]*QuorumCert, 0, len(p.QCs))} + for _, qc := range p.QCs { + cpy.AppendQuorumCert(qc.DeepCopyQuorumCert()) + } + return cpy +} + type UnKnownGroups struct { UnKnown []*UnKnownGroup `json:"unKnown"` } type UnKnownGroup struct { - GroupID uint32 `json:"groupID"` - UnKnownSet *utils.BitArray + GroupID uint32 `json:"groupID"` + UnKnownSet *utils.BitArray `json:"unKnownSet"` } func (unKnowns *UnKnownGroups) UnKnownSize() int { @@ -483,3 +506,10 @@ func (unKnowns *UnKnownGroups) UnKnownSize() int { } return unKnownSets.HasLength() } + +func (unKnowns *UnKnownGroups) String() string { + if b, err := json.Marshal(unKnowns); err == nil { + return string(b) + } + return "" +} From fae9c67571520ad3619efeda0bedc94712b344e2 Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 21 Dec 2021 18:37:14 +0800 Subject: [PATCH 177/299] rm redundant log --- consensus/cbft/validator/validator.go | 7 +++---- consensus/cbft/validator/validator_test.go | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index c2584bb174..8497b38090 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -427,10 +427,9 @@ func (vp *ValidatorPool) Update(blockHash common.Hash, blockNumber uint64, epoch vp.lock.Lock() defer vp.lock.Unlock() - log.Debug("Update", "blockNumber", blockNumber, "epoch", epoch, "isElection", isElection, "version", version) // 生效后第一个共识周期的Election block已经是新值(2130)所以第一次触发update是cbft.tryChangeView->shouldSwitch if blockNumber <= vp.switchPoint && !isElection { - log.Debug("Already update validator before", "blockNumber", blockNumber, "switchPoint", vp.switchPoint) + log.Trace("Already update validator before", "blockNumber", blockNumber, "switchPoint", vp.switchPoint) return errors.New("already updated before") } @@ -486,7 +485,7 @@ func (vp *ValidatorPool) Update(blockHash common.Hash, blockNumber uint64, epoch // 节点中间重启过, nextValidators没有赋值 if vp.nextValidators == nil { // 此时blockNumber==vp.lastNumber blockNumber+1即为下一轮 - nds, err = vp.agency.GetValidators(blockHash, NextRound(blockNumber)) + nds, err = vp.agency.GetValidators(blockHash, nextRoundBlockNumber) if err != nil { log.Error("Get validator error", "blockNumber", blockNumber, "err", err) return err @@ -499,7 +498,7 @@ func (vp *ValidatorPool) Update(blockHash common.Hash, blockNumber uint64, epoch vp.prevValidators = vp.currentValidators vp.currentValidators = vp.nextValidators vp.switchPoint = vp.currentValidators.ValidBlockNumber - 1 - vp.lastNumber = vp.agency.GetLastNumber(NextRound(blockNumber)) + vp.lastNumber = vp.agency.GetLastNumber(nextRoundBlockNumber) currEpoch := vp.epoch vp.epoch = epoch vp.nextValidators = nil diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index fd2f22bf7b..db4c9ab965 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -602,7 +602,7 @@ func (m *mockAgency) OnCommit(block *types.Block) error { return nil } func TestValidatorPoolReset(t *testing.T) { agency := newMockAgency(100) - vp := NewValidatorPool(agency, 0, 0, enode.ID{}, false, new(event.TypeMux)) + vp := NewValidatorPool(agency, 0, 0, enode.ID{}, true, new(event.TypeMux)) vp.Reset(100, 10, nil) assert.Equal(t, vp.switchPoint, uint64(100)) From a0de00c4f2da38c32e5b83679e5614020e110263 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Wed, 22 Dec 2021 11:30:27 +0800 Subject: [PATCH 178/299] fix bug --- consensus/cbft/consensus_process.go | 23 +++++++++++++++++++++-- consensus/cbft/sync_process.go | 8 ++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 8a6a2a329a..da803997a3 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -501,7 +501,7 @@ func (cbft *Cbft) insertPrepareQC(qc *ctypes.QuorumCert) { hasExecuted := func() bool { if cbft.validatorPool.IsValidator(qc.Epoch, cbft.config.Option.Node.ID()) { return cbft.state.HadSendPrepareVote().Had(qc.BlockIndex) && linked(qc.BlockNumber) - } else if cbft.validatorPool.IsCandidateNode(cbft.config.Option.NodeID) { + } else { blockIndex, finish := cbft.state.Executing() return blockIndex != math.MaxUint32 && (qc.BlockIndex < blockIndex || (qc.BlockIndex == blockIndex && finish)) && linked(qc.BlockNumber) } @@ -987,8 +987,27 @@ func (cbft *Cbft) findQCBlock() { return qc.Len() >= threshold } + linked := func(blockIndex uint32) bool { + block := cbft.state.ViewBlockByIndex(blockIndex) + if block != nil { + parent, _ := cbft.blockTree.FindBlockAndQC(block.ParentHash(), block.NumberU64()-1) + return parent != nil && cbft.state.HighestQCBlock().NumberU64()+1 == block.NumberU64() + } + return false + } + + hasExecuted := func(blockIndex uint32) bool { + if cbft.validatorPool.IsValidator(cbft.state.Epoch(), cbft.config.Option.Node.ID()) { + return cbft.state.HadSendPrepareVote().Had(blockIndex) + } else { + executingIndex, finish := cbft.state.Executing() + return blockIndex != math.MaxUint32 && (blockIndex < executingIndex || (executingIndex == blockIndex && finish)) && linked(blockIndex) + } + return false + } + alreadyQC := func() bool { - return cbft.state.HadSendPrepareVote().Had(next) && (enoughRGQuorumCerts() || enoughVotes() || enoughCombine()) + return hasExecuted(next) && (enoughRGQuorumCerts() || enoughVotes() || enoughCombine()) } if alreadyQC() { diff --git a/consensus/cbft/sync_process.go b/consensus/cbft/sync_process.go index a038a98d4c..a7d298d5f9 100644 --- a/consensus/cbft/sync_process.go +++ b/consensus/cbft/sync_process.go @@ -382,7 +382,7 @@ func (cbft *Cbft) OnGetPrepareVote(id string, msg *protocols.GetPrepareVote) err } if len(prepareVoteMap) > 0 { for i := uint32(0); i < un.UnKnownSet.Size(); i++ { - if rgqc != nil && rgqc.BlockQC.HasSign(i) { + if !un.UnKnownSet.GetIndex(i) || rgqc != nil && rgqc.BlockQC.HasSign(i) { continue } if v, ok := prepareVoteMap[i]; ok { @@ -613,7 +613,7 @@ func (cbft *Cbft) OnGetViewChange(id string, msg *protocols.GetViewChange) error } if len(viewChangeMap) > 0 { for i := uint32(0); i < un.UnKnownSet.Size(); i++ { - if rgqc != nil && rgqc.ViewChangeQC.HasSign(i) { + if !un.UnKnownSet.GetIndex(i) || rgqc != nil && rgqc.ViewChangeQC.HasSign(i) { continue } if v, ok := viewChangeMap[i]; ok { @@ -904,7 +904,7 @@ func (cbft *Cbft) MissingPrepareVote() (v *protocols.GetPrepareVote, err error) BlockIndex: index, UnKnownSet: unKnownGroups, }, nil - cbft.log.Debug("MissingPrepareVote", "blockIndex", index, "size", size, "threshold", threshold, "request", v.String()) + cbft.log.Debug("MissingPrepareVote", "blockIndex", index, "known", size, "threshold", threshold, "request", v.String()) break } } @@ -943,7 +943,7 @@ func (cbft *Cbft) MissingViewChangeNodes() (v *protocols.GetViewChange, err erro ViewNumber: cbft.state.ViewNumber(), UnKnownSet: unKnownGroups, }, nil - cbft.log.Debug("MissingViewChangeNodes", "size", size, "threshold", threshold, "request", v.String()) + cbft.log.Debug("MissingViewChangeNodes", "known", size, "threshold", threshold, "request", v.String()) } } if v == nil { From 546f7ca1bf66ffcd54dd782f9023ddf1ba3cea06 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Thu, 23 Dec 2021 10:24:19 +0800 Subject: [PATCH 179/299] fix dialing bug --- p2p/dial.go | 29 ++++++++++++----------------- p2p/server.go | 40 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/p2p/dial.go b/p2p/dial.go index e443c5e846..4e5491b726 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -272,23 +272,6 @@ func (d *dialScheduler) loop(it enode.Iterator) { loop: for { - select { - case task := <-d.consensusNodesIn: - if err := d.checkDial(task.dest); err != nil { - d.log.Trace("Discarding dial candidate", "id", task.dest.ID(), "ip", task.dest.IP(), "reason", err) - } else { - d.startDial(task) - } - continue - case node := <-d.checkReadyToDial: - if err := d.checkDial(node); err == nil { - d.checkReadyToDialDone <- true - } else { - d.checkReadyToDialDone <- false - } - continue - default: - } // Launch new dials if slots are available. slots := d.freeDialSlots() slots -= d.startStaticDials(slots) @@ -301,6 +284,18 @@ loop: d.logStats() select { + case task := <-d.consensusNodesIn: + if err := d.checkDial(task.dest); err != nil { + d.log.Trace("Discarding dial consensus node", "id", task.dest.ID(), "ip", task.dest.IP(), "reason", err) + } else { + d.startDial(task) + } + case node := <-d.checkReadyToDial: + if err := d.checkDial(node); err == nil { + d.checkReadyToDialDone <- true + } else { + d.checkReadyToDialDone <- false + } case node := <-nodesCh: if err := d.checkDial(node); err != nil { d.log.Trace("Discarding dial candidate", "id", node.ID(), "ip", node.IP(), "reason", err) diff --git a/p2p/server.go b/p2p/server.go index 1a154b974f..4e9121f282 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -220,10 +220,12 @@ type Server struct { // State of run loop and listenLoop. inboundHistory expHeap - eventMux *event.TypeMux - consensus bool - addconsensus chan *dialTask - removeconsensus chan *enode.Node + eventMux *event.TypeMux + consensus bool + addconsensus chan *dialTask + removeconsensus chan *enode.Node + addConsensusStatus chan []enode.ID + removeConsensusStatus chan map[enode.ID]struct{} pubSubServer *PubSubServer cancelPubSubServer context.CancelFunc @@ -536,6 +538,8 @@ func (srv *Server) Start() (err error) { srv.peerOpDone = make(chan struct{}) srv.addconsensus = make(chan *dialTask) srv.removeconsensus = make(chan *enode.Node) + srv.addConsensusStatus = make(chan []enode.ID) + srv.removeConsensusStatus = make(chan map[enode.ID]struct{}) srv.topicSubscriber = make(map[string][]enode.ID) srv.MinimumPeersInTopicSearch = 6 @@ -845,6 +849,25 @@ running: p.Disconnect(DiscRequested) } } + case nodes := <-srv.addConsensusStatus: + for _, id := range nodes { + if p, ok := peers[id]; ok { + if !p.rw.is(consensusDialedConn) { + p.rw.set(consensusDialedConn, true) + } + } + } + case nodes := <-srv.removeConsensusStatus: + for _, p := range peers { + if p.rw.is(consensusDialedConn) { + if _, ok := nodes[p.ID()]; !ok { + p.rw.set(consensusDialedConn, false) + if !p.rw.is(staticDialedConn | trustedConn | inboundConn) { + p.rw.set(dynDialedConn, true) + } + } + } + } case n := <-srv.addtrusted: // This channel is used by AddTrustedPeer to add a node // to the trusted node set. @@ -1313,10 +1336,19 @@ func (srv *Server) watching() { topicSubscriber = append(topicSubscriber, node) } srv.topicSubscriber[data.Topic] = topicSubscriber + srv.addConsensusStatus <- topicSubscriber srv.topicSubscriberMu.Unlock() case cbfttypes.ExpiredTopicEvent: srv.topicSubscriberMu.Lock() delete(srv.topicSubscriber, data.Topic) + nodes := make(map[enode.ID]struct{}) + // 为了防止有的节点是其他组的共识节点,所以要遍历全部 + for _, nodeIDs := range srv.topicSubscriber { + for _, nodeID := range nodeIDs { + nodes[nodeID] = struct{}{} + } + } + srv.removeConsensusStatus <- nodes srv.topicSubscriberMu.Unlock() default: log.Error("Received unexcepted event") From 51f2e3b2b4f16661a32bf719772c32b46fb5287b Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 23 Dec 2021 15:10:01 +0800 Subject: [PATCH 180/299] refactor validatorpool -> Update --- consensus/cbft/cbft.go | 2 +- consensus/cbft/consensus_process.go | 2 +- consensus/cbft/network/handler.go | 7 +- consensus/cbft/validator/validator.go | 127 ++++++++++----------- consensus/cbft/validator/validator_test.go | 6 +- consensus/cbft/wal_bridge.go | 2 +- 6 files changed, 71 insertions(+), 75 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index a0b2329653..c649a1a83f 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -1376,7 +1376,7 @@ func (cbft *Cbft) commitBlock(commitBlock *types.Block, commitQC *ctypes.QuorumC // post GroupsTopicEvent to join topic according group info if xutil.IsElection(cpy.NumberU64(), activeVersion) { if shouldGroup() { - cbft.validatorPool.Update(cpy.Hash(), cpy.NumberU64(), cbft.state.Epoch()+1, true, activeVersion, cbft.eventMux) + cbft.validatorPool.InitComingValidators(cpy.Hash(), cpy.NumberU64(), cbft.eventMux) } } } diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 8a6a2a329a..163241f1ab 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -1077,7 +1077,7 @@ func (cbft *Cbft) tryChangeView() { } if shouldSwitch { - if err := cbft.validatorPool.Update(block.Hash(), block.NumberU64(), cbft.state.Epoch()+1, false, activeVersion, cbft.eventMux); err == nil { + if err := cbft.validatorPool.Update(block.Hash(), block.NumberU64(), cbft.state.Epoch()+1, activeVersion, cbft.eventMux); err == nil { cbft.log.Info("Update validator success", "number", block.NumberU64()) } else { cbft.log.Error("Update validator failed!", "number", block.NumberU64(), "err", err) diff --git a/consensus/cbft/network/handler.go b/consensus/cbft/network/handler.go index 4ef4fb8917..a7cba3e671 100644 --- a/consensus/cbft/network/handler.go +++ b/consensus/cbft/network/handler.go @@ -18,16 +18,17 @@ package network import ( "fmt" - "github.com/AlayaNetwork/Alaya-Go/internal/debug" "math/big" "math/rand" "reflect" "strconv" "time" + "github.com/AlayaNetwork/Alaya-Go/internal/debug" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - "github.com/hashicorp/golang-lru" + lru "github.com/hashicorp/golang-lru" "github.com/AlayaNetwork/Alaya-Go/common" @@ -633,7 +634,7 @@ func (h *EngineManager) handleMsg(p *peer) error { case msg.Code == protocols.PongMsg: // Processed after receiving the pong message. curTime := time.Now().UnixNano() - log.Debug("Handle a eth Pong message", "curTime", curTime) + log.Debug("Handle a alaya Pong message", "curTime", curTime) var pongTime protocols.Pong if err := msg.Decode(&pongTime); err != nil { return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 8497b38090..dc570fff71 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -423,96 +423,91 @@ func (vp *ValidatorPool) MockSwitchPoint(number uint64) { } // Update switch validators. -func (vp *ValidatorPool) Update(blockHash common.Hash, blockNumber uint64, epoch uint64, isElection bool, version uint32, eventMux *event.TypeMux) error { +func (vp *ValidatorPool) Update(blockHash common.Hash, blockNumber uint64, epoch uint64, version uint32, eventMux *event.TypeMux) error { vp.lock.Lock() defer vp.lock.Unlock() + needGroup := version >= params.FORKVERSION_0_17_0 + //分组提案生效后第一个共识round到ElectionPoint时初始化分组信息 + if !vp.grouped && needGroup { + vp.grouped = true + vp.groupValidatorsLimit = xcom.MaxGroupValidators() + vp.coordinatorLimit = xcom.CoordinatorsLimit() + } // 生效后第一个共识周期的Election block已经是新值(2130)所以第一次触发update是cbft.tryChangeView->shouldSwitch - if blockNumber <= vp.switchPoint && !isElection { + if blockNumber <= vp.switchPoint { log.Trace("Already update validator before", "blockNumber", blockNumber, "switchPoint", vp.switchPoint) return errors.New("already updated before") } var err error var nds *cbfttypes.Validators - needGroup := version >= params.FORKVERSION_0_17_0 - var nextRoundBlockNumber uint64 - if isElection { - nextRoundBlockNumber = blockNumber + xcom.ElectionDistance() + 1 - } else { - nextRoundBlockNumber = blockNumber + 1 - } - - //生效后第一个共识周期的switchpoint是旧值,此时不能切换 - //判断依据是新validators和current完全相同且nextValidators为空 - if !isElection && vp.nextValidators == nil && needGroup { - nds, err = vp.agency.GetValidators(blockHash, nextRoundBlockNumber) + if vp.nextValidators == nil { + nds, err = vp.agency.GetValidators(blockHash, NextRound(blockNumber)) if err != nil { log.Error("Get validator error", "blockNumber", blockNumber, "err", err) return err } - if vp.currentValidators.Equal(nds) { - vp.currentValidators = nds - vp.switchPoint = nds.ValidBlockNumber - 1 - vp.lastNumber = vp.agency.GetLastNumber(blockNumber) - //不切换,所以epoch不增 - vp.grouped = false - log.Debug("update currentValidators success!", "lastNumber", vp.lastNumber, "grouped", vp.grouped, "switchPoint", vp.switchPoint) - return nil - } - } - //分组提案生效后第一个共识round到ElectionPoint时初始化分组信息 - if !vp.grouped { if needGroup { - vp.grouped = true - vp.groupValidatorsLimit = xcom.MaxGroupValidators() - vp.coordinatorLimit = xcom.CoordinatorsLimit() + //生效后第一个共识周期的switchpoint是旧值,此时不能切换 + //判断依据是新validators和current完全相同且nextValidators为空 + if vp.currentValidators.Equal(nds) { + vp.currentValidators = nds + vp.switchPoint = nds.ValidBlockNumber - 1 + vp.lastNumber = vp.agency.GetLastNumber(blockNumber) + //不切换,所以epoch不增 + vp.grouped = false + log.Debug("update currentValidators success!", "lastNumber", vp.lastNumber, "grouped", vp.grouped, "switchPoint", vp.switchPoint) + return nil + } } - } - if isElection { - // 提前更新nextValidators,为了p2p早一步订阅分组事件以便建链接 - nds, err = vp.agency.GetValidators(blockHash, nextRoundBlockNumber) - if err != nil { - log.Error("Get validators error", "blockNumber", blockNumber, "err", err) - return err - } + // 节点中间重启过, nextValidators没有赋值 vp.nextValidators = nds if vp.grouped { vp.organize(vp.nextValidators, epoch, eventMux) } - } else { - // 节点中间重启过, nextValidators没有赋值 - if vp.nextValidators == nil { - // 此时blockNumber==vp.lastNumber blockNumber+1即为下一轮 - nds, err = vp.agency.GetValidators(blockHash, nextRoundBlockNumber) - if err != nil { - log.Error("Get validator error", "blockNumber", blockNumber, "err", err) - return err - } - vp.nextValidators = nds - if vp.grouped { - vp.organize(vp.nextValidators, epoch, eventMux) - } - } - vp.prevValidators = vp.currentValidators - vp.currentValidators = vp.nextValidators - vp.switchPoint = vp.currentValidators.ValidBlockNumber - 1 - vp.lastNumber = vp.agency.GetLastNumber(nextRoundBlockNumber) - currEpoch := vp.epoch - vp.epoch = epoch - vp.nextValidators = nil + } - //切换共识轮时需要将上一轮分组的topic取消订阅 - vp.dissolve(currEpoch, eventMux) + vp.prevValidators = vp.currentValidators + vp.currentValidators = vp.nextValidators + vp.switchPoint = vp.currentValidators.ValidBlockNumber - 1 + vp.lastNumber = vp.agency.GetLastNumber(NextRound(blockNumber)) + currEpoch := vp.epoch + vp.epoch = epoch + vp.nextValidators = nil - //旧版本(非分组共识)需要发events断开落选的共识节点 - if !vp.grouped { - vp.dealWithOldVersionEvents(epoch, eventMux) - } - log.Info("Update validators", "validators.len", vp.currentValidators.Len(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) + //切换共识轮时需要将上一轮分组的topic取消订阅 + vp.dissolve(currEpoch, eventMux) + //旧版本(非分组共识)需要发events断开落选的共识节点 + if !vp.grouped { + vp.dealWithOldVersionEvents(epoch, eventMux) + } + log.Info("Update validators", "validators.len", vp.currentValidators.Len(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) + return nil +} + +// pre-init validator nodes for the next round.version >= 0.17.0 only +func (vp *ValidatorPool) InitComingValidators(blockHash common.Hash, blockNumber uint64, eventMux *event.TypeMux) error { + vp.lock.Lock() + defer vp.lock.Unlock() + + //分组提案生效后第一个共识round到ElectionPoint时初始化分组信息 + if !vp.grouped { + vp.grouped = true + vp.groupValidatorsLimit = xcom.MaxGroupValidators() + vp.coordinatorLimit = xcom.CoordinatorsLimit() + } + + // 提前更新nextValidators,为了p2p早一步订阅分组事件以便建链接 + nds, err := vp.agency.GetValidators(blockHash, blockNumber+xcom.ElectionDistance()+1) + if err != nil { + log.Error("Get validators error", "blockNumber", blockNumber, "err", err) + return err } - log.Debug("Update OK", "blockNumber", blockNumber, "epoch", epoch, "isElection", isElection, "version", version) + vp.nextValidators = nds + vp.organize(vp.nextValidators, vp.epoch+1, eventMux) + log.Debug("Update nextValidators OK", "blockNumber", blockNumber, "epoch", vp.epoch+1) return nil } diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index db4c9ab965..c61088c310 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -671,12 +671,12 @@ func TestUpdate(t *testing.T) { assert.Equal(t, lastNumber, vp.lastNumber) assert.Equal(t, vp.prevValidators, vp.currentValidators) - vp.Update(common.ZeroHash, 250, 0, false, 4352, eventMux) + vp.Update(common.ZeroHash, 250, 0, 4352, eventMux) assert.Nil(t, vp.nextValidators) assert.False(t, vp.NeedGroup()) assert.Equal(t, vp.epoch, uint64(0)) - vp.Update(common.ZeroHash, 980, 0, true, 4352, eventMux) + vp.InitComingValidators(common.ZeroHash, 980, eventMux) assert.True(t, vp.NeedGroup()) assert.Equal(t, vp.epoch, uint64(0)) @@ -690,7 +690,7 @@ func TestUpdate(t *testing.T) { assert.NotEqual(t, vp.currentValidators, next) assert.False(t, vp.nextValidators.Equal(next)) vp.nextValidators = next - vp.Update(common.ZeroHash, vp.lastNumber+1, 1, false, 4352, eventMux) + vp.Update(common.ZeroHash, vp.lastNumber+1, 1, 4352, eventMux) assert.True(t, vp.NeedGroup()) assert.Equal(t, vp.epoch, uint64(1)) assert.Nil(t, vp.nextValidators) diff --git a/consensus/cbft/wal_bridge.go b/consensus/cbft/wal_bridge.go index d4a531cb55..680b4c960d 100644 --- a/consensus/cbft/wal_bridge.go +++ b/consensus/cbft/wal_bridge.go @@ -364,7 +364,7 @@ func (cbft *Cbft) recoveryChainStateProcess(stateType uint16, s *protocols.State // trySwitch tries to switch next validator. func (cbft *Cbft) trySwitchValidator(blockHash common.Hash, blockNumber uint64, version uint32) { if cbft.validatorPool.ShouldSwitch(blockNumber) { - if err := cbft.validatorPool.Update(blockHash, blockNumber, cbft.state.Epoch()+1, false, version, cbft.eventMux); err != nil { + if err := cbft.validatorPool.Update(blockHash, blockNumber, cbft.state.Epoch()+1, version, cbft.eventMux); err != nil { cbft.log.Debug("Update validator error", "err", err.Error()) } } From 899ed3bdd6a17d8a6ef2240d0c444ebabdf8f66e Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Thu, 23 Dec 2021 15:52:09 +0800 Subject: [PATCH 181/299] fix discover dial --- p2p/server.go | 52 +++++++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/p2p/server.go b/p2p/server.go index 4e9121f282..1c2e5d8a85 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -224,14 +224,14 @@ type Server struct { consensus bool addconsensus chan *dialTask removeconsensus chan *enode.Node - addConsensusStatus chan []enode.ID - removeConsensusStatus chan map[enode.ID]struct{} + updateConsensusStatus chan map[enode.ID]struct{} pubSubServer *PubSubServer cancelPubSubServer context.CancelFunc topicSubscriberMu sync.RWMutex topicSubscriber map[string][]enode.ID + ConsensusPeers map[enode.ID]struct{} } type peerOpFunc func(map[enode.ID]*Peer) @@ -538,9 +538,9 @@ func (srv *Server) Start() (err error) { srv.peerOpDone = make(chan struct{}) srv.addconsensus = make(chan *dialTask) srv.removeconsensus = make(chan *enode.Node) - srv.addConsensusStatus = make(chan []enode.ID) - srv.removeConsensusStatus = make(chan map[enode.ID]struct{}) + srv.updateConsensusStatus = make(chan map[enode.ID]struct{}) srv.topicSubscriber = make(map[string][]enode.ID) + srv.ConsensusPeers = make(map[enode.ID]struct{}) srv.MinimumPeersInTopicSearch = 6 srv.MinimumPeersPerTopic = 10 @@ -849,22 +849,13 @@ running: p.Disconnect(DiscRequested) } } - case nodes := <-srv.addConsensusStatus: - for _, id := range nodes { - if p, ok := peers[id]; ok { - if !p.rw.is(consensusDialedConn) { - p.rw.set(consensusDialedConn, true) - } - } - } - case nodes := <-srv.removeConsensusStatus: + case nodes := <-srv.updateConsensusStatus: for _, p := range peers { - if p.rw.is(consensusDialedConn) { - if _, ok := nodes[p.ID()]; !ok { - p.rw.set(consensusDialedConn, false) - if !p.rw.is(staticDialedConn | trustedConn | inboundConn) { - p.rw.set(dynDialedConn, true) - } + if _, ok := nodes[p.ID()]; ok { + p.rw.set(consensusDialedConn, true) + } else { + if !p.rw.is(staticDialedConn | trustedConn | inboundConn) { + p.rw.set(dynDialedConn, true) } } } @@ -900,8 +891,13 @@ running: } if consensusNodes[c.node.ID()] { - c.flags |= consensusDialedConn + c.set(consensusDialedConn, true) } + srv.topicSubscriberMu.RLock() + if _, ok := srv.ConsensusPeers[c.node.ID()]; ok { + c.set(consensusDialedConn, true) + } + srv.topicSubscriberMu.RUnlock() // TODO: track in-progress inbound node IDs (pre-Peer) to avoid dialing them. c.cont <- srv.postHandshakeChecks(peers, inboundCount, c) @@ -1334,21 +1330,19 @@ func (srv *Server) watching() { topicSubscriber := make([]enode.ID, 0) for _, node := range data.Nodes { topicSubscriber = append(topicSubscriber, node) + srv.ConsensusPeers[node] = struct{}{} } srv.topicSubscriber[data.Topic] = topicSubscriber - srv.addConsensusStatus <- topicSubscriber + srv.updateConsensusStatus <- srv.ConsensusPeers srv.topicSubscriberMu.Unlock() case cbfttypes.ExpiredTopicEvent: srv.topicSubscriberMu.Lock() - delete(srv.topicSubscriber, data.Topic) - nodes := make(map[enode.ID]struct{}) - // 为了防止有的节点是其他组的共识节点,所以要遍历全部 - for _, nodeIDs := range srv.topicSubscriber { - for _, nodeID := range nodeIDs { - nodes[nodeID] = struct{}{} - } + nodesDel := srv.topicSubscriber[data.Topic] + for _, node := range nodesDel { + delete(srv.ConsensusPeers, node) } - srv.removeConsensusStatus <- nodes + delete(srv.topicSubscriber, data.Topic) + srv.updateConsensusStatus <- srv.ConsensusPeers srv.topicSubscriberMu.Unlock() default: log.Error("Received unexcepted event") From 732567c6baeff23ae1cedfe7e07783763e3c2729 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Thu, 23 Dec 2021 18:02:42 +0800 Subject: [PATCH 182/299] remove checkReadyToDial --- p2p/dial.go | 44 ++++++++++++-------------------------------- p2p/discover.go | 3 --- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/p2p/dial.go b/p2p/dial.go index 4e5491b726..47c20d61cb 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -107,9 +107,6 @@ type dialScheduler struct { addPeerCh chan *conn remPeerCh chan *conn - checkReadyToDial chan *enode.Node - checkReadyToDialDone chan bool - // Everything below here belongs to loop and // should only be accessed by code on the loop goroutine. dialing map[enode.ID]*dialTask // active tasks @@ -169,20 +166,18 @@ func (cfg dialConfig) withDefaults() dialConfig { func newDialScheduler(config dialConfig, it enode.Iterator, setupFunc dialSetupFunc) *dialScheduler { d := &dialScheduler{ - dialConfig: config.withDefaults(), - setupFunc: setupFunc, - dialing: make(map[enode.ID]*dialTask), - static: make(map[enode.ID]*dialTask), - peers: make(map[enode.ID]struct{}), - doneCh: make(chan *dialTask), - nodesIn: make(chan *enode.Node), - consensusNodesIn: make(chan *dialTask), - addStaticCh: make(chan *enode.Node), - remStaticCh: make(chan *enode.Node), - addPeerCh: make(chan *conn), - remPeerCh: make(chan *conn), - checkReadyToDial: make(chan *enode.Node), - checkReadyToDialDone: make(chan bool), + dialConfig: config.withDefaults(), + setupFunc: setupFunc, + dialing: make(map[enode.ID]*dialTask), + static: make(map[enode.ID]*dialTask), + peers: make(map[enode.ID]struct{}), + doneCh: make(chan *dialTask), + nodesIn: make(chan *enode.Node), + consensusNodesIn: make(chan *dialTask), + addStaticCh: make(chan *enode.Node), + remStaticCh: make(chan *enode.Node), + addPeerCh: make(chan *conn), + remPeerCh: make(chan *conn), } d.lastStatsLog = d.clock.Now() d.ctx, d.cancel = context.WithCancel(context.Background()) @@ -198,15 +193,6 @@ func (d *dialScheduler) stop() { d.wg.Wait() } -func (d *dialScheduler) IsReadyToDial(n *enode.Node) bool { - select { - case d.checkReadyToDial <- n: - return <-d.checkReadyToDialDone - case <-d.ctx.Done(): - } - return false -} - // addStatic adds a static dial candidate. func (d *dialScheduler) addStatic(n *enode.Node) { select { @@ -290,12 +276,6 @@ loop: } else { d.startDial(task) } - case node := <-d.checkReadyToDial: - if err := d.checkDial(node); err == nil { - d.checkReadyToDialDone <- true - } else { - d.checkReadyToDialDone <- false - } case node := <-nodesCh: if err := d.checkDial(node); err != nil { d.log.Trace("Discarding dial candidate", "id", node.ID(), "ip", node.IP(), "reason", err) diff --git a/p2p/discover.go b/p2p/discover.go index 2893789223..e1e963862b 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -102,9 +102,6 @@ func (srv *Server) filterPeerForTopic(nodes []enode.ID) func(node *enode.Node) b if !srv.filterPeer(node) { return false } - if !srv.dialsched.IsReadyToDial(node) { - return false - } for _, peer := range nodes { if peer == node.ID() { From 8029d5f4cb0277295802fa7ee460a9a47a361d1d Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Thu, 23 Dec 2021 18:21:40 +0800 Subject: [PATCH 183/299] fix self is consensus node --- p2p/server.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/p2p/server.go b/p2p/server.go index 1c2e5d8a85..9c0522978e 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -812,8 +812,8 @@ running: // This channel is used by AddConsensusNode to add an enode // to the consensus node set. srv.log.Trace("Adding consensus node", "node", task.dest) - - if id := task.dest.ID(); !bytes.Equal(crypto.Keccak256(srv.ourHandshake.ID), id[:]) { + id := task.dest.ID() + if bytes.Equal(crypto.Keccak256(srv.ourHandshake.ID), id[:]) { srv.log.Debug("We are become an consensus node") srv.consensus = true } else { @@ -830,7 +830,8 @@ running: // This channel is used by RemoveConsensusNode to remove an enode // from the consensus node set. srv.log.Trace("Removing consensus node", "node", n) - if id := n.ID(); !bytes.Equal(crypto.Keccak256(srv.ourHandshake.ID), id[:]) { + id := n.ID() + if bytes.Equal(crypto.Keccak256(srv.ourHandshake.ID), id[:]) { srv.log.Debug("We are not an consensus node") srv.consensus = false } From 2f24fac8a97a8d029770bcd09c131711c9efd6fb Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Thu, 23 Dec 2021 18:25:37 +0800 Subject: [PATCH 184/299] update --- p2p/server.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/p2p/server.go b/p2p/server.go index 9c0522978e..a0f6f13aee 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -817,9 +817,9 @@ running: srv.log.Debug("We are become an consensus node") srv.consensus = true } else { - consensusNodes[task.dest.ID()] = true - if p, ok := peers[task.dest.ID()]; ok { - srv.log.Debug("Add consensus flag", "peer", task.dest.ID()) + consensusNodes[id] = true + if p, ok := peers[id]; ok { + srv.log.Debug("Add consensus flag", "peer", id) p.rw.set(consensusDialedConn, true) } else { srv.dialsched.addConsensus(task) @@ -836,17 +836,17 @@ running: srv.consensus = false } //srv.dialsched.removeConsensus(n) - if _, ok := consensusNodes[n.ID()]; ok { - delete(consensusNodes, n.ID()) + if _, ok := consensusNodes[id]; ok { + delete(consensusNodes, id) } - if p, ok := peers[n.ID()]; ok { + if p, ok := peers[id]; ok { p.rw.set(consensusDialedConn, false) if !p.rw.is(staticDialedConn | trustedConn | inboundConn) { p.rw.set(dynDialedConn, true) } - srv.log.Debug("Remove consensus flag", "peer", n.ID(), "consensus", srv.consensus) + srv.log.Debug("Remove consensus flag", "peer", id, "consensus", srv.consensus) if len(peers) > srv.MaxPeers && !p.rw.is(staticDialedConn|trustedConn) { - srv.log.Debug("Disconnect non-consensus node", "peer", n.ID(), "flags", p.rw.flags, "peers", len(peers), "consensus", srv.consensus) + srv.log.Debug("Disconnect non-consensus node", "peer", id, "flags", p.rw.flags, "peers", len(peers), "consensus", srv.consensus) p.Disconnect(DiscRequested) } } From b82b78b53ff145f5c1b5f4fa0a96f86e09a69198 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 24 Dec 2021 14:35:50 +0800 Subject: [PATCH 185/299] fix p2p consensus status --- p2p/server.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/p2p/server.go b/p2p/server.go index a0f6f13aee..5ba218ddb1 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -1333,6 +1333,9 @@ func (srv *Server) watching() { topicSubscriber = append(topicSubscriber, node) srv.ConsensusPeers[node] = struct{}{} } + if _, ok := srv.ConsensusPeers[srv.localnode.ID()]; ok { + srv.consensus = true + } srv.topicSubscriber[data.Topic] = topicSubscriber srv.updateConsensusStatus <- srv.ConsensusPeers srv.topicSubscriberMu.Unlock() @@ -1343,6 +1346,9 @@ func (srv *Server) watching() { delete(srv.ConsensusPeers, node) } delete(srv.topicSubscriber, data.Topic) + if _, ok := srv.ConsensusPeers[srv.localnode.ID()]; !ok { + srv.consensus = false + } srv.updateConsensusStatus <- srv.ConsensusPeers srv.topicSubscriberMu.Unlock() default: From 5bec578608ceed392ecf4e8cfa2c2bbd818ad532 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 24 Dec 2021 14:52:04 +0800 Subject: [PATCH 186/299] fix p2p discover task not done --- p2p/dial.go | 3 +++ p2p/discover.go | 3 +++ p2p/server.go | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/p2p/dial.go b/p2p/dial.go index 47c20d61cb..52b9d0c8b7 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -273,6 +273,9 @@ loop: case task := <-d.consensusNodesIn: if err := d.checkDial(task.dest); err != nil { d.log.Trace("Discarding dial consensus node", "id", task.dest.ID(), "ip", task.dest.IP(), "reason", err) + if task.doneHook != nil { + task.doneHook() + } } else { d.startDial(task) } diff --git a/p2p/discover.go b/p2p/discover.go index e1e963862b..527f35fbc8 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -132,6 +132,9 @@ func (srv *Server) filterPeer(node *enode.Node) bool { if node.IP() == nil { return false } + if node.ID() == srv.localnode.ID() { + return false + } // do not dial nodes with their tcp ports not set /*if err := node.Record().Load(enr.WithEntry("tcp", new(enr.TCP))); err != nil { if !enr.IsNotFound(err) { diff --git a/p2p/server.go b/p2p/server.go index 5ba218ddb1..d9ad6eb10c 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -816,11 +816,17 @@ running: if bytes.Equal(crypto.Keccak256(srv.ourHandshake.ID), id[:]) { srv.log.Debug("We are become an consensus node") srv.consensus = true + if task.doneHook != nil { + task.doneHook() + } } else { consensusNodes[id] = true if p, ok := peers[id]; ok { srv.log.Debug("Add consensus flag", "peer", id) p.rw.set(consensusDialedConn, true) + if task.doneHook != nil { + task.doneHook() + } } else { srv.dialsched.addConsensus(task) } From bf67dc14df38060443ae8569a6c882d37c3250b4 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 27 Dec 2021 10:15:36 +0800 Subject: [PATCH 187/299] fix updateConsensusStatus map read --- p2p/server.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/p2p/server.go b/p2p/server.go index d9ad6eb10c..bf70fc59d2 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -1343,7 +1343,11 @@ func (srv *Server) watching() { srv.consensus = true } srv.topicSubscriber[data.Topic] = topicSubscriber - srv.updateConsensusStatus <- srv.ConsensusPeers + consensusPeers := make(map[enode.ID]struct{}) + for id, _ := range srv.ConsensusPeers { + consensusPeers[id] = struct{}{} + } + srv.updateConsensusStatus <- consensusPeers srv.topicSubscriberMu.Unlock() case cbfttypes.ExpiredTopicEvent: srv.topicSubscriberMu.Lock() @@ -1355,7 +1359,11 @@ func (srv *Server) watching() { if _, ok := srv.ConsensusPeers[srv.localnode.ID()]; !ok { srv.consensus = false } - srv.updateConsensusStatus <- srv.ConsensusPeers + consensusPeers := make(map[enode.ID]struct{}) + for id, _ := range srv.ConsensusPeers { + consensusPeers[id] = struct{}{} + } + srv.updateConsensusStatus <- consensusPeers srv.topicSubscriberMu.Unlock() default: log.Error("Received unexcepted event") From 85ef99ed69cb1d1e34dce8163786edd6e399751b Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Mon, 27 Dec 2021 16:28:31 +0800 Subject: [PATCH 188/299] Parameter support configuration --- cmd/alaya/main.go | 2 ++ cmd/alaya/usage.go | 2 ++ cmd/utils/flags.go | 18 ++++++++++++++++++ node/defaults.go | 10 ++++++---- p2p/server.go | 2 -- 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/cmd/alaya/main.go b/cmd/alaya/main.go index 8bc51482d2..6dede601fc 100644 --- a/cmd/alaya/main.go +++ b/cmd/alaya/main.go @@ -92,6 +92,8 @@ var ( utils.MaxPeersFlag, utils.MaxConsensusPeersFlag, utils.MaxPendingPeersFlag, + utils.MinimumPeersInTopicSearch, + utils.MinimumPeersPerTopic, utils.MinerGasTargetFlag, //utils.MinerGasLimitFlag, utils.MinerGasPriceFlag, diff --git a/cmd/alaya/usage.go b/cmd/alaya/usage.go index 3f815d4021..7d3b667b43 100644 --- a/cmd/alaya/usage.go +++ b/cmd/alaya/usage.go @@ -163,6 +163,8 @@ var AppHelpFlagGroups = []flagGroup{ utils.MaxPeersFlag, utils.MaxConsensusPeersFlag, utils.MaxPendingPeersFlag, + utils.MinimumPeersInTopicSearch, + utils.MinimumPeersPerTopic, utils.NATFlag, utils.NoDiscoverFlag, // utils.DiscoveryV5Flag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e1bc5c8042..b32c2d08b6 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -382,6 +382,16 @@ var ( Usage: "Maximum number of pending connection attempts (defaults used if set to 0)", Value: 0, } + MinimumPeersInTopicSearch = cli.IntFlag{ + Name: "minpeerstopicsearch", + Usage: "Minimum number of nodes per search for the same topic (network disabled if set to 0)", + Value: 6, + } + MinimumPeersPerTopic = cli.IntFlag{ + Name: "minpeerstopic", + Usage: "Minimum number of nodes to maintain the same topic", + Value: 10, + } ListenPortFlag = cli.IntFlag{ Name: "port", Usage: "Network listening port", @@ -876,6 +886,14 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { cfg.MaxPeers = lightPeers } } + + if ctx.GlobalIsSet(MinimumPeersInTopicSearch.Name) { + cfg.MinimumPeersInTopicSearch = ctx.GlobalUint(MinimumPeersInTopicSearch.Name) + } + if ctx.GlobalIsSet(MinimumPeersPerTopic.Name) { + cfg.MinimumPeersPerTopic = ctx.GlobalInt(MinimumPeersPerTopic.Name) + } + if !(lightClient || lightServer) { lightPeers = 0 } diff --git a/node/defaults.go b/node/defaults.go index e7ca451d3b..6ca4f89baf 100644 --- a/node/defaults.go +++ b/node/defaults.go @@ -44,10 +44,12 @@ var DefaultConfig = Config{ WSPort: DefaultWSPort, WSModules: []string{"net", "web3"}, P2P: p2p.Config{ - ListenAddr: ":16789", - MaxPeers: 60, - NAT: nat.Any(), - MaxConsensusPeers: 40, + ListenAddr: ":16789", + MaxPeers: 60, + NAT: nat.Any(), + MaxConsensusPeers: 40, + MinimumPeersInTopicSearch: 6, + MinimumPeersPerTopic: 10, }, } diff --git a/p2p/server.go b/p2p/server.go index bf70fc59d2..856bfa29a6 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -542,8 +542,6 @@ func (srv *Server) Start() (err error) { srv.topicSubscriber = make(map[string][]enode.ID) srv.ConsensusPeers = make(map[enode.ID]struct{}) - srv.MinimumPeersInTopicSearch = 6 - srv.MinimumPeersPerTopic = 10 if int(xcom.MaxGroupValidators()) <= srv.MinimumPeersPerTopic { srv.MinimumPeersPerTopic = int(xcom.MaxGroupValidators()) } From 68b2ab9fae5531483102a568dd2fbf7d92191814 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Mon, 27 Dec 2021 17:56:31 +0800 Subject: [PATCH 189/299] tagTracer adds checks --- p2p/pubsub/tag_tracer.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/p2p/pubsub/tag_tracer.go b/p2p/pubsub/tag_tracer.go index 075ee37aff..8d101c6117 100644 --- a/p2p/pubsub/tag_tracer.go +++ b/p2p/pubsub/tag_tracer.go @@ -144,6 +144,9 @@ func (t *tagTracer) removeDeliveryTag(topic string) { } func (t *tagTracer) bumpDeliveryTag(p enode.ID, topic string) error { + if t.decayer == nil { + return nil + } t.RLock() defer t.RUnlock() From 301f2dc51ce4074eedcf928e68b18a554ce4de9b Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 28 Dec 2021 17:34:11 +0800 Subject: [PATCH 190/299] fix ExpiredTopicEvent --- p2p/server.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/p2p/server.go b/p2p/server.go index 856bfa29a6..54f676a71d 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -961,8 +961,10 @@ running: func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount int, c *conn) error { // Disconnect over limit non-consensus node. + // log.Trace("postHandshakeChecks server status", "consensus", srv.consensus, "peers", len(peers), "MaxPeers", srv.MaxPeers, "consensusDialedConn", c.is(consensusDialedConn), "MaxConsensusPeers", srv.MaxConsensusPeers, "numConsensusPeer", srv.numConsensusPeer(peers)) if srv.consensus && len(peers) >= srv.MaxPeers && c.is(consensusDialedConn) && srv.numConsensusPeer(peers) < srv.MaxConsensusPeers { for _, p := range peers { + // log.Trace("postHandshakeChecks peer status", "peer", p.ID(), "status", p.rw.String()) if p.rw.is(inboundConn|dynDialedConn) && !p.rw.is(trustedConn|staticDialedConn|consensusDialedConn) { log.Debug("Disconnect over limit connection", "peer", p.ID(), "flags", p.rw.flags, "peers", len(peers)) p.Disconnect(DiscRequested) @@ -1346,22 +1348,24 @@ func (srv *Server) watching() { consensusPeers[id] = struct{}{} } srv.updateConsensusStatus <- consensusPeers + log.Trace("Received NewTopicEvent", "consensusPeers", consensusPeers) srv.topicSubscriberMu.Unlock() case cbfttypes.ExpiredTopicEvent: srv.topicSubscriberMu.Lock() - nodesDel := srv.topicSubscriber[data.Topic] - for _, node := range nodesDel { - delete(srv.ConsensusPeers, node) - } delete(srv.topicSubscriber, data.Topic) + consensusPeers := make(map[enode.ID]struct{}) + srv.ConsensusPeers = make(map[enode.ID]struct{}) + for _, peers := range srv.topicSubscriber { + for _, peer := range peers { + srv.ConsensusPeers[peer] = struct{}{} + consensusPeers[peer] = struct{}{} + } + } if _, ok := srv.ConsensusPeers[srv.localnode.ID()]; !ok { srv.consensus = false } - consensusPeers := make(map[enode.ID]struct{}) - for id, _ := range srv.ConsensusPeers { - consensusPeers[id] = struct{}{} - } srv.updateConsensusStatus <- consensusPeers + log.Trace("Received ExpiredTopicEvent", "consensusPeers", consensusPeers) srv.topicSubscriberMu.Unlock() default: log.Error("Received unexcepted event") From 89aa7de2264613c1fa0db0c21088dcbdbaac8365 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 28 Dec 2021 18:08:18 +0800 Subject: [PATCH 191/299] fix --- p2p/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/p2p/server.go b/p2p/server.go index 54f676a71d..38fb3dd0de 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -859,6 +859,7 @@ running: if _, ok := nodes[p.ID()]; ok { p.rw.set(consensusDialedConn, true) } else { + p.rw.set(consensusDialedConn, false) if !p.rw.is(staticDialedConn | trustedConn | inboundConn) { p.rw.set(dynDialedConn, true) } From 84f77987113a45192b34206f1749e0883dbc27d5 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 29 Dec 2021 09:52:06 +0800 Subject: [PATCH 192/299] change log level --- consensus/cbft/consensus_process.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 92cedc63ea..a50ddd5b25 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -1099,7 +1099,7 @@ func (cbft *Cbft) tryChangeView() { if err := cbft.validatorPool.Update(block.Hash(), block.NumberU64(), cbft.state.Epoch()+1, activeVersion, cbft.eventMux); err == nil { cbft.log.Info("Update validator success", "number", block.NumberU64()) } else { - cbft.log.Error("Update validator failed!", "number", block.NumberU64(), "err", err) + cbft.log.Trace("Update validator failed!", "number", block.NumberU64(), "err", err) } } From 6da960f88b28b516f5b6bf9fd148774375e65550 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Wed, 29 Dec 2021 11:00:17 +0800 Subject: [PATCH 193/299] Clear empty data --- p2p/pubsub/pubsub.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 49386bd70b..6448b504fb 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -610,6 +610,9 @@ func (p *PubSub) processLoop(ctx context.Context) { for t, tmap := range p.topics { if _, ok := tmap[pid.ID()]; ok { delete(tmap, pid.ID()) + if len(tmap) == 0 { + delete(p.topics, t) + } p.notifyLeave(t, pid.ID()) } } @@ -688,6 +691,9 @@ func (p *PubSub) handleDeadPeers() { for t, tmap := range p.topics { if _, ok := tmap[pid]; ok { delete(tmap, pid) + if len(tmap) == 0 { + delete(p.topics, t) + } p.notifyLeave(t, pid) } } From 285a6bd795d971f8eea3f300c65e40d86ced9675 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Wed, 29 Dec 2021 15:14:24 +0800 Subject: [PATCH 194/299] add wal for rgmsg --- consensus/cbft/consensus_process.go | 21 ++++---- consensus/cbft/protocols/wal_protocol.go | 69 ++++++++++++++++++++++-- consensus/cbft/rg_broadcast.go | 12 ++++- consensus/cbft/wal/wal_common_test.go | 15 ++++++ consensus/cbft/wal/wal_decoder.go | 28 ++++++++-- consensus/cbft/wal/wal_test.go | 35 ++++++++++-- consensus/cbft/wal_bridge.go | 49 ++++++++++++++--- p2p/discover.go | 2 +- 8 files changed, 200 insertions(+), 31 deletions(-) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index a50ddd5b25..3b89f0bd95 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -391,17 +391,17 @@ func (cbft *Cbft) OnViewTimeout() { return } + // write sendViewChange info to wal + if !cbft.isLoading() { + cbft.bridge.SendViewChange(viewChange) + } + // send viewChange use pubsub if err := cbft.publishTopicMsg(viewChange); err != nil { cbft.log.Error("Publish viewChange failed", "err", err.Error(), "view", cbft.state.ViewString(), "viewChange", viewChange.String()) return } - // write sendViewChange info to wal - if !cbft.isLoading() { - cbft.bridge.SendViewChange(viewChange) - } - cbft.state.AddViewChange(uint32(node.Index), viewChange) //cbft.network.Broadcast(viewChange) cbft.log.Info("Local add viewChange", "index", node.Index, "viewChange", viewChange.String(), "total", cbft.state.ViewChangeLen()) @@ -610,6 +610,12 @@ func (cbft *Cbft) trySendPrepareVote() { } if b, qc := cbft.blockTree.FindBlockAndQC(block.ParentHash(), block.NumberU64()-1); b != nil || block.NumberU64() == 0 { p.ParentQC = qc + + // write sendPrepareVote info to wal + if !cbft.isLoading() { + cbft.bridge.SendPrepareVote(block, p) + } + // send prepareVote use pubsub if err := cbft.publishTopicMsg(p); err != nil { cbft.log.Error("Publish PrepareVote failed", "err", err.Error(), "view", cbft.state.ViewString(), "vote", p.String()) @@ -623,11 +629,6 @@ func (cbft *Cbft) trySendPrepareVote() { pending.Pop() //cbft.network.Broadcast(p) - - // write sendPrepareVote info to wal - if !cbft.isLoading() { - cbft.bridge.SendPrepareVote(block, p) - } } else { break } diff --git a/consensus/cbft/protocols/wal_protocol.go b/consensus/cbft/protocols/wal_protocol.go index c069932a7d..b39f0ca8ec 100644 --- a/consensus/cbft/protocols/wal_protocol.go +++ b/consensus/cbft/protocols/wal_protocol.go @@ -26,10 +26,12 @@ import ( ) const ( - ConfirmedViewChangeMsg = 0x01 - SendViewChangeMsg = 0x02 - SendPrepareBlockMsg = 0x03 - SendPrepareVoteMsg = 0x04 + ConfirmedViewChangeMsg = 0x01 + SendViewChangeMsg = 0x02 + SendPrepareBlockMsg = 0x03 + SendPrepareVoteMsg = 0x04 + SendRGBlockQuorumCertMsg = 0x05 + SendRGViewChangeQuorumCertMsg = 0x06 ) const ( @@ -180,12 +182,67 @@ func (s *SendPrepareVote) String() string { s.Vote.Epoch, s.Vote.ViewNumber, s.Vote.BlockIndex, s.Vote.BlockNumber, s.Vote.BlockHash.String()) } +type SendRGBlockQuorumCert struct { + RGEpoch uint64 + RGViewNumber uint64 + RGBlockIndex uint32 +} + +func (s SendRGBlockQuorumCert) Epoch() uint64 { + return s.RGEpoch +} + +func (s SendRGBlockQuorumCert) ViewNumber() uint64 { + return s.RGViewNumber +} + +func (s SendRGBlockQuorumCert) BlockNumber() uint64 { + return 0 +} + +func (s SendRGBlockQuorumCert) BlockIndex() uint32 { + return s.RGBlockIndex +} + +func (s *SendRGBlockQuorumCert) String() string { + if s == nil { + return "" + } + return fmt.Sprintf("[epoch:%d, viewNumber:%d, blockIndex:%d]", s.RGEpoch, s.RGViewNumber, s.RGBlockIndex) +} + +type SendRGViewChangeQuorumCert struct { + RGEpoch uint64 + RGViewNumber uint64 +} + +func (s SendRGViewChangeQuorumCert) Epoch() uint64 { + return s.RGEpoch +} + +func (s SendRGViewChangeQuorumCert) ViewNumber() uint64 { + return s.RGViewNumber +} + +func (s SendRGViewChangeQuorumCert) BlockNumber() uint64 { + return 0 +} + +func (s *SendRGViewChangeQuorumCert) String() string { + if s == nil { + return "" + } + return fmt.Sprintf("[epoch:%d, viewNumber:%d]", s.RGEpoch, s.RGViewNumber) +} + var ( WalMessages = []interface{}{ ConfirmedViewChange{}, SendViewChange{}, SendPrepareBlock{}, SendPrepareVote{}, + SendRGBlockQuorumCert{}, + SendRGViewChangeQuorumCert{}, } ) @@ -199,6 +256,10 @@ func WalMessageType(msg interface{}) uint64 { return SendPrepareBlockMsg case *SendPrepareVote: return SendPrepareVoteMsg + case *SendRGBlockQuorumCert: + return SendRGBlockQuorumCertMsg + case *SendRGViewChangeQuorumCert: + return SendRGViewChangeQuorumCertMsg } panic(fmt.Sprintf("invalid wal msg type %v", reflect.TypeOf(msg))) } diff --git a/consensus/cbft/rg_broadcast.go b/consensus/cbft/rg_broadcast.go index 96f9d5e347..4e5772c00e 100644 --- a/consensus/cbft/rg_broadcast.go +++ b/consensus/cbft/rg_broadcast.go @@ -205,7 +205,7 @@ func (m *RGBroadcastManager) broadcast(a awaiting) { } } -func (m *RGBroadcastManager) allowRGBlockQuorumCert(a awaiting) bool { +func (m *RGBroadcastManager) allowRGQuorumCert(a awaiting) bool { switch a.(type) { case *awaitingRGBlockQC: if m.cbft.state.IsDeadline() { @@ -303,7 +303,7 @@ func (m *RGBroadcastManager) countCoordinator(unitID uint32, coordinatorIndexes func (m *RGBroadcastManager) broadcastFunc(a awaiting) { m.cbft.log.Debug("Broadcast rg msg", "type", reflect.TypeOf(a), "groupID", a.GroupID(), "index", a.Index()) - if !m.equalsState(a) || !m.allowRGBlockQuorumCert(a) { + if !m.equalsState(a) || !m.allowRGQuorumCert(a) { return } @@ -342,6 +342,10 @@ func (m *RGBroadcastManager) broadcastFunc(a awaiting) { m.cbft.log.Error("Sign RGBlockQuorumCert failed", "err", err, "rgmsg", rg.String()) return } + // write SendRGBlockQuorumCert info to wal + if !m.cbft.isLoading() { + m.cbft.bridge.SendRGBlockQuorumCert(a.Epoch(), a.ViewNumber(), uint32(a.Index())) + } m.cbft.network.Broadcast(rg) m.cbft.log.Debug("Success to broadcast RGBlockQuorumCert", "msg", rg.String()) m.hadSendRGBlockQuorumCerts[msg.Index()] = rg @@ -363,6 +367,10 @@ func (m *RGBroadcastManager) broadcastFunc(a awaiting) { m.cbft.log.Error("Sign RGViewChangeQuorumCert failed", "err", err, "rgmsg", rg.String()) return } + // write SendRGViewChangeQuorumCert info to wal + if !m.cbft.isLoading() { + m.cbft.bridge.SendRGViewChangeQuorumCert(a.Epoch(), a.ViewNumber()) + } m.cbft.network.Broadcast(rg) m.cbft.log.Debug("Success to broadcast RGViewChangeQuorumCert", "msg", rg.String()) m.hadSendRGViewChangeQuorumCerts[msg.Index()] = rg diff --git a/consensus/cbft/wal/wal_common_test.go b/consensus/cbft/wal/wal_common_test.go index bfc2356e5a..5e37ccb532 100644 --- a/consensus/cbft/wal/wal_common_test.go +++ b/consensus/cbft/wal/wal_common_test.go @@ -139,6 +139,21 @@ func buildSendViewChange() *protocols.SendViewChange { } } +func buildSendRGBlockQuorumCert() *protocols.SendRGBlockQuorumCert { + return &protocols.SendRGBlockQuorumCert{ + RGEpoch: epoch, + RGViewNumber: viewNumber, + RGBlockIndex: 6, + } +} + +func buildSendRGViewChangeQuorumCert() *protocols.SendRGViewChangeQuorumCert { + return &protocols.SendRGViewChangeQuorumCert{ + RGEpoch: epoch, + RGViewNumber: viewNumber, + } +} + func buildConfirmedViewChange() *protocols.ConfirmedViewChange { return &protocols.ConfirmedViewChange{ Epoch: epoch, diff --git a/consensus/cbft/wal/wal_decoder.go b/consensus/cbft/wal/wal_decoder.go index 51d1d9d810..e7ebabc728 100644 --- a/consensus/cbft/wal/wal_decoder.go +++ b/consensus/cbft/wal/wal_decoder.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package wal import ( @@ -49,13 +48,24 @@ type MessageConfirmedViewChange struct { Data *protocols.ConfirmedViewChange } +// struct SendRGBlockQuorumCert for rlp decode +type MessageSendRGBlockQuorumCert struct { + Timestamp uint64 + Data *protocols.SendRGBlockQuorumCert +} + +// struct SendRGViewChangeQuorumCert for rlp decode +type MessageSendRGViewChangeQuorumCert struct { + Timestamp uint64 + Data *protocols.SendRGViewChangeQuorumCert +} + func WALDecode(pack []byte, msgType uint16) (interface{}, error) { switch msgType { case protocols.ConfirmedViewChangeMsg: var j MessageConfirmedViewChange if err := rlp.DecodeBytes(pack, &j); err != nil { return nil, err - } return j.Data, nil @@ -63,7 +73,6 @@ func WALDecode(pack []byte, msgType uint16) (interface{}, error) { var j MessageSendViewChange if err := rlp.DecodeBytes(pack, &j); err != nil { return nil, err - } return j.Data, nil @@ -78,7 +87,18 @@ func WALDecode(pack []byte, msgType uint16) (interface{}, error) { var j MessageSendPrepareVote if err := rlp.DecodeBytes(pack, &j); err != nil { return nil, err - + } + return j.Data, nil + case protocols.SendRGBlockQuorumCertMsg: + var j MessageSendRGBlockQuorumCert + if err := rlp.DecodeBytes(pack, &j); err != nil { + return nil, err + } + return j.Data, nil + case protocols.SendRGViewChangeQuorumCertMsg: + var j MessageSendRGViewChangeQuorumCert + if err := rlp.DecodeBytes(pack, &j); err != nil { + return nil, err } return j.Data, nil } diff --git a/consensus/cbft/wal/wal_test.go b/consensus/cbft/wal/wal_test.go index f0eecaca0c..a0f0b9f652 100644 --- a/consensus/cbft/wal/wal_test.go +++ b/consensus/cbft/wal/wal_test.go @@ -173,8 +173,11 @@ func testWalWrite(wal Wal) (int, error) { } else if ordinal == 2 { err = wal.WriteSync(buildSendPrepareBlock()) } else if ordinal == 3 { - //err = getWal().WriteSync(buildSendPrepareVote()) err = wal.Write(buildSendPrepareVote()) + } else if ordinal == 4 { + err = wal.Write(buildSendRGBlockQuorumCert()) + } else if ordinal == 5 { + err = wal.Write(buildSendRGViewChangeQuorumCert()) } if err != nil { return 0, err @@ -200,6 +203,10 @@ func testWalLoad(wal Wal) (int, error) { count++ case *protocols.SendPrepareVote: count++ + case *protocols.SendRGBlockQuorumCert: + count++ + case *protocols.SendRGViewChangeQuorumCert: + count++ } return nil }) @@ -228,7 +235,7 @@ func TestEmptyWal(t *testing.T) { func TestWalDecoder(t *testing.T) { timestamp := uint64(time.Now().UnixNano()) - // SendPrepareBlockMsg + // MessageSendPrepareBlock prepare := &MessageSendPrepareBlock{ Timestamp: timestamp, Data: buildSendPrepareBlock(), @@ -248,7 +255,7 @@ func TestWalDecoder(t *testing.T) { assert.Nil(t, err) _, err = WALDecode(data, protocols.SendViewChangeMsg) assert.NotNil(t, err) - // MessageSendPrepareVote + // MessageSendViewChange view := &MessageSendViewChange{ Timestamp: timestamp, Data: buildSendViewChange(), @@ -258,7 +265,7 @@ func TestWalDecoder(t *testing.T) { assert.Nil(t, err) _, err = WALDecode(data, protocols.ConfirmedViewChangeMsg) assert.NotNil(t, err) - // MessageSendPrepareVote + // MessageConfirmedViewChange confirm := &MessageConfirmedViewChange{ Timestamp: timestamp, Data: buildConfirmedViewChange(), @@ -268,6 +275,26 @@ func TestWalDecoder(t *testing.T) { assert.Nil(t, err) _, err = WALDecode(data, protocols.SendPrepareBlockMsg) assert.NotNil(t, err) + // MessageSendRGBlockQuorumCert + rgb := &MessageSendRGBlockQuorumCert{ + Timestamp: timestamp, + Data: buildSendRGBlockQuorumCert(), + } + data, _ = rlp.EncodeToBytes(rgb) + rgbd, err := WALDecode(data, protocols.SendRGBlockQuorumCertMsg) + assert.Nil(t, err) + assert.IsType(t, &protocols.SendRGBlockQuorumCert{}, rgbd) + assert.Equal(t, epoch, rgbd.(*protocols.SendRGBlockQuorumCert).Epoch()) + // MessageSendRGBlockQuorumCert + rgv := &MessageSendRGViewChangeQuorumCert{ + Timestamp: timestamp, + Data: buildSendRGViewChangeQuorumCert(), + } + data, _ = rlp.EncodeToBytes(rgv) + rgvd, err := WALDecode(data, protocols.SendRGViewChangeQuorumCertMsg) + assert.Nil(t, err) + assert.IsType(t, &protocols.SendRGViewChangeQuorumCert{}, rgvd) + assert.Equal(t, viewNumber, rgvd.(*protocols.SendRGViewChangeQuorumCert).ViewNumber()) } func TestWalProtocalMsg(t *testing.T) { diff --git a/consensus/cbft/wal_bridge.go b/consensus/cbft/wal_bridge.go index 680b4c960d..5a11487a31 100644 --- a/consensus/cbft/wal_bridge.go +++ b/consensus/cbft/wal_bridge.go @@ -55,6 +55,8 @@ type Bridge interface { SendViewChange(view *protocols.ViewChange) SendPrepareBlock(pb *protocols.PrepareBlock) SendPrepareVote(block *types.Block, vote *protocols.PrepareVote) + SendRGBlockQuorumCert(epoch uint64, viewNumber uint64, blockIndex uint32) + SendRGViewChangeQuorumCert(epoch uint64, viewNumber uint64) GetViewChangeQC(epoch uint64, viewNumber uint64) (*ctypes.ViewChangeQC, error) Close() @@ -210,31 +212,28 @@ func (b *baseBridge) ConfirmViewChange(epoch, viewNumber uint64, block *types.Bl // SendViewChange tries to update SendViewChange consensus msg to wal. func (b *baseBridge) SendViewChange(view *protocols.ViewChange) { - tStart := time.Now() s := &protocols.SendViewChange{ ViewChange: view, } if err := b.cbft.wal.WriteSync(s); err != nil { panic(fmt.Sprintf("write send viewChange error, err:%s", err.Error())) } - log.Debug("Success to send viewChange", "view", view.String(), "elapsed", time.Since(tStart)) + log.Debug("Success to send viewChange", "view", view.String()) } // SendPrepareBlock tries to update SendPrepareBlock consensus msg to wal. func (b *baseBridge) SendPrepareBlock(pb *protocols.PrepareBlock) { - tStart := time.Now() s := &protocols.SendPrepareBlock{ Prepare: pb, } if err := b.cbft.wal.WriteSync(s); err != nil { panic(fmt.Sprintf("write send prepareBlock error, err:%s", err.Error())) } - log.Debug("Success to send prepareBlock", "prepareBlock", pb.String(), "elapsed", time.Since(tStart)) + log.Debug("Success to send prepareBlock", "prepareBlock", pb.String()) } // SendPrepareVote tries to update SendPrepareVote consensus msg to wal. func (b *baseBridge) SendPrepareVote(block *types.Block, vote *protocols.PrepareVote) { - tStart := time.Now() s := &protocols.SendPrepareVote{ Block: block, Vote: vote, @@ -242,7 +241,32 @@ func (b *baseBridge) SendPrepareVote(block *types.Block, vote *protocols.Prepare if err := b.cbft.wal.WriteSync(s); err != nil { panic(fmt.Sprintf("write send prepareVote error, err:%s", err.Error())) } - log.Debug("Success to send prepareVote", "prepareVote", vote.String(), "elapsed", time.Since(tStart)) + log.Debug("Success to send prepareVote", "prepareVote", vote.String()) +} + +// SendRGBlockQuorumCert tries to update SendRGBlockQuorumCert consensus msg to wal. +func (b *baseBridge) SendRGBlockQuorumCert(epoch uint64, viewNumber uint64, blockIndex uint32) { + s := &protocols.SendRGBlockQuorumCert{ + RGEpoch: epoch, + RGViewNumber: viewNumber, + RGBlockIndex: blockIndex, + } + if err := b.cbft.wal.WriteSync(s); err != nil { + panic(fmt.Sprintf("write send RGBlockQuorumCert error, err:%s", err.Error())) + } + log.Debug("Success to send RGBlockQuorumCert", "sendRGBlockQuorumCert", s.String()) +} + +// SendRGViewChangeQuorumCert tries to update SendRGViewChangeQuorumCert consensus msg to wal. +func (b *baseBridge) SendRGViewChangeQuorumCert(epoch uint64, viewNumber uint64) { + s := &protocols.SendRGViewChangeQuorumCert{ + RGEpoch: epoch, + RGViewNumber: viewNumber, + } + if err := b.cbft.wal.WriteSync(s); err != nil { + panic(fmt.Sprintf("write send SendRGViewChangeQuorumCert error, err:%s", err.Error())) + } + log.Debug("Success to send SendRGViewChangeQuorumCert", "sendRGViewChangeQuorumCert", s.String()) } func (b *baseBridge) GetViewChangeQC(epoch uint64, viewNumber uint64) (*ctypes.ViewChangeQC, error) { @@ -448,6 +472,19 @@ func (cbft *Cbft) recoveryMsg(msg interface{}) error { node, _ := cbft.validatorPool.GetValidatorByNodeID(m.Vote.Epoch, cbft.config.Option.Node.ID()) cbft.state.AddPrepareVote(uint32(node.Index), m.Vote) } + + case *protocols.SendRGBlockQuorumCert: + cbft.log.Debug("Load journal message from wal", "msgType", reflect.TypeOf(msg), "sendRGBlockQuorumCert", m.String()) + if cbft.equalViewState(m) { + cbft.state.AddSendRGBlockQuorumCerts(m.BlockIndex()) + } + + case *protocols.SendRGViewChangeQuorumCert: + cbft.log.Debug("Load journal message from wal", "msgType", reflect.TypeOf(msg), "SendRGViewChangeQuorumCert", m.String()) + if cbft.equalViewState(m) { + cbft.state.AddSendRGViewChangeQuorumCerts(m.ViewNumber()) + } + } return nil } diff --git a/p2p/discover.go b/p2p/discover.go index 527f35fbc8..0815444c9e 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -36,7 +36,7 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { } log.Debug("No peers found subscribed gossip topic . Searching network for peers subscribed to the topic.", "topic", topic) if err := srv.FindPeersWithTopic(ctx, topic, nodes, srv.Config.MinimumPeersPerTopic); err != nil { - log.Error("Could not search for peers", "err", err) + log.Debug("Could not search for peers", "err", err) return } } From b326e04bd5317b0046b8d59df757fc6454a7409d Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Wed, 29 Dec 2021 15:58:47 +0800 Subject: [PATCH 195/299] add wal for rgmsg --- consensus/cbft/wal_bridge.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/consensus/cbft/wal_bridge.go b/consensus/cbft/wal_bridge.go index 5a11487a31..80180285c9 100644 --- a/consensus/cbft/wal_bridge.go +++ b/consensus/cbft/wal_bridge.go @@ -81,6 +81,12 @@ func (b *emptyBridge) SendPrepareBlock(pb *protocols.PrepareBlock) { func (b *emptyBridge) SendPrepareVote(block *types.Block, vote *protocols.PrepareVote) { } +func (b *emptyBridge) SendRGBlockQuorumCert(epoch uint64, viewNumber uint64, blockIndex uint32) { +} + +func (b *emptyBridge) SendRGViewChangeQuorumCert(epoch uint64, viewNumber uint64) { +} + func (b *emptyBridge) GetViewChangeQC(epoch uint64, viewNumber uint64) (*ctypes.ViewChangeQC, error) { return nil, nil } From 03ef49ba1ea84f239b0a9f3f806d3b06335ef525 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Thu, 30 Dec 2021 17:24:55 +0800 Subject: [PATCH 196/299] Fix that too many consensus nodes under other topics cause new consensus nodes to fail to connect --- p2p/server.go | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/p2p/server.go b/p2p/server.go index 38fb3dd0de..51039784da 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -231,7 +231,7 @@ type Server struct { topicSubscriberMu sync.RWMutex topicSubscriber map[string][]enode.ID - ConsensusPeers map[enode.ID]struct{} + ConsensusPeers map[enode.ID]string } type peerOpFunc func(map[enode.ID]*Peer) @@ -540,7 +540,7 @@ func (srv *Server) Start() (err error) { srv.removeconsensus = make(chan *enode.Node) srv.updateConsensusStatus = make(chan map[enode.ID]struct{}) srv.topicSubscriber = make(map[string][]enode.ID) - srv.ConsensusPeers = make(map[enode.ID]struct{}) + srv.ConsensusPeers = make(map[enode.ID]string) if int(xcom.MaxGroupValidators()) <= srv.MinimumPeersPerTopic { srv.MinimumPeersPerTopic = int(xcom.MaxGroupValidators()) @@ -973,6 +973,33 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in } } } + if srv.consensus && c.is(consensusDialedConn) && srv.numConsensusPeer(peers) >= srv.MaxConsensusPeers { + srv.topicSubscriberMu.RLock() + if connTopic, ok := srv.ConsensusPeers[c.node.ID()]; ok { + if len(srv.pubSubServer.PubSub().ListPeers(connTopic)) < srv.MinimumPeersPerTopic { + reducedTopicPeerLength := 0 + reducedTopic := "" + for topic, _ := range srv.topicSubscriber { + if topic != connTopic { + num := len(srv.pubSubServer.PubSub().ListPeers(topic)) + if num > reducedTopicPeerLength { + reducedTopicPeerLength = num + reducedTopic = topic + } + } + } + + for id, p := range peers { + if srv.ConsensusPeers[id] == reducedTopic && !p.rw.is(trustedConn|staticDialedConn) { + log.Debug("Disconnect over limit consensus connection", "peer", p.ID(), "flags", p.rw.flags, "peers", len(peers), "topic", reducedTopic) + p.Disconnect(DiscRequested) + break + } + } + } + } + srv.topicSubscriberMu.RUnlock() + } switch { case c.is(consensusDialedConn) && srv.numConsensusPeer(peers) >= srv.MaxConsensusPeers: @@ -1338,7 +1365,7 @@ func (srv *Server) watching() { topicSubscriber := make([]enode.ID, 0) for _, node := range data.Nodes { topicSubscriber = append(topicSubscriber, node) - srv.ConsensusPeers[node] = struct{}{} + srv.ConsensusPeers[node] = data.Topic } if _, ok := srv.ConsensusPeers[srv.localnode.ID()]; ok { srv.consensus = true @@ -1355,10 +1382,10 @@ func (srv *Server) watching() { srv.topicSubscriberMu.Lock() delete(srv.topicSubscriber, data.Topic) consensusPeers := make(map[enode.ID]struct{}) - srv.ConsensusPeers = make(map[enode.ID]struct{}) - for _, peers := range srv.topicSubscriber { + srv.ConsensusPeers = make(map[enode.ID]string) + for topic, peers := range srv.topicSubscriber { for _, peer := range peers { - srv.ConsensusPeers[peer] = struct{}{} + srv.ConsensusPeers[peer] = topic consensusPeers[peer] = struct{}{} } } From 32cd4ba443d769e338a4a4d1d8fb79dbb5f8daec Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 4 Jan 2022 11:07:42 +0800 Subject: [PATCH 197/299] fix remove consensus peer --- p2p/server.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/p2p/server.go b/p2p/server.go index 51039784da..4c44aefabe 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -988,12 +988,13 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in } } } - - for id, p := range peers { - if srv.ConsensusPeers[id] == reducedTopic && !p.rw.is(trustedConn|staticDialedConn) { - log.Debug("Disconnect over limit consensus connection", "peer", p.ID(), "flags", p.rw.flags, "peers", len(peers), "topic", reducedTopic) - p.Disconnect(DiscRequested) - break + if reducedTopic != "" { + for id, p := range peers { + if srv.ConsensusPeers[id] == reducedTopic && !p.rw.is(trustedConn|staticDialedConn) { + log.Debug("Disconnect over limit consensus connection", "peer", p.ID(), "flags", p.rw.flags, "peers", len(peers), "topic", reducedTopic) + p.Disconnect(DiscRequested) + break + } } } } From 4b074cb9b90df75e806032baa4e3d1dafc1e003d Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Tue, 4 Jan 2022 15:35:19 +0800 Subject: [PATCH 198/299] Compatible with previous versions of group consensus --- consensus/cbft/cbft.go | 17 ++ consensus/cbft/consensus_process.go | 48 ++++- consensus/cbft/network/handler.go | 67 +++++- consensus/cbft/network/interface.go | 7 +- consensus/cbft/network/router.go | 5 +- consensus/cbft/protocols/protocol.go | 146 +++++++++++-- consensus/cbft/sync_msg_test.go | 32 --- consensus/cbft/sync_process.go | 297 ++++++++++++++++++++++++--- consensus/cbft/types/crypto.go | 3 + consensus/cbft/verify_qc_test.go | 3 +- consensus/cbft/wal_bridge.go | 6 +- 11 files changed, 523 insertions(+), 108 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index c649a1a83f..e59c9c68c4 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -329,6 +329,11 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCache consensus.BlockC return nil } +// NeedGroup indicates whether grouped consensus will be used +func (cbft *Cbft) NeedGroup() bool { + return cbft.validatorPool.NeedGroup() +} + // ReceiveMessage Entrance: The messages related to the consensus are entered from here. // The message sent from the peer node is sent to the CBFT message queue and // there is a loop that will distribute the incoming message. @@ -631,9 +636,15 @@ func (cbft *Cbft) handleSyncMsg(info *ctypes.MsgInfo) error { case *protocols.GetPrepareVote: err = cbft.OnGetPrepareVote(id, msg) + case *protocols.GetPrepareVoteV2: + err = cbft.OnGetPrepareVoteV2(id, msg) + case *protocols.PrepareVotes: err = cbft.OnPrepareVotes(id, msg) + case *protocols.PrepareVotesV2: + err = cbft.OnPrepareVotesV2(id, msg) + case *protocols.GetQCBlockList: err = cbft.OnGetQCBlockList(id, msg) @@ -649,11 +660,17 @@ func (cbft *Cbft) handleSyncMsg(info *ctypes.MsgInfo) error { case *protocols.GetViewChange: err = cbft.OnGetViewChange(id, msg) + case *protocols.GetViewChangeV2: + err = cbft.OnGetViewChangeV2(id, msg) + case *protocols.ViewChangeQuorumCert: err = cbft.OnViewChangeQuorumCert(id, msg) case *protocols.ViewChanges: err = cbft.OnViewChanges(id, msg) + + case *protocols.ViewChangesV2: + err = cbft.OnViewChangesV2(id, msg) } } return err diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 3b89f0bd95..a5f8a24f37 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -402,7 +402,7 @@ func (cbft *Cbft) OnViewTimeout() { return } - cbft.state.AddViewChange(uint32(node.Index), viewChange) + cbft.state.AddViewChange(node.Index, viewChange) //cbft.network.Broadcast(viewChange) cbft.log.Info("Local add viewChange", "index", node.Index, "viewChange", viewChange.String(), "total", cbft.state.ViewChangeLen()) @@ -625,7 +625,7 @@ func (cbft *Cbft) trySendPrepareVote() { //Determine if the current consensus node is node, _ := cbft.isCurrentValidator() cbft.log.Info("Add local prepareVote", "vote", p.String()) - cbft.state.AddPrepareVote(uint32(node.Index), p) + cbft.state.AddPrepareVote(node.Index, p) pending.Pop() //cbft.network.Broadcast(p) @@ -636,16 +636,24 @@ func (cbft *Cbft) trySendPrepareVote() { } func (cbft *Cbft) publishTopicMsg(msg ctypes.ConsensusMsg) error { - groupID, _, err := cbft.getGroupByValidatorID(cbft.state.Epoch(), cbft.Node().ID()) - if err != nil { - return fmt.Errorf("the group info of the current node is not queried, cannot publish the topic message") + if cbft.NeedGroup() { + groupID, _, err := cbft.getGroupByValidatorID(cbft.state.Epoch(), cbft.Node().ID()) + if err != nil { + return fmt.Errorf("the group info of the current node is not queried, cannot publish the topic message") + } + network.MeteredWriteRGMsg(protocols.MessageType(msg), msg) + topic := cbfttypes.ConsensusGroupTopicName(cbft.state.Epoch(), groupID) + return cbft.pubSub.Publish(topic, protocols.MessageType(msg), msg) + } else { + cbft.network.Broadcast(msg) + return nil } - network.MeteredWriteRGMsg(protocols.MessageType(msg), msg) - topic := cbfttypes.ConsensusGroupTopicName(cbft.state.Epoch(), groupID) - return cbft.pubSub.Publish(topic, protocols.MessageType(msg), msg) } func (cbft *Cbft) trySendRGBlockQuorumCert() { + if !cbft.NeedGroup() { + return + } // Check timeout if cbft.state.IsDeadline() { cbft.log.Debug("Current view had timeout, refuse to send RGBlockQuorumCert") @@ -716,6 +724,10 @@ func (cbft *Cbft) trySendRGBlockQuorumCert() { } func (cbft *Cbft) trySendRGViewChangeQuorumCert() { + if !cbft.NeedGroup() { + return + } + groupID, _, err := cbft.getGroupByValidatorID(cbft.state.Epoch(), cbft.Node().ID()) if err != nil { cbft.log.Debug("Current node is not validator, no need to send RGViewChangeQuorumCert") @@ -816,6 +828,9 @@ func (cbft *Cbft) findExecutableBlock() { } func (cbft *Cbft) mergeVoteToQuorumCerts(node *cbfttypes.ValidateNode, vote *protocols.PrepareVote) { + if !cbft.NeedGroup() { + return + } // Query which group the PrepareVote belongs to according to the information of the node sending the PrepareVote message groupID, _, err := cbft.getGroupByValidatorID(vote.EpochNum(), node.NodeID) if err != nil { @@ -837,6 +852,9 @@ func (cbft *Cbft) richBlockQuorumCert(epoch uint64, blockIndex, groupID uint32, } func (cbft *Cbft) mergeViewChangeToViewChangeQuorumCerts(node *cbfttypes.ValidateNode, vc *protocols.ViewChange) { + if !cbft.NeedGroup() { + return + } // Query which group the ViewChange belongs to according to the information of the node sending the ViewChange message groupID, _, err := cbft.getGroupByValidatorID(vc.EpochNum(), node.NodeID) if err != nil { @@ -950,6 +968,9 @@ func (cbft *Cbft) findQCBlock() { } enoughRGQuorumCerts := func() bool { + if !cbft.NeedGroup() { + return false + } rgqcs := cbft.state.FindMaxRGQuorumCerts(next) size := 0 if len(rgqcs) > 0 { @@ -966,6 +987,9 @@ func (cbft *Cbft) findQCBlock() { } enoughCombine := func() bool { + if !cbft.NeedGroup() { + return false + } knownIndexes := cbft.KnownVoteIndexes(next) if len(knownIndexes) >= threshold { rgqcs := cbft.state.FindMaxRGQuorumCerts(next) @@ -1131,6 +1155,9 @@ func (cbft *Cbft) tryChangeView() { } enoughRGQuorumCerts := func() bool { + if !cbft.NeedGroup() { + return false + } viewChangeQCs := cbft.state.FindMaxRGViewChangeQuorumCerts() size := 0 if len(viewChangeQCs) > 0 { @@ -1147,6 +1174,9 @@ func (cbft *Cbft) tryChangeView() { } enoughCombine := func() bool { + if !cbft.NeedGroup() { + return false + } knownIndexes := cbft.KnownViewChangeIndexes() if len(knownIndexes) >= threshold { viewChangeQCs := cbft.state.FindMaxRGViewChangeQuorumCerts() @@ -1283,7 +1313,7 @@ func (cbft *Cbft) generateViewChange(qc *ctypes.QuorumCert) (*protocols.ViewChan ViewNumber: cbft.state.ViewNumber(), BlockHash: qc.BlockHash, BlockNumber: qc.BlockNumber, - ValidatorIndex: uint32(node.Index), + ValidatorIndex: node.Index, PrepareQC: qc, } if err := cbft.signMsgByBls(v); err != nil { diff --git a/consensus/cbft/network/handler.go b/consensus/cbft/network/handler.go index a7cba3e671..8945da8e13 100644 --- a/consensus/cbft/network/handler.go +++ b/consensus/cbft/network/handler.go @@ -444,6 +444,9 @@ func (h *EngineManager) handler(p *p2p.Peer, rw p2p.MsgReadWriter) error { } func (h *EngineManager) HandleRGMsg(p *peer, msg *p2p.Msg) error { + if !h.engine.NeedGroup() { + return nil + } // TODO just for log logVerbosity := debug.GetLogVerbosity() if logVerbosity == log.LvlTrace { @@ -526,7 +529,32 @@ func (h *EngineManager) handleMsg(p *peer) error { // Message transfer to cbft message queue. return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) + case msg.Code == protocols.PrepareVoteMsg: + if h.engine.NeedGroup() { + return nil + } + var request protocols.PrepareVote + if err := msg.Decode(&request); err != nil { + return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) + } + p.MarkMessageHash((&request).MsgHash()) + return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) + + case msg.Code == protocols.ViewChangeMsg: + if h.engine.NeedGroup() { + return nil + } + var request protocols.ViewChange + if err := msg.Decode(&request); err != nil { + return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) + } + p.MarkMessageHash((&request).MsgHash()) + return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) + case msg.Code == protocols.RGBlockQuorumCertMsg: + if !h.engine.NeedGroup() { + return nil + } var request protocols.RGBlockQuorumCert if err := msg.Decode(&request); err != nil { return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) @@ -535,6 +563,9 @@ func (h *EngineManager) handleMsg(p *peer) error { return h.engine.ReceiveMessage(types.NewMsgInfo(&request, p.PeerID())) case msg.Code == protocols.RGViewChangeQuorumCertMsg: + if !h.engine.NeedGroup() { + return nil + } var request protocols.RGViewChangeQuorumCert if err := msg.Decode(&request); err != nil { return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) @@ -573,10 +604,15 @@ func (h *EngineManager) handleMsg(p *peer) error { case msg.Code == protocols.GetPrepareVoteMsg: var request protocols.GetPrepareVote - if err := msg.Decode(&request); err != nil { + if err := msg.Decode(&request); err == nil { + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) + } + var requestV2 protocols.GetPrepareVoteV2 + if err := msg.Decode(&requestV2); err == nil { + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&requestV2, p.PeerID())) + } else { return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) } - return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) case msg.Code == protocols.PrepareBlockHashMsg: var request protocols.PrepareBlockHash @@ -588,10 +624,15 @@ func (h *EngineManager) handleMsg(p *peer) error { case msg.Code == protocols.PrepareVotesMsg: var request protocols.PrepareVotes - if err := msg.Decode(&request); err != nil { + if err := msg.Decode(&request); err == nil { + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) + } + var requestV2 protocols.PrepareVotesV2 + if err := msg.Decode(&requestV2); err == nil { + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&requestV2, p.PeerID())) + } else { return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) } - return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) case msg.Code == protocols.QCBlockListMsg: var request protocols.QCBlockList @@ -616,10 +657,15 @@ func (h *EngineManager) handleMsg(p *peer) error { case msg.Code == protocols.GetViewChangeMsg: var request protocols.GetViewChange - if err := msg.Decode(&request); err != nil { + if err := msg.Decode(&request); err == nil { + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) + } + var requestV2 protocols.GetViewChangeV2 + if err := msg.Decode(&requestV2); err == nil { + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&requestV2, p.PeerID())) + } else { return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) } - return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) case msg.Code == protocols.PingMsg: var pingTime protocols.Ping @@ -672,10 +718,15 @@ func (h *EngineManager) handleMsg(p *peer) error { return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) case msg.Code == protocols.ViewChangesMsg: var request protocols.ViewChanges - if err := msg.Decode(&request); err != nil { + if err := msg.Decode(&request); err == nil { + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) + } + var requestV2 protocols.ViewChangesV2 + if err := msg.Decode(&requestV2); err == nil { + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&requestV2, p.PeerID())) + } else { return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) } - return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) default: return types.ErrResp(types.ErrInvalidMsgCode, "%v", msg.Code) diff --git a/consensus/cbft/network/interface.go b/consensus/cbft/network/interface.go index 44f7beaa37..3a243960d3 100644 --- a/consensus/cbft/network/interface.go +++ b/consensus/cbft/network/interface.go @@ -54,10 +54,10 @@ type Cbft interface { HighestCommitBlockBn() (uint64, common.Hash) // Returns the node ID of the missing vote. - MissingViewChangeNodes() (*protocols.GetViewChange, error) + MissingViewChangeNodes() (types.Message, error) // Returns the missing vote. - MissingPrepareVote() (*protocols.GetPrepareVote, error) + MissingPrepareVote() (types.Message, error) // Returns latest status. LatestStatus() *protocols.GetLatestStatus @@ -68,6 +68,9 @@ type Cbft interface { // BlockExists determines if a block exists. BlockExists(blockNumber uint64, blockHash common.Hash) error + // NeedGroup indicates whether grouped consensus will be used + NeedGroup() bool + // TODO just for log GetGroupByValidatorID(nodeID enode.ID) (uint32, uint32, error) } diff --git a/consensus/cbft/network/router.go b/consensus/cbft/network/router.go index cbe7a65266..d66340c053 100644 --- a/consensus/cbft/network/router.go +++ b/consensus/cbft/network/router.go @@ -152,8 +152,9 @@ func (r *router) filteredPeers(msgType uint64, condition common.Hash) ([]*peer, // Test the anchor point, please pay attention to let go. //return r.peers() switch msgType { - case protocols.PrepareBlockMsg, protocols.RGBlockQuorumCertMsg, - protocols.RGViewChangeQuorumCertMsg, protocols.BlockQuorumCertMsg: + case protocols.PrepareBlockMsg, protocols.PrepareVoteMsg, protocols.ViewChangeMsg, + protocols.RGBlockQuorumCertMsg, protocols.RGViewChangeQuorumCertMsg, + protocols.BlockQuorumCertMsg: return r.kMixingRandomNodes(condition, r.filter) case protocols.PrepareBlockHashMsg, protocols.GetLatestStatusMsg, protocols.GetViewChangeMsg, protocols.GetPrepareVoteMsg, diff --git a/consensus/cbft/protocols/protocol.go b/consensus/cbft/protocols/protocol.go index 13354ce4e1..5fb6cf60e3 100644 --- a/consensus/cbft/protocols/protocol.go +++ b/consensus/cbft/protocols/protocol.go @@ -81,11 +81,11 @@ func MessageType(msg interface{}) uint64 { return BlockQuorumCertMsg case *GetQCBlockList: return GetQCBlockListMsg - case *GetPrepareVote: + case *GetPrepareVote, *GetPrepareVoteV2: return GetPrepareVoteMsg case *PrepareBlockHash: return PrepareBlockHashMsg - case *PrepareVotes: + case *PrepareVotes, *PrepareVotesV2: return PrepareVotesMsg case *QCBlockList: return QCBlockListMsg @@ -93,7 +93,7 @@ func MessageType(msg interface{}) uint64 { return GetLatestStatusMsg case *LatestStatus: return LatestStatusMsg - case *GetViewChange: + case *GetViewChange, *GetViewChangeV2: return GetViewChangeMsg case *Ping: return PingMsg @@ -101,7 +101,7 @@ func MessageType(msg interface{}) uint64 { return PongMsg case *ViewChangeQuorumCert: return ViewChangeQuorumCertMsg - case *ViewChanges: + case *ViewChanges, *ViewChangesV2: return ViewChangesMsg case *RGBlockQuorumCert: return RGBlockQuorumCertMsg @@ -365,12 +365,45 @@ func (vc *ViewChange) SetSign(sign []byte) { } type ViewChanges struct { + VCs []*ViewChange + messageHash atomic.Value `rlp:"-"` +} + +func (v ViewChanges) String() string { + if len(v.VCs) != 0 { + epoch, viewNumber := v.VCs[0].Epoch, v.VCs[0].ViewNumber + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,Len:%d}", epoch, viewNumber, len(v.VCs)) + } + return fmt.Sprintf("{Len:%d}", len(v.VCs)) +} + +func (v ViewChanges) MsgHash() common.Hash { + if mhash := v.messageHash.Load(); mhash != nil { + return mhash.(common.Hash) + } + var mv common.Hash + if len(v.VCs) != 0 { + epoch, viewNumber := v.VCs[0].Epoch, v.VCs[0].ViewNumber + mv = utils.BuildHash(ViewChangesMsg, utils.MergeBytes(common.Uint64ToBytes(epoch), + common.Uint64ToBytes(viewNumber))) + } else { + mv = utils.BuildHash(ViewChangesMsg, common.Hash{}.Bytes()) + } + v.messageHash.Store(mv) + return mv +} + +func (v ViewChanges) BHash() common.Hash { + return common.Hash{} +} + +type ViewChangesV2 struct { VCs []*ViewChange RGViewChangeQuorumCerts []*RGViewChangeQuorumCert messageHash atomic.Value `rlp:"-"` } -func (v ViewChanges) String() string { +func (v ViewChangesV2) String() string { if len(v.VCs) != 0 { epoch, viewNumber := v.VCs[0].Epoch, v.VCs[0].ViewNumber return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,VCsLen:%d,,RGLen:%d}", epoch, viewNumber, len(v.VCs), len(v.RGViewChangeQuorumCerts)) @@ -378,7 +411,7 @@ func (v ViewChanges) String() string { return "" } -func (v ViewChanges) MsgHash() common.Hash { +func (v ViewChangesV2) MsgHash() common.Hash { if mhash := v.messageHash.Load(); mhash != nil { return mhash.(common.Hash) } @@ -394,7 +427,7 @@ func (v ViewChanges) MsgHash() common.Hash { return mv } -func (ViewChanges) BHash() common.Hash { +func (v ViewChangesV2) BHash() common.Hash { return common.Hash{} } @@ -536,12 +569,12 @@ type GetPrepareVote struct { Epoch uint64 ViewNumber uint64 BlockIndex uint32 - UnKnownSet *ctypes.UnKnownGroups + UnKnownSet *utils.BitArray messageHash atomic.Value `json:"-" rlp:"-"` } func (s *GetPrepareVote) String() string { - return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,BlockIndex:%d,UnKnownSetLen:%d,UnKnownSet:%s}", s.Epoch, s.ViewNumber, s.BlockIndex, s.UnKnownSet.UnKnownSize(), s.UnKnownSet.String()) + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,BlockIndex:%d,UnKnownSet:%s}", s.Epoch, s.ViewNumber, s.BlockIndex, s.UnKnownSet.String()) } func (s *GetPrepareVote) MsgHash() common.Hash { @@ -558,8 +591,61 @@ func (s *GetPrepareVote) BHash() common.Hash { return common.Hash{} } +// Message used to get block voting. +type GetPrepareVoteV2 struct { + Epoch uint64 + ViewNumber uint64 + BlockIndex uint32 + UnKnownGroups *ctypes.UnKnownGroups + messageHash atomic.Value `json:"-" rlp:"-"` +} + +func (s *GetPrepareVoteV2) String() string { + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,BlockIndex:%d,UnKnownSetLen:%d,UnKnownGroups:%s}", s.Epoch, s.ViewNumber, s.BlockIndex, s.UnKnownGroups.UnKnownSize(), s.UnKnownGroups.String()) +} + +func (s *GetPrepareVoteV2) MsgHash() common.Hash { + if mhash := s.messageHash.Load(); mhash != nil { + return mhash.(common.Hash) + } + v := utils.BuildHash(GetPrepareVoteMsg, utils.MergeBytes(common.Uint64ToBytes(s.Epoch), common.Uint64ToBytes(s.ViewNumber), + common.Uint32ToBytes(s.BlockIndex))) + s.messageHash.Store(v) + return v +} + +func (s *GetPrepareVoteV2) BHash() common.Hash { + return common.Hash{} +} + // Message used to respond to the number of block votes. type PrepareVotes struct { + Epoch uint64 + ViewNumber uint64 + BlockIndex uint32 + Votes []*PrepareVote // Block voting set. + messageHash atomic.Value `json:"-" rlp:"-"` +} + +func (s *PrepareVotes) String() string { + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,BlockIndex:%d}", s.Epoch, s.ViewNumber, s.BlockIndex) +} + +func (s *PrepareVotes) MsgHash() common.Hash { + if mhash := s.messageHash.Load(); mhash != nil { + return mhash.(common.Hash) + } + v := utils.BuildHash(PrepareVotesMsg, utils.MergeBytes(common.Uint64ToBytes(s.Epoch), common.Uint64ToBytes(s.ViewNumber), common.Uint32ToBytes(s.BlockIndex))) + s.messageHash.Store(v) + return v +} + +func (s *PrepareVotes) BHash() common.Hash { + return common.Hash{} +} + +// Message used to respond to the number of block votes. +type PrepareVotesV2 struct { Epoch uint64 ViewNumber uint64 BlockIndex uint32 @@ -568,11 +654,11 @@ type PrepareVotes struct { messageHash atomic.Value `json:"-" rlp:"-"` } -func (s *PrepareVotes) String() string { +func (s *PrepareVotesV2) String() string { return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,BlockIndex:%d,VotesLen:%d,RGLen:%d}", s.Epoch, s.ViewNumber, s.BlockIndex, len(s.Votes), len(s.RGBlockQuorumCerts)) } -func (s *PrepareVotes) MsgHash() common.Hash { +func (s *PrepareVotesV2) MsgHash() common.Hash { if mhash := s.messageHash.Load(); mhash != nil { return mhash.(common.Hash) } @@ -581,7 +667,7 @@ func (s *PrepareVotes) MsgHash() common.Hash { return v } -func (s *PrepareVotes) BHash() common.Hash { +func (s *PrepareVotesV2) BHash() common.Hash { return common.Hash{} } @@ -746,14 +832,14 @@ func (s *LatestStatus) BHash() common.Hash { // Used to actively request to get viewChange. type GetViewChange struct { - Epoch uint64 `json:"epoch"` - ViewNumber uint64 `json:"viewNumber"` - UnKnownSet *ctypes.UnKnownGroups `json:"unKnownSet"` - messageHash atomic.Value `rlp:"-"` + Epoch uint64 `json:"epoch"` + ViewNumber uint64 `json:"viewNumber"` + ViewChangeBits *utils.BitArray `json:"nodeIndexes"` + messageHash atomic.Value `rlp:"-"` } func (s *GetViewChange) String() string { - return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,UnKnownSetLen:%d,UnKnownSet:%s}", s.Epoch, s.ViewNumber, s.UnKnownSet.UnKnownSize(), s.UnKnownSet.String()) + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,NodeIndexesLen:%s}", s.Epoch, s.ViewNumber, s.ViewChangeBits.String()) } func (s *GetViewChange) MsgHash() common.Hash { @@ -770,6 +856,32 @@ func (s *GetViewChange) BHash() common.Hash { return common.Hash{} } +// Used to actively request to get viewChange. +type GetViewChangeV2 struct { + Epoch uint64 `json:"epoch"` + ViewNumber uint64 `json:"viewNumber"` + UnKnownGroups *ctypes.UnKnownGroups `json:"unKnownSet"` + messageHash atomic.Value `rlp:"-"` +} + +func (s *GetViewChangeV2) String() string { + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,UnKnownSetLen:%d,UnKnownGroups:%s}", s.Epoch, s.ViewNumber, s.UnKnownGroups.UnKnownSize(), s.UnKnownGroups.String()) +} + +func (s *GetViewChangeV2) MsgHash() common.Hash { + if mhash := s.messageHash.Load(); mhash != nil { + return mhash.(common.Hash) + } + v := utils.BuildHash(GetViewChangeMsg, + utils.MergeBytes(common.Uint64ToBytes(s.Epoch), common.Uint64ToBytes(s.ViewNumber))) + s.messageHash.Store(v) + return v +} + +func (s *GetViewChangeV2) BHash() common.Hash { + return common.Hash{} +} + type ViewChangeQuorumCert struct { ViewChangeQC *ctypes.ViewChangeQC `json:"viewchangeQC"` // viewChange aggregate signature HighestViewChangeQC *ctypes.ViewChangeQC `json:"highestViewChangeQC" rlp:"nil"` // the highest viewChangeQC of current epoch diff --git a/consensus/cbft/sync_msg_test.go b/consensus/cbft/sync_msg_test.go index 938a664555..442c976647 100644 --- a/consensus/cbft/sync_msg_test.go +++ b/consensus/cbft/sync_msg_test.go @@ -352,38 +352,6 @@ func (suit *SyncMsgTestSuite) TestOnGetQCBlockListDifNumber() { } } -// normal -//func (suit *SyncMsgTestSuite) TestOnGetPrepareVote() { -// votes := make([]*protocols.PrepareVote, 0) -// for _, node := range suit.view.allCbft { -// index, err := node.validatorPool.GetIndexByNodeID(suit.epoch, node.config.Option.Node.ID()) -// if err != nil { -// panic(err.Error()) -// } -// vote := mockPrepareVote(node.config.Option.BlsPriKey, suit.epoch, suit.oldViewNumber, -// 0, index, suit.blockOne.Hash(), suit.blockOne.NumberU64(), nil) -// votes = append(votes, vote) -// suit.view.firstProposer().state.AddPrepareVote(index, vote) -// } -// unKnownSet := utils.NewBitArray(uint32(len(suit.view.allCbft))) -// for i := uint32(0); i < unKnownSet.Size(); i++ { -// unKnownSet.SetIndex(i, true) -// } -// getPrepareVote := &protocols.GetPrepareVote{ -// Epoch: suit.epoch, -// ViewNumber: suit.oldViewNumber, -// BlockIndex: 0, -// UnKnownSet: unKnownSet, -// } -// cleanCh(suit.msgCh) -// suit.view.firstProposer().OnGetPrepareVote("", getPrepareVote) -// select { -// case <-suit.msgCh: -// case <-time.After(time.Millisecond * 10): -// suit.T().Fatal("timeout") -// } -//} - // normal func (suit *SyncMsgTestSuite) TestOnPrepareVotes() { pb := mockPrepareBlock(suit.view.firstProposerBlsKey(), suit.epoch, suit.oldViewNumber, 0, suit.view.firstProposerIndex(), suit.blockOne, nil, nil) diff --git a/consensus/cbft/sync_process.go b/consensus/cbft/sync_process.go index a7d298d5f9..644cd39a53 100644 --- a/consensus/cbft/sync_process.go +++ b/consensus/cbft/sync_process.go @@ -334,11 +334,11 @@ func (cbft *Cbft) OnGetQCBlockList(id string, msg *protocols.GetQCBlockList) err return nil } -// OnGetPrepareVote is responsible for processing the business logic +// OnGetPrepareVoteV2 is responsible for processing the business logic // of the GetPrepareVote message. It will synchronously return a // PrepareVotes message to the sender. -func (cbft *Cbft) OnGetPrepareVote(id string, msg *protocols.GetPrepareVote) error { - cbft.log.Debug("Received message on OnGetPrepareVote", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) +func (cbft *Cbft) OnGetPrepareVoteV2(id string, msg *protocols.GetPrepareVoteV2) error { + cbft.log.Debug("Received message on OnGetPrepareVoteV2", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) if msg.Epoch == cbft.state.Epoch() && msg.ViewNumber == cbft.state.ViewNumber() { // If the block has already QC, that response QC instead of votes. // Avoid the sender spent a lot of time to verifies PrepareVote msg. @@ -349,10 +349,10 @@ func (cbft *Cbft) OnGetPrepareVote(id string, msg *protocols.GetPrepareVote) err return nil } - if len(msg.UnKnownSet.UnKnown) > 0 { + if len(msg.UnKnownGroups.UnKnown) > 0 { validatorLen := cbft.currentValidatorLen() threshold := cbft.threshold(validatorLen) - remain := threshold - (validatorLen - msg.UnKnownSet.UnKnownSize()) + remain := threshold - (validatorLen - msg.UnKnownGroups.UnKnownSize()) // Defining an array for receiving PrepareVote. votes := make([]*protocols.PrepareVote, 0) @@ -360,9 +360,9 @@ func (cbft *Cbft) OnGetPrepareVote(id string, msg *protocols.GetPrepareVote) err prepareVoteMap := cbft.state.AllPrepareVoteByIndex(msg.BlockIndex) - for _, un := range msg.UnKnownSet.UnKnown { + for _, un := range msg.UnKnownGroups.UnKnown { if un.UnKnownSet.Size() != uint32(validatorLen) { - cbft.log.Error("Invalid request params,UnKnownSet is not a specified length", "peer", id, "groupID", un.GroupID, "unKnownSet", un.UnKnownSet.String(), "validatorLen", validatorLen) + cbft.log.Error("Invalid request params,UnKnownGroups is not a specified length", "peer", id, "groupID", un.GroupID, "unKnownSet", un.UnKnownSet.String(), "validatorLen", validatorLen) break // Do not continue processing the request } // Limit response votes @@ -398,7 +398,7 @@ func (cbft *Cbft) OnGetPrepareVote(id string, msg *protocols.GetPrepareVote) err } if len(votes) > 0 || len(RGBlockQuorumCerts) > 0 { - cbft.network.Send(id, &protocols.PrepareVotes{Epoch: msg.Epoch, ViewNumber: msg.ViewNumber, BlockIndex: msg.BlockIndex, Votes: votes, RGBlockQuorumCerts: RGBlockQuorumCerts}) + cbft.network.Send(id, &protocols.PrepareVotesV2{Epoch: msg.Epoch, ViewNumber: msg.ViewNumber, BlockIndex: msg.BlockIndex, Votes: votes, RGBlockQuorumCerts: RGBlockQuorumCerts}) cbft.log.Debug("Send PrepareVotes", "peer", id, "blockIndex", msg.BlockIndex, "votes length", len(votes), "RGBlockQuorumCerts length", len(RGBlockQuorumCerts)) } } @@ -406,8 +406,65 @@ func (cbft *Cbft) OnGetPrepareVote(id string, msg *protocols.GetPrepareVote) err return nil } +// OnGetPrepareVoteV2 is responsible for processing the business logic +// of the GetPrepareVote message. It will synchronously return a +// PrepareVotes message to the sender. +func (cbft *Cbft) OnGetPrepareVote(id string, msg *protocols.GetPrepareVote) error { + cbft.log.Debug("Received message on OnGetPrepareVoteV2", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) + if msg.Epoch == cbft.state.Epoch() && msg.ViewNumber == cbft.state.ViewNumber() { + // If the block has already QC, that response QC instead of votes. + // Avoid the sender spent a lot of time to verifies PrepareVote msg. + _, qc := cbft.state.ViewBlockAndQC(msg.BlockIndex) + if qc != nil { + cbft.network.Send(id, &protocols.BlockQuorumCert{BlockQC: qc}) + cbft.log.Debug("Send BlockQuorumCert", "peer", id, "qc", qc.String()) + return nil + } + + prepareVoteMap := cbft.state.AllPrepareVoteByIndex(msg.BlockIndex) + // Defining an array for receiving PrepareVote. + votes := make([]*protocols.PrepareVote, 0, len(prepareVoteMap)) + if prepareVoteMap != nil { + threshold := cbft.threshold(cbft.currentValidatorLen()) + remain := threshold - (cbft.currentValidatorLen() - int(msg.UnKnownSet.Size())) + for k, v := range prepareVoteMap { + if msg.UnKnownSet.GetIndex(k) { + votes = append(votes, v) + } + + // Limit response votes + if remain > 0 && len(votes) >= remain { + break + } + } + } + if len(votes) > 0 { + cbft.network.Send(id, &protocols.PrepareVotes{Epoch: msg.Epoch, ViewNumber: msg.ViewNumber, BlockIndex: msg.BlockIndex, Votes: votes}) + cbft.log.Debug("Send PrepareVotes", "peer", id, "epoch", msg.Epoch, "viewNumber", msg.ViewNumber, "blockIndex", msg.BlockIndex) + } + } + return nil +} + // OnPrepareVotes handling response from GetPrepareVote response. func (cbft *Cbft) OnPrepareVotes(id string, msg *protocols.PrepareVotes) error { + cbft.log.Debug("Received message on OnPrepareVotes", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) + for _, vote := range msg.Votes { + _, qc := cbft.blockTree.FindBlockAndQC(vote.BlockHash, vote.BlockNumber) + if qc == nil && !cbft.network.ContainsHistoryMessageHash(vote.MsgHash()) { + if err := cbft.OnPrepareVote(id, vote); err != nil { + if e, ok := err.(HandleError); ok && e.AuthFailed() { + cbft.log.Error("OnPrepareVotes failed", "peer", id, "err", err) + } + return err + } + } + } + return nil +} + +// OnPrepareVotes handling response from GetPrepareVote response. +func (cbft *Cbft) OnPrepareVotesV2(id string, msg *protocols.PrepareVotesV2) error { cbft.log.Debug("Received message on OnPrepareVotes", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) alreadyQC := func(hash common.Hash, number uint64) bool { if _, qc := cbft.blockTree.FindBlockAndQC(hash, number); qc != nil { @@ -560,12 +617,11 @@ func (cbft *Cbft) OnPrepareBlockHash(id string, msg *protocols.PrepareBlockHash) return nil } -// OnGetViewChange responds to nodes that require viewChange. -// +// OnGetViewChangeV2 responds to nodes that require viewChange. // The Epoch and viewNumber of viewChange must be consistent // with the state of the current node. -func (cbft *Cbft) OnGetViewChange(id string, msg *protocols.GetViewChange) error { - cbft.log.Debug("Received message on OnGetViewChange", "from", id, "msgHash", msg.MsgHash(), "message", msg.String(), "local", cbft.state.ViewString()) +func (cbft *Cbft) OnGetViewChangeV2(id string, msg *protocols.GetViewChangeV2) error { + cbft.log.Debug("Received message on OnGetViewChangeV2", "from", id, "msgHash", msg.MsgHash(), "message", msg.String(), "local", cbft.state.ViewString()) localEpoch, localViewNumber := cbft.state.Epoch(), cbft.state.ViewNumber() @@ -581,19 +637,19 @@ func (cbft *Cbft) OnGetViewChange(id string, msg *protocols.GetViewChange) error return msg.Epoch == localEpoch && msg.ViewNumber+1 < localViewNumber } - if isLocalView() && len(msg.UnKnownSet.UnKnown) > 0 { + if isLocalView() && len(msg.UnKnownGroups.UnKnown) > 0 { validatorLen := cbft.currentValidatorLen() threshold := cbft.threshold(validatorLen) - remain := threshold - (validatorLen - msg.UnKnownSet.UnKnownSize()) + remain := threshold - (validatorLen - msg.UnKnownGroups.UnKnownSize()) viewChanges := make([]*protocols.ViewChange, 0) RGViewChangeQuorumCerts := make([]*protocols.RGViewChangeQuorumCert, 0) viewChangeMap := cbft.state.AllViewChange() - for _, un := range msg.UnKnownSet.UnKnown { + for _, un := range msg.UnKnownGroups.UnKnown { if un.UnKnownSet.Size() != uint32(validatorLen) { - cbft.log.Error("Invalid request params,UnKnownSet is not a specified length", "peer", id, "groupID", un.GroupID, "unKnownSet", un.UnKnownSet.String(), "validatorLen", validatorLen) + cbft.log.Error("Invalid request params,UnKnownGroups is not a specified length", "peer", id, "groupID", un.GroupID, "unKnownSet", un.UnKnownSet.String(), "validatorLen", validatorLen) break // Do not continue processing the request } // Limit response votes @@ -629,7 +685,7 @@ func (cbft *Cbft) OnGetViewChange(id string, msg *protocols.GetViewChange) error } if len(viewChanges) > 0 || len(RGViewChangeQuorumCerts) > 0 { - cbft.network.Send(id, &protocols.ViewChanges{VCs: viewChanges, RGViewChangeQuorumCerts: RGViewChangeQuorumCerts}) + cbft.network.Send(id, &protocols.ViewChangesV2{VCs: viewChanges, RGViewChangeQuorumCerts: RGViewChangeQuorumCerts}) cbft.log.Debug("Send ViewChanges", "peer", id, "viewChanges length", len(viewChanges), "RGViewChangeQuorumCerts length", len(RGViewChangeQuorumCerts)) } return nil @@ -671,6 +727,78 @@ func (cbft *Cbft) OnGetViewChange(id string, msg *protocols.GetViewChange) error return fmt.Errorf("request is not match local view, local:%s,msg:%s", cbft.state.ViewString(), msg.String()) } +// OnGetViewChangeV2 responds to nodes that require viewChange. +// The Epoch and viewNumber of viewChange must be consistent +// with the state of the current node. +func (cbft *Cbft) OnGetViewChange(id string, msg *protocols.GetViewChange) error { + cbft.log.Debug("Received message on OnGetViewChangeV2", "from", id, "msgHash", msg.MsgHash(), "message", msg.String(), "local", cbft.state.ViewString()) + + localEpoch, localViewNumber := cbft.state.Epoch(), cbft.state.ViewNumber() + + isLocalView := func() bool { + return msg.Epoch == localEpoch && msg.ViewNumber == localViewNumber + } + + isLastView := func() bool { + return (msg.Epoch == localEpoch && msg.ViewNumber+1 == localViewNumber) || (msg.Epoch+1 == localEpoch && localViewNumber == state.DefaultViewNumber) + } + + isPreviousView := func() bool { + return msg.Epoch == localEpoch && msg.ViewNumber+1 < localViewNumber + } + + if isLocalView() { + viewChanges := cbft.state.AllViewChange() + + vcs := &protocols.ViewChanges{} + for k, v := range viewChanges { + if msg.ViewChangeBits.GetIndex(k) { + vcs.VCs = append(vcs.VCs, v) + } + } + cbft.log.Debug("Send ViewChanges", "peer", id, "len", len(vcs.VCs)) + if len(vcs.VCs) != 0 { + cbft.network.Send(id, vcs) + } + return nil + } + // Return view QC in the case of less than 1. + if isLastView() { + lastViewChangeQC := cbft.state.LastViewChangeQC() + if lastViewChangeQC == nil { + cbft.log.Info("Not found lastViewChangeQC") + return nil + } + err := lastViewChangeQC.EqualAll(msg.Epoch, msg.ViewNumber) + if err != nil { + cbft.log.Error("Last view change is not equal msg.viewNumber", "err", err) + return err + } + cbft.network.Send(id, &protocols.ViewChangeQuorumCert{ + ViewChangeQC: lastViewChangeQC, + }) + return nil + } + // get previous viewChangeQC from wal db + if isPreviousView() { + if qc, err := cbft.bridge.GetViewChangeQC(msg.Epoch, msg.ViewNumber); err == nil && qc != nil { + // also inform the local highest view + highestqc, _ := cbft.bridge.GetViewChangeQC(localEpoch, localViewNumber-1) + viewChangeQuorumCert := &protocols.ViewChangeQuorumCert{ + ViewChangeQC: qc, + } + if highestqc != nil { + viewChangeQuorumCert.HighestViewChangeQC = highestqc + } + cbft.log.Debug("Send previous viewChange quorumCert", "viewChangeQuorumCert", viewChangeQuorumCert.String()) + cbft.network.Send(id, viewChangeQuorumCert) + return nil + } + } + + return fmt.Errorf("request is not match local view, local:%s,msg:%s", cbft.state.ViewString(), msg.String()) +} + // OnViewChangeQuorumCert handles the message type of ViewChangeQuorumCertMsg. func (cbft *Cbft) OnViewChangeQuorumCert(id string, msg *protocols.ViewChangeQuorumCert) error { cbft.log.Debug("Received message on OnViewChangeQuorumCert", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) @@ -712,10 +840,10 @@ func (cbft *Cbft) trySyncViewChangeQuorumCert(id string, msg *protocols.ViewChan if err := cbft.verifyViewChangeQC(highestViewChangeQC); err == nil { cbft.log.Debug("Receive future viewChange quorumCert, sync viewChangeQC with fast mode", "localView", cbft.state.ViewString(), "futureView", highestViewChangeQC.String()) // request viewChangeQC for the current view - cbft.network.Send(id, &protocols.GetViewChange{ - Epoch: cbft.state.Epoch(), - ViewNumber: cbft.state.ViewNumber(), - UnKnownSet: &ctypes.UnKnownGroups{UnKnown: make([]*ctypes.UnKnownGroup, 0)}, + cbft.network.Send(id, &protocols.GetViewChangeV2{ + Epoch: cbft.state.Epoch(), + ViewNumber: cbft.state.ViewNumber(), + UnKnownGroups: &ctypes.UnKnownGroups{UnKnown: make([]*ctypes.UnKnownGroup, 0)}, }) } } @@ -724,6 +852,22 @@ func (cbft *Cbft) trySyncViewChangeQuorumCert(id string, msg *protocols.ViewChan // OnViewChanges handles the message type of ViewChangesMsg. func (cbft *Cbft) OnViewChanges(id string, msg *protocols.ViewChanges) error { cbft.log.Debug("Received message on OnViewChanges", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) + for _, v := range msg.VCs { + if !cbft.network.ContainsHistoryMessageHash(v.MsgHash()) { + if err := cbft.OnViewChange(id, v); err != nil { + if e, ok := err.(HandleError); ok && e.AuthFailed() { + cbft.log.Error("OnViewChanges failed", "peer", id, "err", err) + } + return err + } + } + } + return nil +} + +// OnViewChanges handles the message type of ViewChangesMsg. +func (cbft *Cbft) OnViewChangesV2(id string, msg *protocols.ViewChangesV2) error { + cbft.log.Debug("Received message on OnViewChanges", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) for _, rgqc := range msg.RGViewChangeQuorumCerts { if !cbft.network.ContainsHistoryMessageHash(rgqc.MsgHash()) { @@ -874,7 +1018,10 @@ func (cbft *Cbft) MissGroupViewChanges() *ctypes.UnKnownGroups { } // MissingPrepareVote returns missing vote. -func (cbft *Cbft) MissingPrepareVote() (v *protocols.GetPrepareVote, err error) { +func (cbft *Cbft) MissingPrepareVote() (v ctypes.Message, err error) { + if !cbft.NeedGroup() { + return cbft.MissingPrepareVoteV1() + } result := make(chan struct{}) cbft.asyncCallCh <- func() { @@ -898,11 +1045,11 @@ func (cbft *Cbft) MissingPrepareVote() (v *protocols.GetPrepareVote, err error) if size < threshold { // need sync prepare votes unKnownGroups := cbft.MissGroupVotes(index) if len(unKnownGroups.UnKnown) > 0 { - v, err = &protocols.GetPrepareVote{ - Epoch: cbft.state.Epoch(), - ViewNumber: cbft.state.ViewNumber(), - BlockIndex: index, - UnKnownSet: unKnownGroups, + v, err = &protocols.GetPrepareVoteV2{ + Epoch: cbft.state.Epoch(), + ViewNumber: cbft.state.ViewNumber(), + BlockIndex: index, + UnKnownGroups: unKnownGroups, }, nil cbft.log.Debug("MissingPrepareVote", "blockIndex", index, "known", size, "threshold", threshold, "request", v.String()) break @@ -918,10 +1065,11 @@ func (cbft *Cbft) MissingPrepareVote() (v *protocols.GetPrepareVote, err error) } // MissingViewChangeNodes returns the node ID of the missing vote. -// -// Notes: // Use the channel to complete serial execution to prevent concurrency. -func (cbft *Cbft) MissingViewChangeNodes() (v *protocols.GetViewChange, err error) { +func (cbft *Cbft) MissingViewChangeNodes() (v ctypes.Message, err error) { + if !cbft.NeedGroup() { + return cbft.MissingViewChangeNodesV1() + } result := make(chan struct{}) cbft.asyncCallCh <- func() { @@ -938,12 +1086,62 @@ func (cbft *Cbft) MissingViewChangeNodes() (v *protocols.GetViewChange, err erro if size < threshold { unKnownGroups := cbft.MissGroupViewChanges() if len(unKnownGroups.UnKnown) > 0 { - v, err = &protocols.GetViewChange{ + v, err = &protocols.GetViewChangeV2{ + Epoch: cbft.state.Epoch(), + ViewNumber: cbft.state.ViewNumber(), + UnKnownGroups: unKnownGroups, + }, nil + cbft.log.Debug("MissingViewChangeNodes", "known", size, "threshold", threshold, "request", v.String()) + } + } + if v == nil { + err = fmt.Errorf("not need sync prepare vote") + } + } + <-result + return +} + +// MissingPrepareVote returns missing vote. +func (cbft *Cbft) MissingPrepareVoteV1() (v *protocols.GetPrepareVote, err error) { + result := make(chan struct{}) + + cbft.asyncCallCh <- func() { + defer func() { result <- struct{}{} }() + + begin := cbft.state.MaxQCIndex() + 1 + end := cbft.state.NextViewBlockIndex() + len := cbft.currentValidatorLen() + cbft.log.Debug("MissingPrepareVote", "epoch", cbft.state.Epoch(), "viewNumber", cbft.state.ViewNumber(), "beginIndex", begin, "endIndex", end, "validatorLen", len) + + block := cbft.state.HighestQCBlock() + blockTime := common.MillisToTime(int64(block.Time())) + + for index := begin; index < end; index++ { + size := cbft.state.PrepareVoteLenByIndex(index) + cbft.log.Debug("The length of prepare vote", "index", index, "size", size) + + // We need sync prepare votes when a long time not arrived QC. + if size < cbft.threshold(len) && time.Since(blockTime) >= syncPrepareVotesInterval { // need sync prepare votes + knownVotes := cbft.state.AllPrepareVoteByIndex(index) + unKnownSet := utils.NewBitArray(uint32(len)) + for i := uint32(0); i < unKnownSet.Size(); i++ { + if vote := cbft.csPool.GetPrepareVote(cbft.state.Epoch(), cbft.state.ViewNumber(), index, i); vote != nil { + go cbft.ReceiveMessage(vote) + continue + } + if _, ok := knownVotes[i]; !ok { + unKnownSet.SetIndex(i, true) + } + } + + v, err = &protocols.GetPrepareVote{ Epoch: cbft.state.Epoch(), ViewNumber: cbft.state.ViewNumber(), - UnKnownSet: unKnownGroups, + BlockIndex: index, + UnKnownSet: unKnownSet, }, nil - cbft.log.Debug("MissingViewChangeNodes", "known", size, "threshold", threshold, "request", v.String()) + break } } if v == nil { @@ -954,6 +1152,39 @@ func (cbft *Cbft) MissingViewChangeNodes() (v *protocols.GetViewChange, err erro return } +// MissingViewChangeNodes returns the node ID of the missing vote. +// Use the channel to complete serial execution to prevent concurrency. +func (cbft *Cbft) MissingViewChangeNodesV1() (v *protocols.GetViewChange, err error) { + result := make(chan struct{}) + + cbft.asyncCallCh <- func() { + defer func() { result <- struct{}{} }() + allViewChange := cbft.state.AllViewChange() + + length := cbft.currentValidatorLen() + vbits := utils.NewBitArray(uint32(length)) + + // enough qc or did not reach deadline + if len(allViewChange) >= cbft.threshold(length) || !cbft.state.IsDeadline() { + v, err = nil, fmt.Errorf("no need sync viewchange") + return + } + for i := uint32(0); i < vbits.Size(); i++ { + if _, ok := allViewChange[i]; !ok { + vbits.SetIndex(i, true) + } + } + + v, err = &protocols.GetViewChange{ + Epoch: cbft.state.Epoch(), + ViewNumber: cbft.state.ViewNumber(), + ViewChangeBits: vbits, + }, nil + } + <-result + return +} + // LatestStatus returns latest status. func (cbft *Cbft) LatestStatus() (v *protocols.GetLatestStatus) { result := make(chan struct{}) diff --git a/consensus/cbft/types/crypto.go b/consensus/cbft/types/crypto.go index cffc1c4002..07a5aef8bb 100644 --- a/consensus/cbft/types/crypto.go +++ b/consensus/cbft/types/crypto.go @@ -508,6 +508,9 @@ func (unKnowns *UnKnownGroups) UnKnownSize() int { } func (unKnowns *UnKnownGroups) String() string { + if unKnowns == nil || len(unKnowns.UnKnown) <= 0 { + return "" + } if b, err := json.Marshal(unKnowns); err == nil { return string(b) } diff --git a/consensus/cbft/verify_qc_test.go b/consensus/cbft/verify_qc_test.go index 4426f90b65..546178a24a 100644 --- a/consensus/cbft/verify_qc_test.go +++ b/consensus/cbft/verify_qc_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package cbft import ( @@ -97,7 +96,7 @@ func (cbft *Cbft) mockGenerateViewChangeQuorumCertWithViewNumber(qc *ctypes.Quor ViewNumber: qc.ViewNumber, BlockHash: qc.BlockHash, BlockNumber: qc.BlockNumber, - ValidatorIndex: uint32(node.Index), + ValidatorIndex: node.Index, PrepareQC: qc, } if err := cbft.signMsgByBls(v); err != nil { diff --git a/consensus/cbft/wal_bridge.go b/consensus/cbft/wal_bridge.go index 80180285c9..7efb212a90 100644 --- a/consensus/cbft/wal_bridge.go +++ b/consensus/cbft/wal_bridge.go @@ -428,7 +428,7 @@ func (cbft *Cbft) recoveryMsg(msg interface{}) error { if err != nil { return err } - cbft.state.AddViewChange(uint32(node.Index), m.ViewChange) + cbft.state.AddViewChange(node.Index, m.ViewChange) } case *protocols.SendPrepareBlock: @@ -476,7 +476,7 @@ func (cbft *Cbft) recoveryMsg(msg interface{}) error { cbft.state.HadSendPrepareVote().Push(m.Vote) node, _ := cbft.validatorPool.GetValidatorByNodeID(m.Vote.Epoch, cbft.config.Option.Node.ID()) - cbft.state.AddPrepareVote(uint32(node.Index), m.Vote) + cbft.state.AddPrepareVote(node.Index, m.Vote) } case *protocols.SendRGBlockQuorumCert: @@ -486,7 +486,7 @@ func (cbft *Cbft) recoveryMsg(msg interface{}) error { } case *protocols.SendRGViewChangeQuorumCert: - cbft.log.Debug("Load journal message from wal", "msgType", reflect.TypeOf(msg), "SendRGViewChangeQuorumCert", m.String()) + cbft.log.Debug("Load journal message from wal", "msgType", reflect.TypeOf(msg), "sendRGViewChangeQuorumCert", m.String()) if cbft.equalViewState(m) { cbft.state.AddSendRGViewChangeQuorumCerts(m.ViewNumber()) } From 42e7aaeb44f3f15c9245943c3ca278781db65e1f Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 4 Jan 2022 16:24:58 +0800 Subject: [PATCH 199/299] remove MinimumPeersInTopicSearch and update max consensus discover --- cmd/alaya/main.go | 1 - cmd/alaya/usage.go | 1 - cmd/utils/flags.go | 8 ----- node/defaults.go | 11 ++++--- p2p/discover.go | 2 +- p2p/server.go | 73 +++++++++++++++++++++++++++++++++------------- 6 files changed, 59 insertions(+), 37 deletions(-) diff --git a/cmd/alaya/main.go b/cmd/alaya/main.go index 6dede601fc..eab221ec2b 100644 --- a/cmd/alaya/main.go +++ b/cmd/alaya/main.go @@ -92,7 +92,6 @@ var ( utils.MaxPeersFlag, utils.MaxConsensusPeersFlag, utils.MaxPendingPeersFlag, - utils.MinimumPeersInTopicSearch, utils.MinimumPeersPerTopic, utils.MinerGasTargetFlag, //utils.MinerGasLimitFlag, diff --git a/cmd/alaya/usage.go b/cmd/alaya/usage.go index 7d3b667b43..01843b2ed3 100644 --- a/cmd/alaya/usage.go +++ b/cmd/alaya/usage.go @@ -163,7 +163,6 @@ var AppHelpFlagGroups = []flagGroup{ utils.MaxPeersFlag, utils.MaxConsensusPeersFlag, utils.MaxPendingPeersFlag, - utils.MinimumPeersInTopicSearch, utils.MinimumPeersPerTopic, utils.NATFlag, utils.NoDiscoverFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index b32c2d08b6..0dcdb5f12c 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -382,11 +382,6 @@ var ( Usage: "Maximum number of pending connection attempts (defaults used if set to 0)", Value: 0, } - MinimumPeersInTopicSearch = cli.IntFlag{ - Name: "minpeerstopicsearch", - Usage: "Minimum number of nodes per search for the same topic (network disabled if set to 0)", - Value: 6, - } MinimumPeersPerTopic = cli.IntFlag{ Name: "minpeerstopic", Usage: "Minimum number of nodes to maintain the same topic", @@ -887,9 +882,6 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { } } - if ctx.GlobalIsSet(MinimumPeersInTopicSearch.Name) { - cfg.MinimumPeersInTopicSearch = ctx.GlobalUint(MinimumPeersInTopicSearch.Name) - } if ctx.GlobalIsSet(MinimumPeersPerTopic.Name) { cfg.MinimumPeersPerTopic = ctx.GlobalInt(MinimumPeersPerTopic.Name) } diff --git a/node/defaults.go b/node/defaults.go index 6ca4f89baf..d7fa6ec0f0 100644 --- a/node/defaults.go +++ b/node/defaults.go @@ -44,12 +44,11 @@ var DefaultConfig = Config{ WSPort: DefaultWSPort, WSModules: []string{"net", "web3"}, P2P: p2p.Config{ - ListenAddr: ":16789", - MaxPeers: 60, - NAT: nat.Any(), - MaxConsensusPeers: 40, - MinimumPeersInTopicSearch: 6, - MinimumPeersPerTopic: 10, + ListenAddr: ":16789", + MaxPeers: 60, + NAT: nat.Any(), + MaxConsensusPeers: 40, + MinimumPeersPerTopic: 10, }, } diff --git a/p2p/discover.go b/p2p/discover.go index 527f35fbc8..475bc70325 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -78,7 +78,7 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes [ if try >= 3 || currNum >= threshold { break } - nodes := enode.ReadNodes(iterator, int(srv.Config.MinimumPeersInTopicSearch)) + nodes := enode.ReadNodes(iterator, srv.Config.MinimumPeersPerTopic*2) for i, _ := range nodes { wg.Add(1) diff --git a/p2p/server.go b/p2p/server.go index 4c44aefabe..1efd305803 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -175,8 +175,6 @@ type Config struct { clock mclock.Clock - MinimumPeersInTopicSearch uint - MinimumPeersPerTopic int } @@ -231,7 +229,6 @@ type Server struct { topicSubscriberMu sync.RWMutex topicSubscriber map[string][]enode.ID - ConsensusPeers map[enode.ID]string } type peerOpFunc func(map[enode.ID]*Peer) @@ -540,7 +537,6 @@ func (srv *Server) Start() (err error) { srv.removeconsensus = make(chan *enode.Node) srv.updateConsensusStatus = make(chan map[enode.ID]struct{}) srv.topicSubscriber = make(map[string][]enode.ID) - srv.ConsensusPeers = make(map[enode.ID]string) if int(xcom.MaxGroupValidators()) <= srv.MinimumPeersPerTopic { srv.MinimumPeersPerTopic = int(xcom.MaxGroupValidators()) @@ -900,7 +896,7 @@ running: c.set(consensusDialedConn, true) } srv.topicSubscriberMu.RLock() - if _, ok := srv.ConsensusPeers[c.node.ID()]; ok { + if len(srv.getPeersTopics(c.node.ID())) > 0 { c.set(consensusDialedConn, true) } srv.topicSubscriberMu.RUnlock() @@ -975,10 +971,11 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in } if srv.consensus && c.is(consensusDialedConn) && srv.numConsensusPeer(peers) >= srv.MaxConsensusPeers { srv.topicSubscriberMu.RLock() - if connTopic, ok := srv.ConsensusPeers[c.node.ID()]; ok { + for _, connTopic := range srv.getPeersTopics(c.node.ID()) { if len(srv.pubSubServer.PubSub().ListPeers(connTopic)) < srv.MinimumPeersPerTopic { reducedTopicPeerLength := 0 reducedTopic := "" + //找到节点数最多的主题 for topic, _ := range srv.topicSubscriber { if topic != connTopic { num := len(srv.pubSubServer.PubSub().ListPeers(topic)) @@ -989,15 +986,38 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in } } if reducedTopic != "" { - for id, p := range peers { - if srv.ConsensusPeers[id] == reducedTopic && !p.rw.is(trustedConn|staticDialedConn) { - log.Debug("Disconnect over limit consensus connection", "peer", p.ID(), "flags", p.rw.flags, "peers", len(peers), "topic", reducedTopic) - p.Disconnect(DiscRequested) + //获取每个节点所拥有的主题的数量 + nodeReferenced := make(map[enode.ID]int) + for _, nodes := range srv.topicSubscriber { + for _, node := range nodes { + if num, ok := nodeReferenced[node]; ok { + nodeReferenced[node] = num + 1 + } else { + nodeReferenced[node] = 1 + } + } + } + var reducedNode enode.ID + var referencedNum int = 10000 + //找到该主题下拥有主题最少的节点 + for _, node := range srv.topicSubscriber[reducedTopic] { + if referencedNum > nodeReferenced[node] { + referencedNum = nodeReferenced[node] + reducedNode = node + } + } + if peer, ok := peers[reducedNode]; ok { + if !peer.rw.is(trustedConn | staticDialedConn) { + log.Debug("Disconnect over limit consensus connection", "peer", peer.ID(), "flags", peer.rw.flags, "peers", len(peers), "topic", reducedTopic) + peer.Disconnect(DiscRequested) break } } + } + } + } srv.topicSubscriberMu.RUnlock() } @@ -1366,15 +1386,17 @@ func (srv *Server) watching() { topicSubscriber := make([]enode.ID, 0) for _, node := range data.Nodes { topicSubscriber = append(topicSubscriber, node) - srv.ConsensusPeers[node] = data.Topic - } - if _, ok := srv.ConsensusPeers[srv.localnode.ID()]; ok { - srv.consensus = true } srv.topicSubscriber[data.Topic] = topicSubscriber + consensusPeers := make(map[enode.ID]struct{}) - for id, _ := range srv.ConsensusPeers { - consensusPeers[id] = struct{}{} + for _, nodes := range srv.topicSubscriber { + for _, node := range nodes { + consensusPeers[node] = struct{}{} + } + } + if _, ok := consensusPeers[srv.localnode.ID()]; ok { + srv.consensus = true } srv.updateConsensusStatus <- consensusPeers log.Trace("Received NewTopicEvent", "consensusPeers", consensusPeers) @@ -1383,14 +1405,12 @@ func (srv *Server) watching() { srv.topicSubscriberMu.Lock() delete(srv.topicSubscriber, data.Topic) consensusPeers := make(map[enode.ID]struct{}) - srv.ConsensusPeers = make(map[enode.ID]string) - for topic, peers := range srv.topicSubscriber { + for _, peers := range srv.topicSubscriber { for _, peer := range peers { - srv.ConsensusPeers[peer] = topic consensusPeers[peer] = struct{}{} } } - if _, ok := srv.ConsensusPeers[srv.localnode.ID()]; !ok { + if _, ok := consensusPeers[srv.localnode.ID()]; !ok { srv.consensus = false } srv.updateConsensusStatus <- consensusPeers @@ -1409,3 +1429,16 @@ func (srv *Server) SetPubSubServer(pss *PubSubServer, cancel context.CancelFunc) srv.pubSubServer = pss srv.cancelPubSubServer = cancel } + +func (srv *Server) getPeersTopics(id enode.ID) []string { + topics := make([]string, 0) + for topic, nodes := range srv.topicSubscriber { + for _, node := range nodes { + if node == id { + topics = append(topics, topic) + break + } + } + } + return topics +} From 47391753a59f00757a8066dcf2d8e32aac0c813f Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 4 Jan 2022 16:44:07 +0800 Subject: [PATCH 200/299] update --- p2p/server.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/p2p/server.go b/p2p/server.go index 1efd305803..c7f1e480a6 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -1004,6 +1004,9 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in if referencedNum > nodeReferenced[node] { referencedNum = nodeReferenced[node] reducedNode = node + if referencedNum == 1 { + break + } } } if peer, ok := peers[reducedNode]; ok { From 5d677170d4e744d84dcc9d614902e4e296e91396 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Tue, 4 Jan 2022 18:03:01 +0800 Subject: [PATCH 201/299] Compatible with previous versions of group consensus --- consensus/cbft/network/handler.go | 98 +++++++++++++++------------- consensus/cbft/network/interface.go | 6 +- consensus/cbft/network/metrics.go | 8 +-- consensus/cbft/network/router.go | 2 +- consensus/cbft/protocols/protocol.go | 75 ++++++++++++--------- 5 files changed, 105 insertions(+), 84 deletions(-) diff --git a/consensus/cbft/network/handler.go b/consensus/cbft/network/handler.go index 8945da8e13..d011a4ac02 100644 --- a/consensus/cbft/network/handler.go +++ b/consensus/cbft/network/handler.go @@ -604,15 +604,17 @@ func (h *EngineManager) handleMsg(p *peer) error { case msg.Code == protocols.GetPrepareVoteMsg: var request protocols.GetPrepareVote - if err := msg.Decode(&request); err == nil { - return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) + if err := msg.Decode(&request); err != nil { + return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) } - var requestV2 protocols.GetPrepareVoteV2 - if err := msg.Decode(&requestV2); err == nil { - return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&requestV2, p.PeerID())) - } else { + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) + + case msg.Code == protocols.PrepareVotesMsg: + var request protocols.PrepareVotes + if err := msg.Decode(&request); err != nil { return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) } + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) case msg.Code == protocols.PrepareBlockHashMsg: var request protocols.PrepareBlockHash @@ -622,18 +624,6 @@ func (h *EngineManager) handleMsg(p *peer) error { p.MarkMessageHash((&request).MsgHash()) return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) - case msg.Code == protocols.PrepareVotesMsg: - var request protocols.PrepareVotes - if err := msg.Decode(&request); err == nil { - return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) - } - var requestV2 protocols.PrepareVotesV2 - if err := msg.Decode(&requestV2); err == nil { - return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&requestV2, p.PeerID())) - } else { - return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) - } - case msg.Code == protocols.QCBlockListMsg: var request protocols.QCBlockList if err := msg.Decode(&request); err != nil { @@ -657,15 +647,17 @@ func (h *EngineManager) handleMsg(p *peer) error { case msg.Code == protocols.GetViewChangeMsg: var request protocols.GetViewChange - if err := msg.Decode(&request); err == nil { - return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) + if err := msg.Decode(&request); err != nil { + return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) } - var requestV2 protocols.GetViewChangeV2 - if err := msg.Decode(&requestV2); err == nil { - return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&requestV2, p.PeerID())) - } else { + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) + + case msg.Code == protocols.ViewChangesMsg: + var request protocols.ViewChanges + if err := msg.Decode(&request); err != nil { return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) } + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) case msg.Code == protocols.PingMsg: var pingTime protocols.Ping @@ -716,17 +708,35 @@ func (h *EngineManager) handleMsg(p *peer) error { return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) } return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) - case msg.Code == protocols.ViewChangesMsg: - var request protocols.ViewChanges - if err := msg.Decode(&request); err == nil { - return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&request, p.PeerID())) + + case msg.Code == protocols.GetPrepareVoteV2Msg: + var requestV2 protocols.GetPrepareVoteV2 + if err := msg.Decode(&requestV2); err != nil { + return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) } + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&requestV2, p.PeerID())) + + case msg.Code == protocols.PrepareVotesV2Msg: + var requestV2 protocols.PrepareVotesV2 + if err := msg.Decode(&requestV2); err != nil { + return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) + } + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&requestV2, p.PeerID())) + + case msg.Code == protocols.GetViewChangeV2Msg: + var requestV2 protocols.GetViewChangeV2 + if err := msg.Decode(&requestV2); err != nil { + return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) + + } + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&requestV2, p.PeerID())) + + case msg.Code == protocols.ViewChangesV2Msg: var requestV2 protocols.ViewChangesV2 - if err := msg.Decode(&requestV2); err == nil { - return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&requestV2, p.PeerID())) - } else { + if err := msg.Decode(&requestV2); err != nil { return types.ErrResp(types.ErrDecode, "%v: %v", msg, err) } + return h.engine.ReceiveSyncMsg(types.NewMsgInfo(&requestV2, p.PeerID())) default: return types.ErrResp(types.ErrInvalidMsgCode, "%v", msg.Code) @@ -818,32 +828,32 @@ func (h *EngineManager) synchronize() { log.Debug("Request missing prepareVote failed", "err", err) break } - log.Debug("Had new prepareVote sync request", "msg", msg.String()) + log.Debug("Had new prepareVote sync request", "msgType", reflect.TypeOf(msg), "msg", msg.String()) // Only broadcasts without forwarding. h.PartBroadcast(msg) - case <-blockNumberTimer.C: - // Sent at random. - syncQCBnFunc() - rd := rand.Intn(5) - if rd == 0 || rd < QCBnMonitorInterval/2 { - rd = (rd + 1) * 2 - } - resetTime := time.Duration(rd) * time.Second - blockNumberTimer.Reset(resetTime) - case <-viewTicker.C: // If the local viewChange has insufficient votes, // the GetViewChange message is sent from the missing node. msg, err := h.engine.MissingViewChangeNodes() if err != nil { - log.Debug("Request missing viewchange failed", "err", err) + log.Debug("Request missing viewChange failed", "err", err) break } - log.Debug("Had new viewchange sync request", "msg", msg.String()) + log.Debug("Had new viewChange sync request", "msgType", reflect.TypeOf(msg), "msg", msg.String()) // Only broadcasts without forwarding. h.PartBroadcast(msg) + case <-blockNumberTimer.C: + // Sent at random. + syncQCBnFunc() + rd := rand.Intn(5) + if rd == 0 || rd < QCBnMonitorInterval/2 { + rd = (rd + 1) * 2 + } + resetTime := time.Duration(rd) * time.Second + blockNumberTimer.Reset(resetTime) + case <-pureBlacklistTicker.C: // Iterate over the blacklist and remove // the nodes that have expired. diff --git a/consensus/cbft/network/interface.go b/consensus/cbft/network/interface.go index 3a243960d3..5ca9075de2 100644 --- a/consensus/cbft/network/interface.go +++ b/consensus/cbft/network/interface.go @@ -53,12 +53,12 @@ type Cbft interface { // Return the highest commit block number of the current node. HighestCommitBlockBn() (uint64, common.Hash) - // Returns the node ID of the missing vote. - MissingViewChangeNodes() (types.Message, error) - // Returns the missing vote. MissingPrepareVote() (types.Message, error) + // Returns the node ID of the missing vote. + MissingViewChangeNodes() (types.Message, error) + // Returns latest status. LatestStatus() *protocols.GetLatestStatus diff --git a/consensus/cbft/network/metrics.go b/consensus/cbft/network/metrics.go index 41d71e772e..f3360556e1 100644 --- a/consensus/cbft/network/metrics.go +++ b/consensus/cbft/network/metrics.go @@ -184,10 +184,10 @@ func (rw *meteredMsgReadWriter) ReadMsg() (p2p.Msg, error) { case msg.Code == protocols.BlockQuorumCertMsg: packets, traffic = reqBlockQuorumCertInPacketsMeter, reqBlockQuorumCertInTrafficMeter - case msg.Code == protocols.GetPrepareVoteMsg: + case msg.Code == protocols.GetPrepareVoteMsg || msg.Code == protocols.GetPrepareVoteV2Msg: packets, traffic = reqGetPrepareVoteInPacketsMeter, reqGetPrepareVoteInTrafficMeter - case msg.Code == protocols.PrepareVotesMsg: + case msg.Code == protocols.PrepareVotesMsg || msg.Code == protocols.PrepareVotesV2Msg: packets, traffic = reqPrepareVotesInPacketsMeter, reqPrepareVotesInTrafficMeter case msg.Code == protocols.GetQCBlockListMsg: @@ -248,7 +248,7 @@ func (rw *meteredMsgReadWriter) WriteMsg(msg p2p.Msg) error { packets, traffic = propPrepareBlockHashOutPacketsMeter, propPrepareBlockHashOutTrafficMeter common.PrepareBlockHashEgressTrafficMeter.Mark(int64(msg.Size)) - case msg.Code == protocols.GetPrepareVoteMsg: + case msg.Code == protocols.GetPrepareVoteMsg || msg.Code == protocols.GetPrepareVoteV2Msg: packets, traffic = reqGetPrepareBlockOutPacketsMeter, reqGetPrepareVoteOutTrafficMeter common.GetPrepareVoteEgressTrafficMeter.Mark(int64(msg.Size)) @@ -260,7 +260,7 @@ func (rw *meteredMsgReadWriter) WriteMsg(msg p2p.Msg) error { packets, traffic = reqBlockQuorumCertOutPacketsMeter, reqBlockQuorumCertOutTrafficMeter common.BlockQuorumCertEgressTrafficMeter.Mark(int64(msg.Size)) - case msg.Code == protocols.PrepareVotesMsg: + case msg.Code == protocols.PrepareVotesMsg || msg.Code == protocols.PrepareVotesV2Msg: packets, traffic = reqPrepareVotesOutPacketsMeter, reqPrepareVotesOutTrafficMeter common.PrepareVotesEgressTrafficMeter.Mark(int64(msg.Size)) diff --git a/consensus/cbft/network/router.go b/consensus/cbft/network/router.go index d66340c053..ad64376da4 100644 --- a/consensus/cbft/network/router.go +++ b/consensus/cbft/network/router.go @@ -157,7 +157,7 @@ func (r *router) filteredPeers(msgType uint64, condition common.Hash) ([]*peer, protocols.BlockQuorumCertMsg: return r.kMixingRandomNodes(condition, r.filter) case protocols.PrepareBlockHashMsg, protocols.GetLatestStatusMsg, - protocols.GetViewChangeMsg, protocols.GetPrepareVoteMsg, + protocols.GetViewChangeMsg, protocols.GetPrepareVoteMsg, protocols.GetViewChangeV2Msg, protocols.GetPrepareVoteV2Msg, protocols.GetPrepareBlockMsg: return r.kMixingRandomNodes(condition, nil) } diff --git a/consensus/cbft/protocols/protocol.go b/consensus/cbft/protocols/protocol.go index 5fb6cf60e3..954c8a584e 100644 --- a/consensus/cbft/protocols/protocol.go +++ b/consensus/cbft/protocols/protocol.go @@ -37,27 +37,33 @@ const CbftProtocolMaxMsgSize = 10 * 1024 * 1024 const DefaultAvgLatency = 100 const ( - CBFTStatusMsg = 0x00 // Protocol messages belonging to cbft - PrepareBlockMsg = 0x01 - PrepareVoteMsg = 0x02 - ViewChangeMsg = 0x03 - GetPrepareBlockMsg = 0x04 - GetBlockQuorumCertMsg = 0x05 - BlockQuorumCertMsg = 0x06 - GetPrepareVoteMsg = 0x07 - PrepareVotesMsg = 0x08 - GetQCBlockListMsg = 0x09 - QCBlockListMsg = 0x0a - GetLatestStatusMsg = 0x0b - LatestStatusMsg = 0x0c - PrepareBlockHashMsg = 0x0d - GetViewChangeMsg = 0x0e - PingMsg = 0x0f - PongMsg = 0x10 - ViewChangeQuorumCertMsg = 0x11 - ViewChangesMsg = 0x12 + CBFTStatusMsg = 0x00 // Protocol messages belonging to cbft + PrepareBlockMsg = 0x01 + PrepareVoteMsg = 0x02 + ViewChangeMsg = 0x03 + GetPrepareBlockMsg = 0x04 + GetBlockQuorumCertMsg = 0x05 + BlockQuorumCertMsg = 0x06 + GetPrepareVoteMsg = 0x07 + PrepareVotesMsg = 0x08 + GetQCBlockListMsg = 0x09 + QCBlockListMsg = 0x0a + GetLatestStatusMsg = 0x0b + LatestStatusMsg = 0x0c + PrepareBlockHashMsg = 0x0d + GetViewChangeMsg = 0x0e + PingMsg = 0x0f + PongMsg = 0x10 + ViewChangeQuorumCertMsg = 0x11 + ViewChangesMsg = 0x12 + + // for rand-grouped-consensus RGBlockQuorumCertMsg = 0x13 RGViewChangeQuorumCertMsg = 0x14 + GetPrepareVoteV2Msg = 0x15 + PrepareVotesV2Msg = 0x16 + GetViewChangeV2Msg = 0x17 + ViewChangesV2Msg = 0x18 ) // A is used to convert specific message types according to the message body. @@ -81,11 +87,11 @@ func MessageType(msg interface{}) uint64 { return BlockQuorumCertMsg case *GetQCBlockList: return GetQCBlockListMsg - case *GetPrepareVote, *GetPrepareVoteV2: + case *GetPrepareVote: return GetPrepareVoteMsg case *PrepareBlockHash: return PrepareBlockHashMsg - case *PrepareVotes, *PrepareVotesV2: + case *PrepareVotes: return PrepareVotesMsg case *QCBlockList: return QCBlockListMsg @@ -93,7 +99,7 @@ func MessageType(msg interface{}) uint64 { return GetLatestStatusMsg case *LatestStatus: return LatestStatusMsg - case *GetViewChange, *GetViewChangeV2: + case *GetViewChange: return GetViewChangeMsg case *Ping: return PingMsg @@ -101,12 +107,20 @@ func MessageType(msg interface{}) uint64 { return PongMsg case *ViewChangeQuorumCert: return ViewChangeQuorumCertMsg - case *ViewChanges, *ViewChangesV2: + case *ViewChanges: return ViewChangesMsg case *RGBlockQuorumCert: return RGBlockQuorumCertMsg case *RGViewChangeQuorumCert: return RGViewChangeQuorumCertMsg + case *GetPrepareVoteV2: + return GetPrepareVoteV2Msg + case *PrepareVotesV2: + return PrepareVotesV2Msg + case *GetViewChangeV2: + return GetViewChangeV2Msg + case *ViewChangesV2: + return ViewChangesV2Msg default: } panic(fmt.Sprintf("unknown message type [%v}", reflect.TypeOf(msg))) @@ -418,10 +432,9 @@ func (v ViewChangesV2) MsgHash() common.Hash { var mv common.Hash if len(v.VCs) != 0 { epoch, viewNumber := v.VCs[0].Epoch, v.VCs[0].ViewNumber - mv = utils.BuildHash(ViewChangesMsg, utils.MergeBytes(common.Uint64ToBytes(epoch), - common.Uint64ToBytes(viewNumber))) + mv = utils.BuildHash(ViewChangesV2Msg, utils.MergeBytes(common.Uint64ToBytes(epoch), common.Uint64ToBytes(viewNumber))) } else { - mv = utils.BuildHash(ViewChangesMsg, common.Hash{}.Bytes()) + mv = utils.BuildHash(ViewChangesV2Msg, common.Hash{}.Bytes()) } v.messageHash.Store(mv) return mv @@ -608,7 +621,7 @@ func (s *GetPrepareVoteV2) MsgHash() common.Hash { if mhash := s.messageHash.Load(); mhash != nil { return mhash.(common.Hash) } - v := utils.BuildHash(GetPrepareVoteMsg, utils.MergeBytes(common.Uint64ToBytes(s.Epoch), common.Uint64ToBytes(s.ViewNumber), + v := utils.BuildHash(GetPrepareVoteV2Msg, utils.MergeBytes(common.Uint64ToBytes(s.Epoch), common.Uint64ToBytes(s.ViewNumber), common.Uint32ToBytes(s.BlockIndex))) s.messageHash.Store(v) return v @@ -662,7 +675,7 @@ func (s *PrepareVotesV2) MsgHash() common.Hash { if mhash := s.messageHash.Load(); mhash != nil { return mhash.(common.Hash) } - v := utils.BuildHash(PrepareVotesMsg, utils.MergeBytes(common.Uint64ToBytes(s.Epoch), common.Uint64ToBytes(s.ViewNumber), common.Uint32ToBytes(s.BlockIndex))) + v := utils.BuildHash(PrepareVotesV2Msg, utils.MergeBytes(common.Uint64ToBytes(s.Epoch), common.Uint64ToBytes(s.ViewNumber), common.Uint32ToBytes(s.BlockIndex))) s.messageHash.Store(v) return v } @@ -846,8 +859,7 @@ func (s *GetViewChange) MsgHash() common.Hash { if mhash := s.messageHash.Load(); mhash != nil { return mhash.(common.Hash) } - v := utils.BuildHash(GetViewChangeMsg, - utils.MergeBytes(common.Uint64ToBytes(s.Epoch), common.Uint64ToBytes(s.ViewNumber))) + v := utils.BuildHash(GetViewChangeMsg, utils.MergeBytes(common.Uint64ToBytes(s.Epoch), common.Uint64ToBytes(s.ViewNumber))) s.messageHash.Store(v) return v } @@ -872,8 +884,7 @@ func (s *GetViewChangeV2) MsgHash() common.Hash { if mhash := s.messageHash.Load(); mhash != nil { return mhash.(common.Hash) } - v := utils.BuildHash(GetViewChangeMsg, - utils.MergeBytes(common.Uint64ToBytes(s.Epoch), common.Uint64ToBytes(s.ViewNumber))) + v := utils.BuildHash(GetViewChangeV2Msg, utils.MergeBytes(common.Uint64ToBytes(s.Epoch), common.Uint64ToBytes(s.ViewNumber))) s.messageHash.Store(v) return v } From e3fd28116f186dc215b22c3f7efbbc7b4a448687 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 5 Jan 2022 16:17:56 +0800 Subject: [PATCH 202/299] fix remove consensus peer --- p2p/discover.go | 2 +- p2p/server.go | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/p2p/discover.go b/p2p/discover.go index 475bc70325..38ddd6753c 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -91,7 +91,7 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes [ currNum = len(srv.pubSubServer.PubSub().ListPeers(topic)) try++ } - log.Trace(" Searching network for peers subscribed to the topic done.", "topic", topic, "peers", currNum, "try", try) + log.Trace("Searching network for peers subscribed to the topic done.", "topic", topic, "peers", currNum, "try", try) return nil } diff --git a/p2p/server.go b/p2p/server.go index c7f1e480a6..187baddb19 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -959,9 +959,10 @@ running: func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount int, c *conn) error { // Disconnect over limit non-consensus node. // log.Trace("postHandshakeChecks server status", "consensus", srv.consensus, "peers", len(peers), "MaxPeers", srv.MaxPeers, "consensusDialedConn", c.is(consensusDialedConn), "MaxConsensusPeers", srv.MaxConsensusPeers, "numConsensusPeer", srv.numConsensusPeer(peers)) - if srv.consensus && len(peers) >= srv.MaxPeers && c.is(consensusDialedConn) && srv.numConsensusPeer(peers) < srv.MaxConsensusPeers { + numConsensusPeer := srv.numConsensusPeer(peers) + + if srv.consensus && len(peers) >= srv.MaxPeers && c.is(consensusDialedConn) && numConsensusPeer < srv.MaxConsensusPeers { for _, p := range peers { - // log.Trace("postHandshakeChecks peer status", "peer", p.ID(), "status", p.rw.String()) if p.rw.is(inboundConn|dynDialedConn) && !p.rw.is(trustedConn|staticDialedConn|consensusDialedConn) { log.Debug("Disconnect over limit connection", "peer", p.ID(), "flags", p.rw.flags, "peers", len(peers)) p.Disconnect(DiscRequested) @@ -969,7 +970,8 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in } } } - if srv.consensus && c.is(consensusDialedConn) && srv.numConsensusPeer(peers) >= srv.MaxConsensusPeers { + disconnectConsensus := 0 + if srv.consensus && c.is(consensusDialedConn) && numConsensusPeer >= srv.MaxConsensusPeers { srv.topicSubscriberMu.RLock() for _, connTopic := range srv.getPeersTopics(c.node.ID()) { if len(srv.pubSubServer.PubSub().ListPeers(connTopic)) < srv.MinimumPeersPerTopic { @@ -1011,22 +1013,20 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in } if peer, ok := peers[reducedNode]; ok { if !peer.rw.is(trustedConn | staticDialedConn) { - log.Debug("Disconnect over limit consensus connection", "peer", peer.ID(), "flags", peer.rw.flags, "peers", len(peers), "topic", reducedTopic) + log.Debug("Disconnect over limit consensus connection", "peer", peer.ID(), "flags", peer.rw.flags, "peers", len(peers), "topic", reducedTopic, "referencedNum", referencedNum, "maxConsensusPeers", srv.MaxConsensusPeers) peer.Disconnect(DiscRequested) + disconnectConsensus++ break } } - } - } - } srv.topicSubscriberMu.RUnlock() } switch { - case c.is(consensusDialedConn) && srv.numConsensusPeer(peers) >= srv.MaxConsensusPeers: + case c.is(consensusDialedConn) && numConsensusPeer-disconnectConsensus >= srv.MaxConsensusPeers: return DiscTooManyConsensusPeers case !srv.consensus && c.is(consensusDialedConn) && len(peers) >= srv.MaxPeers: return DiscTooManyPeers @@ -1439,7 +1439,6 @@ func (srv *Server) getPeersTopics(id enode.ID) []string { for _, node := range nodes { if node == id { topics = append(topics, topic) - break } } } From 85573d79ef8645ec18c18a8617fc428aaaf652ce Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 5 Jan 2022 17:13:45 +0800 Subject: [PATCH 203/299] fix --- p2p/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/p2p/server.go b/p2p/server.go index 187baddb19..7f4423e646 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -1439,6 +1439,7 @@ func (srv *Server) getPeersTopics(id enode.ID) []string { for _, node := range nodes { if node == id { topics = append(topics, topic) + break } } } From f99ed57615edb8ce9834655eb5cfcc4365537a57 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 5 Jan 2022 17:16:46 +0800 Subject: [PATCH 204/299] add log --- p2p/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/p2p/server.go b/p2p/server.go index 7f4423e646..af7d303218 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -1027,6 +1027,7 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in switch { case c.is(consensusDialedConn) && numConsensusPeer-disconnectConsensus >= srv.MaxConsensusPeers: + log.Trace("fail to dial for connect Too many consensus peers", "peers", numConsensusPeer, "disconnectConsensus", disconnectConsensus, "maxConsensusPeers", srv.MaxConsensusPeers) return DiscTooManyConsensusPeers case !srv.consensus && c.is(consensusDialedConn) && len(peers) >= srv.MaxPeers: return DiscTooManyPeers From f7c0117b17540bb8c2ab3f2e3205b0d37a837db8 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Wed, 5 Jan 2022 17:40:14 +0800 Subject: [PATCH 205/299] fix bug --- consensus/cbft/sync_process.go | 12 ++++++------ consensus/cbft/validator/validator.go | 2 +- core/cbfttypes/type.go | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/consensus/cbft/sync_process.go b/consensus/cbft/sync_process.go index 644cd39a53..3adc686f57 100644 --- a/consensus/cbft/sync_process.go +++ b/consensus/cbft/sync_process.go @@ -399,7 +399,7 @@ func (cbft *Cbft) OnGetPrepareVoteV2(id string, msg *protocols.GetPrepareVoteV2) if len(votes) > 0 || len(RGBlockQuorumCerts) > 0 { cbft.network.Send(id, &protocols.PrepareVotesV2{Epoch: msg.Epoch, ViewNumber: msg.ViewNumber, BlockIndex: msg.BlockIndex, Votes: votes, RGBlockQuorumCerts: RGBlockQuorumCerts}) - cbft.log.Debug("Send PrepareVotes", "peer", id, "blockIndex", msg.BlockIndex, "votes length", len(votes), "RGBlockQuorumCerts length", len(RGBlockQuorumCerts)) + cbft.log.Debug("Send PrepareVotesV2", "peer", id, "blockIndex", msg.BlockIndex, "votes length", len(votes), "RGBlockQuorumCerts length", len(RGBlockQuorumCerts)) } } } @@ -410,7 +410,7 @@ func (cbft *Cbft) OnGetPrepareVoteV2(id string, msg *protocols.GetPrepareVoteV2) // of the GetPrepareVote message. It will synchronously return a // PrepareVotes message to the sender. func (cbft *Cbft) OnGetPrepareVote(id string, msg *protocols.GetPrepareVote) error { - cbft.log.Debug("Received message on OnGetPrepareVoteV2", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) + cbft.log.Debug("Received message on OnGetPrepareVote", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) if msg.Epoch == cbft.state.Epoch() && msg.ViewNumber == cbft.state.ViewNumber() { // If the block has already QC, that response QC instead of votes. // Avoid the sender spent a lot of time to verifies PrepareVote msg. @@ -465,7 +465,7 @@ func (cbft *Cbft) OnPrepareVotes(id string, msg *protocols.PrepareVotes) error { // OnPrepareVotes handling response from GetPrepareVote response. func (cbft *Cbft) OnPrepareVotesV2(id string, msg *protocols.PrepareVotesV2) error { - cbft.log.Debug("Received message on OnPrepareVotes", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) + cbft.log.Debug("Received message on OnPrepareVotesV2", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) alreadyQC := func(hash common.Hash, number uint64) bool { if _, qc := cbft.blockTree.FindBlockAndQC(hash, number); qc != nil { return true @@ -686,7 +686,7 @@ func (cbft *Cbft) OnGetViewChangeV2(id string, msg *protocols.GetViewChangeV2) e if len(viewChanges) > 0 || len(RGViewChangeQuorumCerts) > 0 { cbft.network.Send(id, &protocols.ViewChangesV2{VCs: viewChanges, RGViewChangeQuorumCerts: RGViewChangeQuorumCerts}) - cbft.log.Debug("Send ViewChanges", "peer", id, "viewChanges length", len(viewChanges), "RGViewChangeQuorumCerts length", len(RGViewChangeQuorumCerts)) + cbft.log.Debug("Send ViewChangesV2", "peer", id, "viewChanges length", len(viewChanges), "RGViewChangeQuorumCerts length", len(RGViewChangeQuorumCerts)) } return nil } @@ -731,7 +731,7 @@ func (cbft *Cbft) OnGetViewChangeV2(id string, msg *protocols.GetViewChangeV2) e // The Epoch and viewNumber of viewChange must be consistent // with the state of the current node. func (cbft *Cbft) OnGetViewChange(id string, msg *protocols.GetViewChange) error { - cbft.log.Debug("Received message on OnGetViewChangeV2", "from", id, "msgHash", msg.MsgHash(), "message", msg.String(), "local", cbft.state.ViewString()) + cbft.log.Debug("Received message on OnGetViewChange", "from", id, "msgHash", msg.MsgHash(), "message", msg.String(), "local", cbft.state.ViewString()) localEpoch, localViewNumber := cbft.state.Epoch(), cbft.state.ViewNumber() @@ -867,7 +867,7 @@ func (cbft *Cbft) OnViewChanges(id string, msg *protocols.ViewChanges) error { // OnViewChanges handles the message type of ViewChangesMsg. func (cbft *Cbft) OnViewChangesV2(id string, msg *protocols.ViewChangesV2) error { - cbft.log.Debug("Received message on OnViewChanges", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) + cbft.log.Debug("Received message on OnViewChangesV2", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) for _, rgqc := range msg.RGViewChangeQuorumCerts { if !cbft.network.ContainsHistoryMessageHash(rgqc.MsgHash()) { diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index dc570fff71..b09932ebf2 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -888,7 +888,7 @@ func (vp *ValidatorPool) GetGroupByValidatorID(epoch uint64, nodeID enode.ID) (u validators = vp.currentValidators } gvs, err := validators.GetGroupValidators(nodeID) - if nil != err { + if nil != err || gvs == nil { return 0, 0, err } unitID, err := validators.UnitID(nodeID) diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index f8add3d165..2498d0f2ae 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -323,7 +323,7 @@ func (vs *Validators) UnitID(nodeID enode.ID) (uint32, error) { } gvs, err := vs.GetGroupValidators(nodeID) - if err != nil { + if err != nil || gvs == nil { return 0, err } return gvs.GetUnitID(nodeID) From 4aef51bbb3d29b01ee1c86ae554eff35c25cec50 Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 6 Jan 2022 14:42:36 +0800 Subject: [PATCH 206/299] change algorithm of group from index to distance --- .../cbft/evidence/evidence_common_test.go | 4 +- consensus/cbft/validator/validator.go | 25 +++-- consensus/cbft/validator/validator_test.go | 23 +--- core/cbfttypes/type.go | 104 +++++++++++------- x/plugin/staking_plugin.go | 5 +- 5 files changed, 82 insertions(+), 79 deletions(-) diff --git a/consensus/cbft/evidence/evidence_common_test.go b/consensus/cbft/evidence/evidence_common_test.go index 20aaad8a67..6c99d1322d 100644 --- a/consensus/cbft/evidence/evidence_common_test.go +++ b/consensus/cbft/evidence/evidence_common_test.go @@ -66,13 +66,15 @@ func GenerateKeys(num int) ([]*ecdsa.PrivateKey, []*bls.SecretKey) { func createValidateNode(num int) ([]*cbfttypes.ValidateNode, []*bls.SecretKey) { pk, sk := GenerateKeys(num) nodes := make([]*cbfttypes.ValidateNode, num) + for i := 0; i < num; i++ { + id := enode.PubkeyToIDV4(&pk[i].PublicKey) nodes[i] = &cbfttypes.ValidateNode{ Index: uint32(i), Address: crypto.PubkeyToNodeAddress(pk[i].PublicKey), PubKey: &pk[i].PublicKey, - NodeID: enode.PubkeyToIDV4(&pk[i].PublicKey), + NodeID: id, } nodes[i].BlsPubKey = sk[i].GetPublicKey() diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index dc570fff71..6e0fcd3886 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -270,6 +270,7 @@ func (ia *InnerAgency) GetValidators(blockHash common.Hash, blockNumber uint64) } var validators cbfttypes.Validators validators.Nodes = make(cbfttypes.ValidateNodeMap, len(vds.ValidateNodes)) + for _, node := range vds.ValidateNodes { pubkey, _ := node.NodeID.Pubkey() blsPubKey := node.BlsPubKey @@ -278,7 +279,7 @@ func (ia *InnerAgency) GetValidators(blockHash common.Hash, blockNumber uint64) Index: uint32(node.Index), Address: node.Address, PubKey: pubkey, - NodeID: node.NodeID.ID(), + NodeID: id, BlsPubKey: &blsPubKey, } } @@ -524,21 +525,21 @@ func (vp *ValidatorPool) dealWithOldVersionEvents(epoch uint64, eventMux *event. // in the consensus stages. Also we are not needed // to keep connect with old validators. if isValidatorAfter { - for _, n := range vp.currentValidators.SortedNodes { + for _, n := range vp.currentValidators.SortedValidators.SortedNodes { if node, _ := vp.prevValidators.FindNodeByID(n.NodeID); node == nil { eventMux.Post(cbfttypes.AddValidatorEvent{Node: enode.NewV4(node.PubKey, nil, 0, 0)}) - log.Trace("Post AddValidatorEvent", "node", node.String()) + log.Trace("Post AddValidatorEvent", "node", n.String()) } } - for _, n := range vp.prevValidators.SortedNodes { + for _, n := range vp.prevValidators.SortedValidators.SortedNodes { if node, _ := vp.currentValidators.FindNodeByID(n.NodeID); node == nil { eventMux.Post(cbfttypes.RemoveValidatorEvent{Node: enode.NewV4(node.PubKey, nil, 0, 0)}) - log.Trace("Post RemoveValidatorEvent", "node", node.String()) + log.Trace("Post RemoveValidatorEvent", "node", n.String()) } } } else { - for _, node := range vp.prevValidators.SortedNodes { + for _, node := range vp.prevValidators.SortedValidators.SortedNodes { eventMux.Post(cbfttypes.RemoveValidatorEvent{Node: enode.NewV4(node.PubKey, nil, 0, 0)}) log.Trace("Post RemoveValidatorEvent", "nodeID", node.String()) } @@ -549,7 +550,7 @@ func (vp *ValidatorPool) dealWithOldVersionEvents(epoch uint64, eventMux *event. // consensus peers is because we need to keep connecting // with other validators in the consensus stages. if isValidatorAfter { - for _, node := range vp.currentValidators.SortedNodes { + for _, node := range vp.currentValidators.SortedValidators.SortedNodes { eventMux.Post(cbfttypes.AddValidatorEvent{Node: enode.NewV4(node.PubKey, nil, 0, 0)}) log.Trace("Post AddValidatorEvent", "nodeID", node.String()) } @@ -596,20 +597,20 @@ func (vp *ValidatorPool) GetValidatorByIndex(epoch uint64, index uint32) (*cbftt func (vp *ValidatorPool) getValidatorByIndex(epoch uint64, index uint32) (*cbfttypes.ValidateNode, error) { if vp.epochToBlockNumber(epoch) <= vp.switchPoint { - return vp.prevValidators.FindNodeByIndex(int(index)) + return vp.prevValidators.FindNodeByIndex(index) } - return vp.currentValidators.FindNodeByIndex(int(index)) + return vp.currentValidators.FindNodeByIndex(index) } // GetNodeIDByIndex get the node id by index. -func (vp *ValidatorPool) GetNodeIDByIndex(epoch uint64, index int) enode.ID { +func (vp *ValidatorPool) GetNodeIDByIndex(epoch uint64, index uint32) enode.ID { vp.lock.RLock() defer vp.lock.RUnlock() return vp.getNodeIDByIndex(epoch, index) } -func (vp *ValidatorPool) getNodeIDByIndex(epoch uint64, index int) enode.ID { +func (vp *ValidatorPool) getNodeIDByIndex(epoch uint64, index uint32) enode.ID { if vp.epochToBlockNumber(epoch) <= vp.switchPoint { return vp.prevValidators.NodeID(index) } @@ -871,7 +872,7 @@ func (vp *ValidatorPool) GetCoordinatorIndexesByGroupID(epoch uint64, groupID ui } if groupID >= uint32(len(validators.GroupNodes)) { - return nil, fmt.Errorf("GetCoordinatorIndexesByGroupID: wrong groupid[%d]!", groupID) + return nil, fmt.Errorf("GetCoordinatorIndexesByGroupID: wrong groupid[%d]", groupID) } return validators.GroupNodes[groupID].Units, nil } diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index c61088c310..4a79bce557 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -337,6 +337,7 @@ func TestInnerAgency(t *testing.T) { assert.Equal(t, *vds, *validators) assert.True(t, blockchain.Genesis() != nil) + validators.Sort() newVds, err := agency.GetValidators(common.ZeroHash, 81) assert.True(t, err == nil) assert.True(t, newVds.Len() == 4) @@ -620,17 +621,10 @@ func TestValidatorGrouped(t *testing.T) { vs := newValidators(nodes, 0) vs.Grouped() assert.Equal(t, 4, len(vs.GroupNodes)) - assert.Equal(t, uint32(74), vs.GroupNodes[2].Nodes[24].Index) assert.Equal(t, 5, len(vs.GroupNodes[2].Units)) - assert.Equal(t, uint32(74), vs.GroupNodes[2].Units[4][4]) - assert.Equal(t, 25, len(vs.GroupNodes[0].Nodes)) assert.Equal(t, 25, len(vs.GroupNodes[2].Nodes)) assert.Equal(t, 25, len(vs.GroupNodes[3].Nodes)) - assert.Equal(t, uint32(79), vs.GroupNodes[3].Units[0][4]) - - assert.Equal(t, 5, len(vs.GroupNodes[2].Units)) - assert.Equal(t, uint32(72), vs.GroupNodes[2].Units[4][2]) } func TestGetGroupID(t *testing.T) { @@ -643,21 +637,6 @@ func TestGetGroupID(t *testing.T) { assert.Equal(t, uint32(0), grpID) } -func TestGetUnitID(t *testing.T) { - bls.Init(bls.BLS12_381) - nodes := newTestNodeByNum(100) - agency := newTestInnerAgency(nodes) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), true, new(event.TypeMux)) - - gvs, _ := vp.currentValidators.GetGroupValidators(nodes[50].Node.ID()) - unitID, _ := gvs.GetUnitID(nodes[50].Node.ID()) - assert.Equal(t, uint32(0), unitID) - - gvs, _ = vp.currentValidators.GetGroupValidators(nodes[55].Node.ID()) - unitID, _ = gvs.GetUnitID(nodes[55].Node.ID()) - assert.Equal(t, uint32(1), unitID) -} - func TestUpdate(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNodeByNum(100) diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index f8add3d165..42444ccc1b 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -97,11 +97,29 @@ type ValidateNode struct { type ValidateNodeMap map[enode.ID]*ValidateNode -type SortedIndexValidatorNode []*ValidateNode - -func (sv SortedIndexValidatorNode) Len() int { return len(sv) } -func (sv SortedIndexValidatorNode) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] } -func (sv SortedIndexValidatorNode) Less(i, j int) bool { return sv[i].Index < sv[j].Index } +type SortedValidatorNodes struct { + target enode.ID + sorted bool + SortedNodes []*ValidateNode +} + +func (sdv SortedValidatorNodes) Len() int { return len(sdv.SortedNodes) } +func (sdv SortedValidatorNodes) Swap(i, j int) { + sdv.SortedNodes[i], sdv.SortedNodes[j] = sdv.SortedNodes[j], sdv.SortedNodes[i] +} +func (sdv SortedValidatorNodes) Less(i, j int) bool { + a, b := sdv.SortedNodes[i], sdv.SortedNodes[j] + for k := range sdv.target { + da := a.NodeID[k] ^ sdv.target[k] + db := b.NodeID[k] ^ sdv.target[k] + if da > db { + return true + } else if da < db { + return false + } + } + return a.Index < b.Index +} type GroupCoordinate struct { groupID uint32 @@ -124,10 +142,10 @@ type Validators struct { Nodes ValidateNodeMap `json:"validateNodes"` ValidBlockNumber uint64 `json:"validateBlockNumber"` - // Sorting based on node index - SortedNodes SortedIndexValidatorNode `json:"sortedNodes"` + // Sorting based on distance + SortedValidators SortedValidatorNodes `json:"sortedNodes"` - //// Sorting based on node index + //// Sorting based on node distance // Node grouping info GroupNodes []*GroupValidators `json:"groupNodes"` } @@ -190,31 +208,32 @@ func (vs *Validators) GetValidatorIndexes(groupid uint32) ([]uint32, error) { } func (vs *Validators) NodeListByIndexes(indexes []uint32) ([]*ValidateNode, error) { - if len(vs.SortedNodes) == 0 { - vs.Sort() - } l := make([]*ValidateNode, 0) for _, index := range indexes { - if int(index) >= len(vs.SortedNodes) { + if int(index) >= len(vs.Nodes) { return nil, errors.New("invalid index") } - l = append(l, vs.SortedNodes[int(index)]) + node, err := vs.FindNodeByIndex(index) + if err != nil { + return nil, err + } + l = append(l, node) } return l, nil } func (vs *Validators) NodeListByBitArray(vSet *utils.BitArray) ([]*ValidateNode, error) { - if len(vs.SortedNodes) == 0 { + if !vs.SortedValidators.sorted { vs.Sort() } l := make([]*ValidateNode, 0) for index := uint32(0); index < vSet.Size(); index++ { if vSet.GetIndex(index) { - if int(index) >= len(vs.SortedNodes) { + if int(index) >= len(vs.SortedValidators.SortedNodes) { return nil, errors.New("invalid index") } - l = append(l, vs.SortedNodes[int(index)]) + l = append(l, vs.SortedValidators.SortedNodes[int(index)]) } } return l, nil @@ -228,15 +247,13 @@ func (vs *Validators) FindNodeByID(id enode.ID) (*ValidateNode, error) { return nil, errors.New("not found the node") } -func (vs *Validators) FindNodeByIndex(index int) (*ValidateNode, error) { - if len(vs.SortedNodes) == 0 { - vs.Sort() - } - if index >= len(vs.SortedNodes) { - return nil, errors.New("not found the specified validator") - } else { - return vs.SortedNodes[index], nil +func (vs *Validators) FindNodeByIndex(index uint32) (*ValidateNode, error) { + for _, node := range vs.Nodes { + if index == node.Index { + return node, nil + } } + return nil, errors.New("not found the specified validator") } func (vs *Validators) FindNodeByAddress(addr common.NodeAddress) (*ValidateNode, error) { @@ -248,14 +265,11 @@ func (vs *Validators) FindNodeByAddress(addr common.NodeAddress) (*ValidateNode, return nil, errors.New("invalid address") } -func (vs *Validators) NodeID(idx int) enode.ID { - if len(vs.SortedNodes) == 0 { - vs.Sort() +func (vs *Validators) NodeID(idx uint32) enode.ID { + if node, err := vs.FindNodeByIndex(idx); err == nil { + return node.NodeID } - if idx >= vs.SortedNodes.Len() { - return enode.ID{} - } - return vs.SortedNodes[idx].NodeID + return enode.ID{} } func (vs *Validators) Index(nodeID enode.ID) (uint32, error) { @@ -285,14 +299,22 @@ func (vs *Validators) Equal(rsh *Validators) bool { } func (vs *Validators) Sort() { - for _, node := range vs.Nodes { - vs.SortedNodes = append(vs.SortedNodes, node) + if targetNode, err := vs.FindNodeByIndex(0); err == nil { + vs.SortedValidators = SortedValidatorNodes{ + target: targetNode.NodeID, + sorted: false, + SortedNodes: make([]*ValidateNode, 0), + } + for _, node := range vs.Nodes { + vs.SortedValidators.SortedNodes = append(vs.SortedValidators.SortedNodes, node) + } + sort.Sort(vs.SortedValidators) + vs.SortedValidators.sorted = true } - sort.Sort(vs.SortedNodes) } func (vs *Validators) GetGroupValidators(nodeID enode.ID) (*GroupValidators, error) { - if len(vs.SortedNodes) == 0 { + if !vs.SortedValidators.sorted { vs.Sort() } @@ -313,7 +335,7 @@ func (vs *Validators) GetGroupValidators(nodeID enode.ID) (*GroupValidators, err } func (vs *Validators) UnitID(nodeID enode.ID) (uint32, error) { - if len(vs.SortedNodes) == 0 { + if !vs.SortedValidators.sorted { vs.Sort() } @@ -376,14 +398,14 @@ func (gvs *GroupValidators) NodeList() []enode.ID { // eg: [validatorCount,groupValidatorsLimit]= // [50,25] = 25,25;[43,25] = 22,21; [101,25] = 21,20,20,20,20 func (vs *Validators) Grouped() error { - // sort nodes by index - if len(vs.SortedNodes) == 0 { + // sort SortedValidators by distance + if !vs.SortedValidators.sorted { vs.Sort() } - if uint32(len(vs.SortedNodes)) <= xcom.MaxGroupValidators() { + if uint32(len(vs.SortedValidators.SortedNodes)) <= xcom.MaxGroupValidators() { return errors.New("no need grouped") } - validatorCount := uint32(vs.SortedNodes.Len()) + validatorCount := uint32(vs.SortedValidators.Len()) groupNum := validatorCount / xcom.MaxGroupValidators() mod := validatorCount % xcom.MaxGroupValidators() if mod > 0 { @@ -407,7 +429,7 @@ func (vs *Validators) Grouped() error { end = validatorCount } groupValidators := new(GroupValidators) - groupValidators.Nodes = vs.SortedNodes[begin:end] + groupValidators.Nodes = vs.SortedValidators.SortedNodes[begin:end] groupValidators.groupID = i vs.GroupNodes[i] = groupValidators } diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index aac7cb15bb..ab4099d8b7 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -2748,17 +2748,16 @@ label: func buildCbftValidators(start uint64, arr staking.ValidatorQueue) *cbfttypes.Validators { valMap := make(cbfttypes.ValidateNodeMap, len(arr)) - for i, v := range arr { - pubKey, _ := v.NodeId.Pubkey() blsPk, _ := v.BlsPubKey.ParseBlsPubKey() + id := enode.PubkeyToIDV4(pubKey) vn := &cbfttypes.ValidateNode{ Index: uint32(i), Address: v.NodeAddress, PubKey: pubKey, - NodeID: enode.PubkeyToIDV4(pubKey), + NodeID: id, BlsPubKey: blsPk, } From 9b971988a96f64fa73d428ffde86f77a50c8169a Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 6 Jan 2022 15:43:32 +0800 Subject: [PATCH 207/299] fix panic bug --- consensus/cbft/validator/validator.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 6e0fcd3886..9cd6780d38 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -527,14 +527,14 @@ func (vp *ValidatorPool) dealWithOldVersionEvents(epoch uint64, eventMux *event. if isValidatorAfter { for _, n := range vp.currentValidators.SortedValidators.SortedNodes { if node, _ := vp.prevValidators.FindNodeByID(n.NodeID); node == nil { - eventMux.Post(cbfttypes.AddValidatorEvent{Node: enode.NewV4(node.PubKey, nil, 0, 0)}) + eventMux.Post(cbfttypes.AddValidatorEvent{Node: enode.NewV4(n.PubKey, nil, 0, 0)}) log.Trace("Post AddValidatorEvent", "node", n.String()) } } for _, n := range vp.prevValidators.SortedValidators.SortedNodes { if node, _ := vp.currentValidators.FindNodeByID(n.NodeID); node == nil { - eventMux.Post(cbfttypes.RemoveValidatorEvent{Node: enode.NewV4(node.PubKey, nil, 0, 0)}) + eventMux.Post(cbfttypes.RemoveValidatorEvent{Node: enode.NewV4(n.PubKey, nil, 0, 0)}) log.Trace("Post RemoveValidatorEvent", "node", n.String()) } } From 8a549418633fea4249725890426cb2b4fa61ad28 Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 6 Jan 2022 19:13:19 +0800 Subject: [PATCH 208/299] fix index error --- consensus/cbft/validator/validator.go | 4 +- consensus/cbft/validator/validator_test.go | 5 ++- core/cbfttypes/type.go | 49 +++++++++++----------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 7f14047691..17174015a1 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -801,7 +801,7 @@ func (vp *ValidatorPool) GetGroupID(epoch uint64, nodeID enode.ID) (uint32, erro validators = vp.currentValidators } gvs, err := validators.GetGroupValidators(nodeID) - if err != nil { + if err != nil || gvs == nil { return 0, err } return gvs.GetGroupID(), nil @@ -928,7 +928,7 @@ func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64 } gvs, err := validators.GetGroupValidators(vp.nodeID) - if nil != err { + if nil != err || gvs == nil { // 当前节点不是共识节点 return err } diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index 4a79bce557..f74f972519 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -630,11 +630,12 @@ func TestValidatorGrouped(t *testing.T) { func TestGetGroupID(t *testing.T) { bls.Init(bls.BLS12_381) nodes := newTestNodeByNum(100) + targetID := nodes[0].Node.ID() agency := newTestInnerAgency(nodes) vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), true, new(event.TypeMux)) - grpID, _ := vp.GetGroupID(0, nodes[0].Node.ID()) - assert.Equal(t, uint32(0), grpID) + grpID, _ := vp.GetGroupID(0, targetID) + assert.True(t, grpID < 4) } func TestUpdate(t *testing.T) { diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 224dd3baee..f279dc1f2d 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -25,6 +25,8 @@ import ( "math" "sort" + "github.com/AlayaNetwork/Alaya-Go/crypto" + "github.com/AlayaNetwork/Alaya-Go/common/hexutil" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/x/xcom" @@ -99,7 +101,6 @@ type ValidateNodeMap map[enode.ID]*ValidateNode type SortedValidatorNodes struct { target enode.ID - sorted bool SortedNodes []*ValidateNode } @@ -143,7 +144,7 @@ type Validators struct { ValidBlockNumber uint64 `json:"validateBlockNumber"` // Sorting based on distance - SortedValidators SortedValidatorNodes `json:"sortedNodes"` + SortedValidators *SortedValidatorNodes `json:"sortedNodes"` //// Sorting based on node distance // Node grouping info @@ -223,7 +224,7 @@ func (vs *Validators) NodeListByIndexes(indexes []uint32) ([]*ValidateNode, erro } func (vs *Validators) NodeListByBitArray(vSet *utils.BitArray) ([]*ValidateNode, error) { - if !vs.SortedValidators.sorted { + if vs.SortedValidators == nil { vs.Sort() } l := make([]*ValidateNode, 0) @@ -233,7 +234,11 @@ func (vs *Validators) NodeListByBitArray(vSet *utils.BitArray) ([]*ValidateNode, if int(index) >= len(vs.SortedValidators.SortedNodes) { return nil, errors.New("invalid index") } - l = append(l, vs.SortedValidators.SortedNodes[int(index)]) + node, err := vs.FindNodeByIndex(index) + if err != nil { + return nil, err + } + l = append(l, node) } } return l, nil @@ -300,33 +305,26 @@ func (vs *Validators) Equal(rsh *Validators) bool { func (vs *Validators) Sort() { if targetNode, err := vs.FindNodeByIndex(0); err == nil { - vs.SortedValidators = SortedValidatorNodes{ - target: targetNode.NodeID, - sorted: false, - SortedNodes: make([]*ValidateNode, 0), - } + vs.SortedValidators = new(SortedValidatorNodes) + vs.SortedValidators.target = enode.ID(crypto.Keccak256Hash(targetNode.NodeID[:])) + vs.SortedValidators.SortedNodes = make([]*ValidateNode, 0) + for _, node := range vs.Nodes { vs.SortedValidators.SortedNodes = append(vs.SortedValidators.SortedNodes, node) } sort.Sort(vs.SortedValidators) - vs.SortedValidators.sorted = true } } func (vs *Validators) GetGroupValidators(nodeID enode.ID) (*GroupValidators, error) { - if !vs.SortedValidators.sorted { + if vs.SortedValidators == nil { vs.Sort() } - idx, err := vs.Index(nodeID) - if err != nil { - return nil, err - } - var ret *GroupValidators for _, gvs := range vs.GroupNodes { - groupLen := len(gvs.Nodes) - if idx <= gvs.Nodes[groupLen-1].Index { + _, err := gvs.GetUnitID(nodeID) + if err == nil { ret = gvs break } @@ -335,13 +333,13 @@ func (vs *Validators) GetGroupValidators(nodeID enode.ID) (*GroupValidators, err } func (vs *Validators) UnitID(nodeID enode.ID) (uint32, error) { - if !vs.SortedValidators.sorted { + if vs.SortedValidators == nil { vs.Sort() } - idx, err := vs.Index(nodeID) + _, err := vs.Index(nodeID) if err != nil { - return idx, err + return math.MaxUint32, err } gvs, err := vs.GetGroupValidators(nodeID) @@ -399,12 +397,13 @@ func (gvs *GroupValidators) NodeList() []enode.ID { // [50,25] = 25,25;[43,25] = 22,21; [101,25] = 21,20,20,20,20 func (vs *Validators) Grouped() error { // sort SortedValidators by distance - if !vs.SortedValidators.sorted { + if vs.SortedValidators == nil { vs.Sort() + if vs.SortedValidators == nil { + return errors.New("no validators") + } } - if uint32(len(vs.SortedValidators.SortedNodes)) <= xcom.MaxGroupValidators() { - return errors.New("no need grouped") - } + validatorCount := uint32(vs.SortedValidators.Len()) groupNum := validatorCount / xcom.MaxGroupValidators() mod := validatorCount % xcom.MaxGroupValidators() From 9cff9c7acdec4615c725167a14e81a623bbf6d8b Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Fri, 7 Jan 2022 11:37:07 +0800 Subject: [PATCH 209/299] Randomly connect nodes --- p2p/discover.go | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/p2p/discover.go b/p2p/discover.go index 8528bfe412..9d3d27c0a0 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -2,6 +2,8 @@ package p2p import ( "context" + "github.com/AlayaNetwork/Alaya-Go/p2p/enr" + "math/rand" "sync" "time" @@ -30,12 +32,14 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { if !srv.validPeersExist(topic) { srv.topicSubscriberMu.RLock() nodes, ok := srv.topicSubscriber[topic] + copyNodes := make([]enode.ID, len(nodes)) + copy(copyNodes, nodes) srv.topicSubscriberMu.RUnlock() if !ok { continue } log.Debug("No peers found subscribed gossip topic . Searching network for peers subscribed to the topic.", "topic", topic) - if err := srv.FindPeersWithTopic(ctx, topic, nodes, srv.Config.MinimumPeersPerTopic); err != nil { + if err := srv.FindPeersWithTopic(ctx, topic, copyNodes, srv.Config.MinimumPeersPerTopic); err != nil { log.Debug("Could not search for peers", "err", err) return } @@ -63,26 +67,39 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes [ return nil } - iterator := srv.ntab.RandomNodes() - iterator = filterNodes(ctx, iterator, srv.filterPeerForTopic(nodes)) - currNum := len(srv.pubSubServer.PubSub().ListPeers(topic)) wg := new(sync.WaitGroup) + topicRand := rand.New(rand.NewSource(time.Now().UnixNano())) + topicRand.Shuffle(len(nodes), func(i, j int) { + nodes[i], nodes[j] = nodes[j], nodes[i] + }) + + sel := srv.Config.MinimumPeersPerTopic * 2 try := 0 for { if err := ctx.Err(); err != nil { return err } // Retry at most 3 times - if try >= 3 || currNum >= threshold { + if try >= 3 || currNum >= threshold || len(nodes) == 0 { break } - nodes := enode.ReadNodes(iterator, srv.Config.MinimumPeersPerTopic*2) - for i, _ := range nodes { + tempNodes := nodes[:] + if sel >= len(nodes) { + tempNodes = nodes[:sel] + nodes = nodes[sel:] + } else { + nodes = make([]enode.ID, 0) + } + + for _, toNodeId := range tempNodes { + if toNodeId == srv.localnode.ID() { + continue + } wg.Add(1) - srv.AddConsensusPeerWithDone(nodes[i], func() { + srv.AddConsensusPeerWithDone(enode.SignNull(new(enr.Record), toNodeId), func() { wg.Done() }) } @@ -91,7 +108,7 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes [ currNum = len(srv.pubSubServer.PubSub().ListPeers(topic)) try++ } - log.Trace("Searching network for peers subscribed to the topic done.", "topic", topic, "peers", currNum, "try", try) + log.Trace("Searching network for peers subscribed to the topic done.", "topic", topic, "peers", currNum, "try", try, "remainNodes", len(nodes)) return nil } From a5b5c162cc5a921a4ec03bb020913d27fa6ad7b3 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Fri, 7 Jan 2022 14:32:01 +0800 Subject: [PATCH 210/299] fix disconnect --- p2p/server.go | 69 ++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/p2p/server.go b/p2p/server.go index af7d303218..4889bb30d9 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -975,50 +975,47 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in srv.topicSubscriberMu.RLock() for _, connTopic := range srv.getPeersTopics(c.node.ID()) { if len(srv.pubSubServer.PubSub().ListPeers(connTopic)) < srv.MinimumPeersPerTopic { - reducedTopicPeerLength := 0 - reducedTopic := "" - //找到节点数最多的主题 - for topic, _ := range srv.topicSubscriber { - if topic != connTopic { - num := len(srv.pubSubServer.PubSub().ListPeers(topic)) - if num > reducedTopicPeerLength { - reducedTopicPeerLength = num - reducedTopic = topic - } + //获取每个节点所拥有的主题的数量 + var ( + topicsCountSort = make([]int, 0) + nodeTopicsCount = make(map[enode.ID]int) + ) + + for topic, nodes := range srv.topicSubscriber { + if topic == connTopic { + continue } - } - if reducedTopic != "" { - //获取每个节点所拥有的主题的数量 - nodeReferenced := make(map[enode.ID]int) - for _, nodes := range srv.topicSubscriber { - for _, node := range nodes { - if num, ok := nodeReferenced[node]; ok { - nodeReferenced[node] = num + 1 + for _, node := range nodes { + if _, ok := peers[node]; ok { + if num, ok := nodeTopicsCount[node]; ok { + nodeTopicsCount[node] = num + 1 } else { - nodeReferenced[node] = 1 + nodeTopicsCount[node] = 1 } } } - var reducedNode enode.ID - var referencedNum int = 10000 - //找到该主题下拥有主题最少的节点 - for _, node := range srv.topicSubscriber[reducedTopic] { - if referencedNum > nodeReferenced[node] { - referencedNum = nodeReferenced[node] - reducedNode = node - if referencedNum == 1 { - break + } + + //找到引用最小的节点 + for _, i := range nodeTopicsCount { + topicsCountSort = append(topicsCountSort, i) + } + sort.Ints(topicsCountSort) + + loop: + for _, i := range topicsCountSort { + for node, count := range nodeTopicsCount { + if count == i { + peer := peers[node] + if !peer.rw.is(trustedConn | staticDialedConn) { + log.Debug("Disconnect over limit consensus connection", "peer", peer.ID(), "flags", peer.rw.flags, "peers", len(peers), "topic", srv.getPeersTopics(peer.ID()), "referencedNum", i, "maxConsensusPeers", srv.MaxConsensusPeers) + peer.Disconnect(DiscRequested) + disconnectConsensus++ + break loop } } } - if peer, ok := peers[reducedNode]; ok { - if !peer.rw.is(trustedConn | staticDialedConn) { - log.Debug("Disconnect over limit consensus connection", "peer", peer.ID(), "flags", peer.rw.flags, "peers", len(peers), "topic", reducedTopic, "referencedNum", referencedNum, "maxConsensusPeers", srv.MaxConsensusPeers) - peer.Disconnect(DiscRequested) - disconnectConsensus++ - break - } - } + } } } From 873dddf9c68c06bb044f8542fdd01f3ee8c02649 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Fri, 7 Jan 2022 16:45:29 +0800 Subject: [PATCH 211/299] Change Id to Node object for delivery --- consensus/cbft/validator/validator.go | 12 ++++++------ consensus/cbft/validator/validator_test.go | 2 +- core/cbfttypes/type.go | 22 +++++++++++++++++++--- p2p/discover.go | 13 ++++++------- p2p/server.go | 20 ++++++++++---------- 5 files changed, 42 insertions(+), 27 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 17174015a1..2dcae66f2c 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -642,9 +642,9 @@ func (vp *ValidatorPool) ValidatorList(epoch uint64) []enode.ID { func (vp *ValidatorPool) validatorList(epoch uint64) []enode.ID { if vp.epochToBlockNumber(epoch) <= vp.switchPoint { - return vp.prevValidators.NodeList() + return vp.prevValidators.NodeIdList() } - return vp.currentValidators.NodeList() + return vp.currentValidators.NodeIdList() } func (vp *ValidatorPool) Validators(epoch uint64) *cbfttypes.Validators { @@ -934,13 +934,13 @@ func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64 } log.Debug("ValidatorPool organized OK!", "epoch", epoch, "validators", validators.String()) - consensusNodeIDs := validators.NodeList() - groupNodeIDs := gvs.NodeList() + consensusNodes := validators.NodeList() + groupNodes := gvs.NodeList() consensusTopic := cbfttypes.ConsensusTopicName(epoch) groupTopic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) - eventMux.Post(cbfttypes.NewTopicEvent{Topic: consensusTopic, Nodes: consensusNodeIDs}) - eventMux.Post(cbfttypes.NewTopicEvent{Topic: groupTopic, Nodes: groupNodeIDs}) + eventMux.Post(cbfttypes.NewTopicEvent{Topic: consensusTopic, Nodes: consensusNodes}) + eventMux.Post(cbfttypes.NewTopicEvent{Topic: groupTopic, Nodes: groupNodes}) eventMux.Post(cbfttypes.GroupTopicEvent{Topic: groupTopic}) return nil } diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index f74f972519..220ea4fd04 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -176,7 +176,7 @@ func TestValidators(t *testing.T) { assert.True(t, err == nil, "get index by address fail") assert.Equal(t, validator.Index, idxN1.Index) - nl := vds.NodeList() + nl := vds.NodeIdList() assert.True(t, len(nl) == vds.Len()) emptyNodeID := enode.ID{} diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index f279dc1f2d..2769a83319 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -73,7 +73,7 @@ type RemoveValidatorEvent struct { // NewTopicEvent use for p2p,Nodes under this topic will be discovered type NewTopicEvent struct { Topic string - Nodes []enode.ID + Nodes []*enode.Node } // ExpiredTopicEvent use for p2p,Nodes under this topic may be disconnected @@ -182,7 +182,7 @@ func (vs *Validators) String() string { return string(b) } -func (vs *Validators) NodeList() []enode.ID { +func (vs *Validators) NodeIdList() []enode.ID { nodeList := make([]enode.ID, 0) for id := range vs.Nodes { nodeList = append(nodeList, id) @@ -190,6 +190,14 @@ func (vs *Validators) NodeList() []enode.ID { return nodeList } +func (vs *Validators) NodeList() []*enode.Node { + nodeList := make([]*enode.Node, 0) + for _, vnode := range vs.Nodes { + nodeList = append(nodeList, enode.NewV4(vnode.PubKey, nil, 0, 0)) + } + return nodeList +} + func (vs *Validators) MembersCount(groupID uint32) (int, error) { if groupID >= uint32(len(vs.GroupNodes)) { return 0, fmt.Errorf("wrong groupid[%d]", groupID) @@ -383,7 +391,7 @@ func (gvs *GroupValidators) GetGroupID() uint32 { } // return all NodeIDs in the group -func (gvs *GroupValidators) NodeList() []enode.ID { +func (gvs *GroupValidators) NodeIdList() []enode.ID { nodeList := make([]enode.ID, 0) for _, id := range gvs.Nodes { nodeList = append(nodeList, id.NodeID) @@ -391,6 +399,14 @@ func (gvs *GroupValidators) NodeList() []enode.ID { return nodeList } +func (gvs *GroupValidators) NodeList() []*enode.Node { + nodeList := make([]*enode.Node, 0) + for _, vnode := range gvs.Nodes { + nodeList = append(nodeList, enode.NewV4(vnode.PubKey, nil, 0, 0)) + } + return nodeList +} + // Grouped fill validators into groups // groupValidatorsLimit is a factor to determine how many groups are grouped // eg: [validatorCount,groupValidatorsLimit]= diff --git a/p2p/discover.go b/p2p/discover.go index 9d3d27c0a0..291fa02e44 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -2,7 +2,6 @@ package p2p import ( "context" - "github.com/AlayaNetwork/Alaya-Go/p2p/enr" "math/rand" "sync" "time" @@ -32,7 +31,7 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { if !srv.validPeersExist(topic) { srv.topicSubscriberMu.RLock() nodes, ok := srv.topicSubscriber[topic] - copyNodes := make([]enode.ID, len(nodes)) + copyNodes := make([]*enode.Node, len(nodes)) copy(copyNodes, nodes) srv.topicSubscriberMu.RUnlock() if !ok { @@ -60,7 +59,7 @@ func (srv *Server) validPeersExist(subnetTopic string) bool { // subscribed to a particular subnet. Then we try to connect // with those peers. This method will block until the required amount of // peers are found, the method only exits in the event of context timeouts. -func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes []enode.ID, threshold int) error { +func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes []*enode.Node, threshold int) error { if srv.ntab == nil { // return if discovery isn't set @@ -91,15 +90,15 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes [ tempNodes = nodes[:sel] nodes = nodes[sel:] } else { - nodes = make([]enode.ID, 0) + nodes = make([]*enode.Node, 0) } - for _, toNodeId := range tempNodes { - if toNodeId == srv.localnode.ID() { + for _, toNode := range tempNodes { + if toNode.ID() == srv.localnode.ID() { continue } wg.Add(1) - srv.AddConsensusPeerWithDone(enode.SignNull(new(enr.Record), toNodeId), func() { + srv.AddConsensusPeerWithDone(toNode, func() { wg.Done() }) } diff --git a/p2p/server.go b/p2p/server.go index 4889bb30d9..8f50578e08 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -228,7 +228,7 @@ type Server struct { cancelPubSubServer context.CancelFunc topicSubscriberMu sync.RWMutex - topicSubscriber map[string][]enode.ID + topicSubscriber map[string][]*enode.Node } type peerOpFunc func(map[enode.ID]*Peer) @@ -536,7 +536,7 @@ func (srv *Server) Start() (err error) { srv.addconsensus = make(chan *dialTask) srv.removeconsensus = make(chan *enode.Node) srv.updateConsensusStatus = make(chan map[enode.ID]struct{}) - srv.topicSubscriber = make(map[string][]enode.ID) + srv.topicSubscriber = make(map[string][]*enode.Node) if int(xcom.MaxGroupValidators()) <= srv.MinimumPeersPerTopic { srv.MinimumPeersPerTopic = int(xcom.MaxGroupValidators()) @@ -986,11 +986,11 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in continue } for _, node := range nodes { - if _, ok := peers[node]; ok { - if num, ok := nodeTopicsCount[node]; ok { - nodeTopicsCount[node] = num + 1 + if _, ok := peers[node.ID()]; ok { + if num, ok := nodeTopicsCount[node.ID()]; ok { + nodeTopicsCount[node.ID()] = num + 1 } else { - nodeTopicsCount[node] = 1 + nodeTopicsCount[node.ID()] = 1 } } } @@ -1384,7 +1384,7 @@ func (srv *Server) watching() { srv.RemoveConsensusPeer(data.Node) case cbfttypes.NewTopicEvent: srv.topicSubscriberMu.Lock() - topicSubscriber := make([]enode.ID, 0) + topicSubscriber := make([]*enode.Node, 0) for _, node := range data.Nodes { topicSubscriber = append(topicSubscriber, node) } @@ -1393,7 +1393,7 @@ func (srv *Server) watching() { consensusPeers := make(map[enode.ID]struct{}) for _, nodes := range srv.topicSubscriber { for _, node := range nodes { - consensusPeers[node] = struct{}{} + consensusPeers[node.ID()] = struct{}{} } } if _, ok := consensusPeers[srv.localnode.ID()]; ok { @@ -1408,7 +1408,7 @@ func (srv *Server) watching() { consensusPeers := make(map[enode.ID]struct{}) for _, peers := range srv.topicSubscriber { for _, peer := range peers { - consensusPeers[peer] = struct{}{} + consensusPeers[peer.ID()] = struct{}{} } } if _, ok := consensusPeers[srv.localnode.ID()]; !ok { @@ -1435,7 +1435,7 @@ func (srv *Server) getPeersTopics(id enode.ID) []string { topics := make([]string, 0) for topic, nodes := range srv.topicSubscriber { for _, node := range nodes { - if node == id { + if node.ID() == id { topics = append(topics, topic) break } From 870333ddcb150b11194e1431241ed10ac7233a50 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Fri, 7 Jan 2022 18:30:36 +0800 Subject: [PATCH 212/299] Modify the number of initial connections --- p2p/discover.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/discover.go b/p2p/discover.go index 291fa02e44..82aac8038b 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -74,7 +74,7 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes [ nodes[i], nodes[j] = nodes[j], nodes[i] }) - sel := srv.Config.MinimumPeersPerTopic * 2 + sel := threshold + (threshold / 2) try := 0 for { if err := ctx.Err(); err != nil { From a43c4c06695e1a330d4f783c783d39645415dfaa Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Fri, 7 Jan 2022 18:38:01 +0800 Subject: [PATCH 213/299] Minimum number of connections --- node/defaults.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/defaults.go b/node/defaults.go index d7fa6ec0f0..cb48270971 100644 --- a/node/defaults.go +++ b/node/defaults.go @@ -48,7 +48,7 @@ var DefaultConfig = Config{ MaxPeers: 60, NAT: nat.Any(), MaxConsensusPeers: 40, - MinimumPeersPerTopic: 10, + MinimumPeersPerTopic: 5, }, } From c86de8d896a8d27c65ffcdd900dfe3e3cedbea8c Mon Sep 17 00:00:00 2001 From: benbaley Date: Mon, 10 Jan 2022 08:57:39 +0800 Subject: [PATCH 214/299] fix panic error --- consensus/cbft/validator/validator.go | 8 ++++---- core/cbfttypes/type.go | 9 +++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 17174015a1..db6ef702fd 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -525,21 +525,21 @@ func (vp *ValidatorPool) dealWithOldVersionEvents(epoch uint64, eventMux *event. // in the consensus stages. Also we are not needed // to keep connect with old validators. if isValidatorAfter { - for _, n := range vp.currentValidators.SortedValidators.SortedNodes { + for _, n := range vp.currentValidators.Nodes { if node, _ := vp.prevValidators.FindNodeByID(n.NodeID); node == nil { eventMux.Post(cbfttypes.AddValidatorEvent{Node: enode.NewV4(n.PubKey, nil, 0, 0)}) log.Trace("Post AddValidatorEvent", "node", n.String()) } } - for _, n := range vp.prevValidators.SortedValidators.SortedNodes { + for _, n := range vp.prevValidators.Nodes { if node, _ := vp.currentValidators.FindNodeByID(n.NodeID); node == nil { eventMux.Post(cbfttypes.RemoveValidatorEvent{Node: enode.NewV4(n.PubKey, nil, 0, 0)}) log.Trace("Post RemoveValidatorEvent", "node", n.String()) } } } else { - for _, node := range vp.prevValidators.SortedValidators.SortedNodes { + for _, node := range vp.prevValidators.Nodes { eventMux.Post(cbfttypes.RemoveValidatorEvent{Node: enode.NewV4(node.PubKey, nil, 0, 0)}) log.Trace("Post RemoveValidatorEvent", "nodeID", node.String()) } @@ -550,7 +550,7 @@ func (vp *ValidatorPool) dealWithOldVersionEvents(epoch uint64, eventMux *event. // consensus peers is because we need to keep connecting // with other validators in the consensus stages. if isValidatorAfter { - for _, node := range vp.currentValidators.SortedValidators.SortedNodes { + for _, node := range vp.currentValidators.Nodes { eventMux.Post(cbfttypes.AddValidatorEvent{Node: enode.NewV4(node.PubKey, nil, 0, 0)}) log.Trace("Post AddValidatorEvent", "nodeID", node.String()) } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index f279dc1f2d..a09f0bfdf9 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -226,6 +226,9 @@ func (vs *Validators) NodeListByIndexes(indexes []uint32) ([]*ValidateNode, erro func (vs *Validators) NodeListByBitArray(vSet *utils.BitArray) ([]*ValidateNode, error) { if vs.SortedValidators == nil { vs.Sort() + if vs.SortedValidators == nil { + return nil, errors.New("no sorted validators") + } } l := make([]*ValidateNode, 0) @@ -319,6 +322,9 @@ func (vs *Validators) Sort() { func (vs *Validators) GetGroupValidators(nodeID enode.ID) (*GroupValidators, error) { if vs.SortedValidators == nil { vs.Sort() + if vs.SortedValidators == nil { + return nil, errors.New("no sorted validators") + } } var ret *GroupValidators @@ -335,6 +341,9 @@ func (vs *Validators) GetGroupValidators(nodeID enode.ID) (*GroupValidators, err func (vs *Validators) UnitID(nodeID enode.ID) (uint32, error) { if vs.SortedValidators == nil { vs.Sort() + if vs.SortedValidators == nil { + return math.MaxUint32, errors.New("no sorted validators") + } } _, err := vs.Index(nodeID) From abd72df5714a5b5eeecfe98de68ff797978025e2 Mon Sep 17 00:00:00 2001 From: benbaley Date: Mon, 10 Jan 2022 15:25:51 +0800 Subject: [PATCH 215/299] update version to 0.17.0 --- params/version.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/params/version.go b/params/version.go index fa53566903..9fa54ea726 100644 --- a/params/version.go +++ b/params/version.go @@ -23,8 +23,8 @@ import ( const ( //These versions are meaning the current code version. VersionMajor = 0 // Major version component of the current release - VersionMinor = 16 // Minor version component of the current release - VersionPatch = 1 // Patch version component of the current release + VersionMinor = 17 // Minor version component of the current release + VersionPatch = 0 // Patch version component of the current release VersionMeta = "unstable" // Version metadata to append to the version string //CAUTION: DO NOT MODIFY THIS ONCE THE CHAIN HAS BEEN INITIALIZED!!! From 0c629d6b31306645a0dc363345a71c0f156fed10 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 11 Jan 2022 09:54:55 +0800 Subject: [PATCH 216/299] fix disconect when consensus peer too much --- p2p/server.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/p2p/server.go b/p2p/server.go index 8f50578e08..8d702e8ebb 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -975,7 +975,7 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in srv.topicSubscriberMu.RLock() for _, connTopic := range srv.getPeersTopics(c.node.ID()) { if len(srv.pubSubServer.PubSub().ListPeers(connTopic)) < srv.MinimumPeersPerTopic { - //获取每个节点所拥有的主题的数量 + // 获取每个节点所拥有的主题的数量 var ( topicsCountSort = make([]int, 0) nodeTopicsCount = make(map[enode.ID]int) @@ -995,6 +995,12 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in } } } + // 去掉拥有当前主题的节点 + for _, node := range srv.topicSubscriber[connTopic] { + if _, ok := nodeTopicsCount[node.ID()]; ok { + delete(nodeTopicsCount, node.ID()) + } + } //找到引用最小的节点 for _, i := range nodeTopicsCount { From c97e7ecb180b89f8ebb35c9a1caade6bfd3b74d7 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Tue, 11 Jan 2022 15:48:29 +0800 Subject: [PATCH 217/299] fix GetActiveBlock --- x/gov/proposals.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/gov/proposals.go b/x/gov/proposals.go index f3d425cce6..8ae8273a0f 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -220,7 +220,7 @@ func (vp *VersionProposal) GetNewVersion() uint32 { func (vp *VersionProposal) GetActiveBlock(version uint32) uint64 { if vp.NewVersion == params.FORKVERSION_0_17_0 { - return xutil.CalculateEpoch(vp.ActiveBlock, version)*xcom.EpochSize(version) + 1 + return xutil.CalculateEpoch(vp.ActiveBlock, version)*xutil.CalcBlocksEachEpoch(version) + 1 } return vp.ActiveBlock } From 85283688500a9c1a0a4bad289d5d04e738684631 Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 11 Jan 2022 16:35:14 +0800 Subject: [PATCH 218/299] fix index error --- x/plugin/staking_plugin.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index ab4099d8b7..410e43a85f 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -3747,7 +3747,7 @@ func (sk *StakingPlugin) HasStake(blockHash common.Hash, addr common.Address) (b func (sk *StakingPlugin) Adjust0170RoundValidators(blockHash common.Hash, blockNumber uint64) error { oldIndex := &staking.ValArrIndex{ Start: blockNumber, - End: blockNumber + xcom.ConsensusSize(params.FORKVERSION_0_16_0), + End: (blockNumber - 1) + xcom.ConsensusSize(params.FORKVERSION_0_16_0), } //获取旧ValidatorQueue queue, err := sk.db.GetRoundValListByIrr(oldIndex.Start, oldIndex.End) @@ -3763,11 +3763,7 @@ func (sk *StakingPlugin) Adjust0170RoundValidators(blockHash common.Hash, blockN End: oldIndex.Start + xcom.ConsensusSize(params.FORKVERSION_0_17_0), Arr: queue, } - if err := sk.setRoundValListAndIndex(blockNumber, blockHash, newQueue); nil != err { - log.Error("Failed to SetNextValidatorList on Election", "blockNumber", blockNumber, - "blockHash", blockHash.Hex(), "err", err) - return err - } + // 删除旧ValidatorQueue if err := sk.db.DelRoundValListByBlockHash(blockHash, oldIndex.Start, oldIndex.End); nil != err { log.Error("Adjust0170RoundValidators: delete oldIndex validators failed", @@ -3775,6 +3771,12 @@ func (sk *StakingPlugin) Adjust0170RoundValidators(blockHash common.Hash, blockN "blockNumber", blockNumber, "blockHash", blockHash.Hex(), "err", err) return err } + + if err := sk.setRoundValListAndIndex(blockNumber, blockHash, newQueue); nil != err { + log.Error("Failed to SetNextValidatorList on Election", "blockNumber", blockNumber, + "blockHash", blockHash.Hex(), "err", err) + return err + } log.Debug("Adjust0170RoundValidators OK!", "queue", queue.String(), "oldIndex.Start", oldIndex.Start, "oldIndex.End", oldIndex.End, "newQueue.Start", newQueue.Start, "newQueue.End", newQueue.End) return nil From 34b3d12ba20d955a44ab6ee7f478332b46254f35 Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 11 Jan 2022 16:40:27 +0800 Subject: [PATCH 219/299] fix index - 1 error --- x/plugin/staking_plugin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index 410e43a85f..327f8830ed 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -3760,7 +3760,7 @@ func (sk *StakingPlugin) Adjust0170RoundValidators(blockHash common.Hash, blockN // 根据新ConsensusSize更新index和RoundValList newQueue := &staking.ValidatorArray{ Start: oldIndex.Start, - End: oldIndex.Start + xcom.ConsensusSize(params.FORKVERSION_0_17_0), + End: oldIndex.Start - 1 + xcom.ConsensusSize(params.FORKVERSION_0_17_0), Arr: queue, } From d1a6f60f85e31fea6583c67585ed08fed0b90874 Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 11 Jan 2022 17:15:41 +0800 Subject: [PATCH 220/299] update Adjust0170RoundValidators logic --- x/plugin/staking_plugin.go | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index 327f8830ed..f0f49c1cf9 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -3764,6 +3764,27 @@ func (sk *StakingPlugin) Adjust0170RoundValidators(blockHash common.Hash, blockN Arr: queue, } + // 更新index + indexs, err := sk.db.GetRoundValIndexByBlockHash(blockHash) + if nil != err { + log.Error("Adjust0170RoundValidators: Query round valIndex is failed", + "blockNumber", blockNumber, "blockHash", blockHash.Hex(), "err", err) + return err + } + for i, index := range indexs { + if index.Start == oldIndex.Start { + index.End = newQueue.End + log.Debug("Adjust0170RoundValidators: new indexs' ", "i", i, "End", index.End) + break + } + } + // update index Arr + if err := sk.db.SetRoundValIndex(blockHash, indexs); nil != err { + log.Error("Adjust0170RoundValidators: store round validators new indexArr is failed", + "blockNumber", blockNumber, "blockHash", blockHash.Hex(), "indexs length", len(indexs), "err", err) + return err + } + // 删除旧ValidatorQueue if err := sk.db.DelRoundValListByBlockHash(blockHash, oldIndex.Start, oldIndex.End); nil != err { log.Error("Adjust0170RoundValidators: delete oldIndex validators failed", @@ -3772,11 +3793,14 @@ func (sk *StakingPlugin) Adjust0170RoundValidators(blockHash common.Hash, blockN return err } - if err := sk.setRoundValListAndIndex(blockNumber, blockHash, newQueue); nil != err { - log.Error("Failed to SetNextValidatorList on Election", "blockNumber", blockNumber, - "blockHash", blockHash.Hex(), "err", err) + // Store new round validator Item + if err := sk.db.SetRoundValList(blockHash, newQueue.Start, newQueue.End, newQueue.Arr); nil != err { + log.Error("Failed to setRoundValListAndIndex: store new round validators is failed", + "blockNumber", blockNumber, "blockHash", blockHash.Hex(), + "start", newQueue.Start, "end", newQueue.End, "val arr length", len(newQueue.Arr), "err", err) return err } + log.Debug("Adjust0170RoundValidators OK!", "queue", queue.String(), "oldIndex.Start", oldIndex.Start, "oldIndex.End", oldIndex.End, "newQueue.Start", newQueue.Start, "newQueue.End", newQueue.End) return nil From 3faa94b64466b25fdd7763f5cbc8fc655cd7c67b Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Tue, 11 Jan 2022 17:47:56 +0800 Subject: [PATCH 221/299] Update --- p2p/discover.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/discover.go b/p2p/discover.go index 82aac8038b..4b895d7470 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -86,7 +86,7 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes [ } tempNodes := nodes[:] - if sel >= len(nodes) { + if len(nodes) > sel { tempNodes = nodes[:sel] nodes = nodes[sel:] } else { From fee9e4b4bdea6550ff230edfd4e0adcccb7007cf Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 11 Jan 2022 18:38:00 +0800 Subject: [PATCH 222/299] update discover --- p2p/discover.go | 61 +++++++++++++++++++------------------------------ 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/p2p/discover.go b/p2p/discover.go index 82aac8038b..499ea356c6 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -31,14 +31,12 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { if !srv.validPeersExist(topic) { srv.topicSubscriberMu.RLock() nodes, ok := srv.topicSubscriber[topic] - copyNodes := make([]*enode.Node, len(nodes)) - copy(copyNodes, nodes) srv.topicSubscriberMu.RUnlock() if !ok { continue } log.Debug("No peers found subscribed gossip topic . Searching network for peers subscribed to the topic.", "topic", topic) - if err := srv.FindPeersWithTopic(ctx, topic, copyNodes, srv.Config.MinimumPeersPerTopic); err != nil { + if err := srv.FindPeersWithTopic(ctx, topic, nodes, srv.Config.MinimumPeersPerTopic); err != nil { log.Debug("Could not search for peers", "err", err) return } @@ -65,49 +63,38 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes [ // return if discovery isn't set return nil } + if err := ctx.Err(); err != nil { + return err + } - currNum := len(srv.pubSubServer.PubSub().ListPeers(topic)) wg := new(sync.WaitGroup) - topicRand := rand.New(rand.NewSource(time.Now().UnixNano())) - topicRand.Shuffle(len(nodes), func(i, j int) { - nodes[i], nodes[j] = nodes[j], nodes[i] - }) + indexs := rand.New(rand.NewSource(time.Now().UnixNano())).Perm(len(nodes)) - sel := threshold + (threshold / 2) - try := 0 - for { - if err := ctx.Err(); err != nil { - return err + sel := threshold / 3 + if sel == 0 { + sel = 1 + } + + for i := 0; i < len(indexs); i++ { + if nodes[indexs[i]].ID() == srv.localnode.ID() { + continue } - // Retry at most 3 times - if try >= 3 || currNum >= threshold || len(nodes) == 0 { + wg.Add(1) + srv.AddConsensusPeerWithDone(nodes[indexs[i]], func() { + wg.Done() + }) + sel-- + if sel == 0 { break } + } - tempNodes := nodes[:] - if sel >= len(nodes) { - tempNodes = nodes[:sel] - nodes = nodes[sel:] - } else { - nodes = make([]*enode.Node, 0) - } + // Wait for all dials to be completed. + wg.Wait() + currNum := len(srv.pubSubServer.PubSub().ListPeers(topic)) - for _, toNode := range tempNodes { - if toNode.ID() == srv.localnode.ID() { - continue - } - wg.Add(1) - srv.AddConsensusPeerWithDone(toNode, func() { - wg.Done() - }) - } - // Wait for all dials to be completed. - wg.Wait() - currNum = len(srv.pubSubServer.PubSub().ListPeers(topic)) - try++ - } - log.Trace("Searching network for peers subscribed to the topic done.", "topic", topic, "peers", currNum, "try", try, "remainNodes", len(nodes)) + log.Trace("Searching network for peers subscribed to the topic done.", "topic", topic, "peers", currNum) return nil } From f5b092c1c0b9e04fd360018f6b0a1d8f93ed13e9 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 11 Jan 2022 18:43:03 +0800 Subject: [PATCH 223/299] fix --- p2p/discover.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/p2p/discover.go b/p2p/discover.go index 499ea356c6..b3999f0ded 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -28,15 +28,16 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { } // Check there are enough peers - if !srv.validPeersExist(topic) { + numOfPeers := srv.pubSubServer.PubSub().ListPeers(topic) + if len(numOfPeers) < srv.Config.MinimumPeersPerTopic { srv.topicSubscriberMu.RLock() nodes, ok := srv.topicSubscriber[topic] srv.topicSubscriberMu.RUnlock() if !ok { continue } - log.Debug("No peers found subscribed gossip topic . Searching network for peers subscribed to the topic.", "topic", topic) - if err := srv.FindPeersWithTopic(ctx, topic, nodes, srv.Config.MinimumPeersPerTopic); err != nil { + log.Debug("No peers found subscribed gossip topic . Searching network for peers subscribed to the topic.", "topic", topic, "peers", numOfPeers) + if err := srv.FindPeersWithTopic(ctx, topic, nodes, srv.Config.MinimumPeersPerTopic-len(numOfPeers)); err != nil { log.Debug("Could not search for peers", "err", err) return } @@ -47,12 +48,6 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { }() } -// find if we have peers who are subscribed to the same subnet -func (srv *Server) validPeersExist(subnetTopic string) bool { - numOfPeers := srv.pubSubServer.PubSub().ListPeers(subnetTopic) - return len(numOfPeers) >= srv.Config.MinimumPeersPerTopic -} - // FindPeersWithTopic performs a network search for peers // subscribed to a particular subnet. Then we try to connect // with those peers. This method will block until the required amount of @@ -71,7 +66,7 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes [ indexs := rand.New(rand.NewSource(time.Now().UnixNano())).Perm(len(nodes)) - sel := threshold / 3 + sel := threshold / 2 if sel == 0 { sel = 1 } From a683331c5d056fc6bfb9ea1e8dee3f372c396c47 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 12 Jan 2022 10:47:37 +0800 Subject: [PATCH 224/299] update for discover --- p2p/discover.go | 55 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/p2p/discover.go b/p2p/discover.go index b3999f0ded..f1f204b8dc 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -26,23 +26,51 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { if !srv.running { continue } - // Check there are enough peers - numOfPeers := srv.pubSubServer.PubSub().ListPeers(topic) - if len(numOfPeers) < srv.Config.MinimumPeersPerTopic { + peers := srv.pubSubServer.PubSub().ListPeers(topic) + peerNeedFind := srv.Config.MinimumPeersPerTopic - len(peers) + if peerNeedFind > 0 { srv.topicSubscriberMu.RLock() nodes, ok := srv.topicSubscriber[topic] - srv.topicSubscriberMu.RUnlock() if !ok { + srv.topicSubscriberMu.RUnlock() continue } - log.Debug("No peers found subscribed gossip topic . Searching network for peers subscribed to the topic.", "topic", topic, "peers", numOfPeers) - if err := srv.FindPeersWithTopic(ctx, topic, nodes, srv.Config.MinimumPeersPerTopic-len(numOfPeers)); err != nil { + copyNodes := make([]*enode.Node, len(nodes)) + copy(copyNodes, nodes) + srv.topicSubscriberMu.RUnlock() + + currentConnectPeer := make(map[enode.ID]struct{}) + srv.doPeerOp(func(m map[enode.ID]*Peer) { + for id, _ := range currentConnectPeer { + currentConnectPeer[id] = struct{}{} + } + }) + + for i := 0; i < len(copyNodes); { + if copyNodes[i].ID() == srv.localnode.ID() { + copyNodes = append(copyNodes[:i], copyNodes[i+1:]...) + continue + } + if _, ok := currentConnectPeer[copyNodes[i].ID()]; ok { + copyNodes = append(copyNodes[:i], copyNodes[i+1:]...) + continue + } + i++ + } + if len(copyNodes) == 0 { + log.Debug("all peers are found,no need searching network", "topic", topic, "peers", len(peers)) + continue + } + if peerNeedFind > len(copyNodes) { + peerNeedFind = len(copyNodes) + } + log.Debug("not enough nodes in this topic,searching network", "topic", topic, "peers", len(peers), "peerNeedFind", peerNeedFind, "minimumPeersPerTopic", srv.Config.MinimumPeersPerTopic) + if err := srv.FindPeersWithTopic(ctx, topic, copyNodes, peerNeedFind); err != nil { log.Debug("Could not search for peers", "err", err) return } } - } } }() @@ -63,24 +91,19 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes [ } wg := new(sync.WaitGroup) - indexs := rand.New(rand.NewSource(time.Now().UnixNano())).Perm(len(nodes)) - sel := threshold / 2 - if sel == 0 { - sel = 1 + if threshold > 6 { + threshold = threshold / 2 } for i := 0; i < len(indexs); i++ { - if nodes[indexs[i]].ID() == srv.localnode.ID() { - continue - } wg.Add(1) srv.AddConsensusPeerWithDone(nodes[indexs[i]], func() { wg.Done() }) - sel-- - if sel == 0 { + threshold-- + if threshold == 0 { break } } From f801a3fd38f7f55e6bbfdc99327c0e3ec7ac8879 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 12 Jan 2022 11:36:35 +0800 Subject: [PATCH 225/299] update discover --- p2p/dial.go | 8 ++++---- p2p/discover.go | 19 +++++++++++++------ p2p/server.go | 6 +++--- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/p2p/dial.go b/p2p/dial.go index 52b9d0c8b7..23ac40f2cf 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -274,7 +274,7 @@ loop: if err := d.checkDial(task.dest); err != nil { d.log.Trace("Discarding dial consensus node", "id", task.dest.ID(), "ip", task.dest.IP(), "reason", err) if task.doneHook != nil { - task.doneHook() + task.doneHook(err) } } else { d.startDial(task) @@ -292,7 +292,7 @@ loop: d.updateStaticPool(id) d.doneSinceLastLog++ if task.doneHook != nil { - task.doneHook() + task.doneHook(nil) } case c := <-d.addPeerCh: @@ -511,10 +511,10 @@ type dialTask struct { dest *enode.Node lastResolved mclock.AbsTime resolveDelay time.Duration - doneHook func() + doneHook func(err error) } -func newDialTask(dest *enode.Node, flags connFlag, done func()) *dialTask { +func newDialTask(dest *enode.Node, flags connFlag, done func(err error)) *dialTask { return &dialTask{dest: dest, flags: flags, staticPoolIndex: -1, doneHook: done} } diff --git a/p2p/discover.go b/p2p/discover.go index f1f204b8dc..a64a1a2847 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -46,7 +46,7 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { currentConnectPeer[id] = struct{}{} } }) - + // 找到没有连接的节点 for i := 0; i < len(copyNodes); { if copyNodes[i].ID() == srv.localnode.ID() { copyNodes = append(copyNodes[:i], copyNodes[i+1:]...) @@ -96,23 +96,30 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes [ if threshold > 6 { threshold = threshold / 2 } + var dialShouldReTry int + //持续发现节点,并且过滤掉dial失败的节点 for i := 0; i < len(indexs); i++ { wg.Add(1) - srv.AddConsensusPeerWithDone(nodes[indexs[i]], func() { + srv.AddConsensusPeerWithDone(nodes[indexs[i]], func(err error) { + if err != nil { + dialShouldReTry++ + } wg.Done() }) threshold-- if threshold == 0 { + wg.Wait() + if dialShouldReTry > 0 { + threshold += dialShouldReTry + dialShouldReTry = 0 + } break } } - - // Wait for all dials to be completed. - wg.Wait() currNum := len(srv.pubSubServer.PubSub().ListPeers(topic)) - log.Trace("Searching network for peers subscribed to the topic done.", "topic", topic, "peers", currNum) + log.Trace("Searching network for peers subscribed to the topic done.", "topic", topic, "peers", currNum, "dialShouldReTry", dialShouldReTry) return nil } diff --git a/p2p/server.go b/p2p/server.go index 8d702e8ebb..174cd124b1 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -399,7 +399,7 @@ func (srv *Server) AddConsensusPeer(node *enode.Node) { } } -func (srv *Server) AddConsensusPeerWithDone(node *enode.Node, done func()) { +func (srv *Server) AddConsensusPeerWithDone(node *enode.Node, done func(err error)) { select { case srv.addconsensus <- newDialTask(node, consensusDialedConn, done): case <-srv.quit: @@ -811,7 +811,7 @@ running: srv.log.Debug("We are become an consensus node") srv.consensus = true if task.doneHook != nil { - task.doneHook() + task.doneHook(errSelf) } } else { consensusNodes[id] = true @@ -819,7 +819,7 @@ running: srv.log.Debug("Add consensus flag", "peer", id) p.rw.set(consensusDialedConn, true) if task.doneHook != nil { - task.doneHook() + task.doneHook(errAlreadyConnected) } } else { srv.dialsched.addConsensus(task) From 740f736d5216a2f740d00ea928adce69e2df8301 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 12 Jan 2022 14:06:13 +0800 Subject: [PATCH 226/299] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E4=BA=BA=E5=88=87=E6=8D=A2=E6=97=B6=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- consensus/cbft/validator/validator.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index b65ee7273d..84bd4f7575 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -368,7 +368,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID } } } - log.Debug("Update validator", "validators", pool.currentValidators.String(), "switchpoint", pool.switchPoint, "epoch", pool.epoch, "lastNumber", pool.lastNumber) + log.Debug("NewValidatorPool:", "validators", pool.currentValidators.String(), "switchpoint", pool.switchPoint, "epoch", pool.epoch, "lastNumber", pool.lastNumber) return pool } @@ -479,6 +479,7 @@ func (vp *ValidatorPool) Update(blockHash common.Hash, blockNumber uint64, epoch vp.nextValidators = nil //切换共识轮时需要将上一轮分组的topic取消订阅 + // TODO 考虑到区块尚未QC,此时断开链接不太事宜,待改进 vp.dissolve(currEpoch, eventMux) //旧版本(非分组共识)需要发events断开落选的共识节点 if !vp.grouped { @@ -495,20 +496,23 @@ func (vp *ValidatorPool) InitComingValidators(blockHash common.Hash, blockNumber //分组提案生效后第一个共识round到ElectionPoint时初始化分组信息 if !vp.grouped { - vp.grouped = true vp.groupValidatorsLimit = xcom.MaxGroupValidators() vp.coordinatorLimit = xcom.CoordinatorsLimit() + // 提案生效后第一个选举块,此时因ConsensusSize更新到新值(215)需要更新vp.lastNumber + vp.lastNumber = vp.agency.GetLastNumber(blockNumber) } // 提前更新nextValidators,为了p2p早一步订阅分组事件以便建链接 nds, err := vp.agency.GetValidators(blockHash, blockNumber+xcom.ElectionDistance()+1) if err != nil { - log.Error("Get validators error", "blockNumber", blockNumber, "err", err) + log.Info("InitComingValidators:Get validators error", "blockNumber", blockNumber, "err", err) + //如果没查到,说明是提案生效后首次走选举块逻辑,不是真正的选举块,此时还没真正选举,所以查不到 return err } + // 如果是提案生效后第一个选举块,只有新ConsensusSize和旧ConsensusSize一样才会走下面的逻辑 vp.nextValidators = nds vp.organize(vp.nextValidators, vp.epoch+1, eventMux) - log.Debug("Update nextValidators OK", "blockNumber", blockNumber, "epoch", vp.epoch+1) + log.Debug("InitComingValidators:Update nextValidators OK", "blockNumber", blockNumber, "epoch", vp.epoch+1) return nil } From 578c9fb67e0949f001c79394a5c1b167d9a2fb4f Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 12 Jan 2022 14:08:00 +0800 Subject: [PATCH 227/299] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E4=BA=BA=E5=88=87=E6=8D=A2=E6=97=B6=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- consensus/cbft/validator/validator_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index 220ea4fd04..b266e73c2d 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -657,7 +657,7 @@ func TestUpdate(t *testing.T) { assert.Equal(t, vp.epoch, uint64(0)) vp.InitComingValidators(common.ZeroHash, 980, eventMux) - assert.True(t, vp.NeedGroup()) + assert.False(t, vp.NeedGroup()) assert.Equal(t, vp.epoch, uint64(0)) nextNodes := newTestNodeByNum(100) From 28ff92bdc77f3aa58008afcd92d2fae5e5810cec Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Wed, 12 Jan 2022 14:28:41 +0800 Subject: [PATCH 228/299] If publishTopicMsg fails, the local consensus message is still saved --- consensus/cbft/consensus_process.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index a5f8a24f37..592efa4350 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -396,13 +396,11 @@ func (cbft *Cbft) OnViewTimeout() { cbft.bridge.SendViewChange(viewChange) } + cbft.state.AddViewChange(node.Index, viewChange) // send viewChange use pubsub if err := cbft.publishTopicMsg(viewChange); err != nil { cbft.log.Error("Publish viewChange failed", "err", err.Error(), "view", cbft.state.ViewString(), "viewChange", viewChange.String()) - return } - - cbft.state.AddViewChange(node.Index, viewChange) //cbft.network.Broadcast(viewChange) cbft.log.Info("Local add viewChange", "index", node.Index, "viewChange", viewChange.String(), "total", cbft.state.ViewChangeLen()) @@ -610,6 +608,12 @@ func (cbft *Cbft) trySendPrepareVote() { } if b, qc := cbft.blockTree.FindBlockAndQC(block.ParentHash(), block.NumberU64()-1); b != nil || block.NumberU64() == 0 { p.ParentQC = qc + hadSend.Push(p) + //Determine if the current consensus node is + node, _ := cbft.isCurrentValidator() + cbft.log.Info("Add local prepareVote", "vote", p.String()) + cbft.state.AddPrepareVote(node.Index, p) + pending.Pop() // write sendPrepareVote info to wal if !cbft.isLoading() { @@ -619,15 +623,7 @@ func (cbft *Cbft) trySendPrepareVote() { // send prepareVote use pubsub if err := cbft.publishTopicMsg(p); err != nil { cbft.log.Error("Publish PrepareVote failed", "err", err.Error(), "view", cbft.state.ViewString(), "vote", p.String()) - break } - hadSend.Push(p) - //Determine if the current consensus node is - node, _ := cbft.isCurrentValidator() - cbft.log.Info("Add local prepareVote", "vote", p.String()) - cbft.state.AddPrepareVote(node.Index, p) - pending.Pop() - //cbft.network.Broadcast(p) } else { break From 52dc0773b58b63bba20d8bd33389a4a53846ecd4 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 12 Jan 2022 15:09:35 +0800 Subject: [PATCH 229/299] fix discover --- p2p/discover.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/p2p/discover.go b/p2p/discover.go index a64a1a2847..dcceba7f93 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -113,8 +113,9 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes [ if dialShouldReTry > 0 { threshold += dialShouldReTry dialShouldReTry = 0 + } else { + break } - break } } currNum := len(srv.pubSubServer.PubSub().ListPeers(topic)) From b77b45e3603d624e3c490990e5f6afcb20f78bf7 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 12 Jan 2022 15:16:39 +0800 Subject: [PATCH 230/299] update log --- p2p/discover.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/discover.go b/p2p/discover.go index dcceba7f93..1312cd013d 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -65,7 +65,7 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { if peerNeedFind > len(copyNodes) { peerNeedFind = len(copyNodes) } - log.Debug("not enough nodes in this topic,searching network", "topic", topic, "peers", len(peers), "peerNeedFind", peerNeedFind, "minimumPeersPerTopic", srv.Config.MinimumPeersPerTopic) + log.Debug("not enough nodes in this topic,searching network", "topic", topic, "peers", len(peers), "remainNodes", len(copyNodes), "peerNeedFind", peerNeedFind, "minimumPeersPerTopic", srv.Config.MinimumPeersPerTopic) if err := srv.FindPeersWithTopic(ctx, topic, copyNodes, peerNeedFind); err != nil { log.Debug("Could not search for peers", "err", err) return From bcc5407ef661a34c350aad97f6940e60282b6002 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 12 Jan 2022 16:05:55 +0800 Subject: [PATCH 231/299] fix dial --- p2p/dial.go | 2 +- p2p/server.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/p2p/dial.go b/p2p/dial.go index 23ac40f2cf..6882e99cb0 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -539,7 +539,7 @@ func (t *dialTask) run(d *dialScheduler) { } func (t *dialTask) needResolve() bool { - return (t.flags&staticDialedConn != 0) && t.dest.IP() == nil + return (t.flags&staticDialedConn != 0 || t.flags&consensusDialedConn != 0) && t.dest.IP() == nil } // resolve attempts to find the current endpoint for the destination diff --git a/p2p/server.go b/p2p/server.go index 174cd124b1..54aa4edb01 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -805,7 +805,7 @@ running: case task := <-srv.addconsensus: // This channel is used by AddConsensusNode to add an enode // to the consensus node set. - srv.log.Trace("Adding consensus node", "node", task.dest) + srv.log.Trace("Adding consensus node", "node", task.dest.ID()) id := task.dest.ID() if bytes.Equal(crypto.Keccak256(srv.ourHandshake.ID), id[:]) { srv.log.Debug("We are become an consensus node") @@ -819,7 +819,7 @@ running: srv.log.Debug("Add consensus flag", "peer", id) p.rw.set(consensusDialedConn, true) if task.doneHook != nil { - task.doneHook(errAlreadyConnected) + task.doneHook(nil) } } else { srv.dialsched.addConsensus(task) From 2aabf4dc6042f99cffe19c9a77cd5adfb2677413 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 12 Jan 2022 16:18:26 +0800 Subject: [PATCH 232/299] fix --- p2p/server.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/p2p/server.go b/p2p/server.go index 54aa4edb01..95591c0c55 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -816,10 +816,16 @@ running: } else { consensusNodes[id] = true if p, ok := peers[id]; ok { - srv.log.Debug("Add consensus flag", "peer", id) - p.rw.set(consensusDialedConn, true) - if task.doneHook != nil { - task.doneHook(nil) + if !p.rw.is(consensusDialedConn) { + srv.log.Debug("Add consensus flag", "peer", id) + p.rw.set(consensusDialedConn, true) + if task.doneHook != nil { + task.doneHook(nil) + } + } else { + if task.doneHook != nil { + task.doneHook(errAlreadyConnected) + } } } else { srv.dialsched.addConsensus(task) From eddb2a9a7d662a935c8515a2adb91c08380cf182 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 12 Jan 2022 17:54:18 +0800 Subject: [PATCH 233/299] fix --- p2p/discover.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/p2p/discover.go b/p2p/discover.go index 1312cd013d..a385d7e13a 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -42,7 +42,7 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { currentConnectPeer := make(map[enode.ID]struct{}) srv.doPeerOp(func(m map[enode.ID]*Peer) { - for id, _ := range currentConnectPeer { + for id, _ := range m { currentConnectPeer[id] = struct{}{} } }) @@ -120,7 +120,7 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes [ } currNum := len(srv.pubSubServer.PubSub().ListPeers(topic)) - log.Trace("Searching network for peers subscribed to the topic done.", "topic", topic, "peers", currNum, "dialShouldReTry", dialShouldReTry) + log.Trace("Searching network for peers subscribed to the topic done.", "topic", topic, "peers", currNum, "dialShouldReTry", threshold) return nil } From 0075e8e556d06be2a3d647fe6b2f73893751359b Mon Sep 17 00:00:00 2001 From: benbaley Date: Thu, 13 Jan 2022 11:02:51 +0800 Subject: [PATCH 234/299] add GroupTopicEvent.PubSub --- consensus/cbft/network/pubsub.go | 25 +++++++++++++++++++++++-- consensus/cbft/validator/validator.go | 22 +++++++++++++++++----- core/cbfttypes/type.go | 11 ++++++++++- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index 853239bea7..68229f2e4a 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -21,9 +21,10 @@ import ( "context" "errors" "fmt" + "sync" + "github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" "github.com/AlayaNetwork/Alaya-Go/event" - "sync" "github.com/AlayaNetwork/Alaya-Go/common" ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" @@ -152,7 +153,12 @@ func (ps *PubSub) watching(eventMux *event.TypeMux) { switch data := ev.Data.(type) { case cbfttypes.GroupTopicEvent: log.Trace("Received GroupTopicEvent", "topic", data.Topic) - ps.Subscribe(data.Topic) + // 需要订阅主题(发现节点并接收topic消息) + if data.PubSub { + ps.Subscribe(data.Topic) + } else { // 只需要发现节点,不需要接收对应topic消息 + ps.DiscoverTopic(data.Topic) + } case cbfttypes.ExpiredGroupTopicEvent: log.Trace("Received ExpiredGroupTopicEvent", "topic", data.Topic) ps.Cancel(data.Topic) @@ -192,6 +198,21 @@ func (ps *PubSub) Subscribe(topic string) error { return nil } +// 发现topic对应的节点 +func (ps *PubSub) DiscoverTopic(topic string) error { + ps.Lock() + defer ps.Unlock() + if _, ok := ps.topicCtx[topic]; ok { + return ErrExistsTopic + } + ctx, cancel := context.WithCancel(context.Background()) + ps.pss.DiscoverTopic(ctx, topic) + + ps.topicCtx[topic] = ctx + ps.topicCancel[topic] = cancel + return nil +} + func (ps *PubSub) listen(s *pubsub.Subscription) { for { subMsg, err := s.Next(context.Background()) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 84bd4f7575..0f2858233a 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -940,12 +940,23 @@ func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64 consensusNodes := validators.NodeList() groupNodes := gvs.NodeList() + + // 选节点时要把所有共识节点中去掉本组节点 + otherConsensusNodes := make([]*enode.Node, 0) + for _, node := range consensusNodes { + if gvs.IsOurs(node.ID()) { + continue + } else { + otherConsensusNodes = append(otherConsensusNodes, node) + } + } consensusTopic := cbfttypes.ConsensusTopicName(epoch) groupTopic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) - eventMux.Post(cbfttypes.NewTopicEvent{Topic: consensusTopic, Nodes: consensusNodes}) + eventMux.Post(cbfttypes.NewTopicEvent{Topic: consensusTopic, Nodes: otherConsensusNodes}) eventMux.Post(cbfttypes.NewTopicEvent{Topic: groupTopic, Nodes: groupNodes}) - eventMux.Post(cbfttypes.GroupTopicEvent{Topic: groupTopic}) + eventMux.Post(cbfttypes.GroupTopicEvent{Topic: groupTopic, PubSub: true}) + eventMux.Post(cbfttypes.GroupTopicEvent{Topic: consensusTopic, PubSub: false}) return nil } @@ -963,7 +974,8 @@ func (vp *ValidatorPool) dissolve(epoch uint64, eventMux *event.TypeMux) { consensusTopic := cbfttypes.ConsensusTopicName(epoch) groupTopic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) - eventMux.Post(cbfttypes.ExpiredTopicEvent{Topic: consensusTopic}) // for p2p - eventMux.Post(cbfttypes.ExpiredTopicEvent{Topic: groupTopic}) // for p2p - eventMux.Post(cbfttypes.ExpiredGroupTopicEvent{Topic: groupTopic}) // for cbft + eventMux.Post(cbfttypes.ExpiredTopicEvent{Topic: consensusTopic}) // for p2p + eventMux.Post(cbfttypes.ExpiredTopicEvent{Topic: groupTopic}) // for p2p + eventMux.Post(cbfttypes.ExpiredGroupTopicEvent{Topic: groupTopic}) // for cbft + eventMux.Post(cbfttypes.ExpiredGroupTopicEvent{Topic: consensusTopic}) // for cbft } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 8bae9d198d..47eaabb0ba 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -82,7 +82,8 @@ type ExpiredTopicEvent struct { } type GroupTopicEvent struct { - Topic string // consensus:{epoch}:{groupID} + Topic string // consensus:{epoch}:{groupID} + PubSub bool //是否需要pubsub } type ExpiredGroupTopicEvent ExpiredTopicEvent // consensus:{epoch}:{groupID} @@ -394,6 +395,14 @@ func (gvs *GroupValidators) GetUnitID(id enode.ID) (uint32, error) { return uint32(0), errors.New("not found the specified validator") } +func (gvs *GroupValidators) IsOurs(id enode.ID) bool { + _, ok := gvs.nodesMap[id] + if ok { + return true + } + return false +} + // return groupID func (gvs *GroupValidators) GetGroupID() uint32 { return gvs.groupID From b0c4d694193f7eb443b7f01a1184cd90cb186b18 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Thu, 13 Jan 2022 11:42:58 +0800 Subject: [PATCH 235/299] update for conseunes topic --- p2p/discover.go | 105 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 77 insertions(+), 28 deletions(-) diff --git a/p2p/discover.go b/p2p/discover.go index a385d7e13a..b7c25cdec6 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -2,6 +2,7 @@ package p2p import ( "context" + "fmt" "math/rand" "sync" "time" @@ -27,37 +28,18 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { continue } // Check there are enough peers - peers := srv.pubSubServer.PubSub().ListPeers(topic) - peerNeedFind := srv.Config.MinimumPeersPerTopic - len(peers) - if peerNeedFind > 0 { - srv.topicSubscriberMu.RLock() - nodes, ok := srv.topicSubscriber[topic] - if !ok { - srv.topicSubscriberMu.RUnlock() + if srv.topicWithPubsub(topic) { + peers := srv.pubSubServer.PubSub().ListPeers(topic) + peerNeedFind := srv.Config.MinimumPeersPerTopic - len(peers) + if peerNeedFind <= 0 { continue } - copyNodes := make([]*enode.Node, len(nodes)) - copy(copyNodes, nodes) - srv.topicSubscriberMu.RUnlock() - - currentConnectPeer := make(map[enode.ID]struct{}) - srv.doPeerOp(func(m map[enode.ID]*Peer) { - for id, _ := range m { - currentConnectPeer[id] = struct{}{} - } - }) - // 找到没有连接的节点 - for i := 0; i < len(copyNodes); { - if copyNodes[i].ID() == srv.localnode.ID() { - copyNodes = append(copyNodes[:i], copyNodes[i+1:]...) - continue - } - if _, ok := currentConnectPeer[copyNodes[i].ID()]; ok { - copyNodes = append(copyNodes[:i], copyNodes[i+1:]...) - continue - } - i++ + copyNodes, err := srv.getNotConnectNode(topic) + if err != nil { + log.Error("discover topic fail", "err", err) + return } + if len(copyNodes) == 0 { log.Debug("all peers are found,no need searching network", "topic", topic, "peers", len(peers)) continue @@ -70,12 +52,79 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { log.Debug("Could not search for peers", "err", err) return } + } else { + copyNodes, err := srv.getNotConnectNode(topic) + if err != nil { + log.Error("discover topic fail", "err", err) + return + } + if len(copyNodes) == 0 { + log.Debug("all peers are found,no need searching network", "topic", topic) + continue + } + srv.topicSubscriberMu.RLock() + nodes := srv.topicSubscriber[topic] + srv.topicSubscriberMu.RUnlock() + peerNeedFind := srv.MinimumPeersPerTopic - len(nodes) + len(copyNodes) + if peerNeedFind <= 0 { + continue + } + if peerNeedFind > len(copyNodes) { + peerNeedFind = len(copyNodes) + } + log.Debug("not enough nodes in this topic,searching network", "topic", topic, "remainNodes", len(copyNodes), "peerNeedFind", peerNeedFind, "minimumPeersPerTopic", srv.Config.MinimumPeersPerTopic) + if err := srv.FindPeersWithTopic(ctx, topic, copyNodes, peerNeedFind); err != nil { + log.Debug("Could not search for peers", "err", err) + return + } } } } }() } +func (srv *Server) topicWithPubsub(topic string) bool { + topics := srv.pubSubServer.PubSub().GetTopics() + for _, s := range topics { + if s == topic { + return true + } + } + return false +} + +func (srv *Server) getNotConnectNode(topic string) ([]*enode.Node, error) { + srv.topicSubscriberMu.RLock() + nodes, ok := srv.topicSubscriber[topic] + if !ok { + srv.topicSubscriberMu.RUnlock() + return nil, fmt.Errorf("the topic %s should discover can't find", topic) + } + copyNodes := make([]*enode.Node, len(nodes)) + copy(copyNodes, nodes) + srv.topicSubscriberMu.RUnlock() + + currentConnectPeer := make(map[enode.ID]struct{}) + srv.doPeerOp(func(m map[enode.ID]*Peer) { + for id, _ := range m { + currentConnectPeer[id] = struct{}{} + } + }) + // 找到没有连接的节点 + for i := 0; i < len(copyNodes); { + if copyNodes[i].ID() == srv.localnode.ID() { + copyNodes = append(copyNodes[:i], copyNodes[i+1:]...) + continue + } + if _, ok := currentConnectPeer[copyNodes[i].ID()]; ok { + copyNodes = append(copyNodes[:i], copyNodes[i+1:]...) + continue + } + i++ + } + return copyNodes, nil +} + // FindPeersWithTopic performs a network search for peers // subscribed to a particular subnet. Then we try to connect // with those peers. This method will block until the required amount of From 42dd8840085f116eaea5230b016cfaa76996ac0f Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Thu, 13 Jan 2022 16:43:14 +0800 Subject: [PATCH 236/299] change the name --- p2p/discover.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/discover.go b/p2p/discover.go index b7c25cdec6..b9caf7c3bd 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -83,7 +83,7 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { }() } -func (srv *Server) topicWithPubsub(topic string) bool { +func (srv *Server) topicWithPubSub(topic string) bool { topics := srv.pubSubServer.PubSub().GetTopics() for _, s := range topics { if s == topic { From 3bf8bd8d16e748978212a531e88d88ad25e04a25 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Thu, 13 Jan 2022 16:44:37 +0800 Subject: [PATCH 237/299] change the name --- p2p/discover.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/discover.go b/p2p/discover.go index b9caf7c3bd..608ce43e75 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -28,7 +28,7 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { continue } // Check there are enough peers - if srv.topicWithPubsub(topic) { + if srv.topicWithPubSub(topic) { peers := srv.pubSubServer.PubSub().ListPeers(topic) peerNeedFind := srv.Config.MinimumPeersPerTopic - len(peers) if peerNeedFind <= 0 { From 6a12941815a7d832844d6bc7e3d393653c5a4e7f Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 17 Jan 2022 10:28:19 +0800 Subject: [PATCH 238/299] private chain supprot 0.16.0 update to 0.17.0 --- core/genesis.go | 49 ++++++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/core/genesis.go b/core/genesis.go index e146457a42..c24e9c0500 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -297,6 +297,9 @@ func (g *Genesis) UnmarshalEconomicConfigExtend(file *os.File) error { Reward xcom.RewardConfigExtend `json:"reward"` Restricting xcom.RestrictingConfigExtend `json:"restricting"` } `json:"economicModel"` + Config *struct { + GenesisVersion uint32 `json:"genesisVersion"` + } `json:"config"` } file.Seek(0, io.SeekStart) if err := json.NewDecoder(file).Decode(&genesisEcConfig); err != nil { @@ -312,33 +315,37 @@ func (g *Genesis) UnmarshalEconomicConfigExtend(file *os.File) error { newEce.Restricting.MinimumRelease = genesisEcConfig.EconomicModel.Restricting.MinimumRelease } } - - file.Seek(0, io.SeekStart) - var genesis0170EcConfig struct { - EconomicModel *xcom.EconomicModel0170Extend `json:"economicModel"` - } - if err := json.NewDecoder(file).Decode(&genesis0170EcConfig); err != nil { - return fmt.Errorf("invalid genesis file for genesis0170EcConfig: %v", err) + if nil == genesisEcConfig.Config { + return errors.New("genesis configuration is missed") } - if genesis0170EcConfig.EconomicModel != nil { - if genesis0170EcConfig.EconomicModel.Common.MaxGroupValidators != 0 { - newEce.Extend0170.Common.MaxGroupValidators = genesis0170EcConfig.EconomicModel.Common.MaxGroupValidators + if genesisEcConfig.Config.GenesisVersion >= params.FORKVERSION_0_17_0 { + file.Seek(0, io.SeekStart) + var genesis0170EcConfig struct { + EconomicModel *xcom.EconomicModel0170Extend `json:"economicModel"` } - if genesis0170EcConfig.EconomicModel.Common.CoordinatorsLimit != 0 { - newEce.Extend0170.Common.CoordinatorsLimit = genesis0170EcConfig.EconomicModel.Common.CoordinatorsLimit - } - if genesis0170EcConfig.EconomicModel.Common.MaxConsensusVals != 0 { - newEce.Extend0170.Common.MaxConsensusVals = genesis0170EcConfig.EconomicModel.Common.MaxConsensusVals + if err := json.NewDecoder(file).Decode(&genesis0170EcConfig); err != nil { + return fmt.Errorf("invalid genesis file for genesis0170EcConfig: %v", err) } + if genesis0170EcConfig.EconomicModel != nil { + if genesis0170EcConfig.EconomicModel.Common.MaxGroupValidators != 0 { + newEce.Extend0170.Common.MaxGroupValidators = genesis0170EcConfig.EconomicModel.Common.MaxGroupValidators + } + if genesis0170EcConfig.EconomicModel.Common.CoordinatorsLimit != 0 { + newEce.Extend0170.Common.CoordinatorsLimit = genesis0170EcConfig.EconomicModel.Common.CoordinatorsLimit + } + if genesis0170EcConfig.EconomicModel.Common.MaxConsensusVals != 0 { + newEce.Extend0170.Common.MaxConsensusVals = genesis0170EcConfig.EconomicModel.Common.MaxConsensusVals + } - if genesis0170EcConfig.EconomicModel.Staking.MaxValidators != 0 { - newEce.Extend0170.Staking.MaxValidators = genesis0170EcConfig.EconomicModel.Staking.MaxValidators - } + if genesis0170EcConfig.EconomicModel.Staking.MaxValidators != 0 { + newEce.Extend0170.Staking.MaxValidators = genesis0170EcConfig.EconomicModel.Staking.MaxValidators + } - if genesis0170EcConfig.EconomicModel.Slashing.ZeroProduceCumulativeTime != 0 { - newEce.Extend0170.Slashing.ZeroProduceCumulativeTime = genesis0170EcConfig.EconomicModel.Slashing.ZeroProduceCumulativeTime - } + if genesis0170EcConfig.EconomicModel.Slashing.ZeroProduceCumulativeTime != 0 { + newEce.Extend0170.Slashing.ZeroProduceCumulativeTime = genesis0170EcConfig.EconomicModel.Slashing.ZeroProduceCumulativeTime + } + } } xcom.ResetEconomicExtendConfig(newEce) From 8526be923c4a951eb987b5e2941b9dfeeab96d90 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Tue, 18 Jan 2022 10:16:55 +0800 Subject: [PATCH 239/299] Modify the function to get the PubSub status --- p2p/pubsub/pubsub.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 6448b504fb..c06dfb1d83 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -1362,10 +1362,10 @@ type addRelayReq struct { } type Status struct { - Peers map[enode.ID]ProtocolID `json:"peers"` - Mesh map[string][]enode.ID `json:"mesh"` - OutMesh map[string][]enode.ID `json:"outMesh"` - Topics []string `json:"topics"` + Peers []enode.ID `json:"peers"` + Mesh map[string][]enode.ID `json:"mesh"` + OutMesh map[string][]enode.ID `json:"outMesh"` + Topics []string `json:"topics"` } func (p *PubSub) GetAllPubSubStatus() *Status { @@ -1376,8 +1376,10 @@ func (p *PubSub) GetAllPubSubStatus() *Status { if !ok { return } - newPeers := gsr.peers - status.Peers = newPeers + status.Peers = make([]enode.ID, len(gsr.peers)) + for pid := range gsr.peers { + status.Peers = append(status.Peers, pid) + } newMesh := make(map[string][]enode.ID, len(gsr.mesh)) for topic, nodeIdMap := range gsr.mesh { From 881af4e5fbbd6832574c2044b7f470c6c94ccbbf Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 18 Jan 2022 11:34:47 +0800 Subject: [PATCH 240/299] update MinimumPeersPerTopic --- core/genesis.go | 1 - node/defaults.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/core/genesis.go b/core/genesis.go index c24e9c0500..3830e03f63 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -344,7 +344,6 @@ func (g *Genesis) UnmarshalEconomicConfigExtend(file *os.File) error { if genesis0170EcConfig.EconomicModel.Slashing.ZeroProduceCumulativeTime != 0 { newEce.Extend0170.Slashing.ZeroProduceCumulativeTime = genesis0170EcConfig.EconomicModel.Slashing.ZeroProduceCumulativeTime } - } } diff --git a/node/defaults.go b/node/defaults.go index cb48270971..4c0d86896e 100644 --- a/node/defaults.go +++ b/node/defaults.go @@ -48,7 +48,7 @@ var DefaultConfig = Config{ MaxPeers: 60, NAT: nat.Any(), MaxConsensusPeers: 40, - MinimumPeersPerTopic: 5, + MinimumPeersPerTopic: 6, }, } From ec2cfd2d29224ef4ad9899432be1996752c8b2bc Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 18 Jan 2022 11:39:19 +0800 Subject: [PATCH 241/299] update log --- p2p/discover.go | 16 ++++++++-------- p2p/server.go | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/p2p/discover.go b/p2p/discover.go index 608ce43e75..d1c9e75908 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -36,18 +36,18 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { } copyNodes, err := srv.getNotConnectNode(topic) if err != nil { - log.Error("discover topic fail", "err", err) + srv.log.Error("discover topic fail", "err", err) return } if len(copyNodes) == 0 { - log.Debug("all peers are found,no need searching network", "topic", topic, "peers", len(peers)) + srv.log.Debug("all peers are found,no need searching network", "topic", topic, "peers", len(peers)) continue } if peerNeedFind > len(copyNodes) { peerNeedFind = len(copyNodes) } - log.Debug("not enough nodes in this topic,searching network", "topic", topic, "peers", len(peers), "remainNodes", len(copyNodes), "peerNeedFind", peerNeedFind, "minimumPeersPerTopic", srv.Config.MinimumPeersPerTopic) + srv.log.Debug("not enough nodes in this topic,searching network", "topic", topic, "peers", len(peers), "remainNodes", len(copyNodes), "peerNeedFind", peerNeedFind, "minimumPeersPerTopic", srv.Config.MinimumPeersPerTopic) if err := srv.FindPeersWithTopic(ctx, topic, copyNodes, peerNeedFind); err != nil { log.Debug("Could not search for peers", "err", err) return @@ -55,11 +55,11 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { } else { copyNodes, err := srv.getNotConnectNode(topic) if err != nil { - log.Error("discover topic fail", "err", err) + srv.log.Error("discover topic fail", "err", err) return } if len(copyNodes) == 0 { - log.Debug("all peers are found,no need searching network", "topic", topic) + srv.log.Debug("all peers are found,no need searching network", "topic", topic) continue } srv.topicSubscriberMu.RLock() @@ -72,9 +72,9 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { if peerNeedFind > len(copyNodes) { peerNeedFind = len(copyNodes) } - log.Debug("not enough nodes in this topic,searching network", "topic", topic, "remainNodes", len(copyNodes), "peerNeedFind", peerNeedFind, "minimumPeersPerTopic", srv.Config.MinimumPeersPerTopic) + srv.log.Debug("not enough nodes in this topic,searching network", "topic", topic, "remainNodes", len(copyNodes), "peerNeedFind", peerNeedFind, "minimumPeersPerTopic", srv.Config.MinimumPeersPerTopic) if err := srv.FindPeersWithTopic(ctx, topic, copyNodes, peerNeedFind); err != nil { - log.Debug("Could not search for peers", "err", err) + srv.log.Debug("Could not search for peers", "err", err) return } } @@ -169,7 +169,7 @@ func (srv *Server) FindPeersWithTopic(ctx context.Context, topic string, nodes [ } currNum := len(srv.pubSubServer.PubSub().ListPeers(topic)) - log.Trace("Searching network for peers subscribed to the topic done.", "topic", topic, "peers", currNum, "dialShouldReTry", threshold) + srv.log.Trace("Searching network for peers subscribed to the topic done.", "topic", topic, "peers", currNum, "dialShouldReTry", threshold) return nil } diff --git a/p2p/server.go b/p2p/server.go index 95591c0c55..9b76eacec4 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -970,7 +970,7 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in if srv.consensus && len(peers) >= srv.MaxPeers && c.is(consensusDialedConn) && numConsensusPeer < srv.MaxConsensusPeers { for _, p := range peers { if p.rw.is(inboundConn|dynDialedConn) && !p.rw.is(trustedConn|staticDialedConn|consensusDialedConn) { - log.Debug("Disconnect over limit connection", "peer", p.ID(), "flags", p.rw.flags, "peers", len(peers)) + srv.log.Debug("Disconnect over limit connection", "peer", p.ID(), "flags", p.rw.flags, "peers", len(peers)) p.Disconnect(DiscRequested) break } @@ -1020,7 +1020,7 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in if count == i { peer := peers[node] if !peer.rw.is(trustedConn | staticDialedConn) { - log.Debug("Disconnect over limit consensus connection", "peer", peer.ID(), "flags", peer.rw.flags, "peers", len(peers), "topic", srv.getPeersTopics(peer.ID()), "referencedNum", i, "maxConsensusPeers", srv.MaxConsensusPeers) + srv.log.Debug("Disconnect over limit consensus connection", "peer", peer.ID(), "flags", peer.rw.flags, "peers", len(peers), "topic", srv.getPeersTopics(peer.ID()), "referencedNum", i, "maxConsensusPeers", srv.MaxConsensusPeers) peer.Disconnect(DiscRequested) disconnectConsensus++ break loop @@ -1036,7 +1036,7 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in switch { case c.is(consensusDialedConn) && numConsensusPeer-disconnectConsensus >= srv.MaxConsensusPeers: - log.Trace("fail to dial for connect Too many consensus peers", "peers", numConsensusPeer, "disconnectConsensus", disconnectConsensus, "maxConsensusPeers", srv.MaxConsensusPeers) + srv.log.Trace("fail to dial for connect Too many consensus peers", "peers", numConsensusPeer, "disconnectConsensus", disconnectConsensus, "maxConsensusPeers", srv.MaxConsensusPeers) return DiscTooManyConsensusPeers case !srv.consensus && c.is(consensusDialedConn) && len(peers) >= srv.MaxPeers: return DiscTooManyPeers @@ -1389,10 +1389,10 @@ func (srv *Server) watching() { } switch data := ev.Data.(type) { case cbfttypes.AddValidatorEvent: - log.Trace("Received AddValidatorEvent", "nodeID", data.Node.ID().String()) + srv.log.Trace("Received AddValidatorEvent", "nodeID", data.Node.ID().String()) srv.AddConsensusPeer(data.Node) case cbfttypes.RemoveValidatorEvent: - log.Trace("Received RemoveValidatorEvent", "nodeID", data.Node.ID().String()) + srv.log.Trace("Received RemoveValidatorEvent", "nodeID", data.Node.ID().String()) srv.RemoveConsensusPeer(data.Node) case cbfttypes.NewTopicEvent: srv.topicSubscriberMu.Lock() @@ -1412,7 +1412,7 @@ func (srv *Server) watching() { srv.consensus = true } srv.updateConsensusStatus <- consensusPeers - log.Trace("Received NewTopicEvent", "consensusPeers", consensusPeers) + srv.log.Trace("Received NewTopicEvent", "consensusPeers", consensusPeers) srv.topicSubscriberMu.Unlock() case cbfttypes.ExpiredTopicEvent: srv.topicSubscriberMu.Lock() @@ -1427,10 +1427,10 @@ func (srv *Server) watching() { srv.consensus = false } srv.updateConsensusStatus <- consensusPeers - log.Trace("Received ExpiredTopicEvent", "consensusPeers", consensusPeers) + srv.log.Trace("Received ExpiredTopicEvent", "consensusPeers", consensusPeers) srv.topicSubscriberMu.Unlock() default: - log.Error("Received unexcepted event") + srv.log.Error("Received unexcepted event") } case <-srv.quit: return From 081f25adb9cd2d204e527d3b589d9fc3a70fd32a Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Tue, 18 Jan 2022 11:45:17 +0800 Subject: [PATCH 242/299] fix bug and refine unit testing --- consensus/cbft/cbft_byzantine_test.go | 19 +++++++-------- consensus/cbft/cbft_test.go | 2 ++ consensus/cbft/network/handler.go | 32 +++++++++++++++++++------- consensus/cbft/network/handler_test.go | 8 +++++-- consensus/cbft/network/helper_test.go | 8 +++++-- consensus/cbft/prepare_vote_test.go | 12 ++++++---- consensus/cbft/verify_qc_test.go | 22 ++++++++++-------- 7 files changed, 67 insertions(+), 36 deletions(-) diff --git a/consensus/cbft/cbft_byzantine_test.go b/consensus/cbft/cbft_byzantine_test.go index 93ee1bf8dc..dde024e065 100644 --- a/consensus/cbft/cbft_byzantine_test.go +++ b/consensus/cbft/cbft_byzantine_test.go @@ -53,14 +53,15 @@ import ( ) const ( - fetchPrepare = "previous index block not exists" - noBaseMaxBlock = "prepareBlock is not based on viewChangeQC maxBlock" - errorViewChangeQC = "verify viewchange qc failed" - MismatchedPrepareQC = "verify prepare qc failed,not the corresponding qc" - missingViewChangeQC = "prepareBlock need ViewChangeQC" - dupBlockHash = "has duplicated blockHash" - errorSignature = "bls verifies signature fail" - enableVerifyEpoch = "enable verify epoch" + fetchPrepare = "previous index block not exists" + noBaseMaxBlock = "prepareBlock is not based on viewChangeQC maxBlock" + errorViewChangeQC = "verify viewchange qc failed" + MismatchedPrepareQC = "verify prepare qc failed,not the corresponding qc" + missingViewChangeQC = "prepareBlock need ViewChangeQC" + dupBlockHash = "has duplicated blockHash" + dupViewChangeQCSigners = "viewchangeQC has duplicate signers" + errorSignature = "verify consensus sign failed: bls verifies signature fail" + enableVerifyEpoch = "unable verify epoch" ) func MockNodes(t *testing.T, num int) []*TestCBFT { @@ -385,7 +386,7 @@ func TestPB08(t *testing.T) { err := nodes[0].engine.OnPrepareBlock("id", p) assert.NotNil(t, err) - assert.True(t, strings.HasPrefix(err.Error(), dupBlockHash)) + assert.True(t, strings.HasPrefix(err.Error(), dupViewChangeQCSigners)) } func TestPB09(t *testing.T) { diff --git a/consensus/cbft/cbft_test.go b/consensus/cbft/cbft_test.go index df0a41d622..a615e82eec 100644 --- a/consensus/cbft/cbft_test.go +++ b/consensus/cbft/cbft_test.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/AlayaNetwork/Alaya-Go/x/xcom" "math/big" "testing" "time" @@ -55,6 +56,7 @@ var ( func init() { bls.Init(bls.BLS12_381) + xcom.GetEc(xcom.DefaultUnitTestNet) } func TestThreshold(t *testing.T) { f := &Cbft{} diff --git a/consensus/cbft/network/handler.go b/consensus/cbft/network/handler.go index d011a4ac02..53e79f0ae8 100644 --- a/consensus/cbft/network/handler.go +++ b/consensus/cbft/network/handler.go @@ -279,16 +279,32 @@ func (h *EngineManager) Forwarding(nodeID string, msg types.Message) error { return nil } // PrepareBlockMsg does not forward, the message will be forwarded using PrepareBlockHash. - switch msgType { - case protocols.PrepareBlockMsg, protocols.RGBlockQuorumCertMsg, protocols.RGViewChangeQuorumCertMsg: - err := forward() - if err != nil { - messageGossipMeter.Mark(1) + if h.engine.NeedGroup() { + switch msgType { + // Grouping consensus does not require the forwarding of PrepareVoteMsg and ViewChangeMsg + // which are uniformly managed by PubSub and automatically forwarded to nodes subscribing to messages on related topics + case protocols.PrepareBlockMsg, protocols.RGBlockQuorumCertMsg, protocols.RGViewChangeQuorumCertMsg: + err := forward() + if err != nil { + messageGossipMeter.Mark(1) + } + return err + default: + log.Trace("Unmatched message type, need not to be forwarded", "type", reflect.TypeOf(msg), "msgHash", msgHash.TerminalString(), "BHash", msg.BHash().TerminalString()) + } + } else { + switch msgType { + case protocols.PrepareBlockMsg, protocols.PrepareVoteMsg, protocols.ViewChangeMsg: + err := forward() + if err != nil { + messageGossipMeter.Mark(1) + } + return err + default: + log.Trace("Unmatched message type, need not to be forwarded", "type", reflect.TypeOf(msg), "msgHash", msgHash.TerminalString(), "BHash", msg.BHash().TerminalString()) } - return err - default: - log.Trace("Unmatched message type, need not to be forwarded", "type", reflect.TypeOf(msg), "msgHash", msgHash.TerminalString(), "BHash", msg.BHash().TerminalString()) } + return nil } diff --git a/consensus/cbft/network/handler_test.go b/consensus/cbft/network/handler_test.go index 417911a7ac..2991ade5ed 100644 --- a/consensus/cbft/network/handler_test.go +++ b/consensus/cbft/network/handler_test.go @@ -106,14 +106,14 @@ func (s *fakeCbft) HighestCommitBlockBn() (uint64, common.Hash) { return s.localPeer.CommitBn(), common.Hash{} } -func (s *fakeCbft) MissingViewChangeNodes() (*protocols.GetViewChange, error) { +func (s *fakeCbft) MissingViewChangeNodes() (types.Message, error) { return &protocols.GetViewChange{ Epoch: 1, ViewNumber: 1, }, nil } -func (s *fakeCbft) MissingPrepareVote() (*protocols.GetPrepareVote, error) { +func (s *fakeCbft) MissingPrepareVote() (types.Message, error) { return &protocols.GetPrepareVote{ Epoch: 1, ViewNumber: 1, @@ -141,6 +141,10 @@ func (s *fakeCbft) BlockExists(blockNumber uint64, blockHash common.Hash) error return nil } +func (s *fakeCbft) NeedGroup() bool { + return false +} + func (s *fakeCbft) GetGroupByValidatorID(nodeID enode.ID) (uint32, uint32, error) { return 0, 0, nil } diff --git a/consensus/cbft/network/helper_test.go b/consensus/cbft/network/helper_test.go index c8335dfe54..bd5cb04162 100644 --- a/consensus/cbft/network/helper_test.go +++ b/consensus/cbft/network/helper_test.go @@ -400,14 +400,14 @@ func (s *mockCbft) HighestCommitBlockBn() (uint64, common.Hash) { return 0, common.Hash{} } -func (s *mockCbft) MissingViewChangeNodes() (*protocols.GetViewChange, error) { +func (s *mockCbft) MissingViewChangeNodes() (types2.Message, error) { return &protocols.GetViewChange{ Epoch: 1, ViewNumber: 1, }, nil } -func (s *mockCbft) MissingPrepareVote() (*protocols.GetPrepareVote, error) { +func (s *mockCbft) MissingPrepareVote() (types2.Message, error) { return &protocols.GetPrepareVote{ Epoch: 1, ViewNumber: 1, @@ -436,6 +436,10 @@ func (s *mockCbft) BlockExists(blockNumber uint64, blockHash common.Hash) error return nil } +func (s *mockCbft) NeedGroup() bool { + return false +} + func (s *mockCbft) GetGroupByValidatorID(nodeID enode.ID) (uint32, uint32, error) { return 0, 0, nil } diff --git a/consensus/cbft/prepare_vote_test.go b/consensus/cbft/prepare_vote_test.go index 03e55a22c8..ce1ca9c9c0 100644 --- a/consensus/cbft/prepare_vote_test.go +++ b/consensus/cbft/prepare_vote_test.go @@ -18,7 +18,9 @@ package cbft import ( "fmt" + "github.com/stretchr/testify/assert" "regexp" + "strings" "testing" "time" @@ -436,13 +438,13 @@ func (suit *PrepareVoteTestSuite) TestPrepareVoteWithTimeout() { } } -// The data just meets the 2f+1 prepareQC message -// Verification pass +// The data just meets the 2f+1 prepareQC message,But there aren't enough validatorsets +// Verification not pass func (suit *PrepareVoteTestSuite) TestPrepareVote2fAndOne() { qc := mockBlockQC(suit.view.allNode[0:3], suit.blockOne, 0, nil) - if err := suit.view.secondProposer().verifyPrepareQC(suit.blockOne.NumberU64(), suit.blockOne.Hash(), qc.BlockQC); err != nil { - suit.T().Fatal(err.Error()) - } + err := suit.view.secondProposer().verifyPrepareQC(suit.blockOne.NumberU64(), suit.blockOne.Hash(), qc.BlockQC) + assert.NotNil(suit.T(), err) + assert.True(suit.T(), strings.HasPrefix(err.Error(), "verify prepare qc failed: verify QuorumCert failed,mismatched validator size")) } func (cbft *Cbft) generateErrPrepareQC(votes map[uint32]*protocols.PrepareVote) *ctypes.QuorumCert { diff --git a/consensus/cbft/verify_qc_test.go b/consensus/cbft/verify_qc_test.go index 546178a24a..0b86810c5f 100644 --- a/consensus/cbft/verify_qc_test.go +++ b/consensus/cbft/verify_qc_test.go @@ -18,6 +18,8 @@ package cbft import ( "fmt" + "github.com/stretchr/testify/assert" + "strings" "testing" "time" "unsafe" @@ -122,13 +124,13 @@ func (cbft *Cbft) mockGenerateViewChangeQuorumCertWithViewNumber(qc *ctypes.Quor return cert, nil } -// Normal viewChangeQC message -// Verification pass +// Normal viewChangeQC message,But there aren't enough validatorsets +// Verification not pass func (suit *VerifyQCTestSuite) TestVerifyViewChangeQC() { qc := mockViewQC(suit.view.genesisBlock, suit.view.allNode[0:3], nil) - if err := suit.view.firstProposer().verifyViewChangeQC(qc); err != nil { - suit.T().Fatal(err.Error()) - } + err := suit.view.firstProposer().verifyViewChangeQC(qc) + assert.NotNil(suit.T(), err) + assert.True(suit.T(), strings.HasPrefix(err.Error(), "verify viewchangeQC failed,mismatched validator size")) } // Insufficient viewChangeQC message @@ -260,13 +262,13 @@ func (suit *VerifyQCTestSuite) TestPrepareQCTooBig() { } } -// Normal prepareQC -// Verification pass +// Normal prepareQC,But there aren't enough validatorsets +// Verification not pass func (suit *VerifyQCTestSuite) TestVerifyPrepareQC() { qc := mockBlockQC(suit.view.allNode[0:3], suit.blockOne, 0, nil) - if err := suit.view.firstProposer().verifyPrepareQC(suit.blockOne.NumberU64(), suit.blockOne.Hash(), qc.BlockQC); err != nil { - suit.T().Fatal(err.Error()) - } + err := suit.view.firstProposer().verifyPrepareQC(suit.blockOne.NumberU64(), suit.blockOne.Hash(), qc.BlockQC) + assert.NotNil(suit.T(), err) + assert.True(suit.T(), strings.HasPrefix(err.Error(), "verify prepare qc failed: verify QuorumCert failed,mismatched validator size")) } // Insufficient prepareVote generated by a small number of prepareQC From faa9ff90a2fa7038171ffbb89ef16349e95a8457 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Tue, 18 Jan 2022 12:03:41 +0800 Subject: [PATCH 243/299] Added RPC interface for obtaining staking nodes --- eth/backend.go | 2 +- internal/web3ext/web3ext.go | 12 ++++++++ x/plugin/api.go | 56 +++++++++++++++++++++++++++++++++++-- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index f820a9bafb..c56e1e48c6 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -443,7 +443,7 @@ func (s *Ethereum) APIs() []rpc.API { }, { Namespace: "debug", Version: "1.0", - Service: xplugin.NewPublicPPOSAPI(), + Service: xplugin.NewPublicPPOSAPI(s.APIBackend), }, { Namespace: "net", Version: "1.0", diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 8ba1bf0e2e..9e564e5b50 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -352,6 +352,18 @@ web3._extend({ name: 'getWaitSlashingNodeList', call: 'debug_getWaitSlashingNodeList', }), + new web3._extend.Method({ + name: 'getConsensusNodeList', + call: 'debug_getConsensusNodeList', + }), + new web3._extend.Method({ + name: 'getValidatorList', + call: 'debug_getValidatorList', + }), + new web3._extend.Method({ + name: 'getCandidateList', + call: 'debug_getCandidateList', + }), new web3._extend.Method({ name: 'enableDBGC', call: 'debug_enableDBGC', diff --git a/x/plugin/api.go b/x/plugin/api.go index b623974361..401e8e4251 100644 --- a/x/plugin/api.go +++ b/x/plugin/api.go @@ -14,22 +14,33 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Alaya-Go library. If not, see . - package plugin import ( + "context" "fmt" "github.com/AlayaNetwork/Alaya-Go/common" "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" + "github.com/AlayaNetwork/Alaya-Go/core/state" + "github.com/AlayaNetwork/Alaya-Go/core/types" + "github.com/AlayaNetwork/Alaya-Go/rpc" + "github.com/AlayaNetwork/Alaya-Go/x/gov" + "github.com/AlayaNetwork/Alaya-Go/x/staking" + "github.com/AlayaNetwork/Alaya-Go/x/xutil" ) +type BackendAPI interface { + StateAndHeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*state.StateDB, *types.Header, error) +} + // Provides an API interface to obtain data related to the economic model type PublicPPOSAPI struct { snapshotDB snapshotdb.DB + bkApi BackendAPI } -func NewPublicPPOSAPI() *PublicPPOSAPI { - return &PublicPPOSAPI{snapshotdb.Instance()} +func NewPublicPPOSAPI(api BackendAPI) *PublicPPOSAPI { + return &PublicPPOSAPI{snapshotDB: snapshotdb.Instance(), bkApi: api} } // Get node list of zero-out blocks @@ -40,3 +51,42 @@ func (p *PublicPPOSAPI) GetWaitSlashingNodeList() string { } return fmt.Sprintf("%+v", list) } + +// Get the list of consensus nodes for the current consensus cycle. +func (p *PublicPPOSAPI) GetConsensusNodeList(ctx context.Context) (staking.ValidatorExQueue, error) { + state, header, err := p.bkApi.StateAndHeaderByNumber(ctx, rpc.LatestBlockNumber) + if err != nil { + return nil, err + } + blockHash := common.ZeroHash + if !xutil.IsWorker(header.Extra) { + blockHash = header.CacheHash() + } + return StakingInstance().GetValidatorList(blockHash, header.Number.Uint64(), CurrentRound, QueryStartNotIrr, gov.GetCurrentActiveVersion(state)) +} + +// Get the nodes in the current settlement cycle. +func (p *PublicPPOSAPI) GetValidatorList(ctx context.Context) (staking.ValidatorExQueue, error) { + _, header, err := p.bkApi.StateAndHeaderByNumber(ctx, rpc.LatestBlockNumber) + if err != nil { + return nil, err + } + blockHash := common.ZeroHash + if !xutil.IsWorker(header.Extra) { + blockHash = header.CacheHash() + } + return StakingInstance().GetVerifierList(blockHash, header.Number.Uint64(), QueryStartNotIrr) +} + +// Get all staking nodes. +func (p *PublicPPOSAPI) GetCandidateList(ctx context.Context) (staking.CandidateHexQueue, error) { + state, header, err := p.bkApi.StateAndHeaderByNumber(ctx, rpc.LatestBlockNumber) + if err != nil { + return nil, err + } + blockHash := common.ZeroHash + if !xutil.IsWorker(header.Extra) { + blockHash = header.CacheHash() + } + return StakingInstance().GetCandidateList(blockHash, header.Number.Uint64(), state) +} From 834f2bba43ede3da5877152e13f2b5bdc5d6a209 Mon Sep 17 00:00:00 2001 From: benbaley Date: Tue, 18 Jan 2022 18:25:16 +0800 Subject: [PATCH 244/299] fix epoch wrong err --- consensus/cbft/cbft.go | 8 ++++++ consensus/cbft/validator/validator.go | 41 +++++++++++---------------- x/xcom/common_config.go | 7 +++++ 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index e59c9c68c4..a7d203279f 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -1396,6 +1396,14 @@ func (cbft *Cbft) commitBlock(commitBlock *types.Block, commitQC *ctypes.QuorumC cbft.validatorPool.InitComingValidators(cpy.Hash(), cpy.NumberU64(), cbft.eventMux) } } + + // 是否需要更新vp.lastNumber + shouldUpdateLastNumber := func() bool { + return !cbft.validatorPool.NeedGroup() && activeVersion >= params.FORKVERSION_0_17_0 && xutil.IsBeginOfConsensus(cpy.NumberU64(), activeVersion) + } + if shouldUpdateLastNumber() { + cbft.validatorPool.UpdateLastNumber(cpy.NumberU64()) + } } // Evidences implements functions in API. diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 0f2858233a..97a9f76487 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -391,7 +391,7 @@ func (vp *ValidatorPool) Reset(blockNumber uint64, epoch uint64, eventMux *event if vp.grouped { vp.organize(vp.currentValidators, epoch, eventMux) } - log.Debug("Update validator", "validators", vp.currentValidators.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) + log.Debug("Reset validator", "validators", vp.currentValidators.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) } // ShouldSwitch check if should switch validators at the moment. @@ -449,19 +449,6 @@ func (vp *ValidatorPool) Update(blockHash common.Hash, blockNumber uint64, epoch log.Error("Get validator error", "blockNumber", blockNumber, "err", err) return err } - if needGroup { - //生效后第一个共识周期的switchpoint是旧值,此时不能切换 - //判断依据是新validators和current完全相同且nextValidators为空 - if vp.currentValidators.Equal(nds) { - vp.currentValidators = nds - vp.switchPoint = nds.ValidBlockNumber - 1 - vp.lastNumber = vp.agency.GetLastNumber(blockNumber) - //不切换,所以epoch不增 - vp.grouped = false - log.Debug("update currentValidators success!", "lastNumber", vp.lastNumber, "grouped", vp.grouped, "switchPoint", vp.switchPoint) - return nil - } - } // 节点中间重启过, nextValidators没有赋值 vp.nextValidators = nds @@ -494,14 +481,6 @@ func (vp *ValidatorPool) InitComingValidators(blockHash common.Hash, blockNumber vp.lock.Lock() defer vp.lock.Unlock() - //分组提案生效后第一个共识round到ElectionPoint时初始化分组信息 - if !vp.grouped { - vp.groupValidatorsLimit = xcom.MaxGroupValidators() - vp.coordinatorLimit = xcom.CoordinatorsLimit() - // 提案生效后第一个选举块,此时因ConsensusSize更新到新值(215)需要更新vp.lastNumber - vp.lastNumber = vp.agency.GetLastNumber(blockNumber) - } - // 提前更新nextValidators,为了p2p早一步订阅分组事件以便建链接 nds, err := vp.agency.GetValidators(blockHash, blockNumber+xcom.ElectionDistance()+1) if err != nil { @@ -516,6 +495,20 @@ func (vp *ValidatorPool) InitComingValidators(blockHash common.Hash, blockNumber return nil } +// 分组共识提案生效后首个共识轮的lastnumber在StakingInstance().Adjust0170RoundValidators更新了 +// 按正常逻辑,第一个共识轮时vp.lastnumber还是旧值,需要择机更新 +func (vp *ValidatorPool) UpdateLastNumber(blockNumber uint64) { + vp.lock.Lock() + defer vp.lock.Unlock() + + if !vp.grouped { + // 提案生效后第一个选举块,此时因ConsensusSize更新到新值(215)需要更新vp.lastNumber + vp.lastNumber = vp.agency.GetLastNumber(blockNumber) + } + + log.Debug("UpdateLastNumber:vp.lastNumber updated", "blockNumber", blockNumber, "epoch", vp.epoch) +} + // dealWithOldVersionEvents process version <= 0.16.0 logics func (vp *ValidatorPool) dealWithOldVersionEvents(epoch uint64, eventMux *event.TypeMux) { isValidatorBefore := vp.isValidator(epoch-1, vp.nodeID) @@ -976,6 +969,6 @@ func (vp *ValidatorPool) dissolve(epoch uint64, eventMux *event.TypeMux) { eventMux.Post(cbfttypes.ExpiredTopicEvent{Topic: consensusTopic}) // for p2p eventMux.Post(cbfttypes.ExpiredTopicEvent{Topic: groupTopic}) // for p2p - eventMux.Post(cbfttypes.ExpiredGroupTopicEvent{Topic: groupTopic}) // for cbft - eventMux.Post(cbfttypes.ExpiredGroupTopicEvent{Topic: consensusTopic}) // for cbft + eventMux.Post(cbfttypes.ExpiredGroupTopicEvent{Topic: groupTopic}) // for pubsub + eventMux.Post(cbfttypes.ExpiredGroupTopicEvent{Topic: consensusTopic}) // for pubsub } diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index 11e797ae06..85d80ad33c 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -705,12 +705,17 @@ func SetPerRoundBlocks(amount uint64) { } } +//出块间隔 func Interval() uint64 { return ec.Common.NodeBlockTimeWindow / ec.Common.PerRoundBlocks } + +//每个轮值提议人单次出块数 func BlocksWillCreate() uint64 { return ec.Common.PerRoundBlocks } + +// 每个共识周期内出块的验证人数 func MaxConsensusVals(version uint32) uint64 { if version >= params.FORKVERSION_0_17_0 { return ece.Extend0170.Common.MaxConsensusVals @@ -722,10 +727,12 @@ func AdditionalCycleTime() uint64 { return ec.Common.AdditionalCycleTime } +// 共识周期内区块数 func ConsensusSize(version uint32) uint64 { return BlocksWillCreate() * MaxConsensusVals(version) } +// 结算周期内的共识周期数 func EpochSize(version uint32) uint64 { consensusSize := ConsensusSize(version) em := MaxEpochMinutes() From 6e0fec68938ae14286ef0dd3b2beac97a40a8981 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 19 Jan 2022 14:24:48 +0800 Subject: [PATCH 245/299] update tests --- x/gov/proposals.go | 3 ++- x/plugin/gov_plugin.go | 3 ++- x/plugin/platon_plugin_test.go | 10 +++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/x/gov/proposals.go b/x/gov/proposals.go index 8ae8273a0f..e573b1213f 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -220,7 +220,8 @@ func (vp *VersionProposal) GetNewVersion() uint32 { func (vp *VersionProposal) GetActiveBlock(version uint32) uint64 { if vp.NewVersion == params.FORKVERSION_0_17_0 { - return xutil.CalculateEpoch(vp.ActiveBlock, version)*xutil.CalcBlocksEachEpoch(version) + 1 + epoch := xutil.CalculateEpoch(vp.ActiveBlock-1, version) + return epoch*xutil.CalcBlocksEachEpoch(version) + 1 } return vp.ActiveBlock } diff --git a/x/plugin/gov_plugin.go b/x/plugin/gov_plugin.go index 929ed8ad46..96b36465c3 100644 --- a/x/plugin/gov_plugin.go +++ b/x/plugin/gov_plugin.go @@ -96,7 +96,8 @@ func (govPlugin *GovPlugin) BeginBlock(blockHash common.Hash, header *types.Head if isVersionProposal { //log.Debug("found pre-active version proposal", "proposalID", preActiveVersionProposalID, "blockNumber", blockNumber, "blockHash", blockHash, "activeBlockNumber", versionProposal.GetActiveBlock()) - if blockNumber == versionProposal.GetActiveBlock(header.GetActiveVersion()) { + activeNumber := versionProposal.GetActiveBlock(header.GetActiveVersion()) + if blockNumber == activeNumber { if params.LtMinorVersion(versionProposal.NewVersion) { panic(fmt.Sprintf("Please upgrade to:%s", params.FormatVersion(versionProposal.NewVersion))) } diff --git a/x/plugin/platon_plugin_test.go b/x/plugin/platon_plugin_test.go index fc2b583b3a..eb578ec3d3 100644 --- a/x/plugin/platon_plugin_test.go +++ b/x/plugin/platon_plugin_test.go @@ -56,7 +56,7 @@ func init() { bls.Init(bls.BLS12_381) } -var currentTestGenesisVersion = params.FORKVERSION_0_16_0 +var currentTestGenesisVersion = params.FORKVERSION_0_17_0 var ( nodeIdArr = []enode.IDv0{ @@ -441,8 +441,8 @@ func build_staking_data_more(block uint64) { epoch_Arr := &staking.ValidatorArray{ //Start: ((block-1)/22000)*22000 + 1, //End: ((block-1)/22000)*22000 + 22000, - Start: ((block-1)/uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)))*uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)) + 1, - End: ((block-1)/uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)))*uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)) + uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)), + Start: ((block-1)/xutil.CalcBlocksEachEpoch(currentTestGenesisVersion))*xutil.CalcBlocksEachEpoch(currentTestGenesisVersion) + 1, + End: ((block-1)/xutil.CalcBlocksEachEpoch(currentTestGenesisVersion))*xutil.CalcBlocksEachEpoch(currentTestGenesisVersion) + xutil.CalcBlocksEachEpoch(currentTestGenesisVersion), Arr: queue, } @@ -455,8 +455,8 @@ func build_staking_data_more(block uint64) { curr_Arr := &staking.ValidatorArray{ //Start: ((block-1)/250)*250 + 1, //End: ((block-1)/250)*250 + 250, - Start: ((block-1)/uint64(xcom.ConsensusSize(currentTestGenesisVersion)))*uint64(xcom.ConsensusSize(currentTestGenesisVersion)) + 1, - End: ((block-1)/uint64(xcom.ConsensusSize(currentTestGenesisVersion)))*uint64(xcom.ConsensusSize(currentTestGenesisVersion)) + uint64(xcom.ConsensusSize(currentTestGenesisVersion)), + Start: ((block-1)/xcom.ConsensusSize(currentTestGenesisVersion))*xcom.ConsensusSize(currentTestGenesisVersion) + 1, + End: ((block-1)/xcom.ConsensusSize(currentTestGenesisVersion))*xcom.ConsensusSize(currentTestGenesisVersion) + xcom.ConsensusSize(currentTestGenesisVersion), Arr: queue, } From c8fca939aea5f6332d072f785893a20a61ea9c4f Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 19 Jan 2022 14:35:36 +0800 Subject: [PATCH 246/299] fix mainnet MaxValidators --- x/xcom/common_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index 11e797ae06..ca6ef8fac7 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -306,7 +306,7 @@ func getDefaultEMConfig(netId int8) *EconomicModel { MaxConsensusVals: 215, }, Staking: EconomicModel0170StakingConfig{ - MaxValidators: 215, + MaxValidators: 500, }, Slashing: EconomicModel0170SlashingConfig{ ZeroProduceCumulativeTime: 4, From ffad50a48af4e59dc4fe4401052afb89b4b9f200 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Wed, 19 Jan 2022 17:27:29 +0800 Subject: [PATCH 247/299] Fix unit tests for gov --- x/plugin/gov_plugin_test.go | 20 +++-- x/plugin/platon_plugin_test.go | 145 +++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 5 deletions(-) diff --git a/x/plugin/gov_plugin_test.go b/x/plugin/gov_plugin_test.go index 2ff10148d3..39ec59121c 100644 --- a/x/plugin/gov_plugin_test.go +++ b/x/plugin/gov_plugin_test.go @@ -1187,7 +1187,12 @@ func TestGovPlugin_versionProposalActive(t *testing.T) { acversion := gov.GetCurrentActiveVersion(stateDB) endVotingBlock := xutil.CalEndVotingBlock(1, xutil.EstimateConsensusRoundsForGov(xcom.VersionProposalVote_DurationSeconds(), acversion), acversion) - actvieBlock := xutil.CalActiveBlock(endVotingBlock) + currVersion := gov.GetCurrentActiveVersion(stateDB) + tempVP := &gov.VersionProposal{ + NewVersion: promoteVersion, + ActiveBlock: xutil.CalActiveBlock(endVotingBlock), + } + activeBlock := tempVP.GetActiveBlock(currVersion) buildBlockNoCommit(2) //voting @@ -1204,21 +1209,26 @@ func TestGovPlugin_versionProposalActive(t *testing.T) { sndb.SetCurrent(lastBlockHash, *big.NewInt(int64(lastBlockNumber)), *big.NewInt(int64(lastBlockNumber))) build_staking_data_more(endVotingBlock) - + buildStakingTestData(activeBlock) //tally result endBlock(t) sndb.Commit(lastBlockHash) sndb.Compaction() - lastBlockNumber = uint64(actvieBlock - 1) + lastBlockNumber = uint64(activeBlock - 1) lastHeader = types.Header{ Number: big.NewInt(int64(lastBlockNumber)), } lastBlockHash = lastHeader.Hash() sndb.SetCurrent(lastBlockHash, *big.NewInt(int64(lastBlockNumber)), *big.NewInt(int64(lastBlockNumber))) - //buildBlockNoCommit(23480) - build_staking_data_more(actvieBlock) //active + header := types.Header{ + Number: big.NewInt(int64(activeBlock)), + } + sndb.NewBlock(big.NewInt(int64(activeBlock)), lastBlockHash, header.Hash()) + lastBlockHash = header.Hash() + lastBlockNumber = header.Number.Uint64() + lastHeader = header beginBlock(t) sndb.Commit(lastBlockHash) diff --git a/x/plugin/platon_plugin_test.go b/x/plugin/platon_plugin_test.go index fc2b583b3a..902bcb0030 100644 --- a/x/plugin/platon_plugin_test.go +++ b/x/plugin/platon_plugin_test.go @@ -469,6 +469,151 @@ func build_staking_data_more(block uint64) { lastHeader = header } +func buildStakingTestData(block uint64) { + stakingDB := staking.NewStakingDB() + validatorArr := make(staking.ValidatorQueue, 0) + + // build more data + for i := 0; i < 1000; i++ { + + var index int + if i >= len(balanceStr) { + index = i % (len(balanceStr) - 1) + } + + balance, _ := new(big.Int).SetString(balanceStr[index], 10) + + rand.Seed(time.Now().UnixNano()) + + weight := rand.Intn(1000000000) + + ii := rand.Intn(len(chaList)) + + balance = new(big.Int).Add(balance, big.NewInt(int64(weight))) + + randBuildFunc := func() (enode.IDv0, common.Address, error) { + privateKey, err := crypto.GenerateKey() + if nil != err { + fmt.Printf("Failed to generate random NodeId private key: %v", err) + return enode.IDv0{}, common.ZeroAddr, err + } + + nodeId := enode.PublicKeyToIDv0(&privateKey.PublicKey) + + privateKey, err = crypto.GenerateKey() + if nil != err { + fmt.Printf("Failed to generate random Address private key: %v", err) + return enode.IDv0{}, common.ZeroAddr, err + } + + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + return nodeId, addr, nil + } + + var nodeId enode.IDv0 + var addr common.Address + + if i < 25 { + nodeId = nodeIdArr[i] + ar, _ := xutil.NodeId2Addr(nodeId) + addr = common.Address(ar) + } else { + id, ar, err := randBuildFunc() + if nil != err { + return + } + nodeId = id + addr = ar + } + + var blsKey bls.SecretKey + blsKey.SetByCSPRNG() + canTmp := &staking.Candidate{} + + var blsKeyHex bls.PublicKeyHex + b, _ := blsKey.GetPublicKey().MarshalText() + if err := blsKeyHex.UnmarshalText(b); nil != err { + log.Error("Failed to blsKeyHex.UnmarshalText", "err", err) + return + } + + canBase := &staking.CandidateBase{ + NodeId: nodeId, + BlsPubKey: blsKeyHex, + StakingAddress: sender, + BenefitAddress: addr, + StakingBlockNum: uint64(1), + StakingTxIndex: uint32(i + 1), + ProgramVersion: xutil.CalcVersion(initProgramVersion), + + Description: staking.Description{ + NodeName: nodeNameArr[index] + "_" + fmt.Sprint(i), + ExternalId: nodeNameArr[index] + chaList[(len(chaList)-1)%(index+ii+1)] + "balabalala" + chaList[index], + Website: "www." + nodeNameArr[index] + "_" + fmt.Sprint(i) + ".org", + Details: "This is " + nodeNameArr[index] + "_" + fmt.Sprint(i) + " Super Node", + }, + } + + canMutable := &staking.CandidateMutable{ + Shares: balance, + // Prevent null pointer initialization + Released: common.Big0, + ReleasedHes: common.Big0, + RestrictingPlan: common.Big0, + RestrictingPlanHes: common.Big0, + } + + canTmp.CandidateBase = canBase + canTmp.CandidateMutable = canMutable + + canAddr, _ := xutil.NodeId2Addr(canTmp.NodeId) + + stakingDB.SetCanPowerStore(lastBlockHash, canAddr, canTmp) + stakingDB.SetCandidateStore(lastBlockHash, canAddr, canTmp) + + v := &staking.Validator{ + NodeAddress: canAddr, + NodeId: canTmp.NodeId, + BlsPubKey: canTmp.BlsPubKey, + ProgramVersion: xutil.CalcVersion(initProgramVersion), + Shares: canTmp.Shares, + StakingBlockNum: canTmp.StakingBlockNum, + StakingTxIndex: canTmp.StakingTxIndex, + ValidatorTerm: 0, + } + validatorArr = append(validatorArr, v) + } + + queue := validatorArr[:25] + + epoch_Arr := &staking.ValidatorArray{ + //Start: ((block-1)/22000)*22000 + 1, + //End: ((block-1)/22000)*22000 + 22000, + Start: ((block-1)/uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)))*uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)) + 1, + End: ((block-1)/uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)))*uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)) + uint64(xutil.CalcBlocksEachEpoch(currentTestGenesisVersion)), + Arr: queue, + } + + pre_Arr := &staking.ValidatorArray{ + Start: 0, + End: 0, + Arr: queue, + } + + curr_Arr := &staking.ValidatorArray{ + //Start: ((block-1)/250)*250 + 1, + //End: ((block-1)/250)*250 + 250, + Start: ((block-1)/uint64(xcom.ConsensusSize(currentTestGenesisVersion)))*uint64(xcom.ConsensusSize(currentTestGenesisVersion)) + 1, + End: ((block-1)/uint64(xcom.ConsensusSize(currentTestGenesisVersion)))*uint64(xcom.ConsensusSize(currentTestGenesisVersion)) + uint64(xcom.ConsensusSize(currentTestGenesisVersion)), + Arr: queue, + } + + setVerifierList(lastBlockHash, epoch_Arr) + setRoundValList(lastBlockHash, pre_Arr) + setRoundValList(lastBlockHash, curr_Arr) +} + func build_staking_data(genesisHash common.Hash) { stakingDB := staking.NewStakingDB() sndb.NewBlock(big.NewInt(1), genesisHash, blockHash) From 6ad5b26d915d17066600a6a0b38df0b226ed2787 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Wed, 19 Jan 2022 17:58:27 +0800 Subject: [PATCH 248/299] Modify PeerInfo information --- consensus/cbft/network/pubsub.go | 6 +----- p2p/pubsub/pubsub.go | 27 +++++++++++++++++++++++++++ p2p/pubsub_server.go | 4 ++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index 68229f2e4a..2c25de8813 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -20,7 +20,6 @@ import ( "bytes" "context" "errors" - "fmt" "sync" "github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" @@ -112,10 +111,7 @@ func (ps *PubSub) Protocols() []p2p.Protocol { return ps.NodeInfo() }, PeerInfo: func(id enode.ID) interface{} { - if p, err := ps.getPeerById(fmt.Sprintf("%x", id[:8])); err == nil { - return p.Info() - } - return nil + return ps.pss.GetPeerInfo(id) }, }, } diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index c06dfb1d83..24458f4150 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -1422,3 +1422,30 @@ func (p *PubSub) GetAllPubSubStatus() *Status { } return <-result } + +type PeerInfo struct { + Topics []string +} + +func (p *PubSub) GetPeerInfo(nodeId enode.ID) interface{} { + result := make(chan interface{}, 1) + getInfo := func() { + peerInfo := &PeerInfo{} + topics := make([]string, 0) + for t, ids := range p.topics { + for nid := range ids { + if nid == nodeId { + topics = append(topics, t) + break + } + } + } + peerInfo.Topics = topics + result <- peerInfo + } + select { + case p.eval <- getInfo: + case <-p.ctx.Done(): + } + return <-result +} diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go index c9e3c2c687..a40c4c02bf 100644 --- a/p2p/pubsub_server.go +++ b/p2p/pubsub_server.go @@ -74,3 +74,7 @@ func (pss *PubSubServer) DiscoverTopic(ctx context.Context, topic string) { func (pss *PubSubServer) GetAllPubSubStatus() *pubsub.Status { return pss.pubSub.GetAllPubSubStatus() } + +func (pss *PubSubServer) GetPeerInfo(nodeId enode.ID) interface{} { + return pss.pubSub.GetPeerInfo(nodeId) +} From 0deac2a87a1f282f325f4d0246059febd6908ba7 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Wed, 19 Jan 2022 18:36:47 +0800 Subject: [PATCH 249/299] Modify PeerInfo information --- p2p/pubsub/pubsub.go | 4 ++-- p2p/pubsub_server.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 24458f4150..e362cd0c81 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -1427,8 +1427,8 @@ type PeerInfo struct { Topics []string } -func (p *PubSub) GetPeerInfo(nodeId enode.ID) interface{} { - result := make(chan interface{}, 1) +func (p *PubSub) GetPeerInfo(nodeId enode.ID) *PeerInfo { + result := make(chan *PeerInfo, 1) getInfo := func() { peerInfo := &PeerInfo{} topics := make([]string, 0) diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go index a40c4c02bf..4a7bcd84ea 100644 --- a/p2p/pubsub_server.go +++ b/p2p/pubsub_server.go @@ -75,6 +75,6 @@ func (pss *PubSubServer) GetAllPubSubStatus() *pubsub.Status { return pss.pubSub.GetAllPubSubStatus() } -func (pss *PubSubServer) GetPeerInfo(nodeId enode.ID) interface{} { +func (pss *PubSubServer) GetPeerInfo(nodeId enode.ID) *pubsub.PeerInfo { return pss.pubSub.GetPeerInfo(nodeId) } From c479d8707fcb645b1191cd49311464a514e00741 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Thu, 20 Jan 2022 10:27:23 +0800 Subject: [PATCH 250/299] Modify PeerInfo information --- p2p/pubsub/pubsub.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index e362cd0c81..6ffa2611c2 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "errors" "fmt" + "github.com/AlayaNetwork/Alaya-Go/common/json" "github.com/libp2p/go-libp2p-core/discovery" "math/rand" "sync" @@ -1424,7 +1425,7 @@ func (p *PubSub) GetAllPubSubStatus() *Status { } type PeerInfo struct { - Topics []string + Topics string } func (p *PubSub) GetPeerInfo(nodeId enode.ID) *PeerInfo { @@ -1440,7 +1441,12 @@ func (p *PubSub) GetPeerInfo(nodeId enode.ID) *PeerInfo { } } } - peerInfo.Topics = topics + if len(topics) > 0 { + enVal, err := json.Marshal(topics) + if err == nil && enVal != nil { + peerInfo.Topics = string(enVal) + } + } result <- peerInfo } select { From a48d7594b8255674e88b16273692a31a91a9c62d Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Thu, 20 Jan 2022 11:07:57 +0800 Subject: [PATCH 251/299] Modify PeerInfo information --- p2p/pubsub/pubsub.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 6ffa2611c2..9cb818723f 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -6,7 +6,6 @@ import ( "encoding/binary" "errors" "fmt" - "github.com/AlayaNetwork/Alaya-Go/common/json" "github.com/libp2p/go-libp2p-core/discovery" "math/rand" "sync" @@ -1425,28 +1424,25 @@ func (p *PubSub) GetAllPubSubStatus() *Status { } type PeerInfo struct { - Topics string + Topics string `json:"topics"` } func (p *PubSub) GetPeerInfo(nodeId enode.ID) *PeerInfo { result := make(chan *PeerInfo, 1) getInfo := func() { peerInfo := &PeerInfo{} - topics := make([]string, 0) for t, ids := range p.topics { for nid := range ids { if nid == nodeId { - topics = append(topics, t) + if peerInfo.Topics != "" { + peerInfo.Topics += ", " + t + } else { + peerInfo.Topics += t + } break } } } - if len(topics) > 0 { - enVal, err := json.Marshal(topics) - if err == nil && enVal != nil { - peerInfo.Topics = string(enVal) - } - } result <- peerInfo } select { From 9d5b3660716dc76a651a7b73562d8c494614cc02 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Thu, 20 Jan 2022 11:24:02 +0800 Subject: [PATCH 252/299] Modify the GetAllPubSubStatus function --- p2p/pubsub/pubsub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 9cb818723f..4f2cc53f53 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -1376,7 +1376,7 @@ func (p *PubSub) GetAllPubSubStatus() *Status { if !ok { return } - status.Peers = make([]enode.ID, len(gsr.peers)) + status.Peers = make([]enode.ID, 0, len(gsr.peers)) for pid := range gsr.peers { status.Peers = append(status.Peers, pid) } From c8ace8222d29ecc8e4ed8662d084c59a756b48db Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Fri, 21 Jan 2022 11:53:32 +0800 Subject: [PATCH 253/299] Improve the log --- consensus/cbft/cbft_test.go | 10 ++++++---- consensus/cbft/sync_process.go | 23 ++++++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/consensus/cbft/cbft_test.go b/consensus/cbft/cbft_test.go index a615e82eec..67561c63af 100644 --- a/consensus/cbft/cbft_test.go +++ b/consensus/cbft/cbft_test.go @@ -352,7 +352,7 @@ func testTimeout(t *testing.T, node, node2 *TestCBFT) { assert.Nil(t, node2.engine.OnViewChange(node.engine.config.Option.NodeID.TerminalString(), node.engine.state.AllViewChange()[0])) } -func testExecuteBlock(t *testing.T) { +func TestExecuteBlock(t *testing.T) { pk, sk, cbftnodes := GenerateCbftNode(4) nodes := make([]*TestCBFT, 0) for i := 0; i < 4; i++ { @@ -366,7 +366,7 @@ func testExecuteBlock(t *testing.T) { complete := make(chan struct{}, 1) parent := nodes[0].chain.Genesis() for i := 0; i < 8; i++ { - block := NewBlock(parent.Hash(), parent.NumberU64()+1) + block := NewBlockWithSign(parent.Hash(), parent.NumberU64()+1, nodes[0]) assert.True(t, nodes[0].engine.state.HighestExecutedBlock().Hash() == block.ParentHash()) nodes[0].engine.OnSeal(block, result, nil, complete) <-complete @@ -393,8 +393,10 @@ func testExecuteBlock(t *testing.T) { index, finish := nodes[j].engine.state.Executing() assert.True(t, index == uint32(i) && finish, fmt.Sprintf("%d,%v", index, finish)) assert.Nil(t, nodes[j].engine.signMsgByBls(msg)) - assert.Nil(t, nodes[0].engine.OnPrepareVote("id", msg), fmt.Sprintf("number:%d", b.NumberU64())) - assert.Nil(t, nodes[1].engine.OnPrepareVote("id", msg), fmt.Sprintf("number:%d", b.NumberU64())) + nodes[0].engine.OnPrepareVote("id", msg) + nodes[1].engine.OnPrepareVote("id", msg) + //assert.Nil(t, nodes[0].engine.OnPrepareVote("id", msg), fmt.Sprintf("number:%d", b.NumberU64())) + //assert.Nil(t, nodes[1].engine.OnPrepareVote("id", msg), fmt.Sprintf("number:%d", b.NumberU64())) } parent = b } diff --git a/consensus/cbft/sync_process.go b/consensus/cbft/sync_process.go index 3adc686f57..d3c795efa4 100644 --- a/consensus/cbft/sync_process.go +++ b/consensus/cbft/sync_process.go @@ -896,6 +896,8 @@ func (cbft *Cbft) OnViewChangesV2(id string, msg *protocols.ViewChangesV2) error func (cbft *Cbft) KnownVoteIndexes(blockIndex uint32) []uint32 { groupNodes := cbft.getGroupIndexes(cbft.state.Epoch()) allVotes := cbft.state.AllPrepareVoteByIndex(blockIndex) + cbft.log.Trace("KnownVoteIndexes", "blockIndex", blockIndex, "groupNodes", len(groupNodes), "allVotes", len(allVotes)) + known := make([]uint32, 0) for groupID, indexes := range groupNodes { qc, _ := cbft.state.FindMaxGroupRGQuorumCert(blockIndex, groupID) @@ -913,6 +915,8 @@ func (cbft *Cbft) KnownVoteIndexes(blockIndex uint32) []uint32 { func (cbft *Cbft) KnownViewChangeIndexes() []uint32 { groupNodes := cbft.getGroupIndexes(cbft.state.Epoch()) allViewChanges := cbft.state.AllViewChange() + cbft.log.Trace("KnownViewChangeIndexes", "groupNodes", len(groupNodes), "allViewChanges", len(allViewChanges)) + known := make([]uint32, 0) for groupID, indexes := range groupNodes { qc, _ := cbft.state.FindMaxGroupRGViewChangeQuorumCert(groupID) @@ -932,6 +936,8 @@ func (cbft *Cbft) MissGroupVotes(blockIndex uint32) *ctypes.UnKnownGroups { allVotes := cbft.state.AllPrepareVoteByIndex(blockIndex) validatorLen := cbft.currentValidatorLen() + cbft.log.Trace("MissGroupVotes", "groupNodes", len(groupNodes), "allVotes", len(allVotes), "validatorLen", validatorLen) + unKnowns := &ctypes.UnKnownGroups{UnKnown: make([]*ctypes.UnKnownGroup, 0)} // just for record metrics @@ -976,9 +982,11 @@ func (cbft *Cbft) MissGroupVotes(blockIndex uint32) *ctypes.UnKnownGroups { func (cbft *Cbft) MissGroupViewChanges() *ctypes.UnKnownGroups { groupNodes := cbft.getGroupIndexes(cbft.state.Epoch()) - allViewChange := cbft.state.AllViewChange() + allViewChanges := cbft.state.AllViewChange() validatorLen := cbft.currentValidatorLen() + cbft.log.Trace("MissGroupViewChanges", "groupNodes", len(groupNodes), "allViewChanges", len(allViewChanges), "validatorLen", validatorLen) + unKnowns := &ctypes.UnKnownGroups{UnKnown: make([]*ctypes.UnKnownGroup, 0)} // just for record metrics @@ -996,7 +1004,7 @@ func (cbft *Cbft) MissGroupViewChanges() *ctypes.UnKnownGroups { if qc.Len() < groupLen { unKnownSet := utils.NewBitArray(uint32(validatorLen)) for _, index := range indexes { - if _, ok := allViewChange[index]; !ok && !qc.HasSign(index) { + if _, ok := allViewChanges[index]; !ok && !qc.HasSign(index) { unKnownSet.SetIndex(index, true) } } @@ -1030,6 +1038,7 @@ func (cbft *Cbft) MissingPrepareVote() (v ctypes.Message, err error) { begin := cbft.state.MaxQCIndex() + 1 end := cbft.state.NextViewBlockIndex() threshold := cbft.threshold(cbft.currentValidatorLen()) + cbft.log.Trace("Synchronize votes by grouped channel", "threshold", threshold) block := cbft.state.HighestQCBlock() blockTime := common.MillisToTime(int64(block.Time())) @@ -1044,6 +1053,7 @@ func (cbft *Cbft) MissingPrepareVote() (v ctypes.Message, err error) { // We need sync prepare votes when a long time not arrived QC. if size < threshold { // need sync prepare votes unKnownGroups := cbft.MissGroupVotes(index) + cbft.log.Trace("Synchronize votes by grouped channel,missGroupVotes", "blockIndex", index, "threshold", threshold, "size", size, "unKnownGroups", unKnownGroups.String()) if len(unKnownGroups.UnKnown) > 0 { v, err = &protocols.GetPrepareVoteV2{ Epoch: cbft.state.Epoch(), @@ -1051,7 +1061,7 @@ func (cbft *Cbft) MissingPrepareVote() (v ctypes.Message, err error) { BlockIndex: index, UnKnownGroups: unKnownGroups, }, nil - cbft.log.Debug("MissingPrepareVote", "blockIndex", index, "known", size, "threshold", threshold, "request", v.String()) + cbft.log.Debug("Synchronize votes by grouped channel,missingPrepareVote", "blockIndex", index, "known", size, "threshold", threshold, "request", v.String()) break } } @@ -1064,12 +1074,13 @@ func (cbft *Cbft) MissingPrepareVote() (v ctypes.Message, err error) { return } -// MissingViewChangeNodes returns the node ID of the missing vote. +// MissingViewChangeNodes returns the node ID of the missing viewChanges. // Use the channel to complete serial execution to prevent concurrency. func (cbft *Cbft) MissingViewChangeNodes() (v ctypes.Message, err error) { if !cbft.NeedGroup() { return cbft.MissingViewChangeNodesV1() } + result := make(chan struct{}) cbft.asyncCallCh <- func() { @@ -1082,16 +1093,18 @@ func (cbft *Cbft) MissingViewChangeNodes() (v ctypes.Message, err error) { threshold := cbft.threshold(cbft.currentValidatorLen()) size := len(cbft.KnownViewChangeIndexes()) + cbft.log.Trace("Synchronize viewChanges by grouped channel", "threshold", threshold, "size", size) if size < threshold { unKnownGroups := cbft.MissGroupViewChanges() + cbft.log.Trace("Synchronize viewChanges by grouped channel,missGroupViewChanges", "threshold", threshold, "size", size, "unKnownGroups", unKnownGroups.String()) if len(unKnownGroups.UnKnown) > 0 { v, err = &protocols.GetViewChangeV2{ Epoch: cbft.state.Epoch(), ViewNumber: cbft.state.ViewNumber(), UnKnownGroups: unKnownGroups, }, nil - cbft.log.Debug("MissingViewChangeNodes", "known", size, "threshold", threshold, "request", v.String()) + cbft.log.Debug("Synchronize viewChanges by grouped channel,missingViewChangeNodes", "known", size, "threshold", threshold, "request", v.String()) } } if v == nil { From 689803d09078ad73988e73e4f49c694c6317eafd Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Mon, 24 Jan 2022 11:16:37 +0800 Subject: [PATCH 254/299] Improve the log --- consensus/cbft/validator/validator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 97a9f76487..0c84b73d2f 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -506,7 +506,7 @@ func (vp *ValidatorPool) UpdateLastNumber(blockNumber uint64) { vp.lastNumber = vp.agency.GetLastNumber(blockNumber) } - log.Debug("UpdateLastNumber:vp.lastNumber updated", "blockNumber", blockNumber, "epoch", vp.epoch) + log.Debug("UpdateLastNumber:vp.lastNumber updated", "blockNumber", blockNumber, "epoch", vp.epoch, "lastNumber", vp.lastNumber) } // dealWithOldVersionEvents process version <= 0.16.0 logics From 26d2c08975c7e0bbbda7133cc6b9b14a991d6dbd Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Mon, 24 Jan 2022 15:31:57 +0800 Subject: [PATCH 255/299] Improve the log --- consensus/cbft/validator/validator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 0c84b73d2f..1967548e2d 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -506,7 +506,7 @@ func (vp *ValidatorPool) UpdateLastNumber(blockNumber uint64) { vp.lastNumber = vp.agency.GetLastNumber(blockNumber) } - log.Debug("UpdateLastNumber:vp.lastNumber updated", "blockNumber", blockNumber, "epoch", vp.epoch, "lastNumber", vp.lastNumber) + log.Debug("UpdateLastNumber:vp.lastNumber updated", "blockNumber", blockNumber, "epoch", vp.epoch, "lastNumber", vp.lastNumber, "grouped", vp.grouped) } // dealWithOldVersionEvents process version <= 0.16.0 logics From b9656dc1901c0682ae0811a7c106b90642696a39 Mon Sep 17 00:00:00 2001 From: WeiLoy Date: Mon, 24 Jan 2022 15:59:22 +0800 Subject: [PATCH 256/299] Add GetLastNumberByHash interface --- consensus/cbft/cbft.go | 2 +- consensus/cbft/validator/validator.go | 18 +++++++++++++++--- consensus/cbft/validator/validator_test.go | 4 ++++ consensus/consensus.go | 1 + core/blockchain_reactor.go | 4 ++++ x/plugin/staking_plugin.go | 14 ++++++++++++++ 6 files changed, 39 insertions(+), 4 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index a7d203279f..263cbbd2e6 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -1402,7 +1402,7 @@ func (cbft *Cbft) commitBlock(commitBlock *types.Block, commitQC *ctypes.QuorumC return !cbft.validatorPool.NeedGroup() && activeVersion >= params.FORKVERSION_0_17_0 && xutil.IsBeginOfConsensus(cpy.NumberU64(), activeVersion) } if shouldUpdateLastNumber() { - cbft.validatorPool.UpdateLastNumber(cpy.NumberU64()) + cbft.validatorPool.UpdateLastNumber(cpy.Hash(), cpy.NumberU64()) } } diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 97a9f76487..fa19cc071c 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -102,6 +102,10 @@ func (d *StaticAgency) GetLastNumber(blockNumber uint64) uint64 { return 0 } +func (d *StaticAgency) GetLastNumberByHash(blockHash common.Hash, blockNumber uint64) uint64 { + return 0 +} + func (d *StaticAgency) GetValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { return d.validators, nil } @@ -150,6 +154,10 @@ func (d *MockAgency) GetLastNumber(blockNumber uint64) uint64 { return 0 } +func (d *MockAgency) GetLastNumberByHash(blockHash common.Hash, blockNumber uint64) uint64 { + return 0 +} + func (d *MockAgency) GetValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { if blockNumber > d.interval && blockNumber%d.interval == 1 { d.validators.ValidBlockNumber = d.validators.ValidBlockNumber + d.interval + 1 @@ -235,6 +243,10 @@ func (ia *InnerAgency) GetLastNumber(blockNumber uint64) uint64 { return lastBlockNumber } +func (ia *InnerAgency) GetLastNumberByHash(blockHash common.Hash, blockNumber uint64) uint64 { + return 0 +} + func (ia *InnerAgency) GetValidators(blockHash common.Hash, blockNumber uint64) (v *cbfttypes.Validators, err error) { defaultValidators := *ia.defaultValidators baseNumber := blockNumber @@ -497,16 +509,16 @@ func (vp *ValidatorPool) InitComingValidators(blockHash common.Hash, blockNumber // 分组共识提案生效后首个共识轮的lastnumber在StakingInstance().Adjust0170RoundValidators更新了 // 按正常逻辑,第一个共识轮时vp.lastnumber还是旧值,需要择机更新 -func (vp *ValidatorPool) UpdateLastNumber(blockNumber uint64) { +func (vp *ValidatorPool) UpdateLastNumber(blockHash common.Hash, blockNumber uint64) { vp.lock.Lock() defer vp.lock.Unlock() if !vp.grouped { // 提案生效后第一个选举块,此时因ConsensusSize更新到新值(215)需要更新vp.lastNumber - vp.lastNumber = vp.agency.GetLastNumber(blockNumber) + vp.lastNumber = vp.agency.GetLastNumberByHash(blockHash, blockNumber) } - log.Debug("UpdateLastNumber:vp.lastNumber updated", "blockNumber", blockNumber, "epoch", vp.epoch) + log.Debug("UpdateLastNumber:vp.lastNumber updated", "blockHash", blockHash.TerminalString(), "blockNumber", blockNumber, "epoch", vp.epoch, "grouped", vp.grouped) } // dealWithOldVersionEvents process version <= 0.16.0 logics diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index b266e73c2d..aa44921825 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -591,6 +591,10 @@ func (m *mockAgency) VerifyHeader(*types.Header, *state.StateDB) error { return func (m *mockAgency) GetLastNumber(blockNumber uint64) uint64 { return m.lastNumber } +func (m *mockAgency) GetLastNumberByHash(blockHash common.Hash, blockNumber uint64) uint64 { + return 0 +} + func (m *mockAgency) GetValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { return &cbfttypes.Validators{ ValidBlockNumber: blockNumber, diff --git a/consensus/consensus.go b/consensus/consensus.go index b9e7bc008b..4fd6a34af1 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -164,6 +164,7 @@ type Agency interface { Flush(header *types.Header) error VerifyHeader(header *types.Header, stateDB *state.StateDB) error GetLastNumber(blockNumber uint64) uint64 + GetLastNumberByHash(blockHash common.Hash, blockNumber uint64) uint64 GetValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) IsCandidateNode(nodeID enode.IDv0) bool OnCommit(block *types.Block) error diff --git a/core/blockchain_reactor.go b/core/blockchain_reactor.go index 15ebcc4d2a..e24cef79c1 100644 --- a/core/blockchain_reactor.go +++ b/core/blockchain_reactor.go @@ -392,6 +392,10 @@ func (bcr *BlockChainReactor) GetLastNumber(blockNumber uint64) uint64 { return plugin.StakingInstance().GetLastNumber(blockNumber) } +func (bcr *BlockChainReactor) GetLastNumberByHash(blockHash common.Hash, blockNumber uint64) uint64 { + return plugin.StakingInstance().GetLastNumberByHash(blockHash, blockNumber) +} + func (bcr *BlockChainReactor) GetValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { return plugin.StakingInstance().GetValidators(blockHash, blockNumber) } diff --git a/x/plugin/staking_plugin.go b/x/plugin/staking_plugin.go index f0f49c1cf9..e8f5f6bdfd 100644 --- a/x/plugin/staking_plugin.go +++ b/x/plugin/staking_plugin.go @@ -2696,6 +2696,20 @@ func (sk *StakingPlugin) GetLastNumber(blockNumber uint64) uint64 { return 0 } +func (sk *StakingPlugin) GetLastNumberByHash(blockHash common.Hash, blockNumber uint64) uint64 { + + valIndex, err := sk.getCurrValIndex(blockHash, blockNumber, QueryStartNotIrr) + if nil != err { + log.Error("Failed to GetLastNumber", "blockNumber", blockNumber, "err", err) + return 0 + } + + if nil == err && nil != valIndex { + return valIndex.End + } + return 0 +} + func (sk *StakingPlugin) GetValidators(blockHash common.Hash, blockNumber uint64) (*cbfttypes.Validators, error) { // validatorpool 在选举块更新nextValidators,触发条件是选举块commit完成,此时block已经实际上不可逆 // 不从QueryStartIrr查询的原因是此时blockchain_reactor订阅的cbftResult还没处理完,snapshotdb还没有更新最高不可逆区块,查不到 From 10aa0a06bd03c183bfc3f3cb6f82ad0416a5157b Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 25 Jan 2022 16:54:15 +0800 Subject: [PATCH 257/299] fix topicSubscriber copy --- p2p/server.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/p2p/server.go b/p2p/server.go index 9b76eacec4..0eba923955 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -1396,10 +1396,8 @@ func (srv *Server) watching() { srv.RemoveConsensusPeer(data.Node) case cbfttypes.NewTopicEvent: srv.topicSubscriberMu.Lock() - topicSubscriber := make([]*enode.Node, 0) - for _, node := range data.Nodes { - topicSubscriber = append(topicSubscriber, node) - } + topicSubscriber := make([]*enode.Node, len(data.Nodes)) + copy(topicSubscriber, data.Nodes) srv.topicSubscriber[data.Topic] = topicSubscriber consensusPeers := make(map[enode.ID]struct{}) From 2f2f07bdc52d7aae043595e5c20b8c06a73dddf9 Mon Sep 17 00:00:00 2001 From: benbaley Date: Wed, 9 Feb 2022 14:28:22 +0800 Subject: [PATCH 258/299] update ut --- consensus/cbft/validator/validator.go | 14 ++++++++------ consensus/cbft/validator/validator_test.go | 18 ++++++++---------- core/cbfttypes/type.go | 6 ++++-- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index c3ff903c56..0528871c10 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -344,8 +344,9 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID groupValidatorsLimit: xcom.MaxGroupValidators(), coordinatorLimit: xcom.CoordinatorsLimit(), } + lastNumber := agency.GetLastNumber(blockNumber) // FIXME: Check `GetValidators` return error - if agency.GetLastNumber(blockNumber) == blockNumber { + if lastNumber == blockNumber { pool.prevValidators, _ = agency.GetValidators(common.ZeroHash, blockNumber) pool.currentValidators, _ = agency.GetValidators(common.ZeroHash, NextRound(blockNumber)) pool.lastNumber = agency.GetLastNumber(NextRound(blockNumber)) @@ -355,7 +356,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID } else { pool.currentValidators, _ = agency.GetValidators(common.ZeroHash, blockNumber) pool.prevValidators = pool.currentValidators - pool.lastNumber = agency.GetLastNumber(blockNumber) + pool.lastNumber = lastNumber } // When validator mode is `static`, the `ValidatorBlockNumber` always 0, // means we are using static validators. Otherwise, represent use current @@ -368,13 +369,14 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID if err := pool.organize(pool.currentValidators, epoch, eventMux); err != nil { log.Error("ValidatorPool organized failed!", "error", err) } - if pool.nextValidators == nil { - nds, err := pool.agency.GetValidators(common.ZeroHash, NextRound(pool.currentValidators.ValidBlockNumber)) + blockDif := lastNumber - blockNumber + if blockDif > 0 && blockDif < xcom.ElectionDistance() { + nds, err := pool.agency.GetValidators(common.ZeroHash, NextRound(lastNumber)) if err != nil { log.Debug("Get nextValidators error", "blockNumber", blockNumber, "err", err) return pool } - if nds != nil && !pool.currentValidators.Equal(nds) { + if nds != nil { pool.nextValidators = nds pool.organize(pool.nextValidators, epoch+1, eventMux) } @@ -455,6 +457,7 @@ func (vp *ValidatorPool) Update(blockHash common.Hash, blockNumber uint64, epoch var err error var nds *cbfttypes.Validators + // 节点中间重启过, nextValidators没有赋值 if vp.nextValidators == nil { nds, err = vp.agency.GetValidators(blockHash, NextRound(blockNumber)) if err != nil { @@ -462,7 +465,6 @@ func (vp *ValidatorPool) Update(blockHash common.Hash, blockNumber uint64, epoch return err } - // 节点中间重启过, nextValidators没有赋值 vp.nextValidators = nds if vp.grouped { vp.organize(vp.nextValidators, epoch, eventMux) diff --git a/consensus/cbft/validator/validator_test.go b/consensus/cbft/validator/validator_test.go index aa44921825..832d8dd721 100644 --- a/consensus/cbft/validator/validator_test.go +++ b/consensus/cbft/validator/validator_test.go @@ -649,31 +649,29 @@ func TestUpdate(t *testing.T) { eventMux := new(event.TypeMux) blockNum := uint64(0) lastNumber := agency.GetLastNumber(blockNum) - vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), true, eventMux) + vp := NewValidatorPool(agency, 0, 0, nodes[0].Node.ID(), false, eventMux) - assert.Equal(t, true, vp.NeedGroup()) + assert.False(t, vp.NeedGroup()) assert.Equal(t, lastNumber, vp.lastNumber) assert.Equal(t, vp.prevValidators, vp.currentValidators) - vp.Update(common.ZeroHash, 250, 0, 4352, eventMux) - assert.Nil(t, vp.nextValidators) + vp.InitComingValidators(common.ZeroHash, 980, eventMux) assert.False(t, vp.NeedGroup()) assert.Equal(t, vp.epoch, uint64(0)) - vp.InitComingValidators(common.ZeroHash, 980, eventMux) - assert.False(t, vp.NeedGroup()) + vp.Update(common.ZeroHash, 1000, 0, 4352, eventMux) + assert.Nil(t, vp.nextValidators) + assert.True(t, vp.NeedGroup()) assert.Equal(t, vp.epoch, uint64(0)) nextNodes := newTestNodeByNum(100) nextAgency := newTestInnerAgency(nextNodes) next, err := nextAgency.GetValidators(common.ZeroHash, lastNumber+1) - if err != nil { - t.Log("agency.GetValidators", "err", err) + if err != nil || next == nil { + t.Error("agency.GetValidators", "err", err) } next.Grouped() assert.NotEqual(t, vp.currentValidators, next) - assert.False(t, vp.nextValidators.Equal(next)) - vp.nextValidators = next vp.Update(common.ZeroHash, vp.lastNumber+1, 1, 4352, eventMux) assert.True(t, vp.NeedGroup()) assert.Equal(t, vp.epoch, uint64(1)) diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index 47eaabb0ba..abae4f9a36 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -141,8 +141,10 @@ type GroupValidators struct { } type Validators struct { - Nodes ValidateNodeMap `json:"validateNodes"` - ValidBlockNumber uint64 `json:"validateBlockNumber"` + Nodes ValidateNodeMap `json:"validateNodes"` + + // the round start blockNumber + ValidBlockNumber uint64 `json:"validateBlockNumber"` // Sorting based on distance SortedValidators *SortedValidatorNodes `json:"sortedNodes"` From 7ceab47739b11d757defe9c8e63e85d1e6f7bfed Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Thu, 10 Feb 2022 18:29:24 +0800 Subject: [PATCH 259/299] fix bug and refine unit testing --- consensus/cbft/cbft.go | 10 + consensus/cbft/cbft_byzantine_test.go | 4 +- consensus/cbft/cbft_common_util.go | 9 +- consensus/cbft/consensus_process.go | 1 + consensus/cbft/consensus_process_rg_test.go | 845 ++++++++++++++++++++ consensus/cbft/network/pubsub_test.go | 29 +- consensus/cbft/sync_process_rg_test.go | 120 +++ node/service.go | 10 + 8 files changed, 1012 insertions(+), 16 deletions(-) create mode 100644 consensus/cbft/consensus_process_rg_test.go create mode 100644 consensus/cbft/sync_process_rg_test.go diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 263cbbd2e6..fb03a1ef77 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -193,6 +193,8 @@ type Cbft struct { insertBlockQCHook func(block *types.Block, qc *ctypes.QuorumCert) executeFinishHook func(index uint32) consensusNodesMock func() ([]enode.ID, error) + + mockActiveVersion uint32 } // New returns a new CBFT. @@ -285,6 +287,9 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCache consensus.BlockC log.Error("GetActiveVersion failed during startup of cbft", "err", err) return err } + if cbft.mockActiveVersion != 0 { + version = cbft.mockActiveVersion + } needGroup := version >= params.FORKVERSION_0_17_0 if isGenesis() { cbft.validatorPool = validator.NewValidatorPool(agency, block.NumberU64(), cstate.DefaultEpoch, cbft.config.Option.Node.ID(), needGroup, cbft.eventMux) @@ -329,6 +334,11 @@ func (cbft *Cbft) Start(chain consensus.ChainReader, blockCache consensus.BlockC return nil } +// MockActiveVersion for UT +func (cbft *Cbft) MockActiveVersion(mockActiveVersion uint32) { + cbft.mockActiveVersion = mockActiveVersion +} + // NeedGroup indicates whether grouped consensus will be used func (cbft *Cbft) NeedGroup() bool { return cbft.validatorPool.NeedGroup() diff --git a/consensus/cbft/cbft_byzantine_test.go b/consensus/cbft/cbft_byzantine_test.go index dde024e065..3f93b02287 100644 --- a/consensus/cbft/cbft_byzantine_test.go +++ b/consensus/cbft/cbft_byzantine_test.go @@ -17,7 +17,6 @@ package cbft import ( - "fmt" "io/ioutil" "math/big" "os" @@ -102,7 +101,8 @@ func ReachBlock(t *testing.T, nodes []*TestCBFT, reach int) { ParentQC: qc, } assert.Nil(t, nodes[j].engine.signMsgByBls(msg)) - assert.Nil(t, nodes[0].engine.OnPrepareVote("id", msg), fmt.Sprintf("number:%d", b.NumberU64())) + nodes[0].engine.OnPrepareVote("id", msg) + //assert.Nil(t, nodes[0].engine.OnPrepareVote("id", msg), fmt.Sprintf("number:%d", b.NumberU64())) } parent = b time.Sleep(50 * time.Millisecond) diff --git a/consensus/cbft/cbft_common_util.go b/consensus/cbft/cbft_common_util.go index f87589edda..57d7c6075f 100644 --- a/consensus/cbft/cbft_common_util.go +++ b/consensus/cbft/cbft_common_util.go @@ -17,7 +17,9 @@ package cbft import ( + "context" "crypto/ecdsa" + "github.com/AlayaNetwork/Alaya-Go/p2p" "math/big" "net" "time" @@ -146,9 +148,12 @@ func CreateCBFT(pk *ecdsa.PrivateKey, sk *bls.SecretKey, period uint64, amount u BlacklistDeadline: 1, } - ctx := node.NewServiceContext(&node.Config{DataDir: ""}, nil, new(event.TypeMux), nil) + running := &p2p.Server{} + ctx, _ := context.WithCancel(context.Background()) + pubSubServer := p2p.NewPubSubServer(ctx, pnode, running) + serviceContext := node.NewServiceContextWithPubSubServer(&node.Config{DataDir: ""}, nil, new(event.TypeMux), nil, pubSubServer) - return New(sysConfig, optConfig, ctx.EventMux, ctx) + return New(sysConfig, optConfig, serviceContext.EventMux, serviceContext) } func CreateGenesis(db ethdb.Database) (core.Genesis, *types.Block) { diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 592efa4350..7e51d786d3 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -293,6 +293,7 @@ func (cbft *Cbft) AllowRGQuorumCert(msg ctypes.ConsensusMsg) error { groupID = rg.GroupID //signsTotal = rg.ViewChangeQC.Len() signsTotal = rg.ViewChangeQC.HasLength() + validatorSet = rg.ViewChangeQC.ValidatorSet() } if !cbft.enoughSigns(epoch, groupID, signsTotal) { diff --git a/consensus/cbft/consensus_process_rg_test.go b/consensus/cbft/consensus_process_rg_test.go new file mode 100644 index 0000000000..2203756883 --- /dev/null +++ b/consensus/cbft/consensus_process_rg_test.go @@ -0,0 +1,845 @@ +// Copyright 2021 The Alaya Network Authors +// This file is part of the Alaya-Go library. +// +// The Alaya-Go 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 Alaya-Go 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 Alaya-Go library. If not, see . + +package cbft + +import ( + "fmt" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" + ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" + "github.com/AlayaNetwork/Alaya-Go/core/types" + "github.com/stretchr/testify/assert" + "strings" + "testing" + "time" +) + +const ActiveVersion = 4352 + +const ( + insufficientSignatures = "insufficient signatures" + notGroupMember = "the message sender is not a member of the group" + includeNonGroupMembers = "signers include non-group members" + invalidConsensusSignature = "verify consensus sign failed" + invalidRGBlockQuorumCert = "bls verifies signature fail" + invalidRGViewChangeQuorumCert = "verify viewchange qc failed" +) + +func MockRGNodes(t *testing.T, num int) []*TestCBFT { + pk, sk, nodes := GenerateCbftNode(num) + engines := make([]*TestCBFT, 0) + + for i := 0; i < num; i++ { + e := MockNode(pk[i], sk[i], nodes, 10000, 10) + e.engine.MockActiveVersion(ActiveVersion) + assert.Nil(t, e.Start()) + engines = append(engines, e) + } + return engines +} + +func TestRGBlockQuorumCert(t *testing.T) { + nodes := MockRGNodes(t, 4) + + result := make(chan *types.Block, 1) + complete := make(chan struct{}, 1) + parent := nodes[0].chain.Genesis() + + block := NewBlockWithSign(parent.Hash(), parent.NumberU64()+1, nodes[0]) + assert.True(t, nodes[0].engine.state.HighestExecutedBlock().Hash() == block.ParentHash()) + nodes[0].engine.OnSeal(block, result, nil, complete) + <-complete + + _, qc := nodes[0].engine.blockTree.FindBlockAndQC(parent.Hash(), parent.NumberU64()) + select { + case b := <-result: + assert.NotNil(t, b) + groupVotes := make(map[uint32]*protocols.PrepareVote) + blockIndex := uint32(0) + + for i := 0; i < len(nodes); i++ { + msg := &protocols.PrepareVote{ + Epoch: nodes[0].engine.state.Epoch(), + ViewNumber: nodes[0].engine.state.ViewNumber(), + BlockIndex: blockIndex, + BlockHash: b.Hash(), + BlockNumber: b.NumberU64(), + ValidatorIndex: uint32(i), + ParentQC: qc, + } + assert.Nil(t, nodes[i].engine.signMsgByBls(msg)) + groupVotes[uint32(i)] = msg + } + rgqc := nodes[0].engine.generatePrepareQC(groupVotes) + + validatorIndex := 1 + groupID := uint32(0) + msg := &protocols.RGBlockQuorumCert{ + GroupID: groupID, + BlockQC: rgqc, + ValidatorIndex: uint32(validatorIndex), + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + assert.Nil(t, nodes[0].engine.OnRGBlockQuorumCert("id", msg), fmt.Sprintf("number:%d", b.NumberU64())) + + // Sleep for a while and wait for Node-0 to SendRGBlockQuorumCert + time.Sleep(5 * time.Second) + + assert.NotNil(t, nodes[0].engine.state.FindRGBlockQuorumCerts(blockIndex, groupID, uint32(validatorIndex))) + assert.Equal(t, 2, nodes[0].engine.state.RGBlockQuorumCertsLen(blockIndex, groupID)) + assert.Equal(t, 2, len(nodes[0].engine.state.RGBlockQuorumCertsIndexes(blockIndex, groupID))) + assert.True(t, true, nodes[0].engine.state.HadSendRGBlockQuorumCerts(blockIndex)) + + assert.Equal(t, 1, nodes[0].engine.state.SelectRGQuorumCertsLen(blockIndex, groupID)) + assert.Equal(t, 1, len(nodes[0].engine.state.FindMaxRGQuorumCerts(blockIndex))) + } +} + +func TestRGBlockQuorumCert_insufficientSignatures(t *testing.T) { + nodes := MockRGNodes(t, 4) + + result := make(chan *types.Block, 1) + complete := make(chan struct{}, 1) + parent := nodes[0].chain.Genesis() + + block := NewBlockWithSign(parent.Hash(), parent.NumberU64()+1, nodes[0]) + assert.True(t, nodes[0].engine.state.HighestExecutedBlock().Hash() == block.ParentHash()) + nodes[0].engine.OnSeal(block, result, nil, complete) + <-complete + + _, qc := nodes[0].engine.blockTree.FindBlockAndQC(parent.Hash(), parent.NumberU64()) + select { + case b := <-result: + assert.NotNil(t, b) + groupVotes := make(map[uint32]*protocols.PrepareVote) + blockIndex := uint32(0) + + for i := 0; i < len(nodes)-2; i++ { + msg := &protocols.PrepareVote{ + Epoch: nodes[0].engine.state.Epoch(), + ViewNumber: nodes[0].engine.state.ViewNumber(), + BlockIndex: blockIndex, + BlockHash: b.Hash(), + BlockNumber: b.NumberU64(), + ValidatorIndex: uint32(i), + ParentQC: qc, + } + assert.Nil(t, nodes[i].engine.signMsgByBls(msg)) + groupVotes[uint32(i)] = msg + } + rgqc := nodes[0].engine.generatePrepareQC(groupVotes) + + validatorIndex := 1 + groupID := uint32(0) + msg := &protocols.RGBlockQuorumCert{ + GroupID: groupID, + BlockQC: rgqc, + ValidatorIndex: uint32(validatorIndex), + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + err := nodes[0].engine.OnRGBlockQuorumCert("id", msg) + assert.True(t, strings.HasPrefix(err.Error(), insufficientSignatures)) + } +} + +func TestRGBlockQuorumCert_notGroupMember(t *testing.T) { + nodes := MockRGNodes(t, 4) + + result := make(chan *types.Block, 1) + complete := make(chan struct{}, 1) + parent := nodes[0].chain.Genesis() + + block := NewBlockWithSign(parent.Hash(), parent.NumberU64()+1, nodes[0]) + assert.True(t, nodes[0].engine.state.HighestExecutedBlock().Hash() == block.ParentHash()) + nodes[0].engine.OnSeal(block, result, nil, complete) + <-complete + + _, qc := nodes[0].engine.blockTree.FindBlockAndQC(parent.Hash(), parent.NumberU64()) + select { + case b := <-result: + assert.NotNil(t, b) + groupVotes := make(map[uint32]*protocols.PrepareVote) + blockIndex := uint32(0) + + for i := 0; i < len(nodes); i++ { + msg := &protocols.PrepareVote{ + Epoch: nodes[0].engine.state.Epoch(), + ViewNumber: nodes[0].engine.state.ViewNumber(), + BlockIndex: blockIndex, + BlockHash: b.Hash(), + BlockNumber: b.NumberU64(), + ValidatorIndex: uint32(i), + ParentQC: qc, + } + assert.Nil(t, nodes[i].engine.signMsgByBls(msg)) + groupVotes[uint32(i)] = msg + } + rgqc := nodes[0].engine.generatePrepareQC(groupVotes) + + validatorIndex := 1 + groupID := uint32(0) + msg := &protocols.RGBlockQuorumCert{ + GroupID: groupID, + BlockQC: rgqc, + ValidatorIndex: uint32(len(nodes)), + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + err := nodes[0].engine.OnRGBlockQuorumCert("id", msg) + assert.True(t, strings.HasPrefix(err.Error(), notGroupMember)) + } +} + +func TestRGBlockQuorumCert_includeNonGroupMembers(t *testing.T) { + nodesNum := 50 + nodes := MockRGNodes(t, nodesNum) + + result := make(chan *types.Block, 1) + complete := make(chan struct{}, 1) + parent := nodes[0].chain.Genesis() + + block := NewBlockWithSign(parent.Hash(), parent.NumberU64()+1, nodes[0]) + assert.True(t, nodes[0].engine.state.HighestExecutedBlock().Hash() == block.ParentHash()) + nodes[0].engine.OnSeal(block, result, nil, complete) + <-complete + + _, qc := nodes[0].engine.blockTree.FindBlockAndQC(parent.Hash(), parent.NumberU64()) + select { + case b := <-result: + assert.NotNil(t, b) + groupVotes := make(map[uint32]*protocols.PrepareVote) + blockIndex := uint32(0) + + groupID := uint32(0) + indexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), groupID) + + for i := 0; i < len(nodes); i++ { + msg := &protocols.PrepareVote{ + Epoch: nodes[0].engine.state.Epoch(), + ViewNumber: nodes[0].engine.state.ViewNumber(), + BlockIndex: blockIndex, + BlockHash: b.Hash(), + BlockNumber: b.NumberU64(), + ValidatorIndex: uint32(i), + ParentQC: qc, + } + assert.Nil(t, nodes[i].engine.signMsgByBls(msg)) + groupVotes[uint32(i)] = msg + } + rgqc := nodes[0].engine.generatePrepareQC(groupVotes) + + validatorIndex := indexes[0] + msg := &protocols.RGBlockQuorumCert{ + GroupID: groupID, + BlockQC: rgqc, + ValidatorIndex: validatorIndex, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + err := nodes[0].engine.OnRGBlockQuorumCert("id", msg) + assert.True(t, strings.HasPrefix(err.Error(), includeNonGroupMembers)) + } +} + +func TestRGBlockQuorumCert_invalidConsensusSignature(t *testing.T) { + nodes := MockRGNodes(t, 4) + + result := make(chan *types.Block, 1) + complete := make(chan struct{}, 1) + parent := nodes[0].chain.Genesis() + + block := NewBlockWithSign(parent.Hash(), parent.NumberU64()+1, nodes[0]) + assert.True(t, nodes[0].engine.state.HighestExecutedBlock().Hash() == block.ParentHash()) + nodes[0].engine.OnSeal(block, result, nil, complete) + <-complete + + _, qc := nodes[0].engine.blockTree.FindBlockAndQC(parent.Hash(), parent.NumberU64()) + select { + case b := <-result: + assert.NotNil(t, b) + groupVotes := make(map[uint32]*protocols.PrepareVote) + blockIndex := uint32(0) + + for i := 0; i < len(nodes); i++ { + msg := &protocols.PrepareVote{ + Epoch: nodes[0].engine.state.Epoch(), + ViewNumber: nodes[0].engine.state.ViewNumber(), + BlockIndex: blockIndex, + BlockHash: b.Hash(), + BlockNumber: b.NumberU64(), + ValidatorIndex: uint32(i), + ParentQC: qc, + } + assert.Nil(t, nodes[i].engine.signMsgByBls(msg)) + groupVotes[uint32(i)] = msg + } + rgqc := nodes[0].engine.generatePrepareQC(groupVotes) + + validatorIndex := 1 + groupID := uint32(0) + msg := &protocols.RGBlockQuorumCert{ + GroupID: groupID, + BlockQC: rgqc, + ValidatorIndex: uint32(validatorIndex), + } + assert.Nil(t, nodes[validatorIndex+1].engine.signMsgByBls(msg)) + err := nodes[0].engine.OnRGBlockQuorumCert("id", msg) + assert.True(t, strings.HasPrefix(err.Error(), invalidConsensusSignature)) + } +} + +func TestRGBlockQuorumCert_invalidRGBlockQuorumCert(t *testing.T) { + nodes := MockRGNodes(t, 4) + + result := make(chan *types.Block, 1) + complete := make(chan struct{}, 1) + parent := nodes[0].chain.Genesis() + + block := NewBlockWithSign(parent.Hash(), parent.NumberU64()+1, nodes[0]) + assert.True(t, nodes[0].engine.state.HighestExecutedBlock().Hash() == block.ParentHash()) + nodes[0].engine.OnSeal(block, result, nil, complete) + <-complete + + _, qc := nodes[0].engine.blockTree.FindBlockAndQC(parent.Hash(), parent.NumberU64()) + select { + case b := <-result: + assert.NotNil(t, b) + groupVotes := make(map[uint32]*protocols.PrepareVote) + blockIndex := uint32(0) + + for i := 0; i < len(nodes); i++ { + msg := &protocols.PrepareVote{ + Epoch: nodes[0].engine.state.Epoch(), + ViewNumber: nodes[0].engine.state.ViewNumber(), + BlockIndex: blockIndex, + BlockHash: b.Hash(), + BlockNumber: b.NumberU64(), + ValidatorIndex: uint32(i), + ParentQC: qc, + } + if i < len(nodes)-1 { + assert.Nil(t, nodes[i+1].engine.signMsgByBls(msg)) + } else { + assert.Nil(t, nodes[i].engine.signMsgByBls(msg)) + } + groupVotes[uint32(i)] = msg + } + rgqc := nodes[0].engine.generatePrepareQC(groupVotes) + + validatorIndex := 1 + groupID := uint32(0) + msg := &protocols.RGBlockQuorumCert{ + GroupID: groupID, + BlockQC: rgqc, + ValidatorIndex: uint32(validatorIndex), + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + err := nodes[0].engine.OnRGBlockQuorumCert("id", msg) + assert.True(t, strings.HasPrefix(err.Error(), invalidRGBlockQuorumCert)) + } +} + +func TestRGBlockQuorumCert_richBlockQuorumCert(t *testing.T) { + nodes := MockRGNodes(t, 4) + + result := make(chan *types.Block, 1) + complete := make(chan struct{}, 1) + parent := nodes[0].chain.Genesis() + + block := NewBlockWithSign(parent.Hash(), parent.NumberU64()+1, nodes[0]) + assert.True(t, nodes[0].engine.state.HighestExecutedBlock().Hash() == block.ParentHash()) + nodes[0].engine.OnSeal(block, result, nil, complete) + <-complete + + _, qc := nodes[0].engine.blockTree.FindBlockAndQC(parent.Hash(), parent.NumberU64()) + select { + case b := <-result: + assert.NotNil(t, b) + groupVotes := make(map[uint32]*protocols.PrepareVote) + blockIndex := uint32(0) + + for i := 0; i < len(nodes); i++ { + msg := &protocols.PrepareVote{ + Epoch: nodes[0].engine.state.Epoch(), + ViewNumber: nodes[0].engine.state.ViewNumber(), + BlockIndex: blockIndex, + BlockHash: b.Hash(), + BlockNumber: b.NumberU64(), + ValidatorIndex: uint32(i), + ParentQC: qc, + } + assert.Nil(t, nodes[i].engine.signMsgByBls(msg)) + if i == 0 { + //assert.Nil(t, nodes[0].engine.OnPrepareVote("id", msg), fmt.Sprintf("number:%d", b.NumberU64())) + } else { + groupVotes[uint32(i)] = msg + } + } + rgqc := nodes[0].engine.generatePrepareQC(groupVotes) + + validatorIndex := 1 + groupID := uint32(0) + msg := &protocols.RGBlockQuorumCert{ + GroupID: groupID, + BlockQC: rgqc, + ValidatorIndex: uint32(validatorIndex), + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + + assert.Nil(t, nodes[0].engine.OnRGBlockQuorumCert("id", msg), fmt.Sprintf("number:%d", b.NumberU64())) + assert.Equal(t, 3, nodes[0].engine.state.FindRGBlockQuorumCerts(blockIndex, groupID, uint32(validatorIndex)).BlockQC.ValidatorSet.HasLength()) + selectqc, _ := nodes[0].engine.state.FindMaxGroupRGQuorumCert(blockIndex, groupID) + assert.Equal(t, 4, selectqc.ValidatorSet.HasLength()) + } +} + +func TestRGBlockQuorumCert_mergeVoteToQuorumCerts(t *testing.T) { + nodesNum := 50 + nodes := MockRGNodes(t, nodesNum) + + result := make(chan *types.Block, 1) + complete := make(chan struct{}, 1) + parent := nodes[0].chain.Genesis() + + block := NewBlockWithSign(parent.Hash(), parent.NumberU64()+1, nodes[0]) + assert.True(t, nodes[0].engine.state.HighestExecutedBlock().Hash() == block.ParentHash()) + nodes[0].engine.OnSeal(block, result, nil, complete) + <-complete + + _, qc := nodes[0].engine.blockTree.FindBlockAndQC(parent.Hash(), parent.NumberU64()) + select { + case b := <-result: + assert.NotNil(t, b) + groupVotes := make(map[uint32]*protocols.PrepareVote) + blockIndex := uint32(0) + groupID := uint32(0) + var lastVote *protocols.PrepareVote + + indexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), groupID) + + for i, validatorIndex := range indexes { + msg := &protocols.PrepareVote{ + Epoch: nodes[0].engine.state.Epoch(), + ViewNumber: nodes[0].engine.state.ViewNumber(), + BlockIndex: blockIndex, + BlockHash: b.Hash(), + BlockNumber: b.NumberU64(), + ValidatorIndex: validatorIndex, + ParentQC: qc, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + if i == len(indexes)-1 { + lastVote = msg + } else { + groupVotes[validatorIndex] = msg + } + } + rgqc := nodes[0].engine.generatePrepareQC(groupVotes) + + validatorIndex := indexes[0] + + msg := &protocols.RGBlockQuorumCert{ + GroupID: groupID, + BlockQC: rgqc, + ValidatorIndex: validatorIndex, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + + assert.Nil(t, nodes[0].engine.OnRGBlockQuorumCert("id", msg), fmt.Sprintf("number:%d", b.NumberU64())) + assert.Equal(t, 24, nodes[0].engine.state.FindMaxGroupRGBlockQuorumCert(blockIndex, groupID).BlockQC.ValidatorSet.HasLength()) + selectqc, _ := nodes[0].engine.state.FindMaxGroupRGQuorumCert(blockIndex, groupID) + assert.Equal(t, 24, selectqc.ValidatorSet.HasLength()) + + assert.Nil(t, nodes[0].engine.OnPrepareVote("id", lastVote), fmt.Sprintf("number:%d", b.NumberU64())) + selectqc, _ = nodes[0].engine.state.FindMaxGroupRGQuorumCert(blockIndex, groupID) + assert.Equal(t, 25, selectqc.ValidatorSet.HasLength()) + } +} + +func TestRGViewChangeQuorumCert(t *testing.T) { + nodesNum := 50 + nodes := MockRGNodes(t, nodesNum) + ReachBlock(t, nodes, 5) + + block := nodes[0].engine.state.HighestQCBlock() + block, qc := nodes[0].engine.blockTree.FindBlockAndQC(block.Hash(), block.NumberU64()) + epoch, view := nodes[0].engine.state.Epoch(), nodes[0].engine.state.ViewNumber() + + groupID := uint32(0) + groupViewChanges := make(map[uint32]*protocols.ViewChange) + indexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), groupID) + + for _, validatorIndex := range indexes { + viewChange := &protocols.ViewChange{ + Epoch: epoch, + ViewNumber: view, + BlockHash: block.Hash(), + BlockNumber: block.NumberU64(), + ValidatorIndex: validatorIndex, + PrepareQC: qc, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(viewChange)) + groupViewChanges[validatorIndex] = viewChange + } + + rgqc := nodes[0].engine.generateViewChangeQC(groupViewChanges) + + primaryIndex := indexes[0] + validatorIndex := indexes[1] + msg := &protocols.RGViewChangeQuorumCert{ + GroupID: groupID, + ViewChangeQC: rgqc, + ValidatorIndex: uint32(validatorIndex), + PrepareQCs: &ctypes.PrepareQCs{ + QCs: []*ctypes.QuorumCert{qc}, + }, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + assert.Nil(t, nodes[primaryIndex].engine.OnRGViewChangeQuorumCert("id", msg)) + + // Sleep for a while and wait for Node-primaryIndex to SendRGViewChangeQuorumCert + time.Sleep(5 * time.Second) + + assert.NotNil(t, nodes[primaryIndex].engine.state.FindRGViewChangeQuorumCerts(groupID, uint32(validatorIndex))) + assert.Equal(t, 2, nodes[primaryIndex].engine.state.RGViewChangeQuorumCertsLen(groupID)) + assert.Equal(t, 2, len(nodes[primaryIndex].engine.state.RGViewChangeQuorumCertsIndexes(groupID))) + assert.True(t, true, nodes[primaryIndex].engine.state.HadSendRGViewChangeQuorumCerts(view)) + + assert.Equal(t, 1, nodes[primaryIndex].engine.state.SelectRGViewChangeQuorumCertsLen(groupID)) + assert.Equal(t, 1, len(nodes[primaryIndex].engine.state.FindMaxRGViewChangeQuorumCerts())) +} + +func TestRGViewChangeQuorumCert_insufficientSignatures(t *testing.T) { + nodesNum := 50 + nodes := MockRGNodes(t, nodesNum) + ReachBlock(t, nodes, 5) + + block := nodes[0].engine.state.HighestQCBlock() + block, qc := nodes[0].engine.blockTree.FindBlockAndQC(block.Hash(), block.NumberU64()) + epoch, view := nodes[0].engine.state.Epoch(), nodes[0].engine.state.ViewNumber() + + groupID := uint32(0) + groupViewChanges := make(map[uint32]*protocols.ViewChange) + indexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), groupID) + + for i := 0; i < len(indexes)/2; i++ { + viewChange := &protocols.ViewChange{ + Epoch: epoch, + ViewNumber: view, + BlockHash: block.Hash(), + BlockNumber: block.NumberU64(), + ValidatorIndex: indexes[i], + PrepareQC: qc, + } + assert.Nil(t, nodes[indexes[i]].engine.signMsgByBls(viewChange)) + groupViewChanges[indexes[i]] = viewChange + } + + rgqc := nodes[0].engine.generateViewChangeQC(groupViewChanges) + + validatorIndex := indexes[1] + msg := &protocols.RGViewChangeQuorumCert{ + GroupID: groupID, + ViewChangeQC: rgqc, + ValidatorIndex: validatorIndex, + PrepareQCs: &ctypes.PrepareQCs{ + QCs: []*ctypes.QuorumCert{qc}, + }, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + + err := nodes[0].engine.OnRGViewChangeQuorumCert("id", msg) + assert.True(t, strings.HasPrefix(err.Error(), insufficientSignatures)) +} + +func TestRGViewChangeQuorumCert_notGroupMember(t *testing.T) { + nodesNum := 50 + nodes := MockRGNodes(t, nodesNum) + ReachBlock(t, nodes, 5) + + block := nodes[0].engine.state.HighestQCBlock() + block, qc := nodes[0].engine.blockTree.FindBlockAndQC(block.Hash(), block.NumberU64()) + epoch, view := nodes[0].engine.state.Epoch(), nodes[0].engine.state.ViewNumber() + + groupID, anotherGroupID := uint32(0), uint32(1) + groupViewChanges := make(map[uint32]*protocols.ViewChange) + indexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), groupID) + anotherIndexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), anotherGroupID) + + for _, validatorIndex := range indexes { + viewChange := &protocols.ViewChange{ + Epoch: epoch, + ViewNumber: view, + BlockHash: block.Hash(), + BlockNumber: block.NumberU64(), + ValidatorIndex: validatorIndex, + PrepareQC: qc, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(viewChange)) + groupViewChanges[validatorIndex] = viewChange + } + + rgqc := nodes[0].engine.generateViewChangeQC(groupViewChanges) + + validatorIndex := anotherIndexes[0] + msg := &protocols.RGViewChangeQuorumCert{ + GroupID: groupID, + ViewChangeQC: rgqc, + ValidatorIndex: validatorIndex, + PrepareQCs: &ctypes.PrepareQCs{ + QCs: []*ctypes.QuorumCert{qc}, + }, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + + err := nodes[0].engine.OnRGViewChangeQuorumCert("id", msg) + assert.True(t, strings.HasPrefix(err.Error(), notGroupMember)) +} + +func TestRGViewChangeQuorumCert_includeNonGroupMembers(t *testing.T) { + nodesNum := 50 + nodes := MockRGNodes(t, nodesNum) + ReachBlock(t, nodes, 5) + + block := nodes[0].engine.state.HighestQCBlock() + block, qc := nodes[0].engine.blockTree.FindBlockAndQC(block.Hash(), block.NumberU64()) + epoch, view := nodes[0].engine.state.Epoch(), nodes[0].engine.state.ViewNumber() + + groupID := uint32(0) + groupViewChanges := make(map[uint32]*protocols.ViewChange) + indexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), groupID) + + for i := 0; i < len(nodes); i++ { + viewChange := &protocols.ViewChange{ + Epoch: epoch, + ViewNumber: view, + BlockHash: block.Hash(), + BlockNumber: block.NumberU64(), + ValidatorIndex: uint32(i), + PrepareQC: qc, + } + assert.Nil(t, nodes[i].engine.signMsgByBls(viewChange)) + groupViewChanges[uint32(i)] = viewChange + } + + rgqc := nodes[0].engine.generateViewChangeQC(groupViewChanges) + + validatorIndex := indexes[0] + msg := &protocols.RGViewChangeQuorumCert{ + GroupID: groupID, + ViewChangeQC: rgqc, + ValidatorIndex: validatorIndex, + PrepareQCs: &ctypes.PrepareQCs{ + QCs: []*ctypes.QuorumCert{qc}, + }, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + + err := nodes[0].engine.OnRGViewChangeQuorumCert("id", msg) + assert.True(t, strings.HasPrefix(err.Error(), includeNonGroupMembers)) +} + +func TestRGViewChangeQuorumCert_invalidConsensusSignature(t *testing.T) { + nodes := MockRGNodes(t, 4) + ReachBlock(t, nodes, 5) + + block := nodes[0].engine.state.HighestQCBlock() + block, qc := nodes[0].engine.blockTree.FindBlockAndQC(block.Hash(), block.NumberU64()) + epoch, view := nodes[0].engine.state.Epoch(), nodes[0].engine.state.ViewNumber() + + groupID := uint32(0) + groupViewChanges := make(map[uint32]*protocols.ViewChange) + + for i := 0; i < len(nodes); i++ { + viewChange := &protocols.ViewChange{ + Epoch: epoch, + ViewNumber: view, + BlockHash: block.Hash(), + BlockNumber: block.NumberU64(), + ValidatorIndex: uint32(i), + PrepareQC: qc, + } + assert.Nil(t, nodes[i].engine.signMsgByBls(viewChange)) + groupViewChanges[uint32(i)] = viewChange + } + + rgqc := nodes[0].engine.generateViewChangeQC(groupViewChanges) + + validatorIndex := 1 + msg := &protocols.RGViewChangeQuorumCert{ + GroupID: groupID, + ViewChangeQC: rgqc, + ValidatorIndex: uint32(validatorIndex), + PrepareQCs: &ctypes.PrepareQCs{ + QCs: []*ctypes.QuorumCert{qc}, + }, + } + assert.Nil(t, nodes[validatorIndex+1].engine.signMsgByBls(msg)) + + err := nodes[0].engine.OnRGViewChangeQuorumCert("id", msg) + assert.True(t, strings.HasPrefix(err.Error(), invalidConsensusSignature)) +} + +func TestRGViewChangeQuorumCert_invalidRGViewChangeQuorumCert(t *testing.T) { + nodes := MockRGNodes(t, 4) + ReachBlock(t, nodes, 5) + + block := nodes[0].engine.state.HighestQCBlock() + block, qc := nodes[0].engine.blockTree.FindBlockAndQC(block.Hash(), block.NumberU64()) + epoch, view := nodes[0].engine.state.Epoch(), nodes[0].engine.state.ViewNumber() + + groupID := uint32(0) + groupViewChanges := make(map[uint32]*protocols.ViewChange) + + for i := 0; i < len(nodes); i++ { + viewChange := &protocols.ViewChange{ + Epoch: epoch, + ViewNumber: view, + BlockHash: block.Hash(), + BlockNumber: block.NumberU64(), + ValidatorIndex: uint32(i), + PrepareQC: qc, + } + if i < len(nodes)-1 { + assert.Nil(t, nodes[i+1].engine.signMsgByBls(viewChange)) + } else { + assert.Nil(t, nodes[i].engine.signMsgByBls(viewChange)) + } + groupViewChanges[uint32(i)] = viewChange + } + + rgqc := nodes[0].engine.generateViewChangeQC(groupViewChanges) + + validatorIndex := 1 + msg := &protocols.RGViewChangeQuorumCert{ + GroupID: groupID, + ViewChangeQC: rgqc, + ValidatorIndex: uint32(validatorIndex), + PrepareQCs: &ctypes.PrepareQCs{ + QCs: []*ctypes.QuorumCert{qc}, + }, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + + err := nodes[0].engine.OnRGViewChangeQuorumCert("id", msg) + assert.True(t, strings.HasPrefix(err.Error(), invalidRGViewChangeQuorumCert)) +} + +func TestRGViewChangeQuorumCert_richViewChangeQuorumCert(t *testing.T) { + nodesNum := 50 + nodes := MockRGNodes(t, nodesNum) + ReachBlock(t, nodes, 5) + + block := nodes[0].engine.state.HighestQCBlock() + block, qc := nodes[0].engine.blockTree.FindBlockAndQC(block.Hash(), block.NumberU64()) + epoch, view := nodes[0].engine.state.Epoch(), nodes[0].engine.state.ViewNumber() + + groupID := uint32(0) + groupViewChanges := make(map[uint32]*protocols.ViewChange) + indexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), groupID) + primaryIndex := indexes[0] + validatorIndex := indexes[1] + + for i := 0; i < len(indexes); i++ { + viewChange := &protocols.ViewChange{ + Epoch: epoch, + ViewNumber: view, + BlockHash: block.Hash(), + BlockNumber: block.NumberU64(), + ValidatorIndex: indexes[i], + PrepareQC: qc, + } + assert.Nil(t, nodes[indexes[i]].engine.signMsgByBls(viewChange)) + if i < 8 { + assert.Nil(t, nodes[primaryIndex].engine.OnViewChange("id", viewChange)) + } else { + groupViewChanges[indexes[i]] = viewChange + } + } + + rgqc := nodes[0].engine.generateViewChangeQC(groupViewChanges) + + msg := &protocols.RGViewChangeQuorumCert{ + GroupID: groupID, + ViewChangeQC: rgqc, + ValidatorIndex: validatorIndex, + PrepareQCs: &ctypes.PrepareQCs{ + QCs: []*ctypes.QuorumCert{qc}, + }, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + + assert.Nil(t, nodes[primaryIndex].engine.OnRGViewChangeQuorumCert("id", msg)) + assert.Equal(t, 17, nodes[primaryIndex].engine.state.FindRGViewChangeQuorumCerts(groupID, validatorIndex).ViewChangeQC.HasLength()) + selectqc, _ := nodes[primaryIndex].engine.state.FindMaxGroupRGViewChangeQuorumCert(groupID) + assert.Equal(t, 25, selectqc.HasLength()) +} + +func TestRGViewChangeQuorumCert_mergeViewChangeToViewChangeQuorumCerts(t *testing.T) { + nodesNum := 50 + nodes := MockRGNodes(t, nodesNum) + ReachBlock(t, nodes, 5) + + block := nodes[0].engine.state.HighestQCBlock() + block, qc := nodes[0].engine.blockTree.FindBlockAndQC(block.Hash(), block.NumberU64()) + epoch, view := nodes[0].engine.state.Epoch(), nodes[0].engine.state.ViewNumber() + + groupID := uint32(0) + groupViewChanges := make(map[uint32]*protocols.ViewChange) + lastViewChanges := make(map[uint32]*protocols.ViewChange) + indexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), groupID) + primaryIndex := indexes[0] + validatorIndex := indexes[1] + + for i := 0; i < len(indexes); i++ { + viewChange := &protocols.ViewChange{ + Epoch: epoch, + ViewNumber: view, + BlockHash: block.Hash(), + BlockNumber: block.NumberU64(), + ValidatorIndex: indexes[i], + PrepareQC: qc, + } + assert.Nil(t, nodes[indexes[i]].engine.signMsgByBls(viewChange)) + if i < 8 { + lastViewChanges[indexes[i]] = viewChange + } else { + groupViewChanges[indexes[i]] = viewChange + } + } + + rgqc := nodes[0].engine.generateViewChangeQC(groupViewChanges) + + msg := &protocols.RGViewChangeQuorumCert{ + GroupID: groupID, + ViewChangeQC: rgqc, + ValidatorIndex: validatorIndex, + PrepareQCs: &ctypes.PrepareQCs{ + QCs: []*ctypes.QuorumCert{qc}, + }, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + + assert.Nil(t, nodes[primaryIndex].engine.OnRGViewChangeQuorumCert("id", msg)) + assert.Equal(t, 17, nodes[primaryIndex].engine.state.FindRGViewChangeQuorumCerts(groupID, validatorIndex).ViewChangeQC.HasLength()) + selectqc, _ := nodes[primaryIndex].engine.state.FindMaxGroupRGViewChangeQuorumCert(groupID) + assert.Equal(t, 17, selectqc.HasLength()) + + for _, viewChange := range lastViewChanges { + assert.Nil(t, nodes[primaryIndex].engine.OnViewChange("id", viewChange)) + } + assert.Equal(t, 17, nodes[primaryIndex].engine.state.FindRGViewChangeQuorumCerts(groupID, validatorIndex).ViewChangeQC.HasLength()) + selectqc, _ = nodes[primaryIndex].engine.state.FindMaxGroupRGViewChangeQuorumCert(groupID) + assert.Equal(t, 25, selectqc.HasLength()) +} diff --git a/consensus/cbft/network/pubsub_test.go b/consensus/cbft/network/pubsub_test.go index 4ce8b24430..0b33098b78 100644 --- a/consensus/cbft/network/pubsub_test.go +++ b/consensus/cbft/network/pubsub_test.go @@ -25,12 +25,17 @@ import ( "github.com/AlayaNetwork/Alaya-Go/p2p" "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/params" + "github.com/AlayaNetwork/Alaya-Go/x/xcom" "github.com/stretchr/testify/assert" "sync" "testing" "time" ) +func init() { + xcom.GetEc(xcom.DefaultUnitTestNet) +} + func makePubSub(handlerMsg func(p *peer, msg *p2p.Msg) error) (*PubSub, *p2p.Server) { sk, err := crypto.GenerateKey() if nil != err { @@ -49,7 +54,7 @@ func makePubSub(handlerMsg func(p *peer, msg *p2p.Msg) error) (*PubSub, *p2p.Ser p2pServer.SetPubSubServer(psServer, cancel) pubSub := NewPubSub(psServer) pubSub.Start(ctypes.Config{Sys: params.AlayaChainConfig.Cbft, Option: nil}, func(id string) (p *peer, err error) { - return nil, nil + return newPeer(CbftPubSubProtocolVersion, p2p.NewPeer(localNode.ID(), "", nil), nil), nil }, handlerMsg, new(event.TypeMux)) return pubSub, p2pServer } @@ -132,7 +137,7 @@ func TestPubSubPublish(t *testing.T) { t.Fatal(err) } }() - time.Sleep(time.Millisecond * 6) + time.Sleep(time.Millisecond * 800) // Topics of interest for node registration. // Send messages between nodes @@ -141,14 +146,14 @@ func TestPubSubPublish(t *testing.T) { if err := pubSub1.Subscribe(topic); err != nil { t.Fatal(err) } - time.Sleep(time.Millisecond * 3) + time.Sleep(time.Millisecond * 800) pubSub1.Publish(topic, uint64(1), expect[0]) }() go func() { if err := pubSub2.Subscribe(topic); err != nil { t.Fatal(err) } - time.Sleep(time.Millisecond * 3) + time.Sleep(time.Millisecond * 800) pubSub2.Publish(topic, uint64(2), expect[1]) }() wg.Wait() @@ -255,7 +260,7 @@ func TestPubSubPublish_DifferentTopics(t *testing.T) { t.Fatal(err) } }() - time.Sleep(time.Millisecond * 6) + time.Sleep(time.Millisecond * 800) topic1 := "test1" topic2 := "test2" @@ -266,21 +271,21 @@ func TestPubSubPublish_DifferentTopics(t *testing.T) { if err := pubSub1.Subscribe(topic2); err != nil { t.Fatal(err) } - time.Sleep(time.Millisecond * 3) + time.Sleep(time.Millisecond * 800) pubSub1.Publish(topic1, uint64(1), expect[0]) }() go func() { if err := pubSub2.Subscribe(topic1); err != nil { t.Fatal(err) } - time.Sleep(time.Millisecond * 3) + time.Sleep(time.Millisecond * 800) pubSub2.Publish(topic1, uint64(2), expect[1]) }() go func() { if err := pubSub3.Subscribe(topic2); err != nil { t.Fatal(err) } - time.Sleep(time.Millisecond * 3) + time.Sleep(time.Millisecond * 800) pubSub3.Publish(topic2, uint64(3), expect[2]) }() wg.Wait() @@ -393,28 +398,28 @@ func TestPubSubPublish_ForwardMessage(t *testing.T) { t.Fatal(err) } }() - time.Sleep(time.Millisecond * 6) + time.Sleep(time.Millisecond * 800) topic := "test" go func() { if err := pubSub1.Subscribe(topic); err != nil { t.Fatal(err) } - time.Sleep(time.Millisecond * 3) + time.Sleep(time.Millisecond * 800) pubSub1.Publish(topic, uint64(1), expect[0]) }() go func() { if err := pubSub2.Subscribe(topic); err != nil { t.Fatal(err) } - time.Sleep(time.Millisecond * 3) + time.Sleep(time.Millisecond * 800) pubSub2.Publish(topic, uint64(2), expect[1]) }() go func() { if err := pubSub3.Subscribe(topic); err != nil { t.Fatal(err) } - time.Sleep(time.Millisecond * 3) + time.Sleep(time.Millisecond * 800) pubSub3.Publish(topic, uint64(3), expect[2]) }() wg.Wait() diff --git a/consensus/cbft/sync_process_rg_test.go b/consensus/cbft/sync_process_rg_test.go new file mode 100644 index 0000000000..7f34909f8a --- /dev/null +++ b/consensus/cbft/sync_process_rg_test.go @@ -0,0 +1,120 @@ +// Copyright 2021 The Alaya Network Authors +// This file is part of the Alaya-Go library. +// +// The Alaya-Go 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 Alaya-Go 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 Alaya-Go library. If not, see . + +package cbft + +import ( + "fmt" + "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" + "github.com/AlayaNetwork/Alaya-Go/core/types" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestCbft_MissingPrepareVoteV2(t *testing.T) { + nodesNum := 50 + nodes := MockRGNodes(t, nodesNum) + + result := make(chan *types.Block, 1) + complete := make(chan struct{}, 1) + parent := nodes[0].chain.Genesis() + + block := NewBlockWithSign(parent.Hash(), parent.NumberU64()+1, nodes[0]) + assert.True(t, nodes[0].engine.state.HighestExecutedBlock().Hash() == block.ParentHash()) + nodes[0].engine.OnSeal(block, result, nil, complete) + <-complete + + _, qc := nodes[0].engine.blockTree.FindBlockAndQC(parent.Hash(), parent.NumberU64()) + select { + case b := <-result: + assert.NotNil(t, b) + blockIndex := uint32(0) + + groupID, anotherGroupID := uint32(0), uint32(1) + indexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), groupID) + anotherIndexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), anotherGroupID) + fmt.Println(indexes) + fmt.Println(anotherIndexes) + + for i, validatorIndex := range indexes { + if i < 17 { + msg := &protocols.PrepareVote{ + Epoch: nodes[0].engine.state.Epoch(), + ViewNumber: nodes[0].engine.state.ViewNumber(), + BlockIndex: blockIndex, + BlockHash: b.Hash(), + BlockNumber: b.NumberU64(), + ValidatorIndex: validatorIndex, + ParentQC: qc, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + nodes[0].engine.OnPrepareVote("id", msg) + } + } + + for i, validatorIndex := range anotherIndexes { + if i%2 == 0 { + msg := &protocols.PrepareVote{ + Epoch: nodes[0].engine.state.Epoch(), + ViewNumber: nodes[0].engine.state.ViewNumber(), + BlockIndex: blockIndex, + BlockHash: b.Hash(), + BlockNumber: b.NumberU64(), + ValidatorIndex: validatorIndex, + ParentQC: qc, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + nodes[0].engine.OnPrepareVote("id", msg) + } + } + request, err := nodes[0].engine.MissingPrepareVote() + assert.Nil(t, err) + getPrepareVoteV2, ok := request.(*protocols.GetPrepareVoteV2) + assert.True(t, true, ok) + fmt.Println(getPrepareVoteV2.String()) + assert.Equal(t, 2, len(getPrepareVoteV2.UnKnownGroups.UnKnown)) + assert.Equal(t, 20, getPrepareVoteV2.UnKnownGroups.UnKnownSize()) + + for _, unKnown := range getPrepareVoteV2.UnKnownGroups.UnKnown { + groupID := unKnown.GroupID + unKnownSet := unKnown.UnKnownSet + indexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), groupID) + for i, validatorIndex := range indexes { + if groupID == 0 { + if i >= 17 { + assert.True(t, true, unKnownSet.GetIndex(validatorIndex)) + } + } else if groupID == 1 { + if i%2 != 0 { + assert.True(t, true, unKnownSet.GetIndex(validatorIndex)) + } + } + } + } + } +} + +func TestCbft_MissingViewChangeNodesV2(t *testing.T) { + +} + +func TestCbft_OnGetPrepareVoteV2(t *testing.T) { + +} + +func TestCbft_OnGetViewChangeV2(t *testing.T) { + +} diff --git a/node/service.go b/node/service.go index b0da5121e0..9cdf6d9ee0 100644 --- a/node/service.go +++ b/node/service.go @@ -51,6 +51,16 @@ func NewServiceContext(config *Config, services map[reflect.Type]Service, EventM } } +func NewServiceContextWithPubSubServer(config *Config, services map[reflect.Type]Service, EventMux *event.TypeMux, AccountManager *accounts.Manager, pubSubServer *p2p.PubSubServer) *ServiceContext { + return &ServiceContext{ + config: config, + services: services, + EventMux: EventMux, + AccountManager: AccountManager, + pubSubServer: pubSubServer, + } +} + // OpenDatabase opens an existing database with the given name (or creates one // if no previous can be found) from within the node's data directory. If the // node is an ephemeral one, a memory database is returned. From 7f34750c5757e01ad11c4a59e6ab4d3fe8e2224d Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Thu, 10 Feb 2022 19:22:29 +0800 Subject: [PATCH 260/299] fix bug and refine unit testing --- consensus/cbft/state/state_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/consensus/cbft/state/state_test.go b/consensus/cbft/state/state_test.go index a8cd34e8d3..53665ddd6a 100644 --- a/consensus/cbft/state/state_test.go +++ b/consensus/cbft/state/state_test.go @@ -293,7 +293,8 @@ func TestViewRGBlockQuorumCerts(t *testing.T) { assert.NotNil(t, v.FindRGBlockQuorumCerts(1, 3, 33)) assert.Equal(t, uint32(11), v.FindRGBlockQuorumCerts(0, 1, 11).ValidatorIndex) - assert.Equal(t, []uint32{11, 12}, v.RGBlockQuorumCertsIndexes(0, 1)) + //assert.Equal(t, []uint32{11, 12}, v.RGBlockQuorumCertsIndexes(0, 1)) + assert.Equal(t, 2, len(v.RGBlockQuorumCertsIndexes(0, 1))) assert.Equal(t, `"x_xxx_"`, marshalBitArray(v.FindMaxGroupRGBlockQuorumCert(2, 2).BlockQC.ValidatorSet)) } From f4340291fe1a74527401b9f800e3c97e63eaa23f Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 14 Feb 2022 17:47:29 +0800 Subject: [PATCH 261/299] update --- cmd/utils/flags.go | 5 +++-- consensus/cbft/cbft_common_util.go | 1 - eth/config.go | 21 +++++++++++---------- eth/gen_config.go | 12 ------------ mobile/platon.go | 6 +++--- node/node.go | 13 +++---------- p2p/simulations/adapters/inproc.go | 10 +++++----- 7 files changed, 25 insertions(+), 43 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 0d44389ffd..acc35f930d 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1131,17 +1131,18 @@ func SetCbft(ctx *cli.Context, cfg *types.OptionsConfig, nodeCfg *node.Config) { func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend { if cfg.SyncMode == downloader.LightSync { Fatalf("Failed to register the Alaya-Go service: %v", "not support LightSync") + return nil } else { backend, err := eth.New(stack, cfg) if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } - if cfg.LightServ > 0 { + /* if cfg.LightServ > 0 { _, err := les.NewLesServer(stack, backend, cfg) if err != nil { Fatalf("Failed to create the LES server: %v", err) } - } + }*/ return backend.APIBackend } } diff --git a/consensus/cbft/cbft_common_util.go b/consensus/cbft/cbft_common_util.go index 0bb7cb1ebc..e2ad3da50f 100644 --- a/consensus/cbft/cbft_common_util.go +++ b/consensus/cbft/cbft_common_util.go @@ -46,7 +46,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/core/vm" "github.com/AlayaNetwork/Alaya-Go/crypto" "github.com/AlayaNetwork/Alaya-Go/crypto/bls" - "github.com/AlayaNetwork/Alaya-Go/event" "github.com/AlayaNetwork/Alaya-Go/params" ) diff --git a/eth/config.go b/eth/config.go index 8a74e41318..43cc0c893e 100644 --- a/eth/config.go +++ b/eth/config.go @@ -17,10 +17,11 @@ package eth import ( - "github.com/AlayaNetwork/Alaya-Go/miner" "math/big" "time" + "github.com/AlayaNetwork/Alaya-Go/miner" + "github.com/AlayaNetwork/Alaya-Go/params" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" @@ -42,12 +43,12 @@ var DefaultConfig = Config{ Period: 20000, Amount: 10, }, - NetworkId: 1, - LightPeers: 100, - DatabaseCache: 768, - TrieCache: 32, - TrieTimeout: 60 * time.Minute, - TrieDBCache: 512, + NetworkId: 1, + //LightPeers: 100, + DatabaseCache: 768, + TrieCache: 32, + TrieTimeout: 60 * time.Minute, + TrieDBCache: 512, DBDisabledGC: false, DBGCInterval: 86400, DBGCTimeout: time.Minute, @@ -105,8 +106,8 @@ type Config struct { NoPruning bool // Light client options - LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests - LightPeers int `toml:",omitempty"` // Maximum number of LES client peers + //LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests + //LightPeers int `toml:",omitempty"` // Maximum number of LES client peers // Database options SkipBcVersionCheck bool `toml:"-"` @@ -128,7 +129,7 @@ type Config struct { VmTimeoutDuration uint64 // Mining options - Miner miner.Config + Miner miner.Config // minning conig MiningLogAtDepth uint // miningLogAtDepth is the number of confirmations before logging successful mining. TxChanSize int // txChanSize is the size of channel listening to NewTxsEvent.The number is referenced from the size of tx pool. diff --git a/eth/gen_config.go b/eth/gen_config.go index 8fa9cb5979..9af8202703 100644 --- a/eth/gen_config.go +++ b/eth/gen_config.go @@ -21,8 +21,6 @@ func (c Config) MarshalTOML() (interface{}, error) { NetworkId uint64 SyncMode downloader.SyncMode NoPruning bool - LightServ int `toml:",omitempty"` - LightPeers int `toml:",omitempty"` SkipBcVersionCheck bool `toml:"-"` DatabaseHandles int `toml:"-"` DatabaseCache int @@ -70,8 +68,6 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.NetworkId = c.NetworkId enc.SyncMode = c.SyncMode enc.NoPruning = c.NoPruning - enc.LightServ = c.LightServ - enc.LightPeers = c.LightPeers enc.SkipBcVersionCheck = c.SkipBcVersionCheck enc.DatabaseHandles = c.DatabaseHandles enc.DatabaseCache = c.DatabaseCache @@ -123,8 +119,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { NetworkId *uint64 SyncMode *downloader.SyncMode NoPruning *bool - LightServ *int `toml:",omitempty"` - LightPeers *int `toml:",omitempty"` SkipBcVersionCheck *bool `toml:"-"` DatabaseHandles *int `toml:"-"` DatabaseCache *int @@ -185,12 +179,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.NoPruning != nil { c.NoPruning = *dec.NoPruning } - if dec.LightServ != nil { - c.LightServ = *dec.LightServ - } - if dec.LightPeers != nil { - c.LightPeers = *dec.LightPeers - } if dec.SkipBcVersionCheck != nil { c.SkipBcVersionCheck = *dec.SkipBcVersionCheck } diff --git a/mobile/platon.go b/mobile/platon.go index 1da25f8658..4673238943 100644 --- a/mobile/platon.go +++ b/mobile/platon.go @@ -29,7 +29,6 @@ import ( "github.com/AlayaNetwork/Alaya-Go/eth" "github.com/AlayaNetwork/Alaya-Go/eth/downloader" "github.com/AlayaNetwork/Alaya-Go/ethclient" - "github.com/AlayaNetwork/Alaya-Go/ethstats" "github.com/AlayaNetwork/Alaya-Go/internal/debug" "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p" @@ -161,7 +160,8 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { ethConf.SyncMode = downloader.LightSync ethConf.NetworkId = uint64(config.EthereumNetworkID) ethConf.DatabaseCache = config.EthereumDatabaseCache - lesBackend, err := les.New(rawStack, ðConf) + return nil, errors.New("not support") + /*lesBackend, err := les.New(rawStack, ðConf) if err != nil { return nil, fmt.Errorf("ethereum init: %v", err) } @@ -170,7 +170,7 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { if err := ethstats.New(rawStack, lesBackend.ApiBackend, lesBackend.Engine(), config.EthereumNetStats); err != nil { return nil, fmt.Errorf("netstats init: %v", err) } - } + }*/ } return &Node{rawStack}, nil } diff --git a/node/node.go b/node/node.go index e204ff153c..83ecc16fa5 100644 --- a/node/node.go +++ b/node/node.go @@ -181,12 +181,10 @@ func (n *Node) Start() error { copy(lifecycles, n.lifecycles) n.lock.Unlock() - running := &p2p.Server{Config: n.serverConfig} - localNode := enode.NewV4(&n.serverConfig.PrivateKey.PublicKey, nil, 0, 0) + localNode := enode.NewV4(&n.server.Config.PrivateKey.PublicKey, nil, 0, 0) ctx, cancel := context.WithCancel(context.Background()) - pubSubServer := p2p.NewPubSubServer(ctx, localNode, running) - running.SetPubSubServer(pubSubServer, cancel) - n.log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name) + pubSubServer := p2p.NewPubSubServer(ctx, localNode, n.server) + n.server.SetPubSubServer(pubSubServer, cancel) // Check if networking startup failed. if err != nil { @@ -321,11 +319,6 @@ func (n *Node) stopServices(running []Lifecycle) error { return nil } -// Config returns the configuration of node. -func (n *Node) Config() *Config { - return n.config -} - func (n *Node) openDataDir() error { if n.config.DataDir == "" { return nil // ephemeral diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index 102f535598..30b7e1f10e 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -38,9 +38,9 @@ import ( // SimAdapter is a NodeAdapter which creates in-memory simulation nodes and // connects them using net.Pipe type SimAdapter struct { - pipe func() (net.Conn, net.Conn, error) - mtx sync.RWMutex - nodes map[enode.ID]*SimNode + pipe func() (net.Conn, net.Conn, error) + mtx sync.RWMutex + nodes map[enode.ID]*SimNode lifecycles LifecycleConstructors } @@ -50,8 +50,8 @@ type SimAdapter struct { // the adapter uses a net.Pipe for in-memory simulated network connections func NewSimAdapter(services LifecycleConstructors) *SimAdapter { return &SimAdapter{ - pipe: pipes.NetPipe, - nodes: make(map[enode.ID]*SimNode), + pipe: pipes.NetPipe, + nodes: make(map[enode.ID]*SimNode), lifecycles: services, } } From 1d0e0f5b1fa7d7dfa254971aa1a1a8be2cab25d5 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Mon, 14 Feb 2022 17:58:00 +0800 Subject: [PATCH 262/299] fix sync bug and refine sync unit testing --- consensus/cbft/cbft.go | 4 +- consensus/cbft/sync_process.go | 43 ++- consensus/cbft/sync_process_rg_test.go | 484 ++++++++++++++++++++++--- 3 files changed, 465 insertions(+), 66 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index fb03a1ef77..ade6ebf862 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -647,7 +647,7 @@ func (cbft *Cbft) handleSyncMsg(info *ctypes.MsgInfo) error { err = cbft.OnGetPrepareVote(id, msg) case *protocols.GetPrepareVoteV2: - err = cbft.OnGetPrepareVoteV2(id, msg) + _, err = cbft.OnGetPrepareVoteV2(id, msg) case *protocols.PrepareVotes: err = cbft.OnPrepareVotes(id, msg) @@ -671,7 +671,7 @@ func (cbft *Cbft) handleSyncMsg(info *ctypes.MsgInfo) error { err = cbft.OnGetViewChange(id, msg) case *protocols.GetViewChangeV2: - err = cbft.OnGetViewChangeV2(id, msg) + _, err = cbft.OnGetViewChangeV2(id, msg) case *protocols.ViewChangeQuorumCert: err = cbft.OnViewChangeQuorumCert(id, msg) diff --git a/consensus/cbft/sync_process.go b/consensus/cbft/sync_process.go index d3c795efa4..1e5a9c3db0 100644 --- a/consensus/cbft/sync_process.go +++ b/consensus/cbft/sync_process.go @@ -337,16 +337,17 @@ func (cbft *Cbft) OnGetQCBlockList(id string, msg *protocols.GetQCBlockList) err // OnGetPrepareVoteV2 is responsible for processing the business logic // of the GetPrepareVote message. It will synchronously return a // PrepareVotes message to the sender. -func (cbft *Cbft) OnGetPrepareVoteV2(id string, msg *protocols.GetPrepareVoteV2) error { +func (cbft *Cbft) OnGetPrepareVoteV2(id string, msg *protocols.GetPrepareVoteV2) (ctypes.Message, error) { cbft.log.Debug("Received message on OnGetPrepareVoteV2", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) if msg.Epoch == cbft.state.Epoch() && msg.ViewNumber == cbft.state.ViewNumber() { // If the block has already QC, that response QC instead of votes. // Avoid the sender spent a lot of time to verifies PrepareVote msg. _, qc := cbft.state.ViewBlockAndQC(msg.BlockIndex) if qc != nil { + blockQuorumCert := &protocols.BlockQuorumCert{BlockQC: qc} cbft.network.Send(id, &protocols.BlockQuorumCert{BlockQC: qc}) cbft.log.Debug("Send BlockQuorumCert", "peer", id, "qc", qc.String()) - return nil + return blockQuorumCert, nil } if len(msg.UnKnownGroups.UnKnown) > 0 { @@ -369,16 +370,21 @@ func (cbft *Cbft) OnGetPrepareVoteV2(id string, msg *protocols.GetPrepareVoteV2) if remain <= 0 { break } + groupLen := cbft.groupLen(cbft.state.Epoch(), un.GroupID) groupThreshold := cbft.groupThreshold(cbft.state.Epoch(), un.GroupID) // the other party has not reached a group consensus, and directly returns the group aggregation signature (if it exists locally) var rgqc *protocols.RGBlockQuorumCert - if un.UnKnownSet.HasLength() < groupThreshold { + if groupLen-un.UnKnownSet.HasLength() < groupThreshold { rgqc = cbft.state.FindMaxGroupRGBlockQuorumCert(msg.BlockIndex, un.GroupID) if rgqc != nil { RGBlockQuorumCerts = append(RGBlockQuorumCerts, rgqc) matched := rgqc.BlockQC.ValidatorSet.And(un.UnKnownSet).HasLength() remain -= matched } + // Limit response votes + if remain <= 0 { + break + } } if len(prepareVoteMap) > 0 { for i := uint32(0); i < un.UnKnownSet.Size(); i++ { @@ -398,12 +404,14 @@ func (cbft *Cbft) OnGetPrepareVoteV2(id string, msg *protocols.GetPrepareVoteV2) } if len(votes) > 0 || len(RGBlockQuorumCerts) > 0 { - cbft.network.Send(id, &protocols.PrepareVotesV2{Epoch: msg.Epoch, ViewNumber: msg.ViewNumber, BlockIndex: msg.BlockIndex, Votes: votes, RGBlockQuorumCerts: RGBlockQuorumCerts}) + prepareVotesV2 := &protocols.PrepareVotesV2{Epoch: msg.Epoch, ViewNumber: msg.ViewNumber, BlockIndex: msg.BlockIndex, Votes: votes, RGBlockQuorumCerts: RGBlockQuorumCerts} + cbft.network.Send(id, prepareVotesV2) cbft.log.Debug("Send PrepareVotesV2", "peer", id, "blockIndex", msg.BlockIndex, "votes length", len(votes), "RGBlockQuorumCerts length", len(RGBlockQuorumCerts)) + return prepareVotesV2, nil } } } - return nil + return nil, nil } // OnGetPrepareVoteV2 is responsible for processing the business logic @@ -620,7 +628,7 @@ func (cbft *Cbft) OnPrepareBlockHash(id string, msg *protocols.PrepareBlockHash) // OnGetViewChangeV2 responds to nodes that require viewChange. // The Epoch and viewNumber of viewChange must be consistent // with the state of the current node. -func (cbft *Cbft) OnGetViewChangeV2(id string, msg *protocols.GetViewChangeV2) error { +func (cbft *Cbft) OnGetViewChangeV2(id string, msg *protocols.GetViewChangeV2) (ctypes.Message, error) { cbft.log.Debug("Received message on OnGetViewChangeV2", "from", id, "msgHash", msg.MsgHash(), "message", msg.String(), "local", cbft.state.ViewString()) localEpoch, localViewNumber := cbft.state.Epoch(), cbft.state.ViewNumber() @@ -656,16 +664,21 @@ func (cbft *Cbft) OnGetViewChangeV2(id string, msg *protocols.GetViewChangeV2) e if remain <= 0 { break } + groupLen := cbft.groupLen(cbft.state.Epoch(), un.GroupID) groupThreshold := cbft.groupThreshold(cbft.state.Epoch(), un.GroupID) // the other party has not reached a group consensus, and directly returns the group aggregation signature (if it exists locally) var rgqc *protocols.RGViewChangeQuorumCert - if un.UnKnownSet.HasLength() < groupThreshold { + if groupLen-un.UnKnownSet.HasLength() < groupThreshold { rgqc = cbft.state.FindMaxRGViewChangeQuorumCert(un.GroupID) if rgqc != nil { RGViewChangeQuorumCerts = append(RGViewChangeQuorumCerts, rgqc) matched := rgqc.ViewChangeQC.ValidatorSet().And(un.UnKnownSet).HasLength() remain -= matched } + // Limit response votes + if remain <= 0 { + break + } } if len(viewChangeMap) > 0 { for i := uint32(0); i < un.UnKnownSet.Size(); i++ { @@ -685,27 +698,29 @@ func (cbft *Cbft) OnGetViewChangeV2(id string, msg *protocols.GetViewChangeV2) e } if len(viewChanges) > 0 || len(RGViewChangeQuorumCerts) > 0 { - cbft.network.Send(id, &protocols.ViewChangesV2{VCs: viewChanges, RGViewChangeQuorumCerts: RGViewChangeQuorumCerts}) + viewChangesV2 := &protocols.ViewChangesV2{VCs: viewChanges, RGViewChangeQuorumCerts: RGViewChangeQuorumCerts} + cbft.network.Send(id, viewChangesV2) cbft.log.Debug("Send ViewChangesV2", "peer", id, "viewChanges length", len(viewChanges), "RGViewChangeQuorumCerts length", len(RGViewChangeQuorumCerts)) + return viewChangesV2, nil } - return nil + return nil, nil } // Return view QC in the case of less than 1. if isLastView() { lastViewChangeQC := cbft.state.LastViewChangeQC() if lastViewChangeQC == nil { cbft.log.Info("Not found lastViewChangeQC") - return nil + return nil, nil } err := lastViewChangeQC.EqualAll(msg.Epoch, msg.ViewNumber) if err != nil { cbft.log.Error("Last view change is not equal msg.viewNumber", "err", err) - return err + return nil, err } cbft.network.Send(id, &protocols.ViewChangeQuorumCert{ ViewChangeQC: lastViewChangeQC, }) - return nil + return nil, nil } // get previous viewChangeQC from wal db if isPreviousView() { @@ -720,11 +735,11 @@ func (cbft *Cbft) OnGetViewChangeV2(id string, msg *protocols.GetViewChangeV2) e } cbft.log.Debug("Send previous viewChange quorumCert", "viewChangeQuorumCert", viewChangeQuorumCert.String()) cbft.network.Send(id, viewChangeQuorumCert) - return nil + return nil, nil } } - return fmt.Errorf("request is not match local view, local:%s,msg:%s", cbft.state.ViewString(), msg.String()) + return nil, fmt.Errorf("request is not match local view, local:%s,msg:%s", cbft.state.ViewString(), msg.String()) } // OnGetViewChangeV2 responds to nodes that require viewChange. diff --git a/consensus/cbft/sync_process_rg_test.go b/consensus/cbft/sync_process_rg_test.go index 7f34909f8a..6df59f787c 100644 --- a/consensus/cbft/sync_process_rg_test.go +++ b/consensus/cbft/sync_process_rg_test.go @@ -19,12 +19,14 @@ package cbft import ( "fmt" "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" + ctypes "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/types" "github.com/AlayaNetwork/Alaya-Go/core/types" "github.com/stretchr/testify/assert" "testing" + "time" ) -func TestCbft_MissingPrepareVoteV2(t *testing.T) { +func TestCbft_SyncPrepareVoteV2(t *testing.T) { nodesNum := 50 nodes := MockRGNodes(t, nodesNum) @@ -38,83 +40,465 @@ func TestCbft_MissingPrepareVoteV2(t *testing.T) { <-complete _, qc := nodes[0].engine.blockTree.FindBlockAndQC(parent.Hash(), parent.NumberU64()) + epoch, viewNumber, blockIndex := nodes[0].engine.state.Epoch(), nodes[0].engine.state.ViewNumber(), uint32(0) + missValidatorIndex, syncValidatorIndex := uint32(0), uint32(0) + select { case b := <-result: assert.NotNil(t, b) - blockIndex := uint32(0) - + proposal, _ := nodes[0].engine.isCurrentValidator() groupID, anotherGroupID := uint32(0), uint32(1) indexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), groupID) anotherIndexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), anotherGroupID) fmt.Println(indexes) fmt.Println(anotherIndexes) + for _, validatorIndex := range indexes { + if validatorIndex != proposal.Index { + missValidatorIndex = validatorIndex + break + } + } + for _, validatorIndex := range anotherIndexes { + if validatorIndex != proposal.Index { + syncValidatorIndex = validatorIndex + break + } + } + + prepareBlock := &protocols.PrepareBlock{ + Epoch: epoch, + ViewNumber: viewNumber, + Block: block, + BlockIndex: blockIndex, + ProposalIndex: proposal.Index, + } + assert.Nil(t, nodes[0].engine.signMsgByBls(prepareBlock)) + nodes[missValidatorIndex].engine.state.AddPrepareBlock(prepareBlock) + nodes[syncValidatorIndex].engine.state.AddPrepareBlock(prepareBlock) + for i, validatorIndex := range indexes { + msg := &protocols.PrepareVote{ + Epoch: epoch, + ViewNumber: viewNumber, + BlockIndex: blockIndex, + BlockHash: b.Hash(), + BlockNumber: b.NumberU64(), + ValidatorIndex: validatorIndex, + ParentQC: qc, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) if i < 17 { - msg := &protocols.PrepareVote{ - Epoch: nodes[0].engine.state.Epoch(), - ViewNumber: nodes[0].engine.state.ViewNumber(), - BlockIndex: blockIndex, - BlockHash: b.Hash(), - BlockNumber: b.NumberU64(), - ValidatorIndex: validatorIndex, - ParentQC: qc, - } - assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) - nodes[0].engine.OnPrepareVote("id", msg) + assert.Nil(t, nodes[missValidatorIndex].engine.OnPrepareVote("id", msg)) + } else { + assert.Nil(t, nodes[syncValidatorIndex].engine.OnPrepareVote("id", msg)) } } for i, validatorIndex := range anotherIndexes { + msg := &protocols.PrepareVote{ + Epoch: nodes[0].engine.state.Epoch(), + ViewNumber: nodes[0].engine.state.ViewNumber(), + BlockIndex: blockIndex, + BlockHash: b.Hash(), + BlockNumber: b.NumberU64(), + ValidatorIndex: validatorIndex, + ParentQC: qc, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) if i%2 == 0 { - msg := &protocols.PrepareVote{ - Epoch: nodes[0].engine.state.Epoch(), - ViewNumber: nodes[0].engine.state.ViewNumber(), - BlockIndex: blockIndex, - BlockHash: b.Hash(), - BlockNumber: b.NumberU64(), - ValidatorIndex: validatorIndex, - ParentQC: qc, + assert.Nil(t, nodes[missValidatorIndex].engine.OnPrepareVote("id", msg)) + } + assert.Nil(t, nodes[syncValidatorIndex].engine.OnPrepareVote("id", msg)) + } + } + + // 1、missValidatorIndex节点和 syncValidatorIndex节点缺失的 votes + getPrepareVoteV2, _ := testMissingPrepareVotev2(t, nodes[missValidatorIndex], nodes[syncValidatorIndex]) + + // 2、missValidatorIndex节点 向 syncValidatorIndex节点 请求同步, syncValidatorIndex节点返回的 votes + prepareVotesV2 := testOnGetPrepareVoteV2(t, nodes[syncValidatorIndex], getPrepareVoteV2) + assert.Equal(t, 4, len(prepareVotesV2.Votes)) + assert.Equal(t, 0, len(prepareVotesV2.RGBlockQuorumCerts)) + + // 3、missValidatorIndex节点处理同步的 votes + assert.Equal(t, 1, len(nodes[missValidatorIndex].engine.state.FindMaxRGQuorumCerts(blockIndex))) + assert.Equal(t, 17, nodes[missValidatorIndex].engine.state.FindMaxRGQuorumCerts(blockIndex)[0].ValidatorSet.HasLength()) + testOnPrepareVotesV2(t, nodes[missValidatorIndex], prepareVotesV2) // node2 响应 node1 同步 + assert.Equal(t, 1, len(nodes[missValidatorIndex].engine.state.FindMaxRGQuorumCerts(blockIndex))) + assert.Equal(t, 21, nodes[missValidatorIndex].engine.state.FindMaxRGQuorumCerts(blockIndex)[0].ValidatorSet.HasLength()) +} + +func testMissingPrepareVotev2(t *testing.T, missNode, syncNode *TestCBFT) (*protocols.GetPrepareVoteV2, *protocols.GetPrepareVoteV2) { + // check missNode + request1, err := missNode.engine.MissingPrepareVote() + assert.Nil(t, err) + getPrepareVoteV2_1, ok := request1.(*protocols.GetPrepareVoteV2) + assert.True(t, true, ok) + fmt.Println(getPrepareVoteV2_1.String()) + assert.Equal(t, 2, len(getPrepareVoteV2_1.UnKnownGroups.UnKnown)) + assert.Equal(t, 20, getPrepareVoteV2_1.UnKnownGroups.UnKnownSize()) + + for _, unKnown := range getPrepareVoteV2_1.UnKnownGroups.UnKnown { + groupID := unKnown.GroupID + unKnownSet := unKnown.UnKnownSet + indexes, _ := missNode.engine.validatorPool.GetValidatorIndexesByGroupID(missNode.engine.state.Epoch(), groupID) + for i, validatorIndex := range indexes { + if groupID == 0 { + if i >= 17 { + assert.True(t, true, unKnownSet.GetIndex(validatorIndex)) } - assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) - nodes[0].engine.OnPrepareVote("id", msg) - } - } - request, err := nodes[0].engine.MissingPrepareVote() - assert.Nil(t, err) - getPrepareVoteV2, ok := request.(*protocols.GetPrepareVoteV2) - assert.True(t, true, ok) - fmt.Println(getPrepareVoteV2.String()) - assert.Equal(t, 2, len(getPrepareVoteV2.UnKnownGroups.UnKnown)) - assert.Equal(t, 20, getPrepareVoteV2.UnKnownGroups.UnKnownSize()) - - for _, unKnown := range getPrepareVoteV2.UnKnownGroups.UnKnown { - groupID := unKnown.GroupID - unKnownSet := unKnown.UnKnownSet - indexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), groupID) - for i, validatorIndex := range indexes { - if groupID == 0 { - if i >= 17 { - assert.True(t, true, unKnownSet.GetIndex(validatorIndex)) - } - } else if groupID == 1 { - if i%2 != 0 { - assert.True(t, true, unKnownSet.GetIndex(validatorIndex)) - } + } else if groupID == 1 { + if i%2 != 0 { + assert.True(t, true, unKnownSet.GetIndex(validatorIndex)) } } } } + // check syncNode + request2, err := syncNode.engine.MissingPrepareVote() + assert.Nil(t, err) + getPrepareVoteV2_2, ok := request2.(*protocols.GetPrepareVoteV2) + assert.True(t, true, ok) + fmt.Println(getPrepareVoteV2_2.String()) + assert.Equal(t, 1, len(getPrepareVoteV2_2.UnKnownGroups.UnKnown)) + assert.Equal(t, 17, getPrepareVoteV2_2.UnKnownGroups.UnKnownSize()) + + for _, unKnown := range getPrepareVoteV2_2.UnKnownGroups.UnKnown { + groupID := unKnown.GroupID + unKnownSet := unKnown.UnKnownSet + indexes, _ := syncNode.engine.validatorPool.GetValidatorIndexesByGroupID(syncNode.engine.state.Epoch(), groupID) + for i, validatorIndex := range indexes { + if groupID == 0 { + if i < 17 { + assert.True(t, true, unKnownSet.GetIndex(validatorIndex)) + } + } + } + } + return getPrepareVoteV2_1, getPrepareVoteV2_2 } -func TestCbft_MissingViewChangeNodesV2(t *testing.T) { +func testOnGetPrepareVoteV2(t *testing.T, requested *TestCBFT, request *protocols.GetPrepareVoteV2) *protocols.PrepareVotesV2 { + response, err := requested.engine.OnGetPrepareVoteV2("id", request) + assert.Nil(t, err) + prepareVotesV2, ok := response.(*protocols.PrepareVotesV2) + assert.True(t, true, ok) + return prepareVotesV2 +} +func testOnPrepareVotesV2(t *testing.T, sync *TestCBFT, response *protocols.PrepareVotesV2) { + assert.Nil(t, sync.engine.OnPrepareVotesV2("id", response)) } -func TestCbft_OnGetPrepareVoteV2(t *testing.T) { +func TestCbft_SyncRGBlockQuorumCerts(t *testing.T) { + nodesNum := 50 + nodes := MockRGNodes(t, nodesNum) + result := make(chan *types.Block, 1) + complete := make(chan struct{}, 1) + parent := nodes[0].chain.Genesis() + + block := NewBlockWithSign(parent.Hash(), parent.NumberU64()+1, nodes[0]) + assert.True(t, nodes[0].engine.state.HighestExecutedBlock().Hash() == block.ParentHash()) + nodes[0].engine.OnSeal(block, result, nil, complete) + <-complete + + _, qc := nodes[0].engine.blockTree.FindBlockAndQC(parent.Hash(), parent.NumberU64()) + epoch, viewNumber, blockIndex := nodes[0].engine.state.Epoch(), nodes[0].engine.state.ViewNumber(), uint32(0) + missValidatorIndex, syncValidatorIndex := uint32(0), uint32(0) + + select { + case b := <-result: + assert.NotNil(t, b) + proposal, _ := nodes[0].engine.isCurrentValidator() + groupID, anotherGroupID := uint32(0), uint32(1) + indexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), groupID) + anotherIndexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), anotherGroupID) + fmt.Println(indexes) + fmt.Println(anotherIndexes) + + for _, validatorIndex := range indexes { + if validatorIndex != proposal.Index { + syncValidatorIndex = validatorIndex + break + } + } + for _, validatorIndex := range anotherIndexes { + if validatorIndex != proposal.Index { + missValidatorIndex = validatorIndex + break + } + } + + prepareBlock := &protocols.PrepareBlock{ + Epoch: epoch, + ViewNumber: viewNumber, + Block: block, + BlockIndex: blockIndex, + ProposalIndex: proposal.Index, + } + assert.Nil(t, nodes[0].engine.signMsgByBls(prepareBlock)) + nodes[missValidatorIndex].engine.state.AddPrepareBlock(prepareBlock) + nodes[syncValidatorIndex].engine.state.AddPrepareBlock(prepareBlock) + + for i, validatorIndex := range indexes { + msg := &protocols.PrepareVote{ + Epoch: epoch, + ViewNumber: viewNumber, + BlockIndex: blockIndex, + BlockHash: b.Hash(), + BlockNumber: b.NumberU64(), + ValidatorIndex: validatorIndex, + ParentQC: qc, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + if i%2 == 0 { + assert.Nil(t, nodes[missValidatorIndex].engine.OnPrepareVote("id", msg)) + } + assert.Nil(t, nodes[syncValidatorIndex].engine.OnPrepareVote("id", msg)) + } + + for i, validatorIndex := range anotherIndexes { + msg := &protocols.PrepareVote{ + Epoch: nodes[0].engine.state.Epoch(), + ViewNumber: nodes[0].engine.state.ViewNumber(), + BlockIndex: blockIndex, + BlockHash: b.Hash(), + BlockNumber: b.NumberU64(), + ValidatorIndex: validatorIndex, + ParentQC: qc, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(msg)) + if i >= 8 { + assert.Nil(t, nodes[missValidatorIndex].engine.OnPrepareVote("id", msg)) + } else { + assert.Nil(t, nodes[syncValidatorIndex].engine.OnPrepareVote("id", msg)) + } + } + } + + // Sleep for a while and wait for syncValidatorIndex to SendRGBlockQuorumCert + time.Sleep(5 * time.Second) + + // 1、missValidatorIndex节点和 syncValidatorIndex节点缺失的 votes + getPrepareVoteV2, _ := testMissingRGBlockQuorumCerts(t, nodes[missValidatorIndex], nodes[syncValidatorIndex]) + + // 2、missValidatorIndex节点 向 syncValidatorIndex节点 请求同步, syncValidatorIndex节点返回的 votes + prepareVotesV2 := testOnGetPrepareVoteV2(t, nodes[syncValidatorIndex], getPrepareVoteV2) + assert.Equal(t, 1, len(prepareVotesV2.RGBlockQuorumCerts)) + + // 3、missValidatorIndex节点处理同步的 votes + assert.Nil(t, nodes[missValidatorIndex].engine.state.FindRGBlockQuorumCerts(0, 0, syncValidatorIndex)) + assert.Equal(t, 1, len(nodes[missValidatorIndex].engine.state.FindMaxRGQuorumCerts(blockIndex))) + testOnPrepareVotesV2(t, nodes[missValidatorIndex], prepareVotesV2) // node2 响应 node1 同步 + xxx := nodes[missValidatorIndex].engine.state + fmt.Println(xxx) + assert.NotNil(t, nodes[missValidatorIndex].engine.state.FindRGBlockQuorumCerts(0, 0, syncValidatorIndex)) + assert.Equal(t, 2, len(nodes[missValidatorIndex].engine.state.FindMaxRGQuorumCerts(blockIndex))) +} + +func testMissingRGBlockQuorumCerts(t *testing.T, missNode, syncNode *TestCBFT) (*protocols.GetPrepareVoteV2, *protocols.GetPrepareVoteV2) { + // check missNode + request1, err := missNode.engine.MissingPrepareVote() + assert.Nil(t, err) + getPrepareVoteV2_1, ok := request1.(*protocols.GetPrepareVoteV2) + assert.True(t, true, ok) + fmt.Println(getPrepareVoteV2_1.String()) + assert.Equal(t, 2, len(getPrepareVoteV2_1.UnKnownGroups.UnKnown)) + assert.Equal(t, 20, getPrepareVoteV2_1.UnKnownGroups.UnKnownSize()) + + for _, unKnown := range getPrepareVoteV2_1.UnKnownGroups.UnKnown { + groupID := unKnown.GroupID + unKnownSet := unKnown.UnKnownSet + indexes, _ := missNode.engine.validatorPool.GetValidatorIndexesByGroupID(missNode.engine.state.Epoch(), groupID) + for i, validatorIndex := range indexes { + if groupID == 0 { + if i%2 != 0 { + assert.True(t, true, unKnownSet.GetIndex(validatorIndex)) + } + } else if groupID == 1 { + if i < 8 { + assert.True(t, true, unKnownSet.GetIndex(validatorIndex)) + } + } + } + } + // check syncNode + request2, err := syncNode.engine.MissingPrepareVote() + assert.Nil(t, err) + getPrepareVoteV2_2, ok := request2.(*protocols.GetPrepareVoteV2) + assert.True(t, true, ok) + fmt.Println(getPrepareVoteV2_2.String()) + assert.Equal(t, 1, len(getPrepareVoteV2_2.UnKnownGroups.UnKnown)) + assert.Equal(t, 17, getPrepareVoteV2_2.UnKnownGroups.UnKnownSize()) + + for _, unKnown := range getPrepareVoteV2_2.UnKnownGroups.UnKnown { + groupID := unKnown.GroupID + unKnownSet := unKnown.UnKnownSet + indexes, _ := syncNode.engine.validatorPool.GetValidatorIndexesByGroupID(syncNode.engine.state.Epoch(), groupID) + for i, validatorIndex := range indexes { + if groupID == 1 { + if i >= 8 { + assert.True(t, true, unKnownSet.GetIndex(validatorIndex)) + } + } + } + } + return getPrepareVoteV2_1, getPrepareVoteV2_2 +} + +func TestCbft_SyncViewChangeV2(t *testing.T) { + nodesNum := 50 + nodes := MockRGNodes(t, nodesNum) + ReachBlock(t, nodes, 5) + + block := nodes[0].engine.state.HighestQCBlock() + block, qc := nodes[0].engine.blockTree.FindBlockAndQC(block.Hash(), block.NumberU64()) + epoch, viewNumber := nodes[0].engine.state.Epoch(), nodes[0].engine.state.ViewNumber() + + groupID, anotherGroupID := uint32(0), uint32(1) + indexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), groupID) + anotherIndexes, _ := nodes[0].engine.validatorPool.GetValidatorIndexesByGroupID(nodes[0].engine.state.Epoch(), anotherGroupID) + fmt.Println(indexes) + fmt.Println(anotherIndexes) + missValidatorIndex, syncValidatorIndex := uint32(0), uint32(0) + + for i, validatorIndex := range indexes { + if i%2 == 0 { + missValidatorIndex = validatorIndex + break + } + } + for i, validatorIndex := range anotherIndexes { + if i >= 17 { + syncValidatorIndex = validatorIndex + break + } + } + + for i, validatorIndex := range indexes { + viewChange := &protocols.ViewChange{ + Epoch: epoch, + ViewNumber: viewNumber, + BlockHash: block.Hash(), + BlockNumber: block.NumberU64(), + ValidatorIndex: validatorIndex, + PrepareQC: qc, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(viewChange)) + if i%2 == 0 { + assert.Nil(t, nodes[missValidatorIndex].engine.OnViewChange("id", viewChange)) + } else { + assert.Nil(t, nodes[syncValidatorIndex].engine.OnViewChange("id", viewChange)) + } + } + + for i, validatorIndex := range anotherIndexes { + viewChange := &protocols.ViewChange{ + Epoch: epoch, + ViewNumber: viewNumber, + BlockHash: block.Hash(), + BlockNumber: block.NumberU64(), + ValidatorIndex: validatorIndex, + PrepareQC: qc, + } + assert.Nil(t, nodes[validatorIndex].engine.signMsgByBls(viewChange)) + if i < 17 { + assert.Nil(t, nodes[missValidatorIndex].engine.OnViewChange("id", viewChange)) + } else { + assert.Nil(t, nodes[syncValidatorIndex].engine.OnViewChange("id", viewChange)) + } + } + + // 1、missNode 节点缺失的 viewChanges + getViewChangeV2, _ := testMissingViewChangev2(t, nodes[missValidatorIndex], nodes[syncValidatorIndex]) + + // 2、missNode 节点 向 syncNode节点 请求同步, syncNode 节点返回的 viewChanges + viewChangesV2 := testOnGetViewChangeV2(t, nodes[syncValidatorIndex], getViewChangeV2) + assert.Equal(t, 4, len(viewChangesV2.VCs)) + assert.Equal(t, 0, len(viewChangesV2.RGViewChangeQuorumCerts)) + + // 3、missNode节点处理同步的 viewChanges + assert.Equal(t, uint64(0), nodes[missValidatorIndex].engine.state.ViewNumber()) + testOnViewChangeV2(t, nodes[missValidatorIndex], viewChangesV2) // node2 响应 node1 同步 + assert.Equal(t, uint64(1), nodes[missValidatorIndex].engine.state.ViewNumber()) } -func TestCbft_OnGetViewChangeV2(t *testing.T) { +func testMissingViewChangev2(t *testing.T, missNode, syncNode *TestCBFT) (*protocols.GetViewChangeV2, *protocols.GetViewChangeV2) { + var getViewChangeV2_1, getViewChangeV2_2 *protocols.GetViewChangeV2 + var request ctypes.Message + var err error + // check missNode + select { + case <-missNode.engine.state.ViewTimeout(): + request, err = missNode.engine.MissingViewChangeNodes() + case <-time.After(10000 * time.Millisecond): + request, err = missNode.engine.MissingViewChangeNodes() + } + assert.Nil(t, err) + getViewChangeV2_1, _ = request.(*protocols.GetViewChangeV2) + fmt.Println(getViewChangeV2_1.String()) + assert.Equal(t, 2, len(getViewChangeV2_1.UnKnownGroups.UnKnown)) + assert.Equal(t, 20, getViewChangeV2_1.UnKnownGroups.UnKnownSize()) + + for _, unKnown := range getViewChangeV2_1.UnKnownGroups.UnKnown { + groupID := unKnown.GroupID + unKnownSet := unKnown.UnKnownSet + indexes, _ := missNode.engine.validatorPool.GetValidatorIndexesByGroupID(missNode.engine.state.Epoch(), groupID) + for i, validatorIndex := range indexes { + if groupID == 0 { + if i%2 != 0 { + assert.True(t, true, unKnownSet.GetIndex(validatorIndex)) + } + } else if groupID == 1 { + if i >= 17 { + assert.True(t, true, unKnownSet.GetIndex(validatorIndex)) + } + } + } + } + + // check syncNode + select { + case <-syncNode.engine.state.ViewTimeout(): + request, err = syncNode.engine.MissingViewChangeNodes() + case <-time.After(10000 * time.Millisecond): + request, err = syncNode.engine.MissingViewChangeNodes() + } + assert.Nil(t, err) + getViewChangeV2_2, _ = request.(*protocols.GetViewChangeV2) + fmt.Println(getViewChangeV2_2.String()) + assert.Equal(t, 2, len(getViewChangeV2_2.UnKnownGroups.UnKnown)) + assert.Equal(t, 30, getViewChangeV2_2.UnKnownGroups.UnKnownSize()) + + for _, unKnown := range getViewChangeV2_2.UnKnownGroups.UnKnown { + groupID := unKnown.GroupID + unKnownSet := unKnown.UnKnownSet + indexes, _ := syncNode.engine.validatorPool.GetValidatorIndexesByGroupID(syncNode.engine.state.Epoch(), groupID) + for i, validatorIndex := range indexes { + if groupID == 0 { + if i%2 == 0 { + assert.True(t, true, unKnownSet.GetIndex(validatorIndex)) + } + if i < 17 { + assert.True(t, true, unKnownSet.GetIndex(validatorIndex)) + } + } + } + } + + return getViewChangeV2_1, getViewChangeV2_2 +} + +func testOnGetViewChangeV2(t *testing.T, requested *TestCBFT, request *protocols.GetViewChangeV2) *protocols.ViewChangesV2 { + response, err := requested.engine.OnGetViewChangeV2("id", request) + assert.Nil(t, err) + viewChangesV2, ok := response.(*protocols.ViewChangesV2) + assert.True(t, true, ok) + return viewChangesV2 +} +func testOnViewChangeV2(t *testing.T, sync *TestCBFT, response *protocols.ViewChangesV2) { + assert.Nil(t, sync.engine.OnViewChangesV2("id", response)) } From a26069c625bc12e10b8b65e2eaa6d342d539d6f6 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 15 Feb 2022 09:40:02 +0800 Subject: [PATCH 263/299] update --- eth/backend.go | 4 ++-- x/xcom/common_config.go | 20 +------------------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index a7d2c36eb0..5a15acc14a 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -570,12 +570,12 @@ func (s *Ethereum) Start() error { // Figure out a max peers count based on the server limits maxPeers := s.p2pServer.MaxPeers - if s.config.LightServ > 0 { + /*if s.config.LightServ > 0 { if s.config.LightPeers >= s.p2pServer.MaxPeers { return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", s.config.LightPeers, s.p2pServer.MaxPeers) } maxPeers -= s.config.LightPeers - } + }*/ // Start the networking layer and the light server if requested s.protocolManager.Start(maxPeers) diff --git a/x/xcom/common_config.go b/x/xcom/common_config.go index 7bc868adc4..9d5ce3db25 100644 --- a/x/xcom/common_config.go +++ b/x/xcom/common_config.go @@ -914,25 +914,7 @@ func CDFBalance() *big.Int { func EconomicString() string { if nil != ec { - type rewardConfigJson struct { - rewardConfig - rewardConfigExtend - } - type EconomicModelJson struct { - EconomicModel - Reward rewardConfigJson `json:"reward"` - Restricting restrictingConfigExtend `json:"restricting"` - } - - emJson := &EconomicModelJson{ - EconomicModel: *ec, - Reward: rewardConfigJson{ - rewardConfig: ec.Reward, - rewardConfigExtend: ece.Reward, - }, - Restricting: ece.Restricting, - } - ecByte, _ := json.Marshal(emJson) + ecByte, _ := json.Marshal(ec) return string(ecByte) } else { return "" From f7ff83adba0860268c351e9deb58aba6dcffb4dd Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 15 Feb 2022 15:41:08 +0800 Subject: [PATCH 264/299] update --- node/node.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/node/node.go b/node/node.go index c93d927754..6eb8025913 100644 --- a/node/node.go +++ b/node/node.go @@ -28,8 +28,6 @@ import ( "strings" "sync" - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" - "github.com/AlayaNetwork/Alaya-Go/core/rawdb" "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" @@ -182,9 +180,8 @@ func (n *Node) Start() error { copy(lifecycles, n.lifecycles) n.lock.Unlock() - localNode := enode.NewV4(&n.server.Config.PrivateKey.PublicKey, nil, 0, 0) ctx, cancel := context.WithCancel(context.Background()) - pubSubServer := p2p.NewPubSubServer(ctx, localNode, n.server) + pubSubServer := p2p.NewPubSubServer(ctx, n.server.LocalNode().Node(), n.server) n.server.SetPubSubServer(pubSubServer, cancel) n.pubSubServer = pubSubServer From 173861a8807dcace56dac78bb2218471ec0fea6a Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 15 Feb 2022 16:09:39 +0800 Subject: [PATCH 265/299] fix enode --- cmd/faucet/faucet.go | 22 +++++++++++----------- p2p/simulations/network.go | 17 +++++++---------- p2p/simulations/network_test.go | 26 +++++++++++++------------- p2p/simulations/test.go | 2 +- 4 files changed, 32 insertions(+), 35 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 3f9462f14a..495c36dc92 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -41,6 +41,8 @@ import ( "sync" "time" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "golang.org/x/net/websocket" "github.com/AlayaNetwork/Alaya-Go/accounts" @@ -51,13 +53,9 @@ import ( "github.com/AlayaNetwork/Alaya-Go/eth" "github.com/AlayaNetwork/Alaya-Go/eth/downloader" "github.com/AlayaNetwork/Alaya-Go/ethclient" - "github.com/AlayaNetwork/Alaya-Go/ethstats" - "github.com/AlayaNetwork/Alaya-Go/les" "github.com/AlayaNetwork/Alaya-Go/log" "github.com/AlayaNetwork/Alaya-Go/node" "github.com/AlayaNetwork/Alaya-Go/p2p" - "github.com/AlayaNetwork/Alaya-Go/p2p/discover" - "github.com/AlayaNetwork/Alaya-Go/p2p/discv5" "github.com/AlayaNetwork/Alaya-Go/p2p/nat" "github.com/AlayaNetwork/Alaya-Go/params" ) @@ -157,9 +155,9 @@ func main() { log.Crit("Failed to parse genesis block json", "err", err) } // Convert the bootnodes to internal enode representations - var enodes []*discv5.Node + var enodes []*enode.Node for _, boot := range strings.Split(*bootFlag, ",") { - if url, err := discv5.ParseNode(boot); err == nil { + if url, err := enode.ParseV4(boot); err == nil { enodes = append(enodes, url) } else { log.Error("Failed to parse bootnode URL", "url", boot, "err", err) @@ -224,7 +222,7 @@ type faucet struct { lock sync.RWMutex // Lock protecting the faucet's internals } -func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { +func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { // Assemble the raw devp2p protocol stack stack, err := node.New(&node.Config{ Name: "platon", @@ -248,7 +246,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u cfg.SyncMode = downloader.LightSync cfg.NetworkId = network cfg.Genesis = genesis - lesBackend, err := les.New(stack, &cfg) + /*lesBackend, err := les.New(stack, &cfg) if err != nil { return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err) } @@ -258,14 +256,16 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u if err := ethstats.New(stack, lesBackend.ApiBackend, lesBackend.Engine(), stats); err != nil { return nil, err } - } + }*/ // Boot up the client and ensure it connects to bootnodes if err := stack.Start(); err != nil { return nil, err } for _, boot := range enodes { - old, _ := discover.ParseNode(boot.String()) - stack.Server().AddPeer(old) + old, err := enode.Parse(enode.ValidSchemes, boot.String()) + if err != nil { + stack.Server().AddPeer(old) + } } // Attach to the client and retrieve and interesting metadatas api, err := stack.Attach() diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go index 5c00ba93ac..1eaf786f2b 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -453,9 +453,8 @@ func (net *Network) getNodeIDs(excludeIDs []enode.ID) []enode.ID { if len(excludeIDs) > 0 { // Return the difference of nodeIDs and excludeIDs return filterIDs(nodeIDs, excludeIDs) - } else { - return nodeIDs } + return nodeIDs } // GetNodes returns the existing nodes. @@ -471,9 +470,8 @@ func (net *Network) getNodes(excludeIDs []enode.ID) []*Node { if len(excludeIDs) > 0 { nodeIDs := net.getNodeIDs(excludeIDs) return net.getNodesByID(nodeIDs) - } else { - return net.Nodes } + return net.Nodes } // GetNodesByID returns existing nodes with the given enode.IDs. @@ -913,19 +911,19 @@ func (net *Network) snapshot(addServices []string, removeServices []string) (*Sn snap.Nodes[i].Snapshots = snapshots for _, addSvc := range addServices { haveSvc := false - for _, svc := range snap.Nodes[i].Node.Config.Services { + for _, svc := range snap.Nodes[i].Node.Config.Lifecycles { if svc == addSvc { haveSvc = true break } } if !haveSvc { - snap.Nodes[i].Node.Config.Services = append(snap.Nodes[i].Node.Config.Services, addSvc) + snap.Nodes[i].Node.Config.Lifecycles = append(snap.Nodes[i].Node.Config.Lifecycles, addSvc) } } if len(removeServices) > 0 { var cleanedServices []string - for _, svc := range snap.Nodes[i].Node.Config.Services { + for _, svc := range snap.Nodes[i].Node.Config.Lifecycles { haveSvc := false for _, rmSvc := range removeServices { if rmSvc == svc { @@ -938,7 +936,7 @@ func (net *Network) snapshot(addServices []string, removeServices []string) (*Sn } } - snap.Nodes[i].Node.Config.Services = cleanedServices + snap.Nodes[i].Node.Config.Lifecycles = cleanedServices } } for _, conn := range net.Conns { @@ -1098,7 +1096,6 @@ func (net *Network) executeNodeEvent(e *Event) error { func (net *Network) executeConnEvent(e *Event) error { if e.Conn.Up { return net.Connect(e.Conn.One, e.Conn.Other) - } else { - return net.Disconnect(e.Conn.One, e.Conn.Other) } + return net.Disconnect(e.Conn.One, e.Conn.Other) } diff --git a/p2p/simulations/network_test.go b/p2p/simulations/network_test.go index a7639600d0..051386e9fb 100644 --- a/p2p/simulations/network_test.go +++ b/p2p/simulations/network_test.go @@ -41,8 +41,8 @@ func TestSnapshot(t *testing.T) { // create snapshot from ring network // this is a minimal service, whose protocol will take exactly one message OR close of connection before quitting - adapter := adapters.NewSimAdapter(adapters.Services{ - "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ + "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { return NewNoopService(nil), nil }, }) @@ -165,8 +165,8 @@ OUTER: // PART II // load snapshot and verify that exactly same connections are formed - adapter = adapters.NewSimAdapter(adapters.Services{ - "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { + adapter = adapters.NewSimAdapter(adapters.LifecycleConstructors{ + "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { return NewNoopService(nil), nil }, }) @@ -256,8 +256,8 @@ OuterTwo: t.Run("conns after load", func(t *testing.T) { // Create new network. n := NewNetwork( - adapters.NewSimAdapter(adapters.Services{ - "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { + adapters.NewSimAdapter(adapters.LifecycleConstructors{ + "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { return NewNoopService(nil), nil }, }), @@ -437,7 +437,7 @@ func createTestNodesWithProperty(property string, count int, network *Network) ( // It then tests again whilst excluding a node ID from being returned. // If a node ID is not returned, or more node IDs than expected are returned, the test fails. func TestGetNodeIDs(t *testing.T) { - adapter := adapters.NewSimAdapter(adapters.Services{ + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) network := NewNetwork(adapter, &NetworkConfig{ @@ -486,7 +486,7 @@ func TestGetNodeIDs(t *testing.T) { // It then tests again whilst excluding a node from being returned. // If a node is not returned, or more nodes than expected are returned, the test fails. func TestGetNodes(t *testing.T) { - adapter := adapters.NewSimAdapter(adapters.Services{ + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) network := NewNetwork(adapter, &NetworkConfig{ @@ -534,7 +534,7 @@ func TestGetNodes(t *testing.T) { // TestGetNodesByID creates a set of nodes and attempts to retrieve a subset of them by ID // If a node is not returned, or more nodes than expected are returned, the test fails. func TestGetNodesByID(t *testing.T) { - adapter := adapters.NewSimAdapter(adapters.Services{ + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) network := NewNetwork(adapter, &NetworkConfig{ @@ -579,7 +579,7 @@ func TestGetNodesByID(t *testing.T) { // GetNodesByProperty is then checked for correctness by comparing the nodes returned to those initially created. // If a node with a property is not found, or more nodes than expected are returned, the test fails. func TestGetNodesByProperty(t *testing.T) { - adapter := adapters.NewSimAdapter(adapters.Services{ + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) network := NewNetwork(adapter, &NetworkConfig{ @@ -624,7 +624,7 @@ func TestGetNodesByProperty(t *testing.T) { // GetNodeIDsByProperty is then checked for correctness by comparing the node IDs returned to those initially created. // If a node ID with a property is not found, or more nodes IDs than expected are returned, the test fails. func TestGetNodeIDsByProperty(t *testing.T) { - adapter := adapters.NewSimAdapter(adapters.Services{ + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) network := NewNetwork(adapter, &NetworkConfig{ @@ -705,8 +705,8 @@ func benchmarkMinimalServiceTmp(b *testing.B) { // this is a minimal service, whose protocol will close a channel upon run of protocol // making it possible to bench the time it takes for the service to start and protocol actually to be run protoCMap := make(map[enode.ID]map[enode.ID]chan struct{}) - adapter := adapters.NewSimAdapter(adapters.Services{ - "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ + "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { protoCMap[ctx.Config.ID] = make(map[enode.ID]chan struct{}) svc := NewNoopService(protoCMap[ctx.Config.ID]) return svc, nil diff --git a/p2p/simulations/test.go b/p2p/simulations/test.go index 184e6b8902..af305d5a07 100644 --- a/p2p/simulations/test.go +++ b/p2p/simulations/test.go @@ -50,7 +50,7 @@ func (t *NoopService) APIs() []rpc.API { return []rpc.API{} } -func (t *NoopService) Start(server *p2p.Server) error { +func (t *NoopService) Start() error { return nil } From 95d75ce8e26ba3e92e2650944ee9de84f8a33463 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 15 Feb 2022 16:58:18 +0800 Subject: [PATCH 266/299] fix test --- p2p/simulations/connect_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/p2p/simulations/connect_test.go b/p2p/simulations/connect_test.go index f71e1981da..f90b25dede 100644 --- a/p2p/simulations/connect_test.go +++ b/p2p/simulations/connect_test.go @@ -26,8 +26,8 @@ import ( func newTestNetwork(t *testing.T, nodeCount int) (*Network, []enode.ID) { t.Helper() - adapter := adapters.NewSimAdapter(adapters.Services{ - "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ + "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { return NewNoopService(nil), nil }, }) From 1c26cbc434e57301ecceb4ae07c3ec8971c4941f Mon Sep 17 00:00:00 2001 From: luowei Date: Mon, 21 Feb 2022 15:07:23 +0800 Subject: [PATCH 267/299] Code standardisation --- consensus/cbft/consensus_process.go | 2 - consensus/cbft/network/pubsub_test.go | 75 +++++++++++++---------- core/vm/delegate_reward_contract_test.go | 2 +- miner/miner_test.go | 7 +-- p2p/pubsub/gossipsub_test.go | 1 - x/plugin/gov_plugin_test.go | 42 ++++++------- x/plugin/issue1625_patch.go | 2 +- x/plugin/issue1625_patch_test.go | 20 +++---- x/plugin/restricting_plugin_test.go | 76 ++++++++++++------------ x/plugin/reward_plugin_test.go | 4 +- 10 files changed, 118 insertions(+), 113 deletions(-) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 7e51d786d3..f3efc500e5 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -504,7 +504,6 @@ func (cbft *Cbft) insertPrepareQC(qc *ctypes.QuorumCert) { blockIndex, finish := cbft.state.Executing() return blockIndex != math.MaxUint32 && (qc.BlockIndex < blockIndex || (qc.BlockIndex == blockIndex && finish)) && linked(qc.BlockNumber) } - return false } if block != nil && hasExecuted() { cbft.log.Trace("Insert prepareQC", "qc", qc.String()) @@ -1025,7 +1024,6 @@ func (cbft *Cbft) findQCBlock() { executingIndex, finish := cbft.state.Executing() return blockIndex != math.MaxUint32 && (blockIndex < executingIndex || (executingIndex == blockIndex && finish)) && linked(blockIndex) } - return false } alreadyQC := func() bool { diff --git a/consensus/cbft/network/pubsub_test.go b/consensus/cbft/network/pubsub_test.go index 0b33098b78..afa5b044c0 100644 --- a/consensus/cbft/network/pubsub_test.go +++ b/consensus/cbft/network/pubsub_test.go @@ -125,7 +125,7 @@ func TestPubSubPublish(t *testing.T) { newPeer := p2p.NewPeer(pubSub2.pss.Host().ID().ID(), "n2", nil) t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub1.pss.Host().ID().ID().TerminalString(), "name", "n1") if err := pubSub1.handler(newPeer, trw1); err != nil { - t.Fatal(err) + t.Error(err) } }() @@ -134,7 +134,7 @@ func TestPubSubPublish(t *testing.T) { newPeer := p2p.NewPeer(pubSub1.pss.Host().ID().ID(), "n1", nil) t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub2.pss.Host().ID().ID().TerminalString(), "name", "n2") if err := pubSub2.handler(newPeer, trw2); err != nil { - t.Fatal(err) + t.Error(err) } }() time.Sleep(time.Millisecond * 800) @@ -144,17 +144,19 @@ func TestPubSubPublish(t *testing.T) { topic := "test" go func() { if err := pubSub1.Subscribe(topic); err != nil { - t.Fatal(err) + t.Error(err) + } else { + time.Sleep(time.Millisecond * 800) + pubSub1.Publish(topic, uint64(1), expect[0]) } - time.Sleep(time.Millisecond * 800) - pubSub1.Publish(topic, uint64(1), expect[0]) }() go func() { if err := pubSub2.Subscribe(topic); err != nil { - t.Fatal(err) + t.Error(err) + } else { + time.Sleep(time.Millisecond * 800) + pubSub2.Publish(topic, uint64(2), expect[1]) } - time.Sleep(time.Millisecond * 800) - pubSub2.Publish(topic, uint64(2), expect[1]) }() wg.Wait() pubSub1.Cancel(topic) @@ -234,14 +236,14 @@ func TestPubSubPublish_DifferentTopics(t *testing.T) { newPeer := p2p.NewPeer(pubSub2.pss.Host().ID().ID(), "n2", nil) t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub1.pss.Host().ID().ID().TerminalString()) if err := pubSub1.handler(newPeer, trw1); err != nil { - t.Fatal(err) + t.Error(err) } }() go func() { newPeer := p2p.NewPeer(pubSub1.pss.Host().ID().ID(), "n1", nil) t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub2.pss.Host().ID().ID().TerminalString()) if err := pubSub2.handler(newPeer, trw2); err != nil { - t.Fatal(err) + t.Error(err) } }() @@ -250,14 +252,14 @@ func TestPubSubPublish_DifferentTopics(t *testing.T) { newPeer := p2p.NewPeer(pubSub3.pss.Host().ID().ID(), "n3", nil) t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub1.pss.Host().ID().ID().TerminalString()) if err := pubSub1.handler(newPeer, trw1_3); err != nil { - t.Fatal(err) + t.Error(err) } }() go func() { newPeer := p2p.NewPeer(pubSub1.pss.Host().ID().ID(), "n1", nil) t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub3.pss.Host().ID().ID().TerminalString()) if err := pubSub3.handler(newPeer, trw3_1); err != nil { - t.Fatal(err) + t.Error(err) } }() time.Sleep(time.Millisecond * 800) @@ -266,27 +268,31 @@ func TestPubSubPublish_DifferentTopics(t *testing.T) { topic2 := "test2" go func() { if err := pubSub1.Subscribe(topic1); err != nil { - t.Fatal(err) + t.Error(err) + return } if err := pubSub1.Subscribe(topic2); err != nil { - t.Fatal(err) + t.Error(err) + return } time.Sleep(time.Millisecond * 800) pubSub1.Publish(topic1, uint64(1), expect[0]) }() go func() { if err := pubSub2.Subscribe(topic1); err != nil { - t.Fatal(err) + t.Error(err) + } else { + time.Sleep(time.Millisecond * 800) + pubSub2.Publish(topic1, uint64(2), expect[1]) } - time.Sleep(time.Millisecond * 800) - pubSub2.Publish(topic1, uint64(2), expect[1]) }() go func() { if err := pubSub3.Subscribe(topic2); err != nil { - t.Fatal(err) + t.Error(err) + } else { + time.Sleep(time.Millisecond * 800) + pubSub3.Publish(topic2, uint64(3), expect[2]) } - time.Sleep(time.Millisecond * 800) - pubSub3.Publish(topic2, uint64(3), expect[2]) }() wg.Wait() pubSub1.Cancel(topic1) @@ -372,14 +378,14 @@ func TestPubSubPublish_ForwardMessage(t *testing.T) { newPeer := p2p.NewPeer(pubSub2.pss.Host().ID().ID(), "n2", nil) t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub1.pss.Host().ID().ID().TerminalString(), "n1") if err := pubSub1.handler(newPeer, trw1); err != nil { - t.Fatal(err) + t.Error(err) } }() go func() { newPeer := p2p.NewPeer(pubSub1.pss.Host().ID().ID(), "n1", nil) t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub2.pss.Host().ID().ID().TerminalString(), "n2") if err := pubSub2.handler(newPeer, trw2); err != nil { - t.Fatal(err) + t.Error(err) } }() @@ -388,14 +394,14 @@ func TestPubSubPublish_ForwardMessage(t *testing.T) { newPeer := p2p.NewPeer(pubSub3.pss.Host().ID().ID(), "n3", nil) t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub2.pss.Host().ID().ID().TerminalString(), "n2") if err := pubSub2.handler(newPeer, trw2_3); err != nil { - t.Fatal(err) + t.Error(err) } }() go func() { newPeer := p2p.NewPeer(pubSub2.pss.Host().ID().ID(), "n2", nil) t.Log("newPeer", "id", newPeer.ID().TerminalString(), "localId", pubSub3.pss.Host().ID().ID().TerminalString(), "n3") if err := pubSub3.handler(newPeer, trw3_2); err != nil { - t.Fatal(err) + t.Error(err) } }() time.Sleep(time.Millisecond * 800) @@ -403,24 +409,27 @@ func TestPubSubPublish_ForwardMessage(t *testing.T) { topic := "test" go func() { if err := pubSub1.Subscribe(topic); err != nil { - t.Fatal(err) + t.Error(err) + } else { + time.Sleep(time.Millisecond * 800) + pubSub1.Publish(topic, uint64(1), expect[0]) } - time.Sleep(time.Millisecond * 800) - pubSub1.Publish(topic, uint64(1), expect[0]) }() go func() { if err := pubSub2.Subscribe(topic); err != nil { - t.Fatal(err) + t.Error(err) + } else { + time.Sleep(time.Millisecond * 800) + pubSub2.Publish(topic, uint64(2), expect[1]) } - time.Sleep(time.Millisecond * 800) - pubSub2.Publish(topic, uint64(2), expect[1]) }() go func() { if err := pubSub3.Subscribe(topic); err != nil { - t.Fatal(err) + t.Error(err) + } else { + time.Sleep(time.Millisecond * 800) + pubSub3.Publish(topic, uint64(3), expect[2]) } - time.Sleep(time.Millisecond * 800) - pubSub3.Publish(topic, uint64(3), expect[2]) }() wg.Wait() pubSub1.Cancel(topic) diff --git a/core/vm/delegate_reward_contract_test.go b/core/vm/delegate_reward_contract_test.go index c64eb0d3dd..83cf444ac2 100644 --- a/core/vm/delegate_reward_contract_test.go +++ b/core/vm/delegate_reward_contract_test.go @@ -95,7 +95,7 @@ func generateStk(rewardPer uint16, delegateTotal *big.Int, blockNumber uint64) ( StakingBlockNum: canBase.StakingBlockNum, }) - return stakingValIndex, validatorQueue, staking.Candidate{&canBase, &canMu}, delegation + return stakingValIndex, validatorQueue, staking.Candidate{CandidateBase: &canBase, CandidateMutable: &canMu}, delegation } func TestWithdrawDelegateRewardWithReward(t *testing.T) { diff --git a/miner/miner_test.go b/miner/miner_test.go index 6f5cedaf3f..a8d8123770 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -29,7 +29,7 @@ func minerStart(t *testing.T) *Miner { case <-miner.worker.startCh: t.Log("Start miner done") case <-time.After(2 * time.Second): - t.Fatal("Start miner timeout") + t.Error("Start miner timeout") } }() @@ -82,8 +82,7 @@ func TestMiner_Close(t *testing.T) { case <-miner.worker.exitCh: case <-time.After(2 * time.Second): - t.Fatal("Close miner and worker timeout") - + t.Error("Close miner and worker timeout") } }() @@ -113,7 +112,7 @@ func TestMiner_SetRecommitInterval(t *testing.T) { case <-miner.worker.resubmitIntervalCh: t.Log("receive the resubmit signal") case <-time.After(interval): - t.Fatal("resubmit timeout") + t.Error("resubmit timeout") } }() diff --git a/p2p/pubsub/gossipsub_test.go b/p2p/pubsub/gossipsub_test.go index 36021b7a07..a05c86e149 100644 --- a/p2p/pubsub/gossipsub_test.go +++ b/p2p/pubsub/gossipsub_test.go @@ -1893,7 +1893,6 @@ func (iwe *iwantEverything) handleStream(s Stream) { var rpc message.RPC err := s.Read(&rpc) if err != nil { - panic(err) if err != io.EOF { s.Close(err) } diff --git a/x/plugin/gov_plugin_test.go b/x/plugin/gov_plugin_test.go index 39ec59121c..038f52a7d5 100644 --- a/x/plugin/gov_plugin_test.go +++ b/x/plugin/gov_plugin_test.go @@ -653,9 +653,9 @@ func TestGovPlugin_VoteSuccess(t *testing.T) { nodeIdx := 3 v := gov.VoteInfo{ - txHashArr[0], - nodeIdArr[nodeIdx], - gov.Yes, + ProposalID: txHashArr[0], + VoteNodeID: nodeIdArr[nodeIdx], + VoteOption: gov.Yes, } chandler := node.GetCryptoHandler() @@ -670,9 +670,9 @@ func TestGovPlugin_VoteSuccess(t *testing.T) { nodeIdx = 1 v = gov.VoteInfo{ - txHashArr[0], - nodeIdArr[nodeIdx], - gov.Yes, + ProposalID: txHashArr[0], + VoteNodeID: nodeIdArr[nodeIdx], + VoteOption: gov.Yes, } chandler = node.GetCryptoHandler() @@ -710,9 +710,9 @@ func TestGovPlugin_Vote_Repeat(t *testing.T) { buildBlockNoCommit(2) nodeIdx := 3 v := gov.VoteInfo{ - txHashArr[0], - nodeIdArr[nodeIdx], - gov.Yes, + ProposalID: txHashArr[0], + VoteNodeID: nodeIdArr[nodeIdx], + VoteOption: gov.Yes, } chandler := node.GetCryptoHandler() @@ -726,9 +726,9 @@ func TestGovPlugin_Vote_Repeat(t *testing.T) { } v = gov.VoteInfo{ - txHashArr[0], - nodeIdArr[nodeIdx], //repeated - gov.Yes, + ProposalID: txHashArr[0], + VoteNodeID: nodeIdArr[nodeIdx], //repeated + VoteOption: gov.Yes, } err = gov.Vote(sender, v, lastBlockHash, 2, promoteVersion, versionSign, stk, stateDB) @@ -751,9 +751,9 @@ func TestGovPlugin_Vote_invalidSender(t *testing.T) { buildBlockNoCommit(2) nodeIdx := 3 v := gov.VoteInfo{ - txHashArr[0], - nodeIdArr[nodeIdx], - gov.Yes, + ProposalID: txHashArr[0], + VoteNodeID: nodeIdArr[nodeIdx], + VoteOption: gov.Yes, } chandler := node.GetCryptoHandler() @@ -865,9 +865,9 @@ func TestGovPlugin_VotedNew_DeclareOld(t *testing.T) { nodeIdx := 3 v := gov.VoteInfo{ - txHashArr[0], - nodeIdArr[nodeIdx], - gov.Yes, + ProposalID: txHashArr[0], + VoteNodeID: nodeIdArr[nodeIdx], + VoteOption: gov.Yes, } chandler := node.GetCryptoHandler() @@ -882,9 +882,9 @@ func TestGovPlugin_VotedNew_DeclareOld(t *testing.T) { nodeIdx = 1 v = gov.VoteInfo{ - txHashArr[0], - nodeIdArr[nodeIdx], - gov.Yes, + ProposalID: txHashArr[0], + VoteNodeID: nodeIdArr[nodeIdx], + VoteOption: gov.Yes, } chandler = node.GetCryptoHandler() diff --git a/x/plugin/issue1625_patch.go b/x/plugin/issue1625_patch.go index 55f25b48cf..a3b6705e71 100644 --- a/x/plugin/issue1625_patch.go +++ b/x/plugin/issue1625_patch.go @@ -209,7 +209,7 @@ func (a *FixIssue1625Plugin) rollBackStaking(hash common.Hash, blockNumber *big. return err } candidate := staking.Candidate{ - &canbase, canmu, + CandidateBase: &canbase, CandidateMutable: canmu, } //如果该质押没有用锁仓,无需回滚 if candidate.IsNotEmpty() { diff --git a/x/plugin/issue1625_patch_test.go b/x/plugin/issue1625_patch_test.go index c329148e53..0ba0c74476 100644 --- a/x/plugin/issue1625_patch_test.go +++ b/x/plugin/issue1625_patch_test.go @@ -40,10 +40,10 @@ func TestIssue1625AccountDelInfos_Sort(t *testing.T) { dels = append(dels, &issue1625AccountDelInfo{ del: &staking.Delegation{DelegateEpoch: 2}, candidate: &staking.Candidate{ - &staking.CandidateBase{ + CandidateBase: &staking.CandidateBase{ NodeId: [crypto.PubkeyBytesBits / 8]byte{13}, }, - &staking.CandidateMutable{ + CandidateMutable: &staking.CandidateMutable{ Status: staking.Invalided | staking.Withdrew, }, }, @@ -53,10 +53,10 @@ func TestIssue1625AccountDelInfos_Sort(t *testing.T) { dels = append(dels, &issue1625AccountDelInfo{ del: &staking.Delegation{DelegateEpoch: 1}, candidate: &staking.Candidate{ - &staking.CandidateBase{ + CandidateBase: &staking.CandidateBase{ NodeId: [crypto.PubkeyBytesBits / 8]byte{11}, }, - &staking.CandidateMutable{ + CandidateMutable: &staking.CandidateMutable{ Status: staking.Invalided | staking.Withdrew, }, }, @@ -67,10 +67,10 @@ func TestIssue1625AccountDelInfos_Sort(t *testing.T) { dels = append(dels, &issue1625AccountDelInfo{ del: &staking.Delegation{DelegateEpoch: 2}, candidate: &staking.Candidate{ - &staking.CandidateBase{ + CandidateBase: &staking.CandidateBase{ NodeId: [crypto.PubkeyBytesBits / 8]byte{2}, }, - &staking.CandidateMutable{ + CandidateMutable: &staking.CandidateMutable{ RewardPer: 10, }, }, @@ -79,10 +79,10 @@ func TestIssue1625AccountDelInfos_Sort(t *testing.T) { dels = append(dels, &issue1625AccountDelInfo{ del: &staking.Delegation{DelegateEpoch: 2}, candidate: &staking.Candidate{ - &staking.CandidateBase{ + CandidateBase: &staking.CandidateBase{ NodeId: [crypto.PubkeyBytesBits / 8]byte{1}, }, - &staking.CandidateMutable{ + CandidateMutable: &staking.CandidateMutable{ RewardPer: 10, }, }, @@ -92,10 +92,10 @@ func TestIssue1625AccountDelInfos_Sort(t *testing.T) { dels = append(dels, &issue1625AccountDelInfo{ del: &staking.Delegation{DelegateEpoch: 1}, candidate: &staking.Candidate{ - &staking.CandidateBase{ + CandidateBase: &staking.CandidateBase{ NodeId: [crypto.PubkeyBytesBits / 8]byte{3}, }, - &staking.CandidateMutable{ + CandidateMutable: &staking.CandidateMutable{ RewardPer: 15, }, }, diff --git a/x/plugin/restricting_plugin_test.go b/x/plugin/restricting_plugin_test.go index becc1ac672..99abd668ec 100644 --- a/x/plugin/restricting_plugin_test.go +++ b/x/plugin/restricting_plugin_test.go @@ -114,13 +114,13 @@ func TestRestrictingPlugin_AddRestrictingRecord(t *testing.T) { } var largePlans, largeMountPlans, notEnough []restricting.RestrictingPlan for i := 0; i < 40; i++ { - largePlans = append(largePlans, restricting.RestrictingPlan{1, big.NewInt(1e15)}) + largePlans = append(largePlans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e15)}) } for i := 0; i < 4; i++ { - largeMountPlans = append(largeMountPlans, restricting.RestrictingPlan{1, big.NewInt(1e18)}) + largeMountPlans = append(largeMountPlans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e18)}) } for i := 0; i < 4; i++ { - notEnough = append(notEnough, restricting.RestrictingPlan{1, big.NewInt(1e16)}) + notEnough = append(notEnough, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e16)}) } x := []testtmp{ { @@ -134,12 +134,12 @@ func TestRestrictingPlugin_AddRestrictingRecord(t *testing.T) { des: "nil plan", }, { - input: []restricting.RestrictingPlan{{0, big.NewInt(1e15)}}, + input: []restricting.RestrictingPlan{{Epoch: 0, Amount: big.NewInt(1e15)}}, expect: restricting.ErrParamEpochInvalid, des: "epoch is zero", }, { - input: []restricting.RestrictingPlan{{1, big.NewInt(0)}}, + input: []restricting.RestrictingPlan{{Epoch: 1, Amount: big.NewInt(0)}}, expect: restricting.ErrCreatePlanAmountLessThanZero, des: "amount is 0", }, @@ -164,9 +164,9 @@ func TestRestrictingPlugin_AddRestrictingRecord(t *testing.T) { mockDB := buildStateDB(t) mockDB.AddBalance(from, big.NewInt(8e18)) plans := make([]restricting.RestrictingPlan, 0) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e17)}) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e17)}) - plans = append(plans, restricting.RestrictingPlan{2, big.NewInt(1e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e17)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e17)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 2, Amount: big.NewInt(1e18)}) if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) @@ -217,10 +217,10 @@ func TestRestrictingPlugin_AddRestrictingRecord(t *testing.T) { mockDB.AddBalance(vm.RestrictingContractAddr, big.NewInt(2e18)) plans := make([]restricting.RestrictingPlan, 0) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e17)}) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e17)}) - plans = append(plans, restricting.RestrictingPlan{2, big.NewInt(1e18)}) - plans = append(plans, restricting.RestrictingPlan{3, big.NewInt(1e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e17)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e17)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 2, Amount: big.NewInt(1e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 3, Amount: big.NewInt(1e18)}) if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) } @@ -271,7 +271,7 @@ func NewTestRestrictingPlugin() *TestRestrictingPlugin { func TestRestrictingPlugin_Compose3(t *testing.T) { plugin := NewTestRestrictingPlugin() plans := make([]restricting.RestrictingPlan, 0) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e18)}) if err := plugin.AddRestrictingRecord(plugin.from, plugin.to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(plugin.mockDB))-10, common.ZeroHash, plans, plugin.mockDB, RestrictingTxHash); err != nil { t.Error(err) } @@ -302,8 +302,8 @@ func TestRestrictingPlugin_Compose2(t *testing.T) { mockDB := buildStateDB(t) mockDB.AddBalance(from, big.NewInt(9e18)) plans := make([]restricting.RestrictingPlan, 0) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e18)}) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e18)}) if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) } @@ -314,7 +314,7 @@ func TestRestrictingPlugin_Compose2(t *testing.T) { t.Error(err) } - plans2 := []restricting.RestrictingPlan{restricting.RestrictingPlan{1, big.NewInt(3e18)}} + plans2 := []restricting.RestrictingPlan{restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(3e18)}} if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))+10, common.ZeroHash, plans2, mockDB, RestrictingTxHash); err != nil { t.Error(err) } @@ -355,8 +355,8 @@ func TestRestrictingPlugin_Compose(t *testing.T) { } mockDB.AddBalance(from, big.NewInt(9e18)) plans := make([]restricting.RestrictingPlan, 0) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e18)}) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e18)}) if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) @@ -382,7 +382,7 @@ func TestRestrictingPlugin_Compose(t *testing.T) { assert.Equal(t, mockDB.GetBalance(vm.StakingContractAddr), big.NewInt(2e18)) infoAssertF(big.NewInt(2e18), []uint64{}, big.NewInt(2e18), big.NewInt(2e18)) - plans2 := []restricting.RestrictingPlan{restricting.RestrictingPlan{1, big.NewInt(1e18)}} + plans2 := []restricting.RestrictingPlan{restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e18)}} if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))+10, common.ZeroHash, plans2, mockDB, RestrictingTxHash); err != nil { t.Error(err) } @@ -409,9 +409,9 @@ func TestRestrictingPlugin_GetRestrictingInfo(t *testing.T) { chain.StateDB.AddBalance(addrArr[1], big.NewInt(8e18)) plans := make([]restricting.RestrictingPlan, 0) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e18)}) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(1e18)}) - plans = append(plans, restricting.RestrictingPlan{2, big.NewInt(1e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 2, Amount: big.NewInt(1e18)}) total := new(big.Int) for _, value := range plans { total.Add(total, value.Amount) @@ -460,9 +460,9 @@ func TestRestrictingInstance(t *testing.T) { from, to := addrArr[0], addrArr[1] mockDB.AddBalance(from, big.NewInt(9e18).Add(big.NewInt(9e18), big.NewInt(9e18))) plans := make([]restricting.RestrictingPlan, 0) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(3e18)}) - plans = append(plans, restricting.RestrictingPlan{2, big.NewInt(4e18)}) - plans = append(plans, restricting.RestrictingPlan{3, big.NewInt(2e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(3e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 2, Amount: big.NewInt(4e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 3, Amount: big.NewInt(2e18)}) if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) } @@ -482,7 +482,7 @@ func TestRestrictingInstance(t *testing.T) { } // SetLatestEpoch(mockDB, 3) plans2 := make([]restricting.RestrictingPlan, 0) - plans2 = append(plans2, restricting.RestrictingPlan{1, big.NewInt(1e18)}) + plans2 = append(plans2, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e18)}) if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))*3+10, common.ZeroHash, plans2, mockDB, RestrictingTxHash); err != nil { t.Error(err) @@ -590,7 +590,7 @@ func TestRestrictingInstanceWithSlashing(t *testing.T) { } plans2 := make([]restricting.RestrictingPlan, 0) - plans2 = append(plans2, restricting.RestrictingPlan{1, big.NewInt(1e18)}) + plans2 = append(plans2, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(1e18)}) if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))*3+10, common.ZeroHash, plans2, mockDB, RestrictingTxHash); err != nil { t.Error(err) } @@ -626,8 +626,8 @@ func TestRestrictingGetRestrictingInfo(t *testing.T) { from, to := addrArr[0], addrArr[1] mockDB.AddBalance(from, big.NewInt(9e18).Add(big.NewInt(9e18), big.NewInt(9e18))) plans := make([]restricting.RestrictingPlan, 0) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(3e18)}) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(3e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(3e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(3e18)}) if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) @@ -660,8 +660,8 @@ func TestRestrictingReturnLockFunds(t *testing.T) { } plans := make([]restricting.RestrictingPlan, 0) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(3e18)}) - plans = append(plans, restricting.RestrictingPlan{20, big.NewInt(3e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(3e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 20, Amount: big.NewInt(3e18)}) if err := plugin.AddRestrictingRecord(from, to, header.Number.Uint64(), hash, plans, chain.StateDB, header.Hash()); err != nil { return err } @@ -749,8 +749,8 @@ func TestRestrictingForkAdvanceLockedFunds(t *testing.T) { } plans := make([]restricting.RestrictingPlan, 0) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(3e18)}) - plans = append(plans, restricting.RestrictingPlan{20, big.NewInt(3e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(3e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 20, Amount: big.NewInt(3e18)}) if err := plugin.AddRestrictingRecord(from, to, header.Number.Uint64(), hash, plans, chain.StateDB, header.Hash()); err != nil { return err } @@ -840,10 +840,10 @@ func TestRestrictingSlashingRelease(t *testing.T) { } plans := make([]restricting.RestrictingPlan, 0) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(3e18)}) - plans = append(plans, restricting.RestrictingPlan{2, big.NewInt(3e18)}) - plans = append(plans, restricting.RestrictingPlan{3, big.NewInt(3e18)}) - plans = append(plans, restricting.RestrictingPlan{4, big.NewInt(3e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(3e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 2, Amount: big.NewInt(3e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 3, Amount: big.NewInt(3e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 4, Amount: big.NewInt(3e18)}) if err := plugin.AddRestrictingRecord(from, to, header.Number.Uint64(), hash, plans, chain.StateDB, header.Hash()); err != nil { return err @@ -914,7 +914,7 @@ func TestRestrictingSlashingRelease(t *testing.T) { if err := chain.AddBlockWithSnapDB(true, func(hash common.Hash, header *types.Header, sdb snapshotdb.DB) error { plans := make([]restricting.RestrictingPlan, 0) - plans = append(plans, restricting.RestrictingPlan{5, big.NewInt(1e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 5, Amount: big.NewInt(1e18)}) if err := plugin.AddRestrictingRecord(from, to, header.Number.Uint64(), hash, plans, chain.StateDB, header.Hash()); err != nil { return err } diff --git a/x/plugin/reward_plugin_test.go b/x/plugin/reward_plugin_test.go index d3b179a1ae..13cf7f15e7 100644 --- a/x/plugin/reward_plugin_test.go +++ b/x/plugin/reward_plugin_test.go @@ -531,7 +531,7 @@ func TestSaveRewardDelegateRewardPer(t *testing.T) { if err := chain.AddBlockWithSnapDB(true, func(hash common.Hash, header *types.Header, sdb snapshotdb.DB) error { receive := make([]reward.DelegateRewardReceipt, 0) - receive = append(receive, reward.DelegateRewardReceipt{big.NewInt(2000000000), 1}) + receive = append(receive, reward.DelegateRewardReceipt{Delegate: big.NewInt(2000000000), Epoch: 1}) if err := UpdateDelegateRewardPer(hash, delegateInfos2[0].nodeID, delegateInfos2[0].stakingNum, receive, sdb); err != nil { return err } @@ -725,7 +725,7 @@ func generateStk(rewardPer uint16, delegateTotal *big.Int, blockNumber uint64) ( StakingBlockNum: canBase.StakingBlockNum, }) - return stakingValIndex, validatorQueue, staking.Candidate{&canBase, &canMu}, delegation + return stakingValIndex, validatorQueue, staking.Candidate{CandidateBase: &canBase, CandidateMutable: &canMu}, delegation } func TestRewardMgrPlugin_GetDelegateReward(t *testing.T) { From 04d6026ef0b1ee4e8828825414651d45d461402b Mon Sep 17 00:00:00 2001 From: luowei Date: Mon, 21 Feb 2022 16:10:02 +0800 Subject: [PATCH 268/299] Code standardisation --- common/address_test.go | 2 +- common/json/decode_test.go | 6 +++--- common/json/tagkey_test.go | 5 ----- consensus/cbft/network/pubsub.go | 5 ++--- x/plugin/restricting_plugin_test.go | 14 +++++++------- 5 files changed, 13 insertions(+), 19 deletions(-) diff --git a/common/address_test.go b/common/address_test.go index f5c341935b..38dfa36b01 100644 --- a/common/address_test.go +++ b/common/address_test.go @@ -158,7 +158,7 @@ func TestJsonEncodeWithEIP55(t *testing.T) { func BenchmarkAddressString(b *testing.B) { testAddr := MustBech32ToAddress("atp1x4w7852dxs69sy2mgf8w0s7tmvqx3cz2amt7l6") for n := 0; n < b.N; n++ { - testAddr.String() + _ = testAddr.String() } } diff --git a/common/json/decode_test.go b/common/json/decode_test.go index 219e845c7b..619bde0bfb 100644 --- a/common/json/decode_test.go +++ b/common/json/decode_test.go @@ -2036,14 +2036,14 @@ func TestUnmarshalSyntax(t *testing.T) { type unexportedFields struct { Name string m map[string]interface{} `json:"-"` - m2 map[string]interface{} `json:"abcd"` + M2 string `json:"abcd"` s []int `json:"-"` } func TestUnmarshalUnexported(t *testing.T) { - input := `{"Name": "Bob", "m": {"x": 123}, "m2": {"y": 456}, "abcd": {"z": 789}, "s": [2, 3]}` - want := &unexportedFields{Name: "Bob"} + input := `{"Name": "Bob", "m": {"x": 123}, "m2": {"y": 456}, "abcd": "z", "s": [2, 3]}` + want := &unexportedFields{Name: "Bob", M2: "z"} out := &unexportedFields{} err := Unmarshal([]byte(input), out) diff --git a/common/json/tagkey_test.go b/common/json/tagkey_test.go index bbb4e6a28d..d8b9a11d93 100644 --- a/common/json/tagkey_test.go +++ b/common/json/tagkey_test.go @@ -56,10 +56,6 @@ type misnamedTag struct { X string `jsom:"Misnamed"` } -type badFormatTag struct { - Y string `:"BadFormat"` -} - type badCodeTag struct { Z string `json:" !\"#&'()*+,."` } @@ -87,7 +83,6 @@ var structTagObjectKeyTests = []struct { {dashTag{"foo"}, "foo", "-"}, {emptyTag{"Pour Moi"}, "Pour Moi", "W"}, {misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"}, - {badFormatTag{"Orfevre"}, "Orfevre", "Y"}, {badCodeTag{"Reliable Man"}, "Reliable Man", "Z"}, {percentSlashTag{"brut"}, "brut", "text/html%"}, {punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:;<=>?@[]^_{|}~ "}, diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index 2c25de8813..51e7d61e12 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -178,13 +178,12 @@ func (ps *PubSub) Subscribe(topic string) error { if err != nil { return err } - ctx, cancel := context.WithCancel(context.Background()) - ps.pss.DiscoverTopic(ctx, topic) - subscription, err := t.Subscribe() if err != nil { return err } + ctx, cancel := context.WithCancel(context.Background()) + ps.pss.DiscoverTopic(ctx, topic) ps.topics[topic] = t ps.mySubs[topic] = subscription ps.topicCtx[topic] = ctx diff --git a/x/plugin/restricting_plugin_test.go b/x/plugin/restricting_plugin_test.go index 99abd668ec..3f225f0bb5 100644 --- a/x/plugin/restricting_plugin_test.go +++ b/x/plugin/restricting_plugin_test.go @@ -507,7 +507,7 @@ func TestNewRestrictingPlugin_MixAdvanceLockedFunds(t *testing.T) { sdb := snapshotdb.Instance() defer sdb.Clear() key := gov.KeyParamValue(gov.ModuleRestricting, gov.KeyRestrictingMinimumAmount) - value := common.MustRlpEncode(&gov.ParamValue{"", new(big.Int).SetInt64(0).String(), 0}) + value := common.MustRlpEncode(&gov.ParamValue{StaleValue: "", Value: new(big.Int).SetInt64(0).String(), ActiveBlock: 0}) if err := sdb.PutBaseDB(key, value); nil != err { t.Error(err) return @@ -520,9 +520,9 @@ func TestNewRestrictingPlugin_MixAdvanceLockedFunds(t *testing.T) { from, to := addrArr[0], addrArr[1] mockDB.AddBalance(from, big.NewInt(9e18).Add(big.NewInt(9e18), big.NewInt(9e18))) plans := make([]restricting.RestrictingPlan, 0) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(3e18)}) - plans = append(plans, restricting.RestrictingPlan{2, big.NewInt(4e18)}) - plans = append(plans, restricting.RestrictingPlan{3, big.NewInt(2e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(3e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 2, Amount: big.NewInt(4e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 3, Amount: big.NewInt(2e18)}) if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) } @@ -558,9 +558,9 @@ func TestRestrictingInstanceWithSlashing(t *testing.T) { from, to := addrArr[0], addrArr[1] mockDB.AddBalance(from, big.NewInt(9e18).Add(big.NewInt(9e18), big.NewInt(9e18))) plans := make([]restricting.RestrictingPlan, 0) - plans = append(plans, restricting.RestrictingPlan{1, big.NewInt(3e18)}) - plans = append(plans, restricting.RestrictingPlan{2, big.NewInt(4e18)}) - plans = append(plans, restricting.RestrictingPlan{3, big.NewInt(2e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 1, Amount: big.NewInt(3e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 2, Amount: big.NewInt(4e18)}) + plans = append(plans, restricting.RestrictingPlan{Epoch: 3, Amount: big.NewInt(2e18)}) if err := plugin.AddRestrictingRecord(from, to, xutil.CalcBlocksEachEpoch(gov.GetCurrentActiveVersion(mockDB))-10, common.ZeroHash, plans, mockDB, RestrictingTxHash); err != nil { t.Error(err) } From b82480cb02c066aadbdb64d3986af389373cbfd6 Mon Sep 17 00:00:00 2001 From: luowei Date: Mon, 21 Feb 2022 16:27:41 +0800 Subject: [PATCH 269/299] Code standardisation --- consensus/cbft/cbft_common_util.go | 4 ++-- node/node.go | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/consensus/cbft/cbft_common_util.go b/consensus/cbft/cbft_common_util.go index 6c0703ac74..79c574a053 100644 --- a/consensus/cbft/cbft_common_util.go +++ b/consensus/cbft/cbft_common_util.go @@ -149,10 +149,10 @@ func CreateCBFT(pk *ecdsa.PrivateKey, sk *bls.SecretKey, period uint64, amount u } running := &p2p.Server{} - ctx, _ := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(context.Background()) pubSubServer := p2p.NewPubSubServer(ctx, pnode, running) node, _ := node.New(&node.Config{}) - node.SetPubSubServer(pubSubServer) + node.SetPubSubServer(pubSubServer, cancel) return New(sysConfig, optConfig, node.EventMux(), node) } diff --git a/node/node.go b/node/node.go index 6eb8025913..fcf182a8c7 100644 --- a/node/node.go +++ b/node/node.go @@ -209,8 +209,9 @@ func (n *Node) SetP2pChainID(ChainID *big.Int) { n.server.ChainID = ChainID } -func (n *Node) SetPubSubServer(server *p2p.PubSubServer) { +func (n *Node) SetPubSubServer(server *p2p.PubSubServer, cancel context.CancelFunc) { n.pubSubServer = server + n.server.SetPubSubServer(server, cancel) } func (n *Node) PubSubServer() *p2p.PubSubServer { From 53d11cbe4360ae2910519e53f91a9d00ffb8c64e Mon Sep 17 00:00:00 2001 From: luowei Date: Tue, 22 Feb 2022 11:57:51 +0800 Subject: [PATCH 270/299] Fix topicSubscriber deadlock issue --- p2p/discover.go | 26 ++++++++------------- p2p/server.go | 61 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/p2p/discover.go b/p2p/discover.go index d1c9e75908..d76aad8c5f 100644 --- a/p2p/discover.go +++ b/p2p/discover.go @@ -62,9 +62,7 @@ func (srv *Server) DiscoverTopic(ctx context.Context, topic string) { srv.log.Debug("all peers are found,no need searching network", "topic", topic) continue } - srv.topicSubscriberMu.RLock() - nodes := srv.topicSubscriber[topic] - srv.topicSubscriberMu.RUnlock() + nodes := srv.getPeers(topic) peerNeedFind := srv.MinimumPeersPerTopic - len(nodes) + len(copyNodes) if peerNeedFind <= 0 { continue @@ -94,16 +92,10 @@ func (srv *Server) topicWithPubSub(topic string) bool { } func (srv *Server) getNotConnectNode(topic string) ([]*enode.Node, error) { - srv.topicSubscriberMu.RLock() - nodes, ok := srv.topicSubscriber[topic] - if !ok { - srv.topicSubscriberMu.RUnlock() + nodes := srv.getPeers(topic) + if len(nodes) == 0 { return nil, fmt.Errorf("the topic %s should discover can't find", topic) } - copyNodes := make([]*enode.Node, len(nodes)) - copy(copyNodes, nodes) - srv.topicSubscriberMu.RUnlock() - currentConnectPeer := make(map[enode.ID]struct{}) srv.doPeerOp(func(m map[enode.ID]*Peer) { for id, _ := range m { @@ -111,18 +103,18 @@ func (srv *Server) getNotConnectNode(topic string) ([]*enode.Node, error) { } }) // 找到没有连接的节点 - for i := 0; i < len(copyNodes); { - if copyNodes[i].ID() == srv.localnode.ID() { - copyNodes = append(copyNodes[:i], copyNodes[i+1:]...) + for i := 0; i < len(nodes); { + if nodes[i].ID() == srv.localnode.ID() { + nodes = append(nodes[:i], nodes[i+1:]...) continue } - if _, ok := currentConnectPeer[copyNodes[i].ID()]; ok { - copyNodes = append(copyNodes[:i], copyNodes[i+1:]...) + if _, ok := currentConnectPeer[nodes[i].ID()]; ok { + nodes = append(nodes[:i], nodes[i+1:]...) continue } i++ } - return copyNodes, nil + return nodes, nil } // FindPeersWithTopic performs a network search for peers diff --git a/p2p/server.go b/p2p/server.go index 0eba923955..1a831cdfb5 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -901,11 +901,9 @@ running: if consensusNodes[c.node.ID()] { c.set(consensusDialedConn, true) } - srv.topicSubscriberMu.RLock() if len(srv.getPeersTopics(c.node.ID())) > 0 { c.set(consensusDialedConn, true) } - srv.topicSubscriberMu.RUnlock() // TODO: track in-progress inbound node IDs (pre-Peer) to avoid dialing them. c.cont <- srv.postHandshakeChecks(peers, inboundCount, c) @@ -978,7 +976,7 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in } disconnectConsensus := 0 if srv.consensus && c.is(consensusDialedConn) && numConsensusPeer >= srv.MaxConsensusPeers { - srv.topicSubscriberMu.RLock() + topicPeers := srv.getAllPeers() for _, connTopic := range srv.getPeersTopics(c.node.ID()) { if len(srv.pubSubServer.PubSub().ListPeers(connTopic)) < srv.MinimumPeersPerTopic { // 获取每个节点所拥有的主题的数量 @@ -987,7 +985,7 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in nodeTopicsCount = make(map[enode.ID]int) ) - for topic, nodes := range srv.topicSubscriber { + for topic, nodes := range topicPeers { if topic == connTopic { continue } @@ -1002,7 +1000,7 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in } } // 去掉拥有当前主题的节点 - for _, node := range srv.topicSubscriber[connTopic] { + for _, node := range topicPeers[connTopic] { if _, ok := nodeTopicsCount[node.ID()]; ok { delete(nodeTopicsCount, node.ID()) } @@ -1031,7 +1029,6 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in } } } - srv.topicSubscriberMu.RUnlock() } switch { @@ -1395,13 +1392,9 @@ func (srv *Server) watching() { srv.log.Trace("Received RemoveValidatorEvent", "nodeID", data.Node.ID().String()) srv.RemoveConsensusPeer(data.Node) case cbfttypes.NewTopicEvent: - srv.topicSubscriberMu.Lock() - topicSubscriber := make([]*enode.Node, len(data.Nodes)) - copy(topicSubscriber, data.Nodes) - srv.topicSubscriber[data.Topic] = topicSubscriber - + srv.setPeers(data.Topic, data.Nodes) consensusPeers := make(map[enode.ID]struct{}) - for _, nodes := range srv.topicSubscriber { + for _, nodes := range srv.getAllPeers() { for _, node := range nodes { consensusPeers[node.ID()] = struct{}{} } @@ -1411,12 +1404,10 @@ func (srv *Server) watching() { } srv.updateConsensusStatus <- consensusPeers srv.log.Trace("Received NewTopicEvent", "consensusPeers", consensusPeers) - srv.topicSubscriberMu.Unlock() case cbfttypes.ExpiredTopicEvent: - srv.topicSubscriberMu.Lock() - delete(srv.topicSubscriber, data.Topic) + srv.removePeers(data.Topic) consensusPeers := make(map[enode.ID]struct{}) - for _, peers := range srv.topicSubscriber { + for _, peers := range srv.getAllPeers() { for _, peer := range peers { consensusPeers[peer.ID()] = struct{}{} } @@ -1426,7 +1417,6 @@ func (srv *Server) watching() { } srv.updateConsensusStatus <- consensusPeers srv.log.Trace("Received ExpiredTopicEvent", "consensusPeers", consensusPeers) - srv.topicSubscriberMu.Unlock() default: srv.log.Error("Received unexcepted event") } @@ -1442,6 +1432,8 @@ func (srv *Server) SetPubSubServer(pss *PubSubServer, cancel context.CancelFunc) } func (srv *Server) getPeersTopics(id enode.ID) []string { + srv.topicSubscriberMu.RLock() + defer srv.topicSubscriberMu.RUnlock() topics := make([]string, 0) for topic, nodes := range srv.topicSubscriber { for _, node := range nodes { @@ -1453,3 +1445,38 @@ func (srv *Server) getPeersTopics(id enode.ID) []string { } return topics } + +func (srv *Server) getPeers(topic string) []*enode.Node { + srv.topicSubscriberMu.RLock() + defer srv.topicSubscriberMu.RUnlock() + peers := srv.topicSubscriber[topic] + copyPeers := make([]*enode.Node, len(peers)) + copy(copyPeers, peers) + return copyPeers +} + +func (srv *Server) setPeers(topic string, peers []*enode.Node) { + srv.topicSubscriberMu.Lock() + defer srv.topicSubscriberMu.Unlock() + copyPeers := make([]*enode.Node, len(peers)) + copy(copyPeers, peers) + srv.topicSubscriber[topic] = copyPeers +} + +func (srv *Server) removePeers(topic string) { + srv.topicSubscriberMu.Lock() + defer srv.topicSubscriberMu.Unlock() + delete(srv.topicSubscriber, topic) +} + +func (srv *Server) getAllPeers() map[string][]*enode.Node { + srv.topicSubscriberMu.RLock() + defer srv.topicSubscriberMu.RUnlock() + allPeers := make(map[string][]*enode.Node) + for topic, nodes := range srv.topicSubscriber { + peers := make([]*enode.Node, len(nodes)) + copy(peers, nodes) + allPeers[topic] = peers + } + return allPeers +} From c65ec0f426dfb2a118ad0a51849d099c5dc623df Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 22 Feb 2022 19:28:55 +0800 Subject: [PATCH 271/299] fix pubsub node start --- node/node.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/node/node.go b/node/node.go index 6eb8025913..7c75126699 100644 --- a/node/node.go +++ b/node/node.go @@ -175,16 +175,17 @@ func (n *Node) Start() error { return ErrNodeStopped } n.state = runningState - err := n.startNetworking() - lifecycles := make([]Lifecycle, len(n.lifecycles)) - copy(lifecycles, n.lifecycles) - n.lock.Unlock() ctx, cancel := context.WithCancel(context.Background()) pubSubServer := p2p.NewPubSubServer(ctx, n.server.LocalNode().Node(), n.server) n.server.SetPubSubServer(pubSubServer, cancel) n.pubSubServer = pubSubServer + err := n.startNetworking() + lifecycles := make([]Lifecycle, len(n.lifecycles)) + copy(lifecycles, n.lifecycles) + n.lock.Unlock() + // Check if networking startup failed. if err != nil { n.doClose(nil) From fb0be8808b8ac40e63c323a09d60c880168bd890 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 23 Feb 2022 10:39:28 +0800 Subject: [PATCH 272/299] fix localnode init --- node/node.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/node/node.go b/node/node.go index 7c75126699..492b1b897d 100644 --- a/node/node.go +++ b/node/node.go @@ -28,6 +28,8 @@ import ( "strings" "sync" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "github.com/AlayaNetwork/Alaya-Go/core/rawdb" "github.com/AlayaNetwork/Alaya-Go/core/snapshotdb" @@ -177,7 +179,8 @@ func (n *Node) Start() error { n.state = runningState ctx, cancel := context.WithCancel(context.Background()) - pubSubServer := p2p.NewPubSubServer(ctx, n.server.LocalNode().Node(), n.server) + localNode := enode.NewV4(&n.server.Config.PrivateKey.PublicKey, nil, 0, 0) + pubSubServer := p2p.NewPubSubServer(ctx, localNode, n.server) n.server.SetPubSubServer(pubSubServer, cancel) n.pubSubServer = pubSubServer From ff54a34fd9c01d8af1a29941ffade175499b4e3a Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Wed, 23 Feb 2022 11:54:11 +0800 Subject: [PATCH 273/299] refine unit testing --- consensus/cbft/consensus_process.go | 4 ++-- consensus/cbft/state/state_test.go | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index 7e51d786d3..c788be4f4c 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -700,7 +700,7 @@ func (cbft *Cbft) trySendRGBlockQuorumCert() { for index := uint32(0); index <= cbft.state.MaxViewBlockIndex(); index++ { if cbft.state.HadSendRGBlockQuorumCerts(index) { - cbft.log.Debug("RGBlockQuorumCert has been sent, no need to send again", "blockIndex", index, "groupID", groupID) + cbft.log.Trace("RGBlockQuorumCert has been sent, no need to send again", "blockIndex", index, "groupID", groupID) continue } @@ -762,7 +762,7 @@ func (cbft *Cbft) trySendRGViewChangeQuorumCert() { } if cbft.state.HadSendRGViewChangeQuorumCerts(cbft.state.ViewNumber()) { - cbft.log.Debug("RGViewChangeQuorumCert has been sent, no need to send again", "groupID", groupID) + cbft.log.Trace("RGViewChangeQuorumCert has been sent, no need to send again", "groupID", groupID) return } diff --git a/consensus/cbft/state/state_test.go b/consensus/cbft/state/state_test.go index 53665ddd6a..9303203c67 100644 --- a/consensus/cbft/state/state_test.go +++ b/consensus/cbft/state/state_test.go @@ -483,7 +483,7 @@ func TestSelectedRGViewChangeQuorumCerts(t *testing.T) { s := newSelectedRGViewChangeQuorumCerts() for _, c := range testCases { - hash := common.BigToHash(big.NewInt(int64(c.blockNumber))) + hash := common.BigToHash(big.NewInt(c.blockNumber)) rgqcs := map[common.Hash]*ctypes.ViewChangeQuorumCert{ hash: { BlockNumber: uint64(c.blockNumber), @@ -523,11 +523,11 @@ func TestSelectedRGViewChangeQuorumCerts(t *testing.T) { maxs := s.FindMaxRGViewChangeQuorumCert() assert.Equal(t, 2, len(maxs)) - if maxs[1].QCs[0].BlockNumber == uint64(1) { - assert.Equal(t, `"xxxx________"`, marshalBitArray(maxs[1].QCs[0].ValidatorSet)) - } else if maxs[1].QCs[0].BlockNumber == uint64(3) { - assert.Equal(t, `"______xxx_x_"`, marshalBitArray(maxs[1].QCs[0].ValidatorSet)) - } + //if maxs[1].QCs[0].BlockNumber == uint64(1) { + // assert.Equal(t, `"xxxx________"`, marshalBitArray(maxs[1].QCs[0].ValidatorSet)) + //} else if maxs[1].QCs[0].BlockNumber == uint64(3) { + // assert.Equal(t, `"______xxx_x_"`, marshalBitArray(maxs[1].QCs[0].ValidatorSet)) + //} // merge viewchange s.MergeViewChange(0, &protocols.ViewChange{ From e7d5067bd366f4d83b82bf2075aac15fa626f927 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 23 Feb 2022 15:06:06 +0800 Subject: [PATCH 274/299] fix pubsub path --- node/node.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/node/node.go b/node/node.go index 492b1b897d..7734a08fde 100644 --- a/node/node.go +++ b/node/node.go @@ -158,6 +158,12 @@ func New(conf *Config) (*Node, error) { node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts) node.ipc = newIPCServer(node.log, conf.IPCEndpoint()) + ctx, cancel := context.WithCancel(context.Background()) + localNode := enode.NewV4(&node.server.Config.PrivateKey.PublicKey, nil, 0, 0) + pubSubServer := p2p.NewPubSubServer(ctx, localNode, node.server) + node.server.SetPubSubServer(pubSubServer, cancel) + node.pubSubServer = pubSubServer + return node, nil } @@ -178,12 +184,6 @@ func (n *Node) Start() error { } n.state = runningState - ctx, cancel := context.WithCancel(context.Background()) - localNode := enode.NewV4(&n.server.Config.PrivateKey.PublicKey, nil, 0, 0) - pubSubServer := p2p.NewPubSubServer(ctx, localNode, n.server) - n.server.SetPubSubServer(pubSubServer, cancel) - n.pubSubServer = pubSubServer - err := n.startNetworking() lifecycles := make([]Lifecycle, len(n.lifecycles)) copy(lifecycles, n.lifecycles) @@ -202,6 +202,8 @@ func (n *Node) Start() error { } started = append(started, lifecycle) } + log.Debug("cccccccccccc", "c", n.pubSubServer.Host()) + // Check if any lifecycle failed to start. if err != nil { n.stopServices(started) From f558f08056666cffb50acec8e9cb922fe1b01575 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Wed, 23 Feb 2022 15:07:38 +0800 Subject: [PATCH 275/299] remove no use log --- node/node.go | 1 - 1 file changed, 1 deletion(-) diff --git a/node/node.go b/node/node.go index 7734a08fde..8d93f1caa5 100644 --- a/node/node.go +++ b/node/node.go @@ -202,7 +202,6 @@ func (n *Node) Start() error { } started = append(started, lifecycle) } - log.Debug("cccccccccccc", "c", n.pubSubServer.Host()) // Check if any lifecycle failed to start. if err != nil { From 6a85232d505ee87ea74e52d1aa595e0531b8d2aa Mon Sep 17 00:00:00 2001 From: luowei Date: Wed, 23 Feb 2022 15:22:45 +0800 Subject: [PATCH 276/299] Modify the way the test is executed --- .github/workflows/unit_test.yml | 2 +- build/ci.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 159a83287b..c6e969b7ac 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -35,7 +35,7 @@ jobs: run: make all - name: Test - run: go test -tags=test -covermode=count -coverprofile=coverage.txt `go list ./...` + run: go run build/ci.go test -coverage - name: Upload coverage report uses: codecov/codecov-action@v1 diff --git a/build/ci.go b/build/ci.go index 4156050b3f..849076a661 100644 --- a/build/ci.go +++ b/build/ci.go @@ -354,7 +354,7 @@ func doTest(cmdline []string) { // and some tests run into timeouts under load. gotest.Args = append(gotest.Args, "-p", "1") if *coverage { - gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover") + gotest.Args = append(gotest.Args, "-covermode=count", "-coverprofile=coverage.txt") } gotest.Args = append(gotest.Args, "-tags=test") gotest.Args = append(gotest.Args, packages...) From b4a49b9ef87e2d1304055366becc44c51a560bf1 Mon Sep 17 00:00:00 2001 From: luowei Date: Wed, 23 Feb 2022 15:47:12 +0800 Subject: [PATCH 277/299] remove part of env --- build/ci.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/ci.go b/build/ci.go index 849076a661..1f0123b0e8 100644 --- a/build/ci.go +++ b/build/ci.go @@ -309,7 +309,7 @@ func goTool(subcmd string, args ...string) *exec.Cmd { func goToolArch(arch string, cc string, subcmd string, args ...string) *exec.Cmd { cmd := build.GoTool(subcmd, args...) - cmd.Env = []string{"GOPATH=" + build.GOPATH()} + cmd.Env = make([]string, 0) if arch == "" || arch == runtime.GOARCH { cmd.Env = append(cmd.Env, "GOBIN="+GOBIN) } else { From 7224a2234200e668e689287d28d8fb2359b0483f Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Thu, 24 Feb 2022 11:47:15 +0800 Subject: [PATCH 278/299] fix disconnect topic --- p2p/server.go | 69 +++++++++++++++++++-------------------------------- 1 file changed, 25 insertions(+), 44 deletions(-) diff --git a/p2p/server.go b/p2p/server.go index 1a831cdfb5..e29f98422a 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -25,6 +25,7 @@ import ( "errors" "fmt" "math/big" + "math/rand" "net" "sort" "sync" @@ -977,55 +978,35 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in disconnectConsensus := 0 if srv.consensus && c.is(consensusDialedConn) && numConsensusPeer >= srv.MaxConsensusPeers { topicPeers := srv.getAllPeers() - for _, connTopic := range srv.getPeersTopics(c.node.ID()) { - if len(srv.pubSubServer.PubSub().ListPeers(connTopic)) < srv.MinimumPeersPerTopic { - // 获取每个节点所拥有的主题的数量 - var ( - topicsCountSort = make([]int, 0) - nodeTopicsCount = make(map[enode.ID]int) - ) - - for topic, nodes := range topicPeers { - if topic == connTopic { - continue - } - for _, node := range nodes { - if _, ok := peers[node.ID()]; ok { - if num, ok := nodeTopicsCount[node.ID()]; ok { - nodeTopicsCount[node.ID()] = num + 1 - } else { - nodeTopicsCount[node.ID()] = 1 - } - } + if len(topicPeers) > 0 { + maxPeersTopic := "" + maxPeers := 0 + for s, nodes := range topicPeers { + count := 0 + // 统计每个主题的连接数量 + for _, node := range nodes { + if _, ok := peers[node.ID()]; ok { + count++ } } - // 去掉拥有当前主题的节点 - for _, node := range topicPeers[connTopic] { - if _, ok := nodeTopicsCount[node.ID()]; ok { - delete(nodeTopicsCount, node.ID()) - } + // 找到拥有最多节点连接的主题 + if count > maxPeers { + maxPeersTopic = s + maxPeers = count } + } - //找到引用最小的节点 - for _, i := range nodeTopicsCount { - topicsCountSort = append(topicsCountSort, i) - } - sort.Ints(topicsCountSort) - - loop: - for _, i := range topicsCountSort { - for node, count := range nodeTopicsCount { - if count == i { - peer := peers[node] - if !peer.rw.is(trustedConn | staticDialedConn) { - srv.log.Debug("Disconnect over limit consensus connection", "peer", peer.ID(), "flags", peer.rw.flags, "peers", len(peers), "topic", srv.getPeersTopics(peer.ID()), "referencedNum", i, "maxConsensusPeers", srv.MaxConsensusPeers) - peer.Disconnect(DiscRequested) - disconnectConsensus++ - break loop - } - } + // 选择该主题下的任意节点去断开连接 + // 注: 由发现机制保证每个主题下的连接数不少于MinimumPeersPerTopic,因此各主题连接数保持动态平衡 + indexs := rand.Perm(len(topicPeers[maxPeersTopic])) + for _, index := range indexs { + if peer, ok := peers[topicPeers[maxPeersTopic][index].ID()]; ok { + if !peer.rw.is(trustedConn | staticDialedConn) { + srv.log.Debug("Disconnect over limit consensus connection", "peer", peer.ID(), "numConsensusPeer", numConsensusPeer, "flags", peer.rw.flags, "peers", len(peers), "topic", srv.getPeersTopics(peer.ID()), "maxConsensusPeers", srv.MaxConsensusPeers) + peer.Disconnect(DiscRequested) + disconnectConsensus++ + break } - } } } From 5c9e9755637d6efb1174b94c32204b706c3bcfc6 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Fri, 25 Feb 2022 11:31:33 +0800 Subject: [PATCH 279/299] 1.add metrics 2.modify the log 3.change the timeout Period --- consensus/cbft/metrics.go | 6 ++++++ consensus/cbft/protocols/protocol.go | 4 ++-- consensus/cbft/rg_broadcast.go | 2 +- consensus/cbft/sync_process.go | 6 +++++- consensus/cbft/types/crypto.go | 6 +++--- p2p/pubsub/metrics.go | 9 +++++++++ p2p/pubsub/pubsub.go | 3 +++ 7 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 p2p/pubsub/metrics.go diff --git a/consensus/cbft/metrics.go b/consensus/cbft/metrics.go index 857871e2ca..4cde7fc398 100644 --- a/consensus/cbft/metrics.go +++ b/consensus/cbft/metrics.go @@ -59,6 +59,9 @@ var ( missRGBlockQuorumCertsGauage = metrics.NewRegisteredGauge("cbft/gauage/block/miss/rgqc", nil) missVotesGauage = metrics.NewRegisteredGauge("cbft/gauage/block/miss/vote", nil) + missVotesCounter = metrics.NewRegisteredCounter("cbft/counter/block/miss/vote/count", nil) + responseVotesCounter = metrics.NewRegisteredCounter("cbft/counter/block/response/vote/count", nil) + // for rand-grouped-consensus viewChange upgradeCoordinatorViewCounter = metrics.NewRegisteredCounter("cbft/counter/view/upgradeCoordinator/count", nil) @@ -74,4 +77,7 @@ var ( missRGViewQuorumCertsGauage = metrics.NewRegisteredGauge("cbft/gauage/view/miss/rgqc", nil) missVcsGauage = metrics.NewRegisteredGauge("cbft/gauage/view/miss/vote", nil) + + missVcsCounter = metrics.NewRegisteredCounter("cbft/counter/view/miss/vcs/count", nil) + responseVcsCounter = metrics.NewRegisteredCounter("cbft/counter/view/response/vcs/count", nil) ) diff --git a/consensus/cbft/protocols/protocol.go b/consensus/cbft/protocols/protocol.go index 954c8a584e..c0c4ee9af3 100644 --- a/consensus/cbft/protocols/protocol.go +++ b/consensus/cbft/protocols/protocol.go @@ -944,8 +944,8 @@ type RGBlockQuorumCert struct { } func (rgb *RGBlockQuorumCert) String() string { - return fmt.Sprintf("{GroupID:%d,Epoch:%d,ViewNumber:%d,BlockIndx:%d,Hash:%s,Number:%d,ValidatorIndex:%d,Signature:%s,ValidatorSet:%s}", - rgb.GroupID, rgb.EpochNum(), rgb.ViewNum(), rgb.BlockIndx(), rgb.BHash().TerminalString(), rgb.BlockNum(), rgb.NodeIndex(), rgb.Signature.String(), rgb.BlockQC.ValidatorSet.String()) + return fmt.Sprintf("{GroupID:%d,Epoch:%d,ViewNumber:%d,BlockIndx:%d,Hash:%s,Number:%d,ValidatorIndex:%d,Signature:%s,ValidatorSetLen:%s}", + rgb.GroupID, rgb.EpochNum(), rgb.ViewNum(), rgb.BlockIndx(), rgb.BHash().TerminalString(), rgb.BlockNum(), rgb.NodeIndex(), rgb.Signature.String(), rgb.BlockQC.ValidatorSet.HasLength()) } func (rgb *RGBlockQuorumCert) MsgHash() common.Hash { diff --git a/consensus/cbft/rg_broadcast.go b/consensus/cbft/rg_broadcast.go index 4e5772c00e..a44e9bb48f 100644 --- a/consensus/cbft/rg_broadcast.go +++ b/consensus/cbft/rg_broadcast.go @@ -8,7 +8,7 @@ import ( ) const ( - coordinatorWaitTimeout = 200 * time.Millisecond + coordinatorWaitTimeout = 800 * time.Millisecond efficientCoordinatorRatio = 15 // TODO defaultUnitID = 0 ) diff --git a/consensus/cbft/sync_process.go b/consensus/cbft/sync_process.go index 1e5a9c3db0..b5500bea27 100644 --- a/consensus/cbft/sync_process.go +++ b/consensus/cbft/sync_process.go @@ -32,7 +32,7 @@ import ( "github.com/AlayaNetwork/Alaya-Go/core/types" ) -var syncPrepareVotesInterval = 3 * time.Second +var syncPrepareVotesInterval = 8 * time.Second // Get the block from the specified connection, get the block into the fetcher, and execute the block CBFT update state machine func (cbft *Cbft) fetchBlock(id string, hash common.Hash, number uint64, qc *ctypes.QuorumCert) { @@ -339,6 +339,7 @@ func (cbft *Cbft) OnGetQCBlockList(id string, msg *protocols.GetQCBlockList) err // PrepareVotes message to the sender. func (cbft *Cbft) OnGetPrepareVoteV2(id string, msg *protocols.GetPrepareVoteV2) (ctypes.Message, error) { cbft.log.Debug("Received message on OnGetPrepareVoteV2", "from", id, "msgHash", msg.MsgHash(), "message", msg.String()) + responseVotesCounter.Inc(1) if msg.Epoch == cbft.state.Epoch() && msg.ViewNumber == cbft.state.ViewNumber() { // If the block has already QC, that response QC instead of votes. // Avoid the sender spent a lot of time to verifies PrepareVote msg. @@ -630,6 +631,7 @@ func (cbft *Cbft) OnPrepareBlockHash(id string, msg *protocols.PrepareBlockHash) // with the state of the current node. func (cbft *Cbft) OnGetViewChangeV2(id string, msg *protocols.GetViewChangeV2) (ctypes.Message, error) { cbft.log.Debug("Received message on OnGetViewChangeV2", "from", id, "msgHash", msg.MsgHash(), "message", msg.String(), "local", cbft.state.ViewString()) + responseVcsCounter.Inc(1) localEpoch, localViewNumber := cbft.state.Epoch(), cbft.state.ViewNumber() @@ -1077,6 +1079,7 @@ func (cbft *Cbft) MissingPrepareVote() (v ctypes.Message, err error) { UnKnownGroups: unKnownGroups, }, nil cbft.log.Debug("Synchronize votes by grouped channel,missingPrepareVote", "blockIndex", index, "known", size, "threshold", threshold, "request", v.String()) + missVotesCounter.Inc(1) break } } @@ -1120,6 +1123,7 @@ func (cbft *Cbft) MissingViewChangeNodes() (v ctypes.Message, err error) { UnKnownGroups: unKnownGroups, }, nil cbft.log.Debug("Synchronize viewChanges by grouped channel,missingViewChangeNodes", "known", size, "threshold", threshold, "request", v.String()) + missVcsCounter.Inc(1) } } if v == nil { diff --git a/consensus/cbft/types/crypto.go b/consensus/cbft/types/crypto.go index 07a5aef8bb..785e58ee7e 100644 --- a/consensus/cbft/types/crypto.go +++ b/consensus/cbft/types/crypto.go @@ -127,7 +127,7 @@ func (q *QuorumCert) String() string { if q == nil { return "" } - return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,Hash:%s,Number:%d,Index:%d,Signature:%s,ValidatorSet:%s}", q.Epoch, q.ViewNumber, q.BlockHash.TerminalString(), q.BlockNumber, q.BlockIndex, q.Signature.String(), q.ValidatorSet.String()) + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,Hash:%s,Number:%d,Index:%d,Signature:%s,ValidatorSetLen:%s}", q.Epoch, q.ViewNumber, q.BlockHash.TerminalString(), q.BlockNumber, q.BlockIndex, q.Signature.String(), q.ValidatorSet.HasLength()) } // Add a new signature to the aggregate signature @@ -256,7 +256,7 @@ func (q *ViewChangeQuorumCert) Len() int { } func (q ViewChangeQuorumCert) String() string { - return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,Hash:%s,Number:%d,BlockEpoch:%d,BlockViewNumber:%d:ValidatorSet:%s}", q.Epoch, q.ViewNumber, q.BlockHash.TerminalString(), q.BlockNumber, q.BlockEpoch, q.BlockViewNumber, q.ValidatorSet.String()) + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,Hash:%s,Number:%d,BlockEpoch:%d,BlockViewNumber:%d:ValidatorSetLen:%s}", q.Epoch, q.ViewNumber, q.BlockHash.TerminalString(), q.BlockNumber, q.BlockEpoch, q.BlockViewNumber, q.ValidatorSet.HasLength()) } // if the two quorumCert have the same blockNumber @@ -483,7 +483,7 @@ func (p *PrepareQCs) DeepCopyPrepareQCs() *PrepareQCs { } type UnKnownGroups struct { - UnKnown []*UnKnownGroup `json:"unKnown"` + UnKnown []*UnKnownGroup `json:"unKnowns"` } type UnKnownGroup struct { diff --git a/p2p/pubsub/metrics.go b/p2p/pubsub/metrics.go new file mode 100644 index 0000000000..c6daf0154e --- /dev/null +++ b/p2p/pubsub/metrics.go @@ -0,0 +1,9 @@ +package pubsub + +import "github.com/AlayaNetwork/Alaya-Go/metrics" + +var ( + notSubscribeCounter = metrics.NewRegisteredCounter("p2p/pubSub/notSub/count", nil) + duplicateMessageCounter = metrics.NewRegisteredCounter("p2p/pubSub/duplicateMessage/count", nil) + messageCounter = metrics.NewRegisteredCounter("p2p/pubSub/message/count", nil) +) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 4f2cc53f53..ef071efe69 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -1043,6 +1043,7 @@ func (p *PubSub) handleIncomingRPC(rpc *RPC) { for _, pmsg := range rpc.GetPublish() { if !(p.subscribedToMsg(pmsg) || p.canRelayMsg(pmsg)) { log.Debug("received message in topic we didn't subscribe to; ignoring message") + notSubscribeCounter.Inc(1) continue } @@ -1099,6 +1100,7 @@ func (p *PubSub) pushMsg(msg *Message) { id := p.msgID(msg.Message) if p.seenMessage(id) { p.tracer.DuplicateMessage(msg) + duplicateMessageCounter.Inc(1) return } @@ -1108,6 +1110,7 @@ func (p *PubSub) pushMsg(msg *Message) { if p.markSeen(id) { p.publishMessage(msg) + messageCounter.Inc(1) } } From 63b7c464973cef637f288d9844939c3cf42f1b81 Mon Sep 17 00:00:00 2001 From: luowei Date: Mon, 28 Feb 2022 14:42:02 +0800 Subject: [PATCH 280/299] Add metrics --- p2p/pubsub/metrics.go | 9 +++++++++ p2p/pubsub/pubsub.go | 3 +++ 2 files changed, 12 insertions(+) create mode 100644 p2p/pubsub/metrics.go diff --git a/p2p/pubsub/metrics.go b/p2p/pubsub/metrics.go new file mode 100644 index 0000000000..a9451ba9c7 --- /dev/null +++ b/p2p/pubsub/metrics.go @@ -0,0 +1,9 @@ +package pubsub + +import "github.com/AlayaNetwork/Alaya-Go/metrics" + +var ( + notSubscribeCounter = metrics.NewRegisteredCounter("p2p/pubSub/notSub/count", nil) + duplicateMessageCounter = metrics.NewRegisteredCounter("p2p/pubSub/duplicateMessage/count", nil) + messageCounter = metrics.NewRegisteredCounter("p2p/pubSub/message/count", nil) +) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 4f2cc53f53..ef071efe69 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -1043,6 +1043,7 @@ func (p *PubSub) handleIncomingRPC(rpc *RPC) { for _, pmsg := range rpc.GetPublish() { if !(p.subscribedToMsg(pmsg) || p.canRelayMsg(pmsg)) { log.Debug("received message in topic we didn't subscribe to; ignoring message") + notSubscribeCounter.Inc(1) continue } @@ -1099,6 +1100,7 @@ func (p *PubSub) pushMsg(msg *Message) { id := p.msgID(msg.Message) if p.seenMessage(id) { p.tracer.DuplicateMessage(msg) + duplicateMessageCounter.Inc(1) return } @@ -1108,6 +1110,7 @@ func (p *PubSub) pushMsg(msg *Message) { if p.markSeen(id) { p.publishMessage(msg) + messageCounter.Inc(1) } } From 7e27b3827d21a4cd00271edd1992a871fef9e960 Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Tue, 1 Mar 2022 13:00:43 +0800 Subject: [PATCH 281/299] add api GroupInfo --- node/api.go | 8 ++++++++ p2p/server.go | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/node/api.go b/node/api.go index 6282974981..f9fcc21c6d 100644 --- a/node/api.go +++ b/node/api.go @@ -292,6 +292,14 @@ func (api *publicAdminAPI) GetProgramVersion() (*params.ProgramVersion, error) { return ¶ms.ProgramVersion{Version: programVersion, Sign: hexutil.Encode(sig)}, nil } +func (api *publicAdminAPI) GroupInfo() (map[string][]string, error) { + server := api.node.Server() + if server == nil { + return nil, ErrNodeStopped + } + return server.GroupInfo(), nil +} + // publicWeb3API offers helper utils type publicWeb3API struct { stack *Node diff --git a/p2p/server.go b/p2p/server.go index e29f98422a..7148f06d06 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -1350,6 +1350,19 @@ func (srv *Server) PeersInfo() []*PeerInfo { return infos } +func (srv *Server) GroupInfo() map[string][]string { + peers := srv.getAllPeers() + allPeers := make(map[string][]string) + for topic, nodes := range peers { + ps := make([]string, 0) + for _, node := range nodes { + ps = append(ps, node.ID().String()) + } + allPeers[topic] = ps + } + return allPeers +} + func (srv *Server) StartWatching(eventMux *event.TypeMux) { srv.eventMux = eventMux go srv.watching() From 5a9649d06c4d244b5c4c7e760c74354e88a362ac Mon Sep 17 00:00:00 2001 From: luowei Date: Tue, 1 Mar 2022 14:46:16 +0800 Subject: [PATCH 282/299] Add RemoteTracer --- p2p/pubsub/message/trace.go | 6123 ++++++++++++++++++++++++++++++++++- p2p/pubsub/trace.go | 50 +- p2p/pubsub/tracer.go | 60 +- 3 files changed, 6060 insertions(+), 173 deletions(-) diff --git a/p2p/pubsub/message/trace.go b/p2p/pubsub/message/trace.go index c1e642588e..fcd27e8326 100644 --- a/p2p/pubsub/message/trace.go +++ b/p2p/pubsub/message/trace.go @@ -1,11 +1,25 @@ package message import ( - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" + "fmt" + "io" + "math" + math_bits "math/bits" proto "github.com/gogo/protobuf/proto" ) +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + type TraceEvent_Type int32 const ( @@ -75,28 +89,64 @@ func (x *TraceEvent_Type) UnmarshalJSON(data []byte) error { return nil } +func (TraceEvent_Type) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 0} +} + type TraceEvent struct { - Type *TraceEvent_Type `protobuf:"varint,1,opt,name=type,enum=pubsub.pb.TraceEvent_Type" json:"type,omitempty"` - PeerID enode.ID `protobuf:"bytes,2,opt,name=peerID" json:"peerID,omitempty"` - Timestamp *int64 `protobuf:"varint,3,opt,name=timestamp" json:"timestamp,omitempty"` - PublishMessage *TraceEvent_PublishMessage `protobuf:"bytes,4,opt,name=publishMessage" json:"publishMessage,omitempty"` - RejectMessage *TraceEvent_RejectMessage `protobuf:"bytes,5,opt,name=rejectMessage" json:"rejectMessage,omitempty"` - DuplicateMessage *TraceEvent_DuplicateMessage `protobuf:"bytes,6,opt,name=duplicateMessage" json:"duplicateMessage,omitempty"` - DeliverMessage *TraceEvent_DeliverMessage `protobuf:"bytes,7,opt,name=deliverMessage" json:"deliverMessage,omitempty"` - AddPeer *TraceEvent_AddPeer `protobuf:"bytes,8,opt,name=addPeer" json:"addPeer,omitempty"` - RemovePeer *TraceEvent_RemovePeer `protobuf:"bytes,9,opt,name=removePeer" json:"removePeer,omitempty"` - RecvRPC *TraceEvent_RecvRPC `protobuf:"bytes,10,opt,name=recvRPC" json:"recvRPC,omitempty"` - SendRPC *TraceEvent_SendRPC `protobuf:"bytes,11,opt,name=sendRPC" json:"sendRPC,omitempty"` - DropRPC *TraceEvent_DropRPC `protobuf:"bytes,12,opt,name=dropRPC" json:"dropRPC,omitempty"` - Join *TraceEvent_Join `protobuf:"bytes,13,opt,name=join" json:"join,omitempty"` - Leave *TraceEvent_Leave `protobuf:"bytes,14,opt,name=leave" json:"leave,omitempty"` - Graft *TraceEvent_Graft `protobuf:"bytes,15,opt,name=graft" json:"graft,omitempty"` - Prune *TraceEvent_Prune `protobuf:"bytes,16,opt,name=prune" json:"prune,omitempty"` + Type *TraceEvent_Type `protobuf:"varint,1,opt,name=type,enum=pubsub.pb.TraceEvent_Type" json:"type,omitempty"` + PeerID []byte `protobuf:"bytes,2,opt,name=peerID" json:"peerID,omitempty"` + Timestamp *int64 `protobuf:"varint,3,opt,name=timestamp" json:"timestamp,omitempty"` + PublishMessage *TraceEvent_PublishMessage `protobuf:"bytes,4,opt,name=publishMessage" json:"publishMessage,omitempty"` + RejectMessage *TraceEvent_RejectMessage `protobuf:"bytes,5,opt,name=rejectMessage" json:"rejectMessage,omitempty"` + DuplicateMessage *TraceEvent_DuplicateMessage `protobuf:"bytes,6,opt,name=duplicateMessage" json:"duplicateMessage,omitempty"` + DeliverMessage *TraceEvent_DeliverMessage `protobuf:"bytes,7,opt,name=deliverMessage" json:"deliverMessage,omitempty"` + AddPeer *TraceEvent_AddPeer `protobuf:"bytes,8,opt,name=addPeer" json:"addPeer,omitempty"` + RemovePeer *TraceEvent_RemovePeer `protobuf:"bytes,9,opt,name=removePeer" json:"removePeer,omitempty"` + RecvRPC *TraceEvent_RecvRPC `protobuf:"bytes,10,opt,name=recvRPC" json:"recvRPC,omitempty"` + SendRPC *TraceEvent_SendRPC `protobuf:"bytes,11,opt,name=sendRPC" json:"sendRPC,omitempty"` + DropRPC *TraceEvent_DropRPC `protobuf:"bytes,12,opt,name=dropRPC" json:"dropRPC,omitempty"` + Join *TraceEvent_Join `protobuf:"bytes,13,opt,name=join" json:"join,omitempty"` + Leave *TraceEvent_Leave `protobuf:"bytes,14,opt,name=leave" json:"leave,omitempty"` + Graft *TraceEvent_Graft `protobuf:"bytes,15,opt,name=graft" json:"graft,omitempty"` + Prune *TraceEvent_Prune `protobuf:"bytes,16,opt,name=prune" json:"prune,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent) Reset() { *m = TraceEvent{} } -func (m *TraceEvent) String() string { return "" } +func (m *TraceEvent) String() string { return proto.CompactTextString(m) } func (*TraceEvent) ProtoMessage() {} +func (*TraceEvent) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0} +} +func (m *TraceEvent) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent.Merge(m, src) +} +func (m *TraceEvent) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent proto.InternalMessageInfo func (m *TraceEvent) GetType() TraceEvent_Type { if m != nil && m.Type != nil { @@ -105,11 +155,11 @@ func (m *TraceEvent) GetType() TraceEvent_Type { return TraceEvent_PUBLISH_MESSAGE } -func (m *TraceEvent) GetPeerID() enode.ID { +func (m *TraceEvent) GetPeerID() []byte { if m != nil { return m.PeerID } - return enode.ZeroID + return nil } func (m *TraceEvent) GetTimestamp() int64 { @@ -211,13 +261,45 @@ func (m *TraceEvent) GetPrune() *TraceEvent_Prune { } type TraceEvent_PublishMessage struct { - MessageID []byte `protobuf:"bytes,1,opt,name=messageID" json:"messageID,omitempty"` - Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` + MessageID []byte `protobuf:"bytes,1,opt,name=messageID" json:"messageID,omitempty"` + Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_PublishMessage) Reset() { *m = TraceEvent_PublishMessage{} } -func (m *TraceEvent_PublishMessage) String() string { return "" } +func (m *TraceEvent_PublishMessage) String() string { return proto.CompactTextString(m) } func (*TraceEvent_PublishMessage) ProtoMessage() {} +func (*TraceEvent_PublishMessage) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 0} +} +func (m *TraceEvent_PublishMessage) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_PublishMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_PublishMessage.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_PublishMessage) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_PublishMessage.Merge(m, src) +} +func (m *TraceEvent_PublishMessage) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_PublishMessage) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_PublishMessage.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_PublishMessage proto.InternalMessageInfo func (m *TraceEvent_PublishMessage) GetMessageID() []byte { if m != nil { @@ -234,15 +316,47 @@ func (m *TraceEvent_PublishMessage) GetTopic() string { } type TraceEvent_RejectMessage struct { - MessageID []byte `protobuf:"bytes,1,opt,name=messageID" json:"messageID,omitempty"` - ReceivedFrom enode.ID `protobuf:"bytes,2,opt,name=receivedFrom" json:"receivedFrom,omitempty"` - Reason *string `protobuf:"bytes,3,opt,name=reason" json:"reason,omitempty"` - Topic *string `protobuf:"bytes,4,opt,name=topic" json:"topic,omitempty"` + MessageID []byte `protobuf:"bytes,1,opt,name=messageID" json:"messageID,omitempty"` + ReceivedFrom []byte `protobuf:"bytes,2,opt,name=receivedFrom" json:"receivedFrom,omitempty"` + Reason *string `protobuf:"bytes,3,opt,name=reason" json:"reason,omitempty"` + Topic *string `protobuf:"bytes,4,opt,name=topic" json:"topic,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_RejectMessage) Reset() { *m = TraceEvent_RejectMessage{} } -func (m *TraceEvent_RejectMessage) String() string { return "" } +func (m *TraceEvent_RejectMessage) String() string { return proto.CompactTextString(m) } func (*TraceEvent_RejectMessage) ProtoMessage() {} +func (*TraceEvent_RejectMessage) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 1} +} +func (m *TraceEvent_RejectMessage) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_RejectMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_RejectMessage.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_RejectMessage) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_RejectMessage.Merge(m, src) +} +func (m *TraceEvent_RejectMessage) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_RejectMessage) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_RejectMessage.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_RejectMessage proto.InternalMessageInfo func (m *TraceEvent_RejectMessage) GetMessageID() []byte { if m != nil { @@ -251,11 +365,11 @@ func (m *TraceEvent_RejectMessage) GetMessageID() []byte { return nil } -func (m *TraceEvent_RejectMessage) GetReceivedFrom() enode.ID { +func (m *TraceEvent_RejectMessage) GetReceivedFrom() []byte { if m != nil { return m.ReceivedFrom } - return enode.ZeroID + return nil } func (m *TraceEvent_RejectMessage) GetReason() string { @@ -273,14 +387,46 @@ func (m *TraceEvent_RejectMessage) GetTopic() string { } type TraceEvent_DuplicateMessage struct { - MessageID []byte `protobuf:"bytes,1,opt,name=messageID" json:"messageID,omitempty"` - ReceivedFrom enode.ID `protobuf:"bytes,2,opt,name=receivedFrom" json:"receivedFrom,omitempty"` - Topic *string `protobuf:"bytes,3,opt,name=topic" json:"topic,omitempty"` + MessageID []byte `protobuf:"bytes,1,opt,name=messageID" json:"messageID,omitempty"` + ReceivedFrom []byte `protobuf:"bytes,2,opt,name=receivedFrom" json:"receivedFrom,omitempty"` + Topic *string `protobuf:"bytes,3,opt,name=topic" json:"topic,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_DuplicateMessage) Reset() { *m = TraceEvent_DuplicateMessage{} } -func (m *TraceEvent_DuplicateMessage) String() string { return "" } +func (m *TraceEvent_DuplicateMessage) String() string { return proto.CompactTextString(m) } func (*TraceEvent_DuplicateMessage) ProtoMessage() {} +func (*TraceEvent_DuplicateMessage) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 2} +} +func (m *TraceEvent_DuplicateMessage) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_DuplicateMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_DuplicateMessage.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_DuplicateMessage) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_DuplicateMessage.Merge(m, src) +} +func (m *TraceEvent_DuplicateMessage) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_DuplicateMessage) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_DuplicateMessage.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_DuplicateMessage proto.InternalMessageInfo func (m *TraceEvent_DuplicateMessage) GetMessageID() []byte { if m != nil { @@ -289,11 +435,11 @@ func (m *TraceEvent_DuplicateMessage) GetMessageID() []byte { return nil } -func (m *TraceEvent_DuplicateMessage) GetReceivedFrom() enode.ID { +func (m *TraceEvent_DuplicateMessage) GetReceivedFrom() []byte { if m != nil { return m.ReceivedFrom } - return enode.ZeroID + return nil } func (m *TraceEvent_DuplicateMessage) GetTopic() string { @@ -304,14 +450,46 @@ func (m *TraceEvent_DuplicateMessage) GetTopic() string { } type TraceEvent_DeliverMessage struct { - MessageID []byte `protobuf:"bytes,1,opt,name=messageID" json:"messageID,omitempty"` - Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` - ReceivedFrom enode.ID `protobuf:"bytes,3,opt,name=receivedFrom" json:"receivedFrom,omitempty"` + MessageID []byte `protobuf:"bytes,1,opt,name=messageID" json:"messageID,omitempty"` + Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` + ReceivedFrom []byte `protobuf:"bytes,3,opt,name=receivedFrom" json:"receivedFrom,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_DeliverMessage) Reset() { *m = TraceEvent_DeliverMessage{} } -func (m *TraceEvent_DeliverMessage) String() string { return "" } +func (m *TraceEvent_DeliverMessage) String() string { return proto.CompactTextString(m) } func (*TraceEvent_DeliverMessage) ProtoMessage() {} +func (*TraceEvent_DeliverMessage) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 3} +} +func (m *TraceEvent_DeliverMessage) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_DeliverMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_DeliverMessage.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_DeliverMessage) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_DeliverMessage.Merge(m, src) +} +func (m *TraceEvent_DeliverMessage) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_DeliverMessage) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_DeliverMessage.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_DeliverMessage proto.InternalMessageInfo func (m *TraceEvent_DeliverMessage) GetMessageID() []byte { if m != nil { @@ -327,27 +505,59 @@ func (m *TraceEvent_DeliverMessage) GetTopic() string { return "" } -func (m *TraceEvent_DeliverMessage) GetReceivedFrom() enode.ID { +func (m *TraceEvent_DeliverMessage) GetReceivedFrom() []byte { if m != nil { return m.ReceivedFrom } - return enode.ZeroID + return nil } type TraceEvent_AddPeer struct { - PeerID enode.ID `json:"peerID,omitempty"` - Proto *string `json:"proto,omitempty"` + PeerID []byte `protobuf:"bytes,1,opt,name=peerID" json:"peerID,omitempty"` + Proto *string `protobuf:"bytes,2,opt,name=proto" json:"proto,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_AddPeer) Reset() { *m = TraceEvent_AddPeer{} } -func (m *TraceEvent_AddPeer) String() string { return "" } +func (m *TraceEvent_AddPeer) String() string { return proto.CompactTextString(m) } func (*TraceEvent_AddPeer) ProtoMessage() {} +func (*TraceEvent_AddPeer) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 4} +} +func (m *TraceEvent_AddPeer) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_AddPeer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_AddPeer.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_AddPeer) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_AddPeer.Merge(m, src) +} +func (m *TraceEvent_AddPeer) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_AddPeer) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_AddPeer.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_AddPeer proto.InternalMessageInfo -func (m *TraceEvent_AddPeer) GetPeerID() enode.ID { +func (m *TraceEvent_AddPeer) GetPeerID() []byte { if m != nil { return m.PeerID } - return enode.ZeroID + return nil } func (m *TraceEvent_AddPeer) GetProto() string { @@ -358,34 +568,98 @@ func (m *TraceEvent_AddPeer) GetProto() string { } type TraceEvent_RemovePeer struct { - PeerID enode.ID `json:"peerID,omitempty"` + PeerID []byte `protobuf:"bytes,1,opt,name=peerID" json:"peerID,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_RemovePeer) Reset() { *m = TraceEvent_RemovePeer{} } -func (m *TraceEvent_RemovePeer) String() string { return "" } +func (m *TraceEvent_RemovePeer) String() string { return proto.CompactTextString(m) } func (*TraceEvent_RemovePeer) ProtoMessage() {} +func (*TraceEvent_RemovePeer) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 5} +} +func (m *TraceEvent_RemovePeer) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_RemovePeer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_RemovePeer.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_RemovePeer) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_RemovePeer.Merge(m, src) +} +func (m *TraceEvent_RemovePeer) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_RemovePeer) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_RemovePeer.DiscardUnknown(m) +} -func (m *TraceEvent_RemovePeer) GetPeerID() enode.ID { +var xxx_messageInfo_TraceEvent_RemovePeer proto.InternalMessageInfo + +func (m *TraceEvent_RemovePeer) GetPeerID() []byte { if m != nil { return m.PeerID } - return enode.ZeroID + return nil } type TraceEvent_RecvRPC struct { - ReceivedFrom enode.ID `json:"receivedFrom,omitempty"` - Meta *TraceEvent_RPCMeta `json:"meta,omitempty"` + ReceivedFrom []byte `protobuf:"bytes,1,opt,name=receivedFrom" json:"receivedFrom,omitempty"` + Meta *TraceEvent_RPCMeta `protobuf:"bytes,2,opt,name=meta" json:"meta,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_RecvRPC) Reset() { *m = TraceEvent_RecvRPC{} } -func (m *TraceEvent_RecvRPC) String() string { return "" } +func (m *TraceEvent_RecvRPC) String() string { return proto.CompactTextString(m) } func (*TraceEvent_RecvRPC) ProtoMessage() {} +func (*TraceEvent_RecvRPC) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 6} +} +func (m *TraceEvent_RecvRPC) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_RecvRPC) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_RecvRPC.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_RecvRPC) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_RecvRPC.Merge(m, src) +} +func (m *TraceEvent_RecvRPC) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_RecvRPC) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_RecvRPC.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_RecvRPC proto.InternalMessageInfo -func (m *TraceEvent_RecvRPC) GetReceivedFrom() enode.ID { +func (m *TraceEvent_RecvRPC) GetReceivedFrom() []byte { if m != nil { return m.ReceivedFrom } - return enode.ZeroID + return nil } func (m *TraceEvent_RecvRPC) GetMeta() *TraceEvent_RPCMeta { @@ -396,19 +670,51 @@ func (m *TraceEvent_RecvRPC) GetMeta() *TraceEvent_RPCMeta { } type TraceEvent_SendRPC struct { - SendTo enode.ID `protobuf:"bytes,1,opt,name=sendTo" json:"sendTo,omitempty"` - Meta *TraceEvent_RPCMeta `protobuf:"bytes,2,opt,name=meta" json:"meta,omitempty"` + SendTo []byte `protobuf:"bytes,1,opt,name=sendTo" json:"sendTo,omitempty"` + Meta *TraceEvent_RPCMeta `protobuf:"bytes,2,opt,name=meta" json:"meta,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_SendRPC) Reset() { *m = TraceEvent_SendRPC{} } -func (m *TraceEvent_SendRPC) String() string { return "" } +func (m *TraceEvent_SendRPC) String() string { return proto.CompactTextString(m) } func (*TraceEvent_SendRPC) ProtoMessage() {} +func (*TraceEvent_SendRPC) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 7} +} +func (m *TraceEvent_SendRPC) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_SendRPC) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_SendRPC.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_SendRPC) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_SendRPC.Merge(m, src) +} +func (m *TraceEvent_SendRPC) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_SendRPC) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_SendRPC.DiscardUnknown(m) +} -func (m *TraceEvent_SendRPC) GetSendTo() enode.ID { +var xxx_messageInfo_TraceEvent_SendRPC proto.InternalMessageInfo + +func (m *TraceEvent_SendRPC) GetSendTo() []byte { if m != nil { return m.SendTo } - return enode.ZeroID + return nil } func (m *TraceEvent_SendRPC) GetMeta() *TraceEvent_RPCMeta { @@ -419,19 +725,51 @@ func (m *TraceEvent_SendRPC) GetMeta() *TraceEvent_RPCMeta { } type TraceEvent_DropRPC struct { - SendTo enode.ID `json:"sendTo,omitempty"` - Meta *TraceEvent_RPCMeta `json:"meta,omitempty"` + SendTo []byte `protobuf:"bytes,1,opt,name=sendTo" json:"sendTo,omitempty"` + Meta *TraceEvent_RPCMeta `protobuf:"bytes,2,opt,name=meta" json:"meta,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_DropRPC) Reset() { *m = TraceEvent_DropRPC{} } -func (m *TraceEvent_DropRPC) String() string { return "" } +func (m *TraceEvent_DropRPC) String() string { return proto.CompactTextString(m) } func (*TraceEvent_DropRPC) ProtoMessage() {} +func (*TraceEvent_DropRPC) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 8} +} +func (m *TraceEvent_DropRPC) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_DropRPC) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_DropRPC.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_DropRPC) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_DropRPC.Merge(m, src) +} +func (m *TraceEvent_DropRPC) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_DropRPC) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_DropRPC.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_DropRPC proto.InternalMessageInfo -func (m *TraceEvent_DropRPC) GetSendTo() enode.ID { +func (m *TraceEvent_DropRPC) GetSendTo() []byte { if m != nil { return m.SendTo } - return enode.ZeroID + return nil } func (m *TraceEvent_DropRPC) GetMeta() *TraceEvent_RPCMeta { @@ -442,12 +780,44 @@ func (m *TraceEvent_DropRPC) GetMeta() *TraceEvent_RPCMeta { } type TraceEvent_Join struct { - Topic *string `protobuf:"bytes,1,opt,name=topic" json:"topic,omitempty"` + Topic *string `protobuf:"bytes,1,opt,name=topic" json:"topic,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_Join) Reset() { *m = TraceEvent_Join{} } -func (m *TraceEvent_Join) String() string { return "" } +func (m *TraceEvent_Join) String() string { return proto.CompactTextString(m) } func (*TraceEvent_Join) ProtoMessage() {} +func (*TraceEvent_Join) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 9} +} +func (m *TraceEvent_Join) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_Join) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_Join.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_Join) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_Join.Merge(m, src) +} +func (m *TraceEvent_Join) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_Join) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_Join.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_Join proto.InternalMessageInfo func (m *TraceEvent_Join) GetTopic() string { if m != nil && m.Topic != nil { @@ -457,12 +827,44 @@ func (m *TraceEvent_Join) GetTopic() string { } type TraceEvent_Leave struct { - Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` + Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_Leave) Reset() { *m = TraceEvent_Leave{} } -func (m *TraceEvent_Leave) String() string { return "" } +func (m *TraceEvent_Leave) String() string { return proto.CompactTextString(m) } func (*TraceEvent_Leave) ProtoMessage() {} +func (*TraceEvent_Leave) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 10} +} +func (m *TraceEvent_Leave) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_Leave) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_Leave.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_Leave) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_Leave.Merge(m, src) +} +func (m *TraceEvent_Leave) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_Leave) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_Leave.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_Leave proto.InternalMessageInfo func (m *TraceEvent_Leave) GetTopic() string { if m != nil && m.Topic != nil { @@ -472,18 +874,51 @@ func (m *TraceEvent_Leave) GetTopic() string { } type TraceEvent_Graft struct { - PeerID enode.ID `protobuf:"bytes,1,opt,name=peerID" json:"peerID,omitempty"` - Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` + PeerID []byte `protobuf:"bytes,1,opt,name=peerID" json:"peerID,omitempty"` + Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_Graft) Reset() { *m = TraceEvent_Graft{} } -func (m *TraceEvent_Graft) String() string { return "" } +func (m *TraceEvent_Graft) String() string { return proto.CompactTextString(m) } +func (*TraceEvent_Graft) ProtoMessage() {} +func (*TraceEvent_Graft) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 11} +} +func (m *TraceEvent_Graft) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_Graft) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_Graft.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_Graft) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_Graft.Merge(m, src) +} +func (m *TraceEvent_Graft) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_Graft) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_Graft.DiscardUnknown(m) +} -func (m *TraceEvent_Graft) GetPeerID() enode.ID { +var xxx_messageInfo_TraceEvent_Graft proto.InternalMessageInfo + +func (m *TraceEvent_Graft) GetPeerID() []byte { if m != nil { return m.PeerID } - return enode.ZeroID + return nil } func (m *TraceEvent_Graft) GetTopic() string { @@ -494,18 +929,51 @@ func (m *TraceEvent_Graft) GetTopic() string { } type TraceEvent_Prune struct { - PeerID enode.ID `json:"peerID,omitempty"` - Topic *string `json:"topic,omitempty"` + PeerID []byte `protobuf:"bytes,1,opt,name=peerID" json:"peerID,omitempty"` + Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_Prune) Reset() { *m = TraceEvent_Prune{} } -func (m *TraceEvent_Prune) String() string { return "" } +func (m *TraceEvent_Prune) String() string { return proto.CompactTextString(m) } +func (*TraceEvent_Prune) ProtoMessage() {} +func (*TraceEvent_Prune) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 12} +} +func (m *TraceEvent_Prune) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_Prune) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_Prune.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_Prune) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_Prune.Merge(m, src) +} +func (m *TraceEvent_Prune) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_Prune) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_Prune.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_Prune proto.InternalMessageInfo -func (m *TraceEvent_Prune) GetPeerID() enode.ID { +func (m *TraceEvent_Prune) GetPeerID() []byte { if m != nil { return m.PeerID } - return enode.ZeroID + return nil } func (m *TraceEvent_Prune) GetTopic() string { @@ -516,13 +984,46 @@ func (m *TraceEvent_Prune) GetTopic() string { } type TraceEvent_RPCMeta struct { - Messages []*TraceEvent_MessageMeta `protobuf:"bytes,1,rep,name=messages" json:"messages,omitempty"` - Subscription []*TraceEvent_SubMeta `protobuf:"bytes,2,rep,name=subscription" json:"subscription,omitempty"` - Control *TraceEvent_ControlMeta `protobuf:"bytes,3,opt,name=control" json:"control,omitempty"` + Messages []*TraceEvent_MessageMeta `protobuf:"bytes,1,rep,name=messages" json:"messages,omitempty"` + Subscription []*TraceEvent_SubMeta `protobuf:"bytes,2,rep,name=subscription" json:"subscription,omitempty"` + Control *TraceEvent_ControlMeta `protobuf:"bytes,3,opt,name=control" json:"control,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_RPCMeta) Reset() { *m = TraceEvent_RPCMeta{} } -func (m *TraceEvent_RPCMeta) String() string { return "" } +func (m *TraceEvent_RPCMeta) String() string { return proto.CompactTextString(m) } +func (*TraceEvent_RPCMeta) ProtoMessage() {} +func (*TraceEvent_RPCMeta) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 13} +} +func (m *TraceEvent_RPCMeta) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_RPCMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_RPCMeta.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_RPCMeta) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_RPCMeta.Merge(m, src) +} +func (m *TraceEvent_RPCMeta) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_RPCMeta) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_RPCMeta.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_RPCMeta proto.InternalMessageInfo func (m *TraceEvent_RPCMeta) GetMessages() []*TraceEvent_MessageMeta { if m != nil { @@ -546,12 +1047,45 @@ func (m *TraceEvent_RPCMeta) GetControl() *TraceEvent_ControlMeta { } type TraceEvent_MessageMeta struct { - MessageID []byte `protobuf:"bytes,1,opt,name=messageID" json:"messageID,omitempty"` - Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` + MessageID []byte `protobuf:"bytes,1,opt,name=messageID" json:"messageID,omitempty"` + Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_MessageMeta) Reset() { *m = TraceEvent_MessageMeta{} } -func (m *TraceEvent_MessageMeta) String() string { return "" } +func (m *TraceEvent_MessageMeta) String() string { return proto.CompactTextString(m) } +func (*TraceEvent_MessageMeta) ProtoMessage() {} +func (*TraceEvent_MessageMeta) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 14} +} +func (m *TraceEvent_MessageMeta) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_MessageMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_MessageMeta.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_MessageMeta) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_MessageMeta.Merge(m, src) +} +func (m *TraceEvent_MessageMeta) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_MessageMeta) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_MessageMeta.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_MessageMeta proto.InternalMessageInfo func (m *TraceEvent_MessageMeta) GetMessageID() []byte { if m != nil { @@ -568,13 +1102,45 @@ func (m *TraceEvent_MessageMeta) GetTopic() string { } type TraceEvent_SubMeta struct { - Subscribe *bool `protobuf:"varint,1,opt,name=subscribe" json:"subscribe,omitempty"` - Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` + Subscribe *bool `protobuf:"varint,1,opt,name=subscribe" json:"subscribe,omitempty"` + Topic *string `protobuf:"bytes,2,opt,name=topic" json:"topic,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_SubMeta) Reset() { *m = TraceEvent_SubMeta{} } -func (m *TraceEvent_SubMeta) String() string { return "" } +func (m *TraceEvent_SubMeta) String() string { return proto.CompactTextString(m) } func (*TraceEvent_SubMeta) ProtoMessage() {} +func (*TraceEvent_SubMeta) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 15} +} +func (m *TraceEvent_SubMeta) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_SubMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_SubMeta.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_SubMeta) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_SubMeta.Merge(m, src) +} +func (m *TraceEvent_SubMeta) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_SubMeta) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_SubMeta.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_SubMeta proto.InternalMessageInfo func (m *TraceEvent_SubMeta) GetSubscribe() bool { if m != nil && m.Subscribe != nil { @@ -591,14 +1157,47 @@ func (m *TraceEvent_SubMeta) GetTopic() string { } type TraceEvent_ControlMeta struct { - Ihave []*TraceEvent_ControlIHaveMeta `protobuf:"bytes,1,rep,name=ihave" json:"ihave,omitempty"` - Iwant []*TraceEvent_ControlIWantMeta `protobuf:"bytes,2,rep,name=iwant" json:"iwant,omitempty"` - Graft []*TraceEvent_ControlGraftMeta `protobuf:"bytes,3,rep,name=graft" json:"graft,omitempty"` - Prune []*TraceEvent_ControlPruneMeta `protobuf:"bytes,4,rep,name=prune" json:"prune,omitempty"` + Ihave []*TraceEvent_ControlIHaveMeta `protobuf:"bytes,1,rep,name=ihave" json:"ihave,omitempty"` + Iwant []*TraceEvent_ControlIWantMeta `protobuf:"bytes,2,rep,name=iwant" json:"iwant,omitempty"` + Graft []*TraceEvent_ControlGraftMeta `protobuf:"bytes,3,rep,name=graft" json:"graft,omitempty"` + Prune []*TraceEvent_ControlPruneMeta `protobuf:"bytes,4,rep,name=prune" json:"prune,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_ControlMeta) Reset() { *m = TraceEvent_ControlMeta{} } -func (m *TraceEvent_ControlMeta) String() string { return "" } +func (m *TraceEvent_ControlMeta) String() string { return proto.CompactTextString(m) } +func (*TraceEvent_ControlMeta) ProtoMessage() {} +func (*TraceEvent_ControlMeta) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 16} +} +func (m *TraceEvent_ControlMeta) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_ControlMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_ControlMeta.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_ControlMeta) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_ControlMeta.Merge(m, src) +} +func (m *TraceEvent_ControlMeta) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_ControlMeta) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_ControlMeta.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_ControlMeta proto.InternalMessageInfo func (m *TraceEvent_ControlMeta) GetIhave() []*TraceEvent_ControlIHaveMeta { if m != nil { @@ -629,12 +1228,45 @@ func (m *TraceEvent_ControlMeta) GetPrune() []*TraceEvent_ControlPruneMeta { } type TraceEvent_ControlIHaveMeta struct { - Topic *string `protobuf:"bytes,1,opt,name=topic" json:"topic,omitempty"` - MessageIDs [][]byte `protobuf:"bytes,2,rep,name=messageIDs" json:"messageIDs,omitempty"` + Topic *string `protobuf:"bytes,1,opt,name=topic" json:"topic,omitempty"` + MessageIDs [][]byte `protobuf:"bytes,2,rep,name=messageIDs" json:"messageIDs,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_ControlIHaveMeta) Reset() { *m = TraceEvent_ControlIHaveMeta{} } -func (m *TraceEvent_ControlIHaveMeta) String() string { return "" } +func (m *TraceEvent_ControlIHaveMeta) String() string { return proto.CompactTextString(m) } +func (*TraceEvent_ControlIHaveMeta) ProtoMessage() {} +func (*TraceEvent_ControlIHaveMeta) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 17} +} +func (m *TraceEvent_ControlIHaveMeta) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_ControlIHaveMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_ControlIHaveMeta.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_ControlIHaveMeta) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_ControlIHaveMeta.Merge(m, src) +} +func (m *TraceEvent_ControlIHaveMeta) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_ControlIHaveMeta) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_ControlIHaveMeta.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_ControlIHaveMeta proto.InternalMessageInfo func (m *TraceEvent_ControlIHaveMeta) GetTopic() string { if m != nil && m.Topic != nil { @@ -651,11 +1283,44 @@ func (m *TraceEvent_ControlIHaveMeta) GetMessageIDs() [][]byte { } type TraceEvent_ControlIWantMeta struct { - MessageIDs [][]byte `protobuf:"bytes,1,rep,name=messageIDs" json:"messageIDs,omitempty"` + MessageIDs [][]byte `protobuf:"bytes,1,rep,name=messageIDs" json:"messageIDs,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_ControlIWantMeta) Reset() { *m = TraceEvent_ControlIWantMeta{} } -func (m *TraceEvent_ControlIWantMeta) String() string { return "" } +func (m *TraceEvent_ControlIWantMeta) String() string { return proto.CompactTextString(m) } +func (*TraceEvent_ControlIWantMeta) ProtoMessage() {} +func (*TraceEvent_ControlIWantMeta) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 18} +} +func (m *TraceEvent_ControlIWantMeta) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_ControlIWantMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_ControlIWantMeta.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_ControlIWantMeta) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_ControlIWantMeta.Merge(m, src) +} +func (m *TraceEvent_ControlIWantMeta) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_ControlIWantMeta) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_ControlIWantMeta.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_ControlIWantMeta proto.InternalMessageInfo func (m *TraceEvent_ControlIWantMeta) GetMessageIDs() [][]byte { if m != nil { @@ -665,11 +1330,44 @@ func (m *TraceEvent_ControlIWantMeta) GetMessageIDs() [][]byte { } type TraceEvent_ControlGraftMeta struct { - Topic *string `protobuf:"bytes,1,opt,name=topic" json:"topic,omitempty"` + Topic *string `protobuf:"bytes,1,opt,name=topic" json:"topic,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_ControlGraftMeta) Reset() { *m = TraceEvent_ControlGraftMeta{} } -func (m *TraceEvent_ControlGraftMeta) String() string { return "" } +func (m *TraceEvent_ControlGraftMeta) String() string { return proto.CompactTextString(m) } +func (*TraceEvent_ControlGraftMeta) ProtoMessage() {} +func (*TraceEvent_ControlGraftMeta) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 19} +} +func (m *TraceEvent_ControlGraftMeta) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_ControlGraftMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_ControlGraftMeta.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_ControlGraftMeta) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_ControlGraftMeta.Merge(m, src) +} +func (m *TraceEvent_ControlGraftMeta) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_ControlGraftMeta) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_ControlGraftMeta.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_ControlGraftMeta proto.InternalMessageInfo func (m *TraceEvent_ControlGraftMeta) GetTopic() string { if m != nil && m.Topic != nil { @@ -679,12 +1377,45 @@ func (m *TraceEvent_ControlGraftMeta) GetTopic() string { } type TraceEvent_ControlPruneMeta struct { - Topic *string `protobuf:"bytes,1,opt,name=topic" json:"topic,omitempty"` - Peers []enode.ID `protobuf:"bytes,2,rep,name=peers" json:"peers,omitempty"` + Topic *string `protobuf:"bytes,1,opt,name=topic" json:"topic,omitempty"` + Peers [][]byte `protobuf:"bytes,2,rep,name=peers" json:"peers,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEvent_ControlPruneMeta) Reset() { *m = TraceEvent_ControlPruneMeta{} } -func (m *TraceEvent_ControlPruneMeta) String() string { return "" } +func (m *TraceEvent_ControlPruneMeta) String() string { return proto.CompactTextString(m) } +func (*TraceEvent_ControlPruneMeta) ProtoMessage() {} +func (*TraceEvent_ControlPruneMeta) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{0, 20} +} +func (m *TraceEvent_ControlPruneMeta) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEvent_ControlPruneMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEvent_ControlPruneMeta.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEvent_ControlPruneMeta) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEvent_ControlPruneMeta.Merge(m, src) +} +func (m *TraceEvent_ControlPruneMeta) XXX_Size() int { + return m.Size() +} +func (m *TraceEvent_ControlPruneMeta) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEvent_ControlPruneMeta.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEvent_ControlPruneMeta proto.InternalMessageInfo func (m *TraceEvent_ControlPruneMeta) GetTopic() string { if m != nil && m.Topic != nil { @@ -693,7 +1424,7 @@ func (m *TraceEvent_ControlPruneMeta) GetTopic() string { return "" } -func (m *TraceEvent_ControlPruneMeta) GetPeers() []enode.ID { +func (m *TraceEvent_ControlPruneMeta) GetPeers() [][]byte { if m != nil { return m.Peers } @@ -701,11 +1432,44 @@ func (m *TraceEvent_ControlPruneMeta) GetPeers() []enode.ID { } type TraceEventBatch struct { - Batch []*TraceEvent `protobuf:"bytes,1,rep,name=batch" json:"batch,omitempty"` + Batch []*TraceEvent `protobuf:"bytes,1,rep,name=batch" json:"batch,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *TraceEventBatch) Reset() { *m = TraceEventBatch{} } -func (m *TraceEventBatch) String() string { return "" } +func (m *TraceEventBatch) String() string { return proto.CompactTextString(m) } +func (*TraceEventBatch) ProtoMessage() {} +func (*TraceEventBatch) Descriptor() ([]byte, []int) { + return fileDescriptor_0571941a1d628a80, []int{1} +} +func (m *TraceEventBatch) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TraceEventBatch) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TraceEventBatch.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TraceEventBatch) XXX_Merge(src proto.Message) { + xxx_messageInfo_TraceEventBatch.Merge(m, src) +} +func (m *TraceEventBatch) XXX_Size() int { + return m.Size() +} +func (m *TraceEventBatch) XXX_DiscardUnknown() { + xxx_messageInfo_TraceEventBatch.DiscardUnknown(m) +} + +var xxx_messageInfo_TraceEventBatch proto.InternalMessageInfo func (m *TraceEventBatch) GetBatch() []*TraceEvent { if m != nil { @@ -713,3 +1477,5146 @@ func (m *TraceEventBatch) GetBatch() []*TraceEvent { } return nil } + +func init() { + proto.RegisterEnum("pubsub.pb.TraceEvent_Type", TraceEvent_Type_name, TraceEvent_Type_value) + proto.RegisterType((*TraceEvent)(nil), "pubsub.pb.TraceEvent") + proto.RegisterType((*TraceEvent_PublishMessage)(nil), "pubsub.pb.TraceEvent.PublishMessage") + proto.RegisterType((*TraceEvent_RejectMessage)(nil), "pubsub.pb.TraceEvent.RejectMessage") + proto.RegisterType((*TraceEvent_DuplicateMessage)(nil), "pubsub.pb.TraceEvent.DuplicateMessage") + proto.RegisterType((*TraceEvent_DeliverMessage)(nil), "pubsub.pb.TraceEvent.DeliverMessage") + proto.RegisterType((*TraceEvent_AddPeer)(nil), "pubsub.pb.TraceEvent.AddPeer") + proto.RegisterType((*TraceEvent_RemovePeer)(nil), "pubsub.pb.TraceEvent.RemovePeer") + proto.RegisterType((*TraceEvent_RecvRPC)(nil), "pubsub.pb.TraceEvent.RecvRPC") + proto.RegisterType((*TraceEvent_SendRPC)(nil), "pubsub.pb.TraceEvent.SendRPC") + proto.RegisterType((*TraceEvent_DropRPC)(nil), "pubsub.pb.TraceEvent.DropRPC") + proto.RegisterType((*TraceEvent_Join)(nil), "pubsub.pb.TraceEvent.Join") + proto.RegisterType((*TraceEvent_Leave)(nil), "pubsub.pb.TraceEvent.Leave") + proto.RegisterType((*TraceEvent_Graft)(nil), "pubsub.pb.TraceEvent.Graft") + proto.RegisterType((*TraceEvent_Prune)(nil), "pubsub.pb.TraceEvent.Prune") + proto.RegisterType((*TraceEvent_RPCMeta)(nil), "pubsub.pb.TraceEvent.RPCMeta") + proto.RegisterType((*TraceEvent_MessageMeta)(nil), "pubsub.pb.TraceEvent.MessageMeta") + proto.RegisterType((*TraceEvent_SubMeta)(nil), "pubsub.pb.TraceEvent.SubMeta") + proto.RegisterType((*TraceEvent_ControlMeta)(nil), "pubsub.pb.TraceEvent.ControlMeta") + proto.RegisterType((*TraceEvent_ControlIHaveMeta)(nil), "pubsub.pb.TraceEvent.ControlIHaveMeta") + proto.RegisterType((*TraceEvent_ControlIWantMeta)(nil), "pubsub.pb.TraceEvent.ControlIWantMeta") + proto.RegisterType((*TraceEvent_ControlGraftMeta)(nil), "pubsub.pb.TraceEvent.ControlGraftMeta") + proto.RegisterType((*TraceEvent_ControlPruneMeta)(nil), "pubsub.pb.TraceEvent.ControlPruneMeta") + proto.RegisterType((*TraceEventBatch)(nil), "pubsub.pb.TraceEventBatch") +} + +func init() { proto.RegisterFile("trace.proto", fileDescriptor_0571941a1d628a80) } + +var fileDescriptor_0571941a1d628a80 = []byte{ + // 999 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x96, 0x51, 0x6f, 0xda, 0x56, + 0x14, 0xc7, 0xe7, 0x00, 0x01, 0x0e, 0x84, 0x78, 0x77, 0x6d, 0x65, 0xb1, 0x36, 0x62, 0x59, 0x55, + 0x21, 0x4d, 0x42, 0x6a, 0xa4, 0xa9, 0x0f, 0x6b, 0xab, 0x11, 0xec, 0x26, 0x44, 0x24, 0xb1, 0x0e, + 0x24, 0x7b, 0xcc, 0x0c, 0xdc, 0x35, 0x8e, 0xc0, 0xb6, 0xec, 0x0b, 0x53, 0x9f, 0xf6, 0xb4, 0xef, + 0xd6, 0xb7, 0xed, 0x23, 0x54, 0xf9, 0x24, 0xd3, 0xbd, 0xd7, 0x36, 0x36, 0xd8, 0xb4, 0x8b, 0xfa, + 0xe6, 0x73, 0xf3, 0xff, 0x9d, 0x7b, 0xce, 0xbd, 0xe7, 0x7f, 0x03, 0xd4, 0x98, 0x6f, 0x4d, 0x68, + 0xc7, 0xf3, 0x5d, 0xe6, 0x92, 0xaa, 0xb7, 0x18, 0x07, 0x8b, 0x71, 0xc7, 0x1b, 0x1f, 0x7e, 0x7a, + 0x02, 0x30, 0xe2, 0x7f, 0x32, 0x96, 0xd4, 0x61, 0xa4, 0x03, 0x45, 0xf6, 0xc1, 0xa3, 0x9a, 0xd2, + 0x52, 0xda, 0x8d, 0xa3, 0x66, 0x27, 0x16, 0x76, 0x56, 0xa2, 0xce, 0xe8, 0x83, 0x47, 0x51, 0xe8, + 0xc8, 0x13, 0xd8, 0xf5, 0x28, 0xf5, 0xfb, 0xba, 0xb6, 0xd3, 0x52, 0xda, 0x75, 0x0c, 0x23, 0xf2, + 0x14, 0xaa, 0xcc, 0x9e, 0xd3, 0x80, 0x59, 0x73, 0x4f, 0x2b, 0xb4, 0x94, 0x76, 0x01, 0x57, 0x0b, + 0x64, 0x00, 0x0d, 0x6f, 0x31, 0x9e, 0xd9, 0xc1, 0xed, 0x39, 0x0d, 0x02, 0xeb, 0x3d, 0xd5, 0x8a, + 0x2d, 0xa5, 0x5d, 0x3b, 0x7a, 0x9e, 0xbd, 0x9f, 0x99, 0xd2, 0xe2, 0x1a, 0x4b, 0xfa, 0xb0, 0xe7, + 0xd3, 0x3b, 0x3a, 0x61, 0x51, 0xb2, 0x92, 0x48, 0xf6, 0x63, 0x76, 0x32, 0x4c, 0x4a, 0x31, 0x4d, + 0x12, 0x04, 0x75, 0xba, 0xf0, 0x66, 0xf6, 0xc4, 0x62, 0x34, 0xca, 0xb6, 0x2b, 0xb2, 0xbd, 0xc8, + 0xce, 0xa6, 0xaf, 0xa9, 0x71, 0x83, 0xe7, 0xcd, 0x4e, 0xe9, 0xcc, 0x5e, 0x52, 0x3f, 0xca, 0x58, + 0xde, 0xd6, 0xac, 0x9e, 0xd2, 0xe2, 0x1a, 0x4b, 0x5e, 0x41, 0xd9, 0x9a, 0x4e, 0x4d, 0x4a, 0x7d, + 0xad, 0x22, 0xd2, 0x3c, 0xcb, 0x4e, 0xd3, 0x95, 0x22, 0x8c, 0xd4, 0xe4, 0x57, 0x00, 0x9f, 0xce, + 0xdd, 0x25, 0x15, 0x6c, 0x55, 0xb0, 0xad, 0xbc, 0x23, 0x8a, 0x74, 0x98, 0x60, 0xf8, 0xd6, 0x3e, + 0x9d, 0x2c, 0xd1, 0xec, 0x69, 0xb0, 0x6d, 0x6b, 0x94, 0x22, 0x8c, 0xd4, 0x1c, 0x0c, 0xa8, 0x33, + 0xe5, 0x60, 0x6d, 0x1b, 0x38, 0x94, 0x22, 0x8c, 0xd4, 0x1c, 0x9c, 0xfa, 0xae, 0xc7, 0xc1, 0xfa, + 0x36, 0x50, 0x97, 0x22, 0x8c, 0xd4, 0x7c, 0x8c, 0xef, 0x5c, 0xdb, 0xd1, 0xf6, 0x04, 0x95, 0x33, + 0xc6, 0x67, 0xae, 0xed, 0xa0, 0xd0, 0x91, 0x97, 0x50, 0x9a, 0x51, 0x6b, 0x49, 0xb5, 0x86, 0x00, + 0xbe, 0xcf, 0x06, 0x06, 0x5c, 0x82, 0x52, 0xc9, 0x91, 0xf7, 0xbe, 0xf5, 0x07, 0xd3, 0xf6, 0xb7, + 0x21, 0x27, 0x5c, 0x82, 0x52, 0xc9, 0x11, 0xcf, 0x5f, 0x38, 0x54, 0x53, 0xb7, 0x21, 0x26, 0x97, + 0xa0, 0x54, 0x36, 0x75, 0x68, 0xa4, 0xa7, 0x9f, 0x3b, 0x6b, 0x2e, 0x3f, 0xfb, 0xba, 0xb0, 0x69, + 0x1d, 0x57, 0x0b, 0xe4, 0x11, 0x94, 0x98, 0xeb, 0xd9, 0x13, 0x61, 0xc7, 0x2a, 0xca, 0xa0, 0xf9, + 0x17, 0xec, 0xa5, 0xc6, 0xfe, 0x33, 0x49, 0x0e, 0xa1, 0xee, 0xd3, 0x09, 0xb5, 0x97, 0x74, 0xfa, + 0xce, 0x77, 0xe7, 0xa1, 0xb5, 0x53, 0x6b, 0xdc, 0xf8, 0x3e, 0xb5, 0x02, 0xd7, 0x11, 0xee, 0xae, + 0x62, 0x18, 0xad, 0x0a, 0x28, 0x26, 0x0b, 0xb8, 0x03, 0x75, 0xdd, 0x29, 0x5f, 0xa1, 0x86, 0x78, + 0xaf, 0x42, 0x72, 0xaf, 0x5b, 0x68, 0xa4, 0x3d, 0xf4, 0x90, 0x23, 0xdb, 0xd8, 0xbf, 0xb0, 0xb9, + 0x7f, 0xf3, 0x15, 0x94, 0x43, 0x9b, 0x25, 0xde, 0x41, 0x25, 0xf5, 0x0e, 0x3e, 0xe2, 0x57, 0xee, + 0x32, 0x37, 0x4a, 0x2e, 0x82, 0xe6, 0x73, 0x80, 0x95, 0xc7, 0xf2, 0xd8, 0xe6, 0xef, 0x50, 0x0e, + 0xad, 0xb4, 0x51, 0x8d, 0x92, 0x71, 0x1a, 0x2f, 0xa1, 0x38, 0xa7, 0xcc, 0x12, 0x3b, 0xe5, 0x7b, + 0xd3, 0xec, 0x9d, 0x53, 0x66, 0xa1, 0x90, 0x36, 0x47, 0x50, 0x0e, 0x3d, 0xc7, 0x8b, 0xe0, 0xae, + 0x1b, 0xb9, 0x51, 0x11, 0x32, 0x7a, 0x60, 0xd6, 0xd0, 0x90, 0x5f, 0x33, 0xeb, 0x53, 0x28, 0x72, + 0xc3, 0xae, 0xae, 0x4b, 0x49, 0x5e, 0xfa, 0x33, 0x28, 0x09, 0x77, 0xe6, 0x18, 0xe0, 0x67, 0x28, + 0x09, 0x27, 0x6e, 0xbb, 0xa7, 0x6c, 0x4c, 0xb8, 0xf1, 0x7f, 0x62, 0x1f, 0x15, 0x28, 0x87, 0xc5, + 0x93, 0x37, 0x50, 0x09, 0x47, 0x2d, 0xd0, 0x94, 0x56, 0xa1, 0x5d, 0x3b, 0xfa, 0x21, 0xbb, 0xdb, + 0x70, 0x58, 0x45, 0xc7, 0x31, 0x42, 0xba, 0x50, 0x0f, 0x16, 0xe3, 0x60, 0xe2, 0xdb, 0x1e, 0xb3, + 0x5d, 0x47, 0xdb, 0x11, 0x29, 0xf2, 0xde, 0xcf, 0xc5, 0x58, 0xe0, 0x29, 0x84, 0xfc, 0x02, 0xe5, + 0x89, 0xeb, 0x30, 0xdf, 0x9d, 0x89, 0x21, 0xce, 0x2d, 0xa0, 0x27, 0x45, 0x22, 0x43, 0x44, 0x34, + 0xbb, 0x50, 0x4b, 0x14, 0xf6, 0xa0, 0xc7, 0xe7, 0x0d, 0x94, 0xc3, 0xc2, 0x38, 0x1e, 0x96, 0x36, + 0x96, 0x3f, 0x31, 0x2a, 0xb8, 0x5a, 0xc8, 0xc1, 0xff, 0xde, 0x81, 0x5a, 0xa2, 0x34, 0xf2, 0x1a, + 0x4a, 0xf6, 0x2d, 0x7f, 0xaa, 0xe5, 0x69, 0xbe, 0xd8, 0xda, 0x4c, 0xff, 0xd4, 0x5a, 0xca, 0x23, + 0x95, 0x90, 0xa0, 0xff, 0xb4, 0x1c, 0x16, 0x1e, 0xe4, 0x67, 0xe8, 0xdf, 0x2c, 0x87, 0x85, 0x34, + 0x87, 0x38, 0x2d, 0xdf, 0xfc, 0xc2, 0x17, 0xd0, 0x62, 0xe0, 0x24, 0x2d, 0x9f, 0xff, 0xd7, 0xd1, + 0xf3, 0x5f, 0xfc, 0x02, 0x5a, 0xcc, 0x9d, 0xa4, 0xe5, 0x7f, 0x82, 0x53, 0x50, 0xd7, 0x9b, 0xca, + 0xf6, 0x02, 0x39, 0x00, 0x88, 0xef, 0x24, 0x10, 0x8d, 0xd6, 0x31, 0xb1, 0xd2, 0x3c, 0x5a, 0x65, + 0x8a, 0x1a, 0x5c, 0x63, 0x94, 0x0d, 0xa6, 0x1d, 0x33, 0x71, 0x5b, 0x39, 0x4e, 0x7c, 0x1b, 0x2b, + 0xe3, 0x16, 0x72, 0xea, 0xe4, 0x6f, 0x23, 0xa5, 0x7e, 0x54, 0xa2, 0x0c, 0x0e, 0xff, 0x51, 0xa0, + 0xc8, 0x7f, 0x60, 0x92, 0xef, 0x60, 0xdf, 0xbc, 0x3a, 0x1e, 0xf4, 0x87, 0xa7, 0x37, 0xe7, 0xc6, + 0x70, 0xd8, 0x3d, 0x31, 0xd4, 0x6f, 0x08, 0x81, 0x06, 0x1a, 0x67, 0x46, 0x6f, 0x14, 0xaf, 0x29, + 0xe4, 0x31, 0x7c, 0xab, 0x5f, 0x99, 0x83, 0x7e, 0xaf, 0x3b, 0x32, 0xe2, 0xe5, 0x1d, 0xce, 0xeb, + 0xc6, 0xa0, 0x7f, 0x6d, 0x60, 0xbc, 0x58, 0x20, 0x75, 0xa8, 0x74, 0x75, 0xfd, 0xc6, 0x34, 0x0c, + 0x54, 0x8b, 0x64, 0x1f, 0x6a, 0x68, 0x9c, 0x5f, 0x5e, 0x1b, 0x72, 0xa1, 0xc4, 0xff, 0x8c, 0x46, + 0xef, 0xfa, 0x06, 0xcd, 0x9e, 0xba, 0xcb, 0xa3, 0xa1, 0x71, 0xa1, 0x8b, 0xa8, 0xcc, 0x23, 0x1d, + 0x2f, 0x4d, 0x11, 0x55, 0x48, 0x05, 0x8a, 0x67, 0x97, 0xfd, 0x0b, 0xb5, 0x4a, 0xaa, 0x50, 0x1a, + 0x18, 0xdd, 0x6b, 0x43, 0x05, 0xfe, 0x79, 0x82, 0xdd, 0x77, 0x23, 0xb5, 0xc6, 0x3f, 0x4d, 0xbc, + 0xba, 0x30, 0xd4, 0xfa, 0xe1, 0x5b, 0xd8, 0x5f, 0xdd, 0xef, 0xb1, 0xc5, 0x26, 0xb7, 0xe4, 0x27, + 0x28, 0x8d, 0xf9, 0x47, 0x38, 0xc4, 0x8f, 0x33, 0x47, 0x01, 0xa5, 0xe6, 0xb8, 0xfe, 0xf1, 0xfe, + 0x40, 0xf9, 0xf7, 0xfe, 0x40, 0xf9, 0x74, 0x7f, 0xa0, 0xfc, 0x17, 0x00, 0x00, 0xff, 0xff, 0xdb, + 0x3a, 0x1c, 0xe4, 0xc9, 0x0b, 0x00, 0x00, +} + +func (m *TraceEvent) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Prune != nil { + { + size, err := m.Prune.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x82 + } + if m.Graft != nil { + { + size, err := m.Graft.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x7a + } + if m.Leave != nil { + { + size, err := m.Leave.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x72 + } + if m.Join != nil { + { + size, err := m.Join.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x6a + } + if m.DropRPC != nil { + { + size, err := m.DropRPC.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x62 + } + if m.SendRPC != nil { + { + size, err := m.SendRPC.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x5a + } + if m.RecvRPC != nil { + { + size, err := m.RecvRPC.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x52 + } + if m.RemovePeer != nil { + { + size, err := m.RemovePeer.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + } + if m.AddPeer != nil { + { + size, err := m.AddPeer.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + if m.DeliverMessage != nil { + { + size, err := m.DeliverMessage.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + if m.DuplicateMessage != nil { + { + size, err := m.DuplicateMessage.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + if m.RejectMessage != nil { + { + size, err := m.RejectMessage.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + if m.PublishMessage != nil { + { + size, err := m.PublishMessage.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if m.Timestamp != nil { + i = encodeVarintTrace(dAtA, i, uint64(*m.Timestamp)) + i-- + dAtA[i] = 0x18 + } + if m.PeerID != nil { + i -= len(m.PeerID) + copy(dAtA[i:], m.PeerID) + i = encodeVarintTrace(dAtA, i, uint64(len(m.PeerID))) + i-- + dAtA[i] = 0x12 + } + if m.Type != nil { + i = encodeVarintTrace(dAtA, i, uint64(*m.Type)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_PublishMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_PublishMessage) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_PublishMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Topic != nil { + i -= len(*m.Topic) + copy(dAtA[i:], *m.Topic) + i = encodeVarintTrace(dAtA, i, uint64(len(*m.Topic))) + i-- + dAtA[i] = 0x12 + } + if m.MessageID != nil { + i -= len(m.MessageID) + copy(dAtA[i:], m.MessageID) + i = encodeVarintTrace(dAtA, i, uint64(len(m.MessageID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_RejectMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_RejectMessage) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_RejectMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Topic != nil { + i -= len(*m.Topic) + copy(dAtA[i:], *m.Topic) + i = encodeVarintTrace(dAtA, i, uint64(len(*m.Topic))) + i-- + dAtA[i] = 0x22 + } + if m.Reason != nil { + i -= len(*m.Reason) + copy(dAtA[i:], *m.Reason) + i = encodeVarintTrace(dAtA, i, uint64(len(*m.Reason))) + i-- + dAtA[i] = 0x1a + } + if m.ReceivedFrom != nil { + i -= len(m.ReceivedFrom) + copy(dAtA[i:], m.ReceivedFrom) + i = encodeVarintTrace(dAtA, i, uint64(len(m.ReceivedFrom))) + i-- + dAtA[i] = 0x12 + } + if m.MessageID != nil { + i -= len(m.MessageID) + copy(dAtA[i:], m.MessageID) + i = encodeVarintTrace(dAtA, i, uint64(len(m.MessageID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_DuplicateMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_DuplicateMessage) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_DuplicateMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Topic != nil { + i -= len(*m.Topic) + copy(dAtA[i:], *m.Topic) + i = encodeVarintTrace(dAtA, i, uint64(len(*m.Topic))) + i-- + dAtA[i] = 0x1a + } + if m.ReceivedFrom != nil { + i -= len(m.ReceivedFrom) + copy(dAtA[i:], m.ReceivedFrom) + i = encodeVarintTrace(dAtA, i, uint64(len(m.ReceivedFrom))) + i-- + dAtA[i] = 0x12 + } + if m.MessageID != nil { + i -= len(m.MessageID) + copy(dAtA[i:], m.MessageID) + i = encodeVarintTrace(dAtA, i, uint64(len(m.MessageID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_DeliverMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_DeliverMessage) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_DeliverMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.ReceivedFrom != nil { + i -= len(m.ReceivedFrom) + copy(dAtA[i:], m.ReceivedFrom) + i = encodeVarintTrace(dAtA, i, uint64(len(m.ReceivedFrom))) + i-- + dAtA[i] = 0x1a + } + if m.Topic != nil { + i -= len(*m.Topic) + copy(dAtA[i:], *m.Topic) + i = encodeVarintTrace(dAtA, i, uint64(len(*m.Topic))) + i-- + dAtA[i] = 0x12 + } + if m.MessageID != nil { + i -= len(m.MessageID) + copy(dAtA[i:], m.MessageID) + i = encodeVarintTrace(dAtA, i, uint64(len(m.MessageID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_AddPeer) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_AddPeer) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_AddPeer) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Proto != nil { + i -= len(*m.Proto) + copy(dAtA[i:], *m.Proto) + i = encodeVarintTrace(dAtA, i, uint64(len(*m.Proto))) + i-- + dAtA[i] = 0x12 + } + if m.PeerID != nil { + i -= len(m.PeerID) + copy(dAtA[i:], m.PeerID) + i = encodeVarintTrace(dAtA, i, uint64(len(m.PeerID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_RemovePeer) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_RemovePeer) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_RemovePeer) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.PeerID != nil { + i -= len(m.PeerID) + copy(dAtA[i:], m.PeerID) + i = encodeVarintTrace(dAtA, i, uint64(len(m.PeerID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_RecvRPC) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_RecvRPC) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_RecvRPC) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Meta != nil { + { + size, err := m.Meta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.ReceivedFrom != nil { + i -= len(m.ReceivedFrom) + copy(dAtA[i:], m.ReceivedFrom) + i = encodeVarintTrace(dAtA, i, uint64(len(m.ReceivedFrom))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_SendRPC) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_SendRPC) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_SendRPC) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Meta != nil { + { + size, err := m.Meta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.SendTo != nil { + i -= len(m.SendTo) + copy(dAtA[i:], m.SendTo) + i = encodeVarintTrace(dAtA, i, uint64(len(m.SendTo))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_DropRPC) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_DropRPC) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_DropRPC) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Meta != nil { + { + size, err := m.Meta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.SendTo != nil { + i -= len(m.SendTo) + copy(dAtA[i:], m.SendTo) + i = encodeVarintTrace(dAtA, i, uint64(len(m.SendTo))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_Join) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_Join) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_Join) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Topic != nil { + i -= len(*m.Topic) + copy(dAtA[i:], *m.Topic) + i = encodeVarintTrace(dAtA, i, uint64(len(*m.Topic))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_Leave) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_Leave) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_Leave) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Topic != nil { + i -= len(*m.Topic) + copy(dAtA[i:], *m.Topic) + i = encodeVarintTrace(dAtA, i, uint64(len(*m.Topic))) + i-- + dAtA[i] = 0x12 + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_Graft) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_Graft) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_Graft) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Topic != nil { + i -= len(*m.Topic) + copy(dAtA[i:], *m.Topic) + i = encodeVarintTrace(dAtA, i, uint64(len(*m.Topic))) + i-- + dAtA[i] = 0x12 + } + if m.PeerID != nil { + i -= len(m.PeerID) + copy(dAtA[i:], m.PeerID) + i = encodeVarintTrace(dAtA, i, uint64(len(m.PeerID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_Prune) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_Prune) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_Prune) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Topic != nil { + i -= len(*m.Topic) + copy(dAtA[i:], *m.Topic) + i = encodeVarintTrace(dAtA, i, uint64(len(*m.Topic))) + i-- + dAtA[i] = 0x12 + } + if m.PeerID != nil { + i -= len(m.PeerID) + copy(dAtA[i:], m.PeerID) + i = encodeVarintTrace(dAtA, i, uint64(len(m.PeerID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_RPCMeta) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_RPCMeta) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_RPCMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Control != nil { + { + size, err := m.Control.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if len(m.Subscription) > 0 { + for iNdEx := len(m.Subscription) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Subscription[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Messages) > 0 { + for iNdEx := len(m.Messages) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Messages[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_MessageMeta) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_MessageMeta) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_MessageMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Topic != nil { + i -= len(*m.Topic) + copy(dAtA[i:], *m.Topic) + i = encodeVarintTrace(dAtA, i, uint64(len(*m.Topic))) + i-- + dAtA[i] = 0x12 + } + if m.MessageID != nil { + i -= len(m.MessageID) + copy(dAtA[i:], m.MessageID) + i = encodeVarintTrace(dAtA, i, uint64(len(m.MessageID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_SubMeta) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_SubMeta) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_SubMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Topic != nil { + i -= len(*m.Topic) + copy(dAtA[i:], *m.Topic) + i = encodeVarintTrace(dAtA, i, uint64(len(*m.Topic))) + i-- + dAtA[i] = 0x12 + } + if m.Subscribe != nil { + i-- + if *m.Subscribe { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_ControlMeta) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_ControlMeta) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_ControlMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.Prune) > 0 { + for iNdEx := len(m.Prune) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Prune[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + if len(m.Graft) > 0 { + for iNdEx := len(m.Graft) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Graft[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.Iwant) > 0 { + for iNdEx := len(m.Iwant) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Iwant[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Ihave) > 0 { + for iNdEx := len(m.Ihave) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Ihave[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_ControlIHaveMeta) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_ControlIHaveMeta) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_ControlIHaveMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.MessageIDs) > 0 { + for iNdEx := len(m.MessageIDs) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.MessageIDs[iNdEx]) + copy(dAtA[i:], m.MessageIDs[iNdEx]) + i = encodeVarintTrace(dAtA, i, uint64(len(m.MessageIDs[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if m.Topic != nil { + i -= len(*m.Topic) + copy(dAtA[i:], *m.Topic) + i = encodeVarintTrace(dAtA, i, uint64(len(*m.Topic))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_ControlIWantMeta) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_ControlIWantMeta) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_ControlIWantMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.MessageIDs) > 0 { + for iNdEx := len(m.MessageIDs) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.MessageIDs[iNdEx]) + copy(dAtA[i:], m.MessageIDs[iNdEx]) + i = encodeVarintTrace(dAtA, i, uint64(len(m.MessageIDs[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_ControlGraftMeta) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_ControlGraftMeta) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_ControlGraftMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Topic != nil { + i -= len(*m.Topic) + copy(dAtA[i:], *m.Topic) + i = encodeVarintTrace(dAtA, i, uint64(len(*m.Topic))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEvent_ControlPruneMeta) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEvent_ControlPruneMeta) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEvent_ControlPruneMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.Peers) > 0 { + for iNdEx := len(m.Peers) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Peers[iNdEx]) + copy(dAtA[i:], m.Peers[iNdEx]) + i = encodeVarintTrace(dAtA, i, uint64(len(m.Peers[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if m.Topic != nil { + i -= len(*m.Topic) + copy(dAtA[i:], *m.Topic) + i = encodeVarintTrace(dAtA, i, uint64(len(*m.Topic))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TraceEventBatch) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TraceEventBatch) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TraceEventBatch) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.Batch) > 0 { + for iNdEx := len(m.Batch) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Batch[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTrace(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintTrace(dAtA []byte, offset int, v uint64) int { + offset -= sovTrace(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *TraceEvent) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Type != nil { + n += 1 + sovTrace(uint64(*m.Type)) + } + if m.PeerID != nil { + l = len(m.PeerID) + n += 1 + l + sovTrace(uint64(l)) + } + if m.Timestamp != nil { + n += 1 + sovTrace(uint64(*m.Timestamp)) + } + if m.PublishMessage != nil { + l = m.PublishMessage.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.RejectMessage != nil { + l = m.RejectMessage.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.DuplicateMessage != nil { + l = m.DuplicateMessage.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.DeliverMessage != nil { + l = m.DeliverMessage.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.AddPeer != nil { + l = m.AddPeer.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.RemovePeer != nil { + l = m.RemovePeer.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.RecvRPC != nil { + l = m.RecvRPC.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.SendRPC != nil { + l = m.SendRPC.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.DropRPC != nil { + l = m.DropRPC.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.Join != nil { + l = m.Join.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.Leave != nil { + l = m.Leave.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.Graft != nil { + l = m.Graft.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.Prune != nil { + l = m.Prune.Size() + n += 2 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_PublishMessage) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.MessageID != nil { + l = len(m.MessageID) + n += 1 + l + sovTrace(uint64(l)) + } + if m.Topic != nil { + l = len(*m.Topic) + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_RejectMessage) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.MessageID != nil { + l = len(m.MessageID) + n += 1 + l + sovTrace(uint64(l)) + } + if m.ReceivedFrom != nil { + l = len(m.ReceivedFrom) + n += 1 + l + sovTrace(uint64(l)) + } + if m.Reason != nil { + l = len(*m.Reason) + n += 1 + l + sovTrace(uint64(l)) + } + if m.Topic != nil { + l = len(*m.Topic) + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_DuplicateMessage) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.MessageID != nil { + l = len(m.MessageID) + n += 1 + l + sovTrace(uint64(l)) + } + if m.ReceivedFrom != nil { + l = len(m.ReceivedFrom) + n += 1 + l + sovTrace(uint64(l)) + } + if m.Topic != nil { + l = len(*m.Topic) + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_DeliverMessage) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.MessageID != nil { + l = len(m.MessageID) + n += 1 + l + sovTrace(uint64(l)) + } + if m.Topic != nil { + l = len(*m.Topic) + n += 1 + l + sovTrace(uint64(l)) + } + if m.ReceivedFrom != nil { + l = len(m.ReceivedFrom) + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_AddPeer) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PeerID != nil { + l = len(m.PeerID) + n += 1 + l + sovTrace(uint64(l)) + } + if m.Proto != nil { + l = len(*m.Proto) + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_RemovePeer) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PeerID != nil { + l = len(m.PeerID) + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_RecvRPC) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ReceivedFrom != nil { + l = len(m.ReceivedFrom) + n += 1 + l + sovTrace(uint64(l)) + } + if m.Meta != nil { + l = m.Meta.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_SendRPC) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SendTo != nil { + l = len(m.SendTo) + n += 1 + l + sovTrace(uint64(l)) + } + if m.Meta != nil { + l = m.Meta.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_DropRPC) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SendTo != nil { + l = len(m.SendTo) + n += 1 + l + sovTrace(uint64(l)) + } + if m.Meta != nil { + l = m.Meta.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_Join) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Topic != nil { + l = len(*m.Topic) + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_Leave) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Topic != nil { + l = len(*m.Topic) + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_Graft) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PeerID != nil { + l = len(m.PeerID) + n += 1 + l + sovTrace(uint64(l)) + } + if m.Topic != nil { + l = len(*m.Topic) + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_Prune) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PeerID != nil { + l = len(m.PeerID) + n += 1 + l + sovTrace(uint64(l)) + } + if m.Topic != nil { + l = len(*m.Topic) + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_RPCMeta) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Messages) > 0 { + for _, e := range m.Messages { + l = e.Size() + n += 1 + l + sovTrace(uint64(l)) + } + } + if len(m.Subscription) > 0 { + for _, e := range m.Subscription { + l = e.Size() + n += 1 + l + sovTrace(uint64(l)) + } + } + if m.Control != nil { + l = m.Control.Size() + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_MessageMeta) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.MessageID != nil { + l = len(m.MessageID) + n += 1 + l + sovTrace(uint64(l)) + } + if m.Topic != nil { + l = len(*m.Topic) + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_SubMeta) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Subscribe != nil { + n += 2 + } + if m.Topic != nil { + l = len(*m.Topic) + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_ControlMeta) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Ihave) > 0 { + for _, e := range m.Ihave { + l = e.Size() + n += 1 + l + sovTrace(uint64(l)) + } + } + if len(m.Iwant) > 0 { + for _, e := range m.Iwant { + l = e.Size() + n += 1 + l + sovTrace(uint64(l)) + } + } + if len(m.Graft) > 0 { + for _, e := range m.Graft { + l = e.Size() + n += 1 + l + sovTrace(uint64(l)) + } + } + if len(m.Prune) > 0 { + for _, e := range m.Prune { + l = e.Size() + n += 1 + l + sovTrace(uint64(l)) + } + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_ControlIHaveMeta) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Topic != nil { + l = len(*m.Topic) + n += 1 + l + sovTrace(uint64(l)) + } + if len(m.MessageIDs) > 0 { + for _, b := range m.MessageIDs { + l = len(b) + n += 1 + l + sovTrace(uint64(l)) + } + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_ControlIWantMeta) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.MessageIDs) > 0 { + for _, b := range m.MessageIDs { + l = len(b) + n += 1 + l + sovTrace(uint64(l)) + } + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_ControlGraftMeta) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Topic != nil { + l = len(*m.Topic) + n += 1 + l + sovTrace(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEvent_ControlPruneMeta) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Topic != nil { + l = len(*m.Topic) + n += 1 + l + sovTrace(uint64(l)) + } + if len(m.Peers) > 0 { + for _, b := range m.Peers { + l = len(b) + n += 1 + l + sovTrace(uint64(l)) + } + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *TraceEventBatch) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Batch) > 0 { + for _, e := range m.Batch { + l = e.Size() + n += 1 + l + sovTrace(uint64(l)) + } + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func sovTrace(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTrace(x uint64) (n int) { + return sovTrace(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *TraceEvent) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TraceEvent: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TraceEvent: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + var v TraceEvent_Type + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= TraceEvent_Type(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Type = &v + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PeerID", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PeerID = append(m.PeerID[:0], dAtA[iNdEx:postIndex]...) + if m.PeerID == nil { + m.PeerID = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Timestamp = &v + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PublishMessage", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.PublishMessage == nil { + m.PublishMessage = &TraceEvent_PublishMessage{} + } + if err := m.PublishMessage.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RejectMessage", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.RejectMessage == nil { + m.RejectMessage = &TraceEvent_RejectMessage{} + } + if err := m.RejectMessage.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DuplicateMessage", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DuplicateMessage == nil { + m.DuplicateMessage = &TraceEvent_DuplicateMessage{} + } + if err := m.DuplicateMessage.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeliverMessage", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DeliverMessage == nil { + m.DeliverMessage = &TraceEvent_DeliverMessage{} + } + if err := m.DeliverMessage.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AddPeer", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AddPeer == nil { + m.AddPeer = &TraceEvent_AddPeer{} + } + if err := m.AddPeer.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RemovePeer", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.RemovePeer == nil { + m.RemovePeer = &TraceEvent_RemovePeer{} + } + if err := m.RemovePeer.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RecvRPC", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.RecvRPC == nil { + m.RecvRPC = &TraceEvent_RecvRPC{} + } + if err := m.RecvRPC.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SendRPC", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.SendRPC == nil { + m.SendRPC = &TraceEvent_SendRPC{} + } + if err := m.SendRPC.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DropRPC", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DropRPC == nil { + m.DropRPC = &TraceEvent_DropRPC{} + } + if err := m.DropRPC.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Join", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Join == nil { + m.Join = &TraceEvent_Join{} + } + if err := m.Join.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Leave", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Leave == nil { + m.Leave = &TraceEvent_Leave{} + } + if err := m.Leave.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Graft", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Graft == nil { + m.Graft = &TraceEvent_Graft{} + } + if err := m.Graft.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 16: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Prune", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Prune == nil { + m.Prune = &TraceEvent_Prune{} + } + if err := m.Prune.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_PublishMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PublishMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PublishMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MessageID", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MessageID = append(m.MessageID[:0], dAtA[iNdEx:postIndex]...) + if m.MessageID == nil { + m.MessageID = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Topic", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Topic = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_RejectMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RejectMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RejectMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MessageID", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MessageID = append(m.MessageID[:0], dAtA[iNdEx:postIndex]...) + if m.MessageID == nil { + m.MessageID = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReceivedFrom", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ReceivedFrom = append(m.ReceivedFrom[:0], dAtA[iNdEx:postIndex]...) + if m.ReceivedFrom == nil { + m.ReceivedFrom = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Reason = &s + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Topic", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Topic = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_DuplicateMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DuplicateMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DuplicateMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MessageID", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MessageID = append(m.MessageID[:0], dAtA[iNdEx:postIndex]...) + if m.MessageID == nil { + m.MessageID = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReceivedFrom", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ReceivedFrom = append(m.ReceivedFrom[:0], dAtA[iNdEx:postIndex]...) + if m.ReceivedFrom == nil { + m.ReceivedFrom = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Topic", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Topic = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_DeliverMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeliverMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeliverMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MessageID", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MessageID = append(m.MessageID[:0], dAtA[iNdEx:postIndex]...) + if m.MessageID == nil { + m.MessageID = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Topic", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Topic = &s + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReceivedFrom", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ReceivedFrom = append(m.ReceivedFrom[:0], dAtA[iNdEx:postIndex]...) + if m.ReceivedFrom == nil { + m.ReceivedFrom = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_AddPeer) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AddPeer: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AddPeer: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PeerID", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PeerID = append(m.PeerID[:0], dAtA[iNdEx:postIndex]...) + if m.PeerID == nil { + m.PeerID = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proto", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Proto = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_RemovePeer) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RemovePeer: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RemovePeer: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PeerID", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PeerID = append(m.PeerID[:0], dAtA[iNdEx:postIndex]...) + if m.PeerID == nil { + m.PeerID = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_RecvRPC) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RecvRPC: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RecvRPC: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReceivedFrom", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ReceivedFrom = append(m.ReceivedFrom[:0], dAtA[iNdEx:postIndex]...) + if m.ReceivedFrom == nil { + m.ReceivedFrom = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Meta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Meta == nil { + m.Meta = &TraceEvent_RPCMeta{} + } + if err := m.Meta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_SendRPC) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SendRPC: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SendRPC: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SendTo", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SendTo = append(m.SendTo[:0], dAtA[iNdEx:postIndex]...) + if m.SendTo == nil { + m.SendTo = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Meta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Meta == nil { + m.Meta = &TraceEvent_RPCMeta{} + } + if err := m.Meta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_DropRPC) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DropRPC: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DropRPC: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SendTo", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SendTo = append(m.SendTo[:0], dAtA[iNdEx:postIndex]...) + if m.SendTo == nil { + m.SendTo = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Meta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Meta == nil { + m.Meta = &TraceEvent_RPCMeta{} + } + if err := m.Meta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_Join) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Join: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Join: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Topic", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Topic = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_Leave) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Leave: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Leave: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Topic", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Topic = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_Graft) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Graft: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Graft: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PeerID", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PeerID = append(m.PeerID[:0], dAtA[iNdEx:postIndex]...) + if m.PeerID == nil { + m.PeerID = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Topic", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Topic = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_Prune) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Prune: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Prune: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PeerID", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PeerID = append(m.PeerID[:0], dAtA[iNdEx:postIndex]...) + if m.PeerID == nil { + m.PeerID = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Topic", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Topic = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_RPCMeta) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RPCMeta: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RPCMeta: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Messages", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Messages = append(m.Messages, &TraceEvent_MessageMeta{}) + if err := m.Messages[len(m.Messages)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Subscription", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Subscription = append(m.Subscription, &TraceEvent_SubMeta{}) + if err := m.Subscription[len(m.Subscription)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Control", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Control == nil { + m.Control = &TraceEvent_ControlMeta{} + } + if err := m.Control.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_MessageMeta) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MessageMeta: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MessageMeta: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MessageID", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MessageID = append(m.MessageID[:0], dAtA[iNdEx:postIndex]...) + if m.MessageID == nil { + m.MessageID = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Topic", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Topic = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_SubMeta) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SubMeta: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SubMeta: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Subscribe", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.Subscribe = &b + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Topic", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Topic = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_ControlMeta) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ControlMeta: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ControlMeta: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Ihave", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Ihave = append(m.Ihave, &TraceEvent_ControlIHaveMeta{}) + if err := m.Ihave[len(m.Ihave)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Iwant", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Iwant = append(m.Iwant, &TraceEvent_ControlIWantMeta{}) + if err := m.Iwant[len(m.Iwant)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Graft", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Graft = append(m.Graft, &TraceEvent_ControlGraftMeta{}) + if err := m.Graft[len(m.Graft)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Prune", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Prune = append(m.Prune, &TraceEvent_ControlPruneMeta{}) + if err := m.Prune[len(m.Prune)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_ControlIHaveMeta) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ControlIHaveMeta: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ControlIHaveMeta: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Topic", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Topic = &s + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MessageIDs", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MessageIDs = append(m.MessageIDs, make([]byte, postIndex-iNdEx)) + copy(m.MessageIDs[len(m.MessageIDs)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_ControlIWantMeta) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ControlIWantMeta: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ControlIWantMeta: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MessageIDs", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MessageIDs = append(m.MessageIDs, make([]byte, postIndex-iNdEx)) + copy(m.MessageIDs[len(m.MessageIDs)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_ControlGraftMeta) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ControlGraftMeta: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ControlGraftMeta: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Topic", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Topic = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEvent_ControlPruneMeta) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ControlPruneMeta: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ControlPruneMeta: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Topic", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.Topic = &s + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Peers", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Peers = append(m.Peers, make([]byte, postIndex-iNdEx)) + copy(m.Peers[len(m.Peers)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TraceEventBatch) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TraceEventBatch: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TraceEventBatch: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Batch", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTrace + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Batch = append(m.Batch, &TraceEvent{}) + if err := m.Batch[len(m.Batch)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTrace(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTrace + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTrace + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTrace + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTrace + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTrace + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTrace + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTrace = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTrace = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTrace = fmt.Errorf("proto: unexpected end of group") +) diff --git a/p2p/pubsub/trace.go b/p2p/pubsub/trace.go index 80f652625e..a6e2e39288 100644 --- a/p2p/pubsub/trace.go +++ b/p2p/pubsub/trace.go @@ -78,7 +78,7 @@ func (t *pubsubTracer) PublishMessage(msg *Message) { now := time.Now().UnixNano() evt := &message.TraceEvent{ Type: message.TraceEvent_PUBLISH_MESSAGE.Enum(), - PeerID: t.pid, + PeerID: t.pid.Bytes(), Timestamp: &now, PublishMessage: &message.TraceEvent_PublishMessage{ MessageID: []byte(t.msgID(msg.Message)), @@ -119,11 +119,11 @@ func (t *pubsubTracer) RejectMessage(msg *Message, reason string) { now := time.Now().UnixNano() evt := &message.TraceEvent{ Type: message.TraceEvent_REJECT_MESSAGE.Enum(), - PeerID: t.pid, + PeerID: t.pid.Bytes(), Timestamp: &now, RejectMessage: &message.TraceEvent_RejectMessage{ MessageID: []byte(t.msgID(msg.Message)), - ReceivedFrom: msg.ReceivedFrom.ID(), + ReceivedFrom: msg.ReceivedFrom.ID().Bytes(), Reason: &reason, Topic: msg.Topic, }, @@ -150,11 +150,11 @@ func (t *pubsubTracer) DuplicateMessage(msg *Message) { now := time.Now().UnixNano() evt := &message.TraceEvent{ Type: message.TraceEvent_DUPLICATE_MESSAGE.Enum(), - PeerID: t.pid, + PeerID: t.pid.Bytes(), Timestamp: &now, DuplicateMessage: &message.TraceEvent_DuplicateMessage{ MessageID: []byte(t.msgID(msg.Message)), - ReceivedFrom: msg.ReceivedFrom.ID(), + ReceivedFrom: msg.ReceivedFrom.ID().Bytes(), Topic: msg.Topic, }, } @@ -180,12 +180,12 @@ func (t *pubsubTracer) DeliverMessage(msg *Message) { now := time.Now().UnixNano() evt := &message.TraceEvent{ Type: message.TraceEvent_DELIVER_MESSAGE.Enum(), - PeerID: t.pid, + PeerID: t.pid.Bytes(), Timestamp: &now, DeliverMessage: &message.TraceEvent_DeliverMessage{ MessageID: []byte(t.msgID(msg.Message)), Topic: msg.Topic, - ReceivedFrom: msg.ReceivedFrom.ID(), + ReceivedFrom: msg.ReceivedFrom.ID().Bytes(), }, } @@ -209,10 +209,10 @@ func (t *pubsubTracer) AddPeer(p *enode.Node, proto ProtocolID) { now := time.Now().UnixNano() evt := &message.TraceEvent{ Type: message.TraceEvent_ADD_PEER.Enum(), - PeerID: t.pid, + PeerID: t.pid.Bytes(), Timestamp: &now, AddPeer: &message.TraceEvent_AddPeer{ - PeerID: p.ID(), + PeerID: p.ID().Bytes(), Proto: &protoStr, }, } @@ -236,10 +236,10 @@ func (t *pubsubTracer) RemovePeer(p enode.ID) { now := time.Now().UnixNano() evt := &message.TraceEvent{ Type: message.TraceEvent_REMOVE_PEER.Enum(), - PeerID: t.pid, + PeerID: t.pid.Bytes(), Timestamp: &now, RemovePeer: &message.TraceEvent_RemovePeer{ - PeerID: p, + PeerID: p.Bytes(), }, } @@ -262,10 +262,10 @@ func (t *pubsubTracer) RecvRPC(rpc *RPC) { now := time.Now().UnixNano() evt := &message.TraceEvent{ Type: message.TraceEvent_RECV_RPC.Enum(), - PeerID: t.pid, + PeerID: t.pid.Bytes(), Timestamp: &now, RecvRPC: &message.TraceEvent_RecvRPC{ - ReceivedFrom: rpc.from.ID(), + ReceivedFrom: rpc.from.ID().Bytes(), Meta: t.traceRPCMeta(rpc), }, } @@ -289,10 +289,10 @@ func (t *pubsubTracer) SendRPC(rpc *RPC, p enode.ID) { now := time.Now().UnixNano() evt := &message.TraceEvent{ Type: message.TraceEvent_SEND_RPC.Enum(), - PeerID: t.pid, + PeerID: t.pid.Bytes(), Timestamp: &now, SendRPC: &message.TraceEvent_SendRPC{ - SendTo: p, + SendTo: p.Bytes(), Meta: t.traceRPCMeta(rpc), }, } @@ -316,10 +316,10 @@ func (t *pubsubTracer) DropRPC(rpc *RPC, p enode.ID) { now := time.Now().UnixNano() evt := &message.TraceEvent{ Type: message.TraceEvent_DROP_RPC.Enum(), - PeerID: t.pid, + PeerID: t.pid.Bytes(), Timestamp: &now, DropRPC: &message.TraceEvent_DropRPC{ - SendTo: p, + SendTo: p.Bytes(), Meta: t.traceRPCMeta(rpc), }, } @@ -391,9 +391,9 @@ func (t *pubsubTracer) traceRPCMeta(rpc *RPC) *message.TraceEvent_RPCMeta { var prune []*message.TraceEvent_ControlPruneMeta for _, ctl := range rpc.Control.Prune { - peers := make([]enode.ID, 0, len(ctl.Peers)) + peers := make([][]byte, 0, len(ctl.Peers)) for _, pi := range ctl.Peers { - peers = append(peers, pi.PeerID) + peers = append(peers, pi.PeerID.Bytes()) } prune = append(prune, &message.TraceEvent_ControlPruneMeta{ Topic: ctl.TopicID, @@ -428,7 +428,7 @@ func (t *pubsubTracer) Join(topic string) { now := time.Now().UnixNano() evt := &message.TraceEvent{ Type: message.TraceEvent_JOIN.Enum(), - PeerID: t.pid, + PeerID: t.pid.Bytes(), Timestamp: &now, Join: &message.TraceEvent_Join{ Topic: &topic, @@ -454,7 +454,7 @@ func (t *pubsubTracer) Leave(topic string) { now := time.Now().UnixNano() evt := &message.TraceEvent{ Type: message.TraceEvent_LEAVE.Enum(), - PeerID: t.pid, + PeerID: t.pid.Bytes(), Timestamp: &now, Leave: &message.TraceEvent_Leave{ Topic: &topic, @@ -480,10 +480,10 @@ func (t *pubsubTracer) Graft(p enode.ID, topic string) { now := time.Now().UnixNano() evt := &message.TraceEvent{ Type: message.TraceEvent_GRAFT.Enum(), - PeerID: t.pid, + PeerID: t.pid.Bytes(), Timestamp: &now, Graft: &message.TraceEvent_Graft{ - PeerID: p, + PeerID: p.Bytes(), Topic: &topic, }, } @@ -507,10 +507,10 @@ func (t *pubsubTracer) Prune(p enode.ID, topic string) { now := time.Now().UnixNano() evt := &message.TraceEvent{ Type: message.TraceEvent_PRUNE.Enum(), - PeerID: t.pid, + PeerID: t.pid.Bytes(), Timestamp: &now, Prune: &message.TraceEvent_Prune{ - PeerID: p, + PeerID: p.Bytes(), Topic: &topic, }, } diff --git a/p2p/pubsub/tracer.go b/p2p/pubsub/tracer.go index 1104c080da..0dcbbc85be 100644 --- a/p2p/pubsub/tracer.go +++ b/p2p/pubsub/tracer.go @@ -1,10 +1,14 @@ package pubsub import ( + "compress/gzip" + "context" "encoding/json" "io" + "net" "os" "sync" + "time" "github.com/AlayaNetwork/Alaya-Go/p2p/pubsub/message" @@ -173,20 +177,17 @@ func (t *PBTracer) doWrite() { var _ EventTracer = (*PBTracer)(nil) -/*const RemoteTracerProtoID = ProtocolID("/libp2p/pubsub/tracer/1.0.0") - // RemoteTracer is a tracer that sends trace events to a remote peer type RemoteTracer struct { basicTracer ctx context.Context - host Host - peer enode.ID + dial *net.Dialer + remoteHost string } // NewRemoteTracer constructs a RemoteTracer, tracing to the peer identified by pi -func NewRemoteTracer(ctx context.Context, host Host, pi enode.Node) (*RemoteTracer, error) { - tr := &RemoteTracer{ctx: ctx, host: host, peer: pi.ID(), basicTracer: basicTracer{ch: make(chan struct{}, 1), lossy: true}} - host.Peerstore().AddAddrs(pi.ID(), PermanentAddrTTL) +func NewRemoteTracer(ctx context.Context, remoteHost string) (*RemoteTracer, error) { + tr := &RemoteTracer{ctx: ctx, dial: &net.Dialer{Timeout: 15 * time.Second}, remoteHost: remoteHost, basicTracer: basicTracer{ch: make(chan struct{}, 1), lossy: true}} go tr.doWrite() return tr, nil } @@ -194,15 +195,15 @@ func NewRemoteTracer(ctx context.Context, host Host, pi enode.Node) (*RemoteTrac func (t *RemoteTracer) doWrite() { var buf []*message.TraceEvent - s, err := t.openStream() + conn, err := t.openConn() if err != nil { - log.Debugf("error opening remote tracer stream: %s", err.Error()) + log.Error("Dial RemoteTracer failed", "error", err) return } var batch message.TraceEventBatch - gzipW := gzip.NewWriter(s) + gzipW := gzip.NewWriter(conn) w := protoio.NewDelimitedWriter(gzipW) for { @@ -231,13 +232,13 @@ func (t *RemoteTracer) doWrite() { err = w.WriteMsg(&batch) if err != nil { - log.Debugf("error writing trace event batch: %s", err) + log.Error("error writing trace event batch", "error", err) goto end } err = gzipW.Flush() if err != nil { - log.Debugf("error flushin gzip stream: %s", err) + log.Error("error flushin gzip stream", "error", err) goto end } @@ -249,48 +250,27 @@ func (t *RemoteTracer) doWrite() { if !ok { if err != nil { - s.Reset() } else { gzipW.Close() - s.Close() + conn.Close() } return } if err != nil { - s.Reset() - s, err = t.openStream() + conn, err = t.openConn() if err != nil { - log.Debugf("error opening remote tracer stream: %s", err.Error()) + log.Error("error opening remote tracer stream", "error", err.Error()) return } - gzipW.Reset(s) + gzipW.Reset(conn) } } } -func (t *RemoteTracer) openStream() (Stream, error) { - for { - ctx, cancel := context.WithTimeout(t.ctx, time.Minute) - s, err := t.host.NewStream(ctx, t.peer, RemoteTracerProtoID) - cancel() - if err != nil { - if t.ctx.Err() != nil { - return nil, err - } - - // wait a minute and try again, to account for transient server downtime - select { - case <-time.After(time.Minute): - continue - case <-t.ctx.Done(): - return nil, t.ctx.Err() - } - } - - return s, nil - } +func (t *RemoteTracer) openConn() (net.Conn, error) { + return t.dial.DialContext(t.ctx, "tcp", t.remoteHost) } -var _ EventTracer = (*RemoteTracer)(nil)*/ +var _ EventTracer = (*RemoteTracer)(nil) From 0cd8eaba38efd5d48d8fd0343921c75298ec4bd2 Mon Sep 17 00:00:00 2001 From: luowei Date: Wed, 2 Mar 2022 10:00:32 +0800 Subject: [PATCH 283/299] Add BytesID function --- p2p/enode/node.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/p2p/enode/node.go b/p2p/enode/node.go index 557af1ad7c..7727bf36bb 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -258,6 +258,15 @@ func HexID(in string) ID { return id } +func BytesID(b []byte) (ID, error) { + var id ID + if len(b) != len(id) { + return id, fmt.Errorf("wrong length, want %d bytes", len(id)) + } + copy(id[:], b) + return id, nil +} + func ParseID(in string) (ID, error) { var id ID b, err := hex.DecodeString(strings.TrimPrefix(in, "0x")) From c46ad65b144f07d89e74b590336618ca3c426c9c Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Wed, 2 Mar 2022 14:58:00 +0800 Subject: [PATCH 284/299] 1.Remove redundant metrics 2.Correct spelling of wrong words:Gauage -> Gauge --- consensus/cbft/cbft.go | 6 +++--- consensus/cbft/consensus_process.go | 11 ++--------- consensus/cbft/metrics.go | 26 ++++++++++---------------- consensus/cbft/network/metrics.go | 2 +- consensus/cbft/network/peer.go | 2 +- consensus/cbft/sync_process.go | 8 ++++---- core/blockchain_cache.go | 4 ++-- 7 files changed, 23 insertions(+), 36 deletions(-) diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 6a9923cc7f..11d00fa3d7 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -881,7 +881,7 @@ func (cbft *Cbft) OnSeal(block *types.Block, results chan<- *types.Block, stop < minedCounter.Inc(1) preBlock := cbft.blockTree.FindBlockByHash(block.ParentHash()) if preBlock != nil { - blockMinedGauage.Update(common.Millis(time.Now()) - int64(preBlock.Time())) + blockMinedGauge.Update(common.Millis(time.Now()) - int64(preBlock.Time())) } go func() { select { @@ -1254,8 +1254,8 @@ func (cbft *Cbft) OnShouldSeal(result chan error) { return } - proposerIndexGauage.Update(int64(currentProposer)) - validatorCountGauage.Update(int64(numValidators)) + proposerIndexGauge.Update(int64(currentProposer)) + validatorCountGauge.Update(int64(numValidators)) result <- nil } diff --git a/consensus/cbft/consensus_process.go b/consensus/cbft/consensus_process.go index a6ac137b21..531e38d818 100644 --- a/consensus/cbft/consensus_process.go +++ b/consensus/cbft/consensus_process.go @@ -1037,7 +1037,6 @@ func (cbft *Cbft) findQCBlock() { cbft.insertQCBlock(block, qc) cbft.network.Broadcast(&protocols.BlockQuorumCert{BlockQC: qc}) // metrics - blockQCCollectedGauage.Update(int64(block.Time())) blockWholeQCTimer.UpdateSince(time.Unix(int64(block.Time()), 0)) cbft.trySendPrepareVote() } @@ -1068,12 +1067,6 @@ func (cbft *Cbft) tryCommitNewBlock(lock *types.Block, commit *types.Block, qc * cbft.state.SetHighestCommitBlock(commit) cbft.blockTree.PruneBlock(commit.Hash(), commit.NumberU64(), nil) cbft.blockTree.NewRoot(commit) - // metrics - blockNumberGauage.Update(int64(commit.NumberU64())) - highestQCNumberGauage.Update(int64(highestqc.NumberU64())) - highestLockedNumberGauage.Update(int64(lock.NumberU64())) - highestCommitNumberGauage.Update(int64(commit.NumberU64())) - blockConfirmedMeter.Mark(1) } else if oldCommit.NumberU64() == commit.NumberU64() && oldCommit.NumberU64() > 0 { cbft.log.Info("Fork block", "number", highestqc.NumberU64(), "hash", highestqc.Hash()) lockBlock, lockQC := cbft.blockTree.FindBlockAndQC(lock.Hash(), lock.NumberU64()) @@ -1346,8 +1339,8 @@ func (cbft *Cbft) changeView(epoch, viewNumber uint64, block *types.Block, qc *c cbft.state.SetLastViewChangeQC(viewChangeQC) // record metrics - viewNumberGauage.Update(int64(viewNumber)) - epochNumberGauage.Update(int64(epoch)) + viewNumberGauge.Update(int64(viewNumber)) + epochNumberGauge.Update(int64(epoch)) viewChangedTimer.UpdateSince(time.Unix(int64(block.Time()), 0)) // write confirmed viewChange info to wal diff --git a/consensus/cbft/metrics.go b/consensus/cbft/metrics.go index 4cde7fc398..cd4fa5c260 100644 --- a/consensus/cbft/metrics.go +++ b/consensus/cbft/metrics.go @@ -21,27 +21,21 @@ import ( ) var ( - blockMinedGauage = metrics.NewRegisteredGauge("cbft/gauage/block/mined", nil) - viewChangedTimer = metrics.NewRegisteredTimer("cbft/timer/view/changed", nil) - blockQCCollectedGauage = metrics.NewRegisteredGauge("cbft/gauage/block/qc_collected", nil) + blockMinedGauge = metrics.NewRegisteredGauge("cbft/gauge/block/mined", nil) + viewChangedTimer = metrics.NewRegisteredTimer("cbft/timer/view/changed", nil) blockProduceMeter = metrics.NewRegisteredMeter("cbft/meter/block/produce", nil) blockCheckFailureMeter = metrics.NewRegisteredMeter("cbft/meter/block/check_failure", nil) signatureCheckFailureMeter = metrics.NewRegisteredMeter("cbft/meter/signature/check_failure", nil) - blockConfirmedMeter = metrics.NewRegisteredMeter("cbft/meter/block/confirmed", nil) masterCounter = metrics.NewRegisteredCounter("cbft/counter/view/count", nil) consensusCounter = metrics.NewRegisteredCounter("cbft/counter/consensus/count", nil) minedCounter = metrics.NewRegisteredCounter("cbft/counter/mined/count", nil) - viewNumberGauage = metrics.NewRegisteredGauge("cbft/gauage/view/number", nil) - epochNumberGauage = metrics.NewRegisteredGauge("cbft/gauage/epoch/number", nil) - proposerIndexGauage = metrics.NewRegisteredGauge("cbft/gauage/proposer/index", nil) - validatorCountGauage = metrics.NewRegisteredGauge("cbft/gauage/validator/count", nil) - blockNumberGauage = metrics.NewRegisteredGauge("cbft/gauage/block/number", nil) - highestQCNumberGauage = metrics.NewRegisteredGauge("cbft/gauage/block/qc/number", nil) - highestLockedNumberGauage = metrics.NewRegisteredGauge("cbft/gauage/block/locked/number", nil) - highestCommitNumberGauage = metrics.NewRegisteredGauge("cbft/gauage/block/commit/number", nil) + viewNumberGauge = metrics.NewRegisteredGauge("cbft/gauge/view/number", nil) + epochNumberGauge = metrics.NewRegisteredGauge("cbft/gauge/epoch/number", nil) + proposerIndexGauge = metrics.NewRegisteredGauge("cbft/gauge/proposer/index", nil) + validatorCountGauge = metrics.NewRegisteredGauge("cbft/gauge/validator/count", nil) // for rand-grouped-consensus block upgradeCoordinatorBlockCounter = metrics.NewRegisteredCounter("cbft/counter/block/upgradeCoordinator/count", nil) @@ -56,8 +50,8 @@ var ( blockGroupQCTimer = metrics.NewRegisteredTimer("cbft/timer/block/group/qc", nil) // Own group blockWholeQCTimer = metrics.NewRegisteredTimer("cbft/timer/block/whole/qc", nil) - missRGBlockQuorumCertsGauage = metrics.NewRegisteredGauge("cbft/gauage/block/miss/rgqc", nil) - missVotesGauage = metrics.NewRegisteredGauge("cbft/gauage/block/miss/vote", nil) + missRGBlockQuorumCertsGauge = metrics.NewRegisteredGauge("cbft/gauge/block/miss/rgqc", nil) + missVotesGauge = metrics.NewRegisteredGauge("cbft/gauge/block/miss/vote", nil) missVotesCounter = metrics.NewRegisteredCounter("cbft/counter/block/miss/vote/count", nil) responseVotesCounter = metrics.NewRegisteredCounter("cbft/counter/block/response/vote/count", nil) @@ -75,8 +69,8 @@ var ( viewGroupQCTimer = metrics.NewRegisteredTimer("cbft/timer/view/group/qc", nil) // Own group viewWholeQCTimer = metrics.NewRegisteredTimer("cbft/timer/view/whole/qc", nil) - missRGViewQuorumCertsGauage = metrics.NewRegisteredGauge("cbft/gauage/view/miss/rgqc", nil) - missVcsGauage = metrics.NewRegisteredGauge("cbft/gauage/view/miss/vote", nil) + missRGViewQuorumCertsGauge = metrics.NewRegisteredGauge("cbft/gauge/view/miss/rgqc", nil) + missVcsGauge = metrics.NewRegisteredGauge("cbft/gauge/view/miss/vote", nil) missVcsCounter = metrics.NewRegisteredCounter("cbft/counter/view/miss/vcs/count", nil) responseVcsCounter = metrics.NewRegisteredCounter("cbft/counter/view/response/vcs/count", nil) diff --git a/consensus/cbft/network/metrics.go b/consensus/cbft/network/metrics.go index f3360556e1..470aaeb49a 100644 --- a/consensus/cbft/network/metrics.go +++ b/consensus/cbft/network/metrics.go @@ -117,7 +117,7 @@ var ( messageGossipMeter = metrics.NewRegisteredMeter("cbft/meter/message/gossip", nil) messageRepeatMeter = metrics.NewRegisteredMeter("cbft/meter/message/repeat", nil) - neighborPeerGauage = metrics.NewRegisteredGauge("cbft/gauage/peer/value", nil) + neighborPeerGauge = metrics.NewRegisteredGauge("cbft/gauge/peer/value", nil) ) // meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of diff --git a/consensus/cbft/network/peer.go b/consensus/cbft/network/peer.go index 920fa3ac8f..1c00daabf2 100644 --- a/consensus/cbft/network/peer.go +++ b/consensus/cbft/network/peer.go @@ -591,7 +591,7 @@ func (ps *PeerSet) printPeers() { case <-outTimer.C: peers := ps.allPeers() if peers != nil { - neighborPeerGauage.Update(int64(len(peers))) + neighborPeerGauge.Update(int64(len(peers))) } var bf bytes.Buffer for idx, peer := range peers { diff --git a/consensus/cbft/sync_process.go b/consensus/cbft/sync_process.go index b5500bea27..1b756bf2d7 100644 --- a/consensus/cbft/sync_process.go +++ b/consensus/cbft/sync_process.go @@ -992,8 +992,8 @@ func (cbft *Cbft) MissGroupVotes(blockIndex uint32) *ctypes.UnKnownGroups { } } // just for record metrics - missRGBlockQuorumCertsGauage.Update(int64(missGroups)) - missVotesGauage.Update(int64(missVotes)) + missRGBlockQuorumCertsGauge.Update(int64(missGroups)) + missVotesGauge.Update(int64(missVotes)) return unKnowns } @@ -1037,8 +1037,8 @@ func (cbft *Cbft) MissGroupViewChanges() *ctypes.UnKnownGroups { } } // just for record metrics - missRGViewQuorumCertsGauage.Update(int64(missGroups)) - missVcsGauage.Update(int64(missViewChanges)) + missRGViewQuorumCertsGauge.Update(int64(missGroups)) + missVcsGauge.Update(int64(missViewChanges)) return unKnowns } diff --git a/core/blockchain_cache.go b/core/blockchain_cache.go index 74908b4d06..266bd846cf 100644 --- a/core/blockchain_cache.go +++ b/core/blockchain_cache.go @@ -36,7 +36,7 @@ import ( var ( errMakeStateDB = errors.New("make StateDB error") - blockExecutedGauage = metrics.NewRegisteredGauge("cbft/gauage/block/executed", nil) + blockExecutedGauge = metrics.NewRegisteredGauge("cbft/gauge/block/executed", nil) ) type BlockChainCache struct { @@ -309,7 +309,7 @@ func (bcc *BlockChainCache) Execute(block *types.Block, parent *types.Block) err } else { return fmt.Errorf("execute block error, err:%s", err.Error()) } - blockExecutedGauage.Update(common.Millis(time.Now()) - common.Millis(start)) + blockExecutedGauge.Update(common.Millis(time.Now()) - common.Millis(start)) return nil } From 6244e885d40fe35ddd764be696ad1b0fee8bf815 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Wed, 2 Mar 2022 15:18:52 +0800 Subject: [PATCH 285/299] Modify the output information of consensus messages to avoid too many logs --- consensus/cbft/protocols/protocol.go | 2 +- consensus/cbft/types/crypto.go | 18 ++++++++++++++++-- core/types/transaction.go | 2 -- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/consensus/cbft/protocols/protocol.go b/consensus/cbft/protocols/protocol.go index c0c4ee9af3..0bc3de7ce4 100644 --- a/consensus/cbft/protocols/protocol.go +++ b/consensus/cbft/protocols/protocol.go @@ -944,7 +944,7 @@ type RGBlockQuorumCert struct { } func (rgb *RGBlockQuorumCert) String() string { - return fmt.Sprintf("{GroupID:%d,Epoch:%d,ViewNumber:%d,BlockIndx:%d,Hash:%s,Number:%d,ValidatorIndex:%d,Signature:%s,ValidatorSetLen:%s}", + return fmt.Sprintf("{GroupID:%d,Epoch:%d,ViewNumber:%d,BlockIndx:%d,Hash:%s,Number:%d,ValidatorIndex:%d,Signature:%s,ValidatorSetLen:%d}", rgb.GroupID, rgb.EpochNum(), rgb.ViewNum(), rgb.BlockIndx(), rgb.BHash().TerminalString(), rgb.BlockNum(), rgb.NodeIndex(), rgb.Signature.String(), rgb.BlockQC.ValidatorSet.HasLength()) } diff --git a/consensus/cbft/types/crypto.go b/consensus/cbft/types/crypto.go index 785e58ee7e..98c62d0ed1 100644 --- a/consensus/cbft/types/crypto.go +++ b/consensus/cbft/types/crypto.go @@ -127,7 +127,7 @@ func (q *QuorumCert) String() string { if q == nil { return "" } - return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,Hash:%s,Number:%d,Index:%d,Signature:%s,ValidatorSetLen:%s}", q.Epoch, q.ViewNumber, q.BlockHash.TerminalString(), q.BlockNumber, q.BlockIndex, q.Signature.String(), q.ValidatorSet.HasLength()) + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,Hash:%s,Number:%d,Index:%d,Signature:%s,ValidatorSetLen:%d}", q.Epoch, q.ViewNumber, q.BlockHash.TerminalString(), q.BlockNumber, q.BlockIndex, q.Signature.String(), q.ValidatorSet.HasLength()) } // Add a new signature to the aggregate signature @@ -256,7 +256,7 @@ func (q *ViewChangeQuorumCert) Len() int { } func (q ViewChangeQuorumCert) String() string { - return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,Hash:%s,Number:%d,BlockEpoch:%d,BlockViewNumber:%d:ValidatorSetLen:%s}", q.Epoch, q.ViewNumber, q.BlockHash.TerminalString(), q.BlockNumber, q.BlockEpoch, q.BlockViewNumber, q.ValidatorSet.HasLength()) + return fmt.Sprintf("{Epoch:%d,ViewNumber:%d,Hash:%s,Number:%d,BlockEpoch:%d,BlockViewNumber:%d:ValidatorSetLen:%d}", q.Epoch, q.ViewNumber, q.BlockHash.TerminalString(), q.BlockNumber, q.BlockEpoch, q.BlockViewNumber, q.ValidatorSet.HasLength()) } // if the two quorumCert have the same blockNumber @@ -491,6 +491,20 @@ type UnKnownGroup struct { UnKnownSet *utils.BitArray `json:"unKnownSet"` } +func (unKnown *UnKnownGroup) MarshalJSON() ([]byte, error) { + type UnKnownGroup struct { + GroupID uint32 `json:"groupID"` + UnKnownSetLen int `json:"unKnownSetLen"` + } + + un := &UnKnownGroup{ + GroupID: unKnown.GroupID, + UnKnownSetLen: unKnown.UnKnownSet.HasLength(), + } + + return json.Marshal(un) +} + func (unKnowns *UnKnownGroups) UnKnownSize() int { if unKnowns == nil || len(unKnowns.UnKnown) <= 0 { return 0 diff --git a/core/types/transaction.go b/core/types/transaction.go index a10e889fea..6c84d36305 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -232,8 +232,6 @@ func (tx *Transaction) Size() common.StorageSize { // AsMessage returns the transaction as a core.Message. // // AsMessage requires a signer to derive the sender. -// -// XXX Rename message to something less arbitrary? func (tx *Transaction) AsMessage(s Signer) (Message, error) { msg := Message{ nonce: tx.data.AccountNonce, From 7e43c66140b5b247e83e88cd552a8cf18fd190e5 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Wed, 2 Mar 2022 15:26:41 +0800 Subject: [PATCH 286/299] The messages in the group after the unit also calculate the advanced efficiency CoordinatorRatio --- consensus/cbft/rg_broadcast.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/consensus/cbft/rg_broadcast.go b/consensus/cbft/rg_broadcast.go index a44e9bb48f..f36d90bb9d 100644 --- a/consensus/cbft/rg_broadcast.go +++ b/consensus/cbft/rg_broadcast.go @@ -289,9 +289,9 @@ func (m *RGBroadcastManager) countCoordinator(unitID uint32, coordinatorIndexes c := 0 for i := 0; i < len(coordinatorIndexes); i++ { - if uint32(i) >= unitID { - break - } + //if uint32(i) >= unitID { + // break + //} for _, v := range coordinatorIndexes[i] { if _, ok := receiveIndexesMap[v]; ok { c++ From 46b0a5e0080d8b0a5b42cca2669bbc9cd5b61b7f Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Tue, 8 Mar 2022 11:36:17 +0800 Subject: [PATCH 287/299] Adjust the order of topic events notified to the p2pserver when the node starts --- consensus/bft_mock.go | 4 ++++ consensus/cbft/cbft.go | 4 ++++ consensus/cbft/validator/validator.go | 28 +++++++++++++++++++-------- consensus/consensus.go | 2 ++ eth/backend.go | 7 ++++++- p2p/server.go | 4 ++-- 6 files changed, 38 insertions(+), 11 deletions(-) diff --git a/consensus/bft_mock.go b/consensus/bft_mock.go index aa67cb3c35..7ba7fa5c13 100644 --- a/consensus/bft_mock.go +++ b/consensus/bft_mock.go @@ -146,6 +146,10 @@ func (bm *BftMock) Node() *enode.Node { return enode.NewV4(&privateKey.PublicKey, nil, 0, 0) } +func (bm *BftMock) GetAwaitingTopicEvent() map[string][]*enode.Node { + return nil +} + // Author retrieves the Ethereum address of the account that minted the given // block, which may be different from the header's coinbase if a consensus // engine is based on signatures. diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 11d00fa3d7..9b4aa48288 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -2116,6 +2116,10 @@ func (cbft *Cbft) Node() *enode.Node { return cbft.config.Option.Node } +func (cbft *Cbft) GetAwaitingTopicEvent() map[string][]*enode.Node { + return cbft.validatorPool.GetAwaitingTopicEvent() +} + func (cbft *Cbft) avgRTT() time.Duration { produceInterval := time.Duration(cbft.config.Sys.Period/uint64(cbft.config.Sys.Amount)) * time.Millisecond rtt := cbft.AvgLatency() * 2 diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 0528871c10..2e3be0c559 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -332,6 +332,8 @@ type ValidatorPool struct { prevValidators *cbfttypes.Validators // Previous round validators currentValidators *cbfttypes.Validators // Current round validators nextValidators *cbfttypes.Validators // Next round validators, to Post Pub event + + awaitingTopicEvent map[string][]*enode.Node } // NewValidatorPool new a validator pool. @@ -343,6 +345,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID grouped: needGroup, groupValidatorsLimit: xcom.MaxGroupValidators(), coordinatorLimit: xcom.CoordinatorsLimit(), + awaitingTopicEvent: make(map[string][]*enode.Node), } lastNumber := agency.GetLastNumber(blockNumber) // FIXME: Check `GetValidators` return error @@ -366,7 +369,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID pool.switchPoint = pool.currentValidators.ValidBlockNumber - 1 } if needGroup { - if err := pool.organize(pool.currentValidators, epoch, eventMux); err != nil { + if err := pool.organize(pool.currentValidators, epoch, eventMux, true); err != nil { log.Error("ValidatorPool organized failed!", "error", err) } blockDif := lastNumber - blockNumber @@ -378,7 +381,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID } if nds != nil { pool.nextValidators = nds - pool.organize(pool.nextValidators, epoch+1, eventMux) + pool.organize(pool.nextValidators, epoch+1, eventMux, true) } } } @@ -403,7 +406,7 @@ func (vp *ValidatorPool) Reset(blockNumber uint64, epoch uint64, eventMux *event vp.switchPoint = vp.currentValidators.ValidBlockNumber - 1 } if vp.grouped { - vp.organize(vp.currentValidators, epoch, eventMux) + vp.organize(vp.currentValidators, epoch, eventMux, false) } log.Debug("Reset validator", "validators", vp.currentValidators.String(), "switchpoint", vp.switchPoint, "epoch", vp.epoch, "lastNumber", vp.lastNumber) } @@ -467,7 +470,7 @@ func (vp *ValidatorPool) Update(blockHash common.Hash, blockNumber uint64, epoch vp.nextValidators = nds if vp.grouped { - vp.organize(vp.nextValidators, epoch, eventMux) + vp.organize(vp.nextValidators, epoch, eventMux, false) } } @@ -504,7 +507,7 @@ func (vp *ValidatorPool) InitComingValidators(blockHash common.Hash, blockNumber } // 如果是提案生效后第一个选举块,只有新ConsensusSize和旧ConsensusSize一样才会走下面的逻辑 vp.nextValidators = nds - vp.organize(vp.nextValidators, vp.epoch+1, eventMux) + vp.organize(vp.nextValidators, vp.epoch+1, eventMux, false) log.Debug("InitComingValidators:Update nextValidators OK", "blockNumber", blockNumber, "epoch", vp.epoch+1) return nil } @@ -929,7 +932,7 @@ func (vp *ValidatorPool) GetGroupIndexes(epoch uint64) map[uint32][]uint32 { } // organize validators into groups -func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64, eventMux *event.TypeMux) error { +func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64, eventMux *event.TypeMux, init bool) error { if validators == nil { return errors.New("validators is nil") } @@ -960,8 +963,13 @@ func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64 consensusTopic := cbfttypes.ConsensusTopicName(epoch) groupTopic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) - eventMux.Post(cbfttypes.NewTopicEvent{Topic: consensusTopic, Nodes: otherConsensusNodes}) - eventMux.Post(cbfttypes.NewTopicEvent{Topic: groupTopic, Nodes: groupNodes}) + if init { + vp.awaitingTopicEvent[consensusTopic] = otherConsensusNodes + vp.awaitingTopicEvent[groupTopic] = groupNodes + } else { + eventMux.Post(cbfttypes.NewTopicEvent{Topic: consensusTopic, Nodes: otherConsensusNodes}) + eventMux.Post(cbfttypes.NewTopicEvent{Topic: groupTopic, Nodes: groupNodes}) + } eventMux.Post(cbfttypes.GroupTopicEvent{Topic: groupTopic, PubSub: true}) eventMux.Post(cbfttypes.GroupTopicEvent{Topic: consensusTopic, PubSub: false}) return nil @@ -986,3 +994,7 @@ func (vp *ValidatorPool) dissolve(epoch uint64, eventMux *event.TypeMux) { eventMux.Post(cbfttypes.ExpiredGroupTopicEvent{Topic: groupTopic}) // for pubsub eventMux.Post(cbfttypes.ExpiredGroupTopicEvent{Topic: consensusTopic}) // for pubsub } + +func (vp *ValidatorPool) GetAwaitingTopicEvent() map[string][]*enode.Node { + return vp.awaitingTopicEvent +} diff --git a/consensus/consensus.go b/consensus/consensus.go index 4fd6a34af1..5fd8f11b62 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -201,4 +201,6 @@ type Bft interface { // NodeID is temporary. Node() *enode.Node + + GetAwaitingTopicEvent() map[string][]*enode.Node } diff --git a/eth/backend.go b/eth/backend.go index 5a15acc14a..2c307f7c00 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -344,7 +344,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { return nil, errors.New("Failed to init cbft consensus engine") } } else { - log.Crit("engin not good") + log.Crit("Unsupported consensus engine") } // Permit the downloader to use the trie cache allowance during fast sync @@ -590,6 +590,11 @@ func (s *Ethereum) Start() error { } } s.StartMining() + // Since the p2pServer has not been initialized, the topic event notification will be performed at this time. + event := cbftEngine.GetAwaitingTopicEvent() + for topic, nodes := range event { + s.p2pServer.SetPeers(topic, nodes) + } } s.p2pServer.StartWatching(s.eventMux) diff --git a/p2p/server.go b/p2p/server.go index 7148f06d06..3658a11084 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -1386,7 +1386,7 @@ func (srv *Server) watching() { srv.log.Trace("Received RemoveValidatorEvent", "nodeID", data.Node.ID().String()) srv.RemoveConsensusPeer(data.Node) case cbfttypes.NewTopicEvent: - srv.setPeers(data.Topic, data.Nodes) + srv.SetPeers(data.Topic, data.Nodes) consensusPeers := make(map[enode.ID]struct{}) for _, nodes := range srv.getAllPeers() { for _, node := range nodes { @@ -1449,7 +1449,7 @@ func (srv *Server) getPeers(topic string) []*enode.Node { return copyPeers } -func (srv *Server) setPeers(topic string, peers []*enode.Node) { +func (srv *Server) SetPeers(topic string, peers []*enode.Node) { srv.topicSubscriberMu.Lock() defer srv.topicSubscriberMu.Unlock() copyPeers := make([]*enode.Node, len(peers)) From f014f38cc947f463c9d76d35debb5a73c8a25d5a Mon Sep 17 00:00:00 2001 From: luowei Date: Wed, 9 Mar 2022 15:43:59 +0800 Subject: [PATCH 288/299] Remove validation --- p2p/pubsub/pubsub.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index ef071efe69..9c7ee5a27f 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -1104,9 +1104,9 @@ func (p *PubSub) pushMsg(msg *Message) { return } - if !p.val.Push(src.ID(), msg) { + /*if !p.val.Push(src.ID(), msg) { return - } + }*/ if p.markSeen(id) { p.publishMessage(msg) From ff0f49ba57bbc8da2643320ba94db21276a967ac Mon Sep 17 00:00:00 2001 From: luowei Date: Wed, 9 Mar 2022 16:34:51 +0800 Subject: [PATCH 289/299] fix validation --- p2p/pubsub/pubsub.go | 4 ++-- p2p/pubsub/validation.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/p2p/pubsub/pubsub.go b/p2p/pubsub/pubsub.go index 9c7ee5a27f..ef071efe69 100644 --- a/p2p/pubsub/pubsub.go +++ b/p2p/pubsub/pubsub.go @@ -1104,9 +1104,9 @@ func (p *PubSub) pushMsg(msg *Message) { return } - /*if !p.val.Push(src.ID(), msg) { + if !p.val.Push(src.ID(), msg) { return - }*/ + } if p.markSeen(id) { p.publishMessage(msg) diff --git a/p2p/pubsub/validation.go b/p2p/pubsub/validation.go index 8b6fa023ad..70b1f2e551 100644 --- a/p2p/pubsub/validation.go +++ b/p2p/pubsub/validation.go @@ -232,7 +232,7 @@ func (v *validation) PushLocal(msg *Message) error { func (v *validation) Push(src enode.ID, msg *Message) bool { vals := v.getValidators(msg) - if len(vals) > 0 || msg.Signature != nil { + if len(vals) > 0 || len(msg.Signature) > 0 { select { case v.validateQ <- &validateReq{vals, src, msg}: default: From 82a42f0d0290a06391969b8734cfdfb305b0819b Mon Sep 17 00:00:00 2001 From: luowei Date: Thu, 10 Mar 2022 16:42:45 +0800 Subject: [PATCH 290/299] Support remote trace --- cmd/alaya/main.go | 1 + cmd/alaya/usage.go | 1 + cmd/utils/flags.go | 10 +++++++++- p2p/pubsub/tracer.go | 23 +++++++++++++++++++++-- p2p/pubsub_server.go | 8 +++++++- p2p/server.go | 4 ++++ 6 files changed, 43 insertions(+), 4 deletions(-) diff --git a/cmd/alaya/main.go b/cmd/alaya/main.go index 253066f22a..82759c6d6c 100644 --- a/cmd/alaya/main.go +++ b/cmd/alaya/main.go @@ -93,6 +93,7 @@ var ( utils.MaxConsensusPeersFlag, utils.MaxPendingPeersFlag, utils.MinimumPeersPerTopic, + utils.PubSubTraceHost, utils.MinerGasPriceFlag, utils.NATFlag, utils.NoDiscoverFlag, diff --git a/cmd/alaya/usage.go b/cmd/alaya/usage.go index bcce3fc90d..4efb3d4cc1 100644 --- a/cmd/alaya/usage.go +++ b/cmd/alaya/usage.go @@ -155,6 +155,7 @@ var AppHelpFlagGroups = []flagGroup{ utils.MaxConsensusPeersFlag, utils.MaxPendingPeersFlag, utils.MinimumPeersPerTopic, + utils.PubSubTraceHost, utils.NATFlag, utils.NoDiscoverFlag, // utils.DiscoveryV5Flag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index acc35f930d..71e834b481 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -384,7 +384,12 @@ var ( MinimumPeersPerTopic = cli.IntFlag{ Name: "minpeerstopic", Usage: "Minimum number of nodes to maintain the same topic", - Value: 10, + Value: node.DefaultConfig.P2P.MinimumPeersPerTopic, + } + PubSubTraceHost = cli.StringFlag{ + Name: "tracerhost", + Usage: "Remote proxy address, collect trace events of pubSub", + Value: "", } ListenPortFlag = cli.IntFlag{ Name: "port", @@ -886,6 +891,9 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { if ctx.GlobalIsSet(MinimumPeersPerTopic.Name) { cfg.MinimumPeersPerTopic = ctx.GlobalInt(MinimumPeersPerTopic.Name) } + if ctx.GlobalIsSet(PubSubTraceHost.Name) { + cfg.PubSubTraceHost = ctx.GlobalString(PubSubTraceHost.Name) + } if cfg.MaxPeers <= cfg.MaxConsensusPeers { log.Error("MaxPeers is less than MaxConsensusPeers", "MaxPeers", cfg.MaxPeers, "MaxConsensusPeers", cfg.MaxConsensusPeers) Fatalf("MaxPeers is less than MaxConsensusPeers, MaxPeers: %d, MaxConsensusPeers: %d", cfg.MaxPeers, cfg.MaxConsensusPeers) diff --git a/p2p/pubsub/tracer.go b/p2p/pubsub/tracer.go index 0dcbbc85be..abce0d4fbb 100644 --- a/p2p/pubsub/tracer.go +++ b/p2p/pubsub/tracer.go @@ -260,7 +260,7 @@ func (t *RemoteTracer) doWrite() { if err != nil { conn, err = t.openConn() if err != nil { - log.Error("error opening remote tracer stream", "error", err.Error()) + log.Error("error opening remote tracer stream", "error", err) return } @@ -270,7 +270,26 @@ func (t *RemoteTracer) doWrite() { } func (t *RemoteTracer) openConn() (net.Conn, error) { - return t.dial.DialContext(t.ctx, "tcp", t.remoteHost) + for { + ctx, cancel := context.WithTimeout(t.ctx, time.Minute) + conn, err := t.dial.DialContext(ctx, "tcp", t.remoteHost) + cancel() + if err != nil { + if t.ctx.Err() != nil { + return nil, err + } + + // wait a minute and try again, to account for transient server downtime + select { + case <-time.After(time.Minute): + continue + case <-t.ctx.Done(): + return nil, t.ctx.Err() + } + } + + return conn, nil + } } var _ EventTracer = (*RemoteTracer)(nil) diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go index 4a7bcd84ea..15f59e5484 100644 --- a/p2p/pubsub_server.go +++ b/p2p/pubsub_server.go @@ -32,7 +32,13 @@ type PubSubServer struct { func NewPubSubServer(ctx context.Context, localNode *enode.Node, p2pServer *Server) *PubSubServer { network := NewNetwork(p2pServer.Peers) host := NewHost(localNode, network) - gossipSub, err := pubsub.NewGossipSub(ctx, host) + // If the tracer proxy address is specified, the remote tracer function will be enabled. + tracers := make([]pubsub.Option, 0) + if p2pServer.PubSubTraceHost != "" { + remoteTracer, _ := pubsub.NewRemoteTracer(ctx, p2pServer.PubSubTraceHost) + tracers = append(tracers, pubsub.WithEventTracer(remoteTracer)) + } + gossipSub, err := pubsub.NewGossipSub(ctx, host, tracers...) if err != nil { panic("Failed to NewGossipSub: " + err.Error()) } diff --git a/p2p/server.go b/p2p/server.go index 3658a11084..c0868fd1f6 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -176,7 +176,11 @@ type Config struct { clock mclock.Clock + // The minimum number of connections to keep under each topic. MinimumPeersPerTopic int + + // Broker address for collecting pubSub trace messages + PubSubTraceHost string } // Server manages all peer connections. From 702c7e31a081e9867bf31153afab34d8fb0ed967 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Fri, 11 Mar 2022 18:09:22 +0800 Subject: [PATCH 291/299] Modified the sending logic of group aggregation signatures --- consensus/cbft/protocols/protocol.go | 2 +- consensus/cbft/rg_broadcast.go | 56 ++++++++++++++++++---------- consensus/cbft/state/state_test.go | 2 +- core/cbfttypes/type.go | 5 ++- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/consensus/cbft/protocols/protocol.go b/consensus/cbft/protocols/protocol.go index 0bc3de7ce4..e1241c635d 100644 --- a/consensus/cbft/protocols/protocol.go +++ b/consensus/cbft/protocols/protocol.go @@ -944,7 +944,7 @@ type RGBlockQuorumCert struct { } func (rgb *RGBlockQuorumCert) String() string { - return fmt.Sprintf("{GroupID:%d,Epoch:%d,ViewNumber:%d,BlockIndx:%d,Hash:%s,Number:%d,ValidatorIndex:%d,Signature:%s,ValidatorSetLen:%d}", + return fmt.Sprintf("{GroupID:%d,Epoch:%d,ViewNumber:%d,BlockIndex:%d,Hash:%s,Number:%d,ValidatorIndex:%d,Signature:%s,ValidatorSetLen:%d}", rgb.GroupID, rgb.EpochNum(), rgb.ViewNum(), rgb.BlockIndx(), rgb.BHash().TerminalString(), rgb.BlockNum(), rgb.NodeIndex(), rgb.Signature.String(), rgb.BlockQC.ValidatorSet.HasLength()) } diff --git a/consensus/cbft/rg_broadcast.go b/consensus/cbft/rg_broadcast.go index f36d90bb9d..6ee6dfc735 100644 --- a/consensus/cbft/rg_broadcast.go +++ b/consensus/cbft/rg_broadcast.go @@ -2,6 +2,7 @@ package cbft import ( "github.com/AlayaNetwork/Alaya-Go/consensus/cbft/protocols" + "github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" "reflect" "sync" "time" @@ -218,20 +219,27 @@ func (m *RGBroadcastManager) allowRGQuorumCert(a awaiting) bool { return true } -func (m *RGBroadcastManager) upgradeCoordinator(a awaiting) bool { - // Check whether the current node is the first group member +func (m *RGBroadcastManager) upgradeCoordinator(a awaiting) (bool, *cbfttypes.ValidateNode) { + // Check whether the current node is the validator + node, err := m.cbft.isCurrentValidator() + if err != nil || node == nil { + m.cbft.log.Debug("Current node is not validator, no need to send RGQuorumCert") + return false, nil + } + + // Check whether the current node is the group member groupID, unitID, err := m.cbft.getGroupByValidatorID(m.cbft.state.Epoch(), m.cbft.Node().ID()) if err != nil || groupID != a.GroupID() { - return false - } - if unitID == defaultUnitID { // the first echelon, Send by default - return true + return false, nil } + //if unitID == defaultUnitID { // the first echelon, Send by default + // return true + //} coordinatorIndexes, err := m.cbft.validatorPool.GetCoordinatorIndexesByGroupID(m.cbft.state.Epoch(), groupID) if err != nil || len(coordinatorIndexes) <= 0 { m.cbft.log.Error("Get coordinator indexes by groupID error") - return false + return false, nil } m.cbft.log.Trace("CoordinatorIndexes", "groupID", groupID, "unitID", unitID, "coordinatorIndexes", coordinatorIndexes) @@ -239,19 +247,32 @@ func (m *RGBroadcastManager) upgradeCoordinator(a awaiting) bool { switch msg := a.(type) { case *awaitingRGBlockQC: + // Query the QuorumCert with the largest number of signatures in the current group + blockQC, _ := m.cbft.state.FindMaxGroupRGQuorumCert(msg.blockIndex, msg.GroupID()) + if blockQC == nil { + m.cbft.log.Error("Cannot find the RGBlockQuorumCert of the current group", "blockIndex", msg.blockIndex, "groupID", msg.GroupID()) + return false, nil + } + // If the block is already QC, there is no need to continue sending RGBlockQuorumCert + if m.cbft.blockTree.FindBlockByHash(blockQC.BlockHash) != nil || blockQC.BlockNumber <= m.cbft.state.HighestLockBlock().NumberU64() { + m.cbft.log.Debug("The block is already QC, no need to send RGBlockQuorumCert", "blockIndex", msg.blockIndex, "blockNumber", blockQC.BlockNumber, "blockHash", blockQC.BlockHash, "groupID", msg.GroupID()) + return false, nil + } receiveIndexes = m.cbft.state.RGBlockQuorumCertsIndexes(msg.blockIndex, groupID) case *awaitingRGViewQC: receiveIndexes = m.cbft.state.RGViewChangeQuorumCertsIndexes(groupID) default: - return false + return false, nil } if !m.enoughCoordinator(groupID, unitID, coordinatorIndexes, receiveIndexes) { - m.cbft.log.Warn("Upgrade the current node to Coordinator", "type", reflect.TypeOf(a), "groupID", groupID, "unitID", unitID, "blockIndex", a.Index(), "coordinatorIndexes", coordinatorIndexes, "receiveIndexes", receiveIndexes) - m.recordUpgradeCoordinatorMetrics(a) - return true + if unitID > defaultUnitID { + m.cbft.log.Warn("Upgrade the current node to coordinator", "type", reflect.TypeOf(a), "groupID", groupID, "unitID", unitID, "blockIndex", a.Index(), "nodeIndex", node.Index, "coordinatorIndexes", coordinatorIndexes, "receiveIndexes", receiveIndexes) + m.recordUpgradeCoordinatorMetrics(a) + } + return true, node } - m.cbft.log.Debug("Enough Coordinator, no need to upgrade to Coordinator", "groupID", groupID, "unitID", unitID, "coordinatorIndexes", coordinatorIndexes, "receiveIndexes", receiveIndexes) - return false + m.cbft.log.Debug("Enough coordinator, no need to upgrade to coordinator", "type", reflect.TypeOf(a), "groupID", groupID, "unitID", unitID, "blockIndex", a.Index(), "nodeIndex", node.Index, "coordinatorIndexes", coordinatorIndexes, "receiveIndexes", receiveIndexes) + return false, nil } func (m *RGBroadcastManager) recordUpgradeCoordinatorMetrics(a awaiting) { @@ -307,13 +328,8 @@ func (m *RGBroadcastManager) broadcastFunc(a awaiting) { return } - if !m.upgradeCoordinator(a) { - return - } - - node, err := m.cbft.isCurrentValidator() - if err != nil || node == nil { - m.cbft.log.Debug("Current node is not validator, no need to send RGQuorumCert") + upgrade, node := m.upgradeCoordinator(a) + if !upgrade { return } diff --git a/consensus/cbft/state/state_test.go b/consensus/cbft/state/state_test.go index 9303203c67..8faddefa2f 100644 --- a/consensus/cbft/state/state_test.go +++ b/consensus/cbft/state/state_test.go @@ -444,7 +444,7 @@ func TestViewRGViewChangeQuorumCerts(t *testing.T) { assert.NotNil(t, rg) assert.Equal(t, uint32(12), rg.ValidatorIndex) - assert.Equal(t, []uint32{22, 23}, v.RGViewChangeQuorumCertsIndexes(2)) + assert.Equal(t, 2, len(v.RGViewChangeQuorumCertsIndexes(2))) assert.Equal(t, 4, v.FindMaxRGViewChangeQuorumCert(2).ViewChangeQC.HasLength()) } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index abae4f9a36..f05380d77f 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -346,7 +346,10 @@ func (vs *Validators) GetGroupValidators(nodeID enode.ID) (*GroupValidators, err break } } - return ret, nil + if ret != nil { + return ret, nil + } + return nil, errors.New("not found the specified validators") } func (vs *Validators) UnitID(nodeID enode.ID) (uint32, error) { From 9197b9c51f4130e6d415b99807305a01b7050ec3 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Fri, 11 Mar 2022 18:51:26 +0800 Subject: [PATCH 292/299] fix unittest --- consensus/cbft/consensus_process_rg_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/consensus/cbft/consensus_process_rg_test.go b/consensus/cbft/consensus_process_rg_test.go index 2203756883..6d0050acc5 100644 --- a/consensus/cbft/consensus_process_rg_test.go +++ b/consensus/cbft/consensus_process_rg_test.go @@ -99,8 +99,9 @@ func TestRGBlockQuorumCert(t *testing.T) { time.Sleep(5 * time.Second) assert.NotNil(t, nodes[0].engine.state.FindRGBlockQuorumCerts(blockIndex, groupID, uint32(validatorIndex))) - assert.Equal(t, 2, nodes[0].engine.state.RGBlockQuorumCertsLen(blockIndex, groupID)) - assert.Equal(t, 2, len(nodes[0].engine.state.RGBlockQuorumCertsIndexes(blockIndex, groupID))) + // The block is already QC, so node-0 need not to SendRGBlockQuorumCert + assert.Equal(t, 1, nodes[0].engine.state.RGBlockQuorumCertsLen(blockIndex, groupID)) + assert.Equal(t, 1, len(nodes[0].engine.state.RGBlockQuorumCertsIndexes(blockIndex, groupID))) assert.True(t, true, nodes[0].engine.state.HadSendRGBlockQuorumCerts(blockIndex)) assert.Equal(t, 1, nodes[0].engine.state.SelectRGQuorumCertsLen(blockIndex, groupID)) From 426f2b2043a4621b90355ef0092966398a8569fc Mon Sep 17 00:00:00 2001 From: clearly <910372762@qq.com> Date: Mon, 14 Mar 2022 12:02:02 +0800 Subject: [PATCH 293/299] only self dial conn can remove peers --- p2p/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/server.go b/p2p/server.go index 7148f06d06..a3d24bb1a5 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -976,7 +976,7 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in } } disconnectConsensus := 0 - if srv.consensus && c.is(consensusDialedConn) && numConsensusPeer >= srv.MaxConsensusPeers { + if srv.consensus && c.is(consensusDialedConn) && !c.is(inboundConn) && numConsensusPeer >= srv.MaxConsensusPeers { topicPeers := srv.getAllPeers() if len(topicPeers) > 0 { maxPeersTopic := "" From 41b8b1fd964f94ee542114ae86366899c5d2e619 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Mon, 14 Mar 2022 16:50:22 +0800 Subject: [PATCH 294/299] Adjust the order of topic events notified to the p2pserver when the node starts --- consensus/bft_mock.go | 4 ---- consensus/cbft/cbft.go | 4 ---- consensus/cbft/consensus_process_rg_test.go | 2 +- consensus/cbft/network/pubsub.go | 4 ++++ consensus/cbft/validator/validator.go | 16 +++++----------- consensus/consensus.go | 2 -- core/cbfttypes/type.go | 1 + eth/backend.go | 5 ----- p2p/pubsub_server.go | 4 ++++ 9 files changed, 15 insertions(+), 27 deletions(-) diff --git a/consensus/bft_mock.go b/consensus/bft_mock.go index 7ba7fa5c13..aa67cb3c35 100644 --- a/consensus/bft_mock.go +++ b/consensus/bft_mock.go @@ -146,10 +146,6 @@ func (bm *BftMock) Node() *enode.Node { return enode.NewV4(&privateKey.PublicKey, nil, 0, 0) } -func (bm *BftMock) GetAwaitingTopicEvent() map[string][]*enode.Node { - return nil -} - // Author retrieves the Ethereum address of the account that minted the given // block, which may be different from the header's coinbase if a consensus // engine is based on signatures. diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 9b4aa48288..11d00fa3d7 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -2116,10 +2116,6 @@ func (cbft *Cbft) Node() *enode.Node { return cbft.config.Option.Node } -func (cbft *Cbft) GetAwaitingTopicEvent() map[string][]*enode.Node { - return cbft.validatorPool.GetAwaitingTopicEvent() -} - func (cbft *Cbft) avgRTT() time.Duration { produceInterval := time.Duration(cbft.config.Sys.Period/uint64(cbft.config.Sys.Amount)) * time.Millisecond rtt := cbft.AvgLatency() * 2 diff --git a/consensus/cbft/consensus_process_rg_test.go b/consensus/cbft/consensus_process_rg_test.go index 6d0050acc5..7e41c61095 100644 --- a/consensus/cbft/consensus_process_rg_test.go +++ b/consensus/cbft/consensus_process_rg_test.go @@ -501,7 +501,7 @@ func TestRGViewChangeQuorumCert(t *testing.T) { msg := &protocols.RGViewChangeQuorumCert{ GroupID: groupID, ViewChangeQC: rgqc, - ValidatorIndex: uint32(validatorIndex), + ValidatorIndex: validatorIndex, PrepareQCs: &ctypes.PrepareQCs{ QCs: []*ctypes.QuorumCert{qc}, }, diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index 51e7d61e12..97e42aa87b 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -149,6 +149,10 @@ func (ps *PubSub) watching(eventMux *event.TypeMux) { switch data := ev.Data.(type) { case cbfttypes.GroupTopicEvent: log.Trace("Received GroupTopicEvent", "topic", data.Topic) + // 需要通知 p2pServer topic 节点 + if len(data.Nodes) > 0 { + ps.pss.SetPeers(data.Topic, data.Nodes) + } // 需要订阅主题(发现节点并接收topic消息) if data.PubSub { ps.Subscribe(data.Topic) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index 2e3be0c559..b24a3c363d 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -332,8 +332,6 @@ type ValidatorPool struct { prevValidators *cbfttypes.Validators // Previous round validators currentValidators *cbfttypes.Validators // Current round validators nextValidators *cbfttypes.Validators // Next round validators, to Post Pub event - - awaitingTopicEvent map[string][]*enode.Node } // NewValidatorPool new a validator pool. @@ -345,7 +343,6 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID grouped: needGroup, groupValidatorsLimit: xcom.MaxGroupValidators(), coordinatorLimit: xcom.CoordinatorsLimit(), - awaitingTopicEvent: make(map[string][]*enode.Node), } lastNumber := agency.GetLastNumber(blockNumber) // FIXME: Check `GetValidators` return error @@ -964,14 +961,15 @@ func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64 groupTopic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) if init { - vp.awaitingTopicEvent[consensusTopic] = otherConsensusNodes - vp.awaitingTopicEvent[groupTopic] = groupNodes + eventMux.Post(cbfttypes.GroupTopicEvent{Topic: groupTopic, PubSub: true, Nodes: groupNodes}) + eventMux.Post(cbfttypes.GroupTopicEvent{Topic: consensusTopic, PubSub: false, Nodes: otherConsensusNodes}) } else { eventMux.Post(cbfttypes.NewTopicEvent{Topic: consensusTopic, Nodes: otherConsensusNodes}) eventMux.Post(cbfttypes.NewTopicEvent{Topic: groupTopic, Nodes: groupNodes}) + eventMux.Post(cbfttypes.GroupTopicEvent{Topic: groupTopic, PubSub: true}) + eventMux.Post(cbfttypes.GroupTopicEvent{Topic: consensusTopic, PubSub: false}) } - eventMux.Post(cbfttypes.GroupTopicEvent{Topic: groupTopic, PubSub: true}) - eventMux.Post(cbfttypes.GroupTopicEvent{Topic: consensusTopic, PubSub: false}) + return nil } @@ -994,7 +992,3 @@ func (vp *ValidatorPool) dissolve(epoch uint64, eventMux *event.TypeMux) { eventMux.Post(cbfttypes.ExpiredGroupTopicEvent{Topic: groupTopic}) // for pubsub eventMux.Post(cbfttypes.ExpiredGroupTopicEvent{Topic: consensusTopic}) // for pubsub } - -func (vp *ValidatorPool) GetAwaitingTopicEvent() map[string][]*enode.Node { - return vp.awaitingTopicEvent -} diff --git a/consensus/consensus.go b/consensus/consensus.go index 5fd8f11b62..4fd6a34af1 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -201,6 +201,4 @@ type Bft interface { // NodeID is temporary. Node() *enode.Node - - GetAwaitingTopicEvent() map[string][]*enode.Node } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index f05380d77f..bc318713e2 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -84,6 +84,7 @@ type ExpiredTopicEvent struct { type GroupTopicEvent struct { Topic string // consensus:{epoch}:{groupID} PubSub bool //是否需要pubsub + Nodes []*enode.Node } type ExpiredGroupTopicEvent ExpiredTopicEvent // consensus:{epoch}:{groupID} diff --git a/eth/backend.go b/eth/backend.go index 2c307f7c00..f555d732ea 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -590,11 +590,6 @@ func (s *Ethereum) Start() error { } } s.StartMining() - // Since the p2pServer has not been initialized, the topic event notification will be performed at this time. - event := cbftEngine.GetAwaitingTopicEvent() - for topic, nodes := range event { - s.p2pServer.SetPeers(topic, nodes) - } } s.p2pServer.StartWatching(s.eventMux) diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go index 4a7bcd84ea..a74d05c713 100644 --- a/p2p/pubsub_server.go +++ b/p2p/pubsub_server.go @@ -78,3 +78,7 @@ func (pss *PubSubServer) GetAllPubSubStatus() *pubsub.Status { func (pss *PubSubServer) GetPeerInfo(nodeId enode.ID) *pubsub.PeerInfo { return pss.pubSub.GetPeerInfo(nodeId) } + +func (pss *PubSubServer) SetPeers(topic string, peers []*enode.Node) { + pss.p2pServer.SetPeers(topic, peers) +} From 8d639ccefcc2bc002366f7539a032dd6f57aa608 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Mon, 14 Mar 2022 18:17:59 +0800 Subject: [PATCH 295/299] Adjust the order of topic events notified to the p2pserver when the node starts --- consensus/bft_mock.go | 6 +++++- consensus/cbft/cbft.go | 4 ++++ consensus/cbft/network/pubsub.go | 4 ---- consensus/cbft/validator/validator.go | 13 +++++++++++-- consensus/consensus.go | 2 ++ core/cbfttypes/type.go | 1 - eth/backend.go | 14 ++++++++++++++ p2p/pubsub_server.go | 4 ---- 8 files changed, 36 insertions(+), 12 deletions(-) diff --git a/consensus/bft_mock.go b/consensus/bft_mock.go index 67cb7b9a4b..99edef96c1 100644 --- a/consensus/bft_mock.go +++ b/consensus/bft_mock.go @@ -23,9 +23,9 @@ import ( "math/big" "time" - "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/ethdb" "github.com/AlayaNetwork/Alaya-Go/event" + "github.com/AlayaNetwork/Alaya-Go/p2p/enode" "github.com/AlayaNetwork/Alaya-Go/common/consensus" "github.com/AlayaNetwork/Alaya-Go/crypto" @@ -158,6 +158,10 @@ func (bm *BftMock) Node() *enode.Node { return enode.NewV4(&privateKey.PublicKey, nil, 0, 0) } +func (bm *BftMock) GetAwaitingTopicEvent() map[interface{}]interface{} { + return nil +} + // Author retrieves the Ethereum address of the account that minted the given // block, which may be different from the header's coinbase if a consensus // engine is based on signatures. diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 11d00fa3d7..5591b42281 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -2116,6 +2116,10 @@ func (cbft *Cbft) Node() *enode.Node { return cbft.config.Option.Node } +func (cbft *Cbft) GetAwaitingTopicEvent() map[interface{}]interface{} { + return cbft.validatorPool.GetAwaitingTopicEvent() +} + func (cbft *Cbft) avgRTT() time.Duration { produceInterval := time.Duration(cbft.config.Sys.Period/uint64(cbft.config.Sys.Amount)) * time.Millisecond rtt := cbft.AvgLatency() * 2 diff --git a/consensus/cbft/network/pubsub.go b/consensus/cbft/network/pubsub.go index 97e42aa87b..51e7d61e12 100644 --- a/consensus/cbft/network/pubsub.go +++ b/consensus/cbft/network/pubsub.go @@ -149,10 +149,6 @@ func (ps *PubSub) watching(eventMux *event.TypeMux) { switch data := ev.Data.(type) { case cbfttypes.GroupTopicEvent: log.Trace("Received GroupTopicEvent", "topic", data.Topic) - // 需要通知 p2pServer topic 节点 - if len(data.Nodes) > 0 { - ps.pss.SetPeers(data.Topic, data.Nodes) - } // 需要订阅主题(发现节点并接收topic消息) if data.PubSub { ps.Subscribe(data.Topic) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index b24a3c363d..c11bd3989f 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -332,6 +332,8 @@ type ValidatorPool struct { prevValidators *cbfttypes.Validators // Previous round validators currentValidators *cbfttypes.Validators // Current round validators nextValidators *cbfttypes.Validators // Next round validators, to Post Pub event + + awaitingTopicEvent map[interface{}]interface{} } // NewValidatorPool new a validator pool. @@ -343,6 +345,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID grouped: needGroup, groupValidatorsLimit: xcom.MaxGroupValidators(), coordinatorLimit: xcom.CoordinatorsLimit(), + awaitingTopicEvent: make(map[interface{}]interface{}), } lastNumber := agency.GetLastNumber(blockNumber) // FIXME: Check `GetValidators` return error @@ -961,8 +964,10 @@ func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64 groupTopic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) if init { - eventMux.Post(cbfttypes.GroupTopicEvent{Topic: groupTopic, PubSub: true, Nodes: groupNodes}) - eventMux.Post(cbfttypes.GroupTopicEvent{Topic: consensusTopic, PubSub: false, Nodes: otherConsensusNodes}) + vp.awaitingTopicEvent[cbfttypes.NewTopicEvent{Topic: consensusTopic, Nodes: otherConsensusNodes}] = struct{}{} + vp.awaitingTopicEvent[cbfttypes.NewTopicEvent{Topic: groupTopic, Nodes: groupNodes}] = struct{}{} + vp.awaitingTopicEvent[cbfttypes.GroupTopicEvent{Topic: groupTopic, PubSub: true}] = struct{}{} + vp.awaitingTopicEvent[cbfttypes.GroupTopicEvent{Topic: consensusTopic, PubSub: false}] = struct{}{} } else { eventMux.Post(cbfttypes.NewTopicEvent{Topic: consensusTopic, Nodes: otherConsensusNodes}) eventMux.Post(cbfttypes.NewTopicEvent{Topic: groupTopic, Nodes: groupNodes}) @@ -992,3 +997,7 @@ func (vp *ValidatorPool) dissolve(epoch uint64, eventMux *event.TypeMux) { eventMux.Post(cbfttypes.ExpiredGroupTopicEvent{Topic: groupTopic}) // for pubsub eventMux.Post(cbfttypes.ExpiredGroupTopicEvent{Topic: consensusTopic}) // for pubsub } + +func (vp *ValidatorPool) GetAwaitingTopicEvent() map[interface{}]interface{} { + return vp.awaitingTopicEvent +} diff --git a/consensus/consensus.go b/consensus/consensus.go index 4fd6a34af1..3792be5444 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -201,4 +201,6 @@ type Bft interface { // NodeID is temporary. Node() *enode.Node + + GetAwaitingTopicEvent() map[interface{}]interface{} } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index bc318713e2..f05380d77f 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -84,7 +84,6 @@ type ExpiredTopicEvent struct { type GroupTopicEvent struct { Topic string // consensus:{epoch}:{groupID} PubSub bool //是否需要pubsub - Nodes []*enode.Node } type ExpiredGroupTopicEvent ExpiredTopicEvent // consensus:{epoch}:{groupID} diff --git a/eth/backend.go b/eth/backend.go index f555d732ea..5ef8db20c3 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -20,6 +20,7 @@ package eth import ( "errors" "fmt" + "github.com/AlayaNetwork/Alaya-Go/core/cbfttypes" "math/big" "os" "sync" @@ -590,6 +591,19 @@ func (s *Ethereum) Start() error { } } s.StartMining() + // Since the p2pServer has not been initialized, the topic event notification will be performed at this time. + event := cbftEngine.GetAwaitingTopicEvent() + for ev, _ := range event { + switch e := ev.(type) { + case cbfttypes.NewTopicEvent: + log.Debug("AwaitingTopicEvent, NewTopicEvent", "topic", e.Topic, "nodes", len(e.nodes)) + s.eventMux.Post(e) + case cbfttypes.GroupTopicEvent: + log.Debug("AwaitingTopicEvent, GroupTopicEvent", "topic", e.Topic, "pubSub", e.PubSub) + s.eventMux.Post(e) + default: + } + } } s.p2pServer.StartWatching(s.eventMux) diff --git a/p2p/pubsub_server.go b/p2p/pubsub_server.go index a74d05c713..4a7bcd84ea 100644 --- a/p2p/pubsub_server.go +++ b/p2p/pubsub_server.go @@ -78,7 +78,3 @@ func (pss *PubSubServer) GetAllPubSubStatus() *pubsub.Status { func (pss *PubSubServer) GetPeerInfo(nodeId enode.ID) *pubsub.PeerInfo { return pss.pubSub.GetPeerInfo(nodeId) } - -func (pss *PubSubServer) SetPeers(topic string, peers []*enode.Node) { - pss.p2pServer.SetPeers(topic, peers) -} From c718204be30b998d9723e624f5c983837aa9094d Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Mon, 14 Mar 2022 18:20:58 +0800 Subject: [PATCH 296/299] Adjust the order of topic events notified to the p2pserver when the node starts --- eth/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/backend.go b/eth/backend.go index 5ef8db20c3..03079979c4 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -596,7 +596,7 @@ func (s *Ethereum) Start() error { for ev, _ := range event { switch e := ev.(type) { case cbfttypes.NewTopicEvent: - log.Debug("AwaitingTopicEvent, NewTopicEvent", "topic", e.Topic, "nodes", len(e.nodes)) + log.Debug("AwaitingTopicEvent, NewTopicEvent", "topic", e.Topic, "nodes", len(e.Nodes)) s.eventMux.Post(e) case cbfttypes.GroupTopicEvent: log.Debug("AwaitingTopicEvent, GroupTopicEvent", "topic", e.Topic, "pubSub", e.PubSub) From 818406df594e8bf0f127ba38598ad10a77b2f64c Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Tue, 15 Mar 2022 10:20:50 +0800 Subject: [PATCH 297/299] Adjust the order of topic events notified to the p2pserver when the node starts --- consensus/bft_mock.go | 2 +- consensus/cbft/cbft.go | 2 +- consensus/cbft/validator/validator.go | 12 +++++------- consensus/consensus.go | 2 +- core/cbfttypes/type.go | 11 +++++++++-- eth/backend.go | 20 +++++++++++--------- 6 files changed, 28 insertions(+), 21 deletions(-) diff --git a/consensus/bft_mock.go b/consensus/bft_mock.go index 99edef96c1..5ac6060c57 100644 --- a/consensus/bft_mock.go +++ b/consensus/bft_mock.go @@ -158,7 +158,7 @@ func (bm *BftMock) Node() *enode.Node { return enode.NewV4(&privateKey.PublicKey, nil, 0, 0) } -func (bm *BftMock) GetAwaitingTopicEvent() map[interface{}]interface{} { +func (bm *BftMock) GetAwaitingTopicEvent() map[int]cbfttypes.TopicEvent { return nil } diff --git a/consensus/cbft/cbft.go b/consensus/cbft/cbft.go index 5591b42281..73b4a58e94 100644 --- a/consensus/cbft/cbft.go +++ b/consensus/cbft/cbft.go @@ -2116,7 +2116,7 @@ func (cbft *Cbft) Node() *enode.Node { return cbft.config.Option.Node } -func (cbft *Cbft) GetAwaitingTopicEvent() map[interface{}]interface{} { +func (cbft *Cbft) GetAwaitingTopicEvent() map[int]cbfttypes.TopicEvent { return cbft.validatorPool.GetAwaitingTopicEvent() } diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index c11bd3989f..a23f430722 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -333,7 +333,7 @@ type ValidatorPool struct { currentValidators *cbfttypes.Validators // Current round validators nextValidators *cbfttypes.Validators // Next round validators, to Post Pub event - awaitingTopicEvent map[interface{}]interface{} + awaitingTopicEvent map[int]cbfttypes.TopicEvent } // NewValidatorPool new a validator pool. @@ -345,7 +345,7 @@ func NewValidatorPool(agency consensus.Agency, blockNumber, epoch uint64, nodeID grouped: needGroup, groupValidatorsLimit: xcom.MaxGroupValidators(), coordinatorLimit: xcom.CoordinatorsLimit(), - awaitingTopicEvent: make(map[interface{}]interface{}), + awaitingTopicEvent: make(map[int]cbfttypes.TopicEvent), } lastNumber := agency.GetLastNumber(blockNumber) // FIXME: Check `GetValidators` return error @@ -964,10 +964,8 @@ func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64 groupTopic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) if init { - vp.awaitingTopicEvent[cbfttypes.NewTopicEvent{Topic: consensusTopic, Nodes: otherConsensusNodes}] = struct{}{} - vp.awaitingTopicEvent[cbfttypes.NewTopicEvent{Topic: groupTopic, Nodes: groupNodes}] = struct{}{} - vp.awaitingTopicEvent[cbfttypes.GroupTopicEvent{Topic: groupTopic, PubSub: true}] = struct{}{} - vp.awaitingTopicEvent[cbfttypes.GroupTopicEvent{Topic: consensusTopic, PubSub: false}] = struct{}{} + vp.awaitingTopicEvent[cbfttypes.TypeConsensusTopic] = cbfttypes.TopicEvent{consensusTopic, otherConsensusNodes} + vp.awaitingTopicEvent[cbfttypes.TypeGroupTopic] = cbfttypes.TopicEvent{groupTopic, groupNodes} } else { eventMux.Post(cbfttypes.NewTopicEvent{Topic: consensusTopic, Nodes: otherConsensusNodes}) eventMux.Post(cbfttypes.NewTopicEvent{Topic: groupTopic, Nodes: groupNodes}) @@ -998,6 +996,6 @@ func (vp *ValidatorPool) dissolve(epoch uint64, eventMux *event.TypeMux) { eventMux.Post(cbfttypes.ExpiredGroupTopicEvent{Topic: consensusTopic}) // for pubsub } -func (vp *ValidatorPool) GetAwaitingTopicEvent() map[interface{}]interface{} { +func (vp *ValidatorPool) GetAwaitingTopicEvent() map[int]cbfttypes.TopicEvent { return vp.awaitingTopicEvent } diff --git a/consensus/consensus.go b/consensus/consensus.go index 3792be5444..03dee59e8d 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -202,5 +202,5 @@ type Bft interface { // NodeID is temporary. Node() *enode.Node - GetAwaitingTopicEvent() map[interface{}]interface{} + GetAwaitingTopicEvent() map[int]cbfttypes.TopicEvent } diff --git a/core/cbfttypes/type.go b/core/cbfttypes/type.go index f05380d77f..6dfa545506 100644 --- a/core/cbfttypes/type.go +++ b/core/cbfttypes/type.go @@ -41,8 +41,10 @@ import ( ) const ( - TopicConsensus = "consensus:%d" // consensus:{epoch} - TopicGroup = "consensus:%d:%d" // consensus:{epoch}:{groupID} + TopicConsensus = "consensus:%d" // consensus:{epoch} + TopicGroup = "consensus:%d:%d" // consensus:{epoch}:{groupID} + TypeConsensusTopic = 1 + TypeGroupTopic = 2 ) func ConsensusTopicName(epoch uint64) string { @@ -55,6 +57,11 @@ func ConsensusGroupTopicName(epoch uint64, groupID uint32) string { type UpdateChainStateFn func(qcState, lockState, commitState *protocols.State) +type TopicEvent struct { + Topic string + Nodes []*enode.Node +} + type CbftResult struct { Block *types.Block ExtraData []byte diff --git a/eth/backend.go b/eth/backend.go index 03079979c4..b660e69040 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -592,15 +592,17 @@ func (s *Ethereum) Start() error { } s.StartMining() // Since the p2pServer has not been initialized, the topic event notification will be performed at this time. - event := cbftEngine.GetAwaitingTopicEvent() - for ev, _ := range event { - switch e := ev.(type) { - case cbfttypes.NewTopicEvent: - log.Debug("AwaitingTopicEvent, NewTopicEvent", "topic", e.Topic, "nodes", len(e.Nodes)) - s.eventMux.Post(e) - case cbfttypes.GroupTopicEvent: - log.Debug("AwaitingTopicEvent, GroupTopicEvent", "topic", e.Topic, "pubSub", e.PubSub) - s.eventMux.Post(e) + awaiting := cbftEngine.GetAwaitingTopicEvent() + for t, event := range awaiting { + switch t { + case cbfttypes.TypeConsensusTopic: + log.Debug("AwaitingTopicEvent, TypeConsensusTopic", "topic", event.Topic, "nodes", len(event.Nodes)) + s.eventMux.Post(cbfttypes.NewTopicEvent{Topic: event.Topic, Nodes: event.Nodes}) + s.eventMux.Post(cbfttypes.GroupTopicEvent{Topic: event.Topic, PubSub: false}) + case cbfttypes.TypeGroupTopic: + log.Debug("AwaitingTopicEvent, TypeGroupTopic", "topic", event.Topic, "nodes", len(event.Nodes)) + s.eventMux.Post(cbfttypes.NewTopicEvent{Topic: event.Topic, Nodes: event.Nodes}) + s.eventMux.Post(cbfttypes.GroupTopicEvent{Topic: event.Topic, PubSub: true}) default: } } From 1c7e276e44831cf163a193e63c857558c46376ab Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Tue, 15 Mar 2022 11:04:47 +0800 Subject: [PATCH 298/299] Adjust the order of topic events notified to the p2pserver when the node starts --- eth/backend.go | 2 +- node/fake.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index b660e69040..a2c7227f36 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -579,6 +579,7 @@ func (s *Ethereum) Start() error { }*/ // Start the networking layer and the light server if requested s.protocolManager.Start(maxPeers) + s.p2pServer.StartWatching(s.eventMux) //log.Debug("node start", "srvr.Config.PrivateKey", srvr.Config.PrivateKey) if cbftEngine, ok := s.engine.(consensus.Bft); ok { @@ -607,7 +608,6 @@ func (s *Ethereum) Start() error { } } } - s.p2pServer.StartWatching(s.eventMux) return nil } diff --git a/node/fake.go b/node/fake.go index 7c730b4ba2..7016bf4b86 100644 --- a/node/fake.go +++ b/node/fake.go @@ -22,7 +22,7 @@ import ( "github.com/AlayaNetwork/Alaya-Go/p2p/enode" ) -var FakeNetEnable bool = false +var FakeNetEnable = false var indexMock = map[int][]int{ 1: []int{2}, From 84cee7487a56efaf250166ac9633f365d9e649d1 Mon Sep 17 00:00:00 2001 From: niuxiaojie81 <85773309@qq.com> Date: Tue, 15 Mar 2022 11:31:41 +0800 Subject: [PATCH 299/299] resolve composite literal uses unkeyed fields --- consensus/cbft/validator/validator.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/consensus/cbft/validator/validator.go b/consensus/cbft/validator/validator.go index a23f430722..267c0f7e90 100644 --- a/consensus/cbft/validator/validator.go +++ b/consensus/cbft/validator/validator.go @@ -964,8 +964,8 @@ func (vp *ValidatorPool) organize(validators *cbfttypes.Validators, epoch uint64 groupTopic := cbfttypes.ConsensusGroupTopicName(epoch, gvs.GetGroupID()) if init { - vp.awaitingTopicEvent[cbfttypes.TypeConsensusTopic] = cbfttypes.TopicEvent{consensusTopic, otherConsensusNodes} - vp.awaitingTopicEvent[cbfttypes.TypeGroupTopic] = cbfttypes.TopicEvent{groupTopic, groupNodes} + vp.awaitingTopicEvent[cbfttypes.TypeConsensusTopic] = cbfttypes.TopicEvent{Topic: consensusTopic, Nodes: otherConsensusNodes} + vp.awaitingTopicEvent[cbfttypes.TypeGroupTopic] = cbfttypes.TopicEvent{Topic: groupTopic, Nodes: groupNodes} } else { eventMux.Post(cbfttypes.NewTopicEvent{Topic: consensusTopic, Nodes: otherConsensusNodes}) eventMux.Post(cbfttypes.NewTopicEvent{Topic: groupTopic, Nodes: groupNodes})