Skip to content

Commit

Permalink
chain: Support Merkle proof fixups in AddV2PoolTransactions
Browse files Browse the repository at this point in the history
  • Loading branch information
lukechampine committed Jan 3, 2024
1 parent cb031ab commit bae2b8f
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 79 deletions.
193 changes: 118 additions & 75 deletions chain/manager.go
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions gateway/encoding.go
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions gateway/peer.go
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit bae2b8f

Please sign in to comment.