diff --git a/cmd/satellite/compensation.go b/cmd/satellite/compensation.go index f880f4525..12f7c83b9 100644 --- a/cmd/satellite/compensation.go +++ b/cmd/satellite/compensation.go @@ -81,18 +81,19 @@ func generateInvoicesCSV(ctx context.Context, period compensation.Period, out io } nodeInfo := compensation.NodeInfo{ - ID: usage.NodeID, - CreatedAt: node.CreatedAt, - Disqualified: node.Disqualified, - GracefulExit: gracefulExit, - UsageAtRest: usage.AtRestTotal, - UsageGet: usage.GetTotal, - UsagePut: usage.PutTotal, - UsageGetRepair: usage.GetRepairTotal, - UsagePutRepair: usage.PutRepairTotal, - UsageGetAudit: usage.GetAuditTotal, - TotalHeld: withheldAmounts.TotalHeld, - TotalDisposed: withheldAmounts.TotalDisposed, + ID: usage.NodeID, + CreatedAt: node.CreatedAt, + LastContactSuccess: node.Reputation.LastContactSuccess, + Disqualified: node.Disqualified, + GracefulExit: gracefulExit, + UsageAtRest: usage.AtRestTotal, + UsageGet: usage.GetTotal, + UsagePut: usage.PutTotal, + UsageGetRepair: usage.GetRepairTotal, + UsagePutRepair: usage.PutRepairTotal, + UsageGetAudit: usage.GetAuditTotal, + TotalHeld: withheldAmounts.TotalHeld, + TotalDisposed: withheldAmounts.TotalDisposed, } invoice := compensation.Invoice{ diff --git a/satellite/compensation/codes.go b/satellite/compensation/codes.go index 10dc1f78e..6215bebf3 100644 --- a/satellite/compensation/codes.go +++ b/satellite/compensation/codes.go @@ -26,13 +26,17 @@ const ( // GracefulExit is included if the node has gracefully exited. GracefulExit Code = "X" + + // Offline is included if the node's last contact success is before the starting + // period. + Offline Code = "O" ) // CodeFromString parses the string into a Code. func CodeFromString(s string) (Code, error) { code := Code(s) switch code { - case Disqualified, Sanctioned, No1099, InWithholding, GracefulExit: + case Disqualified, Sanctioned, No1099, InWithholding, GracefulExit, Offline: return code, nil default: return "", Error.New("no such code %q", code) diff --git a/satellite/compensation/statement.go b/satellite/compensation/statement.go index 67c6a901b..a24cb186c 100644 --- a/satellite/compensation/statement.go +++ b/satellite/compensation/statement.go @@ -36,18 +36,19 @@ var ( // NodeInfo contains all of the information about a node and the operations // it performed in some period. type NodeInfo struct { - ID storj.NodeID - CreatedAt time.Time - Disqualified *time.Time - GracefulExit *time.Time - UsageAtRest float64 - UsageGet int64 - UsagePut int64 - UsageGetRepair int64 - UsagePutRepair int64 - UsageGetAudit int64 - TotalHeld currency.MicroUnit - TotalDisposed currency.MicroUnit + ID storj.NodeID + CreatedAt time.Time + LastContactSuccess time.Time + Disqualified *time.Time + GracefulExit *time.Time + UsageAtRest float64 + UsageGet int64 + UsagePut int64 + UsageGetRepair int64 + UsagePutRepair int64 + UsageGetAudit int64 + TotalHeld currency.MicroUnit + TotalDisposed currency.MicroUnit } // Statement is the computed amounts and codes from a node. @@ -98,6 +99,7 @@ type PeriodInfo struct { // GenerateStatements generates all of the Statements for the given PeriodInfo. func GenerateStatements(info PeriodInfo) ([]Statement, error) { + startDate := info.Period.StartDate() endDate := info.Period.EndDateExclusive() rates := info.Rates @@ -150,6 +152,11 @@ func GenerateStatements(info PeriodInfo) ([]Statement, error) { codes = append(codes, GracefulExit) } + offline := node.LastContactSuccess.Before(startDate) + if offline { + codes = append(codes, Offline) + } + withheldPercent, inWithholding := NodeWithheldPercent(withheldPercents, node.CreatedAt, endDate) held := PercentOf(total, decimal.NewFromInt(int64(withheldPercent))) owed := total.Sub(held) @@ -177,7 +184,7 @@ func GenerateStatements(info PeriodInfo) ([]Statement, error) { owed = owed.Add(disposed) } - // If the node is disqualified nothing is owed/held/disposed. + // If the node is disqualified but not gracefully exited, nothing is owed/held/disposed. if node.Disqualified != nil && node.Disqualified.Before(endDate) && !gracefullyExited { codes = append(codes, Disqualified) disposed = decimal.Zero @@ -185,6 +192,13 @@ func GenerateStatements(info PeriodInfo) ([]Statement, error) { owed = decimal.Zero } + // If the node is offline, nothing is owed/held/disposed. + if offline { + disposed = decimal.Zero + held = decimal.Zero + owed = decimal.Zero + } + var overflowErrs errs.Group toMicroUnit := func(v decimal.Decimal) currency.MicroUnit { m, err := currency.MicroUnitFromDecimal(v) diff --git a/satellite/compensation/statement_test.go b/satellite/compensation/statement_test.go index 5d3ab9cad..64be2ac7c 100644 --- a/satellite/compensation/statement_test.go +++ b/satellite/compensation/statement_test.go @@ -52,14 +52,15 @@ func TestGenerateStatements(t *testing.T) { name: "within withholding", surgePercent: 0, node: compensation.NodeInfo{ - ID: nodeID, - CreatedAt: time.Date(2019, 11, 2, 0, 0, 0, 0, time.UTC), - UsageAtRest: 1 * GB, - UsageGet: 2 * TB, - UsagePut: 3 * TB, - UsageGetRepair: 4 * TB, - UsagePutRepair: 5 * TB, - UsageGetAudit: 6 * TB, + ID: nodeID, + LastContactSuccess: time.Date(2019, 11, 2, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2019, 11, 2, 0, 0, 0, 0, time.UTC), + UsageAtRest: 1 * GB, + UsageGet: 2 * TB, + UsagePut: 3 * TB, + UsageGetRepair: 4 * TB, + UsagePutRepair: 5 * TB, + UsageGetAudit: 6 * TB, }, statement: compensation.Statement{ NodeID: nodeID, @@ -79,15 +80,16 @@ func TestGenerateStatements(t *testing.T) { name: "just out of withheld", surgePercent: 0, node: compensation.NodeInfo{ - ID: nodeID, - CreatedAt: time.Date(2019, 11, 1, 0, 0, 0, 0, time.UTC), - UsageAtRest: 1 * GB, - UsageGet: 2 * TB, - UsagePut: 3 * TB, - UsageGetRepair: 4 * TB, - UsagePutRepair: 5 * TB, - UsageGetAudit: 6 * TB, - TotalHeld: D(40), + ID: nodeID, + LastContactSuccess: time.Date(2019, 11, 2, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2019, 11, 1, 0, 0, 0, 0, time.UTC), + UsageAtRest: 1 * GB, + UsageGet: 2 * TB, + UsagePut: 3 * TB, + UsageGetRepair: 4 * TB, + UsagePutRepair: 5 * TB, + UsageGetAudit: 6 * TB, + TotalHeld: D(40), }, statement: compensation.Statement{ NodeID: nodeID, @@ -106,16 +108,17 @@ func TestGenerateStatements(t *testing.T) { name: "out of withheld and already disposed", surgePercent: 0, node: compensation.NodeInfo{ - ID: nodeID, - CreatedAt: time.Date(2019, 6, 12, 0, 0, 0, 0, time.UTC), - UsageAtRest: 1 * GB, - UsageGet: 2 * TB, - UsagePut: 3 * TB, - UsageGetRepair: 4 * TB, - UsagePutRepair: 5 * TB, - UsageGetAudit: 6 * TB, - TotalHeld: D(40), - TotalDisposed: D(24), + ID: nodeID, + LastContactSuccess: time.Date(2019, 11, 2, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2019, 6, 12, 0, 0, 0, 0, time.UTC), + UsageAtRest: 1 * GB, + UsageGet: 2 * TB, + UsagePut: 3 * TB, + UsageGetRepair: 4 * TB, + UsagePutRepair: 5 * TB, + UsageGetAudit: 6 * TB, + TotalHeld: D(40), + TotalDisposed: D(24), }, statement: compensation.Statement{ NodeID: nodeID, @@ -134,17 +137,18 @@ func TestGenerateStatements(t *testing.T) { name: "graceful exit within period", surgePercent: 0, node: compensation.NodeInfo{ - ID: nodeID, - CreatedAt: time.Date(2018, 6, 12, 0, 0, 0, 0, time.UTC), - GracefulExit: timePtr(time.Date(2019, 11, 30, 23, 59, 59, 0, time.UTC)), - UsageAtRest: 1 * GB, - UsageGet: 2 * TB, - UsagePut: 3 * TB, - UsageGetRepair: 4 * TB, - UsagePutRepair: 5 * TB, - UsageGetAudit: 6 * TB, - TotalHeld: D(40), - TotalDisposed: D(24), + ID: nodeID, + LastContactSuccess: time.Date(2019, 11, 2, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2018, 6, 12, 0, 0, 0, 0, time.UTC), + GracefulExit: timePtr(time.Date(2019, 11, 30, 23, 59, 59, 0, time.UTC)), + UsageAtRest: 1 * GB, + UsageGet: 2 * TB, + UsagePut: 3 * TB, + UsageGetRepair: 4 * TB, + UsagePutRepair: 5 * TB, + UsageGetAudit: 6 * TB, + TotalHeld: D(40), + TotalDisposed: D(24), }, statement: compensation.Statement{ NodeID: nodeID, @@ -160,6 +164,34 @@ func TestGenerateStatements(t *testing.T) { Disposed: D(16), }, }, + { + name: "offline", + surgePercent: 0, + node: compensation.NodeInfo{ + ID: nodeID, + LastContactSuccess: time.Date(2019, 10, 2, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2019, 11, 2, 0, 0, 0, 0, time.UTC), + UsageAtRest: 1 * GB, + UsageGet: 2 * TB, + UsagePut: 3 * TB, + UsageGetRepair: 4 * TB, + UsagePutRepair: 5 * TB, + UsageGetAudit: 6 * TB, + }, + statement: compensation.Statement{ + NodeID: nodeID, + Codes: compensation.Codes{compensation.Offline, compensation.InWithholding}, + AtRest: D(2), + Get: D(6), + Put: D(15), + GetRepair: D(28), + PutRepair: D(55), + GetAudit: D(78), + Owed: D(0), + Held: D(0), + Disposed: D(0), + }, + }, } { tt := tt t.Run(tt.name, func(t *testing.T) {