github.com/hashgraph/hedera-sdk-go/v2@v2.48.0/topic_message_submit_transaction.go (about)

     1  package hedera
     2  
     3  /*-
     4   *
     5   * Hedera Go SDK
     6   *
     7   * Copyright (C) 2020 - 2024 Hedera Hashgraph, LLC
     8   *
     9   * Licensed under the Apache License, Version 2.0 (the "License");
    10   * you may not use this file except in compliance with the License.
    11   * You may obtain a copy of the License at
    12   *
    13   *      http://www.apache.org/licenses/LICENSE-2.0
    14   *
    15   * Unless required by applicable law or agreed to in writing, software
    16   * distributed under the License is distributed on an "AS IS" BASIS,
    17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    18   * See the License for the specific language governing permissions and
    19   * limitations under the License.
    20   *
    21   */
    22  
    23  import (
    24  	"time"
    25  
    26  	"github.com/pkg/errors"
    27  
    28  	"github.com/hashgraph/hedera-protobufs-go/services"
    29  	protobuf "google.golang.org/protobuf/proto"
    30  )
    31  
    32  const chunkSize = 1024
    33  
    34  // TopicMessageSubmitTransaction
    35  // Sends a message/messages to the Topic ID
    36  type TopicMessageSubmitTransaction struct {
    37  	Transaction
    38  	maxChunks uint64
    39  	message   []byte
    40  	topicID   *TopicID
    41  }
    42  
    43  // NewTopicMessageSubmitTransaction createsTopicMessageSubmitTransaction which
    44  // sends a message/messages to the Topic ID
    45  func NewTopicMessageSubmitTransaction() *TopicMessageSubmitTransaction {
    46  	tx := TopicMessageSubmitTransaction{
    47  		Transaction: _NewTransaction(),
    48  		maxChunks:   20,
    49  		message:     make([]byte, 0),
    50  	}
    51  
    52  	tx._SetDefaultMaxTransactionFee(NewHbar(2))
    53  
    54  	return &tx
    55  }
    56  
    57  func _TopicMessageSubmitTransactionFromProtobuf(tx Transaction, pb *services.TransactionBody) *TopicMessageSubmitTransaction {
    58  	return &TopicMessageSubmitTransaction{
    59  		Transaction: tx,
    60  		maxChunks:   20,
    61  		message:     pb.GetConsensusSubmitMessage().GetMessage(),
    62  		topicID:     _TopicIDFromProtobuf(pb.GetConsensusSubmitMessage().GetTopicID()),
    63  	}
    64  }
    65  
    66  // SetTopicID Sets the topic to submit message to.
    67  func (tx *TopicMessageSubmitTransaction) SetTopicID(topicID TopicID) *TopicMessageSubmitTransaction {
    68  	tx._RequireNotFrozen()
    69  	tx.topicID = &topicID
    70  	return tx
    71  }
    72  
    73  // GetTopicID returns the TopicID for this TopicMessageSubmitTransaction
    74  func (tx *TopicMessageSubmitTransaction) GetTopicID() TopicID {
    75  	if tx.topicID == nil {
    76  		return TopicID{}
    77  	}
    78  
    79  	return *tx.topicID
    80  }
    81  
    82  // SetMessage Sets the message to be submitted.
    83  // The message should be a byte array or a string
    84  // If other types are provided, it will not set the value
    85  func (tx *TopicMessageSubmitTransaction) SetMessage(message interface{}) *TopicMessageSubmitTransaction {
    86  	tx._RequireNotFrozen()
    87  	switch m := message.(type) {
    88  	case string:
    89  		tx.message = []byte(m)
    90  	case []byte:
    91  		tx.message = m
    92  	}
    93  	return tx
    94  }
    95  
    96  func (tx *TopicMessageSubmitTransaction) GetMessage() []byte {
    97  	return tx.message
    98  }
    99  
   100  // SetMaxChunks sets the maximum amount of chunks to use to send the message
   101  func (tx *TopicMessageSubmitTransaction) SetMaxChunks(maxChunks uint64) *TopicMessageSubmitTransaction {
   102  	tx._RequireNotFrozen()
   103  	tx.maxChunks = maxChunks
   104  	return tx
   105  }
   106  
   107  // GetMaxChunks returns the maximum amount of chunks to use to send the message
   108  func (tx *TopicMessageSubmitTransaction) GetMaxChunks() uint64 {
   109  	return tx.maxChunks
   110  }
   111  
   112  // ---- Required Interfaces ---- //
   113  
   114  // Sign uses the provided privateKey to sign the transaction.
   115  func (tx *TopicMessageSubmitTransaction) Sign(privateKey PrivateKey) *TopicMessageSubmitTransaction {
   116  	tx.Transaction.Sign(privateKey)
   117  	return tx
   118  }
   119  
   120  // SignWithOperator signs the transaction with client's operator privateKey.
   121  func (tx *TopicMessageSubmitTransaction) SignWithOperator(client *Client) (*TopicMessageSubmitTransaction, error) {
   122  	_, err := tx.Transaction.signWithOperator(client, tx)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	return tx, nil
   127  }
   128  
   129  // SignWith executes the TransactionSigner and adds the resulting signature data to the Transaction's signature map
   130  // with the publicKey as the map key.
   131  func (tx *TopicMessageSubmitTransaction) SignWith(
   132  	publicKey PublicKey,
   133  	signer TransactionSigner,
   134  ) *TopicMessageSubmitTransaction {
   135  	tx.Transaction.SignWith(publicKey, signer)
   136  	return tx
   137  }
   138  
   139  // AddSignature adds a signature to the transaction.
   140  func (tx *TopicMessageSubmitTransaction) AddSignature(publicKey PublicKey, signature []byte) *TopicMessageSubmitTransaction {
   141  	tx.Transaction.AddSignature(publicKey, signature)
   142  	return tx
   143  }
   144  
   145  // When execution is attempted, a single attempt will timeout when this deadline is reached. (The SDK may subsequently retry the execution.)
   146  func (tx *TopicMessageSubmitTransaction) SetGrpcDeadline(deadline *time.Duration) *TopicMessageSubmitTransaction {
   147  	tx.Transaction.SetGrpcDeadline(deadline)
   148  	return tx
   149  }
   150  
   151  func (tx *TopicMessageSubmitTransaction) Freeze() (*TopicMessageSubmitTransaction, error) {
   152  	return tx.FreezeWith(nil)
   153  }
   154  
   155  func (tx *TopicMessageSubmitTransaction) FreezeWith(client *Client) (*TopicMessageSubmitTransaction, error) {
   156  	var err error
   157  	if tx.nodeAccountIDs._Length() == 0 {
   158  		if client == nil {
   159  			return tx, errNoClientOrTransactionIDOrNodeId
   160  		}
   161  
   162  		tx.SetNodeAccountIDs(client.network._GetNodeAccountIDsForExecute())
   163  	}
   164  
   165  	tx._InitFee(client)
   166  	err = tx.validateNetworkOnIDs(client)
   167  	if err != nil {
   168  		return &TopicMessageSubmitTransaction{}, err
   169  	}
   170  	if err := tx._InitTransactionID(client); err != nil {
   171  		return tx, err
   172  	}
   173  	body := tx.build()
   174  
   175  	chunks := uint64((len(tx.message) + (chunkSize - 1)) / chunkSize)
   176  	if chunks > tx.maxChunks {
   177  		return tx, ErrMaxChunksExceeded{
   178  			Chunks:    chunks,
   179  			MaxChunks: tx.maxChunks,
   180  		}
   181  	}
   182  
   183  	initialTransactionID := tx.transactionIDs._GetCurrent().(TransactionID)
   184  	nextTransactionID, _ := TransactionIdFromString(initialTransactionID.String())
   185  
   186  	tx.transactionIDs = _NewLockableSlice()
   187  	tx.transactions = _NewLockableSlice()
   188  	tx.signedTransactions = _NewLockableSlice()
   189  	if b, ok := body.Data.(*services.TransactionBody_ConsensusSubmitMessage); ok {
   190  		for i := 0; uint64(i) < chunks; i++ {
   191  			start := i * chunkSize
   192  			end := start + chunkSize
   193  
   194  			if end > len(tx.message) {
   195  				end = len(tx.message)
   196  			}
   197  
   198  			tx.transactionIDs._Push(_TransactionIDFromProtobuf(nextTransactionID._ToProtobuf()))
   199  
   200  			b.ConsensusSubmitMessage.Message = tx.message[start:end]
   201  			b.ConsensusSubmitMessage.ChunkInfo = &services.ConsensusMessageChunkInfo{
   202  				InitialTransactionID: initialTransactionID._ToProtobuf(),
   203  				Total:                int32(chunks),
   204  				Number:               int32(i) + 1,
   205  			}
   206  
   207  			body.TransactionID = nextTransactionID._ToProtobuf()
   208  			body.Data = &services.TransactionBody_ConsensusSubmitMessage{
   209  				ConsensusSubmitMessage: b.ConsensusSubmitMessage,
   210  			}
   211  
   212  			for _, nodeAccountID := range tx.nodeAccountIDs.slice {
   213  				body.NodeAccountID = nodeAccountID.(AccountID)._ToProtobuf()
   214  
   215  				bodyBytes, err := protobuf.Marshal(body)
   216  				if err != nil {
   217  					return tx, errors.Wrap(err, "error serializing tx body for topic submission")
   218  				}
   219  
   220  				tx.signedTransactions._Push(&services.SignedTransaction{
   221  					BodyBytes: bodyBytes,
   222  					SigMap:    &services.SignatureMap{},
   223  				})
   224  			}
   225  
   226  			validStart := *nextTransactionID.ValidStart
   227  
   228  			*nextTransactionID.ValidStart = validStart.Add(1 * time.Nanosecond)
   229  		}
   230  	}
   231  
   232  	return tx, nil
   233  }
   234  
   235  // SetMaxTransactionFee sets the max transaction fee for this TopicMessageSubmitTransaction.
   236  func (tx *TopicMessageSubmitTransaction) SetMaxTransactionFee(fee Hbar) *TopicMessageSubmitTransaction {
   237  	tx.Transaction.SetMaxTransactionFee(fee)
   238  	return tx
   239  }
   240  
   241  // SetRegenerateTransactionID sets if transaction IDs should be regenerated when `TRANSACTION_EXPIRED` is received
   242  func (tx *TopicMessageSubmitTransaction) SetRegenerateTransactionID(regenerateTransactionID bool) *TopicMessageSubmitTransaction {
   243  	tx.Transaction.SetRegenerateTransactionID(regenerateTransactionID)
   244  	return tx
   245  }
   246  
   247  // SetTransactionMemo sets the memo for this TopicMessageSubmitTransaction.
   248  func (tx *TopicMessageSubmitTransaction) SetTransactionMemo(memo string) *TopicMessageSubmitTransaction {
   249  	tx.Transaction.SetTransactionMemo(memo)
   250  	return tx
   251  }
   252  
   253  // SetTransactionValidDuration sets the valid duration for this TopicMessageSubmitTransaction.
   254  func (tx *TopicMessageSubmitTransaction) SetTransactionValidDuration(duration time.Duration) *TopicMessageSubmitTransaction {
   255  	tx.Transaction.SetTransactionValidDuration(duration)
   256  	return tx
   257  }
   258  
   259  // ToBytes serialise the tx to bytes, no matter if it is signed (locked), or not
   260  func (tx *TopicMessageSubmitTransaction) ToBytes() ([]byte, error) {
   261  	bytes, err := tx.Transaction.toBytes(tx)
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  	return bytes, nil
   266  }
   267  
   268  // SetTransactionID sets the TransactionID for this TopicMessageSubmitTransaction.
   269  func (tx *TopicMessageSubmitTransaction) SetTransactionID(transactionID TransactionID) *TopicMessageSubmitTransaction {
   270  	tx.Transaction.SetTransactionID(transactionID)
   271  	return tx
   272  }
   273  
   274  // SetNodeAccountIDs sets the _Node AccountID for this TopicMessageSubmitTransaction.
   275  func (tx *TopicMessageSubmitTransaction) SetNodeAccountIDs(nodeID []AccountID) *TopicMessageSubmitTransaction {
   276  	tx.Transaction.SetNodeAccountIDs(nodeID)
   277  	return tx
   278  }
   279  
   280  // SetMaxRetry sets the max number of errors before execution will fail.
   281  func (tx *TopicMessageSubmitTransaction) SetMaxRetry(count int) *TopicMessageSubmitTransaction {
   282  	tx.Transaction.SetMaxRetry(count)
   283  	return tx
   284  }
   285  
   286  // SetMaxBackoff The maximum amount of time to wait between retries.
   287  // Every retry attempt will increase the wait time exponentially until it reaches this time.
   288  func (tx *TopicMessageSubmitTransaction) SetMaxBackoff(max time.Duration) *TopicMessageSubmitTransaction {
   289  	tx.Transaction.SetMaxBackoff(max)
   290  	return tx
   291  }
   292  
   293  // SetMinBackoff sets the minimum amount of time to wait between retries.
   294  func (tx *TopicMessageSubmitTransaction) SetMinBackoff(min time.Duration) *TopicMessageSubmitTransaction {
   295  	tx.Transaction.SetMinBackoff(min)
   296  	return tx
   297  }
   298  
   299  func (tx *TopicMessageSubmitTransaction) SetLogLevel(level LogLevel) *TopicMessageSubmitTransaction {
   300  	tx.Transaction.SetLogLevel(level)
   301  	return tx
   302  }
   303  
   304  func (tx *TopicMessageSubmitTransaction) Schedule() (*ScheduleCreateTransaction, error) {
   305  	chunks := uint64((len(tx.message) + (chunkSize - 1)) / chunkSize)
   306  	if chunks > 1 {
   307  		return &ScheduleCreateTransaction{}, ErrMaxChunksExceeded{
   308  			Chunks:    chunks,
   309  			MaxChunks: 1,
   310  		}
   311  	}
   312  
   313  	return tx.Transaction.schedule(tx)
   314  }
   315  
   316  // ----------- Overridden functions ----------------
   317  
   318  func (tx *TopicMessageSubmitTransaction) getName() string {
   319  	return "TopicMessageSubmitTransaction"
   320  }
   321  func (tx *TopicMessageSubmitTransaction) validateNetworkOnIDs(client *Client) error {
   322  	if client == nil || !client.autoValidateChecksums {
   323  		return nil
   324  	}
   325  
   326  	if tx.topicID != nil {
   327  		if err := tx.topicID.ValidateChecksum(client); err != nil {
   328  			return err
   329  		}
   330  	}
   331  
   332  	return nil
   333  }
   334  
   335  func (tx *TopicMessageSubmitTransaction) build() *services.TransactionBody {
   336  	return &services.TransactionBody{
   337  		TransactionFee:           tx.transactionFee,
   338  		Memo:                     tx.Transaction.memo,
   339  		TransactionValidDuration: _DurationToProtobuf(tx.GetTransactionValidDuration()),
   340  		TransactionID:            tx.transactionID._ToProtobuf(),
   341  		Data: &services.TransactionBody_ConsensusSubmitMessage{
   342  			ConsensusSubmitMessage: tx.buildProtoBody(),
   343  		},
   344  	}
   345  }
   346  
   347  func (tx *TopicMessageSubmitTransaction) buildScheduled() (*services.SchedulableTransactionBody, error) {
   348  	return &services.SchedulableTransactionBody{
   349  		TransactionFee: tx.transactionFee,
   350  		Memo:           tx.Transaction.memo,
   351  		Data: &services.SchedulableTransactionBody_ConsensusSubmitMessage{
   352  			ConsensusSubmitMessage: tx.buildProtoBody(),
   353  		},
   354  	}, nil
   355  }
   356  
   357  func (tx *TopicMessageSubmitTransaction) buildProtoBody() *services.ConsensusSubmitMessageTransactionBody {
   358  	body := &services.ConsensusSubmitMessageTransactionBody{
   359  		Message: tx.message,
   360  	}
   361  
   362  	if tx.topicID != nil {
   363  		body.TopicID = tx.topicID._ToProtobuf()
   364  	}
   365  
   366  	return body
   367  }
   368  
   369  func (tx *TopicMessageSubmitTransaction) getMethod(channel *_Channel) _Method {
   370  	return _Method{
   371  		transaction: channel._GetTopic().SubmitMessage,
   372  	}
   373  }
   374  
   375  // Execute executes the Query with the provided client
   376  func (tx *TopicMessageSubmitTransaction) Execute(
   377  	client *Client,
   378  ) (TransactionResponse, error) {
   379  	if client == nil {
   380  		return TransactionResponse{}, errNoClientProvided
   381  	}
   382  
   383  	if tx.freezeError != nil {
   384  		return TransactionResponse{}, tx.freezeError
   385  	}
   386  
   387  	list, err := tx.ExecuteAll(client)
   388  
   389  	if err != nil {
   390  		return TransactionResponse{}, err
   391  	}
   392  
   393  	if len(list) > 0 {
   394  		return list[0], nil
   395  	}
   396  
   397  	return TransactionResponse{}, errNoTransactions
   398  }
   399  
   400  // ExecuteAll executes the all the Transactions with the provided client
   401  func (tx *TopicMessageSubmitTransaction) ExecuteAll(
   402  	client *Client,
   403  ) ([]TransactionResponse, error) {
   404  	if !tx.IsFrozen() {
   405  		_, err := tx.FreezeWith(client)
   406  		if err != nil {
   407  			return []TransactionResponse{}, err
   408  		}
   409  	}
   410  	transactionID := tx.GetTransactionID()
   411  	accountID := AccountID{}
   412  	if transactionID.AccountID != nil {
   413  		accountID = *transactionID.AccountID
   414  	}
   415  
   416  	if !client.GetOperatorAccountID()._IsZero() && client.GetOperatorAccountID()._Equals(accountID) {
   417  		tx.SignWith(
   418  			client.GetOperatorPublicKey(),
   419  			client.operator.signer,
   420  		)
   421  	}
   422  
   423  	size := tx.signedTransactions._Length() / tx.nodeAccountIDs._Length()
   424  	list := make([]TransactionResponse, size)
   425  
   426  	for i := 0; i < size; i++ {
   427  		resp, err := _Execute(client, tx)
   428  
   429  		if err != nil {
   430  			return []TransactionResponse{}, err
   431  		}
   432  
   433  		list[i] = resp.(TransactionResponse)
   434  	}
   435  
   436  	return list, nil
   437  }
   438  
   439  func (tx *TopicMessageSubmitTransaction) _ConstructScheduleProtobuf() (*services.SchedulableTransactionBody, error) {
   440  	return tx.buildScheduled()
   441  }