Skip to content

Commit

Permalink
fireblocks adjust confirmation threshold
Browse files Browse the repository at this point in the history
  • Loading branch information
ian-shim committed Mar 31, 2024
1 parent c0efcad commit fc1db04
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 20 deletions.
4 changes: 4 additions & 0 deletions chainio/clients/fireblocks/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ type Client interface {
// GetAssetAddresses makes a GetAssetAddresses request to the Fireblocks API
// It returns the addresses for the given asset ID and vault ID.
GetAssetAddresses(ctx context.Context, vaultID string, assetID AssetID) ([]AssetAddress, error)
// SetConfirmationThreshold makes a SetConfirmationThreshold request to the Fireblocks API
// It sets the confirmation threshold for the given transaction ID.
// By default, Fireblocks defaults the confirmation threshold to 3.
SetConfirmationThreshold(ctx context.Context, txID string, threshold int) (bool, error)
}

type client struct {
Expand Down
12 changes: 11 additions & 1 deletion chainio/clients/fireblocks/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestContractCall(t *testing.T) {
"2",
destinationAccountID,
"0",
"0x5140a548000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000002177dee1f66d6dbfbf517d9c4f316024c6a21aeb000000000000000000000000ad9770d6b5514724c7b766f087bea8a784038cbe000000000000000000000000cb14cfaac122e52024232583e7354589aede74ff00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000",
"0x5140a5480000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000300000000000000000000000008300f3797dcee9cc813ebaae25127b4e3b550e3000000000000000000000000ad9770d6b5514724c7b766f087bea8a784038cbe000000000000000000000000cb14cfaac122e52024232583e7354589aede74ff00000000000000000000000000000000000000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000",
"", // replaceTxByHash
"", // gasPrice
"", // gasLimit
Expand Down Expand Up @@ -99,3 +99,13 @@ func TestGetAssetAddresses(t *testing.T) {
t.Logf("Address: %+v", address)
}
}

func TestSetConfirmationThreshold(t *testing.T) {
t.Skip("skipping test as it's meant for manual runs only")

c := newFireblocksClient(t)
txID := "FILL_ME_IN"
success, err := c.SetConfirmationThreshold(context.Background(), txID, 1)
assert.NoError(t, err)
t.Logf("Success: %+v", success)
}
36 changes: 36 additions & 0 deletions chainio/clients/fireblocks/set_confirmation_threshold.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package fireblocks

import (
"context"
"encoding/json"
"fmt"
"strings"
)

type SetConfirmationThresholdRequest struct {
NumOfConfirmations int `json:"numOfConfirmations"`
}

type SetConfirmationThresholdResponse struct {
Success bool `json:"success"`
Transactions []string `json:"transactions"`
}

func (f *client) SetConfirmationThreshold(ctx context.Context, txID string, threshold int) (bool, error) {
f.logger.Debug("Fireblocks set confirmation threshold", "txID", txID, "threshold", threshold)
url := fmt.Sprintf("/v1/transactions/%s/set_confirmation_threshold", txID)
res, err := f.makeRequest(ctx, "POST", url, &SetConfirmationThresholdRequest{
NumOfConfirmations: threshold,
})
if err != nil {
return false, fmt.Errorf("error making request: %w", err)
}
var response SetConfirmationThresholdResponse
err = json.NewDecoder(strings.NewReader(string(res))).Decode(&response)
if err != nil {
return false, fmt.Errorf("error parsing response body: %w", err)
}
f.logger.Debug("Fireblocks set confirmation threshold response", "response", string(res))

return response.Success, nil
}
15 changes: 15 additions & 0 deletions chainio/clients/mocks/fireblocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 20 additions & 11 deletions chainio/clients/wallet/fireblocks_wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ type fireblocksWallet struct {
// accessed concurrently by SendTransaction and GetTransactionReceipt
mu sync.Mutex

fireblocksClient fireblocks.Client
ethClient eth.Client
vaultAccountName string
logger logging.Logger
chainID *big.Int
fireblocksClient fireblocks.Client
ethClient eth.Client
vaultAccountName string
logger logging.Logger
chainID *big.Int
confirmationThreshold int

// nonceToTx keeps track of the transaction ID for each nonce
// this is used to retrieve the transaction hash for a given nonce
Expand All @@ -47,18 +48,19 @@ type fireblocksWallet struct {
whitelistedContracts map[common.Address]*fireblocks.WhitelistedContract
}

func NewFireblocksWallet(fireblocksClient fireblocks.Client, ethClient eth.Client, vaultAccountName string, logger logging.Logger) (Wallet, error) {
func NewFireblocksWallet(fireblocksClient fireblocks.Client, ethClient eth.Client, vaultAccountName string, confirmationThreshold int, logger logging.Logger) (Wallet, error) {
chainID, err := ethClient.ChainID(context.Background())
if err != nil {
return nil, fmt.Errorf("error getting chain ID: %w", err)
}
logger.Debug("Creating new Fireblocks wallet for chain", "chainID", chainID)
return &fireblocksWallet{
fireblocksClient: fireblocksClient,
ethClient: ethClient,
vaultAccountName: vaultAccountName,
logger: logger,
chainID: chainID,
fireblocksClient: fireblocksClient,
ethClient: ethClient,
vaultAccountName: vaultAccountName,
logger: logger,
chainID: chainID,
confirmationThreshold: confirmationThreshold,

nonceToTxID: make(map[uint64]TxID),
txIDToNonce: make(map[TxID]uint64),
Expand Down Expand Up @@ -199,6 +201,13 @@ func (t *fireblocksWallet) SendTransaction(ctx context.Context, tx *types.Transa
t.txIDToNonce[res.ID] = nonce
t.logger.Debug("Fireblocks contract call complete", "txID", res.ID, "status", res.Status)

success, err := t.fireblocksClient.SetConfirmationThreshold(ctx, res.ID, t.confirmationThreshold)
if err != nil {
t.logger.Error("failed to set confirmation threshold", "txID", res.ID, "threshold", t.confirmationThreshold, "error", err)
} else if !success {
t.logger.Error("failed to set confirmation threshold", "txID", res.ID, "threshold", t.confirmationThreshold)
}

return res.ID, nil
}

Expand Down
19 changes: 11 additions & 8 deletions chainio/clients/wallet/fireblocks_wallet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestSendTransaction(t *testing.T) {
logger, err := logging.NewZapLogger(logging.Development)
assert.NoError(t, err)
ethClient.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(5), nil)
sender, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, logger)
sender, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, 1, logger)
assert.NoError(t, err)

fireblocksClient.EXPECT().ListContracts(gomock.Any()).Return([]fireblocks.WhitelistedContract{
Expand All @@ -55,6 +55,7 @@ func TestSendTransaction(t *testing.T) {
ID: "1234",
Status: fireblocks.Confirming,
}, nil)
fireblocksClient.EXPECT().SetConfirmationThreshold(gomock.Any(), gomock.Any(), 1).Return(true, nil)
fireblocksClient.EXPECT().ListVaultAccounts(gomock.Any()).Return([]fireblocks.VaultAccount{
{
ID: "vaultAccountID",
Expand Down Expand Up @@ -90,7 +91,7 @@ func TestSendTransactionNoValidContract(t *testing.T) {
logger, err := logging.NewZapLogger(logging.Development)
assert.NoError(t, err)
ethClient.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(5), nil)
sender, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, logger)
sender, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, 1, logger)
assert.NoError(t, err)

fireblocksClient.EXPECT().ListContracts(gomock.Any()).Return([]fireblocks.WhitelistedContract{
Expand Down Expand Up @@ -146,7 +147,7 @@ func TestSendTransactionInvalidVault(t *testing.T) {
logger, err := logging.NewZapLogger(logging.Development)
assert.NoError(t, err)
ethClient.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(5), nil)
sender, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, logger)
sender, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, 1, logger)
assert.NoError(t, err)

fireblocksClient.EXPECT().ListVaultAccounts(gomock.Any()).Return([]fireblocks.VaultAccount{
Expand Down Expand Up @@ -184,7 +185,7 @@ func TestSendTransactionReplaceTx(t *testing.T) {
logger, err := logging.NewZapLogger(logging.Development)
assert.NoError(t, err)
ethClient.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(5), nil)
sender, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, logger)
sender, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, 1, logger)
assert.NoError(t, err)

fireblocksClient.EXPECT().ListContracts(gomock.Any()).Return([]fireblocks.WhitelistedContract{
Expand All @@ -209,6 +210,7 @@ func TestSendTransactionReplaceTx(t *testing.T) {
ID: "1234",
Status: fireblocks.Confirming,
}, nil)
fireblocksClient.EXPECT().SetConfirmationThreshold(gomock.Any(), gomock.Any(), 1).Return(true, nil)
fireblocksClient.EXPECT().ListVaultAccounts(gomock.Any()).Return([]fireblocks.VaultAccount{
{
ID: "vaultAccountID",
Expand Down Expand Up @@ -270,6 +272,7 @@ func TestSendTransactionReplaceTx(t *testing.T) {
ID: "5678",
Status: fireblocks.Confirming,
}, nil)
fireblocksClient.EXPECT().SetConfirmationThreshold(gomock.Any(), gomock.Any(), 1).Return(true, nil)
// send another tx with the same nonce
txID, err = sender.SendTransaction(context.Background(), replacementTx)
assert.NoError(t, err)
Expand All @@ -284,7 +287,7 @@ func TestWaitForTransactionReceipt(t *testing.T) {
logger, err := logging.NewZapLogger(logging.Development)
assert.NoError(t, err)
ethClient.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(5), nil)
sender, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, logger)
sender, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, 1, logger)
assert.NoError(t, err)

expectedTxHash := "0x0000000000000000000000000000000000000000000000000000000000001234"
Expand Down Expand Up @@ -312,7 +315,7 @@ func TestWaitForTransactionReceiptFailFromFireblocks(t *testing.T) {
logger, err := logging.NewZapLogger(logging.Development)
assert.NoError(t, err)
ethClient.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(5), nil)
sender, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, logger)
sender, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, 1, logger)
assert.NoError(t, err)

expectedTxHash := "0x0000000000000000000000000000000000000000000000000000000000001234"
Expand All @@ -335,7 +338,7 @@ func TestWaitForTransactionReceiptFailFromChain(t *testing.T) {
logger, err := logging.NewZapLogger(logging.Development)
assert.NoError(t, err)
ethClient.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(5), nil)
sender, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, logger)
sender, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, 1, logger)
assert.NoError(t, err)

expectedTxHash := "0x0000000000000000000000000000000000000000000000000000000000001234"
Expand All @@ -359,7 +362,7 @@ func TestSenderAddress(t *testing.T) {
logger, err := logging.NewZapLogger(logging.Development)
assert.NoError(t, err)
ethClient.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(5), nil)
w, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, logger)
w, err := wallet.NewFireblocksWallet(fireblocksClient, ethClient, vaultAccountName, 1, logger)
assert.NoError(t, err)
assetID := fireblocks.AssetIDByChain[5]
fireblocksClient.EXPECT().ListVaultAccounts(gomock.Any()).Return([]fireblocks.VaultAccount{
Expand Down

0 comments on commit fc1db04

Please sign in to comment.