func TestCreateSpendsWithBurn(t *testing.T) { now := tNow() amt := wallet.Balance{10e6, 100} uxs := makeUxBalances([]wallet.Balance{ wallet.Balance{1e6, 50}, wallet.Balance{8e6, 40}, wallet.Balance{2e6, 60}, }, now) // Force them to get sorted uxs[2].Head.BkSeq = uint64(0) uxs[1].Head.BkSeq = uint64(1) uxs[0].Head.BkSeq = uint64(2) cuxs := append(coin.UxArray{}, uxs...) // Should spend 8e6,2e6 for the exact amount, but have to add 1e6 to // obtain +50 for a 50% fee spends, err := createSpends(now, uxs, amt, 0, 2) assert.Nil(t, err) assert.Equal(t, len(spends), 3) assert.Equal(t, spends, coin.UxArray{cuxs[2], cuxs[1], cuxs[0]}) have := wallet.Balance{0, 0} for _, ux := range spends { have = have.Add(wallet.NewBalanceFromUxOut(now, &ux)) } burn, change, err := calculateBurnAndChange(have.Hours, amt.Hours, 0, 2) assert.Equal(t, burn, uint64(50)) assert.Equal(t, change, uint64(0)) assert.Nil(t, err) }
//delete this function func createSpends(headTime uint64, uxa coin.UxArray, amt wallet.Balance) (coin.UxArray, error) { if amt.Coins == 0 { return nil, errors.New("Zero spend amount") } if amt.Coins%1e6 != 0 { return nil, errors.New("Coins must be multiple of 1e6") } uxs := OldestUxOut(uxa) sort.Sort(uxs) have := wallet.Balance{0, 0} spending := make(coin.UxArray, 0) for i := range uxs { b := wallet.NewBalanceFromUxOut(headTime, &uxs[i]) //this is bullshit if b.Coins == 0 || b.Coins%1e6 != 0 { logger.Error("UxOut coins are 0 or 1e6, can't spend") continue } have = have.Add(b) spending = append(spending, uxs[i]) } if amt.Coins > have.Coins { return nil, errors.New("Not enough coins") } return spending, nil }
// Creates a Transaction spending coins and hours from our coins func CreateSpendingTransaction(wlt wallet.Wallet, unconfirmed *UnconfirmedTxnPool, unspent *coin.UnspentPool, headTime uint64, amt wallet.Balance, fee, burnFactor uint64, dest cipher.Address) (coin.Transaction, error) { txn := coin.Transaction{} auxs := unspent.AllForAddresses(wlt.GetAddresses()) // Subtract pending spends from available puxs := unconfirmed.SpendsForAddresses(unspent, wlt.GetAddressSet()) auxs = auxs.Sub(puxs) // Determine which unspents to spend spends, err := createSpends(headTime, auxs.Flatten(), amt, fee, burnFactor) if err != nil { return txn, err } // Add these unspents as tx inputs toSign := make([]cipher.SecKey, len(spends)) spending := wallet.Balance{0, 0} for i, au := range spends { entry, exists := wlt.GetEntry(au.Body.Address) if !exists { log.Panic("On second thought, the wallet entry does not exist") } txn.PushInput(au.Hash()) toSign[i] = entry.Secret spending.Coins += au.Body.Coins spending.Hours += au.CoinHours(headTime) } // Determine how much change we get back, if any _, changeHours, err := calculateBurnAndChange(spending.Hours, amt.Hours, fee, burnFactor) if err != nil { // This should not occur, else createSpends is broken return txn, err } change := wallet.NewBalance(spending.Coins-amt.Coins, changeHours) // TODO -- send change to a new address changeAddr := spends[0].Body.Address if change.Coins == 0 { if change.Hours > 0 { msg := ("Have enough coins, but not enough to send coin hours " + "change back. Would spend %d more hours than requested.") return txn, fmt.Errorf(msg, change.Hours) } } else { txn.PushOutput(changeAddr, change.Coins, change.Hours) } // Finalize the the transaction txn.PushOutput(dest, amt.Coins, amt.Hours) txn.SignInputs(toSign) txn.UpdateHeader() return txn, nil }
//DEPRECATE //deprecate dependency on wallet // Creates a Transaction spending coins and hours from our coins //MOVE SOMEWHERE ELSE //Move to wallet or move to ??? func CreateSpendingTransaction(wlt wallet.Wallet, unconfirmed *UnconfirmedTxnPool, unspent *coin.UnspentPool, headTime uint64, amt wallet.Balance, dest cipher.Address) (coin.Transaction, error) { txn := coin.Transaction{} auxs := unspent.AllForAddresses(wlt.GetAddresses()) // Subtract pending spends from available puxs := unconfirmed.SpendsForAddresses(unspent, wlt.GetAddressSet()) auxs = auxs.Sub(puxs) // Determine which unspents to spend spends, err := createSpends(headTime, auxs.Flatten(), amt) if err != nil { return txn, err } // Add these unspents as tx inputs toSign := make([]cipher.SecKey, len(spends)) spending := wallet.Balance{0, 0} for i, au := range spends { entry, exists := wlt.GetEntry(au.Body.Address) if !exists { log.Panic("On second thought, the wallet entry does not exist") } txn.PushInput(au.Hash()) toSign[i] = entry.Secret spending.Coins += au.Body.Coins spending.Hours += au.CoinHours(headTime) } //keep 1/4th of hours as change //send half to each address var changeHours uint64 = spending.Hours / 4 if amt.Coins == spending.Coins { txn.PushOutput(dest, amt.Coins, changeHours/2) txn.SignInputs(toSign) txn.UpdateHeader() return txn, nil } change := wallet.NewBalance(spending.Coins-amt.Coins, changeHours/2) // TODO -- send change to a new address changeAddr := spends[0].Body.Address //create transaction txn.PushOutput(changeAddr, change.Coins, change.Hours) txn.PushOutput(dest, amt.Coins, changeHours/2) txn.SignInputs(toSign) txn.UpdateHeader() return txn, nil }
func createSpends(headTime uint64, uxa coin.UxArray, amt wallet.Balance, fee, burnFactor uint64) (coin.UxArray, error) { if amt.Coins == 0 { return nil, errors.New("Zero spend amount") } if amt.Coins%1e6 != 0 { return nil, errors.New("Coins must be multiple of 1e6") } uxs := OldestUxOut(uxa) sort.Sort(uxs) have := wallet.Balance{0, 0} spending := make(coin.UxArray, 0) for i, _ := range uxs { burn, _, err := calculateBurnAndChange(have.Hours, amt.Hours, fee, burnFactor) if err == nil { trueHours := amt.Hours + fee + burn // Adjust hours as a moving target as outputs change if have.Coins > amt.Coins && have.Hours >= trueHours { break } // If we have the exact amount of both, we don't need any extra coins // for change if have.Coins == amt.Coins && have.Hours == trueHours { break } } b := wallet.NewBalanceFromUxOut(headTime, &uxs[i]) if b.Coins == 0 || b.Coins%1e6 != 0 { logger.Error("UxOut coins are 0 or 1e6, can't spend") continue } have = have.Add(b) spending = append(spending, uxs[i]) } if amt.Coins > have.Coins { return nil, errors.New("Not enough coins") } if amt.Hours+fee > have.Hours { return nil, errors.New("Not enough hours") } if _, _, err := calculateBurnAndChange(have.Hours, amt.Hours, fee, burnFactor); err != nil { return nil, errors.New("Not enough hours to burn") } return spending, nil }
func TestCreateSpends(t *testing.T) { now := tNow() amt := wallet.Balance{12e6, 125} uxs := makeUxBalances([]wallet.Balance{ wallet.Balance{1e6, 50}, wallet.Balance{8e6, 10}, // 3 wallet.Balance{2e6, 80}, // 2 wallet.Balance{5e6, 15}, // 4 wallet.Balance{7e6, 20}, // 1 }, now) uxs[4].Head.BkSeq = uint64(1) uxs[3].Head.BkSeq = uint64(4) uxs[2].Head.BkSeq = uint64(2) uxs[1].Head.BkSeq = uint64(3) uxs[0].Head.BkSeq = uint64(5) if sort.IsSorted(OldestUxOut(uxs)) { uxs[0], uxs[1] = uxs[1], uxs[0] } assert.False(t, sort.IsSorted(OldestUxOut(uxs))) expectedSorting := coin.UxArray{uxs[4], uxs[2], uxs[1], uxs[3], uxs[0]} cuxs := append(coin.UxArray{}, uxs...) sort.Sort(OldestUxOut(cuxs)) assert.Equal(t, expectedSorting, cuxs) assert.True(t, sort.IsSorted(OldestUxOut(cuxs))) assert.False(t, sort.IsSorted(OldestUxOut(uxs))) ouxs := append(coin.UxArray{}, uxs...) spends, err := createSpends(now, uxs, amt, 0, 0) assert.True(t, sort.IsSorted(OldestUxOut(uxs))) assert.Nil(t, err) assert.Equal(t, spends, cuxs[:len(spends)]) assert.Equal(t, len(spends), 4) assert.Equal(t, spends, coin.UxArray{ouxs[4], ouxs[2], ouxs[1], ouxs[3]}) // Recalculate what it should be b := wallet.Balance{0, 0} ouxs = make(coin.UxArray, 0, len(spends)) for _, ux := range cuxs { if b.Coins > amt.Coins && b.Hours >= amt.Hours { break } b = b.Add(wallet.NewBalanceFromUxOut(now, &ux)) ouxs = append(ouxs, ux) } assert.Equal(t, len(ouxs), len(spends)) assert.Equal(t, ouxs, spends) }