4
4
"bytes"
5
5
"context"
6
6
"crypto/sha256"
7
+ "encoding/binary"
7
8
"encoding/hex"
8
9
"fmt"
9
10
"math/rand"
@@ -15,6 +16,8 @@ import (
15
16
block "github.com/ipfs/go-block-format"
16
17
cid "github.com/ipfs/go-cid"
17
18
cbor "github.com/ipfs/go-ipld-cbor"
19
+ "github.com/stretchr/testify/assert"
20
+ "github.com/stretchr/testify/require"
18
21
cbg "github.com/whyrusleeping/cbor-gen"
19
22
)
20
23
@@ -1236,3 +1239,72 @@ func TestMalformedHamt(t *testing.T) {
1236
1239
t .Fatal ("Should have returned found entry" )
1237
1240
}
1238
1241
}
1242
+
1243
+ func TestCleanChildOrdering (t * testing.T ) {
1244
+ // This test originates from a case hit while testing filecoin-project/specs-actors.
1245
+ // TODO a HAMT exercising this case can very likely be constructed with many fewer
1246
+ // operations. I'm duplicating the full original test for expedience
1247
+ //
1248
+ // The important part of this HAMT is that at some point child with index 20 looks like:
1249
+ // P0 -- []KV{KV{Key: 0x01a0, Value: v0}}
1250
+ // P1 -- []KV{KV{Key: 0x01a8, Value: v1}}
1251
+ // P2 -- []KV{KV{Key: 0x0181, Value: v2}}
1252
+ // P3 -- []KV{KV{Key: 0x006c, Value: v2}}
1253
+ //
1254
+ // We then delete 0x006c. This forces this child node into a bucket.
1255
+ // before writing this test cleanChild did not explicitly sort KVs from
1256
+ // all pointers, so the new bucket looked like:
1257
+ // []KV{
1258
+ // KV{Key: 0x01a0, Value: v1},
1259
+ // KV{Key: 0x01a8, Value: v2},
1260
+ // KV{Key: 0x0181, Value: v2},
1261
+ // }
1262
+ //
1263
+ // This violated the buckets-are-sorted-by-key condition
1264
+
1265
+ // Construct HAMT
1266
+ makeKey := func (i uint64 ) string {
1267
+ buf := make ([]byte , 10 )
1268
+ n := binary .PutUvarint (buf , i )
1269
+ return string (buf [:n ])
1270
+ }
1271
+ dummyValue := []byte {0xaa , 0xbb , 0xcc , 0xdd }
1272
+
1273
+ ctx := context .Background ()
1274
+ cs := cbor .NewCborStore (newMockBlocks ())
1275
+ hamtOptions := []Option {
1276
+ UseTreeBitWidth (5 ),
1277
+ UseHashFunction (func (input []byte ) []byte {
1278
+ res := sha256 .Sum256 (input )
1279
+ return res [:]
1280
+ }),
1281
+ }
1282
+
1283
+ h := NewNode (cs , hamtOptions ... )
1284
+
1285
+ for i := uint64 (100 ); i < uint64 (195 ); i ++ {
1286
+ err := h .Set (ctx , makeKey (i ), dummyValue )
1287
+ require .NoError (t , err )
1288
+ }
1289
+
1290
+ // Shouldn't matter but repeating original case exactly
1291
+ require .NoError (t , h .Flush (ctx ))
1292
+ root , err := cs .Put (ctx , h )
1293
+ require .NoError (t , err )
1294
+ h , err = LoadNode (ctx , cs , root , hamtOptions ... )
1295
+ require .NoError (t , err )
1296
+
1297
+ // Delete key 104 so child indexed at 20 has four pointers
1298
+ err = h .Delete (ctx , makeKey (104 ))
1299
+ assert .NoError (t , err )
1300
+ err = h .Delete (ctx , makeKey (108 ))
1301
+ assert .NoError (t , err )
1302
+ err = h .Flush (ctx )
1303
+ assert .NoError (t , err )
1304
+ root , err = cs .Put (ctx , h )
1305
+
1306
+ // Reload without error
1307
+ require .NoError (t , err )
1308
+ h , err = LoadNode (ctx , cs , root , hamtOptions ... )
1309
+ assert .NoError (t , err )
1310
+ }
0 commit comments