-
Notifications
You must be signed in to change notification settings - Fork 2
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
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this 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": { |
There was a problem hiding this comment.
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
.
export const rpc = createSolanaRpc( | ||
process.env.RPC_ENDPOINT, | ||
); | ||
export const rpcSubscriptions = createSolanaRpcSubscriptions_UNSTABLE( | ||
process.env.WS_ENDPOINT, | ||
); |
There was a problem hiding this comment.
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.
utils/blockhash.mjs
Outdated
for await (const notification of recentBlockhashesNotifications) { | ||
latestBlockhash = getLatestBlockhashFromNotification( | ||
notification, | ||
differenceBetweenSlotHeightAndBlockHeight | ||
); | ||
yield latestBlockhash; | ||
} |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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.
ping-thing-client.mjs
Outdated
[slotSent] = await Promise.all([ | ||
getNextSlot(), | ||
mSendTransaction(transactionSignedWithFeePayer, { | ||
commitment: "confirmed", | ||
maxRetries: 0n, | ||
skipPreflight: true, | ||
}), | ||
]); |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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:
- Once.
- 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), |
There was a problem hiding this comment.
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…
const transactionSignedWithFeePayer = | ||
await signTransactionMessageWithSigners(transactionMessage); |
There was a problem hiding this comment.
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!
ping-thing-client.mjs
Outdated
const mSendTransaction = sendTransactionWithoutConfirmingFactory({ | ||
rpc, | ||
}); | ||
|
||
const getRecentSignatureConfirmationPromise = | ||
createRecentSignatureConfirmationPromiseFactory({ | ||
rpc, | ||
rpcSubscriptions, | ||
}); |
There was a problem hiding this comment.
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
.
ping-thing-client.mjs
Outdated
const mSendAndConfirmTransaction = sendAndConfirmTransactionFactory({ | ||
rpc, | ||
rpcSubscriptions, |
There was a problem hiding this comment.
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.
ping-thing-client.mjs
Outdated
mSendAndConfirmTransaction(transactionSignedWithFeePayer, { | ||
commitment: "confirmed", | ||
}), | ||
timeout(TX_RETRY_INTERVAL * txSendAttempts), |
There was a problem hiding this comment.
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.
949da54
to
a727a51
Compare
ping-thing-client.mjs
Outdated
maxRetries: 0n, | ||
skipPreflight: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accidentally dropped these.
bcfbe15
to
625773e
Compare
Converting this to draft. Some parts of this refactoring are bad, actually. I'll update this later. |
3893ad5
to
ef28626
Compare
ef28626
to
5047a05
Compare
const latestUnusedBlockhash = latestBlockhashes.find( | ||
({ blockhash }) => !usedBlockhashes.has(blockhash), | ||
); |
There was a problem hiding this comment.
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:
- Sending an identical transaction
- Asked for a latest blockhash quickly enough to get the same one as last time (eg. maybe the blockhash fetcher has fallen behind)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
ba8d65f
to
d4fed25
Compare
ping-thing-client.mjs
Outdated
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, | ||
}); |
There was a problem hiding this comment.
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:
- Get the slot.
- Fire off the transaction
- Start one confirmer
- Retry the transaction send if you haven't gotten confirmation in x milliseconds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that's correct
b11a72b
to
f9da5a7
Compare
f9da5a7
to
56509bd
Compare
… `Signer#publicKey`
… of the leading edge (firstShredReceived)
2087094
to
4acf40b
Compare
No description provided.