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

Verify events hash [Verifiable events part 2] #739

Merged
merged 12 commits into from Jun 23, 2021
29 changes: 29 additions & 0 deletions model/chunks/chunkFaults.go
Expand Up @@ -79,6 +79,35 @@ func NewCFNonMatchingFinalState(expected flow.StateCommitment, computed flow.Sta
execResID: execResID}
}

// CFInvalidEventsCollection is returned when computed events collection hash is different from the chunk's one
type CFInvalidEventsCollection struct {
expected flow.Identifier
computed flow.Identifier
chunkIndex uint64
execResID flow.Identifier
m4ksio marked this conversation as resolved.
Show resolved Hide resolved
}

func NewCFInvalidEventsCollection(expected flow.Identifier, computed flow.Identifier, chInx uint64, execResID flow.Identifier) *CFInvalidEventsCollection {
return &CFInvalidEventsCollection{
expected: expected,
computed: computed,
chunkIndex: chInx,
execResID: execResID,
}
}

func (c *CFInvalidEventsCollection) ChunkIndex() uint64 {
return c.chunkIndex
}

func (c *CFInvalidEventsCollection) ExecutionResultID() flow.Identifier {
return c.execResID
}

func (c *CFInvalidEventsCollection) String() string {
return fmt.Sprintf("events collection hash differs, got %x expected %x", c.computed, c.expected)
m4ksio marked this conversation as resolved.
Show resolved Hide resolved
}

// CFInvalidVerifiableChunk is returned when a verifiable chunk is invalid
// this includes cases that code fails to construct a partial trie,
// collection hashes doesn't match
Expand Down
9 changes: 9 additions & 0 deletions module/chunks/chunkVerifier.go
Expand Up @@ -100,6 +100,8 @@ func (fcv *ChunkVerifier) verifyTransactionsInContext(context fvm.Context, chunk
return nil, nil, fmt.Errorf("missing chunk data pack")
}

events := make(flow.EventsList, 0)

// constructing a partial trie given chunk data package
psmt, err := partial.NewLedger(chunkDataPack.Proof, ledger.State(chunkDataPack.StartState), partial.DefaultPathFinderVersion)

Expand Down Expand Up @@ -166,6 +168,8 @@ func (fcv *ChunkVerifier) verifyTransactionsInContext(context fvm.Context, chunk
return nil, nil, fmt.Errorf("failed to execute transaction: %d (%w)", i, err)
}

events = append(events, tx.Events...)

// always merge back the tx view (fvm is responsible for changes on tx errors)
err = chunkView.MergeView(txView)
if err != nil {
Expand All @@ -182,6 +186,11 @@ func (fcv *ChunkVerifier) verifyTransactionsInContext(context fvm.Context, chunk
return nil, chmodels.NewCFMissingRegisterTouch(missingRegs, chIndex, execResID), nil
}

eventsHash := events.Hash()
if chunk.EventCollection != eventsHash {
return nil, chmodels.NewCFInvalidEventsCollection(chunk.EventCollection, eventsHash, chIndex, execResID), nil
}

// applying chunk delta (register updates at chunk level) to the partial trie
// this returns the expected end state commitment after updates and the list of
// register keys that was not provided by the chunk data package (err).
Expand Down
39 changes: 39 additions & 0 deletions module/chunks/chunkVerifier_test.go
Expand Up @@ -27,6 +27,23 @@ import (
"github.com/onflow/flow-go/utils/unittest"
)

var eventsList = flow.EventsList{
{
Type: "event.someType",
TransactionID: flow.Identifier{2, 3, 2, 3},
TransactionIndex: 1,
EventIndex: 2,
Payload: []byte{7, 3, 1, 2},
},
{
Type: "event.otherType",
TransactionID: flow.Identifier{3, 3, 3},
TransactionIndex: 4,
EventIndex: 4,
Payload: []byte{7, 3, 1, 2},
},
}

type ChunkVerifierTestSuite struct {
suite.Suite
verifier *chunks.ChunkVerifier
Expand Down Expand Up @@ -116,6 +133,17 @@ func (s *ChunkVerifierTestSuite) TestFailedTx() {
assert.NotNil(s.T(), spockSecret)
}

// TestEventsMismatch tests verification behavior in case
// of emitted events not matching chunks
func (s *ChunkVerifierTestSuite) TestEventsMismatch() {
vch := GetBaselineVerifiableChunk(s.T(), []byte("eventsMismatch"))
assert.NotNil(s.T(), vch)
_, chFault, err := s.verifier.Verify(vch)
assert.Nil(s.T(), err)
assert.NotNil(s.T(), chFault)
assert.IsType(s.T(), &chunksmodels.CFInvalidEventsCollection{}, chFault)
}

// TestVerifyWrongChunkType evaluates that following invocations return an error:
// - verifying a system chunk with Verify method.
// - verifying a non-system chunk with SystemChunkVerify method.
Expand Down Expand Up @@ -145,6 +173,7 @@ func (s *ChunkVerifierTestSuite) TestEmptyCollection() {
col := unittest.CollectionFixture(0)
vch.Collection = &col
vch.EndState = vch.ChunkDataPack.StartState
vch.Chunk.EventCollection = flow.EventsList{}.Hash() //empty collection emits no events
spockSecret, chFaults, err := s.verifier.Verify(vch)
assert.Nil(s.T(), err)
assert.Nil(s.T(), chFaults)
Expand Down Expand Up @@ -234,6 +263,7 @@ func GetBaselineVerifiableChunk(t *testing.T, script []byte) *verification.Verif
CollectionIndex: 0,
StartState: flow.StateCommitment(startState),
BlockID: blockID,
EventCollection: eventsList.Hash(),
},
Index: 0,
}
Expand Down Expand Up @@ -280,11 +310,20 @@ func (vm *vmMock) Run(ctx fvm.Context, proc fvm.Procedure, led state.View, progr
// add updates to the ledger
_ = led.Set("05", "", "", []byte{'B'})
tx.Err = &fvmErrors.CadenceRuntimeError{} // inside the runtime (e.g. div by zero, access account)
case "eventsMismatch":
tx.Events = append(eventsList, flow.Event{
Type: "event.Extra",
TransactionID: flow.Identifier{2, 3},
TransactionIndex: 0,
EventIndex: 0,
Payload: []byte{88},
})
default:
_, _ = led.Get("00", "", "")
_, _ = led.Get("05", "", "")
_ = led.Set("05", "", "", []byte{'B'})
tx.Logs = []string{"log1", "log2"}
tx.Events = eventsList
}

return nil
Expand Down