github.com/core-coin/go-core/v2@v2.1.9/les/api_test.go (about) 1 // Copyright 2019 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package les 18 19 import ( 20 "context" 21 "errors" 22 "flag" 23 "io/ioutil" 24 "math/rand" 25 "os" 26 "sync" 27 "sync/atomic" 28 "testing" 29 "time" 30 31 "github.com/mattn/go-colorable" 32 33 "github.com/core-coin/go-core/v2/consensus/cryptore" 34 35 "github.com/core-coin/go-core/v2/common" 36 "github.com/core-coin/go-core/v2/common/hexutil" 37 "github.com/core-coin/go-core/v2/les/flowcontrol" 38 "github.com/core-coin/go-core/v2/log" 39 "github.com/core-coin/go-core/v2/node" 40 "github.com/core-coin/go-core/v2/p2p/enode" 41 "github.com/core-coin/go-core/v2/p2p/simulations" 42 "github.com/core-coin/go-core/v2/p2p/simulations/adapters" 43 "github.com/core-coin/go-core/v2/rpc" 44 "github.com/core-coin/go-core/v2/xcb" 45 "github.com/core-coin/go-core/v2/xcb/downloader" 46 ) 47 48 // Additional command line flags for the test binary. 49 var ( 50 loglevel = flag.Int("loglevel", 0, "verbosity of logs") 51 simAdapter = flag.String("adapter", "exec", "type of simulation: sim|socket|exec|docker") 52 ) 53 54 func TestMain(m *testing.M) { 55 flag.Parse() 56 log.PrintOrigins(true) 57 log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) 58 // register the Delivery service which will run as a devp2p 59 // protocol when using the exec adapter 60 adapters.RegisterLifecycles(services) 61 os.Exit(m.Run()) 62 } 63 64 // This test is not meant to be a part of the automatic testing process because it 65 // runs for a long time and also requires a large database in order to do a meaningful 66 // request performance test. When testServerDataDir is empty, the test is skipped. 67 68 const ( 69 testServerDataDir = "" // should always be empty on the master branch 70 testServerCapacity = 200 71 testMaxClients = 10 72 testTolerance = 0.1 73 minRelCap = 0.2 74 ) 75 76 func TestCapacityAPI3(t *testing.T) { 77 testCapacityAPI(t, 3) 78 } 79 80 func TestCapacityAPI6(t *testing.T) { 81 testCapacityAPI(t, 6) 82 } 83 84 func TestCapacityAPI10(t *testing.T) { 85 testCapacityAPI(t, 10) 86 } 87 88 // testCapacityAPI runs an end-to-end simulation test connecting one server with 89 // a given number of clients. It sets different priority capacities to all clients 90 // except a randomly selected one which runs in free client mode. All clients send 91 // similar requests at the maximum allowed rate and the test verifies whether the 92 // ratio of processed requests is close enough to the ratio of assigned capacities. 93 // Running multiple rounds with different settings ensures that changing capacity 94 // while connected and going back and forth between free and priority mode with 95 // the supplied API calls is also thoroughly tested. 96 func testCapacityAPI(t *testing.T, clientCount int) { 97 // Skip test if no data dir specified 98 if testServerDataDir == "" { 99 return 100 } 101 for !testSim(t, 1, clientCount, []string{testServerDataDir}, nil, func(ctx context.Context, net *simulations.Network, servers []*simulations.Node, clients []*simulations.Node) bool { 102 if len(servers) != 1 { 103 t.Fatalf("Invalid number of servers: %d", len(servers)) 104 } 105 server := servers[0] 106 107 serverRpcClient, err := server.Client() 108 if err != nil { 109 t.Fatalf("Failed to obtain rpc client: %v", err) 110 } 111 headNum, headHash := getHead(ctx, t, serverRpcClient) 112 minCap, totalCap := getCapacityInfo(ctx, t, serverRpcClient) 113 testCap := totalCap * 3 / 4 114 t.Logf("Server testCap: %d minCap: %d head number: %d head hash: %064x\n", testCap, minCap, headNum, headHash) 115 reqMinCap := uint64(float64(testCap) * minRelCap / (minRelCap + float64(len(clients)-1))) 116 if minCap > reqMinCap { 117 t.Fatalf("Minimum client capacity (%d) bigger than required minimum for this test (%d)", minCap, reqMinCap) 118 } 119 freeIdx := rand.Intn(len(clients)) 120 121 clientRpcClients := make([]*rpc.Client, len(clients)) 122 for i, client := range clients { 123 var err error 124 clientRpcClients[i], err = client.Client() 125 if err != nil { 126 t.Fatalf("Failed to obtain rpc client: %v", err) 127 } 128 t.Log("connecting client", i) 129 if i != freeIdx { 130 setCapacity(ctx, t, serverRpcClient, client.ID(), testCap/uint64(len(clients))) 131 } 132 net.Connect(client.ID(), server.ID()) 133 134 for { 135 select { 136 case <-ctx.Done(): 137 t.Fatalf("Timeout") 138 default: 139 } 140 num, hash := getHead(ctx, t, clientRpcClients[i]) 141 if num == headNum && hash == headHash { 142 t.Log("client", i, "synced") 143 break 144 } 145 time.Sleep(time.Millisecond * 200) 146 } 147 } 148 149 var wg sync.WaitGroup 150 stop := make(chan struct{}) 151 152 reqCount := make([]uint64, len(clientRpcClients)) 153 154 // Send light request like crazy. 155 for i, c := range clientRpcClients { 156 wg.Add(1) 157 i, c := i, c 158 go func() { 159 defer wg.Done() 160 161 queue := make(chan struct{}, 100) 162 reqCount[i] = 0 163 for { 164 select { 165 case queue <- struct{}{}: 166 select { 167 case <-stop: 168 return 169 case <-ctx.Done(): 170 return 171 default: 172 wg.Add(1) 173 go func() { 174 ok := testRequest(ctx, t, c) 175 wg.Done() 176 <-queue 177 if ok { 178 count := atomic.AddUint64(&reqCount[i], 1) 179 if count%10000 == 0 { 180 freezeClient(ctx, t, serverRpcClient, clients[i].ID()) 181 } 182 } 183 }() 184 } 185 case <-stop: 186 return 187 case <-ctx.Done(): 188 return 189 } 190 } 191 }() 192 } 193 194 processedSince := func(start []uint64) []uint64 { 195 res := make([]uint64, len(reqCount)) 196 for i := range reqCount { 197 res[i] = atomic.LoadUint64(&reqCount[i]) 198 if start != nil { 199 res[i] -= start[i] 200 } 201 } 202 return res 203 } 204 205 weights := make([]float64, len(clients)) 206 for c := 0; c < 5; c++ { 207 setCapacity(ctx, t, serverRpcClient, clients[freeIdx].ID(), minCap) 208 freeIdx = rand.Intn(len(clients)) 209 var sum float64 210 for i := range clients { 211 if i == freeIdx { 212 weights[i] = 0 213 } else { 214 weights[i] = rand.Float64()*(1-minRelCap) + minRelCap 215 } 216 sum += weights[i] 217 } 218 for i, client := range clients { 219 weights[i] *= float64(testCap-minCap-100) / sum 220 capacity := uint64(weights[i]) 221 if i != freeIdx && capacity < getCapacity(ctx, t, serverRpcClient, client.ID()) { 222 setCapacity(ctx, t, serverRpcClient, client.ID(), capacity) 223 } 224 } 225 setCapacity(ctx, t, serverRpcClient, clients[freeIdx].ID(), 0) 226 for i, client := range clients { 227 capacity := uint64(weights[i]) 228 if i != freeIdx && capacity > getCapacity(ctx, t, serverRpcClient, client.ID()) { 229 setCapacity(ctx, t, serverRpcClient, client.ID(), capacity) 230 } 231 } 232 weights[freeIdx] = float64(minCap) 233 for i := range clients { 234 weights[i] /= float64(testCap) 235 } 236 237 time.Sleep(flowcontrol.DecParamDelay) 238 t.Log("Starting measurement") 239 t.Logf("Relative weights:") 240 for i := range clients { 241 t.Logf(" %f", weights[i]) 242 } 243 t.Log() 244 start := processedSince(nil) 245 for { 246 select { 247 case <-ctx.Done(): 248 t.Fatalf("Timeout") 249 default: 250 } 251 252 _, totalCap = getCapacityInfo(ctx, t, serverRpcClient) 253 if totalCap < testCap { 254 t.Log("Total capacity underrun") 255 close(stop) 256 wg.Wait() 257 return false 258 } 259 260 processed := processedSince(start) 261 var avg uint64 262 t.Logf("Processed") 263 for i, p := range processed { 264 t.Logf(" %d", p) 265 processed[i] = uint64(float64(p) / weights[i]) 266 avg += processed[i] 267 } 268 avg /= uint64(len(processed)) 269 270 if avg >= 10000 { 271 var maxDev float64 272 for _, p := range processed { 273 dev := float64(int64(p-avg)) / float64(avg) 274 t.Logf(" %7.4f", dev) 275 if dev < 0 { 276 dev = -dev 277 } 278 if dev > maxDev { 279 maxDev = dev 280 } 281 } 282 t.Logf(" max deviation: %f totalCap: %d\n", maxDev, totalCap) 283 if maxDev <= testTolerance { 284 t.Log("success") 285 break 286 } 287 } else { 288 t.Log() 289 } 290 time.Sleep(time.Millisecond * 200) 291 } 292 } 293 294 close(stop) 295 wg.Wait() 296 297 for i, count := range reqCount { 298 t.Log("client", i, "processed", count) 299 } 300 return true 301 }) { 302 t.Log("restarting test") 303 } 304 } 305 306 func getHead(ctx context.Context, t *testing.T, client *rpc.Client) (uint64, common.Hash) { 307 res := make(map[string]interface{}) 308 if err := client.CallContext(ctx, &res, "xcb_getBlockByNumber", "latest", false); err != nil { 309 t.Fatalf("Failed to obtain head block: %v", err) 310 } 311 numStr, ok := res["number"].(string) 312 if !ok { 313 t.Fatalf("RPC block number field invalid") 314 } 315 num, err := hexutil.DecodeUint64(numStr) 316 if err != nil { 317 t.Fatalf("Failed to decode RPC block number: %v", err) 318 } 319 hashStr, ok := res["hash"].(string) 320 if !ok { 321 t.Fatalf("RPC block number field invalid") 322 } 323 hash := common.HexToHash(hashStr) 324 return num, hash 325 } 326 327 func testRequest(ctx context.Context, t *testing.T, client *rpc.Client) bool { 328 var res string 329 var addr common.Address 330 rand.Read(addr[:]) 331 c, cancel := context.WithTimeout(ctx, time.Second*12) 332 defer cancel() 333 err := client.CallContext(c, &res, "xcb_getBalance", addr, "latest") 334 if err != nil { 335 t.Log("request error:", err) 336 } 337 return err == nil 338 } 339 340 func freezeClient(ctx context.Context, t *testing.T, server *rpc.Client, clientID enode.ID) { 341 if err := server.CallContext(ctx, nil, "debug_freezeClient", clientID); err != nil { 342 t.Fatalf("Failed to freeze client: %v", err) 343 } 344 345 } 346 347 func setCapacity(ctx context.Context, t *testing.T, server *rpc.Client, clientID enode.ID, cap uint64) { 348 params := make(map[string]interface{}) 349 params["capacity"] = cap 350 if err := server.CallContext(ctx, nil, "les_setClientParams", []enode.ID{clientID}, []string{}, params); err != nil { 351 t.Fatalf("Failed to set client capacity: %v", err) 352 } 353 } 354 355 func getCapacity(ctx context.Context, t *testing.T, server *rpc.Client, clientID enode.ID) uint64 { 356 var res map[enode.ID]map[string]interface{} 357 if err := server.CallContext(ctx, &res, "les_clientInfo", []enode.ID{clientID}, []string{}); err != nil { 358 t.Fatalf("Failed to get client info: %v", err) 359 } 360 info, ok := res[clientID] 361 if !ok { 362 t.Fatalf("Missing client info") 363 } 364 v, ok := info["capacity"] 365 if !ok { 366 t.Fatalf("Missing field in client info: capacity") 367 } 368 vv, ok := v.(float64) 369 if !ok { 370 t.Fatalf("Failed to decode capacity field") 371 } 372 return uint64(vv) 373 } 374 375 func getCapacityInfo(ctx context.Context, t *testing.T, server *rpc.Client) (minCap, totalCap uint64) { 376 var res map[string]interface{} 377 if err := server.CallContext(ctx, &res, "les_serverInfo"); err != nil { 378 t.Fatalf("Failed to query server info: %v", err) 379 } 380 decode := func(s string) uint64 { 381 v, ok := res[s] 382 if !ok { 383 t.Fatalf("Missing field in server info: %s", s) 384 } 385 vv, ok := v.(float64) 386 if !ok { 387 t.Fatalf("Failed to decode server info field: %s", s) 388 } 389 return uint64(vv) 390 } 391 minCap = decode("minimumCapacity") 392 totalCap = decode("totalCapacity") 393 return 394 } 395 396 var services = adapters.LifecycleConstructors{ 397 "lesclient": newLesClientService, 398 "lesserver": newLesServerService, 399 } 400 401 func NewNetwork() (*simulations.Network, func(), error) { 402 adapter, adapterTeardown, err := NewAdapter(*simAdapter, services) 403 if err != nil { 404 return nil, adapterTeardown, err 405 } 406 defaultService := "streamer" 407 net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{ 408 ID: "0", 409 DefaultService: defaultService, 410 }) 411 teardown := func() { 412 adapterTeardown() 413 net.Shutdown() 414 } 415 return net, teardown, nil 416 } 417 418 func NewAdapter(adapterType string, services adapters.LifecycleConstructors) (adapter adapters.NodeAdapter, teardown func(), err error) { 419 teardown = func() {} 420 switch adapterType { 421 case "sim": 422 adapter = adapters.NewSimAdapter(services) 423 // case "socket": 424 // adapter = adapters.NewSocketAdapter(services) 425 case "exec": 426 baseDir, err0 := ioutil.TempDir("", "les-test") 427 if err0 != nil { 428 return nil, teardown, err0 429 } 430 teardown = func() { os.RemoveAll(baseDir) } 431 adapter = adapters.NewExecAdapter(baseDir) 432 /*case "docker": 433 adapter, err = adapters.NewDockerAdapter() 434 if err != nil { 435 return nil, teardown, err 436 }*/ 437 default: 438 return nil, teardown, errors.New("adapter needs to be one of sim, socket, exec, docker") 439 } 440 return adapter, teardown, nil 441 } 442 443 func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir []string, test func(ctx context.Context, net *simulations.Network, servers []*simulations.Node, clients []*simulations.Node) bool) bool { 444 net, teardown, err := NewNetwork() 445 defer teardown() 446 if err != nil { 447 t.Fatalf("Failed to create network: %v", err) 448 } 449 timeout := 1800 * time.Second 450 ctx, cancel := context.WithTimeout(context.Background(), timeout) 451 defer cancel() 452 453 servers := make([]*simulations.Node, serverCount) 454 clients := make([]*simulations.Node, clientCount) 455 456 for i := range clients { 457 clientconf := adapters.RandomNodeConfig() 458 clientconf.Lifecycles = []string{"lesclient"} 459 if len(clientDir) == clientCount { 460 clientconf.DataDir = clientDir[i] 461 } 462 client, err := net.NewNodeWithConfig(clientconf) 463 if err != nil { 464 t.Fatalf("Failed to create client: %v", err) 465 } 466 clients[i] = client 467 } 468 469 for i := range servers { 470 serverconf := adapters.RandomNodeConfig() 471 serverconf.Lifecycles = []string{"lesserver"} 472 if len(serverDir) == serverCount { 473 serverconf.DataDir = serverDir[i] 474 } 475 server, err := net.NewNodeWithConfig(serverconf) 476 if err != nil { 477 t.Fatalf("Failed to create server: %v", err) 478 } 479 servers[i] = server 480 } 481 482 for _, client := range clients { 483 if err := net.Start(client.ID()); err != nil { 484 t.Fatalf("Failed to start client node: %v", err) 485 } 486 } 487 for _, server := range servers { 488 if err := net.Start(server.ID()); err != nil { 489 t.Fatalf("Failed to start server node: %v", err) 490 } 491 } 492 493 return test(ctx, net, servers, clients) 494 } 495 496 func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { 497 config := xcb.DefaultConfig 498 config.SyncMode = downloader.LightSync 499 config.Cryptore.PowMode = cryptore.ModeFake 500 return New(stack, &config) 501 } 502 503 func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { 504 config := xcb.DefaultConfig 505 config.SyncMode = downloader.FullSync 506 config.LightServ = testServerCapacity 507 config.LightPeers = testMaxClients 508 core, err := xcb.New(stack, &config) 509 if err != nil { 510 return nil, err 511 } 512 _, err = NewLesServer(stack, core, &config) 513 if err != nil { 514 return nil, err 515 } 516 return core, nil 517 }