TransferChain Secure Simple Storage Service SDK
230
TransferChain SDK Secure Simple Storage Service
Image: transferchain/transferchain-sdk:2.0.0
A local HTTP API in front of the TransferChain Go SDK: account, storage and
log collect endpoints, a local bbolt database (<dataPath>/sdk.db) and a
syncher that follows the blockchain for the account's addresses.
Architecture note: the API has no authentication and answers CORS
with Access-Control-Allow-Origin: *. Run it on a local or trusted network
next to the application that uses it. Protecting the endpoint (network
isolation, reverse proxy, firewall) is the operator's responsibility.
Important
**The container runs as non-root user UID 1001 (group 0).**
Existing installations: before upgrading, give UID 1001 ownership of every mounted data, log and config volume, otherwise the SDK cannot open its database, logs or config file.
Upload/download paths: files used by upload requests and download targets must be under
allowedpaths(default<dataPath>/files, i.e./var/data/transferchain/files). Create this directory in the data volume and keep it owned by1001:0. Paths outsideallowedpaths(e.g./root/files) are rejected.mkdir -p ./data/files chown -R 1001:0 ./data ./log ./env/sdk.yamlUse the host paths you mount to
/var/data/transferchain,/var/log/transferchainand/etc/transferchain/sdk.yaml.
Docker run:
docker run --name transferchain-sdk \
-v ./env/sdk.yaml:/etc/transferchain/sdk.yaml \
-v ./data:/var/data/transferchain \
-v ./log:/var/log/transferchain \
-e LOG_PATH=/var/log/transferchain \
-e PORT=8080 \
-e WITHOUT_LISTEN=false \
-p "8080:8080" \
transferchain/transferchain-sdk:2.0.0
Docker compose:
name: transferchain-sdk
services:
sdk-local:
image: transferchain/transferchain-sdk:2.0.0
environment:
PORT: 8080
WITHOUT_LISTEN: 'false'
LOG_PATH: /var/log/transferchain
volumes:
- ./env/sdk.yaml:/etc/transferchain/sdk.yaml
- ./data:/var/data/transferchain
- ./log:/var/log/transferchain
restart: always
ports:
- '8080:8080'
| Environment | Default | Meaning |
|---|---|---|
CONFIG_FILE | /etc/transferchain/sdk.yaml | config file |
DATA_PATH | /var/data/transferchain | data directory |
LOG_PATH | empty | log directory; when set, logs go to <LOG_PATH>/sdk.log, otherwise to stdout |
PORT | 8080 | HTTP port |
WITHOUT_LISTEN | false | true disables blockchain listening |
| Volume | Content | Persistent |
|---|---|---|
/etc/transferchain/sdk.yaml | config; the SDK writes account.mnemonics and account.ns into it (mode 0600) | yes |
/var/data/transferchain | sdk.db, files/ (default allowedpaths root), collect/ (log collect WAL) | yes |
/var/log/transferchain | sdk.log | recommended |
The image only starts in prod mode (default). Keep collect/ on the
persistent data volume: entries not uploaded yet live there.
Example sdk.yaml:
version: v1
behaviour: "standard" # or log-collect
# TransferChain authentication and authorization information.
account:
api:
token:
secret:
url: https://connect.transferchain.io
proxy: # this option is optional
type: "http" # http|socks5|socks5h
host:
port:
identifier: 0
mnemonics: # written by init/restore; not returned in responses
ns:
uuid:
# TransferChain file operation's (storage upload, download, delete) Server URL.
fileoperation:
host: https://doperation.transferchain.io
fingerprint:
insecure: false
downloadurl:
proxy: # this option is optional
type: "http" # http|socks5|socks5h
host:
port:
limits:
bodysize: 0 # in bytes, the default is 0, so it's 5368709120(5gb)
timeout: 0 # in seconds, the default is 0, so it's 1800(30minutes)
# Logger configurations - required in log-collect mode
logger:
sizeThreshold: 1048576 # in bytes (min 1048576); no longer used
prefix: "sdk-" # no longer used
wal: # all optional, defaults shown
flushBytes: 1048576
flushInterval: 200ms
checkpointBytes: 67108864
checkpointInterval: 5m
maxBytes: 1073741824
# TransferChain Blockchain information.
readnode:
address: https://read-node-01.transferchain.io
chain:
name: medusa # constant
version: v2 # constant
wsaddress: wss://read-node-01.transferchain.io/ws
fingerprint:
insecure: false
proxy: # this option is optional
type: "http" # http|socks5|socks5h
host:
port:
# APIs verbose mode. if open showed all log events.
log:
verbose: false
syslog: false
beauty: false
format: "json" # or 'text'
listen: true
# Upload "files" absolute paths and download "path" must be under these
# directories (otherwise 403). Default: <dataPath>/files
allowedpaths:
- /var/data/transferchain/files
# If true and an account exists, forced init and forced restore return 403.
disableforceinit: false
| Key | Default | Description |
|---|---|---|
version | required | must be v1 |
behaviour | standard | log-collect enables the /v1/log endpoints and requires logger |
account.* | — | account API (url, token, secret, proxy); mnemonics and ns are written by the SDK |
fileoperation.* | — | file operation server; chunksize when set is at least 262144 |
readnode.* | — | blockchain read node |
limits.bodysize | 0 → 5 GiB | maximum request body in bytes |
limits.timeout | 0 → 30 min | HTTP read/write timeout in seconds |
allowedpaths | [<dataPath>/files] | allowed directories for upload file paths and download targets; others get 403 |
disableforceinit | false | true rejects forced init/restore over an existing account with 403 |
log.* | false / json | log output (verbose, syslog, beauty, format, backoff) |
logger.sizeThreshold, logger.prefix | — | still accepted, no longer used (warning at startup) |
logger.wal.flushBytes | 1048576 (1 MiB) | group commit size: many requests share one fsync; best disk throughput in the local benchmark (~110 MB/s), 16 MiB was never filled (~5 MB/s) |
logger.wal.flushInterval | 200ms | upper bound for the fsync wait of a 201; an idle writer fsyncs at once |
logger.wal.checkpointBytes | 67108864 (64 MiB) | size of one uploaded segment and the most a checkpoint reads into memory; at least 10 MB |
logger.wal.checkpointInterval | 5m | longest time a durable entry stays only on local disk under low traffic; also the "upload is lagging" warning threshold |
logger.wal.maxBytes | 1073741824 (1 GiB) | disk budget for unuploaded data; when full, new requests get 507 instead of data being dropped |
Every JSON answer has success, message and, when present, data,
total_count, errors and detail. Replace placeholders such as <txID>
and <word1>; never put real mnemonics into commands.
Init: creates the cloud account for the first time, or re-creates it with
force. On success the mnemonics are written to the config file; they are
not returned. Without force over an existing account: 400; with
disableforceinit: true, force over an existing account: 403.
curl -X POST http://localhost:8080/v1/account/init \
-H "Content-Type: application/json" \
-d '{"force": false}'
{
"success": true,
"message": "Successfully created new cloud account and new mnemonics wrote on config"
}
Restore: without force over an existing account 400, forced restore with
disableforceinit: true 403, invalid mnemonics 400; none of them delete
anything. A valid forced restore deletes the previous account's local
records first. The mnemonics are not returned.
curl -X POST http://localhost:8080/v1/account/restore \
-H "Content-Type: application/json" \
-d '{"mnemonics": ["<word1>", "<word2>", "<...>"], "force": false}'
Info (403 when no account): address, ns, user_id, user_uuid,
wallet_id, wallet_uuid, email.
curl http://localhost:8080/v1/account/info
Status: buildstamp, database, syncher, account,
synced_last_block, usage counters.
curl http://localhost:8080/v1/status
Upload with a JSON body (application/json or no Content-Type). Entries
in files that are absolute paths are read from disk and must be under
allowedpaths (otherwise 403); other entries are uploaded as content.
Optional fields: recipients, delete_after, url_flag, notes (at most
500 characters), download_limit (at most 300).
curl -X POST http://localhost:8080/v1/storage/upload \
-H "Content-Type: application/json" \
-d '{"files": ["raw file data", "/var/data/transferchain/files/file1.zip"]}'
Upload raw content of one file (application/octet-stream); the file name
is the name query parameter, the other fields are optional query
parameters. Any other Content-Type gets 415.
curl -X POST "http://localhost:8080/v1/storage/upload?name=report.pdf" \
-H "Content-Type: application/octet-stream" \
--data-binary @report.pdf
Upload response (201):
{
"data": {
"transactions": [
{
"id": "<txID>",
"version": 2,
"type": "storage",
"sender_addr": "<your random address>",
"recipient_addr": "<your random address>",
"data": "<base encoded, encrypted data>",
"sign": "<base encoded, sha512 hashed sender signed data>",
"fee": 0
}
],
"storages": [
{
"UUID": "<string>",
"FileName": "<uuid_v4>",
"Size": 97,
"Slots": [
{
"base_uuid": "<string>",
"uuid": "<string>",
"storage_service": "<provider title>",
"storage_code": "<provider code>",
"size": 0,
"size_rl": 0,
"user_id": 0,
"chunk_size": 0
}
],
"KeyAES": "<base64 encoded random bytes>",
"KeyHMAC": "<base64 encoded random bytes>",
"UploadDate": "RFC3339 TIMESTAMP",
"Policy": {
"ID": "<string>",
"Identifier": "<string>",
"Owner": "<your random address>",
"SourceIdentifier": "<your random address>",
"TargetIdentifier": "<your random address>",
"OPCode": "storage",
"Policy": "owner",
"EventType": "create",
"CreatedAt": "RFC3339 TIMESTAMP"
}
}
],
"mapped_storages": {
"<txID>": {
"UUID": "<string>",
"FileName": "<uuid_v4>",
"Size": 97,
"Slots": [
{
"base_uuid": "<string>",
"uuid": "<string>",
"storage_service": "<provider title>",
"storage_code": "<provider code>",
"size": 0,
"size_rl": 0,
"user_id": 0,
"chunk_size": 0
}
],
"KeyAES": "<base64 encoded random bytes>",
"KeyHMAC": "<base64 encoded random bytes>",
"UploadDate": "RFC3339 TIMESTAMP",
"Policy": {
"ID": "<string>",
"Identifier": "<string>",
"Owner": "<your random address>",
"SourceIdentifier": "<your random address>",
"TargetIdentifier": "<your random address>",
"OPCode": "storage",
"Policy": "owner",
"EventType": "create",
"CreatedAt": "RFC3339 TIMESTAMP"
}
}
}
}
}
List (limit: tx count per page, default 40; last_id: last tx id of the
previous page):
curl "http://localhost:8080/v1/storage/?limit=40&last_id=<txID>"
{
"data": {
"transactions": [
{
"id": "<txID>",
"version": 2,
"type": "storage",
"sender_addr": "<your random address>",
"recipient_addr": "<your random address>",
"data": "<base encoded, encrypted data>",
"sign": "<base encoded, sha512 hashed sender signed data>",
"fee": 0
}
]
}
}
Get one record (from the local database, otherwise from the chain; a chain
record is accepted only if sent from the account's own addresses, else
403; unknown 404):
curl http://localhost:8080/v1/storage/<txID>
Download: with path the file is written to that directory (must be under
allowedpaths, else 403) and the answer is JSON; without path the
answer is the file content.
curl -X POST http://localhost:8080/v1/storage/download/<txID> \
-H "Content-Type: application/json" \
-d '{"path": "/var/data/transferchain/files"}'
curl -X POST http://localhost:8080/v1/storage/download/<txID> -o report.pdf
Delete (also DELETE /v1/storage/deletion):
curl -X DELETE http://localhost:8080/v1/storage/ \
-H "Content-Type: application/json" \
-d '{"tx_id": ["<txID_1>", "<txID_2>"]}'
Only with behaviour: log-collect. The body format is selected by
Content-Type (parameters such as ; charset=utf-8 are ignored):
Content-Type | Body | WAL entry |
|---|---|---|
application/json or no header | {"data": "<one log entry>"} | the data string |
application/octet-stream | raw bytes | the body as is: one entry, no JSON parsing or conversion; \n inside the body stays as is |
| anything else | — | 415 Unsupported Media Type |
For both formats an empty body gets 400, a body larger than 10 MB gets
413, and 201 means the entry is durable.
curl -X POST "http://localhost:8080/v1/log/collect" \
-H "Content-Type: application/json" \
-d '{"data": "one log line"}'
curl -X POST "http://localhost:8080/v1/log/collect" \
-H "Content-Type: application/octet-stream" \
--data-binary @app.log
Uploaded log segments: GET /v1/log/, GET /v1/log/<txID> and
POST /v1/log/download/<txID>, with the same paging and path rules as
storage.
/v1/log/collect entries are first written to a write-ahead log,
<dataPath>/collect/wal.log, and then uploaded in segments to TransferChain
storage:
flushBytes is reached) it writes a flush marker and fsyncs. Entries
that arrive during an fsync share the next one (group commit).201.checkpointBytes is reached, every checkpointInterval and after
account init, a checkpoint uploads the unuploaded entries as one segment
named seg-<account>-<seq>.log (one entry per line, as before) and
writes the local records.Guarantees
201 means durable: the entry is written and fsynced before the
answer.checkpointBytes)
uploads exactly the same range.<dataPath>/collect
are uploaded once at startup; each file is deleted only after its local
record is written.Guards
413 (also the largest WAL entry).maxBytes) full or no disk space: 507; entries already
accepted are kept. Other write errors: 500.checkpointBytes.checkpointInterval.wal.log.corrupt-<time>, a file
with an older format version as wal.log.v1-<time>; neither is deleted.log_segments status). If the process
stops after the upload but before the local record, the segment is
uploaded again and a warning is logged (it may appear twice).*: run on a trusted network.limits.bodysize, default 5 GiB) is separate. Log collect
accepts at most 10 MB.Addresses transactions from the chain are opened
with at most 2 MiB input and 16 MiB output.allowedpaths: upload file paths and download targets outside these
directories get 403.0600; created data, log and
collect directories 0750.| Code | When |
|---|---|
200 | successful read (status, info, list, fetch, download, delete) |
201 | account created/restored, upload, log entry stored durably |
204 | CORS preflight (OPTIONS) |
400 | invalid or empty body, validation error, missing account, init/restore without force over an existing account |
403 | path outside allowedpaths, forced init/restore disabled, foreign chain record, no account for info |
404 | unknown route or record |
405 | method not allowed |
413 | body larger than the limit (log collect: 10 MB) |
415 | unsupported Content-Type (upload, log collect) |
500 | internal error (generic message) |
507 | log collect WAL budget or disk full |
chown -R 1001:0 the mounted
volumes and config (see Quick start).allowedpaths: upload file paths and download targets must be under
these directories (default <dataPath>/files); otherwise 403.Content-Type: application/json (or no Content-Type) keeps
the previous JSON body; application/octet-stream sends the raw content of
one file with the file name in the name query parameter; any other
Content-Type gets 415.disableforceinit: when true and an account already exists, forced
/v1/account/init and forced /v1/account/restore get 403.account.mnemonics), which is
written with 0600 permissions.201 answer waits for the
fsync (at most flushInterval, ~200 ms) and means the entry is durable;
507 when the WAL budget or disk is full; uploaded log files are named
seg-<account>-<seq>.log; legacy log files are migrated;
logger.sizeThreshold and logger.prefix are no longer used; raw
application/octet-stream bodies are accepted. Keep <dataPath>/collect
on a persistent volume.Content type
Image
Digest
sha256:7c9ba792a…
Size
23.3 MB
Last updated
8 days ago
docker pull transferchain/transferchain-sdk