Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5de7b62dbc | ||
|
|
5fb76ddad2 | ||
|
|
cc02092aae | ||
|
|
3e4b80e7f6 | ||
|
|
14ae8c7272 | ||
|
|
567b8082c3 | ||
|
|
f3f92ce5bc | ||
|
|
c4158f12f1 | ||
|
|
3429941639 | ||
|
|
11dc902d53 | ||
|
|
41b775fdd4 | ||
|
|
e9d2a3edd9 | ||
|
|
42696372ee | ||
|
|
39625eada7 | ||
|
|
7a7e5a85ad | ||
|
|
254295b9c3 | ||
|
|
4dd79ec6fa | ||
|
|
9ccd25b072 | ||
|
|
5e7f561133 | ||
|
|
c68a8f802e | ||
|
|
29f5f451b3 | ||
|
|
2caa7164a6 | ||
|
|
21148033e2 | ||
|
|
bc88c632d1 | ||
|
|
1e17ea3fff | ||
|
|
ca7e5f5e16 | ||
|
|
cd2595da67 | ||
|
|
3a9f1bf9a2 | ||
|
|
9094405537 | ||
|
|
4db61e71da | ||
|
|
a6417dc24b | ||
|
|
1bc9fbca20 | ||
|
|
369ec8221f | ||
|
|
052fc35a91 | ||
|
|
b8ab549bcb | ||
|
|
4f60c1afe7 | ||
|
|
9cd34509ee | ||
|
|
a2fd0aad41 | ||
|
|
01ac4e5da3 | ||
|
|
01e230e810 | ||
|
|
78ee64d9f8 | ||
|
|
e5a7cb635c | ||
|
|
49ddcdb4b0 | ||
|
|
b5ae3c055b | ||
|
|
9712bbc854 | ||
|
|
3134fcb54a | ||
|
|
87d668390e | ||
|
|
710286d6e7 | ||
|
|
04edeccbf4 | ||
|
|
db5929f4a5 | ||
|
|
e65215f1b5 | ||
|
|
c97720e731 | ||
|
|
3aaa4fb692 | ||
|
|
7e1e9bcd60 | ||
|
|
8c347d8764 | ||
|
|
f7bffdf63a | ||
|
|
bda43a4627 | ||
|
|
19195ddf96 | ||
|
|
a5935486ef | ||
|
|
ffd012906c | ||
|
|
f8a1fb2321 | ||
|
|
168b0f8df6 | ||
|
|
70d8378404 | ||
|
|
00e38ce126 | ||
|
|
02c3f8ed80 | ||
|
|
e949a458ec | ||
|
|
228ff3ba57 | ||
|
|
4c1dfd7eda | ||
|
|
e1799f1524 | ||
|
|
8f0e04027e | ||
|
|
abdafc8ee4 | ||
|
|
9030b559e8 | ||
|
|
cd34d02377 | ||
|
|
a362ef97df | ||
|
|
92f392d422 | ||
|
|
e8ddaab1c3 | ||
|
|
da78ff9151 | ||
|
|
e576c7a08e | ||
|
|
2d180cdacf | ||
|
|
77c7eb6fd9 | ||
|
|
857b5962da | ||
|
|
8c54c09ab7 | ||
|
|
6927a532cf | ||
|
|
49dccf4284 | ||
|
|
a4751ddd01 | ||
|
|
c811133aac | ||
|
|
c16ea42fad | ||
|
|
05e521ab03 | ||
|
|
1ef490feea | ||
|
|
3b826e41f6 | ||
|
|
d232299c77 | ||
|
|
f774d2aab1 | ||
|
|
642aed93d9 | ||
|
|
4f9c907248 | ||
|
|
612b2fe9d7 | ||
|
|
0fddf3e114 | ||
|
|
c3898e4abf | ||
|
|
726c09618a | ||
|
|
12f10ac414 | ||
|
|
77b3bf8bc2 | ||
|
|
a0e1d5f567 | ||
|
|
4e32c7edb3 | ||
|
|
c72def55fe | ||
|
|
982962ef0a | ||
|
|
90a470ed78 | ||
|
|
f812cac142 | ||
|
|
aeab3a709a | ||
|
|
7058f4eb63 | ||
|
|
8d51a20143 | ||
|
|
a4f6269724 | ||
|
|
fbfe57cd63 | ||
|
|
0118a78967 | ||
|
|
659c723ea3 | ||
|
|
bbb9a9cd1a | ||
|
|
5052a87a82 | ||
|
|
b9180a99d4 | ||
|
|
db1773d708 | ||
|
|
a1b009328d | ||
|
|
393a9ca4f2 | ||
|
|
e910a045d0 | ||
|
|
5d9a775699 | ||
|
|
8233143578 | ||
|
|
950cb8bc03 | ||
|
|
eafb04004e | ||
|
|
3d3e384d5c | ||
|
|
38533631bf | ||
|
|
6fe39ef065 | ||
|
|
47c3e14d4b | ||
|
|
d8b647cc3a | ||
|
|
abad4a3941 | ||
|
|
2c0a4914cf | ||
|
|
e54b49b442 | ||
|
|
50aa1e6baa | ||
|
|
e3c897fb68 | ||
|
|
728afc0587 | ||
|
|
95a9dc9db0 | ||
|
|
b62fef7541 | ||
|
|
dd490220d7 | ||
|
|
7f932b96ae | ||
|
|
4af9a20903 | ||
|
|
f9cb549fbe | ||
|
|
7e342e60d1 | ||
|
|
f98063d604 | ||
|
|
10c4fae8c0 | ||
|
|
cae24a226b | ||
|
|
0162322833 | ||
|
|
06dfe3c5ae | ||
|
|
a98c4038f4 | ||
|
|
4c760ec1b1 | ||
|
|
176d0bb7c0 | ||
|
|
bc90b857c9 | ||
|
|
ef0abf2b98 | ||
|
|
008a2969b2 | ||
|
|
7d9f9ba756 | ||
|
|
358b36bc66 | ||
|
|
f1e686f706 | ||
|
|
0b1fa54a4d | ||
|
|
24c7f47566 | ||
|
|
4037d707c9 | ||
|
|
001d4ab002 | ||
|
|
f115603093 | ||
|
|
d7bb284e57 | ||
|
|
11c53c26ff | ||
|
|
d28bbf4674 | ||
|
|
a7d662a7d8 | ||
|
|
6f8c464393 | ||
|
|
c8104a03e6 | ||
|
|
69559bcf4f | ||
|
|
d524821c86 | ||
|
|
e864244226 | ||
|
|
5074cf8beb | ||
|
|
b448de422e | ||
|
|
99d3571772 | ||
|
|
0354275b39 | ||
|
|
309fbc15b2 | ||
|
|
28e3c863bf | ||
|
|
06162290af | ||
|
|
628872d0e4 | ||
|
|
960523f45f | ||
|
|
fb04b17fad | ||
|
|
8adc831a31 | ||
|
|
566a99240d | ||
|
|
7b2241f051 | ||
|
|
55ed45f108 | ||
|
|
f8e7901b89 | ||
|
|
9e70e95242 | ||
|
|
68d38eff33 | ||
|
|
abd400801c | ||
|
|
d24a750ba4 | ||
|
|
9903eba7fb | ||
|
|
3752311f44 | ||
|
|
b71f771acb | ||
|
|
9f3145aec0 | ||
|
|
8638076ecb | ||
|
|
8649baccf7 | ||
|
|
c33d9e9668 | ||
|
|
03d70e1513 | ||
|
|
d20255b1b4 | ||
|
|
cd91e42a56 | ||
|
|
e2435be0b9 | ||
|
|
5adc188466 | ||
|
|
101ba0b796 | ||
|
|
8cd2012d27 | ||
|
|
4959596527 | ||
|
|
956c397c3c | ||
|
|
1249ede2b7 | ||
|
|
596ed330dd | ||
|
|
b4c1594d99 | ||
|
|
98cb08d853 | ||
|
|
6b86dcde4a | ||
|
|
fbeaab466a | ||
|
|
5e77b5da9f | ||
|
|
7c4225d01c | ||
|
|
de7d6c255c | ||
|
|
99060fbb64 | ||
|
|
0c8d648998 | ||
|
|
aa18d1985e | ||
|
|
b0edf924b4 | ||
|
|
997a46a90f | ||
|
|
20503de3ba | ||
|
|
030472d61c | ||
|
|
4f1efbe38f | ||
|
|
d8a517f992 | ||
|
|
c5a9f180af | ||
|
|
9077dea0cd | ||
|
|
d670257402 | ||
|
|
159a1a4645 | ||
|
|
d4b230c137 | ||
|
|
ce30162907 | ||
|
|
c0cdcbb9e0 | ||
|
|
21415a9847 | ||
|
|
8c05251cce | ||
|
|
7c15d1e8c3 |
@@ -314,7 +314,7 @@ workflows:
|
||||
ci:
|
||||
jobs:
|
||||
- lint-changes:
|
||||
args: "--new-from-rev origin/next"
|
||||
args: "--new-from-rev origin/master"
|
||||
- mod-tidy-check
|
||||
- gofmt
|
||||
- test:
|
||||
|
||||
@@ -35,3 +35,4 @@ build/paramfetch.sh
|
||||
bin/ipget
|
||||
bin/tmp/*
|
||||
.idea
|
||||
scratchpad
|
||||
|
||||
@@ -6,7 +6,7 @@ all: build
|
||||
unexport GOFLAGS
|
||||
|
||||
GOVERSION:=$(shell go version | cut -d' ' -f 3 | cut -d. -f 2)
|
||||
ifeq ($(shell expr $(GOVERSION) \< 13), 1)
|
||||
ifeq ($(shell expr $(GOVERSION) \< 14), 1)
|
||||
$(warning Your Golang version is go 1.$(GOVERSION))
|
||||
$(error Update Golang to version $(shell grep '^go' go.mod))
|
||||
endif
|
||||
@@ -105,15 +105,17 @@ install:
|
||||
|
||||
install-services: install
|
||||
mkdir -p /usr/local/lib/systemd/system
|
||||
mkdir -p /var/log/lotus
|
||||
install -C -m 0644 ./scripts/lotus-daemon.service /usr/local/lib/systemd/system/lotus-daemon.service
|
||||
install -C -m 0644 ./scripts/lotus-miner.service /usr/local/lib/systemd/system/lotus-miner.service
|
||||
systemctl daemon-reload
|
||||
@echo
|
||||
@echo "lotus and lotus-miner services installed. Don't forget to 'systemctl enable lotus|lotus-miner' for it to be enabled on startup."
|
||||
@echo "lotus-daemon and lotus-miner services installed. Don't forget to 'systemctl enable lotus-daemon|lotus-miner' for it to be enabled on startup."
|
||||
|
||||
clean-services:
|
||||
rm -f /usr/local/lib/systemd/system/lotus-daemon.service
|
||||
rm -f /usr/local/lib/systemd/system/lotus-miner.service
|
||||
rm -f /usr/local/lib/systemd/system/chainwatch.service
|
||||
systemctl daemon-reload
|
||||
|
||||
# TOOLS
|
||||
@@ -160,6 +162,13 @@ chainwatch:
|
||||
.PHONY: chainwatch
|
||||
BINS+=chainwatch
|
||||
|
||||
install-chainwatch-service: chainwatch
|
||||
install -C ./chainwatch /usr/local/bin/chainwatch
|
||||
install -C -m 0644 ./scripts/chainwatch.service /usr/local/lib/systemd/system/chainwatch.service
|
||||
systemctl daemon-reload
|
||||
@echo
|
||||
@echo "chainwatch installed. Don't forget to 'systemctl enable chainwatch' for it to be enabled on startup."
|
||||
|
||||
bench:
|
||||
rm -f bench
|
||||
go build -o bench ./cmd/lotus-bench
|
||||
@@ -182,6 +191,12 @@ health:
|
||||
.PHONY: health
|
||||
BINS+=health
|
||||
|
||||
testground:
|
||||
go build -tags testground -o /dev/null ./cmd/lotus
|
||||
|
||||
.PHONY: testground
|
||||
BINS+=testground
|
||||
|
||||
# MISC
|
||||
|
||||
buildall: $(BINS)
|
||||
|
||||
@@ -8,6 +8,10 @@ Lotus is an implementation of the Filecoin Distributed Storage Network. For more
|
||||
|
||||
For instructions on how to build lotus from source, please visit [https://docs.lotu.sh](https://docs.lotu.sh) or read the source [here](https://github.com/filecoin-project/lotus/tree/master/documentation).
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please send an email to security@filecoin.org. See our [security policy](SECURITY.md) for more details.
|
||||
|
||||
## Development
|
||||
|
||||
All work is tracked via issues. An attempt at keeping an up-to-date view on remaining work is in the [lotus testnet github project board](https://github.com/filecoin-project/lotus/projects/1).
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
For *critical* bugs, please send an email to security@filecoin.org.
|
||||
|
||||
The bug reporting process differs between bugs that are critical and may crash the network, and others that are unlikely to cause problems if malicious parties know about it. For non-critical bugs, please simply file a GitHub [issue](https://github.com/filecoin-project/lotus/issues/new?template=bug_report.md).
|
||||
|
||||
Please try to provide a clear description of any bugs reported, along with how to reproduce the bug if possible. More detailed bug reports (especially those with a PoC included) will help us move forward much faster. Additionally, please avoid reporting bugs that already have open issues. Take a moment to search the issue list of the related GitHub repositories before writing up a new report.
|
||||
|
||||
Here are some examples of bugs we would consider 'critical':
|
||||
|
||||
* If you can spend from a `multisig` wallet you do not control the keys for.
|
||||
* If you can cause a miner to be slashed without them actually misbehaving.
|
||||
* If you can maintain power without submitting windowed posts regularly.
|
||||
* If you can craft a message that causes lotus nodes to panic.
|
||||
* If you can cause your miner to win significantly more blocks than it should.
|
||||
* If you can craft a message that causes a persistent fork in the network.
|
||||
* If you can cause the total amount of Filecoin in the network to no longer be 2 billion.
|
||||
|
||||
This is not an exhaustive list, but should provide some idea of what we consider 'critical'.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
* TODO: This should be defined and set up by Mainnet launch.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| Testnet | :white_check_mark: |
|
||||
+1
-1
@@ -206,7 +206,7 @@ type FullNode interface {
|
||||
StateCall(context.Context, *types.Message, types.TipSetKey) (*InvocResult, error)
|
||||
StateReplay(context.Context, types.TipSetKey, cid.Cid) (*InvocResult, error)
|
||||
StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error)
|
||||
StateReadState(ctx context.Context, act *types.Actor, tsk types.TipSetKey) (*ActorState, error)
|
||||
StateReadState(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*ActorState, error)
|
||||
StateListMessages(ctx context.Context, match *types.Message, tsk types.TipSetKey, toht abi.ChainEpoch) ([]cid.Cid, error)
|
||||
|
||||
StateNetworkName(context.Context) (dtypes.NetworkName, error)
|
||||
|
||||
+11
-1
@@ -36,6 +36,7 @@ type StorageMiner interface {
|
||||
SectorsRefs(context.Context) (map[string][]SealedRef, error)
|
||||
|
||||
SectorsUpdate(context.Context, abi.SectorNumber, SectorState) error
|
||||
SectorRemove(context.Context, abi.SectorNumber) error
|
||||
|
||||
StorageList(ctx context.Context) (map[stores.ID][]stores.Decl, error)
|
||||
StorageLocal(ctx context.Context) (map[stores.ID]string, error)
|
||||
@@ -55,7 +56,16 @@ type StorageMiner interface {
|
||||
|
||||
DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error
|
||||
DealsList(ctx context.Context) ([]storagemarket.StorageDeal, error)
|
||||
DealsSetAcceptingStorageDeals(context.Context, bool) error
|
||||
DealsConsiderOnlineStorageDeals(context.Context) (bool, error)
|
||||
DealsSetConsiderOnlineStorageDeals(context.Context, bool) error
|
||||
DealsConsiderOnlineRetrievalDeals(context.Context) (bool, error)
|
||||
DealsSetConsiderOnlineRetrievalDeals(context.Context, bool) error
|
||||
DealsPieceCidBlocklist(context.Context) ([]cid.Cid, error)
|
||||
DealsSetPieceCidBlocklist(context.Context, []cid.Cid) error
|
||||
DealsConsiderOfflineStorageDeals(context.Context) (bool, error)
|
||||
DealsSetConsiderOfflineStorageDeals(context.Context, bool) error
|
||||
DealsConsiderOfflineRetrievalDeals(context.Context) (bool, error)
|
||||
DealsSetConsiderOfflineRetrievalDeals(context.Context, bool) error
|
||||
|
||||
StorageAddLocal(ctx context.Context, path string) error
|
||||
}
|
||||
|
||||
+76
-16
@@ -139,7 +139,7 @@ type FullNodeStruct struct {
|
||||
StateCall func(context.Context, *types.Message, types.TipSetKey) (*api.InvocResult, error) `perm:"read"`
|
||||
StateReplay func(context.Context, types.TipSetKey, cid.Cid) (*api.InvocResult, error) `perm:"read"`
|
||||
StateGetActor func(context.Context, address.Address, types.TipSetKey) (*types.Actor, error) `perm:"read"`
|
||||
StateReadState func(context.Context, *types.Actor, types.TipSetKey) (*api.ActorState, error) `perm:"read"`
|
||||
StateReadState func(context.Context, address.Address, types.TipSetKey) (*api.ActorState, error) `perm:"read"`
|
||||
StatePledgeCollateral func(context.Context, types.TipSetKey) (types.BigInt, error) `perm:"read"`
|
||||
StateWaitMsg func(ctx context.Context, cid cid.Cid, confidence uint64) (*api.MsgLookup, error) `perm:"read"`
|
||||
StateSearchMsg func(context.Context, cid.Cid) (*api.MsgLookup, error) `perm:"read"`
|
||||
@@ -206,6 +206,7 @@ type StorageMinerStruct struct {
|
||||
SectorsList func(context.Context) ([]abi.SectorNumber, error) `perm:"read"`
|
||||
SectorsRefs func(context.Context) (map[string][]api.SealedRef, error) `perm:"read"`
|
||||
SectorsUpdate func(context.Context, abi.SectorNumber, api.SectorState) error `perm:"write"`
|
||||
SectorRemove func(context.Context, abi.SectorNumber) error `perm:"admin"`
|
||||
|
||||
WorkerConnect func(context.Context, string) error `perm:"admin"` // TODO: worker perm
|
||||
WorkerStats func(context.Context) (map[uint64]storiface.WorkerStats, error) `perm:"admin"`
|
||||
@@ -223,9 +224,18 @@ type StorageMinerStruct struct {
|
||||
StorageLock func(ctx context.Context, sector abi.SectorID, read stores.SectorFileType, write stores.SectorFileType) error `perm:"admin"`
|
||||
StorageTryLock func(ctx context.Context, sector abi.SectorID, read stores.SectorFileType, write stores.SectorFileType) (bool, error) `perm:"admin"`
|
||||
|
||||
DealsImportData func(ctx context.Context, dealPropCid cid.Cid, file string) error `perm:"write"`
|
||||
DealsList func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"`
|
||||
DealsSetAcceptingStorageDeals func(context.Context, bool) error `perm:"admin"`
|
||||
DealsImportData func(ctx context.Context, dealPropCid cid.Cid, file string) error `perm:"write"`
|
||||
DealsList func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"`
|
||||
DealsConsiderOnlineStorageDeals func(context.Context) (bool, error) `perm:"read"`
|
||||
DealsSetConsiderOnlineStorageDeals func(context.Context, bool) error `perm:"admin"`
|
||||
DealsConsiderOnlineRetrievalDeals func(context.Context) (bool, error) `perm:"read"`
|
||||
DealsSetConsiderOnlineRetrievalDeals func(context.Context, bool) error `perm:"admin"`
|
||||
DealsConsiderOfflineStorageDeals func(context.Context) (bool, error) `perm:"read"`
|
||||
DealsSetConsiderOfflineStorageDeals func(context.Context, bool) error `perm:"admin"`
|
||||
DealsConsiderOfflineRetrievalDeals func(context.Context) (bool, error) `perm:"read"`
|
||||
DealsSetConsiderOfflineRetrievalDeals func(context.Context, bool) error `perm:"admin"`
|
||||
DealsPieceCidBlocklist func(context.Context) ([]cid.Cid, error) `perm:"read"`
|
||||
DealsSetPieceCidBlocklist func(context.Context, []cid.Cid) error `perm:"admin"`
|
||||
|
||||
StorageAddLocal func(ctx context.Context, path string) error `perm:"admin"`
|
||||
}
|
||||
@@ -241,12 +251,14 @@ type WorkerStruct struct {
|
||||
Paths func(context.Context) ([]stores.StoragePath, error) `perm:"admin"`
|
||||
Info func(context.Context) (storiface.WorkerInfo, error) `perm:"admin"`
|
||||
|
||||
SealPreCommit1 func(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, pieces []abi.PieceInfo) (storage.PreCommit1Out, error) `perm:"admin"`
|
||||
SealPreCommit2 func(context.Context, abi.SectorID, storage.PreCommit1Out) (cids storage.SectorCids, err error) `perm:"admin"`
|
||||
SealCommit1 func(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, seed abi.InteractiveSealRandomness, pieces []abi.PieceInfo, cids storage.SectorCids) (storage.Commit1Out, error) `perm:"admin"`
|
||||
SealCommit2 func(context.Context, abi.SectorID, storage.Commit1Out) (storage.Proof, error) `perm:"admin"`
|
||||
FinalizeSector func(context.Context, abi.SectorID) error `perm:"admin"`
|
||||
MoveStorage func(ctx context.Context, sector abi.SectorID) error `perm:"admin"`
|
||||
SealPreCommit1 func(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, pieces []abi.PieceInfo) (storage.PreCommit1Out, error) `perm:"admin"`
|
||||
SealPreCommit2 func(context.Context, abi.SectorID, storage.PreCommit1Out) (cids storage.SectorCids, err error) `perm:"admin"`
|
||||
SealCommit1 func(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, seed abi.InteractiveSealRandomness, pieces []abi.PieceInfo, cids storage.SectorCids) (storage.Commit1Out, error) `perm:"admin"`
|
||||
SealCommit2 func(context.Context, abi.SectorID, storage.Commit1Out) (storage.Proof, error) `perm:"admin"`
|
||||
FinalizeSector func(context.Context, abi.SectorID, []storage.Range) error `perm:"admin"`
|
||||
ReleaseUnsealed func(ctx context.Context, sector abi.SectorID, safeToFree []storage.Range) error `perm:"admin"`
|
||||
Remove func(ctx context.Context, sector abi.SectorID) error `perm:"admin"`
|
||||
MoveStorage func(ctx context.Context, sector abi.SectorID) error `perm:"admin"`
|
||||
|
||||
UnsealPiece func(context.Context, abi.SectorID, storiface.UnpaddedByteIndex, abi.UnpaddedPieceSize, abi.SealRandomness, cid.Cid) error `perm:"admin"`
|
||||
ReadPiece func(context.Context, io.Writer, abi.SectorID, storiface.UnpaddedByteIndex, abi.UnpaddedPieceSize) error `perm:"admin"`
|
||||
@@ -612,8 +624,8 @@ func (c *FullNodeStruct) StateGetActor(ctx context.Context, actor address.Addres
|
||||
return c.Internal.StateGetActor(ctx, actor, tsk)
|
||||
}
|
||||
|
||||
func (c *FullNodeStruct) StateReadState(ctx context.Context, act *types.Actor, tsk types.TipSetKey) (*api.ActorState, error) {
|
||||
return c.Internal.StateReadState(ctx, act, tsk)
|
||||
func (c *FullNodeStruct) StateReadState(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*api.ActorState, error) {
|
||||
return c.Internal.StateReadState(ctx, addr, tsk)
|
||||
}
|
||||
|
||||
func (c *FullNodeStruct) StatePledgeCollateral(ctx context.Context, tsk types.TipSetKey) (types.BigInt, error) {
|
||||
@@ -784,6 +796,10 @@ func (c *StorageMinerStruct) SectorsUpdate(ctx context.Context, id abi.SectorNum
|
||||
return c.Internal.SectorsUpdate(ctx, id, state)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) SectorRemove(ctx context.Context, number abi.SectorNumber) error {
|
||||
return c.Internal.SectorRemove(ctx, number)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) WorkerConnect(ctx context.Context, url string) error {
|
||||
return c.Internal.WorkerConnect(ctx, url)
|
||||
}
|
||||
@@ -868,8 +884,44 @@ func (c *StorageMinerStruct) DealsList(ctx context.Context) ([]storagemarket.Sto
|
||||
return c.Internal.DealsList(ctx)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsSetAcceptingStorageDeals(ctx context.Context, b bool) error {
|
||||
return c.Internal.DealsSetAcceptingStorageDeals(ctx, b)
|
||||
func (c *StorageMinerStruct) DealsConsiderOnlineStorageDeals(ctx context.Context) (bool, error) {
|
||||
return c.Internal.DealsConsiderOnlineStorageDeals(ctx)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsSetConsiderOnlineStorageDeals(ctx context.Context, b bool) error {
|
||||
return c.Internal.DealsSetConsiderOnlineStorageDeals(ctx, b)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsConsiderOnlineRetrievalDeals(ctx context.Context) (bool, error) {
|
||||
return c.Internal.DealsConsiderOnlineRetrievalDeals(ctx)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsSetConsiderOnlineRetrievalDeals(ctx context.Context, b bool) error {
|
||||
return c.Internal.DealsSetConsiderOnlineRetrievalDeals(ctx, b)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsPieceCidBlocklist(ctx context.Context) ([]cid.Cid, error) {
|
||||
return c.Internal.DealsPieceCidBlocklist(ctx)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsSetPieceCidBlocklist(ctx context.Context, cids []cid.Cid) error {
|
||||
return c.Internal.DealsSetPieceCidBlocklist(ctx, cids)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsConsiderOfflineStorageDeals(ctx context.Context) (bool, error) {
|
||||
return c.Internal.DealsConsiderOfflineStorageDeals(ctx)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsSetConsiderOfflineStorageDeals(ctx context.Context, b bool) error {
|
||||
return c.Internal.DealsSetConsiderOfflineStorageDeals(ctx, b)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsConsiderOfflineRetrievalDeals(ctx context.Context) (bool, error) {
|
||||
return c.Internal.DealsConsiderOfflineRetrievalDeals(ctx)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsSetConsiderOfflineRetrievalDeals(ctx context.Context, b bool) error {
|
||||
return c.Internal.DealsSetConsiderOfflineRetrievalDeals(ctx, b)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) StorageAddLocal(ctx context.Context, path string) error {
|
||||
@@ -910,8 +962,16 @@ func (w *WorkerStruct) SealCommit2(ctx context.Context, sector abi.SectorID, c1o
|
||||
return w.Internal.SealCommit2(ctx, sector, c1o)
|
||||
}
|
||||
|
||||
func (w *WorkerStruct) FinalizeSector(ctx context.Context, sector abi.SectorID) error {
|
||||
return w.Internal.FinalizeSector(ctx, sector)
|
||||
func (w *WorkerStruct) FinalizeSector(ctx context.Context, sector abi.SectorID, keepUnsealed []storage.Range) error {
|
||||
return w.Internal.FinalizeSector(ctx, sector, keepUnsealed)
|
||||
}
|
||||
|
||||
func (w *WorkerStruct) ReleaseUnsealed(ctx context.Context, sector abi.SectorID, safeToFree []storage.Range) error {
|
||||
return w.Internal.ReleaseUnsealed(ctx, sector, safeToFree)
|
||||
}
|
||||
|
||||
func (w *WorkerStruct) Remove(ctx context.Context, sector abi.SectorID) error {
|
||||
return w.Internal.Remove(ctx, sector)
|
||||
}
|
||||
|
||||
func (w *WorkerStruct) MoveStorage(ctx context.Context, sector abi.SectorID) error {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/filecoin-project/go-bitfield"
|
||||
"github.com/filecoin-project/go-jsonrpc/auth"
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/api/apistruct"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
@@ -334,6 +335,9 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
permStruct := reflect.TypeOf(apistruct.FullNodeStruct{}.Internal)
|
||||
commonPermStruct := reflect.TypeOf(apistruct.CommonStruct{}.Internal)
|
||||
|
||||
for _, g := range groupslice {
|
||||
g := g
|
||||
fmt.Printf("## %s\n", g.GroupName)
|
||||
@@ -347,6 +351,18 @@ func main() {
|
||||
fmt.Printf("### %s\n", m.Name)
|
||||
fmt.Printf("%s\n\n", m.Comment)
|
||||
|
||||
meth, ok := permStruct.FieldByName(m.Name)
|
||||
if !ok {
|
||||
meth, ok = commonPermStruct.FieldByName(m.Name)
|
||||
if !ok {
|
||||
panic("no perms for method: " + m.Name)
|
||||
}
|
||||
}
|
||||
|
||||
perms := meth.Tag.Get("perm")
|
||||
|
||||
fmt.Printf("Perms: %s\n\n", perms)
|
||||
|
||||
if strings.Count(m.InputExample, "\n") > 0 {
|
||||
fmt.Printf("Inputs:\n```json\n%s\n```\n\n", m.InputExample)
|
||||
} else {
|
||||
|
||||
+7
-6
@@ -8,6 +8,7 @@ import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -52,11 +53,11 @@ func TestDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, carExport
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
mine := true
|
||||
mine := int64(1)
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
for mine {
|
||||
for atomic.LoadInt64(&mine) == 1 {
|
||||
time.Sleep(blocktime)
|
||||
if err := sn[0].MineOne(ctx, func(bool) {}); err != nil {
|
||||
t.Error(err)
|
||||
@@ -66,7 +67,7 @@ func TestDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, carExport
|
||||
|
||||
makeDeal(t, ctx, 6, client, miner, carExport)
|
||||
|
||||
mine = false
|
||||
atomic.AddInt64(&mine, -1)
|
||||
fmt.Println("shutting down mining")
|
||||
<-done
|
||||
}
|
||||
@@ -89,12 +90,12 @@ func TestDoubleDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration) {
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
mine := true
|
||||
mine := int64(1)
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(done)
|
||||
for mine {
|
||||
for atomic.LoadInt64(&mine) == 1 {
|
||||
time.Sleep(blocktime)
|
||||
if err := sn[0].MineOne(ctx, func(bool) {}); err != nil {
|
||||
t.Error(err)
|
||||
@@ -105,7 +106,7 @@ func TestDoubleDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration) {
|
||||
makeDeal(t, ctx, 6, client, miner, false)
|
||||
makeDeal(t, ctx, 7, client, miner, false)
|
||||
|
||||
mine = false
|
||||
atomic.AddInt64(&mine, -1)
|
||||
fmt.Println("shutting down mining")
|
||||
<-done
|
||||
}
|
||||
|
||||
+3
-2
@@ -126,6 +126,7 @@ func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExpo
|
||||
minedTwo := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
doneMinedTwo := false
|
||||
defer close(done)
|
||||
|
||||
prevExpect := 0
|
||||
@@ -175,9 +176,9 @@ func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExpo
|
||||
time.Sleep(blocktime)
|
||||
}
|
||||
|
||||
if prevExpect == 2 && expect == 2 && minedTwo != nil {
|
||||
if prevExpect == 2 && expect == 2 && !doneMinedTwo {
|
||||
close(minedTwo)
|
||||
minedTwo = nil
|
||||
doneMinedTwo = true
|
||||
}
|
||||
|
||||
prevExpect = expect
|
||||
|
||||
@@ -13,6 +13,10 @@ import (
|
||||
)
|
||||
|
||||
func BuiltinBootstrap() ([]peer.AddrInfo, error) {
|
||||
if DisableBuiltinAssets {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var out []peer.AddrInfo
|
||||
|
||||
b := rice.MustFindBox("bootstrap")
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package build
|
||||
|
||||
// DisableBuiltinAssets disables the resolution of go.rice boxes that store
|
||||
// built-in assets, such as proof parameters, bootstrap peers, genesis blocks,
|
||||
// etc.
|
||||
//
|
||||
// When this value is set to true, it is expected that the user will
|
||||
// provide any such configurations through the Lotus API itself.
|
||||
//
|
||||
// This is useful when you're using Lotus as a library, such as to orchestrate
|
||||
// test scenarios, or for other purposes where you don't need to use the
|
||||
// defaults shipped with the binary.
|
||||
//
|
||||
// For this flag to be effective, it must be enabled _before_ instantiating Lotus.
|
||||
var DisableBuiltinAssets = false
|
||||
+2
-3
@@ -20,10 +20,9 @@ func init() {
|
||||
BuildType |= Build2k
|
||||
}
|
||||
|
||||
// Seconds
|
||||
const BlockDelay = 2
|
||||
const BlockDelaySecs = uint64(2)
|
||||
|
||||
const PropagationDelay = 3
|
||||
const PropagationDelaySecs = uint64(3)
|
||||
|
||||
// SlashablePowerDelay is the number of epochs after ElectionPeriodStart, after
|
||||
// which the miner is slashed
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/protocol"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
)
|
||||
|
||||
func DefaultSectorSize() abi.SectorSize {
|
||||
szs := make([]abi.SectorSize, 0, len(miner.SupportedProofTypes))
|
||||
for spt := range miner.SupportedProofTypes {
|
||||
ss, err := spt.SectorSize()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
szs = append(szs, ss)
|
||||
}
|
||||
|
||||
sort.Slice(szs, func(i, j int) bool {
|
||||
return szs[i] < szs[j]
|
||||
})
|
||||
|
||||
return szs[0]
|
||||
}
|
||||
|
||||
// Core network constants
|
||||
|
||||
func BlocksTopic(netName dtypes.NetworkName) string { return "/fil/blocks/" + string(netName) }
|
||||
func MessagesTopic(netName dtypes.NetworkName) string { return "/fil/msgs/" + string(netName) }
|
||||
func DhtProtocolName(netName dtypes.NetworkName) protocol.ID {
|
||||
return protocol.ID("/fil/kad/" + string(netName))
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
// +build !testground
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"sort"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/protocol"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
@@ -13,32 +12,6 @@ import (
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
)
|
||||
|
||||
func DefaultSectorSize() abi.SectorSize {
|
||||
szs := make([]abi.SectorSize, 0, len(miner.SupportedProofTypes))
|
||||
for spt := range miner.SupportedProofTypes {
|
||||
ss, err := spt.SectorSize()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
szs = append(szs, ss)
|
||||
}
|
||||
|
||||
sort.Slice(szs, func(i, j int) bool {
|
||||
return szs[i] < szs[j]
|
||||
})
|
||||
|
||||
return szs[0]
|
||||
}
|
||||
|
||||
// Core network constants
|
||||
|
||||
func BlocksTopic(netName dtypes.NetworkName) string { return "/fil/blocks/" + string(netName) }
|
||||
func MessagesTopic(netName dtypes.NetworkName) string { return "/fil/msgs/" + string(netName) }
|
||||
func DhtProtocolName(netName dtypes.NetworkName) protocol.ID {
|
||||
return protocol.ID("/fil/kad/" + string(netName))
|
||||
}
|
||||
|
||||
// /////
|
||||
// Storage
|
||||
|
||||
@@ -48,8 +21,7 @@ const UnixfsLinksPerLevel = 1024
|
||||
// /////
|
||||
// Consensus / Network
|
||||
|
||||
// Seconds
|
||||
const AllowableClockDrift = 1
|
||||
const AllowableClockDriftSecs = uint64(1)
|
||||
|
||||
// Epochs
|
||||
const ForkLengthThreshold = Finality
|
||||
@@ -59,12 +31,12 @@ var BlocksPerEpoch = uint64(builtin.ExpectedLeadersPerEpoch)
|
||||
|
||||
// Epochs
|
||||
const Finality = miner.ChainFinalityish
|
||||
const MessageConfidence = 5
|
||||
const MessageConfidence = uint64(5)
|
||||
|
||||
// constants for Weight calculation
|
||||
// The ratio of weight contributed by short-term vs long-term factors in a given round
|
||||
const WRatioNum = int64(1)
|
||||
const WRatioDen = 2
|
||||
const WRatioDen = uint64(2)
|
||||
|
||||
// /////
|
||||
// Proofs
|
||||
@@ -82,25 +54,25 @@ const MaxSealLookback = SealRandomnessLookbackLimit + 2000 // TODO: Get from spe
|
||||
// Mining
|
||||
|
||||
// Epochs
|
||||
const TicketRandomnessLookback = 1
|
||||
const TicketRandomnessLookback = abi.ChainEpoch(1)
|
||||
|
||||
const WinningPoStSectorSetLookback = 10
|
||||
const WinningPoStSectorSetLookback = abi.ChainEpoch(10)
|
||||
|
||||
// /////
|
||||
// Devnet settings
|
||||
|
||||
const TotalFilecoin = 2_000_000_000
|
||||
const MiningRewardTotal = 1_400_000_000
|
||||
const TotalFilecoin = uint64(2_000_000_000)
|
||||
const MiningRewardTotal = uint64(1_400_000_000)
|
||||
|
||||
const FilecoinPrecision = 1_000_000_000_000_000_000
|
||||
const FilecoinPrecision = uint64(1_000_000_000_000_000_000)
|
||||
|
||||
var InitialRewardBalance *big.Int
|
||||
|
||||
// TODO: Move other important consts here
|
||||
|
||||
func init() {
|
||||
InitialRewardBalance = big.NewInt(MiningRewardTotal)
|
||||
InitialRewardBalance = InitialRewardBalance.Mul(InitialRewardBalance, big.NewInt(FilecoinPrecision))
|
||||
InitialRewardBalance = big.NewInt(int64(MiningRewardTotal))
|
||||
InitialRewardBalance = InitialRewardBalance.Mul(InitialRewardBalance, big.NewInt(int64(FilecoinPrecision)))
|
||||
}
|
||||
|
||||
// Sync
|
||||
@@ -121,4 +93,11 @@ const VerifSigCacheSize = 32000
|
||||
const BlockMessageLimit = 512
|
||||
const BlockGasLimit = 100_000_000_000
|
||||
|
||||
var DrandChain = `{"public_key":"922a2e93828ff83345bae533f5172669a26c02dc76d6bf59c80892e12ab1455c229211886f35bb56af6d5bea981024df","period":25,"genesis_time":1590445175,"hash":"138a324aa6540f93d0dad002aa89454b1bec2b6e948682cde6bd4db40f4b7c9b"}`
|
||||
var DrandConfig = dtypes.DrandConfig{
|
||||
Servers: []string{
|
||||
"https://pl-eu.testnet.drand.sh",
|
||||
"https://pl-us.testnet.drand.sh",
|
||||
"https://pl-sin.testnet.drand.sh",
|
||||
},
|
||||
ChainInfoJSON: `{"public_key":"922a2e93828ff83345bae533f5172669a26c02dc76d6bf59c80892e12ab1455c229211886f35bb56af6d5bea981024df","period":25,"genesis_time":1590445175,"hash":"138a324aa6540f93d0dad002aa89454b1bec2b6e948682cde6bd4db40f4b7c9b"}`,
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// +build testground
|
||||
|
||||
// This file makes hardcoded parameters (const) configurable as vars.
|
||||
//
|
||||
// Its purpose is to unlock various degrees of flexibility and parametrization
|
||||
// when writing Testground plans for Lotus.
|
||||
//
|
||||
package build
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
)
|
||||
|
||||
var (
|
||||
UnixfsChunkSize = uint64(1 << 20)
|
||||
UnixfsLinksPerLevel = 1024
|
||||
|
||||
BlocksPerEpoch = uint64(builtin.ExpectedLeadersPerEpoch)
|
||||
BlockMessageLimit = 512
|
||||
BlockGasLimit = int64(100_000_000_000)
|
||||
BlockDelaySecs = uint64(builtin.EpochDurationSeconds)
|
||||
PropagationDelaySecs = uint64(6)
|
||||
|
||||
AllowableClockDriftSecs = uint64(1)
|
||||
|
||||
Finality = miner.ChainFinalityish
|
||||
ForkLengthThreshold = Finality
|
||||
|
||||
SlashablePowerDelay = 20
|
||||
InteractivePoRepConfidence = 6
|
||||
|
||||
MessageConfidence uint64 = 5
|
||||
|
||||
WRatioNum = int64(1)
|
||||
WRatioDen = uint64(2)
|
||||
|
||||
BadBlockCacheSize = 1 << 15
|
||||
BlsSignatureCacheSize = 40000
|
||||
VerifSigCacheSize = 32000
|
||||
|
||||
SealRandomnessLookback = Finality
|
||||
SealRandomnessLookbackLimit = SealRandomnessLookback + 2000
|
||||
MaxSealLookback = SealRandomnessLookbackLimit + 2000
|
||||
|
||||
TicketRandomnessLookback = abi.ChainEpoch(1)
|
||||
WinningPoStSectorSetLookback = abi.ChainEpoch(10)
|
||||
|
||||
TotalFilecoin uint64 = 2_000_000_000
|
||||
MiningRewardTotal uint64 = 1_400_000_000
|
||||
|
||||
FilecoinPrecision uint64 = 1_000_000_000_000_000_000
|
||||
|
||||
InitialRewardBalance = func() *big.Int {
|
||||
v := big.NewInt(int64(MiningRewardTotal))
|
||||
v = v.Mul(v, big.NewInt(int64(FilecoinPrecision)))
|
||||
return v
|
||||
}()
|
||||
|
||||
DrandConfig = dtypes.DrandConfig{
|
||||
Servers: []string{
|
||||
"https://pl-eu.testnet.drand.sh",
|
||||
"https://pl-us.testnet.drand.sh",
|
||||
"https://pl-sin.testnet.drand.sh",
|
||||
},
|
||||
ChainInfoJSON: `{"public_key":"922a2e93828ff83345bae533f5172669a26c02dc76d6bf59c80892e12ab1455c229211886f35bb56af6d5bea981024df","period":25,"genesis_time":1590445175,"hash":"138a324aa6540f93d0dad002aa89454b1bec2b6e948682cde6bd4db40f4b7c9b"}`,
|
||||
}
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
// +build !debug
|
||||
// +build !2k
|
||||
// +build !testground
|
||||
|
||||
package build
|
||||
|
||||
@@ -19,7 +20,6 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// Seconds
|
||||
const BlockDelay = builtin.EpochDurationSeconds
|
||||
const BlockDelaySecs = uint64(builtin.EpochDurationSeconds)
|
||||
|
||||
const PropagationDelay = 6
|
||||
const PropagationDelaySecs = uint64(6)
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@ func buildType() string {
|
||||
}
|
||||
|
||||
// BuildVersion is the local build version, set by build system
|
||||
const BuildVersion = "0.4.0"
|
||||
const BuildVersion = "0.4.1"
|
||||
|
||||
func UserVersion() string {
|
||||
return BuildVersion + buildType() + CurrentCommit
|
||||
@@ -53,7 +53,7 @@ func (ve Version) EqMajorMinor(v2 Version) bool {
|
||||
}
|
||||
|
||||
// APIVersion is a semver version of the rpc api exposed
|
||||
var APIVersion Version = newVer(0, 3, 0)
|
||||
var APIVersion Version = newVer(0, 5, 0)
|
||||
|
||||
//nolint:varcheck,deadcode
|
||||
const (
|
||||
|
||||
@@ -17,6 +17,10 @@ type Response struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// RandomBeacon represents a system that provides randomness to Lotus.
|
||||
// Other components interrogate the RandomBeacon to acquire randomness that's
|
||||
// valid for a specific chain epoch. Also to verify beacon entries that have
|
||||
// been posted on chain.
|
||||
type RandomBeacon interface {
|
||||
Entry(context.Context, uint64) <-chan Response
|
||||
VerifyEntry(types.BeaconEntry, types.BeaconEntry) error
|
||||
|
||||
+17
-21
@@ -19,31 +19,15 @@ import (
|
||||
logging "github.com/ipfs/go-log"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/beacon"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
)
|
||||
|
||||
var log = logging.Logger("drand")
|
||||
|
||||
var drandServers = []string{
|
||||
"https://pl-eu.testnet.drand.sh",
|
||||
"https://pl-us.testnet.drand.sh",
|
||||
"https://pl-sin.testnet.drand.sh",
|
||||
}
|
||||
|
||||
var drandChain *dchain.Info
|
||||
|
||||
func init() {
|
||||
|
||||
var err error
|
||||
drandChain, err = dchain.InfoFromJSON(bytes.NewReader([]byte(build.DrandChain)))
|
||||
if err != nil {
|
||||
panic("could not unmarshal chain info: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
type drandPeer struct {
|
||||
addr string
|
||||
tls bool
|
||||
@@ -57,6 +41,13 @@ func (dp *drandPeer) IsTLS() bool {
|
||||
return dp.tls
|
||||
}
|
||||
|
||||
// DrandBeacon connects Lotus with a drand network in order to provide
|
||||
// randomness to the system in a way that's aligned with Filecoin rounds/epochs.
|
||||
//
|
||||
// We connect to drand peers via their public HTTP endpoints. The peers are
|
||||
// enumerated in the drandServers variable.
|
||||
//
|
||||
// The root trust for the Drand chain is configured from build.DrandChain.
|
||||
type DrandBeacon struct {
|
||||
client dclient.Client
|
||||
|
||||
@@ -73,16 +64,21 @@ type DrandBeacon struct {
|
||||
localCache map[uint64]types.BeaconEntry
|
||||
}
|
||||
|
||||
func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub) (*DrandBeacon, error) {
|
||||
func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub, config dtypes.DrandConfig) (*DrandBeacon, error) {
|
||||
if genesisTs == 0 {
|
||||
panic("what are you doing this cant be zero")
|
||||
}
|
||||
|
||||
drandChain, err := dchain.InfoFromJSON(bytes.NewReader([]byte(config.ChainInfoJSON)))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("unable to unmarshal drand chain info: %w", err)
|
||||
}
|
||||
|
||||
dlogger := dlog.NewKitLoggerFrom(kzap.NewZapSugarLogger(
|
||||
log.SugaredLogger.Desugar(), zapcore.InfoLevel))
|
||||
|
||||
var clients []dclient.Client
|
||||
for _, url := range drandServers {
|
||||
for _, url := range config.Servers {
|
||||
hc, err := hclient.NewWithInfo(url, drandChain, nil)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("could not create http drand client: %w", err)
|
||||
|
||||
@@ -7,10 +7,13 @@ import (
|
||||
dchain "github.com/drand/drand/chain"
|
||||
hclient "github.com/drand/drand/client/http"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
)
|
||||
|
||||
func TestPrintGroupInfo(t *testing.T) {
|
||||
c, err := hclient.New(drandServers[0], nil, nil)
|
||||
server := build.DrandConfig.Servers[0]
|
||||
c, err := hclient.New(server, nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cg := c.(interface {
|
||||
FetchChainInfo(groupHash []byte) (*dchain.Info, error)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
cborutil "github.com/filecoin-project/go-cbor-util"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
|
||||
@@ -27,6 +28,24 @@ const BlockSyncProtocolID = "/fil/sync/blk/0.0.1"
|
||||
|
||||
const BlockSyncMaxRequestLength = 800
|
||||
|
||||
// BlockSyncService is the component that services BlockSync requests from
|
||||
// peers.
|
||||
//
|
||||
// BlockSync is the basic chain synchronization protocol of Filecoin. BlockSync
|
||||
// is an RPC-oriented protocol, with a single operation to request blocks.
|
||||
//
|
||||
// A request contains a start anchor block (referred to with a CID), and a
|
||||
// amount of blocks requested beyond the anchor (including the anchor itself).
|
||||
//
|
||||
// A client can also pass options, encoded as a 64-bit bitfield. Lotus supports
|
||||
// two options at the moment:
|
||||
//
|
||||
// - include block contents
|
||||
// - include block messages
|
||||
//
|
||||
// The response will include a status code, an optional message, and the
|
||||
// response payload in case of success. The payload is a slice of serialized
|
||||
// tipsets.
|
||||
type BlockSyncService struct {
|
||||
cs *store.ChainStore
|
||||
}
|
||||
|
||||
@@ -64,6 +64,11 @@ func (bs *BlockSync) processStatus(req *BlockSyncRequest, res *BlockSyncResponse
|
||||
}
|
||||
}
|
||||
|
||||
// GetBlocks fetches count blocks from the network, from the provided tipset
|
||||
// *backwards*, returning as many tipsets as count.
|
||||
//
|
||||
// {hint/usage}: This is used by the Syncer during normal chain syncing and when
|
||||
// resolving forks.
|
||||
func (bs *BlockSync) GetBlocks(ctx context.Context, tsk types.TipSetKey, count int) ([]*types.TipSet, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "bsync.GetBlocks")
|
||||
defer span.End()
|
||||
@@ -80,7 +85,9 @@ func (bs *BlockSync) GetBlocks(ctx context.Context, tsk types.TipSetKey, count i
|
||||
Options: BSOptBlocks,
|
||||
}
|
||||
|
||||
// this peerset is sorted by latency and failure counting.
|
||||
peers := bs.getPeers()
|
||||
|
||||
// randomize the first few peers so we don't always pick the same peer
|
||||
shufflePrefix(peers)
|
||||
|
||||
@@ -356,6 +363,7 @@ func (bs *BlockSync) RemovePeer(p peer.ID) {
|
||||
bs.syncPeers.removePeer(p)
|
||||
}
|
||||
|
||||
// getPeers returns a preference-sorted set of peers to query.
|
||||
func (bs *BlockSync) getPeers() []peer.ID {
|
||||
return bs.syncPeers.prefSortedPeers()
|
||||
}
|
||||
|
||||
+4
-15
@@ -51,7 +51,7 @@ type Events struct {
|
||||
readyOnce sync.Once
|
||||
|
||||
heightEvents
|
||||
calledEvents
|
||||
*hcEvents
|
||||
}
|
||||
|
||||
func NewEvents(ctx context.Context, api eventAPI) *Events {
|
||||
@@ -74,18 +74,7 @@ func NewEvents(ctx context.Context, api eventAPI) *Events {
|
||||
htHeights: map[abi.ChainEpoch][]uint64{},
|
||||
},
|
||||
|
||||
calledEvents: calledEvents{
|
||||
cs: api,
|
||||
tsc: tsc,
|
||||
ctx: ctx,
|
||||
gcConfidence: uint64(gcConfidence),
|
||||
|
||||
confQueue: map[triggerH]map[msgH][]*queuedEvent{},
|
||||
revertQueue: map[msgH][]triggerH{},
|
||||
triggers: map[triggerID]*callHandler{},
|
||||
matchers: map[triggerID][]MatchFunc{},
|
||||
timeouts: map[abi.ChainEpoch]map[triggerID]int{},
|
||||
},
|
||||
hcEvents: newHCEvents(ctx, api, tsc, uint64(gcConfidence)),
|
||||
}
|
||||
|
||||
e.ready.Add(1)
|
||||
@@ -143,7 +132,7 @@ func (e *Events) listenHeadChangesOnce(ctx context.Context) error {
|
||||
}
|
||||
|
||||
e.readyOnce.Do(func() {
|
||||
e.at = cur[0].Val.Height()
|
||||
e.lastTs = cur[0].Val
|
||||
|
||||
e.ready.Done()
|
||||
})
|
||||
@@ -186,5 +175,5 @@ func (e *Events) headChange(rev, app []*types.TipSet) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.headChangeCalled(rev, app)
|
||||
return e.processHeadChangeEvent(rev, app)
|
||||
}
|
||||
|
||||
+363
-109
@@ -13,6 +13,7 @@ import (
|
||||
)
|
||||
|
||||
const NoTimeout = math.MaxInt64
|
||||
const NoHeight = abi.ChainEpoch(-1)
|
||||
|
||||
type triggerID = uint64
|
||||
|
||||
@@ -23,54 +24,60 @@ type msgH = abi.ChainEpoch
|
||||
// message (msgH+confidence)
|
||||
type triggerH = abi.ChainEpoch
|
||||
|
||||
// CalledHandler arguments:
|
||||
// `ts` is the tipset, in which the `msg` is included.
|
||||
type eventData interface{}
|
||||
|
||||
// EventHandler arguments:
|
||||
// `prevTs` is the previous tipset, eg the "from" tipset for a state change.
|
||||
// `ts` is the event tipset, eg the tipset in which the `msg` is included.
|
||||
// `curH`-`ts.Height` = `confidence`
|
||||
type CalledHandler func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error)
|
||||
type EventHandler func(data eventData, prevTs, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error)
|
||||
|
||||
// CheckFunc is used for atomicity guarantees. If the condition the callbacks
|
||||
// wait for has already happened in tipset `ts`
|
||||
//
|
||||
// If `done` is true, timeout won't be triggered
|
||||
// If `more` is false, no messages will be sent to CalledHandler (RevertHandler
|
||||
// If `more` is false, no messages will be sent to EventHandler (RevertHandler
|
||||
// may still be called)
|
||||
type CheckFunc func(ts *types.TipSet) (done bool, more bool, err error)
|
||||
|
||||
type MatchFunc func(msg *types.Message) (bool, error)
|
||||
|
||||
type callHandler struct {
|
||||
// Keep track of information for an event handler
|
||||
type handlerInfo struct {
|
||||
confidence int
|
||||
timeout abi.ChainEpoch
|
||||
|
||||
disabled bool // TODO: GC after gcConfidence reached
|
||||
|
||||
handle CalledHandler
|
||||
handle EventHandler
|
||||
revert RevertHandler
|
||||
}
|
||||
|
||||
// When a change occurs, a queuedEvent is created and put into a queue
|
||||
// until the required confidence is reached
|
||||
type queuedEvent struct {
|
||||
trigger triggerID
|
||||
|
||||
h abi.ChainEpoch
|
||||
msg *types.Message
|
||||
prevH abi.ChainEpoch
|
||||
h abi.ChainEpoch
|
||||
data eventData
|
||||
|
||||
called bool
|
||||
}
|
||||
|
||||
type calledEvents struct {
|
||||
// Manages chain head change events, which may be forward (new tipset added to
|
||||
// chain) or backward (chain branch discarded in favour of heavier branch)
|
||||
type hcEvents struct {
|
||||
cs eventAPI
|
||||
tsc *tipSetCache
|
||||
ctx context.Context
|
||||
gcConfidence uint64
|
||||
|
||||
at abi.ChainEpoch
|
||||
lastTs *types.TipSet
|
||||
|
||||
lk sync.Mutex
|
||||
|
||||
ctr triggerID
|
||||
|
||||
triggers map[triggerID]*callHandler
|
||||
matchers map[triggerID][]MatchFunc
|
||||
triggers map[triggerID]*handlerInfo
|
||||
|
||||
// maps block heights to events
|
||||
// [triggerH][msgH][event]
|
||||
@@ -81,27 +88,79 @@ type calledEvents struct {
|
||||
|
||||
// [timeoutH+confidence][triggerID]{calls}
|
||||
timeouts map[abi.ChainEpoch]map[triggerID]int
|
||||
|
||||
messageEvents
|
||||
watcherEvents
|
||||
}
|
||||
|
||||
func (e *calledEvents) headChangeCalled(rev, app []*types.TipSet) error {
|
||||
func newHCEvents(ctx context.Context, cs eventAPI, tsc *tipSetCache, gcConfidence uint64) *hcEvents {
|
||||
e := hcEvents{
|
||||
ctx: ctx,
|
||||
cs: cs,
|
||||
tsc: tsc,
|
||||
gcConfidence: gcConfidence,
|
||||
|
||||
confQueue: map[triggerH]map[msgH][]*queuedEvent{},
|
||||
revertQueue: map[msgH][]triggerH{},
|
||||
triggers: map[triggerID]*handlerInfo{},
|
||||
timeouts: map[abi.ChainEpoch]map[triggerID]int{},
|
||||
}
|
||||
|
||||
e.messageEvents = newMessageEvents(ctx, &e, cs)
|
||||
e.watcherEvents = newWatcherEvents(ctx, &e, cs)
|
||||
|
||||
return &e
|
||||
}
|
||||
|
||||
// Called when there is a change to the head with tipsets to be
|
||||
// reverted / applied
|
||||
func (e *hcEvents) processHeadChangeEvent(rev, app []*types.TipSet) error {
|
||||
e.lk.Lock()
|
||||
defer e.lk.Unlock()
|
||||
|
||||
for _, ts := range rev {
|
||||
e.handleReverts(ts)
|
||||
e.at = ts.Height()
|
||||
e.lastTs = ts
|
||||
}
|
||||
|
||||
for _, ts := range app {
|
||||
// called triggers
|
||||
e.checkNewCalls(ts)
|
||||
for ; e.at <= ts.Height(); e.at++ {
|
||||
e.applyWithConfidence(ts, e.at)
|
||||
// Check if the head change caused any state changes that we were
|
||||
// waiting for
|
||||
stateChanges := e.watcherEvents.checkStateChanges(e.lastTs, ts)
|
||||
|
||||
// Queue up calls until there have been enough blocks to reach
|
||||
// confidence on the state changes
|
||||
for tid, data := range stateChanges {
|
||||
e.queueForConfidence(tid, data, e.lastTs, ts)
|
||||
}
|
||||
|
||||
// Check if the head change included any new message calls
|
||||
newCalls, err := e.messageEvents.checkNewCalls(ts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Queue up calls until there have been enough blocks to reach
|
||||
// confidence on the message calls
|
||||
for tid, data := range newCalls {
|
||||
e.queueForConfidence(tid, data, nil, ts)
|
||||
}
|
||||
|
||||
for at := e.lastTs.Height(); at <= ts.Height(); at++ {
|
||||
// Apply any queued events and timeouts that were targeted at the
|
||||
// current chain height
|
||||
e.applyWithConfidence(ts, at)
|
||||
e.applyTimeouts(ts)
|
||||
}
|
||||
|
||||
// Update the latest known tipset
|
||||
e.lastTs = ts
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *calledEvents) handleReverts(ts *types.TipSet) {
|
||||
func (e *hcEvents) handleReverts(ts *types.TipSet) {
|
||||
reverts, ok := e.revertQueue[ts.Height()]
|
||||
if !ok {
|
||||
return // nothing to do
|
||||
@@ -117,7 +176,7 @@ func (e *calledEvents) handleReverts(ts *types.TipSet) {
|
||||
trigger := e.triggers[event.trigger]
|
||||
|
||||
if err := trigger.revert(e.ctx, ts); err != nil {
|
||||
log.Errorf("reverting chain trigger (call %s.%d() @H %d, called @ %d) failed: %s", event.msg.To, event.msg.Method, ts.Height(), triggerH, err)
|
||||
log.Errorf("reverting chain trigger (@H %d, triggered @ %d) failed: %s", ts.Height(), triggerH, err)
|
||||
}
|
||||
}
|
||||
delete(e.confQueue[triggerH], ts.Height())
|
||||
@@ -125,42 +184,15 @@ func (e *calledEvents) handleReverts(ts *types.TipSet) {
|
||||
delete(e.revertQueue, ts.Height())
|
||||
}
|
||||
|
||||
func (e *calledEvents) checkNewCalls(ts *types.TipSet) {
|
||||
pts, err := e.cs.ChainGetTipSet(e.ctx, ts.Parents()) // we actually care about messages in the parent tipset here
|
||||
if err != nil {
|
||||
log.Errorf("getting parent tipset in checkNewCalls: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
e.messagesForTs(pts, func(msg *types.Message) {
|
||||
// TODO: provide receipts
|
||||
|
||||
for tid, matchFns := range e.matchers {
|
||||
var matched bool
|
||||
for _, matchFn := range matchFns {
|
||||
ok, err := matchFn(msg)
|
||||
if err != nil {
|
||||
log.Errorf("event matcher failed: %s", err)
|
||||
continue
|
||||
}
|
||||
matched = ok
|
||||
|
||||
if matched {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
e.queueForConfidence(tid, msg, ts)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *calledEvents) queueForConfidence(trigID uint64, msg *types.Message, ts *types.TipSet) {
|
||||
// Queue up events until the chain has reached a height that reflects the
|
||||
// desired confidence
|
||||
func (e *hcEvents) queueForConfidence(trigID uint64, data eventData, prevTs, ts *types.TipSet) {
|
||||
trigger := e.triggers[trigID]
|
||||
|
||||
prevH := NoHeight
|
||||
if prevTs != nil {
|
||||
prevH = prevTs.Height()
|
||||
}
|
||||
appliedH := ts.Height()
|
||||
|
||||
triggerH := appliedH + abi.ChainEpoch(trigger.confidence)
|
||||
@@ -173,17 +205,19 @@ func (e *calledEvents) queueForConfidence(trigID uint64, msg *types.Message, ts
|
||||
|
||||
byOrigH[appliedH] = append(byOrigH[appliedH], &queuedEvent{
|
||||
trigger: trigID,
|
||||
prevH: prevH,
|
||||
h: appliedH,
|
||||
msg: msg,
|
||||
data: data,
|
||||
})
|
||||
|
||||
e.revertQueue[appliedH] = append(e.revertQueue[appliedH], triggerH)
|
||||
}
|
||||
|
||||
func (e *calledEvents) applyWithConfidence(ts *types.TipSet, height abi.ChainEpoch) {
|
||||
// Apply any events that were waiting for this chain height for confidence
|
||||
func (e *hcEvents) applyWithConfidence(ts *types.TipSet, height abi.ChainEpoch) {
|
||||
byOrigH, ok := e.confQueue[height]
|
||||
if !ok {
|
||||
return // no triggers at thin height
|
||||
return // no triggers at this height
|
||||
}
|
||||
|
||||
for origH, events := range byOrigH {
|
||||
@@ -202,15 +236,20 @@ func (e *calledEvents) applyWithConfidence(ts *types.TipSet, height abi.ChainEpo
|
||||
continue
|
||||
}
|
||||
|
||||
rec, err := e.cs.StateGetReceipt(e.ctx, event.msg.Cid(), ts.Key())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
// Previous tipset - this is relevant for example in a state change
|
||||
// from one tipset to another
|
||||
var prevTs *types.TipSet
|
||||
if event.prevH != NoHeight {
|
||||
prevTs, err = e.tsc.get(event.prevH)
|
||||
if err != nil {
|
||||
log.Errorf("events: applyWithConfidence didn't find tipset for previous event; wanted %d; current %d", event.prevH, height)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
more, err := trigger.handle(event.msg, rec, triggerTs, height)
|
||||
more, err := trigger.handle(event.data, prevTs, triggerTs, height)
|
||||
if err != nil {
|
||||
log.Errorf("chain trigger (call %s.%d() @H %d, called @ %d) failed: %s", event.msg.To, event.msg.Method, origH, height, err)
|
||||
log.Errorf("chain trigger (@H %d, triggered @ %d) failed: %s", origH, height, err)
|
||||
continue // don't revert failed calls
|
||||
}
|
||||
|
||||
@@ -226,7 +265,8 @@ func (e *calledEvents) applyWithConfidence(ts *types.TipSet, height abi.ChainEpo
|
||||
}
|
||||
}
|
||||
|
||||
func (e *calledEvents) applyTimeouts(ts *types.TipSet) {
|
||||
// Apply any timeouts that expire at this height
|
||||
func (e *hcEvents) applyTimeouts(ts *types.TipSet) {
|
||||
triggers, ok := e.timeouts[ts.Height()]
|
||||
if !ok {
|
||||
return // nothing to do
|
||||
@@ -256,12 +296,225 @@ func (e *calledEvents) applyTimeouts(ts *types.TipSet) {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *calledEvents) messagesForTs(ts *types.TipSet, consume func(*types.Message)) {
|
||||
// Listen for an event
|
||||
// - CheckFunc: immediately checks if the event already occurred
|
||||
// - EventHandler: called when the event has occurred, after confidence tipsets
|
||||
// - RevertHandler: called if the chain head changes causing the event to revert
|
||||
// - confidence: wait this many tipsets before calling EventHandler
|
||||
// - timeout: at this chain height, timeout on waiting for this event
|
||||
func (e *hcEvents) onHeadChanged(check CheckFunc, hnd EventHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch) (triggerID, error) {
|
||||
e.lk.Lock()
|
||||
defer e.lk.Unlock()
|
||||
|
||||
// Check if the event has already occurred
|
||||
ts := e.tsc.best()
|
||||
done, more, err := check(ts)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("called check error (h: %d): %w", ts.Height(), err)
|
||||
}
|
||||
if done {
|
||||
timeout = NoTimeout
|
||||
}
|
||||
|
||||
// Create a trigger for the event
|
||||
id := e.ctr
|
||||
e.ctr++
|
||||
|
||||
e.triggers[id] = &handlerInfo{
|
||||
confidence: confidence,
|
||||
timeout: timeout + abi.ChainEpoch(confidence),
|
||||
|
||||
disabled: !more,
|
||||
|
||||
handle: hnd,
|
||||
revert: rev,
|
||||
}
|
||||
|
||||
// If there's a timeout, set up a timeout check at that height
|
||||
if timeout != NoTimeout {
|
||||
if e.timeouts[timeout+abi.ChainEpoch(confidence)] == nil {
|
||||
e.timeouts[timeout+abi.ChainEpoch(confidence)] = map[uint64]int{}
|
||||
}
|
||||
e.timeouts[timeout+abi.ChainEpoch(confidence)][id] = 0
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// headChangeAPI is used to allow the composed event APIs to call back to hcEvents
|
||||
// to listen for changes
|
||||
type headChangeAPI interface {
|
||||
onHeadChanged(check CheckFunc, hnd EventHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch) (triggerID, error)
|
||||
}
|
||||
|
||||
// watcherEvents watches for a state change
|
||||
type watcherEvents struct {
|
||||
ctx context.Context
|
||||
cs eventAPI
|
||||
hcAPI headChangeAPI
|
||||
|
||||
lk sync.RWMutex
|
||||
matchers map[triggerID]StateMatchFunc
|
||||
}
|
||||
|
||||
func newWatcherEvents(ctx context.Context, hcAPI headChangeAPI, cs eventAPI) watcherEvents {
|
||||
return watcherEvents{
|
||||
ctx: ctx,
|
||||
cs: cs,
|
||||
hcAPI: hcAPI,
|
||||
matchers: make(map[triggerID]StateMatchFunc),
|
||||
}
|
||||
}
|
||||
|
||||
// Run each of the matchers against the previous and current state to see if
|
||||
// there's a change
|
||||
func (we *watcherEvents) checkStateChanges(oldState, newState *types.TipSet) map[triggerID]eventData {
|
||||
we.lk.RLock()
|
||||
defer we.lk.RUnlock()
|
||||
|
||||
res := make(map[triggerID]eventData)
|
||||
for tid, matchFn := range we.matchers {
|
||||
ok, data, err := matchFn(oldState, newState)
|
||||
if err != nil {
|
||||
log.Errorf("event diff fn failed: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if ok {
|
||||
res[tid] = data
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// StateChange represents a change in state
|
||||
type StateChange interface{}
|
||||
|
||||
// StateChangeHandler arguments:
|
||||
// `oldTs` is the state "from" tipset
|
||||
// `newTs` is the state "to" tipset
|
||||
// `states` is the change in state
|
||||
// `curH`-`ts.Height` = `confidence`
|
||||
type StateChangeHandler func(oldTs, newTs *types.TipSet, states StateChange, curH abi.ChainEpoch) (more bool, err error)
|
||||
|
||||
type StateMatchFunc func(oldTs, newTs *types.TipSet) (bool, StateChange, error)
|
||||
|
||||
// StateChanged registers a callback which is triggered when a specified state
|
||||
// change occurs or a timeout is reached.
|
||||
//
|
||||
// * `CheckFunc` callback is invoked immediately with a recent tipset, it
|
||||
// returns two booleans - `done`, and `more`.
|
||||
//
|
||||
// * `done` should be true when some on-chain state change we are waiting
|
||||
// for has happened. When `done` is set to true, timeout trigger is disabled.
|
||||
//
|
||||
// * `more` should be false when we don't want to receive new notifications
|
||||
// through StateChangeHandler. Note that notifications may still be delivered to
|
||||
// RevertHandler
|
||||
//
|
||||
// * `StateChangeHandler` is called when the specified state change was observed
|
||||
// on-chain, and a confidence threshold was reached, or the specified `timeout`
|
||||
// height was reached with no state change observed. When this callback is
|
||||
// invoked on a timeout, `oldState` and `newState` are set to nil.
|
||||
// This callback returns a boolean specifying whether further notifications
|
||||
// should be sent, like `more` return param from `CheckFunc` above.
|
||||
//
|
||||
// * `RevertHandler` is called after apply handler, when we drop the tipset
|
||||
// containing the message. The tipset passed as the argument is the tipset
|
||||
// that is being dropped. Note that the event dropped may be re-applied
|
||||
// in a different tipset in small amount of time.
|
||||
//
|
||||
// * `StateMatchFunc` is called against each tipset state. If there is a match,
|
||||
// the state change is queued up until the confidence interval has elapsed (and
|
||||
// `StateChangeHandler` is called)
|
||||
func (we *watcherEvents) StateChanged(check CheckFunc, scHnd StateChangeHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, mf StateMatchFunc) error {
|
||||
hnd := func(data eventData, prevTs, ts *types.TipSet, height abi.ChainEpoch) (bool, error) {
|
||||
states, ok := data.(StateChange)
|
||||
if data != nil && !ok {
|
||||
panic("expected StateChange")
|
||||
}
|
||||
|
||||
return scHnd(prevTs, ts, states, height)
|
||||
}
|
||||
|
||||
id, err := we.hcAPI.onHeadChanged(check, hnd, rev, confidence, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
we.lk.Lock()
|
||||
defer we.lk.Unlock()
|
||||
we.matchers[id] = mf
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// messageEvents watches for message calls to actors
|
||||
type messageEvents struct {
|
||||
ctx context.Context
|
||||
cs eventAPI
|
||||
hcAPI headChangeAPI
|
||||
|
||||
lk sync.RWMutex
|
||||
matchers map[triggerID][]MsgMatchFunc
|
||||
}
|
||||
|
||||
func newMessageEvents(ctx context.Context, hcAPI headChangeAPI, cs eventAPI) messageEvents {
|
||||
return messageEvents{
|
||||
ctx: ctx,
|
||||
cs: cs,
|
||||
hcAPI: hcAPI,
|
||||
matchers: map[triggerID][]MsgMatchFunc{},
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there are any new actor calls
|
||||
func (me *messageEvents) checkNewCalls(ts *types.TipSet) (map[triggerID]eventData, error) {
|
||||
pts, err := me.cs.ChainGetTipSet(me.ctx, ts.Parents()) // we actually care about messages in the parent tipset here
|
||||
if err != nil {
|
||||
log.Errorf("getting parent tipset in checkNewCalls: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
me.lk.RLock()
|
||||
defer me.lk.RUnlock()
|
||||
|
||||
res := make(map[triggerID]eventData)
|
||||
me.messagesForTs(pts, func(msg *types.Message) {
|
||||
// TODO: provide receipts
|
||||
|
||||
for tid, matchFns := range me.matchers {
|
||||
var matched bool
|
||||
for _, matchFn := range matchFns {
|
||||
ok, err := matchFn(msg)
|
||||
if err != nil {
|
||||
log.Errorf("event matcher failed: %s", err)
|
||||
continue
|
||||
}
|
||||
matched = ok
|
||||
|
||||
if matched {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
res[tid] = msg
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Get the messages in a tipset
|
||||
func (me *messageEvents) messagesForTs(ts *types.TipSet, consume func(*types.Message)) {
|
||||
seen := map[cid.Cid]struct{}{}
|
||||
|
||||
for _, tsb := range ts.Blocks() {
|
||||
|
||||
msgs, err := e.cs.ChainGetBlockMessages(context.TODO(), tsb.Cid())
|
||||
msgs, err := me.cs.ChainGetBlockMessages(context.TODO(), tsb.Cid())
|
||||
if err != nil {
|
||||
log.Errorf("messagesForTs MessagesForBlock failed (ts.H=%d, Bcid:%s, B.Mcid:%s): %s", ts.Height(), tsb.Cid(), tsb.Messages, err)
|
||||
// this is quite bad, but probably better than missing all the other updates
|
||||
@@ -290,20 +543,27 @@ func (e *calledEvents) messagesForTs(ts *types.TipSet, consume func(*types.Messa
|
||||
}
|
||||
}
|
||||
|
||||
// Called registers a callbacks which are triggered when a specified method is
|
||||
// MsgHandler arguments:
|
||||
// `ts` is the tipset, in which the `msg` is included.
|
||||
// `curH`-`ts.Height` = `confidence`
|
||||
type MsgHandler func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error)
|
||||
|
||||
type MsgMatchFunc func(msg *types.Message) (bool, error)
|
||||
|
||||
// Called registers a callback which is triggered when a specified method is
|
||||
// called on an actor, or a timeout is reached.
|
||||
//
|
||||
// * `CheckFunc` callback is invoked immediately with a recent tipset, it
|
||||
// returns two booleans - `done`, and `more`.
|
||||
//
|
||||
// * `done` should be true when some on-chain action we are waiting for has
|
||||
// happened. When `done` is set to true, timeout trigger is disabled.
|
||||
// * `done` should be true when some on-chain action we are waiting for has
|
||||
// happened. When `done` is set to true, timeout trigger is disabled.
|
||||
//
|
||||
// * `more` should be false when we don't want to receive new notifications
|
||||
// through CalledHandler. Note that notifications may still be delivered to
|
||||
// RevertHandler
|
||||
// * `more` should be false when we don't want to receive new notifications
|
||||
// through MsgHandler. Note that notifications may still be delivered to
|
||||
// RevertHandler
|
||||
//
|
||||
// * `CalledHandler` is called when the specified event was observed on-chain,
|
||||
// * `MsgHandler` is called when the specified event was observed on-chain,
|
||||
// and a confidence threshold was reached, or the specified `timeout` height
|
||||
// was reached with no events observed. When this callback is invoked on a
|
||||
// timeout, `msg` is set to nil. This callback returns a boolean specifying
|
||||
@@ -314,44 +574,38 @@ func (e *calledEvents) messagesForTs(ts *types.TipSet, consume func(*types.Messa
|
||||
// containing the message. The tipset passed as the argument is the tipset
|
||||
// that is being dropped. Note that the message dropped may be re-applied
|
||||
// in a different tipset in small amount of time.
|
||||
func (e *calledEvents) Called(check CheckFunc, hnd CalledHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, mf MatchFunc) error {
|
||||
e.lk.Lock()
|
||||
defer e.lk.Unlock()
|
||||
|
||||
ts := e.tsc.best()
|
||||
done, more, err := check(ts)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("called check error (h: %d): %w", ts.Height(), err)
|
||||
}
|
||||
if done {
|
||||
timeout = NoTimeout
|
||||
}
|
||||
|
||||
id := e.ctr
|
||||
e.ctr++
|
||||
|
||||
e.triggers[id] = &callHandler{
|
||||
confidence: confidence,
|
||||
timeout: timeout + abi.ChainEpoch(confidence),
|
||||
|
||||
disabled: !more,
|
||||
|
||||
handle: hnd,
|
||||
revert: rev,
|
||||
}
|
||||
|
||||
e.matchers[id] = append(e.matchers[id], mf)
|
||||
|
||||
if timeout != NoTimeout {
|
||||
if e.timeouts[timeout+abi.ChainEpoch(confidence)] == nil {
|
||||
e.timeouts[timeout+abi.ChainEpoch(confidence)] = map[uint64]int{}
|
||||
//
|
||||
// * `MsgMatchFunc` is called against each message. If there is a match, the
|
||||
// message is queued up until the confidence interval has elapsed (and
|
||||
// `MsgHandler` is called)
|
||||
func (me *messageEvents) Called(check CheckFunc, msgHnd MsgHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, mf MsgMatchFunc) error {
|
||||
hnd := func(data eventData, prevTs, ts *types.TipSet, height abi.ChainEpoch) (bool, error) {
|
||||
msg, ok := data.(*types.Message)
|
||||
if data != nil && !ok {
|
||||
panic("expected msg")
|
||||
}
|
||||
e.timeouts[timeout+abi.ChainEpoch(confidence)][id] = 0
|
||||
|
||||
rec, err := me.cs.StateGetReceipt(me.ctx, msg.Cid(), ts.Key())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return msgHnd(msg, rec, ts, height)
|
||||
}
|
||||
|
||||
id, err := me.hcAPI.onHeadChanged(check, hnd, rev, confidence, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
me.lk.Lock()
|
||||
defer me.lk.Unlock()
|
||||
me.matchers[id] = append(me.matchers[id], mf)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *calledEvents) CalledMsg(ctx context.Context, hnd CalledHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, msg types.ChainMsg) error {
|
||||
return e.Called(e.CheckMsg(ctx, msg, hnd), hnd, rev, confidence, timeout, e.MatchMsg(msg.VMMessage()))
|
||||
// Convenience function for checking and matching messages
|
||||
func (me *messageEvents) CalledMsg(ctx context.Context, hnd MsgHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, msg types.ChainMsg) error {
|
||||
return me.Called(me.CheckMsg(ctx, msg, hnd), hnd, rev, confidence, timeout, me.MatchMsg(msg.VMMessage()))
|
||||
}
|
||||
|
||||
@@ -26,12 +26,15 @@ type heightEvents struct {
|
||||
}
|
||||
|
||||
func (e *heightEvents) headChangeAt(rev, app []*types.TipSet) error {
|
||||
|
||||
ctx, span := trace.StartSpan(e.ctx, "events.HeightHeadChange")
|
||||
defer span.End()
|
||||
span.AddAttributes(trace.Int64Attribute("endHeight", int64(app[0].Height())))
|
||||
span.AddAttributes(trace.Int64Attribute("reverts", int64(len(rev))))
|
||||
span.AddAttributes(trace.Int64Attribute("applies", int64(len(app))))
|
||||
|
||||
e.lk.Lock()
|
||||
defer e.lk.Unlock()
|
||||
for _, ts := range rev {
|
||||
// TODO: log error if h below gcconfidence
|
||||
// revert height-based triggers
|
||||
@@ -40,7 +43,10 @@ func (e *heightEvents) headChangeAt(rev, app []*types.TipSet) error {
|
||||
for _, tid := range e.htHeights[h] {
|
||||
ctx, span := trace.StartSpan(ctx, "events.HeightRevert")
|
||||
|
||||
err := e.heightTriggers[tid].revert(ctx, ts)
|
||||
rev := e.heightTriggers[tid].revert
|
||||
e.lk.Unlock()
|
||||
err := rev(ctx, ts)
|
||||
e.lk.Lock()
|
||||
e.heightTriggers[tid].called = false
|
||||
|
||||
span.End()
|
||||
@@ -98,8 +104,10 @@ func (e *heightEvents) headChangeAt(rev, app []*types.TipSet) error {
|
||||
|
||||
ctx, span := trace.StartSpan(ctx, "events.HeightApply")
|
||||
span.AddAttributes(trace.BoolAttribute("immediate", false))
|
||||
|
||||
err = hnd.handle(ctx, incTs, h)
|
||||
handle := hnd.handle
|
||||
e.lk.Unlock()
|
||||
err = handle(ctx, incTs, h)
|
||||
e.lk.Lock()
|
||||
span.End()
|
||||
|
||||
if err != nil {
|
||||
|
||||
+247
-2
@@ -1004,8 +1004,6 @@ func TestRemoveTriggersOnMessage(t *testing.T) {
|
||||
return false, true, nil
|
||||
}, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) {
|
||||
require.Equal(t, false, applied)
|
||||
fmt.Println(msg == nil)
|
||||
fmt.Println(curH)
|
||||
applied = true
|
||||
return more, nil
|
||||
}, func(_ context.Context, ts *types.TipSet) error {
|
||||
@@ -1067,3 +1065,250 @@ func TestRemoveTriggersOnMessage(t *testing.T) {
|
||||
require.Equal(t, true, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
}
|
||||
|
||||
type testStateChange struct {
|
||||
from string
|
||||
to string
|
||||
}
|
||||
|
||||
func TestStateChanged(t *testing.T) {
|
||||
fcs := &fakeCS{
|
||||
t: t,
|
||||
h: 1,
|
||||
|
||||
msgs: map[cid.Cid]fakeMsg{},
|
||||
blkMsgs: map[cid.Cid]cid.Cid{},
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
more := true
|
||||
var applied, reverted bool
|
||||
var appliedData StateChange
|
||||
var appliedOldTs *types.TipSet
|
||||
var appliedNewTs *types.TipSet
|
||||
var appliedH abi.ChainEpoch
|
||||
var matchData StateChange
|
||||
|
||||
confidence := 3
|
||||
timeout := abi.ChainEpoch(20)
|
||||
|
||||
err := events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) {
|
||||
return false, true, nil
|
||||
}, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) {
|
||||
require.Equal(t, false, applied)
|
||||
applied = true
|
||||
appliedData = data
|
||||
appliedOldTs = oldTs
|
||||
appliedNewTs = newTs
|
||||
appliedH = curH
|
||||
return more, nil
|
||||
}, func(_ context.Context, ts *types.TipSet) error {
|
||||
reverted = true
|
||||
return nil
|
||||
}, confidence, timeout, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) {
|
||||
if matchData == nil {
|
||||
return false, matchData, nil
|
||||
}
|
||||
|
||||
d := matchData
|
||||
matchData = nil
|
||||
return true, d, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// create few blocks to make sure nothing get's randomly called
|
||||
|
||||
fcs.advance(0, 4, nil) // H=5
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// create state change (but below confidence threshold)
|
||||
matchData = testStateChange{from: "a", to: "b"}
|
||||
fcs.advance(0, 3, nil)
|
||||
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// create additional block so we are above confidence threshold
|
||||
|
||||
fcs.advance(0, 2, nil) // H=10 (confidence=3, apply)
|
||||
|
||||
require.Equal(t, true, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
applied = false
|
||||
|
||||
// dip below confidence (should not apply again)
|
||||
fcs.advance(2, 2, nil) // H=10 (confidence=3, apply)
|
||||
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// Change happens from 5 -> 6
|
||||
require.Equal(t, abi.ChainEpoch(5), appliedOldTs.Height())
|
||||
require.Equal(t, abi.ChainEpoch(6), appliedNewTs.Height())
|
||||
|
||||
// Actually applied (with confidence) at 9
|
||||
require.Equal(t, abi.ChainEpoch(9), appliedH)
|
||||
|
||||
// Make sure the state change was correctly passed through
|
||||
rcvd := appliedData.(testStateChange)
|
||||
require.Equal(t, "a", rcvd.from)
|
||||
require.Equal(t, "b", rcvd.to)
|
||||
}
|
||||
|
||||
func TestStateChangedRevert(t *testing.T) {
|
||||
fcs := &fakeCS{
|
||||
t: t,
|
||||
h: 1,
|
||||
|
||||
msgs: map[cid.Cid]fakeMsg{},
|
||||
blkMsgs: map[cid.Cid]cid.Cid{},
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
more := true
|
||||
var applied, reverted bool
|
||||
var matchData StateChange
|
||||
|
||||
confidence := 1
|
||||
timeout := abi.ChainEpoch(20)
|
||||
|
||||
err := events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) {
|
||||
return false, true, nil
|
||||
}, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) {
|
||||
require.Equal(t, false, applied)
|
||||
applied = true
|
||||
return more, nil
|
||||
}, func(_ context.Context, ts *types.TipSet) error {
|
||||
reverted = true
|
||||
return nil
|
||||
}, confidence, timeout, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) {
|
||||
if matchData == nil {
|
||||
return false, matchData, nil
|
||||
}
|
||||
|
||||
d := matchData
|
||||
matchData = nil
|
||||
return true, d, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
fcs.advance(0, 2, nil) // H=3
|
||||
|
||||
// Make a state change from TS at height 3 to TS at height 4
|
||||
matchData = testStateChange{from: "a", to: "b"}
|
||||
fcs.advance(0, 1, nil) // H=4
|
||||
|
||||
// Haven't yet reached confidence
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// Advance to reach confidence level
|
||||
fcs.advance(0, 1, nil) // H=5
|
||||
|
||||
// Should now have called the handler
|
||||
require.Equal(t, true, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
applied = false
|
||||
|
||||
// Advance 3 more TS
|
||||
fcs.advance(0, 3, nil) // H=8
|
||||
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// Regress but not so far as to cause a revert
|
||||
fcs.advance(3, 1, nil) // H=6
|
||||
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// Regress back to state where change happened
|
||||
fcs.advance(3, 1, nil) // H=4
|
||||
|
||||
// Expect revert to have happened
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, true, reverted)
|
||||
}
|
||||
|
||||
func TestStateChangedTimeout(t *testing.T) {
|
||||
fcs := &fakeCS{
|
||||
t: t,
|
||||
h: 1,
|
||||
|
||||
msgs: map[cid.Cid]fakeMsg{},
|
||||
blkMsgs: map[cid.Cid]cid.Cid{},
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
called := false
|
||||
|
||||
err := events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) {
|
||||
return false, true, nil
|
||||
}, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) {
|
||||
called = true
|
||||
require.Nil(t, data)
|
||||
require.Equal(t, abi.ChainEpoch(20), newTs.Height())
|
||||
require.Equal(t, abi.ChainEpoch(23), curH)
|
||||
return false, nil
|
||||
}, func(_ context.Context, ts *types.TipSet) error {
|
||||
t.Fatal("revert on timeout")
|
||||
return nil
|
||||
}, 3, 20, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) {
|
||||
return false, nil, nil
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
fcs.advance(0, 21, nil)
|
||||
require.False(t, called)
|
||||
|
||||
fcs.advance(0, 5, nil)
|
||||
require.True(t, called)
|
||||
called = false
|
||||
|
||||
// with check func reporting done
|
||||
|
||||
fcs = &fakeCS{
|
||||
t: t,
|
||||
h: 1,
|
||||
|
||||
msgs: map[cid.Cid]fakeMsg{},
|
||||
blkMsgs: map[cid.Cid]cid.Cid{},
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events = NewEvents(context.Background(), fcs)
|
||||
|
||||
err = events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) {
|
||||
return true, true, nil
|
||||
}, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) {
|
||||
called = true
|
||||
require.Nil(t, data)
|
||||
require.Equal(t, abi.ChainEpoch(20), newTs.Height())
|
||||
require.Equal(t, abi.ChainEpoch(23), curH)
|
||||
return false, nil
|
||||
}, func(_ context.Context, ts *types.TipSet) error {
|
||||
t.Fatal("revert on timeout")
|
||||
return nil
|
||||
}, 3, 20, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) {
|
||||
return false, nil, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
fcs.advance(0, 21, nil)
|
||||
require.False(t, called)
|
||||
|
||||
fcs.advance(0, 5, nil)
|
||||
require.False(t, called)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-amt-ipld/v2"
|
||||
"github.com/filecoin-project/lotus/api/apibstore"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/market"
|
||||
"github.com/ipfs/go-cid"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
)
|
||||
|
||||
// UserData is the data returned from the DiffFunc
|
||||
type UserData interface{}
|
||||
|
||||
// ChainAPI abstracts out calls made by this class to external APIs
|
||||
type ChainAPI interface {
|
||||
apibstore.ChainIO
|
||||
StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error)
|
||||
}
|
||||
|
||||
// StatePredicates has common predicates for responding to state changes
|
||||
type StatePredicates struct {
|
||||
api ChainAPI
|
||||
cst *cbor.BasicIpldStore
|
||||
}
|
||||
|
||||
func NewStatePredicates(api ChainAPI) *StatePredicates {
|
||||
return &StatePredicates{
|
||||
api: api,
|
||||
cst: cbor.NewCborStore(apibstore.NewAPIBlockstore(api)),
|
||||
}
|
||||
}
|
||||
|
||||
// DiffFunc check if there's a change form oldState to newState, and returns
|
||||
// - changed: was there a change
|
||||
// - user: user-defined data representing the state change
|
||||
// - err
|
||||
type DiffFunc func(ctx context.Context, oldState, newState *types.TipSet) (changed bool, user UserData, err error)
|
||||
|
||||
type DiffStateFunc func(ctx context.Context, oldActorStateHead, newActorStateHead cid.Cid) (changed bool, user UserData, err error)
|
||||
|
||||
// OnActorStateChanged calls diffStateFunc when the state changes for the given actor
|
||||
func (sp *StatePredicates) OnActorStateChanged(addr address.Address, diffStateFunc DiffStateFunc) DiffFunc {
|
||||
return func(ctx context.Context, oldState, newState *types.TipSet) (changed bool, user UserData, err error) {
|
||||
oldActor, err := sp.api.StateGetActor(ctx, addr, oldState.Key())
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
newActor, err := sp.api.StateGetActor(ctx, addr, newState.Key())
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if oldActor.Head.Equals(newActor.Head) {
|
||||
return false, nil, nil
|
||||
}
|
||||
return diffStateFunc(ctx, oldActor.Head, newActor.Head)
|
||||
}
|
||||
}
|
||||
|
||||
type DiffStorageMarketStateFunc func(ctx context.Context, oldState *market.State, newState *market.State) (changed bool, user UserData, err error)
|
||||
|
||||
// OnStorageMarketActorChanged calls diffStorageMarketState when the state changes for the market actor
|
||||
func (sp *StatePredicates) OnStorageMarketActorChanged(diffStorageMarketState DiffStorageMarketStateFunc) DiffFunc {
|
||||
return sp.OnActorStateChanged(builtin.StorageMarketActorAddr, func(ctx context.Context, oldActorStateHead, newActorStateHead cid.Cid) (changed bool, user UserData, err error) {
|
||||
var oldState market.State
|
||||
if err := sp.cst.Get(ctx, oldActorStateHead, &oldState); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
var newState market.State
|
||||
if err := sp.cst.Get(ctx, newActorStateHead, &newState); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return diffStorageMarketState(ctx, &oldState, &newState)
|
||||
})
|
||||
}
|
||||
|
||||
type DiffDealStatesFunc func(ctx context.Context, oldDealStateRoot *amt.Root, newDealStateRoot *amt.Root) (changed bool, user UserData, err error)
|
||||
|
||||
// OnDealStateChanged calls diffDealStates when the market state changes
|
||||
func (sp *StatePredicates) OnDealStateChanged(diffDealStates DiffDealStatesFunc) DiffStorageMarketStateFunc {
|
||||
return func(ctx context.Context, oldState *market.State, newState *market.State) (changed bool, user UserData, err error) {
|
||||
if oldState.States.Equals(newState.States) {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
oldRoot, err := amt.LoadAMT(ctx, sp.cst, oldState.States)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
newRoot, err := amt.LoadAMT(ctx, sp.cst, newState.States)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
return diffDealStates(ctx, oldRoot, newRoot)
|
||||
}
|
||||
}
|
||||
|
||||
// ChangedDeals is a set of changes to deal state
|
||||
type ChangedDeals map[abi.DealID]DealStateChange
|
||||
|
||||
// DealStateChange is a change in deal state from -> to
|
||||
type DealStateChange struct {
|
||||
From market.DealState
|
||||
To market.DealState
|
||||
}
|
||||
|
||||
// DealStateChangedForIDs detects changes in the deal state AMT for the given deal IDs
|
||||
func (sp *StatePredicates) DealStateChangedForIDs(dealIds []abi.DealID) DiffDealStatesFunc {
|
||||
return func(ctx context.Context, oldDealStateRoot *amt.Root, newDealStateRoot *amt.Root) (changed bool, user UserData, err error) {
|
||||
changedDeals := make(ChangedDeals)
|
||||
for _, dealID := range dealIds {
|
||||
var oldDeal, newDeal market.DealState
|
||||
err := oldDealStateRoot.Get(ctx, uint64(dealID), &oldDeal)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
err = newDealStateRoot.Get(ctx, uint64(dealID), &newDeal)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if oldDeal != newDeal {
|
||||
changedDeals[dealID] = DealStateChange{oldDeal, newDeal}
|
||||
}
|
||||
}
|
||||
if len(changedDeals) > 0 {
|
||||
return true, changedDeals, nil
|
||||
}
|
||||
return false, nil, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/crypto"
|
||||
"github.com/ipfs/go-hamt-ipld"
|
||||
|
||||
"github.com/filecoin-project/go-amt-ipld/v2"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/market"
|
||||
ds "github.com/ipfs/go-datastore"
|
||||
ds_sync "github.com/ipfs/go-datastore/sync"
|
||||
bstore "github.com/ipfs/go-ipfs-blockstore"
|
||||
cbornode "github.com/ipfs/go-ipld-cbor"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
)
|
||||
|
||||
var dummyCid cid.Cid
|
||||
|
||||
func init() {
|
||||
dummyCid, _ = cid.Parse("bafkqaaa")
|
||||
}
|
||||
|
||||
type mockAPI struct {
|
||||
ts map[types.TipSetKey]*types.Actor
|
||||
bs bstore.Blockstore
|
||||
}
|
||||
|
||||
func newMockAPI(bs bstore.Blockstore) *mockAPI {
|
||||
return &mockAPI{
|
||||
bs: bs,
|
||||
ts: make(map[types.TipSetKey]*types.Actor),
|
||||
}
|
||||
}
|
||||
|
||||
func (m mockAPI) ChainHasObj(ctx context.Context, c cid.Cid) (bool, error) {
|
||||
return m.bs.Has(c)
|
||||
}
|
||||
|
||||
func (m mockAPI) ChainReadObj(ctx context.Context, c cid.Cid) ([]byte, error) {
|
||||
blk, err := m.bs.Get(c)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("blockstore get: %w", err)
|
||||
}
|
||||
|
||||
return blk.RawData(), nil
|
||||
}
|
||||
|
||||
func (m mockAPI) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) {
|
||||
return m.ts[tsk], nil
|
||||
}
|
||||
|
||||
func (m mockAPI) setActor(tsk types.TipSetKey, act *types.Actor) {
|
||||
m.ts[tsk] = act
|
||||
}
|
||||
|
||||
func TestPredicates(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
bs := bstore.NewBlockstore(ds_sync.MutexWrap(ds.NewMapDatastore()))
|
||||
store := cbornode.NewCborStore(bs)
|
||||
|
||||
oldDeals := map[abi.DealID]*market.DealState{
|
||||
abi.DealID(1): {
|
||||
SectorStartEpoch: 1,
|
||||
LastUpdatedEpoch: 2,
|
||||
SlashEpoch: 0,
|
||||
},
|
||||
abi.DealID(2): {
|
||||
SectorStartEpoch: 4,
|
||||
LastUpdatedEpoch: 5,
|
||||
SlashEpoch: 0,
|
||||
},
|
||||
}
|
||||
oldStateC := createMarketState(ctx, t, store, oldDeals)
|
||||
|
||||
newDeals := map[abi.DealID]*market.DealState{
|
||||
abi.DealID(1): {
|
||||
SectorStartEpoch: 1,
|
||||
LastUpdatedEpoch: 3,
|
||||
SlashEpoch: 0,
|
||||
},
|
||||
abi.DealID(2): {
|
||||
SectorStartEpoch: 4,
|
||||
LastUpdatedEpoch: 6,
|
||||
SlashEpoch: 6,
|
||||
},
|
||||
}
|
||||
newStateC := createMarketState(ctx, t, store, newDeals)
|
||||
|
||||
miner, err := address.NewFromString("t00")
|
||||
require.NoError(t, err)
|
||||
oldState, err := mockTipset(miner, 1)
|
||||
require.NoError(t, err)
|
||||
newState, err := mockTipset(miner, 2)
|
||||
require.NoError(t, err)
|
||||
|
||||
api := newMockAPI(bs)
|
||||
api.setActor(oldState.Key(), &types.Actor{Head: oldStateC})
|
||||
api.setActor(newState.Key(), &types.Actor{Head: newStateC})
|
||||
|
||||
preds := NewStatePredicates(api)
|
||||
|
||||
dealIds := []abi.DealID{abi.DealID(1), abi.DealID(2)}
|
||||
diffFn := preds.OnStorageMarketActorChanged(preds.OnDealStateChanged(preds.DealStateChangedForIDs(dealIds)))
|
||||
|
||||
// Diff a state against itself: expect no change
|
||||
changed, _, err := diffFn(ctx, oldState, oldState)
|
||||
require.NoError(t, err)
|
||||
require.False(t, changed)
|
||||
|
||||
// Diff old state against new state
|
||||
changed, val, err := diffFn(ctx, oldState, newState)
|
||||
require.NoError(t, err)
|
||||
require.True(t, changed)
|
||||
|
||||
changedDeals, ok := val.(ChangedDeals)
|
||||
require.True(t, ok)
|
||||
require.Len(t, changedDeals, 2)
|
||||
require.Contains(t, changedDeals, abi.DealID(1))
|
||||
require.Contains(t, changedDeals, abi.DealID(2))
|
||||
deal1 := changedDeals[abi.DealID(1)]
|
||||
if deal1.From.LastUpdatedEpoch != 2 || deal1.To.LastUpdatedEpoch != 3 {
|
||||
t.Fatal("Unexpected change to LastUpdatedEpoch")
|
||||
}
|
||||
deal2 := changedDeals[abi.DealID(2)]
|
||||
if deal2.From.SlashEpoch != 0 || deal2.To.SlashEpoch != 6 {
|
||||
t.Fatal("Unexpected change to SlashEpoch")
|
||||
}
|
||||
|
||||
// Test that OnActorStateChanged does not call the callback if the state has not changed
|
||||
mockAddr, err := address.NewFromString("t01")
|
||||
require.NoError(t, err)
|
||||
actorDiffFn := preds.OnActorStateChanged(mockAddr, func(context.Context, cid.Cid, cid.Cid) (bool, UserData, error) {
|
||||
t.Fatal("No state change so this should not be called")
|
||||
return false, nil, nil
|
||||
})
|
||||
changed, _, err = actorDiffFn(ctx, oldState, oldState)
|
||||
require.NoError(t, err)
|
||||
require.False(t, changed)
|
||||
|
||||
// Test that OnDealStateChanged does not call the callback if the state has not changed
|
||||
diffDealStateFn := preds.OnDealStateChanged(func(context.Context, *amt.Root, *amt.Root) (bool, UserData, error) {
|
||||
t.Fatal("No state change so this should not be called")
|
||||
return false, nil, nil
|
||||
})
|
||||
marketState := createEmptyMarketState(t, store)
|
||||
changed, _, err = diffDealStateFn(ctx, marketState, marketState)
|
||||
require.NoError(t, err)
|
||||
require.False(t, changed)
|
||||
}
|
||||
|
||||
func mockTipset(miner address.Address, timestamp uint64) (*types.TipSet, error) {
|
||||
return types.NewTipSet([]*types.BlockHeader{{
|
||||
Miner: miner,
|
||||
Height: 5,
|
||||
ParentStateRoot: dummyCid,
|
||||
Messages: dummyCid,
|
||||
ParentMessageReceipts: dummyCid,
|
||||
BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS},
|
||||
BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS},
|
||||
Timestamp: timestamp,
|
||||
}})
|
||||
}
|
||||
|
||||
func createMarketState(ctx context.Context, t *testing.T, store *cbornode.BasicIpldStore, deals map[abi.DealID]*market.DealState) cid.Cid {
|
||||
rootCid := createAMT(ctx, t, store, deals)
|
||||
|
||||
state := createEmptyMarketState(t, store)
|
||||
state.States = rootCid
|
||||
|
||||
stateC, err := store.Put(ctx, state)
|
||||
require.NoError(t, err)
|
||||
return stateC
|
||||
}
|
||||
|
||||
func createEmptyMarketState(t *testing.T, store *cbornode.BasicIpldStore) *market.State {
|
||||
emptyArrayCid, err := amt.NewAMT(store).Flush(context.TODO())
|
||||
require.NoError(t, err)
|
||||
emptyMap, err := store.Put(context.TODO(), hamt.NewNode(store, hamt.UseTreeBitWidth(5)))
|
||||
require.NoError(t, err)
|
||||
return market.ConstructState(emptyArrayCid, emptyMap, emptyMap)
|
||||
}
|
||||
|
||||
func createAMT(ctx context.Context, t *testing.T, store *cbornode.BasicIpldStore, deals map[abi.DealID]*market.DealState) cid.Cid {
|
||||
root := amt.NewAMT(store)
|
||||
for dealID, dealState := range deals {
|
||||
err := root.Set(ctx, uint64(dealID), dealState)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
rootCid, err := root.Flush(ctx)
|
||||
require.NoError(t, err)
|
||||
return rootCid
|
||||
}
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
|
||||
func (e *calledEvents) CheckMsg(ctx context.Context, smsg types.ChainMsg, hnd CalledHandler) CheckFunc {
|
||||
func (me *messageEvents) CheckMsg(ctx context.Context, smsg types.ChainMsg, hnd MsgHandler) CheckFunc {
|
||||
msg := smsg.VMMessage()
|
||||
|
||||
return func(ts *types.TipSet) (done bool, more bool, err error) {
|
||||
fa, err := e.cs.StateGetActor(ctx, msg.From, ts.Key())
|
||||
fa, err := me.cs.StateGetActor(ctx, msg.From, ts.Key())
|
||||
if err != nil {
|
||||
return false, true, err
|
||||
}
|
||||
@@ -22,7 +22,7 @@ func (e *calledEvents) CheckMsg(ctx context.Context, smsg types.ChainMsg, hnd Ca
|
||||
return false, true, nil
|
||||
}
|
||||
|
||||
rec, err := e.cs.StateGetReceipt(ctx, smsg.VMMessage().Cid(), ts.Key())
|
||||
rec, err := me.cs.StateGetReceipt(ctx, smsg.VMMessage().Cid(), ts.Key())
|
||||
if err != nil {
|
||||
return false, true, xerrors.Errorf("getting receipt in CheckMsg: %w", err)
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func (e *calledEvents) CheckMsg(ctx context.Context, smsg types.ChainMsg, hnd Ca
|
||||
}
|
||||
}
|
||||
|
||||
func (e *calledEvents) MatchMsg(inmsg *types.Message) MatchFunc {
|
||||
func (me *messageEvents) MatchMsg(inmsg *types.Message) MsgMatchFunc {
|
||||
return func(msg *types.Message) (bool, error) {
|
||||
if msg.From == inmsg.From && msg.Nonce == inmsg.Nonce && !inmsg.Equals(msg) {
|
||||
return false, xerrors.Errorf("matching msg %s from %s, nonce %d: got duplicate origin/nonce msg %d", inmsg.Cid(), inmsg.From, inmsg.Nonce, msg.Nonce)
|
||||
|
||||
+3
-3
@@ -196,7 +196,7 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) {
|
||||
*genm2,
|
||||
},
|
||||
NetworkName: "",
|
||||
Timestamp: uint64(time.Now().Add(-500 * build.BlockDelay * time.Second).Unix()),
|
||||
Timestamp: uint64(time.Now().Add(-500 * time.Duration(build.BlockDelaySecs) * time.Second).Unix()),
|
||||
}
|
||||
|
||||
genb, err := genesis2.MakeGenesisBlock(context.TODO(), bs, sys, tpl)
|
||||
@@ -223,7 +223,7 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) {
|
||||
miners := []address.Address{maddr1, maddr2}
|
||||
|
||||
beac := beacon.NewMockBeacon(time.Second)
|
||||
//beac, err := drand.NewDrandBeacon(tpl.Timestamp, build.BlockDelay)
|
||||
//beac, err := drand.NewDrandBeacon(tpl.Timestamp, build.BlockDelaySecs)
|
||||
//if err != nil {
|
||||
//return nil, xerrors.Errorf("creating drand beacon: %w", err)
|
||||
//}
|
||||
@@ -414,7 +414,7 @@ func (cg *ChainGen) makeBlock(parents *types.TipSet, m address.Address, vrfticke
|
||||
if cg.Timestamper != nil {
|
||||
ts = cg.Timestamper(parents, height-parents.Height())
|
||||
} else {
|
||||
ts = parents.MinTimestamp() + uint64((height-parents.Height())*build.BlockDelay)
|
||||
ts = parents.MinTimestamp() + uint64(height-parents.Height())*build.BlockDelaySecs
|
||||
}
|
||||
|
||||
fblk, err := MinerCreateBlock(context.TODO(), cg.sm, cg.w, &api.BlockTemplate{
|
||||
|
||||
@@ -150,6 +150,17 @@ func aggregateSignatures(sigs []crypto.Signature) (*crypto.Signature, error) {
|
||||
}
|
||||
|
||||
aggSig := bls.Aggregate(blsSigs)
|
||||
if aggSig == nil {
|
||||
if len(sigs) > 0 {
|
||||
return nil, xerrors.Errorf("bls.Aggregate returned nil with %d signatures", len(sigs))
|
||||
}
|
||||
|
||||
return &crypto.Signature{
|
||||
Type: crypto.SigTypeBLS,
|
||||
Data: new(bls.Signature)[:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &crypto.Signature{
|
||||
Type: crypto.SigTypeBLS,
|
||||
Data: aggSig[:],
|
||||
|
||||
@@ -187,7 +187,7 @@ func New(api Provider, ds dtypes.MetadataDS, netName dtypes.NetworkName) (*Messa
|
||||
|
||||
mp := &MessagePool{
|
||||
closer: make(chan struct{}),
|
||||
repubTk: time.NewTicker(build.BlockDelay * 10 * time.Second),
|
||||
repubTk: time.NewTicker(time.Duration(build.BlockDelaySecs) * 10 * time.Second),
|
||||
localAddrs: make(map[address.Address]struct{}),
|
||||
pending: make(map[address.Address]*msgSet),
|
||||
minGasPrice: types.NewInt(0),
|
||||
|
||||
+47
-8
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
amt "github.com/filecoin-project/go-amt-ipld/v2"
|
||||
"github.com/filecoin-project/go-bitfield"
|
||||
"github.com/filecoin-project/sector-storage/ffiwrapper"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
@@ -173,13 +174,34 @@ func GetSectorsForWinningPoSt(ctx context.Context, pv ffiwrapper.Verifier, sm *S
|
||||
return nil, xerrors.Errorf("(get sectors) failed to load miner actor state: %w", err)
|
||||
}
|
||||
|
||||
// TODO: Optimization: we could avoid loaditg the whole proving set here if we had AMT.GetNth with bitfield filtering
|
||||
sectorSet, err := GetProvingSetRaw(ctx, sm, mas)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("getting proving set: %w", err)
|
||||
cst := cbor.NewCborStore(sm.cs.Blockstore())
|
||||
var deadlines miner.Deadlines
|
||||
if err := cst.Get(ctx, mas.Deadlines, &deadlines); err != nil {
|
||||
return nil, xerrors.Errorf("failed to load deadlines: %w", err)
|
||||
}
|
||||
|
||||
if len(sectorSet) == 0 {
|
||||
notProving, err := abi.BitFieldUnion(mas.Faults, mas.Recoveries)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to union faults and recoveries: %w", err)
|
||||
}
|
||||
|
||||
allSectors, err := bitfield.MultiMerge(append(deadlines.Due[:], mas.NewSectors)...)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("merging deadline bitfields failed: %w", err)
|
||||
}
|
||||
|
||||
provingSectors, err := bitfield.SubtractBitField(allSectors, notProving)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to subtract non-proving sectors from set: %w", err)
|
||||
}
|
||||
|
||||
numProvSect, err := provingSectors.Count()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to count bits: %w", err)
|
||||
}
|
||||
|
||||
// TODO(review): is this right? feels fishy to me
|
||||
if numProvSect == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -198,17 +220,34 @@ func GetSectorsForWinningPoSt(ctx context.Context, pv ffiwrapper.Verifier, sm *S
|
||||
return nil, xerrors.Errorf("getting miner ID: %w", err)
|
||||
}
|
||||
|
||||
ids, err := pv.GenerateWinningPoStSectorChallenge(ctx, wpt, abi.ActorID(mid), rand, uint64(len(sectorSet)))
|
||||
ids, err := pv.GenerateWinningPoStSectorChallenge(ctx, wpt, abi.ActorID(mid), rand, numProvSect)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("generating winning post challenges: %w", err)
|
||||
}
|
||||
|
||||
sectors, err := provingSectors.All(miner.SectorsMax)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to enumerate all sector IDs: %w", err)
|
||||
}
|
||||
|
||||
sectorAmt, err := amt.LoadAMT(ctx, cst, mas.Sectors)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to load sectors amt: %w", err)
|
||||
}
|
||||
|
||||
out := make([]abi.SectorInfo, len(ids))
|
||||
for i, n := range ids {
|
||||
sid := sectors[n]
|
||||
|
||||
var sinfo miner.SectorOnChainInfo
|
||||
if err := sectorAmt.Get(ctx, sid, &sinfo); err != nil {
|
||||
return nil, xerrors.Errorf("failed to get sector %d: %w", sid, err)
|
||||
}
|
||||
|
||||
out[i] = abi.SectorInfo{
|
||||
SealProof: spt,
|
||||
SectorNumber: sectorSet[n].ID,
|
||||
SealedCID: sectorSet[n].Info.Info.SealedCID,
|
||||
SectorNumber: sinfo.Info.SectorNumber,
|
||||
SealedCID: sinfo.Info.SealedCID,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,11 @@ func (fts *FullTipSet) Cids() []cid.Cid {
|
||||
return cids
|
||||
}
|
||||
|
||||
// TipSet returns a narrower view of this FullTipSet elliding the block
|
||||
// messages.
|
||||
func (fts *FullTipSet) TipSet() *types.TipSet {
|
||||
if fts.tipset != nil {
|
||||
// FIXME: fts.tipset is actually never set. Should it memoize?
|
||||
return fts.tipset
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ type lbEntry struct {
|
||||
target types.TipSetKey
|
||||
}
|
||||
|
||||
func (ci *ChainIndex) GetTipsetByHeight(ctx context.Context, from *types.TipSet, to abi.ChainEpoch) (*types.TipSet, error) {
|
||||
func (ci *ChainIndex) GetTipsetByHeight(_ context.Context, from *types.TipSet, to abi.ChainEpoch) (*types.TipSet, error) {
|
||||
if from.Height()-to <= ci.skipLength {
|
||||
return ci.walkBack(from, to)
|
||||
}
|
||||
|
||||
@@ -52,6 +52,15 @@ var blockValidationCacheKeyPrefix = dstore.NewKey("blockValidation")
|
||||
// ReorgNotifee represents a callback that gets called upon reorgs.
|
||||
type ReorgNotifee func(rev, app []*types.TipSet) error
|
||||
|
||||
// ChainStore is the main point of access to chain data.
|
||||
//
|
||||
// Raw chain data is stored in the Blockstore, with relevant markers (genesis,
|
||||
// latest head tipset references) being tracked in the Datastore (key-value
|
||||
// store).
|
||||
//
|
||||
// To alleviate disk access, the ChainStore has two ARC caches:
|
||||
// 1. a tipset cache
|
||||
// 2. a block => messages references cache.
|
||||
type ChainStore struct {
|
||||
bs bstore.Blockstore
|
||||
ds dstore.Datastore
|
||||
@@ -266,6 +275,9 @@ func (cs *ChainStore) PutTipSet(ctx context.Context, ts *types.TipSet) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaybeTakeHeavierTipSet evaluates the incoming tipset and locks it in our
|
||||
// internal state as our new head, if and only if it is heavier than the current
|
||||
// head.
|
||||
func (cs *ChainStore) MaybeTakeHeavierTipSet(ctx context.Context, ts *types.TipSet) error {
|
||||
cs.heaviestLk.Lock()
|
||||
defer cs.heaviestLk.Unlock()
|
||||
@@ -331,6 +343,9 @@ func (cs *ChainStore) reorgWorker(ctx context.Context, initialNotifees []ReorgNo
|
||||
return out
|
||||
}
|
||||
|
||||
// takeHeaviestTipSet actually sets the incoming tipset as our head both in
|
||||
// memory and in the ChainStore. It also sends a notification to deliver to
|
||||
// ReorgNotifees.
|
||||
func (cs *ChainStore) takeHeaviestTipSet(ctx context.Context, ts *types.TipSet) error {
|
||||
_, span := trace.StartSpan(ctx, "takeHeaviestTipSet")
|
||||
defer span.End()
|
||||
@@ -368,6 +383,7 @@ func (cs *ChainStore) SetHead(ts *types.TipSet) error {
|
||||
return cs.takeHeaviestTipSet(context.TODO(), ts)
|
||||
}
|
||||
|
||||
// Contains returns whether our BlockStore has all blocks in the supplied TipSet.
|
||||
func (cs *ChainStore) Contains(ts *types.TipSet) (bool, error) {
|
||||
for _, c := range ts.Cids() {
|
||||
has, err := cs.bs.Has(c)
|
||||
@@ -382,6 +398,8 @@ func (cs *ChainStore) Contains(ts *types.TipSet) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// GetBlock fetches a BlockHeader with the supplied CID. It returns
|
||||
// blockstore.ErrNotFound if the block was not found in the BlockStore.
|
||||
func (cs *ChainStore) GetBlock(c cid.Cid) (*types.BlockHeader, error) {
|
||||
sb, err := cs.bs.Get(c)
|
||||
if err != nil {
|
||||
@@ -474,6 +492,7 @@ func (cs *ChainStore) ReorgOps(a, b *types.TipSet) ([]*types.TipSet, []*types.Ti
|
||||
return leftChain, rightChain, nil
|
||||
}
|
||||
|
||||
// GetHeaviestTipSet returns the current heaviest tipset known (i.e. our head).
|
||||
func (cs *ChainStore) GetHeaviestTipSet() *types.TipSet {
|
||||
cs.heaviestLk.Lock()
|
||||
defer cs.heaviestLk.Unlock()
|
||||
|
||||
+163
-18
@@ -49,10 +49,37 @@ import (
|
||||
"github.com/filecoin-project/lotus/metrics"
|
||||
)
|
||||
|
||||
// Blocks that are more than MaxHeightDrift epochs above
|
||||
//the theoretical max height based on systime are quickly rejected
|
||||
const MaxHeightDrift = 5
|
||||
|
||||
var log = logging.Logger("chain")
|
||||
|
||||
var LocalIncoming = "incoming"
|
||||
|
||||
// Syncer is in charge of running the chain synchronization logic. As such, it
|
||||
// is tasked with these functions, amongst others:
|
||||
//
|
||||
// * Fast-forwards the chain as it learns of new TipSets from the network via
|
||||
// the SyncManager.
|
||||
// * Applies the fork choice rule to select the correct side when confronted
|
||||
// with a fork in the network.
|
||||
// * Requests block headers and messages from other peers when not available
|
||||
// in our BlockStore.
|
||||
// * Tracks blocks marked as bad in a cache.
|
||||
// * Keeps the BlockStore and ChainStore consistent with our view of the world,
|
||||
// the latter of which in turn informs other components when a reorg has been
|
||||
// committed.
|
||||
//
|
||||
// The Syncer does not run workers itself. It's mainly concerned with
|
||||
// ensuring a consistent state of chain consensus. The reactive and network-
|
||||
// interfacing processes are part of other components, such as the SyncManager
|
||||
// (which owns the sync scheduler and sync workers), BlockSync, the HELLO
|
||||
// protocol, and the gossipsub block propagation layer.
|
||||
//
|
||||
// {hint/concept} The fork-choice rule as it currently stands is: "pick the
|
||||
// chain with the heaviest weight, so long as it hasn’t deviated one finality
|
||||
// threshold from our head (900 epochs, parameter determined by spec-actors)".
|
||||
type Syncer struct {
|
||||
// The interface for accessing and putting tipsets into local storage
|
||||
store *store.ChainStore
|
||||
@@ -85,6 +112,7 @@ type Syncer struct {
|
||||
verifier ffiwrapper.Verifier
|
||||
}
|
||||
|
||||
// NewSyncer creates a new Syncer object.
|
||||
func NewSyncer(sm *stmgr.StateManager, bsync *blocksync.BlockSync, connmgr connmgr.ConnManager, self peer.ID, beacon beacon.RandomBeacon, verifier ffiwrapper.Verifier) (*Syncer, error) {
|
||||
gen, err := sm.ChainStore().GetGenesis()
|
||||
if err != nil {
|
||||
@@ -133,6 +161,11 @@ func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if syncer.IsEpochBeyondCurrMax(fts.TipSet().Height()) {
|
||||
log.Errorf("Received block with impossibly large height %d", fts.TipSet().Height())
|
||||
return false
|
||||
}
|
||||
|
||||
for _, b := range fts.Blocks {
|
||||
if reason, ok := syncer.bad.Has(b.Cid()); ok {
|
||||
log.Warnf("InformNewHead called on block marked as bad: %s (reason: %s)", b.Cid(), reason)
|
||||
@@ -182,6 +215,11 @@ func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IncomingBlocks spawns a goroutine that subscribes to the local eventbus to
|
||||
// receive new block headers as they arrive from the network, and sends them to
|
||||
// the returned channel.
|
||||
//
|
||||
// These blocks have not necessarily been incorporated to our view of the chain.
|
||||
func (syncer *Syncer) IncomingBlocks(ctx context.Context) (<-chan *types.BlockHeader, error) {
|
||||
sub := syncer.incoming.Sub(LocalIncoming)
|
||||
out := make(chan *types.BlockHeader, 10)
|
||||
@@ -209,11 +247,15 @@ func (syncer *Syncer) IncomingBlocks(ctx context.Context) (<-chan *types.BlockHe
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ValidateMsgMeta performs structural and content hash validation of the
|
||||
// messages within this block. If validation passes, it stores the messages in
|
||||
// the underlying IPLD block store.
|
||||
func (syncer *Syncer) ValidateMsgMeta(fblk *types.FullBlock) error {
|
||||
if msgc := len(fblk.BlsMessages) + len(fblk.SecpkMessages); msgc > build.BlockMessageLimit {
|
||||
return xerrors.Errorf("block %s has too many messages (%d)", fblk.Header.Cid(), msgc)
|
||||
}
|
||||
|
||||
// Collect the CIDs of both types of messages separately: BLS and Secpk.
|
||||
var bcids, scids []cbg.CBORMarshaler
|
||||
for _, m := range fblk.BlsMessages {
|
||||
c := cbg.CborCid(m.Cid())
|
||||
@@ -231,11 +273,14 @@ func (syncer *Syncer) ValidateMsgMeta(fblk *types.FullBlock) error {
|
||||
blockstore := syncer.store.Blockstore()
|
||||
|
||||
bs := cbor.NewCborStore(blockstore)
|
||||
|
||||
// Compute the root CID of the combined message trie.
|
||||
smroot, err := computeMsgMeta(bs, bcids, scids)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("validating msgmeta, compute failed: %w", err)
|
||||
}
|
||||
|
||||
// Check that the message trie root matches with what's in the block.
|
||||
if fblk.Header.Messages != smroot {
|
||||
return xerrors.Errorf("messages in full block did not match msgmeta root in header (%s != %s)", fblk.Header.Messages, smroot)
|
||||
}
|
||||
@@ -345,6 +390,8 @@ func zipTipSetAndMessages(bs cbor.IpldStore, ts *types.TipSet, allbmsgs []*types
|
||||
return fts, nil
|
||||
}
|
||||
|
||||
// computeMsgMeta computes the root CID of the combined arrays of message CIDs
|
||||
// of both types (BLS and Secpk).
|
||||
func computeMsgMeta(bs cbor.IpldStore, bmsgCids, smsgCids []cbg.CBORMarshaler) (cid.Cid, error) {
|
||||
ctx := context.TODO()
|
||||
bmroot, err := amt.FromArray(ctx, bs, bmsgCids)
|
||||
@@ -368,14 +415,24 @@ func computeMsgMeta(bs cbor.IpldStore, bmsgCids, smsgCids []cbg.CBORMarshaler) (
|
||||
return mrcid, nil
|
||||
}
|
||||
|
||||
// FetchTipSet tries to load the provided tipset from the store, and falls back
|
||||
// to the network (BlockSync) by querying the supplied peer if not found
|
||||
// locally.
|
||||
//
|
||||
// {hint/usage} This is used from the HELLO protocol, to fetch the greeting
|
||||
// peer's heaviest tipset if we don't have it.
|
||||
func (syncer *Syncer) FetchTipSet(ctx context.Context, p peer.ID, tsk types.TipSetKey) (*store.FullTipSet, error) {
|
||||
if fts, err := syncer.tryLoadFullTipSet(tsk); err == nil {
|
||||
return fts, nil
|
||||
}
|
||||
|
||||
// fall back to the network.
|
||||
return syncer.Bsync.GetFullTipSet(ctx, p, tsk)
|
||||
}
|
||||
|
||||
// tryLoadFullTipSet queries the tipset in the ChainStore, and returns a full
|
||||
// representation of it containing FullBlocks. If ALL blocks are not found
|
||||
// locally, it errors entirely with blockstore.ErrNotFound.
|
||||
func (syncer *Syncer) tryLoadFullTipSet(tsk types.TipSetKey) (*store.FullTipSet, error) {
|
||||
ts, err := syncer.store.LoadTipSet(tsk)
|
||||
if err != nil {
|
||||
@@ -400,6 +457,12 @@ func (syncer *Syncer) tryLoadFullTipSet(tsk types.TipSetKey) (*store.FullTipSet,
|
||||
return fts, nil
|
||||
}
|
||||
|
||||
// Sync tries to advance our view of the chain to `maybeHead`. It does nothing
|
||||
// if our current head is heavier than the requested tipset, or if we're already
|
||||
// at the requested head, or if the head is the genesis.
|
||||
//
|
||||
// Most of the heavy-lifting logic happens in syncer#collectChain. Refer to the
|
||||
// godocs on that method for a more detailed view.
|
||||
func (syncer *Syncer) Sync(ctx context.Context, maybeHead *types.TipSet) error {
|
||||
ctx, span := trace.StartSpan(ctx, "chain.Sync")
|
||||
defer span.End()
|
||||
@@ -466,16 +529,27 @@ func (syncer *Syncer) ValidateTipSet(ctx context.Context, fts *store.FullTipSet)
|
||||
return nil
|
||||
}
|
||||
|
||||
var futures []async.ErrorFuture
|
||||
for _, b := range fts.Blocks {
|
||||
if err := syncer.ValidateBlock(ctx, b); err != nil {
|
||||
if isPermanent(err) {
|
||||
syncer.bad.Add(b.Cid(), err.Error())
|
||||
}
|
||||
return xerrors.Errorf("validating block %s: %w", b.Cid(), err)
|
||||
}
|
||||
b := b // rebind to a scoped variable
|
||||
|
||||
if err := syncer.sm.ChainStore().AddToTipSetTracker(b.Header); err != nil {
|
||||
return xerrors.Errorf("failed to add validated header to tipset tracker: %w", err)
|
||||
futures = append(futures, async.Err(func() error {
|
||||
if err := syncer.ValidateBlock(ctx, b); err != nil {
|
||||
if isPermanent(err) {
|
||||
syncer.bad.Add(b.Cid(), err.Error())
|
||||
}
|
||||
return xerrors.Errorf("validating block %s: %w", b.Cid(), err)
|
||||
}
|
||||
|
||||
if err := syncer.sm.ChainStore().AddToTipSetTracker(b.Header); err != nil {
|
||||
return xerrors.Errorf("failed to add validated header to tipset tracker: %w", err)
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
for _, f := range futures {
|
||||
if err := f.AwaitContext(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -592,18 +666,18 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) (er
|
||||
// fast checks first
|
||||
|
||||
now := uint64(time.Now().Unix())
|
||||
if h.Timestamp > now+build.AllowableClockDrift {
|
||||
if h.Timestamp > now+build.AllowableClockDriftSecs {
|
||||
return xerrors.Errorf("block was from the future (now=%d, blk=%d): %w", now, h.Timestamp, ErrTemporal)
|
||||
}
|
||||
if h.Timestamp > now {
|
||||
log.Warn("Got block from the future, but within threshold", h.Timestamp, time.Now().Unix())
|
||||
}
|
||||
|
||||
if h.Timestamp < baseTs.MinTimestamp()+(build.BlockDelay*uint64(h.Height-baseTs.Height())) {
|
||||
if h.Timestamp < baseTs.MinTimestamp()+(build.BlockDelaySecs*uint64(h.Height-baseTs.Height())) {
|
||||
log.Warn("timestamp funtimes: ", h.Timestamp, baseTs.MinTimestamp(), h.Height, baseTs.Height())
|
||||
diff := (baseTs.MinTimestamp() + (build.BlockDelay * uint64(h.Height-baseTs.Height()))) - h.Timestamp
|
||||
diff := (baseTs.MinTimestamp() + (build.BlockDelaySecs * uint64(h.Height-baseTs.Height()))) - h.Timestamp
|
||||
|
||||
return xerrors.Errorf("block was generated too soon (h.ts:%d < base.mints:%d + BLOCK_DELAY:%d * deltaH:%d; diff %d)", h.Timestamp, baseTs.MinTimestamp(), build.BlockDelay, h.Height-baseTs.Height(), diff)
|
||||
return xerrors.Errorf("block was generated too soon (h.ts:%d < base.mints:%d + BLOCK_DELAY:%d * deltaH:%d; diff %d)", h.Timestamp, baseTs.MinTimestamp(), build.BlockDelaySecs, h.Height-baseTs.Height(), diff)
|
||||
}
|
||||
|
||||
msgsCheck := async.Err(func() error {
|
||||
@@ -993,6 +1067,39 @@ func extractSyncState(ctx context.Context) *SyncerState {
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectHeaders collects the headers from the blocks between any two tipsets.
|
||||
//
|
||||
// `from` is the heaviest/projected/target tipset we have learned about, and
|
||||
// `to` is usually an anchor tipset we already have in our view of the chain
|
||||
// (which could be the genesis).
|
||||
//
|
||||
// collectHeaders checks if portions of the chain are in our ChainStore; falling
|
||||
// down to the network to retrieve the missing parts. If during the process, any
|
||||
// portion we receive is in our denylist (bad list), we short-circuit.
|
||||
//
|
||||
// {hint/naming}: `from` and `to` is in inverse order. `from` is the highest,
|
||||
// and `to` is the lowest. This method traverses the chain backwards.
|
||||
//
|
||||
// {hint/usage}: This is used by collectChain, which is in turn called from the
|
||||
// main Sync method (Syncer#Sync), so it's a pretty central method.
|
||||
//
|
||||
// {hint/logic}: The logic of this method is as follows:
|
||||
//
|
||||
// 1. Check that the from tipset is not linked to a parent block known to be
|
||||
// bad.
|
||||
// 2. Check the consistency of beacon entries in the from tipset. We check
|
||||
// total equality of the BeaconEntries in each block.
|
||||
// 3. Travers the chain backwards, for each tipset:
|
||||
// 3a. Load it from the chainstore; if found, it move on to its parent.
|
||||
// 3b. Query our peers via BlockSync in batches, requesting up to a
|
||||
// maximum of 500 tipsets every time.
|
||||
//
|
||||
// Once we've concluded, if we find a mismatching tipset at the height where the
|
||||
// anchor tipset should be, we are facing a fork, and we invoke Syncer#syncFork
|
||||
// to resolve it. Refer to the godocs there.
|
||||
//
|
||||
// All throughout the process, we keep checking if the received blocks are in
|
||||
// the deny list, and short-circuit the process if so.
|
||||
func (syncer *Syncer) collectHeaders(ctx context.Context, from *types.TipSet, to *types.TipSet) ([]*types.TipSet, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "collectHeaders")
|
||||
defer span.End()
|
||||
@@ -1009,6 +1116,8 @@ func (syncer *Syncer) collectHeaders(ctx context.Context, from *types.TipSet, to
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the parents of the from block are in the denylist.
|
||||
// i.e. if a fork of the chain has been requested that we know to be bad.
|
||||
for _, pcid := range from.Parents().Cids() {
|
||||
if reason, ok := syncer.bad.Has(pcid); ok {
|
||||
markBad("linked to %s", pcid)
|
||||
@@ -1079,8 +1188,8 @@ loop:
|
||||
}
|
||||
|
||||
// NB: GetBlocks validates that the blocks are in-fact the ones we
|
||||
// requested, and that they are correctly linked to eachother. It does
|
||||
// not validate any state transitions
|
||||
// requested, and that they are correctly linked to one another. It does
|
||||
// not validate any state transitions.
|
||||
window := 500
|
||||
if gap := int(blockSet[len(blockSet)-1].Height() - untilHeight); gap < window {
|
||||
window = gap
|
||||
@@ -1121,7 +1230,6 @@ loop:
|
||||
at = blks[len(blks)-1].Parents()
|
||||
}
|
||||
|
||||
// We have now ascertained that this is *not* a 'fast forward'
|
||||
if !types.CidArrsEqual(blockSet[len(blockSet)-1].Parents().Cids(), to.Cids()) {
|
||||
last := blockSet[len(blockSet)-1]
|
||||
if last.Parents() == to.Parents() {
|
||||
@@ -1129,6 +1237,8 @@ loop:
|
||||
return blockSet, nil
|
||||
}
|
||||
|
||||
// We have now ascertained that this is *not* a 'fast forward'
|
||||
|
||||
log.Warnf("(fork detected) synced header chain (%s - %d) does not link to our best block (%s - %d)", from.Cids(), from.Height(), to.Cids(), to.Height())
|
||||
fork, err := syncer.syncFork(ctx, last, to)
|
||||
if err != nil {
|
||||
@@ -1150,6 +1260,12 @@ loop:
|
||||
|
||||
var ErrForkTooLong = fmt.Errorf("fork longer than threshold")
|
||||
|
||||
// syncFork tries to obtain the chain fragment that links a fork into a common
|
||||
// ancestor in our view of the chain.
|
||||
//
|
||||
// If the fork is too long (build.ForkLengthThreshold), we add the entire subchain to the
|
||||
// denylist. Else, we find the common ancestor, and add the missing chain
|
||||
// fragment until the fork point to the returned []TipSet.
|
||||
func (syncer *Syncer) syncFork(ctx context.Context, from *types.TipSet, to *types.TipSet) ([]*types.TipSet, error) {
|
||||
tips, err := syncer.Bsync.GetBlocks(ctx, from.Parents(), int(build.ForkLengthThreshold))
|
||||
if err != nil {
|
||||
@@ -1301,6 +1417,25 @@ func persistMessages(bs bstore.Blockstore, bst *blocksync.BSTipSet) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectChain tries to advance our view of the chain to the purported head.
|
||||
//
|
||||
// It goes through various stages:
|
||||
//
|
||||
// 1. StageHeaders: we proceed in the sync process by requesting block headers
|
||||
// from our peers, moving back from their heads, until we reach a tipset
|
||||
// that we have in common (such a common tipset must exist, thought it may
|
||||
// simply be the genesis block).
|
||||
//
|
||||
// If the common tipset is our head, we treat the sync as a "fast-forward",
|
||||
// else we must drop part of our chain to connect to the peer's head
|
||||
// (referred to as "forking").
|
||||
//
|
||||
// 2. StagePersistHeaders: now that we've collected the missing headers,
|
||||
// augmented by those on the other side of a fork, we persist them to the
|
||||
// BlockStore.
|
||||
//
|
||||
// 3. StageMessages: having acquired the headers and found a common tipset,
|
||||
// we then move forward, requesting the full blocks, including the messages.
|
||||
func (syncer *Syncer) collectChain(ctx context.Context, ts *types.TipSet) error {
|
||||
ctx, span := trace.StartSpan(ctx, "collectChain")
|
||||
defer span.End()
|
||||
@@ -1350,9 +1485,8 @@ func (syncer *Syncer) collectChain(ctx context.Context, ts *types.TipSet) error
|
||||
func VerifyElectionPoStVRF(ctx context.Context, worker address.Address, rand []byte, evrf []byte) error {
|
||||
if build.InsecurePoStValidation {
|
||||
return nil
|
||||
} else {
|
||||
return gen.VerifyVRF(ctx, worker, rand, evrf)
|
||||
}
|
||||
return gen.VerifyVRF(ctx, worker, rand, evrf)
|
||||
}
|
||||
|
||||
func (syncer *Syncer) State() []SyncerState {
|
||||
@@ -1363,6 +1497,7 @@ func (syncer *Syncer) State() []SyncerState {
|
||||
return out
|
||||
}
|
||||
|
||||
// MarkBad manually adds a block to the "bad blocks" cache.
|
||||
func (syncer *Syncer) MarkBad(blk cid.Cid) {
|
||||
syncer.bad.Add(blk, "manually marked bad")
|
||||
}
|
||||
@@ -1370,7 +1505,7 @@ func (syncer *Syncer) MarkBad(blk cid.Cid) {
|
||||
func (syncer *Syncer) CheckBadBlockCache(blk cid.Cid) (string, bool) {
|
||||
return syncer.bad.Has(blk)
|
||||
}
|
||||
func (syncer *Syncer) getLatestBeaconEntry(ctx context.Context, ts *types.TipSet) (*types.BeaconEntry, error) {
|
||||
func (syncer *Syncer) getLatestBeaconEntry(_ context.Context, ts *types.TipSet) (*types.BeaconEntry, error) {
|
||||
cur := ts
|
||||
for i := 0; i < 20; i++ {
|
||||
cbe := cur.Blocks()[0].BeaconEntries
|
||||
@@ -1391,3 +1526,13 @@ func (syncer *Syncer) getLatestBeaconEntry(ctx context.Context, ts *types.TipSet
|
||||
|
||||
return nil, xerrors.Errorf("found NO beacon entries in the 20 blocks prior to given tipset")
|
||||
}
|
||||
|
||||
func (syncer *Syncer) IsEpochBeyondCurrMax(epoch abi.ChainEpoch) bool {
|
||||
g, err := syncer.store.GetGenesis()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
now := uint64(time.Now().Unix())
|
||||
return epoch > (abi.ChainEpoch((now-g.Timestamp)/build.BlockDelaySecs) + MaxHeightDrift)
|
||||
}
|
||||
|
||||
+1
-1
@@ -408,7 +408,7 @@ func TestSyncBadTimestamp(t *testing.T) {
|
||||
|
||||
base := tu.g.CurTipset
|
||||
tu.g.Timestamper = func(pts *types.TipSet, tl abi.ChainEpoch) uint64 {
|
||||
return pts.MinTimestamp() + (build.BlockDelay / 2)
|
||||
return pts.MinTimestamp() + (build.BlockDelaySecs / 2)
|
||||
}
|
||||
|
||||
fmt.Println("BASE: ", base.Cids())
|
||||
|
||||
@@ -76,7 +76,7 @@ func SizeStr(bi BigInt) string {
|
||||
}
|
||||
|
||||
f, _ := r.Float64()
|
||||
return fmt.Sprintf("%.3g %s", f, byteSizeUnits[i])
|
||||
return fmt.Sprintf("%.4g %s", f, byteSizeUnits[i])
|
||||
}
|
||||
|
||||
var deciUnits = []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"}
|
||||
|
||||
@@ -3,7 +3,12 @@ package types
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -60,8 +65,10 @@ func TestSizeStr(t *testing.T) {
|
||||
}{
|
||||
{0, "0 B"},
|
||||
{1, "1 B"},
|
||||
{1016, "1016 B"},
|
||||
{1024, "1 KiB"},
|
||||
{2000, "1.95 KiB"},
|
||||
{1000 * 1024, "1000 KiB"},
|
||||
{2000, "1.953 KiB"},
|
||||
{5 << 20, "5 MiB"},
|
||||
{11 << 60, "11 EiB"},
|
||||
}
|
||||
@@ -71,6 +78,22 @@ func TestSizeStr(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSizeStrUnitsSymmetry(t *testing.T) {
|
||||
s := rand.NewSource(time.Now().UnixNano())
|
||||
r := rand.New(s)
|
||||
|
||||
for i := 0; i < 1000000; i++ {
|
||||
n := r.Uint64()
|
||||
l := strings.ReplaceAll(units.BytesSize(float64(n)), " ", "")
|
||||
r := strings.ReplaceAll(SizeStr(NewInt(n)), " ", "")
|
||||
|
||||
assert.NotContains(t, l, "e+")
|
||||
assert.NotContains(t, r, "e+")
|
||||
|
||||
assert.Equal(t, l, r, "wrong formatting for %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSizeStrBig(t *testing.T) {
|
||||
ZiB := big.NewInt(50000)
|
||||
ZiB = ZiB.Lsh(ZiB, 70)
|
||||
|
||||
@@ -21,16 +21,16 @@ type ExecutionTrace struct {
|
||||
type GasTrace struct {
|
||||
Name string
|
||||
|
||||
Location []Loc
|
||||
TotalGas int64
|
||||
ComputeGas int64
|
||||
StorageGas int64
|
||||
TotalVirtualGas int64
|
||||
VirtualComputeGas int64
|
||||
VirtualStorageGas int64
|
||||
Location []Loc `json:"loc"`
|
||||
TotalGas int64 `json:"tg"`
|
||||
ComputeGas int64 `json:"cg"`
|
||||
StorageGas int64 `json:"sg"`
|
||||
TotalVirtualGas int64 `json:"vtg"`
|
||||
VirtualComputeGas int64 `json:"vcg"`
|
||||
VirtualStorageGas int64 `json:"vsg"`
|
||||
|
||||
TimeTaken time.Duration
|
||||
Extra interface{} `json:",omitempty"`
|
||||
TimeTaken time.Duration `json:"tt"`
|
||||
Extra interface{} `json:"ex,omitempty"`
|
||||
|
||||
Callers []uintptr `json:"-"`
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ import (
|
||||
type FIL BigInt
|
||||
|
||||
func (f FIL) String() string {
|
||||
r := new(big.Rat).SetFrac(f.Int, big.NewInt(build.FilecoinPrecision))
|
||||
r := new(big.Rat).SetFrac(f.Int, big.NewInt(int64(build.FilecoinPrecision)))
|
||||
if r.Sign() == 0 {
|
||||
return "0"
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func ParseFIL(s string) (FIL, error) {
|
||||
return FIL{}, fmt.Errorf("failed to parse %q as a decimal number", s)
|
||||
}
|
||||
|
||||
r = r.Mul(r, big.NewRat(build.FilecoinPrecision, 1))
|
||||
r = r.Mul(r, big.NewRat(int64(build.FilecoinPrecision), 1))
|
||||
if !r.IsInt() {
|
||||
return FIL{}, fmt.Errorf("invalid FIL value: %q", s)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ type ChainMsg interface {
|
||||
Cid() cid.Cid
|
||||
VMMessage() *Message
|
||||
ToStorageBlock() (block.Block, error)
|
||||
// FIXME: This is the *message* length, this name is misleading.
|
||||
ChainLength() int
|
||||
}
|
||||
|
||||
|
||||
+18
-6
@@ -143,30 +143,40 @@ func (ps pricedSyscalls) VerifySignature(signature crypto.Signature, signer addr
|
||||
return err
|
||||
}
|
||||
ps.chargeGas(c)
|
||||
defer ps.chargeGas(gasOnActorExec)
|
||||
|
||||
return ps.under.VerifySignature(signature, signer, plaintext)
|
||||
}
|
||||
|
||||
// Hashes input data using blake2b with 256 bit output.
|
||||
func (ps pricedSyscalls) HashBlake2b(data []byte) [32]byte {
|
||||
ps.chargeGas(ps.pl.OnHashing(len(data)))
|
||||
defer ps.chargeGas(gasOnActorExec)
|
||||
|
||||
return ps.under.HashBlake2b(data)
|
||||
}
|
||||
|
||||
// Computes an unsealed sector CID (CommD) from its constituent piece CIDs (CommPs) and sizes.
|
||||
func (ps pricedSyscalls) ComputeUnsealedSectorCID(reg abi.RegisteredSealProof, pieces []abi.PieceInfo) (cid.Cid, error) {
|
||||
ps.chargeGas(ps.pl.OnComputeUnsealedSectorCid(reg, pieces))
|
||||
defer ps.chargeGas(gasOnActorExec)
|
||||
|
||||
return ps.under.ComputeUnsealedSectorCID(reg, pieces)
|
||||
}
|
||||
|
||||
// Verifies a sector seal proof.
|
||||
func (ps pricedSyscalls) VerifySeal(vi abi.SealVerifyInfo) error {
|
||||
ps.chargeGas(ps.pl.OnVerifySeal(vi))
|
||||
defer ps.chargeGas(gasOnActorExec)
|
||||
|
||||
return ps.under.VerifySeal(vi)
|
||||
}
|
||||
|
||||
// Verifies a proof of spacetime.
|
||||
func (ps pricedSyscalls) VerifyPoSt(vi abi.WindowPoStVerifyInfo) error {
|
||||
ps.chargeGas(ps.pl.OnVerifyPost(vi))
|
||||
defer ps.chargeGas(gasOnActorExec)
|
||||
|
||||
return ps.under.VerifyPoSt(vi)
|
||||
}
|
||||
|
||||
@@ -182,19 +192,21 @@ func (ps pricedSyscalls) VerifyPoSt(vi abi.WindowPoStVerifyInfo) error {
|
||||
// Returns nil and an error if the headers don't prove a fault.
|
||||
func (ps pricedSyscalls) VerifyConsensusFault(h1 []byte, h2 []byte, extra []byte) (*runtime.ConsensusFault, error) {
|
||||
ps.chargeGas(ps.pl.OnVerifyConsensusFault())
|
||||
defer ps.chargeGas(gasOnActorExec)
|
||||
|
||||
return ps.under.VerifyConsensusFault(h1, h2, extra)
|
||||
}
|
||||
|
||||
func (ps pricedSyscalls) BatchVerifySeals(inp map[address.Address][]abi.SealVerifyInfo) (map[address.Address][]bool, error) {
|
||||
var gasChargeSum GasCharge
|
||||
gasChargeSum.Name = "BatchVerifySeals"
|
||||
ps.chargeGas(gasChargeSum) // TODO: this is only called by the cron actor. Should we even charge gas?
|
||||
|
||||
count := int64(0)
|
||||
for _, svis := range inp {
|
||||
for _, svi := range svis {
|
||||
ch := ps.pl.OnVerifySeal(svi)
|
||||
ps.chargeGas(newGasCharge("BatchVerifySingle", 0, 0).WithVirtual(ch.VirtualCompute+ch.ComputeGas, 0))
|
||||
}
|
||||
count += int64(len(svis))
|
||||
}
|
||||
gasChargeSum = gasChargeSum.WithExtra(count).WithVirtual(129778623*count+716683250, 0)
|
||||
ps.chargeGas(gasChargeSum) // TODO: this is only called by the cron actor. Should we even charge gas?
|
||||
defer ps.chargeGas(gasOnActorExec)
|
||||
|
||||
return ps.under.BatchVerifySeals(inp)
|
||||
}
|
||||
|
||||
+44
-12
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
"github.com/filecoin-project/specs-actors/actors/crypto"
|
||||
)
|
||||
@@ -86,39 +87,57 @@ var _ Pricelist = (*pricelistV0)(nil)
|
||||
|
||||
// OnChainMessage returns the gas used for storing a message of a given size in the chain.
|
||||
func (pl *pricelistV0) OnChainMessage(msgSize int) GasCharge {
|
||||
return newGasCharge("OnChainMessage", 0, pl.onChainMessageBase+pl.onChainMessagePerByte*int64(msgSize))
|
||||
return newGasCharge("OnChainMessage", 0, pl.onChainMessageBase+pl.onChainMessagePerByte*int64(msgSize)).WithVirtual(77302, 0)
|
||||
}
|
||||
|
||||
// OnChainReturnValue returns the gas used for storing the response of a message in the chain.
|
||||
func (pl *pricelistV0) OnChainReturnValue(dataSize int) GasCharge {
|
||||
return newGasCharge("OnChainReturnValue", 0, int64(dataSize)*pl.onChainReturnValuePerByte)
|
||||
return newGasCharge("OnChainReturnValue", 0, int64(dataSize)*pl.onChainReturnValuePerByte).WithVirtual(107294, 0)
|
||||
}
|
||||
|
||||
// OnMethodInvocation returns the gas used when invoking a method.
|
||||
func (pl *pricelistV0) OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) GasCharge {
|
||||
ret := pl.sendBase
|
||||
extra := ""
|
||||
virtGas := int64(1072944)
|
||||
|
||||
if value != abi.NewTokenAmount(0) {
|
||||
// TODO: fix this, it is comparing pointers instead of values
|
||||
// see vv
|
||||
ret += pl.sendTransferFunds
|
||||
}
|
||||
if big.Cmp(value, abi.NewTokenAmount(0)) != 0 {
|
||||
virtGas += 497495
|
||||
if methodNum == builtin.MethodSend {
|
||||
// transfer only
|
||||
virtGas += 973940
|
||||
}
|
||||
extra += "t"
|
||||
}
|
||||
if methodNum != builtin.MethodSend {
|
||||
ret += pl.sendInvokeMethod
|
||||
extra += "i"
|
||||
// running actors is cheaper becase we hand over to actors
|
||||
virtGas += -295779
|
||||
}
|
||||
return newGasCharge("OnMethodInvocation", ret, 0).WithVirtual(ret*15000, 0)
|
||||
return newGasCharge("OnMethodInvocation", ret, 0).WithVirtual(virtGas, 0).WithExtra(extra)
|
||||
}
|
||||
|
||||
// OnIpldGet returns the gas used for storing an object
|
||||
func (pl *pricelistV0) OnIpldGet(dataSize int) GasCharge {
|
||||
return newGasCharge("OnIpldGet", pl.ipldGetBase+int64(dataSize)*pl.ipldGetPerByte, 0).WithExtra(dataSize).WithVirtual(pl.ipldGetBase*13750+(pl.ipldGetPerByte*100), 0)
|
||||
return newGasCharge("OnIpldGet", pl.ipldGetBase+int64(dataSize)*pl.ipldGetPerByte, 0).
|
||||
WithExtra(dataSize).WithVirtual(433685, 0)
|
||||
}
|
||||
|
||||
// OnIpldPut returns the gas used for storing an object
|
||||
func (pl *pricelistV0) OnIpldPut(dataSize int) GasCharge {
|
||||
return newGasCharge("OnIpldPut", pl.ipldPutBase, int64(dataSize)*pl.ipldPutPerByte).WithExtra(dataSize).WithVirtual(pl.ipldPutBase*8700+(pl.ipldPutPerByte*100), 0)
|
||||
return newGasCharge("OnIpldPut", pl.ipldPutBase, int64(dataSize)*pl.ipldPutPerByte).
|
||||
WithExtra(dataSize).WithVirtual(88970, 0)
|
||||
}
|
||||
|
||||
// OnCreateActor returns the gas used for creating an actor
|
||||
func (pl *pricelistV0) OnCreateActor() GasCharge {
|
||||
return newGasCharge("OnCreateActor", pl.createActorBase, pl.createActorExtra)
|
||||
return newGasCharge("OnCreateActor", pl.createActorBase, pl.createActorExtra).WithVirtual(65636, 0)
|
||||
}
|
||||
|
||||
// OnDeleteActor returns the gas used for deleting an actor
|
||||
@@ -127,39 +146,52 @@ func (pl *pricelistV0) OnDeleteActor() GasCharge {
|
||||
}
|
||||
|
||||
// OnVerifySignature
|
||||
|
||||
func (pl *pricelistV0) OnVerifySignature(sigType crypto.SigType, planTextSize int) (GasCharge, error) {
|
||||
costFn, ok := pl.verifySignature[sigType]
|
||||
if !ok {
|
||||
return GasCharge{}, fmt.Errorf("cost function for signature type %d not supported", sigType)
|
||||
}
|
||||
sigName, _ := sigType.Name()
|
||||
return newGasCharge("OnVerifySignature", costFn(int64(planTextSize)), 0).WithExtra(sigName), nil
|
||||
virtGas := int64(0)
|
||||
switch sigType {
|
||||
case crypto.SigTypeBLS:
|
||||
virtGas = 220138570
|
||||
case crypto.SigTypeSecp256k1:
|
||||
virtGas = 7053730
|
||||
}
|
||||
|
||||
return newGasCharge("OnVerifySignature", costFn(int64(planTextSize)), 0).
|
||||
WithExtra(map[string]interface{}{
|
||||
"type": sigName,
|
||||
"size": planTextSize,
|
||||
}).WithVirtual(virtGas, 0), nil
|
||||
}
|
||||
|
||||
// OnHashing
|
||||
func (pl *pricelistV0) OnHashing(dataSize int) GasCharge {
|
||||
return newGasCharge("OnHashing", pl.hashingBase+int64(dataSize)*pl.hashingPerByte, 0)
|
||||
return newGasCharge("OnHashing", pl.hashingBase+int64(dataSize)*pl.hashingPerByte, 0).WithExtra(dataSize).WithVirtual(77300, 0)
|
||||
}
|
||||
|
||||
// OnComputeUnsealedSectorCid
|
||||
func (pl *pricelistV0) OnComputeUnsealedSectorCid(proofType abi.RegisteredSealProof, pieces []abi.PieceInfo) GasCharge {
|
||||
// TODO: this needs more cost tunning, check with @lotus
|
||||
return newGasCharge("OnComputeUnsealedSectorCid", pl.computeUnsealedSectorCidBase, 0).WithVirtual(pl.computeUnsealedSectorCidBase*24500, 0)
|
||||
return newGasCharge("OnComputeUnsealedSectorCid", pl.computeUnsealedSectorCidBase, 0).WithVirtual(382370, 0)
|
||||
}
|
||||
|
||||
// OnVerifySeal
|
||||
func (pl *pricelistV0) OnVerifySeal(info abi.SealVerifyInfo) GasCharge {
|
||||
// TODO: this needs more cost tunning, check with @lotus
|
||||
return newGasCharge("OnVerifySeal", pl.verifySealBase, 0).WithVirtual(pl.verifySealBase*177500, 0)
|
||||
return newGasCharge("OnVerifySeal", pl.verifySealBase, 0).WithVirtual(199954003, 0)
|
||||
}
|
||||
|
||||
// OnVerifyPost
|
||||
func (pl *pricelistV0) OnVerifyPost(info abi.WindowPoStVerifyInfo) GasCharge {
|
||||
// TODO: this needs more cost tunning, check with @lotus
|
||||
return newGasCharge("OnVerifyPost", pl.verifyPostBase, 0)
|
||||
return newGasCharge("OnVerifyPost", pl.verifyPostBase, 0).WithVirtual(2629471704, 0).WithExtra(len(info.ChallengedSectors))
|
||||
}
|
||||
|
||||
// OnVerifyConsensusFault
|
||||
func (pl *pricelistV0) OnVerifyConsensusFault() GasCharge {
|
||||
return newGasCharge("OnVerifyConsensusFault", pl.verifyConsensusFault, 0)
|
||||
return newGasCharge("OnVerifyConsensusFault", pl.verifyConsensusFault, 0).WithVirtual(551935, 0)
|
||||
}
|
||||
|
||||
+5
-3
@@ -264,6 +264,7 @@ func (rt *Runtime) CreateActor(codeID cid.Cid, address address.Address) {
|
||||
if err != nil {
|
||||
panic(aerrors.Fatalf("creating actor entry: %v", err))
|
||||
}
|
||||
_ = rt.chargeGasSafe(gasOnActorExec)
|
||||
}
|
||||
|
||||
func (rt *Runtime) DeleteActor(addr address.Address) {
|
||||
@@ -284,10 +285,10 @@ func (rt *Runtime) DeleteActor(addr address.Address) {
|
||||
if err := rt.state.DeleteActor(rt.Message().Receiver()); err != nil {
|
||||
panic(aerrors.Fatalf("failed to delete actor: %s", err))
|
||||
}
|
||||
_ = rt.chargeGasSafe(gasOnActorExec)
|
||||
}
|
||||
|
||||
func (rt *Runtime) Syscalls() vmr.Syscalls {
|
||||
// TODO: Make sure this is wrapped in something that charges gas for each of the calls
|
||||
return rt.sys
|
||||
}
|
||||
|
||||
@@ -312,7 +313,7 @@ func (rt *Runtime) Context() context.Context {
|
||||
}
|
||||
|
||||
func (rt *Runtime) Abortf(code exitcode.ExitCode, msg string, args ...interface{}) {
|
||||
log.Warnf("Abortf: ", fmt.Sprintf(msg, args...))
|
||||
log.Warnf("Abortf: " + fmt.Sprintf(msg, args...))
|
||||
panic(aerrors.NewfSkip(2, code, msg, args...))
|
||||
}
|
||||
|
||||
@@ -367,6 +368,7 @@ func (rt *Runtime) Send(to address.Address, method abi.MethodNum, m vmr.CBORMars
|
||||
log.Warnf("vmctx send failed: to: %s, method: %d: ret: %d, err: %s", to, method, ret, err)
|
||||
return nil, err.RetCode()
|
||||
}
|
||||
_ = rt.chargeGasSafe(gasOnActorExec)
|
||||
return &dumbWrapperType{ret}, 0
|
||||
}
|
||||
|
||||
@@ -408,7 +410,7 @@ func (rt *Runtime) internalSend(from, to address.Address, method abi.MethodNum,
|
||||
if subrt != nil {
|
||||
rt.numActorsCreated = subrt.numActorsCreated
|
||||
}
|
||||
rt.executionTrace.Subcalls = append(rt.executionTrace.Subcalls, subrt.executionTrace) //&er)
|
||||
rt.executionTrace.Subcalls = append(rt.executionTrace.Subcalls, subrt.executionTrace)
|
||||
return ret, errSend
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
)
|
||||
|
||||
var log = logging.Logger("vm")
|
||||
var gasOnActorExec = newGasCharge("OnActorExec", 0, 0)
|
||||
|
||||
// ResolveToKeyAddr returns the public key type of address (`BLS`/`SECP256K1`) of an account actor identified by `addr`.
|
||||
func ResolveToKeyAddr(state types.StateTree, cst cbor.IpldStore, addr address.Address) (address.Address, aerrors.ActorError) {
|
||||
@@ -68,11 +69,13 @@ type gasChargingBlocks struct {
|
||||
}
|
||||
|
||||
func (bs *gasChargingBlocks) Get(c cid.Cid) (block.Block, error) {
|
||||
bs.chargeGas(newGasCharge("OnIpldGetStart", 0, 0))
|
||||
blk, err := bs.under.Get(c)
|
||||
if err != nil {
|
||||
return nil, aerrors.Escalate(err, "failed to get block from blockstore")
|
||||
}
|
||||
bs.chargeGas(bs.pricelist.OnIpldGet(len(blk.RawData())))
|
||||
bs.chargeGas(gasOnActorExec)
|
||||
|
||||
return blk, nil
|
||||
}
|
||||
@@ -83,6 +86,7 @@ func (bs *gasChargingBlocks) Put(blk block.Block) error {
|
||||
if err := bs.under.Put(blk); err != nil {
|
||||
return aerrors.Escalate(err, "failed to write data to disk")
|
||||
}
|
||||
bs.chargeGas(gasOnActorExec)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -231,7 +235,9 @@ func (vm *VM) send(ctx context.Context, msg *types.Message, parent *Runtime,
|
||||
|
||||
if msg.Method != 0 {
|
||||
var ret []byte
|
||||
_ = rt.chargeGasSafe(gasOnActorExec)
|
||||
ret, err := vm.Invoke(toActor, rt, msg.Method, msg.Params)
|
||||
_ = rt.chargeGasSafe(newGasCharge("OnActorExecDone", 0, 0))
|
||||
return ret, err
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
+1
-6
@@ -742,12 +742,7 @@ var stateReadStateCmd = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
act, err := api.StateGetActor(ctx, addr, ts.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
as, err := api.StateReadState(ctx, act, ts.Key())
|
||||
as, err := api.StateReadState(ctx, addr, ts.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+1
-1
@@ -186,7 +186,7 @@ func SyncWait(ctx context.Context, napi api.FullNode) error {
|
||||
|
||||
fmt.Printf("\r\x1b[2KWorker %d: Target: %s\tState: %s\tHeight: %d", working, target, chain.SyncStageString(ss.Stage), ss.Height)
|
||||
|
||||
if time.Now().Unix()-int64(head.MinTimestamp()) < build.BlockDelay {
|
||||
if time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs) {
|
||||
fmt.Println("\nDone!")
|
||||
return nil
|
||||
}
|
||||
|
||||
+377
-48
@@ -1,11 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
@@ -119,14 +123,22 @@ var importBenchCmd = &cli.Command{
|
||||
ts = next
|
||||
}
|
||||
|
||||
out := make([]TipSetExec, 0, len(tschain))
|
||||
ibj, err := os.Create("import-bench.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ibj.Close() //nolint:errcheck
|
||||
|
||||
enc := json.NewEncoder(ibj)
|
||||
|
||||
var lastTse *TipSetExec
|
||||
|
||||
lastState := tschain[len(tschain)-1].ParentState()
|
||||
for i := len(tschain) - 2; i >= 0; i-- {
|
||||
cur := tschain[i]
|
||||
log.Infof("computing state (height: %d, ts=%s)", cur.Height(), cur.Cids())
|
||||
if cur.ParentState() != lastState {
|
||||
lastTrace := out[len(out)-1].Trace
|
||||
lastTrace := lastTse.Trace
|
||||
d, err := json.MarshalIndent(lastTrace, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -140,31 +152,41 @@ var importBenchCmd = &cli.Command{
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out = append(out, TipSetExec{
|
||||
stripCallers(trace)
|
||||
|
||||
lastTse = &TipSetExec{
|
||||
TipSet: cur.Key(),
|
||||
Trace: trace,
|
||||
Duration: time.Since(start),
|
||||
})
|
||||
}
|
||||
lastState = st
|
||||
if err := enc.Encode(lastTse); err != nil {
|
||||
return xerrors.Errorf("failed to write out tipsetexec: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
pprof.StopCPUProfile()
|
||||
|
||||
ibj, err := os.Create("import-bench.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ibj.Close() //nolint:errcheck
|
||||
|
||||
if err := json.NewEncoder(ibj).Encode(out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
func walkExecutionTrace(et *types.ExecutionTrace) {
|
||||
for _, gc := range et.GasCharges {
|
||||
gc.Callers = nil
|
||||
}
|
||||
for _, sub := range et.Subcalls {
|
||||
walkExecutionTrace(&sub) //nolint:scopelint,gosec
|
||||
}
|
||||
}
|
||||
|
||||
func stripCallers(trace []*api.InvocResult) {
|
||||
for _, t := range trace {
|
||||
walkExecutionTrace(&t.ExecutionTrace)
|
||||
}
|
||||
}
|
||||
|
||||
type Invocation struct {
|
||||
TipSet types.TipSetKey
|
||||
Invoc *api.InvocResult
|
||||
@@ -207,19 +229,222 @@ func compStats(vals []float64) (float64, float64) {
|
||||
return av, math.Sqrt(varsum / float64(len(vals)))
|
||||
}
|
||||
|
||||
func tallyGasCharges(charges map[string][]float64, et *types.ExecutionTrace) {
|
||||
for _, gc := range et.GasCharges {
|
||||
type stats struct {
|
||||
timeTaken meanVar
|
||||
gasRatio meanVar
|
||||
|
||||
compGas := gc.ComputeGas + gc.VirtualComputeGas
|
||||
ratio := float64(compGas) / float64(gc.TimeTaken.Nanoseconds())
|
||||
extraCovar *covar
|
||||
}
|
||||
|
||||
charges[gc.Name] = append(charges[gc.Name], 1/(ratio/GasPerNs))
|
||||
//fmt.Printf("%s: %d, %s: %0.2f\n", gc.Name, compGas, gc.TimeTaken, 1/(ratio/GasPerNs))
|
||||
for _, sub := range et.Subcalls {
|
||||
tallyGasCharges(charges, &sub)
|
||||
}
|
||||
type covar struct {
|
||||
meanX float64
|
||||
meanY float64
|
||||
c float64
|
||||
n float64
|
||||
m2x float64
|
||||
m2y float64
|
||||
}
|
||||
|
||||
func (cov1 *covar) Covariance() float64 {
|
||||
return cov1.c / (cov1.n - 1)
|
||||
}
|
||||
|
||||
func (cov1 *covar) VarianceX() float64 {
|
||||
return cov1.m2x / (cov1.n - 1)
|
||||
}
|
||||
|
||||
func (v1 *covar) StddevX() float64 {
|
||||
return math.Sqrt(v1.VarianceX())
|
||||
}
|
||||
|
||||
func (cov1 *covar) VarianceY() float64 {
|
||||
return cov1.m2y / (cov1.n - 1)
|
||||
}
|
||||
|
||||
func (v1 *covar) StddevY() float64 {
|
||||
return math.Sqrt(v1.VarianceY())
|
||||
}
|
||||
|
||||
func (cov1 *covar) AddPoint(x, y float64) {
|
||||
cov1.n += 1
|
||||
|
||||
dx := x - cov1.meanX
|
||||
cov1.meanX += dx / cov1.n
|
||||
dx2 := x - cov1.meanX
|
||||
cov1.m2x += dx * dx2
|
||||
|
||||
dy := y - cov1.meanY
|
||||
cov1.meanY += dy / cov1.n
|
||||
dy2 := y - cov1.meanY
|
||||
cov1.m2y += dy * dy2
|
||||
|
||||
cov1.c += dx * dy
|
||||
}
|
||||
|
||||
func (cov1 *covar) Combine(cov2 *covar) {
|
||||
if cov1.n == 0 {
|
||||
*cov1 = *cov2
|
||||
return
|
||||
}
|
||||
if cov2.n == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if cov1.n == 1 {
|
||||
cpy := *cov2
|
||||
cpy.AddPoint(cov2.meanX, cov2.meanY)
|
||||
*cov1 = cpy
|
||||
return
|
||||
}
|
||||
if cov2.n == 1 {
|
||||
cov1.AddPoint(cov2.meanX, cov2.meanY)
|
||||
}
|
||||
|
||||
out := covar{}
|
||||
out.n = cov1.n + cov2.n
|
||||
|
||||
dx := cov1.meanX - cov2.meanX
|
||||
out.meanX = cov1.meanX - dx*cov2.n/out.n
|
||||
out.m2x = cov1.m2x + cov2.m2x + dx*dx*cov1.n*cov2.n/out.n
|
||||
|
||||
dy := cov1.meanY - cov2.meanY
|
||||
out.meanY = cov1.meanY - dy*cov2.n/out.n
|
||||
out.m2y = cov1.m2y + cov2.m2y + dy*dy*cov1.n*cov2.n/out.n
|
||||
|
||||
out.c = cov1.c + cov2.c + dx*dy*cov1.n*cov2.n/out.n
|
||||
*cov1 = out
|
||||
}
|
||||
|
||||
func (cov1 *covar) A() float64 {
|
||||
return cov1.Covariance() / cov1.VarianceX()
|
||||
}
|
||||
func (cov1 *covar) B() float64 {
|
||||
return cov1.meanY - cov1.meanX*cov1.A()
|
||||
}
|
||||
func (cov1 *covar) Correl() float64 {
|
||||
return cov1.Covariance() / cov1.StddevX() / cov1.StddevY()
|
||||
}
|
||||
|
||||
type meanVar struct {
|
||||
n float64
|
||||
mean float64
|
||||
m2 float64
|
||||
}
|
||||
|
||||
func (v1 *meanVar) AddPoint(value float64) {
|
||||
// based on https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm
|
||||
v1.n += 1
|
||||
delta := value - v1.mean
|
||||
v1.mean += delta / v1.n
|
||||
delta2 := value - v1.mean
|
||||
v1.m2 += delta * delta2
|
||||
}
|
||||
|
||||
func (v1 *meanVar) Variance() float64 {
|
||||
return v1.m2 / (v1.n - 1)
|
||||
}
|
||||
func (v1 *meanVar) Mean() float64 {
|
||||
return v1.mean
|
||||
}
|
||||
func (v1 *meanVar) Stddev() float64 {
|
||||
return math.Sqrt(v1.Variance())
|
||||
}
|
||||
|
||||
func (v1 *meanVar) Combine(v2 *meanVar) {
|
||||
if v1.n == 0 {
|
||||
*v1 = *v2
|
||||
return
|
||||
}
|
||||
if v2.n == 0 {
|
||||
return
|
||||
}
|
||||
if v1.n == 1 {
|
||||
cpy := *v2
|
||||
cpy.AddPoint(v1.mean)
|
||||
*v1 = cpy
|
||||
return
|
||||
}
|
||||
if v2.n == 1 {
|
||||
v1.AddPoint(v2.mean)
|
||||
return
|
||||
}
|
||||
|
||||
newCount := v1.n + v2.n
|
||||
delta := v2.mean - v1.mean
|
||||
meanDelta := delta * v2.n / newCount
|
||||
m2 := v1.m2 + v2.m2 + delta*meanDelta*v1.n
|
||||
v1.n = newCount
|
||||
v1.mean += meanDelta
|
||||
v1.m2 = m2
|
||||
}
|
||||
|
||||
func getExtras(ex interface{}) (*string, *float64) {
|
||||
if t, ok := ex.(string); ok {
|
||||
return &t, nil
|
||||
}
|
||||
if size, ok := ex.(float64); ok {
|
||||
return nil, &size
|
||||
}
|
||||
if exMap, ok := ex.(map[string]interface{}); ok {
|
||||
t, tok := exMap["type"].(string)
|
||||
size, sok := exMap["size"].(float64)
|
||||
if tok && sok {
|
||||
return &t, &size
|
||||
}
|
||||
if tok {
|
||||
return &t, nil
|
||||
}
|
||||
if sok {
|
||||
return nil, &size
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func tallyGasCharges(charges map[string]*stats, et types.ExecutionTrace) {
|
||||
for i, gc := range et.GasCharges {
|
||||
name := gc.Name
|
||||
if name == "OnIpldGetStart" {
|
||||
continue
|
||||
}
|
||||
tt := float64(gc.TimeTaken.Nanoseconds())
|
||||
if name == "OnIpldGet" {
|
||||
prev := et.GasCharges[i-1]
|
||||
if prev.Name != "OnIpldGetStart" {
|
||||
log.Warn("OnIpldGet without OnIpldGetStart")
|
||||
}
|
||||
tt += float64(prev.TimeTaken.Nanoseconds())
|
||||
}
|
||||
eType, eSize := getExtras(gc.Extra)
|
||||
if eType != nil {
|
||||
name += "-" + *eType
|
||||
}
|
||||
compGas := gc.VirtualComputeGas
|
||||
if compGas == 0 {
|
||||
compGas = 1
|
||||
}
|
||||
s := charges[name]
|
||||
if s == nil {
|
||||
s = new(stats)
|
||||
charges[name] = s
|
||||
}
|
||||
|
||||
if eSize != nil {
|
||||
if s.extraCovar == nil {
|
||||
s.extraCovar = &covar{}
|
||||
}
|
||||
s.extraCovar.AddPoint(*eSize, tt)
|
||||
}
|
||||
|
||||
s.timeTaken.AddPoint(tt)
|
||||
|
||||
ratio := tt / float64(compGas) * GasPerNs
|
||||
s.gasRatio.AddPoint(ratio)
|
||||
}
|
||||
for _, sub := range et.Subcalls {
|
||||
tallyGasCharges(charges, sub)
|
||||
}
|
||||
}
|
||||
|
||||
var importAnalyzeCmd = &cli.Command{
|
||||
@@ -230,50 +455,143 @@ var importAnalyzeCmd = &cli.Command{
|
||||
return nil
|
||||
}
|
||||
|
||||
go func() {
|
||||
http.ListenAndServe("localhost:6060", nil)
|
||||
}()
|
||||
|
||||
fi, err := os.Open(cctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var results []TipSetExec
|
||||
if err := json.NewDecoder(fi).Decode(&results); err != nil {
|
||||
return err
|
||||
const nWorkers = 16
|
||||
jsonIn := make(chan []byte, 2*nWorkers)
|
||||
type result struct {
|
||||
totalTime time.Duration
|
||||
chargeStats map[string]*stats
|
||||
expensiveInvocs []Invocation
|
||||
}
|
||||
|
||||
chargeDeltas := make(map[string][]float64)
|
||||
results := make(chan result, nWorkers)
|
||||
|
||||
for i := 0; i < nWorkers; i++ {
|
||||
go func() {
|
||||
chargeStats := make(map[string]*stats)
|
||||
var totalTime time.Duration
|
||||
const invocsKeep = 32
|
||||
var expensiveInvocs = make([]Invocation, 0, 8*invocsKeep)
|
||||
var leastExpensiveInvoc = time.Duration(0)
|
||||
|
||||
for {
|
||||
b, ok := <-jsonIn
|
||||
if !ok {
|
||||
results <- result{
|
||||
totalTime: totalTime,
|
||||
chargeStats: chargeStats,
|
||||
expensiveInvocs: expensiveInvocs,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var tse TipSetExec
|
||||
err := json.Unmarshal(b, &tse)
|
||||
if err != nil {
|
||||
log.Warnf("error unmarshaling tipset: %+v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
totalTime += tse.Duration
|
||||
for _, inv := range tse.Trace {
|
||||
if inv.Duration > leastExpensiveInvoc {
|
||||
expensiveInvocs = append(expensiveInvocs, Invocation{
|
||||
TipSet: tse.TipSet,
|
||||
Invoc: inv,
|
||||
})
|
||||
}
|
||||
|
||||
tallyGasCharges(chargeStats, inv.ExecutionTrace)
|
||||
}
|
||||
if len(expensiveInvocs) > 4*invocsKeep {
|
||||
sort.Slice(expensiveInvocs, func(i, j int) bool {
|
||||
return expensiveInvocs[i].Invoc.Duration > expensiveInvocs[j].Invoc.Duration
|
||||
})
|
||||
leastExpensiveInvoc = expensiveInvocs[len(expensiveInvocs)-1].Invoc.Duration
|
||||
n := 30
|
||||
if len(expensiveInvocs) < n {
|
||||
n = len(expensiveInvocs)
|
||||
}
|
||||
expensiveInvocs = expensiveInvocs[:n]
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var totalTipsets int64
|
||||
reader := bufio.NewReader(fi)
|
||||
for {
|
||||
b, err := reader.ReadBytes('\n')
|
||||
if err != nil && err != io.EOF {
|
||||
if e, ok := err.(*json.SyntaxError); ok {
|
||||
log.Warnf("syntax error at byte offset %d", e.Offset)
|
||||
}
|
||||
return err
|
||||
}
|
||||
totalTipsets++
|
||||
jsonIn <- b
|
||||
fmt.Fprintf(os.Stderr, "\rProcessed %d tipsets", totalTipsets)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
close(jsonIn)
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, "Collecting results\n")
|
||||
|
||||
var invocs []Invocation
|
||||
var totalTime time.Duration
|
||||
for i, r := range results {
|
||||
_ = i
|
||||
totalTime += r.Duration
|
||||
|
||||
for _, inv := range r.Trace {
|
||||
invocs = append(invocs, Invocation{
|
||||
TipSet: r.TipSet,
|
||||
Invoc: inv,
|
||||
})
|
||||
|
||||
cgas, vgas := countGasCosts(&inv.ExecutionTrace)
|
||||
fmt.Printf("Invocation: %d %s: %s %d -> %0.2f\n", inv.Msg.Method, inv.Msg.To, inv.Duration, cgas+vgas, float64(GasPerNs*inv.Duration.Nanoseconds())/float64(cgas+vgas))
|
||||
|
||||
tallyGasCharges(chargeDeltas, &inv.ExecutionTrace)
|
||||
var keys []string
|
||||
var charges = make(map[string]*stats)
|
||||
for i := 0; i < nWorkers; i++ {
|
||||
fmt.Fprintf(os.Stderr, "\rProcessing results from worker %d/%d", i+1, nWorkers)
|
||||
res := <-results
|
||||
invocs = append(invocs, res.expensiveInvocs...)
|
||||
for k, v := range res.chargeStats {
|
||||
s := charges[k]
|
||||
if s == nil {
|
||||
s = new(stats)
|
||||
charges[k] = s
|
||||
}
|
||||
s.timeTaken.Combine(&v.timeTaken)
|
||||
s.gasRatio.Combine(&v.gasRatio)
|
||||
|
||||
if v.extraCovar != nil {
|
||||
if s.extraCovar == nil {
|
||||
s.extraCovar = &covar{}
|
||||
}
|
||||
s.extraCovar.Combine(v.extraCovar)
|
||||
}
|
||||
}
|
||||
totalTime += res.totalTime
|
||||
}
|
||||
|
||||
var keys []string
|
||||
for k := range chargeDeltas {
|
||||
fmt.Fprintf(os.Stderr, "\nCollecting gas keys\n")
|
||||
for k := range charges {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
fmt.Println("Gas Price Deltas")
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
vals := chargeDeltas[k]
|
||||
av, stdev := compStats(vals)
|
||||
|
||||
fmt.Printf("%s: incr by %f (%f)\n", k, av, stdev)
|
||||
s := charges[k]
|
||||
fmt.Printf("%s: incr by %.4f~%.4f; tt %.4f~%.4f\n", k, s.gasRatio.Mean(), s.gasRatio.Stddev(),
|
||||
s.timeTaken.Mean(), s.timeTaken.Stddev())
|
||||
if s.extraCovar != nil {
|
||||
fmt.Printf("\t correll: %.2f, tt = %.2f * extra + %.2f\n", s.extraCovar.Correl(),
|
||||
s.extraCovar.A(), s.extraCovar.B())
|
||||
fmt.Printf("\t covar: %.2f, extra: %.2f~%.2f, tt2: %.2f~%.2f, count %.0f\n",
|
||||
s.extraCovar.Covariance(), s.extraCovar.meanX, s.extraCovar.StddevX(),
|
||||
s.extraCovar.meanY, s.extraCovar.StddevY(), s.extraCovar.n)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(invocs, func(i, j int) bool {
|
||||
@@ -281,9 +599,20 @@ var importAnalyzeCmd = &cli.Command{
|
||||
})
|
||||
|
||||
fmt.Println("Total time: ", totalTime)
|
||||
fmt.Println("Average time per epoch: ", totalTime/time.Duration(len(results)))
|
||||
fmt.Println("Average time per epoch: ", totalTime/time.Duration(totalTipsets))
|
||||
if actorExec, ok := charges["OnActorExec"]; ok {
|
||||
timeInActors := actorExec.timeTaken.Mean() * actorExec.timeTaken.n
|
||||
fmt.Printf("Avarage time per epoch in actors: %s (%.1f%%)\n", time.Duration(timeInActors)/time.Duration(totalTipsets), timeInActors/float64(totalTime)*100)
|
||||
}
|
||||
if actorExecDone, ok := charges["OnActorExecDone"]; ok {
|
||||
timeInActors := actorExecDone.timeTaken.Mean() * actorExecDone.timeTaken.n
|
||||
fmt.Printf("Avarage time per epoch in OnActorExecDone %s (%.1f%%)\n", time.Duration(timeInActors)/time.Duration(totalTipsets), timeInActors/float64(totalTime)*100)
|
||||
}
|
||||
|
||||
n := 30
|
||||
if len(invocs) < n {
|
||||
n = len(invocs)
|
||||
}
|
||||
fmt.Printf("Top %d most expensive calls:\n", n)
|
||||
for i := 0; i < n; i++ {
|
||||
inv := invocs[i].Invoc
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMeanVar(t *testing.T) {
|
||||
N := 16
|
||||
ss := make([]*meanVar, N)
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
for i := 0; i < N; i++ {
|
||||
ss[i] = &meanVar{}
|
||||
maxJ := rng.Intn(1000)
|
||||
for j := 0; j < maxJ; j++ {
|
||||
ss[i].AddPoint(rng.NormFloat64()*5 + 500)
|
||||
}
|
||||
t.Logf("mean: %f, stddev: %f, count %f", ss[i].mean, ss[i].Stddev(), ss[i].n)
|
||||
}
|
||||
out := &meanVar{}
|
||||
for i := 0; i < N; i++ {
|
||||
out.Combine(ss[i])
|
||||
t.Logf("combine: mean: %f, stddev: %f", out.mean, out.Stddev())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCovar(t *testing.T) {
|
||||
N := 16
|
||||
ss := make([]*covar, N)
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
for i := 0; i < N; i++ {
|
||||
ss[i] = &covar{}
|
||||
maxJ := rng.Intn(1000) + 500
|
||||
for j := 0; j < maxJ; j++ {
|
||||
x := rng.NormFloat64()*5 + 500
|
||||
ss[i].AddPoint(x, x*2-1000)
|
||||
}
|
||||
t.Logf("corell: %f, y = %f*x+%f @%.0f", ss[i].Correl(), ss[i].A(), ss[i].B(), ss[i].n)
|
||||
t.Logf("\txVar: %f yVar: %f covar: %f", ss[i].StddevX(), ss[i].StddevY(), ss[i].Covariance())
|
||||
}
|
||||
out := &covar{}
|
||||
for i := 0; i < N; i++ {
|
||||
out.Combine(ss[i])
|
||||
t.Logf("combine: corell: %f, y = %f*x+%f", out.Correl(), out.A(), out.B())
|
||||
t.Logf("\txVar: %f yVar: %f covar: %f", out.StddevX(), out.StddevY(), out.Covariance())
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,9 @@ var log = logging.Logger("chainwatch")
|
||||
|
||||
func main() {
|
||||
_ = logging.SetLogLevel("*", "INFO")
|
||||
if err := logging.SetLogLevel("rpc", "error"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Info("Starting chainwatch")
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ func subMpool(ctx context.Context, api aapi.FullNode, st *storage) {
|
||||
msgs[v.Message.Message.Cid()] = &v.Message.Message
|
||||
}
|
||||
|
||||
log.Infof("Processing %d mpool updates", len(msgs))
|
||||
log.Debugf("Processing %d mpool updates", len(msgs))
|
||||
|
||||
err := st.storeMessages(msgs)
|
||||
if err != nil {
|
||||
|
||||
+327
-59
@@ -1,11 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
miner_spec "github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
"github.com/ipfs/go-cid"
|
||||
_ "github.com/lib/pq"
|
||||
"golang.org/x/xerrors"
|
||||
@@ -18,6 +24,9 @@ type storage struct {
|
||||
db *sql.DB
|
||||
|
||||
headerLk sync.Mutex
|
||||
|
||||
// stateful miner data
|
||||
minerSectors map[cid.Cid]struct{}
|
||||
}
|
||||
|
||||
func openStorage(dbSource string) (*storage, error) {
|
||||
@@ -28,7 +37,10 @@ func openStorage(dbSource string) (*storage, error) {
|
||||
|
||||
db.SetMaxOpenConns(1350)
|
||||
|
||||
st := &storage{db: db}
|
||||
ms := make(map[cid.Cid]struct{})
|
||||
ms[cid.Undef] = struct{}{}
|
||||
|
||||
st := &storage{db: db, minerSectors: ms}
|
||||
|
||||
return st, st.setup()
|
||||
}
|
||||
@@ -118,7 +130,7 @@ create unique index if not exists block_cid_uindex
|
||||
create materialized view if not exists state_heights
|
||||
as select distinct height, parentstateroot from blocks;
|
||||
|
||||
create unique index if not exists state_heights_uindex
|
||||
create index if not exists state_heights_index
|
||||
on state_heights (height);
|
||||
|
||||
create index if not exists state_heights_height_index
|
||||
@@ -252,31 +264,56 @@ create table if not exists receipts
|
||||
|
||||
create index if not exists receipts_msg_state_index
|
||||
on receipts (msg, state);
|
||||
/*
|
||||
create table if not exists miner_heads
|
||||
|
||||
create table if not exists miner_sectors
|
||||
(
|
||||
head text not null,
|
||||
addr text not null,
|
||||
stateroot text not null,
|
||||
sectorset text not null,
|
||||
setsize decimal not null,
|
||||
provingset text not null,
|
||||
provingsize decimal not null,
|
||||
owner text not null,
|
||||
worker text not null,
|
||||
peerid text not null,
|
||||
sectorsize bigint not null,
|
||||
power decimal not null,
|
||||
active bool,
|
||||
ppe bigint not null,
|
||||
slashed_at bigint not null,
|
||||
constraint miner_heads_pk
|
||||
primary key (head, addr)
|
||||
miner_id text not null,
|
||||
sector_id bigint not null,
|
||||
|
||||
activation_epoch bigint not null,
|
||||
expiration_epoch bigint not null,
|
||||
termination_epoch bigint,
|
||||
|
||||
deal_weight text not null,
|
||||
verified_deal_weight text not null,
|
||||
seal_cid text not null,
|
||||
seal_rand_epoch bigint not null,
|
||||
constraint miner_sectors_pk
|
||||
primary key (miner_id, sector_id)
|
||||
);
|
||||
|
||||
create index if not exists miner_heads_stateroot_index
|
||||
on miner_heads (stateroot);
|
||||
create index if not exists miner_sectors_miner_sectorid_index
|
||||
on miner_sectors (miner_id, sector_id);
|
||||
|
||||
create table if not exists miner_info
|
||||
(
|
||||
miner_id text not null,
|
||||
owner_addr text not null,
|
||||
worker_addr text not null,
|
||||
peer_id text,
|
||||
sector_size text not null,
|
||||
|
||||
precommit_deposits text not null,
|
||||
locked_funds text not null,
|
||||
next_deadline_process_faults bigint not null,
|
||||
constraint miner_info_pk
|
||||
primary key (miner_id)
|
||||
);
|
||||
|
||||
/* used to tell when a miners sectors (proven-not-yet-expired) changed if the miner_sectors_cid's are different a new sector was added or removed (terminated/expired) */
|
||||
create table if not exists miner_sectors_heads
|
||||
(
|
||||
miner_id text not null,
|
||||
miner_sectors_cid text not null,
|
||||
|
||||
state_root text not null,
|
||||
|
||||
constraint miner_sectors_heads_pk
|
||||
primary key (miner_id,miner_sectors_cid)
|
||||
|
||||
);
|
||||
|
||||
/*
|
||||
create or replace function miner_tips(epoch bigint)
|
||||
returns table (head text,
|
||||
addr text,
|
||||
@@ -456,54 +493,261 @@ func (st *storage) storeActors(actors map[address.Address]map[types.Actor]actorI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *storage) storeMiners(miners map[minerKey]*minerInfo) error {
|
||||
/*tx, err := st.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
type storeSectorsAPI interface {
|
||||
StateMinerSectors(context.Context, address.Address, *abi.BitField, bool, types.TipSetKey) ([]*api.ChainSectorInfo, error)
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`
|
||||
func (st *storage) storeSectors(minerTips map[types.TipSetKey][]*minerStateInfo, sectorApi storeSectorsAPI) error {
|
||||
tx, err := st.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
create temp table mh (like miner_heads excluding constraints) on commit drop;
|
||||
if _, err := tx.Exec(`create temp table ms (like miner_sectors excluding constraints) on commit drop;`); err != nil {
|
||||
return xerrors.Errorf("prep temp: %w", err)
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(`copy ms (miner_id, sector_id, activation_epoch, expiration_epoch, deal_weight, verified_deal_weight, seal_cid, seal_rand_epoch) from STDIN`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
`); err != nil {
|
||||
return xerrors.Errorf("prep temp: %w", err)
|
||||
}
|
||||
for tipset, miners := range minerTips {
|
||||
for _, miner := range miners {
|
||||
sectors, err := sectorApi.StateMinerSectors(context.TODO(), miner.addr, nil, true, tipset)
|
||||
if err != nil {
|
||||
log.Debugw("Failed to load sectors", "tipset", tipset.String(), "miner", miner.addr.String(), "error", err)
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(`copy mh (head, addr, stateroot, sectorset, setsize, provingset, provingsize, owner, worker, peerid, sectorsize, power, ppe) from STDIN`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, i := range miners {
|
||||
if _, err := stmt.Exec(
|
||||
k.act.Head.String(),
|
||||
k.addr.String(),
|
||||
k.stateroot.String(),
|
||||
i.state.Sectors.String(),
|
||||
fmt.Sprint(i.ssize),
|
||||
i.state.ProvingSet.String(),
|
||||
fmt.Sprint(i.psize),
|
||||
i.info.Owner.String(),
|
||||
i.info.Worker.String(),
|
||||
i.info.PeerId.String(),
|
||||
i.info.SectorSize,
|
||||
i.power.String(), // TODO: SPA
|
||||
i.state.PoStState.ProvingPeriodStart,
|
||||
); err != nil {
|
||||
return err
|
||||
for _, sector := range sectors {
|
||||
if _, err := stmt.Exec(
|
||||
miner.addr.String(),
|
||||
uint64(sector.ID),
|
||||
int64(sector.Info.ActivationEpoch),
|
||||
int64(sector.Info.Info.Expiration),
|
||||
sector.Info.DealWeight.String(),
|
||||
sector.Info.VerifiedDealWeight.String(),
|
||||
sector.Info.Info.SealedCID.String(),
|
||||
int64(sector.Info.Info.SealRandEpoch),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := stmt.Close(); err != nil {
|
||||
}
|
||||
|
||||
if err := stmt.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`insert into miner_sectors select * from ms on conflict do nothing `); err != nil {
|
||||
return xerrors.Errorf("actor put: %w", err)
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (st *storage) storeMiners(minerTips map[types.TipSetKey][]*minerStateInfo) error {
|
||||
tx, err := st.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`create temp table mi (like miner_info excluding constraints) on commit drop;`); err != nil {
|
||||
return xerrors.Errorf("prep temp: %w", err)
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(`copy mi (miner_id, owner_addr, worker_addr, peer_id, sector_size, precommit_deposits, locked_funds, next_deadline_process_faults) from STDIN`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for ts, miners := range minerTips {
|
||||
for _, miner := range miners {
|
||||
var pid string
|
||||
if len(miner.info.PeerId) != 0 {
|
||||
peerid, err := peer.IDFromBytes(miner.info.PeerId)
|
||||
if err != nil {
|
||||
// this should "never happen", but if it does we should still store info about the miner.
|
||||
log.Warnw("failed to decode peerID", "peerID (bytes)", miner.info.PeerId, "miner", miner.addr, "tipset", ts.String())
|
||||
} else {
|
||||
pid = peerid.String()
|
||||
}
|
||||
}
|
||||
if _, err := stmt.Exec(
|
||||
miner.addr.String(),
|
||||
miner.info.Owner.String(),
|
||||
miner.info.Worker.String(),
|
||||
pid,
|
||||
miner.info.SectorSize.ShortString(),
|
||||
miner.state.PreCommitDeposits.String(),
|
||||
miner.state.LockedFunds.String(),
|
||||
miner.state.NextDeadlineToProcessFaults,
|
||||
); err != nil {
|
||||
log.Errorw("failed to store miner state", "state", miner.state, "info", miner.info, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if err := stmt.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`insert into miner_info select * from mi on conflict do nothing `); err != nil {
|
||||
return xerrors.Errorf("actor put: %w", err)
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
type minerSectorUpdate struct {
|
||||
minerState *minerStateInfo
|
||||
tskey types.TipSetKey
|
||||
oldSector cid.Cid
|
||||
}
|
||||
|
||||
func (st *storage) storeMinerSectorsHeads(minerTips map[types.TipSetKey][]*minerStateInfo, api api.FullNode) error {
|
||||
tx, err := st.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`create temp table msh (like miner_sectors_heads excluding constraints) on commit drop;`); err != nil {
|
||||
return xerrors.Errorf("prep temp: %w", err)
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(`copy msh (miner_id, miner_sectors_cid, state_root) from STDIN`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var updateMiners []*minerSectorUpdate
|
||||
for tsk, miners := range minerTips {
|
||||
for _, miner := range miners {
|
||||
sectorCID, err := st.getLatestMinerSectorCID(context.TODO(), miner.addr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if sectorCID == cid.Undef {
|
||||
continue
|
||||
}
|
||||
if _, found := st.minerSectors[sectorCID]; !found {
|
||||
// schedule miner table update
|
||||
updateMiners = append(updateMiners, &minerSectorUpdate{
|
||||
minerState: miner,
|
||||
tskey: tsk,
|
||||
oldSector: sectorCID,
|
||||
})
|
||||
}
|
||||
st.minerSectors[sectorCID] = struct{}{}
|
||||
log.Debugw("got sector CID", "miner", miner.addr, "cid", sectorCID.String())
|
||||
if _, err := stmt.Exec(
|
||||
miner.addr.String(),
|
||||
miner.state.Sectors.String(),
|
||||
miner.stateroot.String(),
|
||||
); err != nil {
|
||||
log.Errorw("failed to store miners sectors head", "state", miner.state, "info", miner.info, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if err := stmt.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`insert into miner_sectors_heads select * from msh on conflict do nothing `); err != nil {
|
||||
return xerrors.Errorf("actor put: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
return st.updateMinerSectors(updateMiners, api)
|
||||
}
|
||||
|
||||
type deletedSector struct {
|
||||
deletedSector miner_spec.SectorOnChainInfo
|
||||
miner address.Address
|
||||
tskey types.TipSetKey
|
||||
}
|
||||
|
||||
func (st *storage) updateMinerSectors(miners []*minerSectorUpdate, api api.FullNode) error {
|
||||
log.Info("updating miners constant sector table")
|
||||
var deletedSectors []*deletedSector
|
||||
for _, miner := range miners {
|
||||
s := &apiIpldStore{context.TODO(), api}
|
||||
newSectors, err := adt.AsArray(s, miner.minerState.state.Sectors)
|
||||
if err != nil {
|
||||
log.Warnw("new sectors as array", "error", err, "cid", miner.minerState.state.Sectors)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`insert into miner_heads select * from mh on conflict do nothing `); err != nil {
|
||||
return xerrors.Errorf("actor put: %w", err)
|
||||
oldSectors, err := adt.AsArray(s, miner.oldSector)
|
||||
if err != nil {
|
||||
log.Warnw("old sectors as array", "error", err, "cid", miner.oldSector.String())
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()*/
|
||||
return nil
|
||||
var oldSecInfo miner_spec.SectorOnChainInfo
|
||||
var newSecInfo miner_spec.SectorOnChainInfo
|
||||
// if we cannot find an old sector in the new list then it was removed.
|
||||
if err := oldSectors.ForEach(&oldSecInfo, func(i int64) error {
|
||||
found, err := newSectors.Get(uint64(oldSecInfo.Info.SectorNumber), &newSecInfo)
|
||||
if err != nil {
|
||||
log.Warnw("new sectors get", "error", err)
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
log.Infow("MINER DELETED SECTOR", "miner", miner.minerState.addr.String(), "sector", oldSecInfo.Info.SectorNumber, "tipset", miner.tskey.String())
|
||||
deletedSectors = append(deletedSectors, &deletedSector{
|
||||
deletedSector: oldSecInfo,
|
||||
miner: miner.minerState.addr,
|
||||
tskey: miner.tskey,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Warnw("old sectors foreach", "error", err)
|
||||
return err
|
||||
}
|
||||
if len(deletedSectors) > 0 {
|
||||
log.Infow("Calculated updates", "miner", miner.minerState.addr, "deleted sectors", len(deletedSectors))
|
||||
}
|
||||
}
|
||||
// now we have all the sectors that were removed, update the database
|
||||
tx, err := st.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stmt, err := tx.Prepare(`UPDATE miner_sectors SET termination_epoch=$1 WHERE miner_id=$2 AND sector_id=$3`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, ds := range deletedSectors {
|
||||
ts, err := api.ChainGetTipSet(context.TODO(), ds.tskey)
|
||||
if err != nil {
|
||||
log.Warnw("get tipset", "error", err)
|
||||
return err
|
||||
}
|
||||
// TODO validate this shits right
|
||||
if ts.Height() >= ds.deletedSector.Info.Expiration {
|
||||
// means it expired, do nothing
|
||||
log.Infow("expired sector", "miner", ds.miner.String(), "sector", ds.deletedSector.Info.SectorNumber)
|
||||
continue
|
||||
}
|
||||
log.Infow("terminated sector", "miner", ds.miner.String(), "sector", ds.deletedSector.Info.SectorNumber)
|
||||
// means it was terminated.
|
||||
if _, err := stmt.Exec(int64(ts.Height()), ds.miner.String(), int64(ds.deletedSector.Info.SectorNumber)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := stmt.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer log.Info("update miner sectors complete")
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (st *storage) storeHeaders(bhs map[cid.Cid]*types.BlockHeader, sync bool) error {
|
||||
@@ -1008,3 +1252,27 @@ func (st *storage) refreshViews() error {
|
||||
func (st *storage) close() error {
|
||||
return st.db.Close()
|
||||
}
|
||||
|
||||
func (st *storage) getLatestMinerSectorCID(ctx context.Context, miner address.Address) (cid.Cid, error) {
|
||||
queryStr := fmt.Sprintf(`
|
||||
SELECT miner_sectors_cid
|
||||
FROM miner_sectors_heads
|
||||
LEFT JOIN blocks ON miner_sectors_heads.state_root = blocks.parentstateroot
|
||||
WHERE miner_id = '%s'
|
||||
ORDER BY blocks.height DESC
|
||||
LIMIT 1;
|
||||
`,
|
||||
miner.String())
|
||||
|
||||
var cidstr string
|
||||
err := st.db.QueryRowContext(ctx, queryStr).Scan(&cidstr)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
log.Warnf("no miner with miner_id: %s in table", miner)
|
||||
return cid.Undef, nil
|
||||
case err != nil:
|
||||
return cid.Undef, err
|
||||
default:
|
||||
return cid.Decode(cidstr)
|
||||
}
|
||||
}
|
||||
|
||||
+203
-60
@@ -5,17 +5,21 @@ import (
|
||||
"container/list"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
"time"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/ipfs/go-cid"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/power"
|
||||
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
@@ -49,27 +53,30 @@ func runSyncer(ctx context.Context, api api.FullNode, st *storage, maxBatch int)
|
||||
}()
|
||||
}
|
||||
|
||||
type minerKey struct {
|
||||
type minerStateInfo struct {
|
||||
// common
|
||||
addr address.Address
|
||||
act types.Actor
|
||||
stateroot cid.Cid
|
||||
}
|
||||
|
||||
type minerInfo struct {
|
||||
// miner specific
|
||||
state miner.State
|
||||
info miner.MinerInfo
|
||||
|
||||
power big.Int
|
||||
ssize uint64
|
||||
psize uint64
|
||||
// tracked by power actor
|
||||
rawPower big.Int
|
||||
qalPower big.Int
|
||||
ssize uint64
|
||||
psize uint64
|
||||
}
|
||||
|
||||
type actorInfo struct {
|
||||
stateroot cid.Cid
|
||||
tsKey types.TipSetKey
|
||||
state string
|
||||
}
|
||||
|
||||
func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipSet, maxBatch int) {
|
||||
func syncHead(ctx context.Context, api api.FullNode, st *storage, headTs *types.TipSet, maxBatch int) {
|
||||
var alk sync.Mutex
|
||||
|
||||
log.Infof("Getting synced block list")
|
||||
@@ -78,25 +85,28 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
|
||||
log.Infof("Getting headers / actors")
|
||||
|
||||
// global list of all blocks that need to be synced
|
||||
allToSync := map[cid.Cid]*types.BlockHeader{}
|
||||
// a stack
|
||||
toVisit := list.New()
|
||||
|
||||
for _, header := range ts.Blocks() {
|
||||
for _, header := range headTs.Blocks() {
|
||||
toVisit.PushBack(header)
|
||||
}
|
||||
|
||||
// TODO consider making a db query to check where syncing left off at in the case of a restart and avoid reprocessing
|
||||
// those entries, or write value to file on shutdown
|
||||
// walk the entire chain starting from headTS
|
||||
for toVisit.Len() > 0 {
|
||||
bh := toVisit.Remove(toVisit.Back()).(*types.BlockHeader)
|
||||
|
||||
_, has := hazlist[bh.Cid()]
|
||||
if _, seen := allToSync[bh.Cid()]; seen || has {
|
||||
continue
|
||||
}
|
||||
|
||||
allToSync[bh.Cid()] = bh
|
||||
|
||||
if len(allToSync)%500 == 10 {
|
||||
log.Infof("todo: (%d) %s @%d", len(allToSync), bh.Cid(), bh.Height)
|
||||
log.Debugf("to visit: (%d) %s @%d", len(allToSync), bh.Cid(), bh.Height)
|
||||
}
|
||||
|
||||
if len(bh.Parents) == 0 {
|
||||
@@ -114,30 +124,42 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
}
|
||||
}
|
||||
|
||||
// Main worker loop, this loop runs until all tipse from headTS to genesis have been processed.
|
||||
for len(allToSync) > 0 {
|
||||
// first map is addresses -> common actors states (head, code, balance, nonce)
|
||||
// second map common actor states -> chain state (tipset, stateroot) & unique actor state (deserialization of their head CID) represented as json.
|
||||
actors := map[address.Address]map[types.Actor]actorInfo{}
|
||||
addresses := map[address.Address]address.Address{}
|
||||
|
||||
// map of actor public key address to ID address
|
||||
addressToID := map[address.Address]address.Address{}
|
||||
minH := abi.ChainEpoch(math.MaxInt64)
|
||||
|
||||
// find the blockheader with the lowest height
|
||||
for _, header := range allToSync {
|
||||
if header.Height < minH {
|
||||
minH = header.Height
|
||||
}
|
||||
}
|
||||
|
||||
// toSync maps block cids to their headers and contains all block headers that will be synced in this batch
|
||||
// `maxBatch` is a tunable parameter to control how many blocks we sync per iteration.
|
||||
toSync := map[cid.Cid]*types.BlockHeader{}
|
||||
for c, header := range allToSync {
|
||||
if header.Height < minH+abi.ChainEpoch(maxBatch) {
|
||||
toSync[c] = header
|
||||
addresses[header.Miner] = address.Undef
|
||||
addressToID[header.Miner] = address.Undef
|
||||
}
|
||||
}
|
||||
// remove everything we are syncing this round from the global list of blocks to sync
|
||||
for c := range toSync {
|
||||
delete(allToSync, c)
|
||||
}
|
||||
|
||||
log.Infof("Syncing %d blocks", len(toSync))
|
||||
log.Infow("Starting Sync", "height", minH, "numBlocks", len(toSync), "maxBatch", maxBatch)
|
||||
|
||||
// map of addresses to changed actors
|
||||
var changes map[string]types.Actor
|
||||
// collect all actor state that has changes between block headers
|
||||
paDone := 0
|
||||
parmap.Par(50, parmap.MapArr(toSync), func(bh *types.BlockHeader) {
|
||||
paDone++
|
||||
@@ -146,20 +168,23 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
}
|
||||
|
||||
if len(bh.Parents) == 0 { // genesis case
|
||||
ts, _ := types.NewTipSet([]*types.BlockHeader{bh})
|
||||
aadrs, err := api.StateListActors(ctx, ts.Key())
|
||||
genesisTs, _ := types.NewTipSet([]*types.BlockHeader{bh})
|
||||
aadrs, err := api.StateListActors(ctx, genesisTs.Key())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO suspicious there is not a lot to be gained by doing this in parallel since the genesis state
|
||||
// is unlikely to contain a lot of actors, why not for loop here?
|
||||
parmap.Par(50, aadrs, func(addr address.Address) {
|
||||
act, err := api.StateGetActor(ctx, addr, ts.Key())
|
||||
act, err := api.StateGetActor(ctx, addr, genesisTs.Key())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
ast, err := api.StateReadState(ctx, act, ts.Key())
|
||||
|
||||
ast, err := api.StateReadState(ctx, addr, genesisTs.Key())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
@@ -177,9 +202,10 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
}
|
||||
actors[addr][*act] = actorInfo{
|
||||
stateroot: bh.ParentStateRoot,
|
||||
tsKey: genesisTs.Key(),
|
||||
state: string(state),
|
||||
}
|
||||
addresses[addr] = address.Undef
|
||||
addressToID[addr] = address.Undef
|
||||
alk.Unlock()
|
||||
})
|
||||
|
||||
@@ -192,12 +218,15 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
return
|
||||
}
|
||||
|
||||
changes, err := api.StateChangedActors(ctx, pts.ParentState(), bh.ParentStateRoot)
|
||||
// TODO Does this return actors that have been deleted between states?
|
||||
// collect all actors that had state changes between the blockheader parent-state and its grandparent-state.
|
||||
changes, err = api.StateChangedActors(ctx, pts.ParentState(), bh.ParentStateRoot)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// record the state of all actors that have changed
|
||||
for a, act := range changes {
|
||||
act := act
|
||||
|
||||
@@ -206,11 +235,14 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
ast, err := api.StateReadState(ctx, &act, pts.Key())
|
||||
|
||||
ast, err := api.StateReadState(ctx, addr, pts.Key())
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
state, err := json.Marshal(ast.State)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
@@ -222,15 +254,22 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
if !ok {
|
||||
actors[addr] = map[types.Actor]actorInfo{}
|
||||
}
|
||||
// a change occurred for the actor with address `addr` and state `act` at tipset `pts`.
|
||||
actors[addr][act] = actorInfo{
|
||||
stateroot: bh.ParentStateRoot,
|
||||
state: string(state),
|
||||
tsKey: pts.Key(),
|
||||
}
|
||||
addresses[addr] = address.Undef
|
||||
addressToID[addr] = address.Undef
|
||||
alk.Unlock()
|
||||
}
|
||||
})
|
||||
|
||||
// map of tipset to all miners that had a head-change at that tipset.
|
||||
minerTips := make(map[types.TipSetKey][]*minerStateInfo, len(changes))
|
||||
// heads we've seen, im being paranoid
|
||||
headsSeen := make(map[cid.Cid]struct{}, len(actors))
|
||||
|
||||
log.Infof("Getting messages")
|
||||
|
||||
msgs, incls := fetchMessages(ctx, api, toSync)
|
||||
@@ -238,70 +277,109 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
log.Infof("Resolving addresses")
|
||||
|
||||
for _, message := range msgs {
|
||||
addresses[message.To] = address.Undef
|
||||
addresses[message.From] = address.Undef
|
||||
addressToID[message.To] = address.Undef
|
||||
addressToID[message.From] = address.Undef
|
||||
}
|
||||
|
||||
parmap.Par(50, parmap.KMapArr(addresses), func(addr address.Address) {
|
||||
parmap.Par(50, parmap.KMapArr(addressToID), func(addr address.Address) {
|
||||
// FIXME: cannot use EmptyTSK here since actorID's can change during reorgs, need to use the corresponding tipset.
|
||||
// TODO: figure out a way to get the corresponding tipset...
|
||||
raddr, err := api.StateLookupID(ctx, addr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
return
|
||||
}
|
||||
alk.Lock()
|
||||
addresses[addr] = raddr
|
||||
addressToID[addr] = raddr
|
||||
alk.Unlock()
|
||||
})
|
||||
|
||||
log.Infof("Getting miner info")
|
||||
|
||||
miners := map[minerKey]*minerInfo{}
|
||||
|
||||
minerChanges := 0
|
||||
for addr, m := range actors {
|
||||
for actor, c := range m {
|
||||
if actor.Code != builtin.StorageMinerActorCodeID {
|
||||
continue
|
||||
}
|
||||
|
||||
miners[minerKey{
|
||||
// only want miner actors with head change events
|
||||
if _, found := headsSeen[actor.Head]; found {
|
||||
continue
|
||||
}
|
||||
minerChanges++
|
||||
|
||||
minerTips[c.tsKey] = append(minerTips[c.tsKey], &minerStateInfo{
|
||||
addr: addr,
|
||||
act: actor,
|
||||
stateroot: c.stateroot,
|
||||
}] = &minerInfo{}
|
||||
|
||||
state: miner.State{},
|
||||
info: miner.MinerInfo{},
|
||||
|
||||
rawPower: big.Zero(),
|
||||
qalPower: big.Zero(),
|
||||
})
|
||||
|
||||
headsSeen[actor.Head] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
parmap.Par(50, parmap.KVMapArr(miners), func(it func() (minerKey, *minerInfo)) {
|
||||
k, info := it()
|
||||
minerProcessingStartedAt := time.Now()
|
||||
log.Infow("Processing miners", "numTips", len(minerTips), "numMinerChanges", minerChanges)
|
||||
// extract the power actor state at each tipset, loop over all miners that changed at said tipset and extract their
|
||||
// claims from the power actor state. This ensures we only fetch the power actors state once for each tipset.
|
||||
parmap.Par(50, parmap.KVMapArr(minerTips), func(it func() (types.TipSetKey, []*minerStateInfo)) {
|
||||
tsKey, minerInfo := it()
|
||||
|
||||
pow, err := api.StateMinerPower(ctx, k.addr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
// Not sure why this would fail, but its probably worth continuing
|
||||
}
|
||||
info.power = pow.MinerPower.QualityAdjPower
|
||||
|
||||
sszs, err := api.StateMinerSectorCount(ctx, k.addr, types.EmptyTSK)
|
||||
// get the power actors claims map
|
||||
mp, err := getPowerActorClaimsMap(ctx, api, tsKey)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
info.psize = sszs.Pset
|
||||
info.ssize = sszs.Sset
|
||||
// Get miner raw and quality power
|
||||
for _, mi := range minerInfo {
|
||||
var claim power.Claim
|
||||
// get miner claim from power actors claim map and store if found, else the miner had no claim at
|
||||
// this tipset
|
||||
found, err := mp.Get(adt.AddrKey(mi.addr), &claim)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
if found {
|
||||
mi.qalPower = claim.QualityAdjPower
|
||||
mi.rawPower = claim.RawBytePower
|
||||
}
|
||||
|
||||
astb, err := api.ChainReadObj(ctx, k.act.Head)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
// Get the miner state info
|
||||
astb, err := api.ChainReadObj(ctx, mi.act.Head)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
if err := mi.state.UnmarshalCBOR(bytes.NewReader(astb)); err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
mi.info = mi.state.Info
|
||||
}
|
||||
|
||||
if err := info.state.UnmarshalCBOR(bytes.NewReader(astb)); err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
info.info = info.state.Info
|
||||
// TODO Get the Sector Count
|
||||
// FIXME this is returning a lot of "address not found" errors, which is strange given that StateChangedActors
|
||||
// retruns all actors that had a state change at tipset `k.tsKey`, maybe its returning deleted miners too??
|
||||
/*
|
||||
sszs, err := api.StateMinerSectorCount(ctx, k.addr, k.tsKey)
|
||||
if err != nil {
|
||||
info.psize = 0
|
||||
info.ssize = 0
|
||||
} else {
|
||||
info.psize = sszs.Pset
|
||||
info.ssize = sszs.Sset
|
||||
}
|
||||
*/
|
||||
})
|
||||
log.Infow("Completed Miner Processing", "duration", time.Since(minerProcessingStartedAt).String(), "processed", minerChanges)
|
||||
|
||||
log.Info("Getting receipts")
|
||||
|
||||
@@ -316,7 +394,7 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
|
||||
log.Info("Storing address mapping")
|
||||
|
||||
if err := st.storeAddressMap(addresses); err != nil {
|
||||
if err := st.storeAddressMap(addressToID); err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
@@ -329,8 +407,21 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
}
|
||||
|
||||
log.Info("Storing miners")
|
||||
if err := st.storeMiners(minerTips); err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := st.storeMiners(miners); err != nil {
|
||||
log.Info("Storing miner sectors")
|
||||
sectorStart := time.Now()
|
||||
if err := st.storeSectors(minerTips, api); err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
log.Infow("Finished storing miner sectors", "duration", time.Since(sectorStart).String())
|
||||
|
||||
log.Info("Storing miner sectors heads")
|
||||
if err := st.storeMinerSectorsHeads(minerTips, api); err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
@@ -361,7 +452,7 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
log.Infof("Get deals")
|
||||
|
||||
// TODO: incremental, gather expired
|
||||
deals, err := api.StateMarketDeals(ctx, ts.Key())
|
||||
deals, err := api.StateMarketDeals(ctx, headTs.Key())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
@@ -451,3 +542,55 @@ func fetchParentReceipts(ctx context.Context, api api.FullNode, toSync map[cid.C
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// load the power actor state clam as an adt.Map at the tipset `ts`.
|
||||
func getPowerActorClaimsMap(ctx context.Context, api api.FullNode, ts types.TipSetKey) (*adt.Map, error) {
|
||||
powerActor, err := api.StateGetActor(ctx, builtin.StoragePowerActorAddr, ts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
powerRaw, err := api.ChainReadObj(ctx, powerActor.Head)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var powerActorState power.State
|
||||
if err := powerActorState.UnmarshalCBOR(bytes.NewReader(powerRaw)); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal power actor state: %w", err)
|
||||
}
|
||||
|
||||
s := &apiIpldStore{ctx, api}
|
||||
return adt.AsMap(s, powerActorState.Claims)
|
||||
}
|
||||
|
||||
// require for AMT and HAMT access
|
||||
// TODO extract this to a common location in lotus and reuse the code
|
||||
type apiIpldStore struct {
|
||||
ctx context.Context
|
||||
api api.FullNode
|
||||
}
|
||||
|
||||
func (ht *apiIpldStore) Context() context.Context {
|
||||
return ht.ctx
|
||||
}
|
||||
|
||||
func (ht *apiIpldStore) Get(ctx context.Context, c cid.Cid, out interface{}) error {
|
||||
raw, err := ht.api.ChainReadObj(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cu, ok := out.(cbg.CBORUnmarshaler)
|
||||
if ok {
|
||||
if err := cu.UnmarshalCBOR(bytes.NewReader(raw)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Object does not implement CBORUnmarshaler: %T", out)
|
||||
}
|
||||
|
||||
func (ht *apiIpldStore) Put(ctx context.Context, v interface{}) (cid.Cid, error) {
|
||||
return cid.Undef, fmt.Errorf("Put is not implemented on apiIpldStore")
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ var watchHeadCmd = &cli.Command{
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "interval",
|
||||
Value: build.BlockDelay,
|
||||
Value: int(build.BlockDelaySecs),
|
||||
Usage: "interval in seconds between chain head checks",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
@@ -72,8 +72,9 @@ var watchHeadCmd = &cli.Command{
|
||||
Usage: "systemd unit name to restart on health check failure",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "api-timeout",
|
||||
Value: build.BlockDelay,
|
||||
Name: "api-timeout",
|
||||
// TODO: this default value seems spurious.
|
||||
Value: int(build.BlockDelaySecs),
|
||||
Usage: "timeout between API retries",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
@@ -236,7 +237,7 @@ func waitForSyncComplete(ctx context.Context, a api.FullNode, r int, t time.Dura
|
||||
return err
|
||||
}
|
||||
|
||||
if time.Now().Unix()-int64(head.MinTimestamp()) < build.BlockDelay {
|
||||
if time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ var genesisAddMinerCmd = &cli.Command{
|
||||
log.Infof("Giving %s some initial balance", miner.Owner)
|
||||
template.Accounts = append(template.Accounts, genesis.Actor{
|
||||
Type: genesis.TAccount,
|
||||
Balance: big.Mul(big.NewInt(50_000_000), big.NewInt(build.FilecoinPrecision)),
|
||||
Balance: big.Mul(big.NewInt(50_000_000), big.NewInt(int64(build.FilecoinPrecision))),
|
||||
Meta: (&genesis.AccountMeta{Owner: miner.Owner}).ActorMeta(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ func PreSeal(maddr address.Address, spt abi.RegisteredSealProof, offset abi.Sect
|
||||
return nil, nil, xerrors.Errorf("commit: %w", err)
|
||||
}
|
||||
|
||||
if err := sb.FinalizeSector(context.TODO(), sid); err != nil {
|
||||
if err := sb.FinalizeSector(context.TODO(), sid, nil); err != nil {
|
||||
return nil, nil, xerrors.Errorf("trim cache: %w", err)
|
||||
}
|
||||
|
||||
|
||||
+315
-34
@@ -1,8 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -11,77 +13,356 @@ import (
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
_ "github.com/filecoin-project/lotus/lib/sigs/bls"
|
||||
_ "github.com/filecoin-project/lotus/lib/sigs/secp"
|
||||
"github.com/libp2p/go-libp2p-core/crypto"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/wallet"
|
||||
"github.com/filecoin-project/lotus/node/modules/lp2p"
|
||||
"github.com/filecoin-project/lotus/node/repo"
|
||||
|
||||
_ "github.com/filecoin-project/lotus/lib/sigs/bls"
|
||||
_ "github.com/filecoin-project/lotus/lib/sigs/secp"
|
||||
)
|
||||
|
||||
type walletInfo struct {
|
||||
var validTypes = []string{wallet.KTBLS, wallet.KTSecp256k1, lp2p.KTLibp2pHost}
|
||||
|
||||
type keyInfoOutput struct {
|
||||
Type string
|
||||
Address string
|
||||
PublicKey string
|
||||
}
|
||||
|
||||
func (wi walletInfo) String() string {
|
||||
bs, _ := json.Marshal(wi)
|
||||
return string(bs)
|
||||
var keyinfoCmd = &cli.Command{
|
||||
Name: "keyinfo",
|
||||
Usage: "work with lotus keyinfo files (wallets and libp2p host keys)",
|
||||
Description: `The subcommands of keyinfo provide helpful tools for working with keyinfo files without
|
||||
having to run the lotus daemon.`,
|
||||
Subcommands: []*cli.Command{
|
||||
keyinfoNewCmd,
|
||||
keyinfoInfoCmd,
|
||||
keyinfoImportCmd,
|
||||
},
|
||||
}
|
||||
|
||||
var keyinfoCmd = &cli.Command{
|
||||
Name: "keyinfo",
|
||||
Description: "decode a keyinfo",
|
||||
var keyinfoImportCmd = &cli.Command{
|
||||
Name: "import",
|
||||
Usage: "import a keyinfo file into a lotus repository",
|
||||
Description: `The import command provides a way to import keyfiles into a lotus repository
|
||||
without running the daemon.
|
||||
|
||||
Note: The LOTUS_PATH directory must be created. This command will not create this directory for you.
|
||||
|
||||
Examples
|
||||
|
||||
env LOTUS_PATH=/var/lib/lotus lotus-shed keyinfo import libp2p-host.keyinfo`,
|
||||
Action: func(cctx *cli.Context) error {
|
||||
flagRepo := cctx.String("repo")
|
||||
|
||||
var input io.Reader
|
||||
if cctx.Args().Len() == 0 {
|
||||
input = os.Stdin
|
||||
} else {
|
||||
var err error
|
||||
input, err = os.Open(cctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
encoded, err := ioutil.ReadAll(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decoded, err := hex.DecodeString(strings.TrimSpace(string(encoded)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var keyInfo types.KeyInfo
|
||||
if err := json.Unmarshal(decoded, &keyInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fsrepo, err := repo.NewFS(flagRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lkrepo, err := fsrepo.Lock(repo.FullNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer lkrepo.Close()
|
||||
|
||||
keystore, err := lkrepo.KeyStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch keyInfo.Type {
|
||||
case lp2p.KTLibp2pHost:
|
||||
if err := keystore.Put(lp2p.KLibp2pHost, keyInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sk, err := crypto.UnmarshalPrivateKey(keyInfo.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerid, err := peer.IDFromPrivateKey(sk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", peerid.String())
|
||||
|
||||
break
|
||||
case wallet.KTSecp256k1:
|
||||
case wallet.KTBLS:
|
||||
w, err := wallet.NewWallet(keystore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addr, err := w.Import(&keyInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", addr.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var keyinfoInfoCmd = &cli.Command{
|
||||
Name: "info",
|
||||
Usage: "print information about a keyinfo file",
|
||||
Description: `The info command prints additional information about a key which can't easily
|
||||
be retrieved by inspecting the file itself.
|
||||
|
||||
The 'format' flag takes a golang text/template template as its value.
|
||||
|
||||
The following fields can be retrived through this command
|
||||
Type
|
||||
Address
|
||||
PublicKey
|
||||
|
||||
The PublicKey value will be printed base64 encoded using golangs StdEncoding
|
||||
|
||||
Examples
|
||||
|
||||
Retreive the address of a lotus wallet
|
||||
lotus-shed keyinfo info --format '{{ .Address }}' wallet.keyinfo
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "format",
|
||||
Value: "{{.Address}}",
|
||||
Usage: "Format to output",
|
||||
Value: "{{ .Type }} {{ .Address }}",
|
||||
Usage: "specify which output columns to print",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
format := cctx.String("format")
|
||||
|
||||
var input io.Reader
|
||||
|
||||
if cctx.Args().Len() == 0 {
|
||||
input = os.Stdin
|
||||
} else {
|
||||
input = strings.NewReader(cctx.Args().First())
|
||||
var err error
|
||||
input, err = os.Open(cctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadAll(input)
|
||||
|
||||
data, err := hex.DecodeString(strings.TrimSpace(string(bytes)))
|
||||
encoded, err := ioutil.ReadAll(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ki types.KeyInfo
|
||||
if err := json.Unmarshal(data, &ki); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := wallet.NewKey(ki)
|
||||
decoded, err := hex.DecodeString(strings.TrimSpace(string(encoded)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bs, err := json.Marshal(key)
|
||||
var keyInfo types.KeyInfo
|
||||
if err := json.Unmarshal(decoded, &keyInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var kio keyInfoOutput
|
||||
|
||||
switch keyInfo.Type {
|
||||
case lp2p.KTLibp2pHost:
|
||||
kio.Type = keyInfo.Type
|
||||
|
||||
sk, err := crypto.UnmarshalPrivateKey(keyInfo.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pk := sk.GetPublic()
|
||||
|
||||
peerid, err := peer.IDFromPrivateKey(sk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkBytes, err := pk.Raw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kio.Address = peerid.String()
|
||||
kio.PublicKey = base64.StdEncoding.EncodeToString(pkBytes)
|
||||
|
||||
break
|
||||
case wallet.KTSecp256k1:
|
||||
case wallet.KTBLS:
|
||||
kio.Type = keyInfo.Type
|
||||
|
||||
key, err := wallet.NewKey(keyInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kio.Address = key.Address.String()
|
||||
kio.PublicKey = base64.StdEncoding.EncodeToString(key.PublicKey)
|
||||
}
|
||||
|
||||
tmpl, err := template.New("output").Parse(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var wi walletInfo
|
||||
if err := json.Unmarshal(bs, &wi); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("").Parse(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tmpl.Execute(os.Stdout, wi)
|
||||
return tmpl.Execute(os.Stdout, kio)
|
||||
},
|
||||
}
|
||||
|
||||
var keyinfoNewCmd = &cli.Command{
|
||||
Name: "new",
|
||||
Usage: "create a new keyinfo file of the provided type",
|
||||
ArgsUsage: "[bls|secp256k1|libp2p-host]",
|
||||
Description: `Keyinfo files are base16 encoded json structures containing a type
|
||||
string value, and a base64 encoded private key.
|
||||
|
||||
Both the bls and secp256k1 keyfiles can be imported into a running lotus daemon using
|
||||
the 'lotus wallet import' command. Or imported to a non-running / unitialized repo using
|
||||
the 'lotus-shed keyinfo import' command. Libp2p host keys can only be imported using lotus-shed
|
||||
as lotus itself does not provide this functionality at the moment.`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "output",
|
||||
Value: "<type>-<addr>.keyinfo",
|
||||
Usage: "output file formt",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "silent",
|
||||
Value: false,
|
||||
Usage: "do not print the address to stdout",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
if !cctx.Args().Present() {
|
||||
return fmt.Errorf("please specify a type to generate")
|
||||
}
|
||||
|
||||
keyType := cctx.Args().First()
|
||||
flagOutput := cctx.String("output")
|
||||
|
||||
if i := SliceIndex(len(validTypes), func(i int) bool {
|
||||
if keyType == validTypes[i] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}); i == -1 {
|
||||
return fmt.Errorf("invalid key type argument provided '%s'", keyType)
|
||||
}
|
||||
|
||||
keystore := wallet.NewMemKeyStore()
|
||||
|
||||
var keyAddr string
|
||||
var keyInfo types.KeyInfo
|
||||
|
||||
switch keyType {
|
||||
case lp2p.KTLibp2pHost:
|
||||
sk, err := lp2p.PrivKey(keystore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ki, err := keystore.Get(lp2p.KLibp2pHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerid, err := peer.IDFromPrivateKey(sk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyAddr = peerid.String()
|
||||
keyInfo = ki
|
||||
|
||||
break
|
||||
case wallet.KTSecp256k1:
|
||||
case wallet.KTBLS:
|
||||
key, err := wallet.GenerateKey(wallet.ActSigType(keyType))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyAddr = key.Address.String()
|
||||
keyInfo = key.KeyInfo
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
filename := flagOutput
|
||||
filename = strings.ReplaceAll(filename, "<addr>", keyAddr)
|
||||
filename = strings.ReplaceAll(filename, "<type>", keyType)
|
||||
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := file.Close(); err != nil {
|
||||
log.Warnf("failed to close output file: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
bytes, err := json.Marshal(keyInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encoded := hex.EncodeToString(bytes)
|
||||
if _, err := file.Write([]byte(encoded)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cctx.Bool("silent") {
|
||||
fmt.Println(keyAddr)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func SliceIndex(length int, fn func(i int) bool) int {
|
||||
for i := 0; i < length; i++ {
|
||||
if fn(i) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ func main() {
|
||||
base16Cmd,
|
||||
bitFieldCmd,
|
||||
keyinfoCmd,
|
||||
peerkeyCmd,
|
||||
noncefix,
|
||||
bigIntParseCmd,
|
||||
staterootStatsCmd,
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/node/modules/lp2p"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
)
|
||||
|
||||
type keystore struct {
|
||||
set bool
|
||||
info types.KeyInfo
|
||||
}
|
||||
|
||||
func (ks *keystore) Put(name string, info types.KeyInfo) error {
|
||||
ks.info = info
|
||||
ks.set = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ks *keystore) Get(name string) (types.KeyInfo, error) {
|
||||
if !ks.set {
|
||||
return types.KeyInfo{}, types.ErrKeyInfoNotFound
|
||||
}
|
||||
|
||||
return ks.info, nil
|
||||
}
|
||||
|
||||
func (ks *keystore) Delete(name string) error {
|
||||
panic("Implement me")
|
||||
}
|
||||
|
||||
func (ks *keystore) List() ([]string, error) {
|
||||
panic("Implement me")
|
||||
}
|
||||
|
||||
var peerkeyCmd = &cli.Command{
|
||||
Name: "peerkey",
|
||||
Description: "create libp2p host key",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "output",
|
||||
Value: "<peerid>.peerkey",
|
||||
Usage: "Output file format",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "silent",
|
||||
Value: false,
|
||||
Usage: "Do not print peerid at end",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
output := cctx.String("output")
|
||||
ks := keystore{}
|
||||
|
||||
sk, err := lp2p.PrivKey(&ks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bs, err := json.Marshal(ks.info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerid, err := peer.IDFromPrivateKey(sk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output = strings.ReplaceAll(output, "<peerid>", peerid.String())
|
||||
|
||||
f, err := os.Create(output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
log.Warnf("failed to close output file: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := f.Write(bs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cctx.Bool("silent") {
|
||||
fmt.Println(peerid.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
)
|
||||
|
||||
var actorCmd = &cli.Command{
|
||||
Name: "actor",
|
||||
Usage: "manipulate the miner actor",
|
||||
Subcommands: []*cli.Command{
|
||||
actorSetAddrsCmd,
|
||||
},
|
||||
}
|
||||
|
||||
var actorSetAddrsCmd = &cli.Command{
|
||||
Name: "set-addrs",
|
||||
Usage: "set addresses that your miner can be publically dialed on",
|
||||
Flags: []cli.Flag{
|
||||
&cli.Int64Flag{
|
||||
Name: "gas-limit",
|
||||
Usage: "set gas limit",
|
||||
Value: 100000,
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
nodeAPI, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
api, acloser, err := lcli.GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer acloser()
|
||||
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
|
||||
var addrs []abi.Multiaddrs
|
||||
for _, a := range cctx.Args().Slice() {
|
||||
maddr, err := ma.NewMultiaddr(a)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse %q as a multiaddr: %w", a, err)
|
||||
}
|
||||
|
||||
addrs = append(addrs, maddr.Bytes())
|
||||
}
|
||||
|
||||
maddr, err := nodeAPI.ActorAddress(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
minfo, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params, err := actors.SerializeParams(&miner.ChangeMultiaddrsParams{NewMultiaddrs: addrs})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gasLimit := cctx.Int64("gas-limit")
|
||||
|
||||
smsg, err := api.MpoolPushMessage(ctx, &types.Message{
|
||||
To: maddr,
|
||||
From: minfo.Worker,
|
||||
Value: types.NewInt(0),
|
||||
GasPrice: types.NewInt(1),
|
||||
GasLimit: gasLimit,
|
||||
Method: 18,
|
||||
Params: params,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Requested multiaddrs change in message %s\n", smsg.Cid())
|
||||
return nil
|
||||
|
||||
},
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/power"
|
||||
sealing "github.com/filecoin-project/storage-fsm"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
@@ -119,16 +120,20 @@ var infoCmd = &cli.Command{
|
||||
faultyPercentage)
|
||||
}
|
||||
|
||||
expWinChance := float64(types.BigMul(qpercI, types.NewInt(build.BlocksPerEpoch)).Int64()) / 1000000
|
||||
if expWinChance > 0 {
|
||||
if expWinChance > 1 {
|
||||
expWinChance = 1
|
||||
}
|
||||
winRate := time.Duration(float64(time.Second*build.BlockDelay) / expWinChance)
|
||||
winPerDay := float64(time.Hour*24) / float64(winRate)
|
||||
if pow.MinerPower.RawBytePower.LessThan(power.ConsensusMinerMinPower) {
|
||||
fmt.Print("Below minimum power threshold, no blocks will be won")
|
||||
} else {
|
||||
expWinChance := float64(types.BigMul(qpercI, types.NewInt(build.BlocksPerEpoch)).Int64()) / 1000000
|
||||
if expWinChance > 0 {
|
||||
if expWinChance > 1 {
|
||||
expWinChance = 1
|
||||
}
|
||||
winRate := time.Duration(float64(time.Second*time.Duration(build.BlockDelaySecs)) / expWinChance)
|
||||
winPerDay := float64(time.Hour*24) / float64(winRate)
|
||||
|
||||
fmt.Print("Expected block win rate: ")
|
||||
color.Blue("%.4f/day (every %s)", winPerDay, winRate.Truncate(time.Second))
|
||||
fmt.Print("Expected block win rate: ")
|
||||
color.Blue("%.4f/day (every %s)", winPerDay, winRate.Truncate(time.Second))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
@@ -22,7 +22,9 @@ func main() {
|
||||
lotuslog.SetupLogLevels()
|
||||
|
||||
local := []*cli.Command{
|
||||
dealsCmd,
|
||||
actorCmd,
|
||||
storageDealsCmd,
|
||||
retrievalDealsCmd,
|
||||
infoCmd,
|
||||
initCmd,
|
||||
rewardsCmd,
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipfs/go-cidutil/cidenc"
|
||||
"github.com/multiformats/go-multibase"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
@@ -20,33 +24,126 @@ import (
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
)
|
||||
|
||||
var enableCmd = &cli.Command{
|
||||
Name: "enable",
|
||||
Usage: "Configure the miner to consider storage deal proposals",
|
||||
Flags: []cli.Flag{},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
var CidBaseFlag = cli.StringFlag{
|
||||
Name: "cid-base",
|
||||
Hidden: true,
|
||||
Value: "base32",
|
||||
Usage: "Multibase encoding used for version 1 CIDs in output.",
|
||||
DefaultText: "base32",
|
||||
}
|
||||
|
||||
return api.DealsSetAcceptingStorageDeals(lcli.DaemonContext(cctx), true)
|
||||
// GetCidEncoder returns an encoder using the `cid-base` flag if provided, or
|
||||
// the default (Base32) encoder if not.
|
||||
func GetCidEncoder(cctx *cli.Context) (cidenc.Encoder, error) {
|
||||
val := cctx.String("cid-base")
|
||||
|
||||
e := cidenc.Encoder{Base: multibase.MustNewEncoder(multibase.Base32)}
|
||||
|
||||
if val != "" {
|
||||
var err error
|
||||
e.Base, err = multibase.EncoderByName(val)
|
||||
if err != nil {
|
||||
return e, err
|
||||
}
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
var storageDealSelectionCmd = &cli.Command{
|
||||
Name: "selection",
|
||||
Usage: "Configure acceptance criteria for storage deal proposals",
|
||||
Subcommands: []*cli.Command{
|
||||
storageDealSelectionShowCmd,
|
||||
storageDealSelectionResetCmd,
|
||||
storageDealSelectionRejectCmd,
|
||||
},
|
||||
}
|
||||
|
||||
var disableCmd = &cli.Command{
|
||||
Name: "disable",
|
||||
Usage: "Configure the miner to reject all storage deal proposals",
|
||||
Flags: []cli.Flag{},
|
||||
var storageDealSelectionShowCmd = &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List storage deal proposal selection criteria",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
smapi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
return api.DealsSetAcceptingStorageDeals(lcli.DaemonContext(cctx), false)
|
||||
onlineOk, err := smapi.DealsConsiderOnlineStorageDeals(lcli.DaemonContext(cctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
offlineOk, err := smapi.DealsConsiderOfflineStorageDeals(lcli.DaemonContext(cctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("considering online storage deals: %t\n", onlineOk)
|
||||
fmt.Printf("considering offline storage deals: %t\n", offlineOk)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var storageDealSelectionResetCmd = &cli.Command{
|
||||
Name: "reset",
|
||||
Usage: "Reset storage deal proposal selection criteria to default values",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
smapi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
err = smapi.DealsSetConsiderOnlineStorageDeals(lcli.DaemonContext(cctx), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = smapi.DealsSetConsiderOfflineStorageDeals(lcli.DaemonContext(cctx), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var storageDealSelectionRejectCmd = &cli.Command{
|
||||
Name: "reject",
|
||||
Usage: "Configure criteria which necessitate automatic rejection",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "online",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "offline",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
smapi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
if cctx.Bool("online") {
|
||||
err = smapi.DealsSetConsiderOnlineStorageDeals(lcli.DaemonContext(cctx), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if cctx.Bool("offline") {
|
||||
err = smapi.DealsSetConsiderOfflineStorageDeals(lcli.DaemonContext(cctx), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -93,7 +190,7 @@ var setAskCmd = &cli.Command{
|
||||
return xerrors.Errorf("cannot parse duration: %w", err)
|
||||
}
|
||||
|
||||
qty := dur.Seconds() / build.BlockDelay
|
||||
qty := dur.Seconds() / float64(build.BlockDelaySecs)
|
||||
|
||||
min, err := units.RAMInBytes(cctx.String("min-piece-size"))
|
||||
if err != nil {
|
||||
@@ -178,7 +275,7 @@ var getAskCmd = &cli.Command{
|
||||
dlt := ask.Expiry - head.Height()
|
||||
rem := "<expired>"
|
||||
if dlt > 0 {
|
||||
rem = (time.Second * time.Duration(dlt*build.BlockDelay)).String()
|
||||
rem = (time.Second * time.Duration(int64(dlt)*int64(build.BlockDelaySecs))).String()
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\t%d\n", ask.Price, types.SizeStr(types.NewInt(uint64(ask.MinPieceSize))), types.SizeStr(types.NewInt(uint64(ask.MaxPieceSize))), ask.Expiry, rem, ask.SeqNo)
|
||||
@@ -187,16 +284,18 @@ var getAskCmd = &cli.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var dealsCmd = &cli.Command{
|
||||
Name: "deals",
|
||||
Usage: "interact with your deals",
|
||||
var storageDealsCmd = &cli.Command{
|
||||
Name: "storage-deals",
|
||||
Usage: "Manage storage deals and related configuration",
|
||||
Subcommands: []*cli.Command{
|
||||
dealsImportDataCmd,
|
||||
dealsListCmd,
|
||||
enableCmd,
|
||||
disableCmd,
|
||||
storageDealSelectionCmd,
|
||||
setAskCmd,
|
||||
getAskCmd,
|
||||
setBlocklistCmd,
|
||||
getBlocklistCmd,
|
||||
resetBlocklistCmd,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -255,3 +354,96 @@ var dealsListCmd = &cli.Command{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var getBlocklistCmd = &cli.Command{
|
||||
Name: "get-blocklist",
|
||||
Usage: "List the contents of the storage miner's piece CID blocklist",
|
||||
Flags: []cli.Flag{
|
||||
&CidBaseFlag,
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
blocklist, err := api.DealsPieceCidBlocklist(lcli.DaemonContext(cctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encoder, err := GetCidEncoder(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for idx := range blocklist {
|
||||
fmt.Println(encoder.Encode(blocklist[idx]))
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var setBlocklistCmd = &cli.Command{
|
||||
Name: "set-blocklist",
|
||||
Usage: "Set the storage miner's list of blocklisted piece CIDs",
|
||||
ArgsUsage: "[<path-of-file-containing-newline-delimited-piece-CIDs> (optional, will read from stdin if omitted)]",
|
||||
Flags: []cli.Flag{},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
if cctx.Args().Present() && cctx.Args().First() != "-" {
|
||||
absPath, err := filepath.Abs(cctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.Open(absPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer file.Close() //nolint:errcheck
|
||||
|
||||
scanner = bufio.NewScanner(file)
|
||||
}
|
||||
|
||||
var blocklist []cid.Cid
|
||||
for scanner.Scan() {
|
||||
decoded, err := cid.Decode(scanner.Text())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
blocklist = append(blocklist, decoded)
|
||||
}
|
||||
|
||||
err = scanner.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return api.DealsSetPieceCidBlocklist(lcli.DaemonContext(cctx), blocklist)
|
||||
},
|
||||
}
|
||||
|
||||
var resetBlocklistCmd = &cli.Command{
|
||||
Name: "reset-blocklist",
|
||||
Usage: "Remove all entries from the storage miner's piece CID blocklist",
|
||||
Flags: []cli.Flag{},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
return api.DealsSetPieceCidBlocklist(lcli.DaemonContext(cctx), []cid.Cid{})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -26,6 +26,75 @@ var provingCmd = &cli.Command{
|
||||
Subcommands: []*cli.Command{
|
||||
provingInfoCmd,
|
||||
provingDeadlinesCmd,
|
||||
provingFaultsCmd,
|
||||
},
|
||||
}
|
||||
|
||||
var provingFaultsCmd = &cli.Command{
|
||||
Name: "faults",
|
||||
Usage: "View the currently known proving faulty sectors information",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
api, acloser, err := lcli.GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer acloser()
|
||||
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
|
||||
maddr, err := nodeApi.ActorAddress(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting actor address: %w", err)
|
||||
}
|
||||
|
||||
var mas miner.State
|
||||
{
|
||||
mact, err := api.StateGetActor(ctx, maddr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rmas, err := api.ChainReadObj(ctx, mact.Head)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mas.UnmarshalCBOR(bytes.NewReader(rmas)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
faults, err := mas.Faults.All(100000000000)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(faults) == 0 {
|
||||
fmt.Println("no faulty sectors")
|
||||
}
|
||||
head, err := api.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting chain head: %w", err)
|
||||
}
|
||||
deadlines, err := api.StateMinerDeadlines(ctx, maddr, head.Key())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting miner deadlines: %w", err)
|
||||
}
|
||||
tw := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0)
|
||||
_, _ = fmt.Fprintln(tw, "deadline\tsectors")
|
||||
for deadline, sectors := range deadlines.Due {
|
||||
intersectSectors, _ := bitfield.IntersectBitField(sectors, mas.Faults)
|
||||
if intersectSectors != nil {
|
||||
allSectors, _ := intersectSectors.All(100000000000)
|
||||
for _, num := range allSectors {
|
||||
_, _ = fmt.Fprintf(tw, "%d\t%d\n", deadline, num)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return tw.Flush()
|
||||
},
|
||||
}
|
||||
|
||||
@@ -142,11 +211,11 @@ var provingInfoCmd = &cli.Command{
|
||||
func epochTime(curr, e abi.ChainEpoch) string {
|
||||
switch {
|
||||
case curr > e:
|
||||
return fmt.Sprintf("%d (%s ago)", e, time.Second*time.Duration(build.BlockDelay*(curr-e)))
|
||||
return fmt.Sprintf("%d (%s ago)", e, time.Second*time.Duration(int64(build.BlockDelaySecs)*int64(curr-e)))
|
||||
case curr == e:
|
||||
return fmt.Sprintf("%d (now)", e)
|
||||
case curr < e:
|
||||
return fmt.Sprintf("%d (in %s)", e, time.Second*time.Duration(build.BlockDelay*(e-curr)))
|
||||
return fmt.Sprintf("%d (in %s)", e, time.Second*time.Duration(int64(build.BlockDelaySecs)*int64(e-curr)))
|
||||
}
|
||||
|
||||
panic("math broke")
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var retrievalDealsCmd = &cli.Command{
|
||||
Name: "retrieval-deals",
|
||||
Usage: "Manage retrieval deals and related configuration",
|
||||
Subcommands: []*cli.Command{
|
||||
retrievalDealSelectionCmd,
|
||||
},
|
||||
}
|
||||
|
||||
var retrievalDealSelectionCmd = &cli.Command{
|
||||
Name: "selection",
|
||||
Usage: "Configure acceptance criteria for retrieval deal proposals",
|
||||
Subcommands: []*cli.Command{
|
||||
retrievalDealSelectionShowCmd,
|
||||
retrievalDealSelectionResetCmd,
|
||||
retrievalDealSelectionRejectCmd,
|
||||
},
|
||||
}
|
||||
|
||||
var retrievalDealSelectionShowCmd = &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List retrieval deal proposal selection criteria",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
smapi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
onlineOk, err := smapi.DealsConsiderOnlineRetrievalDeals(lcli.DaemonContext(cctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
offlineOk, err := smapi.DealsConsiderOfflineRetrievalDeals(lcli.DaemonContext(cctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("considering online retrieval deals: %t\n", onlineOk)
|
||||
fmt.Printf("considering offline retrieval deals: %t\n", offlineOk)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var retrievalDealSelectionResetCmd = &cli.Command{
|
||||
Name: "reset",
|
||||
Usage: "Reset retrieval deal proposal selection criteria to default values",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
smapi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
err = smapi.DealsSetConsiderOnlineRetrievalDeals(lcli.DaemonContext(cctx), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = smapi.DealsSetConsiderOfflineRetrievalDeals(lcli.DaemonContext(cctx), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var retrievalDealSelectionRejectCmd = &cli.Command{
|
||||
Name: "reject",
|
||||
Usage: "Configure criteria which necessitate automatic rejection",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "online",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "offline",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
smapi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
if cctx.Bool("online") {
|
||||
err = smapi.DealsSetConsiderOnlineRetrievalDeals(lcli.DaemonContext(cctx), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if cctx.Bool("offline") {
|
||||
err = smapi.DealsSetConsiderOfflineRetrievalDeals(lcli.DaemonContext(cctx), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -27,6 +27,7 @@ var sectorsCmd = &cli.Command{
|
||||
sectorsRefsCmd,
|
||||
sectorsUpdateCmd,
|
||||
sectorsPledgeCmd,
|
||||
sectorsRemoveCmd,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -46,8 +47,9 @@ var sectorsPledgeCmd = &cli.Command{
|
||||
}
|
||||
|
||||
var sectorsStatusCmd = &cli.Command{
|
||||
Name: "status",
|
||||
Usage: "Get the seal status of a sector by its ID",
|
||||
Name: "status",
|
||||
Usage: "Get the seal status of a sector by its number",
|
||||
ArgsUsage: "<sectorNum>",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "log",
|
||||
@@ -63,7 +65,7 @@ var sectorsStatusCmd = &cli.Command{
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
|
||||
if !cctx.Args().Present() {
|
||||
return fmt.Errorf("must specify sector ID to get status of")
|
||||
return fmt.Errorf("must specify sector number to get status of")
|
||||
}
|
||||
|
||||
id, err := strconv.ParseUint(cctx.Args().First(), 10, 64)
|
||||
@@ -208,6 +210,39 @@ var sectorsRefsCmd = &cli.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var sectorsRemoveCmd = &cli.Command{
|
||||
Name: "remove",
|
||||
Usage: "Forcefully remove a sector (WARNING: This means losing power and collateral for the removed sector)",
|
||||
ArgsUsage: "<sectorNum>",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "really-do-it",
|
||||
Usage: "pass this flag if you know what you are doing",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
if !cctx.Bool("really-do-it") {
|
||||
return xerrors.Errorf("this is a command for advanced users, only use it if you are sure of what you are doing")
|
||||
}
|
||||
nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
if cctx.Args().Len() != 1 {
|
||||
return xerrors.Errorf("must pass sector number")
|
||||
}
|
||||
|
||||
id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("could not parse sector number: %w", err)
|
||||
}
|
||||
|
||||
return nodeApi.SectorRemove(ctx, abi.SectorNumber(id))
|
||||
},
|
||||
}
|
||||
|
||||
var sectorsUpdateCmd = &cli.Command{
|
||||
Name: "update-state",
|
||||
Usage: "ADVANCED: manually update the state of a sector, this may aid in error recovery",
|
||||
@@ -228,12 +263,12 @@ var sectorsUpdateCmd = &cli.Command{
|
||||
defer closer()
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
if cctx.Args().Len() < 2 {
|
||||
return xerrors.Errorf("must pass sector ID and new state")
|
||||
return xerrors.Errorf("must pass sector number and new state")
|
||||
}
|
||||
|
||||
id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("could not parse sector ID: %w", err)
|
||||
return xerrors.Errorf("could not parse sector number: %w", err)
|
||||
}
|
||||
|
||||
return nodeApi.SectorsUpdate(ctx, abi.SectorNumber(id), api.SectorState(cctx.Args().Get(1)))
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/filecoin-project/lotus/chain/vm"
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
"github.com/filecoin-project/lotus/lib/peermgr"
|
||||
"github.com/filecoin-project/lotus/lib/ulimit"
|
||||
"github.com/filecoin-project/lotus/metrics"
|
||||
"github.com/filecoin-project/lotus/node"
|
||||
"github.com/filecoin-project/lotus/node/modules"
|
||||
@@ -113,6 +114,11 @@ var DaemonCmd = &cli.Command{
|
||||
Name: "profile",
|
||||
Usage: "specify type of node",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "manage-fdlimit",
|
||||
Usage: "manage open file limit",
|
||||
Value: true,
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
err := runmetrics.Enable(runmetrics.RunMetricOptions{
|
||||
@@ -122,6 +128,13 @@ var DaemonCmd = &cli.Command{
|
||||
if err != nil {
|
||||
return xerrors.Errorf("enabling runtime metrics: %w", err)
|
||||
}
|
||||
|
||||
if cctx.Bool("manage-fdlimit") {
|
||||
if _, _, err := ulimit.ManageFdLimit(); err != nil {
|
||||
log.Errorf("setting file descriptor limit: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if prof := cctx.String("pprof"); prof != "" {
|
||||
profile, err := os.Create(prof)
|
||||
if err != nil {
|
||||
|
||||
@@ -69,7 +69,7 @@ func init() {
|
||||
}
|
||||
// TODO: beacon
|
||||
|
||||
uts := head.MinTimestamp() + uint64(build.BlockDelay)
|
||||
uts := head.MinTimestamp() + uint64(build.BlockDelaySecs)
|
||||
nheight := head.Height() + 1
|
||||
blk, err := api.MinerCreateBlock(ctx, &lapi.BlockTemplate{
|
||||
addr, head.Key(), ticket, &types.ElectionProof{}, nil, msgs, nheight, uts, gen.ValidWpostForTesting,
|
||||
|
||||
@@ -63,10 +63,6 @@
|
||||
"title": "Proof-of-Spacetime(s)",
|
||||
"value": "Filecoin is a protocol token whose blockchain runs on a novel proof, called Proof-of-Spacetime, where blocks are created by miners that are storing data."
|
||||
},
|
||||
"lotus-testnet": {
|
||||
"title": "Filecoin Testnet",
|
||||
"value": "Until we launch, we are making lots of changes to Lotus. The Testnet is expected to bring a few significant fixes/improvements. During Testnet, you can retrieve test filecoin from our network faucet to use as collateral to start mining. Test filecoin do not have any value – the official filecoin tokens will not be released until Mainnet launch."
|
||||
},
|
||||
"filecoin-testnet": {
|
||||
"title": "Filecoin Testnet",
|
||||
"value": "Until we launch, we are making lots of changes to Lotus. The Testnet is expected to bring a few significant fixes/improvements. During Testnet, you can retrieve test filecoin from our network faucet to use as collateral to start mining. Test filecoin do not have any value – the official filecoin tokens will not be released until Mainnet launch."
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Lotus
|
||||
|
||||
Lotus is an implementation of the [Filecoin Distributed Storage Network](https://filecoin.io/).
|
||||
A Lotus node syncs blockchains that follow the
|
||||
A Lotus node syncs blockchains that follow the
|
||||
Filecoin protocol, validating the blocks and state transitions.
|
||||
The specification for the Filecoin protocol can be found [here](https://filecoin-project.github.io/specs/).
|
||||
|
||||
For information on how to setup and operate a Lotus node,
|
||||
For information on how to setup and operate a Lotus node,
|
||||
please follow the instructions [here](https://lotu.sh/en+getting-started).
|
||||
|
||||
# Components
|
||||
@@ -24,7 +24,7 @@ FIXME: No mention of block production here, cross-reference with schomatis's min
|
||||
- Other PL dependencies (IPFS, libp2p, IPLD? FIXME, missing)
|
||||
- External libraries used by Lotus and other deps (FIXME, missing)
|
||||
|
||||
# Preliminaries
|
||||
# Preliminaries
|
||||
|
||||
We discuss some key Filecoin concepts here, aiming to explain them by contrasting them with analogous concepts
|
||||
in other well-known blockchains like Ethereum. We only provide brief descriptions here; elaboration
|
||||
@@ -34,10 +34,10 @@ can be found in the [spec](https://filecoin-project.github.io/specs/).
|
||||
|
||||
Unlike in Ethereum, a block can have multiple parents in Filecoin. We thus refer to the parent set of a block,
|
||||
instead of a single parent.
|
||||
A [tipset](https://filecoin-project.github.io/specs/#systems__filecoin_blockchain__struct__tipset)
|
||||
is any set of blocks that share the same parent set.
|
||||
A [tipset](https://filecoin-project.github.io/specs/#systems__filecoin_blockchain__struct__tipset)
|
||||
is any set of blocks that share the same parent set.
|
||||
|
||||
There is no concept of "block difficulty" in Filecoin. Instead,
|
||||
There is no concept of "block difficulty" in Filecoin. Instead,
|
||||
the weight of a tipset is simply the number of blocks in the chain that ends in that tipset. Note that a longer chain
|
||||
can have less weight than a shorter chain with more blocks per tipset.
|
||||
|
||||
@@ -49,8 +49,8 @@ We call the heaviest tipset in a chain the "head" of the chain.
|
||||
### Actors and Messages
|
||||
|
||||
An [Actor](https://filecoin-project.github.io/specs/#systems__filecoin_vm__actor)
|
||||
is analogous to a smart contract in Ethereum. Filecoin does not allow users to define their own
|
||||
actors, but comes with several [builtin actors](https://github.com/filecoin-project/specs-actors),
|
||||
is analogous to a smart contract in Ethereum. Filecoin does not allow users to define their own
|
||||
actors, but comes with several [builtin actors](https://github.com/filecoin-project/specs-actors),
|
||||
which can be thought of as pre-compiled contracts.
|
||||
|
||||
A [Message](https://filecoin-project.github.io/specs/#systems__filecoin_vm__message)
|
||||
@@ -70,8 +70,8 @@ We now discuss the various stages of the sync process.
|
||||
|
||||
## Sync setup
|
||||
|
||||
When a Lotus node connects to a new peer, we exchange the head of our chain
|
||||
with the new peer through [the `hello` protocol](https://github.com/filecoin-project/lotus/blob/master/node/hello/hello.go).
|
||||
When a Lotus node connects to a new peer, we exchange the head of our chain
|
||||
with the new peer through [the `hello` protocol](https://github.com/filecoin-project/lotus/blob/master/node/hello/hello.go).
|
||||
If the peer's head is heavier than ours, we try to sync to it. Note
|
||||
that we do NOT update our chain head at this stage.
|
||||
|
||||
@@ -79,7 +79,7 @@ that we do NOT update our chain head at this stage.
|
||||
|
||||
Note: The API refers to these stages as `StageHeaders` and `StagePersistHeaders`.
|
||||
|
||||
We proceed in the sync process by requesting block headers from the peer,
|
||||
We proceed in the sync process by requesting block headers from the peer,
|
||||
moving back from their head, until we reach a tipset that we have in common
|
||||
(such a common tipset must exist, thought it may simply be the genesis block).
|
||||
The functionality can be found in `Syncer::collectHeaders()`.
|
||||
@@ -90,7 +90,7 @@ drop part of our chain to connect to the peer's head (referred to as "forking").
|
||||
FIXME: This next para might be best replaced with a link to the validation doc
|
||||
Some of the possible causes of failure in this stage include:
|
||||
|
||||
- The chain is linked to a block that we have previously marked as bad,
|
||||
- The chain is linked to a block that we have previously marked as bad,
|
||||
and stored in a [`BadBlockCache`](https://github.com/filecoin-project/lotus/blob/master/chain/badtscache.go).
|
||||
- The beacon entries in a block are inconsistent (FIXME: more details about what is validated here wouldn't be bad).
|
||||
- Switching to this new chain would involve a chain reorganization beyond the allowed threshold (SPECK-CHECK).
|
||||
@@ -101,7 +101,7 @@ Note: The API refers to this stage as `StageMessages`.
|
||||
|
||||
Having acquired the headers and found a common tipset, we then move forward, requesting the full blocks, including the messages.
|
||||
|
||||
For each block, we first confirm the syntactic validity of the block (SPECK-CHECK),
|
||||
For each block, we first confirm the syntactic validity of the block (SPECK-CHECK),
|
||||
which includes the syntactic validity of messages included
|
||||
in the block.
|
||||
We then apply the messages, running all the state transitions, and compare the state root we calculate with the provided state root.
|
||||
@@ -121,11 +121,11 @@ syntactic validation of messages.
|
||||
|
||||
Note: The API refers to this stage as `StageSyncComplete`.
|
||||
|
||||
If all validations pass we will now set that head as our heaviest tipset in
|
||||
If all validations pass we will now set that head as our heaviest tipset in
|
||||
[`ChainStore`](https://github.com/filecoin-project/lotus/blob/master/chain/store/store.go).
|
||||
We already have the full state, since we calculated
|
||||
it during the sync process.
|
||||
|
||||
|
||||
FIXME (aayush) I don't fuilly understand the next 2 paragraphs, but it seems important. Confirm and polish.
|
||||
Relevant issue in IPFS: https://github.com/ipfs/ipfs-docs/issues/264
|
||||
|
||||
@@ -135,7 +135,7 @@ FIXME: Create a further reading appendix, move this next para to it, along with
|
||||
extraneous content
|
||||
This is one of the few items we store in `Datastore` by key, location, allowing its contents to change on every sync. This is reflected in the `(*ChainStore) writeHead()` function (called by `takeHeaviestTipSet()` above) where we reference the pointer by the explicit `chainHeadKey` address (the string `"head"`, not a hash embedded in a CID), and similarly in `(*ChainStore).Load()` when we start the node and create the `ChainStore`. Compare this to a Filecoin block or message which are immutable, stored in the `Blockstore` by CID, once created they never change.
|
||||
|
||||
## Keeping up with the chain
|
||||
## Keeping up with the chain
|
||||
|
||||
A Lotus node also listens for new blocks broadcast by its peers over the `gossipsub` channel (see FIXME for more).
|
||||
If we have validated such a block's parent tipset, and adding it to our tipset at its height would lead to a heavier
|
||||
@@ -144,11 +144,11 @@ process (indeed, it's the same codepath).
|
||||
|
||||
# State
|
||||
|
||||
In Filecoin, the chain state at any given point is a collection of data stored under a root CID
|
||||
In Filecoin, the chain state at any given point is a collection of data stored under a root CID
|
||||
encapsulated in the [`StateTree`](https://github.com/filecoin-project/lotus/blob/master/chain/state/statetree.go),
|
||||
and accessed through the
|
||||
and accessed through the
|
||||
[`StateManager`](https://github.com/filecoin-project/lotus/blob/master/chain/stmgr/stmgr.go).
|
||||
The state at the chain's head is thus easily tracked and updated in a state root CID.
|
||||
The state at the chain's head is thus easily tracked and updated in a state root CID.
|
||||
(FIXME: Talk about CIDs somewhere, we might want to explain some of the modify/flush/update-root mechanism here.))
|
||||
|
||||
## Calculating a Tipset State
|
||||
@@ -156,7 +156,7 @@ The state at the chain's head is thus easily tracked and updated in a state root
|
||||
Recall that a tipset is a set of blocks that have identical parents (that is, that are built on top of the same tipset).
|
||||
The genesis tipset comprises the genesis block(s), and has some state corresponding to it.
|
||||
|
||||
The methods `TipSetState()` and `computeTipSetState()` in
|
||||
The methods `TipSetState()` and `computeTipSetState()` in
|
||||
[`StateManager`](https://github.com/filecoin-project/lotus/blob/master/chain/stmgr/stmgr.go)
|
||||
are responsible for computing
|
||||
the state that results from applying a tipset. This involves applying all the messages included
|
||||
@@ -168,25 +168,25 @@ State Root (which is to be expected, since they have the same parent tipset)
|
||||
|
||||
### Preparing to apply a tipset
|
||||
|
||||
When `StateManager::computeTipsetState()` is called with a tipset, `ts`,
|
||||
When `StateManager::computeTipsetState()` is called with a tipset, `ts`,
|
||||
it retrieves the parent state root of the blocks in `ts`. It also creates a list of `BlockMessages`, which wraps the BLS
|
||||
and SecP messages in a block along with the miner that produced the block.
|
||||
and SecP messages in a block along with the miner that produced the block.
|
||||
|
||||
Control then flows to `StateManager::ApplyBlocks()`, which builds a VM to apply the messages given to it. The VM
|
||||
Control then flows to `StateManager::ApplyBlocks()`, which builds a VM to apply the messages given to it. The VM
|
||||
is initialized with the parent state root of the blocks in `ts`. We apply the blocks in `ts` in order (see FIXME for
|
||||
ordering of blocks in a tipset).
|
||||
|
||||
### Applying a block
|
||||
|
||||
For each block, we prepare to apply the ordered messages (first BLS, then SecP). Before applying a message, we check if
|
||||
For each block, we prepare to apply the ordered messages (first BLS, then SecP). Before applying a message, we check if
|
||||
we have already applied a message with that CID within the scope of this method. If so, we simply skip that message;
|
||||
this is how duplicate messages included in the same tipset are skipped (with only the miner of the "first" block to
|
||||
include the message getting the reward). For the actual process of message application, see FIXME (need an
|
||||
internal link here), for now we
|
||||
simply assume that the outcome of the VM applying a message is either an error, or a
|
||||
internal link here), for now we
|
||||
simply assume that the outcome of the VM applying a message is either an error, or a
|
||||
[`MessageReceipt`](https://github.com/filecoin-project/lotus/blob/master/chain/types/message_receipt.go)
|
||||
and some
|
||||
other information.
|
||||
other information.
|
||||
|
||||
We treat an error from the VM as a showstopper; there is no recovery, and no meaningful state can be computed for `ts`.
|
||||
Given a successful receipt, we add the rewards and penalties to what the miner has earned so far. Once all the messages
|
||||
@@ -205,8 +205,8 @@ is the computed state of the tipset.
|
||||
|
||||
# Virtual Machine
|
||||
|
||||
The Virtual Machine (VM) is responsible for executing messages.
|
||||
The [Lotus Virtual Machine](https://github.com/filecoin-project/lotus/blob/master/chain/vm/vm.go)
|
||||
The Virtual Machine (VM) is responsible for executing messages.
|
||||
The [Lotus Virtual Machine](https://github.com/filecoin-project/lotus/blob/master/chain/vm/vm.go)
|
||||
invokes the appropriate methods in the builtin actors, and provides
|
||||
a [`Runtime`](https://github.com/filecoin-project/specs-actors/blob/master/actors/runtime/runtime.go)
|
||||
interface to the [builtin actors](https://github.com/filecoin-project/specs-actors)
|
||||
@@ -233,10 +233,10 @@ It then transfers the message's value to the recipient, creating a new account a
|
||||
### Method Invocation
|
||||
|
||||
We use reflection to translate a Filecoin message for the VM to an actual Go function, relying on the VM's
|
||||
[`invoker`](https://github.com/filecoin-project/lotus/blob/master/chain/vm/invoker.go) structure.
|
||||
[`invoker`](https://github.com/filecoin-project/lotus/blob/master/chain/vm/invoker.go) structure.
|
||||
Each actor has its own set of codes defined in `specs-actors/actors/builtin/methods.go`.
|
||||
The `invoker` structure maps the builtin actors' CIDs
|
||||
to a list of `invokeFunc` (one per exported method), which each take the `Runtime` (for state manipulation)
|
||||
to a list of `invokeFunc` (one per exported method), which each take the `Runtime` (for state manipulation)
|
||||
and the serialized input parameters.
|
||||
|
||||
FIXME (aayush) Polish this next para.
|
||||
@@ -245,39 +245,39 @@ The basic layout (without reflection details) of `(*invoker).transform()` is as
|
||||
|
||||
### Returning from the VM
|
||||
|
||||
Once method invocation is complete (including any subcalls), we return to `ApplyMessage()`, which receives
|
||||
the serialized response and the [`ActorError`](https://github.com/filecoin-project/lotus/blob/master/chain/actors/aerrors/error.go).
|
||||
Once method invocation is complete (including any subcalls), we return to `ApplyMessage()`, which receives
|
||||
the serialized response and the [`ActorError`](https://github.com/filecoin-project/lotus/blob/master/chain/actors/aerrors/error.go).
|
||||
The sender will be charged the appropriate amount of gas for the returned response, which gets put into the
|
||||
[`MessageReceipt`](https://github.com/filecoin-project/lotus/blob/master/chain/types/message_receipt.go).
|
||||
|
||||
The method then refunds any unused gas to the sender, sets up the gas reward for the miner, and
|
||||
The method then refunds any unused gas to the sender, sets up the gas reward for the miner, and
|
||||
wraps all of this into an `ApplyRet`, which is returned.
|
||||
|
||||
# Building a Lotus node
|
||||
|
||||
When we launch a Lotus node with the command `./lotus daemon`
|
||||
When we launch a Lotus node with the command `./lotus daemon`
|
||||
(see [here](https://github.com/filecoin-project/lotus/blob/master/cmd/lotus/daemon.go) for more),
|
||||
the node is created through [dependency injection](https://godoc.org/go.uber.org/fx).
|
||||
This relies on reflection, which makes some of the references hard to follow.
|
||||
This relies on reflection, which makes some of the references hard to follow.
|
||||
The node sets up all of the subsystems it needs to run, such as the repository, the network connections, thechain sync
|
||||
service, etc.
|
||||
service, etc.
|
||||
This setup is orchestrated through calls to the `node.Override` function.
|
||||
The structure of each call indicates the type of component it will set up
|
||||
The structure of each call indicates the type of component it will set up
|
||||
(many defined in [`node/modules/dtypes/`](https://github.com/filecoin-project/lotus/tree/master/node/modules/dtypes)),
|
||||
and the function that will provide it.
|
||||
and the function that will provide it.
|
||||
The dependency is implicit in the argument of the provider function.
|
||||
|
||||
As an example, consider the `modules.ChainStore()` function that provides the
|
||||
[`ChainStore`](https://github.com/filecoin-project/lotus/blob/master/chain/store/store.go) structure.
|
||||
It takes as one of its parameters the [`ChainBlockstore`](https://github.com/filecoin-project/lotus/blob/master/node/modules/dtypes/storage.go)
|
||||
type, which becomes one of its dependencies.
|
||||
type, which becomes one of its dependencies.
|
||||
For the node to be built successfully the `ChainBlockstore` will need to be provided before `ChainStore`, a requirement
|
||||
that is made explicit in another `Override()` call that sets the provider of that type as the `ChainBlockstore()` function.
|
||||
|
||||
## The Repository
|
||||
|
||||
The repo is the directory where all of a node's information is stored. The node is entirely defined by its repo, which
|
||||
makes it easy to port to another location. This one-to-one relationship means we can speak
|
||||
makes it easy to port to another location. This one-to-one relationship means we can speak
|
||||
of the node as the repo it is associated with, instead of the daemon process that runs from that repo.
|
||||
|
||||
Only one daemon can run be running with an associated repo at a time.
|
||||
@@ -292,17 +292,17 @@ lsof ~/.lotus/repo.lock
|
||||
Trying to launch a second daemon hooked to the same repo leads to a `repo is already locked (lotus daemon already running)`
|
||||
error.
|
||||
|
||||
The `node.Repo()` function (`node/builder.go`) contains most of the dependencies (specified as `Override()` calls)
|
||||
The `node.Repo()` function (`node/builder.go`) contains most of the dependencies (specified as `Override()` calls)
|
||||
needed to properly set up the node's repo. We list the most salient ones here.
|
||||
|
||||
### Datastore
|
||||
|
||||
`Datastore` and `ChainBlockstore`: Data related to the node state is saved in the repo's `Datastore`,
|
||||
an IPFS interface defined [here](github.com/ipfs/go-datastore/datastore.go).
|
||||
`Datastore` and `ChainBlockstore`: Data related to the node state is saved in the repo's `Datastore`,
|
||||
an IPFS interface defined [here](https://github.com/ipfs/go-datastore/blob/master/datastore.go).
|
||||
Lotus creates this interface from a [Badger DB](https://github.com/dgraph-io/badger) in
|
||||
[`FsRepo`](https://github.com/filecoin-project/lotus/blob/master/node/repo/fsrepo.go).
|
||||
Every piece of data is fundamentally a key-value pair in the `datastore` directory of the repo.
|
||||
There are several abstractions laid on top of it that appear through the code depending on *how* we access it,
|
||||
There are several abstractions laid on top of it that appear through the code depending on *how* we access it,
|
||||
but it is important to remember that we're always accessing it from the same place.
|
||||
|
||||
FIXME: Maybe mention the `Batching` interface as the developer will stumble upon it before reaching the `Datastore` one.
|
||||
@@ -314,8 +314,8 @@ FIXME: IPFS blocks vs Filecoin blocks ideally happens before this / here
|
||||
The [`Blockstore` interface](`github.com/ipfs/go-ipfs-blockstore/blockstore.go`) structures the key-value pair
|
||||
into the CID format for the key and the [`Block` interface](`github.com/ipfs/go-block-format/blocks.go`) for the value.
|
||||
The `Block` value is just a raw string of bytes addressed by its hash, which is included in the CID key.
|
||||
|
||||
`ChainBlockstore` creates a `Blockstore` in the repo under the `/blocks` namespace.
|
||||
|
||||
`ChainBlockstore` creates a `Blockstore` in the repo under the `/blocks` namespace.
|
||||
Every key stored there will have the `blocks` prefix so that it does not collide with other stores that use the same repo.
|
||||
|
||||
FIXME: Link to IPFS documentation about DAG, CID, and related, especially we need a diagram that shows how do we wrap each datastore inside the next layer (datastore, batching, block store, gc, etc).
|
||||
@@ -323,7 +323,7 @@ FIXME: Link to IPFS documentation about DAG, CID, and related, especially we nee
|
||||
#### Metadata
|
||||
|
||||
`modules.Datastore()` creates a `dtypes.MetadataDS`, which is an alias for the basic `Datastore` interface.
|
||||
Metadata is stored here under the `/metadata` prefix.
|
||||
Metadata is stored here under the `/metadata` prefix.
|
||||
(FIXME: Explain *what* is metadata in contrast with the block store, namely we store the pointer to the heaviest chain, we might just link to that unwritten section here later.)
|
||||
|
||||
FIXME: Explain the key store related calls (maybe remove, per Schomatis)
|
||||
@@ -331,10 +331,10 @@ FIXME: Explain the key store related calls (maybe remove, per Schomatis)
|
||||
### LockedRepo
|
||||
|
||||
`LockedRepo()`: This method doesn't create or initialize any new structures, but rather registers an
|
||||
`OnStop` [hook](https://godoc.org/go.uber.org/fx/internal/lifecycle#Hook)
|
||||
`OnStop` [hook](https://godoc.org/go.uber.org/fx/internal/lifecycle#Hook)
|
||||
that will close the locked repository associated with it on shutdown.
|
||||
|
||||
|
||||
|
||||
|
||||
### Repo types / Node types
|
||||
|
||||
FIXME: This section needs to be clarified / corrected...I don't fully understand the config differences (what do they have in common, if anything?)
|
||||
@@ -351,19 +351,19 @@ As we said, the repo fully identifies the node so a repo type is also a *node* t
|
||||
FIXME: Much of this might need to be subsumed into the p2p section
|
||||
|
||||
The `node.Online()` configuration function (`node/builder.go`) initializes components that involve connecting to,
|
||||
or interacting with, the Filecoin network. These connections are managed through the libp2p stack (FIXME link to this section when it exists).
|
||||
or interacting with, the Filecoin network. These connections are managed through the libp2p stack (FIXME link to this section when it exists).
|
||||
We discuss some of the components found in the full node type (that is, included in the `ApplyIf(isType(repo.FullNode),` call).
|
||||
|
||||
#### Chainstore
|
||||
|
||||
`modules.ChainStore()` creates the [`store.ChainStore`](https://github.com/filecoin-project/lotus/blob/master/chain/store/store.go))
|
||||
`modules.ChainStore()` creates the [`store.ChainStore`](https://github.com/filecoin-project/lotus/blob/master/chain/store/store.go))
|
||||
that wraps the stores
|
||||
previously instantiated in `Repo()`. It is the main point of entry for the node to all chain-related data
|
||||
(FIXME: this is incorrect, we sometimes access its underlying block store directly, and probably shouldn't).
|
||||
previously instantiated in `Repo()`. It is the main point of entry for the node to all chain-related data
|
||||
(FIXME: this is incorrect, we sometimes access its underlying block store directly, and probably shouldn't).
|
||||
It also holds the crucial `heaviest` pointer, which indicates the current head of the chain.
|
||||
|
||||
|
||||
#### ChainExchange and ChainBlockservice
|
||||
`ChainExchange()` and `ChainBlockservice()` establish a BitSwap connection (FIXME libp2p link)
|
||||
`ChainExchange()` and `ChainBlockservice()` establish a BitSwap connection (FIXME libp2p link)
|
||||
to exchange chain information in the form of `blocks.Block`s stored in the repo. (See sync section for more details, the Filecoin blocks and messages are backed by these raw IPFS blocks that together form the different structures that define the state of the current/heaviest chain.)
|
||||
|
||||
#### Incoming handlers
|
||||
@@ -371,7 +371,7 @@ to exchange chain information in the form of `blocks.Block`s stored in the repo.
|
||||
and messages from the network (see `<undefined>` for more information about the topics the node is subscribed to, FIXME: should that be part of the libp2p section or should we expand on gossipsub separately?).
|
||||
|
||||
#### Hello
|
||||
`RunHello()`: starts the services to both send (`(*Service).SayHello()`) and receive (`(*Service).HandleStream()`, `node/hello/hello.go`)
|
||||
`RunHello()`: starts the services to both send (`(*Service).SayHello()`) and receive (`(*Service).HandleStream()`, `node/hello/hello.go`)
|
||||
`hello` messages. When nodes establish a new connection with each other, they exchange these messages
|
||||
to share chain-related information (namely their genesis block and their heaviest tipset).
|
||||
|
||||
@@ -381,14 +381,14 @@ to share chain-related information (namely their genesis block and their heavies
|
||||
### Ordering the dependencies
|
||||
|
||||
We can establish the dependency relations by looking at the parameters that each function needs and by understanding
|
||||
the architecture of the node and how the different components relate to each other (the chief purpose of this document).
|
||||
the architecture of the node and how the different components relate to each other (the chief purpose of this document).
|
||||
|
||||
As an example, the sync mechanism depends on the node being able to exchange different IPFS blocks with the network,
|
||||
so as to be able to request the "missing pieces" needed to construct the chain. This dependency is reflected by `NewSyncer()`
|
||||
having a `blocksync.BlockSync` parameter, which in turn depends on `ChainBlockservice()` and `ChainExchange()`.
|
||||
The chain exchange service further depends on the chain store to save and retrieve chain data, which is reflected
|
||||
having a `blocksync.BlockSync` parameter, which in turn depends on `ChainBlockservice()` and `ChainExchange()`.
|
||||
The chain exchange service further depends on the chain store to save and retrieve chain data, which is reflected
|
||||
in `ChainExchange()` having `ChainGCBlockstore` as a parameter (which is just a wrapper around `ChainBlockstore` capable
|
||||
of garbage collection).
|
||||
|
||||
This block store is the same store underlying the chain store, which is an indirect dependency of `NewSyncer()` (through the `StateManager`).
|
||||
(FIXME: This last line is flaky, we need to resolve the hierarchy better, we sometimes refer to the chain store and sometimes to its underlying block store. We need a diagram to visualize all the different components just mentioned otherwise it is too hard to follow. We probably even need to skip some of the connections mentioned.)
|
||||
(FIXME: This last line is flaky, we need to resolve the hierarchy better, we sometimes refer to the chain store and sometimes to its underlying block store. We need a diagram to visualize all the different components just mentioned otherwise it is too hard to follow. We probably even need to skip some of the connections mentioned.)
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
# Incoming block validations
|
||||
|
||||
This document reviews the code flow that takes place inside the full node after receiving a new block from the GossipSub `/fil/blocks` topic and traces all of its protocol-related validation logic. We do not include validation logic *inside* the VM, the analysis stops at `(*VM).Invoke()`. The `V:` tag explicitly signals validations throughout the text.
|
||||
|
||||
## `modules.HandleIncomingBlocks()`
|
||||
|
||||
We subscribe to the `/fil/blocks` PubSub topic to receive external blocks from peers in the network and register a block validator that operates at the PubSub (`libp2p` stack) level, validating each PubSub message containing a Filecoin block header.
|
||||
|
||||
`V:` PubSub message is a valid CBOR `BlockMsg`.
|
||||
|
||||
`V:` Total messages in block are under `BlockMessageLimit`.
|
||||
|
||||
`V:` Aggregate message CIDs, encapsulated in the `MsgMeta` structure, serialize to the `Messages` CID in the block header (`ValidateMsgMeta()`).
|
||||
|
||||
`V:` Miner `Address` in block header is present and corresponds to a public-key address in the current chain state.
|
||||
|
||||
`V:` Block signature (`BlockSig`) is present and belongs to the public-key address retrieved for the miner (`CheckBlockSignature()`).
|
||||
|
||||
## `sub.HandleIncomingBlocks()`
|
||||
|
||||
Assemble a `FullBlock` from the received block header retrieving its Filecoin messages.
|
||||
|
||||
`V:` Block messages CIDs can be retrieved from the network and decode into valid CBOR `Message`/`SignedMessage`.
|
||||
|
||||
## `(*Syncer).InformNewHead()`
|
||||
|
||||
Assemble a `FullTipSet` populated with the single block received earlier.
|
||||
|
||||
`V:` `ValidateMsgMeta()` (already done in the topic validator).
|
||||
|
||||
`V:` Block's `ParentWeight` is greater than the one from the (first block of the) heaviest tipset.
|
||||
|
||||
## `(*Syncer).Sync()`
|
||||
|
||||
`(*Syncer).collectHeaders()`: we retrieve all tipsets from the received block down to our chain. Validation now is expanded to *every* block inside these tipsets.
|
||||
|
||||
`V`: Beacon entires are ordered by their round number.
|
||||
|
||||
`V:` Tipset `Parents` CIDs match the fetched parent tipset through block sync. (This check is not enforced correctly at the moment, see [issue](https://github.com/filecoin-project/lotus/issues/1918).)
|
||||
|
||||
## `(*Syncer).ValidateBlock()`
|
||||
|
||||
This function contains most of the validation logic grouped in separate closures that run asynchronously, this list does not reflect validation order then.
|
||||
|
||||
`V:` Block `Timestamp`:
|
||||
* Is not bigger than current time plus `AllowableClockDriftSecs`.
|
||||
* Is not smaller than previous block's `Timestamp` plus `BlockDelay` (including null blocks).
|
||||
|
||||
### Messages
|
||||
|
||||
We check all the messages contained in one block at a time (`(*Syncer).checkBlockMessages()`).
|
||||
|
||||
`V:` The block's `BLSAggregate` matches the aggregate of BLS messages digests and public keys (extracted from the messages `From`).
|
||||
|
||||
`V:` Each `secp256k1` message `Signature` is signed with the public key extracted from the message `From`.
|
||||
|
||||
`V:` Aggregate message CIDs, encapsulated in the `MsgMeta` structure, serialize to the `Messages` CID in block header (similar to `ValidateMsgMeta()` call).
|
||||
|
||||
`V:` For each message, in `ValidForBlockInclusion()`:
|
||||
* Message fields `Version`, `To`, `From`, `Value`, `GasPrice`, and `GasLimit` are correctly defined.
|
||||
* Message `GasLimit` is under the message minimum gas cost (derived from chain height and message length).
|
||||
|
||||
`V:` Actor associated with message `From` exists and is an account actor, its `Nonce` matches the message `Nonce`.
|
||||
|
||||
### Miner
|
||||
|
||||
`V:` Miner address is registered in the `Claims` HAMT of the Power actor.
|
||||
|
||||
### Compute parent tipset state
|
||||
|
||||
`V:` Block's `ParentStateRoot` CID matches the state CID computed from the parent tipset.
|
||||
|
||||
`V:` Block's `ParentMessageReceipts` CID matches receipts CID computed from the parent tipset.
|
||||
|
||||
### Winner
|
||||
|
||||
Draw randomness for current epoch with minimum ticket from previous tipset, using `ElectionProofProduction`
|
||||
domain separation tag.
|
||||
`V`: `ElectionProof.VRFProof` is computed correctly by checking BLS signature using miner's key.
|
||||
`V`: Miner is not slashed in `StoragePowerActor`.
|
||||
`V`: Check if ticket is a winning ticket:
|
||||
```
|
||||
h := blake2b(VRFProof)
|
||||
lhs := AsInt(h) * totalNetworkPower
|
||||
rhs := minerPower * 2^256
|
||||
if lhs < rhs { return "Winner" } else { return "Not a winner" }
|
||||
```
|
||||
|
||||
### Block signature
|
||||
|
||||
`V:` `CheckBlockSignature()` (same signature validation as the one applied to the incoming block).
|
||||
|
||||
### Beacon values check
|
||||
|
||||
`V`: Validate that all `BeaconEntries` are valid. Check that every one of them is a signature of a message: `previousSignature || round` signed using drand's public key.
|
||||
`V`: All entries between `MaxBeaconRoundForEpoch` down to `prevEntry` (from previous tipset) are included.
|
||||
|
||||
### Verify VRF Ticket chain
|
||||
|
||||
Draw randomness for current epoch with minimum ticket from previous tipset, using `TicketProduction`
|
||||
domain separation tag.
|
||||
`V`: `VerifyVRF` using drawn randomness and miner public key.
|
||||
|
||||
### Winning PoSt proof
|
||||
|
||||
Draw randomness for current epoch with `WinningPoSt` domain separation tag.
|
||||
Get list of sectors challanged in this epoch for this miner, based on the randomness drawn.
|
||||
|
||||
`V`: Use filecoin proofs system to verify that miner prooved access to sealed versions of these sectors.
|
||||
|
||||
## `(*StateManager).TipSetState()`
|
||||
|
||||
Called throughout the validation process for the parent of each tipset being validated. The checks here then do not apply to the received new head itself that started the validation process.
|
||||
|
||||
### `(*StateManager).computeTipSetState()`
|
||||
|
||||
`V:` Every block in the tipset should belong to different a miner.
|
||||
|
||||
### `(*StateManager).ApplyBlocks()`
|
||||
|
||||
We create a new VM with the tipset's `ParentStateRoot` (this is then the parent state of the parent of the tipset currently being validated) on which to apply all messages from all blocks in the tipset. For each message independently we apply the validations listed next.
|
||||
|
||||
### `(*VM).ApplyMessage()`
|
||||
|
||||
`V:` Basic gas and value checks in `checkMessage()`:
|
||||
* Message `GasLimit` is bigger than zero.
|
||||
* Message `GasPrice` and `Value` are set.
|
||||
|
||||
`V:` Message storage gas cost is under the message's `GasLimit`.
|
||||
|
||||
`V:` Message's `Nonce` matches nonce in actor retrieved from message's `From`.
|
||||
|
||||
`V:` Message's maximum gas cost (derived from its `GasLimit`, `GasPrice`, and `Value`) is under the balance of the actor retrieved from message's `From`.
|
||||
|
||||
### `(*VM).send()`
|
||||
|
||||
`V:` Message's transfer `Value` is under the balance in actor retrieved from message's `From`.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
These steps will install the following dependencies:
|
||||
|
||||
- go (1.13 or higher)
|
||||
- go (1.14 or higher)
|
||||
- gcc (7.4.0 or higher)
|
||||
- git (version 2 or higher)
|
||||
- bzr (some go dependency needs this)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
These steps will install the following dependencies:
|
||||
|
||||
- go (1.13 or higher)
|
||||
- go (1.14 or higher)
|
||||
- gcc (7.4.0 or higher)
|
||||
- git (version 2 or higher)
|
||||
- bzr (some go dependency needs this)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
These steps will install the following dependencies:
|
||||
|
||||
- go (1.13 or higher)
|
||||
- go (1.14 or higher)
|
||||
- gcc (7.4.0 or higher)
|
||||
- git (version 2 or higher)
|
||||
- bzr (some go dependency needs this)
|
||||
@@ -15,29 +15,26 @@ These steps will install the following dependencies:
|
||||
- llvm (proofs build)
|
||||
- clang (proofs build)
|
||||
|
||||
Run
|
||||
### Install dependencies
|
||||
|
||||
```sh
|
||||
sudo apt update
|
||||
sudo apt install mesa-opencl-icd ocl-icd-opencl-dev
|
||||
sudo apt install mesa-opencl-icd ocl-icd-opencl-dev gcc git bzr jq pkg-config curl
|
||||
sudo apt upgrade
|
||||
```
|
||||
|
||||
Build
|
||||
### Install Go 1.14
|
||||
|
||||
```sh
|
||||
sudo add-apt-repository ppa:longsleep/golang-backports
|
||||
sudo apt update
|
||||
sudo apt install golang-go gcc git bzr jq pkg-config mesa-opencl-icd ocl-icd-opencl-dev
|
||||
```
|
||||
Install the latest version of Go by following [the docs on their website](https://golang.org/doc/install).
|
||||
|
||||
Clone
|
||||
### Clone the Lotus repository
|
||||
|
||||
```sh
|
||||
git clone https://github.com/filecoin-project/lotus.git
|
||||
cd lotus/
|
||||
```
|
||||
|
||||
Install
|
||||
### Build the Lotus binaries from source and install
|
||||
|
||||
```sh
|
||||
make clean && make all
|
||||
@@ -45,3 +42,12 @@ sudo make install
|
||||
```
|
||||
|
||||
After installing Lotus, you can run the `lotus` command directly from your CLI to see usage documentation. Next, you can join the [Lotus Testnet](https://docs.lotu.sh/en+join-testnet).
|
||||
|
||||
### Interopnet
|
||||
|
||||
If you seek a smaller network to test, you can join the `interopnet`. Please note that this network is meant for developers - it resets much more often, and is much smaller. To join this network, checkout the branch `interopnet` instead of `master` before building and installing;
|
||||
```
|
||||
git checkout interopnet
|
||||
```
|
||||
|
||||
Please also note that this documentation (if viewed on the website) might not be up to date with the interopnet. For the latest documentation on the interopnet branch, see the [Lotus Documentation Interopnet Branch on GitHub](https://github.com/filecoin-project/lotus/tree/interopnet/documentation/en)
|
||||
|
||||
@@ -38,7 +38,7 @@ If you see this, that means your computer is too slow and your blocks are not in
|
||||
## Error: No space left on device
|
||||
|
||||
```sh
|
||||
lotus-storage-miner pledge-sector
|
||||
lotus-storage-miner sectors pledge
|
||||
# No space left on device (os error 28)
|
||||
```
|
||||
|
||||
@@ -51,7 +51,8 @@ If you suspect that your GPU is not being used, first make sure it is properly c
|
||||
First, to watch GPU utilization run `nvtop` in one terminal, then in a separate terminal, run:
|
||||
|
||||
```sh
|
||||
lotus-bench --sector-size=2KiB
|
||||
make bench
|
||||
./bench sealing --sector-size=2KiB
|
||||
```
|
||||
|
||||
This process uses a fair amount of GPU, and generally takes ~4 minutes to complete. If you do not see any activity in nvtop from lotus during the entire process, it is likely something is misconfigured with your GPU.
|
||||
|
||||
@@ -82,12 +82,12 @@ lotus-storage-miner sectors pledge
|
||||
Get **miner power** and **sector usage**:
|
||||
|
||||
```sh
|
||||
lotus-storage-miner state power
|
||||
lotus state power
|
||||
# returns total power
|
||||
|
||||
lotus-storage-miner state power <miner>
|
||||
lotus state power <miner>
|
||||
|
||||
lotus-storage-miner state sectors <miner>
|
||||
lotus state sectors <miner>
|
||||
```
|
||||
|
||||
## Performance tuning
|
||||
|
||||
Vendored
+1
-1
Submodule extern/filecoin-ffi updated: ca281af0b6...5342c7c97d
@@ -1,6 +1,6 @@
|
||||
module github.com/filecoin-project/lotus
|
||||
|
||||
go 1.13
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
contrib.go.opencensus.io/exporter/jaeger v0.1.0
|
||||
@@ -18,21 +18,21 @@ require (
|
||||
github.com/filecoin-project/filecoin-ffi v0.26.1-0.20200508175440-05b30afeb00d
|
||||
github.com/filecoin-project/go-address v0.0.2-0.20200504173055-8b6f2fb2b3ef
|
||||
github.com/filecoin-project/go-amt-ipld/v2 v2.0.1-0.20200424220931-6263827e49f2
|
||||
github.com/filecoin-project/go-bitfield v0.0.2-0.20200518150651-562fdb554b6e
|
||||
github.com/filecoin-project/go-bitfield v0.0.2-0.20200629135455-587b27927d38
|
||||
github.com/filecoin-project/go-cbor-util v0.0.0-20191219014500-08c40a1e63a2
|
||||
github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03
|
||||
github.com/filecoin-project/go-data-transfer v0.3.0
|
||||
github.com/filecoin-project/go-fil-commcid v0.0.0-20200208005934-2b8bd03caca5
|
||||
github.com/filecoin-project/go-fil-markets v0.3.0
|
||||
github.com/filecoin-project/go-fil-markets v0.3.2-0.20200702145639-4034a18364e4
|
||||
github.com/filecoin-project/go-jsonrpc v0.1.1-0.20200602181149-522144ab4e24
|
||||
github.com/filecoin-project/go-padreader v0.0.0-20200210211231-548257017ca6
|
||||
github.com/filecoin-project/go-paramfetch v0.0.2-0.20200605171344-fcac609550ca
|
||||
github.com/filecoin-project/go-paramfetch v0.0.2-0.20200701152213-3e0f0afdc261
|
||||
github.com/filecoin-project/go-statestore v0.1.0
|
||||
github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b
|
||||
github.com/filecoin-project/sector-storage v0.0.0-20200618073200-d9de9b7cb4b4
|
||||
github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121
|
||||
github.com/filecoin-project/specs-storage v0.1.0
|
||||
github.com/filecoin-project/storage-fsm v0.0.0-20200617183754-4380106d3e94
|
||||
github.com/filecoin-project/sector-storage v0.0.0-20200630180318-4c1968f62a8f
|
||||
github.com/filecoin-project/specs-actors v0.6.2-0.20200702170846-2cd72643a5cf
|
||||
github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea
|
||||
github.com/filecoin-project/storage-fsm v0.0.0-20200625160832-379a4655b044
|
||||
github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1
|
||||
github.com/go-kit/kit v0.10.0
|
||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
||||
@@ -44,8 +44,8 @@ require (
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d
|
||||
github.com/ipfs/go-bitswap v0.2.8
|
||||
github.com/ipfs/go-block-format v0.0.2
|
||||
github.com/ipfs/go-blockservice v0.1.3
|
||||
github.com/ipfs/go-cid v0.0.6-0.20200501230655-7c82f3b81c00
|
||||
github.com/ipfs/go-blockservice v0.1.4-0.20200624145336-a978cec6e834
|
||||
github.com/ipfs/go-cid v0.0.6
|
||||
github.com/ipfs/go-cidutil v0.0.2
|
||||
github.com/ipfs/go-datastore v0.4.4
|
||||
github.com/ipfs/go-ds-badger2 v0.1.0
|
||||
@@ -66,7 +66,7 @@ require (
|
||||
github.com/ipfs/go-ipld-cbor v0.0.5-0.20200428170625-a0bd04d3cbdf
|
||||
github.com/ipfs/go-ipld-format v0.2.0
|
||||
github.com/ipfs/go-log v1.0.4
|
||||
github.com/ipfs/go-log/v2 v2.1.2-0.20200609205458-f8d20c392cb7
|
||||
github.com/ipfs/go-log/v2 v2.1.2-0.20200626104915-0016c0b4b3e4
|
||||
github.com/ipfs/go-merkledag v0.3.1
|
||||
github.com/ipfs/go-path v0.0.7
|
||||
github.com/ipfs/go-unixfs v0.2.4
|
||||
@@ -76,20 +76,20 @@ require (
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
github.com/lib/pq v1.2.0
|
||||
github.com/libp2p/go-eventbus v0.2.1
|
||||
github.com/libp2p/go-libp2p v0.9.4
|
||||
github.com/libp2p/go-libp2p v0.10.0
|
||||
github.com/libp2p/go-libp2p-connmgr v0.2.4
|
||||
github.com/libp2p/go-libp2p-core v0.5.7
|
||||
github.com/libp2p/go-libp2p-core v0.6.0
|
||||
github.com/libp2p/go-libp2p-discovery v0.4.0
|
||||
github.com/libp2p/go-libp2p-kad-dht v0.8.1
|
||||
github.com/libp2p/go-libp2p-mplex v0.2.3
|
||||
github.com/libp2p/go-libp2p-peer v0.2.0
|
||||
github.com/libp2p/go-libp2p-peerstore v0.2.4
|
||||
github.com/libp2p/go-libp2p-peerstore v0.2.6
|
||||
github.com/libp2p/go-libp2p-pubsub v0.3.2
|
||||
github.com/libp2p/go-libp2p-quic-transport v0.5.0
|
||||
github.com/libp2p/go-libp2p-record v0.1.2
|
||||
github.com/libp2p/go-libp2p-routing-helpers v0.2.3
|
||||
github.com/libp2p/go-libp2p-secio v0.2.2
|
||||
github.com/libp2p/go-libp2p-swarm v0.2.6
|
||||
github.com/libp2p/go-libp2p-swarm v0.2.7
|
||||
github.com/libp2p/go-libp2p-tls v0.1.3
|
||||
github.com/libp2p/go-libp2p-yamux v0.2.8
|
||||
github.com/libp2p/go-maddr-filter v0.1.0
|
||||
@@ -100,11 +100,11 @@ require (
|
||||
github.com/multiformats/go-multiaddr v0.2.2
|
||||
github.com/multiformats/go-multiaddr-dns v0.2.0
|
||||
github.com/multiformats/go-multiaddr-net v0.1.5
|
||||
github.com/multiformats/go-multibase v0.0.2
|
||||
github.com/multiformats/go-multibase v0.0.3
|
||||
github.com/multiformats/go-multihash v0.0.13
|
||||
github.com/opentracing/opentracing-go v1.1.0
|
||||
github.com/stretchr/objx v0.2.0 // indirect
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
github.com/urfave/cli/v2 v2.2.0
|
||||
github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba
|
||||
|
||||
@@ -62,7 +62,6 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBA
|
||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||
github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75/go.mod h1:uAXEEpARkRhCZfEvy/y0Jcc888f9tHCc1W7/UeEtreE=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -226,6 +225,8 @@ github.com/filecoin-project/go-bitfield v0.0.0-20200416002808-b3ee67ec9060/go.mo
|
||||
github.com/filecoin-project/go-bitfield v0.0.1/go.mod h1:Ry9/iUlWSyjPUzlAvdnfy4Gtvrq4kWmWDztCU1yEgJY=
|
||||
github.com/filecoin-project/go-bitfield v0.0.2-0.20200518150651-562fdb554b6e h1:gkG/7G+iKy4He+IiQNeQn+nndFznb/vCoOR8iRQsm60=
|
||||
github.com/filecoin-project/go-bitfield v0.0.2-0.20200518150651-562fdb554b6e/go.mod h1:Ry9/iUlWSyjPUzlAvdnfy4Gtvrq4kWmWDztCU1yEgJY=
|
||||
github.com/filecoin-project/go-bitfield v0.0.2-0.20200629135455-587b27927d38 h1:B2gUde2DlfCb5YMYNVems2orobxC3KhrX3migym1IOQ=
|
||||
github.com/filecoin-project/go-bitfield v0.0.2-0.20200629135455-587b27927d38/go.mod h1:Ry9/iUlWSyjPUzlAvdnfy4Gtvrq4kWmWDztCU1yEgJY=
|
||||
github.com/filecoin-project/go-cbor-util v0.0.0-20191219014500-08c40a1e63a2 h1:av5fw6wmm58FYMgJeoB/lK9XXrgdugYiTqkdxjTy9k8=
|
||||
github.com/filecoin-project/go-cbor-util v0.0.0-20191219014500-08c40a1e63a2/go.mod h1:pqTiPHobNkOVM5thSRsHYjyQfq7O5QSCMhvuu9JoDlg=
|
||||
github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03 h1:2pMXdBnCiXjfCYx/hLqFxccPoqsSveQFxVLvNxy9bus=
|
||||
@@ -234,16 +235,16 @@ github.com/filecoin-project/go-data-transfer v0.3.0 h1:BwBrrXu9Unh9JjjX4GAc5FfzU
|
||||
github.com/filecoin-project/go-data-transfer v0.3.0/go.mod h1:cONglGP4s/d+IUQw5mWZrQK+FQATQxr3AXzi4dRh0l4=
|
||||
github.com/filecoin-project/go-fil-commcid v0.0.0-20200208005934-2b8bd03caca5 h1:yvQJCW9mmi9zy+51xA01Ea2X7/dL7r8eKDPuGUjRmbo=
|
||||
github.com/filecoin-project/go-fil-commcid v0.0.0-20200208005934-2b8bd03caca5/go.mod h1:JbkIgFF/Z9BDlvrJO1FuKkaWsH673/UdFaiVS6uIHlA=
|
||||
github.com/filecoin-project/go-fil-markets v0.3.0 h1:7iCGiuTSia4f4DmOn3s96NWUwMNSOI0ZHel/XgeApAQ=
|
||||
github.com/filecoin-project/go-fil-markets v0.3.0/go.mod h1:UXsXi43AyUQ5ieb4yIaLgk4PVt7TAbl1UCccuNw+7ds=
|
||||
github.com/filecoin-project/go-fil-markets v0.3.2-0.20200702145639-4034a18364e4 h1:VqNmKGy4/ryzo/TqevSa1kancc3hSdws7sl/NCTZzT0=
|
||||
github.com/filecoin-project/go-fil-markets v0.3.2-0.20200702145639-4034a18364e4/go.mod h1:UY+/zwNXHN73HcrN6HxNDpv6KKM6ehqfCuE9vK9khF8=
|
||||
github.com/filecoin-project/go-jsonrpc v0.1.1-0.20200602181149-522144ab4e24 h1:Jc7vkplmZYVuaEcSXGHDwefvZIdoyyaoGDLqSr8Svms=
|
||||
github.com/filecoin-project/go-jsonrpc v0.1.1-0.20200602181149-522144ab4e24/go.mod h1:j6zV//WXIIY5kky873Q3iIKt/ViOE8rcijovmpxrXzM=
|
||||
github.com/filecoin-project/go-padreader v0.0.0-20200210211231-548257017ca6 h1:92PET+sx1Hb4W/8CgFwGuxaKbttwY+UNspYZTvXY0vs=
|
||||
github.com/filecoin-project/go-padreader v0.0.0-20200210211231-548257017ca6/go.mod h1:0HgYnrkeSU4lu1p+LEOeDpFsNBssa0OGGriWdA4hvaE=
|
||||
github.com/filecoin-project/go-paramfetch v0.0.1/go.mod h1:fZzmf4tftbwf9S37XRifoJlz7nCjRdIrMGLR07dKLCc=
|
||||
github.com/filecoin-project/go-paramfetch v0.0.2-0.20200218225740-47c639bab663/go.mod h1:fZzmf4tftbwf9S37XRifoJlz7nCjRdIrMGLR07dKLCc=
|
||||
github.com/filecoin-project/go-paramfetch v0.0.2-0.20200605171344-fcac609550ca h1:OGykrCr6mSn/ckk2IFbIlkc76nsgEs7tSLhZXQt7+z4=
|
||||
github.com/filecoin-project/go-paramfetch v0.0.2-0.20200605171344-fcac609550ca/go.mod h1:fZzmf4tftbwf9S37XRifoJlz7nCjRdIrMGLR07dKLCc=
|
||||
github.com/filecoin-project/go-paramfetch v0.0.2-0.20200701152213-3e0f0afdc261 h1:A256QonvzRaknIIAuWhe/M2dpV2otzs3NBhi5TWa/UA=
|
||||
github.com/filecoin-project/go-paramfetch v0.0.2-0.20200701152213-3e0f0afdc261/go.mod h1:fZzmf4tftbwf9S37XRifoJlz7nCjRdIrMGLR07dKLCc=
|
||||
github.com/filecoin-project/go-statemachine v0.0.0-20200226041606-2074af6d51d9 h1:k9qVR9ItcziSB2rxtlkN/MDWNlbsI6yzec+zjUatLW0=
|
||||
github.com/filecoin-project/go-statemachine v0.0.0-20200226041606-2074af6d51d9/go.mod h1:FGwQgZAt2Gh5mjlwJUlVB62JeYdo+if0xWxSEfBD9ig=
|
||||
github.com/filecoin-project/go-statemachine v0.0.0-20200612181802-4eb3d0c68eba h1:GEWb/6KQyNZt4jm8fgVcIFPH0ElAGXfHM59ZSiqPTvY=
|
||||
@@ -253,17 +254,22 @@ github.com/filecoin-project/go-statestore v0.1.0/go.mod h1:LFc9hD+fRxPqiHiaqUEZO
|
||||
github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b h1:fkRZSPrYpk42PV3/lIXiL0LHetxde7vyYYvSsttQtfg=
|
||||
github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/go.mod h1:Q0GQOBtKf1oE10eSXSlhN45kDBdGvEcVOqMiffqX+N8=
|
||||
github.com/filecoin-project/sector-storage v0.0.0-20200615154852-728a47ab99d6/go.mod h1:M59QnAeA/oV+Z8oHFLoNpGMv0LZ8Rll+vHVXX7GirPM=
|
||||
github.com/filecoin-project/sector-storage v0.0.0-20200618073200-d9de9b7cb4b4 h1:lQC8Fbyn31/H4QxYAYwVV3PYZ9vS61EmjktZc5CaiYs=
|
||||
github.com/filecoin-project/sector-storage v0.0.0-20200618073200-d9de9b7cb4b4/go.mod h1:M59QnAeA/oV+Z8oHFLoNpGMv0LZ8Rll+vHVXX7GirPM=
|
||||
github.com/filecoin-project/sector-storage v0.0.0-20200625154333-98ef8e4ef246 h1:NfYQRmVRe0LzlNbK5Ket3vbBOwFD5TvtcNtfo/Sd8mg=
|
||||
github.com/filecoin-project/sector-storage v0.0.0-20200625154333-98ef8e4ef246/go.mod h1:8f0hWDzzIi1hKs4IVKH9RnDsO4LEHVz8BNat0okDOuY=
|
||||
github.com/filecoin-project/sector-storage v0.0.0-20200630180318-4c1968f62a8f h1:EHKqNJNIcYggqfrd5nu7SV1KR93ReZygfdSV0w/jefQ=
|
||||
github.com/filecoin-project/sector-storage v0.0.0-20200630180318-4c1968f62a8f/go.mod h1:r12d7tsmJKz8QDGoCvl65Ay2al6mOgDqxAGUxbyrgMs=
|
||||
github.com/filecoin-project/specs-actors v0.0.0-20200210130641-2d1fbd8672cf/go.mod h1:xtDZUB6pe4Pksa/bAJbJ693OilaC5Wbot9jMhLm3cZA=
|
||||
github.com/filecoin-project/specs-actors v0.3.0/go.mod h1:nQYnFbQ7Y0bHZyq6HDEuVlCPR+U3z5Q3wMOQ+2aiV+Y=
|
||||
github.com/filecoin-project/specs-actors v0.6.0/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY=
|
||||
github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121 h1:oRA+b4iN4H86xXDXbU3TOyvmBZp7//c5VqTc0oJ6nLg=
|
||||
github.com/filecoin-project/specs-actors v0.6.2-0.20200617175406-de392ca14121/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY=
|
||||
github.com/filecoin-project/specs-actors v0.6.1/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY=
|
||||
github.com/filecoin-project/specs-actors v0.6.2-0.20200702170846-2cd72643a5cf h1:2ERozAZteHYef3tVLVJRepzYieLtJdxvfXNUel19CeU=
|
||||
github.com/filecoin-project/specs-actors v0.6.2-0.20200702170846-2cd72643a5cf/go.mod h1:dRdy3cURykh2R8O/DKqy8olScl70rmIS7GrB4hB1IDY=
|
||||
github.com/filecoin-project/specs-storage v0.1.0 h1:PkDgTOT5W5Ao7752onjDl4QSv+sgOVdJbvFjOnD5w94=
|
||||
github.com/filecoin-project/specs-storage v0.1.0/go.mod h1:Pr5ntAaxsh+sLG/LYiL4tKzvA83Vk5vLODYhfNwOg7k=
|
||||
github.com/filecoin-project/storage-fsm v0.0.0-20200617183754-4380106d3e94 h1:zPKiZPMgkFF0Lq13hsk8lcWlxeVAs6vvJaa3uHn9v70=
|
||||
github.com/filecoin-project/storage-fsm v0.0.0-20200617183754-4380106d3e94/go.mod h1:q1YCutTSMq/yGYvDPHReT37bPfDLHltnwJutzR9kOY0=
|
||||
github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea h1:iixjULRQFPn7Q9KlIqfwLJnlAXO10bbkI+xy5GKGdLY=
|
||||
github.com/filecoin-project/specs-storage v0.1.1-0.20200622113353-88a9704877ea/go.mod h1:Pr5ntAaxsh+sLG/LYiL4tKzvA83Vk5vLODYhfNwOg7k=
|
||||
github.com/filecoin-project/storage-fsm v0.0.0-20200625160832-379a4655b044 h1:i4oMhv1kx/MAUxRN4EM5tag5fI1uagrwQwINgKrzUt4=
|
||||
github.com/filecoin-project/storage-fsm v0.0.0-20200625160832-379a4655b044/go.mod h1:JD7fmV1BYADDcy4EYQnqFH/rUzXsh0Je0jXarCjZqSk=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
@@ -464,6 +470,8 @@ github.com/ipfs/go-blockservice v0.0.7/go.mod h1:EOfb9k/Y878ZTRY/CH0x5+ATtaipfbR
|
||||
github.com/ipfs/go-blockservice v0.1.0/go.mod h1:hzmMScl1kXHg3M2BjTymbVPjv627N7sYcvYaKbop39M=
|
||||
github.com/ipfs/go-blockservice v0.1.3 h1:9XgsPMwwWJSC9uVr2pMDsW2qFTBSkxpGMhmna8mIjPM=
|
||||
github.com/ipfs/go-blockservice v0.1.3/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU=
|
||||
github.com/ipfs/go-blockservice v0.1.4-0.20200624145336-a978cec6e834 h1:hFJoI1D2a3MqiNkSb4nKwrdkhCngUxUTFNwVwovZX2s=
|
||||
github.com/ipfs/go-blockservice v0.1.4-0.20200624145336-a978cec6e834/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU=
|
||||
github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
|
||||
github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
|
||||
github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
|
||||
@@ -472,6 +480,8 @@ github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj
|
||||
github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog=
|
||||
github.com/ipfs/go-cid v0.0.6-0.20200501230655-7c82f3b81c00 h1:QN88Q0kT2QiDaLxpR/SDsqOBtNIEF/F3n96gSDUimkA=
|
||||
github.com/ipfs/go-cid v0.0.6-0.20200501230655-7c82f3b81c00/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog=
|
||||
github.com/ipfs/go-cid v0.0.6 h1:go0y+GcDOGeJIV01FeBsta4FHngoA4Wz7KMeLkXAhMs=
|
||||
github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I=
|
||||
github.com/ipfs/go-cidutil v0.0.2 h1:CNOboQf1t7Qp0nuNh8QMmhJs0+Q//bRL1axtCnIB1Yo=
|
||||
github.com/ipfs/go-cidutil v0.0.2/go.mod h1:ewllrvrxG6AMYStla3GD7Cqn+XYSLqjK0vc+086tB6s=
|
||||
github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE=
|
||||
@@ -557,6 +567,8 @@ github.com/ipfs/go-ipfs-routing v0.1.0 h1:gAJTT1cEeeLj6/DlLX6t+NxD9fQe2ymTO6qWRD
|
||||
github.com/ipfs/go-ipfs-routing v0.1.0/go.mod h1:hYoUkJLyAUKhF58tysKpids8RNDPO42BVMgK5dNsoqY=
|
||||
github.com/ipfs/go-ipfs-util v0.0.1 h1:Wz9bL2wB2YBJqggkA4dD7oSmqB4cAnpNbGrlHJulv50=
|
||||
github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc=
|
||||
github.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8=
|
||||
github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ=
|
||||
github.com/ipfs/go-ipld-cbor v0.0.1/go.mod h1:RXHr8s4k0NE0TKhnrxqZC9M888QfsBN9rhS5NjfKzY8=
|
||||
github.com/ipfs/go-ipld-cbor v0.0.2/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc=
|
||||
github.com/ipfs/go-ipld-cbor v0.0.3/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc=
|
||||
@@ -582,8 +594,8 @@ github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBW
|
||||
github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0=
|
||||
github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw=
|
||||
github.com/ipfs/go-log/v2 v2.0.8/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw=
|
||||
github.com/ipfs/go-log/v2 v2.1.2-0.20200609205458-f8d20c392cb7 h1:LtL/rvdfbKSthZGmAAD9o4KKg6HA6Qn8gXCCdgnj7lw=
|
||||
github.com/ipfs/go-log/v2 v2.1.2-0.20200609205458-f8d20c392cb7/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM=
|
||||
github.com/ipfs/go-log/v2 v2.1.2-0.20200626104915-0016c0b4b3e4 h1:3bijxqzQ1O9yg7gd7Aqk80oaEvsJ+uXw0zSvi2qR3Jw=
|
||||
github.com/ipfs/go-log/v2 v2.1.2-0.20200626104915-0016c0b4b3e4/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM=
|
||||
github.com/ipfs/go-merkledag v0.0.3/go.mod h1:Oc5kIXLHokkE1hWGMBHw+oxehkAaTOqtEb7Zbh6BhLA=
|
||||
github.com/ipfs/go-merkledag v0.0.6/go.mod h1:QYPdnlvkOg7GnQRofu9XZimC5ZW5Wi3bKys/4GQQfto=
|
||||
github.com/ipfs/go-merkledag v0.2.3/go.mod h1:SQiXrtSts3KGNmgOzMICy5c0POOpUNQLvB3ClKnBAlk=
|
||||
@@ -723,8 +735,8 @@ github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniV
|
||||
github.com/libp2p/go-libp2p v0.8.2/go.mod h1:NQDA/F/qArMHGe0J7sDScaKjW8Jh4y/ozQqBbYJ+BnA=
|
||||
github.com/libp2p/go-libp2p v0.8.3/go.mod h1:EsH1A+8yoWK+L4iKcbPYu6MPluZ+CHWI9El8cTaefiM=
|
||||
github.com/libp2p/go-libp2p v0.9.2/go.mod h1:cunHNLDVus66Ct9iXXcjKRLdmHdFdHVe1TAnbubJQqQ=
|
||||
github.com/libp2p/go-libp2p v0.9.4 h1:yighwjFvsF/qQaGtHPZfxcF+ph4ydCNnsKvg712lYRo=
|
||||
github.com/libp2p/go-libp2p v0.9.4/go.mod h1:NzQcC2o19xgwGqCmjx7DN+4h2F13qPCZ9UJmweYzsnU=
|
||||
github.com/libp2p/go-libp2p v0.10.0 h1:7ooOvK1wi8eLpyTppy8TeH43UHy5uI75GAHGJxenUi0=
|
||||
github.com/libp2p/go-libp2p v0.10.0/go.mod h1:yBJNpb+mGJdgrwbKAKrhPU0u3ogyNFTfjJ6bdM+Q/G8=
|
||||
github.com/libp2p/go-libp2p-autonat v0.0.2/go.mod h1:fs71q5Xk+pdnKU014o2iq1RhMs9/PMaG5zXRFNnIIT4=
|
||||
github.com/libp2p/go-libp2p-autonat v0.0.6/go.mod h1:uZneLdOkZHro35xIhpbtTzLlgYturpu4J5+0cZK3MqE=
|
||||
github.com/libp2p/go-libp2p-autonat v0.1.0/go.mod h1:1tLf2yXxiE/oKGtDwPYWTSYG3PtvYlJmg7NeVtPRqH8=
|
||||
@@ -750,6 +762,8 @@ github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3
|
||||
github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo=
|
||||
github.com/libp2p/go-libp2p-circuit v0.2.2 h1:87RLabJ9lrhoiSDDZyCJ80ZlI5TLJMwfyoGAaWXzWqA=
|
||||
github.com/libp2p/go-libp2p-circuit v0.2.2/go.mod h1:nkG3iE01tR3FoQ2nMm06IUrCpCyJp1Eo4A1xYdpjfs4=
|
||||
github.com/libp2p/go-libp2p-circuit v0.2.3 h1:3Uw1fPHWrp1tgIhBz0vSOxRUmnKL8L/NGUyEd5WfSGM=
|
||||
github.com/libp2p/go-libp2p-circuit v0.2.3/go.mod h1:nkG3iE01tR3FoQ2nMm06IUrCpCyJp1Eo4A1xYdpjfs4=
|
||||
github.com/libp2p/go-libp2p-connmgr v0.1.1/go.mod h1:wZxh8veAmU5qdrfJ0ZBLcU8oJe9L82ciVP/fl1VHjXk=
|
||||
github.com/libp2p/go-libp2p-connmgr v0.2.3/go.mod h1:Gqjg29zI8CwXX21zRxy6gOg8VYu3zVerJRt2KyktzH4=
|
||||
github.com/libp2p/go-libp2p-connmgr v0.2.4 h1:TMS0vc0TCBomtQJyWr7fYxcVYYhx+q/2gF++G5Jkl/w=
|
||||
@@ -776,6 +790,8 @@ github.com/libp2p/go-libp2p-core v0.5.5/go.mod h1:vj3awlOr9+GMZJFH9s4mpt9RHHgGqe
|
||||
github.com/libp2p/go-libp2p-core v0.5.6/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo=
|
||||
github.com/libp2p/go-libp2p-core v0.5.7 h1:QK3xRwFxqd0Xd9bSZL+8yZ8ncZZbl6Zngd/+Y+A6sgQ=
|
||||
github.com/libp2p/go-libp2p-core v0.5.7/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo=
|
||||
github.com/libp2p/go-libp2p-core v0.6.0 h1:u03qofNYTBN+yVg08PuAKylZogVf0xcTEeM8skGf+ak=
|
||||
github.com/libp2p/go-libp2p-core v0.6.0/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo=
|
||||
github.com/libp2p/go-libp2p-crypto v0.0.1/go.mod h1:yJkNyDmO341d5wwXxDUGO0LykUVT72ImHNUqh5D/dBE=
|
||||
github.com/libp2p/go-libp2p-crypto v0.0.2/go.mod h1:eETI5OUfBnvARGOHrJz2eWNyTUxEGZnBxMcbUjfIj4I=
|
||||
github.com/libp2p/go-libp2p-crypto v0.1.0 h1:k9MFy+o2zGDNGsaoZl0MA3iZ75qXxr9OOoAZF+sD5OQ=
|
||||
@@ -835,17 +851,17 @@ github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRj
|
||||
github.com/libp2p/go-libp2p-peerstore v0.2.3/go.mod h1:K8ljLdFn590GMttg/luh4caB/3g0vKuY01psze0upRw=
|
||||
github.com/libp2p/go-libp2p-peerstore v0.2.4 h1:jU9S4jYN30kdzTpDAR7SlHUD+meDUjTODh4waLWF1ws=
|
||||
github.com/libp2p/go-libp2p-peerstore v0.2.4/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s=
|
||||
github.com/libp2p/go-libp2p-peerstore v0.2.6 h1:2ACefBX23iMdJU9Ke+dcXt3w86MIryes9v7In4+Qq3U=
|
||||
github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s=
|
||||
github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k=
|
||||
github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA=
|
||||
github.com/libp2p/go-libp2p-protocol v0.0.1/go.mod h1:Af9n4PiruirSDjHycM1QuiMi/1VZNHYcK8cLgFJLZ4s=
|
||||
github.com/libp2p/go-libp2p-protocol v0.1.0/go.mod h1:KQPHpAabB57XQxGrXCNvbL6UEXfQqUgC/1adR2Xtflk=
|
||||
github.com/libp2p/go-libp2p-pubsub v0.1.1/go.mod h1:ZwlKzRSe1eGvSIdU5bD7+8RZN/Uzw0t1Bp9R1znpR/Q=
|
||||
github.com/libp2p/go-libp2p-pubsub v0.3.1/go.mod h1:TxPOBuo1FPdsTjFnv+FGZbNbWYsp74Culx+4ViQpato=
|
||||
github.com/libp2p/go-libp2p-pubsub v0.3.2-0.20200527132641-c0712c6e92cf/go.mod h1:TxPOBuo1FPdsTjFnv+FGZbNbWYsp74Culx+4ViQpato=
|
||||
github.com/libp2p/go-libp2p-pubsub v0.3.2 h1:k3cJm5JW5mjaWZkobS50sJLJWaB2mBi0HW4eRlE8mSo=
|
||||
github.com/libp2p/go-libp2p-pubsub v0.3.2/go.mod h1:Uss7/Cfz872KggNb+doCVPHeCDmXB7z500m/R8DaAUk=
|
||||
github.com/libp2p/go-libp2p-quic-transport v0.1.1/go.mod h1:wqG/jzhF3Pu2NrhJEvE+IE0NTHNXslOPn9JQzyCAxzU=
|
||||
github.com/libp2p/go-libp2p-quic-transport v0.3.7/go.mod h1:Kr4aDtnfHHNeENn5J+sZIVc+t8HpQn9W6BOxhVGHbgI=
|
||||
github.com/libp2p/go-libp2p-quic-transport v0.5.0 h1:BUN1lgYNUrtv4WLLQ5rQmC9MCJ6uEXusezGvYRNoJXE=
|
||||
github.com/libp2p/go-libp2p-quic-transport v0.5.0/go.mod h1:IEcuC5MLxvZ5KuHKjRu+dr3LjCT1Be3rcD/4d8JrX8M=
|
||||
github.com/libp2p/go-libp2p-record v0.0.1/go.mod h1:grzqg263Rug/sRex85QrDOLntdFAymLDLm7lxMgU79Q=
|
||||
@@ -871,8 +887,8 @@ github.com/libp2p/go-libp2p-swarm v0.2.1/go.mod h1:x07b4zkMFo2EvgPV2bMTlNmdQc8i+
|
||||
github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU=
|
||||
github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM=
|
||||
github.com/libp2p/go-libp2p-swarm v0.2.4/go.mod h1:/xIpHFPPh3wmSthtxdGbkHZ0OET1h/GGZes8Wku/M5Y=
|
||||
github.com/libp2p/go-libp2p-swarm v0.2.6 h1:UhMXIa+yCOALQyceENEIStMlbTCzOM6aWo6vw8QW17Q=
|
||||
github.com/libp2p/go-libp2p-swarm v0.2.6/go.mod h1:F9hrkZjO7dDbcEiYii/fAB1QdpLuU6h1pa4P5VNsEgc=
|
||||
github.com/libp2p/go-libp2p-swarm v0.2.7 h1:4lV/sf7f0NuVqunOpt1I11+Z54+xp+m0eeAvxj/LyRc=
|
||||
github.com/libp2p/go-libp2p-swarm v0.2.7/go.mod h1:ZSJ0Q+oq/B1JgfPHJAT2HTall+xYRNYp1xs4S2FBWKA=
|
||||
github.com/libp2p/go-libp2p-testing v0.0.1/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E=
|
||||
github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E=
|
||||
github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E=
|
||||
@@ -971,7 +987,6 @@ github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/h
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/lucas-clemente/quic-go v0.11.2/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
|
||||
github.com/lucas-clemente/quic-go v0.15.7/go.mod h1:Myi1OyS0FOjL3not4BxT7KN29bRkcMUV5JVVFLKtDp8=
|
||||
github.com/lucas-clemente/quic-go v0.16.0 h1:jJw36wfzGJhmOhAOaOC2lS36WgeqXQszH47A7spo1LI=
|
||||
github.com/lucas-clemente/quic-go v0.16.0/go.mod h1:I0+fcNTdb9eS1ZcjQZbDVPGchJ86chcIxPALn9lEJqE=
|
||||
github.com/lufia/iostat v1.1.0/go.mod h1:rEPNA0xXgjHQjuI5Cy05sLlS2oRcSlWHRLrvh/AQ+Pg=
|
||||
@@ -1042,6 +1057,8 @@ github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
|
||||
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI=
|
||||
github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
|
||||
github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4=
|
||||
github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM=
|
||||
github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
|
||||
github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
|
||||
github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
|
||||
@@ -1071,6 +1088,8 @@ github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysj
|
||||
github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs=
|
||||
github.com/multiformats/go-multibase v0.0.2 h1:2pAgScmS1g9XjH7EtAfNhTuyrWYEWcxy0G5Wo85hWDA=
|
||||
github.com/multiformats/go-multibase v0.0.2/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs=
|
||||
github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk=
|
||||
github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc=
|
||||
github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U=
|
||||
github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po=
|
||||
github.com/multiformats/go-multihash v0.0.7/go.mod h1:XuKXPp8VHcTygube3OWZC+aZrA+H1IhmjoCDtJc7PXM=
|
||||
@@ -1290,6 +1309,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
@@ -1777,6 +1799,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package bls
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"github.com/filecoin-project/go-address"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkBLSSign(b *testing.B) {
|
||||
signer := blsSigner{}
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
pk, _ := signer.GenPrivate()
|
||||
randMsg := make([]byte, 32)
|
||||
rand.Read(randMsg)
|
||||
b.StartTimer()
|
||||
|
||||
_, _ = signer.Sign(pk, randMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBLSVerify(b *testing.B) {
|
||||
signer := blsSigner{}
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
randMsg := make([]byte, 32)
|
||||
rand.Read(randMsg)
|
||||
|
||||
priv, _ := signer.GenPrivate()
|
||||
pk, _ := signer.ToPublic(priv)
|
||||
addr, _ := address.NewBLSAddress(pk)
|
||||
sig, _ := signer.Sign(priv, randMsg)
|
||||
|
||||
b.StartTimer()
|
||||
|
||||
_ = signer.Verify(sig, addr, randMsg)
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"github.com/filecoin-project/specs-actors/actors/crypto"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/events"
|
||||
@@ -80,7 +80,15 @@ func (n *ClientNodeAdapter) ListStorageProviders(ctx context.Context, encodedTs
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storageProviderInfo := utils.NewStorageProviderInfo(addr, mi.Worker, mi.SectorSize, peer.ID(mi.PeerId))
|
||||
multiaddrs := make([]multiaddr.Multiaddr, 0, len(mi.Multiaddrs))
|
||||
for _, a := range mi.Multiaddrs {
|
||||
maddr, err := multiaddr.NewMultiaddrBytes(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
multiaddrs = append(multiaddrs, maddr)
|
||||
}
|
||||
storageProviderInfo := utils.NewStorageProviderInfo(addr, mi.Worker, mi.SectorSize, mi.PeerId, multiaddrs)
|
||||
out = append(out, &storageProviderInfo)
|
||||
}
|
||||
|
||||
@@ -322,7 +330,7 @@ func (c *ClientNodeAdapter) OnDealSectorCommitted(ctx context.Context, provider
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.ev.Called(checkFunc, called, revert, build.MessageConfidence+1, build.SealRandomnessLookbackLimit, matchEvent); err != nil {
|
||||
if err := c.ev.Called(checkFunc, called, revert, int(build.MessageConfidence+1), build.SealRandomnessLookbackLimit, matchEvent); err != nil {
|
||||
return xerrors.Errorf("failed to set up called handler: %w", err)
|
||||
}
|
||||
|
||||
@@ -405,4 +413,25 @@ func (n *ClientNodeAdapter) WaitForMessage(ctx context.Context, mcid cid.Cid, cb
|
||||
return cb(receipt.Receipt.ExitCode, receipt.Receipt.Return, nil)
|
||||
}
|
||||
|
||||
func (n *ClientNodeAdapter) GetMinerInfo(ctx context.Context, addr address.Address, encodedTs shared.TipSetToken) (*storagemarket.StorageProviderInfo, error) {
|
||||
tsk, err := types.TipSetKeyFromBytes(encodedTs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mi, err := n.StateMinerInfo(ctx, addr, tsk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
multiaddrs := make([]multiaddr.Multiaddr, 0, len(mi.Multiaddrs))
|
||||
for _, a := range mi.Multiaddrs {
|
||||
maddr, err := multiaddr.NewMultiaddrBytes(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
multiaddrs = append(multiaddrs, maddr)
|
||||
}
|
||||
out := utils.NewStorageProviderInfo(addr, mi.Worker, mi.SectorSize, mi.PeerId, multiaddrs)
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
var _ storagemarket.StorageClientNode = &ClientNodeAdapter{}
|
||||
|
||||
@@ -321,7 +321,7 @@ func (n *ProviderNodeAdapter) OnDealSectorCommitted(ctx context.Context, provide
|
||||
|
||||
}
|
||||
|
||||
if err := n.ev.Called(checkFunc, called, revert, build.MessageConfidence+1, build.SealRandomnessLookbackLimit, matchEvent); err != nil {
|
||||
if err := n.ev.Called(checkFunc, called, revert, int(build.MessageConfidence+1), build.SealRandomnessLookbackLimit, matchEvent); err != nil {
|
||||
return xerrors.Errorf("failed to set up called handler: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,17 +6,20 @@ import (
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/market"
|
||||
peer "github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-fil-markets/storagemarket"
|
||||
)
|
||||
|
||||
func NewStorageProviderInfo(address address.Address, miner address.Address, sectorSize abi.SectorSize, peer peer.ID) storagemarket.StorageProviderInfo {
|
||||
func NewStorageProviderInfo(address address.Address, miner address.Address, sectorSize abi.SectorSize, peer peer.ID, addrs []multiaddr.Multiaddr) storagemarket.StorageProviderInfo {
|
||||
|
||||
return storagemarket.StorageProviderInfo{
|
||||
Address: address,
|
||||
Worker: miner,
|
||||
SectorSize: uint64(sectorSize),
|
||||
PeerID: peer,
|
||||
Addrs: addrs,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-7
@@ -42,7 +42,7 @@ func NewMiner(api api.FullNode, epp gen.WinningPoStProver, addr address.Address)
|
||||
address: addr,
|
||||
waitFunc: func(ctx context.Context, baseTime uint64) (func(bool), error) {
|
||||
// Wait around for half the block time in case other parents come in
|
||||
deadline := baseTime + build.PropagationDelay
|
||||
deadline := baseTime + build.PropagationDelaySecs
|
||||
time.Sleep(time.Until(time.Unix(int64(deadline), 0)))
|
||||
|
||||
return func(bool) {}, nil
|
||||
@@ -140,7 +140,7 @@ func (m *Miner) mine(ctx context.Context) {
|
||||
onDone, err := m.waitFunc(ctx, prebase.TipSet.MinTimestamp())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
continue
|
||||
}
|
||||
|
||||
base, err := m.GetBestMiningCandidate(ctx)
|
||||
@@ -150,7 +150,7 @@ func (m *Miner) mine(ctx context.Context) {
|
||||
}
|
||||
if base.TipSet.Equals(lastBase.TipSet) && lastBase.NullRounds == base.NullRounds {
|
||||
log.Warnf("BestMiningCandidate from the previous round: %s (nulls:%d)", lastBase.TipSet.Cids(), lastBase.NullRounds)
|
||||
m.niceSleep(build.BlockDelay * time.Second)
|
||||
m.niceSleep(time.Duration(build.BlockDelaySecs) * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ func (m *Miner) mine(ctx context.Context) {
|
||||
// has enough time to form.
|
||||
//
|
||||
// See: https://github.com/filecoin-project/lotus/issues/1845
|
||||
nextRound := time.Unix(int64(base.TipSet.MinTimestamp()+uint64(build.BlockDelay*base.NullRounds))+int64(build.PropagationDelay), 0)
|
||||
nextRound := time.Unix(int64(base.TipSet.MinTimestamp()+build.BlockDelaySecs*uint64(base.NullRounds))+int64(build.PropagationDelaySecs), 0)
|
||||
|
||||
select {
|
||||
case <-time.After(time.Until(nextRound)):
|
||||
@@ -215,6 +215,9 @@ type MiningBase struct {
|
||||
}
|
||||
|
||||
func (m *Miner) GetBestMiningCandidate(ctx context.Context) (*MiningBase, error) {
|
||||
m.lk.Lock()
|
||||
defer m.lk.Unlock()
|
||||
|
||||
bts, err := m.api.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -252,6 +255,16 @@ func (m *Miner) hasPower(ctx context.Context, addr address.Address, ts *types.Ti
|
||||
return mpower.MinerPower.QualityAdjPower.GreaterThanEqual(power.ConsensusMinerMinPower), nil
|
||||
}
|
||||
|
||||
// mineOne attempts to mine a single block, and does so synchronously, if and
|
||||
// only if we are eligible to mine.
|
||||
//
|
||||
// {hint/landmark}: This method coordinates all the steps involved in mining a
|
||||
// block, including the condition of whether mine or not at all depending on
|
||||
// whether we win the round or not.
|
||||
//
|
||||
// This method does the following:
|
||||
//
|
||||
// 1.
|
||||
func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg, error) {
|
||||
log.Debugw("attempting to mine a block", "tipset", types.LogCids(base.TipSet.Cids()))
|
||||
start := time.Now()
|
||||
@@ -343,7 +356,7 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg,
|
||||
tCreateBlock := time.Now()
|
||||
dur := tCreateBlock.Sub(start)
|
||||
log.Infow("mined new block", "cid", b.Cid(), "height", b.Header.Height, "took", dur)
|
||||
if dur > time.Second*build.BlockDelay {
|
||||
if dur > time.Second*time.Duration(build.BlockDelaySecs) {
|
||||
log.Warn("CAUTION: block production took longer than the block delay. Your computer may not be fast enough to keep up")
|
||||
|
||||
log.Warnw("tMinerBaseInfo ", "duration", tMBI.Sub(start))
|
||||
@@ -404,7 +417,7 @@ func (m *Miner) createBlock(base *MiningBase, addr address.Address, ticket *type
|
||||
msgs = msgs[:build.BlockMessageLimit]
|
||||
}
|
||||
|
||||
uts := base.TipSet.MinTimestamp() + uint64(build.BlockDelay*(base.NullRounds+1))
|
||||
uts := base.TipSet.MinTimestamp() + build.BlockDelaySecs*(uint64(base.NullRounds)+1)
|
||||
|
||||
nheight := base.TipSet.Height() + base.NullRounds + 1
|
||||
|
||||
@@ -548,7 +561,7 @@ func SelectMessages(ctx context.Context, al ActorLookup, ts *types.TipSet, msgs
|
||||
}
|
||||
|
||||
if tooHighNonceMsgs > 0 {
|
||||
log.Warnf("%d messages in mempool had too high nonce", tooLowFundMsgs)
|
||||
log.Warnf("%d messages in mempool had too high nonce", tooHighNonceMsgs)
|
||||
}
|
||||
|
||||
sm := time.Now()
|
||||
|
||||
+11
-2
@@ -218,6 +218,7 @@ func Online() Option {
|
||||
|
||||
Override(new(dtypes.BootstrapPeers), modules.BuiltinBootstrap),
|
||||
Override(new(dtypes.DrandBootstrap), modules.DrandBootstrap),
|
||||
Override(new(dtypes.DrandConfig), modules.BuiltinDrandConfig),
|
||||
|
||||
Override(HandleIncomingMessagesKey, modules.HandleIncomingMessages),
|
||||
|
||||
@@ -312,8 +313,16 @@ func Online() Option {
|
||||
Override(new(gen.WinningPoStProver), storage.NewWinningPoStProver),
|
||||
Override(new(*miner.Miner), modules.SetupBlockProducer),
|
||||
|
||||
Override(new(dtypes.AcceptingStorageDealsConfigFunc), modules.NewAcceptingStorageDealsConfigFunc),
|
||||
Override(new(dtypes.SetAcceptingStorageDealsConfigFunc), modules.NewSetAcceptingStorageDealsConfigFunc),
|
||||
Override(new(dtypes.ConsiderOnlineStorageDealsConfigFunc), modules.NewConsiderOnlineStorageDealsConfigFunc),
|
||||
Override(new(dtypes.SetConsiderOnlineStorageDealsConfigFunc), modules.NewSetConsideringOnlineStorageDealsFunc),
|
||||
Override(new(dtypes.ConsiderOnlineRetrievalDealsConfigFunc), modules.NewConsiderOnlineRetrievalDealsConfigFunc),
|
||||
Override(new(dtypes.SetConsiderOnlineRetrievalDealsConfigFunc), modules.NewSetConsiderOnlineRetrievalDealsConfigFunc),
|
||||
Override(new(dtypes.StorageDealPieceCidBlocklistConfigFunc), modules.NewStorageDealPieceCidBlocklistConfigFunc),
|
||||
Override(new(dtypes.SetStorageDealPieceCidBlocklistConfigFunc), modules.NewSetStorageDealPieceCidBlocklistConfigFunc),
|
||||
Override(new(dtypes.ConsiderOfflineStorageDealsConfigFunc), modules.NewConsiderOfflineStorageDealsConfigFunc),
|
||||
Override(new(dtypes.SetConsiderOfflineStorageDealsConfigFunc), modules.NewSetConsideringOfflineStorageDealsFunc),
|
||||
Override(new(dtypes.ConsiderOfflineRetrievalDealsConfigFunc), modules.NewConsiderOfflineRetrievalDealsConfigFunc),
|
||||
Override(new(dtypes.SetConsiderOfflineRetrievalDealsConfigFunc), modules.NewSetConsiderOfflineRetrievalDealsConfigFunc),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
+12
-2
@@ -4,6 +4,8 @@ import (
|
||||
"encoding"
|
||||
"time"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
sectorstorage "github.com/filecoin-project/sector-storage"
|
||||
)
|
||||
|
||||
@@ -32,7 +34,11 @@ type StorageMiner struct {
|
||||
}
|
||||
|
||||
type DealmakingConfig struct {
|
||||
AcceptingStorageDeals bool
|
||||
ConsiderOnlineStorageDeals bool
|
||||
ConsiderOfflineStorageDeals bool
|
||||
ConsiderOnlineRetrievalDeals bool
|
||||
ConsiderOfflineRetrievalDeals bool
|
||||
PieceCidBlocklist []cid.Cid
|
||||
}
|
||||
|
||||
// API contains configs for API endpoint
|
||||
@@ -120,7 +126,11 @@ func DefaultStorageMiner() *StorageMiner {
|
||||
},
|
||||
|
||||
Dealmaking: DealmakingConfig{
|
||||
AcceptingStorageDeals: true,
|
||||
ConsiderOnlineStorageDeals: true,
|
||||
ConsiderOfflineStorageDeals: true,
|
||||
ConsiderOnlineRetrievalDeals: true,
|
||||
ConsiderOfflineRetrievalDeals: true,
|
||||
PieceCidBlocklist: []cid.Cid{},
|
||||
},
|
||||
}
|
||||
cfg.Common.API.ListenAddress = "/ip4/127.0.0.1/tcp/2345/http"
|
||||
|
||||
+49
-17
@@ -9,6 +9,7 @@ import (
|
||||
basicnode "github.com/ipld/go-ipld-prime/node/basic"
|
||||
"github.com/ipld/go-ipld-prime/traversal/selector"
|
||||
"github.com/ipld/go-ipld-prime/traversal/selector/builder"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
|
||||
"io"
|
||||
"os"
|
||||
@@ -31,7 +32,7 @@ import (
|
||||
"go.uber.org/fx"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-fil-markets/retrievalmarket"
|
||||
rm "github.com/filecoin-project/go-fil-markets/retrievalmarket"
|
||||
"github.com/filecoin-project/go-fil-markets/storagemarket"
|
||||
"github.com/filecoin-project/sector-storage/ffiwrapper"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
@@ -59,8 +60,8 @@ type API struct {
|
||||
paych.PaychAPI
|
||||
|
||||
SMDealClient storagemarket.StorageClient
|
||||
RetDiscovery retrievalmarket.PeerResolver
|
||||
Retrieval retrievalmarket.RetrievalClient
|
||||
RetDiscovery rm.PeerResolver
|
||||
Retrieval rm.RetrievalClient
|
||||
Chain *store.ChainStore
|
||||
|
||||
LocalDAG dtypes.ClientDAG
|
||||
@@ -90,6 +91,15 @@ func (a *API) ClientStartDeal(ctx context.Context, params *api.StartDealParams)
|
||||
return nil, xerrors.Errorf("failed getting peer ID: %w", err)
|
||||
}
|
||||
|
||||
multiaddrs := make([]multiaddr.Multiaddr, 0, len(mi.Multiaddrs))
|
||||
for _, a := range mi.Multiaddrs {
|
||||
maddr, err := multiaddr.NewMultiaddrBytes(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
multiaddrs = append(multiaddrs, maddr)
|
||||
}
|
||||
|
||||
md, err := a.StateMinerProvingDeadline(ctx, params.Miner, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed getting peer ID: %w", err)
|
||||
@@ -104,7 +114,7 @@ func (a *API) ClientStartDeal(ctx context.Context, params *api.StartDealParams)
|
||||
return nil, xerrors.New("data doesn't fit in a sector")
|
||||
}
|
||||
|
||||
providerInfo := utils.NewStorageProviderInfo(params.Miner, mi.Worker, mi.SectorSize, peer.ID(mi.PeerId))
|
||||
providerInfo := utils.NewStorageProviderInfo(params.Miner, mi.Worker, mi.SectorSize, mi.PeerId, multiaddrs)
|
||||
|
||||
dealStart := params.DealStartEpoch
|
||||
if dealStart <= 0 { // unset, or explicitly 'epoch undefined'
|
||||
@@ -202,7 +212,7 @@ func (a *API) ClientFindData(ctx context.Context, root cid.Cid) ([]api.QueryOffe
|
||||
|
||||
out := make([]api.QueryOffer, len(peers))
|
||||
for k, p := range peers {
|
||||
out[k] = a.makeRetrievalQuery(ctx, p, root, retrievalmarket.QueryParams{})
|
||||
out[k] = a.makeRetrievalQuery(ctx, p, root, rm.QueryParams{})
|
||||
}
|
||||
|
||||
return out, nil
|
||||
@@ -213,25 +223,25 @@ func (a *API) ClientMinerQueryOffer(ctx context.Context, payload cid.Cid, miner
|
||||
if err != nil {
|
||||
return api.QueryOffer{}, err
|
||||
}
|
||||
rp := retrievalmarket.RetrievalPeer{
|
||||
rp := rm.RetrievalPeer{
|
||||
Address: miner,
|
||||
ID: mi.PeerId,
|
||||
}
|
||||
return a.makeRetrievalQuery(ctx, rp, payload, retrievalmarket.QueryParams{}), nil
|
||||
return a.makeRetrievalQuery(ctx, rp, payload, rm.QueryParams{}), nil
|
||||
}
|
||||
|
||||
func (a *API) makeRetrievalQuery(ctx context.Context, rp retrievalmarket.RetrievalPeer, payload cid.Cid, qp retrievalmarket.QueryParams) api.QueryOffer {
|
||||
func (a *API) makeRetrievalQuery(ctx context.Context, rp rm.RetrievalPeer, payload cid.Cid, qp rm.QueryParams) api.QueryOffer {
|
||||
queryResponse, err := a.Retrieval.Query(ctx, rp, payload, qp)
|
||||
if err != nil {
|
||||
return api.QueryOffer{Err: err.Error(), Miner: rp.Address, MinerPeerID: rp.ID}
|
||||
}
|
||||
var errStr string
|
||||
switch queryResponse.Status {
|
||||
case retrievalmarket.QueryResponseAvailable:
|
||||
case rm.QueryResponseAvailable:
|
||||
errStr = ""
|
||||
case retrievalmarket.QueryResponseUnavailable:
|
||||
case rm.QueryResponseUnavailable:
|
||||
errStr = fmt.Sprintf("retrieval query offer was unavailable: %s", queryResponse.Message)
|
||||
case retrievalmarket.QueryResponseError:
|
||||
case rm.QueryResponseError:
|
||||
errStr = fmt.Sprintf("retrieval query offer errored: %s", queryResponse.Message)
|
||||
}
|
||||
|
||||
@@ -345,13 +355,35 @@ func (a *API) ClientRetrieve(ctx context.Context, order api.RetrievalOrder, ref
|
||||
|
||||
retrievalResult := make(chan error, 1)
|
||||
|
||||
unsubscribe := a.Retrieval.SubscribeToEvents(func(event retrievalmarket.ClientEvent, state retrievalmarket.ClientDealState) {
|
||||
unsubscribe := a.Retrieval.SubscribeToEvents(func(event rm.ClientEvent, state rm.ClientDealState) {
|
||||
if state.PayloadCID.Equals(order.Root) {
|
||||
switch state.Status {
|
||||
case retrievalmarket.DealStatusFailed, retrievalmarket.DealStatusErrored:
|
||||
retrievalResult <- xerrors.Errorf("Retrieval Error: %s", state.Message)
|
||||
case retrievalmarket.DealStatusCompleted:
|
||||
case rm.DealStatusCompleted:
|
||||
retrievalResult <- nil
|
||||
case rm.DealStatusRejected:
|
||||
retrievalResult <- xerrors.Errorf("Retrieval Proposal Rejected: %s", state.Message)
|
||||
case
|
||||
rm.DealStatusDealNotFound,
|
||||
rm.DealStatusErrored,
|
||||
rm.DealStatusFailed:
|
||||
retrievalResult <- xerrors.Errorf("Retrieval Error: %s", state.Message)
|
||||
case
|
||||
rm.DealStatusAccepted,
|
||||
rm.DealStatusAwaitingAcceptance,
|
||||
rm.DealStatusBlocksComplete,
|
||||
rm.DealStatusFinalizing,
|
||||
rm.DealStatusFundsNeeded,
|
||||
rm.DealStatusFundsNeededLastPayment,
|
||||
rm.DealStatusNew,
|
||||
rm.DealStatusOngoing,
|
||||
rm.DealStatusPaymentChannelAddingFunds,
|
||||
rm.DealStatusPaymentChannelAllocatingLane,
|
||||
rm.DealStatusPaymentChannelCreating,
|
||||
rm.DealStatusPaymentChannelReady,
|
||||
rm.DealStatusVerified:
|
||||
return
|
||||
default:
|
||||
retrievalResult <- xerrors.Errorf("Unhandled Retrieval Status: %+v", state.Status)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -361,7 +393,7 @@ func (a *API) ClientRetrieve(ctx context.Context, order api.RetrievalOrder, ref
|
||||
_, err := a.Retrieval.Retrieve(
|
||||
ctx,
|
||||
order.Root,
|
||||
retrievalmarket.NewParamsV0(ppb, order.PaymentInterval, order.PaymentIntervalIncrease),
|
||||
rm.NewParamsV0(ppb, order.PaymentInterval, order.PaymentIntervalIncrease),
|
||||
order.Total,
|
||||
order.MinerPeerID,
|
||||
order.Client,
|
||||
@@ -409,7 +441,7 @@ func (a *API) ClientRetrieve(ctx context.Context, order api.RetrievalOrder, ref
|
||||
}
|
||||
|
||||
func (a *API) ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.SignedStorageAsk, error) {
|
||||
info := utils.NewStorageProviderInfo(miner, address.Undef, 0, p)
|
||||
info := utils.NewStorageProviderInfo(miner, address.Undef, 0, p, nil)
|
||||
signedAsk, err := a.SMDealClient.GetAsk(ctx, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -122,7 +122,7 @@ func (a *CommonAPI) Version(context.Context) (api.Version, error) {
|
||||
Version: build.UserVersion(),
|
||||
APIVersion: build.APIVersion,
|
||||
|
||||
BlockDelay: build.BlockDelay,
|
||||
BlockDelay: build.BlockDelaySecs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
+20
-16
@@ -15,6 +15,15 @@ import (
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-amt-ipld/v2"
|
||||
"github.com/filecoin-project/sector-storage/ffiwrapper"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/market"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
samsig "github.com/filecoin-project/specs-actors/actors/builtin/multisig"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/power"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/beacon"
|
||||
@@ -27,14 +36,6 @@ import (
|
||||
"github.com/filecoin-project/lotus/chain/wallet"
|
||||
"github.com/filecoin-project/lotus/lib/bufbstore"
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
"github.com/filecoin-project/sector-storage/ffiwrapper"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/market"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
samsig "github.com/filecoin-project/specs-actors/actors/builtin/multisig"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/power"
|
||||
)
|
||||
|
||||
type StateAPI struct {
|
||||
@@ -300,7 +301,7 @@ func (a *StateAPI) StateAccountKey(ctx context.Context, addr address.Address, ts
|
||||
return a.StateManager.ResolveToKeyAddress(ctx, addr, ts)
|
||||
}
|
||||
|
||||
func (a *StateAPI) StateReadState(ctx context.Context, act *types.Actor, tsk types.TipSetKey) (*api.ActorState, error) {
|
||||
func (a *StateAPI) StateReadState(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*api.ActorState, error) {
|
||||
ts, err := a.Chain.GetTipSetFromKey(tsk)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err)
|
||||
@@ -310,6 +311,11 @@ func (a *StateAPI) StateReadState(ctx context.Context, act *types.Actor, tsk typ
|
||||
return nil, err
|
||||
}
|
||||
|
||||
act, err := state.GetActor(actor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blk, err := state.Store.(*cbor.BasicIpldStore).Blocks.Get(act.Head)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -425,7 +431,7 @@ func (a *StateAPI) StateMarketParticipants(ctx context.Context, tsk types.TipSet
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
locked, err := hamt.LoadNode(ctx, cst, state.EscrowTable, hamt.UseTreeBitWidth(5))
|
||||
locked, err := hamt.LoadNode(ctx, cst, state.LockedTable, hamt.UseTreeBitWidth(5))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -489,13 +495,11 @@ func (a *StateAPI) StateMarketDeals(ctx context.Context, tsk types.TipSetKey) (m
|
||||
|
||||
var s market.DealState
|
||||
if err := sa.Get(ctx, i, &s); err != nil {
|
||||
if err != nil {
|
||||
if _, ok := err.(*amt.ErrNotFound); !ok {
|
||||
return xerrors.Errorf("failed to get state for deal in proposals array: %w", err)
|
||||
}
|
||||
|
||||
s.SectorStartEpoch = -1
|
||||
if _, ok := err.(*amt.ErrNotFound); !ok {
|
||||
return xerrors.Errorf("failed to get state for deal in proposals array: %w", err)
|
||||
}
|
||||
|
||||
s.SectorStartEpoch = -1
|
||||
}
|
||||
out[strconv.FormatInt(int64(i), 10)] = api.MarketDeal{
|
||||
Proposal: d,
|
||||
|
||||
+52
-3
@@ -43,7 +43,16 @@ type StorageMinerAPI struct {
|
||||
StorageMgr *sectorstorage.Manager `optional:"true"`
|
||||
*stores.Index
|
||||
|
||||
SetAcceptingStorageDealsConfigFunc dtypes.SetAcceptingStorageDealsConfigFunc
|
||||
ConsiderOnlineStorageDealsConfigFunc dtypes.ConsiderOnlineStorageDealsConfigFunc
|
||||
SetConsiderOnlineStorageDealsConfigFunc dtypes.SetConsiderOnlineStorageDealsConfigFunc
|
||||
ConsiderOnlineRetrievalDealsConfigFunc dtypes.ConsiderOnlineRetrievalDealsConfigFunc
|
||||
SetConsiderOnlineRetrievalDealsConfigFunc dtypes.SetConsiderOnlineRetrievalDealsConfigFunc
|
||||
StorageDealPieceCidBlocklistConfigFunc dtypes.StorageDealPieceCidBlocklistConfigFunc
|
||||
SetStorageDealPieceCidBlocklistConfigFunc dtypes.SetStorageDealPieceCidBlocklistConfigFunc
|
||||
ConsiderOfflineStorageDealsConfigFunc dtypes.ConsiderOfflineStorageDealsConfigFunc
|
||||
SetConsiderOfflineStorageDealsConfigFunc dtypes.SetConsiderOfflineStorageDealsConfigFunc
|
||||
ConsiderOfflineRetrievalDealsConfigFunc dtypes.ConsiderOfflineRetrievalDealsConfigFunc
|
||||
SetConsiderOfflineRetrievalDealsConfigFunc dtypes.SetConsiderOfflineRetrievalDealsConfigFunc
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) ServeRemote(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -172,6 +181,10 @@ func (sm *StorageMinerAPI) SectorsUpdate(ctx context.Context, id abi.SectorNumbe
|
||||
return sm.Miner.ForceSectorState(ctx, id, sealing.SectorState(state))
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) SectorRemove(ctx context.Context, id abi.SectorNumber) error {
|
||||
return sm.Miner.RemoveSector(ctx, id)
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) WorkerConnect(ctx context.Context, url string) error {
|
||||
w, err := connectRemoteWorker(ctx, sm, url)
|
||||
if err != nil {
|
||||
@@ -218,8 +231,36 @@ func (sm *StorageMinerAPI) DealsList(ctx context.Context) ([]storagemarket.Stora
|
||||
return sm.StorageProvider.ListDeals(ctx)
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) DealsSetAcceptingStorageDeals(ctx context.Context, b bool) error {
|
||||
return sm.SetAcceptingStorageDealsConfigFunc(b)
|
||||
func (sm *StorageMinerAPI) DealsConsiderOnlineStorageDeals(ctx context.Context) (bool, error) {
|
||||
return sm.ConsiderOnlineStorageDealsConfigFunc()
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) DealsSetConsiderOnlineStorageDeals(ctx context.Context, b bool) error {
|
||||
return sm.SetConsiderOnlineStorageDealsConfigFunc(b)
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) DealsConsiderOnlineRetrievalDeals(ctx context.Context) (bool, error) {
|
||||
return sm.ConsiderOnlineRetrievalDealsConfigFunc()
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) DealsSetConsiderOnlineRetrievalDeals(ctx context.Context, b bool) error {
|
||||
return sm.SetConsiderOnlineRetrievalDealsConfigFunc(b)
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) DealsConsiderOfflineStorageDeals(ctx context.Context) (bool, error) {
|
||||
return sm.ConsiderOfflineStorageDealsConfigFunc()
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) DealsSetConsiderOfflineStorageDeals(ctx context.Context, b bool) error {
|
||||
return sm.SetConsiderOfflineStorageDealsConfigFunc(b)
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) DealsConsiderOfflineRetrievalDeals(ctx context.Context) (bool, error) {
|
||||
return sm.ConsiderOfflineRetrievalDealsConfigFunc()
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) DealsSetConsiderOfflineRetrievalDeals(ctx context.Context, b bool) error {
|
||||
return sm.SetConsiderOfflineRetrievalDealsConfigFunc(b)
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) DealsImportData(ctx context.Context, deal cid.Cid, fname string) error {
|
||||
@@ -232,6 +273,14 @@ func (sm *StorageMinerAPI) DealsImportData(ctx context.Context, deal cid.Cid, fn
|
||||
return sm.StorageProvider.ImportDataForDeal(ctx, deal, fi)
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) DealsPieceCidBlocklist(ctx context.Context) ([]cid.Cid, error) {
|
||||
return sm.StorageDealPieceCidBlocklistConfigFunc()
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) DealsSetPieceCidBlocklist(ctx context.Context, cids []cid.Cid) error {
|
||||
return sm.SetStorageDealPieceCidBlocklistConfigFunc(cids)
|
||||
}
|
||||
|
||||
func (sm *StorageMinerAPI) StorageAddLocal(ctx context.Context, path string) error {
|
||||
if sm.StorageMgr == nil {
|
||||
return xerrors.Errorf("no storage manager")
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package dtypes
|
||||
|
||||
type DrandConfig struct {
|
||||
Servers []string
|
||||
ChainInfoJSON string
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package dtypes
|
||||
|
||||
import (
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
)
|
||||
@@ -8,10 +10,43 @@ import (
|
||||
type MinerAddress address.Address
|
||||
type MinerID abi.ActorID
|
||||
|
||||
// AcceptingStorageDealsFunc is a function which reads from miner config to
|
||||
// determine if the user has disabled storage deals (or not).
|
||||
type AcceptingStorageDealsConfigFunc func() (bool, error)
|
||||
// ConsiderOnlineStorageDealsConfigFunc is a function which reads from miner
|
||||
// config to determine if the user has disabled storage deals (or not).
|
||||
type ConsiderOnlineStorageDealsConfigFunc func() (bool, error)
|
||||
|
||||
// SetAcceptingStorageDealsFunc is a function which is used to disable or enable
|
||||
// storage deal acceptance.
|
||||
type SetAcceptingStorageDealsConfigFunc func(bool) error
|
||||
// SetConsiderOnlineStorageDealsConfigFunc is a function which is used to
|
||||
// disable or enable storage deal acceptance.
|
||||
type SetConsiderOnlineStorageDealsConfigFunc func(bool) error
|
||||
|
||||
// ConsiderOnlineRetrievalDealsConfigFunc is a function which reads from miner
|
||||
// config to determine if the user has disabled retrieval acceptance (or not).
|
||||
type ConsiderOnlineRetrievalDealsConfigFunc func() (bool, error)
|
||||
|
||||
// SetConsiderOnlineRetrievalDealsConfigFunc is a function which is used to
|
||||
// disable or enable retrieval deal acceptance.
|
||||
type SetConsiderOnlineRetrievalDealsConfigFunc func(bool) error
|
||||
|
||||
// StorageDealPieceCidBlocklistConfigFunc is a function which reads from miner
|
||||
// config to obtain a list of CIDs for which the storage miner will not accept
|
||||
// storage proposals.
|
||||
type StorageDealPieceCidBlocklistConfigFunc func() ([]cid.Cid, error)
|
||||
|
||||
// SetStorageDealPieceCidBlocklistConfigFunc is a function which is used to set a
|
||||
// list of CIDs for which the storage miner will reject deal proposals.
|
||||
type SetStorageDealPieceCidBlocklistConfigFunc func([]cid.Cid) error
|
||||
|
||||
// ConsiderOfflineStorageDealsConfigFunc is a function which reads from miner
|
||||
// config to determine if the user has disabled storage deals (or not).
|
||||
type ConsiderOfflineStorageDealsConfigFunc func() (bool, error)
|
||||
|
||||
// SetConsiderOfflineStorageDealsConfigFunc is a function which is used to
|
||||
// disable or enable storage deal acceptance.
|
||||
type SetConsiderOfflineStorageDealsConfigFunc func(bool) error
|
||||
|
||||
// ConsiderOfflineRetrievalDealsConfigFunc is a function which reads from miner
|
||||
// config to determine if the user has disabled retrieval acceptance (or not).
|
||||
type ConsiderOfflineRetrievalDealsConfigFunc func() (bool, error)
|
||||
|
||||
// SetConsiderOfflineRetrievalDealsConfigFunc is a function which is used to
|
||||
// disable or enable retrieval deal acceptance.
|
||||
type SetConsiderOfflineRetrievalDealsConfigFunc func(bool) error
|
||||
|
||||
@@ -19,7 +19,10 @@ import (
|
||||
|
||||
var log = logging.Logger("p2pnode")
|
||||
|
||||
const kstorePrivkey = "libp2p-host"
|
||||
const (
|
||||
KLibp2pHost = "libp2p-host"
|
||||
KTLibp2pHost = KLibp2pHost
|
||||
)
|
||||
|
||||
type Libp2pOpts struct {
|
||||
fx.Out
|
||||
@@ -28,7 +31,7 @@ type Libp2pOpts struct {
|
||||
}
|
||||
|
||||
func PrivKey(ks types.KeyStore) (crypto.PrivKey, error) {
|
||||
k, err := ks.Get(kstorePrivkey)
|
||||
k, err := ks.Get(KLibp2pHost)
|
||||
if err == nil {
|
||||
return crypto.UnmarshalPrivateKey(k.PrivateKey)
|
||||
}
|
||||
@@ -44,8 +47,8 @@ func PrivKey(ks types.KeyStore) (crypto.PrivKey, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := ks.Put(kstorePrivkey, types.KeyInfo{
|
||||
Type: kstorePrivkey,
|
||||
if err := ks.Put(KLibp2pHost, types.KeyInfo{
|
||||
Type: KTLibp2pHost,
|
||||
PrivateKey: kbytes,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -44,13 +44,14 @@ type GossipIn struct {
|
||||
Db dtypes.DrandBootstrap
|
||||
Cfg *config.Pubsub
|
||||
Sk *dtypes.ScoreKeeper
|
||||
Dr dtypes.DrandConfig
|
||||
}
|
||||
|
||||
func getDrandTopic() (string, error) {
|
||||
func getDrandTopic(chainInfoJSON string) (string, error) {
|
||||
var drandInfo = struct {
|
||||
Hash string `json:"hash"`
|
||||
}{}
|
||||
err := json.Unmarshal([]byte(build.DrandChain), &drandInfo)
|
||||
err := json.Unmarshal([]byte(chainInfoJSON), &drandInfo)
|
||||
if err != nil {
|
||||
return "", xerrors.Errorf("could not unmarshal drand chain info: %w", err)
|
||||
}
|
||||
@@ -68,7 +69,7 @@ func GossipSub(in GossipIn) (service *pubsub.PubSub, err error) {
|
||||
}
|
||||
|
||||
isBootstrapNode := in.Cfg.Bootstrapper
|
||||
drandTopic, err := getDrandTopic()
|
||||
drandTopic, err := getDrandTopic(in.Dr.ChainInfoJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func RunHello(mctx helpers.MetricsCtx, lc fx.Lifecycle, h host.Host, svc *hello.
|
||||
pic := evt.(event.EvtPeerIdentificationCompleted)
|
||||
go func() {
|
||||
if err := svc.SayHello(helpers.LifecycleCtx(mctx, lc), pic.Peer); err != nil {
|
||||
log.Warnw("failed to say hello", "error", err)
|
||||
log.Warnw("failed to say hello", "error", err, "peer", pic.Peer)
|
||||
return
|
||||
}
|
||||
}()
|
||||
@@ -108,8 +108,13 @@ func RetrievalResolver(l *discovery.Local) retrievalmarket.PeerResolver {
|
||||
type RandomBeaconParams struct {
|
||||
fx.In
|
||||
|
||||
PubSub *pubsub.PubSub `optional:"true"`
|
||||
Cs *store.ChainStore
|
||||
PubSub *pubsub.PubSub `optional:"true"`
|
||||
Cs *store.ChainStore
|
||||
DrandConfig dtypes.DrandConfig
|
||||
}
|
||||
|
||||
func BuiltinDrandConfig() dtypes.DrandConfig {
|
||||
return build.DrandConfig
|
||||
}
|
||||
|
||||
func RandomBeacon(p RandomBeaconParams, _ dtypes.AfterGenesisSet) (beacon.RandomBeacon, error) {
|
||||
@@ -118,6 +123,6 @@ func RandomBeacon(p RandomBeaconParams, _ dtypes.AfterGenesisSet) (beacon.Random
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//return beacon.NewMockBeacon(build.BlockDelay * time.Second)
|
||||
return drand.NewDrandBeacon(gen.Timestamp, build.BlockDelay, p.PubSub)
|
||||
//return beacon.NewMockBeacon(build.BlockDelaySecs * time.Second)
|
||||
return drand.NewDrandBeacon(gen.Timestamp, build.BlockDelaySecs, p.PubSub, p.DrandConfig)
|
||||
}
|
||||
|
||||
+179
-35
@@ -3,11 +3,13 @@ package modules
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/ipfs/go-bitswap"
|
||||
"github.com/ipfs/go-bitswap/network"
|
||||
"github.com/ipfs/go-blockservice"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipfs/go-datastore"
|
||||
"github.com/ipfs/go-datastore/namespace"
|
||||
graphsync "github.com/ipfs/go-graphsync/impl"
|
||||
@@ -73,6 +75,12 @@ func GetParams(sbc *ffiwrapper.Config) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// If built-in assets are disabled, we expect the user to have placed the right
|
||||
// parameters in the right location on the filesystem (/var/tmp/filecoin-proof-parameters).
|
||||
if build.DisableBuiltinAssets {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := paramfetch.GetParams(context.TODO(), build.ParametersJSON(), uint64(ssize)); err != nil {
|
||||
return xerrors.Errorf("fetching proof parameters: %w", err)
|
||||
}
|
||||
@@ -307,7 +315,7 @@ func NewStorageAsk(ctx helpers.MetricsCtx, fapi lapi.FullNode, ds dtypes.Metadat
|
||||
return storedAsk, nil
|
||||
}
|
||||
|
||||
func StorageProvider(minerAddress dtypes.MinerAddress, ffiConfig *ffiwrapper.Config, storedAsk *storedask.StoredAsk, h host.Host, ds dtypes.MetadataDS, ibs dtypes.StagingBlockstore, r repo.LockedRepo, pieceStore dtypes.ProviderPieceStore, dataTransfer dtypes.ProviderDataTransfer, spn storagemarket.StorageProviderNode, isAcceptingFunc dtypes.AcceptingStorageDealsConfigFunc) (storagemarket.StorageProvider, error) {
|
||||
func StorageProvider(minerAddress dtypes.MinerAddress, ffiConfig *ffiwrapper.Config, storedAsk *storedask.StoredAsk, h host.Host, ds dtypes.MetadataDS, ibs dtypes.StagingBlockstore, r repo.LockedRepo, pieceStore dtypes.ProviderPieceStore, dataTransfer dtypes.ProviderDataTransfer, spn storagemarket.StorageProviderNode, onlineOk dtypes.ConsiderOnlineStorageDealsConfigFunc, offlineOk dtypes.ConsiderOfflineStorageDealsConfigFunc, blocklistFunc dtypes.StorageDealPieceCidBlocklistConfigFunc) (storagemarket.StorageProvider, error) {
|
||||
net := smnet.NewFromLibp2pHost(h)
|
||||
store, err := piecefilestore.NewLocalFileStore(piecefilestore.OsPath(r.Path()))
|
||||
if err != nil {
|
||||
@@ -315,14 +323,36 @@ func StorageProvider(minerAddress dtypes.MinerAddress, ffiConfig *ffiwrapper.Con
|
||||
}
|
||||
|
||||
opt := storageimpl.CustomDealDecisionLogic(func(ctx context.Context, deal storagemarket.MinerDeal) (bool, string, error) {
|
||||
b, err := isAcceptingFunc()
|
||||
b, err := onlineOk()
|
||||
if err != nil {
|
||||
return false, "miner error", err
|
||||
}
|
||||
|
||||
if !b {
|
||||
log.Warnf("storage deal acceptance disabled; rejecting storage deal proposal from client: %s", deal.Client.String())
|
||||
return false, "miner is not accepting storage deals", nil
|
||||
if deal.Ref != nil && deal.Ref.TransferType != storagemarket.TTManual && !b {
|
||||
log.Warnf("online storage deal consideration disabled; rejecting storage deal proposal from client: %s", deal.Client.String())
|
||||
return false, "miner is not considering online storage deals", nil
|
||||
}
|
||||
|
||||
b, err = offlineOk()
|
||||
if err != nil {
|
||||
return false, "miner error", err
|
||||
}
|
||||
|
||||
if deal.Ref != nil && deal.Ref.TransferType == storagemarket.TTManual && !b {
|
||||
log.Warnf("offline storage deal consideration disabled; rejecting storage deal proposal from client: %s", deal.Client.String())
|
||||
return false, "miner is not accepting offline storage deals", nil
|
||||
}
|
||||
|
||||
blocklist, err := blocklistFunc()
|
||||
if err != nil {
|
||||
return false, "miner error", err
|
||||
}
|
||||
|
||||
for idx := range blocklist {
|
||||
if deal.Proposal.PieceCID.Equals(blocklist[idx]) {
|
||||
log.Warnf("piece CID in proposal %s is blocklisted; rejecting storage deal proposal from client: %s", deal.Proposal.PieceCID, deal.Client.String())
|
||||
return false, fmt.Sprintf("miner has blocklisted piece CID %s", deal.Proposal.PieceCID), nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, "", nil
|
||||
@@ -337,14 +367,40 @@ func StorageProvider(minerAddress dtypes.MinerAddress, ffiConfig *ffiwrapper.Con
|
||||
}
|
||||
|
||||
// RetrievalProvider creates a new retrieval provider attached to the provider blockstore
|
||||
func RetrievalProvider(h host.Host, miner *storage.Miner, sealer sectorstorage.SectorManager, full lapi.FullNode, ds dtypes.MetadataDS, pieceStore dtypes.ProviderPieceStore, ibs dtypes.StagingBlockstore) (retrievalmarket.RetrievalProvider, error) {
|
||||
func RetrievalProvider(h host.Host, miner *storage.Miner, sealer sectorstorage.SectorManager, full lapi.FullNode, ds dtypes.MetadataDS, pieceStore dtypes.ProviderPieceStore, ibs dtypes.StagingBlockstore, onlineOk dtypes.ConsiderOnlineRetrievalDealsConfigFunc, offlineOk dtypes.ConsiderOfflineRetrievalDealsConfigFunc) (retrievalmarket.RetrievalProvider, error) {
|
||||
adapter := retrievaladapter.NewRetrievalProviderNode(miner, sealer, full)
|
||||
address, err := minerAddrFromDS(ds)
|
||||
|
||||
maddr, err := minerAddrFromDS(ds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
network := rmnet.NewFromLibp2pHost(h)
|
||||
return retrievalimpl.NewProvider(address, adapter, network, pieceStore, ibs, namespace.Wrap(ds, datastore.NewKey("/retrievals/provider")))
|
||||
|
||||
netwk := rmnet.NewFromLibp2pHost(h)
|
||||
|
||||
opt := retrievalimpl.DealDeciderOpt(func(ctx context.Context, state retrievalmarket.ProviderDealState) (bool, string, error) {
|
||||
b, err := onlineOk()
|
||||
if err != nil {
|
||||
return false, "miner error", err
|
||||
}
|
||||
|
||||
if !b {
|
||||
log.Warn("online retrieval deal consideration disabled; rejecting retrieval deal proposal from client")
|
||||
return false, "miner is not accepting online retrieval deals", nil
|
||||
}
|
||||
|
||||
b, err = offlineOk()
|
||||
if err != nil {
|
||||
return false, "miner error", err
|
||||
}
|
||||
|
||||
if !b {
|
||||
log.Info("offline retrieval has not been implemented yet")
|
||||
}
|
||||
|
||||
return true, "", nil
|
||||
})
|
||||
|
||||
return retrievalimpl.NewProvider(maddr, adapter, netwk, pieceStore, ibs, namespace.Wrap(ds, datastore.NewKey("/retrievals/provider")), opt)
|
||||
}
|
||||
|
||||
func SectorStorage(mctx helpers.MetricsCtx, lc fx.Lifecycle, ls stores.LocalStorage, si stores.SectorIndex, cfg *ffiwrapper.Config, sc sectorstorage.SealerConfig, urls sectorstorage.URLs, sa sectorstorage.StorageAuth) (*sectorstorage.Manager, error) {
|
||||
@@ -379,36 +435,124 @@ func StorageAuth(ctx helpers.MetricsCtx, ca lapi.Common) (sectorstorage.StorageA
|
||||
return sectorstorage.StorageAuth(headers), nil
|
||||
}
|
||||
|
||||
func NewAcceptingStorageDealsConfigFunc(r repo.LockedRepo) (dtypes.AcceptingStorageDealsConfigFunc, error) {
|
||||
return func() (bool, error) {
|
||||
raw, err := r.Config()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
func NewConsiderOnlineStorageDealsConfigFunc(r repo.LockedRepo) (dtypes.ConsiderOnlineStorageDealsConfigFunc, error) {
|
||||
return func() (out bool, err error) {
|
||||
err = readCfg(r, func(cfg *config.StorageMiner) {
|
||||
out = cfg.Dealmaking.ConsiderOnlineStorageDeals
|
||||
})
|
||||
return
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewSetConsideringOnlineStorageDealsFunc(r repo.LockedRepo) (dtypes.SetConsiderOnlineStorageDealsConfigFunc, error) {
|
||||
return func(b bool) (err error) {
|
||||
err = mutateCfg(r, func(cfg *config.StorageMiner) {
|
||||
cfg.Dealmaking.ConsiderOnlineStorageDeals = b
|
||||
})
|
||||
return
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewConsiderOnlineRetrievalDealsConfigFunc(r repo.LockedRepo) (dtypes.ConsiderOnlineRetrievalDealsConfigFunc, error) {
|
||||
return func() (out bool, err error) {
|
||||
err = readCfg(r, func(cfg *config.StorageMiner) {
|
||||
out = cfg.Dealmaking.ConsiderOnlineRetrievalDeals
|
||||
})
|
||||
return
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewSetConsiderOnlineRetrievalDealsConfigFunc(r repo.LockedRepo) (dtypes.SetConsiderOnlineRetrievalDealsConfigFunc, error) {
|
||||
return func(b bool) (err error) {
|
||||
err = mutateCfg(r, func(cfg *config.StorageMiner) {
|
||||
cfg.Dealmaking.ConsiderOnlineRetrievalDeals = b
|
||||
})
|
||||
return
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewStorageDealPieceCidBlocklistConfigFunc(r repo.LockedRepo) (dtypes.StorageDealPieceCidBlocklistConfigFunc, error) {
|
||||
return func() (out []cid.Cid, err error) {
|
||||
err = readCfg(r, func(cfg *config.StorageMiner) {
|
||||
out = cfg.Dealmaking.PieceCidBlocklist
|
||||
})
|
||||
return
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewSetStorageDealPieceCidBlocklistConfigFunc(r repo.LockedRepo) (dtypes.SetStorageDealPieceCidBlocklistConfigFunc, error) {
|
||||
return func(blocklist []cid.Cid) (err error) {
|
||||
err = mutateCfg(r, func(cfg *config.StorageMiner) {
|
||||
cfg.Dealmaking.PieceCidBlocklist = blocklist
|
||||
})
|
||||
return
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewConsiderOfflineStorageDealsConfigFunc(r repo.LockedRepo) (dtypes.ConsiderOfflineStorageDealsConfigFunc, error) {
|
||||
return func() (out bool, err error) {
|
||||
err = readCfg(r, func(cfg *config.StorageMiner) {
|
||||
out = cfg.Dealmaking.ConsiderOfflineStorageDeals
|
||||
})
|
||||
return
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewSetConsideringOfflineStorageDealsFunc(r repo.LockedRepo) (dtypes.SetConsiderOfflineStorageDealsConfigFunc, error) {
|
||||
return func(b bool) (err error) {
|
||||
err = mutateCfg(r, func(cfg *config.StorageMiner) {
|
||||
cfg.Dealmaking.ConsiderOfflineStorageDeals = b
|
||||
})
|
||||
return
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewConsiderOfflineRetrievalDealsConfigFunc(r repo.LockedRepo) (dtypes.ConsiderOfflineRetrievalDealsConfigFunc, error) {
|
||||
return func() (out bool, err error) {
|
||||
err = readCfg(r, func(cfg *config.StorageMiner) {
|
||||
out = cfg.Dealmaking.ConsiderOfflineRetrievalDeals
|
||||
})
|
||||
return
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewSetConsiderOfflineRetrievalDealsConfigFunc(r repo.LockedRepo) (dtypes.SetConsiderOfflineRetrievalDealsConfigFunc, error) {
|
||||
return func(b bool) (err error) {
|
||||
err = mutateCfg(r, func(cfg *config.StorageMiner) {
|
||||
cfg.Dealmaking.ConsiderOfflineRetrievalDeals = b
|
||||
})
|
||||
return
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readCfg(r repo.LockedRepo, accessor func(*config.StorageMiner)) error {
|
||||
raw, err := r.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, ok := raw.(*config.StorageMiner)
|
||||
if !ok {
|
||||
return xerrors.New("expected address of config.StorageMiner")
|
||||
}
|
||||
|
||||
accessor(cfg)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mutateCfg(r repo.LockedRepo, mutator func(*config.StorageMiner)) error {
|
||||
var typeErr error
|
||||
|
||||
setConfigErr := r.SetConfig(func(raw interface{}) {
|
||||
cfg, ok := raw.(*config.StorageMiner)
|
||||
if !ok {
|
||||
return false, xerrors.New("expected address of config.StorageMiner")
|
||||
typeErr = errors.New("expected storage miner config")
|
||||
return
|
||||
}
|
||||
|
||||
return cfg.Dealmaking.AcceptingStorageDeals, nil
|
||||
}, nil
|
||||
}
|
||||
mutator(cfg)
|
||||
})
|
||||
|
||||
func NewSetAcceptingStorageDealsConfigFunc(r repo.LockedRepo) (dtypes.SetAcceptingStorageDealsConfigFunc, error) {
|
||||
return func(b bool) error {
|
||||
var typeErr error
|
||||
|
||||
setConfigErr := r.SetConfig(func(raw interface{}) {
|
||||
cfg, ok := raw.(*config.StorageMiner)
|
||||
if !ok {
|
||||
typeErr = errors.New("expected storage miner config")
|
||||
return
|
||||
}
|
||||
|
||||
cfg.Dealmaking.AcceptingStorageDeals = b
|
||||
})
|
||||
|
||||
return multierr.Combine(typeErr, setConfigErr)
|
||||
}, nil
|
||||
return multierr.Combine(typeErr, setConfigErr)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user