Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

emit & event built-in functions #575

Closed
r3v4s opened this issue Mar 8, 2023 · 16 comments
Closed

emit & event built-in functions #575

r3v4s opened this issue Mar 8, 2023 · 16 comments
Assignees
Labels
📦 🌐 tendermint v2 Issues or PRs tm2 related 📦 🤖 gnovm Issues or PRs gnovm related ❓ question Questions about Gno 🌱 feature New update to Gno

Comments

@r3v4s
Copy link
Contributor

r3v4s commented Mar 8, 2023

I opened this issue to discuss about emit (i.e event subscribe).

In Solidity, you can generate logs using emit & event methods

  • EVM has five log opcodes (LOG0, LOG1, ..., LOG4) (source)
  • Developers (or dapps) can retrieve logs via subscribe
    • web3.js >> web3.eth.subscribe()
    • json rpc => eth_newFilter() && eth_getFilterChanges()

Theses concepts are used to communicate between Ethereum and the outside world.

  • An example with price oracle: When the event 'GetPriceEth' gets emit, outer server (most likely eventlistener run by nodejs) catches it and updates the price to the latest.

Questions

Would there be any similar ways to get Gno's event from the outside world?

  • It seems like ws is open by default, however a list of methods I've used on Cosmos doesn't work.

Suggestions

Pub-sub

  1. We can use the ws subscribe concept from Tendermint, which has been widely used.
  2. We can use a new method that the Tx indexer will be using (I think feat: add file-based transaction indexing #546, being developed by @zivkovicmilos, has some ideas that we can bring to this issue).
@moul
Copy link
Member

moul commented May 4, 2023

Let's explore implementing this feature. Some initial questions and suggestions:

  • Should this feature be restricted to those replaying transactions, or should we make the data available to other contracts ([WIP]feat: Protocol to support contract-contract interaction #473)?
  • Should we use std.Emit or std.Log for naming convention?
  • Should we use std.Log(interface{}) or std.Log("key1", "value1", "key2", "value2") for logging?

Please share your thoughts.

@moul
Copy link
Member

moul commented May 4, 2023

I have a new idea: what if we use the following code snippet instead?

import "log"

func Transfer(amount grc20.Token) {
    // ...
    log.Print("foobar")
}

This would append a transaction receipt similar to the following:

{"caller":"gno.land/r/demo/foo20","msg":"foobar"}

What do you think?

@r3v4s
Copy link
Contributor Author

r3v4s commented May 5, 2023

Let's explore implementing this feature. Some initial questions and suggestions:

  • for d-app developers, making data available to other contracts will be more flexible(or you know.. eaiser to do something)
  • Should we use std.Emit or std.Log for naming convention?
  • if not only emit but event is going to be implement, I think std.Emit is good, but without event std.Log looks more intuitive
  • Should we use std.Log(interface{}) or std.Log("key1", "value1", "key2", "value2") for logging?
  • shouldn't it be std.Log(args ...interface{}) ?? we don't know how many arguments will be passed

@r3v4s
Copy link
Contributor Author

r3v4s commented May 5, 2023

I have a new idea: what if we use the following code snippet instead?

import "log"

func Transfer(amount grc20.Token) {
    // ...
    log.Print("foobar")
}

This would append a transaction receipt similar to the following:

{"caller":"gno.land/r/demo/foo20","msg":"foobar"}

What do you think?

this looks very straightforward + easy to understand + very simple.

But to be sure is that log package will be from go's log? If it is, I can't think any better ways, but if not it might give developers confuse

@moul
Copy link
Member

moul commented May 5, 2023

Yes, my suggestion is to port and adapt the official log package while maintaining API compatibility as closely as possible.

This will allow us to have transaction receipts when on-chain, while also retaining the standard stderr logging feature when using gnodev.

@ltzmaxwell
Copy link
Contributor

ltzmaxwell commented May 5, 2023

But to be sure is that log package will be from go's log? If it is, I can't think any better ways, but if not it might give developers confuse

The name of log does confuse a bit, I will expect log.print() print out something in contract only, without any other side effect.

@thehowl
Copy link
Member

thehowl commented May 5, 2023

I personally think that the name "log" should not be used for communicating and using events; in Go it is used to add a "human interface" and what is logged should not be parsed or read by other machines - aside from what @ltzmaxwell pointed out as well.

I'm more in favour of something in std or similar, although honestly I think in Gno we could just use the language and make pub/sub not a stdlib feature but a realm, as I personally think that pub/sub resides more in the realm of "useful" rather than "founding feature" - aside from the fact that having it as a realm would serve as a further demonstration of the power of stateful realms.

Here's a way I could imagine this being used:

package foo // import "gno.land/r/demo/foo"

import "gno.land/r/demo/broker"

type X struct {
    A int
}

const (
    // EventFoo is emitted when ...
    // Type: [X]
    EventFoo = "foo"
)

func Emit() {
    // As a guideline (and possibly future linter), a package should always emit the same
    // type, and it should be documented like in this case.
    broker.Emit(EventFoo, X{1})
}
package bar // import "gno.land/r/demo/bar"

import (
    "gno.land/r/demo/foo"
    "gno.land/r/demo/broker"
)

func init() {
    // There could be runtime errors (ie. Handler's argument type is not that emitted by pkg foo)
    // so this is an idea of how to handle them.
    broker.OnError(errorHandler)
    // The listeners are scoped to the calling realm.
    // Other APIs could be SetListener(pkg, evt string, fn interface{}), RemoveListeners(pkg, evt string)
    broker.AddListener("gno.land/r/demo/foo", foo.EventFoo, Handler)
}

func Handler(x foo.X) {
   _ = x.A
}

@r3v4s
Copy link
Contributor Author

r3v4s commented May 5, 2023

Since emit is actually action of Log in EVM, I thought reusing go's log does fit into this circumstance. But as @ltzmaxwell said some devs won't expect log.xx does something else rather than what it does in go.

If we want this has to be LOG we can just make new method for gno only(idk... maybe Log.PrintState), or if it doesn't have to be LOG, personally I prefer std.* stuff like @thehowl

BTW @thehowl, making it realm is very interesting stuff, but I'm worried about situation where we have to upgrade pub/sub contracts. What do you think??

@thehowl
Copy link
Member

thehowl commented May 5, 2023

@r3v4s Are you referring to updating the broker package from my examples, or its users? Could you specify what your concern is? :)

@r3v4s
Copy link
Contributor Author

r3v4s commented May 5, 2023

@r3v4s Are you referring to updating the broker package from my examples, or its users? Could you specify what your concern is? :)

updating broker package from your examples.
AFAIK, we can't modify contract without change package path(or address).

Lets say...

  1. we published gno.land/r/broker
  2. other user(such as d-app devs) import gno.land/r/broker into their realm
  3. guess what, we found there is a bug(or critical security issue) to gno.land/r/broker
  4. we made patch and deployed gno.land/r/broker_v2
  5. maybe we can pause gno.land/r/broker but it will make other realm(from step 2) crash

I had similar experience in EVM, so this is my concern.

Speaking of which, maybe we can make proxy contract for gno.land/r/broker to avoid above situation :D

@thehowl
Copy link
Member

thehowl commented May 5, 2023

@r3v4s I spent a few words explaining how I think we should handle contract upgrades while retaining state information in this comment - so it should allow us to change broker while still keeping the data :)

@anarcher
Copy link
Contributor

anarcher commented May 8, 2023

import "std"

  e := std.NewEvent("admin_added") // MakeEvent? // Native go wrapper  // Event Prefix: `gnovm-`
  e.AddAttributes("addr",newAdminAddr) // key string, value string 

IMHO, I'm not sure, but what if we treated the event that occurs in the contract as one of abci.Event?

@ajnavarro ajnavarro added ❓ question Questions about Gno 🌱 feature New update to Gno 📦 🌐 tendermint v2 Issues or PRs tm2 related 📦 🤖 gnovm Issues or PRs gnovm related labels May 15, 2023
@jaekwon
Copy link
Contributor

jaekwon commented May 24, 2023

I see two things.

std.Emit(std.Event)

  • Event is somehow included in the merkle root, probably using tm2.SimpleTree.
  • Event generates an abci.Event with type "gno:std.Event".
  • Therefore, websocket listeners to tm2 can listen for these events.
  • Is expected to cost reasonably, so usage is OK, but debug logging is discouraged by cost.
  • Not readable by gno logic.
  • Rolled back as per usual when tx fails.

log.Log(fmt, args...)

  • Event generates an abci.Event with type "gno:log.Log".
  • Doesn't get included in merkle hash.
  • like std.Event, websocket listeners to tm2 can listen for these events.
  • cheap, but not zero cost. this way tm2 doesn't get clogged.
  • Not readable by gno logic.
  • abci.Event generated even if tx fails???

Rather than Tendermint1's pubsub which is made to be asynchronous (unnecessarily I would add), I would go with tm2's synchronous simple EventSwitch, which with little modification should be portable go gno. So this gno EventSwitch should be used for gno in-logic events, neither std.Emit nor log.Log. The user, if needed, can easily write a function that calls both std.Emit (or log.Log) AND EventSwitch.FireEvent.

"log" to me is something cheap, and something that doesn't have any side effects. While std.Emit(std.Event) could potentially have side effects, as in, become an input to subsequent logic (in the current or a later transaction), I think there's value to having a merkle-root included event that is guaranteed to be cheap and (direct) side-effect free, where the intent is to produce a byte sequence that can be parsed from clients like wallets. If you want to emit an event that is to be read by later logic, where the event includes a (potentially) mutable object, then tm2.EventSwitch can handle that better, or some custom pubsub system that uses the persistence of realms, as @thehowl mentioned. In other words, some function probably should exist that does nothing but produce amino bytes that get included in the merkle root, and nothing else; and std.Emit should be that.

Must log.Log generate abci.Events even when the tx fails? That would be very useful, but there's as of yet no way to do this. It wouldn't be hard to implement -- just need to define via PackageNode.DefineNative, to stash the logs somewhere. This function should be defined in gnoland (not gnovm), since it is a particular quirk of the usage of gno, and the gno VM doesn't need to be aware of it. BTW tm2/pkgs/sdk/vm should be moved into gno.land/pkgs/sdk/vm, then afterwards this log.Log maybe can be injected by modifying what is currently tm2/pkgs/sdk/vm/builts.go. // @piux2 @moul I suggest we move it to gno.land/pkgs/sdk/vm sooner than later.

@anarcher
Copy link
Contributor

I'm going (would like) to start a PR about std.EmitEvent() using abci.Event :-) I probably won't be able to include it in this PR, but I think gas costs for using event should be added later.

@moul
Copy link
Member

moul commented Mar 25, 2024

The initial implementation could be:

  1. std.Emit can store events occurring in the transaction in gnovm/vmkeeper, aggregate, format them, and return in abci.Event with the result.
  2. A websocket pubsub system can be set up to receive notifications for new events that match a filter.

moul added a commit that referenced this issue Apr 30, 2024
# Description

Succeed in my predecessor's legacy.

I have implemented the output to show the path where the event occurred,
as discussed in #1833. Also made it print the timestamp or block height
together.

## Key Changes

In this change, event emission functionality has been added to the Gno.
The main changes include:

1. Introducing of the `emitEvent` function:
- The `emitEvent` function emits an event based on the given type and
attributes
    - Attributes are passed as an even-length of array key-value pairs
- When emitting an event, the current _package path_, _timestamp_, and
_block height_ information are recorded along with the event(discussed
in #1833). This metadata provides additional context about where the
event occured.

2. Additional of event-related types and functions in the `sdk`
packages:
- `NewEvent` creates a new `Event` object based on the provided
information.
- `ArributedEvent` struct contains informations such as event type,
package path, block height, timestamp and attributes. But I'm not sure
how to utilize the `eventType` yet. So, I've just put it as a
placeholder which will be a disscussion for another time.
- `EventArribute` represents an attribute of an event and consists of a
key-value pair
- `NewEventArribute` creates a new `EventAttribute` object based on the
given key-value pair.

## Example

```go
package ee

import (
	"std"
)

const (
	EventSender = "sender"
	EventReceiver = "receiver"
)

func Sender(){
    SubSender()
    SubReceiver()
}

func SubSender() {
    std.Emit(
        EventSender,
		"key1", "value1",
		"key2", "value2",
		"key3", "value3",
    )  
}

func SubReceiver() {
    std.Emit(
        EventReceiver,
        "bar", "baz",
    )
}

func Receiver() {
    std.Emit(
        EventReceiver,
        "foo", "bar",
    )
}
```

### Result

```json
[
  "{\"type\":\"sender\",\"pkg_path\":\"gno.land/r/demo/ee\",\"identifier\":\"SubSender\",\"timestamp\":1713846501,\"attributes\":[{\"key\":\"key1\",\"value\":\"value1\"},{\"key\":\"key2\",\"value\":\"value2\"},{\"key\":\"key3\",\"value\":\"value3\"}]}",
  "{\"type\":\"receiver\",\"pkg_path\":\"gno.land/r/demo/ee\",\"identifier\":\"SubReceiver\",\"timestamp\":1713846501,\"attributes\":[{\"key\":\"bar\",\"value\":\"baz\"}]}"
]
```

## Related Issue/PR

#575 emit & event built-in functions (@r3v4s)
#853 feat: event & emit in gno (@anarcher) <- previous work.
#975 [META] Gno Wishlist / Feature Request Dump (@zivkovicmilos)

---------

Co-authored-by: n3wbie <r3v4@onbloc.xyz>
Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com>
gfanton pushed a commit to gfanton/gno that referenced this issue May 1, 2024
)

# Description

Succeed in my predecessor's legacy.

I have implemented the output to show the path where the event occurred,
as discussed in gnolang#1833. Also made it print the timestamp or block height
together.

## Key Changes

In this change, event emission functionality has been added to the Gno.
The main changes include:

1. Introducing of the `emitEvent` function:
- The `emitEvent` function emits an event based on the given type and
attributes
    - Attributes are passed as an even-length of array key-value pairs
- When emitting an event, the current _package path_, _timestamp_, and
_block height_ information are recorded along with the event(discussed
in gnolang#1833). This metadata provides additional context about where the
event occured.

2. Additional of event-related types and functions in the `sdk`
packages:
- `NewEvent` creates a new `Event` object based on the provided
information.
- `ArributedEvent` struct contains informations such as event type,
package path, block height, timestamp and attributes. But I'm not sure
how to utilize the `eventType` yet. So, I've just put it as a
placeholder which will be a disscussion for another time.
- `EventArribute` represents an attribute of an event and consists of a
key-value pair
- `NewEventArribute` creates a new `EventAttribute` object based on the
given key-value pair.

## Example

```go
package ee

import (
	"std"
)

const (
	EventSender = "sender"
	EventReceiver = "receiver"
)

func Sender(){
    SubSender()
    SubReceiver()
}

func SubSender() {
    std.Emit(
        EventSender,
		"key1", "value1",
		"key2", "value2",
		"key3", "value3",
    )  
}

func SubReceiver() {
    std.Emit(
        EventReceiver,
        "bar", "baz",
    )
}

func Receiver() {
    std.Emit(
        EventReceiver,
        "foo", "bar",
    )
}
```

### Result

```json
[
  "{\"type\":\"sender\",\"pkg_path\":\"gno.land/r/demo/ee\",\"identifier\":\"SubSender\",\"timestamp\":1713846501,\"attributes\":[{\"key\":\"key1\",\"value\":\"value1\"},{\"key\":\"key2\",\"value\":\"value2\"},{\"key\":\"key3\",\"value\":\"value3\"}]}",
  "{\"type\":\"receiver\",\"pkg_path\":\"gno.land/r/demo/ee\",\"identifier\":\"SubReceiver\",\"timestamp\":1713846501,\"attributes\":[{\"key\":\"bar\",\"value\":\"baz\"}]}"
]
```

## Related Issue/PR

gnolang#575 emit & event built-in functions (@r3v4s)
gnolang#853 feat: event & emit in gno (@anarcher) <- previous work.
gnolang#975 [META] Gno Wishlist / Feature Request Dump (@zivkovicmilos)

---------

Co-authored-by: n3wbie <r3v4@onbloc.xyz>
Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com>
@zivkovicmilos
Copy link
Member

Closed as part of #1653 🎉

zivkovicmilos added a commit that referenced this issue Jun 12, 2024
This PR showcases a model that combines multiple contracts with defined
goals and constraints. The aim is to make everything in `sys/*` usable
by the chain (tm2 powered) efficiently, with minimal need for updates
while maintaining flexibility in usage.

The `sys/` contracts focus on defining data types and helpers to ensure
that received callbacks meet minimal constraints, like GovDAO approval.
They do not handle DAO logic or state due to complexity and
upgradability requirements for DAOs.

I won't include complete DAO examples in this PR. Imagine having these
sections once everything is done:
-  `{p,r}/sys: minimal interface with the chain`
-  `{p,r}/gov: simple DAO frameworks`
- `{p,r}/*`: where users will develop permissionless logic and propose
it to `gov` for approval using `sys` to trigger the chain.

Personal note -> try to introduce and document the notion of "pausable
threads". Related with #1974.

---

TODO:
- [x] pseudo-code for proof of conribution's valset management.
- [x] proposal example.
- [ ] pseudo-code for gnosdk v0 to catch the event and apply the change
from tm2. cc @gfanton
- [ ] add unit-tests, to illustrate the expected usage.

depends on std.Emit (#575).
depends on #1948 (need rebase).

---------

Signed-off-by: moul <94029+moul@users.noreply.github.com>
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
Co-authored-by: Milos Zivkovic <milos.zivkovic@tendermint.com>
Co-authored-by: gfanton <8671905+gfanton@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
📦 🌐 tendermint v2 Issues or PRs tm2 related 📦 🤖 gnovm Issues or PRs gnovm related ❓ question Questions about Gno 🌱 feature New update to Gno
Projects
Status: 🌟 Wanted for Launch
Status: Done
Development

No branches or pull requests

8 participants