dwyl / learn-ipfs

🌠 Learn how to use IPFS to store data on the ultimate decentralised/distributed file system!
GNU General Public License v2.0
31 stars 2 forks source link

ProtoSchool #5

Open nelsonic opened 5 years ago

nelsonic commented 5 years ago

https://proto.school

image

thanks to @alanshaw for sharing this! image It's Next on my "to learn" list. 🤓

nelsonic commented 5 years ago

ipfs.dag.put is used in these basic examples but "DAG" is never explained. This is what you are looking for: https://github.com/ipfs/specs/tree/master/merkledag that in turn points to: https://github.com/ipld/specs/ It's a bit of a "rabbit hole" ... a few ASCII diagrams but no real clarity or real-world examples. 😕

Part 1 - Basic Content Addressing

Let's get started: image

https://proto.school/#/basics/01

image

https://proto.school/#/basics/02

image

https://proto.school/#/basics/03

image

Part 2 - Blog

image

https://proto.school/#/blog/01

Context: image

Solution: image

https://proto.school/#/blog/02

image

Exercise instructs learner to open Dev Tools: image

Latest Google Chrome Browser. Thousands of errors. not very confidence inspiring ... 🙄 Mostly the same errors: Uncaught Error: already piped, failed: WebSocket opening handshake was canceled and Uncaught Error: first argument must be a buffer

I'm not "phased" by these errors, but a beginner definitely would be.

CIDs in dev tools console before making updates: image

CIDs in dev tools console after adding the 'tags' array to each blog post: image

OK, this is a curiosity thing to understand how the cid function works (before I go read the code...) Question: does the CID depend on the order of the fields in the Object? For example: at present the two blog posts are:

  const treePostCid = await ipfs.dag.put({
    content: "trees",
    author: {"/": samCid.toBaseEncodedString()},
    tags: ['outdoor', 'hobby']
  })
  const computerPostCid = await ipfs.dag.put({
    content: "computers",
    author: {"/": natCid.toBaseEncodedString()},
    tags: ['hobby']
  })

And the corresponding CIDs are:

post about trees: zdpuAri55PR9iW239ahcbnfkFU2TVyD5iLmqEFmwY634KZAJV
post about computers: zdpuAqaHPSosSZFRPe7u5q3yNqgg4JuvrLaUJxGamNPLhWivX

What if we change the order of the keys and data to:

  const treePostCid = await ipfs.dag.put({
    author: {"/": samCid.toBaseEncodedString()},
    content: "trees",
    tags: ['hobby', 'outdoor']
  })
  const computerPostCid = await ipfs.dag.put({
    author: {"/": natCid.toBaseEncodedString()},
    content: "computers",
    tags: ['hobby']
  })

CIDs are identical if after re-ordering the fields and the "tags" in the first blog post. ✅

post about trees: zdpuB1fDHR9vZQfNT1knNBtbcSedwTC9n4smYskwoumWcXvu1
post about computers: zdpuAqaHPSosSZFRPe7u5q3yNqgg4JuvrLaUJxGamNPLhWivX

image

This is a good sign because the content hasn't changed, just the order of the key:value in the Object. 👍

https://proto.school/#/blog/03

Context: image

This time the order is a deciding factor in the resulting CID ... 🙄

Exercise: image

Solution:

  // Add your code here
  return [
    await ipfs.dag.put({
      tag: "hobby",
      posts: [
        {'/': treePostCid.toBaseEncodedString() },
        {'/': computerPostCid.toBaseEncodedString() }
      ]
    }),
    await ipfs.dag.put({
      tag: "outdoor",
      posts: [
        {'/': treePostCid.toBaseEncodedString() }
      ]
    })
  ]

Works: image

https://proto.school/#/blog/04

The lesson in which the "DAG" acronym is finally expanded: DAG (Directed Acyclic Graph)

image

Exercise: image

solution:

  // Add your code here
  const dogsPostCid = await ipfs.dag.put({
    content: "dogs",
    author: {"/": samCid.toBaseEncodedString()},
    tags: ["funny", "hobby"]
  })

  const hobbyTagCid = await ipfs.dag.put({
    tag: "hobby",
    posts: [
      {"/": treePostCid.toBaseEncodedString()},
      {"/": computerPostCid.toBaseEncodedString()},
      {"/": dogsPostCid.toBaseEncodedString()},
    ]
  })
  const funnyTagCid = await ipfs.dag.put({
    tag: "funny",
    posts: [
      {"/": dogsPostCid.toBaseEncodedString()}
    ]
  })

  return dogsPostCid;

image

https://proto.school/#/blog/05

Step 5: Add the code I added proactively above ...

image

Solution:

  const hobbyTagCid = await ipfs.dag.put({
    tag: "hobby",
    posts: [
      {"/": treePostCid.toBaseEncodedString()},
      {"/": computerPostCid.toBaseEncodedString()},
      {"/": dogPostCid.toBaseEncodedString()}
    ]
  })

  // Add your new code here and modify the tags above
    const funnyTagCid = await ipfs.dag.put({
    tag: "funny",
    posts: [
      {"/": dogPostCid.toBaseEncodedString()}
    ]
  })
  return [outdoorTagCid, hobbyTagCid, funnyTagCid]; // not clear *HOW* to return the 3 CIDs ...

image

https://proto.school/#/blog/06 - List posts chronologically with a chain of links

image

This exercise is pretty massive ...: image

Fail: (I don't understand why the prev should point to a post written by a different author...?) image

Solution: image

https://proto.school/#/blog/07 - Traverse through all posts

image

Hit a "brick wall" trying to implement the traversePosts function: image

Your function threw an error: TypeError: Cannot read property '/' of undefined. image

This is basically Game Over.

Kept trying: image

Calling it a day.

nelsonic commented 5 years ago

BEFORE: (trying too hard...)

// I added a second "list" parameter so I could write the function recursively ...
const traversePosts = async (cid, list = []) => {
  console.log('cid', cid)
  // Your code goes here
  list.push(new CID(cid.multihash));
  console.log(list);
  if(list.length === 3) { return list };
  let pcid, pCID;
  try {
    pcid = await ipfs.dag.get(cid);
    console.log('pcid', pcid)

    if(pcid && pcid.value && pcid.value.prev && pcid.value.prev['/']) {
      pCID = new CID((pcid).value.prev['/'])
      console.log('pCID', pCID)
      traversePosts(pCID, list);
    } else {
      console.log(list);
      console.log('return!!')
      return list;
    }

  } catch (e) {
    console.log('not a valid cid');
    console.log(list);
    console.log('return!!')
    return list;
  }
}

AFTER: using a while loop: (against my better judgement... but inspired by: https://github.com/ipfs-shipyard/proto.school/issues/68 )

const traversePosts = async (cid) => {
  let list = [];
  while (cid) {
    list.push(cid)
    const got = await ipfs.dag.get(cid)
    const prev = got.value.prev
    if (prev) {
      cid = new CID(prev['/'])
    } else {
      return list
    }
  }
}

image

Can't say that was a very beginner friendly experience ... 😕 Overall I still feel IPFS has potential ... But I still have lots of questions!

nelsonic commented 5 years ago

Opened issue: https://github.com/ipfs-shipyard/proto.school/issues/96 to inform creators of difficulty.

nelsonic commented 5 years ago

@alanshaw, where should I go next to continue my IPFS learning quest? 🤔

alanshaw commented 5 years ago

This is super good feedback for the protoschool team thank you for documenting ❤️

It's worth checking out the examples:

I can't remember if I told you about https://awesome.ipfs.io/

I have some fun videos you can watch:

nelsonic commented 5 years ago

@alanshaw thanks for the links and encouragement. 👍 Looks like I have this weekend's learning list/objective sorted. 🤓😉 I have a few practical questions regarding using IPFS for "real apps" 🤔❓ (further to our verbal discussion the other day...) Where is the most effective place to post them? e.g: https://stackoverflow.com/questions/tagged/ipfs ?

Hope all is well in JailMake Land. ☀️ Still gutted we didn't get to see @olizilla 😞 Love to all! ❤️

alanshaw commented 5 years ago

We have a discussion place here https://discuss.ipfs.io/

We are on IRC #ipfs on freenode

nelsonic commented 5 years ago

@alanshaw thanks looks like I have a bit of reading to do ... https://discuss.ipfs.io/top 👍

nelsonic commented 5 years ago

In light of @terichadbourne's comment on https://github.com/ProtoSchool/protoschool.github.io/issues/96#issuecomment-445295786 image

I'm going to go through the https://proto.school from scratch again with "shoshin" ... 🤓

nelsonic commented 5 years ago

No change to home page: https://proto.school image

https://proto.school/#/basics/01

image

image

https://proto.school/#/basics/02

image

First attempt: (faiulre) image

I already knew how to link one item of content to another:

const run = async () => {
  let cid = await ipfs.dag.put({test: 1})
  return await ipfs.dag.put({ bar: {"/": cid } })
}

So I was able to "pass" this exercise: image But the following code:

return await ipfs.dag.put({ bar: cid })

Also works: image

So we no longer need to have the {"/":cid} to link items! 🎉

https://proto.school/#/basics/03

image

Relevant line of code to make this exercise pass:

return ipfs.dag.get(cid2, '/bar/test')

image

... on to the next one ...

image

https://proto.school/#/blog/01

image

image

Solution: image

The way of linking to related content is much better! 🎉

https://proto.school/#/blog/02

image

image

Solution: image

https://proto.school/#/blog/03

image

image

Solution:

  // Add your code here
  const hobbyTags = await ipfs.dag.put({
    tag: "hobby",
    posts: [
      treePostCid,
      computerPostCid
    ]
  })
  const outdoorTags = await ipfs.dag.put({
    tag: "outdoor",
    posts: [
      treePostCid
    ]
  })
  return [hobbyTags, outdoorTags]

image

https://proto.school/#/blog/04

image

Solution:

  const dogPostCid = await ipfs.dag.put({
    content: "dogs",
    author: samCid,
    tags: ["funny", "hobby"]
  })

image

https://proto.school/#/blog/05

image

Solution:

  // Add your new code here and modify the tags above
  const funnyTagCid = await ipfs.dag.put({
    tag: "funny",
    posts: [ dogPostCid ]
  })

  return [funnyTagCid, hobbyTagCid, outdoorTagCid]

image

https://proto.school/#/blog/06

image

Pretty straightforward:

// Modify the blog posts below

  const treePostCid = await ipfs.dag.put({
    content: "trees",
    author: samCid,
    tags: ["outdoor", "hobby"]
  })
  const computerPostCid = await ipfs.dag.put({
    content: "computers",
    author: natCid,
    tags: ["hobby"],
    prev: treePostCid
  })
  const dogPostCid = await ipfs.dag.put({
    content: "dogs",
    author: samCid,
    tags: ["funny", "hobby"],
    prev: computerPostCid
  })

  const outdoorTagCid = await ipfs.dag.put({
    tag: "outdoor",
    posts: [ treePostCid ]
  })
  const hobbyTagCid = await ipfs.dag.put({
    tag: "hobby",
    posts: [ treePostCid, computerPostCid, dogPostCid ]
  })
  const funnyTagCid = await ipfs.dag.put({
    tag: "funny",
    posts: [ dogPostCid ]
  })
  return dogPostCid

image

https://proto.school/#/blog/07

image

Solution:

const traversePosts = async (cid) => {
  let list = [];
  while (cid) {
    list.push(cid);
    cid = (await ipfs.dag.get(cid)).value.prev
  }
  return list;
}

image

Second time round it was much easier. I don't know if a complete beginner would "reach for" a while loop ... But overall the way posts are linked is cleaner so I feel it's an improvement. 👍

terichadbourne commented 5 years ago

@nelsonic I'm glad you liked the new link API and found the instructions more clear this time!

A couple of questions out of curiosity if you have a sec:

nelsonic commented 5 years ago

@terichadbourne I did not notice the while loop copy ... 🙄 (pebkac...) image

It's definitely there, I just didn't see it. Other beginners will. ✅ I don't know how many will automatically figure how to put the while(cid) but it's "enough" of a hint. 👍

The UI on the home page displays a "checkmark" whenever a lesson is complete: image Nice touch. 😉

The code editor seems really slick! noticed auto-saving, syntax checking and error notification. Great implementation. Is this a custom-built ("from scratch") editor? or are you using a 3rd party open source package? 💭

Thanks again!

terichadbourne commented 5 years ago

@nelsonic Glad you noticed the code caching and the status indicator about whether you've completed a lesson. There are npm packages called monaco-editor and vue-monaco-editor that are used to create that code editor, but we recently built out the caching functionality ourselves by combining it with localStorage.

nelsonic commented 5 years ago

@terichadbourne nice work! 🎉

Note-to-self: https://microsoft.github.io/monaco-editor image