Skip to content

Commit 7aecf7a

Browse files
committed
chore: revert prover to goroutine path
1 parent 24abb91 commit 7aecf7a

12 files changed

Lines changed: 148 additions & 182 deletions

File tree

pkg/bmt/bmt.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package bmt
77
import (
88
"encoding/binary"
99
"hash"
10-
"sync/atomic"
1110

1211
"github.com/ethersphere/bee/v2/pkg/swarm"
1312
)
@@ -17,18 +16,12 @@ var (
1716
zerosection = make([]byte, 64)
1817
)
1918

20-
// simdOptIn controls whether NewPool and friends return a SIMD-backed BMT pool.
21-
// Default is false; cmd/bee flips it on startup after parsing --use-simd-hashing.
22-
// Accessed via SIMDOptIn / SetSIMDOptIn so concurrent reads during startup are
23-
// race-free.
24-
var simdOptIn atomic.Bool
25-
2619
// SIMDOptIn reports whether the SIMD hasher has been opted into.
27-
func SIMDOptIn() bool { return simdOptIn.Load() }
20+
var SIMDOptIn func() bool = func() bool { return false }
2821

2922
// SetSIMDOptIn sets the SIMD opt-in flag. Intended to be called once during
3023
// startup before the first NewPool call (cmd/bee calls it after flag parsing).
31-
func SetSIMDOptIn(b bool) { simdOptIn.Store(b) }
24+
func SetSIMDOptIn(b bool) { SIMDOptIn = func() bool { return b } }
3225

3326
// LengthToSpan creates a binary data span size representation.
3427
// It is required for calculating the BMT hash.

pkg/bmt/hasher_goroutine.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,17 +108,6 @@ func (h *goroutineHasher) Hash(b []byte) ([]byte, error) {
108108
}
109109
}
110110

111-
// HashPadded zero-pads any unwritten sections then computes the BMT root.
112-
// Required for inclusion-proof generation so the tree is fully populated.
113-
func (h *goroutineHasher) HashPadded(b []byte) ([]byte, error) {
114-
for i := h.size; i < h.maxSize; i += len(zerosection) {
115-
if _, err := h.Write(zerosection); err != nil {
116-
return nil, err
117-
}
118-
}
119-
return h.Hash(b)
120-
}
121-
122111
// Reset prepares the Hasher for reuse.
123112
func (h *goroutineHasher) Reset() {
124113
h.pos = 0

pkg/bmt/hasher_simd.go

Lines changed: 0 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,6 @@ func (h *simdHasher) Hash(b []byte) ([]byte, error) {
116116
return doHash(h.baseHasher(), h.span, rootHash)
117117
}
118118

119-
// HashPadded is equivalent to Hash for the SIMD hasher since Hash already
120-
// zero-fills the buffer. Exposed so the Hasher interface contract is satisfied.
121-
func (h *simdHasher) HashPadded(b []byte) ([]byte, error) {
122-
return h.Hash(b)
123-
}
124-
125119
// Reset prepares the Hasher for reuse. The internal data buffer is not zeroed;
126120
// any stale bytes past h.size are overwritten on the next Write and zero-filled
127121
// on demand by Hash, so post-Reset inspection of the buffer may still see
@@ -130,90 +124,3 @@ func (h *simdHasher) Reset() {
130124
h.size = 0
131125
copy(h.span, zerospan)
132126
}
133-
134-
// Proof returns the inclusion proof of the i-th data segment.
135-
func (h *simdHasher) Proof(i int) Proof {
136-
// keep the original (segment-level) index around: callers identify the
137-
// segment by its 0..127 position; we divide down to a leaf-node (section)
138-
// index below for tree navigation.
139-
index := i
140-
if i < 0 || i > 127 {
141-
panic("segment index can only lie between 0-127")
142-
}
143-
// two segments share one leaf section (32B + 32B hashed together),
144-
// so the leaf-node index is i/2.
145-
i = i / 2
146-
n := h.bmt.leaves[i]
147-
isLeft := n.isLeft
148-
// walk from the leaf's parent to the root, collecting the sister hash
149-
// at each level along the way.
150-
var sisters [][]byte
151-
for n = n.parent; n != nil; n = n.parent {
152-
sisters = append(sisters, n.getSister(isLeft))
153-
isLeft = n.isLeft
154-
}
155-
156-
// copy out the two segments that live in this leaf section. One is the
157-
// segment being proven, the other is its immediate sister (the very first
158-
// entry in the proof path, below the leaf level).
159-
secsize := 2 * h.segmentSize
160-
offset := i * secsize
161-
section := make([]byte, secsize)
162-
copy(section, h.bmt.buffer[offset:offset+secsize])
163-
segment, firstSegmentSister := section[:h.segmentSize], section[h.segmentSize:]
164-
// if the original index is odd, the proven segment sits on the right,
165-
// so swap which half is the segment and which is the sister.
166-
if index%2 != 0 {
167-
segment, firstSegmentSister = firstSegmentSister, segment
168-
}
169-
// prepend the in-section sister so the proof list reads leaf-up: the
170-
// first hop combines segment with firstSegmentSister, then walks upward.
171-
sisters = append([][]byte{firstSegmentSister}, sisters...)
172-
return Proof{segment, sisters, h.span, index}
173-
}
174-
175-
// Verify reconstructs the BMT root from a proof for the i-th segment.
176-
func (h *simdHasher) Verify(i int, proof Proof) (root []byte, err error) {
177-
// rebuild the leaf section (two 32B segments concatenated) in the same
178-
// order they were originally hashed. Even index → proven segment on the
179-
// left, odd index → proven segment on the right.
180-
var section []byte
181-
if i%2 == 0 {
182-
section = append(append(section, proof.ProveSegment...), proof.ProofSegments[0]...)
183-
} else {
184-
section = append(append(section, proof.ProofSegments[0]...), proof.ProveSegment...)
185-
}
186-
// derive the leaf-node index from the segment index, mirroring Proof.
187-
i = i / 2
188-
n := h.bmt.leaves[i]
189-
hasher := h.baseHasher()
190-
isLeft := n.isLeft
191-
// start by hashing the reconstructed section: this is the leaf-level
192-
// hash that the proof path will lift up to the root.
193-
root, err = doHash(hasher, section)
194-
if err != nil {
195-
return nil, err
196-
}
197-
n = n.parent
198-
199-
// climb level by level, pairing the running hash with the sister from
200-
// the proof. Orientation alternates based on whether we arrived at this
201-
// node from its left or right child.
202-
for _, sister := range proof.ProofSegments[1:] {
203-
if isLeft {
204-
root, err = doHash(hasher, root, sister)
205-
} else {
206-
root, err = doHash(hasher, sister, root)
207-
}
208-
if err != nil {
209-
return nil, err
210-
}
211-
// advance to the next level; record which side we came from so the
212-
// next hash pairs the operands in the correct order.
213-
isLeft = n.isLeft
214-
n = n.parent
215-
}
216-
// finally mix in the span to produce the chunk address, matching what
217-
// Hash does at the top of the tree.
218-
return doHash(hasher, proof.Span, root)
219-
}

pkg/bmt/interface.go

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ const (
1616
//
1717
// Any implementation should make it possible to generate a BMT hash using the hash.Hash interface only.
1818
// However, the limitation will be that the Span of the BMT hash always must be limited to the amount of bytes actually written.
19+
//
20+
// Inclusion-proof generation (Proof / Verify / zero-padded Hash) is NOT part of
21+
// this interface — it lives on the Prover type, which is always goroutine-backed
22+
// regardless of SIMDOptIn. See pkg/bmt/proof.go.
1923
type Hasher interface {
2024
hash.Hash
2125

@@ -30,15 +34,4 @@ type Hasher interface {
3034

3135
// Capacity returns the maximum amount of bytes that will be processed by the implementation.
3236
Capacity() int
33-
34-
// HashPadded calculates the BMT hash after zero-padding any unwritten sections so
35-
// the tree is fully populated. Required for inclusion-proof generation.
36-
HashPadded([]byte) ([]byte, error)
37-
38-
// Proof returns an inclusion proof for the i-th data segment of the last hashed chunk.
39-
Proof(i int) Proof
40-
41-
// Verify reconstructs the BMT root from a proof for the i-th segment. Returns the
42-
// computed root hash (caller compares to the expected chunk address).
43-
Verify(i int, proof Proof) ([]byte, error)
4437
}

pkg/bmt/pool_goroutine.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,45 @@ func (p *goroutinePool) Put(h Hasher) {
106106
p.c <- gh.bmt
107107
}
108108

109+
// goroutineProverPool is a pool of goroutine-backed Provers. Trees are reused
110+
// via a channel; the hasher shell is reallocated per GetProver (cheap relative
111+
// to a tree).
112+
type goroutineProverPool struct {
113+
c chan *goroutineTree
114+
*goroutineConf
115+
}
116+
117+
func newGoroutineProverPool(c *Conf) *goroutineProverPool {
118+
gc := newGoroutineConf(c.Prefix, c.SegmentCount, c.Capacity)
119+
p := &goroutineProverPool{
120+
goroutineConf: gc,
121+
c: make(chan *goroutineTree, gc.capacity),
122+
}
123+
for i := 0; i < gc.capacity; i++ {
124+
p.c <- newGoroutineTree(gc.maxSize, gc.depth, gc.hasherFunc)
125+
}
126+
return p
127+
}
128+
129+
// GetProver returns a goroutine-backed Prover, reusing a tree from the pool.
130+
func (p *goroutineProverPool) GetProver() *Prover {
131+
t := <-p.c
132+
return &Prover{
133+
goroutineHasher: &goroutineHasher{
134+
goroutineConf: p.goroutineConf,
135+
result: make(chan []byte),
136+
errc: make(chan error, 1),
137+
span: make([]byte, SpanSize),
138+
bmt: t,
139+
},
140+
}
141+
}
142+
143+
// PutProver returns a Prover's tree to the pool for reuse.
144+
func (p *goroutineProverPool) PutProver(pr *Prover) {
145+
p.c <- pr.goroutineHasher.bmt
146+
}
147+
109148
// goroutineTree is the tree structure used by the goroutine hasher.
110149
type goroutineTree struct {
111150
leaves []*goroutineNode

pkg/bmt/pool_simd.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -169,15 +169,3 @@ func newSIMDTree(maxsize, depth int, hashfunc func() hash.Hash, prefix []byte) *
169169
}
170170
}
171171

172-
// getSister returns a copy of the sibling section on the opposite side.
173-
func (n *simdNode) getSister(isLeft bool) []byte {
174-
var src []byte
175-
if isLeft {
176-
src = n.right
177-
} else {
178-
src = n.left
179-
}
180-
buf := make([]byte, len(src))
181-
copy(buf, src)
182-
return buf
183-
}

pkg/bmt/proof.go

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,68 @@
44

55
package bmt
66

7-
// Prover wraps a BMT Hasher to add Merkle-proof convenience methods.
7+
// Prover adds Merkle-proof generation and verification on top of a BMT hasher.
88
//
99
// Proof generation requires the tree to be fully populated, so Prover.Hash
10-
// zero-pads any unwritten sections before hashing (unlike the bare Hash method
11-
// on the underlying hasher). Proof and Verify are promoted from the embedded
12-
// Hasher.
10+
// zero-pads any unwritten sections before hashing. Prover is always backed by
11+
// the goroutine BMT implementation, independent of the SIMD opt-in flag:
12+
// proofs are produced on a rare, redistribution-only code path where the
13+
// well-tested goroutine implementation is preferred over the SIMD speedup.
14+
//
15+
// The embedded *goroutineHasher is package-private, so callers outside
16+
// pkg/bmt cannot bypass Hash/Sum to skip padding.
1317
type Prover struct {
14-
Hasher
18+
*goroutineHasher
1519
}
1620

17-
// Proof represents a Merkle proof of segment
21+
// Proof represents a Merkle proof of segment.
1822
type Proof struct {
1923
ProveSegment []byte
2024
ProofSegments [][]byte
2125
Span []byte
2226
Index int
2327
}
2428

25-
// Hash computes the BMT root with zero-padding, so the proof tree is fully populated.
26-
// It shadows the embedded Hasher.Hash to preserve the existing Prover API.
27-
func (p Prover) Hash(b []byte) ([]byte, error) {
28-
return p.HashPadded(b)
29+
// Hash zero-pads any unwritten sections (so every leaf section in the BMT is
30+
// populated and Proof paths are reconstructible), then computes the BMT root.
31+
// Shadows the promoted goroutineHasher.Hash.
32+
func (p *Prover) Hash(b []byte) ([]byte, error) {
33+
for i := p.size; i < p.maxSize; i += len(zerosection) {
34+
if _, err := p.Write(zerosection); err != nil {
35+
return nil, err
36+
}
37+
}
38+
return p.goroutineHasher.Hash(b)
39+
}
40+
41+
// Sum shadows the promoted goroutineHasher.Sum so Prover used as a hash.Hash
42+
// also pads. Without this override Sum would call the unpadded Hash and
43+
// produce a different digest than Prover.Hash.
44+
func (p *Prover) Sum(b []byte) []byte {
45+
s, _ := p.Hash(b)
46+
return s
47+
}
48+
49+
// NewProver returns a Prover backed by a freshly allocated goroutine-based
50+
// BMT hasher, independent of the SIMD opt-in flag.
51+
func NewProver() *Prover {
52+
return &Prover{goroutineHasher: newGoroutineHasher()}
53+
}
54+
55+
// NewPrefixProver is NewProver with an optional keccak prefix prepended to every
56+
// BMT node hash. Also goroutine-backed regardless of SIMDOptIn.
57+
func NewPrefixProver(prefix []byte) *Prover {
58+
return &Prover{goroutineHasher: newGoroutinePrefixHasher(prefix)}
59+
}
60+
61+
// ProverPool is a pool of goroutine-backed Provers. Ignores SIMDOptIn by design.
62+
type ProverPool interface {
63+
GetProver() *Prover
64+
PutProver(*Prover)
65+
}
66+
67+
// NewProverPool returns a pool of goroutine-backed Provers, independent of
68+
// SIMDOptIn. See Prover for the rationale.
69+
func NewProverPool(c *Conf) ProverPool {
70+
return newGoroutineProverPool(c)
2971
}

0 commit comments

Comments
 (0)