From cb6a3f462c98416be46fceae3d140f0c24486cf2 Mon Sep 17 00:00:00 2001 From: CrossAgent Date: Tue, 26 May 2026 21:40:18 +0800 Subject: [PATCH 1/2] Fix AgentKeys live ABI audit paths --- internal/agentkeys/audit.go | 71 +++++++++++-- internal/agentkeys/audit_test.go | 169 ++++++++++++++++++++++++++++++ internal/server/http/agentkeys.go | 66 +++++++++--- 3 files changed, 281 insertions(+), 25 deletions(-) diff --git a/internal/agentkeys/audit.go b/internal/agentkeys/audit.go index 794a006..0aa247f 100644 --- a/internal/agentkeys/audit.go +++ b/internal/agentkeys/audit.go @@ -25,16 +25,18 @@ const ( HeimaChainID = 212013 CredentialAuditContractAddress = "0x63c4545ac01c77cc74044f25b8edea3880224577" - AuditAppendedV2Signature = "AuditAppendedV2(bytes32,bytes32,uint8,bytes32)" - AuditAppendedCurrentSignature = "AuditAppended(bytes32,bytes32,bytes32,uint8,uint256,bytes32)" - AuditRootAppendedV2Signature = "AuditRootAppendedV2(bytes32,bytes32,bytes32,uint64)" + AuditAppendedV2Signature = "AuditAppendedV2(bytes32,bytes32,uint8,bytes32)" + AuditAppendedCurrentSignature = "AuditAppended(bytes32,bytes32,bytes32,uint8,uint256,bytes32)" + AuditRootAppendedV2Signature = "AuditRootAppendedV2(bytes32,bytes32,bytes32,uint64)" + AuditRootAppendedCurrentSignature = "AuditRootAppended(bytes32,bytes32,uint256,uint64)" ) var ( - AuditAppendedV2Topic = crypto.Keccak256Hash([]byte(AuditAppendedV2Signature)).Hex() - AuditAppendedCurrentTopic = crypto.Keccak256Hash([]byte(AuditAppendedCurrentSignature)).Hex() - AuditRootAppendedV2Topic = crypto.Keccak256Hash([]byte(AuditRootAppendedV2Signature)).Hex() - ErrEnvelopeNotFound = errors.New("agentkeys audit envelope not found") + AuditAppendedV2Topic = crypto.Keccak256Hash([]byte(AuditAppendedV2Signature)).Hex() + AuditAppendedCurrentTopic = crypto.Keccak256Hash([]byte(AuditAppendedCurrentSignature)).Hex() + AuditRootAppendedV2Topic = crypto.Keccak256Hash([]byte(AuditRootAppendedV2Signature)).Hex() + AuditRootAppendedCurrentTopic = crypto.Keccak256Hash([]byte(AuditRootAppendedCurrentSignature)).Hex() + ErrEnvelopeNotFound = errors.New("agentkeys audit envelope not found") ) type Envelope struct { @@ -72,6 +74,8 @@ type AuditAppendedV2Event struct { } type AuditRootAppendedV2Event struct { + EventName string `json:"event_name"` + EventTopic string `json:"event_topic"` OperatorOmni string `json:"operator_omni"` MerkleRoot string `json:"merkle_root"` OpKindBitmapU256 string `json:"op_kind_bitmap_u256"` @@ -470,7 +474,7 @@ func DecodeTypedAuditRowsBestEffort(ctx context.Context, logs []EVMLogRecord, wo } func DecodeAuditRootRows(ctx context.Context, rootLog EVMLogRecord, leafLogs []EVMLogRecord, workerBaseURL string, cache *EnvelopeCache) (*AuditRootRows, error) { - event, err := DecodeAuditRootAppendedV2Log(rootLog.Topics, rootLog.Data) + event, err := DecodeAuditRootAppendedLog(rootLog.Topics, rootLog.Data) if err != nil { return nil, err } @@ -483,7 +487,7 @@ func DecodeAuditRootRows(ctx context.Context, rootLog EVMLogRecord, leafLogs []E return nil, fmt.Errorf("root logIndex: %w", err) } - rows, err := DecodeTypedAuditRows(ctx, leafLogs, workerBaseURL, cache) + rows, err := DecodeTypedAuditRowsBestEffort(ctx, leafLogs, workerBaseURL, cache) if err != nil { return nil, err } @@ -518,6 +522,10 @@ func PaddedOpKindTopic(opKind uint8) string { return "0x" + strings.Repeat("0", 62) + fmt.Sprintf("%02x", opKind) } +func CurrentAuditOpKindDataPrefix(opKind uint8) string { + return fmt.Sprintf("%064x", opKind) +} + func OpKindTopicsFromBitmap(bitmap string) ([]string, error) { bytes, err := hex.DecodeString(strings.TrimPrefix(strings.ToLower(bitmap), "0x")) if err != nil { @@ -631,6 +639,8 @@ func DecodeAuditRootAppendedV2Log(topics []string, data string) (*AuditRootAppen return nil, fmt.Errorf("entry_count: %w", err) } return &AuditRootAppendedV2Event{ + EventName: "AuditRootAppendedV2", + EventTopic: AuditRootAppendedV2Topic, OperatorOmni: normalizeBytes32Topic(topics[1]), MerkleRoot: normalizeBytes32Topic(topics[2]), OpKindBitmapU256: bitmap, @@ -638,6 +648,49 @@ func DecodeAuditRootAppendedV2Log(topics []string, data string) (*AuditRootAppen }, nil } +func DecodeAuditRootAppendedLog(topics []string, data string) (*AuditRootAppendedV2Event, error) { + if len(topics) == 0 { + return nil, fmt.Errorf("audit root event requires topic0") + } + switch { + case strings.EqualFold(topics[0], AuditRootAppendedV2Topic): + return DecodeAuditRootAppendedV2Log(topics, data) + case strings.EqualFold(topics[0], AuditRootAppendedCurrentTopic): + return DecodeAuditRootAppendedCurrentLog(topics, data) + default: + return nil, fmt.Errorf("unexpected audit root event topic0 %s", topics[0]) + } +} + +func DecodeAuditRootAppendedCurrentLog(topics []string, data string) (*AuditRootAppendedV2Event, error) { + if len(topics) != 3 { + return nil, fmt.Errorf("AuditRootAppended requires 3 topics") + } + if !strings.EqualFold(topics[0], AuditRootAppendedCurrentTopic) { + return nil, fmt.Errorf("unexpected AuditRootAppended topic0 %s", topics[0]) + } + bitmap, err := abiWord(data, 0) + if err != nil { + return nil, fmt.Errorf("op_kind_bitmap: %w", err) + } + countHex, err := abiWord(data, 1) + if err != nil { + return nil, fmt.Errorf("entry_count: %w", err) + } + count, err := strconv.ParseUint(countHex[48:], 16, 64) + if err != nil { + return nil, fmt.Errorf("entry_count: %w", err) + } + return &AuditRootAppendedV2Event{ + EventName: "AuditRootAppended", + EventTopic: AuditRootAppendedCurrentTopic, + OperatorOmni: normalizeBytes32Topic(topics[1]), + MerkleRoot: normalizeBytes32Topic(topics[2]), + OpKindBitmapU256: "0x" + bitmap, + EntryCount: count, + }, nil +} + func FetchEnvelope(ctx context.Context, workerBaseURL string, hash string) ([]byte, error) { base, err := url.Parse(workerBaseURL) if err != nil { diff --git a/internal/agentkeys/audit_test.go b/internal/agentkeys/audit_test.go index dfc1fd6..d852494 100644 --- a/internal/agentkeys/audit_test.go +++ b/internal/agentkeys/audit_test.go @@ -9,6 +9,7 @@ import ( "net/http" "net/http/httptest" "os" + "sort" "strings" "testing" @@ -423,6 +424,42 @@ func TestDecodeTypedAuditRowsAndRootLeaves(t *testing.T) { assert.Equal(t, "DeviceAdd", rootRows.Rows[1].OpKindName) } +func TestDecodeAuditRootRowsCurrentKeepsChainOnlyLeavesWhenWorkerMissing(t *testing.T) { + operator := "0x941cb1c3260518bbf40eac7d02663517fc7cff304d9b03e80d2cc54126c6bef2" + actor := "0x82a0609ae28527453e7c7654ec11d57f1b66a442e39a2ec5e3b3f76178c87268" + rootHash := "0x32301a0bd7c9c1d064f0d3891c78ad00a6d9fa758ebb14a1a0ff64eb4f4ca3aa" + firstHash := "0xdb927ad4467c02867819a1379c7c0b9a35103452c789badeae6e531b5d2f8e1c" + secondHash := "0x3d67f9734a38829d9e2289cd9551caa7f50ba66bd521981fb8504be4ab23a223" + rootLog := auditRootCurrentLog(operator, rootHash, "0x"+strings.Repeat("0", 62)+"1c", 2, 9634690, 0) + leafLogs := []EVMLogRecord{ + auditAppendedCurrentLog(operator, actor, 0, secondHash, 1, 9625271, 0), + auditAppendedCurrentLog(operator, actor, 0, firstHash, 0, 9625257, 0), + } + + srv := httptest.NewServer(http.NotFoundHandler()) + defer srv.Close() + + rootRows, err := DecodeAuditRootRows(context.Background(), rootLog, leafLogs, srv.URL, NewEnvelopeCache()) + require.NoError(t, err) + assert.Equal(t, rootHash, rootRows.MerkleRoot) + assert.Equal(t, operator, rootRows.OperatorOmni) + assert.Equal(t, "0x"+strings.Repeat("0", 62)+"1c", rootRows.OpKindBitmapU256) + assert.Equal(t, uint64(2), rootRows.EntryCount) + assert.Equal(t, uint64(9634690), rootRows.Block) + assert.Equal(t, []string{firstHash, secondHash}, rootRows.Leaves) + require.Len(t, rootRows.Rows, 2) + assert.Equal(t, "0", rootRows.Rows[0].CurrentSequence) + assert.Equal(t, "1", rootRows.Rows[1].CurrentSequence) + for _, row := range rootRows.Rows { + assert.Equal(t, "AuditAppended", row.EventName) + assert.Equal(t, uint8(0), row.OpKind) + assert.Equal(t, "CredStore", row.OpKindName) + assert.False(t, row.EnvelopeAvailable) + assert.False(t, row.HashVerified) + require.NotNil(t, row.EnvelopeFetchError) + } +} + func TestDecodeTypedAuditRowsBestEffortKeepsLiveChainRowsWhenWorkerMissing(t *testing.T) { operator := "0x" + strings.Repeat("94", 32) actor := "0x" + strings.Repeat("82", 32) @@ -444,6 +481,24 @@ func TestDecodeTypedAuditRowsBestEffortKeepsLiveChainRowsWhenWorkerMissing(t *te assert.Contains(t, *rows[0].EnvelopeFetchError, "agentkeys audit envelope not found") } +func TestCurrentAuditOpKindDataPrefixMatchesStoredReceiptData(t *testing.T) { + prefix := CurrentAuditOpKindDataPrefix(0) + + assert.Equal(t, strings.Repeat("0", 64), prefix) + assert.NotContains(t, prefix, "0x") + + liveStoredData := strings.TrimPrefix( + "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000db927ad4467c02867819a1379c7c0b9a35103452c789badeae6e531b5d2f8e1c", + "0x", + ) + assert.True(t, strings.HasPrefix(liveStoredData, prefix)) +} + +func TestCurrentAuditOpKindDataPrefixSupportsUint8Boundary(t *testing.T) { + assert.Equal(t, strings.Repeat("0", 62)+"ff", CurrentAuditOpKindDataPrefix(255)) + assert.Equal(t, fmt.Sprintf("%064x", 21), CurrentAuditOpKindDataPrefix(21)) +} + func TestDecodeLiveHeimaCurrentAuditFixture(t *testing.T) { body, err := os.ReadFile("../../tests/fixtures/agentkeys/heima-mainnet-current-auditappended.jsonl") require.NoError(t, err) @@ -482,6 +537,57 @@ func TestDecodeLiveHeimaCurrentAuditFixture(t *testing.T) { } } +func TestDecodeLiveHeimaCurrentAuditFixtureFilteringAndPagination(t *testing.T) { + rows := decodeLiveHeimaCurrentRows(t) + require.Len(t, rows, 13) + + opKindRows := rows[:0] + for _, row := range rows { + if row.OpKind == 0 { + opKindRows = append(opKindRows, row) + } + } + require.Len(t, opKindRows, len(rows)) + + sort.SliceStable(opKindRows, func(i, j int) bool { + if opKindRows[i].Block == opKindRows[j].Block { + return opKindRows[i].LogIndex > opKindRows[j].LogIndex + } + return opKindRows[i].Block > opKindRows[j].Block + }) + + firstPage := opKindRows[:5] + secondPage := make([]TypedAuditRow, 0, 5) + cursorBlock := firstPage[len(firstPage)-1].Block + cursorLogIndex := firstPage[len(firstPage)-1].LogIndex + for _, row := range opKindRows { + if row.Block < cursorBlock || (row.Block == cursorBlock && row.LogIndex < cursorLogIndex) { + secondPage = append(secondPage, row) + if len(secondPage) == 5 { + break + } + } + } + + require.Len(t, firstPage, 5) + require.Len(t, secondPage, 5) + assert.Equal(t, uint64(9632387), firstPage[0].Block) + assert.Equal(t, "12", firstPage[0].CurrentSequence) + assert.Equal(t, uint64(9631511), firstPage[4].Block) + assert.Equal(t, "8", firstPage[4].CurrentSequence) + assert.Equal(t, uint64(9631477), secondPage[0].Block) + assert.Equal(t, "7", secondPage[0].CurrentSequence) + combined := append([]TypedAuditRow{}, firstPage...) + combined = append(combined, secondPage...) + for _, row := range combined { + assert.Equal(t, "AuditAppended", row.EventName) + assert.Equal(t, uint8(0), row.OpKind) + assert.Equal(t, "CredStore", row.OpKindName) + assert.False(t, row.EnvelopeAvailable) + require.NotNil(t, row.EnvelopeFetchError) + } +} + func TestDecodeEnvelopeRejectsVersionAndNonCanonicalMap(t *testing.T) { envelope := map[string]interface{}{ "version": uint8(2), @@ -546,8 +652,16 @@ func TestDecodeAuditEventLogs(t *testing.T) { rootData := "0x" + strings.Repeat("dd", 32) + strings.Repeat("0", 63) + "7" root, err := DecodeAuditRootAppendedV2Log([]string{AuditRootAppendedV2Topic, operator, envelopeHash}, rootData) require.NoError(t, err) + assert.Equal(t, "AuditRootAppendedV2", root.EventName) assert.Equal(t, "0x"+strings.Repeat("dd", 32), root.OpKindBitmapU256) assert.Equal(t, uint64(7), root.EntryCount) + + currentRootData := "0x" + strings.Repeat("0", 62) + "1c" + strings.Repeat("0", 63) + "2" + currentRoot, err := DecodeAuditRootAppendedCurrentLog([]string{AuditRootAppendedCurrentTopic, operator, envelopeHash}, currentRootData) + require.NoError(t, err) + assert.Equal(t, "AuditRootAppended", currentRoot.EventName) + assert.Equal(t, "0x"+strings.Repeat("0", 62)+"1c", currentRoot.OpKindBitmapU256) + assert.Equal(t, uint64(2), currentRoot.EntryCount) } func TestFetchEnvelopeAndDecodeAcceptsHashWithoutPrefix(t *testing.T) { @@ -676,6 +790,61 @@ func auditRootLog(operator, merkleRoot string, opKinds []uint8, entryCount uint6 } } +func auditRootCurrentLog(operator, merkleRoot string, opKindBitmap string, entryCount uint64, block uint64, logIndex uint64) EVMLogRecord { + return EVMLogRecord{ + Address: CredentialAuditContractAddress, + Topics: []string{AuditRootAppendedCurrentTopic, operator, merkleRoot}, + Data: "0x" + strings.TrimPrefix(opKindBitmap, "0x") + fmt.Sprintf("%064x", entryCount), + BlockNumber: fmt.Sprintf("0x%x", block), + BlockHash: "0x" + strings.Repeat("77", 32), + Timestamp: "0x65f00000", + LogIndex: fmt.Sprintf("0x%x", logIndex), + TransactionHash: "0x" + strings.Repeat("88", 32), + TransactionIndex: "0x0", + } +} + +func decodeLiveHeimaCurrentRows(t *testing.T) []TypedAuditRow { + t.Helper() + + body, err := os.ReadFile("../../tests/fixtures/agentkeys/heima-mainnet-current-auditappended.jsonl") + require.NoError(t, err) + + srv := httptest.NewServer(http.NotFoundHandler()) + defer srv.Close() + + logs := make([]EVMLogRecord, 0) + for _, line := range strings.Split(strings.TrimSpace(string(body)), "\n") { + var row struct { + ContractAddress string `json:"contract_address"` + RawTopics []string `json:"raw_topics"` + RawData string `json:"raw_data"` + BlockNumber uint64 `json:"block_number"` + BlockHash string `json:"block_hash"` + Timestamp uint64 `json:"timestamp"` + TxHash string `json:"txhash"` + TransactionIndex uint64 `json:"transaction_index"` + LogIndex uint64 `json:"log_index"` + } + require.NoError(t, json.Unmarshal([]byte(line), &row)) + logs = append(logs, EVMLogRecord{ + Address: row.ContractAddress, + Topics: row.RawTopics, + Data: row.RawData, + BlockNumber: fmt.Sprintf("0x%x", row.BlockNumber), + BlockHash: row.BlockHash, + Timestamp: fmt.Sprintf("0x%x", row.Timestamp), + LogIndex: fmt.Sprintf("0x%x", row.LogIndex), + TransactionHash: row.TxHash, + TransactionIndex: fmt.Sprintf("0x%x", row.TransactionIndex), + }) + } + + rows, err := DecodeTypedAuditRowsBestEffort(context.Background(), logs, srv.URL, NewEnvelopeCache()) + require.NoError(t, err) + return rows +} + func mustHexBytes(t *testing.T, value string) []byte { t.Helper() b, err := hex.DecodeString(strings.TrimPrefix(value, "0x")) diff --git a/internal/server/http/agentkeys.go b/internal/server/http/agentkeys.go index 511afba..9dc58e9 100644 --- a/internal/server/http/agentkeys.go +++ b/internal/server/http/agentkeys.go @@ -86,10 +86,20 @@ func agentkeysAuditRowsHandle(c *gin.Context) { c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) return } - if query.opKind != nil { + rows, nextCursor := agentkeysAuditRowsPage(rows, sortDir, limit, query.opKind) + c.JSON(http.StatusOK, agentkeys.AuditRowsPage{ + ChainID: agentkeys.HeimaChainID, + ContractAddress: agentkeysAuditContractAddress(), + Events: rows, + NextCursor: nextCursor, + }) +} + +func agentkeysAuditRowsPage(rows []agentkeys.TypedAuditRow, sortDir string, limit int, opKind *uint8) ([]agentkeys.TypedAuditRow, *string) { + if opKind != nil { filtered := rows[:0] for _, row := range rows { - if row.OpKind == *query.opKind { + if row.OpKind == *opKind { filtered = append(filtered, row) } } @@ -116,28 +126,19 @@ func agentkeysAuditRowsHandle(c *gin.Context) { cursor := encodeAgentKeysCursor(agentkeysAuditCursor{Block: rows[len(rows)-1].Block, LogIndex: rows[len(rows)-1].LogIndex}) nextCursor = &cursor } - c.JSON(http.StatusOK, agentkeys.AuditRowsPage{ - ChainID: agentkeys.HeimaChainID, - ContractAddress: agentkeysAuditContractAddress(), - Events: rows, - NextCursor: nextCursor, - }) + return rows, nextCursor } func agentkeysAuditRootHandle(c *gin.Context) { root := normalizeAgentKeysBytes32(c.Param("merkle_root")) - rootLogs := agentkeysEvmAPI.API_GetLogsForAgentKeys(c.Request.Context(), "block_num desc, `index` desc", 1, - model.Where("address = ?", agentkeysAuditContractAddress()), - model.Where("method_hash = ?", agentkeys.AuditRootAppendedV2Topic), - model.Where("topic2 = ?", root), - ) + rootLogs := agentkeysAuditRootLogs(c, root) if len(rootLogs) == 0 { c.JSON(http.StatusNotFound, gin.H{"error": "not_found"}) return } rootRecord := toAgentKeysLogs(rootLogs)[0] - rootEvent, err := agentkeys.DecodeAuditRootAppendedV2Log(rootRecord.Topics, rootRecord.Data) + rootEvent, err := agentkeys.DecodeAuditRootAppendedLog(rootRecord.Topics, rootRecord.Data) if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) return @@ -159,7 +160,14 @@ func agentkeysAuditRootHandle(c *gin.Context) { } leafLogs := []evmdao.EtherscanLogsRes{} - if rootEvent.EntryCount > 0 && len(opKindTopics) > 0 { + if rootEvent.EntryCount > 0 && strings.EqualFold(rootEvent.EventTopic, agentkeys.AuditRootAppendedCurrentTopic) { + leafLogs = agentkeysEvmAPI.API_GetLogsForAgentKeys(c.Request.Context(), "block_num desc, `index` desc", int(rootEvent.EntryCount), + model.Where("address = ?", agentkeysAuditContractAddress()), + model.Where("method_hash = ?", agentkeys.AuditAppendedCurrentTopic), + model.Where("topic1 = ?", rootEvent.OperatorOmni), + model.Where("(block_num < ? OR (block_num = ? AND `index` < ?))", rootBlock, rootBlock, rootLogIndex), + ) + } else if rootEvent.EntryCount > 0 && len(opKindTopics) > 0 { leafLogs = agentkeysEvmAPI.API_GetLogsForAgentKeys(c.Request.Context(), "block_num desc, `index` desc", int(rootEvent.EntryCount), model.Where("address = ?", agentkeysAuditContractAddress()), model.Where("method_hash = ?", agentkeys.AuditAppendedV2Topic), @@ -177,6 +185,31 @@ func agentkeysAuditRootHandle(c *gin.Context) { c.JSON(http.StatusOK, rows) } +func agentkeysAuditRootLogs(c *gin.Context, root string) []evmdao.EtherscanLogsRes { + opts := []model.Option{ + model.Where("address = ?", agentkeysAuditContractAddress()), + model.Where("topic2 = ?", root), + } + logs := agentkeysEvmAPI.API_GetLogsForAgentKeys(c.Request.Context(), "block_num desc, `index` desc", 1, + appendAgentKeysAuditOpts(opts, model.Where("method_hash = ?", agentkeys.AuditRootAppendedCurrentTopic))...) + logs = append(logs, agentkeysEvmAPI.API_GetLogsForAgentKeys(c.Request.Context(), "block_num desc, `index` desc", 1, + appendAgentKeysAuditOpts(opts, model.Where("method_hash = ?", agentkeys.AuditRootAppendedV2Topic))...)...) + sort.SliceStable(logs, func(i, j int) bool { + leftBlock, _ := parseAgentKeysUint(logs[i].BlockNumber) + rightBlock, _ := parseAgentKeysUint(logs[j].BlockNumber) + if leftBlock == rightBlock { + leftIndex, _ := parseAgentKeysUint(logs[i].LogIndex) + rightIndex, _ := parseAgentKeysUint(logs[j].LogIndex) + return leftIndex > rightIndex + } + return leftBlock > rightBlock + }) + if len(logs) > 1 { + logs = logs[:1] + } + return logs +} + func agentkeysAuditWorkerURL() string { workerURL := os.Getenv("AGENTKEYS_AUDIT_WORKER_URL") if workerURL == "" { @@ -248,7 +281,8 @@ func agentkeysAuditLogs(c *gin.Context, order string, limit int, query agentkeys } currentOpts := appendAgentKeysAuditOpts(query.opts, model.Where("method_hash = ?", agentkeys.AuditAppendedCurrentTopic)) if query.opKind != nil { - currentOpts = append(currentOpts, model.Where("data like ?", "0x"+fmt.Sprintf("%064x", *query.opKind)+"%")) + prefix := agentkeys.CurrentAuditOpKindDataPrefix(*query.opKind) + currentOpts = append(currentOpts, model.Where("(data like ? or data like ?)", prefix+"%", "0x"+prefix+"%")) } logs := agentkeysEvmAPI.API_GetLogsForAgentKeys(c.Request.Context(), order, limit, v2Opts...) logs = append(logs, agentkeysEvmAPI.API_GetLogsForAgentKeys(c.Request.Context(), order, limit, currentOpts...)...) From 295470c844477e57828aebc63a7b98a4dfe19eae Mon Sep 17 00:00:00 2001 From: CrossAgent Date: Wed, 27 May 2026 13:43:57 +0800 Subject: [PATCH 2/2] Clarify AgentKeys current root metadata --- internal/agentkeys/audit.go | 23 +++++++++++++---------- internal/agentkeys/audit_test.go | 10 ++++++---- internal/server/http/agentkeys.go | 29 +++++++++++++++-------------- 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/internal/agentkeys/audit.go b/internal/agentkeys/audit.go index 0aa247f..3e80642 100644 --- a/internal/agentkeys/audit.go +++ b/internal/agentkeys/audit.go @@ -78,7 +78,8 @@ type AuditRootAppendedV2Event struct { EventTopic string `json:"event_topic"` OperatorOmni string `json:"operator_omni"` MerkleRoot string `json:"merkle_root"` - OpKindBitmapU256 string `json:"op_kind_bitmap_u256"` + OpKindBitmapU256 string `json:"op_kind_bitmap_u256,omitempty"` + RootIndex string `json:"root_index,omitempty"` EntryCount uint64 `json:"entry_count"` } @@ -125,7 +126,8 @@ type AuditRootRows struct { ContractAddress string `json:"contract_address"` MerkleRoot string `json:"merkle_root"` OperatorOmni string `json:"operator_omni"` - OpKindBitmapU256 string `json:"op_kind_bitmap_u256"` + OpKindBitmapU256 string `json:"op_kind_bitmap_u256,omitempty"` + RootIndex string `json:"root_index,omitempty"` EntryCount uint64 `json:"entry_count"` Block uint64 `json:"block"` BlockHash string `json:"block_hash"` @@ -508,6 +510,7 @@ func DecodeAuditRootRows(ctx context.Context, rootLog EVMLogRecord, leafLogs []E MerkleRoot: event.MerkleRoot, OperatorOmni: event.OperatorOmni, OpKindBitmapU256: event.OpKindBitmapU256, + RootIndex: event.RootIndex, EntryCount: event.EntryCount, Block: block, BlockHash: normalizeHexHash(rootLog.BlockHash), @@ -669,9 +672,9 @@ func DecodeAuditRootAppendedCurrentLog(topics []string, data string) (*AuditRoot if !strings.EqualFold(topics[0], AuditRootAppendedCurrentTopic) { return nil, fmt.Errorf("unexpected AuditRootAppended topic0 %s", topics[0]) } - bitmap, err := abiWord(data, 0) + rootIndex, err := abiUint256Decimal(data, 0) if err != nil { - return nil, fmt.Errorf("op_kind_bitmap: %w", err) + return nil, fmt.Errorf("root_index: %w", err) } countHex, err := abiWord(data, 1) if err != nil { @@ -682,12 +685,12 @@ func DecodeAuditRootAppendedCurrentLog(topics []string, data string) (*AuditRoot return nil, fmt.Errorf("entry_count: %w", err) } return &AuditRootAppendedV2Event{ - EventName: "AuditRootAppended", - EventTopic: AuditRootAppendedCurrentTopic, - OperatorOmni: normalizeBytes32Topic(topics[1]), - MerkleRoot: normalizeBytes32Topic(topics[2]), - OpKindBitmapU256: "0x" + bitmap, - EntryCount: count, + EventName: "AuditRootAppended", + EventTopic: AuditRootAppendedCurrentTopic, + OperatorOmni: normalizeBytes32Topic(topics[1]), + MerkleRoot: normalizeBytes32Topic(topics[2]), + RootIndex: rootIndex, + EntryCount: count, }, nil } diff --git a/internal/agentkeys/audit_test.go b/internal/agentkeys/audit_test.go index d852494..7c36663 100644 --- a/internal/agentkeys/audit_test.go +++ b/internal/agentkeys/audit_test.go @@ -443,7 +443,8 @@ func TestDecodeAuditRootRowsCurrentKeepsChainOnlyLeavesWhenWorkerMissing(t *test require.NoError(t, err) assert.Equal(t, rootHash, rootRows.MerkleRoot) assert.Equal(t, operator, rootRows.OperatorOmni) - assert.Equal(t, "0x"+strings.Repeat("0", 62)+"1c", rootRows.OpKindBitmapU256) + assert.Empty(t, rootRows.OpKindBitmapU256) + assert.Equal(t, "28", rootRows.RootIndex) assert.Equal(t, uint64(2), rootRows.EntryCount) assert.Equal(t, uint64(9634690), rootRows.Block) assert.Equal(t, []string{firstHash, secondHash}, rootRows.Leaves) @@ -660,7 +661,8 @@ func TestDecodeAuditEventLogs(t *testing.T) { currentRoot, err := DecodeAuditRootAppendedCurrentLog([]string{AuditRootAppendedCurrentTopic, operator, envelopeHash}, currentRootData) require.NoError(t, err) assert.Equal(t, "AuditRootAppended", currentRoot.EventName) - assert.Equal(t, "0x"+strings.Repeat("0", 62)+"1c", currentRoot.OpKindBitmapU256) + assert.Empty(t, currentRoot.OpKindBitmapU256) + assert.Equal(t, "28", currentRoot.RootIndex) assert.Equal(t, uint64(2), currentRoot.EntryCount) } @@ -790,11 +792,11 @@ func auditRootLog(operator, merkleRoot string, opKinds []uint8, entryCount uint6 } } -func auditRootCurrentLog(operator, merkleRoot string, opKindBitmap string, entryCount uint64, block uint64, logIndex uint64) EVMLogRecord { +func auditRootCurrentLog(operator, merkleRoot string, rootIndex string, entryCount uint64, block uint64, logIndex uint64) EVMLogRecord { return EVMLogRecord{ Address: CredentialAuditContractAddress, Topics: []string{AuditRootAppendedCurrentTopic, operator, merkleRoot}, - Data: "0x" + strings.TrimPrefix(opKindBitmap, "0x") + fmt.Sprintf("%064x", entryCount), + Data: "0x" + strings.TrimPrefix(rootIndex, "0x") + fmt.Sprintf("%064x", entryCount), BlockNumber: fmt.Sprintf("0x%x", block), BlockHash: "0x" + strings.Repeat("77", 32), Timestamp: "0x65f00000", diff --git a/internal/server/http/agentkeys.go b/internal/server/http/agentkeys.go index 9dc58e9..e40839a 100644 --- a/internal/server/http/agentkeys.go +++ b/internal/server/http/agentkeys.go @@ -153,12 +153,6 @@ func agentkeysAuditRootHandle(c *gin.Context) { c.JSON(http.StatusBadGateway, gin.H{"error": "root logIndex: " + err.Error()}) return } - opKindTopics, err := agentkeys.OpKindTopicsFromBitmap(rootEvent.OpKindBitmapU256) - if err != nil { - c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) - return - } - leafLogs := []evmdao.EtherscanLogsRes{} if rootEvent.EntryCount > 0 && strings.EqualFold(rootEvent.EventTopic, agentkeys.AuditRootAppendedCurrentTopic) { leafLogs = agentkeysEvmAPI.API_GetLogsForAgentKeys(c.Request.Context(), "block_num desc, `index` desc", int(rootEvent.EntryCount), @@ -167,14 +161,21 @@ func agentkeysAuditRootHandle(c *gin.Context) { model.Where("topic1 = ?", rootEvent.OperatorOmni), model.Where("(block_num < ? OR (block_num = ? AND `index` < ?))", rootBlock, rootBlock, rootLogIndex), ) - } else if rootEvent.EntryCount > 0 && len(opKindTopics) > 0 { - leafLogs = agentkeysEvmAPI.API_GetLogsForAgentKeys(c.Request.Context(), "block_num desc, `index` desc", int(rootEvent.EntryCount), - model.Where("address = ?", agentkeysAuditContractAddress()), - model.Where("method_hash = ?", agentkeys.AuditAppendedV2Topic), - model.Where("topic1 = ?", rootEvent.OperatorOmni), - model.Where("topic3 in ?", opKindTopics), - model.Where("(block_num < ? OR (block_num = ? AND `index` < ?))", rootBlock, rootBlock, rootLogIndex), - ) + } else if rootEvent.EntryCount > 0 { + opKindTopics, err := agentkeys.OpKindTopicsFromBitmap(rootEvent.OpKindBitmapU256) + if err != nil { + c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) + return + } + if len(opKindTopics) > 0 { + leafLogs = agentkeysEvmAPI.API_GetLogsForAgentKeys(c.Request.Context(), "block_num desc, `index` desc", int(rootEvent.EntryCount), + model.Where("address = ?", agentkeysAuditContractAddress()), + model.Where("method_hash = ?", agentkeys.AuditAppendedV2Topic), + model.Where("topic1 = ?", rootEvent.OperatorOmni), + model.Where("topic3 in ?", opKindTopics), + model.Where("(block_num < ? OR (block_num = ? AND `index` < ?))", rootBlock, rootBlock, rootLogIndex), + ) + } } rows, err := agentkeys.DecodeAuditRootRows(c.Request.Context(), rootRecord, toAgentKeysLogs(leafLogs), agentkeysAuditWorkerURL(), agentkeysEnvelopeCache)