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