Use GRPC Client Get Events

The GRPC client contains a function to get events by access path.

If I have the following code and execute it, how do I get the emitted event using the GRPC client?

module M {
    import 0x0.LibraAccount;

    struct MyEvent { b: bool }

    public emit_event() {
        let handle: LibraAccount.EventHandle<Self.MyEvent>;
        handle = LibraAccount.new_event_handle<Self.MyEvent>();
        LibraAccount.emit_event<Self.MyEvent>(&mut handle, MyEvent{ b: true });
        LibraAccount.destroy_handle<Self.MyEvent>(move(handle));
        return;
    }
}
import 0x0.LibraAccount;
import {{sender}}.M;

main() {
    M.emit_event();
    return;
}

What will be the access path of the event emitted by the contract after the script is executed?

1 Like

For others that might also be looking to get a list of all events of a particular type, it seems like there currently isn’t a quick way to do this.

Looking at the code, it seems that the function to get events by access path is currently only intended to work with LibraAccount send and receive events.

Looking at the get_events_by_query_path function in storage/libradb/src/lib.rs it seems like it fetches the account resource for the address passed through the access path and then calls get_event_handle_by_query_path(&query_path.path) on it. However, if we look at that function, we can see that it is hard coded to only work with the LibraAccount sent and receive events.

pub fn get_event_handle_by_query_path(&self, query_path: &[u8]) -> Result<&EventHandle> {
        if *ACCOUNT_RECEIVED_EVENT_PATH == query_path {
            Ok(&self.received_events)
        } else if *ACCOUNT_SENT_EVENT_PATH == query_path {
            Ok(&self.sent_events)
        } else {
            bail!("Unrecognized query path: {:?}", query_path);
        }
    }

This explains why “Unrecognized query path” is returned for any access path tried.

Given that it is not possible to get all events of a certain type like this. Is there a way to get a list of all emitted events of interest without having to go through all transactions?

2 Likes

I have also been trying to get the get events by access path working but I’m not able to serialize the path parameter correctly. I’d be happy with just the sent/received events. Would someone who’s got it working please share an example and explain the access path serialization?

1 Like

I got this working in nodejs, these are example codes of query sent event of the account address ‘9af6fff76e8065808102c0f4b5ec7fbec7be37f28738b0cbab21802d6431c9e6’

// 1. Create request object
const request = {
  get_events_by_event_access_path_request: {
    access_path: {
      address: Uint8Array.from(Buffer.from('9af6fff76e8065808102c0f4b5ec7fbec7be37f28738b0cbab21802d6431c9e6', "hex")),
      path: Uint8Array.from(
        Buffer.concat([
          Buffer.from('0116608f05d24742a043e6fd12d3b32735f6bfcba287bea92b28a175cd4f3eee32', "hex"),  // This is the path string (hex)
          Buffer.from('/sent_events_count/') // Type of event you need to query
        ])
      )
    },
    start_event_seq_num:  0,
    ascending: true,
    limit: 100
  }
}

// 2. Use the request object from 1 to create the updateToLedgerRequest object that will be sent to the libra blockchain.
const updateToLedgerRequest =  {
  client_known_version:  0, // version
  requested_items: [request] // request obj from 1
}

// 3. Send the updateToLedgerRequest object via grpc.
ledgerRequest(updateToLedgerRequest)

Here is the link to my github repo https://github.com/autsada/libra-graphql

You can find the code in src/grpcClient/grpcClient.js and src/resolvers/Query.js

Hope this may help.

2 Likes

Thanks, I got it working with your sample :+1: , but I’m still interested in how the hex string 0116608f05d24742a043e6fd12d3b32735f6bfcba287bea92b28a175cd4f3eee32 was formed. Do you have that somewhere in your code or could you explain it?

2 Likes

I got this path string from this forum, just grab it from someone’s comment and have no idea how to compute it. :grinning:

In Rust the access path for the sent event is generated like this:

let mut path = AccessPath::resource_access_vec(
    &StructTag {
        address: AccountAddress::default(),
        module: Identifier::new("LibraAccount").unwrap(),
        name: Identifier::new("T").unwrap(),
         type_params: vec![],
    },
    &Accesses::empty(),
);

path.extend_from_slice(b"/sent_events_count/");

let access_path = AccessPath::new(account, path.to_vec());

Similarly for the received event. But change sent_events_count to received_events_count.

This is extrapolated from how ACCOUNT_RECEIVED_EVENT_PATH and ACCOUNT_SENT_EVENT_PATH are defined in the Libra source code.

And if you print these access paths you get

Received Events Access Path: 
AccessPath { 
  address: 0fb0bc0045797c15ba84ede106fec54b3d392f503a3169dabc77f26404124776, 
  path: 0116608f05d24742a043e6fd12d3b32735f6bfcba287bea92b28a175cd4f3eee322f72656365697665645f6576656e74735f636f756e742f 
}

Sent Events Access Path: 
AccessPath { 
  address: 0fb0bc0045797c15ba84ede106fec54b3d392f503a3169dabc77f26404124776, 
  path: 0116608f05d24742a043e6fd12d3b32735f6bfcba287bea92b28a175cd4f3eee322f73656e745f6576656e74735f636f756e742f 
}

Where address is the address you want access those event for.

However, as explained above this doesn’t seem to be an actual access path but rather just a way to tell the validator to get the sent events which it happens to keep track of. However, this is not the case in general. In general events are just stored with the transcations and are not fetchable this way. Someone working on Libra might be able to clarify if I am wrong though.

1 Like

Thanks, that got me further, though it did not contain everything to create the access path. I will add the rest here if someone else is wondering how to do this and maybe others will also get an idea what fun it is sometimes to backwards engineer things in Libra :sweat_smile:

The reason why I’m doing this is because I’m implementing a library to call the api from Java.

Even though these are fun puzzles to solve, I could appreciate a bit more self-explaining api.

First, take a look at the source file of AccessPath, on row 255 you will see how the access path byte array is formed.

I will start by creating the hash of StructTag from line 259:

  • The StructTag is serialized using the Libra Canonical Serialization. This means we will concatenate the byte array representations of all the attributes in StructTag (address, module, name, type_params). First, we convert the AccountAddress to bytes (the ‘default’ address used here is ‘0000000000000000000000000000000000000000000000000000000000000000’), then we add the byte array length of string ‘LibraAccount’, then the actual bytes of string ‘LibraAccount’, then byte array length of string ‘T’, then the actual bytes of string ‘T’ and last the length of type_params array as bytes which is 0 in this case.

  • Before creating the hash, salt is added to the bytes. The salt is the string ‘StructTag::libra_types::language_storage@@$$LIBRA$$@@’ and the byte representation of that string is added to the StructTag bytes and the sha3 hash is created of the bytes.

  • Now we have the hash of StructTag which is ‘16608f05d24742a043e6fd12d3b32735f6bfcba287bea92b28a175cd4f3eee32’ and in front of the hash we will add the constant value 1 which identifies a resource path (line 257). After this step the path is ‘0116608f05d24742a043e6fd12d3b32735f6bfcba287bea92b28a175cd4f3eee32’

  • finally we convert the string “/sent_events_count/” to bytes and add that to the end (line 263) and we have the access path ready as ‘0116608f05d24742a043e6fd12d3b32735f6bfcba287bea92b28a175cd4f3eee322f73656e745f6576656e74735f636f756e742f’

and now we can call the api to get the sent events of an account by calling the get_events_by_access_path method and providing the path as a parameter :smiley:

You should expect the JLibra client to contain this feature soon! https://github.com/ketola/jlibra :wink:

4 Likes

Thank you a lot for the detailed explanation, I now have a better understanding of this path string. :grinning:

1 Like