Files
dchain/docs/development/inter-contract.md
vsecoder 7e7393e4f8 chore: initial commit for v0.0.1
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
2026-04-17 14:16:44 +03:00

5.2 KiB
Raw Permalink Blame History

Межконтрактные вызовы

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 — не вызывайте непроверенные контракты.