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 }