Merge pull request #143 from filecoin-project/feat/pond-explorer
pond: Chain Explorer
This commit is contained in:
commit
7402c54b67
@ -4,15 +4,18 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
cbor "github.com/ipfs/go-ipld-cbor"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
actors "github.com/filecoin-project/go-lotus/chain/actors"
|
actors "github.com/filecoin-project/go-lotus/chain/actors"
|
||||||
"github.com/filecoin-project/go-lotus/chain/actors/aerrors"
|
"github.com/filecoin-project/go-lotus/chain/actors/aerrors"
|
||||||
"github.com/filecoin-project/go-lotus/chain/types"
|
"github.com/filecoin-project/go-lotus/chain/types"
|
||||||
"github.com/ipfs/go-cid"
|
|
||||||
cbor "github.com/ipfs/go-ipld-cbor"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type invoker struct {
|
type invoker struct {
|
||||||
builtInCode map[cid.Cid]nativeCode
|
builtInCode map[cid.Cid]nativeCode
|
||||||
|
builtInState map[cid.Cid]reflect.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
type invokeFunc func(act *types.Actor, vmctx types.VMContext, params []byte) ([]byte, aerrors.ActorError)
|
type invokeFunc func(act *types.Actor, vmctx types.VMContext, params []byte) ([]byte, aerrors.ActorError)
|
||||||
@ -20,15 +23,16 @@ type nativeCode []invokeFunc
|
|||||||
|
|
||||||
func newInvoker() *invoker {
|
func newInvoker() *invoker {
|
||||||
inv := &invoker{
|
inv := &invoker{
|
||||||
builtInCode: make(map[cid.Cid]nativeCode),
|
builtInCode: make(map[cid.Cid]nativeCode),
|
||||||
|
builtInState: make(map[cid.Cid]reflect.Type),
|
||||||
}
|
}
|
||||||
|
|
||||||
// add builtInCode using: register(cid, singleton)
|
// add builtInCode using: register(cid, singleton)
|
||||||
inv.register(actors.InitActorCodeCid, actors.InitActor{})
|
inv.register(actors.InitActorCodeCid, actors.InitActor{}, actors.InitActorState{})
|
||||||
inv.register(actors.StorageMarketActorCodeCid, actors.StorageMarketActor{})
|
inv.register(actors.StorageMarketActorCodeCid, actors.StorageMarketActor{}, actors.StorageMarketState{})
|
||||||
inv.register(actors.StorageMinerCodeCid, actors.StorageMinerActor{})
|
inv.register(actors.StorageMinerCodeCid, actors.StorageMinerActor{}, actors.StorageMinerActorState{})
|
||||||
inv.register(actors.MultisigActorCodeCid, actors.MultiSigActor{})
|
inv.register(actors.MultisigActorCodeCid, actors.MultiSigActor{}, actors.MultiSigActorState{})
|
||||||
inv.register(actors.PaymentChannelActorCodeCid, actors.PaymentChannelActor{})
|
inv.register(actors.PaymentChannelActorCodeCid, actors.PaymentChannelActor{}, actors.PaymentChannelActorState{})
|
||||||
|
|
||||||
return inv
|
return inv
|
||||||
}
|
}
|
||||||
@ -46,12 +50,13 @@ func (inv *invoker) Invoke(act *types.Actor, vmctx types.VMContext, method uint6
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (inv *invoker) register(c cid.Cid, instance Invokee) {
|
func (inv *invoker) register(c cid.Cid, instance Invokee, state interface{}) {
|
||||||
code, err := inv.transform(instance)
|
code, err := inv.transform(instance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
inv.builtInCode[c] = code
|
inv.builtInCode[c] = code
|
||||||
|
inv.builtInState[c] = reflect.TypeOf(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Invokee interface {
|
type Invokee interface {
|
||||||
@ -136,3 +141,19 @@ func (*invoker) transform(instance Invokee) (nativeCode, error) {
|
|||||||
}
|
}
|
||||||
return code, nil
|
return code, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DumpActorState(code cid.Cid, b []byte) (interface{}, error) {
|
||||||
|
i := newInvoker() // TODO: register builtins in init block
|
||||||
|
|
||||||
|
typ, ok := i.builtInState[code]
|
||||||
|
if !ok {
|
||||||
|
return nil, xerrors.New("state type for actor not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := reflect.New(typ)
|
||||||
|
if err := cbor.DecodeInto(b, rv.Interface()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv.Elem().Interface(), nil
|
||||||
|
}
|
||||||
|
@ -29,33 +29,47 @@ class Address extends React.Component {
|
|||||||
async refresh() {
|
async refresh() {
|
||||||
let balance = 0
|
let balance = 0
|
||||||
let actor = {}
|
let actor = {}
|
||||||
|
let actorInfo
|
||||||
|
|
||||||
try {
|
try {
|
||||||
balance = await this.props.client.call('Filecoin.WalletBalance', [this.props.addr])
|
balance = await this.props.client.call('Filecoin.WalletBalance', [this.props.addr])
|
||||||
actor = await this.props.client.call('Filecoin.ChainGetActor', [this.props.addr, this.props.ts || null])
|
actor = await this.props.client.call('Filecoin.ChainGetActor', [this.props.addr, this.props.ts || null])
|
||||||
|
actorInfo = await this.actorInfo(actor)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
balance = -1
|
balance = -1
|
||||||
}
|
}
|
||||||
this.setState({balance, actor})
|
this.setState({balance, actor, actorInfo})
|
||||||
}
|
}
|
||||||
|
|
||||||
openState() {
|
openState() {
|
||||||
this.props.mountWindow((onClose) => <State addr={this.props.addr} actor={this.state.actor} client={this.props.client} onClose={onClose}/>)
|
this.props.mountWindow((onClose) => <State addr={this.props.addr} actor={this.state.actor} client={this.props.client} onClose={onClose}/>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async actorInfo(actor) {
|
||||||
|
const c = new CID(actor.Code['/'])
|
||||||
|
const mh = multihash.decode(c.multihash) // TODO: check identity
|
||||||
|
|
||||||
|
let info = <span>({mh.digest.toString()})</span>
|
||||||
|
switch(mh.digest.toString()) {
|
||||||
|
case 'paych':
|
||||||
|
const actstate = await this.props.client.call('Filecoin.ChainReadState', [actor, this.props.ts || null])
|
||||||
|
info = <span>({mh.digest.toString()} to <Address nobalance={true} client={this.props.client} addr={actstate.State.To} mountWindow={this.props.mountWindow}/>)</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let add1k = <span/>
|
let add1k = <span/>
|
||||||
if(this.props.add1k) {
|
if(this.props.add1k) {
|
||||||
add1k = <a href="#" onClick={() => this.props.add1k(this.props.addr)}>[+1k]</a>
|
add1k = <span> <a href="#" onClick={() => this.props.add1k(this.props.addr)}>[+1k]</a></span>
|
||||||
}
|
}
|
||||||
let addr = truncAddr(this.props.addr)
|
let addr = truncAddr(this.props.addr)
|
||||||
|
|
||||||
let actInfo = <span>(?)</span>
|
let actInfo = <span>(?)</span>
|
||||||
if(this.state.balance >= 0) {
|
if(this.state.balance >= 0) {
|
||||||
const c = new CID(this.state.actor.Code['/'])
|
actInfo = this.state.actorInfo
|
||||||
const mh = multihash.decode(c.multihash) // TODO: check identity
|
|
||||||
|
|
||||||
actInfo = <span>({mh.digest.toString()})</span>
|
|
||||||
addr = <a href="#" onClick={this.openState}>{addr}</a>
|
addr = <a href="#" onClick={this.openState}>{addr}</a>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +78,7 @@ class Address extends React.Component {
|
|||||||
balance = <span></span>
|
balance = <span></span>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <span>{addr}{balance} {actInfo} {add1k}</span>
|
return <span>{addr}{balance} {actInfo}{add1k}</span>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,4 +57,20 @@
|
|||||||
|
|
||||||
.Consensus {
|
.Consensus {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ChainExplorer {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ChainExplorer-at {
|
||||||
|
background: #77ff77;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ChainExplorer-after {
|
||||||
|
background: #cc9c44
|
||||||
|
}
|
||||||
|
|
||||||
|
.ChainExplorer-before {
|
||||||
|
background: #cccc00
|
||||||
|
}
|
||||||
|
135
lotuspond/front/src/ChainExplorer.js
Normal file
135
lotuspond/front/src/ChainExplorer.js
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {Cristal} from "react-cristal";
|
||||||
|
import {BlockLinks} from "./BlockLink";
|
||||||
|
|
||||||
|
const rows = 32
|
||||||
|
|
||||||
|
class ChainExplorer extends React.Component {
|
||||||
|
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.ChainGetBlockMessages', [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, cache, msgcache) {
|
||||||
|
const cids = cache[h + 1].Blocks.map(b => b.Parents).reduce((acc, val) => acc.concat(val), [])
|
||||||
|
const blocks = await Promise.all(cids.map(cid => this.props.client.call('Filecoin.ChainGetBlock', [cid])))
|
||||||
|
|
||||||
|
cache[h] = {
|
||||||
|
Height: h,
|
||||||
|
Cids: cids,
|
||||||
|
Blocks: blocks,
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.updateMessages(cids, msgcache)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tofetch = Array(rows).fill(0).map((_, i) => top - i)
|
||||||
|
.filter(v => !this.state.cache[v])
|
||||||
|
|
||||||
|
let cache = {...this.state.cache}
|
||||||
|
let msgcache = {...this.state.messages}
|
||||||
|
|
||||||
|
await tofetch.reduce(async (prev, next) => [...await prev, await this.fetch(next, cache, msgcache)], Promise.resolve([]))
|
||||||
|
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>
|
||||||
|
if(this.state.cache[row]) {
|
||||||
|
const ts = this.state.cache[row]
|
||||||
|
|
||||||
|
let msgc = -1
|
||||||
|
if(ts.Cids[0] && this.state.messages[ts.Cids[0]['/']]) {
|
||||||
|
msgc = this.state.messages[ts.Cids[0]['/']].SecpkMessages.length + this.state.messages[ts.Cids[0]['/']].BlsMessages.length
|
||||||
|
}
|
||||||
|
|
||||||
|
info = <span>
|
||||||
|
<BlockLinks cids={ts.Cids} conn={this.props.client} mountWindow={this.props.mountWindow} /> Msgs: <b>{msgc}</b>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div key={row} className={className}>@{row} {info}</div>
|
||||||
|
})}</div>
|
||||||
|
|
||||||
|
return (<Cristal onClose={this.props.onClose} title={`Chain Explorer ${this.state.follow ? '(Following)' : ''}`}>
|
||||||
|
{content}
|
||||||
|
</Cristal>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChainExplorer
|
@ -4,10 +4,7 @@ import Cristal from 'react-cristal'
|
|||||||
import { BlockLinks } from "./BlockLink";
|
import { BlockLinks } from "./BlockLink";
|
||||||
import StorageNodeInit from "./StorageNodeInit";
|
import StorageNodeInit from "./StorageNodeInit";
|
||||||
import Address from "./Address";
|
import Address from "./Address";
|
||||||
|
import ChainExplorer from "./ChainExplorer";
|
||||||
async function awaitListReducer(prev, c) {
|
|
||||||
return [...await prev, await c]
|
|
||||||
}
|
|
||||||
|
|
||||||
class FullNode extends React.Component {
|
class FullNode extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -22,6 +19,7 @@ class FullNode extends React.Component {
|
|||||||
this.newScepAddr = this.newScepAddr.bind(this)
|
this.newScepAddr = this.newScepAddr.bind(this)
|
||||||
this.startStorageMiner = this.startStorageMiner.bind(this)
|
this.startStorageMiner = this.startStorageMiner.bind(this)
|
||||||
this.add1k = this.add1k.bind(this)
|
this.add1k = this.add1k.bind(this)
|
||||||
|
this.explorer = this.explorer.bind(this)
|
||||||
|
|
||||||
this.loadInfo()
|
this.loadInfo()
|
||||||
setInterval(this.loadInfo, 2050)
|
setInterval(this.loadInfo, 2050)
|
||||||
@ -88,6 +86,10 @@ class FullNode extends React.Component {
|
|||||||
await this.props.give1k(to)
|
await this.props.give1k(to)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
explorer() {
|
||||||
|
this.props.mountWindow((onClose) => <ChainExplorer onClose={onClose} ts={this.state.tipset} client={this.props.client} mountWindow={this.props.mountWindow}/>)
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let runtime = <div></div>
|
let runtime = <div></div>
|
||||||
|
|
||||||
@ -98,7 +100,7 @@ class FullNode extends React.Component {
|
|||||||
<div>
|
<div>
|
||||||
Head: {
|
Head: {
|
||||||
<BlockLinks cids={this.state.tipset.Cids} conn={this.props.client} mountWindow={this.props.mountWindow} />
|
<BlockLinks cids={this.state.tipset.Cids} conn={this.props.client} mountWindow={this.props.mountWindow} />
|
||||||
} H:{this.state.tipset.Height}
|
} H:{this.state.tipset.Height} <a href="#" onClick={this.explorer}>[Explore]</a>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -122,7 +124,7 @@ class FullNode extends React.Component {
|
|||||||
const vouchers = this.state.vouchers[ak].map(voucher => {
|
const vouchers = this.state.vouchers[ak].map(voucher => {
|
||||||
let extra = <span></span>
|
let extra = <span></span>
|
||||||
if(voucher.Extra) {
|
if(voucher.Extra) {
|
||||||
extra = <span>Verif: <<b><Address nobalance={true} client={this.props.client} addr={voucher.Extra.Actor} mountWindow={this.props.mountWindow}/>M{voucher.Extra.Method}</b>></span>
|
extra = <span>Verif: <<b><Address nobalance={true} client={this.props.client} addr={voucher.Extra.Actor} mountWindow={this.props.mountWindow}/> M{voucher.Extra.Method}</b>></span>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div key={voucher.Nonce} className="FullNode-voucher">
|
return <div key={voucher.Nonce} className="FullNode-voucher">
|
||||||
|
@ -244,8 +244,13 @@ func (a *FullNodeAPI) ChainReadState(ctx context.Context, act *types.Actor, ts *
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var oif interface{}
|
blk, err := state.Store.Blocks.GetBlock(ctx, act.Head)
|
||||||
if err := state.Store.Get(context.TODO(), act.Head, &oif); err != nil {
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
oif, err := vm.DumpActorState(act.Code, blk.RawData())
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user