0

I've got a question regarding events. More specifically I've been reading this https://aptos.dev/concepts/events/ and came across the following sentence.

These represent event streams, or a list of events with each entry containing a sequentially increasing sequence_number beginning at 0, a type, and data.

I assume sequence_number starts at 0 for each block. However, I'm not sure if it's different for different events within the same block. Basically, if there are events E1 and E2 within the same block, can they have the same sequence_number?

I recently came across two events within the same block with the exact same sequence number so I wonder if that's something I'm doing wrong or it is the normal behaviour.

Here's an example https://explorer.aptoslabs.com/txn/17809383/events?network=mainnet

All three events have the same sequence number. The question then is how can we determine the ordering of those events? There is no other field in the Event definition https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos-protos/proto/aptos/transaction/testing1/v1/transaction.proto#L87

Also from the same example https://explorer.aptoslabs.com/txn/17809383/events?network=mainnet it seems like the index of the events is not correct either. the 0x3::token::DepositEvent is at index 1 and 0x3::token::MintTokenEvent at index 2. However, looking into the token module it seems like MintTokenEvent should happen before DepositEvent.

Is there are any suggested way to determine the ordering of events within the same block?

ppoliani
  • 4,792
  • 3
  • 34
  • 62

1 Answers1

1

I think your confusion arises from a misunderstanding about the scope of sequence numbers. Sequence numbers start at zero and increase each time an event is emitted per event stream. An event stream can be identified by the unique combination of:

  1. Account from which the event is being emitted.
  2. Event type, which is the combination of module, address where that module is deployed, and the event itself, e.g. 0x789::my_module::MyEvent.

This means every time a function emits 0x789::my_module::MyEvent the sequence number will increase by 1.

In your example, you refer to these three event streams:

  • 0x3::token::CreateTokenDataEvent
  • 0x3::token::DepositEvent
  • 0x3::token::MintTokenEvent

Even though these events were all emitted from the same account (0xb4e07588374bd19370de08bbb6b0537a975bf57ce755d7976e181e5f4e4d0da4) and they're all from the same module (0x3::token), these are still distinct token streams, so you should not expect anything about how they relate, there is no intrinsic order between them. The fact that they all have the same sequence number (125) is coincidental, it is just because whatever function is being called always emit these 3 events together.

To further clarify:

  • Event sequence numbers are not local to transactions, they just increase every time an event is emitted, forever.
  • Event sequence numbers have nothing to do with blocks.
  • Events defined in the same module do not share an event stream.
  • Different event types emitted from the same account do not share an event stream.

To answer part of your question:

How can we determine the ordering of those events?

You can't, it is not possible to determine the order that events were emitted within a transaction. You could figure this out by using your own counter that you share between events, but there is no real reason to do this. A transaction is an atomic unit, so there is no reason to care about the order that events were emitted, they were either all emitted or none were emitted. This document about how transactions ultimately result in changes to storage might be helpful: Transactions change ledger state.


Appendix

Here is a demonstration that sequence numbers indeed work this way.

Publish the following code: banool/move-examples.

Call the add_friend function:

aptos move run --function-id `yq .profiles.default.account < .aptos/config.yaml`::emit_events::add_friend --args 'string:Jimmy'

Get the creation number that identifies the event stream for AddFriendEvent:

$ curl https://fullnode.devnet.aptoslabs.com/v1/accounts/0x0e80c7246986f75d56c7a5a8eb12e0eeacf3536db6c0df09299cc1436c290f9f/resource/0x0e80c7246986f75d56c7a5a8eb12e0eeacf3536db6c0df09299cc1436c290f9f::emit_events::FriendStore | jq -r .data.add_friend_events.guid.id.creation_num
4

Query the event stream:

$ curl https://fullnode.devnet.aptoslabs.com/v1/accounts/ecbce66aa4b872e4c56d237311802381f8d7da0cb7ea872eaed43560a1f9118a/events/4 | jq .
[
  {
    "version": "2011834",
    "guid": {
      "creation_number": "4",
      "account_address": "0xecbce66aa4b872e4c56d237311802381f8d7da0cb7ea872eaed43560a1f9118a"
    },
    "sequence_number": "0",
    "type": "0xecbce66aa4b872e4c56d237311802381f8d7da0cb7ea872eaed43560a1f9118a::emit_events::AddFriendEvent",
    "data": {
      "name": "Jimmy"
    }
  }
]

See that because this is the first event emitted in this stream that the sequence number is 0.

Call the function again:

aptos move run --function-id `yq .profiles.default.account < .aptos/config.yaml`::emit_events::add_friend --args 'string:Alice'

See that there are now two events in this stream, and the sequence number of the latest event is 1:

$ curl https://fullnode.devnet.aptoslabs.com/v1/accounts/ecbce66aa4b872e4c56d237311802381f8d7da0cb7ea872eaed43560a1f9118a/events/4 | jq .
[
  {
    "version": "2011834",
    "guid": {
      "creation_number": "4",
      "account_address": "0xecbce66aa4b872e4c56d237311802381f8d7da0cb7ea872eaed43560a1f9118a"
    },
    "sequence_number": "0",
    "type": "0xecbce66aa4b872e4c56d237311802381f8d7da0cb7ea872eaed43560a1f9118a::emit_events::AddFriendEvent",
    "data": {
      "name": "Jimmy"
    }
  },
  {
    "version": "2013959",
    "guid": {
      "creation_number": "4",
      "account_address": "0xecbce66aa4b872e4c56d237311802381f8d7da0cb7ea872eaed43560a1f9118a"
    },
    "sequence_number": "1",
    "type": "0xecbce66aa4b872e4c56d237311802381f8d7da0cb7ea872eaed43560a1f9118a::emit_events::AddFriendEvent",
    "data": {
      "name": "Alice"
    }
  }
]

Now let's call remove_newest_friend:

aptos move run --function-id `yq .profiles.default.account < .aptos/config.yaml`::emit_events::remove_newest_friend

If we query the event stream for RemoveNewestFriendEvent, we can see that this is a separate stream. The creation number is different (indicating a different stream) and the sequence number started at 0 again:

$ curl https://fullnode.devnet.aptoslabs.com/v1/accounts/ecbce66aa4b872e4c56d237311802381f8d7da0cb7ea872eaed43560a1f9118a/events/5 | jq .
[
  {
    "version": "2015644",
    "guid": {
      "creation_number": "5",
      "account_address": "0xecbce66aa4b872e4c56d237311802381f8d7da0cb7ea872eaed43560a1f9118a"
    },
    "sequence_number": "0",
    "type": "0xecbce66aa4b872e4c56d237311802381f8d7da0cb7ea872eaed43560a1f9118a::emit_events::RemoveNewestFriendEvent",
    "data": {
      "new_friend_count": "1"
    }
  }
]

If we look at the stream of AddFriendEvent, we see that there are no new events and the sequence number of the latest event has not changed:

[
  {
    "version": "2011834",
    "guid": {
      "creation_number": "4",
      "account_address": "0xecbce66aa4b872e4c56d237311802381f8d7da0cb7ea872eaed43560a1f9118a"
    },
    "sequence_number": "0",
    "type": "0xecbce66aa4b872e4c56d237311802381f8d7da0cb7ea872eaed43560a1f9118a::emit_events::AddFriendEvent",
    "data": {
      "name": "Jimmy"
    }
  },
  {
    "version": "2013959",
    "guid": {
      "creation_number": "4",
      "account_address": "0xecbce66aa4b872e4c56d237311802381f8d7da0cb7ea872eaed43560a1f9118a"
    },
    "sequence_number": "1",
    "type": "0xecbce66aa4b872e4c56d237311802381f8d7da0cb7ea872eaed43560a1f9118a::emit_events::AddFriendEvent",
    "data": {
      "name": "Alice"
    }
  }
]
Daniel Porteous
  • 5,536
  • 3
  • 25
  • 44
  • This answers makes things clear to be honest. Thank you Daniel for taking the effort to explain this. I still though believe it's quite crucial knowing the sequence of events. For example, imagine having an indexer reading these events, and let's say in one transaction the `DepositEvent` appears before the `CreateTokenDataEvent`. This can be problematic because when we try to update a database record we would for example try to update the balance of a token which is not stored in the db. So it violates some invariants. The indexer example is simple but I can imagine other critical use cases – ppoliani Apr 24 '23 at 16:12
  • In our experience there really isn't a case where the event order matters that can't be resolved downstream. For example, in the processor case, because the output of a transaction is an atomic state delta, you know that there will only ever be both of those events or none of them. This means you can just "reorder" the events in the processor. If you have other cases in mind I'd love to hear them though! – Daniel Porteous Apr 24 '23 at 16:45