Merge pull request #632 from filecoin-project/feat/chainwatch
ChainWatch
This commit is contained in:
commit
4e2d7f0b1a
4
.gitignore
vendored
4
.gitignore
vendored
@ -13,6 +13,10 @@
|
|||||||
build/.*
|
build/.*
|
||||||
build/paramfetch.sh
|
build/paramfetch.sh
|
||||||
/vendor
|
/vendor
|
||||||
|
/blocks.dot
|
||||||
|
/blocks.svg
|
||||||
|
/chainwatch
|
||||||
|
/chainwatch.db
|
||||||
|
|
||||||
*-fuzz.zip
|
*-fuzz.zip
|
||||||
/chain/types/work_msg/
|
/chain/types/work_msg/
|
||||||
|
6
Makefile
6
Makefile
@ -109,6 +109,12 @@ fountain:
|
|||||||
go run github.com/GeertJohan/go.rice/rice append --exec fountain -i ./cmd/lotus-fountain
|
go run github.com/GeertJohan/go.rice/rice append --exec fountain -i ./cmd/lotus-fountain
|
||||||
.PHONY: fountain
|
.PHONY: fountain
|
||||||
|
|
||||||
|
chainwatch:
|
||||||
|
rm -f chainwatch
|
||||||
|
go build -o chainwatch ./cmd/lotus-chainwatch
|
||||||
|
go run github.com/GeertJohan/go.rice/rice append --exec chainwatch -i ./cmd/lotus-chainwatch
|
||||||
|
.PHONY: chainwatch
|
||||||
|
|
||||||
stats:
|
stats:
|
||||||
rm -f stats
|
rm -f stats
|
||||||
go build -o stats ./tools/stats
|
go build -o stats ./tools/stats
|
||||||
|
@ -38,12 +38,14 @@ type FullNode interface {
|
|||||||
// syncer
|
// syncer
|
||||||
SyncState(context.Context) (*SyncState, error)
|
SyncState(context.Context) (*SyncState, error)
|
||||||
SyncSubmitBlock(ctx context.Context, blk *types.BlockMsg) error
|
SyncSubmitBlock(ctx context.Context, blk *types.BlockMsg) error
|
||||||
|
SyncIncomingBlocks(ctx context.Context) (<-chan *types.BlockHeader, error)
|
||||||
|
|
||||||
// messages
|
// messages
|
||||||
MpoolPending(context.Context, *types.TipSet) ([]*types.SignedMessage, error)
|
MpoolPending(context.Context, *types.TipSet) ([]*types.SignedMessage, error)
|
||||||
MpoolPush(context.Context, *types.SignedMessage) error // TODO: remove
|
MpoolPush(context.Context, *types.SignedMessage) error // TODO: remove
|
||||||
MpoolPushMessage(context.Context, *types.Message) (*types.SignedMessage, error) // get nonce, sign, push
|
MpoolPushMessage(context.Context, *types.Message) (*types.SignedMessage, error) // get nonce, sign, push
|
||||||
MpoolGetNonce(context.Context, address.Address) (uint64, error)
|
MpoolGetNonce(context.Context, address.Address) (uint64, error)
|
||||||
|
MpoolSub(context.Context) (<-chan MpoolUpdate, error)
|
||||||
|
|
||||||
// FullNodeStruct
|
// FullNodeStruct
|
||||||
|
|
||||||
@ -110,6 +112,8 @@ type FullNode interface {
|
|||||||
StateMarketParticipants(context.Context, *types.TipSet) (map[string]actors.StorageParticipantBalance, error)
|
StateMarketParticipants(context.Context, *types.TipSet) (map[string]actors.StorageParticipantBalance, error)
|
||||||
StateMarketDeals(context.Context, *types.TipSet) (map[string]actors.OnChainDeal, error)
|
StateMarketDeals(context.Context, *types.TipSet) (map[string]actors.OnChainDeal, error)
|
||||||
StateMarketStorageDeal(context.Context, uint64, *types.TipSet) (*actors.OnChainDeal, error)
|
StateMarketStorageDeal(context.Context, uint64, *types.TipSet) (*actors.OnChainDeal, error)
|
||||||
|
StateLookupID(context.Context, address.Address, *types.TipSet) (address.Address, error)
|
||||||
|
StateChangedActors(context.Context, cid.Cid, cid.Cid) (map[string]types.Actor, error)
|
||||||
|
|
||||||
MarketEnsureAvailable(context.Context, address.Address, types.BigInt) error
|
MarketEnsureAvailable(context.Context, address.Address, types.BigInt) error
|
||||||
// MarketFreeBalance
|
// MarketFreeBalance
|
||||||
@ -274,3 +278,15 @@ const (
|
|||||||
StageMessages
|
StageMessages
|
||||||
StageSyncComplete
|
StageSyncComplete
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type MpoolChange int
|
||||||
|
|
||||||
|
const (
|
||||||
|
MpoolAdd MpoolChange = iota
|
||||||
|
MpoolRemove
|
||||||
|
)
|
||||||
|
|
||||||
|
type MpoolUpdate struct {
|
||||||
|
Type MpoolChange
|
||||||
|
Message *types.SignedMessage
|
||||||
|
}
|
||||||
|
@ -51,12 +51,15 @@ type FullNodeStruct struct {
|
|||||||
ChainGetGenesis func(context.Context) (*types.TipSet, error) `perm:"read"`
|
ChainGetGenesis func(context.Context) (*types.TipSet, error) `perm:"read"`
|
||||||
ChainTipSetWeight func(context.Context, *types.TipSet) (types.BigInt, error) `perm:"read"`
|
ChainTipSetWeight func(context.Context, *types.TipSet) (types.BigInt, error) `perm:"read"`
|
||||||
|
|
||||||
SyncState func(context.Context) (*SyncState, error) `perm:"read"`
|
SyncState func(context.Context) (*SyncState, error) `perm:"read"`
|
||||||
SyncSubmitBlock func(ctx context.Context, blk *types.BlockMsg) error `perm:"write"`
|
SyncSubmitBlock func(ctx context.Context, blk *types.BlockMsg) error `perm:"write"`
|
||||||
|
SyncIncomingBlocks func(ctx context.Context) (<-chan *types.BlockHeader, error) `perm:"read"`
|
||||||
|
|
||||||
MpoolPending func(context.Context, *types.TipSet) ([]*types.SignedMessage, error) `perm:"read"`
|
MpoolPending func(context.Context, *types.TipSet) ([]*types.SignedMessage, error) `perm:"read"`
|
||||||
MpoolPush func(context.Context, *types.SignedMessage) error `perm:"write"`
|
MpoolPush func(context.Context, *types.SignedMessage) error `perm:"write"`
|
||||||
MpoolPushMessage func(context.Context, *types.Message) (*types.SignedMessage, error) `perm:"sign"`
|
MpoolPushMessage func(context.Context, *types.Message) (*types.SignedMessage, error) `perm:"sign"`
|
||||||
|
MpoolGetNonce func(context.Context, address.Address) (uint64, error) `perm:"read"`
|
||||||
|
MpoolSub func(context.Context) (<-chan MpoolUpdate, error) `perm:"read"`
|
||||||
|
|
||||||
MinerRegister func(context.Context, address.Address) error `perm:"admin"`
|
MinerRegister func(context.Context, address.Address) error `perm:"admin"`
|
||||||
MinerUnregister func(context.Context, address.Address) error `perm:"admin"`
|
MinerUnregister func(context.Context, address.Address) error `perm:"admin"`
|
||||||
@ -74,8 +77,6 @@ type FullNodeStruct struct {
|
|||||||
WalletExport func(context.Context, address.Address) (*types.KeyInfo, error) `perm:"admin"`
|
WalletExport func(context.Context, address.Address) (*types.KeyInfo, error) `perm:"admin"`
|
||||||
WalletImport func(context.Context, *types.KeyInfo) (address.Address, error) `perm:"admin"`
|
WalletImport func(context.Context, *types.KeyInfo) (address.Address, error) `perm:"admin"`
|
||||||
|
|
||||||
MpoolGetNonce func(context.Context, address.Address) (uint64, error) `perm:"read"`
|
|
||||||
|
|
||||||
ClientImport func(ctx context.Context, path string) (cid.Cid, error) `perm:"admin"`
|
ClientImport func(ctx context.Context, path string) (cid.Cid, error) `perm:"admin"`
|
||||||
ClientListImports func(ctx context.Context) ([]Import, error) `perm:"write"`
|
ClientListImports func(ctx context.Context) ([]Import, error) `perm:"write"`
|
||||||
ClientHasLocal func(ctx context.Context, root cid.Cid) (bool, error) `perm:"write"`
|
ClientHasLocal func(ctx context.Context, root cid.Cid) (bool, error) `perm:"write"`
|
||||||
@ -105,6 +106,8 @@ type FullNodeStruct struct {
|
|||||||
StateMarketParticipants func(context.Context, *types.TipSet) (map[string]actors.StorageParticipantBalance, error) `perm:"read"`
|
StateMarketParticipants func(context.Context, *types.TipSet) (map[string]actors.StorageParticipantBalance, error) `perm:"read"`
|
||||||
StateMarketDeals func(context.Context, *types.TipSet) (map[string]actors.OnChainDeal, error) `perm:"read"`
|
StateMarketDeals func(context.Context, *types.TipSet) (map[string]actors.OnChainDeal, error) `perm:"read"`
|
||||||
StateMarketStorageDeal func(context.Context, uint64, *types.TipSet) (*actors.OnChainDeal, error) `perm:"read"`
|
StateMarketStorageDeal func(context.Context, uint64, *types.TipSet) (*actors.OnChainDeal, error) `perm:"read"`
|
||||||
|
StateLookupID func(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) `perm:"read"`
|
||||||
|
StateChangedActors func(context.Context, cid.Cid, cid.Cid) (map[string]types.Actor, error) `perm:"read"`
|
||||||
|
|
||||||
MarketEnsureAvailable func(context.Context, address.Address, types.BigInt) error `perm:"sign"`
|
MarketEnsureAvailable func(context.Context, address.Address, types.BigInt) error `perm:"sign"`
|
||||||
|
|
||||||
@ -225,6 +228,10 @@ func (c *FullNodeStruct) MpoolPushMessage(ctx context.Context, msg *types.Messag
|
|||||||
return c.Internal.MpoolPushMessage(ctx, msg)
|
return c.Internal.MpoolPushMessage(ctx, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *FullNodeStruct) MpoolSub(ctx context.Context) (<-chan MpoolUpdate, error) {
|
||||||
|
return c.Internal.MpoolSub(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *FullNodeStruct) MinerRegister(ctx context.Context, addr address.Address) error {
|
func (c *FullNodeStruct) MinerRegister(ctx context.Context, addr address.Address) error {
|
||||||
return c.Internal.MinerRegister(ctx, addr)
|
return c.Internal.MinerRegister(ctx, addr)
|
||||||
}
|
}
|
||||||
@ -345,6 +352,10 @@ func (c *FullNodeStruct) SyncSubmitBlock(ctx context.Context, blk *types.BlockMs
|
|||||||
return c.Internal.SyncSubmitBlock(ctx, blk)
|
return c.Internal.SyncSubmitBlock(ctx, blk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *FullNodeStruct) SyncIncomingBlocks(ctx context.Context) (<-chan *types.BlockHeader, error) {
|
||||||
|
return c.Internal.SyncIncomingBlocks(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *FullNodeStruct) StateMinerSectors(ctx context.Context, addr address.Address, ts *types.TipSet) ([]*ChainSectorInfo, error) {
|
func (c *FullNodeStruct) StateMinerSectors(ctx context.Context, addr address.Address, ts *types.TipSet) ([]*ChainSectorInfo, error) {
|
||||||
return c.Internal.StateMinerSectors(ctx, addr, ts)
|
return c.Internal.StateMinerSectors(ctx, addr, ts)
|
||||||
}
|
}
|
||||||
@ -420,6 +431,14 @@ func (c *FullNodeStruct) StateMarketStorageDeal(ctx context.Context, dealid uint
|
|||||||
return c.Internal.StateMarketStorageDeal(ctx, dealid, ts)
|
return c.Internal.StateMarketStorageDeal(ctx, dealid, ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *FullNodeStruct) StateLookupID(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) {
|
||||||
|
return c.Internal.StateLookupID(ctx, addr, ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FullNodeStruct) StateChangedActors(ctx context.Context, olnstate cid.Cid, newstate cid.Cid) (map[string]types.Actor, error) {
|
||||||
|
return c.Internal.StateChangedActors(ctx, olnstate, newstate)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *FullNodeStruct) MarketEnsureAvailable(ctx context.Context, addr address.Address, amt types.BigInt) error {
|
func (c *FullNodeStruct) MarketEnsureAvailable(ctx context.Context, addr address.Address, amt types.BigInt) error {
|
||||||
return c.Internal.MarketEnsureAvailable(ctx, addr, amt)
|
return c.Internal.MarketEnsureAvailable(ctx, addr, amt)
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
cbor "github.com/ipfs/go-ipld-cbor"
|
cbor "github.com/ipfs/go-ipld-cbor"
|
||||||
"github.com/minio/blake2b-simd"
|
"github.com/minio/blake2b-simd"
|
||||||
"github.com/polydawn/refmt/obj/atlas"
|
"github.com/polydawn/refmt/obj/atlas"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
cbg "github.com/whyrusleeping/cbor-gen"
|
cbg "github.com/whyrusleeping/cbor-gen"
|
||||||
)
|
)
|
||||||
@ -147,6 +148,22 @@ func (a Address) Format(f fmt.State, c rune) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Address) Scan(value interface{}) error {
|
||||||
|
switch value := value.(type) {
|
||||||
|
case string:
|
||||||
|
a1, err := decode(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*a = a1
|
||||||
|
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return xerrors.New("non-string types unsupported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewIDAddress returns an address using the ID protocol.
|
// NewIDAddress returns an address using the ID protocol.
|
||||||
func NewIDAddress(id uint64) (Address, error) {
|
func NewIDAddress(id uint64) (Address, error) {
|
||||||
return newAddress(ID, leb128.FromUInt64(id))
|
return newAddress(ID, leb128.FromUInt64(id))
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
package chain
|
package chain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
lru "github.com/hashicorp/golang-lru"
|
lru "github.com/hashicorp/golang-lru"
|
||||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||||
|
lps "github.com/whyrusleeping/pubsub"
|
||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/api"
|
||||||
"github.com/filecoin-project/lotus/build"
|
"github.com/filecoin-project/lotus/build"
|
||||||
"github.com/filecoin-project/lotus/chain/address"
|
"github.com/filecoin-project/lotus/chain/address"
|
||||||
"github.com/filecoin-project/lotus/chain/stmgr"
|
"github.com/filecoin-project/lotus/chain/stmgr"
|
||||||
@ -32,6 +34,8 @@ var (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
msgTopic = "/fil/messages"
|
msgTopic = "/fil/messages"
|
||||||
|
|
||||||
|
localUpdates = "update"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MessagePool struct {
|
type MessagePool struct {
|
||||||
@ -54,6 +58,8 @@ type MessagePool struct {
|
|||||||
maxTxPoolSize int
|
maxTxPoolSize int
|
||||||
|
|
||||||
blsSigCache *lru.TwoQueueCache
|
blsSigCache *lru.TwoQueueCache
|
||||||
|
|
||||||
|
changes *lps.PubSub
|
||||||
}
|
}
|
||||||
|
|
||||||
type msgSet struct {
|
type msgSet struct {
|
||||||
@ -95,6 +101,7 @@ func NewMessagePool(sm *stmgr.StateManager, ps *pubsub.PubSub) *MessagePool {
|
|||||||
minGasPrice: types.NewInt(0),
|
minGasPrice: types.NewInt(0),
|
||||||
maxTxPoolSize: 100000,
|
maxTxPoolSize: 100000,
|
||||||
blsSigCache: cache,
|
blsSigCache: cache,
|
||||||
|
changes: lps.New(50),
|
||||||
}
|
}
|
||||||
sm.ChainStore().SubscribeHeadChanges(func(rev, app []*types.TipSet) error {
|
sm.ChainStore().SubscribeHeadChanges(func(rev, app []*types.TipSet) error {
|
||||||
err := mp.HeadChange(rev, app)
|
err := mp.HeadChange(rev, app)
|
||||||
@ -231,6 +238,11 @@ func (mp *MessagePool) addLocked(m *types.SignedMessage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mset.add(m)
|
mset.add(m)
|
||||||
|
|
||||||
|
mp.changes.Pub(api.MpoolUpdate{
|
||||||
|
Type: api.MpoolAdd,
|
||||||
|
Message: m,
|
||||||
|
}, localUpdates)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,6 +316,13 @@ func (mp *MessagePool) Remove(from address.Address, nonce uint64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if m, ok := mset.msgs[nonce]; ok {
|
||||||
|
mp.changes.Pub(api.MpoolUpdate{
|
||||||
|
Type: api.MpoolRemove,
|
||||||
|
Message: m,
|
||||||
|
}, localUpdates)
|
||||||
|
}
|
||||||
|
|
||||||
// NB: This deletes any message with the given nonce. This makes sense
|
// NB: This deletes any message with the given nonce. This makes sense
|
||||||
// as two messages with the same sender cannot have the same nonce
|
// as two messages with the same sender cannot have the same nonce
|
||||||
delete(mset.msgs, nonce)
|
delete(mset.msgs, nonce)
|
||||||
@ -413,3 +432,27 @@ func (mp *MessagePool) RecoverSig(msg *types.Message) *types.SignedMessage {
|
|||||||
Signature: sig,
|
Signature: sig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mp *MessagePool) Updates(ctx context.Context) (<-chan api.MpoolUpdate, error) {
|
||||||
|
out := make(chan api.MpoolUpdate, 20)
|
||||||
|
sub := mp.changes.Sub(localUpdates)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer mp.changes.Unsub(sub, localIncoming)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case u := <-sub:
|
||||||
|
select {
|
||||||
|
case out <- u.(api.MpoolUpdate):
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
@ -47,13 +47,11 @@ func LoadStateTree(cst *hamt.CborIpldStore, c cid.Cid) (*StateTree, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (st *StateTree) SetActor(addr address.Address, act *types.Actor) error {
|
func (st *StateTree) SetActor(addr address.Address, act *types.Actor) error {
|
||||||
if addr.Protocol() != address.ID {
|
iaddr, err := st.LookupID(addr)
|
||||||
iaddr, err := st.lookupID(addr)
|
if err != nil {
|
||||||
if err != nil {
|
return xerrors.Errorf("ID lookup failed: %w", err)
|
||||||
return xerrors.Errorf("ID lookup failed: %w", err)
|
|
||||||
}
|
|
||||||
addr = iaddr
|
|
||||||
}
|
}
|
||||||
|
addr = iaddr
|
||||||
|
|
||||||
cact, ok := st.actorcache[addr]
|
cact, ok := st.actorcache[addr]
|
||||||
if ok {
|
if ok {
|
||||||
@ -67,7 +65,11 @@ func (st *StateTree) SetActor(addr address.Address, act *types.Actor) error {
|
|||||||
return st.root.Set(context.TODO(), string(addr.Bytes()), act)
|
return st.root.Set(context.TODO(), string(addr.Bytes()), act)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *StateTree) lookupID(addr address.Address) (address.Address, error) {
|
func (st *StateTree) LookupID(addr address.Address) (address.Address, error) {
|
||||||
|
if addr.Protocol() == address.ID {
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
act, err := st.GetActor(actors.InitAddress)
|
act, err := st.GetActor(actors.InitAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return address.Undef, xerrors.Errorf("getting init actor: %w", err)
|
return address.Undef, xerrors.Errorf("getting init actor: %w", err)
|
||||||
@ -86,16 +88,14 @@ func (st *StateTree) GetActor(addr address.Address) (*types.Actor, error) {
|
|||||||
return nil, fmt.Errorf("GetActor called on undefined address")
|
return nil, fmt.Errorf("GetActor called on undefined address")
|
||||||
}
|
}
|
||||||
|
|
||||||
if addr.Protocol() != address.ID {
|
iaddr, err := st.LookupID(addr)
|
||||||
iaddr, err := st.lookupID(addr)
|
if err != nil {
|
||||||
if err != nil {
|
if xerrors.Is(err, hamt.ErrNotFound) {
|
||||||
if xerrors.Is(err, hamt.ErrNotFound) {
|
return nil, xerrors.Errorf("resolution lookup failed (%s): %w", addr, types.ErrActorNotFound)
|
||||||
return nil, xerrors.Errorf("resolution lookup failed (%s): %w", addr, types.ErrActorNotFound)
|
|
||||||
}
|
|
||||||
return nil, xerrors.Errorf("address resolution: %w", err)
|
|
||||||
}
|
}
|
||||||
addr = iaddr
|
return nil, xerrors.Errorf("address resolution: %w", err)
|
||||||
}
|
}
|
||||||
|
addr = iaddr
|
||||||
|
|
||||||
cact, ok := st.actorcache[addr]
|
cact, ok := st.actorcache[addr]
|
||||||
if ok {
|
if ok {
|
||||||
@ -103,7 +103,7 @@ func (st *StateTree) GetActor(addr address.Address) (*types.Actor, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var act types.Actor
|
var act types.Actor
|
||||||
err := st.root.Find(context.TODO(), string(addr.Bytes()), &act)
|
err = st.root.Find(context.TODO(), string(addr.Bytes()), &act)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == hamt.ErrNotFound {
|
if err == hamt.ErrNotFound {
|
||||||
return nil, types.ErrActorNotFound
|
return nil, types.ErrActorNotFound
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
logging "github.com/ipfs/go-log"
|
logging "github.com/ipfs/go-log"
|
||||||
"github.com/libp2p/go-libp2p-core/peer"
|
"github.com/libp2p/go-libp2p-core/peer"
|
||||||
cbg "github.com/whyrusleeping/cbor-gen"
|
cbg "github.com/whyrusleeping/cbor-gen"
|
||||||
|
"github.com/whyrusleeping/pubsub"
|
||||||
"go.opencensus.io/trace"
|
"go.opencensus.io/trace"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
@ -35,6 +36,8 @@ import (
|
|||||||
|
|
||||||
var log = logging.Logger("chain")
|
var log = logging.Logger("chain")
|
||||||
|
|
||||||
|
var localIncoming = "incoming"
|
||||||
|
|
||||||
type Syncer struct {
|
type Syncer struct {
|
||||||
// The heaviest known tipset in the network.
|
// The heaviest known tipset in the network.
|
||||||
|
|
||||||
@ -58,6 +61,8 @@ type Syncer struct {
|
|||||||
syncLock sync.Mutex
|
syncLock sync.Mutex
|
||||||
|
|
||||||
syncmgr *SyncManager
|
syncmgr *SyncManager
|
||||||
|
|
||||||
|
incoming *pubsub.PubSub
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSyncer(sm *stmgr.StateManager, bsync *blocksync.BlockSync, self peer.ID) (*Syncer, error) {
|
func NewSyncer(sm *stmgr.StateManager, bsync *blocksync.BlockSync, self peer.ID) (*Syncer, error) {
|
||||||
@ -78,6 +83,8 @@ func NewSyncer(sm *stmgr.StateManager, bsync *blocksync.BlockSync, self peer.ID)
|
|||||||
store: sm.ChainStore(),
|
store: sm.ChainStore(),
|
||||||
sm: sm,
|
sm: sm,
|
||||||
self: self,
|
self: self,
|
||||||
|
|
||||||
|
incoming: pubsub.New(50),
|
||||||
}
|
}
|
||||||
|
|
||||||
s.syncmgr = NewSyncManager(s.Sync)
|
s.syncmgr = NewSyncManager(s.Sync)
|
||||||
@ -109,6 +116,8 @@ func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncer.incoming.Pub(fts.TipSet().Blocks(), localIncoming)
|
||||||
|
|
||||||
if from == syncer.self {
|
if from == syncer.self {
|
||||||
// TODO: this is kindof a hack...
|
// TODO: this is kindof a hack...
|
||||||
log.Debug("got block from ourselves")
|
log.Debug("got block from ourselves")
|
||||||
@ -139,6 +148,33 @@ func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) {
|
|||||||
syncer.syncmgr.SetPeerHead(ctx, from, fts.TipSet())
|
syncer.syncmgr.SetPeerHead(ctx, from, fts.TipSet())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (syncer *Syncer) IncomingBlocks(ctx context.Context) (<-chan *types.BlockHeader, error) {
|
||||||
|
sub := syncer.incoming.Sub(localIncoming)
|
||||||
|
out := make(chan *types.BlockHeader, 10)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer syncer.incoming.Unsub(sub, localIncoming)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case r := <-sub:
|
||||||
|
hs := r.([]*types.BlockHeader)
|
||||||
|
for _, h := range hs {
|
||||||
|
select {
|
||||||
|
case out <- h:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (syncer *Syncer) ValidateMsgMeta(fblk *types.FullBlock) error {
|
func (syncer *Syncer) ValidateMsgMeta(fblk *types.FullBlock) error {
|
||||||
var bcids, scids []cbg.CBORMarshaler
|
var bcids, scids []cbg.CBORMarshaler
|
||||||
for _, m := range fblk.BlsMessages {
|
for _, m := range fblk.BlsMessages {
|
||||||
|
@ -124,6 +124,28 @@ func (bi *BigInt) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bi *BigInt) Scan(value interface{}) error {
|
||||||
|
switch value := value.(type) {
|
||||||
|
case string:
|
||||||
|
i, ok := big.NewInt(0).SetString(value, 10)
|
||||||
|
if !ok {
|
||||||
|
if value == "<nil>" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return xerrors.Errorf("failed to parse bigint string: '%s'", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
bi.Int = i
|
||||||
|
|
||||||
|
return nil
|
||||||
|
case int64:
|
||||||
|
bi.Int = big.NewInt(value)
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return xerrors.Errorf("non-string types unsupported: %T", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (bi *BigInt) cborBytes() []byte {
|
func (bi *BigInt) cborBytes() []byte {
|
||||||
if bi.Int == nil {
|
if bi.Int == nil {
|
||||||
return []byte{}
|
return []byte{}
|
||||||
|
33
cli/mpool.go
33
cli/mpool.go
@ -12,6 +12,7 @@ var mpoolCmd = &cli.Command{
|
|||||||
Usage: "Manage message pool",
|
Usage: "Manage message pool",
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
mpoolPending,
|
mpoolPending,
|
||||||
|
mpoolSub,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,3 +44,35 @@ var mpoolPending = &cli.Command{
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mpoolSub = &cli.Command{
|
||||||
|
Name: "sub",
|
||||||
|
Usage: "Subscibe to mpool changes",
|
||||||
|
Action: func(cctx *cli.Context) error {
|
||||||
|
api, closer, err := GetFullNodeAPI(cctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
ctx := ReqContext(cctx)
|
||||||
|
|
||||||
|
sub, err := api.MpoolSub(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case update := <-sub:
|
||||||
|
out, err := json.MarshalIndent(update, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(string(out))
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
33
cli/state.go
33
cli/state.go
@ -21,6 +21,7 @@ var stateCmd = &cli.Command{
|
|||||||
stateListActorsCmd,
|
stateListActorsCmd,
|
||||||
stateListMinersCmd,
|
stateListMinersCmd,
|
||||||
stateGetActorCmd,
|
stateGetActorCmd,
|
||||||
|
stateLookupIDCmd,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,3 +289,35 @@ var stateGetActorCmd = &cli.Command{
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var stateLookupIDCmd = &cli.Command{
|
||||||
|
Name: "lookup",
|
||||||
|
Usage: "Find corresponding ID address",
|
||||||
|
Action: func(cctx *cli.Context) error {
|
||||||
|
api, closer, err := GetFullNodeAPI(cctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
ctx := ReqContext(cctx)
|
||||||
|
|
||||||
|
if !cctx.Args().Present() {
|
||||||
|
return fmt.Errorf("must pass address of actor to get")
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := address.NewFromString(cctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err := api.StateLookupID(ctx, addr, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s\n", a)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
27
cmd/lotus-chainwatch/blockssub.go
Normal file
27
cmd/lotus-chainwatch/blockssub.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
|
||||||
|
aapi "github.com/filecoin-project/lotus/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func subBlocks(ctx context.Context, api aapi.FullNode, st *storage) {
|
||||||
|
sub, err := api.SyncIncomingBlocks(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for bh := range sub {
|
||||||
|
err := st.storeHeaders(map[cid.Cid]*types.BlockHeader{
|
||||||
|
bh.Cid(): bh,
|
||||||
|
}, false)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
cmd/lotus-chainwatch/dot.go
Normal file
63
cmd/lotus-chainwatch/dot.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"hash/crc32"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
"gopkg.in/urfave/cli.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dotCmd = &cli.Command{
|
||||||
|
Name: "dot",
|
||||||
|
Usage: "generate dot graphs",
|
||||||
|
Action: func(cctx *cli.Context) error {
|
||||||
|
st, err := openStorage()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
minH, err := strconv.ParseInt(cctx.Args().Get(0), 10, 32)
|
||||||
|
tosee, err := strconv.ParseInt(cctx.Args().Get(1), 10, 32)
|
||||||
|
maxH := minH + tosee
|
||||||
|
|
||||||
|
res, err := st.db.Query("select block, parent, b.miner, b.height from block_parents inner join blocks b on block_parents.block = b.cid where b.height > ? and b.height < ?", minH, maxH)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("digraph D {")
|
||||||
|
|
||||||
|
for res.Next() {
|
||||||
|
var block, parent, miner string
|
||||||
|
var height uint64
|
||||||
|
if err := res.Scan(&block, &parent, &miner, &height); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bc, err := cid.Parse(block)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
has := st.hasBlock(bc)
|
||||||
|
|
||||||
|
col := crc32.Checksum([]byte(miner), crc32.MakeTable(crc32.Castagnoli))&0xc0c0c0c0 + 0x30303030
|
||||||
|
|
||||||
|
hasstr := ""
|
||||||
|
if !has {
|
||||||
|
hasstr = " UNSYNCED"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s [label = \"%s:%d%s\", fillcolor = \"#%06x\", style=filled, forcelabels=true]\n%s -> %s\n", block, miner, height, hasstr, col, block, parent)
|
||||||
|
}
|
||||||
|
if res.Err() != nil {
|
||||||
|
return res.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("}")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
99
cmd/lotus-chainwatch/main.go
Normal file
99
cmd/lotus-chainwatch/main.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
logging "github.com/ipfs/go-log"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
"gopkg.in/urfave/cli.v2"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/build"
|
||||||
|
lcli "github.com/filecoin-project/lotus/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logging.Logger("chainwatch")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logging.SetLogLevel("*", "INFO")
|
||||||
|
|
||||||
|
log.Info("Starting chainwatch")
|
||||||
|
|
||||||
|
local := []*cli.Command{
|
||||||
|
runCmd,
|
||||||
|
dotCmd,
|
||||||
|
}
|
||||||
|
|
||||||
|
app := &cli.App{
|
||||||
|
Name: "lotus-chainwatch",
|
||||||
|
Usage: "Devnet token distribution utility",
|
||||||
|
Version: build.Version,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "repo",
|
||||||
|
EnvVars: []string{"LOTUS_PATH"},
|
||||||
|
Value: "~/.lotus", // TODO: Consider XDG_DATA_HOME
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: local,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
log.Warnf("%+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var runCmd = &cli.Command{
|
||||||
|
Name: "run",
|
||||||
|
Usage: "Start lotus chainwatch",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "front",
|
||||||
|
Value: "127.0.0.1:8418",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(cctx *cli.Context) error {
|
||||||
|
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer closer()
|
||||||
|
ctx := lcli.ReqContext(cctx)
|
||||||
|
|
||||||
|
v, err := api.Version(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Remote version: %s", v.Version)
|
||||||
|
|
||||||
|
st, err := openStorage()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer st.close()
|
||||||
|
|
||||||
|
runSyncer(ctx, api, st)
|
||||||
|
go subMpool(ctx, api, st)
|
||||||
|
go subBlocks(ctx, api, st)
|
||||||
|
|
||||||
|
h, err := newHandler(api, st)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("handler setup: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Handle("/", h)
|
||||||
|
|
||||||
|
fmt.Printf("Open http://%s\n", cctx.String("front"))
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return http.ListenAndServe(cctx.String("front"), nil)
|
||||||
|
},
|
||||||
|
}
|
38
cmd/lotus-chainwatch/mpool.go
Normal file
38
cmd/lotus-chainwatch/mpool.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
|
||||||
|
aapi "github.com/filecoin-project/lotus/api"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func subMpool(ctx context.Context, api aapi.FullNode, st *storage) {
|
||||||
|
sub, err := api.MpoolSub(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for change := range sub {
|
||||||
|
if change.Type != aapi.MpoolAdd {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("mpool message")
|
||||||
|
|
||||||
|
err := st.storeMessages(map[cid.Cid]*types.Message{
|
||||||
|
change.Message.Message.Cid(): &change.Message.Message,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := st.storeMpoolInclusion(change.Message.Message.Cid()); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
cmd/lotus-chainwatch/site/index.html
Normal file
32
cmd/lotus-chainwatch/site/index.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Lotus ChainWatch</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="Index">
|
||||||
|
<div class="Index-header">
|
||||||
|
<div>
|
||||||
|
<span>Lotus ChainWatch</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="Index-nodes">
|
||||||
|
<div class="Index-node">
|
||||||
|
<b>{{countCol "actors" "id"}}</b> Actors;
|
||||||
|
<b>{{countCol "miner_heads" "addr"}}</b> Miners;
|
||||||
|
<b>{{netPower "slashed_at = 0" | sizeStr}}</b> Power
|
||||||
|
(<b>{{netPower "" | sizeStr}}</b> Total;
|
||||||
|
<b>{{netPower "slashed_at > 0" | sizeStr}}</b> Slashed)
|
||||||
|
</div>
|
||||||
|
<div class="Index-node">
|
||||||
|
{{count "messages"}} Messages; {{count "actors"}} state changes
|
||||||
|
</div>
|
||||||
|
<div class="Index-node">
|
||||||
|
{{count "id_address_map" "id != address"}} <a href="keys.html">Keys</a>;
|
||||||
|
E% FIL in wallets; F% FIL in miners; M% in market; %G Other actors; %H FIL it treasury
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
40
cmd/lotus-chainwatch/site/key.html
Normal file
40
cmd/lotus-chainwatch/site/key.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Lotus ChainWatch</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{$wallet := param "w"}}
|
||||||
|
|
||||||
|
<div class="Index">
|
||||||
|
<div class="Index-header">
|
||||||
|
<div>
|
||||||
|
<span>Lotus ChainWatch - Wallet {{$wallet}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="Index-nodes">
|
||||||
|
<div class="Index-node">
|
||||||
|
Balance: {{queryNum "select balance from actors inner join main.id_address_map m on m.address = ? where actors.id = m.id order by nonce desc limit 1" $wallet }}
|
||||||
|
</div>
|
||||||
|
<div class="Index-node">
|
||||||
|
Messages:
|
||||||
|
<table>
|
||||||
|
<tr><td>Dir</td><td>Peer</td><td>Nonce</td><td>Value</td><td>Block</td><td>Mpool Wait</td></tr>
|
||||||
|
{{ range messages "`from` = ? or `to` = ?" $wallet $wallet $wallet}}
|
||||||
|
<tr>
|
||||||
|
{{ if eq .From.String $wallet }}
|
||||||
|
<td>To</td><td>{{.To.String}}</td>
|
||||||
|
{{else}}
|
||||||
|
<td>From</td><td>{{.From.String}}</td>
|
||||||
|
{{end}}
|
||||||
|
<td>{{.Nonce}}</td>
|
||||||
|
<td>{{.Value}}</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
23
cmd/lotus-chainwatch/site/keys.html
Normal file
23
cmd/lotus-chainwatch/site/keys.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Lotus ChainWatch</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="Index">
|
||||||
|
<div class="Index-header">
|
||||||
|
<div>
|
||||||
|
<span>Lotus ChainWatch - Wallets</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="Index-nodes">
|
||||||
|
<div class="Index-node">
|
||||||
|
{{range strings "id_address_map" "address" "address != id"}}
|
||||||
|
<div><a href="key.html?w={{.}}">{{.}}</a></div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
62
cmd/lotus-chainwatch/site/main.css
Normal file
62
cmd/lotus-chainwatch/site/main.css
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
body {
|
||||||
|
font-family: 'monospace';
|
||||||
|
background: #1f1f1f;
|
||||||
|
color: #f0f0f0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Index {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: #1a1a1a;
|
||||||
|
color: #f0f0f0;
|
||||||
|
font-family: monospace;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 80vw auto;
|
||||||
|
grid-template-rows: 3em auto auto auto;
|
||||||
|
grid-template-areas:
|
||||||
|
"header header header header"
|
||||||
|
". . . ."
|
||||||
|
". main main ."
|
||||||
|
". main main ."
|
||||||
|
". main main ."
|
||||||
|
". main main ."
|
||||||
|
". main main ."
|
||||||
|
". . . .";
|
||||||
|
}
|
||||||
|
|
||||||
|
.Index-header {
|
||||||
|
background: #2a2a2a;
|
||||||
|
grid-area: header;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Index-Index-header > div {
|
||||||
|
padding-left: 0.7em;
|
||||||
|
padding-top: 0.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Index-nodes {
|
||||||
|
grid-area: main;
|
||||||
|
background: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Index-node {
|
||||||
|
margin: 5px;
|
||||||
|
padding: 15px;
|
||||||
|
background: #1f1f1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link {
|
||||||
|
color: #50f020;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: #50f020;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #30a00a;
|
||||||
|
}
|
421
cmd/lotus-chainwatch/storage.go
Normal file
421
cmd/lotus-chainwatch/storage.go
Normal file
@ -0,0 +1,421 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/chain/address"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type storage struct {
|
||||||
|
db *sql.DB
|
||||||
|
|
||||||
|
headerLk sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func openStorage() (*storage, error) {
|
||||||
|
db, err := sql.Open("sqlite3", "./chainwatch.db")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
st := &storage{db: db}
|
||||||
|
|
||||||
|
return st, st.setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *storage) setup() error {
|
||||||
|
tx, err := st.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = tx.Exec(`
|
||||||
|
create table if not exists actors
|
||||||
|
(
|
||||||
|
id text not null,
|
||||||
|
code text not null,
|
||||||
|
head text not null,
|
||||||
|
nonce int not null,
|
||||||
|
balance text not null,
|
||||||
|
stateroot text
|
||||||
|
constraint actors_blocks_stateroot_fk
|
||||||
|
references blocks (parentStateRoot),
|
||||||
|
constraint actors_pk
|
||||||
|
primary key (id, nonce, balance, stateroot)
|
||||||
|
);
|
||||||
|
|
||||||
|
create index if not exists actors_id_index
|
||||||
|
on actors (id);
|
||||||
|
|
||||||
|
create table if not exists id_address_map
|
||||||
|
(
|
||||||
|
id text not null
|
||||||
|
constraint id_address_map_actors_id_fk
|
||||||
|
references actors (id),
|
||||||
|
address text not null,
|
||||||
|
constraint id_address_map_pk
|
||||||
|
primary key (id, address)
|
||||||
|
);
|
||||||
|
|
||||||
|
create index if not exists id_address_map_address_index
|
||||||
|
on id_address_map (address);
|
||||||
|
|
||||||
|
create index if not exists id_address_map_id_index
|
||||||
|
on id_address_map (id);
|
||||||
|
|
||||||
|
create table if not exists messages
|
||||||
|
(
|
||||||
|
cid text not null
|
||||||
|
constraint messages_pk
|
||||||
|
primary key,
|
||||||
|
"from" text not null
|
||||||
|
constraint messages_id_address_map_from_fk
|
||||||
|
references id_address_map (address),
|
||||||
|
"to" text not null
|
||||||
|
constraint messages_id_address_map_to_fk
|
||||||
|
references id_address_map (address),
|
||||||
|
nonce int not null,
|
||||||
|
value text not null,
|
||||||
|
gasprice int not null,
|
||||||
|
gaslimit int not null,
|
||||||
|
method int,
|
||||||
|
params blob
|
||||||
|
);
|
||||||
|
|
||||||
|
create unique index if not exists messages_cid_uindex
|
||||||
|
on messages (cid);
|
||||||
|
|
||||||
|
create table if not exists blocks
|
||||||
|
(
|
||||||
|
cid text not null
|
||||||
|
constraint blocks_pk
|
||||||
|
primary key,
|
||||||
|
parentWeight numeric not null,
|
||||||
|
parentStateRoot text not null,
|
||||||
|
height int not null,
|
||||||
|
miner text not null
|
||||||
|
constraint blocks_id_address_map_miner_fk
|
||||||
|
references id_address_map (address),
|
||||||
|
timestamp int not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create unique index if not exists block_cid_uindex
|
||||||
|
on blocks (cid);
|
||||||
|
|
||||||
|
create table if not exists blocks_synced
|
||||||
|
(
|
||||||
|
cid text not null
|
||||||
|
constraint blocks_synced_pk
|
||||||
|
primary key
|
||||||
|
constraint blocks_synced_blocks_cid_fk
|
||||||
|
references blocks
|
||||||
|
);
|
||||||
|
|
||||||
|
create unique index if not exists blocks_synced_cid_uindex
|
||||||
|
on blocks_synced (cid);
|
||||||
|
|
||||||
|
create table if not exists block_parents
|
||||||
|
(
|
||||||
|
block text not null
|
||||||
|
constraint block_parents_blocks_cid_fk
|
||||||
|
references blocks,
|
||||||
|
parent text not null
|
||||||
|
constraint block_parents_blocks_cid_fk_2
|
||||||
|
references blocks
|
||||||
|
);
|
||||||
|
|
||||||
|
create unique index if not exists block_parents_block_parent_uindex
|
||||||
|
on block_parents (block, parent);
|
||||||
|
|
||||||
|
create unique index if not exists blocks_cid_uindex
|
||||||
|
on blocks (cid);
|
||||||
|
|
||||||
|
create table if not exists block_messages
|
||||||
|
(
|
||||||
|
block text not null
|
||||||
|
constraint block_messages_blk_fk
|
||||||
|
references blocks (cid),
|
||||||
|
message text not null
|
||||||
|
constraint block_messages_msg_fk
|
||||||
|
references messages,
|
||||||
|
constraint block_messages_pk
|
||||||
|
primary key (block, message)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table if not exists mpool_messages
|
||||||
|
(
|
||||||
|
msg text not null
|
||||||
|
constraint mpool_messages_pk
|
||||||
|
primary key
|
||||||
|
constraint mpool_messages_messages_cid_fk
|
||||||
|
references messages,
|
||||||
|
add_ts int not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create unique index if not exists mpool_messages_msg_uindex
|
||||||
|
on mpool_messages (msg);
|
||||||
|
|
||||||
|
create table if not exists miner_heads
|
||||||
|
(
|
||||||
|
head text not null
|
||||||
|
constraint miner_heads_actors_head_fk
|
||||||
|
references actors (head),
|
||||||
|
addr text not null
|
||||||
|
constraint miner_heads_actors_id_fk
|
||||||
|
references actors (id),
|
||||||
|
stateroot text not null
|
||||||
|
constraint miner_heads_blocks_stateroot_fk
|
||||||
|
references blocks (parentStateRoot),
|
||||||
|
sectorset text not null,
|
||||||
|
provingset text not null,
|
||||||
|
owner text not null,
|
||||||
|
worker text not null,
|
||||||
|
peerid text not null,
|
||||||
|
sectorsize int not null,
|
||||||
|
power text not null,
|
||||||
|
active int,
|
||||||
|
ppe int not null,
|
||||||
|
slashed_at int not null,
|
||||||
|
constraint miner_heads_id_address_map_owner_fk
|
||||||
|
foreign key (owner) references id_address_map (address),
|
||||||
|
constraint miner_heads_id_address_map_worker_fk
|
||||||
|
foreign key (worker) references id_address_map (address),
|
||||||
|
constraint miner_heads_pk
|
||||||
|
primary key (head, addr)
|
||||||
|
);
|
||||||
|
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *storage) hasBlock(bh cid.Cid) bool {
|
||||||
|
var exitsts bool
|
||||||
|
err := st.db.QueryRow(`select exists (select 1 FROM blocks_synced where cid=?)`, bh.String()).Scan(&exitsts)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return exitsts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *storage) storeActors(actors map[address.Address]map[types.Actor]cid.Cid) error {
|
||||||
|
tx, err := st.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := tx.Prepare(`insert into actors (id, code, head, nonce, balance, stateroot) values (?, ?, ?, ?, ?, ?) on conflict do nothing`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
for addr, acts := range actors {
|
||||||
|
for act, st := range acts {
|
||||||
|
if _, err := stmt.Exec(addr.String(), act.Code.String(), act.Head.String(), act.Nonce, act.Balance.String(), st.String()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *storage) storeMiners(miners map[minerKey]*minerInfo) error {
|
||||||
|
tx, err := st.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := tx.Prepare(`insert into miner_heads (head, addr, stateroot, sectorset, provingset, owner, worker, peerid, sectorsize, power, active, ppe, slashed_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) on conflict do nothing`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
for k, i := range miners {
|
||||||
|
if _, err := stmt.Exec(
|
||||||
|
k.act.Head.String(),
|
||||||
|
k.addr.String(),
|
||||||
|
k.stateroot.String(),
|
||||||
|
i.state.Sectors.String(),
|
||||||
|
i.state.ProvingSet.String(),
|
||||||
|
i.info.Owner.String(),
|
||||||
|
i.info.Worker.String(),
|
||||||
|
i.info.PeerID.String(),
|
||||||
|
i.info.SectorSize,
|
||||||
|
i.state.Power.String(),
|
||||||
|
i.state.Active,
|
||||||
|
i.state.ProvingPeriodEnd,
|
||||||
|
i.state.SlashedAt,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *storage) storeHeaders(bhs map[cid.Cid]*types.BlockHeader, sync bool) error {
|
||||||
|
st.headerLk.Lock()
|
||||||
|
defer st.headerLk.Unlock()
|
||||||
|
|
||||||
|
tx, err := st.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := tx.Prepare(`insert into blocks (cid, parentWeight, parentStateRoot, height, miner, "timestamp") values (?, ?, ?, ?, ?, ?) on conflict do nothing`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
for _, bh := range bhs {
|
||||||
|
if _, err := stmt.Exec(bh.Cid().String(), bh.ParentWeight.String(), bh.ParentStateRoot.String(), bh.Height, bh.Miner.String(), bh.Timestamp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt2, err := tx.Prepare(`insert into block_parents (block, parent) values (?, ?) on conflict do nothing`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stmt2.Close()
|
||||||
|
for _, bh := range bhs {
|
||||||
|
for _, parent := range bh.Parents {
|
||||||
|
if _, err := stmt2.Exec(bh.Cid().String(), parent.String()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sync {
|
||||||
|
stmt, err := tx.Prepare(`insert into blocks_synced (cid) values (?) on conflict do nothing`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
for _, bh := range bhs {
|
||||||
|
if _, err := stmt.Exec(bh.Cid().String()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *storage) storeMessages(msgs map[cid.Cid]*types.Message) error {
|
||||||
|
tx, err := st.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := tx.Prepare(`insert into messages (cid, "from", "to", nonce, "value", gasprice, gaslimit, method, params) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) on conflict do nothing`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
for c, m := range msgs {
|
||||||
|
if _, err := stmt.Exec(
|
||||||
|
c.String(),
|
||||||
|
m.From.String(),
|
||||||
|
m.To.String(),
|
||||||
|
m.Nonce,
|
||||||
|
m.Value.String(),
|
||||||
|
m.GasPrice.String(),
|
||||||
|
m.GasLimit.String(),
|
||||||
|
m.Method,
|
||||||
|
m.Params,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *storage) storeAddressMap(addrs map[address.Address]address.Address) error {
|
||||||
|
tx, err := st.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := tx.Prepare(`insert into id_address_map (id, address) VALUES (?, ?) on conflict do nothing`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
for a, i := range addrs {
|
||||||
|
if i == address.Undef {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := stmt.Exec(
|
||||||
|
i.String(),
|
||||||
|
a.String(),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *storage) storeMsgInclusions(incls map[cid.Cid][]cid.Cid) error {
|
||||||
|
tx, err := st.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := tx.Prepare(`insert into block_messages (block, message) VALUES (?, ?) on conflict do nothing`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
for b, msgs := range incls {
|
||||||
|
for _, msg := range msgs {
|
||||||
|
if _, err := stmt.Exec(
|
||||||
|
b.String(),
|
||||||
|
msg.String(),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *storage) storeMpoolInclusion(msg cid.Cid) error {
|
||||||
|
tx, err := st.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := tx.Prepare(`insert into mpool_messages (msg, add_ts) VALUES (?, ?) on conflict do nothing`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
if _, err := stmt.Exec(
|
||||||
|
msg.String(),
|
||||||
|
time.Now().Unix(),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *storage) close() error {
|
||||||
|
return st.db.Close()
|
||||||
|
}
|
291
cmd/lotus-chainwatch/sync.go
Normal file
291
cmd/lotus-chainwatch/sync.go
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"container/list"
|
||||||
|
"context"
|
||||||
|
actors2 "github.com/filecoin-project/lotus/chain/actors"
|
||||||
|
"github.com/filecoin-project/lotus/chain/address"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/api"
|
||||||
|
"github.com/filecoin-project/lotus/chain/store"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runSyncer(ctx context.Context, api api.FullNode, st *storage) {
|
||||||
|
notifs, err := api.ChainNotify(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for notif := range notifs {
|
||||||
|
for _, change := range notif {
|
||||||
|
switch change.Type {
|
||||||
|
case store.HCCurrent:
|
||||||
|
fallthrough
|
||||||
|
case store.HCApply:
|
||||||
|
syncHead(ctx, api, st, change.Val)
|
||||||
|
case store.HCRevert:
|
||||||
|
log.Warnf("revert todo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
type minerKey struct {
|
||||||
|
addr address.Address
|
||||||
|
act types.Actor
|
||||||
|
stateroot cid.Cid
|
||||||
|
}
|
||||||
|
|
||||||
|
type minerInfo struct {
|
||||||
|
state actors2.StorageMinerActorState
|
||||||
|
info actors2.MinerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipSet) {
|
||||||
|
addresses := map[address.Address]address.Address{}
|
||||||
|
actors := map[address.Address]map[types.Actor]cid.Cid{}
|
||||||
|
var alk sync.Mutex
|
||||||
|
|
||||||
|
log.Infof("Getting headers / actors")
|
||||||
|
|
||||||
|
toSync := map[cid.Cid]*types.BlockHeader{}
|
||||||
|
toVisit := list.New()
|
||||||
|
|
||||||
|
for _, header := range ts.Blocks() {
|
||||||
|
toVisit.PushBack(header)
|
||||||
|
}
|
||||||
|
|
||||||
|
for toVisit.Len() > 0 {
|
||||||
|
bh := toVisit.Remove(toVisit.Back()).(*types.BlockHeader)
|
||||||
|
|
||||||
|
if _, seen := toSync[bh.Cid()]; seen || st.hasBlock(bh.Cid()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
toSync[bh.Cid()] = bh
|
||||||
|
addresses[bh.Miner] = address.Undef
|
||||||
|
|
||||||
|
if len(toSync)%500 == 10 {
|
||||||
|
log.Infof("todo: (%d) %s", len(toSync), bh.Cid())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bh.Parents) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pts, err := api.ChainGetTipSet(ctx, types.NewTipSetKey(bh.Parents...))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, header := range pts.Blocks() {
|
||||||
|
toVisit.PushBack(header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Syncing %d blocks", len(toSync))
|
||||||
|
|
||||||
|
log.Infof("Persisting actors")
|
||||||
|
|
||||||
|
paDone := 0
|
||||||
|
par(50, maparr(toSync), func(bh *types.BlockHeader) {
|
||||||
|
paDone++
|
||||||
|
if paDone%100 == 0 {
|
||||||
|
log.Infof("pa: %d %d%%", paDone, (paDone*100)/len(toSync))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bh.Parents) == 0 { // genesis case
|
||||||
|
ts, err := types.NewTipSet([]*types.BlockHeader{bh})
|
||||||
|
aadrs, err := api.StateListActors(ctx, ts)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
par(50, aadrs, func(addr address.Address) {
|
||||||
|
act, err := api.StateGetActor(ctx, addr, ts)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
alk.Lock()
|
||||||
|
_, ok := actors[addr]
|
||||||
|
if !ok {
|
||||||
|
actors[addr] = map[types.Actor]cid.Cid{}
|
||||||
|
}
|
||||||
|
actors[addr][*act] = bh.ParentStateRoot
|
||||||
|
addresses[addr] = address.Undef
|
||||||
|
alk.Unlock()
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pts, err := api.ChainGetTipSet(ctx, types.NewTipSetKey(bh.Parents...))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
changes, err := api.StateChangedActors(ctx, pts.ParentState(), bh.ParentStateRoot)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for a, act := range changes {
|
||||||
|
addr, err := address.NewFromString(a)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
alk.Lock()
|
||||||
|
_, ok := actors[addr]
|
||||||
|
if !ok {
|
||||||
|
actors[addr] = map[types.Actor]cid.Cid{}
|
||||||
|
}
|
||||||
|
actors[addr][act] = bh.ParentStateRoot
|
||||||
|
addresses[addr] = address.Undef
|
||||||
|
alk.Unlock()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := st.storeActors(actors); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Persisting miners")
|
||||||
|
|
||||||
|
miners := map[minerKey]*minerInfo{}
|
||||||
|
|
||||||
|
for addr, m := range actors {
|
||||||
|
for actor, c := range m {
|
||||||
|
if actor.Code != actors2.StorageMinerCodeCid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
miners[minerKey{
|
||||||
|
addr: addr,
|
||||||
|
act: actor,
|
||||||
|
stateroot: c,
|
||||||
|
}] = &minerInfo{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
par(50, kvmaparr(miners), func(it func() (minerKey, *minerInfo)) {
|
||||||
|
k, info := it()
|
||||||
|
|
||||||
|
astb, err := api.ChainReadObj(ctx, k.act.Head)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := info.state.UnmarshalCBOR(bytes.NewReader(astb)); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ib, err := api.ChainReadObj(ctx, info.state.Info)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := info.info.UnmarshalCBOR(bytes.NewReader(ib)); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := st.storeMiners(miners); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Persisting headers")
|
||||||
|
if err := st.storeHeaders(toSync, true); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Getting messages")
|
||||||
|
|
||||||
|
msgs, incls := fetchMessages(ctx, api, toSync)
|
||||||
|
|
||||||
|
if err := st.storeMessages(msgs); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := st.storeMsgInclusions(incls); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Resolving addresses")
|
||||||
|
|
||||||
|
for _, message := range msgs {
|
||||||
|
addresses[message.To] = address.Undef
|
||||||
|
addresses[message.From] = address.Undef
|
||||||
|
}
|
||||||
|
|
||||||
|
par(50, kmaparr(addresses), func(addr address.Address) {
|
||||||
|
raddr, err := api.StateLookupID(ctx, addr, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
alk.Lock()
|
||||||
|
addresses[addr] = raddr
|
||||||
|
alk.Unlock()
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := st.storeAddressMap(addresses); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Sync done")
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchMessages(ctx context.Context, api api.FullNode, toSync map[cid.Cid]*types.BlockHeader) (map[cid.Cid]*types.Message, map[cid.Cid][]cid.Cid) {
|
||||||
|
var lk sync.Mutex
|
||||||
|
messages := map[cid.Cid]*types.Message{}
|
||||||
|
inclusions := map[cid.Cid][]cid.Cid{} // block -> msgs
|
||||||
|
|
||||||
|
par(50, maparr(toSync), func(header *types.BlockHeader) {
|
||||||
|
msgs, err := api.ChainGetBlockMessages(ctx, header.Cid())
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vmm := make([]*types.Message, 0, len(msgs.Cids))
|
||||||
|
for _, m := range msgs.BlsMessages {
|
||||||
|
vmm = append(vmm, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range msgs.SecpkMessages {
|
||||||
|
vmm = append(vmm, &m.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
lk.Lock()
|
||||||
|
for _, message := range vmm {
|
||||||
|
messages[message.Cid()] = message
|
||||||
|
inclusions[header.Cid()] = append(inclusions[header.Cid()], message.Cid())
|
||||||
|
}
|
||||||
|
lk.Unlock()
|
||||||
|
})
|
||||||
|
|
||||||
|
return messages, inclusions
|
||||||
|
}
|
253
cmd/lotus-chainwatch/templates.go
Normal file
253
cmd/lotus-chainwatch/templates.go
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
rice "github.com/GeertJohan/go.rice"
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/api"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
api api.FullNode
|
||||||
|
st *storage
|
||||||
|
site *rice.Box
|
||||||
|
assets http.Handler
|
||||||
|
|
||||||
|
templates map[string]*template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHandler(api api.FullNode, st *storage) (*handler, error) {
|
||||||
|
h := &handler{
|
||||||
|
api: api,
|
||||||
|
st: st,
|
||||||
|
site: rice.MustFindBox("site"),
|
||||||
|
|
||||||
|
templates: map[string]*template.Template{},
|
||||||
|
}
|
||||||
|
h.assets = http.FileServer(h.site.HTTPBox())
|
||||||
|
|
||||||
|
funcs := template.FuncMap{
|
||||||
|
"count": h.count,
|
||||||
|
"countCol": h.countCol,
|
||||||
|
"sum": h.sum,
|
||||||
|
"netPower": h.netPower,
|
||||||
|
"queryNum": h.queryNum,
|
||||||
|
"sizeStr": sizeStr,
|
||||||
|
"strings": h.strings,
|
||||||
|
"messages": h.messages,
|
||||||
|
|
||||||
|
"param": func(string) string { return "" }, // replaced in request handler
|
||||||
|
}
|
||||||
|
|
||||||
|
base := template.New("")
|
||||||
|
|
||||||
|
base.Funcs(funcs)
|
||||||
|
|
||||||
|
return h, h.site.Walk("", func(path string, info os.FileInfo, err error) error {
|
||||||
|
if filepath.Ext(path) != ".html" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info(path)
|
||||||
|
|
||||||
|
h.templates["/"+path], err = base.New(path).Parse(h.site.MustString(path))
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h, err := newHandler(h.api, h.st) // for faster dev
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p := r.URL.Path
|
||||||
|
if p == "/" {
|
||||||
|
p = "/index.html"
|
||||||
|
}
|
||||||
|
|
||||||
|
t, ok := h.templates[p]
|
||||||
|
if !ok {
|
||||||
|
h.assets.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err = t.Clone()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Funcs(map[string]interface{}{
|
||||||
|
"param": r.FormValue,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := t.Execute(w, nil); err != nil {
|
||||||
|
log.Errorf("%+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info(r.URL.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template funcs
|
||||||
|
|
||||||
|
func (h *handler) count(table string, filters ...string) (int, error) {
|
||||||
|
// explicitly not caring about sql injection too much, this doesn't take user input
|
||||||
|
|
||||||
|
filts := ""
|
||||||
|
if len(filters) > 0 {
|
||||||
|
filts = " where "
|
||||||
|
for _, filter := range filters {
|
||||||
|
filts += filter + " and "
|
||||||
|
}
|
||||||
|
filts = filts[:len(filts)-5]
|
||||||
|
}
|
||||||
|
|
||||||
|
var c int
|
||||||
|
err := h.st.db.QueryRow("select count(1) from " + table + filts).Scan(&c)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) countCol(table string, col string, filters ...string) (int, error) {
|
||||||
|
// explicitly not caring about sql injection too much, this doesn't take user input
|
||||||
|
|
||||||
|
filts := ""
|
||||||
|
if len(filters) > 0 {
|
||||||
|
filts = " where "
|
||||||
|
for _, filter := range filters {
|
||||||
|
filts += filter + " and "
|
||||||
|
}
|
||||||
|
filts = filts[:len(filts)-5]
|
||||||
|
}
|
||||||
|
|
||||||
|
var c int
|
||||||
|
err := h.st.db.QueryRow("select count(distinct " + col + ") from " + table + filts).Scan(&c)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) sum(table string, col string) (types.BigInt, error) {
|
||||||
|
return h.queryNum("select sum(cast(" + col + " as bigint)) from " + table)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) netPower(slashFilt string) (types.BigInt, error) {
|
||||||
|
if slashFilt != "" {
|
||||||
|
slashFilt = " where " + slashFilt
|
||||||
|
}
|
||||||
|
return h.queryNum(`select sum(power) from (
|
||||||
|
select miner_heads.power, miner_heads.slashed_at, max(height) from miner_heads
|
||||||
|
inner join blocks b on miner_heads.stateroot = b.parentStateRoot
|
||||||
|
group by miner_heads.addr)` + slashFilt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) queryNum(q string, p ...interface{}) (types.BigInt, error) {
|
||||||
|
// explicitly not caring about sql injection too much, this doesn't take user input
|
||||||
|
|
||||||
|
var c string
|
||||||
|
err := h.st.db.QueryRow(q, p...).Scan(&c)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("qnum ", q, p, err)
|
||||||
|
return types.NewInt(0), err
|
||||||
|
}
|
||||||
|
|
||||||
|
i := types.NewInt(0)
|
||||||
|
_, ok := i.SetString(c, 10)
|
||||||
|
if !ok {
|
||||||
|
return types.NewInt(0), xerrors.New("num parse error: " + c)
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var units = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB"}
|
||||||
|
|
||||||
|
func sizeStr(size types.BigInt) string {
|
||||||
|
size = types.BigMul(size, types.NewInt(100))
|
||||||
|
i := 0
|
||||||
|
for types.BigCmp(size, types.NewInt(102400)) >= 0 && i < len(units)-1 {
|
||||||
|
size = types.BigDiv(size, types.NewInt(1024))
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s.%s %s", types.BigDiv(size, types.NewInt(100)), types.BigMod(size, types.NewInt(100)), units[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) strings(table string, col string, filter string, args ...interface{}) (out []string, err error) {
|
||||||
|
if len(filter) > 0 {
|
||||||
|
filter = " where " + filter
|
||||||
|
}
|
||||||
|
log.Info("strings qstr ", "select "+col+" from "+table+filter)
|
||||||
|
rws, err := h.st.db.Query("select "+col+" from "+table+filter, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for rws.Next() {
|
||||||
|
var r string
|
||||||
|
if err := rws.Scan(&r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out = append(out, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) messages(filter string, args ...interface{}) (out []types.Message, err error) {
|
||||||
|
if len(filter) > 0 {
|
||||||
|
filter = " where " + filter
|
||||||
|
}
|
||||||
|
|
||||||
|
rws, err := h.st.db.Query("select * from messages "+filter, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for rws.Next() {
|
||||||
|
var r types.Message
|
||||||
|
var cs string
|
||||||
|
|
||||||
|
if err := rws.Scan(
|
||||||
|
&cs,
|
||||||
|
&r.From,
|
||||||
|
&r.To,
|
||||||
|
&r.Nonce,
|
||||||
|
&r.Value,
|
||||||
|
&r.GasPrice,
|
||||||
|
&r.GasLimit,
|
||||||
|
&r.Method,
|
||||||
|
&r.Params,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := cid.Parse(cs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c != r.Cid() {
|
||||||
|
log.Warn("msg cid doesn't match")
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ http.Handler = &handler{}
|
85
cmd/lotus-chainwatch/utils.go
Normal file
85
cmd/lotus-chainwatch/utils.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func maparr(in interface{}) interface{} {
|
||||||
|
rin := reflect.ValueOf(in)
|
||||||
|
rout := reflect.MakeSlice(reflect.SliceOf(rin.Type().Elem()), rin.Len(), rin.Len())
|
||||||
|
var i int
|
||||||
|
|
||||||
|
it := rin.MapRange()
|
||||||
|
for it.Next() {
|
||||||
|
rout.Index(i).Set(it.Value())
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return rout.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
func kmaparr(in interface{}) interface{} {
|
||||||
|
rin := reflect.ValueOf(in)
|
||||||
|
rout := reflect.MakeSlice(reflect.SliceOf(rin.Type().Key()), rin.Len(), rin.Len())
|
||||||
|
var i int
|
||||||
|
|
||||||
|
it := rin.MapRange()
|
||||||
|
for it.Next() {
|
||||||
|
rout.Index(i).Set(it.Key())
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return rout.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// map[k]v => []func() (k, v)
|
||||||
|
func kvmaparr(in interface{}) interface{} {
|
||||||
|
rin := reflect.ValueOf(in)
|
||||||
|
|
||||||
|
t := reflect.FuncOf([]reflect.Type{}, []reflect.Type{
|
||||||
|
rin.Type().Key(),
|
||||||
|
rin.Type().Elem(),
|
||||||
|
}, false)
|
||||||
|
|
||||||
|
rout := reflect.MakeSlice(reflect.SliceOf(t), rin.Len(), rin.Len())
|
||||||
|
var i int
|
||||||
|
|
||||||
|
it := rin.MapRange()
|
||||||
|
for it.Next() {
|
||||||
|
k := it.Key()
|
||||||
|
v := it.Value()
|
||||||
|
|
||||||
|
rout.Index(i).Set(reflect.MakeFunc(t, func(args []reflect.Value) (results []reflect.Value) {
|
||||||
|
return []reflect.Value{k, v}
|
||||||
|
}))
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return rout.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
func par(concurrency int, arr interface{}, f interface{}) {
|
||||||
|
throttle := make(chan struct{}, concurrency)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
varr := reflect.ValueOf(arr)
|
||||||
|
l := varr.Len()
|
||||||
|
|
||||||
|
rf := reflect.ValueOf(f)
|
||||||
|
|
||||||
|
wg.Add(l)
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
throttle <- struct{}{}
|
||||||
|
|
||||||
|
go func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
defer func() {
|
||||||
|
<-throttle
|
||||||
|
}()
|
||||||
|
rf.Call([]reflect.Value{varr.Index(i)})
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
1
go.mod
1
go.mod
@ -72,6 +72,7 @@ require (
|
|||||||
github.com/libp2p/go-maddr-filter v0.0.5
|
github.com/libp2p/go-maddr-filter v0.0.5
|
||||||
github.com/mattn/go-isatty v0.0.9 // indirect
|
github.com/mattn/go-isatty v0.0.9 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.12.0
|
||||||
github.com/miekg/dns v1.1.16 // indirect
|
github.com/miekg/dns v1.1.16 // indirect
|
||||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1
|
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1
|
||||||
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771
|
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771
|
||||||
|
2
go.sum
2
go.sum
@ -425,6 +425,8 @@ github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg
|
|||||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.12.0 h1:u/x3mp++qUxvYfulZ4HKOvVO0JWhk7HtE8lWhbGz/Do=
|
||||||
|
github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/api"
|
||||||
"github.com/filecoin-project/lotus/chain"
|
"github.com/filecoin-project/lotus/chain"
|
||||||
"github.com/filecoin-project/lotus/chain/address"
|
"github.com/filecoin-project/lotus/chain/address"
|
||||||
"github.com/filecoin-project/lotus/chain/types"
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
@ -53,3 +54,7 @@ func (a *MpoolAPI) MpoolPushMessage(ctx context.Context, msg *types.Message) (*t
|
|||||||
func (a *MpoolAPI) MpoolGetNonce(ctx context.Context, addr address.Address) (uint64, error) {
|
func (a *MpoolAPI) MpoolGetNonce(ctx context.Context, addr address.Address) (uint64, error) {
|
||||||
return a.Mpool.GetNonce(addr)
|
return a.Mpool.GetNonce(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *MpoolAPI) MpoolSub(ctx context.Context) (<-chan api.MpoolUpdate, error) {
|
||||||
|
return a.Mpool.Updates(ctx)
|
||||||
|
}
|
||||||
|
@ -154,6 +154,15 @@ func (a *StateAPI) StateGetActor(ctx context.Context, actor address.Address, ts
|
|||||||
return state.GetActor(actor)
|
return state.GetActor(actor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *StateAPI) StateLookupID(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) {
|
||||||
|
state, err := a.stateForTs(ctx, ts)
|
||||||
|
if err != nil {
|
||||||
|
return address.Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.LookupID(addr)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *StateAPI) StateReadState(ctx context.Context, act *types.Actor, ts *types.TipSet) (*api.ActorState, error) {
|
func (a *StateAPI) StateReadState(ctx context.Context, act *types.Actor, ts *types.TipSet) (*api.ActorState, error) {
|
||||||
state, err := a.stateForTs(ctx, ts)
|
state, err := a.stateForTs(ctx, ts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -294,3 +303,54 @@ func (a *StateAPI) StateMarketDeals(ctx context.Context, ts *types.TipSet) (map[
|
|||||||
func (a *StateAPI) StateMarketStorageDeal(ctx context.Context, dealId uint64, ts *types.TipSet) (*actors.OnChainDeal, error) {
|
func (a *StateAPI) StateMarketStorageDeal(ctx context.Context, dealId uint64, ts *types.TipSet) (*actors.OnChainDeal, error) {
|
||||||
return stmgr.GetStorageDeal(ctx, a.StateManager, dealId, ts)
|
return stmgr.GetStorageDeal(ctx, a.StateManager, dealId, ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *StateAPI) StateChangedActors(ctx context.Context, old cid.Cid, new cid.Cid) (map[string]types.Actor, error) {
|
||||||
|
cst := hamt.CSTFromBstore(a.Chain.Blockstore())
|
||||||
|
|
||||||
|
nh, err := hamt.LoadNode(ctx, cst, new)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
oh, err := hamt.LoadNode(ctx, cst, old)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := map[string]types.Actor{}
|
||||||
|
|
||||||
|
err = nh.ForEach(ctx, func(k string, nval interface{}) error {
|
||||||
|
ncval := nval.(*cbg.Deferred)
|
||||||
|
var act types.Actor
|
||||||
|
|
||||||
|
var ocval cbg.Deferred
|
||||||
|
|
||||||
|
switch err := oh.Find(ctx, k, &ocval); err {
|
||||||
|
case nil:
|
||||||
|
if bytes.Equal(ocval.Raw, ncval.Raw) {
|
||||||
|
return nil // not changed
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case hamt.ErrNotFound:
|
||||||
|
if err := act.UnmarshalCBOR(bytes.NewReader(ncval.Raw)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := address.NewFromBytes([]byte(k))
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("address in state tree was not valid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out[addr.String()] = act
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
@ -54,7 +54,7 @@ func (a *SyncAPI) SyncSubmitBlock(ctx context.Context, blk *types.BlockMsg) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := a.Syncer.ValidateMsgMeta(fb); err != nil {
|
if err := a.Syncer.ValidateMsgMeta(fb); err != nil {
|
||||||
xerrors.Errorf("provided messages did not match block: %w", err)
|
return xerrors.Errorf("provided messages did not match block: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ts, err := types.NewTipSet([]*types.BlockHeader{blk.Header})
|
ts, err := types.NewTipSet([]*types.BlockHeader{blk.Header})
|
||||||
@ -73,3 +73,7 @@ func (a *SyncAPI) SyncSubmitBlock(ctx context.Context, blk *types.BlockMsg) erro
|
|||||||
// TODO: anything else to do here?
|
// TODO: anything else to do here?
|
||||||
return a.PubSub.Publish("/fil/blocks", b)
|
return a.PubSub.Publish("/fil/blocks", b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *SyncAPI) SyncIncomingBlocks(ctx context.Context) (<-chan *types.BlockHeader, error) {
|
||||||
|
return a.Syncer.IncomingBlocks(ctx)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user