diff --git a/go.mod b/go.mod index 6b82d33..794a24b 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21.8 require ( go.etcd.io/bbolt v1.3.10 - go.sia.tech/core v0.4.1 + go.sia.tech/core v0.4.2-0.20240723013228-2b1c3d890e25 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.25.0 lukechampine.com/frand v1.4.2 diff --git a/go.sum b/go.sum index 834e6e8..2c22806 100644 --- a/go.sum +++ b/go.sum @@ -8,10 +8,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= -go.sia.tech/core v0.4.0 h1:TlbVuiw1nk7wAybSvuZozRixnI4lpmcK0MVIlpJ9ApA= -go.sia.tech/core v0.4.0/go.mod h1:6dN3J2GDX+f8H2p82MJ7V4BFdnmgoHAiovfmBD/F1Hg= -go.sia.tech/core v0.4.1 h1:yawkyvr7mHYKWXa8RsHAPriLtJdvDQzqXgq4/hHqjHQ= -go.sia.tech/core v0.4.1/go.mod h1:6dN3J2GDX+f8H2p82MJ7V4BFdnmgoHAiovfmBD/F1Hg= +go.sia.tech/core v0.4.2-0.20240723013228-2b1c3d890e25 h1:VEldn/1zMNPdwv8wPimDk6V4D0UXUTU+CufSwQ0Cxug= +go.sia.tech/core v0.4.2-0.20240723013228-2b1c3d890e25/go.mod h1:6dN3J2GDX+f8H2p82MJ7V4BFdnmgoHAiovfmBD/F1Hg= go.sia.tech/mux v1.2.0 h1:ofa1Us9mdymBbGMY2XH/lSpY8itFsKIo/Aq8zwe+GHU= go.sia.tech/mux v1.2.0/go.mod h1:Yyo6wZelOYTyvrHmJZ6aQfRoer3o4xyKQ4NmQLJrBSo= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= diff --git a/testutil/wallet.go b/testutil/wallet.go index e1fc4a3..f29ec1f 100644 --- a/testutil/wallet.go +++ b/testutil/wallet.go @@ -7,7 +7,6 @@ import ( "sync" "go.sia.tech/core/types" - "go.sia.tech/coreutils/chain" "go.sia.tech/coreutils/wallet" ) @@ -35,7 +34,7 @@ func (et *ephemeralWalletUpdateTxn) WalletStateElements() (elements []types.Stat return } -func (et *ephemeralWalletUpdateTxn) UpdateStateElements(elements []types.StateElement) error { +func (et *ephemeralWalletUpdateTxn) UpdateWalletStateElements(elements []types.StateElement) error { for _, se := range elements { utxo := et.store.utxos[types.SiacoinOutputID(se.ID)] utxo.StateElement = se @@ -44,7 +43,7 @@ func (et *ephemeralWalletUpdateTxn) UpdateStateElements(elements []types.StateEl return nil } -func (et *ephemeralWalletUpdateTxn) ApplyIndex(index types.ChainIndex, created, spent []types.SiacoinElement, events []wallet.Event) error { +func (et *ephemeralWalletUpdateTxn) WalletApplyIndex(index types.ChainIndex, created, spent []types.SiacoinElement, events []wallet.Event) error { for _, se := range spent { if _, ok := et.store.utxos[types.SiacoinOutputID(se.ID)]; !ok { panic(fmt.Sprintf("siacoin element %q does not exist", se.ID)) @@ -61,10 +60,11 @@ func (et *ephemeralWalletUpdateTxn) ApplyIndex(index types.ChainIndex, created, // add events et.store.events = append(et.store.events, events...) + et.store.tip = index return nil } -func (et *ephemeralWalletUpdateTxn) RevertIndex(index types.ChainIndex, removed, unspent []types.SiacoinElement) error { +func (et *ephemeralWalletUpdateTxn) WalletRevertIndex(index types.ChainIndex, removed, unspent []types.SiacoinElement) error { // remove any events that were added in the reverted block filtered := et.store.events[:0] for i := range et.store.events { @@ -84,16 +84,13 @@ func (et *ephemeralWalletUpdateTxn) RevertIndex(index types.ChainIndex, removed, for _, se := range unspent { et.store.utxos[types.SiacoinOutputID(se.ID)] = se } + et.store.tip = index return nil } // UpdateChainState applies and reverts chain updates to the wallet. -func (es *EphemeralWalletStore) UpdateChainState(reverted []chain.RevertUpdate, applied []chain.ApplyUpdate) error { - if err := wallet.UpdateChainState(&ephemeralWalletUpdateTxn{store: es}, types.StandardUnlockHash(es.privateKey.PublicKey()), applied, reverted); err != nil { - return fmt.Errorf("failed to update chain state: %w", err) - } - es.tip = applied[len(applied)-1].State.Index - return nil +func (es *EphemeralWalletStore) UpdateChainState(fn func(ux wallet.UpdateTx) error) error { + return fn(&ephemeralWalletUpdateTxn{store: es}) } // WalletEvents returns the wallet's events. diff --git a/wallet/update.go b/wallet/update.go index f4d39cf..5a986e4 100644 --- a/wallet/update.go +++ b/wallet/update.go @@ -23,16 +23,16 @@ type ( // WalletStateElements returns all state elements related to the wallet. It is used // to update the proofs of all state elements affected by the update. WalletStateElements() ([]types.StateElement, error) - // UpdateStateElements updates the proofs of all state elements affected by the + // UpdateWalletStateElements updates the proofs of all state elements affected by the // update. - UpdateStateElements([]types.StateElement) error + UpdateWalletStateElements([]types.StateElement) error - // ApplyIndex is called with the chain index that is being applied. + // WalletApplyIndex is called with the chain index that is being applied. // Any transactions and siacoin elements that were created by the index // should be added and any siacoin elements that were spent should be // removed. - ApplyIndex(index types.ChainIndex, created, spent []types.SiacoinElement, events []Event) error - // RevertIndex is called with the chain index that is being reverted. + WalletApplyIndex(index types.ChainIndex, created, spent []types.SiacoinElement, events []Event) error + // WalletRevertIndex is called with the chain index that is being reverted. // Any transactions that were added by the index should be removed // // removed contains the siacoin elements that were created by the index @@ -41,7 +41,7 @@ type ( // unspent contains the siacoin elements that were spent and should be // recreated. They are not necessarily created by the index and should // not be associated with it. - RevertIndex(index types.ChainIndex, removed, unspent []types.SiacoinElement) error + WalletRevertIndex(index types.ChainIndex, removed, unspent []types.SiacoinElement) error } ) @@ -315,9 +315,9 @@ func applyChainState(tx UpdateTx, address types.Address, cau chain.ApplyUpdate) cau.UpdateElementProof(&stateElements[i]) } - if err := tx.ApplyIndex(cau.State.Index, createdUTXOs, spentUTXOs, appliedEvents(cau, address)); err != nil { + if err := tx.WalletApplyIndex(cau.State.Index, createdUTXOs, spentUTXOs, appliedEvents(cau, address)); err != nil { return fmt.Errorf("failed to apply index: %w", err) - } else if err := tx.UpdateStateElements(stateElements); err != nil { + } else if err := tx.UpdateWalletStateElements(stateElements); err != nil { return fmt.Errorf("failed to update state elements: %w", err) } return nil @@ -338,7 +338,7 @@ func revertChainUpdate(tx UpdateTx, revertedIndex types.ChainIndex, address type }) // remove any existing events that were added in the reverted block - if err := tx.RevertIndex(revertedIndex, removedUTXOs, unspentUTXOs); err != nil { + if err := tx.WalletRevertIndex(revertedIndex, removedUTXOs, unspentUTXOs); err != nil { return fmt.Errorf("failed to revert block: %w", err) } @@ -352,7 +352,7 @@ func revertChainUpdate(tx UpdateTx, revertedIndex types.ChainIndex, address type cru.UpdateElementProof(&stateElements[i]) } - if err := tx.UpdateStateElements(stateElements); err != nil { + if err := tx.UpdateWalletStateElements(stateElements); err != nil { return fmt.Errorf("failed to update state elements: %w", err) } return nil @@ -360,22 +360,21 @@ func revertChainUpdate(tx UpdateTx, revertedIndex types.ChainIndex, address type // UpdateChainState atomically applies and reverts chain updates to a single // wallet store. -func UpdateChainState(tx UpdateTx, address types.Address, applied []chain.ApplyUpdate, reverted []chain.RevertUpdate) error { +func (sw *SingleAddressWallet) UpdateChainState(tx UpdateTx, reverted []chain.RevertUpdate, applied []chain.ApplyUpdate) error { for _, cru := range reverted { revertedIndex := types.ChainIndex{ ID: cru.Block.ID(), Height: cru.State.Index.Height + 1, } - if err := revertChainUpdate(tx, revertedIndex, address, cru); err != nil { + if err := revertChainUpdate(tx, revertedIndex, sw.addr, cru); err != nil { return err } } for _, cau := range applied { - if err := applyChainState(tx, address, cau); err != nil { + if err := applyChainState(tx, sw.addr, cau); err != nil { return fmt.Errorf("failed to apply chain update %q: %w", cau.State.Index, err) } } - return nil } diff --git a/wallet/wallet.go b/wallet/wallet.go index d016ae1..4489538 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -9,7 +9,6 @@ import ( "go.sia.tech/core/consensus" "go.sia.tech/core/types" - "go.sia.tech/coreutils/chain" "go.uber.org/zap" ) @@ -63,9 +62,6 @@ type ( // WalletEventCount returns the total number of events relevant to the // wallet. WalletEventCount() (uint64, error) - - // UpdateChainState applies and reverts chain updates to the wallet. - UpdateChainState([]chain.RevertUpdate, []chain.ApplyUpdate) error } // A SingleAddressWallet is a hot wallet that manages the outputs controlled diff --git a/wallet/wallet_test.go b/wallet/wallet_test.go index d285afa..6714c91 100644 --- a/wallet/wallet_test.go +++ b/wallet/wallet_test.go @@ -15,7 +15,7 @@ import ( "go.uber.org/zap/zaptest" ) -func syncDB(cm *chain.Manager, store wallet.SingleAddressStore) error { +func syncDB(cm *chain.Manager, store *testutil.EphemeralWalletStore, w *wallet.SingleAddressWallet) error { for { tip, err := store.Tip() if err != nil { @@ -29,13 +29,16 @@ func syncDB(cm *chain.Manager, store wallet.SingleAddressStore) error { return fmt.Errorf("failed to get updates: %w", err) } - if err := store.UpdateChainState(reverted, applied); err != nil { + err = store.UpdateChainState(func(tx wallet.UpdateTx) error { + return w.UpdateChainState(tx, reverted, applied) + }) + if err != nil { return fmt.Errorf("failed to update chain state: %w", err) } } } -func mineAndSync(t *testing.T, cm *chain.Manager, ws wallet.SingleAddressStore, address types.Address, n uint64) { +func mineAndSync(t *testing.T, cm *chain.Manager, ws *testutil.EphemeralWalletStore, w *wallet.SingleAddressWallet, address types.Address, n uint64) { t.Helper() // mine n blocks @@ -47,7 +50,7 @@ func mineAndSync(t *testing.T, cm *chain.Manager, ws wallet.SingleAddressStore, } } // wait for the wallet to sync - if err := syncDB(cm, ws); err != nil { + if err := syncDB(cm, ws, w); err != nil { t.Fatal(err) } } @@ -96,7 +99,7 @@ func TestWallet(t *testing.T) { assertBalance(t, w, types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency) // mine a block to fund the wallet - mineAndSync(t, cm, ws, w.Address(), 1) + mineAndSync(t, cm, ws, w, w.Address(), 1) maturityHeight := cm.TipState().MaturityHeight() // check that the wallet has a single event @@ -134,7 +137,7 @@ func TestWallet(t *testing.T) { // mine until the payout matures tip := cm.TipState() target := tip.MaturityHeight() - mineAndSync(t, cm, ws, types.VoidAddress, target-tip.Index.Height) + mineAndSync(t, cm, ws, w, types.VoidAddress, target-tip.Index.Height) // check that one payout has matured assertBalance(t, w, initialReward, initialReward, types.ZeroCurrency, types.ZeroCurrency) @@ -201,7 +204,7 @@ func TestWallet(t *testing.T) { // transaction is not yet confirmed. assertBalance(t, w, types.ZeroCurrency, initialReward, types.ZeroCurrency, initialReward) // mine a block to confirm the transaction - mineAndSync(t, cm, ws, types.VoidAddress, 1) + mineAndSync(t, cm, ws, w, types.VoidAddress, 1) // check that the balance was confirmed and the other values reset assertBalance(t, w, initialReward, initialReward, types.ZeroCurrency, types.ZeroCurrency) @@ -244,7 +247,7 @@ func TestWallet(t *testing.T) { if _, err := cm.AddPoolTransactions(sent); err != nil { t.Fatal(err) } - mineAndSync(t, cm, ws, types.VoidAddress, 1) + mineAndSync(t, cm, ws, w, types.VoidAddress, 1) // check that the wallet now has 22 transactions, the initial payout // transaction, the split transaction, and 20 void transactions @@ -298,8 +301,8 @@ func TestWalletUnconfirmed(t *testing.T) { defer w.Close() // fund the wallet - mineAndSync(t, cm, ws, w.Address(), 1) - mineAndSync(t, cm, ws, types.VoidAddress, cm.TipState().MaturityHeight()-1) + mineAndSync(t, cm, ws, w, w.Address(), 1) + mineAndSync(t, cm, ws, w, types.VoidAddress, cm.TipState().MaturityHeight()-1) // check that one payout has matured initialReward := cm.TipState().BlockReward() @@ -382,8 +385,8 @@ func TestWalletRedistribute(t *testing.T) { defer w.Close() // fund the wallet - mineAndSync(t, cm, ws, w.Address(), 1) - mineAndSync(t, cm, ws, types.VoidAddress, cm.TipState().MaturityHeight()-1) + mineAndSync(t, cm, ws, w, w.Address(), 1) + mineAndSync(t, cm, ws, w, types.VoidAddress, cm.TipState().MaturityHeight()-1) redistribute := func(amount types.Currency, n int) error { txns, toSign, err := w.Redistribute(n, amount, types.ZeroCurrency) @@ -399,7 +402,7 @@ func TestWalletRedistribute(t *testing.T) { if _, err := cm.AddPoolTransactions(txns); err != nil { return fmt.Errorf("failed to add transactions to pool: %w", err) } - mineAndSync(t, cm, ws, types.VoidAddress, 1) + mineAndSync(t, cm, ws, w, types.VoidAddress, 1) return nil } @@ -479,7 +482,7 @@ func TestReorg(t *testing.T) { assertBalance(t, w, types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency) // mine a block to fund the wallet - mineAndSync(t, cm, ws, w.Address(), 1) + mineAndSync(t, cm, ws, w, w.Address(), 1) maturityHeight := cm.TipState().MaturityHeight() // check that the wallet has a single event @@ -517,7 +520,7 @@ func TestReorg(t *testing.T) { // mine until the payout matures tip := cm.TipState() target := tip.MaturityHeight() - mineAndSync(t, cm, ws, types.VoidAddress, target-tip.Index.Height) + mineAndSync(t, cm, ws, w, types.VoidAddress, target-tip.Index.Height) // check that one payout has matured assertBalance(t, w, initialReward, initialReward, types.ZeroCurrency, types.ZeroCurrency) @@ -584,7 +587,7 @@ func TestReorg(t *testing.T) { // transaction is not yet confirmed. assertBalance(t, w, types.ZeroCurrency, initialReward, types.ZeroCurrency, initialReward) // mine a block to confirm the transaction - mineAndSync(t, cm, ws, types.VoidAddress, 1) + mineAndSync(t, cm, ws, w, types.VoidAddress, 1) rollbackState := cm.TipState() // check that the balance was confirmed and the other values reset @@ -639,7 +642,7 @@ func TestReorg(t *testing.T) { if _, err := cm.AddPoolTransactions([]types.Transaction{txn1}); err != nil { t.Fatal(err) } - mineAndSync(t, cm, ws, types.VoidAddress, 1) + mineAndSync(t, cm, ws, w, types.VoidAddress, 1) // check that the wallet now has 3 transactions: the initial payout // transaction, the split transaction, and a void transaction @@ -689,7 +692,7 @@ func TestReorg(t *testing.T) { reorgBlocks = append(reorgBlocks, b) if err := cm.AddBlocks(reorgBlocks); err != nil { t.Fatal(err) - } else if err := syncDB(cm, ws); err != nil { + } else if err := syncDB(cm, ws, w); err != nil { t.Fatal(err) } @@ -744,7 +747,7 @@ func TestWalletV2(t *testing.T) { assertBalance(t, w, types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency) // mine a block to fund the wallet - mineAndSync(t, cm, ws, w.Address(), 1) + mineAndSync(t, cm, ws, w, w.Address(), 1) maturityHeight := cm.TipState().MaturityHeight() // check that the wallet has a single event @@ -782,7 +785,7 @@ func TestWalletV2(t *testing.T) { // mine until the payout matures tip := cm.TipState() target := tip.MaturityHeight() - mineAndSync(t, cm, ws, types.VoidAddress, target-tip.Index.Height) + mineAndSync(t, cm, ws, w, types.VoidAddress, target-tip.Index.Height) // check that one payout has matured assertBalance(t, w, initialReward, initialReward, types.ZeroCurrency, types.ZeroCurrency) @@ -849,7 +852,7 @@ func TestWalletV2(t *testing.T) { // transaction is not yet confirmed. assertBalance(t, w, types.ZeroCurrency, initialReward, types.ZeroCurrency, initialReward) // mine a block to confirm the transaction - mineAndSync(t, cm, ws, types.VoidAddress, 1) + mineAndSync(t, cm, ws, w, types.VoidAddress, 1) // check that the balance was confirmed and the other values reset assertBalance(t, w, initialReward, initialReward, types.ZeroCurrency, types.ZeroCurrency) @@ -875,7 +878,7 @@ func TestWalletV2(t *testing.T) { } // mine until the v2 require height - mineAndSync(t, cm, ws, types.VoidAddress, network.HardforkV2.RequireHeight-cm.Tip().Height) + mineAndSync(t, cm, ws, w, types.VoidAddress, network.HardforkV2.RequireHeight-cm.Tip().Height) v2Txn := types.V2Transaction{ SiacoinOutputs: []types.SiacoinOutput{ @@ -908,7 +911,7 @@ func TestWalletV2(t *testing.T) { } // confirm the transaction - mineAndSync(t, cm, ws, types.VoidAddress, 1) + mineAndSync(t, cm, ws, w, types.VoidAddress, 1) // check that the wallet has three events count, err = w.EventCount() @@ -960,10 +963,10 @@ func TestReorgV2(t *testing.T) { assertBalance(t, w, types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency, types.ZeroCurrency) // mine a block to fund the wallet - mineAndSync(t, cm, ws, w.Address(), 1) + mineAndSync(t, cm, ws, w, w.Address(), 1) maturityHeight := cm.TipState().MaturityHeight() // mine until the require height - mineAndSync(t, cm, ws, types.VoidAddress, network.HardforkV2.RequireHeight-cm.Tip().Height) + mineAndSync(t, cm, ws, w, types.VoidAddress, network.HardforkV2.RequireHeight-cm.Tip().Height) // check that the wallet has a single event if events, err := w.Events(0, 100); err != nil { @@ -1000,7 +1003,7 @@ func TestReorgV2(t *testing.T) { // mine until the payout matures tip := cm.TipState() target := tip.MaturityHeight() - mineAndSync(t, cm, ws, types.VoidAddress, target-tip.Index.Height) + mineAndSync(t, cm, ws, w, types.VoidAddress, target-tip.Index.Height) // check that one payout has matured assertBalance(t, w, initialReward, initialReward, types.ZeroCurrency, types.ZeroCurrency) @@ -1067,7 +1070,7 @@ func TestReorgV2(t *testing.T) { // transaction is not yet confirmed. assertBalance(t, w, types.ZeroCurrency, initialReward, types.ZeroCurrency, initialReward) // mine a block to confirm the transaction - mineAndSync(t, cm, ws, types.VoidAddress, 1) + mineAndSync(t, cm, ws, w, types.VoidAddress, 1) // save a marker to this state to rollback to later rollbackState := cm.TipState() @@ -1124,7 +1127,7 @@ func TestReorgV2(t *testing.T) { if _, err := cm.AddV2PoolTransactions(state.Index, []types.V2Transaction{txn1}); err != nil { t.Fatal(err) } - mineAndSync(t, cm, ws, types.VoidAddress, 1) + mineAndSync(t, cm, ws, w, types.VoidAddress, 1) // check that the wallet now has 3 transactions: the initial payout // transaction, the split transaction, and a void transaction @@ -1185,7 +1188,7 @@ func TestReorgV2(t *testing.T) { if err := cm.AddBlocks(reorgBlocks); err != nil { t.Fatal(err) - } else if err := syncDB(cm, ws); err != nil { + } else if err := syncDB(cm, ws, w); err != nil { t.Fatal(err) } else if cm.Tip() != state.Index { t.Fatalf("expected tip %v, got %v", state.Index, cm.Tip())