mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-28 00:24:19 +00:00
fix(node): import per-client traffic history on first sync of a node-hosted inbound
On the first sync of a node-hosted inbound, the central inbound adopted the node's full lifetime counter but every client_traffics row was seeded at 0 (with the delta baseline set to the node's current counter). So adding or migrating a node that already had traffic kept the inbound total correct while every per-client counter restarted from zero, and the master under-reported per-client usage by the entire pre-attach history. Seed a new client_traffics row from the node counter only when the inbound was created during the same sync (a genuine node-add / inbound re-import); a client reappearing under a pre-existing inbound still seeds 0, preserving the ghost protection in TestGhostData_NoPhantomTraffic. The seed is additionally gated on the delete tombstone so a just-deleted client cannot be resurrected if its inbound is recreated. Baseline still equals the seeded value, so the next sync delta is 0 and no traffic is double counted. Adds TestNodeAdd_ImportsClientHistoryWithNewInbound and TestNodeAdd_TombstonedClientNotResurrected.
This commit is contained in:
@@ -380,6 +380,8 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
|
||||
|
||||
structuralChange := false
|
||||
|
||||
newInboundIDs := make(map[int]struct{})
|
||||
|
||||
snapTags := make(map[string]struct{}, len(snap.Inbounds))
|
||||
for _, snapIb := range snap.Inbounds {
|
||||
if snapIb == nil {
|
||||
@@ -466,6 +468,7 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
|
||||
if newIb.Tag != snapIb.Tag {
|
||||
tagToCentral[newIb.Tag] = &newIb
|
||||
}
|
||||
newInboundIDs[newIb.Id] = struct{}{}
|
||||
structuralChange = true
|
||||
continue
|
||||
}
|
||||
@@ -620,6 +623,10 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
|
||||
if dirty {
|
||||
continue
|
||||
}
|
||||
var seedUp, seedDown int64
|
||||
if _, isNewInbound := newInboundIDs[c.Id]; isNewInbound && !isClientEmailTombstoned(cs.Email) {
|
||||
seedUp, seedDown = canon.Up, canon.Down
|
||||
}
|
||||
row := &xray.ClientTraffic{
|
||||
InboundId: c.Id,
|
||||
Email: cs.Email,
|
||||
@@ -627,8 +634,8 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
|
||||
Total: cs.Total,
|
||||
ExpiryTime: cs.ExpiryTime,
|
||||
Reset: cs.Reset,
|
||||
Up: 0,
|
||||
Down: 0,
|
||||
Up: seedUp,
|
||||
Down: seedDown,
|
||||
LastOnline: cs.LastOnline,
|
||||
}
|
||||
if err := tx.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "email"}}, DoNothing: true}).
|
||||
|
||||
@@ -120,6 +120,32 @@ func TestSingleNode_MirrorsCorrectly(t *testing.T) {
|
||||
assertUpDown(t, readTraffic(t, db, email), 200, 200, "second sync — delta accrues")
|
||||
}
|
||||
|
||||
func TestNodeAdd_ImportsClientHistoryWithNewInbound(t *testing.T) {
|
||||
db := initTrafficTestDB(t)
|
||||
svc := &InboundService{}
|
||||
|
||||
const email = "newnode-client"
|
||||
const histUp, histDown int64 = 6_000_000_000, 200_000_000_000
|
||||
|
||||
syncNode(t, svc, 1, "fresh-in", xray.ClientTraffic{Email: email, Up: histUp, Down: histDown, Enable: true})
|
||||
assertUpDown(t, readTraffic(t, db, email), histUp, histDown, "node-add: client history imported with its brand-new inbound")
|
||||
|
||||
syncNode(t, svc, 1, "fresh-in", xray.ClientTraffic{Email: email, Up: histUp + 1024, Down: histDown + 2048, Enable: true})
|
||||
assertUpDown(t, readTraffic(t, db, email), histUp+1024, histDown+2048, "post-import delta accrues, no double count")
|
||||
}
|
||||
|
||||
func TestNodeAdd_TombstonedClientNotResurrected(t *testing.T) {
|
||||
db := initTrafficTestDB(t)
|
||||
svc := &InboundService{}
|
||||
|
||||
const email = "deleted-ghost"
|
||||
const stale int64 = 50_000_000_000
|
||||
|
||||
tombstoneClientEmail(email)
|
||||
syncNode(t, svc, 1, "fresh-in", xray.ClientTraffic{Email: email, Up: stale, Down: stale, Enable: true})
|
||||
assertUpDown(t, readTraffic(t, db, email), 0, 0, "tombstoned client must not resurrect via node-add seed")
|
||||
}
|
||||
|
||||
func TestUpgrade_PreExistingRow_NoDoubleCount(t *testing.T) {
|
||||
db := initTrafficTestDB(t)
|
||||
createNodeInbound(t, db, 1, "n1-in", 41001)
|
||||
|
||||
Reference in New Issue
Block a user