decred.org/dcrdex@v1.0.3/client/orderbook/epochqueue_test.go (about)

     1  package orderbook
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"math/rand"
     7  	"os"
     8  	"sort"
     9  	"testing"
    10  
    11  	"decred.org/dcrdex/dex"
    12  	"decred.org/dcrdex/dex/msgjson"
    13  	"decred.org/dcrdex/dex/order"
    14  	"github.com/decred/dcrd/crypto/blake256"
    15  )
    16  
    17  var tLogger = dex.NewLogger("TBOOK", dex.LevelTrace, os.Stdout)
    18  
    19  func makeEpochOrderNote(mid string, oid order.OrderID, side uint8, rate uint64, qty uint64, commitment order.Commitment, epoch uint64) *msgjson.EpochOrderNote {
    20  	return &msgjson.EpochOrderNote{
    21  		Commit: commitment[:],
    22  		BookOrderNote: msgjson.BookOrderNote{
    23  			OrderNote: msgjson.OrderNote{
    24  				MarketID: mid,
    25  				OrderID:  oid[:],
    26  			},
    27  			TradeNote: msgjson.TradeNote{
    28  				Side:     side,
    29  				Rate:     rate,
    30  				Quantity: qty,
    31  			},
    32  		},
    33  		Epoch: epoch,
    34  	}
    35  }
    36  
    37  func makeCommitment(pimg order.Preimage) order.Commitment {
    38  	return order.Commitment(blake256.Sum256(pimg[:]))
    39  }
    40  
    41  // makeMatchProof generates the sorting seed and commitment checksum from the
    42  // provided ordered set of preimages. The provide commitments are sorted.
    43  func makeMatchProof(preimages []order.Preimage, commitments []order.Commitment) (msgjson.Bytes, msgjson.Bytes) {
    44  	sort.Slice(commitments, func(i, j int) bool {
    45  		return bytes.Compare(commitments[i][:], commitments[j][:]) < 0
    46  	})
    47  
    48  	cbuff := make([]byte, 0, len(commitments)*order.CommitmentSize)
    49  	for i := range commitments {
    50  		cbuff = append(cbuff, commitments[i][:]...)
    51  	}
    52  	csum := blake256.Sum256(cbuff)
    53  
    54  	sbuff := make([]byte, 0, len(preimages)*order.PreimageSize)
    55  	for i := 0; i < len(preimages); i++ {
    56  		sbuff = append(sbuff, preimages[i][:]...)
    57  	}
    58  	seed := blake256.Sum256(sbuff)
    59  
    60  	return seed[:], csum[:]
    61  }
    62  
    63  func TestEpochQueue(t *testing.T) {
    64  	mid := "mkt"
    65  	epoch := uint64(10)
    66  	eq := NewEpochQueue()
    67  	n1PimgB, _ := hex.DecodeString("e1f796fa0fc16ba7bb90be2a33e87c3d60ab628471a420834383661801bb0bfd")
    68  	var n1Pimg order.Preimage
    69  	copy(n1Pimg[:], n1PimgB)
    70  	n1Commitment := n1Pimg.Commit() // aba75140b1f6edf26955a97e1b09d7b17abdc9c0b099fc73d9729501652fbf66
    71  	n1OrderID := [32]byte{'a'}
    72  	n1 := makeEpochOrderNote(mid, n1OrderID, msgjson.BuyOrderNum, 1, 2, n1Commitment, epoch)
    73  
    74  	n2PimgB, _ := hex.DecodeString("8e6c140071db1eb2f7a18194f1a045a94c078835c75dff2f3e836180baad9e95")
    75  	var n2Pimg order.Preimage
    76  	copy(n2Pimg[:], n2PimgB)
    77  	n2Commitment := n2Pimg.Commit() // 0f4bc030d392cef3f44d0781870ab7fcb78a0cda36c73e50b88c741b4f851600
    78  	n2OrderID := [32]byte{'b'}
    79  	n2 := makeEpochOrderNote(mid, n2OrderID, msgjson.BuyOrderNum, 1, 2, n2Commitment, epoch)
    80  
    81  	n3PimgB, _ := hex.DecodeString("e1f796fa0fc16ba7bb90be2a33e87c3d60ab628471a420834383661801bb0bfd")
    82  	var n3Pimg order.Preimage
    83  	copy(n3Pimg[:], n3PimgB)
    84  	n3Commitment := n3Pimg.Commit() // aba75140b1f6edf26955a97e1b09d7b17abdc9c0b099fc73d9729501652fbf66
    85  	n3OrderID := [32]byte{'c'}
    86  	n3 := makeEpochOrderNote(mid, n3OrderID, msgjson.BuyOrderNum, 1, 2, n3Commitment, epoch)
    87  
    88  	// This csum matches the server-side tests.
    89  	wantCSum, _ := hex.DecodeString("8c743c3225b89ffbb50b5d766d3e078cd8e2658fa8cb6e543c4101e1d59a8e8e")
    90  
    91  	err := eq.Enqueue(n1)
    92  	if err != nil {
    93  		t.Fatalf("[Enqueue]: unexpected error: %v", err)
    94  	}
    95  
    96  	// Ensure the epoch queue size is 1.
    97  	if eq.Size() != 1 {
    98  		t.Fatalf("[Size] expected queue size of %d, got %d", 1, eq.Size())
    99  	}
   100  
   101  	// Reset the epoch queue.
   102  	eq = NewEpochQueue()
   103  
   104  	// Ensure the epoch queue does not enqueue duplicate orders.
   105  	err = eq.Enqueue(n1)
   106  	if err != nil {
   107  		t.Fatalf("[Enqueue]: unexpected error: %v", err)
   108  	}
   109  
   110  	if epoch != n1.Epoch {
   111  		t.Fatalf("[Epoch]: expected epoch value of %d, got %d", n1.Epoch, epoch)
   112  	}
   113  
   114  	err = eq.Enqueue(n1)
   115  	if err == nil {
   116  		t.Fatal("[Enqueue]: expected a duplicate enqueue error")
   117  	}
   118  
   119  	// Ensure the epoch queue size is 1.
   120  	if eq.Size() != 1 {
   121  		t.Fatalf("[Size] expected queue size of %d, got %d", 1, eq.Size())
   122  	}
   123  
   124  	// Reset the epoch queue.
   125  	eq = NewEpochQueue()
   126  
   127  	// Ensure match proof generation fails if there epoch queue is empty.
   128  	preimages := []order.Preimage{n1Pimg, n2Pimg, n3Pimg}
   129  	_, _, err = eq.GenerateMatchProof(preimages, nil)
   130  	if err == nil {
   131  		t.Fatalf("[GenerateMatchProof] expected an empty epoch queue error")
   132  	}
   133  
   134  	err = eq.Enqueue(n1)
   135  	if err != nil {
   136  		t.Fatalf("[Enqueue]: unexpected error: %v", err)
   137  	}
   138  
   139  	err = eq.Enqueue(n2)
   140  	if err != nil {
   141  		t.Fatalf("[Enqueue]: unexpected error: %v", err)
   142  	}
   143  
   144  	err = eq.Enqueue(n3)
   145  	if err != nil {
   146  		t.Fatalf("[Enqueue]: unexpected error: %v", err)
   147  	}
   148  
   149  	// Ensure the queue has n2 epoch order.
   150  	if !eq.Exists(n2OrderID) {
   151  		t.Fatalf("[Exists] expected order with id %x in the epoch queue", n2OrderID)
   152  	}
   153  
   154  	// Ensure the epoch queue size is 3.
   155  	if eq.Size() != 3 {
   156  		t.Fatalf("[Size] expected queue size of %d, got %d", 3, eq.Size())
   157  	}
   158  
   159  	// Ensure match proof generation works as expected.
   160  	preimages = []order.Preimage{n1Pimg, n2Pimg, n3Pimg}
   161  	commitments := []order.Commitment{n1Commitment, n3Commitment, n2Commitment}
   162  	expectedSeed, expectedCmtChecksum := makeMatchProof(preimages, commitments)
   163  
   164  	seed, cmtChecksum, err := eq.GenerateMatchProof(preimages, nil)
   165  	if err != nil {
   166  		t.Fatalf("[GenerateMatchProof] unexpected error: %v", err)
   167  	}
   168  
   169  	if !bytes.Equal(expectedSeed, seed) {
   170  		t.Fatalf("expected seed %v, got %v", expectedSeed, seed)
   171  	}
   172  
   173  	if !bytes.Equal(expectedCmtChecksum, cmtChecksum) {
   174  		t.Fatalf("expected commitment checksum %v, got %v",
   175  			expectedCmtChecksum, cmtChecksum)
   176  	}
   177  	if !bytes.Equal(wantCSum, cmtChecksum) {
   178  		t.Fatalf("expected commitment checksum %v, got %v",
   179  			wantCSum, cmtChecksum)
   180  	}
   181  
   182  	eq = NewEpochQueue()
   183  
   184  	// Queue epoch orders.
   185  	err = eq.Enqueue(n3)
   186  	if err != nil {
   187  		t.Fatalf("[Enqueue]: unexpected error: %v", err)
   188  	}
   189  
   190  	err = eq.Enqueue(n1)
   191  	if err != nil {
   192  		t.Fatalf("[Enqueue]: unexpected error: %v", err)
   193  	}
   194  
   195  	err = eq.Enqueue(n2)
   196  	if err != nil {
   197  		t.Fatalf("[Enqueue]: unexpected error: %v", err)
   198  	}
   199  
   200  	// Ensure the queue has n1 epoch order.
   201  	if !eq.Exists(n1OrderID) {
   202  		t.Fatalf("[Exists] expected order with id %x in the epoch queue", n1OrderID)
   203  	}
   204  
   205  	// Ensure match proof generation works as expected, when there are misses.
   206  	preimages = []order.Preimage{n1Pimg, n2Pimg}                               // n3 missed
   207  	commitments = []order.Commitment{n1Commitment, n2Commitment, n3Commitment} // all orders in the epoch queue
   208  	expectedSeed, expectedCmtChecksum = makeMatchProof(preimages, commitments)
   209  
   210  	misses := []order.OrderID{n3OrderID}
   211  	seed, cmtChecksum, err = eq.GenerateMatchProof(preimages, misses)
   212  	if err != nil {
   213  		t.Fatalf("[GenerateMatchProof] unexpected error: %v", err)
   214  	}
   215  
   216  	if !bytes.Equal(expectedSeed, seed) {
   217  		t.Fatalf("expected seed %v, got %v", expectedSeed, seed)
   218  	}
   219  
   220  	if !bytes.Equal(expectedCmtChecksum, cmtChecksum) {
   221  		t.Fatalf("expected commitment checksum %v, got %v",
   222  			expectedCmtChecksum, cmtChecksum)
   223  	}
   224  	if !bytes.Equal(wantCSum, cmtChecksum) {
   225  		t.Fatalf("expected commitment checksum %v, got %v",
   226  			wantCSum, cmtChecksum)
   227  	}
   228  
   229  	// Ensure match proof fails when there is a preimage mismatch.
   230  	preimages = []order.Preimage{n1Pimg}
   231  	_, _, err = eq.GenerateMatchProof(preimages, nil)
   232  	if err == nil {
   233  		t.Fatalf("[GenerateMatchProof] expected a preimage/orders mismatch")
   234  	}
   235  
   236  	preimages = []order.Preimage{n1Pimg, n2Pimg, n3Pimg}
   237  	_, _, err = eq.GenerateMatchProof(preimages, nil)
   238  	if err == nil {
   239  		t.Fatalf("[GenerateMatchProof] expected no order match for preimage error")
   240  	}
   241  }
   242  
   243  func randOrderID() order.OrderID {
   244  	var oid order.OrderID
   245  	rand.Read(oid[:])
   246  	return oid
   247  }
   248  
   249  func randPreimage() order.Preimage {
   250  	var pi order.Preimage
   251  	rand.Read(pi[:])
   252  	return pi
   253  }
   254  
   255  func benchmarkGenerateMatchProof(c int, b *testing.B) {
   256  	notes := make([]*msgjson.EpochOrderNote, c)
   257  	preimages := make([]order.Preimage, 0, c)
   258  	for i := range notes {
   259  		pi := randPreimage()
   260  		notes[i] = makeEpochOrderNote("mkt", randOrderID(),
   261  			msgjson.BuyOrderNum, 1, 2, blake256.Sum256(pi[:]), 10)
   262  		preimages = append(preimages, pi)
   263  	}
   264  
   265  	numMisses := c / 20
   266  	misses := make([]order.OrderID, numMisses)
   267  	for i := range misses {
   268  		in := rand.Intn(len(notes))
   269  		copy(misses[i][:], notes[in].OrderID)
   270  
   271  		// Remove the missed preimage.
   272  		for idx := 0; idx < len(preimages); idx++ {
   273  			commit := blake256.Sum256(preimages[idx][:])
   274  			if bytes.Equal(commit[:], notes[in].Commit[:]) {
   275  				if idx < len(preimages)-1 {
   276  					copy(preimages[idx:], preimages[idx+1:])
   277  				}
   278  				preimages[len(preimages)-1] = order.Preimage{}
   279  				preimages = preimages[:len(preimages)-1]
   280  			}
   281  		}
   282  	}
   283  
   284  	b.ResetTimer()
   285  
   286  	for i := 0; i < b.N; i++ {
   287  		eq := NewEpochQueue()
   288  
   289  		for _, note := range notes {
   290  			if err := eq.Enqueue(note); err != nil {
   291  				b.Fatalf("[Enqueue]: unexpected error: %v", err)
   292  			}
   293  		}
   294  
   295  		_, _, err := eq.GenerateMatchProof(preimages, misses)
   296  		if err != nil {
   297  			b.Error(err)
   298  		}
   299  	}
   300  }
   301  
   302  func BenchmarkMatchProof500(b *testing.B)  { benchmarkGenerateMatchProof(500, b) }
   303  func BenchmarkMatchProof1000(b *testing.B) { benchmarkGenerateMatchProof(1000, b) }
   304  func BenchmarkMatchProof5000(b *testing.B) { benchmarkGenerateMatchProof(5000, b) }