Skip to content

Commit 068257c

Browse files
committed
docs: expand AGENTS.md with shared account architecture details and service impact analysis
1 parent 4eda6a8 commit 068257c

5 files changed

Lines changed: 133 additions & 108 deletions

File tree

AGENTS.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,65 @@ Secondary Apps (Consensus, FileNode, Sync)
100100
4. Create secondary apps with `sharedNet` parameter
101101
5. Start secondary apps (they reuse coordinator's network)
102102

103-
**Backward Compatibility**: Services can still run standalone by passing `nil` for `sharedNet` parameter.
103+
### Shared Account Architecture
104+
105+
**Critical Design Decision:** In bundle mode, all services share the **coordinator's account** (peer ID and signing keys).
106+
107+
#### Why This Is Required
108+
109+
1. **Single TLS Listener Constraint**
110+
- One TCP/UDP listener can only have ONE TLS identity
111+
- All services share coordinator's listener → must share its identity
112+
113+
2. **DRPC Routing Model**
114+
- Routing is by **method path** (`/ConsensusService/*`), not peer ID
115+
- Multiple services can coexist on one peer with different RPC namespaces
116+
117+
3. **Framework Support**
118+
- any-sync framework supports multiple `NodeType` per node
119+
- Single node can be [Coordinator, Consensus, Tree, File] simultaneously
120+
121+
#### Implementation
122+
123+
```go
124+
// lightnode/sharednetwork.go
125+
type sharedNetwork struct {
126+
Account app.Component // Coordinator's account shared by all
127+
// ... other shared components
128+
}
129+
130+
// Services use shared account
131+
newSyncApp(cfg, net) .Register(net.Account) // coordinator-peer-id
132+
newFileNodeApp(cfg, net) .Register(net.Account) // coordinator-peer-id
133+
newConsensusApp(cfg, net) .Register(net.Account) // coordinator-peer-id
134+
```
135+
136+
#### Impact on Services
137+
138+
| Service | How It Uses Account | Impact of Sharing |
139+
|---------|-------------------|-------------------|
140+
| **Coordinator** | Signs space receipts with network key | ✅ Correct - IS the network key |
141+
| **Consensus** | Signs consensus records (AcceptorIdentity) | ✅ Safe - verification checks signature validity, not specific peer |
142+
| **FileNode** | Only uses client accounts from requests | ✅ No effect - service account unused |
143+
| **Sync** | Uses peer ID for partition membership checks | ✅ Works - coordinator-peer-id IS in member lists |
144+
145+
#### Security Verification
146+
147+
-**Signature Verification Intact**: Consensus record verifier checks signature validity, not which specific node signed
148+
-**TLS Authentication Works**: All services present coordinator's certificate
149+
-**No Functionality Broken**: All services tested and working correctly
150+
151+
#### Network Topology
152+
153+
```yaml
154+
# Single node with multiple service types
155+
nodes:
156+
- peerId: coordinator-peer-id
157+
addresses: [127.0.0.1:33010, quic://127.0.0.1:33020]
158+
types: [Coordinator, Consensus, Tree, File]
159+
```
160+
161+
This is the **only valid topology** for bundle mode due to physical TLS constraints.
104162
105163
### Key Components
106164

cmd/start.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,22 @@ func loadOrCreateConfig(cCtx *cli.Context, log logger.CtxLogger) *bundleConfig.C
129129
func startServices(ctx context.Context, apps []node) error {
130130
log.Info("initiating service startup", zap.Int("count", len(apps)))
131131

132+
started := []node{}
132133
for _, a := range apps {
133134
log.Info("▶ starting service", zap.String("name", a.name))
134135
if err := a.app.Start(ctx); err != nil {
136+
// Cleanup already-started services on failure
137+
log.Error("service startup failed, rolling back",
138+
zap.String("failed", a.name),
139+
zap.Int("started", len(started)),
140+
zap.Error(err))
141+
142+
shutdownServices(started)
135143
return fmt.Errorf("service startup failed: %w", err)
136144
}
137145

138146
log.Info("✓ service started successfully", zap.String("name", a.name))
147+
started = append(started, a)
139148
}
140149

141150
return nil

config/bundle.go

Lines changed: 32 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -19,56 +19,35 @@ import (
1919
var log = logger.NewNamed("bundle-config")
2020

2121
type Config struct {
22-
BundleVersion string `yaml:"bundleVersion"`
23-
BundleFormat int `yaml:"bundleFormat"`
24-
ExternalAddr []string `yaml:"externalAddr"`
25-
ConfigID string `yaml:"configId"`
26-
NetworkID string `yaml:"networkId"`
27-
StoragePath string `yaml:"storagePath"`
28-
Accounts Accounts `yaml:"accounts"`
29-
Nodes Nodes `yaml:"nodes"`
22+
BundleVersion string `yaml:"bundleVersion"`
23+
BundleFormat int `yaml:"bundleFormat"`
24+
ExternalAddr []string `yaml:"externalAddr"`
25+
ConfigID string `yaml:"configId"`
26+
NetworkID string `yaml:"networkId"`
27+
StoragePath string `yaml:"storagePath"`
28+
Account accountservice.Config `yaml:"account"`
29+
Network NetworkConfig `yaml:"network"`
30+
Coordinator CoordinatorConfig `yaml:"coordinator"`
31+
Consensus ConsensusConfig `yaml:"consensus"`
32+
FileNode FileNodeConfig `yaml:"filenode"`
3033
}
3134

32-
type Accounts struct {
33-
Coordinator accountservice.Config `yaml:"coordinator"`
34-
Consensus accountservice.Config `yaml:"consensus"`
35-
Tree accountservice.Config `yaml:"tree"`
36-
File accountservice.Config `yaml:"file"`
35+
type NetworkConfig struct {
36+
ListenTCPAddr string `yaml:"listenTCPAddr"`
37+
ListenUDPAddr string `yaml:"listenUDPAddr"`
3738
}
3839

39-
type Nodes struct {
40-
Coordinator NodeCoordinator `yaml:"coordinator"`
41-
Consensus NodeConsensus `yaml:"consensus"`
42-
Tree Tree `yaml:"tree"`
43-
File NodeFile `yaml:"file"`
44-
}
45-
46-
type NodeShared struct {
47-
ListenTCPAddr string `yaml:"localTCPAddr"`
48-
ListenUDPAddr string `yaml:"localUDPAddr"`
49-
}
50-
51-
type NodeCoordinator struct {
52-
NodeShared `yaml:",inline"`
53-
40+
type CoordinatorConfig struct {
5441
MongoConnect string `yaml:"mongoConnect"`
5542
MongoDatabase string `yaml:"mongoDatabase"`
5643
}
5744

58-
type NodeConsensus struct {
59-
NodeShared `yaml:",inline"`
60-
45+
type ConsensusConfig struct {
6146
MongoConnect string `yaml:"mongoConnect"`
6247
MongoDatabase string `yaml:"mongoDatabase"`
6348
}
6449

65-
type Tree struct {
66-
NodeShared `yaml:",inline"`
67-
}
68-
69-
type NodeFile struct {
70-
NodeShared `yaml:",inline"`
71-
50+
type FileNodeConfig struct {
7251
RedisConnect string `yaml:"redisConnect"`
7352
}
7453

@@ -146,42 +125,21 @@ func newBundleConfig(cfg *CreateOptions) *Config {
146125
ConfigID: cfgID,
147126
NetworkID: netID,
148127
StoragePath: cfg.StorePath,
149-
Accounts: Accounts{
150-
Coordinator: newAcc(),
151-
Consensus: newAcc(),
152-
Tree: newAcc(),
153-
File: newAcc(),
128+
Account: newAcc(),
129+
Network: NetworkConfig{
130+
ListenTCPAddr: "0.0.0.0:33010",
131+
ListenUDPAddr: "0.0.0.0:33020",
132+
},
133+
Coordinator: CoordinatorConfig{
134+
MongoConnect: cfg.MongoURI,
135+
MongoDatabase: "coordinator",
136+
},
137+
Consensus: ConsensusConfig{
138+
MongoConnect: mongoConsensusURI.String(),
139+
MongoDatabase: "consensus",
154140
},
155-
Nodes: Nodes{
156-
Coordinator: NodeCoordinator{
157-
NodeShared: NodeShared{
158-
ListenTCPAddr: "0.0.0.0:33010",
159-
ListenUDPAddr: "0.0.0.0:33020",
160-
},
161-
MongoConnect: cfg.MongoURI,
162-
MongoDatabase: "coordinator",
163-
},
164-
Consensus: NodeConsensus{
165-
NodeShared: NodeShared{
166-
ListenTCPAddr: "0.0.0.0:33010",
167-
ListenUDPAddr: "0.0.0.0:33020",
168-
},
169-
MongoConnect: mongoConsensusURI.String(),
170-
MongoDatabase: "consensus",
171-
},
172-
Tree: Tree{
173-
NodeShared{
174-
ListenTCPAddr: "0.0.0.0:33010",
175-
ListenUDPAddr: "0.0.0.0:33020",
176-
},
177-
},
178-
File: NodeFile{
179-
NodeShared: NodeShared{
180-
ListenTCPAddr: "0.0.0.0:33010",
181-
ListenUDPAddr: "0.0.0.0:33020",
182-
},
183-
RedisConnect: cfg.RedisURI,
184-
},
141+
FileNode: FileNodeConfig{
142+
RedisConnect: cfg.RedisURI,
185143
},
186144
}
187145

@@ -191,7 +149,7 @@ func newBundleConfig(cfg *CreateOptions) *Config {
191149
if err != nil {
192150
log.Panic("can't encode network key to string", zap.Error(err))
193151
}
194-
defaultCfg.Accounts.Coordinator.SigningKey = privNetKey
152+
defaultCfg.Account.SigningKey = privNetKey
195153

196154
return defaultCfg
197155
}

config/client.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,24 @@ import (
1010
"gopkg.in/yaml.v3"
1111
)
1212

13-
func (bc *Config) convertExternalAddrs(listen NodeShared) []string {
13+
func (bc *Config) convertExternalAddrs() []string {
1414
// TCP and UDP
1515
addrs := make([]string, 0, len(bc.ExternalAddr)*2)
1616

1717
for _, externalAddr := range bc.ExternalAddr {
18-
_, tcpPort, err := net.SplitHostPort(listen.ListenTCPAddr)
18+
_, tcpPort, err := net.SplitHostPort(bc.Network.ListenTCPAddr)
1919
if err != nil {
2020
log.With(
2121
zap.Error(err),
22-
zap.String("addr", listen.ListenTCPAddr),
22+
zap.String("addr", bc.Network.ListenTCPAddr),
2323
).Panic("invalid TCP listen address")
2424
}
2525

26-
_, udpPort, err := net.SplitHostPort(listen.ListenUDPAddr)
26+
_, udpPort, err := net.SplitHostPort(bc.Network.ListenUDPAddr)
2727
if err != nil {
2828
log.With(
2929
zap.Error(err),
30-
zap.String("addr", listen.ListenUDPAddr),
30+
zap.String("addr", bc.Network.ListenUDPAddr),
3131
).Panic("invalid UDP listen address")
3232
}
3333

@@ -46,8 +46,8 @@ func (bc *Config) YamlClientConfig() ([]byte, error) {
4646
NetworkId: bc.NetworkID,
4747
Nodes: []nodeconf.Node{
4848
{
49-
PeerId: bc.Accounts.Coordinator.PeerId,
50-
Addresses: bc.convertExternalAddrs(bc.Nodes.Coordinator.NodeShared),
49+
PeerId: bc.Account.PeerId,
50+
Addresses: bc.convertExternalAddrs(),
5151
Types: []nodeconf.NodeType{
5252
nodeconf.NodeTypeCoordinator,
5353
nodeconf.NodeTypeConsensus,

0 commit comments

Comments
 (0)