Merge pull request #227 from filecoin-project/feat/mining-integration-test
tests: Basic mining integration test
This commit is contained in:
commit
a21846aa6e
31
api/test/mining.go
Normal file
31
api/test/mining.go
Normal 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())
|
||||||
|
}
|
@ -9,9 +9,22 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"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
|
// APIBuilder is a function which is invoked in test suite to provide
|
||||||
// test nodes and networks
|
// 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 {
|
type testSuite struct {
|
||||||
makeNodes APIBuilder
|
makeNodes APIBuilder
|
||||||
}
|
}
|
||||||
@ -25,12 +38,13 @@ func TestApis(t *testing.T, b APIBuilder) {
|
|||||||
t.Run("version", ts.testVersion)
|
t.Run("version", ts.testVersion)
|
||||||
t.Run("id", ts.testID)
|
t.Run("id", ts.testID)
|
||||||
t.Run("testConnectTwo", ts.testConnectTwo)
|
t.Run("testConnectTwo", ts.testConnectTwo)
|
||||||
|
t.Run("testMining", ts.testMining)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts *testSuite) testVersion(t *testing.T) {
|
func (ts *testSuite) testVersion(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
api := ts.makeNodes(t, 1)[0]
|
apis, _ := ts.makeNodes(t, 1, []int{})
|
||||||
|
api := apis[0]
|
||||||
|
|
||||||
v, err := api.Version(ctx)
|
v, err := api.Version(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -43,7 +57,8 @@ func (ts *testSuite) testVersion(t *testing.T) {
|
|||||||
|
|
||||||
func (ts *testSuite) testID(t *testing.T) {
|
func (ts *testSuite) testID(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
api := ts.makeNodes(t, 1)[0]
|
apis, _ := ts.makeNodes(t, 1, []int{})
|
||||||
|
api := apis[0]
|
||||||
|
|
||||||
id, err := api.ID(ctx)
|
id, err := api.ID(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -54,7 +69,7 @@ func (ts *testSuite) testID(t *testing.T) {
|
|||||||
|
|
||||||
func (ts *testSuite) testConnectTwo(t *testing.T) {
|
func (ts *testSuite) testConnectTwo(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
apis := ts.makeNodes(t, 2)
|
apis, _ := ts.makeNodes(t, 2, []int{})
|
||||||
|
|
||||||
p, err := apis[0].NetPeers(ctx)
|
p, err := apis[0].NetPeers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -22,6 +22,8 @@ import (
|
|||||||
|
|
||||||
var log = logging.Logger("miner")
|
var log = logging.Logger("miner")
|
||||||
|
|
||||||
|
type vdfFunc func(ctx context.Context, input []byte) ([]byte, []byte, error)
|
||||||
|
|
||||||
type api struct {
|
type api struct {
|
||||||
fx.In
|
fx.In
|
||||||
|
|
||||||
@ -33,8 +35,10 @@ type api struct {
|
|||||||
|
|
||||||
func NewMiner(api api) *Miner {
|
func NewMiner(api api) *Miner {
|
||||||
return &Miner{
|
return &Miner{
|
||||||
api: api,
|
api: api,
|
||||||
Delay: build.BlockDelay * time.Second,
|
|
||||||
|
// time between blocks, network parameter
|
||||||
|
runVDF: delayVDF(build.BlockDelay * time.Second),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,8 +50,7 @@ type Miner struct {
|
|||||||
stop chan struct{}
|
stop chan struct{}
|
||||||
stopping chan struct{}
|
stopping chan struct{}
|
||||||
|
|
||||||
// time between blocks, network parameter
|
runVDF vdfFunc
|
||||||
Delay time.Duration
|
|
||||||
|
|
||||||
lastWork *MiningBase
|
lastWork *MiningBase
|
||||||
}
|
}
|
||||||
@ -252,14 +255,16 @@ func (m *Miner) getMinerWorker(ctx context.Context, addr address.Address, ts *ty
|
|||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Miner) runVDF(ctx context.Context, input []byte) ([]byte, []byte, error) {
|
func delayVDF(delay time.Duration) func(ctx context.Context, input []byte) ([]byte, []byte, error) {
|
||||||
select {
|
return func(ctx context.Context, input []byte) ([]byte, []byte, error) {
|
||||||
case <-ctx.Done():
|
select {
|
||||||
return nil, nil, ctx.Err()
|
case <-ctx.Done():
|
||||||
case <-time.After(m.Delay):
|
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) {
|
func (m *Miner) scratchTicket(ctx context.Context, base *MiningBase) (*types.Ticket, error) {
|
||||||
|
28
miner/testminer.go
Normal file
28
miner/testminer.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -3,31 +3,105 @@ package node_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-datastore"
|
||||||
|
"github.com/libp2p/go-libp2p-core/peer"
|
||||||
mocknet "github.com/libp2p/go-libp2p/p2p/net/mock"
|
mocknet "github.com/libp2p/go-libp2p/p2p/net/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"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/filecoin-project/go-lotus/api"
|
"github.com/filecoin-project/go-lotus/api"
|
||||||
"github.com/filecoin-project/go-lotus/api/client"
|
"github.com/filecoin-project/go-lotus/api/client"
|
||||||
"github.com/filecoin-project/go-lotus/api/test"
|
"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/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()
|
ctx := context.Background()
|
||||||
mn := mocknet.New(ctx)
|
mn := mocknet.New(ctx)
|
||||||
|
|
||||||
out := make([]api.FullNode, n)
|
fulls := make([]test.TestNode, nFull)
|
||||||
|
storers := make([]test.TestStorageNode, len(storage))
|
||||||
|
|
||||||
var genbuf bytes.Buffer
|
var genbuf bytes.Buffer
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < nFull; i++ {
|
||||||
var genesis node.Option
|
var genesis node.Option
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
genesis = node.Override(new(modules.Genesis), modtest.MakeGenesisMem(&genbuf))
|
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()))
|
genesis = node.Override(new(modules.Genesis), modules.LoadGenesis(genbuf.Bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mineBlock := make(chan struct{})
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
// TODO: Don't ignore stop
|
// TODO: Don't ignore stop
|
||||||
_, err = node.New(ctx,
|
_, err = node.New(ctx,
|
||||||
node.FullAPI(&out[i]),
|
node.FullAPI(&fulls[i].FullNode),
|
||||||
node.Online(),
|
node.Online(),
|
||||||
node.Repo(repo.NewMemory(nil)),
|
node.Repo(repo.NewMemory(nil)),
|
||||||
node.MockHost(mn),
|
node.MockHost(mn),
|
||||||
|
|
||||||
|
node.Override(new(*miner.Miner), miner.NewTestMiner(mineBlock)),
|
||||||
|
|
||||||
genesis,
|
genesis,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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 {
|
if err := mn.LinkAll(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
return fulls, storers
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPI(t *testing.T) {
|
func TestAPI(t *testing.T) {
|
||||||
test.TestApis(t, builder)
|
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 {
|
for i, a := range fullApis {
|
||||||
nodeApis := builder(t, n)
|
|
||||||
out := make([]api.FullNode, n)
|
|
||||||
|
|
||||||
for i, a := range nodeApis {
|
|
||||||
rpcServer := jsonrpc.NewServer()
|
rpcServer := jsonrpc.NewServer()
|
||||||
rpcServer.Register("Filecoin", a)
|
rpcServer.Register("Filecoin", a)
|
||||||
testServ := httptest.NewServer(rpcServer) // todo: close
|
testServ := httptest.NewServer(rpcServer) // todo: close
|
||||||
|
|
||||||
var err error
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out
|
|
||||||
|
return fulls, storers
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIRPC(t *testing.T) {
|
func TestAPIRPC(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user