Skip to content

Commit

Permalink
Starting matching and orderbook abstractions
Browse files Browse the repository at this point in the history
  • Loading branch information
Rjected committed May 7, 2019
1 parent b0c4b4f commit 3a76dba
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 48 deletions.
13 changes: 13 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
language: go

# Force-enable Go modules.
# This will be unnecessary when Go 1.13 lands.
env:
- GO111MODULE=on

go:
- 1.12.x

# script always runs to completion
script:
- go test -v -race ./... # Run all the tests with the race detector enabled
86 changes: 43 additions & 43 deletions crypto/hashtimelock/hashtimelock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,133 +200,133 @@ func TestTenMillionTimeSHA256(t *testing.T) {
}
}

func TestHundredMillionTimeSHA256(t *testing.T) {
func BenchmarkHundredMillionTimeSHA256(b *testing.B) {
seed := make([]byte, 32)
copy(seed[:], []byte("opencxhundredmillion"))
hashFunction := sha256.New()
hashPuzzle := New(seed, hashFunction)
time := uint64(100000000)
puzzle, expectedAns, err := hashPuzzle.SetupTimelockPuzzle(time)
if err != nil {
t.Fatalf("There was an error setting up the timelock puzzle: %s\n", err)
b.Fatalf("There was an error setting up the timelock puzzle: %s\n", err)
}
puzzleAns, err := puzzle.Solve()
if err != nil {
t.Fatalf("Error solving puzzle: %s\n", err)
b.Fatalf("Error solving puzzle: %s\n", err)
}
if !bytes.Equal(puzzleAns, expectedAns) {
t.Fatalf("Answer did not equal puzzle for time = %d. Expected %x, got %x\n", time, expectedAns, puzzleAns)
b.Fatalf("Answer did not equal puzzle for time = %d. Expected %x, got %x\n", time, expectedAns, puzzleAns)
}
}

// Doing this to see how much a better algo makes a difference (figure out if the bottleneck is Read/Write speed)
func TestHundredMillionTimeFastSHA256(t *testing.T) {
func BenchmarkHundredMillionTimeFastSHA256(b *testing.B) {
seed := make([]byte, 32)
copy(seed[:], []byte("opencxhundredmillion"))
hashFunction := fastsha256.New()
hashPuzzle := New(seed, hashFunction)
time := uint64(100000000)
puzzle, expectedAns, err := hashPuzzle.SetupTimelockPuzzle(time)
if err != nil {
t.Fatalf("There was an error setting up the timelock puzzle: %s\n", err)
b.Fatalf("There was an error setting up the timelock puzzle: %s\n", err)
}
puzzleAns, err := puzzle.Solve()
if err != nil {
t.Fatalf("Error solving puzzle: %s\n", err)
b.Fatalf("Error solving puzzle: %s\n", err)
}
if !bytes.Equal(puzzleAns, expectedAns) {
t.Fatalf("Answer did not equal puzzle for time = %d. Expected %x, got %x\n", time, expectedAns, puzzleAns)
b.Fatalf("Answer did not equal puzzle for time = %d. Expected %x, got %x\n", time, expectedAns, puzzleAns)
}
}

// lol it's slower than crypto/sha256???

// Blake2B is cool
func TestHundredMillionTimeBlake2B(t *testing.T) {
func BenchmarkHundredMillionTimeBlake2B(b *testing.B) {
seed := make([]byte, 32)
copy(seed[:], []byte("opencxhundredmillion"))
hashFunction, err := blake2b.New256(nil)
if err != nil {
t.Fatalf("Could not set up blake2b: %s\n", err)
b.Fatalf("Could not set up blake2b: %s\n", err)
}
hashPuzzle := New(seed, hashFunction)
time := uint64(100000000)
puzzle, expectedAns, err := hashPuzzle.SetupTimelockPuzzle(time)
if err != nil {
t.Fatalf("There was an error setting up the timelock puzzle: %s\n", err)
b.Fatalf("There was an error setting up the timelock puzzle: %s\n", err)
}
puzzleAns, err := puzzle.Solve()
if err != nil {
t.Fatalf("Error solving puzzle: %s\n", err)
b.Fatalf("Error solving puzzle: %s\n", err)
}
if !bytes.Equal(puzzleAns, expectedAns) {
t.Fatalf("Answer did not equal puzzle for time = %d. Expected %x, got %x\n", time, expectedAns, puzzleAns)
b.Fatalf("Answer did not equal puzzle for time = %d. Expected %x, got %x\n", time, expectedAns, puzzleAns)
}
}

// Now let's see, siphash is supposed to be fast
func TestHundredMillionTimeSipHash(t *testing.T) {
func BenchmarkHundredMillionTimeSipHash(b *testing.B) {
seed := make([]byte, 32)
copy(seed[:], []byte("opencxhundredmillion"))
hashFunction := siphash.New(seed)
hashPuzzle := New(seed, hashFunction)
time := uint64(100000000)
puzzle, expectedAns, err := hashPuzzle.SetupTimelockPuzzle(time)
if err != nil {
t.Fatalf("There was an error setting up the timelock puzzle: %s\n", err)
b.Fatalf("There was an error setting up the timelock puzzle: %s\n", err)
}
puzzleAns, err := puzzle.Solve()
if err != nil {
t.Fatalf("Error solving puzzle: %s\n", err)
b.Fatalf("Error solving puzzle: %s\n", err)
}
if !bytes.Equal(puzzleAns, expectedAns) {
t.Fatalf("Answer did not equal puzzle for time = %d. Expected %x, got %x\n", time, expectedAns, puzzleAns)
b.Fatalf("Answer did not equal puzzle for time = %d. Expected %x, got %x\n", time, expectedAns, puzzleAns)
}
}

// Now let's see, highwayhash is supposed to be faster
func TestHundredMillionTimeHighwayHash(t *testing.T) {
func BenchmarkHundredMillionTimeHighwayHash(b *testing.B) {
seed := make([]byte, 32)
copy(seed[:], []byte("opencxhundredmillion"))
hashFunction, err := highwayhash.New(seed)
if err != nil {
t.Fatalf("Could not create highwayhash: %s\n", err)
b.Fatalf("Could not create highwayhash: %s\n", err)
}
hashPuzzle := New(seed, hashFunction)
time := uint64(100000000)
puzzle, expectedAns, err := hashPuzzle.SetupTimelockPuzzle(time)
if err != nil {
t.Fatalf("There was an error setting up the timelock puzzle: %s\n", err)
b.Fatalf("There was an error setting up the timelock puzzle: %s\n", err)
}
puzzleAns, err := puzzle.Solve()
if err != nil {
t.Fatalf("Error solving puzzle: %s\n", err)
b.Fatalf("Error solving puzzle: %s\n", err)
}
if !bytes.Equal(puzzleAns, expectedAns) {
t.Fatalf("Answer did not equal puzzle for time = %d. Expected %x, got %x\n", time, expectedAns, puzzleAns)
b.Fatalf("Answer did not equal puzzle for time = %d. Expected %x, got %x\n", time, expectedAns, puzzleAns)
}
}

// TestBillionTimeSHA256 is even more fun, but we can't run it because we need the time to be > 10 minutes
// func TestBillionTimeSHA256(t *testing.T) {
// seed := make([]byte, 32)
// copy(seed[:], []byte("opencxbillion"))
// hashFunction := sha256.New()
// hashPuzzle := New(seed, hashFunction)
// time := uint64(1000000000)
// BenchmarkBillionTimeSHA256 is even more fun, but we can't run it because we need the time to be > 10 minutes
func BenchmarkBillionTimeSHA256(b *testing.B) {
seed := make([]byte, 32)
copy(seed[:], []byte("opencxbillion"))
hashFunction := sha256.New()
hashPuzzle := New(seed, hashFunction)
time := uint64(1000000000)

// // so we solve it once here, this can take some time
// puzzle, expectedAns, err := hashPuzzle.SetupTimelockPuzzle(time)
// if err != nil {
// t.Fatalf("There was an error setting up the timelock puzzle: %s\n", err)
// }
// so we solve it once here, this can take some time
puzzle, expectedAns, err := hashPuzzle.SetupTimelockPuzzle(time)
if err != nil {
b.Fatalf("There was an error setting up the timelock puzzle: %s\n", err)
}

// // now we do the exact same thing all over again lol
// puzzleAns, err := puzzle.Solve()
// if err != nil {
// t.Fatalf("Error solving puzzle: %s\n", err)
// }
// if !bytes.Equal(puzzleAns, expectedAns) {
// t.Fatalf("Answer did not equal puzzle for time = 0. Expected %x, got %x\n", expectedAns, puzzleAns)
// }
// }
// now we do the exact same thing all over again lol
puzzleAns, err := puzzle.Solve()
if err != nil {
b.Fatalf("Error solving puzzle: %s\n", err)
}
if !bytes.Equal(puzzleAns, expectedAns) {
b.Fatalf("Answer did not equal puzzle for time = 0. Expected %x, got %x\n", expectedAns, puzzleAns)
}
}
8 changes: 3 additions & 5 deletions match/limitorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ import (
"time"
)

// Order is a struct that represents a stored side of a trade
type Order interface {
Type() string
Price() (float64, error)
}
// TODO: Order, Side, Price, User abstraction: The Price should really be the pair {amountHave,amountWant}, and we should be comparing Prices by doing fraction comparison.
// TODO: Remove orderbookprice
// TODO: Create arithmetic for orders, work out decimals, make testable.

// LimitOrder represents a limit order, implementing the order interface
type LimitOrder struct {
Expand Down
20 changes: 20 additions & 0 deletions match/orderbook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package match

// LimitOrderbook provides all of the operations needed for a normal exchange orderbook. This should be a single-pair orderbook. TODO: Determine if this should actually be a single-pair orderbook
type LimitOrderbook interface {
// SetMatchingAlgorithm should set the matching algorithm. TODO: I want this to be a thing.
// SetMatchingAlgorithm(func(book *LimitOrderbook) (err error)) (err error)
// PlaceOrders places multiple orders in the orderbook, and returns orders with their assigned IDs. These orders will all get the same time priority, we assume they come in at the same time.
// TODO: figure out if this is the right thing to do, or if PlaceOrder should be here instead
PlaceOrders(orders []*LimitOrder) (idOrders []*LimitOrder, err error)
// GetBook takes in a trading pair and returns the whole orderbook.
GetBook() (book []*LimitOrder, err error)
// GetOrder gets an order from an OrderID
GetOrder(id *OrderID) (order *LimitOrder, err error)
// CancelOrder cancels an order with order id
CancelOrder(id *OrderID) (err error)
// CalculatePrice returns the calculated price based on the order book.
CalculatePrice() (price Price, err error)
// GetPairs gets the trading pair we can trade on
GetPair() (pair *Pair, err error)
}
38 changes: 38 additions & 0 deletions match/orderid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package match

import (
"encoding/hex"
"fmt"
)

// OrderID represents an order's unique ID.
// This is a byte array alias because sometimes the ID will be represented as text, and sometimes it will be represented as bytes.
// We conform it to the BinaryMarshaler interface and TextMarshaler interface.
type OrderID []byte

// MarshalBinary encodes the receiver into a binary form and returns the result. This conforms to the BinaryMarshaler interface
func (o *OrderID) MarshalBinary() (data []byte, err error) {
copy(data, *o)
return
}

// UnmarshalBinary decodes the form generated by MarshalBinary. This conforms to the BinaryMarshaler interface
func (o *OrderID) UnmarshalBinary(data []byte) (err error) {
copy(*o, data)
return
}

// MarshalText encodes the receiver into UTF-8-encoded text and returns the result. This conforms to the TextMarshaler interface
func (o *OrderID) MarshalText() (text []byte, err error) {
copy(text, []byte(hex.EncodeToString(*o)))
return
}

// UnmarshalText deocdes the form generated by MarshalText. This conforms to the TextMarshaler interface
func (o *OrderID) UnmarshalText(text []byte) (err error) {
if _, err = hex.Decode(*o, text); err != nil {
err = fmt.Errorf("Error unmarshalling text OrderID: %s", err)
return
}
return
}
40 changes: 40 additions & 0 deletions match/price.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package match

import (
"fmt"
"math/big"
)

// Price represents an exchange rate. It's basically a fancy fraction. It follows the Want / Have method of doing things. Removal of Want/Have is TODO.
// We don't want this to be a big int because that means it can't really be sent over the wire. We're not multiple precision here, but we do want some
// standard, reasonable level of precision
type Price struct {
AmountWant uint64
AmountHave uint64
}

// Note on the Want / Have model: It makes sense from an exchange perspective, but in reality "side", "price", and "volume" are all connected.

// ToFloat converts the price to a float value
func (p *Price) ToFloat() (price float64, err error) {
if p.AmountHave == 0 {
err = fmt.Errorf("AmountHave cannot be 0 to convert to float")
return
}
price = float64(p.AmountWant) / float64(p.AmountHave)
return
}

// Cmp compares p and otherPrice and returns:
//
// -1 if x < y
// 0 if x == y (incl. -0 == 0, -Inf == -Inf, and +Inf == +Inf)
// +1 if x > y
//
func (p *Price) Cmp(otherPrice *Price) (compIndicator int) {
// Just use math/big's comparison, they already wrote it
price1 := new(big.Float).Quo(new(big.Float).SetUint64(p.AmountWant), new(big.Float).SetUint64(p.AmountHave))
price2 := new(big.Float).Quo(new(big.Float).SetUint64(otherPrice.AmountWant), new(big.Float).SetUint64(otherPrice.AmountHave))
compIndicator = price1.Cmp(price2)
return
}

0 comments on commit 3a76dba

Please sign in to comment.