lotus/chain/events/events_test.go
2019-09-05 09:57:18 +02:00

512 lines
12 KiB
Go

package events
import (
"fmt"
"testing"
"github.com/ipfs/go-cid"
"github.com/multiformats/go-multihash"
"github.com/stretchr/testify/require"
"github.com/filecoin-project/go-lotus/build"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/types"
)
var dummyCid cid.Cid
func init() {
dummyCid, _ = cid.Parse("bafkqaaa")
}
type fakeMsg struct {
bmsgs []*types.Message
smsgs []*types.SignedMessage
}
type fakeCS struct {
t *testing.T
h uint64
tsc *tipSetCache
msgs map[cid.Cid]fakeMsg
sub func(rev, app []*types.TipSet) error
}
func makeTs(t *testing.T, h uint64, msgcid cid.Cid) *types.TipSet {
ts, err := types.NewTipSet([]*types.BlockHeader{
{
Height: h,
StateRoot: dummyCid,
Messages: msgcid,
MessageReceipts: dummyCid,
},
})
require.NoError(t, err)
return ts
}
func (fcs *fakeCS) SubscribeHeadChanges(f func(rev, app []*types.TipSet) error) {
if fcs.sub != nil {
fcs.t.Fatal("sub should be nil")
}
fcs.sub = f
}
func (fcs *fakeCS) GetHeaviestTipSet() *types.TipSet {
return fcs.tsc.best()
}
func (fcs *fakeCS) MessagesForBlock(b *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) {
ms, ok := fcs.msgs[b.Messages]
if ok {
return ms.bmsgs, ms.smsgs, nil
}
return []*types.Message{}, []*types.SignedMessage{}, nil
}
func (fcs *fakeCS) fakeMsgs(m fakeMsg) cid.Cid {
n := len(fcs.msgs)
c, err := cid.Prefix{
Version: 1,
Codec: cid.Raw,
MhType: multihash.IDENTITY,
MhLength: -1,
}.Sum([]byte(fmt.Sprintf("%d", n)))
require.NoError(fcs.t, err)
fcs.msgs[c] = m
return c
}
func (fcs *fakeCS) advance(rev, app int, msgs map[int]cid.Cid) { // todo: allow msgs
if fcs.sub == nil {
fcs.t.Fatal("sub not be nil")
}
var revs []*types.TipSet
for i := 0; i < rev; i++ {
ts := fcs.tsc.best()
revs = append(revs, ts)
fcs.h--
require.NoError(fcs.t, fcs.tsc.revert(ts))
}
apps := make([]*types.TipSet, app)
for i := 0; i < app; i++ {
fcs.h++
mc, _ := msgs[i]
if mc == cid.Undef {
mc = dummyCid
}
ts := makeTs(fcs.t, fcs.h, mc)
require.NoError(fcs.t, fcs.tsc.add(ts))
apps[app-i-1] = ts
}
err := fcs.sub(revs, apps)
require.NoError(fcs.t, err)
}
var _ eventChainStore = &fakeCS{}
func TestAt(t *testing.T) {
fcs := &fakeCS{
t: t,
h: 1,
tsc: newTSCache(2 * build.ForkLengthThreshold),
}
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
events := NewEvents(fcs)
var applied bool
var reverted bool
err := events.ChainAt(func(ts *types.TipSet, curH uint64) error {
require.Equal(t, 5, int(ts.Height()))
require.Equal(t, 8, int(curH))
applied = true
return nil
}, func(ts *types.TipSet) error {
reverted = true
return nil
}, 3, 5)
require.NoError(t, err)
fcs.advance(0, 3, nil)
require.Equal(t, false, applied)
require.Equal(t, false, reverted)
fcs.advance(0, 3, nil)
require.Equal(t, false, applied)
require.Equal(t, false, reverted)
fcs.advance(0, 3, nil)
require.Equal(t, true, applied)
require.Equal(t, false, reverted)
applied = false
fcs.advance(0, 3, nil)
require.Equal(t, false, applied)
require.Equal(t, false, reverted)
fcs.advance(10, 1, nil)
require.Equal(t, false, applied)
require.Equal(t, true, reverted)
reverted = false
fcs.advance(0, 1, nil)
require.Equal(t, false, applied)
require.Equal(t, false, reverted)
fcs.advance(0, 2, nil)
require.Equal(t, false, applied)
require.Equal(t, false, reverted)
fcs.advance(0, 1, nil) // 8
require.Equal(t, true, applied)
require.Equal(t, false, reverted)
}
func TestCalled(t *testing.T) {
fcs := &fakeCS{
t: t,
h: 1,
msgs: map[cid.Cid]fakeMsg{},
tsc: newTSCache(2 * build.ForkLengthThreshold),
}
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
events := NewEvents(fcs)
t0123, err := address.NewFromString("t0123")
require.NoError(t, err)
more := true
var applied, reverted bool
var appliedMsg *types.Message
var appliedTs *types.TipSet
var appliedH uint64
err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) {
return false, true, nil
}, func(msg *types.Message, ts *types.TipSet, curH uint64) (bool, error) {
applied = true
appliedMsg = msg
appliedTs = ts
appliedH = curH
return more, nil
}, func(ts *types.TipSet) error {
reverted = true
return nil
}, 3, 20, t0123, 5)
require.NoError(t, err)
// create few blocks to make sure nothing get's randomly called
fcs.advance(0, 4, nil) // H=5
require.Equal(t, false, applied)
require.Equal(t, false, reverted)
// create blocks with message (but below confidence threshold)
fcs.advance(0, 3, map[int]cid.Cid{ // msg at H=6; H=8 (confidence=2)
0: fcs.fakeMsgs(fakeMsg{
bmsgs: []*types.Message{
{To: t0123, Method: 5, Nonce: 1},
},
}),
})
require.Equal(t, false, applied)
require.Equal(t, false, reverted)
// create additional block so we are above confidence threshold
fcs.advance(0, 1, nil) // H=9 (confidence=3, apply)
require.Equal(t, true, applied)
require.Equal(t, false, reverted)
applied = false
require.Equal(t, uint64(6), appliedTs.Height())
require.Equal(t, "bafkqaajq", appliedTs.Blocks()[0].Messages.String())
require.Equal(t, uint64(9), appliedH)
require.Equal(t, t0123, appliedMsg.To)
require.Equal(t, uint64(1), appliedMsg.Nonce)
require.Equal(t, uint64(5), appliedMsg.Method)
// revert some blocks, keep the message
fcs.advance(3, 1, nil) // H=7 (confidence=1)
require.Equal(t, false, applied)
require.Equal(t, false, reverted)
// revert the message
fcs.advance(2, 1, nil) // H=6, we reverted ts with the msg
require.Equal(t, false, applied)
require.Equal(t, true, reverted)
reverted = false
// send new message on different height
n2msg := fcs.fakeMsgs(fakeMsg{
bmsgs: []*types.Message{
{To: t0123, Method: 5, Nonce: 2},
},
})
fcs.advance(0, 4, map[int]cid.Cid{ // msg at H=7; H=10 (confidence=3)
0: n2msg,
})
require.Equal(t, true, applied)
require.Equal(t, false, reverted)
applied = false
require.Equal(t, uint64(7), appliedTs.Height())
require.Equal(t, "bafkqaajr", appliedTs.Blocks()[0].Messages.String())
require.Equal(t, uint64(10), appliedH)
require.Equal(t, t0123, appliedMsg.To)
require.Equal(t, uint64(2), appliedMsg.Nonce)
require.Equal(t, uint64(5), appliedMsg.Method)
// revert and apply at different height
fcs.advance(4, 5, map[int]cid.Cid{ // msg at H=8; H=11 (confidence=3)
1: n2msg,
})
// TODO: We probably don't want to call revert/apply, as restarting certain
// actions may be expensive, and in this case the message is still
// on-chain, just at different height
require.Equal(t, true, applied)
require.Equal(t, true, reverted)
reverted = false
applied = false
require.Equal(t, uint64(8), appliedTs.Height())
require.Equal(t, "bafkqaajr", appliedTs.Blocks()[0].Messages.String())
require.Equal(t, uint64(11), appliedH)
require.Equal(t, t0123, appliedMsg.To)
require.Equal(t, uint64(2), appliedMsg.Nonce)
require.Equal(t, uint64(5), appliedMsg.Method)
// call method again
fcs.advance(0, 4, map[int]cid.Cid{ // msg at H=12; H=15
0: n2msg,
})
require.Equal(t, true, applied)
require.Equal(t, false, reverted)
applied = false
// send and revert below confidence, then cross confidence
fcs.advance(0, 1, map[int]cid.Cid{ // msg at H=16; H=16
0: fcs.fakeMsgs(fakeMsg{
bmsgs: []*types.Message{
{To: t0123, Method: 5, Nonce: 3},
},
}),
})
fcs.advance(1, 4, nil) // H=19, but message reverted
require.Equal(t, false, applied)
require.Equal(t, false, reverted)
// test timeout (it's set to 20 in the call to `events.Called` above)
fcs.advance(0, 6, nil) // H=25
require.Equal(t, false, applied) // not calling timeout as we received messages
require.Equal(t, false, reverted)
// test unregistering with more
more = false
fcs.advance(0, 4, map[int]cid.Cid{ // msg at H=26; H=29
0: fcs.fakeMsgs(fakeMsg{
bmsgs: []*types.Message{
{To: t0123, Method: 5, Nonce: 4}, // this signals we don't want more
},
}),
})
require.Equal(t, true, applied)
require.Equal(t, false, reverted)
applied = false
fcs.advance(0, 4, map[int]cid.Cid{ // msg at H=26; H=29
0: fcs.fakeMsgs(fakeMsg{
bmsgs: []*types.Message{
{To: t0123, Method: 5, Nonce: 5},
},
}),
})
require.Equal(t, false, applied) // should not get any further notifications
require.Equal(t, false, reverted)
// revert after disabled
fcs.advance(5, 1, nil) // try reverting msg sent after disabling
require.Equal(t, false, applied)
require.Equal(t, false, reverted)
fcs.advance(5, 1, nil) // try reverting msg sent before disabling
require.Equal(t, false, applied)
require.Equal(t, true, reverted)
}
func TestCalledTimeout(t *testing.T) {
fcs := &fakeCS{
t: t,
h: 1,
msgs: map[cid.Cid]fakeMsg{},
tsc: newTSCache(2 * build.ForkLengthThreshold),
}
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
events := NewEvents(fcs)
t0123, err := address.NewFromString("t0123")
require.NoError(t, err)
called := false
err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) {
return false, true, nil
}, func(msg *types.Message, ts *types.TipSet, curH uint64) (bool, error) {
called = true
require.Nil(t, msg)
require.Equal(t, uint64(20), ts.Height())
require.Equal(t, uint64(23), curH)
return false, nil
}, func(ts *types.TipSet) error {
t.Fatal("revert on timeout")
return nil
}, 3, 20, t0123, 5)
require.NoError(t, err)
fcs.advance(0, 21, nil)
require.False(t, called)
fcs.advance(0, 5, nil)
require.True(t, called)
called = false
// with check func reporting done
fcs = &fakeCS{
t: t,
h: 1,
msgs: map[cid.Cid]fakeMsg{},
tsc: newTSCache(2 * build.ForkLengthThreshold),
}
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
events = NewEvents(fcs)
err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) {
return true, true, nil
}, func(msg *types.Message, ts *types.TipSet, curH uint64) (bool, error) {
called = true
require.Nil(t, msg)
require.Equal(t, uint64(20), ts.Height())
require.Equal(t, uint64(23), curH)
return false, nil
}, func(ts *types.TipSet) error {
t.Fatal("revert on timeout")
return nil
}, 3, 20, t0123, 5)
require.NoError(t, err)
fcs.advance(0, 21, nil)
require.False(t, called)
fcs.advance(0, 5, nil)
require.False(t, called)
}
func TestCalledOrder(t *testing.T) {
fcs := &fakeCS{
t: t,
h: 1,
msgs: map[cid.Cid]fakeMsg{},
tsc: newTSCache(2 * build.ForkLengthThreshold),
}
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
events := NewEvents(fcs)
t0123, err := address.NewFromString("t0123")
require.NoError(t, err)
at := 0
err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) {
return false, true, nil
}, func(msg *types.Message, ts *types.TipSet, curH uint64) (bool, error) {
switch at {
case 0:
require.Equal(t, uint64(1), msg.Nonce)
require.Equal(t, uint64(3), ts.Height())
case 1:
require.Equal(t, uint64(2), msg.Nonce)
require.Equal(t, uint64(4), ts.Height())
default:
t.Fatal("apply should only get called twice, at: ", at)
}
at++
return true, nil
}, func(ts *types.TipSet) error {
switch at {
case 2:
require.Equal(t, uint64(4), ts.Height())
case 3:
require.Equal(t, uint64(3), ts.Height())
default:
t.Fatal("revert should only get called twice, at: ", at)
}
at++
return nil
}, 3, 20, t0123, 5)
require.NoError(t, err)
fcs.advance(0, 10, map[int]cid.Cid{
1: fcs.fakeMsgs(fakeMsg{
bmsgs: []*types.Message{
{To: t0123, Method: 5, Nonce: 1},
},
}),
2: fcs.fakeMsgs(fakeMsg{
bmsgs: []*types.Message{
{To: t0123, Method: 5, Nonce: 2},
},
}),
})
fcs.advance(9, 1, nil)
}