hashgraph / hedera-sdk-swift

Hedera™ SDK for Swift
Apache License 2.0
10 stars 9 forks source link

TopicMessageSubmit transaction results in INVALID_CHUNK_TRANSACTION_ID #322

Closed Diegoescalonaro closed 4 months ago

Diegoescalonaro commented 6 months ago

Description

When submitting a message to a topic using a custom transactionId and a message large enough to be split into several chunks/transactions, the operation fails with INVALID_CHUNK_TRANSACTION_ID and only the first chunk transaction is successfully registered to the topic.

The problem seems to occur because the second and following chunks do not have the same transactionId as the first one. Only the first chunk transaction has the transactionId set in the transaction creation.

This has been tested with the hedera-sdk-js and it works well as all chunk transactions are executed with the same transactionId. In the TopicMessageSubmitTransaction code there seems to be some logic to configured transactionId for all chunk transactions.

Steps to reproduce

  1. Set a user account and client
  2. Set a payer account and client
  3. Create the TopicMessageSubmitTransaction instance with:
    1. TopicId: previously created
    2. Message: 12 (2 bytes)
    3. Chunk size: 1 (1 byte)
    4. TransactionId: generated with payer account
  4. Freeze and sign transaction with the user account
  5. Sign and execute transaction with the payer account
  6. Get the transaction response and receipt

Notes: The message has been set to "12" and the chunk size to 1 byte to cause the message to be split into 2 chunks. So the first chunk transaction would contain message "1" and the second chunk transaction would contain message "2".

Code snippet for testing:

//  main.swift

import Foundation
import Hedera

let USER_ACCOUNT_ID = ""
let USER_PRIVATE_KEY = ""

let PAYER_ACCOUNT_ID = ""
let PAYER_PRIVATE_KEY = ""

public func submitMessage() async throws {
    do{
        // Set user account
        let userAccountId = try AccountId.fromString(USER_ACCOUNT_ID);
        let userPrivateKey = try PrivateKey.fromStringDer(USER_PRIVATE_KEY);
        let userClient = Client.forTestnet().setOperator(userAccountId, userPrivateKey);

        // Set payer account
        let payerAccountId = try AccountId.fromString(PAYER_ACCOUNT_ID);
        let payerPrivateKey = try PrivateKey.fromStringDer(PAYER_PRIVATE_KEY);
        let payerClient = Client.forTestnet().setOperator(payerAccountId, payerPrivateKey);

        // Transaction attributes
        let topicId = "0.0.7130942"; // Previously created HCS topic
        let message = "12"; // 2 bytes message
        let chunkSize = 1; // 1 byte chunk size
        let transactionId = TransactionId.generateFrom(payerAccountId); // custom transactionId

        // Transaction creation
        let transaction = TopicMessageSubmitTransaction()
            .topicId(try TopicId.fromString(topicId))
            .message(message.data(using: .utf8)!)
            .chunkSize(chunkSize)
            .transactionId(transactionId);

        // Transaction signature and execution
        let frozenTx = try transaction.freezeWith(userClient);
        let signedTx = try frozenTx.signWithOperator(userClient);
        let doubleSignedTx = try signedTx.signWithOperator(payerClient);
        let txResponses = try await doubleSignedTx.executeAll(payerClient)

        // Transaction response
        let firstTransactionId = txResponses[0].transactionId.toString();
        let firstTransactionReceipt = try await txResponses[0].getReceipt(payerClient);
        print(firstTransactionId, firstTransactionReceipt.status.description );

        let secondTransactionId = txResponses[1].transactionId.toString();
        let secondTransactionReceipt = try await txResponses[1].getReceipt(payerClient)
        print(secondTransactionId, secondTransactionReceipt.status.description );
    } catch let error {
        print("Error: \(error)")
    }
}

try await submitMessage()

Additional context

Results using hedera-sdk-swift:

Results using hedera-sdk-js:

Hedera network

testnet

Version

v0.26.0

Operating system

macOS

SimiHunjan commented 6 months ago

@Diegoescalonaro the issue was reproduced by @RickyLB and a fix will be issued

RickyLB commented 6 months ago

@Diegoescalonaro The transaction should be frozen with the payerClient, and not the userClient. Once the transactionId is set for TopicMessageSubmitTransaction, the accountId must match that in the set transactionId. Hence, there cannot be 2 payers.

Diegoescalonaro commented 6 months ago

Hey @RickyLB, thanks for the comment 😃

I've tried what you mention by modifying the line below in the code snippet and it seems to work well. By freezing the transaction with the payerClient, the first and second chunk transactions are successfully executed in the Network.

let frozenTx = try transaction.freezeWith(payerClient);

However, this model does not work for most of the use cases where the user firstly creates and signs the transaction in the client application (which requires the transaction to be frozen with the userClient), and then payer account executes the signed transaction in the backend.

In my opinion, even if the transaction is frozen by the user or the payer, all transaction chunks should contain the same transactionId that has been specified in the moment of creation, thus designating the payer account for the the first and the following transaction chunks. That is how other SDKs like the hedera-sdk-js works.

I also tested it by utilizing the Swift SDK for creating, freezing, signing and serializing the transaction in the client, and utilizing the JavaScript SDK only for unserializing and executing the transaction in the backend server, but it also throws the same error: INVALID_CHUNK_TRANSACTION_ID. This makes me think that the problem should be related to the time of creation or freezing...