github.com/koko1123/flow-go-1@v0.29.6/module/epochs/base_client.go (about)

     1  package epochs
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/sethvargo/go-retry"
     9  
    10  	"github.com/rs/zerolog"
    11  
    12  	sdk "github.com/onflow/flow-go-sdk"
    13  	sdkcrypto "github.com/onflow/flow-go-sdk/crypto"
    14  
    15  	"github.com/koko1123/flow-go-1/module"
    16  )
    17  
    18  const (
    19  	waitForSealedRetryInterval = 3 * time.Second
    20  	waitForSealedMaxDuration   = 5 * time.Minute
    21  )
    22  
    23  // BaseClient represents the core fields and methods needed to create
    24  // a client to a contract on the Flow Network.
    25  type BaseClient struct {
    26  	Log zerolog.Logger // default logger
    27  
    28  	ContractAddress string                  // contract address
    29  	FlowClient      module.SDKClientWrapper // flow access node client
    30  
    31  	AccountAddress  sdk.Address      // account belonging to node interacting with the contract
    32  	AccountKeyIndex uint             // account key index
    33  	Signer          sdkcrypto.Signer // signer used to sign transactions
    34  }
    35  
    36  // NewBaseClient creates a instance of BaseClient
    37  func NewBaseClient(
    38  	log zerolog.Logger,
    39  	flowClient module.SDKClientWrapper,
    40  	accountAddress string,
    41  	accountKeyIndex uint,
    42  	signer sdkcrypto.Signer,
    43  	contractAddress string,
    44  ) *BaseClient {
    45  
    46  	return &BaseClient{
    47  		Log:             log,
    48  		ContractAddress: contractAddress,
    49  		FlowClient:      flowClient,
    50  		AccountKeyIndex: accountKeyIndex,
    51  		Signer:          signer,
    52  		AccountAddress:  sdk.HexToAddress(accountAddress),
    53  	}
    54  }
    55  
    56  func (c *BaseClient) GetAccount(ctx context.Context) (*sdk.Account, error) {
    57  
    58  	// get account from access node for given address
    59  	account, err := c.FlowClient.GetAccount(ctx, c.AccountAddress)
    60  	if err != nil {
    61  		return nil, fmt.Errorf("could not get account: %w", err)
    62  	}
    63  
    64  	// check if account key index within range of keys
    65  	if len(account.Keys) <= int(c.AccountKeyIndex) {
    66  		return nil, fmt.Errorf("given account key index is bigger than the number of keys for this account")
    67  	}
    68  
    69  	return account, nil
    70  }
    71  
    72  // SendTransaction submits a transaction to Flow. Requires transaction to be signed.
    73  func (c *BaseClient) SendTransaction(ctx context.Context, tx *sdk.Transaction) (sdk.Identifier, error) {
    74  
    75  	// check if the transaction has a signature
    76  	if len(tx.EnvelopeSignatures) == 0 {
    77  		return sdk.EmptyID, fmt.Errorf("can not submit an unsigned transaction")
    78  	}
    79  
    80  	// submit transaction to client
    81  	err := c.FlowClient.SendTransaction(ctx, *tx)
    82  	if err != nil {
    83  		return sdk.EmptyID, fmt.Errorf("failed to send transaction: %w", err)
    84  	}
    85  
    86  	return tx.ID(), nil
    87  }
    88  
    89  // WaitForSealed waits for a transaction to be sealed
    90  func (c *BaseClient) WaitForSealed(ctx context.Context, txID sdk.Identifier, started time.Time) error {
    91  
    92  	log := c.Log.With().Str("tx_id", txID.Hex()).Logger()
    93  
    94  	backoff := retry.NewConstant(waitForSealedRetryInterval)
    95  	backoff = retry.WithMaxDuration(waitForSealedMaxDuration, backoff)
    96  
    97  	attempts := 0
    98  	err := retry.Do(ctx, backoff, func(ctx context.Context) error {
    99  		attempts++
   100  		log = c.Log.With().Int("attempt", attempts).Float64("time_elapsed_s", time.Since(started).Seconds()).Logger()
   101  
   102  		result, err := c.FlowClient.GetTransactionResult(ctx, txID)
   103  		if err != nil {
   104  			msg := "could not get transaction result, retrying"
   105  			log.Error().Err(err).Msg(msg)
   106  			return retry.RetryableError(fmt.Errorf(msg))
   107  		}
   108  
   109  		if result.Error != nil {
   110  			return fmt.Errorf("error executing transaction: %w", result.Error)
   111  		}
   112  
   113  		log.Info().Str("status", result.Status.String()).Msg("got transaction result")
   114  
   115  		// if the transaction has expired we skip waiting for seal
   116  		if result.Status == sdk.TransactionStatusExpired {
   117  			return fmt.Errorf("transaction has expired")
   118  		}
   119  
   120  		if result.Status == sdk.TransactionStatusSealed {
   121  			return nil
   122  		}
   123  
   124  		return retry.RetryableError(fmt.Errorf("waiting for transaction to be sealed retrying"))
   125  	})
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	return nil
   131  }