fix: fix blockhash cache reuse
This commit is contained in:
		
				
					committed by
					
						
						Michael Vines
					
				
			
			
				
	
			
			
			
						parent
						
							24bb060292
						
					
				
				
					commit
					22a63fe93c
				
			@@ -19,6 +19,8 @@ import type {FeeCalculator} from './fee-calculator';
 | 
			
		||||
import type {Account} from './account';
 | 
			
		||||
import type {TransactionSignature} from './transaction';
 | 
			
		||||
 | 
			
		||||
export const BLOCKHASH_CACHE_TIMEOUT_MS = 30 * 1000;
 | 
			
		||||
 | 
			
		||||
type RpcRequest = (methodName: string, args: Array<any>) => any;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -975,7 +977,7 @@ export class Connection {
 | 
			
		||||
  _commitment: ?Commitment;
 | 
			
		||||
  _blockhashInfo: {
 | 
			
		||||
    recentBlockhash: Blockhash | null,
 | 
			
		||||
    seconds: number,
 | 
			
		||||
    lastFetch: Date,
 | 
			
		||||
    transactionSignatures: Array<string>,
 | 
			
		||||
  };
 | 
			
		||||
  _disableBlockhashCaching: boolean = false;
 | 
			
		||||
@@ -1011,7 +1013,7 @@ export class Connection {
 | 
			
		||||
    this._commitment = commitment;
 | 
			
		||||
    this._blockhashInfo = {
 | 
			
		||||
      recentBlockhash: null,
 | 
			
		||||
      seconds: -1,
 | 
			
		||||
      lastFetch: new Date(0),
 | 
			
		||||
      transactionSignatures: [],
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@@ -1731,10 +1733,10 @@ export class Connection {
 | 
			
		||||
    } else {
 | 
			
		||||
      for (;;) {
 | 
			
		||||
        // Attempt to use a recent blockhash for up to 30 seconds
 | 
			
		||||
        const seconds = new Date().getSeconds();
 | 
			
		||||
        if (
 | 
			
		||||
          this._blockhashInfo.recentBlockhash != null &&
 | 
			
		||||
          this._blockhashInfo.seconds < seconds + 30
 | 
			
		||||
          Date.now() - this._blockhashInfo.lastFetch <
 | 
			
		||||
            BLOCKHASH_CACHE_TIMEOUT_MS
 | 
			
		||||
        ) {
 | 
			
		||||
          transaction.recentBlockhash = this._blockhashInfo.recentBlockhash;
 | 
			
		||||
          transaction.sign(...signers);
 | 
			
		||||
@@ -1748,7 +1750,7 @@ export class Connection {
 | 
			
		||||
          if (!this._blockhashInfo.transactionSignatures.includes(signature)) {
 | 
			
		||||
            this._blockhashInfo.transactionSignatures.push(signature);
 | 
			
		||||
            if (this._disableBlockhashCaching) {
 | 
			
		||||
              this._blockhashInfo.seconds = -1;
 | 
			
		||||
              this._blockhashInfo.lastFetch = new Date(0);
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
@@ -1763,7 +1765,7 @@ export class Connection {
 | 
			
		||||
          if (this._blockhashInfo.recentBlockhash != blockhash) {
 | 
			
		||||
            this._blockhashInfo = {
 | 
			
		||||
              recentBlockhash: blockhash,
 | 
			
		||||
              seconds: new Date().getSeconds(),
 | 
			
		||||
              lastFetch: new Date(),
 | 
			
		||||
              transactionSignatures: [],
 | 
			
		||||
            };
 | 
			
		||||
            break;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,12 +14,11 @@ import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch';
 | 
			
		||||
import {mockGetRecentBlockhash} from './mockrpc/get-recent-blockhash';
 | 
			
		||||
import {url} from './url';
 | 
			
		||||
import {sleep} from '../src/util/sleep';
 | 
			
		||||
import {BLOCKHASH_CACHE_TIMEOUT_MS} from '../src/connection';
 | 
			
		||||
import type {SignatureStatus, TransactionError} from '../src/connection';
 | 
			
		||||
 | 
			
		||||
if (!mockRpcEnabled) {
 | 
			
		||||
  // Testing max commitment level takes around 20s to complete
 | 
			
		||||
  jest.setTimeout(30000);
 | 
			
		||||
}
 | 
			
		||||
// Testing blockhash cache takes around 30s to complete
 | 
			
		||||
jest.setTimeout(40000);
 | 
			
		||||
 | 
			
		||||
const errorMessage = 'Invalid';
 | 
			
		||||
const errorResponse = {
 | 
			
		||||
@@ -46,7 +45,7 @@ const verifySignatureStatus = (
 | 
			
		||||
 | 
			
		||||
  const confirmations = status.confirmations;
 | 
			
		||||
  if (typeof confirmations === 'number') {
 | 
			
		||||
    expect(confirmations).toBeGreaterThan(0);
 | 
			
		||||
    expect(confirmations).toBeGreaterThanOrEqual(0);
 | 
			
		||||
  } else {
 | 
			
		||||
    expect(confirmations).toBeNull();
 | 
			
		||||
  }
 | 
			
		||||
@@ -1630,6 +1629,33 @@ test('transaction', async () => {
 | 
			
		||||
        '0WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
  mockRpc.push([
 | 
			
		||||
    url,
 | 
			
		||||
    {
 | 
			
		||||
      method: 'getSignatureStatuses',
 | 
			
		||||
      params: [
 | 
			
		||||
        [
 | 
			
		||||
          '0WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      error: null,
 | 
			
		||||
      result: {
 | 
			
		||||
        context: {
 | 
			
		||||
          slot: 11,
 | 
			
		||||
        },
 | 
			
		||||
        value: [
 | 
			
		||||
          {
 | 
			
		||||
            slot: 0,
 | 
			
		||||
            confirmations: 0,
 | 
			
		||||
            status: {Ok: null},
 | 
			
		||||
            err: null,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
  mockRpc.push([
 | 
			
		||||
    url,
 | 
			
		||||
    {
 | 
			
		||||
@@ -1646,10 +1672,11 @@ test('transaction', async () => {
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
  await connection.requestAirdrop(
 | 
			
		||||
  const airdropFromSig = await connection.requestAirdrop(
 | 
			
		||||
    accountFrom.publicKey,
 | 
			
		||||
    minimumAmount + 100010,
 | 
			
		||||
  );
 | 
			
		||||
  await connection.confirmTransaction(airdropFromSig, 0);
 | 
			
		||||
  expect(await connection.getBalance(accountFrom.publicKey)).toBe(
 | 
			
		||||
    minimumAmount + 100010,
 | 
			
		||||
  );
 | 
			
		||||
@@ -1658,7 +1685,7 @@ test('transaction', async () => {
 | 
			
		||||
    url,
 | 
			
		||||
    {
 | 
			
		||||
      method: 'requestAirdrop',
 | 
			
		||||
      params: [accountTo.publicKey.toBase58(), minimumAmount + 21],
 | 
			
		||||
      params: [accountTo.publicKey.toBase58(), minimumAmount],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      error: null,
 | 
			
		||||
@@ -1666,6 +1693,33 @@ test('transaction', async () => {
 | 
			
		||||
        '8WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
  mockRpc.push([
 | 
			
		||||
    url,
 | 
			
		||||
    {
 | 
			
		||||
      method: 'getSignatureStatuses',
 | 
			
		||||
      params: [
 | 
			
		||||
        [
 | 
			
		||||
          '8WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      error: null,
 | 
			
		||||
      result: {
 | 
			
		||||
        context: {
 | 
			
		||||
          slot: 11,
 | 
			
		||||
        },
 | 
			
		||||
        value: [
 | 
			
		||||
          {
 | 
			
		||||
            slot: 0,
 | 
			
		||||
            confirmations: 0,
 | 
			
		||||
            status: {Ok: null},
 | 
			
		||||
            err: null,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
  mockRpc.push([
 | 
			
		||||
    url,
 | 
			
		||||
    {
 | 
			
		||||
@@ -1678,14 +1732,16 @@ test('transaction', async () => {
 | 
			
		||||
        context: {
 | 
			
		||||
          slot: 11,
 | 
			
		||||
        },
 | 
			
		||||
        value: minimumAmount + 21,
 | 
			
		||||
        value: minimumAmount,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
  await connection.requestAirdrop(accountTo.publicKey, minimumAmount + 21);
 | 
			
		||||
  expect(await connection.getBalance(accountTo.publicKey)).toBe(
 | 
			
		||||
    minimumAmount + 21,
 | 
			
		||||
  const airdropToSig = await connection.requestAirdrop(
 | 
			
		||||
    accountTo.publicKey,
 | 
			
		||||
    minimumAmount,
 | 
			
		||||
  );
 | 
			
		||||
  await connection.confirmTransaction(airdropToSig, 0);
 | 
			
		||||
  expect(await connection.getBalance(accountTo.publicKey)).toBe(minimumAmount);
 | 
			
		||||
 | 
			
		||||
  mockGetRecentBlockhash('max');
 | 
			
		||||
  mockRpc.push([
 | 
			
		||||
@@ -1696,7 +1752,7 @@ test('transaction', async () => {
 | 
			
		||||
    {
 | 
			
		||||
      error: null,
 | 
			
		||||
      result:
 | 
			
		||||
        '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
 | 
			
		||||
        '1WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
@@ -1717,7 +1773,7 @@ test('transaction', async () => {
 | 
			
		||||
      method: 'getSignatureStatuses',
 | 
			
		||||
      params: [
 | 
			
		||||
        [
 | 
			
		||||
          '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
 | 
			
		||||
          '1WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
@@ -1730,7 +1786,7 @@ test('transaction', async () => {
 | 
			
		||||
        value: [
 | 
			
		||||
          {
 | 
			
		||||
            slot: 0,
 | 
			
		||||
            confirmations: 1,
 | 
			
		||||
            confirmations: 0,
 | 
			
		||||
            status: {Ok: null},
 | 
			
		||||
            err: null,
 | 
			
		||||
          },
 | 
			
		||||
@@ -1739,96 +1795,91 @@ test('transaction', async () => {
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  // Wait for one confirmation
 | 
			
		||||
  const confirmResult = (await connection.confirmTransaction(signature, 1))
 | 
			
		||||
    .value;
 | 
			
		||||
  let confirmResult = (await connection.confirmTransaction(signature, 0)).value;
 | 
			
		||||
  verifySignatureStatus(confirmResult);
 | 
			
		||||
 | 
			
		||||
  mockGetRecentBlockhash('max');
 | 
			
		||||
  mockRpc.push([
 | 
			
		||||
    url,
 | 
			
		||||
    {
 | 
			
		||||
      method: 'getSignatureStatuses',
 | 
			
		||||
      params: [
 | 
			
		||||
        [
 | 
			
		||||
          '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      method: 'sendTransaction',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      error: null,
 | 
			
		||||
      result: {
 | 
			
		||||
        context: {
 | 
			
		||||
          slot: 11,
 | 
			
		||||
        },
 | 
			
		||||
        value: [
 | 
			
		||||
          {
 | 
			
		||||
            slot: 0,
 | 
			
		||||
            confirmations: 11,
 | 
			
		||||
            status: {Ok: null},
 | 
			
		||||
            err: null,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
      result:
 | 
			
		||||
        '2WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  const response = verifySignatureStatus(
 | 
			
		||||
    (await connection.getSignatureStatus(signature)).value,
 | 
			
		||||
  // Send again and ensure that new blockhash is used
 | 
			
		||||
  const lastFetch = Date.now();
 | 
			
		||||
  const transaction2 = SystemProgram.transfer({
 | 
			
		||||
    fromPubkey: accountFrom.publicKey,
 | 
			
		||||
    toPubkey: accountTo.publicKey,
 | 
			
		||||
    lamports: 10,
 | 
			
		||||
  });
 | 
			
		||||
  const signature2 = await connection.sendTransaction(
 | 
			
		||||
    transaction2,
 | 
			
		||||
    [accountFrom],
 | 
			
		||||
    {skipPreflight: true},
 | 
			
		||||
  );
 | 
			
		||||
  expect(signature).not.toEqual(signature2);
 | 
			
		||||
  expect(transaction.recentBlockhash).not.toEqual(transaction2.recentBlockhash);
 | 
			
		||||
 | 
			
		||||
  mockRpc.push([
 | 
			
		||||
    url,
 | 
			
		||||
    {
 | 
			
		||||
      method: 'sendTransaction',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      error: null,
 | 
			
		||||
      result:
 | 
			
		||||
        '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  // Send new transaction and ensure that same blockhash is used
 | 
			
		||||
  const transaction3 = SystemProgram.transfer({
 | 
			
		||||
    fromPubkey: accountFrom.publicKey,
 | 
			
		||||
    toPubkey: accountTo.publicKey,
 | 
			
		||||
    lamports: 9,
 | 
			
		||||
  });
 | 
			
		||||
  await connection.sendTransaction(transaction3, [accountFrom], {
 | 
			
		||||
    skipPreflight: true,
 | 
			
		||||
  });
 | 
			
		||||
  expect(transaction2.recentBlockhash).toEqual(transaction3.recentBlockhash);
 | 
			
		||||
 | 
			
		||||
  // Sleep until blockhash cache times out
 | 
			
		||||
  await sleep(
 | 
			
		||||
    Math.max(0, 1000 + BLOCKHASH_CACHE_TIMEOUT_MS - (Date.now() - lastFetch)),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const unprocessedSignature =
 | 
			
		||||
    '8WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk';
 | 
			
		||||
  mockGetRecentBlockhash('max');
 | 
			
		||||
  mockRpc.push([
 | 
			
		||||
    url,
 | 
			
		||||
    {
 | 
			
		||||
      method: 'getSignatureStatuses',
 | 
			
		||||
      params: [
 | 
			
		||||
        [
 | 
			
		||||
          '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
 | 
			
		||||
          unprocessedSignature,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      method: 'sendTransaction',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      error: null,
 | 
			
		||||
      result: {
 | 
			
		||||
        context: {
 | 
			
		||||
          slot: 11,
 | 
			
		||||
        },
 | 
			
		||||
        value: [
 | 
			
		||||
          {
 | 
			
		||||
            slot: 0,
 | 
			
		||||
            confirmations: 11,
 | 
			
		||||
            status: {Ok: null},
 | 
			
		||||
            err: null,
 | 
			
		||||
          },
 | 
			
		||||
          null,
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
      result:
 | 
			
		||||
        '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  const responses = (
 | 
			
		||||
    await connection.getSignatureStatuses([signature, unprocessedSignature])
 | 
			
		||||
  ).value;
 | 
			
		||||
  expect(responses.length).toEqual(2);
 | 
			
		||||
  expect(responses[1]).toBeNull();
 | 
			
		||||
  const transaction4 = SystemProgram.transfer({
 | 
			
		||||
    fromPubkey: accountFrom.publicKey,
 | 
			
		||||
    toPubkey: accountTo.publicKey,
 | 
			
		||||
    lamports: 13,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const firstResponse = verifySignatureStatus(responses[0]);
 | 
			
		||||
  expect(firstResponse.slot).toBeGreaterThanOrEqual(response.slot);
 | 
			
		||||
  expect(firstResponse.err).toEqual(response.err);
 | 
			
		||||
  await connection.sendTransaction(transaction4, [accountFrom], {
 | 
			
		||||
    skipPreflight: true,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const responseConfirmations = response.confirmations;
 | 
			
		||||
  if (
 | 
			
		||||
    typeof responseConfirmations === 'number' &&
 | 
			
		||||
    typeof firstResponse.confirmations === 'number'
 | 
			
		||||
  ) {
 | 
			
		||||
    expect(firstResponse.confirmations).toBeGreaterThanOrEqual(
 | 
			
		||||
      responseConfirmations,
 | 
			
		||||
    );
 | 
			
		||||
  } else {
 | 
			
		||||
    expect(firstResponse.confirmations).toBeNull();
 | 
			
		||||
  }
 | 
			
		||||
  expect(transaction4.recentBlockhash).not.toEqual(
 | 
			
		||||
    transaction3.recentBlockhash,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  mockRpc.push([
 | 
			
		||||
    url,
 | 
			
		||||
@@ -1864,12 +1915,12 @@ test('transaction', async () => {
 | 
			
		||||
        context: {
 | 
			
		||||
          slot: 11,
 | 
			
		||||
        },
 | 
			
		||||
        value: minimumAmount + 31,
 | 
			
		||||
        value: minimumAmount + 42,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
  expect(await connection.getBalance(accountTo.publicKey)).toBe(
 | 
			
		||||
    minimumAmount + 31,
 | 
			
		||||
    minimumAmount + 42,
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -1883,7 +1934,11 @@ test('multi-instruction transaction', async () => {
 | 
			
		||||
  const accountTo = new Account();
 | 
			
		||||
  const connection = new Connection(url, 'recent');
 | 
			
		||||
 | 
			
		||||
  await connection.requestAirdrop(accountFrom.publicKey, LAMPORTS_PER_SOL);
 | 
			
		||||
  let signature = await connection.requestAirdrop(
 | 
			
		||||
    accountFrom.publicKey,
 | 
			
		||||
    LAMPORTS_PER_SOL,
 | 
			
		||||
  );
 | 
			
		||||
  await connection.confirmTransaction(signature, 0);
 | 
			
		||||
  expect(await connection.getBalance(accountFrom.publicKey)).toBe(
 | 
			
		||||
    LAMPORTS_PER_SOL,
 | 
			
		||||
  );
 | 
			
		||||
@@ -1893,7 +1948,11 @@ test('multi-instruction transaction', async () => {
 | 
			
		||||
    'recent',
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  await connection.requestAirdrop(accountTo.publicKey, minimumAmount + 21);
 | 
			
		||||
  signature = await connection.requestAirdrop(
 | 
			
		||||
    accountTo.publicKey,
 | 
			
		||||
    minimumAmount + 21,
 | 
			
		||||
  );
 | 
			
		||||
  await connection.confirmTransaction(signature, 0);
 | 
			
		||||
  expect(await connection.getBalance(accountTo.publicKey)).toBe(
 | 
			
		||||
    minimumAmount + 21,
 | 
			
		||||
  );
 | 
			
		||||
@@ -1911,7 +1970,7 @@ test('multi-instruction transaction', async () => {
 | 
			
		||||
      lamports: 100,
 | 
			
		||||
    }),
 | 
			
		||||
  );
 | 
			
		||||
  const signature = await connection.sendTransaction(
 | 
			
		||||
  signature = await connection.sendTransaction(
 | 
			
		||||
    transaction,
 | 
			
		||||
    [accountFrom, accountTo],
 | 
			
		||||
    {skipPreflight: true},
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user