diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index f92ace0..9cb67a6 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4 with: - go-version: "1.26.4" + go-version: "1.25.11" - name: Setup Go Vulncheck run: go install golang.org/x/vuln/cmd/govulncheck@latest diff --git a/CHANGELOG.md b/CHANGELOG.md index a76ffa0..e0a60f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,34 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.7.1] - 2026-06-24 + +This version should add support to process new Gnoland testnet 13. Added support for Auth message +types, and the bank multi send should be supported. + +### Added + +- Feat(api): add new message types to the tx message route [e38c9f3](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/e38c9f38fe4b0a7285593196d89c140b29f71125) +- Feat(cli): add to setup db to init auth tables and multi send [1b56f5f](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/1b56f5f3832682d04457ca9b8300a6a0fdc06c5a) +- Feat(decoder): add convert to auth methods [72e2334](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/72e2334b44b2625e70ec3b863faf104c5f6ad3d2) +- Feat(sql_data_types): add schemas for the new auth msg types [0ecf0c7](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/0ecf0c7fed377b75f8532dbae7d39029a4521eb9) +- Feat(decoder): add auth msg types [32fb8df](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/32fb8df4c820a6d9a6c7782720194137aeb7bf87) +- Feat(timescaledb): add auth insert methods [33f4d34](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/33f4d340dc27fa31467625438fcdd266e07348b1) + +### Changes + +- Refac(timescaledb): move the GetAllBlockSigners to query_block.go [a4f9bf3](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/a4f9bf317c5da9bca5ff1abc4fb9bdebc144ed16) +- Refac(data_processor): add new auth types and partially move decoding [81ff897](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/81ff897728d48b4916c86aee79143ad2e15659b0) +- Refactor(decoder): refactor the decoder to use smaller fn per msg type [c71f39c](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/c71f39cbf4211f40e29193f39d2439506ba2628a) +- Deps: update gno to chain/test13 [f08460d](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/f08460d6a44dcf3cf437f8456f7c1ed6a85b0b65) + +### Fixed + +- Fix: add missing add address [f681153](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/f6811537e78ba1bcb9d46199a9938968b2cd7556) +- Fix(sql_data_types): fix chain_name to use enum type in database [ca36797](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/ca36797cfd6e92d3b9745ef0abe2f0ebd87551fc) +- Fix: add missing data to the create session [ac2130c](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/ac2130c71e40ad8bd435d49dc58624d06c27778e) +- Fix: dockerfile indexer.go path updated and force the toolcahin to auto [a04c48c](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/a04c48ca497a82565673900f90a6e0343232a1ba) +- Fix(ci): fix the release.yml to use correct path to indexer.go [61b96b3](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/61b96b3df53d342567fa8f46c69b63da31505312) ## [0.7.0] - 2026-06-14 diff --git a/api/handlers/database_test.go b/api/handlers/database_test.go index 232b253..8cee06c 100644 --- a/api/handlers/database_test.go +++ b/api/handlers/database_test.go @@ -16,11 +16,15 @@ type MockDatabase struct { blockSigners map[uint64]*database.BlockSigners latestBlock *database.BlockData - bankSend map[string]*database.BankSend - msgCall map[string]*database.MsgCall - msgAddPackage map[string]*database.MsgAddPackage - msgRun map[string]*database.MsgRun - msgTypes map[string][]string + bankSend map[string]*database.BankSend + bankMultiSend map[string][]*database.BankMultiSendRow + msgCall map[string]*database.MsgCall + msgAddPackage map[string]*database.MsgAddPackage + msgRun map[string]*database.MsgRun + msgAuthCrSession map[string]*database.MsgAuthCrSession + msgAuthRvSession map[string]*database.MsgAuthRvSession + msgAuthRvAll map[string]*database.MsgAuthRvAllSessions + msgTypes map[string][]string shouldError bool errorMsg string @@ -205,6 +209,50 @@ func (m *MockDatabase) GetMsgRun(ctx context.Context, txHash string, chainName s return []*database.MsgRun{msgRun}, nil } +func (m *MockDatabase) GetBankMultiSend(ctx context.Context, txHash string, chainName string) ([]*database.BankMultiSendRow, error) { + if m.shouldError { + return nil, m.simulatedError() + } + rows, ok := m.bankMultiSend[txHash] + if !ok { + return nil, notFoundErr("bank multi send not found") + } + return rows, nil +} + +func (m *MockDatabase) GetMsgAuthCrSession(ctx context.Context, txHash string, chainName string) ([]*database.MsgAuthCrSession, error) { + if m.shouldError { + return nil, m.simulatedError() + } + msg, ok := m.msgAuthCrSession[txHash] + if !ok { + return nil, notFoundErr("auth create session not found") + } + return []*database.MsgAuthCrSession{msg}, nil +} + +func (m *MockDatabase) GetMsgAuthRvSession(ctx context.Context, txHash string, chainName string) ([]*database.MsgAuthRvSession, error) { + if m.shouldError { + return nil, m.simulatedError() + } + msg, ok := m.msgAuthRvSession[txHash] + if !ok { + return nil, notFoundErr("auth revoke session not found") + } + return []*database.MsgAuthRvSession{msg}, nil +} + +func (m *MockDatabase) GetMsgAuthRvAllSessions(ctx context.Context, txHash string, chainName string) ([]*database.MsgAuthRvAllSessions, error) { + if m.shouldError { + return nil, m.simulatedError() + } + msg, ok := m.msgAuthRvAll[txHash] + if !ok { + return nil, notFoundErr("auth revoke all sessions not found") + } + return []*database.MsgAuthRvAllSessions{msg}, nil +} + func (m *MockDatabase) GetTransactionsByRange( ctx context.Context, chainName string, cursor string, limit uint64, direction database.Direction, ) ([]*database.Transaction, bool, error) { diff --git a/api/handlers/interface.go b/api/handlers/interface.go index 8a019dc..da354d3 100644 --- a/api/handlers/interface.go +++ b/api/handlers/interface.go @@ -68,9 +68,13 @@ type TransactionDbHandler interface { ) ([]*database.Transaction, error) GetMsgTypes(ctx context.Context, txHash string, chainName string) ([]string, error) GetBankSend(ctx context.Context, txHash string, chainName string) ([]*database.BankSend, error) + GetBankMultiSend(ctx context.Context, txHash string, chainName string) ([]*database.BankMultiSendRow, error) GetMsgCall(ctx context.Context, txHash string, chainName string) ([]*database.MsgCall, error) GetMsgAddPackage(ctx context.Context, txHash string, chainName string) ([]*database.MsgAddPackage, error) GetMsgRun(ctx context.Context, txHash string, chainName string) ([]*database.MsgRun, error) + GetMsgAuthCrSession(ctx context.Context, txHash string, chainName string) ([]*database.MsgAuthCrSession, error) + GetMsgAuthRvSession(ctx context.Context, txHash string, chainName string) ([]*database.MsgAuthRvSession, error) + GetMsgAuthRvAllSessions(ctx context.Context, txHash string, chainName string) ([]*database.MsgAuthRvAllSessions, error) GetTransactionsByRange( ctx context.Context, chainName string, cursor string, limit uint64, direction database.Direction, ) ([]*database.Transaction, bool, error) diff --git a/api/handlers/transactions.go b/api/handlers/transactions.go index 331107c..0c5bd71 100644 --- a/api/handlers/transactions.go +++ b/api/handlers/transactions.go @@ -67,19 +67,35 @@ func (h *TransactionsHandler) GetTransactionMessage( for _, msgType := range msgTypes { switch msgType { case "bank_msg_send": - if err := h.getBankSendResponse(ctx, msgType, txHashBase64, h.chainName, &response); err != nil { + if err := h.getBankSendResponse(ctx, msgType, txHashBase64, h.chainName, response); err != nil { + return nil, err + } + case "bank_msg_multi_send": + if err := h.getBankMultiSendResponse(ctx, msgType, txHashBase64, h.chainName, response); err != nil { return nil, err } case "vm_msg_call": - if err := h.getMsgCallResponse(ctx, msgType, txHashBase64, h.chainName, &response); err != nil { + if err := h.getMsgCallResponse(ctx, msgType, txHashBase64, h.chainName, response); err != nil { return nil, err } case "vm_msg_add_package": - if err := h.getMsgAddPackageResponse(ctx, msgType, txHashBase64, h.chainName, &response); err != nil { + if err := h.getMsgAddPackageResponse(ctx, msgType, txHashBase64, h.chainName, response); err != nil { return nil, err } case "vm_msg_run": - if err := h.getMsgRunResponse(ctx, msgType, txHashBase64, h.chainName, &response); err != nil { + if err := h.getMsgRunResponse(ctx, msgType, txHashBase64, h.chainName, response); err != nil { + return nil, err + } + case "auth_msg_create_session": + if err := h.getMsgAuthCrSessionResponse(ctx, msgType, txHashBase64, h.chainName, response); err != nil { + return nil, err + } + case "auth_msg_revoke_session": + if err := h.getMsgAuthRvSessionResponse(ctx, msgType, txHashBase64, h.chainName, response); err != nil { + return nil, err + } + case "auth_msg_revoke_all_sessions": + if err := h.getMsgAuthRvAllSessionsResponse(ctx, msgType, txHashBase64, h.chainName, response); err != nil { return nil, err } default: @@ -331,7 +347,7 @@ func (h *TransactionsHandler) getMsgCallResponse( msgType string, txHash string, chainName string, - response *map[int16]humatypes.TransactionMessage, + response map[int16]humatypes.TransactionMessage, ) error { data, err := h.db.GetMsgCall(ctx, txHash, chainName) if err != nil { @@ -339,7 +355,7 @@ func (h *TransactionsHandler) getMsgCallResponse( } for _, d := range data { index := d.MessageCounter - (*response)[index] = humatypes.TransactionMessage{ + response[index] = humatypes.TransactionMessage{ MessageType: msgType, TxHash: d.TxHash, Timestamp: d.Timestamp, @@ -361,7 +377,7 @@ func (h *TransactionsHandler) getMsgAddPackageResponse( msgType string, txHash string, chainName string, - response *map[int16]humatypes.TransactionMessage, + response map[int16]humatypes.TransactionMessage, ) error { data, err := h.db.GetMsgAddPackage(ctx, txHash, chainName) if err != nil { @@ -369,7 +385,7 @@ func (h *TransactionsHandler) getMsgAddPackageResponse( } for _, d := range data { index := d.MessageCounter - (*response)[index] = humatypes.TransactionMessage{ + response[index] = humatypes.TransactionMessage{ MessageType: msgType, TxHash: d.TxHash, Timestamp: d.Timestamp, @@ -391,7 +407,7 @@ func (h *TransactionsHandler) getMsgRunResponse( msgType string, txHash string, chainName string, - response *map[int16]humatypes.TransactionMessage, + response map[int16]humatypes.TransactionMessage, ) error { data, err := h.db.GetMsgRun(ctx, txHash, chainName) if err != nil { @@ -399,7 +415,7 @@ func (h *TransactionsHandler) getMsgRunResponse( } for _, d := range data { index := d.MessageCounter - (*response)[index] = humatypes.TransactionMessage{ + response[index] = humatypes.TransactionMessage{ MessageType: msgType, TxHash: d.TxHash, Timestamp: d.Timestamp, @@ -415,13 +431,143 @@ func (h *TransactionsHandler) getMsgRunResponse( return nil } +// Helper method that collects bank multi-send data from the database and adds it to the response. +// Rows are aggregated by message_counter: direction=true rows become Outputs, direction=false become Inputs. +func (h *TransactionsHandler) getBankMultiSendResponse( + ctx context.Context, + msgType string, + txHash string, + chainName string, + response map[int16]humatypes.TransactionMessage, +) error { + data, err := h.db.GetBankMultiSend(ctx, txHash, chainName) + if err != nil { + return internalError("GetBankMultiSend", err) + } + type agg struct { + base humatypes.TransactionMessage + inputs []humatypes.MultiSendEntry + outputs []humatypes.MultiSendEntry + } + byCounter := make(map[int16]*agg) + for _, d := range data { + idx := d.MessageCounter + if _, ok := byCounter[idx]; !ok { + byCounter[idx] = &agg{ + base: humatypes.TransactionMessage{ + MessageType: msgType, + TxHash: d.TxHash, + Timestamp: d.Timestamp, + Signers: d.Signers, + }, + } + } + entry := humatypes.MultiSendEntry{Address: d.Address, Coins: d.Coins} + if d.Direction { + byCounter[idx].outputs = append(byCounter[idx].outputs, entry) + } else { + byCounter[idx].inputs = append(byCounter[idx].inputs, entry) + } + } + for idx, a := range byCounter { + msg := a.base + msg.Inputs = a.inputs + msg.Outputs = a.outputs + response[idx] = msg + } + return nil +} + +// Helper method that collects auth create-session data from the database and adds it to the response +func (h *TransactionsHandler) getMsgAuthCrSessionResponse( + ctx context.Context, + msgType string, + txHash string, + chainName string, + response map[int16]humatypes.TransactionMessage, +) error { + data, err := h.db.GetMsgAuthCrSession(ctx, txHash, chainName) + if err != nil { + return internalError("GetMsgAuthCrSession", err) + } + for _, d := range data { + index := d.MessageCounter + expiresAt := d.ExpiresAt + spendPeriod := d.SpendPeriod + response[index] = humatypes.TransactionMessage{ + MessageType: msgType, + TxHash: d.TxHash, + Timestamp: d.Timestamp, + Signers: d.Signers, + Creator: d.Creator, + SessionKey: d.SessionKey, + ExpiresAt: &expiresAt, + AllowPaths: d.AllowPaths, + SpendLimit: d.SpendLimit, + SpendPeriod: &spendPeriod, + } + } + return nil +} + +// Helper method that collects auth revoke-session data from the database and adds it to the response +func (h *TransactionsHandler) getMsgAuthRvSessionResponse( + ctx context.Context, + msgType string, + txHash string, + chainName string, + response map[int16]humatypes.TransactionMessage, +) error { + data, err := h.db.GetMsgAuthRvSession(ctx, txHash, chainName) + if err != nil { + return internalError("GetMsgAuthRvSession", err) + } + for _, d := range data { + index := d.MessageCounter + response[index] = humatypes.TransactionMessage{ + MessageType: msgType, + TxHash: d.TxHash, + Timestamp: d.Timestamp, + Signers: d.Signers, + Creator: d.Creator, + SessionKey: d.SessionKey, + } + } + return nil +} + +// Helper method that collects auth revoke-all-sessions data from the database and adds it to the response +func (h *TransactionsHandler) getMsgAuthRvAllSessionsResponse( + ctx context.Context, + msgType string, + txHash string, + chainName string, + response map[int16]humatypes.TransactionMessage, +) error { + data, err := h.db.GetMsgAuthRvAllSessions(ctx, txHash, chainName) + if err != nil { + return internalError("GetMsgAuthRvAllSessions", err) + } + for _, d := range data { + index := d.MessageCounter + response[index] = humatypes.TransactionMessage{ + MessageType: msgType, + TxHash: d.TxHash, + Timestamp: d.Timestamp, + Signers: d.Signers, + Creator: d.Creator, + } + } + return nil +} + // Helper method that collects bank send data from the database and adds it to the response func (h *TransactionsHandler) getBankSendResponse( ctx context.Context, msgType string, txHash string, chainName string, - response *map[int16]humatypes.TransactionMessage, + response map[int16]humatypes.TransactionMessage, ) error { data, err := h.db.GetBankSend(ctx, txHash, chainName) if err != nil { @@ -429,7 +575,7 @@ func (h *TransactionsHandler) getBankSendResponse( } for _, d := range data { index := d.MessageCounter - (*response)[index] = humatypes.TransactionMessage{ + response[index] = humatypes.TransactionMessage{ MessageType: msgType, TxHash: d.TxHash, Timestamp: d.Timestamp, diff --git a/api/huma-types/transaction.go b/api/huma-types/transaction.go index 6f08a6d..d8c044a 100644 --- a/api/huma-types/transaction.go +++ b/api/huma-types/transaction.go @@ -17,14 +17,15 @@ type TransactionBasicGetOutput struct { Body database.Transaction } -// TransactionMessage represents a unified transaction message type that can be one of: -// bank_msg_send, vm_msg_call, vm_msg_add_package, or vm_msg_run -// not maybe the best implementation, but this one works for now -// to future me, if you figure out a better way to do this, please do so -// for now this is good enough +type MultiSendEntry struct { + Address string `json:"address" doc:"Address"` + Coins []database.Amount `json:"coins" doc:"Coins"` +} + +// TransactionMessage represents a unified transaction message type. type TransactionMessage struct { // Common fields (always present) - MessageType string `json:"message_type" doc:"Type of message: bank_msg_send, vm_msg_call, vm_msg_add_package, or vm_msg_run" enum:"bank_msg_send,vm_msg_call,vm_msg_add_package,vm_msg_run"` + MessageType string `json:"message_type" doc:"Type of message" enum:"bank_msg_send,bank_msg_multi_send,vm_msg_call,vm_msg_add_package,vm_msg_run,auth_msg_create_session,auth_msg_revoke_session,auth_msg_revoke_all_sessions"` TxHash string `json:"tx_hash" doc:"Transaction hash (base64 encoded)"` Timestamp time.Time `json:"timestamp" doc:"Transaction timestamp"` Signers []string `json:"signers" doc:"Signers (addresses)"` @@ -34,13 +35,17 @@ type TransactionMessage struct { ToAddress string `json:"to_address,omitempty" doc:"To address (only for bank_msg_send)"` Amount []database.Amount `json:"amount,omitempty" doc:"Amount (only for bank_msg_send)"` + // BankMultiSend specific fields + Inputs []MultiSendEntry `json:"inputs,omitempty" doc:"Input entries (only for bank_msg_multi_send)"` + Outputs []MultiSendEntry `json:"outputs,omitempty" doc:"Output entries (only for bank_msg_multi_send)"` + // MsgCall specific fields Caller string `json:"caller,omitempty" doc:"Caller address (for vm_msg_call and vm_msg_run)"` FuncName string `json:"func_name,omitempty" doc:"Function name (only for vm_msg_call)"` Args string `json:"args,omitempty" doc:"Arguments (only for vm_msg_call)"` - // MsgAddPackage and MsgRun specific fields - Creator string `json:"creator,omitempty" doc:"Creator address (only for vm_msg_add_package)"` + // MsgAddPackage, MsgRun, and auth message specific fields + Creator string `json:"creator,omitempty" doc:"Creator address (for vm_msg_add_package and auth messages)"` PkgName string `json:"pkg_name,omitempty" doc:"Package name (for vm_msg_add_package and vm_msg_run)"` PkgFileNames []string `json:"pkg_file_names,omitempty" doc:"Package file names (for vm_msg_add_package and vm_msg_run)"` @@ -48,6 +53,13 @@ type TransactionMessage struct { PkgPath string `json:"pkg_path,omitempty" doc:"Package path (for vm_msg_call, vm_msg_add_package, and vm_msg_run)"` Send []database.Amount `json:"send,omitempty" doc:"Send amount (for vm_msg_call, vm_msg_add_package, and vm_msg_run)"` MaxDeposit []database.Amount `json:"max_deposit,omitempty" doc:"Max deposit (for vm_msg_call, vm_msg_add_package, and vm_msg_run)"` + + // Auth message specific fields + SessionKey string `json:"session_key,omitempty" doc:"Session key hex (for auth_msg_create_session and auth_msg_revoke_session)"` + AllowPaths []string `json:"allow_paths,omitempty" doc:"Allowed paths (only for auth_msg_create_session)"` + ExpiresAt *time.Time `json:"expires_at,omitempty" doc:"Session expiry (only for auth_msg_create_session)"` + SpendLimit []database.Amount `json:"spend_limit,omitempty" doc:"Spend limit (only for auth_msg_create_session)"` + SpendPeriod *int64 `json:"spend_period,omitempty" doc:"Spend period in seconds; 0 means infinite (only for auth_msg_create_session)"` } // TransactionMessageGetOutput represents the response containing all messages within a transaction diff --git a/cliff.toml b/cliff.toml index bd73921..7e5f9e5 100644 --- a/cliff.toml +++ b/cliff.toml @@ -1,10 +1,15 @@ # git-cliff configuration for spectra-gnoland-indexer # Tuned to the existing CHANGELOG.md style (Keep a Changelog + Added/Changes/Fixed). -# Generate the next section with: git cliff --unreleased --tag v0.7.0 -# Regenerate the whole file with: git cliff -o CHANGELOG.md +# +# Release workflow example: +# 1. Preview: git cliff --unreleased --tag v0.7.1 +# 2. Prepend: git cliff --unreleased --tag v0.7.1 --prepend CHANGELOG.md +# 3. Edit the new section: add a release summary paragraph below the heading. +# 4. Commit: git add CHANGELOG.md && git commit -m "chore: release v0.7.1" +# 5. Tag: git tag -a v0.7.1 -m "v0.7.1" && git push --follow-tags [changelog] -# Static header that matches the existing CHANGELOG.md preamble. +# Static header — only emitted when regenerating the full file (-o), not on --prepend. header = """ # Changelog @@ -24,7 +29,7 @@ body = """ {% for group, commits in commits | group_by(attribute="group") %} ### {{ group | striptags | trim | upper_first }} {% for commit in commits %} -- {{ commit.message | upper_first }}\ +- {{ commit.message | split(pat="\n") | first | upper_first }}\ {% if commit.id %} [{{ commit.id | truncate(length=7, end="") }}](https://github.com/Cogwheel-Validator/spectra-gnoland-indexer/commit/{{ commit.id }}){% endif %}\ {% endfor %} {% endfor %}\n @@ -32,31 +37,26 @@ body = """ trim = true [git] -# For now do not strictly follow Conventional Commits. After v1 this should be changed. conventional_commits = false filter_unconventional = false -# Don't split a commit into multiple changelog entries. split_commits = false -# Strip leading/trailing whitespace from messages. protect_breaking_commits = false -# Tidy up the subject line before it lands in the changelog: -# drop the "prefix:" token (we re-group on it below) and the trailing period. commit_preprocessors = [ - { pattern = '^\s*(?i)(add|added|feat|feature|change|changed|chore|deps|dep|refactor|refac|fix|bug|test|vuln|vulncheck|audit| ?fix|fixed|lint|go fmt|go fix|perf|docs|doc|test|ci|build)\s*:\s*', replace = "" }, + # Only strip a trailing period from the subject — the type prefix stays so parsers can see it. { pattern = '\.\s*$', replace = "" }, ] -# Map each commit to a group by matching the ORIGINAL prefix. -# The markers control section ordering (git-cliff sorts groups by them). +# Parsers match against the FULL commit message (including type prefix) since conventional_commits = false. +# (\([^)]+\))? handles optional scopes: both "feat:" and "feat(api):" are matched. +# The markers control section sort order (lower number = higher in the output). commit_parsers = [ - { message = '^\s*(?i)(add|added|feat|feature)\s*:', group = "Added" }, - { message = '^\s*(?i)(fix|bug ?fix|fixed)\s*:', group = "Fixed" }, - { message = '^\s*(?i)(change|changed|chore|deps|dep|refactor|refac|lint|go fmt|go fix|perf|build|ci)\s*:', group = "Changes" }, - { message = '^\s*(?i)(test|audit|vulncheck|vuln)\s*:', group = "Tests and Code Check" }, - # Anything without a recognised prefix lands here so nothing silently vanishes. - # Triage these into the right section (or, better, start prefixing your commits). - { message = '.*', group = "Uncategorized (please triage)" }, + { message = '^\s*(?i)(feat|feature|add|added)(\([^)]+\))?:', group = "Added" }, + { message = '^\s*(?i)(fix|bug ?fix|fixed)(\([^)]+\))?:', group = "Fixed" }, + { message = '^\s*(?i)(change|changed|chore|deps|dep|refactor|refac|lint|go fmt|go fix|perf|docs|doc|build|ci)(\([^)]+\))?:', group = "Changes" }, + { message = '^\s*(?i)(test|audit|vulncheck|vuln)(\([^)]+\))?:', group = "Tests and Code Check" }, + # Catch-all so nothing silently vanishes — triage these before releasing. + { message = '.*', group = "Uncategorized (please triage)" }, ] # Process commits newest-first within each group. diff --git a/go.mod b/go.mod index de80c9c..ce6b644 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.26.4 require ( github.com/caarlos0/env/v6 v6.10.1 github.com/danielgtaylor/huma/v2 v2.37.3 - github.com/gnolang/gno v1.1.0 + github.com/gnolang/gno v0.0.0-20260605100654-f45cc5c88e6a github.com/go-chi/chi/v5 v5.2.5 github.com/go-chi/cors v1.2.2 github.com/jackc/pgx/v5 v5.9.2 @@ -26,7 +26,14 @@ require ( google.golang.org/protobuf v1.36.11 ) -require go.opentelemetry.io/otel/log/logtest v0.18.0 // indirect +require ( + github.com/bits-and-blooms/bitset v1.14.2 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.14.0 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + go.opentelemetry.io/otel/log/logtest v0.18.0 // indirect + rsc.io/tmplfunc v0.0.3 // indirect +) require ( github.com/btcsuite/btcd/btcec/v2 v2.3.6 // indirect diff --git a/go.sum b/go.sum index 3d0d999..e757037 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,10 @@ github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bits-and-blooms/bitset v1.14.2 h1:YXVoyPndbdvcEVcseEovVfp0qjJp7S+i5+xgp/Nfbdc= +github.com/bits-and-blooms/bitset v1.14.2/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bmatsuo/lmdb-go v1.8.0 h1:ohf3Q4xjXZBKh4AayUY4bb2CXuhRAI8BYGlJq08EfNA= +github.com/bmatsuo/lmdb-go v1.8.0/go.mod h1:wWPZmKdOAZsl4qOqkowQ1aCrFie1HU8gWloHMCeAUdM= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= @@ -52,6 +56,10 @@ github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwP github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.14.0 h1:DDBdl4HaBtdQsq/wfMwJvZNE80sHidrK3Nfrefatm0E= +github.com/consensys/gnark-crypto v0.14.0/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCeWHHrb1li/n1XoU0= github.com/cosmos/gogoproto v1.7.2 h1:5G25McIraOC0mRFv9TVO139Uh3OklV2hczr13KKVHCA= github.com/cosmos/gogoproto v1.7.2/go.mod h1:8S7w53P1Y1cHwND64o0BnArT6RmdgIvsBuco6uTllsk= github.com/cosmos/ics23/go v0.11.0 h1:jk5skjT0TqX5e5QJbEnwXIS2yI2vnmLOgpQPeM5RtnU= @@ -79,6 +87,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/dot v1.11.0 h1:zsrhCuFHAJge/aZIC4N4LdHy5tqYu4tWEaUzIwdYj4Y= github.com/emicklei/dot v1.11.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= +github.com/erigontech/mdbx-go v0.40.0 h1:wbjPSyF/jQWafvNYZOkz93m2kZRnh2MN5OkH6kOroGs= +github.com/erigontech/mdbx-go v0.40.0/go.mod h1:tHUS492F5YZvccRqatNdpTDQAaN+Vv4HRARYq89KqeY= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -87,8 +97,8 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/getsentry/sentry-go v0.35.0 h1:+FJNlnjJsZMG3g0/rmmP7GiKjQoUF5EXfEtBwtPtkzY= github.com/getsentry/sentry-go v0.35.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE= -github.com/gnolang/gno v1.1.0 h1:03wmdxmXgnGn49xVYVqEu/Ri28VFYnUnMjfHIKEnwBo= -github.com/gnolang/gno v1.1.0/go.mod h1:990lKB5nQ8JHad9HZnGztOR5C0Q3KJQIsb9jB0pB6oE= +github.com/gnolang/gno v0.0.0-20260605100654-f45cc5c88e6a h1:D61mJwrkYaEdo7S49vm3bxpQmNXZ8rQOWUNHKs5aCBY= +github.com/gnolang/gno v0.0.0-20260605100654-f45cc5c88e6a/go.mod h1:09QMoeuZkGy1wBU07NMe8SNOkqmCODFJmVYg9IkGWbs= github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE= @@ -122,6 +132,7 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -132,6 +143,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDe github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/cgosymbolizer v0.0.0-20241129212102-9c50ad6b591e h1:8AnObPi8WmIgjwcidUxaREhXMSpyUJeeSrIkZTXdabw= +github.com/ianlancetaylor/cgosymbolizer v0.0.0-20241129212102-9c50ad6b591e/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -154,6 +167,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= +github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= @@ -162,6 +177,9 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -230,12 +248,8 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/bridges/otelzerolog v0.0.0-20240809024635-0c3fcdf3c470 h1:sWl5wVvtCqwmq6E4hMwTvYOZ+2KK/mobiYUKGtDPhq8= go.opentelemetry.io/contrib/bridges/otelzerolog v0.0.0-20240809024635-0c3fcdf3c470/go.mod h1:19QonlFAPKOV21UMTHW8yWjGBZ2ilfhSAyU5LKPA5HE= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0/go.mod h1:ji9vId85hMxqfvICA0Jt8JqEdrXaAkcpkI9HPXya0ro= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0 h1:owlhcJ3QO3X0YTDTCcDZ4V+6aVDkWbNmBoQ5NUp7Oww= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0/go.mod h1:MP4eemTiI9zC8fgg+DYynhYDYf3ba72S376TvP+Ye0Q= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc= @@ -246,33 +260,22 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bT go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= -go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= -go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= go.opentelemetry.io/otel/log v0.20.0 h1:/5i0vuHxCLWUfChWG41K9wkM0jafruPw9NU1/RCJirs= go.opentelemetry.io/otel/log v0.20.0/go.mod h1:wOcMcjsZpG8x7Bak7IhSi/lg8wscV2C1VdrKCLPlt0E= go.opentelemetry.io/otel/log/logtest v0.18.0 h1:2QeyoKJdIgK2LJhG1yn78o/zmpXx1EditeyRDREqVS8= go.opentelemetry.io/otel/log/logtest v0.18.0/go.mod h1:v1vh3PYR9zIa5MK6HwkH2lMrLBg/Y9Of6Qc+krlesX0= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= +go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= -go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= -go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= go.opentelemetry.io/otel/sdk/log v0.20.0 h1:vM3xI7TQgKPiSghe6urZtAkyFY7SodrSpC83CffDFuY= go.opentelemetry.io/otel/sdk/log v0.20.0/go.mod h1:Knej2nmsTUzN79T2eeXdRsjjPcoxoq2pUyUHz9TFyyU= -go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk= -go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk= go.opentelemetry.io/otel/sdk/log/logtest v0.20.0 h1:OqdRZ1guyzamK3M6LlRsmGqRrjkHWw6WZOKKli5ELpg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/sdk/log/logtest v0.20.0/go.mod h1:PuMIlm7zAt7c3z8zfOI5ox4iT1Z87We+PF6YoINux/M= go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= @@ -288,14 +291,10 @@ go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfP golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -303,13 +302,9 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -321,40 +316,26 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348 h1:U8orV30l6KpDsi9dxU0CoJZGbjS8EEpw+6ba+XwGPQA= -google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348/go.mod h1:Yzdzr5OOZFgSsEV2D/Xi9NL3bszpXFAg0hFJiRohcD8= google.golang.org/genproto/googleapis/api v0.0.0-20260610212136-7ab31c22f7ad h1:3iLyITS/sySRwbUKoC7ogfj2Yr1Cjs0pfaRKj5U5HEw= google.golang.org/genproto/googleapis/api v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:KdNqO+rCIWgFumrNBSEDlDNrkrQnpkax7Tv1WxNY8V4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 h1:pfIbyB44sWzHiCpRqIen67ZQnVXSfIxWrqUMk1qwODE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad h1:45WmJvIV6C2+O/jjLkPUH+F3aOj/1miDoU2DD0+NWbg= google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw= -google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -376,3 +357,5 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/indexer/cli/setup.go b/indexer/cli/setup.go index ea11f51..0363c25 100644 --- a/indexer/cli/setup.go +++ b/indexer/cli/setup.go @@ -249,6 +249,12 @@ func createHypertables(dbInit *dbinit.DBInitializer, chainName string) error { OrderBy: "timestamp DESC", SegmentBy: []string{"chain_name", "message_counter"}, }}, + {sdt.MsgMultiSend{}, dbinit.HypertableParams{ + PartitionColumn: "timestamp", + ChunkInterval: "1 week", + OrderBy: "timestamp DESC", + SegmentBy: []string{"chain_name", "message_counter"}, + }}, {sdt.MsgCall{}, dbinit.HypertableParams{ PartitionColumn: "timestamp", ChunkInterval: "1 week", @@ -267,17 +273,29 @@ func createHypertables(dbInit *dbinit.DBInitializer, chainName string) error { OrderBy: "timestamp DESC", SegmentBy: []string{"chain_name", "message_counter"}, }}, - {sdt.MsgMultiSend{}, dbinit.HypertableParams{ + {sdt.TxHashId{}, dbinit.HypertableParams{ + PartitionColumn: "timestamp", + ChunkInterval: "1 week", + OrderBy: "timestamp DESC", + SegmentBy: []string{"chain_name"}, + }}, + {sdt.MsgAuthCrSession{}, dbinit.HypertableParams{ PartitionColumn: "timestamp", ChunkInterval: "1 week", OrderBy: "timestamp DESC", SegmentBy: []string{"chain_name", "message_counter"}, }}, - {sdt.TxHashId{}, dbinit.HypertableParams{ + {sdt.MsgAuthRvSession{}, dbinit.HypertableParams{ PartitionColumn: "timestamp", ChunkInterval: "1 week", OrderBy: "timestamp DESC", - SegmentBy: []string{"chain_name"}, + SegmentBy: []string{"chain_name", "message_counter"}, + }}, + {sdt.MsgAuthRvAllSessions{}, dbinit.HypertableParams{ + PartitionColumn: "timestamp", + ChunkInterval: "1 week", + OrderBy: "timestamp DESC", + SegmentBy: []string{"chain_name", "message_counter"}, }}, } @@ -485,7 +503,7 @@ var createUserCmd = &cobra.Command{ var createConfigCmd = &cobra.Command{ Use: "create-config", Short: "Generate a config with default values.", - Long: `Generate a config with default values. It will make a config file with default values. + Long: `Generate a config with default values. It will make a config file with default values. You can add --overwrite to overwrite the existing config file. And you can use --config to specify the path`, RunE: func(cmd *cobra.Command, args []string) error { l := logger.Get() diff --git a/indexer/data_processor/operator.go b/indexer/data_processor/operator.go index 9b5d34a..b89b3eb 100644 --- a/indexer/data_processor/operator.go +++ b/indexer/data_processor/operator.go @@ -440,20 +440,10 @@ func (d *DataProcessor) ProcessMessages( wg.Wait() - aggregatedDbGroups := &decoder.DbMessageGroups{ - MsgSend: make([]sqlDataTypes.MsgSend, 0), - MsgMultiSend: make([]sqlDataTypes.MsgMultiSend, 0), - MsgCall: make([]sqlDataTypes.MsgCall, 0), - MsgAddPkg: make([]sqlDataTypes.MsgAddPackage, 0), - MsgRun: make([]sqlDataTypes.MsgRun, 0), - } + aggregatedDbGroups := &decoder.DbMessageGroups{} for _, result := range msgResults { if result != nil { - aggregatedDbGroups.MsgSend = append(aggregatedDbGroups.MsgSend, result.MsgSend...) - aggregatedDbGroups.MsgMultiSend = append(aggregatedDbGroups.MsgMultiSend, result.MsgMultiSend...) - aggregatedDbGroups.MsgCall = append(aggregatedDbGroups.MsgCall, result.MsgCall...) - aggregatedDbGroups.MsgAddPkg = append(aggregatedDbGroups.MsgAddPkg, result.MsgAddPkg...) - aggregatedDbGroups.MsgRun = append(aggregatedDbGroups.MsgRun, result.MsgRun...) + aggregatedDbGroups.Merge(result) } } @@ -470,13 +460,16 @@ func (d *DataProcessor) ProcessMessages( return fmt.Errorf("failed to insert optimized messages: %w", err) } - l.Info().Msgf("Messages processed from %d to %d: MsgSend=%d, MsgCall=%d, MsgAddPkg=%d, MsgRun=%d, MsgMultiSend=%d", + l.Info().Msgf("Messages processed from %d to %d: MsgSend=%d, MsgCall=%d, MsgAddPkg=%d, MsgRun=%d, MsgMultiSend=%d, MsgAuthCrSession=%d, MsgAuthRvSession=%d, MsgAuthRvAllSessions=%d", fromHeight, toHeight, len(aggregatedDbGroups.MsgSend), len(aggregatedDbGroups.MsgCall), len(aggregatedDbGroups.MsgAddPkg), len(aggregatedDbGroups.MsgRun), len(aggregatedDbGroups.MsgMultiSend), + len(aggregatedDbGroups.MsgAuthCrSession), + len(aggregatedDbGroups.MsgAuthRvSession), + len(aggregatedDbGroups.MsgAuthRvAllSessions), ) return nil @@ -585,50 +578,34 @@ func (d *DataProcessor) processMessageGroup( results[idx] = dbMessageGroups } -// insertDbMessageGroups performs optimized batch insertions using address IDs -func (d *DataProcessor) insertDbMessageGroups(groups *decoder.DbMessageGroups) error { - var insertErrors = make([]error, 0) - - msgSendCount := len(groups.MsgSend) - msgCallCount := len(groups.MsgCall) - msgAddPkgCount := len(groups.MsgAddPkg) - msgRunCount := len(groups.MsgRun) - msgMultiSendCount := len(groups.MsgMultiSend) - - // Insert DbMsgSend messages with address IDs - if msgSendCount > 0 { - d.insertMessage(msgSendInserter(groups.MsgSend), msgSendCount, &insertErrors) - } - - // Insert DbMsgCall messages with address IDs - if msgCallCount > 0 { - d.insertMessage(msgCallInserter(groups.MsgCall), msgCallCount, &insertErrors) - } - - // Insert DbMsgAddPackage messages with address IDs - if msgAddPkgCount > 0 { - d.insertMessage(msgAddPackageInserter(groups.MsgAddPkg), msgAddPkgCount, &insertErrors) - } - - // Insert DbMsgRun messages with address IDs - if msgRunCount > 0 { - d.insertMessage(msgRunInserter(groups.MsgRun), msgRunCount, &insertErrors) +func allInserters(g *decoder.DbMessageGroups) []messageInserter { + return []messageInserter{ + msgSendInserter(g.MsgSend), + msgMultiSendInserter(g.MsgMultiSend), + msgCallInserter(g.MsgCall), + msgAddPackageInserter(g.MsgAddPkg), + msgRunInserter(g.MsgRun), + msgAuthCrSessionInserter(g.MsgAuthCrSession), + msgAuthRvSessionInserter(g.MsgAuthRvSession), + msgAuthRvAllSessionsInserter(g.MsgAuthRvAllSessions), } +} - // Insert DbMsgMultiSend messages with address IDs - if msgMultiSendCount > 0 { - d.insertMessage(msgMultiSendInserter(groups.MsgMultiSend), msgMultiSendCount, &insertErrors) +// insertDbMessageGroups performs optimized batch insertions using address IDs +func (d *DataProcessor) insertDbMessageGroups(groups *decoder.DbMessageGroups) error { + var insertErrors []error + for _, ins := range allInserters(groups) { + if ins.count() > 0 { + d.insertMessage(ins, ins.count(), &insertErrors) + } } - - // Combine all errors if any occurred if len(insertErrors) > 0 { - var errorMessages []string - for _, err := range insertErrors { - errorMessages = append(errorMessages, err.Error()) + msgs := make([]string, len(insertErrors)) + for i, err := range insertErrors { + msgs[i] = err.Error() } - return fmt.Errorf("multiple insertion errors: %s", strings.Join(errorMessages, "; ")) + return fmt.Errorf("multiple insertion errors: %s", strings.Join(msgs, "; ")) } - return nil } @@ -743,20 +720,8 @@ func (d *DataProcessor) findHashes(txIds []int64) []string { // createAddressTx builds a flat slice of AddressTx rows from all message groups. func createAddressTx(msgGroups *decoder.DbMessageGroups) []sqlDataTypes.AddressTx { seen := make(map[key]sqlDataTypes.AddressTx) - for _, m := range msgGroups.MsgSend { - addToAddressTx(seen, m.GetAllAddresses(), m.ChainName, m.Timestamp, m.TableName()) - } - for _, m := range msgGroups.MsgCall { - addToAddressTx(seen, m.GetAllAddresses(), m.ChainName, m.Timestamp, m.TableName()) - } - for _, m := range msgGroups.MsgAddPkg { - addToAddressTx(seen, m.GetAllAddresses(), m.ChainName, m.Timestamp, m.TableName()) - } - for _, m := range msgGroups.MsgRun { - addToAddressTx(seen, m.GetAllAddresses(), m.ChainName, m.Timestamp, m.TableName()) - } - for _, m := range msgGroups.MsgMultiSend { - addToAddressTx(seen, m.GetAllAddresses(), m.ChainName, m.Timestamp, m.TableName()) + for _, entry := range msgGroups.AllAddressEntries() { + addToAddressTx(seen, entry.Addresses, entry.ChainName, entry.Timestamp, entry.MsgType) } result := make([]sqlDataTypes.AddressTx, 0, len(seen)) for _, e := range seen { diff --git a/indexer/data_processor/operator_test.go b/indexer/data_processor/operator_test.go index c84f50e..d0560ac 100644 --- a/indexer/data_processor/operator_test.go +++ b/indexer/data_processor/operator_test.go @@ -54,6 +54,18 @@ func (m *MockDatabase) InsertMsgMultiSend(ctx context.Context, messages []sqlDat return m.LastInsertError } +func (m *MockDatabase) InsertMsgAuthCrSession(ctx context.Context, messages []sqlDataTypes.MsgAuthCrSession) error { + return m.LastInsertError +} + +func (m *MockDatabase) InsertMsgAuthRvSession(ctx context.Context, messages []sqlDataTypes.MsgAuthRvSession) error { + return m.LastInsertError +} + +func (m *MockDatabase) InsertMsgAuthRvAllSessions(ctx context.Context, messages []sqlDataTypes.MsgAuthRvAllSessions) error { + return m.LastInsertError +} + func (m *MockDatabase) InsertTxHashIds(ctx context.Context, txHashes []string, timestamps []time.Time, chainName string) (map[string]int64, error) { return nil, m.LastInsertError } diff --git a/indexer/data_processor/types.go b/indexer/data_processor/types.go index 6006c6c..3f3cb2b 100644 --- a/indexer/data_processor/types.go +++ b/indexer/data_processor/types.go @@ -18,6 +18,9 @@ type Database interface { InsertMsgAddPackage(ctx context.Context, messages []sqlDataTypes.MsgAddPackage) error InsertMsgRun(ctx context.Context, messages []sqlDataTypes.MsgRun) error InsertMsgMultiSend(ctx context.Context, messages []sqlDataTypes.MsgMultiSend) error + InsertMsgAuthCrSession(ctx context.Context, messages []sqlDataTypes.MsgAuthCrSession) error + InsertMsgAuthRvSession(ctx context.Context, messages []sqlDataTypes.MsgAuthRvSession) error + InsertMsgAuthRvAllSessions(ctx context.Context, messages []sqlDataTypes.MsgAuthRvAllSessions) error InsertAddressTx(ctx context.Context, addresses []sqlDataTypes.AddressTx) error InsertTxHashIds(ctx context.Context, txHashes []string, timestamps []time.Time, chainName string) (map[string]int64, error) } @@ -151,6 +154,48 @@ func (m msgMultiSendInserter) getTxIds() []int64 { return txIds } +type msgAuthCrSessionInserter []sqlDataTypes.MsgAuthCrSession + +func (m msgAuthCrSessionInserter) count() int { return len(m) } +func (m msgAuthCrSessionInserter) insert(ctx context.Context, db Database) error { + return db.InsertMsgAuthCrSession(ctx, []sqlDataTypes.MsgAuthCrSession(m)) +} +func (m msgAuthCrSessionInserter) getTxIds() []int64 { + txIds := make([]int64, len(m)) + for i, msg := range m { + txIds[i] = msg.TxId + } + return txIds +} + +type msgAuthRvSessionInserter []sqlDataTypes.MsgAuthRvSession + +func (m msgAuthRvSessionInserter) count() int { return len(m) } +func (m msgAuthRvSessionInserter) insert(ctx context.Context, db Database) error { + return db.InsertMsgAuthRvSession(ctx, []sqlDataTypes.MsgAuthRvSession(m)) +} +func (m msgAuthRvSessionInserter) getTxIds() []int64 { + txIds := make([]int64, len(m)) + for i, msg := range m { + txIds[i] = msg.TxId + } + return txIds +} + +type msgAuthRvAllSessionsInserter []sqlDataTypes.MsgAuthRvAllSessions + +func (m msgAuthRvAllSessionsInserter) count() int { return len(m) } +func (m msgAuthRvAllSessionsInserter) insert(ctx context.Context, db Database) error { + return db.InsertMsgAuthRvAllSessions(ctx, []sqlDataTypes.MsgAuthRvAllSessions(m)) +} +func (m msgAuthRvAllSessionsInserter) getTxIds() []int64 { + txIds := make([]int64, len(m)) + for i, msg := range m { + txIds[i] = msg.TxId + } + return txIds +} + // Interface for message inserter type messageInserter interface { insert(ctx context.Context, db Database) error diff --git a/indexer/decoder/convert.go b/indexer/decoder/convert.go new file mode 100644 index 0000000..ee8ab71 --- /dev/null +++ b/indexer/decoder/convert.go @@ -0,0 +1,452 @@ +package decoder + +import ( + "fmt" + "math/big" + "time" + + dataTypes "github.com/Cogwheel-Validator/spectra-gnoland-indexer/pkgs/sql_data_types" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" + "github.com/jackc/pgx/v5/pgtype" +) + +// A converter is used to convert map data types to database-ready structs +type converter struct { + msgMap map[string]any + txId int64 + chainName string + addressResolver AddressResolver + timestamp time.Time + signerIds []int32 +} + +// convertToDbMsgSend converts a map data type directly to a database-ready MsgSend struct +func (c *converter) toMsgSend() (*dataTypes.MsgSend, error) { + data := c.msgMap + messageCounter, ok := data["message_counter"].(int16) + if !ok { + return nil, fmt.Errorf("missing message_counter") + } + + fromAddress, ok := data["from_address"].(string) + if !ok { + return nil, fmt.Errorf("missing from_address") + } + + toAddress, ok := data["to_address"].(string) + if !ok { + return nil, fmt.Errorf("missing to_address") + } + + // Convert amount from []Coin to dataTypes.Amount + coinAmount, ok := data["amount"].([]Coin) + if !ok { + return nil, fmt.Errorf("missing amount") + } + + amount := make([]dataTypes.Amount, len(coinAmount)) + for j, amt := range coinAmount { + bigInt := big.NewInt(amt.Amount) + amount[j] = dataTypes.Amount{ + Amount: pgtype.Numeric{Int: bigInt, Valid: true}, + Denom: amt.Denom, + } + } + + return &dataTypes.MsgSend{ + TxId: c.txId, + ChainName: c.chainName, + ToAddress: c.addressResolver.GetAddress(toAddress), + FromAddress: c.addressResolver.GetAddress(fromAddress), + Amount: amount, + Signers: c.signerIds, + Timestamp: c.timestamp, + MessageCounter: messageCounter, + }, nil +} + +func (c *converter) toMsgMultiSend() ([]dataTypes.MsgMultiSend, error) { + data := c.msgMap + messageCounter, ok := data["message_counter"].(int16) + if !ok { + return nil, fmt.Errorf("missing message_counter") + } + + input, ok := data["input"].([]bank.Input) + if !ok { + return nil, fmt.Errorf("missing input") + } + + output, ok := data["output"].([]bank.Output) + if !ok { + return nil, fmt.Errorf("missing output") + } + + msgMultiSend := make([]dataTypes.MsgMultiSend, 0, len(input)+len(output)) + + for _, in := range input { + coins := make([]dataTypes.Amount, len(in.Coins)) + for i, coin := range in.Coins { + bigInt := big.NewInt(coin.Amount) + coins[i].Amount = pgtype.Numeric{Int: bigInt, Valid: true} + coins[i].Denom = coin.Denom + } + multiSend := dataTypes.MsgMultiSend{ + TxId: c.txId, + Timestamp: c.timestamp, + ChainName: c.chainName, + Direction: false, + AddressId: c.addressResolver.GetAddress(in.Address.String()), + Coins: coins, + MessageCounter: messageCounter, + Signers: c.signerIds, + } + msgMultiSend = append(msgMultiSend, multiSend) + } + + for _, ou := range output { + coins := make([]dataTypes.Amount, len(ou.Coins)) + for i, coin := range ou.Coins { + bigInt := big.NewInt(coin.Amount) + coins[i].Amount = pgtype.Numeric{Int: bigInt, Valid: true} + coins[i].Denom = coin.Denom + } + multiSend := dataTypes.MsgMultiSend{ + TxId: c.txId, + Timestamp: c.timestamp, + ChainName: c.chainName, + Direction: true, + AddressId: c.addressResolver.GetAddress(ou.Address.String()), + Coins: coins, + MessageCounter: messageCounter, + Signers: c.signerIds, + } + msgMultiSend = append(msgMultiSend, multiSend) + } + return msgMultiSend, nil +} + +// convertToDbMsgCall converts a map data type directly to a database-ready MsgCall struct +func (c *converter) toMsgCall() (*dataTypes.MsgCall, error) { + data := c.msgMap + messageCounter, ok := data["message_counter"].(int16) + if !ok { + return nil, fmt.Errorf("missing message_counter") + } + + caller, ok := data["caller"].(string) + if !ok { + return nil, fmt.Errorf("missing caller") + } + + pkgPath, ok := data["pkg_path"].(string) + if !ok { + return nil, fmt.Errorf("missing pkg_path") + } + + funcName, ok := data["func_name"].(string) + if !ok { + return nil, fmt.Errorf("missing func_name") + } + + argsStr, ok := data["args"].(string) + if !ok { + return nil, fmt.Errorf("missing args") + } + + // Convert send from []Coin to dataTypes.Amount + coinSend, ok := data["send"].([]Coin) + if !ok { + return nil, fmt.Errorf("missing send") + } + + send := make([]dataTypes.Amount, len(coinSend)) + for j, amt := range coinSend { + bigInt := big.NewInt(amt.Amount) + send[j] = dataTypes.Amount{ + Amount: pgtype.Numeric{Int: bigInt, Valid: true}, + Denom: amt.Denom, + } + } + + // Convert maxDeposit from []Coin to dataTypes.Amount + coinMaxDeposit, ok := data["max_deposit"].([]Coin) + if !ok { + return nil, fmt.Errorf("missing max_deposit") + } + + maxDeposit := make([]dataTypes.Amount, len(coinMaxDeposit)) + for j, amt := range coinMaxDeposit { + bigInt := big.NewInt(amt.Amount) + maxDeposit[j] = dataTypes.Amount{ + Amount: pgtype.Numeric{Int: bigInt, Valid: true}, + Denom: amt.Denom, + } + } + + return &dataTypes.MsgCall{ + TxId: c.txId, + MessageCounter: messageCounter, + ChainName: c.chainName, + Caller: c.addressResolver.GetAddress(caller), + Send: send, + PkgPath: pkgPath, + FuncName: funcName, + Args: argsStr, + MaxDeposit: maxDeposit, + Signers: c.signerIds, + Timestamp: c.timestamp, + }, nil +} + +// convertToDbMsgAddPackage converts a map data type directly to a database-ready MsgAddPackage struct +func (c *converter) toMsgAddPackage() (*dataTypes.MsgAddPackage, error) { + data := c.msgMap + messageCounter, ok := data["message_counter"].(int16) + if !ok { + return nil, fmt.Errorf("missing message_counter") + } + + creator, ok := data["creator"].(string) + if !ok { + return nil, fmt.Errorf("missing creator") + } + + pkgPath, ok := data["pkg_path"].(string) + if !ok { + return nil, fmt.Errorf("missing pkg_path") + } + + pkgName, ok := data["pkg_name"].(string) + if !ok { + return nil, fmt.Errorf("missing pkg_name") + } + + // Convert send from []Coin to dataTypes.Amount + coinSend, ok := data["send"].([]Coin) + if !ok { + return nil, fmt.Errorf("missing send") + } + + pkgFileNames, ok := data["pkg_file_names"].([]string) + if !ok { + return nil, fmt.Errorf("missing pkg_file_names") + } + + send := make([]dataTypes.Amount, len(coinSend)) + for j, amt := range coinSend { + bigInt := big.NewInt(amt.Amount) + send[j] = dataTypes.Amount{ + Amount: pgtype.Numeric{Int: bigInt, Valid: true}, + Denom: amt.Denom, + } + } + + // Convert maxDeposit from []Coin to dataTypes.Amount + coinMaxDeposit, ok := data["max_deposit"].([]Coin) + if !ok { + return nil, fmt.Errorf("missing max_deposit") + } + + maxDeposit := make([]dataTypes.Amount, len(coinMaxDeposit)) + for j, amt := range coinMaxDeposit { + bigInt := big.NewInt(amt.Amount) + maxDeposit[j] = dataTypes.Amount{ + Amount: pgtype.Numeric{Int: bigInt, Valid: true}, + Denom: amt.Denom, + } + } + + return &dataTypes.MsgAddPackage{ + TxId: c.txId, + MessageCounter: messageCounter, + ChainName: c.chainName, + Creator: c.addressResolver.GetAddress(creator), + PkgPath: pkgPath, + PkgName: pkgName, + Send: send, + PkgFileNames: pkgFileNames, + MaxDeposit: maxDeposit, + Signers: c.signerIds, + Timestamp: c.timestamp, + }, nil +} + +// convertToDbMsgRun converts a map data type directly to a database-ready MsgRun struct +func (c *converter) toMsgRun() (*dataTypes.MsgRun, error) { + data := c.msgMap + messageCounter, ok := data["message_counter"].(int16) + if !ok { + return nil, fmt.Errorf("missing message_counter") + } + + caller, ok := data["caller"].(string) + if !ok { + return nil, fmt.Errorf("missing caller") + } + + pkgPath, ok := data["pkg_path"].(string) + if !ok { + return nil, fmt.Errorf("missing pkg_path") + } + + pkgName, ok := data["pkg_name"].(string) + if !ok { + return nil, fmt.Errorf("missing pkg_name") + } + + // Convert send from []Coin to dataTypes.Amount + coinSend, ok := data["send"].([]Coin) + if !ok { + return nil, fmt.Errorf("missing send") + } + + send := make([]dataTypes.Amount, len(coinSend)) + for j, amt := range coinSend { + bigInt := big.NewInt(amt.Amount) + send[j] = dataTypes.Amount{ + Amount: pgtype.Numeric{Int: bigInt, Valid: true}, + Denom: amt.Denom, + } + } + + // Convert maxDeposit from []Coin to dataTypes.Amount + coinMaxDeposit, ok := data["max_deposit"].([]Coin) + if !ok { + return nil, fmt.Errorf("missing max_deposit") + } + + maxDeposit := make([]dataTypes.Amount, len(coinMaxDeposit)) + for j, amt := range coinMaxDeposit { + bigInt := big.NewInt(amt.Amount) + maxDeposit[j] = dataTypes.Amount{ + Amount: pgtype.Numeric{Int: bigInt, Valid: true}, + Denom: amt.Denom, + } + } + + pkgFileNames, ok := data["pkg_file_names"].([]string) + if !ok { + return nil, fmt.Errorf("missing pkg_file_names") + } + + return &dataTypes.MsgRun{ + TxId: c.txId, + MessageCounter: messageCounter, + ChainName: c.chainName, + Caller: c.addressResolver.GetAddress(caller), + PkgPath: pkgPath, + PkgName: pkgName, + PkgFileNames: pkgFileNames, + Send: send, + MaxDeposit: maxDeposit, + Signers: c.signerIds, + Timestamp: c.timestamp, + }, nil +} + +func (c *converter) toMsgCrSession() (*dataTypes.MsgAuthCrSession, error) { + data := c.msgMap + creator, ok := data["creator"].(string) + if !ok { + return nil, fmt.Errorf("missing creator") + } + sessionKey, ok := data["session_key"].(string) + if !ok { + return nil, fmt.Errorf("missing session_key") + } + expiresAt, ok := data["expires_at"].(time.Time) + if !ok { + return nil, fmt.Errorf("missing expires_at") + } + + spendLimit, ok := data["spend_limit"].([]Coin) + if !ok { + return nil, fmt.Errorf("missing spend_limit") + } + + spendLimitAmount := make([]dataTypes.Amount, len(spendLimit)) + for i, coin := range spendLimit { + amt := big.NewInt(coin.Amount) + spendLimitAmount[i] = dataTypes.Amount{ + Amount: pgtype.Numeric{Int: amt, Valid: true}, + Denom: coin.Denom, + } + } + + spendPeriod, ok := data["spend_period"].(int64) + if !ok { + return nil, fmt.Errorf("missing spend_period") + } + + messageCounter, ok := data["message_counter"].(int16) + if !ok { + return nil, fmt.Errorf("missing message_counter") + } + + allowPaths, ok := data["allow_paths"].([]string) + if !ok { + return nil, fmt.Errorf("missing allow_path") + } + + return &dataTypes.MsgAuthCrSession{ + TxId: c.txId, + ChainName: c.chainName, + Timestamp: c.timestamp, + Creator: c.addressResolver.GetAddress(creator), + SessionKey: c.addressResolver.GetAddress(sessionKey), + ExpiresAt: expiresAt, + SpendLimit: spendLimitAmount, + SpendPeriod: spendPeriod, + Signers: c.signerIds, + MessageCounter: messageCounter, + AllowPaths: allowPaths, + }, nil +} + +func (c *converter) toMsgRvSession() (*dataTypes.MsgAuthRvSession, error) { + data := c.msgMap + creator, ok := data["creator"].(string) + if !ok { + return nil, fmt.Errorf("missing creator") + } + sessionKey, ok := data["session_key"].(string) + if !ok { + return nil, fmt.Errorf("missing session_key") + } + messageCounter, ok := data["message_counter"].(int16) + if !ok { + return nil, fmt.Errorf("missing message_counter") + } + + return &dataTypes.MsgAuthRvSession{ + TxId: c.txId, + ChainName: c.chainName, + Timestamp: c.timestamp, + Creator: c.addressResolver.GetAddress(creator), + SessionKey: c.addressResolver.GetAddress(sessionKey), + Signers: c.signerIds, + MessageCounter: messageCounter, + }, nil +} + +func (c *converter) toMsgRvAllSessions() (*dataTypes.MsgAuthRvAllSessions, error) { + data := c.msgMap + creator, ok := data["creator"].(string) + if !ok { + return nil, fmt.Errorf("missing creator") + } + messageCounter, ok := data["message_counter"].(int16) + if !ok { + return nil, fmt.Errorf("missing message_counter") + } + + return &dataTypes.MsgAuthRvAllSessions{ + TxId: c.txId, + ChainName: c.chainName, + Timestamp: c.timestamp, + Creator: c.addressResolver.GetAddress(creator), + Signers: c.signerIds, + MessageCounter: messageCounter, + }, nil +} diff --git a/indexer/decoder/decoder.go b/indexer/decoder/decoder.go index 79b879d..27fdae0 100644 --- a/indexer/decoder/decoder.go +++ b/indexer/decoder/decoder.go @@ -3,14 +3,10 @@ package decoder import ( "crypto/sha256" "encoding/base64" - "fmt" "math/big" - "strings" dataTypes "github.com/Cogwheel-Validator/spectra-gnoland-indexer/pkgs/sql_data_types" - "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/tm2/pkg/amino" - "github.com/gnolang/gno/tm2/pkg/sdk/bank" "github.com/gnolang/gno/tm2/pkg/std" "github.com/jackc/pgx/v5/pgtype" ) @@ -114,140 +110,3 @@ func (d *Decoder) GetMessageFromStdTx() (BasicTxData, []map[string]any, error) { } return basicTxData, messages, nil } - -func processMsgs( - tx *std.Tx, - messages []map[string]any, -) error { - for i, msg := range tx.GetMsgs() { - if i > 32767 { - return fmt.Errorf("transaction message count exceeds maximum: %d", i) - } - messageCounter := int16(i) - switch m := msg.(type) { - case bank.MsgSend: - // amount should have something like 1000000 ugnot we just need to split it and convert it to uint64 - amount, err := extractCoins(m.Amount) - if err != nil { - amount = []Coin{} - } - messages[i] = map[string]any{ - "msg_type": "bank_msg_send", - "from_address": m.FromAddress.String(), - "to_address": m.ToAddress.String(), - "amount": amount, - "message_counter": messageCounter, - } - case bank.MsgMultiSend: - distinctAddresses := make(map[string]struct{}) - for _, input := range m.Inputs { - distinctAddresses[input.Address.String()] = struct{}{} - } - for _, output := range m.Outputs { - distinctAddresses[output.Address.String()] = struct{}{} - } - messages[i] = map[string]any{ - "msg_type": "bank_msg_multi_send", - "input": m.Inputs, - "output": m.Outputs, - "message_counter": messageCounter, - "distinct_addresses": distinctAddresses, - } - - // VM messages - case vm.MsgCall: - caller := m.Caller.String() - send, err := extractCoins(m.Send) - if err != nil { - send = []Coin{} - } - pkgPath := m.PkgPath - // max deposit could be empty and there is a chance it will return an error - // so we need to handle that - maxDeposit, err := extractCoins(m.MaxDeposit) - if err != nil { - maxDeposit = []Coin{} - } - funcName := m.Func - // combine the args into a string - args := strings.Join(m.Args, ",") - messages[i] = map[string]any{ - "msg_type": "vm_msg_call", - "caller": caller, - "pkg_path": pkgPath, - "func_name": funcName, - "args": args, - "send": send, - "max_deposit": maxDeposit, - "message_counter": messageCounter, - } - case vm.MsgAddPackage: - pkgPath := m.Package.Path - pkgName := m.Package.Name - pkgFileNames := m.Package.FileNames() - creator := m.Creator.String() - send, err := extractCoins(m.Send) - if err != nil { - send = []Coin{} - } - maxDeposit, err := extractCoins(m.MaxDeposit) - if err != nil { - maxDeposit = []Coin{} - } - messages[i] = map[string]any{ - "msg_type": "vm_msg_add_package", - "pkg_path": pkgPath, - "pkg_name": pkgName, - "pkg_file_names": pkgFileNames, - "creator": creator, - "send": send, - "max_deposit": maxDeposit, - "message_counter": messageCounter, - } - - case vm.MsgRun: - caller := m.Caller.String() - pkgPath := m.Package.Path - pkgName := m.Package.Name - pkgFileNames := m.Package.FileNames() - send, err := extractCoins(m.Send) - if err != nil { - send = []Coin{} - } - // max deposit could be empty and there is a chance it will return an error - // so we need to handle that - maxDeposit, err := extractCoins(m.MaxDeposit) - if err != nil { - maxDeposit = []Coin{} - } - messages[i] = map[string]any{ - "msg_type": "vm_msg_run", - "caller": caller, - "pkg_path": pkgPath, - "pkg_name": pkgName, - "pkg_file_names": pkgFileNames, - "send": send, - "max_deposit": maxDeposit, - "message_counter": messageCounter, - } - - // TO-DO: when available add auth messages - default: - return fmt.Errorf("unknown or unsupported message type: %T", m) - } - } - return nil -} - -// Local function to split the amount and denom -func extractCoins(amount std.Coins) ([]Coin, error) { - // make a string and split it by space - coins := make([]Coin, len(amount)) - for i, coin := range amount { - coins[i] = Coin{ - Amount: coin.Amount, - Denom: coin.Denom, - } - } - return coins, nil -} diff --git a/indexer/decoder/process.go b/indexer/decoder/process.go new file mode 100644 index 0000000..f2197ea --- /dev/null +++ b/indexer/decoder/process.go @@ -0,0 +1,254 @@ +package decoder + +import ( + "fmt" + "strings" + "time" + + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/tm2/pkg/sdk/auth" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" + "github.com/gnolang/gno/tm2/pkg/std" +) + +func processMsgs( + tx *std.Tx, + messages []map[string]any, +) error { + for i, msg := range tx.GetMsgs() { + if i > 32767 { + return fmt.Errorf("transaction message count exceeds maximum: %d", i) + } + messageCounter := int16(i) + switch m := msg.(type) { + case bank.MsgSend: + processSend(&m, messages, i, messageCounter) + case bank.MsgMultiSend: + processMultiSend(&m, messages, i, messageCounter) + // VM messages + case vm.MsgCall: + processVmCall(&m, messages, i, messageCounter) + case vm.MsgAddPackage: + processVmAddPkg(&m, messages, i, messageCounter) + case vm.MsgRun: + processVmRun(&m, messages, i, messageCounter) + // Auth messages + case auth.MsgCreateSession: + processAuthCr(&m, messages, i, messageCounter) + case auth.MsgRevokeSession: + processAuthRv(&m, messages, i, messageCounter) + case auth.MsgRevokeAllSessions: + processAuthRvAll(&m, messages, i, messageCounter) + + default: + return fmt.Errorf("unknown or unsupported message type: %T", m) + } + } + return nil +} + +// Local function to split the amount and denom +func extractCoins(amount std.Coins) ([]Coin, error) { + // make a string and split it by space + coins := make([]Coin, len(amount)) + for i, coin := range amount { + coins[i] = Coin{ + Amount: coin.Amount, + Denom: coin.Denom, + } + } + return coins, nil +} + +func processSend( + m *bank.MsgSend, + messages []map[string]any, + i int, + messageCounter int16, +) { + // amount should have something like 1000000 ugnot we just need to split it and convert it to uint64 + amount, err := extractCoins(m.Amount) + if err != nil { + amount = []Coin{} + } + messages[i] = map[string]any{ + "msg_type": "bank_msg_send", + "from_address": m.FromAddress.String(), + "to_address": m.ToAddress.String(), + "amount": amount, + "message_counter": messageCounter, + } +} + +func processMultiSend( + m *bank.MsgMultiSend, + messages []map[string]any, + i int, + messageCounter int16, +) { + distinctAddresses := make(map[string]struct{}) + for _, input := range m.Inputs { + distinctAddresses[input.Address.String()] = struct{}{} + } + for _, output := range m.Outputs { + distinctAddresses[output.Address.String()] = struct{}{} + } + messages[i] = map[string]any{ + "msg_type": "bank_msg_multi_send", + "input": m.Inputs, + "output": m.Outputs, + "message_counter": messageCounter, + "distinct_addresses": distinctAddresses, + } +} + +func processVmCall( + m *vm.MsgCall, + messages []map[string]any, + i int, + messageCounter int16, +) { + caller := m.Caller.String() + send, err := extractCoins(m.Send) + if err != nil { + send = []Coin{} + } + pkgPath := m.PkgPath + // max deposit could be empty and there is a chance it will return an error + // so we need to handle that + maxDeposit, err := extractCoins(m.MaxDeposit) + if err != nil { + maxDeposit = []Coin{} + } + funcName := m.Func + // combine the args into a string + args := strings.Join(m.Args, ",") + messages[i] = map[string]any{ + "msg_type": "vm_msg_call", + "caller": caller, + "pkg_path": pkgPath, + "func_name": funcName, + "args": args, + "send": send, + "max_deposit": maxDeposit, + "message_counter": messageCounter, + } +} + +func processVmAddPkg( + m *vm.MsgAddPackage, + messages []map[string]any, + i int, + messageCounter int16, +) { + pkgPath := m.Package.Path + pkgName := m.Package.Name + pkgFileNames := m.Package.FileNames() + creator := m.Creator.String() + send, err := extractCoins(m.Send) + if err != nil { + send = []Coin{} + } + maxDeposit, err := extractCoins(m.MaxDeposit) + if err != nil { + maxDeposit = []Coin{} + } + messages[i] = map[string]any{ + "msg_type": "vm_msg_add_package", + "pkg_path": pkgPath, + "pkg_name": pkgName, + "pkg_file_names": pkgFileNames, + "creator": creator, + "send": send, + "max_deposit": maxDeposit, + "message_counter": messageCounter, + } +} + +func processVmRun( + m *vm.MsgRun, + messages []map[string]any, + i int, + messageCounter int16, +) { + caller := m.Caller.String() + pkgPath := m.Package.Path + pkgName := m.Package.Name + pkgFileNames := m.Package.FileNames() + send, err := extractCoins(m.Send) + if err != nil { + send = []Coin{} + } + // max deposit could be empty and there is a chance it will return an error + // so we need to handle that + maxDeposit, err := extractCoins(m.MaxDeposit) + if err != nil { + maxDeposit = []Coin{} + } + messages[i] = map[string]any{ + "msg_type": "vm_msg_run", + "caller": caller, + "pkg_path": pkgPath, + "pkg_name": pkgName, + "pkg_file_names": pkgFileNames, + "send": send, + "max_deposit": maxDeposit, + "message_counter": messageCounter, + } +} + +func processAuthCr( + m *auth.MsgCreateSession, + messages []map[string]any, + i int, + messageCounter int16, +) { + creator := m.Creator.String() + sessionKey := m.SessionKey.Address().Bech32().String() + // It might set it to local time so force it to UTC just in case. + expiresAt := time.Unix(m.ExpiresAt, 0).UTC() + spendLimit, err := extractCoins(m.SpendLimit) + if err != nil { + spendLimit = []Coin{} + } + messages[i] = map[string]any{ + "msg_type": "auth_msg_create_session", + "creator": creator, + "session_key": sessionKey, + "expires_at": expiresAt, + "allow_paths": m.AllowPaths, + "spend_limit": spendLimit, + "spend_period": m.SpendPeriod, + "message_counter": messageCounter, + } +} + +func processAuthRv( + m *auth.MsgRevokeSession, + messages []map[string]any, + i int, + messageCounter int16, +) { + creator := m.Creator.String() + sessionKey := m.SessionKey.Address().Bech32().String() + messages[i] = map[string]any{ + "msg_type": "auth_msg_revoke_session", + "creator": creator, + "session_key": sessionKey, + "message_counter": messageCounter, + } +} + +func processAuthRvAll( + m *auth.MsgRevokeAllSessions, + messages []map[string]any, + i int, + messageCounter int16, +) { + creator := m.Creator.String() + messages[i] = map[string]any{ + "msg_type": "auth_msg_revoke_all_sessions", + "creator": creator, + "message_counter": messageCounter, + } +} diff --git a/indexer/decoder/product.go b/indexer/decoder/product.go index de89c97..c6a0632 100644 --- a/indexer/decoder/product.go +++ b/indexer/decoder/product.go @@ -2,14 +2,14 @@ package decoder import ( "fmt" - "math/big" "time" + "github.com/Cogwheel-Validator/spectra-gnoland-indexer/pkgs/logger" dataTypes "github.com/Cogwheel-Validator/spectra-gnoland-indexer/pkgs/sql_data_types" - "github.com/gnolang/gno/tm2/pkg/sdk/bank" - "github.com/jackc/pgx/v5/pgtype" ) +var l = logger.Get() + // NewDecodedMsg creates a new DecodedMsg struct. // // Parameters: @@ -24,6 +24,8 @@ func NewDecodedMsg(encodedTx string) *DecodedMsg { decoder := NewDecoder(encodedTx) basicData, messages, err := decoder.GetMessageFromStdTx() if err != nil { + // It should never happen, but we need some way to halt the program. + l.Fatal().Stack().Msgf("failed to decode message %s", err) return nil } return &DecodedMsg{ @@ -153,7 +155,29 @@ func (dm *DecodedMsg) CollectAllAddresses() []string { if caller, ok := msgMap["caller"].(string); ok { addressSet[caller] = struct{}{} } + + case "auth_msg_create_session": + if caller, ok := msgMap["caller"].(string); ok { + addressSet[caller] = struct{}{} + } + if sessionKey, ok := msgMap["session_key"].(string); ok { + addressSet[sessionKey] = struct{}{} + } + + case "auth_msg_revoke_session": + if caller, ok := msgMap["caller"].(string); ok { + addressSet[caller] = struct{}{} + } + if sessionKey, ok := msgMap["session_key"].(string); ok { + addressSet[sessionKey] = struct{}{} + } + + case "auth_msg_revoke_all_sessions": + if caller, ok := msgMap["caller"].(string); ok { + addressSet[caller] = struct{}{} + } } + } // Convert set to slice @@ -167,11 +191,64 @@ func (dm *DecodedMsg) CollectAllAddresses() []string { // DbMessageGroups holds database-ready message types with address IDs type DbMessageGroups struct { - MsgSend []dataTypes.MsgSend - MsgMultiSend []dataTypes.MsgMultiSend - MsgCall []dataTypes.MsgCall - MsgAddPkg []dataTypes.MsgAddPackage - MsgRun []dataTypes.MsgRun + MsgSend []dataTypes.MsgSend + MsgMultiSend []dataTypes.MsgMultiSend + MsgCall []dataTypes.MsgCall + MsgAddPkg []dataTypes.MsgAddPackage + MsgRun []dataTypes.MsgRun + MsgAuthCrSession []dataTypes.MsgAuthCrSession + MsgAuthRvSession []dataTypes.MsgAuthRvSession + MsgAuthRvAllSessions []dataTypes.MsgAuthRvAllSessions +} + +// Merge appends all message slices from other into g. +func (g *DbMessageGroups) Merge(other *DbMessageGroups) { + g.MsgSend = append(g.MsgSend, other.MsgSend...) + g.MsgMultiSend = append(g.MsgMultiSend, other.MsgMultiSend...) + g.MsgCall = append(g.MsgCall, other.MsgCall...) + g.MsgAddPkg = append(g.MsgAddPkg, other.MsgAddPkg...) + g.MsgRun = append(g.MsgRun, other.MsgRun...) + g.MsgAuthCrSession = append(g.MsgAuthCrSession, other.MsgAuthCrSession...) + g.MsgAuthRvSession = append(g.MsgAuthRvSession, other.MsgAuthRvSession...) + g.MsgAuthRvAllSessions = append(g.MsgAuthRvAllSessions, other.MsgAuthRvAllSessions...) +} + +// AddressEntry holds the data needed to populate the address_tx table for a single message. +type AddressEntry struct { + Addresses *dataTypes.TxAddresses + ChainName string + Timestamp time.Time + MsgType string +} + +// AllAddressEntries returns one AddressEntry per message across all groups. +func (g *DbMessageGroups) AllAddressEntries() []AddressEntry { + entries := make([]AddressEntry, 0) + for _, m := range g.MsgSend { + entries = append(entries, AddressEntry{m.GetAllAddresses(), m.ChainName, m.Timestamp, m.TableName()}) + } + for _, m := range g.MsgMultiSend { + entries = append(entries, AddressEntry{m.GetAllAddresses(), m.ChainName, m.Timestamp, m.TableName()}) + } + for _, m := range g.MsgCall { + entries = append(entries, AddressEntry{m.GetAllAddresses(), m.ChainName, m.Timestamp, m.TableName()}) + } + for _, m := range g.MsgAddPkg { + entries = append(entries, AddressEntry{m.GetAllAddresses(), m.ChainName, m.Timestamp, m.TableName()}) + } + for _, m := range g.MsgRun { + entries = append(entries, AddressEntry{m.GetAllAddresses(), m.ChainName, m.Timestamp, m.TableName()}) + } + for _, m := range g.MsgAuthCrSession { + entries = append(entries, AddressEntry{m.GetAllAddresses(), m.ChainName, m.Timestamp, m.TableName()}) + } + for _, m := range g.MsgAuthRvSession { + entries = append(entries, AddressEntry{m.GetAllAddresses(), m.ChainName, m.Timestamp, m.TableName()}) + } + for _, m := range g.MsgAuthRvAllSessions { + entries = append(entries, AddressEntry{m.GetAllAddresses(), m.ChainName, m.Timestamp, m.TableName()}) + } + return entries } // ConvertToDbMessages directly converts the decoded message maps to database-ready message types @@ -190,13 +267,26 @@ func (dm *DecodedMsg) ConvertToDbMessages( } dbGroups := &DbMessageGroups{ - MsgSend: make([]dataTypes.MsgSend, 0), - MsgCall: make([]dataTypes.MsgCall, 0), - MsgAddPkg: make([]dataTypes.MsgAddPackage, 0), - MsgRun: make([]dataTypes.MsgRun, 0), + MsgSend: make([]dataTypes.MsgSend, 0), + MsgMultiSend: make([]dataTypes.MsgMultiSend, 0), + MsgCall: make([]dataTypes.MsgCall, 0), + MsgAddPkg: make([]dataTypes.MsgAddPackage, 0), + MsgRun: make([]dataTypes.MsgRun, 0), + MsgAuthCrSession: make([]dataTypes.MsgAuthCrSession, 0), + MsgAuthRvSession: make([]dataTypes.MsgAuthRvSession, 0), + MsgAuthRvAllSessions: make([]dataTypes.MsgAuthRvAllSessions, 0), + } + + cvt := converter{ + msgMap: nil, + txId: txId, + chainName: chainName, + addressResolver: addressResolver, + timestamp: timestamp, + signerIds: signerIds, } - for _, msgMap := range dm.Messages { + cvt.msgMap = msgMap msgType, ok := msgMap["msg_type"].(string) if !ok { return nil, fmt.Errorf("missing or invalid msg_type") @@ -204,387 +294,65 @@ func (dm *DecodedMsg) ConvertToDbMessages( switch msgType { case "bank_msg_send": - msg, err := dm.convertToDbMsgSend(msgMap, addressResolver, txId, chainName, timestamp, signerIds) + msg, err := cvt.toMsgSend() if err != nil { return nil, fmt.Errorf("failed to convert bank_msg_send: %w", err) } dbGroups.MsgSend = append(dbGroups.MsgSend, *msg) case "bank_msg_multi_send": - msgs, err := dm.convertToDbMsgMultiSend(msgMap, addressResolver, txId, chainName, timestamp, signerIds) + msgs, err := cvt.toMsgMultiSend() if err != nil { return nil, fmt.Errorf("failed to convert bank_msg_multi_send: %w", err) } dbGroups.MsgMultiSend = append(dbGroups.MsgMultiSend, msgs...) case "vm_msg_call": - msg, err := dm.convertToDbMsgCall(msgMap, addressResolver, txId, chainName, timestamp, signerIds) + msg, err := cvt.toMsgCall() if err != nil { return nil, fmt.Errorf("failed to convert vm_msg_call: %w", err) } dbGroups.MsgCall = append(dbGroups.MsgCall, *msg) case "vm_msg_add_package": - msg, err := dm.convertToDbMsgAddPackage(msgMap, addressResolver, txId, chainName, timestamp, signerIds) + msg, err := cvt.toMsgAddPackage() if err != nil { return nil, fmt.Errorf("failed to convert vm_msg_add_package: %w", err) } dbGroups.MsgAddPkg = append(dbGroups.MsgAddPkg, *msg) case "vm_msg_run": - msg, err := dm.convertToDbMsgRun(msgMap, addressResolver, txId, chainName, timestamp, signerIds) + msg, err := cvt.toMsgRun() if err != nil { return nil, fmt.Errorf("failed to convert vm_msg_run: %w", err) } dbGroups.MsgRun = append(dbGroups.MsgRun, *msg) - default: - return nil, fmt.Errorf("unknown message type: %s", msgType) - } - } - - return dbGroups, nil -} - -// convertToDbMsgSend converts a map data type directly to a database-ready MsgSend struct -func (dm *DecodedMsg) convertToDbMsgSend( - msgMap map[string]any, - addressResolver AddressResolver, - txId int64, - chainName string, - timestamp time.Time, - signerIds []int32, -) (*dataTypes.MsgSend, error) { - messageCounter, ok := msgMap["message_counter"].(int16) - if !ok { - return nil, fmt.Errorf("missing message_counter") - } - - fromAddress, ok := msgMap["from_address"].(string) - if !ok { - return nil, fmt.Errorf("missing from_address") - } - - toAddress, ok := msgMap["to_address"].(string) - if !ok { - return nil, fmt.Errorf("missing to_address") - } - - // Convert amount from []Coin to dataTypes.Amount - coinAmount, ok := msgMap["amount"].([]Coin) - if !ok { - return nil, fmt.Errorf("missing amount") - } - - amount := make([]dataTypes.Amount, len(coinAmount)) - for j, amt := range coinAmount { - bigInt := big.NewInt(amt.Amount) - amount[j] = dataTypes.Amount{ - Amount: pgtype.Numeric{Int: bigInt, Valid: true}, - Denom: amt.Denom, - } - } - - return &dataTypes.MsgSend{ - TxId: txId, - ChainName: chainName, - FromAddress: addressResolver.GetAddress(fromAddress), - ToAddress: addressResolver.GetAddress(toAddress), - Amount: amount, - Signers: signerIds, - Timestamp: timestamp, - MessageCounter: messageCounter, - }, nil -} - -func (dm *DecodedMsg) convertToDbMsgMultiSend( - msgMap map[string]any, - addressResolver AddressResolver, - txId int64, - chainName string, - timestamp time.Time, - signerIds []int32, -) ([]dataTypes.MsgMultiSend, error) { - messageCounter, ok := msgMap["message_counter"].(int16) - if !ok { - return nil, fmt.Errorf("missing message_counter") - } - - input, ok := msgMap["input"].([]bank.Input) - if !ok { - return nil, fmt.Errorf("missing input") - } - - output, ok := msgMap["output"].([]bank.Output) - if !ok { - return nil, fmt.Errorf("missing output") - } - - msgMultiSend := make([]dataTypes.MsgMultiSend, 0, len(input)+len(output)) - - for _, in := range input { - coins := make([]dataTypes.Amount, len(in.Coins)) - for i, coin := range in.Coins { - bigInt := big.NewInt(coin.Amount) - coins[i].Amount = pgtype.Numeric{Int: bigInt, Valid: true} - coins[i].Denom = coin.Denom - } - multiSend := dataTypes.MsgMultiSend{ - TxId: txId, - Timestamp: timestamp, - ChainName: chainName, - Direction: false, - AddressId: addressResolver.GetAddress(in.Address.String()), - Coins: coins, - MessageCounter: messageCounter, - Signers: signerIds, - } - msgMultiSend = append(msgMultiSend, multiSend) - } - - for _, ou := range output { - coins := make([]dataTypes.Amount, len(ou.Coins)) - for i, coin := range ou.Coins { - bigInt := big.NewInt(coin.Amount) - coins[i].Amount = pgtype.Numeric{Int: bigInt, Valid: true} - coins[i].Denom = coin.Denom - } - multiSend := dataTypes.MsgMultiSend{ - TxId: txId, - Timestamp: timestamp, - ChainName: chainName, - Direction: true, - AddressId: addressResolver.GetAddress(ou.Address.String()), - Coins: coins, - MessageCounter: messageCounter, - Signers: signerIds, - } - msgMultiSend = append(msgMultiSend, multiSend) - } - return msgMultiSend, nil -} - -// convertToDbMsgCall converts a map data type directly to a database-ready MsgCall struct -func (dm *DecodedMsg) convertToDbMsgCall( - msgMap map[string]any, - addressResolver AddressResolver, - txId int64, - chainName string, - timestamp time.Time, - signerIds []int32, -) (*dataTypes.MsgCall, error) { - messageCounter, ok := msgMap["message_counter"].(int16) - if !ok { - return nil, fmt.Errorf("missing message_counter") - } - - caller, ok := msgMap["caller"].(string) - if !ok { - return nil, fmt.Errorf("missing caller") - } - - pkgPath, ok := msgMap["pkg_path"].(string) - if !ok { - return nil, fmt.Errorf("missing pkg_path") - } - - funcName, ok := msgMap["func_name"].(string) - if !ok { - return nil, fmt.Errorf("missing func_name") - } - - argsStr, ok := msgMap["args"].(string) - if !ok { - return nil, fmt.Errorf("missing args") - } - - // Convert send from []Coin to dataTypes.Amount - coinSend, ok := msgMap["send"].([]Coin) - if !ok { - return nil, fmt.Errorf("missing send") - } - - send := make([]dataTypes.Amount, len(coinSend)) - for j, amt := range coinSend { - bigInt := big.NewInt(amt.Amount) - send[j] = dataTypes.Amount{ - Amount: pgtype.Numeric{Int: bigInt, Valid: true}, - Denom: amt.Denom, - } - } - - // Convert maxDeposit from []Coin to dataTypes.Amount - coinMaxDeposit, ok := msgMap["max_deposit"].([]Coin) - if !ok { - return nil, fmt.Errorf("missing max_deposit") - } - - maxDeposit := make([]dataTypes.Amount, len(coinMaxDeposit)) - for j, amt := range coinMaxDeposit { - bigInt := big.NewInt(amt.Amount) - maxDeposit[j] = dataTypes.Amount{ - Amount: pgtype.Numeric{Int: bigInt, Valid: true}, - Denom: amt.Denom, - } - } - - return &dataTypes.MsgCall{ - TxId: txId, - MessageCounter: messageCounter, - ChainName: chainName, - Caller: addressResolver.GetAddress(caller), - Send: send, - PkgPath: pkgPath, - FuncName: funcName, - Args: argsStr, - MaxDeposit: maxDeposit, - Signers: signerIds, - Timestamp: timestamp, - }, nil -} - -// convertToDbMsgAddPackage converts a map data type directly to a database-ready MsgAddPackage struct -func (dm *DecodedMsg) convertToDbMsgAddPackage( - msgMap map[string]any, - addressResolver AddressResolver, - txId int64, - chainName string, - timestamp time.Time, - signerIds []int32, -) (*dataTypes.MsgAddPackage, error) { - messageCounter, ok := msgMap["message_counter"].(int16) - if !ok { - return nil, fmt.Errorf("missing message_counter") - } - - creator, ok := msgMap["creator"].(string) - if !ok { - return nil, fmt.Errorf("missing creator") - } - - pkgPath, ok := msgMap["pkg_path"].(string) - if !ok { - return nil, fmt.Errorf("missing pkg_path") - } - - pkgName, ok := msgMap["pkg_name"].(string) - if !ok { - return nil, fmt.Errorf("missing pkg_name") - } - - // Convert send from []Coin to dataTypes.Amount - coinSend, ok := msgMap["send"].([]Coin) - if !ok { - return nil, fmt.Errorf("missing send") - } - - send := make([]dataTypes.Amount, len(coinSend)) - for j, amt := range coinSend { - bigInt := big.NewInt(amt.Amount) - send[j] = dataTypes.Amount{ - Amount: pgtype.Numeric{Int: bigInt, Valid: true}, - Denom: amt.Denom, - } - } - - // Convert maxDeposit from []Coin to dataTypes.Amount - coinMaxDeposit, ok := msgMap["max_deposit"].([]Coin) - if !ok { - return nil, fmt.Errorf("missing max_deposit") - } - - maxDeposit := make([]dataTypes.Amount, len(coinMaxDeposit)) - for j, amt := range coinMaxDeposit { - bigInt := big.NewInt(amt.Amount) - maxDeposit[j] = dataTypes.Amount{ - Amount: pgtype.Numeric{Int: bigInt, Valid: true}, - Denom: amt.Denom, - } - } - - return &dataTypes.MsgAddPackage{ - TxId: txId, - MessageCounter: messageCounter, - ChainName: chainName, - Creator: addressResolver.GetAddress(creator), - PkgPath: pkgPath, - PkgName: pkgName, - Send: send, - MaxDeposit: maxDeposit, - Signers: signerIds, - Timestamp: timestamp, - }, nil -} - -// convertToDbMsgRun converts a map data type directly to a database-ready MsgRun struct -func (dm *DecodedMsg) convertToDbMsgRun( - msgMap map[string]any, - addressResolver AddressResolver, - txId int64, - chainName string, - timestamp time.Time, - signerIds []int32, -) (*dataTypes.MsgRun, error) { - messageCounter, ok := msgMap["message_counter"].(int16) - if !ok { - return nil, fmt.Errorf("missing message_counter") - } - - caller, ok := msgMap["caller"].(string) - if !ok { - return nil, fmt.Errorf("missing caller") - } - - pkgPath, ok := msgMap["pkg_path"].(string) - if !ok { - return nil, fmt.Errorf("missing pkg_path") - } - - pkgName, ok := msgMap["pkg_name"].(string) - if !ok { - return nil, fmt.Errorf("missing pkg_name") - } - - // Convert send from []Coin to dataTypes.Amount - coinSend, ok := msgMap["send"].([]Coin) - if !ok { - return nil, fmt.Errorf("missing send") - } + case "auth_msg_create_session": + msg, err := cvt.toMsgCrSession() + if err != nil { + return nil, fmt.Errorf("failed to convert auth_msg_auth_cr_session: %w", err) + } + dbGroups.MsgAuthCrSession = append(dbGroups.MsgAuthCrSession, *msg) - send := make([]dataTypes.Amount, len(coinSend)) - for j, amt := range coinSend { - bigInt := big.NewInt(amt.Amount) - send[j] = dataTypes.Amount{ - Amount: pgtype.Numeric{Int: bigInt, Valid: true}, - Denom: amt.Denom, - } - } + case "auth_msg_revoke_session": + msg, err := cvt.toMsgRvSession() + if err != nil { + return nil, fmt.Errorf("failed to convert auth_msg_auth_rv_session: %w", err) + } + dbGroups.MsgAuthRvSession = append(dbGroups.MsgAuthRvSession, *msg) - // Convert maxDeposit from []Coin to dataTypes.Amount - coinMaxDeposit, ok := msgMap["max_deposit"].([]Coin) - if !ok { - return nil, fmt.Errorf("missing max_deposit") - } + case "auth_msg_revoke_all_sessions": + msg, err := cvt.toMsgRvAllSessions() + if err != nil { + return nil, fmt.Errorf("failed to convert auth_msg_auth_rv_all_sessions: %w", err) + } + dbGroups.MsgAuthRvAllSessions = append(dbGroups.MsgAuthRvAllSessions, *msg) - maxDeposit := make([]dataTypes.Amount, len(coinMaxDeposit)) - for j, amt := range coinMaxDeposit { - bigInt := big.NewInt(amt.Amount) - maxDeposit[j] = dataTypes.Amount{ - Amount: pgtype.Numeric{Int: bigInt, Valid: true}, - Denom: amt.Denom, + default: + return nil, fmt.Errorf("unknown message type: %s", msgType) } } - return &dataTypes.MsgRun{ - TxId: txId, - MessageCounter: messageCounter, - ChainName: chainName, - Caller: addressResolver.GetAddress(caller), - PkgPath: pkgPath, - PkgName: pkgName, - Send: send, - MaxDeposit: maxDeposit, - Signers: signerIds, - Timestamp: timestamp, - }, nil + return dbGroups, nil } diff --git a/pkgs/database/timescaledb/insert.go b/pkgs/database/timescaledb/insert.go index e10d481..4326881 100644 --- a/pkgs/database/timescaledb/insert.go +++ b/pkgs/database/timescaledb/insert.go @@ -278,6 +278,75 @@ func (t *TimescaleDb) InsertMsgMultiSend( return err } +// InsertMsgAuthCrSession inserts a slice of MsgAuthCrSession messages into the database using COPY FROM. +func (t *TimescaleDb) InsertMsgAuthCrSession(ctx context.Context, messages []sql_data_types.MsgAuthCrSession) error { + if len(messages) == 0 { + return nil + } + pgxSlice := pgx.CopyFromSlice(len(messages), func(i int) ([]any, error) { + return []any{ + messages[i].TxId, + messages[i].Timestamp, + messages[i].ChainName, + messages[i].Creator, + messages[i].SessionKey, + messages[i].ExpiresAt, + makePgxArray(messages[i].SpendLimit), + makePgxArray(messages[i].AllowPaths), + messages[i].SpendPeriod, + makePgxArray(messages[i].Signers), + messages[i].MessageCounter, + }, nil + }) + columns := messages[0].TableColumns() + tableName := messages[0].TableName() + _, err := t.pool.CopyFrom(ctx, pgx.Identifier{tableName}, columns, pgxSlice) + return err +} + +// InsertMsgAuthRvSession inserts a slice of MsgAuthRvSession messages into the database using COPY FROM. +func (t *TimescaleDb) InsertMsgAuthRvSession(ctx context.Context, messages []sql_data_types.MsgAuthRvSession) error { + if len(messages) == 0 { + return nil + } + pgxSlice := pgx.CopyFromSlice(len(messages), func(i int) ([]any, error) { + return []any{ + messages[i].TxId, + messages[i].Timestamp, + messages[i].ChainName, + messages[i].Creator, + messages[i].SessionKey, + makePgxArray(messages[i].Signers), + messages[i].MessageCounter, + }, nil + }) + columns := messages[0].TableColumns() + tableName := messages[0].TableName() + _, err := t.pool.CopyFrom(ctx, pgx.Identifier{tableName}, columns, pgxSlice) + return err +} + +// InsertMsgAuthRvAllSessions inserts a slice of MsgAuthRvAllSessions messages into the database using COPY FROM. +func (t *TimescaleDb) InsertMsgAuthRvAllSessions(ctx context.Context, messages []sql_data_types.MsgAuthRvAllSessions) error { + if len(messages) == 0 { + return nil + } + pgxSlice := pgx.CopyFromSlice(len(messages), func(i int) ([]any, error) { + return []any{ + messages[i].TxId, + messages[i].Timestamp, + messages[i].ChainName, + messages[i].Creator, + makePgxArray(messages[i].Signers), + messages[i].MessageCounter, + }, nil + }) + columns := messages[0].TableColumns() + tableName := messages[0].TableName() + _, err := t.pool.CopyFrom(ctx, pgx.Identifier{tableName}, columns, pgxSlice) + return err +} + // makePgxArray converts a Go slice into a pgx typed array for COPY FROM inserts. // Handles composite types and byte arrays; works on any insertable slice type. func makePgxArray[T any](v []T) pgtype.Array[T] { diff --git a/pkgs/database/timescaledb/query_block.go b/pkgs/database/timescaledb/query_block.go index cd6745d..be29a60 100644 --- a/pkgs/database/timescaledb/query_block.go +++ b/pkgs/database/timescaledb/query_block.go @@ -2,10 +2,12 @@ package timescaledb import ( "context" + "errors" "fmt" "time" "github.com/Cogwheel-Validator/spectra-gnoland-indexer/pkgs/database" + "github.com/jackc/pgx/v5" "golang.org/x/sync/errgroup" ) @@ -324,3 +326,35 @@ func (t *TimescaleDb) fetchTransactionData(ctx context.Context, query string, ar } return txs, rows.Err() } + +// GetAllBlockSigners returns all validators that signed a block, plus the proposer. +func (t *TimescaleDb) GetAllBlockSigners( + ctx context.Context, + chainName string, + blockHeight uint64, +) (*database.BlockSigners, error) { + query := ` + SELECT + vb.block_height, + gv.address AS proposer, + array( + SELECT gv.address + FROM unnest(vb.signed_vals) AS signed_val_id + JOIN gno_validators gv ON gv.id = signed_val_id + ) AS signed_vals + FROM validator_block_signing vb + LEFT JOIN gno_validators gv ON vb.proposer = gv.id + WHERE vb.chain_name = $1 + AND vb.block_height = $2 + ` + row := t.pool.QueryRow(ctx, query, chainName, blockHeight) + var blockSigners database.BlockSigners + err := row.Scan(&blockSigners.BlockHeight, &blockSigners.Proposer, &blockSigners.SignedVals) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, fmt.Errorf("block signers at height %d: %w", blockHeight, database.ErrNotFound) + } + return nil, err + } + return &blockSigners, nil +} diff --git a/pkgs/database/timescaledb/query_msg.go b/pkgs/database/timescaledb/query_msg.go index c155520..8710798 100644 --- a/pkgs/database/timescaledb/query_msg.go +++ b/pkgs/database/timescaledb/query_msg.go @@ -9,38 +9,6 @@ import ( "github.com/jackc/pgx/v5" ) -// GetAllBlockSigners returns all validators that signed a block, plus the proposer. -func (t *TimescaleDb) GetAllBlockSigners( - ctx context.Context, - chainName string, - blockHeight uint64, -) (*database.BlockSigners, error) { - query := ` - SELECT - vb.block_height, - gv.address AS proposer, - array( - SELECT gv.address - FROM unnest(vb.signed_vals) AS signed_val_id - JOIN gno_validators gv ON gv.id = signed_val_id - ) AS signed_vals - FROM validator_block_signing vb - LEFT JOIN gno_validators gv ON vb.proposer = gv.id - WHERE vb.chain_name = $1 - AND vb.block_height = $2 - ` - row := t.pool.QueryRow(ctx, query, chainName, blockHeight) - var blockSigners database.BlockSigners - err := row.Scan(&blockSigners.BlockHeight, &blockSigners.Proposer, &blockSigners.SignedVals) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return nil, fmt.Errorf("block signers at height %d: %w", blockHeight, database.ErrNotFound) - } - return nil, err - } - return &blockSigners, nil -} - // GetBankSend returns the bank send messages for a given transaction hash. func (t *TimescaleDb) GetBankSend( ctx context.Context, @@ -270,6 +238,222 @@ func (t *TimescaleDb) GetMsgRun( return msgRuns, nil } +// GetBankMultiSend returns the bank multi-send message rows for a given transaction hash. +// Each row represents one input (direction=false) or output (direction=true) entry. +func (t *TimescaleDb) GetBankMultiSend( + ctx context.Context, + txHash string, + chainName string, +) ([]*database.BankMultiSendRow, error) { + query := ` + SELECT + encode(id.tx_hash, 'base64') AS tx_hash, + bms.message_counter, + bms.timestamp, + bms.direction, + gna.address, + bms.coins, + array( + SELECT gn.address + FROM unnest(bms.signers) AS signer_id + JOIN gno_addresses gn ON gn.id = signer_id + ) AS signers + FROM bank_msg_multi_send bms + JOIN tx_hash_id id ON bms.tx_id = id.tx_id AND bms.chain_name = id.chain_name + LEFT JOIN gno_addresses gna ON bms.address_id = gna.id + WHERE id.tx_hash = decode($1, 'base64') + AND bms.chain_name = $2 + ` + rows, err := t.pool.Query(ctx, query, txHash, chainName) + if err != nil { + return nil, err + } + defer rows.Close() + result := make([]*database.BankMultiSendRow, 0) + for rows.Next() { + row := &database.BankMultiSendRow{} + err := rows.Scan( + &row.TxHash, + &row.MessageCounter, + &row.Timestamp, + &row.Direction, + &row.Address, + &row.Coins, + &row.Signers, + ) + if err != nil { + return nil, err + } + result = append(result, row) + } + if err := rows.Err(); err != nil { + return nil, err + } + return result, nil +} + +// GetMsgAuthCrSession returns the auth create-session messages for a given transaction hash. +func (t *TimescaleDb) GetMsgAuthCrSession( + ctx context.Context, + txHash string, + chainName string, +) ([]*database.MsgAuthCrSession, error) { + query := ` + SELECT + encode(id.tx_hash, 'base64') AS tx_hash, + acs.message_counter, + acs.timestamp, + gn_creator.address AS creator, + gn_session.address AS session_key, + acs.expires_at, + acs.spend_limit, + acs.spend_period, + acs.allow_paths, + array( + SELECT gn.address + FROM unnest(acs.signers) AS signer_id + JOIN gno_addresses gn ON gn.id = signer_id + ) AS signers + FROM auth_msg_create_session acs + JOIN tx_hash_id id ON acs.tx_id = id.tx_id AND acs.chain_name = id.chain_name + LEFT JOIN gno_addresses gn_creator ON acs.creator = gn_creator.id + LEFT JOIN gno_addresses gn_session ON acs.session_key = gn_session.id + WHERE id.tx_hash = decode($1, 'base64') + AND acs.chain_name = $2 + ` + rows, err := t.pool.Query(ctx, query, txHash, chainName) + if err != nil { + return nil, err + } + defer rows.Close() + + result := make([]*database.MsgAuthCrSession, 0) + for rows.Next() { + msg := &database.MsgAuthCrSession{} + err := rows.Scan( + &msg.TxHash, + &msg.MessageCounter, + &msg.Timestamp, + &msg.Creator, + &msg.SessionKey, + &msg.ExpiresAt, + &msg.SpendLimit, + &msg.SpendPeriod, + &msg.AllowPaths, + &msg.Signers, + ) + if err != nil { + return nil, err + } + result = append(result, msg) + } + if err := rows.Err(); err != nil { + return nil, err + } + return result, nil +} + +// GetMsgAuthRvSession returns the auth revoke-session messages for a given transaction hash. +func (t *TimescaleDb) GetMsgAuthRvSession( + ctx context.Context, + txHash string, + chainName string, +) ([]*database.MsgAuthRvSession, error) { + query := ` + SELECT + encode(id.tx_hash, 'base64') AS tx_hash, + rvs.message_counter, + rvs.timestamp, + gn_creator.address AS creator, + gn_session.address AS session_key, + array( + SELECT gn.address + FROM unnest(rvs.signers) AS signer_id + JOIN gno_addresses gn ON gn.id = signer_id + ) AS signers + FROM auth_msg_revoke_session rvs + JOIN tx_hash_id id ON rvs.tx_id = id.tx_id AND rvs.chain_name = id.chain_name + LEFT JOIN gno_addresses gn_creator ON rvs.creator = gn_creator.id + LEFT JOIN gno_addresses gn_session ON rvs.session_key = gn_session.id + WHERE id.tx_hash = decode($1, 'base64') + AND rvs.chain_name = $2 + ` + rows, err := t.pool.Query(ctx, query, txHash, chainName) + if err != nil { + return nil, err + } + defer rows.Close() + result := make([]*database.MsgAuthRvSession, 0) + for rows.Next() { + msg := &database.MsgAuthRvSession{} + err := rows.Scan( + &msg.TxHash, + &msg.MessageCounter, + &msg.Timestamp, + &msg.Creator, + &msg.SessionKey, + &msg.Signers, + ) + if err != nil { + return nil, err + } + result = append(result, msg) + } + if err := rows.Err(); err != nil { + return nil, err + } + return result, nil +} + +// GetMsgAuthRvAllSessions returns the auth revoke-all-sessions messages for a given transaction hash. +func (t *TimescaleDb) GetMsgAuthRvAllSessions( + ctx context.Context, + txHash string, + chainName string, +) ([]*database.MsgAuthRvAllSessions, error) { + query := ` + SELECT + encode(id.tx_hash, 'base64') AS tx_hash, + rvas.message_counter, + rvas.timestamp, + gn.address AS creator, + array( + SELECT gn.address + FROM unnest(rvas.signers) AS signer_id + JOIN gno_addresses gn ON gn.id = signer_id + ) AS signers + FROM auth_msg_revoke_all_sessions rvas + JOIN tx_hash_id id ON rvas.tx_id = id.tx_id AND rvas.chain_name = id.chain_name + LEFT JOIN gno_addresses gn ON rvas.creator = gn.id + WHERE id.tx_hash = decode($1, 'base64') + AND rvas.chain_name = $2 + ` + rows, err := t.pool.Query(ctx, query, txHash, chainName) + if err != nil { + return nil, err + } + defer rows.Close() + result := make([]*database.MsgAuthRvAllSessions, 0) + for rows.Next() { + msg := &database.MsgAuthRvAllSessions{} + err := rows.Scan( + &msg.TxHash, + &msg.MessageCounter, + &msg.Timestamp, + &msg.Creator, + &msg.Signers, + ) + if err != nil { + return nil, err + } + result = append(result, msg) + } + if err := rows.Err(); err != nil { + return nil, err + } + return result, nil +} + // GetMsgTypes returns the message types for a given transaction hash. func (t *TimescaleDb) GetMsgTypes(ctx context.Context, txHash string, chainName string) ([]string, error) { query := ` diff --git a/pkgs/database/types.go b/pkgs/database/types.go index 051cf33..498a1c9 100644 --- a/pkgs/database/types.go +++ b/pkgs/database/types.go @@ -219,6 +219,51 @@ type DenomVolumeHourly struct { Volume decimal.Decimal `json:"volume" doc:"Volume"` } +type MultiSendEntry struct { + Address string `json:"address" doc:"Address"` + Coins []Amount `json:"coins" doc:"Coins"` +} + +type BankMultiSendRow struct { + MessageCounter int16 `json:"message_counter" doc:"Transaction order integer, starts from 0"` + TxHash string `json:"tx_hash" doc:"Transaction hash (base64 encoded)"` + Timestamp time.Time `json:"timestamp" doc:"Transaction timestamp"` + Direction bool `json:"direction" doc:"True if output, false if input"` + Address string `json:"address" doc:"Address"` + Coins []Amount `json:"coins" doc:"Coins"` + Signers []string `json:"signers" doc:"Signers (addresses)"` +} + +type MsgAuthCrSession struct { + MessageCounter int16 `json:"message_counter" doc:"Transaction order integer, starts from 0"` + TxHash string `json:"tx_hash" doc:"Transaction hash (base64 encoded)"` + Timestamp time.Time `json:"timestamp" doc:"Transaction timestamp"` + Creator string `json:"creator" doc:"Creator address"` + SessionKey string `json:"session_key" doc:"Session key (bech32 address)"` + ExpiresAt time.Time `json:"expires_at" doc:"Session expiry timestamp"` + AllowPaths []string `json:"allow_paths" doc:"Allowed paths"` + SpendLimit []Amount `json:"spend_limit" doc:"Spend limit"` + SpendPeriod int64 `json:"spend_period" doc:"Spend period in seconds; 0 means infinite"` + Signers []string `json:"signers" doc:"Signers (addresses)"` +} + +type MsgAuthRvSession struct { + MessageCounter int16 `json:"message_counter" doc:"Transaction order integer, starts from 0"` + TxHash string `json:"tx_hash" doc:"Transaction hash (base64 encoded)"` + Timestamp time.Time `json:"timestamp" doc:"Transaction timestamp"` + Creator string `json:"creator" doc:"Creator address"` + SessionKey string `json:"session_key" doc:"Session key (bech32 address)"` + Signers []string `json:"signers" doc:"Signers (addresses)"` +} + +type MsgAuthRvAllSessions struct { + MessageCounter int16 `json:"message_counter" doc:"Transaction order integer, starts from 0"` + TxHash string `json:"tx_hash" doc:"Transaction hash (base64 encoded)"` + Timestamp time.Time `json:"timestamp" doc:"Transaction timestamp"` + Creator string `json:"creator" doc:"Creator address"` + Signers []string `json:"signers" doc:"Signers (addresses)"` +} + type ValidatorSigning struct { Time *time.Time `json:"time" doc:"Time" omitempty:"true"` BlocksSigned int64 `json:"blocks_signed" doc:"Blocks signed"` diff --git a/pkgs/sql_data_types/hypertables.go b/pkgs/sql_data_types/hypertables.go index f11fb89..23c5f00 100644 --- a/pkgs/sql_data_types/hypertables.go +++ b/pkgs/sql_data_types/hypertables.go @@ -501,7 +501,7 @@ func (mr *MsgRun) GetAllAddresses() *TxAddresses { type MsgMultiSend struct { TxId int64 `db:"tx_id" dbtype:"bigint" nullable:"false" primary:"true"` Timestamp time.Time `db:"timestamp" dbtype:"timestamptz" nullable:"false" primary:"true"` - ChainName string `db:"chain_name" dbtype:"text" nullable:"false" primary:"true"` + ChainName string `db:"chain_name" dbtype:"chain_name" nullable:"false" primary:"true"` // By direction it refers to if this part is output entry or input entry. // True means output, false means input. It has primary to true Direction bool `db:"direction" dbtype:"boolean" nullable:"false" primary:"true"` @@ -542,3 +542,124 @@ func (m MsgMultiSend) TableColumns() []string { } return columns } + +type MsgAuthCrSession struct { + TxId int64 `db:"tx_id" dbtype:"bigint" nullable:"false" primary:"true"` + Timestamp time.Time `db:"timestamp" dbtype:"timestamptz" nullable:"false" primary:"true"` + ChainName string `db:"chain_name" dbtype:"chain_name" nullable:"false" primary:"true"` + Creator int32 `db:"creator" dbtype:"integer" nullable:"false" primary:"false"` + SessionKey int32 `db:"session_key" dbtype:"integer" nullable:"false" primary:"false"` + ExpiresAt time.Time `db:"expires_at" dbtype:"timestamptz" nullable:"false" primary:"false"` + SpendLimit []Amount `db:"spend_limit" dbtype:"amount[]" nullable:"false" primary:"false"` + AllowPaths []string `db:"allow_paths" dbtype:"text[]" nullable:"false" primary:"false"` + // According to the original type in gno spend period is supposed to be seconds. + // Keep that in mind. Also 0 means infinite! + SpendPeriod int64 `db:"spend_period" dbtype:"bigint" nullable:"false" primary:"false"` + Signers []int32 `db:"signers" dbtype:"INTEGER[]" nullable:"false" primary:"false"` + MessageCounter int16 `db:"message_counter" dbtype:"smallint" nullable:"false" primary:"true"` +} + +func (ma MsgAuthCrSession) TableName() string { + return "auth_msg_create_session" +} + +func (ma MsgAuthCrSession) GetTableInfo() (*dbinit.TableInfo, error) { + return dbinit.GetTableInfo(ma, ma.TableName()) +} + +func (ma MsgAuthCrSession) TableColumns() []string { + fields := reflect.TypeFor[MsgAuthCrSession]() + numFields := fields.NumField() + columns := make([]string, numFields) + for i := range numFields { + field := fields.Field(i) + columns[i] = field.Tag.Get("db") + } + return columns +} + +func (ma *MsgAuthCrSession) GetAllAddresses() *TxAddresses { + txAddresses := NewTxAddresses(ma.TxId) + txAddresses.AddAddress(ma.Creator) + txAddresses.AddAddress(ma.SessionKey) + for _, addr := range ma.Signers { + txAddresses.AddAddress(addr) + } + return txAddresses +} + +type MsgAuthRvSession struct { + TxId int64 `db:"tx_id" dbtype:"bigint" nullable:"false" primary:"true"` + Timestamp time.Time `db:"timestamp" dbtype:"timestamptz" nullable:"false" primary:"true"` + ChainName string `db:"chain_name" dbtype:"chain_name" nullable:"false" primary:"true"` + Creator int32 `db:"creator" dbtype:"integer" nullable:"false" primary:"false"` + SessionKey int32 `db:"session_key" dbtype:"integer" nullable:"false" primary:"false"` + Signers []int32 `db:"signers" dbtype:"INTEGER[]" nullable:"false" primary:"false"` + MessageCounter int16 `db:"message_counter" dbtype:"smallint" nullable:"false" primary:"true"` +} + +func (ma MsgAuthRvSession) TableName() string { + return "auth_msg_revoke_session" +} + +func (ma MsgAuthRvSession) GetTableInfo() (*dbinit.TableInfo, error) { + return dbinit.GetTableInfo(ma, ma.TableName()) +} + +func (ma MsgAuthRvSession) TableColumns() []string { + fields := reflect.TypeFor[MsgAuthRvSession]() + numFields := fields.NumField() + columns := make([]string, numFields) + for i := range numFields { + field := fields.Field(i) + columns[i] = field.Tag.Get("db") + } + return columns +} + +func (ma *MsgAuthRvSession) GetAllAddresses() *TxAddresses { + txAddresses := NewTxAddresses(ma.TxId) + txAddresses.AddAddress(ma.Creator) + txAddresses.AddAddress(ma.SessionKey) + for _, addr := range ma.Signers { + txAddresses.AddAddress(addr) + } + return txAddresses +} + +type MsgAuthRvAllSessions struct { + TxId int64 `db:"tx_id" dbtype:"bigint" nullable:"false" primary:"true"` + Timestamp time.Time `db:"timestamp" dbtype:"timestamptz" nullable:"false" primary:"true"` + ChainName string `db:"chain_name" dbtype:"chain_name" nullable:"false" primary:"true"` + Creator int32 `db:"creator" dbtype:"integer" nullable:"false" primary:"false"` + Signers []int32 `db:"signers" dbtype:"INTEGER[]" nullable:"false" primary:"false"` + MessageCounter int16 `db:"message_counter" dbtype:"smallint" nullable:"false" primary:"true"` +} + +func (ma MsgAuthRvAllSessions) TableName() string { + return "auth_msg_revoke_all_sessions" +} + +func (ma MsgAuthRvAllSessions) GetTableInfo() (*dbinit.TableInfo, error) { + return dbinit.GetTableInfo(ma, ma.TableName()) +} + +func (ma MsgAuthRvAllSessions) TableColumns() []string { + fields := reflect.TypeFor[MsgAuthRvAllSessions]() + numFields := fields.NumField() + columns := make([]string, numFields) + for i := range numFields { + field := fields.Field(i) + columns[i] = field.Tag.Get("db") + } + return columns +} + +func (ma *MsgAuthRvAllSessions) GetAllAddresses() *TxAddresses { + txAddresses := NewTxAddresses(ma.TxId) + txAddresses.AddAddress(ma.Creator) + for _, addr := range ma.Signers { + txAddresses.AddAddress(addr) + } + return txAddresses +}