-
Notifications
You must be signed in to change notification settings - Fork 176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
connecting unstaked AN with the staked AN using DHT for peer discovery #1098
Changes from all commits
0141e0e
36c3423
8b2b501
9b54112
11eaf04
926b059
525ac07
fa880d6
5e94cbe
5f7e4f1
303cb37
822e2fc
5e8aa0e
ff8b012
6fa88fd
0006b24
f3f9dc2
988d98f
44b6132
fb98c6a
75320b8
f1e4f81
d7f7afb
b4494d6
95249d8
9c41474
c4a7186
0b84f70
92ac71b
7813efa
f968f65
fee327c
cd2648f
4681eee
d22b611
8262650
c725e61
9dcc4fd
b49380d
6e7ff1c
f41a22f
11b7ec6
669ce7b
8662509
a650688
d4072e1
21b84d8
999215e
d3254bf
7c62b31
5b1b6cd
279e377
4c7dead
ad849c7
f50fb0b
1930d5c
af519d3
43fbdaf
370b4bb
26b0c27
b72ea56
8385ceb
7f16e42
5ecf542
bc9334f
ff47c7e
5aa84f5
0f77b68
c272ae0
790034b
f5c6f0b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,17 +2,18 @@ package node_builder | |
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"strings" | ||
"time" | ||
|
||
dht "github.com/libp2p/go-libp2p-kad-dht" | ||
"github.com/onflow/flow/protobuf/go/flow/access" | ||
"github.com/spf13/pflag" | ||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/credentials" | ||
|
||
"github.com/onflow/flow-go/cmd" | ||
"github.com/onflow/flow-go/cmd/build" | ||
"github.com/onflow/flow-go/consensus" | ||
"github.com/onflow/flow-go/consensus/hotstuff" | ||
"github.com/onflow/flow-go/consensus/hotstuff/committees" | ||
|
@@ -42,6 +43,7 @@ import ( | |
"github.com/onflow/flow-go/network" | ||
jsoncodec "github.com/onflow/flow-go/network/codec/json" | ||
"github.com/onflow/flow-go/network/p2p" | ||
"github.com/onflow/flow-go/network/validator" | ||
"github.com/onflow/flow-go/state/protocol" | ||
badgerState "github.com/onflow/flow-go/state/protocol/badger" | ||
"github.com/onflow/flow-go/state/protocol/blocktimer" | ||
|
@@ -84,7 +86,9 @@ type AccessNodeBuilder interface { | |
// while for a node running as a library, the config fields are expected to be initialized by the caller. | ||
type AccessNodeConfig struct { | ||
staked bool | ||
stakedAccessNodeIDHex string | ||
bootstrapNodeAddresses []string | ||
bootstrapNodePublicKeys []string | ||
bootstrapIdentites flow.IdentityList // the identity list of bootstrap peers the node uses to discover other nodes | ||
unstakedNetworkBindAddr string | ||
collectionGRPCPort uint | ||
executionGRPCPort uint | ||
|
@@ -131,7 +135,8 @@ func DefaultAccessNodeConfig() *AccessNodeConfig { | |
apiRatelimits: nil, | ||
apiBurstlimits: nil, | ||
staked: true, | ||
stakedAccessNodeIDHex: "", | ||
bootstrapNodeAddresses: []string{}, | ||
bootstrapNodePublicKeys: []string{}, | ||
unstakedNetworkBindAddr: cmd.NotSet, | ||
} | ||
} | ||
|
@@ -144,6 +149,7 @@ type FlowAccessNodeBuilder struct { | |
*AccessNodeConfig | ||
|
||
// components | ||
UnstakedLibP2PNode *p2p.Node | ||
UnstakedNetwork *p2p.Network | ||
unstakedMiddleware *p2p.Middleware | ||
FollowerState protocol.MutableState | ||
|
@@ -483,9 +489,9 @@ func (anb *FlowAccessNodeBuilder) Build() AccessNodeBuilder { | |
|
||
type Option func(*AccessNodeConfig) | ||
|
||
func WithUpstreamAccessNodeID(upstreamAccessNodeID flow.Identifier) Option { | ||
func WithBootStrapPeers(bootstrapNodes ...*flow.Identity) Option { | ||
return func(config *AccessNodeConfig) { | ||
config.stakedAccessNodeIDHex = upstreamAccessNodeID.String() | ||
config.bootstrapIdentites = bootstrapNodes | ||
} | ||
} | ||
|
||
|
@@ -563,71 +569,59 @@ func (builder *FlowAccessNodeBuilder) extraFlags() { | |
flags.StringToIntVar(&builder.apiRatelimits, "api-rate-limits", defaultConfig.apiRatelimits, "per second rate limits for Access API methods e.g. Ping=300,GetTransaction=500 etc.") | ||
flags.StringToIntVar(&builder.apiBurstlimits, "api-burst-limits", defaultConfig.apiBurstlimits, "burst limits for Access API methods e.g. Ping=100,GetTransaction=100 etc.") | ||
flags.BoolVar(&builder.staked, "staked", defaultConfig.staked, "whether this node is a staked access node or not") | ||
flags.StringVar(&builder.stakedAccessNodeIDHex, "staked-access-node-id", defaultConfig.stakedAccessNodeIDHex, "the node ID of the upstream staked access node if this is an unstaked access node") | ||
flags.StringSliceVar(&builder.bootstrapNodeAddresses, "bootstrap-node-addresses", defaultConfig.bootstrapNodeAddresses, "the network addresses of the bootstrap access node if this is an unstaked access node e.g. access-001.mainnet.flow.org:9653,access-002.mainnet.flow.org:9653") | ||
flags.StringSliceVar(&builder.bootstrapNodePublicKeys, "bootstrap-node-public-keys", defaultConfig.bootstrapNodePublicKeys, "the networking public key of the bootstrap access node if this is an unstaked access node (in the same order as the bootstrap node addresses) e.g. \"d57a5e9c5.....\",\"44ded42d....\"") | ||
flags.StringVar(&builder.unstakedNetworkBindAddr, "unstaked-bind-addr", defaultConfig.unstakedNetworkBindAddr, "address to bind on for the unstaked network") | ||
}) | ||
} | ||
|
||
// initLibP2PFactory creates the LibP2P factory function for the given node ID and network key. | ||
// The factory function is later passed into the initMiddleware function to eventually instantiate the p2p.LibP2PNode instance | ||
func (builder *FlowAccessNodeBuilder) initLibP2PFactory( | ||
ctx context.Context, | ||
func (builder *FlowAccessNodeBuilder) initLibP2PFactory(ctx context.Context, | ||
nodeID flow.Identifier, | ||
networkMetrics module.NetworkMetrics, | ||
networkKey crypto.PrivateKey, | ||
) (p2p.LibP2PFactoryFunc, error) { | ||
networkKey crypto.PrivateKey) (p2p.LibP2PFactoryFunc, error) { | ||
|
||
// setup the Ping provider to return the software version and the sealed block height | ||
pingProvider := p2p.PingInfoProviderImpl{ | ||
SoftwareVersionFun: func() string { | ||
return build.Semver() | ||
}, | ||
SealedBlockHeightFun: func() (uint64, error) { | ||
head, err := builder.State.Sealed().Head() | ||
if err != nil { | ||
return 0, err | ||
} | ||
return head.Height, nil | ||
}, | ||
} | ||
// The staked nodes act as the DHT servers | ||
dhtOptions := []dht.Option{p2p.AsServer(builder.IsStaked())} | ||
|
||
libP2PNodeFactory, err := p2p.DefaultLibP2PNodeFactory( | ||
ctx, | ||
builder.Logger, | ||
nodeID, | ||
builder.unstakedNetworkBindAddr, | ||
networkKey, | ||
builder.RootBlock.ID().String(), | ||
p2p.DefaultMaxPubSubMsgSize, | ||
networkMetrics, | ||
pingProvider, | ||
) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not generate libp2p node factory: %w", err) | ||
// if this is an unstaked access node, then seed the DHT with the boostrap identities | ||
if !builder.IsStaked() { | ||
bootstrapPeersOpt, err := p2p.WithBootstrapPeers(builder.bootstrapIdentites) | ||
builder.MustNot(err) | ||
dhtOptions = append(dhtOptions, bootstrapPeersOpt) | ||
} | ||
|
||
return libP2PNodeFactory, nil | ||
return func() (*p2p.Node, error) { | ||
libp2pNode, err := p2p.NewDefaultLibP2PNodeBuilder(nodeID, builder.unstakedNetworkBindAddr, networkKey). | ||
SetRootBlockID(builder.RootBlock.ID().String()). | ||
// unlike the staked network where currently all the node addresses are known upfront, | ||
// for the unstaked network the nodes need to discover each other using DHT Discovery. | ||
SetPubsubOptions(p2p.WithDHTDiscovery(dhtOptions...)). | ||
SetLogger(builder.Logger). | ||
Build(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
builder.UnstakedLibP2PNode = libp2pNode | ||
return builder.UnstakedLibP2PNode, nil | ||
}, nil | ||
} | ||
|
||
// initMiddleware creates the network.Middleware implementation with the libp2p factory function, metrics, peer update | ||
// interval, and validators. The network.Middleware is then passed into the initNetwork function. | ||
func (builder *FlowAccessNodeBuilder) initMiddleware(nodeID flow.Identifier, | ||
networkMetrics module.NetworkMetrics, | ||
factoryFunc p2p.LibP2PFactoryFunc, | ||
peerUpdateInterval time.Duration, | ||
unicastMessageTimeout time.Duration, | ||
connectionGating bool, | ||
managerPeerConnections bool, | ||
validators ...network.MessageValidator) *p2p.Middleware { | ||
builder.unstakedMiddleware = p2p.NewMiddleware(builder.Logger, | ||
factoryFunc, | ||
nodeID, | ||
networkMetrics, | ||
builder.RootBlock.ID().String(), | ||
peerUpdateInterval, | ||
unicastMessageTimeout, | ||
connectionGating, | ||
managerPeerConnections, | ||
time.Hour, // TODO: this is pretty meaningless since there is no peermanager in play. | ||
p2p.DefaultUnicastTimeout, | ||
false, // no connection gating for the unstaked network | ||
false, // no peer management for the unstaked network (peer discovery will be done via LibP2P discovery mechanism) | ||
validators...) | ||
return builder.unstakedMiddleware | ||
} | ||
|
@@ -661,3 +655,49 @@ func (builder *FlowAccessNodeBuilder) initNetwork(nodeID module.Local, | |
|
||
return net, nil | ||
} | ||
|
||
func unstakedNetworkMsgValidators(selfID flow.Identifier) []network.MessageValidator { | ||
return []network.MessageValidator{ | ||
// filter out messages sent by this node itself | ||
validator.ValidateNotSender(selfID), | ||
} | ||
Comment on lines
+660
to
+663
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We probably need something better here? We have two scenarios:
The validation logic should be something like: "If the origin ID is from the Identity list in the protocol state, then accept, otherwise only accept if I am one of the targets". This relies on #1115 being done, which I think Francois is working on. This can be done in a later PR though. I'll create an issue for it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
|
||
// BootstrapIdentities converts the bootstrap node addresses and keys to a Flow Identity list where | ||
// each Flow Identity is initialized with the passed address, the networking key | ||
// and the Node ID set to ZeroID, role set to Access, 0 stake and no staking key. | ||
func BootstrapIdentities(addresses []string, keys []string) (flow.IdentityList, error) { | ||
|
||
if len(addresses) != len(keys) { | ||
return nil, fmt.Errorf("number of addresses and keys provided for the boostrap nodes don't match") | ||
} | ||
|
||
ids := make([]*flow.Identity, len(addresses)) | ||
for i, address := range addresses { | ||
|
||
key := keys[i] | ||
// json unmarshaller needs a quotes before and after the string | ||
// the pflags.StringSliceVar does not retain quotes for the command line arg even if escaped with \" | ||
// hence this additional check to ensure the key is indeed quoted | ||
if !strings.HasPrefix(key, "\"") { | ||
key = fmt.Sprintf("\"%s\"", key) | ||
} | ||
// networking public key | ||
var networkKey encodable.NetworkPubKey | ||
err := json.Unmarshal([]byte(key), &networkKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// create the identity of the peer by setting only the relevant fields | ||
id := &flow.Identity{ | ||
NodeID: flow.ZeroID, // the NodeID is the hash of the staking key and for the unstaked network it does not apply | ||
Address: address, | ||
Role: flow.RoleAccess, // the upstream node has to be an access node | ||
NetworkPubKey: networkKey, | ||
} | ||
|
||
ids = append(ids, id) | ||
} | ||
return ids, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,14 +3,12 @@ package node_builder | |
import ( | ||
"context" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/onflow/flow-go/cmd" | ||
pingeng "github.com/onflow/flow-go/engine/access/ping" | ||
"github.com/onflow/flow-go/model/flow" | ||
"github.com/onflow/flow-go/module" | ||
"github.com/onflow/flow-go/module/metrics" | ||
"github.com/onflow/flow-go/network/p2p" | ||
"github.com/onflow/flow-go/network/topology" | ||
) | ||
|
||
|
@@ -83,26 +81,17 @@ func (builder *StakedAccessNodeBuilder) enqueueUnstakedNetworkInit(ctx context.C | |
// TODO: set a different networking key of the staked access node on the unstaked network | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This todo will probably go away: if we use a single network (2 topics), the staked AN has a single network identity (PeerID) and therefore a single Network PublicKey. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, it will go away but ripping it out later will be more easier than changing it here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup |
||
unstakedNetworkKey := builder.NetworkKey | ||
|
||
libP2PFactory, err := builder.initLibP2PFactory(ctx, unstakedNodeID, unstakedNetworkKey) | ||
builder.MustNot(err) | ||
|
||
msgValidators := unstakedNetworkMsgValidators(unstakedNodeID) | ||
|
||
// Network Metrics | ||
// for now we use the empty metrics NoopCollector till we have defined the new unstaked network metrics | ||
// TODO: define new network metrics for the unstaked network | ||
unstakedNetworkMetrics := metrics.NewNoopCollector() | ||
|
||
libP2PFactory, err := builder.FlowAccessNodeBuilder.initLibP2PFactory(ctx, unstakedNodeID, unstakedNetworkMetrics, unstakedNetworkKey) | ||
builder.MustNot(err) | ||
|
||
// use the default validators for the staked access node unstaked networks | ||
msgValidators := p2p.DefaultValidators(builder.Logger, unstakedNodeID) | ||
|
||
// don't need any peer updates since this will be taken care by the DHT discovery mechanism | ||
peerUpdateInterval := time.Hour | ||
|
||
middleware := builder.initMiddleware(unstakedNodeID, | ||
unstakedNetworkMetrics, libP2PFactory, peerUpdateInterval, | ||
builder.UnicastMessageTimeout, | ||
false, // no connection gating for the unstaked network | ||
false, // no peer management for the unstaked network (peer discovery will be done via LibP2P discovery mechanism) | ||
msgValidators...) | ||
middleware := builder.initMiddleware(unstakedNodeID, unstakedNetworkMetrics, libP2PFactory, msgValidators...) | ||
|
||
// empty list of unstaked network participants since they will be discovered dynamically and are not known upfront | ||
// TODO: this list should be the unstaked addresses of all the staked AN that participate in the unstaked network | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now the unstaked node only needs the bootstrap node addresses and the public keys of those nodes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that is an improvement at all. Now, we have to manage multiple node identities with public keys on the unstaked consensus follower side. That doesn't make sense. Knowing a single access node address should be the only dependency needed. The DHT information should not be information that you need to connect; it should be served by the access node we connect to.