Пример #1
0
// Uncredit the user's account for the given payment.
// If the deposit isn't credited, do nothing.
// Returns true if the resulting balance is negative.
// Must be idempotent.
func UncreditDepositForPayment(payment *bitcoin.Payment) (balance *Balance) {

	// SANITY CHECK
	paymentId := payment.Id
	// Reload the payment.
	payment = bitcoin.LoadPaymentByTxId(payment.TxId, payment.Vout)
	// Ensure that it is orphaned.
	if payment.Orphaned != bitcoin.PAYMENT_ORPHANED_STATUS_ORPHANED {
		panic(NewError("Cannot uncredit deposit for a payment that isn't in STATUS_ORPHANED"))
	}
	// Are you paranoid enough?
	if paymentId != payment.Id {
		panic(NewError("payment.Id didn't match"))
	}
	// END SANITY CHECK

	err := db.DoBeginSerializable(func(tx *db.ModelTx) {
		// Load the corresponding deposit.
		deposit := LoadDepositForPayment(tx, payment.Id)
		// If the deposit isn't credited, do nothing.
		if deposit.Status != DEPOSIT_STATUS_CREDITED {
			return
		}
		// Uncredit the account.
		balance = UpdateBalanceByWallet(tx, deposit.UserId, deposit.Wallet, deposit.Coin, -int64(deposit.Amount), false)
		UpdateDepositSetStatus(tx, deposit.Id, DEPOSIT_STATUS_PENDING)
	})
	if err != nil {
		panic(err)
	}
	return
}
Пример #2
0
// Cancels an existing order.
// Returns the most up-to-date version of order.
func (market *Market) ProcessOrderCancellation(order *Order) *Order {

	// reload the order, it might have been touched since.
	order = LoadOrder(order.Id)
	switch order.Status {
	case ORDER_STATUS_COMPLETE:
		return order
	case ORDER_STATUS_CANCELED:
		return order
	case ORDER_STATUS_PENDING:
		break
	default:
		panic(NewError("Unrecognized order status %v", order.Status))
	}

	// remove it from mempool
	dropped := market.DropOrderFromMempool(order)
	if dropped != nil {
		market.LoadMore(order.Type, order.Id)
	}

	err := db.DoBeginSerializable(func(tx *db.ModelTx) {
		// save the status as canceled
		order.Status = ORDER_STATUS_CANCELED
		UpdateOrder(tx, order)

		// return the reserved funds
		ReleaseReservedFundsForOrder(tx, order)
	})
	if err != nil {
		panic(err)
	}
	return order
}
Пример #3
0
// plog: The basis PriceLog entry to save
// nextTime: The next PriceLog entry will have this time.
//  * This is how we know whether to finalize parent blogs.
func (logger *PriceLogger) addPriceLog(plog *PriceLog, nextTime int64) {
	if plog.Interval != BasisInterval {
		panic("addPriceLog() expects smallest interval")
	}
	if plog.Time%plog.Interval != 0 {
		panic("plog.Time % Interval should be zero")
	}
	if plog.Market != logger.Market {
		panic("plog.Market wasn't logger.market")
	}

	logger.entries = append(logger.entries, plog)

	toSave := []*PriceLog{plog}
	for i, interval := range Intervals {
		if i == 0 {
			continue
		}
		// If this interval window has closed...
		//if (plog.Time / interval) < (nextTime / interval) {
		toSave = append(toSave, logger.computeForInterval(plog.Time, interval))
		//}
	}

	// TODO: this doesn't have to be serializable
	err := db.DoBeginSerializable(func(tx *db.ModelTx) {
		for _, plog := range toSave {
			SaveOrUpdatePriceLog(tx, plog)
		}
	})
	if err != nil {
		panic(err)
	}

}
Пример #4
0
func DepositMoneyForUser(user *auth.User, coin string, amount uint64) {
	err := db.DoBeginSerializable(func(tx *db.ModelTx) {
		account.UpdateBalanceByWallet(tx, user.Id, account.WALLET_MAIN, coin, int64(amount), false)
	})
	if err != nil {
		panic(err)
	}
}
Пример #5
0
func MarkPaymentsAsSpent(paymentIds []interface{}, wtxId int64) {
	err := db.DoBeginSerializable(func(tx *db.ModelTx) {
		UpdatePaymentsSpent(tx, paymentIds, PAYMENT_SPENT_STATUS_CHECKEDOUT,
			PAYMENT_SPENT_STATUS_SPENT, wtxId)
	})
	if err != nil {
		panic(err)
	}
}
Пример #6
0
func CheckoutPaymentsToSpend(paymentIds []interface{}, wtxId int64) {
	err := db.DoBeginSerializable(func(tx *db.ModelTx) {
		UpdatePaymentsSpent(tx, paymentIds, PAYMENT_SPENT_STATUS_AVAILABLE,
			PAYMENT_SPENT_STATUS_CHECKEDOUT, wtxId)
	})
	if err != nil {
		panic(err)
	}
}
Пример #7
0
func ResumeWithdrawals(wthIds []interface{}) {
	err := db.DoBeginSerializable(func(tx *db.ModelTx) {
		// update status
		UpdateWithdrawals(tx, wthIds, WITHDRAWAL_STATUS_STALLED,
			WITHDRAWAL_STATUS_PENDING, 0)
	})
	if err != nil {
		panic(err)
	}
}
Пример #8
0
func CancelWithdrawal(wth *Withdrawal) {
	err := db.DoBeginSerializable(func(tx *db.ModelTx) {
		// update status
		UpdateWithdrawals(tx, []interface{}{wth.Id}, WITHDRAWAL_STATUS_PENDING,
			WITHDRAWAL_STATUS_CANCELED, 0)
		// adjust balance
		UpdateBalanceByWallet(tx, wth.UserId, WALLET_RESERVED_WITHDRAWAL, wth.Coin, -int64(wth.Amount), true)
		UpdateBalanceByWallet(tx, wth.UserId, WALLET_MAIN, wth.Coin, int64(wth.Amount), false)
	})
	if err != nil {
		panic(err)
	}
}
Пример #9
0
func CheckoutWithdrawals(coin string, limit uint) (wths []*Withdrawal) {
	err := db.DoBeginSerializable(func(tx *db.ModelTx) {
		wths = LoadWithdrawalsByStatus(tx, coin, WITHDRAWAL_STATUS_PENDING, limit)
		wthIds := Map(wths, "Id")

		UpdateWithdrawals(tx, wthIds, WITHDRAWAL_STATUS_PENDING,
			WITHDRAWAL_STATUS_CHECKEDOUT, 0)
	})
	if err != nil {
		panic(err)
	}
	return
}
Пример #10
0
func injectMoneyForUsers(users []*auth.User, coins []string) {
	Info("Injecting money for each user")
	for _, user := range users {
		for _, coin := range coins {
			err := db.DoBeginSerializable(func(tx *db.ModelTx) {
				account.UpdateBalanceByWallet(tx, user.Id, account.WALLET_MAIN, coin, int64(100000000000000), false)
			})
			if err != nil {
				panic(err)
			}
		}
	}
	Info("Done injecting money for each user")
}
Пример #11
0
func CompleteWithdrawals(wths []*Withdrawal, wtxId int64) {
	wthIds := Map(wths, "Id")
	err := db.DoBeginSerializable(func(tx *db.ModelTx) {
		// update status
		UpdateWithdrawals(tx, wthIds, WITHDRAWAL_STATUS_CHECKEDOUT,
			WITHDRAWAL_STATUS_COMPLETE, wtxId)
		// adjust balance
		for _, wth := range wths {
			UpdateBalanceByWallet(tx, wth.UserId, WALLET_RESERVED_WITHDRAWAL, wth.Coin, -int64(wth.Amount), true)
		}
	})
	if err != nil {
		panic(err)
	}
}
Пример #12
0
// Funds are reserved by moving them to the account.WALLET_RESERVED_ORDER wallet when
// the order is saved to the DB.
// If there aren't enough funds, the order isn't saved, and an error is returned.
// The returned error.Error() is a front-end message.
func SaveAndReserveFundsForOrder(order *Order) {
	err := db.DoBeginSerializable(func(tx *db.ModelTx) {
		// Save the order, get the id
		SaveOrder(tx, order)
		// Reserve the funds
		if order.Type == ORDER_TYPE_BID {
			account.UpdateBalanceByWallet(tx, order.UserId, account.WALLET_MAIN, order.BasisCoin, -int64(order.BasisAmount+order.BasisFee), true)
			account.UpdateBalanceByWallet(tx, order.UserId, account.WALLET_RESERVED_ORDER, order.BasisCoin, int64(order.BasisAmount+order.BasisFee), false)
		} else {
			account.UpdateBalanceByWallet(tx, order.UserId, account.WALLET_MAIN, order.Coin, -int64(order.Amount), true)
			account.UpdateBalanceByWallet(tx, order.UserId, account.WALLET_RESERVED_ORDER, order.Coin, int64(order.Amount), false)
		}
	})
	if err != nil {
		panic(err)
	}
}
Пример #13
0
func AddWithdrawal(userId int64, toAddr string, coin string, amount uint64) (*Withdrawal, error) {
	wth := &Withdrawal{
		UserId:    userId,
		Wallet:    WALLET_MAIN,
		Coin:      coin,
		ToAddress: toAddr,
		Amount:    amount,
		Status:    WITHDRAWAL_STATUS_PENDING,
	}

	err := db.DoBeginSerializable(func(tx *db.ModelTx) {
		// save withdrawal
		SaveWithdrawal(tx, wth)
		// adjust balance.
		UpdateBalanceByWallet(tx, userId, WALLET_MAIN, coin, -int64(amount), true)
		UpdateBalanceByWallet(tx, userId, WALLET_RESERVED_WITHDRAWAL, coin, int64(amount), false)
	})
	return wth, err
}
Пример #14
0
func AddTransfer(fromUserId int64, fromWallet string, toUserId int64, toWallet string, coin string, amount uint64) error {
	// Create new transfer item that moves amount.
	trans := &Transfer{
		UserId:  fromUserId,
		Wallet:  fromWallet,
		User2Id: toUserId,
		Wallet2: toWallet,
		Coin:    coin,
		Amount:  amount,
		Fee:     uint64(0),
	}

	err := db.DoBeginSerializable(func(tx *db.ModelTx) {
		// Adjust balance
		UpdateBalanceByWallet(tx, trans.UserId, trans.Wallet, trans.Coin, -int64(trans.Amount), true)
		UpdateBalanceByWallet(tx, trans.User2Id, trans.Wallet2, trans.Coin, int64(trans.Amount), false)
		// Save transfer
		SaveTransfer(tx, trans)
	})
	return err
}
Пример #15
0
// Credit the user's account for the given bank deposit.
// If the deposit is already credited, do nothing.
// Must be idempotent.
func CreditDeposit(deposit *Deposit) {

	// SANITY CHECK
	if deposit.Id == 0 {
		panic(NewError("Expected a saved deposit but got a new one"))
	}
	// END SANITY CHECK

	err := db.DoBeginSerializable(func(tx *db.ModelTx) {
		// Load the corresponding deposit.
		deposit := LoadDeposit(tx, deposit.Id)
		// If the deposit isn't pending, do nothing.
		if deposit.Status != DEPOSIT_STATUS_PENDING {
			return
		}
		// Credit the account.
		UpdateBalanceByWallet(tx, deposit.UserId, deposit.Wallet, deposit.Coin, int64(deposit.Amount), false)
		UpdateDepositSetStatus(tx, deposit.Id, DEPOSIT_STATUS_CREDITED)
	})
	if err != nil {
		panic(err)
	}
}
Пример #16
0
// Executes a new order, which may or may not be a market order.
// The order should have already been saved.
// If the order is executable, it gets executed as well.
// If the order is not executable, or after execution it is
// not complete and no longer executable, it gets added
// into the mempool.
func (market *Market) ProcessOrderExecution(order *Order) {
	if order.Id == 0 {
		panic("Order hasn't been saved yet")
	}
	if order.Complete() {
		panic("New order is already complete.")
	}

	// Until order is complete, or there are no more matches...
	for {

		match := market.NextMatch(order)
		if match != nil {
			if match.Complete() {
				panic(NewError("Match %v is already complete.", match.Id))
			}

			// Figure out which is bid & ask.
			var bid, ask *Order
			if order.Type == ORDER_TYPE_BID {
				bid, ask = order, match
			} else {
				bid, ask = match, order
			}

			// Figure out how much to trade.
			tradeAmount, tradeBasis, bidBasisFee, askBasisFee := order.ComputeTradeAndFees(match)
			// Info("TradeCoin %v tradeBasis %v", tradeAmount, tradeBasis)

			// Update filled & sanity check before updating the DB.
			bid.Filled += tradeAmount
			bid.BasisFilled += tradeBasis
			bid.BasisFeeFilled += bidBasisFee
			ask.Filled += tradeAmount
			ask.BasisFilled += tradeBasis
			ask.BasisFeeFilled += askBasisFee

			// Update order & match accordingly.
			if order.Complete() {
				order.Status = ORDER_STATUS_COMPLETE
			}
			if match.Complete() {
				match.Status = ORDER_STATUS_COMPLETE
			}

			// Sanity check
			bid.Validate()
			ask.Validate()
			if askBasisFee > tradeBasis {
				panic("askBasisFee exceeded tradeBasis ?!")
			}
			if !ask.Complete() && !bid.Complete() {
				panic("Neither ask nor bid was fulfilled after trade.")
			}

			// Make trade
			trade := &Trade{
				BidUserId:   bid.UserId,
				BidOrderId:  bid.Id,
				BidBasisFee: bidBasisFee,
				AskUserId:   ask.UserId,
				AskOrderId:  ask.Id,
				AskBasisFee: askBasisFee,
				Coin:        order.Coin,
				BasisCoin:   order.BasisCoin,
				TradeAmount: tradeAmount,
				TradeBasis:  tradeBasis,
				Price:       match.Price,
			}

			// Perform transaction.
			// -> update order & match filled & status.
			// -> perform trade of coins between both users.
			// -> return unfilled reserved coins back to the account.WALLET_MAIN wallet.
			err := db.DoBeginSerializable(func(tx *db.ModelTx) {

				UpdateOrder(tx, match)
				UpdateOrder(tx, order)

				// Save trade info.
				SaveTrade(tx, trade)

				// Release remaining reserved funds
				if bid.Complete() {
					ReleaseReservedFundsForOrder(tx, bid)
				}
				if ask.Complete() {
					ReleaseReservedFundsForOrder(tx, ask)
				}

				// Trade funds & check parity in reserved wallets
				_, err := tx.Exec(`SELECT exchange_do_trade(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
					bid.Id, bid.UserId, bidBasisFee,
					ask.Id, ask.UserId, askBasisFee,
					order.BasisCoin, tradeBasis,
					order.Coin, tradeAmount,
				)
				if err != nil {
					panic(err)
				}
			})
			if err != nil {
				panic(err)
			}

			// Add trade to price log.
			market.PriceLogger.AddTrade(order.Type, tradeAmount, match.Price, trade.Time)

			// Remove match from mempool if complete.
			if match.Complete() {
				market.DropOrderFromMempool(match)
				market.LoadMore(match.Type, order.Id)
			}

			// Return if we're done with this order.
			if order.Complete() {
				return
			}

		} else {

			// There are no more matches for this order,
			// or the order isn't immediatly executable.
			// The order was already saved to DB, but
			// we need to insert it into market.Bids/Asks if in range.
			market.InsertIfInRange(order)
			return
		}
	}
}
Пример #17
0
// Returns false if no withdrawals are available to process.
func ProcessUserWithdrawals(coin string) (bool, error) {

	// Checkout withdrawals
	// TODO: Gather multiple small withdrawals.
	wths := account.CheckoutWithdrawals(coin, 1)
	if len(wths) == 0 {
		return false, nil
	}
	wthIds := Map(wths, "Id")
	amounts := map[string]uint64{}
	amountSum := uint64(0)
	for _, wth := range wths {
		if wth.Amount <= 0 {
			panic(NewError("Invalid send amount %v", wth.Amount))
		}
		amounts[wth.ToAddress] += uint64(wth.Amount)
		amountSum += uint64(wth.Amount)
	}

	// figure out which payments to use.
	signedTx, payments, minerFees, chgAddress, err := ComputeWithdrawalTransaction(coin, amounts)
	if err != nil {
		account.StallWithdrawals(wthIds)
		return false, err
	}
	paymentIds := Map(payments, "Id")

	// save withdrawal info for bookkeeping.
	wthTx := SaveWithdrawalTx(&WithdrawalTx{
		Coin:       coin,
		Type:       WITHDRAWAL_TX_TYPE_WITHDRAWAL,
		Amount:     amountSum,
		MinerFee:   minerFees,
		ChgAddress: chgAddress,
		RawTx:      signedTx,
		TxId:       bitcoin.ComputeTxId(signedTx),
	})

	// checkout those payments.
	bitcoin.CheckoutPaymentsToSpend(paymentIds, wthTx.Id)

	// TODO: the Tx should go out to our partners who sign them for us.
	// TODO: receive the signed Tx.

	// deduct change amount from system user's "change" wallet.
	// this creates a negative balance, which will revert to zero
	// when the change is received.
	if chgAddress != "" {
		changeAmount := amounts[chgAddress]
		err := db.DoBeginSerializable(func(tx *db.ModelTx) {
			account.UpdateBalanceByWallet(tx, 0, account.WALLET_CHANGE, coin, -int64(changeAmount), false)
		})
		if err != nil {
			panic(err)
		}
	}

	// broadcast transaction.
	rpc.SendRawTransaction(coin, signedTx)

	// update payments as spent.
	bitcoin.MarkPaymentsAsSpent(paymentIds, wthTx.Id)

	// update withdrawals as complete.
	account.CompleteWithdrawals(wths, wthTx.Id)

	return true, nil
}
Пример #18
0
// Create a new user.
func SaveUser(user *User) (*User, error) {

	// Create email confirmation code.
	if user.EmailCode == "" {
		user.EmailCode = RandId(24)
	}
	// Create TOTPKey.
	if len(user.TOTPKey) == 0 {
		user.TOTPKey = RandBytes(10)
	}

	// Scrypt the password.
	if user.Password != "" {
		salt := RandId(12)
		scryptPass, err := scrypt.Key([]byte(user.Password), []byte(salt), 16384, 8, 1, 32)
		if err != nil {
			return nil, err
		}
		user.Salt = []byte(salt)
		user.Scrypt = scryptPass
	}

	err := db.DoBeginSerializable(func(tx *db.ModelTx) {
		// Insert into users table.
		err := tx.QueryRow(
			`INSERT INTO auth_user (`+UserModel.FieldsInsert+`)
             VALUES (`+UserModel.Placeholders+`)
             RETURNING id`,
			user,
		).Scan(&user.Id)
		if err != nil {
			panic(err)
		}

		// Set the chain_idx
		if user.Id > math.MaxInt32 {
			panic("User autoinc id has exceeded MaxInt32")
		}
		user.ChainIdx = int32(user.Id)
		_, err = tx.Exec(
			`UPDATE auth_user
             SET chain_idx = id
             WHERE id=?`,
			user.Id,
		)
		if err != nil {
			panic(err)
		}

		// Generate an API key for the user
		apiKey := &APIKey{Key: RandId(24), UserId: user.Id}
		SaveAPIKey(tx, apiKey)

	})
	switch db.GetErrorType(err) {
	case db.ERR_DUPLICATE_ENTRY:
		return nil, ERR_DUPLICATE_ADDRESS
	case nil:
		break
	default:
		panic(err)
	}

	return user, nil
}