github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/u2u/genesisstore/fileshash/filehash_test.go (about)

     1  package fileshash
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"math/rand"
     9  	"os"
    10  	"path"
    11  	"testing"
    12  
    13  	"github.com/stretchr/testify/require"
    14  	"github.com/unicornultrafoundation/go-helios/hash"
    15  
    16  	"github.com/unicornultrafoundation/go-u2u/utils/ioread"
    17  )
    18  
    19  type dropableFile struct {
    20  	io.ReadWriteSeeker
    21  	io.Closer
    22  	path string
    23  }
    24  
    25  func (f dropableFile) Drop() error {
    26  	return os.Remove(f.path)
    27  }
    28  
    29  func TestFileHash_ReadWrite(t *testing.T) {
    30  
    31  	const (
    32  		FILE_CONTENT = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
    33  			Nunc finibus ultricies interdum. Nulla porttitor arcu a tincidunt mollis. Aliquam erat volutpat. 
    34  			Maecenas eget ligula mi. Maecenas in ligula non elit fringilla consequat. 
    35  			Morbi non imperdiet odio. Integer viverra ligula a varius tempor. 
    36  			Duis ac velit vel augue faucibus tincidunt ut ac nisl. Nulla sed magna est. 
    37  			Etiam quis nunc in elit ultricies pulvinar sed at felis. 
    38  			Suspendisse fringilla lectus vel est hendrerit pulvinar. 
    39  			Vivamus nec lorem pharetra ligula pulvinar blandit in quis nunc. 
    40  			Cras id eros fermentum mauris tristique faucibus. 
    41  			Praesent vehicula lectus nec ipsum sollicitudin tempus. Nullam et massa velit.`
    42  	)
    43  	t.Run("Large file, pieceSize=10000", func(t *testing.T) {
    44  		testFileHash_ReadWrite(t, bytes.Repeat([]byte(FILE_CONTENT), 20), hash.HexToHash("0xe3c3075749531525b472f4d6d70578e6d497d3e75b0727fea1ee10bdd1fcd490"), 10000)
    45  	})
    46  	t.Run("Large file, pieceSize=100", func(t *testing.T) {
    47  		testFileHash_ReadWrite(t, bytes.Repeat([]byte(FILE_CONTENT), 20), hash.HexToHash("0xdc6d882fde82b2dd44884a97884d79be40e1d0f780a493dee0f7256d8261f7a5"), 100)
    48  	})
    49  	t.Run("Medium file, pieceSize=1", func(t *testing.T) {
    50  		testFileHash_ReadWrite(t, bytes.Repeat([]byte(FILE_CONTENT), 1), hash.HexToHash("0x63a76929ee27decd5100d07a3cb626c05df1f1e927c5f27fa17c62459685ca6f"), 1)
    51  	})
    52  	t.Run("Medium file, pieceSize=2", func(t *testing.T) {
    53  		testFileHash_ReadWrite(t, bytes.Repeat([]byte(FILE_CONTENT), 1), hash.HexToHash("0x2babd1049c449a60da62a1a0a3dc836e6a222dece07dcba1890a041d60ff29c7"), 2)
    54  	})
    55  	t.Run("Medium file, pieceSize=100", func(t *testing.T) {
    56  		testFileHash_ReadWrite(t, bytes.Repeat([]byte(FILE_CONTENT), 1), hash.HexToHash("0x15c45aba675b7c49f5def32b4f24e827d478e5dfd712613fd6a5df69a2793b60"), 100)
    57  	})
    58  	t.Run("Tiny file, pieceSize=1", func(t *testing.T) {
    59  		testFileHash_ReadWrite(t, []byte{0}, hash.HexToHash("0xbdda25ac486f2b8c0330a6fcace8f3d05d3e713b7920a39a1f60c0d8df024c0e"), 1)
    60  	})
    61  	t.Run("Tiny file, pieceSize=2", func(t *testing.T) {
    62  		testFileHash_ReadWrite(t, []byte{0}, hash.HexToHash("0xf2b22424f7d1d01467d650f18ad49df8929fbefdeefe95e868d52eec6ea399e1"), 2)
    63  	})
    64  	t.Run("Empty file, pieceSize=1", func(t *testing.T) {
    65  		testFileHash_ReadWrite(t, []byte{}, hash.HexToHash("0x9cbc73d18d70c94fe366e696035c4f2cffdbab7ea6d6c2c039ca185f9c9f2746"), 1)
    66  	})
    67  	t.Run("Empty file, pieceSize=2", func(t *testing.T) {
    68  		testFileHash_ReadWrite(t, []byte{}, hash.HexToHash("0x163e7f66d58036ccb1d0b0058d8f46e7cd639816f570e5eb32853ea73634e4cd"), 2)
    69  	})
    70  }
    71  
    72  func testFileHash_ReadWrite(t *testing.T, content []byte, expRoot hash.Hash, pieceSize uint64) {
    73  	require := require.New(t)
    74  	tmpDirPath, err := ioutil.TempDir("", "filehash*")
    75  	defer os.RemoveAll(tmpDirPath)
    76  	require.NoError(err)
    77  	f, err := ioutil.TempFile(tmpDirPath, "testnet.g")
    78  	filePath := f.Name()
    79  	require.NoError(err)
    80  	writer := WrapWriter(f, pieceSize, func(i int) TmpWriter {
    81  		tmpFh, err := os.OpenFile(path.Join(tmpDirPath, fmt.Sprintf("genesis%d.dat", i)), os.O_CREATE|os.O_RDWR, os.ModePerm)
    82  		require.NoError(err)
    83  		return dropableFile{
    84  			ReadWriteSeeker: tmpFh,
    85  			Closer:          tmpFh,
    86  			path:            tmpFh.Name(),
    87  		}
    88  	})
    89  
    90  	// write out the (secure) self-hashed file properly
    91  	_, err = writer.Write(content)
    92  	require.NoError(err)
    93  	root, err := writer.Flush()
    94  	require.NoError(err)
    95  	require.Equal(expRoot.Hex(), root.Hex())
    96  	f.Close()
    97  
    98  	maxMemUsage := memUsageOf(pieceSize, getPiecesNum(uint64(len(content)), pieceSize))
    99  
   100  	// normal case: correct root hash and content after reading file partially
   101  	if len(content) > 0 {
   102  		f, err = os.OpenFile(filePath, os.O_RDONLY, 0600)
   103  		require.NoError(err)
   104  		reader := WrapReader(f, maxMemUsage, root)
   105  		readB := make([]byte, rand.Int63n(int64(len(content))))
   106  		err = ioread.ReadAll(reader, readB)
   107  		require.NoError(err)
   108  		require.Equal(content[:len(readB)], readB)
   109  		reader.Close()
   110  	}
   111  
   112  	// normal case: correct root hash and content after reading the whole file
   113  	{
   114  		f, err = os.OpenFile(filePath, os.O_RDONLY, 0600)
   115  		require.NoError(err)
   116  		reader := WrapReader(f, maxMemUsage, root)
   117  		readB := make([]byte, len(content))
   118  		err = ioread.ReadAll(reader, readB)
   119  		require.NoError(err)
   120  		require.Equal(content, readB)
   121  		// try to read one more byte
   122  		require.Error(ioread.ReadAll(reader, make([]byte, 1)), io.EOF)
   123  		reader.Close()
   124  	}
   125  
   126  	// correct root hash and reading too much content
   127  	{
   128  		f, err = os.OpenFile(filePath, os.O_RDONLY, 0600)
   129  		require.NoError(err)
   130  		reader := WrapReader(f, maxMemUsage, root)
   131  		readB := make([]byte, len(content)+1)
   132  		require.Error(ioread.ReadAll(reader, readB), io.EOF)
   133  		reader.Close()
   134  	}
   135  
   136  	// passing the wrong root hash to reader
   137  	{
   138  		f, err = os.OpenFile(filePath, os.O_RDONLY, 0600)
   139  		require.NoError(err)
   140  		maliciousReader := WrapReader(f, maxMemUsage, hash.HexToHash("0x00"))
   141  		data := make([]byte, 1)
   142  		err = ioread.ReadAll(maliciousReader, data)
   143  		require.Contains(err.Error(), ErrInit.Error())
   144  		maliciousReader.Close()
   145  	}
   146  
   147  	// modify piece data to make the mismatch piece hash
   148  	headerOffset := 4 + 8 + getPiecesNum(uint64(len(content)), pieceSize)*32
   149  	if len(content) > 0 {
   150  		// mutate content byte
   151  		f, err = os.OpenFile(filePath, os.O_RDWR, 0600)
   152  		require.NoError(err)
   153  		s := []byte{0}
   154  		contentPos := rand.Int63n(int64(len(content)))
   155  		pos := int64(headerOffset) + contentPos
   156  		_, err = f.ReadAt(s, pos)
   157  		require.NoError(err)
   158  		s[0]++
   159  		_, err = f.WriteAt(s, pos)
   160  		require.NoError(err)
   161  		require.NoError(f.Close())
   162  
   163  		// try to read
   164  		f, err = os.OpenFile(filePath, os.O_RDONLY, 0600)
   165  		maliciousReader := WrapReader(f, maxMemUsage, root)
   166  		data := make([]byte, contentPos+1)
   167  		err = ioread.ReadAll(maliciousReader, data)
   168  		require.Contains(err.Error(), ErrHashMismatch.Error())
   169  		require.NoError(maliciousReader.Close())
   170  
   171  		// restore
   172  		s[0]--
   173  		f, err = os.OpenFile(filePath, os.O_RDWR, 0600)
   174  		require.NoError(err)
   175  		_, err = f.WriteAt(s, pos)
   176  		require.NoError(err)
   177  		require.NoError(f.Close())
   178  	}
   179  
   180  	// modify a piece hash in file to make the wrong one
   181  	{
   182  		// mutate content byte
   183  		f, err = os.OpenFile(filePath, os.O_RDWR, 0600)
   184  		require.NoError(err)
   185  		pos := rand.Int63n(int64(headerOffset))
   186  		s := []byte{0}
   187  		_, err = f.ReadAt(s, pos)
   188  		require.NoError(err)
   189  		s[0]++
   190  		_, err = f.WriteAt(s, pos)
   191  		require.NoError(err)
   192  		require.NoError(f.Close())
   193  
   194  		// try to read
   195  		f, err = os.OpenFile(filePath, os.O_RDONLY, 0600)
   196  		maliciousReader := WrapReader(f, maxMemUsage*2, root)
   197  		data := make([]byte, 1)
   198  		err = ioread.ReadAll(maliciousReader, data)
   199  		require.Contains(err.Error(), ErrInit.Error())
   200  		require.NoError(maliciousReader.Close())
   201  
   202  		// restore
   203  		s[0]--
   204  		f, err = os.OpenFile(filePath, os.O_RDWR, 0600)
   205  		require.NoError(err)
   206  		_, err = f.WriteAt(s, pos)
   207  		require.NoError(err)
   208  		require.NoError(f.Close())
   209  	}
   210  
   211  	// hashed file requires too much memory
   212  	{
   213  		f, err = os.OpenFile(filePath, os.O_WRONLY, 0600)
   214  		require.NoError(err)
   215  		oomReader := WrapReader(f, maxMemUsage-1, root)
   216  		data := make([]byte, 1)
   217  		err = ioread.ReadAll(oomReader, data)
   218  		require.Errorf(err, "hashed file requires too much memory")
   219  	}
   220  }