commit
027c37281d
4
Makefile
4
Makefile
|
@ -62,12 +62,16 @@ get_vendor_deps:
|
||||||
@echo "--> Running glide install"
|
@echo "--> Running glide install"
|
||||||
@glide install
|
@glide install
|
||||||
|
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -tags gcc `glide novendor`
|
go test -tags gcc `glide novendor`
|
||||||
|
|
||||||
|
test100:
|
||||||
|
@for i in {1..100}; do make test; done
|
||||||
|
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
### Formatting, linting, and vetting
|
### Formatting, linting, and vetting
|
||||||
|
|
22
cli/setup.go
22
cli/setup.go
|
@ -8,9 +8,6 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
data "github.com/tendermint/go-wire/data"
|
|
||||||
"github.com/tendermint/go-wire/data/base58"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -42,7 +39,7 @@ func PrepareBaseCmd(cmd *cobra.Command, envPrefix, defaultHome string) Executor
|
||||||
func PrepareMainCmd(cmd *cobra.Command, envPrefix, defaultHome string) Executor {
|
func PrepareMainCmd(cmd *cobra.Command, envPrefix, defaultHome string) Executor {
|
||||||
cmd.PersistentFlags().StringP(EncodingFlag, "e", "hex", "Binary encoding (hex|b64|btc)")
|
cmd.PersistentFlags().StringP(EncodingFlag, "e", "hex", "Binary encoding (hex|b64|btc)")
|
||||||
cmd.PersistentFlags().StringP(OutputFlag, "o", "text", "Output format (text|json)")
|
cmd.PersistentFlags().StringP(OutputFlag, "o", "text", "Output format (text|json)")
|
||||||
cmd.PersistentPreRunE = concatCobraCmdFuncs(setEncoding, validateOutput, cmd.PersistentPreRunE)
|
cmd.PersistentPreRunE = concatCobraCmdFuncs(validateOutput, cmd.PersistentPreRunE)
|
||||||
return PrepareBaseCmd(cmd, envPrefix, defaultHome)
|
return PrepareBaseCmd(cmd, envPrefix, defaultHome)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,23 +144,6 @@ func bindFlagsLoadViper(cmd *cobra.Command, args []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setEncoding reads the encoding flag
|
|
||||||
func setEncoding(cmd *cobra.Command, args []string) error {
|
|
||||||
// validate and set encoding
|
|
||||||
enc := viper.GetString("encoding")
|
|
||||||
switch enc {
|
|
||||||
case "hex":
|
|
||||||
data.Encoder = data.HexEncoder
|
|
||||||
case "b64":
|
|
||||||
data.Encoder = data.B64Encoder
|
|
||||||
case "btc":
|
|
||||||
data.Encoder = base58.BTCEncoder
|
|
||||||
default:
|
|
||||||
return errors.Errorf("Unsupported encoding: %s", enc)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateOutput(cmd *cobra.Command, args []string) error {
|
func validateOutput(cmd *cobra.Command, args []string) error {
|
||||||
// validate output format
|
// validate output format
|
||||||
output := viper.GetString(OutputFlag)
|
output := viper.GetString(OutputFlag)
|
||||||
|
|
|
@ -80,13 +80,11 @@ func (t *logicalTicker) fireRoutine(interval time.Duration) {
|
||||||
}
|
}
|
||||||
// Init `lasttime` end
|
// Init `lasttime` end
|
||||||
|
|
||||||
timeleft := interval
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case newtime := <-source:
|
case newtime := <-source:
|
||||||
elapsed := newtime.Sub(lasttime)
|
elapsed := newtime.Sub(lasttime)
|
||||||
timeleft -= elapsed
|
if interval <= elapsed {
|
||||||
if timeleft <= 0 {
|
|
||||||
// Block for determinism until the ticker is stopped.
|
// Block for determinism until the ticker is stopped.
|
||||||
select {
|
select {
|
||||||
case t.ch <- newtime:
|
case t.ch <- newtime:
|
||||||
|
@ -97,7 +95,7 @@ func (t *logicalTicker) fireRoutine(interval time.Duration) {
|
||||||
// Don't try to "catch up" by sending more.
|
// Don't try to "catch up" by sending more.
|
||||||
// "Ticker adjusts the intervals or drops ticks to make up for
|
// "Ticker adjusts the intervals or drops ticks to make up for
|
||||||
// slow receivers" - https://golang.org/pkg/time/#Ticker
|
// slow receivers" - https://golang.org/pkg/time/#Ticker
|
||||||
timeleft = interval
|
lasttime = newtime
|
||||||
}
|
}
|
||||||
case <-t.quit:
|
case <-t.quit:
|
||||||
return // done
|
return // done
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -13,29 +14,42 @@ func TestDefaultTicker(t *testing.T) {
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepeat(t *testing.T) {
|
func TestRepeatTimer(t *testing.T) {
|
||||||
|
|
||||||
ch := make(chan time.Time, 100)
|
ch := make(chan time.Time, 100)
|
||||||
lt := time.Time{} // zero time is year 1
|
mtx := new(sync.Mutex)
|
||||||
|
|
||||||
// tick fires `cnt` times for each second.
|
// tick() fires from start to end
|
||||||
tick := func(cnt int) {
|
// (exclusive) in milliseconds with incr.
|
||||||
for i := 0; i < cnt; i++ {
|
// It locks on mtx, so subsequent calls
|
||||||
lt = lt.Add(time.Second)
|
// run in series.
|
||||||
ch <- lt
|
tick := func(startMs, endMs, incrMs time.Duration) {
|
||||||
}
|
mtx.Lock()
|
||||||
|
go func() {
|
||||||
|
for tMs := startMs; tMs < endMs; tMs += incrMs {
|
||||||
|
lt := time.Time{}
|
||||||
|
lt = lt.Add(tMs * time.Millisecond)
|
||||||
|
ch <- lt
|
||||||
|
}
|
||||||
|
mtx.Unlock()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// tock consumes Ticker.Chan() events `cnt` times.
|
// tock consumes Ticker.Chan() events and checks them against the ms in "timesMs".
|
||||||
tock := func(t *testing.T, rt *RepeatTimer, cnt int) {
|
tock := func(t *testing.T, rt *RepeatTimer, timesMs []int64) {
|
||||||
for i := 0; i < cnt; i++ {
|
|
||||||
timeout := time.After(time.Second * 10)
|
// Check against timesMs.
|
||||||
select {
|
for _, timeMs := range timesMs {
|
||||||
case <-rt.Chan():
|
tyme := <-rt.Chan()
|
||||||
case <-timeout:
|
sinceMs := tyme.Sub(time.Time{}) / time.Millisecond
|
||||||
panic("expected RepeatTimer to fire")
|
assert.Equal(t, timeMs, int64(sinceMs))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO detect number of running
|
||||||
|
// goroutines to ensure that
|
||||||
|
// no other times will fire.
|
||||||
|
// See https://github.com/tendermint/tmlibs/issues/120.
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
done := true
|
done := true
|
||||||
select {
|
select {
|
||||||
case <-rt.Chan():
|
case <-rt.Chan():
|
||||||
|
@ -46,46 +60,44 @@ func TestRepeat(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tm := NewLogicalTickerMaker(ch)
|
tm := NewLogicalTickerMaker(ch)
|
||||||
dur := time.Duration(10 * time.Millisecond) // less than a second
|
rt := NewRepeatTimerWithTickerMaker("bar", time.Second, tm)
|
||||||
rt := NewRepeatTimerWithTickerMaker("bar", dur, tm)
|
|
||||||
|
|
||||||
// Start at 0.
|
/* NOTE: Useful for debugging deadlocks...
|
||||||
tock(t, rt, 0)
|
go func() {
|
||||||
tick(1) // init time
|
time.Sleep(time.Second * 3)
|
||||||
|
trace := make([]byte, 102400)
|
||||||
|
count := runtime.Stack(trace, true)
|
||||||
|
fmt.Printf("Stack of %d bytes: %s\n", count, trace)
|
||||||
|
}()
|
||||||
|
*/
|
||||||
|
|
||||||
tock(t, rt, 0)
|
tick(0, 1000, 10)
|
||||||
tick(1) // wait 1 periods
|
tock(t, rt, []int64{})
|
||||||
tock(t, rt, 1)
|
tick(1000, 2000, 10)
|
||||||
tick(2) // wait 2 periods
|
tock(t, rt, []int64{1000})
|
||||||
tock(t, rt, 2)
|
tick(2005, 5000, 10)
|
||||||
tick(3) // wait 3 periods
|
tock(t, rt, []int64{2005, 3005, 4005})
|
||||||
tock(t, rt, 3)
|
tick(5001, 5999, 1)
|
||||||
tick(4) // wait 4 periods
|
// Read 5005 instead of 5001 because
|
||||||
tock(t, rt, 4)
|
// it's 1 second greater than 4005.
|
||||||
|
tock(t, rt, []int64{5005})
|
||||||
|
tick(6000, 7005, 1)
|
||||||
|
tock(t, rt, []int64{6005})
|
||||||
|
tick(7033, 8032, 1)
|
||||||
|
tock(t, rt, []int64{7033})
|
||||||
|
|
||||||
// Multiple resets leads to no firing.
|
// After a reset, nothing happens
|
||||||
for i := 0; i < 20; i++ {
|
// until two ticks are received.
|
||||||
time.Sleep(time.Millisecond)
|
rt.Reset()
|
||||||
rt.Reset()
|
tock(t, rt, []int64{})
|
||||||
}
|
tick(8040, 8041, 1)
|
||||||
|
tock(t, rt, []int64{})
|
||||||
// After this, it works as new.
|
tick(9555, 9556, 1)
|
||||||
tock(t, rt, 0)
|
tock(t, rt, []int64{9555})
|
||||||
tick(1) // init time
|
|
||||||
|
|
||||||
tock(t, rt, 0)
|
|
||||||
tick(1) // wait 1 periods
|
|
||||||
tock(t, rt, 1)
|
|
||||||
tick(2) // wait 2 periods
|
|
||||||
tock(t, rt, 2)
|
|
||||||
tick(3) // wait 3 periods
|
|
||||||
tock(t, rt, 3)
|
|
||||||
tick(4) // wait 4 periods
|
|
||||||
tock(t, rt, 4)
|
|
||||||
|
|
||||||
// After a stop, nothing more is sent.
|
// After a stop, nothing more is sent.
|
||||||
rt.Stop()
|
rt.Stop()
|
||||||
tock(t, rt, 0)
|
tock(t, rt, []int64{})
|
||||||
|
|
||||||
// Another stop panics.
|
// Another stop panics.
|
||||||
assert.Panics(t, func() { rt.Stop() })
|
assert.Panics(t, func() { rt.Stop() })
|
||||||
|
|
|
@ -15,7 +15,7 @@ func cleanupDBDir(dir, name string) {
|
||||||
os.RemoveAll(filepath.Join(dir, name) + ".db")
|
os.RemoveAll(filepath.Join(dir, name) + ".db")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBackendGetSetDelete(t *testing.T, backend string) {
|
func testBackendGetSetDelete(t *testing.T, backend DBBackendType) {
|
||||||
// Default
|
// Default
|
||||||
dir, dirname := cmn.Tempdir(fmt.Sprintf("test_backend_%s_", backend))
|
dir, dirname := cmn.Tempdir(fmt.Sprintf("test_backend_%s_", backend))
|
||||||
defer dir.Close()
|
defer dir.Close()
|
||||||
|
@ -141,9 +141,9 @@ func TestBackendsNilKeys(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGoLevelDBBackendStr(t *testing.T) {
|
func TestGoLevelDBBackend(t *testing.T) {
|
||||||
name := cmn.Fmt("test_%x", cmn.RandStr(12))
|
name := cmn.Fmt("test_%x", cmn.RandStr(12))
|
||||||
db := NewDB(name, GoLevelDBBackendStr, "")
|
db := NewDB(name, GoLevelDBBackend, "")
|
||||||
defer cleanupDBDir("", name)
|
defer cleanupDBDir("", name)
|
||||||
|
|
||||||
_, ok := db.(*GoLevelDB)
|
_, ok := db.(*GoLevelDB)
|
||||||
|
|
|
@ -14,8 +14,8 @@ func init() {
|
||||||
dbCreator := func(name string, dir string) (DB, error) {
|
dbCreator := func(name string, dir string) (DB, error) {
|
||||||
return NewCLevelDB(name, dir)
|
return NewCLevelDB(name, dir)
|
||||||
}
|
}
|
||||||
registerDBCreator(LevelDBBackendStr, dbCreator, true)
|
registerDBCreator(LevelDBBackend, dbCreator, true)
|
||||||
registerDBCreator(CLevelDBBackendStr, dbCreator, false)
|
registerDBCreator(CLevelDBBackend, dbCreator, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ DB = (*CLevelDB)(nil)
|
var _ DB = (*CLevelDB)(nil)
|
||||||
|
|
|
@ -86,9 +86,9 @@ func bytes2Int64(buf []byte) int64 {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func TestCLevelDBBackendStr(t *testing.T) {
|
func TestCLevelDBBackend(t *testing.T) {
|
||||||
name := cmn.Fmt("test_%x", cmn.RandStr(12))
|
name := cmn.Fmt("test_%x", cmn.RandStr(12))
|
||||||
db := NewDB(name, LevelDBBackendStr, "")
|
db := NewDB(name, LevelDBBackend, "")
|
||||||
defer cleanupDBDir("", name)
|
defer cleanupDBDir("", name)
|
||||||
|
|
||||||
_, ok := db.(*CLevelDB)
|
_, ok := db.(*CLevelDB)
|
||||||
|
|
|
@ -45,7 +45,7 @@ func checkValuePanics(t *testing.T, itr Iterator) {
|
||||||
assert.Panics(t, func() { itr.Key() }, "checkValuePanics expected panic but didn't")
|
assert.Panics(t, func() { itr.Key() }, "checkValuePanics expected panic but didn't")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTempDB(t *testing.T, backend string) (db DB) {
|
func newTempDB(t *testing.T, backend DBBackendType) (db DB) {
|
||||||
dir, dirname := cmn.Tempdir("test_go_iterator")
|
dir, dirname := cmn.Tempdir("test_go_iterator")
|
||||||
db = NewDB("testdb", backend, dirname)
|
db = NewDB("testdb", backend, dirname)
|
||||||
dir.Close()
|
dir.Close()
|
||||||
|
|
18
db/db.go
18
db/db.go
|
@ -5,19 +5,21 @@ import "fmt"
|
||||||
//----------------------------------------
|
//----------------------------------------
|
||||||
// Main entry
|
// Main entry
|
||||||
|
|
||||||
|
type DBBackendType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LevelDBBackendStr = "leveldb" // legacy, defaults to goleveldb unless +gcc
|
LevelDBBackend DBBackendType = "leveldb" // legacy, defaults to goleveldb unless +gcc
|
||||||
CLevelDBBackendStr = "cleveldb"
|
CLevelDBBackend DBBackendType = "cleveldb"
|
||||||
GoLevelDBBackendStr = "goleveldb"
|
GoLevelDBBackend DBBackendType = "goleveldb"
|
||||||
MemDBBackendStr = "memdb"
|
MemDBBackend DBBackendType = "memdb"
|
||||||
FSDBBackendStr = "fsdb" // using the filesystem naively
|
FSDBBackend DBBackendType = "fsdb" // using the filesystem naively
|
||||||
)
|
)
|
||||||
|
|
||||||
type dbCreator func(name string, dir string) (DB, error)
|
type dbCreator func(name string, dir string) (DB, error)
|
||||||
|
|
||||||
var backends = map[string]dbCreator{}
|
var backends = map[DBBackendType]dbCreator{}
|
||||||
|
|
||||||
func registerDBCreator(backend string, creator dbCreator, force bool) {
|
func registerDBCreator(backend DBBackendType, creator dbCreator, force bool) {
|
||||||
_, ok := backends[backend]
|
_, ok := backends[backend]
|
||||||
if !force && ok {
|
if !force && ok {
|
||||||
return
|
return
|
||||||
|
@ -25,7 +27,7 @@ func registerDBCreator(backend string, creator dbCreator, force bool) {
|
||||||
backends[backend] = creator
|
backends[backend] = creator
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDB(name string, backend string, dir string) DB {
|
func NewDB(name string, backend DBBackendType, dir string) DB {
|
||||||
db, err := backends[backend](name, dir)
|
db, err := backends[backend](name, dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("Error initializing DB: %v", err))
|
panic(fmt.Sprintf("Error initializing DB: %v", err))
|
||||||
|
|
|
@ -19,7 +19,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerDBCreator(FSDBBackendStr, func(name string, dir string) (DB, error) {
|
registerDBCreator(FSDBBackend, func(name string, dir string) (DB, error) {
|
||||||
dbPath := filepath.Join(dir, name+".db")
|
dbPath := filepath.Join(dir, name+".db")
|
||||||
return NewFSDB(dbPath), nil
|
return NewFSDB(dbPath), nil
|
||||||
}, false)
|
}, false)
|
||||||
|
|
|
@ -17,8 +17,8 @@ func init() {
|
||||||
dbCreator := func(name string, dir string) (DB, error) {
|
dbCreator := func(name string, dir string) (DB, error) {
|
||||||
return NewGoLevelDB(name, dir)
|
return NewGoLevelDB(name, dir)
|
||||||
}
|
}
|
||||||
registerDBCreator(LevelDBBackendStr, dbCreator, false)
|
registerDBCreator(LevelDBBackend, dbCreator, false)
|
||||||
registerDBCreator(GoLevelDBBackendStr, dbCreator, false)
|
registerDBCreator(GoLevelDBBackend, dbCreator, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ DB = (*GoLevelDB)(nil)
|
var _ DB = (*GoLevelDB)(nil)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerDBCreator(MemDBBackendStr, func(name string, dir string) (DB, error) {
|
registerDBCreator(MemDBBackend, func(name string, dir string) (DB, error) {
|
||||||
return NewMemDB(), nil
|
return NewMemDB(), nil
|
||||||
}, false)
|
}, false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
hash: 325b2f9c7e84696f88fa88126a22eb1e1e91c2be5f60402d17bfaad6713b33c2
|
hash: 22e22759d9adc51e3ce0728955143321386891907ce54eb952245d57285d8784
|
||||||
updated: 2017-12-28T18:27:21.247160207-08:00
|
updated: 2018-02-02T18:08:31.85309+01:00
|
||||||
imports:
|
imports:
|
||||||
|
- name: github.com/davecgh/go-spew
|
||||||
|
version: 346938d642f2ec3594ed81d874461961cd0faa76
|
||||||
|
subpackages:
|
||||||
|
- spew
|
||||||
- name: github.com/fsnotify/fsnotify
|
- name: github.com/fsnotify/fsnotify
|
||||||
version: 4da3e2cfbabc9f751898f250b49f2439785783a1
|
version: c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9
|
||||||
- name: github.com/go-kit/kit
|
- name: github.com/go-kit/kit
|
||||||
version: e2b298466b32c7cd5579a9b9b07e968fc9d9452c
|
version: 4dc7be5d2d12881735283bcab7352178e190fc71
|
||||||
subpackages:
|
subpackages:
|
||||||
- log
|
- log
|
||||||
- log/level
|
- log/level
|
||||||
|
@ -14,7 +18,7 @@ imports:
|
||||||
- name: github.com/go-stack/stack
|
- name: github.com/go-stack/stack
|
||||||
version: 817915b46b97fd7bb80e8ab6b69f01a53ac3eebf
|
version: 817915b46b97fd7bb80e8ab6b69f01a53ac3eebf
|
||||||
- name: github.com/gogo/protobuf
|
- name: github.com/gogo/protobuf
|
||||||
version: 342cbe0a04158f6dcb03ca0079991a51a4248c02
|
version: 1adfc126b41513cc696b209667c8656ea7aac67c
|
||||||
subpackages:
|
subpackages:
|
||||||
- gogoproto
|
- gogoproto
|
||||||
- proto
|
- proto
|
||||||
|
@ -39,21 +43,15 @@ imports:
|
||||||
- name: github.com/kr/logfmt
|
- name: github.com/kr/logfmt
|
||||||
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
|
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
|
||||||
- name: github.com/magiconair/properties
|
- name: github.com/magiconair/properties
|
||||||
version: 8d7837e64d3c1ee4e54a880c5a920ab4316fc90a
|
version: 49d762b9817ba1c2e9d0c69183c2b4a8b8f1d934
|
||||||
- name: github.com/mattn/go-colorable
|
|
||||||
version: 6fcc0c1fd9b620311d821b106a400b35dc95c497
|
|
||||||
- name: github.com/mattn/go-isatty
|
|
||||||
version: a5cdd64afdee435007ee3e9f6ed4684af949d568
|
|
||||||
- name: github.com/mitchellh/mapstructure
|
- name: github.com/mitchellh/mapstructure
|
||||||
version: 06020f85339e21b2478f756a78e295255ffa4d6a
|
version: b4575eea38cca1123ec2dc90c26529b5c5acfcff
|
||||||
- name: github.com/pelletier/go-buffruneio
|
|
||||||
version: c37440a7cf42ac63b919c752ca73a85067e05992
|
|
||||||
- name: github.com/pelletier/go-toml
|
- name: github.com/pelletier/go-toml
|
||||||
version: 13d49d4606eb801b8f01ae542b4afc4c6ee3d84a
|
version: acdc4509485b587f5e675510c4f2c63e90ff68a8
|
||||||
- name: github.com/pkg/errors
|
- name: github.com/pkg/errors
|
||||||
version: 645ef00459ed84a119197bfb8d8205042c6df63d
|
version: 645ef00459ed84a119197bfb8d8205042c6df63d
|
||||||
- name: github.com/spf13/afero
|
- name: github.com/spf13/afero
|
||||||
version: 5660eeed305fe5f69c8fc6cf899132a459a97064
|
version: bb8f1927f2a9d3ab41c9340aa034f6b803f4359c
|
||||||
subpackages:
|
subpackages:
|
||||||
- mem
|
- mem
|
||||||
- name: github.com/spf13/cast
|
- name: github.com/spf13/cast
|
||||||
|
@ -61,11 +59,11 @@ imports:
|
||||||
- name: github.com/spf13/cobra
|
- name: github.com/spf13/cobra
|
||||||
version: 7b2c5ac9fc04fc5efafb60700713d4fa609b777b
|
version: 7b2c5ac9fc04fc5efafb60700713d4fa609b777b
|
||||||
- name: github.com/spf13/jwalterweatherman
|
- name: github.com/spf13/jwalterweatherman
|
||||||
version: 12bd96e66386c1960ab0f74ced1362f66f552f7b
|
version: 7c0cea34c8ece3fbeb2b27ab9b59511d360fb394
|
||||||
- name: github.com/spf13/pflag
|
- name: github.com/spf13/pflag
|
||||||
version: 97afa5e7ca8a08a383cb259e06636b5e2cc7897f
|
version: 97afa5e7ca8a08a383cb259e06636b5e2cc7897f
|
||||||
- name: github.com/spf13/viper
|
- name: github.com/spf13/viper
|
||||||
version: 8ef37cbca71638bf32f3d5e194117d4cb46da163
|
version: 25b30aa063fc18e48662b86996252eabdcf2f0c7
|
||||||
- name: github.com/syndtr/goleveldb
|
- name: github.com/syndtr/goleveldb
|
||||||
version: b89cc31ef7977104127d34c1bd31ebd1a9db2199
|
version: b89cc31ef7977104127d34c1bd31ebd1a9db2199
|
||||||
subpackages:
|
subpackages:
|
||||||
|
@ -82,20 +80,13 @@ imports:
|
||||||
- leveldb/table
|
- leveldb/table
|
||||||
- leveldb/util
|
- leveldb/util
|
||||||
- name: github.com/tendermint/go-wire
|
- name: github.com/tendermint/go-wire
|
||||||
version: 27be46e25124ddf775e23317a83647ce62a93f6b
|
version: e723d95ac2838b7ae9919ada25004859236c32ff
|
||||||
subpackages:
|
|
||||||
- data
|
|
||||||
- data/base58
|
|
||||||
- name: github.com/tendermint/log15
|
|
||||||
version: f91285dece9f4875421b481da3e613d83d44f29b
|
|
||||||
subpackages:
|
|
||||||
- term
|
|
||||||
- name: golang.org/x/crypto
|
- name: golang.org/x/crypto
|
||||||
version: edd5e9b0879d13ee6970a50153d85b8fec9f7686
|
version: edd5e9b0879d13ee6970a50153d85b8fec9f7686
|
||||||
subpackages:
|
subpackages:
|
||||||
- ripemd160
|
- ripemd160
|
||||||
- name: golang.org/x/sys
|
- name: golang.org/x/sys
|
||||||
version: 8dbc5d05d6edcc104950cc299a1ce6641235bc86
|
version: 37707fdb30a5b38865cfb95e5aab41707daec7fd
|
||||||
subpackages:
|
subpackages:
|
||||||
- unix
|
- unix
|
||||||
- name: golang.org/x/text
|
- name: golang.org/x/text
|
||||||
|
@ -104,18 +95,14 @@ imports:
|
||||||
- transform
|
- transform
|
||||||
- unicode/norm
|
- unicode/norm
|
||||||
- name: gopkg.in/yaml.v2
|
- name: gopkg.in/yaml.v2
|
||||||
version: eb3733d160e74a9c7e442f435eb3bea458e1d19f
|
version: d670f9405373e636a5a2765eea47fac0c9bc91a4
|
||||||
testImports:
|
testImports:
|
||||||
- name: github.com/davecgh/go-spew
|
|
||||||
version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9
|
|
||||||
subpackages:
|
|
||||||
- spew
|
|
||||||
- name: github.com/pmezard/go-difflib
|
- name: github.com/pmezard/go-difflib
|
||||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||||
subpackages:
|
subpackages:
|
||||||
- difflib
|
- difflib
|
||||||
- name: github.com/stretchr/testify
|
- name: github.com/stretchr/testify
|
||||||
version: 2aa2c176b9dab406a6970f6a55f513e8a8c8b18f
|
version: 12b6f73e6084dad08a7c6e575284b177ecafbc71
|
||||||
subpackages:
|
subpackages:
|
||||||
- assert
|
- assert
|
||||||
- require
|
- require
|
||||||
|
|
17
glide.yaml
17
glide.yaml
|
@ -1,30 +1,39 @@
|
||||||
package: github.com/tendermint/tmlibs
|
package: github.com/tendermint/tmlibs
|
||||||
import:
|
import:
|
||||||
- package: github.com/go-kit/kit
|
- package: github.com/go-kit/kit
|
||||||
|
version: ^0.6.0
|
||||||
subpackages:
|
subpackages:
|
||||||
- log
|
- log
|
||||||
- log/level
|
- log/level
|
||||||
- log/term
|
- log/term
|
||||||
- package: github.com/go-logfmt/logfmt
|
- package: github.com/go-logfmt/logfmt
|
||||||
|
version: ^0.3.0
|
||||||
|
- package: github.com/gogo/protobuf
|
||||||
|
version: ^1.0.0
|
||||||
|
subpackages:
|
||||||
|
- gogoproto
|
||||||
|
- proto
|
||||||
- package: github.com/jmhodges/levigo
|
- package: github.com/jmhodges/levigo
|
||||||
- package: github.com/pkg/errors
|
- package: github.com/pkg/errors
|
||||||
|
version: ^0.8.0
|
||||||
- package: github.com/spf13/cobra
|
- package: github.com/spf13/cobra
|
||||||
|
version: ^0.0.1
|
||||||
- package: github.com/spf13/viper
|
- package: github.com/spf13/viper
|
||||||
|
version: ^1.0.0
|
||||||
- package: github.com/syndtr/goleveldb
|
- package: github.com/syndtr/goleveldb
|
||||||
subpackages:
|
subpackages:
|
||||||
- leveldb
|
- leveldb
|
||||||
- leveldb/errors
|
- leveldb/errors
|
||||||
|
- leveldb/iterator
|
||||||
- leveldb/opt
|
- leveldb/opt
|
||||||
- package: github.com/tendermint/go-wire
|
- package: github.com/tendermint/go-wire
|
||||||
subpackages:
|
version: develop
|
||||||
- data
|
|
||||||
- data/base58
|
|
||||||
- package: github.com/tendermint/log15
|
|
||||||
- package: golang.org/x/crypto
|
- package: golang.org/x/crypto
|
||||||
subpackages:
|
subpackages:
|
||||||
- ripemd160
|
- ripemd160
|
||||||
testImport:
|
testImport:
|
||||||
- package: github.com/stretchr/testify
|
- package: github.com/stretchr/testify
|
||||||
|
version: ^1.2.1
|
||||||
subpackages:
|
subpackages:
|
||||||
- assert
|
- assert
|
||||||
- require
|
- require
|
||||||
|
|
|
@ -18,25 +18,25 @@ func NewSimpleMap() *SimpleMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm *SimpleMap) Set(key string, value interface{}) {
|
func (sm *SimpleMap) Set(key string, value Hasher) {
|
||||||
sm.sorted = false
|
sm.sorted = false
|
||||||
|
|
||||||
// Is value Hashable?
|
// Hash the key to blind it... why not?
|
||||||
var vBytes []byte
|
khash := SimpleHashFromBytes([]byte(key))
|
||||||
if hashable, ok := value.(Hashable); ok {
|
|
||||||
vBytes = hashable.Hash()
|
// And the value is hashed too, so you can
|
||||||
} else {
|
// check for equality with a cached value (say)
|
||||||
vBytes = wire.BinaryBytes(value)
|
// and make a determination to fetch or not.
|
||||||
}
|
vhash := value.Hash()
|
||||||
|
|
||||||
sm.kvs = append(sm.kvs, cmn.KVPair{
|
sm.kvs = append(sm.kvs, cmn.KVPair{
|
||||||
Key: []byte(key),
|
Key: khash,
|
||||||
Value: vBytes,
|
Value: vhash,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merkle root hash of items sorted by key.
|
// Merkle root hash of items sorted by key
|
||||||
// NOTE: Behavior is undefined when key is duplicate.
|
// (UNSTABLE: and by value too if duplicate key).
|
||||||
func (sm *SimpleMap) Hash() []byte {
|
func (sm *SimpleMap) Hash() []byte {
|
||||||
sm.Sort()
|
sm.Sort()
|
||||||
return hashKVPairs(sm.kvs)
|
return hashKVPairs(sm.kvs)
|
||||||
|
@ -51,7 +51,6 @@ func (sm *SimpleMap) Sort() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a copy of sorted KVPairs.
|
// Returns a copy of sorted KVPairs.
|
||||||
// CONTRACT: The returned slice must not be mutated.
|
|
||||||
func (sm *SimpleMap) KVPairs() cmn.KVPairs {
|
func (sm *SimpleMap) KVPairs() cmn.KVPairs {
|
||||||
sm.Sort()
|
sm.Sort()
|
||||||
kvs := make(cmn.KVPairs, len(sm.kvs))
|
kvs := make(cmn.KVPairs, len(sm.kvs))
|
||||||
|
@ -65,22 +64,22 @@ func (sm *SimpleMap) KVPairs() cmn.KVPairs {
|
||||||
type kvPair cmn.KVPair
|
type kvPair cmn.KVPair
|
||||||
|
|
||||||
func (kv kvPair) Hash() []byte {
|
func (kv kvPair) Hash() []byte {
|
||||||
hasher, n, err := ripemd160.New(), new(int), new(error)
|
hasher := ripemd160.New()
|
||||||
wire.WriteByteSlice(kv.Key, hasher, n, err)
|
err := wire.EncodeByteSlice(hasher, kv.Key)
|
||||||
if *err != nil {
|
if err != nil {
|
||||||
panic(*err)
|
panic(err)
|
||||||
}
|
}
|
||||||
wire.WriteByteSlice(kv.Value, hasher, n, err)
|
err = wire.EncodeByteSlice(hasher, kv.Value)
|
||||||
if *err != nil {
|
if err != nil {
|
||||||
panic(*err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return hasher.Sum(nil)
|
return hasher.Sum(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hashKVPairs(kvs cmn.KVPairs) []byte {
|
func hashKVPairs(kvs cmn.KVPairs) []byte {
|
||||||
kvsH := make([]Hashable, 0, len(kvs))
|
kvsH := make([]Hasher, 0, len(kvs))
|
||||||
for _, kvp := range kvs {
|
for _, kvp := range kvs {
|
||||||
kvsH = append(kvsH, kvPair(kvp))
|
kvsH = append(kvsH, kvPair(kvp))
|
||||||
}
|
}
|
||||||
return SimpleHashFromHashables(kvsH)
|
return SimpleHashFromHashers(kvsH)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,41 +7,47 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type strHasher string
|
||||||
|
|
||||||
|
func (str strHasher) Hash() []byte {
|
||||||
|
return SimpleHashFromBytes([]byte(str))
|
||||||
|
}
|
||||||
|
|
||||||
func TestSimpleMap(t *testing.T) {
|
func TestSimpleMap(t *testing.T) {
|
||||||
{
|
{
|
||||||
db := NewSimpleMap()
|
db := NewSimpleMap()
|
||||||
db.Set("key1", "value1")
|
db.Set("key1", strHasher("value1"))
|
||||||
assert.Equal(t, "3bb53f017d2f5b4f144692aa829a5c245ac2b123", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
assert.Equal(t, "19618304d1ad2635c4238bce87f72331b22a11a1", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
db := NewSimpleMap()
|
db := NewSimpleMap()
|
||||||
db.Set("key1", "value2")
|
db.Set("key1", strHasher("value2"))
|
||||||
assert.Equal(t, "14a68db29e3f930ffaafeff5e07c17a439384f39", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
assert.Equal(t, "51cb96d3d41e1714def72eb4bacc211de9ddf284", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
db := NewSimpleMap()
|
db := NewSimpleMap()
|
||||||
db.Set("key1", "value1")
|
db.Set("key1", strHasher("value1"))
|
||||||
db.Set("key2", "value2")
|
db.Set("key2", strHasher("value2"))
|
||||||
assert.Equal(t, "275c6367f4be335f9c482b6ef72e49c84e3f8bda", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
assert.Equal(t, "58a0a99d5019fdcad4bcf55942e833b2dfab9421", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
db := NewSimpleMap()
|
db := NewSimpleMap()
|
||||||
db.Set("key2", "value2") // NOTE: out of order
|
db.Set("key2", strHasher("value2")) // NOTE: out of order
|
||||||
db.Set("key1", "value1")
|
db.Set("key1", strHasher("value1"))
|
||||||
assert.Equal(t, "275c6367f4be335f9c482b6ef72e49c84e3f8bda", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
assert.Equal(t, "58a0a99d5019fdcad4bcf55942e833b2dfab9421", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
db := NewSimpleMap()
|
db := NewSimpleMap()
|
||||||
db.Set("key1", "value1")
|
db.Set("key1", strHasher("value1"))
|
||||||
db.Set("key2", "value2")
|
db.Set("key2", strHasher("value2"))
|
||||||
db.Set("key3", "value3")
|
db.Set("key3", strHasher("value3"))
|
||||||
assert.Equal(t, "48d60701cb4c96916f68a958b3368205ebe3809b", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
assert.Equal(t, "cb56db3c7993e977f4c2789559ae3e5e468a6e9b", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
db := NewSimpleMap()
|
db := NewSimpleMap()
|
||||||
db.Set("key2", "value2") // NOTE: out of order
|
db.Set("key2", strHasher("value2")) // NOTE: out of order
|
||||||
db.Set("key1", "value1")
|
db.Set("key1", strHasher("value1"))
|
||||||
db.Set("key3", "value3")
|
db.Set("key3", strHasher("value3"))
|
||||||
assert.Equal(t, "48d60701cb4c96916f68a958b3368205ebe3809b", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
assert.Equal(t, "cb56db3c7993e977f4c2789559ae3e5e468a6e9b", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@ type SimpleProof struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// proofs[0] is the proof for items[0].
|
// proofs[0] is the proof for items[0].
|
||||||
func SimpleProofsFromHashables(items []Hashable) (rootHash []byte, proofs []*SimpleProof) {
|
func SimpleProofsFromHashers(items []Hasher) (rootHash []byte, proofs []*SimpleProof) {
|
||||||
trails, rootSPN := trailsFromHashables(items)
|
trails, rootSPN := trailsFromHashers(items)
|
||||||
rootHash = rootSPN.Hash
|
rootHash = rootSPN.Hash
|
||||||
proofs = make([]*SimpleProof, len(items))
|
proofs = make([]*SimpleProof, len(items))
|
||||||
for i, trail := range trails {
|
for i, trail := range trails {
|
||||||
|
@ -109,7 +109,7 @@ func (spn *SimpleProofNode) FlattenAunts() [][]byte {
|
||||||
|
|
||||||
// trails[0].Hash is the leaf hash for items[0].
|
// trails[0].Hash is the leaf hash for items[0].
|
||||||
// trails[i].Parent.Parent....Parent == root for all i.
|
// trails[i].Parent.Parent....Parent == root for all i.
|
||||||
func trailsFromHashables(items []Hashable) (trails []*SimpleProofNode, root *SimpleProofNode) {
|
func trailsFromHashers(items []Hasher) (trails []*SimpleProofNode, root *SimpleProofNode) {
|
||||||
// Recursive impl.
|
// Recursive impl.
|
||||||
switch len(items) {
|
switch len(items) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -118,8 +118,8 @@ func trailsFromHashables(items []Hashable) (trails []*SimpleProofNode, root *Sim
|
||||||
trail := &SimpleProofNode{items[0].Hash(), nil, nil, nil}
|
trail := &SimpleProofNode{items[0].Hash(), nil, nil, nil}
|
||||||
return []*SimpleProofNode{trail}, trail
|
return []*SimpleProofNode{trail}, trail
|
||||||
default:
|
default:
|
||||||
lefts, leftRoot := trailsFromHashables(items[:(len(items)+1)/2])
|
lefts, leftRoot := trailsFromHashers(items[:(len(items)+1)/2])
|
||||||
rights, rightRoot := trailsFromHashables(items[(len(items)+1)/2:])
|
rights, rightRoot := trailsFromHashers(items[(len(items)+1)/2:])
|
||||||
rootHash := SimpleHashFromTwoHashes(leftRoot.Hash, rightRoot.Hash)
|
rootHash := SimpleHashFromTwoHashes(leftRoot.Hash, rightRoot.Hash)
|
||||||
root := &SimpleProofNode{rootHash, nil, nil, nil}
|
root := &SimpleProofNode{rootHash, nil, nil, nil}
|
||||||
leftRoot.Parent = root
|
leftRoot.Parent = root
|
||||||
|
|
|
@ -28,17 +28,14 @@ import (
|
||||||
"golang.org/x/crypto/ripemd160"
|
"golang.org/x/crypto/ripemd160"
|
||||||
|
|
||||||
"github.com/tendermint/go-wire"
|
"github.com/tendermint/go-wire"
|
||||||
. "github.com/tendermint/tmlibs/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func SimpleHashFromTwoHashes(left []byte, right []byte) []byte {
|
func SimpleHashFromTwoHashes(left []byte, right []byte) []byte {
|
||||||
var n int
|
|
||||||
var err error
|
|
||||||
var hasher = ripemd160.New()
|
var hasher = ripemd160.New()
|
||||||
wire.WriteByteSlice(left, hasher, &n, &err)
|
err := wire.EncodeByteSlice(hasher, left)
|
||||||
wire.WriteByteSlice(right, hasher, &n, &err)
|
err = wire.EncodeByteSlice(hasher, right)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
PanicCrisis(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return hasher.Sum(nil)
|
return hasher.Sum(nil)
|
||||||
}
|
}
|
||||||
|
@ -57,27 +54,25 @@ func SimpleHashFromHashes(hashes [][]byte) []byte {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience for SimpleHashFromHashes.
|
// NOTE: Do not implement this, use SimpleHashFromByteslices instead.
|
||||||
func SimpleHashFromBinaries(items []interface{}) []byte {
|
// type Byteser interface { Bytes() []byte }
|
||||||
hashes := make([][]byte, len(items))
|
// func SimpleHashFromBytesers(items []Byteser) []byte { ... }
|
||||||
for i, item := range items {
|
|
||||||
hashes[i] = SimpleHashFromBinary(item)
|
func SimpleHashFromByteslices(bzs [][]byte) []byte {
|
||||||
|
hashes := make([][]byte, len(bzs))
|
||||||
|
for i, bz := range bzs {
|
||||||
|
hashes[i] = SimpleHashFromBytes(bz)
|
||||||
}
|
}
|
||||||
return SimpleHashFromHashes(hashes)
|
return SimpleHashFromHashes(hashes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// General Convenience
|
func SimpleHashFromBytes(bz []byte) []byte {
|
||||||
func SimpleHashFromBinary(item interface{}) []byte {
|
hasher := ripemd160.New()
|
||||||
hasher, n, err := ripemd160.New(), new(int), new(error)
|
hasher.Write(bz)
|
||||||
wire.WriteBinary(item, hasher, n, err)
|
|
||||||
if *err != nil {
|
|
||||||
PanicCrisis(err)
|
|
||||||
}
|
|
||||||
return hasher.Sum(nil)
|
return hasher.Sum(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience for SimpleHashFromHashes.
|
func SimpleHashFromHashers(items []Hasher) []byte {
|
||||||
func SimpleHashFromHashables(items []Hashable) []byte {
|
|
||||||
hashes := make([][]byte, len(items))
|
hashes := make([][]byte, len(items))
|
||||||
for i, item := range items {
|
for i, item := range items {
|
||||||
hash := item.Hash()
|
hash := item.Hash()
|
||||||
|
@ -86,8 +81,7 @@ func SimpleHashFromHashables(items []Hashable) []byte {
|
||||||
return SimpleHashFromHashes(hashes)
|
return SimpleHashFromHashes(hashes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience for SimpleHashFromHashes.
|
func SimpleHashFromMap(m map[string]Hasher) []byte {
|
||||||
func SimpleHashFromMap(m map[string]interface{}) []byte {
|
|
||||||
sm := NewSimpleMap()
|
sm := NewSimpleMap()
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
sm.Set(k, v)
|
sm.Set(k, v)
|
||||||
|
|
|
@ -19,14 +19,14 @@ func TestSimpleProof(t *testing.T) {
|
||||||
|
|
||||||
total := 100
|
total := 100
|
||||||
|
|
||||||
items := make([]Hashable, total)
|
items := make([]Hasher, total)
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
items[i] = testItem(RandBytes(32))
|
items[i] = testItem(RandBytes(32))
|
||||||
}
|
}
|
||||||
|
|
||||||
rootHash := SimpleHashFromHashables(items)
|
rootHash := SimpleHashFromHashers(items)
|
||||||
|
|
||||||
rootHash2, proofs := SimpleProofsFromHashables(items)
|
rootHash2, proofs := SimpleProofsFromHashers(items)
|
||||||
|
|
||||||
if !bytes.Equal(rootHash, rootHash2) {
|
if !bytes.Equal(rootHash, rootHash2) {
|
||||||
t.Errorf("Unmatched root hashes: %X vs %X", rootHash, rootHash2)
|
t.Errorf("Unmatched root hashes: %X vs %X", rootHash, rootHash2)
|
||||||
|
|
|
@ -18,6 +18,6 @@ type Tree interface {
|
||||||
IterateRange(start []byte, end []byte, ascending bool, fx func(key []byte, value []byte) (stop bool)) (stopped bool)
|
IterateRange(start []byte, end []byte, ascending bool, fx func(key []byte, value []byte) (stop bool)) (stopped bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Hashable interface {
|
type Hasher interface {
|
||||||
Hash() []byte
|
Hash() []byte
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue