From bae2b8fd302916b537b09b0f7765c467b8dc14a2 Mon Sep 17 00:00:00 2001 From: lukechampine Date: Wed, 3 Jan 2024 18:33:46 -0500 Subject: [PATCH] chain: Support Merkle proof fixups in AddV2PoolTransactions --- chain/manager.go | 193 +++++++++++++++++++++++++++----------------- gateway/encoding.go | 3 + gateway/peer.go | 8 +- 3 files changed, 125 insertions(+), 79 deletions(-) diff --git a/chain/manager.go b/chain/manager.go index 3b8ee2f..cf9a9f4 100644 --- a/chain/manager.go +++ b/chain/manager.go @@ -628,36 +628,65 @@ func (m *Manager) computeParentMap() map[types.Hash256]int { return m.txpool.parentMap } -func (m *Manager) applyPoolUpdate(cau *ApplyUpdate) { - // replace ephemeral elements, if necessary - var newElements map[types.Hash256]types.StateElement +func updateTxnProofs(txn *types.V2Transaction, updateElementProof func(*types.StateElement), numLeaves uint64) (valid bool) { + valid = true + updateProof := func(e *types.StateElement) { + valid = valid && e.LeafIndex < numLeaves + if !valid || e.LeafIndex == types.EphemeralLeafIndex { + return + } + updateElementProof(e) + } + for i := range txn.SiacoinInputs { + updateProof(&txn.SiacoinInputs[i].Parent.StateElement) + } + for i := range txn.SiafundInputs { + updateProof(&txn.SiafundInputs[i].Parent.StateElement) + } + for i := range txn.FileContractRevisions { + updateProof(&txn.FileContractRevisions[i].Parent.StateElement) + } + for i := range txn.FileContractResolutions { + updateProof(&txn.FileContractResolutions[i].Parent.StateElement) + if sp, ok := txn.FileContractResolutions[i].Resolution.(*types.V2StorageProof); ok { + updateProof(&sp.ProofIndex.StateElement) + } + } + return +} + +func (m *Manager) revertPoolUpdate(cru *RevertUpdate) { + // restore ephemeral elements, if necessary + var uncreated map[types.Hash256]bool replaceEphemeral := func(e *types.StateElement) { if e.LeafIndex != types.EphemeralLeafIndex { return - } else if newElements == nil { - newElements = make(map[types.Hash256]types.StateElement) - cau.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { + } else if uncreated == nil { + uncreated = make(map[types.Hash256]bool) + cru.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { if !spent { - newElements[sce.ID] = sce.StateElement + uncreated[sce.ID] = true } }) - cau.ForEachSiafundElement(func(sfe types.SiafundElement, spent bool) { + cru.ForEachSiafundElement(func(sfe types.SiafundElement, spent bool) { if !spent { - newElements[sfe.ID] = sfe.StateElement + uncreated[sfe.ID] = true } }) - cau.ForEachFileContractElement(func(fce types.FileContractElement, rev *types.FileContractElement, resolved, valid bool) { + cru.ForEachFileContractElement(func(fce types.FileContractElement, rev *types.FileContractElement, resolved, valid bool) { if !resolved { - newElements[fce.ID] = fce.StateElement + uncreated[fce.ID] = true } }) - cau.ForEachV2FileContractElement(func(fce types.V2FileContractElement, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { + cru.ForEachV2FileContractElement(func(fce types.V2FileContractElement, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { if res != nil { - newElements[fce.ID] = fce.StateElement + uncreated[fce.ID] = true } }) } - *e = newElements[e.ID] + if uncreated[e.ID] { + *e = types.StateElement{ID: e.ID, LeafIndex: types.EphemeralLeafIndex} + } } for _, txn := range m.txpool.v2txns { for i := range txn.SiacoinInputs { @@ -674,65 +703,45 @@ func (m *Manager) applyPoolUpdate(cau *ApplyUpdate) { } } - // update proofs, noop-ing instead of panicking on un-updateable elements; - // they will be removed later by revalidatePool - updateProof := func(e *types.StateElement) { - if e.LeafIndex == types.EphemeralLeafIndex || e.LeafIndex >= cau.State.Elements.NumLeaves { - return - } - cau.UpdateElementProof(e) - } + rem := m.txpool.v2txns[:0] for _, txn := range m.txpool.v2txns { - for i := range txn.SiacoinInputs { - updateProof(&txn.SiacoinInputs[i].Parent.StateElement) - } - for i := range txn.SiafundInputs { - updateProof(&txn.SiafundInputs[i].Parent.StateElement) - } - for i := range txn.FileContractRevisions { - updateProof(&txn.FileContractRevisions[i].Parent.StateElement) - } - for i := range txn.FileContractResolutions { - updateProof(&txn.FileContractResolutions[i].Parent.StateElement) - if sp, ok := txn.FileContractResolutions[i].Resolution.(*types.V2StorageProof); ok { - updateProof(&sp.ProofIndex.StateElement) - } + if updateTxnProofs(&txn, cru.UpdateElementProof, cru.State.Elements.NumLeaves) { + rem = append(rem, txn) } } + m.txpool.v2txns = rem } -func (m *Manager) revertPoolUpdate(cru *RevertUpdate) { - // restore ephemeral elements, if necessary - var uncreated map[types.Hash256]bool +func (m *Manager) applyPoolUpdate(cau *ApplyUpdate) { + // replace ephemeral elements, if necessary + var newElements map[types.Hash256]types.StateElement replaceEphemeral := func(e *types.StateElement) { if e.LeafIndex != types.EphemeralLeafIndex { return - } else if uncreated == nil { - uncreated = make(map[types.Hash256]bool) - cru.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { + } else if newElements == nil { + newElements = make(map[types.Hash256]types.StateElement) + cau.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { if !spent { - uncreated[sce.ID] = true + newElements[sce.ID] = sce.StateElement } }) - cru.ForEachSiafundElement(func(sfe types.SiafundElement, spent bool) { + cau.ForEachSiafundElement(func(sfe types.SiafundElement, spent bool) { if !spent { - uncreated[sfe.ID] = true + newElements[sfe.ID] = sfe.StateElement } }) - cru.ForEachFileContractElement(func(fce types.FileContractElement, rev *types.FileContractElement, resolved, valid bool) { + cau.ForEachFileContractElement(func(fce types.FileContractElement, rev *types.FileContractElement, resolved, valid bool) { if !resolved { - uncreated[fce.ID] = true + newElements[fce.ID] = fce.StateElement } }) - cru.ForEachV2FileContractElement(func(fce types.V2FileContractElement, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { + cau.ForEachV2FileContractElement(func(fce types.V2FileContractElement, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { if res != nil { - uncreated[fce.ID] = true + newElements[fce.ID] = fce.StateElement } }) } - if uncreated[e.ID] { - *e = types.StateElement{ID: e.ID, LeafIndex: types.EphemeralLeafIndex} - } + *e = newElements[e.ID] } for _, txn := range m.txpool.v2txns { for i := range txn.SiacoinInputs { @@ -749,31 +758,13 @@ func (m *Manager) revertPoolUpdate(cru *RevertUpdate) { } } - // update proofs, noop-ing instead of panicking on un-updateable elements; - // they will be removed later by revalidatePool - updateProof := func(e *types.StateElement) { - if e.LeafIndex == types.EphemeralLeafIndex || e.LeafIndex >= cru.State.Elements.NumLeaves { - return - } - cru.UpdateElementProof(e) - } + rem := m.txpool.v2txns[:0] for _, txn := range m.txpool.v2txns { - for i := range txn.SiacoinInputs { - updateProof(&txn.SiacoinInputs[i].Parent.StateElement) - } - for i := range txn.SiafundInputs { - updateProof(&txn.SiafundInputs[i].Parent.StateElement) - } - for i := range txn.FileContractRevisions { - updateProof(&txn.FileContractRevisions[i].Parent.StateElement) - } - for i := range txn.FileContractResolutions { - updateProof(&txn.FileContractResolutions[i].Parent.StateElement) - if sp, ok := txn.FileContractResolutions[i].Resolution.(*types.V2StorageProof); ok { - updateProof(&sp.ProofIndex.StateElement) - } + if updateTxnProofs(&txn, cau.UpdateElementProof, cau.State.Elements.NumLeaves) { + rem = append(rem, txn) } } + m.txpool.v2txns = rem } // PoolTransaction returns the transaction with the specified ID, if it is @@ -979,11 +970,63 @@ func (m *Manager) AddPoolTransactions(txns []types.Transaction) error { // // If any transaction in the set is invalid, the entire set is rejected and none // of the transactions are added to the pool. -func (m *Manager) AddV2PoolTransactions(txns []types.V2Transaction) error { +// +// Since v2 transactions include Merkle proofs, AddV2PoolTransactions takes an +// index specifying the accumulator state for which those proofs are assumed to +// be valid. If that index differs from the Manager's current tip, the Merkle +// proofs will be updated accordingly. The original transactions are not +// modified. +func (m *Manager) AddV2PoolTransactions(index types.ChainIndex, txns []types.V2Transaction) error { m.mu.Lock() defer m.mu.Unlock() m.revalidatePool() + if index != m.tipState.Index { + // bring txns up-to-date, preserving the originals + txns = append([]types.V2Transaction(nil), txns...) + for i := range txns { + txns[i] = txns[i].DeepCopy() + } + revert, apply, err := m.reorgPath(index, m.tipState.Index) + if err != nil { + return fmt.Errorf("couldn't determine reorg path from %v to %v: %w", index, m.tipState.Index, err) + } else if len(revert)+len(apply) > 144 { + return fmt.Errorf("reorg path from %v to %v is too long (-%v +%v)", index, m.tipState.Index, len(revert), len(apply)) + } + for _, index := range revert { + b, bs, cs, ok := blockAndParent(m.store, index.ID) + if !ok { + return fmt.Errorf("missing reverted block at index %v", index) + } else if bs == nil { + panic("missing supplement for reverted block") + } + cru := consensus.RevertBlock(cs, b, *bs) + for i := range txns { + if !updateTxnProofs(&txns[i], cru.UpdateElementProof, cs.Elements.NumLeaves) { + return fmt.Errorf("transaction %v references element that does not exist in our chain", txns[i].ID()) + } + } + } + for _, index := range apply { + b, bs, cs, ok := blockAndParent(m.store, index.ID) + if !ok { + return fmt.Errorf("missing applied block at index %v", index) + } else if bs == nil { + panic("missing supplement for applied block") + } + ancestorTimestamp, ok := m.store.AncestorTimestamp(b.ParentID) + if !ok && index.Height != 0 { + return fmt.Errorf("missing ancestor timestamp for block %v", b.ParentID) + } + cs, cau := consensus.ApplyBlock(cs, b, *bs, ancestorTimestamp) + for i := range txns { + if !updateTxnProofs(&txns[i], cau.UpdateElementProof, cs.Elements.NumLeaves) { + return fmt.Errorf("transaction %v references element that does not exist in our chain", txns[i].ID()) + } + } + } + } + // validate as a standalone set ms := consensus.NewMidState(m.tipState) for _, txn := range txns { diff --git a/gateway/encoding.go b/gateway/encoding.go index 929bd09..734f700 100644 --- a/gateway/encoding.go +++ b/gateway/encoding.go @@ -436,17 +436,20 @@ func (r *RPCRelayV2BlockOutline) maxRequestLen() int { return 5e6 } // RPCRelayV2TransactionSet relays a v2 transaction set. type RPCRelayV2TransactionSet struct { + Index types.ChainIndex Transactions []types.V2Transaction emptyResponse } func (r *RPCRelayV2TransactionSet) encodeRequest(e *types.Encoder) { + r.Index.EncodeTo(e) e.WritePrefix(len(r.Transactions)) for i := range r.Transactions { r.Transactions[i].EncodeTo(e) } } func (r *RPCRelayV2TransactionSet) decodeRequest(d *types.Decoder) { + r.Index.DecodeFrom(d) r.Transactions = make([]types.V2Transaction, d.ReadPrefix()) for i := range r.Transactions { r.Transactions[i].DecodeFrom(d) diff --git a/gateway/peer.go b/gateway/peer.go index 8eefb43..08ab071 100644 --- a/gateway/peer.go +++ b/gateway/peer.go @@ -107,7 +107,7 @@ type RPCHandler interface { Checkpoint(index types.ChainIndex) (types.Block, consensus.State, error) RelayV2Header(h V2BlockHeader, origin *Peer) RelayV2BlockOutline(b V2BlockOutline, origin *Peer) - RelayV2TransactionSet(txns []types.V2Transaction, origin *Peer) + RelayV2TransactionSet(index types.ChainIndex, txns []types.V2Transaction, origin *Peer) } // HandleRPC handles an RPC received from the peer. @@ -209,7 +209,7 @@ func (p *Peer) HandleRPC(id types.Specifier, stream net.Conn, h RPCHandler) erro if err := p.withDecoder(stream, r.maxRequestLen(), r.decodeRequest); err != nil { return err } - h.RelayV2TransactionSet(r.Transactions, p) + h.RelayV2TransactionSet(r.Index, r.Transactions, p) return nil case *RPCSendV2Blocks: err := p.withDecoder(stream, r.maxRequestLen(), r.decodeRequest) @@ -359,8 +359,8 @@ func (p *Peer) RelayV2BlockOutline(b V2BlockOutline, timeout time.Duration) erro } // RelayV2TransactionSet relays a v2 transaction set to the peer. -func (p *Peer) RelayV2TransactionSet(txns []types.V2Transaction, timeout time.Duration) error { - return p.callRPC(&RPCRelayV2TransactionSet{Transactions: txns}, timeout) +func (p *Peer) RelayV2TransactionSet(index types.ChainIndex, txns []types.V2Transaction, timeout time.Duration) error { + return p.callRPC(&RPCRelayV2TransactionSet{Index: index, Transactions: txns}, timeout) } // SendV2Blocks requests up to n blocks from p, starting from the most recent