github.com/0chain/gosdk@v1.17.11/core/node/node.go (about) 1 // Provides functions and data structures to interact with the system nodes in the context of the blockchain network. 2 package node 3 4 import ( 5 "context" 6 "encoding/json" 7 stdErrors "errors" 8 "fmt" 9 "net/http" 10 "sort" 11 "strconv" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/0chain/errors" 17 "github.com/0chain/gosdk/core/block" 18 "github.com/0chain/gosdk/core/encryption" 19 "github.com/0chain/gosdk/core/util" 20 "github.com/0chain/gosdk/zboxcore/logger" 21 "github.com/ethereum/go-ethereum/common/math" 22 ) 23 24 const statSize = 20 25 const defaultTimeout = 5 * time.Second 26 27 type NodeHolder struct { 28 consensus int 29 guard sync.Mutex 30 stats map[string]*Node 31 nodes []string 32 } 33 34 type Node struct { 35 id string 36 weight int64 37 stats []int 38 } 39 40 func NewHolder(nodes []string, consensus int) *NodeHolder { 41 if len(nodes) < consensus { 42 panic("consensus is not correct") 43 } 44 holder := NodeHolder{consensus: consensus, stats: make(map[string]*Node)} 45 46 for _, n := range nodes { 47 holder.nodes = append(holder.nodes, n) 48 holder.stats[n] = NewNode(n) 49 } 50 return &holder 51 } 52 53 func NewNode(id string) *Node { 54 return &Node{ 55 id: id, 56 weight: 1, 57 stats: []int{1}, 58 } 59 } 60 61 func (h *NodeHolder) Success(id string) { 62 h.guard.Lock() 63 defer h.guard.Unlock() 64 h.adjustNode(id, 1) 65 } 66 67 func (h *NodeHolder) Fail(id string) { 68 h.guard.Lock() 69 defer h.guard.Unlock() 70 h.adjustNode(id, -1) 71 } 72 73 func (h *NodeHolder) adjustNode(id string, res int) { 74 n := NewNode(id) 75 nodes := h.nodes 76 if node, ok := h.stats[id]; ok { 77 for i, v := range nodes { 78 if v == id { 79 nodes = append(nodes[:i], nodes[i+1:]...) 80 break 81 } 82 } 83 84 sourceStats := node.stats 85 sourceStats = append(sourceStats, res) 86 if len(sourceStats) > statSize { 87 sourceStats = sourceStats[1:] 88 } 89 node.stats = sourceStats 90 91 w := int64(0) 92 for i, s := range sourceStats { 93 w += int64(i+1) * int64(s) 94 } 95 node.weight = w 96 97 n = node 98 } 99 100 i := sort.Search(len(nodes), func(i int) bool { 101 return h.stats[nodes[i]].weight < n.weight 102 }) 103 h.nodes = append(nodes[:i], append([]string{n.id}, nodes[i:]...)...) 104 } 105 106 func (h *NodeHolder) Healthy() (res []string) { 107 h.guard.Lock() 108 defer h.guard.Unlock() 109 110 return h.nodes[:h.consensus] 111 } 112 113 func (h *NodeHolder) All() (res []string) { 114 h.guard.Lock() 115 defer h.guard.Unlock() 116 117 return h.nodes 118 } 119 120 const consensusThresh = 25 121 const ( 122 GET_BALANCE = `/v1/client/get/balance?client_id=` 123 CURRENT_ROUND = "/v1/current-round" 124 GET_BLOCK_INFO = `/v1/block/get?` 125 GET_HARDFORK_ROUND = `/v1/screst/6dba10422e368813802877a85039d3985d96760ed844092319743fb3a76712d9/hardfork?name=` 126 ) 127 128 func (h *NodeHolder) GetNonceFromSharders(clientID string) (int64, string, error) { 129 return h.GetBalanceFieldFromSharders(clientID, "nonce") 130 } 131 132 func (h *NodeHolder) GetBalanceFieldFromSharders(clientID, name string) (int64, string, error) { 133 result := make(chan *util.GetResponse) 134 defer close(result) 135 // getMinShardersVerify 136 numSharders := len(h.Healthy()) 137 h.QueryFromSharders(numSharders, fmt.Sprintf("%v%v", GET_BALANCE, clientID), result) 138 139 consensusMaps := util.NewHttpConsensusMaps(consensusThresh) 140 141 for i := 0; i < numSharders; i++ { 142 rsp := <-result 143 if rsp == nil { 144 logger.Logger.Error("nil response") 145 continue 146 } 147 148 logger.Logger.Debug(rsp.Url, rsp.Status) 149 if rsp.StatusCode != http.StatusOK { 150 logger.Logger.Error(rsp.Body) 151 152 } else { 153 logger.Logger.Debug(rsp.Body) 154 } 155 156 if err := consensusMaps.Add(rsp.StatusCode, rsp.Body); err != nil { 157 logger.Logger.Error(rsp.Body) 158 } 159 } 160 161 rate := consensusMaps.MaxConsensus * 100 / numSharders 162 if rate < consensusThresh { 163 if strings.TrimSpace(consensusMaps.WinError) == `{"error":"value not present"}` { 164 return 0, consensusMaps.WinError, nil 165 } 166 return 0, consensusMaps.WinError, errors.New("", "get balance failed. consensus not reached") 167 } 168 169 winValue, ok := consensusMaps.GetValue(name) 170 if ok { 171 winBalance, err := strconv.ParseInt(string(winValue), 10, 64) 172 if err != nil { 173 return 0, "", fmt.Errorf("get balance failed. %w", err) 174 } 175 176 return winBalance, consensusMaps.WinInfo, nil 177 } 178 179 return 0, consensusMaps.WinInfo, errors.New("", "get balance failed. balance field is missed") 180 } 181 182 func (h *NodeHolder) QueryFromSharders(numSharders int, query string, 183 result chan *util.GetResponse) { 184 185 h.QueryFromShardersContext(context.Background(), numSharders, query, result) 186 } 187 188 func (h *NodeHolder) QueryFromShardersContext(ctx context.Context, numSharders int, 189 query string, result chan *util.GetResponse) { 190 191 sharders := h.Healthy() 192 193 for _, sharder := range util.Shuffle(sharders)[:numSharders] { 194 go func(sharderurl string) { 195 logger.Logger.Info("Query from ", sharderurl+query) 196 url := fmt.Sprintf("%v%v", sharderurl, query) 197 timeout, cancelFunc := context.WithTimeout(ctx, defaultTimeout) 198 defer cancelFunc() 199 200 req, err := util.NewHTTPGetRequestContext(timeout, url) 201 if err != nil { 202 logger.Logger.Error(sharderurl, " new get request failed. ", err.Error()) 203 h.Fail(sharderurl) 204 result <- nil 205 return 206 } 207 res, err := req.Get() 208 if err != nil { 209 logger.Logger.Error(sharderurl, " get error. ", err.Error()) 210 } 211 212 if res.StatusCode > http.StatusBadRequest { 213 h.Fail(sharderurl) 214 } else { 215 h.Success(sharderurl) 216 } 217 218 result <- res 219 }(sharder) 220 } 221 } 222 223 func (h *NodeHolder) GetBlockByRound(ctx context.Context, numSharders int, round int64) (b *block.Block, err error) { 224 225 var result = make(chan *util.GetResponse, numSharders) 226 defer close(result) 227 228 numSharders = len(h.Healthy()) // overwrite, use all 229 h.QueryFromShardersContext(ctx, numSharders, 230 fmt.Sprintf("%sround=%d&content=full,header", GET_BLOCK_INFO, round), 231 result) 232 233 var ( 234 maxConsensus int 235 roundConsensus = make(map[string]int) 236 ) 237 238 type respObj struct { 239 Block *block.Block `json:"block"` 240 Header *block.Header `json:"header"` 241 } 242 243 for i := 0; i < numSharders; i++ { 244 var rsp = <-result 245 if rsp == nil { 246 logger.Logger.Error("nil response") 247 continue 248 } 249 logger.Logger.Debug(rsp.Url, rsp.Status) 250 251 if rsp.StatusCode != http.StatusOK { 252 logger.Logger.Error(rsp.Body) 253 continue 254 } 255 256 var respo respObj 257 if err = json.Unmarshal([]byte(rsp.Body), &respo); err != nil { 258 logger.Logger.Error("block parse error: ", err) 259 err = nil 260 continue 261 } 262 263 if respo.Block == nil { 264 logger.Logger.Debug(rsp.Url, "no block in response:", rsp.Body) 265 continue 266 } 267 268 if respo.Header == nil { 269 logger.Logger.Debug(rsp.Url, "no block header in response:", rsp.Body) 270 continue 271 } 272 273 if respo.Header.Hash != string(respo.Block.Hash) { 274 logger.Logger.Debug(rsp.Url, "header and block hash mismatch:", rsp.Body) 275 continue 276 } 277 278 b = respo.Block 279 b.Header = respo.Header 280 281 var h = encryption.FastHash([]byte(b.Hash)) 282 if roundConsensus[h]++; roundConsensus[h] > maxConsensus { 283 maxConsensus = roundConsensus[h] 284 } 285 } 286 287 if maxConsensus == 0 { 288 return nil, errors.New("", "round info not found") 289 } 290 291 return 292 } 293 294 func (h *NodeHolder) GetRoundFromSharders() (int64, error) { 295 296 sharders := h.Healthy() 297 if len(sharders) == 0 { 298 return 0, stdErrors.New("get round failed. no sharders") 299 } 300 301 result := make(chan *util.GetResponse, len(sharders)) 302 303 var numSharders = len(sharders) 304 // use 5 sharders to get round 305 if numSharders > 5 { 306 numSharders = 5 307 } 308 309 h.QueryFromSharders(numSharders, fmt.Sprintf("%v", CURRENT_ROUND), result) 310 311 const consensusThresh = float32(25.0) 312 313 var rounds []int64 314 315 consensus := int64(0) 316 roundMap := make(map[int64]int64) 317 318 round := int64(0) 319 320 waitTimeC := time.After(10 * time.Second) 321 for i := 0; i < numSharders; i++ { 322 select { 323 case <-waitTimeC: 324 return 0, stdErrors.New("get round failed. consensus not reached") 325 case rsp := <-result: 326 if rsp == nil { 327 logger.Logger.Error("nil response") 328 continue 329 } 330 if rsp.StatusCode != http.StatusOK { 331 continue 332 } 333 334 var respRound int64 335 err := json.Unmarshal([]byte(rsp.Body), &respRound) 336 337 if err != nil { 338 continue 339 } 340 341 rounds = append(rounds, respRound) 342 343 sort.Slice(rounds, func(i, j int) bool { 344 return false 345 }) 346 347 medianRound := rounds[len(rounds)/2] 348 349 roundMap[medianRound]++ 350 351 if roundMap[medianRound] > consensus { 352 353 consensus = roundMap[medianRound] 354 round = medianRound 355 rate := consensus * 100 / int64(numSharders) 356 357 if rate >= int64(consensusThresh) { 358 return round, nil 359 } 360 } 361 } 362 } 363 364 return round, nil 365 } 366 367 func (h *NodeHolder) GetHardForkRound(hardFork string) (int64, error) { 368 sharders := h.Healthy() 369 if len(sharders) == 0 { 370 return 0, stdErrors.New("get round failed. no sharders") 371 } 372 373 result := make(chan *util.GetResponse, len(sharders)) 374 375 var numSharders = len(sharders) 376 // use 5 sharders to get round 377 if numSharders > 5 { 378 numSharders = 5 379 } 380 381 h.QueryFromSharders(numSharders, fmt.Sprintf("%s%s", GET_HARDFORK_ROUND, hardFork), result) 382 383 const consensusThresh = float32(25.0) 384 385 var rounds []int64 386 387 consensus := int64(0) 388 roundMap := make(map[int64]int64) 389 // If error then set it to max int64 390 round := int64(math.MaxInt64) 391 392 waitTimeC := time.After(10 * time.Second) 393 for i := 0; i < numSharders; i++ { 394 select { 395 case <-waitTimeC: 396 return 0, stdErrors.New("get round failed. consensus not reached") 397 case rsp := <-result: 398 if rsp == nil { 399 logger.Logger.Error("nil response") 400 continue 401 } 402 if rsp.StatusCode != http.StatusOK { 403 continue 404 } 405 406 var respRound int64 407 var objmap map[string]string 408 err := json.Unmarshal([]byte(rsp.Body), &objmap) 409 if err != nil { 410 continue 411 } 412 413 str := string(objmap["round"]) 414 respRound, err = strconv.ParseInt(str, 10, 64) 415 if err != nil { 416 continue 417 } 418 419 rounds = append(rounds, respRound) 420 421 sort.Slice(rounds, func(i, j int) bool { 422 return false 423 }) 424 425 medianRound := rounds[len(rounds)/2] 426 427 roundMap[medianRound]++ 428 429 if roundMap[medianRound] > consensus { 430 431 consensus = roundMap[medianRound] 432 round = medianRound 433 rate := consensus * 100 / int64(numSharders) 434 435 if rate >= int64(consensusThresh) { 436 return round, nil 437 } 438 } 439 } 440 } 441 442 return round, nil 443 }