Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Several refactoring suggestions for the @solana/[email protected] implementation #10

Open
wants to merge 18 commits into
base: newWeb3Js
Choose a base branch
from

Conversation

steveluscher
Copy link

No description provided.

Copy link
Author

@steveluscher steveluscher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @WilfredAlmeida! Some refactoring suggestions for the new ping thing client.

"axios": "^1.7.3",
"bs58": "^6.0.0",
"dotenv": "^16.4.5",
"js-yaml": "^4.1.0",
"undici": "^6.19.5"
},
"overrides": {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This presumes that you're using npm.

Comment on lines +3 to +11
export const rpc = createSolanaRpc(
process.env.RPC_ENDPOINT,
);
export const rpcSubscriptions = createSolanaRpcSubscriptions_UNSTABLE(
process.env.WS_ENDPOINT,
);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, it's good to share these as widely as possible so that they can maximally coalesce requests and subscriptions together.

Comment on lines 70 to 77
for await (const notification of recentBlockhashesNotifications) {
latestBlockhash = getLatestBlockhashFromNotification(
notification,
differenceBetweenSlotHeightAndBlockHeight
);
yield latestBlockhash;
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You essentially want a read-through cache that has the freshest blockhash at all times. Watching the recent blockhashes sysvar is a fun way to do this, so long as you don't mind the sort of gross computation of lastValidBlockHeight.

@@ -100,15 +91,13 @@ async function pingThing() {
}
try {
try {
const latestBlockhash = await getLatestBlockhash();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull the latest blockhash out of the read-through cache here and jam it into the transaction message.

Comment on lines 131 to 138
[slotSent] = await Promise.all([
getNextSlot(),
mSendTransaction(transactionSignedWithFeePayer, {
commitment: "confirmed",
maxRetries: 0n,
skipPreflight: true,
}),
]);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fetch the sent slot as near to the actual send as possible. I don't know if you want the slot on the leading edge (ie. you don't mind the actual send being one slot later) or the trailing edge (ie. you don't mind it being earlier). I fetch them in parallel here, and the getNextSlot() function really does wait for the next slot (it's not a read-through cache like the blockhash iterator).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Know what, I updated this PR because I think I see what's going on here. You want the sentSlot to live outside this loop and be computed:

  1. Once.
  2. Before even trying to send.

@@ -68,7 +67,7 @@ async function pingThing() {
const signer = await createSignerFromKeyPair(USER_KEYPAIR);
const BASE_TRANSACTION_MESSAGE = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayer(signer.address, tx),
(tx) => setTransactionMessageFeePayerSigner(signer, tx),
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signers API works by jamming TransactionSigner objects into your message…

Comment on lines +102 to +122
const transactionSignedWithFeePayer =
await signTransactionMessageWithSigners(transactionMessage);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…which now knows how to self-sign!

Comment on lines 57 to 65
const mSendTransaction = sendTransactionWithoutConfirmingFactory({
rpc,
});

const getRecentSignatureConfirmationPromise =
createRecentSignatureConfirmationPromiseFactory({
rpc,
rpcSubscriptions,
});
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, we want to create senders/confirmers once and then use them for the lifetime of the rpc/rpcSubscriptions.

Comment on lines 57 to 65
const mSendAndConfirmTransaction = sendAndConfirmTransactionFactory({
rpc,
rpcSubscriptions,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This factory does all of the things you were doing with the pieces.

mSendAndConfirmTransaction(transactionSignedWithFeePayer, {
commitment: "confirmed",
}),
timeout(TX_RETRY_INTERVAL * txSendAttempts),
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can still achieve the timeout behavior by racing the all-up send-and-confirmer with a timeout promise.

@steveluscher steveluscher marked this pull request as ready for review August 8, 2024 18:21
Comment on lines 124 to 125
maxRetries: 0n,
skipPreflight: true,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accidentally dropped these.

@steveluscher steveluscher force-pushed the web-3-new-suggestions branch 2 times, most recently from bcfbe15 to 625773e Compare August 8, 2024 21:16
@steveluscher steveluscher marked this pull request as draft August 25, 2024 05:23
@steveluscher
Copy link
Author

Converting this to draft. Some parts of this refactoring are bad, actually. I'll update this later.

@steveluscher steveluscher force-pushed the web-3-new-suggestions branch 3 times, most recently from 3893ad5 to ef28626 Compare October 6, 2024 05:57
@steveluscher steveluscher marked this pull request as ready for review October 6, 2024 05:58
Comment on lines +107 to +109
const latestUnusedBlockhash = latestBlockhashes.find(
({ blockhash }) => !usedBlockhashes.has(blockhash),
);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This handles the extremely rare case that you're:

  1. Sending an identical transaction
  2. Asked for a latest blockhash quickly enough to get the same one as last time (eg. maybe the blockhash fetcher has fallen behind)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't the usedBlockhashes list increase the memory consumption over time given the ever running nature of the ping thing?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An identical transaction should be fine since, and, if transactions need not be identical then every time a blockhash is read from the watcher, it can have a value like lastServedBlockhash and if both the previously served blockhash and current one are same then handle it by waiting or polling for a new blockhash

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't the usedBlockhashes list increase the memory consumption over time given the ever running nature of the ping thing?

Nope! The code above copies over only the used hashes that are still valid. The maximum size of usedBlockhashes is the number of valid blockhashes.

Comment on lines 137 to 157
slotSent = await getNextSlot();
const sendRetryInterval = setInterval(() => {
sendAbortController.abort();
console.log(`Tx not confirmed after ${TX_RETRY_INTERVAL * txSendAttempts++}ms, resending`);
sendTransaction();
}, TX_RETRY_INTERVAL);
txStart = Date.now();
sendTransaction();
await mConfirmRecentSignature({
abortSignal: AbortSignal.any([]),
commitment: COMMITMENT_LEVEL,
signature,
});
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so I get what your original implementation was angling at now @WilfredAlmeida. You want to:

  1. Get the slot.
  2. Fire off the transaction
  3. Start one confirmer
  4. Retry the transaction send if you haven't gotten confirmation in x milliseconds.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's correct

@steveluscher steveluscher force-pushed the web-3-new-suggestions branch 5 times, most recently from b11a72b to f9da5a7 Compare October 12, 2024 06:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants