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

"seer starknet generate" now generates event crawler and parser #4

Merged
merged 1 commit into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 202 additions & 1 deletion starknet/generators.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ func toCamelCase(s string) string {
return strcase.ToCamel(t)
}

var resultEventParserKey string = "-eventparser"

// Generates a Go name for a Starknet ABI item given its fully qualified ABI name.
// Qualified names for Starknet ABI items are of the form:
// `core::starknet::contract_address::ContractAddress`
Expand Down Expand Up @@ -159,10 +161,15 @@ func GenerateSnippets(parsed *ParsedABI) (map[string]string, error) {
}

eventTemplate, eventTemplateParseErr := template.New("event").Funcs(templateFuncs).Parse(EventTemplate)
if structTemplateParseErr != nil {
if eventTemplateParseErr != nil {
return result, eventTemplateParseErr
}

eventParserTemplate, eventParserTemplatErr := template.New("eventParser").Funcs(templateFuncs).Parse(EventParserTemplate)
if eventParserTemplatErr != nil {
return result, eventParserTemplatErr
}

for _, enum := range parsed.Enums {
goName := GenerateGoNameForType(enum.Name)
parseFunctionName := ParserFunction(goName)
Expand Down Expand Up @@ -216,6 +223,7 @@ func GenerateSnippets(parsed *ParsedABI) (map[string]string, error) {
}
}

generatedEvents := []GeneratedEvent{}
for _, event := range parsed.Events {
if event.Kind == "struct" {
goName := GenerateGoNameForType(event.Name)
Expand Down Expand Up @@ -248,7 +256,18 @@ func GenerateSnippets(parsed *ParsedABI) (map[string]string, error) {
generated.Code = b.String()

result[event.Name] = generated.Code
generatedEvents = append(generatedEvents, generated)
}
}

{
var b bytes.Buffer
templateErr := eventParserTemplate.Execute(&b, generatedEvents)
if templateErr != nil {
return result, templateErr
}

result[resultEventParserKey] = b.String()
}

return result, nil
Expand Down Expand Up @@ -423,7 +442,136 @@ type RawEvent struct {
Parameters []*felt.Felt
}

func FeltFromHexString(hexString string) (*felt.Felt, error) {
fieldAdditiveIdentity := fp.NewElement(0)

if hexString[:2] == "0x" {
hexString = hexString[2:]
}
decodedString, decodeErr := hex.DecodeString(hexString)
if decodeErr != nil {
return nil, decodeErr
}
derivedFelt := felt.NewFelt(&fieldAdditiveIdentity)
derivedFelt.SetBytes(decodedString)

return derivedFelt, nil
}

func AllEventsFilter(fromBlock, toBlock uint64, contractAddress string) (*rpc.EventFilter, error) {
result := rpc.EventFilter{FromBlock: rpc.BlockID{Number: &fromBlock}, ToBlock: rpc.BlockID{Number: &toBlock}}

fieldAdditiveIdentity := fp.NewElement(0)

if contractAddress != "" {
if contractAddress[:2] == "0x" {
contractAddress = contractAddress[2:]
}
decodedAddress, decodeErr := hex.DecodeString(contractAddress)
if decodeErr != nil {
return &result, decodeErr
}
result.Address = felt.NewFelt(&fieldAdditiveIdentity)
result.Address.SetBytes(decodedAddress)
}

result.Keys = [][]*felt.Felt{{}}

return &result, nil
}

func ContractEvents(ctx context.Context, provider *rpc.Provider, contractAddress string, outChan chan<- RawEvent, hotThreshold int, hotInterval, coldInterval time.Duration, fromBlock, toBlock uint64, confirmations, batchSize int) error {
defer func() { close(outChan) }()

type CrawlCursor struct {
FromBlock uint64
ToBlock uint64
ContinuationToken string
Interval time.Duration
Heat int
}

cursor := CrawlCursor{FromBlock: fromBlock, ToBlock: toBlock, ContinuationToken: "", Interval: hotInterval, Heat: 0}

count := 0

for {
select {
case <-ctx.Done():
return nil
case <-time.After(cursor.Interval):
count++
if cursor.ToBlock == 0 {
currentblock, blockErr := provider.BlockNumber(ctx)
if blockErr != nil {
return blockErr
}
cursor.ToBlock = currentblock - uint64(confirmations)
}

if cursor.ToBlock <= cursor.FromBlock {
// Crawl is cold, slow things down.
cursor.Interval = coldInterval

if toBlock == 0 {
// If the crawl is continuous, breaks out of select, not for loop.
// This effects a wait for the given interval.
break
} else {
// If crawl is not continuous, just ends the crawl.
return nil
}
}

filter, filterErr := AllEventsFilter(cursor.FromBlock, cursor.ToBlock, contractAddress)
if filterErr != nil {
return filterErr
}

eventsInput := rpc.EventsInput{
EventFilter: *filter,
ResultPageRequest: rpc.ResultPageRequest{ChunkSize: batchSize, ContinuationToken: cursor.ContinuationToken},
}

eventsChunk, getEventsErr := provider.Events(ctx, eventsInput)
if getEventsErr != nil {
return getEventsErr
}

for _, event := range eventsChunk.Events {
crawledEvent := RawEvent{
BlockNumber: event.BlockNumber,
BlockHash: event.BlockHash,
TransactionHash: event.TransactionHash,
FromAddress: event.FromAddress,
PrimaryKey: event.Keys[0],
Keys: event.Keys,
Parameters: event.Data,
}

outChan <- crawledEvent
}

if eventsChunk.ContinuationToken != "" {
cursor.ContinuationToken = eventsChunk.ContinuationToken
cursor.Interval = hotInterval
} else {
cursor.FromBlock = cursor.ToBlock + 1
cursor.ToBlock = toBlock
cursor.ContinuationToken = ""
if len(eventsChunk.Events) > 0 {
cursor.Heat++
if cursor.Heat >= hotThreshold {
cursor.Interval = hotInterval
}
} else {
cursor.Heat = 0
cursor.Interval = coldInterval
}
}
}
}
}
`

// This is the Go template which is used to generate the Go bindings to a Starknet ABI event.
Expand Down Expand Up @@ -469,6 +617,53 @@ func {{.ParserName}}(parameters []*felt.Felt) ({{.GoName}}, int, error) {

`

// This aggregates all event information to generate an event parser for the given ABI.
// This template should be applied to a []GeneratedEvent list.
var EventParserTemplate string = `var EVENT_UNKNOWN = "UNKNOWN"

type ParsedEvent struct {
Name string
Event interface{}
}

type PartialEvent struct {
Name string
Event json.RawMessage
}

type EventParser struct {
{{range .}}
{{.EventNameVar}}_Felt *felt.Felt
{{- end}}
}

func NewEventParser() (*EventParser, error) {
var feltErr error
parser := &EventParser{}
{{range .}}
parser.{{.EventNameVar}}_Felt, feltErr = FeltFromHexString({{.EventHashVar}})
if feltErr != nil {
return parser, feltErr
}
{{end}}
return parser, nil
}

func (p *EventParser) Parse(event RawEvent) (ParsedEvent, error) {
defaultResult := ParsedEvent{Name: EVENT_UNKNOWN, Event: event}
{{range .}}
if p.{{.EventNameVar}}_Felt.Cmp(event.PrimaryKey) == 0 {
parsedEvent, _, parseErr := {{.ParserName}}(event.Parameters)
if parseErr != nil {
return defaultResult, parseErr
}
return ParsedEvent{Name: {{.EventNameVar}}, Event: parsedEvent}, nil
}
{{- end}}
return defaultResult, nil
}
`

// This is the Go template used to create header information at the top of the generated code.
// At a bare minimum, the header specifies the version of seer that was used to generate the code.
// This template should be applied to a HeaderParameters struct.
Expand All @@ -480,9 +675,15 @@ var HeaderTemplate string = `// This file was generated by seer: https://github.
{{if .PackageName}}package {{.PackageName}}{{end}}

import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"math/big"
"time"

"github.com/NethermindEth/juno/core/felt"
"github.com/NethermindEth/starknet.go/rpc"
"github.com/consensys/gnark-crypto/ecc/stark-curve/fp"
)
`
2 changes: 1 addition & 1 deletion version/version.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package version

var SeerVersion string = "0.0.4"
var SeerVersion string = "0.0.5"
Loading