Skip to content

Commit 69eec3f

Browse files
refactor: reorganize token_chaincode example into examples/token_chaincode directory
- Move token_chaincode.py from root to examples/token_chaincode/main.py - Add async/await implementation with proper error handling - Add comprehensive README with usage examples - Add Dockerfile for CCAAS deployment - Add metadata.json and connection.json for Fabric integration - Follow same structure as ccaas example for consistency - Improve logging and input validation This change improves project organization and follows best practices for Hyperledger Fabric chaincode examples. Signed-off-by: kmilo <kmilo.denis.glez@yandex.com>
1 parent f94575e commit 69eec3f

7 files changed

Lines changed: 399 additions & 41 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Dockerfile for Token Chaincode CCAAS
2+
# Build: docker build -t token-chaincode:latest .
3+
# Run: docker run -e CHAINCODE_ID=token_1.0:sha256:hash -e CHAINCODE_SERVER_ADDRESS=0.0.0.0:9999 -p 9999:9999 token-chaincode:latest
4+
5+
FROM python:3.11-slim
6+
7+
LABEL maintainer="fabric-chaincode-python contributors"
8+
LABEL description="Token Chaincode - Hyperledger Fabric Chaincode Service"
9+
10+
# Set working directory
11+
WORKDIR /app
12+
13+
# Copy the parent fabric-chaincode-python source
14+
COPY ../../pyproject.toml ./
15+
COPY ../../src ./src
16+
COPY ../../fabric_protos_python ./fabric_protos_python
17+
COPY ../../requirements.txt ./
18+
19+
# Copy this example
20+
COPY . ./examples/token_chaincode
21+
22+
# Install dependencies
23+
RUN pip install --no-cache-dir -r requirements.txt
24+
25+
# Set environment variables for CCAAS
26+
ENV PYTHONUNBUFFERED=1
27+
ENV PYTHONPATH=/app
28+
29+
# Run the chaincode server
30+
EXPOSE 9999
31+
CMD ["python", "-u", "examples/token_chaincode/main.py"]

‎examples/token_chaincode/README.md‎

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# Token Chaincode - CCAAS Example
2+
3+
This directory contains a complete example of a Hyperledger Fabric Python chaincode for token management deployed as a Chaincode-as-a-Service (CCAAS).
4+
5+
## Overview
6+
7+
This example implements a simple token system where you can:
8+
- **reset** - Reset token balances for all accounts (equivalent to init)
9+
- **balance** - Query the token balance of an account
10+
- **transfer** - Transfer tokens from one account to another
11+
12+
## Features
13+
14+
- ✅ Async/await chaincode implementation
15+
- ✅ Error handling and validation
16+
- ✅ Logging for debugging
17+
- ✅ Input validation (amount must be numeric)
18+
- ✅ Balance checks before transfers
19+
- ✅ Account existence validation
20+
- ✅ CCAAS deployment ready
21+
- ✅ Docker support
22+
23+
## Prerequisites
24+
25+
- Hyperledger Fabric test network running (e.g., `test-network-nano-bash`)
26+
- Python 3.10+ with `venv`
27+
- Peer CLI (`peer`) configured and in PATH
28+
- The parent `fabric-chaincode-python` repository available
29+
- Docker (optional, for containerized deployment)
30+
31+
## Quick Start
32+
33+
### 1. Prepare Python Environment
34+
35+
```bash
36+
cd /path/to/fabric-chaincode-python
37+
python3 -m venv .venv
38+
source .venv/bin/activate
39+
pip install -r requirements.txt
40+
```
41+
42+
### 2. Install Example Dependencies
43+
44+
```bash
45+
cd examples/token_chaincode
46+
pip install -r requirements.txt
47+
```
48+
49+
### 3. Run the Chaincode Server
50+
51+
```bash
52+
export CHAINCODE_ID=token_1.0:sha256:your_package_hash
53+
export CHAINCODE_SERVER_ADDRESS=127.0.0.1:9999
54+
55+
cd examples/token_chaincode
56+
python main.py
57+
```
58+
59+
The chaincode server will start and wait for connections from a Fabric peer.
60+
61+
### 4. Invoke Chaincode Functions
62+
63+
**Initialize/Reset tokens:**
64+
```bash
65+
peer chaincode invoke -C mychannel -n token_1.0 -c '{"function":"reset","Args":[]}'
66+
```
67+
68+
**Query balance:**
69+
```bash
70+
peer chaincode query -C mychannel -n token_1.0 -c '{"function":"balance","Args":["tommy"]}'
71+
```
72+
73+
**Transfer tokens:**
74+
```bash
75+
peer chaincode invoke -C mychannel -n token_1.0 -c '{"function":"transfer","Args":["tommy","jerry","100"]}'
76+
```
77+
78+
**Check new balances:**
79+
```bash
80+
peer chaincode query -C mychannel -n token_1.0 -c '{"function":"balance","Args":["tommy"]}'
81+
peer chaincode query -C mychannel -n token_1.0 -c '{"function":"balance","Args":["jerry"]}'
82+
```
83+
84+
## Docker Deployment
85+
86+
### Build the Docker Image
87+
88+
```bash
89+
docker build -t token-chaincode:latest .
90+
```
91+
92+
### Run in Docker
93+
94+
```bash
95+
docker run -d \
96+
--name token-chaincode \
97+
-e CHAINCODE_ID=token_1.0:sha256:your_hash \
98+
-e CHAINCODE_SERVER_ADDRESS=0.0.0.0:9999 \
99+
-p 9999:9999 \
100+
token-chaincode:latest
101+
```
102+
103+
### Use in Fabric Network
104+
105+
Package and deploy the chaincode to your Fabric network following the [official Hyperledger Fabric documentation](https://hyperledger-fabric.readthedocs.io/en/latest/chaincode4ade.html).
106+
107+
## Project Structure
108+
109+
```
110+
token_chaincode/
111+
├── main.py # Chaincode implementation
112+
├── requirements.txt # Python dependencies
113+
├── Dockerfile # Container image definition
114+
├── README.md # This file
115+
└── metadata.json # Chaincode metadata (optional)
116+
```
117+
118+
## API Reference
119+
120+
### Functions
121+
122+
#### `reset`
123+
Initializes/resets token balances for test accounts.
124+
125+
**Parameters:** None
126+
127+
**Returns:** `success: b'init ok'` or error message
128+
129+
#### `balance`
130+
Queries the token balance of an account.
131+
132+
**Parameters:**
133+
- `account` (string): Account name
134+
135+
**Returns:** `success: b'balance => <amount>'` or error message
136+
137+
#### `transfer`
138+
Transfers tokens from one account to another.
139+
140+
**Parameters:**
141+
- `from` (string): Source account name
142+
- `to` (string): Destination account name
143+
- `amount` (string): Amount to transfer (must be convertible to integer)
144+
145+
**Returns:** `success: b'transfer ok'` or error message
146+
147+
## Error Handling
148+
149+
The chaincode includes comprehensive error handling:
150+
- Account not found errors
151+
- Insufficient balance errors
152+
- Invalid amount errors (non-numeric input)
153+
- Missing argument errors
154+
155+
All errors are logged and returned with descriptive messages.
156+
157+
## Testing
158+
159+
Run the included tests:
160+
161+
```bash
162+
cd /path/to/fabric-chaincode-python
163+
pytest tests/
164+
```
165+
166+
## Logging
167+
168+
The chaincode uses Python's standard logging module with DEBUG level logging.
169+
170+
To adjust logging level, modify the `logging.basicConfig()` call in `main.py`:
171+
172+
```python
173+
logging.basicConfig(level=logging.INFO) # Change to INFO, WARNING, etc.
174+
```
175+
176+
## Related Examples
177+
178+
- [Asset Transfer (CCAAS)](../ccaas/) - More complex asset management example
179+
- [Fabric Chaincode Python Documentation](../../README.md)
180+
181+
## License
182+
183+
This example is licensed under the Apache-2.0 License. See [LICENSE](../../LICENSE) for details.
184+
185+
## Contributing
186+
187+
Contributions are welcome! See [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"address": "token-chaincode:9999"
3+
}

‎examples/token_chaincode/main.py‎

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#!/usr/bin/env python3
2+
# Copyright the fabric-chaincode-python contributors. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
"""
6+
Token Chaincode Example (CCAAS)
7+
8+
This is a simple token transfer chaincode that demonstrates:
9+
- Token initialization (minting)
10+
- Balance queries
11+
- Token transfers between accounts
12+
- State management with the Fabric ledger
13+
14+
Deploy this as a Chaincode-as-a-Service (CCAAS) in Fabric.
15+
"""
16+
17+
import sys
18+
import logging
19+
20+
from src.fabric_shim.interfaces import Chaincode, ChaincodeStubInterface
21+
from src.fabric_shim.server import start
22+
from src.fabric_shim.response import ResponseCode
23+
24+
logger = logging.getLogger(__name__)
25+
logging.basicConfig(level=logging.DEBUG)
26+
27+
28+
class TokenChaincode(Chaincode):
29+
"""Simple token management chaincode"""
30+
31+
async def init(self, stub: ChaincodeStubInterface):
32+
"""
33+
Chaincode initialization - issues initial tokens to accounts
34+
35+
Args:
36+
stub: ChaincodeStubInterface for ledger access
37+
38+
Returns:
39+
Success or error response
40+
"""
41+
logger.info("=== TokenChaincode Init ===")
42+
try:
43+
# Initialize token balances for test accounts
44+
await stub.put_state('tommy', b'1000') # Issue 1000 tokens to tommy
45+
await stub.put_state('jerry', b'1000') # Issue 1000 tokens to jerry
46+
47+
logger.info("Token initialization successful")
48+
return ResponseCode.SUCCESS, b'init ok'
49+
except Exception as e:
50+
logger.error(f"Error during init: {str(e)}")
51+
return ResponseCode.ERROR, str(e).encode()
52+
53+
async def invoke(self, stub: ChaincodeStubInterface):
54+
"""
55+
Chaincode invocation handler - routes to appropriate function
56+
57+
Args:
58+
stub: ChaincodeStubInterface for ledger access
59+
60+
Returns:
61+
Response from the invoked function
62+
"""
63+
logger.info("=== TokenChaincode Invoke ===")
64+
try:
65+
fcn, args = stub.get_function_and_parameters()
66+
logger.info(f"Invoking function: {fcn} with args: {args}")
67+
68+
if fcn == 'reset':
69+
return await self.init(stub)
70+
elif fcn == 'balance':
71+
if len(args) < 1:
72+
return ResponseCode.ERROR, b'balance requires 1 argument: account'
73+
return await self.balance(stub, args[0])
74+
elif fcn == 'transfer':
75+
if len(args) < 3:
76+
return ResponseCode.ERROR, b'transfer requires 3 arguments: from, to, amount'
77+
return await self.transfer(stub, args[0], args[1], args[2])
78+
else:
79+
return ResponseCode.ERROR, f'method {fcn} not supported'.encode()
80+
81+
except Exception as e:
82+
logger.error(f"Error during invoke: {str(e)}")
83+
return ResponseCode.ERROR, str(e).encode()
84+
85+
async def balance(self, stub: ChaincodeStubInterface, account: str):
86+
"""
87+
Query the balance of an account
88+
89+
Args:
90+
stub: ChaincodeStubInterface for ledger access
91+
account: Account name to query
92+
93+
Returns:
94+
Balance information
95+
"""
96+
logger.info(f"Querying balance for account: {account}")
97+
try:
98+
value = await stub.get_state(account)
99+
if not value:
100+
return ResponseCode.ERROR, f'account {account} not found'.encode()
101+
102+
logger.info(f"Balance for {account}: {value.decode()}")
103+
return ResponseCode.SUCCESS, b'balance => ' + value
104+
except Exception as e:
105+
logger.error(f"Error querying balance: {str(e)}")
106+
return ResponseCode.ERROR, str(e).encode()
107+
108+
async def transfer(self, stub: ChaincodeStubInterface, owner: str, to: str, value: str):
109+
"""
110+
Transfer tokens from one account to another
111+
112+
Args:
113+
stub: ChaincodeStubInterface for ledger access
114+
owner: Source account
115+
to: Destination account
116+
value: Amount to transfer
117+
118+
Returns:
119+
Success or error response
120+
"""
121+
logger.info(f"Transferring {value} tokens from {owner} to {to}")
122+
try:
123+
value = int(value)
124+
125+
# Get owner balance
126+
owner_balance_bytes = await stub.get_state(owner)
127+
if not owner_balance_bytes:
128+
return ResponseCode.ERROR, f'account {owner} not found'.encode()
129+
130+
owner_balance = int(owner_balance_bytes.decode())
131+
132+
# Check sufficient balance
133+
if owner_balance < value:
134+
return ResponseCode.ERROR, f'insufficient balance: {owner_balance} < {value}'.encode()
135+
136+
# Get recipient balance
137+
to_balance_bytes = await stub.get_state(to)
138+
if not to_balance_bytes:
139+
return ResponseCode.ERROR, f'account {to} not found'.encode()
140+
141+
to_balance = int(to_balance_bytes.decode())
142+
143+
# Update balances
144+
owner_balance -= value
145+
to_balance += value
146+
147+
# Write updated balances to ledger
148+
await stub.put_state(owner, str(owner_balance).encode())
149+
await stub.put_state(to, str(to_balance).encode())
150+
151+
logger.info(f"Transfer successful: {owner} ({owner_balance}), {to} ({to_balance})")
152+
return ResponseCode.SUCCESS, b'transfer ok'
153+
except ValueError:
154+
return ResponseCode.ERROR, f'invalid amount: {value}'.encode()
155+
except Exception as e:
156+
logger.error(f"Error during transfer: {str(e)}")
157+
return ResponseCode.ERROR, str(e).encode()
158+
159+
160+
if __name__ == '__main__':
161+
# Start the chaincode server
162+
logger.info("Starting TokenChaincode server...")
163+
start(TokenChaincode())
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "ccaas",
3+
"label": "token-chaincode"
4+
}

0 commit comments

Comments
 (0)