lotus/chain/messagepool/selection_test.go
Jakub Sztandera 2b2b632cd6
Make mpool select only profitable messages
Signed-off-by: Jakub Sztandera <kubuxu@protocol.ai>
2020-08-07 00:40:25 +02:00

432 lines
12 KiB
Go

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 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++
}
}