From a6dafed48610bbb58a4a6fe5058b286466461676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 23 Sep 2019 17:27:30 +0200 Subject: [PATCH] tests: Basic mining integration test --- api/test/mining.go | 31 +++++++++ api/test/test.go | 25 ++++++-- miner/miner.go | 27 ++++---- miner/testminer.go | 28 ++++++++ node/node_test.go | 156 +++++++++++++++++++++++++++++++++++++++------ 5 files changed, 233 insertions(+), 34 deletions(-) create mode 100644 api/test/mining.go create mode 100644 miner/testminer.go diff --git a/api/test/mining.go b/api/test/mining.go new file mode 100644 index 000000000..7e7f639c1 --- /dev/null +++ b/api/test/mining.go @@ -0,0 +1,31 @@ +package test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +func (ts *testSuite) testMining(t *testing.T) { + ctx := context.Background() + apis, _ := ts.makeNodes(t, 1, []int{0}) + api := apis[0] + + h1, err := api.ChainHead(ctx) + require.NoError(t, err) + require.Equal(t, uint64(0), h1.Height()) + + newHeads, err := api.ChainNotify(ctx) + require.NoError(t, err) + <-newHeads + + err = api.MineOne(ctx) + require.NoError(t, err) + + <-newHeads + + h2, err := api.ChainHead(ctx) + require.NoError(t, err) + require.Equal(t, uint64(1), h2.Height()) +} diff --git a/api/test/test.go b/api/test/test.go index 714063bf7..d60659a00 100644 --- a/api/test/test.go +++ b/api/test/test.go @@ -9,9 +9,22 @@ import ( "github.com/stretchr/testify/assert" ) +type TestNode struct { + api.FullNode + + MineOne func(context.Context) error +} + +type TestStorageNode struct { + api.StorageMiner +} + // APIBuilder is a function which is invoked in test suite to provide // test nodes and networks -type APIBuilder func(t *testing.T, n int) []api.FullNode +// +// storage array defines storage nodes, numbers in the array specify full node +// index the storage node 'belongs' to +type APIBuilder func(t *testing.T, nFull int, storage []int) ([]TestNode, []TestStorageNode) type testSuite struct { makeNodes APIBuilder } @@ -25,12 +38,13 @@ func TestApis(t *testing.T, b APIBuilder) { t.Run("version", ts.testVersion) t.Run("id", ts.testID) t.Run("testConnectTwo", ts.testConnectTwo) - + t.Run("testMining", ts.testMining) } func (ts *testSuite) testVersion(t *testing.T) { ctx := context.Background() - api := ts.makeNodes(t, 1)[0] + apis, _ := ts.makeNodes(t, 1, []int{}) + api := apis[0] v, err := api.Version(ctx) if err != nil { @@ -43,7 +57,8 @@ func (ts *testSuite) testVersion(t *testing.T) { func (ts *testSuite) testID(t *testing.T) { ctx := context.Background() - api := ts.makeNodes(t, 1)[0] + apis, _ := ts.makeNodes(t, 1, []int{}) + api := apis[0] id, err := api.ID(ctx) if err != nil { @@ -54,7 +69,7 @@ func (ts *testSuite) testID(t *testing.T) { func (ts *testSuite) testConnectTwo(t *testing.T) { ctx := context.Background() - apis := ts.makeNodes(t, 2) + apis, _ := ts.makeNodes(t, 2, []int{}) p, err := apis[0].NetPeers(ctx) if err != nil { diff --git a/miner/miner.go b/miner/miner.go index b9e1c04b1..bd659d496 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -22,6 +22,8 @@ import ( var log = logging.Logger("miner") +type vdfFunc func(ctx context.Context, input []byte) ([]byte, []byte, error) + type api struct { fx.In @@ -33,8 +35,10 @@ type api struct { func NewMiner(api api) *Miner { return &Miner{ - api: api, - Delay: build.BlockDelay * time.Second, + api: api, + + // time between blocks, network parameter + runVDF: delayVDF(build.BlockDelay * time.Second), } } @@ -46,8 +50,7 @@ type Miner struct { stop chan struct{} stopping chan struct{} - // time between blocks, network parameter - Delay time.Duration + runVDF vdfFunc lastWork *MiningBase } @@ -252,14 +255,16 @@ func (m *Miner) getMinerWorker(ctx context.Context, addr address.Address, ts *ty return w, nil } -func (m *Miner) runVDF(ctx context.Context, input []byte) ([]byte, []byte, error) { - select { - case <-ctx.Done(): - return nil, nil, ctx.Err() - case <-time.After(m.Delay): - } +func delayVDF(delay time.Duration) func(ctx context.Context, input []byte) ([]byte, []byte, error) { + return func(ctx context.Context, input []byte) ([]byte, []byte, error) { + select { + case <-ctx.Done(): + return nil, nil, ctx.Err() + case <-time.After(delay): + } - return vdf.Run(input) + return vdf.Run(input) + } } func (m *Miner) scratchTicket(ctx context.Context, base *MiningBase) (*types.Ticket, error) { diff --git a/miner/testminer.go b/miner/testminer.go new file mode 100644 index 000000000..241f9ffa8 --- /dev/null +++ b/miner/testminer.go @@ -0,0 +1,28 @@ +package miner + +import ( + "context" + + "github.com/filecoin-project/go-lotus/lib/vdf" +) + +func NewTestMiner(nextCh <-chan struct{}) func(api api) *Miner { + return func(api api) *Miner { + return &Miner{ + api: api, + runVDF: chanVDF(nextCh), + } + } +} + +func chanVDF(next <-chan struct{}) func(ctx context.Context, input []byte) ([]byte, []byte, error) { + return func(ctx context.Context, input []byte) ([]byte, []byte, error) { + select { + case <-ctx.Done(): + return nil, nil, ctx.Err() + case <-next: + } + + return vdf.Run(input) + } +} diff --git a/node/node_test.go b/node/node_test.go index 72d577145..57870052b 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -3,31 +3,105 @@ package node_test import ( "bytes" "context" + "io/ioutil" "net/http/httptest" + "os" "testing" + "github.com/ipfs/go-datastore" + "github.com/libp2p/go-libp2p-core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - - "github.com/filecoin-project/go-lotus/node" - "github.com/filecoin-project/go-lotus/node/modules" - modtest "github.com/filecoin-project/go-lotus/node/modules/testing" - "github.com/filecoin-project/go-lotus/node/repo" + "github.com/stretchr/testify/require" "github.com/filecoin-project/go-lotus/api" "github.com/filecoin-project/go-lotus/api/client" "github.com/filecoin-project/go-lotus/api/test" + "github.com/filecoin-project/go-lotus/chain/actors" + "github.com/filecoin-project/go-lotus/chain/address" + "github.com/filecoin-project/go-lotus/chain/types" "github.com/filecoin-project/go-lotus/lib/jsonrpc" + "github.com/filecoin-project/go-lotus/lib/sectorbuilder" + "github.com/filecoin-project/go-lotus/miner" + "github.com/filecoin-project/go-lotus/node" + "github.com/filecoin-project/go-lotus/node/modules" + modtest "github.com/filecoin-project/go-lotus/node/modules/testing" + "github.com/filecoin-project/go-lotus/node/repo" ) -func builder(t *testing.T, n int) []api.FullNode { +func testStorageNode(ctx context.Context, t *testing.T, waddr address.Address, act address.Address, tnd test.TestNode) test.TestStorageNode { + r := repo.NewMemory(nil) + + lr, err := r.Lock() + require.NoError(t, err) + + p2pSk, err := lr.Libp2pIdentity() + require.NoError(t, err) + + ds, err := lr.Datastore("/metadata") + require.NoError(t, err) + err = ds.Put(datastore.NewKey("miner-address"), act.Bytes()) + require.NoError(t, err) + + err = lr.Close() + require.NoError(t, err) + + peerid, err := peer.IDFromPrivateKey(p2pSk) + require.NoError(t, err) + + enc, err := actors.SerializeParams(&actors.UpdatePeerIDParams{PeerID: peerid}) + require.NoError(t, err) + + msg := &types.Message{ + To: act, + From: waddr, + Method: actors.MAMethods.UpdatePeerID, + Params: enc, + Value: types.NewInt(0), + GasPrice: types.NewInt(0), + GasLimit: types.NewInt(1000000), + } + + _, err = tnd.MpoolPushMessage(ctx, msg) + require.NoError(t, err) + + // start node + + secbpath, err := ioutil.TempDir(os.TempDir(), "lotust-stortest-sb-") + require.NoError(t, err) + + var minerapi api.StorageMiner + + // TODO: use stop + _, err = node.New(ctx, + node.StorageMiner(&minerapi), + node.Online(), + node.Repo(r), + + node.Override(new(*sectorbuilder.SectorBuilderConfig), modules.SectorBuilderConfig(secbpath)), + node.Override(new(api.FullNode), tnd), + ) + require.NoError(t, err) + + /*// Bootstrap with full node + remoteAddrs, err := tnd.NetAddrsListen(ctx) + require.NoError(t, err) + + err = minerapi.NetConnect(ctx, remoteAddrs) + require.NoError(t, err)*/ + + return test.TestStorageNode{minerapi} +} + +func builder(t *testing.T, nFull int, storage []int) ([]test.TestNode, []test.TestStorageNode) { ctx := context.Background() mn := mocknet.New(ctx) - out := make([]api.FullNode, n) + fulls := make([]test.TestNode, nFull) + storers := make([]test.TestStorageNode, len(storage)) var genbuf bytes.Buffer - for i := 0; i < n; i++ { + for i := 0; i < nFull; i++ { var genesis node.Option if i == 0 { genesis = node.Override(new(modules.Genesis), modtest.MakeGenesisMem(&genbuf)) @@ -35,50 +109,96 @@ func builder(t *testing.T, n int) []api.FullNode { genesis = node.Override(new(modules.Genesis), modules.LoadGenesis(genbuf.Bytes())) } + mineBlock := make(chan struct{}) + var err error // TODO: Don't ignore stop _, err = node.New(ctx, - node.FullAPI(&out[i]), + node.FullAPI(&fulls[i].FullNode), node.Online(), node.Repo(repo.NewMemory(nil)), node.MockHost(mn), + node.Override(new(*miner.Miner), miner.NewTestMiner(mineBlock)), + genesis, ) if err != nil { t.Fatal(err) } + + fulls[i].MineOne = func(ctx context.Context) error { + select { + case mineBlock <- struct{}{}: + return nil + case <-ctx.Done(): + return ctx.Err() + } + } + } + + for i, full := range storage { + // TODO: support non-bootstrap miners + if i != 0 { + t.Fatal("only one storage node supported") + } + if full != 0 { + t.Fatal("storage nodes only supported on the first full node") + } + + f := fulls[full] + + wa, err := f.WalletDefaultAddress(ctx) + require.NoError(t, err) + + genMiner, err := address.NewFromString("t0101") + require.NoError(t, err) + + storers[i] = testStorageNode(ctx, t, wa, genMiner, f) } if err := mn.LinkAll(); err != nil { t.Fatal(err) } - return out + return fulls, storers } func TestAPI(t *testing.T) { test.TestApis(t, builder) } -var nextApi int +func rpcBuilder(t *testing.T, nFull int, storage []int) ([]test.TestNode, []test.TestStorageNode) { + fullApis, storaApis := builder(t, nFull, storage) + fulls := make([]test.TestNode, nFull) + storers := make([]test.TestStorageNode, len(storage)) -func rpcBuilder(t *testing.T, n int) []api.FullNode { - nodeApis := builder(t, n) - out := make([]api.FullNode, n) - - for i, a := range nodeApis { + for i, a := range fullApis { rpcServer := jsonrpc.NewServer() rpcServer.Register("Filecoin", a) testServ := httptest.NewServer(rpcServer) // todo: close var err error - out[i], err = client.NewFullNodeRPC("ws://"+testServ.Listener.Addr().String(), nil) + fulls[i].FullNode, err = client.NewFullNodeRPC("ws://"+testServ.Listener.Addr().String(), nil) + if err != nil { + t.Fatal(err) + } + fulls[i].MineOne = a.MineOne + } + + for i, a := range storaApis { + rpcServer := jsonrpc.NewServer() + rpcServer.Register("Filecoin", a) + testServ := httptest.NewServer(rpcServer) // todo: close + + var err error + storers[i].StorageMiner, err = client.NewStorageMinerRPC("ws://"+testServ.Listener.Addr().String(), nil) if err != nil { t.Fatal(err) } } - return out + + return fulls, storers } func TestAPIRPC(t *testing.T) {