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)
def to_base58(self) -> bytes: """Public key in base58. Returns: The base58-encoded public key. """ return b58encode(bytes(self))
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 }, )
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
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
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, ))
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))
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, ))