Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add validator integration tests #588

Merged
merged 4 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions internal/repository/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,13 @@ func (pg *Database) InsertOutput(
INSERT INTO output
(index,
raw_data,
hash,
output_hashes_siblings,
input_id)
VALUES
(@index,
@rawData,
@hash,
@outputHashesSiblings,
@inputId)
RETURNING
Expand All @@ -230,6 +232,7 @@ func (pg *Database) InsertOutput(
"inputId": output.InputId,
"index": output.Index,
"rawData": output.RawData,
"hash": output.Hash,
"outputHashesSiblings": output.OutputHashesSiblings,
}

Expand Down
13 changes: 10 additions & 3 deletions internal/validator/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,15 @@ func (v *Validator) validateApplication(ctx context.Context, app Application) er
)
}

if machineClaim == nil {
return fmt.Errorf(
"inconsistent state: machine claim for epoch %v of application %v was not found",
epoch.Index, epoch.AppAddress,
)
}

// ...and compare it to the hash calculated by the Validator
if machineClaim != nil && *machineClaim != *claim {
if *machineClaim != *claim {
return fmt.Errorf(
"validator claim does not match machine claim for epoch %v of application %v",
epoch.Index, epoch.AppAddress,
Expand Down Expand Up @@ -239,8 +246,8 @@ func (v *Validator) createClaimAndProofs(
if previousOutputs[idx].Hash == nil {
// should never happen
return nil, nil, fmt.Errorf(
"missing hash of output %d of epoch %d",
previousOutputs[idx].Index, epoch.Index,
"missing hash of output %d from input %d",
previousOutputs[idx].Index, previousOutputs[idx].InputId,
)
}
leaves = append(leaves, *previousOutputs[idx].Hash)
Expand Down
177 changes: 3 additions & 174 deletions internal/validator/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ package validator
import (
"context"
crand "crypto/rand"
mrand "math/rand"
"testing"

"github.com/cartesi/rollups-node/internal/merkle"
. "github.com/cartesi/rollups-node/internal/node/model"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
)
Expand All @@ -25,10 +23,9 @@ func TestValidatorSuite(t *testing.T) {
}

var (
validator *Validator
repository *MockRepository
dummyEpochs []Epoch
inputBoxDeploymentBlock uint64
validator *Validator
repository *MockRepository
dummyEpochs []Epoch
)

func (s *ValidatorSuite) SetupSubTest() {
Expand All @@ -47,133 +44,6 @@ func (s *ValidatorSuite) TearDownSubTest() {
validator = nil
}

func (s *ValidatorSuite) TestItCreatesClaimAndProofs() {
// returns pristine claim and no proofs
s.Run("WhenThereAreNoOutputsAndNoPreviousEpoch", func() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

epoch := dummyEpochs[0]

repository.On(
"GetOutputsProducedInBlockRange",
mock.Anything, epoch.AppAddress, epoch.FirstBlock, epoch.LastBlock,
).Return(nil, nil)
repository.On("GetPreviousEpoch", mock.Anything, epoch).Return(nil, nil)

claim, outputs, err := validator.createClaimAndProofs(ctx, epoch)
s.Require().Nil(err)
s.Require().NotNil(claim)

expectedClaim, _, err := merkle.CreateProofs(nil, MAX_OUTPUT_TREE_HEIGHT)
s.Require().Nil(err)
s.Require().NotNil(expectedClaim)

s.Equal(expectedClaim, *claim)
s.Nil(outputs)
repository.AssertExpectations(s.T())
})

// returns previous epoch claim and no proofs
s.Run("WhenThereAreNoOutputsAndThereIsAPreviousEpoch", func() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

previousEpoch := dummyEpochs[0]
expectedClaim := randomHash()
previousEpoch.ClaimHash = &expectedClaim
epoch := dummyEpochs[1]

repository.On(
"GetOutputsProducedInBlockRange",
mock.Anything, epoch.AppAddress, epoch.FirstBlock, epoch.LastBlock,
).Return(nil, nil)
repository.On("GetPreviousEpoch", mock.Anything, epoch).Return(&previousEpoch, nil)

claim, outputs, err := validator.createClaimAndProofs(ctx, epoch)
s.Require().Nil(err)
s.Require().NotNil(claim)

s.Equal(expectedClaim, *claim)
s.Nil(outputs)
repository.AssertExpectations(s.T())
})

// returns new claim and proofs
s.Run("WhenThereAreOutputsAndNoPreviousEpoch", func() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

epoch := dummyEpochs[0]
outputs := randomOutputs(2, 0, false)

repository.On(
"GetOutputsProducedInBlockRange",
mock.Anything, epoch.AppAddress, epoch.FirstBlock, epoch.LastBlock,
).Return(outputs, nil).Once()
repository.On("GetPreviousEpoch", mock.Anything, epoch).Return(nil, nil)

claim, updatedOutputs, err := validator.createClaimAndProofs(ctx, epoch)
s.Require().Nil(err)
s.Require().NotNil(claim)

s.Len(updatedOutputs, len(outputs))
for idx, output := range updatedOutputs {
s.Equal(outputs[idx].Id, output.Id)
s.NotNil(output.Hash)
s.NotNil(output.OutputHashesSiblings)
}
repository.AssertExpectations(s.T())
})

// returns new claim and proofs
s.Run("WhenThereAreOutputsAndAPreviousEpoch", func() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

previousEpoch := dummyEpochs[0]
previousEpochClaim := randomHash()
previousEpoch.ClaimHash = &previousEpochClaim
epoch := dummyEpochs[1]
previousOutputs := randomOutputs(2, 0, true)
epochOutputs := randomOutputs(2, 2, false)

repository.On(
"GetOutputsProducedInBlockRange",
mock.Anything, epoch.AppAddress, epoch.FirstBlock, epoch.LastBlock,
).Return(epochOutputs, nil)
repository.On(
"GetOutputsProducedInBlockRange",
mock.Anything, epoch.AppAddress, inputBoxDeploymentBlock, previousEpoch.LastBlock,
).Return(previousOutputs, nil)
repository.On("GetPreviousEpoch", mock.Anything, epoch).Return(&previousEpoch, nil)

claim, updatedOutputs, err := validator.createClaimAndProofs(ctx, epoch)
s.Require().Nil(err)
s.Require().NotNil(claim)

allOutputs := append(previousOutputs, epochOutputs...)
leaves := make([]Hash, 0, len(allOutputs))
for _, output := range allOutputs {
leaves = append(leaves, *output.Hash)
}
expectedClaim, allProofs, err := merkle.CreateProofs(leaves, MAX_OUTPUT_TREE_HEIGHT)
s.Require().Nil(err)

s.NotEqual(previousEpoch.ClaimHash, claim)
s.Equal(&expectedClaim, claim)
s.Len(updatedOutputs, len(epochOutputs))

for idx, output := range updatedOutputs {
s.Equal(epochOutputs[idx].Index, output.Index)
s.NotNil(output.Hash)
s.NotNil(output.OutputHashesSiblings)
s.assertProofs(output, allProofs)
}
repository.AssertExpectations(s.T())
})
}

func (s *ValidatorSuite) TestItFailsWhenClaimDoesNotMatchMachineOutputsHash() {
s.Run("OneAppSingleEpoch", func() {
ctx, cancel := context.WithCancel(context.Background())
Expand Down Expand Up @@ -310,12 +180,6 @@ func (s *ValidatorSuite) TestItFailsWhenClaimDoesNotMatchMachineOutputsHash() {
})
}

func (s *ValidatorSuite) assertProofs(output Output, allProofs []Hash) {
start := output.Index * MAX_OUTPUT_TREE_HEIGHT
end := (output.Index * MAX_OUTPUT_TREE_HEIGHT) + MAX_OUTPUT_TREE_HEIGHT
s.Equal(allProofs[start:end], output.OutputHashesSiblings)
}

func randomAddress() Address {
address := make([]byte, 20)
_, err := crand.Read(address)
Expand All @@ -334,41 +198,6 @@ func randomHash() Hash {
return Hash(hash)
}

func randomBytes() []byte {
size := mrand.Intn(100) + 1
bytes := make([]byte, size)
_, err := crand.Read(bytes)
if err != nil {
panic(err)
}
return bytes
}

// randomOutputs generates n new Outputs with sequential indexes starting at
// `firstIdx` and random data. Optionally, it will generate dummy proofs if
// `withProofs` is true. Returns an slice with the new Outputs.
func randomOutputs(n int, firstIdx int, withProofs bool) []Output {
slice := make([]Output, n)
for idx := 0; idx < n; idx++ {
output := Output{
Id: mrand.Uint64(),
Index: uint64(idx + firstIdx),
RawData: randomBytes(),
}
if withProofs {
proofs := make([]Hash, MAX_OUTPUT_TREE_HEIGHT)
hash := crypto.Keccak256Hash(output.RawData)
output.Hash = &hash
for idx := 0; idx < MAX_OUTPUT_TREE_HEIGHT; idx++ {
proofs[idx] = randomHash()
}
output.OutputHashesSiblings = proofs
}
slice[idx] = output
}
return slice
}

type MockRepository struct {
mock.Mock
}
Expand Down
Loading
Loading