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 }