1

Allright I need help.. My Web3.js sign and send wouldn't work. I either get BlockHeightExceeded , Blockhash not found, or it just gets stucked in the confirmTransaction..

I have googled and looked around in Stack Exchange, Cookbook etc and implement lastValidBlockheight vs blockheight , retrying tx from etc... none of it works... the code i have works sometimes, but it will not work 80% of the time, either stucked or throw error...

Question I have:

  1. Could this be due to RPC/ So basically do i now need to buy the 500$ RPC package from quicknode. I use the 49$ one currently.

  2. If not what is wrong with my code? This code is from the example in wormhole.. and I have tinkered around with multiple variations.. sendRawTransaction seems to complete (i.e no error thrown).. but the confirmTx will wait forever.. i.e tx doesnt get broadcasted in the network

import {
    ComputeBudgetProgram,
    Connection,
    Keypair,
    SendOptions,
    SendTransactionError,
    TransactionExpiredBlockheightExceededError,
    sendAndConfirmRawTransaction

} from '@solana/web3.js';
import {
    SignAndSendSigner,
    UnsignedTransaction,
} from '@wormhole-foundation/connect-sdk';
import { Network } from '@wormhole-foundation/sdk-base';
import { SolanaPlatform } from '../platform';
import { SolanaChains } from '../types';
import { SolanaUnsignedTransaction } from '../unsignedTransaction';
import { logTxDetails } from './debug';

// Number of blocks to wait before considering a transaction expired
const SOLANA_EXPIRED_BLOCKHEIGHT = 150;

export class SolanaSendSignerV1<
    N extends Network,
    C extends SolanaChains = 'Solana',
> implements SignAndSendSigner<N, C>
{
    constructor(
        private _rpc: Connection,
        private _chain: C,
        private _keypair: Keypair,
        private _debug: boolean = false,
        private _sendOpts?: SendOptions,
        private _priotifyFeeAmount: bigint = 30n,
    ) {
        this._sendOpts = this._sendOpts ?? {
            preflightCommitment: this._rpc.commitment,
            maxRetries: 5
        };
    }

    chain(): C {
        return this._chain;
    }

    address(): string {
        return this._keypair.publicKey.toBase58();
    }

    // Handles retrying a Transaction if the error is deemed to be
    // recoverable. Currently handles:
    // - Transaction expired
    // - Blockhash not found
    // - Not enough bytes (storage account not seen yet)
    private retryable(e: any): boolean {
        // Tx expired, set a new block hash and retry
        console.log("ERROR MESSAGE", e.message)
        if (e instanceof TransactionExpiredBlockheightExceededError) return true;
        // if (!(e instanceof SendTransactionError)) return false;
        if (e.message.includes("Transaction simulation failed:")) return true;
        if (e.message.includes("block height exceeded")) return true;
        if (e.message.includes('Blockhash not found')) return true;
        // Besides tx expiry, only handle SendTransactionError
        // if (!(e instanceof SendTransactionError)) return false;
        if (e instanceof SendTransactionError) {
            console.log("ENTER SendTransactionError block")
            // Only handle simulation errors
            if (e.message.includes('Transaction simulation failed')) return true;
            //  if (e.message.includes('Transaction') && e.message.includes('simulation')) return true;
            if (e.message.includes('Blockhash not found')) return true;

            //    return false;
        };

        // Only handle simulation errors
        // if (!e.message.includes('Transaction simulation failed')) return false;

        // Blockhash not found, similar to expired, resend with new blockhash
        //  if (e.message.includes('Blockhash not found')) return true;

        // Find the log message with the error details
        const loggedErr = e.logs?.find((log: string) =>
            log.startsWith('Program log: Error: '),
        );

        // who knows
        if (!loggedErr) return false;

        // Probably caused by storage account not seen yet
        if (loggedErr.includes('Not enough bytes')) return true;
        if (loggedErr.includes('Unexpected length of input')) return true;
        console.log("RETRY END")
        return false;
    }

    async signAndSend(tx: UnsignedTransaction[]): Promise<any[]> {
        let { blockhash: bh, lastValidBlockHeight: lvbh } = await SolanaPlatform.latestBlock(
            this._rpc,
        );

        console.log("lastValidBlockHeight latestBlock: ", lvbh)
        console.log("BH:  latestBlock ", bh)

        let blockhashResponse = await SolanaPlatform.getLatestBlockhashAndContext(this._rpc);
        let lastValidBlockHeight = blockhashResponse.context.slot + 150;
        let blockhash = blockhashResponse.value.blockhash;

        console.log(`lastValidBlockHeight..  ${lastValidBlockHeight} `)
        console.log(`blockhashResponse.value.blockhash  ${blockhashResponse.value.blockhash} `)
        const txids: string[] = [];

        for (const txn of tx) {
            // console.log("TXN", txn)
            const { description, transaction: { transaction, signers: extraSigners }, } = txn as SolanaUnsignedTransaction<N, C>;
            console.log(`Signing: ${description} for ${this.address()}`);


            if (this._priotifyFeeAmount)
                transaction.add(
                    ComputeBudgetProgram.setComputeUnitPrice({
                        microLamports: this._priotifyFeeAmount,
                    }),
                );

            if (this._debug) logTxDetails(transaction);

            // Try to send the transaction up to 5 times
            const maxRetries = 5;
            for (let i = 0; i < maxRetries; i++) {
                console.log(`TRY TX --  tx..  ${i} .. ${blockhash}`)

                try {
                    transaction.recentBlockhash = blockhash;
                    transaction.partialSign(this._keypair, ...(extraSigners ?? []));
                    // console.log("Send Raw Tx")

                    const txid = await this._rpc.sendRawTransaction(
                        transaction.serialize(),
                        this._sendOpts,
                    );
                    // const txid = await sendAndConfirmRawTransaction(this._rpc,
                    //   transaction.serialize(),
                    //   this._sendOpts,
                    // );

                    console.log(`Sent: ${description} for ${this.address()}`);

                    //  console.log("txids", txids)
                    txids.push(txid);

                    break;
                } catch (e) {
                    // No point checking if retryable if we're on the last retry
                    if (i === maxRetries - 1) throw e;

                    console.log(`Catrch Retrying tx..  ${i} .. ${this.retryable(e)} `)
                    // If it's not retryable, throw
                    if (!this.retryable(e)) throw e;
                    console.log(`NOT THROW..  ${i} .. `)
                    // If it is retryable, we need to grab a new block hash
                    const {
                        blockhash: newBlockhash,
                        lastValidBlockHeight: newBlockHeight,
                    } = await SolanaPlatform.latestBlock(this._rpc);

                    // But we should _not_ submit if the blockhash hasnt expired
                    if (
                        newBlockHeight - lastValidBlockHeight <
                        SOLANA_EXPIRED_BLOCKHEIGHT
                    ) {
                        throw e;
                    }

                    lastValidBlockHeight = newBlockHeight;
                    blockhash = newBlockhash;
                }
            }
        }

        // Wait for finalization
        console.log("CONFIRM TX")


        const results = await Promise.all(
            txids.map((signature) =>

                // this._rpc.getSignatureStatus(signature)

                this._rpc.confirmTransaction(
                    {
                        signature,
                        blockhash,
                        lastValidBlockHeight,
                    },
                    this._rpc.commitment,
                ),



            ),
        );

        console.log("results", results)
        const erroredTxs = results
            .filter((result) => result.value.err)
            .map((result) => result.value.err);

        if (erroredTxs.length > 0)
            throw new Error(`Failed to confirm transaction: ${erroredTxs}`);

        return txids;

    }
}

Allright here is the catch..* BEWARE OF FEES ***

By setting into these high number I was paying like 0.20 and upto 0.60 for some of the transactiosn. I didn't realized until i look at the block explorer.

I run again with default compute limit and 1000000 micro lamport price. It seems to work so far.. Fees are about 0.0012 to create account (for reference only, it will depend on transaction complexity etc)

2 Answers 2

1

As you stated correctly, BlockHeightExceeded and Blockhash not found or it just endlessly looping basically means your transaction is not being included in a block before it expires. Therefore Wei's answer is incorrect, changing your commitment level or timeout won't do anything to change this fact.

Now the reason it is not being included in a block is in 99% of cases priority fees. Solana is a bit more congested these days, meaning that if you aren't setting these and especially if you're accessing state that a lot of other transactions are trying to access as well, your transaction will likely fail. Fortunately, they are quite cheap and setting them is easy, feel free to check out the question below to find out how, which also includes a few other avenues of approach if that doesn't work:

How can I analyze the reason for frequent BlockHeightExceeded errors?

10
  • Thanks for the answer Ahri. Adding this in the code doesnt help either. What would be the correct priority fees to include.
    – mesnia
    Jan 22 at 18:01
  • What microlamport price did you include? I noticed the Solana cookbook link says 1 which probably isn't enough. I suggest you try with e.g. 1000 or 10000 to start, and if those work, use solana.com/docs/rpc/http/getrecentprioritizationfees to dynamically adjust your priority fee.
    – Ahri
    Jan 22 at 18:50
  • I am trying to tune the fees... I tried multiple variations.. i am in this so far.. tx still stucked.. I am going to explore this today and see how it goes... const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ units: 1000000 }); transaction.add(modifyComputeUnits); let addPriorityFee = ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1500000 })
    – mesnia
    Jan 23 at 14:56
  • Is the transaction you're trying to send a Jupiter swap fetched via the API by any chance? In that case you might have a different issue
    – Ahri
    Jan 23 at 14:59
  • No.. its a wormhole connect sdk signing and sending transactions... its weird, when i first started using this without priority fees, it failed 50-% of the time... now it failed 95% of the time making it unusable... Even with fees i dont see much improvement.. it may be a matter of tuning the priority fees... idk...
    – mesnia
    Jan 23 at 15:58
0

I get it working by adjusting the priority fees. This is what I am using (I took arbitrary number and increase slowly):

A) Compute Unit Limit: 1,000,000 (1M) Compute Unit Price: 10,000,000 ( 10 lamport)

I dont know if this is too much.

I did another variation: which seems to work too but failed more with "Blockhash not found error". I don't if it has anything to do with setComputeLimit;

B) Compute Unit Limit: Default (200K) Compute Unit Price: 500,000,000 ( 500 lamport)

I do have a question on this, which I hope to eventually figure out.. Need to understand when I would want to change the default 200k for compute Limit? The product of compute limit ( price is same for A and B above. When would it make sense to change the compute Limit/

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.