gitlab.com/jokerrs1/Sia@v1.3.2/node/api/ecosystem_helpers_test.go (about) 1 package api 2 3 // ecosystem_helpers_test.go has a bunch of helper functions to make setting up 4 // large ecosystem tests easier. 5 // 6 // List of helper functions: 7 // addStorageToAllHosts // adds a storage folder to every host 8 // announceAllHosts // announce all hosts to the network (and mine a block) 9 // fullyConnectNodes // connects each server tester to all the others 10 // fundAllNodes // mines blocks until all server testers have money 11 // synchronizationCheck // checks that all server testers have the same recent block 12 // waitForBlock // block until the provided block is the most recent block for all server testers 13 14 import ( 15 "errors" 16 "fmt" 17 "net/url" 18 "time" 19 20 "github.com/NebulousLabs/Sia/types" 21 ) 22 23 // addStorageToAllHosts adds a storage folder with a bunch of storage to each 24 // host. 25 func addStorageToAllHosts(sts []*serverTester) error { 26 for _, st := range sts { 27 values := url.Values{} 28 values.Set("path", st.dir) 29 values.Set("size", "1048576") 30 err := st.stdPostAPI("/host/storage/folders/add", values) 31 if err != nil { 32 return err 33 } 34 } 35 return nil 36 } 37 38 // announceAllHosts will announce every host in the tester set to the 39 // blockchain. 40 func announceAllHosts(sts []*serverTester) error { 41 // Check that all announcements will be on the same chain. 42 _, err := synchronizationCheck(sts) 43 if err != nil { 44 return err 45 } 46 47 // Grab the initial transaction pool size to know how many total transactions 48 // there should be after announcement. 49 initialTpoolSize := len(sts[0].tpool.TransactionList()) 50 51 // Announce each host. 52 for _, st := range sts { 53 // Set the host to be accepting contracts. 54 acceptingContractsValues := url.Values{} 55 acceptingContractsValues.Set("acceptingcontracts", "true") 56 err = st.stdPostAPI("/host", acceptingContractsValues) 57 if err != nil { 58 return err 59 } 60 61 // Fetch the host net address. 62 var hg HostGET 63 err = st.getAPI("/host", &hg) 64 if err != nil { 65 return err 66 } 67 68 // Make the announcement. 69 announceValues := url.Values{} 70 announceValues.Set("address", string(hg.ExternalSettings.NetAddress)) 71 err = st.stdPostAPI("/host/announce", announceValues) 72 if err != nil { 73 return err 74 } 75 } 76 77 // Wait until all of the transactions have propagated to all of the nodes. 78 // 79 // TODO: Replace this direct transaction pool call with a call to the 80 // /transactionpool endpoint. 81 // 82 // TODO: At some point the number of transactions needed to make an 83 // announcement may change. Currently its 2. 84 for i := 0; i < 50; i++ { 85 if len(sts[0].tpool.TransactionList()) == len(sts)*2+initialTpoolSize { 86 break 87 } 88 time.Sleep(time.Millisecond * 100) 89 } 90 if len(sts[0].tpool.TransactionList()) < len(sts)*2+initialTpoolSize { 91 return fmt.Errorf("Host announcements do not seem to have propagated to the leader's tpool: %v, %v, %v", len(sts), len(sts[0].tpool.TransactionList())+initialTpoolSize, initialTpoolSize) 92 } 93 94 // Mine a block and then wait for all of the nodes to syncrhonize to it. 95 _, err = sts[0].miner.AddBlock() 96 if err != nil { 97 return err 98 } 99 // Block until the block propagated to all nodes 100 for _, st := range sts[1:] { 101 err = waitForBlock(sts[0].cs.CurrentBlock().ID(), st) 102 if err != nil { 103 return (err) 104 } 105 } 106 // Check if all nodes are on the same block now 107 _, err = synchronizationCheck(sts) 108 if err != nil { 109 return err 110 } 111 112 // Block until every node has completed the scan of every other node, so 113 // that each node has a full hostdb. 114 for _, st := range sts { 115 var ah HostdbActiveGET 116 for i := 0; i < 100; i++ { 117 err = st.getAPI("/hostdb/active", &ah) 118 if err != nil { 119 return err 120 } 121 if len(ah.Hosts) >= len(sts) { 122 break 123 } 124 time.Sleep(time.Millisecond * 100) 125 } 126 if len(ah.Hosts) < len(sts) { 127 return errors.New("one of the nodes hostdbs was unable to find at least one host announcement") 128 } 129 } 130 return nil 131 } 132 133 // fullyConnectNodes takes a bunch of tester nodes and connects each to the 134 // other, creating a fully connected graph so that everyone is on the same 135 // chain. 136 // 137 // After connecting the nodes, it verifies that all the nodes have 138 // synchronized. 139 func fullyConnectNodes(sts []*serverTester) error { 140 for i, sta := range sts { 141 var gg GatewayGET 142 err := sta.getAPI("/gateway", &gg) 143 if err != nil { 144 return err 145 } 146 147 // Connect this node to every other node. 148 for _, stb := range sts[i+1:] { 149 // Try connecting to the other node until both have the other in 150 // their peer list. 151 err = retry(100, time.Millisecond*100, func() error { 152 // NOTE: this check depends on string-matching an error in the 153 // gateway. If that error changes at all, this string will need to 154 // be updated. 155 err := stb.stdPostAPI("/gateway/connect/"+string(gg.NetAddress), nil) 156 if err != nil && err.Error() != "already connected to this peer" { 157 return err 158 } 159 160 // Check that the gateways are connected. 161 bToA := false 162 aToB := false 163 var ggb GatewayGET 164 err = stb.getAPI("/gateway", &ggb) 165 if err != nil { 166 return err 167 } 168 for _, peer := range ggb.Peers { 169 if peer.NetAddress == gg.NetAddress { 170 bToA = true 171 break 172 } 173 } 174 err = sta.getAPI("/gateway", &gg) 175 if err != nil { 176 return err 177 } 178 for _, peer := range gg.Peers { 179 if peer.NetAddress == ggb.NetAddress { 180 aToB = true 181 break 182 } 183 } 184 if !aToB || !bToA { 185 return fmt.Errorf("called connect between two nodes, but they are not peers: %v %v %v %v %v %v", aToB, bToA, gg.NetAddress, ggb.NetAddress, gg.Peers, ggb.Peers) 186 } 187 return nil 188 189 }) 190 if err != nil { 191 return err 192 } 193 } 194 } 195 196 // Perform a synchronization check. 197 _, err := synchronizationCheck(sts) 198 return err 199 } 200 201 // fundAllNodes will make sure that each node has mined a block in the longest 202 // chain, then will mine enough blocks that the miner payouts manifest in the 203 // wallets of each node. 204 func fundAllNodes(sts []*serverTester) error { 205 // Check that all of the nodes are synchronized. 206 chainTip, err := synchronizationCheck(sts) 207 if err != nil { 208 return err 209 } 210 211 // Mine a block for each node to fund their wallet. 212 for i := range sts { 213 err := waitForBlock(chainTip, sts[i]) 214 if err != nil { 215 return err 216 } 217 218 // Mine a block. The next iteration of this loop will ensure that the 219 // block propagates and does not get orphaned. 220 block, err := sts[i].miner.AddBlock() 221 if err != nil { 222 return err 223 } 224 chainTip = block.ID() 225 } 226 227 // Wait until the chain tip has propagated to the first node. 228 err = waitForBlock(chainTip, sts[0]) 229 if err != nil { 230 return err 231 } 232 233 // Mine types.MaturityDelay more blocks from the final node to mine a 234 // block, to guarantee that all nodes have had their payouts mature, such 235 // that their wallets can begin spending immediately. 236 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 237 _, err := sts[0].miner.AddBlock() 238 if err != nil { 239 return err 240 } 241 } 242 243 // Block until every node has the full chain. 244 _, err = synchronizationCheck(sts) 245 return err 246 } 247 248 // synchronizationCheck takes a bunch of server testers as input and checks 249 // that they all have the same current block as the first server tester. The 250 // first server tester needs to have the most recent block in order for the 251 // check to work. 252 func synchronizationCheck(sts []*serverTester) (types.BlockID, error) { 253 // Prefer returning an error in the event of a zero-length server tester - 254 // an error should be returned if the developer accidentally uses a nil 255 // slice instead of whatever value was intended, and there's no reason to 256 // check for synchronization if there aren't any nodes to be synchronized. 257 if len(sts) == 0 { 258 return types.BlockID{}, errors.New("no server testers provided") 259 } 260 261 // Wait until all nodes are on the same block 262 for _, st := range sts[1:] { 263 err := waitForBlock(sts[0].cs.CurrentBlock().ID(), st) 264 if err != nil { 265 return types.BlockID{}, err 266 } 267 } 268 269 var cg ConsensusGET 270 err := sts[0].getAPI("/consensus", &cg) 271 if err != nil { 272 return types.BlockID{}, err 273 } 274 leaderBlockID := cg.CurrentBlock 275 for i := range sts { 276 // Spin until the current block matches the leader block. 277 success := false 278 for j := 0; j < 100; j++ { 279 err = sts[i].getAPI("/consensus", &cg) 280 if err != nil { 281 return types.BlockID{}, err 282 } 283 if cg.CurrentBlock == leaderBlockID { 284 success = true 285 break 286 } 287 time.Sleep(time.Millisecond * 100) 288 } 289 if !success { 290 return types.BlockID{}, errors.New("synchronization check failed - nodes do not seem to be synchronized") 291 } 292 } 293 return leaderBlockID, nil 294 } 295 296 // waitForBlock will block until the provided chain tip is the most recent 297 // block in the provided testing node. 298 func waitForBlock(chainTip types.BlockID, st *serverTester) error { 299 var cg ConsensusGET 300 success := false 301 for j := 0; j < 100; j++ { 302 err := st.getAPI("/consensus", &cg) 303 if err != nil { 304 return err 305 } 306 if cg.CurrentBlock == chainTip { 307 success = true 308 break 309 } 310 time.Sleep(time.Millisecond * 100) 311 } 312 if !success { 313 return errors.New("node never reached the correct chain tip") 314 } 315 return nil 316 }