Skip to content

Commit

Permalink
Merge pull request #83 from onflow/yahya/4864-optimize-topology
Browse files Browse the repository at this point in the history
NET: Yahya/4864-topic-aware-topology
  • Loading branch information
Kay-Zee authored Nov 24, 2020
2 parents 379b882 + 718c5b8 commit 4553dbb
Show file tree
Hide file tree
Showing 32 changed files with 1,964 additions and 1,048 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ generate-mocks:
GO111MODULE=on mockery -name '.*' -dir=fvm -case=underscore -output="./fvm/mock" -outpkg="mock"
GO111MODULE=on mockery -name '.*' -dir=network/gossip/libp2p/middleware -case=underscore -output="./network/gossip/libp2p/mock" -outpkg="mock"
GO111MODULE=on mockery -name 'Connector' -dir=network/gossip/libp2p -case=underscore -output="./network/gossip/libp2p/mock" -outpkg="mock"
GO111MODULE=on mockery -name 'SubscriptionManager' -dir=network/gossip/libp2p/channel -case=underscore -output="./network/gossip/libp2p/mock" -outpkg="mock"
GO111MODULE=on mockery -name 'Vertex' -dir="./consensus/hotstuff/forks/finalizer/forest" -case=underscore -output="./consensus/hotstuff/forks/finalizer/forest/mock" -outpkg="mock"
GO111MODULE=on mockery -name '.*' -dir="./consensus/hotstuff" -case=underscore -output="./consensus/hotstuff/mocks" -outpkg="mocks"
GO111MODULE=on mockery -name '.*' -dir="./engine/access/wrapper" -case=underscore -output="./engine/access/mock" -outpkg="mock"
Expand Down
29 changes: 16 additions & 13 deletions cmd/scaffold.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,28 +176,31 @@ func (fnb *FlowNodeBuilder) enqueueNetworkInit() {
}
fnb.Middleware = mw

nodeID, err := fnb.State.Final().Identity(fnb.Me.NodeID())
if err != nil {
return nil, fmt.Errorf("could not get node id: %w", err)
}
nodeRole := nodeID.Role

participants, err := fnb.State.Final().Identities(libp2p.NetworkingSetFilter)
if err != nil {
return nil, fmt.Errorf("could not get network identities: %w", err)
}

var nodeTopology topology.Topology
if nodeRole == flow.RoleCollection {
nodeTopology, err = topology.NewCollectionTopology(nodeID.NodeID, fnb.State)
} else {
nodeTopology, err = topology.NewRandPermTopology(nodeRole, nodeID.NodeID)
}
// creates topology, topology manager, and subscription managers
//
// topology
// subscription manager
subscriptionManager := libp2p.NewChannelSubscriptionManager(fnb.Middleware)
top, err := topology.NewTopicBasedTopology(fnb.NodeID, fnb.Logger, fnb.State, subscriptionManager)
if err != nil {
return nil, fmt.Errorf("could not create topology: %w", err)
}

net, err := libp2p.NewNetwork(fnb.Logger, codec, participants, fnb.Me, fnb.Middleware, 10e6, nodeTopology, fnb.Metrics.Network)
// creates network instance
net, err := libp2p.NewNetwork(fnb.Logger,
codec,
participants,
fnb.Me,
fnb.Middleware,
10e6,
top,
subscriptionManager,
fnb.Metrics.Network)
if err != nil {
return nil, fmt.Errorf("could not initialize network: %w", err)
}
Expand Down
105 changes: 105 additions & 0 deletions engine/channels.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,47 @@ import (
"github.com/onflow/flow-go/model/flow"
)

// init is called first time this package is imported.
// It creates and initializes the channel ID map.
func init() {
initializeChannelIdMap()
}

// channelIdMap keeps a map between channel IDs and list of flow roles involved in that channel ID.
var channelIdMap map[string]flow.RoleList

// RolesByChannelID returns list of flow roles involved in the channelID.
func RolesByChannelID(channelID string) (flow.RoleList, bool) {
if clusterChannelID, isCluster := IsClusterChannelID(channelID); isCluster {
// replaces channelID with the stripped-off channel prefix
channelID = clusterChannelID
}
roles, ok := channelIdMap[channelID]
return roles, ok
}

// ChannelIDsByRole returns a list of all channel IDs the role subscribes to.
func ChannelIDsByRole(role flow.Role) []string {
channels := make([]string, 0)
for channelID, roles := range channelIdMap {
if roles.Contains(role) {
channels = append(channels, channelID)
}
}

return channels
}

// ChannelIDs returns all channelIDs nodes of any role have subscribed to.
func ChannelIDs() []string {
channelIDs := make([]string, 0)
for channelID := range channelIdMap {
channelIDs = append(channelIDs, channelID)
}

return channelIDs
}

// channel IDs
const (

Expand Down Expand Up @@ -49,6 +90,70 @@ const (
ProvideReceiptsByBlockID = RequestReceiptsByBlockID
)

// initializeChannelIdMap initializes an instance of channelIdMap and populates it with the channel IDs and their
// Note: Please update this map, if a new channel is defined or a the roles subscribing to a channel have changed
// corresponding list of roles.
func initializeChannelIdMap() {
channelIdMap = make(map[string]flow.RoleList)

// Channels for test
channelIdMap[TestNetwork] = flow.RoleList{flow.RoleCollection, flow.RoleConsensus, flow.RoleExecution,
flow.RoleVerification, flow.RoleAccess}
channelIdMap[TestMetrics] = flow.RoleList{flow.RoleCollection, flow.RoleConsensus, flow.RoleExecution,
flow.RoleVerification, flow.RoleAccess}

// Channels for consensus protocols
channelIdMap[ConsensusCommittee] = flow.RoleList{flow.RoleConsensus}

// Channels for protocols actively synchronizing state across nodes
channelIdMap[SyncCommittee] = flow.RoleList{flow.RoleConsensus}
channelIdMap[SyncExecution] = flow.RoleList{flow.RoleExecution}

// Channels for actively pushing entities to subscribers
channelIdMap[PushTransactions] = flow.RoleList{flow.RoleCollection}
channelIdMap[PushGuarantees] = flow.RoleList{flow.RoleCollection, flow.RoleConsensus}
channelIdMap[PushBlocks] = flow.RoleList{flow.RoleCollection, flow.RoleConsensus, flow.RoleExecution,
flow.RoleVerification, flow.RoleAccess}
channelIdMap[PushReceipts] = flow.RoleList{flow.RoleConsensus, flow.RoleExecution, flow.RoleVerification,
flow.RoleAccess}
channelIdMap[PushApprovals] = flow.RoleList{flow.RoleConsensus, flow.RoleVerification}

// Channels for actively requesting missing entities
channelIdMap[RequestCollections] = flow.RoleList{flow.RoleCollection, flow.RoleExecution}
channelIdMap[RequestChunks] = flow.RoleList{flow.RoleExecution, flow.RoleVerification}
channelIdMap[RequestReceiptsByBlockID] = flow.RoleList{flow.RoleConsensus, flow.RoleExecution}

// Channel aliases to make the code more readable / more robust to errors
channelIdMap[ReceiveGuarantees] = flow.RoleList{flow.RoleCollection, flow.RoleConsensus}
channelIdMap[ReceiveBlocks] = flow.RoleList{flow.RoleCollection, flow.RoleConsensus, flow.RoleExecution,
flow.RoleVerification, flow.RoleAccess}
channelIdMap[ReceiveReceipts] = flow.RoleList{flow.RoleConsensus, flow.RoleExecution, flow.RoleVerification,
flow.RoleAccess}
channelIdMap[ReceiveApprovals] = flow.RoleList{flow.RoleConsensus, flow.RoleVerification}

channelIdMap[ProvideCollections] = flow.RoleList{flow.RoleCollection, flow.RoleExecution}
channelIdMap[ProvideChunks] = flow.RoleList{flow.RoleExecution, flow.RoleVerification}
channelIdMap[ProvideReceiptsByBlockID] = flow.RoleList{flow.RoleConsensus, flow.RoleExecution}

channelIdMap[syncClusterPrefix] = flow.RoleList{flow.RoleCollection}
channelIdMap[consensusClusterPrefix] = flow.RoleList{flow.RoleCollection}
}

// IsClusterChannelID returns true if channel ID is a cluster-related channel ID.
// At the current implementation, only collection nodes are involved in a cluster-related channel ID.
// If the channel ID is a cluster-related one, this method also strips off the channel prefix and returns it.
func IsClusterChannelID(channelID string) (string, bool) {
if strings.HasPrefix(channelID, syncClusterPrefix) {
return syncClusterPrefix, true
}

if strings.HasPrefix(channelID, consensusClusterPrefix) {
return consensusClusterPrefix, true
}

return "", false
}

// FullyQualifiedChannelName returns the unique channel name made up of channel name string suffixed with root block id
// The root block id is used to prevent cross talks between nodes on different sporks
func FullyQualifiedChannelName(channelID string, rootBlockID string) string {
Expand Down
90 changes: 90 additions & 0 deletions engine/channels_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package engine

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/onflow/flow-go/model/flow"
)

// TestGetRolesByChannelID_NonClusterChannelID evaluates correctness of GetRoleByChannelID function against
// inclusion and exclusion of roles. Essentially, the test evaluates that RolesByChannelID
// operates on top of channelIdMap.
func TestGetRolesByChannelID_NonClusterChannelID(t *testing.T) {
// asserts existing topic with its role
// the roles list should contain collection and consensus roles
roles, ok := RolesByChannelID(PushGuarantees)
assert.True(t, ok)
assert.Len(t, roles, 2)
assert.Contains(t, roles, flow.RoleConsensus)
assert.Contains(t, roles, flow.RoleCollection)
assert.NotContains(t, roles, flow.RoleExecution)
assert.NotContains(t, roles, flow.RoleVerification)
assert.NotContains(t, roles, flow.RoleAccess)

// asserts a non-existing topic
roles, ok = RolesByChannelID("non-existing-topic")
assert.False(t, ok)
assert.Nil(t, roles)
}

// TestGetRolesByChannelID_ClusterChannelID evaluates correctness of GetRoleByChannelID function against
// cluster channel ids. Essentially, the test evaluates that RolesByChannelID
// operates on top of channelIdMap, and correctly identifies and strips of the cluster channel ids.
func TestGetRolesByChannelID_ClusterChannelID(t *testing.T) {
// creates a cluster channel id
conClusterChannel := ChannelConsensusCluster("some-consensus-cluster-id")

// the roles list should contain collection
roles, ok := RolesByChannelID(conClusterChannel)
assert.True(t, ok)
assert.Len(t, roles, 1)
assert.Contains(t, roles, flow.RoleCollection)
}

// TestGetChannelIDByRole evaluates retrieving channel IDs associated with a role from the
// channel IDs map using ChannelIDsByRole. Essentially it evaluates that ChannelIDsByRole
// operates on top of channelIDMap.
func TestGetChannelIDByRole(t *testing.T) {
// asserts topics by the role for verification node
// it should have the topics of
// - PushBlocks
// - PushReceipts
// - PushApprovals
// - ProvideChunks
// - TestNetwork
// - TestMetric
// the roles list should contain collection and consensus roles
topics := ChannelIDsByRole(flow.RoleVerification)
assert.Len(t, topics, 6)
assert.Contains(t, topics, PushBlocks)
assert.Contains(t, topics, PushReceipts)
assert.Contains(t, topics, PushApprovals)
assert.Contains(t, topics, RequestChunks)
assert.Contains(t, topics, TestMetrics)
assert.Contains(t, topics, TestNetwork)
}

// TestIsClusterChannelID verifies the correctness of IsClusterChannelID method
// against cluster and non-cluster channel ids.
func TestIsClusterChannelID(t *testing.T) {
// creates a consensus cluster channel and verifies it
conClusterChannel := ChannelConsensusCluster("some-consensus-cluster-id")
clusterChannelID, ok := IsClusterChannelID(conClusterChannel)
require.True(t, ok)
require.Equal(t, clusterChannelID, consensusClusterPrefix)

// creates a sync cluster channel and verifies it
syncClusterID := ChannelSyncCluster("some-sync-cluster-id")
clusterChannelID, ok = IsClusterChannelID(syncClusterID)
require.True(t, ok)
require.Equal(t, clusterChannelID, syncClusterPrefix)

// non-cluster channel should not be verified
clusterChannelID, ok = IsClusterChannelID("non-cluster-channel-id")
require.False(t, ok)
require.Empty(t, clusterChannelID)

}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.13
require (
cloud.google.com/go/storage v1.10.0
github.com/HdrHistogram/hdrhistogram-go v0.9.0 // indirect
github.com/bsipos/thist v1.0.0
github.com/btcsuite/btcd v0.20.1-beta
github.com/codahale/hdrhistogram v0.9.0 // indirect
github.com/davecgh/go-spew v1.1.1
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrU
github.com/VictoriaMetrics/fastcache v1.5.3 h1:2odJnXLbFZcoV9KYtQ+7TH1UOq3dn3AssMgieaezkR4=
github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af h1:wVe6/Ea46ZMeNkQjjBW6xcqyQA/j5e0D6GytH95g0gQ=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
Expand All @@ -93,6 +94,8 @@ github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bsipos/thist v1.0.0 h1:vZ3W5/ZnT54s4LHeonTCbnzCb20ERlJUnhiwXoGpsbY=
github.com/bsipos/thist v1.0.0/go.mod h1:7i0xwRua1/bmUxcxi2xAxaFL895rLtOpKUwnw3NrT8I=
github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ=
github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8=
github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
Expand Down Expand Up @@ -181,6 +184,7 @@ github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6 h1:u/UEqS66A5ckRmS4yNpjmVH56sVtS/RfclBAYocb4as=
github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90 h1:WXb3TSNmHp2vHoCroCIB1foO/yQ36swABL8aOVeDpgg=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
Expand Down Expand Up @@ -208,6 +212,7 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -377,6 +382,7 @@ github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfE
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 h1:PJr+ZMXIecYc1Ey2zucXdR73SMBtgjPgwa31099IMv0=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0=
github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
Expand Down Expand Up @@ -930,6 +936,7 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
Expand Down Expand Up @@ -1035,6 +1042,7 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
Expand Down Expand Up @@ -1088,6 +1096,7 @@ gonum.org/v1/gonum v0.6.1 h1:/LSrTrgZtpbXyAR6+0e152SROCkJJSh7goYWVmdPFGc=
gonum.org/v1/gonum v0.6.1/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b h1:Qh4dB5D/WpoUUp3lSod7qgoyEHbDGPUWjIbnqdqqe1k=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
Expand Down Expand Up @@ -1219,6 +1228,7 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
Loading

0 comments on commit 4553dbb

Please sign in to comment.