diff --git a/app/arbiter/arbiter/arbiter.go b/app/arbiter/arbiter/arbiter.go index 41fe252..5f9a2c8 100644 --- a/app/arbiter/arbiter/arbiter.go +++ b/app/arbiter/arbiter/arbiter.go @@ -9,6 +9,7 @@ import ( "encoding/gob" "encoding/hex" "log" + "math/big" "os" "path/filepath" "strings" @@ -95,6 +96,7 @@ func NewArbiter(ctx context.Context, config *config.Config, password string) *Ar func (v *Arbiter) Start() { if v.config.Signer { go v.processArbiterSig() + go v.processManualConfirm() } if v.config.Listener { @@ -113,6 +115,124 @@ func (v *Arbiter) listenESCContract() { v.escNode.Start(startHeight) } +func (v *Arbiter) processManualConfirm() { + g.Log().Info(v.ctx, "process manually confirm start") + + for { + // get all deploy file + files, err := os.ReadDir(v.config.LoanManuallyConfirmedPath) + if err != nil { + g.Log().Error(v.ctx, "read dir error", err) + continue + } + + for _, file := range files { + // read file + filePath := v.config.LoanManuallyConfirmedPath + "/" + file.Name() + fileContent, err := os.ReadFile(filePath) + if err != nil { + g.Log().Error(v.ctx, "read file error", err) + continue + } + logEvt, err := v.decodeLogEvtByFileContent(fileContent) + if err != nil { + g.Log().Error(v.ctx, "decodeLogEvtByFileContent error", err) + v.moveToDirectory(filePath, v.config.LoanNeedSignFailedPath+"/"+file.Name()+".mcFailed") + v.logger.Println("[ERR] MCFM: decode event failed, file:", filePath) + continue + } + var ev = make(map[string]interface{}) + err = v.escNode.Order_abi.UnpackIntoMap(ev, "ConfirmTransferToLenderEvent", logEvt.EventData) + if err != nil { + g.Log().Error(v.ctx, "UnpackIntoMap error", err) + v.moveToDirectory(filePath, v.config.LoanNeedSignFailedPath+"/"+file.Name()+".mcFailed") + v.logger.Println("[ERR] MCFM: unpack event into map failed, file:", filePath) + continue + } + g.Log().Info(v.ctx, "ev", ev) + orderId := logEvt.Topics[1] + btcTxHash := logEvt.Topics[2] + arbiterAddresss := common.BytesToAddress(logEvt.Topics[3][:]) + fee := ev["arbitratorBtcFee"].(*big.Int) + + g.Log().Info(v.ctx, "orderId", hex.EncodeToString(orderId[:])) + g.Log().Info(v.ctx, "btcTxHash", hex.EncodeToString(btcTxHash[:])) + g.Log().Info(v.ctx, "arbiterAddresss", arbiterAddresss.String()) + + // get btc arbiter BTC address + arbitratorBTCAddress, err := v.escNode.GetArbiterBTCAddress(arbiterAddresss) + if err != nil { + g.Log().Error(v.ctx, "GetArbiterOperatorAddress error", err) + v.moveToDirectory(filePath, v.config.LoanNeedSignFailedPath+"/"+file.Name()+".mcGetArbiterOperatorAddressFailed") + v.logger.Println("[ERR] MCFM: get arbiter operator address failed, block:", logEvt.Block, "tx:", logEvt.TxHash) + continue + } + + btcTx, err := v.mempoolAPI.GetRawTransaction(hex.EncodeToString(btcTxHash[:])) + if err != nil { + g.Log().Error(v.ctx, "GetRawTransaction error", err) + // v.moveToDirectory(filePath, v.config.LoanNeedSignFailedPath+"/"+file.Name()+".GetRawTransactionFailed") + v.logger.Println("[ERR] MCFM: get raw tx failed, block:", logEvt.Block, "tx:", logEvt.TxHash) + continue + } + // check if have enough fee + realFee := int64(0) + // feeOutputIndex := 0 + for _, vout := range btcTx.Vout { + if vout.Value < 546 { + g.Log().Error(v.ctx, "invalid tx outputs with dust value") + v.moveToDirectory(filePath, v.config.LoanNeedSignFailedPath+"/"+file.Name()+".mcInvalidTxOutputs") + v.logger.Println("[ERR] MCFM: invalid tx outputs with dust value, block:", logEvt.Block, "tx:", logEvt.TxHash) + continue + } + utxoAddr := vout.ScriptpubkeyAddress + if utxoAddr == arbitratorBTCAddress { + g.Log().Error(v.ctx, "invalid utxo address:", utxoAddr, "need to be:", arbitratorBTCAddress) + v.moveToDirectory(filePath, v.config.LoanNeedSignFailedPath+"/"+file.Name()+".mcInvalidUtxoAddress") + v.logger.Println("[ERR] MCFM: invalid utxo address, block:", logEvt.Block, "tx:", logEvt.TxHash) + continue + } + if vout.Value > 0 { + realFee += vout.Value + // feeOutputIndex = i + break + } + } + // check fee rate + // preAmount := int64(btcTx.Vout[1-feeOutputIndex].Value) + // feeRate, err := v.escNode.GetManuallyConfirmedBTCFeeRate(&arbiterAddresss) + // if err != nil { + // g.Log().Error(v.ctx, "GetManuallyConfirmedBTCFeeRate error", err) + // v.moveToDirectory(filePath, v.config.LoanNeedSignFailedPath+"/"+file.Name()+".mcGetManuallyConfirmedBTCFeeRate") + // v.logger.Println("[ERR] MCFM: get fee rate failed, block:", logEvt.Block, "tx:", logEvt.TxHash) + // continue + // } + // arbiterFee := preAmount * feeRate.Int64() / 10000 + if realFee < fee.Int64() { + g.Log().Error(v.ctx, "invalid fee:", realFee, "need to be:", fee.Int64()) + v.moveToDirectory(filePath, v.config.LoanNeedSignFailedPath+"/"+file.Name()+".mcInvalidFeeRate") + v.logger.Println("[ERR] MCFM: invalid fee rate, block:", logEvt.Block, "tx:", logEvt.TxHash) + continue + } + + // manually confirm to contract + orderContarctAddress := common.BytesToAddress(orderId[:]) + txhash, err := v.escNode.SubmitManuallyConfirm(&orderContarctAddress) + g.Log().Notice(v.ctx, "SubmitManuallyConfirmed", "txhash ", txhash.String(), " error ", err) + if err != nil { + v.moveToDirectory(filePath, v.config.LoanNeedSignFailedPath+"/"+file.Name()+".mcSubmitFailed") + v.logger.Println("[ERR] MCFM: SubmitManuallyConfirm failed, block:", logEvt.Block, "tx:", logEvt.TxHash, "err:", err.Error()) + } else { + v.moveToDirectory(filePath, v.config.LoanNeedSignSignedPath+"/"+file.Name()+".mcSucceed") + v.logger.Println("[INF] MCFM: SubmitManuallyConfirmed succeed, block:", logEvt.Block, "tx:", logEvt.TxHash) + } + } + + // sleep 10s to check and process next files + time.Sleep(time.Second * 10) + } +} + func (v *Arbiter) processArbiterSig() { g.Log().Info(v.ctx, "processArbiterSignature start") @@ -387,6 +507,20 @@ func createDir(config *config.Config) error { } } + if !gfile.Exists(config.LoanSignedEventPath) { + err := gfile.Mkdir(config.LoanSignedEventPath) + if err != nil { + return err + } + } + + if !gfile.Exists(config.LoanManuallyConfirmedPath) { + err := gfile.Mkdir(config.LoanManuallyConfirmedPath) + if err != nil { + return err + } + } + if !gfile.Exists(config.LoanLogPath) { err := gfile.Mkdir(config.LoanLogPath) if err != nil { diff --git a/app/arbiter/config/config.go b/app/arbiter/config/config.go index a6ab300..70cf349 100644 --- a/app/arbiter/config/config.go +++ b/app/arbiter/config/config.go @@ -13,6 +13,7 @@ type Config struct { ESCArbiterContractAddress string ESCArbiterManagerContractAddress string ESCConfigManagerContractAddress string + ESCOrderManagerContractAddress string ESCArbiterAddress string DataDir string @@ -27,6 +28,8 @@ type Config struct { LoanNeedSignFailedPath string // loan signed path LoanNeedSignSignedPath string + // loan manually confirmed path + LoanManuallyConfirmedPath string // loan logs path LoanLogPath string diff --git a/app/arbiter/contract/contract_abi/arbiterManagerABI.go b/app/arbiter/contract/contract_abi/arbiterManagerABI.go index 8efe970..4d66ede 100644 --- a/app/arbiter/contract/contract_abi/arbiterManagerABI.go +++ b/app/arbiter/contract/contract_abi/arbiterManagerABI.go @@ -3,6 +3,32 @@ package contract_abi const ArbiterManagerABI = `[ +{ + "inputs": [ + { + "internalType": "address", + "name": "arbitrator", + "type": "address" + } + ], + "name": "getArbitratorInfoExt", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "currentBTCFeeRate", + "type": "uint256" + } + ], + "internalType": "struct DataTypes.ArbitratorInfoExt", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "stateMutability": "nonpayable", diff --git a/app/arbiter/contract/contract_abi/orderManagerABI.go b/app/arbiter/contract/contract_abi/orderManagerABI.go new file mode 100644 index 0000000..c369bd1 --- /dev/null +++ b/app/arbiter/contract/contract_abi/orderManagerABI.go @@ -0,0 +1,50 @@ +// Copyright (c) 2025 The bel2 developers + +package contract_abi + +const OrderEventManagerABI = `[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "order", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "arbitrator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "txIndex", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "arbitratorBtcFee", + "type": "uint256" + } + ], + "name": "ConfirmTransferToLenderEvent", + "type": "event" + }, + { + "inputs": [], + "name": "confirmTransferToArbitrator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ]` diff --git a/app/arbiter/contract/contract_listener.go b/app/arbiter/contract/contract_listener.go index 334c05d..cb2403d 100644 --- a/app/arbiter/contract/contract_listener.go +++ b/app/arbiter/contract/contract_listener.go @@ -17,6 +17,8 @@ import ( type ContractListener struct { loanContract common.Address + orderContract common.Address + queryClient *CrossClient listeneTopics []common.Hash ctx context.Context @@ -24,12 +26,13 @@ type ContractListener struct { } func NewListener(ctx context.Context, client *CrossClient, - loanContract common.Address, chan_event chan *events.ContractLogEvent) (*ContractListener, error) { + loanContract, orderContract common.Address, chan_event chan *events.ContractLogEvent) (*ContractListener, error) { c := &ContractListener{ - queryClient: client, - loanContract: loanContract, - ctx: ctx, - chan_events: chan_event, + queryClient: client, + loanContract: loanContract, + orderContract: orderContract, + ctx: ctx, + chan_events: chan_event, } c.listeneTopics = make([]common.Hash, 0) return c, nil @@ -48,7 +51,9 @@ func (c *ContractListener) Start(startHeight uint64) (uint64, error) { distance := uint64(10000) toBlock := startHeight - loanQuery := c.queryClient.BuildQuery(c.loanContract, c.listeneTopics, nil, nil) + // addresses := []common.Address{c.orderContract} + addresses := []common.Address{c.loanContract, c.orderContract} + loanQuery := c.queryClient.BuildQuery(addresses, c.listeneTopics, nil, nil) for i := startHeight; i <= endBlock-confirmBlocksCount; i += distance { if i+distance < endBlock { toBlock = i + distance diff --git a/app/arbiter/contract/contract_main.go b/app/arbiter/contract/contract_main.go index b747aa1..dfaf190 100644 --- a/app/arbiter/contract/contract_main.go +++ b/app/arbiter/contract/contract_main.go @@ -5,6 +5,7 @@ package contract import ( "context" "encoding/json" + "errors" "fmt" "log" "math/big" @@ -31,10 +32,12 @@ type ArbitratorContract struct { Loan_abi abi.ABI Arbiter_manager_abi abi.ABI Arbiter_config_abi abi.ABI + Order_abi abi.ABI loanContract *common.Address arbiterManagerContract *common.Address configManagerContract *common.Address + orderManagerContract *common.Address cfg *config.Config logger *log.Logger @@ -68,9 +71,10 @@ func New(ctx context.Context, cfg *config.Config, privateKey string, logger *log loanAddress := common.HexToAddress(cfg.ESCArbiterContractAddress) arbiterManagerAddress := common.HexToAddress(cfg.ESCArbiterManagerContractAddress) configManagerAddress := common.HexToAddress(cfg.ESCConfigManagerContractAddress) + orderManangerAddress := common.HexToAddress(cfg.ESCOrderManagerContractAddress) eventChan := make(chan *events.ContractLogEvent, 3) chan_interrupt := make(chan struct{}) - listener, err := NewListener(ctx, client, loanAddress, eventChan) + listener, err := NewListener(ctx, client, loanAddress, orderManangerAddress, eventChan) if err != nil { return nil, err } @@ -87,7 +91,10 @@ func New(ctx context.Context, cfg *config.Config, privateKey string, logger *log if err != nil { return nil, err } - + orderABI, err := abi.JSON(strings.NewReader(contract_abi.OrderEventManagerABI)) + if err != nil { + return nil, err + } submitter, err := NewSubmitter(ctx, client, privateKey) if err != nil { return nil, err @@ -101,9 +108,11 @@ func New(ctx context.Context, cfg *config.Config, privateKey string, logger *log Loan_abi: loanABI, Arbiter_manager_abi: arbiterManagerABI, Arbiter_config_abi: arbiterConfigABI, + Order_abi: orderABI, loanContract: &loanAddress, arbiterManagerContract: &arbiterManagerAddress, configManagerContract: &configManagerAddress, + orderManagerContract: &orderManangerAddress, cfg: cfg, logger: logger, } @@ -163,6 +172,9 @@ func (c *ArbitratorContract) parseContractEvent(event *events.ContractLogEvent) } else if event.Topics[0].Cmp(events.ArbitrationResultSubmitted) == 0 { err = c.parseTransferSignedEvent(event) fmt.Println("ArbitrationResultSubmitted >>>>>>>>>>>>>>>> received") + } else if event.Topics[0].Cmp(events.ConfirmTransferToLenderEvent) == 0 { + err = c.parseConfirmTransferToLenderEvent(event) + fmt.Println("ConfirmTransferToLenderEvent >>>>>>>>>>>>>>>> received") } return err } @@ -209,6 +221,47 @@ func (c *ArbitratorContract) parseTransferSignedEvent(event *events.ContractLogE return err } +func (c *ArbitratorContract) parseConfirmTransferToLenderEvent(event *events.ContractLogEvent) error { + var ev = make(map[string]interface{}) + err := c.Order_abi.UnpackIntoMap(ev, "ConfirmTransferToLenderEvent", event.EventData) + if err != nil { + g.Log().Error(c.ctx, "parseLoanLenderManuallyConfirmedEvent UnpackIntoMap error", err) + return err + } + // get btc address + g.Log().Info(c.ctx, "arbiter:", event.Topics[3]) + if len(event.Topics) < 4 { + return errors.New("invalid event count") + } + arbiterAddress, err := c.getArbiterOperatorAddress(common.BytesToAddress(event.Topics[3][:])) + if err != nil { + g.Log().Error(c.ctx, "GetArbiterBTCAddress error", err) + return err + } + if arbiterAddress.String() != c.cfg.ESCArbiterAddress { + g.Log().Debug(c.ctx, "find ConfirmTransferToLenderEvent, but not mine") + return nil + } + c.logger.Println("[INF] EVENT: ConfirmTransferToLenderEvent, block:", event.Block, "tx:", event.TxHash) + + path := c.cfg.LoanManuallyConfirmedPath + "/" + event.TxHash.String() + err = events.SaveContractEvent(path, event) + if err != nil { + g.Log().Error(c.ctx, "SaveContractEvent error", err) + } + g.Log().Noticef(c.ctx, "find btc tx lender manually confirmed:%s ", event.TxHash.String()) + return err +} + +func (c *ArbitratorContract) SubmitManuallyConfirm(orderContractAddress *common.Address) (common.Hash, error) { + input, err := c.Order_abi.Pack("confirmTransferToArbitrator") + if err != nil { + return common.Hash{}, err + } + hash, err := c.submitter.MakeAndSendContractTransaction(input, orderContractAddress, big.NewInt(0)) + return hash, err +} + func (c *ArbitratorContract) SubmitArbitrationSignature(rawData []byte, queryId [32]byte) (common.Hash, error) { input, err := c.Loan_abi.Pack("submitArbitration", queryId, rawData) if err != nil { @@ -282,3 +335,32 @@ func (c *ArbitratorContract) GetArbitrationBTCFeeRate() (*big.Int, error) { } return big.NewInt(0).SetBytes(result), nil } + +type ManuallyConfirmedBTCFeeRate struct { + CurrentBTCFeeRate *big.Int `json:"currentBTCFeeRate"` +} + +func (c *ArbitratorContract) GetManuallyConfirmedBTCFeeRate(arbiter *common.Address) (*big.Int, error) { + input, err := c.Arbiter_manager_abi.Pack("getArbitratorInfoExt", arbiter) + if err != nil { + return nil, err + } + msg := ethereum.CallMsg{From: common.Address{}, To: c.configManagerContract, Data: input} + result, err := c.submitter.CallContract(context.TODO(), msg, nil) + if err != nil { + return nil, err + } + ev, err := c.Arbiter_manager_abi.Unpack("getArbitratorInfoExt", result) + if err != nil || len(ev) == 0 { + g.Log().Error(c.ctx, "parse ArbitratorInfo UnpackIntoMap error", err) + return nil, err + } + info := ManuallyConfirmedBTCFeeRate{} + data, err := json.Marshal(ev[0]) + if err != nil { + return nil, err + } + json.Unmarshal(data, &info) + g.Log().Info(c.ctx, "ManuallyConfirmedBTCFeeRate", info.CurrentBTCFeeRate, "result:", result) + return info.CurrentBTCFeeRate, nil +} diff --git a/app/arbiter/contract/crossClient.go b/app/arbiter/contract/crossClient.go index a8107a4..3d11b13 100644 --- a/app/arbiter/contract/crossClient.go +++ b/app/arbiter/contract/crossClient.go @@ -62,11 +62,11 @@ func (c *CrossClient) GetLatestHeight() (uint64, error) { return head.Number.Uint64(), nil } -func (c *CrossClient) BuildQuery(contractAddress common.Address, topics []common.Hash, startBlock *big.Int, endBlock *big.Int) ethereum.FilterQuery { +func (c *CrossClient) BuildQuery(contractAddresses []common.Address, topics []common.Hash, startBlock *big.Int, endBlock *big.Int) ethereum.FilterQuery { query := ethereum.FilterQuery{ FromBlock: startBlock, ToBlock: endBlock, - Addresses: []common.Address{contractAddress}, + Addresses: contractAddresses, Topics: [][]common.Hash{topics}, } return query diff --git a/app/arbiter/contract/events/log_event.go b/app/arbiter/contract/events/log_event.go index ccebdc6..28eed71 100644 --- a/app/arbiter/contract/events/log_event.go +++ b/app/arbiter/contract/events/log_event.go @@ -10,4 +10,7 @@ var ( ArbitrationRequested = crypto.Keccak256Hash([]byte("ArbitrationRequested(bytes32,address,address,bytes,bytes,address)")) ArbitrationResultSubmitted = crypto.Keccak256Hash([]byte("ArbitrationResultSubmitted(bytes,bytes32)")) + + // ConfirmTransferToLenderEvent(address indexed order, bytes32 indexed txId, address indexed arbitrator, uint32 txIndex, uint256 arbitratorBtcFee); + ConfirmTransferToLenderEvent = crypto.Keccak256Hash([]byte("ConfirmTransferToLenderEvent(address,bytes32,address,uint32,uint256)")) ) diff --git a/app/arbiter/main.go b/app/arbiter/main.go index 57c8052..1caee62 100644 --- a/app/arbiter/main.go +++ b/app/arbiter/main.go @@ -106,6 +106,11 @@ func initConfig(ctx context.Context) *config.Config { g.Log().Error(ctx, "get escArbiterAddress config err:", err) os.Exit(1) } + escOrderManagerAddress, err := g.Cfg().Get(ctx, "arbiter.escOrderManagerContractAddress") + if err != nil { + g.Log().Error(ctx, "get escOrderManagerAddress config err:", err) + os.Exit(1) + } gDataPath, err := g.Cfg().Get(ctx, "arbiter.dataPath") if err != nil { g.Log().Error(ctx, "get dataPath config err:", err) @@ -137,6 +142,7 @@ func initConfig(ctx context.Context) *config.Config { loanNeedSignReqPath := gfile.Join(loanPath, "request/") loanNeedSignFailedPath := gfile.Join(loanPath, "failed/") loanNeedSignSignedPath := gfile.Join(loanPath, "signed/") + loanManuallyConfirmedPath := gfile.Join(loanPath, "confirmed/") LoanSignedEventPath := gfile.Join(dataPath, "loan_signed_event/") return &config.Config{ @@ -148,17 +154,19 @@ func initConfig(ctx context.Context) *config.Config { ESCArbiterContractAddress: escArbiterContractAddress.String(), ESCArbiterManagerContractAddress: escArbiterManagerAddress.String(), ESCConfigManagerContractAddress: escConfigManagerAddress.String(), + ESCOrderManagerContractAddress: escOrderManagerAddress.String(), ESCArbiterAddress: escArbiterAddress.String(), DataDir: dataPath, EscKeyFilePath: escKeyFilePath, ArbiterKeyFilePath: arbiterKeyFilePath, - LoanNeedSignReqPath: loanNeedSignReqPath, - LoanNeedSignFailedPath: loanNeedSignFailedPath, - LoanNeedSignSignedPath: loanNeedSignSignedPath, - LoanSignedEventPath: LoanSignedEventPath, - LoanLogPath: logPath, + LoanNeedSignReqPath: loanNeedSignReqPath, + LoanNeedSignFailedPath: loanNeedSignFailedPath, + LoanNeedSignSignedPath: loanNeedSignSignedPath, + LoanManuallyConfirmedPath: loanManuallyConfirmedPath, + LoanSignedEventPath: LoanSignedEventPath, + LoanLogPath: logPath, } } diff --git a/app/arbiter/manifest/config/config.yaml b/app/arbiter/manifest/config/config.yaml index 4922cba..3b910d2 100644 --- a/app/arbiter/manifest/config/config.yaml +++ b/app/arbiter/manifest/config/config.yaml @@ -7,10 +7,11 @@ arbiter: listener: true signer: true network: "mainnet" - escStartHeight: 28804848 - escArbiterContractAddress: "0xA10b92006743Ef3B12077da67e465963743b03D3" + escStartHeight: 29632056 + escArbiterContractAddress: "0x1f872A1fBc02A38B087DE6af2176ab0BC63a53d0" escArbiterManagerContractAddress: "0x9963b5214434776D043A4e98Bc7f33321F6aaCfc" escConfigManagerContractAddress: "0x4421c63241A262C423277FFA82C376953072d25f" - escArbiterAddress: "0x0262aB0ED65373cC855C34529fDdeAa0e686D913" + escOrderManagerContractAddress: "0x1f00FEE6B4dbbc97B8c035E1813772C38E64081d" + escArbiterAddress: "0x3Cf0BB575527cACf9e274a6eE879f876Dae0BC40" dataPath: "./app/arbiter/data" keyFilePath: "./app/arbiter/data/keys/"