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

     1  package sdk
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/hex"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"mime/multipart"
    12  	"net/http"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"time"
    17  
    18  	"github.com/0chain/errors"
    19  	thrown "github.com/0chain/errors"
    20  	"github.com/0chain/gosdk/zboxcore/allocationchange"
    21  	"github.com/0chain/gosdk/zboxcore/blockchain"
    22  	"github.com/0chain/gosdk/zboxcore/client"
    23  	"github.com/0chain/gosdk/zboxcore/fileref"
    24  	"github.com/0chain/gosdk/zboxcore/logger"
    25  	l "github.com/0chain/gosdk/zboxcore/logger"
    26  	"github.com/0chain/gosdk/zboxcore/marker"
    27  	"github.com/0chain/gosdk/zboxcore/zboxutil"
    28  	"github.com/minio/sha256-simd"
    29  )
    30  
    31  type ReferencePathResult struct {
    32  	*fileref.ReferencePath
    33  	LatestWM *marker.WriteMarker `json:"latest_write_marker"`
    34  	Version  string              `json:"version"`
    35  }
    36  
    37  type CommitResult struct {
    38  	Success      bool   `json:"success"`
    39  	ErrorMessage string `json:"error_msg,omitempty"`
    40  }
    41  
    42  func ErrorCommitResult(errMsg string) *CommitResult {
    43  	result := &CommitResult{Success: false, ErrorMessage: errMsg}
    44  	return result
    45  }
    46  
    47  func SuccessCommitResult() *CommitResult {
    48  	result := &CommitResult{Success: true}
    49  	return result
    50  }
    51  
    52  const MARKER_VERSION = "v2"
    53  
    54  type CommitRequest struct {
    55  	changes      []allocationchange.AllocationChange
    56  	blobber      *blockchain.StorageNode
    57  	allocationID string
    58  	allocationTx string
    59  	connectionID string
    60  	sig          string
    61  	wg           *sync.WaitGroup
    62  	result       *CommitResult
    63  	timestamp    int64
    64  	blobberInd   uint64
    65  }
    66  
    67  var commitChan map[string]chan *CommitRequest
    68  var initCommitMutex sync.Mutex
    69  
    70  func InitCommitWorker(blobbers []*blockchain.StorageNode) {
    71  	initCommitMutex.Lock()
    72  	defer initCommitMutex.Unlock()
    73  	if commitChan == nil {
    74  		commitChan = make(map[string]chan *CommitRequest)
    75  	}
    76  
    77  	for _, blobber := range blobbers {
    78  		if _, ok := commitChan[blobber.ID]; !ok {
    79  			commitChan[blobber.ID] = make(chan *CommitRequest, 1)
    80  			blobberChan := commitChan[blobber.ID]
    81  			go startCommitWorker(blobberChan, blobber.ID)
    82  		}
    83  	}
    84  
    85  }
    86  
    87  func startCommitWorker(blobberChan chan *CommitRequest, blobberID string) {
    88  	for {
    89  		commitreq, open := <-blobberChan
    90  		if !open {
    91  			break
    92  		}
    93  		commitreq.processCommit()
    94  	}
    95  	initCommitMutex.Lock()
    96  	defer initCommitMutex.Unlock()
    97  	delete(commitChan, blobberID)
    98  }
    99  
   100  func (commitreq *CommitRequest) processCommit() {
   101  	defer commitreq.wg.Done()
   102  	start := time.Now()
   103  	l.Logger.Debug("received a commit request")
   104  	paths := make([]string, 0)
   105  	for _, change := range commitreq.changes {
   106  		paths = append(paths, change.GetAffectedPath()...)
   107  	}
   108  	if len(paths) == 0 {
   109  		l.Logger.Debug("Nothing to commit")
   110  		commitreq.result = SuccessCommitResult()
   111  		return
   112  	}
   113  	var req *http.Request
   114  	var lR ReferencePathResult
   115  	req, err := zboxutil.NewReferencePathRequest(commitreq.blobber.Baseurl, commitreq.allocationID, commitreq.allocationTx, commitreq.sig, paths)
   116  	if err != nil {
   117  		l.Logger.Error("Creating ref path req", err)
   118  		return
   119  	}
   120  	ctx, cncl := context.WithTimeout(context.Background(), (time.Second * 30))
   121  	err = zboxutil.HttpDo(ctx, cncl, req, func(resp *http.Response, err error) error {
   122  		if err != nil {
   123  			l.Logger.Error("Ref path error:", err)
   124  			return err
   125  		}
   126  		defer resp.Body.Close()
   127  		if resp.StatusCode != http.StatusOK {
   128  			l.Logger.Error("Ref path response : ", resp.StatusCode)
   129  		}
   130  		resp_body, err := ioutil.ReadAll(resp.Body)
   131  		if err != nil {
   132  			l.Logger.Error("Ref path: Resp", err)
   133  			return err
   134  		}
   135  		if resp.StatusCode != http.StatusOK {
   136  			return errors.New(
   137  				strconv.Itoa(resp.StatusCode),
   138  				fmt.Sprintf("Reference path error response: Status: %d - %s ",
   139  					resp.StatusCode, string(resp_body)))
   140  		}
   141  		err = json.Unmarshal(resp_body, &lR)
   142  		if err != nil {
   143  			l.Logger.Error("Reference path json decode error: ", err)
   144  			return err
   145  		}
   146  		return nil
   147  	})
   148  
   149  	if err != nil {
   150  		commitreq.result = ErrorCommitResult(err.Error())
   151  		return
   152  	}
   153  	rootRef, err := lR.GetDirTree(commitreq.allocationID)
   154  
   155  	if err != nil {
   156  		commitreq.result = ErrorCommitResult(err.Error())
   157  		return
   158  	}
   159  	hasher := sha256.New()
   160  	if lR.LatestWM != nil {
   161  		err = lR.LatestWM.VerifySignature(client.GetClientPublicKey())
   162  		if err != nil {
   163  			e := errors.New("signature_verification_failed", err.Error())
   164  			commitreq.result = ErrorCommitResult(e.Error())
   165  			return
   166  		}
   167  		if commitreq.timestamp <= lR.LatestWM.Timestamp {
   168  			commitreq.timestamp = lR.LatestWM.Timestamp + 1
   169  		}
   170  
   171  		rootRef.CalculateHash()
   172  		prevAllocationRoot := rootRef.Hash
   173  		if prevAllocationRoot != lR.LatestWM.AllocationRoot {
   174  			l.Logger.Error("Allocation root from latest writemarker mismatch. Expected: " + prevAllocationRoot + " got: " + lR.LatestWM.AllocationRoot)
   175  			errMsg := fmt.Sprintf(
   176  				"calculated allocation root mismatch from blobber %s. Expected: %s, Got: %s",
   177  				commitreq.blobber.Baseurl, prevAllocationRoot, lR.LatestWM.AllocationRoot)
   178  			commitreq.result = ErrorCommitResult(errMsg)
   179  			return
   180  		}
   181  		if lR.LatestWM.ChainHash != "" {
   182  			prevChainHash, err := hex.DecodeString(lR.LatestWM.ChainHash)
   183  			if err != nil {
   184  				commitreq.result = ErrorCommitResult(err.Error())
   185  				return
   186  			}
   187  			hasher.Write(prevChainHash) //nolint:errcheck
   188  		}
   189  	}
   190  
   191  	var size int64
   192  	fileIDMeta := make(map[string]string)
   193  
   194  	for _, change := range commitreq.changes {
   195  		err = change.ProcessChange(rootRef, fileIDMeta)
   196  		if err != nil {
   197  			if !errors.Is(err, allocationchange.ErrRefNotFound) {
   198  				commitreq.result = ErrorCommitResult(err.Error())
   199  				return
   200  			}
   201  		} else {
   202  			size += change.GetSize()
   203  		}
   204  	}
   205  	rootRef.CalculateHash()
   206  	var chainHash string
   207  	if lR.Version == MARKER_VERSION {
   208  		decodedHash, _ := hex.DecodeString(rootRef.Hash)
   209  		hasher.Write(decodedHash) //nolint:errcheck
   210  		chainHash = hex.EncodeToString(hasher.Sum(nil))
   211  	}
   212  	err = commitreq.commitBlobber(rootRef, chainHash, lR.LatestWM, size, fileIDMeta)
   213  	if err != nil {
   214  		commitreq.result = ErrorCommitResult(err.Error())
   215  		return
   216  	}
   217  	l.Logger.Debug("[commitBlobber]", time.Since(start).Milliseconds())
   218  	commitreq.result = SuccessCommitResult()
   219  }
   220  
   221  func (req *CommitRequest) commitBlobber(
   222  	rootRef *fileref.Ref, chainHash string, latestWM *marker.WriteMarker, size int64,
   223  	fileIDMeta map[string]string) (err error) {
   224  
   225  	fileIDMetaData, err := json.Marshal(fileIDMeta)
   226  	if err != nil {
   227  		l.Logger.Error("Marshalling inode metadata failed: ", err)
   228  		return err
   229  	}
   230  
   231  	wm := &marker.WriteMarker{}
   232  	wm.AllocationRoot = rootRef.Hash
   233  	wm.ChainSize = size
   234  	if latestWM != nil {
   235  		wm.PreviousAllocationRoot = latestWM.AllocationRoot
   236  		wm.ChainSize += latestWM.ChainSize
   237  	} else {
   238  		wm.PreviousAllocationRoot = ""
   239  	}
   240  	if wm.AllocationRoot == wm.PreviousAllocationRoot {
   241  		l.Logger.Debug("Allocation root and previous allocation root are same")
   242  		return nil
   243  	}
   244  	wm.ChainHash = chainHash
   245  	wm.FileMetaRoot = rootRef.FileMetaHash
   246  	wm.AllocationID = req.allocationID
   247  	wm.Size = size
   248  	wm.BlobberID = req.blobber.ID
   249  	wm.Timestamp = req.timestamp
   250  	wm.ClientID = client.GetClientID()
   251  	err = wm.Sign()
   252  	if err != nil {
   253  		l.Logger.Error("Signing writemarker failed: ", err)
   254  		return err
   255  	}
   256  	wmData, err := json.Marshal(wm)
   257  	if err != nil {
   258  		l.Logger.Error("Creating writemarker failed: ", err)
   259  		return err
   260  	}
   261  
   262  	l.Logger.Debug("Committing to blobber." + req.blobber.Baseurl)
   263  	var (
   264  		resp           *http.Response
   265  		shouldContinue bool
   266  	)
   267  	for retries := 0; retries < 6; retries++ {
   268  		err, shouldContinue = func() (err error, shouldContinue bool) {
   269  			body := new(bytes.Buffer)
   270  			formWriter, err := getFormWritter(req.connectionID, wmData, fileIDMetaData, body)
   271  			if err != nil {
   272  				l.Logger.Error("Creating form writer failed: ", err)
   273  				return
   274  			}
   275  			httpreq, err := zboxutil.NewCommitRequest(req.blobber.Baseurl, req.allocationID, req.allocationTx, body)
   276  			if err != nil {
   277  				l.Logger.Error("Error creating commit req: ", err)
   278  				return
   279  			}
   280  			httpreq.Header.Add("Content-Type", formWriter.FormDataContentType())
   281  			reqCtx, ctxCncl := context.WithTimeout(context.Background(), time.Second*60)
   282  			resp, err = zboxutil.Client.Do(httpreq.WithContext(reqCtx))
   283  			defer ctxCncl()
   284  
   285  			if err != nil {
   286  				logger.Logger.Error("Commit: ", err)
   287  				return
   288  			}
   289  
   290  			if resp.Body != nil {
   291  				defer resp.Body.Close()
   292  			}
   293  
   294  			var respBody []byte
   295  			respBody, err = io.ReadAll(resp.Body)
   296  			if err != nil {
   297  				logger.Logger.Error("Response read: ", err)
   298  				return
   299  			}
   300  			if resp.StatusCode == http.StatusOK {
   301  				logger.Logger.Debug(req.blobber.Baseurl, " committed")
   302  				return
   303  			}
   304  
   305  			if resp.StatusCode == http.StatusTooManyRequests {
   306  				logger.Logger.Debug(req.blobber.Baseurl,
   307  					" got too many request error. Retrying")
   308  
   309  				var r int
   310  				r, err = zboxutil.GetRateLimitValue(resp)
   311  				if err != nil {
   312  					logger.Logger.Error(err)
   313  					return
   314  				}
   315  
   316  				time.Sleep(time.Duration(r) * time.Second)
   317  				shouldContinue = true
   318  				return
   319  			}
   320  
   321  			if strings.Contains(string(respBody), "pending_markers:") {
   322  				logger.Logger.Debug("Commit pending for blobber ",
   323  					req.blobber.Baseurl, " Retrying")
   324  				time.Sleep(5 * time.Second)
   325  				shouldContinue = true
   326  				return
   327  			}
   328  
   329  			if strings.Contains(string(respBody), "chain_length_exceeded") {
   330  				l.Logger.Error("Chain length exceeded for blobber ",
   331  					req.blobber.Baseurl, " Retrying")
   332  				time.Sleep(5 * time.Second)
   333  				shouldContinue = true
   334  				return
   335  			}
   336  
   337  			err = thrown.New("commit_error",
   338  				fmt.Sprintf("Got error response %s with status %d", respBody, resp.StatusCode))
   339  			return
   340  		}()
   341  		if shouldContinue {
   342  			continue
   343  		}
   344  		return
   345  	}
   346  	return thrown.New("commit_error", fmt.Sprintf("Commit failed with response status %d", resp.StatusCode))
   347  }
   348  
   349  func AddCommitRequest(req *CommitRequest) {
   350  	commitChan[req.blobber.ID] <- req
   351  }
   352  
   353  func (commitreq *CommitRequest) calculateHashRequest(ctx context.Context, paths []string) error { //nolint
   354  	var req *http.Request
   355  	req, err := zboxutil.NewCalculateHashRequest(commitreq.blobber.Baseurl, commitreq.allocationID, commitreq.allocationTx, paths)
   356  	if err != nil || len(paths) == 0 {
   357  		l.Logger.Error("Creating calculate hash req", err)
   358  		return err
   359  	}
   360  	ctx, cncl := context.WithTimeout(ctx, (time.Second * 30))
   361  	err = zboxutil.HttpDo(ctx, cncl, req, func(resp *http.Response, err error) error {
   362  		if err != nil {
   363  			l.Logger.Error("Calculate hash error:", err)
   364  			return err
   365  		}
   366  		defer resp.Body.Close()
   367  		if resp.StatusCode != http.StatusOK {
   368  			l.Logger.Error("Calculate hash response : ", resp.StatusCode)
   369  		}
   370  		resp_body, err := ioutil.ReadAll(resp.Body)
   371  		if err != nil {
   372  			l.Logger.Error("Calculate hash: Resp", err)
   373  			return err
   374  		}
   375  		if resp.StatusCode != http.StatusOK {
   376  			return errors.New(strconv.Itoa(resp.StatusCode), fmt.Sprintf("Calculate hash error response: Body: %s ", string(resp_body)))
   377  		}
   378  		return nil
   379  	})
   380  	return err
   381  }
   382  
   383  func getFormWritter(connectionID string, wmData, fileIDMetaData []byte, body *bytes.Buffer) (*multipart.Writer, error) {
   384  	formWriter := multipart.NewWriter(body)
   385  	err := formWriter.WriteField("connection_id", connectionID)
   386  	if err != nil {
   387  		return nil, err
   388  	}
   389  
   390  	err = formWriter.WriteField("write_marker", string(wmData))
   391  	if err != nil {
   392  		return nil, err
   393  	}
   394  
   395  	err = formWriter.WriteField("file_id_meta", string(fileIDMetaData))
   396  	if err != nil {
   397  		return nil, err
   398  	}
   399  	formWriter.Close()
   400  	return formWriter, nil
   401  }