DChain single-node blockchain + React Native messenger client. Core: - PBFT consensus with multi-sig validator admission + equivocation slashing - BadgerDB + schema migration scaffold (CurrentSchemaVersion=0) - libp2p gossipsub (tx/v1, blocks/v1, relay/v1, version/v1) - Native Go contracts (username_registry) alongside WASM (wazero) - WebSocket gateway with topic-based fanout + Ed25519-nonce auth - Relay mailbox with NaCl envelope encryption (X25519 + Ed25519) - Prometheus /metrics, per-IP rate limit, body-size cap Deployment: - Single-node compose (deploy/single/) with Caddy TLS + optional Prometheus - 3-node dev compose (docker-compose.yml) with mocked internet topology - 3-validator prod compose (deploy/prod/) for federation - Auto-update from Gitea via /api/update-check + systemd timer - Build-time version injection (ldflags → node --version) - UI / Swagger toggle flags (DCHAIN_DISABLE_UI, DCHAIN_DISABLE_SWAGGER) Client (client-app/): - Expo / React Native / NativeWind - E2E NaCl encryption, typing indicator, contact requests - Auto-discovery of canonical contracts, chain_id aware, WS reconnect on node switch Documentation: - README.md, CHANGELOG.md, CONTEXT.md - deploy/single/README.md with 6 operator scenarios - deploy/UPDATE_STRATEGY.md with 4-layer forward-compat design - docs/contracts/*.md per contract
5.2 KiB
Межконтрактные вызовы
DChain поддерживает вызовы одного контракта из другого через host-функцию call_contract. Все вызовы в цепочке исполняются в рамках одной транзакции и одного badger.Txn.
Синтаксис (TinyGo SDK)
import dc "go-blockchain/contracts/sdk"
// Вызвать метод с аргументами
ok := dc.CallContract(contractID, "method_name", `["arg1","arg2"]`)
// Без аргументов
ok := dc.CallContract(contractID, "get_price", "[]")
// С числовым аргументом
ok := dc.CallContract(contractID, "increment", `[42]`)
contractID — 16-символьный hex-ID контракта (как в Explorer).
Как это работает
tx CALL_CONTRACT → Contract A
│
│ call_contract(B, "method", args)
▼
Contract B
│
│ call_contract(C, "method", args)
▼
Contract C
│
└── [возврат gasUsed]
│
[gasUsed заряжается на B]
[возврат gasUsed]
│
[gasUsed заряжается на A]
Атомарность
Все изменения состояния в цепочке вызовов используют одну badger.Txn. Если родительский вызов завершился ошибкой, изменения всех подвызовов откатываются вместе с ним. Если подвызов вернул 1 (ошибку), родитель должен сам решить: паниковать или продолжать.
Gas
Gas-бюджет подвызова = gc.Remaining() родителя (оставшийся gas на момент вызова). После возврата, gasUsed подвызова списывается с родительского счётчика. Таким образом, весь gas всей цепочки вычитается из единственного --gas лимита транзакции.
Caller
В подвызове dc.Caller() возвращает contractID вызывающего контракта (не исходного пользователя).
user → Contract A → Contract B
dc.Caller() == Contract A's ID
Глубина
Максимальная глубина вложенности: 8.
A → B → C → ... → H (8 уровней) — допустимо.
Попытка вызвать 9-й уровень вернёт ошибку, транзакция откатится.
Пример: price oracle
// contracts/mycontract/main.go
package main
import dc "go-blockchain/contracts/sdk"
const priceOracleID = "abcd1234abcd1234" // ID oracle-контракта
//export buy
func buy() {
amount := dc.ArgU64(0)
// Узнать цену у другого контракта
if !dc.CallContract(priceOracleID, "get_price", "[]") {
dc.Log("oracle unavailable")
return
}
// После вызова цена может быть записана в shared state под известным ключом,
// или oracle мог сделать transfer напрямую.
dc.Log("buy executed")
}
func main() {}
Пример: ping (hello_go)
//export ping
func ping() {
target := dc.ArgStr(0, 64)
if target == "" {
dc.Log("no target")
return
}
ok := dc.CallContract(target, "get", "[]")
if ok {
dc.Log("ping ok")
} else {
dc.Log("ping failed")
}
}
Ограничения
| Параметр | Значение |
|---|---|
| Максимальная глубина | 8 |
| Разделяемый state | одна badger.Txn |
| Caller в подвызове | contractID родителя |
| Gas бюджет подвызова | Remaining() родителя |
| Возврат данных | только через state (нет return value) |
Нет возврата значений.
call_contractвозвращает только 0/1 (успех/ошибка). Для передачи данных из подвызова обратно: запишите в state под известным ключом или используйте промежуточный общий контракт.
Безопасность
- Не доверяйте
dc.Caller()из подвызванного контракта — он будет contractID инициатора, который может быть любым задеплоенным контрактом. - Проверяйте contractID перед вызовом, если вам важно с кем вы говорите.
- Помните что подвызов может изменять общий state — не вызывайте непроверенные контракты.