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  }