fix(explorer): remove old chain api (#2244)
* fix(explorer): remove old chain api
This commit is contained in:
parent
7ba90904d8
commit
13d4c463cd
@ -1,4 +1,3 @@
|
||||
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
|
||||
NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
|
||||
|
@ -1,5 +1,4 @@
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
|
||||
NX_TENDERMINT_URL=http://localhost:26617
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
|
@ -1,5 +1,4 @@
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
|
||||
NX_TENDERMINT_URL=https://n04.d.vega.xyz/tm
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://n04.d.vega.xyz/tm/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
|
||||
|
@ -1,5 +1,4 @@
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
|
||||
NX_TENDERMINT_URL=https://be.explorer.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.explorer.vega.xyz
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
|
||||
|
@ -1,4 +1,3 @@
|
||||
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
|
||||
NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
|
||||
|
@ -1,5 +1,4 @@
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_BLOCK_EXPLORER=https://be.testnet.vega.xyz/rest
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.be.testnet.vega.xyz/
|
||||
|
@ -1,5 +1,4 @@
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
|
||||
NX_TENDERMINT_URL=http://localhost:26607/
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26607/websocket
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
|
@ -45,21 +45,20 @@ yarn nx run explorer:serve --env={env} # e.g. stagnet3
|
||||
|
||||
There are a few different configuration options offered for this app:
|
||||
|
||||
| **Flag** | **Purpose** |
|
||||
| -------------------------------- | --------------------------------------------------------------- | --- |
|
||||
| `NX_CHAIN_EXPLORER_URL` | The URL of the chain explorer service for decoding transactions |
|
||||
| `NX_TENDERMINT_URL` | The Tendermint REST URL for the Vega consesus engine |
|
||||
| `NX_TENDERMINT_WEBSOCKET_URL` | The Tendermint Websocket URL for the Vega consensus engine |
|
||||
| `NX_VEGA_ENV` | The name of the currently connected vega environment | |
|
||||
| `NX_EXPLORER_ASSETS` | Enable the assets page for the explorer |
|
||||
| `NX_EXPLORER_GENESIS` | Enable the genesis page for the explorer |
|
||||
| `NX_EXPLORER_GOVERNANCE` | Enable the governance page for the explorer |
|
||||
| `NX_EXPLORER_MARKETS` | Enable the markets page for the explorer |
|
||||
| `NX_EXPLORER_ORACLES` | Enable the oracles page for the explorer |
|
||||
| `NX_EXPLORER_TXS_LIST` | Enable the transactions list page for the explorer |
|
||||
| `NX_EXPLORER_NETWORK_PARAMETERS` | Enable the network parameters page for the explorer |
|
||||
| `NX_EXPLORER_PARTIES` | Enable the parties page for the explorer |
|
||||
| `NX_EXPLORER_VALIDATORS` | Enable the validators page for the explorer |
|
||||
| **Flag** | **Purpose** |
|
||||
| -------------------------------- | ---------------------------------------------------------- | --- |
|
||||
| `NX_TENDERMINT_URL` | The Tendermint REST URL for the Vega consesus engine |
|
||||
| `NX_TENDERMINT_WEBSOCKET_URL` | The Tendermint Websocket URL for the Vega consensus engine |
|
||||
| `NX_VEGA_ENV` | The name of the currently connected vega environment | |
|
||||
| `NX_EXPLORER_ASSETS` | Enable the assets page for the explorer |
|
||||
| `NX_EXPLORER_GENESIS` | Enable the genesis page for the explorer |
|
||||
| `NX_EXPLORER_GOVERNANCE` | Enable the governance page for the explorer |
|
||||
| `NX_EXPLORER_MARKETS` | Enable the markets page for the explorer |
|
||||
| `NX_EXPLORER_ORACLES` | Enable the oracles page for the explorer |
|
||||
| `NX_EXPLORER_TXS_LIST` | Enable the transactions list page for the explorer |
|
||||
| `NX_EXPLORER_NETWORK_PARAMETERS` | Enable the network parameters page for the explorer |
|
||||
| `NX_EXPLORER_PARTIES` | Enable the parties page for the explorer |
|
||||
| `NX_EXPLORER_VALIDATORS` | Enable the validators page for the explorer |
|
||||
|
||||
## Testing
|
||||
|
||||
|
@ -1,37 +0,0 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import React from 'react';
|
||||
import type { TendermintBlockchainResponse } from '../../routes/blocks/tendermint-blockchain-response';
|
||||
import { BlockData } from '../blocks';
|
||||
import { TxsPerBlock } from './txs-per-block';
|
||||
|
||||
interface TxsProps {
|
||||
data: TendermintBlockchainResponse | undefined;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const BlockTxsData = ({ data, className }: TxsProps) => {
|
||||
if (!data?.result) {
|
||||
// Data for the block has already been fetched at this point, so no errors
|
||||
// or loading to deal with. This is specifically the case
|
||||
// where the data object is not undefined, but lacks a result.
|
||||
return <div className={className}>{t('No data')}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<ul
|
||||
aria-label={t(
|
||||
`Showing ${data.result?.block_metas.length} most recently loaded blocks and transactions`
|
||||
)}
|
||||
className={className}
|
||||
>
|
||||
{data.result?.block_metas?.map((block, index) => {
|
||||
return (
|
||||
<li key={index} data-testid="transactions-list">
|
||||
<BlockData block={block} className="mb-12" />
|
||||
<TxsPerBlock blockHeight={block.header.height} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
};
|
@ -1,5 +1,4 @@
|
||||
export { TxList } from './tx-list';
|
||||
export { BlockTxsData } from './block-txs-data';
|
||||
export { TxOrderType } from './tx-order-type';
|
||||
export { TxsInfiniteList } from './txs-infinite-list';
|
||||
export { TxsInfiniteListItem } from './txs-infinite-list-item';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import type { ChainExplorerTxResponse } from '../../routes/types/chain-explorer-response';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import { DATA_SOURCES } from '../../config';
|
||||
import { RenderFetched } from '../render-fetched';
|
||||
@ -6,32 +5,28 @@ import { TruncatedLink } from '../truncate/truncated-link';
|
||||
import { TxOrderType } from './tx-order-type';
|
||||
import { Table, TableRow, TableCell } from '../table';
|
||||
import { t, useFetch } from '@vegaprotocol/react-helpers';
|
||||
import type { BlockExplorerTransactions } from '../../routes/types/block-explorer-response';
|
||||
|
||||
interface TxsPerBlockProps {
|
||||
blockHeight: string | undefined;
|
||||
blockHeight: string;
|
||||
txCount: number;
|
||||
}
|
||||
|
||||
const truncateLength = 14;
|
||||
|
||||
export const TxsPerBlock = ({ blockHeight }: TxsPerBlockProps) => {
|
||||
export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
|
||||
// TODO after https://github.com/vegaprotocol/vega/pull/6958/files is merged and deployed, use filter
|
||||
// by block height instead
|
||||
const {
|
||||
state: { data: decodedBlockData, loading, error },
|
||||
} = useFetch<ChainExplorerTxResponse[]>(DATA_SOURCES.chainExplorerUrl, {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
block_height: parseInt(blockHeight!),
|
||||
node_url: `${DATA_SOURCES.tendermintUrl}/`,
|
||||
}),
|
||||
});
|
||||
|
||||
state: { data, loading, error },
|
||||
} = useFetch<BlockExplorerTransactions>(
|
||||
`${
|
||||
DATA_SOURCES.blockExplorerUrl
|
||||
}/transactions/before=${blockHeight.toString()}.0&limit=${txCount}`
|
||||
);
|
||||
return (
|
||||
<RenderFetched error={error} loading={loading} className="text-body-large">
|
||||
{decodedBlockData && decodedBlockData.length > 0 ? (
|
||||
{data && data.transactions.length > 0 ? (
|
||||
<div className="overflow-x-auto whitespace-nowrap mb-28">
|
||||
<Table>
|
||||
<thead>
|
||||
@ -42,33 +37,31 @@ export const TxsPerBlock = ({ blockHeight }: TxsPerBlockProps) => {
|
||||
</TableRow>
|
||||
</thead>
|
||||
<tbody>
|
||||
{decodedBlockData.map(({ TxHash, PubKey, Type, Command }) => {
|
||||
const chainEvent = JSON.parse(Command || '');
|
||||
|
||||
{data.transactions.map(({ hash, submitter, type, command }) => {
|
||||
return (
|
||||
<TableRow
|
||||
modifier="bordered"
|
||||
key={TxHash}
|
||||
key={hash}
|
||||
data-testid="transaction-row"
|
||||
>
|
||||
<TableCell modifier="bordered" className="pr-12">
|
||||
<TruncatedLink
|
||||
to={`/${Routes.TX}/${TxHash}`}
|
||||
text={TxHash}
|
||||
to={`/${Routes.TX}/${hash}`}
|
||||
text={hash}
|
||||
startChars={truncateLength}
|
||||
endChars={truncateLength}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell modifier="bordered" className="pr-12">
|
||||
<TruncatedLink
|
||||
to={`/${Routes.PARTIES}/${PubKey}`}
|
||||
text={PubKey}
|
||||
to={`/${Routes.PARTIES}/${submitter}`}
|
||||
text={submitter}
|
||||
startChars={truncateLength}
|
||||
endChars={truncateLength}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell modifier="bordered">
|
||||
<TxOrderType orderType={Type} chainEvent={chainEvent} />
|
||||
<TxOrderType orderType={type} chainEvent={command} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
|
@ -12,7 +12,6 @@ export const ENV = {
|
||||
// Environment
|
||||
dsn: windowOrDefault('NX_EXPLORER_SENTRY_DSN'),
|
||||
dataSources: {
|
||||
chainExplorerUrl: windowOrDefault('NX_CHAIN_EXPLORER_URL'),
|
||||
blockExplorerUrl: windowOrDefault('NX_BLOCK_EXPLORER'),
|
||||
tendermintUrl: windowOrDefault('NX_TENDERMINT_URL'),
|
||||
tendermintWebsocketUrl: windowOrDefault('NX_TENDERMINT_WEBSOCKET_URL'),
|
||||
|
@ -29,11 +29,13 @@ export const getTxsDataUrl = ({ limit, filters }: IGetTxsDataUrl) => {
|
||||
url.searchParams.append('limit', limit);
|
||||
}
|
||||
|
||||
// Hacky fix for param as array
|
||||
let urlAsString = url.toString();
|
||||
if (filters) {
|
||||
url.searchParams.append('filters', filters);
|
||||
urlAsString += '&' + filters;
|
||||
}
|
||||
|
||||
return url;
|
||||
return urlAsString;
|
||||
};
|
||||
|
||||
export const useTxsData = ({ limit, filters }: IUseTxsData) => {
|
||||
@ -49,7 +51,7 @@ export const useTxsData = ({ limit, filters }: IUseTxsData) => {
|
||||
const {
|
||||
state: { data, error, loading },
|
||||
refetch,
|
||||
} = useFetch<BlockExplorerTransactions>(url.href, {}, false);
|
||||
} = useFetch<BlockExplorerTransactions>(url, {}, false);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.transactions?.length) {
|
||||
|
@ -88,7 +88,10 @@ const Block = () => {
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
{blockData.result.block.data.txs.length > 0 ? (
|
||||
<TxsPerBlock blockHeight={block} />
|
||||
<TxsPerBlock
|
||||
blockHeight={blockData.result.block.header.height}
|
||||
txCount={blockData.result.block.data.txs.length}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
|
@ -8,7 +8,7 @@ export type ExplorerPartyAssetsQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerPartyAssetsQuery = { __typename?: 'Query', partiesConnection?: { __typename?: 'PartyConnection', edges: Array<{ __typename?: 'PartyEdge', node: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } } } | null> | null } | null, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string } | null } } | null> | null } | null } }> } | null };
|
||||
export type ExplorerPartyAssetsQuery = { __typename?: 'Query', partiesConnection?: { __typename?: 'PartyConnection', edges: Array<{ __typename?: 'PartyEdge', node: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } } } | null> | null } | null, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } | null } } | null> | null } | null } }> } | null };
|
||||
|
||||
|
||||
export const ExplorerPartyAssetsDocument = gql`
|
||||
@ -51,6 +51,11 @@ export const ExplorerPartyAssetsDocument = gql`
|
||||
balance
|
||||
market {
|
||||
id
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,39 @@
|
||||
import {
|
||||
t,
|
||||
useFetch,
|
||||
addDecimalsFormatNumber,
|
||||
useScreenDimensions,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { SubHeading } from '../../../components/sub-heading';
|
||||
import { SyntaxHighlighter, AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { Panel } from '../../../components/panel';
|
||||
import { InfoPanel } from '../../../components/info-panel';
|
||||
import { toNonHex } from '../../../components/search/detect-search';
|
||||
import { DATA_SOURCES } from '../../../config';
|
||||
import type { TendermintSearchTransactionResponse } from '../tendermint-transaction-response';
|
||||
import { useTxsData } from '../../../hooks/use-txs-data';
|
||||
import { TxsInfiniteList } from '../../../components/txs';
|
||||
import { PageHeader } from '../../../components/page-header';
|
||||
import { useExplorerPartyAssetsQuery } from './__generated__/party-assets';
|
||||
import type { Schema } from '@vegaprotocol/types';
|
||||
import get from 'lodash/get';
|
||||
|
||||
const accountTypeString: Record<Schema.AccountType, string> = {
|
||||
ACCOUNT_TYPE_BOND: 'Bond',
|
||||
ACCOUNT_TYPE_EXTERNAL: 'External',
|
||||
ACCOUNT_TYPE_FEES_INFRASTRUCTURE: 'Fees (Infrastructure)',
|
||||
ACCOUNT_TYPE_FEES_LIQUIDITY: 'Fees (Liquidity)',
|
||||
ACCOUNT_TYPE_FEES_MAKER: 'Fees (Maker)',
|
||||
ACCOUNT_TYPE_GENERAL: 'General',
|
||||
ACCOUNT_TYPE_GLOBAL_INSURANCE: 'Global Insurance Pool',
|
||||
ACCOUNT_TYPE_GLOBAL_REWARD: 'Global Reward Pool',
|
||||
ACCOUNT_TYPE_INSURANCE: 'Insurance',
|
||||
ACCOUNT_TYPE_MARGIN: 'Margin',
|
||||
ACCOUNT_TYPE_PENDING_TRANSFERS: 'Pending Transfers',
|
||||
ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: 'Reward - LP Fees received',
|
||||
ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES: 'Reward - Maker fees paid',
|
||||
ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES: 'Reward - Maker fees received',
|
||||
ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS: 'Reward - Market proposers',
|
||||
ACCOUNT_TYPE_SETTLEMENT: 'Settlement',
|
||||
};
|
||||
|
||||
const Party = () => {
|
||||
const { party } = useParams<{ party: string }>();
|
||||
@ -29,12 +46,6 @@ const Party = () => {
|
||||
filters,
|
||||
});
|
||||
|
||||
const {
|
||||
state: { data: partyData },
|
||||
} = useFetch<TendermintSearchTransactionResponse>(
|
||||
`${DATA_SOURCES.tendermintUrl}/tx_search?query="tx.submitter='${partyId}'"`
|
||||
);
|
||||
|
||||
const { data } = useExplorerPartyAssetsQuery({
|
||||
// Don't cache data for this query, party information can move quite quickly
|
||||
fetchPolicy: 'network-only',
|
||||
@ -65,9 +76,13 @@ const Party = () => {
|
||||
if (!account || !account.asset) {
|
||||
return '';
|
||||
}
|
||||
const m = get(account, 'market.tradableInstrument.instrument.name');
|
||||
|
||||
return (
|
||||
<InfoPanel title={account.asset.name} id={account.asset.id}>
|
||||
<InfoPanel
|
||||
title={account.asset.name}
|
||||
id={`${accountTypeString[account.type]} ${m ? ` - ${m}` : ''}`}
|
||||
>
|
||||
<section>
|
||||
<dl className="flex gap-2 flex-wrap">
|
||||
<dt className="text-zinc-500 dark:text-zinc-400 text-md">
|
||||
@ -125,32 +140,16 @@ const Party = () => {
|
||||
{accounts}
|
||||
<SubHeading>{t('Staking')}</SubHeading>
|
||||
{staking}
|
||||
<SubHeading>{t('JSON')}</SubHeading>
|
||||
<section data-testid="parties-json">
|
||||
<SyntaxHighlighter data={data} />
|
||||
</section>
|
||||
<AsyncRenderer
|
||||
loading={loading as boolean}
|
||||
error={error}
|
||||
data={txsData}
|
||||
>
|
||||
<SubHeading>{t('Transactions')}</SubHeading>
|
||||
<TxsInfiniteList
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
areTxsLoading={loading}
|
||||
txs={txsData}
|
||||
loadMoreTxs={loadTxs}
|
||||
error={error}
|
||||
className="mb-28"
|
||||
/>
|
||||
</AsyncRenderer>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{partyData ? (
|
||||
<>
|
||||
<SubHeading>{t('Tendermint Data')}</SubHeading>
|
||||
<SyntaxHighlighter data={partyData} />
|
||||
<SubHeading>{t('Transactions')}</SubHeading>
|
||||
<TxsInfiniteList
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
areTxsLoading={loading}
|
||||
txs={txsData}
|
||||
loadMoreTxs={loadTxs}
|
||||
error={error}
|
||||
className="mb-28"
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</section>
|
||||
|
@ -37,6 +37,11 @@ query ExplorerPartyAssets($partyId: ID!) {
|
||||
balance
|
||||
market {
|
||||
id
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +0,0 @@
|
||||
export interface ChainExplorerTxResponse {
|
||||
Type: string;
|
||||
Command: string;
|
||||
Sig: string;
|
||||
PubKey: string;
|
||||
Nonce: number;
|
||||
TxHash: string;
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export interface TendermintErrorResponse {
|
||||
id: number;
|
||||
jsonrpc: string;
|
||||
error: string;
|
||||
}
|
Loading…
Reference in New Issue
Block a user