Example #1
0
// checkMinerFees checks that the total amount of transaction fees in the
// transaction set is sufficient to earn a spot in the transaction pool.
func (tp *TransactionPool) checkMinerFees(ts []types.Transaction) error {
	// Transactions cannot be added after the TransactionPoolSizeLimit has been
	// hit.
	if tp.transactionListSize > TransactionPoolSizeLimit {
		return errFullTransactionPool
	}

	// The first TransactionPoolSizeForFee transactions do not need fees.
	if tp.transactionListSize > TransactionPoolSizeForFee {
		// Currently required fees are set on a per-transaction basis. 2 coins
		// are required per transaction if the free-fee limit has been reached,
		// adding a larger fee is not useful.
		var feeSum types.Currency
		for i := range ts {
			for _, fee := range ts[i].MinerFees {
				feeSum = feeSum.Add(fee)
			}
		}
		feeRequired := TransactionMinFee.Mul(types.NewCurrency64(uint64(len(ts))))
		if feeSum.Cmp(feeRequired) < 0 {
			return errLowMinerFees
		}
	}
	return nil
}
Example #2
0
// currencyUnits converts a types.Currency to a string with human-readable
// units. The unit used will be the largest unit that results in a value
// greater than 1. The value is rounded to 4 significant digits.
func currencyUnits(c types.Currency) string {
	pico := types.SiacoinPrecision.Div64(1e12)
	if c.Cmp(pico) < 0 {
		return c.String() + " H"
	}

	// iterate until we find a unit greater than c
	mag := pico
	unit := ""
	for _, unit = range []string{"pS", "nS", "uS", "mS", "SC", "KS", "MS", "GS", "TS"} {
		if c.Cmp(mag.Mul64(1e3)) < 0 {
			break
		} else if unit != "TS" {
			// don't want to perform this multiply on the last iter; that
			// would give us 1.235 TS instead of 1235 TS
			mag = mag.Mul64(1e3)
		}
	}

	num := new(big.Rat).SetInt(c.Big())
	denom := new(big.Rat).SetInt(mag.Big())
	res, _ := new(big.Rat).Mul(num, denom.Inv(denom)).Float64()

	return fmt.Sprintf("%.4g %s", res, unit)
}
Example #3
0
File: valid.go Project: mm3/Sia
// validUnconfirmedSiafunds checks that all siafund inputs and outputs are
// valid within the context of the unconfirmed consensus set.
func (tp *TransactionPool) validUnconfirmedSiafunds(t types.Transaction) (err error) {
	var inputSum types.Currency
	for _, sfi := range t.SiafundInputs {
		// Check that the corresponding siafund output being spent exists.
		sfo, exists := tp.siafundOutputs[sfi.ParentID]
		if !exists {
			return errors.New("transaction spends unrecognized siafund output")
		}

		// Check that the unlock conditions match the unlock hash of the
		// corresponding output.
		if sfi.UnlockConditions.UnlockHash() != sfo.UnlockHash {
			return errors.New("transaction contains invalid unlock conditions (hash mismatch)")
		}

		// Add this input's value to the inputSum.
		inputSum = inputSum.Add(sfo.Value)
	}

	// Check that the value of the outputs equal the value of the inputs.
	var outputSum types.Currency
	for _, sfo := range t.SiafundOutputs {
		outputSum = outputSum.Add(sfo.Value)
	}
	if outputSum.Cmp(inputSum) != 0 {
		return errors.New("siafund inputs do not equal siafund outputs")
	}

	return
}
Example #4
0
// validSiafunds checks that the siafund portions of the transaction are valid
// in the context of the consensus set.
func validSiafunds(tx *bolt.Tx, t types.Transaction) (err error) {
	// Compare the number of input siafunds to the output siafunds.
	var siafundInputSum types.Currency
	var siafundOutputSum types.Currency
	for _, sfi := range t.SiafundInputs {
		sfo, err := getSiafundOutput(tx, sfi.ParentID)
		if err != nil {
			return err
		}

		// Check the unlock conditions match the unlock hash.
		if sfi.UnlockConditions.UnlockHash() != sfo.UnlockHash {
			return errWrongUnlockConditions
		}

		siafundInputSum = siafundInputSum.Add(sfo.Value)
	}
	for _, sfo := range t.SiafundOutputs {
		siafundOutputSum = siafundOutputSum.Add(sfo.Value)
	}
	if siafundOutputSum.Cmp(siafundInputSum) != 0 {
		return errSiafundInputOutputMismatch
	}
	return
}
// validSiafunds checks that the siafund portions of the transaction are valid
// in the context of the consensus set.
func (cs *ConsensusSet) validSiafunds(t types.Transaction) (err error) {
	// Compare the number of input siafunds to the output siafunds.
	var siafundInputSum types.Currency
	var siafundOutputSum types.Currency
	for _, sfi := range t.SiafundInputs {
		exists := cs.db.inSiafundOutputs(sfi.ParentID)
		if !exists {
			return ErrMissingSiafundOutput
		}
		sfo := cs.db.getSiafundOutputs(sfi.ParentID)

		// Check the unlock conditions match the unlock hash.
		if sfi.UnlockConditions.UnlockHash() != sfo.UnlockHash {
			return ErrWrongUnlockConditions
		}

		siafundInputSum = siafundInputSum.Add(sfo.Value)
	}
	for _, sfo := range t.SiafundOutputs {
		siafundOutputSum = siafundOutputSum.Add(sfo.Value)
	}
	if siafundOutputSum.Cmp(siafundInputSum) != 0 {
		return ErrSiafundInputOutputMismatch
	}
	return
}
Example #6
0
// newRevision revises the current revision to incorporate new data.
func newRevision(rev types.FileContractRevision, pieceLen uint64, merkleRoot crypto.Hash, piecePrice types.Currency) types.FileContractRevision {
	// prevent a negative currency panic
	if piecePrice.Cmp(rev.NewValidProofOutputs[0].Value) > 0 {
		// probably not enough money, but the host might accept it anyway
		piecePrice = rev.NewValidProofOutputs[0].Value
	}
	return types.FileContractRevision{
		ParentID:          rev.ParentID,
		UnlockConditions:  rev.UnlockConditions,
		NewRevisionNumber: rev.NewRevisionNumber + 1,
		NewFileSize:       rev.NewFileSize + pieceLen,
		NewFileMerkleRoot: merkleRoot,
		NewWindowStart:    rev.NewWindowStart,
		NewWindowEnd:      rev.NewWindowEnd,
		NewValidProofOutputs: []types.SiacoinOutput{
			// less returned to renter
			{Value: rev.NewValidProofOutputs[0].Value.Sub(piecePrice), UnlockHash: rev.NewValidProofOutputs[0].UnlockHash},
			// more given to host
			{Value: rev.NewValidProofOutputs[1].Value.Add(piecePrice), UnlockHash: rev.NewValidProofOutputs[1].UnlockHash},
		},
		NewMissedProofOutputs: []types.SiacoinOutput{
			// less returned to renter
			{Value: rev.NewMissedProofOutputs[0].Value.Sub(piecePrice), UnlockHash: rev.NewMissedProofOutputs[0].UnlockHash},
			// more given to void
			{Value: rev.NewMissedProofOutputs[1].Value.Add(piecePrice), UnlockHash: rev.NewMissedProofOutputs[1].UnlockHash},
		},
		NewUnlockHash: rev.NewUnlockHash,
	}
}
Example #7
0
// validSiacoins checks that the siacoin inputs and outputs are valid in the
// context of the current consensus set.
func validSiacoins(tx *bolt.Tx, t types.Transaction) error {
	scoBucket := tx.Bucket(SiacoinOutputs)
	var inputSum types.Currency
	for _, sci := range t.SiacoinInputs {
		// Check that the input spends an existing output.
		scoBytes := scoBucket.Get(sci.ParentID[:])
		if scoBytes == nil {
			return errMissingSiacoinOutput
		}

		// Check that the unlock conditions match the required unlock hash.
		var sco types.SiacoinOutput
		err := encoding.Unmarshal(scoBytes, &sco)
		if build.DEBUG && err != nil {
			panic(err)
		}
		if sci.UnlockConditions.UnlockHash() != sco.UnlockHash {
			return errWrongUnlockConditions
		}

		inputSum = inputSum.Add(sco.Value)
	}
	if inputSum.Cmp(t.SiacoinOutputSum()) != 0 {
		return errSiacoinInputOutputMismatch
	}
	return nil
}
Example #8
0
// wallettransactionscmd lists all of the transactions related to the wallet,
// providing a net flow of siacoins and siafunds for each.
func wallettransactionscmd() {
	wtg := new(api.WalletTransactionsGET)
	err := getAPI("/wallet/transactions?startheight=0&endheight=10000000", wtg)
	if err != nil {
		fmt.Println("Could not fetch transaction history:", err)
		return
	}

	fmt.Println("    [height]                                                   [transaction id]    [net siacoins]   [net siafunds]")
	txns := append(wtg.ConfirmedTransactions, wtg.UnconfirmedTransactions...)
	for _, txn := range txns {
		// Determine the number of outgoing siacoins and siafunds.
		var outgoingSiacoins types.Currency
		var outgoingSiafunds types.Currency
		for _, input := range txn.Inputs {
			if input.FundType == types.SpecifierSiacoinInput && input.WalletAddress {
				outgoingSiacoins = outgoingSiacoins.Add(input.Value)
			}
			if input.FundType == types.SpecifierSiafundInput && input.WalletAddress {
				outgoingSiafunds = outgoingSiafunds.Add(input.Value)
			}
		}

		// Determine the number of incoming siacoins and siafunds.
		var incomingSiacoins types.Currency
		var incomingSiafunds types.Currency
		for _, output := range txn.Outputs {
			if output.FundType == types.SpecifierMinerPayout {
				incomingSiacoins = incomingSiacoins.Add(output.Value)
			}
			if output.FundType == types.SpecifierSiacoinOutput && output.WalletAddress {
				incomingSiacoins = incomingSiacoins.Add(output.Value)
			}
			if output.FundType == types.SpecifierSiafundOutput && output.WalletAddress {
				incomingSiafunds = incomingSiafunds.Add(output.Value)
			}
		}

		// Convert the siacoins to a float.
		incomingSiacoinsFloat, _ := new(big.Rat).SetFrac(incomingSiacoins.Big(), types.SiacoinPrecision.Big()).Float64()
		outgoingSiacoinsFloat, _ := new(big.Rat).SetFrac(outgoingSiacoins.Big(), types.SiacoinPrecision.Big()).Float64()

		// Print the results.
		if txn.ConfirmationHeight < 1e9 {
			fmt.Printf("%12v", txn.ConfirmationHeight)
		} else {
			fmt.Printf(" unconfirmed")
		}
		fmt.Printf("%67v%15.2f SC", txn.TransactionID, incomingSiacoinsFloat-outgoingSiacoinsFloat)
		// For siafunds, need to avoid having a negative types.Currency.
		if incomingSiafunds.Cmp(outgoingSiafunds) >= 0 {
			fmt.Printf("%14v SF\n", incomingSiafunds.Sub(outgoingSiafunds))
		} else {
			fmt.Printf("-%14v SF\n", outgoingSiafunds.Sub(incomingSiafunds))
		}
	}
}
Example #9
0
// TestRPCUPload attempts to upload a file to the host, adding coverage to the
// upload function.
func TestRPCUpload(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	ht, err := newHostTester("TestRPCUpload")
	if err != nil {
		t.Fatal(err)
	}
	ht.host.mu.RLock()
	baselineAnticipatedRevenue := ht.host.anticipatedRevenue
	baselineSpace := ht.host.spaceRemaining
	ht.host.mu.RUnlock()
	_, err = ht.uploadFile("TestRPCUpload - 1", renewDisabled)
	if err != nil {
		t.Fatal(err)
	}

	var expectedRevenue types.Currency
	func() {
		ht.host.mu.RLock()
		defer ht.host.mu.RUnlock()

		if ht.host.anticipatedRevenue.Cmp(baselineAnticipatedRevenue) <= 0 {
			t.Error("Anticipated revenue did not increase after a file was uploaded")
		}
		if baselineSpace <= ht.host.spaceRemaining {
			t.Error("space remaining on the host does not seem to have decreased")
		}
		expectedRevenue = ht.host.anticipatedRevenue
	}()

	// Mine until the storage proof goes through, and the obligation gets
	// cleared.
	for i := types.BlockHeight(0); i <= testUploadDuration+confirmationRequirement+defaultWindowSize; i++ {
		_, err := ht.miner.AddBlock()
		if err != nil {
			t.Fatal(err)
		}
	}

	// Check that the storage proof has succeeded.
	ht.host.mu.Lock()
	defer ht.host.mu.Unlock()
	if len(ht.host.obligationsByID) != 0 {
		t.Error("host still has obligation, when it should have completed the obligation and submitted a storage proof.")
	}
	if !ht.host.anticipatedRevenue.IsZero() {
		t.Error("host anticipated revenue was not set back to zero")
	}
	if ht.host.spaceRemaining != baselineSpace {
		t.Error("host does not seem to have reclaimed the space after a successful obligation")
	}
	if expectedRevenue.Cmp(ht.host.revenue) != 0 {
		t.Error("host's revenue was not moved from anticipated to expected")
	}
}
Example #10
0
// validFileContractRevision checks that each file contract revision is valid
// in the context of the current consensus set.
func (cs *ConsensusSet) validFileContractRevisions(t types.Transaction) (err error) {
	for _, fcr := range t.FileContractRevisions {
		// Check that the revision revises an existing contract.
		exists := cs.db.inFileContracts(fcr.ParentID)
		if !exists {
			return ErrUnrecognizedFileContractID
		}
		fc := cs.db.getFileContracts(fcr.ParentID)

		// Check that the height is less than fc.WindowStart - revisions are
		// not allowed to be submitted once the storage proof window has
		// opened.  This reduces complexity for unconfirmed transactions.
		if cs.height() > fc.WindowStart {
			return ErrLateRevision
		}

		// Check that the revision number of the revision is greater than the
		// revision number of the existing file contract.
		if fc.RevisionNumber >= fcr.NewRevisionNumber {
			return ErrLowRevisionNumber
		}

		// Check that the unlock conditions match the unlock hash.
		if fcr.UnlockConditions.UnlockHash() != fc.UnlockHash {
			return ErrWrongUnlockConditions
		}

		// Check that the payout of the revision matches the payout of the
		// original, and that the payouts match eachother.
		var validPayout, missedPayout types.Currency
		for _, output := range fcr.NewValidProofOutputs {
			validPayout = validPayout.Add(output.Value)
		}
		for _, output := range fcr.NewMissedProofOutputs {
			missedPayout = missedPayout.Add(output.Value)
		}
		if validPayout.Cmp(fc.Payout.Sub(fc.Tax())) != 0 {
			return ErrAlteredRevisionPayouts
		}
		if missedPayout.Cmp(fc.Payout.Sub(fc.Tax())) != 0 {
			return ErrAlteredRevisionPayouts
		}
	}

	return
}
Example #11
0
// validFileContractRevision checks that each file contract revision is valid
// in the context of the current consensus set.
func validFileContractRevisions(tx *bolt.Tx, t types.Transaction) error {
	for _, fcr := range t.FileContractRevisions {
		fc, err := getFileContract(tx, fcr.ParentID)
		if err != nil {
			return err
		}

		// Check that the height is less than fc.WindowStart - revisions are
		// not allowed to be submitted once the storage proof window has
		// opened.  This reduces complexity for unconfirmed transactions.
		if blockHeight(tx) > fc.WindowStart {
			return errLateRevision
		}

		// Check that the revision number of the revision is greater than the
		// revision number of the existing file contract.
		if fc.RevisionNumber >= fcr.NewRevisionNumber {
			return errLowRevisionNumber
		}

		// Check that the unlock conditions match the unlock hash.
		if fcr.UnlockConditions.UnlockHash() != fc.UnlockHash {
			return errWrongUnlockConditions
		}

		// Check that the payout of the revision matches the payout of the
		// original, and that the payouts match each other.
		var validPayout, missedPayout, oldPayout types.Currency
		for _, output := range fcr.NewValidProofOutputs {
			validPayout = validPayout.Add(output.Value)
		}
		for _, output := range fcr.NewMissedProofOutputs {
			missedPayout = missedPayout.Add(output.Value)
		}
		for _, output := range fc.ValidProofOutputs {
			oldPayout = oldPayout.Add(output.Value)
		}
		if validPayout.Cmp(oldPayout) != 0 {
			return errAlteredRevisionPayouts
		}
		if missedPayout.Cmp(oldPayout) != 0 {
			return errAlteredRevisionPayouts
		}
	}
	return nil
}
Example #12
0
// checkSiafundCount checks that the number of siafunds countable within the
// consensus set equal the expected number of siafunds for the block height.
func checkSiafundCount(tx *bolt.Tx) {
	var total types.Currency
	err := tx.Bucket(SiafundOutputs).ForEach(func(_, siafundOutputBytes []byte) error {
		var sfo types.SiafundOutput
		err := encoding.Unmarshal(siafundOutputBytes, &sfo)
		if err != nil {
			manageErr(tx, err)
		}
		total = total.Add(sfo.Value)
		return nil
	})
	if err != nil {
		manageErr(tx, err)
	}
	if total.Cmp(types.SiafundCount) != 0 {
		manageErr(tx, errors.New("wrong number if siafunds in the consensus set"))
	}
}
Example #13
0
// nodeAtWeight grabs an element in the tree that appears at the given weight.
// Though the tree has an arbitrary sorting, a sufficiently random weight will
// pull a random element. The tree is searched through in a post-ordered way.
func (hn *hostNode) nodeAtWeight(weight types.Currency) (*hostNode, error) {
	// Sanity check - weight must be less than the total weight of the tree.
	if weight.Cmp(hn.weight) > 0 {
		return nil, errOverweight
	}

	// Check if the left or right child should be returned.
	if hn.left != nil {
		if weight.Cmp(hn.left.weight) < 0 {
			return hn.left.nodeAtWeight(weight)
		}
		weight = weight.Sub(hn.left.weight) // Search from 0th index of right side.
	}
	if hn.right != nil && weight.Cmp(hn.right.weight) < 0 {
		return hn.right.nodeAtWeight(weight)
	}

	// Sanity check
	if build.DEBUG && !hn.taken {
		build.Critical("nodeAtWeight should not be returning a nil entry")
	}

	// Return the root entry.
	return hn, nil
}
Example #14
0
File: valid.go Project: mm3/Sia
// validUnconfirmedFileContractRevisions checks that all file contract
// revisions are valid within the context of the unconfirmed consensus set.
func (tp *TransactionPool) validUnconfirmedFileContractRevisions(t types.Transaction) (err error) {
	for _, fcr := range t.FileContractRevisions {
		// Check for the corresponding file contract in the unconfirmed set.
		fc, exists := tp.fileContracts[fcr.ParentID]
		if !exists {
			return errors.New("revision given for unrecognized file contract")
		}

		// Check that the revision was submitted before the storage proof
		// window opened.
		if tp.consensusSetHeight > fc.WindowStart {
			return errors.New("revision submitted too late")
		}

		// Check that the revision number is increasing as a result of the
		// revision.
		if fc.RevisionNumber >= fcr.NewRevisionNumber {
			return errors.New("contract revision is outdated")
		}

		// Check that the unlock conditions match the unlock hash of the
		// corresponding file contract.
		if fcr.UnlockConditions.UnlockHash() != fc.UnlockHash {
			return errors.New("unlock conditions do not meet required unlock hash")
		}

		// Check that the payouts in the revision add up to the payout of the
		// contract.
		var payout types.Currency
		for _, output := range fcr.NewMissedProofOutputs {
			payout = payout.Add(output.Value)
		}
		if payout.Cmp(fc.Payout) != 0 {
			return errors.New("contract revision has incorrect payouts")
		}
	}
	return
}
Example #15
0
File: valid.go Project: mm3/Sia
// validUnconfirmedSiacoins checks that all siacoin inputs and outputs are
// valid in the context of the unconfirmed consensus set.
func (tp *TransactionPool) validUnconfirmedSiacoins(t types.Transaction) (err error) {
	var inputSum types.Currency
	for _, sci := range t.SiacoinInputs {
		// All inputs must have corresponding outputs in the unconfirmed set.
		sco, exists := tp.siacoinOutputs[sci.ParentID]
		if !exists {
			return ErrUnrecognizedSiacoinInput
		}

		// The unlock conditions provided must match the unlock hash of the
		// corresponding output.
		if sci.UnlockConditions.UnlockHash() != sco.UnlockHash {
			return ErrBadUnlockConditions
		}

		inputSum = inputSum.Add(sco.Value)
	}

	// The sum of all inputs must equal the sum of all outputs.
	if inputSum.Cmp(t.SiacoinOutputSum()) != 0 {
		return ErrSiacoinOverspend
	}
	return
}
Example #16
0
// SendSiagSiafunds sends siafunds to another address. The siacoins stored in
// the siafunds are sent to an address in the wallet.
func (w *Wallet) SendSiagSiafunds(amount types.Currency, dest types.UnlockHash, keyfiles []string) (types.Transaction, error) {
	if len(keyfiles) < 1 {
		return types.Transaction{}, ErrNoKeyfile
	}

	// Load the siafund keys and verify they are sufficient to sign the
	// transaction.
	skps := make([]SiagKeyPair, len(keyfiles))
	for i, keyfile := range keyfiles {
		err := encoding.ReadFile(keyfile, &skps[i])
		if err != nil {
			return types.Transaction{}, err
		}

		if skps[i].Header != SiagFileHeader {
			return types.Transaction{}, ErrUnknownHeader
		}
		if skps[i].Version != SiagFileVersion {
			return types.Transaction{}, ErrUnknownVersion
		}
	}

	// Check that all of the loaded files have the same address, and that there
	// are enough to create the transaction.
	baseUnlockHash := skps[0].UnlockConditions.UnlockHash()
	for _, skp := range skps {
		if skp.UnlockConditions.UnlockHash() != baseUnlockHash {
			return types.Transaction{}, ErrInconsistentKeys
		}
	}
	if uint64(len(skps)) < skps[0].UnlockConditions.SignaturesRequired {
		return types.Transaction{}, ErrInsufficientKeys
	}

	// Check that there are enough siafunds in the key to complete the spend.
	lockID := w.mu.RLock()
	var availableSiafunds types.Currency
	var sfoids []types.SiafundOutputID
	for sfoid, sfo := range w.siafundOutputs {
		if sfo.UnlockHash == baseUnlockHash {
			availableSiafunds = availableSiafunds.Add(sfo.Value)
			sfoids = append(sfoids, sfoid)
		}
		if availableSiafunds.Cmp(amount) >= 0 {
			break
		}
	}
	w.mu.RUnlock(lockID)
	if availableSiafunds.Cmp(amount) < 0 {
		return types.Transaction{}, ErrInsufficientSiafunds
	}

	// Truncate the keys to exactly the number needed.
	skps = skps[:skps[0].UnlockConditions.SignaturesRequired]

	// Assemble the base transction, including a 10 siacoin fee if possible.
	id, err := w.RegisterTransaction(types.Transaction{})
	if err != nil {
		return types.Transaction{}, err
	}
	// Add a miner fee - if funding the transaction fails, we'll just send a
	// transaction with no fee.
	txn, err := w.FundTransaction(id, types.NewCurrency64(TransactionFee))
	if err == nil {
		txn, _, err = w.AddMinerFee(id, types.NewCurrency64(TransactionFee))
		if err != nil {
			return types.Transaction{}, err
		}
	}
	// Add the siafund inputs to the transcation.
	for _, sfoid := range sfoids {
		// Get an address for the siafund claims.
		lockID := w.mu.Lock()
		claimDest, _, err := w.coinAddress(false)
		w.mu.Unlock(lockID)
		if err != nil {
			return types.Transaction{}, err
		}

		// Assemble the SiafundInput to spend this output.
		sfi := types.SiafundInput{
			ParentID:         sfoid,
			UnlockConditions: skps[0].UnlockConditions,
			ClaimUnlockHash:  claimDest,
		}
		txn, _, err = w.AddSiafundInput(id, sfi)
		if err != nil {
			return types.Transaction{}, err
		}
	}
	// Add the siafund output to the transaction.
	sfo := types.SiafundOutput{
		Value:      amount,
		UnlockHash: dest,
	}
	txn, _, err = w.AddSiafundOutput(id, sfo)
	if err != nil {
		return types.Transaction{}, err
	}
	// Add a refund siafund output if needed.
	if amount.Cmp(availableSiafunds) != 0 {
		refund := availableSiafunds.Sub(amount)
		sfo := types.SiafundOutput{
			Value:      refund,
			UnlockHash: baseUnlockHash,
		}
		txn, _, err = w.AddSiafundOutput(id, sfo)
		if err != nil {
			return types.Transaction{}, err
		}
	}
	// Add signatures for the siafund inputs.
	sigIndex := 0
	for _, sfoid := range sfoids {
		for _, key := range skps {
			txnSig := types.TransactionSignature{
				ParentID:       crypto.Hash(sfoid),
				CoveredFields:  types.CoveredFields{WholeTransaction: true},
				PublicKeyIndex: uint64(key.Index),
			}
			txn.TransactionSignatures = append(txn.TransactionSignatures, txnSig)
			sigHash := txn.SigHash(sigIndex)
			encodedSig, err := crypto.SignHash(sigHash, key.SecretKey)
			if err != nil {
				return types.Transaction{}, err
			}
			txn.TransactionSignatures[sigIndex].Signature = encodedSig[:]

			txn, _, err = w.AddTransactionSignature(id, txn.TransactionSignatures[sigIndex])
			if err != nil {
				return types.Transaction{}, err
			}

			sigIndex++
		}
	}

	// Sign the transaction.
	txn, err = w.SignTransaction(id, true)
	if err != nil {
		return types.Transaction{}, err
	}

	err = w.tpool.AcceptTransaction(txn)
	if err != nil {
		return types.Transaction{}, err
	}
	return txn, nil
}
Example #17
0
// FundSiacoins will add a siacoin input of exaclty 'amount' to the
// transaction. A parent transaction may be needed to achieve an input with the
// correct value. The siacoin input will not be signed until 'Sign' is called
// on the transaction builder.
func (tb *transactionBuilder) FundSiacoins(amount types.Currency) error {
	tb.wallet.mu.Lock()
	defer tb.wallet.mu.Unlock()

	// Collect a value-sorted set of siacoin outputs.
	var so sortedOutputs
	for scoid, sco := range tb.wallet.siacoinOutputs {
		so.ids = append(so.ids, scoid)
		so.outputs = append(so.outputs, sco)
	}
	// Add all of the unconfirmed outputs as well.
	for _, upt := range tb.wallet.unconfirmedProcessedTransactions {
		for i, sco := range upt.Transaction.SiacoinOutputs {
			// Determine if the output belongs to the wallet.
			_, exists := tb.wallet.keys[sco.UnlockHash]
			if !exists {
				continue
			}
			so.ids = append(so.ids, upt.Transaction.SiacoinOutputID(uint64(i)))
			so.outputs = append(so.outputs, sco)
		}
	}
	sort.Sort(sort.Reverse(so))

	// Create and fund a parent transaction that will add the correct amount of
	// siacoins to the transaction.
	var fund types.Currency
	// potentialFund tracks the balance of the wallet including outputs that
	// have been spent in other unconfirmed transactions recently. This is to
	// provide the user with a more useful error message in the event that they
	// are overspending.
	var potentialFund types.Currency
	parentTxn := types.Transaction{}
	var spentScoids []types.SiacoinOutputID
	for i := range so.ids {
		scoid := so.ids[i]
		sco := so.outputs[i]
		// Check that this output has not recently been spent by the wallet.
		spendHeight := tb.wallet.spentOutputs[types.OutputID(scoid)]
		// Prevent an underflow error.
		allowedHeight := tb.wallet.consensusSetHeight - RespendTimeout
		if tb.wallet.consensusSetHeight < RespendTimeout {
			allowedHeight = 0
		}
		if spendHeight > allowedHeight {
			potentialFund = potentialFund.Add(sco.Value)
			continue
		}
		outputUnlockConditions := tb.wallet.keys[sco.UnlockHash].UnlockConditions
		if tb.wallet.consensusSetHeight < outputUnlockConditions.Timelock {
			continue
		}

		// Add a siacoin input for this output.
		sci := types.SiacoinInput{
			ParentID:         scoid,
			UnlockConditions: outputUnlockConditions,
		}
		parentTxn.SiacoinInputs = append(parentTxn.SiacoinInputs, sci)
		spentScoids = append(spentScoids, scoid)

		// Add the output to the total fund
		fund = fund.Add(sco.Value)
		potentialFund = potentialFund.Add(sco.Value)
		if fund.Cmp(amount) >= 0 {
			break
		}
	}
	if potentialFund.Cmp(amount) >= 0 && fund.Cmp(amount) < 0 {
		return modules.ErrPotentialDoubleSpend
	}
	if fund.Cmp(amount) < 0 {
		return modules.ErrLowBalance
	}

	// Create and add the output that will be used to fund the standard
	// transaction.
	parentUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
	if err != nil {
		return err
	}
	exactOutput := types.SiacoinOutput{
		Value:      amount,
		UnlockHash: parentUnlockConditions.UnlockHash(),
	}
	parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, exactOutput)

	// Create a refund output if needed.
	if amount.Cmp(fund) != 0 {
		refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
		if err != nil {
			return err
		}
		refundOutput := types.SiacoinOutput{
			Value:      fund.Sub(amount),
			UnlockHash: refundUnlockConditions.UnlockHash(),
		}
		parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, refundOutput)
	}

	// Sign all of the inputs to the parent trancstion.
	for _, sci := range parentTxn.SiacoinInputs {
		_, err := addSignatures(&parentTxn, types.FullCoveredFields, sci.UnlockConditions, crypto.Hash(sci.ParentID), tb.wallet.keys[sci.UnlockConditions.UnlockHash()])
		if err != nil {
			return err
		}
	}
	// Mark the parent output as spent. Must be done after the transaction is
	// finished because otherwise the txid and output id will change.
	tb.wallet.spentOutputs[types.OutputID(parentTxn.SiacoinOutputID(0))] = tb.wallet.consensusSetHeight

	// Add the exact output.
	newInput := types.SiacoinInput{
		ParentID:         parentTxn.SiacoinOutputID(0),
		UnlockConditions: parentUnlockConditions,
	}
	tb.newParents = append(tb.newParents, len(tb.parents))
	tb.parents = append(tb.parents, parentTxn)
	tb.siacoinInputs = append(tb.siacoinInputs, len(tb.transaction.SiacoinInputs))
	tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, newInput)

	// Mark all outputs that were spent as spent.
	for _, scoid := range spentScoids {
		tb.wallet.spentOutputs[types.OutputID(scoid)] = tb.wallet.consensusSetHeight
	}
	return nil
}
Example #18
0
// FundSiafunds will add a siafund input of exaclty 'amount' to the
// transaction. A parent transaction may be needed to achieve an input with the
// correct value. The siafund input will not be signed until 'Sign' is called
// on the transaction builder.
func (tb *transactionBuilder) FundSiafunds(amount types.Currency) error {
	tb.wallet.mu.Lock()
	defer tb.wallet.mu.Unlock()

	// Create and fund a parent transaction that will add the correct amount of
	// siafunds to the transaction.
	var fund types.Currency
	var potentialFund types.Currency
	parentTxn := types.Transaction{}
	var spentSfoids []types.SiafundOutputID
	for sfoid, sfo := range tb.wallet.siafundOutputs {
		// Check that this output has not recently been spent by the wallet.
		spendHeight := tb.wallet.spentOutputs[types.OutputID(sfoid)]
		// Prevent an underflow error.
		allowedHeight := tb.wallet.consensusSetHeight - RespendTimeout
		if tb.wallet.consensusSetHeight < RespendTimeout {
			allowedHeight = 0
		}
		if spendHeight > allowedHeight {
			potentialFund = potentialFund.Add(sfo.Value)
			continue
		}
		outputUnlockConditions := tb.wallet.keys[sfo.UnlockHash].UnlockConditions
		if tb.wallet.consensusSetHeight < outputUnlockConditions.Timelock {
			continue
		}

		// Add a siafund input for this output.
		parentClaimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
		if err != nil {
			return err
		}
		sfi := types.SiafundInput{
			ParentID:         sfoid,
			UnlockConditions: outputUnlockConditions,
			ClaimUnlockHash:  parentClaimUnlockConditions.UnlockHash(),
		}
		parentTxn.SiafundInputs = append(parentTxn.SiafundInputs, sfi)
		spentSfoids = append(spentSfoids, sfoid)

		// Add the output to the total fund
		fund = fund.Add(sfo.Value)
		potentialFund = potentialFund.Add(sfo.Value)
		if fund.Cmp(amount) >= 0 {
			break
		}
	}
	if potentialFund.Cmp(amount) >= 0 && fund.Cmp(amount) < 0 {
		return modules.ErrPotentialDoubleSpend
	}
	if fund.Cmp(amount) < 0 {
		return modules.ErrLowBalance
	}

	// Create and add the output that will be used to fund the standard
	// transaction.
	parentUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
	if err != nil {
		return err
	}
	exactOutput := types.SiafundOutput{
		Value:      amount,
		UnlockHash: parentUnlockConditions.UnlockHash(),
	}
	parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, exactOutput)

	// Create a refund output if needed.
	if amount.Cmp(fund) != 0 {
		refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
		if err != nil {
			return err
		}
		refundOutput := types.SiafundOutput{
			Value:      fund.Sub(amount),
			UnlockHash: refundUnlockConditions.UnlockHash(),
		}
		parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, refundOutput)
	}

	// Sign all of the inputs to the parent trancstion.
	for _, sfi := range parentTxn.SiafundInputs {
		_, err := addSignatures(&parentTxn, types.FullCoveredFields, sfi.UnlockConditions, crypto.Hash(sfi.ParentID), tb.wallet.keys[sfi.UnlockConditions.UnlockHash()])
		if err != nil {
			return err
		}
	}

	// Add the exact output.
	claimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
	if err != nil {
		return err
	}
	newInput := types.SiafundInput{
		ParentID:         parentTxn.SiafundOutputID(0),
		UnlockConditions: parentUnlockConditions,
		ClaimUnlockHash:  claimUnlockConditions.UnlockHash(),
	}
	tb.newParents = append(tb.newParents, len(tb.parents))
	tb.parents = append(tb.parents, parentTxn)
	tb.siafundInputs = append(tb.siafundInputs, len(tb.transaction.SiafundInputs))
	tb.transaction.SiafundInputs = append(tb.transaction.SiafundInputs, newInput)

	// Mark all outputs that were spent as spent.
	for _, sfoid := range spentSfoids {
		tb.wallet.spentOutputs[types.OutputID(sfoid)] = tb.wallet.consensusSetHeight
	}
	return nil
}
Example #19
0
// checkDSCOs scans the sets of delayed siacoin outputs and checks for
// consistency.
func checkDSCOs(tx *bolt.Tx) {
	// Create a map to track which delayed siacoin output maps exist, and
	// another map to track which ids have appeared in the dsco set.
	dscoTracker := make(map[types.BlockHeight]struct{})
	idMap := make(map[types.SiacoinOutputID]struct{})

	// Iterate through all the buckets looking for the delayed siacoin output
	// buckets, and check that they are for the correct heights.
	err := tx.ForEach(func(name []byte, b *bolt.Bucket) error {
		// If the bucket is not a delayed siacoin output bucket or a file
		// contract expiration bucket, skip.
		if !bytes.HasPrefix(name, prefixDSCO) {
			return nil
		}

		// Add the bucket to the dscoTracker.
		var height types.BlockHeight
		err := encoding.Unmarshal(name[len(prefixDSCO):], &height)
		if err != nil {
			manageErr(tx, err)
		}
		_, exists := dscoTracker[height]
		if exists {
			return errors.New("repeat dsco map")
		}
		dscoTracker[height] = struct{}{}

		var total types.Currency
		err = b.ForEach(func(idBytes, delayedOutput []byte) error {
			// Check that the output id has not appeared in another dsco.
			var id types.SiacoinOutputID
			copy(id[:], idBytes)
			_, exists := idMap[id]
			if exists {
				return errors.New("repeat delayed siacoin output")
			}
			idMap[id] = struct{}{}

			// Sum the funds in the bucket.
			var sco types.SiacoinOutput
			err := encoding.Unmarshal(delayedOutput, &sco)
			if err != nil {
				manageErr(tx, err)
			}
			total = total.Add(sco.Value)
			return nil
		})
		if err != nil {
			return err
		}

		// Check that the minimum value has been achieved - the coinbase from
		// an earlier block is guaranteed to be in the bucket.
		minimumValue := types.CalculateCoinbase(height - types.MaturityDelay)
		if total.Cmp(minimumValue) < 0 {
			return errors.New("total number of coins in the delayed output bucket is incorrect")
		}
		return nil
	})
	if err != nil {
		manageErr(tx, err)
	}

	// Check that all of the correct heights are represented.
	currentHeight := blockHeight(tx)
	expectedBuckets := 0
	for i := currentHeight + 1; i <= currentHeight+types.MaturityDelay; i++ {
		if i < types.MaturityDelay {
			continue
		}
		_, exists := dscoTracker[i]
		if !exists {
			manageErr(tx, errors.New("missing a dsco bucket"))
		}
		expectedBuckets++
	}
	if len(dscoTracker) != expectedBuckets {
		manageErr(tx, errors.New("too many dsco buckets"))
	}
}
Example #20
0
// FundTransaction adds siacoins to a transaction that the wallet knows how to
// spend. The exact amount of coins are always added, and this is achieved by
// creating two transactions. The first transaciton, the parent, spends a set
// of outputs that add up to at least the desired amount, and then creates a
// single output of the exact amount and a second refund output.
func (w *Wallet) FundTransaction(id string, amount types.Currency) (t types.Transaction, err error) {
	counter := w.mu.Lock()
	defer w.mu.Unlock(counter)

	// Create a parent transaction and supply it with enough inputs to cover
	// 'amount'.
	parentTxn := types.Transaction{}
	fundingOutputs, fundingTotal, err := w.findOutputs(amount)
	if err != nil {
		return
	}
	for _, output := range fundingOutputs {
		output.age = w.age
		key := w.keys[output.output.UnlockHash]
		newInput := types.SiacoinInput{
			ParentID:         output.id,
			UnlockConditions: key.unlockConditions,
		}
		parentTxn.SiacoinInputs = append(parentTxn.SiacoinInputs, newInput)
	}

	// Create and add the output that will be used to fund the standard
	// transaction.
	parentDest, parentSpendConds, err := w.coinAddress(false) // false indicates that the address should not be visible to the user
	exactOutput := types.SiacoinOutput{
		Value:      amount,
		UnlockHash: parentDest,
	}
	parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, exactOutput)

	// Create a refund output if needed.
	if amount.Cmp(fundingTotal) != 0 {
		var refundDest types.UnlockHash
		refundDest, _, err = w.coinAddress(false) // false indicates that the address should not be visible to the user
		if err != nil {
			return
		}
		refundOutput := types.SiacoinOutput{
			Value:      fundingTotal.Sub(amount),
			UnlockHash: refundDest,
		}
		parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, refundOutput)
	}

	// Sign all of the inputs to the parent trancstion.
	coveredFields := types.CoveredFields{WholeTransaction: true}
	for _, input := range parentTxn.SiacoinInputs {
		sig := types.TransactionSignature{
			ParentID:       crypto.Hash(input.ParentID),
			CoveredFields:  coveredFields,
			PublicKeyIndex: 0,
		}
		parentTxn.TransactionSignatures = append(parentTxn.TransactionSignatures, sig)

		// Hash the transaction according to the covered fields.
		coinAddress := input.UnlockConditions.UnlockHash()
		sigIndex := len(parentTxn.TransactionSignatures) - 1
		secKey := w.keys[coinAddress].secretKey
		sigHash := parentTxn.SigHash(sigIndex)

		// Get the signature.
		var encodedSig crypto.Signature
		encodedSig, err = crypto.SignHash(sigHash, secKey)
		if err != nil {
			return
		}
		parentTxn.TransactionSignatures[sigIndex].Signature = types.Signature(encodedSig[:])
	}

	// Add the exact output to the wallet's knowledgebase before releasing the
	// lock, to prevent the wallet from using the exact output elsewhere.
	key := w.keys[parentSpendConds.UnlockHash()]
	key.outputs[parentTxn.SiacoinOutputID(0)] = &knownOutput{
		id:     parentTxn.SiacoinOutputID(0),
		output: exactOutput,

		spendable: true,
		age:       w.age,
	}

	// Send the transaction to the transaction pool.
	err = w.tpool.AcceptTransaction(parentTxn)
	if err != nil {
		return
	}

	// Get the transaction that was originally meant to be funded.
	openTxn, exists := w.transactions[id]
	if !exists {
		err = ErrInvalidID
		return
	}
	txn := openTxn.transaction

	// Add the exact output.
	newInput := types.SiacoinInput{
		ParentID:         parentTxn.SiacoinOutputID(0),
		UnlockConditions: parentSpendConds,
	}
	openTxn.inputs = append(openTxn.inputs, len(txn.SiacoinInputs))
	txn.SiacoinInputs = append(txn.SiacoinInputs, newInput)
	t = *txn
	return
}
// FundSiafunds will add a siafund input of exaclty 'amount' to the
// transaction. A parent transaction may be needed to achieve an input with the
// correct value. The siafund input will not be signed until 'Sign' is called
// on the transaction builder.
//
// TODO: The implementation of FundSiacoins is known to have quirks/bugs
// (non-fatal), and has diverged from the implementation of FundSiacoins. The
// implementations should be converged once again.
func (tb *transactionBuilder) FundSiafunds(amount types.Currency) error {
	lockID := tb.wallet.mu.Lock()
	defer tb.wallet.mu.Unlock(lockID)

	// Create and fund a parent transaction that will add the correct amount of
	// siafunds to the transaction.
	var fund types.Currency
	parentTxn := types.Transaction{}
	for scoid, sco := range tb.wallet.siafundOutputs {
		// Check that this output has not recently been spent by the wallet.
		spendHeight := tb.wallet.spentOutputs[types.OutputID(scoid)]
		if spendHeight > tb.wallet.consensusSetHeight-RespendTimeout {
			continue
		}
		outputUnlockConditions := tb.wallet.keys[sco.UnlockHash].unlockConditions
		if tb.wallet.consensusSetHeight < outputUnlockConditions.Timelock {
			continue
		}
		// Mark the output as spent.
		tb.wallet.spentOutputs[types.OutputID(scoid)] = tb.wallet.consensusSetHeight

		// Add a siafund input for this output.
		parentClaimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
		if err != nil {
			return err
		}
		sci := types.SiafundInput{
			ParentID:         scoid,
			UnlockConditions: outputUnlockConditions,
			ClaimUnlockHash:  parentClaimUnlockConditions.UnlockHash(),
		}
		parentTxn.SiafundInputs = append(parentTxn.SiafundInputs, sci)

		// Add the output to the total fund
		fund = fund.Add(sco.Value)
		if fund.Cmp(amount) >= 0 {
			break
		}
	}

	// Create and add the output that will be used to fund the standard
	// transaction.
	parentUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
	if err != nil {
		return err
	}
	exactOutput := types.SiafundOutput{
		Value:      amount,
		UnlockHash: parentUnlockConditions.UnlockHash(),
	}
	parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, exactOutput)

	// Create a refund output if needed.
	if amount.Cmp(fund) != 0 {
		refundUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
		if err != nil {
			return err
		}
		refundOutput := types.SiafundOutput{
			Value:      fund.Sub(amount),
			UnlockHash: refundUnlockConditions.UnlockHash(),
		}
		parentTxn.SiafundOutputs = append(parentTxn.SiafundOutputs, refundOutput)
	}

	// Sign all of the inputs to the parent trancstion.
	for _, sfi := range parentTxn.SiafundInputs {
		err := addSignatures(&parentTxn, types.FullCoveredFields, sfi.UnlockConditions, crypto.Hash(sfi.ParentID), tb.wallet.keys[sfi.UnlockConditions.UnlockHash()])
		if err != nil {
			return err
		}
	}

	// Add the exact output.
	claimUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
	if err != nil {
		return err
	}
	newInput := types.SiafundInput{
		ParentID:         parentTxn.SiafundOutputID(0),
		UnlockConditions: parentUnlockConditions,
		ClaimUnlockHash:  claimUnlockConditions.UnlockHash(),
	}
	tb.parents = append(tb.parents, parentTxn)
	tb.siafundInputs = append(tb.siafundInputs, len(tb.transaction.SiafundInputs))
	tb.transaction.SiafundInputs = append(tb.transaction.SiafundInputs, newInput)
	return nil
}