// 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 }
// 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)) } } }
// checkSiacoins counts the number of siacoins in the database and verifies // that it matches the sum of all the coinbases. func (cs *ConsensusSet) checkSiacoins() error { // Calculate the number of expected coins in constant time. deflationBlocks := types.InitialCoinbase - types.MinimumCoinbase expectedSiacoins := types.CalculateCoinbase(0).Add(types.CalculateCoinbase(cs.height())).Div(types.NewCurrency64(2)) if cs.height() < types.BlockHeight(deflationBlocks) { expectedSiacoins = expectedSiacoins.Mul(types.NewCurrency64(uint64(cs.height()) + 1)) } else { expectedSiacoins = expectedSiacoins.Mul(types.NewCurrency64(deflationBlocks + 1)) trailingSiacoins := types.NewCurrency64(uint64(cs.height()) - deflationBlocks).Mul(types.CalculateCoinbase(cs.height())) expectedSiacoins = expectedSiacoins.Add(trailingSiacoins) } totalSiacoins := types.ZeroCurrency cs.db.forEachSiacoinOutputs(func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) { totalSiacoins = totalSiacoins.Add(sco.Value) }) cs.db.forEachFileContracts(func(fcid types.FileContractID, fc types.FileContract) { var payout types.Currency for _, output := range fc.ValidProofOutputs { payout = payout.Add(output.Value) } totalSiacoins = totalSiacoins.Add(payout) }) cs.db.forEachDelayedSiacoinOutputs(func(v types.SiacoinOutputID, dso types.SiacoinOutput) { totalSiacoins = totalSiacoins.Add(dso.Value) }) var siafundPool types.Currency err := cs.db.Update(func(tx *bolt.Tx) error { siafundPool = getSiafundPool(tx) return nil }) if err != nil { return err } cs.db.forEachSiafundOutputs(func(sfoid types.SiafundOutputID, sfo types.SiafundOutput) { sfoSiacoins := siafundPool.Sub(sfo.ClaimStart).Div(types.SiafundCount).Mul(sfo.Value) totalSiacoins = totalSiacoins.Add(sfoSiacoins) }) if expectedSiacoins.Cmp(totalSiacoins) != 0 { return errSiacoinMiscount } return nil }
// 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 }
// 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 }
// 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 }
// 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 }