github.com/koko1123/flow-go-1@v0.29.6/module/epochs/qc_client.go (about) 1 package epochs 2 3 import ( 4 "context" 5 "encoding/hex" 6 "fmt" 7 "time" 8 9 "github.com/onflow/cadence" 10 "github.com/onflow/flow-core-contracts/lib/go/templates" 11 "github.com/rs/zerolog" 12 13 sdk "github.com/onflow/flow-go-sdk" 14 sdkcrypto "github.com/onflow/flow-go-sdk/crypto" 15 16 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 17 hotstuffver "github.com/koko1123/flow-go-1/consensus/hotstuff/verification" 18 "github.com/koko1123/flow-go-1/model/flow" 19 "github.com/koko1123/flow-go-1/module" 20 ) 21 22 const ( 23 24 // TransactionSubmissionTimeout is the time after which we return an error. 25 TransactionSubmissionTimeout = 5 * time.Minute 26 27 // TransactionStatusRetryTimeout is the time after which the status of a 28 // transaction is checked again 29 TransactionStatusRetryTimeout = 1 * time.Second 30 ) 31 32 // QCContractClient is a client to the Quorum Certificate contract. Allows the client to 33 // functionality to submit a vote and check if collection node has voted already. 34 type QCContractClient struct { 35 BaseClient 36 37 nodeID flow.Identifier // flow identifier of the collection node 38 env templates.Environment 39 } 40 41 // NewQCContractClient returns a new client to the Quorum Certificate contract 42 func NewQCContractClient( 43 log zerolog.Logger, 44 flowClient module.SDKClientWrapper, 45 flowClientANID flow.Identifier, 46 nodeID flow.Identifier, 47 accountAddress string, 48 accountKeyIndex uint, 49 qcContractAddress string, 50 signer sdkcrypto.Signer, 51 ) *QCContractClient { 52 53 log = log.With(). 54 Str("component", "qc_contract_client"). 55 Str("flow_client_an_id", flowClientANID.String()). 56 Logger() 57 base := NewBaseClient(log, flowClient, accountAddress, accountKeyIndex, signer, qcContractAddress) 58 59 // set QCContractAddress to the contract address given 60 env := templates.Environment{QuorumCertificateAddress: qcContractAddress} 61 62 return &QCContractClient{ 63 BaseClient: *base, 64 nodeID: nodeID, 65 env: env, 66 } 67 } 68 69 // SubmitVote submits the given vote to the cluster QC aggregator smart 70 // contract. This function returns only once the transaction has been 71 // processed by the network. An error is returned if the transaction has 72 // failed and should be re-submitted. 73 func (c *QCContractClient) SubmitVote(ctx context.Context, vote *model.Vote) error { 74 75 // time method was invoked 76 started := time.Now() 77 78 // add a timeout to the context 79 ctx, cancel := context.WithTimeout(ctx, TransactionSubmissionTimeout) 80 defer cancel() 81 82 // get account for given address and also validates AccountKeyIndex is valid 83 account, err := c.GetAccount(ctx) 84 if err != nil { 85 return fmt.Errorf("could not get account: %w", err) 86 } 87 88 // get latest finalized block to execute transaction 89 latestBlock, err := c.FlowClient.GetLatestBlock(ctx, false) 90 if err != nil { 91 return fmt.Errorf("could not get latest block from node: %w", err) 92 } 93 94 // attach submit vote transaction template and build transaction 95 seqNumber := account.Keys[int(c.AccountKeyIndex)].SequenceNumber 96 tx := sdk.NewTransaction(). 97 SetScript(templates.GenerateSubmitVoteScript(c.env)). 98 SetGasLimit(9999). 99 SetReferenceBlockID(latestBlock.ID). 100 SetProposalKey(account.Address, int(c.AccountKeyIndex), seqNumber). 101 SetPayer(account.Address). 102 AddAuthorizer(account.Address) 103 104 // add signature to the transaction 105 sigDataHex, err := cadence.NewString(hex.EncodeToString(vote.SigData)) 106 if err != nil { 107 return fmt.Errorf("could not convert vote sig data: %w", err) 108 } 109 err = tx.AddArgument(sigDataHex) 110 if err != nil { 111 return fmt.Errorf("could not add raw vote data to transaction: %w", err) 112 } 113 114 // add message to the transaction 115 voteMessage := hotstuffver.MakeVoteMessage(vote.View, vote.BlockID) 116 voteMessageHex, err := cadence.NewString(hex.EncodeToString(voteMessage)) 117 if err != nil { 118 return fmt.Errorf("could not convert vote message: %w", err) 119 } 120 err = tx.AddArgument(voteMessageHex) 121 if err != nil { 122 return fmt.Errorf("could not add raw vote data to transaction: %w", err) 123 } 124 125 // sign envelope using account signer 126 err = tx.SignEnvelope(account.Address, int(c.AccountKeyIndex), c.Signer) 127 if err != nil { 128 return fmt.Errorf("could not sign transaction: %w", err) 129 } 130 131 // submit signed transaction to node 132 c.Log.Info().Str("tx_id", tx.ID().Hex()).Msg("sending SubmitResult transaction") 133 txID, err := c.SendTransaction(ctx, tx) 134 if err != nil { 135 return fmt.Errorf("failed to submit transaction: %w", err) 136 } 137 138 err = c.WaitForSealed(ctx, txID, started) 139 if err != nil { 140 return fmt.Errorf("failed to wait for transaction seal: %w", err) 141 } 142 143 return nil 144 } 145 146 // Voted returns true if we have successfully submitted a vote to the 147 // cluster QC aggregator smart contract for the current epoch. 148 func (c *QCContractClient) Voted(ctx context.Context) (bool, error) { 149 150 // execute script to read if voted 151 template := templates.GenerateGetNodeHasVotedScript(c.env) 152 hasVoted, err := c.FlowClient.ExecuteScriptAtLatestBlock(ctx, template, []cadence.Value{cadence.String(c.nodeID.String())}) 153 if err != nil { 154 return false, fmt.Errorf("could not execute voted script: %w", err) 155 } 156 157 // check if node has voted 158 if !hasVoted.(cadence.Bool) { 159 return false, nil 160 } 161 162 return true, nil 163 }