github.com/hashgraph/hedera-sdk-go/v2@v2.48.0/query.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 q 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  	"fmt"
    25  	"time"
    26  
    27  	"github.com/hashgraph/hedera-protobufs-go/services"
    28  	"github.com/pkg/errors"
    29  	protobuf "google.golang.org/protobuf/proto"
    30  )
    31  
    32  // Query is the struct used to build queries.
    33  type Query struct {
    34  	executable
    35  	client                *Client
    36  	pb                    *services.Query
    37  	pbHeader              *services.QueryHeader //nolint
    38  	paymentTransactionIDs *_LockableSlice
    39  
    40  	paymentTransactions []*services.Transaction
    41  	maxQueryPayment     Hbar
    42  	queryPayment        Hbar
    43  	timestamp           time.Time
    44  
    45  	isPaymentRequired bool
    46  }
    47  
    48  type queryResponse interface {
    49  	GetHeader() *services.ResponseHeader
    50  }
    51  
    52  type QueryInterface interface {
    53  	Executable
    54  
    55  	buildQuery() *services.Query
    56  	getQueryResponse(response *services.Response) queryResponse
    57  }
    58  
    59  // -------- Executable functions ----------
    60  
    61  func _NewQuery(isPaymentRequired bool, header *services.QueryHeader) Query {
    62  	minBackoff := 250 * time.Millisecond
    63  	maxBackoff := 8 * time.Second
    64  	return Query{
    65  		pb:                    &services.Query{},
    66  		pbHeader:              header,
    67  		paymentTransactionIDs: _NewLockableSlice(),
    68  		paymentTransactions:   make([]*services.Transaction, 0),
    69  		isPaymentRequired:     isPaymentRequired,
    70  		maxQueryPayment:       NewHbar(0),
    71  		queryPayment:          NewHbar(0),
    72  		executable: executable{
    73  			nodeAccountIDs: _NewLockableSlice(),
    74  			maxBackoff:     &maxBackoff,
    75  			minBackoff:     &minBackoff,
    76  			maxRetry:       10,
    77  		},
    78  	}
    79  }
    80  
    81  // SetMaxQueryPayment sets the maximum payment allowed for this query.
    82  func (q *Query) SetMaxQueryPayment(maxPayment Hbar) *Query {
    83  	q.maxQueryPayment = maxPayment
    84  	return q
    85  }
    86  
    87  // SetQueryPayment sets the payment amount for this query.
    88  func (q *Query) SetQueryPayment(paymentAmount Hbar) *Query {
    89  	q.queryPayment = paymentAmount
    90  	return q
    91  }
    92  
    93  // GetMaxQueryPayment returns the maximum payment allowed for this query.
    94  func (q *Query) GetMaxQueryPayment() Hbar {
    95  	return q.maxQueryPayment
    96  }
    97  
    98  // GetQueryPayment returns the payment amount for this query.
    99  func (q *Query) GetQueryPayment() Hbar {
   100  	return q.queryPayment
   101  }
   102  
   103  // GetCost returns the fee that would be charged to get the requested information (if a cost was requested).
   104  func (q *Query) getCost(client *Client, e QueryInterface) (Hbar, error) {
   105  	if client == nil || client.operator == nil {
   106  		return Hbar{}, errNoClientProvided
   107  	}
   108  
   109  	var err error
   110  
   111  	err = e.validateNetworkOnIDs(client)
   112  	if err != nil {
   113  		return Hbar{}, err
   114  	}
   115  	q.paymentTransactions = make([]*services.Transaction, 0)
   116  	if !q.nodeAccountIDs.locked {
   117  		q.SetNodeAccountIDs([]AccountID{client.network._GetNode().accountID})
   118  	}
   119  
   120  	q.pb = e.buildQuery()
   121  
   122  	if q.isPaymentRequired && len(q.paymentTransactions) > 0 {
   123  		q.paymentTransactionIDs._Advance()
   124  	}
   125  
   126  	q.pbHeader.ResponseType = services.ResponseType_COST_ANSWER
   127  	q.paymentTransactionIDs._Advance()
   128  	resp, err := _Execute(client, e)
   129  
   130  	if err != nil {
   131  		return Hbar{}, err
   132  	}
   133  
   134  	queryResp := e.getQueryResponse(resp.(*services.Response))
   135  	cost := int64(queryResp.GetHeader().Cost)
   136  
   137  	return HbarFromTinybar(cost), nil
   138  }
   139  
   140  func _QueryMakePaymentTransaction(transactionID TransactionID, nodeAccountID AccountID, operator *_Operator, cost Hbar) (*services.Transaction, error) {
   141  	accountAmounts := make([]*services.AccountAmount, 0)
   142  	accountAmounts = append(accountAmounts, &services.AccountAmount{
   143  		AccountID: nodeAccountID._ToProtobuf(),
   144  		Amount:    cost.tinybar,
   145  	})
   146  	accountAmounts = append(accountAmounts, &services.AccountAmount{
   147  		AccountID: operator.accountID._ToProtobuf(),
   148  		Amount:    -cost.tinybar,
   149  	})
   150  
   151  	body := services.TransactionBody{
   152  		TransactionID:  transactionID._ToProtobuf(),
   153  		NodeAccountID:  nodeAccountID._ToProtobuf(),
   154  		TransactionFee: uint64(NewHbar(1).tinybar),
   155  		TransactionValidDuration: &services.Duration{
   156  			Seconds: 120,
   157  		},
   158  		Data: &services.TransactionBody_CryptoTransfer{
   159  			CryptoTransfer: &services.CryptoTransferTransactionBody{
   160  				Transfers: &services.TransferList{
   161  					AccountAmounts: accountAmounts,
   162  				},
   163  			},
   164  		},
   165  	}
   166  
   167  	bodyBytes, err := protobuf.Marshal(&body)
   168  	if err != nil {
   169  		return nil, errors.Wrap(err, "error serializing Query body")
   170  	}
   171  
   172  	signature := operator.signer(bodyBytes)
   173  	sigPairs := make([]*services.SignaturePair, 0)
   174  	sigPairs = append(sigPairs, operator.publicKey._ToSignaturePairProtobuf(signature))
   175  
   176  	return &services.Transaction{
   177  		BodyBytes: bodyBytes,
   178  		SigMap: &services.SignatureMap{
   179  			SigPair: sigPairs,
   180  		},
   181  	}, nil
   182  }
   183  
   184  // GetPaymentTransactionID returns the payment transaction id.
   185  func (q *Query) GetPaymentTransactionID() TransactionID {
   186  	if !q.paymentTransactionIDs._IsEmpty() {
   187  		return q.paymentTransactionIDs._GetCurrent().(TransactionID)
   188  	}
   189  
   190  	return TransactionID{}
   191  }
   192  
   193  // GetMaxRetryCount returns the max number of errors before execution will fail.
   194  func (q *Query) GetMaxRetryCount() int {
   195  	return q.GetMaxRetry()
   196  }
   197  
   198  // SetPaymentTransactionID assigns the payment transaction id.
   199  func (q *Query) SetPaymentTransactionID(transactionID TransactionID) *Query {
   200  	q.paymentTransactionIDs._Clear()._Push(transactionID)._SetLocked(true)
   201  	return q
   202  }
   203  
   204  func (q *Query) execute(client *Client, e QueryInterface) (*services.Response, error) {
   205  	q.client = client
   206  	if client == nil {
   207  		return nil, errNoClientProvided
   208  	}
   209  
   210  	var err error
   211  
   212  	err = e.validateNetworkOnIDs(client)
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  
   217  	var cost Hbar
   218  	if q.queryPayment.tinybar == 0 && q.isPaymentRequired {
   219  		if q.maxQueryPayment.tinybar == 0 {
   220  			cost = client.GetDefaultMaxQueryPayment()
   221  		} else {
   222  			cost = q.maxQueryPayment
   223  		}
   224  
   225  		actualCost, err := q.getCost(client, e)
   226  		if err != nil {
   227  			return nil, err
   228  		}
   229  
   230  		if cost.tinybar < actualCost.tinybar {
   231  			return nil, ErrMaxQueryPaymentExceeded{
   232  				QueryCost:       actualCost,
   233  				MaxQueryPayment: cost,
   234  				query:           e.getName(),
   235  			}
   236  		}
   237  
   238  		q.queryPayment = actualCost
   239  	}
   240  
   241  	q.paymentTransactions = make([]*services.Transaction, 0)
   242  	if !q.nodeAccountIDs.locked {
   243  		q.SetNodeAccountIDs([]AccountID{client.network._GetNode().accountID})
   244  	}
   245  
   246  	q.pb = e.buildQuery()
   247  	q.pbHeader.ResponseType = services.ResponseType_ANSWER_ONLY
   248  
   249  	resp, err := _Execute(client, e)
   250  	if err != nil {
   251  		return nil, err
   252  	}
   253  
   254  	return resp.(*services.Response), nil
   255  }
   256  
   257  func (q *Query) shouldRetry(e Executable, response interface{}) _ExecutionState {
   258  	queryResp := e.(QueryInterface).getQueryResponse(response.(*services.Response))
   259  
   260  	status := Status(queryResp.GetHeader().NodeTransactionPrecheckCode)
   261  
   262  	retryableStatuses := map[Status]bool{
   263  		StatusPlatformTransactionNotCreated: true,
   264  		StatusPlatformNotActive:             true,
   265  		StatusBusy:                          true,
   266  	}
   267  
   268  	if retryableStatuses[status] {
   269  		return executionStateRetry
   270  	}
   271  
   272  	if status == StatusOk {
   273  		return executionStateFinished
   274  	}
   275  
   276  	return executionStateError
   277  }
   278  
   279  func (q *Query) generatePayments(client *Client, cost Hbar) (*services.Transaction, error) {
   280  	var tx *services.Transaction
   281  	var err error
   282  	for _, nodeID := range q.nodeAccountIDs.slice {
   283  		txnID := TransactionIDGenerate(client.operator.accountID)
   284  		tx, err = _QueryMakePaymentTransaction(
   285  			txnID,
   286  			nodeID.(AccountID),
   287  			client.operator,
   288  			cost,
   289  		)
   290  		if err != nil {
   291  			return nil, err
   292  		}
   293  		q.paymentTransactions = append(q.paymentTransactions, tx)
   294  	}
   295  	return tx, nil
   296  }
   297  
   298  func (q *Query) advanceRequest() {
   299  	q.nodeAccountIDs._Advance()
   300  }
   301  
   302  func (q *Query) makeRequest() interface{} {
   303  	if q.client != nil && q.isPaymentRequired {
   304  		tx, err := q.generatePayments(q.client, q.queryPayment)
   305  		if err != nil {
   306  			return q.pb
   307  		}
   308  		q.pbHeader.Payment = tx
   309  	}
   310  
   311  	return q.pb
   312  }
   313  
   314  func (q *Query) mapResponse(response interface{}, _ AccountID, _ interface{}) (interface{}, error) { // nolint
   315  	return response.(*services.Response), nil
   316  }
   317  
   318  func (q *Query) isTransaction() bool {
   319  	return false
   320  }
   321  
   322  func (q *Query) mapStatusError(e Executable, response interface{}) error {
   323  	queryResp := e.(QueryInterface).getQueryResponse(response.(*services.Response))
   324  	return ErrHederaPreCheckStatus{
   325  		Status: Status(queryResp.GetHeader().NodeTransactionPrecheckCode),
   326  	}
   327  }
   328  
   329  // ----------- Next methods should be overridden in each subclass ---------------
   330  
   331  // NOTE: Should be implemented in every inheritor. Example:
   332  //
   333  //	return ErrHederaPreCheckStatus{
   334  //		Status: Status(response.(*services.Response).GetNetworkGetVersionInfo().Header.NodeTransactionPrecheckCode),
   335  //	}
   336  func (q *Query) getMethod(*_Channel) _Method {
   337  	return _Method{}
   338  }
   339  
   340  func (q *Query) getName() string {
   341  	return "QueryInterface"
   342  }
   343  
   344  func (q *Query) getLogID(queryInterface Executable) string {
   345  	timestamp := q.timestamp.UnixNano()
   346  	return fmt.Sprintf("%s:%d", queryInterface.getName(), timestamp)
   347  }
   348  
   349  //lint:ignore U1000
   350  func (q *Query) buildQuery() *services.Query {
   351  	return nil
   352  }
   353  
   354  //lint:ignore U1000
   355  func (q *Query) buildScheduled() (*services.SchedulableTransactionBody, error) {
   356  	return nil, errors.New("Not implemented")
   357  }
   358  
   359  // NOTE: Should be implemented in every inheritor.
   360  func (q *Query) validateNetworkOnIDs(*Client) error {
   361  	return errors.New("Not implemented")
   362  }
   363  
   364  func (q *Query) getTransactionIDAndMessage() (string, string) {
   365  	txID := q.GetPaymentTransactionID().String()
   366  	if txID == "" {
   367  		txID = "None"
   368  	}
   369  	return txID, "QueryInterface status received"
   370  }