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  }