diff --git a/chain/db.go b/chain/db.go index 55035ac..e7b2bc2 100644 --- a/chain/db.go +++ b/chain/db.go @@ -331,6 +331,9 @@ func (db *DBStore) treeKey(row, col uint64) []byte { } func (db *DBStore) getElementProof(leafIndex, numLeaves uint64) (proof []types.Hash256) { + if leafIndex >= numLeaves { + panic(fmt.Sprintf("leafIndex %v exceeds accumulator size %v", leafIndex, numLeaves)) // should never happen + } // The size of the proof is the mergeHeight of leafIndex and numLeaves. To // see why, imagine a tree large enough to contain both leafIndex and // numLeaves within the same subtree; the height at which the paths to those @@ -348,7 +351,9 @@ func (db *DBStore) getElementProof(leafIndex, numLeaves uint64) (proof []types.H func (db *DBStore) getSiacoinElement(id types.SiacoinOutputID, numLeaves uint64) (sce types.SiacoinElement, ok bool) { ok = db.bucket(bSiacoinElements).get(id[:], &sce) - sce.MerkleProof = db.getElementProof(sce.LeafIndex, numLeaves) + if ok { + sce.MerkleProof = db.getElementProof(sce.LeafIndex, numLeaves) + } return } @@ -363,7 +368,9 @@ func (db *DBStore) deleteSiacoinElement(id types.SiacoinOutputID) { func (db *DBStore) getSiafundElement(id types.SiafundOutputID, numLeaves uint64) (sfe types.SiafundElement, ok bool) { ok = db.bucket(bSiafundElements).get(id[:], &sfe) - sfe.MerkleProof = db.getElementProof(sfe.LeafIndex, numLeaves) + if ok { + sfe.MerkleProof = db.getElementProof(sfe.LeafIndex, numLeaves) + } return } @@ -378,7 +385,9 @@ func (db *DBStore) deleteSiafundElement(id types.SiafundOutputID) { func (db *DBStore) getFileContractElement(id types.FileContractID, numLeaves uint64) (fce types.FileContractElement, ok bool) { ok = db.bucket(bFileContractElements).get(id[:], &fce) - fce.MerkleProof = db.getElementProof(fce.LeafIndex, numLeaves) + if ok { + fce.MerkleProof = db.getElementProof(fce.LeafIndex, numLeaves) + } return } @@ -428,17 +437,17 @@ func (db *DBStore) applyElements(cau consensus.ApplyUpdate) { db.bucket(bTree).put(db.treeKey(row, col), h) }) - cau.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { - if sce.LeafIndex == types.EphemeralLeafIndex { - return + cau.ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool) { + if created && spent { + return // ephemeral } else if spent { db.deleteSiacoinElement(types.SiacoinOutputID(sce.ID)) } else { db.putSiacoinElement(sce) } }) - cau.ForEachSiafundElement(func(sfe types.SiafundElement, spent bool) { - if sfe.LeafIndex == types.EphemeralLeafIndex { + cau.ForEachSiafundElement(func(sfe types.SiafundElement, created, spent bool) { + if created && spent { return } else if spent { db.deleteSiafundElement(types.SiafundOutputID(sfe.ID)) @@ -446,8 +455,10 @@ func (db *DBStore) applyElements(cau consensus.ApplyUpdate) { db.putSiafundElement(sfe) } }) - cau.ForEachFileContractElement(func(fce types.FileContractElement, rev *types.FileContractElement, resolved, valid bool) { - if resolved { + cau.ForEachFileContractElement(func(fce types.FileContractElement, created bool, rev *types.FileContractElement, resolved, valid bool) { + if created && resolved { + return + } else if resolved { db.deleteFileContractElement(types.FileContractID(fce.ID)) db.deleteFileContractExpiration(types.FileContractID(fce.ID), fce.FileContract.WindowEnd) } else if rev != nil { @@ -464,8 +475,10 @@ func (db *DBStore) applyElements(cau consensus.ApplyUpdate) { } func (db *DBStore) revertElements(cru consensus.RevertUpdate) { - cru.ForEachFileContractElement(func(fce types.FileContractElement, rev *types.FileContractElement, resolved, valid bool) { - if resolved { + cru.ForEachFileContractElement(func(fce types.FileContractElement, created bool, rev *types.FileContractElement, resolved, valid bool) { + if created && resolved { + return + } else if resolved { // contract no longer resolved; restore it db.putFileContractElement(fce) db.putFileContractExpiration(types.FileContractID(fce.ID), fce.FileContract.WindowEnd) @@ -482,8 +495,8 @@ func (db *DBStore) revertElements(cru consensus.RevertUpdate) { db.deleteFileContractExpiration(types.FileContractID(fce.ID), fce.FileContract.WindowEnd) } }) - cru.ForEachSiafundElement(func(sfe types.SiafundElement, spent bool) { - if sfe.LeafIndex == types.EphemeralLeafIndex { + cru.ForEachSiafundElement(func(sfe types.SiafundElement, created, spent bool) { + if created && spent { return } else if spent { // output no longer spent; restore it @@ -493,8 +506,8 @@ func (db *DBStore) revertElements(cru consensus.RevertUpdate) { db.deleteSiafundElement(types.SiafundOutputID(sfe.ID)) } }) - cru.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { - if sce.LeafIndex == types.EphemeralLeafIndex { + cru.ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool) { + if created && spent { return } else if spent { // output no longer spent; restore it diff --git a/chain/manager.go b/chain/manager.go index f89a1c6..364f0b1 100644 --- a/chain/manager.go +++ b/chain/manager.go @@ -614,7 +614,7 @@ func updateTxnProofs(txn *types.V2Transaction, updateElementProof func(*types.St valid = true updateProof := func(e *types.StateElement) { valid = valid && e.LeafIndex < numLeaves - if !valid || e.LeafIndex == types.EphemeralLeafIndex { + if !valid || e.LeafIndex == types.UnassignedLeafIndex { return } updateElementProof(e) @@ -638,36 +638,37 @@ func updateTxnProofs(txn *types.V2Transaction, updateElementProof func(*types.St } func (m *Manager) revertPoolUpdate(cru consensus.RevertUpdate, cs consensus.State) { - // restore ephemeral elements, if necessary + // applying a block can make ephemeral elements in the txpool non-ephemeral; + // here, we undo that var uncreated map[types.Hash256]bool replaceEphemeral := func(e *types.StateElement) { - if e.LeafIndex != types.EphemeralLeafIndex { + if e.LeafIndex == types.UnassignedLeafIndex { return } else if uncreated == nil { uncreated = make(map[types.Hash256]bool) - cru.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { - if !spent { + cru.ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool) { + if created { uncreated[sce.ID] = true } }) - cru.ForEachSiafundElement(func(sfe types.SiafundElement, spent bool) { - if !spent { + cru.ForEachSiafundElement(func(sfe types.SiafundElement, created, spent bool) { + if created { uncreated[sfe.ID] = true } }) - cru.ForEachFileContractElement(func(fce types.FileContractElement, rev *types.FileContractElement, resolved, valid bool) { - if !resolved { + cru.ForEachFileContractElement(func(fce types.FileContractElement, created bool, rev *types.FileContractElement, resolved, valid bool) { + if created { uncreated[fce.ID] = true } }) - cru.ForEachV2FileContractElement(func(fce types.V2FileContractElement, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { - if res != nil { + cru.ForEachV2FileContractElement(func(fce types.V2FileContractElement, created bool, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { + if created { uncreated[fce.ID] = true } }) } if uncreated[e.ID] { - *e = types.StateElement{ID: e.ID, LeafIndex: types.EphemeralLeafIndex} + *e = types.StateElement{ID: e.ID, LeafIndex: types.UnassignedLeafIndex} } } for _, txn := range m.txpool.v2txns { @@ -695,35 +696,37 @@ func (m *Manager) revertPoolUpdate(cru consensus.RevertUpdate, cs consensus.Stat } func (m *Manager) applyPoolUpdate(cau consensus.ApplyUpdate, cs consensus.State) { - // replace ephemeral elements, if necessary + // applying a block can make ephemeral elements in the txpool non-ephemeral var newElements map[types.Hash256]types.StateElement replaceEphemeral := func(e *types.StateElement) { - if e.LeafIndex != types.EphemeralLeafIndex { + if e.LeafIndex != types.UnassignedLeafIndex { return } else if newElements == nil { newElements = make(map[types.Hash256]types.StateElement) - cau.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { - if !spent { + cau.ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool) { + if created { newElements[sce.ID] = sce.StateElement } }) - cau.ForEachSiafundElement(func(sfe types.SiafundElement, spent bool) { - if !spent { + cau.ForEachSiafundElement(func(sfe types.SiafundElement, created, spent bool) { + if created { newElements[sfe.ID] = sfe.StateElement } }) - cau.ForEachFileContractElement(func(fce types.FileContractElement, rev *types.FileContractElement, resolved, valid bool) { - if !resolved { + cau.ForEachFileContractElement(func(fce types.FileContractElement, created bool, rev *types.FileContractElement, resolved, valid bool) { + if created { newElements[fce.ID] = fce.StateElement } }) - cau.ForEachV2FileContractElement(func(fce types.V2FileContractElement, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { - if res != nil { + cau.ForEachV2FileContractElement(func(fce types.V2FileContractElement, created bool, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { + if created { newElements[fce.ID] = fce.StateElement } }) } - *e = newElements[e.ID] + if se, ok := newElements[e.ID]; ok { + *e = se + } } for _, txn := range m.txpool.v2txns { for i := range txn.SiacoinInputs { diff --git a/go.mod b/go.mod index 8db149f..c3cbeba 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.2.9 + go.sia.tech/core v0.3.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.24.0 lukechampine.com/frand v1.4.2 diff --git a/go.sum b/go.sum index f77ddd5..3f609f9 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.2.8 h1:NQZw8gz9XWlkw9zr7HrLIA3xQnoatp8lYzyONS0IXJg= -go.sia.tech/core v0.2.8/go.mod h1:BMgT/reXtgv6XbDgUYTCPY7wSMbspDRDs7KMi1vL6Iw= -go.sia.tech/core v0.2.9 h1:UnO+wXQ3w2dMaU3ULA95fLYspllxaYPSfVW08fFIxQU= -go.sia.tech/core v0.2.9/go.mod h1:BMgT/reXtgv6XbDgUYTCPY7wSMbspDRDs7KMi1vL6Iw= +go.sia.tech/core v0.3.0 h1:PDfAQh9z8PYD+oeVS7rS9SEnTMOZzwwFfAH45yktmko= +go.sia.tech/core v0.3.0/go.mod h1:BMgT/reXtgv6XbDgUYTCPY7wSMbspDRDs7KMi1vL6Iw= 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/wallet/update.go b/wallet/update.go index 18e59dd..f4d39cf 100644 --- a/wallet/update.go +++ b/wallet/update.go @@ -55,7 +55,7 @@ func appliedEvents(cau chain.ApplyUpdate, walletAddress types.Address) (events [ siacoinElements := make(map[types.SiacoinOutputID]types.SiacoinElement) // cache the value of siacoin elements to use when calculating v1 outflow - cau.ForEachSiacoinElement(func(se types.SiacoinElement, spent bool) { + cau.ForEachSiacoinElement(func(se types.SiacoinElement, created, spent bool) { siacoinElements[types.SiacoinOutputID(se.ID)] = se }) @@ -187,7 +187,8 @@ func appliedEvents(cau chain.ApplyUpdate, walletAddress types.Address) (events [ addEvent(types.Hash256(txn.ID()), EventTypeV2Transaction, EventV2Transaction(txn)) } - cau.ForEachFileContractElement(func(fce types.FileContractElement, rev *types.FileContractElement, resolved bool, valid bool) { + // add the file contract outputs + cau.ForEachFileContractElement(func(fce types.FileContractElement, created bool, rev *types.FileContractElement, resolved bool, valid bool) { if !resolved { return } @@ -221,7 +222,7 @@ func appliedEvents(cau chain.ApplyUpdate, walletAddress types.Address) (events [ } }) - cau.ForEachV2FileContractElement(func(fce types.V2FileContractElement, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { + cau.ForEachV2FileContractElement(func(fce types.V2FileContractElement, created bool, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { if res == nil { return } @@ -287,31 +288,10 @@ func applyChainState(tx UpdateTx, address types.Address, cau chain.ApplyUpdate) return fmt.Errorf("failed to get state elements: %w", err) } - // determine which siacoin and siafund elements are ephemeral - // - // note: I thought we could use LeafIndex == EphemeralLeafIndex, but - // it seems to be set before the subscriber is called. - created := make(map[types.Hash256]bool) - ephemeral := make(map[types.Hash256]bool) - for _, txn := range cau.Block.Transactions { - for i := range txn.SiacoinOutputs { - created[types.Hash256(txn.SiacoinOutputID(i))] = true - } - for _, input := range txn.SiacoinInputs { - ephemeral[types.Hash256(input.ParentID)] = created[types.Hash256(input.ParentID)] - } - for i := range txn.SiafundOutputs { - created[types.Hash256(txn.SiafundOutputID(i))] = true - } - for _, input := range txn.SiafundInputs { - ephemeral[types.Hash256(input.ParentID)] = created[types.Hash256(input.ParentID)] - } - } - var createdUTXOs, spentUTXOs []types.SiacoinElement utxoValues := make(map[types.SiacoinOutputID]types.Currency) - cau.ForEachSiacoinElement(func(se types.SiacoinElement, spent bool) { + cau.ForEachSiacoinElement(func(se types.SiacoinElement, created, spent bool) { if se.SiacoinOutput.Address != address { return } @@ -320,7 +300,7 @@ func applyChainState(tx UpdateTx, address types.Address, cau chain.ApplyUpdate) utxoValues[types.SiacoinOutputID(se.ID)] = se.SiacoinOutput.Value // ignore ephemeral elements - if ephemeral[se.ID] { + if created && spent { return } @@ -346,11 +326,10 @@ func applyChainState(tx UpdateTx, address types.Address, cau chain.ApplyUpdate) // revertChainUpdate atomically reverts a chain update from a wallet func revertChainUpdate(tx UpdateTx, revertedIndex types.ChainIndex, address types.Address, cru chain.RevertUpdate) error { var removedUTXOs, unspentUTXOs []types.SiacoinElement - cru.ForEachSiacoinElement(func(se types.SiacoinElement, spent bool) { - if se.SiacoinOutput.Address != address { + cru.ForEachSiacoinElement(func(se types.SiacoinElement, created, spent bool) { + if se.SiacoinOutput.Address != address || (created && spent) { return } - if spent { unspentUTXOs = append(unspentUTXOs, se) } else {