ipld / go-ipld-adl-hamt

Other
4 stars 4 forks source link

Linking sub HAMT or buckets together (question) #25

Closed eduardonunesp closed 3 years ago

eduardonunesp commented 3 years ago

What is the properly way to link sub HAMT structures, let's say the structure needs to be nested like the following idea (based in the following json, let's pretend that each object is a bucket)

{
  "root-bucket": {
    "foo": "bar",
    "zoo": "zar",
    "nested-bucket": {
      "rab": "oof",
      "life": "42"
    }
}

The following code tries to create a main HAMT and nested HAMT but it failed with an error:

package main

import (
    "fmt"

    "github.com/ipfs/go-cid"
    hamt "github.com/ipld/go-ipld-adl-hamt"
    ipld "github.com/ipld/go-ipld-prime"
    _ "github.com/ipld/go-ipld-prime/codec/dagcbor"
    cidlink "github.com/ipld/go-ipld-prime/linking/cid"
    "github.com/multiformats/go-multicodec"
)

func errCheck(err error) {
    if err != nil {
        panic(err)
    }
}

// Storage based on memory example https://github.com/ipld/go-ipld-prime/blob/master/storage/memory.go
var storage Memory

func createBuilder() *hamt.Builder {
    linkSystem := cidlink.DefaultLinkSystem()

    linkProto := cidlink.LinkPrototype{Prefix: cid.Prefix{
        Version:  1, // Usually '1'.
        Codec:    uint64(multicodec.DagCbor),
        MhType:   uint64(multicodec.Sha2_512),
        MhLength: 64, // sha2-512 hash has a 64-byte sum.
    }}

    storage = Memory{}

    linkSystem.StorageWriteOpener = storage.OpenWrite
    linkSystem.StorageReadOpener = storage.OpenRead

    builder := hamt.NewBuilder(hamt.Prototype{BitWidth: 3, BucketSize: 64}).
        WithLinking(linkSystem, linkProto)

    return builder
}

func createMapNode(builder *hamt.Builder, key string, value interface{}) (ipld.Node, error) {
    assembler, err := builder.BeginMap(0)
    if err != nil {
        return nil, err
    }

    err = assembler.AssembleKey().AssignString(key)
    if err != nil {
        return nil, err
    }

    switch v := value.(type) {
    case string:
        err = assembler.AssembleValue().AssignString(v)
        if err != nil {
            return nil, err
        }
    case ipld.Link:
        err = assembler.AssembleValue().AssignLink(v)
        if err != nil {
            return nil, err
        }
    case ipld.Node:
        err = assembler.AssembleValue().AssignNode(v)
        if err != nil {
            return nil, err
        }
    default:
        return nil, fmt.Errorf("Invalid type for value")
    }

    err = assembler.Finish()
    if err != nil {
        return nil, err
    }

    return hamt.Build(builder), nil
}

func main() {
    builder := createBuilder()
    nested_bucket, err := createMapNode(builder, "foo", "bar")
    errCheck(err)

    root_node, err := createMapNode(builder, "zoo", nested_bucket)
    errCheck(err)
}

The example above would produce something like:

{
  "root-bucket": {
    "nested-bucket": {
      "foo": "bar"
    }  
}
panic: interface conversion: ipld.Node is *hamt.Node, not *hamt._Any

goroutine 1 [running]:
github.com/ipld/go-ipld-adl-hamt.valueAssembler.AssignNode(0xc00007e420, 0x14c09a8, 0xc00018ea68, 0x14c09a8, 0xc00018ea68)
    /Users/eduardo/.go/pkg/mod/github.com/ipld/go-ipld-adl-hamt@v0.0.0-20210718132843-6e0f3e82a839/builder.go:276 +0x265
main.createMapNode(0xc00018ea50, 0x14402ec, 0x3, 0x1438260, 0xc00018ea68, 0x14c09a8, 0xc00018ea68, 0x0, 0x0)
    /Users/eduardo/Sandbox/go-hamt-test/main.go:66 +0x3a5
main.main()
    /Users/eduardo/Sandbox/go-hamt-test/main.go:87 +0xe5
eduardonunesp commented 3 years ago

I'm moving to connect HAMT's structures using ipld.Link with assembler.AssembleValue().AssignLink. But would be awesome if the AssignLink already does that heavy lifting, kind of would be easier to connect sub HAMTs

mvdan commented 3 years ago

AssignLink takes a link, so it can't possibly do anything smarter :) Note that I think AssignLink for map entry values is a TODO, but we can fix that fairly easily.

eduardonunesp commented 3 years ago

Ops, looks like there's an error when try to put ipld.Link as value for a HAMT, I created an example that can be found at https://gist.github.com/eduardonunesp/367df320957e75ef43f1002a68cbec49, the example just puts the ipld and hamt stuff and put inside a container to create a common procedure to create new hamt easier.

Error is

panic: func called on wrong kind: AssignLink called on a bytes node (kind: bytes), but only makes sense on link

goroutine 1 [running]:
main.errCheck(...)
    /Users/eduardo/Sandbox/go-hamt-test/utils.go:5
main.main()
    /Users/eduardo/Sandbox/go-hamt-test/main.go:136 +0x3dc  
mvdan commented 3 years ago

Yep that's the TODO I mentioned earlier, I'll send a fix tomorrow.

mvdan commented 3 years ago

Now at https://github.com/ipld/go-ipld-adl-hamt/pull/28.

eduardonunesp commented 3 years ago

Thanks @mvdan for the fixes 👍🏼