commit
6bc21a124f
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
/lotus
|
||||
/lotus-storage-miner
|
||||
/pond
|
||||
**/*.h
|
||||
**/*.a
|
||||
**/*.pc
|
||||
|
5
Makefile
5
Makefile
@ -47,6 +47,11 @@ build: $(BUILD_DEPS)
|
||||
go build -o lotus-storage-miner ./cmd/lotus-storage-miner
|
||||
.PHONY: build
|
||||
|
||||
pond: build
|
||||
go build -o pond ./lotuspond
|
||||
(cd lotuspond/front && npm i && npm run build)
|
||||
.PHONY: pond
|
||||
|
||||
clean:
|
||||
rm -rf $(CLEAN)
|
||||
-$(MAKE) -C $(BLS_PATH) clean
|
||||
|
@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
|
||||
"github.com/filecoin-project/go-lotus/chain"
|
||||
@ -45,9 +46,11 @@ type Common interface {
|
||||
|
||||
// network
|
||||
|
||||
NetConnectedness(context.Context, peer.ID) (network.Connectedness, error)
|
||||
NetPeers(context.Context) ([]peer.AddrInfo, error)
|
||||
NetConnect(context.Context, peer.AddrInfo) error
|
||||
NetAddrsListen(context.Context) (peer.AddrInfo, error)
|
||||
NetDisconnect(context.Context, peer.ID) error
|
||||
|
||||
// ID returns peerID of libp2p node backing this API
|
||||
ID(context.Context) (peer.ID, error)
|
||||
|
@ -3,6 +3,8 @@ package api
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/network"
|
||||
|
||||
"github.com/filecoin-project/go-lotus/chain"
|
||||
"github.com/filecoin-project/go-lotus/chain/address"
|
||||
"github.com/filecoin-project/go-lotus/chain/types"
|
||||
@ -19,9 +21,11 @@ type CommonStruct struct {
|
||||
AuthVerify func(ctx context.Context, token string) ([]string, error) `perm:"read"`
|
||||
AuthNew func(ctx context.Context, perms []string) ([]byte, error) `perm:"admin"`
|
||||
|
||||
NetConnectedness func(context.Context, peer.ID) (network.Connectedness, error) `perm:"read"`
|
||||
NetPeers func(context.Context) ([]peer.AddrInfo, error) `perm:"read"`
|
||||
NetConnect func(context.Context, peer.AddrInfo) error `perm:"write"`
|
||||
NetAddrsListen func(context.Context) (peer.AddrInfo, error) `perm:"read"`
|
||||
NetDisconnect func(context.Context, peer.ID) error `perm:"write"`
|
||||
|
||||
ID func(context.Context) (peer.ID, error) `perm:"read"`
|
||||
Version func(context.Context) (Version, error) `perm:"read"`
|
||||
@ -73,6 +77,10 @@ func (c *CommonStruct) AuthNew(ctx context.Context, perms []string) ([]byte, err
|
||||
return c.Internal.AuthNew(ctx, perms)
|
||||
}
|
||||
|
||||
func (c *CommonStruct) NetConnectedness(ctx context.Context, pid peer.ID) (network.Connectedness, error) {
|
||||
return c.Internal.NetConnectedness(ctx, pid)
|
||||
}
|
||||
|
||||
func (c *CommonStruct) NetPeers(ctx context.Context) ([]peer.AddrInfo, error) {
|
||||
return c.Internal.NetPeers(ctx)
|
||||
}
|
||||
@ -85,6 +93,10 @@ func (c *CommonStruct) NetAddrsListen(ctx context.Context) (peer.AddrInfo, error
|
||||
return c.Internal.NetAddrsListen(ctx)
|
||||
}
|
||||
|
||||
func (c *CommonStruct) NetDisconnect(ctx context.Context, p peer.ID) error {
|
||||
return c.Internal.NetDisconnect(ctx, p)
|
||||
}
|
||||
|
||||
// ID implements API.ID
|
||||
func (c *CommonStruct) ID(ctx context.Context) (peer.ID, error) {
|
||||
return c.Internal.ID(ctx)
|
||||
|
@ -80,7 +80,7 @@ func NewBlockSyncService(cs *ChainStore) *BlockSyncService {
|
||||
|
||||
func (bss *BlockSyncService) HandleStream(s inet.Stream) {
|
||||
defer s.Close()
|
||||
log.Error("handling block sync request")
|
||||
log.Info("handling block sync request")
|
||||
|
||||
var req BlockSyncRequest
|
||||
if err := cborrpc.ReadCborRPC(bufio.NewReader(s), &req); err != nil {
|
||||
|
@ -691,6 +691,7 @@ func (syncer *Syncer) collectChainCaughtUp(fts *FullTipSet) ([]*FullTipSet, erro
|
||||
for {
|
||||
ts, err := syncer.store.LoadTipSet(cur.Parents())
|
||||
if err != nil {
|
||||
log.Errorf("dont have parent blocks for sync tipset: %s", err)
|
||||
panic("should do something better, like fetch? or error?")
|
||||
}
|
||||
|
||||
|
13
go.mod
13
go.mod
@ -12,7 +12,7 @@ require (
|
||||
github.com/ipfs/go-block-format v0.0.2
|
||||
github.com/ipfs/go-blockservice v0.1.2
|
||||
github.com/ipfs/go-car v0.0.1
|
||||
github.com/ipfs/go-cid v0.0.2
|
||||
github.com/ipfs/go-cid v0.0.3
|
||||
github.com/ipfs/go-datastore v0.0.5
|
||||
github.com/ipfs/go-ds-badger v0.0.5
|
||||
github.com/ipfs/go-filestore v0.0.2
|
||||
@ -24,7 +24,7 @@ require (
|
||||
github.com/ipfs/go-ipfs-exchange-offline v0.0.1
|
||||
github.com/ipfs/go-ipfs-files v0.0.3
|
||||
github.com/ipfs/go-ipfs-routing v0.1.0
|
||||
github.com/ipfs/go-ipld-cbor v0.0.2
|
||||
github.com/ipfs/go-ipld-cbor v0.0.3
|
||||
github.com/ipfs/go-ipld-format v0.0.2
|
||||
github.com/ipfs/go-log v0.0.2-0.20190708183747-9c9fd6111bea
|
||||
github.com/ipfs/go-merkledag v0.1.0
|
||||
@ -55,11 +55,12 @@ require (
|
||||
github.com/multiformats/go-multiaddr v0.0.4
|
||||
github.com/multiformats/go-multiaddr-dns v0.0.3
|
||||
github.com/multiformats/go-multiaddr-net v0.0.1
|
||||
github.com/multiformats/go-multihash v0.0.5
|
||||
github.com/multiformats/go-multihash v0.0.6
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/polydawn/refmt v0.0.0-20190408063855-01bf1e26dd14
|
||||
github.com/smartystreets/assertions v1.0.1 // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc
|
||||
github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7
|
||||
github.com/whyrusleeping/pubsub v0.0.0-20131020042734-02de8aa2db3d
|
||||
github.com/whyrusleeping/sharray v0.0.0-20190718051354-e41931821e33
|
||||
@ -68,9 +69,11 @@ require (
|
||||
go.uber.org/fx v1.9.0
|
||||
go.uber.org/goleak v0.10.0 // indirect
|
||||
go4.org v0.0.0-20190313082347-94abd6928b1d // indirect
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect
|
||||
golang.org/x/sys v0.0.0-20190726002231-94b544f455ef // indirect
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
|
||||
google.golang.org/appengine v1.4.0
|
||||
gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8
|
||||
launchpad.net/gocheck v0.0.0-20140225173054-000000000087 // indirect
|
||||
)
|
||||
|
16
go.sum
16
go.sum
@ -116,6 +116,8 @@ github.com/ipfs/go-car v0.0.1/go.mod h1:pUz3tUIpudsTch0ZQrEPOvNUBT1LufCXg8aZ4KOr
|
||||
github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
|
||||
github.com/ipfs/go-cid v0.0.2 h1:tuuKaZPU1M6HcejsO3AcYWW8sZ8MTvyxfc4uqB4eFE8=
|
||||
github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
|
||||
github.com/ipfs/go-cid v0.0.3 h1:UIAh32wymBpStoe83YCzwVQQ5Oy/H0FdxvUS6DJDzms=
|
||||
github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
|
||||
github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE=
|
||||
github.com/ipfs/go-datastore v0.0.5 h1:q3OfiOZV5rlsK1H5V8benjeUApRfMGs4Mrhmr6NriQo=
|
||||
github.com/ipfs/go-datastore v0.0.5/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE=
|
||||
@ -162,6 +164,8 @@ github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyB
|
||||
github.com/ipfs/go-ipld-cbor v0.0.1/go.mod h1:RXHr8s4k0NE0TKhnrxqZC9M888QfsBN9rhS5NjfKzY8=
|
||||
github.com/ipfs/go-ipld-cbor v0.0.2 h1:amzFztBQQQ69UA5+f7JRfoXF/z2l//MGfEDHVkS20+s=
|
||||
github.com/ipfs/go-ipld-cbor v0.0.2/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc=
|
||||
github.com/ipfs/go-ipld-cbor v0.0.3 h1:ENsxvybwkmke7Z/QJOmeJfoguj6GH3Y0YOaGrfy9Q0I=
|
||||
github.com/ipfs/go-ipld-cbor v0.0.3/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc=
|
||||
github.com/ipfs/go-ipld-format v0.0.1/go.mod h1:kyJtbkDALmFHv3QR6et67i35QzO3S0dCDnkOJhcZkms=
|
||||
github.com/ipfs/go-ipld-format v0.0.2 h1:OVAGlyYT6JPZ0pEfGntFPS40lfrDmaDbQwNHEY2G9Zs=
|
||||
github.com/ipfs/go-ipld-format v0.0.2/go.mod h1:4B6+FM2u9OJ9zCV+kSbgFAZlOrv1Hqbf0INGQgiKf9k=
|
||||
@ -397,6 +401,8 @@ github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/g
|
||||
github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U=
|
||||
github.com/multiformats/go-multihash v0.0.5 h1:1wxmCvTXAifAepIMyF39vZinRw5sbqjPs/UIi93+uik=
|
||||
github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po=
|
||||
github.com/multiformats/go-multihash v0.0.6 h1:cAVKO4epVd+SSpYJQD6d3vbdqQJvsrtGbTGzsp+V094=
|
||||
github.com/multiformats/go-multihash v0.0.6/go.mod h1:XuKXPp8VHcTygube3OWZC+aZrA+H1IhmjoCDtJc7PXM=
|
||||
github.com/multiformats/go-multistream v0.0.1/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg=
|
||||
github.com/multiformats/go-multistream v0.1.0 h1:UpO6jrsjqs46mqAK3n6wKRYFhugss9ArzbyUzU+4wkQ=
|
||||
github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg=
|
||||
@ -423,9 +429,13 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
|
||||
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
|
||||
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa/go.mod h1:2RVY1rIf+2J2o/IM9+vPq9RzmHDSseB7FoXiSNIUsoU=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 h1:N8Bg45zpk/UcpNGnfJt2y/3lRWASHNTUET8owPYCgYI=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a h1:/eS3yfGjQKG+9kayBkj0ip1BGhq6zJ3eaVksphxAaek=
|
||||
github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0=
|
||||
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU=
|
||||
@ -507,6 +517,8 @@ golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 h1:IcSOAf4PyMp3U3XbIEj1/xJ2BjNN2jWv7JoyOsMxXUU=
|
||||
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@ -526,6 +538,8 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR
|
||||
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -548,6 +562,8 @@ golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726002231-94b544f455ef h1:vwqipsjwy3Y8/PQk/LmiaFjos8aOnU6Tt6oRXKD3org=
|
||||
golang.org/x/sys v0.0.0-20190726002231-94b544f455ef/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
|
@ -20,6 +20,13 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
token := r.Header.Get("Authorization")
|
||||
if token == "" {
|
||||
token = r.FormValue("token")
|
||||
if token != "" {
|
||||
token = "Bearer " + token
|
||||
}
|
||||
}
|
||||
|
||||
if token != "" {
|
||||
if !strings.HasPrefix(token, "Bearer ") {
|
||||
log.Warn("missing Bearer prefix in auth header")
|
||||
|
@ -2,7 +2,6 @@ package cborrpc
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
)
|
||||
@ -20,10 +19,5 @@ func WriteCborRPC(w io.Writer, obj interface{}) error {
|
||||
}
|
||||
|
||||
func ReadCborRPC(r io.Reader, out interface{}) error {
|
||||
b, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cbor.DecodeInto(b, out)
|
||||
return cbor.DecodeReader(r, out)
|
||||
}
|
||||
|
23
lotuspond/front/.gitignore
vendored
Normal file
23
lotuspond/front/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# 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*
|
12966
lotuspond/front/package-lock.json
generated
Normal file
12966
lotuspond/front/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
35
lotuspond/front/package.json
Normal file
35
lotuspond/front/package.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "front",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"jsonrpc-websocket-client": "^0.5.0",
|
||||
"react": "^16.8.6",
|
||||
"react-cristal": "^0.0.12",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-scripts": "3.0.1",
|
||||
"rpc-websockets": "^4.5.1",
|
||||
"styled-components": "^3.3.3"
|
||||
},
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
14
lotuspond/front/public/index.html
Normal file
14
lotuspond/front/public/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!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>
|
15
lotuspond/front/public/manifest.json
Normal file
15
lotuspond/front/public/manifest.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"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"
|
||||
}
|
23
lotuspond/front/src/App.css
Normal file
23
lotuspond/front/src/App.css
Normal file
@ -0,0 +1,23 @@
|
||||
.App {
|
||||
min-height: 100vh;
|
||||
background: #b7c4cd;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.FullNode {
|
||||
background: #f9be77;
|
||||
user-select: text;
|
||||
font-family: monospace;
|
||||
min-width: 40em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.CristalScroll {
|
||||
min-width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.Consensus {
|
||||
font-family: monospace;
|
||||
}
|
36
lotuspond/front/src/App.js
Normal file
36
lotuspond/front/src/App.js
Normal file
@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import './App.css';
|
||||
import { Client } from 'rpc-websockets'
|
||||
import NodeList from "./NodeList";
|
||||
|
||||
|
||||
class App 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 = {}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.client === undefined) {
|
||||
return (
|
||||
<div>
|
||||
Connecting to RPC
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<NodeList client={this.state.client}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default App
|
9
lotuspond/front/src/App.test.js
Normal file
9
lotuspond/front/src/App.test.js
Normal file
@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<App />, div);
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
});
|
44
lotuspond/front/src/Block.js
Normal file
44
lotuspond/front/src/Block.js
Normal file
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import {Cristal} from "react-cristal";
|
||||
import {BlockLinks} from "./BlockLink";
|
||||
|
||||
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])
|
||||
this.setState({header: header})
|
||||
}
|
||||
|
||||
render() {
|
||||
let content = <div>Loading Block Info</div>
|
||||
if (this.state.header) {
|
||||
let head = this.state.header
|
||||
|
||||
content = (
|
||||
<div>
|
||||
<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>Messages: {head.Messages['/']} {/*TODO: link to message explorer */}</div>
|
||||
<div>Receipts: {head.MessageReceipts['/']}</div>
|
||||
<div>State Root: {head.StateRoot['/']}</div>
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (<Cristal onClose={this.props.onClose} title={`Block ${this.props.cid['/']}`}>
|
||||
{content}
|
||||
</Cristal>)
|
||||
}
|
||||
}
|
||||
|
||||
export default Block
|
27
lotuspond/front/src/BlockLink.js
Normal file
27
lotuspond/front/src/BlockLink.js
Normal file
@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import Block from "./Block";
|
||||
|
||||
|
||||
export class BlockLinks extends React.Component {
|
||||
render() {
|
||||
return this.props.cids.map(c => <BlockLink conn={this.props.conn} cid={c} mountWindow={this.props.mountWindow}/>)
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
return <a href="#" onClick={this.openBlockViewer}><abbr title={this.props.cid['/']}>{this.props.cid['/'].substr(-8)}</abbr></a>
|
||||
}
|
||||
}
|
||||
|
||||
export default BlockLink
|
126
lotuspond/front/src/ConnMgr.js
Normal file
126
lotuspond/front/src/ConnMgr.js
Normal file
@ -0,0 +1,126 @@
|
||||
import React from 'react';
|
||||
import Cristal from 'react-cristal'
|
||||
|
||||
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).map(async (kfrom, i) => {
|
||||
return await keys.filter((_, j) => i >= j).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(
|
||||
<Cristal 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>
|
||||
</Cristal>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ConnMgr
|
69
lotuspond/front/src/Consensus.js
Normal file
69
lotuspond/front/src/Consensus.js
Normal file
@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import {Cristal} from "react-cristal";
|
||||
import {BlockLinks} from "./BlockLink";
|
||||
|
||||
function styleForHDiff(max, act) {
|
||||
switch (max - act) {
|
||||
case 0:
|
||||
return {background: '#00aa00'}
|
||||
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)
|
||||
|
||||
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 (<Cristal 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>
|
||||
</Cristal>)
|
||||
}
|
||||
}
|
||||
|
||||
export default Consensus
|
165
lotuspond/front/src/FullNode.js
Normal file
165
lotuspond/front/src/FullNode.js
Normal file
@ -0,0 +1,165 @@
|
||||
import React from 'react';
|
||||
import { Client } from 'rpc-websockets'
|
||||
import Cristal from 'react-cristal'
|
||||
import { BlockLinks } from "./BlockLink";
|
||||
|
||||
const stateConnected = 'connected'
|
||||
const stateConnecting = 'connecting'
|
||||
const stateGettingToken = 'getting-token'
|
||||
|
||||
async function awaitListReducer(prev, c) {
|
||||
return [...await prev, await c]
|
||||
}
|
||||
|
||||
function truncAddr(addr) {
|
||||
if (addr.length > 41) {
|
||||
return <abbr title={addr}>{addr.substr(0, 38) + '...'}</abbr>
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
class FullNode extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
state: stateGettingToken,
|
||||
id: "~",
|
||||
|
||||
mining: false,
|
||||
}
|
||||
|
||||
this.loadInfo = this.loadInfo.bind(this)
|
||||
this.startMining = this.startMining.bind(this)
|
||||
|
||||
this.connect()
|
||||
}
|
||||
|
||||
async connect() {
|
||||
console.log("gettok")
|
||||
|
||||
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)
|
||||
|
||||
this.loadInfo()
|
||||
setInterval(this.loadInfo, 2050)
|
||||
})
|
||||
|
||||
console.log(token) // todo: use
|
||||
}
|
||||
|
||||
async loadInfo() {
|
||||
const version = await this.state.client.call("Filecoin.Version", [])
|
||||
this.setState(() => ({version: version}))
|
||||
|
||||
const peers = await this.state.client.call("Filecoin.NetPeers", [])
|
||||
this.setState(() => ({peers: peers.length}))
|
||||
|
||||
const tipset = await this.state.client.call("Filecoin.ChainHead", [])
|
||||
this.setState(() => ({tipset: tipset}))
|
||||
|
||||
const addrss = await this.state.client.call('Filecoin.WalletList', [])
|
||||
let defaultAddr = ""
|
||||
if (addrss.length > 0) {
|
||||
defaultAddr = await this.state.client.call('Filecoin.WalletDefaultAddress', [])
|
||||
}
|
||||
|
||||
const balances = await addrss.map(async addr => {
|
||||
let balance = 0
|
||||
try {
|
||||
balance = await this.state.client.call('Filecoin.WalletBalance', [addr])
|
||||
} catch {
|
||||
balance = -1
|
||||
}
|
||||
return [addr, balance]
|
||||
}).reduce(awaitListReducer, Promise.resolve([]))
|
||||
|
||||
this.setState(() => ({balances: balances, defaultAddr: defaultAddr}))
|
||||
}
|
||||
|
||||
async startMining() {
|
||||
// TODO: Use actual miner address
|
||||
// see cli/miner.go
|
||||
this.setState({mining: true})
|
||||
await this.state.client.call("Filecoin.MinerStart", ["t0523423423"])
|
||||
}
|
||||
|
||||
render() {
|
||||
let runtime = <div></div>
|
||||
if (this.state.state === stateConnected) {
|
||||
let chainInfo = <div></div>
|
||||
if (this.state.tipset !== undefined) {
|
||||
chainInfo = (
|
||||
<div>
|
||||
Head: {
|
||||
<BlockLinks cids={this.state.tipset.Cids} conn={this.state.client} mountWindow={this.props.mountWindow} />
|
||||
} H:{this.state.tipset.Height}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
let mine = <a href="#" disabled={this.state.mining} onClick={this.startMining}>[Mine]</a>
|
||||
if (this.state.mining) {
|
||||
mine = "[Mining]"
|
||||
}
|
||||
|
||||
let balances = this.state.balances.map(([addr, balance]) => {
|
||||
let line = <span>{truncAddr(addr)}: {balance} (ActTyp)</span>
|
||||
if (this.state.defaultAddr === addr) {
|
||||
line = <b>{line}</b>
|
||||
}
|
||||
return <div>{line}</div>
|
||||
})
|
||||
|
||||
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_PATH={this.props.node.Repo}</div>
|
||||
{chainInfo}
|
||||
{mine}
|
||||
<div>
|
||||
<div>Balances:</div>
|
||||
<div>{balances}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Cristal
|
||||
title={"Node " + this.props.node.ID}
|
||||
initialPosition={{x: this.props.node.ID*30, y: this.props.node.ID * 30}} >
|
||||
<div className="CristalScroll">
|
||||
<div className="FullNode">
|
||||
<div>{this.props.node.ID} - {this.state.state}</div>
|
||||
{runtime}
|
||||
</div>
|
||||
</div>
|
||||
</Cristal>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default FullNode
|
102
lotuspond/front/src/NodeList.js
Normal file
102
lotuspond/front/src/NodeList.js
Normal file
@ -0,0 +1,102 @@
|
||||
import React from 'react';
|
||||
import FullNode from "./FullNode";
|
||||
import ConnMgr from "./ConnMgr";
|
||||
import Consensus from "./Consensus";
|
||||
|
||||
class NodeList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
existingLoaded: false,
|
||||
nodes: {},
|
||||
|
||||
showConnMgr: false,
|
||||
showConsensus: false,
|
||||
|
||||
windows: {},
|
||||
nextWindow: 0,
|
||||
}
|
||||
|
||||
// This binding is necessary to make `this` work in the callback
|
||||
this.spawnNode = this.spawnNode.bind(this)
|
||||
this.connMgr = this.connMgr.bind(this)
|
||||
this.consensus = this.consensus.bind(this)
|
||||
this.mountWindow = this.mountWindow.bind(this)
|
||||
|
||||
this.getNodes()
|
||||
}
|
||||
|
||||
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)
|
||||
this.setState({existingLoaded: true, nodes: nodes})
|
||||
}
|
||||
|
||||
async spawnNode() {
|
||||
const node = await this.props.client.call('Pond.Spawn')
|
||||
console.log(node)
|
||||
this.setState(state => ({nodes: {...state.nodes, [node.ID]: node}}))
|
||||
}
|
||||
|
||||
connMgr() {
|
||||
this.setState({showConnMgr: true})
|
||||
}
|
||||
|
||||
consensus() {
|
||||
this.setState({showConsensus: true})
|
||||
}
|
||||
|
||||
mountWindow(cb) {
|
||||
const id = this.state.nextWindow
|
||||
this.setState({nextWindow: id + 1})
|
||||
|
||||
const window = cb(() => {
|
||||
console.log("umount wnd todo")
|
||||
})
|
||||
|
||||
this.setState(prev => ({windows: {...prev.windows, [id]: window}}))
|
||||
}
|
||||
|
||||
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.mountWindow}/>)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<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 node = this.state.nodes[n]
|
||||
|
||||
return (<FullNode key={node.ID}
|
||||
node={{...node}}
|
||||
pondClient={this.props.client}
|
||||
onConnect={(conn, id) => this.setState(prev => ({nodes: {...prev.nodes, [n]: {...node, conn: conn, peerid: id}}}))}
|
||||
mountWindow={this.mountWindow}/>)
|
||||
})
|
||||
}
|
||||
{connMgr}
|
||||
{consensus}
|
||||
</div>
|
||||
<div>
|
||||
{Object.keys(this.state.windows).map((w, i) => <div key={i}>{this.state.windows[w]}</div>)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NodeList
|
13
lotuspond/front/src/index.css
Normal file
13
lotuspond/front/src/index.css
Normal file
@ -0,0 +1,13 @@
|
||||
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;
|
||||
}
|
6
lotuspond/front/src/index.js
Normal file
6
lotuspond/front/src/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
149
lotuspond/main.go
Normal file
149
lotuspond/main.go
Normal file
@ -0,0 +1,149 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/filecoin-project/go-lotus/lib/jsonrpc"
|
||||
"github.com/filecoin-project/go-lotus/node/repo"
|
||||
)
|
||||
|
||||
const listenAddr = "127.0.0.1:2222"
|
||||
|
||||
type runningNode struct {
|
||||
cmd *exec.Cmd
|
||||
meta nodeInfo
|
||||
|
||||
stop func()
|
||||
}
|
||||
|
||||
type api struct {
|
||||
cmds int32
|
||||
running map[int32]runningNode
|
||||
runningLk sync.Mutex
|
||||
genesis string
|
||||
}
|
||||
|
||||
type nodeInfo struct {
|
||||
Repo string
|
||||
ID int32
|
||||
ApiPort int32
|
||||
}
|
||||
|
||||
func (api *api) Spawn() (nodeInfo, error) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "lotus-")
|
||||
if err != nil {
|
||||
return nodeInfo{}, err
|
||||
}
|
||||
|
||||
genParam := "--genesis=" + api.genesis
|
||||
id := atomic.AddInt32(&api.cmds, 1)
|
||||
if id == 1 {
|
||||
// make genesis
|
||||
genf, err := ioutil.TempFile(os.TempDir(), "lotus-genesis-")
|
||||
if err != nil {
|
||||
return nodeInfo{}, err
|
||||
}
|
||||
|
||||
api.genesis = genf.Name()
|
||||
genParam = "--lotus-make-random-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
|
||||
}
|
||||
|
||||
cmd := exec.Command("./lotus", "daemon", genParam, "--api", fmt.Sprintf("%d", 2500+id))
|
||||
cmd.Stderr = io.MultiWriter(os.Stderr, errlogfile)
|
||||
cmd.Stdout = io.MultiWriter(os.Stdout, logfile)
|
||||
cmd.Env = []string{"LOTUS_PATH=" + dir}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nodeInfo{}, err
|
||||
}
|
||||
|
||||
info := nodeInfo{
|
||||
Repo: dir,
|
||||
ID: id,
|
||||
ApiPort: 2500 + id,
|
||||
}
|
||||
|
||||
api.runningLk.Lock()
|
||||
api.running[id] = runningNode{
|
||||
cmd: cmd,
|
||||
meta: info,
|
||||
|
||||
stop: func() {
|
||||
defer errlogfile.Close()
|
||||
defer logfile.Close()
|
||||
},
|
||||
}
|
||||
api.runningLk.Unlock()
|
||||
|
||||
time.Sleep(time.Millisecond * 750) // TODO: Something less terrible
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
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 "", errors.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 main() {
|
||||
rpcServer := jsonrpc.NewServer()
|
||||
rpcServer.Register("Pond", &api{running: map[int32]runningNode{}})
|
||||
|
||||
http.Handle("/", http.FileServer(http.Dir("lotuspond/front/build")))
|
||||
http.Handle("/rpc/v0", rpcServer)
|
||||
|
||||
fmt.Printf("Listening on http://%s\n", listenAddr)
|
||||
http.ListenAndServe(listenAddr, nil)
|
||||
}
|
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/gbrlsnchs/jwt/v3"
|
||||
"github.com/libp2p/go-libp2p-core/host"
|
||||
"github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
"go.uber.org/fx"
|
||||
@ -43,6 +44,10 @@ func (a *CommonAPI) AuthNew(ctx context.Context, perms []string) ([]byte, error)
|
||||
return jwt.Sign(&p, (*jwt.HMACSHA)(a.APISecret))
|
||||
}
|
||||
|
||||
func (a *CommonAPI) NetConnectedness(ctx context.Context, pid peer.ID) (network.Connectedness, error) {
|
||||
return a.Host.Network().Connectedness(pid), nil
|
||||
}
|
||||
|
||||
func (a *CommonAPI) NetPeers(context.Context) ([]peer.AddrInfo, error) {
|
||||
conns := a.Host.Network().Conns()
|
||||
out := make([]peer.AddrInfo, len(conns))
|
||||
@ -70,6 +75,10 @@ func (a *CommonAPI) NetAddrsListen(context.Context) (peer.AddrInfo, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *CommonAPI) NetDisconnect(ctx context.Context, p peer.ID) error {
|
||||
return a.Host.Network().ClosePeer(p)
|
||||
}
|
||||
|
||||
func (a *CommonAPI) ID(context.Context) (peer.ID, error) {
|
||||
return a.Host.ID(), nil
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package impl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-lotus/api"
|
||||
"github.com/filecoin-project/go-lotus/chain"
|
||||
@ -132,6 +133,9 @@ func (a *FullNodeAPI) WalletDefaultAddress(ctx context.Context) (address.Address
|
||||
if err != nil {
|
||||
return address.Undef, err
|
||||
}
|
||||
if len(addrs) == 0 {
|
||||
return address.Undef, xerrors.New("no addresses in wallet")
|
||||
}
|
||||
|
||||
// TODO: store a default address in the config or 'wallet' portion of the repo
|
||||
return addrs[0], nil
|
||||
|
Loading…
Reference in New Issue
Block a user