github.com/koko1123/flow-go-1@v0.29.6/module/chunks/chunkVerifier.go (about)

     1  package chunks
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/rs/zerolog"
     8  
     9  	"github.com/koko1123/flow-go-1/fvm/blueprints"
    10  	"github.com/koko1123/flow-go-1/model/convert"
    11  	"github.com/koko1123/flow-go-1/model/verification"
    12  
    13  	"github.com/koko1123/flow-go-1/engine/execution/computation/computer"
    14  	executionState "github.com/koko1123/flow-go-1/engine/execution/state"
    15  	"github.com/koko1123/flow-go-1/engine/execution/state/delta"
    16  	"github.com/koko1123/flow-go-1/fvm"
    17  	"github.com/koko1123/flow-go-1/fvm/derived"
    18  	"github.com/koko1123/flow-go-1/ledger"
    19  	"github.com/koko1123/flow-go-1/ledger/partial"
    20  	chmodels "github.com/koko1123/flow-go-1/model/chunks"
    21  	"github.com/koko1123/flow-go-1/model/flow"
    22  )
    23  
    24  // ChunkVerifier is a verifier based on the current definitions of the flow network
    25  type ChunkVerifier struct {
    26  	vm             fvm.VM
    27  	vmCtx          fvm.Context
    28  	systemChunkCtx fvm.Context
    29  	logger         zerolog.Logger
    30  }
    31  
    32  // NewChunkVerifier creates a chunk verifier containing a flow virtual machine
    33  func NewChunkVerifier(vm fvm.VM, vmCtx fvm.Context, logger zerolog.Logger) *ChunkVerifier {
    34  	return &ChunkVerifier{
    35  		vm:             vm,
    36  		vmCtx:          vmCtx,
    37  		systemChunkCtx: computer.SystemChunkContext(vmCtx, vmCtx.Logger),
    38  		logger:         logger.With().Str("component", "chunk_verifier").Logger(),
    39  	}
    40  }
    41  
    42  // Verify verifies a given VerifiableChunk by executing it and checking the
    43  // final state commitment.
    44  // It returns a Spock Secret as a byte array, verification fault of the chunk,
    45  // and an error.
    46  func (fcv *ChunkVerifier) Verify(
    47  	vc *verification.VerifiableChunkData,
    48  ) (
    49  	[]byte,
    50  	chmodels.ChunkFault,
    51  	error,
    52  ) {
    53  
    54  	var ctx fvm.Context
    55  	var transactions []*fvm.TransactionProcedure
    56  	if vc.IsSystemChunk {
    57  		ctx = fvm.NewContextFromParent(
    58  			fcv.systemChunkCtx,
    59  			fvm.WithBlockHeader(vc.Header))
    60  
    61  		txBody, err := blueprints.SystemChunkTransaction(fcv.vmCtx.Chain)
    62  		if err != nil {
    63  			return nil, nil, fmt.Errorf("could not get system chunk transaction: %w", err)
    64  		}
    65  
    66  		transactions = []*fvm.TransactionProcedure{
    67  			fvm.Transaction(txBody, vc.TransactionOffset+uint32(0)),
    68  		}
    69  	} else {
    70  		ctx = fvm.NewContextFromParent(
    71  			fcv.vmCtx,
    72  			fvm.WithBlockHeader(vc.Header))
    73  
    74  		transactions = make(
    75  			[]*fvm.TransactionProcedure,
    76  			0,
    77  			len(vc.ChunkDataPack.Collection.Transactions))
    78  		for i, txBody := range vc.ChunkDataPack.Collection.Transactions {
    79  			tx := fvm.Transaction(txBody, vc.TransactionOffset+uint32(i))
    80  			transactions = append(transactions, tx)
    81  		}
    82  	}
    83  
    84  	return fcv.verifyTransactionsInContext(
    85  		ctx,
    86  		vc.TransactionOffset,
    87  		vc.Chunk,
    88  		vc.ChunkDataPack,
    89  		vc.Result,
    90  		transactions,
    91  		vc.EndState,
    92  		vc.IsSystemChunk)
    93  }
    94  
    95  func (fcv *ChunkVerifier) verifyTransactionsInContext(
    96  	context fvm.Context,
    97  	transactionOffset uint32,
    98  	chunk *flow.Chunk,
    99  	chunkDataPack *flow.ChunkDataPack,
   100  	result *flow.ExecutionResult,
   101  	transactions []*fvm.TransactionProcedure,
   102  	endState flow.StateCommitment,
   103  	systemChunk bool,
   104  ) (
   105  	[]byte,
   106  	chmodels.ChunkFault,
   107  	error,
   108  ) {
   109  
   110  	// TODO check collection hash to match
   111  	// TODO check datapack hash to match
   112  	// TODO check the number of transactions and computation used
   113  
   114  	chIndex := chunk.Index
   115  	execResID := result.ID()
   116  
   117  	if chunkDataPack == nil {
   118  		return nil, nil, fmt.Errorf("missing chunk data pack")
   119  	}
   120  
   121  	events := make(flow.EventsList, 0)
   122  	serviceEvents := make(flow.EventsList, 0)
   123  
   124  	// constructing a partial trie given chunk data package
   125  	psmt, err := partial.NewLedger(chunkDataPack.Proof, ledger.State(chunkDataPack.StartState), partial.DefaultPathFinderVersion)
   126  
   127  	if err != nil {
   128  		// TODO provide more details based on the error type
   129  		return nil, chmodels.NewCFInvalidVerifiableChunk("error constructing partial trie: ", err, chIndex, execResID),
   130  			nil
   131  	}
   132  
   133  	context = fvm.NewContextFromParent(
   134  		context,
   135  		fvm.WithDerivedBlockData(
   136  			derived.NewEmptyDerivedBlockDataWithTransactionOffset(
   137  				transactionOffset)))
   138  
   139  	// chunk view construction
   140  	// unknown register tracks access to parts of the partial trie which
   141  	// are not expanded and values are unknown.
   142  	unknownRegTouch := make(map[flow.RegisterID]*ledger.Key)
   143  	var problematicTx flow.Identifier
   144  	getRegister := func(owner, key string) (flow.RegisterValue, error) {
   145  		// check if register has been provided in the chunk data pack
   146  		registerID := flow.NewRegisterID(owner, key)
   147  
   148  		registerKey := executionState.RegisterIDToKey(registerID)
   149  
   150  		query, err := ledger.NewQuerySingleValue(ledger.State(chunkDataPack.StartState), registerKey)
   151  
   152  		if err != nil {
   153  			return nil, fmt.Errorf("cannot create query: %w", err)
   154  		}
   155  
   156  		value, err := psmt.GetSingleValue(query)
   157  		if err != nil {
   158  			if errors.Is(err, ledger.ErrMissingKeys{}) {
   159  
   160  				unknownRegTouch[registerID] = &registerKey
   161  
   162  				// don't send error just return empty byte slice
   163  				// we always assume empty value for missing registers (which might cause the transaction to fail)
   164  				// but after execution we check unknownRegTouch and if any
   165  				// register is inside it, code won't generate approvals and
   166  				// it activates a challenge
   167  
   168  				return []byte{}, nil
   169  			}
   170  			// append to missing keys if error is ErrMissingKeys
   171  
   172  			return nil, fmt.Errorf("cannot query register: %w", err)
   173  		}
   174  
   175  		return value, nil
   176  	}
   177  
   178  	chunkView := delta.NewView(getRegister)
   179  
   180  	// executes all transactions in this chunk
   181  	for i, tx := range transactions {
   182  		txView := chunkView.NewChild()
   183  
   184  		err := fcv.vm.Run(context, tx, txView)
   185  		if err != nil {
   186  			// this covers unexpected and very rare cases (e.g. system memory issues...),
   187  			// so we shouldn't be here even if transaction naturally fails (e.g. permission, runtime ... )
   188  			return nil, nil, fmt.Errorf("failed to execute transaction: %d (%w)", i, err)
   189  		}
   190  
   191  		if len(unknownRegTouch) > 0 {
   192  			problematicTx = tx.ID
   193  		}
   194  
   195  		events = append(events, tx.Events...)
   196  		serviceEvents = append(serviceEvents, tx.ServiceEvents...)
   197  
   198  		// always merge back the tx view (fvm is responsible for changes on tx errors)
   199  		err = chunkView.MergeView(txView)
   200  		if err != nil {
   201  			return nil, nil, fmt.Errorf("failed to execute transaction: %d (%w)", i, err)
   202  		}
   203  	}
   204  
   205  	// check read access to unknown registers
   206  	if len(unknownRegTouch) > 0 {
   207  		var missingRegs []string
   208  		for _, key := range unknownRegTouch {
   209  			missingRegs = append(missingRegs, key.String())
   210  		}
   211  		return nil, chmodels.NewCFMissingRegisterTouch(missingRegs, chIndex, execResID, problematicTx), nil
   212  	}
   213  
   214  	eventsHash, err := flow.EventsMerkleRootHash(events)
   215  	if err != nil {
   216  		return nil, nil, fmt.Errorf("cannot calculate events collection hash: %w", err)
   217  	}
   218  	if chunk.EventCollection != eventsHash {
   219  
   220  		for i, event := range events {
   221  
   222  			fcv.logger.Warn().Int("list_index", i).
   223  				Str("event_id", event.ID().String()).
   224  				Hex("event_fingerptint", event.Fingerprint()).
   225  				Str("event_type", string(event.Type)).
   226  				Str("event_tx_id", event.TransactionID.String()).
   227  				Uint32("event_tx_index", event.TransactionIndex).
   228  				Uint32("event_index", event.EventIndex).
   229  				Bytes("event_payload", event.Payload).
   230  				Str("block_id", chunk.BlockID.String()).
   231  				Str("collection_id", chunkDataPack.Collection.ID().String()).
   232  				Str("result_id", result.ID().String()).
   233  				Uint64("chunk_index", chunk.Index).
   234  				Msg("not matching events debug")
   235  		}
   236  
   237  		return nil, chmodels.NewCFInvalidEventsCollection(chunk.EventCollection, eventsHash, chIndex, execResID, events), nil
   238  	}
   239  
   240  	if systemChunk {
   241  
   242  		computedServiceEvents := make(flow.ServiceEventList, len(serviceEvents))
   243  
   244  		for i, serviceEvent := range serviceEvents {
   245  			realServiceEvent, err := convert.ServiceEvent(fcv.vmCtx.Chain.ChainID(), serviceEvent)
   246  			if err != nil {
   247  				return nil, nil, fmt.Errorf("cannot convert service event %d: %w", i, err)
   248  			}
   249  			computedServiceEvents[i] = *realServiceEvent
   250  		}
   251  
   252  		equal, err := result.ServiceEvents.EqualTo(computedServiceEvents)
   253  		if err != nil {
   254  			return nil, nil, fmt.Errorf("error while compariong service events: %w", err)
   255  		}
   256  		if !equal {
   257  			return nil, chmodels.CFInvalidServiceSystemEventsEmitted(result.ServiceEvents, computedServiceEvents, chIndex, execResID), nil
   258  		}
   259  	}
   260  
   261  	// applying chunk delta (register updates at chunk level) to the partial trie
   262  	// this returns the expected end state commitment after updates and the list of
   263  	// register keys that was not provided by the chunk data package (err).
   264  	regs, values := chunkView.Delta().RegisterUpdates()
   265  
   266  	update, err := ledger.NewUpdate(
   267  		ledger.State(chunkDataPack.StartState),
   268  		executionState.RegisterIDSToKeys(regs),
   269  		executionState.RegisterValuesToValues(values),
   270  	)
   271  	if err != nil {
   272  		return nil, nil, fmt.Errorf("cannot create ledger update: %w", err)
   273  	}
   274  
   275  	expEndStateComm, _, err := psmt.Set(update)
   276  
   277  	if err != nil {
   278  		if errors.Is(err, ledger.ErrMissingKeys{}) {
   279  			keys := err.(*ledger.ErrMissingKeys).Keys
   280  			stringKeys := make([]string, len(keys))
   281  			for i, key := range keys {
   282  				stringKeys[i] = key.String()
   283  			}
   284  			return nil, chmodels.NewCFMissingRegisterTouch(stringKeys, chIndex, execResID, problematicTx), nil
   285  		}
   286  		return nil, chmodels.NewCFMissingRegisterTouch(nil, chIndex, execResID, problematicTx), nil
   287  	}
   288  
   289  	// TODO check if exec node provided register touches that was not used (no read and no update)
   290  	// check if the end state commitment mentioned in the chunk matches
   291  	// what the partial trie is providing.
   292  	if flow.StateCommitment(expEndStateComm) != endState {
   293  		return nil, chmodels.NewCFNonMatchingFinalState(flow.StateCommitment(expEndStateComm), endState, chIndex, execResID), nil
   294  	}
   295  	return chunkView.SpockSecret(), nil, nil
   296  }