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 }