From da9fa33fa1ccf592ad2d4893459a5d4cd56e8909 Mon Sep 17 00:00:00 2001 From: JoeGruff Date: Mon, 23 Mar 2026 14:19:05 +0900 Subject: [PATCH] eth: Warn instead of fail on audit txHash mismatch. Some RPC providers return unsigned transaction data, causing the deserialized tx hash to differ from the on-chain tx hash (coinID). The contract state is validated against the locator from the contract data, not the tx hash, so the mismatch is not a security concern. --- client/asset/eth/eth.go | 14 ++++++++++---- client/asset/eth/eth_test.go | 5 +++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/client/asset/eth/eth.go b/client/asset/eth/eth.go index 1f19bfede1..f7c802f366 100644 --- a/client/asset/eth/eth.go +++ b/client/asset/eth/eth.go @@ -5865,9 +5865,9 @@ func (eth *baseWallet) SignCoinMessage(_ asset.Coin, msg dex.Bytes) (pubkeys, si // AuditContract retrieves information about a swap contract on the // blockchain. This would be used to verify the counter-party's contract -// during a swap. coinID is expected to be the transaction id, and must -// be the same as the hash of serializedTx. contract is expected to be -// (contractVersion|secretHash) where the secretHash uniquely keys the swap. +// during a swap. coinID is expected to be the transaction id. contract +// is expected to be (contractVersion|secretHash) where the secretHash +// uniquely keys the swap. func (w *assetWallet) AuditContract(coinID, contract, serializedTx dex.Bytes, rebroadcast bool) (*asset.AuditInfo, error) { tx := new(types.Transaction) err := tx.UnmarshalBinary(serializedTx) @@ -5875,9 +5875,15 @@ func (w *assetWallet) AuditContract(coinID, contract, serializedTx dex.Bytes, re return nil, fmt.Errorf("AuditContract: failed to unmarshal transaction: %w", err) } + // The deserialized tx hash may not match coinID if the RPC provider + // returned the transaction without signature data. This is acceptable + // because the contract state is validated against the locator from the + // contract data, not the tx hash. Use the coinID as the canonical hash. txHash := tx.Hash() if !bytes.Equal(coinID, txHash[:]) { - return nil, fmt.Errorf("AuditContract: coin id != txHash - coin id: %x, txHash: %s", coinID, tx.Hash()) + w.log.Warnf("AuditContract: coinID %x differs from deserialized txHash %s (unsigned tx from RPC?). Proceeding with contract validation.", + []byte(coinID), txHash) + copy(txHash[:], coinID) } version, locator, err := dexeth.DecodeContractData(contract) diff --git a/client/asset/eth/eth_test.go b/client/asset/eth/eth_test.go index 809af0f4c5..684bd04185 100644 --- a/client/asset/eth/eth_test.go +++ b/client/asset/eth/eth_test.go @@ -4725,8 +4725,9 @@ func testAuditContract(t *testing.T, assetID uint32) { Value: dexeth.GweiToWei(1), }, }, - differentHash: true, - wantErr: true, + differentHash: true, + wantRecipient: testAddressA.Hex(), + wantExpiration: now, }, { name: "contract is invalid versioned bytes",