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 }