Skip to content

Commit

Permalink
add Cost() function to transaction builder
Browse files Browse the repository at this point in the history
  • Loading branch information
QuestofIranon committed Feb 19, 2020
1 parent af3806d commit 894a81a
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 74 deletions.
1 change: 1 addition & 0 deletions examples/get_transaction_cost/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package get_transaction_cost
166 changes: 92 additions & 74 deletions transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func (transaction Transaction) SignWith(publicKey Ed25519PublicKey, signer Trans
return transaction
}

func (transaction Transaction) Execute(client *Client) (TransactionID, error) {
func (transaction Transaction) executeForResponse(client *Client) (TransactionID, *proto.TransactionResponse, error) {
if client.operator != nil {
transaction.signWithOperator(*client.operator)
}
Expand All @@ -83,72 +83,13 @@ func (transaction Transaction) Execute(client *Client) (TransactionID, error) {
node := client.node(nodeAccountID)

if node == nil {
return id, newErrLocalValidationf("NodeAccountID %v not found on Client", nodeAccountID)
return id, nil, newErrLocalValidationf("NodeAccountID %v not found on Client", nodeAccountID)
}

var methodName string

switch transactionBody.Data.(type) {
case *proto.TransactionBody_CryptoCreateAccount:
methodName = "/proto.CryptoService/createAccount"

case *proto.TransactionBody_CryptoTransfer:
methodName = "/proto.CryptoService/cryptoTransfer"

case *proto.TransactionBody_CryptoUpdateAccount:
methodName = "/proto.CryptoService/updateAccount"

case *proto.TransactionBody_CryptoDelete:
methodName = "/proto.CryptoService/cryptoDelete"

// FileServices
case *proto.TransactionBody_FileCreate:
methodName = "/proto.FileService/createFile"

case *proto.TransactionBody_FileUpdate:
methodName = "/proto.FileService/updateFile"

case *proto.TransactionBody_FileAppend:
methodName = "/proto.FileService/appendFile"

case *proto.TransactionBody_FileDelete:
methodName = "/proto.FileService/deleteFile"

// Contract
case *proto.TransactionBody_ContractCreateInstance:
methodName = "/proto.SmartContractService/createContract"

case *proto.TransactionBody_ContractDeleteInstance:
methodName = "/proto.SmartContractService/deleteContract"

case *proto.TransactionBody_ContractUpdateInstance:
methodName = "/proto.SmartContractService/updateContract"

case *proto.TransactionBody_ContractCall:
methodName = "/proto.SmartContractService/contractCallMethod"
methodName, err := getMethodName(transaction.body())

// System
case *proto.TransactionBody_Freeze:
methodName = "/proto.FreezeService/freeze"

case *proto.TransactionBody_SystemDelete:
methodName = "/proto.FileService/systemDelete"

case *proto.TransactionBody_SystemUndelete:
methodName = "/proto.FileService/systemUndelete"

// HCS
case *proto.TransactionBody_ConsensusCreateTopic:
methodName = "/proto.ConsensusService/createTopic"
case *proto.TransactionBody_ConsensusDeleteTopic:
methodName = "/proto.ConsensusService/deleteTopic"
case *proto.TransactionBody_ConsensusUpdateTopic:
methodName = "/proto.ConsensusService/updateTopic"
case *proto.TransactionBody_ConsensusSubmitMessage:
methodName = "/proto.ConsensusService/submitMessage"

default:
return id, newErrLocalValidationf("Could not find method name for: %T", transactionBody.Data)
if err != nil {
return id, nil, err
}

validUntil := time.Now().Add(time.Duration(transactionBody.TransactionValidDuration.Seconds) * time.Second)
Expand All @@ -168,27 +109,37 @@ func (transaction Transaction) Execute(client *Client) (TransactionID, error) {

err := node.invoke(methodName, transaction.pb, resp)
if err != nil {
return id, err
return id, resp, err
}

if resp.NodeTransactionPrecheckCode == proto.ResponseCodeEnum_BUSY {
// Try again (in a flash) on BUSY
continue
}

status := Status(resp.NodeTransactionPrecheckCode)
return id, resp, nil
}

// Timed out
return id, nil, newErrHederaPreCheckStatus(transaction.ID, Status(resp.NodeTransactionPrecheckCode))
}

if status.isExceptional(true) {
// precheck failed
return id, newErrHederaPreCheckStatus(transaction.ID, status)
}
func (transaction Transaction) Execute(client *Client) (TransactionID, error) {
id, resp, err := transaction.executeForResponse(client)

// success
return id, nil
if err != nil {
return id, err
}

// Timed out
return id, newErrHederaPreCheckStatus(transaction.ID, Status(resp.NodeTransactionPrecheckCode))
status := Status(resp.NodeTransactionPrecheckCode)

if status.isExceptional(true) {
// precheck failed
return id, newErrHederaPreCheckStatus(transaction.ID, status)
}

// success
return id, nil
}

func (transaction Transaction) String() string {
Expand All @@ -213,3 +164,70 @@ func (transaction Transaction) body() *proto.TransactionBody {

return transactionBody
}

// getMethodName returns the proto method name of the transaction body
func getMethodName(transactionBody *proto.TransactionBody) (string, error) {
switch transactionBody.Data.(type) {
case *proto.TransactionBody_CryptoCreateAccount:
return "/proto.CryptoService/createAccount", nil

case *proto.TransactionBody_CryptoTransfer:
return "/proto.CryptoService/cryptoTransfer", nil

case *proto.TransactionBody_CryptoUpdateAccount:
return "/proto.CryptoService/updateAccount", nil

case *proto.TransactionBody_CryptoDelete:
return "/proto.CryptoService/cryptoDelete", nil

// FileServices
case *proto.TransactionBody_FileCreate:
return "/proto.FileService/createFile", nil

case *proto.TransactionBody_FileUpdate:
return "/proto.FileService/updateFile", nil

case *proto.TransactionBody_FileAppend:
return "/proto.FileService/appendFile", nil

case *proto.TransactionBody_FileDelete:
return "/proto.FileService/deleteFile", nil

// Contract
case *proto.TransactionBody_ContractCreateInstance:
return "/proto.SmartContractService/createContract", nil

case *proto.TransactionBody_ContractDeleteInstance:
return "/proto.SmartContractService/deleteContract", nil

case *proto.TransactionBody_ContractUpdateInstance:
return "/proto.SmartContractService/updateContract", nil

case *proto.TransactionBody_ContractCall:
return "/proto.SmartContractService/contractCallMethod", nil

// System
case *proto.TransactionBody_Freeze:
return "/proto.FreezeService/freeze", nil

case *proto.TransactionBody_SystemDelete:
return "/proto.FileService/systemDelete", nil

case *proto.TransactionBody_SystemUndelete:
return "/proto.FileService/systemUndelete", nil

// HCS
case *proto.TransactionBody_ConsensusCreateTopic:
return "/proto.ConsensusService/createTopic", nil
case *proto.TransactionBody_ConsensusDeleteTopic:
return "/proto.ConsensusService/deleteTopic", nil
case *proto.TransactionBody_ConsensusUpdateTopic:
return "/proto.ConsensusService/updateTopic", nil
case *proto.TransactionBody_ConsensusSubmitMessage:
return "/proto.ConsensusService/submitMessage", nil

default:
return "", newErrLocalValidationf("Could not find method name for: %T", transactionBody.Data)
}

}
50 changes: 50 additions & 0 deletions transaction_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package hedera
import (
protobuf "github.com/golang/protobuf/proto"
"github.com/hashgraph/hedera-sdk-go/proto"
"math"
"time"
)

Expand Down Expand Up @@ -66,6 +67,55 @@ func (builder TransactionBuilder) Execute(client *Client) (TransactionID, error)
return tx.Execute(client)
}

// Cost returns the estimated cost of the transaction.
//
// NOTE: The actual cost returned by Hedera is within 99.8% to 99.9% of the actual fee that will be assessed. We're
// unsure if this is because the fee fluctuates that much or if the calculations are simply incorrect on the server. To
// compensate for this we just bump by a 1% the value returned. As this would only ever be a maximum this will not cause
// you to be charged more.
func (builder TransactionBuilder) Cost(client *Client) (Hbar, error) {
// An operator must be set on the client
if client == nil || client.operator == nil {
return ZeroHbar, newErrLocalValidationf("calling .Cost() requires client.SetOperator")
}

oldFee := builder.pb.TransactionFee
oldTxID := builder.pb.TransactionID
oldValidDuration := builder.pb.TransactionValidDuration

defer func(){
// always reset the state of the builder before exiting this function
builder.pb.TransactionFee = oldFee
builder.pb.TransactionID = oldTxID
builder.pb.TransactionValidDuration = oldValidDuration
}()

costTx, err := builder.
SetMaxTransactionFee(ZeroHbar).
SetTransactionID(NewTransactionID(client.operator.accountID)).
Build(client)

if err != nil {
return ZeroHbar, err
}

_, resp, err := costTx.
executeForResponse(client)

if err != nil {
return ZeroHbar, err
}

status := Status(resp.NodeTransactionPrecheckCode)

if status != StatusInsufficientTxFee {
// any status that is not insufficienttxfee should be considered an error in this case
return ZeroHbar, newErrHederaPreCheckStatus(transactionIDFromProto(builder.pb.TransactionID), status)
}

return HbarFromTinybar(int64(math.Ceil(float64(resp.GetCost()) * 1.1))), nil
}

//
// Shared
//
Expand Down

0 comments on commit 894a81a

Please sign in to comment.