@@ -919,3 +919,121 @@ func TestPEXReactorWhenAddressBookIsSmallerThanMaxDials(t *testing.T) {
919919 assert .Equal (t , 1 , pexR .AttemptsToDial (peer ))
920920 }
921921}
922+
923+ // TestPexFromScratch simulates establishing a working P2P network from scratch with a single bootstrap node.
924+ //
925+ // To further test reactor-specific peer disconnections and bootstrap node accept capabilities, each switch is running
926+ // not only PEX, but also some test reactors.
927+ // It is asserted that nodes can connect to each other and the address books contain other peers.
928+ func TestPexFromScratch (t * testing.T ) {
929+ testCases := []struct {
930+ name string
931+ bootstrap int // n - number of bootstrap nodes
932+ joining int // m - number of joining nodes
933+ }{
934+ {"1 bootstrap, 3 joining" , 1 , 3 },
935+ {"2 bootstrap, 5 joining" , 2 , 5 },
936+ {"3 bootstrap, 20 joining" , 3 , 20 },
937+ }
938+
939+ for _ , tc := range testCases {
940+ t .Run (tc .name , func (t * testing.T ) {
941+ // directory to store address books
942+ dir , err := os .MkdirTemp ("" , "pex_from_scratch" )
943+ require .NoError (t , err )
944+ defer os .RemoveAll (dir )
945+
946+ totalNodes := tc .bootstrap + tc .joining
947+ switches := make ([]* p2p.Switch , totalNodes )
948+ books := make ([]AddrBook , totalNodes )
949+ logger := log .TestingLogger ()
950+
951+ // Create all switches (bootstrap + joining nodes)
952+ for i := 0 ; i < totalNodes ; i ++ {
953+ switches [i ] = p2p .MakeSwitch (cfg , i , func (i int , sw * p2p.Switch ) * p2p.Switch {
954+ books [i ] = NewAddrBook (filepath .Join (dir , fmt .Sprintf ("addrbook%d.json" , i )), false )
955+ books [i ].SetLogger (logger .With ("pex" , i ))
956+ sw .SetAddrBook (books [i ])
957+
958+ sw .SetLogger (logger .With ("pex" , i ))
959+
960+ r := NewReactor (books [i ], & ReactorConfig {SeedMode : true })
961+ r .SetLogger (logger .With ("pex" , i ))
962+ r .SetEnsurePeersPeriod (250 * time .Millisecond )
963+ sw .AddReactor ("pex" , r )
964+
965+ return sw
966+ })
967+ }
968+
969+ // Bootstrap nodes know about each other
970+ for i := 0 ; i < tc .bootstrap ; i ++ {
971+ for j := 0 ; j < tc .bootstrap ; j ++ {
972+ if i != j {
973+ addr := switches [j ].NetAddress ()
974+ err := books [i ].AddAddress (addr , addr )
975+ require .NoError (t , err )
976+ }
977+ }
978+ }
979+
980+ // Joining nodes each know about a single bootstrap node
981+ for i := tc .bootstrap ; i < totalNodes ; i ++ {
982+ // Each joining node connects to a bootstrap node at index (i-tc.bootstrap) % tc.bootstrap
983+ bootstrapIndex := (i - tc .bootstrap ) % tc .bootstrap
984+ addr := switches [bootstrapIndex ].NetAddress ()
985+ err := books [i ].AddAddress (addr , addr )
986+ require .NoError (t , err )
987+ }
988+
989+ // Start bootstrappers
990+ for i := 0 ; i < tc .bootstrap ; i ++ {
991+ err := switches [i ].Start ()
992+ require .NoError (t , err )
993+ }
994+
995+ // Wait a bit and start joining nodes
996+ time .Sleep (100 * time .Millisecond )
997+ for i := tc .bootstrap ; i < totalNodes ; i ++ {
998+ err := switches [i ].Start ()
999+ require .NoError (t , err )
1000+ }
1001+
1002+ assertFullAddressBooks (t , totalNodes , books )
1003+ })
1004+ }
1005+ }
1006+
1007+ // assertFullAddressBooks checks if all address books have the expected number of peer entries within a timeout period.
1008+ func assertFullAddressBooks (t * testing.T , totalNodes int , books []AddrBook ) {
1009+ var (
1010+ ticker = time .NewTicker (50 * time .Millisecond )
1011+ timeoutCh = time .After (5 * time .Second )
1012+ )
1013+ defer ticker .Stop ()
1014+
1015+ allGood := false
1016+ expected := totalNodes - 1
1017+ for ! allGood {
1018+ select {
1019+ case <- ticker .C :
1020+ // PEX is responsible for address exchange, so we need to check address books, not the number of connections
1021+ // let's make a strong assumption - each node knows of all other nodes
1022+ cnt := 0
1023+ for i := 0 ; i < totalNodes ; i ++ {
1024+ total := len (books [i ].GetSelection ())
1025+ if total == expected {
1026+ cnt ++
1027+ }
1028+ }
1029+ t .Logf ("%d nodes with full address book" , cnt )
1030+ if cnt == totalNodes {
1031+ allGood = true
1032+ }
1033+ case <- timeoutCh :
1034+ t .Errorf ("expected all nodes to connect to each other" )
1035+ t .Fail ()
1036+ }
1037+ }
1038+ assert .True (t , allGood , "Not all nodes connected to each other" )
1039+ }
0 commit comments