在Go中实现Merkle-tree数据结构

时间:2014-08-25 23:38:04

标签: data-structures go

我目前正在尝试在Go中实现merkle-tree数据结构。基本上,我的最终目标是存储一小组结构化数据(最大10MB)并允许这个"数据库"与网络上分布的其他节点轻松同步(参见相关内容)。 我已经在Node中合理有效地实现了这一点,因为没有类型检查。这就是Go的问题,我想使用Go的编译时类型检查,尽管我也希望有一个库可以使用任何提供的树。

简而言之,我希望将结构用作merkle节点,并且我希望有一种嵌入所有类型的Merkle.Update()方法。我试图避免为每个结构编写Update()(尽管我知道这可能是唯一/最好的方法)。

我的想法是使用嵌入式类型:

//library
type Merkle struct {
    Initialised bool
    Container interface{} //in example this references foo
    Fields []reflect.Type
    //... other merkle state
}
//Merkle methods... Update()... etc...

//userland
type Foo struct {
    Merkle
    A int
    B bool
    C string
    D map[string]*Bazz
    E []*Bar
}

type Bazz struct {
    Merkle
    S int
    T int
    U int
}

type Bar struct {
    Merkle
    X int
    Y int
    Z int
}

在此示例中,Foo将是根,其中包含BazzBar s。可以通过反映类型来推断这种关系。问题是用法:

foo := &Foo{
    A: 42,
    B: true,
    C: "foo",
    D: map[string]*Bazz{
        "b1": &Bazz{},
        "b2": &Bazz{},
    },
    E: []*Bar{
        &Bar{},
        &Bar{},
        &Bar{},
    },
}

merkle.Init(foo)
foo.Hash //Initial hash => abc...

foo.A = 35
foo.E = append(foo.E, &Bar{})

foo.Update()
foo.Hash //Updated hash => def...

我认为我们需要merkle.Init(foo),因为foo.Init()实际上是foo.Merkle.Init(),并且无法反映foo。未初始化的BarBazz可由父foo.Update()检测并初始化。一些反思是可以接受的,因为正确性比目前的表现更重要。 另一个问题是,当我们Update()一个节点时,所有结构字段(子节点)也需要Update() d(重新散列),因为我们不确定改变了什么。我们可以foo.SetInt("A", 35)来实现自动更新,但是我们失去了编译时类型检查。

这会被认为是惯用的Go吗?如果没有,怎么可以改善?任何人都可以想到一种替代方法,通过简洁的数据集比较(通过网络进行有效的delta传输)将数据集存储在内存中(用于快速读取)? 编辑:还有一个元问题:在哪里问这类问题最好的地方,StackOverflow,Reddit还是疯狂?最初发布在reddit上没有回答:(

3 个答案:

答案 0 :(得分:4)

有些目标似乎是:

  • 哈希任何东西 - 通过散列很多东西使其易于使用
  • 缓存哈希 - 让更新重新发布他们需要的内容
  • 是惯用的 - 适合其他Go代码

我认为你可以像内置encoding/gobencoding/json这样的序列化工具那样大致攻击散列,这是三管齐下的:如果类型实现它,则使用特殊方法(对于MarshalJSON)的JSON,使用类型开关用于基本类型,并使用反射回退到令人讨厌的默认情况。这是一个API草图,它为哈希缓存提供帮助,并允许类型实现Hash或不实现:

package merkle

type HashVal uint64

const MissingHash HashVal = 0

// Hasher provides a custom hash implementation for a type. Not
// everything needs to implement it, but doing so can speed
// updates.
type Hasher interface {
    Hash() HashVal
}

// HashCacher is the interface for items that cache a hash value.
// Normally implemented by embedding HashCache.
type HashCacher interface {
    CachedHash() *HashVal
}

// HashCache implements HashCacher; it's meant to be embedded in your
// structs to make updating hash trees more efficient.
type HashCache struct {
    h HashVal
}

// CachedHash implements HashCacher.
func (h *HashCache) CachedHash() *HashVal {
    return &h.h
}

// Hash returns something's hash, using a cached hash or Hash() method if
// available.
func Hash(i interface{}) HashVal {
    if hashCacher, ok := i.(HashCacher); ok {
        if cached := *hashCacher.CachedHash(); cached != MissingHash {
            return cached
        }
    }

    switch i := i.(type) {
    case Hasher:
        return i.Hash()
    case uint64:
        return HashVal(i * 8675309) // or, you know, use a real hash
    case []byte:
        // CRC the bytes, say
        return 0xdeadbeef
    default:
        return 0xdeadbeef
        // terrible slow recursive case using reflection
        // like: iterate fields using reflect, then hash each
    }

    // instead of panic()ing here, you could live a little
    // dangerously and declare that changes to unhashable
    // types don't invalidate the tree
    panic("unhashable type passed to Hash()")
}

// Item is a node in the Merkle tree, which must know how to find its
// parent Item (the root node should return nil) and should usually
// embed HashCache for efficient updates. To avoid using reflection,
// Items might benefit from being Hashers as well.
type Item interface {
    Parent() Item
    HashCacher
}

// Update updates the chain of items between i and the root, given the
// leaf node that may have been changed.
func Update(i Item) {
    for i != nil {
        cached := i.CachedHash()
        *cached = MissingHash // invalidate
        *cached = Hash(i)
        i = i.Parent()
    }
}

答案 1 :(得分:1)

Go没有与其他语言一样的继承。

“父母”无法修改子项中的项目,您必须在每个结构上实施Update然后在其中开展业务,然后让它调用父项Update

func (b *Bar) Update() {
    b.Merkle.Update()
    //do stuff related to b and b.Merkle
    //stuff
}

func (f *Foo) Update() {
    f.Merkle.Update()
    for _, b := range f.E {
        b.Update()
    }
    //etc
}

我认为你必须以不同的方式重新实现你的树。

请在下次提供可测试的案例。

答案 2 :(得分:1)

您是否看过https://github.com/xsleonard/go-merkle,它允许您创建二进制merkle树。您可以在数据末尾附加一个类型字节以识别它。