Skip to content

Instantly share code, notes, and snippets.

@eduardonunesp
Last active July 21, 2021 20:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eduardonunesp/367df320957e75ef43f1002a68cbec49 to your computer and use it in GitHub Desktop.
Save eduardonunesp/367df320957e75ef43f1002a68cbec49 to your computer and use it in GitHub Desktop.
HAMT Container test
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"
)
// HAMTContainer is just a way to put together all HAMT needed structures in norder to create a ipld.Node
// representing the HAMT after the builder runs
type HAMTContainer struct {
storage Memory
linkSystem ipld.LinkSystem
linkProto ipld.LinkPrototype
assembler ipld.MapAssembler
node ipld.Node
link ipld.Link
cid cid.Cid
builder *hamt.Builder
isBuild bool
}
// NewHAMTContainer creates a new HAMTContainer
func NewHAMTContainer() (*HAMTContainer, error) {
var err error
newHAMTContainer := HAMTContainer{
storage: Memory{},
}
// Sets the link system
newHAMTContainer.linkSystem = cidlink.DefaultLinkSystem()
newHAMTContainer.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.
}}
// Sets the writer and reader interfaces for the link system
newHAMTContainer.linkSystem.StorageWriteOpener = newHAMTContainer.storage.OpenWrite
newHAMTContainer.linkSystem.StorageReadOpener = newHAMTContainer.storage.OpenRead
// Creates the builder for the HAMT
newHAMTContainer.builder = hamt.NewBuilder(hamt.Prototype{BitWidth: 3, BucketSize: 64}).
WithLinking(newHAMTContainer.linkSystem, newHAMTContainer.linkProto)
// Sets the assembler to build the k/v for the map structure
newHAMTContainer.assembler, err = newHAMTContainer.builder.BeginMap(0)
if err != nil {
return nil, err
}
return &newHAMTContainer, nil
}
// GetCID will return the cid.Cid for the ipld.Node
// Or it will return an error if the ipld.Node for the HAMT isn't built
func (hc HAMTContainer) GetCID() (cid.Cid, error) {
if !hc.isBuild {
return cid.Cid{}, fmt.Errorf("HAMT not ready, build first")
}
return hc.cid, nil
}
// GetLink will return the ipld.Link for the ipld.Node
// Or it will return an error if the ipld.Node for the HAMT isn't built
func (hc HAMTContainer) GetLink() (ipld.Link, error) {
if !hc.isBuild {
return nil, fmt.Errorf("HAMT not ready, build first")
}
return hc.link, nil
}
// SetKV adds a new k/v content for the HAMT
// For string values it will add k/v pair of strings
// For ipld.Link values it will add string key and a link for another HAMT structure as value
func (hc HAMTContainer) SetKV(key string, value interface{}) error {
err := hc.assembler.AssembleKey().AssignString(key)
switch v := value.(type) {
case string:
err = hc.assembler.AssembleValue().AssignString(v)
if err != nil {
return err
}
case ipld.Link:
err = hc.assembler.AssembleValue().AssignLink(v)
if err != nil {
return err
}
default:
return fmt.Errorf("Invalid type for value")
}
return nil
}
// BuildHAMTNode will build the HAMT internal representation inside the container
// It will have Link, and ipld.Node representing the HAMT also CID for the root content
func (hc *HAMTContainer) BuildHAMTNode() error {
err := hc.assembler.Finish()
if err != nil {
return err
}
hc.node = hamt.Build(hc.builder)
link, err := hc.linkSystem.Store(
ipld.LinkContext{},
hc.linkProto,
hc.node,
)
if err != nil {
return err
}
hc.link = link
hc.cid, err = cid.Parse(link.String())
if err != nil {
return err
}
hc.isBuild = true
return err
}
func main() {
// Create the first HAMT
childHC, err := NewHAMTContainer()
errCheck(err)
// Set some k/v
err = childHC.SetKV("foo", "bar")
errCheck(err)
// Build the HAMT Node
err = childHC.BuildHAMTNode()
errCheck(err)
// Creates the parent HAMT
parentHC, err := NewHAMTContainer()
errCheck(err)
// Gets the link for the child HAMT
childHCLink, err := childHC.GetLink()
errCheck(err)
// Put the child HAMT as values of the parent HAMT
err = parentHC.SetKV("zar", childHCLink)
errCheck(err)
}
package main
import (
"bytes"
"fmt"
"io"
"github.com/ipld/go-ipld-prime"
)
// Memory is a simple in-memory storage for data indexed by ipld.Link.
// (It's little more than a map -- in fact, the map is exported,
// and you can poke it directly.)
//
// The OpenRead method conforms to ipld.BlockReadOpener,
// and the OpenWrite method conforms to ipld.BlockWriteOpener.
// Therefore it's easy to use in a LinkSystem like this:
//
// store := storage.Memory{}
// lsys.StorageReadOpener = (&store).OpenRead
// lsys.StorageWriteOpener = (&store).OpenWrite
//
// This storage is mostly expected to be used for testing and demos,
// and as an example of how you can implement and integrate your own storage systems.
type Memory struct {
Bag map[ipld.Link][]byte
}
func (store *Memory) beInitialized() {
if store.Bag != nil {
return
}
store.Bag = make(map[ipld.Link][]byte)
}
func (store *Memory) OpenRead(_ ipld.LinkContext, lnk ipld.Link) (io.Reader, error) {
store.beInitialized()
data, exists := store.Bag[lnk]
if !exists {
return nil, fmt.Errorf("404") // FIXME this needs a standard error type
}
return bytes.NewReader(data), nil
}
func (store *Memory) OpenWrite(_ ipld.LinkContext) (io.Writer, ipld.BlockWriteCommitter, error) {
store.beInitialized()
buf := bytes.Buffer{}
return &buf, func(lnk ipld.Link) error {
store.Bag[lnk] = buf.Bytes()
return nil
}, nil
}
package main
func errCheck(err error) {
if err != nil {
panic(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment