2021-03-05 17:55:32 +00:00
|
|
|
package splitstore
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"sync"
|
2021-03-09 07:05:36 +00:00
|
|
|
"sync/atomic"
|
2021-03-05 17:55:32 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/filecoin-project/go-state-types/abi"
|
|
|
|
"github.com/filecoin-project/lotus/blockstore"
|
|
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
|
|
"github.com/filecoin-project/lotus/chain/types/mock"
|
|
|
|
|
2021-03-13 10:00:28 +00:00
|
|
|
blocks "github.com/ipfs/go-block-format"
|
2021-03-05 17:55:32 +00:00
|
|
|
datastore "github.com/ipfs/go-datastore"
|
2021-03-09 00:15:55 +00:00
|
|
|
dssync "github.com/ipfs/go-datastore/sync"
|
2021-03-05 17:55:32 +00:00
|
|
|
logging "github.com/ipfs/go-log/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
CompactionThreshold = 5
|
|
|
|
CompactionCold = 1
|
|
|
|
CompactionBoundary = 2
|
|
|
|
logging.SetLogLevel("splitstore", "DEBUG")
|
|
|
|
}
|
|
|
|
|
|
|
|
func testSplitStore(t *testing.T, cfg *Config) {
|
2021-04-29 02:55:18 +00:00
|
|
|
chain := &mockChain{t: t}
|
2021-03-05 17:55:32 +00:00
|
|
|
|
|
|
|
// the myriads of stores
|
2021-03-09 00:15:55 +00:00
|
|
|
ds := dssync.MutexWrap(datastore.NewMapDatastore())
|
2021-03-05 17:55:32 +00:00
|
|
|
hot := blockstore.NewMemorySync()
|
|
|
|
cold := blockstore.NewMemorySync()
|
|
|
|
|
2021-03-13 10:00:28 +00:00
|
|
|
// this is necessary to avoid the garbage mock puts in the blocks
|
|
|
|
garbage := blocks.NewBlock([]byte{1, 2, 3})
|
|
|
|
err := cold.Put(garbage)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// genesis
|
|
|
|
genBlock := mock.MkBlock(nil, 0, 0)
|
|
|
|
genBlock.Messages = garbage.Cid()
|
|
|
|
genBlock.ParentMessageReceipts = garbage.Cid()
|
|
|
|
genBlock.ParentStateRoot = garbage.Cid()
|
2021-03-14 10:32:05 +00:00
|
|
|
genBlock.Timestamp = uint64(time.Now().Unix())
|
2021-03-13 10:00:28 +00:00
|
|
|
|
|
|
|
genTs := mock.TipSet(genBlock)
|
|
|
|
chain.push(genTs)
|
|
|
|
|
2021-03-05 17:55:32 +00:00
|
|
|
// put the genesis block to cold store
|
|
|
|
blk, err := genBlock.ToStorageBlock()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = cold.Put(blk)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// open the splitstore
|
|
|
|
ss, err := Open("", ds, hot, cold, cfg)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2021-03-05 18:05:32 +00:00
|
|
|
defer ss.Close() //nolint
|
2021-03-05 17:55:32 +00:00
|
|
|
|
|
|
|
err = ss.Start(chain)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// make some tipsets, but not enough to cause compaction
|
|
|
|
mkBlock := func(curTs *types.TipSet, i int) *types.TipSet {
|
|
|
|
blk := mock.MkBlock(curTs, uint64(i), uint64(i))
|
2021-03-13 10:00:28 +00:00
|
|
|
|
|
|
|
blk.Messages = garbage.Cid()
|
|
|
|
blk.ParentMessageReceipts = garbage.Cid()
|
|
|
|
blk.ParentStateRoot = garbage.Cid()
|
2021-03-14 10:32:05 +00:00
|
|
|
blk.Timestamp = uint64(time.Now().Unix())
|
2021-03-13 10:00:28 +00:00
|
|
|
|
2021-03-05 17:55:32 +00:00
|
|
|
sblk, err := blk.ToStorageBlock()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
err = ss.Put(sblk)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
ts := mock.TipSet(blk)
|
|
|
|
chain.push(ts)
|
|
|
|
|
|
|
|
return ts
|
|
|
|
}
|
|
|
|
|
2021-03-09 07:05:36 +00:00
|
|
|
waitForCompaction := func() {
|
|
|
|
for atomic.LoadInt32(&ss.compacting) == 1 {
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-05 17:55:32 +00:00
|
|
|
curTs := genTs
|
|
|
|
for i := 1; i < 5; i++ {
|
|
|
|
curTs = mkBlock(curTs, i)
|
2021-03-09 07:05:36 +00:00
|
|
|
waitForCompaction()
|
2021-03-05 17:55:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// count objects in the cold and hot stores
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
countBlocks := func(bs blockstore.Blockstore) int {
|
|
|
|
count := 0
|
|
|
|
ch, err := bs.AllKeysChan(ctx)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2021-03-05 18:05:32 +00:00
|
|
|
for range ch {
|
2021-03-05 17:55:32 +00:00
|
|
|
count++
|
|
|
|
}
|
|
|
|
return count
|
|
|
|
}
|
|
|
|
|
|
|
|
coldCnt := countBlocks(cold)
|
|
|
|
hotCnt := countBlocks(hot)
|
|
|
|
|
2021-03-13 10:00:28 +00:00
|
|
|
if coldCnt != 2 {
|
|
|
|
t.Errorf("expected %d blocks, but got %d", 2, coldCnt)
|
2021-03-05 17:55:32 +00:00
|
|
|
}
|
|
|
|
|
2021-03-09 00:15:55 +00:00
|
|
|
if hotCnt != 5 {
|
|
|
|
t.Errorf("expected %d blocks, but got %d", 5, hotCnt)
|
2021-03-05 17:55:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// trigger a compaction
|
|
|
|
for i := 5; i < 10; i++ {
|
|
|
|
curTs = mkBlock(curTs, i)
|
2021-03-09 07:05:36 +00:00
|
|
|
waitForCompaction()
|
2021-03-05 17:55:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
coldCnt = countBlocks(cold)
|
|
|
|
hotCnt = countBlocks(hot)
|
|
|
|
|
2021-03-13 10:00:28 +00:00
|
|
|
if coldCnt != 7 {
|
|
|
|
t.Errorf("expected %d cold blocks, but got %d", 7, coldCnt)
|
2021-03-05 17:55:32 +00:00
|
|
|
}
|
|
|
|
|
2021-03-13 10:00:28 +00:00
|
|
|
if hotCnt != 4 {
|
|
|
|
t.Errorf("expected %d hot blocks, but got %d", 4, hotCnt)
|
2021-03-05 17:55:32 +00:00
|
|
|
}
|
2021-04-29 02:55:18 +00:00
|
|
|
|
2021-04-29 05:08:37 +00:00
|
|
|
// Make sure we can revert without panicking.
|
2021-04-29 02:55:18 +00:00
|
|
|
chain.revert(2)
|
2021-03-05 17:55:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestSplitStoreSimpleCompaction(t *testing.T) {
|
|
|
|
testSplitStore(t, &Config{TrackingStoreType: "mem"})
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockChain struct {
|
2021-04-29 02:55:18 +00:00
|
|
|
t testing.TB
|
|
|
|
|
2021-03-05 17:55:32 +00:00
|
|
|
sync.Mutex
|
|
|
|
tipsets []*types.TipSet
|
|
|
|
listener func(revert []*types.TipSet, apply []*types.TipSet) error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *mockChain) push(ts *types.TipSet) {
|
|
|
|
c.Lock()
|
|
|
|
c.tipsets = append(c.tipsets, ts)
|
|
|
|
c.Unlock()
|
|
|
|
|
|
|
|
if c.listener != nil {
|
|
|
|
err := c.listener(nil, []*types.TipSet{ts})
|
|
|
|
if err != nil {
|
2021-04-29 02:55:18 +00:00
|
|
|
c.t.Errorf("mockchain: error dispatching listener: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *mockChain) revert(count int) {
|
|
|
|
c.Lock()
|
|
|
|
revert := make([]*types.TipSet, count)
|
|
|
|
if count > len(c.tipsets) {
|
|
|
|
c.Unlock()
|
|
|
|
c.t.Fatalf("not enough tipsets to revert")
|
|
|
|
}
|
|
|
|
copy(revert, c.tipsets[len(c.tipsets)-count:])
|
|
|
|
c.tipsets = c.tipsets[:len(c.tipsets)-count]
|
|
|
|
c.Unlock()
|
|
|
|
|
|
|
|
if c.listener != nil {
|
|
|
|
err := c.listener(revert, nil)
|
|
|
|
if err != nil {
|
|
|
|
c.t.Errorf("mockchain: error dispatching listener: %s", err)
|
2021-03-05 17:55:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *mockChain) GetTipsetByHeight(_ context.Context, epoch abi.ChainEpoch, _ *types.TipSet, _ bool) (*types.TipSet, error) {
|
|
|
|
c.Lock()
|
|
|
|
defer c.Unlock()
|
|
|
|
|
|
|
|
iEpoch := int(epoch)
|
|
|
|
if iEpoch > len(c.tipsets) {
|
|
|
|
return nil, fmt.Errorf("bad epoch %d", epoch)
|
|
|
|
}
|
|
|
|
|
2021-03-13 10:00:28 +00:00
|
|
|
return c.tipsets[iEpoch], nil
|
2021-03-05 17:55:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *mockChain) GetHeaviestTipSet() *types.TipSet {
|
|
|
|
c.Lock()
|
|
|
|
defer c.Unlock()
|
|
|
|
|
|
|
|
return c.tipsets[len(c.tipsets)-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *mockChain) SubscribeHeadChanges(change func(revert []*types.TipSet, apply []*types.TipSet) error) {
|
|
|
|
c.listener = change
|
|
|
|
}
|