コード例 #1
0
ファイル: treasury.go プロジェクト: jaekwon/ftnox-backend
// Given inputs and outputs, and given that the maximum fee has already been deducted
// such that sum(inputs) - maximum_fee = sum(outputs),
// Adjust outputs such that leftover fees go into changeAddress.
// To prevent dust, if outputs[changeAddress] ends up being dust,
// it is omitted.
// 'privKeys' will be updated include all the private keys for output addresses.
func adjustMinerFee(coin string, inputs []*bitcoin.Payment, outputs map[string]uint64, changeAddress string, privKeys map[string]string) (uint64, error) {
	c := Config.GetCoin(coin)
	maxMinerFee := maxMinerFeeForCoin(coin)
	inputSum := sumInputs(inputs)
	outputSum := sumOutputs(outputs)
	if inputSum < outputSum+maxMinerFee {
		return 0, NewError("Inputs didn't exceed outputs + maximum miner fee")
	}
	collectPrivateKeys(coin, inputs, privKeys)
	// Make RPC call to sign.
	s := rpc.CreateSignedRawTransaction(coin, bitcoin.ToRPCPayments(inputs), outputs, privKeys)
	// Figure out how many base fees we actually need.
	numKBytes := len(s) / 2 / 1000
	requiredBaseFees := numKBytes + 1
	if requiredBaseFees > MAX_BASE_FEES {
		return 0, NewError("Whoa, too many base fees required: %v (max %v)", requiredBaseFees, MAX_BASE_FEES)
	}
	requiredFee := uint64(requiredBaseFees) * uint64(c.MinerFee)
	// Add remainder back to changeAddress
	if maxMinerFee > requiredFee {
		outputs[changeAddress] += (maxMinerFee - requiredFee)
	}
	// Remove dust
	if outputs[changeAddress] < c.MinerFee {
		delete(outputs, changeAddress)
	}
	return requiredFee, nil
}
コード例 #2
0
ファイル: treasury.go プロジェクト: jaekwon/ftnox-backend
// Sweeps inputs into new address(s) generated for mpk.
// - outMPK: Set outMPK to offline wallet MPK to move funds offline
// - minOutput, maxOutput: Range of permittable output transaction sizes.
//      The outputs will be spread out evenly between minOutput & maxOutput linearly
//       though if maxOutput is greater than the sum of all the inputs minus fees,
//       there will only be one output.
//      This means you could set maxOutput to MaxInt64 and you'll be guaranteed to have one output.
// - dry: dry run. The output addresses will be throwaway addresses.
func ComputeSweepTransaction(inputs []*bitcoin.Payment, outMPK *bitcoin.MPK, minOutput, maxOutput uint64, maxNumOutputs int, dry bool) (string, []*bitcoin.Payment, uint64, map[string]uint64, error) {
	returnErr := func(err error) (string, []*bitcoin.Payment, uint64, map[string]uint64, error) {
		return "", nil, 0, nil, err
	}

	var coin = inputs[0].Coin
	for _, payment := range inputs {
		if payment.Coin != coin {
			return returnErr(NewError("Expected all sweep inputs to be for coin %v", coin))
		}
	}

	// compute total inputs
	total := uint64(0)
	for _, payment := range inputs {
		total += payment.Amount
	}

	// compute sweep output spread
	outputAmounts, ok := computeSweepOutputs(total, minOutput, maxOutput, maxNumOutputs)
	if !ok {
		return returnErr(NewError("Could not satisfy output requirements"))
	}

	// Remove maxMinerFees from output initially.
	// We'll readjust later
	outputAmounts[0] -= maxMinerFeeForCoin(coin)

	// Adjust miner fees & collect private keys
	outputs := map[string]uint64{}
	changeAddress := "" // not really a change address, but remaining fees get added back here.
	for _, amount := range outputAmounts {
		address := createNewSweepAddress(coin, outMPK, dry)
		if changeAddress == "" {
			changeAddress = address
		}
		outputs[address] = amount
	}
	privKeys := map[string]string{}
	minerFee, err := adjustMinerFee(coin, inputs, outputs, changeAddress, privKeys)
	if err != nil {
		return returnErr(err)
	}

	// Sign transaction
	s := rpc.CreateSignedRawTransaction(coin, bitcoin.ToRPCPayments(inputs), outputs, privKeys)
	return s, inputs, minerFee, outputs, nil
}
コード例 #3
0
ファイル: treasury.go プロジェクト: jaekwon/ftnox-backend
// Finds payments & constructs transaction to satisfy the given output amounts.
// This function could fail, in which case we'll call it again later.
// This means that this function should be largely side-effect free.
// However, note that 'outputs' may be modified to account for
// fees & change addresses.
func ComputeWithdrawalTransaction(coin string, outputs map[string]uint64) (string, []*bitcoin.Payment, uint64, string, error) {
	returnErr := func(err error) (string, []*bitcoin.Payment, uint64, string, error) { return "", nil, 0, "", err }

	reqHeight := bitcoin.ReqHeight(coin)
	payments := []*bitcoin.Payment{}
	changeAddress := createNewChangeAddress(coin)

	sumAmount := int64(0)
	for _, amount := range outputs {
		sumAmount += int64(amount)
	}
	// Add base fees to sumAmount. Be generous, we'll adjust later.
	sumAmount += int64(bitcoin.MinerFee(coin)) * MAX_BASE_FEES
	sumAmountCopy := sumAmount
	// Then load payments greater than remainder
	for sumAmountCopy > 0 {
		// We shouldn't use too many inputs.
		if len(payments) > len(outputs)*2 {
			return returnErr(NewError("[%v] Too many inputs required for %v", coin, sumAmount))
		}
		// Try to do it with one input
		payment := bitcoin.LoadSmallestSpendablePaymentGreaterThan(hotMPK.Id, coin, uint64(sumAmountCopy), reqHeight, payments)
		if payment == nil {
			// Try to fill it as much as possible
			payment = bitcoin.LoadLargestSpendablePaymentLessThan(hotMPK.Id, coin, uint64(sumAmountCopy), reqHeight, payments)
		}
		if payment == nil {
			return returnErr(NewError("[%v] Unable to gather enough inputs for %v", coin, sumAmount))
		}
		sumAmountCopy -= int64(payment.Amount)
		payments = append(payments, payment)
	}
	// If we need to create a change address, do so.
	if sumAmountCopy != 0 {
		outputs[changeAddress] = uint64(-1 * sumAmountCopy)
	}
	// Adjust miner fees & collect private keys
	privKeys := map[string]string{}
	minerFee, err := adjustMinerFee(coin, payments, outputs, changeAddress, privKeys)
	if err != nil {
		return returnErr(err)
	}
	// Sign transaction
	s := rpc.CreateSignedRawTransaction(coin, bitcoin.ToRPCPayments(payments), outputs, privKeys)
	return s, payments, minerFee, changeAddress, nil
}