Delete lotus-pond (#9352)

This commit is contained in:
Geoff Stuart 2022-09-21 15:39:46 -04:00 committed by GitHub
parent 94add978b0
commit 9e94dc5550
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 3 additions and 17391 deletions

3
.gitignore vendored
View File

@ -9,7 +9,6 @@
/lotus-chainwatch
/lotus-shed
/lotus-sim
/lotus-pond
/lotus-townhall
/lotus-fountain
/lotus-stats
@ -21,8 +20,6 @@
/docgen-md
/docgen-openrpc
/bench.json
/lotuspond/front/node_modules
/lotuspond/front/build
/cmd/lotus-townhall/townhall/node_modules
/cmd/lotus-townhall/townhall/build
/cmd/lotus-townhall/townhall/package-lock.json

View File

@ -43,9 +43,6 @@ issues:
exclude-use-default: false
exclude-rules:
- path: lotuspond
linters:
- errcheck
- path: node/modules/lp2p
linters:

View File

@ -160,18 +160,6 @@ benchmarks:
@curl -X POST 'http://benchmark.kittyhawk.wtf/benchmark' -d '@bench.json' -u "${benchmark_http_cred}"
.PHONY: benchmarks
lotus-pond: 2k
$(GOCC) build -o lotus-pond ./lotuspond
.PHONY: lotus-pond
BINS+=lotus-pond
lotus-pond-front:
(cd lotuspond/front && npm i && CI=false npm run build)
.PHONY: lotus-pond-front
lotus-pond-app: lotus-pond-front lotus-pond
.PHONY: lotus-pond-app
lotus-fountain:
rm -f lotus-fountain
$(GOCC) build $(GOFLAGS) -o lotus-fountain ./cmd/lotus-fountain
@ -299,9 +287,6 @@ type-gen: api-gen
$(GOCC) generate -x ./...
goimports -w api/
method-gen: api-gen
(cd ./lotuspond/front/src/chain && $(GOCC) run ./methodgen.go)
actors-code-gen:
$(GOCC) run ./gen/inline-gen . gen/inlinegen-data.json
$(GOCC) run ./chain/actors/agen
@ -367,7 +352,7 @@ docsgen-openrpc-gateway: docsgen-openrpc-bin
fiximports:
./scripts/fiximports
gen: actors-code-gen type-gen method-gen cfgdoc-gen docsgen api-gen circleci bundle-gen fiximports
gen: actors-code-gen type-gen cfgdoc-gen docsgen api-gen circleci bundle-gen fiximports
@echo ">>> IF YOU'VE MODIFIED THE CLI OR CONFIG, REMEMBER TO ALSO MAKE docsgen-cli"
.PHONY: gen

4
go.mod
View File

@ -63,7 +63,6 @@ require (
github.com/golang/mock v1.6.0
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.7.4
github.com/gorilla/websocket v1.5.0
github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026
github.com/hannahhoward/go-pubsub v0.0.0-20200423002714-8d62886cc36e
github.com/hashicorp/go-multierror v1.1.1
@ -126,7 +125,6 @@ require (
github.com/multiformats/go-multihash v0.2.1
github.com/multiformats/go-varint v0.0.6
github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333
github.com/opentracing/opentracing-go v1.2.0
github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e
github.com/prometheus/client_golang v1.12.1
github.com/raulk/clock v1.1.0
@ -215,6 +213,7 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hannahhoward/cbor-gen-for v0.0.0-20200817222906-ea96cece81f1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/huin/goupnp v1.0.3 // indirect
@ -288,6 +287,7 @@ require (
github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/opencontainers/runtime-spec v1.0.2 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 // indirect
github.com/pkg/errors v0.9.1 // indirect

View File

@ -1,139 +0,0 @@
package main
import (
"context"
"crypto/rand"
"io"
"io/ioutil"
"os"
"sync"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-jsonrpc"
"github.com/filecoin-project/lotus/node/repo"
)
type NodeState int
const (
NodeUnknown = iota //nolint:deadcode
NodeRunning
NodeStopped
)
type api struct {
cmds int32
running map[int32]*runningNode
runningLk sync.Mutex
genesis string
}
type nodeInfo struct {
Repo string
ID int32
APIPort int32
State NodeState
FullNode string // only for storage nodes
Storage bool
}
func (api *api) Nodes() []nodeInfo {
api.runningLk.Lock()
out := make([]nodeInfo, 0, len(api.running))
for _, node := range api.running {
out = append(out, node.meta)
}
api.runningLk.Unlock()
return out
}
func (api *api) TokenFor(id int32) (string, error) {
api.runningLk.Lock()
defer api.runningLk.Unlock()
rnd, ok := api.running[id]
if !ok {
return "", xerrors.New("no running node with this ID")
}
r, err := repo.NewFS(rnd.meta.Repo)
if err != nil {
return "", err
}
t, err := r.APIToken()
if err != nil {
return "", err
}
return string(t), nil
}
func (api *api) FullID(id int32) (int32, error) {
api.runningLk.Lock()
defer api.runningLk.Unlock()
stor, ok := api.running[id]
if !ok {
return 0, xerrors.New("storage node not found")
}
if !stor.meta.Storage {
return 0, xerrors.New("node is not a storage node")
}
for id, n := range api.running {
if n.meta.Repo == stor.meta.FullNode {
return id, nil
}
}
return 0, xerrors.New("node not found")
}
func (api *api) CreateRandomFile(size int64) (string, error) {
tf, err := ioutil.TempFile(os.TempDir(), "pond-random-")
if err != nil {
return "", err
}
_, err = io.CopyN(tf, rand.Reader, size)
if err != nil {
return "", err
}
if err := tf.Close(); err != nil {
return "", err
}
return tf.Name(), nil
}
func (api *api) Stop(node int32) error {
api.runningLk.Lock()
nd, ok := api.running[node]
api.runningLk.Unlock()
if !ok {
return nil
}
nd.stop()
return nil
}
type client struct {
Nodes func() []nodeInfo
}
func apiClient(ctx context.Context) (*client, error) {
c := &client{}
if _, err := jsonrpc.NewClient(ctx, "ws://"+listenAddr+"/rpc/v0", "Pond", c, nil); err != nil {
return nil, err
}
return c, nil
}

View File

@ -1,23 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

File diff suppressed because it is too large Load Diff

View File

@ -1,44 +0,0 @@
{
"name": "front",
"version": "0.1.0",
"private": true,
"dependencies": {
"borc": "^2.1.1",
"cids": "^0.7.1",
"ipld-dag-cbor": "^0.15.0",
"jsonrpc-websocket-client": "^0.5.0",
"multihashes": "^0.4.15",
"react": "^16.8.6",
"react-cristal": "^0.0.12",
"react-dom": "^16.8.6",
"react-router-dom": "^5.0.1",
"react-scripts": "3.0.1",
"react-tooltip": "^3.11.1",
"rpc-websockets": "^4.5.1",
"styled-components": "^3.3.3",
"xterm": "^3.14.5",
"xterm-addon-attach": "^0.1.0",
"xterm-addon-fit": "^0.1.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View File

@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#b7c4cd" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Lotus Pond</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

View File

@ -1,15 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -1,148 +0,0 @@
import React from 'react'
import CID from 'cids'
import ReactTooltip from 'react-tooltip'
import * as multihash from "multihashes"
import State from "./State"
import methods from "./chain/methods.json"
import Fil from "./Fil";
function truncAddr(addr, len) {
if (!addr) {
return "<!nil>"
}
if (addr.length > len) {
return <abbr title={addr}>{addr.substr(0, len - 3) + '..'}</abbr>
}
return addr
}
let sheet = document.createElement('style')
document.body.appendChild(sheet);
class Address extends React.Component {
constructor(props) {
super(props)
this.openState = this.openState.bind(this)
this.state = {balance: -2}
this.refresh = this.refresh.bind(this)
}
componentDidMount() {
this.refresh()
if(!this.props.ts) {
this.updates = setInterval(this.refresh, 2050)
this.props.client.on('close', () => clearInterval(this.updates))
}
}
componentWillUnmount() {
clearInterval(this.updates)
}
async refresh() {
let balance = 0
let actor = {}
let actorInfo
let minerInfo
let nonce
try {
balance = await this.props.client.call('Filecoin.WalletBalance', [this.props.addr])
actor = await this.props.client.call('Filecoin.StateGetActor', [this.props.addr, (this.props.ts || {}).Cids])
actorInfo = await this.actorInfo(actor, this.props.addr)
if(this.props.miner) {
minerInfo = await this.props.client.call('Filecoin.StateMinerPower', [this.props.addr, (this.props.ts || {}).Cids])
}
if(this.props.nonce) {
nonce = await this.props.client.call('Filecoin.MpoolGetNonce', [this.props.addr])
}
} catch (err) {
console.log(err)
balance = -1
}
this.setState({balance, actor, actorInfo, minerInfo, nonce})
}
openState() {
this.props.mountWindow((onClose) => <State addr={this.props.addr} actor={this.state.actor} client={this.props.client} onClose={onClose} mountWindow={this.props.mountWindow}/>)
}
async actorInfo(actor, addr) {
const c = new CID(actor.Code['/'])
const mh = multihash.decode(c.multihash) // TODO: check identity
let method = <span></span>
if(this.props.method !== undefined && mh.digest.toString()) {
method = <span>.{methods[mh.digest.toString()][this.props.method]}</span>
}
let info = <span>({mh.digest.toString()}{method})</span>
switch(mh.digest.toString()) {
case 'paych':
const actstate = await this.props.client.call('Filecoin.StateReadState', [addr, (this.props.ts || {}).Cids])
info = <span>({mh.digest.toString()}{method} to <Address nobalance={true} client={this.props.client} addr={actstate.State.To} mountWindow={this.props.mountWindow}/>)</span>
}
return info
}
addColl = async () => {
const coll = await this.props.client.call('Filecoin.StatePledgeCollateral', [null])
this.props.addN(this.props.addr, coll)
}
render() {
let add20k = <span/>
if(this.props.addN) {
add20k = <span>&nbsp;<a href="#" onClick={() => this.props.addN(this.props.addr, 2e+18)}>[+2]</a></span>
if (this.props.add10k) {
add20k = <span>{add20k}&nbsp;<a href="#" onClick={() => this.props.addN(this.props.addr, 20e+18)}>[+20]</a></span>
add20k = <span>{add20k}&nbsp;<a href="#" onClick={() => this.props.addN(this.props.addr, 200e+18)}>[+200]</a></span>
add20k = <span>{add20k}&nbsp;<a href="#" onClick={() => this.addColl()}>[<abbr title="min collateral">+C</abbr>]</a></span>
}
}
let addr = truncAddr(this.props.addr, this.props.short ? 12 : 17)
let actInfo = <span>(?)</span>
if(this.state.balance >= 0) {
actInfo = this.state.actorInfo
addr = <a href="#" onClick={this.openState}>{addr}</a>
}
addr = <span className={`pondaddr-${this.props.addr}`}
onMouseEnter={() => sheet.sheet.insertRule(`.pondaddr-${this.props.addr}, .pondaddr-${this.props.addr} * { color: #11ee11 !important; }`, 0)}
onMouseLeave={() => sheet.sheet.deleteRule(0)}
>{addr}</span>
let nonce = <span/>
if(this.props.nonce) {
nonce = <span>&nbsp;<abbr title={"Next nonce"}>Nc:{this.state.nonce}</abbr>{nonce}</span>
}
let balance = <span>:&nbsp;{<Fil>{this.state.balance}</Fil>}&nbsp;</span>
if(this.props.nobalance) {
balance = <span/>
}
if(this.props.short) {
actInfo = <ReactTooltip id={this.props.addr} place="top" type="dark" effect="solid">{actInfo}: {<Fil>this.state.balance</Fil>}</ReactTooltip>
balance = <span/>
}
let transfer = <span/>
if(this.props.transfer) {
transfer = <span>&nbsp;<Fil>{this.props.transfer}</Fil>FIL</span>
}
let minerInfo = <span/>
if(this.state.minerInfo) {
minerInfo = <span>&nbsp;Power: {this.state.minerInfo.MinerPower.QualityAdjPower} ({this.state.minerInfo.MinerPower.QualityAdjPower/this.state.minerInfo.TotalPower.QualityAdjPower*100}%)</span>
}
return <span data-tip data-for={this.props.addr}>{addr}{balance}{actInfo}{nonce}{add20k}{transfer}{minerInfo}</span>
}
}
export default Address

View File

@ -1,232 +0,0 @@
.Index {
width: 100vw;
height: 100vh;
background: #1a1a1a;
color: #f0f0f0;
font-family: monospace;
display: grid;
grid-template-columns: auto 40vw auto;
grid-template-rows: auto auto auto 3em;
grid-template-areas:
". . ."
". main ."
". . ."
"footer footer footer";
}
.Index-footer {
background: #2a2a2a;
grid-area: footer;
}
.Index-footer > div {
padding-left: 0.7em;
padding-top: 0.7em;
}
.Index-nodes {
grid-area: main;
background: #2a2a2a;
}
.Index-node {
margin: 5px;
padding: 15px;
background: #1f1f1f;
}
.Index-addwrap {
margin-top: 5px;
}
/* SingleNode */
.SingleNode-connecting {
width: 100vw;
height: 100vh;
background: #1a1a1a;
color: #ffffff;
font-family: monospace;
display: grid;
grid-template-columns: auto min-content auto;
grid-template-rows: auto min-content auto;
grid-template-areas:
". . ."
". main ."
". . ."
}
.SingleNode-connecting > div {
grid-area: main;
padding: 15px;
white-space: nowrap;
background: #2a2a2a;
}
/*****/
a:link {
color: #50f020;
}
a:visited {
color: #50f020;
}
a:hover {
color: #30a00a;
}
.Button {
display: inline-block;
padding: 15px;
background: #1f1f1f;
margin-left: 5px;
}
.Window {
background: #2a2a2a !important;
color: #e0e0e0;
}
.Window b {
color: #f0f0f0;
}
.Window > :first-child > :nth-child(2)::before {
background: #f0f0f0;
}
.Window > :first-child > :nth-child(2)::after {
background: #f0f0f0;
}
.Window a:link {
color: #30a015;
}
.Window a:visited {
color: #30a015;
}
/* POND */
.Pond-connecting {
width: 100vw;
height: 100vh;
background: #1a1a1a;
color: #ffffff;
font-family: monospace;
display: grid;
grid-template-columns: auto min-content auto;
grid-template-rows: auto min-content auto;
grid-template-areas:
". . ."
". main ."
". . ."
}
.App {
min-height: 100vh;
background: #1a1a1a;
font-family: monospace;
}
.NodeList {
user-select: text;
font-family: monospace;
min-width: 40em;
display: inline-block;
}
.FullNode {
user-select: text;
font-family: monospace;
min-width: 50em;
display: inline-block;
}
.FullNode-voucher {
padding-left: 1em;
}
.StorageNode {
user-select: text;
font-family: monospace;
min-width: 40em;
display: inline-block;
}
.Block {
user-select: text;
font-family: monospace;
min-width: 60em;
display: inline-block;
}
.State {
user-select: text;
font-family: monospace;
min-width: 40em;
display: inline-block;
}
.Client {
user-select: text;
font-family: monospace;
display: inline-block;
}
.CristalScroll {
display: flex;
min-width: 100%;
min-height: 100%;
overflow: auto;
}
.Consensus {
font-family: monospace;
}
.ChainExplorer {
font-family: monospace;
color: #d0d0d0;
}
.ChainExplorer-at {
min-width: 40em;
background: #222222;
}
.ChainExplorer-after {
background: #440000
}
.ChainExplorer-after:hover {
background: #770000
}
.ChainExplorer-before {
background: #444400
}
.ChainExplorer-before:hover {
background: #777700
}
.Logs {
width: 100%;
height: 100%;
}
.Logs-window :nth-child(2) {
height: 100%;
}

View File

@ -1,24 +0,0 @@
import React from 'react';
import './App.css';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import Pond from "./Pond";
import SingleNode from "./SingleNode";
import Index from "./NodeIndex";
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<Router>
<Route path="/" exact component={Index} />
<Route path="/app/pond/" component={Pond} />
<Route path="/app/node/:node" component={SingleNode} />
</Router>
)
}
}
export default App

View File

@ -1,9 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Pond from './Pond';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<Pond />, div);
ReactDOM.unmountComponentAtNode(div);
});

View File

@ -1,89 +0,0 @@
import React from 'react';
import {BlockLinks} from "./BlockLink";
import Address from "./Address";
import Window from "./Window";
class Block extends React.Component {
constructor(props) {
super(props)
this.state = {}
this.loadHeader()
}
async loadHeader() {
const header = await this.props.conn.call('Filecoin.ChainGetBlock', [this.props.cid])
let messages = await this.props.conn.call('Filecoin.ChainGetParentMessages', [this.props.cid])
let receipts = await this.props.conn.call('Filecoin.ChainGetParentReceipts', [this.props.cid])
if (!messages) {
messages = []
}
messages = messages.map((msg, k) => ({...msg.Message, cid: msg.Cid, receipt: receipts[k]}))
messages = await Promise.all(messages.map(async (msg, i) => {
if (msg.receipt.ExitCode !== 0) {
let reply = await this.props.conn.call('Filecoin.StateReplay', [{Cids: [this.props.cid], Blocks: [header], Height: header.Height}, msg.Cid])
if(!reply.Error) {
reply.Error = "reply: no error"
}
msg.Error = reply.Error
}
return msg
}))
this.setState({header: header, messages: messages})
}
render() {
let content = <div>Loading Block Info</div>
if (this.state.header) {
let head = this.state.header
const messages = this.state.messages.map((m, k) => (
<div key={k}>
<div>
<Address client={this.props.conn} addr={m.From} mountWindow={this.props.mountWindow}/><b>&nbsp;=>&nbsp;</b>
<Address client={this.props.conn} addr={m.To} mountWindow={this.props.mountWindow} transfer={m.Value} method={m.Method}/>
<span>&nbsp;N{m.Nonce}</span>
<span>&nbsp;{m.receipt.GasUsed}Gas</span>
{m.receipt.ExitCode !== 0 ? <span>&nbsp;<b>EXIT:{m.receipt.ExitCode}</b></span> : <span/>}
</div>
{m.receipt.ExitCode !== 0 ? <div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error: <b>{m.Error}</b></div> : <span/>}
</div>
))
content = (
<div className="Block">
<div>Height: {head.Height}</div>
<div>Parents: <BlockLinks cids={head.Parents} conn={this.props.conn} mountWindow={this.props.mountWindow}/></div>
<div>Weight: {head.ParentWeight}</div>
<div>Miner: {<Address client={this.props.conn} addr={head.Miner} mountWindow={this.props.mountWindow}/>}</div>
<div>Messages: {head.Messages['/']} {/*TODO: link to message explorer */}</div>
<div>Parent Receipts: {head.ParentMessageReceipts['/']}</div>
<div>
<span>Parent State Root:&nbsp;{head.ParentStateRoot['/']}</span>
<span>&nbsp;<Address client={this.props.conn} short={true} addr="t00" mountWindow={this.props.mountWindow}/></span>
<span>&nbsp;<Address client={this.props.conn} short={true} addr="t01" mountWindow={this.props.mountWindow}/></span>
<span>&nbsp;<Address client={this.props.conn} short={true} addr="t02" mountWindow={this.props.mountWindow}/></span>
<span>&nbsp;<Address client={this.props.conn} short={true} addr="t03" mountWindow={this.props.mountWindow}/></span>
<span>&nbsp;<Address client={this.props.conn} short={true} addr="t04" mountWindow={this.props.mountWindow}/></span>
<span>&nbsp;<Address client={this.props.conn} short={true} addr="t05" mountWindow={this.props.mountWindow}/></span>
<span>&nbsp;<Address client={this.props.conn} short={true} addr="t099" mountWindow={this.props.mountWindow}/></span>
</div>
<div>----</div>
<div>{messages}</div>
</div>
)
}
return (<Window className="CristalScroll" initialSize={{width: 1050, height: 400}} onClose={this.props.onClose} title={`Block ${this.props.cid['/']}`}>
{content}
</Window>)
}
}
export default Block

View File

@ -1,41 +0,0 @@
import React from 'react';
import Block from "./Block";
import Address from "./Address";
export class BlockLinks extends React.Component {
render() {
return this.props.cids.map((c, k) => {
let block
if(this.props.blocks) {
block = this.props.blocks[k]
}
return <span key={c + '-' + k}><BlockLink block={block} conn={this.props.conn} cid={c} mountWindow={this.props.mountWindow}/> </span>
})
}
}
class BlockLink extends React.Component {
constructor(props) {
super(props)
this.openBlockViewer = this.openBlockViewer.bind(this)
}
openBlockViewer() {
this.props.mountWindow((onClose) => <Block cid={this.props.cid} conn={this.props.conn} onClose={onClose} mountWindow={this.props.mountWindow}/>)
}
render() {
let info = <span></span>
if(this.props.block) {
info = <span>&nbsp;(by <Address client={this.props.conn} addr={this.props.block.Miner} mountWindow={this.props.mountWindow} short={true}/>)</span>
}
return <span><a href="#" onClick={this.openBlockViewer}><abbr title={this.props.cid['/']}>{this.props.cid['/'].substr(-8)}</abbr></a>{info}</span>
}
}
export default BlockLink

View File

@ -1,178 +0,0 @@
import React from 'react';
import {BlockLinks} from "./BlockLink";
import Window from "./Window";
const rows = 32
class ChainExplorer extends React.Component {
fetching = []
constructor(props) {
super(props)
this.update = this.update.bind(this)
this.scroll = this.scroll.bind(this)
this.state = {
follow: true,
at: props.ts.Height,
highest: props.ts,
cache: {[props.ts.Height]: props.ts},
messages: {},
}
}
viewport() {
const base = this.state.at - this.state.at % rows
return Array(rows).fill(0)
.map((_, k) => k + base)
.map(k => k > this.state.at ? k - rows : k)
}
async componentDidMount() {
let msgcache = {}
await this.updateMessages(this.props.ts.Cids, msgcache)
this.setState(prev => ({messages: {...prev.messages, ...msgcache}}))
setInterval(this.update, 1000)
}
async updateMessages(cids, msgcache) {
const msgs = await Promise.all(cids.map(async cid => [cid['/'], await this.props.client.call('Filecoin.ChainGetParentMessages', [cid])]))
msgs.forEach(([cid, msg]) => msgcache[cid] = msg)
}
async update() {
const tipset = await this.props.client.call("Filecoin.ChainHead", [])
if(tipset.Height > this.state.highest.Height) {
let msgcache = {}
await this.updateMessages(tipset.Cids, msgcache)
this.setState(prev => ({highest: tipset, messages: {...prev.messages, ...msgcache}, cache: {...prev.cache, [tipset.Height]: tipset}}))
if(this.state.follow) {
this.setState({at: tipset.Height})
}
}
await this.fetchVisible()
}
async fetch(h, base, cache, msgcache) {
//console.log(h, base, cache)
if (this.fetching[h]) {
return cache[h]
}
this.fetching[h] = true
if (h < 0) {
return
}
if(!base.Blocks) {
console.log("base for H is nil blk", h, base)
return
}
let cids = base.Blocks.map(b => (b.Parents || []))
.reduce((acc, val) => {
let out = {...acc}
val.forEach(c => out[c['/']] = 8)
return out
}, {})
cids = Object.keys(cids).map(k => ({'/': k}))
console.log("parents", cids)
const blocks = await Promise.all(cids.map(cid => this.props.client.call('Filecoin.ChainGetBlock', [cid])))
if (!blocks[0]) {
return
}
cache[h] = {
Height: blocks[0].Height,
Cids: cids,
Blocks: blocks,
}
await this.updateMessages(cids, msgcache)
return cache[h]
}
async fetchVisible() {
await this.fetchN(this.state.at)
}
async fetchN(top) {
if(!this.state.cache[top]) {
if(top === this.state.highest.Height) {
throw "fetchN broke (tipset not fetched)"
}
let h = top + rows > this.state.highest.Height ? this.state.highest.Height : top + rows
await this.fetchN(h)
}
let cache = {...this.state.cache}
let msgcache = {...this.state.messages}
console.log("top", top)
let parent = cache[top]
for(let i = 0; i < rows; i++) {
let newts = await this.fetch(top - i, parent, cache, msgcache)
parent = newts ? newts : parent
}
this.setState({cache: cache, messages: msgcache})
}
scroll(event) {
if(event.deltaY < 0 && this.state.at > 0) {
this.setState(prev => ({at: prev.at - 1, follow: false}))
}
if(event.deltaY > 0 && this.state.at < this.state.highest.Height) {
this.setState(prev => ({at: prev.at + 1, follow: prev.at + 1 === this.state.highest.Height}))
}
}
render() {
const view = this.viewport()
const content = <div className="ChainExplorer" onWheel={this.scroll}>{view.map(row => {
const base = this.state.at - this.state.at % rows
const className = row === this.state.at ? 'ChainExplorer-at' : (row < base ? 'ChainExplorer-after' : 'ChainExplorer-before')
let info = <span>(fetching)</span>
let h = <i>{row}</i>
if(this.state.cache[row]) {
const ts = this.state.cache[row]
h = ts.Height
let msgc = -1
if(ts.Cids[0] && this.state.messages[ts.Cids[0]['/']]) { // TODO: get from all blks
msgc = this.state.messages[ts.Cids[0]['/']].length
}
if(msgc > 0) {
msgc = <b>{msgc}</b>
}
let time = '?'
if(this.state.cache[row - 1]){
time = <span>{ts.Blocks[0].Timestamp - this.state.cache[row - 1].Blocks[0].Timestamp}s</span>
}
info = <span>
<BlockLinks cids={ts.Cids} blocks={ts.Blocks} conn={this.props.client} mountWindow={this.props.mountWindow} /> Msgs: {msgc} ΔT:{time}
</span>
}
return <div key={row} className={className}>@{h} {info}</div>
})}</div>
return (<Window initialSize={{width: 800}} onClose={this.props.onClose} title={`Chain Explorer ${this.state.follow ? '(Following)' : ''}`}>
{content}
</Window>)
}
}
export default ChainExplorer

View File

@ -1,228 +0,0 @@
import React from 'react'
import Address from './Address'
import Window from './Window'
import Fil from './Fil'
const dealStates = [
'Unknown',
'ProposalNotFound',
'ProposalRejected',
'ProposalAccepted',
'Staged',
'Sealing',
'ProposalSigned',
'Published',
'Committed',
'Active',
'Failing',
'Recovering',
'Expired',
'NotFound',
'Validating',
'Transferring',
'VerifyData',
'Publishing',
'Error'
]
class Client extends React.Component {
constructor(props) {
super(props)
this.state = {
miners: ['t0101'],
ask: { Price: '1000000000' }, // 2x min default ask to account for bin packing (could also do the math correctly below, but..)
kbs: 1,
blocks: 12,
total: 36000,
miner: 't0101',
deals: [],
blockDelay: 10
}
}
async componentDidMount() {
let ver = await this.props.client.call('Filecoin.Version', [])
this.setState({ blockDelay: ver.BlockDelay })
this.getDeals()
setInterval(this.getDeals, 1325)
}
getDeals = async () => {
let miners = await this.props.client.call('Filecoin.StateListMiners', [
null
])
let deals = await this.props.client.call('Filecoin.ClientListDeals', [])
miners.sort()
this.setState({ deals, miners })
}
update = name => e => this.setState({ [name]: e.target.value })
makeDeal = async () => {
let perBlk =
((this.state.ask.Price * this.state.kbs * 1000) / (1 << 30)) * 2
let file = await this.props.pondClient.call('Pond.CreateRandomFile', [
this.state.kbs * 1000
]) // 1024 won't fit in 1k blocks :(
let cid = await this.props.client.call('Filecoin.ClientImport', [
{
Path: file,
IsCar: false
}
])
let dealcid = await this.props.client.call('Filecoin.ClientStartDeal', [
cid,
this.state.miner,
`${Math.round(perBlk)}`,
Number(this.state.blocks)
])
console.log('deal cid: ', dealcid)
}
retrieve = deal => async () => {
console.log(deal)
let client = await this.props.client.call(
'Filecoin.WalletDefaultAddress',
[]
)
let order = {
Root: deal.PieceRef,
Size: deal.Size,
// TODO: support offset
Total: String(deal.Size * 2),
Client: client,
Miner: deal.Miner
}
await this.props.client.call('Filecoin.ClientRetrieve', [
order,
{
Path: '/dev/null',
IsCAR: false
}
])
}
render() {
let perBlk = this.state.ask.Price * this.state.kbs * 1000
let total = perBlk * this.state.blocks
let days = (this.state.blocks * this.state.blockDelay) / 60 / 60 / 24
let dealMaker = (
<div hidden={!this.props.pondClient}>
<div>
<span>Make Deal: </span>
<select>
{this.state.miners.map(m => (
<option key={m} value={m}>
{m}
</option>
))}
</select>
<span>
{' '}
Ask:{' '}
<b>
<Fil>{this.state.ask.Price}</Fil>
</b>{' '}
Fil/Byte/Block
</span>
</div>
<div>
Data Size:{' '}
<input
type="text"
placeholder="KBs"
defaultValue={1}
onChange={this.update('kbs')}
style={{ width: '5em' }}
/>
KB; Duration:
<input
type="text"
placeholder="blocks"
defaultValue={12}
onChange={this.update('blocks')}
style={{ width: '5em' }}
/>
Blocks
</div>
<div>
Total: <Fil>{total}</Fil>; {days} Days
</div>
<button onClick={this.makeDeal}>Deal!</button>
</div>
)
let deals = this.state.deals.map((deal, i) => (
<div key={i}>
<ul>
<li>
{i}. Proposal: {deal.ProposalCid['/'].substr(0, 18)}...{' '}
<Address
nobalance={true}
client={this.props.client}
addr={deal.Provider}
mountWindow={this.props.mountWindow}
/>
: <b>{dealStates[deal.State]}</b>
{dealStates[deal.State] === 'Complete' ? (
<span>
&nbsp;
<a href="#" onClick={this.retrieve(deal)}>
[Retrieve]
</a>
</span>
) : (
<span />
)}
<ul>
<li>
Data: {deal.PieceRef['/']}, <b>{deal.Size}</b>B; Duration:{' '}
<b>{deal.Duration}</b>Blocks
</li>
<li>
Total: <b>{deal.TotalPrice}</b>FIL; Per Block:{' '}
<b>
{Math.round((deal.TotalPrice / deal.Duration) * 100) / 100}
</b>
FIL; PerMbyteByteBlock:{' '}
<b>
{Math.round(
(deal.TotalPrice / deal.Duration / (deal.Size / 1000000)) *
100
) / 100}
</b>
FIL
</li>
</ul>
</li>
</ul>
</div>
))
return (
<Window
title={'Client - Node ' + this.props.node.ID}
onClose={this.props.onClose}
initialSize={{ width: 600, height: 400 }}
>
<div className="Client">
<div>{dealMaker}</div>
<div>{deals}</div>
</div>
</Window>
)
}
}
export default Client

View File

@ -1,126 +0,0 @@
import React from 'react';
import Window from "./Window";
async function awaitReducer(prev, c) {
return {...await prev, ...await c}
}
class ConnMgr extends React.Component {
constructor(props) {
super(props)
this.connect = this.connect.bind(this)
this.connectAll = this.connectAll.bind(this)
this.connect1 = this.connect1.bind(this)
this.connectChain = this.connectChain.bind(this)
this.getActualState = this.getActualState.bind(this)
this.state = {conns: {}, lock: true}
this.getActualState()
setInterval(this.getActualState, 500)
}
async getActualState() {
const nodes = this.props.nodes
let keys = Object.keys(nodes)
const newConns = await keys.filter((_, i) => i > 0).filter(kfrom => this.props.nodes[kfrom].conn !== undefined).map(async (kfrom, i) => {
return keys.filter((_, j) => i >= j).filter(kto => this.props.nodes[kto].conn !== undefined).map(async kto => {
const fromNd = this.props.nodes[kfrom]
const toNd = this.props.nodes[kto]
const connectedness = await fromNd.conn.call('Filecoin.NetConnectedness', [toNd.peerid])
return {[`${kfrom},${kto}`]: connectedness === 1}
}).reduce(awaitReducer, Promise.resolve({}))
}).reduce(awaitReducer, Promise.resolve({}))
this.setState({conns: newConns, lock: false})
}
async connect(action, from, to, noupdate) {
const fromNd = this.props.nodes[from]
const toNd = this.props.nodes[to]
if (action) {
const toPeerInfo = await toNd.conn.call('Filecoin.NetAddrsListen', [])
await fromNd.conn.call('Filecoin.NetConnect', [toPeerInfo])
} else {
await fromNd.conn.call('Filecoin.NetDisconnect', [toNd.peerid])
}
if (!noupdate)
this.setState(prev => ({conns: {...prev.conns, [`${from},${to}`]: action}}))
}
connectAll(discon) {
return () => {
const nodes = this.props.nodes
let keys = Object.keys(nodes)
keys.filter((_, i) => i > 0).forEach((kfrom, i) => {
keys.filter((_, j) => i >= j).forEach((kto, i) => {
this.connect(!discon, kfrom, kto, true)
})
})
}
}
connect1() {
const nodes = this.props.nodes
let keys = Object.keys(nodes)
keys.filter((_, i) => i > 0).forEach((k, i) => {
this.connect(true, k, keys[0])
})
}
connectChain() {
const nodes = this.props.nodes
let keys = Object.keys(nodes)
keys.filter((_, i) => i > 0).forEach((k, i) => {
this.connect(true, k, keys[i])
})
}
render() {
const nodes = this.props.nodes
let keys = Object.keys(nodes)
const rows = keys.filter((_, i) => i > 0).map((k, i) => {
const cols = keys.filter((_, j) => i >= j).map((kt, i) => {
const checked = this.state.conns[`${k},${kt}`] === true
return (
<td key={k + "," + kt}>
<input checked={checked} disabled={this.state.lock} type="checkbox" onChange={e => this.connect(e.target.checked, k, kt)}/>
</td>
)
})
return (
<tr key={k}><td>{k}</td>{cols}</tr>
)
})
return(
<Window title={`Connection Manager${this.state.lock ? ' (syncing)' : ''}`}>
<table>
<thead><tr><td></td>{keys.slice(0, -1).map((i) => (<td key={i}>{i}</td>))}</tr></thead>
<tbody>{rows}</tbody>
</table>
<div>
<button onClick={this.connectAll(true)}>DisonnAll</button>
<button onClick={this.connectAll(false)}>ConnAll</button>
<button onClick={this.connect1}>Conn1</button>
<button onClick={this.connectChain}>ConnChain</button>
</div>
</Window>
)
}
}
export default ConnMgr

View File

@ -1,69 +0,0 @@
import React from 'react';
import {BlockLinks} from "./BlockLink";
import Window from "./Window";
function styleForHDiff(max, act) {
switch (max - act) {
case 0:
return {background: '#004400'}
case 1:
return {background: '#aaaa00'}
default:
return {background: '#aa0000'}
}
}
class Consensus extends React.Component {
constructor(props) {
super(props)
this.state = {
maxH: -1,
tipsets: []
}
this.updateNodes = this.updateNodes.bind(this)
setInterval(this.updateNodes, 333)
}
async updateNodes() {
const nodes = this.props.nodes
let keys = Object.keys(nodes).filter(k => !nodes[k].Storage)
const tipsets = await keys.map(async k => {
const tipset = await nodes[k].conn.call("Filecoin.ChainHead", [])
return [k, tipset]
}).reduce(async(p, i) => ([...await p, await i]), Promise.resolve([]))
const maxH = tipsets.reduce((p, [_, i]) => Math.max(p, i.Height), -1)
this.setState({maxH, tipsets})
}
render() {
return (<Window title={`Consensus`}>
<div className='Consensus'>
<div>Max Height: {this.state.maxH}</div>
<div>
<table cellSpacing={0}>
<thead>
<tr><td>Node</td><td>Height</td><td>TipSet</td></tr>
</thead>
<tbody>
{this.state.tipsets.map(([k, ts]) => {
return (
<tr style={styleForHDiff(this.state.maxH, ts.Height)}>
<td>{k}</td><td>{ts.Height}</td><td><BlockLinks cids={ts.Cids} conn={this.props.nodes[k].conn} mountWindow={this.props.mountWindow}/></td>
</tr>
)
})}
</tbody>
</table>
</div>
</div>
</Window>)
}
}
export default Consensus

View File

@ -1,22 +0,0 @@
import React from "react";
function filStr(raw) {
if(typeof raw !== 'string') {
raw = String(raw)
}
if(raw.length < 19) {
raw = '0'.repeat(19 - raw.length).concat(raw)
}
let out = raw.substring(0, raw.length - 18).concat('.', raw.substring(raw.length - 18, raw.length)).replace(/\.0+$|0+$/g, '');
return out ? out : '0'
}
class Fil extends React.Component {
render() {
return filStr(this.props.children)
}
}
export default Fil

View File

@ -1,173 +0,0 @@
import React from 'react';
import { BlockLinks } from "./BlockLink";
import Address from "./Address";
import ChainExplorer from "./ChainExplorer";
import Client from "./Client";
import Window from "./Window";
class FullNode extends React.Component {
constructor(props) {
super(props)
this.state = {}
this.loadInfo = this.loadInfo.bind(this)
this.newSecpAddr = this.newSecpAddr.bind(this)
this.newBLSAddr = this.newBLSAddr.bind(this)
this.startStorageMiner = this.startStorageMiner.bind(this)
this.explorer = this.explorer.bind(this)
this.client = this.client.bind(this)
this.stop = this.stop.bind(this)
this.loadInfo()
let updates = setInterval(this.loadInfo, 2050)
this.props.client.on('close', () => clearInterval(updates))
}
async loadInfo() {
const id = await this.props.client.call("Filecoin.ID", [])
const version = await this.props.client.call("Filecoin.Version", [])
const peers = await this.props.client.call("Filecoin.NetPeers", [])
const tipset = await this.props.client.call("Filecoin.ChainHead", [])
let addrs = await this.props.client.call('Filecoin.WalletList', [])
let defaultAddr = ""
if (addrs.length > 0) {
defaultAddr = await this.props.client.call('Filecoin.WalletDefaultAddress', [])
}
let paychs = await this.props.client.call('Filecoin.PaychList', [])
if(!paychs)
paychs = []
const vouchers = await Promise.all(paychs.map(paych => {
return this.props.client.call('Filecoin.PaychVoucherList', [paych])
}))
let mpoolPending = (await this.props.client.call('Filecoin.MpoolPending', [tipset.Cids])).length
this.setState(() => ({
id: id,
version: version,
peers: peers.length,
tipset: tipset,
mpoolPending: mpoolPending,
addrs: addrs,
paychs: paychs,
vouchers: vouchers,
defaultAddr: defaultAddr,
}))
}
async newSecpAddr() {
const t = 1
await this.props.client.call("Filecoin.WalletNew", [t])
this.loadInfo()
}
async newBLSAddr() {
const t = 2
await this.props.client.call("Filecoin.WalletNew", [t])
this.loadInfo()
}
startStorageMiner() {
this.props.spawnStorageNode(this.props.node.Repo, this.props.client)
}
explorer() {
this.props.mountWindow((onClose) => <ChainExplorer onClose={onClose} ts={this.state.tipset} client={this.props.client} mountWindow={this.props.mountWindow}/>)
}
client() {
this.props.mountWindow((onClose) => <Client onClose={onClose} node={this.props.node} client={this.props.client} pondClient={this.props.pondClient} mountWindow={this.props.mountWindow}/>)
}
async stop() {
await this.props.stop()
}
render() {
let runtime = <div></div>
if (this.state.id) {
let chainInfo = <div></div>
if (this.state.tipset !== undefined) {
chainInfo = (
<div>
Head: {
<BlockLinks cids={this.state.tipset.Cids} conn={this.props.client} mountWindow={this.props.mountWindow} />
} H:{this.state.tipset.Height} Mp:{this.state.mpoolPending} <a href="#" onClick={this.explorer}>[Explore]</a> <a href="#" onClick={this.client}>[Client]</a>
</div>
)
}
let storageMine = <a href="#" onClick={this.startStorageMiner} hidden={!this.props.spawnStorageNode}>[Spawn Miner]</a>
let addresses = this.state.addrs.map((addr) => {
let line = <Address client={this.props.client} addN={this.props.giveN} add10k={true} nonce={true} addr={addr} mountWindow={this.props.mountWindow}/>
if (this.state.defaultAddr === addr) {
line = <b>{line}</b>
}
return <div key={addr}>{line}</div>
})
let paychannels = this.state.paychs.map((addr, ak) => {
const line = <Address client={this.props.client} addN={this.addN} add10k={true} addr={addr} mountWindow={this.props.mountWindow}/>
const vouchers = this.state.vouchers[ak].map(voucher => {
let extra = <span></span>
if(voucher.Extra) {
extra = <span>Verif: &lt;<b><Address nobalance={true} client={this.props.client} addr={voucher.Extra.Actor} method={voucher.Extra.Method} mountWindow={this.props.mountWindow}/></b>&gt;</span>
}
return <div key={`${addr} ${voucher.Lane} ${voucher.Nonce}`} className="FullNode-voucher">
Voucher Nonce:<b>{voucher.Nonce}</b> Lane:<b>{voucher.Lane}</b> Amt:<b>{voucher.Amount}</b> TL:<b>{voucher.TimeLock}</b> MinCl:<b>{voucher.MinCloseHeight}</b> {extra}
</div>
})
return <div key={addr}>
{line}
{vouchers}
</div>
})
runtime = (
<div>
<div>{this.props.node.ID} - v{this.state.version.Version}, <abbr title={this.state.id}>{this.state.id.substr(-8)}</abbr>, {this.state.peers} peers</div>
<div>Repo: LOTUS_PATH={this.props.node.Repo}</div>
{chainInfo}
<div>
{storageMine}
</div>
<div>
<div>Balances: [New <a href="#" onClick={this.newSecpAddr}>[Secp256k1]</a> <a href="#" onClick={this.newBLSAddr}>[BLS]</a>]</div>
<div>{addresses}</div>
<div>{paychannels}</div>
</div>
</div>
)
}
let nodeID = this.props.node.ID ? this.props.node.ID : ''
let nodePos = this.props.node.ID ? {x: this.props.node.ID*30, y: this.props.node.ID * 30} : 'center'
return (
<Window
title={"Node " + nodeID}
initialPosition={nodePos}
initialSize={{width: 690, height: 300}}
onClose={this.stop} >
<div className="CristalScroll">
<div className="FullNode">
{runtime}
</div>
</div>
</Window>
)
}
}
export default FullNode

View File

@ -1,31 +0,0 @@
import React from 'react'
import { Terminal } from 'xterm';
import { AttachAddon } from "xterm-addon-attach";
import 'xterm/dist/xterm.css';
import * as fit from 'xterm/lib/addons/fit/fit';
import Window from "./Window";
class Logs extends React.Component {
constructor(props) {
super(props);
this.termRef = React.createRef()
this.winRef = React.createRef()
}
async componentDidMount() {
Terminal.applyAddon(fit);
this.terminal = new Terminal({convertEol: true, fontSize: 11});
this.terminal.loadAddon(new AttachAddon(new WebSocket(`ws://127.0.0.1:2222/logs/${this.props.node}`), {bidirectional: false, inputUtf8: true}))
this.terminal.open(this.termRef.current)
setInterval(() => this.terminal.fit(), 200)
}
render() {
return <Window className="Logs-window" onClose={this.props.onClose} initialSize={{width: 1000, height: 480}} title={`Node ${this.props.node} Logs`}>
<div ref={this.termRef} className="Logs"/>
</Window>
}
}
export default Logs

View File

@ -1,110 +0,0 @@
import React from 'react';
import {Link} from "react-router-dom";
import {Client} from "rpc-websockets";
class Index extends React.Component {
constructor(props) {
super(props)
this.state = {rpcUrl: "ws://127.0.0.1:1234/rpc/v0", rpcToken: '', conns: {}, info: {}}
const initialState = JSON.parse(window.localStorage.getItem('saved-nodes'))
if (initialState) {
this.state.nodes = initialState
} else {
this.state.nodes = []
}
this.state.nodes.forEach((n, i) => this.connTo(i, n))
}
componentDidUpdate(prevProps, prevState, snapshot) {
window.localStorage.setItem('saved-nodes', JSON.stringify(this.state.nodes))
//this.state.nodes.filter(i => [i, this.state.conns[i]]).forEach(([i, n]) => this.connTo(i, n))
}
componentWillUnmount() {
Object.keys(this.state.conns).forEach(c => this.state.conns[c].close())
}
async updateInfo(n) {
const conn = this.state.conns[n]
const head = await conn.call('Filecoin.ChainHead', [])
const peers = await conn.call('Filecoin.NetPeers', [])
this.setState(p => ({info: {...p.info, [n]: {head, peers}}}))
}
connTo = async (n, node) => {
const client = new Client(`${node.addr}?token=${node.token}`)
client.on('open', async () => {
this.setState(p => ({conns: {...p.conns, [n]: client}}))
setInterval(() => this.updateInfo(n), 1333)
})
}
onAdd = () => {
this.setState({addingNode: true})
}
update = (name) => (e) => this.setState({ [name]: e.target.value })
tokenOk = () => {
let m = this.state.rpcToken.match(/\.(.+)\./)
// TODO: eww
if(m && atob(m[1]) === '{"Allow":["read","write","sign","admin"]}') {
return (
<span>-Token OK-
<div>
<button onClick={this.addNode}>Add Node</button>
</div>
</span>
)
}
return <span>-Expecting valid admin token-</span>
}
addNode = async () => {
this.setState(p => ({nodes: [...p.nodes, {addr: this.state.rpcUrl, token: this.state.rpcToken}], addingNode: true}))
}
render() {
return (
<div className="Index">
<div className="Index-nodes">
<div>
{
this.state.nodes.map((node, i) => {
let info = <span>[no conn]</span>
if (this.state.info[i]) {
const ni = this.state.info[i]
info = <span>H:{ni.head.Height}; Peers:{ni.peers.length}</span>
}
return <div className="Index-node">
<span>{i}. {node.addr} <Link to={`/app/node/${i}`}>[OPEN UI]</Link> {info}</span>
</div>}
)
}
</div>
<a hidden={this.state.addingNode} href='#' onClick={this.onAdd} className="Button">[Add Node]</a>
<div hidden={!this.state.addingNode}>
<div>---------------</div>
<div>
+ RPC:<input defaultValue={"ws://127.0.0.1:1234/rpc/v0"} onChange={this.update("rpcUrl")}/>
</div>
<div>
Token (<code>lotus auth create-token --perm admin</code>): <input onChange={this.update("rpcToken")}/>{this.tokenOk()}
</div>
</div>
</div>
<div className="Index-footer">
<div>
<Link to={"/app/pond"}>Open Pond</Link>
</div>
</div>
</div>
)
}
}
export default Index

View File

@ -1,198 +0,0 @@
import React from 'react';
import FullNode from "./FullNode";
import ConnMgr from "./ConnMgr";
import Consensus from "./Consensus";
import StorageNode from "./StorageNode";
import {Client} from "rpc-websockets";
import pushMessage from "./chain/send";
import Logs from "./Logs";
import StorageNodeInit from "./StorageNodeInit";
import Window from "./Window";
const [NodeUnknown, NodeRunning, NodeStopped] = [0, 1, 2]
class NodeList extends React.Component {
constructor(props) {
super(props)
this.state = {
existingLoaded: false,
nodes: {},
showConnMgr: false,
showConsensus: false,
}
// This binding is necessary to make `this` work in the callback
this.spawnNode = this.spawnNode.bind(this)
this.spawnStorageNode = this.spawnStorageNode.bind(this)
this.connMgr = this.connMgr.bind(this)
this.consensus = this.consensus.bind(this)
this.transferNFrom1 = this.transferNFrom1.bind(this)
this.getNodes()
}
async mountNode(node) {
const token = await this.props.client.call('Pond.TokenFor', [node.ID])
const client = new Client(`ws://127.0.0.1:${node.APIPort}/rpc/v0?token=${token}`)
client.on('open', async () => {
const id = await client.call("Filecoin.ID", [])
this.setState(prev => ({
nodes: {
...prev.nodes,
[node.ID]: {...node, conn: client, peerid: id}
}
}))
if (!node.Storage) {
this.props.mountWindow((onClose) =>
<FullNode key={node.ID}
node={{...node}}
client={client}
pondClient={this.props.client}
giveN={this.transferNFrom1}
mountWindow={this.props.mountWindow}
spawnStorageNode={this.spawnStorageNode}
stop={this.stopNode(node.ID, onClose)}
/>)
} else {
const fullId = await this.props.client.call('Pond.FullID', [node.ID])
this.props.mountWindow((onClose) =>
<StorageNode node={{...node}}
pondClient={this.props.client}
fullConn={this.state.nodes[fullId].conn}
mountWindow={this.props.mountWindow}
stop={this.stopNode(node.ID, onClose)}
/>)
}
})
}
async getNodes() {
const nds = await this.props.client.call('Pond.Nodes')
const nodes = nds.reduce((o, i) => {o[i.ID] = i; return o}, {})
console.log('nds', nodes)
Object.keys(nodes).map(n => nodes[n]).filter(n => n.State === NodeRunning).forEach(n => this.mountNode(n))
this.setState({existingLoaded: true, nodes: nodes})
}
async transferNFrom1(to, n) {
const addrss = await this.state.nodes[1].conn.call('Filecoin.WalletList', [])
const [bestaddr, bal] = await addrss.map(async addr => {
let balance = 0
try {
balance = await this.state.nodes[1].conn.call('Filecoin.WalletBalance', [addr])
} catch {
balance = -1
}
return [addr, balance]
}).reduce(async (c, n) => (await c)[1] > (await n)[1] ? await c : await n, Promise.resolve(['', -2]))
await pushMessage(this.state.nodes[1].conn, bestaddr, {
To: to,
From: bestaddr,
Value: String(n),
})
}
async spawnNode() {
const node = await this.props.client.call('Pond.Spawn')
console.log(node)
await this.mountNode(node)
this.setState(state => ({nodes: {...state.nodes, [node.ID]: node}}))
}
async spawnStorageNode(fullRepo, fullConn) {
let nodePromise = this.props.client.call('Pond.SpawnStorage', [fullRepo])
this.props.mountWindow((onClose) => <StorageNodeInit node={nodePromise} fullRepo={fullRepo} fullConn={fullConn} pondClient={this.props.client} onClose={onClose} mountWindow={this.props.mountWindow}/>)
let node = await nodePromise
await this.mountNode(node)
//this.setState(state => ({nodes: {...state.nodes, [node.ID]: node}}))
}
stopNode = (id, closeWindow) => async () => {
this.state.nodes[id].conn.close()
await this.props.client.call('Pond.Stop', [id])
closeWindow()
this.setState(prev => ({
nodes: {
...prev.nodes,
[id]: {...(prev.nodes[id]), State: NodeStopped, conn: undefined}
}
}))
}
startNode = (id) => async () => {
let node = await this.props.client.call('Pond.RestartNode', [Number(id)])
await this.mountNode(node)
}
connMgr() {
this.setState({showConnMgr: true})
}
consensus() {
this.setState({showConsensus: true})
}
render() {
let connMgr
if (this.state.showConnMgr) {
connMgr = (<ConnMgr nodes={this.state.nodes}/>)
}
let consensus
if (this.state.showConsensus) {
consensus = (<Consensus nodes={this.state.nodes} mountWindow={this.props.mountWindow}/>)
}
return (
<Window title={"Node List"} initialPosition="bottom-left">
<div className={'NodeList'}>
<div>
<button onClick={this.spawnNode} disabled={!this.state.existingLoaded}>Spawn Node</button>
<button onClick={this.connMgr} disabled={!this.state.existingLoaded && !this.state.showConnMgr}>Connections</button>
<button onClick={this.consensus} disabled={!this.state.existingLoaded && !this.state.showConsensus}>Consensus</button>
</div>
<div>
{Object.keys(this.state.nodes).map(n => {
const nd = this.state.nodes[n]
let type = "FULL"
if (nd.Storage) {
type = "STOR"
}
let logs = "[logs]"
let info = "[CONNECTING..]"
if (nd.conn) {
info = <span>{nd.peerid}</span>
logs = <a href='#' onClick={() => this.props.mountWindow(cl => <Logs node={nd.ID} onClose={cl}/>)}>[logs]</a>
}
if (nd.State === NodeStopped) {
info = <span>[stopped] <a href="#" onClick={this.startNode(n)}>[START]</a></span>
}
return <div key={n}>
{n} {type} {logs} {info}
</div>
})}
</div>
</div>
<div>
{connMgr}
{consensus}
</div>
</Window>
);
}
}
export default NodeList

View File

@ -1,57 +0,0 @@
import React from 'react';
import './App.css';
import { Client } from 'rpc-websockets'
import NodeList from "./NodeList";
class Pond extends React.Component {
constructor(props) {
super(props)
const client = new Client('ws://127.0.0.1:2222/rpc/v0')
client.on('open', () => {
this.setState(() => ({client: client}))
})
this.state = {
windows: {},
nextWindow: 0,
}
this.mountWindow = this.mountWindow.bind(this)
}
mountWindow(cb) {
const id = this.state.nextWindow
this.setState({nextWindow: id + 1})
const window = cb(() => {
this.setState(prev => ({windows: {...prev.windows, [id]: undefined}}))
})
this.setState(prev => ({windows: {...prev.windows, [id]: window}}))
}
render() {
if (this.state.client === undefined) {
return (
<div className="Pond-connecting">
<div>
<div>Connecting to Pond RPC</div>
</div>
</div>
)
}
return (
<div className="App">
<NodeList client={this.state.client} mountWindow={this.mountWindow}/>
<div>
{Object.keys(this.state.windows).map((w, i) => <div key={i}>{this.state.windows[w]}</div>)}
</div>
</div>
)
}
}
export default Pond

View File

@ -1,67 +0,0 @@
import React from 'react';
import './App.css';
import {Client} from "rpc-websockets";
import FullNode from "./FullNode";
class SingleNode extends React.Component {
constructor(props) {
super(props)
const nodes = JSON.parse(window.localStorage.getItem('saved-nodes'))
const node = nodes[this.props.match.params.node]
const client = new Client(`${node.addr}?token=${node.token}`)
client.on('open', async () => {
this.setState(() => ({client: client}))
})
this.state = {
windows: {},
nextWindow: 0,
addr: node.addr
}
}
mountWindow = (cb) => {
const id = this.state.nextWindow
this.setState({nextWindow: id + 1})
const window = cb(() => {
this.setState(prev => ({windows: {...prev.windows, [id]: undefined}}))
})
this.setState(prev => ({windows: {...prev.windows, [id]: window}}))
}
render() {
if (this.state.client === undefined) {
return (
<div className="SingleNode-connecting">
<div>
<div>Connecting to Node RPC:</div>
<div>{`${this.state.addr}?token=****`}</div>
</div>
</div>
)
}
let node = <FullNode
node={{Repo: '/i/dont/exist/fixme', ID: ''}}
client={this.state.client}
give1k={null}
mountWindow={this.mountWindow}
/>
return (
<div className="App">
{node}
<div>
{Object.keys(this.state.windows).map((w, i) => <div key={i}>{this.state.windows[w]}</div>)}
</div>
</div>
)
}
}
export default SingleNode;

View File

@ -1,209 +0,0 @@
import React from 'react'
import Window from "./Window";
import CID from "cids";
import * as multihash from "multihashes";
import code from "./chain/code";
import Address from "./Address";
import Fil from "./Fil";
class State extends React.Component {
byCode = {
[code.init]: InitState,
[code.power]: PowerState,
[code.market]: MarketState,
[code.miner]: MinerState,
}
constructor(props) {
super(props)
this.state = {Balance: -2, State: {}}
}
async componentDidMount() {
const tipset = this.props.tipset || await this.props.client.call("Filecoin.ChainHead", [])
const actstate = await this.props.client.call('Filecoin.StateReadState', [this.props.addr, tipset.Cids])
const c = new CID(this.props.actor.Code['/'])
const mh = multihash.decode(c.multihash)
let code = mh.digest.toString()
this.setState({...actstate, code: code})
}
render() {
let state
if(this.byCode[this.state.code]) {
const Stelem = this.byCode[this.state.code]
state = <Stelem addr={this.props.addr} actor={this.props.actor} client={this.props.client} mountWindow={this.props.mountWindow} tipset={this.props.tipset}/>
} else {
state = <div>{Object.keys(this.state.State || {}).map(k => <div key={k}>{k}: <span>{JSON.stringify(this.state.State[k])}</span></div>)}</div>
}
const content = <div className="State">
<div>Balance: <Fil>{this.state.Balance}</Fil></div>
<div>---</div>
{state}
</div>
return <Window initialSize={{width: 850, height: 400}} onClose={this.props.onClose} title={`Actor ${this.props.addr} ${this.props.tipset && this.props.tipset.Height || ''} ${this.state.code}`}>
{content}
</Window>
}
}
class InitState extends React.Component {
constructor(props) {
super(props)
this.state = {actors: []}
}
async componentDidMount() {
const tipset = await this.props.client.call("Filecoin.ChainHead", []) // TODO: from props
const actors = await this.props.client.call("Filecoin.StateListActors", [tipset.Cids])
this.setState({actors: actors})
}
render() {
return this.state.actors.sort((a, b) => (Number(a.substr(1)) > Number(b.substr(1))))
.map(addr => <div key={addr}><Address addr={addr} client={this.props.client} mountWindow={this.props.mountWindow}/></div>)
}
}
class PowerState extends React.Component {
constructor(props) {
super(props)
this.state = {actors: [], state: {State: {}}}
}
async componentDidMount() {
const tipset = await this.props.client.call("Filecoin.ChainHead", []) // TODO: from props
const actors = await this.props.client.call("Filecoin.StateListMiners", [tipset.Cids])
const state = await this.props.client.call('Filecoin.StateReadState', [this.props.addr, tipset.Cids])
this.setState({actors, state})
}
render() {
return <div>
<div>
<div>Total Power: <b>{this.state.state.State.TotalStorage}</b></div>
</div>
<div>---</div>
<div>{this.state.actors.sort((a, b) => (Number(a.substr(1)) > Number(b.substr(1))))
.map(addr => <div key={addr}><Address miner={true} addr={addr} client={this.props.client} mountWindow={this.props.mountWindow}/></div>)}</div>
</div>
}
}
class MarketState extends React.Component {
constructor(props) {
super(props)
this.state = {participants: {}, deals: []}
}
async componentDidMount() {
const tipset = await this.props.client.call("Filecoin.ChainHead", []) // TODO: from props
const participants = await this.props.client.call("Filecoin.StateMarketParticipants", [tipset.Cids])
const deals = await this.props.client.call("Filecoin.StateMarketDeals", [tipset.Cids])
const state = await this.props.client.call('Filecoin.StateReadState', [this.props.addr, tipset.Cids])
this.setState({participants, deals, nextDeal: state.State.NextDealID})
}
render() {
return <div>
<div>
<div>Participants:</div>
<table>
<tr><td>Address</td><td>Available</td><td>Locked</td></tr>
{Object.keys(this.state.participants).map(p => <tr>
<td><Address addr={p} client={this.props.client} mountWindow={this.props.mountWindow}/></td>
<td>{this.state.participants[p].Available}</td>
<td>{this.state.participants[p].Locked}</td>
</tr>)}
</table>
</div>
<div>
<div>---</div>
<div>Deals ({this.state.nextDeal} Total):</div>
<table>
<tr><td>id</td><td>Started</td><td>Client</td><td>Provider</td><td>Size</td><td>Price</td><td>Duration</td></tr>
{Object.keys(this.state.deals).map(d => <tr>
<td>{d}</td>
<td>{this.state.deals[d].State.SectorStartEpoch || "No"}</td>
<td><Address short={true} addr={this.state.deals[d].Proposal.Client} client={this.props.client} mountWindow={this.props.mountWindow}/></td>
<td><Address short={true} addr={this.state.deals[d].Proposal.Provider} client={this.props.client} mountWindow={this.props.mountWindow}/></td>
<td>{this.state.deals[d].Proposal.PieceSize}B</td>
<td>{this.state.deals[d].Proposal.StoragePricePerEpoch*(this.state.deals[d].Proposal.EndEpoch-this.state.deals[d].Proposal.StartEpoch)}</td>
<td>{this.state.deals[d].Proposal.EndEpoch-this.state.deals[d].Proposal.StartEpoch}</td>
</tr>)}
</table>
</div>
</div>
}
}
class MinerState extends React.Component {
constructor(props) {
super(props)
this.state = {state: {}, sectorSize: -1, worker: "", networkPower: 0, sectors: {}}
}
async componentDidMount() {
const tipset = await this.props.client.call("Filecoin.ChainHead", []) // TODO: from props
const state = await this.props.client.call('Filecoin.StateReadState', [this.props.addr, tipset.Cids])
const sectorSize = await this.props.client.call("Filecoin.StateMinerSectorSize", [this.props.addr, tipset.Cids])
const worker = await this.props.client.call("Filecoin.StateMinerWorker", [this.props.addr, tipset.Cids])
const tpow = await this.props.client.call("Filecoin.StateMinerPower", [this.props.addr, tipset.Cids])
const networkPower = tpow.TotalPower
let sectors = {}
const sset = await this.props.client.call("Filecoin.StateMinerSectors", [this.props.addr, tipset.Cids]) || []
const pset = await this.props.client.call("Filecoin.StateMinerProvingSet", [this.props.addr, tipset.Cids]) || []
sset.forEach(s => sectors[s.SectorID] = {...s, sectorSet: true})
pset.forEach(s => sectors[s.SectorID] = {...(sectors[s.SectorID] || s), provingSet: true})
this.setState({state, sectorSize, worker, networkPower, sectors})
}
render() {
if (!this.state.worker) {
return <span>(...)</span>
}
let state = this.state.state.State
return <div>
<div>Worker: <Address addr={this.state.worker} client={this.props.client} mountWindow={this.props.mountWindow}/></div>
<div>Sector Size: <b>{this.state.sectorSize/1024}</b> KiB</div>
<div>Power: <b>todoPower</b> (<b>{1/this.state.networkPower*100}</b>%)</div>
<div>Election Period Start: <b>{state.ElectionPeriodStart}</b></div>
<div>Slashed: <b>{state.SlashedAt === 0 ? "NO" : state.SlashedAt}</b></div>
<div>
<div>----</div>
<div>Sectors:</div>
<table style={{overflowY: "scroll"}}>
<thead>
<tr><td>ID</td><td>CommD</td><td>CommR</td><td>SectorSet</td><td>Proving</td></tr>
</thead>
<tbody>
{Object.keys(this.state.sectors).map(sid => <tr key={sid} style={{whiteSpace: 'nowrap'}}>
<td>{sid}</td>
<td>{this.state.sectors[sid].CommD}</td>
<td>{this.state.sectors[sid].CommR}</td>
<td>{this.state.sectors[sid].sectorSet ? 'X' : ' '}</td>
<td>{this.state.sectors[sid].provingSet ? 'X' : ' '}</td>
</tr>)}
</tbody>
</table>
</div>
</div>
}
}
export default State

View File

@ -1,161 +0,0 @@
import React from 'react';
import { Client } from 'rpc-websockets'
import Address from "./Address";
import Window from "./Window";
const stateConnected = 'connected'
const stateConnecting = 'connecting'
const stateGettingToken = 'getting-token'
let sealCodes = [
"UndefinedSectorState",
"Empty",
"Packing",
"Unsealed",
"PreCommitting",
"WaitSeed",
"Committing",
"CommitWait",
"FinalizeSector",
"Proving",
"SealFailed",
"PreCommitFailed",
"SealCommitFailed",
"CommitFailed",
"PackingFailed",
"FailedUnrecoverable",
"Faulty",
"FaultReported",
"FaultedFinal",
]
class StorageNode extends React.Component {
constructor(props) {
super(props)
this.state = {
state: stateGettingToken,
id: "~",
mining: false,
statusCounts: [0, 0, 0, 0, 0]
}
this.loadInfo = this.loadInfo.bind(this)
this.pledgeSector = this.pledgeSector.bind(this)
this.stop = this.stop.bind(this)
this.connect()
}
async connect() {
const token = await this.props.pondClient.call('Pond.TokenFor', [this.props.node.ID])
this.setState(() => ({
state: stateConnecting,
token: token,
}))
const client = new Client(`ws://127.0.0.1:${this.props.node.APIPort}/rpc/v0?token=${token}`)
client.on('open', async () => {
this.setState(() => ({
state: stateConnected,
client: client,
version: {Version: "~version~"},
id: "~peerid~",
peers: -1,
balances: []
}))
const id = await this.state.client.call("Filecoin.ID", [])
this.setState(() => ({id: id}))
// this.props.onConnect(client, id) // TODO: dedupe connecting part
let updates = setInterval(this.loadInfo, 1050)
client.on('close', () => clearInterval(updates))
})
console.log(token) // todo: use
}
async loadInfo() {
const version = await this.state.client.call("Filecoin.Version", [])
const peers = await this.state.client.call("Filecoin.NetPeers", [])
const actor = await this.state.client.call("Filecoin.ActorAddress", [])
const actorState = await this.props.fullConn.call('Filecoin.StateReadState', [actor, null])
this.setState({version: version, peers: peers.length, actor: actor, actorState: actorState})
await this.stagedList()
}
async stagedList() {
let stagedList = await this.state.client.call("Filecoin.SectorsList", [])
let staged = await stagedList
.map(sector => this.state.client.call("Filecoin.SectorsStatus", [sector, false]))
.reduce(async (p, n) => [...await p, await n], Promise.resolve([]))
let statusCounts = staged.reduce((p, n) => p.map((e, i) => e + (i === n.State ? 1 : 0) ), [0, 0, 0, 0, 0])
this.setState({staged, statusCounts})
}
async pledgeSector() {
await this.state.client.call("Filecoin.PledgeSector", [])
}
sealStaged = async () => {
await this.state.client.call("Filecoin.SectorsStagedSeal", [])
}
async stop() {
await this.props.stop()
}
render() {
let runtime = <div></div>
if (this.state.actor) {
const pledgeSector = <a href="#" onClick={this.pledgeSector}>[Pledge Sector]</a>
const sealStaged = <a href="#" onClick={this.sealStaged}>[Seal Staged]</a>
runtime = (
<div>
<div>v{this.state.version.Version}, <abbr title={this.state.id}>{this.state.id.substr(-8)}</abbr>, {this.state.peers} peers</div>
<div>Repo: LOTUS_MINER_PATH={this.props.node.Repo}</div>
<div>
{pledgeSector} {sealStaged}
</div>
<div>
<Address client={this.props.fullConn} addr={this.state.actor} mountWindow={this.props.mountWindow}/>
<span>&nbsp;<abbr title="Proving period end">EPS:</abbr> <b>{this.state.actorState.State.ElectionPeriodStart}</b></span>
</div>
<div>{this.state.statusCounts.map((c, i) => <span key={i}>{sealCodes[i]}: {c} | </span>)}</div>
<div>
{this.state.staged ? this.state.staged.map((s, i) => (
<div key={i}>{s.SectorID} {sealCodes[s.State] || `unk ${s.State}`}</div>
)) : <div/>}
</div>
</div>
)
}
return <Window
title={"Miner Node " + this.props.node.ID}
initialPosition={{x: this.props.node.ID*30, y: this.props.node.ID * 30}}
onClose={this.stop} >
<div className="CristalScroll">
<div className="StorageNode">
{runtime}
</div>
</div>
</Window>
}
}
export default StorageNode

View File

@ -1,25 +0,0 @@
import React from 'react';
import Window from "./Window";
class StorageNodeInit extends React.Component {
async componentDidMount() {
const info = await this.props.node
this.props.onClose()
//this.props.mountWindow((onClose) => <StorageNode node={info} fullRepo={this.props.fullRepo} fullConn={this.props.fullConn} pondClient={this.props.pondClient} onClose={onClose} mountWindow={this.props.mountWindow}/>)
}
render() {
return <Window
title={"Miner initializing"}
initialPosition={'center'}>
<div className="CristalScroll">
<div className="StorageNodeInit">
Waiting for init, make sure at least one miner is enabled
</div>
</div>
</Window>
}
}
export default StorageNodeInit

View File

@ -1,15 +0,0 @@
import React from 'react'
import {Cristal} from "react-cristal";
class Window extends React.Component {
render() {
let props = {className: '', ...this.props}
props.className = `${props.className} Window`
return <Cristal {...props}>
{this.props.children}
</Cristal>
}
}
export default Window

View File

@ -1,13 +0,0 @@
{
"account": "fil/1/account",
"cron": "fil/1/cron",
"init": "fil/1/init",
"market": "fil/1/storagemarket",
"miner": "fil/1/storageminer",
"multisig": "fil/1/multisig",
"paych": "fil/1/paymentchannel",
"power": "fil/1/storagepower",
"reward": "fil/1/reward",
"system": "fil/1/system",
"verifreg": "fil/1/verifiedregistry"
}

View File

@ -1,72 +0,0 @@
package main
import (
"encoding/json"
"io/ioutil"
"os"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/chain/actors/builtin"
"github.com/filecoin-project/lotus/chain/consensus/filcns"
)
func main() {
if _, err := os.Stat("code.json"); err != nil {
panic(err) // note: must run in lotuspond/front/src/chain
}
// TODO: ActorUpgrade: this is going to be a problem.
names := map[string]string{
"system": "fil/1/system",
"init": "fil/1/init",
"cron": "fil/1/cron",
"account": "fil/1/account",
"power": "fil/1/storagepower",
"miner": "fil/1/storageminer",
"market": "fil/1/storagemarket",
"paych": "fil/1/paymentchannel",
"multisig": "fil/1/multisig",
"reward": "fil/1/reward",
"verifreg": "fil/1/verifiedregistry",
}
{
b, err := json.MarshalIndent(names, "", " ")
if err != nil {
panic(err)
}
if err := ioutil.WriteFile("code.json", b, 0664); err != nil {
panic(err)
}
}
out := map[string][]string{}
for c, methods := range filcns.NewActorRegistry().Methods {
name := builtin.ActorNameByCode(c)
remaining := len(methods)
// iterate over actor methods in order.
for i := abi.MethodNum(0); remaining > 0; i++ {
m, ok := methods[i]
if !ok {
continue
}
out[name] = append(out[name], m.Num)
remaining--
}
}
{
b, err := json.MarshalIndent(out, "", " ")
if err != nil {
panic(err)
}
if err := ioutil.WriteFile("methods.json", b, 0664); err != nil {
panic(err)
}
}
}

View File

@ -1,955 +0,0 @@
{
"fil/1/account": [
"0",
"1",
"2"
],
"fil/1/cron": [
"0",
"1",
"2"
],
"fil/1/init": [
"0",
"1",
"2"
],
"fil/1/multisig": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/1/paymentchannel": [
"0",
"1",
"2",
"3",
"4"
],
"fil/1/reward": [
"0",
"1",
"2",
"3",
"4"
],
"fil/1/storagemarket": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/1/storageminer": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20"
],
"fil/1/storagepower": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/1/system": [
"0",
"1"
],
"fil/1/verifiedregistry": [
"0",
"1",
"2",
"3",
"4",
"5",
"6"
],
"fil/2/account": [
"0",
"1",
"2"
],
"fil/2/cron": [
"0",
"1",
"2"
],
"fil/2/init": [
"0",
"1",
"2"
],
"fil/2/multisig": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/2/paymentchannel": [
"0",
"1",
"2",
"3",
"4"
],
"fil/2/reward": [
"0",
"1",
"2",
"3",
"4"
],
"fil/2/storagemarket": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/2/storageminer": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23"
],
"fil/2/storagepower": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"8",
"9"
],
"fil/2/system": [
"0",
"1"
],
"fil/2/verifiedregistry": [
"0",
"1",
"2",
"3",
"4",
"5",
"6"
],
"fil/3/account": [
"0",
"1",
"2"
],
"fil/3/cron": [
"0",
"1",
"2"
],
"fil/3/init": [
"0",
"1",
"2"
],
"fil/3/multisig": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/3/paymentchannel": [
"0",
"1",
"2",
"3",
"4"
],
"fil/3/reward": [
"0",
"1",
"2",
"3",
"4"
],
"fil/3/storagemarket": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/3/storageminer": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24"
],
"fil/3/storagepower": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"8",
"9"
],
"fil/3/system": [
"0",
"1"
],
"fil/3/verifiedregistry": [
"0",
"1",
"2",
"3",
"4",
"5",
"6"
],
"fil/4/account": [
"0",
"1",
"2"
],
"fil/4/cron": [
"0",
"1",
"2"
],
"fil/4/init": [
"0",
"1",
"2"
],
"fil/4/multisig": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/4/paymentchannel": [
"0",
"1",
"2",
"3",
"4"
],
"fil/4/reward": [
"0",
"1",
"2",
"3",
"4"
],
"fil/4/storagemarket": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/4/storageminer": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24"
],
"fil/4/storagepower": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"8",
"9"
],
"fil/4/system": [
"0",
"1"
],
"fil/4/verifiedregistry": [
"0",
"1",
"2",
"3",
"4",
"5",
"6"
],
"fil/5/account": [
"0",
"1",
"2"
],
"fil/5/cron": [
"0",
"1",
"2"
],
"fil/5/init": [
"0",
"1",
"2"
],
"fil/5/multisig": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/5/paymentchannel": [
"0",
"1",
"2",
"3",
"4"
],
"fil/5/reward": [
"0",
"1",
"2",
"3",
"4"
],
"fil/5/storagemarket": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/5/storageminer": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24",
"25",
"26"
],
"fil/5/storagepower": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"8",
"9"
],
"fil/5/system": [
"0",
"1"
],
"fil/5/verifiedregistry": [
"0",
"1",
"2",
"3",
"4",
"5",
"6"
],
"fil/6/account": [
"0",
"1",
"2"
],
"fil/6/cron": [
"0",
"1",
"2"
],
"fil/6/init": [
"0",
"1",
"2"
],
"fil/6/multisig": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/6/paymentchannel": [
"0",
"1",
"2",
"3",
"4"
],
"fil/6/reward": [
"0",
"1",
"2",
"3",
"4"
],
"fil/6/storagemarket": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/6/storageminer": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24",
"25",
"26"
],
"fil/6/storagepower": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"8",
"9"
],
"fil/6/system": [
"0",
"1"
],
"fil/6/verifiedregistry": [
"0",
"1",
"2",
"3",
"4",
"5",
"6"
],
"fil/7/account": [
"0",
"1",
"2"
],
"fil/7/cron": [
"0",
"1",
"2"
],
"fil/7/init": [
"0",
"1",
"2"
],
"fil/7/multisig": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/7/paymentchannel": [
"0",
"1",
"2",
"3",
"4"
],
"fil/7/reward": [
"0",
"1",
"2",
"3",
"4"
],
"fil/7/storagemarket": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/7/storageminer": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24",
"25",
"26",
"27"
],
"fil/7/storagepower": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"8",
"9"
],
"fil/7/system": [
"0",
"1"
],
"fil/7/verifiedregistry": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7"
],
"fil/8/account": [
"0",
"1",
"2"
],
"fil/8/cron": [
"0",
"1",
"2"
],
"fil/8/init": [
"0",
"1",
"2"
],
"fil/8/multisig": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/8/paymentchannel": [
"0",
"1",
"2",
"3",
"4"
],
"fil/8/reward": [
"0",
"1",
"2",
"3",
"4"
],
"fil/8/storagemarket": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/8/storageminer": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24",
"25",
"26",
"27"
],
"fil/8/storagepower": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"8",
"9"
],
"fil/8/system": [
"0",
"1"
],
"fil/8/verifiedregistry": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7"
],
"fil/9/account": [
"0",
"1",
"2",
"3"
],
"fil/9/cron": [
"0",
"1",
"2"
],
"fil/9/init": [
"0",
"1",
"2"
],
"fil/9/multisig": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/9/paymentchannel": [
"0",
"1",
"2",
"3",
"4"
],
"fil/9/reward": [
"0",
"1",
"2",
"3",
"4"
],
"fil/9/storagemarket": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
"fil/9/storageminer": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24",
"25",
"26",
"27",
"28",
"29",
"30",
"31"
],
"fil/9/storagepower": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"8",
"9"
],
"fil/9/system": [
"0",
"1"
],
"fil/9/verifiedregistry": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7"
]
}

View File

@ -1,36 +0,0 @@
import util from 'ipld-dag-cbor'
import { Buffer } from 'buffer'
import { Tagged } from 'borc'
async function pushMessage(client, from, inmsg) {
if(!inmsg.Params) {
inmsg.Params = "oA==" // 0b101_00000: empty cbor map: {}
}
if(!inmsg.Value) {
inmsg.Value = "0"
}
if(!inmsg.Method) {
inmsg.Method = 0
}
/* const msg = [
inmsg.To,
inmsg.From,
inmsg.Nonce,
inmsg.Value,
inmsg.GasPrice,
inmsg.GasLimit,
inmsg.Method,
Buffer.from(inmsg.Params, 'base64'),
]*/
console.log(inmsg)
await client.call('Filecoin.MpoolPushMessage', [inmsg, null])
}
export default pushMessage

View File

@ -1,18 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
input[type=text] {
-webkit-appearance: none;
appearance: none;
}

View File

@ -1,6 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));

View File

@ -1,162 +0,0 @@
package main
import (
"fmt"
"net/http"
"os"
"os/exec"
"path"
"strconv"
"github.com/urfave/cli/v2"
"github.com/filecoin-project/go-jsonrpc"
)
const listenAddr = "127.0.0.1:2222"
type runningNode struct {
cmd *exec.Cmd
meta nodeInfo
mux *outmux
stop func()
}
var onCmd = &cli.Command{
Name: "on",
Usage: "run a command on a given node",
Action: func(cctx *cli.Context) error {
client, err := apiClient(cctx.Context)
if err != nil {
return err
}
nd, err := strconv.ParseInt(cctx.Args().Get(0), 10, 32)
if err != nil {
return err
}
node := nodeByID(client.Nodes(), int(nd))
var cmd *exec.Cmd
if !node.Storage {
cmd = exec.Command("./lotus", cctx.Args().Slice()[1:]...)
cmd.Env = []string{
"LOTUS_PATH=" + node.Repo,
}
} else {
cmd = exec.Command("./lotus-miner")
cmd.Env = []string{
"LOTUS_MINER_PATH=" + node.Repo,
"LOTUS_PATH=" + node.FullNode,
}
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
return err
},
}
var shCmd = &cli.Command{
Name: "sh",
Usage: "spawn shell with node shell variables set",
Action: func(cctx *cli.Context) error {
client, err := apiClient(cctx.Context)
if err != nil {
return err
}
nd, err := strconv.ParseInt(cctx.Args().Get(0), 10, 32)
if err != nil {
return err
}
node := nodeByID(client.Nodes(), int(nd))
shcmd := exec.Command("/bin/bash")
if !node.Storage {
shcmd.Env = []string{
"LOTUS_PATH=" + node.Repo,
}
} else {
shcmd.Env = []string{
"LOTUS_MINER_PATH=" + node.Repo,
"LOTUS_PATH=" + node.FullNode,
}
}
shcmd.Env = append(os.Environ(), shcmd.Env...)
shcmd.Stdin = os.Stdin
shcmd.Stdout = os.Stdout
shcmd.Stderr = os.Stderr
fmt.Printf("Entering shell for Node %d\n", nd)
err = shcmd.Run()
fmt.Printf("Closed pond shell\n")
return err
},
}
func nodeByID(nodes []nodeInfo, i int) nodeInfo {
for _, n := range nodes {
if n.ID == int32(i) {
return n
}
}
panic("no node with this id")
}
func logHandler(api *api) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
id, err := strconv.ParseInt(path.Base(req.URL.Path), 10, 32)
if err != nil {
panic(err)
}
api.runningLk.Lock()
n := api.running[int32(id)]
api.runningLk.Unlock()
n.mux.ServeHTTP(w, req)
}
}
var runCmd = &cli.Command{
Name: "run",
Usage: "run lotuspond daemon",
Action: func(cctx *cli.Context) error {
rpcServer := jsonrpc.NewServer()
a := &api{running: map[int32]*runningNode{}}
rpcServer.Register("Pond", a)
http.Handle("/", http.FileServer(http.Dir("lotuspond/front/build")))
http.HandleFunc("/app/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "lotuspond/front/build/index.html")
})
http.Handle("/rpc/v0", rpcServer)
http.HandleFunc("/logs/", logHandler(a))
fmt.Printf("Listening on http://%s\n", listenAddr)
return http.ListenAndServe(listenAddr, nil)
},
}
func main() {
app := &cli.App{
Name: "pond",
Commands: []*cli.Command{
runCmd,
shCmd,
onCmd,
},
}
if err := app.Run(os.Args); err != nil {
panic(err)
}
}

View File

@ -1,127 +0,0 @@
package main
import (
"bufio"
"fmt"
"io"
"net/http"
"strings"
"github.com/gorilla/websocket"
"github.com/opentracing/opentracing-go/log"
)
type outmux struct {
errpw *io.PipeWriter
outpw *io.PipeWriter
errpr *io.PipeReader
outpr *io.PipeReader
n uint64
outs map[uint64]*websocket.Conn
new chan *websocket.Conn
stop chan struct{}
}
func newWsMux() *outmux {
out := &outmux{
n: 0,
outs: map[uint64]*websocket.Conn{},
new: make(chan *websocket.Conn),
stop: make(chan struct{}),
}
out.outpr, out.outpw = io.Pipe()
out.errpr, out.errpw = io.Pipe()
go out.run()
return out
}
func (m *outmux) msgsToChan(r *io.PipeReader, ch chan []byte) {
defer close(ch)
br := bufio.NewReader(r)
for {
buf, _, err := br.ReadLine()
if err != nil {
return
}
out := make([]byte, len(buf)+1)
copy(out, buf)
out[len(out)-1] = '\n'
select {
case ch <- out:
case <-m.stop:
return
}
}
}
func (m *outmux) run() {
stdout := make(chan []byte)
stderr := make(chan []byte)
go m.msgsToChan(m.outpr, stdout)
go m.msgsToChan(m.errpr, stderr)
for {
select {
case msg := <-stdout:
for k, out := range m.outs {
if err := out.WriteMessage(websocket.BinaryMessage, msg); err != nil {
_ = out.Close()
fmt.Printf("outmux write failed: %s\n", err)
delete(m.outs, k)
}
}
case msg := <-stderr:
for k, out := range m.outs {
if err := out.WriteMessage(websocket.BinaryMessage, msg); err != nil {
out.Close()
fmt.Printf("outmux write failed: %s\n", err)
delete(m.outs, k)
}
}
case c := <-m.new:
m.n++
m.outs[m.n] = c
case <-m.stop:
for _, out := range m.outs {
out.Close()
}
return
}
}
}
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func (m *outmux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(r.Header.Get("Connection"), "Upgrade") {
fmt.Println("noupgrade")
w.WriteHeader(500)
return
}
w.Header().Set("Access-Control-Allow-Origin", "*")
if r.Header.Get("Sec-WebSocket-Protocol") != "" {
w.Header().Set("Sec-WebSocket-Protocol", r.Header.Get("Sec-WebSocket-Protocol"))
}
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Error(err)
w.WriteHeader(500)
return
}
m.new <- c
}

View File

@ -1,280 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sync/atomic"
"time"
"github.com/google/uuid"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
"github.com/filecoin-project/lotus/chain/actors/policy"
"github.com/filecoin-project/lotus/chain/gen"
genesis2 "github.com/filecoin-project/lotus/chain/gen/genesis"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/cmd/lotus-seed/seed"
"github.com/filecoin-project/lotus/genesis"
)
func init() {
policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1)
}
func (api *api) Spawn() (nodeInfo, error) {
dir, err := ioutil.TempDir(os.TempDir(), "lotus-")
if err != nil {
return nodeInfo{}, err
}
params := []string{"daemon", "--bootstrap=false"}
genParam := "--genesis=" + api.genesis
id := atomic.AddInt32(&api.cmds, 1)
if id == 1 {
// preseal
genMiner, err := address.NewIDAddress(genesis2.MinerStart)
if err != nil {
return nodeInfo{}, err
}
sbroot := filepath.Join(dir, "preseal")
spt, err := miner.SealProofTypeFromSectorSize(2<<10, build.NewestNetworkVersion)
if err != nil {
return nodeInfo{}, err
}
genm, ki, err := seed.PreSeal(genMiner, spt, 0, 2, sbroot, []byte("8"), nil, false)
if err != nil {
return nodeInfo{}, xerrors.Errorf("preseal failed: %w", err)
}
if err := seed.WriteGenesisMiner(genMiner, sbroot, genm, ki); err != nil {
return nodeInfo{}, xerrors.Errorf("failed to write genminer info: %w", err)
}
params = append(params, "--import-key="+filepath.Join(dir, "preseal", "pre-seal-t01000.key"))
params = append(params, "--genesis-template="+filepath.Join(dir, "preseal", "genesis-template.json"))
// Create template
var template genesis.Template
template.Miners = append(template.Miners, *genm)
template.Accounts = append(template.Accounts, genesis.Actor{
Type: genesis.TAccount,
Balance: types.FromFil(5000000),
Meta: (&genesis.AccountMeta{Owner: genm.Owner}).ActorMeta(),
})
template.VerifregRootKey = gen.DefaultVerifregRootkeyActor
template.RemainderAccount = gen.DefaultRemainderAccountActor
template.NetworkName = "pond-" + uuid.New().String()
template.NetworkVersion = build.NewestNetworkVersion
tb, err := json.Marshal(&template)
if err != nil {
return nodeInfo{}, xerrors.Errorf("marshal genesis template: %w", err)
}
if err := ioutil.WriteFile(filepath.Join(dir, "preseal", "genesis-template.json"), tb, 0664); err != nil {
return nodeInfo{}, xerrors.Errorf("write genesis template: %w", err)
}
// make genesis
genf, err := ioutil.TempFile(os.TempDir(), "lotus-genesis-")
if err != nil {
return nodeInfo{}, err
}
api.genesis = genf.Name()
genParam = "--lotus-make-genesis=" + api.genesis
if err := genf.Close(); err != nil {
return nodeInfo{}, err
}
}
errlogfile, err := os.OpenFile(dir+".err.log", os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nodeInfo{}, err
}
logfile, err := os.OpenFile(dir+".out.log", os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nodeInfo{}, err
}
mux := newWsMux()
confStr := fmt.Sprintf("[API]\nListenAddress = \"/ip4/127.0.0.1/tcp/%d/http\"\n", 2500+id)
err = ioutil.WriteFile(filepath.Join(dir, "config.toml"), []byte(confStr), 0700)
if err != nil {
return nodeInfo{}, err
}
cmd := exec.Command("./lotus", append(params, genParam)...)
cmd.Stderr = io.MultiWriter(os.Stderr, errlogfile, mux.errpw)
cmd.Stdout = io.MultiWriter(os.Stdout, logfile, mux.outpw)
cmd.Env = append(os.Environ(), "LOTUS_PATH="+dir)
if err := cmd.Start(); err != nil {
return nodeInfo{}, err
}
info := nodeInfo{
Repo: dir,
ID: id,
APIPort: 2500 + id,
State: NodeRunning,
}
api.runningLk.Lock()
api.running[id] = &runningNode{
cmd: cmd,
meta: info,
mux: mux,
stop: func() {
cmd.Process.Signal(os.Interrupt)
cmd.Process.Wait()
api.runningLk.Lock()
api.running[id].meta.State = NodeStopped
api.runningLk.Unlock()
},
}
api.runningLk.Unlock()
time.Sleep(time.Millisecond * 750) // TODO: Something less terrible
return info, nil
}
func (api *api) SpawnStorage(fullNodeRepo string) (nodeInfo, error) {
dir, err := ioutil.TempDir(os.TempDir(), "lotus-storage-")
if err != nil {
return nodeInfo{}, err
}
errlogfile, err := os.OpenFile(dir+".err.log", os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nodeInfo{}, err
}
logfile, err := os.OpenFile(dir+".out.log", os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nodeInfo{}, err
}
initArgs := []string{"init", "--nosync"}
if fullNodeRepo == api.running[1].meta.Repo {
presealPrefix := filepath.Join(fullNodeRepo, "preseal")
initArgs = []string{"init", "--actor=t01000", "--genesis-miner", "--pre-sealed-sectors=" + presealPrefix, "--pre-sealed-metadata=" + filepath.Join(presealPrefix, "pre-seal-t01000.json")}
}
id := atomic.AddInt32(&api.cmds, 1)
cmd := exec.Command("./lotus-miner", initArgs...)
cmd.Stderr = io.MultiWriter(os.Stderr, errlogfile)
cmd.Stdout = io.MultiWriter(os.Stdout, logfile)
cmd.Env = append(os.Environ(), "LOTUS_MINER_PATH="+dir, "LOTUS_PATH="+fullNodeRepo)
if err := cmd.Run(); err != nil {
return nodeInfo{}, err
}
time.Sleep(time.Millisecond * 300)
mux := newWsMux()
cmd = exec.Command("./lotus-miner", "run", "--miner-api", fmt.Sprintf("%d", 2500+id), "--nosync")
cmd.Stderr = io.MultiWriter(os.Stderr, errlogfile, mux.errpw)
cmd.Stdout = io.MultiWriter(os.Stdout, logfile, mux.outpw)
cmd.Env = append(os.Environ(), "LOTUS_MINER_PATH="+dir, "LOTUS_PATH="+fullNodeRepo)
if err := cmd.Start(); err != nil {
return nodeInfo{}, err
}
info := nodeInfo{
Repo: dir,
ID: id,
APIPort: 2500 + id,
State: NodeRunning,
FullNode: fullNodeRepo,
Storage: true,
}
api.runningLk.Lock()
api.running[id] = &runningNode{
cmd: cmd,
meta: info,
mux: mux,
stop: func() {
cmd.Process.Signal(os.Interrupt)
cmd.Process.Wait()
api.runningLk.Lock()
api.running[id].meta.State = NodeStopped
api.runningLk.Unlock()
},
}
api.runningLk.Unlock()
time.Sleep(time.Millisecond * 750) // TODO: Something less terrible
return info, nil
}
func (api *api) RestartNode(id int32) (nodeInfo, error) {
api.runningLk.Lock()
defer api.runningLk.Unlock()
nd, ok := api.running[id]
if !ok {
return nodeInfo{}, xerrors.New("node not found")
}
if nd.meta.State != NodeStopped {
return nodeInfo{}, xerrors.New("node not stopped")
}
var cmd *exec.Cmd
if nd.meta.Storage {
cmd = exec.Command("./lotus-miner", "run", "--miner-api", fmt.Sprintf("%d", 2500+id), "--nosync")
} else {
cmd = exec.Command("./lotus", "daemon", "--api", fmt.Sprintf("%d", 2500+id))
}
cmd.Stderr = nd.cmd.Stderr // recycle old vars
cmd.Stdout = nd.cmd.Stdout
cmd.Env = nd.cmd.Env
if err := cmd.Start(); err != nil {
return nodeInfo{}, err
}
nd.cmd = cmd
nd.stop = func() {
cmd.Process.Signal(os.Interrupt)
cmd.Process.Wait()
api.runningLk.Lock()
api.running[id].meta.State = NodeStopped
api.runningLk.Unlock()
}
nd.meta.State = NodeRunning
time.Sleep(time.Millisecond * 750) // TODO: Something less terrible
return nd.meta, nil
}