Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions relayer/alert/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const (
GetTunnelPacketErrorMsg = "Failed to get tunnel packet from BandChain"
GetContractTunnelInfoErrorMsg = "Failed to get tunnel info from contract"
PacketSigningStatusErrorMsg = "Failed tunnel packet signing status"
GetBlockErrorMsg = "Failed to get block from chain"
GetBalanceErrorMsg = "Failed to get balance from chain"
SaveDatabaseErrorMsg = "Failed to save to database"
)

// Alert represents an object that triggers and resets alerts.
Expand Down
254 changes: 132 additions & 122 deletions relayer/chains/evm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,10 @@ func (cp *EVMChainProvider) RelayPacket(ctx context.Context, packet *bandtypes.P
)
return fmt.Errorf("[EVMProvider] failed to estimate gas fee: %w", err)
}
alert.HandleReset(cp.Alert, alert.NewTopic(alert.EstimateGasFeeErrorMsg).WithTunnelID(packet.TunnelID).WithChainName(cp.ChainName))
alert.HandleReset(
cp.Alert,
alert.NewTopic(alert.EstimateGasFeeErrorMsg).WithTunnelID(packet.TunnelID).WithChainName(cp.ChainName),
)

var lastErr error
var bumpGasErr error
Expand All @@ -186,9 +189,19 @@ func (cp *EVMChainProvider) RelayPacket(ctx context.Context, packet *bandtypes.P
continue
}

balance, err := cp.Client.GetBalance(ctx, gethcommon.HexToAddress(freeSigner.GetAddress()), nil)
if err != nil {
log.Error("GetBalance error", err)
var balance *big.Int
if cp.DB != nil {
balance, err = cp.Client.GetBalance(ctx, gethcommon.HexToAddress(freeSigner.GetAddress()), nil)
if err != nil {
log.Error("Failed to get balance", "retry_count", retryCount, err)
alert.HandleAlert(cp.Alert, alert.NewTopic(alert.GetBalanceErrorMsg).
WithTunnelID(packet.TunnelID).
WithChainName(cp.ChainName), err.Error())
} else {
alert.HandleReset(cp.Alert, alert.NewTopic(alert.GetBalanceErrorMsg).
WithTunnelID(packet.TunnelID).
WithChainName(cp.ChainName))
}
}

// submit the transaction, if failed, bump gas and retry
Expand All @@ -213,14 +226,28 @@ func (cp *EVMChainProvider) RelayPacket(ctx context.Context, packet *bandtypes.P
"retry_count", retryCount,
)

if err := cp.saveUnconfirmedTransaction(txHash, types.TX_STATUS_PENDING, packet, freeSigner.GetAddress()); err != nil {
log.Error("SaveTransaction error", "retry_count", retryCount, err)
// save pending tx in db
if cp.DB != nil {
tx := cp.prepareTransaction(ctx, txHash, freeSigner.GetAddress(), packet, nil, balance, log, retryCount)
cp.handleSaveTransaction(tx, log, retryCount)
}

txResult := cp.WaitForConfirmedTx(ctx, txHash, log)

cp.handleMetrics(packet.TunnelID, createdAt, txResult)
cp.handleSaveTransaction(ctx, freeSigner.GetAddress(), balance, packet, txResult, retryCount, log)
if cp.DB != nil {
tx := cp.prepareTransaction(
ctx,
txHash,
freeSigner.GetAddress(),
packet,
&txResult,
balance,
log,
retryCount,
)
cp.handleSaveTransaction(tx, log, retryCount)
}

if txResult.Status == types.TX_STATUS_SUCCESS {
log.Info(
Expand Down Expand Up @@ -336,7 +363,6 @@ func (cp *EVMChainProvider) WaitForConfirmedTx(
}

return NewTxResult(
txHash,
types.TX_STATUS_TIMEOUT,
decimal.NullDecimal{},
decimal.NullDecimal{},
Expand Down Expand Up @@ -370,25 +396,108 @@ func (cp *EVMChainProvider) handleMetrics(tunnelID uint64, createdAt time.Time,
}
}

// handleSaveTransaction saves the transaction to the database based on its status.
func (cp *EVMChainProvider) handleSaveTransaction(ctx context.Context,
// prepareTransaction prepares the transaction to be stored in the database.
func (cp *EVMChainProvider) prepareTransaction(
ctx context.Context,
txHash string,
signerAddress string,
oldBalance *big.Int,
packet *bandtypes.Packet,
txResult TxResult,
retryCount int,
txResult *TxResult,
oldBalance *big.Int,
log logger.Logger,
) {
switch txResult.Status {
case types.TX_STATUS_SUCCESS, types.TX_STATUS_FAILED:
if err := cp.saveConfirmedTransaction(ctx, signerAddress, oldBalance, packet, txResult); err != nil {
log.Error("SaveTransaction error", "retry_count", retryCount, err)
}
default:
if err := cp.saveUnconfirmedTransaction(txResult.TxHash, txResult.Status, packet, signerAddress); err != nil {
log.Error("SaveTransaction error", "retry_count", retryCount, err)
retryCount int,
) *db.Transaction {
var signalPrices []db.SignalPrice

for _, p := range packet.SignalPrices {
signalPrices = append(signalPrices, *db.NewSignalPrice(p.SignalID, p.Price))
}

txStatus := types.TX_STATUS_PENDING
gasUsed := decimal.NullDecimal{}
effectiveGasPrice := decimal.NullDecimal{}
balanceDelta := decimal.NullDecimal{}

var blockTimestamp *time.Time

if txResult != nil {
txStatus = txResult.Status
gasUsed = txResult.GasUsed
effectiveGasPrice = txResult.EffectiveGasPrice

if txResult.Status == types.TX_STATUS_SUCCESS || txResult.Status == types.TX_STATUS_FAILED {
block, err := cp.Client.GetBlock(context.Background(), txResult.BlockNumber)
if err != nil {
log.Error("Failed to get block", "retry_count", retryCount, err)
alert.HandleAlert(cp.Alert, alert.NewTopic(alert.GetBlockErrorMsg).
WithTunnelID(packet.TunnelID).
WithChainName(cp.ChainName), err.Error())
} else {
timestamp := time.Unix(int64(block.Time()), 0).UTC()
blockTimestamp = &timestamp
alert.HandleReset(cp.Alert, alert.NewTopic(alert.GetBlockErrorMsg).
WithTunnelID(packet.TunnelID).
WithChainName(cp.ChainName))
}

// Compute new balance
// Note: this may be incorrect if other transactions affected the user's balance during this period.
if oldBalance != nil {
newBalance, err := cp.Client.GetBalance(
ctx,
gethcommon.HexToAddress(signerAddress),
txResult.BlockNumber,
)
if err != nil {
log.Error("Failed to get balance", "retry_count", retryCount, err)
alert.HandleAlert(cp.Alert, alert.NewTopic(alert.GetBalanceErrorMsg).
WithTunnelID(packet.TunnelID).
WithChainName(cp.ChainName), err.Error())
} else {
diff := new(big.Int).Sub(newBalance, oldBalance)
balanceDelta = decimal.NewNullDecimal(decimal.NewFromBigInt(diff, 0))
alert.HandleReset(cp.Alert, alert.NewTopic(alert.GetBalanceErrorMsg).
WithTunnelID(packet.TunnelID).
WithChainName(cp.ChainName))
}
}
}
}

tx := db.NewTransaction(
txHash,
packet.TunnelID,
packet.Sequence,
cp.ChainName,
types.ChainTypeEVM,
signerAddress,
txStatus,
gasUsed,
effectiveGasPrice,
balanceDelta,
signalPrices,
blockTimestamp,
)

return tx
}

// handleSaveTransaction saves the transaction to the database and triggers alert if any error occurs.
func (cp *EVMChainProvider) handleSaveTransaction(tx *db.Transaction, log logger.Logger, retryCount int) {
if cp.DB == nil {
log.Debug("Database is not set; skipping saving transaction")
return
}
if err := cp.DB.AddOrUpdateTransaction(tx); err != nil {
log.Error("Save transaction error", "retry_count", retryCount, err)
alert.HandleAlert(cp.Alert, alert.NewTopic(alert.SaveDatabaseErrorMsg).
WithTunnelID(tx.TunnelID).
WithChainName(cp.ChainName), err.Error())
} else {
alert.HandleReset(cp.Alert, alert.NewTopic(alert.SaveDatabaseErrorMsg).
WithTunnelID(tx.TunnelID).
WithChainName(cp.ChainName))
}
}

// CheckConfirmedTx checks the confirmed transaction status.
Expand All @@ -400,7 +509,6 @@ func (cp *EVMChainProvider) CheckConfirmedTx(
if err != nil {
err = fmt.Errorf("failed to get tx receipt: %w", err)
return NewTxResult(
txHash,
types.TX_STATUS_PENDING,
decimal.NullDecimal{},
decimal.NullDecimal{},
Expand All @@ -415,7 +523,6 @@ func (cp *EVMChainProvider) CheckConfirmedTx(

if receipt.Status == gethtypes.ReceiptStatusFailed {
return NewTxResult(
txHash,
types.TX_STATUS_FAILED,
gasUsed,
gasPrice,
Expand All @@ -428,7 +535,6 @@ func (cp *EVMChainProvider) CheckConfirmedTx(
if err != nil {
err = fmt.Errorf("failed to get latest block height: %w", err)
return NewTxResult(
txHash,
types.TX_STATUS_PENDING,
decimal.NullDecimal{},
decimal.NullDecimal{},
Expand All @@ -440,7 +546,6 @@ func (cp *EVMChainProvider) CheckConfirmedTx(
// if tx block is not confirmed and waiting too long return status with timeout
if receipt.BlockNumber.Uint64() > latestBlock-cp.Config.BlockConfirmation {
return NewTxResult(
txHash,
types.TX_STATUS_PENDING,
decimal.NullDecimal{},
decimal.NullDecimal{},
Expand All @@ -449,7 +554,7 @@ func (cp *EVMChainProvider) CheckConfirmedTx(
), nil
}

return NewTxResult(txHash, types.TX_STATUS_SUCCESS, gasUsed, gasPrice, receipt.BlockNumber, ""), nil
return NewTxResult(types.TX_STATUS_SUCCESS, gasUsed, gasPrice, receipt.BlockNumber, ""), nil
}

// EstimateGasFee estimates the gas for the transaction.
Expand Down Expand Up @@ -765,98 +870,3 @@ func (cp *EVMChainProvider) queryRelayerGasFee(ctx context.Context) (*big.Int, e

return output, nil
}

func (cp *EVMChainProvider) saveUnconfirmedTransaction(
txHash string,
txStatus types.TxStatus,
packet *bandtypes.Packet,
sender string,
) error {
// db was disabled
if cp.DB == nil {
return nil
}

var signalPrices []db.SignalPrice
for _, p := range packet.SignalPrices {
signalPrices = append(signalPrices, *db.NewSignalPrice(p.SignalID, p.Price))
}

tx := db.NewUnconfirmedTransaction(
txHash,
packet.TunnelID,
packet.Sequence,
cp.ChainName,
types.ChainTypeEVM,
sender,
txStatus,
signalPrices,
)

if err := cp.DB.AddOrUpdateTransaction(tx); err != nil {
return fmt.Errorf("failed to save transaction to database: %w", err)
}

return nil
}

// saveConfirmedTransaction stores the transaction result and related metadata (e.g. gas, status, balance delta) to the database if enabled.
func (cp *EVMChainProvider) saveConfirmedTransaction(
ctx context.Context,
signerAddress string,
oldBalance *big.Int,
packet *bandtypes.Packet,
txResult TxResult,
) error {
// db was disabled
if cp.DB == nil {
return nil
}

var signalPrices []db.SignalPrice
for _, p := range packet.SignalPrices {
signalPrices = append(signalPrices, *db.NewSignalPrice(p.SignalID, p.Price))
}

var blockTimestamp time.Time
balanceDelta := decimal.NullDecimal{}

block, err := cp.Client.GetBlock(ctx, txResult.BlockNumber)
if err != nil {
return fmt.Errorf("failed to get block: %w", err)
}

blockTimestamp = time.Unix(int64(block.Time()), 0).UTC()

// Compute new balance
// Note: this may be incorrect if other transactions affected the user's balance during this period.
if oldBalance != nil {
newBalance, err := cp.Client.GetBalance(ctx, gethcommon.HexToAddress(signerAddress), txResult.BlockNumber)
if err != nil {
return fmt.Errorf("failed to get balance: %w", err)
}
diff := new(big.Int).Sub(newBalance, oldBalance)
balanceDelta = decimal.NewNullDecimal(decimal.NewFromBigInt(diff, 0))
}

tx := db.NewConfirmedTransaction(
txResult.TxHash,
packet.TunnelID,
packet.Sequence,
cp.ChainName,
types.ChainTypeEVM,
signerAddress,
txResult.Status,
txResult.GasUsed,
txResult.EffectiveGasPrice,
balanceDelta,
signalPrices,
blockTimestamp,
)

if err := cp.DB.AddOrUpdateTransaction(tx); err != nil {
return fmt.Errorf("failed to save transaction to database: %w", err)
}

return nil
}
6 changes: 0 additions & 6 deletions relayer/chains/evm/provider_eip1559_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ func (s *EIP1559ProviderTestSuite) TestRelayPacketSuccess() {
GasTipCap: s.gasInfo.GasPriorityFee,
}).Return(uint64(200_000), nil)

s.client.EXPECT().GetBalance(gomock.Any(), s.mockSignerAddress, nil).Return(big.NewInt(10000), nil)
txHash := "0xabc123"
s.client.EXPECT().BroadcastTx(gomock.Any(), gomock.Any()).Return(txHash, nil)
s.client.EXPECT().GetTxReceipt(gomock.Any(), txHash).Return(&evm.TxReceipt{
Expand Down Expand Up @@ -137,7 +136,6 @@ func (s *EIP1559ProviderTestSuite) TestRelayPacketSuccessWithoutQueryMaxGasFee()
GasTipCap: big.NewInt(3_000_000_000),
}).Return(uint64(200_000), nil)

s.client.EXPECT().GetBalance(gomock.Any(), s.mockSignerAddress, nil).Return(big.NewInt(10000), nil)
txHash := "0xabc123"
s.client.EXPECT().BroadcastTx(gomock.Any(), gomock.Any()).Return(txHash, nil)
s.client.EXPECT().GetTxReceipt(gomock.Any(), txHash).Return(&evm.TxReceipt{
Expand Down Expand Up @@ -182,8 +180,6 @@ func (s *EIP1559ProviderTestSuite) TestRelayPacketFailedBroadcastTx() {
GasTipCap: s.gasInfo.GasPriorityFee,
}).Return(uint64(200_000), nil).Times(s.chainProvider.Config.MaxRetry)

s.client.EXPECT().GetBalance(gomock.Any(), s.mockSignerAddress, nil).Return(big.NewInt(10000), nil).
Times(s.chainProvider.Config.MaxRetry)
s.client.EXPECT().
BroadcastTx(gomock.Any(), gomock.Any()).
Return("", fmt.Errorf("failed to broadcast an evm transaction")).
Expand All @@ -204,8 +200,6 @@ func (s *EIP1559ProviderTestSuite) TestRelayPacketFailedTxReceiptStatus() {
GasTipCap: s.gasInfo.GasPriorityFee,
}).Return(uint64(200_000), nil).Times(s.chainProvider.Config.MaxRetry)

s.client.EXPECT().GetBalance(gomock.Any(), s.mockSignerAddress, nil).Return(big.NewInt(10000), nil).
Times(s.chainProvider.Config.MaxRetry)
txHash := "0xabc123"
s.client.EXPECT().
BroadcastTx(gomock.Any(), gomock.Any()).
Expand Down
2 changes: 0 additions & 2 deletions relayer/chains/evm/provider_legacy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ func (s *LegacyProviderTestSuite) TestRelayPacketSuccess() {
GasPrice: s.gasInfo.GasPrice,
}).Return(uint64(200_000), nil).AnyTimes()

s.client.EXPECT().GetBalance(gomock.Any(), s.mockSignerAddress, nil).Return(big.NewInt(10000), nil)
txHash := "0xabc123"
s.client.EXPECT().BroadcastTx(gomock.Any(), gomock.Any()).Return(txHash, nil)
s.client.EXPECT().GetTxReceipt(gomock.Any(), txHash).Return(&evm.TxReceipt{
Expand Down Expand Up @@ -133,7 +132,6 @@ func (s *LegacyProviderTestSuite) TestRelayPacketSuccessWithoutQueryMaxGasFee()
GasPrice: big.NewInt(2_000_000_000),
}).Return(uint64(200_000), nil)

s.client.EXPECT().GetBalance(gomock.Any(), s.mockSignerAddress, nil).Return(big.NewInt(10000), nil)
txHash := "0xabc123"
s.client.EXPECT().BroadcastTx(gomock.Any(), gomock.Any()).Return(txHash, nil)
s.client.EXPECT().GetTxReceipt(gomock.Any(), txHash).Return(&evm.TxReceipt{
Expand Down
Loading
Loading