github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/filerefsworker.go (about)

     1  package sdk
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"math"
    10  	"net/http"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/0chain/errors"
    15  	"github.com/0chain/gosdk/core/common"
    16  	"github.com/0chain/gosdk/zboxcore/blockchain"
    17  	l "github.com/0chain/gosdk/zboxcore/logger"
    18  	"github.com/0chain/gosdk/zboxcore/marker"
    19  	"github.com/0chain/gosdk/zboxcore/zboxutil"
    20  )
    21  
    22  type ObjectTreeResult struct {
    23  	TotalPages int64               `json:"total_pages"`
    24  	OffsetPath string              `json:"offset_path"`
    25  	OffsetDate string              `json:"offset_date"`
    26  	Refs       []ORef              `json:"refs"`
    27  	LatestWM   *marker.WriteMarker `json:"latest_write_marker"`
    28  }
    29  
    30  const INVALID_PATH = "invalid_path"
    31  
    32  type ObjectTreeRequest struct {
    33  	allocationID   string
    34  	allocationTx   string
    35  	sig            string
    36  	blobbers       []*blockchain.StorageNode
    37  	authToken      string
    38  	pathHash       string
    39  	remotefilepath string
    40  	pageLimit      int // numbers of refs that will be returned by blobber at max
    41  	level          int
    42  	fileType       string
    43  	refType        string
    44  	offsetPath     string
    45  	updatedDate    string // must have "2006-01-02T15:04:05.99999Z07:00" format
    46  	offsetDate     string // must have "2006-01-02T15:04:05.99999Z07:00" format
    47  	ctx            context.Context
    48  	Consensus
    49  }
    50  
    51  type oTreeResponse struct {
    52  	oTResult *ObjectTreeResult
    53  	err      error
    54  	hash     string
    55  	idx      int
    56  }
    57  
    58  // Paginated tree should not be collected as this will stall the client
    59  // It should rather be handled by application that uses gosdk
    60  func (o *ObjectTreeRequest) GetRefs() (*ObjectTreeResult, error) {
    61  	totalBlobbersCount := len(o.blobbers)
    62  	oTreeResponses := make([]oTreeResponse, totalBlobbersCount)
    63  	respChan := make(chan *oTreeResponse, totalBlobbersCount)
    64  	for i, blob := range o.blobbers {
    65  		l.Logger.Debug(fmt.Sprintf("Getting file refs for path %v from blobber %v", o.remotefilepath, blob.Baseurl))
    66  		idx := i
    67  		baseURL := blob.Baseurl
    68  		go o.getFileRefs(baseURL, respChan, idx)
    69  	}
    70  	hashCount := make(map[string]int)
    71  	hashRefsMap := make(map[string]*ObjectTreeResult)
    72  	oTreeResponseErrors := make([]error, totalBlobbersCount)
    73  	var successCount int
    74  	for i := 0; i < totalBlobbersCount; i++ {
    75  		select {
    76  		case <-o.ctx.Done():
    77  			return nil, o.ctx.Err()
    78  		case oTreeResponse := <-respChan:
    79  			oTreeResponseErrors[oTreeResponse.idx] = oTreeResponse.err
    80  			if oTreeResponse.err != nil {
    81  				if code, _ := zboxutil.GetErrorMessageCode(oTreeResponse.err.Error()); code != INVALID_PATH {
    82  					l.Logger.Error("Error while getting file refs from blobber:", oTreeResponse.err)
    83  				}
    84  				continue
    85  			}
    86  			successCount++
    87  			hash := oTreeResponse.hash
    88  			if _, ok := hashCount[hash]; ok {
    89  				hashCount[hash]++
    90  			} else {
    91  				hashCount[hash]++
    92  				hashRefsMap[hash] = oTreeResponse.oTResult
    93  			}
    94  			if hashCount[hash] == o.consensusThresh {
    95  				return oTreeResponse.oTResult, nil
    96  			}
    97  		}
    98  	}
    99  	var selected *ObjectTreeResult
   100  	if successCount < o.consensusThresh {
   101  		majorError := zboxutil.MajorError(oTreeResponseErrors)
   102  		majorErrorMsg := ""
   103  		if majorError != nil {
   104  			majorErrorMsg = majorError.Error()
   105  		}
   106  		if code, _ := zboxutil.GetErrorMessageCode(majorErrorMsg); code == INVALID_PATH {
   107  			return &ObjectTreeResult{}, nil
   108  		} else {
   109  			return nil, majorError
   110  		}
   111  	}
   112  	// build the object tree result by using consensus on individual refs
   113  	refHash := make(map[string]int)
   114  	selected = &ObjectTreeResult{}
   115  	minPage := int64(math.MaxInt64)
   116  	for _, oTreeResponse := range oTreeResponses {
   117  		if oTreeResponse.err != nil {
   118  			continue
   119  		}
   120  		if oTreeResponse.oTResult.TotalPages < minPage {
   121  			minPage = oTreeResponse.oTResult.TotalPages
   122  			selected.TotalPages = minPage
   123  		}
   124  		for _, ref := range oTreeResponse.oTResult.Refs {
   125  			if refHash[ref.FileMetaHash] == o.consensusThresh {
   126  				continue
   127  			}
   128  			refHash[ref.FileMetaHash] += 1
   129  			if refHash[ref.FileMetaHash] == o.consensusThresh {
   130  				selected.Refs = append(selected.Refs, ref)
   131  			}
   132  		}
   133  	}
   134  	if len(selected.Refs) > 0 {
   135  		selected.OffsetPath = selected.Refs[len(selected.Refs)-1].Path
   136  		return selected, nil
   137  	}
   138  	return nil, errors.New("consensus_failed", "Refs consensus is less than consensus threshold")
   139  }
   140  
   141  func (o *ObjectTreeRequest) getFileRefs(bUrl string, respChan chan *oTreeResponse, idx int) {
   142  	oTR := &oTreeResponse{
   143  		idx: idx,
   144  	}
   145  	defer func() {
   146  		respChan <- oTR
   147  	}()
   148  	oReq, err := zboxutil.NewRefsRequest(
   149  		bUrl,
   150  		o.allocationID,
   151  		o.sig,
   152  		o.allocationTx,
   153  		o.remotefilepath,
   154  		o.pathHash,
   155  		o.authToken,
   156  		o.offsetPath,
   157  		o.updatedDate,
   158  		o.offsetDate,
   159  		o.fileType,
   160  		o.refType,
   161  		o.level,
   162  		o.pageLimit,
   163  	)
   164  	if err != nil {
   165  		oTR.err = err
   166  		return
   167  	}
   168  	oResult := ObjectTreeResult{}
   169  	ctx, cncl := context.WithTimeout(o.ctx, 2*time.Minute)
   170  	err = zboxutil.HttpDo(ctx, cncl, oReq, func(resp *http.Response, err error) error {
   171  		if err != nil {
   172  			l.Logger.Error(err)
   173  			return err
   174  		}
   175  		defer resp.Body.Close()
   176  		respBody, err := ioutil.ReadAll(resp.Body)
   177  		if err != nil {
   178  			l.Logger.Error(err)
   179  			return err
   180  		}
   181  		if resp.StatusCode == http.StatusOK {
   182  			err := json.Unmarshal(respBody, &oResult)
   183  			if err != nil {
   184  				l.Logger.Error(err)
   185  				return err
   186  			}
   187  			return nil
   188  		} else {
   189  			return errors.New("response_error", fmt.Sprintf("got status %d, err: %s", resp.StatusCode, respBody))
   190  		}
   191  	})
   192  	if err != nil {
   193  		oTR.err = err
   194  		return
   195  	}
   196  	oTR.oTResult = &oResult
   197  	similarFieldRefs := make([]byte, 0, 32*len(oResult.Refs))
   198  	for _, ref := range oResult.Refs {
   199  		decodeBytes, _ := hex.DecodeString(ref.SimilarField.FileMetaHash)
   200  		similarFieldRefs = append(similarFieldRefs, decodeBytes...)
   201  	}
   202  	oTR.hash = zboxutil.GetRefsHash(similarFieldRefs)
   203  }
   204  
   205  // Blobber response will be different from each other so we should only consider similar fields
   206  // i.e. we cannot calculate hash of response and have consensus on it
   207  type ORef struct {
   208  	SimilarField
   209  	ID        int64            `json:"id"`
   210  	CreatedAt common.Timestamp `json:"created_at"`
   211  	UpdatedAt common.Timestamp `json:"updated_at"`
   212  	Err       error            `json:"-"`
   213  }
   214  
   215  type SimilarField struct {
   216  	FileID              string `json:"file_id"`
   217  	FileMetaHash        string `json:"file_meta_hash"`
   218  	Type                string `json:"type"`
   219  	AllocationID        string `json:"allocation_id"`
   220  	LookupHash          string `json:"lookup_hash"`
   221  	Name                string `json:"name"`
   222  	Path                string `json:"path"`
   223  	PathHash            string `json:"path_hash"`
   224  	ParentPath          string `json:"parent_path"`
   225  	PathLevel           int    `json:"level"`
   226  	Size                int64  `json:"size"`
   227  	EncryptedKey        string `json:"encrypted_key"`
   228  	ActualFileSize      int64  `json:"actual_file_size"`
   229  	ActualFileHash      string `json:"actual_file_hash"`
   230  	MimeType            string `json:"mimetype"`
   231  	ActualThumbnailSize int64  `json:"actual_thumbnail_size"`
   232  	ActualThumbnailHash string `json:"actual_thumbnail_hash"`
   233  	CustomMeta          string `json:"custom_meta"`
   234  }
   235  
   236  type RecentlyAddedRefRequest struct {
   237  	ctx          context.Context
   238  	allocationID string
   239  	allocationTx string
   240  	sig          string
   241  	blobbers     []*blockchain.StorageNode
   242  	fromDate     int64
   243  	offset       int64
   244  	pageLimit    int
   245  	wg           *sync.WaitGroup
   246  	Consensus
   247  }
   248  
   249  type RecentlyAddedRefResult struct {
   250  	Offset int    `json:"offset"`
   251  	Refs   []ORef `json:"refs"`
   252  }
   253  
   254  type RecentlyAddedRefResponse struct {
   255  	Result *RecentlyAddedRefResult
   256  	err    error
   257  }
   258  
   259  func (r *RecentlyAddedRefRequest) GetRecentlyAddedRefs() (*RecentlyAddedRefResult, error) {
   260  	totalBlobbers := len(r.blobbers)
   261  	responses := make([]*RecentlyAddedRefResponse, totalBlobbers)
   262  	for i := range responses {
   263  		responses[i] = &RecentlyAddedRefResponse{}
   264  	}
   265  	r.wg.Add(totalBlobbers)
   266  
   267  	for i, blob := range r.blobbers {
   268  		go r.getRecentlyAddedRefs(responses[i], blob.Baseurl)
   269  	}
   270  	r.wg.Wait()
   271  
   272  	hashCount := make(map[string]int)
   273  	hashRefsMap := make(map[string]*RecentlyAddedRefResult)
   274  
   275  	for _, response := range responses {
   276  		if response.err != nil {
   277  			l.Logger.Error(response.err)
   278  			continue
   279  		}
   280  
   281  		var similarFieldRefs []SimilarField
   282  		for _, ref := range response.Result.Refs {
   283  			similarFieldRefs = append(similarFieldRefs, ref.SimilarField)
   284  		}
   285  
   286  		refsMarshall, err := json.Marshal(similarFieldRefs)
   287  		if err != nil {
   288  			l.Logger.Error(err)
   289  			continue
   290  		}
   291  
   292  		hash := zboxutil.GetRefsHash(refsMarshall)
   293  		if _, ok := hashCount[hash]; ok {
   294  			hashCount[hash]++
   295  		} else {
   296  			hashCount[hash]++
   297  			hashRefsMap[hash] = response.Result
   298  		}
   299  	}
   300  
   301  	var selected *RecentlyAddedRefResult
   302  	for k, v := range hashCount {
   303  		if v >= r.consensusThresh {
   304  			selected = hashRefsMap[k]
   305  			break
   306  		}
   307  	}
   308  
   309  	if selected == nil {
   310  		return nil, errors.New("consensus_failed", "Refs consensus is less than consensus threshold")
   311  	}
   312  	return selected, nil
   313  }
   314  
   315  func (r *RecentlyAddedRefRequest) getRecentlyAddedRefs(resp *RecentlyAddedRefResponse, bUrl string) {
   316  	defer r.wg.Done()
   317  	req, err := zboxutil.NewRecentlyAddedRefsRequest(bUrl, r.allocationID, r.allocationTx, r.sig, r.fromDate, r.offset, r.pageLimit)
   318  	if err != nil {
   319  		resp.err = err
   320  		return
   321  	}
   322  
   323  	result := RecentlyAddedRefResult{}
   324  	ctx, cncl := context.WithTimeout(r.ctx, time.Second*30)
   325  	err = zboxutil.HttpDo(ctx, cncl, req, func(hResp *http.Response, err error) error {
   326  		if err != nil {
   327  			l.Logger.Error(err)
   328  			return err
   329  		}
   330  		defer hResp.Body.Close()
   331  		body, err := ioutil.ReadAll(hResp.Body)
   332  		if err != nil {
   333  			l.Logger.Error(err)
   334  			return err
   335  		}
   336  		if hResp.StatusCode != http.StatusOK {
   337  			return fmt.Errorf("Want code %d, got %d. Message: %s",
   338  				http.StatusOK, hResp.StatusCode, string(body))
   339  		}
   340  		err = json.Unmarshal(body, &result)
   341  		if err != nil {
   342  			l.Logger.Error(err)
   343  		}
   344  		return err
   345  
   346  	})
   347  	if err != nil {
   348  		resp.err = err
   349  		return
   350  	}
   351  	resp.Result = &result
   352  }