Пример #1
0
        /// <summary>
        /// Equality implementation.
        /// </summary>
        /// <param name="other">
        /// The other <code>Signatory</code> object to compare.
        /// </param>
        /// <returns>
        /// True if public key layout and requirements are equivalent to the
        /// other <code>Signatory</code> object.
        /// </returns>
        public bool Equals(Signatory?other)
        {
            if (other is null)
            {
                return(false);
            }
            if (_type != other._type)
            {
                return(false);
            }
            switch (_type)
            {
            case Type.Ed25519:
                return(((Ed25519PrivateKeyParameters)_data).GetEncoded().SequenceEqual(((Ed25519PrivateKeyParameters)other._data).GetEncoded()));

            case Type.RSA3072:      // Will need more work
            case Type.ECDSA384:     // Will need more work
                return(Equals(_data, other._data));

            case Type.List:
                var thisList  = (Signatory[])_data;
                var otherList = (Signatory[])other._data;
                if (thisList.Length == otherList.Length)
                {
                    for (int i = 0; i < thisList.Length; i++)
                    {
                        if (!thisList[i].Equals(otherList[i]))
                        {
                            return(false);
                        }
                    }
                    return(true);
                }
                break;

            case Type.Callback:
                return(ReferenceEquals(_data, other._data));

            case Type.Pending:
                var thisPending  = (PendingParams)_data;
                var otherPending = (PendingParams)other._data;
                return(thisPending.Equals(otherPending));
            }
            return(false);
        }
Пример #2
0
 /// <summary>
 /// Internal implementation for Transfer Crypto.
 /// Returns either a receipt or record or throws
 /// an exception.
 /// </summary>
 private async Task <TResult> TransferImplementationAsync <TResult>(Address fromAddress, Address toAddress, long amount, Signatory?signatory, Action <IContext>?configure) where TResult : new()
 {
     fromAddress = RequireInputParameter.FromAddress(fromAddress);
     toAddress   = RequireInputParameter.ToAddress(toAddress);
     amount      = RequireInputParameter.Amount(amount);
     var transferList = new[] { (fromAddress, -amount), (toAddress, amount) };
Пример #3
0
        /// <summary>
        /// Internal helper function implementing the file delete functionality.
        /// </summary>
        public async Task <TResult> SystemRestoreFileImplementationAsync <TResult>(Address fileToRestore, Signatory?signatory, Action <IContext>?configure = null) where TResult : new()
        {
            fileToRestore           = RequireInputParameter.FileToRestore(fileToRestore);
            await using var context = CreateChildContext(configure);
            RequireInContext.Gateway(context);
            var payer           = RequireInContext.Payer(context);
            var signatories     = Transactions.GatherSignatories(context, signatory);
            var transactionId   = Transactions.GetOrCreateTransactionID(context);
            var transactionBody = new TransactionBody(context, transactionId);

            transactionBody.SystemUndelete = new SystemUndeleteTransactionBody
            {
                FileID = new FileID(fileToRestore)
            };
            var receipt = await transactionBody.SignAndExecuteWithRetryAsync(signatories, context);

            if (receipt.Status != ResponseCodeEnum.Success)
            {
                throw new TransactionException($"Unable to delete file, status: {receipt.Status}", transactionId.ToTxId(), (ResponseCode)receipt.Status);
            }
            var result = new TResult();

            if (result is TransactionRecord rec)
            {
                var record = await GetTransactionRecordAsync(context, transactionId);

                record.FillProperties(rec);
            }
            else if (result is TransactionReceipt rcpt)
            {
                receipt.FillProperties(transactionId, rcpt);
            }
            return(result);
        }
Пример #4
0
 /// <summary>
 /// Adds a signature to a pending transaction record. The Scheduled Transaction executes
 /// this signature completes the list of required signatures for execution.
 /// </summary>
 /// <param name="pending">
 /// The identifier (Address/Schedule ID) of the pending transaction to sign.
 /// </param>
 /// <param name="signatory">
 /// The signatory containing any additional private keys or callbacks
 /// to meet the key signing requirements for participants.
 /// </param>
 /// <param name="configure">
 /// Optional callback method providing an opportunity to modify
 /// the execution configuration for just this method call.
 /// It is executed prior to submitting the request to the network.
 /// </param>
 /// <returns>
 /// A Record for the transaction indicating success.
 /// </returns>
 /// <exception cref="ArgumentOutOfRangeException">If required arguments are missing.</exception>
 /// <exception cref="InvalidOperationException">If required context configuration is missing.</exception>
 /// <exception cref="PrecheckException">If the gateway node create rejected the request upon submission.</exception>
 /// <exception cref="ConsensusException">If the network was unable to come to consensus before the duration of the transaction expired.</exception>
 /// <exception cref="TransactionException">If the network rejected the create request as invalid or had missing data.</exception>
 public async Task <TransactionRecord> SignPendingTransactionWithRecordAsync(Address pending, Signatory?signatory = null, Action <IContext>?configure = null)
 {
     return(new TransactionRecord(await ExecuteTransactionAsync(new ScheduleSignTransactionBody(pending), configure, true, signatory).ConfigureAwait(false)));
 }
Пример #5
0
        /// <summary>
        /// Sends an HCS message of arbitrary size
        /// to the network by breaking the message
        /// into segments, submitting each segment
        /// in sequence.  Manages the segment metadata
        /// internally, returning an array of receipts
        /// representing the transactions required to
        /// upload the entier message.
        /// </summary>
        /// <param name="client">
        /// A Hashgraph Client instance.
        /// </param>
        /// <param name="topic">
        /// The address of the topic for the message.
        /// </param>
        /// <param name="message">
        /// The value of the message, may exceed the
        /// network limit size.
        /// </param>
        /// <param name="segmentSize">
        /// The maximum size of each segment.  Must
        /// be under the current allowed size for
        /// transactions currently supported by the
        /// network.  The method will break the
        /// message into as many segments as necessary
        /// to fulfill uploading the entire message.
        /// </param>
        /// <param name="signatory">
        /// The signatory containing any additional private keys or callbacks
        /// to meet the key signing requirements for participants.
        /// </param>
        /// <param name="configure">
        /// Optional callback method providing an opportunity to modify
        /// the execution configuration for just this method call.
        /// It is executed prior to submitting the request to the network.
        /// </param>
        /// <returns>
        /// An array of Submit Message Receipts indicating success, one for each
        /// segment uploaded.  The Transaction ID of the first receipt matches
        /// the correlation transaction ID for the series of message segments
        /// as a whole.
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">If required arguments are missing.</exception>
        /// <exception cref="InvalidOperationException">If required context configuration is missing.</exception>
        /// <exception cref="PrecheckException">If the gateway node create rejected the request upon submission.</exception>
        /// <exception cref="ConsensusException">If the network was unable to come to consensus before the duration of the transaction expired.</exception>
        /// <exception cref="TransactionException">If the network rejected the create request as invalid or had missing data.</exception>

        public static async Task <SubmitMessageReceipt[]> SubmitLargeMessageAsync(this Client client, Address topic, ReadOnlyMemory <byte> message, int segmentSize, Signatory?signatory = null, Action <IContext>?configure = null)
        {
            await using var configuredClient = client.Clone(configure);
            var segmentCount = (message.Length - 1) / segmentSize + 1;
            var receipts     = new SubmitMessageReceipt[segmentCount];

            receipts[0] = await configuredClient.SubmitMessageAsync(new SubmitMessageParams
            {
                Topic             = topic,
                Segment           = segmentCount > 1 ? message.Slice(0, segmentSize) : message,
                Index             = 1,
                TotalSegmentCount = segmentCount,
                Signatory         = signatory
            }).ConfigureAwait(false);

            var parentTx = receipts[0].Id;

            for (int i = 1; i < segmentCount - 1; i++)
            {
                receipts[i] = await configuredClient.SubmitMessageAsync(new SubmitMessageParams
                {
                    Topic             = topic,
                    Segment           = message.Slice(segmentSize *i, segmentSize),
                    ParentTxId        = parentTx,
                    Index             = i + 1,
                    TotalSegmentCount = segmentCount,
                    Signatory         = signatory
                }).ConfigureAwait(false);
            }
            if (segmentCount > 1)
            {
                receipts[segmentCount - 1] = await configuredClient.SubmitMessageAsync(new SubmitMessageParams
                {
                    Topic             = topic,
                    Segment           = message.Slice(segmentSize * (segmentCount - 1)),
                    ParentTxId        = parentTx,
                    Index             = segmentCount,
                    TotalSegmentCount = segmentCount,
                    Signatory         = signatory
                }).ConfigureAwait(false);
            }
            return(receipts);
        }
Пример #6
0
        /// <summary>
        /// Internal implementation of delete token method.
        /// </summary>
        private async Task <TResult> SuspendTokenImplementationAsync <TResult>(Address token, Address address, Signatory?signatory, Action <IContext>?configure) where TResult : new()
        {
            token   = RequireInputParameter.Token(token);
            address = RequireInputParameter.Address(address);
            await using var context = CreateChildContext(configure);
            RequireInContext.Gateway(context);
            var payer           = RequireInContext.Payer(context);
            var signatories     = Transactions.GatherSignatories(context, signatory);
            var transactionId   = Transactions.GetOrCreateTransactionID(context);
            var transactionBody = new TransactionBody(context, transactionId);

            transactionBody.TokenFreeze = new TokenFreezeAccountTransactionBody
            {
                Token   = new TokenID(token),
                Account = new AccountID(address)
            };
            var receipt = await transactionBody.SignAndExecuteWithRetryAsync(signatories, context);

            if (receipt.Status != ResponseCodeEnum.Success)
            {
                throw new TransactionException($"Unable to Suspend Token, status: {receipt.Status}", transactionId.ToTxId(), (ResponseCode)receipt.Status);
            }
            var result = new TResult();

            if (result is TransactionRecord rec)
            {
                var record = await GetTransactionRecordAsync(context, transactionId);

                record.FillProperties(rec);
            }
            else if (result is TransactionReceipt rcpt)
            {
                receipt.FillProperties(transactionId, rcpt);
            }
            return(result);
        }
Пример #7
0
        /// <summary>
        /// Internal implementation of the submit message call.
        /// </summary>
        private async Task <NetworkResult> SubmitMessageImplementationAsync(Address topic, ReadOnlyMemory <byte> message, bool isSegment, TxId?parentTx, int segmentIndex, int segmentTotalCount, Signatory?signatory, Action <IContext>?configure, bool includeRecord)
        {
            var transaction = new ConsensusSubmitMessageTransactionBody(topic, message, isSegment, parentTx, segmentIndex, segmentTotalCount);

            if (isSegment && segmentIndex == 1)
            {
                // Smelly Workaround due to necesity to embed the
                // same transaction ID in the middle of the message
                // as the envelope for the case of the first segment
                // of a segmented message.
                //
                // First We need to apply the confgure command, to
                // create the correct context
                await using var configuredClient = Clone(configure);
                var initialChunkTransactionId = configuredClient._context.GetOrCreateTransactionID();
                if (configuredClient._context.GatherSignatories(signatory).GetSchedule() is null)
                {
                    // The easy path, this is not a scheduled transaction.
                    transaction.ChunkInfo !.InitialTransactionID = initialChunkTransactionId;
                }
                else
                {
                    // Even more smell, we need to check to see if this is a
                    // scheduled transaction.  If this is, the initial chunk
                    // transaction should have the "scheduled" flag set.
                    var scheduledChunkTransactionId = new TransactionID(initialChunkTransactionId);
                    scheduledChunkTransactionId.Scheduled        = true;
                    transaction.ChunkInfo !.InitialTransactionID = scheduledChunkTransactionId;
                }
                // We use our configured client, however we need to override the
                // configuration with one additional configuration rule that will
                // peg the transaction to our pre-computed value.
                var result = await configuredClient.ExecuteTransactionAsync(transaction, ctx => ctx.Transaction = initialChunkTransactionId.AsTxId(), false, signatory).ConfigureAwait(false);

                if (includeRecord)
                {
                    // Note: we use the original context here, because we
                    // don't want to re-use the transaction ID that was pinned
                    // to the subContext, would cause the paying TX to fail as a duplicate.
                    result.Record = await configuredClient.GetTransactionRecordAsync(configuredClient._context, result.TransactionID).ConfigureAwait(false);
                }
                return(result);
            }
            else
            {
                return(await ExecuteTransactionAsync(transaction, configure, includeRecord, signatory).ConfigureAwait(false));
            }
        }
 /// <summary>
 /// Adds a signature to a pending transaction record. The Scheduled Transaction executes
 /// this signature completes the list of required signatures for execution.
 /// </summary>
 /// <param name="pending">
 /// The identifier (Address/Schedule ID) of the pending transaction to sign.
 /// </param>
 /// <param name="signatory">
 /// The signatory containing any additional private keys or callbacks
 /// to meet the key signing requirements for participants.
 /// </param>
 /// <param name="configure">
 /// Optional callback method providing an opportunity to modify
 /// the execution configuration for just this method call.
 /// It is executed prior to submitting the request to the network.
 /// </param>
 /// <returns>
 /// A Record for the transaction indicating success.
 /// </returns>
 /// <exception cref="ArgumentOutOfRangeException">If required arguments are missing.</exception>
 /// <exception cref="InvalidOperationException">If required context configuration is missing.</exception>
 /// <exception cref="PrecheckException">If the gateway node create rejected the request upon submission.</exception>
 /// <exception cref="ConsensusException">If the network was unable to come to consensus before the duration of the transaction expired.</exception>
 /// <exception cref="TransactionException">If the network rejected the create request as invalid or had missing data.</exception>
 public async Task <TransactionRecord> SignPendingTransactionWithRecordAsync(Address pending, Signatory?signatory = null, Action <IContext>?configure = null)
 {
     // Note: we may return a specialized record if the transaction executes and returns additional metadata.
     return((await ExecuteTransactionAsync(new ScheduleSignTransactionBody(pending), configure, true, signatory).ConfigureAwait(false)).ToRecord());
 }
Пример #9
0
        /// <summary>
        /// Internal implementation of the contract call method.
        /// </summary>
        private async Task <TResult> SubmitMessageImplementationAsync <TResult>(Address topic, ReadOnlyMemory <byte> message, Signatory?signatory, Action <IContext>?configure) where TResult : new()
        {
            topic   = RequireInputParameter.Topic(topic);
            message = RequireInputParameter.Message(message);
            await using var context = CreateChildContext(configure);
            var gateway         = RequireInContext.Gateway(context);
            var payer           = RequireInContext.Payer(context);
            var signatories     = Transactions.GatherSignatories(context, signatory);
            var transactionId   = Transactions.GetOrCreateTransactionID(context);
            var transactionBody = Transactions.CreateTransactionBody(context, transactionId);

            transactionBody.ConsensusSubmitMessage = new ConsensusSubmitMessageTransactionBody
            {
                TopicID = Protobuf.ToTopicID(topic),
                Message = ByteString.CopyFrom(message.Span)
            };
            var request = await Transactions.SignTransactionAsync(transactionBody, signatories);

            var precheck = await Transactions.ExecuteSignedRequestWithRetryAsync(context, request, getRequestMethod, getResponseCode);

            ValidateResult.PreCheck(transactionId, precheck);
            var receipt = await GetReceiptAsync(context, transactionId);

            if (receipt.Status != ResponseCodeEnum.Success)
            {
                throw new TransactionException($"Submit Message failed, status: {receipt.Status}", Protobuf.FromTransactionId(transactionId), (ResponseCode)receipt.Status);
            }
            var result = new TResult();

            if (result is SubmitMessageRecord rec)
            {
                var record = await GetTransactionRecordAsync(context, transactionId);

                Protobuf.FillRecordProperties(record, rec);
                rec.RunningHash    = receipt.TopicRunningHash?.ToByteArray();
                rec.SequenceNumber = receipt.TopicSequenceNumber;
            }
            else if (result is SubmitMessageReceipt rcpt)
            {
                Protobuf.FillReceiptProperties(transactionId, receipt, rcpt);
                rcpt.RunningHash    = receipt.TopicRunningHash?.ToByteArray();
                rcpt.SequenceNumber = receipt.TopicSequenceNumber;
            }
            return(result);
Пример #10
0
        /// <summary>
        /// Internal implementation of delete topic method.
        /// </summary>
        private async Task <TransactionReceipt> DeleteTopicImplementationAsync(Address topicToDelete, Signatory?signatory, Action <IContext>?configure)
        {
            topicToDelete           = RequireInputParameter.AddressToDelete(topicToDelete);
            await using var context = CreateChildContext(configure);
            RequireInContext.Gateway(context);
            var payer           = RequireInContext.Payer(context);
            var signatories     = Transactions.GatherSignatories(context, signatory);
            var transactionId   = Transactions.GetOrCreateTransactionID(context);
            var transactionBody = new TransactionBody(context, transactionId);

            transactionBody.ConsensusDeleteTopic = new ConsensusDeleteTopicTransactionBody
            {
                TopicID = new TopicID(topicToDelete)
            };
            var receipt = await transactionBody.SignAndExecuteWithRetryAsync(signatories, context);

            if (receipt.Status != ResponseCodeEnum.Success)
            {
                throw new TransactionException($"Unable to Delete Topic, status: {receipt.Status}", transactionId.ToTxId(), (ResponseCode)receipt.Status);
            }
            return(receipt.FillProperties(transactionId, new TransactionReceipt()));
        }
Пример #11
0
        /// <summary>
        /// Internal helper function implementing the file delete functionality.
        /// </summary>
        public async Task <TResult> DeleteFileImplementationAsync <TResult>(Address fileToDelete, Signatory?signatory, Action <IContext>?configure = null) where TResult : new()
        {
            fileToDelete            = RequireInputParameter.FileToDelete(fileToDelete);
            await using var context = CreateChildContext(configure);
            RequireInContext.Gateway(context);
            var payer           = RequireInContext.Payer(context);
            var signatories     = Transactions.GatherSignatories(context, signatory);
            var transactionId   = Transactions.GetOrCreateTransactionID(context);
            var transactionBody = Transactions.CreateTransactionBody(context, transactionId);

            transactionBody.FileDelete = new FileDeleteTransactionBody
            {
                FileID = Protobuf.ToFileId(fileToDelete)
            };
            var request = await Transactions.SignTransactionAsync(transactionBody, signatories);

            var precheck = await Transactions.ExecuteSignedRequestWithRetryAsync(context, request, getRequestMethod, getResponseCode);

            ValidateResult.PreCheck(transactionId, precheck);
            var receipt = await GetReceiptAsync(context, transactionId);

            if (receipt.Status != ResponseCodeEnum.Success)
            {
                throw new TransactionException($"Unable to delete file, status: {receipt.Status}", Protobuf.FromTransactionId(transactionId), (ResponseCode)receipt.Status);
            }
            var result = new TResult();

            if (result is TransactionRecord rec)
            {
                var record = await GetTransactionRecordAsync(context, transactionId);

                Protobuf.FillRecordProperties(record, rec);
            }
            else if (result is TransactionReceipt rcpt)
            {
                Protobuf.FillReceiptProperties(transactionId, receipt, rcpt);
            }
            return(result);
Пример #12
0
        /// <summary>
        /// Internal implementation for Single Transfer Crypto
        /// Returns either a receipt or record or throws
        /// an exception.
        /// </summary>
        private async Task <TResult> TransferTokenImplementationAsync <TResult>(Address token, Address fromAddress, Address toAddress, long amount, Signatory?signatory, Action <IContext>?configure) where TResult : new()
        {
            amount = RequireInputParameter.Amount(amount);
            var transactions = new List <TokenTransferList>(1);
            var xferList     = new TokenTransferList
            {
                Token = new TokenID(RequireInputParameter.Token(token))
            };

            xferList.Transfers.Add(new AccountAmount
            {
                AccountID = new AccountID(RequireInputParameter.FromAddress(fromAddress)),
                Amount    = -amount
            });
            xferList.Transfers.Add(new AccountAmount
            {
                AccountID = new AccountID(RequireInputParameter.ToAddress(toAddress)),
                Amount    = amount
            });
            transactions.Add(xferList);
            return(await TransferImplementationAsync <TResult>(null, transactions, signatory, configure));
        }
Пример #13
0
        /// <summary>
        /// Internal implementation of delete topic method.
        /// </summary>
        private async Task <TransactionReceipt> DeleteTopicImplementationAsync(Address topicToDelete, Signatory?signatory, Action <IContext>?configure)
        {
            topicToDelete           = RequireInputParameter.AddressToDelete(topicToDelete);
            await using var context = CreateChildContext(configure);
            RequireInContext.Gateway(context);
            var payer           = RequireInContext.Payer(context);
            var signatories     = Transactions.GatherSignatories(context, signatory);
            var transactionId   = Transactions.GetOrCreateTransactionID(context);
            var transactionBody = Transactions.CreateTransactionBody(context, transactionId);

            transactionBody.ConsensusDeleteTopic = new ConsensusDeleteTopicTransactionBody
            {
                TopicID = Protobuf.ToTopicID(topicToDelete)
            };
            var request = await Transactions.SignTransactionAsync(transactionBody, signatories);

            var precheck = await Transactions.ExecuteSignedRequestWithRetryAsync(context, request, getRequestMethod, getResponseCode);

            ValidateResult.PreCheck(transactionId, precheck);
            var receipt = await GetReceiptAsync(context, transactionId);

            if (receipt.Status != ResponseCodeEnum.Success)
            {
                throw new TransactionException($"Unable to Delete Topic, status: {receipt.Status}", Protobuf.FromTransactionId(transactionId), (ResponseCode)receipt.Status);
            }
            var result = new TransactionReceipt();

            Protobuf.FillReceiptProperties(transactionId, receipt, result);
            return(result);
Пример #14
0
        /// <summary>
        /// Internal implementation for Multi Account Transfer Crypto and Tokens.
        /// Returns either a receipt or record or throws an exception.
        /// </summary>
        private async Task <TResult> TransferImplementationAsync <TResult>(TransferList?cryptoTransfers, IEnumerable <TokenTransferList>?tokenTransfers, Signatory?signatory, Action <IContext>?configure) where TResult : new()
        {
            await using var context = CreateChildContext(configure);
            RequireInContext.Gateway(context);
            var signatories     = Transactions.GatherSignatories(context, signatory);
            var transactionId   = Transactions.GetOrCreateTransactionID(context);
            var transactionBody = new TransactionBody(context, transactionId);

            transactionBody.CryptoTransfer = new CryptoTransferTransactionBody();
            if (cryptoTransfers != null)
            {
                transactionBody.CryptoTransfer.Transfers = cryptoTransfers;
            }
            if (tokenTransfers != null)
            {
                transactionBody.CryptoTransfer.TokenTransfers.AddRange(tokenTransfers);
            }
            var receipt = await transactionBody.SignAndExecuteWithRetryAsync(signatories, context);

            if (receipt.Status != ResponseCodeEnum.Success)
            {
                throw new TransactionException($"Unable to execute transfers, status: {receipt.Status}", transactionId.ToTxId(), (ResponseCode)receipt.Status);
            }
            var result = new TResult();

            if (result is TransactionRecord rec)
            {
                var record = await GetTransactionRecordAsync(context, transactionId);

                record.FillProperties(rec);
            }
            else if (result is TransactionReceipt rcpt)
            {
                receipt.FillProperties(transactionId, rcpt);
            }
            return(result);
        }
Пример #15
0
        /// <summary>
        /// Internal implementation for Transfer Crypto.
        /// Returns either a receipt or record or throws
        /// an exception.
        /// </summary>
        private async Task <TResult> TransferImplementationAsync <TResult>(Address fromAddress, Address toAddress, long amount, Signatory?signatory, Action <IContext>?configure) where TResult : new()
        {
            fromAddress = RequireInputParameter.FromAddress(fromAddress);
            toAddress   = RequireInputParameter.ToAddress(toAddress);
            amount      = RequireInputParameter.Amount(amount);
            var cryptoTransfers = RequireInputParameter.CryptoTransferList(new[] { KeyValuePair.Create(fromAddress, -amount), KeyValuePair.Create(toAddress, amount) });

            return(await TransferImplementationAsync <TResult>(cryptoTransfers, null, signatory, configure));
        }
Пример #16
0
        /// <summary>
        /// Internal implementation of the submit message call.
        /// </summary>
        private async Task <TResult> SubmitMessageImplementationAsync <TResult>(Address topic, ReadOnlyMemory <byte> message, bool isSegment, TxId?parentTx, int segmentIndex, int segmentTotalCount, Signatory?signatory, Action <IContext>?configure) where TResult : new()
        {
            topic   = RequireInputParameter.Topic(topic);
            message = RequireInputParameter.Message(message);
            await using var context = CreateChildContext(configure);
            var gateway         = RequireInContext.Gateway(context);
            var payer           = RequireInContext.Payer(context);
            var signatories     = Transactions.GatherSignatories(context, signatory);
            var transactionId   = Transactions.GetOrCreateTransactionID(context);
            var transactionBody = new TransactionBody(context, transactionId);

            transactionBody.ConsensusSubmitMessage = new ConsensusSubmitMessageTransactionBody
            {
                TopicID   = new TopicID(topic),
                Message   = ByteString.CopyFrom(message.Span),
                ChunkInfo = isSegment ? createChunkInfo(transactionId, parentTx, segmentIndex, segmentTotalCount) : null
            };
            var receipt = await transactionBody.SignAndExecuteWithRetryAsync(signatories, context);

            if (receipt.Status != ResponseCodeEnum.Success)
            {
                throw new TransactionException($"Submit Message failed, status: {receipt.Status}", transactionId.ToTxId(), (ResponseCode)receipt.Status);
            }
            var result = new TResult();

            if (result is SubmitMessageRecord rec)
            {
                var record = await GetTransactionRecordAsync(context, transactionId);

                record.FillProperties(rec);
            }
            else if (result is SubmitMessageReceipt rcpt)
            {
                receipt.FillProperties(transactionId, rcpt);
            }
            return(result);