Child Chains
Create an application-specific child chain anchored to a parent. Inherited security through merged mining, independent state and throughput.
Why Child Chains?
Lattice's key insight: you don't need one chain for everything. Child chains let you:
- Isolate throughput — your app's traffic doesn't compete with others
- Customize rules — different block times, transaction filters, state limits
- Inherit security — merged mining means your chain benefits from the parent's hashrate
- Transfer value — move assets between parent and child via Merkle proofs
When a miner finds a valid block for the parent chain, that same proof-of-work can simultaneously validate a child chain block. The child chain doesn't need its own dedicated miners — it piggybacks on the parent's security.
Parent blocks reference child block hashes, and child blocks reference their parent anchor. This bidirectional linking creates a verifiable hierarchy.
Step 1: Set Up the Parent Chain
Start with a standard parent node (from Tutorial 1):
import Foundation
import Lattice
import AcornMemoryWorker
let keyPair = CryptoUtils.generateKeyPair()
// Parent chain: the root of our chain hierarchy
let parentSpec = ChainSpec(
directory: "Nexus",
maxNumberOfTransactionsPerBlock: 100,
maxStateGrowth: 100_000,
premine: 0,
targetBlockTime: 2_000,
initialRewardExponent: 10
)
let genesisConfig = GenesisConfig.standard(spec: parentSpec)
let nodeConfig = LatticeNodeConfig(
publicKey: keyPair.publicKey,
privateKey: keyPair.privateKey,
listenPort: 4001,
storagePath: URL(filePath: "/tmp/multi-chain"),
enableLocalDiscovery: true
)
let node = try await LatticeNode(config: nodeConfig, genesisConfig: genesisConfig)
try await node.start()
Step 2: Define the Child Chain Spec
A child chain has its own ChainSpec with a unique directory. You can tune it for your application's specific needs:
// Child chain: optimized for a high-frequency token application
let tokenChainSpec = ChainSpec(
directory: "TokenLedger", // unique identifier for this child chain
maxNumberOfTransactionsPerBlock: 500, // higher throughput than parent
maxStateGrowth: 200_000, // more state space
premine: 0,
targetBlockTime: 500, // 500ms blocks — 4x faster than parent
initialRewardExponent: 8 // smaller rewards (2^8 = 256)
)
Faster blocks (500ms vs 2s): your token transfers confirm quickly.
Higher throughput (500 txns vs 100): the child chain handles volume without congesting the parent.
Smaller rewards: the child chain's token has a different economic model than the parent.
Step 3: Register the Child Chain via GenesisAction
Child chains are born through a GenesisAction in a parent chain transaction. This embeds the child's genesis block reference into the parent, creating a permanent cryptographic anchor.
// Create the child chain's genesis block
let childGenesisConfig = GenesisConfig.standard(spec: tokenChainSpec)
// The GenesisAction tells the parent chain: "a new child chain exists"
let genesisAction = GenesisAction(
directory: "TokenLedger",
spec: tokenChainSpec
)
// Wrap it in a transaction and submit to the parent chain
let body = TransactionBody(
accountActions: [],
actions: [],
depositActions: [],
genesisActions: [genesisAction],
peerActions: [],
receiptActions: [],
withdrawalActions: [],
signers: [CryptoUtils.sha256(keyPair.publicKey)],
fee: 1,
nonce: 0
)
let bodyData = body.toData()
let bodyHash = CryptoUtils.sha256(String(data: bodyData, encoding: .utf8)!)
let signature = CryptoUtils.sign(message: bodyHash, privateKeyHex: keyPair.privateKey)!
let tx = Transaction(
signatures: [keyPair.publicKey: signature],
body: body
)
await node.submitTransaction(directory: "Nexus", transaction: tx)
print("Child chain genesis submitted to parent")
Step 4: Register the Network and Mine
After the genesis action is mined into a parent block, register the child chain's network layer:
// Register child chain network on the same node
let childIvyConfig = IvyConfig(
publicKey: keyPair.publicKey,
listenPort: 4002, // different port than parent
bootstrapPeers: []
)
try await node.registerChainNetwork(
directory: "TokenLedger",
config: childIvyConfig
)
// Start mining on both chains — merged mining shares work
await node.startMining(directory: "Nexus")
await node.startMining(directory: "TokenLedger")
print("Mining on parent (Nexus) and child (TokenLedger)")
Step 5: Submit Transactions to the Child Chain
Transactions to the child chain work identically — just target the child's directory:
try await Task.sleep(for: .seconds(5))
// Create a token balance entry on the child chain
let tokenAction = Action(
key: "balance:\(CryptoUtils.createAddress(from: keyPair.publicKey))",
oldValue: nil,
newValue: "1000000"
)
let tokenBody = TransactionBody(
accountActions: [],
actions: [tokenAction],
depositActions: [],
genesisActions: [],
peerActions: [],
receiptActions: [],
withdrawalActions: [],
signers: [CryptoUtils.sha256(keyPair.publicKey)],
fee: 1,
nonce: 1
)
let tokenBodyData = tokenBody.toData()
let tokenHash = CryptoUtils.sha256(String(data: tokenBodyData, encoding: .utf8)!)
let tokenSig = CryptoUtils.sign(message: tokenHash, privateKeyHex: keyPair.privateKey)!
let tokenTx = Transaction(
signatures: [keyPair.publicKey: tokenSig],
body: tokenBody
)
// Submit to the child chain, not the parent
await node.submitTransaction(directory: "TokenLedger", transaction: tokenTx)
print("Token balance set on child chain")
The Chain Hierarchy
After these steps, your node maintains two independent chains:
| Nexus (Parent) | TokenLedger (Child) | |
|---|---|---|
| Block Time | 2 seconds | 500ms |
| Txns/Block | 100 | 500 |
| Mining | Independent PoW | Merged with parent |
| State | General purpose | Token-specific |
| Security | Own hashrate | Inherited from parent |
Each chain has its own state tree, mempool, and block history. They share the same P2P identity and storage layer, but are logically independent — a congested child chain doesn't slow down the parent.