2023-11-03 12:51:01 +00:00
package lpwindow
import (
2023-11-03 21:40:28 +00:00
"bytes"
2023-11-03 20:53:15 +00:00
"context"
2023-11-04 10:04:46 +00:00
"golang.org/x/xerrors"
2023-11-03 21:40:28 +00:00
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/builtin"
"github.com/filecoin-project/go-state-types/builtin/v9/miner"
2023-11-08 17:24:17 +00:00
"github.com/filecoin-project/go-state-types/crypto"
2023-11-14 00:06:11 +00:00
2023-11-03 21:40:28 +00:00
"github.com/filecoin-project/lotus/api"
2023-11-03 20:53:15 +00:00
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/lib/harmony/harmonydb"
2023-11-03 12:51:01 +00:00
"github.com/filecoin-project/lotus/lib/harmony/harmonytask"
"github.com/filecoin-project/lotus/lib/harmony/resources"
2023-11-03 20:53:15 +00:00
"github.com/filecoin-project/lotus/lib/promise"
"github.com/filecoin-project/lotus/provider/chainsched"
"github.com/filecoin-project/lotus/provider/lpmessage"
2023-11-03 21:40:28 +00:00
"github.com/filecoin-project/lotus/storage/ctladdr"
2023-11-08 17:24:17 +00:00
"github.com/filecoin-project/lotus/storage/wdpost"
2023-11-03 12:51:01 +00:00
)
2023-11-03 21:40:28 +00:00
type WdPoStSubmitTaskApi interface {
ChainHead ( context . Context ) ( * types . TipSet , error )
WalletBalance ( context . Context , address . Address ) ( types . BigInt , error )
WalletHas ( context . Context , address . Address ) ( bool , error )
StateAccountKey ( context . Context , address . Address , types . TipSetKey ) ( address . Address , error )
StateLookupID ( context . Context , address . Address , types . TipSetKey ) ( address . Address , error )
StateMinerInfo ( context . Context , address . Address , types . TipSetKey ) ( api . MinerInfo , error )
2023-11-04 11:32:27 +00:00
StateGetRandomnessFromTickets ( ctx context . Context , personalization crypto . DomainSeparationTag , randEpoch abi . ChainEpoch , entropy [ ] byte , tsk types . TipSetKey ) ( abi . Randomness , error )
2023-11-03 21:40:28 +00:00
2023-11-09 16:24:37 +00:00
GasEstimateMessageGas ( context . Context , * types . Message , * api . MessageSendSpec , types . TipSetKey ) ( * types . Message , error )
2023-11-03 21:40:28 +00:00
GasEstimateFeeCap ( context . Context , * types . Message , int64 , types . TipSetKey ) ( types . BigInt , error )
GasEstimateGasPremium ( _ context . Context , nblocksincl uint64 , sender address . Address , gaslimit int64 , tsk types . TipSetKey ) ( types . BigInt , error )
}
2023-11-03 12:51:01 +00:00
type WdPostSubmitTask struct {
2023-11-03 20:53:15 +00:00
sender * lpmessage . Sender
db * harmonydb . DB
2023-11-03 21:40:28 +00:00
api WdPoStSubmitTaskApi
maxWindowPoStGasFee types . FIL
as * ctladdr . AddressSelector
2023-11-03 20:53:15 +00:00
submitPoStTF promise . Promise [ harmonytask . AddTaskFunc ]
2023-11-03 12:51:01 +00:00
}
2023-11-03 21:40:28 +00:00
func NewWdPostSubmitTask ( pcs * chainsched . ProviderChainSched , send * lpmessage . Sender , db * harmonydb . DB , api WdPoStSubmitTaskApi , maxWindowPoStGasFee types . FIL , as * ctladdr . AddressSelector ) ( * WdPostSubmitTask , error ) {
res := & WdPostSubmitTask {
sender : send ,
db : db ,
api : api ,
maxWindowPoStGasFee : maxWindowPoStGasFee ,
as : as ,
}
if err := pcs . AddHandler ( res . processHeadChange ) ; err != nil {
return nil , err
}
return res , nil
}
2023-11-03 12:51:01 +00:00
func ( w * WdPostSubmitTask ) Do ( taskID harmonytask . TaskID , stillOwned func ( ) bool ) ( done bool , err error ) {
2023-11-03 21:40:28 +00:00
log . Debugw ( "WdPostSubmitTask.Do" , "taskID" , taskID )
var spID uint64
var deadline uint64
var partition uint64
2023-11-04 11:32:27 +00:00
var pps , submitAtEpoch , submitByEpoch abi . ChainEpoch
var earlyParamBytes [ ] byte
2023-11-03 21:40:28 +00:00
var dbTask uint64
err = w . db . QueryRow (
2023-11-07 13:03:31 +00:00
context . Background ( ) , ` SELECT sp_id , proving_period_start , deadline , partition , submit_at_epoch , submit_by_epoch , proof_params , submit_task_id
2023-11-07 12:38:00 +00:00
FROM wdpost_proofs WHERE submit_task_id = $ 1 ` , taskID ,
2023-11-04 11:32:27 +00:00
) . Scan ( & spID , & pps , & deadline , & partition , & submitAtEpoch , & submitByEpoch , & earlyParamBytes , & dbTask )
2023-11-03 21:40:28 +00:00
if err != nil {
return false , xerrors . Errorf ( "query post proof: %w" , err )
}
if dbTask != uint64 ( taskID ) {
return false , xerrors . Errorf ( "taskID mismatch: %d != %d" , dbTask , taskID )
}
head , err := w . api . ChainHead ( context . Background ( ) )
if err != nil {
return false , xerrors . Errorf ( "getting chain head: %w" , err )
}
if head . Height ( ) > submitByEpoch {
// we missed the deadline, no point in submitting
log . Errorw ( "missed submit deadline" , "spID" , spID , "deadline" , deadline , "partition" , partition , "submitByEpoch" , submitByEpoch , "headHeight" , head . Height ( ) )
return true , nil
}
if head . Height ( ) < submitAtEpoch {
log . Errorw ( "submit epoch not reached" , "spID" , spID , "deadline" , deadline , "partition" , partition , "submitAtEpoch" , submitAtEpoch , "headHeight" , head . Height ( ) )
return false , xerrors . Errorf ( "submit epoch not reached: %d < %d" , head . Height ( ) , submitAtEpoch )
}
2023-11-04 11:32:27 +00:00
dlInfo := wdpost . NewDeadlineInfo ( pps , deadline , head . Height ( ) )
2023-11-03 21:40:28 +00:00
var params miner . SubmitWindowedPoStParams
2023-11-04 11:32:27 +00:00
if err := params . UnmarshalCBOR ( bytes . NewReader ( earlyParamBytes ) ) ; err != nil {
2023-11-03 21:40:28 +00:00
return false , xerrors . Errorf ( "unmarshaling proof message: %w" , err )
}
2023-11-04 11:32:27 +00:00
commEpoch := dlInfo . Challenge
commRand , err := w . api . StateGetRandomnessFromTickets ( context . Background ( ) , crypto . DomainSeparationTag_PoStChainCommit , commEpoch , nil , head . Key ( ) )
if err != nil {
err = xerrors . Errorf ( "failed to get chain randomness from tickets for windowPost (epoch=%d): %w" , commEpoch , err )
log . Errorf ( "submitPoStMessage failed: %+v" , err )
return false , xerrors . Errorf ( "getting post commit randomness: %w" , err )
}
params . ChainCommitEpoch = commEpoch
params . ChainCommitRand = commRand
var pbuf bytes . Buffer
if err := params . MarshalCBOR ( & pbuf ) ; err != nil {
return false , xerrors . Errorf ( "marshaling proof message: %w" , err )
}
2023-11-03 21:40:28 +00:00
maddr , err := address . NewIDAddress ( spID )
if err != nil {
return false , xerrors . Errorf ( "invalid miner address: %w" , err )
}
msg := & types . Message {
To : maddr ,
Method : builtin . MethodsMiner . SubmitWindowedPoSt ,
2023-11-04 11:32:27 +00:00
Params : pbuf . Bytes ( ) ,
2023-11-03 21:40:28 +00:00
Value : big . Zero ( ) ,
}
2023-11-09 16:24:37 +00:00
msg , mss , err := preparePoStMessage ( w . api , w . as , maddr , msg , abi . TokenAmount ( w . maxWindowPoStGasFee ) )
2023-11-03 21:40:28 +00:00
if err != nil {
2023-11-09 16:24:37 +00:00
return false , xerrors . Errorf ( "preparing proof message: %w" , err )
2023-11-03 21:40:28 +00:00
}
smsg , err := w . sender . Send ( context . Background ( ) , msg , mss , "wdpost" )
if err != nil {
return false , xerrors . Errorf ( "sending proof message: %w" , err )
}
// set message_cid in the wdpost_proofs entry
2023-11-07 12:38:00 +00:00
_ , err = w . db . Exec ( context . Background ( ) , ` UPDATE wdpost_proofs SET message_cid = $1 WHERE sp_id = $2 AND proving_period_start = $3 AND deadline = $4 AND partition = $5 ` , smsg . String ( ) , spID , pps , deadline , partition )
2023-11-03 21:40:28 +00:00
if err != nil {
return true , xerrors . Errorf ( "updating wdpost_proofs: %w" , err )
}
return true , nil
2023-11-03 12:51:01 +00:00
}
func ( w * WdPostSubmitTask ) CanAccept ( ids [ ] harmonytask . TaskID , engine * harmonytask . TaskEngine ) ( * harmonytask . TaskID , error ) {
2023-11-03 21:00:19 +00:00
if len ( ids ) == 0 {
// probably can't happen, but panicking is bad
return nil , nil
}
if w . sender == nil {
// we can't send messages
return nil , nil
}
return & ids [ 0 ] , nil
2023-11-03 12:51:01 +00:00
}
func ( w * WdPostSubmitTask ) TypeDetails ( ) harmonytask . TaskTypeDetails {
return harmonytask . TaskTypeDetails {
Max : 128 ,
Name : "WdPostSubmit" ,
Cost : resources . Resources {
Cpu : 0 ,
Gpu : 0 ,
2023-11-03 20:53:15 +00:00
Ram : 10 << 20 ,
2023-11-03 12:51:01 +00:00
} ,
MaxFailures : 10 ,
Follows : nil , // ??
}
}
func ( w * WdPostSubmitTask ) Adder ( taskFunc harmonytask . AddTaskFunc ) {
2023-11-03 20:53:15 +00:00
w . submitPoStTF . Set ( taskFunc )
}
func ( w * WdPostSubmitTask ) processHeadChange ( ctx context . Context , revert , apply * types . TipSet ) error {
tf := w . submitPoStTF . Val ( ctx )
2023-11-07 12:38:00 +00:00
qry , err := w . db . Query ( ctx , ` SELECT sp_id, proving_period_start, deadline, partition, submit_at_epoch FROM wdpost_proofs WHERE submit_task_id IS NULL AND submit_at_epoch <= $1 ` , apply . Height ( ) )
2023-11-03 20:53:15 +00:00
if err != nil {
return err
}
defer qry . Close ( )
for qry . Next ( ) {
var spID int64
2023-11-04 11:32:27 +00:00
var pps int64
2023-11-03 20:53:15 +00:00
var deadline uint64
var partition uint64
var submitAtEpoch uint64
2023-11-04 11:32:27 +00:00
if err := qry . Scan ( & spID , & pps , & deadline , & partition , & submitAtEpoch ) ; err != nil {
return xerrors . Errorf ( "scan submittable posts: %w" , err )
2023-11-03 20:53:15 +00:00
}
tf ( func ( id harmonytask . TaskID , tx * harmonydb . Tx ) ( shouldCommit bool , err error ) {
// update in transaction iff submit_task_id is still null
2023-11-07 12:38:00 +00:00
res , err := tx . Exec ( ` UPDATE wdpost_proofs SET submit_task_id = $1 WHERE sp_id = $2 AND proving_period_start = $3 AND deadline = $4 AND partition = $5 AND submit_task_id IS NULL ` , id , spID , pps , deadline , partition )
2023-11-03 20:53:15 +00:00
if err != nil {
2023-11-04 11:32:27 +00:00
return false , xerrors . Errorf ( "query ready proof: %w" , err )
2023-11-03 20:53:15 +00:00
}
if res != 1 {
return false , nil
}
return true , nil
} )
}
if err := qry . Err ( ) ; err != nil {
return err
}
return nil
2023-11-03 12:51:01 +00:00
}
2023-11-09 16:24:37 +00:00
type MsgPrepAPI interface {
StateMinerInfo ( context . Context , address . Address , types . TipSetKey ) ( api . MinerInfo , error )
GasEstimateMessageGas ( context . Context , * types . Message , * api . MessageSendSpec , types . TipSetKey ) ( * types . Message , error )
GasEstimateFeeCap ( context . Context , * types . Message , int64 , types . TipSetKey ) ( types . BigInt , error )
GasEstimateGasPremium ( ctx context . Context , nblocksincl uint64 , sender address . Address , gaslimit int64 , tsk types . TipSetKey ) ( types . BigInt , error )
WalletBalance ( context . Context , address . Address ) ( types . BigInt , error )
WalletHas ( context . Context , address . Address ) ( bool , error )
StateAccountKey ( context . Context , address . Address , types . TipSetKey ) ( address . Address , error )
StateLookupID ( context . Context , address . Address , types . TipSetKey ) ( address . Address , error )
}
func preparePoStMessage ( w MsgPrepAPI , as * ctladdr . AddressSelector , maddr address . Address , msg * types . Message , maxFee abi . TokenAmount ) ( * types . Message , * api . MessageSendSpec , error ) {
mi , err := w . StateMinerInfo ( context . Background ( ) , maddr , types . EmptyTSK )
if err != nil {
return nil , nil , xerrors . Errorf ( "error getting miner info: %w" , err )
}
// set the worker as a fallback
msg . From = mi . Worker
mss := & api . MessageSendSpec {
MaxFee : abi . TokenAmount ( maxFee ) ,
}
// (optimal) initial estimation with some overestimation that guarantees
// block inclusion within the next 20 tipsets.
gm , err := w . GasEstimateMessageGas ( context . Background ( ) , msg , mss , types . EmptyTSK )
if err != nil {
log . Errorw ( "estimating gas" , "error" , err )
return nil , nil , xerrors . Errorf ( "estimating gas: %w" , err )
}
* msg = * gm
// calculate a more frugal estimation; premium is estimated to guarantee
// inclusion within 5 tipsets, and fee cap is estimated for inclusion
// within 4 tipsets.
minGasFeeMsg := * msg
minGasFeeMsg . GasPremium , err = w . GasEstimateGasPremium ( context . Background ( ) , 5 , msg . From , msg . GasLimit , types . EmptyTSK )
if err != nil {
log . Errorf ( "failed to estimate minimum gas premium: %+v" , err )
minGasFeeMsg . GasPremium = msg . GasPremium
}
minGasFeeMsg . GasFeeCap , err = w . GasEstimateFeeCap ( context . Background ( ) , & minGasFeeMsg , 4 , types . EmptyTSK )
if err != nil {
log . Errorf ( "failed to estimate minimum gas fee cap: %+v" , err )
minGasFeeMsg . GasFeeCap = msg . GasFeeCap
}
// goodFunds = funds needed for optimal inclusion probability.
// minFunds = funds needed for more speculative inclusion probability.
goodFunds := big . Add ( minGasFeeMsg . RequiredFunds ( ) , minGasFeeMsg . Value )
minFunds := big . Min ( big . Add ( minGasFeeMsg . RequiredFunds ( ) , minGasFeeMsg . Value ) , goodFunds )
from , _ , err := as . AddressFor ( context . Background ( ) , w , mi , api . PoStAddr , goodFunds , minFunds )
if err != nil {
return nil , nil , xerrors . Errorf ( "error getting address: %w" , err )
}
msg . From = from
return msg , mss , nil
}
2023-11-03 12:51:01 +00:00
var _ harmonytask . TaskInterface = & WdPostSubmitTask { }