diff --git a/consensus/merkle.go b/consensus/merkle.go index 61ea999c..c208d4df 100644 --- a/consensus/merkle.go +++ b/consensus/merkle.go @@ -400,7 +400,7 @@ type elementApplyUpdate struct { } func (eau *elementApplyUpdate) updateElementProof(e *types.StateElement) { - if e.LeafIndex == types.EphemeralLeafIndex { + if e.LeafIndex == types.UnassignedLeafIndex { panic("cannot update an ephemeral element") } else if e.LeafIndex >= eau.oldNumLeaves { return // newly-added element @@ -417,7 +417,7 @@ type elementRevertUpdate struct { } func (eru *elementRevertUpdate) updateElementProof(e *types.StateElement) { - if e.LeafIndex == types.EphemeralLeafIndex { + if e.LeafIndex == types.UnassignedLeafIndex { panic("cannot update an ephemeral element") } else if e.LeafIndex >= eru.numLeaves { panic("cannot update an element that is not present in the accumulator") diff --git a/consensus/merkle_test.go b/consensus/merkle_test.go index 6dd7b6f1..bbb3ffbf 100644 --- a/consensus/merkle_test.go +++ b/consensus/merkle_test.go @@ -90,8 +90,8 @@ func TestUpdateElementProof(t *testing.T) { expectProofLen int }{ { - name: "EphemeralLeafIndexPanic", - leafIndex: types.EphemeralLeafIndex, + name: "UnassignedLeafIndexPanic", + leafIndex: types.UnassignedLeafIndex, numLeaves: 5, expectPanic: true, }, diff --git a/consensus/state.go b/consensus/state.go index aa709705..04df8e12 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -586,7 +586,7 @@ func (s State) AttestationSigHash(a types.Attestation) types.Hash256 { // A MidState represents the state of the chain within a block. type MidState struct { base State - ephemeral map[types.Hash256]int // indices into element slices + created map[types.Hash256]int // indices into element slices spends map[types.Hash256]types.TransactionID revs map[types.Hash256]*types.FileContractElement res map[types.Hash256]bool @@ -596,7 +596,7 @@ type MidState struct { foundationPrimary types.Address foundationFailsafe types.Address - // elements updated/added by block + // elements created/updated by block sces []types.SiacoinElement sfes []types.SiafundElement fces []types.FileContractElement @@ -606,21 +606,21 @@ type MidState struct { } func (ms *MidState) siacoinElement(ts V1TransactionSupplement, id types.SiacoinOutputID) (types.SiacoinElement, bool) { - if i, ok := ms.ephemeral[types.Hash256(id)]; ok { + if i, ok := ms.created[types.Hash256(id)]; ok { return ms.sces[i], true } return ts.siacoinElement(id) } func (ms *MidState) siafundElement(ts V1TransactionSupplement, id types.SiafundOutputID) (types.SiafundElement, bool) { - if i, ok := ms.ephemeral[types.Hash256(id)]; ok { + if i, ok := ms.created[types.Hash256(id)]; ok { return ms.sfes[i], true } return ts.siafundElement(id) } func (ms *MidState) fileContractElement(ts V1TransactionSupplement, id types.FileContractID) (types.FileContractElement, bool) { - if i, ok := ms.ephemeral[types.Hash256(id)]; ok { + if i, ok := ms.created[types.Hash256(id)]; ok { return ms.fces[i], true } return ts.fileContractElement(id) @@ -660,11 +660,16 @@ func (ms *MidState) isSpent(id types.Hash256) bool { return ok } +func (ms *MidState) isCreated(id types.Hash256) bool { + _, ok := ms.created[id] + return ok || id == ms.cie.ID +} + // NewMidState constructs a MidState initialized to the provided base state. func NewMidState(s State) *MidState { return &MidState{ base: s, - ephemeral: make(map[types.Hash256]int), + created: make(map[types.Hash256]int), spends: make(map[types.Hash256]types.TransactionID), revs: make(map[types.Hash256]*types.FileContractElement), res: make(map[types.Hash256]bool), diff --git a/consensus/update.go b/consensus/update.go index 86fc0c1f..cc0acc9c 100644 --- a/consensus/update.go +++ b/consensus/update.go @@ -334,41 +334,59 @@ func ApplyOrphan(s State, b types.Block, targetTimestamp time.Time) State { return next } -func (ms *MidState) addSiacoinElement(sce types.SiacoinElement) { +func (ms *MidState) addSiacoinElement(id types.SiacoinOutputID, sco types.SiacoinOutput) { + sce := types.SiacoinElement{ + StateElement: types.StateElement{ID: types.Hash256(id)}, + SiacoinOutput: sco, + } ms.sces = append(ms.sces, sce) - ms.ephemeral[ms.sces[len(ms.sces)-1].ID] = len(ms.sces) - 1 + ms.created[ms.sces[len(ms.sces)-1].ID] = len(ms.sces) - 1 +} + +func (ms *MidState) addImmatureSiacoinElement(id types.SiacoinOutputID, sco types.SiacoinOutput) { + ms.addSiacoinElement(id, sco) + ms.sces[len(ms.sces)-1].MaturityHeight = ms.base.MaturityHeight() } func (ms *MidState) spendSiacoinElement(sce types.SiacoinElement, txid types.TransactionID) { ms.spends[sce.ID] = txid - if _, ok := ms.ephemeral[sce.ID]; !ok { + if !ms.isCreated(sce.ID) { sce.MerkleProof = append([]types.Hash256(nil), sce.MerkleProof...) ms.sces = append(ms.sces, sce) } } -func (ms *MidState) addSiafundElement(sfe types.SiafundElement) { +func (ms *MidState) addSiafundElement(id types.SiafundOutputID, sfo types.SiafundOutput) { + sfe := types.SiafundElement{ + StateElement: types.StateElement{ID: types.Hash256(id)}, + SiafundOutput: sfo, + ClaimStart: ms.siafundPool, + } ms.sfes = append(ms.sfes, sfe) - ms.ephemeral[ms.sfes[len(ms.sfes)-1].ID] = len(ms.sfes) - 1 + ms.created[ms.sfes[len(ms.sfes)-1].ID] = len(ms.sfes) - 1 } func (ms *MidState) spendSiafundElement(sfe types.SiafundElement, txid types.TransactionID) { ms.spends[sfe.ID] = txid - if _, ok := ms.ephemeral[sfe.ID]; !ok { + if !ms.isCreated(sfe.ID) { sfe.MerkleProof = append([]types.Hash256(nil), sfe.MerkleProof...) ms.sfes = append(ms.sfes, sfe) } } -func (ms *MidState) addFileContractElement(fce types.FileContractElement) { +func (ms *MidState) addFileContractElement(id types.FileContractID, fc types.FileContract) { + fce := types.FileContractElement{ + StateElement: types.StateElement{ID: types.Hash256(id)}, + FileContract: fc, + } ms.fces = append(ms.fces, fce) - ms.ephemeral[ms.fces[len(ms.fces)-1].ID] = len(ms.fces) - 1 + ms.created[ms.fces[len(ms.fces)-1].ID] = len(ms.fces) - 1 ms.siafundPool = ms.siafundPool.Add(ms.base.FileContractTax(fce.FileContract)) } func (ms *MidState) reviseFileContractElement(fce types.FileContractElement, rev types.FileContract) { rev.Payout = fce.FileContract.Payout - if i, ok := ms.ephemeral[fce.ID]; ok { + if i, ok := ms.created[fce.ID]; ok { ms.fces[i].FileContract = rev } else { if r, ok := ms.revs[fce.ID]; ok { @@ -392,14 +410,18 @@ func (ms *MidState) resolveFileContractElement(fce types.FileContractElement, va ms.fces = append(ms.fces, fce) } -func (ms *MidState) addV2FileContractElement(fce types.V2FileContractElement) { +func (ms *MidState) addV2FileContractElement(id types.FileContractID, fc types.V2FileContract) { + fce := types.V2FileContractElement{ + StateElement: types.StateElement{ID: types.Hash256(id)}, + V2FileContract: fc, + } ms.v2fces = append(ms.v2fces, fce) - ms.ephemeral[ms.v2fces[len(ms.v2fces)-1].ID] = len(ms.v2fces) - 1 + ms.created[ms.v2fces[len(ms.v2fces)-1].ID] = len(ms.v2fces) - 1 ms.siafundPool = ms.siafundPool.Add(ms.base.V2FileContractTax(fce.V2FileContract)) } func (ms *MidState) reviseV2FileContractElement(fce types.V2FileContractElement, rev types.V2FileContract) { - if i, ok := ms.ephemeral[fce.ID]; ok { + if i, ok := ms.created[fce.ID]; ok { ms.v2fces[i].V2FileContract = rev } else { if r, ok := ms.v2revs[fce.ID]; ok { @@ -434,33 +456,19 @@ func (ms *MidState) ApplyTransaction(txn types.Transaction, ts V1TransactionSupp ms.spendSiacoinElement(ms.mustSiacoinElement(ts, sci.ParentID), txid) } for i, sco := range txn.SiacoinOutputs { - ms.addSiacoinElement(types.SiacoinElement{ - StateElement: types.StateElement{ID: types.Hash256(txn.SiacoinOutputID(i))}, - SiacoinOutput: sco, - }) + ms.addSiacoinElement(txn.SiacoinOutputID(i), sco) } for _, sfi := range txn.SiafundInputs { sfe := ms.mustSiafundElement(ts, sfi.ParentID) claimPortion := ms.siafundPool.Sub(sfe.ClaimStart).Div64(ms.base.SiafundCount()).Mul64(sfe.SiafundOutput.Value) ms.spendSiafundElement(sfe, txid) - ms.addSiacoinElement(types.SiacoinElement{ - StateElement: types.StateElement{ID: types.Hash256(sfi.ParentID.ClaimOutputID())}, - SiacoinOutput: types.SiacoinOutput{Value: claimPortion, Address: sfi.ClaimAddress}, - MaturityHeight: ms.base.MaturityHeight(), - }) + ms.addImmatureSiacoinElement(sfi.ParentID.ClaimOutputID(), types.SiacoinOutput{Value: claimPortion, Address: sfi.ClaimAddress}) } for i, sfo := range txn.SiafundOutputs { - ms.addSiafundElement(types.SiafundElement{ - StateElement: types.StateElement{ID: types.Hash256(txn.SiafundOutputID(i))}, - SiafundOutput: sfo, - ClaimStart: ms.siafundPool, - }) + ms.addSiafundElement(txn.SiafundOutputID(i), sfo) } for i, fc := range txn.FileContracts { - ms.addFileContractElement(types.FileContractElement{ - StateElement: types.StateElement{ID: types.Hash256(txn.FileContractID(i))}, - FileContract: fc, - }) + ms.addFileContractElement(txn.FileContractID(i), fc) } for _, fcr := range txn.FileContractRevisions { ms.reviseFileContractElement(ms.mustFileContractElement(ts, fcr.ParentID), fcr.FileContract) @@ -469,11 +477,7 @@ func (ms *MidState) ApplyTransaction(txn types.Transaction, ts V1TransactionSupp fce := ms.mustFileContractElement(ts, sp.ParentID) ms.resolveFileContractElement(fce, true, txid) for i, sco := range fce.FileContract.ValidProofOutputs { - ms.addSiacoinElement(types.SiacoinElement{ - StateElement: types.StateElement{ID: types.Hash256(sp.ParentID.ValidOutputID(i))}, - SiacoinOutput: sco, - MaturityHeight: ms.base.MaturityHeight(), - }) + ms.addImmatureSiacoinElement(sp.ParentID.ValidOutputID(i), sco) } } if ms.base.Index.Height >= ms.base.Network.HardforkFoundation.Height { @@ -496,32 +500,18 @@ func (ms *MidState) ApplyV2Transaction(txn types.V2Transaction) { ms.spendSiacoinElement(sci.Parent, txid) } for i, sco := range txn.SiacoinOutputs { - ms.addSiacoinElement(types.SiacoinElement{ - StateElement: types.StateElement{ID: types.Hash256(txn.SiacoinOutputID(txid, i))}, - SiacoinOutput: sco, - }) + ms.addSiacoinElement(txn.SiacoinOutputID(txid, i), sco) } for _, sfi := range txn.SiafundInputs { ms.spendSiafundElement(sfi.Parent, txid) claimPortion := ms.siafundPool.Sub(sfi.Parent.ClaimStart).Div64(ms.base.SiafundCount()).Mul64(sfi.Parent.SiafundOutput.Value) - ms.addSiacoinElement(types.SiacoinElement{ - StateElement: types.StateElement{ID: types.Hash256(types.SiafundOutputID(sfi.Parent.ID).V2ClaimOutputID())}, - SiacoinOutput: types.SiacoinOutput{Value: claimPortion, Address: sfi.ClaimAddress}, - MaturityHeight: ms.base.MaturityHeight(), - }) + ms.addImmatureSiacoinElement(types.SiafundOutputID(sfi.Parent.ID).V2ClaimOutputID(), types.SiacoinOutput{Value: claimPortion, Address: sfi.ClaimAddress}) } for i, sfo := range txn.SiafundOutputs { - ms.addSiafundElement(types.SiafundElement{ - StateElement: types.StateElement{ID: types.Hash256(txn.SiafundOutputID(txid, i))}, - SiafundOutput: sfo, - ClaimStart: ms.siafundPool, - }) + ms.addSiafundElement(txn.SiafundOutputID(txid, i), sfo) } for i, fc := range txn.FileContracts { - ms.addV2FileContractElement(types.V2FileContractElement{ - StateElement: types.StateElement{ID: types.Hash256(txn.V2FileContractID(txid, i))}, - V2FileContract: fc, - }) + ms.addV2FileContractElement(txn.V2FileContractID(txid, i), fc) } for _, fcr := range txn.FileContractRevisions { ms.reviseV2FileContractElement(fcr.Parent, fcr.Revision) @@ -537,10 +527,7 @@ func (ms *MidState) ApplyV2Transaction(txn types.V2Transaction) { renter, host = r.FinalRevision.RenterOutput, r.FinalRevision.HostOutput renter.Value = renter.Value.Sub(r.RenterRollover) host.Value = host.Value.Sub(r.HostRollover) - ms.addV2FileContractElement(types.V2FileContractElement{ - StateElement: types.StateElement{ID: types.Hash256(types.FileContractID(fce.ID).V2RenewalID())}, - V2FileContract: r.NewContract, - }) + ms.addV2FileContractElement(types.FileContractID(fce.ID).V2RenewalID(), r.NewContract) case *types.V2StorageProof: renter, host = fc.RenterOutput, fc.HostOutput case *types.V2FileContractFinalization: @@ -548,16 +535,8 @@ func (ms *MidState) ApplyV2Transaction(txn types.V2Transaction) { case *types.V2FileContractExpiration: renter, host = fc.RenterOutput, fc.MissedHostOutput() } - ms.addSiacoinElement(types.SiacoinElement{ - StateElement: types.StateElement{ID: types.Hash256(types.FileContractID(fce.ID).V2RenterOutputID())}, - SiacoinOutput: renter, - MaturityHeight: ms.base.MaturityHeight(), - }) - ms.addSiacoinElement(types.SiacoinElement{ - StateElement: types.StateElement{ID: types.Hash256(types.FileContractID(fce.ID).V2HostOutputID())}, - SiacoinOutput: host, - MaturityHeight: ms.base.MaturityHeight(), - }) + ms.addImmatureSiacoinElement(types.FileContractID(fce.ID).V2RenterOutputID(), renter) + ms.addImmatureSiacoinElement(types.FileContractID(fce.ID).V2HostOutputID(), host) } for i, a := range txn.Attestations { ms.addAttestationElement(types.AttestationElement{ @@ -581,18 +560,10 @@ func (ms *MidState) ApplyBlock(b types.Block, bs V1BlockSupplement) { } bid := b.ID() for i, sco := range b.MinerPayouts { - ms.addSiacoinElement(types.SiacoinElement{ - StateElement: types.StateElement{ID: types.Hash256(bid.MinerOutputID(i))}, - SiacoinOutput: sco, - MaturityHeight: ms.base.MaturityHeight(), - }) + ms.addImmatureSiacoinElement(bid.MinerOutputID(i), sco) } if subsidy := ms.base.FoundationSubsidy(); !subsidy.Value.IsZero() { - ms.addSiacoinElement(types.SiacoinElement{ - StateElement: types.StateElement{ID: types.Hash256(bid.FoundationOutputID())}, - SiacoinOutput: subsidy, - MaturityHeight: ms.base.MaturityHeight(), - }) + ms.addImmatureSiacoinElement(bid.FoundationOutputID(), subsidy) } for _, fce := range bs.ExpiringFileContracts { if ms.isSpent(fce.ID) { @@ -600,11 +571,7 @@ func (ms *MidState) ApplyBlock(b types.Block, bs V1BlockSupplement) { } ms.resolveFileContractElement(fce, false, types.TransactionID(bid)) for i, sco := range fce.FileContract.MissedProofOutputs { - ms.addSiacoinElement(types.SiacoinElement{ - StateElement: types.StateElement{ID: types.Hash256(types.FileContractID(fce.ID).MissedOutputID(i))}, - SiacoinOutput: sco, - MaturityHeight: ms.base.MaturityHeight(), - }) + ms.addImmatureSiacoinElement(types.FileContractID(fce.ID).MissedOutputID(i), sco) } } @@ -660,33 +627,33 @@ func (au ApplyUpdate) UpdateElementProof(e *types.StateElement) { } // ForEachSiacoinElement calls fn on each siacoin element related to au. -func (au ApplyUpdate) ForEachSiacoinElement(fn func(sce types.SiacoinElement, spent bool)) { +func (au ApplyUpdate) ForEachSiacoinElement(fn func(sce types.SiacoinElement, created, spent bool)) { for _, sce := range au.ms.sces { - fn(sce, au.ms.isSpent(sce.ID)) + fn(sce, au.ms.isCreated(sce.ID), au.ms.isSpent(sce.ID)) } } // ForEachSiafundElement calls fn on each siafund element related to au. -func (au ApplyUpdate) ForEachSiafundElement(fn func(sfe types.SiafundElement, spent bool)) { +func (au ApplyUpdate) ForEachSiafundElement(fn func(sfe types.SiafundElement, created, spent bool)) { for _, sfe := range au.ms.sfes { - fn(sfe, au.ms.isSpent(sfe.ID)) + fn(sfe, au.ms.isCreated(sfe.ID), au.ms.isSpent(sfe.ID)) } } // ForEachFileContractElement calls fn on each file contract element related to // au. If the contract was revised, rev is non-nil. -func (au ApplyUpdate) ForEachFileContractElement(fn func(fce types.FileContractElement, rev *types.FileContractElement, resolved, valid bool)) { +func (au ApplyUpdate) ForEachFileContractElement(fn func(fce types.FileContractElement, created bool, rev *types.FileContractElement, resolved, valid bool)) { for _, fce := range au.ms.fces { - fn(fce, au.ms.revs[fce.ID], au.ms.isSpent(fce.ID), au.ms.res[fce.ID]) + fn(fce, au.ms.isCreated(fce.ID), au.ms.revs[fce.ID], au.ms.isSpent(fce.ID), au.ms.res[fce.ID]) } } // ForEachV2FileContractElement calls fn on each V2 file contract element // related to au. If the contract was revised, rev is non-nil. If the contract // was resolved, res is non-nil. -func (au ApplyUpdate) ForEachV2FileContractElement(fn func(fce types.V2FileContractElement, rev *types.V2FileContractElement, res types.V2FileContractResolutionType)) { +func (au ApplyUpdate) ForEachV2FileContractElement(fn func(fce types.V2FileContractElement, created bool, rev *types.V2FileContractElement, res types.V2FileContractResolutionType)) { for _, fce := range au.ms.v2fces { - fn(fce, au.ms.v2revs[fce.ID], au.ms.v2res[fce.ID]) + fn(fce, au.ms.isCreated(fce.ID), au.ms.v2revs[fce.ID], au.ms.v2res[fce.ID]) } } @@ -737,7 +704,7 @@ func ApplyBlock(s State, b types.Block, bs V1BlockSupplement, targetTimestamp ti // compute updated and added elements var updated, added []elementLeaf ms.forEachElementLeaf(func(el elementLeaf) { - if el.MerkleProof == nil { + if ms.isCreated(el.ID) { added = append(added, el) } else { updated = append(updated, el) @@ -762,37 +729,37 @@ func (ru RevertUpdate) UpdateElementProof(e *types.StateElement) { } // ForEachSiacoinElement calls fn on each siacoin element related to ru. -func (ru RevertUpdate) ForEachSiacoinElement(fn func(sce types.SiacoinElement, spent bool)) { +func (ru RevertUpdate) ForEachSiacoinElement(fn func(sce types.SiacoinElement, created, spent bool)) { for i := range ru.ms.sces { sce := ru.ms.sces[len(ru.ms.sces)-i-1] - fn(sce, ru.ms.isSpent(sce.ID)) + fn(sce, ru.ms.isCreated(sce.ID), ru.ms.isSpent(sce.ID)) } } // ForEachSiafundElement calls fn on each siafund element related to ru. -func (ru RevertUpdate) ForEachSiafundElement(fn func(sfe types.SiafundElement, spent bool)) { +func (ru RevertUpdate) ForEachSiafundElement(fn func(sfe types.SiafundElement, created, spent bool)) { for i := range ru.ms.sfes { sfe := ru.ms.sfes[len(ru.ms.sfes)-i-1] - fn(sfe, ru.ms.isSpent(sfe.ID)) + fn(sfe, ru.ms.isCreated(sfe.ID), ru.ms.isSpent(sfe.ID)) } } // ForEachFileContractElement calls fn on each file contract element related to // ru. If the contract was revised, rev is non-nil. -func (ru RevertUpdate) ForEachFileContractElement(fn func(fce types.FileContractElement, rev *types.FileContractElement, resolved, valid bool)) { +func (ru RevertUpdate) ForEachFileContractElement(fn func(fce types.FileContractElement, created bool, rev *types.FileContractElement, resolved, valid bool)) { for i := range ru.ms.fces { fce := ru.ms.fces[len(ru.ms.fces)-i-1] - fn(fce, ru.ms.revs[fce.ID], ru.ms.isSpent(fce.ID), ru.ms.res[fce.ID]) + fn(fce, ru.ms.isCreated(fce.ID), ru.ms.revs[fce.ID], ru.ms.isSpent(fce.ID), ru.ms.res[fce.ID]) } } // ForEachV2FileContractElement calls fn on each V2 file contract element // related to au. If the contract was revised, rev is non-nil. If the contract // was resolved, res is non-nil. -func (ru RevertUpdate) ForEachV2FileContractElement(fn func(fce types.V2FileContractElement, rev *types.V2FileContractElement, res types.V2FileContractResolutionType)) { +func (ru RevertUpdate) ForEachV2FileContractElement(fn func(fce types.V2FileContractElement, created bool, rev *types.V2FileContractElement, res types.V2FileContractResolutionType)) { for i := range ru.ms.v2fces { fce := ru.ms.v2fces[len(ru.ms.fces)-i-1] - fn(fce, ru.ms.v2revs[fce.ID], ru.ms.v2res[fce.ID]) + fn(fce, ru.ms.isCreated(fce.ID), ru.ms.v2revs[fce.ID], ru.ms.v2res[fce.ID]) } } @@ -833,7 +800,7 @@ func RevertBlock(s State, b types.Block, bs V1BlockSupplement) RevertUpdate { var updated, added []elementLeaf ms.forEachElementLeaf(func(el elementLeaf) { el.Spent = false // reverting a block can never cause an element to become spent - if el.MerkleProof != nil { + if !ms.isCreated(el.ID) { updated = append(updated, el) } else { added = append(added, el) diff --git a/consensus/update_test.go b/consensus/update_test.go index 8eb1cef0..9673f5af 100644 --- a/consensus/update_test.go +++ b/consensus/update_test.go @@ -47,18 +47,18 @@ func TestApplyBlock(t *testing.T) { appendSig(types.Hash256(txn.FileContractRevisions[i].ParentID)) } } - addBlock := func(b types.Block) (au ApplyUpdate, err error) { - bs := db.supplementTipBlock(b) - findBlockNonce(cs, &b) - if err = ValidateBlock(cs, b, bs); err != nil { + addBlock := func(b *types.Block) (au ApplyUpdate, err error) { + bs := db.supplementTipBlock(*b) + findBlockNonce(cs, b) + if err = ValidateBlock(cs, *b, bs); err != nil { return } - cs, au = ApplyBlock(cs, b, bs, db.ancestorTimestamp(b.ParentID)) + cs, au = ApplyBlock(cs, *b, bs, db.ancestorTimestamp(b.ParentID)) db.applyBlock(au) return } checkUpdateElements := func(au ApplyUpdate, addedSCEs, spentSCEs []types.SiacoinElement, addedSFEs, spentSFEs []types.SiafundElement) { - au.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { + au.ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool) { sces := &addedSCEs if spent { sces = &spentSCEs @@ -72,7 +72,7 @@ func TestApplyBlock(t *testing.T) { } *sces = (*sces)[1:] }) - au.ForEachSiafundElement(func(sfe types.SiafundElement, spent bool) { + au.ForEachSiafundElement(func(sfe types.SiafundElement, created, spent bool) { sfes := &addedSFEs if spent { sfes = &spentSFEs @@ -91,7 +91,7 @@ func TestApplyBlock(t *testing.T) { } } checkRevertElements := func(ru RevertUpdate, addedSCEs, spentSCEs []types.SiacoinElement, addedSFEs, spentSFEs []types.SiafundElement) { - ru.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { + ru.ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool) { sces := &addedSCEs if spent { sces = &spentSCEs @@ -105,7 +105,7 @@ func TestApplyBlock(t *testing.T) { } *sces = (*sces)[:len(*sces)-1] }) - ru.ForEachSiafundElement(func(sfe types.SiafundElement, spent bool) { + ru.ForEachSiafundElement(func(sfe types.SiafundElement, created, spent bool) { sfes := &addedSFEs if spent { sfes = &spentSFEs @@ -136,7 +136,7 @@ func TestApplyBlock(t *testing.T) { spentSCEs := []types.SiacoinElement{} addedSFEs := []types.SiafundElement{} spentSFEs := []types.SiafundElement{} - if au, err := addBlock(b1); err != nil { + if au, err := addBlock(&b1); err != nil { t.Fatal(err) } else { checkUpdateElements(au, addedSCEs, spentSCEs, addedSFEs, spentSFEs) @@ -188,7 +188,7 @@ func TestApplyBlock(t *testing.T) { prev := cs bs := db.supplementTipBlock(b2) - if au, err := addBlock(b2); err != nil { + if au, err := addBlock(&b2); err != nil { t.Fatal(err) } else { checkUpdateElements(au, addedSCEs, spentSCEs, addedSFEs, spentSFEs) diff --git a/consensus/validation.go b/consensus/validation.go index 20c6c60b..5f229652 100644 --- a/consensus/validation.go +++ b/consensus/validation.go @@ -584,8 +584,8 @@ func validateV2Siacoins(ms *MidState, txn types.V2Transaction) error { spent[sci.Parent.ID] = i // check accumulator - if sci.Parent.LeafIndex == types.EphemeralLeafIndex { - if _, ok := ms.ephemeral[sci.Parent.ID]; !ok { + if sci.Parent.LeafIndex == types.UnassignedLeafIndex { + if !ms.isCreated(sci.Parent.ID) { return fmt.Errorf("siacoin input %v spends nonexistent ephemeral output %v", i, sci.Parent.ID) } } else if !ms.base.Elements.containsUnspentSiacoinElement(sci.Parent) { @@ -645,8 +645,8 @@ func validateV2Siafunds(ms *MidState, txn types.V2Transaction) error { spent[sfi.Parent.ID] = i // check accumulator - if sfi.Parent.LeafIndex == types.EphemeralLeafIndex { - if _, ok := ms.ephemeral[sfi.Parent.ID]; !ok { + if sfi.Parent.LeafIndex == types.UnassignedLeafIndex { + if !ms.isCreated(sfi.Parent.ID) { return fmt.Errorf("siafund input %v spends nonexistent ephemeral output %v", i, sfi.Parent.ID) } } else if !ms.base.Elements.containsUnspentSiafundElement(sfi.Parent) { diff --git a/consensus/validation_test.go b/consensus/validation_test.go index c6de507c..4f7c1790 100644 --- a/consensus/validation_test.go +++ b/consensus/validation_test.go @@ -46,25 +46,28 @@ type consensusDB struct { } func (db *consensusDB) applyBlock(au ApplyUpdate) { - au.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { + au.ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool) { if spent { delete(db.sces, types.SiacoinOutputID(sce.ID)) } else { db.sces[types.SiacoinOutputID(sce.ID)] = sce } }) - au.ForEachSiafundElement(func(sfe types.SiafundElement, spent bool) { + au.ForEachSiafundElement(func(sfe types.SiafundElement, created, spent bool) { if spent { delete(db.sfes, types.SiafundOutputID(sfe.ID)) } else { db.sfes[types.SiafundOutputID(sfe.ID)] = sfe } }) - au.ForEachFileContractElement(func(fce types.FileContractElement, rev *types.FileContractElement, resolved, valid bool) { + au.ForEachFileContractElement(func(fce types.FileContractElement, created bool, rev *types.FileContractElement, resolved, valid bool) { if resolved { delete(db.fces, types.FileContractID(fce.ID)) } else { db.fces[types.FileContractID(fce.ID)] = fce + if rev != nil { + db.fces[types.FileContractID(rev.ID)] = *rev + } } }) } @@ -787,15 +790,15 @@ func TestValidateV2Block(t *testing.T) { _, au := ApplyBlock(n.GenesisState(), genesisBlock, V1BlockSupplement{}, time.Time{}) var sces []types.SiacoinElement - au.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { + au.ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool) { sces = append(sces, sce) }) var sfes []types.SiafundElement - au.ForEachSiafundElement(func(sfe types.SiafundElement, spent bool) { + au.ForEachSiafundElement(func(sfe types.SiafundElement, created, spent bool) { sfes = append(sfes, sfe) }) var fces []types.V2FileContractElement - au.ForEachV2FileContractElement(func(fce types.V2FileContractElement, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { + au.ForEachV2FileContractElement(func(fce types.V2FileContractElement, created bool, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { fces = append(fces, fce) }) var cies []types.ChainIndexElement @@ -1177,15 +1180,15 @@ func TestValidateV2Block(t *testing.T) { updateProofs(testAU, sces, sfes, fces, cies) var testSces []types.SiacoinElement - testAU.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { + testAU.ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool) { testSces = append(testSces, sce) }) var testSfes []types.SiafundElement - testAU.ForEachSiafundElement(func(sfe types.SiafundElement, spent bool) { + testAU.ForEachSiafundElement(func(sfe types.SiafundElement, created, spent bool) { testSfes = append(testSfes, sfe) }) var testFces []types.V2FileContractElement - testAU.ForEachV2FileContractElement(func(fce types.V2FileContractElement, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { + testAU.ForEachV2FileContractElement(func(fce types.V2FileContractElement, created bool, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { testFces = append(testFces, fce) }) cies = append(cies, testAU.ChainIndexElement()) @@ -1462,7 +1465,7 @@ func TestV2ImmatureSiacoinOutput(t *testing.T) { var cau ApplyUpdate cs, cau = ApplyBlock(cs, b, db.supplementTipBlock(b), db.ancestorTimestamp(b.ParentID)) - cau.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { + cau.ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool) { if spent { delete(utxos, types.SiacoinOutputID(sce.ID)) } else if sce.SiacoinOutput.Address == addr { diff --git a/types/multiproof.go b/types/multiproof.go index d7237406..3e1ff3c5 100644 --- a/types/multiproof.go +++ b/types/multiproof.go @@ -48,7 +48,7 @@ func splitLeaves(ls []elementLeaf, mid uint64) (left, right []elementLeaf) { func forEachElementLeaf(txns []V2Transaction, fn func(l elementLeaf)) { visit := func(l elementLeaf) { - if l.LeafIndex != EphemeralLeafIndex { + if l.LeafIndex != UnassignedLeafIndex { fn(l) } } diff --git a/types/multiproof_test.go b/types/multiproof_test.go index f006502f..207b89a9 100644 --- a/types/multiproof_test.go +++ b/types/multiproof_test.go @@ -37,11 +37,15 @@ func multiproofTxns(numTxns int, numElems int) []types.V2Transaction { // apply the block and extract the created elements cs, cau := consensus.ApplyBlock(cs, b, consensus.V1BlockSupplement{}, time.Time{}) var sces []types.SiacoinElement - cau.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { sces = append(sces, sce) }) + cau.ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool) { + sces = append(sces, sce) + }) var sfes []types.SiafundElement - cau.ForEachSiafundElement(func(sfe types.SiafundElement, spent bool) { sfes = append(sfes, sfe) }) + cau.ForEachSiafundElement(func(sfe types.SiafundElement, created, spent bool) { + sfes = append(sfes, sfe) + }) var fces []types.V2FileContractElement - 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) { fces = append(fces, fce) }) // select randomly @@ -84,7 +88,7 @@ func multiproofTxns(numTxns int, numElems int) []types.V2Transaction { for i := range txns { for j := range txns[i].SiacoinInputs { if (n+1)%5 == 0 { - txns[i].SiacoinInputs[j].Parent.LeafIndex = types.EphemeralLeafIndex + txns[i].SiacoinInputs[j].Parent.LeafIndex = types.UnassignedLeafIndex txns[i].SiacoinInputs[j].Parent.MerkleProof = nil } n++ diff --git a/types/types.go b/types/types.go index 22a80028..87ce085a 100644 --- a/types/types.go +++ b/types/types.go @@ -30,11 +30,15 @@ const ( // a FileContract. HostContractIndex = 1 - // EphemeralLeafIndex is used as the LeafIndex of StateElements that are created - // and spent within the same block. Such elements do not require a proof of - // existence. They are, however, assigned a proper index and are incorporated - // into the state accumulator when the block is processed. - EphemeralLeafIndex = math.MaxUint64 + // UnassignedLeafIndex is a sentinel value used as the LeafIndex of + // StateElements that have not been added to the accumulator yet. This is + // necessary for constructing blocks sets where later transactions reference + // elements created in earlier transactions. + // + // Most clients do not need to reference this value directly, and should + // instead use the EphemeralSiacoinElement and EphemeralSiafundElement + // functions to construct dependent transaction sets. + UnassignedLeafIndex = 10101010101010101010 ) // Various specifiers. @@ -687,7 +691,7 @@ func (txn *V2Transaction) EphemeralSiacoinOutput(i int) SiacoinElement { return SiacoinElement{ StateElement: StateElement{ ID: Hash256(txn.SiacoinOutputID(txn.ID(), i)), - LeafIndex: EphemeralLeafIndex, + LeafIndex: UnassignedLeafIndex, }, SiacoinOutput: txn.SiacoinOutputs[i], } @@ -699,7 +703,7 @@ func (txn *V2Transaction) EphemeralSiafundOutput(i int) SiafundElement { return SiafundElement{ StateElement: StateElement{ ID: Hash256(txn.SiafundOutputID(txn.ID(), i)), - LeafIndex: EphemeralLeafIndex, + LeafIndex: UnassignedLeafIndex, }, SiafundOutput: txn.SiafundOutputs[i], }