time index shift (#450)

This commit is contained in:
Dmitry Holodov
2023-05-02 10:52:28 -05:00
committed by GitHub
parent c131112da1
commit df057ac5cd
37 changed files with 248 additions and 244 deletions

View File

@@ -292,7 +292,7 @@ func cliApp() *cli.App {
},
{
Name: "set-swap-timeout",
Usage: "Set the duration between swap initiation and t0 and t0 and t1, in seconds",
Usage: "Set the duration between swap initiation and t1 and t1 and t2, in seconds",
Action: runSetSwapTimeout,
Flags: []cli.Flag{
&cli.UintFlag{
@@ -311,7 +311,7 @@ func cliApp() *cli.App {
},
{
Name: "get-swap-timeout",
Usage: "Get the duration between swap initiation and t0 and t0 and t1, in seconds",
Usage: "Get the duration between swap initiation and t1 and t1 and t2, in seconds",
Action: runGetSwapTimeout,
Flags: []cli.Flag{
swapdPortFlag,
@@ -799,9 +799,9 @@ func runGetOngoingSwap(ctx *cli.Context) error {
fmt.Printf("Exchange Rate: %s ETH/XMR\n", info.ExchangeRate)
fmt.Printf("Status: %s\n", info.Status)
fmt.Printf("Time status was last updated: %s\n", info.LastStatusUpdateTime.Format(common.TimeFmtSecs))
if info.Timeout0 != nil && info.Timeout1 != nil {
fmt.Printf("First timeout: %s\n", info.Timeout0.Format(common.TimeFmtSecs))
fmt.Printf("Second timeout: %s\n", info.Timeout1.Format(common.TimeFmtSecs))
if info.Timeout1 != nil && info.Timeout2 != nil {
fmt.Printf("First timeout: %s\n", info.Timeout1.Format(common.TimeFmtSecs))
fmt.Printf("Second timeout: %s\n", info.Timeout2.Format(common.TimeFmtSecs))
}
fmt.Printf("Estimated time to completion: %s\n", info.EstimatedTimeToCompletion)
}
@@ -1065,8 +1065,8 @@ func runGetContractSwapInfo(ctx *cli.Context) error {
fmt.Printf("\tClaimer: %s\n", resp.Swap.Claimer)
fmt.Printf("\tPubKeyClaim: %x\n", resp.Swap.PubKeyClaim)
fmt.Printf("\tPubKeyRefund: %x\n", resp.Swap.PubKeyRefund)
fmt.Printf("\tTimeout0: %s\n", resp.Swap.Timeout0)
fmt.Printf("\tTimeout1: %s\n", resp.Swap.Timeout1)
fmt.Printf("\tTimeout2: %s\n", resp.Swap.Timeout2)
fmt.Printf("\tAsset: %s\n", resp.Swap.Asset)
fmt.Printf("\tValue: %s\n", resp.Swap.Value)
fmt.Printf("\tNonce: %s\n", resp.Swap.Nonce)

View File

@@ -103,7 +103,7 @@ func StagenetConfig() *Config {
Port: 38081,
},
},
SwapCreatorAddr: ethcommon.HexToAddress("0x90119FA88abE871B3e26DF3a57C29A450f006065"),
SwapCreatorAddr: ethcommon.HexToAddress("0xbf2B7a6dCE5598Cf002B3507a8D62cf2C35cE5c6"),
Bootnodes: []string{
"/ip4/134.122.115.208/tcp/9900/p2p/12D3KooWDqCzbjexHEa8Rut7bzxHFpRMZyDRW1L6TGkL1KY24JH5",
"/ip4/143.198.123.27/tcp/9900/p2p/12D3KooWSc4yFkPWBFmPToTMbhChH3FAgGH96DNzSg5fio1pQYoN",

View File

@@ -188,7 +188,7 @@ func TestRunSwapDaemon_SwapBobHasNoEth_AliceRelaysClaim(t *testing.T) {
// Tests the scenario where Bob has no ETH, he can't find an advertised relayer,
// and Alice does not have enough ETH to relay his claim. The end result should
// be a refund. Note that this test has a long pause, as the refund cannot
// happen until T1 expires.
// happen until T2 expires.
func TestRunSwapDaemon_NoRelayersAvailable_Refund(t *testing.T) {
minXMR := coins.StrToDecimal("1")
maxXMR := minXMR

View File

@@ -57,8 +57,8 @@ func TestDatabase_OfferTable(t *testing.T) {
MoneroStartHeight: 12345,
StartTime: time.Now().Add(-30 * time.Minute),
EndTime: nil,
Timeout0: nil,
Timeout1: nil,
Timeout2: nil,
}
err = db.PutSwap(infoA)
@@ -123,8 +123,8 @@ func TestDatabase_GetAllOffers_InvalidEntry(t *testing.T) {
Status: types.ExpectingKeys,
LastStatusUpdateTime: time.Now(),
MoneroStartHeight: 12345,
Timeout0: nil,
Timeout1: nil,
Timeout2: nil,
StartTime: time.Now().Add(-30 * time.Minute),
EndTime: nil,
}
@@ -182,8 +182,8 @@ func TestDatabase_SwapTable(t *testing.T) {
require.NoError(t, err)
startTime := time.Now().Add(-2 * time.Minute)
timeout0 := time.Now().Add(30 * time.Minute)
timeout1 := time.Now().Add(60 * time.Minute)
timeout1 := time.Now().Add(30 * time.Minute)
timeout2 := time.Now().Add(60 * time.Minute)
infoA := &swap.Info{
Version: swap.CurInfoVersion,
@@ -199,8 +199,8 @@ func TestDatabase_SwapTable(t *testing.T) {
MoneroStartHeight: 12345,
StartTime: startTime,
EndTime: nil,
Timeout0: &timeout0,
Timeout1: &timeout1,
Timeout2: &timeout2,
}
err = db.PutSwap(infoA)
require.NoError(t, err)
@@ -219,8 +219,8 @@ func TestDatabase_SwapTable(t *testing.T) {
MoneroStartHeight: 12345,
StartTime: startTime,
EndTime: nil,
Timeout0: &timeout0,
Timeout1: &timeout1,
Timeout2: &timeout2,
}
err = db.PutSwap(infoB)
require.NoError(t, err)
@@ -242,8 +242,8 @@ func TestDatabase_GetAllSwaps_InvalidEntry(t *testing.T) {
require.NoError(t, err)
startTime := time.Now().Add(-2 * time.Minute)
timeout0 := time.Now().Add(30 * time.Minute)
timeout1 := time.Now().Add(60 * time.Minute)
timeout1 := time.Now().Add(30 * time.Minute)
timeout2 := time.Now().Add(60 * time.Minute)
goodInfo := &swap.Info{
Version: swap.CurInfoVersion,
@@ -259,8 +259,8 @@ func TestDatabase_GetAllSwaps_InvalidEntry(t *testing.T) {
MoneroStartHeight: 12345,
StartTime: startTime,
EndTime: nil,
Timeout0: &timeout0,
Timeout1: &timeout1,
Timeout2: &timeout2,
}
err = db.PutSwap(goodInfo)
require.NoError(t, err)
@@ -306,8 +306,8 @@ func TestDatabase_SwapTable_Update(t *testing.T) {
id := types.Hash{0x1}
startTime := time.Now().Add(-2 * time.Minute)
timeout0 := time.Now().Add(30 * time.Minute)
timeout1 := time.Now().Add(60 * time.Minute)
timeout1 := time.Now().Add(30 * time.Minute)
timeout2 := time.Now().Add(60 * time.Minute)
infoA := &swap.Info{
Version: swap.CurInfoVersion,
@@ -323,8 +323,8 @@ func TestDatabase_SwapTable_Update(t *testing.T) {
MoneroStartHeight: 12345,
StartTime: startTime,
EndTime: nil,
Timeout0: &timeout0,
Timeout1: &timeout1,
Timeout2: &timeout2,
}
err = db.PutSwap(infoA)
require.NoError(t, err)

View File

@@ -42,8 +42,8 @@ func TestRecoveryDB_ContractSwapInfo(t *testing.T) {
Claimer: ethcommon.HexToAddress("0xbe0eb53f46cd790cd13851d5eff43d12404d33e8"),
PubKeyClaim: ethcommon.HexToHash("0x5ab9467e70d4e98567991f0179d1f82a3096ed7973f7aff9ea50f649cafa88b9"),
PubKeyRefund: ethcommon.HexToHash("0x4897bc3b9e02c2a8cd6353b9b29377157bf2694daaf52b59c0b42daa39877f14"),
Timeout0: big.NewInt(1672531200),
Timeout1: big.NewInt(1672545600),
Timeout1: big.NewInt(1672531200),
Timeout2: big.NewInt(1672545600),
Asset: types.EthAssetETH.Address(),
Value: big.NewInt(9876),
Nonce: big.NewInt(1234),
@@ -59,8 +59,8 @@ func TestRecoveryDB_ContractSwapInfo(t *testing.T) {
"claimer": "0xbe0eb53f46cd790cd13851d5eff43d12404d33e8",
"pubKeyClaim": "0x5ab9467e70d4e98567991f0179d1f82a3096ed7973f7aff9ea50f649cafa88b9",
"pubKeyRefund": "0x4897bc3b9e02c2a8cd6353b9b29377157bf2694daaf52b59c0b42daa39877f14",
"timeout0": 1672531200,
"timeout1": 1672545600,
"timeout1": 1672531200,
"timeout2": 1672545600,
"asset": "0x0000000000000000000000000000000000000000",
"value": 9876,
"nonce": 1234
@@ -156,8 +156,8 @@ func TestRecoveryDB_DeleteSwap(t *testing.T) {
Claimer: ethcommon.HexToAddress("0xbe0eb53f46cd790cd13851d5eff43d12404d33e8"),
PubKeyClaim: ethcommon.HexToHash("0x5ab9467e70d4e98567991f0179d1f82a3096ed7973f7aff9ea50f649cafa88b9"),
PubKeyRefund: ethcommon.HexToHash("0x4897bc3b9e02c2a8cd6353b9b29377157bf2694daaf52b59c0b42daa39877f14"),
Timeout0: big.NewInt(1672531200),
Timeout1: big.NewInt(1672545600),
Timeout1: big.NewInt(1672531200),
Timeout2: big.NewInt(1672545600),
Asset: types.EthAssetETH.Address(),
Value: big.NewInt(9876),
Nonce: big.NewInt(1234),

View File

@@ -328,7 +328,7 @@ curl -s -X POST http://127.0.0.1:5000 -H 'Content-Type: application/json' -d \
### `personal_setSwapTimeout`
Sets the duration between swap initiation and t0 and t0 and t1, in seconds.
Sets the duration between swap initiation and t1 and t1 and t2, in seconds.
Parameters:
- `duration`: duration of timeout, in seconds
@@ -344,7 +344,7 @@ curl -X POST http://127.0.0.1:5002 -d '{"jsonrpc":"2.0","id":"0","method":"perso
### `personal_getSwapTimeout`
Returns the duration between swap initiation and t0 and t0 and t1, in seconds
Returns the duration between swap initiation and t1 and t1 and t2, in seconds
Parameters:
- none
@@ -398,8 +398,8 @@ Each items in `swaps` contains:
- `exchangeRate`: the exchange rate of the swap, expressed in a ratio of XMR/ETH.
- `status`: the swap's status.
- `startTime`: the start time of the swap (in RFC 3339 format).
- `timeout0`: the time at which the ETH-taker can always claim ETH, and the ETH-maker can no longer refund.
- `timeout1`: the time at which the ETH-taker can no longer claim ETH, and the ETH-maker is able to refund.
- `timeout1`: the time at which the ETH-taker can always claim ETH, and the ETH-maker can no longer refund.
- `timeout2`: the time at which the ETH-taker can no longer claim ETH, and the ETH-maker is able to refund.
Example:
```bash
@@ -420,8 +420,8 @@ curl -s -X POST http://127.0.0.1:5000 -H 'Content-Type: application/json' -d \
"exchangeRate": "0.05",
"status": "ETHLocked",
"startTime": "2023-03-18T16:47:50.598029743-04:00",
"timeout0": "2023-03-18T16:49:55-04:00",
"timeout1": "2023-03-18T16:51:55-04:00"
"timeout1": "2023-03-18T16:49:55-04:00",
"timeout2": "2023-03-18T16:51:55-04:00"
}
]
},
@@ -447,8 +447,8 @@ curl -s -X POST http://127.0.0.1:5000 -H 'Content-Type: application/json' -d \
"exchangeRate": "1",
"status": "ETHLocked",
"startTime": "2023-03-18T16:52:56.304958446-04:00",
"timeout0": "2023-03-18T16:55:01-04:00",
"timeout1": "2023-03-18T16:57:01-04:00"
"timeout1": "2023-03-18T16:55:01-04:00",
"timeout2": "2023-03-18T16:57:01-04:00"
},
{
"id": "0x8f23b7e187b1db26fcfd23c1699c3e56221153fd7225ada0b0cae8fdbd1cab65",
@@ -458,8 +458,8 @@ curl -s -X POST http://127.0.0.1:5000 -H 'Content-Type: application/json' -d \
"exchangeRate": "1",
"status": "ETHLocked",
"startTime": "2023-03-18T16:53:02.642556563-04:00",
"timeout0": "2023-03-18T16:55:07-04:00",
"timeout1": "2023-03-18T16:57:07-04:00"
"timeout1": "2023-03-18T16:55:07-04:00",
"timeout2": "2023-03-18T16:57:07-04:00"
}
]
},

File diff suppressed because one or more lines are too long

View File

@@ -59,7 +59,6 @@ func TestCheckSwapCreatorContractCode_fail(t *testing.T) {
}
func TestSepoliaContract(t *testing.T) {
t.Skip("needs to be redeployed before merge")
ctx := context.Background()
ec := tests.NewEthSepoliaClient(t)

View File

@@ -29,9 +29,9 @@ contract SwapCreator is Secp256k1 {
// this public key is a point on the secp256k1 curve
bytes32 pubKeyRefund;
// timestamp before which Alice can call either `setReady` or `refund`
uint256 timeout0;
// timestamp after which Bob cannot claim, only Alice can refund
uint256 timeout1;
// timestamp after which Bob cannot claim, only Alice can refund
uint256 timeout2;
// the asset being swapped: equal to address(0) for ETH, or an ERC-20 token address
address asset;
// the value of this swap
@@ -60,8 +60,8 @@ contract SwapCreator is Secp256k1 {
bytes32 swapID,
bytes32 claimKey,
bytes32 refundKey,
uint256 timeout0,
uint256 timeout1,
uint256 timeout2,
address asset,
uint256 value
);
@@ -78,7 +78,7 @@ contract SwapCreator is Secp256k1 {
// returned when the claimer parameter for `newSwap` is the zero address
error InvalidClaimer();
// returned when the timeout0 or timeout1 parameters for `newSwap` are zero
// returned when the timeout1 or timeout2 parameters for `newSwap` are zero
error InvalidTimeout();
// returned when the ether sent with a `newSwap` transaction does not match the value parameter
@@ -126,14 +126,14 @@ contract SwapCreator is Secp256k1 {
// newSwap creates a new Swap instance with the given parameters.
// it returns the swap's ID.
// _timeoutDuration0: duration between the current timestamp and timeout0
// _timeoutDuration1: duration between timeout0 and timeout1
// _timeoutDuration0: duration between the current timestamp and timeout1
// _timeoutDuration1: duration between timeout1 and timeout2
function newSwap(
bytes32 _pubKeyClaim,
bytes32 _pubKeyRefund,
address payable _claimer,
uint256 _timeoutDuration0,
uint256 _timeoutDuration1,
uint256 _timeoutDuration2,
address _asset,
uint256 _value,
uint256 _nonce
@@ -149,15 +149,15 @@ contract SwapCreator is Secp256k1 {
if (_pubKeyClaim == 0 || _pubKeyRefund == 0) revert InvalidSwapKey();
if (_claimer == address(0)) revert InvalidClaimer();
if (_timeoutDuration0 == 0 || _timeoutDuration1 == 0) revert InvalidTimeout();
if (_timeoutDuration1 == 0 || _timeoutDuration2 == 0) revert InvalidTimeout();
Swap memory swap = Swap({
owner: payable(msg.sender),
pubKeyClaim: _pubKeyClaim,
pubKeyRefund: _pubKeyRefund,
claimer: _claimer,
timeout0: block.timestamp + _timeoutDuration0,
timeout1: block.timestamp + _timeoutDuration0 + _timeoutDuration1,
timeout1: block.timestamp + _timeoutDuration1,
timeout2: block.timestamp + _timeoutDuration1 + _timeoutDuration2,
asset: _asset,
value: _value,
nonce: _nonce
@@ -172,8 +172,8 @@ contract SwapCreator is Secp256k1 {
swapID,
_pubKeyClaim,
_pubKeyRefund,
swap.timeout0,
swap.timeout1,
swap.timeout2,
swap.asset,
swap.value
);
@@ -181,7 +181,7 @@ contract SwapCreator is Secp256k1 {
return swapID;
}
// Alice should call setReady() before timeout0 once she verifies the XMR has been locked
// Alice should call setReady() before timeout1 once she verifies the XMR has been locked
function setReady(Swap memory _swap) public {
bytes32 swapID = keccak256(abi.encode(_swap));
if (swaps[swapID] != Stage.PENDING) revert SwapNotPending();
@@ -190,8 +190,9 @@ contract SwapCreator is Secp256k1 {
emit Ready(swapID);
}
// Bob can claim if:
// - (Alice has set the swap to `ready` or it's past timeout0) and it's before timeout1
// Bob can call claim if either of these hold true:
// (1) Alice has set the swap to `ready` and it's before timeout1
// (2) It is between timeout0 and timeout1
function claim(Swap memory _swap, bytes32 _secret) public {
if (msg.sender != _swap.claimer) revert OnlySwapClaimer();
_claim(_swap, _secret);
@@ -207,11 +208,14 @@ contract SwapCreator is Secp256k1 {
}
}
// Bob can claim if:
// - (Alice has set the swap to `ready` or it's past timeout0) and it's before timeout1
// It transfers the fee to the relayer address specified in `_relaySwap`.
// Note: this function will revert if the swap value is less than the relayer fee;
// in that case, `claim` must be called instead.
// Anyone can call claimRelayer if they receive a signed _relaySwap object
// from Bob. The same rules for when Bob can call claim() apply here when a
// 3rd party relays a claim for Bob. This version of claiming transfers a
// _relaySwap.fee to _relayer. To prevent front-running, while not requiring
// Bob to know the relayer's payout address, Bob only signs a salted hash of
// the relayer's payout address in _relaySwap.relayerHash.
// Note: claimRelayer will revert if the swap value is less than the relayer
// fee; in that case, Bob must call claim directly.
function claimRelayer(
RelaySwap memory _relaySwap,
bytes32 _secret,
@@ -250,8 +254,8 @@ contract SwapCreator is Secp256k1 {
Stage swapStage = swaps[swapID];
if (swapStage == Stage.INVALID) revert InvalidSwap();
if (swapStage == Stage.COMPLETED) revert SwapCompleted();
if (block.timestamp < _swap.timeout0 && swapStage != Stage.READY) revert TooEarlyToClaim();
if (block.timestamp >= _swap.timeout1) revert TooLateToClaim();
if (block.timestamp < _swap.timeout1 && swapStage != Stage.READY) revert TooEarlyToClaim();
if (block.timestamp >= _swap.timeout2) revert TooLateToClaim();
verifySecret(_secret, _swap.pubKeyClaim);
emit Claimed(swapID, _secret);
@@ -259,8 +263,8 @@ contract SwapCreator is Secp256k1 {
}
// Alice can claim a refund:
// - Until timeout0 unless she calls setReady
// - After timeout1
// - Until timeout1 unless she calls setReady
// - After timeout2
function refund(Swap memory _swap, bytes32 _secret) public {
bytes32 swapID = keccak256(abi.encode(_swap));
Stage swapStage = swaps[swapID];
@@ -268,8 +272,8 @@ contract SwapCreator is Secp256k1 {
if (swapStage == Stage.COMPLETED) revert SwapCompleted();
if (_swap.owner != msg.sender) revert OnlySwapOwner();
if (
block.timestamp < _swap.timeout1 &&
(block.timestamp > _swap.timeout0 || swapStage == Stage.READY)
block.timestamp < _swap.timeout2 &&
(block.timestamp > _swap.timeout1 || swapStage == Stage.READY)
) revert NotTimeToRefund();
verifySecret(_secret, _swap.pubKeyRefund);

View File

@@ -81,7 +81,7 @@ func TestSwapCreator_Claim_ERC20(t *testing.T) {
testClaim(t, types.EthAsset(tokenAddr), 2, big.NewInt(99), tokenContract)
}
func TestSwapCreator_RefundBeforeT0_ERC20(t *testing.T) {
func TestSwapCreator_RefundBeforeT1_ERC20(t *testing.T) {
pkA := tests.GetTakerTestKey(t)
ec, _ := tests.NewEthClient(t)
@@ -95,10 +95,10 @@ func TestSwapCreator_RefundBeforeT0_ERC20(t *testing.T) {
9999,
)
testRefundBeforeT0(t, types.EthAsset(tokenAddr), tokenContract, 2)
testRefundBeforeT1(t, types.EthAsset(tokenAddr), tokenContract, 2)
}
func TestSwapCreator_RefundAfterT1_ERC20(t *testing.T) {
func TestSwapCreator_RefundAfterT2_ERC20(t *testing.T) {
pkA := tests.GetTakerTestKey(t)
ec, _ := tests.NewEthClient(t)
@@ -112,5 +112,5 @@ func TestSwapCreator_RefundAfterT1_ERC20(t *testing.T) {
9999,
)
testRefundAfterT1(t, types.EthAsset(tokenAddr), tokenContract, 2)
testRefundAfterT2(t, types.EthAsset(tokenAddr), tokenContract, 2)
}

File diff suppressed because one or more lines are too long

View File

@@ -19,8 +19,8 @@ type swap struct {
Claimer common.Address `json:"claimer" validate:"required"`
PubKeyClaim types.Hash `json:"pubKeyClaim" validate:"required"`
PubKeyRefund types.Hash `json:"pubKeyRefund" validate:"required"`
Timeout0 *big.Int `json:"timeout0" validate:"required"`
Timeout1 *big.Int `json:"timeout1" validate:"required"`
Timeout2 *big.Int `json:"timeout2" validate:"required"`
Asset common.Address `json:"asset"`
Value *big.Int `json:"value" validate:"required"`
Nonce *big.Int `json:"nonce" validate:"required"`
@@ -33,8 +33,8 @@ func (sfs *SwapCreatorSwap) MarshalJSON() ([]byte, error) {
Claimer: sfs.Claimer,
PubKeyClaim: sfs.PubKeyClaim,
PubKeyRefund: sfs.PubKeyRefund,
Timeout0: sfs.Timeout0,
Timeout1: sfs.Timeout1,
Timeout2: sfs.Timeout2,
Asset: sfs.Asset,
Value: sfs.Value,
Nonce: sfs.Nonce,
@@ -52,8 +52,8 @@ func (sfs *SwapCreatorSwap) UnmarshalJSON(data []byte) error {
Claimer: s.Claimer,
PubKeyClaim: s.PubKeyClaim,
PubKeyRefund: s.PubKeyRefund,
Timeout0: s.Timeout0,
Timeout1: s.Timeout1,
Timeout2: s.Timeout2,
Asset: s.Asset,
Value: s.Value,
Nonce: s.Nonce,

View File

@@ -23,8 +23,8 @@ func TestSwapCreatorSwap_JSON(t *testing.T) {
Claimer: ethcommon.HexToAddress("0xbe0eb53f46cd790cd13851d5eff43d12404d33e8"),
PubKeyClaim: ethcommon.HexToHash("0x5ab9467e70d4e98567991f0179d1f82a3096ed7973f7aff9ea50f649cafa88b9"),
PubKeyRefund: ethcommon.HexToHash("0x4897bc3b9e02c2a8cd6353b9b29377157bf2694daaf52b59c0b42daa39877f14"),
Timeout0: big.NewInt(1672531200),
Timeout1: big.NewInt(1672545600),
Timeout1: big.NewInt(1672531200),
Timeout2: big.NewInt(1672545600),
Asset: ethcommon.HexToAddress("0xdac17f958d2ee523a2206206994597c13d831ec7"),
Value: coins.EtherToWei(apd.New(9876, 0)).BigInt(),
Nonce: big.NewInt(1234),
@@ -34,8 +34,8 @@ func TestSwapCreatorSwap_JSON(t *testing.T) {
"claimer": "0xbe0eb53f46cd790cd13851d5eff43d12404d33e8",
"pubKeyClaim": "0x5ab9467e70d4e98567991f0179d1f82a3096ed7973f7aff9ea50f649cafa88b9",
"pubKeyRefund": "0x4897bc3b9e02c2a8cd6353b9b29377157bf2694daaf52b59c0b42daa39877f14",
"timeout0": 1672531200,
"timeout1": 1672545600,
"timeout1": 1672531200,
"timeout2": 1672545600,
"asset": "0xdac17f958d2ee523a2206206994597c13d831ec7",
"value": 9876000000000000000000,
"nonce": 1234

View File

@@ -134,7 +134,7 @@ func testNewSwap(t *testing.T, asset types.EthAsset, erc20Contract *TestERC20) {
swapID, err := GetIDFromLog(receipt.Logs[newSwapLogIndex])
require.NoError(t, err)
t0, t1, err := GetTimeoutsFromLog(receipt.Logs[newSwapLogIndex])
t1, t2, err := GetTimeoutsFromLog(receipt.Logs[newSwapLogIndex])
require.NoError(t, err)
// validate that off-chain swapID calculation matches the on-chain value
@@ -143,8 +143,8 @@ func testNewSwap(t *testing.T, asset types.EthAsset, erc20Contract *TestERC20) {
Claimer: claimer,
PubKeyClaim: pubKeyClaim,
PubKeyRefund: pubKeyRefund,
Timeout0: t0,
Timeout1: t1,
Timeout2: t2,
Asset: asset.Address(),
Value: value,
Nonce: nonce,
@@ -197,7 +197,7 @@ func TestSwapCreator_Claim_vec(t *testing.T) {
id, err := GetIDFromLog(receipt.Logs[0])
require.NoError(t, err)
t0, t1, err := GetTimeoutsFromLog(receipt.Logs[0])
t1, t2, err := GetTimeoutsFromLog(receipt.Logs[0])
require.NoError(t, err)
swap := SwapCreatorSwap{
@@ -205,8 +205,8 @@ func TestSwapCreator_Claim_vec(t *testing.T) {
Claimer: addr,
PubKeyClaim: cmt,
PubKeyRefund: dummySwapKey,
Timeout0: t0,
Timeout1: t1,
Timeout2: t2,
Asset: ethcommon.Address(types.EthAssetETH),
Value: defaultSwapValue,
Nonce: nonce,
@@ -280,7 +280,7 @@ func testClaim(t *testing.T, asset types.EthAsset, newLogIndex int, value *big.I
id, err := GetIDFromLog(receipt.Logs[newLogIndex])
require.NoError(t, err)
t0, t1, err := GetTimeoutsFromLog(receipt.Logs[newLogIndex])
t1, t2, err := GetTimeoutsFromLog(receipt.Logs[newLogIndex])
require.NoError(t, err)
swap := SwapCreatorSwap{
@@ -288,8 +288,8 @@ func testClaim(t *testing.T, asset types.EthAsset, newLogIndex int, value *big.I
Claimer: addr,
PubKeyClaim: cmt,
PubKeyRefund: dummySwapKey,
Timeout0: t0,
Timeout1: t1,
Timeout2: t2,
Asset: asset.Address(),
Value: value,
Nonce: nonce,
@@ -328,7 +328,7 @@ func TestSwapCreator_Claim_random(t *testing.T) {
testClaim(t, types.EthAssetETH, 0, defaultSwapValue, nil)
}
func testRefundBeforeT0(t *testing.T, asset types.EthAsset, erc20Contract *TestERC20, newLogIndex int) {
func testRefundBeforeT1(t *testing.T, asset types.EthAsset, erc20Contract *TestERC20, newLogIndex int) {
// generate refund secret and public key
dleq := &dleq.DefaultDLEq{}
proof, err := dleq.Prove()
@@ -373,7 +373,7 @@ func testRefundBeforeT0(t *testing.T, asset types.EthAsset, erc20Contract *TestE
id, err := GetIDFromLog(receipt.Logs[newLogIndex])
require.NoError(t, err)
t0, t1, err := GetTimeoutsFromLog(receipt.Logs[newLogIndex])
t1, t2, err := GetTimeoutsFromLog(receipt.Logs[newLogIndex])
require.NoError(t, err)
swap := SwapCreatorSwap{
@@ -381,8 +381,8 @@ func testRefundBeforeT0(t *testing.T, asset types.EthAsset, erc20Contract *TestE
Claimer: addr,
PubKeyClaim: dummySwapKey,
PubKeyRefund: cmt,
Timeout0: t0,
Timeout1: t1,
Timeout2: t2,
Asset: asset.Address(),
Value: defaultSwapValue,
Nonce: nonce,
@@ -408,11 +408,11 @@ func testRefundBeforeT0(t *testing.T, asset types.EthAsset, erc20Contract *TestE
require.Equal(t, StageCompleted, stage)
}
func TestSwapCreator_Refund_beforeT0(t *testing.T) {
testRefundBeforeT0(t, types.EthAssetETH, nil, 0)
func TestSwapCreator_Refund_beforeT1(t *testing.T) {
testRefundBeforeT1(t, types.EthAssetETH, nil, 0)
}
func testRefundAfterT1(t *testing.T, asset types.EthAsset, erc20Contract *TestERC20, newLogIndex int) {
func testRefundAfterT2(t *testing.T, asset types.EthAsset, erc20Contract *TestERC20, newLogIndex int) {
ctx := context.Background()
// generate refund secret and public key
@@ -460,18 +460,18 @@ func testRefundAfterT1(t *testing.T, asset types.EthAsset, erc20Contract *TestER
id, err := GetIDFromLog(receipt.Logs[newLogIndex])
require.NoError(t, err)
t0, t1, err := GetTimeoutsFromLog(receipt.Logs[newLogIndex])
t1, t2, err := GetTimeoutsFromLog(receipt.Logs[newLogIndex])
require.NoError(t, err)
// ensure we can't refund between T0 and T1
<-time.After(time.Until(time.Unix(t0.Int64()+1, 0)))
// ensure we can't refund between T1 and T2
<-time.After(time.Until(time.Unix(t1.Int64()+1, 0)))
swap := SwapCreatorSwap{
Owner: addr,
Claimer: addr,
PubKeyClaim: dummySwapKey,
PubKeyRefund: cmt,
Timeout0: t0,
Timeout1: t1,
Timeout2: t2,
Asset: asset.Address(),
Value: defaultSwapValue,
Nonce: nonce,
@@ -483,7 +483,7 @@ func testRefundAfterT1(t *testing.T, asset types.EthAsset, erc20Contract *TestER
_, err = block.WaitForReceipt(ctx, ec, tx.Hash())
require.ErrorContains(t, err, "VM Exception while processing transaction: revert")
<-time.After(time.Until(time.Unix(t1.Int64()+1, 0)))
<-time.After(time.Until(time.Unix(t2.Int64()+1, 0)))
// now let's try to refund
tx, err = swapCreator.Refund(getAuth(t, pkA), swap, secret)
@@ -506,8 +506,8 @@ func testRefundAfterT1(t *testing.T, asset types.EthAsset, erc20Contract *TestER
require.Equal(t, StageCompleted, stage)
}
func TestSwapCreator_Refund_afterT1(t *testing.T) {
testRefundAfterT1(t, types.EthAssetETH, nil, 0)
func TestSwapCreator_Refund_afterT2(t *testing.T) {
testRefundAfterT2(t, types.EthAssetETH, nil, 0)
}
// test case where contract has multiple swaps happening at once
@@ -549,8 +549,8 @@ func TestSwapCreator_MultipleSwaps(t *testing.T) {
Claimer: addrSwap,
PubKeyClaim: res.Secp256k1PublicKey().Keccak256(),
PubKeyRefund: dummySwapKey, // no one calls refund in this test
Timeout0: nil, // timeouts initialised when swap is created
Timeout1: nil,
Timeout1: nil, // timeouts initialised when swap is created
Timeout2: nil,
Asset: ethcommon.Address(types.EthAssetETH),
Value: defaultSwapValue,
Nonce: big.NewInt(int64(i)),
@@ -591,7 +591,7 @@ func TestSwapCreator_MultipleSwaps(t *testing.T) {
sc.id, err = GetIDFromLog(receipt.Logs[0])
require.NoError(t, err)
sc.swap.Timeout0, sc.swap.Timeout1, err = GetTimeoutsFromLog(receipt.Logs[0])
sc.swap.Timeout1, sc.swap.Timeout2, err = GetTimeoutsFromLog(receipt.Logs[0])
require.NoError(t, err)
}(&swapCases[i])
}

View File

@@ -117,8 +117,8 @@ func (s *SwapCreatorRelaySwap) Hash() types.Hash {
s.Swap.Claimer,
s.Swap.PubKeyClaim,
s.Swap.PubKeyRefund,
s.Swap.Timeout0,
s.Swap.Timeout1,
s.Swap.Timeout2,
s.Swap.Asset,
s.Swap.Value,
s.Swap.Nonce,
@@ -189,8 +189,8 @@ func (sfs *SwapCreatorSwap) SwapID() types.Hash {
sfs.Claimer,
sfs.PubKeyClaim,
sfs.PubKeyRefund,
sfs.Timeout0,
sfs.Timeout1,
sfs.Timeout2,
sfs.Asset,
sfs.Value,
sfs.Nonce,
@@ -290,9 +290,9 @@ func GetTimeoutsFromLog(log *ethtypes.Log) (*big.Int, *big.Int, error) {
return nil, nil, errors.New("log didn't have enough parameters")
}
t0 := res[3].(*big.Int)
t1 := res[4].(*big.Int)
return t0, t1, nil
t1 := res[3].(*big.Int)
t2 := res[4].(*big.Int)
return t1, t2, nil
}
// GenerateNewSwapNonce generates a random nonce value for use with NewSwap

View File

@@ -186,7 +186,7 @@ func (h *Host) SubmitRelayRequest(relayerID peer.ID, request *RelayClaimRequest)
func receiveRelayClaimResponse(stream libp2pnetwork.Stream) (*RelayClaimResponse, error) {
// The timeout should be short enough, that the Maker can try multiple relayers
// before T1 expires even if the receiving node accepts the relay request and
// before T2 expires even if the receiving node accepts the relay request and
// just sits on it without doing anything.
const relayResponseTimeout = time.Minute

View File

@@ -63,8 +63,8 @@ func createTestClaimRequest() *message.RelayClaimRequest {
Claimer: ethcommon.Address{0x1},
PubKeyClaim: [32]byte{0x1},
PubKeyRefund: [32]byte{0x1},
Timeout0: big.NewInt(time.Now().Add(30 * time.Minute).Unix()),
Timeout1: big.NewInt(time.Now().Add(60 * time.Minute).Unix()),
Timeout1: big.NewInt(time.Now().Add(30 * time.Minute).Unix()),
Timeout2: big.NewInt(time.Now().Add(60 * time.Minute).Unix()),
Asset: ethcommon.Address(types.EthAssetETH),
Value: big.NewInt(1e18),
Nonce: big.NewInt(1),

View File

@@ -208,8 +208,8 @@ func (b *backend) SwapTimeout() time.Duration {
return b.swapTimeout
}
// SetSwapTimeout sets the duration between the swap being initiated on-chain and the timeout t0,
// and the duration between t0 and t1.
// SetSwapTimeout sets the duration between the swap being initiated on-chain and the timeout t1,
// and the duration between t1 and t2.
func (b *backend) SetSwapTimeout(timeout time.Duration) {
b.swapTimeout = timeout
}

View File

@@ -53,17 +53,17 @@ type Info struct {
// EndTime is the time at which the swap completes; ie.
// when the node has claimed or refunded its funds.
EndTime *time.Time `json:"endTime,omitempty"`
// Timeout0 is the first swap timeout; before this timeout,
// Timeout1 is the first swap timeout; before this timeout,
// the ETH-maker is able to refund the ETH (if `ready` has not
// been set to true in the contract). After this timeout,
// the ETH-taker is able to claim, and the ETH-maker can
// no longer refund.
Timeout0 *time.Time `json:"timeout0,omitempty"`
// Timeout1 is the second swap timeout; before this timeout
// (and after Timeout0), the ETH-taker is able to claim, but
Timeout1 *time.Time `json:"timeout1,omitempty"`
// Timeout2 is the second swap timeout; before this timeout
// (and after Timeout1), the ETH-taker is able to claim, but
// after this timeout, the ETH-taker can no longer claim, only
// the ETH-maker can refund.
Timeout1 *time.Time `json:"timeout1,omitempty"`
Timeout2 *time.Time `json:"timeout2,omitempty"`
statusCh chan types.Status `json:"-"`
}

View File

@@ -102,13 +102,14 @@ func (s *swapState) checkContract(txHash ethcommon.Hash) error {
return nil
}
// checkAndSetTimeouts checks that the timeouts set by the counterparty when initiating the swap
// are not too short or too long.
// we expect the timeout to be of a certain length (1 hour for mainnet/stagenet), and allow a 3 minute
// variation between now and the expected time until the first timeout t0, to allow for block confirmations.
// the time between t0 and t1 should always be the exact length we expect.
func (s *swapState) checkAndSetTimeouts(t0, t1 *big.Int) error {
s.setTimeouts(t0, t1)
// checkAndSetTimeouts checks that the timeouts set by the counterparty when
// initiating a swap are not too short or long. We expect the timeout to be of a
// certain length (1 hour for mainnet/stagenet), and allow a 3 minute variation
// between now and the expected time until the first timeout t1, to allow for
// block confirmations. The time between t1 and t2 should always be the exact
// length we expect.
func (s *swapState) checkAndSetTimeouts(t1, t2 *big.Int) error {
s.setTimeouts(t1, t2)
// we ignore the timeout for development, as unit tests and integration tests
// often set different timeouts.
@@ -119,20 +120,20 @@ func (s *swapState) checkAndSetTimeouts(t0, t1 *big.Int) error {
expectedTimeout := common.SwapTimeoutFromEnv(s.Backend.Env())
allowableTimeDiff := expectedTimeout / 20
if s.t1.Sub(s.t0) != expectedTimeout {
return errInvalidT1
if s.t2.Sub(s.t1) != expectedTimeout {
return errInvalidT2
}
if time.Now().Add(expectedTimeout).Sub(s.t0).Abs() > allowableTimeDiff {
return errInvalidT0
if time.Now().Add(expectedTimeout).Sub(s.t1).Abs() > allowableTimeDiff {
return errInvalidT1
}
return nil
}
func (s *swapState) setTimeouts(t0, t1 *big.Int) {
s.t0 = time.Unix(t0.Int64(), 0)
func (s *swapState) setTimeouts(t1, t2 *big.Int) {
s.t1 = time.Unix(t1.Int64(), 0)
s.info.Timeout0 = &s.t0
s.t2 = time.Unix(t2.Int64(), 0)
s.info.Timeout1 = &s.t1
s.info.Timeout2 = &s.t2
}

View File

@@ -22,8 +22,8 @@ var (
errSwapIDMismatch = errors.New("hash of swap struct does not match swap ID")
errLockTxReverted = errors.New("other party failed to lock ETH asset (transaction reverted)")
errInvalidETHLockedTransaction = errors.New("eth locked tx was not to correct contract address")
errInvalidT0 = errors.New("invalid t0 value; asset was locked too far in the past")
errInvalidT1 = errors.New("invalid swap timeout set by counterparty")
errInvalidT1 = errors.New("invalid t1 value; asset was locked too far in the past")
errInvalidT2 = errors.New("invalid swap timeout set by counterparty")
errRelayedTransactionTimeout = errors.New("relayed transaction was not included within one minute")
errClaimedLogInvalidContractAddr = errors.New("log was not emitted by correct contract")
errClaimedLogWrongTopicLength = errors.New("log did not have 3 topics")

View File

@@ -24,10 +24,10 @@ const (
EventETHLockedType EventType = iota
// EventContractReadyType is triggered when the taker sets the contract to
// "ready" or timeout0 is reached. When this event occurs, we can claim ETH
// "ready" or timeout1 is reached. When this event occurs, we can claim ETH
// from the contract. After this event, the other possible events are
// EventETHRefundedType (which would only happen if we go offline until
// timeout1, causing us to refund), or EventExitType (refund).
// timeout2, causing us to refund), or EventExitType (refund).
EventContractReadyType
// EventETHRefundedType is triggered when the taker refunds the

View File

@@ -210,8 +210,8 @@ func TestInstance_createOngoingSwap(t *testing.T) {
SwapCreatorAddr: inst.backend.SwapCreatorAddr(),
SwapID: contractSwapID,
Swap: &contracts.SwapCreatorSwap{
Timeout0: big.NewInt(1),
Timeout1: big.NewInt(2),
Timeout1: big.NewInt(1),
Timeout2: big.NewInt(2),
},
}, nil)
rdb.EXPECT().GetSwapPrivateKey(s.OfferID).Return(

View File

@@ -128,7 +128,7 @@ func (s *swapState) handleNotifyETHLocked(msg *message.NotifyETHLocked) error {
return err
}
err = s.checkAndSetTimeouts(msg.ContractSwap.Timeout0, msg.ContractSwap.Timeout1)
err = s.checkAndSetTimeouts(msg.ContractSwap.Timeout1, msg.ContractSwap.Timeout2)
if err != nil {
return err
}
@@ -138,14 +138,14 @@ func (s *swapState) handleNotifyETHLocked(msg *message.NotifyETHLocked) error {
return fmt.Errorf("failed to lock funds: %w", err)
}
go s.runT0ExpirationHandler()
go s.runT1ExpirationHandler()
return nil
}
func (s *swapState) runT0ExpirationHandler() {
log.Debugf("time until t0 (%s): %vs",
s.t0.Format(common.TimeFmtSecs),
time.Until(s.t0).Seconds(),
func (s *swapState) runT1ExpirationHandler() {
log.Debugf("time until t1 (%s): %vs",
s.t1.Format(common.TimeFmtSecs),
time.Until(s.t1).Seconds(),
)
waitCtx, waitCtxCancel := context.WithCancel(context.Background())
@@ -155,7 +155,7 @@ func (s *swapState) runT0ExpirationHandler() {
// with --miner.blockTime!!!
waitCh := make(chan error)
go func() {
waitCh <- s.ETHClient().WaitForTimestamp(waitCtx, s.t0)
waitCh <- s.ETHClient().WaitForTimestamp(waitCtx, s.t1)
close(waitCh)
}()
@@ -163,27 +163,27 @@ func (s *swapState) runT0ExpirationHandler() {
case <-s.ctx.Done():
return
case <-s.readyCh:
log.Debugf("returning from runT0ExpirationHandler as contract was set to ready")
log.Debugf("returning from runT1ExpirationHandler as contract was set to ready")
return
case err := <-waitCh:
if err != nil {
// TODO: Do we propagate this error? If we retry, the logic should probably be inside
// WaitForTimestamp. (#162)
log.Errorf("Failure waiting for T0 timeout: err=%s", err)
log.Errorf("Failure waiting for T1 timeout: err=%s", err)
return
}
log.Debugf("reached t0, time to claim")
s.handleT0Expired()
log.Debugf("reached t1, time to claim")
s.handleT1Expired()
}
}
func (s *swapState) handleT0Expired() {
func (s *swapState) handleT1Expired() {
event := newEventContractReady()
s.eventCh <- event
err := <-event.errCh
if err != nil {
// TODO: this is quite bad, how should this be handled? (#162)
log.Errorf("failed to handle t0 expiration: %s", err)
log.Errorf("failed to handle t1 expiration: %s", err)
}
}

View File

@@ -67,7 +67,7 @@ type swapState struct {
swapCreatorAddr ethcommon.Address
contractSwapID [32]byte
contractSwap *contracts.SwapCreatorSwap
t0, t1 time.Time
t1, t2 time.Time
// XMRTaker's keys for this session
xmrtakerPublicSpendKey *mcrypto.PublicKey
@@ -89,7 +89,7 @@ type swapState struct {
logReadyCh chan ethtypes.Log
// channel for `Refunded` logs seen on-chain
logRefundedCh chan ethtypes.Log
// signals the t0 expiration handler to return
// signals the t1 expiration handler to return
readyCh chan struct{}
// signals to the creator xmrmaker instance that it can delete this swap
done chan struct{}
@@ -312,7 +312,7 @@ func newSwapStateFromOngoing(
return nil, err
}
s.setTimeouts(ethSwapInfo.Swap.Timeout0, ethSwapInfo.Swap.Timeout1)
s.setTimeouts(ethSwapInfo.Swap.Timeout1, ethSwapInfo.Swap.Timeout2)
s.privkeys = sk
s.pubkeys = sk.PublicKeyPair()
s.contractSwapID = ethSwapInfo.SwapID

View File

@@ -97,7 +97,7 @@ func newTestSwap(
contractSwapID, err := contracts.GetIDFromLog(receipt.Logs[0])
require.NoError(t, err)
t0, t1, err := contracts.GetTimeoutsFromLog(receipt.Logs[0])
t1, t2, err := contracts.GetTimeoutsFromLog(receipt.Logs[0])
require.NoError(t, err)
contractSwap := &contracts.SwapCreatorSwap{
@@ -105,8 +105,8 @@ func newTestSwap(
Claimer: ethAddr,
PubKeyClaim: claimKey,
PubKeyRefund: refundKey,
Timeout0: t0,
Timeout1: t1,
Timeout2: t2,
Asset: ethcommon.Address(asset),
Value: amount,
Nonce: nonce,
@@ -133,7 +133,7 @@ func newSwap(
ss.contractSwapID = contractSwapID
ss.contractSwap = contractSwap
ss.setTimeouts(contractSwap.Timeout0, contractSwap.Timeout1)
ss.setTimeouts(contractSwap.Timeout1, contractSwap.Timeout2)
return txHash
}
@@ -252,10 +252,10 @@ func TestSwapState_HandleProtocolMessage_NotifyETHLocked_timeout(t *testing.T) {
require.NoError(t, err)
err = s.setNextExpectedEvent(EventContractReadyType)
require.NoError(t, err)
require.Equal(t, duration, s.t1.Sub(s.t0))
require.Equal(t, duration, s.t2.Sub(s.t1))
require.Equal(t, EventContractReadyType, s.nextExpectedEvent)
go s.runT0ExpirationHandler()
go s.runT1ExpirationHandler()
for status := range s.info.StatusCh() {
if status == types.CompletedSuccess {

View File

@@ -39,8 +39,8 @@ const (
EventETHClaimedType
// EventShouldRefundType is triggered when we should refund, either because
// we are nearing the timeout0 threshold and the maker hasn't locked XMR, or
// because we've reached the timeout1 threshold and the maker hasn't claimed
// we are nearing the timeout1 threshold and the maker hasn't locked XMR, or
// because we've reached the timeout2 threshold and the maker hasn't claimed
// the ETH. It causes us to refund the contract locked ETH locked to
// ourselves. After this event, the only possible event is EventExitType
// (refund path).
@@ -168,7 +168,7 @@ func newEventETHClaimed(sk *mcrypto.PrivateSpendKey) *EventETHClaimed {
}
// EventShouldRefund is an optional event. It occurs when the XMR-maker doesn't
// lock before t0, so we should refund the ETH.
// lock before t1, so we should refund the ETH.
type EventShouldRefund struct {
errCh chan error
txHashCh chan ethcommon.Hash // contains the refund tx hash, if successful

View File

@@ -84,7 +84,7 @@ func TestSwapState_handleEvent_EventETHClaimed(t *testing.T) {
resp := net.LastSentMessage()
require.NotNil(t, resp)
require.Equal(t, message.NotifyETHLockedType, resp.Type())
require.Equal(t, time.Minute*2, s.t1.Sub(s.t0))
require.Equal(t, time.Minute*2, s.t2.Sub(s.t1))
require.Equal(t, msg.PublicSpendKey.Hex(), s.xmrmakerPublicSpendKey.Hex())
require.Equal(t, msg.PrivateViewKey.Hex(), s.xmrmakerPrivateViewKey.Hex())

View File

@@ -68,8 +68,8 @@ func TestInstance_createOngoingSwap(t *testing.T) {
StartNumber: big.NewInt(1),
SwapCreatorAddr: inst.backend.SwapCreatorAddr(),
Swap: &contracts.SwapCreatorSwap{
Timeout0: big.NewInt(1),
Timeout1: big.NewInt(2),
Timeout1: big.NewInt(1),
Timeout2: big.NewInt(2),
},
}, nil)
rdb.EXPECT().GetSwapPrivateKey(s.OfferID).Return(

View File

@@ -119,7 +119,7 @@ func (s *swapState) handleSendKeysMessage(msg *message.SendKeysMessage) (common.
}
// start goroutine to check that XMRMaker locks before t_0
go s.runT0ExpirationHandler()
go s.runT1ExpirationHandler()
// start goroutine to check for xmr being locked
go s.checkForXMRLock()
@@ -185,20 +185,20 @@ func (s *swapState) checkForXMRLock() {
}
}
func (s *swapState) runT0ExpirationHandler() {
defer log.Debugf("returning from runT0ExpirationHandler")
func (s *swapState) runT1ExpirationHandler() {
defer log.Debugf("returning from runT1ExpirationHandler")
// TODO: this variable is so that we definitely refund before t0.
// TODO: this variable is so that we definitely refund before t1.
// Current algorithm is to trigger the timeout when only 15% of the allotted
// time is remaining. If the block interval is 1 second on a test network and
// and T0 is 7 seconds after swap creation, we need the refund to trigger more
// than one second before the block with a timestamp exactly equal to T0 to
// and T1 is 7 seconds after swap creation, we need the refund to trigger more
// than one second before the block with a timestamp exactly equal to T1 to
// satisfy the strictly less than requirement. 7s * 15% = 1.05s. 15% remaining
// may be reasonable even with large timeouts on production networks, but more
// research is needed.
t0Delta := s.t1.Sub(s.t0) // time between swap start and T0 is equal to T1-T0
deltaBeforeT0ToGiveUp := time.Duration(float64(t0Delta) * 0.15)
deltaUntilGiveUp := time.Until(s.t0) - deltaBeforeT0ToGiveUp
t1Delta := s.t2.Sub(s.t1) // time between swap start and T1 is equal to T2-T1
deltaBeforeT1ToGiveUp := time.Duration(float64(t1Delta) * 0.15)
deltaUntilGiveUp := time.Until(s.t1) - deltaBeforeT1ToGiveUp
giveUpAndRefundTimer := time.NewTimer(deltaUntilGiveUp)
defer giveUpAndRefundTimer.Stop() // don't wait for the timeout to garbage collect
log.Debugf("time until refund: %vs", deltaUntilGiveUp.Seconds())
@@ -209,7 +209,7 @@ func (s *swapState) runT0ExpirationHandler() {
case <-s.xmrLockedCh:
return
case <-giveUpAndRefundTimer.C:
log.Infof("approaching T0, attempting to refund ETH")
log.Infof("approaching T1, attempting to refund ETH")
event := newEventShouldRefund()
s.eventCh <- event
err := <-event.errCh
@@ -234,24 +234,24 @@ func (s *swapState) handleNotifyXMRLock() error {
return fmt.Errorf("failed to call Ready: %w", err)
}
go s.runT1ExpirationHandler()
go s.runT2ExpirationHandler()
return nil
}
func (s *swapState) runT1ExpirationHandler() {
log.Debugf("time until t1 (%s): %vs",
s.t1.Format(common.TimeFmtSecs),
time.Until(s.t1).Seconds(),
func (s *swapState) runT2ExpirationHandler() {
log.Debugf("time until t2 (%s): %vs",
s.t2.Format(common.TimeFmtSecs),
time.Until(s.t2).Seconds(),
)
defer log.Debugf("returning from runT1ExpirationHandler")
defer log.Debugf("returning from runT2ExpirationHandler")
waitCtx, waitCtxCancel := context.WithCancel(context.Background())
defer waitCtxCancel() // Unblock WaitForTimestamp if still running when we exit
waitCh := make(chan error)
go func() {
waitCh <- s.ETHClient().WaitForTimestamp(waitCtx, s.t1)
waitCh <- s.ETHClient().WaitForTimestamp(waitCtx, s.t2)
close(waitCh)
}()
@@ -264,15 +264,15 @@ func (s *swapState) runT1ExpirationHandler() {
if err != nil {
// TODO: Do we propagate this error? If we retry, the logic should probably be inside
// WaitForTimestamp. (#162)
log.Errorf("failure waiting for T1 timeout: %s", err)
log.Errorf("failure waiting for T2 timeout: %s", err)
return
}
s.handleT1Expired()
s.handleT2Expired()
}
}
func (s *swapState) handleT1Expired() {
log.Debugf("handling T1")
func (s *swapState) handleT2Expired() {
log.Debugf("handling T2")
event := newEventShouldRefund()
s.eventCh <- event
err := <-event.errCh

View File

@@ -72,7 +72,7 @@ type swapState struct {
// swap contract and timeouts in it; set once contract is deployed
contractSwapID [32]byte
contractSwap *contracts.SwapCreatorSwap
t0, t1 time.Time
t1, t2 time.Time
// tracks the state of the swap
nextExpectedEvent EventType
@@ -86,9 +86,9 @@ type swapState struct {
eventCh chan Event
// channel for `Claimed` logs seen on-chain
logClaimedCh chan ethtypes.Log
// signals the t0 expiration handler to return
xmrLockedCh chan struct{}
// signals the t1 expiration handler to return
xmrLockedCh chan struct{}
// signals the t2 expiration handler to return
claimedCh chan struct{}
// signals to the creator xmrmaker instance that it can delete this swap
done chan struct{}
@@ -192,7 +192,7 @@ func newSwapStateFromOngoing(
return nil, errContractAddrMismatch(ethSwapInfo.SwapCreatorAddr.String())
}
s.setTimeouts(ethSwapInfo.Swap.Timeout0, ethSwapInfo.Swap.Timeout1)
s.setTimeouts(ethSwapInfo.Swap.Timeout1, ethSwapInfo.Swap.Timeout2)
s.privkeys = sk
s.pubkeys = sk.PublicKeyPair()
s.contractSwapID = ethSwapInfo.SwapID
@@ -406,7 +406,7 @@ func (s *swapState) exit() error {
//
// for EventETHClaimed, the XMR has been locked, but the
// ETH hasn't been claimed, but the contract has been set to ready.
// we should also refund in this case, since we might be past t1.
// we should also refund in this case, since we might be past t2.
receipt, err := s.tryRefund()
if err != nil {
if errors.Is(err, errRefundSwapCompleted) || strings.Contains(err.Error(), revertSwapCompleted) {
@@ -469,48 +469,48 @@ func (s *swapState) tryRefund() (*ethtypes.Receipt, error) {
return nil, err
}
log.Debugf("tryRefund isReady=%v untilT0=%vs untilT1=%vs",
isReady, s.t0.Sub(ts).Seconds(), s.t1.Sub(ts).Seconds())
log.Debugf("tryRefund isReady=%v untilT1=%vs untilT2=%vs",
isReady, s.t1.Sub(ts).Seconds(), s.t2.Sub(ts).Seconds())
if ts.Before(s.t0) && !isReady {
if ts.Before(s.t1) && !isReady {
receipt, err := s.refund() //nolint:govet
// TODO: Have refund() return errors that we can use errors.Is to check against
if err == nil {
return receipt, nil
}
// There is a small, but non-zero chance that our transaction gets placed in a block that is after T0
// even though the current block is before T0. In this case, the transaction will be reverted, the
// gas fee is lost, but we can wait until T1 and try again.
// There is a small, but non-zero chance that our transaction gets placed in a block that is after T1
// even though the current block is before T1. In this case, the transaction will be reverted, the
// gas fee is lost, but we can wait until T2 and try again.
log.Warnf("first refund attempt failed: err=%s", err)
}
if ts.After(s.t1) {
if ts.After(s.t2) {
return s.refund()
}
// the contract is "ready", so we can't do anything until
// the counterparty claims or until t1 passes.
// the counterparty claims or until t2 passes.
//
// we let the runT1ExpirationHandler() routine continue to run and read
// we let the runT2ExpirationHandler() routine continue to run and read
// from s.eventCh for EventShouldRefund or EventETHClaimed.
// (since this function is called from inside the event handler routine,
// it won't handle those events while this function is executing.)
log.Infof("waiting until time %s to refund", s.t1)
log.Infof("waiting until time %s to refund", s.t2)
waitCtx, waitCtxCancel := context.WithCancel(s.ctx)
defer waitCtxCancel()
waitCh := make(chan error)
go func() {
waitCh <- s.ETHClient().WaitForTimestamp(waitCtx, s.t1)
waitCh <- s.ETHClient().WaitForTimestamp(waitCtx, s.t2)
close(waitCh)
}()
for {
select {
case event := <-s.eventCh:
log.Debugf("got event %s while waiting for T1", event.Type())
log.Debugf("got event %s while waiting for T2", event.Type())
switch event.(type) {
case *EventShouldRefund:
return s.refund()
@@ -521,11 +521,11 @@ func (s *swapState) tryRefund() (*ethtypes.Receipt, error) {
case *EventExit:
// do nothing, we're already exiting
default:
panic(fmt.Sprintf("got unexpected event while waiting for Claimed/T1: %s", event.Type()))
panic(fmt.Sprintf("got unexpected event while waiting for Claimed/T2: %s", event.Type()))
}
case err = <-waitCh:
if err != nil {
return nil, fmt.Errorf("failed to wait for T1: %w", err)
return nil, fmt.Errorf("failed to wait for T2: %w", err)
}
return s.refund()
@@ -533,11 +533,11 @@ func (s *swapState) tryRefund() (*ethtypes.Receipt, error) {
}
}
func (s *swapState) setTimeouts(t0, t1 *big.Int) {
s.t0 = time.Unix(t0.Int64(), 0)
func (s *swapState) setTimeouts(t1, t2 *big.Int) {
s.t1 = time.Unix(t1.Int64(), 0)
s.info.Timeout0 = &s.t0
s.t2 = time.Unix(t2.Int64(), 0)
s.info.Timeout1 = &s.t1
s.info.Timeout2 = &s.t2
}
func (s *swapState) generateAndSetKeys() error {
@@ -621,10 +621,10 @@ func (s *swapState) lockAsset() (*ethtypes.Receipt, error) {
return nil, fmt.Errorf("swap ID not found in transaction receipt's logs: %w", err)
}
var t0 *big.Int
var t1 *big.Int
var t2 *big.Int
for _, log := range receipt.Logs {
t0, t1, err = contracts.GetTimeoutsFromLog(log)
t1, t2, err = contracts.GetTimeoutsFromLog(log)
if err == nil {
break
}
@@ -634,15 +634,15 @@ func (s *swapState) lockAsset() (*ethtypes.Receipt, error) {
}
s.fundsLocked = true
s.setTimeouts(t0, t1)
s.setTimeouts(t1, t2)
s.contractSwap = &contracts.SwapCreatorSwap{
Owner: s.ETHClient().Address(),
Claimer: s.xmrmakerAddress,
PubKeyClaim: cmtXMRMaker,
PubKeyRefund: cmtXMRTaker,
Timeout0: t0,
Timeout1: t1,
Timeout2: t2,
Asset: ethcommon.Address(s.info.EthAsset),
Value: s.providedAmount.BigInt(),
Nonce: nonce,

View File

@@ -227,13 +227,13 @@ func TestSwapState_HandleProtocolMessage_SendKeysMessage(t *testing.T) {
resp := net.LastSentMessage()
require.NotNil(t, resp)
require.Equal(t, s.SwapTimeout(), s.t1.Sub(s.t0))
require.Equal(t, s.SwapTimeout(), s.t2.Sub(s.t1))
require.Equal(t, xmrmakerKeysAndProof.PublicKeyPair.SpendKey().String(), s.xmrmakerPublicSpendKey.String())
require.Equal(t, xmrmakerKeysAndProof.PrivateKeyPair.ViewKey().String(), s.xmrmakerPrivateViewKey.String())
}
// test the case where XMRTaker deploys and locks her eth, but XMRMaker never locks his monero.
// XMRTaker should call refund before the timeout t0.
// XMRTaker should call refund before the timeout t1.
func TestSwapState_HandleProtocolMessage_SendKeysMessage_Refund(t *testing.T) {
s, net := newTestSwapStateAndNet(t)
defer s.cancel()
@@ -247,19 +247,19 @@ func TestSwapState_HandleProtocolMessage_SendKeysMessage_Refund(t *testing.T) {
resp := net.LastSentMessage()
require.NotNil(t, resp)
require.Equal(t, message.NotifyETHLockedType, resp.Type())
require.Equal(t, s.SwapTimeout(), s.t1.Sub(s.t0))
require.Equal(t, s.SwapTimeout(), s.t2.Sub(s.t1))
require.Equal(t, xmrmakerKeysAndProof.PublicKeyPair.SpendKey().String(), s.xmrmakerPublicSpendKey.String())
require.Equal(t, xmrmakerKeysAndProof.PrivateKeyPair.ViewKey().String(), s.xmrmakerPrivateViewKey.String())
// ensure we refund before t0
// ensure we refund before t1
for status := range s.info.StatusCh() {
if status == types.CompletedRefund {
// check this is before t0
// check this is before t1
// TODO: remove the 10-second buffer, this is needed for now
// because the exact refund time isn't stored, and the time
// between the refund happening and this line being called
// causes it to fail
require.Greater(t, s.t0.Add(time.Second*10), time.Now())
require.Greater(t, s.t1.Add(time.Second*10), time.Now())
break
} else if !status.IsOngoing() {
t.Fatalf("got wrong exit status %s, expected CompletedRefund", status)
@@ -315,7 +315,7 @@ func TestSwapState_NotifyXMRLock(t *testing.T) {
}
// test the case where the monero is locked, but XMRMaker never claims.
// XMRTaker should call refund after the timeout t1.
// XMRTaker should call refund after the timeout t2.
func TestSwapState_NotifyXMRLock_Refund(t *testing.T) {
s := newTestSwapState(t)
defer s.cancel()
@@ -348,8 +348,8 @@ func TestSwapState_NotifyXMRLock_Refund(t *testing.T) {
for status := range s.info.StatusCh() {
if status == types.CompletedRefund {
// check this is after t1
require.Less(t, s.t1, time.Now())
// check this is after t2
require.Less(t, s.t2, time.Now())
break
} else if !status.IsOngoing() {
t.Fatalf("got wrong exit status %s, expected CompletedRefund", status)

View File

@@ -43,8 +43,8 @@ func createTestSwap(claimer ethcommon.Address) *contracts.SwapCreatorSwap {
Claimer: claimer,
PubKeyClaim: [32]byte{0x1},
PubKeyRefund: [32]byte{0x1},
Timeout0: big.NewInt(time.Now().Add(30 * time.Minute).Unix()),
Timeout1: big.NewInt(time.Now().Add(60 * time.Minute).Unix()),
Timeout1: big.NewInt(time.Now().Add(30 * time.Minute).Unix()),
Timeout2: big.NewInt(time.Now().Add(60 * time.Minute).Unix()),
Asset: ethcommon.Address(types.EthAssetETH),
Value: big.NewInt(1e18),
Nonce: big.NewInt(1),

View File

@@ -55,8 +55,8 @@ func Test_ValidateAndSendTransaction(t *testing.T) {
swapCreator, err := contracts.NewSwapCreator(swapCreatorAddr, ec.Raw())
require.NoError(t, err)
testT0Timeout := big.NewInt(300) // 5 minutes
testT1Timeout := testT0Timeout
testT1Timeout := big.NewInt(300) // 5 minutes
testT2Timeout := testT1Timeout
value := big.NewInt(9e16)
nonce := big.NewInt(0)
@@ -68,8 +68,8 @@ func Test_ValidateAndSendTransaction(t *testing.T) {
cmt,
refundKey,
claimerAddr,
testT0Timeout,
testT1Timeout,
testT2Timeout,
types.EthAssetETH.Address(),
value,
nonce,
@@ -85,7 +85,7 @@ func Test_ValidateAndSendTransaction(t *testing.T) {
id, err := contracts.GetIDFromLog(receipt.Logs[logIndex])
require.NoError(t, err)
t0, t1, err := contracts.GetTimeoutsFromLog(receipt.Logs[logIndex])
t1, t2, err := contracts.GetTimeoutsFromLog(receipt.Logs[logIndex])
require.NoError(t, err)
swap := contracts.SwapCreatorSwap{
@@ -93,8 +93,8 @@ func Test_ValidateAndSendTransaction(t *testing.T) {
Claimer: claimerAddr,
PubKeyClaim: cmt,
PubKeyRefund: refundKey,
Timeout0: t0,
Timeout1: t1,
Timeout2: t2,
Asset: types.EthAssetETH.Address(),
Value: value,
Nonce: nonce,

View File

@@ -60,8 +60,8 @@ func TestValidateRelayerFee(t *testing.T) {
Claimer: ethcommon.Address{},
PubKeyClaim: [32]byte{},
PubKeyRefund: [32]byte{},
Timeout0: new(big.Int),
Timeout1: new(big.Int),
Timeout2: new(big.Int),
Asset: ethcommon.Address{},
Value: tc.value,
Nonce: new(big.Int),

View File

@@ -138,8 +138,8 @@ type OngoingSwap struct {
Status types.Status `json:"status" validate:"required"`
LastStatusUpdateTime time.Time `json:"lastStatusUpdateTime" validate:"required"`
StartTime time.Time `json:"startTime" validate:"required"`
Timeout0 *time.Time `json:"timeout0"`
Timeout1 *time.Time `json:"timeout1"`
Timeout2 *time.Time `json:"timeout2"`
EstimatedTimeToCompletion time.Duration `json:"estimatedTimeToCompletion" validate:"required"`
}
@@ -190,8 +190,8 @@ func (s *SwapService) GetOngoing(_ *http.Request, req *GetOngoingRequest, resp *
swap.Status = info.Status
swap.LastStatusUpdateTime = info.LastStatusUpdateTime
swap.StartTime = info.StartTime
swap.Timeout0 = info.Timeout0
swap.Timeout1 = info.Timeout1
swap.Timeout2 = info.Timeout2
swap.EstimatedTimeToCompletion, err = estimatedTimeToCompletion(env, info.Status, info.LastStatusUpdateTime)
if err != nil {
return fmt.Errorf("failed to estimate time to completion for swap %s: %w", info.OfferID, err)

View File

@@ -385,11 +385,11 @@ func (s *IntegrationTestSuite) testRefundXMRTakerCancels(asset types.EthAsset) {
require.Equal(s.T(), len(beforeResp.Offers), len(afterResp.Offers))
}
// TestRefund_XMRMakerCancels_untilAfterT1 tests the case where XMRTaker and XMRMaker
// TestRefund_XMRMakerCancels_untilAfterT2 tests the case where XMRTaker and XMRMaker
// both lock their funds, but XMRMaker goes offline
// until time t1 in the swap contract passes. This triggers XMRTaker to refund, which XMRMaker will then
// until time t2 in the swap contract passes. This triggers XMRTaker to refund, which XMRMaker will then
// "come online" to see, and he will then refund also.
func (s *IntegrationTestSuite) TestRefund_XMRMakerCancels_untilAfterT1() {
func (s *IntegrationTestSuite) TestRefund_XMRMakerCancels_untilAfterT2() {
// Skipping test as it can't guarantee that the refund will happen before the swap completes
// successfully: // https://github.com/athanorlabs/atomic-swap/issues/144
s.T().Skip()
@@ -398,7 +398,7 @@ func (s *IntegrationTestSuite) TestRefund_XMRMakerCancels_untilAfterT1() {
}
// TestRefund_XMRMakerCancels_afterIsReady tests the case where XMRTaker and XMRMaker both lock their
// funds, but XMRMaker goes offline until past isReady==true and t0, but comes online before t1. When
// funds, but XMRMaker goes offline until past isReady==true and t1, but comes online before t2. When
// XMRMaker comes back online, he should claim the ETH, causing XMRTaker to also claim the XMR.
func (s *IntegrationTestSuite) TestRefund_XMRMakerCancels_afterIsReady() {
// Skipping test as it can't guarantee that the refund will happen before the swap completes