Skip to content
This repository has been archived by the owner on Nov 2, 2018. It is now read-only.

Commit

Permalink
Merge pull request #1739 from NebulousLabs/balance-lock
Browse files Browse the repository at this point in the history
move siafundPool into wallet database
  • Loading branch information
David Vorick committed Apr 25, 2017
2 parents f1509a5 + ff2b426 commit c74de53
Show file tree
Hide file tree
Showing 9 changed files with 302 additions and 61 deletions.
119 changes: 119 additions & 0 deletions api/wallet_test.go
Expand Up @@ -833,3 +833,122 @@ func TestWalletReset(t *testing.T) {
t.Error("wallet is not unlocked")
}
}

func TestWalletSiafunds(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
t.Parallel()

walletPassword := "testpass"
key := crypto.TwofishKey(crypto.HashObject(walletPassword))
testdir := build.TempDir("api", t.Name())
st, err := assembleServerTester(key, testdir)
if err != nil {
t.Fatal(err)
}
defer st.server.Close()

// mine some money
for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ {
_, err := st.miner.AddBlock()
if err != nil {
t.Fatal(err)
}
}

// record transactions
var wtg WalletTransactionsGET
err = st.getAPI("/wallet/transactions?startheight=0&endheight=100", &wtg)
if err != nil {
t.Fatal(err)
}
numTxns := len(wtg.ConfirmedTransactions)

// load siafunds into the wallet
siagPath, _ := filepath.Abs("../types/siag0of1of1.siakey")
loadSiagValues := url.Values{}
loadSiagValues.Set("keyfiles", siagPath)
loadSiagValues.Set("encryptionpassword", walletPassword)
err = st.stdPostAPI("/wallet/siagkey", loadSiagValues)
if err != nil {
t.Fatal(err)
}

err = st.getAPI("/wallet/transactions?startheight=0&endheight=100", &wtg)
if err != nil {
t.Fatal(err)
}
if len(wtg.ConfirmedTransactions) != numTxns+1 {
t.Errorf("expected %v transactions, got %v", numTxns+1, len(wtg.ConfirmedTransactions))
}

// check balance
var wg WalletGET
err = st.getAPI("/wallet", &wg)
if err != nil {
t.Fatal(err)
}
if wg.SiafundBalance.Cmp64(2000) != 0 {
t.Fatalf("bad siafund balance: expected %v, got %v", 2000, wg.SiafundBalance)
}

// spend the siafunds into the wallet seed
var wag WalletAddressGET
err = st.getAPI("/wallet/address", &wag)
if err != nil {
t.Fatal(err)
}
sendSiafundsValues := url.Values{}
sendSiafundsValues.Set("amount", "2000")
sendSiafundsValues.Set("destination", wag.Address.String())
err = st.stdPostAPI("/wallet/siafunds", sendSiafundsValues)
if err != nil {
t.Fatal(err)
}

// Announce the host and form an allowance with it. This will result in a
// siafund claim.
err = st.announceHost()
if err != nil {
t.Fatal(err)
}
err = st.setHostStorage()
if err != nil {
t.Fatal(err)
}
err = st.acceptContracts()
if err != nil {
t.Fatal(err)
}
// mine a block so that the announcement makes it into the blockchain
_, err = st.miner.AddBlock()
if err != nil {
t.Fatal(err)
}

// form allowance
allowanceValues := url.Values{}
testFunds := "10000000000000000000000000000" // 10k SC
testPeriod := "20"
allowanceValues.Set("funds", testFunds)
allowanceValues.Set("period", testPeriod)
err = st.stdPostAPI("/renter", allowanceValues)
if err != nil {
t.Fatal(err)
}

// mine a block so that the file contract makes it into the blockchain
_, err = st.miner.AddBlock()
if err != nil {
t.Fatal(err)
}
// wallet should now have a claim balance
err = st.getAPI("/wallet", &wg)
if err != nil {
t.Fatal(err)
}
if wg.SiacoinClaimBalance.IsZero() {
t.Fatal("expected non-zero claim balance")
}
}
65 changes: 38 additions & 27 deletions modules/wallet/database.go
Expand Up @@ -67,6 +67,7 @@ var (
keyConsensusHeight = []byte("keyConsensusHeight")
keySpendableKeyFiles = []byte("keySpendableKeyFiles")
keyAuxiliarySeedFiles = []byte("keyAuxiliarySeedFiles")
keySiafundPool = []byte("keySiafundPool")

errNoKey = errors.New("key does not exist")
)
Expand Down Expand Up @@ -107,6 +108,32 @@ func (w *Wallet) syncDB() {
}
}

// dbReset wipes and reinitializes a wallet database.
func dbReset(tx *bolt.Tx) error {
for _, bucket := range dbBuckets {
err := tx.DeleteBucket(bucket)
if err != nil {
return err
}
_, err = tx.CreateBucket(bucket)
if err != nil {
return err
}
}

// reinitialize the database with default values
wb := tx.Bucket(bucketWallet)
wb.Put(keyUID, fastrand.Bytes(len(uniqueID{})))
wb.Put(keyConsensusHeight, encoding.Marshal(uint64(0)))
wb.Put(keyAuxiliarySeedFiles, encoding.Marshal([]seedFile{}))
wb.Put(keySpendableKeyFiles, encoding.Marshal([]spendableKeyFile{}))
dbPutConsensusHeight(tx, 0)
dbPutConsensusChangeID(tx, modules.ConsensusChangeBeginning)
dbPutSiafundPool(tx, types.ZeroCurrency)

return nil
}

// dbPut is a helper function for storing a marshalled key/value pair.
func dbPut(b *bolt.Bucket, key, val interface{}) error {
return b.Put(encoding.Marshal(key), encoding.Marshal(val))
Expand Down Expand Up @@ -207,33 +234,6 @@ func dbDeleteSpentOutput(tx *bolt.Tx, id types.OutputID) error {
return dbDelete(tx.Bucket(bucketSpentOutputs), id)
}

// dbReset wipes and reinitializes a wallet database.
func dbReset(tx *bolt.Tx) error {
for _, bucket := range dbBuckets {
err := tx.DeleteBucket(bucket)
if err != nil {
return err
}
_, err = tx.CreateBucket(bucket)
if err != nil {
return err
}
}

// reinitialize the database with default values
wb := tx.Bucket(bucketWallet)
uid := make([]byte, len(uniqueID{}))
fastrand.Read(uid[:])
wb.Put(keyUID, uid)
wb.Put(keyConsensusHeight, encoding.Marshal(uint64(0)))
wb.Put(keyAuxiliarySeedFiles, encoding.Marshal([]seedFile{}))
wb.Put(keySpendableKeyFiles, encoding.Marshal([]spendableKeyFile{}))
dbPutConsensusHeight(tx, 0)
dbPutConsensusChangeID(tx, modules.ConsensusChangeBeginning)

return nil
}

// bucketProcessedTransactions works a little differently: the key is
// meaningless, only used to order the transactions chronologically.

Expand Down Expand Up @@ -306,3 +306,14 @@ func dbGetConsensusHeight(tx *bolt.Tx) (height types.BlockHeight, err error) {
func dbPutConsensusHeight(tx *bolt.Tx, height types.BlockHeight) error {
return tx.Bucket(bucketWallet).Put(keyConsensusHeight, encoding.Marshal(height))
}

// dbGetSiafundPool returns the value of the siafund pool.
func dbGetSiafundPool(tx *bolt.Tx) (pool types.Currency, err error) {
err = encoding.Unmarshal(tx.Bucket(bucketWallet).Get(keySiafundPool), &pool)
return
}

// dbPutSiafundPool stores the value of the siafund pool.
func dbPutSiafundPool(tx *bolt.Tx, pool types.Currency) error {
return tx.Bucket(bucketWallet).Put(keySiafundPool, encoding.Marshal(pool))
}
1 change: 0 additions & 1 deletion modules/wallet/encrypt.go
Expand Up @@ -336,7 +336,6 @@ func (w *Wallet) Reset() error {
w.keys = make(map[types.UnlockHash]spendableKey)
w.seeds = []modules.Seed{}
w.unconfirmedProcessedTransactions = []modules.ProcessedTransaction{}
w.siafundPool = types.Currency{}
w.unlocked = false
w.encrypted = false
w.subscribed = false
Expand Down
23 changes: 21 additions & 2 deletions modules/wallet/money.go
Expand Up @@ -2,6 +2,7 @@ package wallet

import (
"github.com/NebulousLabs/Sia/build"
"github.com/NebulousLabs/Sia/modules"
"github.com/NebulousLabs/Sia/types"
)

Expand All @@ -15,19 +16,31 @@ type sortedOutputs struct {
// ConfirmedBalance returns the balance of the wallet according to all of the
// confirmed transactions.
func (w *Wallet) ConfirmedBalance() (siacoinBalance types.Currency, siafundBalance types.Currency, siafundClaimBalance types.Currency) {
// ensure durability of reported balance
w.mu.Lock()
defer w.mu.Unlock()

// ensure durability of reported balance
w.syncDB()

dbForEachSiacoinOutput(w.dbTx, func(_ types.SiacoinOutputID, sco types.SiacoinOutput) {
if sco.Value.Cmp(dustValue()) > 0 {
siacoinBalance = siacoinBalance.Add(sco.Value)
}
})

siafundPool, err := dbGetSiafundPool(w.dbTx)
if err != nil {
return
}
dbForEachSiafundOutput(w.dbTx, func(_ types.SiafundOutputID, sfo types.SiafundOutput) {
siafundBalance = siafundBalance.Add(sfo.Value)
siafundClaimBalance = siafundClaimBalance.Add(w.siafundPool.Sub(sfo.ClaimStart).Mul(sfo.Value).Div(types.SiafundCount))
if sfo.ClaimStart.Cmp(siafundPool) > 0 {
// Skip claims larger than the siafund pool. This should only
// occur if the siafund pool has not been initialized yet.
w.log.Debugf("skipping claim with start value %v because siafund pool is only %v", sfo.ClaimStart, siafundPool)
return
}
siafundClaimBalance = siafundClaimBalance.Add(siafundPool.Sub(sfo.ClaimStart).Mul(sfo.Value).Div(types.SiafundCount))
})
return
}
Expand Down Expand Up @@ -61,6 +74,9 @@ func (w *Wallet) SendSiacoins(amount types.Currency, dest types.UnlockHash) ([]t
return nil, err
}
defer w.tg.Done()
if !w.unlocked {
return nil, modules.ErrLockedWallet
}

_, tpoolFee := w.tpool.FeeEstimation()
tpoolFee = tpoolFee.Mul64(750) // Estimated transaction size in bytes
Expand Down Expand Up @@ -94,6 +110,9 @@ func (w *Wallet) SendSiafunds(amount types.Currency, dest types.UnlockHash) ([]t
return nil, err
}
defer w.tg.Done()
if !w.unlocked {
return nil, modules.ErrLockedWallet
}

_, tpoolFee := w.tpool.FeeEstimation()
tpoolFee = tpoolFee.Mul64(750) // Estimated transaction size in bytes
Expand Down
4 changes: 4 additions & 0 deletions modules/wallet/persist.go
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/NebulousLabs/Sia/encoding"
"github.com/NebulousLabs/Sia/modules"
"github.com/NebulousLabs/Sia/persist"
"github.com/NebulousLabs/Sia/types"
"github.com/NebulousLabs/fastrand"

"github.com/NebulousLabs/bolt"
Expand Down Expand Up @@ -66,6 +67,9 @@ func (w *Wallet) openDB(filename string) (err error) {
if wb.Get(keySpendableKeyFiles) == nil {
wb.Put(keySpendableKeyFiles, encoding.Marshal([]spendableKeyFile{}))
}
if wb.Get(keySiafundPool) == nil {
wb.Put(keySiafundPool, encoding.Marshal(types.ZeroCurrency))
}

// check whether wallet is encrypted
w.encrypted = tx.Bucket(bucketWallet).Get(keyEncryptionVerification) != nil
Expand Down
10 changes: 10 additions & 0 deletions modules/wallet/seed.go
Expand Up @@ -241,6 +241,16 @@ func (w *Wallet) LoadSeed(masterKey crypto.TwofishKey, seed modules.Seed) error
w.integrateSeed(seed, seedProgress)
w.seeds = append(w.seeds, seed)

// delete the set of processed transactions; they will be recreated
// when we rescan
if err = w.dbTx.DeleteBucket(bucketProcessedTransactions); err != nil {
return err
}
if _, err = w.dbTx.CreateBucket(bucketProcessedTransactions); err != nil {
return err
}
w.unconfirmedProcessedTransactions = nil

// reset the consensus change ID and height in preparation for rescan
err = dbPutConsensusChangeID(w.dbTx, modules.ConsensusChangeBeginning)
if err != nil {
Expand Down

0 comments on commit c74de53

Please sign in to comment.