diff --git a/internal/merkle/builder.go b/internal/merkle/builder.go index e517f0fe0..ce11afe48 100644 --- a/internal/merkle/builder.go +++ b/internal/merkle/builder.go @@ -9,6 +9,7 @@ import ( "math/big" "slices" + "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) @@ -120,15 +121,15 @@ func (inner *InnerNode) Valid() bool { return (isPair || isIterated) && !(isPair && isIterated) // xor } -func (inner *InnerNode) Children() (*Tree, *Tree) { +func (inner *InnerNode) Children() (*Tree, *Tree, error) { if !inner.Valid() { - panic(fmt.Sprintf("invalid InnerNode state: %v\n", inner)) + return nil, nil, fmt.Errorf("invalid InnerNode state: %v\n", inner) } if inner.Child != nil { - return inner.Child, inner.Child + return inner.Child, inner.Child, nil } else { - return inner.LHS, inner.RHS + return inner.LHS, inner.RHS, nil } } @@ -144,33 +145,42 @@ func (tree *Tree) GetRootHash() common.Hash { return tree.RootHash } -func (tree *Tree) FindChildByHash(hash common.Hash) *Tree { +func (tree *Tree) FindChildByHash(hash common.Hash) (*Tree, error) { if tree.RootHash == hash { - return tree + return tree, nil } if inner := tree.Subtrees; inner != nil { if !inner.Valid() { - panic(fmt.Sprintf("invalid InnerNode state: %v\n", inner)) + return nil, fmt.Errorf("invalid InnerNode state: %v\n", inner) } if inner.Child != nil { - child := inner.Child.FindChildByHash(hash) + child, err := inner.Child.FindChildByHash(hash) + if err != nil { + return nil, err + } if child != nil { - return child + return child, nil } } else { - lhs := inner.LHS.FindChildByHash(hash) + lhs, err := inner.LHS.FindChildByHash(hash) + if err != nil { + return nil, err + } if lhs != nil { - return lhs + return lhs, nil } - rhs := inner.RHS.FindChildByHash(hash) + rhs, err := inner.RHS.FindChildByHash(hash) + if err != nil { + return nil, err + } if rhs != nil { - return rhs + return rhs, nil } } } - return nil // not found + return nil, nil // not found } func (tree *Tree) Join(other *Tree) *Tree { @@ -198,11 +208,11 @@ func (tree *Tree) Iterated(rep uint64) *Tree { return root } -func (tree *Tree) ProveLeaf(index *big.Int) *Proof { +func (tree *Tree) ProveLeaf(index *big.Int) (*Proof, error) { return tree.ProveLeafRec(index) } -func (tree *Tree) ProveLast() *Proof { +func (tree *Tree) ProveLast() (*Proof, error) { // index = (1 << height) - 1 index := new(big.Int).Sub( new(big.Int).Lsh( @@ -214,21 +224,21 @@ func (tree *Tree) ProveLast() *Proof { return tree.ProveLeaf(index) } -func (tree *Tree) ProveLeafRec(index *big.Int) *Proof { +func (tree *Tree) ProveLeafRec(index *big.Int) (*Proof, error) { numLeafs := new(big.Int).Lsh(one, uint(tree.Height)) if numLeafs.Cmp(index) <= 0 { - panic(fmt.Sprintf("index out of bounds: %v, %v", numLeafs, index)) + return nil, fmt.Errorf("index out of bounds: %v, %v", numLeafs, index) } subtree := tree.Subtrees if subtree == nil { if index.Cmp(zero) != 0 { - panic(fmt.Sprintf("invalid Tree state: %v", tree)) + return nil, fmt.Errorf("invalid Tree state: %v", tree) } if tree.Height != 0 { - panic(fmt.Sprintf("invalid Tree state: %v", tree)) + return nil, fmt.Errorf("invalid Tree state: %v", tree) } - return Leaf(tree.RootHash, index) + return Leaf(tree.RootHash, index), nil } shiftAmount := uint(tree.Height - 1) @@ -245,17 +255,21 @@ func (tree *Tree) ProveLeafRec(index *big.Int) *Proof { ), ) - lhs, rhs := subtree.Children() + lhs, rhs, err := subtree.Children() + if err != nil { + return nil, err + } + if isLeftLeaf { - proof := lhs.ProveLeafRec(innerIndex) + proof, err := lhs.ProveLeafRec(innerIndex) proof.PushHash(rhs.RootHash) proof.Pos = index - return proof + return proof, err } else { - proof := rhs.ProveLeafRec(innerIndex) + proof, err := rhs.ProveLeafRec(innerIndex) proof.PushHash(lhs.RootHash) proof.Pos = index - return proof + return proof, err } } @@ -303,40 +317,45 @@ func (b *Builder) AppendRepeatedUint64(leaf *Tree, reps uint64) { b.AppendRepeated(leaf, new(big.Int).SetUint64(reps)) } -func (b *Builder) AppendRepeated(leaf *Tree, reps *big.Int) { +func (b *Builder) AppendRepeated(leaf *Tree, reps *big.Int) error { if reps.Cmp(zero) <= 0 { - panic("invalid repetitions") + return fmt.Errorf("invalid repetitions: %v", reps) + } + + accumulatedCount, err := b.CalculateAccumulatedCount(reps) + if err != nil { + return err } - accumulatedCount := b.CalculateAccumulatedCount(reps) if height, ok := b.Height(); ok { if height != leaf.Height { - panic("mismatched tree size") + return fmt.Errorf("mismatched tree sizes, height: %v and leaf height: %v", height, leaf.Height) } } b.Trees = append(b.Trees, Node{ Tree: leaf, AccumulatedCount: accumulatedCount, }) + return nil } -func (b *Builder) Build() *Tree { +func (b *Builder) Build() (*Tree, error) { if count, ok := b.Count(); ok { if !isCountPow2(count) { - panic(fmt.Sprintf("builder has %v leafs, which is not a power of two", count)) + return nil, fmt.Errorf("builder has %v leafs, which is not a power of two", count) } log2Size := countTrailingZeroes(count) - return buildMerkle(b.Trees, log2Size, big.NewInt(0)) + return buildMerkle(b.Trees, log2Size, big.NewInt(0)), nil } else { - panic("no leafs in the merkle builder") + return nil, fmt.Errorf("no leafs in the merkle builder: %v", spew.Sprint(b)) } } -func (b *Builder) CalculateAccumulatedCount(reps *big.Int) *big.Int { +func (b *Builder) CalculateAccumulatedCount(reps *big.Int) (*big.Int, error) { n := len(b.Trees) if n != 0 { if reps.Cmp(zero) == 0 { - panic("merkle builder is full") + return nil, fmt.Errorf("merkle builder is full") } accumulatedCount := new(big.Int).And( @@ -344,11 +363,11 @@ func (b *Builder) CalculateAccumulatedCount(reps *big.Int) *big.Int { overflowMask, ) if reps.Cmp(accumulatedCount) >= 0 { - panic("merkle tree overflow") + return nil, fmt.Errorf("merkle tree overflow") } - return accumulatedCount + return accumulatedCount, nil } else { - return reps + return reps, nil } } diff --git a/internal/merkle/builder_test.go b/internal/merkle/builder_test.go index d533ba2b7..e876af59e 100644 --- a/internal/merkle/builder_test.go +++ b/internal/merkle/builder_test.go @@ -37,34 +37,37 @@ func TestIsCountPow2(t *testing.T) { func TestSimple0(t *testing.T) { builder := Builder{} builder.Append(TreeLeaf(oneDigest)) - treeRoot := builder.Build().RootHash + treeRoot, err := builder.Build() + assert.Nil(t, err) expected := oneDigest - assert.Equal(t, expected, treeRoot) + assert.Equal(t, expected, treeRoot.RootHash) } func TestSimple1(t *testing.T) { builder := Builder{} builder.Append(TreeLeaf(zeroDigest)) builder.Append(TreeLeaf(oneDigest)) - treeRoot := builder.Build().RootHash + treeRoot, err := builder.Build() + assert.Nil(t, err) expected := TreeLeaf(zeroDigest).Join(TreeLeaf(oneDigest)).RootHash - assert.Equal(t, expected, treeRoot) + assert.Equal(t, expected, treeRoot.RootHash) } func TestSimple2(t *testing.T) { builder := Builder{} builder.AppendRepeatedUint64(TreeLeaf(oneDigest), 2) builder.AppendRepeatedUint64(TreeLeaf(zeroDigest), 2) - treeRoot := builder.Build().RootHash + treeRoot, err := builder.Build() + assert.Nil(t, err) lhs := TreeLeaf(oneDigest).Join(TreeLeaf(oneDigest)) rhs := TreeLeaf(zeroDigest).Join(TreeLeaf(zeroDigest)) expected := lhs.Join(rhs).RootHash - assert.Equal(t, expected, treeRoot) + assert.Equal(t, expected, treeRoot.RootHash) } func TestSimple3(t *testing.T) { @@ -72,13 +75,14 @@ func TestSimple3(t *testing.T) { builder.Append(TreeLeaf(zeroDigest)) builder.AppendRepeatedUint64(TreeLeaf(oneDigest), 2) builder.Append(TreeLeaf(zeroDigest)) - treeRoot := builder.Build().RootHash + treeRoot, err := builder.Build() + assert.Nil(t, err) lhs := TreeLeaf(zeroDigest).Join(TreeLeaf(oneDigest)) rhs := TreeLeaf(oneDigest).Join(TreeLeaf(zeroDigest)) expected := lhs.Join(rhs).RootHash - assert.Equal(t, expected, treeRoot) + assert.Equal(t, expected, treeRoot.RootHash) } func TestMerkleBuilder8(t *testing.T) { @@ -87,7 +91,8 @@ func TestMerkleBuilder8(t *testing.T) { builder.AppendRepeatedUint64(TreeLeaf(zeroDigest), 6) assert.True(t, builder.CanBuild()) - merkle := builder.Build() + merkle, err := builder.Build() + assert.Nil(t, err) assert.Equal(t, merkle.RootHash, TreeLeaf(zeroDigest).Iterated(3).RootHash) } @@ -101,7 +106,8 @@ func TestMerkleBuilder64(t *testing.T) { builder.AppendRepeated(TreeLeaf(zeroDigest), reps) assert.True(t, builder.CanBuild()) - merkle := builder.Build() + merkle, err := builder.Build() + assert.Nil(t, err) assert.Equal(t, merkle.RootHash, TreeLeaf(zeroDigest).Iterated(64).RootHash) } @@ -113,7 +119,8 @@ func TestMerkleBuilder256(t *testing.T) { builder.AppendRepeated(TreeLeaf(zeroDigest), reps) assert.True(t, builder.CanBuild()) - merkle := builder.Build() + merkle, err := builder.Build() + assert.Nil(t, err) assert.Equal(t, merkle.RootHash, TreeLeaf(zeroDigest).Iterated(256).RootHash) } @@ -121,11 +128,13 @@ func TestAppendAndRepeated(t *testing.T) { builder := Builder{} builder.Append(TreeLeaf(zeroDigest)) assert.True(t, builder.CanBuild()) - tree1 := builder.Build() + tree1, err := builder.Build() + assert.Nil(t, err) builder = Builder{} builder.AppendRepeatedUint64(TreeLeaf(zeroDigest), 1) - tree2 := builder.Build() + tree2, err := builder.Build() + assert.Nil(t, err) assert.Equal(t, tree1, tree2) } @@ -168,8 +177,11 @@ func TestBuildRootChildrenAgainstBuilder(t *testing.T) { builder.AppendRepeatedUint64(TreeLeaf(common.HexToHash("0x1588d343bd73f167bf4886b8ab7694b4d83b60087ddbdb445c427c16f26d2644")), 16777216) builder.AppendRepeatedUint64(TreeLeaf(common.HexToHash("0x1588d343bd73f167bf4886b8ab7694b4d83b60087ddbdb445c427c16f26d2644")), 281474926379008) - builderTree := builder.Build() - proofBuilder := builderTree.ProveLast() + builderTree, err := builder.Build() + assert.Nil(t, err) + + proofBuilder, err := builderTree.ProveLast() + assert.Nil(t, err) rootHashBuilder := proofBuilder.BuildRoot() lhsBuilder, rhsBuilder, err := proofBuilder.BuildRootChildren() @@ -247,9 +259,11 @@ func TestFindChildByHash(t *testing.T) { leaf2 := TreeLeaf(common.HexToHash("0x2")) builder.Append(leaf1) builder.Append(leaf2) - tree := builder.Build() + tree, err := builder.Build() + assert.Nil(t, err) - child1 := tree.FindChildByHash(leaf1.RootHash) + child1, err := tree.FindChildByHash(leaf1.RootHash) + assert.Nil(t, err) assert.NotNil(t, child1) assert.Equal(t, leaf1.RootHash, child1.RootHash) }) @@ -258,9 +272,11 @@ func TestFindChildByHash(t *testing.T) { builder := Builder{} leaf1 := TreeLeaf(common.HexToHash("0x1")) builder.AppendRepeatedUint64(leaf1, 1024) - tree := builder.Build() + tree, err := builder.Build() + assert.Nil(t, err) - child1 := tree.FindChildByHash(leaf1.RootHash) + child1, err := tree.FindChildByHash(leaf1.RootHash) + assert.Nil(t, err) assert.NotNil(t, child1) assert.Equal(t, leaf1.RootHash, child1.RootHash) }) @@ -275,13 +291,16 @@ func TestFindChildByHash(t *testing.T) { builder.Append(leaf2) builder.Append(leaf3) builder.Append(leaf4) - tree := builder.Build() + tree, err := builder.Build() + assert.Nil(t, err) - child1 := tree.FindChildByHash(leaf1.RootHash) + child1, err := tree.FindChildByHash(leaf1.RootHash) + assert.Nil(t, err) assert.NotNil(t, child1) assert.Equal(t, leaf1.RootHash, child1.RootHash) - child2 := tree.FindChildByHash(leaf2.RootHash) + child2, err := tree.FindChildByHash(leaf2.RootHash) + assert.Nil(t, err) assert.NotNil(t, child2) assert.Equal(t, child2.RootHash, leaf2.RootHash) }) @@ -290,10 +309,12 @@ func TestFindChildByHash(t *testing.T) { builder := Builder{} builder.Append(TreeLeaf(zeroDigest)) builder.Append(TreeLeaf(oneDigest)) - tree := builder.Build() + tree, err := builder.Build() + assert.Nil(t, err) missing := common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - child := tree.FindChildByHash(missing) + child, err := tree.FindChildByHash(missing) + assert.Nil(t, err) assert.Nil(t, child) }) } diff --git a/internal/validator/validator.go b/internal/validator/validator.go index fcb214865..24e4ee411 100644 --- a/internal/validator/validator.go +++ b/internal/validator/validator.go @@ -137,6 +137,9 @@ func (s *Service) validateApplication(ctx context.Context, app *Application) err } for _, epoch := range processedEpochs { + if err := ctx.Err(); err != nil { + return err + } s.Logger.Debug("Started calculating outputs merkle root", "application", appAddress, "epoch_index", epoch.Index, @@ -154,6 +157,16 @@ func (s *Service) validateApplication(ctx context.Context, app *Application) err "outputs_merkle_root", *merkleRoot, ) + // Don't crash on a malformed epoch + if epoch.MachineHash == nil { + return s.setApplicationInoperable(ctx, app, + "epoch %v has nil machine hash", epoch.Index) + } + if epoch.OutputsMerkleRoot == nil { + return s.setApplicationInoperable(ctx, app, + "epoch %v has nil outputs merkle root", epoch.Index) + } + // The Cartesi Machine calculates the root hash of the outputs Merkle // tree after each input. Therefore, the root hash calculated after the // last input in the epoch must match the one calculated by the Validator @@ -173,13 +186,19 @@ func (s *Service) validateApplication(ctx context.Context, app *Application) err } // DaveConsensus can have empty epochs. Authority and Quorum don't. - if !app.IsDaveConsensus() || input != nil { + if input != nil { if input.OutputsHash == nil { return s.setApplicationInoperable(ctx, app, "inconsistent state: epoch %v last input (%v) outputs merkle root is not defined", epoch.Index, input.Index) } + if input.MachineHash == nil { + return s.setApplicationInoperable(ctx, app, + "inconsistent state: epoch %v last input (%v) machine hash is not defined", + epoch.Index, input.Index) + } + // ...and compare it to the hash calculated by the Validator if *epoch.OutputsMerkleRoot != *input.OutputsHash { return s.setApplicationInoperable(ctx, app, @@ -193,6 +212,11 @@ func (s *Service) validateApplication(ctx context.Context, app *Application) err epoch.Index, input.Index, *input.MachineHash, *epoch.MachineHash) } } else { // empty epochs + if !app.IsDaveConsensus() { + return s.setApplicationInoperable(ctx, app, + "epoch %v has no inputs but is not DaveConsensus", epoch.Index) + } + if epoch.VirtualIndex > 0 { previousEpoch, err := s.repository.GetEpochByVirtualIndex(ctx, appAddress, epoch.VirtualIndex-1) if err != nil { @@ -201,6 +225,17 @@ func (s *Service) validateApplication(ctx context.Context, app *Application) err epoch.Index, epoch.VirtualIndex, appAddress, err, ) } + + if previousEpoch.MachineHash == nil { + return s.setApplicationInoperable(ctx, app, + "epoch %v machine hash is not defined", previousEpoch.Index) + } + + if previousEpoch.OutputsMerkleRoot == nil { + return s.setApplicationInoperable(ctx, app, + "epoch %v outputs merkle root is not defined", previousEpoch.Index) + } + if *epoch.MachineHash != *previousEpoch.MachineHash { return s.setApplicationInoperable(ctx, app, "epoch %v machine hash does not match previous epoch %v machine hash. Expected: %v, Got %v", @@ -268,8 +303,20 @@ func (s *Service) buildCommitment(ctx context.Context, app *Application, epoch * "application", app.Name, "epoch", epoch.Index) + if epoch.InputIndexLowerBound > epoch.InputIndexUpperBound { + return nil, nil, s.setApplicationInoperable(ctx, app, + "invalid epoch %v (%v): lower bound (%v) > upper bound (%v).", + epoch.Index, epoch.VirtualIndex, epoch.InputIndexLowerBound, epoch.InputIndexUpperBound) + } + builder := merkle.Builder{} inputCount := epoch.InputIndexUpperBound - epoch.InputIndexLowerBound + if pkgm.InputsPerEpoch < inputCount { + return nil, nil, s.setApplicationInoperable(ctx, app, + "input count is too large for epoch %v of application %v: max %v, got %v", + epoch.Index, app.Name, pkgm.InputsPerEpoch, inputCount) + } + if inputCount > 0 { statesHashes, total, err := s.repository.ListStateHashes(ctx, app.IApplicationAddress.String(), repository.StateHashFilter{EpochIndex: &epoch.Index}, repository.Pagination{}, false) @@ -295,9 +342,16 @@ func (s *Service) buildCommitment(ctx context.Context, app *Application, epoch * builder.AppendRepeatedUint64(merkle.TreeLeaf(*epoch.MachineHash), remainingStrides) } - epochCommitmentTree := builder.Build() + epochCommitmentTree, err := builder.Build() + if err != nil { + return nil, nil, err + } + commitment := epochCommitmentTree.GetRootHash() - proof := epochCommitmentTree.ProveLast() + proof, err := epochCommitmentTree.ProveLast() + if err != nil { + return nil, nil, err + } s.Logger.Info("DaveConsensus epoch commitment built", "application", app.Name, "epoch", epoch.Index, diff --git a/internal/validator/validator_test.go b/internal/validator/validator_test.go index fb5cc866e..115cf178e 100644 --- a/internal/validator/validator_test.go +++ b/internal/validator/validator_test.go @@ -136,13 +136,15 @@ func (s *ValidatorSuite) TestCreateClaimAndProofSuccess() { } s.Run("FirstEpochNoOutputs", func() { + ctx := context.Background() repo.On("ListOutputs", mock.Anything, mock.Anything, mock.Anything, mock.Anything, false, ).Return([]*Output{}, uint64(0), nil) - claimHash, _, err := validator.computeMerkleTreeAndProofs(nil, &app, &dummyEpochs[0]) + claimHash, _, err := validator.computeMerkleTreeAndProofs(ctx, &app, &dummyEpochs[0]) + s.NoError(err) claimHashRef, _, err := merkle.CreateProofs(nil, merkle.TREE_DEPTH) - s.ErrorIs(nil, err) + s.NoError(err) s.NotNil(claimHash) s.Equal(claimHashRef, *claimHash) repo.AssertExpectations(s.T()) @@ -158,7 +160,7 @@ func (s *ValidatorSuite) TestCreateClaimAndProofSuccess() { ).Return([]*Output{&output}, uint64(1), nil) claimHash, _, err := validator.computeMerkleTreeAndProofs(nil, &app, &dummyEpochs[0]) - s.ErrorIs(nil, err) + s.NoError(err) s.NotNil(claimHash) repo.AssertExpectations(s.T()) }) @@ -173,7 +175,7 @@ func (s *ValidatorSuite) TestCreateClaimAndProofSuccess() { ).Return(&dummyEpochs[0], nil).Once() claimHash, _, err := validator.computeMerkleTreeAndProofs(nil, &app, &dummyEpochs[1]) - s.ErrorIs(nil, err) + s.NoError(err) s.Equal(dummyEpochs[0].OutputsMerkleRoot, claimHash) repo.AssertExpectations(s.T()) }) @@ -200,7 +202,7 @@ func (s *ValidatorSuite) TestCreateClaimAndProofSuccess() { ).Return(&dummyOutputs[0], nil).Once() _, _, err := validator.computeMerkleTreeAndProofs(nil, &app, &dummyEpochs[1]) - s.ErrorIs(nil, err) + s.NoError(err) repo.AssertExpectations(s.T()) }) } @@ -337,7 +339,7 @@ func (s *ValidatorSuite) TestValidateApplicationSuccess() { ).Return(([]*Epoch)(nil), uint64(0), nil).Once() err := validator.validateApplication(ctx, &app) - s.ErrorIs(nil, err) + s.NoError(err) repo.AssertExpectations(s.T()) }) @@ -365,7 +367,7 @@ func (s *ValidatorSuite) TestValidateApplicationSuccess() { ).Return(nil).Once() err := validator.validateApplication(ctx, &app) - s.ErrorIs(nil, err) + s.NoError(err) repo.AssertExpectations(s.T()) }) } @@ -446,7 +448,7 @@ func (s *ValidatorSuite) TestValidateApplicationFailure() { mock.Anything, mock.Anything, mock.Anything, mock.Anything, ).Return(nil).Once() - err := validator.validateApplication(nil, &app) + err := validator.validateApplication(ctx, &app) s.NotNil(err) repo.AssertExpectations(s.T()) }) @@ -506,6 +508,81 @@ func (s *ValidatorSuite) TestValidateApplicationFailure() { s.ErrorIs(err, xerror) repo.AssertExpectations(s.T()) }) + + s.Run("BuildCommitmentFromInvalidBoundsFailure", func() { + daveApp := Application{ + ID: app.ID, + Name: "dummy-application-name", + ConsensusType: Consensus_PRT, + } + input := Input{ + EpochApplicationID: daveApp.ID, + OutputsHash: &validator.pristineRootHash, + MachineHash: &validator.pristineRootHash, + } + + repo.On("ListEpochs", + mock.Anything, daveApp.IApplicationAddress.String(), mock.Anything, mock.Anything, false, + ).Return([]*Epoch{&Epoch{ + MachineHash: dummyEpochs[0].MachineHash, + OutputsMerkleRoot: dummyEpochs[0].OutputsMerkleRoot, + InputIndexLowerBound: 2, // << usually lower <= upper + InputIndexUpperBound: 1, + }}, uint64(1), nil).Once() + + repo.On("GetLastInput", + mock.Anything, app.IApplicationAddress.String(), dummyEpochs[0].Index, + ).Return(&input, nil).Once() + + repo.On("ListOutputs", + mock.Anything, mock.Anything, mock.Anything, mock.Anything, false, + ).Return([]*Output{}, uint64(0), nil).Once() + + repo.On("UpdateApplicationState", + mock.Anything, mock.Anything, mock.Anything, mock.Anything, + ).Return(nil).Once() + + err := validator.validateApplication(ctx, &daveApp) + repo.AssertExpectations(s.T()) + s.NotNil(err) + }) + + s.Run("InputCountTooLargeFailure", func() { + daveApp := Application{ + ID: app.ID, + Name: "dummy-application-name", + ConsensusType: Consensus_PRT, + } + input := Input{ + EpochApplicationID: daveApp.ID, + OutputsHash: &validator.pristineRootHash, + MachineHash: &validator.pristineRootHash, + } + repo.On("ListEpochs", + mock.Anything, daveApp.IApplicationAddress.String(), mock.Anything, mock.Anything, false, + ).Return([]*Epoch{&Epoch{ + MachineHash: dummyEpochs[0].MachineHash, + OutputsMerkleRoot: dummyEpochs[0].OutputsMerkleRoot, + InputIndexLowerBound: 0, + InputIndexUpperBound: (1 << 24) + 1, // input is too large + }}, uint64(1), nil).Once() + + repo.On("ListOutputs", + mock.Anything, mock.Anything, mock.Anything, mock.Anything, false, + ).Return([]*Output{}, uint64(0), nil).Once() + + repo.On("GetLastInput", + mock.Anything, app.IApplicationAddress.String(), dummyEpochs[0].Index, + ).Return(&input, nil).Once() + + repo.On("UpdateApplicationState", + mock.Anything, mock.Anything, mock.Anything, mock.Anything, + ).Return(nil).Once() + + err := validator.validateApplication(ctx, &daveApp) + repo.AssertExpectations(s.T()) + s.NotNil(err) + }) } type Mockrepo struct {