1234fcfd4f
Previously, we checked message gas/validity with the previous block's height. This doesn't affect consensus, but will help us avoid adding messages to the message pool that shouldn't be there.
437 lines
10 KiB
Go
437 lines
10 KiB
Go
package messagepool
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
stdbig "math/big"
|
|
"sort"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/filecoin-project/go-address"
|
|
"github.com/filecoin-project/go-state-types/big"
|
|
"github.com/filecoin-project/lotus/api"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
"github.com/filecoin-project/lotus/chain/vm"
|
|
)
|
|
|
|
var baseFeeUpperBoundFactor = types.NewInt(10)
|
|
|
|
// CheckMessages performs a set of logic checks for a list of messages, prior to submitting it to the mpool
|
|
func (mp *MessagePool) CheckMessages(ctx context.Context, protos []*api.MessagePrototype) ([][]api.MessageCheckStatus, error) {
|
|
flex := make([]bool, len(protos))
|
|
msgs := make([]*types.Message, len(protos))
|
|
for i, p := range protos {
|
|
flex[i] = !p.ValidNonce
|
|
msgs[i] = &p.Message
|
|
}
|
|
return mp.checkMessages(ctx, msgs, false, flex)
|
|
}
|
|
|
|
// CheckPendingMessages performs a set of logical sets for all messages pending from a given actor
|
|
func (mp *MessagePool) CheckPendingMessages(ctx context.Context, from address.Address) ([][]api.MessageCheckStatus, error) {
|
|
var msgs []*types.Message
|
|
mp.lk.Lock()
|
|
mset, ok := mp.pending[from]
|
|
if ok {
|
|
for _, sm := range mset.msgs {
|
|
msgs = append(msgs, &sm.Message)
|
|
}
|
|
}
|
|
mp.lk.Unlock()
|
|
|
|
if len(msgs) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
sort.Slice(msgs, func(i, j int) bool {
|
|
return msgs[i].Nonce < msgs[j].Nonce
|
|
})
|
|
|
|
return mp.checkMessages(ctx, msgs, true, nil)
|
|
}
|
|
|
|
// CheckReplaceMessages performs a set of logical checks for related messages while performing a
|
|
// replacement.
|
|
func (mp *MessagePool) CheckReplaceMessages(ctx context.Context, replace []*types.Message) ([][]api.MessageCheckStatus, error) {
|
|
msgMap := make(map[address.Address]map[uint64]*types.Message)
|
|
count := 0
|
|
|
|
mp.lk.Lock()
|
|
for _, m := range replace {
|
|
mmap, ok := msgMap[m.From]
|
|
if !ok {
|
|
mmap = make(map[uint64]*types.Message)
|
|
msgMap[m.From] = mmap
|
|
mset, ok := mp.pending[m.From]
|
|
if ok {
|
|
count += len(mset.msgs)
|
|
for _, sm := range mset.msgs {
|
|
mmap[sm.Message.Nonce] = &sm.Message
|
|
}
|
|
} else {
|
|
count++
|
|
}
|
|
}
|
|
mmap[m.Nonce] = m
|
|
}
|
|
mp.lk.Unlock()
|
|
|
|
msgs := make([]*types.Message, 0, count)
|
|
start := 0
|
|
for _, mmap := range msgMap {
|
|
end := start + len(mmap)
|
|
|
|
for _, m := range mmap {
|
|
msgs = append(msgs, m)
|
|
}
|
|
|
|
sort.Slice(msgs[start:end], func(i, j int) bool {
|
|
return msgs[start+i].Nonce < msgs[start+j].Nonce
|
|
})
|
|
|
|
start = end
|
|
}
|
|
|
|
return mp.checkMessages(ctx, msgs, true, nil)
|
|
}
|
|
|
|
// flexibleNonces should be either nil or of len(msgs), it signifies that message at given index
|
|
// has non-determied nonce at this point
|
|
func (mp *MessagePool) checkMessages(ctx context.Context, msgs []*types.Message, interned bool, flexibleNonces []bool) (result [][]api.MessageCheckStatus, err error) {
|
|
if mp.api.IsLite() {
|
|
return nil, nil
|
|
}
|
|
mp.curTsLk.Lock()
|
|
curTs := mp.curTs
|
|
mp.curTsLk.Unlock()
|
|
|
|
epoch := curTs.Height() + 1
|
|
|
|
var baseFee big.Int
|
|
if len(curTs.Blocks()) > 0 {
|
|
baseFee = curTs.Blocks()[0].ParentBaseFee
|
|
} else {
|
|
baseFee, err = mp.api.ChainComputeBaseFee(context.Background(), curTs)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("error computing basefee: %w", err)
|
|
}
|
|
}
|
|
|
|
baseFeeLowerBound := getBaseFeeLowerBound(baseFee, baseFeeLowerBoundFactor)
|
|
baseFeeUpperBound := types.BigMul(baseFee, baseFeeUpperBoundFactor)
|
|
|
|
type actorState struct {
|
|
nextNonce uint64
|
|
requiredFunds *stdbig.Int
|
|
}
|
|
|
|
state := make(map[address.Address]*actorState)
|
|
balances := make(map[address.Address]big.Int)
|
|
|
|
result = make([][]api.MessageCheckStatus, len(msgs))
|
|
|
|
for i, m := range msgs {
|
|
// pre-check: actor nonce
|
|
check := api.MessageCheckStatus{
|
|
Cid: m.Cid(),
|
|
CheckStatus: api.CheckStatus{
|
|
Code: api.CheckStatusMessageGetStateNonce,
|
|
},
|
|
}
|
|
|
|
st, ok := state[m.From]
|
|
if !ok {
|
|
mp.lk.Lock()
|
|
mset, ok := mp.pending[m.From]
|
|
if ok && !interned {
|
|
st = &actorState{nextNonce: mset.nextNonce, requiredFunds: mset.requiredFunds}
|
|
for _, m := range mset.msgs {
|
|
st.requiredFunds = new(stdbig.Int).Add(st.requiredFunds, m.Message.Value.Int)
|
|
}
|
|
state[m.From] = st
|
|
mp.lk.Unlock()
|
|
|
|
check.OK = true
|
|
check.Hint = map[string]interface{}{
|
|
"nonce": st.nextNonce,
|
|
}
|
|
} else {
|
|
mp.lk.Unlock()
|
|
|
|
stateNonce, err := mp.getStateNonce(ctx, m.From, curTs)
|
|
if err != nil {
|
|
check.OK = false
|
|
check.Err = fmt.Sprintf("error retrieving state nonce: %s", err.Error())
|
|
} else {
|
|
check.OK = true
|
|
check.Hint = map[string]interface{}{
|
|
"nonce": stateNonce,
|
|
}
|
|
}
|
|
|
|
st = &actorState{nextNonce: stateNonce, requiredFunds: new(stdbig.Int)}
|
|
state[m.From] = st
|
|
}
|
|
} else {
|
|
check.OK = true
|
|
}
|
|
|
|
result[i] = append(result[i], check)
|
|
if !check.OK {
|
|
continue
|
|
}
|
|
|
|
// pre-check: actor balance
|
|
check = api.MessageCheckStatus{
|
|
Cid: m.Cid(),
|
|
CheckStatus: api.CheckStatus{
|
|
Code: api.CheckStatusMessageGetStateBalance,
|
|
},
|
|
}
|
|
|
|
balance, ok := balances[m.From]
|
|
if !ok {
|
|
balance, err = mp.getStateBalance(ctx, m.From, curTs)
|
|
if err != nil {
|
|
check.OK = false
|
|
check.Err = fmt.Sprintf("error retrieving state balance: %s", err)
|
|
} else {
|
|
check.OK = true
|
|
check.Hint = map[string]interface{}{
|
|
"balance": balance,
|
|
}
|
|
}
|
|
|
|
balances[m.From] = balance
|
|
} else {
|
|
check.OK = true
|
|
check.Hint = map[string]interface{}{
|
|
"balance": balance,
|
|
}
|
|
}
|
|
|
|
result[i] = append(result[i], check)
|
|
if !check.OK {
|
|
continue
|
|
}
|
|
|
|
// 1. Serialization
|
|
check = api.MessageCheckStatus{
|
|
Cid: m.Cid(),
|
|
CheckStatus: api.CheckStatus{
|
|
Code: api.CheckStatusMessageSerialize,
|
|
},
|
|
}
|
|
|
|
bytes, err := m.Serialize()
|
|
if err != nil {
|
|
check.OK = false
|
|
check.Err = err.Error()
|
|
} else {
|
|
check.OK = true
|
|
}
|
|
|
|
result[i] = append(result[i], check)
|
|
|
|
// 2. Message size
|
|
check = api.MessageCheckStatus{
|
|
Cid: m.Cid(),
|
|
CheckStatus: api.CheckStatus{
|
|
Code: api.CheckStatusMessageSize,
|
|
},
|
|
}
|
|
|
|
if len(bytes) > MaxMessageSize-128 { // 128 bytes to account for signature size
|
|
check.OK = false
|
|
check.Err = "message too big"
|
|
} else {
|
|
check.OK = true
|
|
}
|
|
|
|
result[i] = append(result[i], check)
|
|
|
|
// 3. Syntactic validation
|
|
check = api.MessageCheckStatus{
|
|
Cid: m.Cid(),
|
|
CheckStatus: api.CheckStatus{
|
|
Code: api.CheckStatusMessageValidity,
|
|
},
|
|
}
|
|
nv, err := mp.getNtwkVersion(epoch)
|
|
if err != nil {
|
|
check.OK = false
|
|
check.Err = fmt.Sprintf("error retrieving network version: %s", err.Error())
|
|
} else {
|
|
check.OK = true
|
|
}
|
|
if err := m.ValidForBlockInclusion(0, nv); err != nil {
|
|
check.OK = false
|
|
check.Err = fmt.Sprintf("syntactically invalid message: %s", err.Error())
|
|
} else {
|
|
check.OK = true
|
|
}
|
|
|
|
result[i] = append(result[i], check)
|
|
if !check.OK {
|
|
// skip remaining checks if it is a syntatically invalid message
|
|
continue
|
|
}
|
|
|
|
// gas checks
|
|
|
|
// 4. Min Gas
|
|
minGas := vm.PricelistByEpoch(epoch).OnChainMessage(m.ChainLength())
|
|
|
|
check = api.MessageCheckStatus{
|
|
Cid: m.Cid(),
|
|
CheckStatus: api.CheckStatus{
|
|
Code: api.CheckStatusMessageMinGas,
|
|
Hint: map[string]interface{}{
|
|
"minGas": minGas,
|
|
},
|
|
},
|
|
}
|
|
|
|
if m.GasLimit < minGas.Total() {
|
|
check.OK = false
|
|
check.Err = "GasLimit less than epoch minimum gas"
|
|
} else {
|
|
check.OK = true
|
|
}
|
|
|
|
result[i] = append(result[i], check)
|
|
|
|
// 5. Min Base Fee
|
|
check = api.MessageCheckStatus{
|
|
Cid: m.Cid(),
|
|
CheckStatus: api.CheckStatus{
|
|
Code: api.CheckStatusMessageMinBaseFee,
|
|
},
|
|
}
|
|
|
|
if m.GasFeeCap.LessThan(minimumBaseFee) {
|
|
check.OK = false
|
|
check.Err = "GasFeeCap less than minimum base fee"
|
|
} else {
|
|
check.OK = true
|
|
}
|
|
|
|
result[i] = append(result[i], check)
|
|
if !check.OK {
|
|
goto checkState
|
|
}
|
|
|
|
// 6. Base Fee
|
|
check = api.MessageCheckStatus{
|
|
Cid: m.Cid(),
|
|
CheckStatus: api.CheckStatus{
|
|
Code: api.CheckStatusMessageBaseFee,
|
|
Hint: map[string]interface{}{
|
|
"baseFee": baseFee,
|
|
},
|
|
},
|
|
}
|
|
|
|
if m.GasFeeCap.LessThan(baseFee) {
|
|
check.OK = false
|
|
check.Err = "GasFeeCap less than current base fee"
|
|
} else {
|
|
check.OK = true
|
|
}
|
|
|
|
result[i] = append(result[i], check)
|
|
|
|
// 7. Base Fee lower bound
|
|
check = api.MessageCheckStatus{
|
|
Cid: m.Cid(),
|
|
CheckStatus: api.CheckStatus{
|
|
Code: api.CheckStatusMessageBaseFeeLowerBound,
|
|
Hint: map[string]interface{}{
|
|
"baseFeeLowerBound": baseFeeLowerBound,
|
|
"baseFee": baseFee,
|
|
},
|
|
},
|
|
}
|
|
|
|
if m.GasFeeCap.LessThan(baseFeeLowerBound) {
|
|
check.OK = false
|
|
check.Err = "GasFeeCap less than base fee lower bound for inclusion in next 20 epochs"
|
|
} else {
|
|
check.OK = true
|
|
}
|
|
|
|
result[i] = append(result[i], check)
|
|
|
|
// 8. Base Fee upper bound
|
|
check = api.MessageCheckStatus{
|
|
Cid: m.Cid(),
|
|
CheckStatus: api.CheckStatus{
|
|
Code: api.CheckStatusMessageBaseFeeUpperBound,
|
|
Hint: map[string]interface{}{
|
|
"baseFeeUpperBound": baseFeeUpperBound,
|
|
"baseFee": baseFee,
|
|
},
|
|
},
|
|
}
|
|
|
|
if m.GasFeeCap.LessThan(baseFeeUpperBound) {
|
|
check.OK = true // on purpose, the checks is more of a warning
|
|
check.Err = "GasFeeCap less than base fee upper bound for inclusion in next 20 epochs"
|
|
} else {
|
|
check.OK = true
|
|
}
|
|
|
|
result[i] = append(result[i], check)
|
|
|
|
// stateful checks
|
|
checkState:
|
|
// 9. Message Nonce
|
|
check = api.MessageCheckStatus{
|
|
Cid: m.Cid(),
|
|
CheckStatus: api.CheckStatus{
|
|
Code: api.CheckStatusMessageNonce,
|
|
Hint: map[string]interface{}{
|
|
"nextNonce": st.nextNonce,
|
|
},
|
|
},
|
|
}
|
|
|
|
if (flexibleNonces == nil || !flexibleNonces[i]) && st.nextNonce != m.Nonce {
|
|
check.OK = false
|
|
check.Err = fmt.Sprintf("message nonce doesn't match next nonce (%d)", st.nextNonce)
|
|
} else {
|
|
check.OK = true
|
|
st.nextNonce++
|
|
}
|
|
|
|
result[i] = append(result[i], check)
|
|
|
|
// check required funds -vs- balance
|
|
st.requiredFunds = new(stdbig.Int).Add(st.requiredFunds, m.RequiredFunds().Int)
|
|
st.requiredFunds.Add(st.requiredFunds, m.Value.Int)
|
|
|
|
// 10. Balance
|
|
check = api.MessageCheckStatus{
|
|
Cid: m.Cid(),
|
|
CheckStatus: api.CheckStatus{
|
|
Code: api.CheckStatusMessageBalance,
|
|
Hint: map[string]interface{}{
|
|
"requiredFunds": big.Int{Int: stdbig.NewInt(0).Set(st.requiredFunds)},
|
|
},
|
|
},
|
|
}
|
|
|
|
if balance.Int.Cmp(st.requiredFunds) < 0 {
|
|
check.OK = false
|
|
check.Err = "insufficient balance"
|
|
} else {
|
|
check.OK = true
|
|
}
|
|
|
|
result[i] = append(result[i], check)
|
|
}
|
|
|
|
return result, nil
|
|
}
|