A gym tool Holochain Gym Concepts Developers What's next Glossary of Terms Toggle darkmode Give us Feedback!

Basic: Querying the Source Chain

Recap

You learned about entries and headers: two of the most basic building blocks in any holochain app and experienced first hand, while solving the exercises, that hashes are the glue that holds everything together. We briefly mentioned something about hash tables and the DHT. You can learn more about the DHT here.

Headers

Headers are a delightful topic. Knowing how to work with headers will give you great flexibility! And what is strength without flexibility...
In one of the previous exercises you already saw some header stuff passing by. So let's take a look at them again.

In the simulation below we already added one entry.

First click on the grey rounded square with the text snacking_log. This should look familiar. It is just an entry. Then click on the top most blue circle with the text 'Create' that points directly to the entry. And look at what you see on the righthand side in the Header Contents panel.

When you click on the blue square, you will see a blob of json data, similar to the one you see below. The response you get is something that is called a header in Holochain language.

{
  "header": {
    "content": {
      "author": "uhCAk4EP68467-xI_YMR4wO6jYjr3spY5Dvw165WF_aD2Ng8",
      "header_seq": 3,
      "prev_header": "uhCkkK6ag489p016T64YVrN2SyjdM6bY41Kt4RNEM3AKY7_w",
      "timestamp": [
        1616571211,
        293000
      ],
      "entry_hash": "uhCEkEPwEe57YM0PFD1Iy3m0eDD1iHPOcesa-ktpjULuImfE",
      "entry_type": {
        "App": {
          "id": 0,
          "zome_id": 0,
          "visibility": "Public"
        }
      },
      "type": "Create"
    },
    "hash": "uhCkkf9mOVrLftiyHHnXSpZ49l_JyMhi0Qw-SUosIMMBfmVo"
  },
  "signature":
}

An entry does not tell you who created the entry, when it was created or any other useful information. This is where the header comes in. You could think of an header as a kind of metadata.

Let's take a look what we can decypher from the example above.

  • author: the public key of the agent (cell) that signed the entry. This is a crucial component to prove to others you are really the author of this entry. Luckily it is all added automatically.
  • timestamp: timestamp of when this entry was committed. It will state the time for when the entry was created. This the time of a specific machine, not some universal, global atomic clock. The time is in UTC, so no timezone information. And the format is a combination of standard Linux Epoch Time e.g.1616571211 combined with elapsed nanoseconds, e.g.293000. While all this is very helpful to know, it cannot -reliably- be used to order events. Clocks on machine can be skewed, changed manually or do funny stuff on new years eve.
  • header_seq & prev_header are a better way to determine order. We will talk about them in another exercise.
  • type indicates what type of header this is. There are a number of header types:
    • Dna
    • AgentValidationPkg
    • InitZomesComplete
    • CreateLink
    • DeleteLink
    • Create
    • Update
    • Delete

In this exercise you will only have to deal with Create. All headers have the above mentioned fields, with one small exception of the Dna header, which doesn't have a prev_header, for the very simple reason that it is always the first header to be created in a holochain app.

And some headers have some extra fields. Create and Update have 2 more fields. And not surprisingly these fields tell something about entries. Because entries a very lightweight and most of the metadata is in the header, like author, timestamp, etcetera there has to be a link between an entry and its header.

  • entry_hash is the hash of the entry. It is exactly the same hash you worked with in the previous exercise. And since you can get the entry based on its hash, it is enough to store the entry hash inside the header. This makes a header a lightweight data structure. Whether your entry contains a 25000 page document or just a single "Hello world" string, the size of the header will be roughly the same size. That is why entries and headers make such a good team: entries are simple, headers are light.

  • entry_type: contains some additional information on about the entry itself, like the id of the hApp, the id of the zome, it's visibility. We touch on this in a future exercise.

  • hash And last but not least a header has is own hash. Just like entry, the hash of the header is completely determined by its content. Change one letter in the header and the headerhash will be completely different.

Source Chain

The Source chain. It sounds like something out of a scifi movie or a fantasy novel. And it is in fact a large part of what makes holochain unique. It's where some of the magic happens.

The glossary explains it the following way:

A hash chain of elements committed by an agent. Every agent has a separate source chain for each of the cells they’re running, which stores all of the actions they’ve taken in that cell.

Let's unpack this

When you download a compiled DNA, a WASM binary, from somewhere or when you compile it locally and you run this DNA in a holochain conductor that is running on your machine, then this DNA would be instantiated and linked to your agent ID. That thing is a cell.

If you want to keep things simple in your head, you can just say "when you run a DNA". A DNA can consist of one or more zomes. All our exercises so far only contained one zome, the one you compiled from zomes/exercise/lib.rs. For the sake of simplicity we will keep on pretending every DNA has just one zome. For now.

Now for the crucial part: "stores all of the actions". Every action, which in holochain speak means: every header and entry, that are produced by you, the agent, will become a part of the source chain, for as long as that agent has that DNA installed. Whenever a new action is committed (creating entries or links, updating...) a new element is added to that chain, with its header referencing the previous one. Essentially, the source-chain is a hash-chain of all the actions that a particular agent has committed in this DNA.

Perhaps this does sounds like some weird form of magic. Head over to the simulation where you will see that, underneath, it is just headers and entries.

Subconscious

Even before you add your first entries, like you did in the entries exercise, 3 headers and 1 entry will be created automatically in your holochain app. These 4 elements, the genesis events are created by what you call the subconscious of your holochain app. When you talk about the subconscious of your holochain app, you are talking about all the entries, headers, DHT operations and validations that happen that are not actively, consciously, triggered by you, the user.

The 3 headers and 1 entry are created when the happ is installed, the moment when your DNA is instantiated into a cell. Click on the headers and the entry below to learn more about them.

  • DNA is a special type of header that marks the beginning of your source chain. In contrast to other headers it doesn't have a header_seq or a prev_header field, because it is the very first header in your holochain app. If a 1000 people run the same DNA all of their holochain apps will have the same first header. And that is important, because that means you are, just like in real life, part of the same organism. In the world of holochain apps that means you share the same DHT through which you share data, through which you collaborate. Same DNA, same DHT. Different DNA, different DHT

  • AgentValidationPkg is important in deciding who is allowed to become part of the organism. It could be open access, as in, everyone with the same DNA can participate. But you can construct a membrane around your organism that controls who or under which conditions someone can participate. It is a very interesting part of a holochain app, but also a more advanced topic.

  • Create header is just a simple header of type CREATE. The only special thing is that it points to an entry of type AGENT

  • Agent entry or the agent ID entry, this entry contains the agent ID, your public key. The agent ID is crucial in proving who you are to others cell in the organism, other people running the same DNA.

You can think of the source chain as a blank notebook with page numbers. If you use it to record events, one event on one page, and you never skip a page, you are effectively using your notebook as a ledger. This gives your notebook some interesting properties. If you open a page on a specific event, you can find out what happened right before. And if you hand your notebook to someone, that someone can inspect your notebook to see if you removed a page or not, just by looking at the page numbers. The prev_header field in the headers acts like a page number. This is useful if another person is trying to validate if your notebook is telling the complete truth of what you wrote down in there.

Happened before

Let's put this in to practice

In the elements exercise you built the zome for a simple snacking logger app. The simulation below already contains your snacking logs.

  • Click on all the entries (grey rounded squares) to see what you snacked recently
  • Click on the headers (blue circles) and look at hash in the header and at the prev_header value. Notice how they form a flawless chain, all the way down to the DNA header.
  • Select "register_snacking" in the CallZomeFns below, type april 3: ice cream in the input and click _EXECUTE*. You will see that the new header is added at the end of the chain. It is impossible to insert something in the middle of your chain. That would break your chain and make it invalid. So regardless of any dates or timestamp in the entry or header, a new header will always added at the end. Your source chain is append only. You can never hide the fact that you ate lemon pie on april 2nd. And you cannot deny that you logged april 3: ice cream after you logged april 5: marsmallows.
  • Select "say_greeting" in the CallZomeFns below, type Hello world in the input and click _EXECUTE*. Your source chain can contain any entry type that you defined in your zomes. It does not matter if your entries are a snacking_log, a greeting_text or anything else. You can mix entries of different types, the headers will always appear in your source chain in the same order as they were created.
  • Select "query_all" in the CallZomeFns, and execute it. See that the result for the function is the list of all the elements that are committed in your source chain.

Exercise

This time we will do some lightweight exercises, just to get a feel for the chain part of our source chain. You need to implement 3 functions.

  • query_all_elements returns the full array of elements contained in the source chain of the agent.
  • query_snackings queries the source chain and returns only the elements that contain a Snacking entry. WARNING! This query should also return the entries themselves.
  • query_by_time queries the source chain and filters it by a time range of when the element was committed.
  1. Check if you are still inside the nix-shell
    Your terminal should look similar to this [nix-shell:~/path-to-workspace/developer-exercises/path-to-exercise]$
  2. Implement query_all_elements, query_snackings, query_by_time
  3. Compile and test your code: cd tests && npm install && npm test
  4. Don't stop until the tests run green

Errors

If you encounter an error check here if you can find something that looks like your error. If not head to the forum.holochain.org and ask for help.

For Rust specific questions: https://forum.holochain.org/c/technical/rust/15 or your favorite search engine.