Merge branch 'next' into inmem-journal

This commit is contained in:
Raúl Kripalani 2020-08-11 12:41:11 +01:00
commit 92598237c3
261 changed files with 16512 additions and 5697 deletions

View File

@ -320,7 +320,7 @@ workflows:
ci:
jobs:
- lint-changes:
args: "--new-from-rev origin/master"
args: "--new-from-rev origin/next"
- mod-tidy-check
- gofmt
- test:

242
.github/labels.yml vendored Normal file
View File

@ -0,0 +1,242 @@
###
### Special magic GitHub labels
### https://help.github.com/en/github/building-a-strong-community/encouraging-helpful-contributions-to-your-project-with-labels
#
- name: "good first issue"
color: 7057ff
description: "Good for newcomers"
- name: "help wanted"
color: 008672
description: "Extra attention is needed"
###
### Goals
#
- name: goal/incentives
color: ff004d
description: "Incentinet"
###
### Areas
#
- name: area/ux
color: 00A4E0
description: "Area: UX"
- name: area/chain/vm
color: 00A4E2
description: "Area: Chain/VM"
- name: area/chain/sync
color: 00A4E2
description: "Area: Chain/Sync"
- name: area/chain/misc
color: 00A4E2
description: "Area: Chain/Misc"
- name: area/sealing/fsm
color: 0bb1ed
description: "Area: Sealing/FSM"
- name: area/sealing/storage
color: 0EB4F0
description: "Area: Sealing/Storage"
- name: area/proving
color: 0EB4F0
description: "Area: Proving"
- name: area/mining
color: 10B6F2
description: "Area: Mining"
- name: area/client/storage
color: 13B9F5
description: "Area: Client/Storage"
- name: area/client/retrieval
color: 15BBF7
description: "Area: Client/Retrieval"
- name: area/wallet
color: 15BBF7
description: "Area: Wallet"
- name: area/payment-channel
color: ff6767
description: "Area: Payment Channel"
- name: area/multisig
color: fff0ff
description: "Area: Multisig"
- name: area/networking
color: 273f8a
description: "Area: Networking"
###
### Kinds
#
- name: kind/bug
color: c92712
description: "Kind: Bug"
- name: kind/chore
color: fcf0b5
description: "Kind: Chore"
- name: kind/feature
color: FFF3B8
description: "Kind: Feature"
- name: kind/improvement
color: FFF5BA
description: "Kind: Improvement"
- name: kind/test
color: FFF8BD
description: "Kind: Test"
- name: kind/question
color: FFFDC2
description: "Kind: Question"
- name: kind/enhancement
color: FFFFC5
description: "Kind: Enhancement"
- name: kind/discussion
color: FFFFC7
description: "Kind: Discussion"
###
### Difficulties
#
- name: dif/trivial
color: b2b7ff
description: "Can be confidently tackled by newcomers, who are widely unfamiliar with lotus"
- name: dif/easy
color: 7886d7
description: "An existing lotus user should be able to pick this up"
- name: dif/medium
color: 6574cd
description: "Prior development experience with lotus is likely helpful"
- name: dif/hard
color: 5661b3
description: "Suggests that having worked on the specific component affected by this issue is important"
- name: dif/expert
color: 2f365f
description: "Requires extensive knowledge of the history, implications, ramifications of the issue"
###
### Efforts
#
- name: effort/minutes
color: e8fffe
description: "Effort: Minutes"
- name: effort/hours
color: a0f0ed
description: "Effort: Hours"
- name: effort/day
color: 64d5ca
description: "Effort: One Day"
- name: effort/days
color: 4dc0b5
description: "Effort: Multiple Days"
- name: effort/week
color: 38a89d
description: "Effort: One Week"
- name: effort/weeks
color: 20504f
description: "Effort: Multiple Weeks"
###
### Impacts
#
- name: impact/regression
color: f1f5f8
description: "Impact: Regression"
- name: impact/api-breakage
color: ECF0F3
description: "Impact: API Breakage"
- name: impact/quality
color: E7EBEE
description: "Impact: Quality"
- name: impact/dx
color: E2E6E9
description: "Impact: Developer Experience"
- name: impact/test-flakiness
color: DDE1E4
description: "Impact: Test Flakiness"
###
### Topics
#
- name: topic/interoperability
color: bf0f73
description: "Topic: Interoperability"
- name: topic/specs
color: CC1C80
description: "Topic: Specs"
- name: topic/docs
color: D9298D
description: "Topic: Documentation"
- name: topic/architecture
color: E53599
description: "Topic: Architecture"
###
### Priorities
###
- name: P0
color: dd362a
description: "P0: Critical Blocker"
- name: P1
color: ce8048
description: "P1: Must be resolved"
- name: P2
color: dbd81a
description: "P2: Should be resolved"
- name: P3
color: 9fea8f
description: "P3: Might get resolved"
###
### Hints
#
#- name: hint/good-first-issue
# color: 7057ff
# description: "Hint: Good First Issue"
#- name: hint/help-wanted
# color: 008672
# description: "Hint: Help Wanted"
- name: hint/needs-decision
color: 33B9A5
description: "Hint: Needs Decision"
- name: hint/needs-triage
color: 1AA08C
description: "Hint: Needs Triage"
- name: hint/needs-analysis
color: 26AC98
description: "Hint: Needs Analysis"
- name: hint/needs-author-input
color: 33B9A5
description: "Hint: Needs Author Input"
- name: hint/needs-team-input
color: 40C6B2
description: "Hint: Needs Team Input"
- name: hint/needs-community-input
color: 4DD3BF
description: "Hint: Needs Community Input"
- name: hint/needs-review
color: 5AE0CC
description: "Hint: Needs Review"
###
### Statuses
#
- name: status/done
color: edb3a6
description: "Status: Done"
- name: status/deferred
color: E0A699
description: "Status: Deferred"
- name: status/in-progress
color: D49A8D
description: "Status: In Progress"
- name: status/blocked
color: C78D80
description: "Status: Blocked"
- name: status/inactive
color: BA8073
description: "Status: Inactive"
- name: status/waiting
color: AD7366
description: "Status: Waiting"
- name: status/rotten
color: 7A4033
description: "Status: Rotten"
- name: status/discarded
color: 6D3326
description: "Status: Discarded / Won't fix"

17
.github/workflows/label-syncer.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: Label syncer
on:
push:
paths:
- '.github/labels.yml'
branches:
- master
jobs:
build:
name: Sync labels
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1.0.0
- uses: micnncim/action-label-syncer@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -98,26 +98,17 @@ an existing lotus binary in your PATH. This may cause problems if you don't run
.PHONY: build
install:
install: install-daemon install-miner install-worker
install-daemon:
install -C ./lotus /usr/local/bin/lotus
install-miner:
install -C ./lotus-miner /usr/local/bin/lotus-miner
install-worker:
install -C ./lotus-worker /usr/local/bin/lotus-worker
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-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/lotus-chainwatch.service
systemctl daemon-reload
# TOOLS
lotus-seed: $(BUILD_DEPS)
@ -161,14 +152,6 @@ lotus-chainwatch:
.PHONY: lotus-chainwatch
BINS+=lotus-chainwatch
install-chainwatch-service: chainwatch
mkdir -p /etc/lotus
install -C ./lotus-chainwatch /usr/local/bin/lotus-chainwatch
install -C -m 0644 ./scripts/lotus-chainwatch.service /usr/local/lib/systemd/system/lotus-chainwatch.service
systemctl daemon-reload
@echo
@echo "chainwatch installed. Don't forget to 'systemctl enable chainwatch' for it to be enabled on startup."
lotus-bench:
rm -f lotus-bench
go build -o lotus-bench ./cmd/lotus-bench
@ -183,6 +166,13 @@ lotus-stats:
.PHONY: lotus-stats
BINS+=lotus-stats
lotus-pcr:
rm -f lotus-pcr
go build $(GOFLAGS) -o lotus-pcr ./cmd/lotus-pcr
go run github.com/GeertJohan/go.rice/rice append --exec lotus-pcr -i ./build
.PHONY: lotus-pcr
BINS+=lotus-pcr
lotus-health:
rm -f lotus-health
go build -o lotus-health ./cmd/lotus-health
@ -195,6 +185,65 @@ testground:
.PHONY: testground
BINS+=testground
install-chainwatch: lotus-chainwatch
install -C ./lotus-chainwatch /usr/local/bin/lotus-chainwatch
# SYSTEMD
install-daemon-service: install-daemon
mkdir -p /etc/systemd/system
mkdir -p /var/log/lotus
install -C -m 0644 ./scripts/lotus-daemon.service /etc/systemd/system/lotus-daemon.service
systemctl daemon-reload
@echo
@echo "lotus-daemon service installed. Don't forget to run 'sudo systemctl start lotus-daemon' to start it and 'sudo systemctl enable lotus-daemon' for it to be enabled on startup."
install-miner-service: install-miner install-daemon-service
mkdir -p /etc/systemd/system
mkdir -p /var/log/lotus
install -C -m 0644 ./scripts/lotus-miner.service /etc/systemd/system/lotus-miner.service
systemctl daemon-reload
@echo
@echo "lotus-miner service installed. Don't forget to run 'sudo systemctl start lotus-miner' to start it and 'sudo systemctl enable lotus-miner' for it to be enabled on startup."
install-chainwatch-service: install-chainwatch install-daemon-service
mkdir -p /etc/systemd/system
mkdir -p /var/log/lotus
install -C -m 0644 ./scripts/lotus-chainwatch.service /etc/systemd/system/lotus-chainwatch.service
systemctl daemon-reload
@echo
@echo "chainwatch service installed. Don't forget to run 'sudo systemctl start lotus-chainwatch' to start it and 'sudo systemctl enable lotus-chainwatch' for it to be enabled on startup."
install-main-services: install-miner-service
install-all-services: install-main-services install-chainwatch-service
install-services: install-main-services
clean-daemon-service: clean-miner-service clean-chainwatch-service
-systemctl stop lotus-daemon
-systemctl disable lotus-daemon
rm -f /etc/systemd/system/lotus-daemon.service
systemctl daemon-reload
clean-miner-service:
-systemctl stop lotus-miner
-systemctl disable lotus-miner
rm -f /etc/systemd/system/lotus-miner.service
systemctl daemon-reload
clean-chainwatch-service:
-systemctl stop lotus-chainwatch
-systemctl disable lotus-chainwatch
rm -f /etc/systemd/system/lotus-chainwatch.service
systemctl daemon-reload
clean-main-services: clean-daemon-service
clean-all-services: clean-main-services
clean-services: clean-all-services
# MISC
buildall: $(BINS)

View File

@ -1,8 +1,12 @@
![Lotus](documentation/images/lotus_logo_h.png)
<p align="center">
<a href="https://docs.lotu.sh/" title="Lotus Docs">
<img src="documentation/images/lotus_logo_h.png" alt="Project Lotus Logo" width="244" />
</a>
</p>
# Project Lotus - 莲
<h1 align="center">Project Lotus - 莲</h1>
Lotus is an implementation of the Filecoin Distributed Storage Network. For more details about Filecoin, check out the [Filecoin Spec](https://github.com/filecoin-project/specs).
Lotus is an implementation of the Filecoin Distributed Storage Network. For more details about Filecoin, check out the [Filecoin Spec](https://spec.filecoin.io).
## Building & Documentation
@ -14,12 +18,25 @@ Please send an email to security@filecoin.org. See our [security policy](SECURIT
## 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).
The main branches under development at the moment are:
* [`master`](https://github.com/filecoin-project/lotus): current testnet.
* [`next`](https://github.com/filecoin-project/lotus/tree/next): working branch with chain-breaking changes.
* [`interopnet`](https://github.com/filecoin-project/lotus/tree/interopnet): devnet running one of `next` commits.
* [`ntwk-calibration`](https://github.com/filecoin-project/lotus/tree/ntwk-calibration): devnet running one of `next` commits.
### Tracker
All work is tracked via issues. An attempt at keeping an up-to-date view on remaining work towards Mainnet launch can be seen at the [lotus github project board](https://github.com/orgs/filecoin-project/projects/8). The issues labeled with `incentives` are there to identify the issues needed for Space Race launch.
### Packages
The lotus Filecoin implementation unfolds into the following packages:
- [This repo](https://github.com/filecoin-project/lotus)
- [storage-fsm](https://github.com/filecoin-project/storage-fsm)
- [sector-storage](https://github.com/filecoin-project/sector-storage)
- [specs-storage](https://github.com/filecoin-project/specs-storage)
- [go-fil-markets](https://github.com/filecoin-project/go-fil-markets) which has its own [kanban work tracker available here](https://app.zenhub.com/workspaces/markets-shared-components-5daa144a7046a60001c6e253/board)
- [spec-actors](https://github.com/filecoin-project/specs-actors) which has its own [kanban work tracker available here](https://app.zenhub.com/workspaces/actors-5ee6f3aa87591f0016c05685/board)
## License

View File

@ -8,7 +8,9 @@ import (
"github.com/libp2p/go-libp2p-core/peer"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-fil-markets/retrievalmarket"
"github.com/filecoin-project/go-fil-markets/storagemarket"
"github.com/filecoin-project/go-multistore"
"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/market"
@ -57,7 +59,7 @@ type FullNode interface {
ChainGetParentMessages(ctx context.Context, blockCid cid.Cid) ([]Message, error)
// ChainGetTipSetByHeight looks back for a tipset at the specified epoch.
// If there are no blocks at the specified epoch, a tipset at higher epoch
// If there are no blocks at the specified epoch, a tipset at an earlier epoch
// will be returned.
ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error)
@ -67,7 +69,11 @@ type FullNode interface {
// ChainHasObj checks if a given CID exists in the chain blockstore.
ChainHasObj(context.Context, cid.Cid) (bool, error)
ChainStatObj(context.Context, cid.Cid, cid.Cid) (ObjStat, error)
// ChainStatObj returns statistics about the graph referenced by 'obj'.
// If 'base' is also specified, then the returned stat will be a diff
// between the two objects.
ChainStatObj(ctx context.Context, obj cid.Cid, base cid.Cid) (ObjStat, error)
// ChainSetHead forcefully sets current chain head. Use with caution.
ChainSetHead(context.Context, types.TipSetKey) error
@ -101,14 +107,25 @@ type FullNode interface {
// ChainExport returns a stream of bytes with CAR dump of chain data.
ChainExport(context.Context, types.TipSetKey) (<-chan []byte, error)
// MethodGroup: Beacon
// The Beacon method group contains methods for interacting with the random beacon (DRAND)
// BeaconGetEntry returns the beacon entry for the given filecoin epoch. If
// the entry has not yet been produced, the call will block until the entry
// becomes available
BeaconGetEntry(ctx context.Context, epoch abi.ChainEpoch) (*types.BeaconEntry, error)
// GasEstimateFeeCap estimates gas fee cap
GasEstimateFeeCap(context.Context, *types.Message, int64, types.TipSetKey) (types.BigInt, error)
// GasEstimateGasLimit estimates gas used by the message and returns it.
// It fails if message fails to execute.
GasEstimateGasLimit(context.Context, *types.Message, types.TipSetKey) (int64, error)
// GasEstimateGasPrice estimates what gas price should be used for a
// GasEsitmateGasPremium estimates what gas price should be used for a
// message to have high likelihood of inclusion in `nblocksincl` epochs.
GasEstimateGasPrice(_ context.Context, nblocksincl uint64,
GasEsitmateGasPremium(_ context.Context, nblocksincl uint64,
sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error)
// MethodGroup: Sync
@ -141,6 +158,9 @@ type FullNode interface {
// MpoolPending returns pending mempool messages.
MpoolPending(context.Context, types.TipSetKey) ([]*types.SignedMessage, error)
// MpoolSelect returns a list of pending messages for inclusion in the next block
MpoolSelect(context.Context, types.TipSetKey) ([]*types.SignedMessage, error)
// MpoolPush pushes a signed message to mempool.
MpoolPush(context.Context, *types.SignedMessage) (cid.Cid, error)
@ -153,9 +173,10 @@ type FullNode interface {
MpoolGetNonce(context.Context, address.Address) (uint64, error)
MpoolSub(context.Context) (<-chan MpoolUpdate, error)
// MpoolEstimateGasPrice is depracated
// Deprecated: use GasEstimateGasPrice instead
MpoolEstimateGasPrice(ctx context.Context, nblocksincl uint64, sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error)
// MpoolGetConfig returns (a copy of) the current mpool config
MpoolGetConfig(context.Context) (*types.MpoolConfig, error)
// MpoolSetConfig sets the mpool config to (a copy of) the supplied config
MpoolSetConfig(context.Context, *types.MpoolConfig) error
// MethodGroup: Miner
@ -201,7 +222,7 @@ type FullNode interface {
// ClientImport imports file under the specified path into filestore.
ClientImport(ctx context.Context, ref FileRef) (*ImportRes, error)
// ClientRemoveImport removes file import
ClientRemoveImport(ctx context.Context, importID int) error
ClientRemoveImport(ctx context.Context, importID multistore.StoreID) error
// ClientStartDeal proposes a deal with a miner.
ClientStartDeal(ctx context.Context, params *StartDealParams) (*cid.Cid, error)
// ClientGetDealInfo returns the latest information about a given deal.
@ -222,6 +243,8 @@ type FullNode interface {
ClientCalcCommP(ctx context.Context, inpath string, miner address.Address) (*CommPRet, error)
// ClientGenCar generates a CAR file for the specified file.
ClientGenCar(ctx context.Context, ref FileRef, outpath string) error
// ClientDealSize calculates real deal data size
ClientDealSize(ctx context.Context, root cid.Cid) (DataSize, error)
// ClientUnimport removes references to the specified file from filestore
//ClientUnimport(path string)
@ -272,6 +295,8 @@ type FullNode interface {
StateAllMinerFaults(ctx context.Context, lookback abi.ChainEpoch, ts types.TipSetKey) ([]*Fault, error)
// StateMinerRecoveries returns a bitfield indicating the recovering sectors of the given miner
StateMinerRecoveries(context.Context, address.Address, types.TipSetKey) (*abi.BitField, error)
// StateMinerInitialPledgeCollateral returns the precommit deposit for the specified miner's sector
StateMinerPreCommitDepositForPower(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (types.BigInt, error)
// StateMinerInitialPledgeCollateral returns the initial pledge collateral for the specified miner's sector
StateMinerInitialPledgeCollateral(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (types.BigInt, error)
// StateMinerAvailableBalance returns the portion of a miner's balance that can be withdrawn or spent
@ -322,6 +347,12 @@ type FullNode interface {
// Returns nil if there is no entry in the data cap table for the
// address.
StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*verifreg.DataCap, error)
// StateDealProviderCollateralBounds returns the min and max collateral a storage provider
// can issue. It takes the deal size and verified status as parameters.
StateDealProviderCollateralBounds(context.Context, abi.PaddedPieceSize, bool, types.TipSetKey) (DealCollateralBounds, error)
// StateCirculatingSupply returns the circulating supply of Filecoin at the given tipset
StateCirculatingSupply(context.Context, types.TipSetKey) (abi.TokenAmount, error)
// MethodGroup: Msig
// The Msig methods are used to interact with multisig wallets on the
@ -364,10 +395,12 @@ type FullNode interface {
// MethodGroup: Paych
// The Paych methods are for interacting with and managing payment channels
PaychGet(ctx context.Context, from, to address.Address, ensureFunds types.BigInt) (*ChannelInfo, error)
PaychGet(ctx context.Context, from, to address.Address, amt types.BigInt) (*ChannelInfo, error)
PaychGetWaitReady(context.Context, cid.Cid) (address.Address, error)
PaychList(context.Context) ([]address.Address, error)
PaychStatus(context.Context, address.Address) (*PaychStatus, error)
PaychClose(context.Context, address.Address) (cid.Cid, error)
PaychSettle(context.Context, address.Address) (cid.Cid, error)
PaychCollect(context.Context, address.Address) (cid.Cid, error)
PaychAllocateLane(ctx context.Context, ch address.Address) (uint64, error)
PaychNewPayment(ctx context.Context, from, to address.Address, vouchers []VoucherSpec) (*PaymentInfo, error)
PaychVoucherCheckValid(context.Context, address.Address, *paych.SignedVoucher) error
@ -403,11 +436,11 @@ type SectorLocation struct {
type ImportRes struct {
Root cid.Cid
ImportID int
ImportID multistore.StoreID
}
type Import struct {
Key int
Key multistore.StoreID
Err string
Root *cid.Cid
@ -421,6 +454,7 @@ type DealInfo struct {
Message string // more information about deal state, particularly errors
Provider address.Address
DataRef *storagemarket.DataRef
PieceCID cid.Cid
Size uint64
@ -505,10 +539,11 @@ type QueryOffer struct {
Size uint64
MinPrice types.BigInt
UnsealPrice types.BigInt
PaymentInterval uint64
PaymentIntervalIncrease uint64
Miner address.Address
MinerPeerID peer.ID
MinerPeer retrievalmarket.RetrievalPeer
}
func (o *QueryOffer) Order(client address.Address) RetrievalOrder {
@ -517,12 +552,13 @@ func (o *QueryOffer) Order(client address.Address) RetrievalOrder {
Piece: o.Piece,
Size: o.Size,
Total: o.MinPrice,
UnsealPrice: o.UnsealPrice,
PaymentInterval: o.PaymentInterval,
PaymentIntervalIncrease: o.PaymentIntervalIncrease,
Client: client,
Miner: o.Miner,
MinerPeerID: o.MinerPeerID,
Miner: o.Miner,
MinerPeer: o.MinerPeer,
}
}
@ -543,11 +579,12 @@ type RetrievalOrder struct {
Size uint64
// TODO: support offset
Total types.BigInt
UnsealPrice types.BigInt
PaymentInterval uint64
PaymentIntervalIncrease uint64
Client address.Address
Miner address.Address
MinerPeerID peer.ID
MinerPeer retrievalmarket.RetrievalPeer
}
type InvocResult struct {
@ -623,6 +660,11 @@ type ComputeStateOutput struct {
Trace []*InvocResult
}
type DealCollateralBounds struct {
Min abi.TokenAmount
Max abi.TokenAmount
}
type MiningBaseInfo struct {
MinerPower types.BigInt
NetworkPower types.BigInt
@ -631,6 +673,7 @@ type MiningBaseInfo struct {
SectorSize abi.SectorSize
PrevBeaconEntry types.BeaconEntry
BeaconEntries []types.BeaconEntry
HasMinPower bool
}
type BlockTemplate struct {
@ -645,6 +688,11 @@ type BlockTemplate struct {
WinningPoStProof []abi.PoStProof
}
type DataSize struct {
PayloadSize int64
PieceSize abi.PaddedPieceSize
}
type CommPRet struct {
Root cid.Cid
Size abi.UnpaddedPieceSize

View File

@ -8,6 +8,8 @@ import (
"github.com/ipfs/go-cid"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-fil-markets/piecestore"
"github.com/filecoin-project/go-fil-markets/retrievalmarket"
"github.com/filecoin-project/go-fil-markets/storagemarket"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/sector-storage/fsutil"
@ -30,7 +32,7 @@ type StorageMiner interface {
PledgeSector(context.Context) error
// Get the status of a given sector by ID
SectorsStatus(context.Context, abi.SectorNumber) (SectorInfo, error)
SectorsStatus(ctx context.Context, sid abi.SectorNumber, showOnChainInfo bool) (SectorInfo, error)
// List all staged sectors
SectorsList(context.Context) ([]abi.SectorNumber, error)
@ -61,14 +63,22 @@ type StorageMiner interface {
// WorkerConnect tells the node to connect to workers RPC
WorkerConnect(context.Context, string) error
WorkerStats(context.Context) (map[uint64]storiface.WorkerStats, error)
WorkerJobs(context.Context) (map[uint64][]storiface.WorkerJob, error)
// SealingSchedDiag dumps internal sealing scheduler state
SealingSchedDiag(context.Context) (interface{}, error)
stores.SectorIndex
MarketImportDealData(ctx context.Context, propcid cid.Cid, path string) error
MarketListDeals(ctx context.Context) ([]storagemarket.StorageDeal, error)
MarketListRetrievalDeals(ctx context.Context) ([]retrievalmarket.ProviderDealState, error)
MarketGetDealUpdates(ctx context.Context, d cid.Cid) (<-chan storagemarket.MinerDeal, error)
MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error)
MarketSetAsk(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error
MarketSetAsk(ctx context.Context, price types.BigInt, verifiedPrice types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error
MarketGetAsk(ctx context.Context) (*storagemarket.SignedStorageAsk, error)
MarketSetRetrievalAsk(ctx context.Context, rask *retrievalmarket.Ask) error
MarketGetRetrievalAsk(ctx context.Context) (*retrievalmarket.Ask, error)
DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error
DealsList(ctx context.Context) ([]storagemarket.StorageDeal, error)
@ -84,6 +94,11 @@ type StorageMiner interface {
DealsSetConsiderOfflineRetrievalDeals(context.Context, bool) error
StorageAddLocal(ctx context.Context, path string) error
PiecesListPieces(ctx context.Context) ([]cid.Cid, error)
PiecesListCidInfos(ctx context.Context) ([]cid.Cid, error)
PiecesGetPieceInfo(ctx context.Context, pieceCid cid.Cid) (*piecestore.PieceInfo, error)
PiecesGetCIDInfo(ctx context.Context, payloadCid cid.Cid) (*piecestore.CIDInfo, error)
}
type SealRes struct {
@ -116,11 +131,24 @@ type SectorInfo struct {
LastErr string
Log []SectorLog
// On Chain Info
SealProof abi.RegisteredSealProof // The seal proof type implies the PoSt proof/s
Activation abi.ChainEpoch // Epoch during which the sector proof was accepted
Expiration abi.ChainEpoch // Epoch during which the sector expires
DealWeight abi.DealWeight // Integral of active deals over sector lifetime
VerifiedDealWeight abi.DealWeight // Integral of active verified deals over sector lifetime
InitialPledge abi.TokenAmount // Pledge collected to commit this sector
// Expiration Info
OnTime abi.ChainEpoch
// non-zero if sector is faulty, epoch at which it will be permanently
// removed if it doesn't recover
Early abi.ChainEpoch
}
type SealedRef struct {
SectorID abi.SectorNumber
Offset uint64
Offset abi.PaddedPieceSize
Size abi.UnpaddedPieceSize
}

View File

@ -28,7 +28,7 @@ type WorkerAPI interface {
MoveStorage(ctx context.Context, sector abi.SectorID) error
UnsealPiece(context.Context, abi.SectorID, storiface.UnpaddedByteIndex, abi.UnpaddedPieceSize, abi.SealRandomness, cid.Cid) error
ReadPiece(context.Context, io.Writer, abi.SectorID, storiface.UnpaddedByteIndex, abi.UnpaddedPieceSize) error
ReadPiece(context.Context, io.Writer, abi.SectorID, storiface.UnpaddedByteIndex, abi.UnpaddedPieceSize) (bool, error)
Fetch(context.Context, abi.SectorID, stores.SectorFileType, stores.PathType, stores.AcquireMode) error

View File

@ -5,8 +5,9 @@ import (
blocks "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
blockstore "github.com/ipfs/go-ipfs-blockstore"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/lib/blockstore"
)
type ChainIO interface {

View File

@ -10,8 +10,11 @@ import (
"github.com/libp2p/go-libp2p-core/peer"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-fil-markets/piecestore"
"github.com/filecoin-project/go-fil-markets/retrievalmarket"
"github.com/filecoin-project/go-fil-markets/storagemarket"
"github.com/filecoin-project/go-jsonrpc/auth"
"github.com/filecoin-project/go-multistore"
"github.com/filecoin-project/sector-storage/fsutil"
"github.com/filecoin-project/sector-storage/sealtasks"
"github.com/filecoin-project/sector-storage/stores"
@ -82,8 +85,11 @@ type FullNodeStruct struct {
ChainGetPath func(context.Context, types.TipSetKey, types.TipSetKey) ([]*api.HeadChange, error) `perm:"read"`
ChainExport func(context.Context, types.TipSetKey) (<-chan []byte, error) `perm:"read"`
GasEstimateGasPrice func(context.Context, uint64, address.Address, int64, types.TipSetKey) (types.BigInt, error) `perm:"read"`
GasEstimateGasLimit func(context.Context, *types.Message, types.TipSetKey) (int64, error) `perm:"read"`
BeaconGetEntry func(ctx context.Context, epoch abi.ChainEpoch) (*types.BeaconEntry, error) `perm:"read"`
GasEsitmateGasPremium func(context.Context, uint64, address.Address, int64, types.TipSetKey) (types.BigInt, error) `perm:"read"`
GasEstimateGasLimit func(context.Context, *types.Message, types.TipSetKey) (int64, error) `perm:"read"`
GasEstimateFeeCap func(context.Context, *types.Message, int64, types.TipSetKey) (types.BigInt, error) `perm:"read"`
SyncState func(context.Context) (*api.SyncState, error) `perm:"read"`
SyncSubmitBlock func(ctx context.Context, blk *types.BlockMsg) error `perm:"write"`
@ -91,6 +97,9 @@ type FullNodeStruct struct {
SyncMarkBad func(ctx context.Context, bcid cid.Cid) error `perm:"admin"`
SyncCheckBad func(ctx context.Context, bcid cid.Cid) (string, error) `perm:"read"`
MpoolGetConfig func(context.Context) (*types.MpoolConfig, error) `perm:"read"`
MpoolSetConfig func(context.Context, *types.MpoolConfig) error `perm:"write"`
MpoolSelect func(context.Context, types.TipSetKey) ([]*types.SignedMessage, error) `perm:"read"`
MpoolPending func(context.Context, types.TipSetKey) ([]*types.SignedMessage, error) `perm:"read"`
MpoolPush func(context.Context, *types.SignedMessage) (cid.Cid, error) `perm:"write"`
MpoolPushMessage func(context.Context, *types.Message) (*types.SignedMessage, error) `perm:"sign"`
@ -115,7 +124,7 @@ type FullNodeStruct struct {
ClientImport func(ctx context.Context, ref api.FileRef) (*api.ImportRes, error) `perm:"admin"`
ClientListImports func(ctx context.Context) ([]api.Import, error) `perm:"write"`
ClientRemoveImport func(ctx context.Context, importID int) error `perm:"admin"`
ClientRemoveImport func(ctx context.Context, importID multistore.StoreID) error `perm:"admin"`
ClientHasLocal func(ctx context.Context, root cid.Cid) (bool, error) `perm:"write"`
ClientFindData func(ctx context.Context, root cid.Cid, piece *cid.Cid) ([]api.QueryOffer, error) `perm:"read"`
ClientMinerQueryOffer func(ctx context.Context, miner address.Address, root cid.Cid, piece *cid.Cid) (api.QueryOffer, error) `perm:"read"`
@ -126,45 +135,49 @@ type FullNodeStruct struct {
ClientQueryAsk func(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.SignedStorageAsk, error) `perm:"read"`
ClientCalcCommP func(ctx context.Context, inpath string, miner address.Address) (*api.CommPRet, error) `perm:"read"`
ClientGenCar func(ctx context.Context, ref api.FileRef, outpath string) error `perm:"write"`
ClientDealSize func(ctx context.Context, root cid.Cid) (api.DataSize, error) `perm:"read"`
StateNetworkName func(context.Context) (dtypes.NetworkName, error) `perm:"read"`
StateMinerSectors func(context.Context, address.Address, *abi.BitField, bool, types.TipSetKey) ([]*api.ChainSectorInfo, error) `perm:"read"`
StateMinerActiveSectors func(context.Context, address.Address, types.TipSetKey) ([]*api.ChainSectorInfo, error) `perm:"read"`
StateMinerProvingDeadline func(context.Context, address.Address, types.TipSetKey) (*miner.DeadlineInfo, error) `perm:"read"`
StateMinerPower func(context.Context, address.Address, types.TipSetKey) (*api.MinerPower, error) `perm:"read"`
StateMinerInfo func(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error) `perm:"read"`
StateMinerDeadlines func(context.Context, address.Address, types.TipSetKey) ([]*miner.Deadline, error) `perm:"read"`
StateMinerPartitions func(context.Context, address.Address, uint64, types.TipSetKey) ([]*miner.Partition, error) `perm:"read"`
StateMinerFaults func(context.Context, address.Address, types.TipSetKey) (*abi.BitField, error) `perm:"read"`
StateAllMinerFaults func(context.Context, abi.ChainEpoch, types.TipSetKey) ([]*api.Fault, error) `perm:"read"`
StateMinerRecoveries func(context.Context, address.Address, types.TipSetKey) (*abi.BitField, error) `perm:"read"`
StateMinerInitialPledgeCollateral func(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (types.BigInt, error) `perm:"read"`
StateMinerAvailableBalance func(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) `perm:"read"`
StateSectorPreCommitInfo func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (miner.SectorPreCommitOnChainInfo, error) `perm:"read"`
StateSectorGetInfo func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*miner.SectorOnChainInfo, error) `perm:"read"`
StateSectorExpiration func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*api.SectorExpiration, error) `perm:"read"`
StateSectorPartition func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*api.SectorLocation, error) `perm:"read"`
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, 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"`
StateListMiners func(context.Context, types.TipSetKey) ([]address.Address, error) `perm:"read"`
StateListActors func(context.Context, types.TipSetKey) ([]address.Address, error) `perm:"read"`
StateMarketBalance func(context.Context, address.Address, types.TipSetKey) (api.MarketBalance, error) `perm:"read"`
StateMarketParticipants func(context.Context, types.TipSetKey) (map[string]api.MarketBalance, error) `perm:"read"`
StateMarketDeals func(context.Context, types.TipSetKey) (map[string]api.MarketDeal, error) `perm:"read"`
StateMarketStorageDeal func(context.Context, abi.DealID, types.TipSetKey) (*api.MarketDeal, error) `perm:"read"`
StateLookupID func(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) `perm:"read"`
StateAccountKey func(context.Context, address.Address, types.TipSetKey) (address.Address, error) `perm:"read"`
StateChangedActors func(context.Context, cid.Cid, cid.Cid) (map[string]types.Actor, error) `perm:"read"`
StateGetReceipt func(context.Context, cid.Cid, types.TipSetKey) (*types.MessageReceipt, error) `perm:"read"`
StateMinerSectorCount func(context.Context, address.Address, types.TipSetKey) (api.MinerSectors, error) `perm:"read"`
StateListMessages func(ctx context.Context, match *types.Message, tsk types.TipSetKey, toht abi.ChainEpoch) ([]cid.Cid, error) `perm:"read"`
StateCompute func(context.Context, abi.ChainEpoch, []*types.Message, types.TipSetKey) (*api.ComputeStateOutput, error) `perm:"read"`
StateVerifiedClientStatus func(context.Context, address.Address, types.TipSetKey) (*verifreg.DataCap, error) `perm:"read"`
StateNetworkName func(context.Context) (dtypes.NetworkName, error) `perm:"read"`
StateMinerSectors func(context.Context, address.Address, *abi.BitField, bool, types.TipSetKey) ([]*api.ChainSectorInfo, error) `perm:"read"`
StateMinerActiveSectors func(context.Context, address.Address, types.TipSetKey) ([]*api.ChainSectorInfo, error) `perm:"read"`
StateMinerProvingDeadline func(context.Context, address.Address, types.TipSetKey) (*miner.DeadlineInfo, error) `perm:"read"`
StateMinerPower func(context.Context, address.Address, types.TipSetKey) (*api.MinerPower, error) `perm:"read"`
StateMinerInfo func(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error) `perm:"read"`
StateMinerDeadlines func(context.Context, address.Address, types.TipSetKey) ([]*miner.Deadline, error) `perm:"read"`
StateMinerPartitions func(context.Context, address.Address, uint64, types.TipSetKey) ([]*miner.Partition, error) `perm:"read"`
StateMinerFaults func(context.Context, address.Address, types.TipSetKey) (*abi.BitField, error) `perm:"read"`
StateAllMinerFaults func(context.Context, abi.ChainEpoch, types.TipSetKey) ([]*api.Fault, error) `perm:"read"`
StateMinerRecoveries func(context.Context, address.Address, types.TipSetKey) (*abi.BitField, error) `perm:"read"`
StateMinerPreCommitDepositForPower func(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (types.BigInt, error) `perm:"read"`
StateMinerInitialPledgeCollateral func(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (types.BigInt, error) `perm:"read"`
StateMinerAvailableBalance func(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) `perm:"read"`
StateSectorPreCommitInfo func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (miner.SectorPreCommitOnChainInfo, error) `perm:"read"`
StateSectorGetInfo func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*miner.SectorOnChainInfo, error) `perm:"read"`
StateSectorExpiration func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*api.SectorExpiration, error) `perm:"read"`
StateSectorPartition func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*api.SectorLocation, error) `perm:"read"`
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, 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"`
StateListMiners func(context.Context, types.TipSetKey) ([]address.Address, error) `perm:"read"`
StateListActors func(context.Context, types.TipSetKey) ([]address.Address, error) `perm:"read"`
StateMarketBalance func(context.Context, address.Address, types.TipSetKey) (api.MarketBalance, error) `perm:"read"`
StateMarketParticipants func(context.Context, types.TipSetKey) (map[string]api.MarketBalance, error) `perm:"read"`
StateMarketDeals func(context.Context, types.TipSetKey) (map[string]api.MarketDeal, error) `perm:"read"`
StateMarketStorageDeal func(context.Context, abi.DealID, types.TipSetKey) (*api.MarketDeal, error) `perm:"read"`
StateLookupID func(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) `perm:"read"`
StateAccountKey func(context.Context, address.Address, types.TipSetKey) (address.Address, error) `perm:"read"`
StateChangedActors func(context.Context, cid.Cid, cid.Cid) (map[string]types.Actor, error) `perm:"read"`
StateGetReceipt func(context.Context, cid.Cid, types.TipSetKey) (*types.MessageReceipt, error) `perm:"read"`
StateMinerSectorCount func(context.Context, address.Address, types.TipSetKey) (api.MinerSectors, error) `perm:"read"`
StateListMessages func(ctx context.Context, match *types.Message, tsk types.TipSetKey, toht abi.ChainEpoch) ([]cid.Cid, error) `perm:"read"`
StateCompute func(context.Context, abi.ChainEpoch, []*types.Message, types.TipSetKey) (*api.ComputeStateOutput, error) `perm:"read"`
StateVerifiedClientStatus func(context.Context, address.Address, types.TipSetKey) (*verifreg.DataCap, error) `perm:"read"`
StateDealProviderCollateralBounds func(context.Context, abi.PaddedPieceSize, bool, types.TipSetKey) (api.DealCollateralBounds, error) `perm:"read"`
StateCirculatingSupply func(context.Context, types.TipSetKey) (abi.TokenAmount, error) `perm:"read"`
MsigGetAvailableBalance func(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) `perm:"read"`
MsigCreate func(context.Context, uint64, []address.Address, abi.ChainEpoch, types.BigInt, address.Address, types.BigInt) (cid.Cid, error) `perm:"sign"`
@ -177,10 +190,12 @@ type FullNodeStruct struct {
MarketEnsureAvailable func(context.Context, address.Address, address.Address, types.BigInt) (cid.Cid, error) `perm:"sign"`
PaychGet func(ctx context.Context, from, to address.Address, ensureFunds types.BigInt) (*api.ChannelInfo, error) `perm:"sign"`
PaychGet func(ctx context.Context, from, to address.Address, amt types.BigInt) (*api.ChannelInfo, error) `perm:"sign"`
PaychGetWaitReady func(context.Context, cid.Cid) (address.Address, error) `perm:"sign"`
PaychList func(context.Context) ([]address.Address, error) `perm:"read"`
PaychStatus func(context.Context, address.Address) (*api.PaychStatus, error) `perm:"read"`
PaychClose func(context.Context, address.Address) (cid.Cid, error) `perm:"sign"`
PaychSettle func(context.Context, address.Address) (cid.Cid, error) `perm:"sign"`
PaychCollect func(context.Context, address.Address) (cid.Cid, error) `perm:"sign"`
PaychAllocateLane func(context.Context, address.Address) (uint64, error) `perm:"sign"`
PaychNewPayment func(ctx context.Context, from, to address.Address, vouchers []api.VoucherSpec) (*api.PaymentInfo, error) `perm:"sign"`
PaychVoucherCheck func(context.Context, *paych.SignedVoucher) error `perm:"read"`
@ -206,28 +221,35 @@ type StorageMinerStruct struct {
MiningBase func(context.Context) (*types.TipSet, error) `perm:"read"`
MarketImportDealData func(context.Context, cid.Cid, string) error `perm:"write"`
MarketListDeals func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"`
MarketListIncompleteDeals func(ctx context.Context) ([]storagemarket.MinerDeal, error) `perm:"read"`
MarketSetAsk func(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error `perm:"admin"`
MarketGetAsk func(ctx context.Context) (*storagemarket.SignedStorageAsk, error) `perm:"read"`
MarketImportDealData func(context.Context, cid.Cid, string) error `perm:"write"`
MarketListDeals func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"`
MarketListRetrievalDeals func(ctx context.Context) ([]retrievalmarket.ProviderDealState, error) `perm:"read"`
MarketGetDealUpdates func(ctx context.Context, d cid.Cid) (<-chan storagemarket.MinerDeal, error) `perm:"read"`
MarketListIncompleteDeals func(ctx context.Context) ([]storagemarket.MinerDeal, error) `perm:"read"`
MarketSetAsk func(ctx context.Context, price types.BigInt, verifiedPrice types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error `perm:"admin"`
MarketGetAsk func(ctx context.Context) (*storagemarket.SignedStorageAsk, error) `perm:"read"`
MarketSetRetrievalAsk func(ctx context.Context, rask *retrievalmarket.Ask) error `perm:"admin"`
MarketGetRetrievalAsk func(ctx context.Context) (*retrievalmarket.Ask, error) `perm:"read"`
PledgeSector func(context.Context) error `perm:"write"`
SectorsStatus func(context.Context, abi.SectorNumber) (api.SectorInfo, error) `perm:"read"`
SectorsList func(context.Context) ([]abi.SectorNumber, error) `perm:"read"`
SectorsRefs func(context.Context) (map[string][]api.SealedRef, error) `perm:"read"`
SectorStartSealing func(context.Context, abi.SectorNumber) error `perm:"write"`
SectorSetSealDelay func(context.Context, time.Duration) error `perm:"write"`
SectorGetSealDelay func(context.Context) (time.Duration, error) `perm:"read"`
SectorSetExpectedSealDuration func(context.Context, time.Duration) error `perm:"write"`
SectorGetExpectedSealDuration func(context.Context) (time.Duration, error) `perm:"read"`
SectorsUpdate func(context.Context, abi.SectorNumber, api.SectorState) error `perm:"admin"`
SectorRemove func(context.Context, abi.SectorNumber) error `perm:"admin"`
SectorMarkForUpgrade func(ctx context.Context, id abi.SectorNumber) error `perm:"admin"`
SectorsStatus func(ctx context.Context, sid abi.SectorNumber, showOnChainInfo bool) (api.SectorInfo, error) `perm:"read"`
SectorsList func(context.Context) ([]abi.SectorNumber, error) `perm:"read"`
SectorsRefs func(context.Context) (map[string][]api.SealedRef, error) `perm:"read"`
SectorStartSealing func(context.Context, abi.SectorNumber) error `perm:"write"`
SectorSetSealDelay func(context.Context, time.Duration) error `perm:"write"`
SectorGetSealDelay func(context.Context) (time.Duration, error) `perm:"read"`
SectorSetExpectedSealDuration func(context.Context, time.Duration) error `perm:"write"`
SectorGetExpectedSealDuration func(context.Context) (time.Duration, error) `perm:"read"`
SectorsUpdate func(context.Context, abi.SectorNumber, api.SectorState) error `perm:"admin"`
SectorRemove func(context.Context, abi.SectorNumber) error `perm:"admin"`
SectorMarkForUpgrade func(ctx context.Context, id 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"`
WorkerJobs func(context.Context) (map[uint64][]storiface.WorkerJob, error) `perm:"admin"`
SealingSchedDiag func(context.Context) (interface{}, error) `perm:"admin"`
StorageList func(context.Context) (map[stores.ID][]stores.Decl, error) `perm:"admin"`
StorageLocal func(context.Context) (map[stores.ID]string, error) `perm:"admin"`
@ -256,6 +278,11 @@ type StorageMinerStruct struct {
DealsSetPieceCidBlocklist func(context.Context, []cid.Cid) error `perm:"admin"`
StorageAddLocal func(ctx context.Context, path string) error `perm:"admin"`
PiecesListPieces func(ctx context.Context) ([]cid.Cid, error) `perm:"read"`
PiecesListCidInfos func(ctx context.Context) ([]cid.Cid, error) `perm:"read"`
PiecesGetPieceInfo func(ctx context.Context, pieceCid cid.Cid) (*piecestore.PieceInfo, error) `perm:"read"`
PiecesGetCIDInfo func(ctx context.Context, payloadCid cid.Cid) (*piecestore.CIDInfo, error) `perm:"read"`
}
}
@ -279,7 +306,7 @@ type WorkerStruct struct {
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"`
ReadPiece func(context.Context, io.Writer, abi.SectorID, storiface.UnpaddedByteIndex, abi.UnpaddedPieceSize) (bool, error) `perm:"admin"`
Fetch func(context.Context, abi.SectorID, stores.SectorFileType, stores.PathType, stores.AcquireMode) error `perm:"admin"`
@ -356,7 +383,7 @@ func (c *FullNodeStruct) ClientListImports(ctx context.Context) ([]api.Import, e
return c.Internal.ClientListImports(ctx)
}
func (c *FullNodeStruct) ClientRemoveImport(ctx context.Context, importID int) error {
func (c *FullNodeStruct) ClientRemoveImport(ctx context.Context, importID multistore.StoreID) error {
return c.Internal.ClientRemoveImport(ctx, importID)
}
@ -379,6 +406,7 @@ func (c *FullNodeStruct) ClientMinerQueryOffer(ctx context.Context, miner addres
func (c *FullNodeStruct) ClientStartDeal(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) {
return c.Internal.ClientStartDeal(ctx, params)
}
func (c *FullNodeStruct) ClientGetDealInfo(ctx context.Context, deal cid.Cid) (*api.DealInfo, error) {
return c.Internal.ClientGetDealInfo(ctx, deal)
}
@ -402,9 +430,17 @@ func (c *FullNodeStruct) ClientGenCar(ctx context.Context, ref api.FileRef, outp
return c.Internal.ClientGenCar(ctx, ref, outpath)
}
func (c *FullNodeStruct) GasEstimateGasPrice(ctx context.Context, nblocksincl uint64,
func (c *FullNodeStruct) ClientDealSize(ctx context.Context, root cid.Cid) (api.DataSize, error) {
return c.Internal.ClientDealSize(ctx, root)
}
func (c *FullNodeStruct) GasEsitmateGasPremium(ctx context.Context, nblocksincl uint64,
sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error) {
return c.Internal.GasEstimateGasPrice(ctx, nblocksincl, sender, gaslimit, tsk)
return c.Internal.GasEsitmateGasPremium(ctx, nblocksincl, sender, gaslimit, tsk)
}
func (c *FullNodeStruct) GasEstimateFeeCap(ctx context.Context, msg *types.Message,
maxqueueblks int64, tsk types.TipSetKey) (types.BigInt, error) {
return c.Internal.GasEstimateFeeCap(ctx, msg, maxqueueblks, tsk)
}
func (c *FullNodeStruct) GasEstimateGasLimit(ctx context.Context, msg *types.Message,
@ -412,6 +448,18 @@ func (c *FullNodeStruct) GasEstimateGasLimit(ctx context.Context, msg *types.Mes
return c.Internal.GasEstimateGasLimit(ctx, msg, tsk)
}
func (c *FullNodeStruct) MpoolGetConfig(ctx context.Context) (*types.MpoolConfig, error) {
return c.Internal.MpoolGetConfig(ctx)
}
func (c *FullNodeStruct) MpoolSetConfig(ctx context.Context, cfg *types.MpoolConfig) error {
return c.Internal.MpoolSetConfig(ctx, cfg)
}
func (c *FullNodeStruct) MpoolSelect(ctx context.Context, tsk types.TipSetKey) ([]*types.SignedMessage, error) {
return c.Internal.MpoolSelect(ctx, tsk)
}
func (c *FullNodeStruct) MpoolPending(ctx context.Context, tsk types.TipSetKey) ([]*types.SignedMessage, error) {
return c.Internal.MpoolPending(ctx, tsk)
}
@ -428,10 +476,6 @@ func (c *FullNodeStruct) MpoolSub(ctx context.Context) (<-chan api.MpoolUpdate,
return c.Internal.MpoolSub(ctx)
}
func (c *FullNodeStruct) MpoolEstimateGasPrice(ctx context.Context, nblocksincl uint64, sender address.Address, limit int64, tsk types.TipSetKey) (types.BigInt, error) {
return c.Internal.GasEstimateGasPrice(ctx, nblocksincl, sender, limit, tsk)
}
func (c *FullNodeStruct) MinerGetBaseInfo(ctx context.Context, maddr address.Address, epoch abi.ChainEpoch, tsk types.TipSetKey) (*api.MiningBaseInfo, error) {
return c.Internal.MinerGetBaseInfo(ctx, maddr, epoch, tsk)
}
@ -568,6 +612,10 @@ func (c *FullNodeStruct) ChainExport(ctx context.Context, tsk types.TipSetKey) (
return c.Internal.ChainExport(ctx, tsk)
}
func (c *FullNodeStruct) BeaconGetEntry(ctx context.Context, epoch abi.ChainEpoch) (*types.BeaconEntry, error) {
return c.Internal.BeaconGetEntry(ctx, epoch)
}
func (c *FullNodeStruct) SyncState(ctx context.Context) (*api.SyncState, error) {
return c.Internal.SyncState(ctx)
}
@ -632,6 +680,10 @@ func (c *FullNodeStruct) StateMinerRecoveries(ctx context.Context, actor address
return c.Internal.StateMinerRecoveries(ctx, actor, tsk)
}
func (c *FullNodeStruct) StateMinerPreCommitDepositForPower(ctx context.Context, maddr address.Address, pci miner.SectorPreCommitInfo, tsk types.TipSetKey) (types.BigInt, error) {
return c.Internal.StateMinerPreCommitDepositForPower(ctx, maddr, pci, tsk)
}
func (c *FullNodeStruct) StateMinerInitialPledgeCollateral(ctx context.Context, maddr address.Address, pci miner.SectorPreCommitInfo, tsk types.TipSetKey) (types.BigInt, error) {
return c.Internal.StateMinerInitialPledgeCollateral(ctx, maddr, pci, tsk)
}
@ -736,6 +788,14 @@ func (c *FullNodeStruct) StateVerifiedClientStatus(ctx context.Context, addr add
return c.Internal.StateVerifiedClientStatus(ctx, addr, tsk)
}
func (c *FullNodeStruct) StateDealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, verified bool, tsk types.TipSetKey) (api.DealCollateralBounds, error) {
return c.Internal.StateDealProviderCollateralBounds(ctx, size, verified, tsk)
}
func (c *FullNodeStruct) StateCirculatingSupply(ctx context.Context, tsk types.TipSetKey) (abi.TokenAmount, error) {
return c.Internal.StateCirculatingSupply(ctx, tsk)
}
func (c *FullNodeStruct) MsigGetAvailableBalance(ctx context.Context, a address.Address, tsk types.TipSetKey) (types.BigInt, error) {
return c.Internal.MsigGetAvailableBalance(ctx, a, tsk)
}
@ -772,8 +832,12 @@ func (c *FullNodeStruct) MarketEnsureAvailable(ctx context.Context, addr, wallet
return c.Internal.MarketEnsureAvailable(ctx, addr, wallet, amt)
}
func (c *FullNodeStruct) PaychGet(ctx context.Context, from, to address.Address, ensureFunds types.BigInt) (*api.ChannelInfo, error) {
return c.Internal.PaychGet(ctx, from, to, ensureFunds)
func (c *FullNodeStruct) PaychGet(ctx context.Context, from, to address.Address, amt types.BigInt) (*api.ChannelInfo, error) {
return c.Internal.PaychGet(ctx, from, to, amt)
}
func (c *FullNodeStruct) PaychGetWaitReady(ctx context.Context, mcid cid.Cid) (address.Address, error) {
return c.Internal.PaychGetWaitReady(ctx, mcid)
}
func (c *FullNodeStruct) PaychList(ctx context.Context) ([]address.Address, error) {
@ -804,8 +868,12 @@ func (c *FullNodeStruct) PaychVoucherList(ctx context.Context, pch address.Addre
return c.Internal.PaychVoucherList(ctx, pch)
}
func (c *FullNodeStruct) PaychClose(ctx context.Context, a address.Address) (cid.Cid, error) {
return c.Internal.PaychClose(ctx, a)
func (c *FullNodeStruct) PaychSettle(ctx context.Context, a address.Address) (cid.Cid, error) {
return c.Internal.PaychSettle(ctx, a)
}
func (c *FullNodeStruct) PaychCollect(ctx context.Context, a address.Address) (cid.Cid, error) {
return c.Internal.PaychCollect(ctx, a)
}
func (c *FullNodeStruct) PaychAllocateLane(ctx context.Context, ch address.Address) (uint64, error) {
@ -839,8 +907,8 @@ func (c *StorageMinerStruct) PledgeSector(ctx context.Context) error {
}
// Get the status of a given sector by ID
func (c *StorageMinerStruct) SectorsStatus(ctx context.Context, sid abi.SectorNumber) (api.SectorInfo, error) {
return c.Internal.SectorsStatus(ctx, sid)
func (c *StorageMinerStruct) SectorsStatus(ctx context.Context, sid abi.SectorNumber, showOnChainInfo bool) (api.SectorInfo, error) {
return c.Internal.SectorsStatus(ctx, sid, showOnChainInfo)
}
// List all staged sectors
@ -892,6 +960,14 @@ func (c *StorageMinerStruct) WorkerStats(ctx context.Context) (map[uint64]storif
return c.Internal.WorkerStats(ctx)
}
func (c *StorageMinerStruct) WorkerJobs(ctx context.Context) (map[uint64][]storiface.WorkerJob, error) {
return c.Internal.WorkerJobs(ctx)
}
func (c *StorageMinerStruct) SealingSchedDiag(ctx context.Context) (interface{}, error) {
return c.Internal.SealingSchedDiag(ctx)
}
func (c *StorageMinerStruct) StorageAttach(ctx context.Context, si stores.StorageInfo, st fsutil.FsStat) error {
return c.Internal.StorageAttach(ctx, si, st)
}
@ -948,18 +1024,34 @@ func (c *StorageMinerStruct) MarketListDeals(ctx context.Context) ([]storagemark
return c.Internal.MarketListDeals(ctx)
}
func (c *StorageMinerStruct) MarketListRetrievalDeals(ctx context.Context) ([]retrievalmarket.ProviderDealState, error) {
return c.Internal.MarketListRetrievalDeals(ctx)
}
func (c *StorageMinerStruct) MarketGetDealUpdates(ctx context.Context, d cid.Cid) (<-chan storagemarket.MinerDeal, error) {
return c.Internal.MarketGetDealUpdates(ctx, d)
}
func (c *StorageMinerStruct) MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error) {
return c.Internal.MarketListIncompleteDeals(ctx)
}
func (c *StorageMinerStruct) MarketSetAsk(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error {
return c.Internal.MarketSetAsk(ctx, price, duration, minPieceSize, maxPieceSize)
func (c *StorageMinerStruct) MarketSetAsk(ctx context.Context, price types.BigInt, verifiedPrice types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error {
return c.Internal.MarketSetAsk(ctx, price, verifiedPrice, duration, minPieceSize, maxPieceSize)
}
func (c *StorageMinerStruct) MarketGetAsk(ctx context.Context) (*storagemarket.SignedStorageAsk, error) {
return c.Internal.MarketGetAsk(ctx)
}
func (c *StorageMinerStruct) MarketSetRetrievalAsk(ctx context.Context, rask *retrievalmarket.Ask) error {
return c.Internal.MarketSetRetrievalAsk(ctx, rask)
}
func (c *StorageMinerStruct) MarketGetRetrievalAsk(ctx context.Context) (*retrievalmarket.Ask, error) {
return c.Internal.MarketGetRetrievalAsk(ctx)
}
func (c *StorageMinerStruct) DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error {
return c.Internal.DealsImportData(ctx, dealPropCid, file)
}
@ -1012,6 +1104,22 @@ func (c *StorageMinerStruct) StorageAddLocal(ctx context.Context, path string) e
return c.Internal.StorageAddLocal(ctx, path)
}
func (c *StorageMinerStruct) PiecesListPieces(ctx context.Context) ([]cid.Cid, error) {
return c.Internal.PiecesListPieces(ctx)
}
func (c *StorageMinerStruct) PiecesListCidInfos(ctx context.Context) ([]cid.Cid, error) {
return c.Internal.PiecesListCidInfos(ctx)
}
func (c *StorageMinerStruct) PiecesGetPieceInfo(ctx context.Context, pieceCid cid.Cid) (*piecestore.PieceInfo, error) {
return c.Internal.PiecesGetPieceInfo(ctx, pieceCid)
}
func (c *StorageMinerStruct) PiecesGetCIDInfo(ctx context.Context, payloadCid cid.Cid) (*piecestore.CIDInfo, error) {
return c.Internal.PiecesGetCIDInfo(ctx, payloadCid)
}
// WorkerStruct
func (w *WorkerStruct) Version(ctx context.Context) (build.Version, error) {
@ -1066,7 +1174,7 @@ func (w *WorkerStruct) UnsealPiece(ctx context.Context, id abi.SectorID, index s
return w.Internal.UnsealPiece(ctx, id, index, size, randomness, c)
}
func (w *WorkerStruct) ReadPiece(ctx context.Context, writer io.Writer, id abi.SectorID, index storiface.UnpaddedByteIndex, size abi.UnpaddedPieceSize) error {
func (w *WorkerStruct) ReadPiece(ctx context.Context, writer io.Writer, id abi.SectorID, index storiface.UnpaddedByteIndex, size abi.UnpaddedPieceSize) (bool, error) {
return w.Internal.ReadPiece(ctx, writer, id, index, size)
}

View File

@ -33,7 +33,7 @@ func (t *PaymentInfo) MarshalCBOR(w io.Writer) error {
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Channel"))); err != nil {
return err
}
if _, err := io.WriteString(w, "Channel"); err != nil {
if _, err := io.WriteString(w, string("Channel")); err != nil {
return err
}
@ -49,7 +49,7 @@ func (t *PaymentInfo) MarshalCBOR(w io.Writer) error {
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("ChannelMessage"))); err != nil {
return err
}
if _, err := io.WriteString(w, "ChannelMessage"); err != nil {
if _, err := io.WriteString(w, string("ChannelMessage")); err != nil {
return err
}
@ -71,7 +71,7 @@ func (t *PaymentInfo) MarshalCBOR(w io.Writer) error {
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Vouchers"))); err != nil {
return err
}
if _, err := io.WriteString(w, "Vouchers"); err != nil {
if _, err := io.WriteString(w, string("Vouchers")); err != nil {
return err
}
@ -214,7 +214,7 @@ func (t *SealedRef) MarshalCBOR(w io.Writer) error {
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("SectorID"))); err != nil {
return err
}
if _, err := io.WriteString(w, "SectorID"); err != nil {
if _, err := io.WriteString(w, string("SectorID")); err != nil {
return err
}
@ -222,7 +222,7 @@ func (t *SealedRef) MarshalCBOR(w io.Writer) error {
return err
}
// t.Offset (uint64) (uint64)
// t.Offset (abi.PaddedPieceSize) (uint64)
if len("Offset") > cbg.MaxLength {
return xerrors.Errorf("Value in field \"Offset\" was too long")
}
@ -230,7 +230,7 @@ func (t *SealedRef) MarshalCBOR(w io.Writer) error {
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Offset"))); err != nil {
return err
}
if _, err := io.WriteString(w, "Offset"); err != nil {
if _, err := io.WriteString(w, string("Offset")); err != nil {
return err
}
@ -246,7 +246,7 @@ func (t *SealedRef) MarshalCBOR(w io.Writer) error {
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Size"))); err != nil {
return err
}
if _, err := io.WriteString(w, "Size"); err != nil {
if _, err := io.WriteString(w, string("Size")); err != nil {
return err
}
@ -305,7 +305,7 @@ func (t *SealedRef) UnmarshalCBOR(r io.Reader) error {
t.SectorID = abi.SectorNumber(extra)
}
// t.Offset (uint64) (uint64)
// t.Offset (abi.PaddedPieceSize) (uint64)
case "Offset":
{
@ -317,7 +317,7 @@ func (t *SealedRef) UnmarshalCBOR(r io.Reader) error {
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.Offset = uint64(extra)
t.Offset = abi.PaddedPieceSize(extra)
}
// t.Size (abi.UnpaddedPieceSize) (uint64)
@ -362,7 +362,7 @@ func (t *SealedRefs) MarshalCBOR(w io.Writer) error {
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Refs"))); err != nil {
return err
}
if _, err := io.WriteString(w, "Refs"); err != nil {
if _, err := io.WriteString(w, string("Refs")); err != nil {
return err
}
@ -470,7 +470,7 @@ func (t *SealTicket) MarshalCBOR(w io.Writer) error {
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Value"))); err != nil {
return err
}
if _, err := io.WriteString(w, "Value"); err != nil {
if _, err := io.WriteString(w, string("Value")); err != nil {
return err
}
@ -482,7 +482,7 @@ func (t *SealTicket) MarshalCBOR(w io.Writer) error {
return err
}
if _, err := w.Write(t.Value); err != nil {
if _, err := w.Write(t.Value[:]); err != nil {
return err
}
@ -494,7 +494,7 @@ func (t *SealTicket) MarshalCBOR(w io.Writer) error {
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Epoch"))); err != nil {
return err
}
if _, err := io.WriteString(w, "Epoch"); err != nil {
if _, err := io.WriteString(w, string("Epoch")); err != nil {
return err
}
@ -557,8 +557,12 @@ func (t *SealTicket) UnmarshalCBOR(r io.Reader) error {
if maj != cbg.MajByteString {
return fmt.Errorf("expected byte array")
}
t.Value = make([]byte, extra)
if _, err := io.ReadFull(br, t.Value); err != nil {
if extra > 0 {
t.Value = make([]uint8, extra)
}
if _, err := io.ReadFull(br, t.Value[:]); err != nil {
return err
}
// t.Epoch (abi.ChainEpoch) (int64)
@ -614,7 +618,7 @@ func (t *SealSeed) MarshalCBOR(w io.Writer) error {
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Value"))); err != nil {
return err
}
if _, err := io.WriteString(w, "Value"); err != nil {
if _, err := io.WriteString(w, string("Value")); err != nil {
return err
}
@ -626,7 +630,7 @@ func (t *SealSeed) MarshalCBOR(w io.Writer) error {
return err
}
if _, err := w.Write(t.Value); err != nil {
if _, err := w.Write(t.Value[:]); err != nil {
return err
}
@ -638,7 +642,7 @@ func (t *SealSeed) MarshalCBOR(w io.Writer) error {
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Epoch"))); err != nil {
return err
}
if _, err := io.WriteString(w, "Epoch"); err != nil {
if _, err := io.WriteString(w, string("Epoch")); err != nil {
return err
}
@ -701,8 +705,12 @@ func (t *SealSeed) UnmarshalCBOR(r io.Reader) error {
if maj != cbg.MajByteString {
return fmt.Errorf("expected byte array")
}
t.Value = make([]byte, extra)
if _, err := io.ReadFull(br, t.Value); err != nil {
if extra > 0 {
t.Value = make([]uint8, extra)
}
if _, err := io.ReadFull(br, t.Value[:]); err != nil {
return err
}
// t.Epoch (abi.ChainEpoch) (int64)

View File

@ -40,7 +40,7 @@ func TestCCUpgrade(t *testing.T, b APIBuilder, blocktime time.Duration) {
defer close(done)
for atomic.LoadInt64(&mine) == 1 {
time.Sleep(blocktime)
if err := sn[0].MineOne(ctx, func(bool, error) {}); err != nil {
if err := sn[0].MineOne(ctx, MineNext); err != nil {
t.Error(err)
}
}
@ -54,7 +54,7 @@ func TestCCUpgrade(t *testing.T, b APIBuilder, blocktime time.Duration) {
CC := abi.SectorNumber(GenesisPreseals + 1)
Upgraded := CC + 1
pledgeSectors(t, ctx, miner, 1, nil)
pledgeSectors(t, ctx, miner, 1, 0, nil)
sl, err := miner.SectorsList(ctx)
if err != nil {

View File

@ -22,6 +22,7 @@ import (
"github.com/filecoin-project/go-fil-markets/storagemarket"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/miner"
sealing "github.com/filecoin-project/storage-fsm"
dag "github.com/ipfs/go-merkledag"
dstest "github.com/ipfs/go-merkledag/test"
@ -32,6 +33,11 @@ import (
ipld "github.com/ipfs/go-ipld-format"
)
var MineNext = miner.MineReq{
InjectNulls: 0,
Done: func(bool, error) {},
}
func init() {
logging.SetAllLoggers(logging.LevelInfo)
build.InsecurePoStValidation = true
@ -61,7 +67,7 @@ func TestDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, carExport
defer close(done)
for atomic.LoadInt64(&mine) == 1 {
time.Sleep(blocktime)
if err := sn[0].MineOne(ctx, func(bool, error) {}); err != nil {
if err := sn[0].MineOne(ctx, MineNext); err != nil {
t.Error(err)
}
}
@ -99,7 +105,7 @@ func TestDoubleDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration) {
defer close(done)
for atomic.LoadInt64(&mine) == 1 {
time.Sleep(blocktime)
if err := sn[0].MineOne(ctx, func(bool, error) {}); err != nil {
if err := sn[0].MineOne(ctx, MineNext); err != nil {
t.Error(err)
}
}
@ -129,7 +135,7 @@ func makeDeal(t *testing.T, ctx context.Context, rseed int, client *impl.FullNod
// TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this
time.Sleep(time.Second)
waitDealSealed(t, ctx, miner, client, deal)
waitDealSealed(t, ctx, miner, client, deal, false)
// Retrieval
info, err := client.ClientGetDealInfo(ctx, *deal)
@ -138,6 +144,137 @@ func makeDeal(t *testing.T, ctx context.Context, rseed int, client *impl.FullNod
testRetrieval(t, ctx, err, client, fcid, &info.PieceCID, carExport, data)
}
func TestFastRetrievalDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration) {
_ = os.Setenv("BELLMAN_NO_GPU", "1")
ctx := context.Background()
n, sn := b(t, 1, oneMiner)
client := n[0].FullNode.(*impl.FullNodeAPI)
miner := sn[0]
addrinfo, err := client.NetAddrsListen(ctx)
if err != nil {
t.Fatal(err)
}
if err := miner.NetConnect(ctx, addrinfo); err != nil {
t.Fatal(err)
}
time.Sleep(time.Second)
mine := int64(1)
done := make(chan struct{})
go func() {
defer close(done)
for atomic.LoadInt64(&mine) == 1 {
time.Sleep(blocktime)
if err := sn[0].MineOne(ctx, MineNext); err != nil {
t.Error(err)
}
}
}()
data := make([]byte, 1600)
rand.New(rand.NewSource(int64(8))).Read(data)
r := bytes.NewReader(data)
fcid, err := client.ClientImportLocal(ctx, r)
if err != nil {
t.Fatal(err)
}
fmt.Println("FILE CID: ", fcid)
deal := startDeal(t, ctx, miner, client, fcid, true)
waitDealPublished(t, ctx, miner, deal)
fmt.Println("deal published, retrieving")
// Retrieval
info, err := client.ClientGetDealInfo(ctx, *deal)
require.NoError(t, err)
testRetrieval(t, ctx, err, client, fcid, &info.PieceCID, false, data)
atomic.AddInt64(&mine, -1)
fmt.Println("shutting down mining")
<-done
}
func TestSenondDealRetrieval(t *testing.T, b APIBuilder, blocktime time.Duration) {
_ = os.Setenv("BELLMAN_NO_GPU", "1")
ctx := context.Background()
n, sn := b(t, 1, oneMiner)
client := n[0].FullNode.(*impl.FullNodeAPI)
miner := sn[0]
addrinfo, err := client.NetAddrsListen(ctx)
if err != nil {
t.Fatal(err)
}
if err := miner.NetConnect(ctx, addrinfo); err != nil {
t.Fatal(err)
}
time.Sleep(time.Second)
mine := int64(1)
done := make(chan struct{})
go func() {
defer close(done)
for atomic.LoadInt64(&mine) == 1 {
time.Sleep(blocktime)
if err := sn[0].MineOne(ctx, MineNext); err != nil {
t.Error(err)
}
}
}()
{
data1 := make([]byte, 800)
rand.New(rand.NewSource(int64(3))).Read(data1)
r := bytes.NewReader(data1)
fcid1, err := client.ClientImportLocal(ctx, r)
if err != nil {
t.Fatal(err)
}
data2 := make([]byte, 800)
rand.New(rand.NewSource(int64(9))).Read(data2)
r2 := bytes.NewReader(data2)
fcid2, err := client.ClientImportLocal(ctx, r2)
if err != nil {
t.Fatal(err)
}
deal1 := startDeal(t, ctx, miner, client, fcid1, true)
// TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this
time.Sleep(time.Second)
waitDealSealed(t, ctx, miner, client, deal1, true)
deal2 := startDeal(t, ctx, miner, client, fcid2, true)
time.Sleep(time.Second)
waitDealSealed(t, ctx, miner, client, deal2, false)
// Retrieval
info, err := client.ClientGetDealInfo(ctx, *deal2)
require.NoError(t, err)
rf, _ := miner.SectorsRefs(ctx)
fmt.Printf("refs: %+v\n", rf)
testRetrieval(t, ctx, err, client, fcid2, &info.PieceCID, false, data2)
}
atomic.AddInt64(&mine, -1)
fmt.Println("shutting down mining")
<-done
}
func startDeal(t *testing.T, ctx context.Context, miner TestStorageNode, client *impl.FullNodeAPI, fcid cid.Cid, fastRet bool) *cid.Cid {
maddr, err := miner.ActorAddress(ctx)
if err != nil {
@ -149,11 +286,14 @@ func startDeal(t *testing.T, ctx context.Context, miner TestStorageNode, client
t.Fatal(err)
}
deal, err := client.ClientStartDeal(ctx, &api.StartDealParams{
Data: &storagemarket.DataRef{Root: fcid},
Data: &storagemarket.DataRef{
TransferType: storagemarket.TTGraphsync,
Root: fcid,
},
Wallet: addr,
Miner: maddr,
EpochPrice: types.NewInt(1000000),
MinBlocksDuration: 100,
MinBlocksDuration: uint64(build.MinDealDuration),
FastRetrieval: fastRet,
})
if err != nil {
@ -162,7 +302,7 @@ func startDeal(t *testing.T, ctx context.Context, miner TestStorageNode, client
return deal
}
func waitDealSealed(t *testing.T, ctx context.Context, miner TestStorageNode, client *impl.FullNodeAPI, deal *cid.Cid) {
func waitDealSealed(t *testing.T, ctx context.Context, miner TestStorageNode, client *impl.FullNodeAPI, deal *cid.Cid, noseal bool) {
loop:
for {
di, err := client.ClientGetDealInfo(ctx, *deal)
@ -171,6 +311,9 @@ loop:
}
switch di.State {
case storagemarket.StorageDealSealing:
if noseal {
return
}
startSealingWaiting(t, ctx, miner)
case storagemarket.StorageDealProposalRejected:
t.Fatal("deal rejected")
@ -187,12 +330,40 @@ loop:
}
}
func waitDealPublished(t *testing.T, ctx context.Context, miner TestStorageNode, deal *cid.Cid) {
subCtx, cancel := context.WithCancel(ctx)
defer cancel()
updates, err := miner.MarketGetDealUpdates(subCtx, *deal)
if err != nil {
t.Fatal(err)
}
for {
select {
case <-ctx.Done():
t.Fatal("context timeout")
case di := <-updates:
switch di.State {
case storagemarket.StorageDealProposalRejected:
t.Fatal("deal rejected")
case storagemarket.StorageDealFailing:
t.Fatal("deal failed")
case storagemarket.StorageDealError:
t.Fatal("deal errored", di.Message)
case storagemarket.StorageDealFinalizing, storagemarket.StorageDealSealing, storagemarket.StorageDealActive:
fmt.Println("COMPLETE", di)
return
}
fmt.Println("Deal state: ", storagemarket.DealStates[di.State])
}
}
}
func startSealingWaiting(t *testing.T, ctx context.Context, miner TestStorageNode) {
snums, err := miner.SectorsList(ctx)
require.NoError(t, err)
for _, snum := range snums {
si, err := miner.SectorsStatus(ctx, snum)
si, err := miner.SectorsStatus(ctx, snum, false)
require.NoError(t, err)
t.Logf("Sector state: %s", si.State)

View File

@ -16,6 +16,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/miner"
"github.com/filecoin-project/lotus/node/impl"
)
@ -26,22 +27,25 @@ func (ts *testSuite) testMining(t *testing.T) {
apis, sn := ts.makeNodes(t, 1, oneMiner)
api := apis[0]
h1, err := api.ChainHead(ctx)
require.NoError(t, err)
require.Equal(t, abi.ChainEpoch(0), h1.Height())
newHeads, err := api.ChainNotify(ctx)
require.NoError(t, err)
<-newHeads
initHead := (<-newHeads)[0]
if initHead.Val.Height() != 2 {
<-newHeads
}
err = sn[0].MineOne(ctx, func(bool, error) {})
h1, err := api.ChainHead(ctx)
require.NoError(t, err)
require.Equal(t, abi.ChainEpoch(2), h1.Height())
err = sn[0].MineOne(ctx, MineNext)
require.NoError(t, err)
<-newHeads
h2, err := api.ChainHead(ctx)
require.NoError(t, err)
require.Equal(t, abi.ChainEpoch(1), h2.Height())
require.Equal(t, abi.ChainEpoch(3), h2.Height())
}
func (ts *testSuite) testMiningReal(t *testing.T) {
@ -54,31 +58,34 @@ func (ts *testSuite) testMiningReal(t *testing.T) {
apis, sn := ts.makeNodes(t, 1, oneMiner)
api := apis[0]
h1, err := api.ChainHead(ctx)
require.NoError(t, err)
require.Equal(t, abi.ChainEpoch(0), h1.Height())
newHeads, err := api.ChainNotify(ctx)
require.NoError(t, err)
<-newHeads
initHead := (<-newHeads)[0]
if initHead.Val.Height() != 2 {
<-newHeads
}
err = sn[0].MineOne(ctx, func(bool, error) {})
h1, err := api.ChainHead(ctx)
require.NoError(t, err)
require.Equal(t, abi.ChainEpoch(2), h1.Height())
err = sn[0].MineOne(ctx, MineNext)
require.NoError(t, err)
<-newHeads
h2, err := api.ChainHead(ctx)
require.NoError(t, err)
require.Equal(t, abi.ChainEpoch(1), h2.Height())
require.Equal(t, abi.ChainEpoch(3), h2.Height())
err = sn[0].MineOne(ctx, func(bool, error) {})
err = sn[0].MineOne(ctx, MineNext)
require.NoError(t, err)
<-newHeads
h2, err = api.ChainHead(ctx)
require.NoError(t, err)
require.Equal(t, abi.ChainEpoch(2), h2.Height())
require.Equal(t, abi.ChainEpoch(4), h2.Height())
}
func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExport bool) {
@ -125,28 +132,30 @@ func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExpo
done := make(chan struct{})
minedTwo := make(chan struct{})
m2addr, err := sn[1].ActorAddress(context.TODO())
if err != nil {
t.Fatal(err)
}
go func() {
doneMinedTwo := false
defer close(done)
prevExpect := 0
complChan := minedTwo
for atomic.LoadInt32(&mine) != 0 {
wait := make(chan int, 2)
wait := make(chan int)
mdone := func(mined bool, err error) {
go func() {
n := 0
if mined {
n = 1
}
wait <- n
}()
n := 0
if mined {
n = 1
}
wait <- n
}
if err := sn[0].MineOne(ctx, mdone); err != nil {
if err := sn[0].MineOne(ctx, miner.MineReq{Done: mdone}); err != nil {
t.Error(err)
}
if err := sn[1].MineOne(ctx, mdone); err != nil {
if err := sn[1].MineOne(ctx, miner.MineReq{Done: mdone}); err != nil {
t.Error(err)
}
@ -159,33 +168,28 @@ func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExpo
continue
}
for {
n := 0
for i, node := range sn {
mb, err := node.MiningBase(ctx)
if err != nil {
t.Error(err)
return
}
var nodeOneMined bool
for _, node := range sn {
mb, err := node.MiningBase(ctx)
if err != nil {
t.Error(err)
return
}
if len(mb.Cids()) != expect {
log.Warnf("node %d mining base not complete (%d, want %d)", i, len(mb.Cids()), expect)
continue
for _, b := range mb.Blocks() {
if b.Miner == m2addr {
nodeOneMined = true
break
}
n++
}
if n == len(sn) {
break
}
time.Sleep(blocktime)
}
if prevExpect == 2 && expect == 2 && !doneMinedTwo {
close(minedTwo)
doneMinedTwo = true
if nodeOneMined && complChan != nil {
close(complChan)
complChan = nil
}
prevExpect = expect
}
}()
@ -194,7 +198,7 @@ func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExpo
// TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this
time.Sleep(time.Second)
waitDealSealed(t, ctx, provider, client, deal)
waitDealSealed(t, ctx, provider, client, deal, false)
<-minedTwo

321
api/test/paych.go Normal file
View File

@ -0,0 +1,321 @@
package test
import (
"context"
"fmt"
"os"
"sync/atomic"
"testing"
"time"
"github.com/filecoin-project/specs-actors/actors/builtin"
"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/paych"
"github.com/ipfs/go-cid"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/events"
"github.com/filecoin-project/lotus/chain/events/state"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/wallet"
"github.com/filecoin-project/lotus/miner"
)
func TestPaymentChannels(t *testing.T, b APIBuilder, blocktime time.Duration) {
_ = os.Setenv("BELLMAN_NO_GPU", "1")
ctx := context.Background()
n, sn := b(t, 2, oneMiner)
paymentCreator := n[0]
paymentReceiver := n[1]
miner := sn[0]
// get everyone connected
addrs, err := paymentCreator.NetAddrsListen(ctx)
if err != nil {
t.Fatal(err)
}
if err := paymentReceiver.NetConnect(ctx, addrs); err != nil {
t.Fatal(err)
}
if err := miner.NetConnect(ctx, addrs); err != nil {
t.Fatal(err)
}
// start mining blocks
bm := newBlockMiner(ctx, t, miner, blocktime)
bm.mineBlocks()
// send some funds to register the receiver
receiverAddr, err := paymentReceiver.WalletNew(ctx, wallet.ActSigType("secp256k1"))
if err != nil {
t.Fatal(err)
}
sendFunds(ctx, t, paymentCreator, receiverAddr, abi.NewTokenAmount(1e18))
// setup the payment channel
createrAddr, err := paymentCreator.WalletDefaultAddress(ctx)
if err != nil {
t.Fatal(err)
}
channelAmt := int64(100000)
channelInfo, err := paymentCreator.PaychGet(ctx, createrAddr, receiverAddr, abi.NewTokenAmount(channelAmt))
if err != nil {
t.Fatal(err)
}
channel, err := paymentCreator.PaychGetWaitReady(ctx, channelInfo.ChannelMessage)
if err != nil {
t.Fatal(err)
}
// allocate three lanes
var lanes []uint64
for i := 0; i < 3; i++ {
lane, err := paymentCreator.PaychAllocateLane(ctx, channel)
if err != nil {
t.Fatal(err)
}
lanes = append(lanes, lane)
}
// Make two vouchers each for each lane, then save on the other side
// Note that the voucher with a value of 2000 has a higher nonce, so it
// supersedes the voucher with a value of 1000
for _, lane := range lanes {
vouch1, err := paymentCreator.PaychVoucherCreate(ctx, channel, abi.NewTokenAmount(1000), lane)
if err != nil {
t.Fatal(err)
}
vouch2, err := paymentCreator.PaychVoucherCreate(ctx, channel, abi.NewTokenAmount(2000), lane)
if err != nil {
t.Fatal(err)
}
delta1, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch1, nil, abi.NewTokenAmount(1000))
if err != nil {
t.Fatal(err)
}
if !delta1.Equals(abi.NewTokenAmount(1000)) {
t.Fatal("voucher didn't have the right amount")
}
delta2, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch2, nil, abi.NewTokenAmount(1000))
if err != nil {
t.Fatal(err)
}
if !delta2.Equals(abi.NewTokenAmount(1000)) {
t.Fatal("voucher didn't have the right amount")
}
}
// settle the payment channel
settleMsgCid, err := paymentCreator.PaychSettle(ctx, channel)
if err != nil {
t.Fatal(err)
}
res := waitForMessage(ctx, t, paymentCreator, settleMsgCid, time.Second*10, "settle")
if res.Receipt.ExitCode != 0 {
t.Fatal("Unable to settle payment channel")
}
// wait for the receiver to submit their vouchers
ev := events.NewEvents(ctx, paymentCreator)
preds := state.NewStatePredicates(paymentCreator)
finished := make(chan struct{})
err = ev.StateChanged(func(ts *types.TipSet) (done bool, more bool, err error) {
act, err := paymentCreator.StateReadState(ctx, channel, ts.Key())
if err != nil {
return false, false, err
}
state := act.State.(paych.State)
if state.ToSend.GreaterThanEqual(abi.NewTokenAmount(6000)) {
return true, false, nil
}
return false, true, nil
}, func(oldTs, newTs *types.TipSet, states events.StateChange, curH abi.ChainEpoch) (more bool, err error) {
toSendChange := states.(*state.PayChToSendChange)
if toSendChange.NewToSend.GreaterThanEqual(abi.NewTokenAmount(6000)) {
close(finished)
return false, nil
}
return true, nil
}, func(ctx context.Context, ts *types.TipSet) error {
return nil
}, int(build.MessageConfidence)+1, build.SealRandomnessLookbackLimit, func(oldTs, newTs *types.TipSet) (bool, events.StateChange, error) {
return preds.OnPaymentChannelActorChanged(channel, preds.OnToSendAmountChanges())(ctx, oldTs.Key(), newTs.Key())
})
select {
case <-finished:
case <-time.After(time.Second):
t.Fatal("Timed out waiting for receiver to submit vouchers")
}
// wait for the settlement period to pass before collecting
waitForBlocks(ctx, t, bm, paymentReceiver, receiverAddr, paych.SettleDelay)
creatorPreCollectBalance, err := paymentCreator.WalletBalance(ctx, createrAddr)
if err != nil {
t.Fatal(err)
}
// collect funds (from receiver, though either party can do it)
collectMsg, err := paymentReceiver.PaychCollect(ctx, channel)
if err != nil {
t.Fatal(err)
}
res, err = paymentReceiver.StateWaitMsg(ctx, collectMsg, 3)
if err != nil {
t.Fatal(err)
}
if res.Receipt.ExitCode != 0 {
t.Fatal("unable to collect on payment channel")
}
// Finally, check the balance for the creator
currentCreatorBalance, err := paymentCreator.WalletBalance(ctx, createrAddr)
if err != nil {
t.Fatal(err)
}
// The highest nonce voucher that the creator sent on each lane is 2000
totalVouchers := int64(len(lanes) * 2000)
// When receiver submits the tokens to the chain, creator should get a
// refund on the remaining balance, which is
// channel amount - total voucher value
expectedRefund := channelAmt - totalVouchers
delta := big.Sub(currentCreatorBalance, creatorPreCollectBalance)
if !delta.Equals(abi.NewTokenAmount(expectedRefund)) {
t.Fatalf("did not send correct funds from creator: expected %d, got %d", expectedRefund, delta)
}
// shut down mining
bm.stop()
}
func waitForBlocks(ctx context.Context, t *testing.T, bm *blockMiner, paymentReceiver TestNode, receiverAddr address.Address, count int) {
// We need to add null blocks in batches, if we add too many the chain can't sync
batchSize := 60
for i := 0; i < count; i += batchSize {
size := batchSize
if i > count {
size = count - i
}
// Add a batch of null blocks
atomic.StoreInt64(&bm.nulls, int64(size-1))
// Add a real block
m, err := paymentReceiver.MpoolPushMessage(ctx, &types.Message{
To: builtin.BurntFundsActorAddr,
From: receiverAddr,
Value: types.NewInt(0),
})
if err != nil {
t.Fatal(err)
}
_, err = paymentReceiver.StateWaitMsg(ctx, m.Cid(), 1)
if err != nil {
t.Fatal(err)
}
}
}
func waitForMessage(ctx context.Context, t *testing.T, paymentCreator TestNode, msgCid cid.Cid, duration time.Duration, desc string) *api.MsgLookup {
ctx, cancel := context.WithTimeout(ctx, duration)
defer cancel()
fmt.Println("Waiting for", desc)
res, err := paymentCreator.StateWaitMsg(ctx, msgCid, 1)
if err != nil {
fmt.Println("Error waiting for", desc, err)
t.Fatal(err)
}
if res.Receipt.ExitCode != 0 {
t.Fatalf("did not successfully send %s", desc)
}
fmt.Println("Confirmed", desc)
return res
}
type blockMiner struct {
ctx context.Context
t *testing.T
miner TestStorageNode
blocktime time.Duration
mine int64
nulls int64
done chan struct{}
}
func newBlockMiner(ctx context.Context, t *testing.T, miner TestStorageNode, blocktime time.Duration) *blockMiner {
return &blockMiner{
ctx: ctx,
t: t,
miner: miner,
blocktime: blocktime,
mine: int64(1),
done: make(chan struct{}),
}
}
func (bm *blockMiner) mineBlocks() {
time.Sleep(time.Second)
go func() {
defer close(bm.done)
for atomic.LoadInt64(&bm.mine) == 1 {
time.Sleep(bm.blocktime)
nulls := atomic.SwapInt64(&bm.nulls, 0)
if err := bm.miner.MineOne(bm.ctx, miner.MineReq{
InjectNulls: abi.ChainEpoch(nulls),
Done: func(bool, error) {},
}); err != nil {
bm.t.Error(err)
}
}
}()
}
func (bm *blockMiner) stop() {
atomic.AddInt64(&bm.mine, -1)
fmt.Println("shutting down mining")
<-bm.done
}
func sendFunds(ctx context.Context, t *testing.T, sender TestNode, addr address.Address, amount abi.TokenAmount) {
senderAddr, err := sender.WalletDefaultAddress(ctx)
if err != nil {
t.Fatal(err)
}
msg := &types.Message{
From: senderAddr,
To: addr,
Value: amount,
}
sm, err := sender.MpoolPushMessage(ctx, msg)
if err != nil {
t.Fatal(err)
}
res, err := sender.StateWaitMsg(ctx, sm.Cid(), 1)
if err != nil {
t.Fatal(err)
}
if res.Receipt.ExitCode != 0 {
t.Fatal("did not successfully send money")
}
}

View File

@ -9,6 +9,7 @@ import (
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/miner"
)
type TestNode struct {
@ -18,7 +19,7 @@ type TestNode struct {
type TestStorageNode struct {
api.StorageMiner
MineOne func(context.Context, func(bool, error)) error
MineOne func(context.Context, miner.MineReq) error
}
var PresealGenesis = -1

View File

@ -4,9 +4,6 @@ import (
"context"
"fmt"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"os"
"strings"
"testing"
@ -14,11 +11,16 @@ import (
"github.com/stretchr/testify/require"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/sector-storage/mock"
"github.com/filecoin-project/specs-actors/actors/abi"
miner2 "github.com/filecoin-project/specs-actors/actors/builtin/miner"
sealing "github.com/filecoin-project/storage-fsm"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/types"
bminer "github.com/filecoin-project/lotus/miner"
"github.com/filecoin-project/lotus/node/impl"
)
@ -42,30 +44,25 @@ func TestPledgeSector(t *testing.T, b APIBuilder, blocktime time.Duration, nSect
mine := true
done := make(chan struct{})
blockNotif := make(chan struct{}, 1)
go func() {
defer close(done)
for mine {
build.Clock.Sleep(blocktime)
if err := sn[0].MineOne(ctx, func(bool, error) {
select {
case blockNotif <- struct{}{}:
default:
}
if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, error) {
}); err != nil {
}}); err != nil {
t.Error(err)
}
}
}()
pledgeSectors(t, ctx, miner, nSectors, blockNotif)
pledgeSectors(t, ctx, miner, nSectors, 0, nil)
mine = false
<-done
}
func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n int, blockNotif <-chan struct{}) {
func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, existing int, blockNotif <-chan struct{}) {
for i := 0; i < n; i++ {
err := miner.PledgeSector(ctx)
require.NoError(t, err)
@ -78,7 +75,7 @@ func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n i
s, err := miner.SectorsList(ctx) // Note - the test builder doesn't import genesis sectors into FSM
require.NoError(t, err)
fmt.Printf("Sectors: %d\n", len(s))
if len(s) >= n {
if len(s) >= n+existing {
break
}
@ -97,7 +94,7 @@ func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n i
for len(toCheck) > 0 {
for n := range toCheck {
st, err := miner.SectorsStatus(ctx, n)
st, err := miner.SectorsStatus(ctx, n, false)
require.NoError(t, err)
if st.State == api.SectorState(sealing.Proving) {
delete(toCheck, n)
@ -136,13 +133,13 @@ func TestWindowPost(t *testing.T, b APIBuilder, blocktime time.Duration, nSector
defer close(done)
for mine {
build.Clock.Sleep(blocktime)
if err := sn[0].MineOne(ctx, func(bool, error) {}); err != nil {
if err := sn[0].MineOne(ctx, MineNext); err != nil {
t.Error(err)
}
}
}()
pledgeSectors(t, ctx, miner, nSectors, nil)
pledgeSectors(t, ctx, miner, nSectors, 0, nil)
maddr, err := miner.ActorAddress(ctx)
require.NoError(t, err)
@ -150,7 +147,10 @@ func TestWindowPost(t *testing.T, b APIBuilder, blocktime time.Duration, nSector
di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK)
require.NoError(t, err)
fmt.Printf("Running one proving periods\n")
mid, err := address.IDFromAddress(maddr)
require.NoError(t, err)
fmt.Printf("Running one proving period\n")
for {
head, err := client.ChainHead(ctx)
@ -175,7 +175,139 @@ func TestWindowPost(t *testing.T, b APIBuilder, blocktime time.Duration, nSector
require.Equal(t, p.MinerPower, p.TotalPower)
require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(nSectors+GenesisPreseals)))
// TODO: Inject faults here
fmt.Printf("Drop some sectors\n")
// Drop 2 sectors from deadline 2 partition 0 (full partition / deadline)
{
parts, err := client.StateMinerPartitions(ctx, maddr, 2, types.EmptyTSK)
require.NoError(t, err)
require.Greater(t, len(parts), 0)
n, err := parts[0].Sectors.Count()
require.NoError(t, err)
require.Equal(t, uint64(2), n)
// Drop the partition
err = parts[0].Sectors.ForEach(func(sid uint64) error {
return miner.StorageMiner.(*impl.StorageMinerAPI).IStorageMgr.(*mock.SectorMgr).MarkFailed(abi.SectorID{
Miner: abi.ActorID(mid),
Number: abi.SectorNumber(sid),
}, true)
})
require.NoError(t, err)
}
var s abi.SectorID
// Drop 1 sectors from deadline 3 partition 0
{
parts, err := client.StateMinerPartitions(ctx, maddr, 3, types.EmptyTSK)
require.NoError(t, err)
require.Greater(t, len(parts), 0)
n, err := parts[0].Sectors.Count()
require.NoError(t, err)
require.Equal(t, uint64(2), n)
// Drop the sector
sn, err := parts[0].Sectors.First()
require.NoError(t, err)
s = abi.SectorID{
Miner: abi.ActorID(mid),
Number: abi.SectorNumber(sn),
}
err = miner.StorageMiner.(*impl.StorageMinerAPI).IStorageMgr.(*mock.SectorMgr).MarkFailed(s, true)
require.NoError(t, err)
}
di, err = client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK)
require.NoError(t, err)
fmt.Printf("Go through another PP, wait for sectors to become faulty\n")
for {
head, err := client.ChainHead(ctx)
require.NoError(t, err)
if head.Height() > di.PeriodStart+(miner2.WPoStProvingPeriod)+2 {
break
}
if head.Height()%100 == 0 {
fmt.Printf("@%d\n", head.Height())
}
build.Clock.Sleep(blocktime)
}
p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK)
require.NoError(t, err)
require.Equal(t, p.MinerPower, p.TotalPower)
sectors := p.MinerPower.RawBytePower.Uint64() / uint64(ssz)
require.Equal(t, nSectors+GenesisPreseals-3, int(sectors)) // -3 just removed sectors
fmt.Printf("Recover one sector\n")
err = miner.StorageMiner.(*impl.StorageMinerAPI).IStorageMgr.(*mock.SectorMgr).MarkFailed(s, false)
require.NoError(t, err)
di, err = client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK)
require.NoError(t, err)
for {
head, err := client.ChainHead(ctx)
require.NoError(t, err)
if head.Height() > di.PeriodStart+(miner2.WPoStProvingPeriod)+2 {
break
}
if head.Height()%100 == 0 {
fmt.Printf("@%d\n", head.Height())
}
build.Clock.Sleep(blocktime)
}
p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK)
require.NoError(t, err)
require.Equal(t, p.MinerPower, p.TotalPower)
sectors = p.MinerPower.RawBytePower.Uint64() / uint64(ssz)
require.Equal(t, nSectors+GenesisPreseals-2, int(sectors)) // -2 not recovered sectors
// pledge a sector after recovery
pledgeSectors(t, ctx, miner, 1, nSectors, nil)
{
// wait a bit more
head, err := client.ChainHead(ctx)
require.NoError(t, err)
waitUntil := head.Height() + 10
for {
head, err := client.ChainHead(ctx)
require.NoError(t, err)
if head.Height() > waitUntil {
break
}
}
}
p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK)
require.NoError(t, err)
require.Equal(t, p.MinerPower, p.TotalPower)
sectors = p.MinerPower.RawBytePower.Uint64() / uint64(ssz)
require.Equal(t, nSectors+GenesisPreseals-2+1, int(sectors)) // -2 not recovered sectors + 1 just pledged
mine = false
<-done

View File

@ -7,6 +7,7 @@ import (
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
"github.com/libp2p/go-libp2p-core/peer"
pubsub "github.com/libp2p/go-libp2p-pubsub"
ma "github.com/multiformats/go-multiaddr"
)
@ -40,7 +41,7 @@ type ObjStat struct {
type PubsubScore struct {
ID peer.ID
Score float64
Score *pubsub.PeerScoreSnapshot
}
type MinerInfo struct {

View File

@ -38,12 +38,3 @@ func BuiltinBootstrap() ([]peer.AddrInfo, error) {
})
return out, err
}
func DrandBootstrap() ([]peer.AddrInfo, error) {
addrs := []string{
"/dnsaddr/pl-eu.testnet.drand.sh/",
"/dnsaddr/pl-us.testnet.drand.sh/",
"/dnsaddr/pl-sin.testnet.drand.sh/",
}
return addrutil.ParseAddresses(context.TODO(), addrs)
}

View File

@ -6,5 +6,5 @@ import "github.com/raulk/clock"
// we use a real-time clock, which maps to the `time` package.
//
// Tests that need control of time can replace this variable with
// clock.NewMock().
// clock.NewMock(). Always use real time for socket/stream deadlines.
var Clock = clock.New()

58
build/drand.go Normal file
View File

@ -0,0 +1,58 @@
package build
import "github.com/filecoin-project/lotus/node/modules/dtypes"
var DrandNetwork = DrandMainnet
func DrandConfig() dtypes.DrandConfig {
return DrandConfigs[DrandNetwork]
}
type DrandEnum int
const (
DrandMainnet DrandEnum = iota + 1
DrandTestnet
DrandDevnet
DrandLocalnet
)
var DrandConfigs = map[DrandEnum]dtypes.DrandConfig{
DrandMainnet: {
Servers: []string{
"https://api.drand.sh",
"https://api2.drand.sh",
"https://api3.drand.sh",
},
Relays: []string{
"/dnsaddr/api.drand.sh/",
"/dnsaddr/api2.drand.sh/",
"/dnsaddr/api3.drand.sh/",
},
ChainInfoJSON: `{"public_key":"868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31","period":30,"genesis_time":1595431050,"hash":"8990e7a9aaed2ffed73dbd7092123d6f289930540d7651336225dc172e51b2ce","groupHash":"176f93498eac9ca337150b46d21dd58673ea4e3581185f869672e59fa4cb390a"}`,
},
DrandTestnet: {
Servers: []string{
"https://pl-eu.testnet.drand.sh",
"https://pl-us.testnet.drand.sh",
"https://pl-sin.testnet.drand.sh",
},
Relays: []string{
"/dnsaddr/pl-eu.testnet.drand.sh/",
"/dnsaddr/pl-us.testnet.drand.sh/",
"/dnsaddr/pl-sin.testnet.drand.sh/",
},
ChainInfoJSON: `{"public_key":"922a2e93828ff83345bae533f5172669a26c02dc76d6bf59c80892e12ab1455c229211886f35bb56af6d5bea981024df","period":25,"genesis_time":1590445175,"hash":"84b2234fb34e835dccd048255d7ad3194b81af7d978c3bf157e3469592ae4e02","groupHash":"4dd408e5fdff9323c76a9b6f087ba8fdc5a6da907bd9217d9d10f2287d081957"}`,
},
DrandDevnet: {
Servers: []string{
"https://dev1.drand.sh",
"https://dev2.drand.sh",
},
Relays: []string{
"/dnsaddr/dev1.drand.sh/",
"/dnsaddr/dev2.drand.sh/",
},
ChainInfoJSON: `{"public_key":"8cda589f88914aa728fd183f383980b35789ce81b274e5daee1f338b77d02566ef4d3fb0098af1f844f10f9c803c1827","period":25,"genesis_time":1595348225,"hash":"e73b7dc3c4f6a236378220c0dd6aa110eb16eed26c11259606e07ee122838d4f","groupHash":"567d4785122a5a3e75a9bc9911d7ea807dd85ff76b78dc4ff06b075712898607"}`,
},
}

View File

@ -20,9 +20,9 @@ func init() {
BuildType |= Build2k
}
const BlockDelaySecs = uint64(2)
const BlockDelaySecs = uint64(4)
const PropagationDelaySecs = uint64(3)
const PropagationDelaySecs = uint64(1)
// SlashablePowerDelay is the number of epochs after ElectionPeriodStart, after
// which the miner is slashed

View File

@ -8,8 +8,6 @@ import (
"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"
"github.com/filecoin-project/lotus/node/modules/dtypes"
)
// /////
@ -61,8 +59,8 @@ const WinningPoStSectorSetLookback = abi.ChainEpoch(10)
// /////
// Devnet settings
const TotalFilecoin = uint64(2_000_000_000)
const MiningRewardTotal = uint64(1_900_000_000)
const FilBase = uint64(2_000_000_000)
const FilAllocStorageMining = uint64(1_100_000_000)
const FilecoinPrecision = uint64(1_000_000_000_000_000_000)
@ -71,7 +69,7 @@ var InitialRewardBalance *big.Int
// TODO: Move other important consts here
func init() {
InitialRewardBalance = big.NewInt(int64(MiningRewardTotal))
InitialRewardBalance = big.NewInt(int64(FilAllocStorageMining))
InitialRewardBalance = InitialRewardBalance.Mul(InitialRewardBalance, big.NewInt(int64(FilecoinPrecision)))
}
@ -90,14 +88,13 @@ const VerifSigCacheSize = 32000
// Limits
// TODO: If this is gonna stay, it should move to specs-actors
const BlockMessageLimit = 512
const BlockGasLimit = 7_500_000_000
const BlockMessageLimit = 10000
const BlockGasLimit = 10_000_000_000
const BlockGasTarget = BlockGasLimit / 2
const BaseFeeMaxChangeDenom = 8 // 12.5%
const InitialBaseFee = 100e6
const MinimumBaseFee = 100
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":"84b2234fb34e835dccd048255d7ad3194b81af7d978c3bf157e3469592ae4e02","groupHash":"4dd408e5fdff9323c76a9b6f087ba8fdc5a6da907bd9217d9d10f2287d081957"}`,
}
// Actor consts
// TODO: Pull from actors when its made not private
var MinDealDuration = abi.ChainEpoch(180 * builtin.EpochsInDay)

View File

@ -10,8 +10,6 @@ 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"
@ -21,11 +19,15 @@ 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)
BlocksPerEpoch = uint64(builtin.ExpectedLeadersPerEpoch)
BlockMessageLimit = 512
BlockGasLimit = int64(100_000_000_000)
BlockGasTarget = int64(BlockGasLimit / 2)
BaseFeeMaxChangeDenom = int64(8) // 12.5%
InitialBaseFee = int64(100e6)
MinimumBaseFee = int64(100)
BlockDelaySecs = uint64(builtin.EpochDurationSeconds)
PropagationDelaySecs = uint64(6)
AllowableClockDriftSecs = uint64(1)
@ -51,23 +53,17 @@ var (
TicketRandomnessLookback = abi.ChainEpoch(1)
WinningPoStSectorSetLookback = abi.ChainEpoch(10)
TotalFilecoin uint64 = 2_000_000_000
MiningRewardTotal uint64 = 1_400_000_000
FilBase uint64 = 2_000_000_000
FilAllocStorageMining uint64 = 1_400_000_000
FilecoinPrecision uint64 = 1_000_000_000_000_000_000
InitialRewardBalance = func() *big.Int {
v := big.NewInt(int64(MiningRewardTotal))
v := big.NewInt(int64(FilAllocStorageMining))
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"}`,
}
// Actor consts
// TODO: Pull from actors when its made not private
MinDealDuration = abi.ChainEpoch(180 * builtin.EpochsInDay)
)

View File

@ -25,7 +25,7 @@ func buildType() string {
}
// BuildVersion is the local build version, set by build system
const BuildVersion = "0.4.1"
const BuildVersion = "0.4.3"
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, 8, 1)
var APIVersion Version = newVer(0, 9, 0)
//nolint:varcheck,deadcode
const (

View File

@ -12,7 +12,7 @@ import (
)
func TestPrintGroupInfo(t *testing.T) {
server := build.DrandConfig.Servers[0]
server := build.DrandConfig().Servers[0]
c, err := hclient.New(server, nil, nil)
assert.NoError(t, err)
cg := c.(interface {

View File

@ -1,276 +0,0 @@
package blocksync
import (
"bufio"
"context"
"time"
"github.com/libp2p/go-libp2p-core/protocol"
"go.opencensus.io/trace"
"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"
"github.com/ipfs/go-cid"
logging "github.com/ipfs/go-log/v2"
inet "github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
)
var log = logging.Logger("blocksync")
type NewStreamFunc func(context.Context, peer.ID, ...protocol.ID) (inet.Stream, error)
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
}
type BlockSyncRequest struct {
Start []cid.Cid
RequestLength uint64
Options uint64
}
type BSOptions struct {
IncludeBlocks bool
IncludeMessages bool
}
func ParseBSOptions(optfield uint64) *BSOptions {
return &BSOptions{
IncludeBlocks: optfield&(BSOptBlocks) != 0,
IncludeMessages: optfield&(BSOptMessages) != 0,
}
}
const (
BSOptBlocks = 1 << iota
BSOptMessages
)
const (
StatusOK = uint64(0)
StatusPartial = uint64(101)
StatusNotFound = uint64(201)
StatusGoAway = uint64(202)
StatusInternalError = uint64(203)
StatusBadRequest = uint64(204)
)
type BlockSyncResponse struct {
Chain []*BSTipSet
Status uint64
Message string
}
type BSTipSet struct {
Blocks []*types.BlockHeader
BlsMessages []*types.Message
BlsMsgIncludes [][]uint64
SecpkMessages []*types.SignedMessage
SecpkMsgIncludes [][]uint64
}
func NewBlockSyncService(cs *store.ChainStore) *BlockSyncService {
return &BlockSyncService{
cs: cs,
}
}
func (bss *BlockSyncService) HandleStream(s inet.Stream) {
ctx, span := trace.StartSpan(context.Background(), "blocksync.HandleStream")
defer span.End()
defer s.Close() //nolint:errcheck
var req BlockSyncRequest
if err := cborutil.ReadCborRPC(bufio.NewReader(s), &req); err != nil {
log.Warnf("failed to read block sync request: %s", err)
return
}
log.Infow("block sync request", "start", req.Start, "len", req.RequestLength)
resp, err := bss.processRequest(ctx, s.Conn().RemotePeer(), &req)
if err != nil {
log.Warn("failed to process block sync request: ", err)
return
}
writeDeadline := 60 * time.Second
_ = s.SetDeadline(time.Now().Add(writeDeadline)) // always use real time for socket/stream deadlines.
if err := cborutil.WriteCborRPC(s, resp); err != nil {
log.Warnw("failed to write back response for handle stream", "err", err, "peer", s.Conn().RemotePeer())
return
}
}
func (bss *BlockSyncService) processRequest(ctx context.Context, p peer.ID, req *BlockSyncRequest) (*BlockSyncResponse, error) {
_, span := trace.StartSpan(ctx, "blocksync.ProcessRequest")
defer span.End()
opts := ParseBSOptions(req.Options)
if len(req.Start) == 0 {
return &BlockSyncResponse{
Status: StatusBadRequest,
Message: "no cids given in blocksync request",
}, nil
}
span.AddAttributes(
trace.BoolAttribute("blocks", opts.IncludeBlocks),
trace.BoolAttribute("messages", opts.IncludeMessages),
trace.Int64Attribute("reqlen", int64(req.RequestLength)),
)
reqlen := req.RequestLength
if reqlen > BlockSyncMaxRequestLength {
log.Warnw("limiting blocksync request length", "orig", req.RequestLength, "peer", p)
reqlen = BlockSyncMaxRequestLength
}
chain, err := collectChainSegment(bss.cs, types.NewTipSetKey(req.Start...), reqlen, opts)
if err != nil {
log.Warn("encountered error while responding to block sync request: ", err)
return &BlockSyncResponse{
Status: StatusInternalError,
Message: err.Error(),
}, nil
}
status := StatusOK
if reqlen < req.RequestLength {
status = StatusPartial
}
return &BlockSyncResponse{
Chain: chain,
Status: status,
}, nil
}
func collectChainSegment(cs *store.ChainStore, start types.TipSetKey, length uint64, opts *BSOptions) ([]*BSTipSet, error) {
var bstips []*BSTipSet
cur := start
for {
var bst BSTipSet
ts, err := cs.LoadTipSet(cur)
if err != nil {
return nil, xerrors.Errorf("failed loading tipset %s: %w", cur, err)
}
if opts.IncludeMessages {
bmsgs, bmincl, smsgs, smincl, err := gatherMessages(cs, ts)
if err != nil {
return nil, xerrors.Errorf("gather messages failed: %w", err)
}
bst.BlsMessages = bmsgs
bst.BlsMsgIncludes = bmincl
bst.SecpkMessages = smsgs
bst.SecpkMsgIncludes = smincl
}
if opts.IncludeBlocks {
bst.Blocks = ts.Blocks()
}
bstips = append(bstips, &bst)
if uint64(len(bstips)) >= length || ts.Height() == 0 {
return bstips, nil
}
cur = ts.Parents()
}
}
func gatherMessages(cs *store.ChainStore, ts *types.TipSet) ([]*types.Message, [][]uint64, []*types.SignedMessage, [][]uint64, error) {
blsmsgmap := make(map[cid.Cid]uint64)
secpkmsgmap := make(map[cid.Cid]uint64)
var secpkmsgs []*types.SignedMessage
var blsmsgs []*types.Message
var secpkincl, blsincl [][]uint64
for _, b := range ts.Blocks() {
bmsgs, smsgs, err := cs.MessagesForBlock(b)
if err != nil {
return nil, nil, nil, nil, err
}
bmi := make([]uint64, 0, len(bmsgs))
for _, m := range bmsgs {
i, ok := blsmsgmap[m.Cid()]
if !ok {
i = uint64(len(blsmsgs))
blsmsgs = append(blsmsgs, m)
blsmsgmap[m.Cid()] = i
}
bmi = append(bmi, i)
}
blsincl = append(blsincl, bmi)
smi := make([]uint64, 0, len(smsgs))
for _, m := range smsgs {
i, ok := secpkmsgmap[m.Cid()]
if !ok {
i = uint64(len(secpkmsgs))
secpkmsgs = append(secpkmsgs, m)
secpkmsgmap[m.Cid()] = i
}
smi = append(smi, i)
}
secpkincl = append(secpkincl, smi)
}
return blsmsgs, blsincl, secpkmsgs, secpkincl, nil
}
func bstsToFullTipSet(bts *BSTipSet) (*store.FullTipSet, error) {
fts := &store.FullTipSet{}
for i, b := range bts.Blocks {
fb := &types.FullBlock{
Header: b,
}
for _, mi := range bts.BlsMsgIncludes[i] {
fb.BlsMessages = append(fb.BlsMessages, bts.BlsMessages[mi])
}
for _, mi := range bts.SecpkMsgIncludes[i] {
fb.SecpkMessages = append(fb.SecpkMessages, bts.SecpkMessages[mi])
}
fts.Blocks = append(fts.Blocks, fb)
}
return fts, nil
}

View File

@ -1,603 +0,0 @@
package blocksync
import (
"bufio"
"context"
"fmt"
"math/rand"
"sort"
"sync"
"time"
blocks "github.com/ipfs/go-block-format"
bserv "github.com/ipfs/go-blockservice"
"github.com/ipfs/go-cid"
graphsync "github.com/ipfs/go-graphsync"
gsnet "github.com/ipfs/go-graphsync/network"
host "github.com/libp2p/go-libp2p-core/host"
inet "github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
"go.opencensus.io/trace"
"golang.org/x/xerrors"
cborutil "github.com/filecoin-project/go-cbor-util"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
incrt "github.com/filecoin-project/lotus/lib/increadtimeout"
"github.com/filecoin-project/lotus/lib/peermgr"
"github.com/filecoin-project/lotus/node/modules/dtypes"
)
type BlockSync struct {
bserv bserv.BlockService
gsync graphsync.GraphExchange
host host.Host
syncPeers *bsPeerTracker
peerMgr *peermgr.PeerMgr
}
func NewBlockSyncClient(bserv dtypes.ChainBlockService, h host.Host, pmgr peermgr.MaybePeerMgr, gs dtypes.Graphsync) *BlockSync {
return &BlockSync{
bserv: bserv,
host: h,
syncPeers: newPeerTracker(pmgr.Mgr),
peerMgr: pmgr.Mgr,
gsync: gs,
}
}
func (bs *BlockSync) processStatus(req *BlockSyncRequest, res *BlockSyncResponse) error {
switch res.Status {
case StatusPartial: // Partial Response
return xerrors.Errorf("not handling partial blocksync responses yet")
case StatusNotFound: // req.Start not found
return xerrors.Errorf("not found")
case StatusGoAway: // Go Away
return xerrors.Errorf("not handling 'go away' blocksync responses yet")
case StatusInternalError: // Internal Error
return xerrors.Errorf("block sync peer errored: %s", res.Message)
case StatusBadRequest:
return xerrors.Errorf("block sync request invalid: %s", res.Message)
default:
return xerrors.Errorf("unrecognized response code: %d", res.Status)
}
}
// 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()
if span.IsRecordingEvents() {
span.AddAttributes(
trace.StringAttribute("tipset", fmt.Sprint(tsk.Cids())),
trace.Int64Attribute("count", int64(count)),
)
}
req := &BlockSyncRequest{
Start: tsk.Cids(),
RequestLength: uint64(count),
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)
start := build.Clock.Now()
var oerr error
for _, p := range peers {
// TODO: doing this synchronously isnt great, but fetching in parallel
// may not be a good idea either. think about this more
select {
case <-ctx.Done():
return nil, xerrors.Errorf("blocksync getblocks failed: %w", ctx.Err())
default:
}
res, err := bs.sendRequestToPeer(ctx, p, req)
if err != nil {
oerr = err
if !xerrors.Is(err, inet.ErrNoConn) {
log.Warnf("BlockSync request failed for peer %s: %s", p.String(), err)
}
continue
}
if res.Status == StatusOK || res.Status == StatusPartial {
resp, err := bs.processBlocksResponse(req, res)
if err != nil {
return nil, xerrors.Errorf("success response from peer failed to process: %w", err)
}
bs.syncPeers.logGlobalSuccess(build.Clock.Since(start))
bs.host.ConnManager().TagPeer(p, "bsync", 25)
return resp, nil
}
oerr = bs.processStatus(req, res)
if oerr != nil {
log.Warnf("BlockSync peer %s response was an error: %s", p.String(), oerr)
}
}
return nil, xerrors.Errorf("GetBlocks failed with all peers: %w", oerr)
}
func (bs *BlockSync) GetFullTipSet(ctx context.Context, p peer.ID, tsk types.TipSetKey) (*store.FullTipSet, error) {
// TODO: round robin through these peers on error
req := &BlockSyncRequest{
Start: tsk.Cids(),
RequestLength: 1,
Options: BSOptBlocks | BSOptMessages,
}
res, err := bs.sendRequestToPeer(ctx, p, req)
if err != nil {
return nil, err
}
switch res.Status {
case 0: // Success
if len(res.Chain) == 0 {
return nil, fmt.Errorf("got zero length chain response")
}
bts := res.Chain[0]
return bstsToFullTipSet(bts)
case 101: // Partial Response
return nil, xerrors.Errorf("partial responses are not handled for single tipset fetching")
case 201: // req.Start not found
return nil, fmt.Errorf("not found")
case 202: // Go Away
return nil, xerrors.Errorf("received 'go away' response peer")
case 203: // Internal Error
return nil, fmt.Errorf("block sync peer errored: %q", res.Message)
case 204: // Invalid Request
return nil, fmt.Errorf("block sync request invalid: %q", res.Message)
default:
return nil, fmt.Errorf("unrecognized response code")
}
}
func shufflePrefix(peers []peer.ID) {
pref := 5
if len(peers) < pref {
pref = len(peers)
}
buf := make([]peer.ID, pref)
perm := rand.Perm(pref)
for i, v := range perm {
buf[i] = peers[v]
}
copy(peers, buf)
}
func (bs *BlockSync) GetChainMessages(ctx context.Context, h *types.TipSet, count uint64) ([]*BSTipSet, error) {
ctx, span := trace.StartSpan(ctx, "GetChainMessages")
defer span.End()
peers := bs.getPeers()
// randomize the first few peers so we don't always pick the same peer
shufflePrefix(peers)
req := &BlockSyncRequest{
Start: h.Cids(),
RequestLength: count,
Options: BSOptMessages,
}
var err error
start := build.Clock.Now()
for _, p := range peers {
res, rerr := bs.sendRequestToPeer(ctx, p, req)
if rerr != nil {
err = rerr
log.Warnf("BlockSync request failed for peer %s: %s", p.String(), err)
continue
}
if res.Status == StatusOK {
bs.syncPeers.logGlobalSuccess(build.Clock.Since(start))
return res.Chain, nil
}
if res.Status == StatusPartial {
// TODO: track partial response sizes to ensure we don't overrequest too often
return res.Chain, nil
}
err = bs.processStatus(req, res)
if err != nil {
log.Warnf("BlockSync peer %s response was an error: %s", p.String(), err)
}
}
if err == nil {
return nil, xerrors.Errorf("GetChainMessages failed, no peers connected")
}
// TODO: What if we have no peers (and err is nil)?
return nil, xerrors.Errorf("GetChainMessages failed with all peers(%d): %w", len(peers), err)
}
func (bs *BlockSync) sendRequestToPeer(ctx context.Context, p peer.ID, req *BlockSyncRequest) (_ *BlockSyncResponse, err error) {
ctx, span := trace.StartSpan(ctx, "sendRequestToPeer")
defer span.End()
defer func() {
if err != nil {
if span.IsRecordingEvents() {
span.SetStatus(trace.Status{
Code: 5,
Message: err.Error(),
})
}
}
}()
if span.IsRecordingEvents() {
span.AddAttributes(
trace.StringAttribute("peer", p.Pretty()),
)
}
gsproto := string(gsnet.ProtocolGraphsync)
supp, err := bs.host.Peerstore().SupportsProtocols(p, BlockSyncProtocolID, gsproto)
if err != nil {
return nil, xerrors.Errorf("failed to get protocols for peer: %w", err)
}
if len(supp) == 0 {
return nil, xerrors.Errorf("peer %s supports no known sync protocols", p)
}
switch supp[0] {
case BlockSyncProtocolID:
res, err := bs.fetchBlocksBlockSync(ctx, p, req)
if err != nil {
return nil, xerrors.Errorf("blocksync req failed: %w", err)
}
return res, nil
case gsproto:
res, err := bs.fetchBlocksGraphSync(ctx, p, req)
if err != nil {
return nil, xerrors.Errorf("graphsync req failed: %w", err)
}
return res, nil
default:
return nil, xerrors.Errorf("peerstore somehow returned unexpected protocols: %v", supp)
}
}
func (bs *BlockSync) fetchBlocksBlockSync(ctx context.Context, p peer.ID, req *BlockSyncRequest) (*BlockSyncResponse, error) {
ctx, span := trace.StartSpan(ctx, "blockSyncFetch")
defer span.End()
start := build.Clock.Now()
s, err := bs.host.NewStream(inet.WithNoDial(ctx, "should already have connection"), p, BlockSyncProtocolID)
if err != nil {
bs.RemovePeer(p)
return nil, xerrors.Errorf("failed to open stream to peer: %w", err)
}
_ = s.SetWriteDeadline(time.Now().Add(5 * time.Second)) // always use real time for socket/stream deadlines.
if err := cborutil.WriteCborRPC(s, req); err != nil {
_ = s.SetWriteDeadline(time.Time{})
bs.syncPeers.logFailure(p, build.Clock.Since(start))
return nil, err
}
_ = s.SetWriteDeadline(time.Time{})
var res BlockSyncResponse
r := incrt.New(s, 50<<10, 5*time.Second)
if err := cborutil.ReadCborRPC(bufio.NewReader(r), &res); err != nil {
bs.syncPeers.logFailure(p, build.Clock.Since(start))
return nil, err
}
if span.IsRecordingEvents() {
span.AddAttributes(
trace.Int64Attribute("resp_status", int64(res.Status)),
trace.StringAttribute("msg", res.Message),
trace.Int64Attribute("chain_len", int64(len(res.Chain))),
)
}
bs.syncPeers.logSuccess(p, build.Clock.Since(start))
return &res, nil
}
func (bs *BlockSync) processBlocksResponse(req *BlockSyncRequest, res *BlockSyncResponse) ([]*types.TipSet, error) {
if len(res.Chain) == 0 {
return nil, xerrors.Errorf("got no blocks in successful blocksync response")
}
cur, err := types.NewTipSet(res.Chain[0].Blocks)
if err != nil {
return nil, err
}
out := []*types.TipSet{cur}
for bi := 1; bi < len(res.Chain); bi++ {
next := res.Chain[bi].Blocks
nts, err := types.NewTipSet(next)
if err != nil {
return nil, err
}
if !types.CidArrsEqual(cur.Parents().Cids(), nts.Cids()) {
return nil, fmt.Errorf("parents of tipset[%d] were not tipset[%d]", bi-1, bi)
}
out = append(out, nts)
cur = nts
}
return out, nil
}
func (bs *BlockSync) GetBlock(ctx context.Context, c cid.Cid) (*types.BlockHeader, error) {
sb, err := bs.bserv.GetBlock(ctx, c)
if err != nil {
return nil, err
}
return types.DecodeBlock(sb.RawData())
}
func (bs *BlockSync) AddPeer(p peer.ID) {
bs.syncPeers.addPeer(p)
}
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()
}
func (bs *BlockSync) FetchMessagesByCids(ctx context.Context, cids []cid.Cid) ([]*types.Message, error) {
out := make([]*types.Message, len(cids))
err := bs.fetchCids(ctx, cids, func(i int, b blocks.Block) error {
msg, err := types.DecodeMessage(b.RawData())
if err != nil {
return err
}
if out[i] != nil {
return fmt.Errorf("received duplicate message")
}
out[i] = msg
return nil
})
if err != nil {
return nil, err
}
return out, nil
}
func (bs *BlockSync) FetchSignedMessagesByCids(ctx context.Context, cids []cid.Cid) ([]*types.SignedMessage, error) {
out := make([]*types.SignedMessage, len(cids))
err := bs.fetchCids(ctx, cids, func(i int, b blocks.Block) error {
smsg, err := types.DecodeSignedMessage(b.RawData())
if err != nil {
return err
}
if out[i] != nil {
return fmt.Errorf("received duplicate message")
}
out[i] = smsg
return nil
})
if err != nil {
return nil, err
}
return out, nil
}
func (bs *BlockSync) fetchCids(ctx context.Context, cids []cid.Cid, cb func(int, blocks.Block) error) error {
resp := bs.bserv.GetBlocks(context.TODO(), cids)
m := make(map[cid.Cid]int)
for i, c := range cids {
m[c] = i
}
for i := 0; i < len(cids); i++ {
select {
case v, ok := <-resp:
if !ok {
if i == len(cids)-1 {
break
}
return fmt.Errorf("failed to fetch all messages")
}
ix, ok := m[v.Cid()]
if !ok {
return fmt.Errorf("received message we didnt ask for")
}
if err := cb(ix, v); err != nil {
return err
}
}
}
return nil
}
type peerStats struct {
successes int
failures int
firstSeen time.Time
averageTime time.Duration
}
type bsPeerTracker struct {
lk sync.Mutex
peers map[peer.ID]*peerStats
avgGlobalTime time.Duration
pmgr *peermgr.PeerMgr
}
func newPeerTracker(pmgr *peermgr.PeerMgr) *bsPeerTracker {
return &bsPeerTracker{
peers: make(map[peer.ID]*peerStats),
pmgr: pmgr,
}
}
func (bpt *bsPeerTracker) addPeer(p peer.ID) {
bpt.lk.Lock()
defer bpt.lk.Unlock()
if _, ok := bpt.peers[p]; ok {
return
}
bpt.peers[p] = &peerStats{
firstSeen: build.Clock.Now(),
}
}
const (
// newPeerMul is how much better than average is the new peer assumed to be
// less than one to encourouge trying new peers
newPeerMul = 0.9
)
func (bpt *bsPeerTracker) prefSortedPeers() []peer.ID {
// TODO: this could probably be cached, but as long as its not too many peers, fine for now
bpt.lk.Lock()
defer bpt.lk.Unlock()
out := make([]peer.ID, 0, len(bpt.peers))
for p := range bpt.peers {
out = append(out, p)
}
// sort by 'expected cost' of requesting data from that peer
// additionally handle edge cases where not enough data is available
sort.Slice(out, func(i, j int) bool {
pi := bpt.peers[out[i]]
pj := bpt.peers[out[j]]
var costI, costJ float64
getPeerInitLat := func(p peer.ID) float64 {
var res float64
if bpt.pmgr != nil {
if lat, ok := bpt.pmgr.GetPeerLatency(p); ok {
res = float64(lat)
}
}
if res == 0 {
res = float64(bpt.avgGlobalTime)
}
return res * newPeerMul
}
if pi.successes+pi.failures > 0 {
failRateI := float64(pi.failures) / float64(pi.failures+pi.successes)
costI = float64(pi.averageTime) + failRateI*float64(bpt.avgGlobalTime)
} else {
costI = getPeerInitLat(out[i])
}
if pj.successes+pj.failures > 0 {
failRateJ := float64(pj.failures) / float64(pj.failures+pj.successes)
costJ = float64(pj.averageTime) + failRateJ*float64(bpt.avgGlobalTime)
} else {
costJ = getPeerInitLat(out[j])
}
return costI < costJ
})
return out
}
const (
// xInvAlpha = (N+1)/2
localInvAlpha = 5 // 86% of the value is the last 9
globalInvAlpha = 20 // 86% of the value is the last 39
)
func (bpt *bsPeerTracker) logGlobalSuccess(dur time.Duration) {
bpt.lk.Lock()
defer bpt.lk.Unlock()
if bpt.avgGlobalTime == 0 {
bpt.avgGlobalTime = dur
return
}
delta := (dur - bpt.avgGlobalTime) / globalInvAlpha
bpt.avgGlobalTime += delta
}
func logTime(pi *peerStats, dur time.Duration) {
if pi.averageTime == 0 {
pi.averageTime = dur
return
}
delta := (dur - pi.averageTime) / localInvAlpha
pi.averageTime += delta
}
func (bpt *bsPeerTracker) logSuccess(p peer.ID, dur time.Duration) {
bpt.lk.Lock()
defer bpt.lk.Unlock()
var pi *peerStats
var ok bool
if pi, ok = bpt.peers[p]; !ok {
log.Warnw("log success called on peer not in tracker", "peerid", p.String())
return
}
pi.successes++
logTime(pi, dur)
}
func (bpt *bsPeerTracker) logFailure(p peer.ID, dur time.Duration) {
bpt.lk.Lock()
defer bpt.lk.Unlock()
var pi *peerStats
var ok bool
if pi, ok = bpt.peers[p]; !ok {
log.Warn("log failure called on peer not in tracker", "peerid", p.String())
return
}
pi.failures++
logTime(pi, dur)
}
func (bpt *bsPeerTracker) removePeer(p peer.ID) {
bpt.lk.Lock()
defer bpt.lk.Unlock()
delete(bpt.peers, p)
}

View File

@ -14,36 +14,36 @@ import (
var _ = xerrors.Errorf
var lengthBufBlockSyncRequest = []byte{131}
var lengthBufRequest = []byte{131}
func (t *BlockSyncRequest) MarshalCBOR(w io.Writer) error {
func (t *Request) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write(lengthBufBlockSyncRequest); err != nil {
if _, err := w.Write(lengthBufRequest); err != nil {
return err
}
scratch := make([]byte, 9)
// t.Start ([]cid.Cid) (slice)
if len(t.Start) > cbg.MaxLength {
return xerrors.Errorf("Slice value in field t.Start was too long")
// t.Head ([]cid.Cid) (slice)
if len(t.Head) > cbg.MaxLength {
return xerrors.Errorf("Slice value in field t.Head was too long")
}
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Start))); err != nil {
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Head))); err != nil {
return err
}
for _, v := range t.Start {
for _, v := range t.Head {
if err := cbg.WriteCidBuf(scratch, w, v); err != nil {
return xerrors.Errorf("failed writing cid field t.Start: %w", err)
return xerrors.Errorf("failed writing cid field t.Head: %w", err)
}
}
// t.RequestLength (uint64) (uint64)
// t.Length (uint64) (uint64)
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.RequestLength)); err != nil {
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Length)); err != nil {
return err
}
@ -56,8 +56,8 @@ func (t *BlockSyncRequest) MarshalCBOR(w io.Writer) error {
return nil
}
func (t *BlockSyncRequest) UnmarshalCBOR(r io.Reader) error {
*t = BlockSyncRequest{}
func (t *Request) UnmarshalCBOR(r io.Reader) error {
*t = Request{}
br := cbg.GetPeeker(r)
scratch := make([]byte, 8)
@ -74,7 +74,7 @@ func (t *BlockSyncRequest) UnmarshalCBOR(r io.Reader) error {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.Start ([]cid.Cid) (slice)
// t.Head ([]cid.Cid) (slice)
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
@ -82,7 +82,7 @@ func (t *BlockSyncRequest) UnmarshalCBOR(r io.Reader) error {
}
if extra > cbg.MaxLength {
return fmt.Errorf("t.Start: array too large (%d)", extra)
return fmt.Errorf("t.Head: array too large (%d)", extra)
}
if maj != cbg.MajArray {
@ -90,19 +90,19 @@ func (t *BlockSyncRequest) UnmarshalCBOR(r io.Reader) error {
}
if extra > 0 {
t.Start = make([]cid.Cid, extra)
t.Head = make([]cid.Cid, extra)
}
for i := 0; i < int(extra); i++ {
c, err := cbg.ReadCid(br)
if err != nil {
return xerrors.Errorf("reading cid field t.Start failed: %w", err)
return xerrors.Errorf("reading cid field t.Head failed: %w", err)
}
t.Start[i] = c
t.Head[i] = c
}
// t.RequestLength (uint64) (uint64)
// t.Length (uint64) (uint64)
{
@ -113,7 +113,7 @@ func (t *BlockSyncRequest) UnmarshalCBOR(r io.Reader) error {
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.RequestLength = uint64(extra)
t.Length = uint64(extra)
}
// t.Options (uint64) (uint64)
@ -133,19 +133,37 @@ func (t *BlockSyncRequest) UnmarshalCBOR(r io.Reader) error {
return nil
}
var lengthBufBlockSyncResponse = []byte{131}
var lengthBufResponse = []byte{131}
func (t *BlockSyncResponse) MarshalCBOR(w io.Writer) error {
func (t *Response) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write(lengthBufBlockSyncResponse); err != nil {
if _, err := w.Write(lengthBufResponse); err != nil {
return err
}
scratch := make([]byte, 9)
// t.Status (blocksync.status) (uint64)
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Status)); err != nil {
return err
}
// t.ErrorMessage (string) (string)
if len(t.ErrorMessage) > cbg.MaxLength {
return xerrors.Errorf("Value in field t.ErrorMessage was too long")
}
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.ErrorMessage))); err != nil {
return err
}
if _, err := io.WriteString(w, string(t.ErrorMessage)); err != nil {
return err
}
// t.Chain ([]*blocksync.BSTipSet) (slice)
if len(t.Chain) > cbg.MaxLength {
return xerrors.Errorf("Slice value in field t.Chain was too long")
@ -159,29 +177,11 @@ func (t *BlockSyncResponse) MarshalCBOR(w io.Writer) error {
return err
}
}
// t.Status (uint64) (uint64)
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Status)); err != nil {
return err
}
// t.Message (string) (string)
if len(t.Message) > cbg.MaxLength {
return xerrors.Errorf("Value in field t.Message was too long")
}
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.Message))); err != nil {
return err
}
if _, err := io.WriteString(w, t.Message); err != nil {
return err
}
return nil
}
func (t *BlockSyncResponse) UnmarshalCBOR(r io.Reader) error {
*t = BlockSyncResponse{}
func (t *Response) UnmarshalCBOR(r io.Reader) error {
*t = Response{}
br := cbg.GetPeeker(r)
scratch := make([]byte, 8)
@ -198,6 +198,30 @@ func (t *BlockSyncResponse) UnmarshalCBOR(r io.Reader) error {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.Status (blocksync.status) (uint64)
{
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return err
}
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.Status = status(extra)
}
// t.ErrorMessage (string) (string)
{
sval, err := cbg.ReadStringBuf(br, scratch)
if err != nil {
return err
}
t.ErrorMessage = string(sval)
}
// t.Chain ([]*blocksync.BSTipSet) (slice)
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
@ -227,34 +251,296 @@ func (t *BlockSyncResponse) UnmarshalCBOR(r io.Reader) error {
t.Chain[i] = &v
}
// t.Status (uint64) (uint64)
return nil
}
{
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return err
}
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.Status = uint64(extra)
var lengthBufCompactedMessages = []byte{132}
func (t *CompactedMessages) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
if _, err := w.Write(lengthBufCompactedMessages); err != nil {
return err
}
// t.Message (string) (string)
{
sval, err := cbg.ReadStringBuf(br, scratch)
if err != nil {
scratch := make([]byte, 9)
// t.Bls ([]*types.Message) (slice)
if len(t.Bls) > cbg.MaxLength {
return xerrors.Errorf("Slice value in field t.Bls was too long")
}
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Bls))); err != nil {
return err
}
for _, v := range t.Bls {
if err := v.MarshalCBOR(w); err != nil {
return err
}
}
t.Message = string(sval)
// t.BlsIncludes ([][]uint64) (slice)
if len(t.BlsIncludes) > cbg.MaxLength {
return xerrors.Errorf("Slice value in field t.BlsIncludes was too long")
}
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.BlsIncludes))); err != nil {
return err
}
for _, v := range t.BlsIncludes {
if len(v) > cbg.MaxLength {
return xerrors.Errorf("Slice value in field v was too long")
}
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(v))); err != nil {
return err
}
for _, v := range v {
if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil {
return err
}
}
}
// t.Secpk ([]*types.SignedMessage) (slice)
if len(t.Secpk) > cbg.MaxLength {
return xerrors.Errorf("Slice value in field t.Secpk was too long")
}
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Secpk))); err != nil {
return err
}
for _, v := range t.Secpk {
if err := v.MarshalCBOR(w); err != nil {
return err
}
}
// t.SecpkIncludes ([][]uint64) (slice)
if len(t.SecpkIncludes) > cbg.MaxLength {
return xerrors.Errorf("Slice value in field t.SecpkIncludes was too long")
}
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.SecpkIncludes))); err != nil {
return err
}
for _, v := range t.SecpkIncludes {
if len(v) > cbg.MaxLength {
return xerrors.Errorf("Slice value in field v was too long")
}
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(v))); err != nil {
return err
}
for _, v := range v {
if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil {
return err
}
}
}
return nil
}
var lengthBufBSTipSet = []byte{133}
func (t *CompactedMessages) UnmarshalCBOR(r io.Reader) error {
*t = CompactedMessages{}
br := cbg.GetPeeker(r)
scratch := make([]byte, 8)
maj, extra, err := cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return err
}
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 4 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.Bls ([]*types.Message) (slice)
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return err
}
if extra > cbg.MaxLength {
return fmt.Errorf("t.Bls: array too large (%d)", extra)
}
if maj != cbg.MajArray {
return fmt.Errorf("expected cbor array")
}
if extra > 0 {
t.Bls = make([]*types.Message, extra)
}
for i := 0; i < int(extra); i++ {
var v types.Message
if err := v.UnmarshalCBOR(br); err != nil {
return err
}
t.Bls[i] = &v
}
// t.BlsIncludes ([][]uint64) (slice)
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return err
}
if extra > cbg.MaxLength {
return fmt.Errorf("t.BlsIncludes: array too large (%d)", extra)
}
if maj != cbg.MajArray {
return fmt.Errorf("expected cbor array")
}
if extra > 0 {
t.BlsIncludes = make([][]uint64, extra)
}
for i := 0; i < int(extra); i++ {
{
var maj byte
var extra uint64
var err error
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return err
}
if extra > cbg.MaxLength {
return fmt.Errorf("t.BlsIncludes[i]: array too large (%d)", extra)
}
if maj != cbg.MajArray {
return fmt.Errorf("expected cbor array")
}
if extra > 0 {
t.BlsIncludes[i] = make([]uint64, extra)
}
for j := 0; j < int(extra); j++ {
maj, val, err := cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return xerrors.Errorf("failed to read uint64 for t.BlsIncludes[i] slice: %w", err)
}
if maj != cbg.MajUnsignedInt {
return xerrors.Errorf("value read for array t.BlsIncludes[i] was not a uint, instead got %d", maj)
}
t.BlsIncludes[i][j] = uint64(val)
}
}
}
// t.Secpk ([]*types.SignedMessage) (slice)
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return err
}
if extra > cbg.MaxLength {
return fmt.Errorf("t.Secpk: array too large (%d)", extra)
}
if maj != cbg.MajArray {
return fmt.Errorf("expected cbor array")
}
if extra > 0 {
t.Secpk = make([]*types.SignedMessage, extra)
}
for i := 0; i < int(extra); i++ {
var v types.SignedMessage
if err := v.UnmarshalCBOR(br); err != nil {
return err
}
t.Secpk[i] = &v
}
// t.SecpkIncludes ([][]uint64) (slice)
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return err
}
if extra > cbg.MaxLength {
return fmt.Errorf("t.SecpkIncludes: array too large (%d)", extra)
}
if maj != cbg.MajArray {
return fmt.Errorf("expected cbor array")
}
if extra > 0 {
t.SecpkIncludes = make([][]uint64, extra)
}
for i := 0; i < int(extra); i++ {
{
var maj byte
var extra uint64
var err error
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return err
}
if extra > cbg.MaxLength {
return fmt.Errorf("t.SecpkIncludes[i]: array too large (%d)", extra)
}
if maj != cbg.MajArray {
return fmt.Errorf("expected cbor array")
}
if extra > 0 {
t.SecpkIncludes[i] = make([]uint64, extra)
}
for j := 0; j < int(extra); j++ {
maj, val, err := cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return xerrors.Errorf("failed to read uint64 for t.SecpkIncludes[i] slice: %w", err)
}
if maj != cbg.MajUnsignedInt {
return xerrors.Errorf("value read for array t.SecpkIncludes[i] was not a uint, instead got %d", maj)
}
t.SecpkIncludes[i][j] = uint64(val)
}
}
}
return nil
}
var lengthBufBSTipSet = []byte{130}
func (t *BSTipSet) MarshalCBOR(w io.Writer) error {
if t == nil {
@ -281,79 +567,10 @@ func (t *BSTipSet) MarshalCBOR(w io.Writer) error {
}
}
// t.BlsMessages ([]*types.Message) (slice)
if len(t.BlsMessages) > cbg.MaxLength {
return xerrors.Errorf("Slice value in field t.BlsMessages was too long")
}
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.BlsMessages))); err != nil {
// t.Messages (blocksync.CompactedMessages) (struct)
if err := t.Messages.MarshalCBOR(w); err != nil {
return err
}
for _, v := range t.BlsMessages {
if err := v.MarshalCBOR(w); err != nil {
return err
}
}
// t.BlsMsgIncludes ([][]uint64) (slice)
if len(t.BlsMsgIncludes) > cbg.MaxLength {
return xerrors.Errorf("Slice value in field t.BlsMsgIncludes was too long")
}
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.BlsMsgIncludes))); err != nil {
return err
}
for _, v := range t.BlsMsgIncludes {
if len(v) > cbg.MaxLength {
return xerrors.Errorf("Slice value in field v was too long")
}
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(v))); err != nil {
return err
}
for _, v := range v {
if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil {
return err
}
}
}
// t.SecpkMessages ([]*types.SignedMessage) (slice)
if len(t.SecpkMessages) > cbg.MaxLength {
return xerrors.Errorf("Slice value in field t.SecpkMessages was too long")
}
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.SecpkMessages))); err != nil {
return err
}
for _, v := range t.SecpkMessages {
if err := v.MarshalCBOR(w); err != nil {
return err
}
}
// t.SecpkMsgIncludes ([][]uint64) (slice)
if len(t.SecpkMsgIncludes) > cbg.MaxLength {
return xerrors.Errorf("Slice value in field t.SecpkMsgIncludes was too long")
}
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.SecpkMsgIncludes))); err != nil {
return err
}
for _, v := range t.SecpkMsgIncludes {
if len(v) > cbg.MaxLength {
return xerrors.Errorf("Slice value in field v was too long")
}
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(v))); err != nil {
return err
}
for _, v := range v {
if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil {
return err
}
}
}
return nil
}
@ -371,7 +588,7 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 5 {
if extra != 2 {
return fmt.Errorf("cbor input had wrong number of fields")
}
@ -404,181 +621,26 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
t.Blocks[i] = &v
}
// t.BlsMessages ([]*types.Message) (slice)
// t.Messages (blocksync.CompactedMessages) (struct)
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return err
}
{
if extra > cbg.MaxLength {
return fmt.Errorf("t.BlsMessages: array too large (%d)", extra)
}
if maj != cbg.MajArray {
return fmt.Errorf("expected cbor array")
}
if extra > 0 {
t.BlsMessages = make([]*types.Message, extra)
}
for i := 0; i < int(extra); i++ {
var v types.Message
if err := v.UnmarshalCBOR(br); err != nil {
pb, err := br.PeekByte()
if err != nil {
return err
}
t.BlsMessages[i] = &v
}
// t.BlsMsgIncludes ([][]uint64) (slice)
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return err
}
if extra > cbg.MaxLength {
return fmt.Errorf("t.BlsMsgIncludes: array too large (%d)", extra)
}
if maj != cbg.MajArray {
return fmt.Errorf("expected cbor array")
}
if extra > 0 {
t.BlsMsgIncludes = make([][]uint64, extra)
}
for i := 0; i < int(extra); i++ {
{
var maj byte
var extra uint64
var err error
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
if pb == cbg.CborNull[0] {
var nbuf [1]byte
if _, err := br.Read(nbuf[:]); err != nil {
return err
}
if extra > cbg.MaxLength {
return fmt.Errorf("t.BlsMsgIncludes[i]: array too large (%d)", extra)
} else {
t.Messages = new(CompactedMessages)
if err := t.Messages.UnmarshalCBOR(br); err != nil {
return xerrors.Errorf("unmarshaling t.Messages pointer: %w", err)
}
if maj != cbg.MajArray {
return fmt.Errorf("expected cbor array")
}
if extra > 0 {
t.BlsMsgIncludes[i] = make([]uint64, extra)
}
for j := 0; j < int(extra); j++ {
maj, val, err := cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return xerrors.Errorf("failed to read uint64 for t.BlsMsgIncludes[i] slice: %w", err)
}
if maj != cbg.MajUnsignedInt {
return xerrors.Errorf("value read for array t.BlsMsgIncludes[i] was not a uint, instead got %d", maj)
}
t.BlsMsgIncludes[i][j] = uint64(val)
}
}
}
// t.SecpkMessages ([]*types.SignedMessage) (slice)
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return err
}
if extra > cbg.MaxLength {
return fmt.Errorf("t.SecpkMessages: array too large (%d)", extra)
}
if maj != cbg.MajArray {
return fmt.Errorf("expected cbor array")
}
if extra > 0 {
t.SecpkMessages = make([]*types.SignedMessage, extra)
}
for i := 0; i < int(extra); i++ {
var v types.SignedMessage
if err := v.UnmarshalCBOR(br); err != nil {
return err
}
t.SecpkMessages[i] = &v
}
// t.SecpkMsgIncludes ([][]uint64) (slice)
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return err
}
if extra > cbg.MaxLength {
return fmt.Errorf("t.SecpkMsgIncludes: array too large (%d)", extra)
}
if maj != cbg.MajArray {
return fmt.Errorf("expected cbor array")
}
if extra > 0 {
t.SecpkMsgIncludes = make([][]uint64, extra)
}
for i := 0; i < int(extra); i++ {
{
var maj byte
var extra uint64
var err error
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return err
}
if extra > cbg.MaxLength {
return fmt.Errorf("t.SecpkMsgIncludes[i]: array too large (%d)", extra)
}
if maj != cbg.MajArray {
return fmt.Errorf("expected cbor array")
}
if extra > 0 {
t.SecpkMsgIncludes[i] = make([]uint64, extra)
}
for j := 0; j < int(extra); j++ {
maj, val, err := cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return xerrors.Errorf("failed to read uint64 for t.SecpkMsgIncludes[i] slice: %w", err)
}
if maj != cbg.MajUnsignedInt {
return xerrors.Errorf("value read for array t.SecpkMsgIncludes[i] was not a uint, instead got %d", maj)
}
t.SecpkMsgIncludes[i][j] = uint64(val)
}
}
}
return nil
}

446
chain/blocksync/client.go Normal file
View File

@ -0,0 +1,446 @@
package blocksync
import (
"bufio"
"context"
"fmt"
"math/rand"
"time"
host "github.com/libp2p/go-libp2p-core/host"
inet "github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
"go.opencensus.io/trace"
"golang.org/x/xerrors"
cborutil "github.com/filecoin-project/go-cbor-util"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
incrt "github.com/filecoin-project/lotus/lib/increadtimeout"
"github.com/filecoin-project/lotus/lib/peermgr"
)
// Protocol client.
// FIXME: Rename to just `Client`. Not done at the moment to avoid
// disrupting too much of the consumer code, should be done along
// https://github.com/filecoin-project/lotus/issues/2612.
type BlockSync struct {
// Connection manager used to contact the server.
// FIXME: We should have a reduced interface here, initialized
// just with our protocol ID, we shouldn't be able to open *any*
// connection.
host host.Host
peerTracker *bsPeerTracker
}
func NewClient(
host host.Host,
pmgr peermgr.MaybePeerMgr,
) *BlockSync {
return &BlockSync{
host: host,
peerTracker: newPeerTracker(pmgr.Mgr),
}
}
// Main logic of the client request service. The provided `Request`
// is sent to the `singlePeer` if one is indicated or to all available
// ones otherwise. The response is processed and validated according
// to the `Request` options. Either a `validatedResponse` is returned
// (which can be safely accessed), or an `error` that may represent
// either a response error status, a failed validation or an internal
// error.
//
// This is the internal single point of entry for all external-facing
// APIs, currently we have 3 very heterogeneous services exposed:
// * GetBlocks: Headers
// * GetFullTipSet: Headers | Messages
// * GetChainMessages: Messages
// This function handles all the different combinations of the available
// request options without disrupting external calls. In the future the
// consumers should be forced to use a more standardized service and
// adhere to a single API derived from this function.
func (client *BlockSync) doRequest(
ctx context.Context,
req *Request,
singlePeer *peer.ID,
) (*validatedResponse, error) {
// Validate request.
if req.Length == 0 {
return nil, xerrors.Errorf("invalid request of length 0")
}
if req.Length > MaxRequestLength {
return nil, xerrors.Errorf("request length (%d) above maximum (%d)",
req.Length, MaxRequestLength)
}
if req.Options == 0 {
return nil, xerrors.Errorf("request with no options set")
}
// Generate the list of peers to be queried, either the
// `singlePeer` indicated or all peers available (sorted
// by an internal peer tracker with some randomness injected).
var peers []peer.ID
if singlePeer != nil {
peers = []peer.ID{*singlePeer}
} else {
peers = client.getShuffledPeers()
if len(peers) == 0 {
return nil, xerrors.Errorf("no peers available")
}
}
// Try the request for each peer in the list,
// return on the first successful response.
// FIXME: Doing this serially isn't great, but fetching in parallel
// may not be a good idea either. Think about this more.
globalTime := build.Clock.Now()
// Global time used to track what is the expected time we will need to get
// a response if a client fails us.
for _, peer := range peers {
select {
case <-ctx.Done():
return nil, xerrors.Errorf("context cancelled: %w", ctx.Err())
default:
}
// Send request, read response.
res, err := client.sendRequestToPeer(ctx, peer, req)
if err != nil {
if !xerrors.Is(err, inet.ErrNoConn) {
log.Warnf("could not connect to peer %s: %s",
peer.String(), err)
}
continue
}
// Process and validate response.
validRes, err := client.processResponse(req, res)
if err != nil {
log.Warnf("processing peer %s response failed: %s",
peer.String(), err)
continue
}
client.peerTracker.logGlobalSuccess(build.Clock.Since(globalTime))
client.host.ConnManager().TagPeer(peer, "bsync", SUCCESS_PEER_TAG_VALUE)
return validRes, nil
}
errString := "doRequest failed for all peers"
if singlePeer != nil {
errString = fmt.Sprintf("doRequest failed for single peer %s", *singlePeer)
}
return nil, xerrors.Errorf(errString)
}
// Process and validate response. Check the status, the integrity of the
// information returned, and that it matches the request. Extract the information
// into a `validatedResponse` for the external-facing APIs to select what they
// need.
//
// We are conflating in the single error returned both status and validation
// errors. Peer penalization should happen here then, before returning, so
// we can apply the correct penalties depending on the cause of the error.
func (client *BlockSync) processResponse(
req *Request,
res *Response,
// FIXME: Add the `peer` as argument once we implement penalties.
) (*validatedResponse, error) {
err := res.statusToError()
if err != nil {
return nil, xerrors.Errorf("status error: %s", err)
}
options := parseOptions(req.Options)
if options.noOptionsSet() {
// Safety check: this shouldn't have been sent, and even if it did
// it should have been caught by the peer in its error status.
return nil, xerrors.Errorf("nothing was requested")
}
// Verify that the chain segment returned is in the valid range.
// Note that the returned length might be less than requested.
resLength := len(res.Chain)
if resLength == 0 {
return nil, xerrors.Errorf("got no chain in successful response")
}
if resLength > int(req.Length) {
return nil, xerrors.Errorf("got longer response (%d) than requested (%d)",
resLength, req.Length)
}
if resLength < int(req.Length) && res.Status != Partial {
return nil, xerrors.Errorf("got less than requested without a proper status: %s", res.Status)
}
validRes := &validatedResponse{}
if options.IncludeHeaders {
// Check for valid block sets and extract them into `TipSet`s.
validRes.tipsets = make([]*types.TipSet, resLength)
for i := 0; i < resLength; i++ {
validRes.tipsets[i], err = types.NewTipSet(res.Chain[i].Blocks)
if err != nil {
return nil, xerrors.Errorf("invalid tipset blocks at height (head - %d): %w", i, err)
}
}
// Check that the returned head matches the one requested.
if !types.CidArrsEqual(validRes.tipsets[0].Cids(), req.Head) {
return nil, xerrors.Errorf("returned chain head does not match request")
}
// Check `TipSet`s are connected (valid chain).
for i := 0; i < len(validRes.tipsets)-1; i++ {
if validRes.tipsets[i].IsChildOf(validRes.tipsets[i+1]) == false {
return nil, fmt.Errorf("tipsets are not connected at height (head - %d)/(head - %d)",
i, i+1)
// FIXME: Maybe give more information here, like CIDs.
}
}
}
if options.IncludeMessages {
validRes.messages = make([]*CompactedMessages, resLength)
for i := 0; i < resLength; i++ {
if res.Chain[i].Messages == nil {
return nil, xerrors.Errorf("no messages included for tipset at height (head - %d): %w", i)
}
validRes.messages[i] = res.Chain[i].Messages
}
if options.IncludeHeaders {
// If the headers were also returned check that the compression
// indexes are valid before `toFullTipSets()` is called by the
// consumer.
for tipsetIdx := 0; tipsetIdx < resLength; tipsetIdx++ {
msgs := res.Chain[tipsetIdx].Messages
blocksNum := len(res.Chain[tipsetIdx].Blocks)
if len(msgs.BlsIncludes) != blocksNum {
return nil, xerrors.Errorf("BlsIncludes (%d) does not match number of blocks (%d)",
len(msgs.BlsIncludes), blocksNum)
}
if len(msgs.SecpkIncludes) != blocksNum {
return nil, xerrors.Errorf("SecpkIncludes (%d) does not match number of blocks (%d)",
len(msgs.SecpkIncludes), blocksNum)
}
for blockIdx := 0; blockIdx < blocksNum; blockIdx++ {
for _, mi := range msgs.BlsIncludes[blockIdx] {
if int(mi) >= len(msgs.Bls) {
return nil, xerrors.Errorf("index in BlsIncludes (%d) exceeds number of messages (%d)",
mi, len(msgs.Bls))
}
}
for _, mi := range msgs.SecpkIncludes[blockIdx] {
if int(mi) >= len(msgs.Secpk) {
return nil, xerrors.Errorf("index in SecpkIncludes (%d) exceeds number of messages (%d)",
mi, len(msgs.Secpk))
}
}
}
}
}
}
return validRes, nil
}
// 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 (client *BlockSync) GetBlocks(
ctx context.Context,
tsk types.TipSetKey,
count int,
) ([]*types.TipSet, error) {
ctx, span := trace.StartSpan(ctx, "bsync.GetBlocks")
defer span.End()
if span.IsRecordingEvents() {
span.AddAttributes(
trace.StringAttribute("tipset", fmt.Sprint(tsk.Cids())),
trace.Int64Attribute("count", int64(count)),
)
}
req := &Request{
Head: tsk.Cids(),
Length: uint64(count),
Options: Headers,
}
validRes, err := client.doRequest(ctx, req, nil)
if err != nil {
return nil, err
}
return validRes.tipsets, nil
}
func (client *BlockSync) GetFullTipSet(
ctx context.Context,
peer peer.ID,
tsk types.TipSetKey,
) (*store.FullTipSet, error) {
// TODO: round robin through these peers on error
req := &Request{
Head: tsk.Cids(),
Length: 1,
Options: Headers | Messages,
}
validRes, err := client.doRequest(ctx, req, &peer)
if err != nil {
return nil, err
}
return validRes.toFullTipSets()[0], nil
// If `doRequest` didn't fail we are guaranteed to have at least
// *one* tipset here, so it's safe to index directly.
}
func (client *BlockSync) GetChainMessages(
ctx context.Context,
head *types.TipSet,
length uint64,
) ([]*CompactedMessages, error) {
ctx, span := trace.StartSpan(ctx, "GetChainMessages")
defer span.End()
req := &Request{
Head: head.Cids(),
Length: length,
Options: Messages,
}
validRes, err := client.doRequest(ctx, req, nil)
if err != nil {
return nil, err
}
return validRes.messages, nil
}
// Send a request to a peer. Write request in the stream and read the
// response back. We do not do any processing of the request/response
// here.
func (client *BlockSync) sendRequestToPeer(
ctx context.Context,
peer peer.ID,
req *Request,
) (_ *Response, err error) {
// Trace code.
ctx, span := trace.StartSpan(ctx, "sendRequestToPeer")
defer span.End()
if span.IsRecordingEvents() {
span.AddAttributes(
trace.StringAttribute("peer", peer.Pretty()),
)
}
defer func() {
if err != nil {
if span.IsRecordingEvents() {
span.SetStatus(trace.Status{
Code: 5,
Message: err.Error(),
})
}
}
}()
// -- TRACE --
supported, err := client.host.Peerstore().SupportsProtocols(peer, BlockSyncProtocolID)
if err != nil {
return nil, xerrors.Errorf("failed to get protocols for peer: %w", err)
}
if len(supported) == 0 || supported[0] != BlockSyncProtocolID {
return nil, xerrors.Errorf("peer %s does not support protocol %s",
peer, BlockSyncProtocolID)
// FIXME: `ProtoBook` should support a *single* protocol check that returns
// a bool instead of a list.
}
connectionStart := build.Clock.Now()
// Open stream to peer.
stream, err := client.host.NewStream(
inet.WithNoDial(ctx, "should already have connection"),
peer,
BlockSyncProtocolID)
if err != nil {
client.RemovePeer(peer)
return nil, xerrors.Errorf("failed to open stream to peer: %w", err)
}
// Write request.
_ = stream.SetWriteDeadline(time.Now().Add(WRITE_REQ_DEADLINE))
if err := cborutil.WriteCborRPC(stream, req); err != nil {
_ = stream.SetWriteDeadline(time.Time{})
client.peerTracker.logFailure(peer, build.Clock.Since(connectionStart))
// FIXME: Should we also remove peer here?
return nil, err
}
_ = stream.SetWriteDeadline(time.Time{}) // clear deadline // FIXME: Needs
// its own API (https://github.com/libp2p/go-libp2p-core/issues/162).
// Read response.
var res Response
err = cborutil.ReadCborRPC(
bufio.NewReader(incrt.New(stream, READ_RES_MIN_SPEED, READ_RES_DEADLINE)),
&res)
if err != nil {
client.peerTracker.logFailure(peer, build.Clock.Since(connectionStart))
return nil, err
}
// FIXME: Move all this together at the top using a defer as done elsewhere.
// Maybe we need to declare `res` in the signature.
if span.IsRecordingEvents() {
span.AddAttributes(
trace.Int64Attribute("resp_status", int64(res.Status)),
trace.StringAttribute("msg", res.ErrorMessage),
trace.Int64Attribute("chain_len", int64(len(res.Chain))),
)
}
client.peerTracker.logSuccess(peer, build.Clock.Since(connectionStart))
// FIXME: We should really log a success only after we validate the response.
// It might be a bit hard to do.
return &res, nil
}
func (client *BlockSync) AddPeer(p peer.ID) {
client.peerTracker.addPeer(p)
}
func (client *BlockSync) RemovePeer(p peer.ID) {
client.peerTracker.removePeer(p)
}
// getShuffledPeers returns a preference-sorted set of peers (by latency
// and failure counting), shuffling the first few peers so we don't always
// pick the same peer.
// FIXME: Consider merging with `shufflePrefix()s`.
func (client *BlockSync) getShuffledPeers() []peer.ID {
peers := client.peerTracker.prefSortedPeers()
shufflePrefix(peers)
return peers
}
func shufflePrefix(peers []peer.ID) {
prefix := SHUFFLE_PEERS_PREFIX
if len(peers) < prefix {
prefix = len(peers)
}
buf := make([]peer.ID, prefix)
perm := rand.Perm(prefix)
for i, v := range perm {
buf[i] = peers[v]
}
copy(peers, buf)
}

View File

@ -1,152 +0,0 @@
package blocksync
import (
"context"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-datastore"
"github.com/ipfs/go-graphsync"
"github.com/ipld/go-ipld-prime"
"github.com/libp2p/go-libp2p-core/peer"
"golang.org/x/xerrors"
store "github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/journal"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
basicnode "github.com/ipld/go-ipld-prime/node/basic"
ipldselector "github.com/ipld/go-ipld-prime/traversal/selector"
selectorbuilder "github.com/ipld/go-ipld-prime/traversal/selector/builder"
)
const (
// AMT selector recursion. An AMT has arity of 8 so this gives allows
// us to retrieve trees with 8^10 (1,073,741,824) elements.
amtRecursionDepth = uint32(10)
// some constants for looking up tuple encoded struct fields
// field index of Parents field in a block header
blockIndexParentsField = 5
// field index of Messages field in a block header
blockIndexMessagesField = 10
// field index of AMT node in AMT head
amtHeadNodeFieldIndex = 2
// field index of links array AMT node
amtNodeLinksFieldIndex = 1
// field index of values array AMT node
amtNodeValuesFieldIndex = 2
// maximum depth per traversal
maxRequestLength = 50
)
var amtSelector selectorbuilder.SelectorSpec
func init() {
// builer for selectors
ssb := selectorbuilder.NewSelectorSpecBuilder(basicnode.Style.Any)
// amt selector -- needed to selector through a messages AMT
amtSelector = ssb.ExploreIndex(amtHeadNodeFieldIndex,
ssb.ExploreRecursive(ipldselector.RecursionLimitDepth(int(amtRecursionDepth)),
ssb.ExploreUnion(
ssb.ExploreIndex(amtNodeLinksFieldIndex,
ssb.ExploreAll(ssb.ExploreRecursiveEdge())),
ssb.ExploreIndex(amtNodeValuesFieldIndex,
ssb.ExploreAll(ssb.Matcher())))))
}
func selectorForRequest(req *BlockSyncRequest) ipld.Node {
// builer for selectors
ssb := selectorbuilder.NewSelectorSpecBuilder(basicnode.Style.Any)
bso := ParseBSOptions(req.Options)
if bso.IncludeMessages {
return ssb.ExploreRecursive(ipldselector.RecursionLimitDepth(int(req.RequestLength)),
ssb.ExploreIndex(blockIndexParentsField,
ssb.ExploreUnion(
ssb.ExploreAll(
ssb.ExploreIndex(blockIndexMessagesField,
ssb.ExploreRange(0, 2, amtSelector),
)),
ssb.ExploreIndex(0, ssb.ExploreRecursiveEdge()),
))).Node()
}
return ssb.ExploreRecursive(ipldselector.RecursionLimitDepth(int(req.RequestLength)), ssb.ExploreIndex(blockIndexParentsField,
ssb.ExploreUnion(
ssb.ExploreAll(
ssb.Matcher(),
),
ssb.ExploreIndex(0, ssb.ExploreRecursiveEdge()),
))).Node()
}
func firstTipsetSelector(req *BlockSyncRequest) ipld.Node {
// builer for selectors
ssb := selectorbuilder.NewSelectorSpecBuilder(basicnode.Style.Any)
bso := ParseBSOptions(req.Options)
if bso.IncludeMessages {
return ssb.ExploreIndex(blockIndexMessagesField,
ssb.ExploreRange(0, 2, amtSelector),
).Node()
}
return ssb.Matcher().Node()
}
func (bs *BlockSync) executeGsyncSelector(ctx context.Context, p peer.ID, root cid.Cid, sel ipld.Node) error {
extension := graphsync.ExtensionData{
Name: "chainsync",
Data: nil,
}
_, errs := bs.gsync.Request(ctx, p, cidlink.Link{Cid: root}, sel, extension)
for err := range errs {
return xerrors.Errorf("failed to complete graphsync request: %w", err)
}
return nil
}
// Fallback for interacting with other non-lotus nodes
func (bs *BlockSync) fetchBlocksGraphSync(ctx context.Context, p peer.ID, req *BlockSyncRequest) (*BlockSyncResponse, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
immediateTsSelector := firstTipsetSelector(req)
// Do this because we can only request one root at a time
for _, r := range req.Start {
if err := bs.executeGsyncSelector(ctx, p, r, immediateTsSelector); err != nil {
return nil, err
}
}
if req.RequestLength > maxRequestLength {
req.RequestLength = maxRequestLength
}
sel := selectorForRequest(req)
// execute the selector forreal
if err := bs.executeGsyncSelector(ctx, p, req.Start[0], sel); err != nil {
return nil, err
}
// Now pull the data we fetched out of the chainstore (where it should now be persisted)
tempcs := store.NewChainStore(bs.bserv.Blockstore(), datastore.NewMapDatastore(), nil, journal.NilJournal())
opts := ParseBSOptions(req.Options)
tsk := types.NewTipSetKey(req.Start...)
chain, err := collectChainSegment(tempcs, tsk, req.RequestLength, opts)
if err != nil {
return nil, xerrors.Errorf("failed to load chain data from chainstore after successful graphsync response (start = %v): %w", req.Start, err)
}
return &BlockSyncResponse{Chain: chain}, nil
}

View File

@ -0,0 +1,170 @@
package blocksync
// FIXME: This needs to be reviewed.
import (
"sort"
"sync"
"time"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/lib/peermgr"
)
type peerStats struct {
successes int
failures int
firstSeen time.Time
averageTime time.Duration
}
type bsPeerTracker struct {
lk sync.Mutex
peers map[peer.ID]*peerStats
avgGlobalTime time.Duration
pmgr *peermgr.PeerMgr
}
func newPeerTracker(pmgr *peermgr.PeerMgr) *bsPeerTracker {
return &bsPeerTracker{
peers: make(map[peer.ID]*peerStats),
pmgr: pmgr,
}
}
func (bpt *bsPeerTracker) addPeer(p peer.ID) {
bpt.lk.Lock()
defer bpt.lk.Unlock()
if _, ok := bpt.peers[p]; ok {
return
}
bpt.peers[p] = &peerStats{
firstSeen: build.Clock.Now(),
}
}
const (
// newPeerMul is how much better than average is the new peer assumed to be
// less than one to encourouge trying new peers
newPeerMul = 0.9
)
func (bpt *bsPeerTracker) prefSortedPeers() []peer.ID {
// TODO: this could probably be cached, but as long as its not too many peers, fine for now
bpt.lk.Lock()
defer bpt.lk.Unlock()
out := make([]peer.ID, 0, len(bpt.peers))
for p := range bpt.peers {
out = append(out, p)
}
// sort by 'expected cost' of requesting data from that peer
// additionally handle edge cases where not enough data is available
sort.Slice(out, func(i, j int) bool {
pi := bpt.peers[out[i]]
pj := bpt.peers[out[j]]
var costI, costJ float64
getPeerInitLat := func(p peer.ID) float64 {
var res float64
if bpt.pmgr != nil {
if lat, ok := bpt.pmgr.GetPeerLatency(p); ok {
res = float64(lat)
}
}
if res == 0 {
res = float64(bpt.avgGlobalTime)
}
return res * newPeerMul
}
if pi.successes+pi.failures > 0 {
failRateI := float64(pi.failures) / float64(pi.failures+pi.successes)
costI = float64(pi.averageTime) + failRateI*float64(bpt.avgGlobalTime)
} else {
costI = getPeerInitLat(out[i])
}
if pj.successes+pj.failures > 0 {
failRateJ := float64(pj.failures) / float64(pj.failures+pj.successes)
costJ = float64(pj.averageTime) + failRateJ*float64(bpt.avgGlobalTime)
} else {
costJ = getPeerInitLat(out[j])
}
return costI < costJ
})
return out
}
const (
// xInvAlpha = (N+1)/2
localInvAlpha = 5 // 86% of the value is the last 9
globalInvAlpha = 20 // 86% of the value is the last 39
)
func (bpt *bsPeerTracker) logGlobalSuccess(dur time.Duration) {
bpt.lk.Lock()
defer bpt.lk.Unlock()
if bpt.avgGlobalTime == 0 {
bpt.avgGlobalTime = dur
return
}
delta := (dur - bpt.avgGlobalTime) / globalInvAlpha
bpt.avgGlobalTime += delta
}
func logTime(pi *peerStats, dur time.Duration) {
if pi.averageTime == 0 {
pi.averageTime = dur
return
}
delta := (dur - pi.averageTime) / localInvAlpha
pi.averageTime += delta
}
func (bpt *bsPeerTracker) logSuccess(p peer.ID, dur time.Duration) {
bpt.lk.Lock()
defer bpt.lk.Unlock()
var pi *peerStats
var ok bool
if pi, ok = bpt.peers[p]; !ok {
log.Warnw("log success called on peer not in tracker", "peerid", p.String())
return
}
pi.successes++
logTime(pi, dur)
}
func (bpt *bsPeerTracker) logFailure(p peer.ID, dur time.Duration) {
bpt.lk.Lock()
defer bpt.lk.Unlock()
var pi *peerStats
var ok bool
if pi, ok = bpt.peers[p]; !ok {
log.Warn("log failure called on peer not in tracker", "peerid", p.String())
return
}
pi.failures++
logTime(pi, dur)
}
func (bpt *bsPeerTracker) removePeer(p peer.ID) {
bpt.lk.Lock()
defer bpt.lk.Unlock()
delete(bpt.peers, p)
}

194
chain/blocksync/protocol.go Normal file
View File

@ -0,0 +1,194 @@
package blocksync
import (
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/store"
"time"
"github.com/ipfs/go-cid"
logging "github.com/ipfs/go-log"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/chain/types"
)
var log = logging.Logger("blocksync")
const BlockSyncProtocolID = "/fil/sync/blk/0.0.1"
// FIXME: Bumped from original 800 to this to accommodate `syncFork()`
// use of `GetBlocks()`. It seems the expectation of that API is to
// fetch any amount of blocks leaving it to the internal logic here
// to partition and reassemble the requests if they go above the maximum.
// (Also as a consequence of this temporarily removing the `const`
// qualifier to avoid "const initializer [...] is not a constant" error.)
var MaxRequestLength = uint64(build.ForkLengthThreshold)
// Extracted constants from the code.
// FIXME: Should be reviewed and confirmed.
const SUCCESS_PEER_TAG_VALUE = 25
const WRITE_REQ_DEADLINE = 5 * time.Second
const READ_RES_DEADLINE = WRITE_REQ_DEADLINE
const READ_RES_MIN_SPEED = 50 << 10
const SHUFFLE_PEERS_PREFIX = 5
const WRITE_RES_DEADLINE = 60 * time.Second
// FIXME: Rename. Make private.
type Request struct {
// List of ordered CIDs comprising a `TipSetKey` from where to start
// fetching backwards.
// FIXME: Consider using `TipSetKey` now (introduced after the creation
// of this protocol) instead of converting back and forth.
Head []cid.Cid
// Number of block sets to fetch from `Head` (inclusive, should always
// be in the range `[1, MaxRequestLength]`).
Length uint64
// Request options, see `Options` type for more details. Compressed
// in a single `uint64` to save space.
Options uint64
}
// `Request` processed and validated to query the tipsets needed.
type validatedRequest struct {
head types.TipSetKey
length uint64
options *parsedOptions
}
// Request options. When fetching the chain segment we can fetch
// either block headers, messages, or both.
const (
Headers = 1 << iota
Messages
)
// Decompressed options into separate struct members for easy access
// during internal processing..
type parsedOptions struct {
IncludeHeaders bool
IncludeMessages bool
}
func (options *parsedOptions) noOptionsSet() bool {
return options.IncludeHeaders == false &&
options.IncludeMessages == false
}
func parseOptions(optfield uint64) *parsedOptions {
return &parsedOptions{
IncludeHeaders: optfield&(uint64(Headers)) != 0,
IncludeMessages: optfield&(uint64(Messages)) != 0,
}
}
// FIXME: Rename. Make private.
type Response struct {
Status status
// String that complements the error status when converting to an
// internal error (see `statusToError()`).
ErrorMessage string
Chain []*BSTipSet
}
type status uint64
const (
Ok status = 0
// We could not fetch all blocks requested (but at least we returned
// the `Head` requested). Not considered an error.
Partial = 101
// Errors
NotFound = 201
GoAway = 202
InternalError = 203
BadRequest = 204
)
// Convert status to internal error.
func (res *Response) statusToError() error {
switch res.Status {
case Ok, Partial:
return nil
// FIXME: Consider if we want to not process `Partial` responses
// and return an error instead.
case NotFound:
return xerrors.Errorf("not found")
case GoAway:
return xerrors.Errorf("not handling 'go away' blocksync responses yet")
case InternalError:
return xerrors.Errorf("block sync peer errored: %s", res.ErrorMessage)
case BadRequest:
return xerrors.Errorf("block sync request invalid: %s", res.ErrorMessage)
default:
return xerrors.Errorf("unrecognized response code: %d", res.Status)
}
}
// FIXME: Rename.
type BSTipSet struct {
Blocks []*types.BlockHeader
Messages *CompactedMessages
}
// All messages of a single tipset compacted together instead
// of grouped by block to save space, since there are normally
// many repeated messages per tipset in different blocks.
//
// `BlsIncludes`/`SecpkIncludes` matches `Bls`/`Secpk` messages
// to blocks in the tipsets with the format:
// `BlsIncludes[BI][MI]`
// * BI: block index in the tipset.
// * MI: message index in `Bls` list
//
// FIXME: The logic to decompress this structure should belong
// to itself, not to the consumer.
type CompactedMessages struct {
Bls []*types.Message
BlsIncludes [][]uint64
Secpk []*types.SignedMessage
SecpkIncludes [][]uint64
}
// Response that has been validated according to the protocol
// and can be safely accessed.
type validatedResponse struct {
tipsets []*types.TipSet
// List of all messages per tipset (grouped by tipset,
// not by block, hence a single index like `tipsets`).
messages []*CompactedMessages
}
// Decompress messages and form full tipsets with them. The headers
// need to have been requested as well.
func (res *validatedResponse) toFullTipSets() []*store.FullTipSet {
if len(res.tipsets) == 0 || len(res.tipsets) != len(res.messages) {
// This decompression can only be done if both headers and
// messages are returned in the response. (The second check
// is already implied by the guarantees of `validatedResponse`,
// added here just for completeness.)
return nil
}
ftsList := make([]*store.FullTipSet, len(res.tipsets))
for tipsetIdx := range res.tipsets {
fts := &store.FullTipSet{} // FIXME: We should use the `NewFullTipSet` API.
msgs := res.messages[tipsetIdx]
for blockIdx, b := range res.tipsets[tipsetIdx].Blocks() {
fb := &types.FullBlock{
Header: b,
}
for _, mi := range msgs.BlsIncludes[blockIdx] {
fb.BlsMessages = append(fb.BlsMessages, msgs.Bls[mi])
}
for _, mi := range msgs.SecpkIncludes[blockIdx] {
fb.SecpkMessages = append(fb.SecpkMessages, msgs.Secpk[mi])
}
fts.Blocks = append(fts.Blocks, fb)
}
ftsList[tipsetIdx] = fts
}
return ftsList
}

263
chain/blocksync/server.go Normal file
View File

@ -0,0 +1,263 @@
package blocksync
import (
"bufio"
"context"
"fmt"
"time"
"go.opencensus.io/trace"
"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"
"github.com/ipfs/go-cid"
inet "github.com/libp2p/go-libp2p-core/network"
)
// 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.
// FIXME: Rename to just `Server` (will be done later, see note on `BlockSync`).
type BlockSyncService struct {
cs *store.ChainStore
}
func NewBlockSyncService(cs *store.ChainStore) *BlockSyncService {
return &BlockSyncService{
cs: cs,
}
}
// Entry point of the service, handles `Request`s.
func (server *BlockSyncService) HandleStream(stream inet.Stream) {
ctx, span := trace.StartSpan(context.Background(), "blocksync.HandleStream")
defer span.End()
defer stream.Close() //nolint:errcheck
var req Request
if err := cborutil.ReadCborRPC(bufio.NewReader(stream), &req); err != nil {
log.Warnf("failed to read block sync request: %s", err)
return
}
log.Infow("block sync request",
"start", req.Head, "len", req.Length)
resp, err := server.processRequest(ctx, &req)
if err != nil {
log.Warn("failed to process request: ", err)
return
}
_ = stream.SetDeadline(time.Now().Add(WRITE_RES_DEADLINE))
if err := cborutil.WriteCborRPC(stream, resp); err != nil {
_ = stream.SetDeadline(time.Time{})
log.Warnw("failed to write back response for handle stream",
"err", err, "peer", stream.Conn().RemotePeer())
return
}
_ = stream.SetDeadline(time.Time{})
}
// Validate and service the request. We return either a protocol
// response or an internal error.
func (server *BlockSyncService) processRequest(
ctx context.Context,
req *Request,
) (*Response, error) {
validReq, errResponse := validateRequest(ctx, req)
if errResponse != nil {
// The request did not pass validation, return the response
// indicating it.
return errResponse, nil
}
return server.serviceRequest(ctx, validReq)
}
// Validate request. We either return a `validatedRequest`, or an error
// `Response` indicating why we can't process it. We do not return any
// internal errors here, we just signal protocol ones.
func validateRequest(
ctx context.Context,
req *Request,
) (*validatedRequest, *Response) {
_, span := trace.StartSpan(ctx, "blocksync.ValidateRequest")
defer span.End()
validReq := validatedRequest{}
validReq.options = parseOptions(req.Options)
if validReq.options.noOptionsSet() {
return nil, &Response{
Status: BadRequest,
ErrorMessage: "no options set",
}
}
validReq.length = req.Length
if validReq.length > MaxRequestLength {
return nil, &Response{
Status: BadRequest,
ErrorMessage: fmt.Sprintf("request length over maximum allowed (%d)",
MaxRequestLength),
}
}
if validReq.length == 0 {
return nil, &Response{
Status: BadRequest,
ErrorMessage: "invalid request length of zero",
}
}
if len(req.Head) == 0 {
return nil, &Response{
Status: BadRequest,
ErrorMessage: "no cids in request",
}
}
validReq.head = types.NewTipSetKey(req.Head...)
// FIXME: Add as a defer at the start.
span.AddAttributes(
trace.BoolAttribute("blocks", validReq.options.IncludeHeaders),
trace.BoolAttribute("messages", validReq.options.IncludeMessages),
trace.Int64Attribute("reqlen", int64(validReq.length)),
)
return &validReq, nil
}
func (server *BlockSyncService) serviceRequest(
ctx context.Context,
req *validatedRequest,
) (*Response, error) {
_, span := trace.StartSpan(ctx, "blocksync.ServiceRequest")
defer span.End()
chain, err := collectChainSegment(server.cs, req)
if err != nil {
log.Warn("block sync request: collectChainSegment failed: ", err)
return &Response{
Status: InternalError,
ErrorMessage: err.Error(),
}, nil
}
status := Ok
if len(chain) < int(req.length) {
status = Partial
}
return &Response{
Chain: chain,
Status: status,
}, nil
}
func collectChainSegment(
cs *store.ChainStore,
req *validatedRequest,
) ([]*BSTipSet, error) {
var bstips []*BSTipSet
cur := req.head
for {
var bst BSTipSet
ts, err := cs.LoadTipSet(cur)
if err != nil {
return nil, xerrors.Errorf("failed loading tipset %s: %w", cur, err)
}
if req.options.IncludeHeaders {
bst.Blocks = ts.Blocks()
}
if req.options.IncludeMessages {
bmsgs, bmincl, smsgs, smincl, err := gatherMessages(cs, ts)
if err != nil {
return nil, xerrors.Errorf("gather messages failed: %w", err)
}
// FIXME: Pass the response to `gatherMessages()` and set all this there.
bst.Messages = &CompactedMessages{}
bst.Messages.Bls = bmsgs
bst.Messages.BlsIncludes = bmincl
bst.Messages.Secpk = smsgs
bst.Messages.SecpkIncludes = smincl
}
bstips = append(bstips, &bst)
// If we collected the length requested or if we reached the
// start (genesis), then stop.
if uint64(len(bstips)) >= req.length || ts.Height() == 0 {
return bstips, nil
}
cur = ts.Parents()
}
}
func gatherMessages(cs *store.ChainStore, ts *types.TipSet) ([]*types.Message, [][]uint64, []*types.SignedMessage, [][]uint64, error) {
blsmsgmap := make(map[cid.Cid]uint64)
secpkmsgmap := make(map[cid.Cid]uint64)
var secpkmsgs []*types.SignedMessage
var blsmsgs []*types.Message
var secpkincl, blsincl [][]uint64
for _, block := range ts.Blocks() {
bmsgs, smsgs, err := cs.MessagesForBlock(block)
if err != nil {
return nil, nil, nil, nil, err
}
// FIXME: DRY. Use `chain.Message` interface.
bmi := make([]uint64, 0, len(bmsgs))
for _, m := range bmsgs {
i, ok := blsmsgmap[m.Cid()]
if !ok {
i = uint64(len(blsmsgs))
blsmsgs = append(blsmsgs, m)
blsmsgmap[m.Cid()] = i
}
bmi = append(bmi, i)
}
blsincl = append(blsincl, bmi)
smi := make([]uint64, 0, len(smsgs))
for _, m := range smsgs {
i, ok := secpkmsgmap[m.Cid()]
if !ok {
i = uint64(len(secpkmsgs))
secpkmsgs = append(secpkmsgs, m)
secpkmsgmap[m.Cid()] = i
}
smi = append(smi, i)
}
secpkincl = append(secpkincl, smi)
}
return blsmsgs, blsincl, secpkmsgs, secpkincl, nil
}

View File

@ -8,14 +8,13 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
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"
typegen "github.com/whyrusleeping/cbor-gen"
"github.com/filecoin-project/specs-actors/actors/runtime"
"github.com/filecoin-project/specs-actors/actors/util/adt"
bstore "github.com/filecoin-project/lotus/lib/blockstore"
)
func TestDiffAdtArray(t *testing.T) {
@ -146,7 +145,7 @@ func (t *TestAdtDiff) Remove(key uint64, val *typegen.Deferred) error {
func newContextStore() *contextStore {
ctx := context.Background()
bs := bstore.NewBlockstore(ds_sync.MutexWrap(ds.NewMapDatastore()))
bs := bstore.NewTemporarySync()
store := cbornode.NewCborStore(bs)
return &contextStore{
ctx: ctx,

View File

@ -3,15 +3,18 @@ package state
import (
"bytes"
"context"
"github.com/filecoin-project/go-address"
"github.com/ipfs/go-cid"
cbor "github.com/ipfs/go-ipld-cbor"
typegen "github.com/whyrusleeping/cbor-gen"
"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"
"github.com/filecoin-project/specs-actors/actors/builtin/paych"
"github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/filecoin-project/lotus/api/apibstore"
@ -84,6 +87,50 @@ func (sp *StatePredicates) OnStorageMarketActorChanged(diffStorageMarketState Di
})
}
type BalanceTables struct {
EscrowTable *adt.BalanceTable
LockedTable *adt.BalanceTable
}
// DiffBalanceTablesFunc compares two balance tables
type DiffBalanceTablesFunc func(ctx context.Context, oldBalanceTable, newBalanceTable BalanceTables) (changed bool, user UserData, err error)
// OnBalanceChanged runs when the escrow table for available balances changes
func (sp *StatePredicates) OnBalanceChanged(diffBalances DiffBalanceTablesFunc) DiffStorageMarketStateFunc {
return func(ctx context.Context, oldState *market.State, newState *market.State) (changed bool, user UserData, err error) {
if oldState.EscrowTable.Equals(newState.EscrowTable) && oldState.LockedTable.Equals(newState.LockedTable) {
return false, nil, nil
}
ctxStore := &contextStore{
ctx: ctx,
cst: sp.cst,
}
oldEscrowRoot, err := adt.AsBalanceTable(ctxStore, oldState.EscrowTable)
if err != nil {
return false, nil, err
}
oldLockedRoot, err := adt.AsBalanceTable(ctxStore, oldState.LockedTable)
if err != nil {
return false, nil, err
}
newEscrowRoot, err := adt.AsBalanceTable(ctxStore, newState.EscrowTable)
if err != nil {
return false, nil, err
}
newLockedRoot, err := adt.AsBalanceTable(ctxStore, newState.LockedTable)
if err != nil {
return false, nil, err
}
return diffBalances(ctx, BalanceTables{oldEscrowRoot, oldLockedRoot}, BalanceTables{newEscrowRoot, newLockedRoot})
}
}
type DiffAdtArraysFunc func(ctx context.Context, oldDealStateRoot, newDealStateRoot *adt.Array) (changed bool, user UserData, err error)
// OnDealStateChanged calls diffDealStates when the market deal state changes
@ -307,6 +354,57 @@ func (sp *StatePredicates) DealStateChangedForIDs(dealIds []abi.DealID) DiffAdtA
}
}
// ChangedBalances is a set of changes to deal state
type ChangedBalances map[address.Address]BalanceChange
// BalanceChange is a change in balance from -> to
type BalanceChange struct {
From abi.TokenAmount
To abi.TokenAmount
}
// AvailableBalanceChangedForAddresses detects changes in the escrow table for the given addresses
func (sp *StatePredicates) AvailableBalanceChangedForAddresses(getAddrs func() []address.Address) DiffBalanceTablesFunc {
return func(ctx context.Context, oldBalances, newBalances BalanceTables) (changed bool, user UserData, err error) {
changedBalances := make(ChangedBalances)
addrs := getAddrs()
for _, addr := range addrs {
// If the deal has been removed, we just set it to nil
oldEscrowBalance, err := oldBalances.EscrowTable.Get(addr)
if err != nil {
return false, nil, err
}
oldLockedBalance, err := oldBalances.LockedTable.Get(addr)
if err != nil {
return false, nil, err
}
oldBalance := big.Sub(oldEscrowBalance, oldLockedBalance)
newEscrowBalance, err := newBalances.EscrowTable.Get(addr)
if err != nil {
return false, nil, err
}
newLockedBalance, err := newBalances.LockedTable.Get(addr)
if err != nil {
return false, nil, err
}
newBalance := big.Sub(newEscrowBalance, newLockedBalance)
if !oldBalance.Equals(newBalance) {
changedBalances[addr] = BalanceChange{oldBalance, newBalance}
}
}
if len(changedBalances) > 0 {
return true, changedBalances, nil
}
return false, nil, nil
}
}
type DiffMinerActorStateFunc func(ctx context.Context, oldState *miner.State, newState *miner.State) (changed bool, user UserData, err error)
func (sp *StatePredicates) OnMinerActorChange(minerAddr address.Address, diffMinerActorState DiffMinerActorStateFunc) DiffTipSetKeyFunc {
@ -493,3 +591,40 @@ func (sp *StatePredicates) OnMinerPreCommitChange() DiffMinerActorStateFunc {
return true, precommitChanges, nil
}
}
// DiffPaymentChannelStateFunc is function that compares two states for the payment channel
type DiffPaymentChannelStateFunc func(ctx context.Context, oldState *paych.State, newState *paych.State) (changed bool, user UserData, err error)
// OnPaymentChannelActorChanged calls diffPaymentChannelState when the state changes for the the payment channel actor
func (sp *StatePredicates) OnPaymentChannelActorChanged(paychAddr address.Address, diffPaymentChannelState DiffPaymentChannelStateFunc) DiffTipSetKeyFunc {
return sp.OnActorStateChanged(paychAddr, func(ctx context.Context, oldActorStateHead, newActorStateHead cid.Cid) (changed bool, user UserData, err error) {
var oldState paych.State
if err := sp.cst.Get(ctx, oldActorStateHead, &oldState); err != nil {
return false, nil, err
}
var newState paych.State
if err := sp.cst.Get(ctx, newActorStateHead, &newState); err != nil {
return false, nil, err
}
return diffPaymentChannelState(ctx, &oldState, &newState)
})
}
// PayChToSendChange is a difference in the amount to send on a payment channel when the money is collected
type PayChToSendChange struct {
OldToSend abi.TokenAmount
NewToSend abi.TokenAmount
}
// OnToSendAmountChanges monitors changes on the total amount to send from one party to the other on a payment channel
func (sp *StatePredicates) OnToSendAmountChanges() DiffPaymentChannelStateFunc {
return func(ctx context.Context, oldState *paych.State, newState *paych.State) (changed bool, user UserData, err error) {
if oldState.ToSend.Equals(newState.ToSend) {
return false, nil, nil
}
return true, &PayChToSendChange{
OldToSend: oldState.ToSend,
NewToSend: newState.ToSend,
}, nil
}
}

View File

@ -4,18 +4,15 @@ import (
"context"
"testing"
"github.com/filecoin-project/go-bitfield"
"github.com/stretchr/testify/require"
"golang.org/x/xerrors"
"github.com/ipfs/go-cid"
ds "github.com/ipfs/go-datastore"
ds_sync "github.com/ipfs/go-datastore/sync"
"github.com/ipfs/go-hamt-ipld"
bstore "github.com/ipfs/go-ipfs-blockstore"
cbornode "github.com/ipfs/go-ipld-cbor"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-amt-ipld/v2"
"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/market"
@ -25,6 +22,7 @@ import (
tutils "github.com/filecoin-project/specs-actors/support/testing"
"github.com/filecoin-project/lotus/chain/types"
bstore "github.com/filecoin-project/lotus/lib/blockstore"
)
var dummyCid cid.Cid
@ -68,8 +66,8 @@ func (m mockAPI) setActor(tsk types.TipSetKey, act *types.Actor) {
func TestMarketPredicates(t *testing.T) {
ctx := context.Background()
bs := bstore.NewBlockstore(ds_sync.MutexWrap(ds.NewMapDatastore()))
store := cbornode.NewCborStore(bs)
bs := bstore.NewTemporarySync()
store := adt.WrapStore(ctx, cbornode.NewCborStore(bs))
oldDeal1 := &market.DealState{
SectorStartEpoch: 1,
@ -115,7 +113,14 @@ func TestMarketPredicates(t *testing.T) {
abi.DealID(2): oldProp2,
}
oldStateC := createMarketState(ctx, t, store, oldDeals, oldProps)
oldBalances := map[address.Address]balance{
tutils.NewIDAddr(t, 1): balance{abi.NewTokenAmount(1000), abi.NewTokenAmount(1000)},
tutils.NewIDAddr(t, 2): balance{abi.NewTokenAmount(2000), abi.NewTokenAmount(500)},
tutils.NewIDAddr(t, 3): balance{abi.NewTokenAmount(3000), abi.NewTokenAmount(2000)},
tutils.NewIDAddr(t, 5): balance{abi.NewTokenAmount(3000), abi.NewTokenAmount(1000)},
}
oldStateC := createMarketState(ctx, t, store, oldDeals, oldProps, oldBalances)
newDeal1 := &market.DealState{
SectorStartEpoch: 1,
@ -156,7 +161,14 @@ func TestMarketPredicates(t *testing.T) {
abi.DealID(3): newProp3, // new
// NB: DealProposals cannot be modified, so don't test that case.
}
newStateC := createMarketState(ctx, t, store, newDeals, newProps)
newBalances := map[address.Address]balance{
tutils.NewIDAddr(t, 1): balance{abi.NewTokenAmount(3000), abi.NewTokenAmount(0)},
tutils.NewIDAddr(t, 2): balance{abi.NewTokenAmount(2000), abi.NewTokenAmount(500)},
tutils.NewIDAddr(t, 4): balance{abi.NewTokenAmount(5000), abi.NewTokenAmount(0)},
tutils.NewIDAddr(t, 5): balance{abi.NewTokenAmount(1000), abi.NewTokenAmount(3000)},
}
newStateC := createMarketState(ctx, t, store, newDeals, newProps, newBalances)
minerAddr, err := address.NewFromString("t00")
require.NoError(t, err)
@ -279,12 +291,69 @@ func TestMarketPredicates(t *testing.T) {
require.Equal(t, abi.DealID(2), changedProps.Removed[0].ID)
require.Equal(t, *oldProp2, changedProps.Removed[0].Proposal)
})
t.Run("balances predicate", func(t *testing.T) {
preds := NewStatePredicates(api)
getAddresses := func() []address.Address {
return []address.Address{tutils.NewIDAddr(t, 1), tutils.NewIDAddr(t, 2), tutils.NewIDAddr(t, 3), tutils.NewIDAddr(t, 4)}
}
diffBalancesFn := preds.OnStorageMarketActorChanged(preds.OnBalanceChanged(preds.AvailableBalanceChangedForAddresses(getAddresses)))
// Diff a state against itself: expect no change
changed, _, err := diffBalancesFn(ctx, oldState.Key(), oldState.Key())
require.NoError(t, err)
require.False(t, changed)
// Diff old state against new state
changed, valIDs, err := diffBalancesFn(ctx, oldState.Key(), newState.Key())
require.NoError(t, err)
require.True(t, changed)
changedBalances, ok := valIDs.(ChangedBalances)
require.True(t, ok)
require.Len(t, changedBalances, 3)
require.Contains(t, changedBalances, tutils.NewIDAddr(t, 1))
require.Contains(t, changedBalances, tutils.NewIDAddr(t, 3))
require.Contains(t, changedBalances, tutils.NewIDAddr(t, 4))
balance1 := changedBalances[tutils.NewIDAddr(t, 1)]
if !balance1.From.Equals(abi.NewTokenAmount(1000)) || !balance1.To.Equals(abi.NewTokenAmount(3000)) {
t.Fatal("Unexpected change to balance")
}
balance3 := changedBalances[tutils.NewIDAddr(t, 3)]
if !balance3.From.Equals(abi.NewTokenAmount(3000)) || !balance3.To.Equals(abi.NewTokenAmount(0)) {
t.Fatal("Unexpected change to balance")
}
balance4 := changedBalances[tutils.NewIDAddr(t, 4)]
if !balance4.From.Equals(abi.NewTokenAmount(0)) || !balance4.To.Equals(abi.NewTokenAmount(5000)) {
t.Fatal("Unexpected change to balance")
}
// Diff with non-existent address.
getNoAddress := func() []address.Address { return []address.Address{tutils.NewIDAddr(t, 6)} }
diffNoAddressFn := preds.OnStorageMarketActorChanged(preds.OnBalanceChanged(preds.AvailableBalanceChangedForAddresses(getNoAddress)))
changed, _, err = diffNoAddressFn(ctx, oldState.Key(), newState.Key())
require.NoError(t, err)
require.False(t, changed)
// Test that OnBalanceChanged does not call the callback if the state has not changed
diffDealBalancesFn := preds.OnBalanceChanged(func(context.Context, BalanceTables, BalanceTables) (bool, UserData, error) {
t.Fatal("No state change so this should not be called")
return false, nil, nil
})
marketState := createEmptyMarketState(t, store)
changed, _, err = diffDealBalancesFn(ctx, marketState, marketState)
require.NoError(t, err)
require.False(t, changed)
})
}
func TestMinerSectorChange(t *testing.T) {
ctx := context.Background()
bs := bstore.NewBlockstore(ds_sync.MutexWrap(ds.NewMapDatastore()))
store := cbornode.NewCborStore(bs)
bs := bstore.NewTemporarySync()
store := adt.WrapStore(ctx, cbornode.NewCborStore(bs))
nextID := uint64(0)
nextIDAddrF := func() address.Address {
@ -376,50 +445,82 @@ func mockTipset(minerAddr address.Address, timestamp uint64) (*types.TipSet, err
}})
}
func createMarketState(ctx context.Context, t *testing.T, store *cbornode.BasicIpldStore, deals map[abi.DealID]*market.DealState, props map[abi.DealID]*market.DealProposal) cid.Cid {
type balance struct {
available abi.TokenAmount
locked abi.TokenAmount
}
func createMarketState(ctx context.Context, t *testing.T, store adt.Store, deals map[abi.DealID]*market.DealState, props map[abi.DealID]*market.DealProposal, balances map[address.Address]balance) cid.Cid {
dealRootCid := createDealAMT(ctx, t, store, deals)
propRootCid := createProposalAMT(ctx, t, store, props)
balancesCids := createBalanceTable(ctx, t, store, balances)
state := createEmptyMarketState(t, store)
state.States = dealRootCid
state.Proposals = propRootCid
state.EscrowTable = balancesCids[0]
state.LockedTable = balancesCids[1]
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())
func createEmptyMarketState(t *testing.T, store adt.Store) *market.State {
emptyArrayCid, err := adt.MakeEmptyArray(store).Root()
require.NoError(t, err)
emptyMap, err := store.Put(context.TODO(), hamt.NewNode(store, hamt.UseTreeBitWidth(5)))
emptyMap, err := adt.MakeEmptyMap(store).Root()
require.NoError(t, err)
return market.ConstructState(emptyArrayCid, emptyMap, emptyMap)
}
func createDealAMT(ctx context.Context, t *testing.T, store *cbornode.BasicIpldStore, deals map[abi.DealID]*market.DealState) cid.Cid {
root := amt.NewAMT(store)
func createDealAMT(ctx context.Context, t *testing.T, store adt.Store, deals map[abi.DealID]*market.DealState) cid.Cid {
root := adt.MakeEmptyArray(store)
for dealID, dealState := range deals {
err := root.Set(ctx, uint64(dealID), dealState)
err := root.Set(uint64(dealID), dealState)
require.NoError(t, err)
}
rootCid, err := root.Flush(ctx)
rootCid, err := root.Root()
require.NoError(t, err)
return rootCid
}
func createProposalAMT(ctx context.Context, t *testing.T, store *cbornode.BasicIpldStore, props map[abi.DealID]*market.DealProposal) cid.Cid {
root := amt.NewAMT(store)
func createProposalAMT(ctx context.Context, t *testing.T, store adt.Store, props map[abi.DealID]*market.DealProposal) cid.Cid {
root := adt.MakeEmptyArray(store)
for dealID, prop := range props {
err := root.Set(ctx, uint64(dealID), prop)
err := root.Set(uint64(dealID), prop)
require.NoError(t, err)
}
rootCid, err := root.Flush(ctx)
rootCid, err := root.Root()
require.NoError(t, err)
return rootCid
}
func createMinerState(ctx context.Context, t *testing.T, store *cbornode.BasicIpldStore, owner, worker address.Address, sectors []miner.SectorOnChainInfo) cid.Cid {
func createBalanceTable(ctx context.Context, t *testing.T, store adt.Store, balances map[address.Address]balance) [2]cid.Cid {
escrowMapRoot := adt.MakeEmptyMap(store)
escrowMapRootCid, err := escrowMapRoot.Root()
require.NoError(t, err)
escrowRoot, err := adt.AsBalanceTable(store, escrowMapRootCid)
require.NoError(t, err)
lockedMapRoot := adt.MakeEmptyMap(store)
lockedMapRootCid, err := lockedMapRoot.Root()
require.NoError(t, err)
lockedRoot, err := adt.AsBalanceTable(store, lockedMapRootCid)
for addr, balance := range balances {
err := escrowRoot.Add(addr, big.Add(balance.available, balance.locked))
require.NoError(t, err)
err = lockedRoot.Add(addr, balance.locked)
require.NoError(t, err)
}
escrowRootCid, err := escrowRoot.Root()
require.NoError(t, err)
lockedRootCid, err := lockedRoot.Root()
require.NoError(t, err)
return [2]cid.Cid{escrowRootCid, lockedRootCid}
}
func createMinerState(ctx context.Context, t *testing.T, store adt.Store, owner, worker address.Address, sectors []miner.SectorOnChainInfo) cid.Cid {
rootCid := createSectorsAMT(ctx, t, store, sectors)
state := createEmptyMinerState(ctx, t, store, owner, worker)
@ -430,10 +531,10 @@ func createMinerState(ctx context.Context, t *testing.T, store *cbornode.BasicIp
return stateC
}
func createEmptyMinerState(ctx context.Context, t *testing.T, store *cbornode.BasicIpldStore, owner, worker address.Address) *miner.State {
emptyArrayCid, err := amt.NewAMT(store).Flush(context.TODO())
func createEmptyMinerState(ctx context.Context, t *testing.T, store adt.Store, owner, worker address.Address) *miner.State {
emptyArrayCid, err := adt.MakeEmptyArray(store).Root()
require.NoError(t, err)
emptyMap, err := store.Put(context.TODO(), hamt.NewNode(store, hamt.UseTreeBitWidth(5)))
emptyMap, err := adt.MakeEmptyMap(store).Root()
require.NoError(t, err)
emptyDeadline, err := store.Put(context.TODO(), &miner.Deadline{
@ -451,20 +552,24 @@ func createEmptyMinerState(ctx context.Context, t *testing.T, store *cbornode.Ba
minerInfo := emptyMap
state, err := miner.ConstructState(minerInfo, 123, emptyArrayCid, emptyMap, emptyDeadlinesCid)
emptyBitfield := bitfield.NewFromSet(nil)
emptyBitfieldCid, err := store.Put(context.Background(), emptyBitfield)
require.NoError(t, err)
state, err := miner.ConstructState(minerInfo, 123, emptyBitfieldCid, emptyArrayCid, emptyMap, emptyDeadlinesCid)
require.NoError(t, err)
return state
}
func createSectorsAMT(ctx context.Context, t *testing.T, store *cbornode.BasicIpldStore, sectors []miner.SectorOnChainInfo) cid.Cid {
root := amt.NewAMT(store)
func createSectorsAMT(ctx context.Context, t *testing.T, store adt.Store, sectors []miner.SectorOnChainInfo) cid.Cid {
root := adt.MakeEmptyArray(store)
for _, sector := range sectors {
sector := sector
err := root.Set(ctx, uint64(sector.SectorNumber), &sector)
err := root.Set(uint64(sector.SectorNumber), &sector)
require.NoError(t, err)
}
rootCid, err := root.Flush(ctx)
rootCid, err := root.Root()
require.NoError(t, err)
return rootCid
}
@ -479,10 +584,12 @@ func newSectorOnChainInfo(sectorNo abi.SectorNumber, sealed cid.Cid, weight big.
DealIDs: info.DealIDs,
Expiration: info.Expiration,
Activation: activation,
DealWeight: weight,
VerifiedDealWeight: weight,
InitialPledge: big.Zero(),
Activation: activation,
DealWeight: weight,
VerifiedDealWeight: weight,
InitialPledge: big.Zero(),
ExpectedDayReward: big.Zero(),
ExpectedStoragePledge: big.Zero(),
}
}

View File

@ -10,12 +10,12 @@ import (
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
saminer "github.com/filecoin-project/specs-actors/actors/builtin/miner"
"github.com/filecoin-project/specs-actors/actors/crypto"
block "github.com/ipfs/go-block-format"
"github.com/ipfs/go-blockservice"
"github.com/ipfs/go-cid"
blockstore "github.com/ipfs/go-ipfs-blockstore"
offline "github.com/ipfs/go-ipfs-exchange-offline"
format "github.com/ipfs/go-ipld-format"
logging "github.com/ipfs/go-log/v2"
@ -36,6 +36,7 @@ import (
"github.com/filecoin-project/lotus/cmd/lotus-seed/seed"
"github.com/filecoin-project/lotus/genesis"
"github.com/filecoin-project/lotus/journal"
"github.com/filecoin-project/lotus/lib/blockstore"
"github.com/filecoin-project/lotus/lib/sigs"
"github.com/filecoin-project/lotus/node/repo"
"github.com/filecoin-project/sector-storage/ffiwrapper"
@ -92,6 +93,21 @@ func (m mybs) Get(c cid.Cid) (block.Block, error) {
return b, nil
}
var rootkey, _ = address.NewIDAddress(80)
var rootkeyMultisig = genesis.MultisigMeta{
Signers: []address.Address{rootkey},
Threshold: 1,
VestingDuration: 0,
VestingStart: 0,
}
var DefaultVerifregRootkeyActor = genesis.Actor{
Type: genesis.TMultisig,
Balance: big.NewInt(0),
Meta: rootkeyMultisig.ActorMeta(),
}
func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) {
saminer.SupportedProofTypes = map[abi.RegisteredSealProof]struct{}{
abi.RegisteredSealProof_StackedDrg2KiBV1: {},
@ -113,7 +129,7 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) {
return nil, xerrors.Errorf("failed to get blocks datastore: %w", err)
}
bs := mybs{blockstore.NewIdStore(blockstore.NewBlockstore(bds))}
bs := mybs{blockstore.NewBlockstore(bds)}
ks, err := lr.KeyStore()
if err != nil {
@ -195,8 +211,9 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) {
*genm1,
*genm2,
},
NetworkName: "",
Timestamp: uint64(build.Clock.Now().Add(-500 * time.Duration(build.BlockDelaySecs) * time.Second).Unix()),
VerifregRootKey: DefaultVerifregRootkeyActor,
NetworkName: "",
Timestamp: uint64(build.Clock.Now().Add(-500 * time.Duration(build.BlockDelaySecs) * time.Second).Unix()),
}
genb, err := genesis2.MakeGenesisBlock(context.TODO(), bs, sys, tpl)
@ -362,6 +379,10 @@ func (cg *ChainGen) NextTipSet() (*MinedTipSet, error) {
return mts, nil
}
func (cg *ChainGen) SetWinningPoStProver(m address.Address, wpp WinningPoStProver) {
cg.eppProvs[m] = wpp
}
func (cg *ChainGen) NextTipSetFromMiners(base *types.TipSet, miners []address.Address) (*MinedTipSet, error) {
var blks []*types.FullBlock
@ -469,8 +490,9 @@ func getRandomMessages(cg *ChainGen) ([]*types.SignedMessage, error) {
Method: 0,
GasLimit: 100_000_000,
GasPrice: types.NewInt(0),
GasLimit: 100_000_000,
GasFeeCap: types.NewInt(0),
GasPremium: types.NewInt(0),
}
sig, err := cg.w.Sign(context.TODO(), cg.banker, msg.Cid().Bytes())

View File

@ -4,30 +4,32 @@ import (
"context"
"encoding/json"
"github.com/filecoin-project/go-amt-ipld/v2"
"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/account"
"github.com/filecoin-project/specs-actors/actors/builtin/multisig"
"github.com/filecoin-project/specs-actors/actors/builtin/verifreg"
"github.com/filecoin-project/specs-actors/actors/runtime"
"github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-datastore"
bstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
logging "github.com/ipfs/go-log/v2"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"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/account"
"github.com/filecoin-project/specs-actors/actors/builtin/multisig"
"github.com/filecoin-project/specs-actors/actors/builtin/verifreg"
"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/lotus/genesis"
"github.com/filecoin-project/lotus/journal"
bstore "github.com/filecoin-project/lotus/lib/blockstore"
"github.com/filecoin-project/lotus/lib/sigs"
)
const AccountStart = 100
@ -102,91 +104,91 @@ Genesis: {
*/
func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template genesis.Template) (*state.StateTree, error) {
func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template genesis.Template) (*state.StateTree, map[address.Address]address.Address, error) {
// Create empty state tree
cst := cbor.NewCborStore(bs)
_, err := cst.Put(context.TODO(), []struct{}{})
if err != nil {
return nil, xerrors.Errorf("putting empty object: %w", err)
return nil, nil, xerrors.Errorf("putting empty object: %w", err)
}
state, err := state.NewStateTree(cst)
if err != nil {
return nil, xerrors.Errorf("making new state tree: %w", err)
return nil, nil, xerrors.Errorf("making new state tree: %w", err)
}
emptyobject, err := cst.Put(context.TODO(), []struct{}{})
if err != nil {
return nil, xerrors.Errorf("failed putting empty object: %w", err)
return nil, nil, xerrors.Errorf("failed putting empty object: %w", err)
}
// Create system actor
sysact, err := SetupSystemActor(bs)
if err != nil {
return nil, xerrors.Errorf("setup init actor: %w", err)
return nil, nil, xerrors.Errorf("setup init actor: %w", err)
}
if err := state.SetActor(builtin.SystemActorAddr, sysact); err != nil {
return nil, xerrors.Errorf("set init actor: %w", err)
return nil, nil, xerrors.Errorf("set init actor: %w", err)
}
// Create init actor
initact, err := SetupInitActor(bs, template.NetworkName, template.Accounts)
initact, keyIDs, err := SetupInitActor(bs, template.NetworkName, template.Accounts, template.VerifregRootKey)
if err != nil {
return nil, xerrors.Errorf("setup init actor: %w", err)
return nil, nil, xerrors.Errorf("setup init actor: %w", err)
}
if err := state.SetActor(builtin.InitActorAddr, initact); err != nil {
return nil, xerrors.Errorf("set init actor: %w", err)
return nil, nil, xerrors.Errorf("set init actor: %w", err)
}
// Setup reward
// RewardActor's state is overrwritten by SetupStorageMiners
rewact, err := SetupRewardActor(bs, big.Zero())
if err != nil {
return nil, xerrors.Errorf("setup init actor: %w", err)
return nil, nil, xerrors.Errorf("setup init actor: %w", err)
}
err = state.SetActor(builtin.RewardActorAddr, rewact)
if err != nil {
return nil, xerrors.Errorf("set network account actor: %w", err)
return nil, nil, xerrors.Errorf("set network account actor: %w", err)
}
// Setup cron
cronact, err := SetupCronActor(bs)
if err != nil {
return nil, xerrors.Errorf("setup cron actor: %w", err)
return nil, nil, xerrors.Errorf("setup cron actor: %w", err)
}
if err := state.SetActor(builtin.CronActorAddr, cronact); err != nil {
return nil, xerrors.Errorf("set cron actor: %w", err)
return nil, nil, xerrors.Errorf("set cron actor: %w", err)
}
// Create empty power actor
spact, err := SetupStoragePowerActor(bs)
if err != nil {
return nil, xerrors.Errorf("setup storage market actor: %w", err)
return nil, nil, xerrors.Errorf("setup storage market actor: %w", err)
}
if err := state.SetActor(builtin.StoragePowerActorAddr, spact); err != nil {
return nil, xerrors.Errorf("set storage market actor: %w", err)
return nil, nil, xerrors.Errorf("set storage market actor: %w", err)
}
// Create empty market actor
marketact, err := SetupStorageMarketActor(bs)
if err != nil {
return nil, xerrors.Errorf("setup storage market actor: %w", err)
return nil, nil, xerrors.Errorf("setup storage market actor: %w", err)
}
if err := state.SetActor(builtin.StorageMarketActorAddr, marketact); err != nil {
return nil, xerrors.Errorf("set market actor: %w", err)
return nil, nil, xerrors.Errorf("set market actor: %w", err)
}
// Create verified registry
verifact, err := SetupVerifiedRegistryActor(bs)
if err != nil {
return nil, xerrors.Errorf("setup storage market actor: %w", err)
return nil, nil, xerrors.Errorf("setup storage market actor: %w", err)
}
if err := state.SetActor(builtin.VerifiedRegistryActorAddr, verifact); err != nil {
return nil, xerrors.Errorf("set market actor: %w", err)
return nil, nil, xerrors.Errorf("set market actor: %w", err)
}
// Setup burnt-funds
@ -196,124 +198,174 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge
Head: emptyobject,
})
if err != nil {
return nil, xerrors.Errorf("set burnt funds account actor: %w", err)
return nil, nil, xerrors.Errorf("set burnt funds account actor: %w", err)
}
// Create accounts
for id, info := range template.Accounts {
if info.Type != genesis.TAccount && info.Type != genesis.TMultisig {
return nil, xerrors.New("unsupported account type")
return nil, nil, xerrors.New("unsupported account type")
}
ida, err := address.NewIDAddress(uint64(AccountStart + id))
if err != nil {
return nil, err
return nil, nil, err
}
// var newAddress address.Address
if info.Type == genesis.TAccount {
var ainfo genesis.AccountMeta
if err := json.Unmarshal(info.Meta, &ainfo); err != nil {
return nil, xerrors.Errorf("unmarshaling account meta: %w", err)
}
st, err := cst.Put(ctx, &account.State{Address: ainfo.Owner})
if err != nil {
return nil, err
}
err = state.SetActor(ida, &types.Actor{
Code: builtin.AccountActorCodeID,
Balance: info.Balance,
Head: st,
})
if err != nil {
return nil, xerrors.Errorf("setting account from actmap: %w", err)
}
} else if info.Type == genesis.TMultisig {
var ainfo genesis.MultisigMeta
if err := json.Unmarshal(info.Meta, &ainfo); err != nil {
return nil, xerrors.Errorf("unmarshaling account meta: %w", err)
}
pending, err := adt.MakeEmptyMap(adt.WrapStore(ctx, cst)).Root()
if err != nil {
return nil, xerrors.Errorf("failed to create empty map: %v", err)
}
st, err := cst.Put(ctx, &multisig.State{
Signers: ainfo.Signers,
NumApprovalsThreshold: uint64(ainfo.Threshold),
StartEpoch: abi.ChainEpoch(ainfo.VestingStart),
UnlockDuration: abi.ChainEpoch(ainfo.VestingDuration),
PendingTxns: pending,
InitialBalance: info.Balance,
})
if err != nil {
return nil, err
}
err = state.SetActor(ida, &types.Actor{
Code: builtin.MultisigActorCodeID,
Balance: info.Balance,
Head: st,
})
if err != nil {
return nil, xerrors.Errorf("setting account from actmap: %w", err)
}
if err = createAccount(ctx, bs, cst, state, ida, info); err != nil {
return nil, nil, err
}
}
vregroot, err := address.NewIDAddress(80)
if err != nil {
return nil, err
return nil, nil, err
}
vrst, err := cst.Put(ctx, &account.State{Address: RootVerifierAddr})
if err = createAccount(ctx, bs, cst, state, vregroot, template.VerifregRootKey); err != nil {
return nil, nil, err
}
// Setup the first verifier as ID-address 81
// TODO: remove this
skBytes, err := sigs.Generate(crypto.SigTypeBLS)
if err != nil {
return nil, err
return nil, nil, xerrors.Errorf("creating random verifier secret key: %w", err)
}
err = state.SetActor(vregroot, &types.Actor{
verifierPk, err := sigs.ToPublic(crypto.SigTypeBLS, skBytes)
if err != nil {
return nil, nil, xerrors.Errorf("creating random verifier public key: %w", err)
}
verifierAd, err := address.NewBLSAddress(verifierPk)
if err != nil {
return nil, nil, xerrors.Errorf("creating random verifier address: %w", err)
}
verifierId, err := address.NewIDAddress(81)
if err != nil {
return nil, nil, err
}
verifierState, err := cst.Put(ctx, &account.State{Address: verifierAd})
if err != nil {
return nil, nil, err
}
err = state.SetActor(verifierId, &types.Actor{
Code: builtin.AccountActorCodeID,
Balance: types.NewInt(0),
Head: vrst,
Head: verifierState,
})
if err != nil {
return nil, xerrors.Errorf("setting account from actmap: %w", err)
return nil, nil, xerrors.Errorf("setting account from actmap: %w", err)
}
return state, nil
return state, keyIDs, nil
}
func VerifyPreSealedData(ctx context.Context, cs *store.ChainStore, stateroot cid.Cid, template genesis.Template) (cid.Cid, error) {
verifNeeds := make(map[address.Address]abi.PaddedPieceSize)
var sum abi.PaddedPieceSize
for _, m := range template.Miners {
for _, s := range m.Sectors {
amt := s.Deal.PieceSize
verifNeeds[s.Deal.Client] += amt
sum += amt
func createAccount(ctx context.Context, bs bstore.Blockstore, cst cbor.IpldStore, state *state.StateTree, ida address.Address, info genesis.Actor) error {
if info.Type == genesis.TAccount {
var ainfo genesis.AccountMeta
if err := json.Unmarshal(info.Meta, &ainfo); err != nil {
return xerrors.Errorf("unmarshaling account meta: %w", err)
}
st, err := cst.Put(ctx, &account.State{Address: ainfo.Owner})
if err != nil {
return err
}
err = state.SetActor(ida, &types.Actor{
Code: builtin.AccountActorCodeID,
Balance: info.Balance,
Head: st,
})
if err != nil {
return xerrors.Errorf("setting account from actmap: %w", err)
}
} else if info.Type == genesis.TMultisig {
var ainfo genesis.MultisigMeta
if err := json.Unmarshal(info.Meta, &ainfo); err != nil {
return xerrors.Errorf("unmarshaling account meta: %w", err)
}
pending, err := adt.MakeEmptyMap(adt.WrapStore(ctx, cst)).Root()
if err != nil {
return xerrors.Errorf("failed to create empty map: %v", err)
}
st, err := cst.Put(ctx, &multisig.State{
Signers: ainfo.Signers,
NumApprovalsThreshold: uint64(ainfo.Threshold),
StartEpoch: abi.ChainEpoch(ainfo.VestingStart),
UnlockDuration: abi.ChainEpoch(ainfo.VestingDuration),
PendingTxns: pending,
InitialBalance: info.Balance,
})
if err != nil {
return err
}
err = state.SetActor(ida, &types.Actor{
Code: builtin.MultisigActorCodeID,
Balance: info.Balance,
Head: st,
})
if err != nil {
return xerrors.Errorf("setting account from actmap: %w", err)
}
}
verifier, err := address.NewIDAddress(80)
if err != nil {
return cid.Undef, err
}
return nil
}
vm, err := vm.NewVM(stateroot, 0, &fakeRand{}, cs.Blockstore(), &fakedSigSyscalls{cs.VMSys()})
func VerifyPreSealedData(ctx context.Context, cs *store.ChainStore, stateroot cid.Cid, template genesis.Template, keyIDs map[address.Address]address.Address) (cid.Cid, error) {
verifNeeds := make(map[address.Address]abi.PaddedPieceSize)
var sum abi.PaddedPieceSize
vmopt := vm.VMOpts{
StateBase: stateroot,
Epoch: 0,
Rand: &fakeRand{},
Bstore: cs.Blockstore(),
Syscalls: mkFakedSigSyscalls(cs.VMSys()),
VestedCalc: nil,
BaseFee: types.NewInt(0),
}
vm, err := vm.NewVM(&vmopt)
if err != nil {
return cid.Undef, xerrors.Errorf("failed to create NewVM: %w", err)
}
_, err = doExecValue(ctx, vm, builtin.VerifiedRegistryActorAddr, RootVerifierAddr, types.NewInt(0), builtin.MethodsVerifiedRegistry.AddVerifier, mustEnc(&verifreg.AddVerifierParams{
for _, m := range template.Miners {
// Add the miner to the market actor's balance table
_, err = doExec(ctx, vm, builtin.StorageMarketActorAddr, m.Owner, builtin.MethodsMarket.AddBalance, mustEnc(adt.Empty))
for _, s := range m.Sectors {
amt := s.Deal.PieceSize
verifNeeds[keyIDs[s.Deal.Client]] += amt
sum += amt
}
}
verifregRoot, err := address.NewIDAddress(80)
if err != nil {
return cid.Undef, err
}
verifier, err := address.NewIDAddress(81)
if err != nil {
return cid.Undef, err
}
_, err = doExecValue(ctx, vm, builtin.VerifiedRegistryActorAddr, verifregRoot, types.NewInt(0), builtin.MethodsVerifiedRegistry.AddVerifier, mustEnc(&verifreg.AddVerifierParams{
Address: verifier,
Allowance: abi.NewStoragePower(int64(sum)), // eh, close enough
}))
if err != nil {
return cid.Undef, xerrors.Errorf("failed to failed to create verifier: %w", err)
return cid.Undef, xerrors.Errorf("failed to create verifier: %w", err)
}
for c, amt := range verifNeeds {
@ -334,8 +386,8 @@ func VerifyPreSealedData(ctx context.Context, cs *store.ChainStore, stateroot ci
return st, nil
}
func MakeGenesisBlock(ctx context.Context, bs bstore.Blockstore, sys runtime.Syscalls, template genesis.Template) (*GenesisBootstrap, error) {
st, err := MakeInitialStateTree(ctx, bs, template)
func MakeGenesisBlock(ctx context.Context, bs bstore.Blockstore, sys vm.SyscallBuilder, template genesis.Template) (*GenesisBootstrap, error) {
st, keyIDs, err := MakeInitialStateTree(ctx, bs, template)
if err != nil {
return nil, xerrors.Errorf("make initial state tree failed: %w", err)
}
@ -349,7 +401,7 @@ func MakeGenesisBlock(ctx context.Context, bs bstore.Blockstore, sys runtime.Sys
cs := store.NewChainStore(bs, datastore.NewMapDatastore(), sys, journal.NilJournal())
// Verify PreSealed Data
stateroot, err = VerifyPreSealedData(ctx, cs, stateroot, template)
stateroot, err = VerifyPreSealedData(ctx, cs, stateroot, template, keyIDs)
if err != nil {
return nil, xerrors.Errorf("failed to verify presealed data: %w", err)
}
@ -359,9 +411,8 @@ func MakeGenesisBlock(ctx context.Context, bs bstore.Blockstore, sys runtime.Sys
return nil, xerrors.Errorf("setup miners failed: %w", err)
}
cst := cbor.NewCborStore(bs)
emptyroot, err := amt.FromArray(ctx, cst, nil)
store := adt.WrapStore(ctx, cbor.NewCborStore(bs))
emptyroot, err := adt.MakeEmptyArray(store).Root()
if err != nil {
return nil, xerrors.Errorf("amt build failed: %w", err)
}
@ -403,6 +454,7 @@ func MakeGenesisBlock(ctx context.Context, bs bstore.Blockstore, sys runtime.Sys
Data: make([]byte, 32),
},
},
ParentBaseFee: abi.NewTokenAmount(build.InitialBaseFee),
}
sb, err := b.ToStorageBlock()

View File

@ -6,6 +6,8 @@ import (
"fmt"
"math/rand"
"github.com/filecoin-project/lotus/chain/state"
"github.com/ipfs/go-cid"
cbor "github.com/ipfs/go-ipld-cbor"
cbg "github.com/whyrusleeping/cbor-gen"
@ -46,8 +48,30 @@ func (fss *fakedSigSyscalls) VerifySignature(signature crypto.Signature, signer
return nil
}
func mkFakedSigSyscalls(base vm.SyscallBuilder) vm.SyscallBuilder {
return func(ctx context.Context, cstate *state.StateTree, cst cbor.IpldStore) runtime.Syscalls {
return &fakedSigSyscalls{
base(ctx, cstate, cst),
}
}
}
func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid, miners []genesis.Miner) (cid.Cid, error) {
vm, err := vm.NewVM(sroot, 0, &fakeRand{}, cs.Blockstore(), &fakedSigSyscalls{cs.VMSys()})
vc := func(context.Context, abi.ChainEpoch) (abi.TokenAmount, error) {
return big.Zero(), nil
}
vmopt := &vm.VMOpts{
StateBase: sroot,
Epoch: 0,
Rand: &fakeRand{},
Bstore: cs.Blockstore(),
Syscalls: mkFakedSigSyscalls(cs.VMSys()),
VestedCalc: vc,
BaseFee: types.NewInt(0),
}
vm, err := vm.NewVM(vmopt)
if err != nil {
return cid.Undef, xerrors.Errorf("failed to create NewVM: %w", err)
}
@ -109,20 +133,13 @@ func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid
// Add market funds
{
if m.MarketBalance.GreaterThan(big.Zero()) {
params := mustEnc(&minerInfos[i].maddr)
_, err := doExecValue(ctx, vm, builtin.StorageMarketActorAddr, m.Worker, m.MarketBalance, builtin.MethodsMarket.AddBalance, params)
if err != nil {
return cid.Undef, xerrors.Errorf("failed to create genesis miner: %w", err)
}
}
{
params := mustEnc(&m.Worker)
_, err := doExecValue(ctx, vm, builtin.StorageMarketActorAddr, m.Worker, big.Zero(), builtin.MethodsMarket.AddBalance, params)
if err != nil {
return cid.Undef, xerrors.Errorf("failed to create genesis miner: %w", err)
}
}
// Publish preseal deals
@ -245,7 +262,15 @@ func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid
return cid.Undef, xerrors.Errorf("getting current total power: %w", err)
}
pledge := miner.InitialPledgeForPower(sectorWeight, tpow.QualityAdjPower, epochReward.ThisEpochBaselinePower, tpow.PledgeCollateral, epochReward.ThisEpochReward, circSupply(ctx, vm, minerInfos[i].maddr))
pledge := miner.InitialPledgeForPower(
sectorWeight,
epochReward.ThisEpochBaselinePower,
tpow.PledgeCollateral,
epochReward.ThisEpochRewardSmoothed,
tpow.QualityAdjPowerSmoothed,
circSupply(ctx, vm, minerInfos[i].maddr),
)
fmt.Println(types.FIL(pledge))
_, err = doExecValue(ctx, vm, minerInfos[i].maddr, m.Worker, pledge, builtin.MethodsMiner.PreCommitSector, mustEnc(params))
if err != nil {

View File

@ -2,12 +2,14 @@ package genesis
import (
"context"
"github.com/filecoin-project/specs-actors/actors/builtin/system"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/specs-actors/actors/builtin"
bstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/filecoin-project/lotus/chain/types"
bstore "github.com/filecoin-project/lotus/lib/blockstore"
)
func SetupSystemActor(bs bstore.Blockstore) (*types.Actor, error) {

View File

@ -4,30 +4,34 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/util/adt"
init_ "github.com/filecoin-project/specs-actors/actors/builtin/init"
"github.com/ipfs/go-hamt-ipld"
bstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/genesis"
bstore "github.com/filecoin-project/lotus/lib/blockstore"
)
func SetupInitActor(bs bstore.Blockstore, netname string, initialActors []genesis.Actor) (*types.Actor, error) {
func SetupInitActor(bs bstore.Blockstore, netname string, initialActors []genesis.Actor, rootVerifier genesis.Actor) (*types.Actor, map[address.Address]address.Address, error) {
if len(initialActors) > MaxAccounts {
return nil, xerrors.New("too many initial actors")
return nil, nil, xerrors.New("too many initial actors")
}
var ias init_.State
ias.NextID = MinerStart
ias.NetworkName = netname
cst := cbor.NewCborStore(bs)
amap := hamt.NewNode(cst, hamt.UseTreeBitWidth(5)) // TODO: use spec adt map
store := adt.WrapStore(context.TODO(), cbor.NewCborStore(bs))
amap := adt.MakeEmptyMap(store)
keyToId := map[address.Address]address.Address{}
for i, a := range initialActors {
if a.Type == genesis.TMultisig {
@ -35,38 +39,48 @@ func SetupInitActor(bs bstore.Blockstore, netname string, initialActors []genesi
}
if a.Type != genesis.TAccount {
return nil, xerrors.Errorf("unsupported account type: %s", a.Type) // TODO: Support msig (skip here)
return nil, nil, xerrors.Errorf("unsupported account type: %s", a.Type) // TODO: Support msig (skip here)
}
var ainfo genesis.AccountMeta
if err := json.Unmarshal(a.Meta, &ainfo); err != nil {
return nil, xerrors.Errorf("unmarshaling account meta: %w", err)
return nil, nil, xerrors.Errorf("unmarshaling account meta: %w", err)
}
fmt.Printf("init set %s t0%d\n", ainfo.Owner, AccountStart+uint64(i))
fmt.Printf("init set %s t0%d\n", ainfo.Owner, AccountStart+int64(i))
if err := amap.Set(context.TODO(), string(ainfo.Owner.Bytes()), AccountStart+uint64(i)); err != nil {
return nil, err
value := cbg.CborInt(AccountStart + int64(i))
if err := amap.Put(adt.AddrKey(ainfo.Owner), &value); err != nil {
return nil, nil, err
}
var err error
keyToId[ainfo.Owner], err = address.NewIDAddress(uint64(value))
if err != nil {
return nil, nil, err
}
}
if err := amap.Set(context.TODO(), string(RootVerifierAddr.Bytes()), 80); err != nil {
return nil, err
if rootVerifier.Type == genesis.TAccount {
var ainfo genesis.AccountMeta
if err := json.Unmarshal(rootVerifier.Meta, &ainfo); err != nil {
return nil, nil, xerrors.Errorf("unmarshaling account meta: %w", err)
}
value := cbg.CborInt(80)
if err := amap.Put(adt.AddrKey(ainfo.Owner), &value); err != nil {
return nil, nil, err
}
}
if err := amap.Flush(context.TODO()); err != nil {
return nil, err
}
amapcid, err := cst.Put(context.TODO(), amap)
amapaddr, err := amap.Root()
if err != nil {
return nil, err
return nil, nil, err
}
ias.AddressMap = amapaddr
ias.AddressMap = amapcid
statecid, err := cst.Put(context.TODO(), &ias)
statecid, err := store.Put(store.Context(), &ias)
if err != nil {
return nil, err
return nil, nil, err
}
act := &types.Actor{
@ -74,5 +88,5 @@ func SetupInitActor(bs bstore.Blockstore, netname string, initialActors []genesi
Head: statecid,
}
return act, nil
return act, keyToId, nil
}

View File

@ -5,12 +5,13 @@ import (
"github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/builtin/reward"
bstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/types"
bstore "github.com/filecoin-project/lotus/lib/blockstore"
)
func SetupRewardActor(bs bstore.Blockstore, qaPower big.Int) (*types.Actor, error) {

View File

@ -5,10 +5,10 @@ import (
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/builtin/cron"
bstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/filecoin-project/lotus/chain/types"
bstore "github.com/filecoin-project/lotus/lib/blockstore"
)
func SetupCronActor(bs bstore.Blockstore) (*types.Actor, error) {

View File

@ -2,41 +2,37 @@ package genesis
import (
"context"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/filecoin-project/specs-actors/actors/builtin/power"
"github.com/ipfs/go-hamt-ipld"
bstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/filecoin-project/lotus/chain/types"
bstore "github.com/filecoin-project/lotus/lib/blockstore"
)
func SetupStoragePowerActor(bs bstore.Blockstore) (*types.Actor, error) {
ctx := context.TODO()
cst := cbor.NewCborStore(bs)
nd := hamt.NewNode(cst, hamt.UseTreeBitWidth(5))
emptyhamt, err := cst.Put(ctx, nd)
store := adt.WrapStore(context.TODO(), cbor.NewCborStore(bs))
emptyMap, err := adt.MakeEmptyMap(store).Root()
if err != nil {
return nil, err
}
sms := &power.State{
TotalRawBytePower: big.NewInt(0),
TotalBytesCommitted: big.NewInt(0),
TotalQualityAdjPower: big.NewInt(0),
TotalQABytesCommitted: big.NewInt(0),
TotalPledgeCollateral: big.NewInt(0),
MinerCount: 0,
MinerAboveMinPowerCount: 0,
CronEventQueue: emptyhamt,
FirstCronEpoch: 0,
Claims: emptyhamt,
ProofValidationBatch: nil,
multiMap, err := adt.AsMultimap(store, emptyMap)
if err != nil {
return nil, err
}
stcid, err := cst.Put(ctx, sms)
emptyMultiMap, err := multiMap.Root()
if err != nil {
return nil, err
}
sms := power.ConstructState(emptyMap, emptyMultiMap)
stcid, err := store.Put(store.Context(), sms)
if err != nil {
return nil, err
}

View File

@ -2,32 +2,31 @@ package genesis
import (
"context"
"github.com/ipfs/go-hamt-ipld"
"github.com/filecoin-project/go-amt-ipld/v2"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/builtin/market"
bstore "github.com/ipfs/go-ipfs-blockstore"
"github.com/filecoin-project/specs-actors/actors/util/adt"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/filecoin-project/lotus/chain/types"
bstore "github.com/filecoin-project/lotus/lib/blockstore"
)
func SetupStorageMarketActor(bs bstore.Blockstore) (*types.Actor, error) {
cst := cbor.NewCborStore(bs)
store := adt.WrapStore(context.TODO(), cbor.NewCborStore(bs))
a, err := amt.NewAMT(cst).Flush(context.TODO())
a, err := adt.MakeEmptyArray(store).Root()
if err != nil {
return nil, err
}
h, err := cst.Put(context.TODO(), hamt.NewNode(cst, hamt.UseTreeBitWidth(5)))
h, err := adt.MakeEmptyMap(store).Root()
if err != nil {
return nil, err
}
sms := market.ConstructState(a, h, h)
stcid, err := cst.Put(context.TODO(), sms)
stcid, err := store.Put(store.Context(), sms)
if err != nil {
return nil, err
}

View File

@ -4,27 +4,19 @@ import (
"context"
"github.com/filecoin-project/go-address"
"github.com/ipfs/go-hamt-ipld"
bstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/builtin/verifreg"
"github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/filecoin-project/lotus/chain/types"
bstore "github.com/filecoin-project/lotus/lib/blockstore"
)
var RootVerifierAddr address.Address
var RootVerifierID address.Address
func init() {
k, err := address.NewFromString("t3qfoulel6fy6gn3hjmbhpdpf6fs5aqjb5fkurhtwvgssizq4jey5nw4ptq5up6h7jk7frdvvobv52qzmgjinq")
if err != nil {
panic(err)
}
RootVerifierAddr = k
idk, err := address.NewFromString("t080")
if err != nil {
@ -35,16 +27,16 @@ func init() {
}
func SetupVerifiedRegistryActor(bs bstore.Blockstore) (*types.Actor, error) {
cst := cbor.NewCborStore(bs)
store := adt.WrapStore(context.TODO(), cbor.NewCborStore(bs))
h, err := cst.Put(context.TODO(), hamt.NewNode(cst, hamt.UseTreeBitWidth(5)))
h, err := adt.MakeEmptyMap(store).Root()
if err != nil {
return nil, err
}
sms := verifreg.ConstructState(h, RootVerifierID)
stcid, err := cst.Put(context.TODO(), sms)
stcid, err := store.Put(store.Context(), sms)
if err != nil {
return nil, err
}

View File

@ -37,7 +37,6 @@ func doExecValue(ctx context.Context, vm *vm.VM, to, from address.Address, value
Method: method,
Params: params,
GasLimit: 1_000_000_000_000_000,
GasPrice: types.NewInt(0),
Value: value,
Nonce: act.Nonce,
})

View File

@ -3,9 +3,8 @@ package gen
import (
"context"
bls "github.com/filecoin-project/filecoin-ffi"
amt "github.com/filecoin-project/go-amt-ipld/v2"
"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/filecoin-project/specs-actors/actors/util/adt"
cid "github.com/ipfs/go-cid"
cbor "github.com/ipfs/go-ipld-cbor"
cbg "github.com/whyrusleeping/cbor-gen"
@ -17,6 +16,7 @@ import (
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/lotus/chain/wallet"
"github.com/filecoin-project/lotus/lib/sigs/bls"
)
func MinerCreateBlock(ctx context.Context, sm *stmgr.StateManager, w *wallet.Wallet, bt *api.BlockTemplate) (*types.FullBlock, error) {
@ -78,17 +78,17 @@ func MinerCreateBlock(ctx context.Context, sm *stmgr.StateManager, w *wallet.Wal
}
}
bs := cbor.NewCborStore(sm.ChainStore().Blockstore())
blsmsgroot, err := amt.FromArray(ctx, bs, toIfArr(blsMsgCids))
store := sm.ChainStore().Store(ctx)
blsmsgroot, err := toArray(store, blsMsgCids)
if err != nil {
return nil, xerrors.Errorf("building bls amt: %w", err)
}
secpkmsgroot, err := amt.FromArray(ctx, bs, toIfArr(secpkMsgCids))
secpkmsgroot, err := toArray(store, secpkMsgCids)
if err != nil {
return nil, xerrors.Errorf("building secpk amt: %w", err)
}
mmcid, err := bs.Put(ctx, &types.MsgMeta{
mmcid, err := store.Put(store.Context(), &types.MsgMeta{
BlsMessages: blsmsgroot,
SecpkMessages: secpkmsgroot,
})
@ -109,6 +109,12 @@ func MinerCreateBlock(ctx context.Context, sm *stmgr.StateManager, w *wallet.Wal
}
next.ParentWeight = pweight
baseFee, err := sm.ChainStore().ComputeBaseFee(ctx, pts)
if err != nil {
return nil, xerrors.Errorf("computing base fee: %w", err)
}
next.ParentBaseFee = baseFee
cst := cbor.NewCborStore(sm.ChainStore().Blockstore())
tree, err := state.LoadStateTree(cst, st)
if err != nil {
@ -142,36 +148,45 @@ func MinerCreateBlock(ctx context.Context, sm *stmgr.StateManager, w *wallet.Wal
}
func aggregateSignatures(sigs []crypto.Signature) (*crypto.Signature, error) {
var blsSigs []bls.Signature
for _, s := range sigs {
var bsig bls.Signature
copy(bsig[:], s.Data)
blsSigs = append(blsSigs, bsig)
sigsS := make([][]byte, len(sigs))
for i := 0; i < len(sigs); i++ {
sigsS[i] = sigs[i].Data
}
aggSig := bls.Aggregate(blsSigs)
if aggSig == nil {
aggregator := new(bls.AggregateSignature).AggregateCompressed(sigsS)
if aggregator == nil {
if len(sigs) > 0 {
return nil, xerrors.Errorf("bls.Aggregate returned nil with %d signatures", len(sigs))
}
// Note: for blst this condition should not happen - nil should not
// be returned
return &crypto.Signature{
Type: crypto.SigTypeBLS,
Data: new(bls.Signature)[:],
Data: new(bls.Signature).Compress(),
}, nil
}
aggSigAff := aggregator.ToAffine()
if aggSigAff == nil {
return &crypto.Signature{
Type: crypto.SigTypeBLS,
Data: new(bls.Signature).Compress(),
}, nil
}
aggSig := aggSigAff.Compress()
return &crypto.Signature{
Type: crypto.SigTypeBLS,
Data: aggSig[:],
Data: aggSig,
}, nil
}
func toIfArr(cids []cid.Cid) []cbg.CBORMarshaler {
out := make([]cbg.CBORMarshaler, 0, len(cids))
for _, c := range cids {
func toArray(store adt.Store, cids []cid.Cid) (cid.Cid, error) {
arr := adt.MakeEmptyArray(store)
for i, c := range cids {
oc := cbg.CborCid(c)
out = append(out, &oc)
if err := arr.Set(uint64(i), &oc); err != nil {
return cid.Undef, err
}
}
return out
return arr.Root()
}

View File

@ -0,0 +1,112 @@
package slashfilter
import (
"fmt"
"golang.org/x/xerrors"
"github.com/ipfs/go-cid"
ds "github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/namespace"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/specs-actors/actors/abi"
)
type SlashFilter struct {
byEpoch ds.Datastore // double-fork mining faults, parent-grinding fault
byParents ds.Datastore // time-offset mining faults
}
func New(dstore ds.Batching) *SlashFilter {
return &SlashFilter{
byEpoch: namespace.Wrap(dstore, ds.NewKey("/slashfilter/epoch")),
byParents: namespace.Wrap(dstore, ds.NewKey("/slashfilter/parents")),
}
}
func (f *SlashFilter) MinedBlock(bh *types.BlockHeader, parentEpoch abi.ChainEpoch) error {
epochKey := ds.NewKey(fmt.Sprintf("/%s/%d", bh.Miner, bh.Height))
{
// double-fork mining (2 blocks at one epoch)
if err := checkFault(f.byEpoch, epochKey, bh, "double-fork mining faults"); err != nil {
return err
}
}
parentsKey := ds.NewKey(fmt.Sprintf("/%s/%x", bh.Miner, types.NewTipSetKey(bh.Parents...).Bytes()))
{
// time-offset mining faults (2 blocks with the same parents)
if err := checkFault(f.byParents, parentsKey, bh, "time-offset mining faults"); err != nil {
return err
}
}
{
// parent-grinding fault (didn't mine on top of our own block)
// First check if we have mined a block on the parent epoch
parentEpochKey := ds.NewKey(fmt.Sprintf("/%s/%d", bh.Miner, parentEpoch))
have, err := f.byEpoch.Has(parentEpochKey)
if err != nil {
return err
}
if have {
// If we had, make sure it's in our parent tipset
cidb, err := f.byEpoch.Get(parentEpochKey)
if err != nil {
return xerrors.Errorf("getting other block cid: %w", err)
}
_, parent, err := cid.CidFromBytes(cidb)
if err != nil {
return err
}
var found bool
for _, c := range bh.Parents {
if c.Equals(parent) {
found = true
}
}
if !found {
return xerrors.Errorf("produced block would trigger 'parent-grinding fault' consensus fault; miner: %s; bh: %s, expected parent: %s", bh.Miner, bh.Cid(), parent)
}
}
}
if err := f.byParents.Put(parentsKey, bh.Cid().Bytes()); err != nil {
return xerrors.Errorf("putting byEpoch entry: %w", err)
}
if err := f.byEpoch.Put(epochKey, bh.Cid().Bytes()); err != nil {
return xerrors.Errorf("putting byEpoch entry: %w", err)
}
return nil
}
func checkFault(t ds.Datastore, key ds.Key, bh *types.BlockHeader, faultType string) error {
fault, err := t.Has(key)
if err != nil {
return err
}
if fault {
cidb, err := t.Get(key)
if err != nil {
return xerrors.Errorf("getting other block cid: %w", err)
}
_, other, err := cid.CidFromBytes(cidb)
if err != nil {
return err
}
return xerrors.Errorf("produced block would trigger '%s' consensus fault; miner: %s; bh: %s, other: %s", faultType, bh.Miner, bh.Cid(), other)
}
return nil
}

View File

@ -4,41 +4,121 @@ import (
"context"
"sync"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"go.uber.org/fx"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/ipfs/go-cid"
logging "github.com/ipfs/go-log"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/events"
"github.com/filecoin-project/lotus/chain/events/state"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/node/impl/full"
)
var log = logging.Logger("market_adapter")
type FundMgr struct {
sm *stmgr.StateManager
mpool full.MpoolAPI
// API is the dependencies need to run a fund manager
type API struct {
fx.In
lk sync.Mutex
full.ChainAPI
full.StateAPI
full.MpoolAPI
}
// FundMgr monitors available balances and adds funds when EnsureAvailable is called
type FundMgr struct {
api fundMgrAPI
lk sync.RWMutex
available map[address.Address]types.BigInt
}
func NewFundMgr(sm *stmgr.StateManager, mpool full.MpoolAPI) *FundMgr {
return &FundMgr{
sm: sm,
mpool: mpool,
// StartFundManager creates a new fund manager and sets up event hooks to manage state changes
func StartFundManager(lc fx.Lifecycle, api API) *FundMgr {
fm := newFundMgr(&api)
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
ev := events.NewEvents(ctx, &api)
preds := state.NewStatePredicates(&api)
dealDiffFn := preds.OnStorageMarketActorChanged(preds.OnBalanceChanged(preds.AvailableBalanceChangedForAddresses(fm.getAddresses)))
match := func(oldTs, newTs *types.TipSet) (bool, events.StateChange, error) {
return dealDiffFn(ctx, oldTs.Key(), newTs.Key())
}
return ev.StateChanged(fm.checkFunc, fm.stateChanged, fm.revert, int(build.MessageConfidence), events.NoTimeout, match)
},
})
return fm
}
type fundMgrAPI interface {
StateMarketBalance(context.Context, address.Address, types.TipSetKey) (api.MarketBalance, error)
MpoolPushMessage(context.Context, *types.Message) (*types.SignedMessage, error)
StateLookupID(context.Context, address.Address, types.TipSetKey) (address.Address, error)
}
func newFundMgr(api fundMgrAPI) *FundMgr {
return &FundMgr{
api: api,
available: map[address.Address]types.BigInt{},
}
}
func (fm *FundMgr) EnsureAvailable(ctx context.Context, addr, wallet address.Address, amt types.BigInt) (cid.Cid, error) {
fm.lk.Lock()
avail, ok := fm.available[addr]
// checkFunc tells the events api to simply proceed (we always want to watch)
func (fm *FundMgr) checkFunc(ts *types.TipSet) (done bool, more bool, err error) {
return false, true, nil
}
// revert handles reverts to balances
func (fm *FundMgr) revert(ctx context.Context, ts *types.TipSet) error {
// TODO: Is it ok to just ignore this?
log.Warn("balance change reverted; TODO: actually handle this!")
return nil
}
// stateChanged handles balance changes monitored on the chain from one tipset to the next
func (fm *FundMgr) stateChanged(ts *types.TipSet, ts2 *types.TipSet, states events.StateChange, h abi.ChainEpoch) (more bool, err error) {
changedBalances, ok := states.(state.ChangedBalances)
if !ok {
bal, err := fm.sm.MarketBalance(ctx, addr, nil)
panic("Expected state.ChangedBalances")
}
// overwrite our in memory cache with new values from chain (chain is canonical)
fm.lk.Lock()
for addr, balanceChange := range changedBalances {
fm.available[addr] = balanceChange.To
}
fm.lk.Unlock()
return true, nil
}
func (fm *FundMgr) getAddresses() []address.Address {
fm.lk.RLock()
defer fm.lk.RUnlock()
addrs := make([]address.Address, 0, len(fm.available))
for addr := range fm.available {
addrs = append(addrs, addr)
}
return addrs
}
// EnsureAvailable looks at the available balance in escrow for a given
// address, and if less than the passed in amount, adds the difference
func (fm *FundMgr) EnsureAvailable(ctx context.Context, addr, wallet address.Address, amt types.BigInt) (cid.Cid, error) {
idAddr, err := fm.api.StateLookupID(ctx, addr, types.EmptyTSK)
if err != nil {
return cid.Undef, err
}
fm.lk.Lock()
avail, ok := fm.available[idAddr]
if !ok {
bal, err := fm.api.StateMarketBalance(ctx, addr, types.EmptyTSK)
if err != nil {
fm.lk.Unlock()
return cid.Undef, err
@ -47,25 +127,23 @@ func (fm *FundMgr) EnsureAvailable(ctx context.Context, addr, wallet address.Add
avail = types.BigSub(bal.Escrow, bal.Locked)
}
toAdd := types.NewInt(0)
avail = types.BigSub(avail, amt)
if avail.LessThan(types.NewInt(0)) {
// TODO: some rules around adding more to avoid doing stuff on-chain
// all the time
toAdd = types.BigSub(toAdd, avail)
avail = types.NewInt(0)
toAdd := types.BigSub(amt, avail)
if toAdd.LessThan(types.NewInt(0)) {
toAdd = types.NewInt(0)
}
fm.available[addr] = avail
fm.available[idAddr] = big.Add(avail, toAdd)
fm.lk.Unlock()
var err error
if toAdd.LessThanEqual(big.Zero()) {
return cid.Undef, nil
}
params, err := actors.SerializeParams(&addr)
if err != nil {
return cid.Undef, err
}
smsg, err := fm.mpool.MpoolPushMessage(ctx, &types.Message{
smsg, err := fm.api.MpoolPushMessage(ctx, &types.Message{
To: builtin.StorageMarketActorAddr,
From: wallet,
Value: toAdd,

View File

@ -0,0 +1,187 @@
package market
import (
"context"
"errors"
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/crypto"
tutils "github.com/filecoin-project/specs-actors/support/testing"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
)
type fakeAPI struct {
returnedBalance api.MarketBalance
returnedBalanceErr error
signature crypto.Signature
receivedMessage *types.Message
pushMessageErr error
lookupIDErr error
}
func (fapi *fakeAPI) StateLookupID(_ context.Context, addr address.Address, _ types.TipSetKey) (address.Address, error) {
return addr, fapi.lookupIDErr
}
func (fapi *fakeAPI) StateMarketBalance(context.Context, address.Address, types.TipSetKey) (api.MarketBalance, error) {
return fapi.returnedBalance, fapi.returnedBalanceErr
}
func (fapi *fakeAPI) MpoolPushMessage(ctx context.Context, msg *types.Message) (*types.SignedMessage, error) {
fapi.receivedMessage = msg
return &types.SignedMessage{
Message: *msg,
Signature: fapi.signature,
}, fapi.pushMessageErr
}
func addFundsMsg(toAdd abi.TokenAmount, addr address.Address, wallet address.Address) *types.Message {
params, _ := actors.SerializeParams(&addr)
return &types.Message{
To: builtin.StorageMarketActorAddr,
From: wallet,
Value: toAdd,
Method: builtin.MethodsMarket.AddBalance,
Params: params,
}
}
type expectedResult struct {
addAmt abi.TokenAmount
shouldAdd bool
err error
}
func TestAddFunds(t *testing.T) {
ctx := context.Background()
testCases := map[string]struct {
returnedBalanceErr error
returnedBalance api.MarketBalance
addAmounts []abi.TokenAmount
pushMessageErr error
expectedResults []expectedResult
lookupIDErr error
}{
"succeeds, trivial case": {
returnedBalance: api.MarketBalance{Escrow: abi.NewTokenAmount(0), Locked: abi.NewTokenAmount(0)},
addAmounts: []abi.TokenAmount{abi.NewTokenAmount(100)},
expectedResults: []expectedResult{
{
addAmt: abi.NewTokenAmount(100),
shouldAdd: true,
err: nil,
},
},
},
"succeeds, money already present": {
returnedBalance: api.MarketBalance{Escrow: abi.NewTokenAmount(150), Locked: abi.NewTokenAmount(50)},
addAmounts: []abi.TokenAmount{abi.NewTokenAmount(100)},
expectedResults: []expectedResult{
{
shouldAdd: false,
err: nil,
},
},
},
"succeeds, multiple adds": {
returnedBalance: api.MarketBalance{Escrow: abi.NewTokenAmount(150), Locked: abi.NewTokenAmount(50)},
addAmounts: []abi.TokenAmount{abi.NewTokenAmount(100), abi.NewTokenAmount(200), abi.NewTokenAmount(250), abi.NewTokenAmount(250)},
expectedResults: []expectedResult{
{
shouldAdd: false,
err: nil,
},
{
addAmt: abi.NewTokenAmount(100),
shouldAdd: true,
err: nil,
},
{
addAmt: abi.NewTokenAmount(50),
shouldAdd: true,
err: nil,
},
{
shouldAdd: false,
err: nil,
},
},
},
"error on market balance": {
returnedBalanceErr: errors.New("something went wrong"),
addAmounts: []abi.TokenAmount{abi.NewTokenAmount(100)},
expectedResults: []expectedResult{
{
err: errors.New("something went wrong"),
},
},
},
"error on push message": {
returnedBalance: api.MarketBalance{Escrow: abi.NewTokenAmount(0), Locked: abi.NewTokenAmount(0)},
pushMessageErr: errors.New("something went wrong"),
addAmounts: []abi.TokenAmount{abi.NewTokenAmount(100)},
expectedResults: []expectedResult{
{
err: errors.New("something went wrong"),
},
},
},
"error looking up address": {
lookupIDErr: errors.New("something went wrong"),
addAmounts: []abi.TokenAmount{abi.NewTokenAmount(100)},
expectedResults: []expectedResult{
{
err: errors.New("something went wrong"),
},
},
},
}
for testCase, data := range testCases {
t.Run(testCase, func(t *testing.T) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
sig := make([]byte, 100)
_, err := rand.Read(sig)
require.NoError(t, err)
fapi := &fakeAPI{
returnedBalance: data.returnedBalance,
returnedBalanceErr: data.returnedBalanceErr,
signature: crypto.Signature{
Type: crypto.SigTypeUnknown,
Data: sig,
},
pushMessageErr: data.pushMessageErr,
lookupIDErr: data.lookupIDErr,
}
fundMgr := newFundMgr(fapi)
addr := tutils.NewIDAddr(t, uint64(rand.Uint32()))
wallet := tutils.NewIDAddr(t, uint64(rand.Uint32()))
for i, amount := range data.addAmounts {
fapi.receivedMessage = nil
_, err := fundMgr.EnsureAvailable(ctx, addr, wallet, amount)
expected := data.expectedResults[i]
if expected.err == nil {
require.NoError(t, err)
if expected.shouldAdd {
expectedMessage := addFundsMsg(expected.addAmt, addr, wallet)
require.Equal(t, expectedMessage, fapi.receivedMessage)
} else {
require.Nil(t, fapi.receivedMessage)
}
} else {
require.EqualError(t, err, expected.err.Error())
}
}
})
}
}

View File

@ -0,0 +1,79 @@
package messagepool
import (
"encoding/json"
"time"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/node/modules/dtypes"
"github.com/ipfs/go-datastore"
)
var (
ReplaceByFeeRatioDefault = 1.25
MemPoolSizeLimitHiDefault = 30000
MemPoolSizeLimitLoDefault = 20000
PruneCooldownDefault = time.Minute
GasLimitOverestimation = 1.25
ConfigKey = datastore.NewKey("/mpool/config")
)
func loadConfig(ds dtypes.MetadataDS) (*types.MpoolConfig, error) {
haveCfg, err := ds.Has(ConfigKey)
if err != nil {
return nil, err
}
if !haveCfg {
return DefaultConfig(), nil
}
cfgBytes, err := ds.Get(ConfigKey)
if err != nil {
return nil, err
}
cfg := new(types.MpoolConfig)
err = json.Unmarshal(cfgBytes, cfg)
if cfg.GasLimitOverestimation == 0 {
// TODO: remove in next reset
cfg.GasLimitOverestimation = GasLimitOverestimation
}
return cfg, err
}
func saveConfig(cfg *types.MpoolConfig, ds dtypes.MetadataDS) error {
cfgBytes, err := json.Marshal(cfg)
if err != nil {
return err
}
return ds.Put(ConfigKey, cfgBytes)
}
func (mp *MessagePool) GetConfig() *types.MpoolConfig {
mp.cfgLk.Lock()
defer mp.cfgLk.Unlock()
return mp.cfg.Clone()
}
func (mp *MessagePool) SetConfig(cfg *types.MpoolConfig) {
cfg = cfg.Clone()
mp.cfgLk.Lock()
mp.cfg = cfg
mp.rbfNum = types.NewInt(uint64((cfg.ReplaceByFeeRatio - 1) * RbfDenom))
err := saveConfig(cfg, mp.ds)
if err != nil {
log.Warnf("error persisting mpool config: %s", err)
}
mp.cfgLk.Unlock()
}
func DefaultConfig() *types.MpoolConfig {
return &types.MpoolConfig{
SizeLimitHigh: MemPoolSizeLimitHiDefault,
SizeLimitLow: MemPoolSizeLimitLoDefault,
ReplaceByFeeRatio: ReplaceByFeeRatioDefault,
PruneCooldown: PruneCooldownDefault,
GasLimitOverestimation: GasLimitOverestimation,
}
}

View File

@ -0,0 +1,79 @@
package gasguess
import (
"context"
"github.com/ipfs/go-cid"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/builtin"
)
type ActorLookup func(context.Context, address.Address, types.TipSetKey) (*types.Actor, error)
const failedGasGuessRatio = 0.5
const failedGasGuessMax = 25_000_000
const MinGas = 1298450
const MaxGas = 1600271356
type CostKey struct {
Code cid.Cid
M abi.MethodNum
}
var Costs = map[CostKey]int64{
{builtin.InitActorCodeID, 2}: 8916753,
{builtin.StorageMarketActorCodeID, 2}: 6955002,
{builtin.StorageMarketActorCodeID, 4}: 245436108,
{builtin.StorageMinerActorCodeID, 4}: 2315133,
{builtin.StorageMinerActorCodeID, 5}: 1600271356,
{builtin.StorageMinerActorCodeID, 6}: 22864493,
{builtin.StorageMinerActorCodeID, 7}: 142002419,
{builtin.StorageMinerActorCodeID, 10}: 23008274,
{builtin.StorageMinerActorCodeID, 11}: 19303178,
{builtin.StorageMinerActorCodeID, 14}: 566356835,
{builtin.StorageMinerActorCodeID, 16}: 5325185,
{builtin.StorageMinerActorCodeID, 18}: 2328637,
{builtin.StoragePowerActorCodeID, 2}: 23600956,
}
func failedGuess(msg *types.SignedMessage) int64 {
guess := int64(float64(msg.Message.GasLimit) * failedGasGuessRatio)
if guess > failedGasGuessMax {
guess = failedGasGuessMax
}
return guess
}
func GuessGasUsed(ctx context.Context, tsk types.TipSetKey, msg *types.SignedMessage, al ActorLookup) (int64, error) {
if msg.Message.Method == builtin.MethodSend {
switch msg.Message.From.Protocol() {
case address.BLS:
return 1298450, nil
case address.SECP256K1:
return 1385999, nil
default:
// who knows?
return 1298450, nil
}
}
to, err := al(ctx, msg.Message.To, tsk)
if err != nil {
return failedGuess(msg), xerrors.Errorf("could not lookup actor: %w", err)
}
guess, ok := Costs[CostKey{to.Code, msg.Message.Method}]
if !ok {
return failedGuess(msg), xerrors.Errorf("unknown code-method combo")
}
if guess > msg.Message.GasLimit {
guess = msg.Message.GasLimit
}
return guess, nil
}

View File

@ -10,6 +10,7 @@ import (
"sync"
"time"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/crypto"
lru "github.com/hashicorp/golang-lru"
"github.com/ipfs/go-cid"
@ -28,6 +29,7 @@ import (
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/lotus/journal"
"github.com/filecoin-project/lotus/lib/sigs"
"github.com/filecoin-project/lotus/node/modules/dtypes"
@ -39,12 +41,9 @@ var log = logging.Logger("messagepool")
const futureDebug = false
const ReplaceByFeeRatio = 1.25
const repubMsgLimit = 5
var (
rbfNum = types.NewInt(uint64((ReplaceByFeeRatio - 1) * 256))
rbfDenom = types.NewInt(256)
)
const RbfDenom = 256
var (
ErrMessageTooBig = errors.New("message too big")
@ -57,7 +56,8 @@ var (
ErrInvalidToAddr = errors.New("message had invalid to address")
ErrBroadcastAnyway = errors.New("broadcasting message despite validation fail")
ErrBroadcastAnyway = errors.New("broadcasting message despite validation fail")
ErrRBFTooLowPremium = errors.New("replace by fee has too low GasPremium")
)
const (
@ -89,6 +89,8 @@ type MessagePoolEvt_Message struct {
type MessagePool struct {
lk sync.Mutex
ds dtypes.MetadataDS
closer chan struct{}
repubTk *clock.Ticker
@ -99,11 +101,22 @@ type MessagePool struct {
curTsLk sync.Mutex // DO NOT LOCK INSIDE lk
curTs *types.TipSet
cfgLk sync.Mutex
cfg *types.MpoolConfig
rbfNum, rbfDenom types.BigInt
api Provider
minGasPrice types.BigInt
maxTxPoolSize int
currentSize int
// pruneTrigger is a channel used to trigger a mempool pruning
pruneTrigger chan struct{}
// pruneCooldown is a channel used to allow a cooldown time between prunes
pruneCooldown chan struct{}
blsSigCache *lru.TwoQueueCache
@ -130,7 +143,7 @@ func newMsgSet() *msgSet {
}
}
func (ms *msgSet) add(m *types.SignedMessage) error {
func (ms *msgSet) add(m *types.SignedMessage, mp *MessagePool) (bool, error) {
if len(ms.msgs) == 0 || m.Message.Nonce >= ms.nextNonce {
ms.nextNonce = m.Message.Nonce + 1
}
@ -138,21 +151,24 @@ func (ms *msgSet) add(m *types.SignedMessage) error {
if has {
if m.Cid() != exms.Cid() {
// check if RBF passes
minPrice := exms.Message.GasPrice
minPrice = types.BigAdd(minPrice, types.BigDiv(types.BigMul(minPrice, rbfNum), rbfDenom))
minPrice := exms.Message.GasPremium
minPrice = types.BigAdd(minPrice, types.BigDiv(types.BigMul(minPrice, mp.rbfNum), mp.rbfDenom))
minPrice = types.BigAdd(minPrice, types.NewInt(1))
if types.BigCmp(m.Message.GasPrice, minPrice) > 0 {
log.Infow("add with RBF", "oldprice", exms.Message.GasPrice,
"newprice", m.Message.GasPrice, "addr", m.Message.From, "nonce", m.Message.Nonce)
if types.BigCmp(m.Message.GasPremium, minPrice) >= 0 {
log.Infow("add with RBF", "oldpremium", exms.Message.GasPremium,
"newpremium", m.Message.GasPremium, "addr", m.Message.From, "nonce", m.Message.Nonce)
} else {
log.Info("add with duplicate nonce")
return xerrors.Errorf("message to %s with nonce %d already in mpool", m.Message.To, m.Message.Nonce)
return false, xerrors.Errorf("message from %s with nonce %d already in mpool,"+
" increase GasPremium to %s from %s to trigger replace by fee: %w",
m.Message.From, m.Message.Nonce, minPrice, m.Message.GasPremium,
ErrRBFTooLowPremium)
}
}
}
ms.msgs[m.Message.Nonce] = m
return nil
return !has, nil
}
type Provider interface {
@ -164,6 +180,7 @@ type Provider interface {
MessagesForBlock(*types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error)
MessagesForTipset(*types.TipSet) ([]types.ChainMsg, error)
LoadTipSet(tsk types.TipSetKey) (*types.TipSet, error)
ChainComputeBaseFee(ctx context.Context, ts *types.TipSet) (types.BigInt, error)
}
type mpoolProvider struct {
@ -172,7 +189,7 @@ type mpoolProvider struct {
}
func NewProvider(sm *stmgr.StateManager, ps *pubsub.PubSub) Provider {
return &mpoolProvider{sm, ps}
return &mpoolProvider{sm: sm, ps: ps}
}
func (mpp *mpoolProvider) SubscribeHeadChanges(cb func(rev, app []*types.TipSet) error) *types.TipSet {
@ -209,23 +226,43 @@ func (mpp *mpoolProvider) LoadTipSet(tsk types.TipSetKey) (*types.TipSet, error)
return mpp.sm.ChainStore().LoadTipSet(tsk)
}
func (mpp *mpoolProvider) ChainComputeBaseFee(ctx context.Context, ts *types.TipSet) (types.BigInt, error) {
baseFee, err := mpp.sm.ChainStore().ComputeBaseFee(ctx, ts)
if err != nil {
return types.NewInt(0), xerrors.Errorf("computing base fee at %s: %w", ts, err)
}
return baseFee, nil
}
func New(api Provider, ds dtypes.MetadataDS, netName dtypes.NetworkName, jrnl journal.Journal) (*MessagePool, error) {
cache, _ := lru.New2Q(build.BlsSignatureCacheSize)
verifcache, _ := lru.New2Q(build.VerifSigCacheSize)
cfg, err := loadConfig(ds)
if err != nil {
if err != nil {
return nil, xerrors.Errorf("error loading mpool config: %w", err)
}
}
mp := &MessagePool{
ds: ds,
closer: make(chan struct{}),
repubTk: build.Clock.Ticker(time.Duration(build.BlockDelaySecs) * 10 * time.Second),
repubTk: build.Clock.Ticker(time.Duration(build.BlockDelaySecs) * time.Second),
localAddrs: make(map[address.Address]struct{}),
pending: make(map[address.Address]*msgSet),
minGasPrice: types.NewInt(0),
maxTxPoolSize: 5000,
pruneTrigger: make(chan struct{}, 1),
pruneCooldown: make(chan struct{}, 1),
blsSigCache: cache,
sigValCache: verifcache,
changes: lps.New(50),
localMsgs: namespace.Wrap(ds, datastore.NewKey(localMsgsDs)),
api: api,
netName: netName,
cfg: cfg,
rbfNum: types.NewInt(uint64((cfg.ReplaceByFeeRatio - 1) * RbfDenom)),
rbfDenom: types.NewInt(RbfDenom),
jrnl: jrnl,
evtTypes: [...]journal.EventType{
evtTypeMpoolAdd: jrnl.RegisterEventType("mpool", "add"),
@ -234,11 +271,14 @@ func New(api Provider, ds dtypes.MetadataDS, netName dtypes.NetworkName, jrnl jo
},
}
// enable initial prunes
mp.pruneCooldown <- struct{}{}
if err := mp.loadLocal(); err != nil {
log.Errorf("loading local messages: %+v", err)
}
go mp.repubLocal()
go mp.runLoop()
mp.curTs = api.SubscribeHeadChanges(func(rev, app []*types.TipSet) error {
err := mp.HeadChange(rev, app)
@ -256,7 +296,19 @@ func (mp *MessagePool) Close() error {
return nil
}
func (mp *MessagePool) repubLocal() {
func (mp *MessagePool) Prune() {
// this magic incantation of triggering prune thrice is here to make the Prune method
// synchronous:
// so, its a single slot buffered channel. The first send fills the channel,
// the second send goes through when the pruning starts,
// and the third send goes through (and noops) after the pruning finishes
// and goes through the loop again
mp.pruneTrigger <- struct{}{}
mp.pruneTrigger <- struct{}{}
mp.pruneTrigger <- struct{}{}
}
func (mp *MessagePool) runLoop() {
for {
select {
case <-mp.repubTk.C:
@ -297,6 +349,10 @@ func (mp *MessagePool) repubLocal() {
log.Infow("republishing local messages", "n", len(outputMsgs))
}
if len(outputMsgs) > repubMsgLimit {
outputMsgs = outputMsgs[:repubMsgLimit]
}
for _, msg := range outputMsgs {
msgb, err := msg.Serialize()
if err != nil {
@ -327,6 +383,11 @@ func (mp *MessagePool) repubLocal() {
}
})
case <-mp.pruneTrigger:
if err := mp.pruneExcessMessages(); err != nil {
log.Errorf("failed to prune excess messages from mempool: %s", err)
}
case <-mp.closer:
mp.repubTk.Stop()
return
@ -345,7 +406,23 @@ func (mp *MessagePool) addLocal(m *types.SignedMessage, msgb []byte) error {
return nil
}
func (mp *MessagePool) verifyMsgBeforePush(m *types.SignedMessage, epoch abi.ChainEpoch) error {
minGas := vm.PricelistByEpoch(epoch).OnChainMessage(m.ChainLength())
if err := m.VMMessage().ValidForBlockInclusion(minGas.Total()); err != nil {
return xerrors.Errorf("message will not be included in a block: %w", err)
}
return nil
}
func (mp *MessagePool) Push(m *types.SignedMessage) (cid.Cid, error) {
mp.curTsLk.Lock()
epoch := mp.curTs.Height()
mp.curTsLk.Unlock()
if err := mp.verifyMsgBeforePush(m, epoch); err != nil {
return cid.Undef, err
}
msgb, err := m.Serialize()
if err != nil {
return cid.Undef, err
@ -483,8 +560,21 @@ func (mp *MessagePool) addLocked(m *types.SignedMessage) error {
mp.pending[m.Message.From] = mset
}
if err := mset.add(m); err != nil {
incr, err := mset.add(m, mp)
if err != nil {
log.Info(err)
return err
}
if incr {
mp.currentSize++
if mp.currentSize > mp.cfg.SizeLimitHigh {
// send signal to prune messages if it hasnt already been sent
select {
case mp.pruneTrigger <- struct{}{}:
default:
}
}
}
mp.changes.Pub(api.MpoolUpdate{
@ -602,6 +692,10 @@ func (mp *MessagePool) PushWithNonce(ctx context.Context, addr address.Address,
return nil, err
}
if err := mp.verifyMsgBeforePush(msg, mp.curTs.Height()); err != nil {
return nil, err
}
msgb, err := msg.Serialize()
if err != nil {
return nil, err
@ -621,6 +715,10 @@ func (mp *MessagePool) Remove(from address.Address, nonce uint64) {
mp.lk.Lock()
defer mp.lk.Unlock()
mp.remove(from, nonce)
}
func (mp *MessagePool) remove(from address.Address, nonce uint64) {
mset, ok := mp.pending[from]
if !ok {
return
@ -637,6 +735,8 @@ func (mp *MessagePool) Remove(from address.Address, nonce uint64) {
Action: "remove",
Messages: []MessagePoolEvt_Message{{Message: m.Message, CID: m.Cid()}}}
})
mp.currentSize--
}
// NB: This deletes any message with the given nonce. This makes sense
@ -674,6 +774,14 @@ func (mp *MessagePool) Pending() ([]*types.SignedMessage, *types.TipSet) {
return out, mp.curTs
}
func (mp *MessagePool) PendingFor(a address.Address) ([]*types.SignedMessage, *types.TipSet) {
mp.curTsLk.Lock()
defer mp.curTsLk.Unlock()
mp.lk.Lock()
defer mp.lk.Unlock()
return mp.pendingFor(a), mp.curTs
}
func (mp *MessagePool) pendingFor(a address.Address) []*types.SignedMessage {
mset := mp.pending[a]
@ -925,6 +1033,8 @@ func (mp *MessagePool) loadLocal() error {
log.Errorf("adding local message: %+v", err)
}
mp.localAddrs[sm.Message.From] = struct{}{}
}
return nil

View File

@ -12,16 +12,23 @@ import (
"github.com/filecoin-project/lotus/journal"
_ "github.com/filecoin-project/lotus/lib/sigs/bls"
_ "github.com/filecoin-project/lotus/lib/sigs/secp"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-datastore"
logging "github.com/ipfs/go-log/v2"
)
func init() {
_ = logging.SetLogLevel("*", "INFO")
}
type testMpoolAPI struct {
cb func(rev, app []*types.TipSet) error
bmsgs map[cid.Cid][]*types.SignedMessage
statenonce map[address.Address]uint64
balance map[address.Address]types.BigInt
tipsets []*types.TipSet
}
@ -30,6 +37,7 @@ func newTestMpoolAPI() *testMpoolAPI {
return &testMpoolAPI{
bmsgs: make(map[cid.Cid][]*types.SignedMessage),
statenonce: make(map[address.Address]uint64),
balance: make(map[address.Address]types.BigInt),
}
}
@ -51,6 +59,14 @@ func (tma *testMpoolAPI) setStateNonce(addr address.Address, v uint64) {
tma.statenonce[addr] = v
}
func (tma *testMpoolAPI) setBalance(addr address.Address, v uint64) {
tma.balance[addr] = types.FromFil(v)
}
func (tma *testMpoolAPI) setBalanceRaw(addr address.Address, v types.BigInt) {
tma.balance[addr] = v
}
func (tma *testMpoolAPI) setBlockMessages(h *types.BlockHeader, msgs ...*types.SignedMessage) {
tma.bmsgs[h.Cid()] = msgs
tma.tipsets = append(tma.tipsets, mock.TipSet(h))
@ -70,9 +86,15 @@ func (tma *testMpoolAPI) PubSubPublish(string, []byte) error {
}
func (tma *testMpoolAPI) StateGetActor(addr address.Address, ts *types.TipSet) (*types.Actor, error) {
balance, ok := tma.balance[addr]
if !ok {
balance = types.NewInt(1000e6)
tma.balance[addr] = balance
}
return &types.Actor{
Code: builtin.StorageMarketActorCodeID,
Nonce: tma.statenonce[addr],
Balance: types.NewInt(90000000),
Balance: balance,
}, nil
}
@ -119,6 +141,10 @@ func (tma *testMpoolAPI) LoadTipSet(tsk types.TipSetKey) (*types.TipSet, error)
return nil, fmt.Errorf("tipset not found")
}
func (tma *testMpoolAPI) ChainComputeBaseFee(ctx context.Context, ts *types.TipSet) (types.BigInt, error) {
return types.NewInt(100), nil
}
func assertNonce(t *testing.T, mp *MessagePool, addr address.Address, val uint64) {
t.Helper()
n, err := mp.GetNonce(addr)
@ -234,3 +260,52 @@ func TestRevertMessages(t *testing.T) {
}
}
func TestPruningSimple(t *testing.T) {
tma := newTestMpoolAPI()
w, err := wallet.NewWallet(wallet.NewMemKeyStore())
if err != nil {
t.Fatal(err)
}
ds := datastore.NewMapDatastore()
mp, err := New(tma, ds, "mptest")
if err != nil {
t.Fatal(err)
}
a := mock.MkBlock(nil, 1, 1)
tma.applyBlock(t, a)
sender, err := w.GenerateKey(crypto.SigTypeBLS)
if err != nil {
t.Fatal(err)
}
target := mock.Address(1001)
for i := 0; i < 5; i++ {
smsg := mock.MkMessage(sender, target, uint64(i), w)
if err := mp.Add(smsg); err != nil {
t.Fatal(err)
}
}
for i := 10; i < 50; i++ {
smsg := mock.MkMessage(sender, target, uint64(i), w)
if err := mp.Add(smsg); err != nil {
t.Fatal(err)
}
}
mp.cfg.SizeLimitHigh = 40
mp.cfg.SizeLimitLow = 10
mp.Prune()
msgs, _ := mp.Pending()
if len(msgs) != 5 {
t.Fatal("expected only 5 messages in pool, got: ", len(msgs))
}
}

View File

@ -0,0 +1,105 @@
package messagepool
import (
"context"
"sort"
"time"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/chain/types"
"github.com/ipfs/go-cid"
"golang.org/x/xerrors"
)
func (mp *MessagePool) pruneExcessMessages() error {
mp.curTsLk.Lock()
ts := mp.curTs
mp.curTsLk.Unlock()
mp.lk.Lock()
defer mp.lk.Unlock()
if mp.currentSize < mp.cfg.SizeLimitHigh {
return nil
}
select {
case <-mp.pruneCooldown:
err := mp.pruneMessages(context.TODO(), ts)
go func() {
time.Sleep(mp.cfg.PruneCooldown)
mp.pruneCooldown <- struct{}{}
}()
return err
default:
return xerrors.New("cannot prune before cooldown")
}
}
func (mp *MessagePool) pruneMessages(ctx context.Context, ts *types.TipSet) error {
start := time.Now()
defer func() {
log.Infof("message pruning took %s", time.Since(start))
}()
baseFee, err := mp.api.ChainComputeBaseFee(ctx, ts)
if err != nil {
return xerrors.Errorf("computing basefee: %w", err)
}
pending, _ := mp.getPendingMessages(ts, ts)
// priority actors -- not pruned
priority := make(map[address.Address]struct{})
for _, actor := range mp.cfg.PriorityAddrs {
priority[actor] = struct{}{}
}
// Collect all messages to track which ones to remove and create chains for block inclusion
pruneMsgs := make(map[cid.Cid]*types.SignedMessage, mp.currentSize)
keepCount := 0
var chains []*msgChain
for actor, mset := range pending {
// we never prune priority actors
_, keep := priority[actor]
if keep {
keepCount += len(mset)
continue
}
// not a priority actor, track the messages and create chains
for _, m := range mset {
pruneMsgs[m.Message.Cid()] = m
}
actorChains := mp.createMessageChains(actor, mset, baseFee, ts)
chains = append(chains, actorChains...)
}
// Sort the chains
sort.Slice(chains, func(i, j int) bool {
return chains[i].Before(chains[j])
})
// Keep messages (remove them from pruneMsgs) from chains while we are under the low water mark
loWaterMark := mp.cfg.SizeLimitLow
keepLoop:
for _, chain := range chains {
for _, m := range chain.msgs {
if keepCount < loWaterMark {
delete(pruneMsgs, m.Message.Cid())
keepCount++
} else {
break keepLoop
}
}
}
// and remove all messages that are still in pruneMsgs after processing the chains
log.Infof("Pruning %d messages", len(pruneMsgs))
for _, m := range pruneMsgs {
mp.remove(m.Message.From, m.Message.Nonce)
}
return nil
}

View File

@ -0,0 +1,578 @@
package messagepool
import (
"context"
"math/big"
"sort"
"time"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/messagepool/gasguess"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
abig "github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/ipfs/go-cid"
)
var bigBlockGasLimit = big.NewInt(build.BlockGasLimit)
type msgChain struct {
msgs []*types.SignedMessage
gasReward *big.Int
gasLimit int64
gasPerf float64
valid bool
next *msgChain
}
func (mp *MessagePool) SelectMessages(ts *types.TipSet) ([]*types.SignedMessage, error) {
mp.curTsLk.Lock()
defer mp.curTsLk.Unlock()
mp.lk.Lock()
defer mp.lk.Unlock()
return mp.selectMessages(mp.curTs, ts)
}
func (mp *MessagePool) selectMessages(curTs, ts *types.TipSet) ([]*types.SignedMessage, error) {
start := time.Now()
baseFee, err := mp.api.ChainComputeBaseFee(context.TODO(), ts)
if err != nil {
return nil, xerrors.Errorf("computing basefee: %w", err)
}
// 0. Load messages for the target tipset; if it is the same as the current tipset in the mpool
// then this is just the pending messages
pending, err := mp.getPendingMessages(curTs, ts)
if err != nil {
return nil, err
}
if len(pending) == 0 {
return nil, nil
}
// defer only here so if we have no pending messages we don't spam
defer func() {
log.Infow("message selection done", "took", time.Since(start))
}()
// 0b. Select all priority messages that fit in the block
minGas := int64(gasguess.MinGas)
result, gasLimit := mp.selectPriorityMessages(pending, baseFee, ts)
// have we filled the block?
if gasLimit < minGas {
return result, nil
}
// 1. Create a list of dependent message chains with maximal gas reward per limit consumed
startChains := time.Now()
var chains []*msgChain
for actor, mset := range pending {
next := mp.createMessageChains(actor, mset, baseFee, ts)
chains = append(chains, next...)
}
log.Infow("create message chains done", "took", time.Since(startChains))
// 2. Sort the chains
sort.Slice(chains, func(i, j int) bool {
return chains[i].Before(chains[j])
})
if len(chains) != 0 && chains[0].gasPerf < 0 {
log.Warnw("all messages in mpool have non-positive gas performance", "bestGasPerf", chains[0].gasPerf)
return nil, nil
}
// 3. Merge the head chains to produce the list of messages selected for inclusion, subject to
// the block gas limit.
startMerge := time.Now()
last := len(chains)
for i, chain := range chains {
// does it fit in the block?
if chain.gasLimit <= gasLimit && chain.gasPerf >= 0 {
gasLimit -= chain.gasLimit
result = append(result, chain.msgs...)
continue
}
// did we run out of performing chains?
if chain.gasPerf < 0 {
break
}
// we can't fit this chain because of block gasLimit -- we are at the edge
last = i
break
}
log.Infow("merge message chains done", "took", time.Since(startMerge))
// 4. We have reached the edge of what we can fit wholesale; if we still have available gasLimit
// to pack some more chains, then trim the last chain and push it down.
// Trimming invalidates subsequent dependent chains so that they can't be selected as their
// dependency cannot be (fully) included.
// We do this in a loop because the blocker might have been inordinately large and we might
// have to do it multiple times to satisfy tail packing.
startTail := time.Now()
tailLoop:
for gasLimit >= minGas && last < len(chains) {
// trim
chains[last].Trim(gasLimit, mp, baseFee, ts, false)
// push down if it hasn't been invalidated
if chains[last].valid {
for i := last; i < len(chains)-1; i++ {
if chains[i].Before(chains[i+1]) {
break
}
chains[i], chains[i+1] = chains[i+1], chains[i]
}
}
// select the next (valid and fitting) chain for inclusion
for i, chain := range chains[last:] {
// has the chain been invalidated
if !chain.valid {
continue
}
// does it fit in the bock?
if chain.gasLimit <= gasLimit && chain.gasPerf >= 0 {
gasLimit -= chain.gasLimit
result = append(result, chain.msgs...)
continue
}
// if gasPerf < 0 we have no more profitable chains
if chain.gasPerf < 0 {
break tailLoop
}
// this chain needs to be trimmed
last += i
continue tailLoop
}
// the merge loop ended after processing all the chains and we probably still have gas to spare
// -- mark the end.
last = len(chains)
}
log.Infow("pack tail chains done", "took", time.Since(startTail))
return result, nil
}
func (mp *MessagePool) selectPriorityMessages(pending map[address.Address]map[uint64]*types.SignedMessage, baseFee types.BigInt, ts *types.TipSet) ([]*types.SignedMessage, int64) {
start := time.Now()
defer func() {
log.Infow("select priority messages done", "took", time.Since(start))
}()
result := make([]*types.SignedMessage, 0, mp.cfg.SizeLimitLow)
gasLimit := int64(build.BlockGasLimit)
minGas := int64(gasguess.MinGas)
// 1. Get priority actor chains
var chains []*msgChain
priority := mp.cfg.PriorityAddrs
for _, actor := range priority {
mset, ok := pending[actor]
if ok {
// remove actor from pending set as we are already processed these messages
delete(pending, actor)
// create chains for the priority actor
next := mp.createMessageChains(actor, mset, baseFee, ts)
chains = append(chains, next...)
}
}
// 2. Sort the chains
sort.Slice(chains, func(i, j int) bool {
return chains[i].Before(chains[j])
})
// 3. Merge chains until the block limit; we are willing to include negative performing chains
// as these are messages from our own miners
last := len(chains)
for i, chain := range chains {
if chain.gasLimit <= gasLimit {
gasLimit -= chain.gasLimit
result = append(result, chain.msgs...)
continue
}
// we can't fit this chain because of block gasLimit -- we are at the edge
last = i
break
}
tailLoop:
for gasLimit >= minGas && last < len(chains) {
// trim, without discarding negative performing messages
chains[last].Trim(gasLimit, mp, baseFee, ts, true)
// push down if it hasn't been invalidated
if chains[last].valid {
for i := last; i < len(chains)-1; i++ {
if chains[i].Before(chains[i+1]) {
break
}
chains[i], chains[i+1] = chains[i+1], chains[i]
}
}
// select the next (valid and fitting) chain for inclusion
for i, chain := range chains[last:] {
// has the chain been invalidated
if !chain.valid {
continue
}
// does it fit in the bock?
if chain.gasLimit <= gasLimit {
gasLimit -= chain.gasLimit
result = append(result, chain.msgs...)
continue
}
// this chain needs to be trimmed
last += i
continue tailLoop
}
// the merge loop ended after processing all the chains and we probably still have gas to spare
// -- mark the end.
last = len(chains)
}
return result, gasLimit
}
func (mp *MessagePool) getPendingMessages(curTs, ts *types.TipSet) (map[address.Address]map[uint64]*types.SignedMessage, error) {
start := time.Now()
result := make(map[address.Address]map[uint64]*types.SignedMessage)
haveCids := make(map[cid.Cid]struct{})
defer func() {
if time.Since(start) > time.Millisecond {
log.Infow("get pending messages done", "took", time.Since(start))
}
}()
// are we in sync?
inSync := false
if curTs.Height() == ts.Height() && curTs.Equals(ts) {
inSync = true
}
// first add our current pending messages
for a, mset := range mp.pending {
if inSync {
// no need to copy the map
result[a] = mset.msgs
} else {
// we need to copy the map to avoid clobbering it as we load more messages
msetCopy := make(map[uint64]*types.SignedMessage, len(mset.msgs))
for nonce, m := range mset.msgs {
msetCopy[nonce] = m
}
result[a] = msetCopy
// mark the messages as seen
for _, m := range mset.msgs {
haveCids[m.Cid()] = struct{}{}
}
}
}
// we are in sync, that's the happy path
if inSync {
return result, nil
}
// nope, we need to sync the tipsets
for {
if curTs.Height() == ts.Height() {
if curTs.Equals(ts) {
return result, nil
}
// different blocks in tipsets -- we mark them as seen so that they are not included in
// in the message set we return, but *neither me (vyzo) nor why understand why*
// this code is also probably completely untested in production, so I am adding a big fat
// warning to revisit this case and sanity check this decision.
log.Warnf("mpool tipset has same height as target tipset but it's not equal; beware of dragons!")
have, err := mp.MessagesForBlocks(ts.Blocks())
if err != nil {
return nil, xerrors.Errorf("error retrieving messages for tipset: %w", err)
}
for _, m := range have {
haveCids[m.Cid()] = struct{}{}
}
}
msgs, err := mp.MessagesForBlocks(ts.Blocks())
if err != nil {
return nil, xerrors.Errorf("error retrieving messages for tipset: %w", err)
}
for _, m := range msgs {
if _, have := haveCids[m.Cid()]; have {
continue
}
haveCids[m.Cid()] = struct{}{}
mset, ok := result[m.Message.From]
if !ok {
mset = make(map[uint64]*types.SignedMessage)
result[m.Message.From] = mset
}
other, dupNonce := mset[m.Message.Nonce]
if dupNonce {
// duplicate nonce, selfishly keep the message with the highest GasPrice
// if the gas prices are the same, keep the one with the highest GasLimit
switch m.Message.GasPremium.Int.Cmp(other.Message.GasPremium.Int) {
case 0:
if m.Message.GasLimit > other.Message.GasLimit {
mset[m.Message.Nonce] = m
}
case 1:
mset[m.Message.Nonce] = m
}
} else {
mset[m.Message.Nonce] = m
}
}
if curTs.Height() >= ts.Height() {
return result, nil
}
ts, err = mp.api.LoadTipSet(ts.Parents())
if err != nil {
return nil, xerrors.Errorf("error loading parent tipset: %w", err)
}
}
}
func (mp *MessagePool) getGasReward(msg *types.SignedMessage, baseFee types.BigInt, ts *types.TipSet) *big.Int {
gasReward := abig.Mul(msg.Message.GasPremium, types.NewInt(uint64(msg.Message.GasLimit)))
maxReward := types.BigSub(msg.Message.GasFeeCap, baseFee)
if types.BigCmp(maxReward, gasReward) < 0 {
gasReward = maxReward
}
return gasReward.Int
}
func (mp *MessagePool) getGasPerf(gasReward *big.Int, gasLimit int64) float64 {
// gasPerf = gasReward * build.BlockGasLimit / gasLimit
a := new(big.Rat).SetInt(new(big.Int).Mul(gasReward, bigBlockGasLimit))
b := big.NewRat(1, gasLimit)
c := new(big.Rat).Mul(a, b)
r, _ := c.Float64()
return r
}
func (mp *MessagePool) createMessageChains(actor address.Address, mset map[uint64]*types.SignedMessage, baseFee types.BigInt, ts *types.TipSet) []*msgChain {
// collect all messages
msgs := make([]*types.SignedMessage, 0, len(mset))
for _, m := range mset {
msgs = append(msgs, m)
}
// sort by nonce
sort.Slice(msgs, func(i, j int) bool {
return msgs[i].Message.Nonce < msgs[j].Message.Nonce
})
// sanity checks:
// - there can be no gaps in nonces, starting from the current actor nonce
// if there is a gap, drop messages after the gap, we can't include them
// - all messages must have minimum gas and the total gas for the candidate messages
// cannot exceed the block limit; drop all messages that exceed the limit
// - the total gasReward cannot exceed the actor's balance; drop all messages that exceed
// the balance
a, _ := mp.api.StateGetActor(actor, ts)
curNonce := a.Nonce
balance := a.Balance.Int
gasLimit := int64(0)
skip := 0
i := 0
rewards := make([]*big.Int, 0, len(msgs))
for i = 0; i < len(msgs); i++ {
m := msgs[i]
if m.Message.Nonce < curNonce {
log.Warnf("encountered message from actor %s with nonce (%d) less than the current nonce (%d)",
actor, m.Message.Nonce, curNonce)
skip++
continue
}
if m.Message.Nonce != curNonce {
break
}
curNonce++
minGas := vm.PricelistByEpoch(ts.Height()).OnChainMessage(m.ChainLength()).Total()
if m.Message.GasLimit < minGas {
break
}
gasLimit += m.Message.GasLimit
if gasLimit > build.BlockGasLimit {
break
}
required := m.Message.RequiredFunds().Int
if balance.Cmp(required) < 0 {
break
}
balance = new(big.Int).Sub(balance, required)
value := m.Message.Value.Int
if balance.Cmp(value) >= 0 {
// Note: we only account for the value if the balance doesn't drop below 0
// otherwise the message will fail and the miner can reap the gas rewards
balance = new(big.Int).Sub(balance, value)
}
gasReward := mp.getGasReward(m, baseFee, ts)
rewards = append(rewards, gasReward)
}
// check we have a sane set of messages to construct the chains
if i > 0 {
msgs = msgs[skip:i]
} else {
return nil
}
// ok, now we can construct the chains using the messages we have
// invariant: each chain has a bigger gasPerf than the next -- otherwise they can be merged
// and increase the gasPerf of the first chain
// We do this in two passes:
// - in the first pass we create chains that aggreagate messages with non-decreasing gasPerf
// - in the second pass we merge chains to maintain the invariant.
var chains []*msgChain
var curChain *msgChain
newChain := func(m *types.SignedMessage, i int) *msgChain {
chain := new(msgChain)
chain.msgs = []*types.SignedMessage{m}
chain.gasReward = rewards[i]
chain.gasLimit = m.Message.GasLimit
chain.gasPerf = mp.getGasPerf(chain.gasReward, chain.gasLimit)
chain.valid = true
return chain
}
// create the individual chains
for i, m := range msgs {
if curChain == nil {
curChain = newChain(m, i)
continue
}
gasReward := new(big.Int).Add(curChain.gasReward, rewards[i])
gasLimit := curChain.gasLimit + m.Message.GasLimit
gasPerf := mp.getGasPerf(gasReward, gasLimit)
// try to add the message to the current chain -- if it decreases the gasPerf, then make a
// new chain
if gasPerf < curChain.gasPerf {
chains = append(chains, curChain)
curChain = newChain(m, i)
} else {
curChain.msgs = append(curChain.msgs, m)
curChain.gasReward = gasReward
curChain.gasLimit = gasLimit
curChain.gasPerf = gasPerf
}
}
chains = append(chains, curChain)
// merge chains to maintain the invariant
for {
merged := 0
for i := len(chains) - 1; i > 0; i-- {
if chains[i].gasPerf >= chains[i-1].gasPerf {
chains[i-1].msgs = append(chains[i-1].msgs, chains[i].msgs...)
chains[i-1].gasReward = new(big.Int).Add(chains[i-1].gasReward, chains[i].gasReward)
chains[i-1].gasLimit += chains[i].gasLimit
chains[i-1].gasPerf = mp.getGasPerf(chains[i-1].gasReward, chains[i-1].gasLimit)
chains[i].valid = false
merged++
}
}
if merged == 0 {
break
}
// drop invalidated chains
newChains := make([]*msgChain, 0, len(chains)-merged)
for _, c := range chains {
if c.valid {
newChains = append(newChains, c)
}
}
chains = newChains
}
// link dependent chains
for i := 0; i < len(chains)-1; i++ {
chains[i].next = chains[i+1]
}
return chains
}
func (mc *msgChain) Before(other *msgChain) bool {
return mc.gasPerf > other.gasPerf ||
(mc.gasPerf == other.gasPerf && mc.gasReward.Cmp(other.gasReward) > 0)
}
func (mc *msgChain) Trim(gasLimit int64, mp *MessagePool, baseFee types.BigInt, ts *types.TipSet, priority bool) {
i := len(mc.msgs) - 1
for i >= 0 && (mc.gasLimit > gasLimit || (!priority && mc.gasPerf < 0)) {
gasReward := mp.getGasReward(mc.msgs[i], baseFee, ts)
mc.gasReward = new(big.Int).Sub(mc.gasReward, gasReward)
mc.gasLimit -= mc.msgs[i].Message.GasLimit
if mc.gasLimit > 0 {
mc.gasPerf = mp.getGasPerf(mc.gasReward, mc.gasLimit)
} else {
mc.gasPerf = 0
}
i--
}
if i < 0 {
mc.msgs = nil
mc.valid = false
} else {
mc.msgs = mc.msgs[:i+1]
}
if mc.next != nil {
mc.next.invalidate()
mc.next = nil
}
}
func (mc *msgChain) invalidate() {
mc.valid = false
mc.msgs = nil
if mc.next != nil {
mc.next.invalidate()
mc.next = nil
}
}

View File

@ -0,0 +1,645 @@
package messagepool
import (
"context"
"testing"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/messagepool/gasguess"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/types/mock"
"github.com/filecoin-project/lotus/chain/wallet"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/ipfs/go-datastore"
_ "github.com/filecoin-project/lotus/lib/sigs/bls"
_ "github.com/filecoin-project/lotus/lib/sigs/secp"
)
func makeTestMessage(w *wallet.Wallet, from, to address.Address, nonce uint64, gasLimit int64, gasPrice uint64) *types.SignedMessage {
msg := &types.Message{
From: from,
To: to,
Method: 2,
Value: types.FromFil(0),
Nonce: nonce,
GasLimit: gasLimit,
GasFeeCap: types.NewInt(100 + gasPrice),
GasPremium: types.NewInt(gasPrice),
}
sig, err := w.Sign(context.TODO(), from, msg.Cid().Bytes())
if err != nil {
panic(err)
}
return &types.SignedMessage{
Message: *msg,
Signature: *sig,
}
}
func makeTestMpool() (*MessagePool, *testMpoolAPI) {
tma := newTestMpoolAPI()
ds := datastore.NewMapDatastore()
mp, err := New(tma, ds, "test")
if err != nil {
panic(err)
}
return mp, tma
}
func TestMessageChains(t *testing.T) {
mp, tma := makeTestMpool()
// the actors
w1, err := wallet.NewWallet(wallet.NewMemKeyStore())
if err != nil {
t.Fatal(err)
}
a1, err := w1.GenerateKey(crypto.SigTypeBLS)
if err != nil {
t.Fatal(err)
}
w2, err := wallet.NewWallet(wallet.NewMemKeyStore())
if err != nil {
t.Fatal(err)
}
a2, err := w2.GenerateKey(crypto.SigTypeBLS)
if err != nil {
t.Fatal(err)
}
block := mock.MkBlock(nil, 1, 1)
ts := mock.TipSet(block)
gasLimit := gasguess.Costs[gasguess.CostKey{builtin.StorageMarketActorCodeID, 2}]
tma.setBalance(a1, 1) // in FIL
// test chain aggregations
// test1: 10 messages from a1 to a2, with increasing gasPerf; it should
// make a single chain with 10 messages given enough balance
mset := make(map[uint64]*types.SignedMessage)
for i := 0; i < 10; i++ {
m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1))
mset[uint64(i)] = m
}
baseFee := types.NewInt(0)
chains := mp.createMessageChains(a1, mset, baseFee, ts)
if len(chains) != 1 {
t.Fatal("expected a single chain")
}
if len(chains[0].msgs) != 10 {
t.Fatalf("expected 10 messages in the chain but got %d", len(chains[0].msgs))
}
for i, m := range chains[0].msgs {
if m.Message.Nonce != uint64(i) {
t.Fatalf("expected nonce %d but got %d", i, m.Message.Nonce)
}
}
// test2 : 10 messages from a1 to a2, with decreasing gasPerf; it should
// make 10 chains with 1 message each
mset = make(map[uint64]*types.SignedMessage)
for i := 0; i < 10; i++ {
m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(10-i))
mset[uint64(i)] = m
}
chains = mp.createMessageChains(a1, mset, baseFee, ts)
if len(chains) != 10 {
t.Fatal("expected 10 chains")
}
for i, chain := range chains {
if len(chain.msgs) != 1 {
t.Fatalf("expected 1 message in chain %d but got %d", i, len(chain.msgs))
}
}
for i, chain := range chains {
m := chain.msgs[0]
if m.Message.Nonce != uint64(i) {
t.Fatalf("expected nonce %d but got %d", i, m.Message.Nonce)
}
}
// test3a: 10 messages from a1 to a2, with gasPerf increasing in groups of 3; it should
// merge them in two chains, one with 9 messages and one with the last message
mset = make(map[uint64]*types.SignedMessage)
for i := 0; i < 10; i++ {
m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(1+i%3))
mset[uint64(i)] = m
}
chains = mp.createMessageChains(a1, mset, baseFee, ts)
if len(chains) != 2 {
t.Fatal("expected 1 chain")
}
if len(chains[0].msgs) != 9 {
t.Fatalf("expected 9 messages in the chain but got %d", len(chains[0].msgs))
}
if len(chains[1].msgs) != 1 {
t.Fatalf("expected 1 messages in the chain but got %d", len(chains[1].msgs))
}
nextNonce := 0
for _, chain := range chains {
for _, m := range chain.msgs {
if m.Message.Nonce != uint64(nextNonce) {
t.Fatalf("expected nonce %d but got %d", nextNonce, m.Message.Nonce)
}
nextNonce++
}
}
// test3b: 10 messages from a1 to a2, with gasPerf decreasing in groups of 3 with a bias for the
// earlier chains; it should make 4 chains, the first 3 with 3 messages and the last with
// a single message
mset = make(map[uint64]*types.SignedMessage)
for i := 0; i < 10; i++ {
bias := (12 - i) / 3
m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(1+i%3+bias))
mset[uint64(i)] = m
}
chains = mp.createMessageChains(a1, mset, baseFee, ts)
if len(chains) != 4 {
t.Fatal("expected 4 chains")
}
for i, chain := range chains {
expectedLen := 3
if i > 2 {
expectedLen = 1
}
if len(chain.msgs) != expectedLen {
t.Fatalf("expected %d message in chain %d but got %d", expectedLen, i, len(chain.msgs))
}
}
nextNonce = 0
for _, chain := range chains {
for _, m := range chain.msgs {
if m.Message.Nonce != uint64(nextNonce) {
t.Fatalf("expected nonce %d but got %d", nextNonce, m.Message.Nonce)
}
nextNonce++
}
}
// test chain breaks
// test4: 10 messages with non-consecutive nonces; it should make a single chain with just
// the first message
mset = make(map[uint64]*types.SignedMessage)
for i := 0; i < 10; i++ {
m := makeTestMessage(w1, a1, a2, uint64(i*2), gasLimit, uint64(i+1))
mset[uint64(i)] = m
}
chains = mp.createMessageChains(a1, mset, baseFee, ts)
if len(chains) != 1 {
t.Fatal("expected a single chain")
}
if len(chains[0].msgs) != 1 {
t.Fatalf("expected 1 message in the chain but got %d", len(chains[0].msgs))
}
for i, m := range chains[0].msgs {
if m.Message.Nonce != uint64(i) {
t.Fatalf("expected nonce %d but got %d", i, m.Message.Nonce)
}
}
// test5: 10 messages with increasing gasLimit, except for the 6th message which has less than
// the epoch gasLimit; it should create a single chain with the first 5 messages
mset = make(map[uint64]*types.SignedMessage)
for i := 0; i < 10; i++ {
var m *types.SignedMessage
if i != 5 {
m = makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1))
} else {
m = makeTestMessage(w1, a1, a2, uint64(i), 1, uint64(i+1))
}
mset[uint64(i)] = m
}
chains = mp.createMessageChains(a1, mset, baseFee, ts)
if len(chains) != 1 {
t.Fatal("expected a single chain")
}
if len(chains[0].msgs) != 5 {
t.Fatalf("expected 5 message in the chain but got %d", len(chains[0].msgs))
}
for i, m := range chains[0].msgs {
if m.Message.Nonce != uint64(i) {
t.Fatalf("expected nonce %d but got %d", i, m.Message.Nonce)
}
}
// test6: one more message than what can fit in a block according to gas limit, with increasing
// gasPerf; it should create a single chain with the max messages
maxMessages := int(build.BlockGasLimit / gasLimit)
nMessages := maxMessages + 1
mset = make(map[uint64]*types.SignedMessage)
for i := 0; i < nMessages; i++ {
mset[uint64(i)] = makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1))
}
chains = mp.createMessageChains(a1, mset, baseFee, ts)
if len(chains) != 1 {
t.Fatal("expected a single chain")
}
if len(chains[0].msgs) != maxMessages {
t.Fatalf("expected %d message in the chain but got %d", maxMessages, len(chains[0].msgs))
}
for i, m := range chains[0].msgs {
if m.Message.Nonce != uint64(i) {
t.Fatalf("expected nonce %d but got %d", i, m.Message.Nonce)
}
}
// test5: insufficient balance for all messages
tma.setBalanceRaw(a1, types.NewInt(uint64((300)*gasLimit+1)))
mset = make(map[uint64]*types.SignedMessage)
for i := 0; i < 10; i++ {
mset[uint64(i)] = makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1))
}
chains = mp.createMessageChains(a1, mset, baseFee, ts)
if len(chains) != 1 {
t.Fatalf("expected a single chain: got %d", len(chains))
}
if len(chains[0].msgs) != 2 {
t.Fatalf("expected %d message in the chain but got %d", 2, len(chains[0].msgs))
}
for i, m := range chains[0].msgs {
if m.Message.Nonce != uint64(i) {
t.Fatalf("expected nonce %d but got %d", i, m.Message.Nonce)
}
}
}
func TestMessageChainSkipping(t *testing.T) {
// regression test for chain skip bug
mp, tma := makeTestMpool()
// the actors
w1, err := wallet.NewWallet(wallet.NewMemKeyStore())
if err != nil {
t.Fatal(err)
}
a1, err := w1.GenerateKey(crypto.SigTypeBLS)
if err != nil {
t.Fatal(err)
}
w2, err := wallet.NewWallet(wallet.NewMemKeyStore())
if err != nil {
t.Fatal(err)
}
a2, err := w2.GenerateKey(crypto.SigTypeBLS)
if err != nil {
t.Fatal(err)
}
block := mock.MkBlock(nil, 1, 1)
ts := mock.TipSet(block)
gasLimit := gasguess.Costs[gasguess.CostKey{builtin.StorageMarketActorCodeID, 2}]
baseFee := types.NewInt(0)
tma.setBalance(a1, 1) // in FIL
tma.setStateNonce(a1, 10)
mset := make(map[uint64]*types.SignedMessage)
for i := 0; i < 20; i++ {
bias := (20 - i) / 3
m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(1+i%3+bias))
mset[uint64(i)] = m
}
chains := mp.createMessageChains(a1, mset, baseFee, ts)
if len(chains) != 4 {
t.Fatalf("expected 4 chains, got %d", len(chains))
}
for i, chain := range chains {
var expectedLen int
switch {
case i == 0:
expectedLen = 2
case i > 2:
expectedLen = 2
default:
expectedLen = 3
}
if len(chain.msgs) != expectedLen {
t.Fatalf("expected %d message in chain %d but got %d", expectedLen, i, len(chain.msgs))
}
}
nextNonce := 10
for _, chain := range chains {
for _, m := range chain.msgs {
if m.Message.Nonce != uint64(nextNonce) {
t.Fatalf("expected nonce %d but got %d", nextNonce, m.Message.Nonce)
}
nextNonce++
}
}
}
func TestBasicMessageSelection(t *testing.T) {
mp, tma := makeTestMpool()
// the actors
w1, err := wallet.NewWallet(wallet.NewMemKeyStore())
if err != nil {
t.Fatal(err)
}
a1, err := w1.GenerateKey(crypto.SigTypeBLS)
if err != nil {
t.Fatal(err)
}
w2, err := wallet.NewWallet(wallet.NewMemKeyStore())
if err != nil {
t.Fatal(err)
}
a2, err := w2.GenerateKey(crypto.SigTypeBLS)
if err != nil {
t.Fatal(err)
}
block := mock.MkBlock(nil, 1, 1)
ts := mock.TipSet(block)
tma.applyBlock(t, block)
gasLimit := gasguess.Costs[gasguess.CostKey{builtin.StorageMarketActorCodeID, 2}]
tma.setBalance(a1, 1) // in FIL
tma.setBalance(a2, 1) // in FIL
// we create 10 messages from each actor to another, with the first actor paying higher
// gas prices than the second; we expect message selection to order his messages first
for i := 0; i < 10; i++ {
m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(2*i+1))
mustAdd(t, mp, m)
}
for i := 0; i < 10; i++ {
m := makeTestMessage(w2, a2, a1, uint64(i), gasLimit, uint64(i+1))
mustAdd(t, mp, m)
}
msgs, err := mp.SelectMessages(ts)
if err != nil {
t.Fatal(err)
}
if len(msgs) != 20 {
t.Fatalf("exptected 20 messages, got %d", len(msgs))
}
nextNonce := 0
for i := 0; i < 10; i++ {
if msgs[i].Message.From != a1 {
t.Fatalf("expected message from actor a1")
}
if msgs[i].Message.Nonce != uint64(nextNonce) {
t.Fatalf("expected nonce %d, got %d", msgs[i].Message.Nonce, nextNonce)
}
nextNonce++
}
nextNonce = 0
for i := 10; i < 20; i++ {
if msgs[i].Message.From != a2 {
t.Fatalf("expected message from actor a2")
}
if msgs[i].Message.Nonce != uint64(nextNonce) {
t.Fatalf("expected nonce %d, got %d", msgs[i].Message.Nonce, nextNonce)
}
nextNonce++
}
// now we make a block with all the messages and advance the chain
block2 := mock.MkBlock(ts, 2, 2)
tma.setBlockMessages(block2, msgs...)
tma.applyBlock(t, block2)
// we should have no pending messages in the mpool
pend, ts2 := mp.Pending()
if len(pend) != 0 {
t.Fatalf("expected no pending messages, but got %d", len(pend))
}
// create a block and advance the chain without applying to the mpool
msgs = nil
for i := 10; i < 20; i++ {
m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(2*i+1))
msgs = append(msgs, m)
m = makeTestMessage(w2, a2, a1, uint64(i), gasLimit, uint64(i+1))
msgs = append(msgs, m)
}
block3 := mock.MkBlock(ts2, 3, 3)
tma.setBlockMessages(block3, msgs...)
ts3 := mock.TipSet(block3)
// now create another set of messages and add them to the mpool
for i := 20; i < 30; i++ {
m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(2*i+1))
mustAdd(t, mp, m)
m = makeTestMessage(w2, a2, a1, uint64(i), gasLimit, uint64(i+1))
mustAdd(t, mp, m)
}
// select messages in the last tipset; this should include the missed messages as well as
// the last messages we added, with the first actor's messages first
// first we need to update the nonce on the tma
tma.setStateNonce(a1, 10)
tma.setStateNonce(a2, 10)
msgs, err = mp.SelectMessages(ts3)
if err != nil {
t.Fatal(err)
}
if len(msgs) != 40 {
t.Fatalf("expected 40 messages, got %d", len(msgs))
}
nextNonce = 10
for i := 0; i < 20; i++ {
if msgs[i].Message.From != a1 {
t.Fatalf("expected message from actor a1")
}
if msgs[i].Message.Nonce != uint64(nextNonce) {
t.Fatalf("expected nonce %d, got %d", msgs[i].Message.Nonce, nextNonce)
}
nextNonce++
}
nextNonce = 10
for i := 20; i < 40; i++ {
if msgs[i].Message.From != a2 {
t.Fatalf("expected message from actor a2")
}
if msgs[i].Message.Nonce != uint64(nextNonce) {
t.Fatalf("expected nonce %d, got %d", msgs[i].Message.Nonce, nextNonce)
}
nextNonce++
}
}
func TestMessageSelectionTrimming(t *testing.T) {
mp, tma := makeTestMpool()
// the actors
w1, err := wallet.NewWallet(wallet.NewMemKeyStore())
if err != nil {
t.Fatal(err)
}
a1, err := w1.GenerateKey(crypto.SigTypeBLS)
if err != nil {
t.Fatal(err)
}
w2, err := wallet.NewWallet(wallet.NewMemKeyStore())
if err != nil {
t.Fatal(err)
}
a2, err := w2.GenerateKey(crypto.SigTypeBLS)
if err != nil {
t.Fatal(err)
}
block := mock.MkBlock(nil, 1, 1)
ts := mock.TipSet(block)
tma.applyBlock(t, block)
gasLimit := gasguess.Costs[gasguess.CostKey{builtin.StorageMarketActorCodeID, 2}]
tma.setBalance(a1, 1) // in FIL
tma.setBalance(a2, 1) // in FIL
// make many small chains for the two actors
nMessages := int((build.BlockGasLimit / gasLimit) + 1)
for i := 0; i < nMessages; i++ {
bias := (nMessages - i) / 3
m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(1+i%3+bias))
mustAdd(t, mp, m)
m = makeTestMessage(w2, a2, a1, uint64(i), gasLimit, uint64(1+i%3+bias))
mustAdd(t, mp, m)
}
msgs, err := mp.SelectMessages(ts)
if err != nil {
t.Fatal(err)
}
expected := int(build.BlockGasLimit / gasLimit)
if len(msgs) != expected {
t.Fatalf("expected %d messages, bug got %d", expected, len(msgs))
}
mGasLimit := int64(0)
for _, m := range msgs {
mGasLimit += m.Message.GasLimit
}
if mGasLimit > build.BlockGasLimit {
t.Fatal("selected messages gas limit exceeds block gas limit!")
}
}
func TestPriorityMessageSelection(t *testing.T) {
mp, tma := makeTestMpool()
// the actors
w1, err := wallet.NewWallet(wallet.NewMemKeyStore())
if err != nil {
t.Fatal(err)
}
a1, err := w1.GenerateKey(crypto.SigTypeBLS)
if err != nil {
t.Fatal(err)
}
w2, err := wallet.NewWallet(wallet.NewMemKeyStore())
if err != nil {
t.Fatal(err)
}
a2, err := w2.GenerateKey(crypto.SigTypeBLS)
if err != nil {
t.Fatal(err)
}
block := mock.MkBlock(nil, 1, 1)
ts := mock.TipSet(block)
tma.applyBlock(t, block)
gasLimit := gasguess.Costs[gasguess.CostKey{builtin.StorageMarketActorCodeID, 2}]
tma.setBalance(a1, 1) // in FIL
tma.setBalance(a2, 1) // in FIL
mp.cfg.PriorityAddrs = []address.Address{a1}
nMessages := 10
for i := 0; i < nMessages; i++ {
bias := (nMessages - i) / 3
m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(1+i%3+bias))
mustAdd(t, mp, m)
m = makeTestMessage(w2, a2, a1, uint64(i), gasLimit, uint64(1+i%3+bias))
mustAdd(t, mp, m)
}
msgs, err := mp.SelectMessages(ts)
if err != nil {
t.Fatal(err)
}
if len(msgs) != 20 {
t.Fatalf("expected 20 messages but got %d", len(msgs))
}
// messages from a1 must be first
nextNonce := uint64(0)
for i := 0; i < 10; i++ {
m := msgs[i]
if m.Message.From != a1 {
t.Fatal("expected messages from a1 before messages from a2")
}
if m.Message.Nonce != nextNonce {
t.Fatalf("expected nonce %d but got %d", nextNonce, m.Message.Nonce)
}
nextNonce++
}
nextNonce = 0
for i := 10; i < 20; i++ {
m := msgs[i]
if m.Message.From != a2 {
t.Fatal("expected messages from a2 after messages from a1")
}
if m.Message.Nonce != nextNonce {
t.Fatalf("expected nonce %d but got %d", nextNonce, m.Message.Nonce)
}
nextNonce++
}
}

View File

@ -9,7 +9,6 @@ import (
"github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/ipfs/go-cid"
hamt "github.com/ipfs/go-hamt-ipld"
cbor "github.com/ipfs/go-ipld-cbor"
logging "github.com/ipfs/go-log/v2"
"go.opencensus.io/trace"
@ -23,7 +22,7 @@ var log = logging.Logger("statetree")
// StateTree stores actors state by their ID.
type StateTree struct {
root *hamt.Node
root *adt.Map
Store cbor.IpldStore
snaps *stateSnaps
@ -117,15 +116,16 @@ func (ss *stateSnaps) deleteActor(addr address.Address) {
}
func NewStateTree(cst cbor.IpldStore) (*StateTree, error) {
return &StateTree{
root: hamt.NewNode(cst, hamt.UseTreeBitWidth(5)),
root: adt.MakeEmptyMap(adt.WrapStore(context.TODO(), cst)),
Store: cst,
snaps: newStateSnaps(),
}, nil
}
func LoadStateTree(cst cbor.IpldStore, c cid.Cid) (*StateTree, error) {
nd, err := hamt.LoadNode(context.Background(), cst, c, hamt.UseTreeBitWidth(5))
nd, err := adt.AsMap(adt.WrapStore(context.TODO(), cst), c)
if err != nil {
log.Errorf("loading hamt node %s failed: %s", c, err)
return nil, err
@ -206,12 +206,10 @@ func (st *StateTree) GetActor(addr address.Address) (*types.Actor, error) {
}
var act types.Actor
err = st.root.Find(context.TODO(), string(addr.Bytes()), &act)
if err != nil {
if err == hamt.ErrNotFound {
return nil, types.ErrActorNotFound
}
if found, err := st.root.Get(adt.AddrKey(addr), &act); err != nil {
return nil, xerrors.Errorf("hamt find failed: %w", err)
} else if !found {
return nil, types.ErrActorNotFound
}
st.snaps.setActor(addr, &act)
@ -253,21 +251,17 @@ func (st *StateTree) Flush(ctx context.Context) (cid.Cid, error) {
for addr, sto := range st.snaps.layers[0].actors {
if sto.Delete {
if err := st.root.Delete(ctx, string(addr.Bytes())); err != nil {
if err := st.root.Delete(adt.AddrKey(addr)); err != nil {
return cid.Undef, err
}
} else {
if err := st.root.Set(ctx, string(addr.Bytes()), &sto.Act); err != nil {
if err := st.root.Put(adt.AddrKey(addr), &sto.Act); err != nil {
return cid.Undef, err
}
}
}
if err := st.root.Flush(ctx); err != nil {
return cid.Undef, err
}
return st.Store.Put(ctx, st.root)
return st.root.Root()
}
func (st *StateTree) Snapshot(ctx context.Context) error {

View File

@ -272,7 +272,7 @@ func TestStateTreeConsistency(t *testing.T) {
}
fmt.Println("root is: ", root)
if root.String() != "bafy2bzaceadyjnrv3sbjvowfl3jr4pdn5p2bf3exjjie2f3shg4oy5sub7h34" {
if root.String() != "bafy2bzaceb2bhqw75pqp44efoxvlnm73lnctq6djair56bfn5x3gw56epcxbi" {
t.Fatal("MISMATCH!")
}
}

View File

@ -22,7 +22,17 @@ func (sm *StateManager) CallRaw(ctx context.Context, msg *types.Message, bstate
ctx, span := trace.StartSpan(ctx, "statemanager.CallRaw")
defer span.End()
vmi, err := vm.NewVM(bstate, bheight, r, sm.cs.Blockstore(), sm.cs.VMSys())
vmopt := &vm.VMOpts{
StateBase: bstate,
Epoch: bheight,
Rand: r,
Bstore: sm.cs.Blockstore(),
Syscalls: sm.cs.VMSys(),
VestedCalc: sm.GetVestedFunds,
BaseFee: types.NewInt(0),
}
vmi, err := vm.NewVM(vmopt)
if err != nil {
return nil, xerrors.Errorf("failed to set up vm: %w", err)
}
@ -30,9 +40,13 @@ func (sm *StateManager) CallRaw(ctx context.Context, msg *types.Message, bstate
if msg.GasLimit == 0 {
msg.GasLimit = build.BlockGasLimit
}
if msg.GasPrice == types.EmptyInt {
msg.GasPrice = types.NewInt(0)
if msg.GasFeeCap == types.EmptyInt {
msg.GasFeeCap = types.NewInt(0)
}
if msg.GasPremium == types.EmptyInt {
msg.GasPremium = types.NewInt(0)
}
if msg.Value == types.EmptyInt {
msg.Value = types.NewInt(0)
}
@ -40,7 +54,7 @@ func (sm *StateManager) CallRaw(ctx context.Context, msg *types.Message, bstate
if span.IsRecordingEvents() {
span.AddAttributes(
trace.Int64Attribute("gas_limit", msg.GasLimit),
trace.Int64Attribute("gas_price", int64(msg.GasPrice.Uint64())),
trace.StringAttribute("gas_feecap", msg.GasFeeCap.String()),
trace.StringAttribute("value", msg.Value.String()),
)
}
@ -86,7 +100,7 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.
return sm.CallRaw(ctx, msg, state, r, ts.Height())
}
func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) {
func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, priorMsgs []types.ChainMsg, ts *types.TipSet) (*api.InvocResult, error) {
ctx, span := trace.StartSpan(ctx, "statemanager.CallWithGas")
defer span.End()
@ -94,22 +108,41 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, ts
ts = sm.cs.GetHeaviestTipSet()
}
state := ts.ParentState()
state, _, err := sm.TipSetState(ctx, ts)
if err != nil {
return nil, xerrors.Errorf("computing tipset state: %w", err)
}
r := store.NewChainRand(sm.cs, ts.Cids(), ts.Height())
if span.IsRecordingEvents() {
span.AddAttributes(
trace.Int64Attribute("gas_limit", msg.GasLimit),
trace.Int64Attribute("gas_price", int64(msg.GasPrice.Uint64())),
trace.StringAttribute("gas_feecap", msg.GasFeeCap.String()),
trace.StringAttribute("value", msg.Value.String()),
)
}
vmi, err := vm.NewVM(state, ts.Height(), r, sm.cs.Blockstore(), sm.cs.VMSys())
vmopt := &vm.VMOpts{
StateBase: state,
Epoch: ts.Height() + 1,
Rand: r,
Bstore: sm.cs.Blockstore(),
Syscalls: sm.cs.VMSys(),
VestedCalc: sm.GetVestedFunds,
BaseFee: ts.Blocks()[0].ParentBaseFee,
}
vmi, err := vm.NewVM(vmopt)
if err != nil {
return nil, xerrors.Errorf("failed to set up vm: %w", err)
}
for i, m := range priorMsgs {
_, err := vmi.ApplyMessage(ctx, m)
if err != nil {
return nil, xerrors.Errorf("applying prior message (%d, %s): %w", i, m.Cid(), err)
}
}
fromActor, err := vmi.StateTree().GetActor(msg.From)
if err != nil {
return nil, xerrors.Errorf("call raw get actor: %s", err)

View File

@ -3,23 +3,20 @@ package stmgr
import (
"context"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/ipfs/go-cid"
)
var ForksAtHeight = map[abi.ChainEpoch]func(context.Context, *StateManager, cid.Cid) (cid.Cid, error){}
var ForksAtHeight = map[abi.ChainEpoch]func(context.Context, *StateManager, types.StateTree) error{}
func (sm *StateManager) handleStateForks(ctx context.Context, pstate cid.Cid, height, parentH abi.ChainEpoch) (_ cid.Cid, err error) {
for i := parentH; i < height; i++ {
f, ok := ForksAtHeight[i]
if ok {
nstate, err := f(ctx, sm, pstate)
if err != nil {
return cid.Undef, err
}
pstate = nstate
func (sm *StateManager) handleStateForks(ctx context.Context, st types.StateTree, height abi.ChainEpoch) (err error) {
f, ok := ForksAtHeight[height]
if ok {
err := f(ctx, sm, st)
if err != nil {
return err
}
}
return pstate, nil
return nil
}

View File

@ -21,7 +21,6 @@ import (
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/actors/aerrors"
"github.com/filecoin-project/lotus/chain/gen"
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/stmgr"
. "github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/types"
@ -29,8 +28,6 @@ import (
_ "github.com/filecoin-project/lotus/lib/sigs/bls"
_ "github.com/filecoin-project/lotus/lib/sigs/secp"
"github.com/ipfs/go-cid"
blockstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
logging "github.com/ipfs/go-log"
cbg "github.com/whyrusleeping/cbor-gen"
@ -122,42 +119,38 @@ func TestForkHeightTriggers(t *testing.T) {
t.Fatal(err)
}
stmgr.ForksAtHeight[testForkHeight] = func(ctx context.Context, sm *StateManager, pstate cid.Cid) (cid.Cid, error) {
stmgr.ForksAtHeight[testForkHeight] = func(ctx context.Context, sm *StateManager, st types.StateTree) error {
cst := cbor.NewCborStore(sm.ChainStore().Blockstore())
st, err := state.LoadStateTree(cst, pstate)
if err != nil {
return cid.Undef, err
}
act, err := st.GetActor(taddr)
if err != nil {
return cid.Undef, err
return err
}
var tas testActorState
if err := cst.Get(ctx, act.Head, &tas); err != nil {
return cid.Undef, xerrors.Errorf("in fork handler, failed to run get: %w", err)
return xerrors.Errorf("in fork handler, failed to run get: %w", err)
}
tas.HasUpgraded = 55
ns, err := cst.Put(ctx, &tas)
if err != nil {
return cid.Undef, err
return err
}
act.Head = ns
if err := st.SetActor(taddr, act); err != nil {
return cid.Undef, err
return err
}
return st.Flush(ctx)
return nil
}
inv.Register(builtin.PaymentChannelActorCodeID, &testActor{}, &testActorState{})
sm.SetVMConstructor(func(c cid.Cid, h abi.ChainEpoch, r vm.Rand, b blockstore.Blockstore, s runtime.Syscalls) (*vm.VM, error) {
nvm, err := vm.NewVM(c, h, r, b, s)
sm.SetVMConstructor(func(vmopt *vm.VMOpts) (*vm.VM, error) {
nvm, err := vm.NewVM(vmopt)
if err != nil {
return nil, err
}
@ -180,7 +173,6 @@ func TestForkHeightTriggers(t *testing.T) {
Method: builtin.MethodsInit.Exec,
Params: enc,
GasLimit: types.TestGasLimit,
GasPrice: types.NewInt(0),
}
sig, err := cg.Wallet().Sign(ctx, cg.Banker(), m.Cid().Bytes())
if err != nil {
@ -207,7 +199,6 @@ func TestForkHeightTriggers(t *testing.T) {
Params: nil,
Nonce: nonce,
GasLimit: types.TestGasLimit,
GasPrice: types.NewInt(0),
}
nonce++

View File

@ -5,8 +5,9 @@ import (
"fmt"
"sync"
"github.com/filecoin-project/specs-actors/actors/builtin/multisig"
"github.com/filecoin-project/go-address"
amt "github.com/filecoin-project/go-amt-ipld/v2"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
@ -14,22 +15,20 @@ import (
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
"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/reward"
"github.com/filecoin-project/specs-actors/actors/runtime"
"github.com/filecoin-project/specs-actors/actors/util/adt"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
bls "github.com/filecoin-project/filecoin-ffi"
"github.com/ipfs/go-cid"
hamt "github.com/ipfs/go-hamt-ipld"
blockstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
logging "github.com/ipfs/go-log/v2"
cbg "github.com/whyrusleeping/cbor-gen"
"go.opencensus.io/trace"
)
@ -38,10 +37,12 @@ var log = logging.Logger("statemgr")
type StateManager struct {
cs *store.ChainStore
stCache map[string][]cid.Cid
compWait map[string]chan struct{}
stlk sync.Mutex
newVM func(cid.Cid, abi.ChainEpoch, vm.Rand, blockstore.Blockstore, runtime.Syscalls) (*vm.VM, error)
stCache map[string][]cid.Cid
compWait map[string]chan struct{}
stlk sync.Mutex
genesisMsigLk sync.Mutex
newVM func(*vm.VMOpts) (*vm.VM, error)
genesisMsigs []multisig.State
}
func NewStateManager(cs *store.ChainStore) *StateManager {
@ -149,12 +150,74 @@ type BlockMessages struct {
type ExecCallback func(cid.Cid, *types.Message, *vm.ApplyRet) error
func (sm *StateManager) ApplyBlocks(ctx context.Context, pstate cid.Cid, bms []BlockMessages, epoch abi.ChainEpoch, r vm.Rand, cb ExecCallback) (cid.Cid, cid.Cid, error) {
vmi, err := sm.newVM(pstate, epoch, r, sm.cs.Blockstore(), sm.cs.VMSys())
func (sm *StateManager) ApplyBlocks(ctx context.Context, parentEpoch abi.ChainEpoch, pstate cid.Cid, bms []BlockMessages, epoch abi.ChainEpoch, r vm.Rand, cb ExecCallback, baseFee abi.TokenAmount) (cid.Cid, cid.Cid, error) {
vmopt := &vm.VMOpts{
StateBase: pstate,
Epoch: epoch,
Rand: r,
Bstore: sm.cs.Blockstore(),
Syscalls: sm.cs.VMSys(),
VestedCalc: sm.GetVestedFunds,
BaseFee: baseFee,
}
vmi, err := sm.newVM(vmopt)
if err != nil {
return cid.Undef, cid.Undef, xerrors.Errorf("instantiating VM failed: %w", err)
}
runCron := func() error {
// TODO: this nonce-getting is a tiny bit ugly
ca, err := vmi.StateTree().GetActor(builtin.SystemActorAddr)
if err != nil {
return err
}
cronMsg := &types.Message{
To: builtin.CronActorAddr,
From: builtin.SystemActorAddr,
Nonce: ca.Nonce,
Value: types.NewInt(0),
GasFeeCap: types.NewInt(0),
GasPremium: types.NewInt(0),
GasLimit: build.BlockGasLimit * 10000, // Make super sure this is never too little
Method: builtin.MethodsCron.EpochTick,
Params: nil,
}
ret, err := vmi.ApplyImplicitMessage(ctx, cronMsg)
if err != nil {
return err
}
if cb != nil {
if err := cb(cronMsg.Cid(), cronMsg, ret); err != nil {
return xerrors.Errorf("callback failed on cron message: %w", err)
}
}
if ret.ExitCode != 0 {
return xerrors.Errorf("CheckProofSubmissions exit was non-zero: %d", ret.ExitCode)
}
return nil
}
for i := parentEpoch; i < epoch; i++ {
// handle state forks
err = sm.handleStateForks(ctx, vmi.StateTree(), i)
if err != nil {
return cid.Undef, cid.Undef, xerrors.Errorf("error handling state forks: %w", err)
}
if i > parentEpoch {
// run cron for null rounds if any
if err := runCron(); err != nil {
return cid.Cid{}, cid.Cid{}, err
}
}
vmi.SetBlockHeight(i + 1)
}
var receipts []cbg.CBORMarshaler
processedMsgs := map[cid.Cid]bool{}
for _, b := range bms {
@ -172,7 +235,7 @@ func (sm *StateManager) ApplyBlocks(ctx context.Context, pstate cid.Cid, bms []B
}
receipts = append(receipts, &r.MessageReceipt)
gasReward = big.Add(gasReward, big.Mul(m.GasPrice, big.NewInt(r.GasUsed)))
gasReward = big.Add(gasReward, r.MinerTip)
penalty = big.Add(penalty, r.Penalty)
if cb != nil {
@ -200,14 +263,15 @@ func (sm *StateManager) ApplyBlocks(ctx context.Context, pstate cid.Cid, bms []B
}
rwMsg := &types.Message{
From: builtin.SystemActorAddr,
To: builtin.RewardActorAddr,
Nonce: sysAct.Nonce,
Value: types.NewInt(0),
GasPrice: types.NewInt(0),
GasLimit: 1 << 30,
Method: builtin.MethodsReward.AwardBlockReward,
Params: params,
From: builtin.SystemActorAddr,
To: builtin.RewardActorAddr,
Nonce: sysAct.Nonce,
Value: types.NewInt(0),
GasFeeCap: types.NewInt(0),
GasPremium: types.NewInt(0),
GasLimit: 1 << 30,
Method: builtin.MethodsReward.AwardBlockReward,
Params: params,
}
ret, err := vmi.ApplyImplicitMessage(ctx, rwMsg)
if err != nil {
@ -222,40 +286,19 @@ func (sm *StateManager) ApplyBlocks(ctx context.Context, pstate cid.Cid, bms []B
if ret.ExitCode != 0 {
return cid.Undef, cid.Undef, xerrors.Errorf("reward application message failed (exit %d): %s", ret.ExitCode, ret.ActorErr)
}
}
// TODO: this nonce-getting is a tiny bit ugly
ca, err := vmi.StateTree().GetActor(builtin.SystemActorAddr)
if err != nil {
return cid.Undef, cid.Undef, err
if err := runCron(); err != nil {
return cid.Cid{}, cid.Cid{}, err
}
cronMsg := &types.Message{
To: builtin.CronActorAddr,
From: builtin.SystemActorAddr,
Nonce: ca.Nonce,
Value: types.NewInt(0),
GasPrice: types.NewInt(0),
GasLimit: build.BlockGasLimit * 10, // Make super sure this is never too little
Method: builtin.MethodsCron.EpochTick,
Params: nil,
}
ret, err := vmi.ApplyImplicitMessage(ctx, cronMsg)
if err != nil {
return cid.Undef, cid.Undef, err
}
if cb != nil {
if err := cb(cronMsg.Cid(), cronMsg, ret); err != nil {
return cid.Undef, cid.Undef, xerrors.Errorf("callback failed on cron message: %w", err)
rectarr := adt.MakeEmptyArray(sm.cs.Store(ctx))
for i, receipt := range receipts {
if err := rectarr.Set(uint64(i), receipt); err != nil {
return cid.Undef, cid.Undef, xerrors.Errorf("failed to build receipts amt: %w", err)
}
}
if ret.ExitCode != 0 {
return cid.Undef, cid.Undef, xerrors.Errorf("CheckProofSubmissions exit was non-zero: %d", ret.ExitCode)
}
bs := cbor.NewCborStore(sm.cs.Blockstore())
rectroot, err := amt.FromArray(ctx, bs, receipts)
rectroot, err := rectarr.Root()
if err != nil {
return cid.Undef, cid.Undef, xerrors.Errorf("failed to build receipts amt: %w", err)
}
@ -282,17 +325,15 @@ func (sm *StateManager) computeTipSetState(ctx context.Context, blks []*types.Bl
}
}
var parentEpoch abi.ChainEpoch
pstate := blks[0].ParentStateRoot
if len(blks[0].Parents) > 0 { // don't support forks on genesis
if len(blks[0].Parents) > 0 {
parent, err := sm.cs.GetBlock(blks[0].Parents[0])
if err != nil {
return cid.Undef, cid.Undef, xerrors.Errorf("getting parent block: %w", err)
}
pstate, err = sm.handleStateForks(ctx, blks[0].ParentStateRoot, blks[0].Height, parent.Height)
if err != nil {
return cid.Undef, cid.Undef, xerrors.Errorf("error handling state forks: %w", err)
}
parentEpoch = parent.Height
}
cids := make([]cid.Cid, len(blks))
@ -326,8 +367,9 @@ func (sm *StateManager) computeTipSetState(ctx context.Context, blks []*types.Bl
blkmsgs = append(blkmsgs, bm)
}
baseFee := blks[0].ParentBaseFee
return sm.ApplyBlocks(ctx, pstate, blkmsgs, blks[0].Height, r, cb)
return sm.ApplyBlocks(ctx, parentEpoch, pstate, blkmsgs, blks[0].Height, r, cb, baseFee)
}
func (sm *StateManager) parentState(ts *types.TipSet) cid.Cid {
@ -371,7 +413,7 @@ func (sm *StateManager) ResolveToKeyAddress(ctx context.Context, addr address.Ad
return vm.ResolveToKeyAddr(tree, cst, addr)
}
func (sm *StateManager) GetBlsPublicKey(ctx context.Context, addr address.Address, ts *types.TipSet) (pubk bls.PublicKey, err error) {
func (sm *StateManager) GetBlsPublicKey(ctx context.Context, addr address.Address, ts *types.TipSet) (pubk []byte, err error) {
kaddr, err := sm.ResolveToKeyAddress(ctx, addr, ts)
if err != nil {
return pubk, xerrors.Errorf("failed to resolve address to key address: %w", err)
@ -381,8 +423,7 @@ func (sm *StateManager) GetBlsPublicKey(ctx context.Context, addr address.Addres
return pubk, xerrors.Errorf("address must be BLS address to load bls public key")
}
copy(pubk[:], kaddr.Payload())
return pubk, nil
return kaddr.Payload(), nil
}
func (sm *StateManager) LookupID(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) {
@ -652,14 +693,13 @@ func (sm *StateManager) ListAllActors(ctx context.Context, ts *types.TipSet) ([]
return nil, err
}
cst := cbor.NewCborStore(sm.cs.Blockstore())
r, err := hamt.LoadNode(ctx, cst, st, hamt.UseTreeBitWidth(5))
r, err := adt.AsMap(sm.cs.Store(ctx), st)
if err != nil {
return nil, err
}
var out []address.Address
err = r.ForEach(ctx, func(k string, val interface{}) error {
err = r.ForEach(nil, func(k string) error {
addr, err := address.NewFromBytes([]byte(k))
if err != nil {
return xerrors.Errorf("address in state tree was not valid: %w", err)
@ -692,34 +732,18 @@ func (sm *StateManager) MarketBalance(ctx context.Context, addr address.Address,
if err != nil {
return api.MarketBalance{}, err
}
ehas, err := et.Has(addr)
out.Escrow, err = et.Get(addr)
if err != nil {
return api.MarketBalance{}, err
}
if ehas {
out.Escrow, _, err = et.Get(addr)
if err != nil {
return api.MarketBalance{}, xerrors.Errorf("getting escrow balance: %w", err)
}
} else {
out.Escrow = big.Zero()
return api.MarketBalance{}, xerrors.Errorf("getting escrow balance: %w", err)
}
lt, err := adt.AsBalanceTable(sm.cs.Store(ctx), state.LockedTable)
if err != nil {
return api.MarketBalance{}, err
}
lhas, err := lt.Has(addr)
out.Locked, err = lt.Get(addr)
if err != nil {
return api.MarketBalance{}, err
}
if lhas {
out.Locked, _, err = lt.Get(addr)
if err != nil {
return api.MarketBalance{}, xerrors.Errorf("getting locked balance: %w", err)
}
} else {
out.Locked = big.Zero()
return api.MarketBalance{}, xerrors.Errorf("getting locked balance: %w", err)
}
return out, nil
@ -754,6 +778,91 @@ func (sm *StateManager) ValidateChain(ctx context.Context, ts *types.TipSet) err
return nil
}
func (sm *StateManager) SetVMConstructor(nvm func(cid.Cid, abi.ChainEpoch, vm.Rand, blockstore.Blockstore, runtime.Syscalls) (*vm.VM, error)) {
func (sm *StateManager) SetVMConstructor(nvm func(*vm.VMOpts) (*vm.VM, error)) {
sm.newVM = nvm
}
type GenesisMsigEntry struct {
totalFunds abi.TokenAmount
unitVest abi.TokenAmount
}
func (sm *StateManager) setupGenesisMsigs(ctx context.Context) error {
gb, err := sm.cs.GetGenesis()
if err != nil {
return xerrors.Errorf("getting genesis block: %w", err)
}
gts, err := types.NewTipSet([]*types.BlockHeader{gb})
if err != nil {
return xerrors.Errorf("getting genesis tipset: %w", err)
}
st, _, err := sm.TipSetState(ctx, gts)
if err != nil {
return xerrors.Errorf("getting genesis tipset state: %w", err)
}
r, err := adt.AsMap(sm.cs.Store(ctx), st)
if err != nil {
return xerrors.Errorf("getting genesis actors: %w", err)
}
totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount)
var act types.Actor
err = r.ForEach(&act, func(k string) error {
if act.Code == builtin.MultisigActorCodeID {
var s multisig.State
err := sm.cs.Store(ctx).Get(ctx, act.Head, &s)
if err != nil {
return err
}
if s.StartEpoch != 0 {
return xerrors.New("genesis multisig doesn't start vesting at epoch 0!")
}
ot, f := totalsByEpoch[s.UnlockDuration]
if f {
totalsByEpoch[s.UnlockDuration] = big.Add(ot, s.InitialBalance)
} else {
totalsByEpoch[s.UnlockDuration] = s.InitialBalance
}
}
return nil
})
if err != nil {
return xerrors.Errorf("error setting up composite genesis multisigs: %w", err)
}
sm.genesisMsigs = make([]multisig.State, 0, len(totalsByEpoch))
for k, v := range totalsByEpoch {
ns := multisig.State{
InitialBalance: v,
UnlockDuration: k,
PendingTxns: cid.Undef,
}
sm.genesisMsigs = append(sm.genesisMsigs, ns)
}
return nil
}
func (sm *StateManager) GetVestedFunds(ctx context.Context, height abi.ChainEpoch) (abi.TokenAmount, error) {
sm.genesisMsigLk.Lock()
defer sm.genesisMsigLk.Unlock()
if sm.genesisMsigs == nil {
err := sm.setupGenesisMsigs(ctx)
if err != nil {
return big.Zero(), xerrors.Errorf("failed to setup genesis msig entries: %w", err)
}
}
vf := big.Zero()
for _, v := range sm.genesisMsigs {
au := big.Sub(v.InitialBalance, v.AmountLocked(height))
vf = big.Add(vf, au)
}
return vf, nil
}

View File

@ -7,13 +7,11 @@ import (
"reflect"
cid "github.com/ipfs/go-cid"
blockstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
"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"
@ -39,6 +37,7 @@ import (
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/lotus/lib/blockstore"
"github.com/filecoin-project/lotus/node/modules/dtypes"
)
@ -258,7 +257,7 @@ func GetSectorsForWinningPoSt(ctx context.Context, pv ffiwrapper.Verifier, sm *S
return nil, xerrors.Errorf("failed to enumerate all sector IDs: %w", err)
}
sectorAmt, err := amt.LoadAMT(ctx, sm.cs.Store(ctx), mas.Sectors)
sectorAmt, err := adt.AsArray(sm.cs.Store(ctx), mas.Sectors)
if err != nil {
return nil, xerrors.Errorf("failed to load sectors amt: %w", err)
}
@ -268,8 +267,10 @@ func GetSectorsForWinningPoSt(ctx context.Context, pv ffiwrapper.Verifier, sm *S
sid := sectors[n]
var sinfo miner.SectorOnChainInfo
if err := sectorAmt.Get(ctx, sid, &sinfo); err != nil {
if found, err := sectorAmt.Get(sid, &sinfo); err != nil {
return nil, xerrors.Errorf("failed to get sector %d: %w", sid, err)
} else if !found {
return nil, xerrors.Errorf("failed to find sector %d", sid)
}
out[i] = abi.SectorInfo{
@ -328,18 +329,21 @@ func GetStorageDeal(ctx context.Context, sm *StateManager, dealID abi.DealID, ts
if _, err := sm.LoadActorState(ctx, builtin.StorageMarketActorAddr, &state, ts); err != nil {
return nil, err
}
store := sm.ChainStore().Store(ctx)
da, err := amt.LoadAMT(ctx, cbor.NewCborStore(sm.ChainStore().Blockstore()), state.Proposals)
da, err := adt.AsArray(store, state.Proposals)
if err != nil {
return nil, err
}
var dp market.DealProposal
if err := da.Get(ctx, uint64(dealID), &dp); err != nil {
if found, err := da.Get(uint64(dealID), &dp); err != nil {
return nil, err
} else if !found {
return nil, xerrors.Errorf("deal %d not found", dealID)
}
sa, err := market.AsDealStateArray(sm.ChainStore().Store(ctx), state.States)
sa, err := market.AsDealStateArray(store, state.States)
if err != nil {
return nil, err
}
@ -391,15 +395,16 @@ func ListMinerActors(ctx context.Context, sm *StateManager, ts *types.TipSet) ([
}
func LoadSectorsFromSet(ctx context.Context, bs blockstore.Blockstore, ssc cid.Cid, filter *abi.BitField, filterOut bool) ([]*api.ChainSectorInfo, error) {
a, err := amt.LoadAMT(ctx, cbor.NewCborStore(bs), ssc)
a, err := adt.AsArray(store.ActorStore(ctx, bs), ssc)
if err != nil {
return nil, err
}
var sset []*api.ChainSectorInfo
if err := a.ForEach(ctx, func(i uint64, v *cbg.Deferred) error {
var v cbg.Deferred
if err := a.ForEach(&v, func(i int64) error {
if filter != nil {
set, err := filter.IsSet(i)
set, err := filter.IsSet(uint64(i))
if err != nil {
return xerrors.Errorf("filter check error: %w", err)
}
@ -434,15 +439,29 @@ func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch,
return cid.Undef, nil, err
}
fstate, err := sm.handleStateForks(ctx, base, height, ts.Height())
r := store.NewChainRand(sm.cs, ts.Cids(), height)
vmopt := &vm.VMOpts{
StateBase: base,
Epoch: height,
Rand: r,
Bstore: sm.cs.Blockstore(),
Syscalls: sm.cs.VMSys(),
VestedCalc: sm.GetVestedFunds,
BaseFee: ts.Blocks()[0].ParentBaseFee,
}
vmi, err := vm.NewVM(vmopt)
if err != nil {
return cid.Undef, nil, err
}
r := store.NewChainRand(sm.cs, ts.Cids(), height)
vmi, err := vm.NewVM(fstate, height, r, sm.cs.Blockstore(), sm.cs.VMSys())
if err != nil {
return cid.Undef, nil, err
for i := ts.Height(); i < height; i++ {
// handle state forks
err = sm.handleStateForks(ctx, vmi.StateTree(), i)
if err != nil {
return cid.Undef, nil, xerrors.Errorf("error handling state forks: %w", err)
}
// TODO: should we also run cron here?
}
for i, msg := range msgs {
@ -557,6 +576,11 @@ func MinerGetBaseInfo(ctx context.Context, sm *StateManager, bcn beacon.RandomBe
return nil, xerrors.Errorf("resolving worker address: %w", err)
}
hmp, err := MinerHasMinPower(ctx, sm, maddr, lbts)
if err != nil {
return nil, xerrors.Errorf("determining if miner has min power failed: %w", err)
}
return &api.MiningBaseInfo{
MinerPower: mpow.QualityAdjPower,
NetworkPower: tpow.QualityAdjPower,
@ -565,6 +589,7 @@ func MinerGetBaseInfo(ctx context.Context, sm *StateManager, bcn beacon.RandomBe
SectorSize: info.SectorSize,
PrevBeaconEntry: *prev,
BeaconEntries: entries,
HasMinPower: hmp,
}, nil
}
@ -579,7 +604,16 @@ func (sm *StateManager) CirculatingSupply(ctx context.Context, ts *types.TipSet)
}
r := store.NewChainRand(sm.cs, ts.Cids(), ts.Height())
vmi, err := vm.NewVM(st, ts.Height(), r, sm.cs.Blockstore(), sm.cs.VMSys())
vmopt := &vm.VMOpts{
StateBase: st,
Epoch: ts.Height(),
Rand: r,
Bstore: sm.cs.Blockstore(),
Syscalls: sm.cs.VMSys(),
VestedCalc: sm.GetVestedFunds,
BaseFee: ts.Blocks()[0].ParentBaseFee,
}
vmi, err := vm.NewVM(vmopt)
if err != nil {
return big.Zero(), err
}
@ -649,3 +683,40 @@ func GetReturnType(ctx context.Context, sm *StateManager, to address.Address, me
m := MethodsMap[act.Code][method]
return reflect.New(m.Ret.Elem()).Interface().(cbg.CBORUnmarshaler), nil
}
func MinerHasMinPower(ctx context.Context, sm *StateManager, addr address.Address, ts *types.TipSet) (bool, error) {
var ps power.State
_, err := sm.LoadActorState(ctx, builtin.StoragePowerActorAddr, &ps, ts)
if err != nil {
return false, xerrors.Errorf("loading power actor state: %w", err)
}
return ps.MinerNominalPowerMeetsConsensusMinimum(sm.ChainStore().Store(ctx), addr)
}
func GetCirculatingSupply(ctx context.Context, sm *StateManager, ts *types.TipSet) (abi.TokenAmount, error) {
if ts == nil {
ts = sm.cs.GetHeaviestTipSet()
}
r := store.NewChainRand(sm.cs, ts.Cids(), ts.Height())
vmopt := &vm.VMOpts{
StateBase: ts.ParentState(),
Epoch: ts.Height(),
Rand: r,
Bstore: sm.cs.Blockstore(),
Syscalls: sm.cs.VMSys(),
VestedCalc: sm.GetVestedFunds,
BaseFee: ts.Blocks()[0].ParentBaseFee,
}
vmi, err := vm.NewVM(vmopt)
if err != nil {
return abi.NewTokenAmount(0), err
}
uvm := &vm.UnsafeVM{vmi}
rt := uvm.MakeRuntime(ctx, &types.Message{From: builtin.InitActorAddr, GasLimit: 10000000}, builtin.InitActorAddr, 0, 0, 0)
return rt.TotalFilCircSupply(), nil
}

45
chain/store/basefee.go Normal file
View File

@ -0,0 +1,45 @@
package store
import (
"context"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"golang.org/x/xerrors"
)
func computeNextBaseFee(baseFee types.BigInt, gasLimitUsed int64, noOfBlocks int) types.BigInt {
delta := gasLimitUsed/int64(noOfBlocks) - build.BlockGasTarget
change := big.Mul(baseFee, big.NewInt(delta))
change = big.Div(change, big.NewInt(build.BlockGasTarget))
change = big.Div(change, big.NewInt(build.BaseFeeMaxChangeDenom))
nextBaseFee := big.Add(baseFee, change)
if big.Cmp(nextBaseFee, big.NewInt(build.MinimumBaseFee)) < 0 {
nextBaseFee = big.NewInt(build.MinimumBaseFee)
}
return nextBaseFee
}
func (cs *ChainStore) ComputeBaseFee(ctx context.Context, ts *types.TipSet) (abi.TokenAmount, error) {
zero := abi.NewTokenAmount(0)
totalLimit := int64(0)
for _, b := range ts.Blocks() {
msg1, msg2, err := cs.MessagesForBlock(b)
if err != nil {
return zero, xerrors.Errorf("error getting messages for: %s: %w", b.Cid(), err)
}
for _, m := range msg1 {
totalLimit += m.GasLimit
}
for _, m := range msg2 {
totalLimit += m.Message.GasLimit
}
}
parentBaseFee := ts.Blocks()[0].ParentBaseFee
return computeNextBaseFee(parentBaseFee, totalLimit, len(ts.Blocks())), nil
}

View File

@ -0,0 +1,34 @@
package store
import (
"fmt"
"testing"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/types"
"github.com/stretchr/testify/assert"
)
func TestBaseFee(t *testing.T) {
tests := []struct {
basefee uint64
limitUsed int64
noOfBlocks int
output uint64
}{
{100e6, 0, 1, 87.5e6},
{100e6, 0, 5, 87.5e6},
{100e6, build.BlockGasTarget, 1, 100e6},
{100e6, build.BlockGasTarget * 2, 2, 100e6},
{100e6, build.BlockGasLimit * 2, 2, 112.5e6},
{100e6, build.BlockGasLimit * 1.5, 2, 106.25e6},
}
for _, test := range tests {
test := test
t.Run(fmt.Sprintf("%v", test), func(t *testing.T) {
output := computeNextBaseFee(types.NewInt(test.basefee), test.limitUsed, test.noOfBlocks)
assert.Equal(t, fmt.Sprintf("%d", test.output), output.String())
})
}
}

View File

@ -8,10 +8,10 @@ import (
"github.com/filecoin-project/lotus/chain/gen"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types/mock"
"github.com/filecoin-project/lotus/lib/blockstore"
"github.com/filecoin-project/specs-actors/actors/abi"
datastore "github.com/ipfs/go-datastore"
syncds "github.com/ipfs/go-datastore/sync"
blockstore "github.com/ipfs/go-ipfs-blockstore"
"github.com/stretchr/testify/assert"
)
@ -30,7 +30,7 @@ func TestIndexSeeks(t *testing.T) {
ctx := context.TODO()
nbs := blockstore.NewBlockstore(syncds.MutexWrap(datastore.NewMapDatastore()))
nbs := blockstore.NewTemporarySync()
cs := store.NewChainStore(nbs, syncds.MutexWrap(datastore.NewMapDatastore()), nil)
_, err = cs.Import(bytes.NewReader(gencar))

View File

@ -9,35 +9,30 @@ import (
"os"
"sync"
"github.com/filecoin-project/lotus/lib/adtutil"
"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/minio/blake2b-simd"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/runtime"
"github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/lotus/journal"
bstore "github.com/filecoin-project/lotus/lib/blockstore"
"github.com/filecoin-project/lotus/metrics"
"go.opencensus.io/stats"
"go.opencensus.io/trace"
"go.uber.org/multierr"
amt "github.com/filecoin-project/go-amt-ipld/v2"
"github.com/filecoin-project/lotus/chain/types"
lru "github.com/hashicorp/golang-lru"
block "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
dstore "github.com/ipfs/go-datastore"
blockstore "github.com/ipfs/go-ipfs-blockstore"
bstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
logging "github.com/ipfs/go-log/v2"
car "github.com/ipld/go-car"
@ -99,13 +94,13 @@ type ChainStore struct {
mmCache *lru.ARCCache
tsCache *lru.ARCCache
vmcalls runtime.Syscalls
vmcalls vm.SyscallBuilder
journal journal.Journal
evtTypes [1]journal.EventType
}
func NewChainStore(bs bstore.Blockstore, ds dstore.Batching, vmcalls runtime.Syscalls, jrnl journal.Journal) *ChainStore {
func NewChainStore(bs bstore.Blockstore, ds dstore.Batching, vmcalls vm.SyscallBuilder, jrnl journal.Journal) *ChainStore {
c, _ := lru.NewARC(2048)
tsc, _ := lru.NewARC(4096)
cs := &ChainStore{
@ -713,20 +708,25 @@ func (cs *ChainStore) GetSignedMessage(c cid.Cid) (*types.SignedMessage, error)
func (cs *ChainStore) readAMTCids(root cid.Cid) ([]cid.Cid, error) {
ctx := context.TODO()
bs := cbor.NewCborStore(cs.bs)
a, err := amt.LoadAMT(ctx, bs, root)
a, err := adt.AsArray(cs.Store(ctx), root)
if err != nil {
return nil, xerrors.Errorf("amt load: %w", err)
}
var cids []cid.Cid
for i := uint64(0); i < a.Count; i++ {
var c cbg.CborCid
if err := a.Get(ctx, i, &c); err != nil {
return nil, xerrors.Errorf("failed to load cid from amt: %w", err)
}
var (
cids []cid.Cid
cborCid cbg.CborCid
)
if err := a.ForEach(&cborCid, func(i int64) error {
c := cid.Cid(cborCid)
cids = append(cids, c)
return nil
}); err != nil {
return nil, xerrors.Errorf("failed to traverse amt: %w", err)
}
cids = append(cids, cid.Cid(c))
if uint64(len(cids)) != a.Length() {
return nil, xerrors.Errorf("found %d cids, expected %d", len(cids), a.Length())
}
return cids, nil
@ -874,15 +874,16 @@ func (cs *ChainStore) MessagesForBlock(b *types.BlockHeader) ([]*types.Message,
func (cs *ChainStore) GetParentReceipt(b *types.BlockHeader, i int) (*types.MessageReceipt, error) {
ctx := context.TODO()
bs := cbor.NewCborStore(cs.bs)
a, err := amt.LoadAMT(ctx, bs, b.ParentMessageReceipts)
a, err := adt.AsArray(cs.Store(ctx), b.ParentMessageReceipts)
if err != nil {
return nil, xerrors.Errorf("amt load: %w", err)
}
var r types.MessageReceipt
if err := a.Get(ctx, uint64(i), &r); err != nil {
if found, err := a.Get(uint64(i), &r); err != nil {
return nil, err
} else if !found {
return nil, xerrors.Errorf("failed to find receipt %d", i)
}
return &r, nil
@ -920,15 +921,15 @@ func (cs *ChainStore) Blockstore() bstore.Blockstore {
return cs.bs
}
func ActorStore(ctx context.Context, bs blockstore.Blockstore) adt.Store {
return adtutil.NewStore(ctx, cbor.NewCborStore(bs))
func ActorStore(ctx context.Context, bs bstore.Blockstore) adt.Store {
return adt.WrapStore(ctx, cbor.NewCborStore(bs))
}
func (cs *ChainStore) Store(ctx context.Context) adt.Store {
return ActorStore(ctx, cs.bs)
}
func (cs *ChainStore) VMSys() runtime.Syscalls {
func (cs *ChainStore) VMSys() vm.SyscallBuilder {
return cs.vmcalls
}
@ -1043,7 +1044,7 @@ func (cs *ChainStore) GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, t
return cs.LoadTipSet(lbts.Parents())
}
func recurseLinks(bs blockstore.Blockstore, root cid.Cid, in []cid.Cid) ([]cid.Cid, error) {
func recurseLinks(bs bstore.Blockstore, root cid.Cid, in []cid.Cid) ([]cid.Cid, error) {
if root.Prefix().Codec != cid.DagCBOR {
return in, nil
}

View File

@ -6,7 +6,6 @@ import (
"testing"
datastore "github.com/ipfs/go-datastore"
blockstore "github.com/ipfs/go-ipfs-blockstore"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
@ -18,6 +17,7 @@ import (
"github.com/filecoin-project/lotus/chain/gen"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/lib/blockstore"
"github.com/filecoin-project/lotus/node/repo"
)
@ -100,7 +100,7 @@ func TestChainExportImport(t *testing.T) {
t.Fatal(err)
}
nbs := blockstore.NewBlockstore(datastore.NewMapDatastore())
nbs := blockstore.NewTemporary()
cs := store.NewChainStore(nbs, datastore.NewMapDatastore(), nil)
root, err := cs.Import(buf)

View File

@ -7,7 +7,6 @@ import (
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
big2 "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/power"
@ -23,7 +22,7 @@ func (cs *ChainStore) Weight(ctx context.Context, ts *types.TipSet) (types.BigIn
}
// >>> w[r] <<< + wFunction(totalPowerAtTipset(ts)) * 2^8 + (wFunction(totalPowerAtTipset(ts)) * sum(ts.blocks[].ElectionProof.WinCount) * wRatio_num * 2^8) / (e * wRatio_den)
var out = new(big.Int).Set(ts.Blocks()[0].ParentWeight.Int)
var out = new(big.Int).Set(ts.ParentWeight().Int)
// >>> wFunction(totalPowerAtTipset(ts)) * 2^8 <<< + (wFunction(totalPowerAtTipset(ts)) * sum(ts.blocks[].ElectionProof.WinCount) * wRatio_num * 2^8) / (e * wRatio_den)
@ -73,44 +72,3 @@ func (cs *ChainStore) Weight(ctx context.Context, ts *types.TipSet) (types.BigIn
return types.BigInt{Int: out}, nil
}
// todo: dedupe with state manager
func (cs *ChainStore) call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*types.MessageReceipt, error) {
bstate := ts.ParentState()
r := NewChainRand(cs, ts.Cids(), ts.Height())
vmi, err := vm.NewVM(bstate, ts.Height(), r, cs.bs, cs.vmcalls)
if err != nil {
return nil, xerrors.Errorf("failed to set up vm: %w", err)
}
if msg.GasLimit == 0 {
msg.GasLimit = 10000000000
}
if msg.GasPrice == types.EmptyInt {
msg.GasPrice = types.NewInt(0)
}
if msg.Value == types.EmptyInt {
msg.Value = types.NewInt(0)
}
fromActor, err := vmi.StateTree().GetActor(msg.From)
if err != nil {
return nil, xerrors.Errorf("call raw get actor: %s", err)
}
msg.Nonce = fromActor.Nonce
// TODO: maybe just use the invoker directly?
// TODO: use signed message length for secp messages
ret, err := vmi.ApplyMessage(ctx, msg)
if err != nil {
return nil, xerrors.Errorf("apply message failed: %w", err)
}
if ret.ActorErr != nil {
log.Warnf("chain call failed: %s", ret.ActorErr)
}
return &ret.MessageReceipt, nil
}

View File

@ -4,19 +4,18 @@ import (
"bytes"
"context"
"fmt"
"github.com/filecoin-project/lotus/lib/adtutil"
"sync"
"time"
"golang.org/x/xerrors"
address "github.com/filecoin-project/go-address"
amt "github.com/filecoin-project/go-amt-ipld/v2"
miner "github.com/filecoin-project/specs-actors/actors/builtin/miner"
"github.com/filecoin-project/specs-actors/actors/util/adt"
lru "github.com/hashicorp/golang-lru"
blocks "github.com/ipfs/go-block-format"
bserv "github.com/ipfs/go-blockservice"
"github.com/ipfs/go-cid"
dstore "github.com/ipfs/go-datastore"
bstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
logging "github.com/ipfs/go-log/v2"
connmgr "github.com/libp2p/go-libp2p-core/connmgr"
@ -33,6 +32,7 @@ import (
"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/lib/blockstore"
"github.com/filecoin-project/lotus/lib/bufbstore"
"github.com/filecoin-project/lotus/lib/sigs"
"github.com/filecoin-project/lotus/metrics"
@ -40,7 +40,7 @@ import (
var log = logging.Logger("sub")
func HandleIncomingBlocks(ctx context.Context, bsub *pubsub.Subscription, s *chain.Syncer, cmgr connmgr.ConnManager) {
func HandleIncomingBlocks(ctx context.Context, bsub *pubsub.Subscription, s *chain.Syncer, bserv bserv.BlockService, cmgr connmgr.ConnManager) {
for {
msg, err := bsub.Next(ctx)
if err != nil {
@ -63,13 +63,13 @@ func HandleIncomingBlocks(ctx context.Context, bsub *pubsub.Subscription, s *cha
go func() {
start := build.Clock.Now()
log.Debug("about to fetch messages for block from pubsub")
bmsgs, err := s.Bsync.FetchMessagesByCids(context.TODO(), blk.BlsMessages)
bmsgs, err := FetchMessagesByCids(context.TODO(), bserv, blk.BlsMessages)
if err != nil {
log.Errorf("failed to fetch all bls messages for block received over pubusb: %s; source: %s", err, src)
return
}
smsgs, err := s.Bsync.FetchSignedMessagesByCids(context.TODO(), blk.SecpkMessages)
smsgs, err := FetchSignedMessagesByCids(context.TODO(), bserv, blk.SecpkMessages)
if err != nil {
log.Errorf("failed to fetch all secpk messages for block received over pubusb: %s; source: %s", err, src)
return
@ -92,6 +92,108 @@ func HandleIncomingBlocks(ctx context.Context, bsub *pubsub.Subscription, s *cha
}
}
func FetchMessagesByCids(
ctx context.Context,
bserv bserv.BlockService,
cids []cid.Cid,
) ([]*types.Message, error) {
out := make([]*types.Message, len(cids))
err := fetchCids(ctx, bserv, cids, func(i int, b blocks.Block) error {
msg, err := types.DecodeMessage(b.RawData())
if err != nil {
return err
}
// FIXME: We already sort in `fetchCids`, we are duplicating too much work,
// we don't need to pass the index.
if out[i] != nil {
return fmt.Errorf("received duplicate message")
}
out[i] = msg
return nil
})
if err != nil {
return nil, err
}
return out, nil
}
// FIXME: Duplicate of above.
func FetchSignedMessagesByCids(
ctx context.Context,
bserv bserv.BlockService,
cids []cid.Cid,
) ([]*types.SignedMessage, error) {
out := make([]*types.SignedMessage, len(cids))
err := fetchCids(ctx, bserv, cids, func(i int, b blocks.Block) error {
smsg, err := types.DecodeSignedMessage(b.RawData())
if err != nil {
return err
}
if out[i] != nil {
return fmt.Errorf("received duplicate message")
}
out[i] = smsg
return nil
})
if err != nil {
return nil, err
}
return out, nil
}
// Fetch `cids` from the block service, apply `cb` on each of them. Used
// by the fetch message functions above.
// We check that each block is received only once and we do not received
// blocks we did not request.
func fetchCids(
ctx context.Context,
bserv bserv.BlockService,
cids []cid.Cid,
cb func(int, blocks.Block) error,
) error {
// FIXME: Why don't we use the context here?
fetchedBlocks := bserv.GetBlocks(context.TODO(), cids)
cidIndex := make(map[cid.Cid]int)
for i, c := range cids {
cidIndex[c] = i
}
for i := 0; i < len(cids); i++ {
select {
case block, ok := <-fetchedBlocks:
if !ok {
// Closed channel, no more blocks fetched, check if we have all
// of the CIDs requested.
// FIXME: Review this check. We don't call the callback on the
// last index?
if i == len(cids)-1 {
break
}
return fmt.Errorf("failed to fetch all messages")
}
ix, ok := cidIndex[block.Cid()]
if !ok {
return fmt.Errorf("received message we didnt ask for")
}
if err := cb(ix, block); err != nil {
return err
}
}
}
return nil
}
type BlockValidator struct {
peers *lru.TwoQueueCache
@ -192,14 +294,14 @@ func (bv *BlockValidator) Validate(ctx context.Context, pid peer.ID, msg *pubsub
// if we can't find it, we check whether we are (near) synced in the chain.
// if we are not synced we cannot validate the block and we must ignore it.
// if we are synced and the miner is unknown, then the block is rejcected.
key, err := bv.getMinerWorkerKey(ctx, blk)
key, err := bv.checkPowerAndGetWorkerKey(ctx, blk.Header)
if err != nil {
if bv.isChainNearSynced() {
log.Warnf("received block message from unknown miner over pubsub; rejecting message")
log.Warnf("received block from unknown miner or miner that doesn't meet min power over pubsub; rejecting message")
recordFailure("unknown_miner")
return pubsub.ValidationReject
} else {
log.Warnf("cannot validate block message; unknown miner in unsynced chain")
log.Warnf("cannot validate block message; unknown miner or miner that doesn't meet min power in unsynced chain")
return pubsub.ValidationIgnore
}
}
@ -239,31 +341,36 @@ func (bv *BlockValidator) isChainNearSynced() bool {
}
func (bv *BlockValidator) validateMsgMeta(ctx context.Context, msg *types.BlockMsg) error {
var bcids, scids []cbg.CBORMarshaler
for _, m := range msg.BlsMessages {
c := cbg.CborCid(m)
bcids = append(bcids, &c)
}
for _, m := range msg.SecpkMessages {
c := cbg.CborCid(m)
scids = append(scids, &c)
}
// TODO there has to be a simpler way to do this without the blockstore dance
bs := cbor.NewCborStore(bstore.NewBlockstore(dstore.NewMapDatastore()))
store := adt.WrapStore(ctx, cbor.NewCborStore(blockstore.NewTemporary()))
bmArr := adt.MakeEmptyArray(store)
smArr := adt.MakeEmptyArray(store)
bmroot, err := amt.FromArray(ctx, bs, bcids)
for i, m := range msg.BlsMessages {
c := cbg.CborCid(m)
if err := bmArr.Set(uint64(i), &c); err != nil {
return err
}
}
for i, m := range msg.SecpkMessages {
c := cbg.CborCid(m)
if err := smArr.Set(uint64(i), &c); err != nil {
return err
}
}
bmroot, err := bmArr.Root()
if err != nil {
return err
}
smroot, err := amt.FromArray(ctx, bs, scids)
smroot, err := smArr.Root()
if err != nil {
return err
}
mrcid, err := bs.Put(ctx, &types.MsgMeta{
mrcid, err := store.Put(store.Context(), &types.MsgMeta{
BlsMessages: bmroot,
SecpkMessages: smroot,
})
@ -279,60 +386,78 @@ func (bv *BlockValidator) validateMsgMeta(ctx context.Context, msg *types.BlockM
return nil
}
func (bv *BlockValidator) getMinerWorkerKey(ctx context.Context, msg *types.BlockMsg) (address.Address, error) {
addr := msg.Header.Miner
func (bv *BlockValidator) checkPowerAndGetWorkerKey(ctx context.Context, bh *types.BlockHeader) (address.Address, error) {
addr := bh.Miner
bv.mx.Lock()
key, ok := bv.keycache[addr.String()]
bv.mx.Unlock()
if ok {
return key, nil
if !ok {
// TODO I have a feeling all this can be simplified by cleverer DI to use the API
ts := bv.chain.GetHeaviestTipSet()
st, _, err := bv.stmgr.TipSetState(ctx, ts)
if err != nil {
return address.Undef, err
}
buf := bufbstore.NewBufferedBstore(bv.chain.Blockstore())
cst := cbor.NewCborStore(buf)
state, err := state.LoadStateTree(cst, st)
if err != nil {
return address.Undef, err
}
act, err := state.GetActor(addr)
if err != nil {
return address.Undef, err
}
blk, err := bv.chain.Blockstore().Get(act.Head)
if err != nil {
return address.Undef, err
}
aso := blk.RawData()
var mst miner.State
err = mst.UnmarshalCBOR(bytes.NewReader(aso))
if err != nil {
return address.Undef, err
}
info, err := mst.GetInfo(adt.WrapStore(ctx, cst))
if err != nil {
return address.Undef, err
}
worker := info.Worker
key, err = bv.stmgr.ResolveToKeyAddress(ctx, worker, ts)
if err != nil {
return address.Undef, err
}
bv.mx.Lock()
bv.keycache[addr.String()] = key
bv.mx.Unlock()
}
// TODO I have a feeling all this can be simplified by cleverer DI to use the API
ts := bv.chain.GetHeaviestTipSet()
st, _, err := bv.stmgr.TipSetState(ctx, ts)
if err != nil {
return address.Undef, err
}
buf := bufbstore.NewBufferedBstore(bv.chain.Blockstore())
cst := cbor.NewCborStore(buf)
state, err := state.LoadStateTree(cst, st)
if err != nil {
return address.Undef, err
}
act, err := state.GetActor(addr)
// we check that the miner met the minimum power at the lookback tipset
baseTs := bv.chain.GetHeaviestTipSet()
lbts, err := stmgr.GetLookbackTipSetForRound(ctx, bv.stmgr, baseTs, bh.Height)
if err != nil {
log.Warnf("failed to load lookback tipset for incoming block")
return address.Undef, err
}
blk, err := bv.chain.Blockstore().Get(act.Head)
if err != nil {
return address.Undef, err
}
aso := blk.RawData()
var mst miner.State
err = mst.UnmarshalCBOR(bytes.NewReader(aso))
hmp, err := stmgr.MinerHasMinPower(ctx, bv.stmgr, bh.Miner, lbts)
if err != nil {
log.Warnf("failed to determine if incoming block's miner has minimum power")
return address.Undef, err
}
info, err := mst.GetInfo(adtutil.NewStore(ctx, cst))
if err != nil {
return address.Undef, err
if !hmp {
log.Warnf("incoming block's miner does not have minimum power")
return address.Undef, xerrors.New("incoming block's miner does not have minimum power")
}
worker := info.Worker
key, err = bv.stmgr.ResolveToKeyAddress(ctx, worker, ts)
if err != nil {
return address.Undef, err
}
bv.mx.Lock()
bv.keycache[addr.String()] = key
bv.mx.Unlock()
return key, nil
}
@ -385,7 +510,7 @@ func (mv *MessageValidator) Validate(ctx context.Context, pid peer.ID, msg *pubs
)
stats.Record(ctx, metrics.MessageValidationFailure.M(1))
switch {
case xerrors.Is(err, messagepool.ErrBroadcastAnyway):
case xerrors.Is(err, messagepool.ErrBroadcastAnyway) || xerrors.Is(err, messagepool.ErrRBFTooLowPremium):
return pubsub.ValidationIgnore
default:
return pubsub.ValidationReject

View File

@ -13,8 +13,6 @@ import (
"github.com/Gurpartap/async"
"github.com/hashicorp/go-multierror"
"github.com/ipfs/go-cid"
dstore "github.com/ipfs/go-datastore"
bstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
logging "github.com/ipfs/go-log/v2"
"github.com/libp2p/go-libp2p-core/connmgr"
@ -25,15 +23,14 @@ import (
"go.opencensus.io/trace"
"golang.org/x/xerrors"
bls "github.com/filecoin-project/filecoin-ffi"
"github.com/filecoin-project/go-address"
amt "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/builtin"
"github.com/filecoin-project/specs-actors/actors/builtin/power"
"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/filecoin-project/specs-actors/actors/util/adt"
blst "github.com/supranational/blst/bindings/go"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
@ -45,7 +42,9 @@ import (
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
bstore "github.com/filecoin-project/lotus/lib/blockstore"
"github.com/filecoin-project/lotus/lib/sigs"
"github.com/filecoin-project/lotus/lib/sigs/bls"
"github.com/filecoin-project/lotus/metrics"
)
@ -139,6 +138,12 @@ func NewSyncer(sm *stmgr.StateManager, bsync *blocksync.BlockSync, connmgr connm
incoming: pubsub.New(50),
}
if build.InsecurePoStValidation {
log.Warn("*********************************************************************************************")
log.Warn(" [INSECURE-POST-VALIDATION] Insecure test validation is enabled. If you see this outside of a test, it is a severe bug! ")
log.Warn("*********************************************************************************************")
}
s.syncmgr = NewSyncManager(s.Sync)
return s, nil
}
@ -200,8 +205,8 @@ func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) bool {
syncer.Bsync.AddPeer(from)
bestPweight := syncer.store.GetHeaviestTipSet().Blocks()[0].ParentWeight
targetWeight := fts.TipSet().Blocks()[0].ParentWeight
bestPweight := syncer.store.GetHeaviestTipSet().ParentWeight()
targetWeight := fts.TipSet().ParentWeight()
if targetWeight.LessThan(bestPweight) {
var miners []string
for _, blk := range fts.TipSet().Blocks() {
@ -256,15 +261,13 @@ func (syncer *Syncer) ValidateMsgMeta(fblk *types.FullBlock) error {
}
// Collect the CIDs of both types of messages separately: BLS and Secpk.
var bcids, scids []cbg.CBORMarshaler
var bcids, scids []cid.Cid
for _, m := range fblk.BlsMessages {
c := cbg.CborCid(m.Cid())
bcids = append(bcids, &c)
bcids = append(bcids, m.Cid())
}
for _, m := range fblk.SecpkMessages {
c := cbg.CborCid(m.Cid())
scids = append(scids, &c)
scids = append(scids, m.Cid())
}
// TODO: IMPORTANT(GARBAGE). These message puts and the msgmeta
@ -354,19 +357,17 @@ func zipTipSetAndMessages(bs cbor.IpldStore, ts *types.TipSet, allbmsgs []*types
}
var smsgs []*types.SignedMessage
var smsgCids []cbg.CBORMarshaler
var smsgCids []cid.Cid
for _, m := range smi[bi] {
smsgs = append(smsgs, allsmsgs[m])
c := cbg.CborCid(allsmsgs[m].Cid())
smsgCids = append(smsgCids, &c)
smsgCids = append(smsgCids, allsmsgs[m].Cid())
}
var bmsgs []*types.Message
var bmsgCids []cbg.CBORMarshaler
var bmsgCids []cid.Cid
for _, m := range bmi[bi] {
bmsgs = append(bmsgs, allbmsgs[m])
c := cbg.CborCid(allbmsgs[m].Cid())
bmsgCids = append(bmsgCids, &c)
bmsgCids = append(bmsgCids, allbmsgs[m].Cid())
}
mrcid, err := computeMsgMeta(bs, bmsgCids, smsgCids)
@ -392,19 +393,36 @@ func zipTipSetAndMessages(bs cbor.IpldStore, ts *types.TipSet, allbmsgs []*types
// 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)
func computeMsgMeta(bs cbor.IpldStore, bmsgCids, smsgCids []cid.Cid) (cid.Cid, error) {
store := adt.WrapStore(context.TODO(), bs)
bmArr := adt.MakeEmptyArray(store)
smArr := adt.MakeEmptyArray(store)
for i, m := range bmsgCids {
c := cbg.CborCid(m)
if err := bmArr.Set(uint64(i), &c); err != nil {
return cid.Undef, err
}
}
for i, m := range smsgCids {
c := cbg.CborCid(m)
if err := smArr.Set(uint64(i), &c); err != nil {
return cid.Undef, err
}
}
bmroot, err := bmArr.Root()
if err != nil {
return cid.Undef, err
}
smroot, err := amt.FromArray(ctx, bs, smsgCids)
smroot, err := smArr.Root()
if err != nil {
return cid.Undef, err
}
mrcid, err := bs.Put(ctx, &types.MsgMeta{
mrcid, err := store.Put(store.Context(), &types.MsgMeta{
BlsMessages: bmroot,
SecpkMessages: smroot,
})
@ -622,19 +640,13 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) (er
validationStart := build.Clock.Now()
defer func() {
dur := time.Since(validationStart)
durMilli := dur.Seconds() * float64(1000)
stats.Record(ctx, metrics.BlockValidationDurationMilliseconds.M(durMilli))
log.Infow("block validation", "took", dur, "height", b.Header.Height)
stats.Record(ctx, metrics.BlockValidationDurationMilliseconds.M(metrics.SinceInMilliseconds(validationStart)))
log.Infow("block validation", "took", time.Since(validationStart), "height", b.Header.Height)
}()
ctx, span := trace.StartSpan(ctx, "validateBlock")
defer span.End()
if build.InsecurePoStValidation {
log.Warn("[INSECURE-POST-VALIDATION] if you see this outside of a test, it is a severe bug!")
}
if err := blockSanityChecks(b.Header); err != nil {
return xerrors.Errorf("incoming header failed basic sanity checks: %w", err)
}
@ -661,9 +673,11 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) (er
return xerrors.Errorf("failed to get latest beacon entry: %w", err)
}
//nulls := h.Height - (baseTs.Height() + 1)
// fast checks first
nulls := h.Height - (baseTs.Height() + 1)
if tgtTs := baseTs.MinTimestamp() + build.BlockDelaySecs*uint64(nulls+1); h.Timestamp != tgtTs {
return xerrors.Errorf("block has wrong timestamp: %d != %d", h.Timestamp, tgtTs)
}
now := uint64(build.Clock.Now().Unix())
if h.Timestamp > now+build.AllowableClockDriftSecs {
@ -673,13 +687,6 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) (er
log.Warn("Got block from the future, but within threshold", h.Timestamp, build.Clock.Now().Unix())
}
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.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.BlockDelaySecs, h.Height-baseTs.Height(), diff)
}
msgsCheck := async.Err(func() error {
if err := syncer.checkBlockMessages(ctx, b, baseTs); err != nil {
return xerrors.Errorf("block had invalid messages: %w", err)
@ -694,6 +701,27 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) (er
return nil
})
baseFeeCheck := async.Err(func() error {
baseFee, err := syncer.store.ComputeBaseFee(ctx, baseTs)
if err != nil {
return xerrors.Errorf("computing base fee: %w", err)
}
if types.BigCmp(baseFee, b.Header.ParentBaseFee) != 0 {
return xerrors.Errorf("base fee doesn't match: %s (header) != %s (computed)",
b.Header.ParentBaseFee, baseFee)
}
return nil
})
pweight, err := syncer.store.Weight(ctx, baseTs)
if err != nil {
return xerrors.Errorf("getting parent weight: %w", err)
}
if types.BigCmp(pweight, b.Header.ParentWeight) != 0 {
return xerrors.Errorf("parrent weight different: %s (header) != %s (computed)",
b.Header.ParentWeight, pweight)
}
// Stuff that needs stateroot / worker address
stateroot, precp, err := syncer.sm.TipSetState(ctx, baseTs)
if err != nil {
@ -729,6 +757,15 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) (er
return xerrors.Errorf("block is not claiming to be a winner")
}
hp, err := stmgr.MinerHasMinPower(ctx, syncer.sm, h.Miner, lbts)
if err != nil {
return xerrors.Errorf("determining if miner has min power failed: %w", err)
}
if !hp {
return xerrors.New("block's miner does not meet minimum power threshold")
}
rBeacon := *prevBeacon
if len(h.BeaconEntries) != 0 {
rBeacon = h.BeaconEntries[len(h.BeaconEntries)-1]
@ -827,6 +864,7 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) (er
wproofCheck,
winnerCheck,
msgsCheck,
baseFeeCheck,
}
var merr error
@ -851,6 +889,7 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) (er
"%d errors occurred:\n\t%s\n\n",
len(es), strings.Join(points, "\n\t"))
}
return mulErr
}
if err := syncer.store.MarkBlockAsValidated(ctx, b.Cid()); err != nil {
@ -908,7 +947,7 @@ func (syncer *Syncer) VerifyWinningPoStProof(ctx context.Context, h *types.Block
}
if !ok {
log.Errorf("invalid winning post (%x; %v)", rand, sectors)
log.Errorf("invalid winning post (block: %s, %x; %v)", h.Cid(), rand, sectors)
return xerrors.Errorf("winning post was invalid")
}
@ -919,7 +958,7 @@ func (syncer *Syncer) VerifyWinningPoStProof(ctx context.Context, h *types.Block
func (syncer *Syncer) checkBlockMessages(ctx context.Context, b *types.FullBlock, baseTs *types.TipSet) error {
{
var sigCids []cid.Cid // this is what we get for people not wanting the marshalcbor method on the cid type
var pubks []bls.PublicKey
var pubks [][]byte
for _, m := range b.BlsMessages {
sigCids = append(sigCids, m.Cid())
@ -977,7 +1016,6 @@ func (syncer *Syncer) checkBlockMessages(ctx context.Context, b *types.FullBlock
return xerrors.Errorf("failed to get actor: %w", err)
}
// redundant check
if !act.IsAccountActor() {
return xerrors.New("Sender must be an account actor")
}
@ -992,18 +1030,21 @@ func (syncer *Syncer) checkBlockMessages(ctx context.Context, b *types.FullBlock
return nil
}
var blsCids []cbg.CBORMarshaler
store := adt.WrapStore(ctx, cst)
bmArr := adt.MakeEmptyArray(store)
for i, m := range b.BlsMessages {
if err := checkMsg(m); err != nil {
return xerrors.Errorf("block had invalid bls message at index %d: %w", i, err)
}
c := cbg.CborCid(m.Cid())
blsCids = append(blsCids, &c)
if err := bmArr.Set(uint64(i), &c); err != nil {
return xerrors.Errorf("failed to put bls message at index %d: %w", i, err)
}
}
var secpkCids []cbg.CBORMarshaler
smArr := adt.MakeEmptyArray(store)
for i, m := range b.SecpkMessages {
if err := checkMsg(m); err != nil {
return xerrors.Errorf("block had invalid secpk message at index %d: %w", i, err)
@ -1021,17 +1062,19 @@ func (syncer *Syncer) checkBlockMessages(ctx context.Context, b *types.FullBlock
}
c := cbg.CborCid(m.Cid())
secpkCids = append(secpkCids, &c)
if err := smArr.Set(uint64(i), &c); err != nil {
return xerrors.Errorf("failed to put secpk message at index %d: %w", i, err)
}
}
bmroot, err := amt.FromArray(ctx, cst, blsCids)
bmroot, err := bmArr.Root()
if err != nil {
return xerrors.Errorf("failed to build amt from bls msg cids: %w", err)
return err
}
smroot, err := amt.FromArray(ctx, cst, secpkCids)
smroot, err := smArr.Root()
if err != nil {
return xerrors.Errorf("failed to build amt from bls msg cids: %w", err)
return err
}
mrcid, err := cst.Put(ctx, &types.MsgMeta{
@ -1049,24 +1092,27 @@ func (syncer *Syncer) checkBlockMessages(ctx context.Context, b *types.FullBlock
return nil
}
func (syncer *Syncer) verifyBlsAggregate(ctx context.Context, sig *crypto.Signature, msgs []cid.Cid, pubks []bls.PublicKey) error {
func (syncer *Syncer) verifyBlsAggregate(ctx context.Context, sig *crypto.Signature, msgs []cid.Cid, pubks [][]byte) error {
_, span := trace.StartSpan(ctx, "syncer.verifyBlsAggregate")
defer span.End()
span.AddAttributes(
trace.Int64Attribute("msgCount", int64(len(msgs))),
)
bmsgs := make([]bls.Message, len(msgs))
for i, m := range msgs {
bmsgs[i] = m.Bytes()
msgsS := make([]blst.Message, len(msgs))
for i := 0; i < len(msgs); i++ {
msgsS[i] = msgs[i].Bytes()
}
var bsig bls.Signature
copy(bsig[:], sig.Data)
if !bls.HashVerify(&bsig, bmsgs, pubks) {
if len(msgs) == 0 {
return nil
}
valid := new(bls.Signature).AggregateVerifyCompressed(sig.Data, pubks,
msgsS, []byte(bls.DST))
if !valid {
return xerrors.New("bls aggregate signature failed to verify")
}
return nil
}
@ -1216,6 +1262,22 @@ loop:
}
log.Info("Got blocks: ", blks[0].Height(), len(blks))
// Check that the fetched segment of the chain matches what we already
// have. Since we fetch from the head backwards our reassembled chain
// is sorted in reverse here: we have a child -> parent order, our last
// tipset then should be child of the first tipset retrieved.
// FIXME: The reassembly logic should be part of the `BlockSync`
// service, the consumer should not be concerned with the
// `MaxRequestLength` limitation, it should just be able to request
// an segment of arbitrary length. The same burden is put on
// `syncFork()` which needs to be aware this as well.
if blockSet[len(blockSet)-1].IsChildOf(blks[0]) == false {
return nil, xerrors.Errorf("retrieved segments of the chain are not connected at heights %d/%d",
blockSet[len(blockSet)-1].Height(), blks[0].Height())
// A successful `GetBlocks()` call is guaranteed to fetch at least
// one tipset so the acess `blks[0]` is safe.
}
for _, b := range blks {
if b.Height() < untilHeight {
break loop
@ -1239,31 +1301,33 @@ loop:
at = blks[len(blks)-1].Parents()
}
// base is the tipset in the candidate chain at the height equal to our known tipset height.
if base := blockSet[len(blockSet)-1]; !types.CidArrsEqual(base.Parents().Cids(), known.Cids()) {
if base.Parents() == known.Parents() {
// common case: receiving a block thats potentially part of the same tipset as our best block
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)", incoming.Cids(), incoming.Height(), known.Cids(), known.Height())
fork, err := syncer.syncFork(ctx, base, known)
if err != nil {
if xerrors.Is(err, ErrForkTooLong) {
// TODO: we're marking this block bad in the same way that we mark invalid blocks bad. Maybe distinguish?
log.Warn("adding forked chain to our bad tipset cache")
for _, b := range incoming.Blocks() {
syncer.bad.Add(b.Cid(), NewBadBlockReason(incoming.Cids(), "fork past finality"))
}
}
return nil, xerrors.Errorf("failed to sync fork: %w", err)
}
blockSet = append(blockSet, fork...)
base := blockSet[len(blockSet)-1]
if base.Parents() == known.Parents() {
// common case: receiving a block thats potentially part of the same tipset as our best block
return blockSet, nil
}
if types.CidArrsEqual(base.Parents().Cids(), known.Cids()) {
// common case: receiving blocks that are building on top of our best tipset
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)", incoming.Cids(), incoming.Height(), known.Cids(), known.Height())
fork, err := syncer.syncFork(ctx, base, known)
if err != nil {
if xerrors.Is(err, ErrForkTooLong) {
// TODO: we're marking this block bad in the same way that we mark invalid blocks bad. Maybe distinguish?
log.Warn("adding forked chain to our bad tipset cache")
for _, b := range incoming.Blocks() {
syncer.bad.Add(b.Cid(), NewBadBlockReason(incoming.Cids(), "fork past finality"))
}
}
return nil, xerrors.Errorf("failed to sync fork: %w", err)
}
blockSet = append(blockSet, fork...)
return blockSet, nil
}
@ -1291,7 +1355,7 @@ func (syncer *Syncer) syncFork(ctx context.Context, incoming *types.TipSet, know
if !syncer.Genesis.Equals(nts) {
return nil, xerrors.Errorf("somehow synced chain that linked back to a different genesis (bad genesis: %s)", nts.Key())
}
return nil, xerrors.Errorf("synced chain forked at genesis, refusing to sync; incoming: %s")
return nil, xerrors.Errorf("synced chain forked at genesis, refusing to sync; incoming: %s", incoming.Cids())
}
if nts.Equals(tips[cur]) {
@ -1356,7 +1420,7 @@ func (syncer *Syncer) iterFullTipsets(ctx context.Context, headers []*types.TipS
nextI := (i + 1) - batchSize // want to fetch batchSize values, 'i' points to last one we want to fetch, so its 'inclusive' of our request, thus we need to add one to our request start index
var bstout []*blocksync.BSTipSet
var bstout []*blocksync.CompactedMessages
for len(bstout) < batchSize {
next := headers[nextI]
@ -1372,16 +1436,15 @@ func (syncer *Syncer) iterFullTipsets(ctx context.Context, headers []*types.TipS
for bsi := 0; bsi < len(bstout); bsi++ {
// temp storage so we don't persist data we dont want to
ds := dstore.NewMapDatastore()
bs := bstore.NewBlockstore(ds)
bs := bstore.NewTemporary()
blks := cbor.NewCborStore(bs)
this := headers[i-bsi]
bstip := bstout[len(bstout)-(bsi+1)]
fts, err := zipTipSetAndMessages(blks, this, bstip.BlsMessages, bstip.SecpkMessages, bstip.BlsMsgIncludes, bstip.SecpkMsgIncludes)
fts, err := zipTipSetAndMessages(blks, this, bstip.Bls, bstip.Secpk, bstip.BlsIncludes, bstip.SecpkIncludes)
if err != nil {
log.Warnw("zipping failed", "error", err, "bsi", bsi, "i", i,
"height", this.Height(), "bstip-height", bstip.Blocks[0].Height,
"height", this.Height(),
"next-height", i+batchSize)
return xerrors.Errorf("message processing failed: %w", err)
}
@ -1404,15 +1467,15 @@ func (syncer *Syncer) iterFullTipsets(ctx context.Context, headers []*types.TipS
return nil
}
func persistMessages(bs bstore.Blockstore, bst *blocksync.BSTipSet) error {
for _, m := range bst.BlsMessages {
func persistMessages(bs bstore.Blockstore, bst *blocksync.CompactedMessages) error {
for _, m := range bst.Bls {
//log.Infof("putting BLS message: %s", m.Cid())
if _, err := store.PutMessage(bs, m); err != nil {
log.Errorf("failed to persist messages: %+v", err)
return xerrors.Errorf("BLS message processing failed: %w", err)
}
}
for _, m := range bst.SecpkMessages {
for _, m := range bst.Secpk {
if m.Signature.Type != crypto.SigTypeSecp256k1 {
return xerrors.Errorf("unknown signature type on message %s: %q", m.Cid(), m.Signature.Type)
}

View File

@ -7,6 +7,7 @@ import (
"testing"
"time"
ds "github.com/ipfs/go-datastore"
logging "github.com/ipfs/go-log/v2"
"github.com/libp2p/go-libp2p-core/peer"
mocknet "github.com/libp2p/go-libp2p/p2p/net/mock"
@ -22,6 +23,7 @@ import (
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/gen"
"github.com/filecoin-project/lotus/chain/gen/slashfilter"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
mocktypes "github.com/filecoin-project/lotus/chain/types/mock"
@ -170,7 +172,7 @@ func (tu *syncTestUtil) pushTsExpectErr(to int, fts *store.FullTipSet, experr bo
}
}
func (tu *syncTestUtil) mineOnBlock(blk *store.FullTipSet, src int, miners []int, wait, fail bool) *store.FullTipSet {
func (tu *syncTestUtil) mineOnBlock(blk *store.FullTipSet, to int, miners []int, wait, fail bool) *store.FullTipSet {
if miners == nil {
for i := range tu.g.Miners {
miners = append(miners, i)
@ -188,9 +190,9 @@ func (tu *syncTestUtil) mineOnBlock(blk *store.FullTipSet, src int, miners []int
require.NoError(tu.t, err)
if fail {
tu.pushTsExpectErr(src, mts.TipSet, true)
tu.pushTsExpectErr(to, mts.TipSet, true)
} else {
tu.pushFtsAndWait(src, mts.TipSet, wait)
tu.pushFtsAndWait(to, mts.TipSet, wait)
}
return mts.TipSet
@ -419,6 +421,8 @@ func TestSyncBadTimestamp(t *testing.T) {
tu.g.Timestamper = nil
require.NoError(t, tu.g.ResyncBankerNonce(a1.TipSet()))
tu.nds[0].(*impl.FullNodeAPI).SlashFilter = slashfilter.New(ds.NewMapDatastore())
fmt.Println("After mine bad block!")
tu.printHeads()
a2 := tu.mineOnBlock(base, 0, nil, true, false)
@ -433,6 +437,41 @@ func TestSyncBadTimestamp(t *testing.T) {
}
}
type badWpp struct{}
func (wpp badWpp) GenerateCandidates(context.Context, abi.PoStRandomness, uint64) ([]uint64, error) {
return []uint64{1}, nil
}
func (wpp badWpp) ComputeProof(context.Context, []abi.SectorInfo, abi.PoStRandomness) ([]abi.PoStProof, error) {
return []abi.PoStProof{
abi.PoStProof{
PoStProof: abi.RegisteredPoStProof_StackedDrgWinning2KiBV1,
ProofBytes: []byte("evil"),
},
}, nil
}
func TestSyncBadWinningPoSt(t *testing.T) {
H := 15
tu := prepSyncTest(t, H)
client := tu.addClientNode()
require.NoError(t, tu.mn.LinkAll())
tu.connect(client, 0)
tu.waitUntilSync(0, client)
base := tu.g.CurTipset
// both miners now produce invalid winning posts
tu.g.SetWinningPoStProver(tu.g.Miners[0], &badWpp{})
tu.g.SetWinningPoStProver(tu.g.Miners[1], &badWpp{})
// now ensure that new blocks are not accepted
tu.mineOnBlock(base, client, nil, false, true)
}
func (tu *syncTestUtil) loadChainToNode(to int) {
// utility to simulate incoming blocks without miner process
// TODO: should call syncer directly, this won't work correctly in all cases

View File

@ -11,7 +11,7 @@ import (
const BigIntMaxSerializedLen = 128 // is this big enough? or too big?
var TotalFilecoinInt = FromFil(build.TotalFilecoin)
var TotalFilecoinInt = FromFil(build.FilBase)
var EmptyInt = BigInt{}

View File

@ -11,7 +11,6 @@ import (
block "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
"github.com/multiformats/go-multihash"
xerrors "golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
@ -66,6 +65,9 @@ type BlockHeader struct {
ForkSignaling uint64 // 14
// ParentBaseFee is the base fee after executing parent tipset
ParentBaseFee abi.TokenAmount // 15
// internal
validated bool // true if the signature has been validated
}
@ -76,8 +78,7 @@ func (blk *BlockHeader) ToStorageBlock() (block.Block, error) {
return nil, err
}
pref := cid.NewPrefixV1(cid.DagCBOR, multihash.BLAKE2B_MIN+31)
c, err := pref.Sum(data)
c, err := abi.CidBuilder.Sum(data)
if err != nil {
return nil, err
}
@ -145,13 +146,12 @@ func (mm *MsgMeta) Cid() cid.Cid {
}
func (mm *MsgMeta) ToStorageBlock() (block.Block, error) {
buf := new(bytes.Buffer)
if err := mm.MarshalCBOR(buf); err != nil {
var buf bytes.Buffer
if err := mm.MarshalCBOR(&buf); err != nil {
return nil, xerrors.Errorf("failed to marshal MsgMeta: %w", err)
}
pref := cid.NewPrefixV1(cid.DagCBOR, multihash.BLAKE2B_MIN+31)
c, err := pref.Sum(buf.Bytes())
c, err := abi.CidBuilder.Sum(buf.Bytes())
if err != nil {
return nil, err
}
@ -178,6 +178,21 @@ func CidArrsEqual(a, b []cid.Cid) bool {
return true
}
func CidArrsSubset(a, b []cid.Cid) bool {
// order ignoring compare...
s := make(map[cid.Cid]bool)
for _, c := range b {
s[c] = true
}
for _, c := range a {
if !s[c] {
return false
}
}
return true
}
func CidArrsContains(a []cid.Cid, b cid.Cid) bool {
for _, elem := range a {
if elem.Equals(b) {

View File

@ -44,6 +44,7 @@ func testBlockHeader(t testing.TB) *BlockHeader {
Height: 85919298723,
ParentStateRoot: c,
BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS, Data: []byte("boo! im a signature")},
ParentBaseFee: NewInt(3432432843291),
}
}
@ -107,7 +108,8 @@ func TestInteropBH(t *testing.T) {
Type: crypto.SigTypeBLS,
Data: []byte{0x3},
},
BLSAggregate: &crypto.Signature{},
BLSAggregate: &crypto.Signature{},
ParentBaseFee: NewInt(1000000000),
}
bhsb, err := bh.SigningBytes()
@ -116,7 +118,7 @@ func TestInteropBH(t *testing.T) {
t.Fatal(err)
}
gfc := "8f5501d04cb15021bf6bd003073d79e2238d4e61f1ad2281430102038200420a0b818205410c818200410781d82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619cc430003e802d82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619ccd82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619ccd82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619cc410001f603"
gfc := "905501d04cb15021bf6bd003073d79e2238d4e61f1ad2281430102038200420a0b818205410c818200410781d82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619cc430003e802d82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619ccd82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619ccd82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619cc410001f60345003b9aca00"
require.Equal(t, gfc, hex.EncodeToString(bhsb))
}

View File

@ -16,7 +16,7 @@ import (
var _ = xerrors.Errorf
var lengthBufBlockHeader = []byte{143}
var lengthBufBlockHeader = []byte{144}
func (t *BlockHeader) MarshalCBOR(w io.Writer) error {
if t == nil {
@ -142,6 +142,10 @@ func (t *BlockHeader) MarshalCBOR(w io.Writer) error {
return err
}
// t.ParentBaseFee (big.Int) (struct)
if err := t.ParentBaseFee.MarshalCBOR(w); err != nil {
return err
}
return nil
}
@ -159,7 +163,7 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 15 {
if extra != 16 {
return fmt.Errorf("cbor input had wrong number of fields")
}
@ -439,6 +443,15 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error {
}
t.ForkSignaling = uint64(extra)
}
// t.ParentBaseFee (big.Int) (struct)
{
if err := t.ParentBaseFee.UnmarshalCBOR(br); err != nil {
return xerrors.Errorf("unmarshaling t.ParentBaseFee: %w", err)
}
}
return nil
}
@ -465,7 +478,7 @@ func (t *Ticket) MarshalCBOR(w io.Writer) error {
return err
}
if _, err := w.Write(t.VRFProof); err != nil {
if _, err := w.Write(t.VRFProof[:]); err != nil {
return err
}
return nil
@ -502,8 +515,12 @@ func (t *Ticket) UnmarshalCBOR(r io.Reader) error {
if maj != cbg.MajByteString {
return fmt.Errorf("expected byte array")
}
t.VRFProof = make([]byte, extra)
if _, err := io.ReadFull(br, t.VRFProof); err != nil {
if extra > 0 {
t.VRFProof = make([]uint8, extra)
}
if _, err := io.ReadFull(br, t.VRFProof[:]); err != nil {
return err
}
return nil
@ -542,7 +559,7 @@ func (t *ElectionProof) MarshalCBOR(w io.Writer) error {
return err
}
if _, err := w.Write(t.VRFProof); err != nil {
if _, err := w.Write(t.VRFProof[:]); err != nil {
return err
}
return nil
@ -604,14 +621,18 @@ func (t *ElectionProof) UnmarshalCBOR(r io.Reader) error {
if maj != cbg.MajByteString {
return fmt.Errorf("expected byte array")
}
t.VRFProof = make([]byte, extra)
if _, err := io.ReadFull(br, t.VRFProof); err != nil {
if extra > 0 {
t.VRFProof = make([]uint8, extra)
}
if _, err := io.ReadFull(br, t.VRFProof[:]); err != nil {
return err
}
return nil
}
var lengthBufMessage = []byte{137}
var lengthBufMessage = []byte{138}
func (t *Message) MarshalCBOR(w io.Writer) error {
if t == nil {
@ -656,11 +677,6 @@ func (t *Message) MarshalCBOR(w io.Writer) error {
return err
}
// t.GasPrice (big.Int) (struct)
if err := t.GasPrice.MarshalCBOR(w); err != nil {
return err
}
// t.GasLimit (int64) (int64)
if t.GasLimit >= 0 {
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.GasLimit)); err != nil {
@ -672,6 +688,16 @@ func (t *Message) MarshalCBOR(w io.Writer) error {
}
}
// t.GasFeeCap (big.Int) (struct)
if err := t.GasFeeCap.MarshalCBOR(w); err != nil {
return err
}
// t.GasPremium (big.Int) (struct)
if err := t.GasPremium.MarshalCBOR(w); err != nil {
return err
}
// t.Method (abi.MethodNum) (uint64)
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Method)); err != nil {
@ -687,7 +713,7 @@ func (t *Message) MarshalCBOR(w io.Writer) error {
return err
}
if _, err := w.Write(t.Params); err != nil {
if _, err := w.Write(t.Params[:]); err != nil {
return err
}
return nil
@ -707,7 +733,7 @@ func (t *Message) UnmarshalCBOR(r io.Reader) error {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 9 {
if extra != 10 {
return fmt.Errorf("cbor input had wrong number of fields")
}
@ -776,15 +802,6 @@ func (t *Message) UnmarshalCBOR(r io.Reader) error {
return xerrors.Errorf("unmarshaling t.Value: %w", err)
}
}
// t.GasPrice (big.Int) (struct)
{
if err := t.GasPrice.UnmarshalCBOR(br); err != nil {
return xerrors.Errorf("unmarshaling t.GasPrice: %w", err)
}
}
// t.GasLimit (int64) (int64)
{
@ -811,6 +828,24 @@ func (t *Message) UnmarshalCBOR(r io.Reader) error {
t.GasLimit = int64(extraI)
}
// t.GasFeeCap (big.Int) (struct)
{
if err := t.GasFeeCap.UnmarshalCBOR(br); err != nil {
return xerrors.Errorf("unmarshaling t.GasFeeCap: %w", err)
}
}
// t.GasPremium (big.Int) (struct)
{
if err := t.GasPremium.UnmarshalCBOR(br); err != nil {
return xerrors.Errorf("unmarshaling t.GasPremium: %w", err)
}
}
// t.Method (abi.MethodNum) (uint64)
{
@ -838,8 +873,12 @@ func (t *Message) UnmarshalCBOR(r io.Reader) error {
if maj != cbg.MajByteString {
return fmt.Errorf("expected byte array")
}
t.Params = make([]byte, extra)
if _, err := io.ReadFull(br, t.Params); err != nil {
if extra > 0 {
t.Params = make([]uint8, extra)
}
if _, err := io.ReadFull(br, t.Params[:]); err != nil {
return err
}
return nil
@ -1119,7 +1158,7 @@ func (t *MessageReceipt) MarshalCBOR(w io.Writer) error {
return err
}
if _, err := w.Write(t.Return); err != nil {
if _, err := w.Write(t.Return[:]); err != nil {
return err
}
@ -1192,8 +1231,12 @@ func (t *MessageReceipt) UnmarshalCBOR(r io.Reader) error {
if maj != cbg.MajByteString {
return fmt.Errorf("expected byte array")
}
t.Return = make([]byte, extra)
if _, err := io.ReadFull(br, t.Return); err != nil {
if extra > 0 {
t.Return = make([]uint8, extra)
}
if _, err := io.ReadFull(br, t.Return[:]); err != nil {
return err
}
// t.GasUsed (int64) (int64)
@ -1555,7 +1598,7 @@ func (t *BeaconEntry) MarshalCBOR(w io.Writer) error {
return err
}
if _, err := w.Write(t.Data); err != nil {
if _, err := w.Write(t.Data[:]); err != nil {
return err
}
return nil
@ -1606,8 +1649,12 @@ func (t *BeaconEntry) UnmarshalCBOR(r io.Reader) error {
if maj != cbg.MajByteString {
return fmt.Errorf("expected byte array")
}
t.Data = make([]byte, extra)
if _, err := io.ReadFull(br, t.Data); err != nil {
if extra > 0 {
t.Data = make([]uint8, extra)
}
if _, err := io.ReadFull(br, t.Data[:]); err != nil {
return err
}
return nil

View File

@ -9,7 +9,6 @@ import (
"github.com/filecoin-project/specs-actors/actors/abi/big"
block "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
"github.com/multiformats/go-multihash"
xerrors "golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
@ -33,10 +32,11 @@ type Message struct {
Nonce uint64
Value BigInt
Value abi.TokenAmount
GasPrice BigInt
GasLimit int64
GasLimit int64
GasFeeCap abi.TokenAmount
GasPremium abi.TokenAmount
Method abi.MethodNum
Params []byte
@ -89,8 +89,7 @@ func (m *Message) ToStorageBlock() (block.Block, error) {
return nil, err
}
pref := cid.NewPrefixV1(cid.DagCBOR, multihash.BLAKE2B_MIN+31)
c, err := pref.Sum(data)
c, err := abi.CidBuilder.Sum(data)
if err != nil {
return nil, err
}
@ -108,10 +107,7 @@ func (m *Message) Cid() cid.Cid {
}
func (m *Message) RequiredFunds() BigInt {
return BigAdd(
m.Value,
BigMul(m.GasPrice, NewInt(uint64(m.GasLimit))),
)
return BigMul(m.GasFeeCap, NewInt(uint64(m.GasLimit)))
}
func (m *Message) VMMessage() *Message {
@ -143,8 +139,12 @@ func (m *Message) ValidForBlockInclusion(minGas int64) error {
return xerrors.New("'Value' field cannot be greater than total filecoin supply")
}
if m.GasPrice.LessThan(big.Zero()) {
return xerrors.New("'GasPrice' field cannot be negative")
if m.GasFeeCap.LessThan(big.Zero()) {
return xerrors.New("'GasFeeCap' field cannot be negative")
}
if m.GasPremium.LessThan(big.Zero()) {
return xerrors.New("'GasPremium' field cannot be negative")
}
if m.GasLimit > build.BlockGasLimit {

View File

@ -23,12 +23,13 @@ func Address(i uint64) address.Address {
func MkMessage(from, to address.Address, nonce uint64, w *wallet.Wallet) *types.SignedMessage {
msg := &types.Message{
To: to,
From: from,
Value: types.NewInt(1),
Nonce: nonce,
GasLimit: 1,
GasPrice: types.NewInt(0),
To: to,
From: from,
Value: types.NewInt(1),
Nonce: nonce,
GasLimit: 1000000,
GasFeeCap: types.NewInt(100),
GasPremium: types.NewInt(1),
}
sig, err := w.Sign(context.TODO(), from, msg.Cid().Bytes())

22
chain/types/mpool.go Normal file
View File

@ -0,0 +1,22 @@
package types
import (
"time"
"github.com/filecoin-project/go-address"
)
type MpoolConfig struct {
PriorityAddrs []address.Address
SizeLimitHigh int
SizeLimitLow int
ReplaceByFeeRatio float64
PruneCooldown time.Duration
GasLimitOverestimation float64
}
func (mc *MpoolConfig) Clone() *MpoolConfig {
r := new(MpoolConfig)
*r = *mc
return r
}

View File

@ -3,10 +3,10 @@ package types
import (
"bytes"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/crypto"
block "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
"github.com/multiformats/go-multihash"
)
func (sm *SignedMessage) ToStorageBlock() (block.Block, error) {
@ -19,8 +19,7 @@ func (sm *SignedMessage) ToStorageBlock() (block.Block, error) {
return nil, err
}
pref := cid.NewPrefixV1(cid.DagCBOR, multihash.BLAKE2B_MIN+31)
c, err := pref.Sum(data)
c, err := abi.CidBuilder.Sum(data)
if err != nil {
return nil, err
}

View File

@ -97,6 +97,12 @@ func tipsetSortFunc(blks []*BlockHeader) func(i, j int) bool {
}
}
// Checks:
// * A tipset is composed of at least one block. (Because of our variable
// number of blocks per tipset, determined by randomness, we do not impose
// an upper limit.)
// * All blocks have the same height.
// * All blocks have the same parents (same number of them and matching CIDs).
func NewTipSet(blks []*BlockHeader) (*TipSet, error) {
if len(blks) == 0 {
return nil, xerrors.Errorf("NewTipSet called with zero length array of blocks")
@ -224,3 +230,11 @@ func (ts *TipSet) Contains(oc cid.Cid) bool {
}
return false
}
func (ts *TipSet) IsChildOf(parent *TipSet) bool {
return CidArrsEqual(ts.Parents().Cids(), parent.Cids()) &&
// FIXME: The height check might go beyond what is meant by
// "parent", but many parts of the code rely on the tipset's
// height for their processing logic at the moment to obviate it.
ts.height > parent.height
}

View File

@ -5,8 +5,8 @@ import (
"encoding/json"
"strings"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/ipfs/go-cid"
"github.com/multiformats/go-multihash"
)
var EmptyTSK = TipSetKey{}
@ -15,7 +15,9 @@ var EmptyTSK = TipSetKey{}
var blockHeaderCIDLen int
func init() {
c, err := cid.V1Builder{Codec: cid.DagCBOR, MhType: multihash.BLAKE2B_MIN + 31}.Sum([]byte{})
// hash a large string of zeros so we don't estimate based on inlined CIDs.
var buf [256]byte
c, err := abi.CidBuilder.Sum(buf[:])
if err != nil {
panic(err)
}

View File

@ -22,13 +22,14 @@ func blsaddr(n int64) address.Address {
func BenchmarkSerializeMessage(b *testing.B) {
m := &Message{
To: blsaddr(1),
From: blsaddr(2),
Nonce: 197,
Method: 1231254,
Params: []byte("some bytes, idk. probably at least ten of them"),
GasLimit: 126723,
GasPrice: NewInt(1776234),
To: blsaddr(1),
From: blsaddr(2),
Nonce: 197,
Method: 1231254,
Params: []byte("some bytes, idk. probably at least ten of them"),
GasLimit: 126723,
GasPremium: NewInt(1245667),
GasFeeCap: NewInt(1245667),
}
b.ReportAllocs()

View File

@ -13,14 +13,15 @@ func TestSignedMessageJsonRoundtrip(t *testing.T) {
from, _ := address.NewIDAddress(603911192)
smsg := &types.SignedMessage{
Message: types.Message{
To: to,
From: from,
Params: []byte("some bytes, idk"),
Method: 1235126,
Value: types.NewInt(123123),
GasPrice: types.NewInt(1234),
GasLimit: 100_000_000,
Nonce: 123123,
To: to,
From: from,
Params: []byte("some bytes, idk"),
Method: 1235126,
Value: types.NewInt(123123),
GasFeeCap: types.NewInt(1234),
GasPremium: types.NewInt(132414234),
GasLimit: 100_000_000,
Nonce: 123123,
},
}

View File

@ -10,7 +10,6 @@ import (
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/filecoin-project/specs-actors/actors/puppet"
"github.com/filecoin-project/specs-actors/actors/runtime"
"github.com/ipfs/go-cid"
vtypes "github.com/filecoin-project/chain-validation/chain/types"
@ -26,12 +25,12 @@ import (
// Applier applies messages to state trees and storage.
type Applier struct {
stateWrapper *StateWrapper
syscalls runtime.Syscalls
syscalls vm.SyscallBuilder
}
var _ vstate.Applier = &Applier{}
func NewApplier(sw *StateWrapper, syscalls runtime.Syscalls) *Applier {
func NewApplier(sw *StateWrapper, syscalls vm.SyscallBuilder) *Applier {
return &Applier{sw, syscalls}
}
@ -39,6 +38,7 @@ func (a *Applier) ApplyMessage(epoch abi.ChainEpoch, message *vtypes.Message) (v
lm := toLotusMsg(message)
receipt, penalty, reward, err := a.applyMessage(epoch, lm)
return vtypes.ApplyMessageResult{
Msg: *message,
Receipt: receipt,
Penalty: penalty,
Reward: reward,
@ -59,6 +59,7 @@ func (a *Applier) ApplySignedMessage(epoch abi.ChainEpoch, msg *vtypes.SignedMes
// TODO: Validate the sig first
receipt, penalty, reward, err := a.applyMessage(epoch, lm)
return vtypes.ApplyMessageResult{
Msg: msg.Message,
Receipt: receipt,
Penalty: penalty,
Reward: reward,
@ -90,7 +91,8 @@ func (a *Applier) ApplyTipSetMessages(epoch abi.ChainEpoch, blocks []vtypes.Bloc
}
var receipts []vtypes.MessageReceipt
sroot, _, err := sm.ApplyBlocks(context.TODO(), a.stateWrapper.Root(), bms, epoch, &randWrapper{rnd}, func(c cid.Cid, msg *types.Message, ret *vm.ApplyRet) error {
// TODO: base fee
sroot, _, err := sm.ApplyBlocks(context.TODO(), epoch-1, a.stateWrapper.Root(), bms, epoch, &randWrapper{rnd}, func(c cid.Cid, msg *types.Message, ret *vm.ApplyRet) error {
if msg.From == builtin.SystemActorAddr {
return nil // ignore reward and cron calls
}
@ -105,7 +107,7 @@ func (a *Applier) ApplyTipSetMessages(epoch abi.ChainEpoch, blocks []vtypes.Bloc
GasUsed: vtypes.GasUnits(ret.GasUsed),
})
return nil
})
}, abi.NewTokenAmount(100))
if err != nil {
return vtypes.ApplyTipSetResult{}, err
}
@ -137,7 +139,17 @@ func (a *Applier) applyMessage(epoch abi.ChainEpoch, lm types.ChainMsg) (vtypes.
ctx := context.TODO()
base := a.stateWrapper.Root()
lotusVM, err := vm.NewVM(base, epoch, &vmRand{}, a.stateWrapper.bs, a.syscalls)
vmopt := &vm.VMOpts{
StateBase: base,
Epoch: epoch,
Rand: &vmRand{},
Bstore: a.stateWrapper.bs,
Syscalls: a.syscalls,
VestedCalc: nil,
BaseFee: abi.NewTokenAmount(100),
}
lotusVM, err := vm.NewVM(vmopt)
// need to modify the VM invoker to add the puppet actor
chainValInvoker := vm.NewInvoker()
chainValInvoker.Register(puppet.PuppetActorCodeID, puppet.Actor{}, puppet.State{})
@ -178,9 +190,10 @@ func toLotusMsg(msg *vtypes.Message) *types.Message {
Nonce: msg.CallSeqNum,
Method: msg.Method,
Value: types.BigInt{Int: msg.Value.Int},
GasPrice: types.BigInt{Int: msg.GasPrice.Int},
GasLimit: msg.GasLimit,
Value: msg.Value,
GasLimit: msg.GasLimit,
GasFeeCap: msg.GasFeeCap,
GasPremium: msg.GasPremium,
Params: msg.Params,
}

View File

@ -1,7 +1,10 @@
package validation
import (
"context"
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/specs-actors/actors/runtime"
cbor "github.com/ipfs/go-ipld-cbor"
vstate "github.com/filecoin-project/chain-validation/state"
)
@ -18,7 +21,9 @@ func NewFactories() *Factories {
func (f *Factories) NewStateAndApplier(syscalls runtime.Syscalls) (vstate.VMWrapper, vstate.Applier) {
st := NewState()
return st, NewApplier(st, syscalls)
return st, NewApplier(st, func(ctx context.Context, cstate *state.StateTree, cst cbor.IpldStore) runtime.Syscalls {
return syscalls
})
}
func (f *Factories) NewKeyManager() vstate.KeyManager {

View File

@ -3,20 +3,20 @@ package validation
import (
"context"
"github.com/filecoin-project/go-address"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-datastore"
blockstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
"golang.org/x/xerrors"
vstate "github.com/filecoin-project/chain-validation/state"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/filecoin-project/specs-actors/actors/runtime"
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/lib/blockstore"
)
var _ vstate.VMWrapper = &StateWrapper{}
@ -34,7 +34,7 @@ type StateWrapper struct {
}
func NewState() *StateWrapper {
bs := blockstore.NewBlockstore(datastore.NewMapDatastore())
bs := blockstore.NewTemporary()
cst := cbor.NewCborStore(bs)
// Put EmptyObjectCid value in the store. When an actor is initially created its Head is set to this value.
_, err := cst.Put(context.TODO(), map[string]string{})

View File

@ -17,6 +17,7 @@ import (
"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/power"
"github.com/filecoin-project/specs-actors/actors/builtin/verifreg"
"github.com/filecoin-project/specs-actors/actors/crypto"
_ "github.com/filecoin-project/lotus/lib/sigs/bls"
@ -24,6 +25,7 @@ import (
)
func init() {
verifreg.MinVerifiedDealSize = big.NewInt(2048)
power.ConsensusMinerMinPower = big.NewInt(2048)
}
@ -137,7 +139,8 @@ func MakeUnsignedMessageVectors() []vectors.UnsignedMessageVector {
if err != nil {
panic(err)
}
to, err := address.NewIDAddress(rand.Uint64())
uint63mask := uint64(1<<63 - 1)
to, err := address.NewIDAddress(rand.Uint64() & uint63mask)
if err != nil {
panic(err)
}
@ -146,14 +149,15 @@ func MakeUnsignedMessageVectors() []vectors.UnsignedMessageVector {
rand.Read(params)
msg := &types.Message{
To: to,
From: from,
Value: types.NewInt(rand.Uint64()),
Method: abi.MethodNum(rand.Uint64()),
GasPrice: types.NewInt(rand.Uint64()),
GasLimit: rand.Int63(),
Nonce: rand.Uint64(),
Params: params,
To: to,
From: from,
Value: types.NewInt(rand.Uint64()),
Method: abi.MethodNum(rand.Uint64()),
GasFeeCap: types.NewInt(rand.Uint64()),
GasPremium: types.NewInt(rand.Uint64()),
GasLimit: rand.Int63(),
Nonce: rand.Uint64() & (1<<63 - 1),
Params: params,
}
ser, err := msg.Serialize()

View File

@ -18,6 +18,7 @@ func LoadVector(t *testing.T, f string, out interface{}) {
if err != nil {
t.Fatal(err)
}
defer fi.Close()
if err := json.NewDecoder(fi).Decode(out); err != nil {
t.Fatal(err)
@ -64,6 +65,8 @@ func TestMessageSigningVectors(t *testing.T) {
}
func TestUnsignedMessageVectors(t *testing.T) {
t.Skip("test is broken with new safe varuint decoder; serialized vectors need to be fixed!")
var msvs []UnsignedMessageVector
LoadVector(t, "unsigned_messages.json", &msvs)

97
chain/vm/burn.go Normal file
View File

@ -0,0 +1,97 @@
package vm
import (
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
)
const (
gasOveruseNum = 11
gasOveruseDenom = 10
)
type GasOutputs struct {
BaseFeeBurn abi.TokenAmount
OverEstimationBurn abi.TokenAmount
MinerPenalty abi.TokenAmount
MinerTip abi.TokenAmount
Refund abi.TokenAmount
GasRefund int64
GasBurned int64
}
// ComputeGasOverestimationBurn computes amount of gas to be refunded and amount of gas to be burned
// Result is (refund, burn)
func ComputeGasOverestimationBurn(gasUsed, gasLimit int64) (int64, int64) {
if gasUsed == 0 {
return 0, gasLimit
}
// over = gasLimit/gasUsed - 1 - 0.1
// over = min(over, 1)
// gasToBurn = (gasLimit - gasUsed) * over
// so to factor out division from `over`
// over*gasUsed = min(gasLimit - (11*gasUsed)/10, gasUsed)
// gasToBurn = ((gasLimit - gasUsed)*over*gasUsed) / gasUsed
over := gasLimit - (gasOveruseNum*gasUsed)/gasOveruseDenom
if over < 0 {
return gasLimit - gasUsed, 0
}
// if we want sharper scaling it goes here:
// over *= 2
if over > gasUsed {
over = gasUsed
}
// needs bigint, as it overflows in pathological case gasLimit > 2^32 gasUsed = gasLimit / 2
gasToBurn := big.NewInt(gasLimit - gasUsed)
gasToBurn = big.Mul(gasToBurn, big.NewInt(over))
gasToBurn = big.Div(gasToBurn, big.NewInt(gasUsed))
return gasLimit - gasUsed - gasToBurn.Int64(), gasToBurn.Int64()
}
func ComputeGasOutputs(gasUsed, gasLimit int64, baseFee, feeCap, gasPremium abi.TokenAmount) GasOutputs {
gasUsedBig := big.NewInt(gasUsed)
out := GasOutputs{
BaseFeeBurn: big.Zero(),
OverEstimationBurn: big.Zero(),
MinerPenalty: big.Zero(),
MinerTip: big.Zero(),
Refund: big.Zero(),
}
baseFeeToPay := baseFee
if baseFee.Cmp(feeCap.Int) > 0 {
baseFeeToPay = feeCap
out.MinerPenalty = big.Mul(big.Sub(baseFee, feeCap), gasUsedBig)
}
out.BaseFeeBurn = big.Mul(baseFeeToPay, gasUsedBig)
minerTip := gasPremium
if big.Cmp(big.Add(baseFeeToPay, minerTip), feeCap) > 0 {
minerTip = big.Sub(feeCap, baseFeeToPay)
}
out.MinerTip = big.Mul(minerTip, big.NewInt(gasLimit))
out.GasRefund, out.GasBurned = ComputeGasOverestimationBurn(gasUsed, gasLimit)
if out.GasBurned != 0 {
gasBurnedBig := big.NewInt(out.GasBurned)
out.OverEstimationBurn = big.Mul(baseFeeToPay, gasBurnedBig)
minerPenalty := big.Mul(big.Sub(baseFee, baseFeeToPay), gasBurnedBig)
out.MinerPenalty = big.Add(out.MinerPenalty, minerPenalty)
}
requiredFunds := big.Mul(big.NewInt(gasLimit), feeCap)
refund := big.Sub(requiredFunds, out.BaseFeeBurn)
refund = big.Sub(refund, out.MinerTip)
refund = big.Sub(refund, out.OverEstimationBurn)
out.Refund = refund
return out
}

78
chain/vm/burn_test.go Normal file
View File

@ -0,0 +1,78 @@
package vm
import (
"fmt"
"testing"
"github.com/filecoin-project/lotus/chain/types"
"github.com/stretchr/testify/assert"
)
func TestGasBurn(t *testing.T) {
tests := []struct {
used int64
limit int64
refund int64
burn int64
}{
{100, 200, 10, 90},
{100, 150, 30, 20},
{1000, 1300, 240, 60},
{500, 700, 140, 60},
{200, 200, 0, 0},
{20000, 21000, 1000, 0},
{0, 2000, 0, 2000},
{500, 651, 121, 30},
{500, 5000, 0, 4500},
{7499e6, 7500e6, 1000000, 0},
{7500e6 / 2, 7500e6, 375000000, 3375000000},
{1, 7500e6, 0, 7499999999},
}
for _, test := range tests {
test := test
t.Run(fmt.Sprintf("%v", test), func(t *testing.T) {
refund, toBurn := ComputeGasOverestimationBurn(test.used, test.limit)
assert.Equal(t, test.refund, refund, "refund")
assert.Equal(t, test.burn, toBurn, "burned")
})
}
}
func TestGasOutputs(t *testing.T) {
baseFee := types.NewInt(10)
tests := []struct {
used int64
limit int64
feeCap uint64
premium uint64
BaseFeeBurn uint64
OverEstimationBurn uint64
MinerPenalty uint64
MinerTip uint64
Refund uint64
}{
{100, 110, 11, 1, 1000, 0, 0, 110, 100},
{100, 130, 11, 1, 1000, 60, 0, 130, 240},
{100, 110, 10, 1, 1000, 0, 0, 0, 100},
{100, 110, 6, 1, 600, 0, 400, 0, 60},
}
for _, test := range tests {
test := test
t.Run(fmt.Sprintf("%v", test), func(t *testing.T) {
output := ComputeGasOutputs(test.used, test.limit, baseFee, types.NewInt(test.feeCap), types.NewInt(test.premium))
i2s := func(i uint64) string {
return fmt.Sprintf("%d", i)
}
assert.Equal(t, i2s(test.BaseFeeBurn), output.BaseFeeBurn.String(), "BaseFeeBurn")
assert.Equal(t, i2s(test.OverEstimationBurn), output.OverEstimationBurn.String(), "OverEstimationBurn")
assert.Equal(t, i2s(test.MinerPenalty), output.MinerPenalty.String(), "MinerPenalty")
assert.Equal(t, i2s(test.MinerTip), output.MinerTip.String(), "MinerTip")
assert.Equal(t, i2s(test.Refund), output.Refund.String(), "Refund")
})
}
}

View File

@ -13,7 +13,7 @@ import (
)
const (
GasStorageMulti = 1
GasStorageMulti = 1000
GasComputeMulti = 1
)
@ -84,35 +84,48 @@ type Pricelist interface {
var prices = map[abi.ChainEpoch]Pricelist{
abi.ChainEpoch(0): &pricelistV0{
onChainMessageComputeBase: 137137,
onChainMessageStorageBase: 0, // TODO gas
onChainMessageStoragePerByte: 2, // TODO gas
onChainMessageComputeBase: 38863,
onChainMessageStorageBase: 36,
onChainMessageStoragePerByte: 1,
onChainReturnValuePerByte: 8, // TODO gas
onChainReturnValuePerByte: 1,
sendBase: 97236,
sendTransferFunds: 96812,
sendTransferOnlyPremium: 347806,
sendInvokeMethod: -3110,
sendBase: 29233,
sendTransferFunds: 27500,
sendTransferOnlyPremium: 159672,
sendInvokeMethod: -5377,
ipldGetBase: 417230,
ipldPutBase: 396100,
ipldPutPerByte: 2, // TODO gas
ipldGetBase: 75242,
ipldPutBase: 84070,
ipldPutPerByte: 1,
createActorCompute: 750011,
createActorStorage: 500, // TODO gas
deleteActor: -500, // -createActorStorage
createActorCompute: 1108454,
createActorStorage: 36 + 40,
deleteActor: -(36 + 40), // -createActorStorage
verifySignature: map[crypto.SigType]int64{
crypto.SigTypeBLS: 219946580,
crypto.SigTypeSecp256k1: 6726720,
crypto.SigTypeBLS: 16598605,
crypto.SigTypeSecp256k1: 1637292,
},
hashingBase: 110685,
computeUnsealedSectorCidBase: 431890,
hashingBase: 31355,
computeUnsealedSectorCidBase: 98647,
verifySealBase: 2000, // TODO gas , it VerifySeal syscall is not used
verifyPostBase: 2621447835,
verifyConsensusFault: 495422,
verifyPostLookup: map[abi.RegisteredPoStProof]scalingCost{
abi.RegisteredPoStProof_StackedDrgWindow512MiBV1: {
flat: 123861062,
scale: 9226981,
},
abi.RegisteredPoStProof_StackedDrgWindow32GiBV1: {
flat: 748593537,
scale: 85639,
},
abi.RegisteredPoStProof_StackedDrgWindow64GiBV1: {
flat: 748593537,
scale: 85639,
},
},
verifyConsensusFault: 495422,
},
}

View File

@ -9,6 +9,11 @@ import (
"github.com/filecoin-project/specs-actors/actors/crypto"
)
type scalingCost struct {
flat int64
scale int64
}
type pricelistV0 struct {
///////////////////////////////////////////////////////////////////////////
// System operations
@ -81,7 +86,7 @@ type pricelistV0 struct {
computeUnsealedSectorCidBase int64
verifySealBase int64
verifyPostBase int64
verifyPostLookup map[abi.RegisteredPoStProof]scalingCost
verifyConsensusFault int64
}
@ -176,7 +181,30 @@ func (pl *pricelistV0) OnVerifySeal(info abi.SealVerifyInfo) GasCharge {
// OnVerifyPost
func (pl *pricelistV0) OnVerifyPost(info abi.WindowPoStVerifyInfo) GasCharge {
return newGasCharge("OnVerifyPost", pl.verifyPostBase, 0).WithExtra(len(info.ChallengedSectors))
sectorSize := "unknown"
var proofType abi.RegisteredPoStProof
if len(info.Proofs) != 0 {
proofType = info.Proofs[0].PoStProof
ss, err := info.Proofs[0].PoStProof.SectorSize()
if err == nil {
sectorSize = ss.ShortString()
}
}
cost, ok := pl.verifyPostLookup[proofType]
if !ok {
cost = pl.verifyPostLookup[abi.RegisteredPoStProof_StackedDrgWindow512MiBV1]
}
gasUsed := cost.flat + int64(len(info.ChallengedSectors))*cost.scale
gasUsed /= 2 // XXX: this is an artificial discount
return newGasCharge("OnVerifyPost", gasUsed, 0).
WithExtra(map[string]interface{}{
"type": sectorSize,
"size": len(info.ChallengedSectors),
})
}
// OnVerifyConsensusFault

View File

@ -193,7 +193,7 @@ func DumpActorState(code cid.Cid, b []byte) (interface{}, error) {
}
if err := um.UnmarshalCBOR(bytes.NewReader(b)); err != nil {
return nil, err
return nil, xerrors.Errorf("unmarshaling actor state: %w", err)
}
return rv.Elem().Interface(), nil

View File

@ -8,6 +8,8 @@ import (
gruntime "runtime"
"time"
samarket "github.com/filecoin-project/specs-actors/actors/builtin/market"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
@ -20,7 +22,6 @@ import (
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
"github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/ipfs/go-cid"
hamt "github.com/ipfs/go-hamt-ipld"
cbor "github.com/ipfs/go-ipld-cbor"
cbg "github.com/whyrusleeping/cbor-gen"
"go.opencensus.io/trace"
@ -61,38 +62,62 @@ type Runtime struct {
}
func (rt *Runtime) TotalFilCircSupply() abi.TokenAmount {
total := types.FromFil(build.TotalFilecoin)
filVested, err := rt.vm.GetVestedFunds(rt.ctx)
if err != nil {
rt.Abortf(exitcode.ErrIllegalState, "failed to get vested funds for computing total supply: %s", err)
}
rew, err := rt.state.GetActor(builtin.RewardActorAddr)
if err != nil {
rt.Abortf(exitcode.ErrIllegalState, "failed to get reward actor for computing total supply: %s", err)
}
filMined := types.BigSub(types.FromFil(build.FilAllocStorageMining), rew.Balance)
if filMined.LessThan(big.Zero()) {
filMined = big.Zero()
}
burnt, err := rt.state.GetActor(builtin.BurntFundsActorAddr)
if err != nil {
rt.Abortf(exitcode.ErrIllegalState, "failed to get reward actor for computing total supply: %s", err)
}
filBurned := burnt.Balance
market, err := rt.state.GetActor(builtin.StorageMarketActorAddr)
if err != nil {
rt.Abortf(exitcode.ErrIllegalState, "failed to get reward actor for computing total supply: %s", err)
}
var mst samarket.State
if err := rt.cst.Get(rt.ctx, market.Head, &mst); err != nil {
rt.Abortf(exitcode.ErrIllegalState, "failed to get market state: %s", err)
}
filMarketLocked := types.BigAdd(mst.TotalClientLockedCollateral, mst.TotalProviderLockedCollateral)
filMarketLocked = types.BigAdd(filMarketLocked, mst.TotalClientStorageFee)
power, err := rt.state.GetActor(builtin.StoragePowerActorAddr)
if err != nil {
rt.Abortf(exitcode.ErrIllegalState, "failed to get reward actor for computing total supply: %s", err)
}
total = types.BigSub(total, rew.Balance)
total = types.BigSub(total, burnt.Balance)
total = types.BigSub(total, market.Balance)
var st sapower.State
if err := rt.cst.Get(rt.ctx, power.Head, &st); err != nil {
var pst sapower.State
if err := rt.cst.Get(rt.ctx, power.Head, &pst); err != nil {
rt.Abortf(exitcode.ErrIllegalState, "failed to get storage power state: %s", err)
}
return types.BigSub(total, st.TotalPledgeCollateral)
filLocked := types.BigAdd(filMarketLocked, pst.TotalPledgeCollateral)
ret := types.BigAdd(filVested, filMined)
ret = types.BigSub(ret, filBurned)
ret = types.BigSub(ret, filLocked)
if ret.LessThan(big.Zero()) {
ret = big.Zero()
}
return ret
}
func (rt *Runtime) ResolveAddress(addr address.Address) (ret address.Address, ok bool) {
@ -146,6 +171,8 @@ func (rt *Runtime) shimCall(f func() interface{}) (rval []byte, aerr aerrors.Act
aerr = ar
return
}
//log.Desugar().WithOptions(zap.AddStacktrace(zapcore.ErrorLevel)).
//Sugar().Errorf("spec actors failure: %s", r)
log.Errorf("spec actors failure: %s", r)
aerr = aerrors.Newf(1, "spec actors failure: %s", r)
}
@ -267,7 +294,11 @@ func (rt *Runtime) CreateActor(codeID cid.Cid, address address.Address) {
_ = rt.chargeGasSafe(gasOnActorExec)
}
func (rt *Runtime) DeleteActor(addr address.Address) {
// DeleteActor deletes the executing actor from the state tree, transferring
// any balance to beneficiary.
// Aborts if the beneficiary does not exist.
// May only be called by the actor itself.
func (rt *Runtime) DeleteActor(beneficiary address.Address) {
rt.chargeGas(rt.Pricelist().OnDeleteActor())
act, err := rt.state.GetActor(rt.Message().Receiver())
if err != nil {
@ -277,11 +308,13 @@ func (rt *Runtime) DeleteActor(addr address.Address) {
panic(aerrors.Fatalf("failed to get actor: %s", err))
}
if !act.Balance.IsZero() {
if err := rt.vm.transfer(rt.Message().Receiver(), builtin.BurntFundsActorAddr, act.Balance); err != nil {
panic(aerrors.Fatalf("failed to transfer balance to burnt funds actor: %s", err))
// Transfer the executing actor's balance to the beneficiary
if err := rt.vm.transfer(rt.Message().Receiver(), beneficiary, act.Balance); err != nil {
panic(aerrors.Fatalf("failed to transfer balance to beneficiary actor: %s", err))
}
}
// Delete the executing actor
if err := rt.state.DeleteActor(rt.Message().Receiver()); err != nil {
panic(aerrors.Fatalf("failed to delete actor: %s", err))
}
@ -435,7 +468,7 @@ func (ssh *shimStateHandle) Readonly(obj vmr.CBORUnmarshaler) {
ssh.rt.Get(act.Head, obj)
}
func (ssh *shimStateHandle) Transaction(obj vmr.CBORer, f func() interface{}) interface{} {
func (ssh *shimStateHandle) Transaction(obj vmr.CBORer, f func()) {
if obj == nil {
ssh.rt.Abortf(exitcode.SysErrorIllegalActor, "Must not pass nil to Transaction()")
}
@ -448,15 +481,13 @@ func (ssh *shimStateHandle) Transaction(obj vmr.CBORer, f func() interface{}) in
ssh.rt.Get(baseState, obj)
ssh.rt.allowInternal = false
out := f()
f()
ssh.rt.allowInternal = true
c := ssh.rt.Put(obj)
// TODO: handle error below
ssh.rt.stateCommit(baseState, c)
return out
}
func (rt *Runtime) GetBalance(a address.Address) (types.BigInt, aerrors.ActorError) {
@ -464,7 +495,7 @@ func (rt *Runtime) GetBalance(a address.Address) (types.BigInt, aerrors.ActorErr
switch err {
default:
return types.EmptyInt, aerrors.Escalate(err, "failed to look up actor balance")
case hamt.ErrNotFound:
case types.ErrActorNotFound:
return types.NewInt(0), nil
case nil:
return act.Balance, nil
@ -582,12 +613,12 @@ func (rt *Runtime) abortIfAlreadyValidated() {
func (rt *Runtime) Log(level vmr.LogLevel, msg string, args ...interface{}) {
switch level {
case vmr.DEBUG:
actorLog.Debugf(msg, args)
actorLog.Debugf(msg, args...)
case vmr.INFO:
actorLog.Infof(msg, args)
actorLog.Infof(msg, args...)
case vmr.WARN:
actorLog.Warnf(msg, args)
actorLog.Warnf(msg, args...)
case vmr.ERROR:
actorLog.Errorf(msg, args)
actorLog.Errorf(msg, args...)
}
}

View File

@ -16,12 +16,12 @@ import (
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/lib/adtutil"
"github.com/filecoin-project/lotus/lib/sigs"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/filecoin-project/specs-actors/actors/runtime"
"github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/filecoin-project/sector-storage/ffiwrapper"
)
@ -32,15 +32,26 @@ func init() {
// Actual type is defined in chain/types/vmcontext.go because the VMContext interface is there
func Syscalls(verifier ffiwrapper.Verifier) runtime.Syscalls {
return &syscallShim{verifier: verifier}
type SyscallBuilder func(ctx context.Context, cstate *state.StateTree, cst cbor.IpldStore) runtime.Syscalls
func Syscalls(verifier ffiwrapper.Verifier) SyscallBuilder {
return func(ctx context.Context, cstate *state.StateTree, cst cbor.IpldStore) runtime.Syscalls {
return &syscallShim{
ctx: ctx,
cstate: cstate,
cst: cst,
verifier: verifier,
}
}
}
type syscallShim struct {
ctx context.Context
cstate *state.StateTree
cst *cbor.BasicIpldStore
cst cbor.IpldStore
verifier ffiwrapper.Verifier
}
@ -128,6 +139,10 @@ func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime.Consen
// Here extra is the "witness", a third block that shows the connection between A and B as
// A's sibling and B's parent.
// Specifically, since A is of lower height, it must be that B was mined omitting A from its tipset
//
// B
// |
// [A, C]
var blockC types.BlockHeader
if len(extra) > 0 {
if decodeErr := blockC.UnmarshalCBOR(bytes.NewReader(extra)); decodeErr != nil {
@ -180,7 +195,7 @@ func (ss *syscallShim) VerifyBlockSig(blk *types.BlockHeader) error {
return err
}
info, err := mas.GetInfo(adtutil.NewStore(ss.ctx, ss.cst))
info, err := mas.GetInfo(adt.WrapStore(ss.ctx, ss.cst))
if err != nil {
return err
}

View File

@ -7,11 +7,13 @@ import (
"reflect"
"time"
bstore "github.com/filecoin-project/lotus/lib/blockstore"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/filecoin-project/specs-actors/actors/builtin"
block "github.com/ipfs/go-block-format"
cid "github.com/ipfs/go-cid"
blockstore "github.com/ipfs/go-ipfs-blockstore"
cbor "github.com/ipfs/go-ipld-cbor"
logging "github.com/ipfs/go-log/v2"
mh "github.com/multiformats/go-multihash"
@ -24,13 +26,13 @@ import (
"github.com/filecoin-project/specs-actors/actors/builtin/account"
init_ "github.com/filecoin-project/specs-actors/actors/builtin/init"
"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/filecoin-project/specs-actors/actors/runtime"
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors/aerrors"
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/lib/blockstore"
"github.com/filecoin-project/lotus/lib/bufbstore"
)
@ -115,7 +117,7 @@ func (vm *VM) makeRuntime(ctx context.Context, msg *types.Message, origin addres
Atlas: vm.cst.Atlas,
}
rt.sys = pricedSyscalls{
under: vm.Syscalls,
under: vm.Syscalls(ctx, vm.cstate, rt.cst),
chargeGas: rt.chargeGasFunc(1),
pl: rt.pricelist,
}
@ -139,6 +141,8 @@ func (vm *UnsafeVM) MakeRuntime(ctx context.Context, msg *types.Message, origin
return vm.VM.makeRuntime(ctx, msg, origin, originNonce, usedGas, nac)
}
type VestedCalculator func(context.Context, abi.ChainEpoch) (abi.TokenAmount, error)
type VM struct {
cstate *state.StateTree
base cid.Cid
@ -147,27 +151,41 @@ type VM struct {
blockHeight abi.ChainEpoch
inv *Invoker
rand Rand
vc VestedCalculator
baseFee abi.TokenAmount
Syscalls runtime.Syscalls
Syscalls SyscallBuilder
}
func NewVM(base cid.Cid, height abi.ChainEpoch, r Rand, cbs blockstore.Blockstore, syscalls runtime.Syscalls) (*VM, error) {
buf := bufbstore.NewBufferedBstore(cbs)
type VMOpts struct {
StateBase cid.Cid
Epoch abi.ChainEpoch
Rand Rand
Bstore bstore.Blockstore
Syscalls SyscallBuilder
VestedCalc VestedCalculator
BaseFee abi.TokenAmount
}
func NewVM(opts *VMOpts) (*VM, error) {
buf := bufbstore.NewBufferedBstore(opts.Bstore)
cst := cbor.NewCborStore(buf)
state, err := state.LoadStateTree(cst, base)
state, err := state.LoadStateTree(cst, opts.StateBase)
if err != nil {
return nil, err
}
return &VM{
cstate: state,
base: base,
base: opts.StateBase,
cst: cst,
buf: buf,
blockHeight: height,
blockHeight: opts.Epoch,
inv: NewInvoker(),
rand: r, // TODO: Probably should be a syscall
Syscalls: syscalls,
rand: opts.Rand, // TODO: Probably should be a syscall
vc: opts.VestedCalc,
Syscalls: opts.Syscalls,
baseFee: opts.BaseFee,
}, nil
}
@ -179,6 +197,7 @@ type ApplyRet struct {
types.MessageReceipt
ActorErr aerrors.ActorError
Penalty types.BigInt
MinerTip types.BigInt
ExecutionTrace types.ExecutionTrace
Duration time.Duration
}
@ -274,8 +293,12 @@ func checkMessage(msg *types.Message) error {
return xerrors.Errorf("message has negative gas limit")
}
if msg.GasPrice == types.EmptyInt {
return xerrors.Errorf("message gas no gas price set")
if msg.GasFeeCap == types.EmptyInt {
return xerrors.Errorf("message fee cap not set")
}
if msg.GasPremium == types.EmptyInt {
return xerrors.Errorf("message gas premium not set")
}
if msg.Value == types.EmptyInt {
@ -298,6 +321,7 @@ func (vm *VM) ApplyImplicitMessage(ctx context.Context, msg *types.Message) (*Ap
ActorErr: actorErr,
ExecutionTrace: rt.executionTrace,
Penalty: types.NewInt(0),
MinerTip: types.NewInt(0),
Duration: time.Since(start),
}, actorErr
}
@ -330,14 +354,15 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet,
ExitCode: exitcode.SysErrOutOfGas,
GasUsed: 0,
},
Penalty: types.BigMul(msg.GasPrice, types.NewInt(uint64(msgGasCost))),
Penalty: types.BigMul(vm.baseFee, abi.NewTokenAmount(msgGasCost)),
Duration: time.Since(start),
MinerTip: big.Zero(),
}, nil
}
st := vm.cstate
minerPenaltyAmount := types.BigMul(msg.GasPrice, types.NewInt(uint64(msgGasCost)))
minerPenaltyAmount := types.BigMul(vm.baseFee, abi.NewTokenAmount(msg.GasLimit))
fromActor, err := st.GetActor(msg.From)
// this should never happen, but is currently still exercised by some tests
if err != nil {
@ -347,8 +372,10 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet,
ExitCode: exitcode.SysErrSenderInvalid,
GasUsed: 0,
},
ActorErr: aerrors.Newf(exitcode.SysErrSenderInvalid, "actor not found: %s", msg.From),
Penalty: minerPenaltyAmount,
Duration: time.Since(start),
MinerTip: big.Zero(),
}, nil
}
return nil, xerrors.Errorf("failed to look up from actor: %w", err)
@ -361,33 +388,39 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet,
ExitCode: exitcode.SysErrSenderInvalid,
GasUsed: 0,
},
ActorErr: aerrors.Newf(exitcode.SysErrSenderInvalid, "send from not account actor: %s", fromActor.Code),
Penalty: minerPenaltyAmount,
Duration: time.Since(start),
MinerTip: big.Zero(),
}, nil
}
// TODO: We should remove this, we might punish miners for no fault of their own
if msg.Nonce != fromActor.Nonce {
return &ApplyRet{
MessageReceipt: types.MessageReceipt{
ExitCode: exitcode.SysErrSenderStateInvalid,
GasUsed: 0,
},
ActorErr: aerrors.Newf(exitcode.SysErrSenderStateInvalid,
"actor nonce invalid: msg:%d != state:%d", msg.Nonce, fromActor.Nonce),
Penalty: minerPenaltyAmount,
Duration: time.Since(start),
MinerTip: big.Zero(),
}, nil
}
gascost := types.BigMul(types.NewInt(uint64(msg.GasLimit)), msg.GasPrice)
totalCost := types.BigAdd(gascost, msg.Value)
if fromActor.Balance.LessThan(totalCost) {
gascost := types.BigMul(types.NewInt(uint64(msg.GasLimit)), msg.GasFeeCap)
if fromActor.Balance.LessThan(gascost) {
return &ApplyRet{
MessageReceipt: types.MessageReceipt{
ExitCode: exitcode.SysErrSenderStateInvalid,
GasUsed: 0,
},
ActorErr: aerrors.Newf(exitcode.SysErrSenderStateInvalid,
"actor balance less than needed: %s < %s", types.FIL(fromActor.Balance), types.FIL(gascost)),
Penalty: minerPenaltyAmount,
Duration: time.Since(start),
MinerTip: big.Zero(),
}, nil
}
@ -447,17 +480,27 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet,
if gasUsed < 0 {
gasUsed = 0
}
// refund unused gas
refund := types.BigMul(types.NewInt(uint64(msg.GasLimit-gasUsed)), msg.GasPrice)
if err := vm.transferFromGasHolder(msg.From, gasHolder, refund); err != nil {
return nil, xerrors.Errorf("failed to refund gas")
gasOutputs := ComputeGasOutputs(gasUsed, msg.GasLimit, vm.baseFee, msg.GasFeeCap, msg.GasPremium)
if err := vm.transferFromGasHolder(builtin.BurntFundsActorAddr, gasHolder,
gasOutputs.BaseFeeBurn); err != nil {
return nil, xerrors.Errorf("failed to burn base fee: %w", err)
}
gasReward := types.BigMul(msg.GasPrice, types.NewInt(uint64(gasUsed)))
if err := vm.transferFromGasHolder(builtin.RewardActorAddr, gasHolder, gasReward); err != nil {
if err := vm.transferFromGasHolder(builtin.RewardActorAddr, gasHolder, gasOutputs.MinerTip); err != nil {
return nil, xerrors.Errorf("failed to give miner gas reward: %w", err)
}
if err := vm.transferFromGasHolder(builtin.BurntFundsActorAddr, gasHolder,
gasOutputs.OverEstimationBurn); err != nil {
return nil, xerrors.Errorf("failed to burn overestimation fee: %w", err)
}
// refund unused gas
if err := vm.transferFromGasHolder(msg.From, gasHolder, gasOutputs.Refund); err != nil {
return nil, xerrors.Errorf("failed to refund gas: %w", err)
}
if types.BigCmp(types.NewInt(0), gasHolder.Balance) != 0 {
return nil, xerrors.Errorf("gas handling math is wrong")
}
@ -470,7 +513,8 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet,
},
ActorErr: actorErr,
ExecutionTrace: rt.executionTrace,
Penalty: types.NewInt(0),
Penalty: gasOutputs.MinerPenalty,
MinerTip: gasOutputs.MinerTip,
Duration: time.Since(start),
}, nil
}
@ -542,6 +586,9 @@ func linksForObj(blk block.Block, cb func(cid.Cid)) error {
return xerrors.Errorf("cbg.ScanForLinks: %w", err)
}
return nil
case cid.Raw:
// We implicitly have all children of raw blocks.
return nil
default:
return xerrors.Errorf("vm flush copy method only supports dag cbor")
}
@ -591,17 +638,27 @@ func copyRec(from, to blockstore.Blockstore, root cid.Cid, cp func(block.Block)
return
}
if link.Prefix().MhType == mh.IDENTITY || link.Prefix().Codec == cid.FilCommitmentSealed || link.Prefix().Codec == cid.FilCommitmentUnsealed {
prefix := link.Prefix()
if prefix.Codec == cid.FilCommitmentSealed || prefix.Codec == cid.FilCommitmentUnsealed {
return
}
has, err := to.Has(link)
if err != nil {
lerr = xerrors.Errorf("has: %w", err)
return
}
if has {
return
// We always have blocks inlined into CIDs, but we may not have their children.
if prefix.MhType == mh.IDENTITY {
// Unless the inlined block has no children.
if prefix.Codec == cid.Raw {
return
}
} else {
// If we have an object, we already have its children, skip the object.
has, err := to.Has(link)
if err != nil {
lerr = xerrors.Errorf("has: %w", err)
return
}
if has {
return
}
}
if err := copyRec(from, to, link, cp); err != nil {
@ -657,6 +714,10 @@ func (vm *VM) SetInvoker(i *Invoker) {
vm.inv = i
}
func (vm *VM) GetVestedFunds(ctx context.Context) (abi.TokenAmount, error) {
return vm.vc(ctx, vm.blockHeight)
}
func (vm *VM) incrementNonce(addr address.Address) error {
return vm.cstate.MutateActor(addr, func(a *types.Actor) error {
a.Nonce++
@ -732,6 +793,10 @@ func (vm *VM) transferFromGasHolder(addr address.Address, gasHolder *types.Actor
return xerrors.Errorf("attempted to transfer negative value from gas holder")
}
if amt.Equals(big.NewInt(0)) {
return nil
}
return vm.cstate.MutateActor(addr, func(a *types.Actor) error {
if err := deductFunds(gasHolder, amt); err != nil {
return err

Some files were not shown because too many files have changed in this diff Show More