github.com/hardtosaygoodbye/go-ethereum@v1.10.16-0.20220122011429-97003b9e6c15/les/utils/limiter_test.go (about) 1 // Copyright 2020 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 utils 18 19 import ( 20 "math/rand" 21 "testing" 22 23 "github.com/hardtosaygoodbye/go-ethereum/p2p/enode" 24 ) 25 26 const ( 27 ltTolerance = 0.03 28 ltRounds = 7 29 ) 30 31 type ( 32 ltNode struct { 33 addr, id int 34 value, exp float64 35 cost uint 36 reqRate float64 37 reqMax, runCount int 38 lastTotalCost uint 39 40 served, dropped int 41 } 42 43 ltResult struct { 44 node *ltNode 45 ch chan struct{} 46 } 47 48 limTest struct { 49 limiter *Limiter 50 results chan ltResult 51 runCount int 52 expCost, totalCost uint 53 } 54 ) 55 56 func (lt *limTest) request(n *ltNode) { 57 var ( 58 address string 59 id enode.ID 60 ) 61 if n.addr >= 0 { 62 address = string([]byte{byte(n.addr)}) 63 } else { 64 var b [32]byte 65 rand.Read(b[:]) 66 address = string(b[:]) 67 } 68 if n.id >= 0 { 69 id = enode.ID{byte(n.id)} 70 } else { 71 rand.Read(id[:]) 72 } 73 lt.runCount++ 74 n.runCount++ 75 cch := lt.limiter.Add(id, address, n.value, n.cost) 76 go func() { 77 lt.results <- ltResult{n, <-cch} 78 }() 79 } 80 81 func (lt *limTest) moreRequests(n *ltNode) { 82 maxStart := int(float64(lt.totalCost-n.lastTotalCost) * n.reqRate) 83 if maxStart != 0 { 84 n.lastTotalCost = lt.totalCost 85 } 86 for n.reqMax > n.runCount && maxStart > 0 { 87 lt.request(n) 88 maxStart-- 89 } 90 } 91 92 func (lt *limTest) process() { 93 res := <-lt.results 94 lt.runCount-- 95 res.node.runCount-- 96 if res.ch != nil { 97 res.node.served++ 98 if res.node.exp != 0 { 99 lt.expCost += res.node.cost 100 } 101 lt.totalCost += res.node.cost 102 close(res.ch) 103 } else { 104 res.node.dropped++ 105 } 106 } 107 108 func TestLimiter(t *testing.T) { 109 limTests := [][]*ltNode{ 110 { // one id from an individual address and two ids from a shared address 111 {addr: 0, id: 0, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.5}, 112 {addr: 1, id: 1, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, 113 {addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, 114 }, 115 { // varying request costs 116 {addr: 0, id: 0, value: 0, cost: 10, reqRate: 0.2, reqMax: 1, exp: 0.5}, 117 {addr: 1, id: 1, value: 0, cost: 3, reqRate: 0.5, reqMax: 1, exp: 0.25}, 118 {addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, 119 }, 120 { // different request rate 121 {addr: 0, id: 0, value: 0, cost: 1, reqRate: 2, reqMax: 2, exp: 0.5}, 122 {addr: 1, id: 1, value: 0, cost: 1, reqRate: 10, reqMax: 10, exp: 0.25}, 123 {addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, 124 }, 125 { // adding value 126 {addr: 0, id: 0, value: 3, cost: 1, reqRate: 1, reqMax: 1, exp: (0.5 + 0.3) / 2}, 127 {addr: 1, id: 1, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25 / 2}, 128 {addr: 1, id: 2, value: 7, cost: 1, reqRate: 1, reqMax: 1, exp: (0.25 + 0.7) / 2}, 129 }, 130 { // DoS attack from a single address with a single id 131 {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, 132 {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, 133 {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, 134 {addr: 3, id: 3, value: 0, cost: 1, reqRate: 10, reqMax: 1000000000, exp: 0}, 135 }, 136 { // DoS attack from a single address with different ids 137 {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, 138 {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, 139 {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, 140 {addr: 3, id: -1, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0}, 141 }, 142 { // DDoS attack from different addresses with a single id 143 {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, 144 {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, 145 {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, 146 {addr: -1, id: 3, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0}, 147 }, 148 { // DDoS attack from different addresses with different ids 149 {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, 150 {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, 151 {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, 152 {addr: -1, id: -1, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0}, 153 }, 154 } 155 156 lt := &limTest{ 157 limiter: NewLimiter(100), 158 results: make(chan ltResult), 159 } 160 for _, test := range limTests { 161 lt.expCost, lt.totalCost = 0, 0 162 iterCount := 10000 163 for j := 0; j < ltRounds; j++ { 164 // try to reach expected target range in multiple rounds with increasing iteration counts 165 last := j == ltRounds-1 166 for _, n := range test { 167 lt.request(n) 168 } 169 for i := 0; i < iterCount; i++ { 170 lt.process() 171 for _, n := range test { 172 lt.moreRequests(n) 173 } 174 } 175 for lt.runCount > 0 { 176 lt.process() 177 } 178 if spamRatio := 1 - float64(lt.expCost)/float64(lt.totalCost); spamRatio > 0.5*(1+ltTolerance) { 179 t.Errorf("Spam ratio too high (%f)", spamRatio) 180 } 181 fail, success := false, true 182 for _, n := range test { 183 if n.exp != 0 { 184 if n.dropped > 0 { 185 t.Errorf("Dropped %d requests of non-spam node", n.dropped) 186 fail = true 187 } 188 r := float64(n.served) * float64(n.cost) / float64(lt.expCost) 189 if r < n.exp*(1-ltTolerance) || r > n.exp*(1+ltTolerance) { 190 if last { 191 // print error only if the target is still not reached in the last round 192 t.Errorf("Request ratio (%f) does not match expected value (%f)", r, n.exp) 193 } 194 success = false 195 } 196 } 197 } 198 if fail || success { 199 break 200 } 201 // neither failed nor succeeded; try more iterations to reach probability targets 202 iterCount *= 2 203 } 204 } 205 lt.limiter.Stop() 206 }