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

     1  package sdk
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/hex"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"mime/multipart"
    11  	"strings"
    12  	"sync"
    13  	"sync/atomic"
    14  	"time"
    15  
    16  	"net/http"
    17  
    18  	"errors"
    19  
    20  	"github.com/0chain/common/core/common"
    21  	thrown "github.com/0chain/errors"
    22  	"github.com/0chain/gosdk/zboxcore/blockchain"
    23  	"github.com/0chain/gosdk/zboxcore/client"
    24  	l "github.com/0chain/gosdk/zboxcore/logger"
    25  	"github.com/0chain/gosdk/zboxcore/marker"
    26  	"github.com/0chain/gosdk/zboxcore/zboxutil"
    27  	"github.com/minio/sha256-simd"
    28  	"go.uber.org/zap"
    29  )
    30  
    31  type LatestPrevWriteMarker struct {
    32  	LatestWM *marker.WriteMarker `json:"latest_write_marker"`
    33  	PrevWM   *marker.WriteMarker `json:"prev_write_marker"`
    34  	Version  string              `json:"version"`
    35  }
    36  
    37  type AllocStatus byte
    38  
    39  const (
    40  	Commit AllocStatus = iota
    41  	Repair
    42  	Broken
    43  	Rollback
    44  )
    45  
    46  var (
    47  	ErrRetryOperation = errors.New("retry_operation")
    48  	ErrRepairRequired = errors.New("repair_required")
    49  )
    50  
    51  type RollbackBlobber struct {
    52  	blobber      *blockchain.StorageNode
    53  	commitResult *CommitResult
    54  	lpm          *LatestPrevWriteMarker
    55  	blobIndex    int
    56  }
    57  
    58  type BlobberStatus struct {
    59  	ID     string
    60  	Status string
    61  }
    62  
    63  func GetWritemarker(allocID, allocTx, sig, id, baseUrl string) (*LatestPrevWriteMarker, error) {
    64  
    65  	var lpm LatestPrevWriteMarker
    66  
    67  	req, err := zboxutil.NewWritemarkerRequest(baseUrl, allocID, allocTx, sig)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    72  	defer cancel()
    73  	for retries := 0; retries < 3; retries++ {
    74  
    75  		resp, err := zboxutil.Client.Do(req.WithContext(ctx))
    76  		if err != nil {
    77  			return nil, err
    78  		}
    79  		if resp.StatusCode == http.StatusTooManyRequests {
    80  			l.Logger.Info(baseUrl, "got too many requests, retrying")
    81  			var r int
    82  			r, err = zboxutil.GetRateLimitValue(resp)
    83  			if err != nil {
    84  				l.Logger.Error(err)
    85  				return nil, err
    86  			}
    87  			time.Sleep(time.Duration(r) * time.Second)
    88  			continue
    89  		}
    90  		body, err := io.ReadAll(resp.Body)
    91  		defer resp.Body.Close()
    92  		if resp.StatusCode != http.StatusOK {
    93  			return nil, fmt.Errorf("writemarker error response %s with status %d", body, resp.StatusCode)
    94  		}
    95  
    96  		if err != nil {
    97  			return nil, err
    98  		}
    99  		err = json.Unmarshal(body, &lpm)
   100  		if err != nil {
   101  			return nil, err
   102  		}
   103  		if lpm.LatestWM != nil {
   104  			err = lpm.LatestWM.VerifySignature(client.GetClientPublicKey())
   105  			if err != nil {
   106  				return nil, fmt.Errorf("signature verification failed for latest writemarker: %s", err.Error())
   107  			}
   108  			if lpm.PrevWM != nil {
   109  				err = lpm.PrevWM.VerifySignature(client.GetClientPublicKey())
   110  				if err != nil {
   111  					return nil, fmt.Errorf("signature verification failed for latest writemarker: %s", err.Error())
   112  				}
   113  			}
   114  		}
   115  		return &lpm, nil
   116  	}
   117  
   118  	return nil, fmt.Errorf("writemarker error response %d", http.StatusTooManyRequests)
   119  }
   120  
   121  func (rb *RollbackBlobber) processRollback(ctx context.Context, tx string) error {
   122  
   123  	wm := &marker.WriteMarker{}
   124  	wm.AllocationID = rb.lpm.LatestWM.AllocationID
   125  	wm.Timestamp = rb.lpm.LatestWM.Timestamp
   126  	wm.BlobberID = rb.lpm.LatestWM.BlobberID
   127  	wm.ClientID = client.GetClientID()
   128  	wm.Size = -rb.lpm.LatestWM.Size
   129  	wm.ChainSize = wm.Size + rb.lpm.LatestWM.ChainSize
   130  
   131  	if rb.lpm.PrevWM != nil {
   132  		wm.AllocationRoot = rb.lpm.PrevWM.AllocationRoot
   133  		wm.PreviousAllocationRoot = rb.lpm.PrevWM.AllocationRoot
   134  		wm.FileMetaRoot = rb.lpm.PrevWM.FileMetaRoot
   135  		if wm.AllocationRoot == rb.lpm.LatestWM.AllocationRoot {
   136  			return nil
   137  		}
   138  	}
   139  	if rb.lpm.Version == MARKER_VERSION {
   140  		decodedHash, _ := hex.DecodeString(wm.AllocationRoot)
   141  		prevChainHash, _ := hex.DecodeString(rb.lpm.LatestWM.ChainHash)
   142  		hasher := sha256.New()
   143  		hasher.Write(prevChainHash) //nolint:errcheck
   144  		hasher.Write(decodedHash)   //nolint:errcheck
   145  		wm.ChainHash = hex.EncodeToString(hasher.Sum(nil))
   146  	} else if rb.lpm.Version == "" {
   147  		wm.Size = 0
   148  	}
   149  
   150  	err := wm.Sign()
   151  	if err != nil {
   152  		l.Logger.Error("Signing writemarker failed: ", err)
   153  		return err
   154  	}
   155  	body := new(bytes.Buffer)
   156  	formWriter := multipart.NewWriter(body)
   157  	wmData, err := json.Marshal(wm)
   158  	if err != nil {
   159  		l.Logger.Error("Creating writemarker failed: ", err)
   160  		return err
   161  	}
   162  	connID := zboxutil.NewConnectionId()
   163  	formWriter.WriteField("write_marker", string(wmData))
   164  	formWriter.WriteField("connection_id", connID)
   165  	formWriter.Close()
   166  
   167  	req, err := zboxutil.NewRollbackRequest(rb.blobber.Baseurl, wm.AllocationID, tx, body)
   168  	if err != nil {
   169  		l.Logger.Error("Creating rollback request failed: ", err)
   170  		return err
   171  	}
   172  	req.Header.Add("Content-Type", formWriter.FormDataContentType())
   173  
   174  	l.Logger.Info("Sending Rollback request to blobber: ", rb.blobber.Baseurl)
   175  
   176  	var (
   177  		shouldContinue bool
   178  	)
   179  
   180  	for retries := 0; retries < 3; retries++ {
   181  		err, shouldContinue = func() (err error, shouldContinue bool) {
   182  			reqCtx, ctxCncl := context.WithTimeout(ctx, DefaultUploadTimeOut)
   183  			resp, err := zboxutil.Client.Do(req.WithContext(reqCtx))
   184  			defer ctxCncl()
   185  			if err != nil {
   186  				l.Logger.Error("Rollback request failed: ", err)
   187  				return
   188  			}
   189  
   190  			if resp.Body != nil {
   191  				defer resp.Body.Close()
   192  			}
   193  
   194  			var respBody []byte
   195  			respBody, err = io.ReadAll(resp.Body)
   196  			if err != nil {
   197  				l.Logger.Error("Response read: ", err)
   198  				return
   199  			}
   200  			if resp.StatusCode == http.StatusOK {
   201  				l.Logger.Info(rb.blobber.Baseurl, connID, "rollbacked")
   202  				return
   203  			}
   204  
   205  			if resp.StatusCode == http.StatusTooManyRequests {
   206  				l.Logger.Info(rb.blobber.Baseurl, connID, "got too many request error. Retrying")
   207  				var r int
   208  				r, err = zboxutil.GetRateLimitValue(resp)
   209  				if err != nil {
   210  					l.Logger.Error(err)
   211  					return
   212  				}
   213  
   214  				time.Sleep(time.Duration(r) * time.Second)
   215  				shouldContinue = true
   216  				return
   217  			}
   218  
   219  			if strings.Contains(string(respBody), "pending_markers:") {
   220  				l.Logger.Info("Commit pending for blobber ",
   221  					rb.blobber.Baseurl, " Retrying")
   222  				time.Sleep(5 * time.Second)
   223  				shouldContinue = true
   224  				return
   225  			}
   226  
   227  			if strings.Contains(string(respBody), "chain_length_exceeded") {
   228  				l.Logger.Info("Chain length exceeded for blobber ",
   229  					rb.blobber.Baseurl, " Retrying")
   230  				time.Sleep(5 * time.Second)
   231  				shouldContinue = true
   232  				return
   233  			}
   234  
   235  			err = thrown.New("commit_error",
   236  				fmt.Sprintf("Got error response %s with status %d", respBody, resp.StatusCode))
   237  
   238  			return
   239  		}()
   240  		if err != nil {
   241  			l.Logger.Error(err)
   242  			return err
   243  		}
   244  		if shouldContinue {
   245  			continue
   246  		}
   247  		return nil
   248  
   249  	}
   250  
   251  	return thrown.New("rolback_error", fmt.Sprint("Rollback failed"))
   252  }
   253  
   254  // CheckAllocStatus checks the status of the allocation
   255  // and returns the status of the allocation and its blobbers.
   256  func (a *Allocation) CheckAllocStatus() (AllocStatus, []BlobberStatus, error) {
   257  
   258  	wg := &sync.WaitGroup{}
   259  	markerChan := make(chan *RollbackBlobber, len(a.Blobbers))
   260  	var errCnt int32
   261  	var markerError error
   262  	blobberRes := make([]BlobberStatus, len(a.Blobbers))
   263  	for ind, blobber := range a.Blobbers {
   264  
   265  		wg.Add(1)
   266  		go func(blobber *blockchain.StorageNode, ind int) {
   267  
   268  			defer wg.Done()
   269  			blobStatus := BlobberStatus{
   270  				ID:     blobber.ID,
   271  				Status: "available",
   272  			}
   273  			wr, err := GetWritemarker(a.ID, a.Tx, a.sig, blobber.ID, blobber.Baseurl)
   274  			if err != nil {
   275  				atomic.AddInt32(&errCnt, 1)
   276  				markerError = err
   277  				l.Logger.Error("error during getWritemarker", zap.Error(err))
   278  				blobStatus.Status = "unavailable"
   279  			}
   280  			if wr == nil {
   281  				markerChan <- nil
   282  			} else {
   283  				markerChan <- &RollbackBlobber{
   284  					blobber:      blobber,
   285  					lpm:          wr,
   286  					commitResult: &CommitResult{},
   287  					blobIndex:    ind,
   288  				}
   289  			}
   290  			blobberRes[ind] = blobStatus
   291  		}(blobber, ind)
   292  
   293  	}
   294  	wg.Wait()
   295  	close(markerChan)
   296  	if (a.ParityShards > 0 && errCnt > int32(a.ParityShards)) || (a.ParityShards == 0 && errCnt > 0) {
   297  		return Broken, blobberRes, common.NewError("check_alloc_status_failed", markerError.Error())
   298  	}
   299  
   300  	versionMap := make(map[string][]*RollbackBlobber)
   301  
   302  	var (
   303  		prevVersion   string
   304  		latestVersion string
   305  		highestTS     int64
   306  	)
   307  
   308  	for rb := range markerChan {
   309  
   310  		if rb == nil || rb.lpm.LatestWM == nil {
   311  			continue
   312  		}
   313  
   314  		version := rb.lpm.LatestWM.FileMetaRoot
   315  
   316  		if highestTS < rb.lpm.LatestWM.Timestamp {
   317  			prevVersion = latestVersion
   318  			highestTS = rb.lpm.LatestWM.Timestamp
   319  			latestVersion = version
   320  		}
   321  
   322  		if prevVersion == "" && version != latestVersion {
   323  			prevVersion = version
   324  		}
   325  
   326  		if _, ok := versionMap[version]; !ok {
   327  			versionMap[version] = make([]*RollbackBlobber, 0)
   328  		}
   329  
   330  		versionMap[version] = append(versionMap[version], rb)
   331  	}
   332  
   333  	req := a.DataShards
   334  
   335  	if len(versionMap) == 0 {
   336  		return Commit, blobberRes, nil
   337  	}
   338  
   339  	if len(versionMap[latestVersion]) > req || len(versionMap[prevVersion]) > req {
   340  		return Commit, blobberRes, nil
   341  	}
   342  
   343  	if len(versionMap[latestVersion]) >= req || len(versionMap[prevVersion]) >= req || len(versionMap) > 2 {
   344  		for _, rb := range versionMap[prevVersion] {
   345  			blobberRes[rb.blobIndex].Status = "repair"
   346  		}
   347  		return Repair, blobberRes, nil
   348  	} else {
   349  		l.Logger.Info("versionMapLen", zap.Int("versionMapLen", len(versionMap)), zap.Int("latestLen", len(versionMap[latestVersion])), zap.Int("prevLen", len(versionMap[prevVersion])))
   350  	}
   351  
   352  	// rollback to previous version
   353  	l.Logger.Info("Rolling back to previous version")
   354  	fullConsensus := len(versionMap[latestVersion]) - (req - len(versionMap[prevVersion]))
   355  	errCnt = 0
   356  	l.Logger.Info("fullConsensus", zap.Int32("fullConsensus", int32(fullConsensus)), zap.Int("latestLen", len(versionMap[latestVersion])), zap.Int("prevLen", len(versionMap[prevVersion])))
   357  	for _, rb := range versionMap[latestVersion] {
   358  
   359  		wg.Add(1)
   360  		go func(rb *RollbackBlobber) {
   361  			defer wg.Done()
   362  			err := rb.processRollback(context.TODO(), a.Tx)
   363  			if err != nil {
   364  				atomic.AddInt32(&errCnt, 1)
   365  				rb.commitResult = ErrorCommitResult(err.Error())
   366  				l.Logger.Error("error during rollback", zap.Error(err))
   367  			} else {
   368  				rb.commitResult = SuccessCommitResult()
   369  			}
   370  		}(rb)
   371  	}
   372  
   373  	wg.Wait()
   374  	if errCnt > int32(fullConsensus) {
   375  		return Broken, blobberRes, common.NewError("rollback_failed", "Rollback failed")
   376  	}
   377  
   378  	if errCnt == int32(fullConsensus) {
   379  		return Repair, blobberRes, nil
   380  	}
   381  
   382  	return Rollback, blobberRes, nil
   383  }
   384  
   385  // RollbackWithMask rolls back the latest operation from the allocation blobbers which ran it.
   386  // The mask is used to specify which blobbers to rollback.
   387  //   - mask: 128-bitmask to specify which blobbers to rollback
   388  func (a *Allocation) RollbackWithMask(mask zboxutil.Uint128) {
   389  
   390  	wg := &sync.WaitGroup{}
   391  	markerChan := make(chan *RollbackBlobber, mask.CountOnes())
   392  	var pos uint64
   393  	for i := mask; !i.Equals64(0); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) {
   394  		pos = uint64(i.TrailingZeros())
   395  		blobber := a.Blobbers[pos]
   396  		wg.Add(1)
   397  		go func(blobber *blockchain.StorageNode) {
   398  
   399  			defer wg.Done()
   400  			wr, err := GetWritemarker(a.ID, a.Tx, a.sig, blobber.ID, blobber.Baseurl)
   401  			if err != nil {
   402  				l.Logger.Error("error during getWritemarker", zap.Error(err))
   403  			}
   404  			if wr == nil {
   405  				markerChan <- nil
   406  			} else {
   407  				markerChan <- &RollbackBlobber{
   408  					blobber:      blobber,
   409  					lpm:          wr,
   410  					commitResult: &CommitResult{},
   411  				}
   412  			}
   413  		}(blobber)
   414  
   415  	}
   416  	wg.Wait()
   417  	close(markerChan)
   418  
   419  	for rb := range markerChan {
   420  		if rb == nil || rb.lpm.LatestWM == nil {
   421  			continue
   422  		}
   423  		wg.Add(1)
   424  		go func(rb *RollbackBlobber) {
   425  			defer wg.Done()
   426  			err := rb.processRollback(context.TODO(), a.Tx)
   427  			if err != nil {
   428  				rb.commitResult = ErrorCommitResult(err.Error())
   429  				l.Logger.Error("error during rollback", zap.Error(err))
   430  			} else {
   431  				rb.commitResult = SuccessCommitResult()
   432  			}
   433  		}(rb)
   434  	}
   435  
   436  	wg.Wait()
   437  }