github.com/ethersphere/bee/v2@v2.2.0/pkg/bmt/pool.go (about)

     1  // Copyright 2021 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package bmt
     6  
     7  import (
     8  	"hash"
     9  	"sync/atomic"
    10  )
    11  
    12  // BaseHasherFunc is a hash.Hash constructor function used for the base hash of the BMT.
    13  // implemented by Keccak256 SHA3 sha3.NewLegacyKeccak256
    14  type BaseHasherFunc func() hash.Hash
    15  
    16  // configuration
    17  type Conf struct {
    18  	segmentSize  int            // size of leaf segments, stipulated to be = hash size
    19  	segmentCount int            // the number of segments on the base level of the BMT
    20  	capacity     int            // pool capacity, controls concurrency
    21  	depth        int            // depth of the bmt trees = int(log2(segmentCount))+1
    22  	maxSize      int            // the total length of the data (count * size)
    23  	zerohashes   [][]byte       // lookup table for predictable padding subtrees for all levels
    24  	hasher       BaseHasherFunc // base hasher to use for the BMT levels
    25  }
    26  
    27  // Pool provides a pool of trees used as resources by the BMT Hasher.
    28  // A tree popped from the pool is guaranteed to have a clean state ready
    29  // for hashing a new chunk.
    30  type Pool struct {
    31  	c     chan *tree // the channel to obtain a resource from the pool
    32  	*Conf            // configuration
    33  }
    34  
    35  func NewConf(hasher BaseHasherFunc, segmentCount, capacity int) *Conf {
    36  	count, depth := sizeToParams(segmentCount)
    37  	segmentSize := hasher().Size()
    38  	zerohashes := make([][]byte, depth+1)
    39  	zeros := make([]byte, segmentSize)
    40  	zerohashes[0] = zeros
    41  	var err error
    42  	// initialises the zerohashes lookup table
    43  	for i := 1; i < depth+1; i++ {
    44  		if zeros, err = doHash(hasher(), zeros, zeros); err != nil {
    45  			panic(err.Error())
    46  		}
    47  		zerohashes[i] = zeros
    48  	}
    49  	return &Conf{
    50  		hasher:       hasher,
    51  		segmentSize:  segmentSize,
    52  		segmentCount: segmentCount,
    53  		capacity:     capacity,
    54  		maxSize:      count * segmentSize,
    55  		depth:        depth,
    56  		zerohashes:   zerohashes,
    57  	}
    58  }
    59  
    60  // NewPool creates a tree pool with hasher, segment size, segment count and capacity
    61  // it reuses free trees or creates a new one if capacity is not reached.
    62  func NewPool(c *Conf) *Pool {
    63  	p := &Pool{
    64  		Conf: c,
    65  		c:    make(chan *tree, c.capacity),
    66  	}
    67  	for i := 0; i < c.capacity; i++ {
    68  		p.c <- newTree(p.segmentSize, p.maxSize, p.depth, p.hasher)
    69  	}
    70  	return p
    71  }
    72  
    73  // Get returns a BMT hasher possibly reusing a tree from the pool
    74  func (p *Pool) Get() *Hasher {
    75  	t := <-p.c
    76  	return &Hasher{
    77  		Conf:   p.Conf,
    78  		result: make(chan []byte),
    79  		errc:   make(chan error, 1),
    80  		span:   make([]byte, SpanSize),
    81  		bmt:    t,
    82  	}
    83  }
    84  
    85  // Put is called after using a bmt hasher to return the tree to a pool for reuse
    86  func (p *Pool) Put(h *Hasher) {
    87  	p.c <- h.bmt
    88  }
    89  
    90  // tree is a reusable control structure representing a BMT
    91  // organised in a binary tree
    92  //
    93  // Hasher uses a Pool to obtain a tree for each chunk hash
    94  // the tree is 'locked' while not in the pool.
    95  type tree struct {
    96  	leaves []*node // leaf nodes of the tree, other nodes accessible via parent links
    97  	buffer []byte
    98  }
    99  
   100  // node is a reusable segment hasher representing a node in a BMT.
   101  type node struct {
   102  	isLeft      bool      // whether it is left side of the parent double segment
   103  	parent      *node     // pointer to parent node in the BMT
   104  	state       int32     // atomic increment impl concurrent boolean toggle
   105  	left, right []byte    // this is where the two children sections are written
   106  	hasher      hash.Hash // preconstructed hasher on nodes
   107  }
   108  
   109  // newNode constructs a segment hasher node in the BMT (used by newTree).
   110  func newNode(index int, parent *node, hasher hash.Hash) *node {
   111  	return &node{
   112  		parent: parent,
   113  		isLeft: index%2 == 0,
   114  		hasher: hasher,
   115  	}
   116  }
   117  
   118  // newTree initialises a tree by building up the nodes of a BMT
   119  //
   120  // segmentSize is stipulated to be the size of the hash.
   121  func newTree(segmentSize, maxsize, depth int, hashfunc func() hash.Hash) *tree {
   122  	n := newNode(0, nil, hashfunc())
   123  	prevlevel := []*node{n}
   124  	// iterate over levels and creates 2^(depth-level) nodes
   125  	// the 0 level is on double segment sections so we start at depth - 2
   126  	count := 2
   127  	for level := depth - 2; level >= 0; level-- {
   128  		nodes := make([]*node, count)
   129  		for i := 0; i < count; i++ {
   130  			parent := prevlevel[i/2]
   131  			nodes[i] = newNode(i, parent, hashfunc())
   132  		}
   133  		prevlevel = nodes
   134  		count *= 2
   135  	}
   136  	// the datanode level is the nodes on the last level
   137  	return &tree{
   138  		leaves: prevlevel,
   139  		buffer: make([]byte, maxsize),
   140  	}
   141  }
   142  
   143  // atomic bool toggle implementing a concurrent reusable 2-state object.
   144  // Atomic addint with %2 implements atomic bool toggle.
   145  // It returns true if the toggler just put it in the active/waiting state.
   146  func (n *node) toggle() bool {
   147  	return atomic.AddInt32(&n.state, 1)%2 == 1
   148  }
   149  
   150  // sizeToParams calculates the depth (number of levels) and segment count in the BMT tree.
   151  func sizeToParams(n int) (c, d int) {
   152  	c = 2
   153  	for ; c < n; c *= 2 {
   154  		d++
   155  	}
   156  	return c, d + 1
   157  }