bmt: Binary Merkle Tree Hash (#14334)
bmt is a new package that provides hashers for binary merkle tree hashes on size-limited chunks. the main motivation is that using BMT hash as the chunk hash of the swarm hash offers logsize inclusion proofs for arbitrary files on a 32-byte resolution completely viable to use in challenges on the blockchain.
This commit is contained in:
		
							parent
							
								
									32d8d42274
								
							
						
					
					
						commit
						2bacf36d80
					
				
							
								
								
									
										562
									
								
								bmt/bmt.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										562
									
								
								bmt/bmt.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,562 @@ | ||||
| // Copyright 2017 The go-ethereum Authors
 | ||||
| // This file is part of the go-ethereum library.
 | ||||
| //
 | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU Lesser General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| //
 | ||||
| // The go-ethereum library is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | ||||
| // GNU Lesser General Public License for more details.
 | ||||
| //
 | ||||
| // You should have received a copy of the GNU Lesser General Public License
 | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| // Package bmt provides a binary merkle tree implementation
 | ||||
| package bmt | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"hash" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| ) | ||||
| 
 | ||||
| /* | ||||
| Binary Merkle Tree Hash is a hash function over arbitrary datachunks of limited size | ||||
| It is defined as the root hash of the binary merkle tree built over fixed size segments | ||||
| of the underlying chunk using any base hash function (e.g keccak 256 SHA3) | ||||
| 
 | ||||
| It is used as the chunk hash function in swarm which in turn is the basis for the | ||||
| 128 branching swarm hash http://swarm-guide.readthedocs.io/en/latest/architecture.html#swarm-hash
 | ||||
| 
 | ||||
| The BMT is optimal for providing compact inclusion proofs, i.e. prove that a | ||||
| segment is a substring of a chunk starting at a particular offset | ||||
| The size of the underlying segments is fixed at 32 bytes (called the resolution | ||||
| of the BMT hash), the EVM word size to optimize for on-chain BMT verification | ||||
| as well as the hash size optimal for inclusion proofs in the merkle tree of the swarm hash. | ||||
| 
 | ||||
| Two implementations are provided: | ||||
| 
 | ||||
| * RefHasher is optimized for code simplicity and meant as a reference implementation | ||||
| * Hasher is optimized for speed taking advantage of concurrency with minimalistic | ||||
|   control structure to coordinate the concurrent routines | ||||
|   It implements the ChunkHash interface as well as the go standard hash.Hash interface | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| const ( | ||||
| 	// DefaultSegmentCount is the maximum number of segments of the underlying chunk
 | ||||
| 	DefaultSegmentCount = 128 // Should be equal to storage.DefaultBranches
 | ||||
| 	// DefaultPoolSize is the maximum number of bmt trees used by the hashers, i.e,
 | ||||
| 	// the maximum number of concurrent BMT hashing operations performed by the same hasher
 | ||||
| 	DefaultPoolSize = 8 | ||||
| ) | ||||
| 
 | ||||
| // BaseHasher is a hash.Hash constructor function used for the base hash of the  BMT.
 | ||||
| type BaseHasher func() hash.Hash | ||||
| 
 | ||||
| // Hasher a reusable hasher for fixed maximum size chunks representing a BMT
 | ||||
| // implements the hash.Hash interface
 | ||||
| // reuse pool of Tree-s for amortised memory allocation and resource control
 | ||||
| // supports order-agnostic concurrent segment writes
 | ||||
| // as well as sequential read and write
 | ||||
| // can not be called concurrently on more than one chunk
 | ||||
| // can be further appended after Sum
 | ||||
| // Reset gives back the Tree to the pool and guaranteed to leave
 | ||||
| // the tree and itself in a state reusable for hashing a new chunk
 | ||||
| type Hasher struct { | ||||
| 	pool        *TreePool   // BMT resource pool
 | ||||
| 	bmt         *Tree       // prebuilt BMT resource for flowcontrol and proofs
 | ||||
| 	blocksize   int         // segment size (size of hash) also for hash.Hash
 | ||||
| 	count       int         // segment count
 | ||||
| 	size        int         // for hash.Hash same as hashsize
 | ||||
| 	cur         int         // cursor position for righmost currently open chunk
 | ||||
| 	segment     []byte      // the rightmost open segment (not complete)
 | ||||
| 	depth       int         // index of last level
 | ||||
| 	result      chan []byte // result channel
 | ||||
| 	hash        []byte      // to record the result
 | ||||
| 	max         int32       // max segments for SegmentWriter interface
 | ||||
| 	blockLength []byte      // The block length that needes to be added in Sum
 | ||||
| } | ||||
| 
 | ||||
| // New creates a reusable Hasher
 | ||||
| // implements the hash.Hash interface
 | ||||
| // pulls a new Tree from a resource pool for hashing each chunk
 | ||||
| func New(p *TreePool) *Hasher { | ||||
| 	return &Hasher{ | ||||
| 		pool:      p, | ||||
| 		depth:     depth(p.SegmentCount), | ||||
| 		size:      p.SegmentSize, | ||||
| 		blocksize: p.SegmentSize, | ||||
| 		count:     p.SegmentCount, | ||||
| 		result:    make(chan []byte), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Node is a reuseable segment hasher representing a node in a BMT
 | ||||
| // it allows for continued writes after a Sum
 | ||||
| // and is left in completely reusable state after Reset
 | ||||
| type Node struct { | ||||
| 	level, index int   // position of node for information/logging only
 | ||||
| 	initial      bool  // first and last node
 | ||||
| 	root         bool  // whether the node is root to a smaller BMT
 | ||||
| 	isLeft       bool  // whether it is left side of the parent double segment
 | ||||
| 	unbalanced   bool  // indicates if a node has only the left segment
 | ||||
| 	parent       *Node // BMT connections
 | ||||
| 	state        int32 // atomic increment impl concurrent boolean toggle
 | ||||
| 	left, right  []byte | ||||
| } | ||||
| 
 | ||||
| // NewNode constructor for segment hasher nodes in the BMT
 | ||||
| func NewNode(level, index int, parent *Node) *Node { | ||||
| 	return &Node{ | ||||
| 		parent:  parent, | ||||
| 		level:   level, | ||||
| 		index:   index, | ||||
| 		initial: index == 0, | ||||
| 		isLeft:  index%2 == 0, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // TreePool provides a pool of Trees used as resources by Hasher
 | ||||
| // a Tree popped from the pool is guaranteed to have clean state
 | ||||
| // for hashing a new chunk
 | ||||
| // Hasher Reset releases the Tree to the pool
 | ||||
| type TreePool struct { | ||||
| 	lock         sync.Mutex | ||||
| 	c            chan *Tree | ||||
| 	hasher       BaseHasher | ||||
| 	SegmentSize  int | ||||
| 	SegmentCount int | ||||
| 	Capacity     int | ||||
| 	count        int | ||||
| } | ||||
| 
 | ||||
| // NewTreePool creates a Tree pool with hasher, segment size, segment count and capacity
 | ||||
| // on GetTree it reuses free Trees or creates a new one if size is not reached
 | ||||
| func NewTreePool(hasher BaseHasher, segmentCount, capacity int) *TreePool { | ||||
| 	return &TreePool{ | ||||
| 		c:            make(chan *Tree, capacity), | ||||
| 		hasher:       hasher, | ||||
| 		SegmentSize:  hasher().Size(), | ||||
| 		SegmentCount: segmentCount, | ||||
| 		Capacity:     capacity, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Drain drains the pool uptil it has no more than n resources
 | ||||
| func (self *TreePool) Drain(n int) { | ||||
| 	self.lock.Lock() | ||||
| 	defer self.lock.Unlock() | ||||
| 	for len(self.c) > n { | ||||
| 		<-self.c | ||||
| 		self.count-- | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Reserve is blocking until it returns an available Tree
 | ||||
| // it reuses free Trees or creates a new one if size is not reached
 | ||||
| func (self *TreePool) Reserve() *Tree { | ||||
| 	self.lock.Lock() | ||||
| 	defer self.lock.Unlock() | ||||
| 	var t *Tree | ||||
| 	if self.count == self.Capacity { | ||||
| 		return <-self.c | ||||
| 	} | ||||
| 	select { | ||||
| 	case t = <-self.c: | ||||
| 	default: | ||||
| 		t = NewTree(self.hasher, self.SegmentSize, self.SegmentCount) | ||||
| 		self.count++ | ||||
| 	} | ||||
| 	return t | ||||
| } | ||||
| 
 | ||||
| // Release gives back a Tree to the pool.
 | ||||
| // This Tree is guaranteed to be in reusable state
 | ||||
| // does not need locking
 | ||||
| func (self *TreePool) Release(t *Tree) { | ||||
| 	self.c <- t // can never fail but...
 | ||||
| } | ||||
| 
 | ||||
| // Tree is a reusable control structure representing a BMT
 | ||||
| // organised in a binary tree
 | ||||
| // Hasher uses a TreePool to pick one for each chunk hash
 | ||||
| // the Tree is 'locked' while not in the pool
 | ||||
| type Tree struct { | ||||
| 	leaves []*Node | ||||
| } | ||||
| 
 | ||||
| // Draw draws the BMT (badly)
 | ||||
| func (self *Tree) Draw(hash []byte, d int) string { | ||||
| 	var left, right []string | ||||
| 	var anc []*Node | ||||
| 	for i, n := range self.leaves { | ||||
| 		left = append(left, fmt.Sprintf("%v", hashstr(n.left))) | ||||
| 		if i%2 == 0 { | ||||
| 			anc = append(anc, n.parent) | ||||
| 		} | ||||
| 		right = append(right, fmt.Sprintf("%v", hashstr(n.right))) | ||||
| 	} | ||||
| 	anc = self.leaves | ||||
| 	var hashes [][]string | ||||
| 	for l := 0; len(anc) > 0; l++ { | ||||
| 		var nodes []*Node | ||||
| 		hash := []string{""} | ||||
| 		for i, n := range anc { | ||||
| 			hash = append(hash, fmt.Sprintf("%v|%v", hashstr(n.left), hashstr(n.right))) | ||||
| 			if i%2 == 0 && n.parent != nil { | ||||
| 				nodes = append(nodes, n.parent) | ||||
| 			} | ||||
| 		} | ||||
| 		hash = append(hash, "") | ||||
| 		hashes = append(hashes, hash) | ||||
| 		anc = nodes | ||||
| 	} | ||||
| 	hashes = append(hashes, []string{"", fmt.Sprintf("%v", hashstr(hash)), ""}) | ||||
| 	total := 60 | ||||
| 	del := "                             " | ||||
| 	var rows []string | ||||
| 	for i := len(hashes) - 1; i >= 0; i-- { | ||||
| 		var textlen int | ||||
| 		hash := hashes[i] | ||||
| 		for _, s := range hash { | ||||
| 			textlen += len(s) | ||||
| 		} | ||||
| 		if total < textlen { | ||||
| 			total = textlen + len(hash) | ||||
| 		} | ||||
| 		delsize := (total - textlen) / (len(hash) - 1) | ||||
| 		if delsize > len(del) { | ||||
| 			delsize = len(del) | ||||
| 		} | ||||
| 		row := fmt.Sprintf("%v: %v", len(hashes)-i-1, strings.Join(hash, del[:delsize])) | ||||
| 		rows = append(rows, row) | ||||
| 
 | ||||
| 	} | ||||
| 	rows = append(rows, strings.Join(left, "  ")) | ||||
| 	rows = append(rows, strings.Join(right, "  ")) | ||||
| 	return strings.Join(rows, "\n") + "\n" | ||||
| } | ||||
| 
 | ||||
| // NewTree initialises the Tree by building up the nodes of a BMT
 | ||||
| // segment size is stipulated to be the size of the hash
 | ||||
| // segmentCount needs to be positive integer and does not need to be
 | ||||
| // a power of two and can even be an odd number
 | ||||
| // segmentSize * segmentCount determines the maximum chunk size
 | ||||
| // hashed using the tree
 | ||||
| func NewTree(hasher BaseHasher, segmentSize, segmentCount int) *Tree { | ||||
| 	n := NewNode(0, 0, nil) | ||||
| 	n.root = true | ||||
| 	prevlevel := []*Node{n} | ||||
| 	// iterate over levels and creates 2^level nodes
 | ||||
| 	level := 1 | ||||
| 	count := 2 | ||||
| 	for d := 1; d <= depth(segmentCount); d++ { | ||||
| 		nodes := make([]*Node, count) | ||||
| 		for i := 0; i < len(nodes); i++ { | ||||
| 			var parent *Node | ||||
| 			parent = prevlevel[i/2] | ||||
| 			t := NewNode(level, i, parent) | ||||
| 			nodes[i] = t | ||||
| 		} | ||||
| 		prevlevel = nodes | ||||
| 		level++ | ||||
| 		count *= 2 | ||||
| 	} | ||||
| 	// the datanode level is the nodes on the last level where
 | ||||
| 	return &Tree{ | ||||
| 		leaves: prevlevel, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // methods needed by hash.Hash
 | ||||
| 
 | ||||
| // Size returns the size
 | ||||
| func (self *Hasher) Size() int { | ||||
| 	return self.size | ||||
| } | ||||
| 
 | ||||
| // BlockSize returns the block size
 | ||||
| func (self *Hasher) BlockSize() int { | ||||
| 	return self.blocksize | ||||
| } | ||||
| 
 | ||||
| // Sum returns the hash of the buffer
 | ||||
| // hash.Hash interface Sum method appends the byte slice to the underlying
 | ||||
| // data before it calculates and returns the hash of the chunk
 | ||||
| func (self *Hasher) Sum(b []byte) (r []byte) { | ||||
| 	t := self.bmt | ||||
| 	i := self.cur | ||||
| 	n := t.leaves[i] | ||||
| 	j := i | ||||
| 	// must run strictly before all nodes calculate
 | ||||
| 	// datanodes are guaranteed to have a parent
 | ||||
| 	if len(self.segment) > self.size && i > 0 && n.parent != nil { | ||||
| 		n = n.parent | ||||
| 	} else { | ||||
| 		i *= 2 | ||||
| 	} | ||||
| 	d := self.finalise(n, i) | ||||
| 	self.writeSegment(j, self.segment, d) | ||||
| 	c := <-self.result | ||||
| 	self.releaseTree() | ||||
| 
 | ||||
| 	// sha3(length + BMT(pure_chunk))
 | ||||
| 	if self.blockLength == nil { | ||||
| 		return c | ||||
| 	} | ||||
| 	res := self.pool.hasher() | ||||
| 	res.Reset() | ||||
| 	res.Write(self.blockLength) | ||||
| 	res.Write(c) | ||||
| 	return res.Sum(nil) | ||||
| } | ||||
| 
 | ||||
| // Hasher implements the SwarmHash interface
 | ||||
| 
 | ||||
| // Hash waits for the hasher result and returns it
 | ||||
| // caller must call this on a BMT Hasher being written to
 | ||||
| func (self *Hasher) Hash() []byte { | ||||
| 	return <-self.result | ||||
| } | ||||
| 
 | ||||
| // Hasher implements the io.Writer interface
 | ||||
| 
 | ||||
| // Write fills the buffer to hash
 | ||||
| // with every full segment complete launches a hasher go routine
 | ||||
| // that shoots up the BMT
 | ||||
| func (self *Hasher) Write(b []byte) (int, error) { | ||||
| 	l := len(b) | ||||
| 	if l <= 0 { | ||||
| 		return 0, nil | ||||
| 	} | ||||
| 	s := self.segment | ||||
| 	i := self.cur | ||||
| 	count := (self.count + 1) / 2 | ||||
| 	need := self.count*self.size - self.cur*2*self.size | ||||
| 	size := self.size | ||||
| 	if need > size { | ||||
| 		size *= 2 | ||||
| 	} | ||||
| 	if l < need { | ||||
| 		need = l | ||||
| 	} | ||||
| 	// calculate missing bit to complete current open segment
 | ||||
| 	rest := size - len(s) | ||||
| 	if need < rest { | ||||
| 		rest = need | ||||
| 	} | ||||
| 	s = append(s, b[:rest]...) | ||||
| 	need -= rest | ||||
| 	// read full segments and the last possibly partial segment
 | ||||
| 	for need > 0 && i < count-1 { | ||||
| 		// push all finished chunks we read
 | ||||
| 		self.writeSegment(i, s, self.depth) | ||||
| 		need -= size | ||||
| 		if need < 0 { | ||||
| 			size += need | ||||
| 		} | ||||
| 		s = b[rest : rest+size] | ||||
| 		rest += size | ||||
| 		i++ | ||||
| 	} | ||||
| 	self.segment = s | ||||
| 	self.cur = i | ||||
| 	// otherwise, we can assume len(s) == 0, so all buffer is read and chunk is not yet full
 | ||||
| 	return l, nil | ||||
| } | ||||
| 
 | ||||
| // Hasher implements the io.ReaderFrom interface
 | ||||
| 
 | ||||
| // ReadFrom reads from io.Reader and appends to the data to hash using Write
 | ||||
| // it reads so that chunk to hash is maximum length or reader reaches EOF
 | ||||
| // caller must Reset the hasher prior to call
 | ||||
| func (self *Hasher) ReadFrom(r io.Reader) (m int64, err error) { | ||||
| 	bufsize := self.size*self.count - self.size*self.cur - len(self.segment) | ||||
| 	buf := make([]byte, bufsize) | ||||
| 	var read int | ||||
| 	for { | ||||
| 		var n int | ||||
| 		n, err = r.Read(buf) | ||||
| 		read += n | ||||
| 		if err == io.EOF || read == len(buf) { | ||||
| 			hash := self.Sum(buf[:n]) | ||||
| 			if read == len(buf) { | ||||
| 				err = NewEOC(hash) | ||||
| 			} | ||||
| 			break | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			break | ||||
| 		} | ||||
| 		n, err = self.Write(buf[:n]) | ||||
| 		if err != nil { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	return int64(read), err | ||||
| } | ||||
| 
 | ||||
| // Reset needs to be called before writing to the hasher
 | ||||
| func (self *Hasher) Reset() { | ||||
| 	self.getTree() | ||||
| 	self.blockLength = nil | ||||
| } | ||||
| 
 | ||||
| // Hasher implements the SwarmHash interface
 | ||||
| 
 | ||||
| // ResetWithLength needs to be called before writing to the hasher
 | ||||
| // the argument is supposed to be the byte slice binary representation of
 | ||||
| // the legth of the data subsumed under the hash
 | ||||
| func (self *Hasher) ResetWithLength(l []byte) { | ||||
| 	self.Reset() | ||||
| 	self.blockLength = l | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // Release gives back the Tree to the pool whereby it unlocks
 | ||||
| // it resets tree, segment and index
 | ||||
| func (self *Hasher) releaseTree() { | ||||
| 	if self.bmt != nil { | ||||
| 		n := self.bmt.leaves[self.cur] | ||||
| 		for ; n != nil; n = n.parent { | ||||
| 			n.unbalanced = false | ||||
| 			if n.parent != nil { | ||||
| 				n.root = false | ||||
| 			} | ||||
| 		} | ||||
| 		self.pool.Release(self.bmt) | ||||
| 		self.bmt = nil | ||||
| 
 | ||||
| 	} | ||||
| 	self.cur = 0 | ||||
| 	self.segment = nil | ||||
| } | ||||
| 
 | ||||
| func (self *Hasher) writeSegment(i int, s []byte, d int) { | ||||
| 	h := self.pool.hasher() | ||||
| 	n := self.bmt.leaves[i] | ||||
| 
 | ||||
| 	if len(s) > self.size && n.parent != nil { | ||||
| 		go func() { | ||||
| 			h.Reset() | ||||
| 			h.Write(s) | ||||
| 			s = h.Sum(nil) | ||||
| 
 | ||||
| 			if n.root { | ||||
| 				self.result <- s | ||||
| 				return | ||||
| 			} | ||||
| 			self.run(n.parent, h, d, n.index, s) | ||||
| 		}() | ||||
| 		return | ||||
| 	} | ||||
| 	go self.run(n, h, d, i*2, s) | ||||
| } | ||||
| 
 | ||||
| func (self *Hasher) run(n *Node, h hash.Hash, d int, i int, s []byte) { | ||||
| 	isLeft := i%2 == 0 | ||||
| 	for { | ||||
| 		if isLeft { | ||||
| 			n.left = s | ||||
| 		} else { | ||||
| 			n.right = s | ||||
| 		} | ||||
| 		if !n.unbalanced && n.toggle() { | ||||
| 			return | ||||
| 		} | ||||
| 		if !n.unbalanced || !isLeft || i == 0 && d == 0 { | ||||
| 			h.Reset() | ||||
| 			h.Write(n.left) | ||||
| 			h.Write(n.right) | ||||
| 			s = h.Sum(nil) | ||||
| 
 | ||||
| 		} else { | ||||
| 			s = append(n.left, n.right...) | ||||
| 		} | ||||
| 
 | ||||
| 		self.hash = s | ||||
| 		if n.root { | ||||
| 			self.result <- s | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		isLeft = n.isLeft | ||||
| 		n = n.parent | ||||
| 		i++ | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // getTree obtains a BMT resource by reserving one from the pool
 | ||||
| func (self *Hasher) getTree() *Tree { | ||||
| 	if self.bmt != nil { | ||||
| 		return self.bmt | ||||
| 	} | ||||
| 	t := self.pool.Reserve() | ||||
| 	self.bmt = t | ||||
| 	return t | ||||
| } | ||||
| 
 | ||||
| // atomic bool toggle implementing a concurrent reusable 2-state object
 | ||||
| // atomic addint with %2 implements atomic bool toggle
 | ||||
| // it returns true if the toggler just put it in the active/waiting state
 | ||||
| func (self *Node) toggle() bool { | ||||
| 	return atomic.AddInt32(&self.state, 1)%2 == 1 | ||||
| } | ||||
| 
 | ||||
| func hashstr(b []byte) string { | ||||
| 	end := len(b) | ||||
| 	if end > 4 { | ||||
| 		end = 4 | ||||
| 	} | ||||
| 	return fmt.Sprintf("%x", b[:end]) | ||||
| } | ||||
| 
 | ||||
| func depth(n int) (d int) { | ||||
| 	for l := (n - 1) / 2; l > 0; l /= 2 { | ||||
| 		d++ | ||||
| 	} | ||||
| 	return d | ||||
| } | ||||
| 
 | ||||
| // finalise is following the zigzags on the tree belonging
 | ||||
| // to the final datasegment
 | ||||
| func (self *Hasher) finalise(n *Node, i int) (d int) { | ||||
| 	isLeft := i%2 == 0 | ||||
| 	for { | ||||
| 		// when the final segment's path is going via left segments
 | ||||
| 		// the incoming data is pushed to the parent upon pulling the left
 | ||||
| 		// we do not need toogle the state since this condition is
 | ||||
| 		// detectable
 | ||||
| 		n.unbalanced = isLeft | ||||
| 		n.right = nil | ||||
| 		if n.initial { | ||||
| 			n.root = true | ||||
| 			return d | ||||
| 		} | ||||
| 		isLeft = n.isLeft | ||||
| 		n = n.parent | ||||
| 		d++ | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // EOC (end of chunk) implements the error interface
 | ||||
| type EOC struct { | ||||
| 	Hash []byte // read the hash of the chunk off the error
 | ||||
| } | ||||
| 
 | ||||
| // Error returns the error string
 | ||||
| func (self *EOC) Error() string { | ||||
| 	return fmt.Sprintf("hasher limit reached, chunk hash: %x", self.Hash) | ||||
| } | ||||
| 
 | ||||
| // NewEOC creates new end of chunk error with the hash
 | ||||
| func NewEOC(hash []byte) *EOC { | ||||
| 	return &EOC{hash} | ||||
| } | ||||
							
								
								
									
										85
									
								
								bmt/bmt_r.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								bmt/bmt_r.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | ||||
| // Copyright 2017 The go-ethereum Authors
 | ||||
| // This file is part of the go-ethereum library.
 | ||||
| //
 | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU Lesser General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| //
 | ||||
| // The go-ethereum library is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | ||||
| // GNU Lesser General Public License for more details.
 | ||||
| //
 | ||||
| // You should have received a copy of the GNU Lesser General Public License
 | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| // simple nonconcurrent reference implementation for hashsize segment based
 | ||||
| // Binary Merkle tree hash on arbitrary but fixed maximum chunksize
 | ||||
| //
 | ||||
| // This implementation does not take advantage of any paralellisms and uses
 | ||||
| // far more memory than necessary, but it is easy to see that it is correct.
 | ||||
| // It can be used for generating test cases for optimized implementations.
 | ||||
| // see testBMTHasherCorrectness function in bmt_test.go
 | ||||
| package bmt | ||||
| 
 | ||||
| import ( | ||||
| 	"hash" | ||||
| ) | ||||
| 
 | ||||
| // RefHasher is the non-optimized easy to read reference implementation of BMT
 | ||||
| type RefHasher struct { | ||||
| 	span    int | ||||
| 	section int | ||||
| 	cap     int | ||||
| 	h       hash.Hash | ||||
| } | ||||
| 
 | ||||
| // NewRefHasher returns a new RefHasher
 | ||||
| func NewRefHasher(hasher BaseHasher, count int) *RefHasher { | ||||
| 	h := hasher() | ||||
| 	hashsize := h.Size() | ||||
| 	maxsize := hashsize * count | ||||
| 	c := 2 | ||||
| 	for ; c < count; c *= 2 { | ||||
| 	} | ||||
| 	if c > 2 { | ||||
| 		c /= 2 | ||||
| 	} | ||||
| 	return &RefHasher{ | ||||
| 		section: 2 * hashsize, | ||||
| 		span:    c * hashsize, | ||||
| 		cap:     maxsize, | ||||
| 		h:       h, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Hash returns the BMT hash of the byte slice
 | ||||
| // implements the SwarmHash interface
 | ||||
| func (rh *RefHasher) Hash(d []byte) []byte { | ||||
| 	if len(d) > rh.cap { | ||||
| 		d = d[:rh.cap] | ||||
| 	} | ||||
| 
 | ||||
| 	return rh.hash(d, rh.span) | ||||
| } | ||||
| 
 | ||||
| func (rh *RefHasher) hash(d []byte, s int) []byte { | ||||
| 	l := len(d) | ||||
| 	left := d | ||||
| 	var right []byte | ||||
| 	if l > rh.section { | ||||
| 		for ; s >= l; s /= 2 { | ||||
| 		} | ||||
| 		left = rh.hash(d[:s], s) | ||||
| 		right = d[s:] | ||||
| 		if l-s > rh.section/2 { | ||||
| 			right = rh.hash(right, s) | ||||
| 		} | ||||
| 	} | ||||
| 	defer rh.h.Reset() | ||||
| 	rh.h.Write(left) | ||||
| 	rh.h.Write(right) | ||||
| 	h := rh.h.Sum(nil) | ||||
| 	return h | ||||
| } | ||||
							
								
								
									
										481
									
								
								bmt/bmt_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										481
									
								
								bmt/bmt_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,481 @@ | ||||
| // Copyright 2017 The go-ethereum Authors
 | ||||
| // This file is part of the go-ethereum library.
 | ||||
| //
 | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU Lesser General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| //
 | ||||
| // The go-ethereum library is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | ||||
| // GNU Lesser General Public License for more details.
 | ||||
| //
 | ||||
| // You should have received a copy of the GNU Lesser General Public License
 | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| package bmt | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	crand "crypto/rand" | ||||
| 	"fmt" | ||||
| 	"hash" | ||||
| 	"io" | ||||
| 	"math/rand" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/ethereum/go-ethereum/crypto/sha3" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	maxproccnt = 8 | ||||
| ) | ||||
| 
 | ||||
| // TestRefHasher tests that the RefHasher computes the expected BMT hash for
 | ||||
| // all data lengths between 0 and 256 bytes
 | ||||
| func TestRefHasher(t *testing.T) { | ||||
| 	hashFunc := sha3.NewKeccak256 | ||||
| 
 | ||||
| 	sha3 := func(data ...[]byte) []byte { | ||||
| 		h := hashFunc() | ||||
| 		for _, v := range data { | ||||
| 			h.Write(v) | ||||
| 		} | ||||
| 		return h.Sum(nil) | ||||
| 	} | ||||
| 
 | ||||
| 	// the test struct is used to specify the expected BMT hash for data
 | ||||
| 	// lengths between "from" and "to"
 | ||||
| 	type test struct { | ||||
| 		from     int64 | ||||
| 		to       int64 | ||||
| 		expected func([]byte) []byte | ||||
| 	} | ||||
| 
 | ||||
| 	var tests []*test | ||||
| 
 | ||||
| 	// all lengths in [0,64] should be:
 | ||||
| 	//
 | ||||
| 	//   sha3(data)
 | ||||
| 	//
 | ||||
| 	tests = append(tests, &test{ | ||||
| 		from: 0, | ||||
| 		to:   64, | ||||
| 		expected: func(data []byte) []byte { | ||||
| 			return sha3(data) | ||||
| 		}, | ||||
| 	}) | ||||
| 
 | ||||
| 	// all lengths in [65,96] should be:
 | ||||
| 	//
 | ||||
| 	//   sha3(
 | ||||
| 	//     sha3(data[:64])
 | ||||
| 	//     data[64:]
 | ||||
| 	//   )
 | ||||
| 	//
 | ||||
| 	tests = append(tests, &test{ | ||||
| 		from: 65, | ||||
| 		to:   96, | ||||
| 		expected: func(data []byte) []byte { | ||||
| 			return sha3(sha3(data[:64]), data[64:]) | ||||
| 		}, | ||||
| 	}) | ||||
| 
 | ||||
| 	// all lengths in [97,128] should be:
 | ||||
| 	//
 | ||||
| 	//   sha3(
 | ||||
| 	//     sha3(data[:64])
 | ||||
| 	//     sha3(data[64:])
 | ||||
| 	//   )
 | ||||
| 	//
 | ||||
| 	tests = append(tests, &test{ | ||||
| 		from: 97, | ||||
| 		to:   128, | ||||
| 		expected: func(data []byte) []byte { | ||||
| 			return sha3(sha3(data[:64]), sha3(data[64:])) | ||||
| 		}, | ||||
| 	}) | ||||
| 
 | ||||
| 	// all lengths in [129,160] should be:
 | ||||
| 	//
 | ||||
| 	//   sha3(
 | ||||
| 	//     sha3(
 | ||||
| 	//       sha3(data[:64])
 | ||||
| 	//       sha3(data[64:128])
 | ||||
| 	//     )
 | ||||
| 	//     data[128:]
 | ||||
| 	//   )
 | ||||
| 	//
 | ||||
| 	tests = append(tests, &test{ | ||||
| 		from: 129, | ||||
| 		to:   160, | ||||
| 		expected: func(data []byte) []byte { | ||||
| 			return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), data[128:]) | ||||
| 		}, | ||||
| 	}) | ||||
| 
 | ||||
| 	// all lengths in [161,192] should be:
 | ||||
| 	//
 | ||||
| 	//   sha3(
 | ||||
| 	//     sha3(
 | ||||
| 	//       sha3(data[:64])
 | ||||
| 	//       sha3(data[64:128])
 | ||||
| 	//     )
 | ||||
| 	//     sha3(data[128:])
 | ||||
| 	//   )
 | ||||
| 	//
 | ||||
| 	tests = append(tests, &test{ | ||||
| 		from: 161, | ||||
| 		to:   192, | ||||
| 		expected: func(data []byte) []byte { | ||||
| 			return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), sha3(data[128:])) | ||||
| 		}, | ||||
| 	}) | ||||
| 
 | ||||
| 	// all lengths in [193,224] should be:
 | ||||
| 	//
 | ||||
| 	//   sha3(
 | ||||
| 	//     sha3(
 | ||||
| 	//       sha3(data[:64])
 | ||||
| 	//       sha3(data[64:128])
 | ||||
| 	//     )
 | ||||
| 	//     sha3(
 | ||||
| 	//       sha3(data[128:192])
 | ||||
| 	//       data[192:]
 | ||||
| 	//     )
 | ||||
| 	//   )
 | ||||
| 	//
 | ||||
| 	tests = append(tests, &test{ | ||||
| 		from: 193, | ||||
| 		to:   224, | ||||
| 		expected: func(data []byte) []byte { | ||||
| 			return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), sha3(sha3(data[128:192]), data[192:])) | ||||
| 		}, | ||||
| 	}) | ||||
| 
 | ||||
| 	// all lengths in [225,256] should be:
 | ||||
| 	//
 | ||||
| 	//   sha3(
 | ||||
| 	//     sha3(
 | ||||
| 	//       sha3(data[:64])
 | ||||
| 	//       sha3(data[64:128])
 | ||||
| 	//     )
 | ||||
| 	//     sha3(
 | ||||
| 	//       sha3(data[128:192])
 | ||||
| 	//       sha3(data[192:])
 | ||||
| 	//     )
 | ||||
| 	//   )
 | ||||
| 	//
 | ||||
| 	tests = append(tests, &test{ | ||||
| 		from: 225, | ||||
| 		to:   256, | ||||
| 		expected: func(data []byte) []byte { | ||||
| 			return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), sha3(sha3(data[128:192]), sha3(data[192:]))) | ||||
| 		}, | ||||
| 	}) | ||||
| 
 | ||||
| 	// run the tests
 | ||||
| 	for _, x := range tests { | ||||
| 		for length := x.from; length <= x.to; length++ { | ||||
| 			t.Run(fmt.Sprintf("%d_bytes", length), func(t *testing.T) { | ||||
| 				data := make([]byte, length) | ||||
| 				if _, err := io.ReadFull(crand.Reader, data); err != nil && err != io.EOF { | ||||
| 					t.Fatal(err) | ||||
| 				} | ||||
| 				expected := x.expected(data) | ||||
| 				actual := NewRefHasher(hashFunc, 128).Hash(data) | ||||
| 				if !bytes.Equal(actual, expected) { | ||||
| 					t.Fatalf("expected %x, got %x", expected, actual) | ||||
| 				} | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func testDataReader(l int) (r io.Reader) { | ||||
| 	return io.LimitReader(crand.Reader, int64(l)) | ||||
| } | ||||
| 
 | ||||
| func TestHasherCorrectness(t *testing.T) { | ||||
| 	err := testHasher(testBaseHasher) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func testHasher(f func(BaseHasher, []byte, int, int) error) error { | ||||
| 	tdata := testDataReader(4128) | ||||
| 	data := make([]byte, 4128) | ||||
| 	tdata.Read(data) | ||||
| 	hasher := sha3.NewKeccak256 | ||||
| 	size := hasher().Size() | ||||
| 	counts := []int{1, 2, 3, 4, 5, 8, 16, 32, 64, 128} | ||||
| 
 | ||||
| 	var err error | ||||
| 	for _, count := range counts { | ||||
| 		max := count * size | ||||
| 		incr := 1 | ||||
| 		for n := 0; n <= max+incr; n += incr { | ||||
| 			err = f(hasher, data, n, count) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func TestHasherReuseWithoutRelease(t *testing.T) { | ||||
| 	testHasherReuse(1, t) | ||||
| } | ||||
| 
 | ||||
| func TestHasherReuseWithRelease(t *testing.T) { | ||||
| 	testHasherReuse(maxproccnt, t) | ||||
| } | ||||
| 
 | ||||
| func testHasherReuse(i int, t *testing.T) { | ||||
| 	hasher := sha3.NewKeccak256 | ||||
| 	pool := NewTreePool(hasher, 128, i) | ||||
| 	defer pool.Drain(0) | ||||
| 	bmt := New(pool) | ||||
| 
 | ||||
| 	for i := 0; i < 500; i++ { | ||||
| 		n := rand.Intn(4096) | ||||
| 		tdata := testDataReader(n) | ||||
| 		data := make([]byte, n) | ||||
| 		tdata.Read(data) | ||||
| 
 | ||||
| 		err := testHasherCorrectness(bmt, hasher, data, n, 128) | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestHasherConcurrency(t *testing.T) { | ||||
| 	hasher := sha3.NewKeccak256 | ||||
| 	pool := NewTreePool(hasher, 128, maxproccnt) | ||||
| 	defer pool.Drain(0) | ||||
| 	wg := sync.WaitGroup{} | ||||
| 	cycles := 100 | ||||
| 	wg.Add(maxproccnt * cycles) | ||||
| 	errc := make(chan error) | ||||
| 
 | ||||
| 	for p := 0; p < maxproccnt; p++ { | ||||
| 		for i := 0; i < cycles; i++ { | ||||
| 			go func() { | ||||
| 				bmt := New(pool) | ||||
| 				n := rand.Intn(4096) | ||||
| 				tdata := testDataReader(n) | ||||
| 				data := make([]byte, n) | ||||
| 				tdata.Read(data) | ||||
| 				err := testHasherCorrectness(bmt, hasher, data, n, 128) | ||||
| 				wg.Done() | ||||
| 				if err != nil { | ||||
| 					errc <- err | ||||
| 				} | ||||
| 			}() | ||||
| 		} | ||||
| 	} | ||||
| 	go func() { | ||||
| 		wg.Wait() | ||||
| 		close(errc) | ||||
| 	}() | ||||
| 	var err error | ||||
| 	select { | ||||
| 	case <-time.NewTimer(5 * time.Second).C: | ||||
| 		err = fmt.Errorf("timed out") | ||||
| 	case err = <-errc: | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func testBaseHasher(hasher BaseHasher, d []byte, n, count int) error { | ||||
| 	pool := NewTreePool(hasher, count, 1) | ||||
| 	defer pool.Drain(0) | ||||
| 	bmt := New(pool) | ||||
| 	return testHasherCorrectness(bmt, hasher, d, n, count) | ||||
| } | ||||
| 
 | ||||
| func testHasherCorrectness(bmt hash.Hash, hasher BaseHasher, d []byte, n, count int) (err error) { | ||||
| 	data := d[:n] | ||||
| 	rbmt := NewRefHasher(hasher, count) | ||||
| 	exp := rbmt.Hash(data) | ||||
| 	timeout := time.NewTimer(time.Second) | ||||
| 	c := make(chan error) | ||||
| 
 | ||||
| 	go func() { | ||||
| 		bmt.Reset() | ||||
| 		bmt.Write(data) | ||||
| 		got := bmt.Sum(nil) | ||||
| 		if !bytes.Equal(got, exp) { | ||||
| 			c <- fmt.Errorf("wrong hash: expected %x, got %x", exp, got) | ||||
| 		} | ||||
| 		close(c) | ||||
| 	}() | ||||
| 	select { | ||||
| 	case <-timeout.C: | ||||
| 		err = fmt.Errorf("BMT hash calculation timed out") | ||||
| 	case err = <-c: | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func BenchmarkSHA3_4k(t *testing.B)   { benchmarkSHA3(4096, t) } | ||||
| func BenchmarkSHA3_2k(t *testing.B)   { benchmarkSHA3(4096/2, t) } | ||||
| func BenchmarkSHA3_1k(t *testing.B)   { benchmarkSHA3(4096/4, t) } | ||||
| func BenchmarkSHA3_512b(t *testing.B) { benchmarkSHA3(4096/8, t) } | ||||
| func BenchmarkSHA3_256b(t *testing.B) { benchmarkSHA3(4096/16, t) } | ||||
| func BenchmarkSHA3_128b(t *testing.B) { benchmarkSHA3(4096/32, t) } | ||||
| 
 | ||||
| func BenchmarkBMTBaseline_4k(t *testing.B)   { benchmarkBMTBaseline(4096, t) } | ||||
| func BenchmarkBMTBaseline_2k(t *testing.B)   { benchmarkBMTBaseline(4096/2, t) } | ||||
| func BenchmarkBMTBaseline_1k(t *testing.B)   { benchmarkBMTBaseline(4096/4, t) } | ||||
| func BenchmarkBMTBaseline_512b(t *testing.B) { benchmarkBMTBaseline(4096/8, t) } | ||||
| func BenchmarkBMTBaseline_256b(t *testing.B) { benchmarkBMTBaseline(4096/16, t) } | ||||
| func BenchmarkBMTBaseline_128b(t *testing.B) { benchmarkBMTBaseline(4096/32, t) } | ||||
| 
 | ||||
| func BenchmarkRefHasher_4k(t *testing.B)   { benchmarkRefHasher(4096, t) } | ||||
| func BenchmarkRefHasher_2k(t *testing.B)   { benchmarkRefHasher(4096/2, t) } | ||||
| func BenchmarkRefHasher_1k(t *testing.B)   { benchmarkRefHasher(4096/4, t) } | ||||
| func BenchmarkRefHasher_512b(t *testing.B) { benchmarkRefHasher(4096/8, t) } | ||||
| func BenchmarkRefHasher_256b(t *testing.B) { benchmarkRefHasher(4096/16, t) } | ||||
| func BenchmarkRefHasher_128b(t *testing.B) { benchmarkRefHasher(4096/32, t) } | ||||
| 
 | ||||
| func BenchmarkHasher_4k(t *testing.B)   { benchmarkHasher(4096, t) } | ||||
| func BenchmarkHasher_2k(t *testing.B)   { benchmarkHasher(4096/2, t) } | ||||
| func BenchmarkHasher_1k(t *testing.B)   { benchmarkHasher(4096/4, t) } | ||||
| func BenchmarkHasher_512b(t *testing.B) { benchmarkHasher(4096/8, t) } | ||||
| func BenchmarkHasher_256b(t *testing.B) { benchmarkHasher(4096/16, t) } | ||||
| func BenchmarkHasher_128b(t *testing.B) { benchmarkHasher(4096/32, t) } | ||||
| 
 | ||||
| func BenchmarkHasherNoReuse_4k(t *testing.B)   { benchmarkHasherReuse(1, 4096, t) } | ||||
| func BenchmarkHasherNoReuse_2k(t *testing.B)   { benchmarkHasherReuse(1, 4096/2, t) } | ||||
| func BenchmarkHasherNoReuse_1k(t *testing.B)   { benchmarkHasherReuse(1, 4096/4, t) } | ||||
| func BenchmarkHasherNoReuse_512b(t *testing.B) { benchmarkHasherReuse(1, 4096/8, t) } | ||||
| func BenchmarkHasherNoReuse_256b(t *testing.B) { benchmarkHasherReuse(1, 4096/16, t) } | ||||
| func BenchmarkHasherNoReuse_128b(t *testing.B) { benchmarkHasherReuse(1, 4096/32, t) } | ||||
| 
 | ||||
| func BenchmarkHasherReuse_4k(t *testing.B)   { benchmarkHasherReuse(16, 4096, t) } | ||||
| func BenchmarkHasherReuse_2k(t *testing.B)   { benchmarkHasherReuse(16, 4096/2, t) } | ||||
| func BenchmarkHasherReuse_1k(t *testing.B)   { benchmarkHasherReuse(16, 4096/4, t) } | ||||
| func BenchmarkHasherReuse_512b(t *testing.B) { benchmarkHasherReuse(16, 4096/8, t) } | ||||
| func BenchmarkHasherReuse_256b(t *testing.B) { benchmarkHasherReuse(16, 4096/16, t) } | ||||
| func BenchmarkHasherReuse_128b(t *testing.B) { benchmarkHasherReuse(16, 4096/32, t) } | ||||
| 
 | ||||
| // benchmarks the minimum hashing time for a balanced (for simplicity) BMT
 | ||||
| // by doing count/segmentsize parallel hashings of 2*segmentsize bytes
 | ||||
| // doing it on n maxproccnt each reusing the base hasher
 | ||||
| // the premise is that this is the minimum computation needed for a BMT
 | ||||
| // therefore this serves as a theoretical optimum for concurrent implementations
 | ||||
| func benchmarkBMTBaseline(n int, t *testing.B) { | ||||
| 	tdata := testDataReader(64) | ||||
| 	data := make([]byte, 64) | ||||
| 	tdata.Read(data) | ||||
| 	hasher := sha3.NewKeccak256 | ||||
| 
 | ||||
| 	t.ReportAllocs() | ||||
| 	t.ResetTimer() | ||||
| 	for i := 0; i < t.N; i++ { | ||||
| 		count := int32((n-1)/hasher().Size() + 1) | ||||
| 		wg := sync.WaitGroup{} | ||||
| 		wg.Add(maxproccnt) | ||||
| 		var i int32 | ||||
| 		for j := 0; j < maxproccnt; j++ { | ||||
| 			go func() { | ||||
| 				defer wg.Done() | ||||
| 				h := hasher() | ||||
| 				for atomic.AddInt32(&i, 1) < count { | ||||
| 					h.Reset() | ||||
| 					h.Write(data) | ||||
| 					h.Sum(nil) | ||||
| 				} | ||||
| 			}() | ||||
| 		} | ||||
| 		wg.Wait() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func benchmarkHasher(n int, t *testing.B) { | ||||
| 	tdata := testDataReader(n) | ||||
| 	data := make([]byte, n) | ||||
| 	tdata.Read(data) | ||||
| 
 | ||||
| 	size := 1 | ||||
| 	hasher := sha3.NewKeccak256 | ||||
| 	segmentCount := 128 | ||||
| 	pool := NewTreePool(hasher, segmentCount, size) | ||||
| 	bmt := New(pool) | ||||
| 
 | ||||
| 	t.ReportAllocs() | ||||
| 	t.ResetTimer() | ||||
| 	for i := 0; i < t.N; i++ { | ||||
| 		bmt.Reset() | ||||
| 		bmt.Write(data) | ||||
| 		bmt.Sum(nil) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func benchmarkHasherReuse(poolsize, n int, t *testing.B) { | ||||
| 	tdata := testDataReader(n) | ||||
| 	data := make([]byte, n) | ||||
| 	tdata.Read(data) | ||||
| 
 | ||||
| 	hasher := sha3.NewKeccak256 | ||||
| 	segmentCount := 128 | ||||
| 	pool := NewTreePool(hasher, segmentCount, poolsize) | ||||
| 	cycles := 200 | ||||
| 
 | ||||
| 	t.ReportAllocs() | ||||
| 	t.ResetTimer() | ||||
| 	for i := 0; i < t.N; i++ { | ||||
| 		wg := sync.WaitGroup{} | ||||
| 		wg.Add(cycles) | ||||
| 		for j := 0; j < cycles; j++ { | ||||
| 			bmt := New(pool) | ||||
| 			go func() { | ||||
| 				defer wg.Done() | ||||
| 				bmt.Reset() | ||||
| 				bmt.Write(data) | ||||
| 				bmt.Sum(nil) | ||||
| 			}() | ||||
| 		} | ||||
| 		wg.Wait() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func benchmarkSHA3(n int, t *testing.B) { | ||||
| 	data := make([]byte, n) | ||||
| 	tdata := testDataReader(n) | ||||
| 	tdata.Read(data) | ||||
| 	hasher := sha3.NewKeccak256 | ||||
| 	h := hasher() | ||||
| 
 | ||||
| 	t.ReportAllocs() | ||||
| 	t.ResetTimer() | ||||
| 	for i := 0; i < t.N; i++ { | ||||
| 		h.Reset() | ||||
| 		h.Write(data) | ||||
| 		h.Sum(nil) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func benchmarkRefHasher(n int, t *testing.B) { | ||||
| 	data := make([]byte, n) | ||||
| 	tdata := testDataReader(n) | ||||
| 	tdata.Read(data) | ||||
| 	hasher := sha3.NewKeccak256 | ||||
| 	rbmt := NewRefHasher(hasher, 128) | ||||
| 
 | ||||
| 	t.ReportAllocs() | ||||
| 	t.ResetTimer() | ||||
| 	for i := 0; i < t.N; i++ { | ||||
| 		rbmt.Hash(data) | ||||
| 	} | ||||
| } | ||||
| @ -51,7 +51,8 @@ data_{i} := size(subtree_{i}) || key_{j} || key_{j+1} .... || key_{j+n-1} | ||||
| */ | ||||
| 
 | ||||
| const ( | ||||
| 	defaultHash = "SHA3" // http://golang.org/pkg/hash/#Hash
 | ||||
| 	defaultHash = "SHA3" | ||||
| 	// defaultHash = "BMTSHA3" // http://golang.org/pkg/hash/#Hash
 | ||||
| 	// defaultHash           = "SHA256" // http://golang.org/pkg/hash/#Hash
 | ||||
| 	defaultBranches int64 = 128 | ||||
| 	// hashSize     int64 = hasherfunc.New().Size() // hasher knows about its own length in bytes
 | ||||
|  | ||||
| @ -24,6 +24,7 @@ import ( | ||||
| 	"io" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	// "github.com/ethereum/go-ethereum/bmt"
 | ||||
| 	"github.com/ethereum/go-ethereum/common" | ||||
| 	"github.com/ethereum/go-ethereum/crypto/sha3" | ||||
| ) | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user