{storagenode/console,web/storagenode}: fetch pricing model from storagenode 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 https://github.com/storj/storj-private/issues/245

Change-Id: Iac7669e3e6eb690bbaad6e64bbbe42dfd775f078
This commit is contained in:
Clement Sam 2023-05-09 09:36:17 +00:00 committed by Clement Sam
parent d80d674863
commit c64f3f3132
12 changed files with 150 additions and 15 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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 {

View File

@ -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) {

View File

@ -146,9 +146,6 @@ 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 {
@ -156,7 +153,12 @@ import {
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';
@ -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.
*/
@ -375,7 +384,7 @@ 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,
@ -383,7 +392,7 @@ export default class EstimationArea extends Vue {
new EstimationTableRow(
'Repair & Audit',
'Egress',
`$${BANDWIDTH_REPAIR_PRICE_PER_TB / 100} / TB`,
`$${this.pricing.repairBandwidth} / TB`,
'--',
Size.toBase10String(estimatedPayout.egressRepairAudit),
estimatedPayout.egressRepairAuditPayout,
@ -391,7 +400,7 @@ export default class EstimationArea extends Vue {
new EstimationTableRow(
'Disk Average Month',
'Storage',
`$${DISK_SPACE_PRICE_PER_TB / 100} / TBm`,
`$${this.pricing.diskSpace} / TBm`,
Size.toBase10String(estimatedPayout.diskSpace) + 'm',
'--',
estimatedPayout.diskSpacePayout,

View File

@ -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',
@ -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',
@ -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;
@ -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;
},
@ -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;

View File

@ -5,7 +5,7 @@ import {
EstimatedPayout,
PayoutPeriod,
SatelliteHeldHistory,
SatellitePayoutForPeriod,
SatellitePayoutForPeriod, SatellitePricingModel,
TotalPayments,
TotalPaystubForPeriod,
} from '@/storagenode/payouts/payouts';
@ -36,6 +36,7 @@ export class PayoutState {
public payoutHistoryPeriod: string = '',
public estimation: EstimatedPayout = new EstimatedPayout(),
public payoutHistoryAvailablePeriods: PayoutPeriod[] = [],
public pricingModel: SatellitePricingModel = new SatellitePricingModel(),
) {}
}

View File

@ -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) {

View File

@ -9,7 +9,7 @@ import {
Paystub,
PreviousMonthEstimatedPayout,
SatelliteHeldHistory,
SatellitePayoutForPeriod,
SatellitePayoutForPeriod, SatellitePricingModel,
} from '@/storagenode/payouts/payouts';
import { HttpClient } from '@/storagenode/utils/httpClient';
@ -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,
);
}
}

View File

@ -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
@ -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;
}
}

View File

@ -9,6 +9,7 @@ import {
Paystub,
SatelliteHeldHistory,
SatellitePayoutForPeriod,
SatellitePricingModel,
TotalPayments,
TotalPaystubForPeriod,
} from '@/storagenode/payouts/payouts';
@ -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);
}
}