Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix SiaFund support in SQLite #99

Merged
merged 2 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
bin/
walletd.yml
.DS_Store
.vscode/
6 changes: 5 additions & 1 deletion persist/sqlite/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,13 +477,17 @@ func (ut *updateTx) AddSiafundElements(elements []types.SiafundElement, index ty
}

func (ut *updateTx) RemoveSiafundElements(elements []types.SiafundElement, index types.ChainIndex) error {
if len(elements) == 0 {
return nil
}

addrStmt, err := insertAddressStatement(ut.tx)
if err != nil {
return fmt.Errorf("failed to prepare address statement: %w", err)
}
defer addrStmt.Close()

stmt, err := ut.tx.Prepare(`DELETE FROM siacoin_elements WHERE id=$1 RETURNING id`)
stmt, err := ut.tx.Prepare(`DELETE FROM siafund_elements WHERE id=$1 RETURNING id`)
if err != nil {
return fmt.Errorf("failed to prepare statement: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion persist/sqlite/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

const (
longQueryDuration = 10 * time.Millisecond
longTxnDuration = 10 * time.Millisecond
longTxnDuration = time.Second // reduce syncing spam
)

type (
Expand Down
175 changes: 160 additions & 15 deletions wallet/wallet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ func TestResubscribe(t *testing.T) {
}
defer bdb.Close()

// mine a single payout to the wallet
pk := types.GeneratePrivateKey()
addr := types.StandardUnlockHash(pk.PublicKey())

network, genesisBlock := testutil.Network()
// send the siafunds to the owned address
genesisBlock.Transactions[0].SiafundOutputs[0].Address = addr

store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock)
if err != nil {
Expand All @@ -55,10 +61,6 @@ func TestResubscribe(t *testing.T) {
t.Fatal(err)
}

// mine a single payout to the wallet
pk := types.GeneratePrivateKey()
addr := types.StandardUnlockHash(pk.PublicKey())

pk2 := types.GeneratePrivateKey()
addr2 := types.StandardUnlockHash(pk2.PublicKey())

Expand All @@ -73,24 +75,27 @@ func TestResubscribe(t *testing.T) {
t.Fatal(err)
}

checkBalance := func(siacoin, immature types.Currency, siafund uint64) error {
checkBalance := func(siacoin, immature types.Currency) error {
waitForBlock(t, cm, db)

// note: the siafund balance is currently hardcoded to the number of
// siafunds in genesis. If we ever modify this test to also spend
// siafunds, this will need to be updated.
b, err := wm.WalletBalance(w.ID)
if err != nil {
return fmt.Errorf("failed to check balance: %w", err)
} else if !b.Siacoins.Equals(siacoin) {
return fmt.Errorf("expected siacoin balance %v, got %v", siacoin, b.Siacoins)
} else if !b.ImmatureSiacoins.Equals(immature) {
return fmt.Errorf("expected immature siacoin balance %v, got %v", immature, b.ImmatureSiacoins)
} else if b.Siafunds != siafund {
return fmt.Errorf("expected siafund balance %v, got %v", siafund, b.Siafunds)
} else if b.Siafunds != network.GenesisState().SiafundCount() {
return fmt.Errorf("expected siafund balance %v, got %v", network.GenesisState().SiafundCount(), b.Siafunds)
}
return nil
}

// check that the wallet has no balance
if err := checkBalance(types.ZeroCurrency, types.ZeroCurrency, 0); err != nil {
if err := checkBalance(types.ZeroCurrency, types.ZeroCurrency); err != nil {
t.Fatal(err)
}

Expand All @@ -107,7 +112,7 @@ func TestResubscribe(t *testing.T) {
}

// check that the wallet has one immature payout
if err := checkBalance(types.ZeroCurrency, expectedBalance1, 0); err != nil {
if err := checkBalance(types.ZeroCurrency, expectedBalance1); err != nil {
t.Fatal(err)
}

Expand All @@ -119,7 +124,7 @@ func TestResubscribe(t *testing.T) {
}

// check that the wallet balance has matured
if err := checkBalance(expectedBalance1, types.ZeroCurrency, 0); err != nil {
if err := checkBalance(expectedBalance1, types.ZeroCurrency); err != nil {
t.Fatal(err)
}

Expand All @@ -129,14 +134,14 @@ func TestResubscribe(t *testing.T) {
}

// check that the wallet balance did not change
if err := checkBalance(expectedBalance1, types.ZeroCurrency, 0); err != nil {
if err := checkBalance(expectedBalance1, types.ZeroCurrency); err != nil {
t.Fatal(err)
}

// add the second address to the wallet
if err := wm.AddAddress(w.ID, wallet.Address{Address: addr2}); err != nil {
t.Fatal(err)
} else if err := checkBalance(expectedBalance1, types.ZeroCurrency, 0); err != nil {
} else if err := checkBalance(expectedBalance1, types.ZeroCurrency); err != nil {
t.Fatal(err)
}

Expand All @@ -145,7 +150,7 @@ func TestResubscribe(t *testing.T) {
t.Fatal(err)
}

if err := checkBalance(expectedBalance1, expectedBalance2, 0); err != nil {
if err := checkBalance(expectedBalance1, expectedBalance2); err != nil {
t.Fatal(err)
}

Expand All @@ -155,7 +160,7 @@ func TestResubscribe(t *testing.T) {
}

// check that the wallet balance has matured
if err := checkBalance(expectedBalance1.Add(expectedBalance2), types.ZeroCurrency, 0); err != nil {
if err := checkBalance(expectedBalance1.Add(expectedBalance2), types.ZeroCurrency); err != nil {
t.Fatal(err)
}

Expand All @@ -165,7 +170,147 @@ func TestResubscribe(t *testing.T) {
}

// check that the wallet balance has matured
if err := checkBalance(expectedBalance1.Add(expectedBalance2), types.ZeroCurrency, 0); err != nil {
if err := checkBalance(expectedBalance1.Add(expectedBalance2), types.ZeroCurrency); err != nil {
t.Fatal(err)
}
}

func TestSiafunds(t *testing.T) {
log := zaptest.NewLogger(t)
dir := t.TempDir()
db, err := sqlite.OpenDatabase(filepath.Join(dir, "walletd.sqlite3"), log.Named("sqlite3"))
if err != nil {
t.Fatal(err)
}
defer db.Close()

bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db"))
if err != nil {
t.Fatal(err)
}
defer bdb.Close()

// mine a single payout to the wallet
pk := types.GeneratePrivateKey()
addr1 := types.StandardUnlockHash(pk.PublicKey())

network, genesisBlock := testutil.Network()
// send the siafunds to the owned address
genesisBlock.Transactions[0].SiafundOutputs[0].Address = addr1

store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock)
if err != nil {
t.Fatal(err)
}

cm := chain.NewManager(store, genesisState)

wm, err := wallet.NewManager(cm, db, log.Named("wallet"))
if err != nil {
t.Fatal(err)
}

pk2 := types.GeneratePrivateKey()
addr2 := types.StandardUnlockHash(pk2.PublicKey())

// create a wallet with no addresses
w1, err := wm.AddWallet(wallet.Wallet{Name: "test1"})
if err != nil {
t.Fatal(err)
}

// add the address to the wallet
if err := wm.AddAddress(w1.ID, wallet.Address{Address: addr1}); err != nil {
t.Fatal(err)
}

checkBalance := func(walletID wallet.ID, siafunds uint64) error {
waitForBlock(t, cm, db)

b, err := wm.WalletBalance(walletID)
if err != nil {
return fmt.Errorf("failed to check balance: %w", err)
} else if b.Siafunds != siafunds {
return fmt.Errorf("expected siafund balance %v, got %v", siafunds, b.Siafunds)
}
return nil
}

if err := wm.Scan(types.ChainIndex{}); err != nil {
t.Fatal(err)
} else if err := checkBalance(w1.ID, network.GenesisState().SiafundCount()); err != nil {
t.Fatal(err)
}

// split the siafunds between the two addresses
sendAmount := network.GenesisState().SiafundCount() / 2
parentID := genesisBlock.Transactions[0].SiafundOutputID(0)
txn := types.Transaction{
SiafundInputs: []types.SiafundInput{
{
ParentID: parentID,
UnlockConditions: types.StandardUnlockConditions(pk.PublicKey()),
},
},
SiafundOutputs: []types.SiafundOutput{
{Address: addr2, Value: sendAmount},
{Address: addr1, Value: sendAmount},
},
Signatures: []types.TransactionSignature{
{
ParentID: types.Hash256(parentID),
CoveredFields: types.CoveredFields{WholeTransaction: true},
},
},
}
state := cm.TipState()
sigHash := state.WholeSigHash(txn, txn.Signatures[0].ParentID, 0, 0, nil)
sig := pk.SignHash(sigHash)
txn.Signatures[0].Signature = sig[:]

if _, err := cm.AddPoolTransactions([]types.Transaction{txn}); err != nil {
t.Fatal(err)
} else if err := cm.AddBlocks([]types.Block{testutil.MineBlock(cm, types.VoidAddress)}); err != nil {
t.Fatal(err)
} else if err := checkBalance(w1.ID, sendAmount); err != nil {
t.Fatal(err)
}

// rescan for sanity check
if err := wm.Scan(types.ChainIndex{}); err != nil {
t.Fatal(err)
} else if err := checkBalance(w1.ID, sendAmount); err != nil {
t.Fatal(err)
}

// add a second wallet
w2, err := wm.AddWallet(wallet.Wallet{Name: "test2"})
if err != nil {
t.Fatal(err)
} else if err := wm.AddAddress(w2.ID, wallet.Address{Address: addr2}); err != nil {
t.Fatal(err)
}

// wallet should have no balance since it hasn't been scanned
if err := checkBalance(w2.ID, 0); err != nil {
t.Fatal(err)
}

// rescan for the second wallet
if err := wm.Scan(types.ChainIndex{}); err != nil {
t.Fatal(err)
} else if err := checkBalance(w2.ID, sendAmount); err != nil {
t.Fatal(err)
} else if err := checkBalance(w1.ID, sendAmount); err != nil {
t.Fatal(err)
}

// add the first address to the second wallet
if err := wm.AddAddress(w2.ID, wallet.Address{Address: addr1}); err != nil {
t.Fatal(err)
}
// rescan shouldn't be necessary since the address was already scanned
if err := checkBalance(w2.ID, network.GenesisState().SiafundCount()); err != nil {
t.Fatal(err)
}
}
Loading