github.com/cryptogateway/go-paymex@v0.0.0-20210204174735-96277fb1e602/les/lespay/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/rand" 21 "reflect" 22 "testing" 23 "time" 24 25 "github.com/cryptogateway/go-paymex/common/mclock" 26 "github.com/cryptogateway/go-paymex/ethdb/memorydb" 27 "github.com/cryptogateway/go-paymex/les/utils" 28 "github.com/cryptogateway/go-paymex/p2p/enode" 29 "github.com/cryptogateway/go-paymex/p2p/enr" 30 "github.com/cryptogateway/go-paymex/p2p/nodestate" 31 ) 32 33 var ( 34 testFlag = testSetup.NewFlag("testFlag") 35 connAddrFlag = testSetup.NewField("connAddr", reflect.TypeOf("")) 36 btTestSetup = NewBalanceTrackerSetup(testSetup) 37 ) 38 39 func init() { 40 btTestSetup.Connect(connAddrFlag, ppTestSetup.CapacityField) 41 } 42 43 type zeroExpirer struct{} 44 45 func (z zeroExpirer) SetRate(now mclock.AbsTime, rate float64) {} 46 func (z zeroExpirer) SetLogOffset(now mclock.AbsTime, logOffset utils.Fixed64) {} 47 func (z zeroExpirer) LogOffset(now mclock.AbsTime) utils.Fixed64 { return 0 } 48 49 type balanceTestSetup struct { 50 clock *mclock.Simulated 51 ns *nodestate.NodeStateMachine 52 bt *BalanceTracker 53 } 54 55 func newBalanceTestSetup() *balanceTestSetup { 56 clock := &mclock.Simulated{} 57 ns := nodestate.NewNodeStateMachine(nil, nil, clock, testSetup) 58 db := memorydb.New() 59 bt := NewBalanceTracker(ns, btTestSetup, db, clock, zeroExpirer{}, zeroExpirer{}) 60 ns.Start() 61 return &balanceTestSetup{ 62 clock: clock, 63 ns: ns, 64 bt: bt, 65 } 66 } 67 68 func (b *balanceTestSetup) newNode(capacity uint64) *NodeBalance { 69 node := enode.SignNull(&enr.Record{}, enode.ID{}) 70 b.ns.SetState(node, testFlag, nodestate.Flags{}, 0) 71 b.ns.SetField(node, btTestSetup.connAddressField, "") 72 b.ns.SetField(node, ppTestSetup.CapacityField, capacity) 73 n, _ := b.ns.GetField(node, btTestSetup.BalanceField).(*NodeBalance) 74 return n 75 } 76 77 func (b *balanceTestSetup) stop() { 78 b.bt.Stop() 79 b.ns.Stop() 80 } 81 82 func TestAddBalance(t *testing.T) { 83 b := newBalanceTestSetup() 84 defer b.stop() 85 86 node := b.newNode(1000) 87 var inputs = []struct { 88 delta int64 89 expect [2]uint64 90 total uint64 91 expectErr bool 92 }{ 93 {100, [2]uint64{0, 100}, 100, false}, 94 {-100, [2]uint64{100, 0}, 0, false}, 95 {-100, [2]uint64{0, 0}, 0, false}, 96 {1, [2]uint64{0, 1}, 1, false}, 97 {maxBalance, [2]uint64{0, 0}, 0, true}, 98 } 99 for _, i := range inputs { 100 old, new, err := node.AddBalance(i.delta) 101 if i.expectErr { 102 if err == nil { 103 t.Fatalf("Expect get error but nil") 104 } 105 continue 106 } else if err != nil { 107 t.Fatalf("Expect get no error but %v", err) 108 } 109 if old != i.expect[0] || new != i.expect[1] { 110 t.Fatalf("Positive balance mismatch, got %v -> %v", old, new) 111 } 112 if b.bt.TotalTokenAmount() != i.total { 113 t.Fatalf("Total positive balance mismatch, want %v, got %v", i.total, b.bt.TotalTokenAmount()) 114 } 115 } 116 } 117 118 func TestSetBalance(t *testing.T) { 119 b := newBalanceTestSetup() 120 defer b.stop() 121 node := b.newNode(1000) 122 123 var inputs = []struct { 124 pos, neg uint64 125 }{ 126 {1000, 0}, 127 {0, 1000}, 128 {1000, 1000}, 129 } 130 131 for _, i := range inputs { 132 node.SetBalance(i.pos, i.neg) 133 pos, neg := node.GetBalance() 134 if pos != i.pos { 135 t.Fatalf("Positive balance mismatch, want %v, got %v", i.pos, pos) 136 } 137 if neg != i.neg { 138 t.Fatalf("Negative balance mismatch, want %v, got %v", i.neg, neg) 139 } 140 } 141 } 142 143 func TestBalanceTimeCost(t *testing.T) { 144 b := newBalanceTestSetup() 145 defer b.stop() 146 node := b.newNode(1000) 147 148 b.ns.SetField(node.node, ppTestSetup.CapacityField, uint64(1)) 149 node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) 150 node.SetBalance(uint64(time.Minute), 0) // 1 minute time allowance 151 152 var inputs = []struct { 153 runTime time.Duration 154 expPos uint64 155 expNeg uint64 156 }{ 157 {time.Second, uint64(time.Second * 59), 0}, 158 {0, uint64(time.Second * 59), 0}, 159 {time.Second * 59, 0, 0}, 160 {time.Second, 0, uint64(time.Second)}, 161 } 162 for _, i := range inputs { 163 b.clock.Run(i.runTime) 164 if pos, _ := node.GetBalance(); pos != i.expPos { 165 t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) 166 } 167 if _, neg := node.GetBalance(); neg != i.expNeg { 168 t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) 169 } 170 } 171 172 node.SetBalance(uint64(time.Minute), 0) // Refill 1 minute time allowance 173 for _, i := range inputs { 174 b.clock.Run(i.runTime) 175 if pos, _ := node.GetBalance(); pos != i.expPos { 176 t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) 177 } 178 if _, neg := node.GetBalance(); neg != i.expNeg { 179 t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) 180 } 181 } 182 } 183 184 func TestBalanceReqCost(t *testing.T) { 185 b := newBalanceTestSetup() 186 defer b.stop() 187 node := b.newNode(1000) 188 node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) 189 190 b.ns.SetField(node.node, ppTestSetup.CapacityField, uint64(1)) 191 node.SetBalance(uint64(time.Minute), 0) // 1 minute time serving time allowance 192 var inputs = []struct { 193 reqCost uint64 194 expPos uint64 195 expNeg uint64 196 }{ 197 {uint64(time.Second), uint64(time.Second * 59), 0}, 198 {0, uint64(time.Second * 59), 0}, 199 {uint64(time.Second * 59), 0, 0}, 200 {uint64(time.Second), 0, uint64(time.Second)}, 201 } 202 for _, i := range inputs { 203 node.RequestServed(i.reqCost) 204 if pos, _ := node.GetBalance(); pos != i.expPos { 205 t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) 206 } 207 if _, neg := node.GetBalance(); neg != i.expNeg { 208 t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) 209 } 210 } 211 } 212 213 func TestBalanceToPriority(t *testing.T) { 214 b := newBalanceTestSetup() 215 defer b.stop() 216 node := b.newNode(1000) 217 node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) 218 219 var inputs = []struct { 220 pos uint64 221 neg uint64 222 priority int64 223 }{ 224 {1000, 0, 1}, 225 {2000, 0, 2}, // Higher balance, higher priority value 226 {0, 0, 0}, 227 {0, 1000, -1000}, 228 } 229 for _, i := range inputs { 230 node.SetBalance(i.pos, i.neg) 231 priority := node.Priority(b.clock.Now(), 1000) 232 if priority != i.priority { 233 t.Fatalf("Priority mismatch, want %v, got %v", i.priority, priority) 234 } 235 } 236 } 237 238 func TestEstimatedPriority(t *testing.T) { 239 b := newBalanceTestSetup() 240 defer b.stop() 241 node := b.newNode(1000000000) 242 node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) 243 244 b.ns.SetField(node.node, ppTestSetup.CapacityField, uint64(1)) 245 node.SetBalance(uint64(time.Minute), 0) 246 var inputs = []struct { 247 runTime time.Duration // time cost 248 futureTime time.Duration // diff of future time 249 reqCost uint64 // single request cost 250 priority int64 // expected estimated priority 251 }{ 252 {time.Second, time.Second, 0, 58}, 253 {0, time.Second, 0, 58}, 254 255 // 2 seconds time cost, 1 second estimated time cost, 10^9 request cost, 256 // 10^9 estimated request cost per second. 257 {time.Second, time.Second, 1000000000, 55}, 258 259 // 3 seconds time cost, 3 second estimated time cost, 10^9*2 request cost, 260 // 4*10^9 estimated request cost. 261 {time.Second, 3 * time.Second, 1000000000, 48}, 262 263 // All positive balance is used up 264 {time.Second * 55, 0, 0, 0}, 265 266 // 1 minute estimated time cost, 4/58 * 10^9 estimated request cost per sec. 267 {0, time.Minute, 0, -int64(time.Minute) - int64(time.Second)*120/29}, 268 } 269 for _, i := range inputs { 270 b.clock.Run(i.runTime) 271 node.RequestServed(i.reqCost) 272 priority := node.EstMinPriority(b.clock.Now()+mclock.AbsTime(i.futureTime), 1000000000, false) 273 if priority != i.priority { 274 t.Fatalf("Estimated priority mismatch, want %v, got %v", i.priority, priority) 275 } 276 } 277 } 278 279 func TestPosBalanceMissing(t *testing.T) { 280 b := newBalanceTestSetup() 281 defer b.stop() 282 node := b.newNode(1000) 283 node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) 284 285 b.ns.SetField(node.node, ppTestSetup.CapacityField, uint64(1)) 286 var inputs = []struct { 287 pos, neg uint64 288 priority int64 289 cap uint64 290 after time.Duration 291 expect uint64 292 }{ 293 {uint64(time.Second * 2), 0, 0, 1, time.Second, 0}, 294 {uint64(time.Second * 2), 0, 0, 1, 2 * time.Second, 1}, 295 {uint64(time.Second * 2), 0, int64(time.Second), 1, 2 * time.Second, uint64(time.Second) + 1}, 296 {0, 0, int64(time.Second), 1, time.Second, uint64(2*time.Second) + 1}, 297 {0, 0, -int64(time.Second), 1, time.Second, 1}, 298 } 299 for _, i := range inputs { 300 node.SetBalance(i.pos, i.neg) 301 got := node.PosBalanceMissing(i.priority, i.cap, i.after) 302 if got != i.expect { 303 t.Fatalf("Missing budget mismatch, want %v, got %v", i.expect, got) 304 } 305 } 306 } 307 308 func TestPostiveBalanceCounting(t *testing.T) { 309 b := newBalanceTestSetup() 310 defer b.stop() 311 312 var nodes []*NodeBalance 313 for i := 0; i < 100; i += 1 { 314 node := b.newNode(1000000) 315 node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) 316 nodes = append(nodes, node) 317 } 318 319 // Allocate service token 320 var sum uint64 321 for i := 0; i < 100; i += 1 { 322 amount := int64(rand.Intn(100) + 100) 323 nodes[i].AddBalance(amount) 324 sum += uint64(amount) 325 } 326 if b.bt.TotalTokenAmount() != sum { 327 t.Fatalf("Invalid token amount") 328 } 329 330 // Change client status 331 for i := 0; i < 100; i += 1 { 332 if rand.Intn(2) == 0 { 333 b.ns.SetField(nodes[i].node, ppTestSetup.CapacityField, uint64(1)) 334 } 335 } 336 if b.bt.TotalTokenAmount() != sum { 337 t.Fatalf("Invalid token amount") 338 } 339 for i := 0; i < 100; i += 1 { 340 if rand.Intn(2) == 0 { 341 b.ns.SetField(nodes[i].node, ppTestSetup.CapacityField, uint64(1)) 342 } 343 } 344 if b.bt.TotalTokenAmount() != sum { 345 t.Fatalf("Invalid token amount") 346 } 347 } 348 349 func TestCallbackChecking(t *testing.T) { 350 b := newBalanceTestSetup() 351 defer b.stop() 352 node := b.newNode(1000000) 353 node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) 354 355 var inputs = []struct { 356 priority int64 357 expDiff time.Duration 358 }{ 359 {500, time.Millisecond * 500}, 360 {0, time.Second}, 361 {-int64(time.Second), 2 * time.Second}, 362 } 363 node.SetBalance(uint64(time.Second), 0) 364 for _, i := range inputs { 365 diff, _ := node.timeUntil(i.priority) 366 if diff != i.expDiff { 367 t.Fatalf("Time difference mismatch, want %v, got %v", i.expDiff, diff) 368 } 369 } 370 } 371 372 func TestCallback(t *testing.T) { 373 b := newBalanceTestSetup() 374 defer b.stop() 375 node := b.newNode(1000) 376 node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) 377 b.ns.SetField(node.node, ppTestSetup.CapacityField, uint64(1)) 378 379 callCh := make(chan struct{}, 1) 380 node.SetBalance(uint64(time.Minute), 0) 381 node.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} }) 382 383 b.clock.Run(time.Minute) 384 select { 385 case <-callCh: 386 case <-time.NewTimer(time.Second).C: 387 t.Fatalf("Callback hasn't been called yet") 388 } 389 390 node.SetBalance(uint64(time.Minute), 0) 391 node.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} }) 392 node.removeCallback(balanceCallbackZero) 393 394 b.clock.Run(time.Minute) 395 select { 396 case <-callCh: 397 t.Fatalf("Callback shouldn't be called") 398 case <-time.NewTimer(time.Millisecond * 100).C: 399 } 400 }