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 }