github.com/etherbanking/go-etherbanking@v1.7.1-0.20181009210156-cf649bca5aba/swarm/storage/chunker.go (about)

     1  // Copyright 2016 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum 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-ethereum 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-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package storage
    18  
    19  import (
    20  	"encoding/binary"
    21  	"errors"
    22  	"fmt"
    23  	"hash"
    24  	"io"
    25  	"sync"
    26  )
    27  
    28  /*
    29  The distributed storage implemented in this package requires fix sized chunks of content.
    30  
    31  Chunker is the interface to a component that is responsible for disassembling and assembling larger data.
    32  
    33  TreeChunker implements a Chunker based on a tree structure defined as follows:
    34  
    35  1 each node in the tree including the root and other branching nodes are stored as a chunk.
    36  
    37  2 branching nodes encode data contents that includes the size of the dataslice covered by its entire subtree under the node as well as the hash keys of all its children :
    38  data_{i} := size(subtree_{i}) || key_{j} || key_{j+1} .... || key_{j+n-1}
    39  
    40  3 Leaf nodes encode an actual subslice of the input data.
    41  
    42  4 if data size is not more than maximum chunksize, the data is stored in a single chunk
    43    key = hash(int64(size) + data)
    44  
    45  5 if data size is more than chunksize*branches^l, but no more than chunksize*
    46    branches^(l+1), the data vector is split into slices of chunksize*
    47    branches^l length (except the last one).
    48    key = hash(int64(size) + key(slice0) + key(slice1) + ...)
    49  
    50   The underlying hash function is configurable
    51  */
    52  
    53  const (
    54  	defaultHash = "SHA3"
    55  	// defaultHash = "BMTSHA3" // http://golang.org/pkg/hash/#Hash
    56  	// defaultHash           = "SHA256" // http://golang.org/pkg/hash/#Hash
    57  	defaultBranches int64 = 128
    58  	// hashSize     int64 = hasherfunc.New().Size() // hasher knows about its own length in bytes
    59  	// chunksize    int64 = branches * hashSize     // chunk is defined as this
    60  )
    61  
    62  /*
    63  Tree chunker is a concrete implementation of data chunking.
    64  This chunker works in a simple way, it builds a tree out of the document so that each node either represents a chunk of real data or a chunk of data representing an branching non-leaf node of the tree. In particular each such non-leaf chunk will represent is a concatenation of the hash of its respective children. This scheme simultaneously guarantees data integrity as well as self addressing. Abstract nodes are transparent since their represented size component is strictly greater than their maximum data size, since they encode a subtree.
    65  
    66  If all is well it is possible to implement this by simply composing readers so that no extra allocation or buffering is necessary for the data splitting and joining. This means that in principle there can be direct IO between : memory, file system, network socket (bzz peers storage request is read from the socket). In practice there may be need for several stages of internal buffering.
    67  The hashing itself does use extra copies and allocation though, since it does need it.
    68  */
    69  
    70  type ChunkerParams struct {
    71  	Branches int64
    72  	Hash     string
    73  }
    74  
    75  func NewChunkerParams() *ChunkerParams {
    76  	return &ChunkerParams{
    77  		Branches: defaultBranches,
    78  		Hash:     defaultHash,
    79  	}
    80  }
    81  
    82  type TreeChunker struct {
    83  	branches int64
    84  	hashFunc Hasher
    85  	// calculated
    86  	hashSize    int64 // self.hashFunc.New().Size()
    87  	chunkSize   int64 // hashSize* branches
    88  	workerCount int
    89  }
    90  
    91  func NewTreeChunker(params *ChunkerParams) (self *TreeChunker) {
    92  	self = &TreeChunker{}
    93  	self.hashFunc = MakeHashFunc(params.Hash)
    94  	self.branches = params.Branches
    95  	self.hashSize = int64(self.hashFunc().Size())
    96  	self.chunkSize = self.hashSize * self.branches
    97  	self.workerCount = 1
    98  	return
    99  }
   100  
   101  // func (self *TreeChunker) KeySize() int64 {
   102  // 	return self.hashSize
   103  // }
   104  
   105  // String() for pretty printing
   106  func (self *Chunk) String() string {
   107  	return fmt.Sprintf("Key: %v TreeSize: %v Chunksize: %v", self.Key.Log(), self.Size, len(self.SData))
   108  }
   109  
   110  type hashJob struct {
   111  	key      Key
   112  	chunk    []byte
   113  	size     int64
   114  	parentWg *sync.WaitGroup
   115  }
   116  
   117  func (self *TreeChunker) Split(data io.Reader, size int64, chunkC chan *Chunk, swg, wwg *sync.WaitGroup) (Key, error) {
   118  
   119  	if self.chunkSize <= 0 {
   120  		panic("chunker must be initialised")
   121  	}
   122  
   123  	jobC := make(chan *hashJob, 2*processors)
   124  	wg := &sync.WaitGroup{}
   125  	errC := make(chan error)
   126  	quitC := make(chan bool)
   127  
   128  	// wwg = workers waitgroup keeps track of hashworkers spawned by this split call
   129  	if wwg != nil {
   130  		wwg.Add(1)
   131  	}
   132  	go self.hashWorker(jobC, chunkC, errC, quitC, swg, wwg)
   133  
   134  	depth := 0
   135  	treeSize := self.chunkSize
   136  
   137  	// takes lowest depth such that chunksize*HashCount^(depth+1) > size
   138  	// power series, will find the order of magnitude of the data size in base hashCount or numbers of levels of branching in the resulting tree.
   139  	for ; treeSize < size; treeSize *= self.branches {
   140  		depth++
   141  	}
   142  
   143  	key := make([]byte, self.hashFunc().Size())
   144  	// this waitgroup member is released after the root hash is calculated
   145  	wg.Add(1)
   146  	//launch actual recursive function passing the waitgroups
   147  	go self.split(depth, treeSize/self.branches, key, data, size, jobC, chunkC, errC, quitC, wg, swg, wwg)
   148  
   149  	// closes internal error channel if all subprocesses in the workgroup finished
   150  	go func() {
   151  		// waiting for all threads to finish
   152  		wg.Wait()
   153  		// if storage waitgroup is non-nil, we wait for storage to finish too
   154  		if swg != nil {
   155  			swg.Wait()
   156  		}
   157  		close(errC)
   158  	}()
   159  
   160  	//TODO: add a timeout
   161  	if err := <-errC; err != nil {
   162  		close(quitC)
   163  		return nil, err
   164  	}
   165  
   166  	return key, nil
   167  }
   168  
   169  func (self *TreeChunker) split(depth int, treeSize int64, key Key, data io.Reader, size int64, jobC chan *hashJob, chunkC chan *Chunk, errC chan error, quitC chan bool, parentWg, swg, wwg *sync.WaitGroup) {
   170  
   171  	for depth > 0 && size < treeSize {
   172  		treeSize /= self.branches
   173  		depth--
   174  	}
   175  
   176  	if depth == 0 {
   177  		// leaf nodes -> content chunks
   178  		chunkData := make([]byte, size+8)
   179  		binary.LittleEndian.PutUint64(chunkData[0:8], uint64(size))
   180  		var readBytes int64
   181  		for readBytes < size {
   182  			n, err := data.Read(chunkData[8+readBytes:])
   183  			readBytes += int64(n)
   184  			if err != nil && !(err == io.EOF && readBytes == size) {
   185  				errC <- err
   186  				return
   187  			}
   188  		}
   189  		select {
   190  		case jobC <- &hashJob{key, chunkData, size, parentWg}:
   191  		case <-quitC:
   192  		}
   193  		return
   194  	}
   195  	// dept > 0
   196  	// intermediate chunk containing child nodes hashes
   197  	branchCnt := int64((size + treeSize - 1) / treeSize)
   198  
   199  	var chunk []byte = make([]byte, branchCnt*self.hashSize+8)
   200  	var pos, i int64
   201  
   202  	binary.LittleEndian.PutUint64(chunk[0:8], uint64(size))
   203  
   204  	childrenWg := &sync.WaitGroup{}
   205  	var secSize int64
   206  	for i < branchCnt {
   207  		// the last item can have shorter data
   208  		if size-pos < treeSize {
   209  			secSize = size - pos
   210  		} else {
   211  			secSize = treeSize
   212  		}
   213  		// the hash of that data
   214  		subTreeKey := chunk[8+i*self.hashSize : 8+(i+1)*self.hashSize]
   215  
   216  		childrenWg.Add(1)
   217  		self.split(depth-1, treeSize/self.branches, subTreeKey, data, secSize, jobC, chunkC, errC, quitC, childrenWg, swg, wwg)
   218  
   219  		i++
   220  		pos += treeSize
   221  	}
   222  	// wait for all the children to complete calculating their hashes and copying them onto sections of the chunk
   223  	// parentWg.Add(1)
   224  	// go func() {
   225  	childrenWg.Wait()
   226  	if len(jobC) > self.workerCount && self.workerCount < processors {
   227  		if wwg != nil {
   228  			wwg.Add(1)
   229  		}
   230  		self.workerCount++
   231  		go self.hashWorker(jobC, chunkC, errC, quitC, swg, wwg)
   232  	}
   233  	select {
   234  	case jobC <- &hashJob{key, chunk, size, parentWg}:
   235  	case <-quitC:
   236  	}
   237  }
   238  
   239  func (self *TreeChunker) hashWorker(jobC chan *hashJob, chunkC chan *Chunk, errC chan error, quitC chan bool, swg, wwg *sync.WaitGroup) {
   240  	hasher := self.hashFunc()
   241  	if wwg != nil {
   242  		defer wwg.Done()
   243  	}
   244  	for {
   245  		select {
   246  
   247  		case job, ok := <-jobC:
   248  			if !ok {
   249  				return
   250  			}
   251  			// now we got the hashes in the chunk, then hash the chunks
   252  			hasher.Reset()
   253  			self.hashChunk(hasher, job, chunkC, swg)
   254  		case <-quitC:
   255  			return
   256  		}
   257  	}
   258  }
   259  
   260  // The treeChunkers own Hash hashes together
   261  // - the size (of the subtree encoded in the Chunk)
   262  // - the Chunk, ie. the contents read from the input reader
   263  func (self *TreeChunker) hashChunk(hasher hash.Hash, job *hashJob, chunkC chan *Chunk, swg *sync.WaitGroup) {
   264  	hasher.Write(job.chunk)
   265  	h := hasher.Sum(nil)
   266  	newChunk := &Chunk{
   267  		Key:   h,
   268  		SData: job.chunk,
   269  		Size:  job.size,
   270  		wg:    swg,
   271  	}
   272  
   273  	// report hash of this chunk one level up (keys corresponds to the proper subslice of the parent chunk)
   274  	copy(job.key, h)
   275  	// send off new chunk to storage
   276  	if chunkC != nil {
   277  		if swg != nil {
   278  			swg.Add(1)
   279  		}
   280  	}
   281  	job.parentWg.Done()
   282  
   283  	if chunkC != nil {
   284  		chunkC <- newChunk
   285  	}
   286  }
   287  
   288  // LazyChunkReader implements LazySectionReader
   289  type LazyChunkReader struct {
   290  	key       Key         // root key
   291  	chunkC    chan *Chunk // chunk channel to send retrieve requests on
   292  	chunk     *Chunk      // size of the entire subtree
   293  	off       int64       // offset
   294  	chunkSize int64       // inherit from chunker
   295  	branches  int64       // inherit from chunker
   296  	hashSize  int64       // inherit from chunker
   297  }
   298  
   299  // implements the Joiner interface
   300  func (self *TreeChunker) Join(key Key, chunkC chan *Chunk) LazySectionReader {
   301  
   302  	return &LazyChunkReader{
   303  		key:       key,
   304  		chunkC:    chunkC,
   305  		chunkSize: self.chunkSize,
   306  		branches:  self.branches,
   307  		hashSize:  self.hashSize,
   308  	}
   309  }
   310  
   311  // Size is meant to be called on the LazySectionReader
   312  func (self *LazyChunkReader) Size(quitC chan bool) (n int64, err error) {
   313  	if self.chunk != nil {
   314  		return self.chunk.Size, nil
   315  	}
   316  	chunk := retrieve(self.key, self.chunkC, quitC)
   317  	if chunk == nil {
   318  		select {
   319  		case <-quitC:
   320  			return 0, errors.New("aborted")
   321  		default:
   322  			return 0, fmt.Errorf("root chunk not found for %v", self.key.Hex())
   323  		}
   324  	}
   325  	self.chunk = chunk
   326  	return chunk.Size, nil
   327  }
   328  
   329  // read at can be called numerous times
   330  // concurrent reads are allowed
   331  // Size() needs to be called synchronously on the LazyChunkReader first
   332  func (self *LazyChunkReader) ReadAt(b []byte, off int64) (read int, err error) {
   333  	// this is correct, a swarm doc cannot be zero length, so no EOF is expected
   334  	if len(b) == 0 {
   335  		return 0, nil
   336  	}
   337  	quitC := make(chan bool)
   338  	size, err := self.Size(quitC)
   339  	if err != nil {
   340  		return 0, err
   341  	}
   342  
   343  	errC := make(chan error)
   344  
   345  	// }
   346  	var treeSize int64
   347  	var depth int
   348  	// calculate depth and max treeSize
   349  	treeSize = self.chunkSize
   350  	for ; treeSize < size; treeSize *= self.branches {
   351  		depth++
   352  	}
   353  	wg := sync.WaitGroup{}
   354  	wg.Add(1)
   355  	go self.join(b, off, off+int64(len(b)), depth, treeSize/self.branches, self.chunk, &wg, errC, quitC)
   356  	go func() {
   357  		wg.Wait()
   358  		close(errC)
   359  	}()
   360  
   361  	err = <-errC
   362  	if err != nil {
   363  		close(quitC)
   364  
   365  		return 0, err
   366  	}
   367  	if off+int64(len(b)) >= size {
   368  		return len(b), io.EOF
   369  	}
   370  	return len(b), nil
   371  }
   372  
   373  func (self *LazyChunkReader) join(b []byte, off int64, eoff int64, depth int, treeSize int64, chunk *Chunk, parentWg *sync.WaitGroup, errC chan error, quitC chan bool) {
   374  	defer parentWg.Done()
   375  	// return NewDPA(&LocalStore{})
   376  
   377  	// chunk.Size = int64(binary.LittleEndian.Uint64(chunk.SData[0:8]))
   378  
   379  	// find appropriate block level
   380  	for chunk.Size < treeSize && depth > 0 {
   381  		treeSize /= self.branches
   382  		depth--
   383  	}
   384  
   385  	// leaf chunk found
   386  	if depth == 0 {
   387  		extra := 8 + eoff - int64(len(chunk.SData))
   388  		if extra > 0 {
   389  			eoff -= extra
   390  		}
   391  		copy(b, chunk.SData[8+off:8+eoff])
   392  		return // simply give back the chunks reader for content chunks
   393  	}
   394  
   395  	// subtree
   396  	start := off / treeSize
   397  	end := (eoff + treeSize - 1) / treeSize
   398  
   399  	wg := &sync.WaitGroup{}
   400  	defer wg.Wait()
   401  
   402  	for i := start; i < end; i++ {
   403  		soff := i * treeSize
   404  		roff := soff
   405  		seoff := soff + treeSize
   406  
   407  		if soff < off {
   408  			soff = off
   409  		}
   410  		if seoff > eoff {
   411  			seoff = eoff
   412  		}
   413  		if depth > 1 {
   414  			wg.Wait()
   415  		}
   416  		wg.Add(1)
   417  		go func(j int64) {
   418  			childKey := chunk.SData[8+j*self.hashSize : 8+(j+1)*self.hashSize]
   419  			chunk := retrieve(childKey, self.chunkC, quitC)
   420  			if chunk == nil {
   421  				select {
   422  				case errC <- fmt.Errorf("chunk %v-%v not found", off, off+treeSize):
   423  				case <-quitC:
   424  				}
   425  				return
   426  			}
   427  			if soff < off {
   428  				soff = off
   429  			}
   430  			self.join(b[soff-off:seoff-off], soff-roff, seoff-roff, depth-1, treeSize/self.branches, chunk, wg, errC, quitC)
   431  		}(i)
   432  	} //for
   433  }
   434  
   435  // the helper method submits chunks for a key to a oueue (DPA) and
   436  // block until they time out or arrive
   437  // abort if quitC is readable
   438  func retrieve(key Key, chunkC chan *Chunk, quitC chan bool) *Chunk {
   439  	chunk := &Chunk{
   440  		Key: key,
   441  		C:   make(chan bool), // close channel to signal data delivery
   442  	}
   443  	// submit chunk for retrieval
   444  	select {
   445  	case chunkC <- chunk: // submit retrieval request, someone should be listening on the other side (or we will time out globally)
   446  	case <-quitC:
   447  		return nil
   448  	}
   449  	// waiting for the chunk retrieval
   450  	select { // chunk.Size = int64(binary.LittleEndian.Uint64(chunk.SData[0:8]))
   451  
   452  	case <-quitC:
   453  		// this is how we control process leakage (quitC is closed once join is finished (after timeout))
   454  		return nil
   455  	case <-chunk.C: // bells are ringing, data have been delivered
   456  	}
   457  	if len(chunk.SData) == 0 {
   458  		return nil // chunk.Size = int64(binary.LittleEndian.Uint64(chunk.SData[0:8]))
   459  
   460  	}
   461  	return chunk
   462  }
   463  
   464  // Read keeps a cursor so cannot be called simulateously, see ReadAt
   465  func (self *LazyChunkReader) Read(b []byte) (read int, err error) {
   466  	read, err = self.ReadAt(b, self.off)
   467  
   468  	self.off += int64(read)
   469  	return
   470  }
   471  
   472  // completely analogous to standard SectionReader implementation
   473  var errWhence = errors.New("Seek: invalid whence")
   474  var errOffset = errors.New("Seek: invalid offset")
   475  
   476  func (s *LazyChunkReader) Seek(offset int64, whence int) (int64, error) {
   477  	switch whence {
   478  	default:
   479  		return 0, errWhence
   480  	case 0:
   481  		offset += 0
   482  	case 1:
   483  		offset += s.off
   484  	case 2:
   485  		if s.chunk == nil { //seek from the end requires rootchunk for size. call Size first
   486  			_, err := s.Size(nil)
   487  			if err != nil {
   488  				return 0, fmt.Errorf("can't get size: %v", err)
   489  			}
   490  		}
   491  		offset += s.chunk.Size
   492  	}
   493  
   494  	if offset < 0 {
   495  		return 0, errOffset
   496  	}
   497  	s.off = offset
   498  	return offset, nil
   499  }