From 37d36a57608002613e21e13d71f7ee805c2b6eff Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Sun, 12 Apr 2020 15:25:31 +0800 Subject: [PATCH 1/8] docs: change the icon to github action --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 4f5bb72e..a17f6fb0 100644 --- a/README.rst +++ b/README.rst @@ -1,9 +1,9 @@ Stellar Python SDK ================== -.. image:: https://img.shields.io/travis/StellarCN/py-stellar-base/v2?style=flat-square&maxAge=1800 - :alt: Travis (.org) - :target: https://travis-ci.org/StellarCN/py-stellar-base/ +.. image:: https://img.shields.io/github/workflow/status/StellarCN/py-stellar-base/GitHub%20Action/dev?style=flat-square&maxAge=1800 + :alt: GitHub Action + :target: https://github.com/StellarCN/py-stellar-base/actions .. image:: https://img.shields.io/readthedocs/stellar-sdk.svg?style=flat-square&maxAge=1800 :alt: Read the Docs From 686cf05be3c76426b6386eb31658615aa708b293 Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Tue, 14 Apr 2020 19:26:46 +0800 Subject: [PATCH 2/8] deps: update XDR files. --- .xdr/Stellar-ledger-entries.x | 10 +- .xdr/Stellar-transaction.x | 136 ++++++++++- stellar_sdk/xdr/StellarXDR_const.py | 15 +- stellar_sdk/xdr/StellarXDR_pack.py | 333 ++++++++++++++++++++++--- stellar_sdk/xdr/StellarXDR_type.py | 365 +++++++++++++++++++++++++--- 5 files changed, 783 insertions(+), 76 deletions(-) diff --git a/.xdr/Stellar-ledger-entries.x b/.xdr/Stellar-ledger-entries.x index b511de27..433d4b65 100644 --- a/.xdr/Stellar-ledger-entries.x +++ b/.xdr/Stellar-ledger-entries.x @@ -160,11 +160,15 @@ struct AccountEntry enum TrustLineFlags { // issuer has authorized account to perform transactions with its credit - AUTHORIZED_FLAG = 1 + AUTHORIZED_FLAG = 1, + // issuer has authorized account to maintain and reduce liabilities for its + // credit + AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG = 2 }; // mask for all trustline flags const MASK_TRUSTLINE_FLAGS = 1; +const MASK_TRUSTLINE_FLAGS_V13 = 3; struct TrustLineEntry { @@ -287,9 +291,11 @@ struct LedgerEntry // the respective envelopes enum EnvelopeType { + ENVELOPE_TYPE_TX_V0 = 0, ENVELOPE_TYPE_SCP = 1, ENVELOPE_TYPE_TX = 2, ENVELOPE_TYPE_AUTH = 3, - ENVELOPE_TYPE_SCPVALUE = 4 + ENVELOPE_TYPE_SCPVALUE = 4, + ENVELOPE_TYPE_TX_FEE_BUMP = 5 }; } diff --git a/.xdr/Stellar-transaction.x b/.xdr/Stellar-transaction.x index 74d7a9ba..1292776f 100644 --- a/.xdr/Stellar-transaction.x +++ b/.xdr/Stellar-transaction.x @@ -232,7 +232,8 @@ struct AllowTrustOp } asset; - bool authorize; + // 0, or any bitwise combination of TrustLineFlags + uint32 authorize; }; /* Inflation @@ -353,6 +354,34 @@ struct TimeBounds // maximum number of operations per transaction const MAX_OPS_PER_TX = 100; +// TransactionV0 is a transaction with the AccountID discriminant stripped off, +// leaving a raw ed25519 public key to identify the source account. This is used +// for backwards compatibility starting from the protocol 12/13 boundary. If an +// "old-style" TransactionEnvelope containing a Transaction is parsed with this +// XDR definition, it will be parsed as a "new-style" TransactionEnvelope +// containing a TransactionV0. +struct TransactionV0 +{ + uint256 sourceAccountEd25519; + uint32 fee; + SequenceNumber seqNum; + TimeBounds* timeBounds; + Memo memo; + Operation operations; + union switch (int v) { + case 0: + void; + } ext; +}; + +struct TransactionV0Envelope +{ + TransactionV0 tx; + /* Each decorated signature is a signature over the SHA256 hash of + * a TransactionSignaturePayload */ + DecoratedSignature signatures<20>; +}; + /* a transaction is a container for a set of operations - is executed by an account - fees are collected from the account @@ -387,27 +416,61 @@ struct Transaction ext; }; -struct TransactionSignaturePayload +struct TransactionV1Envelope { - Hash networkId; + Transaction tx; + /* Each decorated signature is a signature over the SHA256 hash of + * a TransactionSignaturePayload */ + DecoratedSignature signatures<20>; +}; + +struct FeeBumpTransaction +{ + AccountID feeSource; + int64 fee; union switch (EnvelopeType type) { case ENVELOPE_TYPE_TX: - Transaction tx; - /* All other values of type are invalid */ - } - taggedTransaction; + TransactionV1Envelope v1; + } innerTx; + union switch (int v) { + case 0: + void; + } ext; }; -/* A TransactionEnvelope wraps a transaction with signatures. */ -struct TransactionEnvelope +struct FeeBumpTransactionEnvelope { - Transaction tx; + FeeBumpTransaction tx; /* Each decorated signature is a signature over the SHA256 hash of * a TransactionSignaturePayload */ DecoratedSignature signatures<20>; }; +/* A TransactionEnvelope wraps a transaction with signatures. */ +union TransactionEnvelope switch (EnvelopeType type) { +case ENVELOPE_TYPE_TX_V0: + TransactionV0Envelope v0; +case ENVELOPE_TYPE_TX: + TransactionV1Envelope v1; +case ENVELOPE_TYPE_TX_FEE_BUMP: + FeeBumpTransactionEnvelope feeBump; +}; + +struct TransactionSignaturePayload +{ + Hash networkId; + union switch (EnvelopeType type) + { + // Backwards Compatibility: Use ENVELOPE_TYPE_TX to sign ENVELOPE_TYPE_TX_V0 + case ENVELOPE_TYPE_TX: + Transaction tx; + case ENVELOPE_TYPE_TX_FEE_BUMP: + FeeBumpTransaction feeBump; + } + taggedTransaction; +}; + /* Operation Results section */ /* This result is used when offers are taken during an operation */ @@ -859,6 +922,7 @@ default: enum TransactionResultCode { + txFEE_BUMP_INNER_SUCCESS = 1, // fee bump inner transaction succeeded txSUCCESS = 0, // all operations succeeded txFAILED = -1, // one of the operations failed (none were applied) @@ -873,7 +937,54 @@ enum TransactionResultCode txNO_ACCOUNT = -8, // source account not found txINSUFFICIENT_FEE = -9, // fee is too small txBAD_AUTH_EXTRA = -10, // unused signatures attached to transaction - txINTERNAL_ERROR = -11 // an unknown error occured + txINTERNAL_ERROR = -11, // an unknown error occured + + txNOT_SUPPORTED = -12, // transaction type not supported + txFEE_BUMP_INNER_FAILED = -13 // fee bump inner transaction failed +}; + +// InnerTransactionResult must be binary compatible with TransactionResult +// because it is be used to represent the result of a Transaction. +struct InnerTransactionResult +{ + // Always 0. Here for binary compatibility. + int64 feeCharged; + + union switch (TransactionResultCode code) + { + // txFEE_BUMP_INNER_SUCCESS is not included + case txSUCCESS: + case txFAILED: + OperationResult results<>; + case txTOO_EARLY: + case txTOO_LATE: + case txMISSING_OPERATION: + case txBAD_SEQ: + case txBAD_AUTH: + case txINSUFFICIENT_BALANCE: + case txNO_ACCOUNT: + case txINSUFFICIENT_FEE: + case txBAD_AUTH_EXTRA: + case txINTERNAL_ERROR: + case txNOT_SUPPORTED: + // txFEE_BUMP_INNER_FAILED is not included + void; + } + result; + + // reserved for future use + union switch (int v) + { + case 0: + void; + } + ext; +}; + +struct InnerTransactionResultPair +{ + Hash transactionHash; // hash of the inner transaction + InnerTransactionResult result; // result for the inner transaction }; struct TransactionResult @@ -882,6 +993,9 @@ struct TransactionResult union switch (TransactionResultCode code) { + case txFEE_BUMP_INNER_SUCCESS: + case txFEE_BUMP_INNER_FAILED: + InnerTransactionResultPair innerResultPair; case txSUCCESS: case txFAILED: OperationResult results<>; diff --git a/stellar_sdk/xdr/StellarXDR_const.py b/stellar_sdk/xdr/StellarXDR_const.py index 538fc9e0..46920e75 100644 --- a/stellar_sdk/xdr/StellarXDR_const.py +++ b/stellar_sdk/xdr/StellarXDR_const.py @@ -1,4 +1,4 @@ -# Generated by xdrgen.py from ../../.xdr/ on Thu Jan 23 18:51:48 2020 +# Generated by xdrgen.py from ../../.xdr/ on Tue Apr 14 19:24:25 2020 KEY_TYPE_ED25519 = 0 KEY_TYPE_PRE_AUTH_TX = 1 KEY_TYPE_HASH_X = 2 @@ -103,24 +103,31 @@ } MASK_ACCOUNT_FLAGS = 0x7 AUTHORIZED_FLAG = 1 +AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG = 2 TrustLineFlags = { 1 : 'AUTHORIZED_FLAG', + 2 : 'AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG', } MASK_TRUSTLINE_FLAGS = 1 +MASK_TRUSTLINE_FLAGS_V13 = 3 PASSIVE_FLAG = 1 OfferEntryFlags = { 1 : 'PASSIVE_FLAG', } MASK_OFFERENTRY_FLAGS = 1 +ENVELOPE_TYPE_TX_V0 = 0 ENVELOPE_TYPE_SCP = 1 ENVELOPE_TYPE_TX = 2 ENVELOPE_TYPE_AUTH = 3 ENVELOPE_TYPE_SCPVALUE = 4 +ENVELOPE_TYPE_TX_FEE_BUMP = 5 EnvelopeType = { + 0 : 'ENVELOPE_TYPE_TX_V0', 1 : 'ENVELOPE_TYPE_SCP', 2 : 'ENVELOPE_TYPE_TX', 3 : 'ENVELOPE_TYPE_AUTH', 4 : 'ENVELOPE_TYPE_SCPVALUE', + 5 : 'ENVELOPE_TYPE_TX_FEE_BUMP', } CREATE_ACCOUNT = 0 PAYMENT = 1 @@ -423,6 +430,7 @@ -4 : 'opTOO_MANY_SUBENTRIES', -5 : 'opEXCEEDED_WORK_LIMIT', } +txFEE_BUMP_INNER_SUCCESS = 1 txSUCCESS = 0 txFAILED = -1 txTOO_EARLY = -2 @@ -435,7 +443,10 @@ txINSUFFICIENT_FEE = -9 txBAD_AUTH_EXTRA = -10 txINTERNAL_ERROR = -11 +txNOT_SUPPORTED = -12 +txFEE_BUMP_INNER_FAILED = -13 TransactionResultCode = { + 1 : 'txFEE_BUMP_INNER_SUCCESS', 0 : 'txSUCCESS', -1 : 'txFAILED', -2 : 'txTOO_EARLY', @@ -448,6 +459,8 @@ -9 : 'txINSUFFICIENT_FEE', -10 : 'txBAD_AUTH_EXTRA', -11 : 'txINTERNAL_ERROR', + -12 : 'txNOT_SUPPORTED', + -13 : 'txFEE_BUMP_INNER_FAILED', } ERR_MISC = 0 ERR_DATA = 1 diff --git a/stellar_sdk/xdr/StellarXDR_pack.py b/stellar_sdk/xdr/StellarXDR_pack.py index 6b7e3f6e..10286850 100644 --- a/stellar_sdk/xdr/StellarXDR_pack.py +++ b/stellar_sdk/xdr/StellarXDR_pack.py @@ -1,4 +1,4 @@ -# Generated by xdrgen.py from ../../.xdr/ on Thu Jan 23 18:51:48 2020 +# Generated by xdrgen.py from ../../.xdr/ on Tue Apr 14 19:24:25 2020 from . import StellarXDR_const as const from . import StellarXDR_type as types import xdrlib @@ -949,7 +949,7 @@ def pack_AccountEntry(self, data): def pack_TrustLineFlags(self, data): if hasattr(self, 'filter_TrustLineFlags'): data = getattr(self, 'filter_TrustLineFlags')(data) - if self.check_enum and data not in [const.AUTHORIZED_FLAG]: + if self.check_enum and data not in [const.AUTHORIZED_FLAG, const.AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG]: raise XDRError('value=%s not in enum TrustLineFlags' % data) self.pack_int(data) @@ -1101,7 +1101,7 @@ def pack_LedgerEntry(self, data): def pack_EnvelopeType(self, data): if hasattr(self, 'filter_EnvelopeType'): data = getattr(self, 'filter_EnvelopeType')(data) - if self.check_enum and data not in [const.ENVELOPE_TYPE_SCP, const.ENVELOPE_TYPE_TX, const.ENVELOPE_TYPE_AUTH, const.ENVELOPE_TYPE_SCPVALUE]: + if self.check_enum and data not in [const.ENVELOPE_TYPE_TX_V0, const.ENVELOPE_TYPE_SCP, const.ENVELOPE_TYPE_TX, const.ENVELOPE_TYPE_AUTH, const.ENVELOPE_TYPE_SCPVALUE, const.ENVELOPE_TYPE_TX_FEE_BUMP]: raise XDRError('value=%s not in enum EnvelopeType' % data) self.pack_int(data) @@ -1329,7 +1329,7 @@ def pack_AllowTrustOp(self, data): raise XDRError('bad switch=%s' % data.asset.type) if data.authorize is None: raise TypeError('data.authorize == None') - self.pack_bool(data.authorize) + self.pack_uint32(data.authorize) def pack_ManageDataOp(self, data): if hasattr(self, 'filter_ManageDataOp'): @@ -1466,6 +1466,53 @@ def pack_TimeBounds(self, data): raise TypeError('data.maxTime == None') self.pack_TimePoint(data.maxTime) + def pack_TransactionV0(self, data): + if hasattr(self, 'filter_TransactionV0'): + data = getattr(self, 'filter_TransactionV0')(data) + if data.sourceAccountEd25519 is None: + raise TypeError('data.sourceAccountEd25519 == None') + self.pack_uint256(data.sourceAccountEd25519) + if data.fee is None: + raise TypeError('data.fee == None') + self.pack_uint32(data.fee) + if data.seqNum is None: + raise TypeError('data.seqNum == None') + self.pack_SequenceNumber(data.seqNum) + if data.timeBounds is None: + raise TypeError('data.timeBounds == None') + if len(data.timeBounds) > 1 and self.check_array: + raise XDRError('array length too long for data.timeBounds') + self.pack_array(data.timeBounds, self.pack_TimeBounds) + if data.memo is None: + raise TypeError('data.memo == None') + self.pack_Memo(data.memo) + if data.operations is None: + raise TypeError('data.operations == None') + if len(data.operations) > const.MAX_OPS_PER_TX and self.check_array: + raise XDRError('array length too long for data.operations') + self.pack_array(data.operations, self.pack_Operation) + if data.ext is None: + raise TypeError('data.ext == None') + if data.ext.v is None: + raise TypeError('data.ext.v == None') + self.pack_int(data.ext.v) + if data.ext.v == 0: + pass + else: + raise XDRError('bad switch=%s' % data.ext.v) + + def pack_TransactionV0Envelope(self, data): + if hasattr(self, 'filter_TransactionV0Envelope'): + data = getattr(self, 'filter_TransactionV0Envelope')(data) + if data.tx is None: + raise TypeError('data.tx == None') + self.pack_TransactionV0(data.tx) + if data.signatures is None: + raise TypeError('data.signatures == None') + if len(data.signatures) > 20 and self.check_array: + raise XDRError('array length too long for data.signatures') + self.pack_array(data.signatures, self.pack_DecoratedSignature) + def pack_Transaction(self, data): if hasattr(self, 'filter_Transaction'): data = getattr(self, 'filter_Transaction')(data) @@ -1501,6 +1548,81 @@ def pack_Transaction(self, data): else: raise XDRError('bad switch=%s' % data.ext.v) + def pack_TransactionV1Envelope(self, data): + if hasattr(self, 'filter_TransactionV1Envelope'): + data = getattr(self, 'filter_TransactionV1Envelope')(data) + if data.tx is None: + raise TypeError('data.tx == None') + self.pack_Transaction(data.tx) + if data.signatures is None: + raise TypeError('data.signatures == None') + if len(data.signatures) > 20 and self.check_array: + raise XDRError('array length too long for data.signatures') + self.pack_array(data.signatures, self.pack_DecoratedSignature) + + def pack_FeeBumpTransaction(self, data): + if hasattr(self, 'filter_FeeBumpTransaction'): + data = getattr(self, 'filter_FeeBumpTransaction')(data) + if data.feeSource is None: + raise TypeError('data.feeSource == None') + self.pack_AccountID(data.feeSource) + if data.fee is None: + raise TypeError('data.fee == None') + self.pack_int64(data.fee) + if data.innerTx is None: + raise TypeError('data.innerTx == None') + if data.innerTx.type is None: + raise TypeError('data.innerTx.type == None') + self.pack_EnvelopeType(data.innerTx.type) + if data.innerTx.type == const.ENVELOPE_TYPE_TX: + if data.innerTx.v1 is None: + raise TypeError('data.innerTx.v1 == None') + self.pack_TransactionV1Envelope(data.innerTx.v1) + else: + raise XDRError('bad switch=%s' % data.innerTx.type) + if data.ext is None: + raise TypeError('data.ext == None') + if data.ext.v is None: + raise TypeError('data.ext.v == None') + self.pack_int(data.ext.v) + if data.ext.v == 0: + pass + else: + raise XDRError('bad switch=%s' % data.ext.v) + + def pack_FeeBumpTransactionEnvelope(self, data): + if hasattr(self, 'filter_FeeBumpTransactionEnvelope'): + data = getattr(self, 'filter_FeeBumpTransactionEnvelope')(data) + if data.tx is None: + raise TypeError('data.tx == None') + self.pack_FeeBumpTransaction(data.tx) + if data.signatures is None: + raise TypeError('data.signatures == None') + if len(data.signatures) > 20 and self.check_array: + raise XDRError('array length too long for data.signatures') + self.pack_array(data.signatures, self.pack_DecoratedSignature) + + def pack_TransactionEnvelope(self, data): + if hasattr(self, 'filter_TransactionEnvelope'): + data = getattr(self, 'filter_TransactionEnvelope')(data) + if data.type is None: + raise TypeError('data.type == None') + self.pack_EnvelopeType(data.type) + if data.type == const.ENVELOPE_TYPE_TX_V0: + if data.v0 is None: + raise TypeError('data.v0 == None') + self.pack_TransactionV0Envelope(data.v0) + elif data.type == const.ENVELOPE_TYPE_TX: + if data.v1 is None: + raise TypeError('data.v1 == None') + self.pack_TransactionV1Envelope(data.v1) + elif data.type == const.ENVELOPE_TYPE_TX_FEE_BUMP: + if data.feeBump is None: + raise TypeError('data.feeBump == None') + self.pack_FeeBumpTransactionEnvelope(data.feeBump) + else: + raise XDRError('bad switch=%s' % data.type) + def pack_TransactionSignaturePayload(self, data): if hasattr(self, 'filter_TransactionSignaturePayload'): data = getattr(self, 'filter_TransactionSignaturePayload')(data) @@ -1516,21 +1638,13 @@ def pack_TransactionSignaturePayload(self, data): if data.taggedTransaction.tx is None: raise TypeError('data.taggedTransaction.tx == None') self.pack_Transaction(data.taggedTransaction.tx) + elif data.taggedTransaction.type == const.ENVELOPE_TYPE_TX_FEE_BUMP: + if data.taggedTransaction.feeBump is None: + raise TypeError('data.taggedTransaction.feeBump == None') + self.pack_FeeBumpTransaction(data.taggedTransaction.feeBump) else: raise XDRError('bad switch=%s' % data.taggedTransaction.type) - def pack_TransactionEnvelope(self, data): - if hasattr(self, 'filter_TransactionEnvelope'): - data = getattr(self, 'filter_TransactionEnvelope')(data) - if data.tx is None: - raise TypeError('data.tx == None') - self.pack_Transaction(data.tx) - if data.signatures is None: - raise TypeError('data.signatures == None') - if len(data.signatures) > 20 and self.check_array: - raise XDRError('array length too long for data.signatures') - self.pack_array(data.signatures, self.pack_DecoratedSignature) - def pack_ClaimOfferAtom(self, data): if hasattr(self, 'filter_ClaimOfferAtom'): data = getattr(self, 'filter_ClaimOfferAtom')(data) @@ -1948,10 +2062,49 @@ def pack_OperationResult(self, data): def pack_TransactionResultCode(self, data): if hasattr(self, 'filter_TransactionResultCode'): data = getattr(self, 'filter_TransactionResultCode')(data) - if self.check_enum and data not in [const.txSUCCESS, const.txFAILED, const.txTOO_EARLY, const.txTOO_LATE, const.txMISSING_OPERATION, const.txBAD_SEQ, const.txBAD_AUTH, const.txINSUFFICIENT_BALANCE, const.txNO_ACCOUNT, const.txINSUFFICIENT_FEE, const.txBAD_AUTH_EXTRA, const.txINTERNAL_ERROR]: + if self.check_enum and data not in [const.txFEE_BUMP_INNER_SUCCESS, const.txSUCCESS, const.txFAILED, const.txTOO_EARLY, const.txTOO_LATE, const.txMISSING_OPERATION, const.txBAD_SEQ, const.txBAD_AUTH, const.txINSUFFICIENT_BALANCE, const.txNO_ACCOUNT, const.txINSUFFICIENT_FEE, const.txBAD_AUTH_EXTRA, const.txINTERNAL_ERROR, const.txNOT_SUPPORTED, const.txFEE_BUMP_INNER_FAILED]: raise XDRError('value=%s not in enum TransactionResultCode' % data) self.pack_int(data) + def pack_InnerTransactionResult(self, data): + if hasattr(self, 'filter_InnerTransactionResult'): + data = getattr(self, 'filter_InnerTransactionResult')(data) + if data.feeCharged is None: + raise TypeError('data.feeCharged == None') + self.pack_int64(data.feeCharged) + if data.result is None: + raise TypeError('data.result == None') + if data.result.code is None: + raise TypeError('data.result.code == None') + self.pack_TransactionResultCode(data.result.code) + if data.result.code == const.txSUCCESS or data.result.code == const.txFAILED: + if data.result.results is None: + raise TypeError('data.result.results == None') + self.pack_array(data.result.results, self.pack_OperationResult) + elif data.result.code == const.txTOO_EARLY or data.result.code == const.txTOO_LATE or data.result.code == const.txMISSING_OPERATION or data.result.code == const.txBAD_SEQ or data.result.code == const.txBAD_AUTH or data.result.code == const.txINSUFFICIENT_BALANCE or data.result.code == const.txNO_ACCOUNT or data.result.code == const.txINSUFFICIENT_FEE or data.result.code == const.txBAD_AUTH_EXTRA or data.result.code == const.txINTERNAL_ERROR or data.result.code == const.txNOT_SUPPORTED: + pass + else: + raise XDRError('bad switch=%s' % data.result.code) + if data.ext is None: + raise TypeError('data.ext == None') + if data.ext.v is None: + raise TypeError('data.ext.v == None') + self.pack_int(data.ext.v) + if data.ext.v == 0: + pass + else: + raise XDRError('bad switch=%s' % data.ext.v) + + def pack_InnerTransactionResultPair(self, data): + if hasattr(self, 'filter_InnerTransactionResultPair'): + data = getattr(self, 'filter_InnerTransactionResultPair')(data) + if data.transactionHash is None: + raise TypeError('data.transactionHash == None') + self.pack_Hash(data.transactionHash) + if data.result is None: + raise TypeError('data.result == None') + self.pack_InnerTransactionResult(data.result) + def pack_TransactionResult(self, data): if hasattr(self, 'filter_TransactionResult'): data = getattr(self, 'filter_TransactionResult')(data) @@ -1963,7 +2116,11 @@ def pack_TransactionResult(self, data): if data.result.code is None: raise TypeError('data.result.code == None') self.pack_TransactionResultCode(data.result.code) - if data.result.code == const.txSUCCESS or data.result.code == const.txFAILED: + if data.result.code == const.txFEE_BUMP_INNER_SUCCESS or data.result.code == const.txFEE_BUMP_INNER_FAILED: + if data.result.innerResultPair is None: + raise TypeError('data.result.innerResultPair == None') + self.pack_InnerTransactionResultPair(data.result.innerResultPair) + elif data.result.code == const.txSUCCESS or data.result.code == const.txFAILED: if data.result.results is None: raise TypeError('data.result.results == None') self.pack_array(data.result.results, self.pack_OperationResult) @@ -3080,7 +3237,7 @@ def unpack_AccountEntry(self): def unpack_TrustLineFlags(self): data = self.unpack_int() - if self.check_enum and data not in [const.AUTHORIZED_FLAG]: + if self.check_enum and data not in [const.AUTHORIZED_FLAG, const.AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG]: raise XDRError('value=%s not in enum TrustLineFlags' % data) if hasattr(self, 'filter_TrustLineFlags'): data = getattr(self, 'filter_TrustLineFlags')(data) @@ -3181,7 +3338,7 @@ def unpack_LedgerEntry(self): def unpack_EnvelopeType(self): data = self.unpack_int() - if self.check_enum and data not in [const.ENVELOPE_TYPE_SCP, const.ENVELOPE_TYPE_TX, const.ENVELOPE_TYPE_AUTH, const.ENVELOPE_TYPE_SCPVALUE]: + if self.check_enum and data not in [const.ENVELOPE_TYPE_TX_V0, const.ENVELOPE_TYPE_SCP, const.ENVELOPE_TYPE_TX, const.ENVELOPE_TYPE_AUTH, const.ENVELOPE_TYPE_SCPVALUE, const.ENVELOPE_TYPE_TX_FEE_BUMP]: raise XDRError('value=%s not in enum EnvelopeType' % data) if hasattr(self, 'filter_EnvelopeType'): data = getattr(self, 'filter_EnvelopeType')(data) @@ -3332,7 +3489,7 @@ def unpack_AllowTrustOp(self): data.asset.assetCode12 = self.unpack_AssetCode12() else: raise XDRError('bad switch=%s' % data.asset.type) - data.authorize = self.unpack_bool() + data.authorize = self.unpack_uint32() if hasattr(self, 'filter_AllowTrustOp'): data = getattr(self, 'filter_AllowTrustOp')(data) return data @@ -3432,6 +3589,38 @@ def unpack_TimeBounds(self): data = getattr(self, 'filter_TimeBounds')(data) return data + def unpack_TransactionV0(self): + data = types.TransactionV0() + data.sourceAccountEd25519 = self.unpack_uint256() + data.fee = self.unpack_uint32() + data.seqNum = self.unpack_SequenceNumber() + data.timeBounds = self.unpack_array(self.unpack_TimeBounds) + if len(data.timeBounds) > 1 and self.check_array: + raise XDRError('array length too long for data.timeBounds') + data.memo = self.unpack_Memo() + data.operations = self.unpack_array(self.unpack_Operation) + if len(data.operations) > const.MAX_OPS_PER_TX and self.check_array: + raise XDRError('array length too long for data.operations') + data.ext = nullclass() + data.ext.v = self.unpack_int() + if data.ext.v == 0: + pass + else: + raise XDRError('bad switch=%s' % data.ext.v) + if hasattr(self, 'filter_TransactionV0'): + data = getattr(self, 'filter_TransactionV0')(data) + return data + + def unpack_TransactionV0Envelope(self): + data = types.TransactionV0Envelope() + data.tx = self.unpack_TransactionV0() + data.signatures = self.unpack_array(self.unpack_DecoratedSignature) + if len(data.signatures) > 20 and self.check_array: + raise XDRError('array length too long for data.signatures') + if hasattr(self, 'filter_TransactionV0Envelope'): + data = getattr(self, 'filter_TransactionV0Envelope')(data) + return data + def unpack_Transaction(self): data = types.Transaction() data.sourceAccount = self.unpack_AccountID() @@ -3454,6 +3643,61 @@ def unpack_Transaction(self): data = getattr(self, 'filter_Transaction')(data) return data + def unpack_TransactionV1Envelope(self): + data = types.TransactionV1Envelope() + data.tx = self.unpack_Transaction() + data.signatures = self.unpack_array(self.unpack_DecoratedSignature) + if len(data.signatures) > 20 and self.check_array: + raise XDRError('array length too long for data.signatures') + if hasattr(self, 'filter_TransactionV1Envelope'): + data = getattr(self, 'filter_TransactionV1Envelope')(data) + return data + + def unpack_FeeBumpTransaction(self): + data = types.FeeBumpTransaction() + data.feeSource = self.unpack_AccountID() + data.fee = self.unpack_int64() + data.innerTx = nullclass() + data.innerTx.type = self.unpack_EnvelopeType() + if data.innerTx.type == const.ENVELOPE_TYPE_TX: + data.innerTx.v1 = self.unpack_TransactionV1Envelope() + else: + raise XDRError('bad switch=%s' % data.innerTx.type) + data.ext = nullclass() + data.ext.v = self.unpack_int() + if data.ext.v == 0: + pass + else: + raise XDRError('bad switch=%s' % data.ext.v) + if hasattr(self, 'filter_FeeBumpTransaction'): + data = getattr(self, 'filter_FeeBumpTransaction')(data) + return data + + def unpack_FeeBumpTransactionEnvelope(self): + data = types.FeeBumpTransactionEnvelope() + data.tx = self.unpack_FeeBumpTransaction() + data.signatures = self.unpack_array(self.unpack_DecoratedSignature) + if len(data.signatures) > 20 and self.check_array: + raise XDRError('array length too long for data.signatures') + if hasattr(self, 'filter_FeeBumpTransactionEnvelope'): + data = getattr(self, 'filter_FeeBumpTransactionEnvelope')(data) + return data + + def unpack_TransactionEnvelope(self): + data = types.TransactionEnvelope() + data.type = self.unpack_EnvelopeType() + if data.type == const.ENVELOPE_TYPE_TX_V0: + data.v0 = self.unpack_TransactionV0Envelope() + elif data.type == const.ENVELOPE_TYPE_TX: + data.v1 = self.unpack_TransactionV1Envelope() + elif data.type == const.ENVELOPE_TYPE_TX_FEE_BUMP: + data.feeBump = self.unpack_FeeBumpTransactionEnvelope() + else: + raise XDRError('bad switch=%s' % data.type) + if hasattr(self, 'filter_TransactionEnvelope'): + data = getattr(self, 'filter_TransactionEnvelope')(data) + return data + def unpack_TransactionSignaturePayload(self): data = types.TransactionSignaturePayload() data.networkId = self.unpack_Hash() @@ -3461,22 +3705,14 @@ def unpack_TransactionSignaturePayload(self): data.taggedTransaction.type = self.unpack_EnvelopeType() if data.taggedTransaction.type == const.ENVELOPE_TYPE_TX: data.taggedTransaction.tx = self.unpack_Transaction() + elif data.taggedTransaction.type == const.ENVELOPE_TYPE_TX_FEE_BUMP: + data.taggedTransaction.feeBump = self.unpack_FeeBumpTransaction() else: raise XDRError('bad switch=%s' % data.taggedTransaction.type) if hasattr(self, 'filter_TransactionSignaturePayload'): data = getattr(self, 'filter_TransactionSignaturePayload')(data) return data - def unpack_TransactionEnvelope(self): - data = types.TransactionEnvelope() - data.tx = self.unpack_Transaction() - data.signatures = self.unpack_array(self.unpack_DecoratedSignature) - if len(data.signatures) > 20 and self.check_array: - raise XDRError('array length too long for data.signatures') - if hasattr(self, 'filter_TransactionEnvelope'): - data = getattr(self, 'filter_TransactionEnvelope')(data) - return data - def unpack_ClaimOfferAtom(self): data = types.ClaimOfferAtom() data.sellerID = self.unpack_AccountID() @@ -3834,18 +4070,49 @@ def unpack_OperationResult(self): def unpack_TransactionResultCode(self): data = self.unpack_int() - if self.check_enum and data not in [const.txSUCCESS, const.txFAILED, const.txTOO_EARLY, const.txTOO_LATE, const.txMISSING_OPERATION, const.txBAD_SEQ, const.txBAD_AUTH, const.txINSUFFICIENT_BALANCE, const.txNO_ACCOUNT, const.txINSUFFICIENT_FEE, const.txBAD_AUTH_EXTRA, const.txINTERNAL_ERROR]: + if self.check_enum and data not in [const.txFEE_BUMP_INNER_SUCCESS, const.txSUCCESS, const.txFAILED, const.txTOO_EARLY, const.txTOO_LATE, const.txMISSING_OPERATION, const.txBAD_SEQ, const.txBAD_AUTH, const.txINSUFFICIENT_BALANCE, const.txNO_ACCOUNT, const.txINSUFFICIENT_FEE, const.txBAD_AUTH_EXTRA, const.txINTERNAL_ERROR, const.txNOT_SUPPORTED, const.txFEE_BUMP_INNER_FAILED]: raise XDRError('value=%s not in enum TransactionResultCode' % data) if hasattr(self, 'filter_TransactionResultCode'): data = getattr(self, 'filter_TransactionResultCode')(data) return data + def unpack_InnerTransactionResult(self): + data = types.InnerTransactionResult() + data.feeCharged = self.unpack_int64() + data.result = nullclass() + data.result.code = self.unpack_TransactionResultCode() + if data.result.code == const.txSUCCESS or data.result.code == const.txFAILED: + data.result.results = self.unpack_array(self.unpack_OperationResult) + elif data.result.code == const.txTOO_EARLY or data.result.code == const.txTOO_LATE or data.result.code == const.txMISSING_OPERATION or data.result.code == const.txBAD_SEQ or data.result.code == const.txBAD_AUTH or data.result.code == const.txINSUFFICIENT_BALANCE or data.result.code == const.txNO_ACCOUNT or data.result.code == const.txINSUFFICIENT_FEE or data.result.code == const.txBAD_AUTH_EXTRA or data.result.code == const.txINTERNAL_ERROR or data.result.code == const.txNOT_SUPPORTED: + pass + else: + raise XDRError('bad switch=%s' % data.result.code) + data.ext = nullclass() + data.ext.v = self.unpack_int() + if data.ext.v == 0: + pass + else: + raise XDRError('bad switch=%s' % data.ext.v) + if hasattr(self, 'filter_InnerTransactionResult'): + data = getattr(self, 'filter_InnerTransactionResult')(data) + return data + + def unpack_InnerTransactionResultPair(self): + data = types.InnerTransactionResultPair() + data.transactionHash = self.unpack_Hash() + data.result = self.unpack_InnerTransactionResult() + if hasattr(self, 'filter_InnerTransactionResultPair'): + data = getattr(self, 'filter_InnerTransactionResultPair')(data) + return data + def unpack_TransactionResult(self): data = types.TransactionResult() data.feeCharged = self.unpack_int64() data.result = nullclass() data.result.code = self.unpack_TransactionResultCode() - if data.result.code == const.txSUCCESS or data.result.code == const.txFAILED: + if data.result.code == const.txFEE_BUMP_INNER_SUCCESS or data.result.code == const.txFEE_BUMP_INNER_FAILED: + data.result.innerResultPair = self.unpack_InnerTransactionResultPair() + elif data.result.code == const.txSUCCESS or data.result.code == const.txFAILED: data.result.results = self.unpack_array(self.unpack_OperationResult) else: pass diff --git a/stellar_sdk/xdr/StellarXDR_type.py b/stellar_sdk/xdr/StellarXDR_type.py index 2c46a8f4..31e36a3f 100644 --- a/stellar_sdk/xdr/StellarXDR_type.py +++ b/stellar_sdk/xdr/StellarXDR_type.py @@ -1,4 +1,4 @@ -# Generated by xdrgen.py from ../../.xdr/ on Thu Jan 23 18:51:48 2020 +# Generated by xdrgen.py from ../../.xdr/ on Tue Apr 14 19:24:25 2020 import base64 from . import StellarXDR_const as const @@ -2231,7 +2231,7 @@ class AllowTrustOp: # case ASSET_TYPE_CREDIT_ALPHANUM12: # AssetCode12 assetCode12; # } asset; - # bool authorize; + # uint32 authorize; # }; def __init__(self, trustor=None, asset=None, authorize=None): self.trustor = trustor @@ -2464,6 +2464,94 @@ def __repr__(self): return 'TimeBounds(%s)' % ', '.join(out) __str__ = __repr__ +class TransactionV0: + # XDR definition: + # struct TransactionV0 { + # uint256 sourceAccountEd25519; + # uint32 fee; + # SequenceNumber seqNum; + # TimeBounds timeBounds<1>; + # Memo memo; + # Operation operations; + # union switch(int v) { + # case 0: + # void; + # } ext; + # }; + def __init__(self, sourceAccountEd25519=None, fee=None, seqNum=None, timeBounds=None, memo=None, operations=None, ext=None): + self.sourceAccountEd25519 = sourceAccountEd25519 + self.fee = fee + self.seqNum = seqNum + self.timeBounds = timeBounds + self.memo = memo + self.operations = operations + self.ext = ext + + def to_xdr(self): + transactionv0 = pack.StellarXDRPacker() + transactionv0.pack_TransactionV0(self) + return base64.b64encode(transactionv0.get_buffer()).decode() + + @staticmethod + def from_xdr(xdr): + xdr_decoded = base64.b64decode(xdr) + xdr_unpacked = pack.StellarXDRUnpacker(xdr_decoded) + return xdr_unpacked.unpack_TransactionV0() + + def __repr__(self): + out = [] + if self.sourceAccountEd25519 is not None: + out += ['sourceAccountEd25519=%s' % repr(self.sourceAccountEd25519)] + if self.fee is not None: + out += ['fee=%s' % repr(self.fee)] + if self.seqNum is not None: + out += ['seqNum=%s' % repr(self.seqNum)] + if self.timeBounds is not None: + out += ['timeBounds=%s' % repr(self.timeBounds)] + if self.memo is not None: + out += ['memo=%s' % repr(self.memo)] + if self.operations is not None: + out += ['operations=%s' % repr(self.operations)] + if self.ext is not None: + out += ['ext=%s' % repr(self.ext)] + return 'TransactionV0(%s)' % ', '.join(out) + __str__ = __repr__ + +class TransactionV0Envelope: + # XDR definition: + # struct TransactionV0Envelope { + # TransactionV0 tx; + # DecoratedSignature signatures<20>; + # }; + def __init__(self, tx=None, signatures=None): + self.tx = tx + self.signatures = signatures + + def __getattr__(self, attr): + if attr is '__setstate__': + raise AttributeError + return getattr(self.tx, attr) + + def to_xdr(self): + transactionv0envelope = pack.StellarXDRPacker() + transactionv0envelope.pack_TransactionV0Envelope(self) + return base64.b64encode(transactionv0envelope.get_buffer()).decode() + + @staticmethod + def from_xdr(xdr): + xdr_decoded = base64.b64decode(xdr) + xdr_unpacked = pack.StellarXDRUnpacker(xdr_decoded) + return xdr_unpacked.unpack_TransactionV0Envelope() + + def __repr__(self): + out = [] + if self.tx is not None: + out += ['tx=%s' % repr(self.tx)] + if self.signatures is not None: + out += ['signatures=%s' % repr(self.signatures)] + return 'TransactionV0Envelope(%s)' % ', '.join(out) + __str__ = __repr__ + class Transaction: # XDR definition: # struct Transaction { @@ -2517,48 +2605,89 @@ def __repr__(self): return 'Transaction(%s)' % ', '.join(out) __str__ = __repr__ -class TransactionSignaturePayload: +class TransactionV1Envelope: # XDR definition: - # struct TransactionSignaturePayload { - # Hash networkId; - # union switch(EnvelopeType type) { - # case ENVELOPE_TYPE_TX: - # Transaction tx; - # } taggedTransaction; + # struct TransactionV1Envelope { + # Transaction tx; + # DecoratedSignature signatures<20>; # }; - def __init__(self, networkId=None, taggedTransaction=None): - self.networkId = networkId - self.taggedTransaction = taggedTransaction + def __init__(self, tx=None, signatures=None): + self.tx = tx + self.signatures = signatures def __getattr__(self, attr): if attr is '__setstate__': raise AttributeError - return getattr(self.taggedTransaction, attr) + return getattr(self.tx, attr) def to_xdr(self): - transactionsignaturepayload = pack.StellarXDRPacker() - transactionsignaturepayload.pack_TransactionSignaturePayload(self) - return base64.b64encode(transactionsignaturepayload.get_buffer()).decode() + transactionv1envelope = pack.StellarXDRPacker() + transactionv1envelope.pack_TransactionV1Envelope(self) + return base64.b64encode(transactionv1envelope.get_buffer()).decode() @staticmethod def from_xdr(xdr): xdr_decoded = base64.b64decode(xdr) xdr_unpacked = pack.StellarXDRUnpacker(xdr_decoded) - return xdr_unpacked.unpack_TransactionSignaturePayload() + return xdr_unpacked.unpack_TransactionV1Envelope() def __repr__(self): out = [] - if self.networkId is not None: - out += ['networkId=%s' % repr(self.networkId)] - if self.taggedTransaction is not None: - out += ['taggedTransaction=%s' % repr(self.taggedTransaction)] - return 'TransactionSignaturePayload(%s)' % ', '.join(out) + if self.tx is not None: + out += ['tx=%s' % repr(self.tx)] + if self.signatures is not None: + out += ['signatures=%s' % repr(self.signatures)] + return 'TransactionV1Envelope(%s)' % ', '.join(out) __str__ = __repr__ -class TransactionEnvelope: +class FeeBumpTransaction: # XDR definition: - # struct TransactionEnvelope { - # Transaction tx; + # struct FeeBumpTransaction { + # AccountID feeSource; + # int64 fee; + # union switch(EnvelopeType type) { + # case ENVELOPE_TYPE_TX: + # TransactionV1Envelope v1; + # } innerTx; + # union switch(int v) { + # case 0: + # void; + # } ext; + # }; + def __init__(self, feeSource=None, fee=None, innerTx=None, ext=None): + self.feeSource = feeSource + self.fee = fee + self.innerTx = innerTx + self.ext = ext + + def to_xdr(self): + feebumptransaction = pack.StellarXDRPacker() + feebumptransaction.pack_FeeBumpTransaction(self) + return base64.b64encode(feebumptransaction.get_buffer()).decode() + + @staticmethod + def from_xdr(xdr): + xdr_decoded = base64.b64decode(xdr) + xdr_unpacked = pack.StellarXDRUnpacker(xdr_decoded) + return xdr_unpacked.unpack_FeeBumpTransaction() + + def __repr__(self): + out = [] + if self.feeSource is not None: + out += ['feeSource=%s' % repr(self.feeSource)] + if self.fee is not None: + out += ['fee=%s' % repr(self.fee)] + if self.innerTx is not None: + out += ['innerTx=%s' % repr(self.innerTx)] + if self.ext is not None: + out += ['ext=%s' % repr(self.ext)] + return 'FeeBumpTransaction(%s)' % ', '.join(out) + __str__ = __repr__ + +class FeeBumpTransactionEnvelope: + # XDR definition: + # struct FeeBumpTransactionEnvelope { + # FeeBumpTransaction tx; # DecoratedSignature signatures<20>; # }; def __init__(self, tx=None, signatures=None): @@ -2571,15 +2700,15 @@ def __getattr__(self, attr): return getattr(self.tx, attr) def to_xdr(self): - transactionenvelope = pack.StellarXDRPacker() - transactionenvelope.pack_TransactionEnvelope(self) - return base64.b64encode(transactionenvelope.get_buffer()).decode() + feebumptransactionenvelope = pack.StellarXDRPacker() + feebumptransactionenvelope.pack_FeeBumpTransactionEnvelope(self) + return base64.b64encode(feebumptransactionenvelope.get_buffer()).decode() @staticmethod def from_xdr(xdr): xdr_decoded = base64.b64decode(xdr) xdr_unpacked = pack.StellarXDRUnpacker(xdr_decoded) - return xdr_unpacked.unpack_TransactionEnvelope() + return xdr_unpacked.unpack_FeeBumpTransactionEnvelope() def __repr__(self): out = [] @@ -2587,9 +2716,96 @@ def __repr__(self): out += ['tx=%s' % repr(self.tx)] if self.signatures is not None: out += ['signatures=%s' % repr(self.signatures)] + return 'FeeBumpTransactionEnvelope(%s)' % ', '.join(out) + __str__ = __repr__ + +class TransactionEnvelope: + # XDR definition: + # union TransactionEnvelope switch(EnvelopeType type) { + # case ENVELOPE_TYPE_TX_V0: + # TransactionV0Envelope v0; + # case ENVELOPE_TYPE_TX: + # TransactionV1Envelope v1; + # case ENVELOPE_TYPE_TX_FEE_BUMP: + # FeeBumpTransactionEnvelope feeBump; + # }; + def __init__(self, type=None, v0=None, v1=None, feeBump=None): + self.type = type + self.v0 = v0 + self.v1 = v1 + self.feeBump = feeBump + + switch = property(lambda s: {const.ENVELOPE_TYPE_TX_V0:s.v0,const.ENVELOPE_TYPE_TX:s.v1,const.ENVELOPE_TYPE_TX_FEE_BUMP:s.feeBump,}[s.type]) + + def to_xdr(self): + transactionenvelope = pack.StellarXDRPacker() + transactionenvelope.pack_TransactionEnvelope(self) + return base64.b64encode(transactionenvelope.get_buffer()).decode() + + @staticmethod + def from_xdr(xdr): + xdr_decoded = base64.b64decode(xdr) + xdr_unpacked = pack.StellarXDRUnpacker(xdr_decoded) + return xdr_unpacked.unpack_TransactionEnvelope() + + def __getattr__(self, attr): + if attr is '__setstate__': + raise AttributeError + return getattr(self.switch, attr) + + def __repr__(self): + out = [] + if self.type is not None: + out += ['type=%s' % const.EnvelopeType.get(self.type, self.type)] + if self.v0 is not None: + out += ['v0=%s' % repr(self.v0)] + if self.v1 is not None: + out += ['v1=%s' % repr(self.v1)] + if self.feeBump is not None: + out += ['feeBump=%s' % repr(self.feeBump)] return 'TransactionEnvelope(%s)' % ', '.join(out) __str__ = __repr__ +class TransactionSignaturePayload: + # XDR definition: + # struct TransactionSignaturePayload { + # Hash networkId; + # union switch(EnvelopeType type) { + # case ENVELOPE_TYPE_TX: + # Transaction tx; + # case ENVELOPE_TYPE_TX_FEE_BUMP: + # FeeBumpTransaction feeBump; + # } taggedTransaction; + # }; + def __init__(self, networkId=None, taggedTransaction=None): + self.networkId = networkId + self.taggedTransaction = taggedTransaction + + def __getattr__(self, attr): + if attr is '__setstate__': + raise AttributeError + return getattr(self.taggedTransaction, attr) + + def to_xdr(self): + transactionsignaturepayload = pack.StellarXDRPacker() + transactionsignaturepayload.pack_TransactionSignaturePayload(self) + return base64.b64encode(transactionsignaturepayload.get_buffer()).decode() + + @staticmethod + def from_xdr(xdr): + xdr_decoded = base64.b64decode(xdr) + xdr_unpacked = pack.StellarXDRUnpacker(xdr_decoded) + return xdr_unpacked.unpack_TransactionSignaturePayload() + + def __repr__(self): + out = [] + if self.networkId is not None: + out += ['networkId=%s' % repr(self.networkId)] + if self.taggedTransaction is not None: + out += ['taggedTransaction=%s' % repr(self.taggedTransaction)] + return 'TransactionSignaturePayload(%s)' % ', '.join(out) + __str__ = __repr__ + class ClaimOfferAtom: # XDR definition: # struct ClaimOfferAtom { @@ -3316,11 +3532,102 @@ def __repr__(self): return 'OperationResult(%s)' % ', '.join(out) __str__ = __repr__ +class InnerTransactionResult: + # XDR definition: + # struct InnerTransactionResult { + # int64 feeCharged; + # union switch(TransactionResultCode code) { + # case txSUCCESS: + # case txFAILED: + # OperationResult results<>; + # case txTOO_EARLY: + # case txTOO_LATE: + # case txMISSING_OPERATION: + # case txBAD_SEQ: + # case txBAD_AUTH: + # case txINSUFFICIENT_BALANCE: + # case txNO_ACCOUNT: + # case txINSUFFICIENT_FEE: + # case txBAD_AUTH_EXTRA: + # case txINTERNAL_ERROR: + # case txNOT_SUPPORTED: + # void; + # } result; + # union switch(int v) { + # case 0: + # void; + # } ext; + # }; + def __init__(self, feeCharged=None, result=None, ext=None): + self.feeCharged = feeCharged + self.result = result + self.ext = ext + + def to_xdr(self): + innertransactionresult = pack.StellarXDRPacker() + innertransactionresult.pack_InnerTransactionResult(self) + return base64.b64encode(innertransactionresult.get_buffer()).decode() + + @staticmethod + def from_xdr(xdr): + xdr_decoded = base64.b64decode(xdr) + xdr_unpacked = pack.StellarXDRUnpacker(xdr_decoded) + return xdr_unpacked.unpack_InnerTransactionResult() + + def __repr__(self): + out = [] + if self.feeCharged is not None: + out += ['feeCharged=%s' % repr(self.feeCharged)] + if self.result is not None: + out += ['result=%s' % repr(self.result)] + if self.ext is not None: + out += ['ext=%s' % repr(self.ext)] + return 'InnerTransactionResult(%s)' % ', '.join(out) + __str__ = __repr__ + +class InnerTransactionResultPair: + # XDR definition: + # struct InnerTransactionResultPair { + # Hash transactionHash; + # InnerTransactionResult result; + # }; + def __init__(self, transactionHash=None, result=None): + self.transactionHash = transactionHash + self.result = result + + def __getattr__(self, attr): + if attr is '__setstate__': + raise AttributeError + return getattr(self.result, attr) + + def to_xdr(self): + innertransactionresultpair = pack.StellarXDRPacker() + innertransactionresultpair.pack_InnerTransactionResultPair(self) + return base64.b64encode(innertransactionresultpair.get_buffer()).decode() + + @staticmethod + def from_xdr(xdr): + xdr_decoded = base64.b64decode(xdr) + xdr_unpacked = pack.StellarXDRUnpacker(xdr_decoded) + return xdr_unpacked.unpack_InnerTransactionResultPair() + + def __repr__(self): + out = [] + if self.transactionHash is not None: + out += ['transactionHash=%s' % repr(self.transactionHash)] + if self.result is not None: + out += ['result=%s' % repr(self.result)] + return 'InnerTransactionResultPair(%s)' % ', '.join(out) + __str__ = __repr__ + class TransactionResult: # XDR definition: # struct TransactionResult { # int64 feeCharged; # union switch(TransactionResultCode code) { + # case txFEE_BUMP_INNER_SUCCESS: + # case txFEE_BUMP_INNER_FAILED: + # InnerTransactionResultPair innerResultPair; # case txSUCCESS: # case txFAILED: # OperationResult results<>; From 6238f63daab1dc277022474a21b7e82033501af9 Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Tue, 14 Apr 2020 19:30:28 +0800 Subject: [PATCH 3/8] chore: update pipenv python version. --- Pipfile | 2 +- Pipfile.lock | 144 +++++++++++++++++++++------------------------------ 2 files changed, 60 insertions(+), 86 deletions(-) diff --git a/Pipfile b/Pipfile index a3970164..86ebc276 100644 --- a/Pipfile +++ b/Pipfile @@ -25,7 +25,7 @@ mnemonic = "*" toml = "*" [requires] -python_version = "3.7" +python_version = "3.8" [pipenv] allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock index f9a76994..78beba04 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "6791cb4dc33ff91a6b3717431515036291d610e82fdb2fb7abbfe6edb5e4b9fa" + "sha256": "a17bc7bbc5f4bc8ccc4c19d6b85e8ef0b3f8aa60948bb0c8d787c987e93da70b" }, "pipfile-spec": 6, "requires": { - "python_version": "3.7" + "python_version": "3.8" }, "sources": [ { @@ -285,39 +285,39 @@ }, "coverage": { "hashes": [ - "sha256:03f630aba2b9b0d69871c2e8d23a69b7fe94a1e2f5f10df5049c0df99db639a0", - "sha256:046a1a742e66d065d16fb564a26c2a15867f17695e7f3d358d7b1ad8a61bca30", - "sha256:0a907199566269e1cfa304325cc3b45c72ae341fbb3253ddde19fa820ded7a8b", - "sha256:165a48268bfb5a77e2d9dbb80de7ea917332a79c7adb747bd005b3a07ff8caf0", - "sha256:1b60a95fc995649464e0cd48cecc8288bac5f4198f21d04b8229dc4097d76823", - "sha256:1f66cf263ec77af5b8fe14ef14c5e46e2eb4a795ac495ad7c03adc72ae43fafe", - "sha256:2e08c32cbede4a29e2a701822291ae2bc9b5220a971bba9d1e7615312efd3037", - "sha256:3844c3dab800ca8536f75ae89f3cf566848a3eb2af4d9f7b1103b4f4f7a5dad6", - "sha256:408ce64078398b2ee2ec08199ea3fcf382828d2f8a19c5a5ba2946fe5ddc6c31", - "sha256:443be7602c790960b9514567917af538cac7807a7c0c0727c4d2bbd4014920fd", - "sha256:4482f69e0701139d0f2c44f3c395d1d1d37abd81bfafbf9b6efbe2542679d892", - "sha256:4a8a259bf990044351baf69d3b23e575699dd60b18460c71e81dc565f5819ac1", - "sha256:513e6526e0082c59a984448f4104c9bf346c2da9961779ede1fc458e8e8a1f78", - "sha256:5f587dfd83cb669933186661a351ad6fc7166273bc3e3a1531ec5c783d997aac", - "sha256:62061e87071497951155cbccee487980524d7abea647a1b2a6eb6b9647df9006", - "sha256:641e329e7f2c01531c45c687efcec8aeca2a78a4ff26d49184dce3d53fc35014", - "sha256:65a7e00c00472cd0f59ae09d2fb8a8aaae7f4a0cf54b2b74f3138d9f9ceb9cb2", - "sha256:6ad6ca45e9e92c05295f638e78cd42bfaaf8ee07878c9ed73e93190b26c125f7", - "sha256:73aa6e86034dad9f00f4bbf5a666a889d17d79db73bc5af04abd6c20a014d9c8", - "sha256:7c9762f80a25d8d0e4ab3cb1af5d9dffbddb3ee5d21c43e3474c84bf5ff941f7", - "sha256:85596aa5d9aac1bf39fe39d9fa1051b0f00823982a1de5766e35d495b4a36ca9", - "sha256:86a0ea78fd851b313b2e712266f663e13b6bc78c2fb260b079e8b67d970474b1", - "sha256:8a620767b8209f3446197c0e29ba895d75a1e272a36af0786ec70fe7834e4307", - "sha256:922fb9ef2c67c3ab20e22948dcfd783397e4c043a5c5fa5ff5e9df5529074b0a", - "sha256:9fad78c13e71546a76c2f8789623eec8e499f8d2d799f4b4547162ce0a4df435", - "sha256:a37c6233b28e5bc340054cf6170e7090a4e85069513320275a4dc929144dccf0", - "sha256:c3fc325ce4cbf902d05a80daa47b645d07e796a80682c1c5800d6ac5045193e5", - "sha256:cda33311cb9fb9323958a69499a667bd728a39a7aa4718d7622597a44c4f1441", - "sha256:db1d4e38c9b15be1521722e946ee24f6db95b189d1447fa9ff18dd16ba89f732", - "sha256:eda55e6e9ea258f5e4add23bcf33dc53b2c319e70806e180aecbff8d90ea24de", - "sha256:f372cdbb240e09ee855735b9d85e7f50730dcfb6296b74b95a3e5dea0615c4c1" - ], - "version": "==5.0.4" + "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a", + "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355", + "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65", + "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7", + "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9", + "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1", + "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0", + "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55", + "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c", + "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6", + "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef", + "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019", + "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e", + "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0", + "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf", + "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24", + "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2", + "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c", + "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4", + "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0", + "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd", + "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04", + "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e", + "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730", + "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2", + "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768", + "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796", + "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7", + "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a", + "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489", + "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052" + ], + "version": "==5.1" }, "docutils": { "hashes": [ @@ -340,14 +340,6 @@ ], "version": "==1.2.0" }, - "importlib-metadata": { - "hashes": [ - "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", - "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" - ], - "markers": "python_version < '3.8'", - "version": "==1.6.0" - }, "jinja2": { "hashes": [ "sha256:c10142f819c2d22bdcd17548c46fa9b77cf4fda45097854c689666bf425e7484", @@ -357,41 +349,30 @@ }, "markupsafe": { "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", - "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" - ], - "version": "==1.1.1" + "sha256:06358015a4dee8ee23ae426bf885616ab3963622defd829eb45b44e3dee3515f", + "sha256:0b0c4fc852c5f02c6277ef3b33d23fcbe89b1b227460423e3335374da046b6db", + "sha256:267677fc42afed5094fc5ea1c4236bbe4b6a00fe4b08e93451e65ae9048139c7", + "sha256:303cb70893e2c345588fb5d5b86e0ca369f9bb56942f03064c5e3e75fa7a238a", + "sha256:3c9b624a0d9ed5a5093ac4edc4e823e6b125441e60ef35d36e6f4a6fdacd5054", + "sha256:42033e14cae1f6c86fc0c3e90d04d08ce73ac8e46ba420a0d22d545c2abd4977", + "sha256:4e4a99b6af7bdc0856b50020c095848ec050356a001e1f751510aef6ab14d0e0", + "sha256:4eb07faad54bb07427d848f31030a65a49ebb0cec0b30674f91cf1ddd456bfe4", + "sha256:63a7161cd8c2bc563feeda45df62f42c860dd0675e2b8da2667f25bb3c95eaba", + "sha256:68e0fd039b68d2945b4beb947d4023ca7f8e95b708031c345762efba214ea761", + "sha256:8092a63397025c2f655acd42784b2a1528339b90b987beb9253f22e8cdbb36c3", + "sha256:841218860683c0f2223e24756843d84cc49cccdae6765e04962607754a52d3e0", + "sha256:94076b2314bd2f6cfae508ad65b4d493e3a58a50112b7a2cbb6287bdbc404ae8", + "sha256:9d22aff1c5322e402adfb3ce40839a5056c353e711c033798cf4f02eb9f5124d", + "sha256:b0e4584f62b3e5f5c1a7bcefd2b52f236505e6ef032cc508caa4f4c8dc8d3af1", + "sha256:b1163ffc1384d242964426a8164da12dbcdbc0de18ea36e2c34b898ed38c3b45", + "sha256:beac28ed60c8e838301226a7a85841d0af2068eba2dcb1a58c2d32d6c05e440e", + "sha256:c29f096ce79c03054a1101d6e5fe6bf04b0bb489165d5e0e9653fb4fe8048ee1", + "sha256:c58779966d53e5f14ba393d64e2402a7926601d1ac8adeb4e83893def79d0428", + "sha256:cfe14b37908eaf7d5506302987228bff69e1b8e7071ccd4e70fd0283b1b47f0b", + "sha256:e834249c45aa9837d0753351cdca61a4b8b383cc9ad0ff2325c97ff7b69e72a6", + "sha256:eed1b234c4499811ee85bcefa22ef5e466e75d132502226ed29740d593316c1f" + ], + "version": "==2.0.0a1" }, "more-itertools": { "hashes": [ @@ -598,13 +579,6 @@ "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" ], "version": "==1.0.1" - }, - "zipp": { - "hashes": [ - "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", - "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" - ], - "version": "==3.1.0" } } } From 50eb2874b6d32156601a2d4b8906394cb110f33c Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Wed, 15 Apr 2020 22:30:29 +0800 Subject: [PATCH 4/8] feat: Add support for CAP-0015 (FeeBumpTransaction). --- stellar_sdk/fee_bump_transaction.py | 69 +++++++++ stellar_sdk/server.py | 2 +- stellar_sdk/transaction.py | 60 +++++--- stellar_sdk/transaction_builder.py | 209 +++++++++++++++------------- stellar_sdk/transaction_envelope.py | 47 +++++-- tests/test_transaction.py | 11 +- 6 files changed, 265 insertions(+), 133 deletions(-) create mode 100644 stellar_sdk/fee_bump_transaction.py diff --git a/stellar_sdk/fee_bump_transaction.py b/stellar_sdk/fee_bump_transaction.py new file mode 100644 index 00000000..56a60907 --- /dev/null +++ b/stellar_sdk/fee_bump_transaction.py @@ -0,0 +1,69 @@ +from typing import Union + +from .strkey import StrKey +from .transaction import Transaction +from .exceptions import ValueError +from .xdr import Xdr +from .keypair import Keypair + +BASE_FEE = 100 + + +class FeeBumpTransaction: + def __init__(self, + fee_source: Union[Keypair, str], + base_fee: int, + inner_transaction: Transaction) -> None: + if not inner_transaction.v1: + raise ValueError("Invalid `inner_transaction`, it should be TransactionV1.") + + if isinstance(fee_source, Keypair): + self.fee_source = fee_source + else: + self.fee_source = Keypair.from_public_key(fee_source) + + inner_operations_length = len(self.inner_transaction.operations) + inner_base_fee_rate = self.inner_transaction.fee / inner_operations_length + + if self.base_fee < inner_base_fee_rate or self.base_fee < BASE_FEE: + raise ValueError("Invalid `base_fee`, it should be at least %d stroops.", inner_operations_length) + + self.base_fee = base_fee + self.inner_transaction = inner_transaction + + def to_xdr_object(self) -> Xdr.types.FeeBumpTransaction: + """Get an XDR object representation of this :class:`FeeBumpTransaction`. + + :return: XDR Transaction object + """ + fee_source = self.fee_source.xdr_account_id() + fee = self.base_fee * (self.inner_transaction.operations + 1) + ext = Xdr.nullclass() + ext.v = 0 + return Xdr.types.FeeBumpTransaction(feeSource=fee_source, fee=fee, innerTx=self.inner_transaction, ext=ext) + + @classmethod + def from_xdr_object(cls, tx_xdr_object: Xdr.types.FeeBumpTransaction) -> "FeeBumpTransaction": + """Create a new :class:`FeeBumpTransaction` from an XDR object. + + :param tx_xdr_object: The XDR object that represents a transaction. + + :return: A new :class:`FeeBumpTransaction` object from the given XDR Transaction object. + """ + source = Keypair.from_public_key( + StrKey.encode_ed25519_public_key(tx_xdr_object.feeSource.ed25519) + ) + base_fee = tx_xdr_object.fee + inner_transaction = Transaction.from_xdr_object(tx_xdr_object=tx_xdr_object.innerTx, v1=True) + return cls(fee_source=source, base_fee=base_fee, inner_transaction=inner_transaction) + + @classmethod + def from_xdr(cls, xdr: str) -> "FeeBumpTransaction": + """Create a new :class:`Transaction` from an XDR string. + + :param xdr: The XDR string that represents a transaction. + + :return: A new :class:`TransactionEnvelope` object from the given XDR TransactionEnvelope base64 string object. + """ + xdr_object = Xdr.types.FeeBumpTransaction.from_xdr(xdr) + return cls.from_xdr_object(xdr_object) diff --git a/stellar_sdk/server.py b/stellar_sdk/server.py index 6da6331e..d78450c7 100644 --- a/stellar_sdk/server.py +++ b/stellar_sdk/server.py @@ -140,7 +140,7 @@ def __get_xdr_and_transaction_from_transaction_envelope( tx = transaction_envelope.transaction else: xdr = transaction_envelope - tx = Transaction.from_xdr(xdr) + tx = TransactionEnvelope.from_xdr(xdr, "").transaction return xdr, tx def root(self) -> RootCallBuilder: diff --git a/stellar_sdk/transaction.py b/stellar_sdk/transaction.py index b3c1df00..546aec87 100644 --- a/stellar_sdk/transaction.py +++ b/stellar_sdk/transaction.py @@ -43,16 +43,20 @@ class Transaction: :param memo: The memo being sent with the transaction, being represented as one of the subclasses of the :class:`Memo ` object. + :param v1: Temporary feature flag to allow alpha testing of Stellar Protocol 13 transactions. + We will remove this once all transactions are supposed to be v1. + See `CAP-0015 `_ for more information. """ def __init__( - self, - source: Union[Keypair, str], - sequence: int, - fee: int, - operations: List[Operation], - memo: Memo = None, - time_bounds: TimeBounds = None, + self, + source: Union[Keypair, str], + sequence: int, + fee: int, + operations: List[Operation], + memo: Memo = None, + time_bounds: TimeBounds = None, + v1: bool = False ) -> None: # if not operations: @@ -69,13 +73,13 @@ def __init__( self.memo: Memo = memo self.fee: int = fee self.time_bounds: TimeBounds = time_bounds + self.v1: bool = v1 - def to_xdr_object(self) -> Xdr.types.Transaction: + def to_xdr_object(self) -> Union[Xdr.types.Transaction, Xdr.types.TransactionV0]: """Get an XDR object representation of this :class:`Transaction`. :return: XDR Transaction object """ - source_account = self.source.xdr_account_id() memo = self.memo.to_xdr_object() operations = [operation.to_xdr_object() for operation in self.operations] time_bounds: List[TimeBounds] = [] @@ -83,21 +87,32 @@ def to_xdr_object(self) -> Xdr.types.Transaction: time_bounds = pack_xdr_array(self.time_bounds.to_xdr_object()) ext = Xdr.nullclass() ext.v = 0 - return Xdr.types.Transaction( - source_account, self.fee, self.sequence, time_bounds, memo, operations, ext - ) + if self.v1: + return Xdr.types.Transaction(self.source.xdr_account_id(), self.fee, self.sequence, time_bounds, memo, operations, ext) + return Xdr.types.TransactionV0(self.source.xdr_account_id().ed25519, self.fee, self.sequence, time_bounds, memo, operations, + ext) @classmethod - def from_xdr_object(cls, tx_xdr_object) -> "Transaction": + def from_xdr_object(cls, tx_xdr_object: Union[Xdr.types.Transaction, Xdr.types.TransactionV0], + v1: bool = False) -> "Transaction": """Create a new :class:`Transaction` from an XDR object. :param tx_xdr_object: The XDR object that represents a transaction. + :param v1: Temporary feature flag to allow alpha testing of Stellar Protocol 13 transactions. + We will remove this once all transactions are supposed to be v1. + See `CAP-0015 `_ + for more information. :return: A new :class:`Transaction` object from the given XDR Transaction object. """ - source = Keypair.from_public_key( - StrKey.encode_ed25519_public_key(tx_xdr_object.sourceAccount.ed25519) - ) + if v1: + source = Keypair.from_public_key( + StrKey.encode_ed25519_public_key(tx_xdr_object.sourceAccount.ed25519) + ) + else: + source = Keypair.from_public_key( + StrKey.encode_ed25519_public_key(tx_xdr_object.sourceAccountEd25519) + ) sequence = tx_xdr_object.seqNum fee = tx_xdr_object.fee time_bounds_in_xdr = tx_xdr_object.timeBounds @@ -119,12 +134,19 @@ def from_xdr_object(cls, tx_xdr_object) -> "Transaction": ) @classmethod - def from_xdr(cls, xdr: str) -> "Transaction": + def from_xdr(cls, xdr: str, v1: bool = False) -> "Transaction": """Create a new :class:`Transaction` from an XDR string. :param xdr: The XDR string that represents a transaction. + :param v1: Temporary feature flag to allow alpha testing of Stellar Protocol 13 transactions. + We will remove this once all transactions are supposed to be v1. + See `CAP-0015 `_ + for more information. :return: A new :class:`TransactionEnvelope` object from the given XDR TransactionEnvelope base64 string object. """ - xdr_object = Xdr.types.Transaction.from_xdr(xdr) - return cls.from_xdr_object(xdr_object) + if v1: + xdr_object = Xdr.types.Transaction.from_xdr(xdr) + else: + xdr_object = Xdr.types.TransactionV0.from_xdr(xdr) + return cls.from_xdr_object(xdr_object, v1) diff --git a/stellar_sdk/transaction_builder.py b/stellar_sdk/transaction_builder.py index 0df67480..896a09d2 100644 --- a/stellar_sdk/transaction_builder.py +++ b/stellar_sdk/transaction_builder.py @@ -16,10 +16,10 @@ from .time_bounds import TimeBounds from .transaction import Transaction from .transaction_envelope import TransactionEnvelope +from .fee_bump_transaction import FeeBumpTransaction __all__ = ["TransactionBuilder"] - class TransactionBuilder: """Transaction builder helps constructs a new :class:`TransactionEnvelope ` using the given @@ -39,14 +39,18 @@ class TransactionBuilder: Defaults to **Test SDF Network ; September 2015**. :param base_fee: Base fee in stroops. The network base fee is obtained by default from the latest ledger. Transaction fee is equal to base fee times number of operations in this transaction. + :param v1: Temporary feature flag to allow alpha testing of Stellar Protocol 13 transactions. + We will remove this once all transactions are supposed to be v1. + See `CAP-0015 `_ for more information. """ # TODO: add an example def __init__( - self, - source_account: Account, - network_passphrase: str = Network.TESTNET_NETWORK_PASSPHRASE, - base_fee: int = 100, + self, + source_account: Account, + network_passphrase: str = Network.TESTNET_NETWORK_PASSPHRASE, + base_fee: int = 100, + v1: bool = False ): self.source_account: Account = source_account self.base_fee: int = base_fee @@ -54,6 +58,7 @@ def __init__( self.operations: List[Operation] = [] self.time_bounds: Optional[TimeBounds] = None self.memo: Memo = NoneMemo() + self.v1: bool = v1 def build(self) -> TransactionEnvelope: """This will build the transaction envelope. @@ -70,6 +75,7 @@ def build(self) -> TransactionEnvelope: operations=self.operations, memo=self.memo, time_bounds=self.time_bounds, + v1=self.v1 ) transaction_envelope = TransactionEnvelope( transaction=transaction, network_passphrase=self.network_passphrase @@ -77,6 +83,19 @@ def build(self) -> TransactionEnvelope: self.source_account.increment_sequence_number() return transaction_envelope + @staticmethod + def build_fee_bump_transaction( + fee_source: [Keypair, str], + base_fee: int, + inner_transaction: Transaction, + network_passphrase: str = Network.TESTNET_NETWORK_PASSPHRASE + ): + transaction = FeeBumpTransaction(fee_source=fee_source, base_fee=base_fee, inner_transaction=inner_transaction) + transaction_envelope = TransactionEnvelope( + transaction=transaction, network_passphrase=network_passphrase + ) + return transaction_envelope + @staticmethod def from_xdr(xdr: str, network_passphrase: str) -> "TransactionBuilder": """Create a :class:`TransactionBuilder @@ -194,7 +213,7 @@ def add_hash_memo(self, memo_hash: Union[bytes, str]) -> "TransactionBuilder": return self.add_memo(memo) def add_return_hash_memo( - self, memo_return: Union[bytes, str] + self, memo_return: Union[bytes, str] ) -> "TransactionBuilder": """Set the memo for the transaction to a new :class:`RetHashMemo `. @@ -220,10 +239,10 @@ def append_operation(self, operation: Operation) -> "TransactionBuilder": return self def append_create_account_op( - self, - destination: str, - starting_balance: Union[str, Decimal], - source: str = None, + self, + destination: str, + starting_balance: Union[str, Decimal], + source: str = None, ) -> "TransactionBuilder": """Append a :class:`CreateAccount ` operation to the list of @@ -241,11 +260,11 @@ def append_create_account_op( return self.append_operation(op) def append_change_trust_op( - self, - asset_code: str, - asset_issuer: str, - limit: Union[str, Decimal] = None, - source: str = None, + self, + asset_code: str, + asset_issuer: str, + limit: Union[str, Decimal] = None, + source: str = None, ) -> "TransactionBuilder": """Append a :class:`ChangeTrust ` operation to the list of operations. @@ -262,12 +281,12 @@ def append_change_trust_op( return self.append_operation(op) def append_payment_op( - self, - destination: str, - amount: Union[str, Decimal], - asset_code: str = "XLM", - asset_issuer: Optional[str] = None, - source: str = None, + self, + destination: str, + amount: Union[str, Decimal], + asset_code: str = "XLM", + asset_issuer: Optional[str] = None, + source: str = None, ) -> "TransactionBuilder": """Append a :class:`Payment ` operation to the list of operations. @@ -285,16 +304,16 @@ def append_payment_op( return self.append_operation(op) def append_path_payment_op( - self, - destination: str, - send_code: str, - send_issuer: Optional[str], - send_max: Union[str, Decimal], - dest_code: str, - dest_issuer: Optional[str], - dest_amount: Union[str, Decimal], - path: List[Asset], - source=None, + self, + destination: str, + send_code: str, + send_issuer: Optional[str], + send_max: Union[str, Decimal], + dest_code: str, + dest_issuer: Optional[str], + dest_amount: Union[str, Decimal], + path: List[Asset], + source=None, ) -> "TransactionBuilder": """Append a :class:`PathPayment ` operation to the list of operations. @@ -341,16 +360,16 @@ def append_path_payment_op( return self.append_operation(op) def append_path_payment_strict_receive_op( - self, - destination: str, - send_code: str, - send_issuer: Optional[str], - send_max: Union[str, Decimal], - dest_code: str, - dest_issuer: Optional[str], - dest_amount: Union[str, Decimal], - path: List[Asset], - source=None, + self, + destination: str, + send_code: str, + send_issuer: Optional[str], + send_max: Union[str, Decimal], + dest_code: str, + dest_issuer: Optional[str], + dest_amount: Union[str, Decimal], + path: List[Asset], + source=None, ) -> "TransactionBuilder": """Append a :class:`PathPaymentStrictReceive ` operation to the list of operations. @@ -391,16 +410,16 @@ def append_path_payment_strict_receive_op( return self.append_operation(op) def append_path_payment_strict_send_op( - self, - destination: str, - send_code: str, - send_issuer: Optional[str], - send_amount: Union[str, Decimal], - dest_code: str, - dest_issuer: Optional[str], - dest_min: Union[str, Decimal], - path: List[Asset], - source=None, + self, + destination: str, + send_code: str, + send_issuer: Optional[str], + send_amount: Union[str, Decimal], + dest_code: str, + dest_issuer: Optional[str], + dest_min: Union[str, Decimal], + path: List[Asset], + source=None, ) -> "TransactionBuilder": """Append a :class:`PathPaymentStrictSend ` operation to the list of operations. @@ -439,7 +458,7 @@ def append_path_payment_strict_send_op( return self.append_operation(op) def append_allow_trust_op( - self, trustor: str, asset_code: str, authorize: bool, source: str = None + self, trustor: str, asset_code: str, authorize: bool, source: str = None ) -> "TransactionBuilder": """Append an :class:`AllowTrust ` operation to the list of operations. @@ -459,17 +478,17 @@ def append_allow_trust_op( return self.append_operation(op) def append_set_options_op( - self, - inflation_dest: str = None, - clear_flags: Union[int, Flag] = None, - set_flags: Union[int, Flag] = None, - master_weight: int = None, - low_threshold: int = None, - med_threshold: int = None, - high_threshold: int = None, - home_domain: str = None, - signer: Signer = None, - source: str = None, + self, + inflation_dest: str = None, + clear_flags: Union[int, Flag] = None, + set_flags: Union[int, Flag] = None, + master_weight: int = None, + low_threshold: int = None, + med_threshold: int = None, + high_threshold: int = None, + home_domain: str = None, + signer: Signer = None, + source: str = None, ) -> "TransactionBuilder": """Append a :class:`SetOptions ` operation to the list of operations. @@ -521,7 +540,7 @@ def append_set_options_op( return self.append_operation(op) def append_ed25519_public_key_signer( - self, account_id: str, weight: int, source=None + self, account_id: str, weight: int, source=None ) -> "TransactionBuilder": """Add a ed25519 public key signer to an account. @@ -540,7 +559,7 @@ def append_ed25519_public_key_signer( return self.append_set_options_op(signer=signer, source=source) def append_hashx_signer( - self, sha256_hash: [bytes, str], weight: int, source=None + self, sha256_hash: [bytes, str], weight: int, source=None ) -> "TransactionBuilder": """Add a sha256 hash(HashX) signer to an account. @@ -562,7 +581,7 @@ def append_hashx_signer( return self.append_set_options_op(signer=signer, source=source) def append_pre_auth_tx_signer( - self, pre_auth_tx_hash: bytes, weight: int, source=None + self, pre_auth_tx_hash: bytes, weight: int, source=None ) -> "TransactionBuilder": """Add a PreAuthTx signer to an account. @@ -585,15 +604,15 @@ def append_pre_auth_tx_signer( return self.append_set_options_op(signer=signer, source=source) def append_manage_buy_offer_op( - self, - selling_code: str, - selling_issuer: Optional[str], - buying_code: str, - buying_issuer: Optional[str], - amount: Union[str, Decimal], - price: Union[str, Decimal, Price], - offer_id: int = 0, - source: str = None, + self, + selling_code: str, + selling_issuer: Optional[str], + buying_code: str, + buying_issuer: Optional[str], + amount: Union[str, Decimal], + price: Union[str, Decimal, Price], + offer_id: int = 0, + source: str = None, ) -> "TransactionBuilder": """Append a :class:`ManageBuyOffer ` operation to the list of operations. @@ -629,15 +648,15 @@ def append_manage_buy_offer_op( return self.append_operation(op) def append_manage_sell_offer_op( - self, - selling_code: str, - selling_issuer: Optional[str], - buying_code: str, - buying_issuer: Optional[str], - amount: Union[str, Decimal], - price: Union[str, Price, Decimal], - offer_id: int = 0, - source: str = None, + self, + selling_code: str, + selling_issuer: Optional[str], + buying_code: str, + buying_issuer: Optional[str], + amount: Union[str, Decimal], + price: Union[str, Price, Decimal], + offer_id: int = 0, + source: str = None, ) -> "TransactionBuilder": """Append a :class:`ManageSellOffer ` operation to the list of operations. @@ -673,14 +692,14 @@ def append_manage_sell_offer_op( return self.append_operation(op) def append_create_passive_sell_offer_op( - self, - selling_code: str, - selling_issuer: Optional[str], - buying_code: str, - buying_issuer: Optional[str], - amount: Union[str, Decimal], - price: Union[str, Price, Decimal], - source: str = None, + self, + selling_code: str, + selling_issuer: Optional[str], + buying_code: str, + buying_issuer: Optional[str], + amount: Union[str, Decimal], + price: Union[str, Price, Decimal], + source: str = None, ) -> "TransactionBuilder": """Append a :class:`CreatePassiveSellOffer ` operation to the list of @@ -709,7 +728,7 @@ def append_create_passive_sell_offer_op( return self.append_operation(op) def append_account_merge_op( - self, destination: str, source: str = None + self, destination: str, source: str = None ) -> "TransactionBuilder": """Append a :class:`AccountMerge ` operation to the list of @@ -739,7 +758,7 @@ def append_inflation_op(self, source: str = None) -> "TransactionBuilder": return self.append_operation(op) def append_manage_data_op( - self, data_name: str, data_value: Union[str, bytes, None], source: str = None + self, data_name: str, data_value: Union[str, bytes, None], source: str = None ) -> "TransactionBuilder": """Append a :class:`ManageData ` operation to the list of operations. @@ -759,7 +778,7 @@ def append_manage_data_op( return self.append_operation(op) def append_bump_sequence_op( - self, bump_to: int, source: str = None + self, bump_to: int, source: str = None ) -> "TransactionBuilder": """Append a :class:`BumpSequence ` operation to the list of operations. diff --git a/stellar_sdk/transaction_envelope.py b/stellar_sdk/transaction_envelope.py index e32c8403..c6abfaa2 100644 --- a/stellar_sdk/transaction_envelope.py +++ b/stellar_sdk/transaction_envelope.py @@ -1,11 +1,12 @@ from typing import List, Union from .exceptions import SignatureExistError +from .fee_bump_transaction import FeeBumpTransaction from .keypair import Keypair from .network import Network -from .xdr import Xdr from .transaction import Transaction from .utils import sha256, hex_to_bytes +from .xdr import Xdr __all__ = ["TransactionEnvelope"] @@ -28,7 +29,7 @@ class handles signing and conversion to and from XDR for usage on Stellar's def __init__( self, - transaction: Transaction, + transaction: Union[Transaction, FeeBumpTransaction], network_passphrase: str, signatures: List[Xdr.types.DecoratedSignature] = None, ) -> None: @@ -91,12 +92,19 @@ def signature_base(self) -> bytes: """ network_id = self.network_id tx_type = Xdr.StellarXDRPacker() - tx_type.pack_EnvelopeType(Xdr.const.ENVELOPE_TYPE_TX) + tx_packer = Xdr.StellarXDRPacker() + tx = self.transaction + if isinstance(self.transaction, Transaction) and not self.transaction.v1: + tx = Transaction.from_xdr_object(self.transaction.to_xdr_object(), v1=False) + tx.v1 = True + if isinstance(self.transaction, FeeBumpTransaction): + tx_type.pack_EnvelopeType(Xdr.const.ENVELOPE_TYPE_TX_FEE_BUMP) + tx_packer.pack_FeeBumpTransaction(tx.to_xdr_object()) + else: + tx_type.pack_EnvelopeType(Xdr.const.ENVELOPE_TYPE_TX) + tx_packer.pack_Transaction(tx.to_xdr_object()) tx_type_buffer = tx_type.get_buffer() - - tx = Xdr.StellarXDRPacker() - tx.pack_Transaction(self.transaction.to_xdr_object()) - tx_buffer = tx.get_buffer() + tx_buffer = tx_packer.get_buffer() return network_id + tx_type_buffer + tx_buffer def sign_hashx(self, preimage: bytes) -> None: @@ -124,8 +132,18 @@ def to_xdr_object(self) -> Xdr.types.TransactionEnvelope: :return: XDR TransactionEnvelope object """ tx = self.transaction.to_xdr_object() - transaction_envelope = Xdr.types.TransactionEnvelope(tx, self.signatures) - return transaction_envelope + if isinstance(self.transaction, FeeBumpTransaction): + te_type = Xdr.const.ENVELOPE_TYPE_TX_FEE_BUMP + tx_envelope = Xdr.types.FeeBumpTransactionEnvelope(tx, self.signatures) + return Xdr.types.TransactionEnvelope(type=te_type, feeBump=tx_envelope) + if self.transaction.v1: + te_type = Xdr.const.ENVELOPE_TYPE_TX + tx_envelope = Xdr.types.TransactionV1Envelope(tx, self.signatures) + return Xdr.types.TransactionEnvelope(type=te_type, v1=tx_envelope) + else: + te_type = Xdr.const.ENVELOPE_TYPE_TX_V0 + tx_envelope = Xdr.types.TransactionV0Envelope(tx, self.signatures) + return Xdr.types.TransactionEnvelope(type=te_type, v0=tx_envelope) def to_xdr(self) -> str: """Get the base64 encoded XDR string representing this @@ -145,9 +163,16 @@ def from_xdr_object( :param network_passphrase: The network to connect to for verifying and retrieving additional attributes from. :return: A new :class:`TransactionEnvelope` object from the given XDR TransactionEnvelope object. """ + te_type = te_xdr_object.type + if te_type == Xdr.const.ENVELOPE_TYPE_TX_V0: + tx = Transaction.from_xdr_object(te_xdr_object.v0, v1=False) + elif te_type == Xdr.const.ENVELOPE_TYPE_TX: + tx = Transaction.from_xdr_object(te_xdr_object.v1, v1=True) + elif te_type == Xdr.const.ENVELOPE_TYPE_TX_FEE_BUMP: + tx = FeeBumpTransaction.from_xdr_object(te_xdr_object.feeBump) + else: + raise ValueError("Invalid EnvelopeType: %d.", te_xdr_object.type) signatures = te_xdr_object.signatures - tx_xdr_object = te_xdr_object.tx - tx = Transaction.from_xdr_object(tx_xdr_object) te = TransactionEnvelope( tx, network_passphrase=network_passphrase, signatures=signatures ) diff --git a/tests/test_transaction.py b/tests/test_transaction.py index 247d1b50..81c1f46a 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -1,8 +1,5 @@ -import pytest - from stellar_sdk import Keypair, IdMemo, Asset, NoneMemo from stellar_sdk.operation import Payment, ManageData -from stellar_sdk.exceptions import ValueError from stellar_sdk.time_bounds import TimeBounds from stellar_sdk.transaction import Transaction @@ -22,14 +19,14 @@ def test_to_xdr(self): ops = [Payment(destination, asset, amount), ManageData("hello", "world")] tx_object = Transaction( - source, sequence, fee, ops, memo, time_bounds + source, sequence, fee, ops, memo, time_bounds, True ).to_xdr_object() assert ( tx_object.to_xdr() == "AAAAAImbKEDtVjbFbdxfFLI5dfefG6I4jSaU5MVuzd3JYOXvAAAAyAAAAAAAAAABAAAAAQAAAAAAADA5AAAAAAAA3dUAAAACAAAAAAAAAGQAAAACAAAAAAAAAAEAAAAA0pjFgVcRZZHpMgnpXHpb/xIbLh0/YYto0PzI7+Xl5HAAAAAAAAAAAlQL5AAAAAAAAAAACgAAAAVoZWxsbwAAAAAAAAEAAAAFd29ybGQAAAAAAAAA" ) - restore_transaction = Transaction.from_xdr_object(tx_object) + restore_transaction = Transaction.from_xdr_object(tx_object, v1=True) assert isinstance(restore_transaction, Transaction) assert restore_transaction.source == source assert restore_transaction.fee == fee @@ -49,14 +46,14 @@ def test_to_xdr_str_source(self): ops = [Payment(destination, asset, amount), ManageData("hello", "world")] tx_object = Transaction( - source, sequence, fee, ops, memo, time_bounds + source, sequence, fee, ops, memo, time_bounds, True ).to_xdr_object() assert ( tx_object.to_xdr() == "AAAAAImbKEDtVjbFbdxfFLI5dfefG6I4jSaU5MVuzd3JYOXvAAAAyAAAAAAAAAABAAAAAQAAAAAAADA5AAAAAAAA3dUAAAACAAAAAAAAAGQAAAACAAAAAAAAAAEAAAAA0pjFgVcRZZHpMgnpXHpb/xIbLh0/YYto0PzI7+Xl5HAAAAAAAAAAAlQL5AAAAAAAAAAACgAAAAVoZWxsbwAAAAAAAAEAAAAFd29ybGQAAAAAAAAA" ) - restore_transaction = Transaction.from_xdr_object(tx_object) + restore_transaction = Transaction.from_xdr_object(tx_object, True) assert isinstance(restore_transaction, Transaction) assert restore_transaction.source == Keypair.from_public_key(source) assert restore_transaction.fee == fee From 037d8d579890bbeef68cfbf33fbf5b353a0bbd78 Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Fri, 17 Apr 2020 00:10:54 +0800 Subject: [PATCH 5/8] feat: add FeeBumpTransactionEnvelope --- stellar_sdk/__init__.py | 4 + stellar_sdk/base_transaction_envelope.py | 137 ++++++++++++ stellar_sdk/fee_bump_transaction.py | 71 ++++-- stellar_sdk/fee_bump_transaction_envelope.py | 94 ++++++++ stellar_sdk/memo.py | 4 +- stellar_sdk/server.py | 1 + stellar_sdk/transaction.py | 45 ++-- stellar_sdk/transaction_builder.py | 216 ++++++++++--------- stellar_sdk/transaction_envelope.py | 111 ++-------- stellar_sdk/xdr/StellarXDR_const.py | 2 +- stellar_sdk/xdr/StellarXDR_pack.py | 2 +- stellar_sdk/xdr/StellarXDR_type.py | 106 ++++----- stellar_sdk/xdr/xdrgen.py | 4 +- tests/test_fee_bump_transaction.py | 50 +++++ tests/test_transaction.py | 72 ++++++- 15 files changed, 619 insertions(+), 300 deletions(-) create mode 100644 stellar_sdk/base_transaction_envelope.py create mode 100644 stellar_sdk/fee_bump_transaction_envelope.py create mode 100644 tests/test_fee_bump_transaction.py diff --git a/stellar_sdk/__init__.py b/stellar_sdk/__init__.py index 7b596d1f..18bbc472 100644 --- a/stellar_sdk/__init__.py +++ b/stellar_sdk/__init__.py @@ -13,6 +13,8 @@ from .account import Account from .asset import Asset +from .fee_bump_transaction import FeeBumpTransaction +from .fee_bump_transaction_envelope import FeeBumpTransactionEnvelope from .keypair import Keypair from .memo import Memo, NoneMemo, TextMemo, IdMemo, HashMemo, ReturnHashMemo from .network import Network @@ -36,6 +38,8 @@ "__license__", "Account", "Asset", + "FeeBumpTransaction", + "FeeBumpTransactionEnvelope", "Keypair", "Memo", "NoneMemo", diff --git a/stellar_sdk/base_transaction_envelope.py b/stellar_sdk/base_transaction_envelope.py new file mode 100644 index 00000000..25ad4b2c --- /dev/null +++ b/stellar_sdk/base_transaction_envelope.py @@ -0,0 +1,137 @@ +from abc import abstractmethod +from typing import List, Union, Generic, TypeVar + +from .exceptions import SignatureExistError +from .keypair import Keypair +from .network import Network +from .utils import hex_to_bytes, sha256 +from .xdr import Xdr + +T = TypeVar("T") + + +class BaseTransactionEnvelope(Generic[T]): + def __init__( + self, + transaction, + network_passphrase: str, + signatures: List[Xdr.types.DecoratedSignature] = None, + ) -> None: + self.transaction = transaction + self.network_id: bytes = Network(network_passphrase).network_id() + self.signatures: List[Xdr.types.DecoratedSignature] = signatures or [] + + def hash(self) -> bytes: + """Get the XDR Hash of the signature base. + + This hash is ultimately what is signed before transactions are sent + over the network. See :meth:`signature_base` for more details about + this process. + + :return: The XDR Hash of this transaction envelope's signature base. + + """ + return sha256(self.signature_base()) + + def hash_hex(self) -> str: + """Return a hex encoded hash for this transaction envelope. + + :return: A hex encoded hash for this transaction envelope. + """ + return self.hash().hex() + + def sign(self, signer: Union[Keypair, str]) -> None: + """Sign this transaction envelope with a given keypair. + + Note that the signature must not already be in this instance's list of + signatures. + + :param signer: The keypair or secret to use for signing this transaction + envelope. + :raise: :exc:`SignatureExistError `: + if this signature already exists. + """ + if isinstance(signer, str): + signer = Keypair.from_secret(signer) + tx_hash = self.hash() + sig = signer.sign_decorated(tx_hash) + sig_dict = [signature.__dict__ for signature in self.signatures] + if sig.__dict__ in sig_dict: + raise SignatureExistError("The keypair has already signed.") + else: + self.signatures.append(sig) + + @abstractmethod + def signature_base(self) -> bytes: + """Get the signature base of this transaction envelope. + + Return the "signature base" of this transaction, which is the value + that, when hashed, should be signed to create a signature that + validators on the Stellar Network will accept. + + It is composed of a 4 prefix bytes followed by the xdr-encoded form of + this transaction. + + :return: The signature base of this transaction envelope. + + """ + raise NotImplementedError("The method has not been implemented.") + + def sign_hashx(self, preimage: bytes) -> None: + """Sign this transaction envelope with a Hash(x) signature. + + See Stellar's documentation on `Multi-Sig + `_ + for more details on Hash(x) signatures. + + :param preimage: 32 byte hash or hex encoded string , the "x" value to be hashed and used as a + signature. + """ + hash_preimage = sha256(hex_to_bytes(preimage)) + hint = hash_preimage[-4:] + sig = Xdr.types.DecoratedSignature(hint, preimage) + sig_dict = [signature.__dict__ for signature in self.signatures] + if sig.__dict__ in sig_dict: + raise SignatureExistError("The preimage has already signed.") + else: + self.signatures.append(sig) + + def to_xdr_object(self) -> Xdr.types.TransactionEnvelope: + """Get an XDR object representation of this :class:`TransactionEnvelope`. + + :return: XDR TransactionEnvelope object + """ + raise NotImplementedError("The method has not been implemented.") + + def to_xdr(self) -> str: + """Get the base64 encoded XDR string representing this + :class:`TransactionEnvelope`. + + :return: XDR TransactionEnvelope base64 string object + """ + return self.to_xdr_object().to_xdr() + + @classmethod + def from_xdr_object( + cls, te_xdr_object: Xdr.types.TransactionEnvelope, network_passphrase: str + ) -> T: + """Create a new :class:`TransactionEnvelope` from an XDR object. + + :param te_xdr_object: The XDR object that represents a transaction envelope. + :param network_passphrase: The network to connect to for verifying and retrieving additional attributes from. + :return: A new :class:`TransactionEnvelope` object from the given XDR TransactionEnvelope object. + """ + raise NotImplementedError("The method has not been implemented.") + + @classmethod + def from_xdr(cls, xdr: str, network_passphrase: str) -> T: + """Create a new :class:`TransactionEnvelope` from an XDR string. + + :param xdr: The XDR string that represents a transaction + envelope. + :param network_passphrase: which network this transaction envelope is associated with. + + :return: A new :class:`TransactionEnvelope` object from the given XDR TransactionEnvelope base64 string object. + """ + xdr_object = Xdr.types.TransactionEnvelope.from_xdr(xdr) + return cls.from_xdr_object(xdr_object, network_passphrase) diff --git a/stellar_sdk/fee_bump_transaction.py b/stellar_sdk/fee_bump_transaction.py index 56a60907..1d5ed6de 100644 --- a/stellar_sdk/fee_bump_transaction.py +++ b/stellar_sdk/fee_bump_transaction.py @@ -1,35 +1,46 @@ from typing import Union +from .exceptions import ValueError +from .keypair import Keypair from .strkey import StrKey from .transaction import Transaction -from .exceptions import ValueError +from .transaction_envelope import TransactionEnvelope from .xdr import Xdr -from .keypair import Keypair BASE_FEE = 100 +__all__ = ["FeeBumpTransaction"] + class FeeBumpTransaction: - def __init__(self, - fee_source: Union[Keypair, str], - base_fee: int, - inner_transaction: Transaction) -> None: - if not inner_transaction.v1: + def __init__( + self, + fee_source: Union[Keypair, str], + base_fee: int, + inner_transaction_envelope: TransactionEnvelope, + ) -> None: + + if not ( + isinstance(inner_transaction_envelope.transaction, Transaction) + and inner_transaction_envelope.transaction.v1 + ): raise ValueError("Invalid `inner_transaction`, it should be TransactionV1.") if isinstance(fee_source, Keypair): self.fee_source = fee_source else: self.fee_source = Keypair.from_public_key(fee_source) + self.base_fee = base_fee + self.inner_transaction_envelope = inner_transaction_envelope + self._inner_transaction = inner_transaction_envelope.transaction - inner_operations_length = len(self.inner_transaction.operations) - inner_base_fee_rate = self.inner_transaction.fee / inner_operations_length - + inner_operations_length = len(self._inner_transaction.operations) + inner_base_fee_rate = self._inner_transaction.fee / inner_operations_length if self.base_fee < inner_base_fee_rate or self.base_fee < BASE_FEE: - raise ValueError("Invalid `base_fee`, it should be at least %d stroops.", inner_operations_length) - - self.base_fee = base_fee - self.inner_transaction = inner_transaction + raise ValueError( + "Invalid `base_fee`, it should be at least %d stroops.", + inner_operations_length, + ) def to_xdr_object(self) -> Xdr.types.FeeBumpTransaction: """Get an XDR object representation of this :class:`FeeBumpTransaction`. @@ -37,13 +48,20 @@ def to_xdr_object(self) -> Xdr.types.FeeBumpTransaction: :return: XDR Transaction object """ fee_source = self.fee_source.xdr_account_id() - fee = self.base_fee * (self.inner_transaction.operations + 1) + fee = self.base_fee * (len(self._inner_transaction.operations) + 1) ext = Xdr.nullclass() ext.v = 0 - return Xdr.types.FeeBumpTransaction(feeSource=fee_source, fee=fee, innerTx=self.inner_transaction, ext=ext) + return Xdr.types.FeeBumpTransaction( + feeSource=fee_source, + fee=fee, + innerTx=self.inner_transaction_envelope.to_xdr_object(), + ext=ext, + ) @classmethod - def from_xdr_object(cls, tx_xdr_object: Xdr.types.FeeBumpTransaction) -> "FeeBumpTransaction": + def from_xdr_object( + cls, tx_xdr_object: Xdr.types.FeeBumpTransaction, network_passphrase: str + ) -> "FeeBumpTransaction": """Create a new :class:`FeeBumpTransaction` from an XDR object. :param tx_xdr_object: The XDR object that represents a transaction. @@ -53,12 +71,21 @@ def from_xdr_object(cls, tx_xdr_object: Xdr.types.FeeBumpTransaction) -> "FeeBum source = Keypair.from_public_key( StrKey.encode_ed25519_public_key(tx_xdr_object.feeSource.ed25519) ) - base_fee = tx_xdr_object.fee - inner_transaction = Transaction.from_xdr_object(tx_xdr_object=tx_xdr_object.innerTx, v1=True) - return cls(fee_source=source, base_fee=base_fee, inner_transaction=inner_transaction) + inner_transaction_envelope = TransactionEnvelope.from_xdr_object( + tx_xdr_object.innerTx, network_passphrase + ) + inner_transaction_operation_length = len( + inner_transaction_envelope.transaction.operations + ) + base_fee = tx_xdr_object.fee / (inner_transaction_operation_length + 1) + return cls( + fee_source=source, + base_fee=base_fee, + inner_transaction_envelope=inner_transaction_envelope, + ) @classmethod - def from_xdr(cls, xdr: str) -> "FeeBumpTransaction": + def from_xdr(cls, xdr: str, network_passphrase: str) -> "FeeBumpTransaction": """Create a new :class:`Transaction` from an XDR string. :param xdr: The XDR string that represents a transaction. @@ -66,4 +93,4 @@ def from_xdr(cls, xdr: str) -> "FeeBumpTransaction": :return: A new :class:`TransactionEnvelope` object from the given XDR TransactionEnvelope base64 string object. """ xdr_object = Xdr.types.FeeBumpTransaction.from_xdr(xdr) - return cls.from_xdr_object(xdr_object) + return cls.from_xdr_object(xdr_object, network_passphrase) diff --git a/stellar_sdk/fee_bump_transaction_envelope.py b/stellar_sdk/fee_bump_transaction_envelope.py new file mode 100644 index 00000000..e20aa122 --- /dev/null +++ b/stellar_sdk/fee_bump_transaction_envelope.py @@ -0,0 +1,94 @@ +from typing import List + +from .base_transaction_envelope import BaseTransactionEnvelope +from .fee_bump_transaction import FeeBumpTransaction +from .xdr import Xdr + + +class FeeBumpTransactionEnvelope(BaseTransactionEnvelope["FeeBumpTransactionEnvelope"]): + """The :class:`FeeBumpTransactionEnvelope` object, which represents a transaction + envelope ready to sign and submit to send over the network. + + When a transaction is ready to be prepared for sending over the network, it + must be put into a :class:`FeeBumpTransactionEnvelope`, which includes additional + metadata such as the signers for a given transaction. Ultimately, this + class handles signing and conversion to and from XDR for usage on Stellar's + network. + + :param transaction: The fee bump transaction that is encapsulated in this envelope. + :param list signatures: which contains a list of signatures that have + already been created. + :param network_passphrase: The network to connect to for verifying and retrieving additional attributes from. + """ + + def __init__( + self, + transaction: FeeBumpTransaction, + network_passphrase: str, + signatures: List[Xdr.types.DecoratedSignature] = None, + ) -> None: + # TODO: check + super().__init__(transaction, network_passphrase, signatures) + self.transaction = transaction + + def signature_base(self) -> bytes: + """Get the signature base of this transaction envelope. + + Return the "signature base" of this transaction, which is the value + that, when hashed, should be signed to create a signature that + validators on the Stellar Network will accept. + + It is composed of a 4 prefix bytes followed by the xdr-encoded form of + this transaction. + + :return: The signature base of this transaction envelope. + + """ + network_id = self.network_id + tx_type = Xdr.StellarXDRPacker() + tx_packer = Xdr.StellarXDRPacker() + tx = self.transaction + tx_type.pack_EnvelopeType(Xdr.const.ENVELOPE_TYPE_TX_FEE_BUMP) + tx_packer.pack_FeeBumpTransaction(tx.to_xdr_object()) + tx_type_buffer = tx_type.get_buffer() + tx_buffer = tx_packer.get_buffer() + return network_id + tx_type_buffer + tx_buffer + + def to_xdr_object(self) -> Xdr.types.TransactionEnvelope: + """Get an XDR object representation of this :class:`TransactionEnvelope`. + + :return: XDR TransactionEnvelope object + """ + tx = self.transaction.to_xdr_object() + te_type = Xdr.const.ENVELOPE_TYPE_TX_FEE_BUMP + tx_envelope = Xdr.types.FeeBumpTransactionEnvelope(tx, self.signatures) + return Xdr.types.TransactionEnvelope(type=te_type, feeBump=tx_envelope) + + def to_xdr(self) -> str: + """Get the base64 encoded XDR string representing this + :class:`TransactionEnvelope`. + + :return: XDR TransactionEnvelope base64 string object + """ + return self.to_xdr_object().to_xdr() + + @classmethod + def from_xdr_object( + cls, te_xdr_object: Xdr.types.TransactionEnvelope, network_passphrase: str + ) -> "FeeBumpTransactionEnvelope": + """Create a new :class:`FeeBumpTransactionEnvelope` from an XDR object. + + :param te_xdr_object: The XDR object that represents a fee bump transaction envelope. + :param network_passphrase: The network to connect to for verifying and retrieving additional attributes from. + :return: A new :class:`FeeBumpTransactionEnvelope` object from the given XDR TransactionEnvelope object. + """ + te_type = te_xdr_object.type + if te_type == Xdr.const.ENVELOPE_TYPE_TX_FEE_BUMP: + tx = FeeBumpTransaction.from_xdr_object( + te_xdr_object.feeBump, network_passphrase + ) + else: + raise ValueError("Invalid EnvelopeType: %d.", te_xdr_object.type) + signatures = te_xdr_object.signatures + te = cls(tx, network_passphrase=network_passphrase, signatures=signatures) + return te diff --git a/stellar_sdk/memo.py b/stellar_sdk/memo.py index 92342d72..def9480a 100644 --- a/stellar_sdk/memo.py +++ b/stellar_sdk/memo.py @@ -162,7 +162,9 @@ def __init__(self, memo_hash: Union[bytes, str]) -> None: length = len(memo_hash) if length != 32: raise MemoInvalidException( - "The length of HashMemo should be 32 bytes, got {:d} bytes.".format(length) + "The length of HashMemo should be 32 bytes, got {:d} bytes.".format( + length + ) ) self.memo_hash: bytes = memo_hash diff --git a/stellar_sdk/server.py b/stellar_sdk/server.py index d78450c7..69b0314f 100644 --- a/stellar_sdk/server.py +++ b/stellar_sdk/server.py @@ -125,6 +125,7 @@ async def __submit_transaction_async( xdr, tx = self.__get_xdr_and_transaction_from_transaction_envelope( transaction_envelope ) + # TODO if not skip_memo_required_check: await self.__check_memo_required_async(tx) data = {"tx": xdr} diff --git a/stellar_sdk/transaction.py b/stellar_sdk/transaction.py index 546aec87..221f0d24 100644 --- a/stellar_sdk/transaction.py +++ b/stellar_sdk/transaction.py @@ -49,14 +49,14 @@ class Transaction: """ def __init__( - self, - source: Union[Keypair, str], - sequence: int, - fee: int, - operations: List[Operation], - memo: Memo = None, - time_bounds: TimeBounds = None, - v1: bool = False + self, + source: Union[Keypair, str], + sequence: int, + fee: int, + operations: List[Operation], + memo: Memo = None, + time_bounds: TimeBounds = None, + v1: bool = False, ) -> None: # if not operations: @@ -88,13 +88,31 @@ def to_xdr_object(self) -> Union[Xdr.types.Transaction, Xdr.types.TransactionV0] ext = Xdr.nullclass() ext.v = 0 if self.v1: - return Xdr.types.Transaction(self.source.xdr_account_id(), self.fee, self.sequence, time_bounds, memo, operations, ext) - return Xdr.types.TransactionV0(self.source.xdr_account_id().ed25519, self.fee, self.sequence, time_bounds, memo, operations, - ext) + return Xdr.types.Transaction( + self.source.xdr_account_id(), + self.fee, + self.sequence, + time_bounds, + memo, + operations, + ext, + ) + return Xdr.types.TransactionV0( + self.source.xdr_account_id().ed25519, + self.fee, + self.sequence, + time_bounds, + memo, + operations, + ext, + ) @classmethod - def from_xdr_object(cls, tx_xdr_object: Union[Xdr.types.Transaction, Xdr.types.TransactionV0], - v1: bool = False) -> "Transaction": + def from_xdr_object( + cls, + tx_xdr_object: Union[Xdr.types.Transaction, Xdr.types.TransactionV0], + v1: bool = False, + ) -> "Transaction": """Create a new :class:`Transaction` from an XDR object. :param tx_xdr_object: The XDR object that represents a transaction. @@ -131,6 +149,7 @@ def from_xdr_object(cls, tx_xdr_object: Union[Xdr.types.Transaction, Xdr.types.T memo=memo, fee=fee, operations=operations, + v1=v1, ) @classmethod diff --git a/stellar_sdk/transaction_builder.py b/stellar_sdk/transaction_builder.py index 896a09d2..430a35bd 100644 --- a/stellar_sdk/transaction_builder.py +++ b/stellar_sdk/transaction_builder.py @@ -3,10 +3,11 @@ from decimal import Decimal from typing import List, Union, Optional -from .utils import hex_to_bytes from .account import Account from .asset import Asset from .exceptions import ValueError +from .fee_bump_transaction import FeeBumpTransaction +from .fee_bump_transaction_envelope import FeeBumpTransactionEnvelope from .keypair import Keypair from .memo import * from .network import Network @@ -16,10 +17,11 @@ from .time_bounds import TimeBounds from .transaction import Transaction from .transaction_envelope import TransactionEnvelope -from .fee_bump_transaction import FeeBumpTransaction +from .utils import hex_to_bytes __all__ = ["TransactionBuilder"] + class TransactionBuilder: """Transaction builder helps constructs a new :class:`TransactionEnvelope ` using the given @@ -46,11 +48,11 @@ class TransactionBuilder: # TODO: add an example def __init__( - self, - source_account: Account, - network_passphrase: str = Network.TESTNET_NETWORK_PASSPHRASE, - base_fee: int = 100, - v1: bool = False + self, + source_account: Account, + network_passphrase: str = Network.TESTNET_NETWORK_PASSPHRASE, + base_fee: int = 100, + v1: bool = False, ): self.source_account: Account = source_account self.base_fee: int = base_fee @@ -75,7 +77,7 @@ def build(self) -> TransactionEnvelope: operations=self.operations, memo=self.memo, time_bounds=self.time_bounds, - v1=self.v1 + v1=self.v1, ) transaction_envelope = TransactionEnvelope( transaction=transaction, network_passphrase=self.network_passphrase @@ -85,14 +87,18 @@ def build(self) -> TransactionEnvelope: @staticmethod def build_fee_bump_transaction( - fee_source: [Keypair, str], - base_fee: int, - inner_transaction: Transaction, - network_passphrase: str = Network.TESTNET_NETWORK_PASSPHRASE + fee_source: [Keypair, str], + base_fee: int, + inner_transaction_envelope: TransactionEnvelope, + network_passphrase: str = Network.TESTNET_NETWORK_PASSPHRASE, ): - transaction = FeeBumpTransaction(fee_source=fee_source, base_fee=base_fee, inner_transaction=inner_transaction) - transaction_envelope = TransactionEnvelope( - transaction=transaction, network_passphrase=network_passphrase + fee_bump_transaction = FeeBumpTransaction( + fee_source=fee_source, + base_fee=base_fee, + inner_transaction_envelope=inner_transaction_envelope, + ) + transaction_envelope = FeeBumpTransactionEnvelope( + transaction=fee_bump_transaction, network_passphrase=network_passphrase, ) return transaction_envelope @@ -213,7 +219,7 @@ def add_hash_memo(self, memo_hash: Union[bytes, str]) -> "TransactionBuilder": return self.add_memo(memo) def add_return_hash_memo( - self, memo_return: Union[bytes, str] + self, memo_return: Union[bytes, str] ) -> "TransactionBuilder": """Set the memo for the transaction to a new :class:`RetHashMemo `. @@ -239,10 +245,10 @@ def append_operation(self, operation: Operation) -> "TransactionBuilder": return self def append_create_account_op( - self, - destination: str, - starting_balance: Union[str, Decimal], - source: str = None, + self, + destination: str, + starting_balance: Union[str, Decimal], + source: str = None, ) -> "TransactionBuilder": """Append a :class:`CreateAccount ` operation to the list of @@ -260,11 +266,11 @@ def append_create_account_op( return self.append_operation(op) def append_change_trust_op( - self, - asset_code: str, - asset_issuer: str, - limit: Union[str, Decimal] = None, - source: str = None, + self, + asset_code: str, + asset_issuer: str, + limit: Union[str, Decimal] = None, + source: str = None, ) -> "TransactionBuilder": """Append a :class:`ChangeTrust ` operation to the list of operations. @@ -281,12 +287,12 @@ def append_change_trust_op( return self.append_operation(op) def append_payment_op( - self, - destination: str, - amount: Union[str, Decimal], - asset_code: str = "XLM", - asset_issuer: Optional[str] = None, - source: str = None, + self, + destination: str, + amount: Union[str, Decimal], + asset_code: str = "XLM", + asset_issuer: Optional[str] = None, + source: str = None, ) -> "TransactionBuilder": """Append a :class:`Payment ` operation to the list of operations. @@ -304,16 +310,16 @@ def append_payment_op( return self.append_operation(op) def append_path_payment_op( - self, - destination: str, - send_code: str, - send_issuer: Optional[str], - send_max: Union[str, Decimal], - dest_code: str, - dest_issuer: Optional[str], - dest_amount: Union[str, Decimal], - path: List[Asset], - source=None, + self, + destination: str, + send_code: str, + send_issuer: Optional[str], + send_max: Union[str, Decimal], + dest_code: str, + dest_issuer: Optional[str], + dest_amount: Union[str, Decimal], + path: List[Asset], + source=None, ) -> "TransactionBuilder": """Append a :class:`PathPayment ` operation to the list of operations. @@ -360,16 +366,16 @@ def append_path_payment_op( return self.append_operation(op) def append_path_payment_strict_receive_op( - self, - destination: str, - send_code: str, - send_issuer: Optional[str], - send_max: Union[str, Decimal], - dest_code: str, - dest_issuer: Optional[str], - dest_amount: Union[str, Decimal], - path: List[Asset], - source=None, + self, + destination: str, + send_code: str, + send_issuer: Optional[str], + send_max: Union[str, Decimal], + dest_code: str, + dest_issuer: Optional[str], + dest_amount: Union[str, Decimal], + path: List[Asset], + source=None, ) -> "TransactionBuilder": """Append a :class:`PathPaymentStrictReceive ` operation to the list of operations. @@ -410,16 +416,16 @@ def append_path_payment_strict_receive_op( return self.append_operation(op) def append_path_payment_strict_send_op( - self, - destination: str, - send_code: str, - send_issuer: Optional[str], - send_amount: Union[str, Decimal], - dest_code: str, - dest_issuer: Optional[str], - dest_min: Union[str, Decimal], - path: List[Asset], - source=None, + self, + destination: str, + send_code: str, + send_issuer: Optional[str], + send_amount: Union[str, Decimal], + dest_code: str, + dest_issuer: Optional[str], + dest_min: Union[str, Decimal], + path: List[Asset], + source=None, ) -> "TransactionBuilder": """Append a :class:`PathPaymentStrictSend ` operation to the list of operations. @@ -458,7 +464,7 @@ def append_path_payment_strict_send_op( return self.append_operation(op) def append_allow_trust_op( - self, trustor: str, asset_code: str, authorize: bool, source: str = None + self, trustor: str, asset_code: str, authorize: bool, source: str = None ) -> "TransactionBuilder": """Append an :class:`AllowTrust ` operation to the list of operations. @@ -478,17 +484,17 @@ def append_allow_trust_op( return self.append_operation(op) def append_set_options_op( - self, - inflation_dest: str = None, - clear_flags: Union[int, Flag] = None, - set_flags: Union[int, Flag] = None, - master_weight: int = None, - low_threshold: int = None, - med_threshold: int = None, - high_threshold: int = None, - home_domain: str = None, - signer: Signer = None, - source: str = None, + self, + inflation_dest: str = None, + clear_flags: Union[int, Flag] = None, + set_flags: Union[int, Flag] = None, + master_weight: int = None, + low_threshold: int = None, + med_threshold: int = None, + high_threshold: int = None, + home_domain: str = None, + signer: Signer = None, + source: str = None, ) -> "TransactionBuilder": """Append a :class:`SetOptions ` operation to the list of operations. @@ -540,7 +546,7 @@ def append_set_options_op( return self.append_operation(op) def append_ed25519_public_key_signer( - self, account_id: str, weight: int, source=None + self, account_id: str, weight: int, source=None ) -> "TransactionBuilder": """Add a ed25519 public key signer to an account. @@ -559,7 +565,7 @@ def append_ed25519_public_key_signer( return self.append_set_options_op(signer=signer, source=source) def append_hashx_signer( - self, sha256_hash: [bytes, str], weight: int, source=None + self, sha256_hash: [bytes, str], weight: int, source=None ) -> "TransactionBuilder": """Add a sha256 hash(HashX) signer to an account. @@ -581,7 +587,7 @@ def append_hashx_signer( return self.append_set_options_op(signer=signer, source=source) def append_pre_auth_tx_signer( - self, pre_auth_tx_hash: bytes, weight: int, source=None + self, pre_auth_tx_hash: bytes, weight: int, source=None ) -> "TransactionBuilder": """Add a PreAuthTx signer to an account. @@ -604,15 +610,15 @@ def append_pre_auth_tx_signer( return self.append_set_options_op(signer=signer, source=source) def append_manage_buy_offer_op( - self, - selling_code: str, - selling_issuer: Optional[str], - buying_code: str, - buying_issuer: Optional[str], - amount: Union[str, Decimal], - price: Union[str, Decimal, Price], - offer_id: int = 0, - source: str = None, + self, + selling_code: str, + selling_issuer: Optional[str], + buying_code: str, + buying_issuer: Optional[str], + amount: Union[str, Decimal], + price: Union[str, Decimal, Price], + offer_id: int = 0, + source: str = None, ) -> "TransactionBuilder": """Append a :class:`ManageBuyOffer ` operation to the list of operations. @@ -648,15 +654,15 @@ def append_manage_buy_offer_op( return self.append_operation(op) def append_manage_sell_offer_op( - self, - selling_code: str, - selling_issuer: Optional[str], - buying_code: str, - buying_issuer: Optional[str], - amount: Union[str, Decimal], - price: Union[str, Price, Decimal], - offer_id: int = 0, - source: str = None, + self, + selling_code: str, + selling_issuer: Optional[str], + buying_code: str, + buying_issuer: Optional[str], + amount: Union[str, Decimal], + price: Union[str, Price, Decimal], + offer_id: int = 0, + source: str = None, ) -> "TransactionBuilder": """Append a :class:`ManageSellOffer ` operation to the list of operations. @@ -692,14 +698,14 @@ def append_manage_sell_offer_op( return self.append_operation(op) def append_create_passive_sell_offer_op( - self, - selling_code: str, - selling_issuer: Optional[str], - buying_code: str, - buying_issuer: Optional[str], - amount: Union[str, Decimal], - price: Union[str, Price, Decimal], - source: str = None, + self, + selling_code: str, + selling_issuer: Optional[str], + buying_code: str, + buying_issuer: Optional[str], + amount: Union[str, Decimal], + price: Union[str, Price, Decimal], + source: str = None, ) -> "TransactionBuilder": """Append a :class:`CreatePassiveSellOffer ` operation to the list of @@ -728,7 +734,7 @@ def append_create_passive_sell_offer_op( return self.append_operation(op) def append_account_merge_op( - self, destination: str, source: str = None + self, destination: str, source: str = None ) -> "TransactionBuilder": """Append a :class:`AccountMerge ` operation to the list of @@ -758,7 +764,7 @@ def append_inflation_op(self, source: str = None) -> "TransactionBuilder": return self.append_operation(op) def append_manage_data_op( - self, data_name: str, data_value: Union[str, bytes, None], source: str = None + self, data_name: str, data_value: Union[str, bytes, None], source: str = None ) -> "TransactionBuilder": """Append a :class:`ManageData ` operation to the list of operations. @@ -778,7 +784,7 @@ def append_manage_data_op( return self.append_operation(op) def append_bump_sequence_op( - self, bump_to: int, source: str = None + self, bump_to: int, source: str = None ) -> "TransactionBuilder": """Append a :class:`BumpSequence ` operation to the list of operations. diff --git a/stellar_sdk/transaction_envelope.py b/stellar_sdk/transaction_envelope.py index c6abfaa2..7ae9d24b 100644 --- a/stellar_sdk/transaction_envelope.py +++ b/stellar_sdk/transaction_envelope.py @@ -1,17 +1,13 @@ -from typing import List, Union +from typing import List -from .exceptions import SignatureExistError -from .fee_bump_transaction import FeeBumpTransaction -from .keypair import Keypair -from .network import Network +from .base_transaction_envelope import BaseTransactionEnvelope from .transaction import Transaction -from .utils import sha256, hex_to_bytes from .xdr import Xdr __all__ = ["TransactionEnvelope"] -class TransactionEnvelope: +class TransactionEnvelope(BaseTransactionEnvelope): """The :class:`TransactionEnvelope` object, which represents a transaction envelope ready to sign and submit to send over the network. @@ -29,53 +25,12 @@ class handles signing and conversion to and from XDR for usage on Stellar's def __init__( self, - transaction: Union[Transaction, FeeBumpTransaction], + transaction: Transaction, network_passphrase: str, signatures: List[Xdr.types.DecoratedSignature] = None, ) -> None: - self.transaction: Transaction = transaction - self.network_id: bytes = Network(network_passphrase).network_id() - self.signatures: List[Xdr.types.DecoratedSignature] = signatures or [] - - def hash(self) -> bytes: - """Get the XDR Hash of the signature base. - - This hash is ultimately what is signed before transactions are sent - over the network. See :meth:`signature_base` for more details about - this process. - - :return: The XDR Hash of this transaction envelope's signature base. - - """ - return sha256(self.signature_base()) - - def hash_hex(self) -> str: - """Return a hex encoded hash for this transaction envelope. - - :return: A hex encoded hash for this transaction envelope. - """ - return self.hash().hex() - - def sign(self, signer: Union[Keypair, str]) -> None: - """Sign this transaction envelope with a given keypair. - - Note that the signature must not already be in this instance's list of - signatures. - - :param signer: The keypair or secret to use for signing this transaction - envelope. - :raise: :exc:`SignatureExistError `: - if this signature already exists. - """ - if isinstance(signer, str): - signer = Keypair.from_secret(signer) - tx_hash = self.hash() - sig = signer.sign_decorated(tx_hash) - sig_dict = [signature.__dict__ for signature in self.signatures] - if sig.__dict__ in sig_dict: - raise SignatureExistError("The keypair has already signed.") - else: - self.signatures.append(sig) + super().__init__(transaction, network_passphrase, signatures) + self.transaction = transaction def signature_base(self) -> bytes: """Get the signature base of this transaction envelope. @@ -97,45 +52,18 @@ def signature_base(self) -> bytes: if isinstance(self.transaction, Transaction) and not self.transaction.v1: tx = Transaction.from_xdr_object(self.transaction.to_xdr_object(), v1=False) tx.v1 = True - if isinstance(self.transaction, FeeBumpTransaction): - tx_type.pack_EnvelopeType(Xdr.const.ENVELOPE_TYPE_TX_FEE_BUMP) - tx_packer.pack_FeeBumpTransaction(tx.to_xdr_object()) - else: - tx_type.pack_EnvelopeType(Xdr.const.ENVELOPE_TYPE_TX) - tx_packer.pack_Transaction(tx.to_xdr_object()) + tx_type.pack_EnvelopeType(Xdr.const.ENVELOPE_TYPE_TX) + tx_packer.pack_Transaction(tx.to_xdr_object()) tx_type_buffer = tx_type.get_buffer() tx_buffer = tx_packer.get_buffer() return network_id + tx_type_buffer + tx_buffer - def sign_hashx(self, preimage: bytes) -> None: - """Sign this transaction envelope with a Hash(x) signature. - - See Stellar's documentation on `Multi-Sig - `_ - for more details on Hash(x) signatures. - - :param preimage: 32 byte hash or hex encoded string , the "x" value to be hashed and used as a - signature. - """ - hash_preimage = sha256(hex_to_bytes(preimage)) - hint = hash_preimage[-4:] - sig = Xdr.types.DecoratedSignature(hint, preimage) - sig_dict = [signature.__dict__ for signature in self.signatures] - if sig.__dict__ in sig_dict: - raise SignatureExistError("The preimage has already signed.") - else: - self.signatures.append(sig) - def to_xdr_object(self) -> Xdr.types.TransactionEnvelope: """Get an XDR object representation of this :class:`TransactionEnvelope`. :return: XDR TransactionEnvelope object """ tx = self.transaction.to_xdr_object() - if isinstance(self.transaction, FeeBumpTransaction): - te_type = Xdr.const.ENVELOPE_TYPE_TX_FEE_BUMP - tx_envelope = Xdr.types.FeeBumpTransactionEnvelope(tx, self.signatures) - return Xdr.types.TransactionEnvelope(type=te_type, feeBump=tx_envelope) if self.transaction.v1: te_type = Xdr.const.ENVELOPE_TYPE_TX tx_envelope = Xdr.types.TransactionV1Envelope(tx, self.signatures) @@ -166,27 +94,12 @@ def from_xdr_object( te_type = te_xdr_object.type if te_type == Xdr.const.ENVELOPE_TYPE_TX_V0: tx = Transaction.from_xdr_object(te_xdr_object.v0, v1=False) + signatures = te_xdr_object.v0.signatures elif te_type == Xdr.const.ENVELOPE_TYPE_TX: tx = Transaction.from_xdr_object(te_xdr_object.v1, v1=True) - elif te_type == Xdr.const.ENVELOPE_TYPE_TX_FEE_BUMP: - tx = FeeBumpTransaction.from_xdr_object(te_xdr_object.feeBump) + signatures = te_xdr_object.v1.signatures else: + # TODO: more detail information raise ValueError("Invalid EnvelopeType: %d.", te_xdr_object.type) - signatures = te_xdr_object.signatures - te = TransactionEnvelope( - tx, network_passphrase=network_passphrase, signatures=signatures - ) + te = cls(tx, network_passphrase=network_passphrase, signatures=signatures) return te - - @classmethod - def from_xdr(cls, xdr: str, network_passphrase: str) -> "TransactionEnvelope": - """Create a new :class:`TransactionEnvelope` from an XDR string. - - :param xdr: The XDR string that represents a transaction - envelope. - :param network: which network this transaction envelope is associated with. - - :return: A new :class:`TransactionEnvelope` object from the given XDR TransactionEnvelope base64 string object. - """ - xdr_object = Xdr.types.TransactionEnvelope.from_xdr(xdr) - return cls.from_xdr_object(xdr_object, network_passphrase) diff --git a/stellar_sdk/xdr/StellarXDR_const.py b/stellar_sdk/xdr/StellarXDR_const.py index 46920e75..bc5ae9ea 100644 --- a/stellar_sdk/xdr/StellarXDR_const.py +++ b/stellar_sdk/xdr/StellarXDR_const.py @@ -1,4 +1,4 @@ -# Generated by xdrgen.py from ../../.xdr/ on Tue Apr 14 19:24:25 2020 +# Generated by xdrgen.py from ../../.xdr/ on Thu Apr 16 23:08:17 2020 KEY_TYPE_ED25519 = 0 KEY_TYPE_PRE_AUTH_TX = 1 KEY_TYPE_HASH_X = 2 diff --git a/stellar_sdk/xdr/StellarXDR_pack.py b/stellar_sdk/xdr/StellarXDR_pack.py index 10286850..8dc87890 100644 --- a/stellar_sdk/xdr/StellarXDR_pack.py +++ b/stellar_sdk/xdr/StellarXDR_pack.py @@ -1,4 +1,4 @@ -# Generated by xdrgen.py from ../../.xdr/ on Tue Apr 14 19:24:25 2020 +# Generated by xdrgen.py from ../../.xdr/ on Thu Apr 16 23:08:17 2020 from . import StellarXDR_const as const from . import StellarXDR_type as types import xdrlib diff --git a/stellar_sdk/xdr/StellarXDR_type.py b/stellar_sdk/xdr/StellarXDR_type.py index 31e36a3f..c344076a 100644 --- a/stellar_sdk/xdr/StellarXDR_type.py +++ b/stellar_sdk/xdr/StellarXDR_type.py @@ -1,4 +1,4 @@ -# Generated by xdrgen.py from ../../.xdr/ on Tue Apr 14 19:24:25 2020 +# Generated by xdrgen.py from ../../.xdr/ on Thu Apr 16 23:08:17 2020 import base64 from . import StellarXDR_const as const @@ -27,7 +27,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_PublicKey() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -70,7 +70,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_SignerKey() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -327,7 +327,7 @@ def __init__(self, statement=None, signature=None): self.signature = signature def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.statement, attr) @@ -396,7 +396,7 @@ def __init__(self, nodeID=None, signature=None): self.signature = signature def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.nodeID, attr) @@ -440,7 +440,7 @@ def __init__(self, txSetHash=None, closeTime=None, upgrades=None, ext=None): self.ext = ext def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.ext, attr) @@ -586,7 +586,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_LedgerUpgrade() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -649,7 +649,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_LedgerKey() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -682,7 +682,7 @@ def __init__(self, ledgerVersion=None, ext=None): self.ext = ext def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.ext, attr) @@ -737,7 +737,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_BucketEntry() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -795,7 +795,7 @@ def __init__(self, transactionHash=None, result=None): self.result = result def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.result, attr) @@ -997,7 +997,7 @@ def __init__(self, quorumSets=None, ledgerMessages=None): self.ledgerMessages = ledgerMessages def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.ledgerMessages, attr) @@ -1045,7 +1045,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_SCPHistoryEntry() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -1091,7 +1091,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_LedgerEntryChange() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -1230,7 +1230,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_TransactionMeta() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -1292,7 +1292,7 @@ def __init__(self, upgrade=None, changes=None): self.changes = changes def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.upgrade, attr) @@ -1382,7 +1382,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_LedgerCloseMeta() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -1431,7 +1431,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_Asset() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -1517,7 +1517,7 @@ def __init__(self, key=None, weight=None): self.weight = weight def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.key, attr) @@ -1856,7 +1856,7 @@ def __init__(self, destination=None, startingBalance=None): self.startingBalance = startingBalance def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.destination, attr) @@ -2197,7 +2197,7 @@ def __init__(self, line=None, limit=None): self.limit = limit def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.line, attr) @@ -2356,7 +2356,7 @@ def __init__(self, sourceAccount=None, body=None): self.body = body def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.body, attr) @@ -2415,7 +2415,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_Memo() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -2528,7 +2528,7 @@ def __init__(self, tx=None, signatures=None): self.signatures = signatures def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.tx, attr) @@ -2616,7 +2616,7 @@ def __init__(self, tx=None, signatures=None): self.signatures = signatures def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.tx, attr) @@ -2695,7 +2695,7 @@ def __init__(self, tx=None, signatures=None): self.signatures = signatures def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.tx, attr) @@ -2749,7 +2749,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_TransactionEnvelope() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -2782,7 +2782,7 @@ def __init__(self, networkId=None, taggedTransaction=None): self.taggedTransaction = taggedTransaction def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.taggedTransaction, attr) @@ -2877,7 +2877,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_CreateAccountResult() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -2913,7 +2913,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_PaymentResult() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -2990,7 +2990,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_PathPaymentStrictReceiveResult() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -3037,7 +3037,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_PathPaymentStrictSendResult() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -3069,7 +3069,7 @@ def __init__(self, offersClaimed=None, offer=None): self.offer = offer def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.offer, attr) @@ -3119,7 +3119,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_ManageSellOfferResult() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -3158,7 +3158,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_ManageBuyOfferResult() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -3196,7 +3196,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_SetOptionsResult() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -3232,7 +3232,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_ChangeTrustResult() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -3268,7 +3268,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_AllowTrustResult() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -3305,7 +3305,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_AccountMergeResult() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -3329,7 +3329,7 @@ def __init__(self, destination=None, amount=None): self.amount = amount def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.destination, attr) @@ -3379,7 +3379,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_InflationResult() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -3417,7 +3417,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_ManageDataResult() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -3453,7 +3453,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_BumpSequenceResult() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -3519,7 +3519,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_OperationResult() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -3596,7 +3596,7 @@ def __init__(self, transactionHash=None, result=None): self.result = result def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.result, attr) @@ -3709,7 +3709,7 @@ def __init__(self, pubkey=None, expiration=None, sig=None): self.sig = sig def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.pubkey, attr) @@ -3837,7 +3837,7 @@ def __init__(self, ip=None, port=None, numFailures=None): self.numFailures = numFailures def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.ip, attr) @@ -3946,7 +3946,7 @@ def __init__(self, requestSignature=None, request=None): self.request = request def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.request, attr) @@ -4023,7 +4023,7 @@ def __init__(self, responseSignature=None, response=None): self.response = response def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.response, attr) @@ -4084,7 +4084,7 @@ def __init__(self, id=None, versionStr=None, messagesRead=None, messagesWritten= self.duplicateFetchMessageRecv = duplicateFetchMessageRecv def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.id, attr) @@ -4196,7 +4196,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_SurveyResponseBody() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -4274,7 +4274,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_StellarMessage() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) @@ -4341,7 +4341,7 @@ def from_xdr(xdr): return xdr_unpacked.unpack_AuthenticatedMessage() def __getattr__(self, attr): - if attr is '__setstate__': + if attr == '__setstate__': raise AttributeError return getattr(self.switch, attr) diff --git a/stellar_sdk/xdr/xdrgen.py b/stellar_sdk/xdr/xdrgen.py index ea9f640e..2518ff2d 100644 --- a/stellar_sdk/xdr/xdrgen.py +++ b/stellar_sdk/xdr/xdrgen.py @@ -1218,7 +1218,7 @@ def check(v): candidates = [var for var in varlist if check(var)] if len(candidates) == 1: return "%sdef __getattr__(self, attr):\n" \ - "%sif attr is '__setstate__':\n" \ + "%sif attr == '__setstate__':\n" \ "%s%sraise AttributeError\n" \ "%sreturn getattr(self.%s, attr)\n\n" % \ (indent, indent2, indent2, indent, indent2, candidates[0].id) @@ -1257,7 +1257,7 @@ def __init__(self, id, body, lineno=None, sortno=None): def union_getattr(self, prefix=indent): # see: https://github.com/StellarCN/py-stellar-base/issues/192 return "%sdef __getattr__(self, attr):\n" \ - "%s%sif attr is '__setstate__':\n" \ + "%s%sif attr == '__setstate__':\n" \ "%s%s%sraise AttributeError\n" \ "%s%sreturn getattr(self.switch, attr)\n" % \ (prefix, prefix, indent, prefix, indent, indent, prefix, indent) diff --git a/tests/test_fee_bump_transaction.py b/tests/test_fee_bump_transaction.py new file mode 100644 index 00000000..44987ebb --- /dev/null +++ b/tests/test_fee_bump_transaction.py @@ -0,0 +1,50 @@ +from stellar_sdk import ( + TransactionBuilder, + Account, + Network, + Keypair, + FeeBumpTransactionEnvelope, + FeeBumpTransaction, +) + + +class TestFeeBumpTransaction: + def test_to_xdr(self): + inner_keypair = Keypair.from_secret( + "SBKTIFHJSS3JJWEZO2W74DZSA45WZU56LOL3AY7GAW63BXPEJQFYV53E" + ) + inner_source = Account(inner_keypair.public_key, 7) + destination = "GDQERENWDDSQZS7R7WKHZI3BSOYMV3FSWR7TFUYFTKQ447PIX6NREOJM" + amount = "2000.0000000" + inner_tx = ( + TransactionBuilder( + inner_source, Network.TESTNET_NETWORK_PASSPHRASE, 200, v1=True + ) + .append_payment_op(destination=destination, amount=amount, asset_code="XLM") + .add_time_bounds(0, 0) + .build() + ) + inner_tx.sign(inner_keypair) + fee_source = Keypair.from_secret( + "SB7ZMPZB3YMMK5CUWENXVLZWBK4KYX4YU5JBXQNZSK2DP2Q7V3LVTO5V" + ) + base_fee = 200 + fee_bump_tx = TransactionBuilder.build_fee_bump_transaction( + fee_source.public_key, + base_fee, + inner_tx, + Network.TESTNET_NETWORK_PASSPHRASE, + ) + fee_bump_tx.sign(fee_source) + xdr = "AAAABQAAAADgSJG2GOUMy/H9lHyjYZOwyuyytH8y0wWaoc596L+bEgAAAAAAAAGQAAAAAgAAAAAcVPE+R/n1VnbIrAK3tu5yVfTkhkqVz04ShWk2SrF3wAAAAMgAAAAAAAAACAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAADgSJG2GOUMy/H9lHyjYZOwyuyytH8y0wWaoc596L+bEgAAAAAAAAAEqBfIAAAAAAAAAAABSrF3wAAAAEAordQh63kT50muRLVYaWW7Pgtt8i1tc4t9Bv9MWFWFN3WfTHSU2Jxv7hedjZEyfBPvaS/XnwvYJFcHgPDd1JkNAAAAAAAAAAHov5sSAAAAQKu/RuArXn/P13IIJ8WlnVDStwOquXM0CsWzA4ooZY6gqJ3k1EfmMVIJ0cir0bMTJD9r+g2IUZCANU7wdC38PA0=" + + assert fee_bump_tx.to_xdr() == xdr + restore_te = FeeBumpTransactionEnvelope.from_xdr( + xdr, Network.TESTNET_NETWORK_PASSPHRASE + ) + assert isinstance(restore_te, FeeBumpTransactionEnvelope) + restore_tx = restore_te.transaction + assert isinstance(restore_tx, FeeBumpTransaction) + assert restore_tx.fee_source.public_key == fee_source.public_key + assert restore_tx.base_fee == base_fee + assert restore_tx.inner_transaction_envelope.to_xdr() == inner_tx.to_xdr() diff --git a/tests/test_transaction.py b/tests/test_transaction.py index 81c1f46a..62b7f5ad 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -5,7 +5,7 @@ class TestTransaction: - def test_to_xdr(self): + def test_to_xdr_v1(self): source = Keypair.from_public_key( "GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ" ) @@ -34,7 +34,7 @@ def test_to_xdr(self): assert restore_transaction.time_bounds == time_bounds assert restore_transaction.sequence == sequence - def test_to_xdr_str_source(self): + def test_to_xdr_str_source_v1(self): source = "GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ" destination = "GDJJRRMBK4IWLEPJGIE6SXD2LP7REGZODU7WDC3I2D6MR37F4XSHBKX2" amount = "1000.0" @@ -61,7 +61,73 @@ def test_to_xdr_str_source(self): assert restore_transaction.time_bounds == time_bounds assert restore_transaction.sequence == sequence - def test_none_memo(self): + def test_none_memo_v1(self): + source = Keypair.from_public_key( + "GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ" + ) + destination = "GDJJRRMBK4IWLEPJGIE6SXD2LP7REGZODU7WDC3I2D6MR37F4XSHBKX2" + amount = "1000.0" + sequence = 1 + fee = 200 + asset = Asset.native() + ops = [Payment(destination, asset, amount), ManageData("hello", "world")] + + tx = Transaction(source, sequence, fee, ops) + assert tx.memo == NoneMemo() + + def test_to_xdr_v0(self): + source = Keypair.from_public_key( + "GC3GJU6L7V7ZLPLKG3NTMC6GYYKBMNNKCPP36FG3LWEVPOHUPY6QJIGL" + ) + destination = "GDJJRRMBK4IWLEPJGIE6SXD2LP7REGZODU7WDC3I2D6MR37F4XSHBKX2" + amount = "1000.0" + sequence = 2 + memo = NoneMemo() + fee = 200 + asset = Asset.native() + time_bounds = TimeBounds(12345, 56789) + ops = [Payment(destination, asset, amount)] + + tx_object = Transaction( + source, sequence, fee, ops, memo, time_bounds, False + ).to_xdr_object() + + restore_transaction = Transaction.from_xdr_object(tx_object, False) + assert isinstance(restore_transaction, Transaction) + assert restore_transaction.source == source + assert restore_transaction.fee == fee + assert restore_transaction.memo == memo + assert restore_transaction.time_bounds == time_bounds + assert restore_transaction.sequence == sequence + + def test_to_xdr_str_source_v0(self): + source = "GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ" + destination = "GDJJRRMBK4IWLEPJGIE6SXD2LP7REGZODU7WDC3I2D6MR37F4XSHBKX2" + amount = "1000.0" + sequence = 1 + memo = IdMemo(100) + fee = 200 + asset = Asset.native() + time_bounds = TimeBounds(12345, 56789) + ops = [Payment(destination, asset, amount), ManageData("hello", "world")] + + tx_object = Transaction( + source, sequence, fee, ops, memo, time_bounds, False + ).to_xdr_object() + # assert ( + # tx_object.to_xdr() + # == "AAAAAImbKEDtVjbFbdxfFLI5dfefG6I4jSaU5MVuzd3JYOXvAAAAyAAAAAAAAAABAAAAAQAAAAAAADA5AAAAAAAA3dUAAAACAAAAAAAAAGQAAAACAAAAAAAAAAEAAAAA0pjFgVcRZZHpMgnpXHpb/xIbLh0/YYto0PzI7+Xl5HAAAAAAAAAAAlQL5AAAAAAAAAAACgAAAAVoZWxsbwAAAAAAAAEAAAAFd29ybGQAAAAAAAAA" + # ) + + restore_transaction = Transaction.from_xdr_object(tx_object, False) + assert isinstance(restore_transaction, Transaction) + assert restore_transaction.source == Keypair.from_public_key(source) + assert restore_transaction.fee == fee + assert restore_transaction.memo == memo + assert restore_transaction.time_bounds == time_bounds + assert restore_transaction.sequence == sequence + + def test_none_memo_v0(self): source = Keypair.from_public_key( "GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ" ) From 289904bee38011ceda56e2870bd2cbd944e399f4 Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Sat, 18 Apr 2020 23:12:42 +0800 Subject: [PATCH 6/8] feat: add `stellar.sdk.helpers.parse_transaction_envelope_from_xdr` --- stellar_sdk/__init__.py | 3 +- stellar_sdk/base_transaction_envelope.py | 10 +++--- stellar_sdk/call_builder/base_call_builder.py | 2 +- stellar_sdk/fee_bump_transaction.py | 3 +- stellar_sdk/fee_bump_transaction_envelope.py | 3 +- stellar_sdk/helpers.py | 31 +++++++++++++++++++ stellar_sdk/server.py | 28 ++++++++++++----- stellar_sdk/transaction_envelope.py | 1 + stellar_sdk/utils.py | 2 +- tests/test_helpers.py | 28 +++++++++++++++++ tests/test_transaction_envelope.py | 26 +++++++++++++++- 11 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 stellar_sdk/helpers.py create mode 100644 tests/test_helpers.py diff --git a/stellar_sdk/__init__.py b/stellar_sdk/__init__.py index 18bbc472..69963123 100644 --- a/stellar_sdk/__init__.py +++ b/stellar_sdk/__init__.py @@ -27,6 +27,7 @@ from .transaction_envelope import TransactionEnvelope from .client.requests_client import RequestsClient from .client.aiohttp_client import AiohttpClient +from .helpers import * __all__ = [ "__title__", @@ -57,4 +58,4 @@ "TransactionEnvelope", "RequestsClient", "AiohttpClient", -] + operation_all +] + operation_all + helpers.__all__ diff --git a/stellar_sdk/base_transaction_envelope.py b/stellar_sdk/base_transaction_envelope.py index 25ad4b2c..4419983f 100644 --- a/stellar_sdk/base_transaction_envelope.py +++ b/stellar_sdk/base_transaction_envelope.py @@ -97,7 +97,7 @@ def sign_hashx(self, preimage: bytes) -> None: self.signatures.append(sig) def to_xdr_object(self) -> Xdr.types.TransactionEnvelope: - """Get an XDR object representation of this :class:`TransactionEnvelope`. + """Get an XDR object representation of this :class:`BaseTransactionEnvelope`. :return: XDR TransactionEnvelope object """ @@ -105,7 +105,7 @@ def to_xdr_object(self) -> Xdr.types.TransactionEnvelope: def to_xdr(self) -> str: """Get the base64 encoded XDR string representing this - :class:`TransactionEnvelope`. + :class:`BaseTransactionEnvelope`. :return: XDR TransactionEnvelope base64 string object """ @@ -115,7 +115,7 @@ def to_xdr(self) -> str: def from_xdr_object( cls, te_xdr_object: Xdr.types.TransactionEnvelope, network_passphrase: str ) -> T: - """Create a new :class:`TransactionEnvelope` from an XDR object. + """Create a new :class:`BaseTransactionEnvelope` from an XDR object. :param te_xdr_object: The XDR object that represents a transaction envelope. :param network_passphrase: The network to connect to for verifying and retrieving additional attributes from. @@ -125,13 +125,13 @@ def from_xdr_object( @classmethod def from_xdr(cls, xdr: str, network_passphrase: str) -> T: - """Create a new :class:`TransactionEnvelope` from an XDR string. + """Create a new :class:`BaseTransactionEnvelope` from an XDR string. :param xdr: The XDR string that represents a transaction envelope. :param network_passphrase: which network this transaction envelope is associated with. - :return: A new :class:`TransactionEnvelope` object from the given XDR TransactionEnvelope base64 string object. + :return: A new :class:`BaseTransactionEnvelope` object from the given XDR TransactionEnvelope base64 string object. """ xdr_object = Xdr.types.TransactionEnvelope.from_xdr(xdr) return cls.from_xdr_object(xdr_object, network_passphrase) diff --git a/stellar_sdk/call_builder/base_call_builder.py b/stellar_sdk/call_builder/base_call_builder.py index 13036fed..2f6b8872 100644 --- a/stellar_sdk/call_builder/base_call_builder.py +++ b/stellar_sdk/call_builder/base_call_builder.py @@ -78,7 +78,7 @@ async def __call_async(self, url: str, params: dict = None) -> Dict[str, Any]: return resp def stream( - self + self, ) -> Union[ AsyncGenerator[Dict[str, Any], None], Generator[Dict[str, Any], None, None] ]: diff --git a/stellar_sdk/fee_bump_transaction.py b/stellar_sdk/fee_bump_transaction.py index 1d5ed6de..a981982d 100644 --- a/stellar_sdk/fee_bump_transaction.py +++ b/stellar_sdk/fee_bump_transaction.py @@ -19,7 +19,6 @@ def __init__( base_fee: int, inner_transaction_envelope: TransactionEnvelope, ) -> None: - if not ( isinstance(inner_transaction_envelope.transaction, Transaction) and inner_transaction_envelope.transaction.v1 @@ -65,6 +64,7 @@ def from_xdr_object( """Create a new :class:`FeeBumpTransaction` from an XDR object. :param tx_xdr_object: The XDR object that represents a transaction. + :param network_passphrase: The network to connect to for verifying and retrieving additional attributes from. :return: A new :class:`FeeBumpTransaction` object from the given XDR Transaction object. """ @@ -89,6 +89,7 @@ def from_xdr(cls, xdr: str, network_passphrase: str) -> "FeeBumpTransaction": """Create a new :class:`Transaction` from an XDR string. :param xdr: The XDR string that represents a transaction. + :param network_passphrase: The network to connect to for verifying and retrieving additional attributes from. :return: A new :class:`TransactionEnvelope` object from the given XDR TransactionEnvelope base64 string object. """ diff --git a/stellar_sdk/fee_bump_transaction_envelope.py b/stellar_sdk/fee_bump_transaction_envelope.py index e20aa122..5ee732e9 100644 --- a/stellar_sdk/fee_bump_transaction_envelope.py +++ b/stellar_sdk/fee_bump_transaction_envelope.py @@ -4,6 +4,8 @@ from .fee_bump_transaction import FeeBumpTransaction from .xdr import Xdr +__all__ = ["FeeBumpTransactionEnvelope"] + class FeeBumpTransactionEnvelope(BaseTransactionEnvelope["FeeBumpTransactionEnvelope"]): """The :class:`FeeBumpTransactionEnvelope` object, which represents a transaction @@ -27,7 +29,6 @@ def __init__( network_passphrase: str, signatures: List[Xdr.types.DecoratedSignature] = None, ) -> None: - # TODO: check super().__init__(transaction, network_passphrase, signatures) self.transaction = transaction diff --git a/stellar_sdk/helpers.py b/stellar_sdk/helpers.py new file mode 100644 index 00000000..0d4813c7 --- /dev/null +++ b/stellar_sdk/helpers.py @@ -0,0 +1,31 @@ +from typing import Union + +from .exceptions import ValueError +from .fee_bump_transaction_envelope import FeeBumpTransactionEnvelope +from .transaction_envelope import TransactionEnvelope +from .xdr import Xdr + + +__all__ = ["parse_transaction_envelope_from_xdr"] + + +def parse_transaction_envelope_from_xdr( + xdr: str, network_passphrase: str +) -> Union[TransactionEnvelope, FeeBumpTransactionEnvelope]: + xdr_object = Xdr.types.TransactionEnvelope.from_xdr(xdr) + te_type = xdr_object.type + if te_type == Xdr.const.ENVELOPE_TYPE_TX_FEE_BUMP: + return FeeBumpTransactionEnvelope.from_xdr_object( + xdr_object, network_passphrase + ) + elif ( + te_type == Xdr.const.ENVELOPE_TYPE_TX + or te_type == Xdr.const.ENVELOPE_TYPE_TX_V0 + ): + return TransactionEnvelope.from_xdr_object(xdr_object, network_passphrase) + else: + raise ValueError( + "This transaction envelope type is not currently supported, type = {}.".format( + te_type + ) + ) diff --git a/stellar_sdk/server.py b/stellar_sdk/server.py index 69b0314f..8df902e6 100644 --- a/stellar_sdk/server.py +++ b/stellar_sdk/server.py @@ -1,6 +1,7 @@ import warnings -from typing import Union, Coroutine, Any, Dict, List +from typing import Union, Coroutine, Any, Dict, List, Tuple +from stellar_sdk.base_transaction_envelope import BaseTransactionEnvelope from .account import Account, Thresholds from .asset import Asset from .call_builder.accounts_call_builder import AccountsCallBuilder @@ -26,10 +27,13 @@ from .client.base_sync_client import BaseSyncClient from .client.requests_client import RequestsClient from .exceptions import TypeError, NotFoundError, raise_request_exception +from .fee_bump_transaction import FeeBumpTransaction +from .fee_bump_transaction_envelope import FeeBumpTransactionEnvelope from .memo import NoneMemo from .sep.exceptions import AccountRequiresMemoError from .transaction import Transaction from .transaction_envelope import TransactionEnvelope +from .helpers import parse_transaction_envelope_from_xdr from .utils import urljoin_with_query from .xdr import Xdr @@ -125,7 +129,6 @@ async def __submit_transaction_async( xdr, tx = self.__get_xdr_and_transaction_from_transaction_envelope( transaction_envelope ) - # TODO if not skip_memo_required_check: await self.__check_memo_required_async(tx) data = {"tx": xdr} @@ -134,14 +137,19 @@ async def __submit_transaction_async( return resp.json() def __get_xdr_and_transaction_from_transaction_envelope( - self, transaction_envelope: Union[TransactionEnvelope, str] - ): - if isinstance(transaction_envelope, TransactionEnvelope): + self, + transaction_envelope: Union[ + TransactionEnvelope, FeeBumpTransactionEnvelope, str + ], + ) -> Tuple[str, Union[Transaction, FeeBumpTransaction]]: + if isinstance(transaction_envelope, BaseTransactionEnvelope): xdr = transaction_envelope.to_xdr() tx = transaction_envelope.transaction else: xdr = transaction_envelope - tx = TransactionEnvelope.from_xdr(xdr, "").transaction + tx = parse_transaction_envelope_from_xdr( + transaction_envelope, "" + ).transaction return xdr, tx def root(self) -> RootCallBuilder: @@ -402,6 +410,8 @@ def __load_account_sync(self, account_id: str) -> Account: return account def __check_memo_required_sync(self, transaction: Transaction) -> None: + if not isinstance(transaction, Transaction): + return if not (transaction.memo is None or isinstance(transaction.memo, NoneMemo)): return for index, destination in self.__get_check_memo_required_destinations( @@ -413,7 +423,11 @@ def __check_memo_required_sync(self, transaction: Transaction) -> None: continue self.__check_destination_memo(account_resp, index, destination) - async def __check_memo_required_async(self, transaction: Transaction) -> None: + async def __check_memo_required_async( + self, transaction: Union[Transaction, FeeBumpTransaction] + ) -> None: + if not isinstance(transaction, Transaction): + return if not (transaction.memo is None or isinstance(transaction.memo, NoneMemo)): return for index, destination in self.__get_check_memo_required_destinations( diff --git a/stellar_sdk/transaction_envelope.py b/stellar_sdk/transaction_envelope.py index 7ae9d24b..596e7e20 100644 --- a/stellar_sdk/transaction_envelope.py +++ b/stellar_sdk/transaction_envelope.py @@ -89,6 +89,7 @@ def from_xdr_object( :param te_xdr_object: The XDR object that represents a transaction envelope. :param network_passphrase: The network to connect to for verifying and retrieving additional attributes from. + :return: A new :class:`TransactionEnvelope` object from the given XDR TransactionEnvelope object. """ te_type = te_xdr_object.type diff --git a/stellar_sdk/utils.py b/stellar_sdk/utils.py index 3cb9f499..2852b6f3 100644 --- a/stellar_sdk/utils.py +++ b/stellar_sdk/utils.py @@ -2,7 +2,7 @@ import os from decimal import Decimal, ROUND_FLOOR from typing import List -from urllib.parse import urlsplit, urljoin, urlunsplit +from urllib.parse import urlsplit, urlunsplit from .asset import Asset from .exceptions import NoApproximationError, TypeError diff --git a/tests/test_helpers.py b/tests/test_helpers.py new file mode 100644 index 00000000..668fd9df --- /dev/null +++ b/tests/test_helpers.py @@ -0,0 +1,28 @@ +import pytest + +from stellar_sdk.fee_bump_transaction_envelope import FeeBumpTransactionEnvelope +from stellar_sdk.helpers import parse_transaction_envelope_from_xdr +from stellar_sdk.transaction_envelope import TransactionEnvelope + + +class TestHelpers: + @pytest.mark.parametrize( + "xdr, te_type", + [ + ( + "AAAAAMvXcdYjKhx0qxnsDsczxKuqa/65lZz6sjjHHczyh50JAAAAyAAAAAAAAAABAAAAAQAAAAAAADA5AAAAAAAA3dUAAAACAAAAAAAAAGQAAAACAAAAAAAAAAEAAAAA0pjFgVcRZZHpMgnpXHpb/xIbLh0/YYto0PzI7+Xl5HAAAAAAAAAAAlQL5AAAAAAAAAAACgAAAAVoZWxsbwAAAAAAAAEAAAAFd29ybGQAAAAAAAAAAAAAAvKHnQkAAABAM4dg0J1LEFBmbDESJ5d+60WCuZC8lnA80g45qyEgz2oRBSNw1mOfZETnL/BgrebkG/K03oI2Wqcs9lvDKrDGDE0sOBsAAAAglOgiOlGKwWqMsRCrGVLvFNosELJkZFw4yLPYK9KyAAA=", + TransactionEnvelope, + ), + ( + "AAAAAgAAAADL13HWIyocdKsZ7A7HM8Srqmv+uZWc+rI4xx3M8oedCQAAAMgAAAAAAAAAAQAAAAEAAAAAAAAwOQAAAAAAAN3VAAAAAgAAAAAAAABkAAAAAQAAAAAAAAABAAAAANKYxYFXEWWR6TIJ6Vx6W/8SGy4dP2GLaND8yO/l5eRwAAAAAAAAAAJUC+QAAAAAAAAAAAHyh50JAAAAQCXOQnmno3he687bKRtDc6+BXRUf8t+RnTuHy+sKf35UjfFiQbIge+txehmg0N61JsFWfwbL0JtgOjzyeZw5JAs=", + TransactionEnvelope, + ), + ( + "AAAABQAAAADgSJG2GOUMy/H9lHyjYZOwyuyytH8y0wWaoc596L+bEgAAAAAAAAGQAAAAAgAAAAAcVPE+R/n1VnbIrAK3tu5yVfTkhkqVz04ShWk2SrF3wAAAAMgAAAAAAAAACAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAADgSJG2GOUMy/H9lHyjYZOwyuyytH8y0wWaoc596L+bEgAAAAAAAAAEqBfIAAAAAAAAAAABSrF3wAAAAEAordQh63kT50muRLVYaWW7Pgtt8i1tc4t9Bv9MWFWFN3WfTHSU2Jxv7hedjZEyfBPvaS/XnwvYJFcHgPDd1JkNAAAAAAAAAAHov5sSAAAAQKu/RuArXn/P13IIJ8WlnVDStwOquXM0CsWzA4ooZY6gqJ3k1EfmMVIJ0cir0bMTJD9r+g2IUZCANU7wdC38PA0=", + FeeBumpTransactionEnvelope, + ), + ], + ) + def test_parse_transaction_envelope_from_xdr(self, xdr, te_type): + te = parse_transaction_envelope_from_xdr(xdr, "") + assert isinstance(te, te_type) diff --git a/tests/test_transaction_envelope.py b/tests/test_transaction_envelope.py index d1e652f8..6be6d6e7 100644 --- a/tests/test_transaction_envelope.py +++ b/tests/test_transaction_envelope.py @@ -13,7 +13,7 @@ class TestTransactionEnvelope: - def test_to_xdr(self): + def test_to_xdr_v0(self): # GDF5O4OWEMVBY5FLDHWA5RZTYSV2U276XGKZZ6VSHDDR3THSQ6OQS7UM source = Keypair.from_secret( "SCCS5ZBI7WVIJ4SW36WGOQQIWJYCL3VOAULSXX3FB57USIO25EDOYQHH" @@ -41,6 +41,30 @@ def test_to_xdr(self): ) assert restore_te.to_xdr() == te_xdr + def test_to_xdr_v1(self): + # GDF5O4OWEMVBY5FLDHWA5RZTYSV2U276XGKZZ6VSHDDR3THSQ6OQS7UM + source = Keypair.from_secret( + "SCCS5ZBI7WVIJ4SW36WGOQQIWJYCL3VOAULSXX3FB57USIO25EDOYQHH" + ) + destination = "GDJJRRMBK4IWLEPJGIE6SXD2LP7REGZODU7WDC3I2D6MR37F4XSHBKX2" + amount = "1000.0" + sequence = 1 + memo = IdMemo(100) + fee = 200 + asset = Asset.native() + time_bounds = TimeBounds(12345, 56789) + ops = [Payment(destination, asset, amount)] + tx = Transaction(source, sequence, fee, ops, memo, time_bounds, True) + te = TransactionEnvelope(tx, Network.PUBLIC_NETWORK_PASSPHRASE) + assert binascii.hexlify(te.hash()).decode() == te.hash_hex() + te.sign(source) + te_xdr = "AAAAAgAAAADL13HWIyocdKsZ7A7HM8Srqmv+uZWc+rI4xx3M8oedCQAAAMgAAAAAAAAAAQAAAAEAAAAAAAAwOQAAAAAAAN3VAAAAAgAAAAAAAABkAAAAAQAAAAAAAAABAAAAANKYxYFXEWWR6TIJ6Vx6W/8SGy4dP2GLaND8yO/l5eRwAAAAAAAAAAJUC+QAAAAAAAAAAAHyh50JAAAAQCXOQnmno3he687bKRtDc6+BXRUf8t+RnTuHy+sKf35UjfFiQbIge+txehmg0N61JsFWfwbL0JtgOjzyeZw5JAs=" + assert te.to_xdr() == te_xdr + restore_te = TransactionEnvelope.from_xdr( + te_xdr, Network.PUBLIC_NETWORK_PASSPHRASE + ) + assert restore_te.to_xdr() == te_xdr + def test_already_signed_raise(self): # GDF5O4OWEMVBY5FLDHWA5RZTYSV2U276XGKZZ6VSHDDR3THSQ6OQS7UM source = Keypair.from_secret( From 4e9c2ac4dec410393f59443a39bbedf84e3e7608 Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Sun, 19 Apr 2020 09:50:01 +0800 Subject: [PATCH 7/8] test: add more tests for FeeBumpTransaction --- stellar_sdk/__init__.py | 64 ++++++------- stellar_sdk/base_transaction_envelope.py | 2 - stellar_sdk/fee_bump_transaction.py | 8 +- stellar_sdk/fee_bump_transaction_envelope.py | 10 +-- stellar_sdk/helpers.py | 2 +- stellar_sdk/transaction_envelope.py | 11 +-- tests/test_fee_bump_transaction.py | 94 ++++++++++++++++++++ tests/test_transaction.py | 11 ++- 8 files changed, 142 insertions(+), 60 deletions(-) diff --git a/stellar_sdk/__init__.py b/stellar_sdk/__init__.py index 69963123..d3f64f3a 100644 --- a/stellar_sdk/__init__.py +++ b/stellar_sdk/__init__.py @@ -29,33 +29,37 @@ from .client.aiohttp_client import AiohttpClient from .helpers import * -__all__ = [ - "__title__", - "__description__", - "__url__", - "__version__", - "__author__", - "__author_email__", - "__license__", - "Account", - "Asset", - "FeeBumpTransaction", - "FeeBumpTransactionEnvelope", - "Keypair", - "Memo", - "NoneMemo", - "TextMemo", - "IdMemo", - "HashMemo", - "ReturnHashMemo", - "Network", - "Price", - "Server", - "Signer", - "TimeBounds", - "Transaction", - "TransactionBuilder", - "TransactionEnvelope", - "RequestsClient", - "AiohttpClient", -] + operation_all + helpers.__all__ +__all__ = ( + [ + "__title__", + "__description__", + "__url__", + "__version__", + "__author__", + "__author_email__", + "__license__", + "Account", + "Asset", + "FeeBumpTransaction", + "FeeBumpTransactionEnvelope", + "Keypair", + "Memo", + "NoneMemo", + "TextMemo", + "IdMemo", + "HashMemo", + "ReturnHashMemo", + "Network", + "Price", + "Server", + "Signer", + "TimeBounds", + "Transaction", + "TransactionBuilder", + "TransactionEnvelope", + "RequestsClient", + "AiohttpClient", + ] + + operation_all + + helpers.__all__ +) diff --git a/stellar_sdk/base_transaction_envelope.py b/stellar_sdk/base_transaction_envelope.py index 4419983f..41c23e6e 100644 --- a/stellar_sdk/base_transaction_envelope.py +++ b/stellar_sdk/base_transaction_envelope.py @@ -13,11 +13,9 @@ class BaseTransactionEnvelope(Generic[T]): def __init__( self, - transaction, network_passphrase: str, signatures: List[Xdr.types.DecoratedSignature] = None, ) -> None: - self.transaction = transaction self.network_id: bytes = Network(network_passphrase).network_id() self.signatures: List[Xdr.types.DecoratedSignature] = signatures or [] diff --git a/stellar_sdk/fee_bump_transaction.py b/stellar_sdk/fee_bump_transaction.py index a981982d..af59b7d9 100644 --- a/stellar_sdk/fee_bump_transaction.py +++ b/stellar_sdk/fee_bump_transaction.py @@ -37,8 +37,12 @@ def __init__( inner_base_fee_rate = self._inner_transaction.fee / inner_operations_length if self.base_fee < inner_base_fee_rate or self.base_fee < BASE_FEE: raise ValueError( - "Invalid `base_fee`, it should be at least %d stroops.", - inner_operations_length, + "Invalid `base_fee`, it should be at least %d stroops." + % ( + self._inner_transaction.fee + if self._inner_transaction.fee > BASE_FEE + else BASE_FEE + ) ) def to_xdr_object(self) -> Xdr.types.FeeBumpTransaction: diff --git a/stellar_sdk/fee_bump_transaction_envelope.py b/stellar_sdk/fee_bump_transaction_envelope.py index 5ee732e9..eee8eef7 100644 --- a/stellar_sdk/fee_bump_transaction_envelope.py +++ b/stellar_sdk/fee_bump_transaction_envelope.py @@ -29,7 +29,7 @@ def __init__( network_passphrase: str, signatures: List[Xdr.types.DecoratedSignature] = None, ) -> None: - super().__init__(transaction, network_passphrase, signatures) + super().__init__(network_passphrase, signatures) self.transaction = transaction def signature_base(self) -> bytes: @@ -65,14 +65,6 @@ def to_xdr_object(self) -> Xdr.types.TransactionEnvelope: tx_envelope = Xdr.types.FeeBumpTransactionEnvelope(tx, self.signatures) return Xdr.types.TransactionEnvelope(type=te_type, feeBump=tx_envelope) - def to_xdr(self) -> str: - """Get the base64 encoded XDR string representing this - :class:`TransactionEnvelope`. - - :return: XDR TransactionEnvelope base64 string object - """ - return self.to_xdr_object().to_xdr() - @classmethod def from_xdr_object( cls, te_xdr_object: Xdr.types.TransactionEnvelope, network_passphrase: str diff --git a/stellar_sdk/helpers.py b/stellar_sdk/helpers.py index 0d4813c7..ef5a2717 100644 --- a/stellar_sdk/helpers.py +++ b/stellar_sdk/helpers.py @@ -25,7 +25,7 @@ def parse_transaction_envelope_from_xdr( return TransactionEnvelope.from_xdr_object(xdr_object, network_passphrase) else: raise ValueError( - "This transaction envelope type is not currently supported, type = {}.".format( + "This transaction envelope type is not supported, type = {}.".format( te_type ) ) diff --git a/stellar_sdk/transaction_envelope.py b/stellar_sdk/transaction_envelope.py index 596e7e20..88404603 100644 --- a/stellar_sdk/transaction_envelope.py +++ b/stellar_sdk/transaction_envelope.py @@ -29,7 +29,7 @@ def __init__( network_passphrase: str, signatures: List[Xdr.types.DecoratedSignature] = None, ) -> None: - super().__init__(transaction, network_passphrase, signatures) + super().__init__(network_passphrase, signatures) self.transaction = transaction def signature_base(self) -> bytes: @@ -73,14 +73,6 @@ def to_xdr_object(self) -> Xdr.types.TransactionEnvelope: tx_envelope = Xdr.types.TransactionV0Envelope(tx, self.signatures) return Xdr.types.TransactionEnvelope(type=te_type, v0=tx_envelope) - def to_xdr(self) -> str: - """Get the base64 encoded XDR string representing this - :class:`TransactionEnvelope`. - - :return: XDR TransactionEnvelope base64 string object - """ - return self.to_xdr_object().to_xdr() - @classmethod def from_xdr_object( cls, te_xdr_object: Xdr.types.TransactionEnvelope, network_passphrase: str @@ -100,7 +92,6 @@ def from_xdr_object( tx = Transaction.from_xdr_object(te_xdr_object.v1, v1=True) signatures = te_xdr_object.v1.signatures else: - # TODO: more detail information raise ValueError("Invalid EnvelopeType: %d.", te_xdr_object.type) te = cls(tx, network_passphrase=network_passphrase, signatures=signatures) return te diff --git a/tests/test_fee_bump_transaction.py b/tests/test_fee_bump_transaction.py index 44987ebb..85d315d9 100644 --- a/tests/test_fee_bump_transaction.py +++ b/tests/test_fee_bump_transaction.py @@ -1,3 +1,5 @@ +import pytest + from stellar_sdk import ( TransactionBuilder, Account, @@ -6,6 +8,7 @@ FeeBumpTransactionEnvelope, FeeBumpTransaction, ) +from stellar_sdk.exceptions import ValueError class TestFeeBumpTransaction: @@ -48,3 +51,94 @@ def test_to_xdr(self): assert restore_tx.fee_source.public_key == fee_source.public_key assert restore_tx.base_fee == base_fee assert restore_tx.inner_transaction_envelope.to_xdr() == inner_tx.to_xdr() + + def test_tx_not_v1(self): + inner_keypair = Keypair.from_secret( + "SBKTIFHJSS3JJWEZO2W74DZSA45WZU56LOL3AY7GAW63BXPEJQFYV53E" + ) + inner_source = Account(inner_keypair.public_key, 7) + destination = "GDQERENWDDSQZS7R7WKHZI3BSOYMV3FSWR7TFUYFTKQ447PIX6NREOJM" + amount = "2000.0000000" + inner_tx = ( + TransactionBuilder( + inner_source, Network.TESTNET_NETWORK_PASSPHRASE, 200, v1=False + ) + .append_payment_op(destination=destination, amount=amount, asset_code="XLM") + .add_time_bounds(0, 0) + .build() + ) + inner_tx.sign(inner_keypair) + fee_source = Keypair.from_secret( + "SB7ZMPZB3YMMK5CUWENXVLZWBK4KYX4YU5JBXQNZSK2DP2Q7V3LVTO5V" + ) + base_fee = 200 + with pytest.raises( + ValueError, + match="Invalid `inner_transaction`, it should be TransactionV1.", + ): + TransactionBuilder.build_fee_bump_transaction( + fee_source.public_key, + base_fee, + inner_tx, + Network.TESTNET_NETWORK_PASSPHRASE, + ) + + def test_tx_fee_less_than_inner_tx_fee(self): + inner_keypair = Keypair.from_secret( + "SBKTIFHJSS3JJWEZO2W74DZSA45WZU56LOL3AY7GAW63BXPEJQFYV53E" + ) + inner_source = Account(inner_keypair.public_key, 7) + destination = "GDQERENWDDSQZS7R7WKHZI3BSOYMV3FSWR7TFUYFTKQ447PIX6NREOJM" + amount = "2000.0000000" + inner_tx = ( + TransactionBuilder( + inner_source, Network.TESTNET_NETWORK_PASSPHRASE, 200, v1=True + ) + .append_payment_op(destination=destination, amount=amount, asset_code="XLM") + .add_time_bounds(0, 0) + .build() + ) + inner_tx.sign(inner_keypair) + fee_source = Keypair.from_secret( + "SB7ZMPZB3YMMK5CUWENXVLZWBK4KYX4YU5JBXQNZSK2DP2Q7V3LVTO5V" + ) + base_fee = 150 + with pytest.raises( + ValueError, match="Invalid `base_fee`, it should be at least 200 stroops.", + ): + TransactionBuilder.build_fee_bump_transaction( + fee_source.public_key, + base_fee, + inner_tx, + Network.TESTNET_NETWORK_PASSPHRASE, + ) + + def test_tx_fee_less_than_base_fee(self): + inner_keypair = Keypair.from_secret( + "SBKTIFHJSS3JJWEZO2W74DZSA45WZU56LOL3AY7GAW63BXPEJQFYV53E" + ) + inner_source = Account(inner_keypair.public_key, 7) + destination = "GDQERENWDDSQZS7R7WKHZI3BSOYMV3FSWR7TFUYFTKQ447PIX6NREOJM" + amount = "2000.0000000" + inner_tx = ( + TransactionBuilder( + inner_source, Network.TESTNET_NETWORK_PASSPHRASE, 50, v1=True + ) + .append_payment_op(destination=destination, amount=amount, asset_code="XLM") + .add_time_bounds(0, 0) + .build() + ) + inner_tx.sign(inner_keypair) + fee_source = Keypair.from_secret( + "SB7ZMPZB3YMMK5CUWENXVLZWBK4KYX4YU5JBXQNZSK2DP2Q7V3LVTO5V" + ) + base_fee = 60 + with pytest.raises( + ValueError, match="Invalid `base_fee`, it should be at least 100 stroops.", + ): + TransactionBuilder.build_fee_bump_transaction( + fee_source.public_key, + base_fee, + inner_tx, + Network.TESTNET_NETWORK_PASSPHRASE, + ) diff --git a/tests/test_transaction.py b/tests/test_transaction.py index 62b7f5ad..2014da3e 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -48,12 +48,11 @@ def test_to_xdr_str_source_v1(self): tx_object = Transaction( source, sequence, fee, ops, memo, time_bounds, True ).to_xdr_object() - assert ( - tx_object.to_xdr() - == "AAAAAImbKEDtVjbFbdxfFLI5dfefG6I4jSaU5MVuzd3JYOXvAAAAyAAAAAAAAAABAAAAAQAAAAAAADA5AAAAAAAA3dUAAAACAAAAAAAAAGQAAAACAAAAAAAAAAEAAAAA0pjFgVcRZZHpMgnpXHpb/xIbLh0/YYto0PzI7+Xl5HAAAAAAAAAAAlQL5AAAAAAAAAAACgAAAAVoZWxsbwAAAAAAAAEAAAAFd29ybGQAAAAAAAAA" - ) + xdr = "AAAAAImbKEDtVjbFbdxfFLI5dfefG6I4jSaU5MVuzd3JYOXvAAAAyAAAAAAAAAABAAAAAQAAAAAAADA5AAAAAAAA3dUAAAACAAAAAAAAAGQAAAACAAAAAAAAAAEAAAAA0pjFgVcRZZHpMgnpXHpb/xIbLh0/YYto0PzI7+Xl5HAAAAAAAAAAAlQL5AAAAAAAAAAACgAAAAVoZWxsbwAAAAAAAAEAAAAFd29ybGQAAAAAAAAA" + + assert tx_object.to_xdr() == xdr - restore_transaction = Transaction.from_xdr_object(tx_object, True) + restore_transaction = Transaction.from_xdr(xdr, True) assert isinstance(restore_transaction, Transaction) assert restore_transaction.source == Keypair.from_public_key(source) assert restore_transaction.fee == fee @@ -119,7 +118,7 @@ def test_to_xdr_str_source_v0(self): # == "AAAAAImbKEDtVjbFbdxfFLI5dfefG6I4jSaU5MVuzd3JYOXvAAAAyAAAAAAAAAABAAAAAQAAAAAAADA5AAAAAAAA3dUAAAACAAAAAAAAAGQAAAACAAAAAAAAAAEAAAAA0pjFgVcRZZHpMgnpXHpb/xIbLh0/YYto0PzI7+Xl5HAAAAAAAAAAAlQL5AAAAAAAAAAACgAAAAVoZWxsbwAAAAAAAAEAAAAFd29ybGQAAAAAAAAA" # ) - restore_transaction = Transaction.from_xdr_object(tx_object, False) + restore_transaction = Transaction.from_xdr(tx_object.to_xdr(), False) assert isinstance(restore_transaction, Transaction) assert restore_transaction.source == Keypair.from_public_key(source) assert restore_transaction.fee == fee From 101c2b0ec7a94b7c3cf147f04e0950f287875fa8 Mon Sep 17 00:00:00 2001 From: overcat <4catcode@gmail.com> Date: Sun, 19 Apr 2020 10:50:39 +0800 Subject: [PATCH 8/8] docs: docs for CAP-0015 --- docs/en/api.rst | 19 +++++++++++++++++++ docs/zh_CN/api.rst | 19 +++++++++++++++++++ stellar_sdk/fee_bump_transaction.py | 16 +++++++++++++--- stellar_sdk/helpers.py | 11 +++++++++++ stellar_sdk/transaction.py | 4 ++-- stellar_sdk/transaction_builder.py | 15 ++++++++++++++- stellar_sdk/transaction_envelope.py | 2 +- 7 files changed, 79 insertions(+), 7 deletions(-) diff --git a/docs/en/api.rst b/docs/en/api.rst index a2eb9517..f4234c1e 100644 --- a/docs/en/api.rst +++ b/docs/en/api.rst @@ -461,6 +461,21 @@ TransactionEnvelope .. autoclass:: stellar_sdk.transaction_envelope.TransactionEnvelope :members: + :inherited-members: + +FeeBumpTransaction +^^^^^^^^^^^^^^^^^^ + +.. autoclass:: stellar_sdk.fee_bump_transaction.FeeBumpTransaction + :members: + :inherited-members: + +FeeBumpTransactionEnvelope +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: stellar_sdk.fee_bump_transaction_envelope.FeeBumpTransactionEnvelope + :members: + :inherited-members: TransactionBuilder ^^^^^^^^^^^^^^^^^^ @@ -468,6 +483,10 @@ TransactionBuilder .. autoclass:: stellar_sdk.transaction_builder.TransactionBuilder :members: +Helpers +^^^^^^^ +.. autofunction:: stellar_sdk.helpers.parse_transaction_envelope_from_xdr + Stellar Ecosystem Proposals ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SEP 0001: stellar.toml diff --git a/docs/zh_CN/api.rst b/docs/zh_CN/api.rst index 3ed6a812..4f8830e0 100644 --- a/docs/zh_CN/api.rst +++ b/docs/zh_CN/api.rst @@ -461,6 +461,21 @@ TransactionEnvelope .. autoclass:: stellar_sdk.transaction_envelope.TransactionEnvelope :members: + :inherited-members: + +FeeBumpTransaction +^^^^^^^^^^^^^^^^^^ + +.. autoclass:: stellar_sdk.fee_bump_transaction.FeeBumpTransaction + :members: + :inherited-members: + +FeeBumpTransactionEnvelope +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: stellar_sdk.fee_bump_transaction_envelope.FeeBumpTransactionEnvelope + :members: + :inherited-members: TransactionBuilder ^^^^^^^^^^^^^^^^^^ @@ -468,6 +483,10 @@ TransactionBuilder .. autoclass:: stellar_sdk.transaction_builder.TransactionBuilder :members: +Helpers +^^^^^^^ +.. autofunction:: stellar_sdk.helpers.parse_transaction_envelope_from_xdr + Stellar Ecosystem Proposals ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SEP 0001: stellar.toml diff --git a/stellar_sdk/fee_bump_transaction.py b/stellar_sdk/fee_bump_transaction.py index af59b7d9..9475df9b 100644 --- a/stellar_sdk/fee_bump_transaction.py +++ b/stellar_sdk/fee_bump_transaction.py @@ -13,6 +13,16 @@ class FeeBumpTransaction: + """The :class:`FeeBumpTransaction` object, which represents a fee bump transaction + on Stellar's network. + + See `CAP-0015 `_ for more information. + + :param fee_source: The account paying for the transaction. + :param base_fee: The max fee willing to pay per operation in inner transaction (**in stroops**). + :param inner_transaction_envelope: The TransactionEnvelope to be bumped by the fee bump transaction. + """ + def __init__( self, fee_source: Union[Keypair, str], @@ -67,7 +77,7 @@ def from_xdr_object( ) -> "FeeBumpTransaction": """Create a new :class:`FeeBumpTransaction` from an XDR object. - :param tx_xdr_object: The XDR object that represents a transaction. + :param tx_xdr_object: The XDR object that represents a fee bump transaction. :param network_passphrase: The network to connect to for verifying and retrieving additional attributes from. :return: A new :class:`FeeBumpTransaction` object from the given XDR Transaction object. @@ -90,12 +100,12 @@ def from_xdr_object( @classmethod def from_xdr(cls, xdr: str, network_passphrase: str) -> "FeeBumpTransaction": - """Create a new :class:`Transaction` from an XDR string. + """Create a new :class:`FeeBumpTransaction` from an XDR string. :param xdr: The XDR string that represents a transaction. :param network_passphrase: The network to connect to for verifying and retrieving additional attributes from. - :return: A new :class:`TransactionEnvelope` object from the given XDR TransactionEnvelope base64 string object. + :return: A new :class:`FeeBumpTransaction` object from the given XDR FeeBumpTransaction base64 string object. """ xdr_object = Xdr.types.FeeBumpTransaction.from_xdr(xdr) return cls.from_xdr_object(xdr_object, network_passphrase) diff --git a/stellar_sdk/helpers.py b/stellar_sdk/helpers.py index ef5a2717..2ad120c9 100644 --- a/stellar_sdk/helpers.py +++ b/stellar_sdk/helpers.py @@ -12,6 +12,17 @@ def parse_transaction_envelope_from_xdr( xdr: str, network_passphrase: str ) -> Union[TransactionEnvelope, FeeBumpTransactionEnvelope]: + """When you are not sure whether your XDR belongs to + :py:class:`TransactionEnvelope ` + or :py:class:`FeeBumpTransactionEnvelope `, + you can use this helper function. + + :param xdr: Transaction envelope XDR + :param network_passphrase: The network to connect to for verifying and retrieving + additional attributes from. (ex. 'Public Global Stellar Network ; September 2015') + :raises: :exc:`Value ` - XDR is neither :py:class:`TransactionEnvelope ` + nor :py:class:`FeeBumpTransactionEnvelope ` + """ xdr_object = Xdr.types.TransactionEnvelope.from_xdr(xdr) te_type = xdr_object.type if te_type == Xdr.const.ENVELOPE_TYPE_TX_FEE_BUMP: diff --git a/stellar_sdk/transaction.py b/stellar_sdk/transaction.py index 221f0d24..91d2a47b 100644 --- a/stellar_sdk/transaction.py +++ b/stellar_sdk/transaction.py @@ -12,7 +12,7 @@ class Transaction: - """The :class:`Transaction` object, which represents a transaction + """The :class:`Transaction` object, which represents a transaction(Transaction or TransactionV0) on Stellar's network. A transaction contains a list of operations, which are all executed @@ -162,7 +162,7 @@ def from_xdr(cls, xdr: str, v1: bool = False) -> "Transaction": See `CAP-0015 `_ for more information. - :return: A new :class:`TransactionEnvelope` object from the given XDR TransactionEnvelope base64 string object. + :return: A new :class:`Transaction` object from the given XDR Transaction base64 string object. """ if v1: xdr_object = Xdr.types.Transaction.from_xdr(xdr) diff --git a/stellar_sdk/transaction_builder.py b/stellar_sdk/transaction_builder.py index 430a35bd..0be15901 100644 --- a/stellar_sdk/transaction_builder.py +++ b/stellar_sdk/transaction_builder.py @@ -92,6 +92,18 @@ def build_fee_bump_transaction( inner_transaction_envelope: TransactionEnvelope, network_passphrase: str = Network.TESTNET_NETWORK_PASSPHRASE, ): + """Create a + :py:class:`FeeBumpTransactionEnvelope ` + object. + + See `CAP-0015 `_ for more information. + + :param fee_source: The account paying for the transaction. + :param base_fee: The max fee willing to pay per operation in inner transaction (**in stroops**). + :param inner_transaction_envelope: The TransactionEnvelope to be bumped by the fee bump transaction. + :param network_passphrase: The network to connect to for verifying and retrieving additional attributes from. + :return: a :class:`TransactionBuilder ` via the XDR object. + """ fee_bump_transaction = FeeBumpTransaction( fee_source=fee_source, base_fee=base_fee, @@ -105,7 +117,7 @@ def build_fee_bump_transaction( @staticmethod def from_xdr(xdr: str, network_passphrase: str) -> "TransactionBuilder": """Create a :class:`TransactionBuilder - ` via an XDR + ` via an XDR object. In addition, sets the fields of this builder (the transaction envelope, @@ -115,6 +127,7 @@ def from_xdr(xdr: str, network_passphrase: str) -> "TransactionBuilder": :param xdr: The XDR object representing the transaction envelope to which this builder is setting its state to. :param network_passphrase: The network to connect to for verifying and retrieving additional attributes from. + :return: a :class:`TransactionBuilder ` via the XDR object. """ transaction_envelope = TransactionEnvelope.from_xdr( xdr=xdr, network_passphrase=network_passphrase diff --git a/stellar_sdk/transaction_envelope.py b/stellar_sdk/transaction_envelope.py index 88404603..85924d21 100644 --- a/stellar_sdk/transaction_envelope.py +++ b/stellar_sdk/transaction_envelope.py @@ -7,7 +7,7 @@ __all__ = ["TransactionEnvelope"] -class TransactionEnvelope(BaseTransactionEnvelope): +class TransactionEnvelope(BaseTransactionEnvelope["TransactionEnvelope"]): """The :class:`TransactionEnvelope` object, which represents a transaction envelope ready to sign and submit to send over the network.