decred.org/dcrdex@v1.0.5/server/coinlock/coinlocker_test.go (about)

     1  package coinlock
     2  
     3  import (
     4  	crand "crypto/rand"
     5  	"math/rand"
     6  	"testing"
     7  
     8  	"decred.org/dcrdex/dex/order"
     9  	"decred.org/dcrdex/dex/order/test"
    10  )
    11  
    12  func randomBytes(len int) []byte {
    13  	bytes := make([]byte, len)
    14  	crand.Read(bytes)
    15  	return bytes
    16  }
    17  
    18  func randCoinID() CoinID {
    19  	return CoinID(randomBytes(72))
    20  }
    21  
    22  func randomOrderID() order.OrderID {
    23  	pk := randomBytes(order.OrderIDSize)
    24  	var id order.OrderID
    25  	copy(id[:], pk)
    26  	return id
    27  }
    28  
    29  // utxo implements order.Outpoint
    30  type utxo struct {
    31  	txHash []byte
    32  	vout   uint32
    33  }
    34  
    35  func (u *utxo) TxHash() []byte { return u.txHash }
    36  func (u *utxo) Vout() uint32   { return u.vout }
    37  
    38  func randcomCoinID() order.CoinID {
    39  	return randomBytes(36)
    40  }
    41  
    42  func verifyLocked(cl CoinLockChecker, coins []CoinID, wantLocked bool, t *testing.T) bool {
    43  	for _, coin := range coins {
    44  		locked := cl.CoinLocked(coin)
    45  		if locked != wantLocked {
    46  			t.Errorf("Coin %v locked=%v, wanted=%v.", coin, locked, wantLocked)
    47  			return false
    48  		}
    49  	}
    50  	return true
    51  }
    52  
    53  func Test_swapLocker_LockOrderCoins(t *testing.T) {
    54  	w := &test.Writer{
    55  		Addr: "asdf",
    56  		Acct: test.NextAccount(),
    57  		Sell: true,
    58  		Market: &test.Market{
    59  			Base:    2,
    60  			Quote:   0,
    61  			LotSize: 100,
    62  		},
    63  	}
    64  
    65  	lo0, _ := test.WriteLimitOrder(w, 1000, 1, order.StandingTiF, 0)
    66  	lo0.Coins = []order.CoinID{randcomCoinID(), randcomCoinID()}
    67  	lo1, _ := test.WriteLimitOrder(w, 1000, 2, order.StandingTiF, 0)
    68  	lo1.Coins = []order.CoinID{randcomCoinID(), randcomCoinID(), randcomCoinID()}
    69  
    70  	orders := []order.Order{lo0, lo1}
    71  	oid0, oid1 := lo0.ID(), lo1.ID()
    72  
    73  	masterLock := NewMasterCoinLocker()
    74  	swapLock := masterLock.Swap()
    75  	bookLock := masterLock.Book()
    76  
    77  	emptyCoins := masterLock.OrderCoinsLocked(oid0)
    78  	if len(emptyCoins) != 0 {
    79  		t.Fatalf("found coins that were not yet locked")
    80  	}
    81  
    82  	swapLock.LockOrdersCoins(orders)
    83  
    84  	lo0Coins := masterLock.OrderCoinsLocked(oid0)
    85  	if len(lo0Coins) != len(lo0.Coins) {
    86  		t.Fatalf("Expected %d coins for order %v, got %d", len(lo0.Coins), lo0, len(lo0Coins))
    87  	}
    88  
    89  	lo1Coins := masterLock.OrderCoinsLocked(oid1)
    90  	if len(lo1Coins) != len(lo1.Coins) {
    91  		t.Fatalf("Expected %d coins for order %v, got %d", len(lo1.Coins), lo0, len(lo1Coins))
    92  	}
    93  
    94  	for _, coin := range lo1Coins {
    95  		if !masterLock.CoinLocked(coin) {
    96  			t.Errorf("masterLock said coin %v wasn't locked", coin)
    97  		}
    98  		if !swapLock.CoinLocked(coin) {
    99  			t.Errorf("swapLock said coin %v wasn't locked", coin)
   100  		}
   101  		if !bookLock.CoinLocked(coin) {
   102  			t.Errorf("bookLocker said coin %v wasn't locked", coin)
   103  		}
   104  	}
   105  
   106  	// Try and fail to relock coins.
   107  	failed := swapLock.LockOrdersCoins(orders)
   108  	if len(failed) != len(orders) {
   109  		t.Fatalf("should have failed to lock %d coins, got %d failed", len(orders), len(failed))
   110  	}
   111  
   112  	// Now lock some in the book lock.
   113  	bookLock.LockOrdersCoins([]order.Order{lo0})
   114  	// unlock them in swap lock
   115  	swapLock.UnlockOrderCoins(oid0)
   116  	// verify they are still locked
   117  	for _, coin := range lo0Coins {
   118  		if !masterLock.CoinLocked(coin) {
   119  			t.Errorf("masterLock said coin %v wasn't locked", coin)
   120  		}
   121  		if !swapLock.CoinLocked(coin) {
   122  			t.Errorf("swapLock said coin %v wasn't locked", coin)
   123  		}
   124  		if !bookLock.CoinLocked(coin) {
   125  			t.Errorf("bookLocker said coin %v wasn't locked", coin)
   126  		}
   127  	}
   128  	// now unlock them in book lock too
   129  	bookLock.UnlockOrderCoins(oid0)
   130  	// verify they are now unlocked
   131  	for _, coin := range lo0Coins {
   132  		if masterLock.CoinLocked(coin) {
   133  			t.Errorf("masterLock said coin %v was locked", coin)
   134  		}
   135  		if swapLock.CoinLocked(coin) {
   136  			t.Errorf("swapLock said coin %v was locked", coin)
   137  		}
   138  		if bookLock.CoinLocked(coin) {
   139  			t.Errorf("bookLocker said coin %v was locked", coin)
   140  		}
   141  	}
   142  }
   143  
   144  func Test_bookLocker_LockCoins(t *testing.T) {
   145  	masterLock := NewMasterCoinLocker()
   146  	bookLock := masterLock.Book()
   147  
   148  	coinMap := make(map[order.OrderID][]CoinID)
   149  	var allCoins []CoinID
   150  	numOrders := 99
   151  	allOrderIDs := make([]order.OrderID, numOrders)
   152  	for i := 0; i < numOrders; i++ {
   153  		coins := make([]CoinID, rand.Int63n(8)+1)
   154  		for j := range coins {
   155  			coins[j] = randCoinID()
   156  		}
   157  		oid := randomOrderID()
   158  		coinMap[oid] = coins
   159  		allCoins = append(allCoins, coins...)
   160  		allOrderIDs[i] = oid
   161  	}
   162  
   163  	bookLock.LockCoins(coinMap)
   164  
   165  	verifyLocked := func(cl CoinLockChecker, coins []CoinID, wantLocked bool) (ok bool) {
   166  		for _, coin := range coins {
   167  			locked := cl.CoinLocked(coin)
   168  			if locked != wantLocked {
   169  				t.Errorf("Coin %v locked=%v, wanted=%v.", coin, locked, wantLocked)
   170  				return false
   171  			}
   172  		}
   173  		return true
   174  	}
   175  
   176  	// Make sure the BOOK locker say they are locked.
   177  	if !verifyLocked(bookLock, allCoins, true) {
   178  		t.Errorf("bookLock indicated coins were unlocked that should have been locked")
   179  	}
   180  
   181  	// Make sure the MASTER locker say they are locked too.
   182  	if !verifyLocked(masterLock, allCoins, true) {
   183  		t.Errorf("masterLock indicated coins were unlocked that should have been locked")
   184  	}
   185  
   186  	// Make sure the SWAP lockers sways they are locked too.
   187  	swapLock := masterLock.Swap()
   188  	if !verifyLocked(swapLock, allCoins, true) {
   189  		t.Errorf("swapLock indicated coins were unlocked that should have been locked")
   190  	}
   191  
   192  	// try and fail to unlock coins via the swap lock
   193  	oid := allOrderIDs[0]
   194  	swapLock.UnlockOrderCoins(oid)
   195  	if !verifyLocked(swapLock, allCoins, true) {
   196  		t.Errorf("swapLock indicated coins were unlocked that should have been locked")
   197  	}
   198  
   199  	// unlock properly via the book lock
   200  	bookLock.UnlockOrderCoins(oid)
   201  	orderCoins := coinMap[oid]
   202  	if !verifyLocked(bookLock, orderCoins, false) {
   203  		t.Errorf("bookLock indicated coins were locked that should have been unlocked")
   204  	}
   205  	if !verifyLocked(swapLock, orderCoins, false) {
   206  		t.Errorf("swapLock indicated coins were locked that should have been unlocked")
   207  	}
   208  
   209  	// Attempt relock of the already-locked coins.
   210  	delete(coinMap, oid)
   211  	failed := bookLock.LockCoins(coinMap)
   212  	if len(failed) != len(coinMap) {
   213  		t.Fatalf("should have failed to lock %d coins, got %d failed", len(coinMap), len(failed))
   214  	}
   215  
   216  	// Relock the coins for the removed order.
   217  	bookLock.LockCoins(map[order.OrderID][]CoinID{
   218  		oid: orderCoins,
   219  	})
   220  
   221  	// Make sure the BOOK locker say they are locked.
   222  	if !verifyLocked(bookLock, allCoins, true) {
   223  		t.Errorf("bookLock indicated coins were unlocked that should have been locked")
   224  	}
   225  
   226  	bookLock.UnlockAll()
   227  
   228  	if !verifyLocked(bookLock, orderCoins, false) {
   229  		t.Errorf("bookLock indicated coins were locked that should have been unlocked")
   230  	}
   231  }