Skip to content

Commit

Permalink
{storagenode/console,web/storagenode}: fetch pricing model from stora…
Browse files Browse the repository at this point in the history
…genode API

Instead of the hardcoded payout rates that is assumed for all satellites,
this change adds a new endpoint for fetching the pricing model for
each satellite.
The pricing model is then displayed on the Info & Estimation table
on the dashboard

Updates storj/storj-private#245

Change-Id: Iac7669e3e6eb690bbaad6e64bbbe42dfd775f078
  • Loading branch information
profclems committed May 9, 2023
1 parent d80d674 commit c64f3f3
Show file tree
Hide file tree
Showing 12 changed files with 150 additions and 15 deletions.
32 changes: 32 additions & 0 deletions storagenode/console/consoleapi/storagenode.go
Expand Up @@ -155,6 +155,38 @@ func (dashboard *StorageNode) EstimatedPayout(w http.ResponseWriter, r *http.Req
}
}

// Pricing returns pricing model for specific satellite.
func (dashboard *StorageNode) Pricing(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)

w.Header().Set(contentType, applicationJSON)

params := mux.Vars(r)
id, ok := params["id"]
if !ok {
dashboard.serveJSONError(w, http.StatusInternalServerError, ErrStorageNodeAPI.Wrap(err))
return
}
satelliteID, err := storj.NodeIDFromString(id)
if err != nil {
dashboard.serveJSONError(w, http.StatusBadRequest, ErrStorageNodeAPI.Wrap(err))
return
}

data, err := dashboard.service.GetSatellitePricingModel(ctx, satelliteID)
if err != nil {
dashboard.serveJSONError(w, http.StatusInternalServerError, ErrStorageNodeAPI.Wrap(err))
return
}

if err := json.NewEncoder(w).Encode(data); err != nil {
dashboard.log.Error("failed to encode json response", zap.Error(ErrStorageNodeAPI.Wrap(err)))
return
}
}

// serveJSONError writes JSON error to response output stream.
func (dashboard *StorageNode) serveJSONError(w http.ResponseWriter, status int, err error) {
w.WriteHeader(status)
Expand Down
1 change: 1 addition & 0 deletions storagenode/console/consoleserver/server.go
Expand Up @@ -72,6 +72,7 @@ func NewServer(logger *zap.Logger, assets fs.FS, notifications *notifications.Se
storageNodeRouter.HandleFunc("/", storageNodeController.StorageNode).Methods(http.MethodGet)
storageNodeRouter.HandleFunc("/satellites", storageNodeController.Satellites).Methods(http.MethodGet)
storageNodeRouter.HandleFunc("/satellite/{id}", storageNodeController.Satellite).Methods(http.MethodGet)
storageNodeRouter.HandleFunc("/satellites/{id}/pricing", storageNodeController.Pricing).Methods(http.MethodGet)
storageNodeRouter.HandleFunc("/estimated-payout", storageNodeController.EstimatedPayout).Methods(http.MethodGet)

notificationController := consoleapi.NewNotifications(server.log, server.notifications)
Expand Down
12 changes: 12 additions & 0 deletions storagenode/console/service.go
Expand Up @@ -482,3 +482,15 @@ func (s *Service) VerifySatelliteID(ctx context.Context, satelliteID storj.NodeI

return nil
}

// GetSatellitePricingModel returns pricing model for the specified satellite.
func (s *Service) GetSatellitePricingModel(ctx context.Context, satelliteID storj.NodeID) (pricingModel *pricing.Pricing, err error) {
defer mon.Task()(&ctx)(&err)

pricingModel, err = s.pricingDB.Get(ctx, satelliteID)
if err != nil {
return nil, SNOServiceErr.Wrap(err)
}

return pricingModel, nil
}
6 changes: 6 additions & 0 deletions web/storagenode/src/app/components/SNOHeader.vue
Expand Up @@ -187,6 +187,12 @@ export default class SNOHeader extends Vue {
console.error(error);
}
try {
await this.$store.dispatch(PAYOUT_ACTIONS.GET_PRICING_MODEL, selectedSatelliteId);
} catch (error) {
console.error(error);
}
await this.$store.dispatch(APPSTATE_ACTIONS.SET_LOADING, false);
try {
Expand Down
Expand Up @@ -127,6 +127,12 @@ export default class SatelliteSelectionDropdown extends Vue {
console.error(error);
}
try {
await this.$store.dispatch(PAYOUT_ACTIONS.GET_PRICING_MODEL, id);
} catch (error) {
console.error(error);
}
try {
await this.$store.dispatch(PAYOUT_ACTIONS.GET_TOTAL, id);
} catch (error) {
Expand Down
23 changes: 16 additions & 7 deletions web/storagenode/src/app/components/payments/EstimationArea.vue
Expand Up @@ -146,17 +146,19 @@ import { Component, Vue } from 'vue-property-decorator';
import { APPSTATE_ACTIONS } from '@/app/store/modules/appState';
import {
BANDWIDTH_DOWNLOAD_PRICE_PER_TB,
BANDWIDTH_REPAIR_PRICE_PER_TB,
DISK_SPACE_PRICE_PER_TB,
PAYOUT_ACTIONS,
} from '@/app/store/modules/payout';
import {
monthNames,
PayoutInfoRange,
} from '@/app/types/payout';
import { Size } from '@/private/memory/size';
import { EstimatedPayout, PayoutPeriod, TotalPaystubForPeriod } from '@/storagenode/payouts/payouts';
import {
EstimatedPayout,
PayoutPeriod,
SatellitePricingModel,
TotalPaystubForPeriod,
} from '@/storagenode/payouts/payouts';
import EstimationPeriodDropdown from '@/app/components/payments/EstimationPeriodDropdown.vue';
Expand Down Expand Up @@ -263,6 +265,13 @@ export default class EstimationArea extends Vue {
return this.$store.state.payoutModule.estimation;
}
/**
* Returns satellite pricing model.
*/
public get pricing(): SatellitePricingModel {
return this.$store.state.payoutModule.pricingModel;
}
/**
* Returns calculated or stored held amount.
*/
Expand Down Expand Up @@ -375,23 +384,23 @@ export default class EstimationArea extends Vue {
new EstimationTableRow(
'Download',
'Egress',
`$${BANDWIDTH_DOWNLOAD_PRICE_PER_TB / 100} / TB`,
`$${this.pricing.egressBandwidth} / TB`,
'--',
Size.toBase10String(estimatedPayout.egressBandwidth),
estimatedPayout.egressBandwidthPayout,
),
new EstimationTableRow(
'Repair & Audit',
'Egress',
`$${BANDWIDTH_REPAIR_PRICE_PER_TB / 100} / TB`,
`$${this.pricing.repairBandwidth} / TB`,
'--',
Size.toBase10String(estimatedPayout.egressRepairAudit),
estimatedPayout.egressRepairAuditPayout,
),
new EstimationTableRow(
'Disk Average Month',
'Storage',
`$${DISK_SPACE_PRICE_PER_TB / 100} / TBm`,
`$${this.pricing.diskSpace} / TBm`,
Size.toBase10String(estimatedPayout.diskSpace) + 'm',
'--',
estimatedPayout.diskSpacePayout,
Expand Down
17 changes: 11 additions & 6 deletions web/storagenode/src/app/store/modules/payout.ts
Expand Up @@ -12,13 +12,14 @@ import {
EstimatedPayout,
PayoutPeriod,
SatelliteHeldHistory,
SatellitePayoutForPeriod,
SatellitePayoutForPeriod, SatellitePricingModel,
TotalPayments,
TotalPaystubForPeriod,
} from '@/storagenode/payouts/payouts';
import { PayoutService } from '@/storagenode/payouts/service';

export const PAYOUT_MUTATIONS = {
SET_PRICING_MODEL: 'SET_PRICING_MODEL',
SET_PAYOUT_INFO: 'SET_PAYOUT_INFO',
SET_RANGE: 'SET_RANGE',
SET_TOTAL: 'SET_TOTAL',
Expand All @@ -32,6 +33,7 @@ export const PAYOUT_MUTATIONS = {
};

export const PAYOUT_ACTIONS = {
GET_PRICING_MODEL: 'GET_PRICING_MODEL',
GET_PAYOUT_INFO: 'GET_PAYOUT_INFO',
SET_PERIODS_RANGE: 'SET_PERIODS_RANGE',
GET_TOTAL: 'GET_TOTAL',
Expand All @@ -42,11 +44,6 @@ export const PAYOUT_ACTIONS = {
SET_PAYOUT_HISTORY_PERIOD: 'SET_PAYOUT_HISTORY_PERIOD',
};

// TODO: move to config in storagenode/payouts
export const BANDWIDTH_DOWNLOAD_PRICE_PER_TB = 2000;
export const BANDWIDTH_REPAIR_PRICE_PER_TB = 1000;
export const DISK_SPACE_PRICE_PER_TB = 150;

interface PayoutContext {
rootState: {
node: StorageNodeState;
Expand Down Expand Up @@ -83,6 +80,9 @@ export function newPayoutModule(service: PayoutService): StoreModule<PayoutState
state.estimation = estimatedInfo;
state.currentMonthEarnings = estimatedInfo.currentMonth.payout + estimatedInfo.currentMonth.held;
},
[PAYOUT_MUTATIONS.SET_PRICING_MODEL](state: PayoutState, pricing: SatellitePricingModel): void {
state.pricingModel = pricing;
},
[PAYOUT_MUTATIONS.SET_PERIODS](state: PayoutState, periods: PayoutPeriod[]): void {
state.payoutPeriods = periods;
},
Expand Down Expand Up @@ -139,6 +139,11 @@ export function newPayoutModule(service: PayoutService): StoreModule<PayoutState

commit(PAYOUT_MUTATIONS.SET_ESTIMATION, estimatedInfo);
},
[PAYOUT_ACTIONS.GET_PRICING_MODEL]: async function ({ commit }: PayoutContext, satelliteId): Promise<void> {
const pricing = await service.pricingModel(satelliteId);

commit(PAYOUT_MUTATIONS.SET_PRICING_MODEL, pricing);
},
[PAYOUT_ACTIONS.GET_PAYOUT_HISTORY]: async function ({ commit, state }: PayoutContext): Promise<void> {
if (!state.payoutHistoryPeriod) return;

Expand Down
3 changes: 2 additions & 1 deletion web/storagenode/src/app/types/payout.ts
Expand Up @@ -5,7 +5,7 @@ import {
EstimatedPayout,
PayoutPeriod,
SatelliteHeldHistory,
SatellitePayoutForPeriod,
SatellitePayoutForPeriod, SatellitePricingModel,
TotalPayments,
TotalPaystubForPeriod,
} from '@/storagenode/payouts/payouts';
Expand Down Expand Up @@ -36,6 +36,7 @@ export class PayoutState {
public payoutHistoryPeriod: string = '',
public estimation: EstimatedPayout = new EstimatedPayout(),
public payoutHistoryAvailablePeriods: PayoutPeriod[] = [],
public pricingModel: SatellitePricingModel = new SatellitePricingModel(),
) {}
}

Expand Down
6 changes: 6 additions & 0 deletions web/storagenode/src/app/views/PayoutArea.vue
Expand Up @@ -104,6 +104,12 @@ export default class PayoutArea extends Vue {
console.error(error);
}
try {
await this.$store.dispatch(PAYOUT_ACTIONS.GET_PRICING_MODEL, this.$store.state.node.selectedSatellite.id);
} catch (error) {
console.error(error);
}
try {
await this.$store.dispatch(PAYOUT_ACTIONS.GET_TOTAL);
} catch (error) {
Expand Down
26 changes: 25 additions & 1 deletion web/storagenode/src/storagenode/api/payout.ts
Expand Up @@ -9,7 +9,7 @@ import {
Paystub,
PreviousMonthEstimatedPayout,
SatelliteHeldHistory,
SatellitePayoutForPeriod,
SatellitePayoutForPeriod, SatellitePricingModel,
} from '@/storagenode/payouts/payouts';
import { HttpClient } from '@/storagenode/utils/httpClient';

Expand Down Expand Up @@ -214,4 +214,28 @@ export class PayoutHttpApi implements PayoutApi {
data.currentMonthExpectations,
);
}

public async getPricingModel(satelliteId: string): Promise<SatellitePricingModel> {
if (!satelliteId) {
return new SatellitePricingModel();
}

const path = '/api/sno/satellites/'+ satelliteId +'/pricing';

const response = await this.client.get(path);

if (!response.ok) {
throw new Error('can not get satellite pricing information');
}

const data: any = await response.json() || new SatellitePricingModel(); // eslint-disable-line @typescript-eslint/no-explicit-any

return new SatellitePricingModel(
data.satelliteID,
data.egressBandwidth,
data.repairBandwidth,
data.auditBandwidth,
data.diskSpace,
);
}
}
24 changes: 24 additions & 0 deletions web/storagenode/src/storagenode/payouts/payouts.ts
Expand Up @@ -31,6 +31,12 @@ export interface PayoutApi {
*/
getEstimatedPayout(satelliteId: string): Promise<EstimatedPayout>;

/**
* Fetch satellite payout rate.
* @throws Error
*/
getPricingModel(satelliteId: string): Promise<SatellitePricingModel>;

/**
* Fetches payout history for all satellites.
* @throws Error
Expand Down Expand Up @@ -320,3 +326,21 @@ export class SatellitePayoutForPeriod {
return value / PRICE_DIVIDER;
}
}

/**
* Contains satellite payout rates.
*/
export class SatellitePricingModel {
public constructor(
public satelliteID: string = '',
public egressBandwidth: number = 0,
public repairBandwidth: number = 0,
public auditBandwidth: number = 0,
public diskSpace: number = 0,
) {
this.egressBandwidth = this.egressBandwidth / 100;
this.repairBandwidth = this.repairBandwidth / 100;
this.auditBandwidth = this.auditBandwidth / 100;
this.diskSpace = this.diskSpace / 100;
}
}
9 changes: 9 additions & 0 deletions web/storagenode/src/storagenode/payouts/service.ts
Expand Up @@ -9,6 +9,7 @@ import {
Paystub,
SatelliteHeldHistory,
SatellitePayoutForPeriod,
SatellitePricingModel,
TotalPayments,
TotalPaystubForPeriod,
} from '@/storagenode/payouts/payouts';
Expand Down Expand Up @@ -79,4 +80,12 @@ export class PayoutService {
public async estimatedPayout(satelliteId: string): Promise<EstimatedPayout> {
return await this.payouts.getEstimatedPayout(satelliteId);
}

/**
* Gets satellite pricing model.
* @param satelliteId
*/
public async pricingModel(satelliteId: string): Promise<SatellitePricingModel> {
return await this.payouts.getPricingModel(satelliteId);
}
}

0 comments on commit c64f3f3

Please sign in to comment.