Merge pull request #149 from filecoin-project/feat/pond-gas-updates

pond: Gas-related updates
This commit is contained in:
Łukasz Magiera 2019-08-20 13:54:30 +02:00 committed by GitHub
commit 78b548ab2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 275 additions and 32 deletions

View File

@ -15,7 +15,7 @@ import (
) )
func main() { func main() {
logging.SetLogLevel("*", "DEBUG") logging.SetLogLevel("*", "INFO")
local := []*cli.Command{ local := []*cli.Command{
DaemonCmd, DaemonCmd,
} }

1
go.mod
View File

@ -59,6 +59,7 @@ require (
github.com/multiformats/go-multiaddr-dns v0.0.3 github.com/multiformats/go-multiaddr-dns v0.0.3
github.com/multiformats/go-multiaddr-net v0.0.1 github.com/multiformats/go-multiaddr-net v0.0.1
github.com/multiformats/go-multihash v0.0.6 github.com/multiformats/go-multihash v0.0.6
github.com/opentracing/opentracing-go v1.1.0
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.8.1
github.com/polydawn/refmt v0.0.0-20190809202753-05966cbd336a github.com/polydawn/refmt v0.0.0-20190809202753-05966cbd336a
github.com/smartystreets/assertions v1.0.1 // indirect github.com/smartystreets/assertions v1.0.1 // indirect

View File

@ -65,9 +65,11 @@ func (api *api) Spawn() (nodeInfo, error) {
return nodeInfo{}, err return nodeInfo{}, err
} }
mux := newWsMux()
cmd := exec.Command("./lotus", "daemon", genParam, "--api", fmt.Sprintf("%d", 2500+id)) cmd := exec.Command("./lotus", "daemon", genParam, "--api", fmt.Sprintf("%d", 2500+id))
cmd.Stderr = io.MultiWriter(os.Stderr, errlogfile) cmd.Stderr = io.MultiWriter(os.Stderr, errlogfile, mux.errpw)
cmd.Stdout = io.MultiWriter(os.Stdout, logfile) cmd.Stdout = io.MultiWriter(os.Stdout, logfile, mux.outpw)
cmd.Env = []string{"LOTUS_PATH=" + dir} cmd.Env = []string{"LOTUS_PATH=" + dir}
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return nodeInfo{}, err return nodeInfo{}, err
@ -84,7 +86,9 @@ func (api *api) Spawn() (nodeInfo, error) {
cmd: cmd, cmd: cmd,
meta: info, meta: info,
mux: mux,
stop: func() { stop: func() {
defer close(mux.stop)
defer errlogfile.Close() defer errlogfile.Close()
defer logfile.Close() defer logfile.Close()
}, },
@ -156,9 +160,11 @@ func (api *api) SpawnStorage(fullNodeRepo string) (nodeInfo, error) {
time.Sleep(time.Millisecond * 300) time.Sleep(time.Millisecond * 300)
mux := newWsMux()
cmd = exec.Command("./lotus-storage-miner", "run", "--api", fmt.Sprintf("%d", 2500+id)) cmd = exec.Command("./lotus-storage-miner", "run", "--api", fmt.Sprintf("%d", 2500+id))
cmd.Stderr = io.MultiWriter(os.Stderr, errlogfile) cmd.Stderr = io.MultiWriter(os.Stderr, errlogfile, mux.errpw)
cmd.Stdout = io.MultiWriter(os.Stdout, logfile) cmd.Stdout = io.MultiWriter(os.Stdout, logfile, mux.outpw)
cmd.Env = []string{"LOTUS_STORAGE_PATH=" + dir, "LOTUS_PATH=" + fullNodeRepo} cmd.Env = []string{"LOTUS_STORAGE_PATH=" + dir, "LOTUS_PATH=" + fullNodeRepo}
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return nodeInfo{}, err return nodeInfo{}, err
@ -178,7 +184,9 @@ func (api *api) SpawnStorage(fullNodeRepo string) (nodeInfo, error) {
cmd: cmd, cmd: cmd,
meta: info, meta: info,
mux: mux,
stop: func() { stop: func() {
defer close(mux.stop)
defer errlogfile.Close() defer errlogfile.Close()
defer logfile.Close() defer logfile.Close()
}, },

View File

@ -13075,6 +13075,21 @@
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
}, },
"xterm": {
"version": "3.14.5",
"resolved": "https://registry.npmjs.org/xterm/-/xterm-3.14.5.tgz",
"integrity": "sha512-DVmQ8jlEtL+WbBKUZuMxHMBgK/yeIZwkXB81bH+MGaKKnJGYwA+770hzhXPfwEIokK9On9YIFPRleVp/5G7z9g=="
},
"xterm-addon-attach": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/xterm-addon-attach/-/xterm-addon-attach-0.1.0.tgz",
"integrity": "sha512-vImYAP+AVoW/gnr4CIESrOr2MplzNxnrPX4YEkdk0EEkBOg3Pwnndu1xy7HnY0XZsfGRz7rfn71sAXfJDGLvUQ=="
},
"xterm-addon-fit": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.1.0.tgz",
"integrity": "sha512-DzYThnR5rXYX7JrOZ8rHGMU36BiTwYNFUOhhNwrDSFvoUR2MgwQrfA/JrqLE62KRj0D8bkRR7+xe7qGBp1O4Rw=="
},
"y18n": { "y18n": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",

View File

@ -13,7 +13,10 @@
"react-dom": "^16.8.6", "react-dom": "^16.8.6",
"react-scripts": "3.0.1", "react-scripts": "3.0.1",
"rpc-websockets": "^4.5.1", "rpc-websockets": "^4.5.1",
"styled-components": "^3.3.3" "styled-components": "^3.3.3",
"xterm": "^3.14.5",
"xterm-addon-attach": "^0.1.0",
"xterm-addon-fit": "^0.1.0"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -4,9 +4,9 @@ import * as multihash from "multihashes";
import State from "./State"; import State from "./State";
import methods from "./chain/methods"; import methods from "./chain/methods";
function truncAddr(addr) { function truncAddr(addr, len) {
if (addr.length > 21) { if (addr.length > len) {
return <abbr title={addr}>{addr.substr(0, 18) + '..'}</abbr> return <abbr title={addr}>{addr.substr(0, len - 3) + '..'}</abbr>
} }
return addr return addr
} }
@ -72,7 +72,7 @@ class Address extends React.Component {
if(this.props.add1k) { if(this.props.add1k) {
add1k = <span>&nbsp;<a href="#" onClick={() => this.props.add1k(this.props.addr)}>[+1k]</a></span> add1k = <span>&nbsp;<a href="#" onClick={() => this.props.add1k(this.props.addr)}>[+1k]</a></span>
} }
let addr = truncAddr(this.props.addr) let addr = truncAddr(this.props.addr, this.props.short ? 12 : 17)
let actInfo = <span>(?)</span> let actInfo = <span>(?)</span>
if(this.state.balance >= 0) { if(this.state.balance >= 0) {
@ -80,17 +80,21 @@ class Address extends React.Component {
addr = <a href="#" onClick={this.openState}>{addr}</a> addr = <a href="#" onClick={this.openState}>{addr}</a>
} }
let balance = <span>:&nbsp;{this.state.balance}</span> let balance = <span>:&nbsp;{this.state.balance}&nbsp;</span>
if(this.props.nobalance) { if(this.props.nobalance) {
balance = <span></span> balance = <span/>
}
if(this.props.short) {
actInfo = <span/>
balance = <span/>
} }
let transfer = <span></span> let transfer = <span/>
if(this.props.transfer) { if(this.props.transfer) {
transfer = <span>&nbsp;{this.props.transfer}FIL</span> transfer = <span>&nbsp;{this.props.transfer}FIL</span>
} }
return <span>{addr}{balance}&nbsp;{actInfo}{add1k}{transfer}</span> return <span>{addr}{balance}{actInfo}{add1k}{transfer}</span>
} }
} }

View File

@ -64,6 +64,7 @@
} }
.ChainExplorer-at { .ChainExplorer-at {
min-width: 40em;
background: #77ff77; background: #77ff77;
} }
@ -74,3 +75,12 @@
.ChainExplorer-before { .ChainExplorer-before {
background: #cccc00 background: #cccc00
} }
.Logs {
width: 100%;
height: 100%;
}
.Logs-window :nth-child(2) {
height: 100%;
}

View File

@ -14,8 +14,16 @@ class Block extends React.Component {
async loadHeader() { async loadHeader() {
const header = await this.props.conn.call('Filecoin.ChainGetBlock', [this.props.cid]) const header = await this.props.conn.call('Filecoin.ChainGetBlock', [this.props.cid])
const messages = await this.props.conn.call('Filecoin.ChainGetBlockMessages', [this.props.cid]) let messages = await this.props.conn.call('Filecoin.ChainGetBlockMessages', [this.props.cid])
console.log(messages) let receipts = await this.props.conn.call('Filecoin.ChainGetBlockReceipts', [this.props.cid])
messages = [
...(messages.BlsMessages.map(m => ({...m, type: 'BLS'}))),
...(messages.SecpkMessages.map(m => ({...(m.Message), type: 'Secpk'})))
]
messages = messages.map((msg, k) => ({...msg, receipt: receipts[k]}))
this.setState({header: header, messages: messages}) this.setState({header: header, messages: messages})
} }
@ -24,15 +32,12 @@ class Block extends React.Component {
if (this.state.header) { if (this.state.header) {
let head = this.state.header let head = this.state.header
const messages = this.state.messages.map(m => (
let messages = [
...(this.state.messages.BlsMessages.map(m => ({...m, type: 'BLS'}))),
...(this.state.messages.SecpkMessages.map(m => ({...(m.Message), type: 'Secpk'})))
].map(m => (
<div> <div>
<Address client={this.props.conn} addr={m.From} mountWindow={this.props.mountWindow}/><b>&nbsp;=>&nbsp;</b> <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}/> <Address client={this.props.conn} addr={m.To} mountWindow={this.props.mountWindow} transfer={m.Value} method={m.Method}/>
<span>&nbsp;{m.receipt.GasUsed}Gas</span>
{m.receipt.ExitCode !== 0 ? <span>&nbsp;<b>EXIT:{m.receipt.ExitCode}</b></span> : <span/>}
</div> </div>
)) ))

View File

@ -1,10 +1,19 @@
import React from 'react'; import React from 'react';
import Block from "./Block"; import Block from "./Block";
import Address from "./Address";
export class BlockLinks extends React.Component { export class BlockLinks extends React.Component {
render() { render() {
return this.props.cids.map(c => <BlockLink key={c} conn={this.props.conn} cid={c} mountWindow={this.props.mountWindow}/>) return this.props.cids.map((c, k) => {
let block
if(this.props.blocks) {
block = this.props.blocks[k]
}
return <BlockLink key={c} block={block} conn={this.props.conn} cid={c} mountWindow={this.props.mountWindow}/>
})
} }
} }
@ -20,7 +29,12 @@ class BlockLink extends React.Component {
} }
render() { render() {
return <a href="#" onClick={this.openBlockViewer}><abbr title={this.props.cid['/']}>{this.props.cid['/'].substr(-8)}</abbr></a> 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>
} }
} }

View File

@ -114,12 +114,15 @@ class ChainExplorer extends React.Component {
const ts = this.state.cache[row] const ts = this.state.cache[row]
let msgc = -1 let msgc = -1
if(ts.Cids[0] && this.state.messages[ts.Cids[0]['/']]) { if(ts.Cids[0] && this.state.messages[ts.Cids[0]['/']]) { // TODO: get from all blks
msgc = this.state.messages[ts.Cids[0]['/']].SecpkMessages.length + this.state.messages[ts.Cids[0]['/']].BlsMessages.length msgc = this.state.messages[ts.Cids[0]['/']].SecpkMessages.length + this.state.messages[ts.Cids[0]['/']].BlsMessages.length
} }
if(msgc > 0) {
msgc = <b>{msgc}</b>
}
info = <span> info = <span>
<BlockLinks cids={ts.Cids} conn={this.props.client} mountWindow={this.props.mountWindow} /> Msgs: <b>{msgc}</b> <BlockLinks cids={ts.Cids} blocks={ts.Blocks} conn={this.props.client} mountWindow={this.props.mountWindow} /> Msgs: {msgc}
</span> </span>
} }

View File

@ -63,10 +63,10 @@ class FullNode extends React.Component {
// TODO: Use actual miner address // TODO: Use actual miner address
// see cli/miner.go // see cli/miner.go
this.setState({mining: true}) this.setState({mining: true})
let addr = "t0523423423" // in case we have no wallets let addr = "t0101" // in case we have no wallets
if (this.state.defaultAddr) { /*if (this.state.defaultAddr) {
addr = this.state.defaultAddr addr = this.state.defaultAddr
} }*/
this.setState({mining: true}) this.setState({mining: true})
await this.props.client.call("Filecoin.MinerStart", [addr]) await this.props.client.call("Filecoin.MinerStart", [addr])

View File

@ -0,0 +1,31 @@
import React from 'react'
import {Cristal} from "react-cristal";
import { Terminal } from 'xterm';
import { AttachAddon } from "xterm-addon-attach";
import 'xterm/dist/xterm.css';
import * as fit from 'xterm/lib/addons/fit/fit';
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 <Cristal className="Logs-window" onClose={this.props.onClose} initialSize={{width: 1000, height: 480}} title={`Node ${this.props.node} Logs`}>
<div ref={this.termRef} className="Logs"/>
</Cristal>
}
}
export default Logs

View File

@ -6,6 +6,7 @@ import {Cristal} from "react-cristal";
import StorageNode from "./StorageNode"; import StorageNode from "./StorageNode";
import {Client} from "rpc-websockets"; import {Client} from "rpc-websockets";
import pushMessage from "./chain/send"; import pushMessage from "./chain/send";
import Logs from "./Logs";
class NodeList extends React.Component { class NodeList extends React.Component {
constructor(props) { constructor(props) {
@ -130,13 +131,15 @@ class NodeList extends React.Component {
type = "STOR" type = "STOR"
} }
let logs = "[logs]"
let info = "[CONNECTING..]" let info = "[CONNECTING..]"
if (nd.conn) { if (nd.conn) {
info = <span>{nd.peerid}</span> info = <span>{nd.peerid}</span>
logs = <a href='#' onClick={() => this.props.mountWindow(cl => <Logs node={nd.ID} onClose={cl}/>)}>[logs]</a>
} }
return <div key={n}> return <div key={n}>
{n} {type} {info} {n} {type} {logs} {info}
</div> </div>
})} })}
</div> </div>

View File

@ -4,7 +4,7 @@ import { Tagged } from 'borc'
async function pushMessage(client, from, inmsg) { async function pushMessage(client, from, inmsg) {
if(!inmsg.GasLimit) { if(!inmsg.GasLimit) {
inmsg.GasLimit = "0" inmsg.GasLimit = "1000"
} }
if(!inmsg.GasPrice) { if(!inmsg.GasPrice) {
inmsg.GasPrice = "0" inmsg.GasPrice = "0"

View File

@ -5,6 +5,7 @@ import (
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"path"
"strconv" "strconv"
"github.com/filecoin-project/go-lotus/lib/jsonrpc" "github.com/filecoin-project/go-lotus/lib/jsonrpc"
@ -18,6 +19,7 @@ type runningNode struct {
cmd *exec.Cmd cmd *exec.Cmd
meta nodeInfo meta nodeInfo
mux *outmux
stop func() stop func()
} }
@ -107,15 +109,33 @@ func nodeById(nodes []nodeInfo, i int) nodeInfo {
panic("no node with this id") 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)
return
}
api.runningLk.Lock()
n := api.running[int32(id)]
api.runningLk.Unlock()
n.mux.ServeHTTP(w, req)
}
}
var runCmd = &cli.Command{ var runCmd = &cli.Command{
Name: "run", Name: "run",
Usage: "run lotuspond daemon", Usage: "run lotuspond daemon",
Action: func(cctx *cli.Context) error { Action: func(cctx *cli.Context) error {
rpcServer := jsonrpc.NewServer() rpcServer := jsonrpc.NewServer()
rpcServer.Register("Pond", &api{running: map[int32]runningNode{}}) a := &api{running: map[int32]runningNode{}}
rpcServer.Register("Pond", a)
http.Handle("/", http.FileServer(http.Dir("lotuspond/front/build"))) http.Handle("/", http.FileServer(http.Dir("lotuspond/front/build")))
http.Handle("/rpc/v0", rpcServer) http.Handle("/rpc/v0", rpcServer)
http.HandleFunc("/logs/", logHandler(a))
fmt.Printf("Listening on http://%s\n", listenAddr) fmt.Printf("Listening on http://%s\n", listenAddr)
return http.ListenAndServe(listenAddr, nil) return http.ListenAndServe(listenAddr, nil)

124
lotuspond/outmux.go Normal file
View File

@ -0,0 +1,124 @@
package main
import (
"fmt"
"github.com/gorilla/websocket"
"github.com/opentracing/opentracing-go/log"
"io"
"net/http"
"strings"
"sync"
)
type outmux struct {
lk sync.Mutex
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)
for {
buf := make([]byte, 1)
n, err := r.Read(buf)
if err != nil {
return
}
select {
case ch <- buf[:n]:
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
return
}

View File

@ -80,6 +80,8 @@ func (m *Miner) Mine(ctx context.Context) {
b, err := m.mineOne(ctx, base) b, err := m.mineOne(ctx, base)
if err != nil { if err != nil {
log.Errorf("mining block failed: %s", err) log.Errorf("mining block failed: %s", err)
log.Warn("waiting 400ms before attempting to mine a block")
time.Sleep(400 * time.Millisecond)
continue continue
} }