Skip to content

Commit d5deadd

Browse files
sameh-faroukclaude
andcommitted
fix: resolve data loss bugs in farm IP, node interface, and contract mapping handlers
- B1: Replace async forEach with for...of in farmUpdated to fix race conditions on IP saves/removals - B2: Append IP string to PublicIp ID in farmStored/farmUpdated to prevent overwrites when multiple IPs exist - B3: Append interface name to Interfaces ID in nodeStored (matching nodeUpdated pattern) to prevent overwrites - B4: Add undefined guard for contractID in grace period handlers to prevent unpredictable queries - B5: Fix handlePublicConfigV105 fallthrough creating ghost PublicConfig with null fields when config is undefined - B6: Save inline PublicConfig in nodeStored for spec v9 nodes that carried publicConfig at creation time - Fix cross-farm duplicate IP handling: properly skip instead of silently overwriting gateway - Add '-' separator to composite IDs for readability (interfaces and public IPs) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a4fb1b7 commit d5deadd

3 files changed

Lines changed: 37 additions & 26 deletions

File tree

src/mappings/contracts.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,11 @@ export async function contractGracePeriodStarted(
469469
contractID = contractGracePeriodStartedEvent.asV105.contractId
470470
}
471471

472+
if (contractID === undefined) {
473+
ctx.log.error({ eventName: item.name }, `found ContractGracePeriodStarted with unknown version! make sure types are updated`);
474+
return
475+
}
476+
472477
const savedNodeContract = await ctx.store.get(NodeContract, { where: { contractID } })
473478
if (savedNodeContract) {
474479
savedNodeContract.state = ContractState.GracePeriod
@@ -505,6 +510,11 @@ export async function contractGracePeriodEnded(
505510
contractID = contractGracePeriodEnded.asV105.contractId
506511
}
507512

513+
if (contractID === undefined) {
514+
ctx.log.error({ eventName: item.name }, `found ContractGracePeriodEnded with unknown version! make sure types are updated`);
515+
return
516+
}
517+
508518
const savedNodeContract = await ctx.store.get(NodeContract, { where: { contractID } })
509519
if (savedNodeContract) {
510520
savedNodeContract.state = ContractState.Created

src/mappings/farms.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export async function farmStored(
6767

6868
const newIP = new PublicIp()
6969

70-
newIP.id = item.event.id
70+
newIP.id = item.event.id + '-' + validateString(ctx, ip.ip.toString())
7171

7272
newIP.ip = validateString(ctx, ip.ip.toString())
7373
newIP.gateway = validateString(ctx, ip.gateway.toString())
@@ -167,26 +167,26 @@ export async function farmUpdated(
167167
savedFarm.certification = certification
168168

169169
let eventPublicIPs = farmUpdatedEventParsed.publicIps
170-
farmUpdatedEventParsed.publicIps.forEach(async ip => {
170+
for (const ip of farmUpdatedEventParsed.publicIps) {
171171
if (!checkIPs(ctx, ip.ip.toString(), ip.gateway.toString())) {
172-
return
172+
continue
173173
}
174174
if (ip.ip.toString().indexOf('\x00') >= 0) {
175-
return
175+
continue
176176
}
177177
const savedIP = await ctx.store.get(PublicIp, { where: { ip: ip.ip.toString() }, relations: { farm: true } })
178-
// ip is already there in storage, don't save it again
179178
if (savedIP) {
180-
savedIP.ip = validateString(ctx, ip.ip.toString()) // not effective, but for since we already check for \x00
181-
savedIP.gateway = validateString(ctx, ip.gateway.toString())
182179
if (savedIP.farm.id !== savedFarm.id) {
183-
ctx.log.error({ eventName: item.name, ip: ip.ip.toString() }, `PublicIP: ${ip.ip.toString()} already exists on farm: ${savedIP.farm.farmID}, skiped adding it to farm with ID: ${savedFarm.farmID}`);
180+
ctx.log.error({ eventName: item.name, ip: ip.ip.toString() }, `PublicIP: ${ip.ip.toString()} already exists on farm: ${savedIP.farm.farmID}, skipped adding it to farm with ID: ${savedFarm.farmID}`);
181+
continue
184182
}
183+
// Same farm — update gateway
184+
savedIP.gateway = validateString(ctx, ip.gateway.toString())
185+
savedIP.contractId = ip.contractId
185186
await ctx.store.save<PublicIp>(savedIP)
186187
} else {
187-
188188
const newIP = new PublicIp()
189-
newIP.id = item.event.id
189+
newIP.id = item.event.id + '-' + validateString(ctx, ip.ip.toString())
190190
newIP.ip = validateString(ctx, ip.ip.toString())
191191
newIP.gateway = validateString(ctx, ip.gateway.toString())
192192
newIP.contractId = ip.contractId
@@ -198,21 +198,18 @@ export async function farmUpdated(
198198
savedFarm.publicIPs.push(newIP)
199199
ctx.log.debug({ eventName: item.name, ip: ip.ip.toString() }, `PublicIP: ${ip.ip.toString()} added with farm id: ${savedFarm.farmID}`);
200200
}
201-
})
201+
}
202202

203203
await ctx.store.save<Farm>(savedFarm)
204204

205205
const publicIPsOfFarm = await ctx.store.find<PublicIp>(PublicIp, { where: { farm: { id: savedFarm.id } }, relations: { farm: true } })
206-
publicIPsOfFarm.forEach(async ip => {
206+
for (const ip of publicIPsOfFarm) {
207207
if (eventPublicIPs.filter(eventIp => validateString(ctx, eventIp.ip.toString()) === ip.ip).length === 0) {
208208
// IP got removed from farm
209209
await ctx.store.remove<PublicIp>(ip)
210-
// remove ip from savedFarm.publicIPs
211-
// savedFarm.publicIPs = savedFarm.publicIPs.filter(savedIP => savedIP.id !== ip.id)
212-
// TODO: check if we need this code above? or Cascade delete involving here?
213210
ctx.log.debug({ eventName: item.name, ip: ip.ip.toString() }, `PublicIP: ${ip.ip.toString()} in farm: ${savedFarm.farmID} removed from publicIPs`);
214211
}
215-
})
212+
}
216213

217214
let farm = item.event.args as Farm
218215
if (farm.dedicatedFarm) {

src/mappings/nodes.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,16 @@ export async function nodeStored(
7979
await ctx.store.save<Node>(newNode)
8080

8181
const pubConfig = getNodePublicConfig(ctx, node)
82-
const newPubConfig = new PublicConfig()
83-
newPubConfig.id = item.event.id
84-
newPubConfig.ipv4 = pubConfig?.ip4
85-
newPubConfig.gw4 = pubConfig?.gw4
86-
newPubConfig.ipv6 = pubConfig?.ip6
87-
newPubConfig.gw6 = pubConfig?.gw6
88-
newPubConfig.node = newNode
82+
if (pubConfig) {
83+
const newPubConfig = new PublicConfig()
84+
newPubConfig.id = item.event.id
85+
newPubConfig.ipv4 = pubConfig.ip4
86+
newPubConfig.gw4 = pubConfig.gw4
87+
newPubConfig.ipv6 = pubConfig.ip6
88+
newPubConfig.gw6 = pubConfig.gw6
89+
newPubConfig.node = newNode
90+
await ctx.store.save<PublicConfig>(newPubConfig)
91+
}
8992

9093
if (node.isV43) {
9194
const nodeAsV43 = node.asV43
@@ -138,7 +141,7 @@ export async function nodeStored(
138141

139142
const interfacesPromisses = nodeEvent.interfaces.map(async intf => {
140143
const newInterface = new Interfaces()
141-
newInterface.id = item.event.id
144+
newInterface.id = item.event.id + '-' + validateString(ctx, intf.name.toString())
142145
newInterface.node = newNode
143146
newInterface.name = validateString(ctx, intf.name.toString())
144147
newInterface.mac = validateString(ctx, intf.mac.toString())
@@ -327,7 +330,7 @@ export async function nodeUpdated(
327330
// Save ones from update event
328331
await Promise.all(nodeEvent.interfaces.map(async intf => {
329332
const newInterface = new Interfaces()
330-
newInterface.id = item.event.id + validateString(ctx, intf.name.toString())
333+
newInterface.id = item.event.id + '-' + validateString(ctx, intf.name.toString())
331334
newInterface.name = validateString(ctx, intf.name.toString())
332335
newInterface.mac = validateString(ctx, intf.mac.toString())
333336
newInterface.ips = intf.ips.map(ip => validateString(ctx, ip.toString())).join(',')
@@ -462,8 +465,9 @@ async function handlePublicConfigV105(ctx: Ctx, eventID: string, nodeID: number,
462465
if (!config) {
463466
const pubConfig = await ctx.store.get(PublicConfig, { where: { node: { nodeID } }, relations: { node: true } })
464467
if (pubConfig) {
465-
return await ctx.store.remove(pubConfig)
468+
await ctx.store.remove(pubConfig)
466469
}
470+
return
467471
}
468472

469473
const savedNode = await ctx.store.get(Node, { where: { nodeID: nodeID }, relations: { location: true, interfaces: true } })

0 commit comments

Comments
 (0)