github.com/DxChainNetwork/dxc@v0.8.1-0.20220824085222-1162e304b6e7/les/vflux/server/balance_test.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum 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-ethereum 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-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package server 18 19 import ( 20 "math" 21 "math/rand" 22 "reflect" 23 "testing" 24 "time" 25 26 "github.com/DxChainNetwork/dxc/common/mclock" 27 "github.com/DxChainNetwork/dxc/ethdb" 28 "github.com/DxChainNetwork/dxc/ethdb/memorydb" 29 "github.com/DxChainNetwork/dxc/les/utils" 30 "github.com/DxChainNetwork/dxc/p2p/enode" 31 "github.com/DxChainNetwork/dxc/p2p/enr" 32 "github.com/DxChainNetwork/dxc/p2p/nodestate" 33 ) 34 35 type zeroExpirer struct{} 36 37 func (z zeroExpirer) SetRate(now mclock.AbsTime, rate float64) {} 38 func (z zeroExpirer) SetLogOffset(now mclock.AbsTime, logOffset utils.Fixed64) {} 39 func (z zeroExpirer) LogOffset(now mclock.AbsTime) utils.Fixed64 { return 0 } 40 41 type balanceTestClient struct{} 42 43 func (client balanceTestClient) FreeClientId() string { return "" } 44 45 type balanceTestSetup struct { 46 clock *mclock.Simulated 47 db ethdb.KeyValueStore 48 ns *nodestate.NodeStateMachine 49 setup *serverSetup 50 bt *balanceTracker 51 } 52 53 func newBalanceTestSetup(db ethdb.KeyValueStore, posExp, negExp utils.ValueExpirer) *balanceTestSetup { 54 // Initialize and customize the setup for the balance testing 55 clock := &mclock.Simulated{} 56 setup := newServerSetup() 57 setup.clientField = setup.setup.NewField("balancTestClient", reflect.TypeOf(balanceTestClient{})) 58 59 ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup) 60 if posExp == nil { 61 posExp = zeroExpirer{} 62 } 63 if negExp == nil { 64 negExp = zeroExpirer{} 65 } 66 if db == nil { 67 db = memorydb.New() 68 } 69 bt := newBalanceTracker(ns, setup, db, clock, posExp, negExp) 70 ns.Start() 71 return &balanceTestSetup{ 72 clock: clock, 73 db: db, 74 ns: ns, 75 setup: setup, 76 bt: bt, 77 } 78 } 79 80 func (b *balanceTestSetup) newNode(capacity uint64) *nodeBalance { 81 node := enode.SignNull(&enr.Record{}, enode.ID{}) 82 b.ns.SetField(node, b.setup.clientField, balanceTestClient{}) 83 if capacity != 0 { 84 b.ns.SetField(node, b.setup.capacityField, capacity) 85 } 86 n, _ := b.ns.GetField(node, b.setup.balanceField).(*nodeBalance) 87 return n 88 } 89 90 func (b *balanceTestSetup) setBalance(node *nodeBalance, pos, neg uint64) (err error) { 91 b.bt.BalanceOperation(node.node.ID(), node.connAddress, func(balance AtomicBalanceOperator) { 92 err = balance.SetBalance(pos, neg) 93 }) 94 return 95 } 96 97 func (b *balanceTestSetup) addBalance(node *nodeBalance, add int64) (old, new uint64, err error) { 98 b.bt.BalanceOperation(node.node.ID(), node.connAddress, func(balance AtomicBalanceOperator) { 99 old, new, err = balance.AddBalance(add) 100 }) 101 return 102 } 103 104 func (b *balanceTestSetup) stop() { 105 b.bt.stop() 106 b.ns.Stop() 107 } 108 109 func TestAddBalance(t *testing.T) { 110 b := newBalanceTestSetup(nil, nil, nil) 111 defer b.stop() 112 113 node := b.newNode(1000) 114 var inputs = []struct { 115 delta int64 116 expect [2]uint64 117 total uint64 118 expectErr bool 119 }{ 120 {100, [2]uint64{0, 100}, 100, false}, 121 {-100, [2]uint64{100, 0}, 0, false}, 122 {-100, [2]uint64{0, 0}, 0, false}, 123 {1, [2]uint64{0, 1}, 1, false}, 124 {maxBalance, [2]uint64{0, 0}, 0, true}, 125 } 126 for _, i := range inputs { 127 old, new, err := b.addBalance(node, i.delta) 128 if i.expectErr { 129 if err == nil { 130 t.Fatalf("Expect get error but nil") 131 } 132 continue 133 } else if err != nil { 134 t.Fatalf("Expect get no error but %v", err) 135 } 136 if old != i.expect[0] || new != i.expect[1] { 137 t.Fatalf("Positive balance mismatch, got %v -> %v", old, new) 138 } 139 if b.bt.TotalTokenAmount() != i.total { 140 t.Fatalf("Total positive balance mismatch, want %v, got %v", i.total, b.bt.TotalTokenAmount()) 141 } 142 } 143 } 144 145 func TestSetBalance(t *testing.T) { 146 b := newBalanceTestSetup(nil, nil, nil) 147 defer b.stop() 148 node := b.newNode(1000) 149 150 var inputs = []struct { 151 pos, neg uint64 152 }{ 153 {1000, 0}, 154 {0, 1000}, 155 {1000, 1000}, 156 } 157 for _, i := range inputs { 158 b.setBalance(node, i.pos, i.neg) 159 pos, neg := node.GetBalance() 160 if pos != i.pos { 161 t.Fatalf("Positive balance mismatch, want %v, got %v", i.pos, pos) 162 } 163 if neg != i.neg { 164 t.Fatalf("Negative balance mismatch, want %v, got %v", i.neg, neg) 165 } 166 } 167 } 168 169 func TestBalanceTimeCost(t *testing.T) { 170 b := newBalanceTestSetup(nil, nil, nil) 171 defer b.stop() 172 node := b.newNode(1000) 173 174 node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) 175 b.setBalance(node, uint64(time.Minute), 0) // 1 minute time allowance 176 177 var inputs = []struct { 178 runTime time.Duration 179 expPos uint64 180 expNeg uint64 181 }{ 182 {time.Second, uint64(time.Second * 59), 0}, 183 {0, uint64(time.Second * 59), 0}, 184 {time.Second * 59, 0, 0}, 185 {time.Second, 0, uint64(time.Second)}, 186 } 187 for _, i := range inputs { 188 b.clock.Run(i.runTime) 189 if pos, _ := node.GetBalance(); pos != i.expPos { 190 t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) 191 } 192 if _, neg := node.GetBalance(); neg != i.expNeg { 193 t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) 194 } 195 } 196 197 b.setBalance(node, uint64(time.Minute), 0) // Refill 1 minute time allowance 198 for _, i := range inputs { 199 b.clock.Run(i.runTime) 200 if pos, _ := node.GetBalance(); pos != i.expPos { 201 t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) 202 } 203 if _, neg := node.GetBalance(); neg != i.expNeg { 204 t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) 205 } 206 } 207 } 208 209 func TestBalanceReqCost(t *testing.T) { 210 b := newBalanceTestSetup(nil, nil, nil) 211 defer b.stop() 212 node := b.newNode(1000) 213 node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) 214 215 b.setBalance(node, uint64(time.Minute), 0) // 1 minute time serving time allowance 216 var inputs = []struct { 217 reqCost uint64 218 expPos uint64 219 expNeg uint64 220 }{ 221 {uint64(time.Second), uint64(time.Second * 59), 0}, 222 {0, uint64(time.Second * 59), 0}, 223 {uint64(time.Second * 59), 0, 0}, 224 {uint64(time.Second), 0, uint64(time.Second)}, 225 } 226 for _, i := range inputs { 227 node.RequestServed(i.reqCost) 228 if pos, _ := node.GetBalance(); pos != i.expPos { 229 t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) 230 } 231 if _, neg := node.GetBalance(); neg != i.expNeg { 232 t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) 233 } 234 } 235 } 236 237 func TestBalanceToPriority(t *testing.T) { 238 b := newBalanceTestSetup(nil, nil, nil) 239 defer b.stop() 240 node := b.newNode(1000) 241 node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) 242 243 var inputs = []struct { 244 pos uint64 245 neg uint64 246 priority int64 247 }{ 248 {1000, 0, 1}, 249 {2000, 0, 2}, // Higher balance, higher priority value 250 {0, 0, 0}, 251 {0, 1000, -1000}, 252 } 253 for _, i := range inputs { 254 b.setBalance(node, i.pos, i.neg) 255 priority := node.priority(1000) 256 if priority != i.priority { 257 t.Fatalf("priority mismatch, want %v, got %v", i.priority, priority) 258 } 259 } 260 } 261 262 func TestEstimatedPriority(t *testing.T) { 263 b := newBalanceTestSetup(nil, nil, nil) 264 defer b.stop() 265 node := b.newNode(1000000000) 266 node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) 267 b.setBalance(node, uint64(time.Minute), 0) 268 var inputs = []struct { 269 runTime time.Duration // time cost 270 futureTime time.Duration // diff of future time 271 reqCost uint64 // single request cost 272 priority int64 // expected estimated priority 273 }{ 274 {time.Second, time.Second, 0, 58}, 275 {0, time.Second, 0, 58}, 276 277 // 2 seconds time cost, 1 second estimated time cost, 10^9 request cost, 278 // 10^9 estimated request cost per second. 279 {time.Second, time.Second, 1000000000, 55}, 280 281 // 3 seconds time cost, 3 second estimated time cost, 10^9*2 request cost, 282 // 4*10^9 estimated request cost. 283 {time.Second, 3 * time.Second, 1000000000, 48}, 284 285 // All positive balance is used up 286 {time.Second * 55, 0, 0, -1}, 287 288 // 1 minute estimated time cost, 4/58 * 10^9 estimated request cost per sec. 289 {0, time.Minute, 0, -int64(time.Minute) - int64(time.Second)*120/29}, 290 } 291 for _, i := range inputs { 292 b.clock.Run(i.runTime) 293 node.RequestServed(i.reqCost) 294 priority := node.estimatePriority(1000000000, 0, i.futureTime, 0, false) 295 if priority != i.priority { 296 t.Fatalf("Estimated priority mismatch, want %v, got %v", i.priority, priority) 297 } 298 } 299 } 300 301 func TestPostiveBalanceCounting(t *testing.T) { 302 b := newBalanceTestSetup(nil, nil, nil) 303 defer b.stop() 304 305 var nodes []*nodeBalance 306 for i := 0; i < 100; i += 1 { 307 node := b.newNode(1000000) 308 node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) 309 nodes = append(nodes, node) 310 } 311 312 // Allocate service token 313 var sum uint64 314 for i := 0; i < 100; i += 1 { 315 amount := int64(rand.Intn(100) + 100) 316 b.addBalance(nodes[i], amount) 317 sum += uint64(amount) 318 } 319 if b.bt.TotalTokenAmount() != sum { 320 t.Fatalf("Invalid token amount") 321 } 322 323 // Change client status 324 for i := 0; i < 100; i += 1 { 325 if rand.Intn(2) == 0 { 326 b.ns.SetField(nodes[i].node, b.setup.capacityField, uint64(1)) 327 } 328 } 329 if b.bt.TotalTokenAmount() != sum { 330 t.Fatalf("Invalid token amount") 331 } 332 for i := 0; i < 100; i += 1 { 333 if rand.Intn(2) == 0 { 334 b.ns.SetField(nodes[i].node, b.setup.capacityField, uint64(1)) 335 } 336 } 337 if b.bt.TotalTokenAmount() != sum { 338 t.Fatalf("Invalid token amount") 339 } 340 } 341 342 func TestCallbackChecking(t *testing.T) { 343 b := newBalanceTestSetup(nil, nil, nil) 344 defer b.stop() 345 node := b.newNode(1000000) 346 node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) 347 348 var inputs = []struct { 349 priority int64 350 expDiff time.Duration 351 }{ 352 {500, time.Millisecond * 500}, 353 {0, time.Second}, 354 {-int64(time.Second), 2 * time.Second}, 355 } 356 b.setBalance(node, uint64(time.Second), 0) 357 for _, i := range inputs { 358 diff, _ := node.timeUntil(i.priority) 359 if diff != i.expDiff { 360 t.Fatalf("Time difference mismatch, want %v, got %v", i.expDiff, diff) 361 } 362 } 363 } 364 365 func TestCallback(t *testing.T) { 366 b := newBalanceTestSetup(nil, nil, nil) 367 defer b.stop() 368 node := b.newNode(1000) 369 node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) 370 371 callCh := make(chan struct{}, 1) 372 b.setBalance(node, uint64(time.Minute), 0) 373 node.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} }) 374 375 b.clock.Run(time.Minute) 376 select { 377 case <-callCh: 378 case <-time.NewTimer(time.Second).C: 379 t.Fatalf("Callback hasn't been called yet") 380 } 381 382 b.setBalance(node, uint64(time.Minute), 0) 383 node.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} }) 384 node.removeCallback(balanceCallbackZero) 385 386 b.clock.Run(time.Minute) 387 select { 388 case <-callCh: 389 t.Fatalf("Callback shouldn't be called") 390 case <-time.NewTimer(time.Millisecond * 100).C: 391 } 392 } 393 394 func TestBalancePersistence(t *testing.T) { 395 posExp := &utils.Expirer{} 396 negExp := &utils.Expirer{} 397 posExp.SetRate(0, math.Log(2)/float64(time.Hour*2)) // halves every two hours 398 negExp.SetRate(0, math.Log(2)/float64(time.Hour)) // halves every hour 399 setup := newBalanceTestSetup(nil, posExp, negExp) 400 401 exp := func(balance *nodeBalance, expPos, expNeg uint64) { 402 pos, neg := balance.GetBalance() 403 if pos != expPos { 404 t.Fatalf("Positive balance incorrect, want %v, got %v", expPos, pos) 405 } 406 if neg != expNeg { 407 t.Fatalf("Positive balance incorrect, want %v, got %v", expPos, pos) 408 } 409 } 410 expTotal := func(expTotal uint64) { 411 total := setup.bt.TotalTokenAmount() 412 if total != expTotal { 413 t.Fatalf("Total token amount incorrect, want %v, got %v", expTotal, total) 414 } 415 } 416 417 expTotal(0) 418 balance := setup.newNode(0) 419 expTotal(0) 420 setup.setBalance(balance, 16000000000, 16000000000) 421 exp(balance, 16000000000, 16000000000) 422 expTotal(16000000000) 423 424 setup.clock.Run(time.Hour * 2) 425 exp(balance, 8000000000, 4000000000) 426 expTotal(8000000000) 427 setup.stop() 428 429 // Test the functionalities after restart 430 setup = newBalanceTestSetup(setup.db, posExp, negExp) 431 expTotal(8000000000) 432 balance = setup.newNode(0) 433 exp(balance, 8000000000, 4000000000) 434 expTotal(8000000000) 435 setup.clock.Run(time.Hour * 2) 436 exp(balance, 4000000000, 1000000000) 437 expTotal(4000000000) 438 setup.stop() 439 }