github.com/hashgraph/hedera-sdk-go/v2@v2.48.0/file_append_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/hashgraph/hedera-protobufs-go/services" 27 "github.com/pkg/errors" 28 protobuf "google.golang.org/protobuf/proto" 29 ) 30 31 // FileAppendTransaction appends the given contents to the end of the file. If a file is too big to create with a single 32 // FileCreateTransaction, then it can be created with the first part of its contents, and then appended multiple times 33 // to create the entire file. 34 type FileAppendTransaction struct { 35 Transaction 36 maxChunks uint64 37 contents []byte 38 fileID *FileID 39 chunkSize int 40 } 41 42 // NewFileAppendTransaction creates a FileAppendTransaction transaction which can be 43 // used to construct and execute a File Append Transaction. 44 func NewFileAppendTransaction() *FileAppendTransaction { 45 tx := FileAppendTransaction{ 46 Transaction: _NewTransaction(), 47 maxChunks: 20, 48 contents: make([]byte, 0), 49 chunkSize: 2048, 50 } 51 tx._SetDefaultMaxTransactionFee(NewHbar(5)) 52 53 return &tx 54 } 55 56 func _FileAppendTransactionFromProtobuf(tx Transaction, pb *services.TransactionBody) *FileAppendTransaction { 57 return &FileAppendTransaction{ 58 Transaction: tx, 59 maxChunks: 20, 60 contents: pb.GetFileAppend().GetContents(), 61 chunkSize: 2048, 62 fileID: _FileIDFromProtobuf(pb.GetFileAppend().GetFileID()), 63 } 64 } 65 66 // SetFileID sets the FileID of the file to which the bytes are appended to. 67 func (tx *FileAppendTransaction) SetFileID(fileID FileID) *FileAppendTransaction { 68 tx._RequireNotFrozen() 69 tx.fileID = &fileID 70 return tx 71 } 72 73 // GetFileID returns the FileID of the file to which the bytes are appended to. 74 func (tx *FileAppendTransaction) GetFileID() FileID { 75 if tx.fileID == nil { 76 return FileID{} 77 } 78 79 return *tx.fileID 80 } 81 82 // SetMaxChunkSize Sets maximum amount of chunks append function can create 83 func (tx *FileAppendTransaction) SetMaxChunkSize(size int) *FileAppendTransaction { 84 tx._RequireNotFrozen() 85 tx.chunkSize = size 86 return tx 87 } 88 89 // GetMaxChunkSize returns maximum amount of chunks append function can create 90 func (tx *FileAppendTransaction) GetMaxChunkSize() int { 91 return tx.chunkSize 92 } 93 94 // SetMaxChunks sets the maximum number of chunks that can be created 95 func (tx *FileAppendTransaction) SetMaxChunks(size uint64) *FileAppendTransaction { 96 tx._RequireNotFrozen() 97 tx.maxChunks = size 98 return tx 99 } 100 101 // GetMaxChunks returns the maximum number of chunks that can be created 102 func (tx *FileAppendTransaction) GetMaxChunks() uint64 { 103 return tx.maxChunks 104 } 105 106 // SetContents sets the bytes to append to the contents of the file. 107 func (tx *FileAppendTransaction) SetContents(contents []byte) *FileAppendTransaction { 108 tx._RequireNotFrozen() 109 tx.contents = contents 110 return tx 111 } 112 113 // GetContents returns the bytes to append to the contents of the file. 114 func (tx *FileAppendTransaction) GetContents() []byte { 115 return tx.contents 116 } 117 118 // ---- Required Interfaces ---- // 119 120 // Sign uses the provided privateKey to sign the transaction. 121 func (tx *FileAppendTransaction) Sign( 122 privateKey PrivateKey, 123 ) *FileAppendTransaction { 124 tx.Transaction.Sign(privateKey) 125 return tx 126 } 127 128 // SignWithOperator signs the transaction with client's operator privateKey. 129 func (tx *FileAppendTransaction) SignWithOperator( 130 client *Client, 131 ) (*FileAppendTransaction, error) { 132 // If the transaction is not signed by the _Operator, we need 133 // to sign the transaction with the _Operator 134 _, err := tx.Transaction.signWithOperator(client, tx) 135 if err != nil { 136 return nil, err 137 } 138 return tx, nil 139 } 140 141 // SignWith executes the TransactionSigner and adds the resulting signature data to the Transaction's signature map 142 // with the publicKey as the map key. 143 func (tx *FileAppendTransaction) SignWith( 144 publicKey PublicKey, 145 signer TransactionSigner, 146 ) *FileAppendTransaction { 147 tx.Transaction.SignWith(publicKey, signer) 148 return tx 149 } 150 151 // AddSignature adds a signature to the transaction. 152 func (tx *FileAppendTransaction) AddSignature(publicKey PublicKey, signature []byte) *FileAppendTransaction { 153 tx.Transaction.AddSignature(publicKey, signature) 154 return tx 155 } 156 157 // When execution is attempted, a single attempt will timeout when tx deadline is reached. (The SDK may subsequently retry the execution.) 158 func (tx *FileAppendTransaction) SetGrpcDeadline(deadline *time.Duration) *FileAppendTransaction { 159 tx.Transaction.SetGrpcDeadline(deadline) 160 return tx 161 } 162 163 func (tx *FileAppendTransaction) Freeze() (*FileAppendTransaction, error) { 164 return tx.FreezeWith(nil) 165 } 166 167 func (tx *FileAppendTransaction) FreezeWith(client *Client) (*FileAppendTransaction, error) { 168 if tx.IsFrozen() { 169 return tx, nil 170 } 171 172 if tx.nodeAccountIDs._Length() == 0 { 173 if client == nil { 174 return tx, errNoClientOrTransactionIDOrNodeId 175 } 176 177 tx.SetNodeAccountIDs(client.network._GetNodeAccountIDsForExecute()) 178 } 179 180 tx._InitFee(client) 181 err := tx.validateNetworkOnIDs(client) 182 if err != nil { 183 return &FileAppendTransaction{}, err 184 } 185 if err := tx._InitTransactionID(client); err != nil { 186 return tx, err 187 } 188 body := tx.build() 189 190 chunks := uint64((len(tx.contents) + (tx.chunkSize - 1)) / tx.chunkSize) 191 if chunks > tx.maxChunks { 192 return tx, ErrMaxChunksExceeded{ 193 Chunks: chunks, 194 MaxChunks: tx.maxChunks, 195 } 196 } 197 198 nextTransactionID := tx.transactionIDs._GetCurrent().(TransactionID) 199 200 tx.transactionIDs = _NewLockableSlice() 201 tx.transactions = _NewLockableSlice() 202 tx.signedTransactions = _NewLockableSlice() 203 204 if b, ok := body.Data.(*services.TransactionBody_FileAppend); ok { 205 for i := 0; uint64(i) < chunks; i++ { 206 start := i * tx.chunkSize 207 end := start + tx.chunkSize 208 209 if end > len(tx.contents) { 210 end = len(tx.contents) 211 } 212 213 tx.transactionIDs._Push(_TransactionIDFromProtobuf(nextTransactionID._ToProtobuf())) 214 if err != nil { 215 panic(err) 216 } 217 b.FileAppend.Contents = tx.contents[start:end] 218 219 body.TransactionID = nextTransactionID._ToProtobuf() 220 body.Data = &services.TransactionBody_FileAppend{ 221 FileAppend: b.FileAppend, 222 } 223 224 for _, nodeAccountID := range tx.GetNodeAccountIDs() { 225 body.NodeAccountID = nodeAccountID._ToProtobuf() 226 227 bodyBytes, err := protobuf.Marshal(body) 228 if err != nil { 229 return tx, errors.Wrap(err, "error serializing body for file append") 230 } 231 232 tx.signedTransactions._Push(&services.SignedTransaction{ 233 BodyBytes: bodyBytes, 234 SigMap: &services.SignatureMap{}, 235 }) 236 } 237 238 validStart := *nextTransactionID.ValidStart 239 240 *nextTransactionID.ValidStart = validStart.Add(1 * time.Nanosecond) 241 } 242 } 243 244 return tx, nil 245 } 246 247 // SetMaxTransactionFee sets the maximum transaction fee the operator (paying account) is willing to pay. 248 func (tx *FileAppendTransaction) SetMaxTransactionFee(fee Hbar) *FileAppendTransaction { 249 tx._RequireNotFrozen() 250 tx.Transaction.SetMaxTransactionFee(fee) 251 return tx 252 } 253 254 // SetRegenerateTransactionID sets if transaction IDs should be regenerated when `TRANSACTION_EXPIRED` is received 255 func (tx *FileAppendTransaction) SetRegenerateTransactionID(regenerateTransactionID bool) *FileAppendTransaction { 256 tx._RequireNotFrozen() 257 tx.Transaction.SetRegenerateTransactionID(regenerateTransactionID) 258 return tx 259 } 260 261 // SetTransactionMemo sets the memo for this FileAppendTransaction. 262 func (tx *FileAppendTransaction) SetTransactionMemo(memo string) *FileAppendTransaction { 263 tx._RequireNotFrozen() 264 tx.Transaction.SetTransactionMemo(memo) 265 return tx 266 } 267 268 // SetTransactionValidDuration sets the valid duration for this FileAppendTransaction. 269 func (tx *FileAppendTransaction) SetTransactionValidDuration(duration time.Duration) *FileAppendTransaction { 270 tx._RequireNotFrozen() 271 tx.Transaction.SetTransactionValidDuration(duration) 272 return tx 273 } 274 275 // ToBytes serialise the tx to bytes, no matter if it is signed (locked), or not 276 func (tx *FileAppendTransaction) ToBytes() ([]byte, error) { 277 bytes, err := tx.Transaction.toBytes(tx) 278 if err != nil { 279 return nil, err 280 } 281 return bytes, nil 282 } 283 284 // SetTransactionID sets the TransactionID for this FileAppendTransaction. 285 func (tx *FileAppendTransaction) SetTransactionID(transactionID TransactionID) *FileAppendTransaction { 286 tx._RequireNotFrozen() 287 288 tx.Transaction.SetTransactionID(transactionID) 289 return tx 290 } 291 292 // SetNodeAccountID sets the _Node AccountID for this FileAppendTransaction. 293 func (tx *FileAppendTransaction) SetNodeAccountIDs(nodeAccountIDs []AccountID) *FileAppendTransaction { 294 tx._RequireNotFrozen() 295 tx.Transaction.SetNodeAccountIDs(nodeAccountIDs) 296 return tx 297 } 298 299 // SetMaxRetry sets the max number of errors before execution will fail. 300 func (tx *FileAppendTransaction) SetMaxRetry(count int) *FileAppendTransaction { 301 tx.Transaction.SetMaxRetry(count) 302 return tx 303 } 304 305 // SetMaxBackoff The maximum amount of time to wait between retries. 306 // Every retry attempt will increase the wait time exponentially until it reaches this time. 307 func (tx *FileAppendTransaction) SetMaxBackoff(max time.Duration) *FileAppendTransaction { 308 tx.Transaction.SetMaxBackoff(max) 309 return tx 310 } 311 312 // SetMinBackoff sets the minimum amount of time to wait between retries. 313 func (tx *FileAppendTransaction) SetMinBackoff(min time.Duration) *FileAppendTransaction { 314 tx.Transaction.SetMinBackoff(min) 315 return tx 316 } 317 318 func (tx *FileAppendTransaction) SetLogLevel(level LogLevel) *FileAppendTransaction { 319 tx.Transaction.SetLogLevel(level) 320 return tx 321 } 322 323 // Execute executes the Transaction with the provided client 324 func (tx *FileAppendTransaction) Execute( 325 client *Client, 326 ) (TransactionResponse, error) { 327 if client == nil { 328 return TransactionResponse{}, errNoClientProvided 329 } 330 331 if tx.freezeError != nil { 332 return TransactionResponse{}, tx.freezeError 333 } 334 335 list, err := tx.ExecuteAll(client) 336 337 if err != nil { 338 if len(list) > 0 { 339 return TransactionResponse{ 340 TransactionID: tx.GetTransactionID(), 341 NodeID: list[0].NodeID, 342 Hash: make([]byte, 0), 343 }, err 344 } 345 return TransactionResponse{ 346 TransactionID: tx.GetTransactionID(), 347 Hash: make([]byte, 0), 348 }, err 349 } 350 351 return list[0], nil 352 } 353 354 // ExecuteAll executes the all the Transactions with the provided client 355 func (tx *FileAppendTransaction) ExecuteAll( 356 client *Client, 357 ) ([]TransactionResponse, error) { 358 if client == nil || client.operator == nil { 359 return []TransactionResponse{}, errNoClientProvided 360 } 361 362 if !tx.IsFrozen() { 363 _, err := tx.FreezeWith(client) 364 if err != nil { 365 return []TransactionResponse{}, err 366 } 367 } 368 369 var transactionID TransactionID 370 if tx.transactionIDs._Length() > 0 { 371 transactionID = tx.GetTransactionID() 372 } else { 373 return []TransactionResponse{}, errors.New("transactionID list is empty") 374 } 375 376 if !client.GetOperatorAccountID()._IsZero() && client.GetOperatorAccountID()._Equals(*transactionID.AccountID) { 377 tx.SignWith( 378 client.GetOperatorPublicKey(), 379 client.operator.signer, 380 ) 381 } 382 383 size := tx.signedTransactions._Length() / tx.nodeAccountIDs._Length() 384 list := make([]TransactionResponse, size) 385 386 for i := 0; i < size; i++ { 387 resp, err := _Execute(client, tx) 388 389 if err != nil { 390 return list, err 391 } 392 393 list[i] = resp.(TransactionResponse) 394 395 _, err = list[i].SetValidateStatus(false).GetReceipt(client) 396 if err != nil { 397 return list, err 398 } 399 } 400 401 return list, nil 402 } 403 404 func (tx *FileAppendTransaction) Schedule() (*ScheduleCreateTransaction, error) { 405 chunks := uint64((len(tx.contents) + (tx.chunkSize - 1)) / tx.chunkSize) 406 if chunks > 1 { 407 return &ScheduleCreateTransaction{}, ErrMaxChunksExceeded{ 408 Chunks: chunks, 409 MaxChunks: 1, 410 } 411 } 412 413 return tx.Transaction.schedule(tx) 414 } 415 416 // ----------- Overridden functions ---------------- 417 418 func (tx *FileAppendTransaction) getName() string { 419 return "FileAppendTransaction" 420 } 421 func (tx *FileAppendTransaction) validateNetworkOnIDs(client *Client) error { 422 if client == nil || !client.autoValidateChecksums { 423 return nil 424 } 425 426 if tx.fileID != nil { 427 if err := tx.fileID.ValidateChecksum(client); err != nil { 428 return err 429 } 430 } 431 432 return nil 433 } 434 435 func (tx *FileAppendTransaction) build() *services.TransactionBody { 436 return &services.TransactionBody{ 437 TransactionFee: tx.transactionFee, 438 Memo: tx.Transaction.memo, 439 TransactionValidDuration: _DurationToProtobuf(tx.GetTransactionValidDuration()), 440 TransactionID: tx.transactionID._ToProtobuf(), 441 Data: &services.TransactionBody_FileAppend{ 442 FileAppend: tx.buildProtoBody(), 443 }, 444 } 445 } 446 447 func (tx *FileAppendTransaction) buildScheduled() (*services.SchedulableTransactionBody, error) { 448 return &services.SchedulableTransactionBody{ 449 TransactionFee: tx.transactionFee, 450 Memo: tx.Transaction.memo, 451 Data: &services.SchedulableTransactionBody_FileAppend{ 452 FileAppend: tx.buildProtoBody(), 453 }, 454 }, nil 455 } 456 func (tx *FileAppendTransaction) buildProtoBody() *services.FileAppendTransactionBody { 457 body := &services.FileAppendTransactionBody{ 458 Contents: tx.contents, 459 } 460 461 if tx.fileID != nil { 462 body.FileID = tx.fileID._ToProtobuf() 463 } 464 465 return body 466 } 467 468 func (tx *FileAppendTransaction) getMethod(channel *_Channel) _Method { 469 return _Method{ 470 transaction: channel._GetFile().AppendContent, 471 } 472 } 473 474 func (tx *FileAppendTransaction) _ConstructScheduleProtobuf() (*services.SchedulableTransactionBody, error) { 475 return tx.buildScheduled() 476 }