Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 193 additions & 0 deletions chainhook/PAYLOAD_VALIDATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# Chainhook Payload Validation

## Overview

The chainhook service validates incoming webhook payloads to ensure they contain all required fields before processing. This prevents silent failures and provides clear error messages for malformed or incomplete payloads.

## Validation Rules

### Payload Structure

The root payload must:
- Be a valid object
- Contain an `apply` field that is an array

**Error**: `invalid payload structure: payload.apply must be an array`

### Block Structure

Each block in the `apply` array must:
- Be a valid object
- Contain a `block_identifier` object
- Have a `block_identifier.index` number field

**Error**: `invalid block structure: block at index N missing block_identifier`

### Transaction Structure

Each transaction in a block must:
- Be a valid object
- Contain a `transaction_identifier` object
- Have a `transaction_identifier.hash` string field

**Error**: `invalid transaction structure: transaction at block N, tx M missing transaction_identifier`

### Event Validation

Events without a `value` field are logged as warnings but do not cause the request to fail:

**Warning Log**: `Event missing value field`

## Error Responses

When validation fails, the service returns:

```json
{
"error": "bad_request",
"message": "invalid payload structure: payload.apply must be an array",
"request_id": "..."
}
```

**Status Code**: 400 Bad Request

## Valid Payload Example

```json
{
"apply": [
{
"block_identifier": {
"index": 100,
"hash": "0xblock"
},
"timestamp": 1700000000000,
"transactions": [
{
"transaction_identifier": {
"hash": "0xtx"
},
"metadata": {
"receipt": {
"events": [
{
"type": "SmartContractEvent",
"data": {
"contract_identifier": "SP123.contract",
"value": {
"event": "tip-sent"
}
}
}
]
}
}
}
]
}
]
}
```

## Benefits

1. **Early Detection**: Catches malformed payloads before processing
2. **Clear Errors**: Provides specific error messages with context
3. **Operator Visibility**: Logs warnings for incomplete events
4. **Debugging**: Includes block and transaction indices in error messages
5. **Reliability**: Prevents silent failures from missing fields

## Testing

Validation is tested at multiple levels:

- Unit tests for each validation function
- Integration tests for malformed payloads
- Edge cases for missing fields
- Success cases for valid payloads

Run tests with:
```bash
npm test
```


## Common Validation Errors

### Missing apply Field

**Request**:
```json
{
"invalid": "payload"
}
```

**Response**:
```json
{
"error": "bad_request",
"message": "invalid payload structure: payload.apply must be an array",
"request_id": "abc-123"
}
```

### Missing block_identifier

**Request**:
```json
{
"apply": [
{
"transactions": []
}
]
}
```

**Response**:
```json
{
"error": "bad_request",
"message": "invalid block structure: block at index 0 missing block_identifier",
"request_id": "def-456"
}
```

### Missing transaction_identifier

**Request**:
```json
{
"apply": [
{
"block_identifier": { "index": 100 },
"transactions": [
{
"metadata": {}
}
]
}
]
}
```

**Response**:
```json
{
"error": "bad_request",
"message": "invalid transaction structure: transaction at block 0, tx 0 missing transaction_identifier",
"request_id": "ghi-789"
}
```

## Monitoring

Operators should monitor for:

- 400 Bad Request responses indicating malformed payloads
- Warning logs for events missing value fields
- Validation error patterns in logs

These signals indicate issues with the chainhook configuration or upstream data quality.
14 changes: 14 additions & 0 deletions chainhook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Webhook listener for TipStream on-chain events from the Stacks blockchain.
- Metrics and health endpoints
- Configurable connection pooling
- Graceful shutdown with request rejection
- Payload validation with detailed error messages

## Configuration

Expand Down Expand Up @@ -104,3 +105,16 @@ During shutdown, the service returns:
With HTTP headers:
- Status: 503 Service Unavailable
- Retry-After: 30 seconds


## Payload Validation

The service validates incoming chainhook payloads to ensure they contain all required fields:

- Payload must have an `apply` array
- Each block must have a `block_identifier` with `index`
- Each transaction must have a `transaction_identifier` with `hash`

Malformed payloads are rejected with 400 Bad Request and a detailed error message.

See [PAYLOAD_VALIDATION.md](./PAYLOAD_VALIDATION.md) for complete validation rules and examples.
153 changes: 153 additions & 0 deletions chainhook/VALIDATION_CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Payload Validation Changes

## Summary

Added comprehensive validation for chainhook webhook payloads to detect and reject malformed or incomplete data with clear error messages, addressing issue #348.

## Problem

The chainhook service was silently dropping malformed payloads and partial event objects without surfacing useful warnings or rejections. Operators had no clear signal when webhook payloads were malformed or incomplete, making debugging difficult.

## Solution

Implemented explicit validation for the required event envelope structure with detailed error messages and logging for incomplete events.

## Changes Made

### Core Implementation

1. **validatePayloadStructure()**
- Validates payload is an object
- Ensures `apply` field exists and is an array
- Returns validation result with reason

2. **validateBlock()**
- Validates block structure
- Ensures `block_identifier` exists
- Validates `block_identifier.index` is a number
- Includes block index in error messages

3. **validateTransaction()**
- Validates transaction structure
- Ensures `transaction_identifier` exists
- Validates `transaction_identifier.hash` exists
- Includes block and transaction indices in error messages

4. **extractEvents() Updates**
- Calls validation functions before processing
- Throws BadRequestError for invalid structures
- Logs warnings for events missing value fields
- Provides context in error messages

### Testing

5. **validation-payload.test.js** (new)
- Unit tests for validatePayloadStructure
- Unit tests for validateBlock
- Unit tests for validateTransaction
- Tests for null, invalid types, and missing fields
- Tests for valid structures

6. **server.integration.test.js Updates**
- Test for missing apply field
- Test for missing block_identifier
- Test for missing transaction_identifier
- Test for empty apply array (success case)

7. **server.test.js Updates**
- Updated extractEvents test for validation behavior
- Changed empty payload test to expect error

### Documentation

8. **PAYLOAD_VALIDATION.md** (new)
- Complete validation rules
- Error response formats
- Valid payload examples
- Common validation errors
- Monitoring recommendations

9. **README.md Updates**
- Added payload validation to features
- Added validation section with overview
- Link to detailed validation documentation

10. **package.json Update**
- Updated description to include validation

## Acceptance Criteria

- [x] Validate the required event envelope explicitly
- [x] Log or reject malformed payloads with useful context
- [x] Add integration coverage for missing fields

## Validation Rules

### Required Fields

**Payload Level:**
- `apply` (array)

**Block Level:**
- `block_identifier` (object)
- `block_identifier.index` (number)

**Transaction Level:**
- `transaction_identifier` (object)
- `transaction_identifier.hash` (string)

### Error Handling

**Validation Failures**: Return 400 Bad Request with detailed message

**Missing Event Values**: Log warning but continue processing

## Test Results

- Total tests: 140 (increased from 121)
- All tests passing
- Added 19 new tests for validation
- Updated 1 existing test

## Error Response Format

```json
{
"error": "bad_request",
"message": "invalid payload structure: payload.apply must be an array",
"request_id": "unique-id"
}
```

## Benefits

1. **Early Detection**: Catches malformed payloads immediately
2. **Clear Errors**: Specific error messages with context
3. **Operator Visibility**: Warnings logged for incomplete events
4. **Better Debugging**: Includes indices in error messages
5. **Prevents Silent Failures**: No more dropped events without notice
6. **Improved Reliability**: Ensures data quality before processing

## Backward Compatibility

This change is backward compatible for valid payloads. Only malformed payloads that were previously silently dropped will now receive explicit error responses.

## Monitoring

Operators should monitor:
- 400 Bad Request rate for validation failures
- Warning logs for events missing value fields
- Error message patterns to identify upstream issues

## Example Scenarios

### Before
- Malformed payload → Silent failure, no events indexed
- Missing fields → Events silently skipped
- No operator visibility

### After
- Malformed payload → 400 error with specific reason
- Missing fields → 400 error with field location
- Events without values → Warning logged with context
- Clear operator visibility and debugging information
2 changes: 1 addition & 1 deletion chainhook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"engines": {
"node": ">=18"
},
"description": "Chainhook webhook listener for TipStream on-chain events with configurable PostgreSQL connection pooling and graceful shutdown",
"description": "Chainhook webhook listener for TipStream on-chain events with configurable PostgreSQL connection pooling, graceful shutdown, and payload validation",
"dependencies": {
"pg": "^8.20.0"
}
Expand Down
Loading
Loading