# L0 — 行为记录 PoB

## 解决什么问题

每个 AI Agent 每天产生 **数千条 BehaviorEvent**（用户 query / 模型推理 / 工具调用 / 状态转移）。如果每条 event 都直接发 tx，链根本扛不住。AxBlade L0 的设计：

> **Agent 把多条 BehaviorEvent 在链下打包成 Merkle tree，aggregator 上链 batch header（含 root + 元数据），任何人都可在 7 天内通过 Merkle 反证质疑某条 event 不一致。**

这把 **N 条 event 压缩成 1 笔 tx**，同时仍保留 per-event 的可验证性。

## 核心数据结构

### BehaviorEvent (链下结构)

```rust
// baby-modules/behavior-spec/src/event.rs
struct BehaviorEvent {
    agent_id: [u8; 32], // keccak256(agent DID)
    action_id: [u8; 32], // keccak256(action type, e.g. "decision.medical.triage")
    spec_hash: [u8; 32], // 引用的 BehaviorSpec
    input_hash: [u8; 32], // hash of user input (privacy-preserving)
    output_hash: [u8; 32], // hash of agent output
    timestamp: u64,
    outcome: Outcome, // enum: Allowed / Denied / Pending
}
```

### Merkle Leaf 编码

每个 leaf 的 keccak input 是 **跨语言 byte-pin** 的（Rust / Sol / TS 三栈一致）：

```solidity
// 在 contracts/src/pob/PoBRegistry.sol
bytes32 leaf = keccak256(abi.encode(
    event.agentId,
    event.actionId,
    event.specHash,
    event.inputHash,
    event.outputHash,
    event.timestamp,
    uint8(event.outcome)
));
```

**Cross-language byte-pin** 通过 12 个测试硬编码 keccak vector 维护：任何一端改了 encoding，另外两端编译期 fail。详见 [测试覆盖率](/consolidated-resources/test-coverage.md)。

### Batch Header (链上结构)

```solidity
// IPoBRegistry.sol
struct Batch {
    bytes32 merkleRoot; // BehaviorEvent merkle root
    address submitter; // aggregator 地址 (msg.sender)
    uint64 timestamp; // 链上 block.timestamp
    uint32 leafCount; // batch 内 event 数
    bytes32 specHash; // 整个 batch 引用的主 BehaviorSpec
    bytes32 agentId; // 整个 batch 的 agent (single-agent batches)
    bool zkVerified; // 是否提交了 ZK proof
}
```

## 主流程

### 1. Agent 端：生成 BehaviorEvent

```typescript
// agent runtime (TypeScript SDK)
const event: BehaviorEvent = {
    agentId: keccak256(toBytes(agentDID)),
    actionId: keccak256(toBytes("decision.medical.triage")),
    specHash: behaviorSpec.hash,
    inputHash: keccak256(userQuery),
    outputHash: keccak256(modelOutput),
    timestamp: Date.now() / 1000n,
    outcome: Outcome.Allowed,
};
agentRuntime.bufferedEvents.push(event);
```

### 2. Aggregator 端：构建 Merkle batch

```rust
// baby-pob-aggregator/src/aggregator.rs
let leaves: Vec<[u8; 32]> = events.iter().map(|e| e.encode_leaf()).collect();
let root = positional_merkle_root(&leaves); // sort + dedup + keccak

let header = BatchHeader {
    merkle_root: root,
    submitter: aggregator_address,
    timestamp: block_timestamp,
    leaf_count: leaves.len() as u32,
    spec_hash: events[0].spec_hash,
    agent_id: events[0].agent_id,
    zk_verified: false, // PoC: ZK 提交在后续 batch
};
```

### 3. Aggregator 上链

```rust
let tx = pob_registry
    .submitBatch(header, signature)
    .send()
    .await?;
```

```solidity
// PoBRegistry.sol
function submitBatch(Batch calldata b, bytes calldata sig) external {
    // 验证 aggregator 已抵押 ≥ MIN_STAKE
    require(stakeManager.activeStake(msg.sender) >= MIN_STAKE, "InsufficientStake");
    
    // 验证 sig 由 aggregator 签
    bytes32 hash = keccak256(abi.encode(b));
    require(_recoverSigner(hash, sig) == msg.sender, "InvalidSignature");
    
    // 写入 storage
    uint256 batchId = _batches.length;
    _batches.push(b);
    emit BatchSubmitted(batchId, b);
}
```

### 4. Challenger 端：监听 + 反证

`baby-pob-challenger` Rust daemon 持续轮询 PoBRegistry.getBatchCount()，对每个新 batch:

```rust
// baby-pob-challenger/src/daemon.rs
loop {
    let batch_count = registry.getBatchCount().await?;
    for id in cursor..batch_count {
        let batch = registry.getBatch(id).await?;
        match detector.detect_fraud(batch).await? {
            DetectionResult::NoFraud => continue,
            DetectionResult::Fraud(claim) => {
                opener.open_challenge(claim).await?;
            }
        }
    }
    cursor = batch_count;
    sleep(POLL_INTERVAL).await;
}
```

如果 detector 发现 **batch header 引用的 agentId 与某 leaf 内 agentId 不一致**，opener 调 `ChallengeManager.open(...)`：

```solidity
// ChallengeManager.sol
function open(
    uint256 batchId,
    uint32 leafIndex,
    BehaviorEventView calldata event_,
    bytes32[] calldata merkleProof
) external payable {
    require(msg.value == BOND_AMOUNT, "InvalidBond"); // 0.1 ETH
    
    // 验证 Merkle proof 把 event 锚定到 batch.merkleRoot
    bytes32 leaf = encodeLeaf(event_);
    require(
        PositionalMerkleProof.verify(batch.merkleRoot, leaf, leafIndex, merkleProof),
        "InvalidProof"
    );
    
    // 验证 fraud type: header.agentId ≠ event.agentId
    require(batch.agentId != event_.agentId, "NoFraud");
    
    _challenges[++_nextId] = Challenge({
        accusedAggregator: batch.submitter,
        challenger: msg.sender,
        bondAmount: BOND_AMOUNT,
        openedAt: uint64(block.timestamp),
        status: Status.Pending,
    });
    emit ChallengeOpened(_nextId, batchId, leafIndex, event_);
}
```

### 5. Resolution: aggregator 7 天内 respond，或 timeout slash

```solidity
function resolveExpired(uint256 challengeId) external {
    require(block.timestamp > c.openedAt + CHALLENGE_WINDOW, "NotExpired");
    
    // CEI: Effects 先 (commit 7a1c8b5 修复)
    uint256 active = stakeManager.activeStake(c.accusedAggregator);
    c.status = Status.ResolvedFraud;
    c.resolvedAt = uint64(block.timestamp);
    _slashedAmount[challengeId] = active;
    emit ChallengeResolved(challengeId, true, c.accusedAggregator, active);
    
    // Interaction 后
    if (active > 0) {
        stakeManager.slash(c.accusedAggregator, active);
    }
}
```

## 经济安全

| 参数                        | PoC 值             | 主网建议              |
| ------------------------- | ----------------- | ----------------- |
| MIN\_STAKE                | 0.5 ETH           | 1-10 M USD 等价     |
| BOND\_AMOUNT (challenger) | 0.1 ETH           | 0.5-5% MIN\_STAKE |
| CHALLENGE\_WINDOW         | 7 days            | 7-30 days         |
| CHALLENGER\_REWARD\_BPS   | 5000 (50%)        | 30-50%            |
| SLASH\_TARGET             | full active stake | full active stake |

经济模型分析见 [Slash 经济学](/algorithms/slash-economics.md)。

## 链上事实 (chain 391, 已验证)

| 事件                                                             | tx                           |
| -------------------------------------------------------------- | ---------------------------- |
| StakeManager 0.5 ETH deposit                                   | aggregator 0x70997970...79c8 |
| submitBatch (fraud type: agentId mismatch)                     | batch\_id=0                  |
| ChallengeManager.open (challenger 0x3C44...4293, 0.1 ETH bond) | challenge\_id=1              |
| evm\_increaseTime 7d+1s (绕过 window for demo)                   | —                            |
| resolveExpired → slash 0.5 ETH                                 | status=ResolvedFraud         |
| claim → bond return + reward                                   | status=Claimed               |

**承诺**：以上流程在 chain 391 真链已 e2e 跑通（commit `023f7aa`），不是 mock。

## 已知限制 (PoC stage)

1. **单 aggregator 单 batch** — 多 aggregator 并行 batch 时需要 nonce 协调
2. **0.5 ETH stake** — 远低于真实经济攻击成本（攻击者付 < $1500 即可作恶）
3. **Challenger daemon centralized** — 当前是项目方运行，主网需 permissionless challenger
4. **MerkleProof 仅支持 PositionalMerkleProof（左右位置编码）** — 不支持 sparse merkle，限制 leaf count

## 相关：阅读

* [Fraud-Proof 机制](/protocol-mechanics/fraud-proof-mechanism.md) — ChallengeManager 详细流程
* [Slash 经济学](/algorithms/slash-economics.md) — 抵押金校准 + 博弈论
* [Positional Merkle Tree](/algorithms/merkle-tree-positional.md) — 数据结构 + 验证算法
* [Examples → Submit Batch](/integration-jie-ru-fang-shi/examples/submit-batch.md) — 端到端 SDK 示例


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yellowpaper.axblade.io/protocol-mechanics/pob-behavior-recording.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
