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

Transaction failed to sanitize accounts offsets correctly on SPL transfer #453

Open
neochine opened this issue Oct 2, 2024 · 5 comments

Comments

@neochine
Copy link

neochine commented Oct 2, 2024

This is send_solana_to_single(). It works with solder + raw

    def send_solana_to_single(self, from_address, to_address, sol_amount, signer_keypairs=[]):
        if not from_address:
            from_address = self.keypair.pubkey()
        elif from_address and isinstance(from_address, str):
            from_address = Pubkey.from_string(from_address)
        if isinstance(to_address, str):
            to_address = Pubkey.from_string(to_address)
        if not signer_keypairs:
            signer_keypairs = [self.keypair]

        transaction = Transaction()
        sol_transfer_params = SolTransferParams(from_pubkey=from_address, to_pubkey=to_address, lamports=sol_to_lamport(sol_amount))
        sol_transfer_instruction = SolTransfer(sol_transfer_params)
        transaction.add(sol_transfer_instruction)

        transaction = transaction.to_solders()
        blockhash = self.client.retryable_get_latest_blockhash_value()
        transaction.sign(signer_keypairs, blockhash.blockhash)
        signature = self.client.retryable_send_raw_transaction(transaction)
        return signature

I implement similar for spl

def send_spl_to_single(self, from_address, to_address, spl_address, spl_amount, signer_keypairs=[]):
        if not from_address:
            from_address = self.keypair.pubkey()
        elif from_address and isinstance(from_address, str):
            from_address = Pubkey.from_string(from_address)
        if isinstance(to_address, str):
            to_address = Pubkey.from_string(to_address)
        if isinstance(spl_address, str):
            spl_address = Pubkey.from_string(spl_address)
        if not signer_keypairs:
            signer_keypairs = [self.keypair]
        for n in range(3):
            account_info = self.client.retryable_get_account_info_json_parsed(spl_address)
            parsed_account_info = AccountInfoParser(account_info, spl_address)
            if parsed_account_info.valid_account_info:
                break
            elif n == 3:
                raise ValueError(f"Cant find account info for spl token {spl_address}")
        if parsed_account_info.account_type == 'TOKEN_ACCOUNT' and parsed_account_info.owner == Constants.TOKEN_PROGRAM_ID:
            from_address_ata = get_associated_token_address(from_address, spl_address)
            to_address_ata = get_associated_token_address(to_address, spl_address)
            program_id = Constants.TOKEN_PROGRAM_ID
        elif parsed_account_info.account_type == 'TOKEN_ACCOUNT' and parsed_account_info.owner == Constants.TOKEN_2022_PROGRAM_ID:
            from_address_ata = None
            to_address_ata = None
            program_id = Constants.TOKEN_2022_PROGRAM_ID
        else:
            print(f"SPL address is wrong type {spl_address} {parsed_account_info.account_type}")
            raise ValueError

        # Check if to_address ATA exists, if not create them
        all_spl = self.get_all_spl(to_address) #token_client.get_token_accounts_by_owner does same thing
        if not (str(spl_address) in all_spl):
            print(f"Creating associated token account for {to_address}")
            token_manager = TokenManager(self.client)
            token_manager.create_associated_token_account(spl_address, program_id, signer_keypairs, to_address)

        if isinstance(program_id, str):
            program_id = Pubkey.from_string(program_id)
        transaction = Transaction()
        spl_transfer_params = SplTransferCheckedParams(
            program_id=program_id,
            source=from_address_ata,
            mint=spl_address,
            dest=to_address_ata,
            owner=signer_keypairs[0].pubkey(),
            amount=spl_amount,
            decimals=all_spl[str(spl_address)]['decimals'] #checked uses decimals
        )
        spl_transfer_instruction = SplTransferChecked(spl_transfer_params)
transaction.add(spl_transfer_instruction)

        signature = self.client.retryable_send_transaction(transaction, signer_keypairs[0])
        #transaction = transaction.to_solders()
        #blockhash = self.client.retryable_get_latest_blockhash_value()
        #transaction.sign(signer_keypairs, blockhash.blockhash)
        #signature = self.client.retryable_send_raw_transaction(transaction)
        return signature

It works when I use retryable_send_transaction. I am doing it in devnet

>>> sent
SendTransactionResp(
    Signature(
        445CE2jpJEMEfvvKygjyvf9h79CSnQ8N6KyP756jeU73Ve6JZeH9uN37VSKAUCJCbqss6yB15KdQp34TSzgLCPgH,
    ),
)

but when i use retryable_send_raw_transaction

        #signature = self.client.retryable_send_transaction(transaction, signer_keypairs[0])
        transaction = transaction.to_solders()
        blockhash = self.client.retryable_get_latest_blockhash_value()
        transaction.sign(signer_keypairs, blockhash.blockhash)
        signature = self.client.retryable_send_raw_transaction(transaction)
        return signature

I get

results = solana_client.send_raw_transaction(signed_transaction, opts=tx_opts)\n              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File "/usr/local/lib/python3.11/dist-packages/solana/rpc/api.py", line 996, in send_raw_transaction\n    return self._post_send(resp)\n           ^^^^^^^^^^^^^^^^^^^^^\n  File "/usr/local/lib/python3.11/dist-packages/solana/rpc/core.py", line 511, in _post_send\n    raise RPCNoResultException(resp.message)\nsolana.rpc.core.RPCNoResultException: invalid transaction: Transaction failed to sanitize accounts offsets correctly
@neochine
Copy link
Author

neochine commented Oct 2, 2024

My need is able to sign with multiple wallets. transaction.sign(signer_keypairs, blockhash.blockhash) makes it easy to sign whereas send_transaction() not sure, therefore send_raw_transaction being working is needed

@michaelhly
Copy link
Owner

@neochine should be fixed in 0.35.0

@neochine
Copy link
Author

neochine commented Oct 12, 2024

Hey, this issue is still there in solana==0.35.0

python3.11 -m pip freeze | grep solana
solana==0.35.0
raise RPCNoResultException(resp.message)\nsolana.rpc.core.RPCNoResultException: invalid transaction: Transaction failed to sanitize accounts offsets correctly\n')

@michaelhly
Happens when I uncomment send_raw_transaction and comment out send_transaction

239         #signature = self.client.retryable_send_transaction(transaction, signer_keypairs[0])
240         transaction = transaction.to_solders()
241         blockhash = self.client.retryable_get_latest_blockhash_value()
242         transaction.sign(signer_keypairs, blockhash.blockhash)
243         signature = self.client.retryable_send_raw_transaction(transaction)

@michaelhly michaelhly reopened this Oct 12, 2024
@michaelhly
Copy link
Owner

Is your SPL token created from the 2022 token program?

@neochine
Copy link
Author

neochine commented Oct 13, 2024

I havent tested token 2022 program yet. Currently testing '3CqfBkrmRsK3uXZaxktvTeeBkJp4yeFKs4mUi2jhKExz' in devnet. Errors out at send_spl_to_single with send_raw_transaction uncommented but passes with send_raw_transaction commented and send_transaction uncommented

    # TOKEN
    spl_token = '3CqfBkrmRsK3uXZaxktvTeeBkJp4yeFKs4mUi2jhKExz'

    # Check
    all_spl = utils.get_all_spl(side_keypair.pubkey())
    assert (all_spl == {} or all_spl ==  {'3CqfBkrmRsK3uXZaxktvTeeBkJp4yeFKs4mUi2jhKExz': {'balance': 0.0, 'decimals': 6, 'owner': 'Ff1ghUbiWF6jyZC5VphRjyXaSvpmVjh5gWMF9TU9PEdZ', 'owner_ata': 'Hj1sYMgTotU5UNpeyuCcJz1uhWtgZBAua1aMCxq8NsfH','type': 'TOKEN'}})

    # Send 10
    sent = utils.send_spl_to_single(main_keypair.pubkey(), side_keypair.pubkey(), spl_token, 10, signer_keypairs=[main_keypair])
    assert sent != None and sent.value != None
    while True:
        status = devnet_client.retryable_get_signature_statuses([sent.value])
        time.sleep(1)
        if not status.value:
            continue
        if not status.value[0]:
            continue
        confirmation_status = str(status.value[0].confirmation_status)
        if confirmation_status == 'TransactionConfirmationStatus.Finalized':
            break

    # Check
    all_spl = utils.get_all_spl(side_keypair.pubkey())
    assert all_spl[spl_token]['balance'] == 1e-05

@michaelhly michaelhly closed this as not planned Won't fix, can't repro, duplicate, stale Oct 26, 2024
@michaelhly michaelhly reopened this Oct 26, 2024
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

No branches or pull requests

2 participants