Skip to content

Commit

Permalink
consensus: Insert final renewal revision into accumulator
Browse files Browse the repository at this point in the history
  • Loading branch information
lukechampine committed Nov 30, 2024
1 parent d3d9f55 commit cfd6d6b
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 37 deletions.
12 changes: 12 additions & 0 deletions consensus/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,18 @@ func (ms *MidState) forEachAppliedElement(fn func(elementLeaf)) {
if r, ok := ms.v2revs[ms.v2fces[i].ID]; ok {
fn(v2FileContractLeaf(r, ms.isSpent(ms.v2fces[i].ID)))
} else {
// Renewals require special handling: we can't simply overwrite the
// parent with FinalRevision, since ForEachV2FileContractElement
// needs to observe the original contract.
if res, ok := ms.v2res[ms.v2fces[i].ID]; ok {
if r, ok := res.(*types.V2FileContractRenewal); ok {
prev := ms.v2fces[i].V2FileContract
ms.v2fces[i].V2FileContract = r.FinalRevision
fn(v2FileContractLeaf(&ms.v2fces[i], ms.isSpent(ms.v2fces[i].ID)))
ms.v2fces[i].V2FileContract = prev
continue
}
}
fn(v2FileContractLeaf(&ms.v2fces[i], ms.isSpent(ms.v2fces[i].ID)))
}
// NOTE: Although it is an element, we do not process the ProofIndex
Expand Down
122 changes: 101 additions & 21 deletions consensus/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ func checkApplyUpdate(t *testing.T, cs State, au ApplyUpdate) {
if r, ok := ms.v2revs[fce.ID]; ok {
leaf = v2FileContractLeaf(r, ms.isSpent(fce.ID))
}
if r, ok := ms.v2res[fce.ID]; ok {
if r, ok := r.(*types.V2FileContractRenewal); ok {
fce.V2FileContract = r.FinalRevision
leaf = v2FileContractLeaf(&fce, ms.isSpent(fce.ID))
}
}

if !cs.Elements.containsLeaf(leaf) {
t.Fatal("consensus: v2 file contract element leaf not found in accumulator after apply")
Expand Down Expand Up @@ -1179,7 +1185,7 @@ func TestApplyRevertBlockV2(t *testing.T) {
fcr := v2FC
fcr.RevisionNumber++
fcr.Filesize = 65
fcr.ProofHeight = 3
fcr.ProofHeight = 5
fcr.ExpirationHeight = 20
fcr.FileMerkleRoot = blake2b.SumPair((State{}).StorageProofLeafHash([]byte{1}), (State{}).StorageProofLeafHash([]byte{2}))

Expand Down Expand Up @@ -1208,12 +1214,10 @@ func TestApplyRevertBlockV2(t *testing.T) {
addedSFEs = nil
spentSFEs = nil

var cie types.ChainIndexElement
prev = cs
if au, err := addBlock(&b4); err != nil {
t.Fatal(err)
} else {
cie = au.ChainIndexElement()
checkApplyUpdate(t, cs, au)
checkUpdateElements(au, addedSCEs, spentSCEs, addedSFEs, spentSFEs)
}
Expand All @@ -1233,66 +1237,142 @@ func TestApplyRevertBlockV2(t *testing.T) {
checkUpdateElements(au, addedSCEs, spentSCEs, addedSFEs, spentSFEs)
}

// block with storage proof
// block with renewal
fce := db.v2fces[txnB3.V2FileContractID(txnB3.ID(), 0)]
finalRevision := fce.V2FileContract
finalRevision.RevisionNumber = types.MaxRevisionNumber
finalRevision.RenterSignature = types.Signature{}
finalRevision.HostSignature = types.Signature{}
newContract := types.V2FileContract{
ProofHeight: 10,
ExpirationHeight: 20,
RenterOutput: types.SiacoinOutput{Value: types.Siacoins(1).Div64(2)},
HostOutput: types.SiacoinOutput{Value: types.Siacoins(1).Div64(2)},
}
renterRollover := cs.V2FileContractTax(newContract)
hostRollover := types.Siacoins(1)
txnB5 := types.V2Transaction{
FileContractResolutions: []types.V2FileContractResolution{{
Parent: fce,
Resolution: &types.V2StorageProof{
ProofIndex: cie,
Leaf: [64]byte{1},
Proof: []types.Hash256{cs.StorageProofLeafHash([]byte{2})},
Resolution: &types.V2FileContractRenewal{
FinalRevision: finalRevision,
NewContract: newContract,
RenterRollover: renterRollover,
HostRollover: hostRollover,
},
}},
}
signTxn(cs, &txnB5)
b5 := types.Block{
ParentID: b4.ID(),
Timestamp: types.CurrentTimestamp(),
MinerPayouts: []types.SiacoinOutput{{Address: types.VoidAddress, Value: cs.BlockReward()}},
MinerPayouts: []types.SiacoinOutput{{Address: types.VoidAddress, Value: cs.BlockReward().Add(txnB5.MinerFee)}},
V2: &types.V2BlockData{
Height: 4,
Transactions: []types.V2Transaction{txnB5},
},
}

addedSCEs = []types.SiacoinElement{
{SiacoinOutput: types.SiacoinOutput{Value: finalRevision.RenterOutput.Value.Sub(renterRollover)}, MaturityHeight: cs.MaturityHeight()},
{SiacoinOutput: types.SiacoinOutput{Value: finalRevision.HostOutput.Value.Sub(hostRollover)}, MaturityHeight: cs.MaturityHeight()},
{SiacoinOutput: b5.MinerPayouts[0], MaturityHeight: cs.MaturityHeight()},
}
spentSCEs = nil
addedSFEs = nil
spentSFEs = nil

// add block with renewal
prev = cs
if au, err := addBlock(&b5); err != nil {
t.Fatal(err)
} else {
checkApplyUpdate(t, cs, au)
checkUpdateElements(au, addedSCEs, spentSCEs, addedSFEs, spentSFEs)

finalFCE := fce
finalFCE.StateElement.MerkleProof = append([]types.Hash256(nil), finalFCE.StateElement.MerkleProof...)
finalFCE.V2FileContract = finalRevision
au.UpdateElementProof(&finalFCE.StateElement)
if !cs.Elements.containsResolvedV2FileContractElement(finalFCE) {
t.Fatal("accumulator should contain final revision of contract")
}
}

// revert block with renewal
ru = RevertBlock(prev, b5, V1BlockSupplement{})
cs = prev
checkRevertUpdate(t, cs, ru)
checkRevertElements(ru, addedSCEs, spentSCEs, addedSFEs, spentSFEs)
db.revertBlock(ru)

// mine until ProofHeight
var cie types.ChainIndexElement
for cs.Index.Height < fce.V2FileContract.ProofHeight {
b := types.Block{
ParentID: cs.Index.ID,
Timestamp: types.CurrentTimestamp(),
MinerPayouts: []types.SiacoinOutput{{Address: types.VoidAddress, Value: cs.BlockReward()}},
}
if au, err := addBlock(&b); err != nil {
t.Fatal(err)
} else {
cie = au.ChainIndexElement()
checkApplyUpdate(t, cs, au)
}
}

// block with storage proof
txnB6 := types.V2Transaction{
FileContractResolutions: []types.V2FileContractResolution{{
Parent: fce,
Resolution: &types.V2StorageProof{
ProofIndex: cie,
Leaf: [64]byte{1},
Proof: []types.Hash256{cs.StorageProofLeafHash([]byte{2})},
},
}},
}
if cs.StorageProofLeafIndex(fce.V2FileContract.Filesize, cie.ChainIndex.ID, types.FileContractID(fce.ID)) == 1 {
b5.V2.Transactions[0].FileContractResolutions[0].Resolution = &types.V2StorageProof{
txnB6.FileContractResolutions[0].Resolution = &types.V2StorageProof{
ProofIndex: cie,
Leaf: [64]byte{2},
Proof: []types.Hash256{cs.StorageProofLeafHash([]byte{1})},
}
}
signTxn(cs, &txnB5)
b6 := types.Block{
ParentID: cs.Index.ID,
Timestamp: types.CurrentTimestamp(),
MinerPayouts: []types.SiacoinOutput{{Address: types.VoidAddress, Value: cs.BlockReward()}},
V2: &types.V2BlockData{
Height: cs.Index.Height + 1,
Transactions: []types.V2Transaction{txnB6},
},
}

addedSCEs = []types.SiacoinElement{
{SiacoinOutput: txnB3.FileContracts[0].RenterOutput, MaturityHeight: cs.MaturityHeight()},
{SiacoinOutput: txnB3.FileContracts[0].HostOutput, MaturityHeight: cs.MaturityHeight()},
{SiacoinOutput: b5.MinerPayouts[0], MaturityHeight: cs.MaturityHeight()},
{SiacoinOutput: b6.MinerPayouts[0], MaturityHeight: cs.MaturityHeight()},
}
spentSCEs = nil
addedSFEs = nil
spentSFEs = nil

// add block with storage proof
prev = cs
if au, err := addBlock(&b5); err != nil {
if au, err := addBlock(&b6); err != nil {
t.Fatal(err)
} else {
checkApplyUpdate(t, cs, au)
checkUpdateElements(au, addedSCEs, spentSCEs, addedSFEs, spentSFEs)
}

// revert block with storage proof
ru = RevertBlock(prev, b5, V1BlockSupplement{})
ru = RevertBlock(prev, b6, V1BlockSupplement{})
cs = prev
checkRevertUpdate(t, cs, ru)
checkRevertElements(ru, addedSCEs, spentSCEs, addedSFEs, spentSFEs)
db.revertBlock(ru)

// readd block with storage proof
if au, err := addBlock(&b5); err != nil {
t.Fatal(err)
} else {
checkApplyUpdate(t, cs, au)
checkUpdateElements(au, addedSCEs, spentSCEs, addedSFEs, spentSFEs)
}
}
2 changes: 1 addition & 1 deletion consensus/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ func validateV2FileContracts(ms *MidState, txn types.V2Transaction) error {
} else if !ms.base.Elements.containsChainIndex(sp.ProofIndex) {
return fmt.Errorf("file contract storage proof %v has invalid history proof", i)
}
leafIndex := ms.base.StorageProofLeafIndex(fc.Filesize, sp.ProofIndex.ChainIndex.ID, types.FileContractID(fcr.Parent.ID))
leafIndex := ms.base.StorageProofLeafIndex(fc.Filesize, sp.ProofIndex.ChainIndex.ID, fcr.Parent.ID)
if storageProofRoot(ms.base.StorageProofLeafHash(sp.Leaf[:]), leafIndex, fc.Filesize, sp.Proof) != fc.FileMerkleRoot {
return fmt.Errorf("file contract storage proof %v has root that does not match contract Merkle root", i)
}
Expand Down
24 changes: 9 additions & 15 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,25 +516,19 @@ type V2FileContractRevision struct {
}

// A V2FileContractResolution closes a v2 file contract's payment channel. There
// are four ways a contract can be resolved:
// are three ways a contract can be resolved:
//
// 1) The renter can finalize the contract's current state, preventing further
// revisions and immediately creating its outputs.
//
// 2) The renter and host can jointly renew the contract. The old contract is
// 1) The renter and host can jointly renew the contract. The old contract is
// finalized, and a portion of its funds are "rolled over" into a new contract.
// Renewals must be submitted prior to the contract's ProofHeight.
//
// 3) The host can submit a storage proof, asserting that it has faithfully
// stored the contract data for the agreed-upon duration. Typically, a storage
// proof is only required if the renter is unable or unwilling to sign a
// renewal. A storage proof can only be submitted after the contract's
// ProofHeight; this allows the renter (or host) to broadcast the
// latest contract revision prior to the proof.
// 2) If the renter is unwilling or unable to sign a renewal, the host can
// submit a storage proof, asserting that it has faithfully stored the contract
// data for the agreed-upon duration. Storage proofs must be submitted after the
// contract's ProofHeight and prior to its ExpirationHeight.
//
// 4) Lastly, anyone can submit a contract expiration. An expiration can only
// be submitted after the contract's ExpirationHeight; this gives the host a
// reasonable window of time after the ProofHeight in which to submit a storage
// proof.
// 3) Lastly, anyone can submit a contract expiration. An expiration can only be
// submitted after the contract's ExpirationHeight.
//
// Once a contract has been resolved, it cannot be altered or resolved again.
// When a contract is resolved, its RenterOutput and HostOutput are created
Expand Down

0 comments on commit cfd6d6b

Please sign in to comment.