code: func (tx *transaction) FetchBlocks(hashes []chainhash.Hash) ([][]byte, error) {
  	// Ensure transaction state is valid.
  	if err := tx.checkClosed(); err != nil {
  		return nil, err
  	}
  
  	// NOTE: This could check for the existence of all blocks before loading
  	// any of them which would be faster in the failure case, however
  	// callers will not typically be calling this function with invalid
  	// values, so optimize for the common case.
  
  	// Load the blocks.
  	blocks := make([][]byte, len(hashes))
  	for i := range hashes {
  		var err error
  		blocks[i], err = tx.FetchBlock(&hashes[i])
  		if err != nil {
  			return nil, err
  		}
  	}
  
  	return blocks, nil
  }
docs: // FetchBlocks returns the raw serialized bytes for the blocks identified by the
  // given hashes.  The raw bytes are in the format returned by Serialize on a
  // wire.MsgBlock.
  //
  // Returns the following errors as required by the interface contract:
  //   - ErrBlockNotFound if any of the requested block hashed do not exist
  //   - ErrTxClosed if the transaction has already been closed
  //   - ErrCorruption if the database has somehow become corrupted
  //
  // In addition, returns ErrDriverSpecific if any failures occur when reading the
  // block files.
  //
  // NOTE: The data returned by this function is only valid during a database
  // transaction.  Attempting to access it after a transaction has ended results
  // in undefined behavior.  This constraint prevents additional data copies and
  // allows support for memory-mapped database implementations.
  //
  // This function is part of the database.Tx interface implementation.