How to create a hash linked Merkle tree_ IPLD dag in jsIPFS

In the js-ipfs examples repository I found this piece of code showing how to assemble a merkle-tree or so called ‘dag’.

const myData = {
    name: 'David',
    likes: ['js-ipfs', 'icecream', 'steak']
  }

  ipfs.dag.put(myData, { format: 'dag-cbor', hashAlg: 'sha2-256' }, (err, cid) => {
    if (err) {
      throw err
    }
    console.log(cid.toBaseEncodedString())
    // should print:
    //   zdpuAzZSktMhXjJu5zneSFrg9ue5rLXKAMC9KLigqhQ7Q7vRm
})

Can anyone explain how to -in stead of text- put files in the dag?
And maybe also how to insert a hash, in case the file is already available on IPFS, but is just needed in an update of a merkle-tree.

I hope someone can share some light on how this should be coded,
Thanx :star_struck:

If you’ve files, you’d normally would use the Files API.

You can’t really put files into the DAG. You would read the file into a Buffer and store that. Please be aware that there is size limitations when you want to transfer the data to another peer (one DAG block can only be about 4MB).

Thank you for the reply. I tried to read through the specs, if I understand it correctly I have to upload the files to IPFS with the Files API and then construct a DAGnode with Object API…

With this code I should be able to simply add an existing file to a DAGnode. The files that are used have first been uploaded with the new webUI :tada: , the first hash is a empty folder and the second one is an image file:

ipfs.object.patch.addLink('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', ('just-an-image', 113, 'QmVnJcCyCTJeAdUrN9nAUf45gVqfshk2HdLPcTSiWdQAZB')
, (err, newNode) => {
  if (err) {
    console.log('Hmmm not working')
    throw err
  }
console.log('Hello, the file should have been added to the DAGnode!')
console.log(newNode)

  // newNode is node with the added link
})

But if I paste this in the web console (site with ipfs running in serviceworker), it gives back :
undefined
Hmmm not working

Do you have another tip for me to get further!? :hatched_chick:

I’m not sure why you want to operate on the DAG level. Why not using the File API to add the new file?

The structure of the Merkle DAG should enable me to access files by there name (just after the most up to date hash of the root), like:
QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn/just-an-image
(note: I understand this well, the root hash in my example is going to have another ‘updated’ hash: newNode)

This root hash can give me a specific set of files. In my project, this set or dag is the users account, with content addressed by unanimous names (every user account has the same basic names for the content to be displayed). The use of these nice Merkle dag features of IPFS, with it’s version control and/or verifiability, suits my project perfectly.

If I understand the files API correctly, it enables me to add a file to IPFS on itself, so not in a file structure (addressable by name, and within a directory) right?

It’s kind of the other way round. The point of IPFS is to have a file/directory structures. If you look at the Files API add command, you can see that you can directly add a file via:

ipfs.files.add({
  path: '/tmp/myfile.txt', // The file path
  content: <data> // A Buffer, Readable Stream or Pull Stream with the contents of the file
})

So this file directory structure (with File API) is possible, but only with a single file right? So I can’t add multiple files to the same folder. Or don’t I understand this correctly?

You can add multiple files, see the Files API documentation for more information: https://github.com/ipfs/interface-ipfs-core/blob/8483084c89117d0f416e10761102f33ef1c948f9/SPEC/FILES.md#filesadd

Nice! I see add-readable-stream with an example, I wonder though how to get the hash of the root directory back. And is it possible in this code to put a file to the directory, by it’s hash (so when the file is already available on IPFS)?

 it('should add readable stream of valid files and dirs', function (done) {
      const content = (name) => ({
        path: `test-folder/${name}`,
        content: fixtures.directory.files[name]
      })

      const emptyDir = (name) => ({ path: `test-folder/${name}` })

      const files = [
        content('pp.txt'),
        content('holmes.txt'),
        content('jungle.txt'),
        content('alice.txt'),
        emptyDir('empty-folder'),
        content('files/hello.txt'),
        content('files/ipfs.txt'),
        emptyDir('files/empty')
      ]

      const stream = ipfs.files.addReadableStream()

      stream.on('error', (err) => {
        expect(err).to.not.exist()
      })

      stream.on('data', (file) => {
        if (file.path === 'test-folder') {
          expect(file.hash).to.equal(fixtures.directory.cid)
          done()
        }
      })

      files.forEach((file) => stream.write(file))
      stream.end()
    })
  })
}

Thank you for the reply Volker, I am going to try to get this working with compiling… browserify, simpleserver and all that. :owl:

I don’t know if there’s some direct way to get the root hash. But you could do a files.ls() in the root once the file is added.

an easy way to get the root hash : ipfs files stat /

This is the answer I found working for this issue: