Add filter block range tests
This commit is contained in:
parent
061e8cca1f
commit
6f66ef595e
@ -10,6 +10,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -82,7 +83,7 @@ var EventsContract = SolidityContractDef{
|
|||||||
func TestEthNewPendingTransactionFilter(t *testing.T) {
|
func TestEthNewPendingTransactionFilter(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
kit.QuietMiningLogs()
|
kit.QuietAllLogsExcept("events", "messagepool")
|
||||||
|
|
||||||
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI())
|
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI())
|
||||||
ens.InterconnectAll().BeginMining(10 * time.Millisecond)
|
ens.InterconnectAll().BeginMining(10 * time.Millisecond)
|
||||||
@ -179,7 +180,7 @@ func TestEthNewPendingTransactionFilter(t *testing.T) {
|
|||||||
func TestEthNewBlockFilter(t *testing.T) {
|
func TestEthNewBlockFilter(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
kit.QuietMiningLogs()
|
kit.QuietAllLogsExcept("events", "messagepool")
|
||||||
|
|
||||||
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI())
|
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI())
|
||||||
ens.InterconnectAll().BeginMining(10 * time.Millisecond)
|
ens.InterconnectAll().BeginMining(10 * time.Millisecond)
|
||||||
@ -277,7 +278,7 @@ func TestEthNewBlockFilter(t *testing.T) {
|
|||||||
func TestEthNewFilterCatchAll(t *testing.T) {
|
func TestEthNewFilterCatchAll(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
kit.QuietMiningLogs()
|
kit.QuietAllLogsExcept("events", "messagepool")
|
||||||
|
|
||||||
blockTime := 100 * time.Millisecond
|
blockTime := 100 * time.Millisecond
|
||||||
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI())
|
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI())
|
||||||
@ -426,166 +427,9 @@ func TestEthNewFilterCatchAll(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseEthLogsFromSubscriptionResponses(subResponses []ethtypes.EthSubscriptionResponse) ([]*ethtypes.EthLog, error) {
|
|
||||||
elogs := make([]*ethtypes.EthLog, 0, len(subResponses))
|
|
||||||
for i := range subResponses {
|
|
||||||
rlist, ok := subResponses[i].Result.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, xerrors.Errorf("expected subscription result to be []interface{}, but was %T", subResponses[i].Result)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range rlist {
|
|
||||||
rmap, ok := r.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, xerrors.Errorf("expected subscription result entry to be map[string]interface{}, but was %T", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
elog, err := ParseEthLog(rmap)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
elogs = append(elogs, elog)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return elogs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseEthLogsFromFilterResult(res *ethtypes.EthFilterResult) ([]*ethtypes.EthLog, error) {
|
|
||||||
elogs := make([]*ethtypes.EthLog, 0, len(res.Results))
|
|
||||||
|
|
||||||
for _, r := range res.Results {
|
|
||||||
rmap, ok := r.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, xerrors.Errorf("expected filter result entry to be map[string]interface{}, but was %T", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
elog, err := ParseEthLog(rmap)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
elogs = append(elogs, elog)
|
|
||||||
}
|
|
||||||
|
|
||||||
return elogs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseEthLog(in map[string]interface{}) (*ethtypes.EthLog, error) {
|
|
||||||
el := ðtypes.EthLog{}
|
|
||||||
|
|
||||||
ethHash := func(k string, v interface{}) (ethtypes.EthHash, error) {
|
|
||||||
s, ok := v.(string)
|
|
||||||
if !ok {
|
|
||||||
return ethtypes.EthHash{}, xerrors.Errorf(k + " not a string")
|
|
||||||
}
|
|
||||||
return ethtypes.ParseEthHash(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
ethUint64 := func(k string, v interface{}) (ethtypes.EthUint64, error) {
|
|
||||||
s, ok := v.(string)
|
|
||||||
if !ok {
|
|
||||||
return 0, xerrors.Errorf(k + " not a string")
|
|
||||||
}
|
|
||||||
parsedInt, err := strconv.ParseUint(strings.Replace(s, "0x", "", -1), 16, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return ethtypes.EthUint64(parsedInt), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
for k, v := range in {
|
|
||||||
switch k {
|
|
||||||
case "removed":
|
|
||||||
b, ok := v.(bool)
|
|
||||||
if ok {
|
|
||||||
el.Removed = b
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s, ok := v.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, xerrors.Errorf(k + ": not a string")
|
|
||||||
}
|
|
||||||
el.Removed, err = strconv.ParseBool(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("%s: %w", k, err)
|
|
||||||
}
|
|
||||||
case "address":
|
|
||||||
s, ok := v.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, xerrors.Errorf(k + ": not a string")
|
|
||||||
}
|
|
||||||
el.Address, err = ethtypes.ParseEthAddress(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("%s: %w", k, err)
|
|
||||||
}
|
|
||||||
case "logIndex":
|
|
||||||
el.LogIndex, err = ethUint64(k, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("%s: %w", k, err)
|
|
||||||
}
|
|
||||||
case "transactionIndex":
|
|
||||||
el.TransactionIndex, err = ethUint64(k, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("%s: %w", k, err)
|
|
||||||
}
|
|
||||||
case "blockNumber":
|
|
||||||
el.BlockNumber, err = ethUint64(k, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("%s: %w", k, err)
|
|
||||||
}
|
|
||||||
case "transactionHash":
|
|
||||||
el.TransactionHash, err = ethHash(k, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("%s: %w", k, err)
|
|
||||||
}
|
|
||||||
case "blockHash":
|
|
||||||
el.BlockHash, err = ethHash(k, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("%s: %w", k, err)
|
|
||||||
}
|
|
||||||
case "data":
|
|
||||||
s, ok := v.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, xerrors.Errorf(k + ": not a string")
|
|
||||||
}
|
|
||||||
data, err := hex.DecodeString(s[2:])
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("%s: %w", k, err)
|
|
||||||
}
|
|
||||||
el.Data = data
|
|
||||||
|
|
||||||
case "topics":
|
|
||||||
s, ok := v.(string)
|
|
||||||
if ok {
|
|
||||||
topic, err := hex.DecodeString(s[2:])
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("%s: %w", k, err)
|
|
||||||
}
|
|
||||||
el.Topics = append(el.Topics, topic)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
sl, ok := v.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, xerrors.Errorf(k + ": not a slice")
|
|
||||||
}
|
|
||||||
for _, s := range sl {
|
|
||||||
topic, err := hex.DecodeString(s.(string)[2:])
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("%s: %w", k, err)
|
|
||||||
}
|
|
||||||
el.Topics = append(el.Topics, topic)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return el, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEthGetLogsAll(t *testing.T) {
|
func TestEthGetLogsAll(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
kit.QuietMiningLogs()
|
kit.QuietAllLogsExcept("events", "messagepool")
|
||||||
|
|
||||||
blockTime := 100 * time.Millisecond
|
blockTime := 100 * time.Millisecond
|
||||||
dbpath := filepath.Join(t.TempDir(), "actorevents.db")
|
dbpath := filepath.Join(t.TempDir(), "actorevents.db")
|
||||||
@ -630,7 +474,7 @@ func TestEthGetLogsAll(t *testing.T) {
|
|||||||
func TestEthGetLogsByTopic(t *testing.T) {
|
func TestEthGetLogsByTopic(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
kit.QuietMiningLogs()
|
kit.QuietAllLogsExcept("events", "messagepool")
|
||||||
|
|
||||||
blockTime := 100 * time.Millisecond
|
blockTime := 100 * time.Millisecond
|
||||||
dbpath := filepath.Join(t.TempDir(), "actorevents.db")
|
dbpath := filepath.Join(t.TempDir(), "actorevents.db")
|
||||||
@ -669,7 +513,7 @@ func TestEthGetLogsByTopic(t *testing.T) {
|
|||||||
func TestEthSubscribeLogs(t *testing.T) {
|
func TestEthSubscribeLogs(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
kit.QuietMiningLogs()
|
kit.QuietAllLogsExcept("events", "messagepool")
|
||||||
|
|
||||||
blockTime := 100 * time.Millisecond
|
blockTime := 100 * time.Millisecond
|
||||||
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI())
|
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI())
|
||||||
@ -729,7 +573,7 @@ func TestEthSubscribeLogs(t *testing.T) {
|
|||||||
|
|
||||||
func TestEthGetLogs(t *testing.T) {
|
func TestEthGetLogs(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
kit.QuietMiningLogs()
|
kit.QuietAllLogsExcept("events", "messagepool")
|
||||||
|
|
||||||
blockTime := 100 * time.Millisecond
|
blockTime := 100 * time.Millisecond
|
||||||
dbpath := filepath.Join(t.TempDir(), "actorevents.db")
|
dbpath := filepath.Join(t.TempDir(), "actorevents.db")
|
||||||
@ -740,6 +584,7 @@ func TestEthGetLogs(t *testing.T) {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
// Set up the test fixture with a standard list of invocations
|
||||||
contract1, contract2, messages := invokeEventMatrix(ctx, t, client)
|
contract1, contract2, messages := invokeEventMatrix(ctx, t, client)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@ -846,6 +691,15 @@ func TestEthGetLogs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Data: nil,
|
Data: nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Address: contract1,
|
||||||
|
Topics: []ethtypes.EthBytes{
|
||||||
|
EventMatrixContract.Ev["EventTwoIndexed"],
|
||||||
|
paddedUint64(40),
|
||||||
|
paddedUint64(20),
|
||||||
|
},
|
||||||
|
Data: nil,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1213,15 +1067,231 @@ func TestEthGetLogs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEthGetLogsWithBlockRanges(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
kit.QuietAllLogsExcept("events", "messagepool")
|
||||||
|
|
||||||
|
blockTime := 100 * time.Millisecond
|
||||||
|
dbpath := filepath.Join(t.TempDir(), "actorevents.db")
|
||||||
|
|
||||||
|
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.HistoricFilterAPI(dbpath))
|
||||||
|
ens.InterconnectAll().BeginMining(blockTime)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Set up the test fixture with a standard list of invocations
|
||||||
|
_, _, messages := invokeEventMatrix(ctx, t, client)
|
||||||
|
|
||||||
|
// Organize expected logs into three partitions for range testing
|
||||||
|
expectedByHeight := map[abi.ChainEpoch][]ExpectedEthLog{}
|
||||||
|
distinctHeights := map[abi.ChainEpoch]bool{}
|
||||||
|
|
||||||
|
// Select events for partitioning
|
||||||
|
for _, m := range messages {
|
||||||
|
if bytes.Equal(m.invocation.Selector, EventMatrixContract.Fn["logEventTwoIndexedWithData"]) {
|
||||||
|
addr := getContractEthAddress(ctx, t, client, m.invocation.Target)
|
||||||
|
args := unpackUint64Values(m.invocation.Data)
|
||||||
|
require.Equal(3, len(args), "logEventTwoIndexedWithData should have 3 arguments")
|
||||||
|
|
||||||
|
distinctHeights[m.ts.Height()] = true
|
||||||
|
expectedByHeight[m.ts.Height()] = append(expectedByHeight[m.ts.Height()], ExpectedEthLog{
|
||||||
|
Address: addr,
|
||||||
|
Topics: []ethtypes.EthBytes{
|
||||||
|
EventMatrixContract.Ev["EventTwoIndexedWithData"],
|
||||||
|
paddedUint64(args[0]),
|
||||||
|
paddedUint64(args[1]),
|
||||||
|
},
|
||||||
|
Data: paddedUint64(args[2]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Divide heights into 3 partitions, they don't have to be equal
|
||||||
|
require.True(len(distinctHeights) >= 3, "expected slice should divisible into three partitions")
|
||||||
|
heights := make([]abi.ChainEpoch, 0, len(distinctHeights))
|
||||||
|
for h := range distinctHeights {
|
||||||
|
heights = append(heights, h)
|
||||||
|
}
|
||||||
|
sort.Slice(heights, func(i, j int) bool {
|
||||||
|
return heights[i] < heights[j]
|
||||||
|
})
|
||||||
|
heightsPerPartition := len(heights) / 3
|
||||||
|
|
||||||
|
type partition struct {
|
||||||
|
start abi.ChainEpoch
|
||||||
|
end abi.ChainEpoch
|
||||||
|
expected []ExpectedEthLog
|
||||||
|
}
|
||||||
|
|
||||||
|
var partition1, partition2, partition3 partition
|
||||||
|
|
||||||
|
partition1.start = heights[0]
|
||||||
|
partition1.end = heights[heightsPerPartition-1]
|
||||||
|
for e := partition1.start; e <= partition1.end; e++ {
|
||||||
|
exp, ok := expectedByHeight[e]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
partition1.expected = append(partition1.expected, exp...)
|
||||||
|
}
|
||||||
|
t.Logf("partition1 from %d to %d with %d expected", partition1.start, partition1.end, len(partition1.expected))
|
||||||
|
require.True(len(partition1.expected) > 0, "partition should have events")
|
||||||
|
|
||||||
|
partition2.start = heights[heightsPerPartition]
|
||||||
|
partition2.end = heights[heightsPerPartition*2-1]
|
||||||
|
for e := partition2.start; e <= partition2.end; e++ {
|
||||||
|
exp, ok := expectedByHeight[e]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
partition2.expected = append(partition2.expected, exp...)
|
||||||
|
}
|
||||||
|
t.Logf("partition2 from %d to %d with %d expected", partition2.start, partition2.end, len(partition2.expected))
|
||||||
|
require.True(len(partition2.expected) > 0, "partition should have events")
|
||||||
|
|
||||||
|
partition3.start = heights[heightsPerPartition*2]
|
||||||
|
partition3.end = heights[len(heights)-1]
|
||||||
|
for e := partition3.start; e <= partition3.end; e++ {
|
||||||
|
exp, ok := expectedByHeight[e]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
partition3.expected = append(partition3.expected, exp...)
|
||||||
|
}
|
||||||
|
t.Logf("partition3 from %d to %d with %d expected", partition3.start, partition3.end, len(partition3.expected))
|
||||||
|
require.True(len(partition3.expected) > 0, "partition should have events")
|
||||||
|
|
||||||
|
// these are the topics we selected for partitioning earlier
|
||||||
|
topics := []ethtypes.EthHash{paddedEthHash(EventMatrixContract.Ev["EventTwoIndexedWithData"])}
|
||||||
|
|
||||||
|
union := func(lists ...[]ExpectedEthLog) []ExpectedEthLog {
|
||||||
|
ret := []ExpectedEthLog{}
|
||||||
|
for _, list := range lists {
|
||||||
|
ret = append(ret, list...)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
spec *ethtypes.EthFilterSpec
|
||||||
|
expected []ExpectedEthLog
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "find all events from genesis",
|
||||||
|
spec: EthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(topics...).Filter(),
|
||||||
|
expected: union(partition1.expected, partition2.expected, partition3.expected),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "find all from start of partition1",
|
||||||
|
spec: EthFilterBuilder().FromBlockEpoch(partition1.start).Topic1OneOf(topics...).Filter(),
|
||||||
|
expected: union(partition1.expected, partition2.expected, partition3.expected),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "find all from start of partition2",
|
||||||
|
spec: EthFilterBuilder().FromBlockEpoch(partition2.start).Topic1OneOf(topics...).Filter(),
|
||||||
|
expected: union(partition2.expected, partition3.expected),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "find all from start of partition3",
|
||||||
|
spec: EthFilterBuilder().FromBlockEpoch(partition3.start).Topic1OneOf(topics...).Filter(),
|
||||||
|
expected: union(partition3.expected),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "find none after end of partition3",
|
||||||
|
spec: EthFilterBuilder().FromBlockEpoch(partition3.end + 1).Topic1OneOf(topics...).Filter(),
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "find all events from genesis to end of partition1",
|
||||||
|
spec: EthFilterBuilder().FromBlockEpoch(0).ToBlockEpoch(partition1.end).Topic1OneOf(topics...).Filter(),
|
||||||
|
expected: union(partition1.expected),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "find all events from genesis to end of partition2",
|
||||||
|
spec: EthFilterBuilder().FromBlockEpoch(0).ToBlockEpoch(partition2.end).Topic1OneOf(topics...).Filter(),
|
||||||
|
expected: union(partition1.expected, partition2.expected),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "find all events from genesis to end of partition3",
|
||||||
|
spec: EthFilterBuilder().FromBlockEpoch(0).ToBlockEpoch(partition3.end).Topic1OneOf(topics...).Filter(),
|
||||||
|
expected: union(partition1.expected, partition2.expected, partition3.expected),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "find none from genesis to start of partition1",
|
||||||
|
spec: EthFilterBuilder().FromBlockEpoch(0).ToBlockEpoch(partition1.start - 1).Topic1OneOf(topics...).Filter(),
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "find all events in partition1",
|
||||||
|
spec: EthFilterBuilder().FromBlockEpoch(partition1.start).ToBlockEpoch(partition1.end).Topic1OneOf(topics...).Filter(),
|
||||||
|
expected: union(partition1.expected),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "find all events in partition2",
|
||||||
|
spec: EthFilterBuilder().FromBlockEpoch(partition2.start).ToBlockEpoch(partition2.end).Topic1OneOf(topics...).Filter(),
|
||||||
|
expected: union(partition2.expected),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "find all events in partition3",
|
||||||
|
spec: EthFilterBuilder().FromBlockEpoch(partition3.start).ToBlockEpoch(partition3.end).Topic1OneOf(topics...).Filter(),
|
||||||
|
expected: union(partition3.expected),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "find all events from earliest to end of partition1",
|
||||||
|
spec: EthFilterBuilder().FromBlock("earliest").ToBlockEpoch(partition1.end).Topic1OneOf(topics...).Filter(),
|
||||||
|
expected: union(partition1.expected),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "find all events from start of partition3 to latest",
|
||||||
|
spec: EthFilterBuilder().FromBlockEpoch(partition3.start).ToBlock("latest").Topic1OneOf(topics...).Filter(),
|
||||||
|
expected: union(partition3.expected),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "find all events from earliest to latest",
|
||||||
|
spec: EthFilterBuilder().FromBlock("earliest").ToBlock("latest").Topic1OneOf(topics...).Filter(),
|
||||||
|
expected: union(partition1.expected, partition2.expected, partition3.expected),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc // appease the lint despot
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
res, err := client.EthGetLogs(ctx, tc.spec)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
elogs, err := parseEthLogsFromFilterResult(res)
|
||||||
|
require.NoError(err)
|
||||||
|
AssertEthLogs(t, elogs, tc.expected, messages)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------
|
||||||
// end of tests
|
// end of tests
|
||||||
// -------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------
|
||||||
|
|
||||||
type msgInTipset struct {
|
type msgInTipset struct {
|
||||||
msg api.Message
|
invocation Invocation // the solidity invocation that generated this message
|
||||||
events []types.Event // events extracted from receipt
|
msg api.Message
|
||||||
ts *types.TipSet
|
events []types.Event // events extracted from receipt
|
||||||
reverted bool
|
ts *types.TipSet
|
||||||
|
reverted bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func installContract(ctx context.Context, t *testing.T, client *kit.TestFullNode, filename string) (address.Address, address.Address) {
|
func installContract(ctx context.Context, t *testing.T, client *kit.TestFullNode, filename string) (address.Address, address.Address) {
|
||||||
@ -1236,11 +1306,11 @@ func installContract(ctx context.Context, t *testing.T, client *kit.TestFullNode
|
|||||||
return sender, actorAddr
|
return sender, actorAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContractEthAddress(ctx context.Context, t *testing.T, client *kit.TestFullNode, idAddr address.Address) ethtypes.EthAddress {
|
func getContractEthAddress(ctx context.Context, t *testing.T, client *kit.TestFullNode, addr address.Address) ethtypes.EthAddress {
|
||||||
head, err := client.ChainHead(ctx)
|
head, err := client.ChainHead(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
actor, err := client.StateGetActor(ctx, idAddr, head.Key())
|
actor, err := client.StateGetActor(ctx, addr, head.Key())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, actor.Address)
|
require.NotNil(t, actor.Address)
|
||||||
ethContractAddr, err := ethtypes.EthAddressFromFilecoinAddress(*actor.Address)
|
ethContractAddr, err := ethtypes.EthAddressFromFilecoinAddress(*actor.Address)
|
||||||
@ -1316,7 +1386,8 @@ func invokeAndWaitUntilAllOnChain(t *testing.T, client *kit.TestFullNode, invoca
|
|||||||
t.Fatalf("timeout waiting for first head change")
|
t.Fatalf("timeout waiting for first head change")
|
||||||
}
|
}
|
||||||
|
|
||||||
events := map[cid.Cid][]types.Event{}
|
eventMap := map[cid.Cid][]types.Event{}
|
||||||
|
invocationMap := map[cid.Cid]Invocation{}
|
||||||
for _, inv := range invocations {
|
for _, inv := range invocations {
|
||||||
if inv.MinHeight > 0 {
|
if inv.MinHeight > 0 {
|
||||||
for {
|
for {
|
||||||
@ -1335,10 +1406,12 @@ func invokeAndWaitUntilAllOnChain(t *testing.T, client *kit.TestFullNode, invoca
|
|||||||
ret := client.EVM().InvokeSolidity(ctx, inv.Sender, inv.Target, inv.Selector, inv.Data)
|
ret := client.EVM().InvokeSolidity(ctx, inv.Sender, inv.Target, inv.Selector, inv.Data)
|
||||||
require.True(ret.Receipt.ExitCode.IsSuccess(), "contract execution failed")
|
require.True(ret.Receipt.ExitCode.IsSuccess(), "contract execution failed")
|
||||||
|
|
||||||
|
invocationMap[ret.Message] = inv
|
||||||
|
|
||||||
require.NotNil(t, ret.Receipt.EventsRoot, "no event root on receipt")
|
require.NotNil(t, ret.Receipt.EventsRoot, "no event root on receipt")
|
||||||
|
|
||||||
evs := client.EVM().LoadEvents(ctx, *ret.Receipt.EventsRoot)
|
evs := client.EVM().LoadEvents(ctx, *ret.Receipt.EventsRoot)
|
||||||
events[ret.Message] = evs
|
eventMap[ret.Message] = evs
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@ -1349,7 +1422,11 @@ func invokeAndWaitUntilAllOnChain(t *testing.T, client *kit.TestFullNode, invoca
|
|||||||
|
|
||||||
received := make(map[ethtypes.EthHash]msgInTipset)
|
received := make(map[ethtypes.EthHash]msgInTipset)
|
||||||
for m := range msgChan {
|
for m := range msgChan {
|
||||||
evs, ok := events[m.msg.Cid]
|
inv, ok := invocationMap[m.msg.Cid]
|
||||||
|
require.True(ok)
|
||||||
|
m.invocation = inv
|
||||||
|
|
||||||
|
evs, ok := eventMap[m.msg.Cid]
|
||||||
require.True(ok)
|
require.True(ok)
|
||||||
m.events = evs
|
m.events = evs
|
||||||
|
|
||||||
@ -1424,7 +1501,7 @@ func invokeEventMatrix(ctx context.Context, t *testing.T, client *kit.TestFullNo
|
|||||||
// topic2: 44
|
// topic2: 44
|
||||||
// topic3: 19
|
// topic3: 19
|
||||||
{
|
{
|
||||||
Sender: sender1,
|
Sender: sender2,
|
||||||
Target: contract2,
|
Target: contract2,
|
||||||
Selector: EventMatrixContract.Fn["logEventTwoIndexed"],
|
Selector: EventMatrixContract.Fn["logEventTwoIndexed"],
|
||||||
Data: packUint64Values(44, 19),
|
Data: packUint64Values(44, 19),
|
||||||
@ -1562,6 +1639,16 @@ func invokeEventMatrix(ctx context.Context, t *testing.T, client *kit.TestFullNo
|
|||||||
Selector: EventMatrixContract.Fn["logEventTwoIndexedWithData"],
|
Selector: EventMatrixContract.Fn["logEventTwoIndexedWithData"],
|
||||||
Data: packUint64Values(46, 14, 19),
|
Data: packUint64Values(46, 14, 19),
|
||||||
},
|
},
|
||||||
|
// log EventTwoIndexed(44,19) from contract1
|
||||||
|
// topic1: hash(EventTwoIndexed)
|
||||||
|
// topic2: 44
|
||||||
|
// topic3: 19
|
||||||
|
{
|
||||||
|
Sender: sender1,
|
||||||
|
Target: contract1,
|
||||||
|
Selector: EventMatrixContract.Fn["logEventTwoIndexed"],
|
||||||
|
Data: packUint64Values(40, 20),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
messages := invokeAndWaitUntilAllOnChain(t, client, invocations)
|
messages := invokeAndWaitUntilAllOnChain(t, client, invocations)
|
||||||
@ -1641,7 +1728,7 @@ func AssertEthLogs(t *testing.T, actual []*ethtypes.EthLog, expected []ExpectedE
|
|||||||
|
|
||||||
if !matched {
|
if !matched {
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
buf.WriteString("found unexpected log:\n")
|
buf.WriteString(fmt.Sprintf("found unexpected log at height %d:\n", msg.ts.Height()))
|
||||||
buf.WriteString(fmt.Sprintf(" address: %s\n", elog.Address))
|
buf.WriteString(fmt.Sprintf(" address: %s\n", elog.Address))
|
||||||
buf.WriteString(fmt.Sprintf(" topics: %s\n", formatTopics(elog.Topics)))
|
buf.WriteString(fmt.Sprintf(" topics: %s\n", formatTopics(elog.Topics)))
|
||||||
buf.WriteString(fmt.Sprintf(" data: %x\n", elog.Data))
|
buf.WriteString(fmt.Sprintf(" data: %x\n", elog.Data))
|
||||||
@ -1670,6 +1757,163 @@ func AssertEthLogs(t *testing.T, actual []*ethtypes.EthLog, expected []ExpectedE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseEthLogsFromSubscriptionResponses(subResponses []ethtypes.EthSubscriptionResponse) ([]*ethtypes.EthLog, error) {
|
||||||
|
elogs := make([]*ethtypes.EthLog, 0, len(subResponses))
|
||||||
|
for i := range subResponses {
|
||||||
|
rlist, ok := subResponses[i].Result.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, xerrors.Errorf("expected subscription result to be []interface{}, but was %T", subResponses[i].Result)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range rlist {
|
||||||
|
rmap, ok := r.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, xerrors.Errorf("expected subscription result entry to be map[string]interface{}, but was %T", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
elog, err := ParseEthLog(rmap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
elogs = append(elogs, elog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return elogs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEthLogsFromFilterResult(res *ethtypes.EthFilterResult) ([]*ethtypes.EthLog, error) {
|
||||||
|
elogs := make([]*ethtypes.EthLog, 0, len(res.Results))
|
||||||
|
|
||||||
|
for _, r := range res.Results {
|
||||||
|
rmap, ok := r.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, xerrors.Errorf("expected filter result entry to be map[string]interface{}, but was %T", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
elog, err := ParseEthLog(rmap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
elogs = append(elogs, elog)
|
||||||
|
}
|
||||||
|
|
||||||
|
return elogs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseEthLog(in map[string]interface{}) (*ethtypes.EthLog, error) {
|
||||||
|
el := ðtypes.EthLog{}
|
||||||
|
|
||||||
|
ethHash := func(k string, v interface{}) (ethtypes.EthHash, error) {
|
||||||
|
s, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return ethtypes.EthHash{}, xerrors.Errorf(k + " not a string")
|
||||||
|
}
|
||||||
|
return ethtypes.ParseEthHash(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
ethUint64 := func(k string, v interface{}) (ethtypes.EthUint64, error) {
|
||||||
|
s, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return 0, xerrors.Errorf(k + " not a string")
|
||||||
|
}
|
||||||
|
parsedInt, err := strconv.ParseUint(strings.Replace(s, "0x", "", -1), 16, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return ethtypes.EthUint64(parsedInt), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for k, v := range in {
|
||||||
|
switch k {
|
||||||
|
case "removed":
|
||||||
|
b, ok := v.(bool)
|
||||||
|
if ok {
|
||||||
|
el.Removed = b
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, xerrors.Errorf(k + ": not a string")
|
||||||
|
}
|
||||||
|
el.Removed, err = strconv.ParseBool(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("%s: %w", k, err)
|
||||||
|
}
|
||||||
|
case "address":
|
||||||
|
s, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, xerrors.Errorf(k + ": not a string")
|
||||||
|
}
|
||||||
|
el.Address, err = ethtypes.ParseEthAddress(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("%s: %w", k, err)
|
||||||
|
}
|
||||||
|
case "logIndex":
|
||||||
|
el.LogIndex, err = ethUint64(k, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("%s: %w", k, err)
|
||||||
|
}
|
||||||
|
case "transactionIndex":
|
||||||
|
el.TransactionIndex, err = ethUint64(k, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("%s: %w", k, err)
|
||||||
|
}
|
||||||
|
case "blockNumber":
|
||||||
|
el.BlockNumber, err = ethUint64(k, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("%s: %w", k, err)
|
||||||
|
}
|
||||||
|
case "transactionHash":
|
||||||
|
el.TransactionHash, err = ethHash(k, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("%s: %w", k, err)
|
||||||
|
}
|
||||||
|
case "blockHash":
|
||||||
|
el.BlockHash, err = ethHash(k, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("%s: %w", k, err)
|
||||||
|
}
|
||||||
|
case "data":
|
||||||
|
s, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, xerrors.Errorf(k + ": not a string")
|
||||||
|
}
|
||||||
|
data, err := hex.DecodeString(s[2:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("%s: %w", k, err)
|
||||||
|
}
|
||||||
|
el.Data = data
|
||||||
|
|
||||||
|
case "topics":
|
||||||
|
s, ok := v.(string)
|
||||||
|
if ok {
|
||||||
|
topic, err := hex.DecodeString(s[2:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("%s: %w", k, err)
|
||||||
|
}
|
||||||
|
el.Topics = append(el.Topics, topic)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sl, ok := v.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, xerrors.Errorf(k + ": not a slice")
|
||||||
|
}
|
||||||
|
for _, s := range sl {
|
||||||
|
topic, err := hex.DecodeString(s.(string)[2:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("%s: %w", k, err)
|
||||||
|
}
|
||||||
|
el.Topics = append(el.Topics, topic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return el, err
|
||||||
|
}
|
||||||
|
|
||||||
func decodeLogBytes(orig []byte) []byte {
|
func decodeLogBytes(orig []byte) []byte {
|
||||||
if orig == nil {
|
if orig == nil {
|
||||||
return orig
|
return orig
|
||||||
@ -1728,6 +1972,19 @@ func packUint64Values(vals ...uint64) []byte {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unpackUint64Values(data []byte) []uint64 {
|
||||||
|
if len(data)%32 != 0 {
|
||||||
|
panic("data length not a multiple of 32")
|
||||||
|
}
|
||||||
|
|
||||||
|
var vals []uint64
|
||||||
|
for i := 0; i < len(data); i += 32 {
|
||||||
|
v := binary.BigEndian.Uint64(data[i+24 : i+32])
|
||||||
|
vals = append(vals, v)
|
||||||
|
}
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
func EthFilterBuilder() *ethFilterBuilder { return ðFilterBuilder{} }
|
func EthFilterBuilder() *ethFilterBuilder { return ðFilterBuilder{} }
|
||||||
|
|
||||||
type ethFilterBuilder struct {
|
type ethFilterBuilder struct {
|
||||||
@ -1741,7 +1998,7 @@ func (e *ethFilterBuilder) FromBlock(v string) *ethFilterBuilder {
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ethFilterBuilder) FromBlockEpoch(v uint64) *ethFilterBuilder {
|
func (e *ethFilterBuilder) FromBlockEpoch(v abi.ChainEpoch) *ethFilterBuilder {
|
||||||
s := ethtypes.EthUint64(v).Hex()
|
s := ethtypes.EthUint64(v).Hex()
|
||||||
e.filter.FromBlock = &s
|
e.filter.FromBlock = &s
|
||||||
return e
|
return e
|
||||||
@ -1752,7 +2009,7 @@ func (e *ethFilterBuilder) ToBlock(v string) *ethFilterBuilder {
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ethFilterBuilder) ToBlockEpoch(v uint64) *ethFilterBuilder {
|
func (e *ethFilterBuilder) ToBlockEpoch(v abi.ChainEpoch) *ethFilterBuilder {
|
||||||
s := ethtypes.EthUint64(v).Hex()
|
s := ethtypes.EthUint64(v).Hex()
|
||||||
e.filter.ToBlock = &s
|
e.filter.ToBlock = &s
|
||||||
return e
|
return e
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package kit
|
package kit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
|
||||||
logging "github.com/ipfs/go-log/v2"
|
logging "github.com/ipfs/go-log/v2"
|
||||||
|
|
||||||
"github.com/filecoin-project/lotus/lib/lotuslog"
|
"github.com/filecoin-project/lotus/lib/lotuslog"
|
||||||
@ -20,3 +23,13 @@ func QuietMiningLogs() {
|
|||||||
_ = logging.SetLogLevel("rpc", "ERROR")
|
_ = logging.SetLogLevel("rpc", "ERROR")
|
||||||
_ = logging.SetLogLevel("dht/RtRefreshManager", "ERROR")
|
_ = logging.SetLogLevel("dht/RtRefreshManager", "ERROR")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func QuietAllLogsExcept(names ...string) {
|
||||||
|
log.SetOutput(io.Discard) // suppress LogDatastore messages
|
||||||
|
|
||||||
|
lotuslog.SetupLogLevels()
|
||||||
|
logging.SetAllLoggers(logging.LevelError)
|
||||||
|
for _, name := range names {
|
||||||
|
_ = logging.SetLogLevel(name, "INFO")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -885,18 +885,20 @@ func (e *EthEvent) installEthFilterSpec(ctx context.Context, filterSpec *ethtype
|
|||||||
// Here the client is looking for events between the head and some future height
|
// Here the client is looking for events between the head and some future height
|
||||||
ts := e.Chain.GetHeaviestTipSet()
|
ts := e.Chain.GetHeaviestTipSet()
|
||||||
if maxHeight-ts.Height() > e.MaxFilterHeightRange {
|
if maxHeight-ts.Height() > e.MaxFilterHeightRange {
|
||||||
return nil, xerrors.Errorf("invalid epoch range")
|
return nil, xerrors.Errorf("invalid epoch range: to block is too far in the future (maximum: %d)", e.MaxFilterHeightRange)
|
||||||
}
|
}
|
||||||
} else if minHeight >= 0 && maxHeight == -1 {
|
} else if minHeight >= 0 && maxHeight == -1 {
|
||||||
// Here the client is looking for events between some time in the past and the current head
|
// Here the client is looking for events between some time in the past and the current head
|
||||||
ts := e.Chain.GetHeaviestTipSet()
|
ts := e.Chain.GetHeaviestTipSet()
|
||||||
if ts.Height()-minHeight > e.MaxFilterHeightRange {
|
if ts.Height()-minHeight > e.MaxFilterHeightRange {
|
||||||
return nil, xerrors.Errorf("invalid epoch range")
|
return nil, xerrors.Errorf("invalid epoch range: from block is too far in the past (maximum: %d)", e.MaxFilterHeightRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if minHeight >= 0 && maxHeight >= 0 {
|
} else if minHeight >= 0 && maxHeight >= 0 {
|
||||||
if minHeight > maxHeight || maxHeight-minHeight > e.MaxFilterHeightRange {
|
if minHeight > maxHeight {
|
||||||
return nil, xerrors.Errorf("invalid epoch range")
|
return nil, xerrors.Errorf("invalid epoch range: to block (%d) must be after from block (%d)", minHeight, maxHeight)
|
||||||
|
} else if maxHeight-minHeight > e.MaxFilterHeightRange {
|
||||||
|
return nil, xerrors.Errorf("invalid epoch range: range between to and from blocks is too large (maximum: %d)", e.MaxFilterHeightRange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user