fix: repair web3 connection tests by making fewer assumptions about the existence of particular blocks (#23921)

* fix: repair 'get confirmed signatures for address' test in web3.js

* fix: repair 'get signatures for address' test in web3.js

* fix: repair 'get parsed confirmed transactions' test in web3.js

* fix: repair 'get transaction' test in web3.js

* fix: repair 'get confirmed transaction' test in web3.js

* fix: repair 'get block' test in web3.js

* fix: repair 'get confirmed block' test in web3.js

* fix: repair 'get block signatures' test in web3.js

* fix: repair 'get block time' test in web3.js

Co-authored-by: steveluscher <github@steveluscher.com>
This commit is contained in:
Steven Luscher
2022-03-24 22:21:14 -07:00
committed by GitHub
parent c8c3c4359f
commit 412d9be445
2 changed files with 9295 additions and 316 deletions

View File

@ -24,7 +24,10 @@ import {DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND} from '../src/timing';
import {MOCK_PORT, url} from './url'; import {MOCK_PORT, url} from './url';
import { import {
BLOCKHASH_CACHE_TIMEOUT_MS, BLOCKHASH_CACHE_TIMEOUT_MS,
BlockResponse,
BlockSignatures,
Commitment, Commitment,
ConfirmedBlock,
EpochInfo, EpochInfo,
InflationGovernor, InflationGovernor,
SlotInfo, SlotInfo,
@ -71,7 +74,7 @@ const verifySignatureStatus = (
return status; return status;
}; };
describe('Connection', () => { describe('Connection', function () {
let connection: Connection; let connection: Connection;
beforeEach(() => { beforeEach(() => {
connection = new Connection(url); connection = new Connection(url);
@ -999,12 +1002,17 @@ describe('Connection', () => {
}, },
}); });
// Find a block that has a transaction, usually Block 1 // Find a block that has a transaction.
let slot = 0; await mockRpcResponse({
method: 'getFirstAvailableBlock',
params: [],
value: 1,
});
let slot = await connection.getFirstAvailableBlock();
let address: PublicKey | undefined; let address: PublicKey | undefined;
let expectedSignature: string | undefined; let expectedSignature: string | undefined;
while (!address || !expectedSignature) { while (!address || !expectedSignature) {
slot++;
const block = await connection.getConfirmedBlock(slot); const block = await connection.getConfirmedBlock(slot);
if (block.transactions.length > 0) { if (block.transactions.length > 0) {
const {signature, publicKey} = const {signature, publicKey} =
@ -1012,8 +1020,10 @@ describe('Connection', () => {
if (signature) { if (signature) {
address = publicKey; address = publicKey;
expectedSignature = bs58.encode(signature); expectedSignature = bs58.encode(signature);
break;
} }
} }
slot++;
} }
// getConfirmedSignaturesForAddress tests... // getConfirmedSignaturesForAddress tests...
@ -1174,12 +1184,17 @@ describe('Connection', () => {
}, },
}); });
// Find a block that has a transaction, usually Block 1 // Find a block that has a transaction.
let slot = 0; await mockRpcResponse({
method: 'getFirstAvailableBlock',
params: [],
value: 1,
});
let slot = await connection.getFirstAvailableBlock();
let address: PublicKey | undefined; let address: PublicKey | undefined;
let expectedSignature: string | undefined; let expectedSignature: string | undefined;
while (!address || !expectedSignature) { while (!address || !expectedSignature) {
slot++;
const block = await connection.getConfirmedBlock(slot); const block = await connection.getConfirmedBlock(slot);
if (block.transactions.length > 0) { if (block.transactions.length > 0) {
const {signature, publicKey} = const {signature, publicKey} =
@ -1187,8 +1202,10 @@ describe('Connection', () => {
if (signature) { if (signature) {
address = publicKey; address = publicKey;
expectedSignature = bs58.encode(signature); expectedSignature = bs58.encode(signature);
break;
} }
} }
slot++;
} }
// getSignaturesForAddress tests... // getSignaturesForAddress tests...
@ -1278,17 +1295,24 @@ describe('Connection', () => {
}, },
}); });
// Find a block that has a transaction, usually Block 1 // Find a block that has a transaction.
let slot = 0; await mockRpcResponse({
method: 'getFirstAvailableBlock',
params: [],
value: 1,
});
let slot = await connection.getFirstAvailableBlock();
let confirmedTransaction: string | undefined; let confirmedTransaction: string | undefined;
while (!confirmedTransaction) { while (!confirmedTransaction) {
slot++;
const block = await connection.getConfirmedBlock(slot); const block = await connection.getConfirmedBlock(slot);
for (const tx of block.transactions) { for (const tx of block.transactions) {
if (tx.transaction.signature) { if (tx.transaction.signature) {
confirmedTransaction = bs58.encode(tx.transaction.signature); confirmedTransaction = bs58.encode(tx.transaction.signature);
break;
} }
} }
slot++;
} }
await mockRpcBatchResponse({ await mockRpcBatchResponse({
@ -1551,15 +1575,22 @@ describe('Connection', () => {
}, },
}); });
// Find a block that has a transaction, usually Block 1 // Find a block that has a transaction.
let slot = 0; await mockRpcResponse({
method: 'getFirstAvailableBlock',
params: [],
value: 1,
});
let slot = await connection.getFirstAvailableBlock();
let transaction: string | undefined; let transaction: string | undefined;
while (!transaction) { while (!transaction) {
slot++;
const block = await connection.getBlock(slot); const block = await connection.getBlock(slot);
if (block && block.transactions.length > 0) { if (block && block.transactions.length > 0) {
transaction = block.transactions[0].transaction.signatures[0]; transaction = block.transactions[0].transaction.signatures[0];
continue;
} }
slot++;
} }
await mockRpcResponse({ await mockRpcResponse({
@ -1694,17 +1725,24 @@ describe('Connection', () => {
}, },
}); });
// Find a block that has a transaction, usually Block 1 // Find a block that has a transaction.
let slot = 0; await mockRpcResponse({
method: 'getFirstAvailableBlock',
params: [],
value: 1,
});
let slot = await connection.getFirstAvailableBlock();
let confirmedTransaction: string | undefined; let confirmedTransaction: string | undefined;
while (!confirmedTransaction) { while (!confirmedTransaction) {
slot++;
const block = await connection.getConfirmedBlock(slot); const block = await connection.getConfirmedBlock(slot);
for (const tx of block.transactions) { for (const tx of block.transactions) {
if (tx.transaction.signature) { if (tx.transaction.signature) {
confirmedTransaction = bs58.encode(tx.transaction.signature); confirmedTransaction = bs58.encode(tx.transaction.signature);
break;
} }
} }
slot++;
} }
await mockRpcResponse({ await mockRpcResponse({
@ -1885,7 +1923,8 @@ describe('Connection', () => {
}); });
} }
it('get block', async () => { describe('get block', function () {
beforeEach(async function () {
await mockRpcResponse({ await mockRpcResponse({
method: 'getSlot', method: 'getSlot',
params: [], params: [],
@ -1895,7 +1934,9 @@ describe('Connection', () => {
while ((await connection.getSlot()) <= 0) { while ((await connection.getSlot()) <= 0) {
continue; continue;
} }
});
it('gets the genesis block', async function () {
await mockRpcResponse({ await mockRpcResponse({
method: 'getBlock', method: 'getBlock',
params: [0], params: [0],
@ -1909,19 +1950,46 @@ describe('Connection', () => {
}, },
}); });
// Block 0 never has any transactions in test validator let maybeBlock0: BlockResponse | null;
const block0 = await connection.getBlock(0); try {
if (!block0) { maybeBlock0 = await connection.getBlock(0);
expect(block0).not.to.be.null; } catch (e) {
return; if (process.env.TEST_LIVE) {
console.warn(
'WARNING: We ran no assertions about the genesis block because block 0 ' +
'could not be found. See https://github.com/solana-labs/solana/issues/23853.',
);
this.skip();
} else {
throw e;
} }
}
expect(maybeBlock0).not.to.be.null;
const block0 = maybeBlock0!;
// Block 0 never has any transactions in test validator
const blockhash0 = block0.blockhash; const blockhash0 = block0.blockhash;
expect(block0.transactions).to.have.length(0); expect(block0.transactions).to.have.length(0);
expect(blockhash0).not.to.be.null; expect(blockhash0).not.to.be.null;
expect(block0.previousBlockhash).not.to.be.null; expect(block0.previousBlockhash).not.to.be.null;
expect(block0.parentSlot).to.eq(0); expect(block0.parentSlot).to.eq(0);
});
it('gets a block having a parent', async function () {
// Mock parent of block with transaction.
await mockRpcResponse({
method: 'getBlock',
params: [0],
value: {
blockHeight: 0,
blockTime: 1614281964,
blockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo',
previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo',
parentSlot: 0,
transactions: [],
},
});
// Mock block with transaction.
await mockRpcResponse({ await mockRpcResponse({
method: 'getBlock', method: 'getBlock',
params: [1], params: [1],
@ -1961,7 +2029,8 @@ describe('Connection', () => {
programIdIndex: 4, programIdIndex: 4,
}, },
], ],
recentBlockhash: 'GeyAFFRY3WGpmam2hbgrKw4rbU2RKzfVLm5QLSeZwTZE', recentBlockhash:
'GeyAFFRY3WGpmam2hbgrKw4rbU2RKzfVLm5QLSeZwTZE',
}, },
signatures: [ signatures: [
'w2Zeq8YkpyB463DttvfzARD7k9ZxGEwbsEw4boEK7jDp3pfoxZbTdLFSsEPhzXhpCcjGi2kHtHFobgX49MMhbWt', 'w2Zeq8YkpyB463DttvfzARD7k9ZxGEwbsEw4boEK7jDp3pfoxZbTdLFSsEPhzXhpCcjGi2kHtHFobgX49MMhbWt',
@ -1973,26 +2042,38 @@ describe('Connection', () => {
}, },
}); });
// Find a block that has a transaction // Find a block that has a transaction *and* a parent.
// Compare data with parent await mockRpcResponse({
let x = 1; method: 'getFirstAvailableBlock',
while (x < 10) { params: [],
const block1 = await connection.getBlock(x); value: 0,
if (block1 && block1.transactions.length >= 1) { });
if (block1.parentSlot == 0) { let candidateSlot = (await connection.getFirstAvailableBlock()) + 1;
expect(block1.previousBlockhash).to.eq(blockhash0); let result:
} else { | {
const parentBlock = await connection.getBlock(block1.parentSlot); blockWithTransaction: BlockResponse;
parentBlock: BlockResponse;
}
| undefined;
while (!result) {
const candidateBlock = await connection.getBlock(candidateSlot);
if (candidateBlock && candidateBlock.transactions.length) {
const parentBlock = await connection.getBlock(candidateSlot - 1);
if (parentBlock) { if (parentBlock) {
expect(block1.previousBlockhash).to.eq(parentBlock.blockhash); result = {blockWithTransaction: candidateBlock, parentBlock};
}
}
expect(block1.blockhash).not.to.be.null;
expect(block1.transactions[0].transaction).not.to.be.null;
break; break;
} }
x++;
} }
candidateSlot++;
}
// Compare data with parent
expect(result.blockWithTransaction.previousBlockhash).to.eq(
result.parentBlock.blockhash,
);
expect(result.blockWithTransaction.blockhash).not.to.be.null;
expect(result.blockWithTransaction.transactions[0].transaction).not.to.be
.null;
await mockRpcResponse({ await mockRpcResponse({
method: 'getBlock', method: 'getBlock',
@ -2007,8 +2088,10 @@ describe('Connection', () => {
`Block not available for slot ${Number.MAX_SAFE_INTEGER}`, `Block not available for slot ${Number.MAX_SAFE_INTEGER}`,
); );
}); });
});
it('get confirmed block', async () => { describe('get confirmed block', function () {
beforeEach(async function () {
await mockRpcResponse({ await mockRpcResponse({
method: 'getSlot', method: 'getSlot',
params: [], params: [],
@ -2018,11 +2101,14 @@ describe('Connection', () => {
while ((await connection.getSlot()) <= 0) { while ((await connection.getSlot()) <= 0) {
continue; continue;
} }
});
it('gets the genesis block', async function () {
await mockRpcResponse({ await mockRpcResponse({
method: 'getConfirmedBlock', method: 'getConfirmedBlock',
params: [0], params: [0],
value: { value: {
blockHeight: 0,
blockTime: 1614281964, blockTime: 1614281964,
blockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', blockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo',
previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo',
@ -2031,14 +2117,44 @@ describe('Connection', () => {
}, },
}); });
let block0: ConfirmedBlock;
try {
block0 = await connection.getConfirmedBlock(0);
} catch (e) {
if (process.env.TEST_LIVE) {
console.warn(
'WARNING: We ran no assertions about the genesis block because block 0 ' +
'could not be found. See https://github.com/solana-labs/solana/issues/23853.',
);
this.skip();
} else {
throw e;
}
}
// Block 0 never has any transactions in test validator // Block 0 never has any transactions in test validator
const block0 = await connection.getConfirmedBlock(0);
const blockhash0 = block0.blockhash; const blockhash0 = block0.blockhash;
expect(block0.transactions).to.have.length(0); expect(block0.transactions).to.have.length(0);
expect(blockhash0).not.to.be.null; expect(blockhash0).not.to.be.null;
expect(block0.previousBlockhash).not.to.be.null; expect(block0.previousBlockhash).not.to.be.null;
expect(block0.parentSlot).to.eq(0); expect(block0.parentSlot).to.eq(0);
});
it('gets a block having a parent', async function () {
// Mock parent of block with transaction.
await mockRpcResponse({
method: 'getConfirmedBlock',
params: [0],
value: {
blockHeight: 0,
blockTime: 1614281964,
blockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo',
previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo',
parentSlot: 0,
transactions: [],
},
});
// Mock block with transaction.
await mockRpcResponse({ await mockRpcResponse({
method: 'getConfirmedBlock', method: 'getConfirmedBlock',
params: [1], params: [1],
@ -2077,7 +2193,8 @@ describe('Connection', () => {
programIdIndex: 4, programIdIndex: 4,
}, },
], ],
recentBlockhash: 'GeyAFFRY3WGpmam2hbgrKw4rbU2RKzfVLm5QLSeZwTZE', recentBlockhash:
'GeyAFFRY3WGpmam2hbgrKw4rbU2RKzfVLm5QLSeZwTZE',
}, },
signatures: [ signatures: [
'w2Zeq8YkpyB463DttvfzARD7k9ZxGEwbsEw4boEK7jDp3pfoxZbTdLFSsEPhzXhpCcjGi2kHtHFobgX49MMhbWt', 'w2Zeq8YkpyB463DttvfzARD7k9ZxGEwbsEw4boEK7jDp3pfoxZbTdLFSsEPhzXhpCcjGi2kHtHFobgX49MMhbWt',
@ -2089,26 +2206,42 @@ describe('Connection', () => {
}, },
}); });
// Find a block that has a transaction // Find a block that has a transaction *and* a parent.
// Compare data with parent await mockRpcResponse({
let x = 1; method: 'getFirstAvailableBlock',
while (x < 10) { params: [],
const block1 = await connection.getConfirmedBlock(x); value: 0,
if (block1.transactions.length >= 1) { });
if (block1.parentSlot == 0) { let candidateSlot = (await connection.getFirstAvailableBlock()) + 1;
expect(block1.previousBlockhash).to.eq(blockhash0); let result:
} else { | {
const parentBlock = await connection.getBlock(block1.parentSlot); blockWithTransaction: ConfirmedBlock;
parentBlock: ConfirmedBlock;
}
| undefined;
while (!result) {
const candidateBlock = await connection.getConfirmedBlock(
candidateSlot,
);
if (candidateBlock && candidateBlock.transactions.length) {
const parentBlock = await connection.getConfirmedBlock(
candidateSlot - 1,
);
if (parentBlock) { if (parentBlock) {
expect(block1.previousBlockhash).to.eq(parentBlock.blockhash); result = {blockWithTransaction: candidateBlock, parentBlock};
}
}
expect(block1.blockhash).not.to.be.null;
expect(block1.transactions[0].transaction).not.to.be.null;
break; break;
} }
x++;
} }
candidateSlot++;
}
// Compare data with parent
expect(result.blockWithTransaction.previousBlockhash).to.eq(
result.parentBlock.blockhash,
);
expect(result.blockWithTransaction.blockhash).not.to.be.null;
expect(result.blockWithTransaction.transactions[0].transaction).not.to.be
.null;
await mockRpcResponse({ await mockRpcResponse({
method: 'getConfirmedBlock', method: 'getConfirmedBlock',
@ -2123,6 +2256,7 @@ describe('Connection', () => {
`Block not available for slot ${Number.MAX_SAFE_INTEGER}`, `Block not available for slot ${Number.MAX_SAFE_INTEGER}`,
); );
}); });
});
it('get blocks between two slots', async () => { it('get blocks between two slots', async () => {
await mockRpcResponse({ await mockRpcResponse({
@ -2172,7 +2306,8 @@ describe('Connection', () => {
expect(blocks).to.contain(latestSlot); expect(blocks).to.contain(latestSlot);
}); });
it('get block signatures', async () => { describe('get block signatures', function () {
beforeEach(async function () {
await mockRpcResponse({ await mockRpcResponse({
method: 'getSlot', method: 'getSlot',
params: [], params: [],
@ -2182,7 +2317,9 @@ describe('Connection', () => {
while ((await connection.getSlot()) <= 0) { while ((await connection.getSlot()) <= 0) {
continue; continue;
} }
});
it('gets the genesis block', async function () {
await mockRpcResponse({ await mockRpcResponse({
method: 'getBlock', method: 'getBlock',
params: [ params: [
@ -2202,15 +2339,51 @@ describe('Connection', () => {
}, },
}); });
let block0: BlockSignatures;
try {
block0 = await connection.getBlockSignatures(0);
} catch (e) {
if (process.env.TEST_LIVE) {
console.warn(
'WARNING: We ran no assertions about the genesis block because block 0 ' +
'could not be found. See https://github.com/solana-labs/solana/issues/23853.',
);
this.skip();
} else {
throw e;
}
}
// Block 0 never has any transactions in test validator // Block 0 never has any transactions in test validator
const block0 = await connection.getBlockSignatures(0);
const blockhash0 = block0.blockhash; const blockhash0 = block0.blockhash;
expect(block0.signatures).to.have.length(0); expect(block0.signatures).to.have.length(0);
expect(blockhash0).not.to.be.null; expect(blockhash0).not.to.be.null;
expect(block0.previousBlockhash).not.to.be.null; expect(block0.previousBlockhash).not.to.be.null;
expect(block0.parentSlot).to.eq(0); expect(block0.parentSlot).to.eq(0);
expect(block0).to.not.have.property('rewards'); expect(block0).to.not.have.property('rewards');
});
it('gets a block having a parent', async function () {
// Mock parent of block with transaction.
await mockRpcResponse({
method: 'getBlock',
params: [
0,
{
transactionDetails: 'signatures',
rewards: false,
},
],
value: {
blockHeight: 0,
blockTime: 1614281964,
blockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo',
previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo',
parentSlot: 0,
signatures: [],
},
});
// Mock block with transaction.
await mockRpcResponse({ await mockRpcResponse({
method: 'getBlock', method: 'getBlock',
params: [ params: [
@ -2233,27 +2406,42 @@ describe('Connection', () => {
}, },
}); });
// Find a block that has a transaction // Find a block that has a transaction *and* a parent.
// Compare data with parent await mockRpcResponse({
let x = 1; method: 'getFirstAvailableBlock',
while (x < 10) { params: [],
const block1 = await connection.getBlockSignatures(x); value: 0,
if (block1.signatures.length >= 1) { });
if (block1.parentSlot == 0) { let candidateSlot = (await connection.getFirstAvailableBlock()) + 1;
expect(block1.previousBlockhash).to.eq(blockhash0); let result:
} else { | {
const parentBlock = await connection.getBlock(block1.parentSlot); blockWithTransaction: BlockSignatures;
parentBlock: BlockSignatures;
}
| undefined;
while (!result) {
const candidateBlock = await connection.getBlockSignatures(
candidateSlot,
);
if (candidateBlock && candidateBlock.signatures.length) {
const parentBlock = await connection.getBlockSignatures(
candidateSlot - 1,
);
if (parentBlock) { if (parentBlock) {
expect(block1.previousBlockhash).to.eq(parentBlock.blockhash); result = {blockWithTransaction: candidateBlock, parentBlock};
}
}
expect(block1.blockhash).not.to.be.null;
expect(block1.signatures[0]).not.to.be.null;
expect(block1).to.not.have.property('rewards');
break; break;
} }
x++;
} }
candidateSlot++;
}
// Compare data with parent
expect(result.blockWithTransaction.previousBlockhash).to.eq(
result.parentBlock.blockhash,
);
expect(result.blockWithTransaction.blockhash).not.to.be.null;
expect(result.blockWithTransaction.signatures[0]).not.to.be.null;
expect(result.blockWithTransaction).to.not.have.property('rewards');
await mockRpcResponse({ await mockRpcResponse({
method: 'getBlock', method: 'getBlock',
@ -2268,6 +2456,7 @@ describe('Connection', () => {
`Block not available for slot ${Number.MAX_SAFE_INTEGER}`, `Block not available for slot ${Number.MAX_SAFE_INTEGER}`,
); );
}); });
});
it('get recent blockhash', async () => { it('get recent blockhash', async () => {
const commitments: Commitment[] = ['processed', 'confirmed', 'finalized']; const commitments: Commitment[] = ['processed', 'confirmed', 'finalized'];
@ -2355,7 +2544,13 @@ describe('Connection', () => {
value: 10000, value: 10000,
}); });
const blockTime = await connection.getBlockTime(1); await mockRpcResponse({
method: 'getFirstAvailableBlock',
params: [],
value: 1,
});
const slot = await connection.getFirstAvailableBlock();
const blockTime = await connection.getBlockTime(slot);
if (blockTime === null) { if (blockTime === null) {
expect(blockTime).not.to.be.null; expect(blockTime).not.to.be.null;
} else { } else {

8784
web3.js/yarn.lock Normal file

File diff suppressed because it is too large Load Diff