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

feat: gno ghverify agent #1288

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions gno_github_agent/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
github.db
5 changes: 5 additions & 0 deletions gno_github_agent/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
GNO_MNEMONIC=
GNO_CHAIN_ID=
GNO_RPC_ADDR=
GNO_REALM_PATH=
GNO_TX_INDEXER=
14 changes: 14 additions & 0 deletions gno_github_agent/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Start from the latest golang base image
FROM golang:1.23-bullseye

# Set the Current Working Directory inside the container
WORKDIR /app

# Copy everything from the current directory to the Working Directory inside the container
COPY . .

ENV CGO_ENABLED=1
RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go go build -o bin/main main.go

CMD [ "/app/bin/main" ]

7 changes: 7 additions & 0 deletions gno_github_agent/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
DOCKER_REGISTRY=rg.nl-ams.scw.cloud/teritori
AGENT_DOCKER_IMAGE=$(DOCKER_REGISTRY)/gno-gh-verify-agent:$(shell git rev-parse --short HEAD)

.PHONY: publish
publish:
docker build . --platform linux/amd64 -t $(AGENT_DOCKER_IMAGE)
docker push $(AGENT_DOCKER_IMAGE)
142 changes: 142 additions & 0 deletions gno_github_agent/clientql/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package clientql

import (
"context"
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"

"github.com/Khan/genqlient/graphql"
"github.com/TERITORI/gh-verify-agent/db"
"github.com/TERITORI/gh-verify-agent/gnoindexerql"
"github.com/TERITORI/gh-verify-agent/signer"
"go.uber.org/zap"
"gorm.io/gorm"
)

type IndexerQL struct {
gqlClient graphql.Client
db *gorm.DB
logger *zap.SugaredLogger
signer *signer.Signer
verifyRealmPath string
}

func New(graphqlEndpoint string, db *gorm.DB, logger *zap.SugaredLogger, gnoSigner *signer.Signer, verifyRealmPath string) *IndexerQL {
gqlClient := graphql.NewClient(graphqlEndpoint, nil)
return &IndexerQL{gqlClient: gqlClient, db: db, logger: logger, signer: gnoSigner, verifyRealmPath: verifyRealmPath}
}

func (client *IndexerQL) DealWithVerifications() error {
lastBlock, err := client.getLastTreatedBlock()
if err != nil {
return err
}

validationRequests, err := gnoindexerql.GetValidationRequests(context.Background(), client.gqlClient, lastBlock, client.verifyRealmPath)
if err != nil {
return err
}
client.logger.Infof("validation requests: %d\n", len(validationRequests.Transactions))

for _, validationRequest := range validationRequests.Transactions {
for _, responseEvent := range validationRequest.Response.Events {
switch event := responseEvent.(type) {
case *gnoindexerql.GetValidationRequestsTransactionsTransactionResponseEventsGnoEvent:
client.logger.Infof("args %v\n", event.Attrs)

err := client.dealWithVerification(event, validationRequest.Block_height)
if err != nil {
client.logger.Errorf("failed to deal with verification: %s", err.Error())
continue
}

default:
client.logger.Errorf("unexpected event type: %T", event)
}
}
}

return nil
}

func (client *IndexerQL) getLastTreatedBlock() (int, error) {
var verification db.Verification
err := client.db.Model(&db.Verification{}).Where("status != ?", string(db.VerificationStatusVerified)).Order("id asc").First(&verification).Error

if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont see the purpose if this error checking :o both cases return the same value

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed on f4a2a1f

return 0, nil
}

return 0, err
}
return verification.BlockHeight, err
}

func (client *IndexerQL) dealWithVerification(event *gnoindexerql.GetValidationRequestsTransactionsTransactionResponseEventsGnoEvent, blockHeight int) error {
var handle string
var callerAddress string
for _, attr := range event.Attrs {
switch attr.Key {
case "handle":
handle = attr.Value
case "from":
callerAddress = attr.Value
}
}

var verification db.Verification
err := client.db.Model(&db.Verification{}).Where("handle = ? AND address = ?", handle, callerAddress).Find(&verification).Error
if err != nil {
return err
}

if verification.Status == "verified" {
// Already verified.
return nil
}

client.logger.Infof("handle: %s, callerAddress: %s\n", handle, callerAddress)
res, err := http.DefaultClient.Get(fmt.Sprintf("https://raw.githubusercontent.com/%s/.gno/main/config.yml?version=1", handle))
if err != nil {
return err
}

defer res.Body.Close()
if res.StatusCode != 200 {
return client.updateVerification(handle, callerAddress, db.VerificationStatusConfigNotFound, blockHeight)
}

data, err := io.ReadAll(res.Body)
if err != nil {
client.updateVerification(handle, callerAddress, db.VerificationStatusInvalidData, blockHeight)
return err
}

githubConfiguredAddress := strings.TrimSpace(string(data))
if githubConfiguredAddress == callerAddress {
err = client.signer.CallVerify(githubConfiguredAddress)
if err != nil {
return err
}

return client.updateVerification(handle, callerAddress, db.VerificationStatusVerified, blockHeight)
}
return client.updateVerification(handle, callerAddress, db.VerificationStatusCallerAddressMismatch, blockHeight)
}

func (client *IndexerQL) updateVerification(handle, address string, status db.VerificationStatus, blockHeight int) error {
verification := db.Verification{
Handle: handle,
Address: address,
Status: string(status),
CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
BlockHeight: blockHeight,
}

return client.db.Model(&verification).Where("handle = ? AND address = ?", handle, address).Assign(db.Verification{Status: string(status)}).FirstOrCreate(&verification).Error
}
47 changes: 47 additions & 0 deletions gno_github_agent/db/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package db

import (
"fmt"

"gorm.io/driver/sqlite"
"gorm.io/gorm"
)

func New() (*gorm.DB, error) {
db, err := gorm.Open(sqlite.Open("github.db"), &gorm.Config{})
if err != nil {
return nil, fmt.Errorf("open sqlite db:%w", err)
}

err = db.AutoMigrate(allModels...)
if err != nil {
return nil, fmt.Errorf("migrate sqlite db:%w", err)
}

return db, nil
}

var allModels = []interface{}{
&Verification{},
}

type Verification struct {
gorm.Model
Id uint `json:"id" gorm:"unique;primaryKey;autoIncrement"`

Handle string
Address string
Status string
CreatedAt string
BlockHeight int
}

type VerificationStatus string

const (
VerificationStatusUnverified VerificationStatus = "unverified"
VerificationStatusVerified VerificationStatus = "verified"
VerificationStatusConfigNotFound VerificationStatus = "config_not_found"
VerificationStatusInvalidData VerificationStatus = "invalid_data"
VerificationStatusCallerAddressMismatch VerificationStatus = "caller_address_mismatch"
)
22 changes: 22 additions & 0 deletions gno_github_agent/gnoindexerql/genqlient.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
schema: indexer-schema.graphql
operations:
- indexer-operations.graphql
generated: gnoindexerQL.go

package: gnoindexerql

# We bind github's DateTime scalar type to Go's time.Time (which conveniently
# already defines MarshalJSON and UnmarshalJSON). This means genqlient will
# use time.Time when a query requests a DateTime, and is required for custom
# scalars.
bindings:
DateTime:
type: time.Time
DateTimeUtc:
type: string
I64:
type: string
PublicKey:
type: string
U64:
type: string
Loading
Loading