Skip to content
9 changes: 9 additions & 0 deletions internal/btc/rpc_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ func (s *BTCRPCService) GetBlockHashForTx(txHash chainhash.Hash) (*chainhash.Has
return blockHash, nil
}

// GetBlockTimeFromTx retrieves the block time from transaction hash
func (s *BTCRPCService) GetBlockTimeFromTx(txHash chainhash.Hash) (blockTime int64, err error) {
txRawResult, err := s.client.GetRawTransactionVerbose(&txHash)
if err != nil {
return 0, fmt.Errorf("failed to get raw transaction: %v", err)
}
return txRawResult.Blocktime, nil
}

// GetRawTransactionVerbose retrieves detailed information of a transaction
func (s *BTCRPCService) GetRawTransactionVerbose(txHash *chainhash.Hash) (*btcjson.TxRawResult, error) {
return s.client.GetRawTransactionVerbose(txHash)
Expand Down
428 changes: 271 additions & 157 deletions internal/layer2/abis/task_manager.go

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion internal/layer2/consensus_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,8 +381,19 @@ func (lis *Layer2Listener) processNewDeposit(block uint64, attributes []abcitype
log.Errorf("Abci NewDeposit, add deposit result error: %v", err)
return err
}
// get btc block timestamp from header
txHash, err := chainhash.NewHashFromStr(txid)
if err != nil {
log.Errorf("Abci NewDeposit, parse txid error: %v", err)
return err
}
blockTime, err := lis.btcRPC.GetBlockTimeFromTx(*txHash)
if err != nil {
log.Errorf("Abci NewDeposit, get block time from tx error: %v", err)
return err
}
// verify deposit task whether fund received
if err := lis.state.UpdateSafeboxTaskReceived(txid, address.Hex(), txout, amount); err != nil {
if err := lis.state.UpdateSafeboxTaskReceived(txid, address.Hex(), txout, amount, blockTime); err != nil {
log.Errorf("Abci NewDeposit, check and update safebox task deposit status error: %v", err)
return err
}
Expand Down
2 changes: 1 addition & 1 deletion internal/layer2/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ func (lis *Layer2Listener) filterEvmEvents(ctx context.Context, hash string) err
log.Errorf("failed to unpack funds received event: %v", err)
return err
}
err = lis.handleFundsReceived(fundsReceivedEvent.TaskId, fundsReceivedEvent.FundingTxHash[:], uint64(fundsReceivedEvent.TxOut))
err = lis.handleFundsReceived(fundsReceivedEvent.TaskId, fundsReceivedEvent.FundingTxHash[:], uint64(fundsReceivedEvent.TxOut), uint64(fundsReceivedEvent.TimelockEndTime))
if err != nil {
log.Errorf("failed to handle funds received event: %v", err)
return err
Expand Down
42 changes: 24 additions & 18 deletions internal/layer2/task_manager_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/goatnetwork/goat-relayer/internal/config"
"github.com/goatnetwork/goat-relayer/internal/types"
log "github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -71,7 +70,7 @@ func (lis *Layer2Listener) handleTaskCancelled(taskId *big.Int) error {
return nil
}

func (lis *Layer2Listener) handleFundsReceived(taskId *big.Int, fundingTxHash []byte, txOut uint64) error {
func (lis *Layer2Listener) handleFundsReceived(taskId *big.Int, fundingTxHash []byte, txOut, timelockEndTime uint64) error {
// handle funds received event
log.WithFields(log.Fields{
"taskId": taskId,
Expand All @@ -84,7 +83,8 @@ func (lis *Layer2Listener) handleFundsReceived(taskId *big.Int, fundingTxHash []
log.Errorf("Layer2Listener handleFundsReceived - Failed to encode funding transaction hash: %v", err)
return err
}
err = lis.state.UpdateSafeboxTaskReceivedOK(taskId.Uint64(), fundingTxHashStr, txOut)

err = lis.state.UpdateSafeboxTaskReceivedOK(taskId.Uint64(), fundingTxHashStr, txOut, timelockEndTime)
if err != nil {
log.Errorf("Layer2Listener handleFundsReceived - Failed to update safebox task: %v", err)
return err
Expand All @@ -108,13 +108,19 @@ func (lis *Layer2Listener) handleTaskCreated(ctx context.Context, taskId *big.In
return fmt.Errorf("failed to get task info: %v", err)
}

partnerInfo, err := lis.contractTaskManager.GetPartnerInfo(callOpts, task.PartnerId)
if err != nil {
log.Errorf("Layer2Listener handleTaskCreated - Failed to get partner info for partnerId %s: %v", task.PartnerId.String(), err)
return fmt.Errorf("failed to get partner info: %v", err)
}

log.WithFields(log.Fields{
"taskId": taskId,
"timelockEndTime": time.Unix(int64(task.TimelockEndTime), 0),
"deadline": time.Unix(int64(task.Deadline), 0),
"amount": task.Amount,
"btcAddress": task.BtcAddress,
"pubkey": task.BtcPubKey,
"btcAddress": partnerInfo.BtcAddress,
"pubkey": partnerInfo.BtcPubKey,
"depositAddress": task.DepositAddress,
"partnerId": task.PartnerId,
}).Info("Layer2Listener handleTaskCreated - Retrieved task details")
Expand All @@ -123,23 +129,23 @@ func (lis *Layer2Listener) handleTaskCreated(ctx context.Context, taskId *big.In
amount := new(big.Int).Div(task.Amount, big.NewInt(1e10))
log.Infof("Layer2Listener handleTaskCreated - Converted amount from contract decimal (18) to UTXO decimal (8): %v", amount)

btcAddress := make([]byte, len(task.BtcAddress[0])+len(task.BtcAddress[1]))
copy(btcAddress, task.BtcAddress[0][:])
copy(btcAddress[len(task.BtcAddress[0]):], task.BtcAddress[1][:])
btcAddress := make([]byte, len(partnerInfo.BtcAddress[0])+len(partnerInfo.BtcAddress[1]))
copy(btcAddress, partnerInfo.BtcAddress[0][:])
copy(btcAddress[len(partnerInfo.BtcAddress[0]):], partnerInfo.BtcAddress[1][:])
log.Infof("Layer2Listener handleTaskCreated - Constructed BTC address from parts: %s", hex.EncodeToString(btcAddress))
btcRefundAddress := hex.EncodeToString(btcAddress)

pubkey := make([]byte, 33)
copy(pubkey, task.BtcPubKey[0][:])
pubkey[32] = task.BtcPubKey[1][0]
copy(pubkey, partnerInfo.BtcPubKey[0][:])
pubkey[32] = partnerInfo.BtcPubKey[1][0]
log.Infof("Layer2Listener handleTaskCreated - Constructed BTC pubkey from parts: %s", hex.EncodeToString(pubkey))

timelockP2WSHAddress, witnessScript, err := types.GenerateTimeLockP2WSHAddress(pubkey, time.Unix(int64(task.TimelockEndTime), 0), types.GetBTCNetwork(config.AppConfig.BTCNetworkType))
if err != nil {
log.Errorf("Layer2Listener handleTaskCreated - Ignore invalid safebox task for generating timelock-P2WSH address from pubkey %s and timelock %d error: %v", pubkey, task.TimelockEndTime, err)
return nil
}
timelockAddress := timelockP2WSHAddress.EncodeAddress()
// timelockP2WSHAddress, witnessScript, err := types.GenerateTimeLockP2WSHAddress(pubkey, time.Unix(int64(task.TimelockEndTime), 0), types.GetBTCNetwork(config.AppConfig.BTCNetworkType))
// if err != nil {
// log.Errorf("Layer2Listener handleTaskCreated - Ignore invalid safebox task for generating timelock-P2WSH address from pubkey %s and timelock %d error: %v", pubkey, task.TimelockEndTime, err)
// return nil
// }
// timelockAddress := timelockP2WSHAddress.EncodeAddress()

err = lis.state.CreateSafeboxTask(
taskId.Uint64(),
Expand All @@ -149,9 +155,9 @@ func (lis *Layer2Listener) handleTaskCreated(ctx context.Context, taskId *big.In
amount.Uint64(),
task.DepositAddress.Hex(),
btcRefundAddress,
timelockAddress,
"",
pubkey,
witnessScript,
nil,
)
if err != nil {
log.Errorf("Layer2Listener handleTaskCreated - Failed to create safebox task: %v", err)
Expand Down
2 changes: 1 addition & 1 deletion internal/safebox/safebox.go
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ func (s *SafeboxProcessor) process(ctx context.Context) {
for _, task := range tasks {
unsignTx, messageToSign, err := s.BuildUnsignedTx(ctx, task)
if err != nil {
s.logger.Errorf("Failed to build unsigned transaction: %v", err)
s.logger.Errorf("Failed to build unsigned transaction: %v, task %v", err, task)
return
}

Expand Down
15 changes: 12 additions & 3 deletions internal/state/deposit.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"time"

"github.com/goatnetwork/goat-relayer/internal/config"
"github.com/goatnetwork/goat-relayer/internal/db"
"github.com/goatnetwork/goat-relayer/internal/types"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -397,7 +398,7 @@ func (s *State) UpdateSafeboxTaskInit(timelockAddress string, timelockTxid strin
}

// UpdateSafeboxTaskReceivedOK update safebox task after received consensus event from contract
func (s *State) UpdateSafeboxTaskReceivedOK(taskId uint64, fundingTxHash string, txOut uint64) error {
func (s *State) UpdateSafeboxTaskReceivedOK(taskId uint64, fundingTxHash string, txOut, timelockEndTime uint64) error {
s.walletMu.Lock()
defer s.walletMu.Unlock()

Expand All @@ -412,16 +413,24 @@ func (s *State) UpdateSafeboxTaskReceivedOK(taskId uint64, fundingTxHash string,
if taskDeposit.Status != db.TASK_STATUS_RECEIVED && taskDeposit.Status != db.TASK_STATUS_CREATE {
return fmt.Errorf("task deposit status is not received or create")
}
timelockP2WSHAddress, witnessScript, err := types.GenerateTimeLockP2WSHAddress(taskDeposit.Pubkey, time.Unix(int64(timelockEndTime), 0), types.GetBTCNetwork(config.AppConfig.BTCNetworkType))
if err != nil {
return fmt.Errorf("task deposit generate timelock-P2WSH address from pubkey %s and timelock %d error: %v", taskDeposit.Pubkey, timelockEndTime, err)
}
timelockAddress := timelockP2WSHAddress.EncodeAddress()
taskDeposit.FundingTxid = fundingTxHash
taskDeposit.FundingOutIndex = txOut
taskDeposit.TimelockEndTime = timelockEndTime
taskDeposit.TimelockAddress = timelockAddress
taskDeposit.WitnessScript = witnessScript
taskDeposit.Status = db.TASK_STATUS_RECEIVED_OK
taskDeposit.UpdatedAt = time.Now()
return tx.Save(&taskDeposit).Error
})
return err
}

func (s *State) UpdateSafeboxTaskReceived(txid, evmAddr string, txout uint64, amount uint64) error {
func (s *State) UpdateSafeboxTaskReceived(txid, evmAddr string, txout uint64, amount uint64, blockTime int64) error {
s.walletMu.Lock()
defer s.walletMu.Unlock()

Expand All @@ -445,7 +454,7 @@ func (s *State) UpdateSafeboxTaskReceived(txid, evmAddr string, txout uint64, am
return nil
}
// check if deadline is over
if time.Now().Unix() > int64(taskDeposit.Deadline) {
if blockTime > int64(taskDeposit.Deadline) {
// close it
taskDeposit.Status = db.TASK_STATUS_CLOSED
taskDeposit.UpdatedAt = time.Now()
Expand Down
Loading