Merge pull request #227 from filecoin-project/feat/mining-integration-test

tests: Basic mining integration test
This commit is contained in:
Łukasz Magiera 2019-09-23 20:49:19 +02:00 committed by GitHub
commit a21846aa6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 233 additions and 34 deletions

31
api/test/mining.go Normal file
View File

@ -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())
}

View File

@ -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 {

View File

@ -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) {

28
miner/testminer.go Normal file
View File

@ -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)
}
}

View File

@ -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) {