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 }