Skip to content

Commit 2a37938

Browse files
committed
consensus: Add (ElementAccumulator).ValidateTransactionElements
1 parent 62d750c commit 2a37938

File tree

2 files changed

+229
-0
lines changed

2 files changed

+229
-0
lines changed

consensus/merkle.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,34 @@ func (acc *ElementAccumulator) containsResolvedV2FileContractElement(fce types.V
219219
return acc.containsLeaf(v2FileContractLeaf(&fce, nil, true))
220220
}
221221

222+
// ValidateTransactionElements validates the Merkle proofs of all elements in the
223+
// supplied transaction.
224+
func (acc *ElementAccumulator) ValidateTransactionElements(txn types.V2Transaction) (err error) {
225+
check := func(typ string, l elementLeaf) {
226+
if err == nil && l.LeafIndex != types.UnassignedLeafIndex {
227+
if !acc.containsLeaf(l) {
228+
err = errors.New(typ + " parent has invalid Merkle proof")
229+
}
230+
}
231+
}
232+
for i := range txn.SiacoinInputs {
233+
check("siacoin input", siacoinLeaf(&txn.SiacoinInputs[i].Parent, false))
234+
}
235+
for i := range txn.SiafundInputs {
236+
check("siafund input", siafundLeaf(&txn.SiafundInputs[i].Parent, false))
237+
}
238+
for i := range txn.FileContractRevisions {
239+
check("file contract revision", v2FileContractLeaf(&txn.FileContractRevisions[i].Parent, nil, false))
240+
}
241+
for i := range txn.FileContractResolutions {
242+
check("file contract resolution", v2FileContractLeaf(&txn.FileContractResolutions[i].Parent, nil, false))
243+
if r, ok := txn.FileContractResolutions[i].Resolution.(*types.V2StorageProof); ok {
244+
check("storage proof", chainIndexLeaf(&r.ProofIndex))
245+
}
246+
}
247+
return
248+
}
249+
222250
// addLeaves adds the supplied leaves to the accumulator, filling in their
223251
// Merkle proofs and returning the new node hashes that extend each existing
224252
// tree.

consensus/validation_test.go

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2180,3 +2180,204 @@ func TestV2RenewalResolution(t *testing.T) {
21802180
})
21812181
}
21822182
}
2183+
2184+
func TestValidateTransactionElements(t *testing.T) {
2185+
n, genesisBlock := testnet()
2186+
n.InitialTarget = types.BlockID{0xFF}
2187+
n.HardforkV2.AllowHeight = 0
2188+
n.HardforkV2.RequireHeight = 0
2189+
2190+
giftPrivateKey := types.GeneratePrivateKey()
2191+
giftPublicKey := giftPrivateKey.PublicKey()
2192+
giftPolicy := types.PolicyPublicKey(giftPublicKey)
2193+
giftAddress := types.StandardAddress(giftPublicKey)
2194+
2195+
renterPrivateKey := types.GeneratePrivateKey()
2196+
renterPublicKey := renterPrivateKey.PublicKey()
2197+
hostPrivateKey := types.GeneratePrivateKey()
2198+
hostPublicKey := hostPrivateKey.PublicKey()
2199+
2200+
giftAmountSC := types.Siacoins(100)
2201+
giftAmountSF := uint64(100)
2202+
v1GiftFC := prepareContractFormation(renterPublicKey, hostPublicKey, types.Siacoins(1), types.Siacoins(1), 100, 100, types.VoidAddress)
2203+
v1GiftFC.Filesize = 65
2204+
v1GiftFC.FileMerkleRoot = blake2b.SumPair((State{}).StorageProofLeafHash([]byte{1}), (State{}).StorageProofLeafHash([]byte{2}))
2205+
v2GiftFC := types.V2FileContract{
2206+
Capacity: v1GiftFC.Filesize,
2207+
Filesize: v1GiftFC.Filesize,
2208+
FileMerkleRoot: v1GiftFC.FileMerkleRoot,
2209+
ProofHeight: 20,
2210+
ExpirationHeight: 30,
2211+
RenterOutput: v1GiftFC.ValidProofOutputs[0],
2212+
HostOutput: v1GiftFC.ValidProofOutputs[1],
2213+
MissedHostValue: v1GiftFC.MissedProofOutputs[1].Value,
2214+
TotalCollateral: v1GiftFC.Payout,
2215+
RenterPublicKey: renterPublicKey,
2216+
HostPublicKey: hostPublicKey,
2217+
}
2218+
contractCost := v2GiftFC.RenterOutput.Value.Add(v2GiftFC.HostOutput.Value).Add(n.GenesisState().V2FileContractTax(v2GiftFC))
2219+
2220+
giftTxn := types.V2Transaction{
2221+
SiacoinOutputs: []types.SiacoinOutput{
2222+
{Address: giftAddress, Value: giftAmountSC},
2223+
{Address: giftAddress, Value: contractCost},
2224+
},
2225+
SiafundOutputs: []types.SiafundOutput{
2226+
{Address: giftAddress, Value: giftAmountSF},
2227+
},
2228+
FileContracts: []types.V2FileContract{v2GiftFC},
2229+
}
2230+
2231+
genesisBlock.Transactions = nil
2232+
genesisBlock.V2 = &types.V2BlockData{
2233+
Transactions: []types.V2Transaction{giftTxn},
2234+
}
2235+
2236+
_, au := ApplyBlock(n.GenesisState(), genesisBlock, V1BlockSupplement{}, time.Time{})
2237+
sces := make([]types.SiacoinElement, len(au.SiacoinElementDiffs()))
2238+
for i := range sces {
2239+
sces[i] = au.SiacoinElementDiffs()[i].SiacoinElement.Copy()
2240+
}
2241+
sfes := make([]types.SiafundElement, len(au.SiafundElementDiffs()))
2242+
for i := range sfes {
2243+
sfes[i] = au.SiafundElementDiffs()[i].SiafundElement.Copy()
2244+
}
2245+
fces := make([]types.V2FileContractElement, len(au.V2FileContractElementDiffs()))
2246+
for i := range fces {
2247+
fces[i] = au.V2FileContractElementDiffs()[i].V2FileContractElement.Copy()
2248+
}
2249+
cies := []types.ChainIndexElement{au.ChainIndexElement()}
2250+
2251+
db, cs := newConsensusDB(n, genesisBlock)
2252+
2253+
fc := v2GiftFC
2254+
fc.TotalCollateral = fc.HostOutput.Value
2255+
2256+
rev1 := v2GiftFC
2257+
rev1.RevisionNumber++
2258+
minerFee := types.Siacoins(1)
2259+
b := types.Block{
2260+
ParentID: genesisBlock.ID(),
2261+
Timestamp: types.CurrentTimestamp(),
2262+
V2: &types.V2BlockData{
2263+
Height: 1,
2264+
Transactions: []types.V2Transaction{{
2265+
SiacoinInputs: []types.V2SiacoinInput{{
2266+
Parent: sces[0].Copy(),
2267+
SatisfiedPolicy: types.SatisfiedPolicy{Policy: giftPolicy},
2268+
}},
2269+
SiafundInputs: []types.V2SiafundInput{{
2270+
Parent: sfes[0].Copy(),
2271+
ClaimAddress: types.VoidAddress,
2272+
SatisfiedPolicy: types.SatisfiedPolicy{Policy: giftPolicy},
2273+
}},
2274+
SiacoinOutputs: []types.SiacoinOutput{
2275+
{Value: giftAmountSC.Sub(minerFee).Sub(contractCost), Address: giftAddress},
2276+
},
2277+
SiafundOutputs: []types.SiafundOutput{
2278+
{Value: giftAmountSF / 2, Address: giftAddress},
2279+
{Value: giftAmountSF / 2, Address: types.VoidAddress},
2280+
},
2281+
FileContracts: []types.V2FileContract{fc},
2282+
FileContractRevisions: []types.V2FileContractRevision{
2283+
{Parent: au.V2FileContractElementDiffs()[0].V2FileContractElement.Copy(), Revision: rev1},
2284+
},
2285+
MinerFee: minerFee,
2286+
}},
2287+
},
2288+
MinerPayouts: []types.SiacoinOutput{{
2289+
Address: types.VoidAddress,
2290+
Value: cs.BlockReward().Add(minerFee),
2291+
}},
2292+
}
2293+
2294+
// validate elements
2295+
txn := b.V2.Transactions[0]
2296+
if err := cs.Elements.ValidateTransactionElements(txn); err != nil {
2297+
t.Fatal(err)
2298+
}
2299+
// validate that corrupting an element results in an error
2300+
for _, fn := range []func(){
2301+
func() { txn.SiacoinInputs[0].Parent.ID[0] ^= 1 },
2302+
func() { txn.SiafundInputs[0].Parent.StateElement.LeafIndex ^= 1 },
2303+
func() { txn.FileContractRevisions[0].Parent.StateElement.MerkleProof[0][0] ^= 1 },
2304+
} {
2305+
fn()
2306+
if err := cs.Elements.ValidateTransactionElements(txn); err == nil || !strings.Contains(err.Error(), "invalid Merkle proof") {
2307+
t.Fatal("expected invalid Merkle proof error, got", err)
2308+
}
2309+
fn()
2310+
}
2311+
2312+
cs, testAU := ApplyBlock(cs, b, db.supplementTipBlock(b), time.Now())
2313+
db.applyBlock(testAU)
2314+
updateProofs(testAU, sces, sfes, fces, cies)
2315+
2316+
testSces := make([]types.SiacoinElement, len(testAU.SiacoinElementDiffs()))
2317+
for i := range testSces {
2318+
testSces[i] = testAU.SiacoinElementDiffs()[i].SiacoinElement.Copy()
2319+
}
2320+
testSfes := make([]types.SiafundElement, len(testAU.SiafundElementDiffs()))
2321+
for i := range testSfes {
2322+
testSfes[i] = testAU.SiafundElementDiffs()[i].SiafundElement.Copy()
2323+
}
2324+
testFces := make([]types.V2FileContractElement, len(testAU.V2FileContractElementDiffs()))
2325+
for i := range testFces {
2326+
testFces[i] = testAU.V2FileContractElementDiffs()[i].V2FileContractElement.Copy()
2327+
}
2328+
cies = append(cies, testAU.ChainIndexElement())
2329+
2330+
// mine empty blocks
2331+
blockID := b.ID()
2332+
for i := uint64(0); i < v2GiftFC.ProofHeight; i++ {
2333+
b := types.Block{
2334+
ParentID: blockID,
2335+
Timestamp: types.CurrentTimestamp(),
2336+
V2: &types.V2BlockData{
2337+
Height: cs.Index.Height + 1,
2338+
},
2339+
MinerPayouts: []types.SiacoinOutput{{
2340+
Address: types.VoidAddress,
2341+
Value: cs.BlockReward(),
2342+
}},
2343+
}
2344+
b.V2.Commitment = cs.Commitment(b.MinerPayouts[0].Address, b.Transactions, b.V2Transactions())
2345+
2346+
findBlockNonce(cs, &b)
2347+
if err := ValidateBlock(cs, b, db.supplementTipBlock(b)); err != nil {
2348+
t.Fatal(err)
2349+
}
2350+
cs, au = ApplyBlock(cs, b, db.supplementTipBlock(b), time.Now())
2351+
db.applyBlock(au)
2352+
updateProofs(au, sces, sfes, fces, cies)
2353+
updateProofs(au, testSces, testSfes, testFces, nil)
2354+
cies = append(cies, au.ChainIndexElement())
2355+
2356+
blockID = b.ID()
2357+
}
2358+
2359+
// construct a transaction that resolves the file contract
2360+
txn = types.V2Transaction{
2361+
FileContractResolutions: []types.V2FileContractResolution{{
2362+
Parent: testFces[0].Copy(),
2363+
Resolution: &types.V2StorageProof{
2364+
ProofIndex: cies[len(cies)-2].Copy(),
2365+
Leaf: [64]byte{1},
2366+
Proof: []types.Hash256{cs.StorageProofLeafHash([]byte{2})},
2367+
},
2368+
}},
2369+
}
2370+
if err := cs.Elements.ValidateTransactionElements(txn); err != nil {
2371+
t.Fatal(err)
2372+
}
2373+
for _, fn := range []func(){
2374+
func() { txn.FileContractResolutions[0].Resolution.(*types.V2StorageProof).ProofIndex.ID[0] ^= 1 },
2375+
func() { txn.FileContractResolutions[0].Parent.StateElement.MerkleProof[0][0] ^= 1 },
2376+
} {
2377+
fn()
2378+
if err := cs.Elements.ValidateTransactionElements(txn); err == nil || !strings.Contains(err.Error(), "invalid Merkle proof") {
2379+
t.Fatal("expected invalid Merkle proof error, got", err)
2380+
}
2381+
fn()
2382+
}
2383+
}

0 commit comments

Comments
 (0)