github.com/alexdevranger/node-1.8.27@v0.0.0-20221128213301-aa5841e41d2d/swarm/storage/pyramid.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-dubxcoin library. 3 // 4 // The go-dubxcoin library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-dubxcoin library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-dubxcoin library. If not, see <http://www.gnu.org/licenses/>. 16 17 package storage 18 19 import ( 20 "context" 21 "encoding/binary" 22 "errors" 23 "io" 24 "io/ioutil" 25 "sync" 26 "time" 27 28 ch "github.com/alexdevranger/node-1.8.27/swarm/chunk" 29 "github.com/alexdevranger/node-1.8.27/swarm/log" 30 ) 31 32 /* 33 The main idea of a pyramid chunker is to process the input data without knowing the entire size apriori. 34 For this to be achieved, the chunker tree is built from the ground up until the data is exhausted. 35 This opens up new aveneus such as easy append and other sort of modifications to the tree thereby avoiding 36 duplication of data chunks. 37 38 39 Below is an example of a two level chunks tree. The leaf chunks are called data chunks and all the above 40 chunks are called tree chunks. The tree chunk above data chunks is level 0 and so on until it reaches 41 the root tree chunk. 42 43 44 45 T10 <- Tree chunk lvl1 46 | 47 __________________________|_____________________________ 48 / | | \ 49 / | \ \ 50 __T00__ ___T01__ ___T02__ ___T03__ <- Tree chunks lvl 0 51 / / \ / / \ / / \ / / \ 52 / / \ / / \ / / \ / / \ 53 D1 D2 ... D128 D1 D2 ... D128 D1 D2 ... D128 D1 D2 ... D128 <- Data Chunks 54 55 56 The split function continuously read the data and creates data chunks and send them to storage. 57 When certain no of data chunks are created (defaultBranches), a signal is sent to create a tree 58 entry. When the level 0 tree entries reaches certain threshold (defaultBranches), another signal 59 is sent to a tree entry one level up.. and so on... until only the data is exhausted AND only one 60 tree entry is present in certain level. The key of tree entry is given out as the rootAddress of the file. 61 62 */ 63 64 var ( 65 errLoadingTreeRootChunk = errors.New("LoadTree Error: Could not load root chunk") 66 errLoadingTreeChunk = errors.New("LoadTree Error: Could not load chunk") 67 ) 68 69 const ( 70 ChunkProcessors = 8 71 splitTimeout = time.Minute * 5 72 ) 73 74 type PyramidSplitterParams struct { 75 SplitterParams 76 getter Getter 77 } 78 79 func NewPyramidSplitterParams(addr Address, reader io.Reader, putter Putter, getter Getter, chunkSize int64) *PyramidSplitterParams { 80 hashSize := putter.RefSize() 81 return &PyramidSplitterParams{ 82 SplitterParams: SplitterParams{ 83 ChunkerParams: ChunkerParams{ 84 chunkSize: chunkSize, 85 hashSize: hashSize, 86 }, 87 reader: reader, 88 putter: putter, 89 addr: addr, 90 }, 91 getter: getter, 92 } 93 } 94 95 /* 96 When splitting, data is given as a SectionReader, and the key is a hashSize long byte slice (Address), the root hash of the entire content will fill this once processing finishes. 97 New chunks to store are store using the putter which the caller provides. 98 */ 99 func PyramidSplit(ctx context.Context, reader io.Reader, putter Putter, getter Getter) (Address, func(context.Context) error, error) { 100 return NewPyramidSplitter(NewPyramidSplitterParams(nil, reader, putter, getter, ch.DefaultSize)).Split(ctx) 101 } 102 103 func PyramidAppend(ctx context.Context, addr Address, reader io.Reader, putter Putter, getter Getter) (Address, func(context.Context) error, error) { 104 return NewPyramidSplitter(NewPyramidSplitterParams(addr, reader, putter, getter, ch.DefaultSize)).Append(ctx) 105 } 106 107 // Entry to create a tree node 108 type TreeEntry struct { 109 level int 110 branchCount int64 111 subtreeSize uint64 112 chunk []byte 113 key []byte 114 index int // used in append to indicate the index of existing tree entry 115 updatePending bool // indicates if the entry is loaded from existing tree 116 } 117 118 func NewTreeEntry(pyramid *PyramidChunker) *TreeEntry { 119 return &TreeEntry{ 120 level: 0, 121 branchCount: 0, 122 subtreeSize: 0, 123 chunk: make([]byte, pyramid.chunkSize+8), 124 key: make([]byte, pyramid.hashSize), 125 index: 0, 126 updatePending: false, 127 } 128 } 129 130 // Used by the hash processor to create a data/tree chunk and send to storage 131 type chunkJob struct { 132 key Address 133 chunk []byte 134 parentWg *sync.WaitGroup 135 } 136 137 type PyramidChunker struct { 138 chunkSize int64 139 hashSize int64 140 branches int64 141 reader io.Reader 142 putter Putter 143 getter Getter 144 key Address 145 workerCount int64 146 workerLock sync.RWMutex 147 jobC chan *chunkJob 148 wg *sync.WaitGroup 149 errC chan error 150 quitC chan bool 151 rootAddress []byte 152 chunkLevel [][]*TreeEntry 153 } 154 155 func NewPyramidSplitter(params *PyramidSplitterParams) (pc *PyramidChunker) { 156 pc = &PyramidChunker{} 157 pc.reader = params.reader 158 pc.hashSize = params.hashSize 159 pc.branches = params.chunkSize / pc.hashSize 160 pc.chunkSize = pc.hashSize * pc.branches 161 pc.putter = params.putter 162 pc.getter = params.getter 163 pc.key = params.addr 164 pc.workerCount = 0 165 pc.jobC = make(chan *chunkJob, 2*ChunkProcessors) 166 pc.wg = &sync.WaitGroup{} 167 pc.errC = make(chan error) 168 pc.quitC = make(chan bool) 169 pc.rootAddress = make([]byte, pc.hashSize) 170 pc.chunkLevel = make([][]*TreeEntry, pc.branches) 171 return 172 } 173 174 func (pc *PyramidChunker) Join(addr Address, getter Getter, depth int) LazySectionReader { 175 return &LazyChunkReader{ 176 addr: addr, 177 depth: depth, 178 chunkSize: pc.chunkSize, 179 branches: pc.branches, 180 hashSize: pc.hashSize, 181 getter: getter, 182 } 183 } 184 185 func (pc *PyramidChunker) incrementWorkerCount() { 186 pc.workerLock.Lock() 187 defer pc.workerLock.Unlock() 188 pc.workerCount += 1 189 } 190 191 func (pc *PyramidChunker) getWorkerCount() int64 { 192 pc.workerLock.Lock() 193 defer pc.workerLock.Unlock() 194 return pc.workerCount 195 } 196 197 func (pc *PyramidChunker) decrementWorkerCount() { 198 pc.workerLock.Lock() 199 defer pc.workerLock.Unlock() 200 pc.workerCount -= 1 201 } 202 203 func (pc *PyramidChunker) Split(ctx context.Context) (k Address, wait func(context.Context) error, err error) { 204 pc.wg.Add(1) 205 pc.prepareChunks(ctx, false) 206 207 // closes internal error channel if all subprocesses in the workgroup finished 208 go func() { 209 210 // waiting for all chunks to finish 211 pc.wg.Wait() 212 213 //We close errC here because this is passed down to 8 parallel routines underneath. 214 // if a error happens in one of them.. that particular routine raises error... 215 // once they all complete successfully, the control comes back and we can safely close this here. 216 close(pc.errC) 217 }() 218 219 defer close(pc.quitC) 220 defer pc.putter.Close() 221 222 select { 223 case err := <-pc.errC: 224 if err != nil { 225 return nil, nil, err 226 } 227 case <-ctx.Done(): 228 _ = pc.putter.Wait(ctx) //??? 229 return nil, nil, ctx.Err() 230 } 231 return pc.rootAddress, pc.putter.Wait, nil 232 233 } 234 235 func (pc *PyramidChunker) Append(ctx context.Context) (k Address, wait func(context.Context) error, err error) { 236 // Load the right most unfinished tree chunks in every level 237 pc.loadTree(ctx) 238 239 pc.wg.Add(1) 240 pc.prepareChunks(ctx, true) 241 242 // closes internal error channel if all subprocesses in the workgroup finished 243 go func() { 244 245 // waiting for all chunks to finish 246 pc.wg.Wait() 247 248 close(pc.errC) 249 }() 250 251 defer close(pc.quitC) 252 defer pc.putter.Close() 253 254 select { 255 case err := <-pc.errC: 256 if err != nil { 257 return nil, nil, err 258 } 259 case <-time.NewTimer(splitTimeout).C: 260 } 261 262 return pc.rootAddress, pc.putter.Wait, nil 263 264 } 265 266 func (pc *PyramidChunker) processor(ctx context.Context, id int64) { 267 defer pc.decrementWorkerCount() 268 for { 269 select { 270 271 case job, ok := <-pc.jobC: 272 if !ok { 273 return 274 } 275 pc.processChunk(ctx, id, job) 276 case <-pc.quitC: 277 return 278 } 279 } 280 } 281 282 func (pc *PyramidChunker) processChunk(ctx context.Context, id int64, job *chunkJob) { 283 ref, err := pc.putter.Put(ctx, job.chunk) 284 if err != nil { 285 select { 286 case pc.errC <- err: 287 case <-pc.quitC: 288 } 289 } 290 291 // report hash of this chunk one level up (keys corresponds to the proper subslice of the parent chunk) 292 copy(job.key, ref) 293 294 // send off new chunk to storage 295 job.parentWg.Done() 296 } 297 298 func (pc *PyramidChunker) loadTree(ctx context.Context) error { 299 // Get the root chunk to get the total size 300 chunkData, err := pc.getter.Get(ctx, Reference(pc.key)) 301 if err != nil { 302 return errLoadingTreeRootChunk 303 } 304 chunkSize := int64(chunkData.Size()) 305 log.Trace("pyramid.chunker: root chunk", "chunk.Size", chunkSize, "pc.chunkSize", pc.chunkSize) 306 307 //if data size is less than a chunk... add a parent with update as pending 308 if chunkSize <= pc.chunkSize { 309 newEntry := &TreeEntry{ 310 level: 0, 311 branchCount: 1, 312 subtreeSize: uint64(chunkSize), 313 chunk: make([]byte, pc.chunkSize+8), 314 key: make([]byte, pc.hashSize), 315 index: 0, 316 updatePending: true, 317 } 318 copy(newEntry.chunk[8:], pc.key) 319 pc.chunkLevel[0] = append(pc.chunkLevel[0], newEntry) 320 return nil 321 } 322 323 var treeSize int64 324 var depth int 325 treeSize = pc.chunkSize 326 for ; treeSize < chunkSize; treeSize *= pc.branches { 327 depth++ 328 } 329 log.Trace("pyramid.chunker", "depth", depth) 330 331 // Add the root chunk entry 332 branchCount := int64(len(chunkData)-8) / pc.hashSize 333 newEntry := &TreeEntry{ 334 level: depth - 1, 335 branchCount: branchCount, 336 subtreeSize: uint64(chunkSize), 337 chunk: chunkData, 338 key: pc.key, 339 index: 0, 340 updatePending: true, 341 } 342 pc.chunkLevel[depth-1] = append(pc.chunkLevel[depth-1], newEntry) 343 344 // Add the rest of the tree 345 for lvl := depth - 1; lvl >= 1; lvl-- { 346 347 //TODO(jmozah): instead of loading finished branches and then trim in the end, 348 //avoid loading them in the first place 349 for _, ent := range pc.chunkLevel[lvl] { 350 branchCount = int64(len(ent.chunk)-8) / pc.hashSize 351 for i := int64(0); i < branchCount; i++ { 352 key := ent.chunk[8+(i*pc.hashSize) : 8+((i+1)*pc.hashSize)] 353 newChunkData, err := pc.getter.Get(ctx, Reference(key)) 354 if err != nil { 355 return errLoadingTreeChunk 356 } 357 newChunkSize := newChunkData.Size() 358 bewBranchCount := int64(len(newChunkData)-8) / pc.hashSize 359 newEntry := &TreeEntry{ 360 level: lvl - 1, 361 branchCount: bewBranchCount, 362 subtreeSize: newChunkSize, 363 chunk: newChunkData, 364 key: key, 365 index: 0, 366 updatePending: true, 367 } 368 pc.chunkLevel[lvl-1] = append(pc.chunkLevel[lvl-1], newEntry) 369 370 } 371 372 // We need to get only the right most unfinished branch.. so trim all finished branches 373 if int64(len(pc.chunkLevel[lvl-1])) >= pc.branches { 374 pc.chunkLevel[lvl-1] = nil 375 } 376 } 377 } 378 379 return nil 380 } 381 382 func (pc *PyramidChunker) prepareChunks(ctx context.Context, isAppend bool) { 383 defer pc.wg.Done() 384 385 chunkWG := &sync.WaitGroup{} 386 387 pc.incrementWorkerCount() 388 389 go pc.processor(ctx, pc.workerCount) 390 391 parent := NewTreeEntry(pc) 392 var unfinishedChunkData ChunkData 393 var unfinishedChunkSize uint64 394 395 if isAppend && len(pc.chunkLevel[0]) != 0 { 396 lastIndex := len(pc.chunkLevel[0]) - 1 397 ent := pc.chunkLevel[0][lastIndex] 398 399 if ent.branchCount < pc.branches { 400 parent = &TreeEntry{ 401 level: 0, 402 branchCount: ent.branchCount, 403 subtreeSize: ent.subtreeSize, 404 chunk: ent.chunk, 405 key: ent.key, 406 index: lastIndex, 407 updatePending: true, 408 } 409 410 lastBranch := parent.branchCount - 1 411 lastAddress := parent.chunk[8+lastBranch*pc.hashSize : 8+(lastBranch+1)*pc.hashSize] 412 413 var err error 414 unfinishedChunkData, err = pc.getter.Get(ctx, lastAddress) 415 if err != nil { 416 pc.errC <- err 417 } 418 unfinishedChunkSize = unfinishedChunkData.Size() 419 if unfinishedChunkSize < uint64(pc.chunkSize) { 420 parent.subtreeSize = parent.subtreeSize - unfinishedChunkSize 421 parent.branchCount = parent.branchCount - 1 422 } else { 423 unfinishedChunkData = nil 424 } 425 } 426 } 427 428 for index := 0; ; index++ { 429 var err error 430 chunkData := make([]byte, pc.chunkSize+8) 431 432 var readBytes int 433 434 if unfinishedChunkData != nil { 435 copy(chunkData, unfinishedChunkData) 436 readBytes += int(unfinishedChunkSize) 437 unfinishedChunkData = nil 438 log.Trace("pyramid.chunker: found unfinished chunk", "readBytes", readBytes) 439 } 440 441 var res []byte 442 res, err = ioutil.ReadAll(io.LimitReader(pc.reader, int64(len(chunkData)-(8+readBytes)))) 443 444 // hack for ioutil.ReadAll: 445 // a successful call to ioutil.ReadAll returns err == nil, not err == EOF, whereas we 446 // want to propagate the io.EOF error 447 if len(res) == 0 && err == nil { 448 err = io.EOF 449 } 450 copy(chunkData[8+readBytes:], res) 451 452 readBytes += len(res) 453 log.Trace("pyramid.chunker: copied all data", "readBytes", readBytes) 454 455 if err != nil { 456 if err == io.EOF || err == io.ErrUnexpectedEOF { 457 458 pc.cleanChunkLevels() 459 460 // Check if we are appending or the chunk is the only one. 461 if parent.branchCount == 1 && (pc.depth() == 0 || isAppend) { 462 // Data is exactly one chunk.. pick the last chunk key as root 463 chunkWG.Wait() 464 lastChunksAddress := parent.chunk[8 : 8+pc.hashSize] 465 copy(pc.rootAddress, lastChunksAddress) 466 break 467 } 468 } else { 469 close(pc.quitC) 470 break 471 } 472 } 473 474 // Data ended in chunk boundary.. just signal to start bulding tree 475 if readBytes == 0 { 476 pc.buildTree(isAppend, parent, chunkWG, true, nil) 477 break 478 } else { 479 pkey := pc.enqueueDataChunk(chunkData, uint64(readBytes), parent, chunkWG) 480 481 // update tree related parent data structures 482 parent.subtreeSize += uint64(readBytes) 483 parent.branchCount++ 484 485 // Data got exhausted... signal to send any parent tree related chunks 486 if int64(readBytes) < pc.chunkSize { 487 488 pc.cleanChunkLevels() 489 490 // only one data chunk .. so dont add any parent chunk 491 if parent.branchCount <= 1 { 492 chunkWG.Wait() 493 494 if isAppend || pc.depth() == 0 { 495 // No need to build the tree if the depth is 0 496 // or we are appending. 497 // Just use the last key. 498 copy(pc.rootAddress, pkey) 499 } else { 500 // We need to build the tree and and provide the lonely 501 // chunk key to replace the last tree chunk key. 502 pc.buildTree(isAppend, parent, chunkWG, true, pkey) 503 } 504 break 505 } 506 507 pc.buildTree(isAppend, parent, chunkWG, true, nil) 508 break 509 } 510 511 if parent.branchCount == pc.branches { 512 pc.buildTree(isAppend, parent, chunkWG, false, nil) 513 parent = NewTreeEntry(pc) 514 } 515 516 } 517 518 workers := pc.getWorkerCount() 519 if int64(len(pc.jobC)) > workers && workers < ChunkProcessors { 520 pc.incrementWorkerCount() 521 go pc.processor(ctx, pc.workerCount) 522 } 523 524 } 525 526 } 527 528 func (pc *PyramidChunker) buildTree(isAppend bool, ent *TreeEntry, chunkWG *sync.WaitGroup, last bool, lonelyChunkKey []byte) { 529 chunkWG.Wait() 530 pc.enqueueTreeChunk(ent, chunkWG, last) 531 532 compress := false 533 endLvl := pc.branches 534 for lvl := int64(0); lvl < pc.branches; lvl++ { 535 lvlCount := int64(len(pc.chunkLevel[lvl])) 536 if lvlCount >= pc.branches { 537 endLvl = lvl + 1 538 compress = true 539 break 540 } 541 } 542 543 if !compress && !last { 544 return 545 } 546 547 // Wait for all the keys to be processed before compressing the tree 548 chunkWG.Wait() 549 550 for lvl := int64(ent.level); lvl < endLvl; lvl++ { 551 552 lvlCount := int64(len(pc.chunkLevel[lvl])) 553 if lvlCount == 1 && last { 554 copy(pc.rootAddress, pc.chunkLevel[lvl][0].key) 555 return 556 } 557 558 for startCount := int64(0); startCount < lvlCount; startCount += pc.branches { 559 560 endCount := startCount + pc.branches 561 if endCount > lvlCount { 562 endCount = lvlCount 563 } 564 565 var nextLvlCount int64 566 var tempEntry *TreeEntry 567 if len(pc.chunkLevel[lvl+1]) > 0 { 568 nextLvlCount = int64(len(pc.chunkLevel[lvl+1]) - 1) 569 tempEntry = pc.chunkLevel[lvl+1][nextLvlCount] 570 } 571 if isAppend && tempEntry != nil && tempEntry.updatePending { 572 updateEntry := &TreeEntry{ 573 level: int(lvl + 1), 574 branchCount: 0, 575 subtreeSize: 0, 576 chunk: make([]byte, pc.chunkSize+8), 577 key: make([]byte, pc.hashSize), 578 index: int(nextLvlCount), 579 updatePending: true, 580 } 581 for index := int64(0); index < lvlCount; index++ { 582 updateEntry.branchCount++ 583 updateEntry.subtreeSize += pc.chunkLevel[lvl][index].subtreeSize 584 copy(updateEntry.chunk[8+(index*pc.hashSize):8+((index+1)*pc.hashSize)], pc.chunkLevel[lvl][index].key[:pc.hashSize]) 585 } 586 587 pc.enqueueTreeChunk(updateEntry, chunkWG, last) 588 589 } else { 590 591 noOfBranches := endCount - startCount 592 newEntry := &TreeEntry{ 593 level: int(lvl + 1), 594 branchCount: noOfBranches, 595 subtreeSize: 0, 596 chunk: make([]byte, (noOfBranches*pc.hashSize)+8), 597 key: make([]byte, pc.hashSize), 598 index: int(nextLvlCount), 599 updatePending: false, 600 } 601 602 index := int64(0) 603 for i := startCount; i < endCount; i++ { 604 entry := pc.chunkLevel[lvl][i] 605 newEntry.subtreeSize += entry.subtreeSize 606 copy(newEntry.chunk[8+(index*pc.hashSize):8+((index+1)*pc.hashSize)], entry.key[:pc.hashSize]) 607 index++ 608 } 609 // Lonely chunk key is the key of the last chunk that is only one on the last branch. 610 // In this case, ignore the its tree chunk key and replace it with the lonely chunk key. 611 if lonelyChunkKey != nil { 612 // Overwrite the last tree chunk key with the lonely data chunk key. 613 copy(newEntry.chunk[int64(len(newEntry.chunk))-pc.hashSize:], lonelyChunkKey[:pc.hashSize]) 614 } 615 616 pc.enqueueTreeChunk(newEntry, chunkWG, last) 617 618 } 619 620 } 621 622 if !isAppend { 623 chunkWG.Wait() 624 if compress { 625 pc.chunkLevel[lvl] = nil 626 } 627 } 628 } 629 630 } 631 632 func (pc *PyramidChunker) enqueueTreeChunk(ent *TreeEntry, chunkWG *sync.WaitGroup, last bool) { 633 if ent != nil && ent.branchCount > 0 { 634 635 // wait for data chunks to get over before processing the tree chunk 636 if last { 637 chunkWG.Wait() 638 } 639 640 binary.LittleEndian.PutUint64(ent.chunk[:8], ent.subtreeSize) 641 ent.key = make([]byte, pc.hashSize) 642 chunkWG.Add(1) 643 select { 644 case pc.jobC <- &chunkJob{ent.key, ent.chunk[:ent.branchCount*pc.hashSize+8], chunkWG}: 645 case <-pc.quitC: 646 } 647 648 // Update or append based on weather it is a new entry or being reused 649 if ent.updatePending { 650 chunkWG.Wait() 651 pc.chunkLevel[ent.level][ent.index] = ent 652 } else { 653 pc.chunkLevel[ent.level] = append(pc.chunkLevel[ent.level], ent) 654 } 655 656 } 657 } 658 659 func (pc *PyramidChunker) enqueueDataChunk(chunkData []byte, size uint64, parent *TreeEntry, chunkWG *sync.WaitGroup) Address { 660 binary.LittleEndian.PutUint64(chunkData[:8], size) 661 pkey := parent.chunk[8+parent.branchCount*pc.hashSize : 8+(parent.branchCount+1)*pc.hashSize] 662 663 chunkWG.Add(1) 664 select { 665 case pc.jobC <- &chunkJob{pkey, chunkData[:size+8], chunkWG}: 666 case <-pc.quitC: 667 } 668 669 return pkey 670 671 } 672 673 // depth returns the number of chunk levels. 674 // It is used to detect if there is only one data chunk 675 // left for the last branch. 676 func (pc *PyramidChunker) depth() (d int) { 677 for _, l := range pc.chunkLevel { 678 if l == nil { 679 return 680 } 681 d++ 682 } 683 return 684 } 685 686 // cleanChunkLevels removes gaps (nil levels) between chunk levels 687 // that are not nil. 688 func (pc *PyramidChunker) cleanChunkLevels() { 689 for i, l := range pc.chunkLevel { 690 if l == nil { 691 pc.chunkLevel = append(pc.chunkLevel[:i], append(pc.chunkLevel[i+1:], nil)...) 692 } 693 } 694 }