Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/lib/alarms/csfloat_trade_pings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {gStore} from '../storage/store';
import {StorageKey} from '../storage/keys';
import {reportBlockedBuyers} from './blocked_users';
import {TradeHistoryStatus} from '../bridge/handlers/trade_history_status';
import {pingFailedTrades} from './failed_trade';
import {pingRollbackTrades} from './rollback';
import {FetchSlimTrades} from '../bridge/handlers/fetch_slim_trades';

Expand Down Expand Up @@ -73,6 +74,7 @@ interface UpdateErrors {
trade_offer_error?: string;
blocked_buyers_error?: string;
rollback_trades_error?: string;
failed_trades_error?: string;
}

async function pingUpdates(pendingTrades: SlimTrade[], steamID?: string | null): Promise<UpdateErrors> {
Expand Down Expand Up @@ -119,5 +121,12 @@ async function pingUpdates(pendingTrades: SlimTrade[], steamID?: string | null):
errors.rollback_trades_error = (e as any).toString();
}

try {
await pingFailedTrades(pendingTrades, tradeHistory);
} catch (e) {
console.error('failed to report failed trades', e);
errors.failed_trades_error = (e as any).toString();
}

return errors;
}
57 changes: 57 additions & 0 deletions src/lib/alarms/failed_trade.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {describe, expect, it} from 'vitest';
import {TradeHistoryStatus} from '../bridge/handlers/trade_history_status';
import {SlimTrade, TradeState} from '../types/float_market';
import {TradeOfferState, TradeStatus} from '../types/steam_constants';
import {findFailedTrades} from './failed_trade';

const steamAssetID = '3899876543210123456';
const otherPartyID = '76561198000000000';

describe('failed Steam trades', () => {
it('matches failed history to a relevant pending trade', () => {
const matches = findFailedTrades([pendingTrade], [tradeHistory]);

expect(matches).toHaveLength(1);
expect(matches[0].csfloatTrade.id).toBe('csfloat-trade-id');
expect(matches[0].steamTrade.status).toBe(TradeStatus.Failed);
});

it('does not match an already recorded failed Steam trade', () => {
const trade = {...pendingTrade, steam_trade_failed_id: 'steam-trade-id'};

expect(findFailedTrades([trade], [tradeHistory])).toEqual([]);
});

it('does not match a trade whose CSFloat offer is accepted', () => {
const trade = {
...pendingTrade,
steam_offer: {...pendingTrade.steam_offer, state: TradeOfferState.Accepted},
};

expect(findFailedTrades([trade], [tradeHistory])).toEqual([]);
});
});

const pendingTrade = {
id: 'csfloat-trade-id',
state: TradeState.PENDING,
seller_id: otherPartyID,
buyer_id: '76561198111111111',
contract: {
item: {
asset_id: steamAssetID,
market_hash_name: 'AK-47 | Redline',
},
},
steam_offer: {state: TradeOfferState.Active},
} as SlimTrade;

const tradeHistory: TradeHistoryStatus = {
trade_id: 'steam-trade-id',
status: TradeStatus.Failed,
other_party_url: `https://steamcommunity.com/profiles/${otherPartyID}`,
other_party_id: otherPartyID,
received_assets: [{asset_id: steamAssetID}],
given_assets: [],
time_init: 123,
};
71 changes: 71 additions & 0 deletions src/lib/alarms/failed_trade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {TradeHistoryStatus} from '../bridge/handlers/trade_history_status';
import {StorageKey} from '../storage/keys';
import {gStore} from '../storage/store';
import {SlimTrade, TradeState} from '../types/float_market';
import {TradeOfferState, TradeStatus} from '../types/steam_constants';
import {reportTradeError} from './error_report';
import {isBackgroundNotaryRollbackEnabled, proveTradesInBackground} from './notary';

interface FailedTradeInfo {
steamTrade: TradeHistoryStatus;
csfloatTrade: SlimTrade;
}

export function findFailedTrades(pendingTrades: SlimTrade[], tradeHistory: TradeHistoryStatus[]): FailedTradeInfo[] {
const results: FailedTradeInfo[] = [];

for (const trade of tradeHistory) {
if (trade.status !== TradeStatus.Failed) {
continue;
}

const receivedIDs = trade.received_assets.map((asset) => asset.asset_id);
const givenIDs = trade.given_assets.map((asset) => asset.asset_id);
const assetIDs = [...receivedIDs, ...givenIDs];

const csfloatTrade = pendingTrades.find(
(pendingTrade) =>
pendingTrade.state === TradeState.PENDING &&
pendingTrade.steam_offer?.state === TradeOfferState.Active &&
Comment thread
Step7750 marked this conversation as resolved.
pendingTrade.steam_trade_failed_id !== trade.trade_id &&
assetIDs.includes(pendingTrade.contract.item.asset_id) &&
(trade.other_party_id === pendingTrade.seller_id || trade.other_party_id === pendingTrade.buyer_id)
);
if (!csfloatTrade) {
continue;
}

results.push({steamTrade: trade, csfloatTrade});
}

return results;
}

export async function pingFailedTrades(pendingTrades: SlimTrade[], tradeHistory: TradeHistoryStatus[]) {
if (!pendingTrades?.length || !tradeHistory?.length) {
return;
}

const failedTrades = findFailedTrades(pendingTrades, tradeHistory);
if (failedTrades.length === 0 || !(await isBackgroundNotaryRollbackEnabled())) {
return;
}

const lastFailure = await gStore.getWithStorage<number>(
chrome.storage.local,
StorageKey.LAST_NOTARY_BG_PROOF_FAILURE
);
if (lastFailure && lastFailure > Date.now() - 60 * 60 * 1000) {
console.log('skipping failed-trade notary proof, last failure was less than 60 minutes ago');
return;
}

try {
await proveTradesInBackground(failedTrades.map((failedTrade) => failedTrade.steamTrade));
console.log(`proved ${failedTrades.length} failed trade(s) via notary`);
} catch (e) {
console.error('failed-trade notary proving failed', e);
await gStore.setWithStorage(chrome.storage.local, StorageKey.LAST_NOTARY_BG_PROOF_FAILURE, Date.now());
reportTradeError(failedTrades[0].csfloatTrade.id, `background extension failed-trade notary failed: ${e}`);
}
}
4 changes: 3 additions & 1 deletion src/lib/alarms/notary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ export async function isBackgroundNotaryRollbackEnabled(): Promise<boolean> {
}
}

function buildProveRequest(trades: TradeHistoryStatus[]): NotaryProveRequest {
export function buildProveRequest(trades: TradeHistoryStatus[]): NotaryProveRequest {
if (trades.length === 1) {
return {
type: ProofType.TRADE_HISTORY,
max_trades: 5,
start_after_time: trades[0].time_init,
navigating_back: true,
include_failed: true,
};
}

Expand All @@ -39,6 +40,7 @@ function buildProveRequest(trades: TradeHistoryStatus[]): NotaryProveRequest {
max_trades: MAX_TRADE_HISTORY_FETCH,
start_after_time: oldestTimeInit,
navigating_back: true,
include_failed: true,
};
}

Expand Down
5 changes: 3 additions & 2 deletions src/lib/alarms/trade_history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export async function pingTradeHistory(

async function getTradeHistory(): Promise<{history: TradeHistoryStatus[]; type: TradeHistoryType}> {
try {
const history = await getTradeHistoryFromAPI(MAX_TRADE_HISTORY_FETCH);
const history = await getTradeHistoryFromAPI(MAX_TRADE_HISTORY_FETCH, {includeFailed: true});
if (history.length > 0) {
// Hedge in case this endpoint gets killed, only return if there are results, fallback to HTML parser
return {history, type: TradeHistoryType.API};
Expand Down Expand Up @@ -146,8 +146,9 @@ export async function getTradeHistoryFromAPI(
(e) =>
e.status === TradeStatus.Committed ||
e.status === TradeStatus.Complete ||
e.status === TradeStatus.Failed ||
e.status === TradeStatus.TradeProtectionRollback
) // Only report exchanged/completed trades or trade-protection rollbacks
) // Only report exchanged/completed trades or failed/rolled-back trades
.filter((e) => !e.time_escrow_end || new Date(parseInt(e.time_escrow_end) * 1000).getTime() < Date.now())
.map((e) => {
return {
Expand Down
1 change: 1 addition & 0 deletions src/lib/types/float_market.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export interface Trade {
state: TradeState;
trade_url: string;
steam_offer: SteamOffer;
steam_trade_failed_id?: string;
wait_for_cancel_ping?: boolean;
seller_blocked_buyer_at?: string;
buyer_blocked_seller_at?: string;
Expand Down