gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/trustlessstreamer.go (about) 1 package renter 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "io" 7 8 "gitlab.com/NebulousLabs/errors" 9 "gitlab.com/SkynetLabs/skyd/build" 10 "gitlab.com/SkynetLabs/skyd/skymodules" 11 "go.sia.tech/core/net/rhp" 12 "go.sia.tech/siad/crypto" 13 ) 14 15 // BuildSubSectionProof builds a merkle proof by combining a subSectionProof 16 // leading up to a section root and a sectionProof leading up to a sector root. 17 // The provided sectionIndex describes the index of the datasection within the 18 // streambuffer. 19 func BuildSubSectionProof(sectionIndexInChunk uint64, subSectionProof, sectionProof []crypto.Hash) []crypto.Hash { 20 i := rhp.RangeProofSize(sectionIndexInChunk+1, sectionIndexInChunk, sectionIndexInChunk+1) 21 left := append([]crypto.Hash{}, sectionProof[:i]...) 22 right := append([]crypto.Hash{}, sectionProof[i:]...) 23 proof := append(left, subSectionProof...) 24 proof = append(proof, right...) 25 return proof 26 } 27 28 // writeTrustlessLargeRecursive handles downloading from a large skyfile. Since 29 // this is more involved than downloading a small file this will be implemented 30 // in a f/u. 31 func (s *stream) writeTrustlessLarge(w io.Writer, length uint64) error { 32 // Convenience vars. 33 sb := s.staticStreamBuffer 34 dataSize := sb.staticDataSource.DataSize() 35 36 // Check that the read is within bounds. 37 if s.offset+length > dataSize { 38 return fmt.Errorf("out-of-bounds read: %v + %v > %v", s.offset, length, dataSize) 39 } 40 41 // Write sections one-by-one. 42 for length > 0 { 43 n, err := s.writeTrustlessLargeSection(w, s.offset, length) 44 if err != nil { 45 return fmt.Errorf("failed to write section %v: %v", s.offset, err) 46 } 47 length -= n 48 } 49 return nil 50 } 51 52 // writeTrustlessLargeSection writes the logical data required for the next 53 // length bytes of a section plus proves to the connection. 54 func (s *stream) writeTrustlessLargeSection(w io.Writer, currentOffset, length uint64) (uint64, error) { 55 // Helper vars. 56 sb := s.staticStreamBuffer 57 ds := sb.staticDataSource 58 layout := sb.staticDataSource.Layout() 59 60 // Various helper vars related to indices and offsets within chunks/sections. 61 currentSection := currentOffset / sb.staticDataSource.RequestSize() 62 offsetInSection := currentOffset % sb.staticDataSource.RequestSize() 63 chunkSize := skymodules.ChunkSize(layout.CipherType, uint64(layout.FanoutDataPieces)) 64 sectionSize := sb.staticDataSource.RequestSize() 65 chunkIndex := currentOffset / chunkSize 66 offsetInChunk := currentOffset % chunkSize 67 sectionsPerChunk := chunkSize / sectionSize 68 if chunkSize%sectionSize != 0 { 69 return 0, fmt.Errorf("chunkSize must be section aligned %v mod %v == %v", chunkSize, sectionSize, chunkSize%sectionSize) 70 } 71 72 // Cap the length at a section size. 73 if offsetInSection+length > sb.staticDataSource.RequestSize() { 74 length = sb.staticDataSource.RequestSize() - offsetInSection 75 } 76 77 // Get an erasure coder for the fanout. 78 fanoutEC, err := skymodules.NewRSSubCode(int(layout.FanoutDataPieces), int(layout.FanoutParityPieces), crypto.SegmentSize) 79 if err != nil { 80 return 0, err 81 } 82 83 // Compute the sectionIndexInChunk. 84 sectionIndexInChunk := currentSection % sectionsPerChunk 85 sectionOffInChunk, _ := GetPieceOffsetAndLen(fanoutEC, offsetInChunk/sectionSize*sectionSize, sectionSize) 86 87 // Get the datasection for the current section. 88 sb.mu.Lock() 89 dataSection, exists := sb.dataSections[currentSection] 90 sb.mu.Unlock() 91 if !exists { 92 err := errors.New("writeTrustLessLarge: data section should always be present in the stream buffer for the current offset of a stream") 93 build.Critical(err) 94 return 0, err 95 } 96 97 // Wait for the data. 98 dd, err := dataSection.managedData(s.staticContext) 99 if err != nil { 100 return 0, err 101 } 102 103 // We need to return only the minimum number of pieces 104 neededPieces := layout.FanoutDataPieces 105 106 // Write a piece, followed by the proof. 107 piecesSent := uint8(0) 108 for pieceIndex, piece := range dd.LogicalChunkData { 109 if piecesSent >= neededPieces { 110 break // don't need more than min pieces even if we got more 111 } 112 if len(piece) == 0 { 113 continue 114 } 115 proof := dd.Proofs[pieceIndex] 116 if len(proof) == 0 { 117 build.Critical("shouldn't have an empty proof for a non-empty piece") 118 continue 119 } 120 121 // Fetch the root of the piece within the fanout, the proof for 122 // that root as well as the offset. 123 logicalPieceRoot, pieceRootProof, pieceRootOff, err := ds.ReadFanout(chunkIndex, uint64(pieceIndex)) 124 if err != nil { 125 return 0, err 126 } 127 var pieceRoot crypto.Hash 128 copy(pieceRoot[:], logicalPieceRoot[pieceRootOff%crypto.SegmentSize:][:crypto.HashSize]) 129 130 // Write the offset of the piece root, followed by the piece root and 131 // the proof for the piece root. 132 err = binary.Write(w, binary.LittleEndian, pieceRootOff) 133 if err != nil { 134 return 0, err 135 } 136 _, err = w.Write(logicalPieceRoot) 137 if err != nil { 138 return 0, err 139 } 140 for _, h := range pieceRootProof { 141 _, err = w.Write(h[:]) 142 if err != nil { 143 return 0, err 144 } 145 } 146 147 // Trim the piece according to the requested offset and length. 148 pieceOff, pieceLen := GetPieceOffsetAndLen(fanoutEC, offsetInChunk, length) 149 pieceOffInChunk := pieceOff - sectionOffInChunk 150 trimmedPiece := piece[pieceOffInChunk:][:pieceLen] 151 152 // Finally write the piece and the piece proof. 153 _, err = w.Write(trimmedPiece) 154 if err != nil { 155 return 0, err 156 } 157 158 subSectionProofStart := pieceOffInChunk / crypto.SegmentSize 159 subSectionProofEnd := (pieceOffInChunk + pieceLen) / crypto.SegmentSize 160 subSectionProof := crypto.MerkleRangeProof(piece, int(subSectionProofStart), int(subSectionProofEnd)) 161 fullPieceProof := BuildSubSectionProof(sectionIndexInChunk, subSectionProof, proof) 162 for _, h := range fullPieceProof { 163 _, err = w.Write(h[:]) 164 if err != nil { 165 return 0, err 166 } 167 } 168 169 // Piece sent successfully. 170 piecesSent++ 171 } 172 if piecesSent < neededPieces { 173 return 0, errors.New("not enough pieces sent") 174 } 175 176 // Update offset. 177 s.offset += length 178 s.prepareOffset() 179 return length, nil 180 } 181 182 // writeTrustlessLargeRecursive handles downloading from a large skyfile with a 183 // recursive fanout. 184 // NOTE: This is not supported right at the moment. 185 func (s *stream) writeTrustlessLargeRecursive() error { 186 return errors.New("trustless download of recursive fanout skylinks not yet supported") 187 } 188 189 // writeTrustlessSmall handles downloading from a small skyfile. 190 func (s *stream) writeTrustlessSmall(w io.Writer, length uint64) error { 191 sb := s.staticStreamBuffer 192 ds := sb.staticDataSource 193 dr, err := ds.ReadBaseSectorPayload(s.offset, length) 194 if err != nil { 195 return err 196 } 197 dd := dr.externDownloadedData 198 199 // We are sending payload from a base sector. So we should not need to send more 200 // than a single piece. 201 for pieceIndex, piece := range dd.LogicalChunkData { 202 if piece == nil { 203 continue 204 } 205 // Write the piece. 206 _, err := w.Write(piece) 207 if err != nil { 208 return err 209 } 210 211 // Write the corresponding proof. 212 for _, h := range dd.Proofs[pieceIndex] { 213 _, err = w.Write(h[:]) 214 if err != nil { 215 return err 216 } 217 } 218 return nil 219 } 220 err = errors.New("Failed to send a piece to the client. This should never happen.") 221 build.Critical(err) 222 return err 223 } 224 225 // TrustlessWrite writes the trustless download of a certain length to the 226 // writer. 227 func (s *stream) TrustlessWrite(w io.Writer, length uint64) error { 228 s.mu.Lock() 229 defer s.mu.Unlock() 230 231 sb := s.staticStreamBuffer 232 ds := sb.staticDataSource 233 234 // Fetch the layout and its proof. 235 layout, layoutData, proof := ds.RawLayout() 236 237 // Can't request no data. 238 if length == 0 { 239 return errors.New("can't request 0 byte") 240 } 241 242 // Check for out-of-bounds read. 243 if s.offset+length > layout.Filesize { 244 return fmt.Errorf("read at offset %v and length %v is out-of-bounds for file of size %v", s.offset, length, layout.Filesize) 245 } 246 247 // Write metadata first. 248 err := binary.Write(w, binary.LittleEndian, uint64(ds.RequestSize())) 249 if err != nil { 250 return err 251 } 252 253 // Write the layout and proof. 254 _, err = w.Write(layoutData) 255 if err != nil { 256 return errors.AddContext(err, "failed to write layout data") 257 } 258 259 for _, h := range proof { 260 _, err = w.Write(h[:]) 261 if err != nil { 262 return errors.AddContext(err, "failed to write layout proof") 263 } 264 } 265 266 // Call appropriate handler for the actual data. 267 if layout.IsSmallFile() { 268 return s.writeTrustlessSmall(w, length) 269 } 270 if ds.HasRecursiveFanout() { 271 return s.writeTrustlessLargeRecursive() 272 } 273 return s.writeTrustlessLarge(w, length) 274 }