gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/skynet.go (about)

     1  package skymodules
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"io"
     8  	"math"
     9  	"math/big"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/tus/tusd/pkg/handler"
    16  	"gitlab.com/NebulousLabs/errors"
    17  	"gitlab.com/NebulousLabs/fastrand"
    18  	"gitlab.com/SkynetLabs/skyd/build"
    19  	"gitlab.com/SkynetLabs/skyd/skykey"
    20  	"go.sia.tech/siad/crypto"
    21  	"go.sia.tech/siad/modules"
    22  	"go.sia.tech/siad/types"
    23  )
    24  
    25  const (
    26  	// DefaultSkynetDefaultPath is the defaultPath value we use when the user
    27  	// hasn't specified one and `index.html` exists in the skyfile.
    28  	DefaultSkynetDefaultPath = "index.html"
    29  
    30  	// SkyfileLayoutSize describes the amount of space within the first sector
    31  	// of a skyfile used to describe the rest of the skyfile.
    32  	SkyfileLayoutSize = 99
    33  
    34  	// SkynetFeeDivider is the number by which the renter spending is divided to
    35  	// determine the skynet fee to be paid.
    36  	SkynetFeeDivider = 5 // 20%
    37  
    38  	// SkyfileVersion establishes the current version for creating skyfiles.
    39  	// The skyfile versions are different from the siafile versions.
    40  	SkyfileVersion = 1
    41  
    42  	// layoutKeyDataSize is the size of the key-data field in a skyfileLayout.
    43  	layoutKeyDataSize = 64
    44  
    45  	// monetizationLotteryEntropy is the number of bytes generated as entropy
    46  	// for drawing the lottery ticket.
    47  	monetizationLotteryEntropy = 32
    48  )
    49  
    50  var (
    51  	// DefaultTryFilesValue is the value of tryfiles we set on each skyfile,
    52  	// if none is specified and defaultpath and disabledefaultpath are also
    53  	// unspecified.
    54  	DefaultTryFilesValue = []string{"index.html"}
    55  )
    56  
    57  var (
    58  	// BaseSectorNonceDerivation is the specifier used to derive a nonce for base
    59  	// sector encryption
    60  	BaseSectorNonceDerivation = types.NewSpecifier("BaseSectorNonce")
    61  
    62  	// DefaultSkynetPricePerMS is the default price per millisecond the renter
    63  	// is able to spend on faster workers when downloading a Skyfile. By default
    64  	// this is a sane default of 100 nS.
    65  	DefaultSkynetPricePerMS = types.SiacoinPrecision.MulFloat(1e-7) // 100 nS
    66  
    67  	// FanoutNonceDerivation is the specifier used to derive a nonce for
    68  	// fanout encryption.
    69  	FanoutNonceDerivation = types.NewSpecifier("FanoutNonce")
    70  
    71  	// ExtendedSuffix is the suffix that is added to a skyfile siapath if it is
    72  	// a large file upload
    73  	ExtendedSuffix = "-extended"
    74  
    75  	// ErrZeroMonetizer is returned if a caller tries to set a monetizer with 0H
    76  	// payout.
    77  	ErrZeroMonetizer = errors.New("can't provide 0 monetization")
    78  
    79  	// ErrInvalidCurrency is returned if an unknown monetization currency is
    80  	// specified.
    81  	ErrInvalidCurrency = errors.New("specified monetization currency is invalid")
    82  
    83  	// ErrUnknownLicense is returned if an unknown license is specified.
    84  	ErrUnknownLicense = errors.New("specified license is unknown")
    85  
    86  	// ErrZeroBase is returned when trying to pay for a monetized file with a 0
    87  	// base.
    88  	ErrZeroBase = errors.New("can't pay monetizers when the base is 0")
    89  
    90  	// ErrZeroConversionRate is returned when trying to pay for a monetized file
    91  	// with a 0 conversion rate.
    92  	ErrZeroConversionRate = fmt.Errorf("can't pay monetizers when the conversion rate for 0")
    93  )
    94  
    95  var (
    96  	// SkyfileFormatNotSpecified is the default format for the endpoint when the
    97  	// format isn't specified explicitly.
    98  	SkyfileFormatNotSpecified = SkyfileFormat("")
    99  	// SkyfileFormatConcat returns the skyfiles in a concatenated manner.
   100  	SkyfileFormatConcat = SkyfileFormat("concat")
   101  	// SkyfileFormatTar returns the skyfiles as a .tar.
   102  	SkyfileFormatTar = SkyfileFormat("tar")
   103  	// SkyfileFormatTarGz returns the skyfiles as a .tar.gz.
   104  	SkyfileFormatTarGz = SkyfileFormat("targz")
   105  	// SkyfileFormatZip returns the skyfiles as a .zip.
   106  	SkyfileFormatZip = SkyfileFormat("zip")
   107  )
   108  
   109  // SkynetFeePayoutInterval is the time after which the renter pays out the
   110  // accumulated skynet fees.
   111  var SkynetFeePayoutInterval = build.Select(build.Var{
   112  	Dev:      time.Minute * 5,
   113  	Standard: time.Hour * 24,
   114  	Testing:  time.Second * 5,
   115  }).(time.Duration)
   116  
   117  // SkynetFeePayoutCheckInterval is the time between the renter's periodic payout
   118  // checks.
   119  var SkynetFeePayoutCheckInterval = build.Select(build.Var{
   120  	Dev:      time.Minute,
   121  	Standard: time.Hour,
   122  	Testing:  time.Second,
   123  }).(time.Duration)
   124  
   125  type (
   126  	// HostForRegistryUpdate describes a single host for a registry update.
   127  	HostForRegistryUpdate struct {
   128  		Pubkey types.SiaPublicKey `json:"pubkey"`
   129  	}
   130  
   131  	// SkyfileSubfiles contains the subfiles of a skyfile, indexed by their
   132  	// filename.
   133  	SkyfileSubfiles map[string]SkyfileSubfileMetadata
   134  
   135  	// SkyfileUploadParameters establishes the parameters such as the intra-root
   136  	// erasure coding.
   137  	SkyfileUploadParameters struct {
   138  		// SiaPath defines the siapath that the skyfile is going to be uploaded
   139  		// to. Recommended that the skyfile is placed in /var/skynet
   140  		SiaPath SiaPath
   141  
   142  		// DryRun allows to retrieve the skylink without actually uploading the
   143  		// file to the Sia network.
   144  		DryRun bool
   145  
   146  		// Force determines whether the upload should overwrite an existing
   147  		// siafile at 'SiaPath'. If set to false, an error will be returned if
   148  		// there is already a file or folder at 'SiaPath'. If set to true, any
   149  		// existing file or folder at 'SiaPath' will be deleted and overwritten.
   150  		Force bool
   151  
   152  		// Root determines whether the upload should treat the filepath as a
   153  		// path from system root, or if the path should be from /var/skynet.
   154  		Root bool
   155  
   156  		// The base chunk is always uploaded with a 1-of-N erasure coding
   157  		// setting, meaning that only the redundancy needs to be configured by
   158  		// the user.
   159  		BaseChunkRedundancy uint8
   160  
   161  		// Filename indicates the filename of the skyfile.
   162  		Filename string
   163  
   164  		// Mode indicates the file permissions of the skyfile.
   165  		Mode os.FileMode
   166  
   167  		// DefaultPath indicates what content to serve if the user has not
   168  		// specified a path and the user is not trying to download the Skylink
   169  		// as an archive. If left empty, it will be interpreted as "index.html"
   170  		// on download, if the skyfile contains such a file, or the only file in
   171  		// the skyfile, if the skyfile contains a single file.
   172  		DefaultPath string
   173  
   174  		// DisableDefaultPath prevents the usage of DefaultPath. As a result no
   175  		// content will be automatically served for the skyfile.
   176  		DisableDefaultPath bool
   177  
   178  		// Reader supplies the file data for the skyfile.
   179  		Reader io.Reader
   180  
   181  		// SkykeyName is the name of the Skykey that should be used to encrypt
   182  		// the Skyfile.
   183  		SkykeyName string
   184  
   185  		// SkykeyID is the ID of Skykey that should be used to encrypt the file.
   186  		SkykeyID skykey.SkykeyID
   187  
   188  		// If Encrypt is set to true and one of SkykeyName or SkykeyID was set,
   189  		// a Skykey will be derived from the Master Skykey found under that
   190  		// name/ID to be used for this specific upload.
   191  		FileSpecificSkykey skykey.Skykey
   192  
   193  		// TryFiles is an ordered list of files which to serve in case the
   194  		// requested file does not exist.
   195  		TryFiles []string
   196  
   197  		// ErrorPages overrides the content we serve for some error codes.
   198  		ErrorPages map[int]string
   199  	}
   200  
   201  	// SkyfileMultipartUploadParameters defines the parameters specific to
   202  	// multipart uploads. See SkyfileUploadParameters for a detailed description
   203  	// of the fields.
   204  	SkyfileMultipartUploadParameters struct {
   205  		SiaPath             SiaPath
   206  		Force               bool
   207  		Root                bool
   208  		BaseChunkRedundancy uint8
   209  		Reader              io.Reader
   210  
   211  		// Filename indicates the filename of the skyfile.
   212  		Filename string
   213  
   214  		// DefaultPath indicates the default file to be opened when opening
   215  		// skyfiles that contain directories. If set to empty string no file
   216  		// will be opened by default.
   217  		DefaultPath string
   218  
   219  		// DisableDefaultPath prevents the usage of DefaultPath. As a result no
   220  		// content will be automatically served for the skyfile.
   221  		DisableDefaultPath bool
   222  
   223  		// TryFiles specifies an ordered list of files to serve, in case the
   224  		// requested file does not exist.
   225  		TryFiles []string
   226  
   227  		// ErrorPages overrides the content served for the specified error
   228  		// codes.
   229  		ErrorPages map[int]string
   230  
   231  		// ContentType indicates the media of the data supplied by the reader.
   232  		ContentType string
   233  	}
   234  
   235  	// SkyfilePinParameters defines the parameters specific to pinning a
   236  	// skylink. See SkyfileUploadParameters for a detailed description of the
   237  	// fields.
   238  	SkyfilePinParameters struct {
   239  		SiaPath             SiaPath `json:"siapath"`
   240  		Force               bool    `json:"force"`
   241  		Root                bool    `json:"root"`
   242  		BaseChunkRedundancy uint8   `json:"basechunkredundancy"`
   243  	}
   244  
   245  	// SkyfileMetadata is all of the metadata that gets placed into the first
   246  	// 4096 bytes of the skyfile, and is used to set the metadata of the file
   247  	// when writing back to disk. The data is json-encoded when it is placed
   248  	// into the leading bytes of the skyfile, meaning that this struct can be
   249  	// extended without breaking compatibility.
   250  	SkyfileMetadata struct {
   251  		Filename           string          `json:"filename"`
   252  		Length             uint64          `json:"length"`
   253  		Mode               os.FileMode     `json:"mode,omitempty"`
   254  		Subfiles           SkyfileSubfiles `json:"subfiles,omitempty"`
   255  		DefaultPath        string          `json:"defaultpath,omitempty"`
   256  		DisableDefaultPath bool            `json:"disabledefaultpath,omitempty"`
   257  		TryFiles           []string        `json:"tryfiles,omitempty"`
   258  		ErrorPages         map[int]string  `json:"errorpages,omitempty"`
   259  	}
   260  
   261  	// SkynetPortal contains information identifying a Skynet portal.
   262  	SkynetPortal struct {
   263  		Address modules.NetAddress `json:"address"` // the IP or domain name of the portal. Must be a valid network address
   264  		Public  bool               `json:"public"`  // indicates whether the portal can be accessed publicly or not
   265  
   266  	}
   267  
   268  	// SkynetTUSDataStore is the combined interface of all TUS interfaces that
   269  	// the renter implements for skynet.
   270  	SkynetTUSDataStore interface {
   271  		handler.DataStore
   272  		handler.ConcaterDataStore
   273  		handler.Locker
   274  
   275  		// Skylink returns the Skylink for an upload with a given ID.
   276  		// If the upload can't be found or isn't finished, "false" will
   277  		// be returned alongside an empty string.
   278  		Skylink(id string) (Skylink, bool)
   279  	}
   280  
   281  	// RegistryEntryHealth contains information about a registry entry's
   282  	// health on the network.
   283  	RegistryEntryHealth struct {
   284  		NumBestEntries             uint64 `json:"numbestentries"`
   285  		NumBestEntriesBeforeCutoff uint64 `json:"numbestentriesbeforecutoff"`
   286  		NumBestPrimaryEntries      uint64 `json:"numbestprimaryentries"`
   287  		NumEntries                 uint64 `json:"numentries"`
   288  		RevisionNumber             uint64 `json:"revisionnumber"`
   289  	}
   290  )
   291  
   292  type (
   293  	// SkynetTUSUpload is the interface for a TUS upload in the
   294  	// SkynetTUSUploadStore.
   295  	SkynetTUSUpload interface {
   296  		// GetSkylink returns the upload's skylink if available already.
   297  		GetSkylink() (Skylink, bool)
   298  
   299  		// GetInfo returns the FileInfo of the upload.
   300  		GetInfo(ctx context.Context) (handler.FileInfo, error)
   301  
   302  		// PruneInfo returns the info required to prune uploads.
   303  		PruneInfo(ctx context.Context) (id string, sp SiaPath, err error)
   304  
   305  		// UploadParams returns the upload parameters used for the
   306  		// upload.
   307  		UploadParams(ctx context.Context) (SkyfileUploadParameters, FileUploadParams, error)
   308  
   309  		// CommitWriteChunk commits writing a chunk of either a small or
   310  		// large file with fanout.
   311  		CommitWriteChunk(ctx context.Context, newOffset int64, newLastWrite time.Time, isSmall bool, fanout []byte) error
   312  
   313  		// CommitFinishUpload commits a finalised upload.
   314  		CommitFinishUpload(ctx context.Context, skylink Skylink) error
   315  
   316  		// Fanout returns the fanout of the upload. Should only be
   317  		// called once it's done uploading.
   318  		Fanout(ctx context.Context) ([]byte, error)
   319  
   320  		// SkyfileMetadata returns the metadata of the upload. Should
   321  		// only be called once it's done uploading.
   322  		SkyfileMetadata(ctx context.Context) ([]byte, error)
   323  	}
   324  
   325  	// SkynetTUSUploadStore defines an interface for a storage backend that is
   326  	// capable of storing upload information as well as locking uploads and pruning
   327  	// them.
   328  	SkynetTUSUploadStore interface {
   329  		// ToPrune returns the uploads which should be pruned from skyd
   330  		// and the store.
   331  		ToPrune(ctx context.Context) ([]SkynetTUSUpload, error)
   332  
   333  		// Prune prunes the upload with the given ID from the store.
   334  		Prune(context.Context, []string) error
   335  
   336  		// CreateUpload creates a new upload in the store.
   337  		CreateUpload(ctx context.Context, fi handler.FileInfo, sp SiaPath, fileName string, baseChunkRedundancy uint8, fanoutDataPieces, fanoutParityPieces int, sm []byte, ct crypto.CipherType) (SkynetTUSUpload, error)
   338  
   339  		// GetUpload fetches an upload from the store.
   340  		GetUpload(ctx context.Context, id string) (SkynetTUSUpload, error)
   341  
   342  		// WithTransaction allows for grouping multiple database operations into a
   343  		// single atomic transaction.
   344  		WithTransaction(context.Context, func(context.Context) error) error
   345  
   346  		// The store also implements the Locker interface to allow TUS
   347  		// to automatically lock uploads.
   348  		handler.Locker
   349  
   350  		io.Closer
   351  	}
   352  )
   353  
   354  // ForPath returns a subset of the SkyfileMetadata that contains all of the
   355  // subfiles for the given path. The path can lead to both a directory or a file.
   356  // Note that this method will return the subfiles with offsets relative to the
   357  // given path, so if a directory is requested, the subfiles in that directory
   358  // will start at offset 0, relative to the path.
   359  func (sm SkyfileMetadata) ForPath(path string) (SkyfileMetadata, bool, uint64, uint64) {
   360  	// All paths must be absolute.
   361  	path = EnsurePrefix(path, "/")
   362  	metadata := SkyfileMetadata{
   363  		Filename:   path,
   364  		Subfiles:   make(SkyfileSubfiles),
   365  		TryFiles:   sm.TryFiles,
   366  		ErrorPages: sm.ErrorPages,
   367  	}
   368  
   369  	// Try to find an exact match
   370  	var isFile bool
   371  	for _, sf := range sm.Subfiles {
   372  		if EnsurePrefix(sf.Filename, "/") == path {
   373  			isFile = true
   374  			metadata.Subfiles[sf.Filename] = sf
   375  			break
   376  		}
   377  	}
   378  
   379  	// If there is no exact match look for directories.
   380  	pathDir := EnsureSuffix(path, "/")
   381  	if len(metadata.Subfiles) == 0 {
   382  		for _, sf := range sm.Subfiles {
   383  			// Check if the given file's path starts with `pathDir`.
   384  			if strings.HasPrefix(EnsurePrefix(sf.Filename, "/"), pathDir) {
   385  				metadata.Subfiles[sf.Filename] = sf
   386  			}
   387  		}
   388  	}
   389  	offset := metadata.offset()
   390  	if offset > 0 {
   391  		for _, sf := range metadata.Subfiles {
   392  			sf.Offset -= offset
   393  			metadata.Subfiles[sf.Filename] = sf
   394  		}
   395  	}
   396  	// Set the metadata length by summing up the length of the subfiles.
   397  	for _, file := range metadata.Subfiles {
   398  		metadata.Length += file.Len
   399  	}
   400  	return metadata, isFile, offset, metadata.size()
   401  }
   402  
   403  // ContentType returns the Content Type of the data. We only return a
   404  // content-type if it has exactly one subfile. As that is the only case where we
   405  // can be sure of it.
   406  func (sm SkyfileMetadata) ContentType() string {
   407  	if len(sm.Subfiles) == 1 {
   408  		for _, sf := range sm.Subfiles {
   409  			return sf.ContentType
   410  		}
   411  	}
   412  	return ""
   413  }
   414  
   415  // EffectiveDefaultPath returns the default path based not only on what value is
   416  // set in the metadata struct but also on disabledefaultpath, the number of
   417  // subfiles, etc.
   418  func (sm SkyfileMetadata) EffectiveDefaultPath() string {
   419  	if sm.DisableDefaultPath {
   420  		return ""
   421  	}
   422  	if sm.DefaultPath == "" {
   423  		// If `defaultpath` and `disabledefaultpath` are not set and the
   424  		// skyfile has a single subfile we automatically default to it.
   425  		if len(sm.Subfiles) == 1 {
   426  			for filename := range sm.Subfiles {
   427  				return EnsurePrefix(filename, "/")
   428  			}
   429  		}
   430  		// If the `defaultpath` is not set but the skyfiles has an `/index.html`
   431  		// subfile then we automatically default to that.
   432  		if _, exists := sm.Subfiles[DefaultSkynetDefaultPath]; exists {
   433  			return EnsurePrefix(DefaultSkynetDefaultPath, "/")
   434  		}
   435  	}
   436  	return sm.DefaultPath
   437  }
   438  
   439  // IsDirectory returns true if the SkyfileMetadata represents a directory.
   440  func (sm SkyfileMetadata) IsDirectory() bool {
   441  	if len(sm.Subfiles) > 1 {
   442  		return true
   443  	}
   444  	if len(sm.Subfiles) == 1 {
   445  		var name string
   446  		for _, sf := range sm.Subfiles {
   447  			name = sf.Filename
   448  			break
   449  		}
   450  		if sm.Filename != name {
   451  			return true
   452  		}
   453  	}
   454  	return false
   455  }
   456  
   457  // ServePath takes a requested path and determines what path should be served
   458  // based on the existence of the requested path, defaultpath, tryfiles, etc.
   459  func (sm SkyfileMetadata) ServePath(path string) string {
   460  	// If there's a single subfile in the skyfile we want to serve it. We don't
   461  	// even need to check the tryfiles.
   462  	if path == "/" && len(sm.Subfiles) == 1 && !sm.DisableDefaultPath {
   463  		for filename := range sm.Subfiles {
   464  			return EnsurePrefix(filename, "/")
   465  		}
   466  	}
   467  
   468  	// If there are tryfiles, determine the servePath based on those.
   469  	if len(sm.TryFiles) > 0 {
   470  		return sm.determinePathBasedOnTryfiles(path)
   471  	}
   472  
   473  	// Check the defaultpath to determine the servePath.
   474  	defaultPath := sm.EffectiveDefaultPath()
   475  	if defaultPath != "" && path == "/" {
   476  		_, exists := sm.Subfiles[strings.TrimPrefix(defaultPath, "/")]
   477  		if exists {
   478  			return EnsurePrefix(defaultPath, "/")
   479  		}
   480  	}
   481  	return path
   482  }
   483  
   484  // size returns the total size, which is the sum of the length of all subfiles.
   485  func (sm SkyfileMetadata) size() uint64 {
   486  	var total uint64
   487  	for _, sf := range sm.Subfiles {
   488  		total += sf.Len
   489  	}
   490  	return total
   491  }
   492  
   493  // offset returns the offset of the subfile with the smallest offset.
   494  func (sm SkyfileMetadata) offset() uint64 {
   495  	if len(sm.Subfiles) == 0 {
   496  		return 0
   497  	}
   498  	var min uint64 = math.MaxUint64
   499  	for _, sf := range sm.Subfiles {
   500  		if sf.Offset < min {
   501  			min = sf.Offset
   502  		}
   503  	}
   504  	return min
   505  }
   506  
   507  // determinePathBasedOnTryfiles determines if we should serve a different path
   508  // based on the given metadata.
   509  func (sm SkyfileMetadata) determinePathBasedOnTryfiles(path string) string {
   510  	if sm.Subfiles == nil {
   511  		return path
   512  	}
   513  	file := strings.Trim(path, "/")
   514  	if _, exists := sm.Subfiles[file]; !exists {
   515  		for _, tf := range sm.TryFiles {
   516  			// If we encounter an absolute-path tryfile, and it exists, we stop
   517  			// searching.
   518  			_, exists = sm.Subfiles[strings.Trim(tf, "/")]
   519  			if strings.HasPrefix(tf, "/") && exists {
   520  				return tf
   521  			}
   522  			// Assume the request is for a directory and check if a
   523  			// tryfile matches.
   524  			potentialFilename := strings.Trim(strings.TrimSuffix(file, "/")+EnsurePrefix(tf, "/"), "/")
   525  			if _, exists = sm.Subfiles[potentialFilename]; exists {
   526  				return EnsurePrefix(potentialFilename, "/")
   527  			}
   528  		}
   529  	}
   530  	return path
   531  }
   532  
   533  // SkyfileLayout explains the layout information that is used for storing data
   534  // inside of the skyfile. The SkyfileLayout always appears as the first bytes
   535  // of the leading chunk.
   536  type SkyfileLayout struct {
   537  	Version            uint8
   538  	Filesize           uint64
   539  	MetadataSize       uint64
   540  	FanoutSize         uint64
   541  	FanoutDataPieces   uint8
   542  	FanoutParityPieces uint8
   543  	CipherType         crypto.CipherType
   544  	KeyData            [layoutKeyDataSize]byte // keyData is incompatible with ciphers that need keys larger than 64 bytes
   545  }
   546  
   547  // HasCompressedFanout returns 'true' if the fanout of the skyfile is expected
   548  // to be compressed.
   549  func (sl *SkyfileLayout) HasCompressedFanout() bool {
   550  	return sl.FanoutDataPieces == 1 && sl.CipherType == crypto.TypePlain
   551  }
   552  
   553  // FanoutPiecesPerChunk returns the number of pieces per chunk to expect in the
   554  // fanout of a skyfile. This is usually the number of datapieces + paritypieces
   555  // except for compressed fanouts.
   556  func (sl *SkyfileLayout) FanoutPiecesPerChunk() uint64 {
   557  	if sl.HasCompressedFanout() {
   558  		return 1
   559  	}
   560  	return uint64(sl.FanoutDataPieces + sl.FanoutParityPieces)
   561  }
   562  
   563  // FanoutOffset returns the offset of the fanout within the base sector. It's
   564  // positioned after the skyfile layout.
   565  func (sl *SkyfileLayout) FanoutOffset(layoutOff uint64) uint64 {
   566  	return layoutOff + SkyfileLayoutSize
   567  }
   568  
   569  // MetadataOffset returns the offset of the metadata within the base sector.
   570  // It's positioned after the fanout.
   571  func (sl *SkyfileLayout) MetadataOffset(layoutOff uint64) uint64 {
   572  	return sl.FanoutOffset(layoutOff) + sl.FanoutSize
   573  }
   574  
   575  // HasRecursiveFanout returns 'true' if a layout indicates that a skyfile has
   576  // its fanout uploaded recursively.
   577  func (sl *SkyfileLayout) HasRecursiveFanout(layoutOff uint64) bool {
   578  	if layoutOff > modules.SectorSize {
   579  		build.Critical("HasRecursiveFanout: layoutOff can't be > SectorSize")
   580  		return false
   581  	}
   582  	baseSectorSize := modules.SectorSize - layoutOff
   583  	return layoutOff+SkyfileLayoutSize+sl.FanoutSize+sl.MetadataSize > baseSectorSize || sl.FanoutSize > modules.SectorSize || sl.MetadataSize > modules.SectorSize
   584  }
   585  
   586  // IsSmallFile returns whether the layout belongs to a small skyfile.
   587  func (sl *SkyfileLayout) IsSmallFile() bool {
   588  	return sl.FanoutSize == 0
   589  }
   590  
   591  // NewSkyfileLayout creates a new version 1 layout with fanout.
   592  func NewSkyfileLayout(fileSize, metadataSize, fanoutSize uint64, fanoutEC ErasureCoder, ct crypto.CipherType) SkyfileLayout {
   593  	sl := NewSkyfileLayoutNoFanout(fileSize, metadataSize, ct)
   594  	sl.FanoutSize = fanoutSize
   595  	sl.FanoutDataPieces = uint8(fanoutEC.MinPieces())
   596  	sl.FanoutParityPieces = uint8(fanoutEC.NumPieces() - fanoutEC.MinPieces())
   597  	return sl
   598  }
   599  
   600  // NewSkyfileLayoutNoFanout creates a new version 1 layout without fanout.
   601  func NewSkyfileLayoutNoFanout(fileSize, metadataSize uint64, ct crypto.CipherType) SkyfileLayout {
   602  	return SkyfileLayout{
   603  		Version:      SkyfileVersion,
   604  		Filesize:     fileSize,
   605  		MetadataSize: metadataSize,
   606  		CipherType:   ct,
   607  	}
   608  }
   609  
   610  // Decode will take a []byte and load the layout from that []byte.
   611  func (sl *SkyfileLayout) Decode(b []byte) {
   612  	offset := 0
   613  	sl.Version = b[offset]
   614  	offset++
   615  	sl.Filesize = binary.LittleEndian.Uint64(b[offset:])
   616  	offset += 8
   617  	sl.MetadataSize = binary.LittleEndian.Uint64(b[offset:])
   618  	offset += 8
   619  	sl.FanoutSize = binary.LittleEndian.Uint64(b[offset:])
   620  	offset += 8
   621  	sl.FanoutDataPieces = b[offset]
   622  	offset++
   623  	sl.FanoutParityPieces = b[offset]
   624  	offset++
   625  	copy(sl.CipherType[:], b[offset:])
   626  	offset += len(sl.CipherType)
   627  	copy(sl.KeyData[:], b[offset:])
   628  	offset += len(sl.KeyData)
   629  
   630  	// Sanity check. If this check fails, decode() does not match the
   631  	// SkyfileLayoutSize.
   632  	if offset != SkyfileLayoutSize {
   633  		build.Critical("layout size does not match the amount of data decoded")
   634  	}
   635  }
   636  
   637  // DecodeFanoutIntoChunks will take the fanout bytes from a skyfile and decode
   638  // them in to chunks.
   639  func (sl *SkyfileLayout) DecodeFanoutIntoChunks(fanoutBytes []byte) ([][]crypto.Hash, error) {
   640  	// There is no fanout if there are no fanout settings.
   641  	if len(fanoutBytes) == 0 {
   642  		return nil, nil
   643  	}
   644  
   645  	// Special case: if the data of the file is using 1-of-N erasure coding,
   646  	// each piece will be identical, so the fanout will only have encoded a
   647  	// single piece for each chunk.
   648  	var piecesPerChunk uint64
   649  	var chunkRootsSize uint64
   650  	if sl.FanoutDataPieces == 1 && sl.CipherType == crypto.TypePlain {
   651  		piecesPerChunk = 1
   652  		chunkRootsSize = crypto.HashSize
   653  	} else {
   654  		// This is the case where the file data is not 1-of-N. Every piece is
   655  		// different, so every piece must get enumerated.
   656  		piecesPerChunk = uint64(sl.FanoutDataPieces) + uint64(sl.FanoutParityPieces)
   657  		chunkRootsSize = crypto.HashSize * piecesPerChunk
   658  	}
   659  	// Sanity check - the fanout bytes should be an even number of chunks.
   660  	if uint64(len(fanoutBytes))%chunkRootsSize != 0 {
   661  		return nil, errors.New("the fanout bytes do not contain an even number of chunks")
   662  	}
   663  	numChunks := uint64(len(fanoutBytes)) / chunkRootsSize
   664  
   665  	// Decode the fanout data into the list of chunks for the
   666  	// fanoutStreamBufferDataSource.
   667  	chunks := make([][]crypto.Hash, 0, numChunks)
   668  	for i := uint64(0); i < numChunks; i++ {
   669  		chunk := make([]crypto.Hash, piecesPerChunk)
   670  		for j := uint64(0); j < piecesPerChunk; j++ {
   671  			fanoutOffset := (i * chunkRootsSize) + (j * crypto.HashSize)
   672  			copy(chunk[j][:], fanoutBytes[fanoutOffset:])
   673  		}
   674  		chunks = append(chunks, chunk)
   675  	}
   676  
   677  	// Make sure the fanout chunks match the filesize.
   678  	expectedFanoutChunks := NumChunks(sl.CipherType, sl.Filesize, uint64(sl.FanoutDataPieces))
   679  	if uint64(numChunks) != expectedFanoutChunks {
   680  		return nil, errors.AddContext(ErrMalformedBaseSector, fmt.Sprintf("unexpected fanout length %v != %v", numChunks, expectedFanoutChunks))
   681  	}
   682  	return chunks, nil
   683  }
   684  
   685  // Encode will return a []byte that has compactly encoded all of the layout
   686  // data.
   687  func (sl SkyfileLayout) Encode() []byte {
   688  	b := make([]byte, SkyfileLayoutSize)
   689  	offset := 0
   690  	b[offset] = sl.Version
   691  	offset++
   692  	binary.LittleEndian.PutUint64(b[offset:], sl.Filesize)
   693  	offset += 8
   694  	binary.LittleEndian.PutUint64(b[offset:], sl.MetadataSize)
   695  	offset += 8
   696  	binary.LittleEndian.PutUint64(b[offset:], sl.FanoutSize)
   697  	offset += 8
   698  	b[offset] = sl.FanoutDataPieces
   699  	offset++
   700  	b[offset] = sl.FanoutParityPieces
   701  	offset++
   702  	copy(b[offset:], sl.CipherType[:])
   703  	offset += len(sl.CipherType)
   704  	copy(b[offset:], sl.KeyData[:])
   705  	offset += len(sl.KeyData)
   706  
   707  	// Sanity check. If this check fails, encode() does not match the
   708  	// SkyfileLayoutSize.
   709  	if offset != SkyfileLayoutSize {
   710  		build.Critical("layout size does not match the amount of data encoded")
   711  	}
   712  	return b
   713  }
   714  
   715  // SkyfileSubfileMetadata is all of the metadata that belongs to a subfile in a
   716  // skyfile. Most importantly it contains the offset at which the subfile is
   717  // written and its length. Its filename can potentially include a '/' character
   718  // as nested files and directories are allowed within a single Skyfile, but it
   719  // is not allowed to contain ./, ../, be empty, or start with a forward slash.
   720  type SkyfileSubfileMetadata struct {
   721  	FileMode    os.FileMode `json:"mode,omitempty,siamismatch"` // different json name for compat reasons
   722  	Filename    string      `json:"filename,omitempty"`
   723  	ContentType string      `json:"contenttype,omitempty"`
   724  	Offset      uint64      `json:"offset,omitempty"`
   725  	Len         uint64      `json:"len,omitempty"`
   726  }
   727  
   728  // IsDir implements the os.FileInfo interface for SkyfileSubfileMetadata.
   729  func (sm SkyfileSubfileMetadata) IsDir() bool {
   730  	return false
   731  }
   732  
   733  // Mode implements the os.FileInfo interface for SkyfileSubfileMetadata.
   734  func (sm SkyfileSubfileMetadata) Mode() os.FileMode {
   735  	return sm.FileMode
   736  }
   737  
   738  // ModTime implements the os.FileInfo interface for SkyfileSubfileMetadata.
   739  func (sm SkyfileSubfileMetadata) ModTime() time.Time {
   740  	return time.Time{} // no modtime available
   741  }
   742  
   743  // Name implements the os.FileInfo interface for SkyfileSubfileMetadata.
   744  func (sm SkyfileSubfileMetadata) Name() string {
   745  	return filepath.Base(sm.Filename)
   746  }
   747  
   748  // Size implements the os.FileInfo interface for SkyfileSubfileMetadata.
   749  func (sm SkyfileSubfileMetadata) Size() int64 {
   750  	return int64(sm.Len)
   751  }
   752  
   753  // Sys implements the os.FileInfo interface for SkyfileSubfileMetadata.
   754  func (sm SkyfileSubfileMetadata) Sys() interface{} {
   755  	return nil
   756  }
   757  
   758  // SkyfileFormat is the file format the API uses to return a Skyfile as.
   759  type SkyfileFormat string
   760  
   761  // Extension returns the extension for the format
   762  func (sf SkyfileFormat) Extension() string {
   763  	switch sf {
   764  	case SkyfileFormatZip:
   765  		return ".zip"
   766  	case SkyfileFormatTar:
   767  		return ".tar"
   768  	case SkyfileFormatTarGz:
   769  		return ".tar.gz"
   770  	default:
   771  		return ""
   772  	}
   773  }
   774  
   775  // IsArchive returns true if the format is an archive.
   776  func (sf SkyfileFormat) IsArchive() bool {
   777  	return sf == SkyfileFormatTar ||
   778  		sf == SkyfileFormatTarGz ||
   779  		sf == SkyfileFormatZip
   780  }
   781  
   782  // ComputeMonetizationPayout is a helper function to decide how much money to
   783  // pay out to a monetizer depending on a given amount and base. The amount is
   784  // the amount the monetizer should be paid for a single access of their
   785  // resource. The base is the actual amount the monetizer is paid with 1 txn. So
   786  // if a monetizer wants $5 and the base is $5, they will be paid out the base.
   787  // If they want $6 and the base is $5, they will receive $6. If the amount is $1
   788  // and the base is $10, the monetizer has a 10% chance of being paid $10.
   789  func ComputeMonetizationPayout(amt, base types.Currency) types.Currency {
   790  	payout, err := computeMonetizationPayout(amt, base, fastrand.Reader)
   791  	if err != nil {
   792  		panic("computeMonetizationPayout should never fail with a fastrand.Reader")
   793  	}
   794  	return payout
   795  }
   796  
   797  // IsSkynetDir is a helper that tells if the siapath is in the Skynet Folder
   798  func IsSkynetDir(sp SiaPath) bool {
   799  	return strings.HasPrefix(sp.String(), SkynetFolder.String())
   800  }
   801  
   802  // computeMonetizationPayout is a helper function to decide how much money to
   803  // pay out to a monetizer depending on a given amount and base. The amount is
   804  // the amount the monetizer should be paid for a single access of their
   805  // resource. The base is the actual amount the monetizer is paid with 1 txn. So
   806  // if a monetizer wants $5 and the base is $5, they will be paid out the base.
   807  // If they want $6 and the base is $5, they will receive $6. If the amount is $1
   808  // and the base is $10, the monetizer has a 10% chance of being paid $10.
   809  func computeMonetizationPayout(amt, base types.Currency, rand io.Reader) (types.Currency, error) {
   810  	// If the amt is 0, we don't pay out.
   811  	if amt.IsZero() {
   812  		return types.ZeroCurrency, nil
   813  	}
   814  
   815  	// The base should never be zero.
   816  	if base.IsZero() {
   817  		build.Critical("computeMonetizationPayout called with 0 base")
   818  		return types.ZeroCurrency, nil
   819  	}
   820  
   821  	// If the amount is >= than the base, we pay out the amount.
   822  	if amt.Cmp(base) >= 0 {
   823  		return amt, nil
   824  	}
   825  
   826  	// We need to generate a large random number n.
   827  	nBytes := make([]byte, monetizationLotteryEntropy)
   828  	_, err := io.ReadFull(rand, nBytes)
   829  	if err != nil {
   830  		return types.ZeroCurrency, err
   831  	}
   832  	n := new(big.Int).SetBytes(nBytes)
   833  
   834  	// Adjust it to be in the range [0 , base).
   835  	n = n.Mod(n, base.Big())
   836  
   837  	// If n < amt, you get the base.
   838  	if n.Cmp(amt.Big()) < 0 {
   839  		return base, nil
   840  	}
   841  	return types.ZeroCurrency, nil
   842  }
   843  
   844  // RegistryEntry is a complete registry entry including the pubkey needed to
   845  // verify it.
   846  type RegistryEntry struct {
   847  	modules.SignedRegistryValue
   848  	PubKey types.SiaPublicKey
   849  }
   850  
   851  // LatestRegistryEntry is a complete registry entry with ID.
   852  type LatestRegistryEntry struct {
   853  	EntryID modules.RegistryEntryID
   854  	RegistryEntry
   855  }
   856  
   857  // Verify verifies the entry.
   858  func (re RegistryEntry) Verify() error {
   859  	return re.SignedRegistryValue.Verify(re.PubKey.ToPublicKey())
   860  }
   861  
   862  // NewRegistryEntry creates a new RegistryEntry.
   863  func NewRegistryEntry(spk types.SiaPublicKey, srv modules.SignedRegistryValue) RegistryEntry {
   864  	return RegistryEntry{
   865  		SignedRegistryValue: srv,
   866  		PubKey:              spk,
   867  	}
   868  }