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  }