Skip to content

Commit f91ff27

Browse files
chrisli30will-dz
andauthored
release: staging → main (event trigger fixes, fee classification, JWT API key, sentry logging) (#509)
Co-authored-by: Will Zimmerman <will@avaprotocol.org>
1 parent 4e9f007 commit f91ff27

25 files changed

Lines changed: 860 additions & 106 deletions

.github/scripts/generate-test-config.sh

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ echo "Generating test config files from example template..."
88

99
mkdir -p config
1010

11-
# Prepend protocols to CHAIN_ENDPOINT (which is just the domain)
12-
CHAIN_RPC="https://${CHAIN_ENDPOINT}"
13-
CHAIN_WS="wss://${CHAIN_ENDPOINT}"
11+
# Strip any leading scheme (https://, http://, wss://, ws://) so we can
12+
# safely prepend the right one. The Test-environment secret may store
13+
# either a hostname-only form or a full URL depending on how it was set.
14+
CHAIN_HOST="${CHAIN_ENDPOINT#https://}"
15+
CHAIN_HOST="${CHAIN_HOST#http://}"
16+
CHAIN_HOST="${CHAIN_HOST#wss://}"
17+
CHAIN_HOST="${CHAIN_HOST#ws://}"
18+
CHAIN_RPC="https://${CHAIN_HOST}"
19+
CHAIN_WS="wss://${CHAIN_HOST}"
1420

1521
# Copy example file as base
1622
cp config/aggregator.example.yaml config/aggregator-sepolia.yaml

.github/workflows/run-test-on-pr.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ jobs:
8888
- name: Setup test configuration
8989
uses: ./.github/actions/setup-test-config
9090
with:
91-
chain-endpoint: ${{ vars.CHAIN_ENDPOINT }}
92-
bundler-rpc: ${{ vars.BUNDLER_RPC }}
91+
chain-endpoint: ${{ secrets.CHAIN_ENDPOINT }}
92+
bundler-rpc: ${{ secrets.BUNDLER_RPC }}
9393
tenderly-account: ${{ vars.TENDERLY_ACCOUNT }}
9494
tenderly-project: ${{ vars.TENDERLY_PROJECT }}
9595
controller-private-key: ${{ secrets.CONTROLLER_PRIVATE_KEY }}
@@ -150,8 +150,8 @@ jobs:
150150
- name: Setup test configuration
151151
uses: ./.github/actions/setup-test-config
152152
with:
153-
chain-endpoint: ${{ vars.CHAIN_ENDPOINT }}
154-
bundler-rpc: ${{ vars.BUNDLER_RPC }}
153+
chain-endpoint: ${{ secrets.CHAIN_ENDPOINT }}
154+
bundler-rpc: ${{ secrets.BUNDLER_RPC }}
155155
tenderly-account: ${{ vars.TENDERLY_ACCOUNT }}
156156
tenderly-project: ${{ vars.TENDERLY_PROJECT }}
157157
controller-private-key: ${{ secrets.CONTROLLER_PRIVATE_KEY }}

CLAUDE.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,19 @@ cd contracts && forge test # Run contract tests
8181
- **BigCache**: In-memory caching layer
8282
- **Migrations**: Versioned database migrations for schema changes
8383

84+
### Inspecting Aggregator Data (BadgerDB)
85+
86+
To read the live aggregator's stored task/execution state (e.g. the actual EventTrigger topics array as persisted, not the high-level CreateTask log summary), use the `examples/example.ts` CLI in the `ava-sdk-js` repo. It speaks the gRPC API against whatever endpoint the loaded `.env` points at (`localhost:2206` for the local `dev` aggregator).
87+
88+
```bash
89+
cd <path-to-ava-sdk-js>/examples
90+
yarn start getWorkflow <task_id> # full task: trigger queries, nodes, edges, inputVariables
91+
yarn start getExecution <task_id> <execution_id>
92+
yarn start listWorkflows
93+
```
94+
95+
The `getWorkflow` output is the ground truth — it shows the trigger's `topics`, `addresses`, and `inputVariables` exactly as stored. Prefer this over inferring state from log lines, since `engine.CreateTask` does not dump the full trigger payload to the aggregator log.
96+
8497
## Code Style
8598

8699
### Go Naming Conventions

aggregator/aggregator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ func (agg *Aggregator) migrate() {
253253
agg.backup = backup.NewService(agg.logger, agg.db, agg.config.BackupDir)
254254
agg.migrator = migrator.NewMigrator(agg.db, agg.backup, migrations.Migrations)
255255
if err := agg.migrator.Run(); err != nil {
256-
agg.logger.Error("Failed to run database migrations", "error", err.Error())
256+
agg.logger.Error("Failed to run database migrations", "error", err)
257257
panic("database migration failed - cannot continue")
258258
}
259259
}

aggregator/auth.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,15 @@ func (r *RpcServer) verifyAuth(ctx context.Context) (*model.User, error) {
249249
return nil, fmt.Errorf("%s: invalid chainId in audience", auth.InvalidAuthenticationKey)
250250
}
251251

252+
subject, _ := claims["sub"].(string)
253+
if !common.IsHexAddress(subject) {
254+
r.config.Logger.Error("API key has invalid/non-address subject; refusing to derive smart wallet",
255+
"subject", subject)
256+
return nil, fmt.Errorf("%s: subject must be a valid EOA address", auth.InvalidAuthenticationKey)
257+
}
258+
252259
user := model.User{
253-
Address: common.HexToAddress(claims["sub"].(string)),
260+
Address: common.HexToAddress(subject),
254261
}
255262

256263
// caching to reduce hitting eth rpc node

aggregator/key.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/AvaProtocol/EigenLayer-AVS/core/auth"
88
"github.com/AvaProtocol/EigenLayer-AVS/core/config"
9+
"github.com/ethereum/go-ethereum/common"
910
"github.com/golang-jwt/jwt/v5"
1011
)
1112

@@ -30,6 +31,10 @@ func CreateAdminKey(configPath string, opt CreateApiKeyOption) error {
3031
return fmt.Errorf("error: subject cannot be empty")
3132
}
3233

34+
if !common.IsHexAddress(opt.Subject) {
35+
return fmt.Errorf("error: subject must be a valid 0x-prefixed EOA address (got %q). The subject is used as the owner identity bound to this API key.", opt.Subject)
36+
}
37+
3338
if len(opt.Roles) < 1 {
3439
return fmt.Errorf("error: at least one role is required")
3540
}

aggregator/rpc_server.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -962,7 +962,7 @@ func (r *RpcServer) SimulateTask(ctx context.Context, req *avsproto.SimulateTask
962962
r.config.Logger.Error("simulate task failed",
963963
"user", user.Address.String(),
964964
"trigger_name", req.Trigger.Name,
965-
"error", err.Error(),
965+
"error", err,
966966
)
967967
return nil, status.Errorf(codes.Internal, "simulation failed: %v", err)
968968
}
@@ -1080,7 +1080,7 @@ func (r *RpcServer) handlePaginationError(err error, methodName string, userAddr
10801080
// Prepare logging fields for non-pagination errors
10811081
logFields := []interface{}{
10821082
"user", userAddr,
1083-
"error", err.Error(),
1083+
"error", err,
10841084
}
10851085

10861086
// Add context-specific fields
@@ -1233,7 +1233,7 @@ func (r *RpcServer) EstimateFees(ctx context.Context, req *avsproto.EstimateFees
12331233
resp, err := feeEstimator.EstimateFees(ctx, req)
12341234
if err != nil {
12351235
r.config.Logger.Error("failed to estimate fees",
1236-
"error", err.Error(),
1236+
"error", err,
12371237
"user", user.Address.String(),
12381238
"trigger_type", req.Trigger.Type.String())
12391239
return nil, status.Errorf(codes.Internal, "failed to estimate fees: %v", err)
@@ -1326,7 +1326,7 @@ func (agg *Aggregator) startRpcServer(ctx context.Context) error {
13261326

13271327
goSafe(func() {
13281328
if err := s.Serve(lis); err != nil {
1329-
agg.logger.Error("gRPC server failed to serve", "error", err.Error())
1329+
agg.logger.Error("gRPC server failed to serve", "error", err)
13301330
}
13311331
})
13321332
return nil

aggregator/task_engine.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,15 @@ func (agg *Aggregator) startTaskEngine(ctx context.Context) {
117117
taskengine.JobTypeExecuteTask,
118118
taskExecutor,
119119
); err != nil {
120-
agg.logger.Error("failed to register task processor", "error", err.Error())
120+
agg.logger.Error("failed to register task processor", "error", err)
121121
}
122122
if err := agg.engine.MustStart(); err != nil {
123-
agg.logger.Error("failed to start task engine", "error", err.Error())
123+
agg.logger.Error("failed to start task engine", "error", err)
124124
}
125125

126126
queueErr := agg.queue.MustStart()
127127
if queueErr != nil {
128-
agg.logger.Error("failed to start task queue", "error", queueErr.Error())
128+
agg.logger.Error("failed to start task queue", "error", queueErr)
129129
}
130130

131131
// Start periodic cleanup with environment-specific intervals

cmd/createAdminKey.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package cmd
22

33
import (
4+
"fmt"
5+
"os"
6+
47
"github.com/AvaProtocol/EigenLayer-AVS/aggregator"
58

69
"github.com/spf13/cobra"
@@ -10,16 +13,21 @@ var (
1013
apiKeyOption = aggregator.CreateApiKeyOption{}
1114
createApiKey = &cobra.Command{
1215
Use: "create-api-key",
13-
Short: "Create a long live JWT key to interact with userdata of AVS",
14-
Long: `Create an JWT key that allow one to manage user tasks. This key cannot control operator aspect, only user storage such as tasks management`,
16+
Short: "Create a long-lived JWT key to interact with user data of AVS",
17+
Long: `Create a JWT key that allows one to manage user tasks. This key cannot control operator aspect, only user storage such as tasks management.
18+
19+
The --subject flag must be a 0x-prefixed EOA address. The auth layer treats the JWT subject as the owner address and derives a smart wallet from it (see aggregator/auth.go), so any non-address subject will fail authentication.`,
1520
Run: func(cmd *cobra.Command, args []string) {
16-
aggregator.CreateAdminKey(config, apiKeyOption)
21+
if err := aggregator.CreateAdminKey(config, apiKeyOption); err != nil {
22+
fmt.Fprintln(os.Stderr, err)
23+
os.Exit(1)
24+
}
1725
},
1826
}
1927
)
2028

2129
func init() {
2230
createApiKey.Flags().StringArrayVar(&(apiKeyOption.Roles), "role", []string{}, "Role for API Key")
23-
createApiKey.Flags().StringVarP(&(apiKeyOption.Subject), "subject", "s", "admin", "subject name to be use for jwt api key")
31+
createApiKey.Flags().StringVarP(&(apiKeyOption.Subject), "subject", "s", "", "owner EOA address (0x...) bound to this API key; required")
2432
rootCmd.AddCommand(createApiKey)
2533
}

core/apqueue/worker.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func (w *Worker) ProcessSignal(jid uint64) {
101101
"job_id", jid,
102102
"task_id", job.Name,
103103
"job_type", job.Type,
104-
"error", err.Error())
104+
"error", err)
105105
}
106106
}
107107
}

0 commit comments

Comments
 (0)