【问题标题】:Implementing a Merkle-tree data structure in Go在 Go 中实现 Merkle 树数据结构
【发布时间】:2014-08-26 14:06:53
【问题描述】:

我目前正在尝试在 Go 中实现一个默克尔树数据结构。基本上,我的最终目标是存储一小组结构化数据(最大 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 将是根,其中将包含 Bazzs 和 Bars。这种关系可以通过反映类型来推断。问题是用法:

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。未初始化的Bars 和Bazzs 可以被父foo.Update() 检测和初始化。一些反思是可以接受的,因为目前正确性比性能更重要。 另一个问题是,当我们Update() 一个节点时,所有结构字段(子节点)也需要为Update()d(重新散列),因为我们不确定发生了什么变化。我们可以使用foo.SetInt("A", 35) 来实现自动更新,但是我们会丢失编译时类型检查。

这会被认为是惯用的 Go 吗?如果没有,如何改进?谁能想到另一种方法来将数据集存储在内存中(用于快速读取)并进行简洁的数据集比较(用于通过网络进行有效的增量传输)? 编辑:还有一个元问题:问这类问题的最佳地点在哪里,StackOverflow、Reddit 还是 go-nuts?最初发布在reddit 没有答案:(

【问题讨论】:

  • 你需要在某个地方提供完整的代码,play.golang.com 或者如果它是多个文件,把它放在 github 上。
  • 好的,我会发布我所拥有的(和一个测试用例),尽管它主要只是 API 原型设计。在我承诺实施这一点之前,我想我会问我是否朝着正确的方向前进。

标签: data-structures go


【解决方案1】:

一些目标看起来像:

  • 散列任何东西——通过散列开箱即用的许多东西使其易于使用
  • 缓存散列 -- 让更新只是重新散列他们需要的东西
  • 惯用的——在其他 Go 代码中非常适合

我认为你可以大致像内置的encoding/gobencoding/json 这样的序列化工具那样攻击任何散列,这是三管齐下的:如果类型实现了它,请使用特殊方法(对于 JSON 是 @ 987654324@),对基本类型使用类型开关,并使用反射回退到令人讨厌的默认情况。这是一个 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()
    }
}

【讨论】:

  • 太棒了!谢谢!将这些想法与我所拥有的结合起来并回复您:)
  • 我意识到我最初的代码不会真正使直到树根的所有哈希都失效,所以我改变了它:现在Hash 将使用任何非零缓存值(没有标志),但Update 将需要重新计算的已更改叶子和根之间的哈希值归零。还修复了 merkle.Hash 实际上没有使用类型的 Hash 方法(!)。
  • case Hasher: return i.Hash() 我确实抓住了那个 :) 这需要我为每个结构编写一个 Hash 方法,尽管我认为这仍然比在每次更新时使用反射更好,而且确实如此感觉更像围棋。接受:)
  • 再次感谢您的帮助,这是我目前所得到的:github.com/lumanetworks/merkle
【解决方案2】:

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
}

我认为您将不得不以不同的方式重新实现您的树。

另外请下次提供可测试用例。

【讨论】:

  • 对不起,我应该进一步澄清我的想法。见编辑。反射的字段存储在 Merkle 结构中。 merkle.Init(foo) 的目的是提取字段。因此,我只需要Merkle.Update()(希望如此)。
  • 除非您在 Merkle 结构中使用指针,并且 .Init(foo) 将这些指针分配给您可能使用的每个字段,否则不会起作用。
  • 这是我目前得到的github.com/lumanetworks/merkle 虽然我想我会选择@twotwotwo 的解决方案
【解决方案3】:

你见过https://github.com/xsleonard/go-merkle,它可以让你创建一个二叉默克尔树。您可以在数据末尾附加一个类型字节来识别它。

【讨论】:

  • 我有,但我希望使用 Go 结构来获得编译时类型检查,并避免在每次读写时编组和解组字节。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-07
  • 1970-01-01
  • 2012-12-21
  • 1970-01-01
  • 2010-12-28
  • 1970-01-01
相关资源
最近更新 更多