Пример #1
0
    def deserialize(raw_transaction: bytes) -> Transaction:
        """Parse a wire transaction into a Transaction object.

        Example:

            >>> raw_transaction = bytes.fromhex(
            ...     '019d53be8af3a7c30f86c1092d2c3ea61d270c0cfa2'
            ...     '75a23ba504674c8fbbb724827b23b42dc8e08019e23'
            ...     '120f1b6f40f9799355ce54185b4415be37ca2cee6e0'
            ...     'e010001034cb5abf6ad79fbf5abbccafcc269d85cd2'
            ...     '651ed4b885b5869f241aedf0a5ba290000000000000'
            ...     '0000000000000000000000000000000000000000000'
            ...     '0000000200000000000000000000000000000000000'
            ...     '0000000000000000000000000000000000000000000'
            ...     '0000000000000000000000000000000000000000000'
            ...     '000000301020200010c02000000e803000000000000'
            ... )
            >>> type(Transaction.deserialize(raw_transaction))
            <class 'solana.transaction.Transaction'>

        Returns:
            The deserialized transaction.
        """
        signatures = []
        signature_count, offset = shortvec.decode_length(raw_transaction)
        for _ in range(signature_count):
            signatures.append(
                b58encode(raw_transaction[offset:offset +
                                          SIG_LENGTH]))  # noqa: E203
            offset += SIG_LENGTH
        return Transaction.populate(
            Message.deserialize(raw_transaction[offset:]), signatures)
Пример #2
0
    def to_base58(self) -> bytes:
        """Public key in base58.

        Returns:
            The base58-encoded public key.
        """
        return b58encode(bytes(self))
Пример #3
0
    def _get_signature_statuses_args(
        signatures: List[Union[str, bytes]], search_transaction_history: bool
    ) -> Tuple[types.RPCMethod, List[str], Dict[str, bool]]:
        base58_sigs: List[str] = []
        for sig in signatures:
            if isinstance(sig, str):
                base58_sigs.append(
                    b58encode(b58decode(sig.encode("ascii"))).decode("utf-8"))
            else:
                base58_sigs.append(b58encode(sig).decode("utf-8"))

        return (
            types.RPCMethod("getSignatureStatuses"),
            base58_sigs,
            {
                "searchTransactionHistory": search_transaction_history
            },
        )
Пример #4
0
    def populate(message: Message, signatures: List[bytes]) -> Transaction:
        """Populate Transaction object from message and signatures.

        Example:

            >>> raw_message = bytes.fromhex(
            ...     '0200030500000000000000000000000000000000000000000000'
            ...     '0000000000000000000100000000000000000000000000000000'
            ...     '0000000000000000000000000000000200000000000000000000'
            ...     '0000000000000000000000000000000000000000000300000000'
            ...     '0000000000000000000000000000000000000000000000000000'
            ...     '0004000000000000000000000000000000000000000000000000'
            ...     '0000000000000005c49ae77603782054f17a9decea43b444eba0'
            ...     'edb12c6f1d31c6e0e4a84bf052eb010403010203050909090909'
            ... )
            >>> from based58 import b58encode
            >>> from solana.message import Message
            >>> msg = Message.deserialize(raw_message)
            >>> signatures = [b58encode(bytes([1] * SIG_LENGTH)), b58encode(bytes([2] * SIG_LENGTH))]
            >>> type(Transaction.populate(msg, signatures))
            <class 'solana.transaction.Transaction'>

        Returns:
            The populated transaction.
        """
        transaction = Transaction(recent_blockhash=message.recent_blockhash)

        for idx, sig in enumerate(signatures):
            signature = None if sig == b58encode(
                Transaction.__DEFAULT_SIG) else b58decode(sig)
            transaction.signatures.append(
                SigPubkeyPair(pubkey=message.account_keys[idx],
                              signature=signature))

        for instr in message.instructions:
            account_metas: List[AccountMeta] = []
            for acc_idx in instr.accounts:
                pubkey = message.account_keys[acc_idx]
                is_signer = any((pubkey == sigkeypair.pubkey
                                 for sigkeypair in transaction.signatures))
                account_metas.append(
                    AccountMeta(
                        pubkey=pubkey,
                        is_signer=is_signer,
                        is_writable=message.is_account_writable(acc_idx)))
            program_id = message.account_keys[instr.program_id_index]
            transaction.instructions.append(
                TransactionInstruction(keys=account_metas,
                                       program_id=program_id,
                                       data=b58decode(instr.data)))

        return transaction
Пример #5
0
def test_populate(stubbed_blockhash):
    """Test populating transaction with a message and two signatures."""
    account_keys = [str(PublicKey(i + 1)) for i in range(5)]
    msg = Message(
        MessageArgs(
            account_keys=account_keys,
            header=MessageHeader(num_readonly_signed_accounts=0,
                                 num_readonly_unsigned_accounts=3,
                                 num_required_signatures=2),
            instructions=[
                CompiledInstruction(accounts=[1, 2, 3],
                                    data=b58encode(bytes([9] * 5)),
                                    program_id_index=4)
            ],
            recent_blockhash=stubbed_blockhash,
        ))
    signatures = [
        b58encode(bytes([1] * txlib.SIG_LENGTH)),
        b58encode(bytes([2] * txlib.SIG_LENGTH))
    ]
    transaction = txlib.Transaction.populate(msg, signatures)
    assert len(transaction.instructions) == len(msg.instructions)
    assert len(transaction.signatures) == len(signatures)
    assert transaction.recent_blockhash == msg.recent_blockhash
Пример #6
0
    def deserialize(raw_message: bytes) -> Message:  # pylint: disable=too-many-locals
        """Deserialize raw message bytes.

        Example:

            >>> raw_message = bytes.fromhex(
            ...     '0200030500000000000000000000000000000000000000000000'
            ...     '0000000000000000000100000000000000000000000000000000'
            ...     '0000000000000000000000000000000200000000000000000000'
            ...     '0000000000000000000000000000000000000000000300000000'
            ...     '0000000000000000000000000000000000000000000000000000'
            ...     '0004000000000000000000000000000000000000000000000000'
            ...     '0000000000000005c49ae77603782054f17a9decea43b444eba0'
            ...     'edb12c6f1d31c6e0e4a84bf052eb010403010203050909090909'
            ... )
            >>> type(Message.deserialize(raw_message))
            <class 'solana.message.Message'>

        Returns:
            The deserialized message.
        """
        HEADER_OFFSET = 3  # pylint: disable=invalid-name
        if len(raw_message) < HEADER_OFFSET:
            raise ValueError(
                "byte representation of message is missing message header")
        num_required_signatures = raw_message[0]
        num_readonly_signed_accounts = raw_message[1]
        num_readonly_unsigned_accounts = raw_message[2]
        header = MessageHeader(
            num_required_signatures=num_required_signatures,
            num_readonly_signed_accounts=num_readonly_signed_accounts,
            num_readonly_unsigned_accounts=num_readonly_unsigned_accounts,
        )
        raw_message = raw_message[HEADER_OFFSET:]

        account_keys = []
        accounts_length, accounts_offset = shortvec.decode_length(raw_message)
        for _ in range(accounts_length):
            key_bytes = raw_message[accounts_offset:accounts_offset +
                                    PublicKey.LENGTH]  # noqa: E203
            account_keys.append(str(PublicKey(key_bytes)))
            accounts_offset += PublicKey.LENGTH
        raw_message = raw_message[accounts_offset:]

        recent_blockhash = Blockhash(
            b58encode(raw_message[:PublicKey.LENGTH]).decode("utf-8"))
        raw_message = raw_message[PublicKey.LENGTH:]  # noqa: E203

        instructions = []
        instruction_count, offset = shortvec.decode_length(raw_message)
        raw_message = raw_message[offset:]
        for _ in range(instruction_count):
            program_id_index = raw_message[0]
            raw_message = raw_message[1:]

            accounts_length, offset = shortvec.decode_length(raw_message)
            raw_message = raw_message[offset:]
            accounts = raw_message[:accounts_length]
            raw_message = raw_message[accounts_length:]

            data_length, offset = shortvec.decode_length(raw_message)
            raw_message = raw_message[offset:]
            data = b58encode(raw_message[:data_length])
            raw_message = raw_message[data_length:]

            instructions.append(
                CompiledInstruction(program_id_index=program_id_index,
                                    accounts=accounts,
                                    data=data))

        return Message(
            MessageArgs(
                header=header,
                account_keys=account_keys,
                recent_blockhash=recent_blockhash,
                instructions=instructions,
            ))
Пример #7
0
 def keypair(self) -> bytes:
     """The 64 byte keypair for this account (base 58 encoded)."""
     return b58encode(
         self.secret_key() +
         bytes(signing.SigningKey(self.secret_key()).verify_key))
Пример #8
0
    def compile_message(self) -> Message:  # pylint: disable=too-many-locals
        """Compile transaction data.

        Returns:
            The compiled message.
        """
        if self.nonce_info and self.instructions[
                0] != self.nonce_info.nonce_instruction:
            self.recent_blockhash = self.nonce_info.nonce
            self.instructions = [self.nonce_info.nonce_instruction
                                 ] + self.instructions

        if not self.recent_blockhash:
            raise AttributeError("transaction recentBlockhash required")
        if len(self.instructions) < 1:
            raise AttributeError("no instructions provided")

        fee_payer = self.fee_payer
        if not fee_payer and len(
                self.signatures) > 0 and self.signatures[0].pubkey:
            # Use implicit fee payer
            fee_payer = self.signatures[0].pubkey

        if not fee_payer:
            raise AttributeError("transaction feePayer required")

        account_metas, program_ids = [], []
        for instr in self.instructions:
            if not instr.program_id:
                raise AttributeError("invalid instruction:", instr)
            account_metas.extend(instr.keys)
            if str(instr.program_id) not in program_ids:
                program_ids.append(str(instr.program_id))

        # Append programID account metas.
        for pg_id in program_ids:
            account_metas.append(AccountMeta(PublicKey(pg_id), False, False))

        # Sort. Prioritizing first by signer, then by writable and converting from set to list.
        account_metas.sort(key=lambda account:
                           (not account.is_signer, not account.is_writable))

        # Cull duplicate accounts
        fee_payer_idx = maxsize
        seen: Dict[str, int] = {}
        uniq_metas: List[AccountMeta] = []
        for sig in self.signatures:
            pubkey = str(sig.pubkey)
            if pubkey in seen:
                uniq_metas[seen[pubkey]].is_signer = True
            else:
                uniq_metas.append(AccountMeta(sig.pubkey, True, True))
                seen[pubkey] = len(uniq_metas) - 1
                if sig.pubkey == fee_payer:
                    fee_payer_idx = min(fee_payer_idx, seen[pubkey])

        for a_m in account_metas:
            pubkey = str(a_m.pubkey)
            if pubkey in seen:
                idx = seen[pubkey]
                uniq_metas[idx].is_writable = uniq_metas[
                    idx].is_writable or a_m.is_writable
            else:
                uniq_metas.append(a_m)
                seen[pubkey] = len(uniq_metas) - 1
                if a_m.pubkey == fee_payer:
                    fee_payer_idx = min(fee_payer_idx, seen[pubkey])

        # Move fee payer to the front
        if fee_payer_idx == maxsize:
            uniq_metas = [AccountMeta(fee_payer, True, True)] + uniq_metas
        else:
            uniq_metas = ([uniq_metas[fee_payer_idx]] +
                          uniq_metas[:fee_payer_idx] +
                          uniq_metas[fee_payer_idx + 1:]  # noqa: E203
                          )

        # Split out signing from nonsigning keys and count readonlys
        signed_keys: List[str] = []
        unsigned_keys: List[str] = []
        num_required_signatures = num_readonly_signed_accounts = num_readonly_unsigned_accounts = 0
        for a_m in uniq_metas:
            if a_m.is_signer:
                signed_keys.append(str(a_m.pubkey))
                num_required_signatures += 1
                num_readonly_signed_accounts += int(not a_m.is_writable)
            else:
                num_readonly_unsigned_accounts += int(not a_m.is_writable)
                unsigned_keys.append(str(a_m.pubkey))
        # Initialize signature array, if needed
        if not self.signatures:
            self.signatures = [
                SigPubkeyPair(pubkey=PublicKey(key), signature=None)
                for key in signed_keys
            ]

        account_keys: List[str] = signed_keys + unsigned_keys
        account_indices: Dict[str, int] = {
            str(key): i
            for i, key in enumerate(account_keys)
        }
        compiled_instructions: List[CompiledInstruction] = [
            CompiledInstruction(
                accounts=[
                    account_indices[str(a_m.pubkey)] for a_m in instr.keys
                ],
                program_id_index=account_indices[str(instr.program_id)],
                data=b58encode(instr.data),
            ) for instr in self.instructions
        ]

        return Message(
            MessageArgs(
                header=MessageHeader(
                    num_required_signatures=num_required_signatures,
                    num_readonly_signed_accounts=num_readonly_signed_accounts,
                    num_readonly_unsigned_accounts=
                    num_readonly_unsigned_accounts,
                ),
                account_keys=account_keys,
                instructions=compiled_instructions,
                recent_blockhash=self.recent_blockhash,
            ))